Le traitement nocturne qui assemblait des dossiers de clôture hypothécaire a fonctionné correctement pendant deux ans, puis a commencé à mourir la semaine où un prestataire de numérisation est passé à 600 DPI. Certains fichiers d'archive dépassaient 1,8 Go, et l'étape d'assemblage — qui chargeait chaque document entièrement avant de toucher une seule page — épuisait l'espace d'adressage du worker rien que pour compter les pages. L'exigence n'avait pas changé : ouvrir, compter, choisir des plages, concaténer. Ce qui a changé, c'est que le chargement complet de l'arbre cesse d'être un choix raisonnable quelque part autour du gigaoctet. PDFlibPas, la bibliothèque PDF de losLab pour Delphi et C++Builder, répond précisément à ce cas avec sa couche Direct Access : une famille de fonctions préfixées DA, adossées à un lecteur en flux qui parcourt la table de références croisées en place au lieu de matérialiser le document.
Où part la mémoire lors d'un chargement complet
Charger un PDF « normalement » signifie analyser le xref, résoudre chaque objet indirect dans un arbre en mémoire, décoder les object streams, puis raccorder l'arbre des pages, les polices et les annotations à des objets manipulables. Pour des workflows d'édition, c'est le bon compromis. Pour des charges de fusion, de scission et d'inspection, c'est surtout du gaspillage : une archive numérisée de 30 000 pages peut contenir des millions d'objets indirects, alors qu'un job de scission n'a besoin d'en lire que quelques centaines — les nœuds de page de la plage demandée et ce qu'ils référencent.
La couche Direct Access inverse ce modèle. DAOpenFile et DAOpenFileReadOnly analysent le trailer et le xref — quelques kilo-octets à la fin du fichier — puis renvoient un handle de fichier. Les objets sont récupérés paresseusement lorsqu'un appel en a besoin. En pratique, l'ouverture d'un fichier de plusieurs gigaoctets prend à peu près le même temps que celle d'un petit fichier, et la mémoire suit ce que vous touchez plutôt que ce que le fichier contient.
Sonder un énorme fichier sans le charger
Le schéma ci-dessous vient du benchmark grands fichiers de la bibliothèque elle-même : ouverture en lecture seule, questions ciblées, fermeture. Aucun arbre de document n'existe jamais.
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;
Le mode lecture seule mérite d'être préféré dès que possible : il permet à l'étape d'ingestion de tourner pendant que d'autres processus détiennent le fichier, et il documente l'intention — une étape de sondage qui appellerait accidentellement une fonction mutatrice échouera vite au lieu de corrompre l'archive.
PageRef est un handle d'objet, pas un numéro de page
L'erreur la plus fréquente avec l'API DA consiste à passer un numéro de page là où une fonction attend un PageRef. Presque tous les appels DA par page — DAExtractPageText, DARenderPageToFile, DARotatePage, DACapturePage — prennent un handle de référence vers l'objet page, obtenu en traduisant le numéro visible par l'utilisateur via 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;
Passer directement le nombre 250 ne déclenche pas d'erreur : cela adresse l'objet qui se trouve derrière cette valeur de handle, ce qui échoue visiblement les bons jours et extrait le texte de la mauvaise page dans un document client les mauvais jours. Si vous enveloppez la couche DA dans votre propre code de service, rendez la traduction impossible à oublier : acceptez les numéros de page à la frontière, appelez DAFindPage immédiatement, puis ne transmettez que des refs en interne.
Fusionner des centaines de fichiers avec une liste nommée
Pour deux fichiers, MergeFiles(First, Second, Output) suffit. L'assemblage par lot se dimensionne mieux avec des listes de fichiers : enregistrez les entrées sous un nom de liste, puis fusionnez cette liste en un seul passage.
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);
La famille de fusion comporte trois variantes, et la différence ne se limite pas à la vitesse. MergeFileListFast ignore la préservation de l'arbre de structure ; MergeFileListStrict impose le mode strict ; la version sans suffixe est le choix équilibré par défaut. La règle opérationnelle qui en découle est simple : si une entrée est un Tagged PDF dont la structure d'accessibilité doit survivre — tout ce qui est produit pour PDF/UA, par exemple — utilisez la variante par défaut ou Strict, car Fast supprimera silencieusement l'arbre de structure. Pour de simples archives de scans sans balisage, Fast offre une performance gratuite. Décidez par pipeline, pas selon l'humeur du développeur, et consignez la variante utilisée dans le journal du job.
Scinder sans chargement : extraction de plages
La scission suit la même philosophie sans chargement. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) extrait une plage de pages directement de fichier à fichier — '1-500', '501-1000' ou des sélections séparées par des virgules — sans que la source devienne un arbre de document. Lorsqu'un document est déjà chargé pour d'autres raisons, ExtractPageRanges produit un nouveau document en mémoire depuis l'actuel, et CopyPageRanges récupère des plages depuis un autre document chargé par ID. Pour scinder par relevé des flux d'impression consolidés, la forme fichier vers fichier est celle qui empêche une entrée de 4 Go de gonfler en RAM.
Les fichiers qui mentent sur leur géométrie
Les pipelines grands fichiers rencontrent des fichiers endommagés à un rythme que les petits fichiers ne connaissent pas, simplement parce que les entrées traversent plus de systèmes. Deux formes d'échec méritent un traitement explicite.
D'abord, les en-têtes décalés. Les passerelles mail et les spoolers d'impression ajoutent parfois des octets au début d'un PDF ; le marqueur %PDF n'est alors plus à l'offset 0 et tous les offsets xref du fichier sont faux du même décalage. Le lecteur en flux le détecte et l'expose — DAShiftedHeader au niveau flat, ShiftedHeader sur TSmartPDFReader — puis compense pendant les lectures. Les calculs d'offset faits maison ne le font généralement pas, ce qui explique le symptôme classique « fonctionne sur tous les fichiers que nous générons, échoue sur ceux du client X ».
Ensuite, les tables de références croisées cassées. DACopyFile(InputFileName, OutputFileName, PageCount) diffuse tout le fichier vers une nouvelle copie en reconstruisant le xref, et renvoie le nombre de pages comme sous-produit. L'exécuter comme étape de normalisation avant un consommateur aval exigeant transforme une classe d'échecs intermittents d'analyse en une étape de réparation prévisible. Et lorsque vos propres modifications doivent être enregistrées, DAAppendFile les écrit comme une mise à jour incrémentale — en ajoutant une nouvelle révision plutôt qu'en réécrivant des gigaoctets, ce qui rend le coût d'enregistrement proportionnel au changement, pas au fichier.
Détails de livraison : linéarisation et composition
Deux capacités voisines complètent un pipeline grands fichiers. Lorsque la sortie assemblée est servie en HTTP pour affichage dans le navigateur, LinearizeFile la réorganise pour la diffusion par plages d'octets, afin que la première page s'affiche avant que le reste d'un paquet de 500 Mo soit téléchargé — à faire en dernière étape, après toutes les fusions, car toute modification ultérieure délinéarise le fichier. Et lorsque les paquets demandent de la composition plutôt qu'une simple concaténation — une page de garde tamponnée derrière chaque relevé, deux pages source imposées sur une feuille de sortie — DACapturePage transforme n'importe quelle page en gabarit réutilisable que DADrawCapturedPage place sur une page de destination dans un rectangle arbitraire, toujours sans chargement complet de la source de plusieurs gigaoctets.
Questions fréquentes sur les grands fichiers
Quelle taille de fichier Direct Access peut-il gérer ? Les offsets sont en Int64 dans toute la couche DA ; la limite au niveau du format n'est donc pas la contrainte, contrairement au disque disponible et au plafond à 10 chiffres des offsets xref du PDF classique. En pratique, les archives numérisées de plusieurs gigaoctets sont courantes ; la mémoire reste bornée parce que les objets sont récupérés à la demande.
La fusion préserve-t-elle les signets et les liens ? Le chemin de fusion par défaut transporte la structure du document ; la variante Fast échange la préservation de l'arbre de structure contre de la vitesse. Vérifiez avec vos vraies entrées : ouvrez la sortie, parcourez le plan et contrôlez quelques liens internes — un test de cinq minutes qui a mis fin à beaucoup de longs échanges de support.
Puis-je modifier via Direct Access, ou seulement lire ? Un juste milieu utile existe : des opérations au niveau page comme DARotatePage, DAMovePage, DAHidePage et les lectures de champs de formulaire travaillent sur le handle, et DAAppendFile les persiste de manière incrémentale. L'édition au niveau du contenu reste du ressort de la couche document complète.
Articles liés
Si votre sortie fusionnée doit rester accessible, le contexte sur l'arbre de structure est traité dans l'article sur l'accessibilité Tagged PDF — il explique ce que la variante de fusion Fast supprimerait. Pour extraire du contenu des plages que vous scindez, consultez le guide d'extraction de texte, d'images et de polices.
La liste complète des fonctions Direct Access est livrée avec la bibliothèque ; les éditions et téléchargements d'essai sont disponibles sur la page produit PDFlibPas.