PDF/A y PDF/UA responden a dos preguntas que no tienen nada que ver la una con la otra, y tratarlas como una única casilla de verificación de accesibilidad y archivo es la forma en que los archivos rotos llegan a un archivo llevando una etiqueta de cumplimiento. PDF/A pregunta si un archivo seguirá renderizándose fielmente dentro de veinte años. PDF/UA pregunta si la tecnología de asistencia puede leerlo hoy en día. Un documento puede aprobar uno de forma rotunda y suspender el otro, por lo que el único veredicto honesto proviene de ejecutar ambos, y de ejecutarlos antes de que se escriba el archivo, no después de que un sistema posterior confíe en el identificador de conformidad integrado en sus metadatos. Ese identificador es una autodeclaración. Nada en el formato exige que sea cierto, y una aplicación que escribe "PDF/A-1b" en el XMP sin validar frente a la norma produce un archivo que parece compatible para todos los consumidores que solo leen la etiqueta. losLab PDF Library (PDFlibPas) cierra esa brecha para Delphi y C++Builder al integrar ambos validadores en la biblioteca, por lo que la comprobación se ejecuta en el proceso sin ningún servicio externo que levantar
Dos normas que fallan archivos por razones opuestas
ISO 19005 (PDF/A) es un contrato de reproducción. Un archivo conforme tiene que renderizarse de forma idéntica dentro de décadas en un software que nunca vio el sistema que lo produjo, por lo que las reglas atacan las dependencias externas: cada fuente incrustada, el color anclado a un ICC OutputIntent incrustado o expresado en un espacio independiente del dispositivo, sin cifrado en PDF/A-1, sin JavaScript, metadatos XMP que coincidan con el diccionario de información del documento. ISO 14289 (PDF/UA) es, en cambio, un contrato de semántica. La tecnología de asistencia tiene que atravesar el documento y obtener significado, que vive en una capa totalmente diferente: un árbol de estructura completo, texto alternativo en las figuras, un título de documento establecido para su visualización, niveles de encabezado que no se saltan, relaciones de cabecera de tabla que sobreviven una vez que la página está fuera de la pantalla
Dado que las dos normas controlan capas diferentes, los archivos que le muerden son los que se encuentran entre ellos. Un documento perfecto para archivar puede ser silencioso para un lector de pantalla. Uno bellamente etiquetado puede hacer referencia a una fuente de escritorio que no existirá dentro de diez años. La publicación en el sector público es el lugar habitual donde ambos requisitos aterrizan a la vez, y una canalización allí no puede colapsarlos en una sola puerta. Los hallazgos van a diferentes personas. Las fuentes no incrustadas son un defecto en el código que genera el PDF, mientras que la falta de texto alternativo pertenece a quien posea las plantillas de contenido, y un informe que mezcla los dos simplemente se reenvía dos veces
La parte de PDF/A a la que se dirige importa tanto como si la alcanza. PDF/A-1 está congelado en PDF 1.4 y rechaza la transparencia y JPEG2000, a los que la salida de informes moderna recurre sin pensar. PDF/A-2 (ISO 19005-2, construido sobre ISO 32000-1) acepta ambos y es el valor predeterminado sensato para un nuevo archivo. PDF/A-3 va más allá y permite archivos incrustados de cualquier tipo, que es en lo que confían los formatos de facturación electrónica regulados. Un equipo que todavía se estandariza en PDF/A-1b en 2026 suele llevar consigo un requisito que alguien escribió hace quince años, y renegociar la parte objetivo suele ser más barato que eliminar la transparencia de cada gráfico que emite el sistema
Hallazgos estructurados en el momento de la ingesta
El punto de entrada de API plana es CheckFileCompliance, con el selector de prueba 1 para PDF/A y 2 para PDF/UA. Devuelve un controlador de lista de cadenas cuyos elementos son hallazgos individuales, uno por línea, que es exactamente la forma en que una puerta automatizada quiere caminar:
Dos detalles deciden si esto se ejecuta de forma desatendida. El primero es un valor de retorno que significa dos cosas opuestas. CheckFileCompliance devuelve 0 cuando el archivo es totalmente compatible y también cuando el archivo no se pudo abrir en absoluto, porque internamente una lista de resultados vacía colapsa a 0 en ambos casos. Una puerta que lee 0 como aprobado dejará pasar las cargas corruptas directamente al archivo, por lo que desambigüe con LastErrorCode antes de confiar en el cero, como lo hace la puerta de arriba. El segundo se refiere a dónde se encuentra el archivo en su ciclo de vida. El comprobador se ejecuta en el lector de transmisión (streaming) de la biblioteca en lugar del modelo de documento completo, abriendo el archivo directamente con uso compartido de lectura y nunca llamando a LoadFromFile, razón por la cual puede masticar entradas de varios gigabytes sin construir un árbol de objetos. Esa misma apertura de transmisión falla mientras otro proceso aún retiene el archivo para escritura, y una carga en progreso es precisamente ese estado. Coloque la puerta después de que finalice la transferencia
El diseño de transmisión vale la pena nuevamente bajo carga. Cada comprobación abre su entrada de solo lectura y la comparte para lectura, por lo que una auditoría de corpus se escala horizontalmente a través de subprocesos (threads) de trabajo o procesos con una instancia TPDFlib por trabajador y sin contención entre ellos. El recurso que necesita disciplina es el controlador en sí. Cada resultado distinto de cero de CheckFileCompliance permanece asignado hasta que llame a ReleaseStringList, y una puerta de larga ejecución que se olvida de liberarlos no falla, simplemente sangra memoria lentamente hasta que alguien va a buscar el porqué
Informes para humanos, diferencias (diffs) para puertas de compilación
Una lista de hallazgos es la forma correcta para una puerta y la forma incorrecta para un correo electrónico al equipo de plantillas. CreatePreflightReport renderiza el mismo análisis como prosa legible, CreatePreflightReportEx añade un selector de formato de informe y SavePreflightReport lo escribe en el disco para que el informe pueda viajar dentro del paquete de documentos entregado. Muchos contratos de archivo hacen de ese informe un entregable por derecho propio, no solo un artefacto interno
El miembro de esta familia que se gana su lugar discretamente es ComparePreflightReports. El cumplimiento es una superficie de regresión como cualquier otro comportamiento. Un ajuste de plantilla, una fuente corporativa con licencia nueva o una actualización de la biblioteca pueden introducir un hallazgo que no estaba allí en la última versión, y ninguno de ellos se anuncia por sí mismo. Mantenga informes dorados para un conjunto de documentos representativos bajo control de versiones, regenérelos después de cada cambio y ejecute ComparePreflightReports para calcular el delta. Una diferencia (diff) vacía es un artefacto de versión que vale la pena conservar. Un hallazgo sorpresa hace que falle la compilación, que es un lugar mucho más barato para descubrirlo que la auditoría
Generación de salida que aprueba en la primera ejecución
La comprobación previa (preflight) se gana su sustento en archivos que llegan de otros lugares. Para los documentos que produce su propio código, encontrar infracciones después de la generación y parchearlas es el camino lento. PDFlibPas incluye un modo de lado de generación para cada norma, y puede activar ambos para el mismo documento:
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: required for PDF/UA
// ... draw tagged content here ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// the preflight that counts runs on the saved file:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;La trampa se esconde en el momento de guardar. Varias de las reparaciones de conformidad ocurren mientras se serializa el documento en lugar de cuando se habilita el modo: forzar la bandera de impresión en las anotaciones, escribir la AFRelationship predeterminada para archivos incrustados PDF/A-3, normalizar el orden de las pestañas y las descripciones de los campos de formulario para PDF/UA. El documento que reside en la memoria no es idéntico a nivel de bytes al que llega al disco, por lo que el único veredicto de comprobación previa que significa algo es el calculado a partir del archivo guardado. Valide statement.pdf en sí mismo. No deduzca el cumplimiento del objeto que todavía está en la memoria, porque los bytes que estaría juzgando no son los bytes que envió
Los escenarios de facturas que llevan XML legible por máquina junto con el documento visual siguen el patrón ZUGFeRD y Factur-X, que se basa en PDF/A-3. Deberían establecer la relación de archivo adjunto explícitamente con SetPDFA3DefaultAFRelationship, ya que la norma ISO 19005-3 exige que cada archivo incrustado declare su función en relación con el documento. Déjelo sin establecer y el XML incrustado será solo un objeto (blob) sin ningún propósito declarado, de lo que el validador se da cuenta
Árbitros independientes: veraPDF y Acrobat
Un productor no debe ser el único juez de su propia salida. Los comprobadores de PDFlibPas le ofrecen veredictos rápidos y estructurados en el proceso, que es lo que desea en la ruta principal (hot path), pero la puerta de versión para un lote de archivo debería pasar la salida a través de un validador que nadie en su equipo haya escrito. veraPDF es la implementación de referencia mantenida por la comunidad para PDF/A y la herramienta que la mayoría de los archivos nombran en sus criterios de aceptación, por lo que es la que hay que igualar. Los perfiles de comprobación previa de Acrobat suponen un útil desempate cuando veraPDF y la comprobación en proceso no están de acuerdo. Registre el nombre del validador y su versión junto a cada informe almacenado. Una afirmación de que un archivo aprobó veraPDF dice muy poco sin el número de compilación que lo aprobó, ya que la herramienta endurece sus reglas entre versiones
Los validadores discrepan en los bordes de las normas, y cuando lo hacen, la respuesta no es elegir la herramienta que le gusta. Reduzca el archivo a una muestra mínima que todavía desencadene el desacuerdo y léalo frente al texto de la norma. Una hora de eso suele sacar a la superficie una de dos cosas: un error genuino de la herramienta que vale la pena presentar aguas arriba (upstream), o una cláusula que su equipo ha estado leyendo mal y que debería anotar en las notas de cumplimiento para que la próxima persona no vuelva a litigarla
La entrada cifrada tiene un atajo. Ambos comprobadores toman un argumento de contraseña, pero un archivo PDF/A-1 con un diccionario de cifrado ya no es conforme, porque la norma ISO 19005-1 prohíbe el cifrado rotundamente, por lo que un envío cifrado puede ser rechazado antes de que se ejecute ningún análisis más profundo. Averiguar qué otorga realmente un diccionario de cifrado es una tarea en sí misma, que se trata en Cifrado de PDF y auditoría de permisos
Los hallazgos de PDF/UA casi siempre se remontan a cómo se creó el árbol de estructuras en primer lugar, y las técnicas de etiquetado que hay detrás viven en la creación de árboles de estructura PDF etiquetados en Delphi. Los archivos que también exigen firmas digitales deben emparejar esta puerta con el flujo de trabajo en la Firma y validación PAdES. La referencia completa de la API de comprobación previa se encuentra en la página del producto losLab PDF Library para Delphi
Ejemplos de código adicionales
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: required for PDF/UA
// ... draw tagged content here ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// the preflight that counts runs on the saved file:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;