Wyślij zdanie arabskie يوضح ملف PDF هذا do zwykłego TextOut, a strona, która wraca, jest błędna na dwa sposoby naraz. Wyrazy biegną od lewej do prawej zamiast od prawej do lewej, a litery stoją osobno w izolowanych formach zamiast łączyć się w spójne słowa. Nic nie zgłasza błędu. Delphi się kompiluje, plik się otwiera, a recenzent czytający po arabsku mówi Ci, że wynik jest bezużyteczny. Rozwiązaniem jest jedno wywołanie, nie zmiana biblioteki: HotPDF kieruje tekst od prawej do lewej przez osobną metodę, RtLTextOut, która wykonuje rearanżację, jakiej zwykły TextOut nie wykona. Ta strona to praktyczna referencja tej metody: sygnatura i jej parametry, argument zestawu znaków wybierający skrypt, efekt uboczny na poziomie dokumentu, konfiguracja czcionki, która musi nastąpić najpierw, oraz błędy, które faktycznie trafiają do wsparcia technicznego, każdy wraz z rozwiązaniem
Sygnatura i parametry
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: PWORD; TextLength: Integer); overload;
X i Y zakotwiczają fragment we własnym układzie współrzędnych strony, mierzonym od lewego dolnego rogu, przy czym Y rośnie w górę — ten sam punkt odniesienia, którego używa każde wywołanie TextOut; RtLTextOut zmienia kolejność glifów, a nie punkt, od którego strona mierzy. angle obraca linię bazową dokładnie tak samo jak w TextOut, więc 0 rysuje linię poziomą. Text to ciąg znaków w kolejności logicznej, czyli takiej, w jakiej byś go wpisywał, a drugie przeciążenie przyjmuje te same dane UTF-16 jako surowy bufor PWORD z jawnie podaną liczbą jednostek kodowych — to forma stosowana, gdy tekst pochodzi z API, a nie z łańcucha znaków Delphi. W starszych wersjach Delphi, które nie obsługują jeszcze rozstrzygania przeciążeń dla tych typów, wersja przyjmująca łańcuch znaków jest udostępniona pod nazwą RtLTextOutStr, z identyczną listą parametrów
Podział pracy między dwoma wywołaniami wyjściowymi jest ścisły. TextOut rysuje punkty kodowe w kolejności, w jakiej je przekazujesz, co jest poprawne dla łaciny, cyrylicy i CJK, a błędne dla arabskiego i hebrajskiego. RtLTextOut najpierw zmienia kolejność każdego wiersza na wizualną kolejność od prawej do lewej, a dopiero potem rysuje, dzięki czemu osadzone słowa łacińskie i cyfry nadal czyta się od lewej do prawej wewnątrz linii. HotPDF celowo utrzymuje te dwie metody osobno, zamiast zgadywać kierunek na podstawie znaków, więc wybór metody do wywołania jest wyborem zachowania danego skryptu; używaj RtLTextOut dla przebiegów od prawej do lewej, TextOut dla wszystkiego innego, i nigdy nie kieruj jednego przez drugie. To, dlaczego rearanżacja w ogóle istnieje, co dokładnie robią Algorytm Dwukierunkowy Unicode (Unicode Bidirectional Algorithm) i kontekstowe łączenie liter arabskich, oraz gdzie kończy się kształtowanie tekstu w HotPDF, jest tematem artykułu towarzyszącego o kształtowaniu tekstu arabskiego i RTL w HotPDF; wszystko poniżej to praktyczna konfiguracja

Argument zestawu znaków decyduje o skrypcie
O tym, czy RtLTextOut układa tekst po arabsku, czy po hebrajsku, nie decyduje sama metoda — decyduje czcionka. SetFont przyjmuje zestaw znaków (charset) Windows jako swój czwarty argument, i to ta wartość przenosi reguły danego skryptu do wywołania od prawej do lewej: 178 wybiera arabski, 177 wybiera hebrajski. Ustaw zestaw znaków, a następnie narysuj tekst — obie poniższe linie wyjdą w poprawnej kolejności odczytu bez żadnej dodatkowej konfiguracji
// Arabski: zestaw znaków 178 nakazuje RtLTextOut zastosować reguły arabskie
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebrajski: zestaw znaków 177 przełącza reguły na hebrajskie
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Łatwo przeoczyć jeden szczegół dotyczący kolejności: SetFont musi zostać wywołane jako pierwsze i musi być powtarzane po każdym AddPage, ponieważ bieżąca czcionka, wraz z zestawem znaków, nie przetrwa podziału strony. Jeśli zapomnisz o powtórzeniu, druga strona wróci do dowolnej czcionki, jaka była wcześniej aktywna, co dla arabskiego zwykle oznacza puste prostokąty
To nie odwraca tekstu, który już odwróciłeś
Pojedynczym błędem, który pochłania tu najwięcej czasu na debugowanie, jest podanie do RtLTextOut ciągu znaków, który już wcześniej odwróciłeś ręcznie. Ludzie trafiają na tę metodę po tym, jak pierwsza próba ze zwykłym TextOut wyszła odwrócona, a typowym prowizorycznym rozwiązaniem jest odwrócenie znaków w kodzie przed narysowaniem. RtLTextOut odwraca tekst samodzielnie i wewnętrznie, więc wcześniej odwrócony ciąg zostaje odwrócony po raz drugi i wraca dokładnie tam, skąd zaczął. Przekazuj tekst w kolejności logicznej, czyli takiej, w jakiej byś go wpisał i przeczytał na głos, a samą rearanżację zostaw wywołaniu
Ta pułapka jest podstępniejsza niż zwykłe odwrócenie, ponieważ dwukrotnie odwrócony ciąg znaków może wyglądać poprawnie dla jednej czysto arabskiej frazy testowej, a zepsuć się dopiero w chwili, gdy linia zawiera łacińskie słowo lub liczbę. Wewnątrz linii od prawej do lewej takie osadzone fragmenty powinny czytać się od lewej do prawej, a ręczne odwrócenie niszczy to zagnieżdżenie, podczas gdy przypadek czysto arabski akurat je przetrwa. W efekcie błąd przechodzi przez pierwszy test dymny (smoke test) i ujawnia się dopiero później, na prawdziwej fakturze z numerem konta. Usuń każde ręczne odwracanie tekstu w chwili, gdy przechodzisz na RtLTextOut
Efekt uboczny dotyczący Direction, o którym warto wiedzieć
Wywołanie RtLTextOut zmienia więcej niż tylko rysowaną linię. Zmienia również preferowany kierunek odczytu dokumentu na „od prawej do lewej” — dokładnie to samo, co ustawiłbyś ręcznie za pomocą właściwości Direction. Ten setter dodaje vpDirection do ViewerPreferences dokumentu, co informuje przeglądarkę, jak układać rozkładówki dwustronicowe i po której stronie zaczyna się układ stron naprzeciwległych. Gdy cały dokument jest po arabsku lub po hebrajsku, dokładnie o to Ci chodzi i dostajesz to bez dodatkowego wysiłku
Warto o tym wiedzieć właśnie dlatego, że na pojedynczej stronie ten efekt jest niewidoczny. Jeśli dokument jest w większości od lewej do prawej, a zawiera tylko jeden blok od prawej do lewej, pierwsze wywołanie RtLTextOut i tak przestawi preferencję całego pliku, a nic w Twoim jednostronicowym dowodzie tego nie pokaże. Objaw pojawia się dopiero po kilku tygodniach, gdy ktoś wydrukuje broszurę w druku dwustronnym i rozkładówki wyjdą lustrzanie odwrócone. Jeśli to nie jest to, czego chcesz, ustaw Direction z powrotem jawnie, zaraz po fragmencie od prawej do lewej:
// RtLTextOut ustawiło już kierunek dokumentu na RightToLeft;
// przywróć kierunek od lewej do prawej, jeśli dokument jest przeważnie LTR
Pdf.Direction := LeftToRight;
Dla dokumentu, który rzeczywiście czyta się od prawej do lewej, zostaw to ustawienie bez zmian. Chodzi o to, by wiedzieć, że to wywołanie ma skutek obejmujący cały dokument, dzięki czemu niespodzianka z broszurą nigdy się nie zdarzy
Zarejestruj czcionkę, którą wysyłasz, a nie tę, co do której masz nadzieję, że jest zainstalowana
Cała rearanżacja nie ma znaczenia, jeśli czcionka nie ma glifów do narysowania. Klasyczna awaria to raport, który renderuje się bezbłędnie na komputerze programisty, gdzie akurat jest zainstalowana czcionka Arial Unicode MS, a na serwerze klienta wychodzi jako rzędy pustych prostokątów, ponieważ Windows po cichu podstawił czcionkę bez żadnego pokrycia dla arabskiego. Lekarstwem jest przestać ufać zainstalowanym czcionkom systemowym i zarejestrować czcionkę, którą wysyłasz razem z aplikacją
// Dołącz znaną czcionkę arabską i zarejestruj ją przed rysowaniem
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Wraz z rejestracją wiążą się dwa ograniczenia. Czcionka wczytana przez RegisterUnicodeTTF zostaje osadzona (embedded), a obsługa osadzonego Unicode w HotPDF wymaga dokumentu w wersji PDF 1.5 lub nowszej; ma to znaczenie tylko wtedy, gdy coś dalej w potoku przetwarzania upiera się przy PDF 1.4, ale wtedy awaria jest cicha. Drugie ograniczenie ma naturę prawną, nie techniczną: pliki TrueType niosą bity uprawnień do osadzania, a krój, który na ekranie wygląda dobrze, może być licencjonowany w sposób zabraniający wysyłania go wewnątrz dokumentów dla klienta. Potwierdź licencję, zanim osadzisz czcionkę, a nie po reklamacji
Pełny przykład konsolowy
Łącząc te elementy razem, oto samodzielny program, który zapisuje jedną stronę z linią arabską, linią hebrajską i linią mieszaną zawierającą łacińską nazwę produktu. Każdy blok ustawia swój zestaw znaków, a następnie rysuje w kolejności logicznej
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // główny moduł HotPDF
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// Nagłówek łaciński przechodzi zwykłą ścieżkę TextOut
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabski: zestaw znaków 178, kolejność logiczna, RtLTextOut wykonuje rearanżację
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebrajski: zestaw znaków 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Linia mieszana: osadzone słowo łacińskie nadal czyta się od lewej do prawej
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.
Uruchom program i otwórz wynik. Linie arabska i hebrajska czytają się od prawej do lewej, litery łączą się tam, gdzie łączy je dany skrypt, a w ostatniej linii token HotPDF siedzi w układzie od lewej do prawej wewnątrz przebiegu arabskiego. To zagnieżdżenie jest poprawnym wynikiem dwukierunkowym, a nie błędem, mimo że recenzenci robiący to po raz pierwszy rutynowo zgłaszają je jako błąd; artykuł o kształtowaniu tekstu, do którego link znajduje się powyżej, wyjaśnia, dlaczego wymagają tego reguły Unicode, i jak sformułować kryteria akceptacji, żeby takie zgłoszenie nigdy nie powstało
Typowe błędy i ich rozwiązania
Każda z poniższych awarii pojawiła się w prawdziwym wątku wsparcia technicznego, i każda sprowadza się do jednej z sekcji powyżej
- Wynik czyta się od tyłu albo miesza się w liniach mieszanych — ciąg znaków został ręcznie odwrócony przed wywołaniem, zwykle jako pozostałość po próbie z
TextOut. Usuń każde ręczne odwracanie i przekazuj tekst w kolejności logicznej;RtLTextOutodwraca go wewnętrznie samodzielnie - Litery drukują się rozłącznie, w formach izolowanych — tekst przeszedł przez zwykły
TextOut, alboSetFontzostało wywołane bez zestawu znaków dla kierunku od prawej do lewej. Rysuj za pomocąRtLTextOuti przekaż 178 dla arabskiego lub 177 dla hebrajskiego jako czwarty argumentSetFont - Puste prostokąty na komputerze klienta — Windows podstawił czcionkę bez pokrycia dla arabskiego lub hebrajskiego. Przestań wskazywać czcionki zainstalowane; zarejestruj krój, który wysyłasz, przez
RegisterUnicodeTTF, i wywołaj dla niegoSetFontpod tą nazwą - Druga strona renderuje się w niewłaściwej czcionce — bieżąca czcionka nie przetrwa
AddPage. Powtórz wywołanieSetFont, wraz z zestawem znaków, po każdym podziale strony - Rozkładówki w druku dwustronnym drukują się lustrzanie w dokumencie przeważnie LTR — pierwsze wywołanie
RtLTextOutjako efekt uboczny przestawiłoDirectiondokumentu. UstawPdf.Direction := LeftToRightpo fragmencie od prawej do lewej - Osadzony tekst Unicode po cichu degraduje się dalej w potoku przetwarzania — coś w potoku wymusza PDF 1.4, a obsługa osadzonego Unicode w HotPDF wymaga wersji 1.5 lub nowszej. Podnieś wersję dokumentu albo usuń to ograniczenie dalej w potoku
Zanim format trafi do produkcji, zweryfikuj wynik czymś więcej niż samym patrzeniem: skopiuj tekst z powrotem z przeglądarki, uruchom wyszukiwanie wewnątrz dokumentu, otwórz plik na komputerze bez Twoich czcionek deweloperskich i pokaż jeden prawdziwy dokument osobie, dla której dany język jest ojczysty. Pełna lista kontrolna weryfikacji, mapa pokrycia dla poszczególnych skryptów oraz zestaw testowych ciągów znaków, który warto zbudować, znajdują się w artykule towarzyszącym o kształtowaniu tekstu arabskiego i RTL w HotPDF
Wywołania RtLTextOut, SetFont i RegisterUnicodeTTF pokazane tutaj są częścią komponentu HotPDF Delphi Component dla Delphi i C++Builder