跳转到内容

渲染从右到左的阿拉伯语 HTML

使用 writeHtml() 将从右到左(RTL)的 HTML 渲染为 PDF。设置 CSS direction: rtl 属性,并注册一款支持阿拉伯语的字体。引擎会使用 Unicode 双向算法(UAX #9)将文本重排序为视觉顺序,并把阿拉伯字母塑形为依上下文变化的字形。本示例会渲染一张小型阿拉伯语发票。RTL 适用于阿拉伯语、希伯来语、波斯语和乌尔都语。希伯来语会被重排序,但不会被塑形;这对该文字系统而言是正确的。

Terminal window
composer require nextpdf/core

你还需要一款支持阿拉伯语的 TrueType 或 OpenType 字体。该字体的字符映射表必须覆盖 Arabic Presentation Forms-B 区块。Noto Naskh Arabic 和 Amiri 都是合适的开源授权字体。

RTL 渲染需要两个输入:CSS direction: rtl 属性,以及一款已注册的阿拉伯语字体。

direction: rtl 会让布局从右向左放置文本。随后,引擎会用 Unicode 双向算法(UAX #9)解析视觉顺序。混合内容会正确排序:拉丁单词、阿拉伯单词和数字都会保留各自的方向。跟在阿拉伯语文本之后的数字,其各位数字仍保持从左到右。

阿拉伯语是连写文字,因此每个字母都会根据相邻字符使用不同的字形。引擎会为每个字母选择词首、词中、词尾或独立形式,并应用 Lam-Alef 连字。这种上下文塑形需要一款字符映射表覆盖 Arabic Presentation Forms-B 区块的字体。仅含拉丁文的字体(包括标准 14 款字体)无法绘制阿拉伯语。

在表格中,每个单元格都会独立进行重排序和塑形,并且单元格会对齐到起始边:在 direction: rtl 下就是右边缘。逻辑性的 text-align 取值 startend 会按方向解析,因此对 RTL 内容而言,start 映射到右边缘。

请用 CSS direction 属性设置方向。HTML 的 dir 属性不会映射到该属性。当前实现的边界请参见 RTL — 当前限制

符号位置作用
Document::writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput在当前光标位置渲染 HTML 片段。
FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry以别名注册阿拉伯语字体。
DocumentFactory::create(): DocumentNextPDF\Core\DocumentFactory创建一个会读取你已填充注册表的文档。

本示例使用以下 CSS 属性:directionfont-familytext-align。在 CSS font-family 中,通过注册表别名引用已注册的字体。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
$fontRegistry = new FontRegistry();
$fontRegistry->register(__DIR__ . '/NotoNaskhArabic-Regular.ttf', alias: 'ArabicFont');
$documentFactory = new DocumentFactory($fontRegistry, new ImageRegistry(maxCacheBytes: 0));
$doc = $documentFactory->create();
$doc->addPage();
$doc->writeHtml(
'<div style="direction: rtl; font-family: \'ArabicFont\';">'
. '<h1>فاتورة</h1>'
. '<p>المبلغ الإجمالي 380.00</p>'
. '</div>'
);
$doc->save(__DIR__ . '/rtl-arabic.pdf');

标题会从右到左渲染,而数字 380.00 在这句阿拉伯语句子中仍保持从左到右。

这个自包含示例会渲染一张阿拉伯语发票表格。每个单元格都设置了 direction: rtl 并使用已注册的阿拉伯语字体,因此引擎会对每一行进行重排序和塑形,然后把单元格对齐到右边缘。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// Supply an Arabic-capable face whose cmap covers Arabic Presentation Forms-B.
// Embed only fonts you are licensed to embed.
$fontPath = __DIR__ . '/NotoNaskhArabic-Regular.ttf';
if (!is_file($fontPath)) {
fwrite(STDERR, "Arabic font not found at {$fontPath}\n");
exit(1);
}
$fontRegistry = new FontRegistry();
$fontRegistry->register($fontPath, alias: 'ArabicFont');
$documentFactory = new DocumentFactory($fontRegistry, new ImageRegistry(maxCacheBytes: 0));
$doc = $documentFactory->create();
$doc->setTitle('Arabic invoice');
$doc->addPage();
$html = <<<'HTML'
<div style="direction: rtl; font-family: 'ArabicFont'; font-size: 12pt;">
<h1>فاتورة</h1>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<th style="border: 1px solid #333; padding: 6px;">الوصف</th>
<th style="border: 1px solid #333; padding: 6px;">المبلغ</th>
</tr>
<tr>
<td style="border: 1px solid #333; padding: 6px;">خدمات استشارية</td>
<td style="border: 1px solid #333; padding: 6px;">380.00</td>
</tr>
<tr>
<td style="border: 1px solid #333; padding: 6px;">الإجمالي</td>
<td style="border: 1px solid #333; padding: 6px;">380.00</td>
</tr>
</table>
</div>
HTML;
$doc->writeHtml($html);
$out = getenv('NEXTPDF_OUT');
$doc->save($out !== false ? $out : __DIR__ . '/render-rtl-arabic-html.pdf');
echo "Wrote the Arabic invoice PDF\n";
HTML;$doc->writeHtml($html);$out = getenv('NEXTPDF_OUT');$doc->save($out !== false ? $out : __DIR__ . '/render-rtl-arabic-html.pdf');echo "Wrote the Arabic invoice PDF\n";">
  • 在构建文档之前注册字体。 Document::createStandalone() 会构造自己的注册表,无法看到你在别处注册的字体。请通过 DocumentFactory 构建,让写入器读取你的注册表;两个示例都是这样做的。
  • 让 CSS font-family 与注册表别名一致。 你传给 register(..., alias: 'ArabicFont') 的名称,就是你在 CSS 中引用的名称。
  • 使用 CSS direction,而非 HTML dir 属性。 只有这个 CSS 属性才会切换布局。
  • 跟在阿拉伯语之后的数字保持从左到右。 这遵循 UAX #9:跟在阿拉伯字母之后的欧洲数字会解析为阿拉伯数字,并保持其各位数字的顺序,因此 380.00 不会被反转。

当前实现会对 RTL 文本进行重排序和塑形,并对齐表格单元格。以下边界仍然存在。每一项都需要未来引入逐行的行内格式化行盒:

  • 表格之外的块级与行内对齐。 表格单元格之外的块级和行内文本会被重排序和塑形,但仍会从起始(左)边缘渲染。表格之外文本的右对齐或居中对齐,以及 text-align: justify 的分布,尚未应用。表格单元格会对齐。
  • HTML dir 属性不会映射到 direction 请用 CSS direction 属性设置方向。
  • 双向解析是按文本运行(text run)进行的。 中性字符不会跨越同一行上的两个行内元素(例如紧邻 <b><span>)进行解析。
  • 窄的阿拉伯语列按逻辑文本测量。 换行会按逻辑文本,也就是塑形前的文本来测量,因此一个非常窄的阿拉伯语列可能会把某一行断得略早或略晚。
  • 塑形后的阿拉伯语需要 Presentation Forms-B 覆盖。 该字体必须覆盖 Arabic Presentation Forms-B 区块。对仅依赖 OpenType GSUB 替换的字体的支持,以及 HarfBuzz 塑形路径,属于未来工作。非阿拉伯语字体无法绘制阿拉伯语。

渲染耗时随字形数量线性增长。双向重排序和上下文塑形按行运行,相比从左到右文本只增加较小的常数因子。本示例的预算为 wall_ms: 1500, peak_mb: 64

校验用户提供字符串的长度,使输出大小保持有界。引擎只渲染文本,不解释文本,也不会运行任何脚本。如果你通过远程 @font-face 源加载字体,则该次抓取由安全的外部资源策略管控;为获得可预测的输出,请优先使用本地字体文件。

陈述规范条款reference_id
视觉顺序是通过将字符运行从最高层级向下反转到最低奇数层级而产生的。Unicode UAX #9§3.3.6 Rule L2 (uax9#3.3.6.p13)814977a77019d728dc562a612098a82dc260f6844f5998eca5fe7a3baf3394af
跟在阿拉伯字母之后的欧洲数字会解析为阿拉伯数字,因此其各位数字保持从左到右的顺序。Unicode UAX #9§3.3.4 Rule W2 (uax9#3.3.4.p9)5747405357772797d62b3f4ba79328557fa0c4273a1dd5ffa8d996f24c78e120

阿拉伯语上下文塑形(词首、词中、词尾和独立形式,加上 Lam-Alef 连字)是由测试套件覆盖并经过验证的引擎能力,而非单独的一致性主张。它要求一款字符映射表覆盖 Arabic Presentation Forms-B 区块的字体。

不适用。