บทความเทคนิค

ความสม่ำเสมอหมึก spot แบบ NChannel ใน Delphi ด้วย HotPDF

โรงพิมพ์ prepress ส่งงานกลับมา: หมึก spot สีเดียวกันถูกแยกออกไปติดอยู่บนแผ่น plate สองแผ่น HotPDF ปิดปากรูแบบนี้ตั้งแต่ตอนเขียนไฟล์ ด้วยการเขียน NChannel เป็นรูป DeviceN ห้าองค์ประกอบตาม ISO 32000-2 และคงพื้นที่สี alternate ตัวเดียวกับ tint transform หนึ่งตัวต่อชื่อ spot หนึ่งชื่อตลอดทั้งเอกสาร จากนั้นปฏิเสธนิยามที่สองที่ขัดแย้งกันเสีย แทนที่จะปล่อยมันออกไป

NChannel ไม่ใช่ชื่อตระกูลของพื้นที่สี

สิ่งแรกที่ต้องถอนจากสมองคือชื่อของมันเอง NChannel ไม่ใช่ตระกูลพื้นที่สีอย่างที่ Separation กับ DeviceN เป็นตระกูล ISO 32000-2 §8.6.6.5 อธิบายมันไว้ว่าเป็น subtype ของ DeviceN พื้นที่สี NChannel ที่ถูกต้องจึงถูกเขียนเป็นอาร์เรย์ห้าองค์ประกอบ [/DeviceN names alternateSpace tintTransform attributes] และ dictionary ของ attributes แบก /Subtype /NChannel เอาไว้ ไม่มีอาร์เรย์ [/NChannel ...] ในสเปกอยู่เลย ถ้าคุณเคยประกอบมันขึ้นมาเองแล้วเห็น RIP แค่นเงียบ ๆ นั่นแหละคือเหตุผล

HotPDF เขียนพื้นที่สี NChannel เป็นอาร์เรย์ DeviceN ห้าองค์ประกอบที่ dictionary ของ attributes แบก Subtype NChannel พร้อมรายการ Process, Colorants และ MixingHints เพราะในสเปกไม่มีอาร์เรย์ชื่อตระกูล NChannel อยู่เลย
พื้นที่สี NChannel ที่ถูกต้องคืออาร์เรย์ DeviceN พร้อม dictionary ของ attributes ผู้เขียนไฟล์จึงปล่อยเฉพาะรูปแบบนี้ ขณะที่ฝั่งอ่านยังรับ token รุ่นเก่า

HotPDF เคยเขียนจุดนี้ผิดมาแล้วครั้งหนึ่งแล้วจึงแก้ ซึ่งควรพูดกันตรง ๆ เพราะมันกำหนดพฤติกรรมของคอมโพเนนต์ในวันนี้ HotPDF รุ่นเก่าปล่อยรูปแบบชื่อตระกูลออกมา THotPDF.RegisterNChannelColorSpace ตอนนี้ปล่อยเฉพาะรูปแบบมาตรฐาน DeviceN พร้อม attributes และเพราะ subtype NChannel เข้ามาใน PDF 1.6 จุดเรียกฝั่งผู้ผลิตจึงเกตด้วย RequirePDFVersion(pdf16, ...) และปฏิเสธอย่างสุภาพบนเป้าหมายรุ่นเก่ากว่า ฝั่งเรนเดอร์ตั้งใจใจกว้างกว่าฝั่งเขียน: HPDFResolveColorSpace ยังรับ token /NChannel รุ่นเก่าในฐานะตระกูล DeviceN เพื่อให้ไฟล์จากผู้เขียนรุ่นเก่ายังเรนเดอร์ได้ แต่ทุกอย่างที่ HotPDF เขียนกลับออกไปใช้ encoding มาตรฐาน ยืดหยุ่นตอนรับ เข้มงวดตอนเขียนคือความไม่สมมาตรที่ถูกต้อง เพราะ reader ของคุณต้องรับมือกับไฟล์ที่ตัวเองไม่ได้สร้าง ขณะที่ writer ของคุณไม่มีข้อแก้ตัวแบบนั้น

ทำไมชื่อ spot หนึ่งชื่อจึงไปติดสองแผ่น plate

เพราะชื่อ colorant ของสี spot เป็นตัวตนระดับแผ่น plate ของทั้งเอกสาร ไม่ใช่อาร์กิวเมนต์ระดับท้องถิ่น การเรียกสองครั้งที่ต่างเรียกชื่อ Orange เหมือนกันแต่ยื่นพื้นที่สี alternate ต่างกัน หรือพื้นที่ alternate เดียวกันแต่ tint transform ต่างกัน กำลังอธิบายหมึกสองชนิดที่บังเอิญใช้ฉลากร่วมกัน RIP ที่ประกอบการแยกสีไม่มีทางกระชับความต่างนี้ให้เหลืออันเดียวได้ มันจึงทำสิ่งที่ซื่อสัตย์ที่สุดคือให้คุณไปสองแผ่น HotPDF จึงรักษา signature หลักต่อเอกสารของแต่ละชื่อ colorant RegisterSpotColorantDefinition ประกอบ signature นั้นจากพื้นที่สี alternate และรูปร่างของฟังก์ชัน tint และทุกการเรียก RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT และนิยาม spot ของ NChannel ล้วนวิ่งผ่านมัน เมื่อนิยามที่สองไม่เห็นด้วย การเรียกจะ raise แทนการลงทะเบียนตัวแปรที่สองเงียบ ๆ และข้อความตั้งใจเจาะจงกับโหมดความล้มเหลว เพราะทางเลือกอื่นคือมาเจอกันที่แผ่นพิสูจน์สามสัปดาห์ถัดมา

การลงทะเบียน spot ทุกครั้งของ HotPDF วิ่งผ่าน RegisterSpotColorantDefinition ซึ่งบันทึกพื้นที่สี alternate หลักและ signature ของ tint หนึ่งชุดต่อชื่อ colorant หนึ่งชื่อตลอดเอกสาร และ raise เมื่อนิยามที่สองที่ขัดแย้งของชื่อเดิมมาถึง
หนึ่งชื่อ หนึ่ง signature: นิยามที่สองที่ขัดแย้งของ spot ถูกปฏิเสธตั้งแต่ตอนเขียนไฟล์ แทนที่จะไปพบกันที่แผ่นพิสูจน์
// 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
    // 'Spot colourant "Orange" has inconsistent alternate colour
    //  space or tint definition in this document'
    LogPrepressWarning(E.Message);
end;

แยกชื่อหลักเป็นฝั่ง process และฝั่ง spot

NChannel ที่สมบูรณ์ต้องระบุชื่อ colorant หลักของมันครบทุกชื่อ ครั้งเดียวต่อชื่อ ในฐานะองค์ประกอบ process หรือ colorant spot อย่างใดอย่างหนึ่ง overload ขั้นสูงของ RegisterNChannelColorSpace รับ ColorantNames หลัก, ProcessColorantNames, พื้นที่สี alternate, tint transform รวม, อาร์เรย์ของเรกคอร์ด THPDFNChannelSpotColorant และลำดับการพิมพ์ที่ใส่หรือไม่ใส่ก็ได้ เรกคอร์ด spot แต่ละตัวแบกชื่อของตัวเอง, tint transform แบบ Separation ที่รับอินพุตเดียวของตัวเอง, solidity ที่ใส่หรือไม่ก็ได้ และฟังก์ชัน dot-gain ที่ใส่หรือไม่ก็ได้ tint transform รวมต้องแมปอินพุต N ตัวไปยังจำนวนองค์ประกอบของพื้นที่สี alternate และ tint ของ spot แต่ละตัวต้องแมปอินพุตหนึ่งตัวไปยังจำนวนเดียวกันนั้น

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 ปล่อย dictionary ของ attributes ที่สเปกขอ: /Subtype /NChannel, dictionary /Process ที่ /ColorSpace ของมันคือพื้นที่สี process และ /Components ของมันลิสต์ชื่อ process ตามลำดับองค์ประกอบของพื้นที่นั้น, dictionary /Colorants ที่ถืออาร์เรย์ [/Separation name alternate tintfn] ของจริงหนึ่งชุดต่อ spot และ dictionary /MixingHints ที่แบก /Solidities, /PrintingOrder และ /DotGain ในกรณีที่คุณส่งมาให้ ชื่อ resource ที่คืนกลับไปใช้กับ SetFillColorSpace หรือ SetStrokeColorSpace ได้เหมือนพื้นที่สีแบบง่ายที่อธิบายไว้ในบทความ การเรนเดอร์สี spot แบบ Separation และ DeviceN

HotPDF แยกชื่อ colorant หลักของพื้นที่สี NChannel เป็น dictionary Process สำหรับองค์ประกอบ CMYK สี่ตัวและ dictionary Colorants ที่ถืออาร์เรย์ Separation หนึ่งชุดต่อ spot โดยตรวจความถูกต้องของอาร์เรียทุกจุดก่อนเขียน
ชื่อหลักแต่ละชื่อถูกอ้างสิทธิ์ครั้งเดียวพอดี และ tint transform รวม, tint ราย spot และลำดับการพิมพ์ถูก validate ครบก่อนที่ออบเจกต์ใดจะถูกปล่อยออกไป

การตรวจความสม่ำเสมอปฏิเสธอะไรกันแน่

มันปฏิเสธความไม่สอดคล้องเชิงโครงสร้างภายในพื้นที่สี และทำเช่นนั้นก่อนที่ออบเจกต์จะถูกเขียนออกไปแม้แต่ชิ้นเดียว ชื่อ colorant ต้องไม่ซ้ำและห้ามเป็นสตริงว่าง, All หรือ None นิยาม process กับ spot รวมกันต้องคลุมชื่อหลักพอดี โดยไม่มี colorant ไหนปรากฏทั้งสองบทบาทและไม่มีใครถูกทิ้งไว้ไร้นิยาม เมื่อพื้นที่สี alternate เป็น DeviceCMYK องค์ประกอบ process ต้องเป็น Cyan, Magenta, Yellow, Black ตามลำดับนั้น และจำนวนชื่อ process ต้องตรงกับจำนวนองค์ประกอบ alternate ทุก tint transform และฟังก์ชัน dot-gain ต้องเป็นออบเจกต์ฟังก์ชันแบบ indirect ที่มีอาร์เรียอินพุตกับเอาต์พุตถูกต้อง solidity ต้องเป็นค่าจำกัดภายใน 0..1 ลำดับการพิมพ์ต้องว่างหรือเป็นเพอร์มูเทชันเต็มของชื่อหลัก ห้ามเป็นลิสต์บางส่วน สิ่งที่มันไม่ทำคือตัดสินสี: ไม่มีอะไรที่นี่ตรวจว่า tint transform ของ Orange ของคุณคล้ายหมึกในกระป๋องจริงไหม, สูตร CMYK ของมันเป็นพร็อกซีที่สมเหตุสมผลไหม หรือ solidity ที่คุณส่งมาตรงกับพฤติกรรมที่วัดได้บนพื้นกระดาษไหม นั่นเป็นคำถามระดับเครื่องพิมพ์และการวัด คอมโพเนนต์ไม่มีสิทธิ์ตอบแทน overload แบบ process-only ที่ง่ายกว่าเข้มงวดกว่าอีกขั้นโดยการออกแบบ: มันผลิต NChannel เฉพาะ process และตั้งใจปฏิเสธชื่อ spot เพราะการเขียน spot ลงในอาร์เรย์ชื่อโดยไม่มีรายการ /Colorants ที่จับคู่จะผลิตไฟล์ที่ไม่สอดคล้องเชิงโครงสร้าง และการหลอกประดิษฐ์นิยามเริ่มต้นขึ้นมาย่อมแย่กว่าการยอมล้มเหลว

output intent ของ PDF/X-6n ต้องคลุม spot ที่ลงทะเบียนไว้ทุกตัว

ไฟล์ PDF/X-6n ที่มี N colorant ประกาศ colorant ของมันสองรอบ และการประกาศทั้งสองต้องเห็นตรงกัน AddPDFX6ExternalOutputIntent เขียนการอ้างอิงโปรไฟล์ ICC ภายนอกพร้อม ColorantTable ของมัน และก่อนจะทำเช่นนั้น ValidateRegisteredSpotOutputColorants จะเดินสแกน spot ทุกตัวที่เอกสารลงทะเบียนไว้และ raise ทันทีถ้าตัวหนึ่งหลุดจากตาราง การตรวจวิ่งสองทาง: พอ output intent เผยแพร่รายชื่อ colorant ไปแล้ว การลงทะเบียน spot รายชื่อนอกลิสต์ภายหลังก็ถูกปฏิเสธเช่นกัน AddPDFX6ExternalOutputIntentSpotData เติม metadata รายหมึกซ้อนขึ้นไปอีกชั้น พร้อมบังคับกติกาของตัวเอง โดยเฉพาะข้อที่ว่า colorant หนึ่งตัวแบกได้ทั้งค่า solidity หรือข้อมูล spectral แบบ CxF/X-4 ตัวใดตัวหนึ่งเท่านั้น ห้ามมีทั้งคู่ นี่คือพื้นผิว conformance เดียวกับที่คุยกันไว้ในบทความ การ validate ตาม 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 สำหรับ colorant หนึ่งตัว
    Order,
    ['Orange'], [Spectral]);           // ข้อมูล spectral สำหรับอีกตัวหนึ่ง
finally
  Spectral.Free;
end;

รายละเอียดในการใช้งานสองจุดที่คนมักมองข้าม รีจิสทรีที่รองรับทั้งหมดนี้ผูกกับเอกสารแต่ละฉบับและถูกเคลียร์ที่ขอบเขตของเอกสาร การโหลดไฟล์ใหม่ลงอินสแตนซ์ THotPDF เดิมจึงไม่สืบทอดตัวตน spot ของเอกสารก่อนหน้า ความแยกขาดนี้แหละคือประเด็น เพราะ signature ที่รั่วไหล่องปฏิเสธงานที่สมบูรณ์แหละในงานถัดไป และฝั่งโปรไฟล์ก็มีเพดานอันแน่นอนของตัวเอง: output intent ภายนอกต้องการเป้าหมาย PDF 2.0, URL โปรไฟล์แบบ absolute ที่เป็น HTTP หรือ HTTPS, signature พื้นที่สี ICC สี่ไบต์ และสำหรับ PDF/X-6n ต้องมี colorant ระหว่าง 2 ถึง 15 ตัวพร้อม signature ที่จับคู่ตั้งแต่ 2CLR ถึง FCLR

จุดที่การตรวจสอบหยุดลง

การจัดการ CxF/X-4 คือส่วนที่ควรพูดให้ตรงไปตรงมา HotPDF ใช้การตรวจความปลอดภัยเชิงโครงสร้างแบบมีขอบเขตกับ stream spectral และยืนยันว่าตัวตนหมึกภายในนั้นตรงกับ colorant ที่คุณตั้งชื่อ มันจำกัดขนาด payload, ปฏิเสธไบต์ null ที่ฝังมา, ปฏิเสธ stream ใด ๆ ที่มีการประกาศ DOCTYPE หรือ ENTITY, ต้องการราก CxF ที่จำได้ และต้องการองค์ประกอบ SpotInkCharacterisation ตัวเดียวที่แบก SpotInkName ตัวเดียวซึ่งเท่ากับชื่อ colorant ของคุณ นั่นคือประตูกันอินพุตที่ผิดรูปและอินพุตที่เป็นภัย ไม่ใช่ตัว validate ตามสคีมา มันไม่ใช่การติดตั้ง ISO 17972-4 ฉบับเต็ม ไม่ตรวจสอบค่าการวัด spectral ของคุณ และไม่ย้อนตรวจกราฟออบเจกต์ของบุคคลที่สามที่มีอยู่ก่อนโดยพลการ หรือตาราง colorant ภายในโปรไฟล์ ICC ที่ฝังมาด้วย ถ้าเวิร์กโฟลว์ของคุณพึ่ง conformance แบบเต็มของ CxF จง validate ไฟล์ด้วยเครื่องมือเฉพาะทางก่อนส่งต่อเข้าคอมโพเนนต์

เงื่อนไขที่อยู่ติดกันอีกข้อกัดคนที่ไม่เคยคิดว่าจะเจอ Soft mask แบบ luminosity ใช้ Separation, DeviceN หรือ NChannel เป็น /CS ของ transparency group ของมันไม่ได้ พื้นที่เบลนด์ของ group ต้องเป็นแบบ device หรือ CIE-based สี spot ภายใน group จึงถูก resolve ผ่าน tint transform ลงสู่พื้นที่สี alternate ก่อน luminosity จะถูกคำนวณ RegisterLuminositySoftMaskState สร้าง group แบบ DeviceGray ขึ้นมาเพื่อกันจุดนี้หลุดพลาดโดยบังเอิญโดยเฉพาะ ผลในทางปฏิบัติคือดีไซน์ที่หนักไปทาง spot และถูก mask ด้วยวิธีนี้กำลังถูกประเมินผ่านพร็อกซี CMYK ไม่ใช่ผ่านหมึกจริง ซึ่งมีผลเวลาคุณเทียบกับแผ่นแยกสีตามที่บันทึกไว้ในบันทึกย่อเรื่อง overprint proofing และ render devices

ทั้งหมดนี้ไม่ได้ตัดความจำเป็นของแผ่นพิสูจน์ออกไป แต่มันย้ายกลุ่มความล้มเหลวทั้งก้อนของงาน prepress จากห้องเครื่องพิมพ์กลับมาที่ขั้นตอน build API ของ NChannel, Separation และ output intent ของ PDF/X-6n ที่อธิบายไว้ในบทความนี้มาพร้อม HotPDF Delphi Component ฉบับมาตรฐานสำหรับ Delphi และ C++Builder ที่ซึ่งเอกสารอ้างอิงระบุเลย์เอาต์เรกคอร์ดครบชุดและเงื่อนไขแน่นอนที่การเรียกแต่ละแบบจะ fail closed