Техническа статия

Последователност на спот мастила NChannel в Delphi с HotPDF

Печатна подготовка връща работата: едно и също спот мастило се разпределя на две плаки. HotPDF предотвратява това още при създаването, като записва NChannel в петелементната DeviceN форма по ISO 32000-2 и пази едно канонично алтернативно пространство и tint transform за всяко спот име в целия документ, отказвайки второто, конфликтно определение вместо да го изведе

NChannel не е фамилно име на цветно пространство

Първото, което трябва да забравите, е самото име. NChannel не е фамилия цветни пространства, както Separation и DeviceN са фамилии. ISO 32000-2 §8.6.6.5 го описва като subtype на DeviceN, така че съответстващо NChannel пространство се записва като петелементния масив [/DeviceN names alternateSpace tintTransform attributes], а речникът с атрибути носи /Subtype /NChannel. В спецификацията няма масив [/NChannel ...]. Ако някога сте сглобили такъв на ръка и сте гледали как RIP свива рамене, това е причината

HotPDF записва NChannel пространство като петелементния DeviceN масив, чийто речник с атрибути носи Subtype NChannel заедно със записите Process, Colorants и MixingHints, защото в спецификацията изобщо няма масив с фамилно име NChannel
Съответстващо NChannel пространство е DeviceN масив с речник на атрибутите, затова writer-ът извежда само тази форма, докато reader-ът все още приема стария токен

HotPDF веднъж го обърка и после го оправи, което си струва да се каже направо, защото оформя поведението на компонента днес. По-старите версии на HotPDF извеждаха формата с фамилното име. THotPDF.RegisterNChannelColorSpace вече извежда само стандартната DeviceN-плюс-атрибути форма, а понеже subtype-ът NChannel пристигна в PDF 1.6, producer entry point ключи на RequirePDFVersion(pdf16, ...) и просто се отказва на по-стари цели. Страната на рендериране е умишлено по-снизходителна от writer-а: HPDFResolveColorSpace все още приема стария токен /NChannel като DeviceN фамилия, така че файловете от стария writer продължават да се рендерират, но всичко, което HotPDF записва обратно, използва стандартното кодиране. Снизходително на входа, строго на изхода е правилната асиметрия тук, защото вашият reader трябва да се справя с файлове, които не е създал, а вашият writer няма такъв извинителен аргумент

Защо едно спот име стига до две плаки?

Защото името на спот багрилото е идентичност на плака за целия документ, а не локален аргумент. Два извика, които и двата назовават Orange, но подават различно алтернативно пространство, или същото алтернативно пространство с различен tint transform, описват две различни мастила, които случайно споделят етикет. RIP, изграждащ сепарации, няма как да съгласува това, така че прави единственото честно нещо и ви дава две плаки. Затова HotPDF поддържа каноничен подпис на багрило за цял документ. RegisterSpotColorantDefinition съставя този подпис от алтернативното цветно пространство и формата на tint функцията, а всеки RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT и NChannel спот определение минава през него. Когато второ определение не съвпада, извикването вдига изключение вместо тихо да регистрира втори вариант, а съобщението е умишлено конкретно за режима на провал, защото алтернативата е да го откриете на плаката за проба три седмици по-късно

Всяко извикване за спот регистрация в HotPDF се стича през RegisterSpotColorantDefinition, който записва едно канонично алтернативно пространство и tint подпис за всяко име на багрило в документа и вдига изключение, когато пристигне второ, несъвпадащо определение на същото име
Едно име, един подпис: второто, конфликтно определение на спот се отхвърля при създаването, вместо да се открие на плаката за проба
// Orange вече е регистрирано спрямо DeviceCMYK с
// tint transform 0 / 0.55 / 1 / 0 при пълен tint.
Conflicting := Pdf.RegisterExponentialFunction(
  Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
  Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
  on E: Exception do
    // 'Спот багрилото "Orange" има несъвместимо алтернативно цветно
    //  пространство или tint определение в този документ'
    LogPrepressWarning(E.Message);
end;

Разделяне на главните имена на process и spot

Пълен NChannel трябва да отчете всяко от главните си имена на багрила точно веднъж — като process компонент или като спот багрило. Разширеният overload на RegisterNChannelColorSpace взима главните ColorantNames, ProcessColorantNames, алтернативното пространство, общия tint transform, масив от записи THPDFNChannelSpotColorant и опционален печатен ред. Всеки спот запис носи собствено име, собствен Separation tint transform с един вход, опционална solidity и опционална dot-gain функция. Общият tint transform трябва да съпостави N входа към броя компоненти на алтернативното пространство; всеки спот tint трябва да съпостави един вход към същия брой

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

От това извикване HotPDF извежда речника с атрибути, който спецификацията иска: /Subtype /NChannel, речник /Process, чийто /ColorSpace е process пространството и чийто /Components изброява process имената в реда на компонентите на това пространство, речник /Colorants, съдържащ истински масив [/Separation name alternate tintfn] за всяко спот, и речник /MixingHints, носещ /Solidities, /PrintingOrder и /DotGain, когато сте ги подали. Върнатото име на ресурс отива на SetFillColorSpace или SetStrokeColorSpace точно като по-простите пространства, разгледани в статията за рендериране на Separation и DeviceN спот цветове

HotPDF разделя главните имена на багрилата на NChannel пространство в речник Process за четирите CMYK компонента и речник Colorants, съдържащ по един Separation масив за всяко спот, като всяка арност е проверена преди записа
Всяко главно име е заявено точно веднъж, а общият tint transform, тинтовете за всяко спот и печатният ред са валидирани, преди да бъде изведен какъвто и да е обект

Какво реално отхвърля проверката за последователност?

Тя отхвърля структурна несъобразеност вътре в пространството и го прави, преди да бъде записан дори един обект. Имената на багрилата трябва да са уникални и не бива да са празни, All или None. Process и спот определението заедно трябва да покриват главните имена точно, без нито едно багрило да фигурира и в двете роли и без нито едно да остане неопределено. Когато алтернативното пространство е DeviceCMYK, process компонентите трябва да са Cyan, Magenta, Yellow, Black в този ред, а броят process имена трябва да съвпада с броя компоненти на алтернативното. Всеки tint transform и dot-gain функция трябва да са индиректен функциен обект с правилна входно-изходна арност. Solidity трябва да е крайна стойност в 0..1. Печатният ред трябва да е празен или пълна пермутация на главните имена, никога частичен списък. Това, което не прави, е да отсъжда цвят: нищо тук не проверява дали вашият tint transform за Orange реално прилича на мастилото в кенета, дали CMYK му изграждане е разумен прокси или дали подадената solidity съвпада с измерването на субстрата. Това са въпроси на печата и измерването, а компонентът няма мандат да отговаря на тях. По-простият overload само за process е по дизайн още по-строг: той произвежда NChannel само за process и умишлено отказва да приеме спот имена, защото записът на спот в масива с имена без съответстващ запис /Colorants би родил структурно несъответстващ файл, а измислицата на определение по подразбиране би била по-лоша от провала

Output intent за PDF/X-6n трябва да покрива всяко регистрирано спот

Файл N-багрила за PDF/X-6n декларира багрилата си два пъти и двете декларации трябва да съвпадат. AddPDFX6ExternalOutputIntent записва референцията към външния ICC профил с нейния ColorantTable, а преди това ValidateRegisteredSpotOutputColorants минава всяко спот, което документът е регистрирал, и вдига изключение, ако някое липсва в таблицата. Проверката работи и в двете посоки: щом output intent е публикувал списъка си с багрила, по-късна спот регистрация на име извън него също се отказва. AddPDFX6ExternalOutputIntentSpotData добавя метаданни за всяко мастило отгоре и налага собствени правила, най-вече че багрило носи или стойност solidity, или спектрални данни CxF/X-4, никога и двете. Това е същата съответстваща повърхност, обсъдена в материала за валидация PDF/A, PDF/X и PDF/UA

Spectral := TMemoryStream.Create;
try
  LoadCxFForInk('Orange', Spectral);   // payload по 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],                  // solidity за едно багрило
    Order,
    ['Orange'], [Spectral]);           // спектрални данни за друго
finally
  Spectral.Free;
end;

Две оперативни подробности лесно се пропускат. Регистърът, който стои зад всичко това, е на документ и се изчиства на границите между документи, така че зареждането на нов файл в същия екземпляр THotPDF не наследява спот идентичностите на предишния документ; именно тази изолация е смисълът, защото изтекъл подпис би отхвърлил съвсем валидна работа в следващата задача. А страната на профила има собствени твърди лимити: външен output intent изисква цел PDF 2.0, абсолютен URL на профил по HTTP или HTTPS, четирибайтов ICC подпис за цветно пространство и за PDF/X-6n между 2 и 15 багрила със съответстващ подпис 2CLR до FCLR

Къде проверката спира

Обработката на CxF/X-4 е частта, за която трябва да бъдем честни. HotPDF прилага ограничени структурни проверки за безопасност върху спектралния поток и потвърждава, че идентичността на мастилото вътре съвпада с багрилото, което сте назовали. Ограничава размера на payload, отхвърля вградени null байтове, отказва всеки поток, съдържащ декларация DOCTYPE или ENTITY, иска разпознаваем CxF корен и иска точно един елемент SpotInkCharacterisation, носещ точно един SpotInkName, равен на името на вашето багрило. Това е бариера срещу лошо оформен и враждебен вход, а не валидатор по схема. Не е пълна имплементация на ISO 17972-4, не проверява вашите спектрални измервания и не одитира обратно произволни предварително съществуващи обектни графики на трети страни или вътрешните таблици с багрила във вграден ICC профил. Ако работният ви процес зависи от пълна CxF съобразеност, валидирайте файла със специализиран инструмент, преди да го подадете на компонента

Едно съседно ограничение хапе хора, които никога не са очаквали да го срещнат. Luminosity soft mask не може да използва Separation, DeviceN или NChannel като /CS на своята transparency group; пространството за смесване на групата трябва да е device или CIE-базирано пространство, така че спот боята в групата се разрешава през нейния tint transform в алтернативното пространство, преди luminosity да бъде изчислена. RegisterLuminositySoftMaskState изгражда група DeviceGray именно за да не може това да се обърка случайно. Практическият извод е, че дизайн, тежък на спот, маскиран по този начин, се оценява чрез своя CMYK прокси, а не през мастилото си, което има значение, когато го сравнявате със сепарирана проба, както е описано в бележките за overprint проби и render устройства

Нищо от това не маха нуждата от плака за проба, но премества един цял клас отпечатъчни отхвърляния от печатния цех обратно към build. Описаните тук API за NChannel, Separation и output intent PDF/X-6n идват със стандартния HotPDF Delphi Component за Delphi и C++Builder, където справката документира пълните оформления на записите и точните условия, при които всяко извикване се проваля затворено