losLab PDF Library สามารถฝังโปรแกรมฟอนต์ที่ขาดหายไปของ PDF ที่โหลดไว้แล้วได้ด้วยการเรียกใช้งานเพียงครั้งเดียว: EmbedMissingFonts จะไล่ดูพจนานุกรมฟอนต์ทุกตัวในเอกสาร ค้นหาฟอนต์ระบบที่ติดตั้งไว้ซึ่งตรงกันตามชื่อ BaseFont และเขียนโปรแกรมฟอนต์กลับเข้าไปในไฟล์ สำหรับทีมที่ต้องซ่อมแซมเอกสารของบุคคลที่สามซึ่งไม่ผ่านการตรวจสอบความถูกต้องของ PDF/A ในเรื่องการฝังฟอนต์ นี่คือวิธีแก้ไขที่จะทำให้ข้อผิดพลาด preflight 00030 หายไป
สถานการณ์นี้นับว่าพบเจอได้บ่อยมาก ไพป์ไลน์การรับเอกสารเข้าคลังเก็บข้อมูลจะได้รับไฟล์ PDF จากซัพพลายเออร์ ลูกค้า หรือสำนักสแกนเอกสาร ซึ่งเอกสารเหล่านั้นแสดงผลได้ดีบนทุกเครื่องในอาคาร แต่แล้วตัวตรวจสอบ PDF/A ก็ปฏิเสธทั้งชุดด้วยข้อร้องเรียนเดียวกันซ้ำๆ หนึ่งครั้งต่อหนึ่งไฟล์: มีฟอนต์อย่างน้อยหนึ่งฟอนต์ที่ไม่ได้ฝังไว้ ไม่มีใครในกระบวนการก่อนหน้าที่จะสร้างไฟล์ขึ้นมาใหม่ ดังนั้นไพป์ไลน์จึงต้องซ่อมแซมไฟล์เหล่านั้น บทความนี้จะครอบคลุมถึงเส้นทางการซ่อมแซมดังกล่าว ซึ่งเป็นบทความคู่ขนานกับบทความ preflight ซึ่งครอบคลุมการตรวจหาการละเมิดข้อกำหนด PDF/A และ PDF/UA: บทความชิ้นนั้นจะบอกคุณว่าเอกสารใดเสีย ส่วนบทความนี้จะแก้ไขลักษณะการเสียที่เกิดขึ้นบ่อยที่สุด
ทำไม PDF/A จึงกำหนดให้ต้องฝังฟอนต์ทุกตัว?
ISO 19005-1 §6.3.4 กำหนดให้ทุกฟอนต์ที่ใช้โดยเอกสารที่เป็นไปตามข้อกำหนดต้องมีโปรแกรมฟอนต์บรรจุอยู่ภายในไฟล์ เนื่องจากคำมั่นสัญญาหลักของ PDF/A คือความสามารถในการแสดงผลซ้ำได้เหมือนเดิม: เอกสารจะต้องแสดงผลเหมือนกันทุกประการบนเครื่องคอมพิวเตอร์ในอีกห้าสิบปีข้างหน้าที่ไม่มีฟอนต์ใดๆ ร่วมกับเครื่องที่สร้างเอกสารนั้นขึ้นมาเลย ฟอนต์ที่ไม่ได้ฝังไว้คือคำสั่งให้ไปค้นหา Arial จากที่ใดสักแห่งบนระบบการดู และจุดยืนของมาตรฐานระบุว่า "ที่ใดสักแห่งบนระบบการดู" ไม่ใช่การรับประกันสำหรับการจัดเก็บเอกสารระยะยาว ไม่ว่าฟอนต์ที่นำมาทดแทนจะมีรูปลักษณ์อักขระ (glyph) เมทริกซ์ และความครอบคลุมอย่างไร ผู้อ่านก็จะได้ผลลัพธ์ตามนั้น ซึ่งอาจจะไม่ใช่สิ่งที่ผู้เขียนมองเห็น
ตัวการในอดีตคือข้อตกลงร่วม Standard 14 โดย PDF 1.0 สัญญาว่าโปรแกรมดูทุกตัวจะมาพร้อมกับ Helvetica, Times, Courier, Symbol และ ZapfDingbats ดังนั้นตัวสร้างไฟล์จึงเรียนรู้ที่จะอ้างอิงฟอนต์เหล่านั้นตามชื่อและไม่ฝังอะไรเลย และเครื่องมือต่างๆ ตลอดสามสิบปีที่ผ่านมาก็ยังคงทำเช่นนั้นอยู่ losLab PDF Library ให้ความสำคัญกับข้อกำหนดนี้อย่างมาก โดยในโหมดสร้าง PDF/A การเรียกใช้งาน AddStandardFont จะไม่มีผลใดๆ โดยเจตนา: ไลบรารีไม่ได้ส่งมอบโปรแกรมฟอนต์ของ Standard 14 มาด้วย จึงไม่สามารถฝังสิ่งที่ไม่มีได้ และปฏิเสธที่จะเขียนการอ้างอิงแบบไม่ฝังลงในเอกสารที่ระบุว่าเป็นไปตามข้อกำหนด โดยจะส่งกลับค่า 0 โดยไม่เลือกฟอนต์ ดังนั้นเอกสาร PDF/A จะต้องใช้ AddTrueTypeFont ร่วมกับการฝังแทน และคำขอ Embed=0 ใดๆ จะถูกเปลี่ยนเป็น Embed=1 อย่างเงียบๆ ในขณะที่โหมด PDF/A ทำงานอยู่ นั่นคือฝั่งผู้เขียน ปัญหาที่ยากกว่าคือฝั่งผู้อ่าน: เอกสารที่คนอื่นเขียนไว้แล้ว ซึ่งเต็มไปด้วยพจนานุกรมฟอนต์ที่คุณไม่ได้สร้างขึ้นเอง
EmbedMissingFonts ซ่อมแซมเอกสารที่โหลดไว้อย่างไร?
losLab PDF Library จะซ่อมแซมฟอนต์ในตำแหน่งเดิมแทนที่จะสร้างขึ้นมาใหม่ เมื่อตัวสร้างไฟล์ PDF เขียนฟอนต์ TrueType แบบไม่ฝัง พจนานุกรม FontDescriptor ที่สร้างขึ้นมาจะมีความสมบูรณ์อยู่แล้ว: ไม่ว่าจะเป็น FontName, FontBBox, Flags, Ascent, Descent, StemV ต่างก็อยู่ครบ สิ่งเดียวที่แยกมันออกจากฟอนต์แบบฝังก็คือการไม่มีข้อมูลรายการเดียว ได้แก่การอ้างอิงสตรีม /FontFile2 ที่เก็บโปรแกรมฟอนต์จริง ดังนั้น EmbedMissingFonts จะไม่ไปแตะต้องพจนานุกรมฟอนต์ ตัวเข้ารหัส อาร์เรย์ความกว้าง หรือสตรีมเนื้อหาใดๆ ที่อ้างอิงฟอนต์ตามชื่อทรัพยากร มันจะอ่านโปรแกรมฟอนต์ที่ตรงกันจากระบบ บีบอัดลงในวัตถุสตรีมใหม่ และแนบการอ้างอิง /FontFile2 เพียงหนึ่งรายการ (หรือ /FontFile3 สำหรับฟอนต์ CIDFontType0) เข้ากับ FontDescriptor ที่มีอยู่แล้ว ทุกสิ่งที่หน้าเอกสารชี้ไปจะยังคงอยู่ที่เดิมทุกประการ ซึ่งเป็นสิ่งที่ทำให้การทำงานนี้ปลอดภัยสำหรับการรันบนไฟล์ที่คุณไม่ได้ควบคุม
การครอบคลุมนี้รวมถึงสถาปัตยกรรมฟอนต์ทั้งสองแบบที่คุณจะได้พบในการใช้งานจริง: ฟอนต์ TrueType แบบธรรมดา และฟอนต์รวม Type0/CID ซึ่งเป็นประเภทที่สร้างขึ้นสำหรับข้อความ CJK และผลลัพธ์ Unicode สมัยใหม่ การเคลื่อนย้ายผ่านข้อมูลจะแจกแจงพจนานุกรม Font ทุกตัวในแผนภูมิวัตถุของเอกสารอย่างตั้งใจ แทนที่จะพึ่งพาการเดินตามทรัพยากรทีละหน้า ดังนั้นฟอนต์ที่อ้างอิงจากคำอธิบายประกอบ (annotation) หรือใช้ร่วมกันในแต่ละหน้าก็จะถูกดึงขึ้นมาด้วยเช่นกัน โดย API นี้เป็นการเรียกใช้เพียงครั้งเดียวบนเอกสารที่โหลดขึ้นมา
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// ไล่ดูพจนานุกรม Font ทุกตัว ส่งกลับจำนวนฟอนต์
// ที่ได้รับโปรแกรมฟอนต์ ฟอนต์ที่ระบบไม่พบโปรแกรม
// จะถูกข้ามไป ไม่ส่งผลให้การทำงานล้มเหลว
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
การตรวจสอบความถูกต้องของการซ่อมแซมด้วยรายงาน preflight
CreatePreflightReport คือขั้นตอนการตรวจสอบความถูกต้อง และกระบวนการนี้เป็นแบบวงจรปิดโดยเจตนา: การตรวจสอบแบบเดียวกับที่ระบุความบกพร่องของไฟล์ควรเป็นตัวที่เคลียร์ไฟล์นั้น รหัสข้อผิดพลาด 00030 คือผลการตรวจสอบเชิงลึกของ PDF/A ที่ระบุว่า "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)" และรายงานนี้จะแสดงกับไฟล์โดยรวม ดังนั้นฟอนต์ที่ถูกมองข้ามเพียงตัวเดียวก็ยังคงทำให้มีข้อผิดพลาดนี้อยู่ ให้รันรายงานบนไฟล์ต้นฉบับ ซ่อมแซม บันทึก และรันอีกครั้งบนไฟล์ผลลัพธ์
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 เลือกการตรวจสอบ PDF/A
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
สำหรับการดูผลรายฟอนต์แทนที่จะตัดสินเป็นรายไฟล์ ให้โหลดเอกสารที่ซ่อมแซมแล้วอีกครั้งและทำการแจกแจง: FindFonts ตามด้วย SelectFont และ GetFontIsEmbedded จะรายงานสถานะการฝังฟอนต์ทีละตัว ซึ่งเป็นเครื่องมือที่เหมาะสมเมื่อต้องการบันทึกข้อมูลอย่างแม่นยำในงานแบบแบทช์ว่าฟอนต์หน้าใดในไฟล์ใดที่ไม่สามารถซ่อมแซมได้ รูปแบบการแจกแจงเดียวกันนี้ยังปรากฏในบทความเกี่ยวกับการแยกข้อความ รูปภาพ และฟอนต์ออกจากเอกสาร PDF ที่โหลดขึ้นมา ซึ่งใช้สำหรับกระบวนการแยกข้อมูลแทนการซ่อมแซม
จะเกิดอะไรขึ้นเมื่อไม่ได้ติดตั้งฟอนต์นั้นบนระบบ?
EmbedMissingFonts จะข้ามฟอนต์ใดๆ ที่ไม่สามารถค้นหาโปรแกรมได้ และรายงานการข้ามนั้นผ่านทางค่าส่งกลับ: หากการนับส่งกลับมาต่ำกว่าจำนวนฟอนต์ที่ไม่ได้ฝังที่คุณนับไว้ ผลต่างนั้นก็คือฟอนต์ที่ระบบไม่มี ซึ่งนี่คือโหมดความล้มเหลวที่แท้จริง และย่อมดีกว่าทางเลือกอื่นๆ เพราะการคิดค้นโปรแกรมทดแทนสำหรับฟอนต์ที่ระบุชื่อในเอกสารจะทำให้การแสดงผลเปลี่ยนไป ซึ่งเป็นสิ่งที่การซ่อมแซมเพื่อการจัดเก็บเอกสารระยะยาวต้องไม่ทำอย่างเด็ดขาด สำหรับกรณีเหล่านี้ losLab PDF Library ได้จัดเตรียม EmbedFontProgramFromFile ซึ่งจะฝังไฟล์ .ttf หรือ .otf ที่ผู้เรียกใช้งานจัดเตรียมไว้ให้ลงในฟอนต์ที่ระบุชื่อ เพื่อให้ไพป์ไลน์สามารถส่งมอบฟอนต์ขององค์กรที่คาดว่าจะพบและสลับไปใช้งานฟอนต์เหล่านั้นได้อย่างตั้งใจ
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// ลองใช้ฟอนต์ระบบที่ติดตั้งไว้ก่อน แล้วจึงสลับ
// ไปใช้ไฟล์ฟอนต์ที่ส่งมาพร้อมกับแอปพลิเคชัน
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
มีข้อจำกัดสองประการที่สมควรชี้แจงให้ชัดเจน ประการแรก ฟอนต์ Type1 จะไม่ถูกซ่อมแซมในเวอร์ชันปัจจุบัน: ข้อมูล /FontFile ของฟอนต์ดังกล่าวต้องการโครงสร้าง PFB แบบสามส่วนที่มีคีย์ความยาวที่ชัดเจน และไลบรารีจะข้ามพวกมันไปแทนที่จะเขียนสตรีมที่ผิดรูปแบบ ฟอนต์เหล่านี้หาได้ยากในเอกสารสมัยใหม่ แต่ก็ยังมีปรากฏอยู่ในคลังเก็บข้อมูลเก่าๆ ประการที่สอง การฝังฟอนต์เป็นการดำเนินการด้านลิขสิทธิ์ สิทธิ์การฝังของฟอนต์ TrueType เป็นของบริษัทผู้ผลิตฟอนต์ (foundry) และไพป์ไลน์การซ่อมแซมที่นำโปรแกรมฟอนต์ที่มีลิขสิทธิ์ใส่เข้าไปในเอกสารที่กำลังจะส่งออกจากองค์กรควรได้รับการยืนยันจากผู้รับผิดชอบว่าสิทธิ์การใช้งานฟอนต์นั้นอนุญาตให้กระทำได้จริง ไลบรารีจะทำตามที่คุณสั่ง ส่วนประเด็นที่ว่าคุณได้รับสิทธิ์ให้สั่งหรือไม่นั้นเป็นเรื่องของฝ่ายกฎหมายของคุณ ไม่ใช่เรื่องของคอมไพเลอร์
การฝังเป็นสิ่งจำเป็น แต่ยังไม่เพียงพอ
การซ่อมแซมฟอนต์จะล้างข้อผิดพลาด 00030 เท่านั้น เอกสารที่ไม่ผ่าน PDF/A เนื่องจากมีการเข้ารหัส ขาดข้อมูลเมตา XMP ใช้พื้นที่สีที่ขึ้นกับอุปกรณ์โดยไม่มี OutputIntent หรือไม่มีการแมป ToUnicode จะยังคงไม่ผ่านการตรวจสอบแม้หลังจากฝังฟอนต์ทุกตัวแล้ว นี่จึงเป็นเหตุผลที่การซ่อมแซมควรอยู่ในลูปที่ขับเคลื่อนด้วยรายงาน preflight มากกว่าที่จะทำเพื่อแทนที่มัน ให้รันรายงานฉับเต็ม แก้ไขรายการที่แจ้ง และปล่อยให้รายงานบอกคุณเมื่อทำงานเสร็จสิ้น นอกจากนี้ยังมีมิติด้านขนาดข้อมูลด้วย: โปรแกรมฟอนต์ภาษา CJK แบบเต็มไฟล์จะมีขนาดหลายเมกะไบต์ ดังนั้นการฝังฟอนต์เหล่านั้นหลายๆ ตัวอาจทำให้ขนาดไฟล์ของเอกสารขนาดเล็กเพิ่มขึ้นอย่างมาก ตัวถ่วงดุลในเรื่องนี้คือการทำ subsetting ซึ่งกล่าวถึงในบทความเกี่ยวกับการเพิ่มประสิทธิภาพขนาดไฟล์ PDF และการทำ font subsetting ซึ่งจะตัดลดขนาดโปรแกรมฟอนต์แต่ละตัวที่ฝังไว้ให้เหลือเฉพาะ glyph ที่เอกสารนั้นใช้แสดงผลจริง
การป้องกันไม่ให้เอกสารใหม่เกิดปัญหาซ้ำเดิม
SetEmbedAllFonts คือส่วนของการป้องกันของฟีเจอร์เดียวกันนี้: เป็นตัวป้องกันฝั่งผู้เขียนที่ช่วยหยุดรหัสของคุณไม่ให้สร้างเอกสารที่มีปัญหาซึ่งบทความนี้กำลังแก้ไข เมื่อเปิดใช้งาน SetEmbedAllFonts(1) การเรียกใช้ AddTrueTypeFont ในภายหลังที่ร้องขอ Embed=0 จะถูกปรับให้เป็นการอ้างอิงแบบฝังตัว ซึ่งเป็นการขยายขอบเขตการรับประกันที่โหมด PDF/A บังคับใช้อยู่แล้วไปยังเอกสารทุกฉบับ วิธีนี้จะส่งผลต่อฟอนต์ที่เพิ่มเข้ามาหลังจากการเรียกใช้เท่านั้น ไม่ส่งผลต่อฟอนต์ที่มีอยู่แล้วในไฟล์ที่โหลดขึ้นมา ดังนั้นการแบ่งหน้าที่จึงมีความชัดเจน: ใช้ SetEmbedAllFonts สำหรับเอกสารที่คุณสร้างขึ้น และใช้ EmbedMissingFonts สำหรับเอกสารที่คุณสืบทอดมา
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// จากนี้ไป AddTrueTypeFont(Name, 0) จะทำงาน
// เหมือนกับ AddTrueTypeFont(Name, 1): จะไม่มีการอ้างอิง
// แบบไม่ฝังฟอนต์หลุดไปยังไฟล์ผลลัพธ์
ทั้งสองส่วน ทั้งตัวป้องกันฝั่งผู้เขียนและเส้นทางการโหลด-ซ่อมแซม-บันทึก ต่างเป็นส่วนหนึ่งของ losLab PDF Library สำหรับ Delphi, C# และ VB.NET ควบคู่ไปกับเอนจิน preflight ที่คอยตรวจสอบความถูกต้องของผลลัพธ์ หน้าผลิตภัณฑ์นี้รวบรวมเอกสารอ้างอิง API ของฟอนต์ไว้อย่างครบถ้วน รวมถึงการเรียกใช้งานการฝังตัวและการทำ subsetting รายฟอนต์