Tehnički članak

HotPDF Resolution u Delphiju: crtaća jedinica i UserWidth

U HotPDF komponenti, THotPDF.Resolution definiše crtaću jedinicu: svaka X i Y koordinata, svaka margina, veličina data u SetFont, i rezultati TextWidth-a i GetWideTextWidth-a mere se u 1/Resolution inča. THPDFPage.Width i Height ih ne prate i ostaju u poenima, pa granice rasporeda moraju da dolaze iz read-only UserWidth-a i UserHeight-a. Uobičajen razlog da se Resolution pipa je port: engine izveštaja koji već razmišlja u 1/96 ili 1/144 inča lakše se preseli kad PDF strana govori istu jedinicu nego kad svako mesto poziva dobije faktor konverzije. To dobro radi, dokle znate koji su se brojevi preselili u novu jedinicu a koji ostali

Šta THotPDF.Resolution zapravo menja?

THotPDF.Resolution menja samo to kako HotPDF čita brojeve koje uputite; PDF koji napiše je isti. Seter ima dve linije: SetResolution sačuva vrednost i postavi DocScale := Value / 72. Od tada XProjection i YProjection dele svaku koordinatu sa DocScale-om na ulazu u content stream, a SetFont veličinu deli na isti način pre nego što je zapiše. PDF user space podrazumeva 1/72 inča (ISO 32000-1 §8.3.2.3), pa je pri podrazumevanom Resolutionu od 72 projekcija identitet, a na 144 jedna crtaća jedinica je pola poena. Ne upisuje se /UserUnit unos. Taj page atribut, dodat u PDF 1.6, odvojena je stvar koju HotPDF izlaže kao THPDFPage.SetUserUnit. Detalj koji uhvati ljude sa TextOut tutorijala: koordinate stranice idu od gornjeg levog ugla sa Y koji raste nadole, jer YProjection računa vrh MediaBox-a minus skalirani Y, i to važi pri svakom Resolutionu

Kako THotPDF.Resolution definiše crtaću jedinicu u Delphiju: seter čuva DocScale kao Resolution podeljen sa 72, pa XProjection, YProjection i SetFont dele svaku koordinatu i veličinu na ulazu u content stream, pa je Resolution 72 identitetsko mapiranje a Resolution 144 čini jednu crtaću jedinicu pola poena dok stranica i dalje ide od gornjeg levog ugla sa Y nadole
Ništa u izlaznom fajlu se ne pomera — menja se samo značenje brojeva koje uputite, pa isti content stream izgleda isto na 72 i na 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 crtaća jedinica = 1/144 inča
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // jedan inč u crtaćim jedinicama
    Page.SetFont('Arial', [fsBold], 28); // 28/144 inča, font od 14 pt
    Title := 'INVOICE 2026-0417';
    // Desno poravnaj uz ivicu stranice izmerenu u istoj jedinici
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // linija od 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Zašto se Page.Width ne slaže sa mojim koordinatama na Resolutionu 144?

THPDFPage.Width i Height prijavljuju stranicu u poenima ma šta bio Resolution dokumenta, dok su vaše koordinate u 1/Resolution inča, pa na 144 stranica deluje upola uža nego što jeste. A4 stranica čita Width = 595 i Height = 842 na Resolutionu 72 i i dalje čita 595 i 842 na 144, gde je desna ivica zapravo na X = 1190. UserWidth i UserHeight, dodati u v2.766.0, vraćaju Width * DocScale, što je veličina stranice u jedinici kojom crtate. Pre nego što su postojali, biblioteka je mešala dve stvari interno, a simptomi na Resolutionu 144 bili su dramatični: pasusi su se lomili posle svakog znaka, THPDFTable.Render je svaki red gurao na novu stranicu, i HTML uvoznik i XFA poravnavač crtali su svoj sadržaj upola, sa poravnatom formom uguranom u gornji levi ugao. Raspored pasusa, render tabela, HTML uvoz, EMF centriranje, WMF klip stranice i dijagnostika rasporeda sada svi čitaju veličinu u korisničkim jedinicama. I vaš rasporedni kod treba isto: sve što se poredi sa crtaćom koordinatom (desna margina, test preloma stranice, račun centriranja) pripada UserWidth-u i UserHeight-u, nikada Width-u i Height-u

Zamka prva: dodela Width ili Height prebacuje stranicu na poene

Postavljanje Page.Width ili Page.Height tiho prebacuje stranicu na UserDefined, a UserDefined stranica potpuno ignoriše DocScale, pa je sve što na nju nakon toga nacrtate u poenima, ne u 1/Resolution inča. Seter je star i po dizajnu prima poene, zato mu je značenje i ostavljeno. Projekcija za UserDefined stranicu je čist X + MinX, a SetFont veličinu upisuje nepromenjenu. Na Resolutionu 144 rezultat je stranica čiji sadržaj odjednom izađe duplo veći od prethodne. Biblioteka je napravila baš ovu grešku: stranice nastavka pasusa kopirale su veličinu prethodne stranice kroz Width, i svaka stranica prekoračenja prebacivala se na poene. Te stranice sada kopiraju Size, Orientation i Resolution stranice, i na Width i Height padaju tek kad je originalna stranica već bila UserDefined

Dva izlaza, zavisno od toga šta vam treba. Ako posluži standardni list, postavite Page.Size i Page.Orientation i nastavite da crtate u svojoj Resolution jedinici. Ako baš treba sopstvena veličina stranice, prihvatite da je to page u poenima i crtajte u poenima; UserWidth je tu jednak Width-u, pa rasporedni kod koji uvek čita UserWidth radi na obe vrste stranica. Jedinični test ovo kucava: na Resolutionu 144 A4 stranica prijavljuje UserWidth od 1190, ali posle Width := 500 i Height := 400 prijavljuje 500 i 400. Učitane stranice rade isto, jer stranica ponovo izgrađena iz postojećeg PDF-a zna samo svoj MediaBox u poenima i crta u poenima. Stranice koje je ovaj dokument sam napravio čuvaju svoje jedinice kad se odete i vratite kroz CurrentPageNumber, što je slučaj od v2.766.26

Zašto se Page.Width ne slaže sa vašim koordinatama na Resolutionu 144 u HotPDF-u: Width i Height ostaju u poenima dok crtanje koristi 1/144 inča, pa A4 stranica čita 595 ali njena desna ivica leži na UserWidth 1190, i dodela Width prebacuje stranicu na UserDefined, koji ignoriše DocScale, pa se pasusi lome po znaku, tabele po redu a SetFont veličine prepolove
Sve što se poredi sa crtaćom koordinatom pripada UserWidth-u i UserHeight-u — na UserDefined page-u u poenima dve se poklapaju, pa isti rasporedni kod preživi obe

Zamka druga: zašto veličine fontova izlaze prepolovljene?

Veličina fonta koja je počela kao poeni izlazi prepolovljena na Resolutionu 144 jer SetFont svoj argument veličine tretira kao crtaću jedinicu i pretvara ga u poene pre upisa. Interno, SetFont upisuje ASize / DocScale * DPI u tekući font objekat, pa je sačuvana vrednost uvek u poenima. Biblioteka je ovde posrnula dva puta: font zamena u WideTextOutBoxEx-u i stranica nastavka pasusa obe su tu sačuvanu poentnu vrednost vraćale u SetFont, koji bi je skalirao drugi put i prepolovio tekst. Vaš kod ne može da pročita sačuvanu veličinu, ali se isti bug javlja svaki put kad poentna vrednost odaslane negde stigne do SetFont-a: TFont.Size sa VCL forme, veličina u definiciji izveštaja, CSS pt dužina. Prvo je pretvorite, i u faktor uključite i Resolution same stranice i UserDefined slučaj, kao što radi reprodukcija metafajla kad reprodukuje Canvas stranice (za tu putanju vidi kako HotPDF uvozi EMF i WMF vektorsku grafiku):

// Crtaća jedinica po poenu na tekućoj stranici. Ogleda projekciju
// koju HotPDF koristi: 1 na stranici dimenzionisanoj kroz Width/Height, inače
// (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 u poenima; SetFont očekuje crtaća jedinice
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Biblioteka primenjuje isto pravilo na sopstvene poentne konstante. Font od 12 poena s kojim svaka nova stranica počinje sada se množi internim faktorom jedinica-po-poenu, pa je 12 poena pri bilo kom Resolutionu. DrawChart, čije su margine, veličine oznaka i debljine linija sve ukucane poene, sada radi sa skalom privremeno postavljenom na 1. Ono što ostaje u crtaćim jedinicama, namerno, jesu javni podrazumevani parametri poput veličine modula DrawQRCode-a i podrazumevane veličine fonta tabele: deo su API ugovora, pa na Resolutionu 144 znače polovinu onoga što znače na 72. Ako dimenzionišete izveštaje iz šablona, vodič o izlazu izveštaja sa fontovima i slikama u HotPDF-u pokriva odakle te vrednosti obično dolaze

Kako proveriti da li je raspored nezavisan od Resolutiona?

Najpouzdanija provera je poređenje bajtova: renderujte istu stranicu na Resolutionu 72 pa opet na 144 sa svakom koordinatom i veličinom udvostručenom, i nekompresovani content streamovi moraju biti identični. Oba prolaza slijeću na iste poentne vrednosti posle projekcije, pa je svaka razlika vrednost koja je preskočila konverziju. Tako HotPDF test skup proverava pasuse, tabele, HTML uvoz, XFA poravnavanje, lukove, metafajlove i slike. Isti trik radi za vaš vlastiti kod izveštaja uz gotovo nikakav test okvir:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // čitljivi content streamovi
    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) i RenderPage('r144.pdf', 144, 2)
// moraju dati bajt-identične content streamove stranice
Kako proveriti nezavisnost od Resolutiona u HotPDF Delphi kodu: renderujte identičan raspored dva puta, jednom na Resolutionu 72 sa skalom 1 a jednom na 144 sa svakom koordinatom i veličinom fonta udvostručenom, pa zahtevajte bajt-identične nekompresovane content streamove — neslaganje ukazuje na stranicu prebačenu na UserDefined kroz Width ili na nepretvorenu poentnu vrednost koja je stigla do SetFont
Oba prolaza slijeću na iste poentne vrednosti posle projekcije, pa je svaka razlika broj koji je preskočio konverziju — isti test okvir na koji se oslanja HotPDF test skup

Proverite operatore koji nose brojeve: Td, Tm, Tf, re, w i TJ nizove. Bajtovi na nivou fajla i dalje će se razlikovati u datumu kreiranja i /ID-ju, pa poredite streamove, ne cele fajlove. Neslaganje skoro uvek ukazuje na jednu od dve zamke iznad: stranicu promenjenu kroz Width, ili poentnu vrednost prosleđenu pravo u SetFont. Ako ste novi u samim crtaćim pozivima, počnite sa HotPDF TextOut propitkom za veličinu, stil i rotaciju, pa se vratite i prebacite Resolution kad vaš raspored čita UserWidth. Potpune API pojedinosti i probna preuzimanja su na stranici HotPDF Delphi PDF komponente