Artículo técnico

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

PDFlibPas produce archivos PDF/X para imprenta 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, añade una intención de salida GTS_PDFX, declara /Trapped /False, fija la versión de PDF y desde ese momento 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, normalmente un viernes, normalmente por algo que el código generador podía haber cazado: un logo RGB en un trabajo X-1a, una fuente referenciada en vez de incrustada, una página sin tamaño de corte. PDF/X existe para hacer esos fallos estructurales en lugar de visuales, y la parte que le toca a la biblioteca es impedir que los produzcas de entrada

¿Qué parte PDF/X debería apuntar una aplicación Delphi?

Elige PDF/X-4 salvo que el flujo receptor diga otra cosa, y elige 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 traduce en una compuerta dentro de PDFlibPas:

  • PDF/X-1a:2003 (ISO 15930-4): sintaxis PDF 1.4, solo colores CMYK, gris y directos, sin color basado en ICC, sin transparencia. El archivo ya está separado en colores de imprenta, 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 la propia intención de salida describe una condición RGB
  • PDF/X-4 (ISO 15930-7): sintaxis PDF 1.6, con transparencia viva y contenido opcional (capas). Es la parte para la que están construidos los RIP modernos, porque aplanar transparencia a la salida es de donde venía gran parte del viejo dolor de X-1a

X-1a sigue estando por todas partes en la entrega de publicidad y en flujos de imprenta antiguos que quieren un archivo que puedan grabar sin pensarlo. X-4 es la mejor elección de ingeniería siempre que la imprenta lo acepte: la transparencia sobrevive intacta y las decisiones de color ocurren en el RIP con el perfil real de la prensa, no en tu código. Los números de modo son 1, 2 y 3 respectivamente, y GetInformation(203) te devuelve el modo activo. PDFlibPas también define números de modo superiores para la familia PDF/X-6 sobre PDF 2.0, que siguen reglas distintas de versión e intención de salida y quedan fuera de este artículo

Qué escribe SetPDFXMode en el archivo

TPDFlib.SetPDFXMode se encarga de la identificación y del boilerplate que un lector PDF/X comprueba primero, así que el código de la aplicación solo tiene que aportar el perfil real de imprenta y el contenido. En concreto, una llamada con éxito hace cuatro cosas:

  • Escribe la cadena de edición en el 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 aparte
  • Añade una intención de salida si el documento no tiene ninguna y fija su subtipo en /S /GTS_PDFX, el subtipo que buscan los lectores PDF/X (ISO 32000-1 §14.11.5 define el diccionario de intención de salida)
  • 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 ocurren al guardar. Cada página sin TrimBox recibe uno 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 el flag de impresión que la norma espera. El fallback del 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 mayor, y la guía de los valores por defecto de TrimBox, BleedBox y CropBox en PDFlibPas cubre cómo fijar esas cajas y cómo la herencia puede sorprenderte

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]);

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

    // Embed = 1 incrusta el programa de fuente completo; 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 esquema es deliberado. LoadOutputIntentProfile sustituye el perfil de una intención de salida que ya existe, así que tiene que correr después de que SetPDFXMode haya creado una. La llamada tampoco puede conjurar una intención de la nada: en un documento sin intención de salida simplemente devuelve 0

¿Por qué hay que sustituir la intención de salida por defecto?

La intención de salida que SetPDFXMode añade para los modos 1 a 3 declara DeviceCMYK, pero los bytes ICC que hay detrás son el perfil sRGB incluido de serie en la biblioteca con el número de componentes reescrito a 4. Ese valor por defecto existe por compatibilidad hacia atrás y basta para que el archivo sea estructuralmente PDF/X, pero el perfil incrustado no describe ninguna prensa. Una herramienta de preflight que inspeccione la cabecera del perfil verá un perfil RGB con etiqueta CMYK, y una imprenta que confíe en la intención para la prueba de color recibe tonterías

Así que trata LoadOutputIntentProfile(FileName, 'DeviceCMYK') como obligatorio para trabajo de imprenta real y pásale el perfil ICC que tu proveedor de impresión nombre para el papel y la prensa. TPDFlib.LoadOutputIntentProfile intercambia el stream de DestOutputProfile y actualiza /N, y deja el resto del diccionario de intención tal como está. En particular, el identificador de condición de salida y las cadenas descriptivas de la intención por defecto siguen llevando valores al estilo sRGB, así que si tu imprenta casa por el identificador en lugar de por el perfil incrustado, confirma con ella qué comprueba antes de enviar. Ese es un hueco genuino de los valores por defecto de la era 2003, no algo que tu código hiciera mal

Trampa uno: la versión que no elegiste

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

La negativa sigue existiendo para el único caso donde es correcta. Si algo anterior llamó a 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 va a pisarle esa decisión. En código de configuración de documento compartido, donde un helper bloquea la versión por motivos ajenos, este es exactamente el punto 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.
    // Sáltate el bloqueo en trabajos de imprenta. 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 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 exige inspección y reparación, no un flag

Trampa dos: compuertas que rechazan en silencio

Cuando un modo PDF/X está activo, PDFlibPas rechaza cada llamada de contenido que la parte elegida prohíbe, en el momento de hacerla, en lugar de dejar que el archivo falle el preflight después. Las negativas cubren rellenos y trazos RGB de dispositivo (todo X-1a; X-3 y X-4 salvo que la intención de salida sea RGB), colores basados en ICC en X-1a, transparencia y modos de fusión en X-1a y X-3, contenido opcional en X-1a y X-3, acciones JavaScript, adjuntos de archivo incrustados, el filtro de imagen LZW, fuentes Standard 14 que quedarían sin incrustar y el cifrado de clave pública. El cifrado por contraseña vía Encrypt no queda sujeto a compuerta en el momento de creación, pero el validador marca cualquier archivo cifrado, así que mantén los trabajos de imprenta sin cifrar

Lo que las negativas 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 sosteniendo un valor de una llamada anterior. El código que solo comprueba LastErrorCode tras un lote de llamadas de dibujo se perderá la mitad de las negativas, así que comprueba 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 aviso propio. AddStandardFont devuelve 0 bajo PDF/X porque la biblioteca no trae los contornos de las Standard 14 y por tanto no puede incrustarlas. AddTrueTypeFont es distinto: en modo PDF/A te promociona un Embed = 0 a incrustación completa, pero en modo PDF/X no lo hace, así que una fuente de sistema referenciada entra en el archivo y solo la atrapa el validador. Pasa 1 (completa) o 2 (subconjunto) tú mismo, o llama una vez a SetEmbedAllFonts(1) en la configuración para que un 0 despistado no se cuele

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

Ejecuta CheckFileCompliance con ComplianceTest = 3 sobre el archivo guardado. TPDFlib.CheckFileCompliance devuelve 0 cuando no registró hallazgos y, si no, un handle de lista de cadenas cuyos elementos son líneas con prefijo PDFX-. La auditoría PDF/X lee el archivo con el mismo lector en streaming que el preflight PDF/A y PDF/UA en PDFlibPas, y comprueba las reglas que un preflight de imprenta comprueba primero:

  • El archivo no está cifrado, el XMP lleva un GTS_PDFXVersion conocido y la versión de PDF no supera la base de la parte
  • Existe exactamente una intención de salida, su subtipo es GTS_PDFX y lleva 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, contiene al TrimBox
  • Cada fuente usada en una página tiene un programa incrustado, con las fuentes Type0 comprobadas a través de su fuente descendiente
  • Sin tipos de anotación prohibidos (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), sin acción de apertura JavaScript ni JavaScript a nivel de documento, sin archivos incrustados y sin filtro LZWDecode en ningún punto del 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 elementos empiezan en 1
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

Ten claro qué significa ese aprobado. El validador de PDFlibPas audita las reglas estructurales listadas arriba, y esas son las que cazan errores del generador. No es un preflight fiel al color: no comprueba la cobertura total de tinta, la resolución de imagen ni si el perfil incrustado coincide con la condición que corre tu imprenta. Úsalo como compuerta en tu pipeline de build o exportación, y deja las comprobaciones específicas de prensa al perfil de preflight de la imprenta, que es lo que van a correr de todas formas

FAQ

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

Sí. En SetPDFXMode(3), PDFlibPas permite transparencia, modos de fusión 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 se aplica solo a documentos creados en la sesión TPDFlib actual y devuelve 0 para un archivo cargado. Aun así puedes auditar cualquier PDF existente con CheckFileCompliance y el test 3

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

Las negativas por espacio de color y cifrado de clave pública fijan LastErrorCode en 601. Otras compuertas, como transparencia, JavaScript y fuentes Standard 14, solo devuelven 0, así que comprueba siempre el resultado de la función

La salida PDF/X, el validador PDF/X y el resto del conjunto de herramientas de imprenta y archivado forman parte de PDFlibPas, la biblioteca PDF nativa en Object Pascal de losLab para Delphi. Consulta la página de producto de PDFlibPas Delphi PDF Library para ediciones, plataformas y la referencia completa de la API