跳到內容

字型:值型別、嵌入與後備

在 NextPDF 中,字型是不可變的 FontInfo 值物件,再加上決定引擎如何嵌入它的技術型別。引擎使用的每個字型都會被嵌入。舊式的 Base 14 參照會後備到隨附的度量相容替代字型。

Terminal window
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 項目嵌入,而且必須包含 glyfheadhheahmtxlocamaxp 等表格——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 鍵——helveticahelveticabtimescourier 以及其餘各項——對映到隨附的 Liberation Fonts 檔案。Liberation Sans、Serif 與 Mono 分別與 Helvetica 或 Arial、Times Roman 以及 Courier 度量相容。每一個都是嵌入的 TrueType 字體,因此它能算繪 standard-14 參照所要求的完整 WinAnsiEncoding(Windows-1252)拉丁字集——含重音符號的拉丁字元、歐元符號,以及常見的排印標點(ISO 32000-2 Annex D.2)。SymbolZapfDingbats 沒有授權寬鬆的度量相容替代品,因此刻意不予替代;需要它們的文件必須註冊可嵌入的字型。這個 resolver 沒有副作用:它只回答某個鍵會對映到哪個檔案,除此之外不做任何事。註冊到 registry 仍由呼叫端負責,這樣才能保留鎖的語意與 warmup 管線。

型別類別主要成員穩定度自版本
FontInfofinal readonly class$family, $style, $type, $unitsPerEm, $widths, $unicodeMap, $cmapForward, $fileData, $variationAxes, $kernPairs, getKey(), encodeText()穩定1.0.0
FontTypeenum(字串)TrueType, TrueTypeUnicode, OpenType, Type1, CidFont0穩定1.0.0
Base14SubstituteFontsfinal class(內部)正規化的 Base 14 鍵對映到隨附的 Liberation 檔案路徑穩定2.7.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()穩定3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImpl穩定3.2.0

Base14SubstituteFonts 標示為 @internal,僅供 framework 內部使用;其介面不保證向後相容。

examples/35-cjk-cmap-demo.php
<?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 複合字型。

examples/font/base14-fallback.php
<?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 合約才會成立。SymbolZapfDingbats 依設計不回傳任何路徑。

  • SymbolZapfDingbats 是刻意不予替代的。對這些鍵回傳 null 是有記錄的行為,不是缺字型的 bug。
  • FontInfofinal readonly。請把剖析後的字型當成一個值看待:永遠不要期待就地變更寬度或度量;若來源變了,就重新註冊。
  • Type 1 字型同時需要 PFB 外框與 AFM 度量。FontRegistry::registerType1() 接收這一對檔案;自動探索會依副檔名,從 PFB 路徑推導出 AFM 路徑。
  • FontType::TrueTypeFontType::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 嵌入,並帶有 glyfheadhheahmtxlocamaxp 等表格。ISO 32000-2§9.6.5RAG 摘要因授權上限而截斷,完整值僅保存於內部 localization 工件;前綴為 7b26f37996239b2a,見 _downgraded-claims-o3.md
一個 OpenType(CFF)程式透過 FontFile3 嵌入。ISO 32000-2§9.6.5RAG 摘要因授權上限而截斷,完整值僅保存於內部 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 與後備。此頁刻意不放上轉換連結。