anti scroll

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

jingoo v1.2.5 release

jingooのv1.2.5をリリースしました。

ひょんなことからjingoov1.2.4にバグ(割りとでかい)を見つけてしまい…緊急でリリースしました。

旧バージョンを使用中の方は、アップデートすることをおすすめします。

バグの詳細

具体的なバグは何かというと、オブジェクトをドットで展開する際に、対象が以下の様な入れ子のオブジェクトだったとき、evalが失敗して例外が投げられていたことです。

{{ user.image.filename }}

原因

原因はASTの評価処理で、ドットでプロパティにアクセスする評価式を、次のようなパターンマッチでのみ受け取っていたからでした。

| DotExpr(Ident(name), Ident(prop)) ->
  jg_obj_lookup_by_name env ctx name prop

オブジェクトのドット展開は左結合で、右にドットが2つ以上続く場合は左辺が再帰的にオブジェクトを返すはずですが、DotExprの評価式で左辺がIdentのみの評価しかしていなかったので、左辺がobjectを返すようなASTだったときにマッチするパターンがなく、SyntaxErrorがthrowされていたわけです。

今までこれに気づかなかったのは、単に入れ子のオブジェクトを扱っていなかったからなんですが、今回キャラクタ機能というのをリリースした際に、キャラクタオブジェクトの画像オブジェクト、という入れ子のオブジェクトを展開する必要があって、初めてバグに気づきました。

修正

対応自体は簡単で、パターンマッチのケースを一つ追加するだけで大丈夫でした。

| DotExpr(Ident(name), Ident(prop) ->
  jg_obj_lookup_by_name env ctx name prop

(** 左辺がオブジェクト *)
| DotExpr(left, Ident(prop)) ->
  jg_obj_lookup env ctx (eval_expr env ctx left) prop

おまけ

久しぶりのソースだったので「思い出せるかなあ」と憂鬱だったのですが、OCamlってコンパイラに型エラーで怒られているうちに、徐々に思い出すんですよね。

改めて型システムは偉大だなあ…などと思いました。

キャラクター登録と台詞記法をサポートしました

機能縦書き文庫に「キャラクター」が登録できるようになりました。

そして登録したキャラクターを使った台詞を記述する記法として「台詞記法」を追加しました。

キャラクター登録

キャラクタを登録すると、それぞれのキャラクタにプロフィールページが作られます。

さらにキャラクタを作品に「キャスト登録」すると、プロフィールページの「出演作品」とか、作品ページ脇の「登場人物一覧」というところに反映されます。

台詞記法

台詞記法を使うと、登録したキャラクターに台詞形式の発言をさせることができます。

[speak pochi いただきます]
[speak pochi@excited ごちそうさまでした!]

それぞれのキャラクタには、最大で5つまでの画像を登録できるので、色々な台詞を「笑顔」とか「怒り顔」など、画像を切り替えて表示させることができます(@excitedの部分です)。ノベルゲームなどでよくある手法ですよね。

表示させると、こんな感じになります。

f:id:convertical:20140801093021j:plain

上の画像は「ジュエルセイバーFREE」の画像素材を利用しています。

さらに発言者の画像をクリックすると、プロフィールがポップアップ。

f:id:convertical:20140801094630p:plain

作品に登場するキャラクターは、作品ページの登場人物一覧のところにも表示されます。

f:id:convertical:20140801094603p:plain

メリット

台詞記法を使うと、

  • 発言者の画像クリックでプロフィールがダイアログされるので、読者は人物が誰だったのかを思い出すのが容易
  • 執筆者は発言者が変わったことを仄めかす文章を省略できる
  • 執筆者は画像を切り替えることで、喜怒哀楽の変化を説明する手間も省ける

というのが良いところだと思います。

それよりもなによりも、それぞれのキャラクターにプロフィールページがあることによって、一つの作品やそれを取り巻くコンテンツに、新しい見方が加わるのが面白いと思います。

キャラクター機能や、台詞記法の詳しい説明はヘルプを見てください。

スマホ用のページを作りました

全ページではありませんが、ビューアー以外のページも、スマホに対応したページを用意しました。

おなじみのハンバーガーボタンが左上にあるタイプです。

f:id:convertical:20140716164846p:plain

押すとこんな感じのメニューが出てきます。

f:id:convertical:20140716164938p:plain

最近googleが公開したweb starter kitっていうのがあって、そのテンプレートを流用しました。

ちなみに、プロフィールページ、作品一覧ページ、ログイン、新規登録画面、ランキング画面などはスマホ対応していますが、管理画面は未だにPCページのまんまです。

あとは、スマホ用のビューアーが少しだけ改善されています(ヘッドに作者とトップのリンクが入っただけですが)。

f:id:convertical:20140716195428p:plain

「あらすじ」をページ送りの縦書きで表示するリンクを追加しました

作品一覧ページに「あらすじ」というリンクが追加されました。

f:id:convertical:20140713154611p:plain

クリックすると、あらすじが表示されます。

f:id:convertical:20140713154644p:plain

画面に収まらない時は、こんな感じで下にページャーが表示されます。

f:id:convertical:20140713155248p:plain

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の時)