コンテンツにスキップ

cmap 対応エンコードで CJK テキストをエンコードする

このレシピでは、CJK TrueType フェイスを登録し、cmap 対応の FontInfo::encodeText() ファサードを介して繁体字中国語テキストをエンコードします。このファサードは Identity-H の 2 バイト CID バイトストリームを生成します。このレシピは examples/35-cjk-cmap-demo.php に沿っています。これを前提にする前に、以下のスコープに関する注記をお読みください。

cmap 対応のテキストエンコードアーキテクチャは、フェーズごとに提供されます(ADR-013)。フェーズ 1 は実装済みです。FontInfo::encodeText() ファサードと cmap 対応のエンコード戦略が接続され、ユーザーランドから到達可能です。フェーズ 2 は進行中です。レンダラーとライターをこのファサード経由でルーティングする段階です。フェーズ 3 と 4 は保留中です。フォントごとの /ToUnicode/CIDSystemInfo/Encoding/CIDToGIDMap の出力、および代替フォントリゾルバーは、まだライターに接続されていません。

次の影響を考慮して計画してください。

  • このレシピが示すのは エンコードファサード であり、そのまま使える縦書きモードではありません。現在のドキュメントサーフェスには 公開された書字方向 API はありません。つまり、setWritingModevertical-rl セッターも存在しません。
  • 基となるサンプルは、そのヘッダーにあるとおり、統合スモークテストであり、適合性フィクスチャではありません。この方法で生成された出力は、フェーズ 3 と 4 が実装されるまで、PDF/UA-2 および PDF/A-4 の検証結果が 退行します。このパスからの出力が適合していると述べないでください。適合性を判定するのはチェッカーであり、現時点ではこの出力を合格と判定しません。
  • 縦書きメトリクスの基盤は存在しますが、内部実装です。これは CjkVerticalMetrics 値オブジェクトと、/W2 および /DW2 のエミッターで構成されます。NextPDF はこれをユーザーランドの「縦書きする」呼び出しとして公開しておらず、ライターはその辞書をまだ出力しません。
Terminal window
composer require nextpdf/core:^3

この制約は nextpdf/core パッケージに対応します。このサンプルは PHP 8.4 で動作します。同梱の Noto Sans TC テストフィクスチャがあるため、このレシピは自己完結しています。

ISO 32000-2 は、テキストの出力を 3 つの層、すなわち Unicode コードポイント、文字コード、グリフ ID としてモデル化します。CJK TrueType フェイスの場合、エンジンは Identity-H エンコードを持つ複合 Type 0 フォントを使用します。このエンコードでは、表示される文字列は CIDFont をインデックスするバイトのペアになります(ISO 32000-2)。

FontRegistry::register() はフェイスを解析します。次に FontInfo::encodeText($unicodeText) が、FontEncodingStrategyResolver を通じてエンコード戦略を解決します。登録済みの TrueType CJK フェイスでは、TrueTypeCmapStrategy にディスパッチします。返される EncodedGlyphRun は、Identity-H バイトストリーム、PDF 文字列オペランド、グリフごとの送り幅、使用されたコードポイント、および GID→Unicode マップを保持します。CJK サブセット化は、ADR-008 に従って、使用されたコードポイントを消費します。将来の /ToUnicode ストリームは、GID→Unicode マップを使用します。選択されるモードは EncodingMode::TwoByteCid です。

PDF における縦書きは、2 つの CIDFont 構造で定義されます。1 つ目は、/W2 のグリフごとの縦書きメトリクス配列です(ISO 32000-2)。2 つ目は、/DW2 のデフォルト縦書きメトリクスです(ISO 32000-2)。NextPDF は、CjkVerticalMetrics::toW2Array()toW2RangeArray()toDw2Array() を通じて、両方の値オブジェクトとエミッターを備えています。これらは内部実装であり、ライターはまだそれらを出力しません。スコープに関する注記を参照してください。

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo。フェーズ 1 のファサードです。
  • EncodedGlyphRunNextPDF\Typography\Encoding\EncodedGlyphRunbyteStreampdfStringOperandmodeadvanceWidthstoUnicodeMapusedCodepointsglyphCount())。
  • EncodingModeNextPDF\Typography\Encoding\EncodingModeSingleByteTwoByteCid)。
  • CjkVerticalMetricsNextPDF\Typography\CjkVerticalMetrics。内部向けの縦書きメトリクス値オブジェクトです。透明性のために文書化していますが、ユーザーランドの書き込みパスとして提供されるものではありません。

完全な PHPDoc テーブルはソースから生成されます。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Typography\Encoding\EncodingMode;
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();
$font = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
$encoded = $font->encodeText('PDF 2.0 引擎');
assert($encoded->mode === EncodingMode::TwoByteCid); // cmap-aware branch fired
echo $encoded->glyphCount() . " glyph run entries\n";

このサンプルは自己完結しており、ハーネスで実行できます。これは examples/35-cjk-cmap-demo.php を反映しています。まず、同梱の Noto Sans TC フィクスチャを登録します。次に、cmap 対応のファサードに到達可能であることを確認します。その後、DocumentFactory を通じてレンダリングし、登録済みのレジストリが使用されるようにします。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\Encoding\EncodingMode;
use NextPDF\Typography\FontRegistry;
$cjkFontPath = dirname(__DIR__, 2)
. '/fonts/test-fixtures/Noto Sans TC/NotoSansTC-Regular.ttf';
if (!is_file($cjkFontPath)) {
fwrite(STDERR, "Missing CJK font fixture: {$cjkFontPath}\n");
exit(1);
}
$fontRegistry = new FontRegistry();
$cjkFont = $fontRegistry->register($cjkFontPath, alias: 'NotoSansTC');
// Phase 1 facade: prove the cmap-aware path is reachable from userland.
$cjkSample = 'PDF 2.0 引擎 — 使用 CMap 編碼';
$encoded = $cjkFont->encodeText($cjkSample);
if ($encoded->mode !== EncodingMode::TwoByteCid) {
fwrite(STDERR, "Expected TwoByteCid (TrueTypeCmapStrategy branch)\n");
exit(2);
}
$imageRegistry = new ImageRegistry(maxCacheBytes: 0);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$doc = $documentFactory->create();
$doc->setTitle('NextPDF CJK CMap-Aware Encoding Demo');
$doc->setLanguage('zh-Hant');
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'CJK cmap-aware encoding (Phase 1 facade)', newLine: true);
$doc->setFont('helvetica', '', 10);
$doc->cell(0, 6, 'Mode: ' . $encoded->mode->name . ' (Identity-H, 2-byte CIDs)', newLine: true);
$doc->cell(0, 6, 'Glyphs: ' . $encoded->glyphCount() . ' run entries', newLine: true);
$doc->cell(0, 6, 'Bytes: ' . strlen($encoded->byteStream) . ' encoded bytes', newLine: true);
$doc->ln(4);
$doc->setFont('NotoSansTC', '', 18);
$doc->cell(0, 12, $cjkSample, newLine: true);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/cjk-vertical-writing.pdf');
echo "Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)\n";

期待される STDOUT:

Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)
  • 適合性フィクスチャではありません。 基となるサンプルのヘッダーにあるとおり、この出力は統合スモークテストです。フェーズ 3 と 4 が実装されるまで、この出力に対する PDF/UA-2 および PDF/A-4 のチェック結果は退行します。これを適合性のゴールデンとして登録しないでください。
  • 書字方向 API はありません。 縦書きに切り替える公開呼び出しは存在せず、vertical-rlvertical-lr もカバーされません。/W2/DW2 のエミッターは内部に存在します。これらは公開されておらず、フォント辞書にもまだ書き込まれません。
  • レジストリの所有権。 Document::createStandalone() は独自のレジストリを構築します。DocumentFactory を使用して、ドキュメントが CJK フェイスを登録したレジストリを読み取るようにします。
  • 最終バイトストリームのパス。 フェーズ 2 が完了するまで、表示されるコンテンツストリームは引き続きレガシーのテキストパスを経由します。現時点で実証済みかつ到達可能なのは、上流のエンコードステップ、すなわち cmap の順方向ルックアップと Identity-H バイトストリームです。
  • CJK サブセット化のコスト。 大きな CJK フェイスは、隔離されたサブプロセスを通じてサブセット化されます。そのサブプロセスには、PHP ネイティブのフォールバックと 2 秒のタイムアウトがあります(ADR-008)。

encodeText() は、入力に対して cmap の順方向ルックアップを 1 回だけ実行します。これはコードポイント数に対して線形であり、O(n) です。バジェットは wall_ms: 2000, peak_mb: 128 です。CJK フェイスは大きく、そのサブセット化が支配的なコストになるため、このバジェットはこのセットの中で最も高く設定されています。ADR-008 はその処理を隔離し、呼び出し側をブロックしないようにします。

CJK フォントファイルは、信頼できないバイナリ入力です。パーサーは、ストリームラッパーのパスと NULL バイトを拒否します。CJK サブセット化は、継承された状態を持たない隔離されたサブプロセスで実行されます(ADR-008)。エンドユーザーが提供するフェイスについては、フォントの出所を検証してください。CJK テキストコンテンツは、解釈されるのではなくレンダリングされます。

記述仕様条項reference_id
Identity-H/Identity-V Type 0 フォントでは、表示される文字列は CIDFont をインデックスするバイトのペアです。ISO 32000-2iso32000_2_sec9#x1.x49.p90
W2 配列はグリフごとの縦書きメトリクスを与え、縦書きに使用される CIDFont にのみ適用されます。ISO 32000-2iso32000_2_sec9#x1.x44.p23
DW2 配列は CIDFont のデフォルトの縦書きメトリクスを与えます。ISO 32000-2iso32000_2_sec9#x1.x44.p22

このレシピは、cmap 対応の CJK エンコードファサードがユーザーランドから到達可能であること(フェーズ 1)を示します。生成されたファイルについて、縦書き出力や PDF/UA-2 / PDF/A-4 への適合を主張するものでは ありません。ライター側の /ToUnicode と縦書きメトリクスの出力(フェーズ 3 と 4)は保留中であり、チェッカーは現時点でこの出力を合格と判定しません。

該当しません。