Műszaki cikk

HotPDF Resolution Delphiben: rajzolási egységek és UserWidth

A HotPDF Componentban a THotPDF.Resolution definiálja a rajzolási egységet: minden X és Y koordináta, minden margó, a SetFont-nak átadott méret, valamint a TextWidth és a GetWideTextWidth eredménye 1/Resolution hüvelykben mérődik. A THPDFPage.Width és a Height nem követi őket, és pontban marad, így az elrendezési határoknak a csak olvasható UserWidth-ből és UserHeight-ból kell jönniük. A Resolution megérintésének szokásos oka egy port: egy már 1/96-os vagy 1/144 hüvelykben gondolkodó riportmotort könnyebb áthozni, amikor a PDF oldal ugyanazt az egységet beszéli, mint amikor minden hívási hely kap egy átváltási faktort. Ez jól működik, amíg tudod, mely számok költöztek az új egységbe, és melyek maradtak a helyükön

Mit változtat meg valójában a THotPDF.Resolution?

A THotPDF.Resolution csak azt változtatja meg, hogyan olvassa a HotPDF az átadott számokat; az írt PDF ugyanaz. A setter két sor: a SetResolution eltárolja az értéket, és beállítja a DocScale := Value / 72 képletet. Ezután az XProjection és az YProjection minden koordinátát a DocScale-lel oszt a content streambe érkezés útján, a SetFont pedig ugyanígy osztja a méretet, mielőtt rögzítené. A PDF user space alapértelmezése 1/72 hüvelyk (ISO 32000-1 §8.3.2.3), így a 72-es alapértelmezett Resolutionnél a vetítés identitás, 144-nél pedig egy rajzolási egység fél pont. /UserUnit bejegyzés nem íródik. Az az oldalattribútum, amit a PDF 1.6 vezetett be, külön dolog, és a HotPDF a THPDFPage.SetUserUnit-ként teszi elérhetővé. Egy részlet, ami a TextOut oktatóanyagokból érkezőket meglepi: az oldal koordinátái a bal felső saroktól futnak, Y-lefelé nőve, mert az YProjection a MediaBox tetejéből vonja ki a skálázott Y-t, és ez minden Resolutionnél így marad

Hogyan definiálja a THotPDF.Resolution a rajzolási egységet Delphiben: a setter a DocScale-t a Resolution 72-tel osztott hányadosaként tárolja, majd az XProjection, az YProjection és a SetFont minden koordinátát és méretet oszt a content streambe menet, így a Resolution 72 identitás leképezés, a Resolution 144 pedig fél pontot tesz egy rajzolási egységgé, miközben az oldal továbbra is a bal felső saroktól fut, Y-lefelé
A kimeneti fájlban semmi nem mozdul — csak az átadott számok jelentése változik, ezért ugyanaz a content stream jelenik meg 72-n és 144-en
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 rajzolási egység = 1/144 hüvelyk
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // egy hüvelyk rajzolási egységekben
    Page.SetFont('Arial', [fsBold], 28); // 28/144 hüvelyk, egy 14 pt font
    Title := 'INVOICE 2026-0417';
    // Jobbra zárd az oldal élhez, ugyanabban az egységben mérve
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // 1 pt vonal
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Miért ellentmond a Page.Width a koordinátáimnak Resolution 144-nél?

A THPDFPage.Width és a Height az oldalt pontban jelenti, bármilyen a dokumentum Resolutionje, a koordinátáid pedig 1/Resolution hüvelykben állnak, így 144-nél az oldal félszélességnek látszik. Egy A4 oldal Width = 595 és Height = 842 értéket ad Resolution 72-n, és ugyanúgy 595-öt és 842-t ad 144-nél is, ahol a jobb él valójában X = 1190-nél van. A UserWidth és a UserHeight, amik a v2.766.0-ban érkeztek, Width * DocScale-t adnak vissza, vagyis az oldalméretet abban az egységben, amivel rajzolsz. Mielőtt léteztek, a library keverte a kettőt belül, és a tünetek Resolution 144-nél drámaiak voltak: a bekezdések minden karakter után tördeltek, a THPDFTable.Render minden sort új oldalra tolt, és a HTML importáló és az XFA kiegyenlítő is félméretben rajzolta a tartalmát, a kiegyenlített űrlap a bal felső sarokba zsúfolva. A bekezdéstördelés, a tábla renderelés, a HTML import, az EMF középre igazítás, a WMF oldal clip és az elrendezésdiagnosztika mostantól mind a user-egység méretet olvassa. A saját elrendezési kódodnak is ugyanezt kell: minden, ami rajzolási koordinátával hasonlít össze (jobb margó, oldaltörés-teszt, középre igazítási számítás), a UserWidth-re és a UserHeight-ra tartozik, sosem a Width-re és a Height-re

Első csapda: a Width vagy Height megadása az oldalt pontra váltja

A Page.Width vagy a Page.Height beállítása csendben UserDefined-ra váltja az oldalt, és egy UserDefined oldal teljesen figyelmen kívül hagyja a DocScale-t, így minden, amit utána rá rajzolsz, pontban áll, nem 1/Resolution hüvelykben. A setter régi, és tervezés szerint pontot vesz át, ezért a jelentését békén hagyták. A UserDefined oldal vetítése puszta X + MinX, a SetFont pedig változatlanul tárolja a méretet. Resolution 144-nél az eredmény egy olyan oldal, aminek a tartalma hirtelen kétszer akkora, mint az előző oldalé. A library pontosan ezt a hibát követte el maga is: a bekezdés-folytatási oldalak korábban az előző oldal méretét Width-en át másolták, és minden túlfolyó oldal pontra váltott. Ezek az oldalak mostantól a Size-t, az Orientation-t és az oldal Resolutionjét másolják, és csak akkor esnek vissza a Width-re és a Height-ra, ha az eredeti oldal már UserDefined volt

Két kiút, aszerint, mire van szükséged. Ha megtesz egy standard ív, állítsd a Page.Size-ot és a Page.Orientation-t, és rajzolj tovább a Resolution egységedben. Ha tényleg egyedi oldalméret kell, fogadd el, hogy az pontoldal, és rajzolj pontban; ott a UserWidth egyenlő a Width-szel, így a mindig UserWidth-et olvasó elrendezési kód mindkét oldalfajtán működik. Az egységteszt ezt rögzíti: Resolution 144-nél egy A4 oldal 1190-es UserWidth-et jelent, de Width := 500 és Height := 400 után 500-at és 400-at ad. A betöltött oldalak ugyanígy viselkednek, mert egy meglévő PDF-ből újjáépített oldal csak a MediaBoxát ismeri pontban, és pontban rajzol. Azok az oldalak, amiket ez a dokumentum létrehozott, megtartják a saját egységeiket, amikor elpakolsz és CurrentPageNumber-en át visszatérsz, ez a v2.766.26 óta így van

Miért ellentmond a Page.Width a koordinátáidnak Resolution 144-nél a HotPDF-ben: a Width és a Height pontban marad, miközben a rajzolás 1/144 hüvelyket használ, így egy A4 oldal 595-öt jelent, de a jobb éle a UserWidth 1190-nél van, és a Width megadása UserDefined-ra váltja az oldalt, ami figyelmen kívül hagyja a DocScale-t, így a bekezdések karakterenként tördelnek, a táblák soronként törnek, a SetFont méretek feleződnek
Minden, ami rajzolási koordinátával hasonlít össze, a UserWidth-re és a UserHeight-re tartozik — egy UserDefined pontoldalon a kettő egybeesik, így ugyanaz az elrendezési kód túléli mindkettőt

Második csapda: miért jönnek ki a font méretek félméretben?

A pontokként induló fontméret Resolution 144-nél félméretben jön ki, mert a SetFont a méret argumentumát rajzolási egységnek veszi, és ponttá alakítja tárolás előtt. Belül a SetFont ASize / DocScale * DPI-t tárol az aktuális font objektumban, tehát a tárolt érték mindig pont. A library kétszer botlott ebbe: a WideTextOutBoxEx font tartaléka és a bekezdés-folytatási oldal is visszaadta a SetFont-nak azt a tárolt pontértéket, ami másodszor skálázta, és felezte a szöveget. A saját kódod nem olvashatja a tárolt méretet, de ugyanez a hiba előbukkan, amikor máshonnan érkező pontérték ér a SetFont-hoz: egy TFont.Size egy VCL formból, egy méret egy riportdefinícióban, egy CSS pt hossz. Alakítsd át előbb, és számíld be a faktorba az oldal saját Resolutionjét és a UserDefined esetet, ahogy a metafile lejátszás teszi, amikor visszajátssza az oldal Canvasát (ezt az útvonalat lásd a HotPDF EMF és WMF vektorgrafika importjáról szólóban):

// Rajzolási egység pontra az aktuális oldalon. Tükrözi a HotPDF vetítését:
// 1 Width/Height úton méretezett oldalon, egyébként
// (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
  // A TFont.Size pontban van; a SetFont rajzolási egységeket vár
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

A library ugyanezt a szabályt a saját pontkonstansaira is alkalmazza. A 12 pontos font, amivel minden új oldal indul, mostantól a belső egység-pont faktorral szorzódik, így bármelyik Resolutionnél 12 pont. A DrawChart, aminek a margói, címkeméretei és vonalvastagságai mind beégetett pontok, mostantól ideiglenesen 1-re állított skálával fut. Ami rajzolási egységben marad, szándékosan, azok a nyilvános paraméter alapértékek, mint a DrawQRCode modulmérete és az alapértelmezett tábla fontmérete: az API szerződés részei, így Resolution 144-nél a felét jelentik annak, amit 72-n. Ha sablonból méretezel riportokat, a fontokkal és képekkel készülő riport kimenet HotPDF-ben útmutató elmondja, honnan szoktak ezek az értékek jönni

Hogyan ellenőrizd, hogy egy elrendezés Resolution-független?

A legmegbízhatóbb ellenőrzés a bájt-összehasonlítás: rendereld ugyanazt az oldalt Resolution 72-n, majd 144-n minden koordináta és méret duplájával, és a tömörítetlen content streameknek azonosaknak kell lenniük. Mindkét futás a vetítés után ugyanazokra a pontértékekre ér, így bármilyen eltérés egy átváltást kihagyó érték. Így ellenőrzi a HotPDF tesztcsomagja a bekezdéseket, a táblákat, a HTML importot, az XFA kiegyenlítést, az íveket, a metafile-okat és a képeket. Ugyanez a technika működik a saját riportkódodra is, szinte harness nélkül:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // olvasható content streamek
    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) és RenderPage('r144.pdf', 144, 2)
// bájtonként azonos oldal content streameket kell gyártson
Hogyan ellenőrizd a Resolution-függetlenséget HotPDF Delphi kódban: rendereld kétszer ugyanazt az elrendezést, egyszer Resolution 72-n 1-es skálával, egyszer 144-n minden koordináta és fontméret duplájával, majd követelj meg bájtonként azonos tömörítetlen content streameket — az eltérés a Width-en át UserDefined-ra váltott oldalra vagy a SetFont-hoz érő, át nem váltott pontértékre mutat
Mindkét futás a vetítés után ugyanazokra a pontértékekre ér, így bármilyen eltérés egy átváltást kihagyó szám — ugyanarra a harnessra támaszkodik a HotPDF tesztcsomag

Ellenőrizd a számokat hordozó operátorokat: Td, Tm, Tf, re, w és a TJ tömbök. A fájlszintű bájtok a létrehozás dátumában és a /ID-ben továbbra is eltérnek majd, ezért a streameket hasonlítsd, nem egész fájlokat. Az eltérés szinte mindig a fenti két csapda egyikére mutat: egy Width-en át átméretezett oldalra, vagy egy egyenesen a SetFont-ba kerülő pontértékre. Ha maguk a rajzolási hívások újak neked, kezdd a HotPDF TextOut bejáróval méret, stílus és forgatás témában, majd gyere vissza, és váld át a Resolutiont, amint az elrendezésed UserWidth-et olvas. A teljes API részletek és a trial letöltések a HotPDF Delphi PDF component oldalon