Combinarea sau împărțirea unui PDF de doi gigaocteți în modul evident costă două lucruri simultan: timp de execuție și spațiu de adresare. Modul evident este să încarci fiecare intrare, să faci operația, să scrii ieșirea. Încărcarea este punctul unde totul se strică. O arhivă de scanări care trece de la 300 la 600 DPI își dublează rezoluția liniară și își cvadruplează aproximativ dimensiunea pe disc, așa că aceeași operație de asamblare care a gestionat fișiere de 400 MB tot anul începe să se blocheze de îndată ce o intrare depășește un gigaoctet, adesea în timp ce nu face altceva decât să numere pagini. Sarcina nu a devenit mai grea. A deschide, a număra, a alege intervale, a concatena — asta e tot. Încărcarea completă a arborelui pur și simplu a încetat să mai fie o valoare implicită rezonabilă la această dimensiune. PDF Library for Delphi, biblioteca PDF losLab pentru Delphi și C++Builder, răspunde la asta prin stratul său Direct Access: o familie de funcții cu prefixul DA susținute de un cititor cu flux (streaming) care parcurge tabelul de referințe încrucișate (cross-reference) pe loc, în loc să construiască întregul document în memorie
Unde se duce memoria la o încărcare completă
Încărcarea „normală” a unui PDF înseamnă analiza xref, rezolvarea fiecărui obiect indirect într-un arbore în memorie, decodarea fluxurilor de obiecte (object streams) și conectarea arborelui de pagini, a fonturilor și a adnotărilor în obiecte pe care le poți manipula. Pentru fluxurile de editare, acesta este compromisul corect. Pentru lucrări de combinare, împărțire și inspecție, este în mare parte risipă. O arhivă de scanări cu 30.000 de pagini poate conține milioane de obiecte indirecte, iar o operație de împărțire trebuie să citească doar câteva sute dintre ele: nodurile de pagină din intervalul cerut, plus tot ce referențiază acele noduri
Stratul Direct Access inversează acest model. DAOpenFile și DAOpenFileReadOnly analizează trailer-ul și xref, doar câțiva kiloocteți de la finalul fișierului, și returnează un handle de fișier. Obiectele sunt preluate leneș (lazy) doar când un apel are nevoie de ele. Consecința practică este că deschiderea unui fișier de mai mulți gigaocteți durează cam la fel de mult ca deschiderea unuia mic, iar memoria urmărește ce atingi, nu ce conține fișierul
Investigarea unui fișier uriaș fără a-l încărca
Modelul de mai jos provine din propriul test de performanță (benchmark) al bibliotecii pentru fișiere mari: deschide doar-citire, pune întrebări, închide. Nu există niciodată un arbore de document
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;
Modul doar-citire merită preferat ori de câte ori este posibil: permite etapei de preluare (intake) să ruleze în timp ce alte procese dețin fișierul și documentează intenția. O etapă de investigare care apelează din greșeală o funcție care modifică date eșuează rapid, în loc să corupă arhiva
PageRef este un handle de obiect, nu un număr de pagină
Cea mai frecventă greșeală cu API-ul DA este transmiterea unui număr de pagină acolo unde o funcție așteaptă un PageRef. Aproape fiecare apel DA per pagină ia un handle de referință către obiectul pagină, nu un număr de pagină: DAExtractPageText, DARenderPageToFile, DARotatePage și DACapturePage așteaptă toate o referință (ref). Obții una traducând numărul vizibil pentru utilizator prin DAFindPage:
PageRef := Lib.DAFindPage(Handle, 250); // număr de pagină -> handle obiect
if PageRef <> 0 then
begin
Text := Lib.DAExtractPageText(Handle, PageRef, 0);
Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;
Transmiterea numărului brut 250 în loc de atât nu generează o eroare. Se adresează oricărui obiect care se întâmplă să stea în spatele acelei valori de handle, ceea ce într-o zi bună eșuează vizibil, iar într-o zi proastă extrage text din pagina greșită într-un document destinat clientului. Dacă înfășori stratul DA în propriul cod de serviciu, fă imposibil de omis traducerea: acceptă numere de pagină la graniță, apelează imediat DAFindPage și transmite doar referințe (refs) intern
Combinarea a sute de fișiere cu o listă numită
Pentru două fișiere, MergeFiles(First, Second, Output) este suficient. Asamblarea în loturi (batch) scalează mai bine prin liste de fișiere: înregistrezi intrările sub un nume de listă, apoi combini lista într-o singură trecere
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');
// Verifică rezultatul în mod ieftin: din nou acces direct
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);
Familia merge are trei variante, iar diferența nu este doar viteza. MergeFileListFast omite păstrarea arborelui de structură; MergeFileListStrict impune modul strict; versiunea fără sufix este implicita echilibrată. Regula operațională care rezultă: dacă orice intrare este un Tagged PDF a cărui structură de accesibilitate trebuie să supraviețuiască — cazul evident fiind cel destinat PDF/UA — alege varianta implicită sau Strict, deoarece Fast elimină silențios arborele de structură. Pentru arhive de scanări simple, fără etichetare (tagging), Fast este performanță gratuită. Decide per pipeline, nu după starea de spirit a dezvoltatorului, și înregistrează varianta folosită în jurnalul lucrării
Împărțirea fără încărcare: extragerea de intervale
Împărțirea urmează aceeași filozofie fără încărcare. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) extrage un interval de pagini direct din fișier în fișier, cu o listă de intervale precum '1-500', '501-1000' sau selecții separate prin virgulă, iar sursa nu devine niciodată un arbore de document. Când un document este deja încărcat din alte motive, ExtractPageRanges produce un nou document în memorie pornind de la cel curent, iar CopyPageRanges preia intervale dintr-un alt document încărcat, după ID. Pentru împărțirea pe extras de cont a fluxurilor de tipărire consolidate, forma fișier-în-fișier este cea care împiedică o intrare de 4 GB să se umfle vreodată în memoria RAM
Fișiere care mint în privința geometriei lor
Pipeline-urile pentru fișiere mari întâlnesc fișiere deteriorate la o rată pe care pipeline-urile pentru fișiere mici nu o văd niciodată, pur și simplu pentru că intrările trec prin mai multe sisteme. Două tipare de eșec merită tratate explicit
Primul, anteturi deplasate. Porțile de mail și spoolerele de tipărire uneori adaugă octeți la începutul unui PDF, astfel încât marcajul %PDF nu mai stă la offsetul 0, iar fiecare offset xref din fișier este greșit cu aceeași cantitate. Cititorul cu flux detectează acest lucru și îl expune (DAShiftedHeader la nivelul plat, ShiftedHeader pe TSmartPDFReader), apoi compensează pentru el în timpul citirilor. Aritmetica de offset făcută manual de obicei nu face asta, motiv pentru care „funcționează pe fiecare fișier pe care îl generăm noi, eșuează pe fișierele de la clientul X” este simptomul clasic
Al doilea, tabele de referințe încrucișate corupte. DACopyFile(InputFileName, OutputFileName, PageCount) transmite (streams) întregul fișier către o copie nouă, reconstruind în același timp xref-ul și returnând numărul de pagini ca produs secundar. Rularea sa ca etapă de normalizare în fața unui consumator din aval pretențios transformă o clasă de eșecuri intermitente de analiză într-un singur pas de reparare previzibil. Iar când propriile tale editări trebuie salvate, DAAppendFile le scrie ca o actualizare incrementală, adăugând o nouă revizie în loc să rescrie gigaocteți întregi, ceea ce menține costul salvării proporțional cu modificarea, nu cu fișierul
Detalii de livrare: liniarizare și compunere
Două capabilități adiacente completează un pipeline pentru fișiere mari. Când ieșirea asamblată este servită prin HTTP pentru vizualizare în browser, LinearizeFile o reorganizează pentru streaming pe intervale de octeți (byte-range), astfel încât prima pagină se afișează înainte ca restul unui pachet de 500 MB să termine descărcarea. Rulează-l ca etapă finală, după toate combinările, deoarece orice modificare ulterioară deliniarizează din nou fișierul. Iar când pachetele trebuie compuse, nu doar concatenate simplu — să zicem o copertă ștampilată în spatele fiecărui extras de cont sau două pagini sursă impuse pe o singură pagină de ieșire — DACapturePage transformă orice pagină într-un șablon reutilizabil pe care DADrawCapturedPage îl plasează pe o pagină de destinație într-un dreptunghi arbitrar, tot fără o încărcare completă a documentului sursă de mai mulți gigaocteți
Limite și ce rămâne doar-citire
Formatul în sine rămâne fără spațiu cu mult înainte ca Direct Access să o facă. Offseturile sunt Int64 pe tot parcursul stratului DA, așa că plafoanele reale sunt spațiul de disc disponibil și câmpul de offset xref cu 10 cifre al tabelelor de referințe încrucișate clasice (non-stream). Arhivele de scanări de mai mulți gigaocteți sunt banale în practică, iar memoria rămâne mărginită indiferent de dimensiunea fișierului, pentru că obiectele sunt citite doar când un apel le cere
Două întrebări apar suficient de des încât să merite un răspuns direct. Combinarea prin calea implicită păstrează structura documentului, așa că semnele de carte (bookmarks) și legăturile supraviețuiesc; varianta Fast este cea care schimbă arborele de structură pentru viteză, ceea ce este întregul motiv pentru care se rezervă intrărilor neetichetate. Obiceiul sigur este să deschizi ieșirea combinată, să parcurgi structura ei de tip outline și să verifici punctual câteva legături interne înainte de a o livra. Cât despre editare: există o zonă intermediară utilă între investigarea doar-citire și o încărcare completă. Operațiile la nivel de pagină funcționează direct pe handle — DARotatePage, DAMovePage și DAHidePage printre ele — împreună cu citirile de câmpuri de formular, iar DAAppendFile persistă acele editări ca o revizie incrementală. Editarea la nivel de conținut, orice rescrie operatorii de marcare din interiorul unei pagini, aparține în continuare stratului de document complet
Articole conexe
Dacă ieșirea combinată trebuie să rămână accesibilă, contextul despre arborele de structură este acoperit în articolul despre accesibilitatea Tagged PDF, care explică exact ce ar elimina varianta de combinare Fast. Pentru extragerea conținutului din intervalele pe care le-ai împărțit, vezi ghidul de extragere a textului, imaginilor și fonturilor
Lista completă a funcțiilor Direct Access vine împreună cu biblioteca; edițiile și descărcările de probă sunt pe pagina produsului PDF Library for Delphi