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,并在关闭时将其关闭。 |
| 验证 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() 并验证没有浏览器进程残留。
- 性能测试会按模板与内容大小记录渲染时间。