기술 문서

델파이 HotPDF로 NChannel 스팟 잉크 일관성 지키기

인쇄소에서 작업을 되돌려 보냅니다. 같은 스팟 잉크가 두 개의 판으로 분판된 것입니다. HotPDF는 NChannel을 ISO 32000-2 다섯 요소 DeviceN 형식으로 기록하고, 문서 전체에서 스팟 이름마다 하나의 정준 대체 색 공간과 틴트 변환을 유지하며, 충돌하는 두 번째 정의는 출력하지 않고 거부해서 저작 시점에 이 문제를 막습니다

NChannel은 색 공간 계열 이름이 아니다

먼저 버려야 할 것은 이름 자체입니다. NChannel은 SeparationDeviceN이 계열인 것처럼 색 공간 계열이 아닙니다. ISO 32000-2 §8.6.6.5는 이것을 DeviceN의 서브타입으로 기술하므로, 규격에 맞는 NChannel 공간은 다섯 요소 배열 [/DeviceN names alternateSpace tintTransform attributes]로 기록되고 속성 사전이 /Subtype /NChannel을 싣습니다. 명세에는 [/NChannel ...] 배열이 존재하지 않습니다. 손으로 직접 만들어 놓고 RIP가 그것을 무시하는 걸 본 적이 있다면, 이유는 바로 이것입니다

HotPDF는 NChannel 공간을 속성 사전이 Process, Colorants, MixingHints 항목과 함께 Subtype NChannel을 실는 다섯 요소 DeviceN 배열로 기록하는데, 명세 어디에도 NChannel 계열 이름 배열이 존재하지 않기 때문이다
규격에 맞는 NChannel 공간은 속성 사전을 가진 DeviceN 배열입니다. 그래서 기록기는 이 형식만 내보내고, 읽기 측은 여전히 구형 토큰을 받아들입니다

HotPDF도 한 번은 이것을 잘못 만들었다가 고쳤고, 오늘날 컴포넌트가 동작하는 방식을 결정하므로 이 사실을 분명히 말해 둘 가치가 있습니다. 예전 HotPDF 릴리스는 계열 이름 형식을 내보냈습니다. 이제 THotPDF.RegisterNChannelColorSpace는 표준 DeviceN 플러스 속성 형식만 내보내며, NChannel 서브타입이 PDF 1.6에서 도입되었기 때문에 생산 쪽 진입점은 RequirePDFVersion(pdf16, ...)으로 게이트를 걸어 더 오래된 대상에서는 그냥 거절합니다. 렌더 쪽은 기록기보다 일부러 관대합니다. HPDFResolveColorSpace는 구형 /NChannel 토큰을 DeviceN 계열로 여전히 받아들이므로 옛 기록기가 만든 파일도 계속 렌더링되지만, HotPDF가 다시 기록해 내는 것은 전부 표준 인코딩을 씁니다. 입력은 관대하게, 출력은 엄격하게, 이 비대칭이 맞습니다. 읽는 쪽은 자신이 만들지 않은 파일을 다뤄야 하지만 기록하는 쪽에는 그런 변명이 없기 때문입니다

하나의 스팟 이름이 어째서 두 개의 판에 올라가는가

스팟 colorant 이름은 문서 전체의 판 식별자이지 국소적인 인수가 아니기 때문입니다. 둘 다 Orange라고 이름 붙이지만 서로 다른 대체 색 공간을 넘기는 두 호출, 또는 같은 대체 색 공간에 서로 다른 틴트 변환을 넘기는 두 호출은, 우연히 같은 라벨을 달고 있을 뿐인 서로 다른 두 잉크를 기술합니다. 분판을 만드는 RIP는 그것을 조정할 방법이 없으므로, 유일하게 정직한 행동을 하고 판 두 장을 돌려줍니다. 그래서 HotPDF는 문서별로 colorant 이름마다 정준 시그니처를 유지합니다. RegisterSpotColorantDefinition이 대체 색 공간과 틴트 함수의 형태로 그 시그니처를 구성하고, 모든 RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT와 NChannel 스팟 정의가 그것을 통과합니다. 두 번째 정의가 어긋나면 호출은 조용히 두 번째 변형을 등록하는 대신 예외를 일으키고, 메시지는 실패 양상에 관해 일부러 구체적입니다. 다른 선택지는 삼주 뒤 판 교정쇄에서 그것을 발견하는 것이니까

모든 HotPDF 스팟 등록 호출은 RegisterSpotColorantDefinition으로 모이고, 이것은 문서에서 colorant 이름마다 하나의 정준 대체 색 공간과 틴트 시그니처를 기록하며 같은 이름의 어긋나는 두 번째 정의가 도착하면 예외를 일으킨다
하나의 이름, 하나의 시그니처. 스팟의 충돌하는 두 번째 정의는 판 교정쇄에서 발견되는 게 아니라 저작 시점에 거부됩니다
// 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
    // '이 문서에서 스팟 colorant "Orange"의 대체 색
    //  공간 또는 틴트 정의가 일관되지 않습니다'
    LogPrepressWarning(E.Message);
end;

마스터 이름을 process와 스팟으로 분할하기

완전한 NChannel은 자신의 모든 마스터 colorant 이름을 process 구성 요소나 스팟 colorant 둘 중 하나로 정확히 한 번씩 설명해야 합니다. 고급 RegisterNChannelColorSpace 오버로드는 마스터 ColorantNames, ProcessColorantNames, 대체 색 공간, 전체 틴트 변환, THPDFNChannelSpotColorant 레코드 배열, 선택적 인쇄 순서를 받습니다. 각 스팟 레코드는 자신의 이름, 입력 하나짜리 자체 Separation 틴트 변환, 선택적 솔리디티, 선택적 도트 게인 함수를 싣습니다. 전체 틴트 변환은 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, /ColorSpace가 process 공간이고 /Components가 그 공간 구성 요소 순서로 process 이름을 나열하는 /Process 사전, 스팟마다 실제 [/Separation name alternate tintfn] 배열을 담은 /Colorants 사전, 그리고 공급했다면 /Solidities, /PrintingOrder, /DotGain을 실은 /MixingHints 사전. 반환된 리소스 이름은 Separation과 DeviceN 스팟 색 렌더링 글에서 다룬 더 단순한 색 공간들과 똑같이 SetFillColorSpaceSetStrokeColorSpace로 갑니다

HotPDF는 NChannel 공간의 마스터 colorant 이름을 네 CMYK 구성 요소용 Process 사전과 스팟마다 하나의 Separation 배열을 담은 Colorants 사전으로 분할하고, 기록 전에 모든 arity를 검사한다
각 마스터 이름은 정확히 한 번씩 소유되며, 전체 틴트 변환, 스팟별 틴트, 인쇄 순서는 모두 어떤 객체도 내보내기 전에 검증됩니다

일관성 검사는 실제로 무엇을 거부하는가

이 검사는 공간 내부의 구조적 불일치를 거부하며, 객체 하나 쓰기 전에 그렇게 합니다. colorant 이름은 유일해야 하고 비어 있거나 All, None이어서는 안 됩니다. process 정의와 스팟 정의를 합쳐 마스터 이름을 정확히 덮어야 하고, 어느 colorant도 두 역할에 동시에 나타나지 않으며 정의되지 않고 남는 것도 없어야 합니다. 대체 색 공간이 DeviceCMYK일 때 process 구성 요소는 반드시 그 순서대로 Cyan, Magenta, Yellow, Black이어야 하고, process 이름 수는 대체 구성 요소 수와 일치해야 합니다. 모든 틴트 변환과 도트 게인 함수는 올바른 입력·출력 arity를 가진 간접 함수 객체여야 합니다. 솔리디티는 0..1 안의 유한한 값이어야 합니다. 인쇄 순서는 비어 있거나 마스터 이름의 완전한 순열이어야 하고, 부분 목록이어선 안 됩니다. 이 검사가 하지 않는 것은 색을 판정하는 일입니다. 여기서는 Orange 틴트 변환이 실제로 통에 든 잉크와 닮았는지, 그 CMYK 조합이 말이 되는 프록시인지, 공급한 솔리디티가 기재에서 측정된 동작과 맞는지 아무것도 검사하지 않습니다. 그것들은 인쇄와 측정의 문제이고, 컴포넌트가 답할 자격이 없습니다. 더 단순한 process 전용 오버로드는 설계상 더 엄격합니다. process만 있는 NChannel을 만들고 스팟 이름을 받아들이기를 일부러 거부하는데, 일치하는 /Colorants 항목 없이 이름 배열에 스팟을 쓰면 구조적으로 규격에 맞지 않는 파일이 되고, 기본 정의를 꾸며내는 것은 실패보다 나쁘기 때문입니다

PDF/X-6n 출력 인텐트는 등록된 모든 스팟을 덮어야 한다

N-colorant PDF/X-6n 파일은 자신의 colorant를 두 번 선언하고, 두 선언은 일치해야 합니다. AddPDFX6ExternalOutputIntentColorantTable과 함께 외부 ICC 프로파일 참조를 기록하는데, 그 전에 ValidateRegisteredSpotOutputColorants가 문서가 등록한 모든 스팟을 훑고 테이블에 하나라도 빠져 있으면 예외를 일으킵니다. 검사는 양방향으로 돕니다. 출력 인텐트가 colorant 목록을 공개한 뒤에는, 그 목록 밖 이름에 대한 이후 스팟 등록 역시 거부됩니다. AddPDFX6ExternalOutputIntentSpotData는 그 위에 잉크별 메타데이터를 더하며 자체 규칙을 집행하는데, 특히 colorant는 솔리디티 값이나 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],                  // 한 colorant의 솔리디티
    Order,
    ['Orange'], [Spectral]);           // 다른 하나의 분광 데이터
finally
  Spectral.Free;
end;

놓치기 쉬운 운영상 세부가 두 가지 있습니다. 이 모든 것을 받쳐 주는 레지스트리는 문서별로 존재하고 문서 경계에서 비워지므로, 같은 THotPDF 인스턴스에 새 파일을 불러들여도 이전 문서의 스팟 식별을 상속하지 않습니다. 그 격리가 핵심입니다. 새어 나간 시그니처는 다음 작업에서 완전히 정당한 일을 거부하게 만들 테니까. 그리고 프로파일 쪽에도 자기만의 하드 리밋이 있습니다. 외부 출력 인텐트는 PDF 2.0 대상, 절대 HTTP 또는 HTTPS 프로파일 URL, 4바이트 ICC 색 공간 시그니처를 요구하고, PDF/X-6n은 2에서 15 사이의 colorant에 일치하는 2CLR부터 FCLR까지 시그니처를 요구합니다

검사가 멈추는 지점

CxF/X-4 처리는 정직해야 할 부분입니다. HotPDF는 분광 스트림에 경계가 있는 구조적 안전 검사를 적용하고 그 안의 잉크 식별이 이름 붙인 colorant와 맞는지 확인합니다. 페이로드 크기에 상한을 두고, 묻힌 널 바이트를 거부하고, DOCTYPE이나 ENTITY 선언을 담은 스트림은 어떤 것이든 거부하며, 알아볼 수 있는 CxF 루트를 요구하고, colorant 이름과 같은 SpotInkName을 정확히 하나 실은 SpotInkCharacterisation 요소를 정확히 하나 요구합니다. 그것은 schema validator가 아니라 기형적이고 적대적인 입력에 대한 관문입니다. 완전한 ISO 17972-4 구현이 아니고, 분광 측정값을 검증하지 않으며, 임의로 미리 존재하는 서드파티 객체 그래프나 내장된 ICC 프로파일 안의 내부 colorant 테이블을 역감사하지도 않습니다. 워크플로가 완전한 CxF 컨포먼스에 의존한다면, 컴포넌트에 넘기기 전에 전용 도구로 파일을 검증하십시오

만날 줄 몰랐던 사람을 물어 뜯는 인접 제약이 하나 있습니다. 휘도 소프트 마스크는 투명도 그룹 /CSSeparation, DeviceN, NChannel을 쓸 수 없습니다. 그룹 블렌딩 공간은 장치 의존 공간이나 CIE 기반 공간이어야 하므로, 그룹 안의 스팟 페인트는 휘도가 계산되기 전에 틴트 변환을 거쳐 대체 색 공간으로 환원됩니다. RegisterLuminositySoftMaskState는 바로 이것이 우연히 틀어질 수 없도록 DeviceGray 그룹을 만듭니다. 실무적 귀결은, 이렇게 마스킹된 스팟 위주 디자인이 잉크가 아니라 CMYK 프록시를 통해 평가된다는 것이고, 오버프린트 교정쇄와 렌더 장치 노트에서 기술한 대로 분판 교정쇄와 비교할 때 그것이 문제가 됩니다

이 가운데 어느 것도 판 교정쇄의 필요를 없애지 않지만, 인쇄소로 가던 한 부류의 인쇄 전 거부를 빌드 단계로 되돌려 옵니다. 여기서 기술한 NChannel, Separation, PDF/X-6n 출력 인텐트 API는 델파이와 C++Builder용 표준 HotPDF Delphi Component에 실려 나가며, 레퍼런스는 전체 레코드 레이아웃과 각 호출이 fail close 되는 정확한 조건을 문서화합니다