У HotPDF Component 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, який він пише, — той самий. Сетер — два рядки: SetResolution зберігає значення і ставить DocScale := Value / 72. З того моменту XProjection і YProjection ділять кожну координату на DocScale на шляху в потік вмісту, а SetFont так само ділить розмір перед записом. User space 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 одиниця малювання = 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-кліп сторінки й діагностика макета тепер усе читають розмір у користувацьких одиницях. Ваш власний код макета теж має: усе, що звіряється з координатою малювання (правий відступ, тест розриву сторінки, розрахунок центрування), належить на UserWidth і UserHeight, і ніколи на Width і Height
Пастка один: присвоєння Width чи Height перемикає сторінку в пункти
Задання Page.Width чи Page.Height тихо перемикає сторінку в UserDefined, а UserDefined-сторінка цілком ігнорує DocScale, тож усе, що ви на ній далі малюєте, — у пунктах, а не в 1/Resolution дюйма. Сетер старий і за задумом бере пункти, саме тому його значення лишили недоторканим. Проєкція для 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-сплощення, дуги, метафайли та зображення. Та сама техніка працює для вашого власного коду звітів майже без стенду:
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) і RenderPage('r144.pdf', 144, 2)
// мусять дати байт-у-байт ідентичні потоки вмісту сторінок
Перевіряйте оператори, що несуть числа: Td, Tm, Tf, re, w і масиви TJ. Файлові байти все ще розійдуться в даті створення та /ID, тож порівнюйте потоки, а не цілі файли. Розбіжність майже завжди вказує на одну з двох пасток вище: сторінку, розмірену через Width, чи пунктове значення, передане прямо в SetFont. Якщо ви новачок у самих викликах малювання, почніть із променю HotPDF TextOut для розміру, стилю і ротації, потім поверніться і перемкніть Resolution, щойно ваш макет читає UserWidth. Повні деталі API і пробні завантаження — на сторінці HotPDF Delphi PDF component