跳转到内容

HTML 管线

Spec: CSS Cascade 5, §6.1 Spec: CSS Display 3, §2 Evidence: Code-backed

NextPDF 在你的 PHP 进程内将 HTML 与 CSS 渲染为 PDF——默认不需要浏览器,也不需要子进程。本页说明转换会经过哪些分层阶段、CSS 引擎实际覆盖的范围,以及在哪些场景下委派给真正的浏览器渲染器才是诚实的选择。

「HTML 转 PDF」听起来像是单一操作。但它其实涉及一套层叠(cascade)、一套盒模型、一条布局流程和一条绘制流程。每一步都有明确规格,也各有自己的失效模式。把它们揉成一个单一流程的引擎很脆弱。色彩解析中的一次变更就可能导致某个盒子位移,而唯一确认的方法,是实际渲染后再检查。

进程内模型有一项真正的优势:不必安装浏览器、不必操作沙箱,也不必跨越进程边界封送(marshal)数据。但只有在转换被拆分得足够清晰、每个关注点都能独立测试时,这项优势才值得。正是这套架构,让「在 PHP 中渲染 HTML」不只是可行,而是可信。

  • HTML/CSS 转换通过 writeHtml() 在进程内执行。产出的是原生 PDF 内容,而不是页面图像。
  • 它是单趟(single-pass)且流式的。分词器(tokenizer)产出一份令牌(token)列表。解析器由左至右消费它,且不保留完整的 DOM 树(ADR-001)。硬性上限限制了元素数量与嵌套深度。
  • 引擎以明确的分层组织:CSS 解析与应用器(applicator)、样式状态、布局与格式化、绘制,以及分页媒体——并对各层各自能做什么制定严格规则(ADR-010)。
  • CSS 引擎涵盖层叠、盒模型,以及常见的布局(区块、行内、表格、浮动等)——范围可观,但只是现代浏览器所实现功能中一个已界定的子集。
  • 当你需要对任意现代 CSS 达到精确的浏览器保真度时,NextPDF 可通过一个可选扩展功能委派给无头浏览器渲染器——这是一条刻意设计、网络隔离的接缝,并非默认路径。

这项转换由一连串阶段组成,每个阶段都会消费前一阶段的类型化输出。

  1. Tokenize HTML becomes an ordered token list — no retained DOM tree.
  2. Resolve CSS Parse styles; the cascade and applicators compute typed values.
  3. Style state A push/pop style stack carries computed values per nesting level.
  4. Layout Block, inline, table, and float geometry computed; no paint here.
  5. Paint Borders, backgrounds, text, and decorations emit PDF operators.
  6. Paged media Page-break and @page rules applied as the cursor crosses page bounds.
进程内 HTML 管线:对令牌串流由左至右进行单趟扫描,以 CSS 解析、样式状态、布局与绘制作为各自独立的层,并在光标前进时套用分页媒体断点。

有两条架构规则,让这不只是一条流程。

各层都有契约。 CSS 文本只会在应用器类内部读取。布局代码计算几何,但不产出任何绘制操作符。绘制代码读取的是不可变的计算样式快照,绝不会读取可变的布局跟踪状态。分页媒体代码触发断点,但会把页面装饰委派给绘制层。这些边界是强制执行的(ADR-010)。这就是为什么新增一个 CSS 属性意味着新增一个应用器,而不是一次同时牵动解析器、布局分派与绘制器的变更。

没有 DOM。 这条管线依据决策被确定为单趟且流式(ADR-001):每个嵌套层级至多保留一个样式状态,再加上当前光标,而不是为每个元素保留一个对象。少数操作确实需要前瞻(look-ahead)——表格列宽计算、:has():last-child。这些情况通过在扁平令牌列表之上建立有界的预扫描索引结构来处理,而不是保留一棵树。元素数量与嵌套深度都设有硬性上限,因此病态输入会快速失败,而不会耗尽内存。

CSS 引擎解析的是真正的 CSS 语义,而不是徒具其表的仿制品。相互竞争的声明会按来源、重要性、层、特异性与顺序归结为每个属性一个值——也就是真正的层叠。布局遵循盒模型。盒子的类型及其建立的格式化上下文,决定了它与同层的流内(in-flow)元素如何配置。引擎的源代码正是围绕这些关注点来组织(层叠、box/display、flex、float、表格、分片)。这就是为什么你能对照规格来推论它的行为,而不必靠经验摸索。

本页属于 Evidence: Code-backed 。各阶段与规则可对应到 core 仓库:

  • 进程内的入口点是 writeHtml(string $html): static,位于 src/Core/Concerns/HasTextOutput.php
  • 这套单趟、不保留 DOM、并设有元素与嵌套上限的设计,即 ADR-001,以及 src/Html/ 中的 tokenizer/parser/样式堆栈代码。
  • 分层的引擎契约——CSS parsing/applicators、样式状态、布局、绘制、分页媒体——即 ADR-010,反映在 src/Html/ 的目录结构上(例如 Cascade/Css/Flex/Float/Fragmentation/,以及应用器类)。
  • 浏览器委派接缝是同一文件中的 writeHtmlChrome(),其文档说明需要可选的渲染器扩展功能,外加一个 Chrome/Chromium 可执行文件。

这些标准为覆盖范围的表述提供了诚实依据。层叠会将相互竞争的声明按来源、重要性、层、特异性、顺序,归结为每个属性一个值——依据 Spec: CSS Cascade 5, §6.1 ,而流内配置则遵循盒子与格式化上下文规则,依据 Spec: CSS Display 3, §2 。同样重要的是这条边界:特性查询(feature query)之所以存在,正是因为并非每个处理器都支持每一项特性,依据 Spec: CSS Conditional 5, §2 。NextPDF 的 CSS 引擎是一个已明确界定、与规格对齐的子集,而坦白说明这一点正是该契约的一部分。

进程内渲染只需一次调用。输出的是可选中的 PDF 文字,而不是栅格化的页面:

<?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 and CSS</strong> directly into PDF pages,
<em>in-process</em>.</p>
<ul>
<li>Headings, paragraphs, bold and italic</li>
<li>Lists, tables, inline styles</li>
</ul>
HTML;
$doc->writeHtml($html);
$doc->save(__DIR__ . '/html-basic.pdf');

如果同一份文件需要任意现代 CSS 并达到精确的浏览器保真度,那么这次调用就会改为 writeHtmlChrome($html)——同一份文件、不同的渲染路径,并且明确依赖可选的浏览器渲染器。

反复出现的误解是:HTML 转 PDF 引擎「基本上就是个浏览器」。它并不是,也不声称自己是。浏览器是对整个 Web 平台庞大且持续更新的实现。NextPDF 的进程内引擎是一个与规格对齐、聚焦于文档布局的子集。诚实的心智模型是「一个称职的打印文档 CSS 引擎」,而不是「PHP 里的 Chrome」。当你真的需要完整的平台时,那正是 writeHtmlChrome() 的用途。它是一条独立、需要主动启用、带有自身运维足迹的路径,而不是无声的回退。

第二个误解:认为浏览器路径不过是「通过网络渲染页面」。从设计上看,恰恰相反。委派接缝在渲染时会无条件阻断子资源的网络访问——不允许远程图像、字体、样式表或框架——因此它无法成为发起对外请求的攻击路径。可以获得像素保真度;但不会开放网络出口。

本页说明这条管线的样貌,以及进程内/浏览器之间的取舍。它并非一份 CSS 支持对照表。进程内引擎确切覆盖哪些属性、模块与选择器,是由代码及其符合性测试定义,而不是由本概览定义。该覆盖范围会持续演进。浏览器委派路径需要一个可选扩展功能,以及一个 Chrome/Chromium 可执行文件。该扩展功能的安装设置、运维特性及其内部结构不在本页范围内,并随该软件包一并记载于其文档中。「进程内」描述的是默认的 writeHtml() 路径。它并非声称每一条渲染路径都不使用子进程。这些架构性主张在本页审阅日期当下是准确的。权威来源是 core 仓库中的 src/Html/、ADR-001 与 ADR-010。

进程内 CSS 引擎是 Core 的一项能力。浏览器委派接缝是一个可选扩展功能,这里仅在能力层级呈现:

HTML rendering paths — edition availability
Edition Availability
Core Core 提供进程内 HTML/CSS 引擎(writeHtml)。
Pro 浏览器委派路径是一个可选的附加扩展功能,与版本层级无关。
Enterprise 浏览器委派路径是一个可选的附加扩展功能,与版本层级无关。
  • 管线模型——HTML 内容路径在整体文档流程中所处的位置。
  • 何时不应使用 NextPDF——诚实的边界,包含浏览器路径或其他工具适合的场景。
  • 集成决策指南——针对你的场景,在进程内引擎与渲染器之间做出选择。
  • 进程内渲染 — 在 PHP 进程内将 HTML/CSS 转换为 PDF,默认不使用浏览器或子进程(writeHtml())。
  • 单趟/流式 — 由左至右消费令牌流,不保留完整的 DOM 树(ADR-001)。
  • 层叠(Cascade) — CSS 的处理过程,按来源、重要性、层、特异性与顺序,将相互竞争的声明归结为每个属性一个值。
  • 格式化上下文 — 盒子所建立的布局环境,用以支配其流内内容如何被配置。
  • 引擎分层契约 — 强制执行的规则集(ADR-010),界定解析、样式、布局、绘制与分页媒体各层各自能做什么。
  • 浏览器委派接缝 — 可选的 writeHtmlChrome() 路径,通过无头浏览器渲染,并阻断子资源的网络访问。