PDFium Component produce PDFs Fast Web View (linearizados) desde Delphi vía TPdf.SaveAsLinearized. El método guarda el documento actual con PDFium, y luego reescribe el resultado de acuerdo con el Anexo F de ISO 32000-1: los objetos de la primera página pasan al frente, se escriben dos secciones de cross-reference, y las hint tables de offset de página y de objetos compartidos le dicen a un visor qué rangos de bytes traerse para cualquier página
La razón por la que a alguien le importa es la red. Un catálogo de 40 MB servido desde object storage o un portal de documentos debería mostrar la página uno después de los primeros pocos kilobytes, y saltar a la página 380 debería costar un par de requests de HTTP Range, no una descarga completa. Los visores solo pueden hacerlo cuando el archivo está linearizado. La mayoría de los PDFs que salen de un pipeline de edición no lo están, porque la forma natural de escribir un PDF pone la tabla de cross-reference al final y esparce los objetos de página donde al escritor le tocó emitirlos
¿Por qué PDFium puede leer archivos linearizados pero no escribirlos?
PDFium tiene un lector completo para linearización y ningún escritor para ella. La API de disponibilidad FPDFAvail_* detecta el diccionario de linearización, decodifica hint tables, y le pide al host exactamente los rangos de bytes que necesita, que es sobre lo que está construido el flujo de descarga progresiva y cancelación en PDFium Component. El ABI público de guardado, por otro lado, solo ofrece un guardado de copia plana y un guardado con versión de PDF objetivo. Nada en él reordena objetos ni emite hint streams
PDFium Component cierra ese hueco sin volver a implementar lo que PDFium ya hace bien. TPdf.SaveAsLinearized primero llama al SaveAs existente, así que PDFium sigue siendo dueño del guardado semántico: las páginas editadas, el estado de los form fields y el manejo de versión pasan todos por la ruta de código que lleva años ejercitándose. El componente entonces corre una pasada de reestructuración del Anexo F sobre ese PDF intermedio estable y escribe el resultado linearizado a su destino. Partir el trabajo así significa que la linearización nunca tiene que entender una anotación ni un formulario, solo objetos, referencias y offsets de bytes
Llamar TPdf.SaveAsLinearized desde Delphi
TPdf.SaveAsLinearized tiene dos sobrecargas, una para un TStream y otra para un nombre de archivo, y ambas toman los mismos parámetros opcionales Option: TSaveOption y PdfVersion: TPdfVersion que SaveAs. Ambas devuelven True en éxito. La sobrecarga de archivo escribe a un archivo temporal en el directorio objetivo y reemplaza el destino de forma atómica, así que un guardado fallido nunca deja un PDF a medio escribir donde antes había uno bueno
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... edite páginas, llene forms, estampe, lo que sea que haga el pipeline ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Las reglas de parámetros son estrictas a propósito. saIncremental lanza un error, porque una actualización incremental agrega una nueva revisión al final del archivo y destruiría de inmediato el layout cargado al frente. Una versión objetivo por debajo de PDF 1.2 también se rechaza; pvUnknown conserva la versión de la fuente. La entrada cifrada necesita saRemoveSecurity, ya que el escritor renumera objetos y las llaves de cifrado a nivel de objeto de PDF se derivan del número de objeto. Y como la linearización es una reescritura completa, cualquier firma digital existente queda invalidada. Si tiene que entregar firmado y linearizado, linearice primero y firme después
Cómo agrupa objetos el escritor para Fast Web View
La linearización no es solo mover la página uno a la punta del archivo. El escritor parsea la revisión actual, sea que sus datos de cross-reference sean una tabla clásica, un archivo híbrido o un cross-reference stream, y expande cada miembro de un object stream activo en un objeto independiente. Luego arma un grafo de referencias y ordena los objetos en cinco grupos: los objetos a nivel documento necesarios para abrir el archivo, los objetos de la primera página, los objetos privados de una página posterior, los objetos compartidos por varias páginas posteriores, y todo lo demás
La trampa en ese recorrido del grafo es /Parent. Cada diccionario de página apunta hacia arriba en el árbol de páginas, y un recorrido ingenuo sigue ese apuntador hasta el nodo padre, y luego hacia abajo por /Kids hasta cada página hermana y sus content streams. Un paso después el grupo de la primera página contiene todo el documento, y el archivo es técnicamente linearizado pero inútil para acceso de rango primero. PDFium Component ignora el enlace /Parent de la página y los nodos del árbol de páginas fuera de la página que se está recolectando, que es lo que mantiene pequeña la sección de la primera página. En una muestra de regresión de 51 páginas con un árbol de páginas profundo, la sección de la primera página son 5289 bytes, el 0.48% de la salida de 1105354 bytes
Los object streams y cross-reference streams de la entrada se aplanan en objetos indirectos ordinarios de generación 0, cada referencia se reescribe a los nuevos números, y la salida usa secciones de cross-reference clásicas. Eso cuesta algo de tamaño de archivo comparado con object streams comprimidos, pero simplifica la cobertura de hint tables y es el layout que todo visor consciente de linearización maneja. Si la compacidad importa más que Fast Web View para una salida dada, PDFium Component tiene un escritor de object streams separado para ese trabajo
¿Qué va en el diccionario de linearización y en las dos secciones xref?
Un archivo linearizado escrito por PDFium Component carga dos secciones de cross-reference clásicas y un diccionario de linearización en el primer objeto. La primera sección xref sigue al diccionario de linearización y cubre los objetos a nivel documento y de la primera página, así que un visor puede renderizar la página uno desde el frente del archivo solo. La sección xref principal se sienta al final y cubre todo lo demás
El diccionario de linearización en sí carga los números que un visor necesita antes de leer cualquier otra cosa: /L la longitud del archivo, /H el offset y la longitud del hint stream, /O el número del objeto de la primera página, /E el fin de la sección de la primera página, /N el conteo de páginas, y /T el offset de la primera entrada en la tabla xref principal. Todos dependen del layout final, y el diccionario se sienta antes del layout. Escribirlo con números de ancho natural crea un bucle: una longitud que crece de 999999 a 1000000 agrega un byte, desplaza cada offset siguiente, lo que puede cambiar el ancho de otro número, y así sucesivamente
PDFium Component rompe el bucle reservando 320 bytes fijos para el diccionario de linearización. Cada objeto, el hint stream, ambas secciones xref y los trailers se escriben primero; una vez que todos los offsets son finales, el diccionario se llena en su lugar y se rellena hasta el ancho reservado. Un detalle sutil es /T: debe apuntar a la entrada del objeto 0 de la tabla xref principal, no a la palabra clave xref que la precede. Los validadores estrictos marcan el offset de la palabra clave aunque los visores indulgentes abran el archivo de todos modos
¿Por qué los offsets de hint tables ignoran el hint stream?
Los offsets de las hint tables en un PDF linearizado se interpretan como si el hint stream no existiera en el archivo. El Anexo F lo define así para que el hint stream pueda sentarse entre la sección de la primera página y el resto sin que su propio tamaño retroalimente los números que contiene. La consecuencia práctica para un escritor es fácil de hacer mal: cualquier offset real que quede después del hint stream tiene que reducirse antes de entrar a la hint table de offset de página o de objetos compartidos
La pregunta es reducido por cuánto. La respuesta tentadora es la longitud de los datos del stream, porque esa es la parte que se siente como el contenido del hint. La respuesta correcta es la longitud del objeto indirecto del hint entero, incluidos N 0 obj, el diccionario del stream, las palabras clave stream y endstream, y endobj. Reste solo la longitud de los datos y cada offset posterior queda desviado por unas cuantas docenas de bytes. La página uno igual renderiza porque vive antes del hint stream, así que el bug sobrevive a una revisión visual rápida y solo aparece cuando un visor salta a una página posterior y pide un rango que arranca a media mitad del objeto equivocado
Ambas hint tables se empaquetan por bits con el bit más significativo primero, otra vez según el Anexo F. El componente verifica el resultado de la forma en que un cliente real lo haría: un almacenamiento de rangos disperso suministra solo el primer 1 KiB de la muestra de 51 páginas, y el manejo de hints nativo de PDFium pide el resto. La última página abre sin que el archivo completo haya estado presente jamás
Verificar un PDF linearizado en Delphi
El chequeo más barato es preguntarle al propio PDFium. TPdf.InspectAvailability corre la API de disponibilidad contra un stream sin cargar el documento, y su campo Linearization reporta plnLinearized, plnNotLinearized o plnUnknown
function IsFastWebView(const FileName: string): Boolean;
var
Stream: TFileStream;
Info: TPdfAvailability;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Info := TPdf.InspectAvailability(Stream);
Result := Info.Linearization = plnLinearized;
finally
Stream.Free;
end;
end;
Eso confirma que PDFium reconoce el layout; no prueba que los hints estén correctos. Para una prueba real de extremo a extremo, sirva el archivo a través de un transporte con rangos limitados y abra una página tardía con TPdfProgressiveDocument, de la misma forma que lo hace la guía de streaming de PDFs grandes a demanda con PDFium VCL. Si la página renderiza después de traer solo una pequeña fracción del archivo, las hint tables están haciendo su trabajo. Agregar un segundo verificador de linearización independiente al pipeline de release también vale la pena, porque los visores difieren en cuán indulgentes son ante un /T mal puesto o un offset ligeramente equivocado
Dos límites para tener en mente. El materializador de objetos todavía rechaza arreglos de 2 GiB o más, así que los documentos extremadamente grandes necesitan una estrategia distinta por ahora. Y la linearización solo rinde cuando el servidor honra los requests de HTTP Range; detrás de un proxy que tira Accept-Ranges, un archivo linearizado se descarga exactamente igual que cualquier otro
Preguntas frecuentes
¿TPdf.SaveAsLinearized mantiene válidas las firmas digitales?
No. TPdf.SaveAsLinearized renumera y reescribe cada objeto, así que las firmas existentes quedan invalidadas. Linearice primero y aplique la firma después
¿Puede PDFium Component linearizar un PDF cifrado?
Solo cuando usted pasa saRemoveSecurity. Las llaves de cifrado a nivel de objeto dependen de los números de objeto, que el escritor cambia, así que la salida se escribe sin cifrado
¿Qué versiones de PDF soporta la salida linearizada?
TPdf.SaveAsLinearized acepta pvUnknown para conservar la versión de la fuente o cualquier objetivo desde PDF 1.2 en adelante, y rechaza saIncremental porque una revisión agregada rompería el layout
La salida Fast Web View, la carga progresiva y la API de disponibilidad son parte del PDFium Component para Delphi y C++Builder, que envuelve a PDFium para proyectos de Delphi, C++Builder y Lazarus