破滅派でついにKindle本を出版する機能ができたのですが、そのときに「なんかよくわからないけどはまった」という部分をご紹介します。
landmarksは特定の要素のみ、tocは入れ子OK
landmarksは論理目次(リーダーが理解する目次)で、tocは視覚的目次(ほんの一部として追加される目次)だという理解なのですが、Kindle Voyageあたりだとtocとlandmarksの内容がまぜこぜで表示されるような印象でした。
なので、まずはtocをきちんと作り、その中で重要な要素だけlandmarksにするという感じですね。実際にはtoc.xhtmlというファイルを作り、それをOPFに指定しておけば大丈夫ですね。
<manifest> <item id="toc.xhtml" href="Text/toc.xhtml" media-type="application/xhtml+xml" properties="nav"/> </manifest>
「動作する完全なサンプル」がなかなか見つからなかったので、gistにあげておきます → ePubの目次サンプル
KF8(KindleのePub拡張)はguideぐらい?
guide要素は以前の論理目次です。iOSの目次のためだけに指定しなければいけないようですが、landmark要素と似たようなのを入れておくと安心かもしれません。これはePub 3.0の仕様と矛盾しないので。
<guide> <reference type="titlepage" href="Text/titlepage.xhtml"/> <reference type="toc" href="Text/toc.xhtml"/> <reference type="text" href="Text/foreword.xhtml"/> <reference type="dedication" href="Text/contributors.xhtml"/> <reference type="colophon" href="Text/colophon.xhtml"/> </guide>
参考になったのはろすさんのスライドです。
Webフォント使えない?
破滅派本体がWebフォントを使っているので、ePubでも使いたいなと思ったのですが、以下3つの理由でやめました。
- ePubリーダーのWebフォント実装に期待するなという記事をみかけた(豆腐発生率高そう)
- 日本のフォントベンダーでePubでの利用可否について明言しているところが少ない
- フォントサブセットを引っこ抜くのがめんどくさい
おそらく、ePubに同梱するためには著作権的な理由でフォントのサブセットを作る必要があるのですが、「CSSをパースしてから適用すべき文字列を取得し、さらにそこからサブセットを作る」というのが辛すぎたので、やめました。これは今後に期待。
Pythonでやってる人いるので、できそうっちゃできそうですけどね。
普通のCSSプロパティでも対応していないのがある
ePubなら普通のCSS対応しているだろうと思ったのですが、Kindleだと max-width とかダメでした。positionプロパティとかね。
MacのiBooksは表現力が高すぎるので、あまりあてにしない方がいいです。
Kindle PreviewerがYosemiteで死亡
そもそもKindle PreviewerはMac OS X Yosemiteで動かないんですね。Javaのバージョンが高すぎるようです。なので、JDK 1.6を入れて、いろいろ書くと動きます。くわしくはMac OS XでKindle Previewerが起動しない場合の対処策から。
また、僕の場合はXQuartsが古かったらしく、Kindle Voyageプレビューのmobi変換で必ずこけるという謎の挙動を示しました。インストール自体はされていたので、これは原因わからなくてハマりましたねー。ちなみに、再インストールで治りました。
KindleのiOS版めんどくさい
KindleのiOS版はKindle Previewer経由で.azwファイルに変換してiTunes経由で取り込まないとダメ(CSSが適用されない)なので、クソめんどくさいです。
Kindle PaperWhiteでオーケーならKindle for iOSもまあ大丈夫(論理目次ぐらい?)なので、最後の仕上げでiOSをチェックすると労力が少ないかも。
ついでにライブラリの紹介
今回このePub作成機能を作るにあたり、HamePubというハプニングバーみたいな名前のライブラリを作りました。masterはまだ空っぽですが、開発ブランチは進んでおります……
世にePub作成ツールはたくさんありますが、「WebアプリケーションみたいなのからボロロンとePubを作成するにはどうしたらいいのか?」という問題への解決策として、ライブラリを作ろうとなったわけですよ。
SigilとかInDesignとかで書き出してる人には関係ありません。僕がやりたかったのは「WordPressに保存されている投稿をまとめてePubに変換」なので、「ワンソース・マルチユース」を目指している人には役に立つかもしれません。
このライブラリの特徴は以下の通り。
- HTML文字列(やファイル)をePubに対してボンボン追加していける。
$factory->registerHTML($string)
とかやって。 - 目次がわりと簡単に作れる
$factory->toc->addItem('colophon.xhtml', '奥付')
みたいな。 - XMLをいじらなくていい
$factory->opf->setLang('ja')
みたいな。 - ファイル操作しなくていい。最後に
$factory->compile()
とやればePubができる。 - わりとDOMりやすい。
- 特定のパスに該当する外部リソースを取ってきてローカルに保存
最後のちょっと意味わかりませんが、ようするにWordPressとかだと、CSSのパスとかが絶対パスで書かれてるじゃないですか。その場合はローカルパスで適宜書き換える必要があります。
<link rel="stylesheet" "https://takahashifumiki.com/wp-content/themes/fumiki/style.css" /> <!-- ↑これを↓こうする --> <link rel="stylesheet" "../Assets/wp-content/themes/fumiki/style.css" />
外部リソース(たとえば、gravatarの写真とか)は同じサーバにないので、外部に取りに行ってローカルに保存しないといけないですね。そういうのも$factory->parser->remoteAsset()
とかでできます。
あと、DOMりやすくしておいたというのも重要ですね。破滅派ではJSによって「本文の段落最初の文字が空白または約物でなかったら、テキストインデントを設定して行頭一時下げを行う」ということをやっているのですが、ePubでJSによるDOM操作を行うとろくなことがないので、こういう操作はパッケージングのときにやってしまいたいもの。
ようは、まあ、こんな感じですよ。
foreach( $facory->dom['chapter1']->getElementsByTagName('p') as $p ){ if( $this->need_indent($p->nodeValue) ){ // .indentのpは行頭一時下げ $p->setAttribute('class', 'indent'); } }
こういうのは他にもいろいろあって、縦書きのときはアスキー2文字まで正規表現で縦中横にしたりとか、本文のh1〜h6タグを引っこ抜いてIDを連番でふってさらにtocに追加とか、HTML5で非推奨になったせいでePub Checkerで怒られるタグ(tt, big, acronym, strike, abbr)をspan.tt
に変えたりとか、いろいろとDOMりたいんですよ。
しかも、昨今のHTML5はという閉じタグなしの記法もありなので、XMLパーサーがよく死ぬんですよね。これもライブラリを利用することで回避できます。
とまあ、このように「既存のWebアプリケーションが吐き出すHTMLをePubにパッケージする」ために必要な機能をまとめたのがHamePubですね。
調子こいてpackagistやtravisにも登録したのですが、まだそこまで頑張れず……一度もビルドを通らないまま商用リリース決めてやりました。破滅派での使用実績が溜まってきたら正式リリースとします。ちなみにライセンスはMIT。
ePubを久々に触ってみて
2010年ぐらいは熱心にやっていたのですが、自分のサイトで販売してもまったく売れないという理由から遠ざかっていました。当時はrubyとかもめちゃくちゃなことして対応していましたが、いまは楽ですね。このサイトで売っていたものは破滅派に移し、すでに買っていただいた方には新しいePubをお届けしようと思います。しばしお待ちください。
今回は当時の恨みをはらすべく、「Kindleでだけひたすら売る」というミッションに専念します。終わり。
[429] [429] Client error: `POST https://webservices.amazon.co.jp/paapi5/getitems` resulted in a `429 Too Many Requests` response: {"__type":"com.amazon.paapi5#TooManyRequestsException","Errors":[{"Code":"TooManyRequests","Message":"The request was de (truncated...)