Artículo técnico

Salidas PDF/X-1a, X-3 y X-4 en Delphi con PDFlibPas

PDFlibPas produce archivos de impresión PDF/X desde Delphi con una sola llamada, TPDFlib.SetPDFXMode, que selecciona PDF/X-1a:2003, PDF/X-3:2003 o PDF/X-4. La llamada escribe la identificación XMP pdfxid, agrega un output intent GTS_PDFX, declara /Trapped /False, fija la versión de PDF y a partir de ahí rechaza las llamadas de dibujo que la parte elegida prohíbe. CheckFileCompliance con el test 3 audita el archivo guardado

Esa es la respuesta corta. La larga importa porque las imprentas no rechazan archivos por feos, los rechazan en preflight, usualmente un viernes, usualmente por algo que el código generador pudo haber atrapado: un logo RGB en un trabajo X-1a, una fuente que fue referenciada en lugar de incrustada, una página sin tamaño de corte. PDF/X existe para volver esas fallas estructurales y no visuales, y el lado biblioteca del trabajo es impedir que usted las produzca desde el principio

¿Qué parte PDF/X debe apuntar una aplicación Delphi?

Apunte a PDF/X-4 salvo que el flujo de recepción diga otra cosa, y apunte a PDF/X-1a cuando lo diga. Las tres partes difieren exactamente en tres ejes, color, transparencia y versión base de PDF, y cada eje se mapea a una compuerta dentro de PDFlibPas:

  • PDF/X-1a:2003 (ISO 15930-4): sintaxis PDF 1.4, solo CMYK, grises y colores directos (spot), sin color basado en ICC, sin transparencia. El archivo ya viene separado en colores de prensa, así que la imprenta no hace ninguna conversión de color
  • PDF/X-3:2003 (ISO 15930-6): sigue siendo PDF 1.4 y sigue sin transparencia, pero se permiten espacios gestionados por color, y el RGB de dispositivo se acepta cuando el propio output intent describe una condición RGB
  • PDF/X-4 (ISO 15930-7): sintaxis PDF 1.6, con transparencia viva y contenido opcional (capas). Esta es la parte para la que están construidos los RIP modernos, porque aplanar la transparencia en la salida es de donde vino gran parte del dolor antiguo de X-1a

X-1a sigue estando en todas partes en la entrega de publicidad y en flujos de impresión antiguos que quieren un archivo que puedan imaginar sin pensarlo. X-4 es la mejor decisión de ingeniería siempre que la imprenta lo acepte: la transparencia sobrevive intacta y las decisiones de color suceden en el RIP con el perfil real de la prensa en lugar de en su código. Los números de modo son 1, 2 y 3 respectivamente, y GetInformation(203) reporta de vuelta el modo activo. PDFlibPas también define números de modo más altos para la familia PDF/X-6 sobre PDF 2.0, que siguen reglas distintas de versión y output intent y quedan fuera de alcance aquí

Qué escribe SetPDFXMode en el archivo

TPDFlib.SetPDFXMode maneja la identificación y el boilerplate que un lector PDF/X revisa primero, de modo que el código de la aplicación solo tiene que suministrar el perfil real de prensa y el contenido. En concreto, una llamada exitosa hace cuatro cosas:

  • Escribe la cadena de edición en XMP como pdfxid:GTS_PDFXVersion (PDF/X-1a:2003, PDF/X-3:2003 o PDF/X-4). Las ediciones 2003 identifican la parte por completo mediante esa cadena, así que no se escribe ninguna propiedad de conformidad separada
  • Agrega un output intent si el documento no tiene ninguno y fija su subtipo en /S /GTS_PDFX, el subtipo que los lectores PDF/X buscan (ISO 32000-1 §14.11.5 define el diccionario de output intent)
  • Fija /Trapped /False en el diccionario de información del documento, porque el escritor nunca hace trapping y PDF/X exige que el estado de trapping se declare en lugar de quedar desconocido
  • Fija la versión base: 1.4 para X-1a y X-3, 1.6 para X-4

Dos reparaciones más suceden al guardar. Cada página sin TrimBox recibe una igual a su MediaBox, ya que PDF/X exige un tamaño final declarado en cada página (ISO 32000-1 §14.11.2 cubre los límites de página), y las anotaciones reciben la bandera de impresión que el estándar espera. El fallback de TrimBox es una red de seguridad, no una decisión de maquetación. Un trabajo a sangre necesita un trim box real dentro de un media box más grande, y la guía de valores predeterminados de TrimBox, BleedBox y CropBox en PDFlibPas cubre cómo fijar esas cajas y cómo la herencia puede sorprenderlo

uses
  PDFlibrary;

procedure BuildX1aAd(const OutFile, PressProfile: string);
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.SetPDFXMode(1) <> 1 then          // 1 = PDF/X-1a:2003
      raise Exception.CreateFmt('PDF/X mode refused (error %d)',
        [Pdf.LastErrorCode]);

    // Reemplace el perfil predeterminado con la condición real de prensa
    if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
      raise Exception.Create('Could not load the CMYK output profile');

    // Embed = 1 incrusta el programa completo de la fuente; PDF/X no acepta 0
    if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
      raise Exception.Create('Font could not be loaded for embedding');

    Pdf.SetTextSize(24);
    Pdf.SetFillColorCMYK(0, 1, 1, 0);           // rojo de cuatricromía, sin RGB
    Pdf.DrawText(72, 720, 'Spring catalog, page 1');

    Pdf.SaveToFile(OutFile);
  finally
    Pdf.Free;
  end;
end;

El orden de ese bosquejo es deliberado. LoadOutputIntentProfile reemplaza el perfil de un output intent que ya existe, así que tiene que correr después de que SetPDFXMode haya creado uno. La llamada tampoco puede conjurar un intent de la nada: en un documento sin output intent simplemente devuelve 0

¿Por qué hay que reemplazar el output intent predeterminado?

El output intent que SetPDFXMode agrega para los modos 1 a 3 declara DeviceCMYK, pero los bytes ICC detrás de él son el perfil sRGB incluido con la biblioteca con el conteo de componentes reescrito a 4. Ese valor predeterminado existe por compatibilidad hacia atrás y es suficiente para que el archivo sea estructuralmente PDF/X, pero el perfil incrustado no describe ninguna prensa. Una herramienta de preflight que inspeccione el encabezado del perfil verá un perfil RGB con etiqueta CMYK, y un impresor que confíe en el intent para la prueba de color recibe un disparate

Así que trate LoadOutputIntentProfile(FileName, 'DeviceCMYK') como obligatorio para trabajo de impresión real y pase el perfil ICC que su proveedor de impresión nombre para el papel y la prensa. TPDFlib.LoadOutputIntentProfile cambia el stream DestOutputProfile y actualiza /N, y deja el resto del diccionario del intent tal como fue escrito. En particular, el identificador de condición de salida y las cadenas descriptivas del intent predeterminado siguen cargando valores estilo sRGB, así que si su imprenta hace match sobre el identificador en lugar del perfil incrustado, confirme con ellos qué revisan antes de enviar. Ese es un hueco genuino de los valores predeterminados de la era 2003, no algo que su código hizo mal

Primera trampa: la versión que usted no eligió

Un documento nuevo de PDFlibPas arranca en el valor predeterminado del escritor, PDF 1.5, que está por encima de la base 1.4 que PDF/X-1a y PDF/X-3 requieren. TPDFlib.SetPDFXMode lo resuelve en silencio en el caso común: si la versión de guardado no ha sido bloqueada, baja la versión del documento a la base de la parte y sigue. La primera implementación se negaba en su lugar, y cada SetPDFXMode(1) sobre un documento fresco devolvía 0, que es un buen ejemplo de una regla correcta en el papel e inútil en la práctica

El rechazo todavía existe para el único caso donde es correcto. Si algo anterior llamó LockSaveVersion con una versión por encima de la base, SetPDFXMode devuelve 0 con LastErrorCode 601, porque quien llamó fijó la versión a propósito y la biblioteca no la va a pasar por encima. En código compartido de configuración de documento, donde un helper bloquea la versión por razones ajenas, es exactamente ahí donde muere un trabajo X-1a:

Pdf := TPDFlib.Create;
try
  Pdf.LockSaveVersion('1.7');     // fijado por un helper genérico de "valores de casa"
  if Pdf.SetPDFXMode(1) = 0 then
    // LastErrorCode = 601: 1.7 está bloqueado, PDF/X-1a necesita 1.4.
    // Omita el bloqueo en trabajos de impresión. PDF/X-4 no escapa aquí:
    // su base 1.6 también queda bajo el bloqueo de 1.7 y falla igual.
    LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
  Pdf.Free;
end;

La misma compuerta explica un límite relacionado. El modo PDF/X es un interruptor de creación de documento: SetPDFXMode funciona sobre un documento nuevo y devuelve 0 sobre uno que fue cargado de un archivo, porque la biblioteca no puede responder por contenido que no escribió. Convertir un PDF existente a PDF/X es otro trabajo que requiere inspección y reparación, no una bandera

Segunda trampa: compuertas que rechazan en silencio

Una vez que un modo PDF/X está activo, PDFlibPas rechaza cada llamada de contenido que la parte elegida prohíbe, en el momento en que usted la hace, en lugar de dejar que el archivo falle en preflight después. Los rechazos cubren rellenos y trazos RGB de dispositivo (todo X-1a; X-3 y X-4 salvo que el output intent sea RGB), colores basados en ICC en X-1a, transparencia y blend modes en X-1a y X-3, contenido opcional en X-1a y X-3, acciones JavaScript, archivos adjuntos incrustados, el filtro de imagen LZW, fuentes Standard 14 que quedarían sin incrustar y cifrado de llave pública. El cifrado por contraseña vía Encrypt no pasa por compuerta al momento de creación, pero el validador marca cualquier archivo cifrado, así que mantenga los trabajos de impresión sin cifrar

Lo que los rechazos no comparten es un código de error, y ahí está la trampa. Las compuertas de espacio de color en SetFillColor, SetLineColor, las llamadas de color basado en ICC y EncryptPublicKey fijan LastErrorCode en 601, el código de conformidad de la biblioteca que también usan las compuertas de PDF/A. Las compuertas de transparencia, JavaScript, adjuntos, LZW y AddStandardFont simplemente devuelven 0 y dejan LastErrorCode intacto, lo que significa que puede seguir cargando un valor de una llamada anterior. El código que solo revisa LastErrorCode después de un lote de llamadas de dibujo se perderá la mitad de los rechazos, así que revise cada valor de retorno:

procedure Require(Ok: Integer; const What: string);
begin
  if Ok = 0 then
    raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;

Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency');   // 0 en X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
  'app.alert(1)', 0), 'JavaScript link');           // 0 en todas las partes

Las fuentes merecen su propia advertencia. AddStandardFont devuelve 0 bajo PDF/X porque la biblioteca no trae los contornos de las Standard 14 y por tanto no puede incrustarlos. AddTrueTypeFont es distinto: en modo PDF/A promueve Embed = 0 a una incrustación completa por usted, pero en modo PDF/X no lo hace, así que una fuente de sistema referenciada entra al archivo y solo el validador la atrapa. Pase 1 (completa) o 2 (subset) usted mismo, o llame SetEmbedAllFonts(1) una vez en la configuración para que un 0 extraviado no se cuele

¿Cómo verifica un archivo PDF/X antes de mandarlo a imprenta?

Ejecute CheckFileCompliance con ComplianceTest = 3 sobre el archivo guardado. TPDFlib.CheckFileCompliance devuelve 0 cuando no registró hallazgos, y de lo contrario un handle de lista de cadenas cuyos ítems son líneas con prefijo PDFX-. La auditoría PDF/X lee el archivo a través del mismo lector de streaming que el preflight PDF/A y PDF/UA en PDFlibPas, y revisa las reglas que un preflight de impresión revisa primero:

  • El archivo no está cifrado, el XMP carga un GTS_PDFXVersion conocido, y la versión de PDF no excede la base de la parte
  • Existe exactamente un output intent, su subtipo es GTS_PDFX, y carga un perfil de destino y un identificador de condición de salida
  • El diccionario de información declara /Trapped
  • Cada página tiene un TrimBox que queda dentro del MediaBox, y un BleedBox, cuando existe, encierra al TrimBox
  • Cada fuente usada en una página tiene un programa incrustado, con las fuentes Type0 verificadas a través de su fuente descendiente
  • Ningún tipo de anotación prohibido (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), ninguna acción JavaScript de apertura ni JavaScript a nivel de documento, ningún archivo incrustado y ningún filtro LZWDecode en el grafo de objetos
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
  Report: TStrings): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0);  // 3 = PDF/X
  Result := ListId = 0;
  if Result then
    Exit;
  try
    for I := 1 to Pdf.GetStringListCount(ListId) do       // los ítems empiezan en 1
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

Sea claro sobre lo que significa ese pase. El validador de PDFlibPas audita las reglas estructurales listadas arriba, y esas son las que atrapan errores del generador. No es un preflight fiel al color: no revisa cobertura total de tinta, resolución de imágenes ni si el perfil incrustado coincide con la condición que corre su imprenta. Úselo como compuerta en su pipeline de build o exportación, y deje las verificaciones específicas de prensa al perfil de preflight de la imprenta, que es lo que correrán de todos modos

Preguntas frecuentes

¿PDFlibPas soporta la transparencia de PDF/X-4?

Sí. En SetPDFXMode(3), PDFlibPas permite transparencia, blend modes y grupos de contenido opcional, y escribe un archivo PDF 1.6. Esas mismas llamadas devuelven 0 bajo PDF/X-1a y PDF/X-3

¿Puede PDFlibPas convertir un PDF existente a PDF/X?

No. SetPDFXMode aplica solo a documentos creados en la sesión actual de TPDFlib y devuelve 0 para un archivo cargado. Aun así puede auditar cualquier PDF existente con CheckFileCompliance y el test 3

¿Qué código de error devuelve una llamada PDF/X rechazada?

Los rechazos de espacio de color y de cifrado de llave pública fijan LastErrorCode en 601. Otras compuertas, como transparencia, JavaScript y las fuentes Standard 14, solo devuelven 0, así que pruebe siempre el resultado de la función

La salida PDF/X, el validador PDF/X y el resto del toolset de impresión y archivo son parte de PDFlibPas, la biblioteca PDF nativa en Object Pascal de losLab para Delphi. Vea la página de producto de PDFlibPas Delphi PDF Library para ediciones, plataformas y la referencia completa de la API