前回、py2appの署名が壊れる不具合を追いかけた顛末を書いたが、実はその裏で
もう一つ、ずっと片付けられずにいた宿題があった。narou_dl の
--emit-pdf 、つまりEPUBを縦書きPDFにする機能の話だ。
リポジトリはこちら: https://github.com/ac1965/narou_dl/
直せなかったバグ#
--emit-pdf はもともと、ReportLabを使って文字単位でマス目に配置する
自前の縦書き組版エンジンで動いていた。ルビも傍点も縦中横も一通り動く
ところまでは持っていけたのだが、どうしても解消できないバグが一つ残って
いた。半角英字と長音記号( ー )が、縦書きなのに直立したまま描画されて
しまう。
原因を追ってみると、ReportLabの rotate() を連続して呼び出したときに、
最初の1回は正しく回転するのに、2回目以降がなぜか回転しない、という
挙動にたどり着いた。生のPDFの内容ストリームまで見比べたが、動くケースと
動かないケースでオペレータの構造が同じに見える。ここまで追って、これは
潰すのに見合わないバグだと判断して、直立のまま描画する既知の制約として
一旦諦めていた。
発想を変えた#
そもそも、なぜ自前で組版エンジンを書いていたかというと、当初調べた
WeasyPrintが writing-mode: vertical-rl に対応していなかったからだ。
だが冷静に考えると、縦書き対応のレンダリングエンジンなら他にもある。
普段何気なく使っているブラウザ、Chromiumだ。
EPUBの中身はしょせんXHTMLとCSSなので、それをそのままChromiumに渡して
描画させれば、縦書き・ルビ・禁則処理はブラウザのネイティブ実装が
勝手に正しくやってくれるはずだ。この発想で、Playwright経由でChromiumを
操作する scripts/epub2pdf.py を新規に書いた。
設計の方針はシンプルにした。
- EPUB自身のCSS(
writing-modeや@page)をまず見て、そこに 書いてあることは尊重する。こちらのCSSで強制上書きしない - 判型・書字方向・余白は、EPUB側に指定が無い項目だけこちらの既定値で補う
- ルビ・禁則処理は一切自前で実装せず、Chromiumにそのまま任せる
ついでに、表紙画像の自動配置・目次の自動生成・章見出しの自動検出による 改ページも追加した。EPUBの構造をそのまま利用するアプローチにしたことで、 むしろ自前の組版エンジンより機能が増えている。
実装中に見つけた別のバグ#
作っている途中で、目次が複数ページに渡る作品があることに気づいた。 最初の実装では「表紙1ページ+目次1ページ」と決め打ちでページ番号の 開始位置を計算していたので、目次の2ページ目に本文と同じ番号が振られて しまっていた。
直し方は、表紙と目次だけを本編より先に単独でレンダリングして、実際に 何ページ占めたかを測ってから、その数だけ本番のページ番号をスキップする 方式にした。決め打ちをやめて実測する、というだけの話だが、こういう 「複数ページに渡ることもある」という前提の漏れは、実際のデータで 試してみないと気づけないものだ。
narou_dl本体へ統合#
ここまでできた時点で、旧来の --emit-pdf (ReportLabの自前組版)を
どうするか、という判断が必要になった。品質は明らかにChromiumベースの
方が上なので、思い切って scripts/epub2pdf.py の実装を
narou_dl/pdf_builder.py へ正式に統合し、 --emit-pdf をこちらに
完全に置き換えることにした。ReportLabの自前組版エンジンは削除した。
Makefileの setup-cli / setup-gui / install / app には、
pipでのインストールに続けて playwright install chromium を実行する
ステップを足した。Chromium本体はpipのインストール対象には含まれず、
別途ダウンロードが要るためだ。
大作でも通しで動いた#
最後に、実際にキャッシュ済みの作品で通しの動作確認をした。試したのは 286話ある大作で、生成されたPDFは9586ページになった。これだけの規模でも 最初から最後まで落ちずに変換できたのを見て、地に足の着いた設計に なったという手応えを感じた。



