Contar las páginas en un archivo escaneado de 1.4 GB debería ser económico. Llame a LoadFromFile en ese archivo y deja de ser económico: HotPDF analiza los datos de referencias cruzadas y construye un objeto en memoria para cada uno de los cientos de miles de objetos indirectos del documento, y un trabajador (worker) de 32 bits alcanza el límite de espacio de direcciones de 2 GB en algún punto intermedio de ese análisis. La operación que usted quería, el recuento de páginas, nunca necesitó ninguno de esos objetos. Necesitaba el árbol de páginas y nada más. Esa brecha, entre lo que pide un trabajo y lo que entrega una carga completa, es la razón principal por la que existe la Direct File API
La Direct File API le da a Delphi y C++Builder acceso a nivel de archivo a un PDF: recuento de páginas, copias, desencriptación, anexos incrementales, todo leyendo del disco lo que realmente necesitan en lugar de reconstruir todo el modelo del documento en la RAM. La habilidad consiste en hacer coincidir cada trabajo con el nivel más ligero que pueda responderlo. Si acierta, un servicio mantendrá una memoria plana en cualquier tamaño de entrada. Si se equivoca, el primer archivo de gran tamaño derribará al trabajador
Lo que le cuesta una carga completa
LoadFromFile no es el enemigo. Se gana su memoria: una vez que el árbol está en la RAM, tiene acceso aleatorio a cada página y cada objeto, que es exactamente lo que requieren InsertPagesFromDocument, MovePage y la reserialización a través de SaveLoadedDocument. No hay atajos para una reestructuración genuina; tiene que sostener el documento para reorganizarlo
El problema comienza cuando los tamaños de entrada no son suyos para controlar. Las cargas de clientes, la salida de los escáneres y los archivos de hace una década ignoran lo que su corpus de prueba haya asumido. Cargue cada entrada de manera incondicional y su límite de memoria estará determinado por el archivo más grande que alguien vaya a enviar. El tiempo de análisis sigue el rastro del recuento de objetos, y la memoria residente se asienta en varias veces el tamaño del archivo después de contabilizar las estructuras de objetos y los flujos decodificados, por lo que un gigabyte en disco puede significar múltiples gigabytes residentes
Recompilar para 64 bits eleva el límite del espacio de direcciones pero deja intacta la factura. El trabajador aún quema segundos de CPU y un múltiplo del archivo en la RAM para responder una pregunta que la propia estructura del archivo podría haber respondido en milisegundos. Bajo concurrencia, las matemáticas se vuelven hostiles: cuatro cargas grandes ejecutándose a la vez comparten un presupuesto de memoria, y el rendimiento se desploma precisamente cuando la cola es más profunda y usted menos puede permitírselo
Leer un archivo a través de un identificador (handle)
El nivel de solo lectura abre un archivo como un identificador, responde preguntas estructurales sobre él y lo cierra. Sin árbol de objetos, sin renderizado de páginas, sin memoria que crezca con la entrada
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;
Tres hábitos mantienen este nivel honesto. Primero, verifique el valor de retorno. Un identificador (handle) no positivo significa que la apertura falló, y lanzar DAGetPageCount a un identificador inactivo es el tipo de error que permanece oculto hasta el día en que un cliente envía un archivo malformado. Segundo, empareje cada apertura exitosa con DACloseFile dentro de un bloque finally; un servicio con fuga de identificadores no falla (crash), simplemente se pudre, lo cual es peor. Tercero, respete lo que realmente hace el parámetro de contraseña. DAOpenFileReadOnly acepta uno, pero para las entradas encriptadas cae silenciosamente en un análisis completo para leer el recuento de páginas, por lo que la garantía de memoria plana se evapora. Enrute primero los archivos protegidos a través de DecryptFile y el resto de la canalización seguirá siendo económica
La misma prueba también sirve como puerta de clasificación (triage). Los archivos aparecen mal etiquetados, a medio subir o renombrados desde algún otro formato completamente distinto, y una verificación de DAOpenFileReadOnly rechaza todos esos en la puerta principal en milisegundos, con el error fijado al archivo infractor. La alternativa es dejar que un archivo basura se adentre en un trabajador de cola y explote allí, donde desenredar qué entrada lo causó puede costar una tarde
Copiar, desencriptar y encriptar archivos completos
El segundo nivel mueve y transforma archivos completos sin exponer nunca sus aspectos internos. Estas son las llamadas en las que más se apoyan las canalizaciones de entrada
// 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);
Cada llamada se gana su lugar. DACopyFile es la copia validada de un directorio de cuarentena al almacenamiento administrado: abre e indexa la estructura PDF sobre la marcha, por lo que una entrada truncada o que no es PDF falla aquí mismo en lugar de tres etapas más adelante. DecryptFile escribe una copia desencriptada a lo largo de una ruta de reescritura directa AES-256 que omite el árbol de objetos siempre que la entrada lo permita, la contraparte para archivos grandes del flujo de desencriptación de cargar-y-volver-a-guardar que se cubre en el artículo de encriptación AES-256. EncryptFile ejecuta el mismo movimiento en reversa, aplicando protección con contraseña durante una copia a nivel de archivo con los parámetros de tipo de clave y permisos que la ruta en memoria ya usa
Anexar cambios en lugar de reescribir
La actualización incremental, definida en ISO 32000-1 §7.5.6, es el tercer nivel. Los bytes originales permanecen donde están en el disco, y cualquier objeto nuevo o modificado se anexa después de ellos, seguido de una nueva sección de referencias cruzadas que se encadena de nuevo al original. Para un archivo de 900 MB que necesita que se agregue una sola página, el costo de escritura es el delta, no el archivo completo
// 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
Aquí importan dos puntos de disciplina. BeginIncrementalUpdate tiene que apuntar al archivo original, ya que los datos de referencia cruzada anexados se encadenan de nuevo a posiciones de bytes (offsets) en su interior. Y el modelo es de solo anexar por diseño: cada guardado incremental hace crecer el archivo, nunca lo encoge. Un documento sellado todas las noches se hinchará sin límite hasta que una reserialización periódica, cargándolo y volviéndolo a guardar a través de SaveLoadedDocument, lo compacte. Esa misma naturaleza de solo anexar es lo que hace que la actualización incremental sea la única forma segura de tocar un documento firmado digitalmente, una restricción examinada en el artículo de firmas digitales y PAdES. La maquinaria de referencias cruzadas subyacente tiene su propio tratamiento en el artículo de flujos de objetos y actualizaciones incrementales
Hay una trampa en los guardados de solo anexar que pasa desapercibida en la mayoría de las revisiones. Los bytes originales se quedan en el archivo, legibles para cualquiera que esté dispuesto a mirar. Una actualización incremental que "reemplaza" una página no elimina la antigua; la reemplaza en la revisión actual mientras la revisión anterior permanece allí, totalmente recuperable. Por lo tanto, las actualizaciones incrementales son la herramienta incorrecta para quitar contenido confidencial. Para eliminar verdaderamente el historial que un destinatario nunca debería ver, necesita una reserialización completa: LoadFromFile seguido de SaveLoadedDocument, que escribe solo el estado actual y deja atrás las revisiones enterradas
Hacer coincidir el nivel con la operación
La lógica de selección es lo suficientemente corta como para mantenerla en su cabeza, y vale la pena codificarla como una decisión de enrutamiento explícita en la parte superior de una canalización en lugar de dejar que cada trabajo improvise su propia ruta. La operación que necesita decide el nivel:
- Contar, inspeccionar o clasificar abre un identificador (handle):
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Mover, desencriptar o encriptar un archivo completo se mantiene a nivel de archivo con
DACopyFile,DecryptFileoEncryptFile - Reestructurar páginas o fusionar documentos necesita la carga completa:
LoadFromFile, luegoInsertPagesFromDocumentoMovePage, luegoSaveLoadedDocument - Agregar un pequeño delta a un archivo enorme o firmado llama a
BeginIncrementalUpdatey guarda
A las canalizaciones mixtas les va bien poner un umbral de tamaño frente a la ruta de carga completa. Envíe cualquier cosa que pase de unos pocos cientos de megabytes a través de los niveles de Direct File y reserve la carga completa para reestructuraciones genuinas en un trabajador de 64 bits con un presupuesto de memoria real. El umbral convierte un fallo por falta de memoria en una decisión de enrutamiento que puede ver y ajustar
Sea cual sea el nivel que maneje un trabajo, escriba su salida en un nombre temporal y cámbiele el nombre en su lugar solo una vez que el resultado se valide. Un archivo a medio escribir ubicado bajo el nombre final se ve exactamente como uno bueno para la siguiente etapa de la canalización, y las llamadas a Direct File hacen que la verificación sea económica: confirmar una salida es una prueba de un solo identificador de línea
La Direct File API se incluye como parte de HotPDF Component para Delphi y C++Builder. La página del producto incluye la referencia completa de la función, incluidas las llamadas de actualización incremental que se muestran aquí