跳到內容

將 HTML 繪製為 PDF 頁面

這則 recipe(範例)會使用 writeHtml(),把一段 HTML 與 CSS 片段轉成 PDF 頁面內容。你把 markup(標記語言)交給它,它就會繪製成排好版的頁面。完整且可直接執行的程式碼版本在 examples/08-html-basic.php。你可以依照下面的步驟操作,也可以直接複製這個範例。

NextPDF 會一趟讀完你的 HTML,並把結果直接串流寫進頁面。這是一條單趟串流(streaming)流水線。你不必先理解這套模型也能使用這則範例,但它值得記住,因為本頁後面的幾條規則都受它影響。

Terminal window
composer require nextpdf/core:^3

這個指令會安裝 nextpdf/core 套件。本頁範例以 PHP 8.4 執行,支援的執行環境是 >=8.4 <9.0

writeHtml() 接收一段 HTML 字串,從目前游標位置開始,將內容繪製到目前頁面。以下逐步說明內部流程。首先,引擎只掃描你的 HTML 一次,將其拆成一串 token(HtmlTokenizer)。接著,它會由左到右走訪這份清單(HtmlParser)。對每個元素,它會把對應的 PDF 繪圖指令(也就是內容串流運算子)寫進緩衝區。兩次呼叫之間,引擎從不會在記憶體中建立或保留你的元素樹狀結構。這是刻意的設計,也就是單趟串流模型,記錄於 ADR-001。

每個受支援的區塊元素都會變成版面框,每段文字則會變成顯示文字(text-show)運算子。來自行內 style 屬性與 <style> 區塊的樣式,會透過 CSS 串接(cascade)resolve(解析);也就是在多條規則同時適用時,決定哪個樣式勝出的那套標準規則。文字換行、對齊與間距遵循 CSS Text 模型;該模型規範原始文字如何成為排版後、已斷行的文字(W3C CSS Text Level 3)。

如果你不選字型,內文會使用一套預設字體。那套預設是一個標準 Type 1 字型,也就是 ISO 32000-2 所列 14 個標準字型之一。只有在你註冊並選用自己的字型,或某個一致性設定檔要求 NextPDF 嵌入替代字型時,這套預設才會改變。

先釐清一項期望:NextPDF 支援的是 HTML 與 CSS 的一個子集,而不是兩者的全部。這則範例涵蓋的正是該受支援子集,並不主張完整支援 HTML 或 CSS。每個模組已驗證的確切狀態,請見 CSS 支援矩陣一節。

這個方法的簽章是 writeHtml(string $html): static。它宣告於 NextPDF\Contracts\PdfDocumentInterface 介面,並實作於 NextPDF\Core\Concerns\HasTextOutput。它會繪製到目前頁面;若還沒有任何頁面,它會替你建立一個。這個方法的完整 PHPDoc 表格由原始碼產生。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1>HTML Rendering in NextPDF</h1><p>Rendered with <strong>writeHtml()</strong>.</p>');
$doc->save(__DIR__ . '/out.pdf');

這是完整、自成一體的範例,也是測試載具(harness)實際執行的版本。它對應 examples/08-html-basic.php。它不會寫死輸出路徑,而是寫到載具提供的位置,讓可重現性載具能把腳本跑兩次並比對結果。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$html = <<<'HTML'
<h1 style="color: #1E3A8A;">HTML Rendering in NextPDF</h1>
<p>NextPDF renders <strong>HTML content</strong> directly into PDF pages.
This is the recommended approach for <em>mixed formatting</em>.</p>
<h2>Supported elements</h2>
<ul>
<li>Headings (h1-h6)</li>
<li>Paragraphs with <strong>bold</strong> and <em>italic</em></li>
<li>Ordered and unordered lists</li>
<li>Tables with borders and alignment</li>
<li>Inline styles (color, font-size, margin)</li>
</ul>
<h2>Ordered list</h2>
<ol>
<li>Create a Document instance</li>
<li>Add pages and content</li>
<li>Call save() or output()</li>
</ol>
HTML;
$doc->writeHtml($html);
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script twice.
// Honour it: do not hard-code a path, do not echo the PDF to STDOUT.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/render-html-to-pdf.pdf');
echo "Wrote render-html-to-pdf.pdf\n";

預期的 STDOUT:

Wrote render-html-to-pdf.pdf
  • 游標交接。 writeHtml() 會將游標推進到已繪製內容的結尾。接在後面的 cell() 或第二次 writeHtml() 會從那裡接續,而不是從頁面頂端開始。
  • 還沒有頁面。 若不存在任何頁面,writeHtml() 會在繪製前先加一頁。當你需要先設定特定頁面尺寸時,請明確呼叫 addPage()
  • 元素數與巢狀深度上限。 這個串流引擎設有 50,000 個元素與 100 層巢狀的上限(ADR-001)。超過上限的文件會被拒絕,而不是遭到無聲截斷。
  • 不支援的標記語言。 落在受支援子集之外的元素與屬性會被忽略或退回(fall back);它們不會拋出例外。依賴某個屬性之前,請先對照 CSS 支援矩陣確認涵蓋範圍。
  • 外部資源。 遠端影像與樣式表受外部資源政策管理;預設政策不會擷取任意遠端 URL。

由於分詞(tokenization)與繪製會對你的輸入做單趟處理,成本會隨 token 數量線性成長,也就是 O(n)。這則範例的預設預算是 wall_ms: 1500, peak_mb: 96。由於引擎以串流方式輸出、且不在記憶體中保留 DOM,尖峰記憶體會跟著內容串流緩衝區與作用中的樣式堆疊大小走,而不是跟著整份文件大小走。

CSS 支援矩陣摘錄(僅列 Verified 的列)

標題為「CSS 支援矩陣摘錄(僅列 Verified 的列)」的區段

這裡只列出評為 Verified、且出現在經真實性稽核的 CSS 支援矩陣中的列。「Verified」代表有 src/Html/ 實作、一套實質的專屬 fixture 測試組,且能在結構設定檔下決定性地通過。

W3C 模組層級狀態佐證
CSS 彈性盒版面(css_flexbox_11已驗證(Verified)src/Html/Flex/tests/Unit/Html/Flex/
CSS 格線版面(css_grid_11已驗證(Verified)src/Html/Grid/、WPT 語料
CSS Cascading and Inheritance 串接與繼承(css_cascade_33已驗證(Verified)src/Html/Cascade/tests/Unit/Html/Cascade/
CSS 表格(css_tables_33已驗證(Verified)src/Html/Table/、表格 fixture 與黃金 PDF
CSS 字型(css_fonts_44已驗證(Verified)src/Html/FontFace/tests/Unit/Html/FontFace/

text-aligntext-indentcolor 這類屬性,在矩陣中評為「Claimed」(已實作,但沒有專屬模組 fixture),所以這裡刻意不列為 Verified。

這個 HTML 引擎不保留任何 DOM。它的狀態是一個純量游標,再加上一個 push/pop 樣式堆疊;只含空白的文字節點會在分詞階段被丟棄。一個後果是:較晚出現的元素無法重新設定較早元素的樣式,而需要完整樹狀脈絡的選擇器(例如複雜的 :has() 情況)會依 ADR-006 受到限制。規劃版面時,請只依賴文件順序。

剖析、版面與繪製是彼此分離的層。剖析器不會發出原始繪製運算子,版面分派也不會剖析 CSS;越過這些邊界,正是 ADR-010 禁止的耦合債。對範例作者而言,這代表公開的進入點是 writeHtml(),不要直接操作剖析器內部。

依 ADR-020,以容器為範圍的排版脈絡(flex、表格)可能會建立一棵短暫的子樹,但每個脈絡受限於 5,000 個節點、20 層深度,並在所有作用中脈絡間共享 50 MB 的作用記憶體上限與 10 層巢狀限制。在那些脈絡之外,串流模型不保留任何樹。讓個別表格與 flex 容器維持在節點上限之內,記憶體用量才會可預期。

請將 HTML 輸入視為不可信任。NextPDF 不執行任何指令稿,預設的外部資源政策也不會擷取任意遠端 URL,所以引擎本身採取保守做法。即便如此,凡是由使用者輸入組成的 HTML,在繪製之前都要先驗證或清理。元素與巢狀上限也會保護你:它們限制了一份惡意或格式錯誤的文件能要求多少工作量。

陳述規範條款參考 ID
CSS Text 規範原始文字如何轉換成排版後、已斷行的文字。W3C CSS Text Level 3(文字模組第 3 級)條款 css_text_3#x1.x2.p4
預設內文字體解析為一個標準 Type 1 字型。ISO 32000-2條款 iso32000_2_sec9#x1.x29

這則範例示範 NextPDF 如何繪製受支援的 HTML 與 CSS 子集。它並不主張完整支援 HTML 或 CSS;每個模組已驗證的狀態列在 CSS 支援矩陣中。

不適用。