Перейти к содержимому

Рендеринг арабского HTML с направлением справа налево

Этот рецепт показывает, как рендерить HTML с направлением справа налево (RTL) в PDF с помощью writeHtml(). Задайте CSS-свойство direction: rtl и зарегистрируйте шрифт с поддержкой арабского. Движок переупорядочивает текст в визуальный порядок по алгоритму двунаправленного письма Unicode (UAX #9) и формирует арабские буквы в контекстные формы. В рецепте рендерится небольшой счёт на арабском. RTL используется для арабского, иврита, персидского и урду. Иврит переупорядочивается без формирования, и для этого письма это корректно.

Окно терминала
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 start и end интерпретируются относительно направления, поэтому start сопоставляется с правым краем для содержимого RTL.

Задавайте направление через 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-свойства: direction, font-family, text-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.
  • Двунаправленное разрешение выполняется для каждого текстового фрагмента. Нейтральные символы между двумя встроенными элементами, такими как <span> рядом с <b>, на одной строке не разрешаются.
  • Узкие арабские колонки измеряются по логическому тексту. Переносы строк рассчитываются по логическому тексту до формирования, поэтому очень узкая арабская колонка может перенести строку чуть раньше или позже.
  • Сформированный арабский требует покрытия 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.

Неприменимо.