嵌入并子集化 TrueType 字体
注册一个 TrueType 字体,用它绘制文字,并让写入器嵌入该字体的子集。本示例沿用与 examples/04-text-and-fonts.php 相同的内容路径,并额外加入一个已注册的 TrueType(.ttf)字体。
composer require nextpdf/core:^3这个版本约束适用于 nextpdf/core 包,本示例在 PHP 8.4 上运行。随附的 LiberationSans-Regular.ttf 测试夹具可让本示例独立运行。
概念总览
标题为“概念总览”的章节用 FontRegistry::register($path, $alias) 注册一个字体。注册表会解析文件、返回一个 FontInfo,并使用 TrueTypeParser 处理 .ttf 与 .otf 文件。若要启用该字体,请用 setFont($alias, ...) 选择它的别名。该调用也会记录使用到的码位。
子集化会在 save() 时自动运行。PDF 字体写入器会收集使用到的码位,并调用 FontSubsetter::subset();若为 Compact Font Format(CFF)或 OpenType 字体,则改为调用 CffSubsetter。当子集小于完整字体程序时,写入器会嵌入子集,并把 BaseFont 和 FontName 改写为一个六个大写字母、以加号连接的子集标签。这就是 ISO 32000-2 对字体子集所要求的 ABCDEF+FontName 形式。写入器会把嵌入的 TrueType 程序以 FontFile2 形式存放在字体描述符中(ISO 32000-2)。
子集前缀根据 PostScript 名称确定性生成,因此确定性构建会产生稳定的标签。这就是本示例的可重现性 profile 为 structural 的原因。structural profile 会归一化子集前缀和 trailer 的 /ID,而不是逐字节比对它们。
API 接口
标题为“API 接口”的章节FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfo—NextPDF\Typography\FontRegistry。setFont(string $family, string $style = '', float $size = 12.0): static—NextPDF\Core\Concerns\HasTypography;把已注册的别名当作$family传入。- 子集化是写入器的内部行为(
NextPDF\Writer\PdfFontWriter->NextPDF\Typography\FontSubsetter)。没有公开的开关可以开启或关闭它:只要码位已知且子集较小,写入器一律会进行子集化。
完整 PHPDoc 表格由源代码生成。
代码示例 — 快速上手
标题为“代码示例 — 快速上手”的章节<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/MyFont-Regular.ttf', alias: 'MyFont');
$doc = Document::createStandalone();$doc->addPage();$doc->setFont('MyFont', '', 14);$doc->cell(0, 10, 'Rendered with an embedded, subset TrueType face.', newLine: true);
$doc->save(__DIR__ . '/out.pdf');Document::createStandalone() 会创建自己的注册表。若要使用你自己填入的注册表,请通过 DocumentFactory 建立文档,如生产环境示例所示。工厂会确保写入器读取你已注册的字体。
代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节这个示例可独立运行,也能在测试夹具环境中运行。它会注册随附的 LiberationSans-Regular.ttf 并通过 DocumentFactory 绘制,因此运行时使用的正是你填入的注册表,并依赖保存时自动进行的子集化。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// A bundled TrueType test fixture keeps this recipe self-contained.// Replace with a font you have the right to embed.$fontPath = __DIR__ . '/../../fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';if (!is_file($fontPath)) { // Fall back to the repository-relative fixture location. $fontPath = dirname(__DIR__, 2) . '/fonts/test-fixtures/LiberationSans/LiberationSans-Regular.ttf';}
$fontRegistry = new FontRegistry();$fontRegistry->register($fontPath, alias: 'LiberationSans');
$imageRegistry = new ImageRegistry(maxCacheBytes: 0);$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$doc = $documentFactory->create();$doc->setTitle('Embedded Subset Font');$doc->addPage();
$doc->setFont('LiberationSans', '', 20);$doc->cell(0, 14, 'Embedded TrueType face', newLine: true);
$doc->setFont('LiberationSans', '', 12);$doc->multiCell(0, 7, 'Only the glyphs used by this document are embedded. ' . 'The writer subsets the font program and rewrites the BaseFont with a ' . 'deterministic six-letter subset prefix, for example ABCDEF+LiberationSans.');
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/embed-and-subset-fonts.pdf');
echo "Wrote embed-and-subset-fonts.pdf\n";预期的 STDOUT:
Wrote embed-and-subset-fonts.pdf若要确认已生成子集,请打开输出文件并检查字体字典。BaseFont 会显示为 <TAG>+LiberationSans,描述符中会带有 FontFile2。运行 qpdf --check 不会报告任何结构性错误。
边界情况与陷阱
标题为“边界情况与陷阱”的章节- 注册表的归属。
Document::createStandalone()会创建自己的注册表。你在另一个FontRegistry上注册的字体对它不可见。请用DocumentFactory传入你的注册表,如生产环境示例所示。 - 嵌入权利。 子集化并不会改变授权。只嵌入你已取得授权可嵌入的字体。有些字体会设置嵌入限制位;解析器会读取这些位,但是否合规仍由你负责。
- CFF/OpenType 路径。
.otf和 CFF 字体由CffSubsetter进行子集化,而非FontSubsetter。其行为与子集标签的改写等效,只是代码路径不同。 - 没有体积优势。 有时候子集并不会比原始字体小,这在极小的字体或所有字形都被使用时会发生。在这种情况下,写入器会嵌入原始字体程序,且不带子集标签。这是正确行为,而非失败。
- CJK 字体。 大型中文、日文与韩文(CJK)字体会依据 ADR-008 采用分层子集化策略,并搭配一个隔离的子进程和一个 PHP 原生的回退机制。关于 CJK 的细节与当前的流水线状态,请参阅以具备 cmap 感知的编码设置 CJK 文字一节。
解析只会遍历字体表一次,子集化成本会随字形数量增加。非 CJK 拉丁字体会在进程内完成子集化,并满足 wall_ms: 1500, peak_mb: 96 的预算。大型 CJK 字体会被导向隔离子进程,并设有两秒的挂钟超时和一个 PHP 原生回退机制(ADR-008)。这种导向方式意味着缓慢或崩溃的子集化无法阻塞调用端。
安全注意事项
标题为“安全注意事项”的章节字体文件属于不受信任的二进制输入。解析器会拒绝 stream-wrapper 路径和 null 字节。CJK 子集化子进程运行时不会继承任何数据库连接、文件句柄或框架状态,并会在崩溃或超时时安全回退(ADR-008)。请验证从终端用户接收的字体来源。
符合性
标题为“符合性”的章节| 声明 | 规范 | 条款 | 参考 ID |
|---|---|---|---|
| 字体子集的 BaseFont/FontName 会带有一个六个大写字母、以加号连接的子集前缀。 | ISO 32000-2 | iso32000_2_sec9#x1.x66.p2(第 9 节) | |
| 嵌入的 TrueType 字体程序会以 FontFile2 形式存放在字体描述符中。 | ISO 32000-2 | iso32000_2_sec9#x1.x65.p15(第 9 节) |
本示例演示 NextPDF 如何嵌入并子集化一个 TrueType 字体,并生成一个符合规范的子集前缀。它不会主张字体授权的合规性,嵌入权利由集成者负责。
商业情境
标题为“商业情境”的章节不适用。