เหตุใดข้อความใน PDF จึงไม่ใช่ข้อความจริงๆ
ภาพรวมโดยย่อ
หัวข้อที่มีชื่อว่า “ภาพรวมโดยย่อ”เมื่อคุณอ่าน PDF คุณเห็นถ้อยคำ แต่ไฟล์ไม่ได้บรรจุถ้อยคำไว้ มันบรรจุคำสั่งให้วาดรูปทรงที่พิกัดต่างๆ และรูปทรงเหล่านั้นบังเอิญดูเหมือนตัวอักษร ช่องว่างระหว่างสิ่งที่ PDF วาด กับสิ่งที่มัน หมายถึง คือเหตุผลที่หน้าหนึ่งดูไร้ที่ติได้แต่ยังคัดลอกออกมาเป็นเรื่องไร้สาระ หน้านี้ว่าด้วยช่องว่างนั้น และว่าด้วยแผนที่เล็กๆที่แยกต่างหากซึ่งปิดมันลง
เหตุใดเรื่องนี้จึงสำคัญ
หัวข้อที่มีชื่อว่า “เหตุใดเรื่องนี้จึงสำคัญ”ทุกสิ่งที่คุณทำกับ PDF ในฐานะข้อความ — การเลือก การคัดลอก การค้นหา การทำดัชนีเพื่อการสืบค้น การอ่านออกเสียงด้วยโปรแกรมอ่านหน้าจอ — ล้วนพึ่งพาการกอบกู้อักขระที่ไฟล์ไม่เคยจัดเก็บไว้โดยตรง หากการกอบกู้นั้นล้มเหลว ความล้มเหลวก็มองไม่เห็น หน้านั้นยังเรนเดอร์ออกมาได้ ไม่มีใครสังเกตจนกระทั่งมีคนคัดลอกย่อหน้าหนึ่งไปวางในอีเมลแล้วได้ □□□□ หรือค้นหาสัญญา 400 หน้าเพื่อหาข้อหนึ่งที่อยู่ตรงนั้นชัดเจนแต่กลับไม่พบอะไรเลย
นั่นเป็นบั๊กคลาสที่แพง เพราะมันรอดพ้นจากการตรวจทานเชิงภาพทุกครั้ง ไม่มีอะไรให้มองเห็น เอกสารดูน่าเชื่อถือ และในแง่ของเครื่องจักรแล้วก็เงียบใบ้
ฉบับย่อ
หัวข้อที่มีชื่อว่า “ฉบับย่อ”- PDF วาด glyph — รูปทรงเชิงภาพที่เลือกด้วยรหัสตัวเลขเข้าไปในฟอนต์ — ไม่ใช่ อักขระ (character) ของ Unicode
- รูปทรงเดียวกันหมายถึงอักขระต่างกันได้ และอักขระเดียวกันก็ถูกวาดด้วย glyph ต่างกันได้ หน้าตาและความหมายถูกแยกออกจากกันโดยการออกแบบ
- หากต้องการนำข้อความออกมา โปรแกรมอ่านจะรันเส้นทางการแสดงผล ย้อนกลับ คือ
จากรหัสไปสู่อักขระ CMap แบบ
/ToUnicodeของฟอนต์คือตารางค้นหา สำหรับขั้นตอนย้อนกลับนั้น (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10) - การไม่มี
/ToUnicodeหรือมีแต่ผิด หมายถึงการคัดลอกวางที่อักขระเพี้ยนและ การค้นหาที่ล้มเหลว — โดยที่หน้านั้นยังคงคมชัดทุกพิกเซล - นี่คือปัญหาเรื่อง ความหมาย ส่วนการที่ glyph จะดูถูกต้องหรือไม่นั้นเป็นปัญหา
เรื่อง หน้าตา ที่แยกต่างหาก ซึ่งกล่าวถึงใน
ฟอนต์: ส่วนที่ยาก NextPDF สร้าง
/ToUnicodeที่ถูกต้องเพื่อให้การไป-กลับซื่อตรง
วิธีที่ NextPDF จัดการกับเรื่องนี้
หัวข้อที่มีชื่อว่า “วิธีที่ NextPDF จัดการกับเรื่องนี้”เริ่มจากว่าข้อความขึ้นไปอยู่บนหน้าได้อย่างไรตั้งแต่แรก content stream ไม่ได้บอกว่า “เขียนคำว่า file” มันเลือกฟอนต์ แล้วยื่นสตริงของ รหัส (codes) ให้กับ text-showing operator (Spec: ISO 32000-2, §9ISO 32000-2 §9) รหัสแต่ละตัวคือดัชนี — ตำแหน่งหนึ่งในโปรแกรมฟอนต์ — และฟอนต์เปลี่ยนดัชนีนั้นให้เป็นเส้นโครงร่างของ glyph แล้ววาดมัน รหัส 70 ไม่ใช่อักขระ F มันคือ “ช่องที่ 70 ใน ฟอนต์นี้” และช่องที่ 70 บังเอิญบรรจุเส้นโค้งที่มีรูปร่างแบบ F ไว้ เลือกฟอนต์ตัวอื่นแล้วช่องที่ 70 อาจเป็นเกล็ดหิมะก็ได้
ดังนั้นรหัสจึงมีความหมาย สัมพันธ์กับการเข้ารหัสของฟอนต์ของมัน เท่านั้น สำหรับฟอนต์แบบ simple การเข้ารหัสนั้นคร่าวๆคือหนึ่งไบต์ต่อหนึ่ง glyph สำหรับสคริปต์ที่ต้องใช้ glyph เป็นพันๆตัว — CJK หรือเอกสาร full-Unicode ใดๆ — นั่นไม่พอ และ PDF จึงเอื้อมไปหา ฟอนต์แบบ composite (Type 0) (Spec: ISO 32000-2, §9.7ISO 32000-2 §9.7) ฟอนต์ composite อ่านรหัสแบบหลายไบต์ผ่าน CMap เปลี่ยนมันเป็น CID (ตัวระบุอักขระ) แล้วแมป CID ไปยัง glyph มันคือการอ้อมที่งดงามซึ่งทำให้ฟอนต์ตัวเดียวเข้าถึงชุด glyph มหาศาลได้ และมันยังเป็นอีกหนึ่งจุดที่ร่องรอยจาก “สิ่งที่แสดง” ไปสู่ “สิ่งที่มันหมายถึง” อาจขาดหายไปได้
ทีนี้ลองรันมันย้อนกลับ หากต้องการแยกข้อความออกมา โปรแกรมอ่านจะนำรหัสที่พบใน content stream ไปกอบกู้อักขระ (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10) การเข้ารหัสที่วาด glyph เดินทางจาก อักขระไปสู่ glyph ส่วนการแยกข้อความต้องการ รหัส glyph ไปสู่อักขระ และทิศทางนั้นไม่ได้รับประกันว่าจะผกผันได้ ฟอนต์ subset อาจเปลี่ยนหมายเลข glyph ของมันใหม่ CID ของฟอนต์ composite อาจเป็นแบบส่วนตัว รูปทรงบนหน้าไม่ได้พกความหมายแบบ Unicode มาในตัว
วิธีแก้คือส่งแผนที่ย้อนกลับไปพร้อมกับฟอนต์ แผนที่นั้นคือ CMap แบบ /ToUnicode คือสำหรับแต่ละรหัสที่เอกสารใช้ มันบันทึกค่า Unicode (หนึ่งค่าหรือหลายค่า) ที่รหัสนั้นแทน เมื่อมีมัน การแยกข้อความก็เป็นการค้นหาที่สะอาด หากไม่มีมัน โปรแกรมอ่านก็ถูกทิ้งให้เดาจากการเข้ารหัสของฟอนต์และฮิวริสติก — และการเดานี่แหละคือจุดพอดีที่ fi กลายเป็นเครื่องหมายคำถาม
- Your charactersThe Unicode text you set, for example the word 'file'.
- Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
- Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
- Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
- Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
NextPDF เขียนแผนที่ย้อนกลับเป็นเรื่องปกติ เมื่อมันฝังฟอนต์ composite มันจะสร้างฟอนต์ลูกแบบ CIDFontType2 ฟอนต์แม่แบบ Type0 และ CMap แบบ /ToUnicode เพื่อให้รหัสแปลงกลับไปเป็น Unicode ได้ — งานการเข้ารหัสที่อธิบายไว้ใน ฟอนต์: ส่วนที่ยาก ประเด็นที่ควรแยกออกมาในที่นี้คือ เหตุผล คือสตรีม /ToUnicode ไม่ได้เกี่ยวกับการทำให้หน้าดูถูกต้อง glyph ดูถูกต้องอยู่แล้วโดยไม่ต้องมีมัน มันคือสิ่งประดิษฐ์ชิ้นเดียวที่ทำให้ข้อความยัง แยกออกมาได้
ตัวอย่างเชิงปฏิบัติ
หัวข้อที่มีชื่อว่า “ตัวอย่างเชิงปฏิบัติ”จุดที่เรื่องนี้ปรากฏชัดที่สุดคือ ลิเกเจอร์ fi ฟอนต์หลายตัววาด f และ i เป็น glyph รวมกันหนึ่งตัว เพราะจุดของ i ชนกับขอเกี่ยวของ f บนหน้ามันคือรูปทรงเดียว วาดด้วยรหัสเดียว คำถามคือจะเกิดอะไรขึ้นเมื่อคุณคัดลอกมัน
% A /ToUnicode entry that maps the single ligature code to TWO characters,% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.1 beginbfchar<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'endbfcharรายการเดียวนั้นคือความแตกต่างระหว่างการค้นหา “file” แล้วเจอกับการค้นหา “file” แล้วพลาดทุกครั้งที่เรนเดอร์ด้วยลิเกเจอร์อย่างเงียบๆ แมปรหัสลิเกเจอร์ไปยังลำดับสองอักขระ f + i แล้วคำนั้นก็ค้นหาได้และคัดลอกได้ ปล่อยมันไว้โดยไม่แมป แล้วหน้านั้นก็ยังแสดง file ได้สมบูรณ์แบบ ขณะที่ข้อความเงียบๆบอกในสิ่งที่เครื่องจักรอ่านไม่ได้
นี่คือเหตุผลที่ “fi” แยกออกมาได้เป็นหนึ่งอักขระหรือสองอักขระ และเหตุผลที่ความแตกต่างนั้นไม่ได้สุ่ม — มันคือสิ่งที่แผนที่ /ToUnicode ถูกบอกให้บอก แผนที่ที่ถูกต้องจะแยกลิเกเจอร์กลับเป็นอักขระของมัน NextPDF สร้างรายการแบบนั้นพอดี เพื่อให้ลิเกเจอร์บนหน้าเป็นตัวอักษรธรรมดาสองตัวบนคลิปบอร์ด
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”กับดักที่ตั้งชื่อให้ตรงๆคือ “ข้อความเรนเดอร์ได้ ดังนั้นข้อความก็ไม่มีปัญหา” การเรนเดอร์และการแยกข้อความคือ กลไกที่ต่างกันซึ่งอ่านข้อมูลคนละส่วน การเรนเดอร์เป็นไปตามเส้นทางจากรหัสไปยัง glyph และเป็นสิ่งที่ตาของคุณตรวจ ส่วนการแยกข้อความเป็นไปตามเส้นทางจากรหัสไปยังอักขระผ่าน /ToUnicode และเป็นสิ่งที่ผู้บริโภคที่เป็นเครื่องจักรทุกตัวตรวจ เอกสารหนึ่งทำข้อแรกได้ยอดเยี่ยมแต่ล้มเหลวข้อที่สองโดยสิ้นเชิงได้ เพราะโดยโครงสร้างแล้วไม่มีอะไรเชิงภาพให้เผยมันออกมา
กับดักที่สองที่แยบยลกว่า คือการสันนิษฐานว่า glyph “ย่อมรู้อยู่แล้ว” ว่ามันคืออักขระตัวไหน มันไม่รู้ glyph คือรูปทรงที่มีดัชนีหนึ่ง การแมปกลับไปยังอักขระคือ ข้อมูลเพิ่มเติมที่ผู้สร้างต้องจัดหามาให้ หากผู้สร้างไม่เคยเขียนมันไว้ ก็ไม่มีโปรแกรมอ่านใดกอบกู้มันได้อย่างซื่อตรง — ทำได้แค่เดาเท่านั้น
ขีดจำกัดและขอบเขต
หัวข้อที่มีชื่อว่า “ขีดจำกัดและขอบเขต”| Edition | Availability |
|---|---|
| Core | NextPDF emits a correct /ToUnicode CMap for the fonts it embeds, including the ligature and composite-font cases, so the documents it produces are searchable and copyable. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
แผนที่ /ToUnicode พกพาได้เฉพาะข้อมูลที่ฟอนต์ต้นทางเปิดเผยไว้จริงเท่านั้น ในที่ที่ glyph ไม่มีอักขระที่ระบุได้ — เครื่องหมายที่เป็นการตกแต่งล้วนๆ หรือสัญลักษณ์ในเขต private-use ที่ไม่มีการกำหนดค่า Unicode — ไม่มีแผนที่ใดประดิษฐ์ความหมายที่ไม่เคยมีอยู่ขึ้นมาได้ NextPDF สร้างการแมปที่ซื่อตรงเท่าที่มันอนุมานได้ มันไม่ได้กุอักขระขึ้นมาเติมช่องว่าง
หน้านี้ว่าด้วยเอกสารที่ NextPDF เขียน มันไม่ใช่เครื่องมือซ่อมสำหรับ PDF ขาเข้าใดๆที่ /ToUnicode ขาดหายหรือผิดอยู่แล้ว การกอบกู้ข้อความจาก PDF เหล่านั้นเป็นปัญหาเชิงฮิวริสติกที่แยกต่างหาก และการแยกข้อความที่ซื่อตรงนั้นจำเป็นต่อการเข้าถึง แต่ไม่ใช่ทั้งหมดของมัน — ลำดับ การอ่าน แท็ก และข้อความทางเลือก กล่าวถึงใน อะไรทำให้ PDF เข้าถึงได้
คำถามที่พบบ่อยฉบับย่อ
หัวข้อที่มีชื่อว่า “คำถามที่พบบ่อยฉบับย่อ”ทำไม PDF เดียวกันจึงคัดลอกได้ดีในโปรแกรมอ่านหนึ่งแต่แย่ในอีกโปรแกรม?
โปรแกรมอ่านต่างกันมีการสำรองทางต่างกันเมื่อ /ToUnicode ขาดหาย บางตัวเดาจากการเข้ารหัสในตัวของฟอนต์แล้วโชคดีกับข้อความละตินทั่วไป บางตัวก็ไม่ /ToUnicode ที่ถูกต้องจะขจัดการเสี่ยงโชคนี้ออกไป — โปรแกรมอ่านที่เป็นไปตามข้อกำหนดทุกตัวจะได้อักขระชุดเดียวกัน
นี่เป็นเรื่องเดียวกับการที่ฟอนต์ไม่ได้ถูกฝังหรือไม่?
ไม่ใช่ การฝังเกี่ยวกับ หน้าตา — ว่า glyph จะวาดได้หรือไม่โดยไม่ต้องติดตั้งฟอนต์ ส่วน /ToUnicode เกี่ยวกับ ความหมาย — ว่ารหัสจะแมปกลับไปยังอักขระได้หรือไม่ ฟอนต์ถูกฝังไว้อย่างสมบูรณ์แบบได้แต่ยังไม่มี /ToUnicode ที่ใช้งานได้ ซึ่งก็คือความล้มเหลวแบบค้นหาไม่ได้-แต่-สวยงาม
การแยกข้อความเคยต้องการมากกว่าหนึ่งอักขระต่อหนึ่งรหัสหรือไม่?
ใช่ — กรณีลิเกเจอร์ก็คือสิ่งนั้นพอดี: หนึ่งรหัสแมปไปยังสองอักขระ รายการ /ToUnicode แมปรหัสเดียวไปยังลำดับสั้นๆได้ ซึ่งเป็นวิธีที่ลิเกเจอร์ และรูปประกอบบางรูป กลับมาเป็นตัวอักษรองค์ประกอบของมัน
เอกสารที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เอกสารที่เกี่ยวข้อง”- ฟอนต์: ส่วนที่ยาก — บทเพื่อนคู่กันในฝั่ง หน้าตา: การฝัง การทำ subset และการสร้างการเข้ารหัส หน้านี้คือฝั่ง ความหมาย ของเหรียญเดียวกัน
- อะไรทำให้ PDF เข้าถึงได้ — ข้อความที่ซื่อตรงคือส่วนผสมหนึ่ง ส่วนลำดับการอ่าน แท็ก และข้อความทางเลือก คือส่วนที่เหลือ
- แท้จริงแล้ว PDF คืออะไร — แบบจำลองออบเจกต์
ที่ฟอนต์ การเข้ารหัส และสตรีม
/ToUnicodeใช้เป็นฐาน
อภิธานศัพท์
หัวข้อที่มีชื่อว่า “อภิธานศัพท์”- Glyph — รูปทรงเชิงภาพในฟอนต์ (เส้นโครงร่าง) คือสิ่งที่ PDF วาดออกมาจริง glyph มีดัชนีแต่ไม่มีความหมายของอักขระมาในตัว
- Character (อักขระ) — หน่วยของภาษาเขียน ที่ระบุด้วย code point ของ Unicode คือสิ่งที่คุณหมายถึง และเป็นสิ่งที่การแยกข้อความพยายามกอบกู้
- Code (รหัส) — ค่าตัวเลขใน content stream ที่เลือก glyph จาก ฟอนต์ปัจจุบัน ไม่ใช่อักขระ Unicode มีความหมายเฉพาะสัมพันธ์กับฟอนต์เท่านั้น
- CID — ตัวระบุอักขระที่ฟอนต์ composite ใช้เข้าถึง glyph ภายในชุดขนาดใหญ่ เป็นขั้นตอนกลางระหว่างรหัสกับ glyph
- ฟอนต์แบบ composite (Type 0) — ฟอนต์ที่อ่านรหัสแบบหลายไบต์ผ่าน CMap เพื่อไปถึง CID และ glyph ใช้เมื่อฟอนต์ตัวเดียวต้องเข้าถึง glyph เป็นพันๆตัว
/ToUnicode— สตรีม CMap ที่แมปรหัสที่เอกสารใช้กลับไปยัง ค่า Unicode คือสิ่งที่ทำให้ข้อความ PDF ค้นหาได้ คัดลอกได้ และเข้าถึงได้- Ligature (ลิเกเจอร์) — glyph ตัวเดียวที่วาดตัวอักษรสองตัวขึ้นไปเป็นรูปทรงเดียว
(ตัวอย่างเช่น
fi) รายการ/ToUnicodeของมันแยกมันกลับเป็นอักขระของมัน