שלח את המשפט בערבית يوضح ملف PDF هذا ל-TextOut רגיל והעמוד שחוזר שגוי בשתי דרכים בבת אחת. המילים רצות משמאל לימין במקום מימין לשמאל, והאותיות יושבות בנפרד בצורות המבודדות שלהן במקום להתחבר למילים מחוברות. שום דבר אינו מחזיר שגיאה. ה-Delphi מתקמפל, הקובץ נפתח, וסוקר שקורא ערבית אומר לך שהפלט אינו שמיש. התיקון הוא קריאה אחת, לא החלפת ספרייה: HotPDF מנתב טקסט מימין-לשמאל דרך מתודה נפרדת, RtLTextOut, שמטפלת בסידור מחדש ש-TextOut רגיל לא יעשה. ארבעה דברים לגבי מתודה זו קובעים האם הפלט שמיש: מה היא עושה למחרוזת, כיצד ארגומנט ערכת התווים (charset) שלה בוחר את הסקריפט, השינוי ברמת-המסמך שהיא עושה כתופעת לוואי, ועבודת הגופנים שחייבת להגיע קודם
מדוע ימין-לשמאל זקוק לקריאה משלו
זרם תוכן של PDF אינו מאחסן טקסט הניתן לעריכה. הוא מאחסן גליפים במיקומים קבועים, מה שאומר שכל מי שפולט את הזרם הוא הבעלים של עבודת ההחלטה באיזה סדר הולכים הגליפים האלה. על המסך מערכת ההפעלה עשתה זאת עבורך: זרוק ערבית לתוך TEdit ומחסנית הטקסט של מערכת ההפעלה מסדרת מחדש ומחברת אותה לפני שאתה בכלל רואה פיקסל. זו בדיוק הסיבה שהמחרוזת נראית מושלמת בטופס שלך ונשברת ב-PDF. שולחן העבודה עשה את העבודה בשקט, וברגע שאתה כותב זרם תוכן משלך, העבודה חוזרת לצד שלך
TextOut לוקח אותך במילה שלך. הוא מצייר את נקודות הקוד (codepoints) בסדר שאתה מעביר אותן, משמאל לימין, שזה נכון עבור לטינית, קירילית, ו-CJK ושגוי עבור ערבית ועברית. RtLTextOut היא הקריאה שמסדרת מחדש את השורה לסדר חזותי (visual) של ימין-לשמאל תחילה, ואז מציירת. HotPDF שומר את שתי המתודות נפרדות במכוון במקום לנחש את הכיוון מהתווים, כך שהבחירה למי לקרוא היא הבחירה איזו התנהגות סקריפט תקבל. המכניקה העמוקה יותר של סידור מחדש דו-כיווני (bidirectional) וחיבור הקשרי (contextual) בערבית הם נושא בפני עצמו, המכוסה במאמר על עיצוב טקסט ערבי ו-RTL עם HotPDF; כאן הנקודה המעשית היא צרה יותר. השתמש ב-RtLTextOut עבור ריצות של ימין-לשמאל, השתמש ב-TextOut עבור כל השאר, ולעולם אל תנתב אחד דרך השני

ארגומנט ערכת התווים (charset) מחליט על הסקריפט
מה שאומר ל-RtLTextOut האם הוא פורס ערבית או עברית אינו המתודה, זהו הגופן. SetFont מקבל ערכת תווים (charset) של Windows כארגומנט הרביעי שלו, וערך זה נושא את כללי הסקריפט אל תוך הקריאה של ימין-לשמאל: 178 בוחר ערבית, 177 בוחר עברית. קבע את ערכת התווים, ואז צייר, ושתי השורות למטה יוצאות בסדר קריאה נכון ללא שום תצורה (configuration) נוספת
// 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, מכיוון שהגופן הנוכחי אינו שורד מעבר עמוד. תשכח לחזור על כך והעמוד השני יחזור לכל גופן שהיה פעיל, שעבור ערבית זה בדרך כלל אומר תיבות ריקות
הוא אינו הופך טקסט שכבר הפכת
הטעות הבודדת שבולעת הכי הרבה זמן ניפוי שגיאות (debugging) כאן היא להזין את RtLTextOut במחרוזת שכבר הפכת ידנית. אנשים מגיעים למתודה זו לאחר שניסיון ראשון עם TextOut רגיל יצא הפוך, ופתרון זמני נפוץ הוא להפוך את התווים בקוד לפני הציור. RtLTextOut הופך פנימית בעצמו, כך שמחרוזת שהופכה-מראש הופכת פעם שנייה ונוחתת בדיוק היכן שהתחילה. העבר את הטקסט בסדר לוגי, הסדר שבו היית מקליד וקורא אותו בקול רם, ותן לקריאה לעשות את הסידור מחדש
המלכודת מרושעת יותר מסתם היפוך רגיל מכיוון שמחרוזת בהיפוך-כפול יכולה להיראות נכונה עבור ביטוי בדיקה אחד שכולו ערבית ואז להישבר ברגע ששורה נושאת מילה לטינית או מספר. בתוך שורה של ימין-לשמאל ריצות מוטבעות אלו אמורות להיקרא משמאל לימין, והיפוך ידני הורס את הקינון (nesting) הזה בעוד המקרה של ערבית טהורה במקרה שורד אותו. כך שהבאג מפליג דרך בדיקת העשן (smoke test) הראשונה שלך וצץ מאוחר יותר על חשבונית אמיתית עם מספר חשבון בתוכה. הסר כל היפוך ידני ברגע שאתה עובר ל-RtLTextOut
תופעת הלוואי של כיוון (Direction) ששווה להכיר
קריאה ל-RtLTextOut משנה יותר מאשר את השורה שאתה מצייר. היא גם הופכת את העדפת כיוון-הקריאה של המסמך לימין-לשמאל, אותו הדבר שהיית מגדיר בעצמך בדרך אחרת דרך המאפיין Direction. קובע זה (setter) מוסיף vpDirection ל-ViewerPreferences של המסמך, האומר לצופה כיצד לארגן ממרחים כפולים (two-up spreads) ומאיזה צד מתחילה פריסת עמודים מול עמודים (facing-page layout). כאשר כל המסמך הוא בערבית או בעברית זה בדיוק מה שאתה רוצה, ואתה מקבל את זה בחינם
שווה לדעת על כך בדיוק מכיוון שזה בלתי נראה בעמוד בודד. אם המסמך הוא ברובו משמאל-לימין עם בלוק אחד של ימין-לשמאל, קריאת ה-RtLTextOut הראשונה עדיין תטה את ההעדפה של כל הקובץ, ושום דבר בהוכחת-עמוד-האחד שלך לא יראה זאת. התסמין מופיע שבועות מאוחר יותר כאשר מישהו מדפיס חוברת דו-צדדית (duplex) והממרחים יוצאים מראָה (mirrored). אם זה לא מה שאתה רוצה, החזר את 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 נושאים סיביות הרשאת הטמעה (embedding-permission), ופרצוף (face) שנראה בסדר על המסך יכול להיות מורשה באופן שאוסר שליחתו בתוך מסמכי לקוחות. אמת את הרישיון לפני שאתה מטמיע, לא לאחר תלונה
דוגמה שלמה של קונסולה
כשמחברים את החלקים יחד, הנה תוכנית עצמאית (self-contained) שכותבת עמוד אחד עם שורה בערבית, שורה בעברית, ושורה מעורבת הנושאת שם מוצר בלטינית. כל בלוק קובע את ערכת התווים שלו, ואז מצייר בסדר לוגי
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.
הרץ אותה ופתח את התוצאה. השורות בערבית ובעברית נקראות מימין לשמאל, האותיות מתחברות היכן שהסקריפט מחבר אותן, ובשורה האחרונה האסימון (token) HotPDF יושב משמאל-לימין בתוך הריצה בערבית, וזוהי התוצאה הנכונה-לפי-המפרט גם אם היא מפתיעה כל מי שרואה פריסה דו-כיוונית בפעם הראשונה. הנקודה האחרונה הזו שווה כתיבה לקריטריוני הקבלה (acceptance criteria) שלך לפני שקורא ילידי יסקור את הפלט, מכיוון שהריצה המוטבעת הקוראת בדרך ה"לא נכונה" ביחס לסקריפט שמסביבה היא הדבר היחיד שמוגש הכי הרבה כבאג למרות שאינו כזה
אימות הפלט
עמוד שנראה נכון אינו זהה לעמוד שהוא נכון, אז בדוק אותו בדרך שמערכת במורד-הזרם תבדוק. העתק את הטקסט בחזרה מתוך הצופה והשווה את נקודות הקוד מול מחרוזת המקור שלך; סדר חזותי נכון עם סדר לוגי משובש הוא מצב כשל אמיתי. הפעל את חיפוש הצופה בתוך-המסמך אחר מילה שאתה יכול לראות בעמוד. לאחר מכן פתח את הקובץ במכונה שאין לה את גופני הפיתוח שלך, זו שהכי סביר שתחשוף החלפה שקטה. שום דבר מזה לא מחליף דובר ילידי הקורא מסמך אחד אמיתי, דבר שתופס בעיות שאף מחרוזת בדיקה סינתטית לא תתפוס, אז שים את הסקירה הזו על לוח השנה לפני שהפורמט נשלח ללקוחות
RtLTextOut מטפל בסידור מחדש דו-כיווני ובחיבור הקשרי של ערבית, מה שמכסה את הרוב המכריע של עבודת דוחות ומסמכים מימין-לשמאל. היכן שהוא נעצר, סקריפטים שזקוקים ליותר מסידור מחדש וחיבור כגון משפחות האינדיק (Indic), ומאפייני ה-OpenType האופציונליים שעוברים דרך החלפת-גליף-יחיד, ממופה לצד כיסוי הגליפים ופרטי העיצוב במאמר המלווה על עיצוב טקסט ערבי ו-RTL עם HotPDF
הקריאות RtLTextOut, SetFont, ו-RegisterUnicodeTTF המוצגות כאן הן חלק מרכיב HotPDF עבור Delphi ו-C++Builder