文字:塑形接缝、CJK 与文字段处理
文字模块提供的是塑形接缝:一个小型接口,用于把 UTF-8 文字段转换为定位后的字形;主机具备真正的 OpenType 后端时使用它,不可用时则退回确定性的回退路径;此外还提供一个注册表,用来登记特定 script 的塑形器。
composer require nextpdf/core:^3概念总览
标题为“概念总览”的章节ShaperInterface 是文字排版管线与 OpenType 塑形引擎之间的接缝。它刻意保持最小化:只有一个 shape() 方法,接收一个 ShaperInput 并返回一个 ShapingResult。返回类型是使用方唯一可见的输出——实现不得泄漏塑形引擎的内部细节,而强类型的返回值会在结构上强制这一点。ShapingResult 包含 GlyphRun 记录列表、返回的原始文字、script 与方向,以及一个 shaperImpl 标记,用来指出这个结果由哪个后端产生。
后端选择是显式的,并且会如实反映能力。ShaperFactory 会执行一次能力探测:如果主机具备可运行的 HarfBuzz 绑定,create() 就返回以 HarfBuzz 为后端的塑形器;否则返回 NullShaper。NullShaper 是一个直通式的回退路径。它会为每个 Unicode 码位输出一个合成字形,且宽进量为零、偏移量为零。它会标记结果,让可观测性能够检测到这次回退。它把宽进量的解析(resolve)留给字体度量模块处理。这是一条有明确记录的降级路径,而不是完整塑形:替换、连字、标记定位与上下文字形都需要真正的后端才能完成。wouldUseRealShaper() 是一个诊断用判断式。生产环境代码应根据结果上的 shaperImpl 标记来分支。
特定 script 的塑形是一个 SPI,而不是内置实现。ScriptShaperRegistry 是一个 PSR-11 风格的注册表,会根据 ISO 15924 script 标记解析出 MongolianShaperInterface 或 TibetanShaperInterface。注册表以不区分大小写的方式存储键值,并把 script 代码是否可接受的判断交由单一可信来源决定。注册表与 script 塑形器接口构成一份冻结的合约,因此扩展功能可以在不修改调用方的情况下注册一个 Phase-12 提供者。引擎本身只提供这道接缝,复杂 script 的提供者由使用方自行提供。
CJK 文字段处理位于排版的编码接缝上。嵌入的 CJK TrueType 字体会以 Type 0 字体输出,搭配 Identity-H CMap 与一个 CIDFontType2 后代字体——ISO 32000-2 §9.7.4(RAG 摘要因授权上限被截断;记录于 _downgraded-claims-o3.md)。嵌入 TrueType 程序时,Type 2 CIDFont 会通过 CIDToGIDMap 条目,把字符标识符映射到字形的 Index(索引)——ISO 32000-2 §9(摘要由 B1 合约页面固定)。子集化工具会精确保留原始字形编号,因此 /CIDToGIDMap /Identity 对子集而言仍然有效。CjkFontValidator 是一个诊断工具,会在选用某个候选字体之前,检查它是否覆盖某个 script 所需的 Unicode 区块。
API 接口
标题为“API 接口”的章节| 类型 | 种类 | 主要成员 | 稳定性 | 起始版本 |
|---|---|---|---|---|
ShaperInterface | 接口(interface) | shape(ShaperInput): ShapingResult | 稳定 | 3.2.0 |
ShaperFactory | final class | default()、create()、wouldUseRealShaper() | 稳定 | 3.2.0 |
NullShaper | final readonly class | 直通式的备援塑形器 | 稳定 | 3.2.0 |
ShapingResult | final readonly class | $glyphRuns、$originalText、$script、$direction、$shaperImpl | 稳定 | 3.2.0 |
ScriptShaperRegistry | final class | registerMongolian()、getMongolian()、hasMongolian(),以及对应的 Tibetan 版本 | 稳定 | 3.1.0 |
CjkFontValidator | final class | validateCoverage()、detectScript()、isCjkCodepoint() | 稳定 | 1.0.0 |
无论是 register*、get* 与 has* 形式,还是 ScriptShaperRegistry 与各 script 塑形器接口,都构成一份冻结的合约。按设计,ShapingResult 是塑形器唯一对使用方可见的输出。
代码示例——快速上手
标题为“代码示例——快速上手”的章节<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Font\Shaper\ShaperFactory;use NextPDF\Font\Shaper\ShaperImpl;
$factory = ShaperFactory::default();$shaper = $factory->create();
// Branch on the result tag, not on the concrete class.$wouldShape = $factory->wouldUseRealShaper() ? 'HarfBuzz backend available' : 'NullShaper fallback (degraded — no substitution or positioning)';
echo $wouldShape, "\n";ShaperFactory::default() 会接入生产环境的能力探测。create() 会在工厂生命周期内缓存所选后端。能力状态的真实判断来自 wouldUseRealShaper(),以及每个结果上的 shaperImpl 标记。
代码示例——正式环境
标题为“代码示例——正式环境”的章节<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Text\Shaping\MongolianShaperInterface;use NextPDF\Text\Shaping\ScriptShaperRegistry;
final readonly class ComplexScriptBootstrap{ public function __construct(private ScriptShaperRegistry $registry) {}
/** * Register a consumer-supplied Mongolian shaper provider at boot so * the layout pipeline can resolve it by ISO 15924 script tag. */ public function register(MongolianShaperInterface $mongolian): void { $this->registry->registerMongolian($mongolian); }
public function hasMongolian(): bool { return $this->registry->hasMongolian(); }}注册表是复杂 script 提供者的集成点。引擎提供这道接缝以及冻结的访问器形式。Mongolian 与 Tibetan 的实现则由使用方提供。
边界情况与陷阱
标题为“边界情况与陷阱”的章节- 一个
NullShaper的结果,其宽进量为零、偏移量为零。不要把这些位置直接传给文字排版——请改为从字体度量模块解析宽进量,并通过shaperImpl标记检测这次回退。 - 空输入会产生一个空的
glyphRuns列表,而不是一个空文字段。使用方的迭代代码不需要特别处理长度为零的文字段。 ScriptShaperRegistry没有直接实现Psr\Container\ContainerInterface,这样强类型访问器才能在静态分析中保留收窄后的返回类型。请使用getMongolian()与getTibetan(),而不是通用的get()。- script 标记是以规范的 ISO 15924 alpha-4 值来比对,且以不区分大小写的方式存储。传入
Mong或Tibt。查找时大小写无关紧要。 - CJK 扩展 B 区字符位于 Unicode 第 2 平面,会要求子集中生成一个 cmap Format 12 子表。编码路径会处理这种情况。不要假设所有 CJK 内容都落在基本多文种平面内。
能力探测在每个 ShaperFactory 实例上只执行一次,且后端会被缓存,因此重复调用 create() 几乎没有成本。NullShaper 的成本与输入文字段的码位数呈线性关系,且不涉及 I/O。ScriptShaperRegistry 的解析是一次常数时间的键值查找。CjkFontValidator 会按固定间隔抽样码位,而不是逐一测试每一个码位,因此即使面对 20,000 个字形的 CJK 字体,覆盖率检查的成本仍然很低。1500 毫秒实际耗时与 64 MB 峰值用量的 performance_budget 足以涵盖一次典型执行。执行真实塑形时,主要成本来自 OpenType 后端本身;当回退启用时,这部分不属于本流程范围。
安全性注意事项
标题为“安全性注意事项”的章节塑形接缝接收一个 UTF-8 字符串。NullShaper 会以尽力切分的方式容忍格式错误的 UTF-8,而不会抛出异常,因为这个回退路径的既定合约就是「不做真正的塑形」。调用方已预期会得到质量较低的输出。字节偏移的簇合约采用面向字节的长度,这对多字节输入而言是正确的,也避免了簇映射中差一个码位的缺陷。真正的后端(若存在)是一个第三方原生库。请把它的输入视为不可信,并在上游限制文字段长度。script 塑形器注册表存储的是使用方提供的提供者——这些实现的信任边界属于使用方,而非引擎。
符合性
标题为“符合性”的章节| 宣称 | 标准 | 条款 | 证据 |
|---|---|---|---|
嵌入的 CJK TrueType 字体会以 Type 0 字体输出,搭配 Identity-H CMap 与一个 CIDFontType2 后代字体。 | ISO 32000-2 | §9.7.4 | 摘要因授权上限而被截断,此处仅保留前缀 7a5258772f508e3b,详见 _downgraded-claims-o3.md |
嵌入的 Type 2 CIDFont 会通过 CIDToGIDMap 把字符标识符映射到字形索引。 | ISO 32000-2 | §9 |
两项条款均已改写。第二项以固定摘要为准(沿用自 B1 合约页面),第一项则由 ADR-013 与 cmap 编码器开发者概览佐证。NextPDF 不重现规范性文本。塑形后端与 PDF 符合性无关。此处的符合性宣称针对的是编码接缝所产生的 CJK 字体字典输出,ADR-013 与 cmap 编码器开发者概览中有进一步说明。
商业情境
标题为“商业情境”的章节一套高级文字前处理管线与提取服务,构建在 Core 的塑形接缝与文字段处理值类型之上。Core 文字模块会提供这道接缝、回退路径与 script 塑形器注册表,且无需授权。省略转换链接是刻意的。
另请参阅
标题为“另请参阅”的章节- Typography:注册表、子集化、CMap、编码、BiDi ——编码接缝与 BiDi 引擎。
- Font:值类型、嵌入、回退 ——塑形器输入所引用的
FontInfo。 - Contracts / Typography ——位于塑形上游的文字前处理器合约。