Technical Article

Metadatos, esquemas y anotaciones en PDF: guía técnica

Si se eliminan las descripciones de página, queda una capa delgada de estructura que nadie imprime pero de la que dependen todos los lectores, indexadores y sistemas de archivo. Un objeto de página no sabe nada del capítulo al que pertenece, del autor que lo escribió ni de la nota al pie que enlaza a otro lugar. Ese conocimiento vive un nivel más arriba, en tres estructuras unidas al catálogo del documento: los flujos de metadatos, el árbol de esquemas y los arrays de anotaciones por página. Comparten una característica que facilita cometer errores. Ninguna deja marcas visibles en la página, de modo que un fichero puede renderizarse perfectamente y aun así carecer de sus marcadores, contradecir su propio campo de autor o apuntar un enlace a un objeto de página que ya no existe.

Esta es la capa que una biblioteca PDF expone como propiedades del documento, APIs de marcadores y llamadas de enlace o anotación, y la que un rastreador de búsqueda lee para decidir de qué trata el documento. El modelo de objetos subyacente se explica en el recorrido por la estructura del documento PDF. Aquí el foco está exclusivamente en lo que cuelga del catálogo.

Las tres estructuras se anclan en el catálogo. Un catálogo completo que las conecta tiene este aspecto:

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
   /Outlines 3 0 R
   /Names << /EmbeddedFiles 4 0 R >>
   /Metadata 5 0 R
>>
endobj

Cuatro entradas, cuatro subsistemas independientes. /Pages es el documento visible; /Outlines es el árbol de marcadores; /Metadata apunta al flujo XMP; /Names da acceso al diccionario de nombres del documento, que entre otras cosas alberga los adjuntos de fichero incrustados. Cada uno es opcional, y un lector que no encuentre ninguno sigue mostrando las páginas. Esa opcionalidad es precisamente la razón por la que la capa de navegación es lo primero que se deteriora cuando un fichero se edita con herramientas que solo entienden páginas.

Dos almacenes de metadatos en conflicto

PDF almacena los metadatos del documento en dos lugares a la vez, y los problemas empiezan cuando dicen cosas distintas. El mecanismo original es el diccionario de información del documento, al que se hace referencia con /Info en el tráiler: un conjunto plano de pares clave-valor para /Title, /Author, /Subject, /Keywords, /Creator, /Producer y las dos fechas. Es sencillo y todos los visualizadores lo leen. PDF 2.0 depreca la mayor parte en favor del segundo mecanismo: el flujo de metadatos XMP.

XMP es un documento XML autónomo, escrito en RDF, almacenado como un flujo al que el catálogo accede a través de /Metadata y marcado como /Type /Metadata /Subtype /XML. A diferencia del diccionario Info enterrado dentro de la estructura de objetos PDF, un paquete XMP está diseñado para ser extraído y analizado de forma independiente por herramientas que no saben nada de PDF. He aquí un paquete representativo:

5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
      <dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
      <dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
      <xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
      <xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
      <pdf:Producer>losLab PDF Library</pdf:Producer>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj

Tres detalles de ese bloque determinan si los metadatos sobreviven al contacto con las herramientas reales. Las instrucciones de procesamiento xpacket no son decorativas: enmarcan el paquete para que un extractor pueda localizarlo dentro de un flujo de bytes mayor, y un escritor que omita el cierre <?xpacket end="w"?> produce un fichero que se abre bien pero falla en los validadores estrictos. Los tipos de dato de las propiedades también importan. dc:title es una alternativa de idioma envuelta en rdf:Alt, mientras que dc:creator es una lista ordenada y utiliza rdf:Seq; emitir cualquiera de ellas como nodo de texto simple es el error XMP más habitual, tolerado por la mayoría de los visualizadores hasta que aparece uno que no lo tolera. Los prefijos de espacio de nombres son convencionales, pero los URIs a los que se vinculan son normativos: un analizador se guía por el URI, no por el prefijo.

La regla estricta con dos almacenes es que deben coincidir. Si /Info indica que el autor es una persona y dc:creator nombra a otra, se ha publicado un documento que responde a la misma pregunta de dos maneras distintas, y cuál prevalece depende del campo que lea la herramienta consumidora. Una biblioteca suele escribir ambos automáticamente, pero en cuanto se edita uno a mano o se fusionan ficheros de distintos generadores, ambos divergen. Trata el diccionario Info como compatibilidad heredada y XMP como fuente de verdad, y regenera ambos a partir de un único conjunto de valores en lugar de parchearlos por separado. Para PDF/A esto se convierte en un requisito de conformidad: ISO 19005 exige XMP y prohíbe cualquier propiedad de Info que contradiga su homóloga XMP.

El árbol de esquemas detrás del panel de marcadores

Lo que un visualizador muestra como panel de marcadores es, en el fichero, un árbol de diccionarios con doble enlace denominado esquema del documento. El catálogo apunta a un diccionario raíz del esquema a través de /Outlines; la raíz apunta a sus primeros y últimos elementos de nivel superior; y cada elemento está vinculado a sus vecinos y a su padre. No existe ningún array de marcadores en ningún lugar. Toda la estructura se reconstruye siguiendo referencias, que es precisamente la razón por la que un único enlace roto puede hacer desaparecer una rama entera del panel sin ningún error.

8 0 obj                                    % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj                                    % top-level: a chapter
<< /Title (Chapter 1: Results)
   /Parent 8 0 R /Count 2
   /First 12 0 R /Last 15 0 R >>
endobj
12 0 obj                                   % first child
<< /Title (Introduction)
   /Parent 9 0 R /Next 15 0 R
   /Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj                                   % second child, last sibling
<< /Title (Methodology)
   /Parent 9 0 R /Prev 12 0 R
   /Dest [3 0 R /Fit] >>
endobj

Al leer los enlaces, los invariantes se vuelven evidentes. Cada elemento apunta de vuelta a su /Parent. Los elementos hermanos forman una cadena a través de /Prev y /Next; el primer elemento omite /Prev y el último omite /Next. Un padre nombra a sus primeros y últimos hijos mediante /First y /Last, y los hijos intermedios solo son accesibles recorriendo la cadena de hermanos. Un error en cualquiera de estos invariantes provoca un fallo silencioso: un /Next obsoleto trunca un capítulo, un padre cuyo /Last no termina la cadena deja elementos huérfanos, y el visualizador renderiza lo que puede alcanzar.

El campo /Count lleva un estado que sorprende a muchos. En la raíz y en cualquier elemento expandido contiene el número de descendientes visibles en ese momento; en un elemento contraído es un número negativo cuyo valor absoluto indica cuántos descendientes aparecerían al expandirlo. Por tanto, /Count no es un dato estructural fijo del árbol, sino el estado guardado de apertura o cierre del panel, y un generador que lo fija como un total positivo reabre cada rama que el autor pretendía dejar cerrada.

Cada elemento se justifica apuntando a algún lugar. /Title es lo que muestra el panel; /Dest es el destino al que lleva un clic. Un destino puede estar en línea en el propio elemento, como en el ejemplo anterior, o ser un nombre que se resuelve a través del diccionario de nombres del documento, lo que es preferible cuando muchos marcadores y enlaces apuntan a los mismos lugares, ya que solo hay que corregir el destino movido en un sitio. Una biblioteca generalmente oculta este árbol detrás de un identificador de raíz de esquema y métodos para añadir entradas hijas; en HotPDF el documento expone un OutlineRoot de tipo THPDFDocOutlineObject y gestiona automáticamente los enlaces /Prev, /Next, /Parent y /Count al añadir elementos. Aprovecharlo merece la pena, porque mantener esos invariantes a mano durante las ediciones es donde los esquemas se rompen.

Destinos: la gramática de adónde va un clic

Tanto los marcadores como las anotaciones de enlace apuntan a destinos, y un destino es más que un número de página. Es un array que nombra un objeto de página y luego especifica, mediante un verbo en la segunda posición, cómo debe encuadrarla el visualizador. El más habitual y el más mal usado es /XYZ, con la forma [page /XYZ left top zoom]. Sus tres operandos son independientes y cualquiera puede ser null para indicar "dejar esto como lo tenía el lector". Así, [page /XYZ null null null] salta a la página sin modificar la posición de desplazamiento ni el zoom, que es lo que se suele querer en un enlace "ir a la página". Las cifras están en el espacio de usuario por defecto, medidas desde la esquina inferior izquierda con y creciente hacia arriba, el mismo sistema de coordenadas que usa el contenido de la página. Los autores que vienen de maquetación de pantalla miden instintivamente desde la parte superior y envían al lector al extremo equivocado de la página.

La familia /Fit sacrifica el posicionamiento preciso a favor de la robustez. [page /Fit] escala la página completa para que quepa en la ventana, [page /FitH top] ajusta el ancho de la página con un borde superior dado, y [page /FitR l b r t] hace zoom en un rectángulo para que llene la vista. Como estos destinos calculan la escala a partir de la geometría de la página y no de coordenadas fijas, un destino /Fit sigue funcionando correctamente después de redimensionar la página, mientras que un destino /XYZ con zoom fijo puede dejar al lector mirando el margen. Para un índice de contenidos, /FitH con la coordenada superior de la sección envejece mejor que /XYZ con un zoom calculado a ojo.

Anotaciones: todo lo interactivo que no es contenido de página

Una anotación es un objeto que se superpone a la página sin formar parte de su flujo de contenido. Los enlaces, las notas adhesivas, los resaltados, los widgets de formulario, los iconos de adjuntos de fichero y los sellos son anotaciones, todas enumeradas en el array /Annots de la página en la que se encuentran. Eliminar una anotación de ese array la quita de la página aunque el contenido subyacente no se haya modificado. Ese es el objetivo: las anotaciones son una capa de edición, separada de las marcas sobre las que se sitúan.

Todas las anotaciones comparten una pequeña estructura común. /Subtype indica el tipo, /Rect define su caja delimitadora en coordenadas de página, y /Contents contiene texto que sirve también como descripción accesible. La anotación de enlace es el caso que vale la pena estudiar, porque se presenta en dos formas: un destino directo y una acción.

12 0 obj                                    % link to a destination
<< /Type /Annot /Subtype /Link
   /Rect [100 200 300 250]
   /Border [0 0 0]
   /Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj                                    % link that runs an action
<< /Type /Annot /Subtype /Link
   /Rect [50 50 200 100]
   /Border [0 0 0]
   /A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj

/Rect es una zona activa; al hacer clic dentro de ella se envía al lector al destino, reutilizando la misma gramática que usa el esquema. /Border [0 0 0] realiza un trabajo real: suprime el feo rectángulo por defecto que los visualizadores dibujan alrededor de los enlaces. La segunda forma sustituye el /Dest directo por una acción /A, cuyo subtipo /S selecciona el comportamiento: /GoTo dentro del mismo fichero, /GoToR para otro fichero, /URI para una dirección web, /Launch para ejecutar un programa externo. Este último merece desconfianza. Un /Launch que inicia un ejecutable es el comportamiento que convierte a los PDF en vector de malware, por lo que los visualizadores conformes lo bloquean o muestran una advertencia prominente y el enlace falla para la mayoría de los lectores. Utiliza /URI y /GoTo y deja /Launch de lado.

Las anotaciones de marcado, como los resaltados y las notas adhesivas, y las anotaciones de forma, como /Square, añaden una complicación: su aspecto en pantalla no viene implícito en su tipo. Un visualizador renderiza su propia versión a menos que se fije el aspecto con un flujo de apariencia, la entrada /AP, que referencia un XObject de formulario con los operadores de dibujo. Si se omite, el mismo resaltado puede verse diferente en dos lectores, o antes y después de un ciclo de edición. Para todo aquello cuyo aspecto exacto forma parte del documento, proporciona el /AP. Los adjuntos de fichero, por cierto, reutilizan el mismo mecanismo: un flujo de fichero incrustado y un diccionario de especificación de fichero, accesibles bien como anotación /FileAttachment o a través del árbol de nombres /EmbeddedFiles bajo la entrada /Names del catálogo.

Dónde falla esta capa y cómo detectarlo

El fallo recurrente en todo esto es la referencia colgante. Los marcadores dejan de aparecer cuando el catálogo no tiene entrada /Outlines o cuando una cadena de hermanos se rompe a mitad del árbol; los metadatos se ignoran cuando el flujo XMP carece de su marca /Type /Metadata /Subtype /XML o el envoltorio xpacket está malformado. En todos los casos el contenido de la página está bien, por lo que una apertura casual parece correcta y el defecto solo aflora en el panel que nadie comprobó.

Dos hábitos baratos capturan la mayor parte de los problemas. Abre el fichero terminado en un visualizador real y recorre el panel de marcadores y una muestra de enlaces, con lo que se ejercita el grafo de referencias tal como lo haría un lector. Después, lee los metadatos con una herramienta aparte y confirma que el diccionario Info y el XMP coinciden, pues es la única discrepancia que ninguna cantidad de clics revela. Generar esta capa a través de una biblioteca que gestione el mantenimiento de enlaces evita que la mayoría de estas trampas lleguen a abrirse. El HotPDF Component para Delphi y C++Builder expone las estructuras de esquema, anotación y metadatos a través de APIs a nivel de documento, de modo que se describe la jerarquía de marcadores y los enlaces y se deja que la biblioteca gestione las referencias. Para el modelo de objetos al que se anclan estas estructuras, el resumen técnico de la estructura del fichero PDF cubre el catálogo y la tabla de referencias cruzadas de los que dependen.