契約 / 排版
排版領域包含字型登錄表合約,以及一組文字預處理合約:FontRegistryInterface、TextPreprocessorInterface,以及不可變的 TextPreprocessResult 與 TextSegment 值物件。全部都是 stable(穩定)。
composer require nextpdf/core:^3概念說明
標題為「概念說明」的區段FontRegistryInterface 是行程生命週期內使用的字型儲存區。它會註冊 TrueType、OpenType、TTC 或 PFB 字型,並回傳剖析後的 FontInfo 中繼資料。登錄表的存活期間比個別文件更長,因此一個 worker(工作行程)只需剖析每個字型一次。它可以在啟動時預熱一批字型,再進入鎖定狀態,讓正式環境流量無法變動它。已鎖定的登錄表在呼叫 register()、addFontDirectory() 或 warmup() 時會擲出 LogicException,但查詢仍可使用。登錄表也可以透過 registerFromBinary() 從原始二進位資料接收字型。@font-face 橋接會使用這個方法,註冊從遠端來源或 data URI 取得的字型。登錄表只持有純 PHP 資料,沒有任何資源 handle,因此可以安全地在 worker pool 之間共用。
引擎會嵌入並子集化它用到的每一個字型。嵌入的字型程式會隨 PDF 一起攜帶,因此文件能在任何檢視器中一致呈現,不受系統已安裝字型影響——ISO 32000-2 §9。字型子集只攜帶文件實際參照到的字元(glyph),這對 CJK 或大量使用 Unicode 的內容尤其關鍵——ISO 32000-2 §9。登錄表合約會公開剖析後的中繼資料,供子集化與嵌入階段取用。
TextPreprocessorInterface 會在文字進入字元排版、字型子集化、ToUnicode CMap 與結構樹之前先攔截它。這個位置正是其安全性性質的來源:會遮蔽內容的預處理器,會在內容抵達內容串流、字型子集或中繼資料之前就先將它移除。這份合約帶有兩項不變式。預處理器不得引入會影響排版的字元,而且必須保留邏輯閱讀順序;它的職責是內容替換,不是排版。結果是不可變的 TextPreprocessResult,內含一份有序的 TextSegment 值清單。每個 segment 不是直接通過,就是已遮蔽。對於已遮蔽的 segment,顯示文字取決於遮蔽模式:黑框矩形為空字串、與原始長度相符的星號,或一個固定標籤。segment 上的 originalCharCount 是不可逆的測量提示,只用來決定遮蔽矩形的大小。它絕不可用來重建原始內容。
API 介面
標題為「API 介面」的區段| 型別 | 種類 | 主要成員 | 穩定度 | 起始版本 |
|---|---|---|---|---|
FontRegistryInterface | 介面(interface) | register()、get()、has()、all()、addFontDirectory()、warmup()、lock()、isLocked()、registerBase14()、registerFromBinary()、memoryUsage() | 穩定 | 1.7.0 |
TextPreprocessorInterface | 介面(interface) | process(string): TextPreprocessResult | 穩定 | 1.9.0 |
TextPreprocessResult | final readonly class(最終唯讀類別) | $segments、hasRedactions()、getDisplayText() | 穩定 | 1.9.0 |
TextSegment | 最終唯讀類別 | $displayText、$isRedacted、$originalCharCount、$fillColor | 穩定 | 1.9.0 |
TextPreprocessResult 與 TextSegment 會固定它們的建構式簽章與公開屬性;可以新增方法,但屬性不得變動。
程式碼範例——快速上手
標題為「程式碼範例——快速上手」的區段<?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(解析)字型家族。獨立文件會使用一個私有的登錄表。worker 則會共用同一個(請見 document 頁面)。
程式碼範例——正式環境
標題為「程式碼範例——正式環境」的區段<?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; }}worker 的啟動順序是先呼叫 warmup(),再呼叫 lock()。在 lock() 之後,變動操作會擲出例外;查詢則會繼續服務流量。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 已鎖定的登錄表會拒絕每一個變動方法。請在啟動時預熱並鎖定;切勿在處理請求期間呼叫
register()。 registerFromBinary()會把字型位元組寫到暫存檔以便剖析。未受信任的字型資料是一個剖析攻擊面——請透過ExternalResourcePolicyInterface加以把關(請見 security-policy 頁面)。- 任何
TextPreprocessor都不得加入換行、歸位字元或 tab。這會改變排版,並破壞合約的第一項不變式。 TextSegment::$originalCharCount只是一個寬度提示。用它來推斷原始內容會破壞遮蔽,並違反合約的第三項不變式。TextPreprocessResult::getDisplayText()對黑框 segment 依設計會回傳空字串。不要把空的 segment 當成預處理失敗。
首次使用的成本主要來自字型剖析;登錄表會把這個成本攤提為每個行程只支付一次。預熱之後,get() 與 has() 都是 O(1) 的 map 查詢。memoryUsage() 會回傳一個 MemoryReport,讓 worker 能依預算追蹤字型快取。文字預處理的成本與輸入長度成線性關係。segment 清單會增加有上界的額外負擔,與遮蔽匹配的數量成比例。1500 ms 牆鐘時間與 64 MB 尖峰的 performance_budget,足以涵蓋一組典型字型的預熱加上文件繪製。子集化成本會隨實際用到的字元數量縮放,而不是隨字型的完整字元表縮放。因此,子集化會降低 CJK 內容的輸出大小與繪製成本。
安全性說明
標題為「安全性說明」的區段排版領域有兩個與安全性相關的面向。第一個是字型輸入:registerFromBinary() 會剖析任意位元組。未受信任的字型資料必須先通過一個 ExternalResourcePolicyInterface,在抵達剖析器之前先限制檔案大小與字元數量。第二個是遮蔽:TextPreprocessorInterface 被刻意安排在字元排版、字型子集化、ToUnicode CMap 與結構樹之前,正是為了讓已遮蔽的內容永不進入繪製後的成品。若遮蔽是以繪製時的覆蓋層方式實作,原始文字會洩漏到內容串流與子集中。合約的擺放位置可防止這一類缺陷。segment 上的測量提示刻意設計為不可逆。請把任何由外部提供的字型或文字都視為未受信任。
符合性
標題為「符合性」的區段| 主張 | 標準 | 條款 | 佐證 |
|---|---|---|---|
| 文件用到的每一個字型都會嵌入,因此文件無需仰賴系統字型即可呈現。 | ISO 32000-2 | §9 | |
| 嵌入的字型會子集化為文件參照到的那些字元。 | ISO 32000-2 | §9 |
兩項條款皆為改寫。NextPDF 不會重製規範性條文。PDF/A-4 對每一個字型強制要求嵌入。該符合性記載於擷取與無障礙頁面。
另請參閱
標題為「另請參閱」的區段- 合約:41 個公開介面(SPI)——SPI 總覽與穩定度層級。
- 合約/Document——登錄表在文件生命週期中的角色。
- 合約/安全性政策——
ExternalResourcePolicyInterface為未受信任的字型位元組把關。 - 排版——文字塑形與排版模組。
- 字型——字型剖析、子集化與嵌入。
- 文字——使用預處理器結果的文字輸出。