Abres un PDF de 200 páginas desde un enlace y el navegador se queda a menudo en blanco hasta que llega la mayor parte del fichero. Fast Web View es el nombre que Adobe Acrobat da a un PDF linealizado (Anexo F de ISO 32000-1): la primera página y un pequeño índice van al principio, de modo que el visualizador pinta pronto la página uno y trae las demás páginas mediante peticiones de rango HTTP. HotPDF y PDFium Component pueden escribir ficheros linealizados desde Delphi
Nada de esto es un formato de fichero aparte. Un PDF linealizado es un PDF ordinario que cualquier lector conforme abre sin tratamiento especial; la diferencia está en el orden de sus bytes y en dos estructuras adicionales, el diccionario de parámetros de linealización y el hint stream. Dos condiciones deciden si un lector llega a notar el beneficio. El fichero tiene que estar linealizado y seguir estándolo, algo que una edición añadida deshace silenciosamente, y el servidor web tiene que responder a las peticiones de rango de bytes con 206 Partial Content. En un disco local, donde buscar al final de un fichero no cuesta nada, un PDF linealizado se comporta exactamente como cualquier otro, así que Fast Web View es una optimización puramente de red que compensa en documentos grandes abiertos directamente desde una URL
¿Qué reorganiza un PDF linealizado?
Un PDF linealizado reúne todo lo que la primera página necesita en un bloque contiguo cerca del principio del fichero, por delante de todas las demás páginas. En un PDF ordinario los objetos pueden estar en cualquier orden, porque la tabla de referencias cruzadas del final mapea cada número de objeto a su desplazamiento en bytes: el lector salta al final, lee el puntero startxref, carga la tabla y desde ahí encuentra cualquier cosa (la visión general de la estructura de un fichero PDF recorre ese diseño de cabecera, cuerpo, xref y tráiler). El diseño es excelente en disco y pobre en red, donde el final es justo la parte que llega la última. Para dibujar la primera página, un lector convencional necesita el objeto de página, su flujo de contenido y las fuentes e imágenes que usa, y en un fichero sin ordenar cualquiera de ellos puede estar en el último megabyte
La linealización corrige el orden. Tras la cabecera van el diccionario de linealización y una pequeña sección de referencias cruzadas de primera página, después los objetos de nivel de documento como el catálogo, el hint stream primario y la primera página con todos sus recursos. Las páginas restantes siguen una a una, luego los objetos compartidos entre páginas posteriores, y al final la tabla de referencias cruzadas principal. Un lector que ignora la optimización sigue empezando por el final y encuentra cada objeto a través de la tabla principal, por eso un fichero linealizado sigue siendo totalmente compatible con software antiguo. La numeración de objetos queda al revés en un editor hexadecimal: el bloque frontal lleva los números más altos, mientras que todo lo de detrás se numera desde el 1
El diccionario de linealización y las tablas hint
El primer objeto tras la cabecera %PDF es el diccionario de parámetros de linealización, y el Anexo F exige que esté dentro de los primeros 1024 bytes para que un lector de flujo lo encuentre en el paquete inicial. La tabla F.1 enumera sus entradas: /L es la longitud de todo el fichero en bytes, /H el desplazamiento y la longitud del hint stream primario, /O el número de objeto de la primera página, /E el desplazamiento donde termina la primera página, /N el número de páginas, y /T el desplazamiento de la primera entrada de la tabla de referencias cruzadas principal. La entrada /L pesa más de lo que parece. Cuando no coincide con el tamaño real del fichero, la tabla F.1 indica al lector que trate el documento como un PDF ordinario e ignore todas las hints que contiene
El hint stream es lo que hace barato saltar a la página 40. Contiene una tabla hint de desplazamientos de página, que registra para cada página dónde empiezan sus objetos y cuántos bytes cubren, y una tabla hint de objetos compartidos que hace lo mismo para fuentes, logotipos y otros recursos usados en varias páginas. Un lector que quiere la página 40 consulta su rango de bytes, pide exactamente ese fragmento y renderiza en cuanto llega, trayéndose por el mismo camino cualquier objeto compartido que aún no tenga. Dos detalles desmontan a los linearizadores caseros. Los desplazamientos de las hints son lógicos, calculados como si el propio hint stream no estuviera, de modo que un escritor que guarda desplazamientos físicos manda cada petición de rango posterior desviada el tamaño del objeto de hints. Y un escritor que sigue el enlace /Parent de una página mientras reúne sus recursos retrocede por el árbol de páginas y arrastra el documento entero a la sección de primera página. Ambos ficheros siguen declarándose linealizados. Ninguno carga más rápido
¿Por qué Fast Web View necesita la ayuda del servidor?
Fast Web View solo funciona cuando el servidor entrega fragmentos arbitrarios del fichero a petición. El lector envía peticiones HTTP con una cabecera Range y espera recibir 206 Partial Content; si el servidor nunca anuncia Accept-Ranges: bytes, o un proxy o CDN delante de él convierte las peticiones de rango en transferencias completas, el lector no puede traer la página 40 por su cuenta y descarga el fichero entero en su lugar. La estructura dentro del PDF es entonces correcta y completamente desaprovechada, y este es el fallo que con más frecuencia se diagnostica erróneamente como una linealización rota. Antes de reconstruir un documento, pide el primer kilobyte de la URL exacta que abre el visualizador, por ejemplo con curl -r 0-1023, y comprueba que el estado es 206. Los alojamientos de ficheros estáticos suelen hacerlo bien por defecto, mientras que los endpoints de aplicación que sirven PDFs a través de su propio handler a menudo no
¿Cómo comprobar si un PDF tiene Fast Web View?
Comprueba los bytes en lugar de fiarte de la herramienta que produjo el fichero, y comprueba las dos mitades de la condición: el fichero se declara linealizado y esa declaración sigue coincidiendo con el fichero en disco. Tres comprobaciones cubren casi todos los casos, desde un vistazo rápido en un escritorio hasta una puerta automatizada en un pipeline de publicación:
- Adobe Acrobat o Reader: abre Archivo > Propiedades; la pestaña Descripción muestra
Fast Web View: YesoNoen la esquina inferior derecha. Acrobat informa Yes solo cuando la estructura está presente y vigente, así que un fichero editado después de la linealización muestra No - qpdf en la línea de comandos:
qpdf --check-linearizationvalida las tablas hint contra las posiciones reales de los objetos, yqpdf --show-linearizationvuelca el diccionario y cada entrada de las tablas hint - El primer kilobyte del fichero: ábrelo en un editor de texto o hexadecimal y busca un objeto que contenga
/Linearized 1dentro de los primeros 1024 bytes; si está, compara su valor/Lcon el tamaño de fichero que informa el sistema operativo
# Valida el diccionario de linealización y las tablas hint contra el layout
qpdf --check-linearization report-web.pdf
# Vuelca /L, /H, /O, /E, /N, /T y cada entrada de las tablas hint
qpdf --show-linearization report-web.pdf
La comprobación con qpdf es la que merece la pena conectar a un pipeline, porque reconcilia las tablas hint con las posiciones reales de los objetos; una comprobación que solo confirma que el diccionario existe bendecirá un fichero cuyos desplazamientos apuntan a los bytes equivocados. Desde Delphi, THotPDF.IsLoadedLinearized de HotPDF informa de si un documento cargado se declara linealizado, rastreando los primeros 1024 bytes en busca de /Linearized antes de parsear. PDFium Component responde a la misma pregunta mediante la función de clase TPdf.InspectAvailability, cuyo campo Linearization devuelve plnLinearized, plnNotLinearized o plnUnknown sin cargar el documento en absoluto. Ambas señales informan de lo que el fichero declara; la comparación de /L te dice si esa declaración sigue coincidiendo con los bytes en disco:
uses
System.SysUtils, System.Classes, System.Math, System.StrUtils, PDFium;
type
TFastWebView = (fwvNo, fwvStale, fwvYes);
// Tu propio helper: PDFium decide si el fichero está linealizado,
// y la comparación de /L caza las ediciones añadidas después
function FastWebViewState(const FileName: string): TFastWebView;
var
Stream: TFileStream;
Head: AnsiString;
Text: string;
P, Q: Integer;
begin
Result := fwvNo;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if TPdf.InspectAvailability(Stream).Linearization <> plnLinearized then
Exit;
SetLength(Head, Min(Stream.Size, 1024)); // Anexo F: los primeros 1024 bytes
Stream.Position := 0;
if Length(Head) > 0 then
Stream.ReadBuffer(Head[1], Length(Head));
Text := string(Head);
P := Pos('/Linearized', Text);
if P = 0 then
Exit;
P := PosEx('/L ', Text, P + Length('/Linearized'));
if P = 0 then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Text)) and CharInSet(Text[Q], ['0'..'9']) do
Inc(Q);
if StrToInt64Def(Copy(Text, P, Q - P), -1) = Stream.Size then
Result := fwvYes
else
Result := fwvStale; // linealizado una vez, y luego modificado
finally
Stream.Free;
end;
end;
Activar Fast Web View en Acrobat, qpdf y Delphi
Cualquier herramienta que reescriba el fichero completo en el orden del Anexo F puede activar Fast Web View, y lo que más importa es que se ejecute el último. En Acrobat, activa Guardar como optimiza para Fast Web View en Preferencias > Documentos y usa después Archivo > Guardar como en lugar de Guardar, porque un Guardar simple puede añadir una actualización incremental. En un servidor o en un script de build, qpdf --linearize in.pdf out.pdf reescribe cualquier PDF en forma linealizada y deja la entrada intacta. El código Delphi tiene dos caminos, según si generas el documento o lo recibes hecho. HotPDF escribe salida linealizada directamente cuando THotPDF.LinearizeOutput es True; establécelo junto a las demás opciones de salida antes de BeginDoc, y el layout del Anexo F se calcula y se escribe dentro de EndDoc:
qpdf --linearize report-final.pdf report-web.pdf
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False; // no abras un visualizador tras EndDoc
PDF.FileName := 'catalog-web.pdf';
PDF.LinearizeOutput := True; // se establece antes de BeginDoc
PDF.BeginDoc; // la página 1 existe tras BeginDoc
PDF.CurrentPage.TextOut(72, 72, 0, 'Spring catalog');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Price list');
PDF.EndDoc; // el layout linealizado se escribe aquí
finally
PDF.Free;
end;
end;
Para ficheros que ya existen, PDFium Component proporciona TPdf.SaveAsLinearized. Ejecuta el guardado ordinario de PDFium en un almacén intermedio, de modo que las ediciones de páginas y los valores de formulario pasan por el mismo camino que cualquier otro guardado, y después reestructura esa salida según el Anexo F antes de escribir tu destino. El método acepta los mismos parámetros TSaveOption y TPdfVersion que SaveAs, rechaza saIncremental y cualquier versión inferior a PDF 1.2, y su sobrecarga con nombre de fichero escribe a un fichero temporal que reemplaza el destino solo cuando el guardado ha funcionado. Como el escritor reconstruye el fichero desde la revisión actual, es también la forma de restaurar Fast Web View en un fichero que acumuló ediciones añadidas. La entrada cifrada necesita saRemoveSecurity: renumerar objetos invalida las claves por objeto del manejador de seguridad estándar (ISO 32000-1 §7.6.2), así que la copia web se escribe sin cifrado
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'manual-rev3.pdf'; // arrastra ediciones añadidas
Pdf.Active := True;
if not Pdf.SaveAsLinearized('manual-web.pdf', saNoIncremental) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Las actualizaciones incrementales apagan Fast Web View sin avisar
Una actualización incremental es el motivo más común por el que un fichero correctamente linealizado pierde Fast Web View más adelante. Cuando una herramienta añade una firma, rellena un campo de formulario o adjunta una nota adhesiva mediante un guardado incremental, deja los bytes originales intactos y añade al final los objetos modificados, una nueva sección de referencias cruzadas y un nuevo tráiler. El diccionario de linealización del principio sigue describiendo el layout antiguo, su /L ya no coincide con el tamaño del fichero, y un lector conforme vuelve a tratar el documento como un PDF ordinario, aunque la estructura linealizada siga intacta en la primera parte del fichero. HotPDF añade exactamente este tipo de revisión mediante BeginIncrementalUpdate y SaveIncrementalUpdate, que es la herramienta correcta mientras un documento sigue cambiando y el paso final equivocado para un fichero destinado a un servidor web:
// Añade una revisión: rápido, conserva intactos los bytes previos,
// y deja el resultado sin Fast Web View
PDF.BeginIncrementalUpdate('report.pdf');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 760, 0, 'Addendum');
PDF.SaveIncrementalUpdate('report-rev2.pdf');
La regla que funciona es editar de forma incremental mientras el documento está en cambio, linealizar una sola vez como paso muy final, y dejar Fast Web View desactivado en los ficheros que se envían por correo o se descargan antes de que alguien los abra, porque no ganan nada con el índice adelantado más grande. Los documentos firmados fuerzan una elección real: una reescritura completa cambia los bytes que cubre el /ByteRange de cada firma, y firmar después añade una actualización que devuelve enseguida la discrepancia de /L, así que en los contratos conserva la firma y acepta las descargas completas. La cara de escritura del Anexo F, incluido cómo convergen los desplazamientos antes de escribir el primer byte y por qué la salida linealizada renuncia a los flujos de objetos, está cubierta en escritura de PDF linealizados con tablas hint de HotPDF y linealización Fast Web View con PDFium Component en Delphi. LinearizeOutput e IsLoadedLinearized vienen con el HotPDF Delphi Component, mientras que SaveAsLinearized e InspectAvailability forman parte del PDFium Component para Delphi, C++Builder y Lazarus