Tekninen artikkeli

HotPDF Resolution Delphissä: piirtoyksiköt ja UserWidth

HotPDF Componentissa THotPDF.Resolution määrittelee piirtoyksikön: jokainen X- ja Y-koordinaatti, jokainen marginaali, SetFontille annettu koko sekä TextWidthin ja GetWideTextWidthin tulokset mitataan 1/Resolution tuumassa. THPDFPage.Width ja Height eivät noudata sitä ja pysyvät pisteinä, joten asettelun rajat on saatava vain luku -ominaisuuksista UserWidth ja UserHeight. Tavallinen syy koskettaa Resolutionia on porttaus: raporttimoottori, joka jo ajattelee 1/96 tai 1/144 tuumassa, on helpompi siirtää, kun PDF-puoli puhuu samaa yksikköä, kuin silloin, kun jokainen kutsukohta saa muunnoskertoimen. Se toimii hyvin, niin kauan kuin tiedät, mitkä luvut siirtyivät uuteen yksikköön ja mitkä jäivät taakse

Mitä THotPDF.Resolution oikeasti muuttaa?

THotPDF.Resolution muuttaa vain sen, miten HotPDF lukee antamasi luvut; kirjoitettava PDF on sama. Setterinä on kaksi riviä: SetResolution tallettaa arvon ja asettaa DocScale := Value / 72. Siitä lähtien XProjection ja YProjection jakavat jokaisen koordinaatin arvolla DocScale matkalla sisältöstreamiin, ja SetFont jakaa koon samalla tavalla ennen sen kirjaamista. PDF:n käyttäjätila olettaa 1/72 tuuman (ISO 32000-1 §8.3.2.3), joten oletus-Resolutionilla 72 projektointi on identiteetti ja arvolla 144 yksi piirtoyksikkö on puoli pistettä. /UserUnit-merkintää ei kirjoiteta. Kyseinen sivuattribuutti, lisätty PDF 1.6:ssa, on eri asia, jonka HotPDF näyttää metodina THPDFPage.SetUserUnit. Yksi yksityiskohta saa kiinni TextOut-oppaista tulevat: sivukoordinaatit kulkevat vasemmasta yläkulmasta, Y kasvaen alaspäin, koska YProjection laskee MediaBoxin yläosan miinus skaalatun Y:n, ja niin on joka Resolutionilla

Miten THotPDF.Resolution määrittelee piirtoyksikön Delphissä: setteri tallettaa DocScaleen Resolutionin jaettuna 72:lla, sitten XProjection, YProjection ja SetFont jakavat jokaisen koordinaatin ja koon matkalla sisältöstreamiin, joten Resolution 72 on identiteettikuvaus ja Resolution 144 tekee yhdestä piirtoyksiköstä puolet pisteestä, vaikka sivu yhä kulkee vasemmasta yläkulmasta Y:n kasvaessa alaspäin
Mikään tulostetiedostossa ei liiku — vain antamiesi lukujen merkitys muuttuu, minkä vuoksi sama sisältöstreami ilmestyy niin arvolla 72 kuin 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 piirtoyksikkö = 1/144 tuumaa
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // yksi tuuma piirtoyksiköinä
    Page.SetFont('Arial', [fsBold], 28); // 28/144 tuumaa, 14 pt fontti
    Title := 'INVOICE 2026-0417';
    // Tasaa oikealle sivun reunaan samassa yksikössä mitattuna
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // 1 pt viiva
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Miksi Page.Width eriää koordinaattejani Resolutionilla 144?

THPDFPage.Width ja Height raportoivat sivun pisteinä riippumatta asiakirjan Resolutionista, kun taas koordinaattisi ovat 1/Resolution tuumassa, joten arvolla 144 sivu näyttää puolet niin leveältä kuin onkaan. A4-sivu lukee arvot Width = 595 ja Height = 842 Resolutionilla 72 ja yhä 595 ja 842 arvolla 144, missä oikea reuna on oikeasti kohdassa X = 1190. UserWidth ja UserHeight, lisättynä versiossa v2.766.0, palauttavat arvon Width * DocScale, eli sivukoon siinä yksikössä, jolla piirrät. Ennen niitä kirjasto sekoitti nämä kaksi keskenään sisällään, ja oireet Resolutionilla 144 olivat dramaattiset: kappaleet katkesivat rivin jokaisen merkin jälkeen, THPDFTable.Render työnsi jokaisen rivin uudelle sivulle, ja sekä HTML-tuoja että XFA-tasottaja piirsivät sisältönsä puolikokoisena, tasotettu lomake ahtautuneena vasempaan yläkulmaan. Kappaleasettelu, taulukoiden renderöinti, HTML-tuonti, EMF-keskitys, WMF-sivun leikkaus ja asetteludiagnostiikka lukevat nyt kaikki käyttäjäyksikkökoon. Sinun oman asettelukoodisi kannattaa lukea sekin: kaikki, mikä vertaa piirtoyksikkökoordinaattiin (oikea marginaali, sivunvaihtotesti, keskityslaskelma), kuuluu UserWidthille ja UserHeightille, ei koskaan Widthille ja Heightille

Ansa yksi: Widthin tai Heightin asettaminen vaihtaa sivun pisteisiin

Page.Widthin tai Page.Heightin asettaminen vaihtaa hiljaa sivun UserDefinediksi, ja UserDefined-sivu ohittaa DocScalen kokonaan, joten kaikki, minkä sille sen jälkeen piirrät, on pisteitä, eivät 1/Resolution tuumaa. Setteri on vanha ja ottaa pisteet suunnitellusti, minkä vuoksi sen merkitys jätettiin rauhaan. Projektointi UserDefined-sivulle on pelkkä X + MinX, ja SetFont tallettaa koon muuttumattomana. Resolutionilla 144 tulos on sivu, jonka sisältö yhtäkkiä tulee ulos kaksinkertaisena edelliseen sivuun verrattuna. Kirjasto teki täsmälleen tämän virheen itsekin: kappaleiden jatkosivut kopioivat aiemmin edellisen sivun koon kentän Width kautta, ja jokainen ylivuotosivu vaihtui pisteisiin. Kyseiset sivut kopioivat nyt arvot Size, Orientation ja sivun Resolutionin, ja palaavat arvoihin Width ja Height vain, jos alkuperäinen sivu oli jo UserDefined

Kaksi tieä ulos, tarpeen mukaan. Jos vakiopaperi kelpaa, aseta arvot Page.Size ja Page.Orientation ja jatka piirtämistä Resolution-yksikössäsi. Jos tarvitset oikeasti oman sivukoon, hyväksy, että se on pistesivu, ja piirrä pisteissä; UserWidth on silloin yhtä kuin Width, joten asettelukoodi, joka aina lukee UserWidthin, toimii molemmilla sivutyypeillä. Yksikkötesti lukitsee asian: Resolutionilla 144 A4-sivu raportoi UserWidthin arvon 1190, mutta arvojen Width := 500 ja Height := 400 jälkeen se raportoi 500 ja 400. Ladatut sivut käyttäytyvät samoin, koska olemassa olevasta PDF:stä rakennettu sivu tuntee vain MediaBoxinsa pisteinä ja piirtää pisteissä. Sivut, jotka tämä asiakirja loi, pitävät omat yksikkönsä, kun siirryt pois ja palaat CurrentPageNumberin kautta, kuten on ollut asia versiosta v2.766.26 alkaen

Miksi Page.Width eriää koordinaattejasi Resolutionilla 144 HotPDF:ssä: Width ja Height pysyvät pisteinä, kun taas piirtäminen käyttää 1/144 tuumaa, joten A4-sivu lukee 595, mutta sen oikea reuna on kohdassa UserWidth 1190, ja Widthin asettaminen vaihtaa sivun UserDefinediksi, joka ohittaa DocScale'n, joten kappaleet katkeavat merkittäin, taulukot rivittäin ja SetFont-koot puolittuvat
Kaikki, mikä vertaa piirtoyksikkökoordinaattiin, kuuluu UserWidthille ja UserHeightille — UserDefined-pistesivulla kaksi on yhtä suuri, joten sama asettelukoodi selviää molemmista

Ansa kaksi: miksi fonttikoot tulevat ulos puolikokoisina?

Pisteinä alkanut fonttikoko tulee ulos puolikokoisena Resolutionilla 144, koska SetFont käsittelee kokoparametriaan piirtoyksiköinä ja muuntaa sen pisteiksi ennen tallettamista. Sisäisesti SetFont tallettaa arvon ASize / DocScale * DPI nykyiseen fonttiobjektiin, joten talletettu arvo on aina pisteitä. Kirjasto komistui tähän kahdesti: fonttivaramaisuus funktiossa WideTextOutBoxEx ja kappaleiden jatkosivu antoivat molemmat kyseisen talletetun pistearvon takaisin funktiolle SetFont, joka skaalasi sen toisen kerran ja puolitti tekstin. Oma koodisi ei voi lukea talletettua kokoa, mutta sama vika ilmestyy aina, kun pistearvo jostain muualta päätyy funktiolle SetFont: TFont.Size VCL-lomakkeelta, koko raporttimäärittelyssä, CSS:n pt-pituus. Muunna se ensin, ja ota kerroimeen mukaan sivun oma Resolution ja UserDefined-tapaus, niin kuin metafilen toisto tekee toistaessaan sivun Canvasin (kyseinen polku on kerrottu kirjoituksessa miten HotPDF tuo EMF- ja WMF-vektorigrafiikkaa):

// Piirtoyksiköt pistettä kohti nykyisellä sivulla. Peilaa projektion,
// jota HotPDF käyttää: 1 sivulla, jonka koko on asetettu Width/Heightilla, muuten
// (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 on pisteissä; SetFont odottaa piirtoyksiköitä
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Kirjasto soveltaa samaa sääntöä omiin pistevakioihinsa. 12 pisteen fontti, jolla jokainen uusi sivu alkaa, kerrotaan nyt sisäisellä yksikköä-pistettä-kohti-kertoimella, joten se on 12 pistettä millä tahansa Resolutionilla. DrawChart, jonka marginaalit, leimakoot ja viivaleveydet ovat kaikki kovakoodattuja pisteitä, ajaa nyt skaalan ollessa väliaikaisesti 1. Mikä pysyy piirtoyksiköissä tahallaan, ovat julkisten parametrien oletukset kuten DrawQRCodein moduulikoko ja oletustaulukonfonttikoko: ne ovat osa API-sopimusta, joten Resolutionilla 144 ne merkitsevät puolet siitä, mitä ne merkitsevät arvolla 72. Jos kokoat raportteja mallipohjasta, opas raportin ulostulo fonteilla ja kuvilla HotPDF:ssä kertoo, mistä kyseiset arvot yleensä tulevat

Miten varmistat, että asettelu on riippumaton Resolutionista?

Luotettavin tarkistus on tavuvertailu: renderöi sama sivu Resolutionilla 72 ja uudelleen arvolla 144, jolloin jokainen koordinaatti ja koko on kaksinkertaistettu, ja pakaamattomien sisältöstreamien on oltava identtiset. Molemmat ajot laskeutuvat samoihin pistearvoihin projektoinnin jälkeen, joten jokainen ero on arvo, joka ohitti muunnoksen. Näin HotPDF:n testisarja tarkistaa kappaleet, taulukot, HTML-tuonnin, XFA-tasottamisen, kaaret, metafilet ja kuvat. Sama tekniikka toimii omalle raporttikoodillesi lähes ilman testinosturia:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // luettavat sisältöstreamit
    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) ja RenderPage('r144.pdf', 144, 2)
// on tuotettava tavuiltaan identtiset sivujen sisältöstreamit
Miten varmistetaan Resolution-riippumattomuus HotPDF Delphi -koodissa: renderöi identtinen asettelu kahdesti, kerran Resolutionilla 72 skaalalla 1 ja kerran arvolla 144, jolloin jokainen koordinaatti ja fonttikoko on kaksinkertaistettu, ja vaadi sitten tavuiltaan identtiset pakaamattomat sisältöstreamit — ero osoittaa sivuun, joka vaihdettiin UserDefinediksi Widthin kautta, tai muuntamattomaan pistearvoon, joka päätyi funktiolle SetFont
Molemmat ajot laskeutuvat samoihin pistearvoihin projektoinnin jälkeen, joten jokainen ero on luku, joka ohitti muunnoksensa — sama testinosturi, johon HotPDF:n testisarja nojaa

Tarkista operaattorit, jotka kantavat lukuja: Td, Tm, Tf, re, w ja TJ-taulukot. Tiedostotason tavut eroavat yhä luontipäivästä ja /IDstä, joten vertaa streameja, ei kokonaisia tiedostoja. Ero osoittaa lähes aina jompaan kumpaan yllä olevista ansaista: sivuun, joka oli koollestettu uudelleen kentän Width kautta, tai pistearvoon, joka annettiin suoraan funktiolle SetFont. Jos piirtokutsut itsessään ovat uusia, aloita oppaasta HotPDF TextOut -läpikäynti koolle, tyylille ja kierteelle, ja palaa sitten vaihtamaan Resolutionia, kun asettelusi lukee UserWidthin. Täydet API-yksityiskohdat ja kokeiluversiot ovat HotPDF Delphi PDF component -sivulla