Odborný článok

Vykresľovanie PDF stránok do bitmapy v Delphi pomocou HotPDF

HotPDF vykresľuje načítanú stránku PDF do delphijského TBitmap pomocou jediného volania: RenderLoadedPageToBitmap(PageIndex, DPI). Táto funkcia interpretuje tok obsahu stránky a vracia 24-bitovú RGB bitmapu vo vlastníctve volajúceho v rozlíšení, ktoré si zvolíte, čo je presne to, čo potrebuje pás náhľadov, náhľad tlače alebo systém exportu PDF do obrázkov. Tento článok vás prevedie týmto API, a potom časťou, ktorá odlišuje použiteľný vykresľovač od hračky: vykresľovaním textu priamo zo samotných vložených programov písiem a nie z podobných systémových písiem

Prečo je vykreslenie PDF stránky náročnejšia ako len vykreslenie obrázka?

PDF stránka nie je obrázok. Je to program: tok operátorov, ktoré vytvárajú cesty, vyberajú písma, nastavujú farby a umiestňujú glyfy, vykonávané voči grafickému modelu definovanému v norme ISO 32000-1 §8. Nič v súbore nehovorí, ako vyzerá konkrétny pixel. Ak chcete vytvoriť bitmapu, musíte spustiť tento program — udržiavať aktuálnu transformačnú maticu, zásobník stavov grafiky pre q/Q, orezovú cestu, farebné priestory pre výplň a ťah — a rasterizovať výsledok. Preto požiadavka „len zobraziť stranu 3 ako obrázok“ vyžaduje interpretátor toku obsahu, nie len prevod formátu súboru

Vykresľovač v HotPDF, predstavený vo verzii v2.253.0, je postavený ako šesť oddelených jednotiek odzrkadľujúcich tento model: jadro s afinnou maticou pre algebru transformácie PDF [a b c d e f], zásobník stavov grafiky, vyhodnocovač farebných priestorov (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), tvorca ciest, ktorý prepája operátory ciest PDF s rozhraním GDI, vrstva metrík písiem, ktorá číta polia /Widths pre správne posuny, a interpretátor, ktorý odosiela operátory a riadi ostatných päť jednotiek. Objekty obrázkov (image XObjects) prechádzajú rovnakým dekódovacím zásobníkom, aký knižnica používa na extrakciu, takže každý filter obrázkov, ktorý HotPDF dokáže dekódovať na extrakciu — vrátane JPEG 2000 obrázkov s kompresiou JPXDecode — sa objaví aj vo vykreslenom výstupe

Vykreslenie načítanej stránky do TBitmap

Metóda RenderLoadedPageToBitmap prijíma index stránky začínajúci od nuly a hodnotu DPI, pričom 72 DPI mapuje jednu jednotku používateľského priestoru PDF na jeden pixel. V prípade zlyhania (index mimo rozsahu, chýbajúce prostriedky) vracia nil namiesto vyvolania výnimky, takže prehliadač môže preskočiť chybnú stránku a pokračovať. Volajúci vlastní vrátenú bitmapu a musí ju uvoľniť

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;

Argument DPI vykonáva zmenu mierky pre každý bežný scenár. Pás náhľadov sa vykresľuje pri 36 alebo 48 DPI a získava malé, rýchle bitmapy; náhľad na obrazovke pri 96 alebo 144 DPI zodpovedá typickej hustote displeja; exportná cesta pri 300 DPI vytvára obrázky v tlačovej kvalite. Rotácia stránky zo záznamu /Rotate a preklopenie počiatku /MediaBox (PDF umiestňuje počiatok vľavo dole, GDI vľavo hore) sa spracovávajú vo vnútri matice stránka-zariadenie, takže strana formátu US Letter pri 72 DPI sa vráti presne ako 612×792 pixelov správne otočená

Prečo vykreslené náhľady PDF zobrazujú nesprávne glyfy?

Nesprávne alebo približné glyfy vo vykreslenom výstupe PDF takmer vždy znamenajú, že vykresľovač nahrádza písmo systémovým písmom namiesto toho, aby použil písmo vložené v súbore. Prvý vykresľovač HotPDF robil presne to: odstránil prefix podmnožiny z /BaseFont (čím zmenil ABCDEF+Arial na Arial), požiadal rozhranie GDI o systémové písmo s týmto názvom a vykreslil text pomocou neho. Pre dokument, ktorý používa Arial alebo Times New Roman so štandardným kódovaním, vyzerá výsledok blízko originálu. Je to však len aproximácia a zlyháva presne definovanými spôsobmi

Najhorším prípadom sú písma s vloženou podmnožinou. Písmo s podmnožinou môže niesť iba štyridsať glyfov, ktoré dokument skutočne používa, pričom kódy znakov sú priradené v poradí vyhradenom pre daný súbor — kód 1 môže byť „T“, kód 2 „h“ a tak ďalej. Systémové písmo nevie nič o tomto privátnom priradení, takže text buď zmizne, alebo sa zobrazí ako úplne iné znaky. Rovnakým spôsobom zlyhávajú vlastné kódovania, symbolické písma, písma čiarových kódov a akékoľvek rezy, ktoré nie sú nainštalované na počítači, kde prebieha vykresľovanie. Vykresľovač, ktorý skončí pri nahradení systémovým písmom, vytvára náhľady, ktoré sú rozpoznateľné ako daná strana — až kým strana nepoužije písma, kvôli ktorým bolo vloženie v prvom rade nevyhnutné

Vykresľovanie vložených glyfov: kreslenie zo samotného programu písma

HotPDF odstránil túto medzeru v piatich vydaniach (v2.268.0 až v2.272.0) tým, že analyzuje vložené programy písiem a vykresľuje obrysy ich glyfov ako vyplnené vektorové cesty GDI. Text vo vykreslenej stránke teraz pochádza z rovnakých obrysových údajov, aké používa vyhovujúci prehliadač, čo znamená, že písma s podmnožinami, vlastné kódovania a nenainštalované rezy sa vykreslia so svojimi presnými tvarmi. Podpora bola vybudovaná podľa formátov písiem:

Pre písma Type0/CIDFontType2 s vloženým programom TrueType (FontFile2) vykresľovač priamo analyzuje tabuľky glyf a loca: kvadratické kontúry sa konvertujú na kubické Bézierove krivky, ktorým rozumie GDI, rekonštruujú sa implicitné body na krivke medzi po sebe idúcimi bodmi mimo krivky a rekurzívne sa spracovávajú zložené glyfy. Podporované sú rozloženia Identity aj explicitný prúd CIDToGIDMap, pričom posuny CID rešpektujú položky šírky /W a /DW, so two-byte Identity-H text steps correctly

Programy CFF (FontFile3, či už CIDFontType0C, Type1C alebo OpenType obal) získavajú úplný interpretátor znakových reťazcov (charstring) typu Type 2: čiary, krivky, rodinu flex, masky nápovedy (hint masks) a lokálne/globálne volania podprogramov so správnym posunom podprogramov. Programy CFF s kľúčom CID mapujú kódy znakov cez znakovú sadu písma, čo je dôležité pre písma s podmnožinou, ktorých poradie glyfov sa líši od poradia CID, pričom sa rešpektuje výber slovníka písma (font-DICT) pre jednotlivé glyfy cez FDArray/FDSelect. Jednoduché (ne-CID) TrueType písma vyhodnocujú jednobajtové kódy cez vlastnú tabuľku cmap vloženého písma s robustným reťazcom podtabuliek — najprv formáty Unicode 4 a 12, potom tabuľky symbolov s privátnym zrkadlením F000, potom staršie formáty Macintosh — zatiaľ čo jednoduché písma Type1 sa vyhodnocujú prostredníctvom vstavaného kódovania programu CFF

Obraz dopĺňajú dve vylepšenia. Po prvé, slovníky /Encoding jednoduchých písiem sa vyhodnocujú podľa priority, ktorú predpisuje norma ISO 32000-1 §9.6.6: polia /Differences prepisujú základné kódovanie, ktoré prepisuje vlastnú mapu programu písma — čo je cesta, od ktorej závisia nástroje odvodené od TeX a PostScriptu, pričom názvy glyfov sa vyhodnocujú cez zoznam Adobe Glyph List, znakovú sadu CFF alebo TrueType cmap. Po druhé, písma typu Type3, ktorých glyfy sú samy o sebe malými tokmi obsahu, sa prehrávajú cez vykresľovač so skomponovanou maticou písma, veľkosťou písma a maticou textu; šírky v glyfovom priestore (glyph-space /Widths) sa interpretujú cez /FontMatrix, ako vyžaduje ISO 32000-1 §9.6.5, a procedúry glyfov deklarujúce ohraničujúci rámček d1 (bounding box) sú ním orezané, takže nesprávne vytvorený glyf čiarového kódu nemôže kresliť mimo svojej bunky. Ak kód nemožno priradiť — poškodený program, nemapovaný znak — vykresľovač pre tento glyf prejde na kreslenie systémovým písmom, namiesto toho, aby vynechal celý beh textu

Ako zabezpečiť rýchle opakované vykresľovanie?

Odpoveďou, ktorú HotPDF prináša, je vyrovnávacia pamäť naposledy použitých stránok (MRU page cache): RenderLoadedPageToBitmapCached uchováva až RenderCacheCapacity vykreslených stránok (predvolene 8) kľúčovaných indexom stránky a DPI, pričom zásah vyrovnávacej pamäte vracia čerstvú kópiu vo vlastníctve volajúceho bez toho, aby sa dotkol toku obsahu — zvyčajne tisíckrát rýchlejšie ako opätovná interpretácia stránky. Tento vzor presne vyhovuje prehliadačom: používateľ prepínajúci medzi dvoma stránkami alebo udalosť zmeny veľkosti, ktorá znova požaduje rovnakú stránku s rovnakým DPI, zakaždým trafí vyrovnávaciu pamäť

// Thumbnail strip: first pass renders, scrolling back hits the cache
for I := 0 to ThumbCount - 1 do
begin
  Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
  if Bmp <> nil then
  try
    ThumbList.AddThumbnail(I, Bmp);
  finally
    Bmp.Free;
  end;
end;

// After editing a loaded page in place:
Pdf.InvalidateRenderedPageCache;  // next render reflects the change

Pred zvýšením kapacity buďte úprimní, pokiaľ ide o účet za pamäť. Strana formátu US Letter pri 300 DPI má 2550×3300 pixelov, čo je približne 25 MB ako 24-bitová bitmapa, so eight cached pages at export resolution hold roughly 200 MB. Pri DPI pre náhľady stojí rovnakých osem položiek oveľa menej ako megabajt. Nastavte RenderCacheCapacity pre DPI, pri ktorom reálne ukladáte do vyrovnávacej pamäte, a po akejkoľvek úprave na mieste zavolajte InvalidateRenderedPageCache — pamäť je kľúčovaná iba stránkou a DPI, a nedokáže rozpoznať, že sa zmenil podkladový obsah. Načítanie nového dokumentu ju vymaže automaticky

Pod vyrovnávacou pamäťou stránok funguje druhá vyrovnávacia pamäť: dekódované objekty obrázkov (image XObjects) se uchovávajú v úložisku s obmedzením veľkosti v bajtoch ohraničenom ImageCacheMaxBytes (predvolene 32 MB) s vyraďovaním najmenej používaných (LRU). Logo alebo hlavičkový obrázok opakujúci sa na každej stránke sa dekóduje raz na jedno načítanie dokumentu a nie pri každom operátore Do, čo zhruba na polovicu skracuje čas vykresľovania stránok so zdieľanými obrázkami a rovnakou mierou zrýchľuje export viacstranových súborov TIFF. InvalidateRenderedPageCache vymaže aj túto vyrovnávaciu pamäť

Čo sa stále vykresľuje približne

Vykresľovač sa zameriava na bežnú podmnožinu dokumentov PDF a je užitočné vedieť, kde ležia jeho limity. Farebné priestory CalRGB, Lab a ICC sa skôr aproximujú než spravujú z hľadiska farieb — spracovávajú sa farebné priestory zariadenia, indexované palety a vzorkované vyhľadávania farieb funkcií typu 0, ale súbor z tlačovej produkcie spoliehajúci sa na zámery vykresľovania ICC nebude kolorimetricky presný. Vzory tieňovania (sh) a režimy prelínania nad rámec jednoduchého alfa kanála sú rovnako mimo rozsahu, pričom rekurzia Form XObject je hĺbkovo obmedzená kvôli ochrane pred zacyklením. Pre faktúry, správy, zmluvy a formuláre — stránky zložené z textu, ciest a obrázkov — je výstup verný; pre grafický návrh plný gradientov a skupín priehľadnosti považujte bitmapu za náhľad, nie za finálny nátlačok

Praktické čítanie: ak váš systém generuje dokumenty pomocou HotPDF alebo spracováva typické obchodné PDF, metóda RenderLoadedPageToBitmap ich prenesie s presnými tvarmi vložených glyfov, správnymi posunmi CID a správnou geometriou stránky. Aproximácie sa nachádzajú v kútoch grafického modelu, ktoré obchodné dokumenty navštevujú zriedka

Metóda RenderLoadedPageToBitmap, jej cached variant a tu opísaný mechanizmus vykresľovania vložených glyfov sa dodávajú ako súčasť komponentu HotPDF Component pre Delphi a C++Builder — natívnej knižnice VCL bez externých závislostí na DLL, ktorá v jednom balíku pokrýva tvorbu PDF, úpravy, extrakciu textu a vykresľovanie stránok