„HotPDF“ atvaizduoja įkeltą PDF puslapį į Delphi TBitmap vienu iškvietimu: RenderLoadedPageToBitmap(PageIndex, DPI). Funkcija interpretuoja puslapio turinio srautą ir grąžina kviečiančiajam priklausantį 24 bitų RGB taškinį vaizdą jūsų pasirinkta raiška. Būtent to reikia miniatiūrų juostai, spaudinio peržiūrai arba PDF konvertavimo į vaizdą srautui. Šiame straipsnyje apžvelgiama API, o po to – dalis, kuri skiria naudingą atvaizduoklį nuo žaislo: teksto piešimas iš pačių įterptų šriftų programų, o ne iš panašių sistemos šriftų
PDF puslapis nėra paveikslėlis. Tai yra programa: operatorių srautas, kuris kuria kelius, pasirenka šriftus, nustato spalvas ir išdėsto glifus, vykdomas pagal grafikos modelį, apibrėžtą ISO 32000-1 §8. Failo viduje nieko nėra pasakyta apie tai, kaip atrodo bet kuris pikselis. Norėdami sukurti taškinį vaizdą, turite paleisti tą programą – palaikyti dabartinę transformacijos matricą, grafinės būsenos dėklą (stack) operatoriams q/Q, kirpimo kelią (clipping path), užpildymo bei braukimo spalvų erdves – ir rasterizuoti rezultatą. Todėl reikalavimas „tiesiog parodyti 3 puslapį kaip vaizdą“ iš tikrųjų yra turinio srauto interpretavimas, o ne failo formato konvertavimas
„HotPDF“ atvaizduoklis, pristatytas versijoje v2.253.0, yra sukurtas iš šešių atskirų modulių, atspindinčių šį modelį: afininių matricų šerdies PDF transformacijų algebrai [a b c d e f], grafinės būsenos dėklo, spalvų erdvių sprendiklio (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), kelių kūrėjo, kuris susieja PDF kelio operatorius su GDI, šrifto metrikos sluoksnio, nuskaitančio /Widths masyvus teisingiems poslinkiams, ir interpretatoriaus, kuris paskirsto operatorius bei valdo kitus penkis modulius. Vaizdo XObjects apdorojami naudojant tą patį iškodavimo dėklą, kurį biblioteka naudoja išgavimui, todėl kiekvienas vaizdo filtras, kurį „HotPDF“ gali iškoduoti išgavimui – įskaitant JPEG 2000 vaizdus, suspaustus naudojant JPXDecode – taip pat atsiranda atvaizduotoje išvestyje
PDF puslapio atvaizdavimas yra sudėtingesnis nei paprasto vaizdo piešimas, nes reikia atkurti tekstą, vektorinius kelius, spalvų erdves ir skaidrumą. Todėl miniatiūrai svarbu naudoti tą pačią atvaizdavimo grandinę kaip ir galutiniam puslapiui, o ne bandyti išgauti tik vaizdo išteklius
Įkelto puslapio atvaizdavimas į TBitmap
RenderLoadedPageToBitmap priima puslapio indeksą (nuo nulio) ir DPI reikšmę, kur 72 DPI priskiria vieną PDF vartotojo erdvės vienetą vienam pikseliui. Nesėkmės atveju (indeksas už ribų, trūksta išteklių) ji grąžina nil, o ne sukelia klaidą, todėl peržiūros programa gali praleisti sugadintą puslapį ir tęsti darbą. Kviečiantysis yra grąžinto taškinio vaizdo savininkas ir privalo jį atlaisvinti
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 argumentas atlieka mastelio keitimo darbą kiekvienam įprastam scenarijui. Miniatiūrų juosta atvaizduojama esant 36 arba 48 DPI ir gaunami maži, greiti taškiniai vaizdai; ekrano peržiūra esant 96 arba 144 DPI atitinka tipinį ekrano tankį; eksporto kelias esant 300 DPI sukuria spaudos kokybės vaizdus. Puslapio pasukimas pagal įrašą /Rotate ir /MediaBox pradžios taško apvertimas (PDF nustato koordinačių pradžią apačioje kairėje, o GDI – viršuje kairėje) valdomi puslapio-įrenginio matricoje, todėl JAV Letter puslapis esant 72 DPI grąžinamas tiksliai kaip 612×792 pikselių teisinga kryptimi
Kodėl atvaizduotose PDF miniatiūrose rodomi neteisingi glifai?
Neteisingi arba apytiksliai glifai atvaizduotoje PDF išvestyje beveik visada reiškia, kad atvaizduoklis pakeičia šriftą sisteminiu šriftu, užuot naudojęs faile įterptą šriftą. Pirmasis „HotPDF“ atvaizduoklis darė būtent tai: jis pašalindavo subgrupės prefiksą iš /BaseFont (paversdamas ABCDEF+Arial į Arial), paprašydavo GDI pateikti to paties pavadinimo sistemos šriftą ir nupiešdavo tekstą su juo. Dokumentams, kuriuose naudojamas „Arial“ arba „Times New Roman“ su standartiniu kodavimu, rezultatas atrodo panašus. Tačiau tai yra tik apytikslis atvaizdavimas, kuris sugriūva tam tikrais atvejais
Subgrupuoti įterptieji šriftai yra blogiausias atvejis. Subgrupuotas šriftas gali turėti tik keturiasdešimt glifų, kuriuos dokumentas iš tikrųjų naudoja, o simbolių kodai priskiriami tam tikra failo privačia tvarka – pavyzdžiui, kodas 1 gali būti raidė „T“, kodas 2 – „h“ ir pan. Sistemos šriftas nieko nežino apie šį privatų priskyrimą, todėl tekstas arba išnyksta, arba atvaizduojamas visiškai neteisingais simboliais. Pasirinktiniai kodavimai, simbolių šriftai, brūkšninių kodų šriftai ir bet koks šriftas, kuris nėra įdiegtas atvaizdavimo mašinoje, sugenda taip pat. Atvaizduoklis, kuris apsiriboja tik sistemos šriftų keitimu, sukuria miniatiūras, kurios leidžia atpažinti puslapį – kol puslapyje nenaudojami šriftai, dėl kurių šriftų įterpimas išvis tapo būtinas
Įterptųjų glifų atvaizdavimas: piešimas iš pačios šrifto programos
„HotPDF“ pašalino šią spragą per penkias versijas (nuo v2.268.0 iki v2.272.0), išanalizuodama įterptųjų šriftų programas ir atkursdama jų glifų kontūrus kaip užpildytus GDI vektorinius kelius. Tekstas atvaizduotame puslapyje dabar gaunamas iš tų pačių kontūrų duomenų, kuriuos naudoja reikalavimus atitinkanti peržiūros programa, o tai reiškia, kad subgrupuoti šriftai, pasirinktiniai kodavimai ir neįdiegti šriftai atvaizduojami su tiksliomis savo formomis. Palaikymas buvo kuriamas pagal šrifto tipą:
Šriftams Type0/CIDFontType2 su įterpta „TrueType“ programa (FontFile2), atvaizduoklis tiesiogiai analizuoja lenteles glyf ir loca: kvadratiniai kontūrai konvertuojami į kubinius Bezjė (Béziers) kreivės kontūrus, kuriuos supranta GDI, atstatomi numanomi taškai ant kreivės tarp gretimų taškų už kreivės ribų, o sudėtiniai glifai atkuriami rekursiškai. Palaikomi tiek Identity, tiek aiškūs srauto CIDToGIDMap išdėstymai, o CID poslinkiai atitinka /W ir /DW pločių įrašus, todėl dviejų baitų Identity-H tekstas žengia teisingai
CFF programos (FontFile3, nepriklausomai nuo to, ar tai CIDFontType0C, Type1C, ar „OpenType“ apvalkalas) gauna pilną „Type 2“ simbolių eilučių (charstring) interpretatorių: linijas, kreives, „flex“ šeimą, užuominų (hint) kaukes ir vietinių/globalių paprogramių iškvietimus su teisingu paprogramių poslinkiu. CID koduotos CFF programos susieja simbolių kodus per šrifto simbolių rinkinį (charset), kas yra svarbu subgrupuotiems šriftams, kurių glifų tvarka skiriasi nuo CID tvarkos, ir atsižvelgiama į šrifto-DICT parinkimą kiekvienam glifui per FDArray/FDSelect. Paprasti (ne CID) TrueType šriftai išsprendžia vieno baito kodus per paties įterpto šrifto lentelę cmap su patikima šalutinių lentelių grandine – pirmiausia „Unicode“ formatas 4 ir 12, po to simbolių šalutinės lentelės su privačiu naudojimu veidrodyje F000, o po to – senieji „Macintosh“ formatai – tuo tarpu paprasti Type1 šriftai išsprendžiami per CFF programos integruotą kodavimą
Vaizdą užbaigia du patobulinimai. Pirma, paprastų šriftų /Encoding žodynai išsprendžiami pagal prioritetą, kurį nurodo ISO 32000-1 §9.6.6: /Differences masyvai pakeičia bazinį kodavimą, kuris savo ruožtu pakeičia paties šrifto programos žemėlapį – tai yra kelias, nuo kurio priklauso „TeX“ ir „PostScript“ kilmės įrankių grandinės, o glifų pavadinimai išsprendžiami per „Adobe Glyph List“, CFF simbolių rinkinį arba „TrueType“ lentelę cmap. Antra, Type3 šriftai, kurių glifai patys yra maži turinio srautai, atkuriami per atvaizduoklį sukomponuojant šrifto matricą, šrifto dydį ir teksto matricą; glifų erdvės /Widths interpretuojami per /FontMatrix, kaip reikalauja ISO 32000-1 §9.6.5, o glifų procedūros, deklaruojančios ribojantį rėmelį d1, yra apkarpomi pagal jį, kad netinkamo formato brūkšninio kodo glifas negalėtų piešti už savo langelio ribų. Kai kodo nepavyksta susieti – pavyzdžiui, pažeista programa, nesusietas simbolis – atvaizduoklis tam glifui grįžta prie piešimo sisteminiu šriftu, užuot praleidęs visą teksto dalį
Kaip pagreitinti pasikartojančius atvaizdavimus?
„HotPDF“ pateikiamas atsakymas yra paskiausiai naudotų puslapių talpykla: RenderLoadedPageToBitmapCached saugo iki RenderCacheCapacity atvaizduotų puslapių (numatytoji reikšmė 8), sugrupuotų pagal puslapio indeksą ir DPI, o radus atitikmenį talpykloje, grąžina naują kviečiančiajam priklausanti kopija neliečiant turinio srauto – tai paprastai yra tūkstančius kartų greičiau nei puslapio interpretavimas iš naujo. Šis šablonas puikiai tinka peržiūros programoms: kai vartotojas perjungia du puslapius arba keičiant lango dydį vėl prašoma to paties puslapio tuo pačiu DPI, kaskart pataikoma į talpyklą
// 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
Būkite atviri dėl atminties poreikio prieš didindami talpyklos talpą. „US Letter“ puslapis prie 300 DPI yra 2550×3300 pikselių, kas užima apie 25 MB kaip 24 bitų taškinis vaizdas, todėl aštuoni talpykloje išsaugoti puslapiai eksporto raiška užima maždaug 200 MB. Miniatiūrų DPI atveju tie patys aštuoni įrašai kainuoja daug mažiau nei megabaitą. Nustatykite RenderCacheCapacity pagal DPI, kuriuo iš tikrųjų naudojate talpyklą, ir iškvieskite InvalidateRenderedPageCache po bet kokio redagavimo vietoje – talpykla indeksuojama tik pagal puslapį ir DPI, zodžiu, ji negali matyti, kad pasikeitė pagrindinis turinys. Įkeliant naują dokumentą ji išvaloma automatiškai
Po puslapio talpykla veikia antroji talpykla: iškoduoti vaizdo XObjects saugomi riboto dydžio baitų saugykloje, ribojamoje ImageCacheMaxBytes (numatytoji reikšmė 32 MB) su paskiausiai naudotų elementų šalinimu (LRU). Logotipas arba blanko vaizdas, pasikartojantis kiekviename puslapyje, iškoduojamas vieną kartą per dokumento įkėlimą, o ne kaskart vykdant operatorių Do, o tai maždaug perpus sumažina bendro vaizdo puslapių atvaizdavimo laiką ir tiek pat pagreitina kelių puslapių TIFF eksportą. InvalidateRenderedPageCache išvalo ir šią talpyklą
Kas vis dar atvaizduojama apytiksliai
Atvaizduoklis orientuojasi į įprastą dokumentų PDF poaibį, todėl verta žinoti, kur yra jo ribos. „CalRGB“, „Lab“ ir „ICC“ pagrindu veikiančios spalvų erdvės yra atvaizduojamos apytiksliai, o ne valdomos spalvų valdymo sistemomis – įrenginių spalvų erdvės, indeksuotos paletės ir imtinių Type 0 funkcijos spalvų paieškos yra palaikomos, tačiau spaudai paruoštas failas, priklausantis nuo ICC atvaizdavimo tikslų, nebus spalviškai tikslus. Šešėliavimo šablonai (sh) ir maišymo režimai už paprasto alfa kanalo ribų taip pat nėra palaikomi, o „Form XObject“ rekursijos gylis yra ribojamas kaip apsauga nuo ciklų. Sąskaitoms faktūroms, ataskaitoms, sutartims ir formoms – puslapiams, sudarytiems iš teksto, kelių ir vaizdų – išvestis yra tiksli; dizaino maketui, pilnam gradientų ir skaidrumo grupių, vertinkite taškinį vaizdą kaip peržiūrą, o ne kaip gamybinį pavyzdį
Praktinė išvada: jei jūsų procesas generuoja dokumentus su „HotPDF“ arba naudoja tipinius verslo PDF failus, RenderLoadedPageToBitmap atkuria juos su tiksliomis įterptų glifų formomis, teisingais CID poslinkiais ir teisinga puslapio geometrija. Apytiksliai skaičiavimai egzistuoja grafinio modelio kampuose, kuriuose verslo dokumentai lankosi retai
RenderLoadedPageToBitmap, jo talpyklinis variantas ir čia aprašytas įterptųjų glifų atvaizdavimo srautas pateikiami kaip „HotPDF Component“, skirtos Delphi ir C++Builder, dalis – tai vietinė VCL biblioteka be išorinių DLL priklausomybių, apimanti PDF kūrimą, redagavimą, teksto išgavimą ir puslapio atvaizdavimą viename pakete