コンテンツにスキップ

契約 / タイポグラフィ

タイポグラフィドメインは、フォントレジストリとテキスト前処理のコントラクト、具体的には FontRegistryInterfaceTextPreprocessorInterface、およびイミュータブルな TextPreprocessResultTextSegment 値オブジェクトを保持します。いずれも stable です。

Terminal window
composer require nextpdf/core:^3

FontRegistryInterface は、プロセスのライフタイム全体で使われるフォントストアです。TrueType、OpenType、TTC、または PFB フォントを登録し、解析済みの FontInfo メタデータを返します。レジストリは個々のドキュメントよりも長く存続するため、ワーカーは各フォントを一度だけ解析します。起動時にフォント群をウォームアップしてからロックすることで、本番トラフィックからレジストリを変更されないようにできます。ロックされたレジストリは、register()addFontDirectory()、または warmup() の呼び出し時に LogicException をスローしますが、ルックアップは引き続き利用できます。レジストリは registerFromBinary() を通じて、生のバイナリからフォントを受け取ることもできます。@font-face ブリッジはこのメソッドを使用して、リモートソースまたはデータ URI から取得したフォントを登録します。レジストリは純粋な PHP データのみを保持し、リソースハンドルを持たないため、ワーカープール全体で安全に共有できます。

エンジンは、使用するすべてのフォントを埋め込み、サブセット化します。埋め込まれたフォントプログラムは PDF の内部に格納されるため、ドキュメントはインストール済みのシステムフォントに依存せず、どのビューアーでも同じようにレンダリングされます(ISO 32000-2 §9)。フォントサブセットには、ドキュメントが実際に参照するグリフのみが含まれます。これは CJK や Unicode を多用するコンテンツにとって極めて重要です(ISO 32000-2 §9)。レジストリコントラクトは、サブセット化および埋め込みの各ステージで利用される、解析済みのメタデータを公開します。

TextPreprocessorInterface は、テキストがグリフレイアウト、フォントサブセット化、ToUnicode CMap、および構造ツリーに渡される前に、そのテキストをインターセプトします。この配置自体がセキュリティ上の特性です。コンテンツを墨消しするプリプロセッサーは、そのコンテンツがコンテンツストリーム、フォントサブセット、またはメタデータに到達する前に除去します。このコントラクトには 2 つの不変条件があります。プリプロセッサーはレイアウトに影響する文字を導入してはならず、論理的な読み上げ順序を保持しなければなりません。その責務はコンテンツの置換であり、レイアウトではありません。その結果は、イミュータブルな TextPreprocessResult として返され、TextSegment 値の順序付きリストを保持します。セグメントはパススルーまたは墨消しのいずれかです。墨消しされたセグメントでは、表示テキストはマスキングモードによって異なります。黒塗りの矩形では空文字列、元の長さに一致するアスタリスク、または固定ラベルです。セグメントの originalCharCount は、墨消し矩形のサイズを決めるためだけに使用される、非可逆な計測ヒントです。これを元のコンテンツの復元に使用してはなりません。

種別主要メンバー安定性導入バージョン
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorstable1.9.0

TextPreprocessResultTextSegment は、コンストラクターシグネチャと public プロパティを凍結します。新しいメソッドは追加できますが、プロパティは変更できません。

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

setFont() は、FontRegistryInterface を通じてファミリーを resolve(解決)します。スタンドアロンのドキュメントは、プライベートなレジストリを使用します。ワーカーは 1 つのレジストリを共有します(ドキュメントのページを参照)。

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

warmup() の後に lock() を呼び出すのが、ワーカーの起動シーケンスです。lock() の後で変更しようとすると例外がスローされます。ルックアップは引き続きトラフィックを処理します。

  • ロックされたレジストリは、すべての変更メソッドを拒否します。起動時にウォームアップしてロックしてください。リクエスト処理中に register() を呼び出してはなりません。
  • registerFromBinary() は、フォントのバイト列を解析するために一時ファイルへ書き込みます。信頼できないフォントデータは解析攻撃のサーフェスです。ExternalResourcePolicyInterface を通じてゲートしてください(セキュリティポリシーのページを参照)。
  • 任意の TextPreprocessor は、改行、復帰、またはタブを追加してはなりません。そうするとレイアウトが変わり、コントラクトの 1 つ目の不変条件を破ってしまいます。
  • TextSegment::$originalCharCount は、幅のヒントにすぎません。これを使って元のコンテンツを推測すると、墨消しが無効になり、コントラクトの 3 つ目の不変条件に違反します。
  • TextPreprocessResult::getDisplayText() は設計上、黒塗りセグメントに対して空文字列を返します。空のセグメントを前処理の失敗として扱わないでください。

初回使用時はフォント解析が支配的ですが、レジストリはそのコストをプロセスごとに 1 回へ償却します。ウォームアップ後、get()has() は O(1) のマップルックアップです。memoryUsage()MemoryReport を返すため、ワーカーはフォントキャッシュを予算と照らして追跡できます。テキスト前処理は入力長に対して線形です。セグメントリストは、墨消しの一致数に比例する上限付きのオーバーヘッドを追加します。ウォールクロック 1500 ms、ピーク 64 MB という performance_budget は、一般的なフォントセットのウォームアップとドキュメントのレンダリングをカバーします。サブセット化のコストは、フォントの完全なグリフテーブルではなく、実際に使用されるグリフ数に応じてスケールします。したがってサブセット化は、CJK コンテンツの出力サイズとレンダリングコストを削減します。

タイポグラフィドメインには、セキュリティに関連する 2 つのサーフェスがあります。1 つ目はフォント入力です。registerFromBinary() は任意のバイト列を解析します。信頼できないフォントデータは、パーサーに到達する前に、ファイルサイズとグリフ数を制限する ExternalResourcePolicyInterface を通過しなければなりません。2 つ目は墨消しです。TextPreprocessorInterface は、墨消しされたコンテンツがレンダリング成果物に決して入らないように、グリフレイアウト、フォントサブセット化、ToUnicode CMap、および構造ツリーの直前に配置されています。描画時のオーバーレイとして実装された墨消しは、コンテンツストリームとサブセットに元のテキストを漏洩させます。このコントラクトの配置により、その種の欠陥を防止します。セグメントの計測ヒントは、意図的に非可逆になっています。外部から提供されたフォントやテキストは、すべて信頼できないものとして扱ってください。

主張標準箇条エビデンス
ドキュメントが使用するすべてのフォントの埋め込み、およびシステムフォントに依存しないレンダリングISO 32000-2§9
埋め込まれたフォントの、ドキュメントが参照するグリフへのサブセット化ISO 32000-2§9

どちらの箇条も言い換えです。NextPDF は規範的なテキストを複製しません。PDF/A-4 は、すべてのフォントの埋め込みを義務付けています。その適合性については、抽出およびアクセシビリティのページで説明しています。