Artículo técnico

Coherencia de tintas directas NChannel en Delphi con HotPDF

La imprenta te devuelve el trabajo: la misma tinta directa separada en dos planchas. HotPDF lo evita en tiempo de autoría escribiendo NChannel como la forma DeviceN de cinco elementos de ISO 32000-2 y manteniendo un espacio alternativo y una transformación de tinta canónicos por nombre de tinta directa en todo el documento, rechazando la segunda definición en conflicto en lugar de emitirla

NChannel no es el nombre de una familia de espacios de color

Lo primero que hay que desaprender es el propio nombre. NChannel no es una familia de espacios de color al modo en que lo son Separation y DeviceN. ISO 32000-2 §8.6.6.5 lo describe como un subtipo de DeviceN, de modo que un espacio NChannel conforme se escribe como el array de cinco elementos [/DeviceN names alternateSpace tintTransform attributes], y el diccionario de atributos lleva /Subtype /NChannel. No existe ningún array [/NChannel ...] en la especificación. Si alguna vez has construido uno a mano y has visto cómo el RIP lo ignora, esta es la razón

HotPDF escribe un espacio NChannel como el array DeviceN de cinco elementos cuyo diccionario de atributos lleva Subtype NChannel junto con las entradas Process, Colorants y MixingHints, porque no existe ningún array con nombre de familia NChannel en la especificación
Un espacio NChannel conforme es un array DeviceN con un diccionario de atributos, por eso el escritor emite solo esta forma mientras el lector sigue aceptando el token heredado

HotPDF se equivocó una vez con esto y luego lo corrigió, y conviene decirlo sin rodeos porque condiciona cómo se comporta el componente hoy. Las versiones antiguas de HotPDF emitían la forma con nombre de familia. THotPDF.RegisterNChannelColorSpace ahora emite solo la forma estándar de DeviceN más atributos, y como el subtipo NChannel llegó con PDF 1.6, el punto de entrada de producción se protege con RequirePDFVersion(pdf16, ...) y simplemente declina en destinos más antiguos. El lado de renderizado es deliberadamente más indulgente que el escritor: HPDFResolveColorSpace sigue aceptando el token heredado /NChannel como familia DeviceN para que los archivos del escritor antiguo sigan renderizándose, pero todo lo que HotPDF escribe de nuevo usa la codificación estándar. Indulgente a la entrada, estricto a la salida es la asimetría correcta aquí, porque tu lector tiene que lidiar con archivos que no creó, mientras que tu escritor no tiene esa excusa

¿Por qué un nombre de tinta directa acaba en dos planchas?

Porque el nombre de un colorante directo es una identidad de plancha para todo el documento, no un argumento local. Dos llamadas que ambas nombran Orange pero entregan un espacio alternativo distinto, o el mismo espacio alternativo con una transformación de tinta diferente, describen dos tintas distintas que comparten etiqueta por casualidad. Un RIP que construye separaciones no tiene forma de reconciliar eso, así que hace lo único honesto y te da dos planchas. Por eso HotPDF mantiene una firma canónica por nombre de colorante a nivel de documento. RegisterSpotColorantDefinition compone esa firma a partir del espacio de color alternativo y la forma de la función de tinta, y cada RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT y definición de spot NChannel pasa por ella. Cuando una segunda definición discrepa, la llamada lanza una excepción en lugar de registrar silenciosamente una segunda variante, y el mensaje es deliberadamente específico sobre el modo de fallo, porque la alternativa es descubrirlo en una prueba de plancha tres semanas después

Cada llamada de registro de spot de HotPDF pasa por RegisterSpotColorantDefinition, que registra un espacio alternativo y una firma de tinta canónicos por nombre de colorante para el documento y lanza una excepción cuando llega una segunda definición discordante del mismo nombre
Un nombre, una firma: la segunda definición en conflicto de un spot se rechaza en tiempo de autoría en lugar de descubrirse en una prueba de plancha
// Orange ya estaba registrado contra DeviceCMYK con la
// transformación de tinta 0 / 0.55 / 1 / 0 a tinta plena.
Conflicting := Pdf.RegisterExponentialFunction(
  Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
  Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
  on E: Exception do
    // 'Spot colourant "Orange" has inconsistent alternate colour
    //  space or tint definition in this document'
    LogPrepressWarning(E.Message);
end;

Separar los nombres maestros entre proceso y spot

Un NChannel completo tiene que dar cuenta de cada uno de sus nombres de colorante maestros exactamente una vez, ya sea como componente de proceso o como colorante directo. La sobrecarga avanzada de RegisterNChannelColorSpace toma los ColorantNames maestros, los ProcessColorantNames, el espacio alternativo, la transformación de tinta global, un array de registros THPDFNChannelSpotColorant y un orden de impresión opcional. Cada registro de spot lleva su propio nombre, su propia transformación de tinta Separation de una entrada, una solidez opcional y una función de ganancia de punto opcional. La transformación de tinta global debe mapear N entradas al número de componentes del espacio alternativo; cada tinta de spot debe mapear una entrada a ese mismo número

const
  Colorants: array[0..4] of AnsiString =
    ('Cyan', 'Magenta', 'Yellow', 'Black', 'Orange');
  ProcessNames: array[0..3] of AnsiString =
    ('Cyan', 'Magenta', 'Yellow', 'Black');
  Order: array[0..4] of AnsiString =
    ('Yellow', 'Magenta', 'Cyan', 'Orange', 'Black');
  Domain5: array[0..9] of Single = (0, 1, 0, 1, 0, 1, 0, 1, 0, 1);
  Range4: array[0..7] of Single = (0, 1, 0, 1, 0, 1, 0, 1);
  Domain1: array[0..1] of Single = (0, 1);
  NoInk: array[0..3] of Single = (0, 0, 0, 0);
  OrangeCMYK: array[0..3] of Single = (0, 0.55, 1, 0);
  GainC0: array[0..0] of Single = (0);
  GainC1: array[0..0] of Single = (1);
var
  Pdf: THotPDF;
  Spots: array[0..0] of THPDFNChannelSpotColorant;
  CSName: AnsiString;
begin
  Pdf.Version := pdf20;
  Pdf.BeginDoc;
  Spots[0].Name := 'Orange';
  Spots[0].TintTransform := Pdf.RegisterExponentialFunction(
    Domain1, NoInk, OrangeCMYK, 1, []);
  Spots[0].HasSolidity := True;
  Spots[0].Solidity := 0.82;
  Spots[0].DotGainFunction := Pdf.RegisterExponentialFunction(
    Domain1, GainC0, GainC1, 1, []);
  CSName := Pdf.RegisterNChannelColorSpace(Colorants, ProcessNames,
    'DeviceCMYK',
    Pdf.RegisterPostScriptFunction(Domain5, Range4,
      '{ pop pop pop pop pop 0 0 0 0 }'),
    Spots, Order);

De esa llamada HotPDF emite el diccionario de atributos que pide la especificación: /Subtype /NChannel, un diccionario /Process cuyo /ColorSpace es el espacio de proceso y cuyo /Components lista los nombres de proceso en el orden de componentes de ese espacio, un diccionario /Colorants que contiene un array real [/Separation name alternate tintfn] por cada spot, y un diccionario /MixingHints que lleva /Solidities, /PrintingOrder y /DotGain cuando los has proporcionado. El nombre de recurso devuelto va a SetFillColorSpace o SetStrokeColorSpace igual que los espacios más simples tratados en el artículo sobre renderizado de colores spot Separation y DeviceN

HotPDF divide los nombres de colorante maestros de un espacio NChannel en un diccionario Process para los cuatro componentes CMYK y un diccionario Colorants que contiene un array Separation por spot, con cada aridad comprobada antes de escribir
Cada nombre maestro se reclama exactamente una vez, y la transformación de tinta global, las tintas por spot y el orden de impresión se validan antes de emitir cualquier objeto

¿Qué rechaza realmente la comprobación de coherencia?

Rechaza la incoherencia estructural dentro del espacio, y lo hace antes de escribir un solo objeto. Los nombres de colorante deben ser únicos y no pueden estar vacíos ni ser All o None. Las definiciones de proceso y de spot juntas deben cubrir exactamente los nombres maestros, sin que un colorante aparezca en ambos roles ni que ninguno quede indefinido. Cuando el espacio alternativo es DeviceCMYK, los componentes de proceso deben ser Cyan, Magenta, Yellow, Black en ese orden, y el número de nombres de proceso debe coincidir con el número de componentes del alternativo. Cada transformación de tinta y función de ganancia de punto debe ser un objeto función indirecto con la aridad de entrada y salida correcta. La solidez debe ser un valor finito dentro de 0..1. El orden de impresión debe estar vacío o ser una permutación completa de los nombres maestros, nunca una lista parcial. Lo que no hace es juzgar el color: nada de esto comprueba que tu transformación de tinta de Orange se parezca realmente a la tinta del bote, que su construcción CMYK sea un proxy razonable o que la solidez que has proporcionado coincida con el comportamiento medido sobre el sustrato. Esas son preguntas de prensa y de medición, y el componente no está en posición de responderlas. La sobrecarga más simple, solo proceso, es aún más estricta por diseño: produce un NChannel de solo proceso y se niega deliberadamente a aceptar nombres de spot, porque escribir un spot en el array de nombres sin una entrada /Colorants correspondiente produciría un archivo estructuralmente no conforme, y fabricar una definición por defecto sería peor que fallar

Los output intents PDF/X-6n deben cubrir cada spot registrado

Un archivo PDF/X-6n de N colorantes declara sus colorantes dos veces, y ambas declaraciones deben coincidir. AddPDFX6ExternalOutputIntent escribe la referencia al perfil ICC externo con su ColorantTable, y antes de hacerlo, ValidateRegisteredSpotOutputColorants recorre cada spot que el documento haya registrado y lanza una excepción si alguno falta en la tabla. La comprobación funciona en ambas direcciones: una vez que un output intent ha publicado su lista de colorantes, un registro posterior de spot con un nombre fuera de esa lista también se rechaza. AddPDFX6ExternalOutputIntentSpotData añade encima los metadatos por tinta, y aplica sus propias reglas, notablemente que un colorante lleve o un valor de solidez o datos espectrales CxF/X-4, nunca ambos. Esta es la misma superficie de conformidad tratada en el artículo sobre validación PDF/A, PDF/X y PDF/UA

Spectral := TMemoryStream.Create;
try
  LoadCxFForInk('Orange', Spectral);   // carga útil ISO 17972-4
  Pdf.AddPDFX6ExternalOutputIntentSpotData(
    'ECG-5', 'Five-colour output condition',
    'https://profiles.example.com/ecg-5.icc', '5CLR',
    Colorants,
    '00112233445566778899AABBCCDDEEFF', #4#3#0#0,
    ['Cyan'], [0.70],                  // solidez para un colorante
    Order,
    ['Orange'], [Spectral]);           // datos espectrales para otro
finally
  Spectral.Free;
end;

Dos detalles operativos pasan fácilmente desapercibidos. El registro que sustenta todo esto es por documento y se limpia en los límites de documento, así que cargar un archivo nuevo en la misma instancia de THotPDF no hereda las identidades de spot del documento anterior; ese aislamiento es la gracia, porque una firma filtrada rechazaría trabajo perfectamente válido en el trabajo siguiente. Y el lado del perfil tiene límites duros propios: un output intent externo exige un destino PDF 2.0, una URL de perfil absoluta HTTP o HTTPS, una firma de espacio de color ICC de cuatro bytes y, para PDF/X-6n, entre 2 y 15 colorantes con una firma 2CLR a FCLR coincidente

Hasta dónde llega la comprobación

El manejo de CxF/X-4 es la parte de la que hay que ser honesto. HotPDF aplica comprobaciones estructurales de seguridad acotadas al flujo espectral y confirma que la identidad de tinta que contiene coincide con el colorante que has nombrado. Limita el tamaño de la carga útil, rechaza bytes nulos incrustados, se niega a aceptar cualquier flujo que contenga una declaración DOCTYPE o ENTITY, exige una raíz CxF reconocible y exige exactamente un elemento SpotInkCharacterisation que lleve exactamente un SpotInkName igual al nombre de tu colorante. Es una barrera contra entrada malformada y hostil, no un validador de esquemas. No es una implementación completa de ISO 17972-4, no verifica tus mediciones espectrales y no audita en sentido inverso grafos de objetos de terceros preexistentes arbitrarios ni las tablas de colorantes internas dentro de un perfil ICC incrustado. Si tu flujo de trabajo depende de conformidad CxF completa, valida el archivo con una herramienta dedicada antes de entregarlo al componente

Una restricción adyacente pica a quien nunca esperaba encontrársela. Una máscara suave de luminosidad no puede usar Separation, DeviceN ni NChannel como /CS de su grupo de transparencia; el espacio de fusión del grupo tiene que ser un espacio de dispositivo o basado en CIE, así que la pintura spot dentro del grupo se resuelve a través de su transformación de tinta hacia el espacio alternativo antes de calcular la luminosidad. RegisterLuminositySoftMaskState construye un grupo DeviceGray precisamente para que esto no pueda salir mal por accidente. La consecuencia práctica es que un diseño cargado de spots enmascarado así se evalúa a través de su proxy CMYK, no de su tinta, lo que importa cuando lo comparas con una prueba separada como describen las notas sobre pruebas de overprint y dispositivos de render

Nada de esto elimina la necesidad de una prueba de plancha, pero mueve toda una clase de rechazos de preimpresión de la sala de prensa de vuelta a la compilación. Las APIs NChannel, Separation y de output intent PDF/X-6n descritas aquí se entregan en el HotPDF Delphi Component estándar para Delphi y C++Builder, donde la referencia documenta las estructuras de registro completas y las condiciones exactas bajo las que cada llamada falla de forma cerrada