Передайте арабське речення يوضح ملف PDF هذا до звичайного TextOut, і отримана сторінка буде містити одразу дві помилки. Слова йтимуть зліва направо, а не справа наліво, а літери стоятимуть окремо у своїх ізольованих формах замість того, щоб з'єднуватися у слова. При цьому жодних помилок не виникає. Delphi компілює код, файл відкривається, але рецензент, який читає арабською, скаже вам, що результат непридатний для використання. Виправлення полягає в одному виклику, а не в заміні бібліотеки: HotPDF спрямовує текст, що читається справа наліво, через окремий метод RtLTextOut, який обробляє зміну порядку, з якою звичайний TextOut не впорається. Чотири аспекти цього методу визначають, чи буде результат придатним до використання: що він робить з рядком, як його аргумент charset вибирає скрипт, які зміни рівня документа він вносить як побічний ефект, і яка робота зі шрифтами має передувати цьому
Для арабського тексту недостатньо змінити напрямок рядка: потрібно також виконати формування гліфів і вибрати відповідний charset, інакше літери залишаться в ізольованих формах
Чому для тексту справа наліво потрібен окремий виклик
Потік вмісту PDF не зберігає редагований текст. Він зберігає гліфи у фіксованих позиціях, а отже, той, хто генерує потік, має вирішувати, в якому порядку ці гліфи розташовуватимуться. На екрані операційна система робить це за вас: вставте арабський текст у TEdit, і текстовий стек ОС змінить його порядок та з'єднає літери ще до того, як ви побачите хоча б один піксель. Саме тому рядок виглядає ідеально у вашій формі, але ламається у PDF. Десктоп виконав роботу непомітно, і як тільки ви пишете власний потік вмісту, ця робота знову лягає на ваші плечі
TextOut сприймає вас буквально. Він малює кодові точки в тому порядку, в якому ви їх передаєте, зліва направо, що є правильним для латиниці, кирилиці та CJK, але неправильним для арабської мови та івриту. RtLTextOut є тим викликом, який спочатку змінює порядок рядка на візуальний порядок справа наліво, а вже потім малює. HotPDF свідомо тримає ці два методи окремо замість того, щоб вгадувати напрямок за символами, тому вибір того, що викликати, є вибором того, яку поведінку скрипта ви отримаєте. Більш глибока механіка двоспрямованої зміни порядку та контекстного з'єднання арабської мови є окремою темою, що висвітлюється у статті Arabic and RTL text shaping with HotPDF; тут практичний аспект є вужчим. Використовуйте RtLTextOut для тексту справа наліво, використовуйте TextOut для всього іншого, і ніколи не спрямовуйте одне через інше

Аргумент charset визначає скрипт
Те, що повідомляє RtLTextOut про компонування арабського чи івритського тексту, це не метод, а шрифт. SetFont приймає Windows charset як свій четвертий аргумент, і це значення переносить правила скрипта у виклик для тексту справа наліво: 178 вибирає арабську, 177 вибирає іврит. Встановіть charset, потім намалюйте, і два рядки нижче вийдуть у правильному порядку читання без жодних додаткових налаштувань
// 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 זה');
Дві деталі щодо цих координат легко пропустити. Передана вами позиція все ще є початком рядка у власній системі координат сторінки, яка вимірюється з нижнього лівого кута зі збільшенням Y вгору, і це той самий початок відліку, який використовує кожен TextOut; RtLTextOut змінює порядок гліфів, а не те, звідки сторінка веде відлік. І, як і з будь-яким викликом малювання, SetFont має бути першим і його потрібно повторювати після кожного AddPage, оскільки поточний шрифт не зберігається після розриву сторінки. Забудьте про повторення, і друга сторінка повернеться до будь-якого активного шрифту, що для арабської мови зазвичай означає порожні квадратики
Він не перевертає текст, який ви вже перевернули
Єдина помилка, яка забирає найбільше часу на налагодження тут, полягає в тому, щоб передати RtLTextOut рядок, який ви вже перевернули вручну. Люди приходять до цього методу після того, як перша спроба зі звичайним TextOut вийшла задом наперед, і поширеним тимчасовим рішенням є перевертання символів у коді перед малюванням. RtLTextOut перевертає текст внутрішньо самостійно, тому попередньо перевернутий рядок перевертається вдруге і повертається туди, звідки почав. Передавайте текст у логічному порядку, у тому порядку, в якому ви б його набирали і читали вголос, і дозвольте виклику зробити зміну порядку
Ця пастка набагато гірша за звичайне перевертання, оскільки двічі перевернутий рядок може виглядати правильним для однієї тестової фрази, яка складається повністю з арабської мови, а потім зламатися в ту саму мить, коли рядок містить латинське слово або цифру. Всередині рядка справа наліво ці вбудовані фрагменти повинні читатися зліва направо, і ручне перевертання руйнує це вкладення, тоді як чистий арабський випадок якимось чином це переживає. Тому помилка проходить через ваш перший базовий тест і з'являється пізніше на реальному рахунку з номером рахунку в ньому. Видаліть кожне ручне перевертання в той момент, коли ви переходите на RtLTextOut
Побічний ефект Direction, про який варто знати
Виклик 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 містять біти дозволу на вбудовування, і шрифт, який виглядає добре на екрані, може бути ліцензований таким чином, що забороняє його постачання всередині клієнтських документів. Підтверджуйте ліцензію перед вбудовуванням, а не після скарги
Повний приклад для консолі
Збираючи частини разом, ось автономна програма, яка пише одну сторінку з арабським рядком, івритським рядком та змішаним рядком із латинською назвою продукту. Кожен блок встановлює свій charset, а потім малює в логічному порядку
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 розміщений зліва направо всередині арабського фрагмента, що є правильним результатом відповідно до специфікації, навіть якщо це дивує будь-кого, хто бачить двоспрямований макет вперше. Цей останній пункт варто вписати у ваші критерії прийняття до того, як носій мови почне рецензувати результат, оскільки вбудований фрагмент, що читається "не в той" бік порівняно з навколишнім скриптом, є тим єдиним моментом, який найчастіше фіксується як помилка, хоча нею не є
Перевірка результату
Сторінка, яка виглядає правильно, не є тим самим, що правильна сторінка, тому перевіряйте її так, як це робитиме система нижче за течією. Скопіюйте текст назад із переглядача та порівняйте кодові точки з вихідним рядком; правильний візуальний порядок зі змішаним логічним порядком є реальним режимом збою. Запустіть внутрішній пошук у переглядачі для слова, яке ви бачите на сторінці. Потім відкрийте файл на машині, яка не має ваших шрифтів для розробки, тій самій, яка з найбільшою ймовірністю виявить непомітну підстановку. Жодна з цих дій не замінить носія мови, який читає один справжній документ, що виявляє проблеми, яких не виявить жоден синтетичний тестовий рядок, тому внесіть цей огляд у календар до випуску формату
RtLTextOut обробляє двоспрямовану зміну порядку та контекстне з'єднання арабської мови, що охоплює переважну більшість робіт зі звітами та документами справа наліво. Там, де його можливості закінчуються, скрипти, що потребують більше, ніж просто зміни порядку та з'єднання, як-от індійські сім'ї, а також необов'язкові функції OpenType, які використовують заміну окремих гліфів, детально описані разом із деталями щодо покриття гліфів та формування у супутній статті Arabic and RTL text shaping with HotPDF
Виклики RtLTextOut, SetFont та RegisterUnicodeTTF, показані тут, є частиною HotPDF Component для Delphi та C++Builder