コンテンツにスキップ

フォント:難しいところ

Evidence: Mixed evidence

フォントは、PDF が完全に正しく見えても、内部ではひそかに壊れていることがある領域です。ページは正しいグリフをレンダリングしていても、検索できず、テキストとしてコピーできず、アーカイブプロファイルにも準拠していない場合があります。しかも、それらが同時に起こっていても、警告になるような目に見える兆候はありません。このページでは、正しく機能しなければならない 3 つの要素 — 埋め込み、サブセット化、エンコーディング — と、それぞれに対して NextPDF が何を行うかを説明します。

「見た目は問題ない」は、PDF を扱ううえで最も危険な言葉です。フォントは、その言葉が最も大きな被害をもたらす領域でもあります。3 つの独立した要素が成立していなければなりません。

  1. 埋め込み — フォントプログラムがファイルとともに運ばれるため、そのフォントがインストールされていないマシンでも同じようにレンダリングされます。
  2. サブセット化 — 実際に使われるグリフだけが運ばれるため、20 MB の CJK フォントがすべてのドキュメントを肥大化させません。
  3. エンコーディング — ページ上の文字コードから Unicode へ戻る正しいマップが存在するため、テキストを検索、コピー、インデックス化でき、支援技術で読み取れます。

視覚的なレンダリングで確認できるのは、最初の 1 つだけです。しかも、それも部分的にすぎません。ドキュメントは完璧なグリフを表示していても、3 つ目を完全には満たせないことがあります — そのテキストは言葉ではなく、言葉の絵だからです。これは、あらゆる「見た目は問題ない」レビューを通過しておきながら、コンプライアンス監査や開示請求で失敗する種類の不具合です。

  • PDF 内のフォントは、フォント辞書に加えて、通常は 埋め込まれたフォントプログラム ストリームから成ります。
  • サブセット化 は、そのプログラムを書き換えて、使われているグリフだけを含めます。サブセットフォントの名前には 6 文字の大文字タグと + が付与され、リーダーが別のフォントとして扱えるようにします。
  • エンコーディング は、文字コードを Unicode にマッピングするという別の問題です。/ToUnicode CMap こそがテキストを検索可能・コピー可能にするものであり — それはグリフの見た目が正しいかどうかとは 独立 しています。
  • 見た目は正しいテキストでも /ToUnicode がない(または誤っている)場合は、典型的なサイレント障害です。画面上は完璧でも、実際には検索できません。
  • NextPDF は TrueType フォントをサブセット化し、正しいレンダリングのためにグリフの同一性を保持し、抽出が機能するように /ToUnicode CMap を出力します — さらに、PDF 2.0 の埋め込みルールを、単に警告するのではなく 強制 することができます。

サブセット化。 FontSubsettersrc/Typography/FontSubsetter.php)は、元の TrueType テーブルディレクトリを解析し、cmap を読み取って Unicode コードポイントをグリフ ID にマッピングします。BMP 用のフォーマット 4 と、CJK が必要とする全 Unicode 用のフォーマット 12 の両方に対応します。そのうえで、素朴なサブセッターなら見落としがちなステップを実行します。複合グリフの依存関係 を推移閉包によって解決するのです。ベース文字と結合マークから構成されるアクセント付きグリフは、他のグリフをコンポーネントとして参照します。それらのコンポーネントが削除されると、グリフは正しくレンダリングされません。サブセッターは、新しいコンポーネントが現れなくなるまでそのグラフをたどります。不正なフォントが無限ループに陥らないよう、サイクルガードも備えています。

この実装には、明示しておく価値のあるエンジニアリング上の選択が 2 つあります。第一に、グリフ ID は 再マッピングされず保持 され — 未使用のスロットは glyf/loca 内でゼロ埋めされるため、コンテンツストリームの元のグリフインデックスが CIDToGIDMap /Identity の下で有効なまま保たれます。再マッピングすればより小さくなりますが、すべてのグリフ参照を書き換える必要が生じます。同一性を保持するほうが、構造上正しいのです。第二に、走査は ソート されており(gid 昇順)、サブセットがバイトレベルで決定論的になります — 同じフォントと同じ使用グリフが同じサブセットのバイト列を生成し、これは再現可能なビルドが必要とするものです。サブセット化による節約がファイルの約 10% 未満であれば、元のフォントが変更されずに返されます。わずかな削減のためにそのオーバーヘッドを払う価値はありません。

埋め込み。 フォントプログラムをそもそも運ぶかどうかは、明示的なポリシーが決定します — 推測ではありません。Pdf20FontEmbeddingPolicysrc/Writer/Pdf20FontEmbeddingPolicy.php)には 2 つのモードがあります。PDF 2.0 プロファイルの下では、Strict は埋め込まれていない標準 Type 1(「Base14」)参照を型付き例外で拒否します — これが準拠上正しい挙動です。AllowBase14 は、従来の勧告的なパスを維持します。移行のための猶予期間として、標準が依然として要求する最小限のフォントディスクリプタを出力し、例外を投げる代わりに警告をディスパッチします。この選択は呼び出し側がドキュメント単位で明示的に行うものであり、フォントから推測されることは決してありません。

エンコーディング。 複合(Type 0)フォントについては、EmbeddedTtfFontDictBuildersrc/Writer/EmbeddedTtfFontDictBuilder.php)が、CIDFontType2 子孫、Type0 親、そして文字コードを Unicode へ解決できるようにする /ToUnicode CMap ストリームを出力します。/ToUnicode ストリームが正当に存在しないのは、ただ 1 つのケースだけです。それは、自己記述的な定義済み CJK CMap がすでに文字から Unicode へのマッピングをリーダーに提供している場合です。そこでは CMap が まさに エンコーディングであるため、プレーンプロファイルはバイトを節約するために冗長な /ToUnicode ストリームを省略します。そのケース以外では、/ToUnicode ストリームこそが、テキストをテキストのまま保つ手段です。

関心事保証すること保証 しない こと誤っていた場合のサイレント障害
埋め込みフォント未インストール環境でも同一レンダリングそのテキストが検索可能であること代替フォント。別マシンでメトリクス不正
サブセット化小さなファイル。使用グリフのみエンコーディングの保証一切なし複合コンポーネントの欠落 → アクセント付きグリフの破損
エンコーディング(/ToUnicode検索可能・コピー可能・アクセシブルなテキストグリフが正しくレンダリングされること見た目は完璧なページ。検索不能。コピーすると文字化け

フォントに関する 3 つの関心事は独立しています。埋め込みとサブセット化が関わるのは 見た目とサイズ であり、エンコーディングが関わるのは 意味 です。ページは最初の 2 つを満たしていても、それを示す目に見えるものが何もないまま 3 つ目を満たさないことがあります。

サブセット命名のルールは、規範的で厳密です。 Spec: ISO 32000-2, §9.9.2 は、フォントサブセットの PostScript 名 — BaseFont とディスクリプタの FontName — が ちょうど 6 文字の大文字 のタグで始まり、続いてプラス記号、 そして元のフォントの PostScript 名となることを要求します。さらに、同一フォントの異なるサブセットが 1 つのファイル内で異なるタグを使うことも要求します。このルールがあるからこそ、 リーダーは 2 つのサブセットを区別し、ドキュメントを正しくマージできます。 Evidence: Standard-backed

エンコーディングは、レンダリングとは別個の条項です。 Spec: ISO 32000-2, §9.10.3 /ToUnicode を文字コードを Unicode 値にマッピングする CMap を含むストリームとして定義しており、 また、テキスト抽出手順では Spec: ISO 32000-2, §9.10.2 に従い、その CMap を用いて検索とインデックス化のために文字コードを Unicode へ変換します。グリフ描画の仕組みが /ToUnicode に触れることは一切なく — まさにそれゆえにテキストは見た目が正しくても抽出が誤りうるのです。

埋め込みについては、ほとんどのフォント辞書が、埋め込みフォントファイルストリームを持つフォントディスクリプタを伴うと標準では述べられており、そのストリームは 任意だが強く推奨される とされています。PDF 2.0 は、14 個の標準 Type 1 フォントについて、これを特に厳格化しています。NextPDF の Strict ポリシーは、その厳格化を準拠上正しく読み解いたものです。AllowBase14 は明示的なオプトイン方式の後方互換エスケープであり — エンジンが暗黙のうちにダウングレードすることは決してありません。

Strict PDF 2.0 font-embedding enforcement — edition availability
Edition Availability
Core

利用可能。サブセット化、/ToUnicode の出力、そして明示的な Strict / AllowBase14 埋め込みポリシーは、コアエンジンの挙動です。

Pro

フォント埋め込みに関するより深い準拠強制とレポートをプロファイルレベルで追加します。

Enterprise

エンタープライズ運用の枠組みで、同じ準拠強制を追加します。

以下は、正しく埋め込まれ、サブセット化され、検索可能な複合フォントを構成する 2 つの部分です。サブセットタグは標準の 6 文字ルールに従っており、/ToUnicode 参照こそがテキストを抽出可能に保つものです。

% The Type 0 (composite) font dictionary
20 0 obj
<< /Type /Font /Subtype /Type0
/BaseFont /ABCDEF+NotoSans % six-letter subset tag + '+'
/Encoding /Identity-H
/DescendantFonts [21 0 R]
/ToUnicode 23 0 R >> % the map that makes text searchable
endobj
% The descendant CIDFontType2 (carries the subsetted program)
21 0 obj
<< /Type /Font /Subtype /CIDFontType2
/BaseFont /ABCDEF+NotoSans
/CIDToGIDMap /Identity % glyph IDs preserved, not remapped
/FontDescriptor 22 0 R >>
endobj

オブジェクト 20 の /ToUnicode 23 0 R こそが、検索可能なドキュメントと、見た目だけの絵にすぎないものとの違いです。それを(定義済み CMap のケース以外で)削除すると、すべてのグリフは依然として完璧に描画されるのに、ページ上のどの単語を検索しても何も見つかりません。

率直に言えば、罠は グリフが正しくレンダリングされることは、そのテキストがテキストであるかどうかについて何も語らない という点にあります。レンダリングはエンコーディングからグリフへのパスをたどります。検索とコピーは、コードから Unicode へのパス(/ToUnicode)をたどります。これらはフォント辞書の異なる部分を読む、別の仕組みです。したがって、ドキュメントは欠点のない視覚出力を持ちながら、/ToUnicode がないか誤っている、ということがありえます。その結果が、見た目には信頼できそうでも機能としては検索不能なページです — あらゆる視覚レビューをすり抜ける障害であり、定義上、見るべきものが何もないからです。

関連する罠として、「フォントは埋め込まれているのだからアーカイブは問題ない」という思い込みがあります。埋め込みは必要条件ですが、十分条件ではありません。PDF/A のようなプロファイルは、6 文字ルールに従って命名されたサブセットと正しいエンコーディングも期待します。埋め込まれていても検索できなければ、やはり失敗します。

NextPDF のサブセッターは、明確に TrueType 専用のサブセッターです。不可欠な TrueType テーブルを必要とし、それらが欠けている場合や削減効果が約 10% の閾値を下回る場合は、元のフォントを変更せずに返します。サブセット化と /ToUnicode CMap はテキストを 抽出可能 にしますが、グリフを意味のある文字へ戻すための情報を欠いたソースフォントを補うことはできません。Unicode 値を決定できないところでは、どれだけ CMap を出力しても、その情報を生み出すことはできません。

このページは、NextPDF が 書き出す ドキュメントにおいて正しいフォント構造を生成することについてのものです。任意の入力 PDF を対象とするフォント修復ツールではありません。また、準拠したサブセットとエンコーディングを出力すること自体が、完全なアーカイブプロファイルに対してドキュメントを認証するわけではありません — それは別個の、より広範なチェックです。

なぜ 6 文字タグなのか — フォント名ではだめなのか? リーダーが 同一フォントの異なるサブセット を区別し、それらのグリフセットを衝突させずにドキュメントをマージできるようにするためです。異なるサブセットには異なるタグを使う、というのがルールです。

/ToUnicode がなくても許容されるのはいつか? 自己記述的な定義済み CJK CMap がすでに文字から Unicode へのマッピングを提供している場合です。そこでは CMap がエンコーディングそのものです。別個の /ToUnicode は冗長になります。それ以外では、その欠如は欠陥です。

サブセット化が害になることはあるか? 誤って行った場合に限ります。複合グリフのコンポーネントを削除すると、アクセント付きグリフが壊れます。参照を書き換えずにグリフ ID を再マッピングすると、レンダリングが壊れます。NextPDF は、コンポーネント閉包を解決し、グリフの同一性を保持することで、その両方を回避します。

  • 埋め込みフォントプログラム — 実際のフォントファイル(TrueType/CFF/Type 1)をストリームとして PDF 内に運び、レンダリングがリーダーのインストール済みフォントに依存しないようにするもの。
  • サブセット化 — フォントプログラムを書き換えて、ドキュメントが使用するグリフだけを含め、サイズを削減すること。
  • サブセットタグ — サブセットフォントの名前に付く必須の 6 文字の大文字プレフィックスと +(例:ABCDEF+NotoSans)。
  • /ToUnicode — 文字コードを Unicode 値にマッピングする CMap ストリーム。PDF テキストを検索可能・コピー可能・アクセシブルにするもの。
  • 複合グリフ — 他のグリフをコンポーネントとして参照して構築されるグリフ。サブセット化時にはそのコンポーネントを保持しなければならない。
  • CIDToGIDMap /Identity — コンテンツストリームのグリフインデックスが、フォント自身のグリフ ID そのものになるモード。NextPDF はこれを有効に保つため、グリフの同一性を保持する。
  • Base14 — 14 個の標準 Type 1 フォント。PDF 2.0 は、名前で参照するのではなくフォントを埋め込むことを期待する。