Artículo técnico

Color de dispositivo y OutputIntent en PDF/A con Delphi

veraPDF marca ISO 19005-1 6.2.3.3 en un archivo que nunca dibuja RGB, llama incalibrado a un re f desnudo, o reprueba la página 2 mientras una página 1 idéntica pasa. PDF/A juzga esas reglas por lo que cada página pinta, así que el validador PDF/A de PDFium Component para Delphi traza cada página hasta forms, patterns, glifos y anotaciones antes de sentenciar sobre color y transparencia

Qué exigen en realidad 6.2.3.3 y 6.2.4.3

ISO 19005-1 6.2.3.3 mete tres reglas independientes en un solo párrafo. Un archivo conforme puede usar DeviceRGB o DeviceCMYK pero no debe usar ambos; si se usa cualquier espacio de color incalibrado, el archivo debe contener un OutputIntent de PDF/A-1; y DeviceRGB solo se permite con un intent cuyo perfil es RGB, DeviceCMYK solo con uno CMYK. DeviceGray queda fuera de esa lista porque la cláusula sigue explicando cómo convertir gris bajo cualquiera de los dos intents. El intent en sí es opcional: 6.2.2 dice que un archivo puede especificar uno, y solo cuenta una entrada OutputIntents con /S /GTS_PDFA1 y un stream de perfil ICC válido en /DestOutputProfile. Así que un verificador que le exige un intent a todo archivo rechaza documentos conformes que nunca pintan color de dispositivo. La Parte 1 no menciona DefaultRGB ni DefaultCMYK, y sin embargo un color remapeado a través de ellos se renderiza en un espacio basado en CIE (ISO 32000-1 §8.6.5.6), lo que satisface la exigencia de independencia de dispositivo de 6.2.3.1; la suite de pruebas de veraPDF para 6.2.3.3 lo trata como calibrado, y PDFium Component lee la cláusula igual. La prohibición de mezcla queda aparte: un archivo PDF/A-1 que pinta RGB y CMYK falla incluso cuando entradas Default calibran ambos. ISO 19005-2 6.2.4.3 suelta esa prohibición y calibra cada espacio en sus propios términos, como muestra la columna derecha de abajo, así que PDFium Component corre un escaneo por cada parte y aplica el chequeo de mezcla solo a las declaraciones PDF/A-1

Espacio de dispositivo pintadoPDF/A-1 (ISO 19005-1 6.2.3.3)PDF/A-2 y posteriores (ISO 19005-2 6.2.4.3)
DeviceRGBDefaultRGB en alcance o un intent RGB; no junto a DeviceCMYKDefaultRGB en alcance, o un OutputIntent RGB
DeviceCMYKDefaultCMYK en alcance o un intent CMYK; no junto a DeviceRGBDefaultCMYK en alcance, o un OutputIntent CMYK
DeviceGrayDefaultGray en alcance, o cualquier OutputIntent de PDF/A-1DefaultGray en alcance, o cualquier OutputIntent de PDF/A

¿Qué cuenta como usar un espacio de color?

Usar un espacio de color significa pintar con él sobre algo que un lector va a renderizar. Declararlo no basta. La suite de veraPDF lo demuestra con un archivo aprobado de 6.2.3.3: un Form XObject con nombre fija CMYK con K y k, vive en el diccionario de recursos de la página, jamás es referenciado desde el contenido de la página, y el OutputIntent del archivo es RGB. Una búsqueda de nombres de color de dispositivo en todo el archivo reprueba ese archivo, y se pierde uno que pinta solo DeviceRGB bajo un intent CMYK, donde ningún par de nombres choca. PDFium Component recorre en cambio el grafo de dibujo, construido sobre su escáner preflight PDF/A a nivel de bytes. Desde cada página, el recorrido cuenta el color pintado por:

  • los streams de contenido de la página, y cada Form XObject que invocan con Do, forms anidados incluidos
  • un pattern seleccionado con scn o SCN, leído con los Resources propios del pattern
  • los CharProcs de una fuente Type 3 seleccionada con Tf
  • los image XObjects y las imágenes inline, por el espacio de color que su propio diccionario nombra
  • los streams de apariencia de anotaciones, pintados encima de la página; cuando el /AP /N de un widget es un diccionario de estados, cada estado cuenta, porque accionar el campo pinta el que /AS no seleccionó

La mitad más sutil de la alcanzabilidad es el color que el contenido nunca nombra. q 0 0 10 10 re f Q no contiene ningún operador de color. Aun así pinta. El espacio de color inicial del estado de gráficos es DeviceGray (ISO 32000-1 Tabla 52), así que el relleno es negro DeviceGray, incalibrado bajo todas las partes salvo que exista un OutputIntent de PDF/A o un DefaultGray. El escaneo entonces lleva la cuenta de si algo ya eligió un color: rg, k, g y sus formas de trazo seleccionan un espacio de dispositivo, cs y CS seleccionan lo que nombren, y un operador de relleno, trazo o texto alcanzado antes de cualquier elección registra DeviceGray. /CS0 cs 1 0 0 /P0 scn ya eligió un espacio, así que ese scn no es gris

El camino de pintura alcanzable que PDFium Component recorre en Delphi antes de sentenciar sobre el color de dispositivo PDF/A: forms, patterns, glifos Type 3, imágenes y streams de apariencia de anotaciones
Declarar un espacio de color no basta — el recorrido cuenta lo que cada página pinta, incluido el gris de dispositivo que un re f desnudo levanta del estado inicial

Las imágenes inline traen su espacio de color y sus muestras crudas dentro del propio stream de contenido, y un escáner tiene que leer ambas con cuidado. El /CS puede ser una abreviación (G, RGB, CMYK, I) o un nombre de recurso como uno que resuelve a [/Indexed /DeviceRGB 255 13 0 R], cuya base cuenta; y los bytes entre ID y EI son binario arbitrario, así que EI termina la imagen solo entre espacios en blanco, o un rg dentro de muestras comprimidas se vuelve un operador fantasma

% página 1: DefaultRGB en alcance, y un XObject que nada invoca
4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R
           /Resources << /ColorSpace << /DefaultRGB 9 0 R >>
                         /XObject << /Fm0 6 0 R >> >> >>
endobj
% página 2: el mismo stream de contenido, sin subdiccionario ColorSpace
7 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /Resources << >> >>
endobj
5 0 obj << /Length 46 >> stream
1 0 0 rg 72 72 200 100 re f
q 0 0 10 10 re f Q
endstream
endobj
6 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 50 50] /Length 24 >> stream
0 0 0 1 k 0 0 50 50 re f
endstream
endobj
9 0 obj [/ICCBased 10 0 R]
endobj

Cada síntoma de la apertura se deduce de este fragmento. /Fm0 jamás se encuentra con un Do, así que su k es irrelevante. El re f desnudo en ambas páginas es DeviceGray sin nada que lo calibre. El rg de la página 1 queda cubierto por el DefaultRGB en los recursos con los que pinta la página 1; la página 2 corre el stream idéntico contra recursos que carecen del mapeo, así que los mismos bytes están incalibrados ahí. Esperado para una declaración PDF/A-1b: pvaiMissingOutputIntent con pvaiMissingIccProfile, impulsados por el RGB de la página 2 y el gris de ambas páginas. El form no aporta nada; invocado, su CMYK agregaría encima la prohibición de mezcla de PDF/A-1

Los espacios de color por defecto pertenecen al diccionario de recursos que pinta

Los mapeos Default se buscan en el subdiccionario ColorSpace del diccionario de recursos actual (ISO 32000-1 §8.6.5.6), lo que los hace exactamente tan locales como los recursos mismos; un documento no tiene un default global. Un image XObject no tiene Resources propios, así que una imagen cuyo diccionario dice /ColorSpace /DeviceCMYK queda calibrada por el DefaultCMYK de la página o form que la dibuje, y la misma imagen dibujada desde dos lugares puede quedar calibrada en uno y no en el otro. Los Form XObjects, los tiling patterns y los procedimientos de glifo Type 3 cargan sus propios Resources, así que el DefaultCMYK de una página no llega a entrar en ellos, mientras que un mapeo en su propio diccionario sí. PDFium Component acota los mapeos de la misma manera. El veredicto vuelve como un record TPdfAValidationResult: la Conformance detectada más un conjunto Issues. Eso alcanza para hacer triage de una carpeta

uses
  System.SysUtils, System.Classes, System.TypInfo, FPdfPdfa;

procedure ReportColorFindings(const FileName: string; Log: TStrings);
const
  ColorRules: TPdfAValidationIssues = [pvaiMissingOutputIntent,
    pvaiMissingIccProfile, pvaiMixedDeviceColorSpaces, pvaiBlendSpaceUndefined];
var
  Source: TFileStream;
  Res: TPdfAValidationResult;
  Found: TPdfAValidationIssues;
  Issue: TPdfAValidationIssue;
begin
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Source);   // lee desde la posición 0
  finally
    Source.Free;
  end;
  Log.Add(ExtractFileName(FileName) + ': ' +
    GetEnumName(TypeInfo(TPdfAConformance), Ord(Res.Conformance)));
  Found := Res.Issues * ColorRules;
  for Issue in Found do
    case Issue of
      pvaiMissingOutputIntent:
        Log.Add('  reachable uncalibrated device color, no PDF/A OutputIntent');
      pvaiMissingIccProfile:
        Log.Add('  no OutputIntent with a DestOutputProfile ICC stream');
      pvaiMixedDeviceColorSpaces:
        Log.Add('  RGB/CMYK use disagrees with the intent, or PDF/A-1 paints both');
      pvaiBlendSpaceUndefined:
        Log.Add('  transparent page without a blending color space');
    end;
  if Found = [] then
    Log.Add('  device color and blending rules satisfied');
end;

pvaiMissingOutputIntent y pvaiMissingIccProfile aparecen juntos cuando el recorrido alcanza color de dispositivo incalibrado y no existe ningún OutputIntent de PDF/A. Un pvaiMissingIccProfile solo significa que existe un intent pero no carga perfil, cosa que 6.2.2 no acepta como intent de PDF/A. pvaiMixedDeviceColorSpaces cubre tanto la prohibición de mezcla de PDF/A-1 como un desajuste de componentes: DeviceRGB incalibrado bajo un intent de cuatro componentes, o DeviceCMYK bajo uno de tres. El conjunto nombra la regla y no la página, así que en un documento largo o fusionado, compare qué páginas declaran entradas Default y cuáles no

Una imagen DeviceCMYK dibujada desde dos diccionarios de recursos en Delphi: calibrada por DefaultCMYK en una página e incalibrada en otra, tal como lo reporta PDFium Component
Los espacios de color por defecto pertenecen al diccionario de recursos que pinta, así que la misma imagen puede estar calibrada donde la dibuja una página e incalibrada donde la dibuja un form

¿Cuándo necesita una página transparente su propio espacio de mezcla?

Una página transparente necesita su propio espacio de color de mezcla solo cuando el documento no tiene ningún OutputIntent de PDF/A. ISO 19005-2 6.2.10 (6.2.9 en PDF/A-4) convierte al intent en el espacio de color de mezcla por defecto en el sentido de ISO 32000-1 §11.3.4; sin intent, cada página que contiene transparencia debe cargar un Group cuyo diccionario de atributos (ISO 32000-1 Tabla 147) incluya CS. Ese CS también se sujeta a 6.2.4, así que un grupo de página /DeviceCMYK bajo un intent sRGB se reporta como si faltara, y PDFium Component levanta pvaiBlendSpaceUndefined para ambos; un espacio de grupo CalRGB o ICCBased no tiene tal dependencia. PDF/A-1 no llega a esta pregunta, porque ISO 19005-1 6.4 descarta la transparencia por completo

La parte difícil es la frase contiene transparencia. El Anexo A de ISO 19005-2 es normativo y prueba desde la página hacia afuera; un conteo de claves /SMask o valores /ca en todo el archivo responde otra pregunta. Las búsquedas de bytes también chocan con sintaxis de transparencia en otros lugares: /S /Trans, la acción de transición prohibida, es prefijo de cada grupo /S /Transparency, y /Screen es a la vez un subtipo de anotación prohibido y un modo de mezcla estándar, así que un nombre no significa nada hasta que se empareja con su clave. Siguiendo el Anexo A, PDFium Component trata una página como transparente cuando desde ella se alcanza cualquiera de estos:

  • un ExtGState que el contenido selecciona con gs que tenga un diccionario de soft mask, un ca o CA por debajo de 1, o un modo de mezcla distinto de Normal; uno declarado pero jamás seleccionado no cuenta
  • un Form XObject invocado con Do cuyo /Group tiene /S /Transparency, o cuyo propio contenido cumple alguna de estas condiciones; un tiling pattern se juzga como un form
  • un image XObject con un stream SMask o un valor SMaskInData por encima de 0
  • una descripción de glifo de una fuente Type 3 con la que la página muestra texto, juzgada como un form
  • un stream de apariencia de anotación, incluido cada estado de widget, y un modo de mezcla en el propio diccionario de la anotación

Los grupos también realimentan la regla del color de dispositivo, y aquí el corpus nos corrigió. La suite de veraPDF para 6.2.4.3 guarda archivos PDF/A-4 sin ningún OutputIntent que pintan RGB de dispositivo y se espera que pasen, porque el /Group de cada página nombra un CS calibrado. Asumimos que solo los grupos aislados podían calibrar, ya que un grupo no aislado se compone dentro de su fondo, y eso estaba mal para los grupos de página: la Tabla 147 anota que una página impuesta directamente sobre el medio de salida está efectivamente aislada sin importar I, así que su CS se honra. En un Form XObject o una apariencia de anotación el flag sí lo decide; la suite tiene un archivo de pase y uno de fallo que difieren solo en /I true. DeviceGray no toma calibración de un grupo, porque 6.2.4.3 pide DefaultGray o un intent, y un grupo cuyo CS es un espacio de dispositivo necesita calibrarse él mismo. PDFium Component por lo tanto deja que un grupo de página calibre diga lo que diga su /I, deja que un grupo de form o de apariencia calibre solo cuando está aislado, mantiene el gris fuera de la calibración por grupos, y aun así verifica un espacio de dispositivo que un grupo nombre

Matriz PDF/A para Delphi: cuándo una página transparente necesita su propio CS de mezcla y cómo juzga PDFium Component la calibración de grupos de página, form y apariencia
Un output intent es el espacio de mezcla por defecto, y un grupo de página calibra el color de dispositivo diga lo que diga su flag de aislamiento mientras que un grupo de form necesita /I true

Un OutputIntent en lugar de un grupo en cada página

Si su propio código escribe el archivo, empiece por un OutputIntent de PDF/A a nivel de documento. Una entrada de catálogo calibra DeviceGray en todas partes, cubre el espacio de dispositivo correspondiente en cada página, y es el espacio de mezcla por defecto para cada página transparente. Los grupos de página y las entradas Default se acotan a un diccionario de recursos cada uno, y la página que una fusión posterior agrega sin ellos es la que falla. PDF 2.0 agrega OutputIntents al objeto de página (ISO 32000-2 §7.7.3.3 y §14.11.5); PDFium Component acepta ese intent a nivel de página para PDF/A-4 cuando el catálogo no tiene ninguno, mientras que las partes 1 a 3, construidas sobre PDF 1.4 a 1.7, miran solo el catálogo. Para contenido RGB, SaveAsPdfAToStream agrega un intent de catálogo construido sobre el perfil ICC sRGB incrustado del writer de PDF/A, y validar la salida muestra si el contenido de verdad era solo RGB

uses
  System.SysUtils, System.Classes, PDFium, FPdfPdfa;

function ArchiveWithSrgbIntent(const InFile, OutFile: string;
  out Remaining: TPdfAValidationIssues): Boolean;
var
  Pdf: TPdf;
  Opts: TPdfASaveOptions;
  Output: TMemoryStream;
  Check: TPdfAValidationResult;
begin
  Result := False;
  Remaining := [];
  Pdf := TPdf.Create(nil);
  Output := TMemoryStream.Create;
  try
    Pdf.FileName := InFile;
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;                           // no se pudo abrir el archivo
    Opts := TPdfASaveOptions.Default;
    Opts.Conformance := pac2b;        // PDF/A-1 prohibiría la transparencia de plano
    // IccProfileData queda vacío: se usa el perfil sRGB IEC61966-2.1 incorporado
    if not Pdf.SaveAsPdfAToStream(Output, Opts) then
      Exit;
    Check := ValidatePdfACompliance(Output);
    Remaining := Check.Issues;
    // Esperado para contenido solo RGB: pvaiMissingOutputIntent y
    // pvaiBlendSpaceUndefined desaparecen. pvaiMixedDeviceColorSpaces ahora significa
    // que un camino alcanzable pinta DeviceCMYK que ningún DefaultCMYK calibra.
    if Check.IsCompliant then
    begin
      Output.SaveToFile(OutFile);
      Result := True;
    end;
  finally
    Output.Free;
    Pdf.Free;
  end;
end;

Un pvaiMixedDeviceColorSpaces que sobrevive al intent sRGB es el recorrido reportando DeviceCMYK en un camino alcanzable, y ese documento necesita un mapeo DefaultCMYK en las páginas que lo pintan, o un perfil de salida CMYK, antes de que PDFium Component lo acepte como PDF/A-2b