Teknisk artikel

Render PDF-sider til bitmap i Delphi med HotPDF

HotPDF renderer en indlæst PDF-side til en Delphi TBitmap via et enkelt kald: RenderLoadedPageToBitmap(PageIndex, DPI). Funktionen fortolker sidens indholdsstrøm og returnerer en kalder-ejet 24-bit RGB-bitmap ved den opløsning, du vælger, hvilket er præcis, hvad en miniaturerække, en udskriftsforhåndsvisning eller en PDF-til-billede-eksportpipeline har brug for. Denne artikel gennemgår API'en og derefter den del, der adskiller en brugbar renderer fra et stykke legetøj: at tegne tekst ud fra selve de indlejrede skrifttypeprogrammer frem for lignende systemskrifttyper

Hvorfor er rendering af en PDF-side sværere end at tegne et billede?

En PDF-side er ikke et billede. Det er et program: en strøm af operatorer, der opbygger stier, vælger skrifttyper, angiver farver og placerer glyphs, udført i forhold til grafikmodellen defineret i ISO 32000-1 §8. Intet i filen fortæller, hvordan en pixel ser ud. For at producere en bitmap skal du køre dette program — vedligeholde en aktuel transformationsmatrix, en grafik-tilstands-stak (graphics-state stack) til q/Q, en klipningssti, udfyldnings- og stregfarverum — og rasterisere resultatet. Det er derfor, "vis blot side 3 som et billede" er en indholdsstrøm-fortolker og ikke en filformatkonvertering

HotPDF's renderer, introduceret i v2.253.0, er bygget som seks afkoblede enheder, der afspejler denne model: en affin-matrix-kerne til PDF [a b c d e f]-transformationsalgebra, en grafik-tilstands-stak, en farverums-løser (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), en stiopbygger, der forbinder PDF-stioperatorer til GDI, et skrifttypemetrik-lag, der læser /Widths-arrays for korrekte fremrykninger (advances), og fortolkeren, der fordeler operatorerne og styrer de andre fem. Billed-XObjects går gennem den samme afkodningsstak, som biblioteket bruger til ekstraktion, så ethvert billedfilter, HotPDF kan afkode til ekstraktion — herunder JPXDecode-komprimerede JPEG 2000-billeder — også vises i det renderede output

Rendering af en indlæst side til en TBitmap

RenderLoadedPageToBitmap tager et nul-baseret sideindeks og en DPI-værdi, hvor 72 DPI afbilder én PDF-brugerenhed (user-space unit) til én pixel. Den returnerer nil ved fejl (indeks uden for rækkevidde, manglende ressourcer) frem for at udløse en undtagelse, så en fremviser kan springe en dårlig side over og fortsætte. Kalderen ejer den returnerede bitmap og skal frigive den

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);  // page 1 at 144 DPI
      if Bmp <> nil then
      try
        Image1.Picture.Assign(Bmp);
      finally
        Bmp.Free;  // caller owns the bitmap
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

DPI-argumentet udfører skaleringsarbejdet i alle almindelige scenarier. En miniaturerække renders ved 36 eller 48 DPI og giver små, hurtige bitmapfiler; en skærmforhåndsvisning ved 96 eller 144 DPI matcher typisk skærmtæthed; en eksportsti ved 300 DPI producerer billeder i trykkvalitet. Siderotation fra /Rotate-posten og /MediaBox-nulpunktets vending (PDF placerer nulpunktet nederst til venstre, GDI øverst til venstre) håndteres i side-til-enhed-matrixen, så en US Letter-side ved 72 DPI returneres som præcis 612×792 pixels med den rigtige side opad

Hvorfor viser renderede PDF-miniaturer forkerte glyphs?

Forkerte eller tilnærmede glyphs i renderet PDF-output betyder næsten altid, at renderereren erstatter en skrifttype med en systemskrifttype i stedet for at bruge den skrifttype, der er indlejret i filen. Den første HotPDF-renderer gjorde netop det: Den fjernede delmængdepræfikset (subset prefix) fra /BaseFont (hvilket gjorde ABCDEF+Arial til Arial), anmodede GDI om en systemskrifttype med det navn og tegnede teksten med den. For et dokument, der bruger Arial eller Times New Roman med standardkodning, ser resultatet nogenlunde rigtigt ud. Men det er en tilnærmelse, og det går galt i helt specifikke tilfælde

Indlejrede delmængdeskrifttyper (subset fonts) er det værste tilfælde. En delmængdeskrifttype indeholder måske kun de fyrre glyphs, som et dokument rent faktisk bruger, med tegnkoder tildelt i en rækkefølge, der er privat for den specifikke fil — kode 1 kan være "T", kode 2 "h" og så videre. En systemskrifttype ved intet om denne private tildeling, så teksten enten forsvinder eller vises som helt forkerte tegn. Brugerdefinerede kodninger, symbolskrifttyper, stregkodeskrifttyper og enhver skrifttype, der ikke er installeret på renderingsmaskinen, fejler på samme måde. En renderer, der nøjes med at erstatte med systemskrifttyper, producerer miniaturer, der ligner siden — lige indtil siden bruger de skrifttyper, der i første omgang gjorde indlejring nødvendig

Indlejret glyph-rendering: Tegning ud fra selve skrifttypeprogrammet

HotPDF lukkede dette hul over fem udgivelser (v2.268.0 til v2.272.0) ved at parse de indlejrede skrifttypeprogrammer og afspille deres glyfkonturer som udfyldte GDI-vektorstier. Tekst på en renderet side kommer nu fra de samme konturdata, som en standardfremviser bruger, hvilket betyder, at delmængdeskrifttyper, brugerdefinerede kodninger og ikke-installerede skrifttyper renderes med deres præcise former. Understøttelsen blev opbygget efter skrifttypevariant:

For Type0/CIDFontType2-skrifttyper med et indlejret TrueType-program (FontFile2) parser renderereren tabellerne glyf og loca direkte: kvadratiske konturer konverteres til de kubiske Béziers, som GDI forstår, implicitte on-curve-punkter mellem på hinanden følgende off-curve-punkter rekonstrueres, og sammensatte (composite) glyphs afspilles rekursivt. Både Identity- og eksplicitte CIDToGIDMap-strømlayouts understøttes, og CID-fremrykninger respekterer breddeposterne /W og /DW, så to-byte Identity-H-tekst rykker korrekt frem

CFF-programmer (FontFile3, uanset om det er CIDFontType0C, Type1C eller en OpenType-wrapper) får en fuld Type 2 charstring-fortolker: linjer, kurver, flex-familien, hint-masker samt lokale/globale subrutine-kald med den korrekte subrutine-bias. CID-kodede CFF-programmer afbilder tegnkoder via skrifttypens tegnsæt (charset), hvilket er vigtigt for delmængdeskrifttyper, hvis glyfrækkefølge afviger fra CID-rækkefølgen, og valg af skrifttype-DICT pr. glyf via FDArray/FDSelect respekteres. Simple (ikke-CID) TrueType-skrifttyper løser en-byte koder via den indlejrede skrifttypes egen cmap-tabel med en robust undertabel-kæde — Unicode-format 4 og 12 først, derefter symbolundertabeller med det private F000-område (private-use mirror), og derefter ældre Macintosh-formater — mens simple Type1-skrifttyper løses via CFF-programmets indbyggede kodning

To forfinelser fuldender billedet. For det første løses simple-font /Encoding-ordbøger i henhold til den prioritet, som ISO 32000-1 §9.6.6 foreskriver: /Differences-arrays tilsidesætter basiskodningen, som igen tilsidesætter skrifttypeprogrammets eget kort — den sti, som TeX- og PostScript-afledte værktøjskæder afhænger af, hvor glyfnavne løses via Adobe Glyph List, CFF-tegnsættet eller TrueType-cmap'en. For det andet afspilles Type3-skrifttyper, hvis glyphs i sig selv er små indholdsstrømme, via renderereren med sammensat skrifttypematrix, skriftstørrelse og tekstmatrix; glyph-space-/Widths fortolkes via /FontMatrix som krævet i ISO 32000-1 §9.6.5, og glyph-procedurer, der erklærer en d1-afgrænsningsboks, klippes til denne, så en misdannet stregkodeglyf ikke kan tegne uden for sin celle. Når en kode ikke kan afbildes — f.eks. et beskadiget program eller et ikke-afbildet tegn — falder renderereren tilbage til tegning med systemskrifttype for denne glyf frem for at tabe hele tekstforløbet

Hvordan gør du gentagne renderinger hurtige?

Svaret, som HotPDF leverer, er en sidecache med senest anvendte elementer (most-recently-used): RenderLoadedPageToBitmapCached gemmer op til RenderCacheCapacity renderede sider (standard 8) indekseret efter sideindeks og DPI, og et cache-træf returnerer en frisk kalder-ejet kopi uden at røre indholdsstrømmen — typisk tusindvis af gange hurtigere end at genfortolke siden. Det mønster passer perfekt til fremvisere: en bruger, der skifter frem og tilbage mellem to sider, eller en størrelsesændring, der genanmoder om den samme side ved samme DPI, rammer cachen hver gang

Vær ærlig om hukommelsesforbruget, før du hæver kapaciteten. En US Letter-side ved 300 DPI er 2550×3300 pixels, ca. 25 MB som en 24-bit bitmap, så otte cachede sider ved eksportopløsning beslaglægger omtrent 200 MB. Ved miniature-DPI koster de samme otte poster under en megabyte. Tilpas RenderCacheCapacity til den DPI, du rent faktisk cacher ved, og kald InvalidateRenderedPageCache efter enhver in-place redigering — cachen er kun indekseret efter side og DPI, og den kan ikke se, om det underliggende indhold har ændret sig. Indlæsning af et nyt dokument rydder den automatisk

En anden cache fungerer under sidecachen: Afkodede billed-XObjects opbevares i et hukommelsesbegrænset lager afgrænset af ImageCacheMaxBytes (standard 32 MB) med LRU-udskiftning (least-recently-used). Et logo eller brevhovedbillede, der gentages på hver side, afkodes én gang pr. dokumentindlæsning i stedet for én gang pr. Do-operator, hvilket omtrent halverer renderingstiden for sider med fælles billeder og fremskynder eksport af flersidede TIFF-filer i samme omfang. InvalidateRenderedPageCache rydder også denne cache

Hvad der stadig renderes tilnærmelsesvist

Renderereren er rettet mod den fælles dokument-PDF-delmængde, og det er værd at vide, hvor grænserne går. CalRGB-, Lab- og ICC-baserede farverum tilnærmes frem for at være rigtigt farvestyrede — enhedsfarverum, indekserede paletter og samplede Type 0-farveopslagstabel-funktioner håndteres, men en fil til trykproduktion, der er afhængig af ICC-renderingshensigter (rendering intents), vil ikke være kolorimetrisk nøjagtig. Skygge-mønstre (shading patterns, sh) og blandingstilstande (blend modes) ud over simpel alfa er ligeledes uden for omfanget, og Form XObject-rekursion er dybdebegrænset som en cyklusbeskyttelse. For fakturaer, rapporter, kontrakter og formularer — sider bestående af tekst, stier og billeder — er outputtet trofast; for et designudkast fyldt med gradienter og gennemsigtighedsgrupper bør du behandle bitmappen som en forhåndsvisning, ikke et prøvetryk

Den praktiske konklusion: Hvis dit system genererer dokumenter med HotPDF eller forbruger typiske forretnings-PDF'er, vil RenderLoadedPageToBitmap genskabe dem med de præcise indlejrede glyfformer, korrekte CID-fremrykninger og korrekt sidegeometri. Tilnærmelserne lever i de hjørner af grafikmodellen, som forretningsdokumenter sjældent besøger

RenderLoadedPageToBitmap, dens cachede variant og den indlejrede glyph-renderingspipeline beskrevet her leveres som en del af HotPDF Component til Delphi og C++Builder — et indfødt VCL-bibliotek uden eksterne DLL-afhængigheder, der dækker PDF-oprettelse, redigering, tekstekstraktion og siderendering i én pakke