Optično prebran arhiv lahko v enem samem PDF dokumentu zavzame več gigabajtov. Pregledovalnik, ki odpre tako datoteko, običajno želi prikazati samo eno stran, morda kazalo vsebine, ali pa stran, na katero je uporabnik preskočil prek zaznamka. Branje celotne datoteke v pomnilnik za upodobitev samo dveh strani je potratno v vseh pogledih: porabi naslovni prostor, zaustavi uporabnika zaradi dolgega začetnega nalaganja, v 32-bitnem procesu Delphi pa lahko povsem spodleti še preden se prikaže ena sama stran. PDFium je bil zgrajen s tem v mislih. Dokument lahko naloži prek povratnega klica (callback), ki zahteva specifične obsege bajtov takrat, ko jih potrebuje, in nikoli ne zahteva celotne datoteke naenkrat. Vendar pa na začetku velja ena omejitev: ta pretočni kanal opiše datoteko z 32-bitno dolžino, kar pomeni, da je primeren za posamezno datoteko velikosti do 4 GiB, kar v praksi pokriva skoraj vsak optično prebran arhiv. Datoteka, ki presega to velikost, ni predmet tega članka; potrebno jo je med samim skeniranjem razdeliti v več delov ali pa jo odpreti s strategijo neposrednega dostopa, pravilna varovalka, ki to omejitev upošteva, pa ima spodaj namenjen svoj lasten razdelek
Komponenta izpostavlja to pot s pomočjo adapterja toka. Njemu predate poljuben TStream, PDFium pa po potrebi črpa bloke iz tega toka. Datoteka se lahko nahaja na disku, v podatkovnem polju blob zbirke podatkov ali pa stoji za katerimkoli drugim potomcem TStream, in vnaprej ni prekopirana v pomnilnik
Kako PDFium zahteva bajte
PDFiumov C API naloži dokument iz objekta, ki ga zagotovi klicatelj, opisanega s strukturo FPDF_FILEACCESS. Struktura ima tri pomembne dele: polje za dolžino, povratni klic za branje in neprozoren (opaque) uporabniški parameter. Vstopna točka, ki jo porabi, je FPDF_LoadCustomDocument. Ko PDFium dobi to strukturo, razčleni priklopnik (trailer), locira tabelo navzkrižnih referenc, in od tam naprej prebere le tisto, kar določena operacija zahteva. Odpiranje dokumenta se dotakne zgolj repa datoteke in nekaj objektov kataloga. Upodabljanje strani 400 pa prebere tokove vsebine in vire zgolj za tisto stran in nič drugega
To je razlika med medpomnilnim in pretočnim nalaganjem. Medpomnilno nalaganje prebere datoteko od konca do konca še preden PDFium vidi prvi bajt. Pretočno nalaganje pa obrne razmerje: PDFium vodi branja, in bajti, katerih se nikoli ne dotakne, niso nikoli prebrani. Za datoteko, veliko več gigabajtov in na kateri se gleda po eno stran naenkrat, je to prepad med neuporabnim in takojšnjim nalaganjem
Adapter toka
Adapter, ki premosti razmik med TStream Delphija in FPDF_FILEACCESS, je TPdfStreamAdapter. Njegov konstruktor sprejme tok in zastavico za lastništvo, enkrat zajame dolžino toka, izpolni zapis v FPDF_FILEACCESS in poveže povratni klic za branje. Ko PDFium kasneje vrne klic z odmikom in velikostjo, adapter v toku poišče tisti odmik in prekopira natančno tisti obseg v medpomnilnik, ki ga zagotovi PDFium
// Verbatim from the component: the stream-to-FPDF_FILEACCESS bridge
constructor TPdfStreamAdapter.Create(AStream: TStream; AOwnsStream: Boolean);
begin
inherited Create;
if AStream = nil then
raise EPdfError.Create('TPdfStreamAdapter: AStream is nil');
FStream := AStream;
FOwnsStream := AOwnsStream;
// FPDF_FILEACCESS.m_FileLen is a 32-bit unsigned long. Refuse a stream
// that would silently truncate past 4 GiB.
if AStream.Size > High(FPDF_DWORD) then
raise EPdfError.Create('TPdfStreamAdapter: stream exceeds the 4 GiB limit');
FillChar(FFileAccess, SizeOf(FFileAccess), 0);
FFileAccess.m_FileLen := FPDF_DWORD(AStream.Size);
FFileAccess.m_GetBlock := GetBlockCallback;
FFileAccess.m_Param := Self;
end;
Zastavica lastništva odloči o tem, kdo sprosti tok. Posredujte False in klicatelj bo obdržal tok, poleg tega pa ga mora ohranjati pri življenju skozi celotno življenjsko dobo dokumenta. Posredujte True in nadzor prevzame adapter ter sprosti tok takrat, ko se dokument zapre. V obeh primerih mora tok preživeti vsako branje, ki ga bo PDFium izvedel, saj PDFium drži kazalec FPDF_FILEACCESS in bo vrnil klic kadarkoli medtem, ko bo dokument odprt, ne le med prvotnim nalaganjem
Zakaj je povratni klic statična funkcija
Povratni klic za branje, ki ga PDFium shrani v m_GetBlock, je preprost kazalec funkcije C s konvencijo klica cdecl. Neposredna uporaba metode Delphi ni mogoča, ker metoda prenaša skrit argument Self, o katerem C klicatelj ne ve ničesar in ga ne bo nikoli podal. Zato adapter razglasi povratni klic kot razredno funkcijo (class function), označeno kot cdecl; static, ki se prevede v samostojno funkcijo s postavitvijo okvira C, kakršnega PDFium pričakuje in brez implicitnega Self
To reši konvencijo klica, hkrati pa odpre drugo vprašanje: kako povratni klic brez Self sploh doseže določen tok, iz katerega naj bi bral? Odgovor se skriva v neprozornem uporabniškem parametru. Ko adapter zgradi zapis, shrani svoj lastni kazalec primerka v m_Param. PDFium nato vrne ta isti kazalec nazaj kot prvi argument vsakega povratnega klica. Statična funkcija ga prevede nazaj v obliki TPdfStreamAdapter ter pošlje branje toku tistega primerka. To je standarden postopek premeščanja konteksta objektov čez C mejo, ki pa nima koncepta o tem, kaj so objekti
// Verbatim from the component: the cdecl trampoline back to the instance
class function TPdfStreamAdapter.GetBlockCallback(
param : Pointer;
position: FPDF_DWORD;
pBuf : PByte;
size : FPDF_DWORD): Integer; cdecl;
var
Adapter: TPdfStreamAdapter;
begin
Result := 0;
if (param = nil) or (pBuf = nil) or (size = 0) then
Exit;
Adapter := TPdfStreamAdapter(param); // recover the instance from m_Param
if Adapter.FStream = nil then
Exit;
try
Adapter.FStream.Position := Int64(position);
Adapter.FStream.ReadBuffer(pBuf^, Int64(size));
Result := 1;
except
Result := 0; // report failure by return value, never by raising
end;
end;
Zgornja meja 4 GiB in zakaj je potrebna varovalka
Tu pa nastopi omejitev, omenjena v uvodu. Polje dolžine m_FileLen v strukturi FPDF_FILEACCESS je 32-bitna vrednost brez predznaka. Njegova največja predstavljiva dolžina je za en bajt krajša od 4 GiB. TStream prikaže svojo velikost kot Int64, zato lahko tok opiše precej več bajtov, kot jih polje lahko zadrži. V trenutku, ko velikost toka preseže to zgornjo mejo, PDFiumu ni več mogoče pošteno sporočiti resnične dolžine datoteke
Napačen odziv na to bi bil dodeliti velikost in dopustiti, da se prepolovi in zaobide (wrap). Obrezovanje dolžine 5 GiB na 32-bitno polje povzroči, da nastane majhna in na videz verjetna številka, nakar bo PDFium razčlenil datoteko prepričan, da se konča po približno enem gigabajtu. Priklopnik in tabela navzkrižnih referenc prebivata na dejanskem koncu datoteke in krepko za obrezano dolžino, zaradi česar razčlenjevanje spodleti na način, ki nima nobene zveze z resničnim vzrokom. Prijavili in odpravljali bi napako navzkrižne reference v datoteki, ki je sicer povsem veljavna, s tem pa ne bi imeli nobenega namiga, da se je določen integer prepolovil in zaobidel (wrap) dve ravni više
Vnos v tem primeru raje zavrne adapter. Konstruktor primerja velikost toka z High(FPDF_DWORD) in izroči EPdfError takoj tisti trenutek, ko je tok prevelik za opisovanje. Neposredna in jasna napaka poimenuje pravi problem na točki zgrajevanja. Tiho obrezovanje, na drugi strani, težavo skriva za zavajajočimi simptomi, katere bi najverjetneje poskusili poiskati šele kasneje. Omejitev 4 GiB velja za pravo omejitev te poti nalaganja, z njo pa se je seveda najbolje soočiti transparentno in jasno, namesto da bi skušali prepisati aritmetiko v upanju, da bo naključno uspešno izvedena. Ko arhiv dejansko prekorači mejo, pa se popravila, obljubljena na začetku, nahajajo zunaj tega API-ja: skeniranje je treba razdeliti na datoteke, kjer se pri vsaki posebej pazi na določeno zgornjo mejo, ali pa datoteko pustiti na disku ter do nje dostopati prek dizajna neposrednega dostopa (direct-access design), ki pa je osnovan na 64-bitnih odmikih namesto na strukturi FPDF_FILEACCESS
Neuspehi ne smejo preseči meje
Branje ne uspe vedno. Tok lahko predstavlja objekt, podprt z omrežjem, ki prekorači časovno omejitev, lahko je obravnava podatkov blob, ki se je skrito končala pod njim, lahko pa je datoteka, ki je bila po odprtju dokumenta obrezana. Kontrakt modula PDFium za bralni povratni klic (callback) je seveda povratna vrednost: ne-nič (non-zero) za uspeh, nič pa za neuspeh. Je okvir oblike C in s tem nima mehanizma, ki bi omogočal ulovitev ali širjenje določene izjeme pri Pascal
To pa je razlog, zaradi katerega iskanje in branje obravnavajo tako imenovane try/except oblike, ki pogoltnejo izjemo in tako vrnejo ničlo. Če bi bilo izjemi Delphija dopustno, da se razširi iz povratnega klica (callback), bi to povzročilo vrnitev nazaj po stopničkah pri strukturi cdecl od modula PDFium, ki pa ni bila nikoli načrtovana za odvijanje po navodilih izjeme Pascal. Rezultat za takšno obravnavo je seveda v najboljšem primeru nedefinirano dejanje ter seveda tudi hudo zrušenje celotnega sistema v najslabšem primeru. Prav zato takšno vrnitev k nuli ohranja izjemo (failure) znotraj oblike in jo preprečuje pred širjenjem. PDFium namreč prepozna neuspešno blokovsko branje in prijazno razveljavi samo operacijo, prav FPDF_LoadCustomDocument pa pri tem sporoča, da se določenega dokumenta ne more naložiti. Pri določenih primerih se celotna struktura obravnava in pojavi kot EPdfError pri Pascal strani, torej ravno pri strukturi in tja tudi logično pripada
Odpiranje dokumenta na ta način
Komponentna metoda za poganjanje te obravnavane oblike je metoda LoadCustomDocument in je pri tem opredeljena v prav posebni obliki, pa tudi kot metoda, in to ne v obliki LoadDocument za preobremenitev, saj posredovanje tako imenovane strukture TMemoryStream nikoli ni slučajno povezano z oblikovano obravnavo (buffered path). Postavi in zgradi prilagodilnik ter prikliče strukturo FPDF_LoadCustomDocument, adapter pa ohranja pri življenju skozi celotno dobo življenja vsebovanega dokumenta
var
Pdf: TPdf;
FileStream: TFileStream;
begin
Pdf := TPdf.Create(nil);
FileStream := TFileStream.Create('Archive_4GB.pdf', fmOpenRead or fmShareDenyWrite);
try
// Hand stream ownership to Pdf: it frees FileStream when the document closes.
Pdf.LoadCustomDocument(FileStream, True);
// PDFium has read only the trailer and catalog so far.
// Rendering a page pulls just that page's bytes through the callback.
// ... render or inspect pages here ...
finally
Pdf.Free; // closes the document, which frees the adapter and the stream
end;
end;
Istovrsten pa je sam postopek obravnave in tudi struktura za preobravnavo oblika modula, in sicer kot strukture TMemoryStream, pretok (blob stream) podatkov pa je izveden s posnetki zbirke in podatki po vzorcu določenega dedovanja modela TStream. Prav tako je izveden postopek nalaganja v obliki za primere, ko je namreč določena datoteka resnično obsežna, pa tudi če bo na primer prebrana le pri določenem, se pravi samo pri obravnavanem delu datoteke in podatkov. Ta oblika obravnave na koncu preostaja kot neke vrste arhivski pregledovalnik obravnave podatkov za tiste procese ustvarjanja primarnih prikaznih podob (thumbnailov) in je namenjena pregledovanju manj obsežnih podatkov (samo nekaj strani), izvajajo in nalagajo se naenkrat, posredovanje obravnave modula in preostala izvedba obravnave posameznih komponent in podatkov pa poteka le eno stran naenkrat in nobenih drugih primerov preostalih. Prav pri manjših posameznih podajah oblikovalnega vnašanja je določeno postopanje predhodnega medpomnilnega vnosa (buffered load) namreč veliko bolj učinkovito in preprosto pri obravnavi celotnih podatkov, saj potekajo z bralnim postopkom in takšna oblika je torej precej smotrnejša za takšno delovanje ter izvedbo predvidenih preobravnav in seveda takšnega vnašanja, pa premostitev pretočne oblike branja je ob tem seveda nesmiselna pri določenih obravnavah celotnih podatkov in podajanja. Pomembno in namreč glavno vprašanje pri obravnavanem vplivu strukture, se nanaša izključno na število določenih obravnavanih in posameznih bajtov in podajanja bajtov za samo razmerje izvedb in se z njo premešča prav takšna zasnova določenih datotek, seveda v izvedbi podatkovnih vnosov
Ko pa so tiste, ki so na obravnavi ali predvidenih določenih in načrtovanih izvedbah vključene pri posameznih nalaganjih in pa procesiranjih zahtev priklicane oziroma ko je pretok na zahtevo vzpostavljen, naslednja podaja seveda temelji namreč izključno k določenem varovanju podatkov pri določenih uporabniških vnašanjih in postopnih povečavah strukture ter obravnavanja. Gre za postopek in podajanje vnosov preko drsenja po strani (scrolanja) pri uporabniških vnašanjih. To namreč predstavlja zapis naš zapis o predpomnjenju za upodabljanje ter za učinkovitost približevanja oziroma povečav strukture. Kadar posamezen dokument predstavlja primerno branje izvedbe in tisti del procesnega in posredovalnega dela se mora seveda preprečiti, ne dovoliti branja podatkov od določenih spremenjenih datotek za izvažanje, potem si lahko preberete določene in potrebne nasvete o predhodni postopni izvedbi podatkovnega formata v tako imenovani obliki varovanja (security form), kar obravnavajo na uvodnem vodiču in varnem pregledu PDF strukture podatkov in ta del je tesno povezan pri posameznem postopnem izvajanju branja datotek v preostalih podatkov. Seveda, in se še kako upoštevajo predvsem za primere določenega pretočnega obravnavanja nalaganja in zgoraj omenjenih informativnih razlag in podatkovnega izročila za postopno, ki potekajo prek komponente PDFium Component PDFium Component, ki poteka in je vključeno pri Delphi ter modulu postavitvenega obravnavanja s C++Builder za posredovalne strukture upodabljanja pri tistih zapisih po modulu in blog objavah