В HotPDF Component THotPDF.Resolution дефинира единицата за чертане: всяка координата X и Y, всеки margin, размерът, подаван на SetFont, и резултатите от TextWidth и GetWideTextWidth се мерят в 1/Resolution инч. THPDFPage.Width и Height не ги следват и си остават в пунктове, така че границите на подредбата трябва да идват от read-only UserWidth и UserHeight. Обичайната причина да пипнете Resolution е пренос: отчетна машина, която вече мисли в 1/96 или 1/144 инч, се мести по-лесно, когато PDF страната говори същата единица, отколкото когато всяко място на извикване получава фактор за преобразуване. Това работи добре, стига да знаете кои числа са преминали към новата единица и кои са останали назад
Какво всъщност променя THotPDF.Resolution?
THotPDF.Resolution променя само това как HotPDF чете числата, които подавате; PDF-ът, който записва, е същият. Setter-ът е два реда: SetResolution пази стойността и задава DocScale := Value / 72. Оттам нататък XProjection и YProjection делят всяка координата на DocScale по пътя към content stream-а, а SetFont дели размера по същия начин, преди да го запише. PDF user space по подразбиране е 1/72 инч (ISO 32000-1 §8.3.2.3), така че при подразбиращата се Resolution 72 проекцията е идентична, а при 144 една единица за чертане е половин точка. Никакъв запис /UserUnit не се записва. Този page атрибут, добавен в 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 единица за чертане = 1/144 инч
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // един инч в единици за чертане
Page.SetFont('Arial', [fsBold], 28); // 28/144 инча, шрифт 14 pt
Title := 'INVOICE 2026-0417';
// Подравняване вдясно към ръба на страницата, мерена в същата единица
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // линия 1 pt
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 центрирането, WMF клипът на страницата и диагностиката на подредбата вече всички четат размера в потребителски единици. И вашият код за подредба трябва: всичко, което сравнява с координата за чертане (десен margin, тест за пренасяне на страница, сметка за центриране), принадлежи на 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, продължава да работи и на двата вида страница. Unit тестът го закова: при Resolution 144 A4 страница докладва UserWidth 1190, но след Width := 500 и Height := 400 докладва 500 и 400. Заредени страници се държат по същия начин, защото страница, преизградена от съществуващ PDF, знае само своя MediaBox в пунктове и чертае в пунктове. Страници, които този документ е създал, пазят собствените си единици, когато превключите настрани и се върнете през CurrentPageNumber — така е от v2.766.26 насам
Капан втори: защо размерите на шрифтове излизат наполовина?
Размер на шрифт, тръгнал като пунктове, излиза наполовина при Resolution 144, защото SetFont третира аргумента си за размер като единици за чертане и го превръща в пунктове, преди да го запише. Вътрешно SetFont пази ASize / DocScale * DPI в текущия font обект, така че записаната стойност е винаги в пунктове. Библиотеката се спъна в това два пъти: font fallback-ът в WideTextOutBoxEx и страницата за продължение на абзац подадоха тази записана пунктовa стойност обратно на SetFont, който я мащабира втори път и преполови текста. Вашият код не може да прочете записания размер, но същият бъг излиза наяве всеки път, когато пунктовa стойност отвънка достигне SetFont: TFont.Size от VCL форма, размер в дефиниция на отчет, CSS дължина pt. Превърнете го първо и включете в фактора собствената Resolution на страницата и случая UserDefined, както прави възпроизвеждането на metafile, когато изпълнява отново Canvas на страницата (вижте как HotPDF импортира EMF и WMF векторна графика за този път):
// Единици за чертане на точка на текущата страница. Огледа проекцията,
// която HotPDF ползва: 1 на страница, оразмерена чрез Width/Height, иначе
// (Resolution на документа / 72) * (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, чиито margins, размери на етикети и дебелини на линии са всички hard-коднати пунктове, вече се пуска със scale, временно зададен на 1. Това, което остава в единици за чертане нарочно, са подразбиращите се стойности на публични параметри като размера на модула на DrawQRCode и подразбиращият се размер на шрифта на таблица: те са част от API договора, така че при Resolution 144 значат половината от това, което значат при 72. Ако оразмерявате отчети от шаблон, ръководството за отчетен изход със шрифтове и изображения в HotPDF покрива откъде обикновено идват тези стойности
Как проверявате, че подредбата е независима от Resolution?
Най-надеждната проверка е сравнение на байтове: рендирайте същата страница при Resolution 72 и пак при 144 с всяка координата и размер удвоени, и некомпресираните content stream-ове трябва да са идентични. И двата пуска стигат до същите стойности в пунктове след проекцията, така че всяка разлика е стойност, прескачила преобразуването. Така тестовият набор на HotPDF проверява абзаци, таблици, 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 за размер, стил и ротация, после се върнете и превключете Resolution, щом подредбата ви чете UserWidth. Пълните API подробности и trial изтегляния са на страницата на HotPDF Delphi PDF компонента