โรงพิมพ์งานธุรกรรมส่งงานแถลงยอด 80,000 หน้า กลับมาพร้อมคำปฏิเสธบรรทัดเดียวว่า "not PDF/VT, RIP cannot cache." ไฟล์เปิดได้ในโปรแกรมดูทุกตัวบนโต๊ะ สีถูกต้อง ข้อมูลที่ผสานก็ถูกต้อง แต่ทั้งหมดนั้นไม่ใช่สิ่งที่แท่นพิมพ์ดิจิทัลต้องการ การพิมพ์ข้อมูลแปรผันความเร็วสูงจะอยู่หรือดับที่แท่นพิมพ์ต้องรู้ให้ได้ว่าบล็อกโลโก้ลูกค้าบนหน้า 1 เป็นออบเจ็กต์เดียวกันแบบไบต์ต่อไบต์กับบนหน้า 40,000 จึงเรนเดอร์เพียงครั้งเดียวแล้วนำกลับมาใช้ซ้ำ PDF/VT คือมาตรฐานที่ทำให้คำสัญญานั้นตรวจสอบได้ด้วยเครื่อง และคำว่า "ดูถูกต้อง" คือกับดักพอดี เพราะโครงสร้างที่ RIP อ่านนั้นมองไม่เห็นบนหน้าจอ
PDFiumPas เปิดเผยโครงสร้างนี้ผ่านเมธอดขนาดเล็กบน TPdf คือ SaveAsPdfVT สำหรับเขียน และ ValidatePdfVT สำหรับตรวจสอบ บทความนี้จะอธิบายว่าเมธอดทั้งสองเขียนลงดิสก์และตรวจอะไรจริง ๆ จุดใดที่ ISO 16612-2 เข้มกว่าที่ดูเหมือนในตอนแรก และส่วนไหนเป็นเพียงสมอเชิงโครงสร้างที่เชื่อถือได้ ไม่ใช่ preflight เต็มรูปแบบที่เอาไปคิดค่ากับลูกค้าได้
สิ่งที่ PDF/VT ทำให้เป็นมาตรฐาน และเหตุผลที่ PDF/X ต้องมาก่อน
PDF/VT (ISO 16612-2:2010) ไม่ใช่ฟอร์แมตไฟล์ใหม่ แต่มันคือชั้นของเมตะดาต้าสำหรับการเพิ่มประสิทธิภาพที่วางทับบนไฟล์ PDF/X และลำดับนี้คือแกนสำคัญ มาตรฐานกำหนดระดับความสอดคล้องไว้สามระดับ แต่มีเพียงสองระดับที่ระบุเป็นไฟล์ PDF คือ PDF/VT-1 ซึ่งเป็นเอกสารเดี่ยวในตัวเอง และ PDF/VT-2 ซึ่งเป็นโมเดลแบบชุดไฟล์ที่หน้าต่าง ๆ อ้างอิงทรัพยากรภายนอกร่วมกัน โทเคนที่สามที่คุณอาจเห็นอย่าง PDF/VT-2s ไม่ได้เป็นค่าระดับไฟล์เลย แต่มันอยู่ในส่วนหัวของสตรีม MIME ที่อธิบายไว้ใน Annex A หากคุณเจอโค้ดที่เขียน GTS_PDFVTVersion = "PDF/VT-2s" ลงใน XMP ของเอกสาร โค้ดนั้นผิด
กฎที่ต่อรองไม่ได้สำหรับไฟล์เดี่ยวคือฐาน PDF/X ISO 16612-2 §6.2.1 กำหนดว่าไฟล์ PDF/VT-1 ทุกไฟล์ต้องเป็น PDF/X-4 ที่ถูกต้องด้วย ส่วนชุดไฟล์ของ PDF/VT-2 ตาม §6.2.2 ต้องวางอยู่บน PDF/X-4p, PDF/X-5g หรือ PDF/X-5pg ด้วยเหตุนี้ผู้เขียน PDF/VT จึงไม่สามารถต่อท้ายแค่คีย์ตัวระบุสองสามตัวได้ แต่ต้องพกชุดตัวบ่งชี้ PDF/X-4 ทั้งชุดมาด้วย ซึ่งหมายถึงต้องมี OutputIntent, โปรไฟล์ ICC ปลายทางที่ฝังอยู่, รายการ XMP และข้อมูล Info ของเอกสารที่สอดคล้องกัน, trailer /ID, และไม่มีการเข้ารหัส หากขาดอย่างใดอย่างหนึ่ง คุณก็จะได้ไฟล์ที่อ้างว่าเป็น PDF/VT แต่พังทันทีเมื่อผู้บริโภคที่เป็นไปตามมาตรฐานตรวจฐาน PDF/X PDFiumPas มองชั้น PDF/X-4 เป็นส่วนหนึ่งของการบันทึก PDF/VT ดังนั้นจึงไม่ต้องเรียก SaveAsPdfX แยกก่อน ตัวฉีดจะเขียนทั้งสองชั้นในรอบเดียว
การเขียนไฟล์ด้วย SaveAsPdfVT
การเรียกขั้นต่ำต้องมีเพียงเอกสารที่เปิดใช้อยู่ เพราะ TPdfVTSaveOptions.Default จัดเตรียมโปรไฟล์ ICC แบบ sRGB ที่ฝังมาให้และค่าความสอดคล้อง pvc1 ไว้แล้ว การบันทึกทำงานภายในสามขั้นตอนคือ เอาความปลอดภัยออกก่อน, เชื่อม Info dictionary และ trailer /ID ที่มีอยู่ของเอกสารเข้าไปในชุด marker เพื่อให้ค่า XMP กับ Info ตรงกัน, แล้วค่อยผนวกออบเจ็กต์ PDF/X-4 และ PDF/VT ผ่านการอัปเดตแบบเพิ่มส่วน
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
if Pdf.LoadFromFile('statements-merged.pdf') then
begin
// Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
Writeln('PDF/VT-1 written')
else
Writeln('Save failed (document not active?)');
end;
finally
Pdf.Free;
end;
end;
สำหรับงานผลิตจริง คุณแทบจะต้องแทนค่า OutputIntent ด้วยการกำหนดลักษณะของแท่นพิมพ์แทนค่า sRGB สำรองทั่วไปเสมอ ให้ส่งไบต์ของ ICC และตัวระบุเงื่อนไขผ่าน TPdfVTSaveOptions:
var
Pdf: TPdf;
Opt: TPdfVTSaveOptions;
Icc: TBytes;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('directmail-merged.pdf');
Icc := LoadIccProfile('GRACoL2013_CRPC6.icc'); // your own loader
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 is normalised to pvc1 on write
Opt.IccProfileData := Icc;
Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
Opt.OutputCondition := 'Commercial print, coated, CRPC6';
Opt.RegistryName := 'http://www.color.org';
Opt.Title := 'Spring 2026 Direct Mail Run';
Opt.Trapped := ptvFalse; // PDF/X Info /Trapped state
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
รายละเอียดหนึ่งในตัวอย่างนั้นเป็นราวกันตกที่ตั้งใจใส่มา ไม่ใช่ข้อจำกัดที่เถียงได้ การตั้ง Opt.Conformance := pvc2 ไม่ได้ทำให้ได้ไฟล์ PDF/VT-2 ตัวเขียนจะปรับคำขอที่ไม่ใช่ pvc1 กลับเป็น pvc1 เพราะ PDF/VT-2 เป็นฟอร์แมตแบบชุดไฟล์ และตัวเขียนสำหรับไฟล์เดี่ยวที่ต่อท้ายเอกสารเอาต์พุตเพียงฉบับเดียวไม่สามารถประกอบชุดทรัพยากรภายนอกตามที่ §6.2.2 ต้องการได้จริง ค่า pvc2 มีไว้สำหรับเส้นทางอ่าน เพื่อให้ ValidatePdfVT รู้จักและรายงานเอกสารแบบชุดไฟล์ที่มีอยู่แล้ว ไม่ใช่เป้าหมายสำหรับการเขียน
ต้นไม้ DPart: โครงสร้างที่ RIP อ่านจริง
หัวใจของ PDF/VT คือลำดับชั้น Document Part หรือ DPart มันคือสิ่งที่ทำให้แท่นพิมพ์แบ่งงานพิมพ์ยาว ๆ ออกเป็นระเบียน รวมระเบียนเข้ากับผู้รับหรือชุดจดหมาย และแนบ Document Part Metadata เพื่อให้อุปกรณ์ปลายทางเส้นทางและคิดค่ากับแต่ละชิ้นได้ ISO 16612-2 §6.5 วางการเชื่อมโยงไว้ชัดเจนว่า catalog ต้องมี /DPartRoot, โหนด DPart ระดับรากต้องมี /DPartRootNode และ /NodeNameList ที่ตั้งชื่อแต่ละระดับของลำดับชั้น, DPart ระดับใบครอบคลุมช่วงของ page tree, และทุกหน้าที่เป็นของ part หนึ่งต้องชี้กลับไปยังใบของมันผ่านรายการ /DPart ระดับหน้า
เมื่อเอกสารต้นทางของคุณมีลำดับชั้นที่ใช้ได้อยู่แล้ว SaveAsPdfVT จะเก็บมันไว้ แต่ถ้าไม่มี ตัวเขียนจะสร้างแบบขั้นต่ำขึ้นมาเอง คือ DPart ระดับเอกสารหนึ่งตัวที่ครอบ page tree ปัจจุบันตามลำดับ พร้อมเพิ่มการอ้างกลับ /DPart ให้กับออบเจ็กต์หน้าที่ยังใช้งานอยู่ทุกตัว และ /NodeNameList [/Document] ระดับเดียว จงซื่อสัตย์กับตัวเองว่า tree ขั้นต่ำนั้นคืออะไร มันเป็นสมอเชิงโครงสร้างที่ตอบโจทย์รูปแบบตาม §6.5 ไม่ใช่เมตะดาต้าทางธุรกิจ มันไม่สามารถสร้างผู้รับ, ขอบเขตชิ้นงานจดหมาย, หรือชุดผลิตภัณฑ์ขึ้นมาเองได้ เพราะข้อมูลนั้นไม่เคยมีอยู่ในต้นทาง ถ้าคุณมีข้อมูลรายผู้รับ คุณคาดว่าจะต้องสร้าง DPart tree ที่ลึกกว่านั้นเอง และขยาย /NodeNameList ให้ตรงกับระดับที่คุณสร้าง
การตรวจสอบที่ไม่หยุดอยู่แค่การมีคีย์
ValidatePdfVT คืนค่า record TPdfVTValidationResult ที่มีอยู่สามอย่างคือ Conformance ที่ตรวจพบ, ชุด Issues, และตัวช่วย IsCompliant ซึ่งจะเป็นจริงก็ต่อเมื่อความสอดคล้องเป็นระดับจริงและชุดปัญหาว่างเปล่า การแจกแจงปัญหาถูกออกแบบให้จำเพาะ จึงทำให้ผลที่ล้มเหลวบอกได้ว่าคุณพลาดข้อไหน แทนที่จะบอกแค่ว่า "ไม่ถูกต้อง":
var
Pdf: TPdf;
Res: TPdfVTValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('statements-pdfvt.pdf');
Res := Pdf.ValidatePdfVT;
if Res.IsCompliant then
Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
else
begin
if pvviMissingDPartRoot in Res.Issues then
Writeln('DPart hierarchy missing or unusable');
if pvviMissingPdfXIdentifier in Res.Issues then
Writeln('PDF/X-4 base identifier absent');
if pvviMissingOutputIntent in Res.Issues then
Writeln('OutputIntent / ICC profile missing');
if pvviEncryptionPresent in Res.Issues then
Writeln('Encrypted - PDF/X forbids this');
end;
finally
Pdf.Free;
end;
end;
สองการตรวจที่ควรเข้าใจให้ลึกคือการจับคู่ความสอดคล้องกับการไล่ตรวจ DPart เพราะทั้งคู่เคยผ่อนปรนเกินไปและถูกปรับให้เข้มขึ้นตามสเปกแล้ว ฝั่งการจับคู่ ตัวตรวจสอบจะจับคู่แบบตรงตัว ไม่ใช่แนว "PDF/X แบบไหนก็ได้" ไฟล์ PDF/VT-1 จะยอมรับก็ต่อเมื่ออยู่บนฐาน PDF/X-4 และไฟล์ PDF/VT-2 จะยอมรับได้เฉพาะบน PDF/X-4p, PDF/X-5g หรือ PDF/X-5pg เท่านั้น เมอร์กเกอร์ PDF/VT-1 ที่วางอยู่บนฐาน PDF/X-1a จะถูกรายงาน ไม่ได้ปล่อยผ่าน
ส่วนการไล่ตรวจ DPart คือจุดที่ความเข้มงวดส่วนใหญ่อยู่ มันไม่พอให้ catalog มีคีย์ /DPartRoot เพราะออบเจ็กต์เปล่าที่ถูกปลอมขึ้นมาหรือออบเจ็กต์ที่ไม่มีลิงก์ไปยังหน้าใดเลยก็ยังใช้งานไม่ได้อยู่ดี HasValidDPartHierarchy และ ValidateDPartNode แบบเรียกซ้ำจะไล่โครงสร้างทั้งหมด พวกมันตามลิงก์ parent, ปฏิเสธ child ซ้ำและวงวน, บังคับให้ /Start กับ /DParts ไม่เกิดร่วมกัน, และกำหนดให้ช่วงหน้าของ leaf ต้องครอบ page tree ตามลำดับ depth-first โดยให้ /DPart ของแต่ละหน้าชี้ไปยัง leaf ที่ครอบหน้านั้น ทุกความผิดพลาดภายในเหล่านี้จะถูกรวมลงในบิตปัญหาเดียว pvviMissingDPartRoot แทนที่จะขยาย enum สาธารณะ ดังนั้นให้มองแฟลกนั้นว่า "ลำดับชั้น DPart ใช้งานไม่ได้" ไม่ใช่แปลตรงตัวว่า "ไม่มีคีย์ราก"
กับดักไวยากรณ์สามข้อที่ตัวตรวจสอบบังคับแล้ว
การไล่ตรวจตาม §6.5 Table 4 หลายรอบพบรูปแบบที่เวอร์ชันก่อนหน้านี้ยอมรับ แต่สเปกไม่ยอม รูปแบบเหล่านี้เป็นสิ่งที่ tree DPart ที่สร้างด้วยมือมักทำพลาด จึงคุ้มค่าที่จะพูดให้ชัดเจน:
/DPartsเป็นอาร์เรย์ของอาร์เรย์ ไม่ใช่อาร์เรย์แบน แต่ละสมาชิกของอาร์เรย์ชั้นนอกต้องเป็นอาร์เรย์อ้างอิงแบบ indirect reference เองด้วย/DParts [9 0 R]แบบแบนจะถูกปฏิเสธ ส่วนรูปแบบที่ถูกต้องคือ/DParts [[9 0 R] [10 0 R]]วิธีนี้ช่วยไม่ให้โครงสร้างที่ไม่เป็นลำดับชั้นปลอมตัวเป็นระดับที่ถูกต้องได้/Endใช้ได้เฉพาะเมื่อเป็นช่วงหลายหน้าจริง ๆ เท่านั้น leaf DPart จะมี/Endได้ก็ต่อเมื่อมี/Startด้วย และ/Endต้องอยู่หลัง/Startตามลำดับของ page tree ถ้าเป็นกรณีเสื่อมอย่าง/Start 3 0 R /End 3 0 Rลำดับชั้นจะกลายเป็นใช้งานไม่ได้แทนที่จะถูกมองว่าเป็นส่วนที่มีหน้าเดียว- ชื่อใน
/NodeNameListต้องผ่านการ unescape ชื่อ PDF แล้วเป็น XML NMTOKEN ได้ ชื่ออย่าง/Bad#20Nameจะขยายออกมาเป็นสตริงที่มีช่องว่าง ซึ่งไม่ใช่ token ที่ถูกต้อง การใช้งานจะตรวจ ASCII แบบเบา ๆ คืออักษร ตัวเลข.,-,_,:และไบต์ที่ไม่ใช่ ASCII เพื่อจับความผิดพลาดเรื่องช่องว่างและตัวคั่นโดยไม่ปฏิเสธชื่อท้องถิ่นหรือชื่อเฉพาะของผู้ผลิตที่ถูกต้อง
ตัวบอก XMP: สองวิธีเขียนพร็อพเพอร์ตีเดียวกัน
การระบุ PDF/VT อยู่ใน XMP ภายใต้ namespace pdfvtid โดยเฉพาะ GTS_PDFVTVersion และ GTS_PDFVTModDate ควบคู่ไปกับ xmp:CreateDate และ xmp:ModifyDate มาตรฐาน จุดละเอียดที่ทำให้ผู้อ่านแบบง่ายรายงานว่า "หายไป" แบบผิด ๆ คือค่าพวกนี้เขียนได้สองแบบ คือเป็นข้อความของ element (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) หรือเป็น RDF attribute บน element description เอง PDFiumPas อ่านได้ทั้งสองแบบ ดังนั้นไฟล์ที่เครื่องมืออื่นเขียนด้วยสไตล์ attribute จึงไม่ถูกลงโทษ นอกจากนี้ยังบังคับกฎความสอดคล้องใน §6.3 ว่า GTS_PDFVTModDate ต้องเท่ากับ xmp:ModifyDate หากไม่ตรงกันจะยก pvviModDateMismatch
มีกฎอีกข้อจากข้อเดียวกันคือ ค่า GTS_PDFVTVersion ที่ไม่รู้จักจะถูกเก็บไว้เป็น pvcUnknown แทนที่จะถูกกลืนกลับไปเป็น pvcNone ความต่างนี้สำคัญในการทำงานจริง pvcNone หมายถึง "ไม่มี marker ของ PDF/VT เลย เป็น PDF ธรรมดา" ส่วน pvcUnknown หมายถึง "มีบางอย่างเขียนเวอร์ชันที่ตัวตรวจสอบนี้ไม่รู้จัก" ซึ่งรวมกรณี PDF/VT-2s อยู่ด้วย การปะปนสองค่านี้เข้าด้วยกันจะซ่อนไฟล์ที่ผิดรูปไว้ในกล่องเดียวกับเอกสารธรรมดา
ขอบเขตที่คำรับประกันสิ้นสุดลง
ควรพูดให้ชัดว่าขอบเขตที่เมธอดเหล่านี้รับประกันคืออะไร เพราะการทำให้การพิมพ์ข้อมูลแปรผันเป็นไปตามมาตรฐานมีมูลค่าทางธุรกิจจริง การตรวจ DPart และการจับคู่ระดับความสอดคล้องเป็นการตรวจโครงสร้างระดับไบต์ พวกมันยืนยันว่าโครงสร้างเพิ่มประสิทธิภาพ, ตัวบ่งชี้ฐาน PDF/X-4, OutputIntent และ XMP มีอยู่จริงและสอดคล้องกันภายใน แต่ไม่ใช่ preflight ระดับเนื้อหาของ PDF/X-4 พวกมันไม่ได้ตรวจว่าทุกสีอยู่ในเงื่อนไขเอาต์พุตที่ประกาศไว้, ฟอนต์ทุกตัวฝังอยู่แล้ว, หรือไม่มีกรณีขอบของ transparency-blending ที่ต้องห้ามหลุดเข้ามา หากเป็นงานที่จะส่งเข้าแท่นพิมพ์ตามสัญญา ให้จับการตรวจโครงสร้างของ PDFiumPas คู่กับเอนจิน preflight PDF/X เฉพาะทางและการพิมพ์ทดสอบ เหมือนที่คุณตรวจความสมเหตุสมผลของคำอ้างความสอดคล้องแบบอื่น ๆ ชั้นโครงสร้างจะจับความผิดพลาดที่ทำให้ RIP แคชพังแบบเงียบ ๆ มันคือครึ่งหนึ่งของการตรวจที่สมบูรณ์ ไม่ใช่ทั้งหมด
ถ้าคุณกำลังนำการตรวจเหล่านี้ไปใส่ใน release gate ที่กว้างขึ้น แนวทางสแกนระดับไบต์แบบเดียวกันก็เป็นฐานให้กับงานด้านมาตรฐานอื่นของไลบรารีด้วย รวมถึง การตรวจสอบ object และ cross-reference streams ก่อนที่ไฟล์จะไปถึง preflight และวินัยเรื่อง shared object เบื้องหลัง page stamp แบบใช้ซ้ำได้ด้วย Form XObjects ที่ทำให้เอกสารพร้อมสำหรับ RIP ตั้งแต่ต้น API สำหรับบันทึกและตรวจสอบ PDF/VT กับ PDF/X ที่อธิบายที่นี่เป็นส่วนหนึ่งของ PDFium VCL component สำหรับ Delphi และ C++Builder ซึ่งหน้าผลิตภัณฑ์มีเอกสารอ้างอิงด้านความสอดคล้องครบถ้วน