Fusionner ou scinder un PDF de deux gigaoctets par la voie évidente coûte deux choses à la fois : du temps de traitement et de l'espace d'adressage. La voie évidente, c'est de charger chaque entrée, de faire le travail, d'écrire la sortie. C'est le chargement qui casse tout. Une archive numérisée qui passe de 300 à 600 DPI double sa résolution linéaire et quadruple à peu près sur le disque ; le même job d'assemblage qui absorbait des fichiers de 400 Mo toute l'année se met à ramer dès qu'une entrée dépasse le gigaoctet, souvent alors qu'il ne fait rien de plus que compter des pages. La tâche n'est pourtant pas devenue plus difficile : ouvrir, compter, choisir des plages, concaténer, c'est tout. C'est le chargement de l'arbre complet qui a simplement cessé d'être un choix par défaut raisonnable à cette taille. PDF Library for Delphi, la bibliothèque PDF de losLab pour Delphi et C++Builder, y répond 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 sur place au lieu de construire tout le document en mémoire
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');
// Vérifie le résultat à moindres frais : de nouveau en accès direct
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
Limites et ce qui reste en lecture seule
Le format lui-même manque de place bien avant Direct Access. Les offsets sont en Int64 dans toute la couche DA ; les vraies limites sont donc le disque disponible et le champ d'offset xref à 10 chiffres des tables de références croisées classiques, celles qui ne sont pas stockées en flux. Les archives numérisées de plusieurs gigaoctets n'ont rien d'exceptionnel en pratique, et la mémoire reste bornée quelle que soit la taille du fichier parce que les objets ne sont lus que lorsqu'un appel les réclame
Deux questions reviennent assez souvent pour mériter une réponse directe. La fusion par le chemin par défaut transporte la structure du document : les signets et les liens survivent ; c'est la variante Fast qui échange l'arbre de structure contre de la vitesse, et c'est toute la raison de la réserver aux entrées non balisées. La bonne habitude consiste à ouvrir la sortie fusionnée, à parcourir son plan et à contrôler quelques liens internes avant de la livrer. Quant à l'édition, il existe un juste milieu utile entre le sondage en lecture seule et le chargement complet : les opérations au niveau page travaillent directement sur le handle — DARotatePage, DAMovePage et DAHidePage parmi elles — de même que les lectures de champs de formulaire, et DAAppendFile persiste ces modifications sous forme de révision incrémentale. L'édition au niveau du contenu, tout ce qui réécrit les opérateurs de marquage à l'intérieur d'une page, 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 PDF Library for Delphi