Artisan 套件有兩項相互關聯的職責:透過 Chrome 繪製 HTML,並把產生的 PDF 頁面匯入 NextPDF 文件。除錯時,請分清 Chrome、parser(剖析器)與匯入器之間的邊界。
當你要撰寫 renderer 整合、長駐 worker、parser 診斷,或以 nextpdf/artisan 為核心的測試時,請參考本指南。
| 層級 | 負責方 | 職責 | 不應放在這裡的內容 |
|---|
| 應用程式 | 應用程式 | 處理 HTML 產生的授權,並選擇 renderer 組態。 | 瀏覽器行程管理。 |
| HTML 政策 | 應用程式與套件 | 在繪製之前拒絕不安全或過大的 HTML。 | 租戶授權或商業決策。 |
| Chrome renderer(渲染器) | nextpdf/artisan | 將 HTML 繪製成由 Chrome 產生的獨立 PDF。 | 通用 PDF 修復或任意 PDF 編輯。 |
| parser/匯入器 | nextpdf/artisan | 剖析已繪製的 PDF,並把其中一頁匯入為 form XObject。 | 完整的 PDF 符合性驗證。 |
| 核心引擎 | nextpdf/nextpdf | 放置匯入的 form 物件,並寫出最終文件。 | Chrome CDP 生命週期。 |
| 階段 | 行為 | 開發者動作 |
|---|
| 建立組態 | ChromeRendererConfig 定義執行檔、逾時、CSS、輸入大小與沙箱行為。 | 請使用依環境調整的組態,而不是寫死執行期推測值。 |
| 建立 renderer | ChromeHtmlRenderer 持有 BrowserPool。 | 在 worker 內重複使用 renderer,並在 worker 關閉時關閉它。 |
| 驗證 HTML | 安全政策會檢查大小,並用預設 CSS 包裹文件。 | 在進入此階段前先驗證呼叫端的授權。 |
| Chrome 列印 | CDP 會繪製出一份獨立 PDF。 | 除非有經過審查的政策允許,否則一律封鎖外部資源。 |
| 剖析 PDF | PdfReader::parse() 會讀取 xref 資料、頁面、物件、資源與修訂版本。 | 除非診斷本身就是目的,否則應把 parser 失敗視為繪製失敗。 |
| 匯入頁面 | PageImporter::import() 會擷取頁面內容、media box、資源與內嵌物件。 | 除非工作流程刻意選擇其他頁面,否則匯入第 0 頁。 |
| 路徑 | 用途 |
|---|
app/Pdf/Renderers/* | 包裝 ChromeHtmlRenderer 的應用程式包裝層。 |
app/Pdf/Templates/* | HTML 範本繪製,以及將 DTO 對映到 view 的處理。 |
app/Pdf/Policies/* | HTML 大小、資源與租戶的繪製政策。 |
tests/Pdf/Renderer/* | 使用小型 HTML 測試夾具(fixture)的 renderer 冒煙測試。 |
tests/Pdf/Parser/* | 用於匯入 Chrome 輸出的 parser 測試夾具。 |
請將範本繪製與瀏覽器繪製分離。renderer 應收到最終 HTML 與已知的頁面寬度。
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
use NextPDF\Artisan\PageImporter;
use NextPDF\Parser\PdfReader;
$renderer = new ChromeHtmlRenderer(new ChromeRendererConfig(
$result = $renderer->render($html, widthPt: 595.28);
$reader = new PdfReader($result->getPdfData());
$form = (new PageImporter())->import($reader);
為每個 worker 行程或每個 request 範圍各建立一個 renderer。重複使用 renderer 可避免反覆承擔 Chrome 啟動成本。明確關閉 renderer,可避免 worker 關閉期間發生行程洩漏。
final class InvoiceChromeRenderer
public function __construct(
private readonly ChromeHtmlRenderer $renderer,
public function renderInvoice(string $html): string
->render($html, widthPt: 595.28)
public function close(): void
$this->renderer->close();
當 Chrome 輸出無法匯入時,parser API 會派上用場。診斷應維持唯讀,並避免在成功匯入後變更 parser 狀態。
| 診斷問題 | 要使用的 API | 預期訊號 |
|---|
| 檔案可以剖析嗎? | PdfReader::parse() | 遇到無效的 PDF 結構時會擲回例外。 |
第 0 頁存在嗎? | PdfReader::getPage(0) | 會回傳一個 PdfObject。 |
| 有內容嗎? | PdfReader::getPageContentStream($page) | 非空的內容串流(content stream)。 |
| 有資源存在嗎? | PdfReader::getPageResources($page) | 資源字典陣列。 |
| 有增量修訂版本嗎? | PdfReader::getRevisionCount() | 計數大於一。 |
| 是哪個物件失敗? | PdfTokenizer::getOffset() 與 parser 例外的上下文。 | 用來縮減測試夾具的位元組偏移量。 |
| 擴充點 | 用途 | 限制 |
|---|
ChromeRendererConfig::fromArray() | Framework(框架)組態對映。 | 未知或型別錯誤的選用值會退回預設值。 |
HtmlSecurityPolicyInterface | 剖析層的 HTML 政策。 | 不會取代傳輸、行程或授權控管。 |
LoggerInterface | 繪製與瀏覽器診斷。 | 預設不要記錄 HTML 內容。 |
BrowserPool | 長駐 Chrome 行程的重複使用。 | 必須在 worker 關閉時一併關閉。 |
PageImporter | 內嵌已剖析的外部頁面。 | reader 必須先完成剖析。 |
| parser 類別 | 用於診斷與匯入 Chrome 輸出。 | 並非一般用途的 PDF 修復工具組。 |
- 在最小的繪製測試中重現該 HTML 片段。
- 驗證
maxHtmlSize、預設 CSS 與 Chrome 執行檔路徑。
- 使用固定的點(point)寬度進行繪製。
- 以
PdfReader::parse() 剖析回傳的 PDF 位元組。
- 除非工作流程刻意選擇其他頁面,否則匯入第
0 頁。
- 為每個能重現失敗的最小 HTML 加入測試夾具。
- 在 worker 關閉掛鉤中關閉 renderer。
| 失敗 | 應在何處處理 | 建議的回應方式 |
|---|
| 找不到 Chrome 執行檔 | 部署檢查與 renderer 建構路徑。 | 在開始接受繪製流量前,讓就緒檢查失敗。 |
| HTML 過大 | HTML 政策。 | 在啟動 Chrome 之前就予以拒絕。 |
| 瀏覽器逾時 | renderer 邊界。 | 讓繪製失敗,並記錄範本名稱、大小、寬度與逾時值。 |
| parser 失敗 | 匯入邊界。 | 在政策允許時,儲存一份小型且已淨化的測試夾具以供除錯。 |
| 瀏覽器行程洩漏 | worker 生命週期。 | 在關閉階段將其關閉,並在受控的繪製次數後重新啟動。 |
| 考量項目 | 預設值 | 何時應覆寫 |
|---|
| 繪製逾時 | 30 秒。 | 僅針對已實測、有明確邊界的文件才調高。 |
| HTML 大小上限 | 5,000,000 位元組。 | 對公開的 endpoint 應調低。 |
| 沙箱 | 已啟用。 | 僅在容器限制要求且主機已隔離時才停用。 |
| 高度 | 當 heightPt <= 0 時自動計算。 | 對於嚴格的版面契約,請使用固定高度。 |
| 外部資源 | 由 renderer 政策封鎖。 | 僅在經過審查的資源政策下才允許。 |
- 繪製測試涵蓋具代表性的 HTML 與 CSS。
- 安全測試涵蓋過大的 HTML,以及被封鎖的資源存取嘗試。
- 匯入測試會斷言回傳的 form 物件具備內容、media box 與資源。
- parser 測試涵蓋 xref table、xref stream、object stream 與格式錯誤的測試夾具案例。
- worker 測試會呼叫
close() 並驗證沒有瀏覽器行程殘留。
- 效能測試會依範本與內容大小記錄繪製時間。