Technický článek

Vykreslování stránek PDF do rastru (Bitmap) v Delphi pomocí HotPDF

Knihovna HotPDF vykrslí načtenou stránku PDF do delphijského TBitmap pomocí jediného volání: RenderLoadedPageToBitmap(PageIndex, DPI). Funkce interpretuje stream obsahu stránky a vrací 24bitový rastr RGB vlastněný volajícím v rozlišení, které si zvolíte, což je přesně to, co vyžaduje panel miniatur, náhled tisku nebo linka pro export PDF do obrázků. Tento článek vás provede rozhraním API a poté částí, která odlišuje použitelný vykreslovací nástroj od hračky: vykreslováním textu přímo z programů vložených písem namísto použití podobně vypadajících systémových písem

Proč je vykreslení stránky PDF těžší než nakreslení obrázku?

Stránka PDF není obrázek. Je to program: proud operátorů, které vytvářejí cesty, vybírají písma, nastavují barvy a umisťují glyfy, spouštěný proti grafickému modelu definovanému v normě ISO 32000-1 §8. Nic v souboru neříká, jak který pixel vypadá. Chcete-li vytvořit rastrový obrázek, musíte tento program spustit — udržovat aktuální matici transformace, zásobník grafických stavů pro q/Q, cestu oříznutí, barevné prostory výplně a tahu — a výsledek převést na rastr (rasterizovat). Proto požadavek „prostě zobraz stránku 3 jako obrázek“ představuje interpret streamu obsahu, nikoli konverzi formátu souboru

Vykreslovací modul knihovny HotPDF, představený ve verzi v2.253.0, je postaven jako šest oddělených jednotek odrážejících tento model: jádro afinní matice pro algebru transformací PDF [a b c d e f], zásobník grafických stavů, rozlišovač barevného prostoru (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), tvůrce cest, který přemosťuje operátory cest PDF do GDI, vrstva metrik písem, která načítá pole /Widths pro správný krok, a interpret, který distribuuje operátory a řídí ostatních pět jednotek. Objekty Image XObject procházejí stejným dekódovacím zásobníkem, jaký knihovna používá pro extrakci, takže každý filtr obrázků, který HotPDF dokáže dekódovat pro extrakci — včetně obrázků JPEG 2000 komprimovaných pomocí JPXDecode — se zobrazí i ve vykresleném výstupu

Vykreslení načtené stránky do TBitmap

Metoda RenderLoadedPageToBitmap přijímá od nuly začínající index stránky a hodnotu DPI, kde 72 DPI mapuje jednu jednotku uživatelského prostoru PDF na jeden pixel. V případě selhání (index mimo rozsah, chybějící prostředky) vrací nil namísto vyvolání výjimky, takže prohlížeč může vadnou stránku přeskočit a pokračovat dál. Volající vlastní vrácený rastr a musí jej uvolnit z paměti

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 provádí změnu měřítka pro každý běžný scénář. Panel miniatur se vykresluje při 36 nebo 48 DPI a získává malé, rychlé rastry; náhled na obrazovce při 96 nebo 144 DPI odpovídá typické hustotě zobrazení; exportní cesta při 300 DPI produkuje obrázky tiskové kvality. Rotace stránky z položky /Rotate a převrácení počátku /MediaBox (PDF umisťuje počátek vlevo dole, GDI vlevo nahoře) are zpracovány uvnitř matice převodu stránky na zařízení, takže stránka formátu US Letter při 72 DPI se vrátí jako přesně 612×792 pixelů správnou stranou nahoru

Proč vykreslené miniatury PDF zobrazují špatné glyfy?

Chybné nebo přibližné glyfy ve vykresleném výstupu PDF téměř vždy znamenají, že vykreslovací modul nahrazuje systémové písmo namísto použití písma vloženého v souboru. První vykreslovací modul HotPDF dělal přesně to: odstranil prefix podsady z /BaseFont (převod ABCDEF+Arial na Arial), požádal GDI o systémové písmo tohoto jména a text jím vykreslil. U dokumentu, který používá písmo Arial nebo Times New Roman se standardním kódováním, vypadá výsledek velmi podobně. Jedná se však o aproximaci, která selhává v jasně definovaných případech

Písma s vloženou podsadou jsou nejhorším případem. Písmo s podsadou může nést pouze čtyřicet glyfů, které dokument skutečně používá, s kódy znaků přiřazenými v pořadí soukromém pro daný soubor — kód 1 může být „T“, kód 2 „h“ atd. Systémové písmo o tomto soukromém přiřazení nic neví, takže text buď zmizí, nebo se vykreslí jako zcela chybné znaky. Stejným způsobem selhávají uživatelská kódování, symbolová písma, písma čárových kódů a jakýkoli řez nenainstalovaný na počítači, kde probíhá vykreslování. Vykreslovací modul, který se zastaví u substituce systémových písem, produkuje miniatury, které jsou sice rozpoznatelné jako daná stránka — ale pouze dokud stránka nepoužije písma, kvůli nimž bylo vložení vůbec nutné

Vykreslování vložených glyfů: kreslení ze samotného programu písma

HotPDF tuto mezeru uzavřel v průběhu pěti verzí (v2.268.0 až v2.272.0) tím, že analyzuje integrované programy písem a přehrává obrysy jejich glyfů jako vyplněné vektorové cesty GDI. Text na vykreslené stránce nyní pochází ze stejných dat obrysů, jaké používá standardní prohlížeč, což znamená, že písma s podsadou, uživatelská kódování a nenainstalované řezy se vykreslují ve svých přesných tvarech. Pokrytí bylo vybudováno podle formátu písem:

U písem Type0/CIDFontType2 s vloženým TrueType programem (FontFile2) analyzuje vykreslovací modul přímo tabulky glyf a loca: kvadratické obrysy jsou převedeny na kubické Bézierovy křivky, kterým GDI rozumí, rekonstruují se implicitní body na křivce (on-curve) mezi po sobě jdoucími body mimo křivku (off-curve) a kompozitní glyfy se rekurzivně přehrávají. Jsou podporována rozvržení Identity i explicitní streamy CIDToGIDMap a kroky CID respektují položky šířky /W a /DW, takže dvoubajtový text v kódování Identity-H se posouvá správně

Programy CFF (FontFile3, ať už jde o CIDFontType0C, Type1C nebo OpenType obálku) získávají plný interpret charstringu Type 2: čáry, křivky, rodinu flex, masky hintů a volání lokálních/globálních podprogramů se správným zkreslením podprogramu. CID kódované programy CFF mapují kódy znaků přes znakovou sadu (charset) písma, což je důležité pro písma s podsadou, jejichž pořadí glyfů se liší od pořadí CID, a je respektován výběr font-DICT pro jednotlivé glyfy prostřednictvím FDArray/FDSelect. Jednoduchá (ne-CID) TrueType písma řeší jednobajtové kódy přes vlastní tabulku cmap vloženého písma s robustním řetězcem podtabulek — nejprve formáty Unicode 4 a 12, poté symbolové podtabulky s privátním zrcadlením F000 a nakonec starší formáty Macintosh — zatímco jednoduchá písma Type1 se řeší přes integrované kódování programu CFF

Obraz doplňují dvě vylepšení. Za prvé, slovníky /Encoding jednoduchých písem jsou řešeny podle priorit předepsaných normou ISO 32000-1 §9.6.6: pole /Differences přepisuje základní kódování, které přepisuje vlastní mapu programu písma — což je cesta, na níž závisí nástrojové řetězce odvozené od TeX a PostScriptu, přičemž názvy glyfů se řeší přes Adobe Glyph List, znakovou sadu CFF nebo TrueType cmap. Za druhé, písma Type3, jejichž glyfy jsou samy o sobě malými streamy obsahu, se přehrávají přes vykreslovací modul se složenou maticí písma, velikostí písma a textovou maticí; šířky v prostoru glyfů /Widths jsou interpretovány přes /FontMatrix, jak vyžaduje norma ISO 32000-1 §9.6.5, a procedury glyfů deklarující ohraničující rámeček d1 jsou jím oříznuty, takže poškozený glyf čárového kódu nemůže malovat mimo svou buňku. Pokud kód nelze namapovat — poškozený program, nemapovaný znak — vykreslovací modul se pro daný glyf vrátí k vykreslení systémovým písmem, namísto zahození celého textového úseku

Jak urychlit opakované vykreslování?

Odpovědí, kterou HotPDF nabízí, je cache naposledy použitých stránek: RenderLoadedPageToBitmapCached uchovává až RenderCacheCapacity vykreslených stránek (výchozí hodnota 8) klíčovaných indexem stránky a DPI, a při zásahu cache vrací novou kopii vlastněnou volajícím, aniž by se dotkl streamu obsahu — typicky tisíckrát rychleji než při opětovné interpretaci stránky. Tento vzorec přesně vyhovuje prohlížečům: uživatel přepínající mezi dvěma stránkami nebo událost změny velikosti, která znovu požaduje stejnou stránku při stejném DPI, zasáhne cache pokaždé

// 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

Před zvýšením kapacity buďte opatrní ohledně spotřeby paměti. Stránka US Letter při 300 DPI má 2550×3300 pixelů, což je zhruba 25 MB pro 24bitový rastr, takže osm nacachovaných stránek v exportním rozlišení zabere zhruba 200 MB. Při DPI miniatur stojí stejných osm položek hluboko pod jeden megabajt. Nastavte RenderCacheCapacity pro DPI, při kterém skutečně cachujete, a po jakékoli úpravě na místě zavolejte InvalidateRenderedPageCache — cache je klíčována pouze stránkou a DPI a nedokáže zjistit, že se podkladový obsah změnil. Načtení nového dokumentu ji vymaže automaticky

Pod cší stránek funguje druhá cache: dekódované objekty Image XObject jsou uchovávány v paměťově omezeném úložišti ohraničeném parametrem ImageCacheMaxBytes (výchozí hodnota 32 MB) s vyřazováním naposledy použitých položek (LRU). Logo nebo obrázek záhlaví opakovaný na každé stránce se dekóduje pouze jednou na načtení dokumentu namísto pro každý operátor Do, což zhruba na polovinu zkracuje dobu vykreslování stránek se sdílenými obrázky a stejnou měrou urychluje export vícepatrových TIFF. Volání InvalidateRenderedPageCache vymaže i tuto cache

Co se stále vykresluje přibližně

Vykreslovací modul cílí na běžnou sadu dokumentů PDF a je dobré vědět, kde leží jeho limity. Barevné prostory CalRGB, Lab a prostory založené na ICC jsou spíše aproximovány, než plně barevně spravovány — barevné prostory zařízení, indexované palety a vzorkované barevní převodní tabulky funkcí Type 0 jsou zpracovávány, ale tiskový produkční soubor spoléhající na záměry vykreslování ICC nebude kolorimetricky přesný. Stínovací vzory (sh) a režimy prolnutí mimo jednoduché alfa jsou rovněž mimo rozsah a rekurze Form XObject je hloubkově omezena jako ochrana proti cyklení. Pro faktury, sestavy, smlouvy a formuláře — stránky tvořené textem, cestami a obrázky — je výstup věrný; pro grafický nátisk plný přechodů a skupin průhlednosti berte rastr jako náhled, nikoli jako nátisk

Praktické poučení: pokud vaše linka generuje dokumenty pomocí HotPDF nebo zpracovává běžná obchodní PDF, RenderLoadedPageToBitmap je převede tam a zpět s přesnými tvary vložených glyfů, správnými kroky CID a správnou geometrií stránky. Aproximace žijí v rozích grafického modelu, které obchodní dokumenty navštěvují zřídka

Metoda RenderLoadedPageToBitmap, její cachovaná varianta a zde popsaná linka pro vykreslování vložených glyfů jsou dodávány jako součást komponenty HotPDF Component pro Delphi a C++Builder — nativní knihovny VCL bez externích závislostí na DLL, pokrývající vytváření, úpravy, extrakci textu a vykreslování stránek PDF v jednom balíku