Artículo técnico

Árboles de estructura PDF etiquetados con PDF Library for Delphi

Un PDF accesible descansa sobre una estructura que la página visible nunca muestra: el árbol de estructura definido en ISO 32000-1 §14.7. Es una jerarquía lógica de encabezados, párrafos, tablas y figuras, superpuesta sobre el contenido pintado y mapeada a roles estándar a través de un mapa de roles. Un lector de pantalla lee ese árbol, no las marcas en la página. Sin él, una factura generada que luce impecable es semánticamente vacía, porque el flujo de contenido registra orden de dibujo y nada más. El total se puede anunciar antes de que las líneas, el pie puede cortar un párrafo, y la tabla de líneas puede colapsar en una corrida indiferenciada de palabras. El costo de prevenir eso es desigual a su favor. Emitir estructura mientras dibuja son minutos de código; retroajarla en documentos terminados es un proyecto de remediación. losLab PDF Library (PDF Library for Delphi) expone el árbol a Delphi y C++Builder a través de un conjunto pequeño de llamadas que envuelven cada operación de dibujo en su rol lógico

Cómo el contenido marcado se liga al árbol de estructura

Dos capas cooperan. En el flujo de contenido, las operaciones de dibujo se enmarcan en secuencias de contenido marcado, cada una cargando un MCID entero. En el catálogo del documento, el árbol de estructura mapea esos MCID a una jerarquía de elementos tipados (H1, P, Table, Figure) con atributos como texto alternativo e idioma. Los tipos de elemento personalizados son legales, pero cada uno debe resolver a un rol estándar a través del mapa de roles (ISO 32000-1 §14.8.4). El contenido que no carga significado en absoluto, como reglas, fondos y mobiliario de página repetido, se marca como artefacto para que la tecnología asistiva lo salte en lugar de leerlo a mitad de frase

PDF Library for Delphi mantiene ambas capas detrás de un par de llaves. BeginTag abre un elemento de estructura e inicia la secuencia de contenido marcado, las llamadas de dibujo caen dentro, y EndTag cierra ambos. La contabilidad que tropieza al etiquetado hecho a mano — los MCID y el árbol padre y las referencias de página — ocurre internamente donde no puede equivocarla

Diagrama de PDF Library for Delphi que vincula corridas de contenido marcado con MCIDs enteros a un árbol de estructura H1, P y Figure mediante el role map, con los artefactos excluidos del orden de lectura
Los MCID enteros atan las secuencias de contenido marcado a un árbol de estructura tipado, mientras que el mapa de roles resuelve los roles personalizados y los artifacts quedan fuera del orden de lectura

Dos interruptores a nivel de documento enmarcan el trabajo antes de que se abra alguna etiqueta. SetMarkInfo escribe la bandera de catálogo que declara el documento etiquetado, e IsTaggedPDF la lee de vuelta, que es el primer sondeo barato al decidir si un archivo entrante tiene estructura que valga la pena preservar. El idioma tiene dos puntos de entrada. SetDocumentLanguage fija el predeterminado del documento por sí solo, mientras que SetPDFUAMode lo fija como parte de habilitar la salida PDF/UA completa. Un archivo puede ser etiquetado útilmente sin reclamar conformidad PDF/UA, y un despliegue por fases a menudo empieza justo ahí

Etiquetado al dibujar, no después

El patrón de generación que funciona es tratar la llave de etiqueta como parte de la firma de cada llamada de dibujo, nunca como un pase posterior:

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);                          // origen en la esquina superior izquierda
    Lib.SetPDFUAMode('en-US');                 // eleva la versión de guardado a PDF 1.7
    Lib.SetInformation(1, 'Service Manual');   // /Title es obligatorio para PDF/UA
    Lib.AddRoleMap('ManualTitle', 'H1');       // tipo personalizado -> rol estándar
    Lib.AddStandardFont(4);
    Lib.SetTextSize(18);
    Lib.BeginTagEx2('ManualTitle', '', '', 'en-US', '', 'h1-cover', '');
    Lib.DrawText(72, 96, 'Service Manual');
    Lib.EndTag;
    Lib.BeginTag('Figure', 'Exploded view of the gearbox assembly', '');
    Lib.AddImageFromFile('gearbox.png', 0);
    Lib.EndTag;
    Lib.BeginArtifact('Layout');               // decoración de página: excluida de la lectura
    // ... dibuja líneas y tinte de fondo ...
    Lib.EndArtifact;
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Tres llamadas en esa secuencia cargan peso de cumplimiento. SetPDFUAMode habilita la salida PDF/UA y silenciosamente eleva la versión del documento a PDF 1.7, lo que colisiona con el fijado de versión. Un documento bloqueado a PDF 1.4 con LockSaveVersion se rehúsa a guardar y devuelve el código de error 602 una vez que el modo UA está activo, un choque que tiende a salir a la luz cuando los perfiles de archivo y los requisitos de accesibilidad los configuran equipos distintos. SetInformation(1, ...) escribe el título del documento, que ISO 14289 espera que los visores muestren en lugar del nombre del archivo; su ausencia es uno de los hallazgos PDF/UA más comunes en el medio silvestre. AddRoleMap registra el tipo personalizado ManualTitle como un H1, y saltárselo deja la diagnóstica descrita abajo marcando un rol sin mapear

Los niveles de encabezado merecen una política deliberada, no decisiones ad-hoc tomadas por el aspecto de una página. Los usuarios de lector de pantalla saltan entre secciones por atajo de encabezado, así que una plantilla que va de H1 a H3 porque el nivel intermedio se veía demasiado grande en el diseño visual rompe silenciosamente esa navegación, y ninguna revisión visual nunca la atrapará. Es exactamente el defecto que la diagnóstica HEADING-LEVEL-SKIP existe para nombrar. Mapee los estilos visuales de cada plantilla a una escalera de encabezados fija una vez, en un solo lugar, y la deriva nunca empieza

Tablas que un lector de pantalla en realidad puede navegar

Líneas de cuadrícula dibujadas no significan nada fuera de pantalla. Lo que los lectores de pantalla navegan son relaciones estructurales: qué celdas son encabezados, qué gobierna cada encabezado, y cómo las celdas de datos se ligan a encabezados en diseños irregulares. Las llamadas de atributo de elemento de estructura manejan los tres:

Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column');          // válido solo mientras este TH está abierto
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2);               // el encabezado abarca las columnas de valor y unidad
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part');      // vinculación explícita para tablas irregulares
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Tabla

La regla de orden es estricta y se hace cumplir en silencio. Cada llamada SetStructElem* aplica a la etiqueta que está abierta en ese momento, entre su BeginTag y su EndTag, y devuelve 0 sin levantar nada cuando ninguna etiqueta está abierta o el atributo no aplica a la actual. Una llamada mal colocada simplemente se desvanece. Envolver los valores de retorno en aserciones durante el desarrollo atrapa la deriva mientras todavía puede verla; dejada sola, una visibilidad faltante sale a la luz solo cuando una auditoría de accesibilidad corre un lector de pantalla real a lo largo de la tabla. Los ID de elemento que se pasan por BeginTagEx2 alimentan el árbol de ID (ISO 32000-1 §14.7.4), y eso es lo que hace que la ligadura SetStructElemHeaders sea resoluble en primer lugar

La misma familia de atributos cubre el resto de lo que la tecnología asistiva necesita. SetStructElemListNumbering declara cómo se etiquetan los ítems de lista, así un lector de pantalla anuncia la posición dentro de la lista en lugar de recitar glifos de viñeta. SetStructElemBBox registra la caja delimitadora de figuras y tablas, que las vistas de reflujo usan para colocar contenido. SetStructElemActualText suministra texto de reemplazo para corridas cuyos glifos no se mapean a caracteres legibles, como una capital inicial ensamblada a partir de arte vectorial. Cada uno sigue la misma regla: se liga a la etiqueta abierta, o se desvanece

Diagrama de tabla de PDF Library for Delphi que muestra el scope de TH, un colspan de dos y un atributo headers vinculándose a celdas de datos, junto a la regla de que las llamadas de atributos solo se vinculan mientras su etiqueta está abierta
Los lectores de pantalla siguen el scope de TH, el colspan y los enlaces headers en lugar de las reglas dibujadas, y las llamadas de atributo solo se vinculan mientras su etiqueta está abierta

Artefactos, idioma y la compuerta diagnóstica de pre-guardado

El mobiliario de página repetido, es decir encabezados corrientes, marcas de pliegue, marcas de agua y tintes de fondo, pertenece dentro de llaves BeginArtifact y EndArtifact para que nunca entre al flujo de lectura. El idioma es heredable. El predeterminado del documento viene del argumento SetPDFUAMode, y una corrida en otro idioma lo sobreescribe por elemento a través de BeginTagEx o SetStructElemLang. Eso es lo que mantiene pronunciable una cita en francés dentro de un manual en inglés

Antes de guardar, GetPDFUADiagnostics corre los chequeos estructurales de la biblioteca sobre el documento en memoria y devuelve hallazgos como texto, donde una cadena vacía significa que no se encontró nada. Los códigos nombran los errores clásicos de autoría directamente: FIGURE-NO-ALT para una imagen sin texto alternativo, HEADING-LEVEL-SKIP para un H3 siguiendo a un H1, ROLEMAP-UNMAPPED para un tipo personalizado que nunca se registró. Cablee esto en el build (genere el conjunto de documentos, falle el paso con diagnóstica no vacía) y las regresiones de accesibilidad se convierten en fallas estilo-tiempo-de-compilación en lugar de hallazgos de auditoría meses después. El veredicto de conformidad completo todavía pertenece al preflight sobre el archivo guardado, cubierto en preflight de PDF/A y PDF/UA en Delphi, porque algunas normalizaciones se aplican solo durante la serialización

La navegación de anotaciones tiene su propia perilla. PDF/UA espera que el recorrido por teclado de campos de formulario y enlaces siga el orden de estructura, y SetTabOrderMode escribe la entrada de orden de tabulación a nivel de página que los visores honran, con GetTabOrderMode disponible para auditar archivos entrantes. Es el tipo de requisito que nadie nota hasta que un usuario solo de teclado reporta el error, y cuesta una llamada por documento hacerlo bien

Los árboles de estructura no sobreviven toda fusión

Los documentos etiquetados permanecen etiquetados solo cuando cada paso de procesamiento posterior preserva el árbol, y el borde afilado dentro de PDF Library for Delphi es la familia de lista de fusión. MergeFileListFast intercambia preservación de árbol de estructura por velocidad. Ese es el trato correcto para lotes de imágenes escaneadas y el equivocado para reportes etiquetados, porque la salida abre bien, se renderiza idéntica, y ha perdido silenciosamente su capa de accesibilidad. Use el MergeFileList predeterminado o la variante estricta siempre que cualquier entrada sea un PDF etiquetado, y haga de IsTaggedPDF parte de las aserciones post-ensamblaje para que un lote aplanado no pueda enviarse sin que alguien lo note. Los pipelines de ensamblaje para conjuntos grandes de documentos cargan más compromisos de este tipo, explorados en fusión, división y acceso directo de PDF grandes

Diagrama de PDF Library for Delphi de GetPDFUADiagnostics devolviendo una cadena vacía o hallazgos con nombre como FIGURE-NO-ALT que hacen fallar la compilación antes de que el preflight juzgue el fichero guardado
GetPDFUADiagnostics informa de hallazgos como FIGURE-NO-ALT antes de guardar, y un resultado no vacío conectado al build hace fallar el paso de inmediato

El lazo de verificación se cierra fuera de la biblioteca: abra la salida en Acrobat, inspeccione el panel de etiquetas, y lea al menos un documento por familia de plantillas con un lector de pantalla real. La diagnóstica atrapa errores estructurales; solo un oído humano atrapa un orden de lectura que es técnicamente válido y prácticamente desconcertante. Las compilaciones de evaluación y la referencia completa de la API de etiquetado están en la página del producto losLab PDF Library para Delphi