Artículo técnico

HotPDF y PDF/X-1a: detectar contenido RGB en Delphi

Cuando PDFXCompliance se pone en 'X-1a', HotPDF valida el color del contenido al final mismo del trabajo: EndDoc llama a THotPDF.EnsurePDFX1aContentColorSpaces, que rechaza cualquier contenido DeviceRGB, CalRGB o ICCBased RGB en cualquier parte de cualquier página y lanza una excepción que nombra el recurso infractor. El archivo nunca se escribe como un documento PDF/X-1a que la imprenta va a devolver

Esa última parte es el punto. Un archivo PDF/X-1a con un solo logo RGB escapado se ve perfectamente bien en pantalla, pasa una revisión casual, y luego falla el preflight en la imprenta dos días antes del turno de prensa. Capturarlo en su build de Delphi, con un mensaje que le dice qué página y qué imagen, es mucho más barato que enterarse por un operador de preimpresión molesto

¿Por qué PDF/X-1a rechaza contenido RGB?

PDF/X-1a rechaza RGB porque el formato promete que cada color ya está finalizado para una condición de impresión CMYK específica. ISO 15930-1:2001 §6.2.3 limita el color de contenido a DeviceCMYK, DeviceGray y colorantes planos (spot), o sea espacios Separation y DeviceN cuyos alternates son CMYK o Gray. El OutputIntent GTS_PDFX incrustado en el archivo nombra esa condición de impresión, por ejemplo FOGRA39 offset couché, y el flujo receptor tiene derecho a mandar los números directo a las planchas sin conversión de color alguna

RGB rompe ese contrato. Un valor RGB describe un color relativo a un monitor o un espacio de trabajo, no una receta de tinta, así que alguien aguas abajo tendría que decidir cómo separarlo, y X-1a existe precisamente para quitarle esa decisión al receptor. ISO 32000-1 §8.6 agrupa los espacios de color PDF en familias device, CIE-based y especiales, y los miembros RGB aparecen en más de una familia: DeviceRGB es un espacio device, CalRGB e ICCBased de tres componentes son CIE-based, y un espacio Indexed o Separation puede esconder una base o alternate RGB debajo. Un verificador que solo busca el operador rg se pierde la mayoría. La edición posterior PDF/X-1a:2003 (ISO 15930-4) conserva la misma regla de color; note que el valor 'X-1a' de HotPDF escribe la identificación PDF/X-1a:2001 en el paquete XMP

Si su contenido realmente es RGB y debe seguir siéndolo, X-1a es el objetivo equivocado. HotPDF acota esta verificación solo a 'X-1a', así que 'X-3' y 'X-4', que permiten color basado en ICC y calibrado, no se ven afectados

Qué verifica en realidad EnsurePDFX1aContentColorSpaces

EnsurePDFX1aContentColorSpaces combina una bandera de tiempo de dibujo con un recorrido recursivo de los recursos de cada página, y corre al final en la compuerta PDF/X. EndDoc primero hace cumplir los requisitos generales de PDF/X: Title debe estar puesto, Trapped debe ser 'True', 'False' o 'Unknown', debe existir un OutputIntent GTS_PDFX (EnsurePDFXOutputIntent), y cada página necesita un TrimBox o ArtBox (EnsurePDFXPageBoxes). Solo después de que esos pasen corre la verificación de color

Secuencia de la compuerta PDF/X dentro de HotPDF EndDoc para Delphi: primero metadatos, OutputIntent y page boxes, luego la verificación del color de contenido RGB
la verificación del color corre al final en la compuerta X-1a, y una bandera de tiempo de dibujo significa que el RGB dibujado antes de asignar la conformidad igual se captura

La primera ruta es una bandera de todo el documento. Los setters de color RGB de HotPDF, la incrustación de imágenes bitmap y las entradas de color de anotaciones registran el uso de DeviceRGB mientras usted dibuja, lo que cubre los operadores inline rg y RG que nunca aparecen en un diccionario de recursos. La segunda ruta recorre los /Resources de cada página e inspecciona:

  • Espacios de color nombrados en /ColorSpace, incluida la abreviatura de imagen inline RGB como sinónimo de DeviceRGB
  • El /ColorSpace de cada Image XObject
  • Form XObjects y Patterns de tiling, recurriendo a sus propios /Resources anidados
  • Shading Patterns y entradas en el diccionario /Shading de la página, vía el /ColorSpace del shading
  • Espacios base Indexed, alternates Separation y DeviceN, y espacios base de Patterns, seguidos hasta una profundidad de anidamiento de 8

Tres cosas se rechazan: DeviceRGB, CalRGB, y espacios ICCBased cuyo stream de perfil declara N = 3. Los diccionarios de recursos compartidos entre Form XObjects y patterns se visitan una vez, así que un conjunto de forms que se referencian mutuamente no mete el recorrido en un bucle. El método es público en THotPDF, pero en uso normal usted nunca lo llama, porque EndDoc lo hace por usted

Dónde se esconde el RGB en los recursos de una página PDF: forms anidados, patterns, shadings y alternates llevan el recorrido de HotPDF en Delphi hasta DeviceRGB, CalRGB o ICCBased RGB
el recorrido sigue los recursos anidados hasta profundidad 8 y rechaza los espacios DeviceRGB, CalRGB e ICCBased con N = 3 dondequiera que aparezcan

Dibujar una página X-1a en Delphi que pase la verificación

Un documento X-1a conforme de HotPDF necesita un OutputIntent CMYK, un TrimBox en cada página, y color puesto solo por las APIs CMYK, Gray y spot. El esqueleto de abajo usa el perfil FOGRA39; AddPDFXOutputIntent recibe el identificador de condición de salida, una cadena de información, el stream del perfil ICC, su conteo de componentes, y el espacio alternate

var
  Pdf: THotPDF;
  Profile: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'brochure-x1a.pdf';
    Pdf.Title := 'Spring brochure, press edition';
    Pdf.Trapped := 'False';
    Pdf.PDFXCompliance := 'X-1a';
    Pdf.BeginDoc;

    Profile := TFileStream.Create('CoatedFOGRA39.icc', fmOpenRead or fmShareDenyWrite);
    try
      Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)',
        'Coated FOGRA39 offset', Profile, 4, 'DeviceCMYK');
    finally
      Profile.Free;
    end;
    Pdf.CurrentPage.SetTrimBox(0, 0, 612, 792);   // requerido en cada página

    Pdf.CurrentPage.SetCMYKFillColor(0, 0.5, 0.8, 0.1);
    Pdf.CurrentPage.Rectangle(72, 600, 468, 120);
    Pdf.CurrentPage.Fill;

    Pdf.CurrentPage.SetGrayFillColor(0.2);         // texto DeviceGray
    Pdf.CurrentPage.SetFont('Arial', [], 14);
    Pdf.CurrentPage.TextOut(72, 560, 0, 'Spring catalog, press edition');
    Pdf.EndDoc;                                    // la verificación de color corre aquí
  finally
    Pdf.Free;
  end;
end;

Cada llamada basada en TColor es una trampa en este contexto. SetRGBFillColor, SetRGBStrokeColor y SetRGBColor toman un color GDI y emiten DeviceRGB, así que una rutina helper escrita hace años para reportes orientados a pantalla contaminará un trabajo de impresión en cuanto se reutilice. Use SetCMYKFillColor, SetCMYKStrokeColor o SetCMYKColor con componentes en el rango 0 a 1, o los equivalentes Gray

Colores planos y degradados CMYK

Los colorantes planos (spot) están permitidos en X-1a mientras su alternate sea CMYK o Gray. RegisterSeparation construye el espacio Separation y devuelve su nombre de recurso, y RegisterAxialGradient elige el espacio de color del shading según el número de componentes que pase, donde cuatro componentes significa DeviceCMYK

var
  SpotCS, Ramp: AnsiString;
begin
  // Tinta plana con una construcción CMYK aproximada como alternate
  SpotCS := Pdf.RegisterSeparation('Corporate Red', 'DeviceCMYK',
    [0, 1, 0.8, 0.05]);
  Pdf.CurrentPage.SetFillColorSpace(SpotCS);
  Pdf.CurrentPage.SetFillColor([1.0]);           // tinte al 100%
  Pdf.CurrentPage.Rectangle(72, 420, 200, 80);
  Pdf.CurrentPage.Fill;

  // 4 componentes por parada: el shading es DeviceCMYK, no DeviceRGB
  Ramp := Pdf.RegisterAxialGradient(72, 300, 540, 300,
    [0, 0, 0, 0], [1, 0.6, 0, 0.2]);
  Pdf.CurrentPage.SetFillPattern(Ramp);
  Pdf.CurrentPage.Rectangle(72, 260, 468, 80);
  Pdf.CurrentPage.Fill;
end;

Pase tres componentes a la misma llamada de degradado y obtiene un shading DeviceRGB, que el recorrido de recursos rechaza bajo el nombre del shading pattern. Lo mismo vale para una Separation registrada con 'DeviceRGB' como alternate: el nombre spot se ve inofensivo, pero el recorrido sigue el alternate y encuentra RGB debajo. Cómo se componen tales tintas planas cuando un visor o RIP recurre al alternate está cubierto en el artículo sobre renderizar colores planos Separation y DeviceN en Delphi

Las imágenes raster necesitan la misma disciplina. HotPDF fija los bitmaps comprimidos con Flate a DeviceRGB, mientras que un JPEG incrustado con icJpeg conserva su propio conteo de componentes, así que un JPEG CMYK de cuatro componentes o un JPEG en escala de grises está bien y un JPEG RGB ordinario no. Convierta el arte al perfil de prensa en su pipeline de imágenes antes de que llegue al PDF

¿Por qué /DefaultRGB no rescata un archivo PDF/X-1a?

/DefaultRGB no ayuda a X-1a porque la regla trata de lo que recibe la prensa, no de si el RGB está caracterizado. Para PDF/A, HotPDF deja que RegisterDefaultColorSpace('DefaultRGB', ...) absorba el uso de DeviceRGB: una vez que un espacio de color Default mapea DeviceRGB a un perfil ICC, el contenido es independiente del dispositivo y la verificación de consistencia PDF/A lo acepta. Los desarrolladores que ya pasaron por ese flujo tienden a intentar el mismo arreglo aquí

EnsurePDFX1aContentColorSpaces deliberadamente ignora ese mapeo. Un valor RGB caracterizado sigue siendo RGB, y el OutputIntent X-1a es una condición de impresión CMYK, así que alguien igual tendría que convertirlo. Si recuerda una diferencia entre los dos perfiles, de archivo e impresión, que sea esta: PDF/A pregunta si un color es inequívoco, PDF/X-1a pregunta si ya es tinta

El orden de dibujo, y la configuración de página que no cuenta

Como la verificación corre en EndDoc sobre contenido ya escrito, el orden en que configure las cosas no importa. RGB dibujado antes de asignar PDFXCompliance, o antes de registrar el OutputIntent, igual se captura. Eso importa para código que construye páginas en rutinas compartidas y decide el perfil de salida tarde, a veces desde una preferencia de usuario elegida cuando medio documento ya existe

Pdf.BeginDoc;
// ... OutputIntent y TrimBox como antes ...
Pdf.CurrentPage.SetRGBFillColor(clRed);   // helper legacy, emite DeviceRGB
Pdf.CurrentPage.Rectangle(72, 72, 200, 100);
Pdf.CurrentPage.Fill;

Pdf.PDFXCompliance := 'X-1a';             // decidido después de dibujar
Pdf.EndDoc;                               // igual lanza: DeviceRGB registrado

Una bandera de tiempo de dibujo tiene una trampa que vale conocer si usted construye una verificación similar. Cada página nueva se inicializa con un estado gráfico negro mediante SetRGBColor(0), que emite un par 0 0 0 rg y 0 0 0 RG, y si ese boilerplate de configuración de página pudiera poner la bandera DeviceRGB, todo documento se vería contaminado de RGB, tanto para esta verificación como para la de consistencia de color PDF/A. HotPDF guarda y restablece la bandera alrededor de esa inicialización, así que solo cuenta el color que usted realmente dibuja

¿Cómo encuentra el contenido RGB cuando EndDoc lanza?

Lea la ubicación al inicio del mensaje de la excepción, porque le dice cuál de las dos rutas encontró el problema. El mensaje tiene la forma PDFXCompliance 'X-1a': <where> uses <what>, seguido de la referencia ISO 15930-1 y el consejo de convertir a CMYK o Gray o cambiar a X-3 o X-4. El validador lanza una Exception simple en la primera violación, así que compare por el texto y no por una clase de excepción

Anatomía de la excepción RGB de HotPDF EndDoc en Delphi: la bandera de tiempo de dibujo reporta uso de todo el documento mientras el recorrido de recursos nombra la página y la clave exactas
lea la ubicación al inicio del mensaje — un reporte de todo el documento lo manda al código de dibujo, y un nombre de página y recurso apunta a una sola clave
  • document content uses DeviceRGB (rg/RG operators, RGB images or annotation colours recorded during drawing) significa que se disparó la bandera de tiempo de dibujo. No hay número de página, porque la bandera es de todo el documento y se verifica antes del recorrido. Busque en su código de dibujo llamadas SetRGB*, bitmaps comprimidos con Flate, JPEG RGB y colores de anotación TColor
  • page 3 image XObject /Im2 uses DeviceRGB content o page 1 shading Pattern /P1 uses DeviceRGB content viene del recorrido de recursos y apunta a una clave de recurso exacta
  • uses an ICCBased RGB colour space (N=3) después de una ubicación ColorSpace /... es el resultado típico de registrar un perfil sRGB con RegisterICCProfile y seleccionarlo por SetFillColorSpace, una ruta que la bandera de tiempo de dibujo no ve
  • Las ubicaciones anidadas se encadenan, por ejemplo page 2 Form XObject /Fm1 ColorSpace /CS1, así que puede seguir el camino desde la página hasta el recurso que lleva el espacio RGB
try
  Pdf.EndDoc;
except
  on E: Exception do
  begin
    if Pos('X-1a', E.Message) > 0 then
      LogPrepressFailure(Pdf.FileName, E.Message);  // su propio logger
    raise;
  end;
end;

Como solo se reporta la primera violación, arreglar un trabajo con varias fuentes de RGB es iterativo: arregle, recompile, lea el siguiente mensaje. Para trabajos por lotes conviene registrar el mensaje completo por archivo y mantener una lista corta de infractores conocidos, como un bitmap de logo o un helper de gráficos que todavía recibe TColor

Dónde encaja la compuerta RGB en su preflight

EnsurePDFX1aContentColorSpaces es una compuerta RGB dirigida que corre dentro de su build, y se combina con un preflight completo de PDF/X-1a en vez de reemplazarlo. Cubre las familias RGB, mediante el recorrido de recursos desde los /Resources de cada página y mediante la bandera de tiempo de dibujo que los setters de color de HotPDF mantienen para operadores inline rg y RG; los espacios de color anidados se siguen hasta profundidad 8 como guarda contra ciclos malformados, y la primera violación se reporta por EndDoc. Otras familias de color como Lab o CalGray son asunto del preflight completo, así que pase el archivo terminado por un validador externo como parte de su build para obtener el veredicto completo de conformidad. El panorama más amplio de salida de conformidad está en la guía de salida PDF/A, PDF/X y PDF/UA con HotPDF, y conectar un validador a un pipeline desatendido está cubierto en automatizar verificaciones de preflight PDF en Delphi

HotPDF es un componente PDF VCL nativo para Delphi y C++Builder con salida PDF/X, PDF/A y PDF/UA, APIs de color CMYK y spot, y soporte de OutputIntent ICC. Detalles, licenciamiento y descarga de prueba están en la página del producto del componente PDF HotPDF para Delphi