Odborný článok

HotPDF Resolution v Delphi: kresliace jednotky a UserWidth

V HotPDF Component definuje THotPDF.Resolution kresliacu jednotku: každá súradnica X a Y, každý okraj, veľkosť podaná do SetFont a výsledky TextWidth a GetWideTextWidth sa merajú v 1/Resolution palca. THPDFPage.Width a Height ju nenasledujú a ostanú v bodoch, takže hranice rozloženia musia prichádzať z read-only UserWidth a UserHeight. Bežný dôvod siahnuť na Resolution je port: report engine, ktorý už uvažuje v 1/96 či 1/144 palca, sa presťahuje ľahšie, keď PDF strana hovorí tou istou jednotkou, než keď každé miesto volania dostane konverzný faktor. To funguje dobre, pokiaľ viete, ktoré čísla sa prestáhovali do novej jednotky a ktoré ostanú

Čo THotPDF.Resolution vlastne mení?

THotPDF.Resolution mení len to, ako HotPDF číta čísla, ktoré podáte; PDF, ktoré zapíše, je to isté. Setter má dva riadky: SetResolution uloží hodnotu a nastaví DocScale := Value / 72. Od tej chvíle deli každú súradnicu na ceste do content streamu XProjection a YProjection DocScaleom a SetFont deli veľkosť rovnakým spôsobom skôr, než ju zaznamená. PDF user space má predvoľbu 1/72 palca (ISO 32000-1 §8.3.2.3), takže pri predvolenom Resolution 72 je projekcia identita a pri 144 je jedna kresliaca jednotka pol bodu. Žiadny záznam /UserUnit sa nezapisuje. Ten atribút strany, pridaný v PDF 1.6, je samostatná vec, ktorú HotPDF vystavuje ako THPDFPage.SetUserUnit. Detail, ktorý chytí tých prichádzajúcich z TextOut tutoriálov: súradnice strany bežia od ľavého horného rohu s Y rastúcim nadol, lebo YProjection počíta horný okraj MediaBoxu mínus škálované Y, a to platí pri každom Resolution

Ako definuje THotPDF.Resolution kresliacu jednotku v Delphi: setter uloží DocScale ako Resolution delené 72, potom XProjection, YProjection a SetFont delia každú súradnicu a veľkosť na ceste do content streamu, takže Resolution 72 je identita a Resolution 144 robí z jednej kresliacej jednotky pol bodu, zatiaľ čo strana beží naďalej zľava hore s Y nadol
Vo výstupnom súbore sa nepohne nič — mení sa len význam čísel, ktoré podáte, a preto ten istý content stream vyjde pri 72 aj 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 kresliaca jednotka = 1/144 palca
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // jeden palec v kresliacich jednotkách
    Page.SetFont('Arial', [fsBold], 28); // 28/144 palca, font 14 pt
    Title := 'INVOICE 2026-0417';
    // Zarovnanie vpravo voči okraju strany meranému v tej istej jednotke
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // čiara 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Prečo nesedí Page.Width s mojimi súradnicami pri Resolution 144?

THPDFPage.Width a Height hlásia stranu v bodoch, nech je dokumentový Resolution čokoľvek, zatiaľ čo vaše súradnice sú v 1/Resolution palca, takže pri 144 vyzerá strana na polovicu užšia, než naozaj je. A4 strana číta Width = 595 a Height = 842 pri Resolution 72 a stále 595 a 842 pri 144, kde pravý okraj je v skutočnosti na X = 1190. UserWidth a UserHeight, pridané vo v2.766.0, vrátia Width * DocScale, čiže veľkosť strany v jednotke, ktorou kreslíte. Skôr než existovali, miešala knižnica obe hodnoty vnútri a príznaky pri Resolution 144 boli dramatické: odstavce sa lámal po každom znaku, THPDFTable.Render tlačil každý riadok na novú stranu a HTML importér aj XFA flattener kreslili svoj obsah na polovičnú veľkosť, zrovnaný formulár narvaný do ľavého horného rohu. Layout odsekov, vykresľovanie tabuliek, HTML import, centrovanie EMF, WMF clip strany aj diagnostika rozloženia teraz čítajú veľkosť v user jednotkách. Aj váš vlastný layout kód by mal: čokoľvek, čo sa porovnáva s kresliacou súradnicou (pravý okraj, test zlomu strany, výpočet centrovanía), patrí na UserWidth a UserHeight, nikdy na Width a Height

Pasca jedna: priradenie Width alebo Height prepné stranu na body

Nastavenie Page.Width alebo Page.Height potichu zmení stranu na UserDefined a strana UserDefined ignoruje DocScale úplne, takže všetko, čo na ňu potom nakreslíte, je v bodoch, nie v 1/Resolution palca. Setter je starý a zámierne berie body, preto sa jeho význam nechal na pokoji. Projekcia pre stranu UserDefined je holé X + MinX a SetFont uloží veľkosť nezmenenú. Pri Resolution 144 je výsledkom strana, ktorej obsah zrazu vychádza dvakrát väčší než strana pred ňou. Knižnica urobila presne túto chybu sama: pokračovacie strany odsekov kopírovali veľkosť predchádzajúcej strany cez Width a každá pretečová strana preppla na body. Tie strany teraz kopírujú Size, Orientation a Resolution strany a na Width a Height spadnú len vtedy, keď pôvodná strana už bola UserDefined

Dve cesty von, podľa toho, čo potrebujete. Ak stačí štandardný hárok, nastavte Page.Size a Page.Orientation a kreslite naďalej v svojej Resolution jednotke. Ak naozaj potrebujete vlastnú veľkosť strany, prijmite, že je to bodová strana, a kreslite v bodoch; UserWidth sa tam rovná Width, takže layout kód, ktorý vždy číta UserWidth, funguje na oboch druhoch strán. Jednotkový test to priklincováva: pri Resolution 144 hlási A4 strana UserWidth 1190, ale po Width := 500 a Height := 400 hlási 500 a 400. Načítané strany sa správajú rovnako, lebo strana prestavaná z existujúceho PDF pozná len svoj MediaBox v bodoch a kreslí v bodoch. Strany, ktoré tento dokument vytvoril, si držia vlastné jednotky, keď prepnete preč a vrátite sa cez CurrentPageNumber, a to platí od v2.766.26

Prečo si nesedí Page.Width so súradnicami pri Resolution 144 v HotPDF: Width a Height ostanú v bodoch, kým kreslenie používa 1/144 palca, takže A4 strana číta 595, ale jej pravý okraj leží na UserWidth 1190, a priradenie Width prepné stranu na UserDefined, ktoré ignoruje DocScale, takže odstavce sa lámu po znaku, tabuľky po riadku a veľkosti SetFont sa znížia na polovicu
Čokoľvek, čo sa porovnáva s kresliacou súradnicou, patrí na UserWidth a UserHeight — na bodovej strane UserDefined sa obe zhodujú, takže ten istý layout kód prežije oboje

Pasca dva: prečo vychádzajú veľkosti fontov na polovicu?

Veľkosť fontu, ktorá začala ako body, vyjde pri Resolution 144 na polovicu, lebo SetFont berie svoj argument veľkosti ako kresliace jednotky a prevádza ho na body skôr, než ho uloží. Vnútri ukladá SetFont ASize / DocScale * DPI do aktuálneho font objektu, takže uložená hodnota je vždy v bodoch. Knižnica sa na tom prekotiła dvakrát: font fallback vo WideTextOutBoxEx aj pokračovacia strana odsekov podávali tú uloženú bodovú hodnotu späť do SetFont, ktoré ju škálovalo druhýkrát a text sa znížil na polovicu. Váš kód uloženú veľkosť prečítať nemôže, ale tá istá chyba vystúpi vždy, keď sa bodová hodnota odkiaľkoľvek inia dostane do SetFont: TFont.Size z VCL formulára, veľkosť v definícii reportu, dĺžka pt v CSS. Najprv ju preveďte a do faktora zahrňte vlastný Resolution strany aj prípad UserDefined, ako to robí prehrávanie metafilov, keď prehráva Canvas strany (pre tú cestu pozri ako HotPDF importuje vektorovú grafiku EMF a WMF):

// Kresliace jednotky na bod na aktuálnej strane. Zrkadlí projekciu,
// ktorú používa HotPDF: 1 na strane dimenzovanej cez Width/Height, inak
// (Resolution dokumentu / 72) * (Resolution strany / 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 bodoch; SetFont očakáva kresliace jednotky
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Knižnica aplikuje to isté pravidlo na vlastné bodové konštanty. Font 12 bodov, ktorým začína každá nová strana, sa teraz násobí vnútorným faktorom jednotiek na bod, takže je 12 bodov pri každom Resolution. DrawChart, ktorého okraje, veľkosti popiskov aj šírky čiar sú všetky natvrdo bodové, beží teraz s mierkou dočasne nastavenou na 1. V kresliacich jednotkách ostávajú zámerne verejné predvoľby parametrov ako veľkosť modulu DrawQRCode a predvolená veľkosť fontu tabuľky: sú súčasťou API kontraktu, takže pri Resolution 144 znamenajú polovicu toho, čo znamenajú pri 72. Ak dimenzujete reporty zo šablóny, sprievodca výstupom reportov s fontmi a obrázkami v HotPDF popisuje, odkiaľ tie hodnoty zvyknú prichádzať

Ako overíte, že rozloženie je nezávislé od Resolution?

Najspoľahlivejšia kontrola je porovnanie bajtov: vykreslite tú istú stranu pri Resolution 72 a znova pri 144 s každou súradnicou aj veľkosťou zdvojnásobenou a nekomprimované content streamy musia byť identické. Oba behy dospejú po projekcii k tým istým bodovým hodnotám, takže každý rozdiel je hodnota, ktorá preskočila konverziu. Takto kontroluje test suite HotPDF odstavce, tabuľky, HTML import, XFA zrovnanie, oblúky, metafily aj obrázky. Rovnaká technika funguje aj pre váš vlastný report kód takmer bez postroja:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // čitateľné 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)
// musia vydať bajtovo identické content streamy strany
Ako overiť nezávislosť od Resolution v kóde HotPDF v Delphi: vykreslite totožné rozloženie dvakrát, raz pri Resolution 72 s mierkou 1 a raz pri 144 s každou súradnicou aj veľkosťou fontu zdvojnásobenou, a vyžiadajte si bajtovo identické nekomprimované content streamy — nezrovnalosť ukazuje na stranu prepnutú cez Width na UserDefined alebo na neprevedenú bodovú hodnotu, ktorá dospeje do SetFont
Oba behy dospejú po projekcii k tým istým bodovým hodnotám, takže každý rozdiel je číslo, ktoré preskočilo svoju konverziu — ten istý postroj, na ktorý sa spolieha test suite HotPDF

Skontrolujte operátory nesúce čísla: Td, Tm, Tf, re, w a polia TJ. Bajty na úrovni súboru sa aj tak líšia v dátume vytvorenia a v /ID, takže porovnávajte streamy, nie celé súbory. Nezhoda takmer vždy ukazuje na jednu z dvoch pasci vyššie: stranu zmenenú cez Width alebo bodovú hodnotu podanú rovno do SetFont. Ak ste v kresliacich volaniach samotných noví, začnite sprievodcom HotPDF TextOut pre veľkosť, štýl a rotáciu a potom sa vráťte a prepnite Resolution, keď váš layout číta UserWidth. Úplné API detaily a skúšobné verzie sú na stránke HotPDF Delphi PDF component