El trabajo nocturno que ensamblaba paquetes de cierre hipotecario funcionó bien durante dos años, hasta que empezó a morir la semana en que un proveedor de escaneo pasó a 600 DPI. Los archivos individuales de archivo histórico superaron 1.8 GB, y el paso de ensamblado — que cargaba cada documento completo antes de tocar una sola página — empezó a agotar el espacio de direcciones del worker solo para contar páginas. El requisito no había cambiado: abrir, contar, elegir rangos, concatenar. Lo que cambió es que la carga completa del árbol deja de ser un valor predeterminado razonable en algún punto alrededor de la marca de gigabytes. PDFlibPas, la biblioteca PDF de losLab para Delphi y C++Builder, atiende exactamente esto con su capa Direct Access: una familia de funciones con prefijo DA respaldadas por un lector de streaming que recorre la tabla de referencias cruzadas en sitio en lugar de materializar el documento.
Dónde se va la memoria en una carga completa
Cargar un PDF "normalmente" significa analizar el xref, resolver cada objeto indirecto en un árbol en memoria, decodificar object streams y conectar el árbol de páginas, fuentes y anotaciones en objetos manipulables. Para flujos de edición, ese es el intercambio correcto. Para cargas de fusión, división e inspección, casi todo es desperdicio: un archivo escaneado de 30,000 páginas puede contener millones de objetos indirectos, de los cuales un trabajo de división necesita leer unos cientos — los nodos de página en el rango solicitado y lo que referencian.
La capa Direct Access invierte el modelo. DAOpenFile y DAOpenFileReadOnly analizan el trailer y el xref — unos cuantos kilobytes al final del archivo — y devuelven un handle de archivo. Los objetos se recuperan de forma perezosa cuando una llamada los necesita. La consecuencia práctica es que abrir un archivo de varios gigabytes tarda casi lo mismo que abrir uno pequeño, y la memoria sigue lo que tocan, no lo que contiene el archivo.
Sondear un archivo enorme sin cargarlo
El patrón siguiente proviene del benchmark de archivos grandes de la propia biblioteca: abrir en solo lectura, hacer preguntas, cerrar. Nunca existe un árbol de documento.
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;
Vale la pena preferir el modo de solo lectura siempre que puedan: permite que la etapa de recepción se ejecute mientras otros procesos mantienen el archivo abierto, y documenta la intención — una etapa de sondeo que por accidente llame una función mutante fallará rápido en lugar de corromper el archivo.
PageRef es un handle de objeto, no un número de página
El error más común con la API DA es pasar un número de página donde una función espera un PageRef. Casi todas las llamadas DA por página — DAExtractPageText, DARenderPageToFile, DARotatePage, DACapturePage — toman un handle de referencia al objeto de página, obtenido al traducir el número orientado a humanos mediante 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;
Pasar el número crudo 250 no lanza un error — direcciona cualquier objeto que se encuentre detrás de ese valor de handle, lo que en un día afortunado falla visiblemente y en uno desafortunado extrae texto de la página equivocada hacia un documento de cara al cliente. Si envuelven la capa DA en su propio código de servicio, hagan imposible saltarse la traducción: acepten números de página en el límite, llamen DAFindPage de inmediato y pasen solo refs internamente.
Fusionar cientos de archivos con una lista con nombre
Para dos archivos, MergeFiles(First, Second, Output) es suficiente. El ensamblado por lotes escala mejor mediante listas de archivos: registren entradas bajo un nombre de lista y luego fusionen la lista en una sola pasada.
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 familia de fusión tiene tres variantes, y la diferencia no es solo velocidad. MergeFileListFast omite la preservación del árbol de estructura; MergeFileListStrict aplica modo estricto; la versión sin sufijo es el valor predeterminado equilibrado. De ahí sale la regla operativa: si cualquier entrada es un Tagged PDF cuya estructura de accesibilidad debe sobrevivir — cualquier cosa producida para PDF/UA, por ejemplo — usen la variante predeterminada o Strict, porque Fast descartará silenciosamente el árbol de estructura. Para archivos escaneados simples sin etiquetas, Fast es rendimiento gratis. Decidan por canalización, no por ánimo del desarrollador, y registren en el log del trabajo la variante usada.
Dividir sin cargar: extracción de rangos
La división sigue la misma filosofía sin carga. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) toma un rango de páginas directamente de archivo a archivo — '1-500', '501-1000', o selecciones separadas por comas — sin que el origen se convierta nunca en un árbol de documento. Cuando un documento ya está cargado por otras razones, ExtractPageRanges produce un nuevo documento en memoria desde el actual, y CopyPageRanges trae rangos desde otro documento cargado por ID. Para la división por estado de cuenta de flujos impresos consolidados, la forma archivo a archivo es la que evita que una entrada de 4 GB se infle alguna vez en RAM.
Archivos que mienten sobre su geometría
Las canalizaciones de archivos grandes encuentran archivos dañados con una frecuencia que las de archivos pequeños casi nunca ven, simplemente porque las entradas pasan por más sistemas. Dos formas de fallo merecen manejo explícito.
Primero, encabezados desplazados. Las pasarelas de correo y los spoolers de impresión a veces anteponen bytes a un PDF, de modo que el marcador %PDF ya no está en el offset 0 y todos los offsets xref del archivo quedan mal por la misma cantidad. El lector de streaming detecta esto y lo expone — DAShiftedHeader en el nivel plano, ShiftedHeader en TSmartPDFReader — y compensa durante las lecturas. La aritmética de offsets hecha en casa normalmente no lo hace, por eso "funciona con todos los archivos que generamos, falla con archivos del cliente X" es el síntoma clásico.
Segundo, tablas de referencias cruzadas rotas. DACopyFile(InputFileName, OutputFileName, PageCount) transmite todo el archivo a una copia nueva mientras reconstruye el xref y devuelve el conteo de páginas como subproducto. Ejecutarlo como etapa de normalización antes de un consumidor aguas abajo exigente convierte una clase de fallos intermitentes de análisis en un paso de reparación predecible. Y cuando sus propias ediciones necesitan guardarse, DAAppendFile las escribe como actualización incremental — agrega una nueva revisión en lugar de reescribir gigabytes, lo que mantiene el costo de guardado proporcional al cambio, no al archivo.
Detalles de entrega: linearización y composición
Dos capacidades adyacentes completan una canalización de archivos grandes. Cuando la salida ensamblada se sirve por HTTP para vista en navegador, LinearizeFile la reorganiza para streaming por rangos de bytes, de modo que la primera página aparece antes de que se descargue el resto de un paquete de 500 MB — vale la pena hacerlo como etapa final, después de toda fusión, porque cualquier modificación posterior deslineariza el archivo. Y cuando los paquetes necesitan composición en lugar de concatenación simple — una portada estampada detrás de cada estado de cuenta, dos páginas origen impuestas sobre una hoja de salida — DACapturePage convierte cualquier página en una plantilla reutilizable que DADrawCapturedPage coloca en una página destino en un rectángulo arbitrario, todavía sin carga completa del documento fuente de varios gigabytes.
Preguntas comunes sobre archivos grandes
¿Qué tan grande puede manejar un archivo Direct Access? Los offsets son Int64 en toda la capa DA, así que el límite de formato no es la restricción; lo son el disco disponible y el techo de offsets xref de 10 dígitos del PDF clásico. En la práctica, los archivos escaneados de varios gigabytes son rutinarios; la memoria permanece acotada porque los objetos se recuperan bajo demanda.
¿La fusión preserva marcadores y enlaces? La ruta de fusión predeterminada arrastra la estructura del documento; la variante Fast intercambia preservación del árbol de estructura por velocidad. Verifiquen con entradas reales: abran la salida, recorran el esquema y revisen algunos enlaces internos — una prueba de cinco minutos que ha terminado muchas conversaciones largas de soporte.
¿Puedo editar mediante Direct Access, o solo leer? Existe un punto medio útil: operaciones de nivel página como DARotatePage, DAMovePage, DAHidePage y lecturas de campos de formulario trabajan sobre el handle, y DAAppendFile las persiste incrementalmente. La edición de nivel contenido sigue perteneciendo a la capa de documento completa.
Artículos relacionados
Si su salida fusionada debe seguir siendo accesible, el contexto sobre árboles de estructura está cubierto en el artículo de accesibilidad con Tagged PDF — explica qué descartaría la variante Fast de fusión. Para extraer contenido de los rangos que dividan, consulten la guía de extracción de texto, imágenes y fuentes.
La lista completa de funciones Direct Access se entrega con la biblioteca; las ediciones y descargas de prueba están en la página del producto PDFlibPas.