מאמר טכני

TextOut של HotPDF ב-Delphi: גודל, סגנון, סיבוב וריווח

כל מחרוזת גלויה במסמך HotPDF מגיעה דרך קריאה אחת: TextOut(X, Y, angle, Text). דוגמת ה-Hello World משתמשת בה בצורה הפשוטה ביותר, הגדרת גופן פעם אחת וארבעה ארגומנטים שנותרו בברירות מחדל הגיוניות. מעבר לאותו עמוד ראשון, אותם ארבעה ארגומנטים נושאים את כל משקל הפריסה (layout). הארגומנט השלישי מסובב את הריצה (run). הגופן שהוגדר ממש לפניו קובע גודל וסגנון. והזוג X, Y, הנמדד מפינת העמוד בנקודות, הוא הדבר היחיד שעומד בין דוח נקי לבין טקסט שחופף, נחתך, או נודד שורה נמוך יותר במדפסת של מישהו אחר. זה המקום שבו TextOut מרוויח את לחמו, והיכן שברירות המחדל מפסיקות להספיק

החתימה (signature) שווה קיבוע בראש לפני כל דבר אחר: X ו-Y הם Single בנקודות, angle היא Extended במעלות, ו-Text היא WideString, כך ש-Unicode עובר דרכו ללא קריאה נפרדת. העמסה (overload) שנייה מקבלת PWORD בתוספת אורך עבור מקרים שבהם אתה כבר מחזיק בקודי גליפים (glyph codes), אך עבור מחרוזות רגילות, צורת ה-WideString היא זו שתשלח אליה יד

גודל וסגנון מגיעים מ-SetFont, לא מ-TextOut

ל-TextOut אין פרמטר גודל. הגודל, המשקל, הנטייה, כל זה חי בקריאת ה-SetFont שקודמת לריצה, והוא נשאר בתוקף עד שה-SetFont הבא יחליף אותו. זוהי העובדה היחידה שמסבירה את רוב הבלבול של היום הראשון: שורה יוצאת מודגשת (bold) מכיוון ששלוש קריאות קודם לכן משהו הגדיר [fsBold] ושום דבר לא ניקה את זה

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report');        // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, '  +18.4% YoY');            // styles combine

הארגומנט השני הוא קבוצת TFontStyles, כך ש-[fsBold, fsItalic] הוא מודגש נטוי ו-[] הוא רגיל (plain). הגודל הוא בנקודות, אותה יחידה כמו הקואורדינטות, מה שהופך ריווח אנכי לקל להבנה: שורה של 12 נקודות רוצה בערך 14 עד 16 נקודות של צעד אנכי כדי לנשום, כך שהורדת Y ב-14 לשורה היא מרווח שורות (leading) התחלתי סביר. אין התקדמות שורה אוטומטית. אתה מחשב כל קו בסיס (baseline) בעצמך, שזה מייגע עבור פסקה אבל מדויק עבור טופס, שבו כל שדה יושב בקואורדינטה קבועה

שתי הערות מעשיות לגבי שם הגופן. הוא מפוענח (resolved) מול הגופנים המותקנים על מכונת ה-build, ומה שמערכת ההפעלה מחזירה בחזרה זה מה שמוטמע, כך ששם שמפוענח בשולחן העבודה שלך ושם שמפוענח בשרת build לא מובטחים להיות אותו גופן (face). והגופן חייב לכסות את הכתבים (scripts) במחרוזת. ריצה של טקסט קירילי או CJK תחת גופן לטיני בלבד מתרנדרת כתיבות של גליף חסר ללא שום שגיאה, וזו הסיבה שעמוד ה-Hello World שולח יד לגופן Unicode רחב כאשר הוא מערבב שפות

HotPDF TextOut page showing Arial, Times New Roman, and Courier New rendered with regular, bold, and italic styles across several character sets

הארגומנט זווית מסובב סביב העוגן

הארגומנט השלישי הוא זה שרוב הקוד משאיר על אפס לנצח. העבר ערך שאינו אפס והריצה תסתובב נגד כיוון השעון סביב עוגן ה-(X, Y) של עצמה, החלק השמאלי התחתון של הטקסט, באותו מספר של מעלות. העוגן עצמו אינו זז, כך שאותה קואורדינטה שמיקמה תווית אופקית תמקם את התאומה המסובבת שלה; רק הכיוון שבו הגליפים צועדים משתנה

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

תשעים מעלות הוא המקרה הנפוץ, תווית שרצה במעלה הצד של תרשים או כותרת שדרה. ארבעים וחמש מעלות מטפל בכותרות עמודות מוטות, הטריק שמאפשר לתווית רחבה לשבת מעל עמודה צרה מבלי לגלוש לשכנותיה. הסיבוב אינו משנה את האופן שבו העוגן מפורש, מה שמכשיל אנשים: ריצה של 90 מעלות עדיין מתחילה ב-(X, Y) וגדלה משם כלפי מעלה, כך שכדי למרכז תווית מסובבת אתה מכוונן את העוגן, לא את הזווית. כאשר מספר ריצות מסובבות חולקות קו בסיס, תן להן את אותו Y והתקדם ב-X (step X), בדיוק כפי שהיית מתקדם ב-Y עבור שורות אופקיות מוערמות

מיקום קואורדינטות ללא ניחוש

קואורדינטות הן החלק ששורד בדיקה (review) או נכשל בה בשקט. HotPDF מודד מהפינה השמאלית-התחתונה של העמוד, כאשר Y גדל כלפי מעלה, בנקודות ב-72 לאינץ'. עמוד US Letter הוא בגודל 612 על 792 נקודות; A4 הוא 595 על 842. שוליים עליונים של אינץ' אחד על Letter שמים לפיכך את קו הבסיס הראשון שלך קרוב ל-Y = 792 פחות 72 פחות גודל הגופן, לא באיזשהו מספר קטן קרוב לחלק העליון. כל מי שמגיע מקואורדינטות מסך, שבהן Y גדל כלפי מטה מאפס, כותב את השורה הראשונה מחוץ לקצה התחתון ומבלה עשר דקות בתהייה לאן היא נעלמה

התייחס לפריסה (layout) כאריתמטיקה מול עוגנים בעלי שם ולא כעמודה של מספרי קסם. שוליים שמאליים, קו בסיס רץ שאתה מפחית בכל שורה, ומרווח שורות קבוע (fixed leading) הופכים גוש של תוויות ללולאה קצרה במקום קיר של ליטרלים:

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopBaseline;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y - Leading;
    if Y < 72 then            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

השומר על מעבר עמוד (page-break guard) הוא השורה שכולם שוכחים ראשונה ושהשטח (the field) מכה בה הכי חזק. אין פריסת זרימה (flow layout) מתחת ל-TextOut. הפחת אל מעבר לשוליים התחתונים והטקסט ימשיך להצטייר לתוך השוליים, מחוץ לעמוד, אל תוך שום דבר, ללא אזהרה. אז אתה עוקב אחרי Y בעצמך, קורא ל-AddPage כאשר הוא חוצה את הרצפה, ומאפס את קו הבסיס. ה-SetFont שאחרי AddPage אינו ריפוד אופציונלי: הגופן הנוכחי אינו שורד מעבר עמוד, והריצה הראשונה בעמוד החדש תצא בגופן ברירת המחדל של הצופה (viewer) אם תדלג עליו

ריווח תווים ומילים להתאמה ויישור

לפעמים מחרוזת נכונה אבל ברוחב השגוי: כותרת שצריכה להשתרע על פני קו קבוע, קוד שצריך להיקרא עם ספרות מאווררות יותר, עמודה שצריכה שהערכים שלה יוסטו מעט כדי להתיישר. PDF נושא שני אופרטורים של מצב-טקסט עבור זה, ריווח תווים (Tc, שטח נוסף המתווסף לאחר כל גליף) וריווח מילים (Tw, שטח נוסף המתווסף בכל תו רווח), ושניהם מבוטאים ביחידות מרחב-טקסט שאינן מוגדלות (unscaled), למעשה נקודות בגודל הגופן הנוכחי. הם מצב (state), לא ארגומנטים ל-TextOut, אז אתה מגדיר אותם, מצייר, ומחזיר אותם בחזרה

// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

ריווח מילים פועל רק על תו הרווח (קוד 32), שיש לו השלכה ששווה להכיר: הוא לא עושה שום דבר בתוך ריצת CJK שאין בה רווחי ASCII, והוא מקיים אינטראקציה מוזרה עם טקסט המקודד כאינדקסים של גליפים במקום בתים. עבור פלט טבלאי לטיני זוהי הדרך הזולה להרחיב פערים מבלי להקליד מחדש את המחרוזת. ריווח תווים הוא הכלי הטוב יותר עבור כותרת שחייבת להגיע לרוחב יעד, שכן הוא פורס את ההתאמה באופן שווה על פני כל גליף במקום לקבץ אותה ברווחים

האיפוס הוא כל המשמעת (discipline). ריווח, כמו הגופן, הוא חלק ממצב הציור של העמוד, ומצב מתמיד עד שאתה משנה אותו. רווח באותיות (letter-space) כותרת אחת ותשכח לאפס אותה, וכל פסקה למטה תירש את המתיחה, שנקראת כטעות עדינה שקשה למקם אותה השורדת הגהה אקראית ונכשלת בבדיקה קפדנית. ההרגל האמין הוא להגדיר ערך ריווח, לצייר את הריצה שזקוקה לו, ולהחזיר אותו לאפס בשורה הבאה, כך ששום קוד מאוחר יותר לא יצטרך לדעת מה עשה סעיף קודם

HotPDF TextOut page comparing horizontal text scaling, character spacing, word spacing, and fill versus stroke rendering modes

בדיקת הפלט במקום שבו הוא באמת נשבר

פריסת טקסט נכשלת במכונה השנייה, לא בראשונה, כך שהבדיקות שחשובות קורות הרחק מהשולחן שלך. פתח את הקובץ שנוצר במערכת ללא סט הגופנים של המפתח שלך מותקן וודא שהגופנים המוטמעים עדיין מתרנדרים, כולל לטינית עם סימני הטעמה, כל הכתבים הלא-לטיניים, ופיסוק, במעבר אחד במקום בדיקה מדגמית של התווים הקלים. בחר והעתק כמה שורות כדי לאשר שהטקסט הוא טקסט אמיתי ולא קווי מתאר, שזה משנה ברגע שחיפוש או חילוץ נמצאים בתחום. הזן לפריסה נתונים מייצגים, התווית הגרמנית הארוכה ביותר והמספר הרחב ביותר, לא מחזיק מקום מסודר, מכיוון שהריצה שעולה על שדה היא תמיד זו שלא הקלדת ביד. ואם העמוד חייב לנחות על טופס מודפס מראש, הדפס או בצע רסטריזציה של דגימה אחת והנח אותה מול המקור; נדידה של קו בסיס ברבע מילימטר היא בלתי נראית על המסך וברורה מאליה על הנייר

אם לא כתבת עמוד אחד עדיין, התחל עם דוגמת Hello World של HotPDF, אשר מגדירה את המסמך, הגופן ומערכת הקואורדינטות השמאלית-תחתונה שכל מה שלמעלה תלוי בה. הקריאות ל-TextOut, SetFont, והריווח המוצגות כאן הן חלק מרכיב HotPDF עבור Delphi ו-C++Builder