Изпратете арабското изречение يوضح ملف PDF هذا към обикновения TextOut и страницата, която се връща, е грешна по два начина едновременно. Думите вървят от ляво надясно вместо от дясно на ляво, а буквите седят разделени в техните изолирани форми, вместо да се свързват в свързани думи. Нищо не дава грешка. Delphi се компилира, файлът се отваря и рецензент, който чете арабски, ви казва, че изходът е неизползваем. Поправката е едно извикване, не смяна на библиотека: HotPDF насочва текста от дясно на ляво през отделен метод, RtLTextOut, който се справя с пренареждането, което обикновеният TextOut няма да направи. Тази страница е работещият справочник (working reference) за този метод: сигнатурата и неговите параметри, аргументът 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 закотвят ръна (anchor the run) в собствената координатна система на страницата, измерена от долния ляв ъгъл с Y нарастващо нагоре, същото начало, което използва всяко извикване на TextOut; RtLTextOut променя реда на глифовете, а не откъде мери страницата. angle завърта базовата линия точно както прави в TextOut, така че 0 чертае хоризонтална линия. Text е низът в логически ред, редът, в който бихте го въвели, а второто претоварване (overload) приема същите UTF-16 данни като суров буфер PWORD с изричен брой кодови единици (code-unit count), което е формата, която да използвате, когато текстът пристига от API, а не от Delphi низ. В по-стари версии на Delphi, които предхождат резолюцията на претоварване (overload resolution) за тези типове, низовата форма е изложена под името RtLTextOutStr с идентичен списък с параметри
Разделението на труда между двете извиквания за изход е строго. TextOut чертае кодови точки в реда, в който ги подавате, което е правилно за латиница, кирилица и CJK и грешно за арабски и иврит. RtLTextOut първо пренарежда всеки ред във визуален ред от дясно на ляво, след което чертае, запазвайки вложените латински думи и цифри да се четат от ляво надясно вътре в реда. HotPDF държи двата метода умишлено разделени, вместо да отгатва посоката от знаците, така че изборът кое да се извика е изборът какво поведение на скрипта получавате; използвайте RtLTextOut за рънове (runs) от дясно на ляво, TextOut за всичко останало, и никога не насочвайте едното през другото. Защо пренареждането изобщо съществува, какво всъщност правят Unicode двупосочният алгоритъм (Unicode Bidirectional Algorithm) и арабското контекстуално свързване (Arabic contextual joining), и къде спира оформянето на HotPDF (HotPDF's shaping), са темата на съпътстващата статия за оформяне на арабски и RTL текст с HotPDF; всичко по-долу е практическата настройка

Аргументът 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 זה');
Един детайл в последователността лесно се пропуска: SetFont трябва да дойде първи и трябва да се повтори след всяко AddPage, защото текущият шрифт, включително charset, не оцелява при преминаване на нова страница (page break). Забравете повторението и втората страница се връща към какъвто шрифт е бил активен, което за арабски обикновено означава празни квадратчета
Той не обръща текст, който вече сте обърнали
Единствената грешка, която поглъща най-много време за дебъгване тук, е подаването на RtLTextOut низ, който вече сте обърнали на ръка. Хората стигат до този метод, след като първият опит с обикновен TextOut е излязъл наобратно, и често срещано временно решение (stopgap) е да се обърнат знаците в кода преди чертане. RtLTextOut обръща вътрешно сам, така че предварително обърнат низ бива обърнат втори път и се приземява точно там, откъдето е започнал. Подайте текста в логически ред, реда, в който бихте го въвели и прочели на глас, и оставете извикването да свърши пренареждането
Капанът е по-неприятен от просто обръщане, защото двойно обърнат низ може да изглежда правилно за една изцяло арабска тестова фраза и след това да се счупи в мига, в който даден ред носи латинска дума или число. Вътре в ред от дясно на ляво се предполага, че тези вложени рънове се четат от ляво надясно, и ръчното обръщане съсипва това влагане (nesting), докато чисто арабският случай случайно го преживява. Така че бъгът минава през вашия първи димен тест (smoke test) и изплува по-късно върху истинска фактура, в която има номер на сметка. Изчистете всяко ръчно обръщане в момента, в който превключите към RtLTextOut
Страничният ефект на Direction, който си струва да знаете
Извикването на RtLTextOut променя повече от реда, който чертаете. То също така превключва предпочитанието за посока на четене на документа на от-дясно-наляво, същото нещо, което иначе бихте задали сами чрез свойството Direction. Този сетър добавя vpDirection към ViewerPreferences на документа, което казва на програмата за преглед как да подреди двустранични разгъвания (two-up spreads) и от коя страна започва оформление с обърнати една към друга страници (facing-page layout). Когато целият документ е на арабски или иврит, това е точно това, което искате, и го получавате безплатно
Струва си да се знае за това точно защото е невидимо на единична страница. Ако документът е предимно от ляво надясно с един блок от дясно на ляво, първото извикване на RtLTextOut пак ще наклони предпочитанието на целия файл, и нищо във вашата едностранична проба (one-page proof) няма да го покаже. Симптомът се появява седмици по-късно, когато някой отпечата двустранна книжка и разгъванията излязат огледални. Ако това не е, което искате, задайте 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 هذا');
Две граници (boundaries) пътуват заедно с регистрацията. Шрифт, внесен чрез RegisterUnicodeTTF, бива вграден, а вградената Unicode обработка на HotPDF се нуждае от документа на PDF 1.5 или по-късна версия; това хапе само ако нещо надолу по веригата (downstream) настоява за PDF 1.4, но когато го направи, провалът е тих. Другото е по-скоро юридическо, отколкото техническо: файловете TrueType носят битове за разрешение за вграждане, и дадено начертание (face), което изглежда добре на екран, може да бъде лицензирано по начин, който забранява доставянето му вътре в документи на клиенти. Потвърдете лиценза преди да вградите, а не след оплакване
Пълен конзолен пример
Сглобявайки парчетата, ето една самостоятелна програма, която записва една страница с арабски ред, ред на иврит и смесен ред, носещ латинско име на продукт. Всеки блок задава своя 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 седи от ляво надясно вътре в арабския рън. Това влагане (nesting) е правилният двупосочен резултат, не бъг, въпреки че рецензентите за първи път рутинно го отчитат като такъв; статията за оформянето (shaping), свързана по-горе, обяснява защо Unicode правилата го изискват и как да формулирате вашите критерии за приемане, така че отчетът никога да не бъде подаден
Чести грешки и техните поправки
Всеки провал по-долу се е появявал в реална нишка за поддръжка, и всеки се проследява обратно до един от разделите по-горе
- Изходът се чете наобратно или се разбърква при смесени редове — низът е бил обърнат на ръка преди извикването, обикновено остатъчно временно решение от опит с
TextOut. Изтрийте всяко ръчно обръщане и подайте логически ред;RtLTextOutобръща вътрешно - Буквите се отпечатват разкачени в изолирани форми — текстът е преминал през обикновен
TextOut, илиSetFontе бил извикан без charset от дясно на ляво. Чертайте сRtLTextOutи подайте 178 за арабски или 177 за иврит като четвърти аргумент наSetFont - Празни квадратчета на машината на клиента — Windows е заменил шрифт без покритие на арабски или иврит. Спрете да назовавате инсталирани шрифтове; регистрирайте начертание (face), което доставяте, чрез
RegisterUnicodeTTFи му направетеSetFontпо това име - Втората страница се рендира в грешен шрифт — текущият шрифт не оцелява при
AddPage. Повторете извикването наSetFont, включително charset, след всяко преминаване на нова страница - Двустранните разгъвания се отпечатват огледално върху документ, който е предимно LTR — първото извикване на
RtLTextOutе обърналоDirectionна документа като страничен ефект. ЗадайтеPdf.Direction := LeftToRightслед ръна от дясно на ляво - Вграденият Unicode текст тихо деградира надолу по веригата — нещо в конвейера налага PDF 1.4, а вградената Unicode обработка на HotPDF се нуждае от 1.5 или по-късна версия. Вдигнете версията на документа или премахнете ограничението надолу по веригата
Преди форматът да бъде доставен, проверете отвъд преценката на око: копирайте текста обратно от програмата за преглед, стартирайте търсенето в документа, отворете файла на машина без вашите шрифтове за разработка и поставете един истински документ пред native четец. Пълният списък за проверка, картата на покритието по скриптове (per-script coverage map) и корпусът от тестови низове, който си струва да изградите, живеят в съпътстващата статия за оформяне на арабски и RTL текст с HotPDF
Извикванията на RtLTextOut, SetFont и RegisterUnicodeTTF, показани тук, са част от компонента HotPDF за Delphi и C++Builder