Compter les pages d'une archive numérisée de 1,4 Go devrait être peu coûteux. Appelez LoadFromFile sur ce fichier et cela cesse d'être peu coûteux : HotPDF analyse les données de référence croisée et construit un objet en mémoire pour chacun des plusieurs centaines de milliers d'objets indirects du document, et un travailleur 32 bits atteint le plafond d'espace d'adressage de 2 Go quelque part au milieu de cette analyse. L'opération que vous souhaitiez, le nombre de pages, n'a jamais eu besoin d'aucun de ces objets. Elle n'avait besoin que de l'arborescence des pages, et de rien d'autre. Cet écart, entre ce qu'un travail demande et ce qu'un chargement complet fournit, est la raison d'être de l'API Fichier Direct
L'API Fichier Direct (Direct File API) donne à Delphi et C++Builder un accès au niveau du fichier à un PDF : nombre de pages, copies, déchiffrement, ajouts incrémentiels, le tout en lisant sur le disque ce dont ils ont réellement besoin plutôt que de reconstruire l'intégralité du modèle de document dans la RAM. La compétence consiste à faire correspondre chaque travail au niveau le plus léger qui puisse y répondre. Réussissez cette correspondance et un service conserve une mémoire plate quelle que soit la taille de l'entrée. Si vous vous trompez, le premier fichier surdimensionné fait tomber le travailleur
Ce que vous coûte un chargement complet
LoadFromFile n'est pas l'ennemi. Il mérite sa mémoire : une fois que l'arborescence est dans la RAM, vous avez un accès aléatoire à chaque page et à chaque objet, ce qui est exactement ce que nécessitent InsertPagesFromDocument, MovePage et la re-sérialisation via SaveLoadedDocument. Il n'y a pas de raccourci pour une véritable restructuration ; vous devez détenir le document pour le réarranger
Les ennuis commencent lorsque vous ne contrôlez pas les tailles d'entrée. Les téléversements de clients, les sorties de numériseurs et les archives d'il y a dix ans ignorent tout ce que votre corpus de test supposait. Chargez chaque entrée inconditionnellement et votre plafond de mémoire est fixé par le plus gros fichier que quiconque soumettra jamais. Le temps d'analyse suit le nombre d'objets, et la mémoire résidente s'établit à plusieurs fois la taille du fichier une fois que les structures d'objets et les flux décodés sont comptés, de sorte qu'un gigaoctet sur le disque peut signifier plusieurs gigaoctets en mémoire résidente
Une recompilation pour le 64 bits soulève le plafond d'espace d'adressage mais laisse la facture intacte. Le travailleur brûle encore des secondes de processeur et un multiple du fichier dans la RAM pour répondre à une question à laquelle la propre structure du fichier aurait pu répondre en quelques millisecondes. Sous la concurrence, les calculs deviennent hostiles : quatre chargements importants exécutés simultanément partagent un seul budget de mémoire, et le débit s'effondre précisément lorsque la file d'attente est la plus profonde et que vous pouvez le moins vous le permettre
Lecture d'un fichier via un handle
Le niveau en lecture seule ouvre un fichier en tant que handle (gestionnaire), répond à des questions structurelles à son sujet et le ferme. Pas d'arborescence d'objets, pas de rendu de page, pas de mémoire qui croît avec l'entrée
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Trois habitudes maintiennent ce niveau honnête. Tout d'abord, vérifiez la valeur de retour. Un handle non positif signifie que l'ouverture a échoué, et déclencher DAGetPageCount sur un handle mort est le genre de bug qui reste caché jusqu'au jour où un client envoie un fichier malformé. Deuxièmement, associez chaque ouverture réussie à DACloseFile à l'intérieur d'un bloc finally ; un service qui laisse fuir des handles ne plante pas, il pourrit simplement, ce qui est pire. Troisièmement, respectez ce que fait réellement le paramètre de mot de passe. DAOpenFileReadOnly en accepte un, mais pour les entrées chiffrées, il passe discrètement à une analyse complète pour lire le nombre de pages, la garantie de mémoire plate s'évapore donc. Acheminez d'abord les fichiers protégés via DecryptFile et le reste du pipeline reste peu coûteux
La même sonde fait office de porte de triage. Les fichiers apparaissent mal étiquetés, à moitié téléversés, ou entièrement renommés depuis un autre format, et une vérification DAOpenFileReadOnly rejette tous ceux-ci à la porte d'entrée en millisecondes, l'erreur étant épinglée au fichier incriminé. L'alternative est de laisser un fichier indésirable s'engager profondément dans un travailleur de file d'attente et d'y exploser, là où démêler quelle entrée l'a causé peut coûter un après-midi
Copier, déchiffrer et chiffrer des fichiers entiers
Le deuxième niveau déplace et transforme des fichiers complets sans jamais exposer leurs entrailles. Ce sont les appels sur lesquels les pipelines d'admission s'appuient le plus
// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Chaque appel mérite sa place. DACopyFile est la copie validée d'un répertoire de quarantaine vers un stockage géré : il ouvre et indexe la structure PDF au fur et à mesure, de sorte qu'une entrée tronquée ou non-PDF échoue ici même plutôt que trois étapes plus loin. DecryptFile écrit une copie déchiffrée le long d'un chemin de réécriture AES-256 direct qui ignore l'arborescence des objets chaque fois que l'entrée le permet, l'équivalent pour les gros fichiers du flux de déchiffrement par chargement et ré-enregistrement couvert dans l'article sur le chiffrement AES-256. EncryptFile exécute le même mouvement en sens inverse, appliquant la protection par mot de passe lors d'une copie au niveau du fichier avec les paramètres de type de clé et d'autorisation que le chemin en mémoire utilise déjà
Ajouter des modifications au lieu de réécrire
La mise à jour incrémentielle, définie dans la norme ISO 32000-1 §7.5.6, est le troisième niveau. Les octets d'origine restent là où ils sont sur le disque, et tout objet nouveau ou modifié est ajouté après eux, suivi d'une nouvelle section de référence croisée qui s'enchaîne à l'original. Pour une archive de 900 Mo qui nécessite l'ajout d'une seule page, le coût d'écriture est le delta, pas le fichier entier
// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // original bytes + delta
Deux points de discipline importent ici. BeginIncrementalUpdate doit pointer vers le fichier d'origine, car les données de référence croisée ajoutées s'enchaînent aux décalages d'octets à l'intérieur de celui-ci. Et le modèle est de type ajout uniquement par conception : chaque enregistrement incrémentiel fait croître le fichier, sans jamais le réduire. Un document estampillé chaque nuit gonflera sans limite jusqu'à ce qu'une re-sérialisation périodique, en le chargeant et en le réécrivant via SaveLoadedDocument, le compacte. Cette même nature d'ajout uniquement est ce qui fait de la mise à jour incrémentielle le seul moyen sûr de toucher à un document signé numériquement, une contrainte examinée dans l'article sur les signatures numériques et PAdES. La machinerie de référence croisée sous-jacente fait l'objet de son propre traitement dans l'article sur les flux d'objets et les mises à jour incrémentielles
Il y a un piège dans les enregistrements en ajout uniquement qui échappe à la plupart des examens. Les octets d'origine restent dans le fichier, lisibles par quiconque veut bien y regarder. Une mise à jour incrémentielle qui "remplace" une page ne supprime pas l'ancienne ; elle la remplace dans la révision actuelle tandis que la révision précédente reste là, entièrement récupérable. Les mises à jour incrémentielles sont donc le mauvais outil pour supprimer du contenu sensible. Pour véritablement abandonner un historique qu'un destinataire ne devrait jamais voir, il vous faut une re-sérialisation complète : LoadFromFile suivi de SaveLoadedDocument, qui n'écrit que l'état actuel et laisse les révisions enfouies derrière
Faire correspondre le niveau à l'opération
La logique de sélection est suffisamment courte pour être gardée en tête, et il est payant de l'encoder comme une décision de routage explicite au sommet d'un pipeline au lieu de laisser chaque travail improviser son propre chemin. L'opération dont vous avez besoin décide du niveau :
- Compter, inspecter ou classifier ouvre un handle :
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Déplacer, déchiffrer ou chiffrer un fichier entier reste au niveau du fichier avec
DACopyFile,DecryptFileouEncryptFile - Restructurer des pages ou fusionner des documents nécessite le chargement complet :
LoadFromFile, puisInsertPagesFromDocumentouMovePage, puisSaveLoadedDocument - Ajouter un petit delta à un fichier énorme ou signé appelle
BeginIncrementalUpdateet enregistre
Les pipelines mixtes font bien de placer un seuil de taille devant le chemin de chargement complet. Envoyez tout ce qui dépasse quelques centaines de mégaoctets via les niveaux Fichier Direct, et réservez le chargement complet pour une véritable restructuration sur un travailleur 64 bits avec un budget mémoire réel. Le seuil convertit un plantage dû à un manque de mémoire en une décision de routage que vous pouvez voir et ajuster
Quel que soit le niveau qui gère un travail, écrivez sa sortie sous un nom temporaire et renommez-la à sa place uniquement une fois que le résultat est validé. Un fichier à moitié écrit se trouvant sous le nom final ressemble exactement à un bon fichier pour la prochaine étape du pipeline, et les appels Fichier Direct rendent la vérification peu coûteuse : confirmer une sortie est une sonde de handle d'une ligne
L'API Fichier Direct est fournie dans le cadre du Composant HotPDF pour Delphi et C++Builder. La page du produit lie la référence complète des fonctions, y compris les appels de mise à jour incrémentielle présentés ici