跳转到内容

自定义布局引擎与布局阶段的文本拦截

NextPDF 并未提供可插拔的布局引擎接口。公开的布局扩展契约是 TextPreprocessorInterface,它会在布局阶段接入文本处理。你也会获得内容生命周期事件,用于观察布局生成的内容。

Terminal window
composer require nextpdf/core:^3

布局管线(pipeline)属于内部实现。它涵盖字形布局、字体子集化、ToUnicode CMap 输出以及结构树,NextPDF 不允许替换它。稳定的字节输出与标记式 PDF 一致性,都依赖单一且受控的构建流程。

NextPDF 确实对外公开的是布局之前的切入点:TextPreprocessorInterface。实现会获取原始文本并返回一份切分后的结果,而且这发生在该文本进入字形布局、字体子集化、ToUnicode CMap 或结构树之前。这是在不触碰布局引擎的情况下更改文本内容的受支持做法。

这份契约在其源 PHPDoc 中包含一条硬性规则:实现不得改变布局的工作方式。它不得加入会影响布局的字符,例如换行(line feed)、回车(carriage return)或制表符(tab),而且必须维持逻辑阅读顺序。预处理器声明的是一次内容替换;它不做布局决策。请遵守这条规则,否则会破坏稳定输出与无障碍性。

若要观察布局结果——而不是更改布局——请改用 行为触发器与事件监听器一节中的内容生命周期事件。ContentRenderedEvent 会在内容绘制到页面之后触发。FontLoadedEvent 会针对每个字体族与样式各触发一次。

NextPDF\Contracts\TextPreprocessorInterface(稳定,自 1.9.0 起):

方法返回用途
process(string $text)TextPreprocessResult在进入 render 管线之前转换原始文本;返回一份带有遮蔽(redaction)元数据的分段结果。

返回的 NextPDF\Contracts\TextPreprocessResult 是一个冻结的值对象。它的构造函数签名与公开属性是稳定的,不会在次版本或补丁版本中变动。未来可能会新增方法。

这个小型预处理器会遮蔽一个固定的令牌(token)。它不会加入任何会影响布局的字符,并会维持阅读顺序。

<?php
declare(strict_types=1);
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Contracts\TextPreprocessResult;
use NextPDF\Contracts\TextSegment;
final class TokenMaskingPreprocessor implements TextPreprocessorInterface
{
public function process(string $text): TextPreprocessResult
{
$masked = \str_replace('SECRET-TOKEN', '••••••••••••', $text);
return new TextPreprocessResult([
new TextSegment($masked, redacted: $masked !== $text),
]);
}
}

生产环境中的预处理器会把匹配规则集中放在同一处。遇到错误模式时,它会按失败关闭(fail closed)方式处理,且绝不记录原始文本。

<?php
declare(strict_types=1);
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Contracts\TextPreprocessResult;
use NextPDF\Contracts\TextSegment;
use Psr\Log\LoggerInterface;
final class PatternRedactionPreprocessor implements TextPreprocessorInterface
{
/**
* @param non-empty-string $pattern A valid PCRE pattern for sensitive spans
*/
public function __construct(
private readonly string $pattern,
private readonly LoggerInterface $logger,
) {}
public function process(string $text): TextPreprocessResult
{
$result = \preg_replace($this->pattern, '[REDACTED]', $text);
if ($result === null) {
// Fail closed: never emit unredacted text on a pattern error.
$this->logger->error('Redaction pattern failed; substituting empty text');
return new TextPreprocessResult([new TextSegment('', redacted: true)]);
}
return new TextPreprocessResult([
new TextSegment($result, redacted: $result !== $text),
]);
}
}
  • 不提供布局替换。 没有任何契约可用来替换盒子布局、断行或分页。接入第三方布局引擎的需求,按设计即不在范围内。
  • 规则强制。process() 中加入 \n\r\t,会破坏布局并使稳定输出失效。引擎信任这条规则;它不会重新检查你的输出是否含有会影响布局的字符。
  • 阅读顺序。 重排 segment(段落)会破坏标记式 PDF 的阅读顺序与 PDF/UA 一致性。
  • 单一职责。 预处理器声明的是一次内容替换。请改用生命周期事件来观察,而不要通过 process() 推送副作用。

process() 会在布局热路径上针对每个文本 run 执行一次。请让它保持内存轻量。请在构造函数里一次性编译好模式,而不是每次调用时才编译。没有绑定任何监听器时,内容生命周期事件不会产生任何成本。

TextPreprocessorInterface 是在敏感内容抵达内容流、字体子集或元数据之前将其移除的受支持切入点。因为它在子集化与 ToUnicode CMap 之前执行,被遮蔽的字形永远不会进入文件。请将预处理器的失败视为失败关闭(fail-closed),改用空白或遮蔽后的文本,而不是输出原始内容。

本页不适用任何规范性的签名或归档声明。阅读顺序规则使这份契约与标记式 PDF 的需求一致。标签级别的一致性涵盖在无障碍参考文档中。

NextPDF Pro 提供生产环境级别的文本预处理策略,包括针对常见文档类型调校的 PII 遮蔽。在 Core 中,你可以自行编写 TextPreprocessorInterface,也可以通过同一份公开契约使用经过验证的付费版构建。

术语表定义了 text preprocessor(文本预处理器)与 extension point(扩展点);各自的标准定义请参阅已发布的术语表。