Отправьте арабское предложение يوضح ملف PDF هذا простому TextOut, и возвращенная страница будет ошибочной сразу в двух отношениях. Слова идут слева направо, а не справа налево, а буквы отделены друг от друга в изолированных формах, а не соединяются в связные слова. Никаких ошибок. Delphi компилируется, файл открывается, а рецензент, читающий по-арабски, говорит вам, что вывод непригоден. Исправление заключается в одном вызове, а не в замене библиотеки: HotPDF направляет текст справа налево через отдельный метод, RtLTextOut, который обрабатывает изменение порядка, чего не сделает простой TextOut. Эта страница является рабочим справочником для этого метода: сигнатура и ее параметры, аргумент charset, который выбирает алфавит, побочный эффект на уровне документа, настройка шрифта, которая должна выполняться первой, и сбои, которые на самом деле достигают поддержки, каждый со своим исправлением
Сигнатура и параметры
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: PWORD; TextLength: Integer); overload;
X и Y привязывают выполнение к собственной системе координат страницы, измеряемой от верхнего левого угла с ростом Y вниз, это то же самое начало координат, которое использует каждый вызов TextOut; RtLTextOut изменяет порядок глифов, а не то, откуда страница производит измерения. X — это также левый конец рисуемого фрагмента, а не его правый край: вызов переупорядочивает глифы в визуальный порядок, а затем выкладывает их слева направо от X, поэтому строка, выровненная по правому краю, начинается на правом поле минус ширина строки, измеренная через CurrentPage.GetWideTextWidth после SetFont, который её нарисует. angle поворачивает базовую линию точно так же, как и в TextOut, поэтому 0 рисует горизонтальную линию. Text — это строка в логическом порядке, в котором вы бы ее набрали, а вторая перегрузка принимает те же данные UTF-16 в виде необработанного буфера PWORD с явным подсчетом кодовых единиц, что и форма для использования, когда текст поступает из API, а не из строки Delphi. В старых версиях Delphi, которые предшествовали разрешению перегрузки для этих типов, строковая форма предоставляется под именем RtLTextOutStr с идентичным списком параметров
Разделение труда между двумя вызовами вывода строгое. TextOut рисует кодовые точки в том порядке, в каком вы их передали, — это правильно для латиницы, кириллицы и CJK и неправильно для арабского и иврита. RtLTextOut сперва переупорядочивает каждую строку в визуальный порядок справа налево, затем рисует, сохраняя встроенные латинские слова и цифры читающимися слева направо внутри строки. HotPDF намеренно держит два метода раздельными, а не угадывает направление по символам, так что выбор того, какой вызов использовать, — это выбор того, какое поведение письма вы получите; используйте RtLTextOut для фрагментов справа налево, TextOut для всего остального и никогда не пропускайте одно через другое. Почему переупорядочивание вообще существует, что на самом деле делают алгоритм Unicode Bidirectional и контекстное соединение арабских букв и как шейпинг HotPDF обрабатывает то и другое — тема сопутствующей статьи о формировании арабского и RTL-текста с HotPDF; всё дальнейшее — практическая настройка

Аргумент кодировки (charset) решает алфавит
То, что сообщает RtLTextOut о том, компонует ли он арабский или иврит, — это не метод, это шрифт. SetFont принимает набор символов Windows в качестве своего четвертого аргумента, и это значение переносит правила алфавита в вызов справа налево: 178 выбирает арабский, 177 выбирает иврит. Установите кодировку, затем нарисуйте, и две строки ниже выйдут в правильном порядке чтения без какой-либо дальнейшей настройки
// Арабский: charset 178 указывает RtLTextOut применять арабские правила
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 100, 0, 'يوضح ملف PDF هذا');
// Иврит: charset 177 переключает правила на иврит
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 140, 0, 'קובץ PDF זה');
Одну деталь последовательности легко упустить: SetFont должен идти первым и должен повторяться после каждого AddPage, потому что текущий шрифт, включая кодировку, не переживает разрыв страницы. Забудьте о повторении, и вторая страница вернется к тому шрифту, который был активен, что для арабского языка обычно означает пустые прямоугольники
Он не переворачивает текст, который вы уже перевернули
Единственная ошибка, которая поглощает больше всего времени отладки здесь, — это передача в RtLTextOut строки, которую вы уже перевернули вручную. Люди обращаются к этому методу после первой попытки с простым TextOut, когда он вышел задом наперед, и общим временным решением является реверсирование символов в коде перед рисованием. RtLTextOut реверсирует внутренне самостоятельно, поэтому предварительно реверсированная строка реверсируется во второй раз и попадает туда же, откуда начала. Передайте текст в логическом порядке, в котором вы бы его набрали и прочитали вслух, и позвольте вызову выполнить переупорядочивание
Ловушка противнее обычного переворота, потому что дважды перевернутая строка может выглядеть правильно для одной тестовой фразы, состоящей только из арабских слов, а затем сломаться в тот же момент, когда в строке появится латинское слово или число. Внутри строки, идущей справа налево, эти встроенные фрагменты должны читаться слева направо, а реверс вручную разрушает эту вложенность, в то время как чисто арабский случай случайно выживает. Таким образом, ошибка проходит через ваш первый дымовой тест и позже всплывает в настоящем счете-фактуре с номером счета в нем. Избавьтесь от каждого ручного переворота в тот момент, когда вы переключаетесь на RtLTextOut
Побочный эффект направления, о котором стоит знать
Вызов RtLTextOut изменяет больше, чем просто рисуемую строку. Он также переворачивает предпочтение направления чтения документа справа налево, то же самое, что вы могли бы в противном случае установить самостоятельно через свойство Direction. Этот установщик добавляет vpDirection в настройки ViewerPreferences документа, которые говорят программе просмотра, как расположить развороты на две страницы и с какой стороны начинается макет разворота страниц. Когда весь документ на арабском или иврите, это именно то, что вам нужно, и вы получаете это бесплатно
Об этом стоит знать именно потому, что это незаметно на одной странице. Если документ в основном написан слева направо с одним блоком справа налево, первый вызов RtLTextOut все равно изменит предпочтение всего файла, и ничего на вашей одностраничной пробе этого не покажет. Симптом появляется через несколько недель, когда кто-то печатает двусторонний буклет, и развороты получаются зеркальными. Если это не то, чего вы хотите, явно верните значение свойства Direction обратно после запуска текста справа налево:
// RtLTextOut уже установил направление документа на RightToLeft;
// верните слева направо, если документ в основном LTR
Pdf.Direction := LeftToRight;
Для документа, который действительно читается справа налево, оставьте все как есть. Смысл в том, чтобы знать, что вызов имеет эффект на весь документ, поэтому сюрприза с буклетом никогда не произойдет
Зарегистрируйте шрифт, который вы поставляете, а не тот, на наличие которого надеетесь
Ни одно переупорядочивание не имеет значения, если в шрифте нет глифов для рисования. Классический сбой — это отчет, который безупречно отображается на компьютере разработчика, где Arial Unicode MS присутствует, и выходит в виде рядов пустых полей на сервере клиента, где Windows тихо заменила шрифт на шрифт без арабского покрытия вообще. Решение — перестать доверять установленным системным шрифтам и зарегистрировать шрифт, который вы поставляете вместе с приложением
// Поставьте известный арабский шрифт и зарегистрируйте его перед рисованием
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 100, 0, 'يوضح ملف PDF هذا');
Две границы сопровождают регистрацию. Шрифт, загруженный через RegisterUnicodeTTF, встраивается, а для обработки встроенного Unicode в HotPDF требуется документ в формате PDF 1.5 или более поздней версии; это кусается только в том случае, если что-то ниже по цепочке настаивает на PDF 1.4, но когда это происходит, сбой происходит тихо. Другая граница скорее юридическая, чем техническая: файлы TrueType несут биты разрешения на встраивание, и шрифт, который отлично выглядит на экране, может быть лицензирован таким образом, что запрещается поставлять его внутри клиентских документов. Подтвердите лицензию до встраивания, а не после жалобы
Полный консольный пример
Собирая кусочки воедино, вот автономная программа, которая пишет одну страницу с арабской строкой, ивритской строкой и смешанной строкой с латинским названием продукта. Каждый блок устанавливает свою кодировку, затем рисует в логическом порядке
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // основной модуль HotPDF
const
RightMargin = 545; // A4 имеет ширину 595 pt: правое поле 50 pt
// RtLTextOut выкладывает переупорядоченный фрагмент слева направо от X, поэтому
// выровненная по правому краю строка начинается на поле минус её измеренная ширина
procedure RightAlignedRtL(Page: THPDFPage; Y: Single; const S: WideString);
begin
Page.RtLTextOut(RightMargin - Page.GetWideTextWidth(S), Y, 0, S);
end;
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// Латинский заголовок идёт обычным путём TextOut
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 60, 0, 'Right-to-left text with HotPDF');
// Арабский: charset 178, логический порядок, переупорядочивание выполняет RtLTextOut
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
RightAlignedRtL(Pdf.CurrentPage, 100,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Иврит: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
RightAlignedRtL(Pdf.CurrentPage, 140,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Смешанная строка: встроенное латинское слово по-прежнему читается слева направо
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
RightAlignedRtL(Pdf.CurrentPage, 180,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
Запустите ее и откройте результат. Арабские и ивритские строки читаются справа налево, буквы соединяются там, где их соединяет алфавит, а в последней строке токен HotPDF находится слева направо внутри арабского блока. Такая вложенность является правильным двунаправленным результатом, а не ошибкой, даже если начинающие обозреватели регулярно считают это за нее; в статье о формировании, связанной выше, объясняется, почему правила Unicode требуют этого и как сформулировать ваши критерии приемки так, чтобы об этой проблеме никогда не сообщалось
Распространенные ошибки и их исправление
Каждая приведенная ниже ошибка возникала в реальной теме поддержки, и каждая ведет к одному из вышеперечисленных разделов
- Вывод читается задом наперед или перемешивается в смешанных строках — строка была перевернута вручную перед вызовом, обычно это оставшийся обходной путь из-за попытки использовать
TextOut. Удалите каждый ручной переворот и передайте логический порядок;RtLTextOutпереворачивает внутренне самостоятельно - Буквы печатаются раздельно в изолированных формах — текст прошел через обычный
TextOut, илиSetFontбыл вызван без кодировки справа налево. Рисуйте с помощьюRtLTextOutи передайте 178 для арабского или 177 для иврита в качестве четвертого аргументаSetFont - Пустые прямоугольники на машине клиента — Windows заменила шрифт без арабского или ивритского покрытия. Прекратите называть установленные шрифты; зарегистрируйте шрифт, который вы отправляете, через
RegisterUnicodeTTFи установитеSetFontдля него по этому имени - Вторая страница отображается неправильным шрифтом — текущий шрифт не переживает
AddPage. Повторяйте вызовSetFont, включая кодировку, после каждого разрыва страницы - Двухсторонние развороты печатаются зеркально в документе, который в основном идет слева направо — первый вызов
RtLTextOutперевернулDirectionдокумента как побочный эффект. УстановитеPdf.Direction := LeftToRightпосле выполнения справа налево - Встроенный текст Unicode тихо ухудшается ниже по цепочке — что-то в конвейере заставляет использовать PDF 1.4, а для обработки встроенного Unicode HotPDF требуется версия 1.5 или выше. Повысьте версию документа или снимите ограничение ниже по цепочке
До того, как формат будет выпущен, выполните проверку, помимо визуального осмотра: скопируйте текст обратно из программы просмотра, запустите поиск в документе, откройте файл на компьютере без шрифтов вашей разработки и положите один подлинный документ перед носителем языка. Полный контрольный список проверок, карта покрытия для каждого алфавита и корпус тестовых строк, который стоит создать, — все это описано в сопутствующей статье Арабский язык и формирование текста RTL с помощью HotPDF
Вызовы RtLTextOut, SetFont и RegisterUnicodeTTF, показанные здесь, являются частью компонента HotPDF для Delphi и C++Builder