El archivo se abre sin problemas en su máquina. Acrobat lo muestra, la vista previa de impresión se ve bien, están todas las páginas. Luego llega a la imprenta, o al sistema de archivo que ingiere su lote mensual, y vuelve rechazado: imágenes RGB en un trabajo CMYK, sin clave /Trapped, un output intent que no coincide con la máquina. No había nada mal en el documento que nadie pudiera ver. Estaba mal frente a un perfil, y el perfil se comprobó en un sitio donde usted no estaba. Preflight es el nombre que la preimpresión da a esa comprobación, y la pregunta de verdad es dónde encaja cuando los PDF salen de su propio código Delphi y no del escritorio de un diseñador
HotPDF no le ofrece una función de preflight que pueda llamar. El componente incluye una ventana de informe de preflight en su demo con interfaz gráfica, pero detrás no hay ninguna API que un servicio o un script de compilación pueda invocar, y fingir lo contrario le haría buscar un método que no existe. Suena a agujero hasta que se da cuenta de que, para archivos que genera usted mismo, llamar a un validador sobre su propia salida es de todos modos la forma equivocada de plantearlo. Ya controla todas las propiedades que un validador inspeccionaría. La división útil consiste en hacer que el generador sea incapaz de emitir un archivo malo y demostrarlo después con una herramienta que no ha escrito usted
Por qué su propia salida se comprueba de otra manera
El preflight tradicional presupone el archivo de un desconocido. Lo produjo algún diseñador, alguna otra aplicación, alguna cadena de ediciones ignota, y usted lo inspecciona porque no tiene ni idea de lo que hay dentro. Un documento que produjo su código no es un desconocido. La incrustación de fuentes, el espacio de color, el output intent, el bloque de metadatos: su programa lo decidió todo unos milisegundos antes de que el archivo tocara el disco. Inspeccionarlo después para descubrir decisiones que acaba de tomar es hacer bulto. La jugada más barata es restringir esas decisiones para que nunca llegue a existir un archivo no conforme al que pillar
Hay además una razón de credibilidad para mantener la verificación fuera. Una biblioteca que bendice su propia salida se está corrigiendo su propio examen. Cuando el sistema de archivo de un cliente o el RIP de una imprenta rechaza su archivo, un «nuestro componente dice que está bien» no pesa nada. Un veredicto de veraPDF o de Acrobat sí, porque la otra parte ejecuta las mismas herramientas
Convierta la conformidad en un ajuste, no en una lista de tareas
La capa de prevención es pura configuración. Fije PDFACompliance o PDFXCompliance antes de BeginDoc y HotPDF mantendrá las reglas correspondientes durante toda la pasada de generación: incrusta las fuentes, vigila el uso de DeviceRGB y DeviceCMYK frente al output intent que declaró y rechaza las funciones que el perfil prohíbe. Las contradicciones afloran en EndDoc, donde las barreras de conformidad lanzan una excepción en lugar de publicar calladamente algo que fallará aguas abajo. Una vez guardado el archivo, esas mismas propiedades devuelven lo que realmente se aplicó, que es el dato que más necesita el registro de su cadena:
// Tras EndDoc: registre los perfiles aplicados con los metadatos de la ejecución
if Pdf.PDFACompliance <> '' then
Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);
Ponga esos indicadores en la misma línea de registro que el hash de los datos de entrada y la versión de HotPDF. El día en que un validador y su generador discrepen sobre un archivo, esa línea le dirá qué plantilla lo produjo y qué compilación de la biblioteca estaba cargada, y la discusión que de otro modo se comería una tarde se resuelve con un grep. Los output intents, los perfiles ICC y el etiquetado que hay detrás de estos indicadores se detallan en la guía de salida PDF/A, PDF/X y PDF/UA con HotPDF
Una primera barrera barata para archivos que no ha generado
No toda cadena es puramente generativa. Los clientes suben PDF, los escáneres los dejan en una carpeta, los socios los adjuntan al correo. Pasar todos y cada uno por un validador estructural completo desperdicia tiempo de cola en archivos que ni siquiera se abrirán. La Direct File API de HotPDF lee lo bastante de la estructura de un archivo como para responder a «¿es esto siquiera un PDF utilizable?» sin cargar el árbol de objetos entero, lo que la convierte en un buen sitio donde fallar pronto:
function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
Handle, Pages: Integer;
begin
Result := False;
Handle := Pdf.DAOpenFileReadOnly(FileName, '');
if Handle <= 0 then
Exit; // estructuralmente ilegible: a cuarentena, no validar
try
Pages := Pdf.DAGetPageCount(Handle);
Result := Pages > 0;
finally
Pdf.DACloseFile(Handle);
end;
end;
Dos hechos sobre esta API deciden cómo hay que envolverla. El atajo de memoria plana solo vale para entradas sin cifrar; pase una contraseña a DAOpenFileReadOnly y recurrirá en silencio a un análisis completo, así que un archivo que sabe cifrado debería pasar por DecryptFile hasta una copia de trabajo en claro antes del triaje. Y DAGetPageCount no significa nada sobre un manejador que no se abrió limpiamente, de modo que la comprobación del manejador se queda estricta y un resultado no positivo es un rechazo, no un reintento. Hay más patrones de este estilo en el artículo sobre la Direct File API para flujos de trabajo con PDF grandes
veraPDF, ejecutado como parte de la compilación
Para todo lo que declare como PDF/A o PDF/UA, veraPDF es el validador que hay que conectar. Se ejecuta sin interfaz, admite lotes, emite XML o JSON y nombra cada fallo por su cláusula ISO, de modo que un incumplimiento de la cláusula 6.2.2 de ISO 19005-1 apunta directamente a un ajuste del generador en vez de dejarle adivinar. Gobernarlo desde Delphi es simple control de procesos:
function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
Cmd: string;
SI: TStartupInfo;
PI: TProcessInformation;
begin
Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
[PdfFile, ReportFile]);
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
CREATE_NO_WINDOW, nil, nil, SI, PI) then
RaiseLastOSError;
try
WaitForSingleObject(PI.hProcess, 120000); // acota la espera por archivo
GetExitCodeProcess(PI.hProcess, Result);
finally
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
end;
Ese tiempo límite se gana el sueldo. Un archivo malformado puede meter a cualquier analizador en un rincón del que no sale, y una espera sin fin dentro de un trabajador de cola arrastra consigo al resto de la cola. Acote la espera, dele al vencimiento su propio código de fallo y aparte el archivo para que lo mire una persona. Al leer el resultado, extraiga del XML los identificadores de regla, no el texto legible. Los identificadores de regla sobreviven a las actualizaciones del validador; la redacción de los mensajes no, y un código estable es algo que un técnico de soporte puede buscar en tickets antiguos
Cómo ejecuta el lote importa tanto como si cada archivo pasa. Un proceso por archivo, no uno por lote, para que una entrada venenosa le cueste el vencimiento de ese archivo y nada más. Limite el número de procesos de validación al número de núcleos, porque construir el informe XML depende de la CPU y sobresuscribir solo provoca trasiego. Y ponga un techo de tamaño en la entrada, porque un libro escaneado de dos gigabytes se adueñará de la cola por muy paciente que sea el analizador. Nada de eso es preflight en sentido estricto. Es la diferencia entre una puerta que sobrevive al volumen de cierre de mes y otra que alguien apaga la primera noche que atasca la cadena a las dos de la madrugada
PDF/X es donde esto se queda corto. veraPDF no lo valida, así que la comprobación que funciona sigue siendo el Preflight de Acrobat con el perfil ISO 15930 que le haya indicado su impresor. Acrobat quiere a una persona, lo que implica muestreo en lugar de cobertura total: el primer archivo salido de una plantilla nueva, más un pequeño sorteo aleatorio de cada lote, mientras la puerta automatizada se ocupa de todo lo que puede resolverse sin nadie delante. Una comprobación muestreada que de verdad se ejecuta vale más que una automatización completa que se queda a medias para siempre
Un informe que seguirá queriendo dentro de un año
Una puerta de preflight rinde dos veces. Una cuando detiene un archivo malo en la entrada, y otra mucho después, cuando alguien pregunta por qué se dejó pasar un archivo concreto. Ese segundo momento es el que debería dictar el formato, porque es el que le deja tirado si el informe es pobre. De cada archivo comprobado, guarde el hash de entrada, los indicadores de conformidad del generador y la versión de la biblioteca de la línea de registro anterior, el nombre y la versión del validador, el perfil contra el que se comprobó, el resultado de aprobado o suspenso y los identificadores de las reglas incumplidas con los números de página siempre que el validador los dé. Guarde ese informe junto al archivo que describe. Póngalo en un sistema aparte y ese sistema se retirará antes que el archivo histórico que documenta
Las excepciones también hay que anotarlas. Cuando un cliente insiste en publicar un archivo que a la puerta no le gusta, la respuesta no es relajar la regla para todo el mundo. Registre quién aprobó ese archivo, con qué fundamento y hasta qué fecha, y adjunte esa dispensa a su informe. Una dispensa con nombre y caducidad es una decisión que alguien asume. Una comprobación comentada «temporalmente» es un incidente esperando su fecha
Hay una costumbre más que se paga sola: cuando un archivo falla, cópielo a una carpeta de regresión con nombre antes de que nadie lo toque. Casi todo problema de preflight que merece depuración se remonta a una entrada concreta, y los equipos que conservan esas entradas arreglan la reaparición en una hora en vez de esperar a que vuelva a asomar en producción. Las propiedades de conformidad y la Direct File API mostradas aquí forman parte del HotPDF Delphi Component para Delphi y C++Builder, cuya documentación cubre cada llamada al completo