跳到內容

嵌入並子集化 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 測試 fixture 讓本範例可獨立運作。

使用 FontRegistry::register($path, $alias) 註冊字面。登錄表會剖析檔案並回傳一個 FontInfo,並使用 TrueTypeParser 處理 .ttf.otf 檔。若要啟用該字面,請用 setFont($alias, ...) 選取其別名。這個呼叫也會記錄所使用的碼位。

子集化會在 save() 時自動執行。PDF 字型寫入器會蒐集所使用的碼位,並呼叫 FontSubsetter::subset();若為 Compact Font Format(CFF)或 OpenType 字面,則改呼叫 CffSubsetter。子集比完整字型程式小時,寫入器會嵌入子集,並把 BaseFontFontName 改寫為一個六個大寫字母、以加號連接的子集標籤。這就是 ISO 32000-2 對字型子集所要求的 ABCDEF+FontName 形式。寫入器會把嵌入的 TrueType 程式以 FontFile2 形式存放在字型描述子中(ISO 32000-2)。

子集前綴會根據 PostScript 名稱以決定性方式產生,因此決定性建置會得到穩定的標籤。這也是本範例的可重現性 profile 為 structural 的原因。structural profile 會把子集前綴和 trailer 的 /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 文字一節。

剖析只會掃描字型表格一遍,子集化成本則會隨字元數量增加。非 CJK 拉丁字面會在行程內,於 wall_ms: 1500, peak_mb: 96 的預算內完成子集化。大型的 CJK 字面會被導向一個隔離的子行程,並設有兩秒的牆鐘逾時與一個 PHP 原生的後援機制(ADR-008)。這種導向方式表示,即使子集化緩慢或當機,也無法卡住呼叫端。

字型檔是不受信任的二進位輸入。剖析器會拒絕 stream-wrapper 路徑與 null 位元組。CJK 子集化子行程執行時不會繼承任何資料庫連線、檔案控制代碼或 Framework(框架)狀態,並會在當機或逾時時安全地後援(ADR-008)。請驗證從終端使用者收受之字型的來源。

聲明規範條款參考 ID
字型子集的 BaseFont/FontName 會帶有一個六個大寫字母、以加號連接的子集前綴。ISO 32000-2iso32000_2_sec9#x1.x66.p2(第 9 節)
嵌入的 TrueType 字型程式會以 FontFile2 形式存放在字型描述子中。ISO 32000-2iso32000_2_sec9#x1.x65.p15(第 9 節)

本範例示範 NextPDF 如何嵌入並子集化一個 TrueType 字面,並產生一個符合規範的子集前綴。它不會主張字型授權具備合規性,嵌入權利由整合者負責。

不適用。