字型:值型別、嵌入與後備
快速概覽
標題為「快速概覽」的區段在 NextPDF 中,字型是不可變的 FontInfo 值物件,再加上決定引擎如何嵌入它的技術型別。引擎使用的每個字型都會被嵌入。舊式的 Base 14 參照會後備到隨附的度量相容替代字型。
composer require nextpdf/core:^3概念總覽
標題為「概念總覽」的區段FontInfo 是單一的不可變值物件,承載引擎嵌入字型所需的一切:字族與樣式、PostScript 名稱、descriptor 旗標、縮放到 1000 單位 em 的度量、字元寬度、glyph 對 Unicode 的對映、forward cmap(Unicode 對 glyph 識別碼)、原始字型位元組,以及存在時的變化軸、命名實例、變化選擇器、kern 配對與垂直度量。它是 final readonly。建構子簽章與公開屬性都已凍結,因此剖析後的字型就是一筆穩定、可共享的事實。FontInfo::encodeText() 是唯一會執行動作的方法。它會透過 encoding resolver 解析,並回傳一個 EncodedGlyphRun。
FontType 列舉了引擎會嵌入的各種技術:TrueType(單位元組編碼)、TrueTypeUnicode(針對 Unicode 豐富書寫系統的多位元組 CID 編碼)、OpenType(Compact Font Format 外框)、Type1(PostScript Type 1,由 PFB 與 AFM 一對檔案註冊),以及 CidFont0(以 PostScript 為基礎的 CID 字型)。剖析器指派的型別,會決定寫入器輸出的字型字典形狀。
引擎會嵌入字型程式,讓文件在任何檢視器中都能一致呈現,不依賴系統已安裝的字型——ISO 32000-2 §9。TrueType 程式透過 FontFile2 字型 descriptor 項目嵌入,而且必須包含 glyf、head、hhea、hmtx、loca 與 maxp 等表格——ISO 32000-2 §9.6.5(RAG 摘要因授權上限而截斷;已記錄於 _downgraded-claims-o3.md)。具有 Compact Font Format 外框表格的 OpenType 程式則透過 FontFile3 嵌入——ISO 32000-2 §9.6.5(RAG 摘要已截斷;見同一份日誌)。subsetter 會精確重建這組必要的表格集合,因此嵌入的子集仍是符合規範的程式。
後備會處理舊式的 Base 14 情境。Base14SubstituteFonts 會把正規化後的 Base 14 鍵——helvetica、helveticab、times、courier 以及其餘各項——對映到隨附的 Liberation Fonts 檔案。Liberation Sans、Serif 與 Mono 分別與 Helvetica 或 Arial、Times Roman 以及 Courier 度量相容。每一個都是嵌入的 TrueType 字體,因此它能算繪 standard-14 參照所要求的完整 WinAnsiEncoding(Windows-1252)拉丁字集——含重音符號的拉丁字元、歐元符號,以及常見的排印標點(ISO 32000-2 Annex D.2)。Symbol 與 ZapfDingbats 沒有授權寬鬆的度量相容替代品,因此刻意不予替代;需要它們的文件必須註冊可嵌入的字型。這個 resolver 沒有副作用:它只回答某個鍵會對映到哪個檔案,除此之外不做任何事。註冊到 registry 仍由呼叫端負責,這樣才能保留鎖的語意與 warmup 管線。
API 介面
標題為「API 介面」的區段| 型別 | 類別 | 主要成員 | 穩定度 | 自版本 |
|---|---|---|---|---|
FontInfo | final readonly class | $family, $style, $type, $unitsPerEm, $widths, $unicodeMap, $cmapForward, $fileData, $variationAxes, $kernPairs, getKey(), encodeText() | 穩定 | 1.0.0 |
FontType | enum(字串) | TrueType, TrueTypeUnicode, OpenType, Type1, CidFont0 | 穩定 | 1.0.0 |
Base14SubstituteFonts | final class(內部) | 正規化的 Base 14 鍵對映到隨附的 Liberation 檔案路徑 | 穩定 | 2.7.0 |
ShaperFactory | final class | default(), create(), wouldUseRealShaper() | 穩定 | 3.2.0 |
ShapingResult | final readonly class | $glyphRuns, $originalText, $script, $direction, $shaperImpl | 穩定 | 3.2.0 |
Base14SubstituteFonts 標示為 @internal,僅供 framework 內部使用;其介面不保證向後相容。
程式碼範例——快速上手
標題為「程式碼範例——快速上手」的區段<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Typography\FontRegistry;use NextPDF\Typography\FontType;
$registry = new FontRegistry();$font = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
// FontInfo is the immutable parsed fact about the face.echo $font->family, ' / ', $font->type->value, "\n"; // e.g. "Noto Sans TC / TrueTypeUnicode"assert($font->type === FontType::TrueTypeUnicode);剖析器會填入 FontInfo,並指派 FontType。帶有 Unicode cmap 的 TrueType 字型會成為 TrueTypeUnicode,寫入器會把它輸出為 Type 0 複合字型。
程式碼範例——正式環境
標題為「程式碼範例——正式環境」的區段<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Typography\Base14SubstituteFonts;use NextPDF\Typography\FontRegistry;
final readonly class Base14EmbeddingResolver{ public function __construct(private FontRegistry $registry) {}
/** * Register an embeddable substitute for a legacy Base 14 key so the * output document embeds every font (PDF/A-4 and PDF/UA-2 require it). */ public function ensureEmbeddable(string $base14Key): void { $path = Base14SubstituteFonts::resolve($base14Key);
if ($path === null) { // Symbol / ZapfDingbats have no permissive substitute — the // caller must supply its own embeddable font. throw new \RuntimeException("No bundled substitute for {$base14Key}"); }
if (!$this->registry->has($base14Key)) { $this->registry->register($path, alias: $base14Key); } }}這個 resolver 沒有副作用。註冊仍明確由呼叫端進行,這樣 registry 的鎖與 warmup 合約才會成立。Symbol 與 ZapfDingbats 依設計不回傳任何路徑。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段Symbol與ZapfDingbats是刻意不予替代的。對這些鍵回傳 null 是有記錄的行為,不是缺字型的 bug。FontInfo是final readonly。請把剖析後的字型當成一個值看待:永遠不要期待就地變更寬度或度量;若來源變了,就重新註冊。- Type 1 字型同時需要 PFB 外框與 AFM 度量。
FontRegistry::registerType1()接收這一對檔案;自動探索會依副檔名,從 PFB 路徑推導出 AFM 路徑。 FontType::TrueType與FontType::TrueTypeUnicode的差別,就是單位元組與多位元組的區分。encoding resolver 是依已填入的 forward cmap 判斷,而不是依字族名稱;因此 Unicode TrueType 字型會自動走 Identity-H 路徑。- 變化字型的軸與命名實例存在時會被剖析進
FontInfo,但這個 CJK 實作範例刻意使用靜態字型,以保持剖析後的FontInfo具有決定性。
FontInfo 在每個行程中,每個字型只由 registry 配置一次,之後都以參照共享。它承載原始字型位元組,這是主要的記憶體成本。worker 應該只暖機它需要的字型,並追蹤 memoryUsage()。Base 14 替代 resolver 是常數時間的 map 查找;在呼叫端註冊解析出的檔案之前,都不會有 I/O。performance_budget 設為 1500 ms 牆鐘時間與 64 MB 尖峰,涵蓋典型字型集合的暖機加上算繪。subsetter 執行之前,每個字型的記憶體佔用會隨字型檔案大小,而不是 glyph 數量縮放。
安全性注意事項
標題為「安全性注意事項」的區段FontInfo 本身不具主動行為:它是剖析後的資料,除了純粹的 encodeText() 轉換之外沒有任何行為。攻擊面在上游,也就是剖析時:任意字型位元組抵達 TrueType 或 Type 1 剖析器。剖析器會對每個二進位偏移量做邊界檢查,並拒絕路徑中的串流包裝器與 null 位元組。不受信任的字型輸入在註冊之前,必須通過會限制大小與 glyph 數量的外部資源政策。隨附的 Liberation 替代品是與套件一起出貨的受信任資產,因此後備路徑不會引入任何新的不受信任輸入。
規範符合性
標題為「規範符合性」的區段| 主張 | 標準 | 條款 | 佐證 |
|---|---|---|---|
| 文件使用的每個字型都會被嵌入,因此文件呈現時不必依賴系統字型。 | ISO 32000-2 | §9 | |
TrueType 程式透過 FontFile2 嵌入,並帶有 glyf、head、hhea、hmtx、loca、maxp 等表格。 | ISO 32000-2 | §9.6.5 | RAG 摘要因授權上限而截斷,完整值僅保存於內部 localization 工件;前綴為 7b26f37996239b2a,見 _downgraded-claims-o3.md |
一個 OpenType(CFF)程式透過 FontFile3 嵌入。 | ISO 32000-2 | §9.6.5 | RAG 摘要因授權上限而截斷,完整值僅保存於內部 localization 工件;前綴為 801549ee00623baf,見 _downgraded-claims-o3.md |
第一項條款已在摘要中釘選,並由 B1 佐證。FontFile2 與 FontFile3 兩項條款則是改寫後的主張。它們完整的 RAG 摘要並未回傳(授權上限截斷),而是由 FontSubsetter(它會精確重建這組 glyf/head/hhea/hmtx/loca/maxp 集合)以及 FontType enum 佐證。NextPDF 不會重現規範性文字。Base14SubstituteFonts 在原始碼中引用 ISO 32000-2 §9.6.2.2(標準 Type 1 字型處理)、ISO 14289-2:2024 §8.4.5.5.1(PDF/UA-2 字型嵌入),以及 ISO 19005-4:2020 §6.3.5(PDF/A-4 字型嵌入)。無障礙與規範符合性頁面承載完整的規範設定檔符合性。
商業情境
標題為「商業情境」的區段商業字型授權套件與動態 subsetting 服務,建構在 Core 的 FontInfo 與 registry 之上。Core 字型模組在沒有授權的情況下也能嵌入、subset 與後備。此頁刻意不放上轉換連結。
另請參閱
標題為「另請參閱」的區段- Typography:registry、subsetting、CMap、encoding、BiDi——產生並消費
FontInfo的 registry 與 subsetting。 - Text:shaping、breaking、BiDi——消費編碼後字元序列的 shaping 銜接點。
- Contracts / Typography——
FontRegistryInterface合約。