Отправьте арабское предложение يوضح ملف 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 изменяет порядок глифов, а не то, откуда страница производит измерения. angle поворачивает базовую линию точно так же, как и в TextOut, поэтому 0 рисует горизонтальную линию. Text — это строка в логическом порядке, в котором вы бы ее набрали, а вторая перегрузка принимает те же данные UTF-16 в виде необработанного буфера PWORD с явным подсчетом кодовых единиц, что и форма для использования, когда текст поступает из API, а не из строки Delphi. В старых версиях Delphi, которые предшествовали разрешению перегрузки для этих типов, строковая форма предоставляется под именем RtLTextOutStr с идентичным списком параметров
Разделение труда между двумя вызовами вывода является строгим. TextOut рисует кодовые точки в том порядке, в котором вы их передаете, что правильно для латиницы, кириллицы и CJK и неправильно для арабского и иврита. RtLTextOut сначала переупорядочивает каждую строку в визуальном порядке справа налево, а затем рисует, сохраняя встроенные латинские слова и цифры читаемыми слева направо внутри строки. HotPDF намеренно держит эти два метода разделенными, а не угадывает направление по символам, поэтому выбор того, какой из них вызвать, — это выбор того, поведение какого алфавита вы получите; используйте RtLTextOut для выполнения справа налево, TextOut для всего остального и никогда не направляйте одно через другое. Почему изменение порядка вообще существует, что на самом деле делают алгоритм двунаправленности Unicode и арабское контекстное объединение, и где заканчивается формирование HotPDF, является темой сопутствующей статьи Арабский язык и формирование текста RTL с помощью HotPDF; все нижеперечисленное является практической настройкой

Аргумент кодировки (charset) решает алфавит
То, что сообщает RtLTextOut о том, компонует ли он арабский или иврит, — это не метод, это шрифт. SetFont принимает набор символов Windows в качестве своего четвертого аргумента, и это значение переносит правила алфавита в вызов справа налево: 178 выбирает арабский, 177 выбирает иврит. Установите кодировку, затем нарисуйте, и две строки ниже выйдут в правильном порядке чтения без какой-либо дальнейшей настройки
// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Одну деталь последовательности легко упустить: SetFont должен идти первым и должен повторяться после каждого AddPage, потому что текущий шрифт, включая кодировку, не переживает разрыв страницы. Забудьте о повторении, и вторая страница вернется к тому шрифту, который был активен, что для арабского языка обычно означает пустые прямоугольники
Он не переворачивает текст, который вы уже перевернули
Единственная ошибка, которая поглощает больше всего времени отладки здесь, — это передача в RtLTextOut строки, которую вы уже перевернули вручную. Люди обращаются к этому методу после первой попытки с простым TextOut, когда он вышел задом наперед, и общим временным решением является реверсирование символов в коде перед рисованием. RtLTextOut реверсирует внутренне самостоятельно, поэтому предварительно реверсированная строка реверсируется во второй раз и попадает туда же, откуда начала. Передайте текст в логическом порядке, в котором вы бы его набрали и прочитали вслух, и позвольте вызову выполнить переупорядочивание
Ловушка противнее обычного переворота, потому что дважды перевернутая строка может выглядеть правильно для одной тестовой фразы, состоящей только из арабских слов, а затем сломаться в тот же момент, когда в строке появится латинское слово или число. Внутри строки, идущей справа налево, эти встроенные фрагменты должны читаться слева направо, а реверс вручную разрушает эту вложенность, в то время как чисто арабский случай случайно выживает. Таким образом, ошибка проходит через ваш первый дымовой тест и позже всплывает в настоящем счете-фактуре с номером счета в нем. Избавьтесь от каждого ручного переворота в тот момент, когда вы переключаетесь на RtLTextOut
Побочный эффект направления, о котором стоит знать
Вызов RtLTextOut изменяет больше, чем просто рисуемую строку. Он также переворачивает предпочтение направления чтения документа справа налево, то же самое, что вы могли бы в противном случае установить самостоятельно через свойство Direction. Этот установщик добавляет vpDirection в настройки ViewerPreferences документа, которые говорят программе просмотра, как расположить развороты на две страницы и с какой стороны начинается макет разворота страниц. Когда весь документ на арабском или иврите, это именно то, что вам нужно, и вы получаете это бесплатно
Об этом стоит знать именно потому, что это незаметно на одной странице. Если документ в основном написан слева направо с одним блоком справа налево, первый вызов RtLTextOut все равно изменит предпочтение всего файла, и ничего на вашей одностраничной пробе этого не покажет. Симптом появляется через несколько недель, когда кто-то печатает двусторонний буклет, и развороты получаются зеркальными. Если это не то, чего вы хотите, явно верните значение свойства Direction обратно после запуска текста справа налево:
// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;
Для документа, который действительно читается справа налево, оставьте все как есть. Смысл в том, чтобы знать, что вызов имеет эффект на весь документ, поэтому сюрприза с буклетом никогда не произойдет
Зарегистрируйте шрифт, который вы поставляете, а не тот, на наличие которого надеетесь
Ни одно переупорядочивание не имеет значения, если в шрифте нет глифов для рисования. Классический сбой — это отчет, который безупречно отображается на компьютере разработчика, где Arial Unicode MS присутствует, и выходит в виде рядов пустых полей на сервере клиента, где Windows тихо заменила шрифт на шрифт без арабского покрытия вообще. Решение — перестать доверять установленным системным шрифтам и зарегистрировать шрифт, который вы поставляете вместе с приложением
// Ship a known Arabic font and register it before drawing
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Две границы сопровождают регистрацию. Шрифт, загруженный через RegisterUnicodeTTF, встраивается, а для обработки встроенного Unicode в HotPDF требуется документ в формате PDF 1.5 или более поздней версии; это кусается только в том случае, если что-то ниже по цепочке настаивает на PDF 1.4, но когда это происходит, сбой происходит тихо. Другая граница скорее юридическая, чем техническая: файлы TrueType несут биты разрешения на встраивание, и шрифт, который отлично выглядит на экране, может быть лицензирован таким образом, что запрещается поставлять его внутри клиентских документов. Подтвердите лицензию до встраивания, а не после жалобы
Полный консольный пример
Собирая кусочки воедино, вот автономная программа, которая пишет одну страницу с арабской строкой, ивритской строкой и смешанной строкой с латинским названием продукта. Каждый блок устанавливает свою кодировку, затем рисует в логическом порядке
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // HotPDF main unit
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// A Latin heading goes through the ordinary TextOut path
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabic: charset 178, logical order, RtLTextOut does the reordering
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebrew: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Mixed line: the embedded Latin word still reads left to right
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة 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