Un lector de PDF no comienza al principio del archivo. Comienza por el final. Los últimos bytes contienen la dirección de todo lo demás, y un analizador que no comprende ese orden interpretará mal el formato desde la primera línea. Entonces, la forma más útil de aprender un PDF en el disco es aprenderlo como lo hace un lector: primero la cola, luego saltar hacia atrás hasta el mapa y, a continuación, resolver los objetos que señala el mapa
Los bytes en sí son bastante sencillos de leer en un editor de texto cuando no hay nada comprimido. Un documento mínimo de una página que dibuja "Hello, World!" cabe en menos de quinientos bytes, y cada elemento estructural del formato es visible en él. Aquí está el archivo completo, con las cuatro partes marcadas:
%PDF-1.0 % Encabezado
%âãÏÓ
1 0 obj % Cuerpo: la secuencia de objetos
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj
2 0 obj
<<
/Rotate 0
/Parent 1 0 R
/Resources 3 0 R
/MediaBox [0 0 612 792]
/Contents [4 0 R]
/Type /Page
>>
endobj
3 0 obj
<< /Font << /F0 << /BaseFont /Times-Italic /Subtype /Type1 /Type /Font >> >> >>
endobj
4 0 obj
<< /Length 65 >>
stream
1. 0. 0. 1. 50. 700. cm BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Pages 1 0 R /Type /Catalog >>
endobj
xref % Tabla de referencias cruzadas
0 6
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000192 00000 n
0000000291 00000 n
0000000409 00000 n
trailer % Tráiler
<<
/Root 5 0 R
/Size 6
>>
startxref
459
%%EOF
Cuatro partes, siempre en este orden en el archivo: un encabezado, un cuerpo de objetos, una tabla de referencias cruzadas y un tráiler. El truco es que usted las lee casi en orden inverso. ISO 32000-2 §7.5.1 presenta la misma anatomía de cuatro partes, y la razón del acceso de atrás hacia adelante es puramente práctica: un lector que salta directamente al objeto que necesita es mucho más rápido que uno que escanea cada byte desde el principio, y ese acceso aleatorio es exactamente lo que proporcionan el tráiler y la tabla de referencias cruzadas
El encabezado tiene dos líneas, y la segunda importa
La primera línea es %PDF-1.0. El signo de porcentaje lo convierte en un comentario en lo que respecta a la sintaxis, pero los lectores lo tratan como la firma del archivo y extraen el número de versión de él. El manejo de versiones es flexible en la práctica. Un lector creado para PDF 2.0 abrirá felizmente un archivo que dice ser 1.0, y la mayoría de los lectores intentarán con un archivo cuya versión declarada sea incorrecta o cuya línea de versión esté un poco adentrada en el archivo en lugar de en el byte cero. El número es un indicio sobre qué funciones esperar, no una puerta
La segunda línea es la que la gente elimina por accidente y luego pasa una tarde depurando. También es un comentario, pero su carga útil son cuatro bytes por encima de ASCII 127. Existen para que cualquier cosa que mueva el archivo en "modo de texto" lo reconozca como binario y deje de reescribir los finales de línea. Un PDF transporta flujos comprimidos cuyos bytes pueden coincidir con un retorno de carro o un avance de línea por casualidad; si una herramienta de transferencia los reescribe, la longitud del flujo registrada en el diccionario ya no coincide con los bytes en el disco y el archivo está corrupto. El comentario de byte alto es una defensa de cuarenta años contra FTP en modo ASCII, y todavía se encuentra en todos los archivos que escribe una herramienta seria porque la falla que previene es silenciosa y total
El cuerpo contiene los objetos, cada uno numerado
Todo lo que compone el documento vive en el cuerpo como una secuencia plana de objetos indirectos. Cada uno se abre con dos enteros y la palabra clave obj, contiene su contenido y se cierra con endobj. El objeto 1 en la muestra anterior es el nodo del árbol de páginas: 1 0 obj, luego un diccionario, luego endobj. El primer entero es el número del objeto, el segundo es el número de generación. La generación es casi siempre cero en un archivo recién escrito; aumenta solo cuando un número de objeto se reutiliza entre ediciones, lo que es lo suficientemente raro como para que usted pueda tratar una generación distinta de cero como una señal de que el archivo ha pasado por actualizaciones incrementales. El contenido entre las palabras clave es un diccionario aquí, escrito entre << y >>, pero bien podría ser un número, una cadena, una matriz o un flujo
Lo que hace de esto un gráfico en lugar de una lista es el token de referencia 2 0 R. Eso significa "objeto 2, generación 0, dondequiera que se encuentre en el archivo". El nodo del árbol de páginas anterior no contiene su página; apunta al objeto 2, que apunta a sus recursos y flujo de contenido mediante el mismo mecanismo. El cuerpo se presenta en el orden que le resultó conveniente al escritor, y las referencias lo unen en un árbol enraizado en el catálogo. La posición en el archivo no tiene significado. La identidad proviene del número del objeto, y la ubicación proviene de la tabla de referencias cruzadas
La tabla de referencias cruzadas es un índice de desplazamientos de bytes
La tabla xref es lo que convierte los números de objeto en posiciones de archivo. Es la razón por la que un lector puede abrir un documento de mil páginas y renderizar la página 850 sin analizar las 849 páginas anteriores. Cada entrada registra exactamente dónde comienza su objeto, contado en bytes desde el inicio del archivo:
xref
0 6 % 6 entradas, a partir del objeto 0
0000000000 65535 f % entrada 0: cabeza de la lista libre
0000000015 00000 n % el objeto 1 comienza en el byte 15
0000000074 00000 n % el objeto 2 comienza en el byte 74
0000000192 00000 n % el objeto 3 comienza en el byte 192
0000000291 00000 n % el objeto 4 comienza en el byte 291
0000000409 00000 n % el objeto 5 comienza en el byte 409
El ancho fijo es deliberado. Cada entrada tiene exactamente veinte bytes: un desplazamiento de diez dígitos, un espacio, una generación de cinco dígitos, un espacio, un tipo de un carácter y un final de línea de dos bytes. Debido a que las filas son uniformes, un lector puede indexar directamente a la entrada del objeto n por aritmética en lugar de escanear, por lo que la tabla que brinda acceso aleatorio al cuerpo es a su vez accesible aleatoriamente. La línea 0 6 es un encabezado de subsección: dice que las siguientes entradas describen seis objetos a partir del número 0
El objeto 0 es especial y siempre está presente. Su tipo es f de libre, su generación es 65535, y encabeza la lista enlazada de números de objetos libres. En un archivo que nunca ha sido editado, la lista libre es solo esta entrada, una formalidad. Se gana su lugar durante las actualizaciones incrementales, cuando la eliminación de un objeto agrega su número a esa lista para que una edición posterior pueda reclamarlo. Las otras entradas son de tipo n de en uso, y su número de diez dígitos es el desplazamiento que usted buscaría para leer la definición de ese objeto
El tráiler es el punto de entrada, y se encuentra al final
El tráiler es lo primero que un lector realmente consume, aunque se escribe al final. Un analizador abre el archivo, busca hasta el final y camina hacia atrás buscando %%EOF. Justo encima se encuentra startxref seguido de un solo número, y ese número es el desplazamiento en bytes de la palabra clave xref. Con él, el lector salta directamente a la tabla de referencias cruzadas sin haber escaneado ni un solo objeto:
trailer
<<
/Root 5 0 R % el catálogo de documentos
/Size 6 % uno más que el número de objeto más alto
>>
startxref
459 % desplazamiento de bytes de la tabla xref
%%EOF
El diccionario de tráiler contiene los dos valores que necesita un lector antes de poder hacer cualquier otra cosa. /Root apunta al catálogo de documentos, el objeto 5 aquí, que es la parte superior del gráfico de objetos y la ruta al árbol de páginas. /Size es el recuento de entradas que debe contener la tabla de referencias cruzadas, que es uno más que el número de objeto más alto debido a la entrada libre en la ranura cero. A partir de %%EOF se despliega toda la secuencia de lectura: busque el marcador, lea startxref para localizar la tabla, cargue la tabla para aprender dónde vive cada objeto, lea /Root para encontrar el catálogo y resuelva los objetos a pedido a partir de ahí. El encabezado, que se encuentra en la parte superior, apenas se consulta hasta tarde. El mapa en la parte inferior es lo que el lector necesita primero
La actualización incremental añade un segundo mapa en lugar de reescribir
Ese diseño de cola a cabeza vale la pena cuando un archivo cambia. Se puede editar un PDF sin reescribir ninguno de los bytes que ya están en el disco. Los objetos nuevos y modificados se agregan al final, seguidos de una nueva sección de referencias cruzadas y un nuevo tráiler, y el archivo original debajo se deja intacto. La única información contable nueva es una entrada /Prev en el nuevo tráiler, que contiene el desplazamiento en bytes de la tabla de referencias cruzadas anterior:
% ... archivo original, sin cambios, termina aquí ...
6 0 obj % un objeto agregado por esta edición
<< /Type /Annot /Subtype /Text /Rect [100 700 120 720] >>
endobj
xref % una segunda sección xref, solo para el nuevo objeto
6 1
0000000612 00000 n
trailer
<<
/Root 5 0 R
/Size 7
/Prev 459 % desplazamiento de bytes de la tabla xref anterior
>>
startxref
680 % desplazamiento de esta nueva sección xref
%%EOF
Un lector sigue comenzando en el %%EOF final, sigue siguiendo startxref a la tabla más reciente, pero ahora sigue la cadena /Prev hacia atrás a tablas más antiguas, fusionándolas para que la entrada más reciente para cualquier número de objeto gane. Las secciones de referencias cruzadas forman una lista enlazada a través del archivo, cada una anulando a la anterior para los objetos que toca. Un objeto que una edición reemplazó todavía existe físicamente en su antiguo desplazamiento; simplemente ya no es accesible, porque una entrada xref posterior apunta a un lugar más nuevo
Este es el mecanismo que hace que los archivos PDF firmados sean verificables. Una firma digital cubre un rango de bytes del archivo, y debido a que una actualización incremental solo agrega, los bytes firmados nunca se mueven. La firma aún se valida contra el rango original, mientras que las revisiones posteriores se encuentran más allá de él, cada una con su propio xref y tráiler. También es por eso que un PDF puede llevar un historial recuperable: cada objeto reemplazado todavía está en el disco bajo una sección de referencias cruzadas anterior, que es una característica para el seguimiento de versiones y un pasivo para cualquiera que haya pensado que "eliminar" significaba que los bytes habían desaparecido
El costo es el crecimiento. Cada edición se agrega; no se recupera nada en su lugar, por lo que un archivo revisado muchas veces acumula objetos muertos y una larga cadena de secciones xref. El remedio es una reescritura completa: cargue el documento y guárdelo de nuevo, lo que vuelve a numerar los objetos sobrevivientes, descarta los inaccesibles y emite una sola tabla limpia de referencias cruzadas. Las dos estrategias se intercambian directamente. Agregar es rápido y preserva firmas y el historial; reescribir es más lento y descarta ambos, a cambio de un archivo compacto
Lectura de las cuatro partes en la práctica
Conocer el diseño es suficiente para depurar a mano la mayoría de los problemas de "este archivo no se abre". Si un lector rechaza un PDF, los culpables habituales están en los dos extremos, no en el medio. Una descarga truncada pierde el tráiler, por lo que falta startxref o %%EOF y el lector no tiene punto de entrada; los lectores tolerantes recurren a escanear todo el archivo para reconstruir el xref, que es exactamente el camino lento que la tabla pretendía evitar. Una transferencia de modo de texto fallida corrompe los bytes de flujo o los desplazamientos dejan de coincidir con la realidad, y los objetos se cargan desde la posición incorrecta. Cuando los desplazamientos en la tabla ya no apuntan a las palabras clave obj reales, el archivo se rompe estructuralmente incluso si cada objeto individualmente está bien
Para código nuevo, la lección del diseño es dejar que una biblioteca se apropie de la contabilidad de los bytes. Los desplazamientos en la tabla de referencias cruzadas deben coincidir con las posiciones reales de cada objeto con el byte, el tráiler debe apuntar a la tabla correcta y las actualizaciones incrementales deben encadenarse correctamente a través de /Prev. Un componente nativo como el Componente HotPDF para Delphi y C++Builder maneja todo eso cuando escribe un archivo, incluida la elección entre agregar una revisión incremental y reescribir una compacta. Si desea ver cómo se construye la misma estructura desde la nada en lugar de disecarla, el artículo complementario sobre la creación de un documento PDF desde cero describe la emisión del encabezado, los objetos, la tabla xref y el tráiler en orden