Типография возвращает макет: одна и та же плашечная краска развелась на две формы. HotPDF предотвращает это ещё на этапе создания, записывая NChannel в пятиэлементной DeviceN-форме по ISO 32000-2 и удерживая одну каноническую альтернативную палитру и тинт-трансформ на каждое имя плашки в целом документе, отказывая второй, конфликтующей декларации вместо её записи
NChannel — не имя семейства цветовых пространств
Первое, что придётся разучить, — само имя. NChannel — не семейство цветовых пространств, как Separation и DeviceN. ISO 32000-2 §8.6.6.5 описывает его как подтип DeviceN, поэтому корректное пространство NChannel записывается пятиэлементным массивом [/DeviceN names alternateSpace tintTransform attributes], а словарь атрибутов несёт /Subtype /NChannel. Массива [/NChannel ...] в спецификации нет. Если вы когда-то собирали такой вручную и наблюдали, как RIP пожимает плечами, — вот почему
HotPDF ошибся в этом однажды и затем починил, и об этом стоит сказать прямо, потому что это определяет сегодняшнее поведение компонента. Старые выпуски HotPDF выдавали форму с именем семейства. THotPDF.RegisterNChannelColorSpace теперь выдаёт только стандартную форму DeviceN-плюс-атрибуты, а поскольку подтип NChannel появился в PDF 1.6, точка входа производителя стоит на вратах RequirePDFVersion(pdf16, ...) и просто отказывает на более старых целях. Сторона рендеринга намеренно снисходительнее пишущего: HPDFResolveColorSpace всё ещё принимает унаследованный токен /NChannel как семейство DeviceN, чтобы файлы старого писателя продолжали рендериться, но всё, что HotPDF записывает обратно, использует стандартную кодировку. Снисходительность на входе, строгость на выходе — правильная асимметрия здесь, ведь вашему читателю приходится справляться с файлами, которые он не создавал, а у вашего писателя такого оправдания нет
Почему одно имя плашки оказывается на двух формах?
Потому что имя плашечного красителя — это идентичность формы в масштабе документа, а не локальный аргумент. Два вызова, оба именующие Orange, но передающие разные альтернативные палитры, или одну палитру с другим тинт-трансформом, описывают две разные краски, случайно разделившие этикетку. RIP, строящий цветоделения, не может это примирить, поэтому делает единственно честное — даёт вам две формы. Поэтому HotPDF ведёт каноническую подпись на имя красителя в масштабе документа. RegisterSpotColorantDefinition составляет эту подпись из альтернативного цветового пространства и формы тинт-функции, и каждый RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT и плашечная декларация NChannel проходят через неё. Когда вторая декларация расходится, вызов выбрасывает исключение вместо молчаливой регистрации второго варианта, и сообщение намеренно конкретно о режиме сбоя, ведь альтернатива — обнаружить это на цветопробе формы три недели спустя
// Orange уже был зарегистрирован против DeviceCMYK с
// тинт-трансформом 0 / 0.55 / 1 / 0 при полном тинте.
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;
Разделение главных имён на process и spot
Полный NChannel должен учесть каждое из главных имён красителей ровно один раз — либо как компонент process, либо как плашечный краситель. Расширенная перегрузка RegisterNChannelColorSpace принимает главные ColorantNames, ProcessColorantNames, альтернативную палитру, общий тинт-трансформ, массив записей THPDFNChannelSpotColorant и необязательный порядок печати. Каждая плашечная запись несёт собственное имя, собственный одновходовый Separation тинт-трансформ, необязательную плотность (solidity) и необязательную функцию растискивания. Общий тинт-трансформ должен отображать N входов в число компонентов альтернативной палитры; каждый плашечный тинт — один вход в то же число
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
Что на самом деле отклоняет проверка согласованности?
Она отклоняет структурную несогласованность внутри пространства и делает это прежде, чем записан хоть один объект. Имена красителей должны быть уникальны и не могут быть пустыми, All или None. Декларации process и spot вместе должны покрывать главные имена точно, без красителя в обеих ролях и без неопределённых. Когда альтернативная палитра — DeviceCMYK, process-компоненты должны быть Cyan, Magenta, Yellow, Black именно в этом порядке, а число process-имён должно совпадать с числом компонентов альтернативы. Каждый тинт-трансформ и функция растискивания должны быть косвенным функциональным объектом с правильной арностью входов и выходов. Solidity должна быть конечным значением внутри 0..1. Порядок печати должен быть пустым или полной перестановкой главных имён, никогда частичным списком. Чего она не делает — судит цвет: ничто здесь не проверяет, что ваш тинт-трансформ Orange действительно похож на краску в банке, что его CMYK-сборка — вменяемый прокси, или что переданная solidity совпадает с измеренным поведением на материале. Это вопросы печатного станка и измерений, и компонент не имеет полномочий их решать. Более простая перегрузка только для process строже по дизайну: она выдаёт NChannel только с process и намеренно отказывается принимать имена плашек, ведь запись плашки в массив имён без соответствующей записи /Colorants дала бы структурно некорректный файл, а выдумывание декларации по умолчанию хуже, чем честный отказ
Выходные намерения PDF/X-6n должны покрывать каждую зарегистрированную плашку
Файл PDF/X-6n с N красителями декларирует красители дважды, и обе декларации должны совпадать. AddPDFX6ExternalOutputIntent записывает внешнюю ссылку ICC-профиля с его ColorantTable, и прежде чем сделать это, ValidateRegisteredSpotOutputColorants обходит каждую плашку, зарегистрированную документом, и выбрасывает исключение, если какой-то нет в таблице. Проверка идёт в обе стороны: как только выходное намерение опубликовало свой список красителей, более поздняя регистрация плашки с именем вне списка также отклоняется. AddPDFX6ExternalOutputIntentSpotData добавляет поверх метаданные по краскам и навязывает собственные правила, в частности краситель несёт либо значение solidity, либо спектральные данные CxF/X-4, никогда оба. Это та же поверхность соответствия, обсуждаемая в статье о валидации PDF/A, PDF/X и PDF/UA
Spectral := TMemoryStream.Create;
try
LoadCxFForInk('Orange', Spectral); // полезная нагрузка 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 не наследует плашечные идентичности предыдущего документа; изоляция и есть смысл, ведь утёкшая подпись отвергла бы совершенно корректную работу в следующем задании. А у стороны профиля свои жёсткие пределы: внешнее выходное намерение требует цель PDF 2.0, абсолютный URL профиля HTTP или HTTPS, четырёхбайтовую подпись цветового пространства ICC, а для PDF/X-6n — от 2 до 15 красителей с соответствующей подписью от 2CLR до FCLR
Где проверка останавливается
Обработка CxF/X-4 — та часть, о которой стоит быть честным. HotPDF применяет ограниченные структурные проверки безопасности к спектральному потоку и подтверждает, что идентичность краски внутри совпадает с названным красителем. Он ограничивает размер полезной нагрузки, отвергает встроенные нулевые байты, отказывает любому потоку с декларацией DOCTYPE или ENTITY, требует узнаваемый корень CxF и требует ровно один элемент SpotInkCharacterisation, несущий ровно один SpotInkName, равный имени вашего красителя. Это врата против искажённого и враждебного ввода, а не валидатор схемы. Это не полная реализация ISO 17972-4, она не проверяет ваши спектральные измерения и не проводит обратный аудит произвольных заранее существующих сторонних графов объектов или внутренних таблиц красителей внутри встроенного ICC-профиля. Если ваш поток работы зависит от полного соответствия CxF, валидируйте файл выделенным инструментом, прежде чем отдавать его компоненту
Одно соседнее ограничение кусает тех, кто не ожидал с ним встретиться. Маска мягкости по светимости не может использовать Separation, DeviceN или NChannel как /CS группы прозрачности; пространство смешивания группы должно быть device- или CIE-пространством, поэтому плашечная краска внутри группы разрешается через свой тинт-трансформ в альтернативную палитру до вычисления светимости. RegisterLuminositySoftMaskState строит группу DeviceGray именно затем, чтобы это не могло сломаться случайно. Практическое следствие: насыщенный плашками дизайн, замаскированный так, оценивается через свой CMYK-прокси, а не через краску, что важно при сравнении с цветоделённой пробой, как описано в заметках о пробах с overprint и устройствах рендеринга
Ничто из этого не отменяет нужду в цветопробе формы, но перемещает целый класс допечатных отказов из печатного цеха обратно в сборку. Описанные здесь API NChannel, Separation и выходных намерений PDF/X-6n поставляются в стандартном HotPDF Delphi Component для Delphi и C++Builder, где справочник документирует полные раскладки записей и точные условия, при которых каждый вызов завершается явным отказом