Artículo técnico

Automatizar comprobaciones preflight de PDF en Delphi con HotPDF

La tirada mensual de extractos se veía bien en la pantalla de todos los desarrolladores, así que se envió. La imprenta devolvió todo el lote dos días después: imágenes RGB en un trabajo CMYK y ninguna declaración /Trapped. El coste no fue la reimpresión; fueron dos días de un plazo regulatorio. "Preflight" es el término de preimpresión para detectar exactamente esta clase de problema antes de que los ficheros salgan del edificio, y la pregunta de ingeniería interesante para un equipo Delphi es dónde debe vivir esa comprobación cuando los PDF los produce su propio código con una biblioteca como HotPDF, no las herramientas de escritorio de un diseñador.

Prevenir gana a inspeccionar cuando controla el generador

El preflight clásico asume un fichero ajeno de calidad desconocida y lo inspecciona después. Cuando su propia aplicación genera el documento, esa arquitectura está al revés: cada propiedad que comprobaría el inspector, embebido de fuentes, uso de espacios de color, output intents, metadatos, fue decidida por su código milisegundos antes. El fallo preflight más barato es el que se vuelve imposible en tiempo de generación.

Conviene ser preciso sobre lo que HotPDF ofrece y no ofrece aquí. El componente incluye una ventana de informe preflight como parte de su aplicación demo con GUI, pero no hay una API preflight programática que pueda llamar desde un servicio o un script de compilación. Eso es menos carencia de lo que parece al principio, porque para documentos generados el patrón robusto tiene de todos modos dos mitades independientes: limitar el generador para que no pueda emitir estructuras no conformes y después verificar la salida con un validador que usted no mantiene. Que una biblioteca valide su propia salida es corregirse sus propios deberes; una herramienta externa da evidencias que un cliente o auditor aceptará.

El lado de generación: convertir la conformidad en configuración, no en revisión

Las propiedades de estándares de HotPDF son la capa preventiva. Cuando PDFACompliance o PDFXCompliance se establecen antes de BeginDoc, el componente aplica las reglas correspondientes durante la generación, embebe fuentes, rastrea el uso de DeviceRGB y DeviceCMYK frente al output intent declarado y rechaza funciones prohibidas por el perfil. Después de guardar, esas mismas propiedades registran lo que se ha aplicado, que es exactamente lo que necesita el log de su canalización:

// After EndDoc: record the enforced profiles with the run metadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Escriba esas marcas, el hash de los datos de entrada y la versión de HotPDF en la misma línea de log. Cuando más tarde un validador discrepa del generador, esa línea indica qué revisión de plantilla y qué versión de biblioteca produjeron el fichero discutido, y una disciplina de log de una línea sustituye una tarde de conjeturas forenses. La configuración completa que respalda esas marcas, output intents, perfiles ICC y etiquetado se trata en nuestra guía de salida PDF/A, PDF/X y PDF/UA con HotPDF.

Clasificar ficheros entrantes antes de las comprobaciones caras

Muchas canalizaciones no son puramente generativas: los clientes suben PDF, los escáneres los depositan, los socios los envían por correo. Ejecutar una validación estructural completa sobre cada fichero entrante desperdicia tiempo de cola en entradas que ni siquiera se pueden abrir. La Direct File API de HotPDF lee la estructura del fichero sin cargar el árbol completo de objetos, lo que la convierte en una primera puerta barata:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // structurally unreadable: quarantine, do not validate
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Dos comportamientos de esta API dan forma a la lógica alrededor. DAOpenFileReadOnly mantiene memoria plana solo con entradas sin cifrar — si pasa una contraseña, cae internamente a un análisis completo — así que encamine los ficheros que se sabe que están cifrados por DecryptFile para producir primero una copia de trabajo en claro. Y DAGetPageCount solo es válido sobre un identificador procedente de una apertura correcta, así que mantenga estricta la comprobación del handle en lugar de asumir un valor positivo. Hay más patrones para esta API en el artículo de la Direct File API para flujos de trabajo con PDF grandes.

El lado de verificación: veraPDF como paso de compilación

Para afirmaciones PDF/A y PDF/UA, veraPDF es el validador que merece conectarse a la canalización: se ejecuta sin interfaz, procesa lotes, emite XML o JSON legible por máquina y comunica cada fallo por número de cláusula ISO, de modo que un hallazgo como una infracción de regla contra ISO 19005-1 cláusula 6.2.2 se asigna directamente a una configuración conocida del generador. Invocarlo desde Delphi es control de procesos ordinario:

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);  // bound the wait per file
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

El timeout no es decoración. Un fichero malformado puede llevar cualquier analizador a territorio patológico, y una espera sin límite dentro de un worker de cola tumba toda la cola. Acote la espera, trate un timeout como un fallo con su propio código y ponga la entrada en cuarentena para inspección humana. Analice el XML por identificadores de regla en lugar de raspar mensajes legibles por humanos; los IDs de regla son estables entre versiones del validador, el texto de los mensajes no lo es, y los códigos estables son lo que soporte puede buscar en tickets pasados.

El comportamiento por lotes merece el mismo cuidado que la corrección de un solo fichero. Ejecute el validador como un proceso por fichero, no como un proceso por lote, para que una entrada patológica cueste el timeout de ese fichero y no el lote; limite los procesos de validador concurrentes al número de núcleos, porque la generación de informes XML es intensiva en CPU; e imponga un techo de tamaño de fichero en la entrada, porque un monstruo escaneado de 2 GB dominará la cola por bien que se comporte el analizador. Nada de esto es lógica preflight, pero es la diferencia entre una puerta que sobrevive al volumen de fin de mes y una que se desactiva la primera vez que bloquea la canalización a las 2 de la madrugada.

PDF/X es la laguna de esta historia: veraPDF no lo cubre, y la comprobación práctica sigue siendo Preflight de Acrobat con el perfil ISO 15930 correspondiente. Acrobat es interactivo, así que úselo para muestreo, primer artículo de una plantilla nueva y una pequeña selección aleatoria por lote, mientras la puerta automatizada cubre lo que puede automatizarse. Una comprobación manual muestreada que se hace de verdad gana a una automatización completa teórica que nadie terminó de construir.

Qué debe contener el informe para merecer conservarse

Una puerta preflight produce valor dos veces: una cuando bloquea un fichero malo y otra meses después cuando alguien pregunta por qué se aceptó un fichero. El segundo uso es el que dicta el formato del informe. Conserve, por cada fichero comprobado: el hash de entrada, las marcas de conformidad del generador y la versión de la línea de log anterior, el nombre y versión del validador, el perfil contra el que se comprobó, el resultado de aprobado o fallido y la lista de IDs de regla fallidos con números de página cuando el validador los proporcione. Guarde el informe junto al artefacto que describe, no en un sistema aparte que se jubilará antes que el archivo.

Las desviaciones aceptadas también necesitan papeleo. Cuando un cliente insiste en enviar un fichero que no gusta a la puerta, registre quién lo aprobó, por qué y hasta cuándo, y adjunte esa dispensa al informe en lugar de debilitar la regla de forma global. Una dispensa con propietario y fecha de caducidad es una excepción gestionada; una comprobación comentada es un incidente futuro.

Los fallos merecen un paso más: copie el fichero fallido en una carpeta de regresión con nombre. Cada incidente preflight que hemos ayudado a depurar acabó reduciéndose a una entrada reproducible, y los equipos que conservaron esas entradas arreglaron regresiones en horas en lugar de redescubrirlas en producción.

FAQ

¿Puede HotPDF validar programáticamente un PDF cualquiera de terceros?

No. El informe preflight del producto es una función de demo GUI, no una API invocable. El patrón de automatización soportado es la aplicación desde el lado de generación mediante las propiedades de conformidad más un validador externo como veraPDF para el veredicto formal.

¿veraPDF basta para trabajos de imprenta?

Cubre PDF/A y PDF/UA. Para másteres de imprenta PDF/X, ejecute Acrobat Preflight con el perfil que especifique su impresor y confirme que el output intent coincide con la caracterización de prensa que esperan.

¿Qué debe hacer fallar la compilación: solo errores o también avisos?

Bloquee por fallos de regla del perfil cuya conformidad declara y registre los avisos con seguimiento de tendencia. Convertir cada aviso en bloqueador enseña a la gente a saltarse la puerta, que es peor que no tener ninguna.

Referencia de producto

Las propiedades de conformidad y la Direct File API utilizadas en esta canalización pertenecen a HotPDF Component para Delphi y C++Builder; su documentación describe en detalle cada llamada mostrada aquí.