asciidoctor-pdf-linewrap-jaでの学び その2 ロジック検討


asciidoctor-pdf-linewrap-ja作成過程での学びをまとめるシリーズ。 前回は禁則処理について整理しました。

今回はこれを具体的なロジックに落としこんでいく過程です。

ここは開発のきっかけとなったchloerei/asciidoctor-pdf-cjkのソースを読むところからスタートしました。特徴は2つ。

  • Asciidoctor PDFのメソッドを拡張して処理を差し込んでいる
  • CJ characters(中国語・日本語の文字)の前にzero-width space(ZWSP)を挿入する

拡張方法

まずは前者。Asciidoctorにはextensionという仕組みがあります。ざっくり言うと、8つ用意された拡張ポイントに対して独自の処理を追加できるというもの。 詳しい仕組みはAsciidoctor User Manualのextensionsに関する解説をどうぞ。

ところが、asciidoctor-pdf-cjkは拡張ポイントを使わず、Asciidoctor PDFのメソッドを直接拡張しています。 それはそれで一理あるのですが、せっかくなので用意された拡張ポイントを使いたいところ。

8つの拡張ポイントを見比べてみると、今回の用途だと

Processes the Asciidoctor::Document (AST) once parsing is complete.

ということでTreeProcessorが一番よさそうです。ただ、このASTが厄介で、formatがイマイチよく分からないんですよね...

結局、asciidoctor ast formatで解析されている内容を参考にしつつトライアンドエラーで使えそうな箇所を探り当てることになりました。

禁則処理

前回の記事で禁則処理を3つのルールと3つの対応方法にまとめました。

  • 基本ルール
    • 行頭禁則: 特定の文字は行の頭にきてはいけない
    • 行末禁則: 特定の文字は行の末尾にきてはいけない
    • 分離禁止: 特定の文字列は行をまたいで分離してはいけない
  • 対応方法
    • 追い出し: 次の行に送る
    • 追い込み: 文字間を詰めて行に押し込める
    • ぶら下がり: 領域をはみ出させる

このうち、対応方法は消去法で割とあっさり決まります。 AsciiDoctor PDFだと日本語文章の均等割付(justify)はまともに動いていなくて、コード読んでも直せる気配がありませんでした。均等割り付けできないとなると追い込みは見送りになります。 描画周りも弄れる気がしないのでぶら下がりもパス。ということで追い出しを採用です。

基本ルールは、要するに改行してはいけない箇所が指定できればいいので、当初は単語結合子(word joiner)を使えばいいかなー、と思っていました。

word joinerは「改行してほしくない箇所を表す制御文字」なので今回の目的にピッタリですし、Asciidoctorで{wj}というAttributeが定義されているのでそのまま使えそうです。

ところが、Asciidoctor PDFは{wj}に対応していないことが判明。空白が描画されてしまいます。issueが切られていました。

ソースコードを読んだ限り、Prawnが対応してくれないと厳しそう。そしてPrawnのロジックは「どこで改行するか」にフォーカスしているようなので、改行しない箇所を指定するのはなかなか難しそうです。実際にPrawnを拡張する形で多少試行錯誤してみたのですがうまくいかず。

ということで、次善の策としてasciidoctor-pdf-cjk同様にzero-width space(ZWSP)を使うことになりました。 これだとasciidoctor-pdf-cjkと同じになってしまうので、ZWSPを挿入する箇所をもう少し細かく制御することで違いを出そう、という方針に。 1文字ずつチェックし、行頭禁則、行末禁則、分離禁止かどうかを見ながらZWSPを挿入するかどうか判定しています。

ただ、判定部分は半角括弧の禁則が機能していないなど、いくつか妥協しているところがあります。今後の課題ですね...




関連(するかもしれない)記事


おススメ