در HotPDF Component، THotPDF.Resolution واحد رسم را تعریف میکند: هر مختصات X و Y، هر حاشیه، اندازهای که به SetFont میدهی، و نتایج TextWidth و GetWideTextWidth در 1/Resolution اینچ اندازهگیری میشوند. THPDFPage.Width و Height از آن پیروی نمیکنند و در نقطه میمانند، پس مرزهای چیدمان باید از UserWidth و UserHeight فقطخواندنی بیایند. دلیل معمول برای دست زدن به Resolution یک پورت است: یک موتور گزارش که از قبل به 1/96 یا 1/144 اینچ فکر میکند وقتی سمت PDF همان واحد را صحبت کند راحتتر منتقل میشود تا وقتی هر call site یک ضریب تبدیل بگیرد. این خوب کار میکند، به شرطی که بدانی کدام اعداد به واحد جدید رفتهاند و کدامها جا ماندهاند
THotPDF.Resolution واقعاً چه چیزی را عوض میکند؟
THotPDF.Resolution فقط عوض میکند HotPDF اعدادی را که میدهی چطور بخواند؛ PDF ای که مینویسد همان است. setter دو خط است: SetResolution مقدار را ذخیره میکند و DocScale := Value / 72 را میگذارد. از آن به بعد، XProjection و YProjection هر مختصات را در مسیر ورود به content stream بر DocScale تقسیم میکنند و SetFont هم اندازه را به همان شکل قبل از ثبت تقسیم میکند. user space مربوط به PDF بهصورت پیشفرض 1/72 اینچ است (ISO 32000-1 §8.3.2.3)، پس در Resolution پیشفرض یعنی 72 تصویر کردن همانی است و در 144 یک واحد رسم نصف نقطه است. هیچ درایهٔ /UserUnit ای نوشته نمیشود. آن attribute صفحه که در 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 در Resolution 72 میخواند Width = 595 و Height = 842 و در 144 هم همان 595 و 842 را میخواند، جایی که لبهٔ راست واقعاً در X = 1190 است. UserWidth و UserHeight که در v2.766.0 اضافه شدند برمیگردانند Width * DocScale را، که اندازهٔ صفحه در واحدی است که با آن میکشی. قبل از اینکه وجود داشته باشند کتابخانه این دو را داخلی قاطی میکرد و علائم در Resolution 144 سینمایی بودند: پاراگرافها بعد از هر نویسه میشکستند، THPDFTable.Render هر ردیف را به یک صفحهٔ جدید میفرستاد، و هم importer از HTML و هم flattener از XFA محتوایشان را در نصف اندازه میکشیدند، فرم تختشده به گوشهٔ بالا-چپ ازدحام میکرد. چیدمان پاراگراف و رندر جدول و import از 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 صفحه را کپی میکنند و فقط وقتی صفحهٔ اصلی از قبل UserDefined بوده به Width و Height برمیگردند
دو راه خروج، بسته به اینکه چه میخواهی. اگر یک برگهٔ استاندارد کفایت میکند، 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 را در شیء فونت جاری ذخیره میکند، پس مقدار ذخیرهشده همیشه نقطه است. کتابخانه دو بار روی همین سکندری خورد: fallback فونت در WideTextOutBoxEx و صفحهٔ ادامهٔ پاراگراف هر دو آن مقدار نقطهای ذخیرهشده را به SetFont برمیگرداندند که بار دوم مقیاسش میکرد و متن را نصف میکرد. کد تو نمیتواند مقدار ذخیرهشده را بخواند، اما همین باگ هر وقت یک مقدار نقطهای از جای دیگر به SetFont برسد خودش را نشان میدهد: یک TFont.Size از یک فرم VCL، یک اندازه در تعریف گزارش، یک طول pt از CSS. اول تبدیلش کن و Resolution خود صفحه و حالت UserDefined را هم در ضریب بگنجان، همانطور که پخش metafile موقع پخش Canvas صفحه انجام میدهد (برای آن مسیر چگونگی import گرافیک برداری EMF و WMF در HotPDF را ببین):
// واحدهای رسم بهازای هر نقطه روی صفحهٔ جاری. آینهٔ همان تصویری
// است که 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 نقطهای که هر صفحهٔ جدید با آن شروع میشود حالا در ضریب داخلی واحد-به-ازای-نقطه ضرب میشود، پس در هر Resolution ای 12 نقطه است. DrawChart که حاشیهها و اندازههای برچسب و ضخامت خطهایش همه نقطههای هاردکد هستند حالا با مقیاس موقتاً روی 1 اجرا میشود. آنچه عمداً در واحد رسم میماند پیشفرضهای پارامتر عمومی مثل اندازهٔ ماژول DrawQRCode و اندازهٔ فونت پیشفرض جدول است: بخشی از قرارداد API هستند، پس در Resolution 144 یعنی نصف آنچه در 72 معنی میدهند. اگر گزارشها را از روی یک قالب اندازه میگیری، راهنمای خروجی گزارش با فونت و تصویر در HotPDF پوشش میدهد این مقادیر معمولاً از کجا میآیند
چطور راستیآزمایی کنی که یک چیدمان مستقل از Resolution است؟
قابلاتکاترین چک یک مقایسهٔ بایتی است: همان صفحه را در Resolution 72 و دوباره در 144 با هر مختصات و اندازه دو برابر رندر کن و content streamهای فشردهنشده باید یکسان باشند. هر دو اجرا بعد از تصویر شدن روی همان مقادیر نقطهای فرود میآیند، پس هر تفاوتی یک مقداری است که تبدیل را رد کرده. سویت تست HotPDF پاراگرافها و جدولها و import از HTML و تخت کردن XFA و کمانها و metafileها و تصاویر را همینطور چک میکند. همین تکنیک برای کد گزارش خودت با تقریباً هیچ هارنس کار میکند:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // content streamهای قابل خواندن
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) و RenderPage('r144.pdf', 144, 2)
// باید content streamهای صفحه را بایتبهبایت یکسان تولید کنند
عملگرهایی که عدد حمل میکنند را چک کن: Td و Tm و Tf و re و w و آرایههای TJ. بایتهای سطح فایل همچنان در تاریخ ساخت و /ID تفاوت خواهند داشت، پس streamها را مقایسه کن نه فایلهای کامل را. یک عدم تطابق تقریباً همیشه به یکی از دو دام بالا اشاره میکند: صفحهای که از طریق Width تغییر اندازه داده، یا یک مقدار نقطهای که مستقیم به SetFont رفته. اگر با خود فراخوانیهای رسم تازهکاری، از آموزش گامبهگام TextOut در HotPDF برای اندازه، استایل و چرخش شروع کن، بعد برگرد و وقتی چیدمانت UserWidth را میخواند یک بار Resolution را سوئیچ کن. جزئیات کامل API و دانلودهای آزمایشی در صفحهٔ HotPDF Delphi PDF component است