Techninis straipsnis

PDF puslapių atvaizdavimas į TBitmap Delphi su HotPDF

HotPDF įkeltą PDF puslapį atvaizduoja į Delphi TBitmap vienu iškvietimu: RenderLoadedPageToBitmap(PageIndex, DPI). Funkcija interpretuoja puslapio turinio srautą ir grąžina iškvietėjui priklausantį 24 bitų RGB taškinį vaizdą jūsų pasirinkta raiška, o būtent to ir reikia miniatiūrų juostai, spausdinimo peržiūrai ar PDF vertimo į paveikslėlius grandinei. Šis straipsnis pereina per API, o paskui per tą dalį, kuri skiria tinkamą atvaizdiklį nuo žaislo: teksto piešimą iš pačių įterptų šriftų programų, o ne iš panašiai atrodančių sisteminių šriftų

Kodėl PDF puslapio atvaizdavimas sunkesnis nei paveikslėlio piešimas?

PDF puslapis nėra paveikslas. Tai programa: operatorių srautas, kuris stato kontūrus, parenka šriftus, nustato spalvas ir dėlioja glifus, vykdomas ISO 32000-1 §8 apibrėžtame grafikos modelyje. Faile niekur nepasakyta, kaip atrodo kuris nors pikselis. Kad gautumėte taškinį vaizdą, tą programą privalote paleisti — prižiūrėti dabartinę transformacijos matricą, grafinės būsenos dėklą operatoriams q/Q, apkirpimo kontūrą, užpildo ir brūkšnio spalvų erdves — ir rezultatą rastrizuoti. Todėl „tiesiog parodyk 3 puslapį kaip paveikslėlį“ yra turinio srauto interpretatorius, o ne failo formato konvertavimas

HotPDF atvaizdiklis, pristatytas v2.253.0, sudarytas iš šešių atsietų modulių, atkartojančių tą modelį: afininių matricų šerdis PDF [a b c d e f] transformacijų algebrai, grafinės būsenos dėklas, spalvų erdvių sprendiklis (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), kontūrų statytojas, tiltu jungiantis PDF kontūrų operatorius su GDI, šrifto matmenų sluoksnis, skaitantis /Widths masyvus teisingiems postūmiams, ir interpretatorius, išsiuntinėjantis operatorius bei valdantis kitus penkis. Paveikslėlių XObject eina per tą patį iškodavimo dėklą, kurį biblioteka naudoja ištraukimui, todėl kiekvienas paveikslėlių filtras, kurį HotPDF moka iškoduoti ištraukimui — įskaitant JPXDecode suglaudintus JPEG 2000 paveikslėlius — pasirodo ir atvaizduotoje išvestyje

HotPDF puslapių atvaizdavimo architektūra: PDF turinio srauto interpretatorius valdo šešis atsietus modulius ir pagamina iškvietėjui priklausantį 24 bitų RGB TBitmap
Interpretatorius išsiuntinėja operatorius, o šeši moduliai neša matricos, būsenos, spalvos, kontūro, matmenų ir glifų darbą

Įkelto puslapio atvaizdavimas į TBitmap

RenderLoadedPageToBitmap priima nuo nulio skaičiuojamą puslapio indeksą ir DPI reikšmę, kurioje 72 DPI atvaizduoja vieną PDF naudotojo erdvės vienetą į vieną pikselį. Nesėkmės atveju (indeksas už ribų, trūkstami ištekliai) jis grąžina nil, o ne meta išimtį, todėl peržiūros langas gali blogą puslapį praleisti ir eiti toliau. Grąžintas taškinis vaizdas priklauso iškvietėjui, ir jį būtina 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);  // 1 puslapis ties 144 DPI
      if Bmp <> nil then
      try
        Image1.Picture.Assign(Bmp);
      finally
        Bmp.Free;  // taškinis vaizdas priklauso iškvietėjui
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

DPI argumentas atlieka mastelio darbą kiekvienam įprastam scenarijui. Miniatiūrų juosta atvaizduojama ties 36 ar 48 DPI ir gauna mažus, greitus taškinius vaizdus; ekrano peržiūra ties 96 ar 144 DPI atitinka tipinį ekrano tankį; eksporto kelias ties 300 DPI pagamina spausdinimo kokybės paveikslėlius. Puslapio posūkis iš /Rotate įrašo ir /MediaBox pradžios apvertimas (PDF pradžią deda apačioje kairėje, GDI viršuje kairėje) tvarkomi puslapio ir įrenginio matricoje, todėl US Letter puslapis ties 72 DPI grįžta lygiai 612×792 pikselių ir tinkama puse į viršų

Kodėl atvaizduotose PDF miniatiūrose rodomi netinkami glifai?

Netinkami ar apytiksliai glifai atvaizduotoje PDF išvestyje beveik visada reiškia, kad atvaizdiklis pakeičia šriftą sisteminiu, užuot naudojęs faile įterptąjį. Pirmasis HotPDF atvaizdiklis būtent taip ir darė: jis nuimdavo poaibio priešdėlį nuo /BaseFont (paversdamas ABCDEF+Arial į Arial), paprašydavo GDI to vardo sisteminio šrifto ir juo nupiešdavo tekstą. Dokumentui, naudojančiam Arial ar Times New Roman su standartine koduote, rezultatas atrodo panašiai. Bet tai yra apytikslumas, ir jis lūžta gerai apibrėžtais būdais

Poaibiu įterpti šriftai yra blogiausias atvejis. Poaibinis šriftas gali nešti tik tuos keturiasdešimt glifų, kuriuos dokumentas iš tikrųjų naudoja, o simbolių kodai jame priskirti tvarka, privačia tam failui: kodas 1 gali būti „T“, kodas 2 – „h“ ir taip toliau. Sisteminis šriftas apie tą privatų priskyrimą nežino nieko, todėl tekstas arba dingsta, arba išeina visai kitais simboliais. Savos koduotės, simbolių šriftai, brūkšninių kodų šriftai ir bet kuris atvaizduojančioje mašinoje neįdiegtas šriftas sugenda taip pat. Atvaizdiklis, sustojantis ties sisteminio šrifto pakeitimu, pagamina miniatiūras, kuriose puslapį galima atpažinti, kol puslapis nepanaudoja tų šriftų, dėl kurių įterpimas iš viso ir prireikė

HotPDF: sisteminio šrifto pakeitimas nuima poaibio priešdėlį nuo ABCDEF+Arial ir piešia netinkamus glifus, o įterptų glifų atvaizdavimas atkartoja FontFile kontūrus ir gauna tikslias formas
Pakeitimas išgyvena tik su įprastais sisteminiais šriftais ir sugenda būtent su tais poaibiniais šriftais, dėl kurių įterpimas ir prireikė

Įterptų glifų atvaizdavimas: piešimas iš pačios šrifto programos

HotPDF tą spragą užvėrė per penkis leidimus (nuo v2.268.0 iki v2.272.0) analizuodamas įterptas šriftų programas ir atkartodamas jų glifų kontūrus kaip užpildytus GDI vektorinius kelius. Tekstas atvaizduotame puslapyje dabar ateina iš tų pačių kontūrų duomenų, kuriuos naudoja standartą atitinkanti peržiūros programa, o tai reiškia, kad poaibiniai šriftai, savos koduotės ir neįdiegti šriftai atvaizduojami tiksliomis savo formomis. Aprėptis buvo statoma pagal šriftų rūšis:

Type0 ir CIDFontType2 šriftams su įterpta TrueType programa (FontFile2) atvaizdiklis tiesiogiai analizuoja glyf ir loca lenteles: kvadratiniai kontūrai verčiami į kubines Bezjė kreives, kurias supranta GDI, numanomi ant kreivės esantys taškai tarp gretimų už kreivės esančių taškų atkuriami, o sudėtiniai glifai atkartojami rekursyviai. Palaikomi tiek Identity, tiek atviro srauto CIDToGIDMap išdėstymai, o CID postūmiai gerbia /W ir /DW pločių įrašus, todėl dviejų baitų Identity-H tekstas žengia teisingai

CFF programos (FontFile3, ar tai būtų CIDFontType0C, Type1C, ar OpenType apvalkalas) gauna pilną Type 2 charstring interpretatorių: linijas, kreives, flex šeimą, patarimų kaukes ir vietinius bei globalius paprogramių iškvietimus su teisingu paprogramių poslinkiu. CID raktuotos CFF programos simbolių kodus atvaizduoja per šrifto charset, o tai svarbu poaibiniams šriftams, kurių glifų tvarka skiriasi nuo CID tvarkos, ir gerbiamas kiekvieno glifo šrifto DICT parinkimas per FDArray bei FDSelect. Paprasti (ne CID) TrueType šriftai vieno baito kodus sprendžia per paties įterpto šrifto cmap lentelę su patvaria polentelių grandine — pirma Unicode 4 ir 12 formatai, tada simbolių polentelės su F000 privačiojo naudojimo veidrodžiu, tada senovinis Macintosh formatas — o paprasti Type1 šriftai sprendžia per CFF programos įtaisytąją koduotę

Vaizdą užbaigia du patobulinimai. Pirma, paprastų šriftų /Encoding žodynai sprendžiami pagal prioritetą, kurį nustato ISO 32000-1 §9.6.6: /Differences masyvai nustelbia bazinę koduotę, o ta nustelbia paties šrifto programos atvaizdį — tai kelias, nuo kurio priklauso TeX ir iš PostScript kilusios įrankių grandinės, o glifų vardai sprendžiami per Adobe Glyph List, CFF charset arba TrueType cmap. Antra, Type3 šriftai, kurių glifai patys yra maži turinio srautai, atkartojami per atvaizdiklį sukomponavus šrifto matricą, šrifto dydį ir teksto matricą; glifų erdvės /Widths aiškinami per /FontMatrix, kaip reikalauja ISO 32000-1 §9.6.5, o glifų procedūros, deklaruojančios d1 ribojantį stačiakampį, prie jo apkerpamos, todėl netaisyklingas brūkšninio kodo glifas negali piešti už savo langelio. Kai kodo atvaizduoti nepavyksta, pavyzdžiui, dėl sugadintos programos ar neatvaizduoto simbolio, atvaizdiklis tam glifui atsitraukia į sisteminio šrifto piešimą, o ne numeta visą teksto atkarpą

Kaip pakartotinius atvaizdavimus padaryti greitus?

HotPDF pateikiamas atsakymas yra naujausiai naudotų puslapių talpykla: RenderLoadedPageToBitmapCached laiko iki RenderCacheCapacity atvaizduotų puslapių (pagal nutylėjimą 8), raktuotų pagal puslapio indeksą ir DPI, o pataikymas į talpyklą grąžina šviežią iškvietėjui priklausančią kopiją neliesdamas turinio srauto — paprastai tūkstančius kartų greičiau nei puslapio interpretavimas iš naujo. Toks modelis puikiai tinka peržiūros langams: naudotojas, vartantis tarp dviejų puslapių, ar dydžio keitimo įvykis, vėl prašantis to paties puslapio ta pačia raiška, pataiko į talpyklą kaskart

HotPDF: naujausiai naudotų atvaizduotų puslapių talpyklos eiga: pataikymas grąžina šviežią TBitmap kopiją neinterpretuojant puslapio iš naujo, o nepataikymas atvaizduoja ir išsaugo iki RenderCacheCapacity puslapių
Pataikymas į talpyklą visai aplenkia turinio srautą, o negaliojimo skelbimas ir atminties biudžetas išlaiko pakartotinius atvaizdavimus teisingus bei saugius
// Miniatiūrų juosta: pirmas praėjimas atvaizduoja, grįžtant atgal pataikoma į talpyklą
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;

// Po įkelto puslapio redagavimo vietoje:
Pdf.InvalidateRenderedPageCache;  // kitas atvaizdavimas atspindės pokytį

Prieš didindami talpą būkite sąžiningi dėl atminties sąskaitos. US Letter puslapis ties 300 DPI yra 2550×3300 pikselių, apie 25 MB kaip 24 bitų taškinis vaizdas, todėl aštuoni talpykloje laikomi puslapiai eksporto raiška užima maždaug 200 MB. Miniatiūrų raiška tie patys aštuoni įrašai kainuoja gerokai mažiau nei megabaitą. Parinkite RenderCacheCapacity pagal tą DPI, kuriuo iš tikrųjų talpinate, ir po bet kokio redagavimo vietoje iškvieskite InvalidateRenderedPageCache — talpykla raktuojama tik pagal puslapį ir DPI, ir ji nemato, kad pasikeitė esamas turinys. Naujo dokumento įkėlimas ją išvalo savaime

Po puslapių talpykla dirba antra talpykla: iškoduoti paveikslėlių XObject laikomi baitų biudžetu ribojamoje saugykloje, kurią riboja ImageCacheMaxBytes (pagal nutylėjimą 32 MB), su seniausiai naudotų šalinimu. Logotipas ar firminis antraštės paveikslėlis, kartojamas kiekviename puslapyje, iškoduojamas vieną kartą per dokumento įkėlimą, o ne po kartą kiekvienam Do operatoriui, ir tai maždaug perpus sutrumpina bendrus paveikslėlius turinčių puslapių atvaizdavimo laiką bei tiek pat pagreitina kelių puslapių TIFF eksportą. InvalidateRenderedPageCache išvalo ir šią talpyklą

Kas vis dar atvaizduojama apytiksliai

Atvaizdiklis taikosi į įprastą dokumentinių PDF poaibį, ir verta žinoti, kur yra kraštai. CalRGB, Lab ir ICC paremtos spalvų erdvės yra aproksimuojamos, o ne spalviškai valdomos — įrenginio spalvų erdvės, Indexed paletės ir sampuotos Type 0 funkcijų spalvų peržvalgos tvarkomos, bet spausdinimo gamybos failas, besiremiantis ICC atvaizdavimo ketinimais, kolorimetriškai tikslus nebus. Šešėliavimo raštai (sh) ir maišymo režimai, sudėtingesni už paprastą permatomumą, taip pat lieka už apimties ribų, o Form XObject rekursija ribojama pagal gylį kaip ciklų sargyba. Sąskaitoms, ataskaitoms, sutartims ir formoms, tai yra puslapiams iš teksto, kontūrų ir paveikslėlių, išvestis yra ištikima; dizaino pavyzdžiui, pilnam gradientų ir permatomumo grupių, taškinį vaizdą laikykite peržiūra, o ne etalonu

Praktinė išvada: jei jūsų grandinė kuria dokumentus su HotPDF arba suvartoja tipinius verslo PDF, RenderLoadedPageToBitmap apsuka juos ratu su tiksliomis įterptų glifų formomis, teisingais CID postūmiais ir teisinga puslapio geometrija. Apytikslumai gyvena tuose grafikos modelio kampuose, į kuriuos verslo dokumentai užsuka retai

RenderLoadedPageToBitmap, jo talpyklinis variantas ir čia aprašyta įterptų glifų atvaizdavimo grandinė pateikiami kaip HotPDF Delphi komponento, skirto Delphi ir C++Builder, dalis — gimtosios VCL bibliotekos be išorinių DLL priklausomybių, viename pakete apimančios PDF kūrimą, redagavimą, teksto ištraukimą ir puslapių atvaizdavimą