Techninis straipsnis

HotPDF Resolution Delphi: piešimo vienetai ir UserWidth

HotPDF Component THotPDF.Resolution apibrėžia piešimo vienetą: kiekviena X ir Y koordinatė, kiekviena paraštė, dydis, perduodamas SetFont, ir TextWidth bei GetWideTextWidth rezultatai matuojami 1/Resolution colio. THPDFPage.Width ir Height to neseka ir lieka taškuose, tad maketo ribas reikia imti iš tik skaitymui skirtų UserWidth ir UserHeight. Įprasta Resolution liečimo priežastis yra perkėlimas: ataskaitų varikliui, kuris jau mąsto 1/96 arba 1/144 colio, persikelti lengviau, kai PDF pusė kalba tą patį vienetą, negu kai kiekviena kvietimo vieta gauna konversijos koeficientą. Tai veikia gerai, kol žinote, kurie skaičiai persikėlė į naująjį vienetą, o kurie liko senajame

Ką THotPDF.Resolution iš tikrųjų keičia?

THotPDF.Resolution keičia tik tai, kaip HotPDF skaito jūsų perduodamus skaičius; parašomas PDF lieka toks pats. Nustatytojas yra dvi eilutės: SetResolution įsimena reikšmę ir nustato DocScale := Value / 72. Nuo tos akimirkos XProjection ir YProjection kiekvieną koordinatę pakelyje į turinio srautą padalija iš DocScale, o SetFont taip pat padalija dydį prieš jį užregistruodamas. PDF vartotojo erdvė pagal nutylėjimą yra 1/72 colio (ISO 32000-1 §8.3.2.3), tad numatytajame Resolution 72 projekcija yra tapatybė, o ties 144 vienas piešimo vienetas yra pusė taško. /UserUnit įrašas nerašomas. Tas puslapio atributas, pridėtas PDF 1.6, yra atskiras dalykas, kurį HotPDF atskleidžia kaip THPDFPage.SetUserUnit. Viena detalė, kuri pagauna TextOut pamokų skaitytojus: puslapio koordinatės eina nuo viršutinio kairiojo kampo, Y augant žemyn, nes YProjection skaičiuoja MediaBox viršūnę minus sumastytą Y, ir tai lieka teisybė kiekviename Resolution

Kaip THotPDF.Resolution Delphi apibrėžia piešimo vienetą: nustatytojas įsimena DocScale kaip Resolution, padalytą iš 72, tada XProjection, YProjection ir SetFont kiekvieną koordinatę ir dydį pakelyje į turinio srautą padalija, tad Resolution 72 yra tapatybinis atvaizdavimas, o Resolution 144 daro vieną piešimo vienetą puse taško, kol puslapis vis tiek eina nuo viršutinio kairiojo kampo su Y žemyn
Išvesties faile niekas nejuda – keičiasi tik jūsų perduodamų skaičių reikšmė, todėl tas pats turinio srautas atrodo vienodai ties 72 ir 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 piešimo vienetas = 1/144 colio
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // vienas colis piešimo vienetais
    Page.SetFont('Arial', [fsBold], 28); // 28/144 colio, 14 pt šriftas
    Title := 'INVOICE 2026-0417';
    // Dešinysis lygiavimas pagal puslapio kraštą, išmatuotą tame pačiame vienete
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // 1 pt linija
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Kodėl Page.Width ties Resolution 144 nesutaria su mano koordinatėmis?

THPDFPage.Width ir Height puslapį praneša taškais, koks benagtų dokumento Resolution, o jūsų koordinatės yra 1/Resolution colio, tad ties 144 puslapis atrodo perpus siauresnis, nei yra iš tikrųjų. A4 puslapis skaito Width = 595 ir Height = 842 ties Resolution 72 ir vis tiek skaito 595 ir 842 ties 144, kur dešinysis kraštas iš tikrųjų yra ties X = 1190. UserWidth ir UserHeight, pridėti v2.766.0, grąžina Width * DocScale – tai puslapio dydis tame vienete, kuriuo jūs piešiate. Kol jų nebuvo, biblioteka du vienetus maišydavo viduje, o simptomai ties Resolution 144 buvo dramatiški: pastraipos laužėsi po kiekvieną simbolį, THPDFTable.Render kiekvieną eilutę stumdydavo ant naujo puslapio, ir tiek HTML importuotojas, tiek XFA suvienodintojas savo turinį piešdavo perpus mažesnį, suvienodinta forma susigrūsta į viršutinį kairįjį kampą. Pastraipų išdėstymas, lentelių atvaizdavimas, HTML importas, EMF centravimas, WMF puslapio apkarpymas ir maketo diagnostika dabar visi skaito vartotojo vieneto dydį. Ir jūsų savas maketo kodas turėtų: viskas, kas lyginama su piešimo koordinate (dešinioji paraštė, puslapio lūžio testas, centravimo skaičiavimas), priklauso prie UserWidth ir UserHeight, niekada prie Width ir Height

Spąstai pirmi: priskyrimas Width arba Height perjungia puslapį į taškus

Page.Width arba Page.Height nustatymas tyliai perjungia puslapį į UserDefined, o UserDefined puslapis DocScale ignoruoja visiškai, tad viskas, ką vėliau ant jo dedate, yra taškais, o ne 1/Resolution colio. Nustatytojas senas ir pagal projektą priima taškus – todėl jo reikšmė ir palikta ramybėje. UserDefined puslapio projekcija yra plikas X + MinX, o SetFont dydį saugo nepakeistą. Ties Resolution 144 rezultatas yra puslapis, kurio turinys staiga išeina dukart didesnis nei ankstesniojo. Biblioteka pati padarė lygiai šią klaidą: pastraipų tęsinio puslapiai anksčiau dydį kopijavo per Width, ir kiekvienas perpildymo puslapis persijungdavo į taškus. Tie puslapiai dabar kopijuoja Size, Orientation ir puslapio Resolution, o į Width ir Height grįžta tik tada, kai originalus puslapis jau buvo UserDefined

Du išėjimai, priklausomai nuo to, ko reikia. Jeigu tiks standartinis lapas, nustatykite Page.Size ir Page.Orientation ir pieškite toliau savo Resolution vienetu. Jeigu tikrai reikia savo puslapio dydžio, priimkite, kad tai taškinis puslapis, ir pieškite taškais; UserWidth ten lygus Width, tad maketo kodas, visada skaitantis UserWidth, veikia abiejose puslapių rūšyse. Vieneto testas tai užkalė: ties Resolution 144 A4 puslapis praneša UserWidth 1190, bet po Width := 500 ir Height := 400 praneša 500 ir 400. Pakrauti puslapiai elgiasi taip pat, nes puslapis, atstatytas iš egzistuojančio PDF, žino tik savo MediaBox taškais ir piešia taškais. Šio dokumento sukurti puslapiai savo vienetus išlaiko, kai nueinate kitur ir grįžtate per CurrentPageNumber – taip buvo nuo v2.766.26

Kodėl HotPDF Page.Width nesutaria su jūsų koordinatėmis ties Resolution 144: Width ir Height lieka taškuose, kol piešimas naudoja 1/144 colio, tad A4 puslapis skaito 595, bet jo dešinysis kraštas guli ties UserWidth 1190, o priskyrimas Width perjungia puslapį į UserDefined, kuris ignoruoja DocScale, tad pastraipos laužosi po simbolį, lentelės lūžta po eilutę, o SetFont dydžiai perpus sumažėja
Viskas, kas lyginama su piešimo koordinate, priklauso prie UserWidth ir UserHeight – UserDefined taškiniame puslapyje abu sutampa, tad tas pats maketo kodas išgyvena abu atvejus

Spąstai antri: kodėl šriftų dydžiai išeina perpus mažesni?

Šrifto dydis, prasidėjęs kaip taškai, ties Resolution 144 išeina perpus mažesnis, nes SetFont savo dydžio argumentą traktuoja kaip piešimo vienetus ir prieš saugodamas paverčia jį taškais. Viduje SetFont į dabartinį šrifto objektą saugo ASize / DocScale * DPI, tad saugoma reikšmė visuomet taškai. Biblioteka už šito apsimušdavo du kartus: šriftų atsarginis kelias WideTextOutBoxEx ir pastraipų tęsinio puslapis abu tą saugomą taškinę reikšmę grąžindavo į SetFont, kuris ją sukeisdavo antrą kartą ir perpus sumažindavo tekstą. Jūsų kodas saugomo dydžio neperskaito, bet tas pats defektas iškyla kaskart, kai taškinė reikšmė iš kažkur kito pasiekia SetFont: TFont.Size iš VCL formos, dydis ataskaitos apibrėžime, CSS pt ilgis. Paverskite jį pirmiausiai, ir į koeficientą įtraukite pačios puslapio Resolution bei UserDefined atvejį, kaip daro metafailų atkūrimas, kai pakartoja puslapio Canvas (žr. kaip HotPDF importuoja EMF ir WMF vektorinę grafiką – ten tas kelias):

// Piešimo vienetai taškui dabartiniame puslapyje. Atkartoja projekciją,
// kurią naudoja HotPDF: 1 puslapiui, dydžiui nustatytam per Width/Height, kitu atveju
// (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 yra taškais; SetFont tikisi piešimo vienetų
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Biblioteka tą pačią taisyklę taiko ir savo taškinėms konstantoms. 12 taškų šriftas, nuo kurio prasideda kiekvienas naujas puslapis, dabar dauginamas iš vidinio vienetų-taškui koeficiento, tad jis yra 12 taškų bet kuriame Resolution. DrawChart, kurio paraštės, etikečių dydžiai ir linijų storiai visi įkoduoti taškais, dabar veikia su skale, laikinai nustatyta į 1. Kas lieka piešimo vienetais – sąmoningai – tai viešieji parametrų numatymai, tokie kaip DrawQRCode modulio dydis ir numatytasis lentelės šrifto dydis: jie yra API sutarties dalis, tad ties Resolution 144 jie reiškia pusę to, ką reiškia ties 72. Jei ataskaitas dydžiuojate iš šablono, vadove apie ataskaitų išvestį su šriftais ir vaizdais HotPDF aprašyta, iš kur tos reikšmės paprastai atkeliauja

Kaip patikrinti, ar maketas nepriklauso nuo Resolution?

Patikimiausia patikra yra baitų lyginimas: atvaizduokite tą patį puslapį ties Resolution 72 ir dar kartą ties 144, padvigubinę kiekvieną koordinatę ir dydį – nesuspausti turinio srautai privalo būti identiški. Abu paleidimai po projekcijos nusileidžia ant tų pačių taškinių reikšmių, tad bet koks skirtumas yra reikšmė, praleidusi konversiją. Taip HotPDF testų rinkinys tikrina pastraipas, lenteles, HTML importą, XFA suvienodinimą, lankus, metafailus ir vaizdus. Ta pati technika tinka jūsų pačių ataskaitų kodui beveik be jokios įrangos:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // skaitomi turinio srautai
    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)
// privalo pagaminti baitas į baitą identiškus puslapio turinio srautus
Kaip patikrinti Resolution nepriklausomumą HotPDF Delphi kode: identišką maketą atvaizduokite du kartus – kartą ties Resolution 72 su skale 1 ir kartą ties 144, padvigubinę kiekvieną koordinatę ir šrifto dydį, – tada reikalaukite baitas į baitą identiškų nesuspaustų turinio srautų; nesutapimas rodo puslapį, per Width perjungtą į UserDefined, arba nepaverstą taškinę reikšmę, pasiekusią SetFont
Abu paleidimai po projekcijos nusileidžia ant tų pačių taškinių reikšmių, tad bet koks skirtumas yra skaičius, praleidęs savo konversiją – ta pati įranga, kuria remiasi HotPDF testų rinkinys

Patikrinkite operatorius, nešančius skaičius: Td, Tm, Tf, re, w ir TJ masyvus. Failo lygio baitai vis tiek skirsis sukūrimo data ir /ID, tad lyginkite srautus, o ne visus failus. Nesutapimas beveik visada rodo vieną iš dviejų aukščiau minėtų spąstų: puslapį, pakeistą dydžio per Width, arba taškinę reikšmę, perduotą tiesiai į SetFont. Jei patiems piešimo iškvietimams jūs naujokas, pradėkite nuo HotPDF TextOut aprašymo apie dydį, stilių ir pasukimą, o tada grįžkite ir persijunkite Resolution, kai jūsų maketas skaito UserWidth. Pilna API detalių nuoroda ir bandomieji atsisiuntimai yra HotPDF Delphi PDF komponento puslapyje