anti scroll

ブラウザと小説の新しい関係を模索する

nehan.jsのプラグイン紹介

nehan.jsリポジトリpluginsというディレクトリを追加しました。

最初に追加したプラグインは以下の5つです。

nehan.font-awesome.js

先日のエントリでも紹介しましたが、font-awesomeのアイコンを短く書けるプラグイン

<fa name="user">
<fa name="star">

nehan.gravator.js

emailを指定すると、gravatorのアイコンを出してくれるプラグイン

<gravator email="sample@example.com" size="64">

nehan.tip.js

いったん内容を表示しないで、クリックしたらポップアップで教えるチップを表示するプラグイン

<tip title="クリックしたら出るよ">クリックしたら表示したい内容だよ</tip>

nehan.pasted.js

外部のwidgetを貼付けたい時など、場合に酔っては内容をnehan.jsに組版させず、そのまま貼付けたいときがあります。

それをnehan.jsで指定する為の特別な属性が「pasted」なのですが、それをdivを使わずに短く書けるようにするタグです。

<!-- こんな風に書くのはメンドイ -->
<div style="width:100px;height:100px" pasted>
  そのまま貼付けたい内容
</div>

<!-- この方が短い -->
<pasted size="100x100">
  そのまま貼付けたい内容
</pasted>

nehan.speak.js

脚本のように、発言者と発言をわけて書くのに使えるタグです。

画像を指定することも、発言者の名前を文字列で指定することも出来ます。

両方指定された時は画像が優先されます。

<speak name="太郎">僕は太郎です。</speak>
<speak src="/path/to/jigo.png" size="64">いいえ、僕が次郎です。</speak>

表示すると例えばこんな感じのレイアウトに出来ます。

f:id:convertical:20140701141856p:plain

プラグインの使い方

nehan.jsの読み込みの後に、使いたいプラグインのソースを読み込むだけです。

<script type="text/javascript" src="/path/to/nehan.js"></script>
<!-- nehan.font-awesome.js を使う -->
<script type="text/javascript" src="/path/to/nehan.font-awesome.js"></script>

ただしプラグインによっては、それ以外に読み込むものが必要な場合もあります。

その辺は、各プラグインのREADMEを参照してください。

まとめ

追加したプラグインは、それぞれがnehan.jsの異なる機能を使って実現しています。

プラグインを作る際の参考にでもなれば、と思います。

Nehan.createPagedElementの紹介

nehan.jsでは、組版エンジンを扱う入り口として、主にPageStreamというものを提供しています。

ただしこれは柔軟に扱える反面、はじめての利用者にとっては直感的じゃなかったかもしれませんでした。

そこで、nehan.jsのversion5.0.3からは、より扱いやすい抽象化としてPagedElementというものが利用できるようになりました。

Nehan.createPagedElementで作成できるのですが、イメージとしてはdocument.createElementに近いものです。

PagedElementはDOM elementを内包しますが、通常のDOMと違って内部に複数ページを持っていて、それらを切り替えられる点が異なります。

PagedElementの作成

Nehan.createPagedElementで作成します。

var paged_element = Nehan.createPagedElement();

スタイルの設定

続いて、ページのサイズをsetStyleでセットします。

setStyleの第1引数は、セレクターです。

"body"がページのルートスタイルになります。

paged_element.setStyle("body", {
  "flow":"tb-rl", // 縦書きモード。横書きなら"lr-tb"にする。
  "width":640,
  "height":480,
  "font-size":16 // fontSize(camel case)とは書けない。
});

cssのプロパティとして、camel caseが使えないことに注意してください(つまりfont-sizefontSizeと設定することはできない)。

なおその他の要素のスタイルも、同じようにセットできます。

paged_element.setStyle(".my-header h1", {
  "font-size":"3em"
});

ページ内容をセット

ページの内容はsetContentでセットします。

setContentした直後に組版が始まるので、上述のsetStyleを先に済ませておく必要があることに注意してください。

paged_element.setContent("<h1>hello, nehan.js</h1>");

組版結果のDOMを取得

paged_elementによって組版されるDOMは、getElementで取得できます。

var element = paged_element.getElement();

// 画面上のどこかにセット
document.getElementById("result").appendChild(element);

次ページ、前ページの描画

次ページの描画はsetNextPageで、前ページの描画はsetPrevPageです。

// 次ページボタンをクリック
document.getElementById("next-button").onclick = function(){
  paged_element.setNextPage();
};

// 前ページボタンをクリック
document.getElementById("prev-button").onclick = function(){
  paged_element.setPrevPage();
};

組版の非同期コールバック

ページを組版している最中の状況(パーセントみたいなもの)が欲しい場合は、setContentの第2引数にコールバックを指定します。

onProgressonCompleteが有効です。

paged_element.setContent("適当な内容", {
  onProgress : function(tree){
     console.log("%d page(%d percent) is done", tree.pageNo, tree.percent);
   },
  onComplete : function(time){
     console.log("finish! %f time", time);
  }
});

ページ数、アウトラインの取得

ページ数やアウトラインは、上記のonCompleteのタイミングで確定します。

なので次のように取得すると良いでしょう。

paged_element.setContent("<h1>適当な内容</h1>", {
  onComplete : function(time){
     console.log("page_count = %d", paged_element.getPageCount());
     console.log("outline element = %o", paged_element.engine.createOutlineElement());
  }
});

createOutlineElementについては、paged_element.engineという内部エンジンを経由して呼んでいる点に注意してください。

またcreateOutlineElementは、引数にコールバックオブジェクトを与えることができます。

それによって、アウトラインをクリックした時の挙動なども設定できるのですが、詳しくはnehan.jsのsection-tree-converterを参照してください。

jekyllで縦書き横書きの本が簡単に投稿できるテーマ

jekyll-nehanというものを作りました( デモを見る)。

Jekyllという静的サイトジェネレータのテーマです。

このテーマを使うと、nehan.jsを使った縦書き横書きのページ送りエントリが簡単に投稿できます。

スクリーンショット

見た目はこんな感じになります。

f:id:convertical:20140624173156p:plain

f:id:convertical:20140624173203p:plain

レスポンシブ対応

スマホ等から見ると、こんな感じに。

f:id:convertical:20140625122844p:plain

使い方

投稿記事のヘッダにbook_typeを指定するだけです。

---
layout: post
title: your awesome book
date: 2014-06-24
book_type: vert
---

book_typeには、vert(縦書き)とhori(横書き)が指定できます。

キーボードショートカット

  • j : 次ページ
  • k : 前ページ
  • left: 次ページ(vertの時), 前ページ(horiの時)
  • right: 前ページ(vertの時), 次ページ(horiの時)

reactive programming / getterは共変、setterは反変

reactive programmingについての動画(語り手がハイテンション)を見つけました。

http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote-Duality

まだ前半しかみてないのですが、途中getterとsetterを「共変(covarience)」と「反変(contravarience)」という概念を使って説明している箇所があって、あんまりピンと来なかったので、少し落ち着いて考えてみました。

というわけで以下は備忘録です。

サブタイプ

動画の人は「コーラはソーダのサブタイプ」って説明してました。

ソーダ型はコーラ型を含むので、ソーダ型で記述できた式は、ソーダの部分をコーラに置き換えても動作するはずです。

で、こういう状況を

ソーダ型 >= コーラ型

なんて書くことにします。

共変と反変

例えば型をもとに新しい型を作る型構築ルールRがあったとします。

で、これをソーダ型に適用したR(ソーダ型)と、コーラ型に適用したR(コーラ型)が、

R(ソーダ型)>= R(コーラ型)

という風に、元の関係を維持しているとき(つまりR(ソーダ型)の部分を、R(コーラ型)で置き換えることができるような状況になっているとき)、Rを「共変」と言います。

逆にR(コーラ型)の部分が、R(ソーダ型)で置き換え可能な状況になっている場合は「反変」と言います。

で、動画(の前半)で言ってることは、元のサブタイピングの関係に対して、getterの型構築子は共変だけど、setterの型構築子は反変になるから気をつけてね、ってことだと思います。

getterは共変

例えば、NumのサブタイプをIntとします。

で、Numは他にもFloatとかもサブタイプに含むものとします。

Num >= Int
Num >= Float

続いて、getterを作る型構築ルールを次のように定義します。

getter(A) = () -> A

つまり、getter(A)は型Aから「何かしたらAが返る」という関数型を生み出すものとします。これをNum/Intに当てはめると

  • getter(Num):() -> Num
  • getter(Int): () -> Int

となります。

さて、プログラムの中に、getter(Num)が求められている処理があったとして、それをgetter(Int)に置き換えることはできるでしょうか。

これは問題ありません。

なぜならgetter(Num)を処理できる式は、getter(Num)の返り値となるNum型を処理できるのであり、そこがInt型を返すgetter(Int)に置き換わったとしても、問題なく処理できるはずだからです。

つまり getter(Num)な部分は、getter(Int)で置き換え可能なので、

getter(Num) >= getter(Int)

となります。

これは元のサブタイピングの関係(Num >= Int)と方向が一緒なので「それぞれの型を返す関数を作る」getter型構築子は共変(covarient)である、と言えます。

setterは反変

次にsetter型をつくる型構築ルールを次のように定義します。

setter(A) = A -> ()

型Aを受け取って何かをする、という型です。

つまり

  • setter(Num): Num -> ()
  • setter(Int): Int -> ()

このとき、setter(Num)が求められている箇所を、setter(Int)に置き換えることは出来るでしょうか。

これは出来ません。

なぜなら、Int型だけじゃなくFloat型も受け取るであろうsetter(Num)を前提に書かれた箇所を、Int型しか扱えないsetter(Int)で置き換えることはできないからです。

しかし、その逆はできるのです。

つまり、setter(Int)を受け取る部分を、setter(Num)に置き換えることは出来ます。

なぜなら、setter(Int)が受け取る型のすべて(といってもIntだけですが)を、setter(Num)は受理できるからです。

よって、型変換後の両者の対応関係は、

setter(Int) >= setter(Num)

となり、これは元のサブタイピングの関係性(Num >= Int)と方向が逆なので、「それぞれの型を受け取って何かする関数を作る」というsetter型構築子は反変(contravarient)である、と言えます。

(こんな感じの理解でいいのかな?)

参考:反変性と共変性(計算機科学)

nehan.jsの正規表現セレクタ

nehan.jsのversion5から、セレクターのエレメント名の部分が正規表現で記述できるようになっています。

サンプル

例えば、ヘッダー要素なら全てに適用したいスタイルがある時は、こんな風に書けます。

// すべてのh1〜h6
Nehan.setStyle("/h[1-6]/", {
   "font-weight":"bold"
});

// .header以下のh1〜h6
Nehan.setStyle(".header /h[1-6]/", {
   "color":"#FF0000"
});

h1h6のそれぞれにスタイルを書くよりは楽ですよね。

htmlタグにはヘッダータグのように、名前の中に意味を内包するタグもあるので、こうやって名前を正規表現でグルーピングできるのは便利だと思います。

特に自分専用のカスタムタグを作るときなんかは、重宝するかもしれません。

注意

正規表現で指定できるのは、エレメント名の部分だけです。

次のように、クラス名に該当する部分を正規表現にすることは出来ません。

// こういう指定は出来ない。
Nehan.setStyle("div/¥.(?:b|strong)/", {
  "font-weight":"bold"
});

// こういう指定なら出来る。
Nehan.setStyle("/(?:b|strong)/", {
  "font-weight":"bold"
});

nehan.jsで閉じタグの不要なタグ(single tag)を定義する

前回発表したアイコンの埋め込み特殊タグですが、こういうタグをnehan.jsでどうやって定義するのかを紹介したいと思います。

おさらいですが、先月に発表したfaタグはこんな風に宣言します。

ハートのアイコンは、<fa name="heart">

imgタグとかhrタグみたいに、閉じタグがありませんよね。

実はこういう閉じタグが不要なタグを定義する場合、事前にnehan.jsのパーサーに対し、その旨を教える必要があるのです。

じゃないと、通常のタグと同様に「閉じタグがあるもの」としてパースしてしまい、結果としてfaタグに続く全てのコンテンツがfaタグの「中身」として登録されてしまいます。

なので、こんな風に指定して、事前にfaタグが閉じタグのないタグであることをnehan.jsに教えます。

Nehan.addSingleTagByName("fa");

後は、通常のスタイル設定と一緒で、このタグの振る舞いを定義します。

Nehan.setStyle("fa", {
  display:"inline",
  width:"1em",
  height:"1em",
  onload:function(ctx){
    var markup = ctx.getMarkup();
    var icon_name = markup.getAttr("name");
    markup.setContent("<i class='fa fa-" + icon_name + "'></i>"); 
    markup.setAttr("pasted", true);
  }
});

やっていることは、

  • onloadでマークアップの読み込みをフック
  • markupの中身を通常のfont-awesomeの書式(iタグを使った書式)に書き換える
  • markupの中身をnehan.jsに組版させず、そのまま貼付けさせるために、pasted属性をセットする

というものです(縦書き文庫では、実はもう少し色々とやっていますが、ここでは簡単の為に最低限の処理だけ書きました)。

最後のpasted属性に付いては補足が必要です。

実はこの属性、まだ公式のドキュメントに書いていないのです。

なぜかというと、運用の仕方とか命名について「いいのかな」ってモヤモヤしているからです。

もしかしたら、そのうち仕様とか命名とか変わるかもしれません。ご注意ください。

Font Awesomeのアイコンが簡単に埋め込めるようになりました

Font Awesomeのアイコンを簡単に埋め込めるタグfaを用意しました。

name属性にアイコン名を指定します。閉じタグは不要です。

例えばこんな感じで書くと…

ユーザー:<fa name="user">
スター:<fa name="star">
ハート:<fa name="heart">
ハート(赤):<fa name="heart" style="color:red">
ローディング:<fa name="spin spinner">
回転する歯車:<fa name="spin cog">

こんな感じに表示されます。

f:id:convertical:20140523104932p:plain

詳しくはマークアップヘルプのアイコンフォントを入れるを参照してください。