ברכיב HotPDF, ה-THotPDF.Resolution מגדיר את יחידת הציור: כל קואורדינטת X ו-Y, כל שוליים, הגודל שמועבר אל SetFont, והתוצאות של TextWidth ושל GetWideTextWidth נמדדות ב-1/Resolution אינץ. ה-THPDFPage.Width וה-Height לא הולכים אחריו ונשארים בנקודות, ולכן גבולות הפריסה חייבים לבוא מה-UserWidth וה-UserHeight הקריאות-בלבד. הסיבה הרגילה לגעת ב-Resolution היא העברת פורט: מנוע דוחות שכבר חושב ב-1/96 או ב-1/144 אינץ קל יותר להעביר כשהצד של ה-PDF מדבר באותה יחידה מאשר כשכל אתר קריאה מקבל גורם המרה. זה עובד היטב, כל עוד אתם יודעים אילו מספרים עברו ליחידה החדשה ואילו נשארו מאחור
מה ה-THotPDF.Resolution באמת משנה?
ה-THotPDF.Resolution משנה רק איך HotPDF קורא את המספרים שאתם מעבירים; ה-PDF שהוא כותב הוא אותו דבר. ה-setter הוא שתי שורות: ה-SetResolution מאחסן את הערך ומגדיר DocScale := Value / 72. מאותו רגע, ה-XProjection וה-YProjection מחלקים כל קואורדינטה ב-DocScale בדרך אל זרם התוכן, וה-SetFont מחלק את הגודל באותה צורה לפני שהוא רושם אותו. מרחב המשתמש של PDF מוגדר כברירת מחדל 1/72 אינץ (ISO 32000-1 §8.3.2.3), כך שב-Resolution ברירת המחדל של 72 ההקרנה היא הזהות וב-144 יחידת ציור אחת היא חצי נקודה. שום רשומת /UserUnit אינה נכתבת. אותה תכונת עמוד, שנוספה ב-PDF 1.6, היא עניין נפרד ש-HotPDF חושף כ-THPDFPage.SetUserUnit. פרט אחד שתופס באי-כוח ממדריכי ה-TextOut: קואורדינטות העמוד רצות מפינת השמאלי-העליון כשה-Y גדל כלפי מטה, כי ה-YProjection מחשב את ראש ה-MediaBox פחות ה-Y המוכפל, וזה נכון בכל Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // יחידת ציור אחת = 1/144 אינץ
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // אינץ אחד ביחידות ציור
Page.SetFont('Arial', [fsBold], 28); // 28/144 אינץ, פונט של 14 נקודות
Title := 'INVOICE 2026-0417';
// יישור לימין מול קצה העמוד הנמדד באותה יחידה
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // קו של 1 נקודה
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
למה ה-Page.Width חולק על הקואורדינטות שלי ב-Resolution 144?
ה-THPDFPage.Width וה-Height מדווחים על העמוד בנקודות בלי קשר ל-Resolution של המסמך, בזמן שהקואורדינטות שלכם ב-1/Resolution אינץ, ולכן ב-144 העמוד נראה חצי מהרוחב האמיתי שלו. עמוד A4 קורא Width = 595 ו-Height = 842 ב-Resolution 72 ועדיין קורא 595 ו-842 ב-144, שבה הקצה הימני בעצם ב-X = 1190. ה-UserWidth וה-UserHeight, שנוספו ב-v2.766.0, מחזירים Width * DocScale, שהוא גודל העמוד ביחידה שבה אתם מציירים. לפני שהתקיימו, הספרייה עירבבה את השניים בפנים, והתסמינים ב-Resolution 144 היו דרמטיים: פסקאות עטפו אחרי כל תו, ה-THPDFTable.Render דחף כל שורה אל עמוד חדש, וגם מייבא ה-HTML וגם משטח ה-XFA ציירו את התוכן שלהם בחצי גודל, הטופס המשוטח דחוס אל פינת השמאלי-העליון. פריסת פסקאות, רינדור טבלאות, ייבוא HTML, מרכוז EMF, ה-clip של עמוד WMF ואבחוני פריסה כולם קוראים עכשיו את הגודל ביחידות-משתמש. גם קוד הפריסה שלכם צריך: כל דבר שמשווה מול קואורדינטת ציור (שוליים ימניים, בדיקת מעבר עמוד, חישוב מרכוז) שייך אל ה-UserWidth וה-UserHeight, לעולם לא אל ה-Width וה-Height
מלכודת ראשונה: הצבת Width או Height מעבירה את העמוד לנקודות
הצבת Page.Width או Page.Height משנה בשקט את העמוד ל-UserDefined, ועמוד UserDefined מתעלם לגמרי מה-DocScale, כך שכל מה שתציירו עליו אחר כך הוא בנקודות, לא ב-1/Resolution אינץ. ה-setter ישן ומקבל נקודות בכוונה, ולכן משמעותו הושארה כמו שהיא. ההקרנה עבור עמוד UserDefined היא X + MinX פשוט, וה-SetFont מאחסן את הגודל ללא שינוי. ב-Resolution 144 התוצאה היא עמוד שהתוכן שלו יוצא פתאום פי שניים מהעמוד שלפניו. הספרייה ביצעה בעצמה בדיוק את הטעות הזאת: עמודי ההמשך של פסקאות העתיקו פעם את גודל העמוד הקודם דרך ה-Width, וכל עמוד גלישה עבר לנקודות. העמודים האלה מעתיקים עכשיו את ה-Size, את ה-Orientation ואת ה-Resolution של העמוד במקום, ונופלים חזרה אל Width ו-Height רק כשהעמוד המקורי כבר היה UserDefined
שתי דרכי יציאה, לפי מה שאתם צריכים. אם גיליון תקני יספיק, הגדירו Page.Size ו-Page.Orientation והמשיכו לצייר ביחידת ה-Resolution שלכם. אם אתם באמת צריכים גודל עמוד מותאם, קבלו שזהו עמוד נקודות וציירו בנקודות; ה-UserWidth שווה שם ל-Width, ולכן קוד פריסה שתמיד קורא UserWidth ממשיך לעבוד על שני סוגי העמודים. בדיקת היחידה נועצת את זה: ב-Resolution 144 עמוד A4 מדווח UserWidth של 1190, אבל אחרי Width := 500 ו-Height := 400 הוא מדווח 500 ו-400. עמודים טעונים מתנהגים באותה צורה, כי עמוד שנבנה מחדש מ-PDF קיים מכיר רק את ה-MediaBox שלו בנקודות ומצייר בנקודות. עמודים שהמסמך הזה יצר שומרים על היחידות שלהם כשאתם עוברים הלאה וחוזרים דרך ה-CurrentPageNumber, וזה המצב מאז v2.766.26
מלכודת שנייה: למה גדלי פונטים יוצאים בחצי גודל?
גודל פונט שהתחיל כנקודות יוצא בחצי גודל ב-Resolution 144 כי ה-SetFont מתייחס אל ארגומנט הגודל שלו כיחידות ציור וממיר אותו לנקודות לפני האחסון. בפנים, ה-SetFont מאחסן ASize / DocScale * DPI באובייקט הפונט הנוכחי, כך שהערך המאוחסן תמיד נקודות. הספרייה נתקלה בזה פעמיים: נתיב הפונט החלופי ב-WideTextOutBoxEx ועמוד ההמשך של הפסקה שניהם העבירו את ערך הנקודות המאוחסן הזה חזרה אל ה-SetFont, שהכפיל אותו בקנה מידה פעם שנייה וחצה את הטקסט. הקוד שלכם לא יכול לקרוא את הגודל המאוחסן, אבל אותו באג צץ בכל פעם שערך נקודות ממקום אחר מגיע אל ה-SetFont: ה-TFont.Size מטופס VCL, גודל בהגדרת דוח, אורך pt של CSS. המירו אותו קודם, וכללו בגורם את ה-Resolution של העמוד עצמו ואת המקרה של UserDefined, כפי שהשמעת מטאפיילים עושה כשהיא משמיעה את ה-Canvas של העמוד (ראו איך HotPDF מייבא גרפיקה וקטורית של EMF ו-WMF עבור הנתיב הזה):
// יחידות ציור לנקודה בעמוד הנוכחי. משקף את ההקרנה
// ש-HotPDF משתמש בה: 1 על עמוד שהוגדר דרך Width/Height, אחרת
// (document Resolution / 72) * (page Resolution / 72)
function UnitsPerPoint(Pdf: THotPDF): Single;
begin
if Pdf.CurrentPage.Size = UserDefined then
Result := 1
else
Result := (Pdf.Resolution / 72) * (Pdf.CurrentPage.Resolution / 72);
end;
procedure SetFontFromVcl(Pdf: THotPDF; Font: TFont);
begin
// ה-TFont.Size הוא בנקודות; ה-SetFont מצפה ליחידות ציור
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
הספרייה מיישמת את אותו כלל על קבועי הנקודות שלה. הפונט בן 12 הנקודות שכל עמוד חדש מתחיל איתו מוכפל עכשיו בגורם הפנימי של יחידות-לנקודה, כך שהוא 12 נקודות בכל Resolution. ה-DrawChart, שהשוליים, גדלי התוויות ורוחבי הקו שלו כולם נקודות קשיחות בקוד, רץ עכשיו עם קנה המידה מוגדר זמנית ל-1. מה שנשאר ביחידות ציור, בכוונה, הם ברירות המחדל של פרמטרים ציבוריים כמו גודל המודול של ה-DrawQRCode וגודל הפונט של הטבלה כברירת מחדל: הם חלק מהחוזה של ה-API, ולכן ב-Resolution 144 הם אומרים חצי ממה שהם אומרים ב-72. אם אתם ממדדים דוחות מתבנית, המדריך לפלט דוחות עם פונטים ותמונות ב-HotPDF מכסה מאיפה הערכים האלה בדרך כלל מגיעים
איך מאמתים שפריסה היא בלתי תלוית-Resolution?
הבדיקה האמינה ביותר היא השוואת בייטים: רנדרו את אותו עמוד ב-Resolution 72 ושוב ב-144 עם כל קואורדינטה וגודל מוכפלים, וזרמי התוכן הלא דחוסים חייבים להיות זהים. שתי הריצות נוחתות על אותם ערכי נקודות אחרי ההקרנה, ולכן כל הפרש הוא ערך שדילג על ההמרה. כך סוויטת הבדיקות של HotPDF בודקת פסקאות, טבלאות, ייבוא HTML, שיטוח XFA, קשתות, מטאפיילים ותמונות. אותה טכניקה עובדת עבור קוד הדוחות שלכם עם כמעט אפס harness:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // זרמי תוכן קריאים
Pdf.FileName := FileName;
Pdf.Resolution := Res;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 10 * K);
Pdf.CurrentPage.TextOut(36 * K, 36 * K, 0, 'Line 1');
Pdf.CurrentPage.Rectangle(36 * K, 60 * K, 200 * K, 40 * K);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
// RenderPage('r72.pdf', 72, 1) and RenderPage('r144.pdf', 144, 2)
// חייבים להניב זרמי תוכן עמוד זהים בייט-בייט
בדקו את האופרטורים שנושאים מספרים: ה-Td, ה-Tm, ה-Tf, ה-re, ה-w ומערכי ה-TJ. בייטים ברמת הקובץ עדיין ישונו בתאריך היצירה וב-/ID, ולכן השוו את הזרמים, לא קבצים שלמים. אי-התאמה כמעט תמיד מצביעה על אחת משתי המלכודות שלמעלה: עמוד שהוגדר מחדש דרך ה-Width, או ערך נקודות שהועבר ישר אל ה-SetFont. אם אתם חדשים בקריאות הציור עצמן, התחילו בהליכת ה-TextOut של HotPDF עבור גודל, סגנון וסיבוב, ואז חזרו והחליפו את ה-Resolution ברגע שהפריסה שלכם קוראת UserWidth. פרטי API מלאים והורדות ניסיון נמצאים בעמוד רכיב HotPDF ל-Delphi PDF