Fondere o dividere un PDF di due gigabyte nel modo ovvio ti costa due cose in una sola volta: tempo di wall-clock e spazio di indirizzamento. Il modo ovvio è caricare ciascun input, fare il lavoro, scrivere l'output. Il caricamento è dove si rompe. Un archivio di scansioni che passa da 300 a 600 DPI raddoppia la sua risoluzione lineare e all'incirca quadruplica su disco, così lo stesso lavoro di assemblaggio che ha gestito file da 400 MB tutto l'anno inizia a fare thrashing nel momento in cui un input supera un gigabyte, spesso senza fare nulla di più che contare le pagine. Il compito non è mai diventato più difficile. Apri, conta, scegli range, concatena è tutto qui. Il caricamento dell'albero completo ha semplicemente smesso di essere un default sensato a quella dimensione. PDF Library for Delphi, la libreria PDF di losLab per Delphi e C++Builder, risponde a questo con il suo strato Direct Access: una famiglia di funzioni con prefisso DA supportate da un lettore in flusso che percorre la tabella di cross-reference sul posto invece di costruire l'intero documento in memoria
Dove va la memoria in un caricamento completo
Caricare un PDF "normalmente" significa analizzare l'xref, risolvere ciascun oggetto indiretto in un albero in memoria, decodificare i flussi di oggetto, e cablare l'albero delle pagine, i font e le annotazioni in oggetti che puoi manipolare. Per i flussi di lavoro di editing è lo scambio giusto. Per il lavoro di fusione, divisione e ispezione è per lo più spreco. Un archivio di scansioni da 30.000 pagine potrebbe contenere milioni di oggetti indiretti, e un lavoro di divisione ha bisogno di leggere poche centinaia di essi: i nodi di pagina nel range richiesto, più qualunque cosa quei nodi referenzino
Lo strato Direct Access inverte il modello. DAOpenFile e DAOpenFileReadOnly analizzano il trailer e l'xref, pochi kilobyte in coda al file, e restituiscono un handle di file. Gli oggetti sono recuperati pigramente quando una chiamata li richiede. La conseguenza pratica è che aprire un file multi-gigabyte richiede all'incirca lo stesso tempo che aprirne uno piccolo, e la memoria traccia ciò che tocchi invece di ciò che il file contiene
Sondare un file enorme senza caricarlo
Il pattern sotto viene dal benchmark di file grandi della stessa libreria: apri in sola lettura, fai domande, chiudi. Nessun albero di documento esiste mai
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;
La modalità sola lettura vale la pena preferirla quando puoi: lascia che lo stadio di intake giri mentre altri processi detengono il file, e documenta l'intento. Uno stadio di sondaggio che chiama accidentalmente una funzione mutante fallisce velocemente invece di corrompere l'archivio
PageRef è un handle di oggetto, non un numero di pagina
L'errore singolo più comune con l'API DA è passare un numero di pagina dove una funzione si aspetta un PageRef. Quasi ogni chiamata DA per pagina prende un handle di riferimento all'oggetto pagina invece di un numero di pagina: DAExtractPageText, DARenderPageToFile, DARotatePage e DACapturePage si aspettano tutti una ref. Ne ottieni una traducendo il numero orientato-all'umano attraverso 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;
Passare il numero grezzo 250 invece non solleva un errore. Indirizza qualunque oggetto che capita sieda dietro quel valore di handle, che in un buon giorno fallisce visibilmente e in un cattivo giorno estrae testo dalla pagina sbagliata in un documento rivolto-al-cliente. Se avvolgi lo strato DA nel tuo codice di servizio, rendi la traduzione impossibile da saltare: accetta numeri di pagina alla frontiera, chiama DAFindPage immediatamente, e passa solo ref internamente
Fondere centinaia di file con una lista con nome
Per due file, MergeFiles(First, Second, Output) basta. L'assemblaggio batch scala meglio attraverso liste di file: registra gli input sotto un nome di lista, poi fondi la lista in un solo passaggio
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');
// Verifica il risultato nel modo economico: di nuovo accesso diretto
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);
La famiglia di fusione ha tre varianti, e la differenza non è solo velocità. MergeFileListFast salta la preservazione dell'albero di struttura; MergeFileListStrict applica la modalità rigorosa; la versione senza suffisso è il default equilibrato. La regola operativa che ne deriva: se un qualsiasi input è un PDF taggato la cui struttura di accessibilità deve sopravvivere, qualunque cosa prodotta per PDF/UA essendo il caso ovvio, ricorri al default o alla variante Strict, perché Fast silenziosamente scarta l'albero di struttura. Per archivi di scansioni semplici senza tagging, Fast è prestazione gratis. Decidi per pipeline, non per umore di sviluppatore, e registra la variante usata nel log del lavoro
Dividere senza caricare: estrazione per range
La divisione segue la stessa filosofia senza-caricamento. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) estrae un range di pagine dritto da file a file, con una lista di range come '1-500', '501-1000', o selezioni separate da virgola, e la fonte non diventa mai un albero di documento. Quando un documento è già caricato per altre ragioni, ExtractPageRanges produce un nuovo documento in memoria da quello attuale, e CopyPageRanges estrae range da un altro documento caricato per ID. Per la divisione per-fattura di flussi di stampa consolidati, la forma file-a-file è quella che mantiene un input di 4 GB dal gonfiarsi mai in RAM
File che mentono sulla loro geometria
Le pipeline di file grandi incontrano file danneggiati a un tasso che le pipeline di file piccoli non vedono mai, semplicemente perché gli input passano attraverso più sistemi. Due forme di fallimento meritano gestione esplicita
Primo, intestazioni spostate. I gateway di posta e gli spooler di stampa a volte antepongono byte a un PDF, così il marcatore %PDF non siede più all'offset 0 e ogni offset xref nel file è sbagliato della stessa quantità. Il lettore in flusso lo rileva e lo espone (DAShiftedHeader al livello piatto, ShiftedHeader su TSmartPDFReader), poi lo compensa durante le letture. L'aritmetica degli offset fatta in casa tipicamente non lo fa, che è perché "funziona su ogni file che generiamo, fallisce sui file del cliente X" è il sintomo classico
Secondo, tabelle di cross-reference rotte. DACopyFile(InputFileName, OutputFileName, PageCount) fa fluire l'intero file in una nuova copia mentre ricostruisce l'xref, restituendo il conteggio delle pagine come sottoprodotto. Eseguirlo come stadio di normalizzazione davanti a un consumatore a valle esigente converte una classe di fallimenti di analisi intermittenti in un solo passo di riparazione prevedibile. E quando le tue stesse modifiche hanno bisogno di essere salvate, DAAppendFile le scrive come un aggiornamento incrementale, accodando una nuova revisione invece di riscrivere gigabyte, che mantiene il costo del salvataggio proporzionale alla modifica invece che al file
Dettagli di consegna: linearizzazione e composizione
Due capacità adiacenti completano una pipeline di file grandi. Quando l'output assemblato è servito su HTTP per la visualizzazione nel browser, LinearizeFile lo riorganizza per lo streaming a range di byte così la prima pagina si mostra prima che il resto di un pacchetto da 500 MB abbia finito di scaricare. Eseguilo come stadio finale, dopo tutta la fusione, perché qualsiasi modifica successiva de-linearizza di nuovo il file. E quando i pacchetti hanno bisogno di composizione invece di semplice concatenazione, diciamo un foglio di copertina timbrato dietro ogni fattura o due pagine sorgente imposte su un foglio di output, DACapturePage trasforma qualunque pagina in un modello riutilizzabile che DADrawCapturedPage colloca su una pagina di destinazione in un rettangolo arbitrario, ancora senza un caricamento completo del documento sulla sorgente multi-gigabyte
Limiti e cosa resta in sola lettura
Il formato stesso esaurisce lo spazio molto prima del Direct Access. Gli offset sono Int64 per tutto il percorso attraverso lo strato DA, così i soffitti reali sono il disco disponibile e il campo di offset xref a 10 cifre delle tabelle di cross-reference classiche (non-flusso). Gli archivi di scansioni multi-gigabyte sono normali nella pratica, e la memoria resta limitata a prescindere dalla dimensione del file perché gli oggetti sono letti solo quando una chiamata li richiede
Due domande ricorrono abbastanza da rispondere direttamente. La fusione attraverso il percorso di default porta la struttura del documento di là, così i segnalibri e i link sopravvivono; la variante Fast è quella che scambia l'albero di struttura con la velocità, che è tutta la ragione per riservarla agli input non taggati. L'abitudine sicura è aprire l'output fuso, percorrere il suo contorno, e controllare a campione pochi link interni prima di consegnarlo. Quanto al editing: c'è un utile terreno intermedio tra il sondaggio in sola lettura e un caricamento completo. Le operazioni a livello di pagina funzionano sull'handle direttamente, DARotatePage, DAMovePage e DAHidePage tra esse, oltre alle letture dei campi modulo, e DAAppendFile persiste quelle modifiche come una revisione incrementale. L'editing a livello di contenuto, qualunque cosa che riscrive gli operatori di marcatura dentro una pagina, appartiene ancora allo strato di documento completo
Articoli correlati
Se il tuo output fuso deve restare accessibile, il contesto dell'albero di struttura è trattato in l'articolo sull'accessibilità del PDF taggato, che spiega esattamente cosa la variante di fusione Fast scarterebbe. Per estrarre contenuto dai range che dividi, vedi la guida di estrazione di testo, immagini e font
L'elenco completo delle funzioni Direct Access è incluso nella libreria; le edizioni e i download di prova si trovano sulla pagina prodotto di PDF Library for Delphi