Artículo técnico

Límites de implementación PDF/A y codificación de fuentes

PDFium Component valida los límites de implementación del Anexo C de ISO 19005-1 —tokens de nombre de 127 bytes, 8191 elementos de array, 4095 entradas de diccionario y 28 niveles de anidamiento de contenedor— e informa de las fuentes TrueType simbólicas que portan una entrada /Encoding. Ambas comprobaciones se ejecutan en el cauce de inspección de bytes, así que una aplicación Delphi o Lazarus obtiene el veredicto sin cargar la DLL de PDFium en absoluto

Estos son los fallos que más desconciertan, porque el documento se ve bien. Renderiza, imprime, todas las fuentes están incrustadas, el output intent está presente. Y entonces un validador lo rechaza por un diccionario que tiene 4096 entradas, y nada del documento visible explica por qué

¿Qué protegen realmente los límites del Anexo C?

La interoperabilidad con implementaciones anteriores a tu generador. El Anexo C traslada los límites de implementación de la PDF Reference a todas las partes de PDF/A, y los números no son arbitrarios —describen lo que un lector conforme estaba históricamente obligado a manejar—. Un archivo que los excede puede abrirse perfectamente en un visor moderno y fallar en el lector archivable sobre el que un sistema de registros se estandarizó hace quince años, que es justamente el escenario que PDF/A existe para prevenir

Los cuatro límites son inclusivos. Un token de nombre de exactamente 127 bytes valida; 128 no. Un array con exactamente 8191 elementos valida; 8192 no. PDFium Component fija ambos lados de cada frontera en su suite de pruebas por ese motivo, porque un error de uno en una comprobación de límite produce el peor tipo de validador: uno que rechaza archivos conformes y al que aun así se le cree

uses FPdfPdfa;

var
  Src: TFileStream;
  Res: TPdfAValidationResult;
begin
  Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Src);
    if pvaiArrayOverLimit in Res.Issues then
      Memo1.Lines.Add('An array carries more than 8191 elements');
    if pvaiDictOverLimit in Res.Issues then
      Memo1.Lines.Add('A dictionary carries more than 4095 entries');
    if pvaiNestingOverLimit in Res.Issues then
      Memo1.Lines.Add('Containers nest deeper than 28 levels');
    if pvaiNameOverLimit in Res.Issues then
      Memo1.Lines.Add('A name token is longer than 127 bytes');
  finally
    Src.Free;
  end;
end;

¿Qué generadores alcanzan de verdad estos límites?

Los que construyen estructura de forma programática, que es la mayoría de la salida de línea de negocio. Un formulario con varios miles de campos produce un array /Annots o un array /Fields de AcroForm que crece por encima de 8191. Una página cuyo diccionario de recursos acumula una entrada por cada imagen o instancia de fuente generada cruza 4095. Los árboles de estructura generados en profundidad —un documento etiquetado construido por recursión sobre un modelo de datos anidado— pasan de 28 niveles sin que nadie lo note, porque nadie mira la profundidad de anidamiento

Los nombres largos vienen de un hábito distinto: codificar datos en tokens de nombre. Un nombre de colorante construido a partir de un identificador de cliente, un grupo de contenido opcional nombrado tras una ruta completa de archivo, un campo de formulario cuyo nombre totalmente cualificado concatena seis niveles de jerarquía. Los nombres son baratos de generar y fáciles de alargar, y 127 bytes desaparecen más rápido de lo que esperarías en cuanto hay involucrada una etiqueta codificada en UTF-8

La solución es estructural en todos los casos. Parte el array, parte el diccionario, aplana el anidamiento, acorta el nombre —la recomendación de preflight para cada problema nombra el límite concreto en lugar de limitarse a decir que el archivo no es válido—. La inyección de marcadores no puede ayudar aquí: no son reclamaciones de metadatos, son la forma del grafo de objetos

Por qué una fuente TrueType simbólica no debe portar /Encoding

Porque ISO 19005-1 §6.3.7 admite únicamente el cmap incorporado en la fuente para las fuentes TrueType simbólicas, y una entrada /Encoding lo contradiría. Una fuente simbólica mapea códigos a glifos en sus propios términos —eso es justamente lo que significa simbólica—. Añade una tabla de codificación y ahora hay dos respuestas a la pregunta «qué glifo selecciona el byte 0x41», sin ninguna regla en el archivo que diga cuál gana. Distintos lectores lo resuelven de forma distinta, y un documento que renderiza como texto en un visor renderiza como dingbats en otro

PDFium Component lee la marca simbólica del /FontDescriptor, tanto si el descriptor va escrito en línea en el diccionario de fuente como si va referenciado indirectamente. Una fuente TrueType no simbólica conserva su /WinAnsiEncoding o /MacRomanEncoding obligatorio sin marcarse, porque para fuentes no simbólicas la codificación es justamente lo que pide la norma. La comprobación se dispara en la contradicción, no en la presencia de una codificación

if pvaiSymbolicTrueTypeEncoding in Res.Issues then
  Memo1.Lines.Add(
    'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
    'built-in cmap (ISO 19005-1 6.3.7)');

La fuente práctica de este defecto es el subsetting de fuentes hecho por un productor que trata todas las TrueType igual. Symbol, Wingdings, las fuentes de código de barras y las fuentes de iconos son las portadoras habituales —justo las fuentes que un documento de negocio usa para casillas, logotipos y códigos de barras, y justo las que nadie reexamina cuando un documento falla la validación por «las fuentes»—

Cómo llegan los problemas a un informe de preflight

Los cuatro límites de contenedor se clasifican bajo estructura; el problema de codificación TrueType simbólica se clasifica bajo contenido. Esa división importa cuando un informe va a dos personas distintas: los hallazgos de estructura suelen pertenecer a quien escribió el generador, y los hallazgos de contenido suelen pertenecer a quien suministró los activos

Cada problema lleva una recomendación que nombra el remedio en términos concretos —acorta los tokens de nombre a 127 bytes o menos, divide los arrays para que ninguno porte más de 8191 elementos, retira /Encoding de las fuentes TrueType simbólicas—. Un informe que dice «no conforme con PDF/A» arranca una investigación. Un informe que dice qué límite se excedió y por cuánto la cierra

Validar sin la DLL, y por qué importa aquí

Todas las comprobaciones anteriores se ejecutan contra los bytes del archivo, así que funcionan en un servicio que no tiene el binario de PDFium desplegado, en un paso de build, o en una máquina donde cargar una DLL nativa es un problema de política. Esa es una línea de diseño deliberada en PDFium Component: las comprobaciones que pueden responderse desde la estructura se responden desde la estructura, y la DLL se reserva para las que de verdad necesitan un motor de render

Para el flujo colindante —ejecutar la validación sobre una carpeta, producir informes y decidir qué hacer con los hallazgos— consulta los recorridos por la validación de preflight PDF/A en Delphi y la CLI de informe de preflight por lotes. Para la decisión de perfil archivable que se sienta por encima de todas estas comprobaciones, las notas sobre la conformidad archivable PDF/A cubren a qué parte y nivel apuntar antes de empezar a corregir hallazgos

PDFium Component envuelve el motor PDFium para Delphi, C++Builder y Lazarus con una API VCL de alto nivel y un conjunto de validadores de conformidad que funcionan con o sin la DLL; consulta la página del producto PDFium Component para las normas y plataformas admitidas