יצירת דוח מסתכמת בהצבת שלושה דברים על עמוד ולגרום להם להסכים היכן הם יושבים: טקסט בקואורדינטות ידועות, גופנים שמעבדים אותו הדבר בשרת כמו בשולחן העבודה שלך, ותמונות בגודל שמתאים. כל השאר שספריית דוחות עושה מסודר סביב אותם שלושה. HotPDF, ספריית יצירת PDF של losLab עבור Delphi ו-C++Builder, מעניקה לך כל אחד מהם כקריאה ישירה על אובייקט הדף, והחיכוך היחיד האמיתי הוא מערכת הקואורדינטות שמתחת, שפועלת בכיוון הנגדי מ-canvas ה-VCL אליו אתה רגיל. ייצב את הכיוון הזה תחילה ויתר עבודת הפריסה תפסיק להיאבק בך
מיקום טקסט ומוצא פינה שמאל-תחתון
כמעט כל דוח ראשון יוצא הפוך. הכותרת נוחתת ליד הקצה התחתון וכל שורה מתחתיה מטפסת כלפי מעלה. אין תקלה. מרחב המשתמש של PDF, המוגדר ב-ISO 32000-1 §8.3, מציב את המוצא בפינה שמאל-תחתון עם Y גדל כלפי מעלה, שהוא דימוי הראי של canvas ה-GDI שבו Y גדל כלפי מטה מפינה שמאל-עליון. חמש דקות של פיוס עם זה חוסכות פריסה שאחרת תכתוב מחדש כשהמספרים יפסיקו להיות הגיוניים
הקריאה המרכזית של אובייקט הדף היא TextOut(X, Y, Angle, Text). X ו-Y ממקמים את הטקסט בנקודות מהפינה שמאל-תחתון, ו-Angle מסובב אותו במעלות, כך נמשך חותמת DRAFT או COPY אלכסונית ללא תמיכה מיוחדת. הטריק שמאפשר לאינטואיציה שמאומנת ב-VCL להמשיך לעבוד הוא לבטא Y כגובה הדף פחות המרחק הרצוי מהחלק העליון:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-0001.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE'); // 50pt from top of Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // rotated stamp
Pdf.AddPage; // CurrentPage now points here
Pdf.CurrentPage.SetFont('Arial', [], 10); // font state does not carry over
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
שני ההתנהגויות שמוגדרות במצב ברשימה הזו אחראיות לרוב הבאגים שמופיעים רק בעמוד שני. AddPage מצביע מחדש את CurrentPage לעמוד שזה עתה יצר, כך שהפניית עמוד שמסרת לפני כן כבר לא מציירת היכן שאתה מצפה. בחירת גופן גם היא לכל עמוד ולא לכל מסמך. אם תדלג על SetFont אחרי AddPage, ה-TextOut הראשון בעמוד החדש חוזר לכל ברירת מחדל שהעמוד התחיל בה, לא לגופן הכותרת המודגש שהגדרת שלושה עמודים קודם לכן. ההרגל הבטוח הוא לטפל ב"התחל עמוד חדש" וב"כונן מחדש את מצב הטקסט" כצעד אחד בלתי נפרד בלולאת הדוח
גופנים שקיימים בשרת, לא רק בשולחן העבודה שלך
רוב בעיות הגופן הן למעשה בעיות פריסה בתחפושת. תיבת הפיתוח שלך מותקן בה הגופן הארגוני, לכן הדוח נראה נכון במסך שלך ונשלח. המארח בייצור מריץ את העבודה תחת חשבון שירות שמעולם לא הותקן בו אותו גופן, המרנדר מחליף בשקט משהו שהוא יכול למצוא, והפעם הראשונה שמישהו שומע על כך היא כאשר לקוח שואל מדוע נייר הלוגו השתנה. הדרך לצאת היא להפסיק לסמוך על ספריית גופני מערכת ההפעלה ולטעון את הגופן מקובץ שהמתקין שלך מניח בדיסק. קריאת הרישום של Unicode ב-HotPDF לוקחת נתיב ועושה בדיוק זאת:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
TextOut מקבל WideString ישירות, מה שחשוב יותר ממה שנראה בהתחלה. שם לקוח עם הטעמה, רחוב גרמני, עיר פולנית: אלה אינם מקרי קצה, הם התוכן הרגיל של טבלת לקוחות, והם עוברים דרך אותה קריאה כמו התוויות ASCII שאתה מקודד קשה, כל עוד הגופן הרשום מכיל בפועל את הגליפים. אילוץ גרסה אחד מלווה גופנים מוטמעים: המסמך חייב להיות PDF 1.5 ומאוחר יותר, לכן אם דרישה לא קשורה מצמידה אותך לגרסה ישנה יותר, זה הדבר שישבור בשקט. כתבים מימין לשמאל כמו ערבית ועברית זקוקים לעיצוב אמיתי ולא חיפוש גליפים ישיר, ויש לכך צינור משלו; ראה מאמרנו על עיצוב טקסט כתבים מורכבים עם HotPDF
כאשר אין גופן מותקן שיכול לבטא מה שאתה צריך, כגון תווי MICR על צ'ק או ערכת סמלים קניינית, גופני Type 3 ממלאים את החסר. אתה מגדיר כל גליף כזרם תוכן קטן דרך RegisterType3Font ו-AddType3Glyph. זהו פינה מתמחה של ה-API ונדיר שתגיע אליה, אך היא הרבה יותר נקייה מפיזור מאות ביטמאפ סמלים קטנים על פני דף
תמונות: הארגומנטים האמצעיים הם רוחב וגובה, לא פינה
טיפול בתמונות מתחלק לשני שלבים, ושמירה עליהם נפרדים היא כל הנקודה. AddImage לוקח TBitmap או TJPEGImage, מטמיע אותו פעם אחת, ומחזיר אינדקס. אומנות PNG חייבת להיות מפוענחת לביטמאפ לפני שהיא מגיעה לשם. ShowImage אז מצייר את האינדקס הזה בכל מקום ובכל תדירות שתרצה. סדר הארגומנטים ב-ShowImage הוא המקום היחיד שכדאי להאט ולקרוא:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decode PNG to a bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless for flat-color art
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): not (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
שני המספרים שאחרי המיקום הם רוחב וגובה. הם אינם קואורדינטות הפינה הנגדית, והארגומנט הסופי הוא זווית סיבוב במעלות. קרא את החתימה כקופסת X1/Y1/X2/Y2 ולוגו של 120 על 40 הממוקם ב-(50, 700) נמתח משם עד (120, 40) במקום, משתרע על רוב הדף. הפלט הופך את הטעות לברורה בעוד קוד המקור נראה סביר לחלוטין, וזה מה שגורם לה לבזבז אחר הצהריים. KeepImageAspectRatio מוגדר כברירת מחדל ל-True, כך שקופסה עם פרופורציות שגויות מוסיפה שוליים לתמונה במקום לעוות אותה; הפוך אותו ל-False רק כאשר אתה באמת מתכוון למתוח
הפיצול בין רישום למיקום משתלם בריצות ארוכות. מפני ש-AddImage מטמיע את הפיקסלים פעם אחת וכל ShowImage עם אותו אינדקס מצביע בחזרה לאותו אובייקט מוטמע, היכן שאתה קורא ל-AddImage מחליט על גודל הקובץ. קרא אותו בתוך לולאת הדפים עבור הצהרה של 500 עמודים ואותו לוגו מוטמע 500 פעמים. קרא אותו פעם אחת לפני הלולאה, שמור את האינדקס, והלוגו מאוחסן פעם אחת בלבד. מילון קטן עם מפתח נתיב הנכס מספיק כדי לוודא שכל תמונה נפרדת נרשמת בדיוק פעם אחת
בחירת ה-codec היא ידית הגודל האחרת. תוכן צילומי, קבצים סרוקים וכדומה, שייך ב-JPEG: מסור icJpeg ל-AddImage והוסף JpegQuality לכ-85, מפני שהמאפיין מתחיל ב-100 וההבדל ב-85 אינו נראה בעמוד מודפס. אומנות שטוחת צבע כמו לוגואים, תרשימים וציורי קו שייכת ב-icFlate, שם דחיסה ללא אובדן היא כבר קומפקטית ו-JPEG היה יוצר צלצול נראה סביב הקצוות החדים. ריצת הצהרה שדוחפת תמונה מלאה על כל עמוד יכולה להתנפח לגיגה-בייטים; אותו תוכן ב-JPEG 85 נוחת בסביבות עשירית הגודל, ואף קורא לא יכול להבחין בהבדל
קווים, קופסאות והצללה עם פרימיטיבי נתיב
הקו האופקי מתחת לכותרת טבלה וקופסת האפור מאחורי שורת הסיכום אינם צריכים להיות תמונות. צייר אותם כווקטורים והם יישארו חדים בכל הגדלה, ידפיסו בחדות, ויוסיפו כמעט כלום לקובץ. HotPDF עוקב אחר אותו מודל שזרמי תוכן PDF גולמי משתמשים בו: בנה נתיב, ואז קרא לאופרטור שמצייר אותו
// Horizontal rule under the table header
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Shaded totals box: X, Y, width, height
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
הסדר אינו אופציונלי: הגדר את מצב הציור, בנה את הנתיב, ואז קרא ל-Stroke או ל-Fill. נתיב שבנית אך לא ציירת לא תורם כלום לדף, וזו כמעט תמיד התשובה כאשר קו "לא מופיע". SetRGBFillColor לוקח TColor יחיד, כך שהקבועים המוכרים של VCL כמו clNavy ו-clBlack מתחברים ישירות, ו-Rectangle משתמש באותם ארגומנטי רוחב-וגובה כמו מיקום תמונה ולא שתי פינות. אזהרה אחת לגבי קווים דקים: כל דבר מתחת לכחצי נקודה יכול להיראות אלגנטי על מסך ואז להיעלם במדפסת משרדית של 600 dpi, לכן 0.75 נקודה הוא רצפה סבירה לכל קו שחייב לשרוד הדפסה
עימוד מול נתונים אמיתיים, לא נתוני דוגמה
פרט אחד לסדר לפני שהפריסה מתייצבת: עמודות מספריות צריכות להיות מיושרות על הקצה הימני שלהן, והדרך לעשות זאת היא למדוד את הרוחב המעובד של כל ערך ולמקם אותו אחורה מגבול העמודה, לא לרפד את המחרוזת ברווחים מובילים. ריפוד רווחים מיושר רק בגופן מונו-רוחב, ואף אחד לא מגדיר דוח פיננסי בגופן מונו-רוחב. הרץ את הערכים דרך שגרות מודעות-לאזור של Delphi כמו FormatFloat תחילה, כך שמפריד האלפים שאתה מודד את רוחבו הוא אותו אחד שהאזור של הלקוח יציג בפועל
הסכנה בעימוד היא שאתה כותב אותו מול מערך הנתונים של ההדגמה, שבו עשר שורות קצרות מתאימות בעמוד אחד והלולאה לא צריכה להיקטע. הייצור מוסר לך לקוח ששם החברה שלו רץ 140 תווים והצהרה עם 4,000 פריטי שורה, ועכשיו הלולאה חייבת להיקטע נכון בכל פעם. התבנית שמחזיקה היא סמן Y יחיד שנע כלפי מטה כאשר אתה מחסיר את גובה כל שורה, ובדיקה שמתחילה עמוד חדש ברגע שהסמן יחצה את השוליים התחתונים. כלפי מטה כאן משמעו Y קטן, שהוא המקום היחיד שבו המוצא שמאל-תחתון נשאר לא-אינטואיטיבי. שמור את כל זה בשגרה אחת שגם מוציאה מחדש SetFont ומציירת מחדש את הכותרת השוטפת בעמוד החדש, ובאגי off-by-one-page לא ימצאו אחיזה. כאשר אותם דוחות גם חייבים לעמוד בכללי ארכיון או נגישות, הבחירות שאתה עושה ממש כאן — אילו גופנים אתה מטמיע, האם הפלט מתויג, אילו מרחבי צבע אתה משתמש — הן שאותם תקנים בוחנים; מדריך PDF/A, PDF/X ו-PDF/UA של HotPDF שווה לקרוא לפני שהתבנית מתייצבת
כל קריאה שהוצגה כאן — מיקום טקסט, רישום גופן, הטמעת תמונה וציור נתיב — נשלחת בHotPDF Component עבור Delphi ו-C++Builder, שהפניה שלו מתעדת את ה-API המלא של הפלט לצד תכונות הטפסים, ההצפנה והחתימה שיושבות לידו