Artículo técnico

Validación PDF/UA-2 en Delphi: más que UA-1 parte 2

PDFlibPas valida archivos PDF/UA-2 (ISO 14289-2) en Delphi mediante TPDFlib.CheckFilePDFUACompliance con una parte objetivo de 2, lo cual ejecuta un perfil PDF/UA-2 separado: resuelve los roles de estructura a través de namespaces de PDF 2.0 y de /RoleMapNS, reporta la entrada truncada o ilegible como falla, y devuelve un informe de cobertura que nunca afirma la certificación completa

Esa última parte importa más de lo que suena. La validación de accesibilidad es un terreno donde una palomita verde se copia a una hoja de cálculo de compras y nadie la vuelve a cuestionar. Un validador que se salta en silencio la mitad del estándar y aun así imprime "pass" es peor que no tener validador, porque convierte una incógnita en un dato falso. PDFlibPas toma la postura contraria: verificar lo que se puede verificar de forma mecánica, decir en voz alta lo que no se verificó, y tratar cada "no se pudo terminar" como un hallazgo y no como un éxito

¿Por qué no se puede validar PDF/UA-2 con reglas de PDF/UA-1?

PDF/UA-2 no es PDF/UA-1 con el número de parte incrementado. PDF/UA-1 se apoya en PDF 1.7 y en un único role map plano; PDF/UA-2 se apoya en PDF 2.0 (ISO 32000-2), donde cada elemento de estructura puede pertenecer a un namespace y cada namespace lleva su propio role map. Un verificador UA-1 apuntado a un archivo UA-2 a la vez pasa por alto problemas reales e inventa problemas falsos

Los falsos son la parte vergonzosa. Los validadores PDF/UA-1 típicamente imponen reglas sobre los elementos Note que llevan un ID y sobre cómo puede usarse el encabezado sin numerar H. Ninguna de las dos reglas se traslada. En el namespace de estructura de PDF 2.0, las notas al pie se expresan con FENote, y los encabezados van de H1 a Hn sin techo de seis niveles. PDF/UA-2 va más allá y prohíbe de plano el rol sin numerar H. Aplique la lógica de encabezados UA-1 a ese árbol y o se queja de cosas que el estándar permite o deja pasar lo único que prohíbe

Por eso el perfil PDF/UA-2 de PDFlibPas es una ruta de código separada, no una opción del antiguo. También se niega a reutilizar los números de cláusula de UA-1. Los diagnósticos compartidos con el verificador UA-1 se reasignan a ISO 14289-2 en los informes UA-2, y donde un número de cláusula no ha sido verificado contra el texto del estándar, el informe deja el campo de cláusula vacío en lugar de adivinar. Una cláusula vacía es menos satisfactoria que un número de cláusula seguro, pero es la única salida defendible cuando nadie ha confirmado el mapeo

Cómo PDFlibPas resuelve roles mediante RoleMapNS

PDFlibPas resuelve cada rol de estructura a un rol estándar en un namespace conocido, siguiendo las cadenas de /RoleMapNS a través de namespaces, y reporta cualquier cadena que termine en un rol personalizado, forme un ciclo o rompa las reglas de mapeo. Este es el núcleo de la validación de estructura PDF/UA-2, porque la tecnología de asistencia solo entiende roles estándar; un /InvoiceLine personalizado no significa nada hasta que aterriza en algo como P o TR

El resolvedor parte del nombre /S del elemento y de su namespace /NS. Sin namespace, el elemento está implícitamente en el namespace estándar de PDF 1.7 y aplica el /RoleMap heredado de la raíz del árbol de estructura. Con namespace, aplica el /RoleMapNS propio del diccionario del namespace, y un mapeo puede apuntar a un rol en un namespace diferente mediante un arreglo de dos elementos. Las verificaciones que salen de esto son concretas:

  • Un namespace referenciado desde un elemento de estructura debe ser un diccionario indirecto con un URI /NS válido y debe estar listado en el arreglo /Namespaces de la raíz del árbol de estructura (diagnóstico 10054)
  • Un rol personalizado debe terminar resolviéndose a un rol estándar en el namespace de PDF 1.7, el namespace de PDF 2.0 (http://iso.org/pdf2/ssn) o el namespace de MathML; las cadenas circulares y los callejones sin salida se reportan como 10055
  • Un rol estándar no puede remapearse a uno personalizado, y un rol no puede remapearse dentro de su propio namespace explícito
  • La raíz de estructura debe contener exactamente un elemento Document en el namespace de PDF 2.0 (10057), y el /P de cada elemento debe apuntar a su padre real (10058)
  • Un H sin numerar, o un Note heredado de PDF 1.7 que no esté mapeado a un rol de PDF 2.0, se reporta como 10056

Un detalle que vale la pena copiar a su propia herramienta: PDFlibPas valida cada mapeo declarado, no solo los alcanzables desde el árbol. Una entrada rota en un role map que ningún elemento usa actualmente sigue siendo un defecto latente, porque la próxima actualización de contenido empezará a usarla. MathML también se trata con honestidad. El namespace de MathML se reconoce como un objetivo de resolución válido, pero el vocabulario MathML en sí no se valida, y el informe de cobertura lo dice

Ejecutar una verificación PDF/UA-2 desde Delphi

TPDFlib.CheckFilePDFUACompliance es el punto de entrada de una sola llamada: pase el archivo, la contraseña, la parte objetivo y las opciones, y recibe de vuelta un handle de lista de cadenas con los problemas codificados. Un handle de cero con LastErrorCode igual a cero significa que las verificaciones implementadas no encontraron nada; un LastErrorCode distinto de cero significa que la llamada en sí falló

var
  Lib: TPDFlib;
  Issues, I: Integer;
begin
  Lib := TPDFlib.Create;
  try
    // TargetPart: 0 = detectar desde XMP pdfuaid:part, 1 = PDF/UA-1, 2 = PDF/UA-2
    // Options: 1 = detener en la primera falla, 0 = recolectar cada problema
    Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
    if Issues = 0 then
    begin
      if Lib.LastErrorCode <> 0 then
        Writeln('Validation did not run, error ', Lib.LastErrorCode)
      else
        Writeln('No issues from the implemented PDF/UA-2 checks');
    end
    else
    try
      for I := 1 to Lib.GetStringListCount(Issues) do  // los ítems empiezan en 1
        Writeln(Lib.GetStringListItem(Issues, I));
    finally
      Lib.ReleaseStringList(Issues);
    end;
  finally
    Lib.Free;
  end;
end;

Elija la parte objetivo con intención. Un objetivo de 0 lee pdfuaid:part de los metadatos XMP y elige el perfil a partir de ahí, lo cual es correcto para un validador de bandeja de entrada genérico. Para su propio pipeline de salida, pase 2 explícitamente. Si su generador olvidó escribir la identificación, la autodetección cae en las reglas UA-1 y termina validando el estándar equivocado; un 2 explícito en cambio produce el diagnóstico 10050 por la parte faltante, más 10051 si pdfuaid:rev no es 2024 y 10052 si la versión efectiva de PDF no es 2.0. Del lado de la autoría, TPDFlib.SetPDFUA2Mode escribe esa identificación, sube la versión a 2.0 y selecciona el namespace de estructura de PDF 2.0 como predeterminado; el modelo de namespaces en sí se explica en el recorrido por los namespaces de PDF 2.0, PDF/A-4 y PDF/UA-2

¿Qué pasa cuando el validador se queda sin presupuesto?

PDFlibPas falla cerrado: cuando el verificador PDF/UA-2 golpea un límite de recorrido, un ciclo, un límite del parser o un documento que no puede abrir, emite un diagnóstico, y un resultado sin diagnósticos nunca se promueve a aprobado. El diagnóstico 10059 está reservado exactamente para este caso de "el lector no pudo terminar"

Los presupuestos son explícitos y están publicados en el JSON de cobertura: profundidad de estructura 256, 100.000 objetos de estructura y 256 saltos a lo largo de cualquier cadena de role map. Esos números existen porque los árboles de estructura provienen de entrada no confiable. Un archivo hostil o simplemente roto puede anidar elementos miles de niveles hacia abajo, apuntar un hijo de vuelta a un ancestro, o construir un role map que se encadene a través de cientos de namespaces. Un validador sin presupuestos o se crashea o se cuelga, y un validador con presupuestos pero sin diagnósticos hace algo más sutil y peor: se detiende temprano, no encuentra nada malo en la parte que leyó y reporta éxito

El mismo principio aplica un nivel arriba. Si el lector subyacente devuelve un score menor a 100 pero sin líneas de problemas, lo cual puede ocurrir cuando el parseo aborta antes de que corra alguna regla, PDFlibPas inserta una línea 10059 para que la lista vacía no pueda confundirse con un resultado limpio. Una excepción durante la verificación también se convierte en una línea 10059. Es una pequeña cantidad de código, y cierra la forma más común en que los pipelines de validación le mienten a sus dueños

Compartir un solo parseo con PDF/A y construir un informe

Para trabajo por lotes, la API de pipeline de validación parsea un archivo una vez y ejecuta varias verificaciones contra el parseo compartido. RunPDFUAValidationPipelineTask es el miembro PDF/UA de esa familia y toma los mismos valores de parte objetivo que la función de una sola llamada, lo que evita que un preflight de PDF/A más PDF/UA-2 pague por dos lecturas completas de un documento grande

var
  Pipe, PDFAIssues, UAIssues: Integer;
  Json: WideString;
begin
  Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
  if Pipe = 0 then
    Exit;
  try
    PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0);       // 1 = PDF/A
    UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0);    // PDF/UA-2
    // ... inspeccione y libere ambos handles como en el ejemplo anterior ...
    Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
    // ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = ambos; ReportFormat 1 = JSON
    Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
  finally
    Lib.ReleaseValidationPipeline(Pipe);
  end;
end;

El informe estructurado etiqueta la sección de accesibilidad como PDF/UA-2 e ISO 14289-2 cuando el archivo se identifica como parte 2, y cada sección PDF/UA lleva "fullCertification": false junto con el objeto de cobertura embebido. El informe de preflight en texto agrega una línea que declara que solo corrieron las verificaciones implementadas y que la certificación completa de accesibilidad requiere validación adicional y revisión humana. Los formatos de informe más amplios, los IDs de reglas y los patrones de gating de CI se describen en la guía de preflight PDF/A y PDF/UA

¿Qué garantiza realmente el informe de cobertura?

TPDFlib.GetPDFUAReadValidationCoverageJSON garantiza una cosa: una lista exacta de lo que el verificador PDF/UA-2 examinó y de lo que no. Para la parte 2 devuelve fullCertification fijado en false, un arreglo checked, un arreglo unverified y los límites de recorrido. No garantiza que un archivo sea accesible, y está diseñado de modo que ninguna ruta de código pueda hacer que diga lo contrario

var
  Coverage: WideString;
begin
  Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
  // Guárdelo junto al resultado de validación, no en lugar de él
  TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
  if Pos('"fullCertification":false', Coverage) > 0 then
    Writeln('Machine checks only; schedule a human accessibility review');
end;

La lista checked cubre identificación (parte, revisión, prefijo), versión efectiva de PDF, metadatos de título, /MarkInfo, DisplayDocTitle, referencias de namespaces, resolución de role maps, la raíz del documento, padres de estructura, ciclos del árbol y las reglas de H y Note. La lista unverified es igual de específica: etiquetado de contenido y orden de lectura, contención de estructura, fuentes y mapeo Unicode, anotaciones y formularios, lenguaje natural, descripciones alternativas, el vocabulario MathML y la revisión humana de accesibilidad. Varios de esos puntos ni siquiera son decidibles mecánicamente. Ningún parser puede decirle si un texto alternativo realmente describe la gráfica, o si el orden de lectura tiene sentido para alguien que lo escucha

Trate el JSON de cobertura como parte del resultado. Archívelo con cada ejecución de validación, y cuando un auditor pregunte qué significaba "passed" en una fecha dada, puede entregar el alcance exacto en lugar de reconstruirlo de memoria y de un número de versión

Verificación cruzada con veraPDF

El núcleo PDF/UA-2 se verificó en cruz contra el validador de línea de comandos veraPDF 1.30.3 usando su perfil ua2, y los resultados coinciden donde la cobertura se traslapa. Una muestra válida del núcleo generada de forma independiente pasó en ambas herramientas; una muestra que declara pdfuaid:rev 2023 y una muestra que usa el rol sin numerar H fallaron en ambas herramientas

Una cuarta muestra es la que vale la pena comentar. Tenía contenido de dibujo que no estaba etiquetado para nada, y veraPDF lo marcó mientras que las verificaciones del núcleo de PDFlibPas no, porque el etiquetado de contenido está fuera de la cobertura actual. Ese hueco es exactamente la razón por la que content-tagging-and-reading-order encabeza la lista unverified. Correr un segundo validador es un seguro barato, y un desacuerdo entre dos herramientas suele ser la forma más rápida de encontrar el borde de lo que cada una verifica. Para el lado de la autoría del mismo problema, construir árboles que pasen desde el principio, vea la guía del árbol de estructura de PDF etiquetado

Preguntas frecuentes

¿PDFlibPas certifica un archivo como conforme a PDF/UA-2?

No. PDFlibPas ejecuta las verificaciones mecánicas implementadas para PDF/UA-2 y devuelve un informe de cobertura con fullCertification siempre en false, listando cada área que todavía necesita otras herramientas o revisión humana

¿Qué valor de TargetPart debo pasar a CheckFilePDFUACompliance?

Pase 2 cuando sepa que el archivo debe ser PDF/UA-2, de modo que la identificación faltante se reporte. Pase 0 para autodetectar la parte desde XMP pdfuaid:part, y 1 para forzar las reglas de PDF/UA-1

¿Qué significa el diagnóstico 10059 en un informe PDF/UA-2?

El diagnóstico 10059 significa que el validador no pudo completarse: el archivo no pudo parsearse o abrirse, se excedió un límite de recorrido, se encontró un ciclo o ocurrió una excepción. Siempre cuenta como falla, nunca como aprobado

La validación PDF/UA-2, el pipeline de validación compartido y las APIs de autoría PDF 2.0 vienen en el losLab PDF Library para Delphi, junto con el preflight PDF/A y los informes de validación estructurados