Spajanje ili dijeljenje PDF-a od dva gigabajta na očigledan način košta vas dviju stvari odjednom: vremena izvođenja i adresnog prostora. Očigledan način je učitavanje svakog ulaza, obavljanje posla i zapisivanje izlaza. Učitavanje je točka u kojoj stvar propada. Skenirana arhiva koja prelazi s 300 na 600 DPI udvostručuje svoju linearnu razlučivost i otprilike se učetverostručuje na disku, pa isti posao sklapanja koji je cijele godine rješavao datoteke od 400 MB počinje usporavati rad u trenutku kada ulaz prijeđe gigabajt, često ne radeći ništa više od brojanja stranica. Zadatak nikada nije postao teži. Otvaranje, brojanje, odabir raspona i spajanje je sve što radite. Učitavanje cijelog stabla jednostavno je prestalo biti razumno zadano ponašanje pri toj veličini. PDFlibPas, losLab-ova PDF knjižnica za Delphi i C++Builder, rješava to svojim slojem izravnog pristupa (Direct Access): obitelji funkcija s prefiksom DA, podržanih čitačem strujanja koji prolazi kroz tablicu unakrsnih referenci (xref) na licu mjesta, umjesto izgradnje cijelog dokumenta u memoriji.
Kamo odlazi memorija pri potpunom učitavanju
Učitavanje PDF-a "normalno" znači raščlanjivanje xref-a, razrješavanje svakog neizravnog objekta u stablo u memoriji, dekodiranje tokova objekata i povezivanje stabla stranica, fontova i bilješki u objekte kojima možete manipulirati. Za tijekove rada uređivanja to je ispravna razmjena. Za poslove spajanja, dijeljenja i inspekcije to je uglavnom uzaludno trošenje resursa. Skenirana arhiva od 30.000 stranica može sadržavati milijune neizravnih objekata, a posao dijeljenja treba pročitati samo nekoliko stotina njih: čvorove stranica u zatraženom rasponu i ono na što ti čvorovi upućuju.
Sloj izravnog pristupa okreće model. DAOpenFile i DAOpenFileReadOnly raščlanjuju trailer i xref, nekoliko kilobajta na kraju datoteke, i vraćaju ručku datoteke. Objekti se dohvaćaju lijeno (lazy loading) kada ih poziv zatreba. Praktična posljedica je da otvaranje datoteke od nekoliko gigabajta traje otprilike jednako dugo kao i otvaranje male datoteke, a memorija prati ono što dotičete, a ne ono što datoteka sadrži.
Ispitivanje goleme datoteke bez učitavanja
Uzorak u nastavku dolazi iz testa performansi same knjižnice s velikim datotekama: otvorite samo za čitanje, postavite upite, zatvorite. 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;
Način rada samo za čitanje (read-only) vrijedi odabrati kad god možete: omogućuje izvođenje faze unosa dok drugi procesi drže datoteku i dokumentira namjeru. Faza ispitivanja koja slučajno pozove funkciju koja mijenja podatke brzo ne uspijeva, umjesto da ošteti arhivu.
PageRef je ručka objekta, a ne broj stranice
Najčešća pogreška s DA API-jem je prosljeđivanje broja stranice tamo gdje funkcija očekuje PageRef. Gotovo svaki DA poziv po stranici prima referentnu ručku objekta stranice umjesto broja stranice: DAExtractPageText, DARenderPageToFile, DARotatePage i DACapturePage očekuju referencu. Dobivate je prevođenjem broja koji vidi korisnik pomoću 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;
Prosljeđivanje sirovog broja 250 umjesto toga ne podiže pogrešku. Ono se obraća bilo kojem objektu koji se slučajno nalazi iza te vrijednosti ručke, što u dobrom slučaju ne uspijeva vidljivo, a u lošem slučaju izdvaja tekst s pogrešne stranice u dokument namijenjen korisniku. Ako omotate DA sloj u vlastiti kod usluge, učinite prevođenje nemogućim za preskakanje: prihvatite brojeve stranica na granici, odmah pozovite DAFindPage i interno prenosite samo reference.
Spajanje stotina datoteka pomoću imenovanog popisa
Za dvije datoteke dovoljno je MergeFiles(First, Second, Output). Skupno sklapanje bolje se skalira kroz popise datoteka: registrirajte ulaze pod nazivom popisa, a zatim spojite popis 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);
Obitelj funkcija za spajanje ima tri varijante, a razlika nije samo u brzini. MergeFileListFast preskače očuvanje stabla strukture; MergeFileListStrict provodi strogi način rada; verzija bez sufiksa je uravnotežena zadana postavka. Operativno pravilo koje proizlazi: ako je bilo koji ulaz označeni PDF (Tagged PDF) čija struktura pristupačnosti mora preživjeti, pri čemu je očigledan slučaj bilo što proizvedeno za PDF/UA, posegnite za zadanom ili Strict varijantom, jer Fast tiho odbacuje stablo strukture. Za obične skenirane arhive bez označavanja, Fast pruža besplatne performanse. Odlučite po cjevovodu, a ne po raspoloženju programera, i zabilježite korištenu varijantu u dnevnik poslova.
Dijeljenje bez učitavanja: ekstrakcija raspona
Dijeljenje slijedi istu filozofiju bez učitavanja. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) povlači raspon stranica izravno iz datoteke u datoteku, s popisom raspona kao što su '1-500', '501-1000' ili odabirima odvojenim zarezima, a izvor nikada ne postaje stablo dokumenta. Kada je dokument već učitan iz drugih razloga, ExtractPageRanges proizvodi novi dokument u memoriji iz trenutnog, a CopyPageRanges povlači raspone iz drugog učitanog dokumenta prema ID-u. Za dijeljenje konsolidiranih ispisa po izvodima, oblik datoteke u datoteku je onaj koji sprječava da se ulaz od 4 GB ikada napuše u radnoj memoriji.
Datoteke koje lažu o svojoj geometriji
Cjevovodi za velike datoteke susreću oštećene datoteke po stopi koju cjevovodi za male datoteke nikada ne vide, jednostavno zato što ulazi prolaze kroz više sustava. Dva oblika neuspjeha zaslužuju eksplicitno rukovanje.
Prvo, pomaknuta zaglavlja. Poštanski poslužitelji i redovi čekanja za ispis ponekad dodaju bajtove na početak PDF-a, tako da marker %PDF više ne leži na pomaku 0 i svaki pomak xref-a u datoteci je pogrešan za isti iznos. Čitač strujanja to otkriva i izlaže (DAShiftedHeader na ravnoj razini, ShiftedHeader na TSmartPDFReader), a zatim to kompenzira tijekom čitanja. Vlastita aritmetika pomaka to obično ne radi, zbog čega je "radi na svakoj datoteci koju generiramo, ne uspijeva na datotekama od kupca X" klasičan simptom.
Drugo, oštećene tablice unakrsnih referenci. DACopyFile(InputFileName, OutputFileName, PageCount) usmjerava cijelu datoteku u novu kopiju dok ponovno izgrađuje xref, vraćajući broj stranica kao nusproizvod. Pokretanje ovog koraka kao faze normalizacije ispred izbirljivog nizvodnog potrošača pretvara klasu povremenih pogrešaka raščlanjivanja u jedan predvidljiv korak popravka. A kada vaše vlastite izmjene trebaju spremanje, DAAppendFile ih zapisuje kao inkrementalno ažuriranje, dodajući novu reviziju umjesto prepisivanja gigabajta, što trošak spremanja drži proporcionalnim promjeni umjesto datoteci.
Detalji isporuke: linearizacija i kompozicija
Dvije srodne mogućnosti zaokružuju cjevovod za velike datoteke. Kada se prikupljeni izlaz poslužuje putem HTTP-a za pregled u pregledniku, LinearizeFile ga reorganizira za strujanje raspona bajtova tako da se prva stranica prikazuje prije nego što se završi preuzimanje ostatka paketa od 500 MB. Pokrenite to kao završnu fazu, nakon svih spajanja, jer svaka kasnija izmjena ponovno de-linearizira datoteku. A kada paketi trebaju kompoziciju umjesto običnog spajanja, recimo naslovnu stranicu otisnutu iza svakog izvoda ili dvije izvorne stranice nametnute na jedan izlazni list, DACapturePage pretvara bilo koju stranicu u predložak za višekratnu upotrebu koji DADrawCapturedPage postavlja na odredišnu stranicu na proizvoljnom pravokutniku, i dalje bez potpunog učitavanja dokumenta na izvoru od više gigabajta.
Ograničenja i što ostaje samo za čitanje
Sam format ostaje bez prostora puno prije nego Direct Access. Pomaci su tipa Int64 kroz cijeli DA sloj, pa su stvarni stropovi dostupni disk i 10-znamenkasto polje pomaka xref-a klasičnih (ne-tokovnih) tablica unakrsnih referenci. Arhive skeniranja od više gigabajta uobičajene su 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 izravno odgovorili. Spajanje kroz zadani put prenosi strukturu dokumenta, tako da oznake i poveznice preživljavaju; Fast varijanta je ona koja mijenja stablo strukture za brzinu, što je jedini razlog za njezino rezerviranje za neoznačene ulaze. Sigurna je navika otvoriti spojeni izlaz, prošetati njegovim stablom oznaka i provjeriti nekoliko internih poveznica prije isporuke. Što se tiče uređivanja: postoji korisna sredina između ispitivanja samo za čitanje i potpunog učitavanja. Operacije na razini stranice rade izravno na ručki, uključujući DARotatePage, DAMovePage i DAHidePage, zajedno s čitanjem polja obrazaca, a DAAppendFile trajno sprema te izmjene kao inkrementalnu reviziju. Uređivanje na razini sadržaja – sve što ponovno zapisuje operatore označavanja unutar stranice – i dalje pripada punom sloju dokumenta.
Povezani članci
Ako vaš spojeni izlaz mora ostati pristupačan, pozadina stabla strukture pokrivena je u članku o pristupačnosti označenog PDF-a, koji objašnjava točno što bi Fast varijanta spajanja odbacila. Za izvlačenje sadržaja iz raspona koje podijelite, pogledajte vodič za ekstrakciju teksta, slika i fontova.
Cjeloviti popis funkcija Direct Access dolazi s knjižnicom; izdanja i probna preuzimanja nalaze se na PDFlibPas stranici proizvoda.