テキストに画像を混ぜると崩れる帳票グリフ差込みの3つの罠
BMP外の文字をベクター画像として帳票本文へ差し込むと、テキストなら自然に効いていた色・縦揃え・行数の前提が崩れる。混在レイアウトで遭遇した3つの罠とその補い方を綴る。
前回、純PHPのPDFライブラリTCPDFがBMP外の文字(サロゲートペアで表される漢字や絵文字)を出力できず豆腐になる話を書いた。フォント差し替えでは直らないので、該当文字だけをベクター画像(SVG)として本文の該当位置に描き込む、という方針にたどり着いたところで前回は終わっている。
その方針自体は正しかった。問題は、単体の検証では綺麗に出たグリフ画像を、実際の帳票のセルに載せた瞬間に始まった。発信者名、件名、本文——テキストとして書けば当たり前に効いていた挙動が、途中に画像という別種の要素を混ぜた途端に次々と崩れたのだ。以下は、そのとき踏んだ3つの罠と、それぞれをどう埋めたかの記録である。
罠1:白測定に取り残される黒いグリフ
この帳票エンジンは、可変長のテキストが何行に折り返すかを事前に知る必要がある。セルの高さを確定させないと次のセルのY座標が決まらないからだ。そこでTCPDFのMultiCellを使い、まず本文をいったん白文字で「素振り」描画して行数だけ測り、そのうえで本番の黒文字を描く、という二段構えになっている。
テキストはこれで問題ない。白で描けば紙の上では見えず、行数のカウントだけが得られる。ところが差し込んだグリフ画像は、文字色という概念に追従しない。SVGのfillに黒がハードコードされていれば、白測定のパスでも黒いグリフがそのまま紙に残る。結果、本番描画の前の「見えないはず」の一回目で、ページに黒い外字がゴーストのように焼き付いてしまった。
対処は、グリフ画像を「現在の文字色」に追従させることだった。TCPDFは描画中の文字色を内部プロパティとして保持している。ただしそれは公開されていないので、リフレクションで読み出す。取り出した色をSVGのfillに流し込んでから差し込めば、白測定のパスではグリフも白——不可視だが、幅とベースラインは占有するので行数の計算は正確に保たれる。本番のパスでは黒として現れる。テキストが暗黙にやっていた「文字色に従う」を、画像に対して明示的に再現したわけだ。
罠2:HTMLモードで素通しされる縦揃え
セルの中身を縦方向に中央寄せ・下寄せしたい場面がある。素のテキストならMultiCellのvalign指定でおおむね効く。ところがグリフ画像をインラインで混ぜるにはセルをHTMLモードで描く必要があり、このモードに入った途端、TCPDFは縦位置の指定を無視して素通しにする。
症状は分かりやすかった。外字を含むセル——たとえば発信者名や件名——だけが上端に張り付き、外字のない周囲のセルとベースラインが揃わない。同じ帳票の中で一部のセルだけ天井にくっついているのは、見た目にすぐ分かる崩れだ。
ここは自前でオフセットを計算して補うしかなかった。セルに割り当てられた高さと、実際に描かれた行の合計高さの差、その半分を上に足せば中央寄せになる。下寄せなら差の全量だ。この縦オフセットを本番描画のY座標に加算する。外字を含まないセルはオフセット0で、従来どおりの経路を通す。要は、レンダラが放棄したvalignの意味を、こちら側で座標計算として肩代わりした。
罠3:豆腐幅で測る行数のわずかなズレ
最後は、あえて直さなかった罠である。罠1で触れたとおり、行数を数えるパスはTCPDFの内部を通る。このとき外字はまだ画像に置換される前、つまり豆腐(未対応文字)の幅で測られている。豆腐の字幅と、実際に差し込むグリフ画像の見かけ上の幅は厳密には一致しない。したがって、ひとつのセルに大量の外字が並ぶと、折り返し位置が本来の一行分ほど微妙にズレうる。
理屈のうえでは、測定パスでも豆腐を画像相当の幅に読み替えれば精度は上がる。だが実際の帳票で一セルに外字が何文字も密集することは稀で、出たとしても崩れは一行の増減程度。手間と効果を天秤にかけ、ここは割り切って残した。すべての抽象の隙間を完璧に塞ぐより、影響の軽い箇所は意図的に見送ると判断できることも、実務の一部だと思っている。
AIとの協働作業での学び:混在させた瞬間に前提は崩れる
三つの罠に共通するのは、いずれも「テキストとして自然に効いていた性質」が壊れた、という一点だ。文字色への追従、縦揃え、行数計算——どれもテキストだけを流していれば意識すらしなかった挙動である。そこに画像という別種のプリミティブを一粒混ぜた瞬間、レンダラが暗黙に保証していた前提が音もなく外れた。
混在レイアウトを組むときは、レンダラが各プリミティブをどう扱うか——とりわけ、あるモードで何を無視するか——をひとつずつ確かめ、失われた性質を明示的に補い直す必要がある。色は追従させ、縦位置は自前で計算し、精度の一部は割り切る。派手な設計判断ではなく、抽象が漏らした隙間(leaky abstraction)を地道に埋めていく作業だ。だが混ぜ物のあるレイアウトを破綻させないのは、たいていこの地味な埋め戻しのほうなのである。