コンテンツにスキップ

TrueType フォントを埋め込んでサブセット化する

TrueType フォントを登録してテキストのレンダリングに使用し、フェースのサブセットをライターに埋め込ませます。このレシピは examples/04-text-and-fonts.php と同じコンテンツパスに沿って、登録済みの TrueType(.ttf)フォントを追加します。

Terminal window
composer require nextpdf/core:^3

この制約は nextpdf/core パッケージに対応しており、この例は PHP 8.4 で実行されます。同梱の LiberationSans-Regular.ttf テストフィクスチャにより、このレシピは自己完結します。

フェースを登録するには FontRegistry::register($path, $alias) を使用します。レジストリはファイルを解析して FontInfo を返し、その際に .ttf および .otf ファイルには TrueTypeParser を使用します。このフェースを有効にするには、setFont($alias, ...) でそのエイリアスを選択します。この呼び出しでは、使用されたコードポイントも記録されます。

サブセット化は save() 時に自動的に実行されます。PDF フォントライターは、使用されたコードポイントを収集して FontSubsetter::subset() を呼び出します。Compact Font Format(CFF)または OpenType のフェースの場合は CffSubsetter を呼び出します。サブセットがフルプログラムより小さい場合、ライターはそのサブセットを埋め込み、BaseFontFontName を、6 文字の大文字からなるプレフィックスをプラス記号で連結したサブセットタグに書き換えます。これは、ISO 32000-2 がフォントサブセットに要求する ABCDEF+FontName の形式です。ライターは、埋め込んだ TrueType プログラムをフォントディスクリプター内の FontFile2 として格納します(ISO 32000-2)。

サブセットプレフィックスは PostScript 名から決定的に生成されるため、決定的なビルドでは安定したタグになります。そのため、このレシピの再現性プロファイルは structural です。structural プロファイルでは、サブセットプレフィックスとトレーラーの /ID をバイト単位で直接アサートせず、正規化して除外します。

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry
  • setFont(string $family, string $style = '', float $size = 12.0): staticNextPDF\Core\Concerns\HasTypography。登録済みのエイリアスを $family として渡します。
  • サブセット化はライター内部の処理です(NextPDF\Writer\PdfFontWriter -> NextPDF\Typography\FontSubsetter)。公開されたオン・オフ切り替えはありません。コードポイントが判明していて、かつサブセットの方が小さい場合、ライターは必ずサブセット化します。

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

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();
$registry->register(__DIR__ . '/MyFont-Regular.ttf', alias: 'MyFont');
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('MyFont', '', 14);
$doc->cell(0, 10, 'Rendered with an embedded, subset TrueType face.', newLine: true);
$doc->save(__DIR__ . '/out.pdf');

Document::createStandalone() は独自のレジストリを構築します。事前に設定したレジストリを使用するには、本番サンプルに示すとおり、DocumentFactory を通じてドキュメントを構築します。ファクトリ経由にすることで、登録済みのフェースをライターが参照できるようになります。

このサンプルは自己完結しており、ハーネスで実行できます。同梱の LiberationSans-Regular.ttf を登録し、DocumentFactory を通じてレンダリングするため、事前に設定したレジストリが使用され、保存時の自動サブセット化に依存します。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// A bundled TrueType test fixture keeps this recipe self-contained.
// Replace with a font you have the right to embed.
$fontPath = __DIR__ . '/../../fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';
if (!is_file($fontPath)) {
// Fall back to the repository-relative fixture location.
$fontPath = dirname(__DIR__, 2) . '/fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';
}
$fontRegistry = new FontRegistry();
$fontRegistry->register($fontPath, alias: 'LiberationSans');
$imageRegistry = new ImageRegistry(maxCacheBytes: 0);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$doc = $documentFactory->create();
$doc->setTitle('Embedded Subset Font');
$doc->addPage();
$doc->setFont('LiberationSans', '', 20);
$doc->cell(0, 14, 'Embedded TrueType face', newLine: true);
$doc->setFont('LiberationSans', '', 12);
$doc->multiCell(0, 7, 'Only the glyphs used by this document are embedded. '
. 'The writer subsets the font program and rewrites the BaseFont with a '
. 'deterministic six-letter subset prefix, for example ABCDEF+LiberationSans.');
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/embed-and-subset-fonts.pdf');
echo "Wrote embed-and-subset-fonts.pdf\n";

期待される STDOUT:

Wrote embed-and-subset-fonts.pdf

サブセットを確認するには、出力を開いてフォント辞書を調べます。BaseFont<TAG>+LiberationSans と表示され、ディスクリプターには FontFile2 が含まれます。qpdf --check を実行しても構造上のエラーは報告されません。

  • レジストリの所有権。 Document::createStandalone() は独自のレジストリを構築します。別の FontRegistry に登録したフォントは、そこからは参照できません。本番サンプルと同様に、DocumentFactory を使用して自分のレジストリを渡します。
  • 埋め込み権利。 サブセット化はライセンスを変更しません。埋め込み権利があるフォントのみを埋め込んでください。一部のフォントは埋め込み制限ビットを設定しています。パーサーはそれらのビットを読み取りますが、コンプライアンスの責任は引き続き利用者にあります。
  • CFF/OpenType のパス。 .otf および CFF のフェースは CffSubsetter でサブセット化され、FontSubsetter では処理されません。動作とサブセットタグの書き換えは同等で、異なるのはコードパスのみです。
  • サイズ削減なし。 サブセットが元のフォントより小さくならない場合もあります。これは非常に小さいフォントや、すべてのグリフが使用される場合に発生します。その場合、ライターはサブセットタグなしで元のプログラムを埋め込みます。これは正しい動作であり、失敗ではありません。
  • CJK フォント。 大きな中国語・日本語・韓国語(CJK)フェースは、ADR-008 に従い、分離されたサブプロセスと PHP ネイティブのフォールバックを用いた段階的なサブセット化戦略を使用します。CJK 固有の詳細と現在のパイプラインの状態については、cmap 対応エンコーディングで CJK テキストを設定するを参照してください。

解析はフォントテーブルに対する 1 回のパスで行われ、サブセット化のコストはグリフ数に応じて増加します。非 CJK のラテンフェースは、wall_ms: 1500, peak_mb: 96 の予算内でプロセス内サブセット化されます。大きな CJK フェースは、2 秒のウォールクロックタイムアウトと PHP ネイティブのフォールバックを備えた分離されたサブプロセスにルーティングされます(ADR-008)。このルーティングにより、時間のかかるサブセット処理やクラッシュしたサブセット処理が呼び出し元をブロックすることはありません。

フォントファイルは信頼できないバイナリ入力です。パーサーはストリームラッパーのパスと NULL バイトを拒否します。CJK サブセット化のサブプロセスは、データベース接続、ファイルハンドル、フレームワークの状態を継承せずに実行され、クラッシュやタイムアウト時には安全にフォールバックします(ADR-008)。エンドユーザーから受け取るフォントの出所を検証してください。

記述仕様条項リファレンス ID
フォントサブセットの BaseFont/FontName は、6 文字の大文字からなるサブセットプレフィックスをプラス記号で連結した形式。ISO 32000-2iso32000_2_sec9#x1.x66.p2
埋め込まれた TrueType フォントプログラムは、フォントディスクリプター内の FontFile2 として格納。ISO 32000-2iso32000_2_sec9#x1.x65.p15

このレシピでは、NextPDF が TrueType フェースを埋め込んでサブセット化し、適合するサブセットプレフィックスを出力する方法を示します。フォントライセンスへの準拠を表明するものではなく、埋め込み権利はインテグレーターの責任です。

該当なし。