HotPDF redă o pagină PDF încărcată într-un tip TBitmap Delphi printr-un singur apel: RenderLoadedPageToBitmap(PageIndex, DPI). Funcția interpretează fluxul de conținut al paginii și returnează un bitmap RGB de 24 de biți deținut de apelant la rezoluția aleasă, ceea ce reprezintă exact ceea ce are nevoie o listă de miniaturi, o previzualizare de tipărire sau o linie de export PDF-în-imagine. Acest articol prezintă API-ul, iar apoi componenta care diferențiază un redator (renderer) utilizabil de o jucărie: desenarea textului direct din programele de font încorporate, mai degrabă decât din fonturi de sistem similare
De ce este redarea unei pagini PDF mai dificilă decât desenarea unei imagini?
O pagină PDF nu este o imagine. Este un program: un flux de operatori care construiesc căi, selectează fonturi, definesc culori și plasează glife, executați pe baza modelului grafic definit în ISO 32000-1 §8. Nimic din fișier nu precizează cum arată un pixel. Pentru a obține un bitmap, trebuie să rulați acel program — să mențineți o matrice de transformare curentă, o stivă de stări grafice pentru q/Q, o cale de decupare, spații de culori pentru umplere și contur — și să rasterizați rezultatum. Din acest motiv, „afișarea paginii 3 ca imagine” este un interpret de flux de conținut, nu o conversie de format de fișier
Redatorul din HotPDF, introdus în v2.253.0, este construit ca șase unități independente care reflectă acel model: un nucleu de matrice afină pentru algebra de transformare PDF [a b c d e f], o stivă de stări grafice, un rezolvitor de spațiu de culori (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), un generator de căi care conectează operatorii de căi PDF la GDI, un strat de metrici ale fonturilor care citește tablourile /Widths pentru avansuri corecte și interpretul care trimite operatorii și le controlează pe celelalte cincie. Obiectele imagine XObject trec prin aceeași stivă de decodare pe care biblioteca o utilizează pentru extragere, astfel încât fiecare filtru de imagine pe care HotPDF îl poate decodifica pentru extragere — inclusiv imaginile JPEG 2000 comprimate prin JPXDecode — apare de asemenea în rezultatul redat
Redarea unei pagini încărcate într-un TBitmap
RenderLoadedPageToBitmap primește un index de pagină bazat pe zero și o valoare DPI, unde 72 DPI asociază o unitate din spațiul utilizatorului PDF cu un pixel. Funcția returnează nil în caz de eșec (index în afara intervalului, resurse lipsă) în loc să genereze o excepție, permițând unui vizualizator să ignore o pagină defectă și să continue. Apelantul deține bitmap-ul returnat și trebuie să îl elibereze
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;
Argumentul DPI realizează scalarea pentru fiecare scenariu comun. O listă de miniaturi se redă la 36 sau 48 DPI, obținându-se bitmap-uri mici și rapide; o previzualizare pe ecran la 96 sau 144 DPI corespunde densității tipice a afișajului; o cale de export la 300 DPI produce imagini de calitate pentru tipărire. Rotația paginii din intrarea /Rotate și inversarea originii /MediaBox (PDF plasează originea în stânga-jos, GDI stânga-sus) sunt gestionate în cadrul matricei pagină-dispozitiv, astfel încât o pagină US Letter la 72 DPI este returnată exact sub forma 612×792 pixeli în orientarea corectă
De ce miniaturile PDF redate afișează glife greșite?
Glifele greșite sau aproximative în rezultatul PDF redat înseamnă aproape întotdeauna că redatorul înlocuiește fontul cu unul de sistem, în loc să îl utilizeze pe cel încorporat în fișier. Primul redator HotPDF făcea exact acest lucru: elimina prefixul de subset din /BaseFont (transformând ABCDEF+Arial în Arial), solicita GDI un font de sistem cu acel nume și desena textul cu acesta. Pentru un document care utilizează Arial sau Times New Roman cu o codificare standard, rezultatul pare apropiat. Însă este o aproximare care eșuează în moduri bine definite
Fonturile încorporate ca subset reprezintă cel mai dificil caz. Un font subsetat poate conține doar cele patruzeci de glife pe care un document le utilizează de fapt, cu coduri de caractere atribuite într-o ordine privată a acelui fișier — codul 1 ar putea fi „T”, codul 2 „h” și așa mai departe. Un font de sistem nu are nicio informație despre această atribuire privată, așa că textul fie dispare, fie apare sub forma unor caractere complet greșite. Codificările personalizate, fonturile cu simboluri, fonturile pentru coduri de bare și orice font neinstalat pe mașina care realizează redarea eșuează în același mod. Un redator care se limitează la înlocuirea cu fonturi de sistem produce miniaturi în care pagina este recognoscibilă — până când pagina folosește exact fonturile care au făcut necesară încorporarea în primul rând
Redarea glifelor încorporate: desenarea direct din programul de font
HotPDF a eliminat acest decalaj de-a lungul a cinei versiuni (de la v2.268.0 la v2.272.0) prin analizarea programelor de font încorporate și reproducerea contururilor de glife ca trasee vectoriale GDI umplute. Textul dintr-o pagină redată provine acum din aceleași date de contur utilizate de un vizualizator conform, ceea ce înseamnă că fonturile subsetate, codificările personalizate și fonturile neinstalate sunt redate cu formelelor exacte. Acoperirea a fost extinsă în funcție de tipul de font:
Pentru fonturile Type0/CIDFontType2 cu un program TrueType încorporat (FontFile2), redatorul analizează direct tabelele glyf și loca: contururile pătratice sunt convertite în curbele Bézier cubice pe care GDI le înțelege, punctele de pe curbă implicite dintre punctele consecutive din afara curbei sunt reconstruite, iar glifele compuse sunt redate recursiv. Sunt acceptate atât machetele Identity, cât și fluxurile explicite CIDToGIDMap, iar avansurile CID respectă intrările de lățime /W și /DW, astfel încât textul Identity-H pe doi octeți se afișează corect
Programele CFF (FontFile3, fie că este vorba despre CIDFontType0C, Type1C sau un wrapper OpenType) beneficiază de un interpret complet de șiruri de caractere (charstring) Type 2: linii, curbe, familia flex, măști de sugestie (hint) și apeluri de subrutine locale/globale cu compensarea corectă a subrutinei. Programele CFF cu cheie CID mapează codurile de caractere prin setul de caractere (charset) al fontului, ceea ce contează pentru fonturile subsetate a căror ordine de glife diferă de ordinea CID, și este respectată selectarea font-DICT per glifă prin FDArray/FDSelect. Fonturile TrueType simple (non-CID) rezolvă codurile de un octet prin propria tabelă cmap a fontului încorporat cu un lanț robust de subtabele — formatele Unicode 4 și 12 mai întâi, apoi subtabelele de simboluri cu oglindirea zonei de utilizare privată F000, urmate de formatele vechi Macintosh — în timp ce fonturile simple Type1 se rezolvă prin codificarea integrată a programului CFF
Două rafinări completează această structură. În primul rând, dicționarele /Encoding pentru fonturile simple sunt rezolvate conform priorității prevăzute de ISO 32000-1 §9.6.6: tablourile /Differences înlocuiesc codificarea de bază, care la rândul ei înlocuiește harta proprie a programului de font — calea de care depind instrumentele derivate din TeX și PostScript, numele glifelor fiind rezolvate prin Adobe Glyph List, setul de caractere CFF sau cmap TrueType. În al doilea rând, fonturile Type3, ale căror glife sunt ele însele mici fluxuri de conținut, sunt reproduse prin redator compunând matricea fontului, dimensiunea fontului și matricea textului; valorile /Widths din spațiul glifelor sunt interpretate prin /FontMatrix conform cerințelor ISO 32000-1 §9.6.5, iar procedurile de glife care declară o casetă de încadrare d1 sunt decupate conform acesteia, astfel încât o glifă de cod de bare incorectă să nu poată desena în afara celulei sale. Când un cod nu poate fi mapat — un program deteriorat, un caracter nemapat — redatorul apelează la desenarea cu font de sistem pentru acea glifă, în loc să renunțe la întreaga secțiune de text
Cum se pot accelera redările repetate?
Soluția oferită de HotPDF este o memorie cache a paginilor utilizate cel mai recent: RenderLoadedPageToBitmapCached păstrează până la RenderCacheCapacity pagini redate (implicit 8) identificate prin indexul paginii și DPI, iar o potrivire în cache returnează o copie nouă deținută de apelant fără a accesa fluxul de conținut — de obicei de mii de ori mai rapid decât re-interpretarea paginii. Acest model se potrivește perfect vizualizatoarelor: un utilizator care navighează între două pagini sau un eveniment de redimensionare care solicită din nou aceeași pagină la același DPI va accesa memoria cache de fiecare dată
// 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
Fiți realiști cu privire la consumul de memorie înainte de a mări capacitatea. O pagină US Letter la 300 DPI are 2550×3300 pixeli, adică aproximativ 25 MB ca bitmap pe 24 de biți, astfel încât opt pagini stocate în cache la rezoluția de export ocupă aproximativ 200 MB. La rezoluția DPI pentru miniaturi, aceleași opt intrări costă mult sub un megabyte. Dimensionați RenderCacheCapacity pentru rezoluția DPI la care stocați efectiv în cache și apelați InvalidateRenderedPageCache după orice editare pe loc — memoria cache este identificată doar prin pagină și DPI și nu poate detecta dacă conținutul de bază s-a schimbat. Încărcarea unui nou document o golește automat
O a doua memorie cache funcționează sub memoria cache a paginilor: obiectele imagine decodificate XObject sunt păstrate într-un spațiu limitat în octeți, definit de ImageCacheMaxBytes (implicit 32 MB), cu eliminare pe baza celei mai vechi utilizări. O imagine de logo sau antet repetată pe fiecare pagină se decodifică o singură dată la încărcarea documentului, în loc de o decodare la fiecare operator Do, ceea ce reduce la jumătate timpul de redare pentru paginile cu imagini partajate și accelerează exportul TIFF pe mai multe pagini cu aceeași măsură. InvalidateRenderedPageCache golește de asemenea și această memorie cache
Ce se redă în continuare cu aproximare
Redatorul vizează subsetul comun de documente PDF și merită ocrotit unde sunt limitele. Spațiile de culori CalRGB, Lab și cele bazate pe ICC sunt mai degrabă aproximate decât gestionate din punct de vedere al culorilor — spațiile de culori ale dispozitivului, paletele indexate și căutările de culori funcționale Type 0 eșantionate sunt gestionate, dar un fișier de producție tipografică bazat pe intenții de redare ICC nu va fi exact din punct de vedere colorimetric. Modelele de umbrire (shading patterns - sh) și modurile de amestecare dincolo de canalul alfa simplu sunt de asemenea în afara scopului, iar recursiunea Form XObject este limitată ca adâncime ca protecție împotriva buclelor. Pentru facturi, rapoarte, contracte și formulare — pagini formate din text, căi și imagini — rezultatul este fidel; pentru o probă de design plină de gradiente și grupuri de transparență, tratați bitmap-ul ca pe o previzualizare, nu ca pe o probă de tipar
Concluzia practică: dacă linia dvs. de procesare generează documente cu HotPDF sau procesează PDF-uri tipice de afaceri, RenderLoadedPageToBitmap le procesează complet păstrând formele exacte ale glifelor încorporate, avansurile CID corecte și geometria corectă a paginii. Aproximările se regăsesc în zonele secundare ale modelului grafic pe care documentele de afaceri le vizitează rar
RenderLoadedPageToBitmap, varianta sa stocată în cache și linia de redare a glifelor încorporate descrisă aici sunt livrate ca parte a HotPDF Component pentru Delphi și C++Builder — o bibliotecă nativă VCL fără dependențe de DLL-uri externe, care acoperă crearea, editarea, extragerea textului și redarea paginilor PDF într-un singur pachet