Articolo tecnico

Fusione e divisione di PDF di gigabyte con PDF Library for Delphi

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

Confronto PDF Library for Delphi tra caricare un PDF di un gigabyte in un albero completo di oggetti in memoria e aprirlo con accesso diretto, dove l'analisi si ferma al trailer e agli xref e un handle serve letture lazy per oggetto
Un caricamento completo decodifica ogni oggetto indiretto prima che l'unione possa partire, quindi RAM e tempo di apertura scalano con l'archivio. Il percorso ad accesso diretto restituisce un handle funzionante dopo aver letto kilobyte e lascia che ogni chiamata prelevi solo gli oggetti di cui ha bisogno

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:

PDF Library for Delphi: flusso di traduzione da numero di pagina a PageRef che mostra DAFindPage alimentare le chiamate di accesso diretto per pagina, contro un ref intero grezzo che atterra su un oggetto arbitrario producendo silenziosamente testo della pagina sbagliata
Ogni chiamata di accesso diretto per pagina consuma un PageRef prodotto da DAFindPage, mai il numero rivolto all'utente. Saltare quella traduzione fa passare l'intero per un id di oggetto, e testo di pagina sbagliata può partire invisibilmente
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

PDF Library for Delphi: flusso di lavoro della lista di file con nome in cui gli estratti di gennaio, febbraio e marzo si registrano sotto un nome di lista e si uniscono in una sola passata, con le varianti Fast, predefinita e strict che scambiano la conservazione dell'albero della struttura con la velocità
Centinaia di input registrati collassano in un singolo passaggio MergeFileList il cui risultato si verifica in millisecondi attraverso un'altra sonda in sola lettura. La variante è una decisione per pipeline perché Fast abbandona l'albero di struttura Tagged PDF
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