Tehnični članak

HotPDF Resolution v Delphiju: risalne enote in UserWidth

V HotPDF Component THotPDF.Resolution definira risalno enoto: vsaka koordinata X in Y, vsak rob, velikost, podana k SetFont, in rezultati TextWidth ter GetWideTextWidth se merijo v 1/Resolution palca. THPDFPage.Width in Height ji ne sledita in ostajata v točkah, zato morajo meje postavitve prihajati iz read-only lastnosti UserWidth in UserHeight. Običajen razlog za dotikanje Resolution je prenos: motor poročil, ki že misli v 1/96 ali 1/144 palca, se lažje preseli, kadar PDF stran govori isto enoto, kot kadar vsako klicno mesto dobi pretvorbeni faktor. To dela dobro, dokler veste, katera števila so se preselila v novo enoto in katera so ostala za sabo

Kaj THotPDF.Resolution dejansko spremeni?

THotPDF.Resolution spremeni samo to, kako HotPDF bere števila, ki jih podate; PDF, ki ga zapiše, je isti. Nastavitelj sta dve vrstici: SetResolution shrani vrednost in nastavi DocScale := Value / 72. Od takrat naprej XProjection in YProjection delita vsako koordinato s DocScale na poti v tok vsebine, SetFont pa na enak način deli velikost, preden jo zabeleži. Uporabniški prostor PDF privzame 1/72 palca (ISO 32000-1 §8.3.2.3), zato je pri privzeti Resolution 72 projekcija identiteta, pri 144 pa je ena risalna enota pol točke. Vnos /UserUnit se ne zapiše. Ta atribut strani, dodan v PDF 1.6, je ločena zadeva, ki jo HotPDF izpostavi kot THPDFPage.SetUserUnit. Ena podrobnost, ki ujame ljudi, prišle iz vadnic TextOut: koordinate strani tečejo od zgornjega levega kota z Y, ki raste navzdol, ker YProjection izračuna zgornji rob MediaBox minus skalirani Y, in to ostane res pri vsaki Resolution

Kako THotPDF.Resolution definira risalno enoto v Delphiju: nastavitelj shrani DocScale kot Resolution deljeno s 72, nato XProjection, YProjection in SetFont delita vsako koordinato in velikost na poti v tok vsebine, zato je Resolution 72 identiteta in Resolution 144 naredi eno risalno enoto pol točke, medtem ko stran še vedno teče od zgornjega levega kota z Y navzdol
V izhodni datoteki se nič ne premakne — spremeni se samo pomen števil, ki jih podate, zato se isti tok vsebine pokaže pri 72 in 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 risalna enota = 1/144 palca
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // en palec v risalnih enotah
    Page.SetFont('Arial', [fsBold], 28); // 28/144 palca, pisava 14 pt
    Title := 'INVOICE 2026-0417';
    // Poravnajte desno na rob strani, merjen v isti enoti
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // črta 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Zakaj se Page.Width ne ujema z mojimi koordinatami pri Resolution 144?

THPDFPage.Width in Height poročata stran v točkah, ne glede na Resolution dokumenta, vaše koordinate pa so v 1/Resolution palca, zato pri 144 stran izgleda pol toliko široka, kot je res. Stran A4 bere Width = 595 in Height = 842 pri Resolution 72 in še vedno bere 595 in 842 pri 144, kjer je desni rob dejansko pri X = 1190. UserWidth in UserHeight, dodana v v2.766.0, vrneta Width * DocScale, kar je velikost strani v enoti, s katero rišete. Pred njima je knjižnica notranje mešala oboje, simptomi pri Resolution 144 pa so bili dramatični: odstavki so se lomili po vsakem znaku, THPDFTable.Render je vsako vrstico potisnil na novo stran, izvoznik HTML in izravnalnik XFA pa sta risala svojo vsebino na polovico velikosti, izravnani obrazec stisnjen v zgornji levi kot. Postavitev odstavkov, izrisovanje tabel, uvoz HTML, sredinjenje EMF, izrez strani WMF in diagnostika postavitve zdaj vsi berejo velikost v uporabniških enotah. Tudi vaša lastna koda postavitve bi morala: vse, kar primerja z risalno koordinato (desni rob, preizkus preloma strani, izračun sredinjenja), pripada na UserWidth in UserHeight, nikoli na Width in Height

Past ena: dodelitev Width ali Height preklopi stran na točke

Nastavitev Page.Width ali Page.Height tiho spremeni stran v UserDefined, stran UserDefined pa popolnoma spregleda DocScale, zato je vse, kar nanjo potem narišete, v točkah, ne v 1/Resolution palca. Nastavitelj je star in po zasnovi jemlje točke, zato je bil njegov pomen puščen pri miru. Projekcija za stran UserDefined je goli X + MinX, SetFont pa shrani velikost nespremenjeno. Pri Resolution 144 je rezultat stran, katere vsebina nenadoma izide dvakrat toliko velika kot stran pred njo. Knjižnica je naredila točno to napako sama: strani nadaljevanja odstavkov so kopirale velikost prejšnje strani skozi Width in vsaka stran preliva je prešla na točke. Te strani zdaj namesto tega kopirajo Size, Orientation in Resolution strani ter padejo nazaj na Width in Height samo, kadar je bila izvirna stran že UserDefined

Dva izhoda, odvisno od tega, kaj potrebujete. Če bo zadostoval standarden list, nastavite Page.Size in Page.Orientation in nadaljujte z risanjem v svoji enoti Resolution. Če res potrebujete velikost strani po meri, sprejmite, da je to stran v točkah, in rišite v točkah; UserWidth je tam enak Width, zato koda postavitve, ki vedno bere UserWidth, dela na obeh vrstah strani. Enotski preizkus to pribije: pri Resolution 144 stran A4 poroča UserWidth 1190, po Width := 500 in Height := 400 pa poroča 500 in 400. Naložene strani se obnašajo enako, ker stran, znova zgrajena iz obstoječega PDF, pozna samo svoj MediaBox v točkah in riše v točkah. Strani, ki jih je ta dokument ustvaril, obdržijo svoje enote, kadar odmaknete in se vrnete skozi CurrentPageNumber, kar velja od v2.766.26

Zakaj se Page.Width ne ujema z vašimi koordinatami pri Resolution 144 v HotPDF: Width in Height ostajata v točkah, medtem ko risanje uporablja 1/144 palca, zato stran A4 bere 595, a njen desni rob leži pri UserWidth 1190, dodelitev Width pa preklopi stran na UserDefined, ki spregleda DocScale, zato se odstavki lomijo po znaku, tabele polomijo po vrstici in velikosti SetFont se prepolovijo
Vse, kar primerja z risalno koordinato, pripada na UserWidth in UserHeight — na strani v točkah UserDefined se oboje pokrije, zato ista koda postavitve preživi oboje

Past dva: zakaj velikosti pisav izidejo na polovico?

Velikost pisave, ki je začela kot točke, izide na polovico pri Resolution 144, ker SetFont ravna s svojim argumentom velikosti kot z risalnimi enotami in ga pretvori v točke, preden ga shrani. Notranje SetFont shrani ASize / DocScale * DPI v trenutni objekt pisave, zato je shranjena vrednost vedno v točkah. Knjižnica se je na tem dvakrat spotaknila: pisavna rezerva v WideTextOutBoxEx in stran nadaljevanja odstavkov sta obe podali to shranjeno vrednost v točkah nazaj k SetFont, ki jo je skaliral drugič in prepolovil tekst. Vaša koda ne more brati shranjene velikosti, a isti hrošč se pokaže vsakič, ko vrednost v točkah od kje koli drugje doseže SetFont: TFont.Size iz obrazca VCL, velikost v definiciji poročila, dolžina CSS pt. Najprej jo pretvorite in v faktor vključite lastno Resolution strani ter primer UserDefined, kot to stori predvajanje metafile, ko znova predvaja Canvas strani (glejte kako HotPDF uvaža vektorsko grafiko EMF in WMF za to pot):

// Rislane enote na točko na trenutni strani. Zrcali projekcijo,
// ki jo uporablja HotPDF: 1 na strani, velikostni skozi Width/Height, sicer
// (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 je v točkah; SetFont pričakuje risalne enote
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Knjižnica uveljavi isto pravilo za svoje lastne konstante v točkah. Pisava 12 točk, s katero se začne vsaka nova stran, je zdaj pomnožena z notranjim faktorjem enot-na-točko, zato je 12 točk pri kateri koli Resolution. DrawChart, katerega robovi, velikosti oznak in širine črt so vsi trdo kodirane točke, zdaj teče s skalo, začasno nastavljeno na 1. Kar ostane v risalnih enotah, namerno, so privzete vrednosti javnih parametrov, kot sta velikost modula DrawQRCode in privzeta velikost pisave tabele: del so pogodbe API, zato pri Resolution 144 pomenijo polovico tega, kar pomenijo pri 72. Če velikost poročil jemljete iz predloge, vodnik po izpisu poročil s pisavami in slikami v HotPDF pokrije, od kod te vrednosti običajno pridejo

Kako preverite, da je postavitev neodvisna od Resolution?

Najbolj zanesljiv preizkus je bajtna primerjava: izrišite isto stran pri Resolution 72 in znova pri 144, z vsako koordinato in velikostjo podvojeno, tokova nestisnjene vsebine pa morata biti identična. Obe poganjanji pristaneta na istih vrednostih v točkah po projekciji, zato je vsaka razlika vrednost, ki je preskočila pretvorbo. Tako preizkusna zbirka HotPDF preverja odstavke, tabele, uvoz HTML, izravnavanje XFA, loka, metafile in slike. Ista tehnika dela za vašo lastno kodo poročil s skoraj nič opreme:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // berljivi tokovi vsebine
    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) and RenderPage('r144.pdf', 144, 2)
// morata izdelati bajtno identična tokova vsebine strani
Kako preveriti neodvisnost od Resolution v kodi HotPDF Delphi: izrišite isto postavitev dvakrat, enkrat pri Resolution 72 s skalo 1 in enkrat pri 144 z vsako koordinato in velikostjo pisave podvojeno, nato zahtevajte bajtno identična nestisnjena tokova vsebine — neujemanje kaže na stran, preklopljeno na UserDefined skozi Width, ali nepretvorjeno vrednost v točkah, ki doseže SetFont
Obe poganjanji pristaneta na istih vrednostih v točkah po projekciji, zato je vsaka razlika število, ki je preskočilo svojo pretvorbo — ista oprema, na katero se zanaša preizkusna zbirka HotPDF

Preverite operatorje, ki nosijo števila: Td, Tm, Tf, re, w in polja TJ. Bajti na ravni datoteke se bodo še vedno razlikovali v datumu nastanka in /ID, zato primerjajte tokova, ne celih datotek. Neujemanje skoraj vedno kaže na eno od dveh pasti zgoraj: stran, spremenjeno v velikost skozi Width, ali vrednost v točkah, podano naravnost k SetFont. Če ste novi pri risalnih klicih samih, začnite z vodnikom po TextOut za velikost, stil in zasuk v HotPDF, nato se vrnite in preklopite Resolution, ko vaša postavitev bere UserWidth. Popolne podrobnosti API in preizkusni prenosi so na strani komponente HotPDF Delphi PDF