Spajanje ili deljenje PDF datoteke veličine dva gigabajta na očigledan način košta vas dve stvari istovremeno: vremena izvršavanja i adresnog prostora. Očigledan način podrazumeva učitavanje svakog ulaza, obavljanje posla i upisivanje izlaza. Učitavanje je tačka u kojoj ovaj pristup otkazuje. Arhiva skeniranih dokumenata koja pređe sa 300 na 600 DPI duplira svoju linearnu rezoluciju i otprilike učetvorostručuje veličinu na disku, pa isti posao sklapanja koji je tokom cele godine bez problema obrađivao datoteke od 400 MB počinje da usporava sistem onog trenutka kada ulaz pređe jedan gigabajt, često čak i dok ne radi ništa osim brojanja stranica. Sam zadatak nije postao ništa teži. Otvaranje, brojanje, biranje opsega i nadovezivanje je sve što je potrebno. Učitavanje kompletnog stabla objekata jednostavno je prestalo da bude razumna podrazumevana opcija za datoteke te veličine. PDFlibPas, losLab PDF biblioteka za Delphi i C++Builder, rešava ovaj problem svojim slojem za direktan pristup (Direct Access): porodicom funkcija sa prefiksom DA koje podržava strimujući čitač koji analizira tabelu unakrsnih referenci (cross-reference table) na licu mesta, umesto da gradi ceo dokument u memoriji.
Gde odlazi memorija tokom potpunog učitavanja
Učitavanje PDF-a na „normalan” način podrazumeva analiziranje xref-a, razrešavanje svakog indirektnog objekta u memorijsko stablo, dešifrovanje tokova objekata i povezivanje stabla stranica, fontova i napomena u objekte kojima možete manipulisati. Za procese uređivanja sadržaja to je ispravan kompromis. Za poslove spajanja, deljenja i inspekcije to je uglavnom gubljenje resursa. Arhiva skeniranih dokumenata od 30.000 stranica može sadržati milione indirektnih objekata, dok posao deljenja treba da pročita samo nekoliko stotina njih: čvorove stranica u zahtevanom opsegu, plus sve ono na šta ti čvorovi upućuju.
Sloj za direktan pristup invertuje ovaj model. Funkcije DAOpenFile i DAOpenFileReadOnly analiziraju trejler i xref, nekoliko kilobajta na samom kraju datoteke, i vraćaju opisnik datoteke. Objekti se učitavaju lenjo (lazily), tek kada ih poziv zatraži. Praktična posledica je da otvaranje datoteke od više gigabajta traje skoro isto koliko i otvaranje male datoteke, a potrošnja memorije prati ono što dotičete, a ne ono što datoteka sadrži.
Ispitivanje ogromne datoteke bez njenog učitavanja
Šablon u nastavku potiče iz sopstvenog testa biblioteke za velike datoteke: otvori samo za čitanje, postavi pitanja, zatvori. Stablo dokumenta nikada ne nastaje.
var
Lib: TPDFlib;
Handle, Pages: Integer;
begin
Lib := TPDFlib.Create;
try
Handle := Lib.DAOpenFileReadOnly('archive-2025.pdf', '');
if Handle = 0 then
raise Exception.Create('Direct access open failed');
Pages := Lib.DAGetPageCount(Handle);
Writeln('pages : ', Pages);
Writeln('title : ', Lib.DAGetInformation(Handle, 'Title'));
Lib.DACloseFile(Handle);
finally
Lib.Free;
end;
end;
Režim samo za čitanje (read-only) vredi preferirati kad god je to moguće: on omogućava pokretanje faze prihvata dok drugi procesi drže datoteku i jasno dokumentuje nameru. Faza ispitivanja koja slučajno pozove funkciju koja menja stanje će brzo otkazati umesto da ošteti arhivu.
PageRef je opisnik objekta, a ne broj stranice
Najčešća greška pri radu sa DA API-jem je prosleđivanje broja stranice tamo gde funkcija očekuje PageRef. Skoro svaki DA poziv po stranici prima referentni opisnik objekta stranice umesto broja stranice: DAExtractPageText, DARenderPageToFile, DARotatePage i DACapturePage očekuju referencu (ref). Nju dobijate prevođenjem broja koji vidi korisnik pomoću funkcije DAFindPage:
PageRef := Lib.DAFindPage(Handle, 250); // page number -> object handle
if PageRef <> 0 then
begin
Text := Lib.DAExtractPageText(Handle, PageRef, 0);
Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;
Prosleđivanje sirovog broja 250 umesto reference ne podiže grešku. Ono se obraća bilo kom objektu koji se slučajno nalazi iza te vrednosti opisnika, što u boljem slučaju otkazuje vidno, a u gorem izvlači tekst sa pogrešne stranice u dokument koji ide klijentu. Ako obavijate DA sloj u sopstveni servisni kod, učinite ovo prevođenje nezaobilaznim: prihvatajte brojeve stranica na granici interfejsa, odmah pozovite DAFindPage i interno prenosite samo reference.
Spajanje na stotine datoteka pomoću imenovane liste
Za dve datoteke dovoljna je funkcija MergeFiles(First, Second, Output). Grupno sklapanje skalira se znatno bolje kroz liste datoteka: registrujte ulaze pod imenom liste, a zatim spojite listu u jednom prolazu.
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');
// Verify the result the cheap way: direct access again
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);
Porodica funkcija za spajanje ima tri varijante, a razlika nije samo u brzini. Funkcija MergeFileListFast preskače očuvanje stabla strukture; MergeFileListStrict nameće strogi režim (strict mode); verzija bez sufiksa je uravnotežena i podrazumevana opcija. Operativno pravilo koje iz toga proizilazi: ako je bilo koji ulaz Tagged PDF čija struktura pristupačnosti mora preživeti – pri čemu je izlaz za PDF/UA očigledan primer – koristite podrazumevanu ili Strict varijantu, jer Fast tiho odbacuje stablo strukture. Za obične arhive skeniranih dokumenata bez tagovanja, Fast donosi čiste performanse. Odlučujte o ovome po procesu obrade (pipeline), a ne po trenutnom raspoloženju programera, i zabeležite korišćenu varijantu u dnevnik rada (job log).
Deljenje bez učitavanja: ekstrakcija opsega
Deljenje prati istu filozofiju bez učitavanja u memoriju. Funkcija ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) izvlači opseg stranica direktno iz datoteke u datoteku, koristeći listu opsega kao što su '1-500', '501-1000' ili zarezom odvojene selekcije, pri čemu izvor nikada ne postaje stablo dokumenta. Kada je dokument već učitan iz drugih razloga, ExtractPageRanges proizvodi novi dokument u memoriji od trenutnog, dok CopyPageRanges kopira opsege iz drugog učitanog dokumenta na osnovu ID-ja. Za deljenje objedinjenih štamparskih tokova po izvodima, forma iz datoteke u datoteku je ta koja sprečava da se ulaz od 4 GB ikada učita u RAM.
Datoteke koje lažu o svojoj geometriji
Procesi obrade velikih datoteka nailaze na oštećene datoteke u meri koju procesi sa malim datotekama nikada ne vide, jednostavno zato što ulazi prolaze kroz više sistema. Dva oblika kvara zaslužuju eksplicitno rukovanje.
Prvo, pomerena zaglavlja (shifted headers). Mrežni prolazi za e-poštu i spuleri za štampu ponekad dodaju bajtove na početak PDF-a, tako da se marker %PDF više ne nalazi na ofsetu 0, pa je svaki ofset u tabeli unakrsnih referenci u datoteci pogrešan za isti taj iznos. Strimujući čitač detektuje ovu pojavu i izlaže je (svojstvo DAShiftedHeader na ravnom nivou, odnosno ShiftedHeader u TSmartPDFReader-u), a zatim kompenzuje tu razliku tokom čitanja. Kućno razvijena aritmetika ofseta obično to ne radi, što je razlog zašto je simptom „radi na svakoj datoteci koju mi generišemo, a otkazuje na datotekama od klijenta X” klasičan primer ovog problema.
Drugo, oštećene tabele unakrsnih referenci. Funkcija DACopyFile(InputFileName, OutputFileName, PageCount) strimuje celu datoteku u novu kopiju dok ponovo gradi xref, vraćajući broj stranica kao nusproizvod. Pokretanje ove funkcije kao faze normalizacije pre nekog zahtevnog nizvodnog potrošača pretvara klasu povremenih grešaka u analiziranju u jedan predvidljiv korak popravke. A kada je potrebno sačuvati sopstvene izmene, DAAppendFile ih upisuje kao inkrementalno ažuriranje, dodajući novu reviziju umesto da ponovo upisuje gigabajte, čime se trošak čuvanja drži proporcionalnim izmeni, a ne veličini datoteke.
Detalji isporuke: linearizacija i kompozicija
Dve prateće mogućnosti zaokružuju proces obrade velikih datoteka. Kada se sklopljeni izlaz isporučuje preko HTTP-a za pregled u čitaču, LinearizeFile ga reorganizuje za strimovanje opsega bajtova (byte-range streaming) kako bi se prva stranica prikazala pre nego što se preuzimanje ostatka paketa od 500 MB završi. Pokrenite je kao poslednju fazu, nakon svih spajanja, jer svaka kasnija izmena de-linearizuje datoteku. A kada paketi zahtevaju kompoziciju umesto običnog nadovezivanja, na primer, naslovna strana utisnuta iza svakog izvoda ili dve izvorne stranice smeštene na jedan izlazni list, DACapturePage pretvara bilo koju stranicu u šablon za višekratnu upotrebu koji DADrawCapturedPage postavlja na odredišnu stranicu u okviru proizvoljnog pravougaonika, i dalje bez potpunog učitavanja dokumenta na izvoru od više gigabajta.
Ograničenja i šta ostaje samo za čitanje
Sam format ostaje bez prostora znatno pre nego što ga Direct Access potroši. Ofseti su Int64 duž celog DA sloja, tako da su stvarna ograničenja dostupan prostor na disku i 10-cifreno polje ofseta u tabeli unakrsnih referenci kod klasičnih (non-stream) tabela unakrsnih referenci. Arhive skeniranih dokumenata od više gigabajta su uobičajene u praksi, a memorija ostaje ograničena bez obzira na veličinu datoteke jer se objekti čitaju samo kada ih poziv zatraži.
Dva pitanja se pojavljuju dovoljno često da bismo na njih odgovorili direktno. Spajanje kroz podrazumevanu putanju prenosi strukturu dokumenta, pa obeleživači (bookmarks) i veze (links) preživljavaju; Fast varijanta je ta koja žrtvuje stablo strukture radi brzine, što je jedini razlog zašto je treba rezervisati za netagovane ulaze. Sigurna navika je da otvorite spojeni izlaz, prođete kroz njegovu strukturu i proverite nekoliko internih veza pre nego što ga isporučite. Što se tiče uređivanja: postoji koristan kompromis između ispitivanja samo za čitanje i potpunog učitavanja. Operacije na nivou stranice rade direktno sa opisnikom, uključujući DARotatePage, DAMovePage i DAHidePage, kao i čitanje polja obrazaca, dok DAAppendFile čuva te izmene kao inkrementalnu reviziju. Uređivanje na nivou sadržaja, odnosno sve što prepisuje operatore crtanja unutar stranice, i dalje pripada nivou celog dokumenta.
Povezani članci
Ako vaš spojeni izlaz mora ostati pristupačan, pozadina stabla strukture je pokrivena u članku o pristupačnosti Tagged PDF-a, koji objašnjava šta tačno Fast varijanta spajanja odbacuje. Za izvlačenje sadržaja iz opsega koje ste podelili, pogledajte vodič za ekstrakciju teksta, slika i fontova.
Kompletna lista funkcija Direct Access isporučuje se uz biblioteku; izdanja i probne verzije dostupne su na PDFlibPas stranici proizvoda.