Ir al contenido
getnextpdf.com

La anatomía de un archivo PDF

Abre cualquier PDF en un editor de texto plano y lo primero que ves es tranquilizador: un encabezado %PDF-1.x o %PDF-2.0. Lo último que ves es %%EOF. Todo lo que hay entre esas dos líneas es una pequeña y ordenada máquina para encontrar objetos por número. Esta página es una disección. Abrimos el archivo, nombramos cada órgano y mostramos cómo se conectan.

Es el complemento estructural de dos páginas vecinas. Qué es realmente un PDF trata el archivo como un grafo de objetos; actualizaciones incrementales cubre cómo crece con el tiempo. Esta página se mantiene cerca de los bytes: las regiones físicas que recorre un analizador, en el orden en que están en el disco.

Casi nunca se necesita esto para usar un PDF. Se necesita el día en que uno se estropea. Un archivo se abre en un visor y en otro no; un validador informa de una «tabla de referencias cruzadas dañada»; un documento firmado de pronto no se verifica. Ninguno de esos es un misterio una vez que se sabe leer la anatomía. Son un número que ya no coincide con una posición, una región en el lugar equivocado o una cola que no apunta a nada.

Conocer la disposición convierte «el PDF está corrupto» en un diagnóstico sobre el que se puede actuar. Es la diferencia entre encogerse de hombros ante una caja negra y señalar el byte exacto que miente.

Un PDF conforme tiene cuatro partes físicas, en este orden dentro del archivo (Spec: ISO 32000-2, §7.5.1):

  1. Un encabezado: una línea, %PDF-2.0, que nombra la versión.
  2. Un cuerpo: el grueso del archivo: una sucesión de objetos indirectos numerados.
  3. Una sección de referencias cruzadas: un índice que va del número de objeto al desplazamiento de bytes donde reside ese objeto. Los PDF clásicos usan una tabla de texto; PDF 2.0 usa un flujo xref comprimido.
  4. Un tráiler: un pequeño diccionario que nombra el punto de entrada, seguido de startxref, un desplazamiento y %%EOF.

El giro: un lector no empieza por arriba. Empieza por abajo, lee startxref para encontrar el índice y usa ese índice para llegar a cualquier objeto directamente. El archivo se escribe de principio a fin pero se lee de fin a principio.

Recorramos las cuatro regiones en orden, con los bytes delante.

El encabezado es una línea. NextPDF escribe %PDF-2.0 y, por convención, una segunda línea de comentario con bytes de bit alto para que las herramientas de transferencia ingenuas traten el archivo como binario, no como texto. Esa segunda línea es la razón por la que un PDF abierto como texto plano muestra un poco de basura justo después de la versión.

El cuerpo es donde vive el documento. Cada objeto indirecto es un número, una generación, la palabra clave obj, un valor y endobj (Spec: ISO 32000-2, §7.3.10). El valor es una de unas pocas formas, pero dos soportan casi todo el peso:

  • Un diccionario, << /Key value … >>, es un mapa de nombres a valores. El árbol de páginas, el catálogo, los descriptores de fuentes: todos son diccionarios.
  • Un flujo es un diccionario seguido de stream, un bloque de bytes arbitrarios y endstream. El contenido de página, las fuentes incrustadas y las imágenes son flujos, casi siempre comprimidos. (Sus filtros son toda una historia aparte, contada en flujos y filtros.)

Los objetos se apuntan unos a otros mediante referencia indirecta: 2 0 R significa «objeto 2, generación 0». Ese es el cableado que convierte una lista plana de objetos en un grafo.

La sección de referencias cruzadas es la parte que la mayoría nunca se imagina correctamente. En la forma clásica es texto plano: la palabra clave xref, luego subsecciones de líneas de ancho fijo de 20 bytes (Spec: ISO 32000-2, §7.5.4). Cada línea es un desplazamiento de bytes de diez dígitos, una generación de cinco dígitos y un único indicador —n para en uso, f para libre— rellenado hasta exactamente veinte bytes para que un lector pueda desplazarse a cualquier entrada solo con aritmética. PDF 2.0 reemplaza esto por un flujo de referencias cruzadas: el mismo índice, pero binario y comprimido dentro de un objeto de flujo marcado /Type /XRef (Spec: ISO 32000-2, §7.5.8). Más pequeño, y capaz de describir objetos empaquetados dentro de flujos de objetos.

El tráiler es el índice del archivo (Spec: ISO 32000-2, §7.5.5). Nombra /Root —el catálogo del documento, el único objeto del que cuelga todo lo demás— y /Size, el número de objetos. Luego viene el apretón de manos que hace posible la lectura hacia atrás: startxref, un desplazamiento de bytes en su propia línea y %%EOF. Un lector se desplaza al final, lee ese desplazamiento, salta directo a la sección de referencias cruzadas y se pone en marcha.

  1. EncabezadoUna línea, %PDF-2.0, que nombra la versión. Suele seguir un comentario marcador de binario.
  2. CuerpoObjetos indirectos numerados —diccionarios y flujos— referenciados por N G R.
  3. Sección de referencias cruzadasUna tabla de texto de entradas de 20 bytes, o un flujo /Type /XRef comprimido en PDF 2.0.
  4. TráilerNombra /Root y /Size, luego startxref + desplazamiento + %%EOF.
  5. Orden de lecturaUn lector empieza en %%EOF, sigue startxref hasta el índice y luego llega a cada objeto directamente.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

Hay una quinta región que un archivo de larga vida hace crecer: una actualización incremental. Un cambio no se escribe en su sitio. Los objetos modificados, una nueva sección de referencias cruzadas y un nuevo tráiler se anexan después del primer %%EOF, y ese nuevo tráiler lleva /Prev: el desplazamiento de la sección de referencias cruzadas anterior (Spec: ISO 32000-2, §7.5.6). Las secciones forman una cadena hacia atrás; para cualquier número de objeto, prevalece la entrada más reciente. Como los bytes originales nunca se mueven, la comprobación criptográfica sobre el rango de bytes que una firma realmente cubre sigue siendo válida tras una actualización. Una actualización incremental posterior aún puede cambiar lo que un validador informa sobre el documento en su conjunto —si los cambios posteriores a la firma están permitidos y qué se considera que certifica la firma—, pero no puede alterar los propios bytes firmados. Esa propiedad es todo el tema de actualizaciones incrementales.

Aquí está toda la anatomía en un archivo mínimo. Los números bajo xref son desplazamientos de bytes, y tienen que ser exactos: apunta un carácter más allá de donde empieza un objeto y un lector estricto se rinde.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

Léelo como lo hace un analizador. Última línea: %%EOF. Encima: startxref 186, así que desplázate al byte 186, donde empieza xref. La tabla dice que el objeto 1 reside en el byte 9. El /Root 1 0 R del tráiler apunta ahí —el catálogo— y desde el catálogo recorres /Pages hasta el árbol de páginas y encuentras la única página. El objeto 0 es siempre la cabecera de la lista de libres con generación 65535, un fósil del primer diseño del formato que todo lector aún espera ver.

La trampa es leer un PDF como una historia: de arriba abajo, en orden. No es una historia; es un índice con un puntero hacia atrás. Los números de objeto no tienen por qué ser secuenciales en el archivo, los objetos pueden aparecer en cualquier orden físico, y un lector nunca depende de su posición. El único mapa con autoridad es la sección de referencias cruzadas, y la única forma de encontrar ese mapa es el desplazamiento startxref al final del todo.

La consecuencia sorprende a la gente. Un PDF con un cuerpo impecable y un dígito equivocado en startxref es ilegible: el lector no puede encontrar el índice. Un PDF con sus objetos en orden desordenado pero una sección de referencias cruzadas correcta está perfectamente bien. La posición física no tiene significado. La posición registrada lo tiene todo.

Esta página describe la estructura física, no el contenido de la página. Cómo llegan las marcas a una página —operadores del flujo de contenido, presentación de texto, estado gráfico— es un tema aparte. También describe un archivo bien formado. Los PDF del mundo real están con frecuencia un poco rotos y sobreviven solo porque los visores indulgentes reconstruyen la tabla de referencias cruzadas buscando palabras clave obj. Ese rescate es un comportamiento del visor, no algo que el formato garantice.

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF es un escritor. Registra cada desplazamiento del búfer de salida en el momento en que se emite cada objeto, de modo que los archivos que produce tienen una sección de referencias cruzadas que coincide con el cuerpo por construcción.
ProAnalizar, reconstruir o reparar una tabla de referencias cruzadas dañada en un archivo que NextPDF no escribió queda fuera de alcance en todas las ediciones.
EnterprisePara inspeccionar la estructura de un archivo existente, usa un analizador o validador dedicado; NextPDF garantiza la corrección de lo que escribe, no de lo que lee.

¿Por qué un PDF tiene una línea marcadora de binario después del encabezado? Algunas herramientas de transferencia más antiguas estropeaban un archivo que creían texto plano. El comentario de bit alto hace que el archivo parezca inequívocamente binario, así que sobrevive al trayecto sin cambios.

¿El flujo xref es solo una tabla más pequeña? Más o menos, con un poder extra. Además de estar comprimido, un flujo xref puede describir objetos almacenados dentro de flujos de objetos: entradas que la clásica tabla de texto de 20 bytes no tiene forma de expresar.

¿Puedo tener a la vez una tabla y un flujo en un mismo archivo? Una única revisión usa una u otro. Pero un archivo híbrido puede emparejar una tabla clásica para lectores antiguos con un flujo de referencias cruzadas para los nuevos, de modo que cada tipo de lector encuentre un índice que entienda.

  • Encabezado: la primera línea, %PDF-2.0, que nombra la versión; suele ir seguida de un comentario marcador de binario.
  • Objeto indirecto: un objeto numerado en el cuerpo, escrito N G obj … endobj, donde N es el número de objeto y G la generación.
  • Diccionario: un mapa << /Key value … >> de nombres a valores; la forma de objeto más común.
  • Flujo: un diccionario más un bloque de bytes entre stream y endstream, usado para contenido, fuentes e imágenes.
  • Tabla de referencias cruzadas (xref): el índice que va del número de objeto al desplazamiento de bytes; clásicamente una tabla de texto de 20 bytes por entrada, un flujo /Type /XRef en PDF 2.0.
  • Tráiler: el diccionario que nombra /Root y /Size, localizado mediante el desplazamiento startxref al final del archivo.
  • Actualización incremental: objetos modificados, una nueva sección de referencias cruzadas y un nuevo tráiler anexados después de %%EOF, con /Prev encadenando hacia atrás a la sección anterior.