跳转到内容

Artisan 开发者指南

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、输入大小与沙箱行为。请使用按环境定制的配置,而不是把对运行时环境的猜测写死。
创建 rendererChromeHtmlRenderer 持有一个 BrowserPool在 worker 内复用 renderer,并在关闭时将其关闭。
验证 HTML安全政策会检查大小,并用默认 CSS 包裹文档。进入此阶段前,先验证调用方的授权。
Chrome 打印CDP 会渲染出一份独立的 PDF。除非有经过审查的政策允许,否则应持续阻止外部资源。
解析 PDFPdfReader::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 与已知的页面宽度。

<?php
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
use NextPDF\Artisan\PageImporter;
use NextPDF\Parser\PdfReader;
$renderer = new ChromeHtmlRenderer(new ChromeRendererConfig(
renderTimeout: 30,
maxHtmlSize: 1_000_000,
));
$result = $renderer->render($html, widthPt: 595.28);
$reader = new PdfReader($result->getPdfData());
$reader->parse();
$form = (new PageImporter())->import($reader);

每个 worker 进程或每个 request 范围各创建一个 renderer。复用 renderer 可以避免反复承担 Chrome 启动成本。显式关闭 renderer,可以避免 worker 关闭期间发生进程泄漏。

<?php
final class InvoiceChromeRenderer
{
public function __construct(
private readonly ChromeHtmlRenderer $renderer,
) {}
public function renderInvoice(string $html): string
{
return $this->renderer
->render($html, widthPt: 595.28)
->getPdfData();
}
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 修复工具组。
  1. 在最小的渲染测试中复现该 HTML 片段。
  2. 验证 maxHtmlSize、默认 CSS 与 Chrome 可执行文件路径。
  3. 按固定的点(point)宽度进行渲染。
  4. PdfReader::parse() 解析返回的 PDF 字节。
  5. 除非工作流程刻意选择其他页面,否则导入第 0 页。
  6. 为能复现各个失败的最小 HTML 添加测试夹具。
  7. 在 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() 并验证没有浏览器进程残留。
  • 性能测试会按模板与内容大小记录渲染时间。