跳到內容

自訂字型:FontRegistry 擴充契約

FontRegistryInterface 是行程生命週期層級的契約,用於註冊與查找字型。你可以從檔案路徑、目錄或原始二進位資料註冊字型,接著鎖定此 registry(登錄表),讓正式環境中的 worker 無法再變更它。

Terminal window
composer require nextpdf/core:^3

字型 registry 是 singleton(單例),生命週期比個別的 Document 實例更長。它只保存純 PHP 資料,不持有任何資源 handle 或擴充物件,因此能在長時間執行的 worker 中安全地跨多個請求共用。

此契約支援三種註冊途徑:

  • 從檔案註冊。 register() 會剖析 .ttf.otf.ttc.pfb 檔案,並回傳剖析後的中繼資料。若是 TrueType Collection,請傳入子字型 Index(索引)。
  • 從目錄註冊。 addFontDirectory() 會新增一個搜尋路徑;引擎依名稱 resolve(解析)字型家族時會掃描這個路徑。
  • 從二進位資料註冊。 registerFromBinary() 會剖析原始的 TrueType 或 OpenType 位元組。這也是 @font-face 橋接器處理字型時所走的途徑;這些字型是從 data: URI 或遠端來源取得的。

若要分攤首次請求的延遲,請在 worker 啟動時呼叫 warmup(),預先剖析一批字型,接著呼叫 lock()。在 lock() 之後,每一個變更方法都會擲出 LogicException。這些方法是 register()addFontDirectory()warmup()registerBase14()registerFromBinary()。查找方法仍可使用:get()has()all()getSearchDirectories()。這個鎖定機制是正式環境的安全防護,可確保沒有任何請求能更動共用的字型集合。

多數情況下,你不需要自行實作 FontRegistryInterface。引擎已提供實作,直接呼叫即可。只有在需要自訂字型解析策略時,才會自行實作它,例如以內容定址儲存作為後端的策略。無論哪一種情況,契約都是邊界。

NextPDF\Contracts\FontRegistryInterface(穩定,自 1.7.0 起):

方法回傳用途
register(string $fontFile, string $alias, int $fontIndex)FontInfo剖析並註冊一個字型檔案。若 registry 已鎖定或檔案無法剖析,則會擲出例外。
registerFromBinary(string $fontData, string $alias)FontInfo從原始的 TrueType 或 OpenType 位元組註冊一個字型。
registerBase14(string $key, FontInfo $font)void註冊一個預先建構的 Base 14 標準字型。
addFontDirectory(string $directory)void新增一個字型搜尋目錄。
warmup(array $fontFiles)void在 worker 啟動時預先剖析一批字型。
lock()void凍結 registry,使其無法再被變更。
isLocked()bool回報 registry 是否已鎖定。
get(string $family, string $style)FontInfo | null依字型家族與樣式查找字型。
has(string $key)bool檢查某個註冊鍵是否存在。
all()array<string, FontInfo>回傳所有已註冊的字型。
getSearchDirectories()list<string>依序回傳搜尋目錄。
memoryUsage()MemoryReport回報 registry 目前的記憶體使用量。
<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
/** @var FontRegistryInterface $fonts */
$info = $fonts->register('/srv/fonts/Inter-Regular.ttf', 'Inter');
if (!$fonts->has('inter')) {
throw new RuntimeException('Inter failed to register');
}

這段 worker 啟動常式會預熱一組字型、鎖定 registry,並觀測每次載入以利授權追蹤。它用到的每個型別都是公開型別。

<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Event\Content\FontLoadedEvent;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
use Psr\Log\LoggerInterface;
final class FontWarmup
{
/** @param list<string> $fontFiles */
public function __construct(
private readonly FontRegistryInterface $fonts,
private readonly LoggerInterface $logger,
private readonly array $fontFiles,
) {}
public function boot(): EventDispatcher
{
$listeners = new ListenerProvider();
$listeners->addListener(
FontLoadedEvent::class,
function (FontLoadedEvent $event): void {
$this->logger->info('font.loaded', [
'family' => $event->family,
'style' => $event->style,
'type' => $event->fontType->name,
]);
},
);
if (!$this->fonts->isLocked()) {
$this->fonts->warmup($this->fontFiles);
$this->fonts->lock();
}
return new EventDispatcher($listeners);
}
}
  • 已鎖定的 registry。lock() 之後的任何變更都會擲出 LogicException。在重複使用的 worker 中,進行條件式預熱前一律先檢查 isLocked()
  • 二進位註冊不會依鍵快取。 registerFromBinary() 會寫入一個暫存檔再加以剖析。請把回傳的 FontInfo 當成 handle 看待。
  • TTC 索引。 若是 TrueType Collection,register() 的第三個引數用來選擇子字型。預設值 0 會選擇第一個字型面。
  • 字型家族解析。 對於未知的字型家族與樣式組合,get() 會回傳 null。切勿假設結果一定不是 null。

warmup() 會將剖析成本從首次請求移到啟動階段。registry 的方法都只在純 PHP 資料上運作,查找是常數時間的 map 讀取。請呼叫 memoryUsage(),依你的記憶體預算評估 worker 常駐字型集合的大小。

已註冊的字型會成為可內嵌的 PDF 內容。註冊前必須驗證字型的 provenance(來源資訊)。在未完成大小與格式檢查前,切勿註冊受攻擊者控制的二進位資料。FontLoadedEvent hook 是強制執行字型授權合規,並記錄文件內嵌了哪些字型面的官方支援位置。

此處不適用任何簽章或封存方面的規範性宣告。字型內嵌與子集化符合 PDF 2.0 字型模型;此符合性由內部子集化器負責,而非由此契約負責。

NextPDF Enterprise 在同一份 FontRegistryInterface 之上,加上字型授權證明與經稽核的子集化政策。因為契約就是邊界,所以你的註冊程式碼在各版本之間都不需更動。

詞彙表定義了 font registryimage registryevent listener;各項的標準定義請參閱已發布詞彙表。