V HotPDF Component definuje THotPDF.Resolution kreslicí jednotku: každá souřadnice X a Y, každý okraj, velikost předaná SetFont a výsledky TextWidth a GetWideTextWidth se měří v 1/Resolution palce. THPDFPage.Width a Height se jím nenechají vést a zůstávají v bodech, takže hranice rozvržení musí pocházet z read-only UserWidth a UserHeight. Obvyklý důvod, proč Resolution sáhnout, je port: report engine, který už myslí v 1/96 či 1/144 palce, se snáz přestěhuje, když PDF strana mluví touž jednotkou, než když každé volání dostane konverzní faktor. To funguje dobře, dokud víte, která čísla se do nové jednotky přestěhovala a která zůstala doma
Co THotPDF.Resolution doopravdy mění?
THotPDF.Resolution mění jen to, jak HotPDF čte čísla, která mu dáte; zapsané PDF je totéž. Setter jsou dva řádky: SetResolution uloží hodnotu a nastaví DocScale := Value / 72. Od té chvíle dělí XProjection a YProjection každou souřadnici DocScale na cestě do content streamu a SetFont dělí velikost stejně, než ji zaznamená. PDF user space defaultuje na 1/72 palce (ISO 32000-1 §8.3.2.3), takže při defaultní Resolution 72 je projekce identita a při 144 je jedna kreslicí jednotka půl bodu. Položka /UserUnit se nezapisuje. Tenhle atribut stránky, přidaný v PDF 1.6, je zvláštní věc, kterou HotPDF vystavuje jako THPDFPage.SetUserUnit. Detail, který chytá lidi z TextOut tutoriálů: souřadnice stránky běží z horního levého rohu s Y rostoucím dolů, protože YProjection počítá horní hranu MediaBoxu minus škálované Y — a platí to při každé Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1 kreslicí jednotka = 1/144 palce
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // jeden palec v kreslicích jednotkách
Page.SetFont('Arial', [fsBold], 28); // 28/144 palce, font 14 pt
Title := 'INVOICE 2026-0417';
// Zarovnání vpravo vůči hraně stránky měřené v téže jednotce
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // linka 1 pt
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Proč se Page.Width neshoduje s mými souřadnicemi při Resolution 144?
THPDFPage.Width a Height hlásí stránku v bodech, ať má dokument jakoukoli Resolution, zatímco vaše souřadnice jsou v 1/Resolution palce, takže při 144 vypadá stránka dvakrát užší, než doopravdy je. Stránka A4 čte Width = 595 a Height = 842 při Resolution 72 a stále 595 a 842 při 144, kde pravá hrana leží ve skutečnosti na X = 1190. UserWidth a UserHeight, přidané ve v2.766.0, vracejí Width * DocScale, tedy velikost stránky v jednotce, kterou kreslíte. Před jejich vznikem knihovna oba světy míchala interně a příznaky při Resolution 144 byly dramatické: odstavce se lámaly po každém znaku, THPDFTable.Render tlačil každý řádek na novou stránku a HTML importer i XFA flattener kreslili obsah v poloviční velikosti, zploštěný formulář stísněný v levém horním rohu. Rozvržení odstavců, vykreslování tabulek, HTML import, centrování EMF, WMF ořez stránky i diagnostika rozvržení teď všichni čtou velikost v user jednotkách. I váš vlastní layout kód by měl: cokoli, co se srovnává s kreslicí souřadnicí (pravý okraj, test zlomu stránky, výpočet centrování), patří na UserWidth a UserHeight, nikdy na Width a Height
Past jedna: přiřazení Width nebo Height přepne stránku na body
Nastavení Page.Width nebo Page.Height potichu přepne stránku na UserDefined a UserDefined stránka ignoruje DocScale úplně, takže všechno, co na ni potom nakreslíte, je v bodech, ne v 1/Resolution palce. Setter je starý a bere body záměrně, proto se jeho význam nechal být. Projekce pro UserDefined stránku je prosté X + MinX a SetFont ukládá velikost beze změny. Při Resolution 144 je výsledkem stránka, jejíž obsah najednou vychází dvakrát větší než stránka před ní. Knihovna udělala přesně tutéž chybu sama: pokračovací stránky odstavců kopírovaly velikost předchozí stránky přes Width a každá overflow stránka se přepnula na body. Tyhle stránky teď kopírují Size, Orientation a Resolution stránky a na Width a Height sáhnou jen tehdy, když už byla původní stránka UserDefined
Dvě cesty ven, podle toho, co potřebujete. Pokud poslouží standardní list, nastavte Page.Size a Page.Orientation a kreslete dál ve své Resolution jednotce. Pokud doopravdy potřebujete vlastní velikost stránky, smiřte se s tím, že je to bodová stránka, a kreslete v bodech; UserWidth se tam rovná Width, takže layout kód, který vždy čte UserWidth, funguje na obou typech stránek. Unit test to přibíjí: při Resolution 144 hlásí stránka A4 UserWidth 1190, ale po Width := 500 a Height := 400 hlásí 500 a 400. Načtené stránky se chovají stejně, protože stránka přestavěná z existujícího PDF zná jen svůj MediaBox v bodech a kreslí v bodech. Stránky, které tento dokument vytvořil, si drží své jednotky, když přepnete pryč a vrátíte se přes CurrentPageNumber, což platí od v2.766.26
Past dvě: proč vycházejí velikosti fontů napůl?
Velikost fontu, která začínala jako body, vychází při Resolution 144 napůl, protože SetFont bere svůj argument velikosti jako kreslicí jednotky a před uložením ho převede na body. Interně ukládá SetFont ASize / DocScale * DPI do aktuálního objektu fontu, takže uložená hodnota je vždy v bodech. Knihovna se na tom pošlapala dvakrát: fontový fallback v WideTextOutBoxEx i pokračovací stránka odstavců předaly tuhle uloženou bodovou hodnotu zpět do SetFont, které ji znovu škálovalo a zpolovilo text. Váš kód uloženou velikost nepřečte, ale tentýž bug se vynoří, kdykoli se bodová hodnota odněkud dostane do SetFont: TFont.Size z VCL formy, velikost v definici reportu, délka v CSS pt. Nejdřív ji převeďte a do faktoru zahrňte Resolution stránky i případ UserDefined, tak jako playback metafile, když přehrává Canvas stránky (tu cestu popisuje jak HotPDF importuje vektorovou grafiku EMF a WMF):
// Kreslicí jednotky na bod na aktuální stránce. Zrcadlí projekci,
// kterou HotPDF používá: 1 na stránce velikované přes Width/Height, jinak
// (Resolution dokumentu / 72) * (Resolution stránky / 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 je v bodech; SetFont chce kreslicí jednotky
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
Knihovna aplikuje totéž pravidlo na vlastní bodové konstanty. Font 12 bodů, se kterým začíná každá nová stránka, se teď násobí interním faktorem jednotek na bod, takže je 12 bodů při jakékoli Resolution. DrawChart, jehož okraje, velikosti popisků i šířky čar jsou všichni napevno v bodech, běží teď se škálou dočasně nastavenou na 1. Co zůstává v kreslicích jednotkách, záměrně, jsou defaulty veřejných parametrů jako velikost modulu DrawQRCode a defaultní velikost fontu tabulky: jsou součástí API kontraktu, takže při Resolution 144 znamenají polovinu toho, co znamenají při 72. Pokud sazujete reporty z šablony, průvodce reportovým výstupem s fonty a obrázky v HotPDF pokrývá, odkud tyhle hodnoty obvykle pocházejí
Jak ověříte, že rozvržení je nezávislé na Resolution?
Nejspolehlivější kontrola je bajtové srovnání: vykreslete tutéž stránku při Resolution 72 a znovu při 144 se všemi souřadnicemi a velikostmi zdvojnásobenými a nekomprimované content streamy musí být identické. Oba běhy dopadnou po projekci na tytéž bodové hodnoty, takže jakýkoli rozdíl je hodnota, která konverzi přeskočila. Takhle kontroluje test suite HotPDF odstavce, tabulky, HTML import, XFA zploštění, arc, metafile i obrázky. Tatáž technika funguje pro váš vlastní report kód s téměř žádným obalem:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // čitelné content streamy
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) a RenderPage('r144.pdf', 144, 2)
// musí vyprodukovat bajtově identické content streamy stránek
Zkontrolujte operátory nesoucí čísla: Td, Tm, Tf, re, w a pole TJ. Bajty na úrovni souboru se pořád budou lišit v datu vytvoření a /ID, takže srovnávejte streamy, ne celé soubory. Nesoulad skoro vždy ukazuje na jednu ze dvou pastí výše: stránka, která se změnila velikost přes Width, nebo bodová hodnota pouštěná rovnou do SetFont. Pokud vám jde o kreslicí volání samotná, začněte s TextOut průvodcem HotPDF pro velikost, styl a rotaci a vraťte se, až vaše rozvržení čte UserWidth, a Resolution přepněte. Kompletní detaily API a trial ke stažení jsou na stránce HotPDF Delphi PDF component