從 Dompdf 遷移到 NextPDF
本指南說明如何將一份以 Dompdf 為基礎的程式庫遷移到 NextPDF 的 Html pipeline。 Dompdf 與 NextPDF 的整體流程相同 —— 載入 HTML、render、輸出 PDF —— 因此大多數呼叫端都能以機械式方式改寫。 真正需要投入心力的是選項對照與 CSS 支援度差異。 NextPDF 與 Dompdf 是各自獨立的引擎,因此 Dompdf 產出的版面僅與 NextPDF 的結果 相容,並非與它逐位元組完全相同。 本指南涵蓋動詞對映、選項鍵對映、行為差異,以及一套安全的遷移步驟。
即使 NextPDF 支援某個 HTML/CSS 功能,也不保證能逐像素重現某份 Dompdf 文件。 哪些功能屬於 Verified,以 CSS 支援度對照表 為準。 本指南描述的是行為;它並不主張視覺上的等價。
composer require nextpdf/core:^3過渡期間請先保留 dompdf/dompdf 的安裝(安全遷移步驟 會讓兩者並行,直到每個呼叫端都切換完成),待全部切換完成後再將它移除。
概念總覽
標題為「概念總覽」的區段Dompdf 的 Dompdf 物件是單一 facade(外觀),同時掌管 DOM、樣式表、frame tree 與畫布。 NextPDF 把這些職責拆開:NextPDF\Core\Document 掌管頁面模型與輸出,並由單一方法 writeHtml() 驅動整條 HTML pipeline。 沒有「先 render、再 output」的兩階段步驟。 writeHtml() 在寫入內容的同時就完成版面配置,接著你用 save()、output() 或 getPdfData() 輸出文件。
NextPDF 寫出的頁面內容是以 ISO 32000-2 的內容串流繪製(ISO 32000-2 §8,iso32000_2_sec8#x1.x3.p14)。 紙張尺寸選項所控制的頁面幾何,對映到頁面物件的 MediaBox(ISO 32000-2 §7,iso32000_2_sec7#x1.x104.p10)。 這些是任何相容寫入器共有的引擎基本要素。 但把 CSS 轉成該內容的 版面演算法 是 NextPDF 自有的,它和 Dompdf 的並不相同(見 行為差異 一節)。
API 介面
標題為「API 介面」的區段NextPDF 的 Html API 記載在 Html 模組 參考文件(由 PHPDoc 自動產生)。 下文會用到這些關鍵進入點:Document::createStandalone()、Document::writeHtml(string $html): static、Document::writeHtmlCell(...)、Document::output(?string, OutputDestination)、Document::save(string $path): void、Document::getPdfData(): string,以及 NextPDF\Core\Config 值物件(pageSize、margins、fontsDirectory)。
API 動詞對映
標題為「API 動詞對映」的區段下方的 Dompdf 公開 API 名稱已逐一對照上游公開倉庫(dompdf/dompdf、master)確認,詳見倉庫內的 _source-sidecar-upstream-api.md provenance(來源資訊)附註。 未重製任何上游文件文字。
| Dompdf | NextPDF | 說明 |
|---|---|---|
new Dompdf($options) | Document::createStandalone($config) | Dompdf 接受一個 Options 物件;NextPDF 則接受一個 NextPDF\Core\Config。 對於長時間執行的 worker,請使用 DocumentFactory,而非 createStandalone()。 |
$dompdf->loadHtml($html, $encoding) | $doc->writeHtml($html) | NextPDF 把輸入一律當成 UTF-8 處理;請在呼叫前先把非 UTF-8 的輸入轉碼,而不是傳入一個編碼引數。 |
$dompdf->loadHtmlFile($file) | $doc->writeHtml(file_get_contents($file)) | NextPDF 沒有載入檔案的變體;請自行讀取檔案,讓 I/O 政策留在你自己的程式碼裡。 |
$dompdf->setPaper($size, $orientation) | ConfigpageSize(一個 PageSize 值物件) | 詳見 選項對照表。 |
$dompdf->render() | (隱含) | NextPDF 在 writeHtml() 期間就完成版面配置;沒有獨立的 render 階段。 請移除 render() 這個呼叫。 |
$dompdf->output() | $doc->getPdfData() | 回傳 PDF 位元組。 |
$dompdf->stream($name, $opts) | $doc->output($name, OutputDestination::Download) | NextPDF 透過 OutputDestination enum 來區分輸出目的地。 |
$dompdf->setBasePath($p) / setProtocol() / setBaseHost() | (資源的 resolve(解析)方式不同) | NextPDF 是針對文件工作集來解析相對資源,而不是依賴一組基底 path/protocol 三元組 —— 見 行為差異 一節。 |
$dompdf->addInfo($label, $value) | $doc->setTitle() / setAuthor() / 中繼資料 API | Dompdf 的自由格式 info 配對,會對映到型別化的中繼資料 setter(ISO 32000-2 §14 文件資訊,iso32000_2_sec14#x1.x5.p5)。 |
$dompdf->setHttpContext($ctx) | (無對應項) | NextPDF 不會透過 stream context 抓取遠端資源;見 不支援/無直接對應項 一節。 |
程式碼範例 —— 快速上手
標題為「程式碼範例 —— 快速上手」的區段<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
// Dompdf:// $dompdf = new Dompdf();// $dompdf->loadHtml('<h1>Invoice</h1>');// $dompdf->setPaper('A4', 'portrait');// $dompdf->render();// file_put_contents('out.pdf', $dompdf->output());
// NextPDF — the createStandalone() default page size is A4 portrait:$doc = Document::createStandalone();$doc->setTitle('Invoice');$doc->addPage();$doc->writeHtml('<h1>Invoice</h1>');$doc->save(__DIR__ . '/out.pdf');
echo "Wrote out.pdf\n";程式碼範例 —— 正式環境
標題為「程式碼範例 —— 正式環境」的區段這段對應 examples/08-html-basic.php(也是本指南可執行性的佐證),並帶有明確的非預設紙張尺寸與邊界。 它相當於一次 Dompdf 的 setPaper(),再加上一份 Options 邊界設定。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\OutputDestination;use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\ValueObjects\Margin;use NextPDF\ValueObjects\PageSize;
// Equivalent of: $dompdf->setPaper('letter','portrait') + margin options.// US Letter portrait = 612 x 792 pt.// Margin constructor order is (top, right, bottom, left) — all 0.5in here.$config = new Config( pageSize: new PageSize(612.0, 792.0, 'Letter'), margins: new Margin(36.0, 36.0, 36.0, 36.0), // top,right,bottom,left; 0.5in in points);
$doc = Document::createStandalone($config);$doc->setTitle('Quarterly Report');$doc->setAuthor('Finance');$doc->addPage();
$html = <<<'HTML'<h1 style="color:#1E3A8A;">Quarterly Report</h1><p>This report renders through the NextPDF Html pipeline. The CSS subset thatis <strong>Verified</strong> for production is the support-matrix authority,not this page.</p><table border="1"> <tr><th>Region</th><th>Total</th></tr> <tr><td>EMEA</td><td>1,204</td></tr></table>HTML;
$doc->writeHtml($html);
// Equivalent of $dompdf->stream('report.pdf'):$doc->output('report.pdf', OutputDestination::Download);邊角案例與陷阱
標題為「邊角案例與陷阱」的區段- 沒有兩階段 render。 Dompdf 程式碼若會在
render()與output()之間檢查狀態(例如讀取頁數),NextPDF 沒有與那個確切邊界對應的做法。 請改在writeHtml()之後查詢文件。 - 編碼。 NextPDF 捨棄了 Dompdf 的
$encoding參數:請在writeHtml()之前先把輸入轉成 UTF-8。 傳入 Latin-1 位元組會產生亂碼,而不是丟出錯誤。 render()沒移除。 若殘留$dompdf->render()這類呼叫,NextPDF 中沒有對應方法,會造成致命的「undefined method」錯誤。 切換時請直接刪掉它,不要做成 stub。- 行內 PHP。 Dompdf 的
enable_php會執行<script type="text/php">。 NextPDF 在設計上 沒有 任何文件內 PHP 執行(那是一個注入攻擊面)。 請把那段邏輯移到writeHtml()之前的 PHP 程式碼中。 - 相對資源解析。 Dompdf 會依基底 path/protocol/host 三元組解析
<img src>的 src。 NextPDF 則是針對文件工作集來解析。 遷移期間,請傳入絕對路徑或已預先解析的 data URI,把這個變數消除掉。
writeHtml() 以單一串流走訪完成版面配置(ADR-001)。 版面完成後不會保留任何中間的 frame tree 物件,因此尖峰記憶體取決於文件大小,而非 DOM 節點數量。 本指南範例的效能預算是 wall_ms: 2000, peak_mb: 128。 大型文件:請沿 addPage() 的邊界把 HTML 分塊,而不要組成單一數 MB 的字串。
安全性注意事項
標題為「安全性注意事項」的區段- 不會透過 stream context 做遠端抓取。 NextPDF 並未實作 Dompdf 的
setHttpContext()/enable_remote遠端抓取路徑。 請在你的應用程式裡解析並驗證遠端資產,再把位元組或 data URI 傳進來。 這樣可移除enable_remote所帶來的 SSRF 攻擊面。 - 不做文件內程式碼執行。 沒有
enable_php的對應項,是刻意的強化,並非缺漏。 - 你透過型別化 setter 設定的文件中繼資料,會寫入 ISO 32000-2 §14 的資訊字典/XMP(
iso32000_2_sec14#x1.x5.p5)。 請勿把祕密放在那裡。
相容性聲明
標題為「相容性聲明」的區段| 聲明 | 規範 | 條款 | 參考 ID |
|---|---|---|---|
| 頁面內容是 opaque/transparent 模型中的內容串流繪製。 | ISO 32000-2 | §8 | |
| 紙張尺寸對映到頁面物件的邊界框。 | ISO 32000-2 | §7 | |
| HTML 字型會寫成 embedded/subset 字型程式。 | ISO 32000-2 | §9 | |
| 空白字元/斷行處理是各引擎特有的。 | CSS Text 3(CSS 文字模組第 3 級) | §6.5 |
NextPDF 產出 ISO 32000-2 內容。 它並不主張一份遷移過來的 Dompdf 文件在視覺上完全相同。 一旦更換 renderer(渲染器),就一定要重新審查輸出。
商業情境
標題為「商業情境」的區段不適用。 核心版已涵蓋此處所述的 HTML 轉 PDF 遷移路徑。
延伸閱讀
標題為「延伸閱讀」的區段遷移細節(R6 必備章節)
標題為「遷移細節(R6 必備章節)」的區段適用對象
標題為「適用對象」的區段目前使用 dompdf/dompdf 進行伺服器端 HTML 轉 PDF,並想改用 NextPDF 引擎的團隊。 如果你只呼叫 loadHtml / setPaper / render / output,那麼 動詞對映 就涵蓋了你的整個介面。
涵蓋範圍:Dompdf facade 動詞、Options 鍵、CSS 功能對等性的預期、資源解析、中繼資料。 不涵蓋範圍:Dompdf 內部的 FrameTree/Canvas/Stylesheet 物件(NextPDF 沒有公開的對應物 —— 不要遷移會直接存取這些內部物件的程式碼,請改用公開 API 重寫)。
相容性對照表
標題為「相容性對照表」的區段涵蓋的是 行為相容性,而非可直接替換的 shim(相容層)。 NextPDF 沒有 Dompdf 類別 shim(不像 TCPDF 路徑 —— 見 遷移指南 /migration/tcpdf-compat/)。 你需要用 動詞對映 把每個呼叫端重寫一遍。 CSS 支援度對照表 裡標為 Verified 的列,直接決定 CSS 功能上的預期。 本指南不會逐一重述每個屬性的狀態。
選項與組態對照表
標題為「選項與組態對照表」的區段| Dompdf 選項(鍵/setter) | NextPDF | 說明 |
|---|---|---|
default_paper_size / setDefaultPaperSize() ; setPaper($size,...) | Config->pageSize(PageSize 值物件) | 具名尺寸改為明確的點數尺寸;new PageSize(595.276, 841.890, 'A4') 是 createStandalone() 的預設值。 |
default_paper_orientation / setDefaultPaperOrientation() | 互換 PageSize 的 width/height | NextPDF 沒有方向旗標;橫向頁面就是一個寬度 > 高度的 PageSize。 |
dpi / setDpi() | (不是全域旋鈕) | NextPDF 以 PDF 點(1/72 吋)為單位運作;影像尺寸是逐張影像決定的,而非套用文件層級的 DPI 倍率。 請把固定的像素尺寸重新換算成點數。 |
enable_remote / setIsRemoteEnabled() | (無對應項 —— 刻意設計) | 請在你的程式碼裡解析遠端資產;見 安全性注意事項 一節。 |
enable_html5_parser / setIsHtml5ParserEnabled() | (一律剖析 HTML) | 沒有開關;parser 就是這條 pipeline。 |
enable_php / setIsPhpEnabled() | (無對應項 —— 刻意設計) | 不支援文件內 PHP;請把邏輯移出範本。 |
font_dir / setFontDir() | Config->fontsDirectory | 單一字型目錄字串。 |
chroot | (在應用程式裡解析) | NextPDF 不接受檔案系統 jail 選項;請在傳入位元組之前先做路徑驗證。 |
default_font / setDefaultFont() | CSS font-family / 已註冊字型 | 請透過你的基底樣式表或字型註冊來設定預設值,而不是用一個全域選項。 |
enable_font_subsetting / setIsFontSubsettingEnabled() | (一律做子集化) | NextPDF 一律對嵌入字型做子集化(ISO 32000-2 §9,iso32000_2_sec9#x1.x45.p7);沒有「關閉」選項 —— Dompdf 關閉該旗標的路徑沒有對應項,也不需要。 |
行為差異
標題為「行為差異」的區段- 版面引擎。 Dompdf 與 NextPDF 是各自獨立的 CSS 版面實作。 空白字元收合與斷行雖有規範,但高度受引擎影響(CSS Text 3 §6.5,
css_text_3#x1.x6.x5.p20)。 在文字密集的文件中,請預期會有斷行與分頁差異。 遷移後請重新建立視覺差異的基準。 - render 接縫。 沒有
render()/output()的兩階段邊界(見 邊角案例 一節)。 - 資源解析。 基底 path/protocol/host 對上文件工作集。
- DPI 模型。 點數對上 Dompdf 的 DPI 倍率。
- 中繼資料。 自由格式的
addInfo()配對,對上型別化 setter(ISO 32000-2 §14,iso32000_2_sec14#x1.x5.p5)。
這些都是有記載的行為差異,並非任一引擎的缺陷。
不支援/無直接對應項
標題為「不支援/無直接對應項」的區段enable_php(文件內 PHP)—— 刻意不提供。setHttpContext()/enable_remote遠端抓取 —— 刻意不提供。- 公開存取
FrameTree/Canvas/Stylesheet—— 無公開的對應物。 dpi作為文件全域倍率 —— 並未建模。
依賴這些項目的程式碼無法「遷移」。 你需要將其移除,或依上方各列在應用程式碼裡重新表達。
安全遷移步驟
標題為「安全遷移步驟」的區段- 把
nextpdf/core加到dompdf/dompdf旁邊(先不要移除 Dompdf)。 - 挑一份低風險的文件。 用 動詞對映 重寫它的呼叫端;刪掉
render()呼叫。 - 對相同輸入產生兩份 PDF,再做視覺差異比對。 把差異視為預期之內(各自獨立的引擎),並逐份文件決定是否接受。
- 透過 選項對照表 轉換選項用法;把 DPI 推算出的尺寸重新換算成點數。
- 請把 remote/relative 資產預先解析成絕對路徑或 data URI,消除解析行為這個變數。
- 逐份文件重複進行,從風險最低做到最高。 在最後一個呼叫端切換完成之前,請保持兩個引擎都安裝著。
- 只有在最後一次切換完成後,才把
dompdf/dompdf從composer.json移除。
測試這次遷移
標題為「測試這次遷移」的區段- 在改動程式碼 之前,先將具代表性文件的 Dompdf 輸出建立為快照(golden input,而非 golden bytes —— 位元組一定會不同)。
- 對每一份遷移過的文件,把 NextPDF 的輸出交給你自己的驗收檢查(視覺差異、文字擷取斷言)。 NextPDF 自身的 HTML pipeline 行為由
examples/08-html-basic.php以及核心的tests/Html 測試套件涵蓋。 你的遷移驗收是逐份文件而定,且要由你自行負責斷言。 - 請為每一份遷移過的文件加入一個回歸測試,讓未來引擎更新時能被攔截到。
佐證/可追溯性
標題為「佐證/可追溯性」的區段本頁每一條 NextPDF 行為陳述,都有倉庫內的測試、範例、原始碼簽章或 ADR 支撐 —— 或者,當它是一項 PDF 格式屬性時,則由前置設定 citations: 與 相容性聲明 表中由 RAG 釘選的 ISO 32000-2 / CSS 條款佐證。dompdf 的行為只以「各自獨立的引擎 —— 請預期有記載的差異」這種方式陳述。 凡是倉庫內成品無法證明的對等性,本頁一律不主張。
| NextPDF 行為主張 | 倉庫內佐證(路徑) |
|---|---|
createStandalone() 的預設頁面是 A4 直向(595.276 × 841.890 pt)。 | src/Core/Config.php(預設 PageSize(595.276, 841.890, 'A4'));tests/Unit/Core/DocumentCreateStandaloneAndConfigWithersEdgeCaseTest.php(createStandaloneWithNullConfigBuildsDocumentWithA4Defaults)。 |
writeHtml() 以單一串流走訪完成版面配置;版面完成後不保留 DOM。 | docs/architecture/adr/ADR-001-stream-based-rendering-pipeline.md;src/Core/Concerns/HasTextOutput.php(writeHtml())。 |
writeHtml() 會在沒有任何頁面時自動建立第一頁。 | tests/Unit/Core/Concerns/DocumentTextOutputFontSubsettingAndBorderEdgeCaseTest.php(writeHtmlAutoCreatesFirstPageWhenNoPagesExist)。 |
output() / save() / getPdfData() 是輸出動詞(沒有 render/output 兩階段)。 | src/Core/Concerns/HasOutput.php(output()、save()、getPdfData());tests/Unit/Core/Concerns/DocumentOutputDestinationDispatchTest.php。 |
輸出目的地是 NextPDF\Contracts\OutputDestination enum(Inline/Download/File/String)。 | src/Contracts/OutputDestination.php;tests/Unit/Core/Concerns/DocumentOutputDestinationDispatchTest.php。 |
| HTML 字型一律寫為 embedded/subset 程式。 | tests/Unit/Core/Concerns/DocumentTextOutputFontSubsettingAndBorderEdgeCaseTest.php(recordUsedCharactersAffectsFontSubsetting);ISO 32000-2 §9(前置設定 citations:)。 |
型別化的中繼資料 setter(setTitle/setAuthor)取代自由格式的 addInfo()。 | src/Core/Concerns/HasMetadata.php(setTitle()、setAuthor());tests/Unit/Core/Concerns/DocumentInfoMetadataSetterBaselineTest.php。 |
| 端到端的 HTML pipeline(本指南可執行性的佐證)。 | examples/08-html-basic.php;核心 tests/Unit/Html/ 測試套件。 |
| 空白字元/斷行是各引擎特有的(版面差異)。 | CSS Text 3 §6.5(前置設定 citations: + 相容性聲明)。 |
由於兩個套件在最終切換完成前都會維持安裝,因此若要回復尚未轉換的呼叫端,就是把那個呼叫端還原成 Dompdf 路徑。 在最終切換完成之後,回復則表示從版本控制還原 dompdf/dompdf 以及先前的呼叫端。 這不涉及任何資料遷移 —— 只有程式碼。
效能考量
標題為「效能考量」的區段見 效能 一節。 單一走訪的模型意味著遷移不會引入 frame tree 保留成本。 每份文件主要的成本變化,是預先解析資產(步驟 5),而這個你可以快取起來。
常見陷阱
標題為「常見陷阱」的區段- 把
render()留在原處(致命的 undefined method)。 - 捨棄
$encoding後又傳入非 UTF-8 位元組(未報錯的亂碼)。 - 期待輸出逐位元組或逐像素完全相同(各自獨立的引擎 —— 本指南從未主張可直接替換或 100% 相容)。
- 依賴
enable_php範本(必須重構移除)。 - 把 CSS 支援度對照表當成僅供參考 —— 它是判定該預期什麼的 Verified 功能權威。