跳转到内容

嵌入并子集化 TrueType 字体

注册一个 TrueType 字体,用它绘制文字,并让写入器嵌入该字体的子集。本示例沿用与 examples/04-text-and-fonts.php 相同的内容路径,并额外加入一个已注册的 TrueType(.ttf)字体。

Terminal window
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。当子集小于完整字体程序时,写入器会嵌入子集,并把 BaseFontFontName 改写为一个六个大写字母、以加号连接的子集标签。这就是 ISO 32000-2 对字体子集所要求的 ABCDEF+FontName 形式。写入器会把嵌入的 TrueType 程序以 FontFile2 形式存放在字体描述符中(ISO 32000-2)。

子集前缀根据 PostScript 名称确定性生成,因此确定性构建会产生稳定的标签。这就是本示例的可重现性 profile 为 structural 的原因。structural profile 会归一化子集前缀和 trailer 的 /ID,而不是逐字节比对它们。

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry
  • setFont(string $family, string $style = '', float $size = 12.0): staticNextPDF\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-2iso32000_2_sec9#x1.x66.p2(第 9 节)
嵌入的 TrueType 字体程序会以 FontFile2 形式存放在字体描述符中。ISO 32000-2iso32000_2_sec9#x1.x65.p15(第 9 节)

本示例演示 NextPDF 如何嵌入并子集化一个 TrueType 字体,并生成一个符合规范的子集前缀。它不会主张字体授权的合规性,嵌入权利由集成者负责。

不适用。