Skip to main content
  1. Posts/

PDF化のつづき、Chromiumに全部任せることにした

·1588 words·4 mins
Table of Contents

前回、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ページになった。これだけの規模でも 最初から最後まで落ちずに変換できたのを見て、地に足の着いた設計に なったという手応えを感じた。

Related