מאמר טכני

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 רחב כאשר הוא מערבב שפות

עמוד TextOut של HotPDF המציג את Arial, Times New Roman ו-Courier New בעיבוד סגנונות רגיל, מודגש ונטוי על פני מספר ערכות תווים

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

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

תרשים גיאומטריית הסיבוב של TextOut ב-HotPDF: ריצות בזוויות 0, 45 ו-90 מסתובבות נגד כיוון השעון סביב עוגן קבוע בפינה השמאלית התחתונה, עם דוגמאות קריאת TextOut ב-Delphi לתוויות שדרה, סימני מים וכותרות עמודות מוטות
זווית שאינה אפס מסובבת את הריצה נגד כיוון השעון סביב עוגן השמאלית-תחתונה הנייח שלה, וקווי בסיס משותפים מדרגים את X בדיוק כפי ששורות אופקיות מדרגות את Y
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// תווית ציר אנכי לאורך השוליים השמאליים: 90 מעלות נקראות מלמטה למעלה
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// סימן מים אלכסוני DRAFT לרוחב גוף הדף
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// כותרות עמודות נטויות ב-60 מעלות כדי שתוויות ארוכות ייכנסו לטבלה צרה
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 גדל כלפי מטה מאפס, כותב את השורה הראשונה מחוץ לקצה התחתון ומבלה עשר דקות בתהייה לאן היא נעלמה

HotPDF: תרשים עמוד US Letter המציג חשבון קו הבסיס עם LeftMargin 72, TopBaseline 720 ו-Leading קבוע הצועד אל רצפת השוליים התחתונים ב-Y 72, לצד לולאת פריסה עם קבועים בעלי שם עם שומרת AddPage ידנית ואיפוס SetFont
עוגנים בעלי שם הופכים עמודת מספרים קסומים לאריתמטיקה: הורידו את קו הבסיס הרץ ב-Leading בכל שורה והגנו בעצמכם על הרצפה עם AddPage

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

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // השורה הראשונה, במרחק ~1 inch למטה על Letter
  Leading = 16;            // הצעד האנכי בין השורות
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            // הושג השוליים התחתונים
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // הגופן מתאפס בדף חדש
      Y := TopBaseline;
    end;
  end;
end;

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

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

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

HotPDF: ריווח תווים Tc מפזר מקום נוסף באופן שווה אחרי כל גליף של SUMMARY מול ריווח מילים Tw שאוגר אותו בתווי רווח בלבד, המחובר למחזור set, draw, reset המונע מצב ריווח לדלוף אל פסקאות מאוחרות
Tc פורש את התאמתו על כל גליף בזמן ש-Tw פועל רק על תו הרווח, ואיפוס בשורה הבאה שומר על מצב הציור נקי
// Letter-space כותרת קצרה כך שתימתח על פני קו
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);   // אפס לפני טקסט גוף רגיל

// פתח את הרווחים בין מילים בשורה רחבה אחת
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) כותרת אחת ותשכח לאפס אותה, וכל פסקה למטה תירש את המתיחה, שנקראת כטעות עדינה שקשה למקם אותה השורדת הגהה אקראית ונכשלת בבדיקה קפדנית. ההרגל האמין הוא להגדיר ערך ריווח, לצייר את הריצה שזקוקה לו, ולהחזיר אותו לאפס בשורה הבאה, כך ששום קוד מאוחר יותר לא יצטרך לדעת מה עשה סעיף קודם

עמוד TextOut של HotPDF משווה שינוי קנה מידה אופקי של טקסט, ריווח תווים, ריווח מילים, ומצבי עיבוד fill מול stroke

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

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

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