เศรษฐศาสตร์ของขนาดไฟล์ PDF
Spec: ISO 32000-2, §7.5.7ISO 32000-2 §7.5.7
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”PDF สองไฟล์สามารถดูเหมือนกันพิกเซลต่อพิกเซลบนหน้าจอแต่ต่างกันสิบเท่าบนดิสก์ ความแตกต่างแทบไม่เคยเป็นเนื้อหาที่คุณเห็น แต่เป็นวิธีที่ไฟล์ถูกประกอบขึ้นเบื้องล่าง หน้านี้คือทัวร์เศรษฐศาสตร์ขนาด: ไบต์ของ PDF ไปอยู่ที่ใดจริงๆ และคันโยกสี่ตัวที่ผู้เขียนใช้จ่ายงบประมาณไบต์
มันคือเพื่อนคู่กันที่ว่าด้วย ทำไมไฟล์ถึงใหญ่ ของ สตรีมและฟิลเตอร์ ซึ่งครอบคลุม วิธีที่ฟิลเตอร์ถอดรหัส หน้านี้คงอยู่ที่เรื่องงบประมาณ
เหตุใดเรื่องนี้จึงสำคัญ
หัวข้อที่มีชื่อว่า “เหตุใดเรื่องนี้จึงสำคัญ”ขนาดไฟล์แทบไม่เคยเป็นตัวชี้วัดเพื่อความโก้ มันคือแบนด์วิดท์ในทุกการดาวน์โหลด พื้นที่จัดเก็บในทุกการเก็บถาวร และ latency ในทุกการแสดงตัวอย่าง ใบแจ้งหนี้ขนาด 12 MB ที่ควรเป็น 400 KB ไม่ใช่ปัญหาเชิงเครื่องสำอางเมื่อคุณสร้างมันหนึ่งล้านใบต่อเดือน — มันคือบิลที่แพงขึ้นสามสิบเท่า
ส่วนที่น่าหงุดหงิดคือความบวมมักมองไม่เห็น เอกสารเรนเดอร์ถูกต้อง เปิดได้ดี พิมพ์ได้ดี ไม่มีอะไรบอกคุณว่าอาร์ติแฟกต์เดียวกันนี้อาจมีขนาดเป็นเศษเสี้ยวได้ เพราะไบต์ที่เสียเปล่าเป็นเชิงโครงสร้าง ไม่ใช่เชิงภาพ การจะหามันคุณต้องดูที่งบประมาณ ไม่ใช่หน้า
ฉบับย่อ
หัวข้อที่มีชื่อว่า “ฉบับย่อ”ลองนึกว่า PDF คืองบประมาณที่คุณใช้จ่ายกับรายการสี่รายการ
- โอเวอร์เฮดต่ออ็อบเจกต์ ทุก indirect object พกซองจดหมาย
N G obj/endobjและถูกติดตามด้วย cross-reference entry หนึ่งรายการ เอกสารที่มีหน้าจำนวนมากมีอ็อบเจกต์เล็กๆนับพัน และซองจดหมายก็สะสมรวมกัน Object stream ทิ้งซองจดหมายนั้นไปสำหรับอ็อบเจกต์ทั้งกลุ่ม แต่ละอ็อบเจกต์ที่แพ็กไว้ยังคงเก็บ cross-reference entry ของตัวเอง แต่เป็น type-2 entry ที่กะทัดรัด - ไบต์รูปภาพ สำหรับเอกสารใดก็ตามที่มีภาพถ่ายหรือภาพสแกน รูปภาพครอบงำ และคันโยกที่ใหญ่ที่สุดเพียงตัวเดียวคือ การเลือกฟิลเตอร์ — codec แบบไม่สูญเสียข้อมูลเทียบกับ codec รูปภาพแบบสูญเสียข้อมูลคือความแตกต่างระหว่างเมกะไบต์กับกิโลไบต์
- ไบต์ฟอนต์ ฟอนต์ฝังตัวเต็มรูปแบบคือ glyph หลายร้อยกิโลไบต์ที่คุณไม่เคยใช้ Subsetting เก็บไว้เฉพาะ glyph ที่เอกสารวาดจริง
- ดัชนี cross-reference table ที่ให้โปรแกรมอ่านหาทุกอ็อบเจกต์ก็สามารถเป็น สตรีมที่บีบอัดแล้ว แทนที่จะเป็นข้อความธรรมดาได้
ทำทั้งสี่ให้ถูกแล้วไฟล์ก็เล็ก พลาดข้อหนึ่งแล้วมันก็ครอบงำทุกอย่างที่คุณทำได้ดี
NextPDF จัดการเรื่องนี้อย่างไร
หัวข้อที่มีชื่อว่า “NextPDF จัดการเรื่องนี้อย่างไร”ตัวเขียนของ NextPDF เป็น serializer แบบสตรีมรอบเดียว: มันต่อท้ายไบต์ของแต่ละอ็อบเจกต์ขณะที่มันถูกผลิตและบันทึก in-use cross-reference entry แบบดั้งเดิมสำหรับแต่ละตัว ค่าเริ่มต้นนั้นเร็ว คาดการณ์ได้ และผลิตไฟล์ที่เสถียรในระดับไบต์ — แต่มัน ไม่ใช่ เลย์เอาต์ที่เล็กที่สุดเท่าที่เป็นไปได้ และ NextPDF ก็ซื่อสัตย์ในเรื่องนั้น
คันโยกที่ 1 — object stream (ObjStm)
หัวข้อที่มีชื่อว่า “คันโยกที่ 1 — object stream (ObjStm)”PDF คือกราฟของ indirect object ส่วนใหญ่เป็น dictionary เล็กๆ: page node, annotation dictionary, structure-tree element, outline entry แต่ละตัวจ่ายภาษีตายตัว — คีย์เวิร์ด obj / endobj หมายเลขอ็อบเจกต์และหมายเลข generation และ cross-reference entry ที่ระบุตำแหน่งมัน บนเอกสารที่มีอ็อบเจกต์เล็กๆนับพัน ภาษีนั้นเป็นส่วนแบ่งที่มีนัยสำคัญของไฟล์
Object stream รวบรวมอ็อบเจกต์ที่ไม่ใช่สตรีมขนาดเล็กเหล่านั้นจำนวนมากเข้าไว้ในสตรีมเดียวและบีบอัดมันด้วยกัน (Spec: ISO 32000-2, §7.5.7ISO 32000-2 §7.5.7) ซองจดหมาย obj / endobj ถูกทิ้งไปสำหรับทั้งกลุ่ม ค่าที่อยู่ภายในถูกจัดเก็บติดกันโดยไม่มีคีย์เวิร์ดต่ออ็อบเจกต์ จากนั้นถูก deflate เป็นบล็อกเดียว — ซึ่งยังบีบอัด ได้ดีกว่า เพราะตัวบีบอัดที่ขจัดข้อมูลซ้ำตอนนี้เห็น dictionary ที่คล้ายกันทั้งหมดในคราวเดียว cross-reference entry ไม่ได้หายไป — แต่ละอ็อบเจกต์ที่แพ็กไว้ยังต้องการหนึ่งรายการ — แต่มันหดเหลือเป็น type-2 entry แบบไบนารีที่กะทัดรัดใน cross-reference stream (รายละเอียดเพิ่มในคันโยกที่ 2)
ใน NextPDF สิ่งนี้ถูกส่งมอบโดย ObjectStreamPacker ตัวประมวลผลภายหลังที่สมบูรณ์ในตัวซึ่งรับ PDF แบบ cross-reference-stream ที่เสร็จแล้ว และเขียนอ็อบเจกต์ที่มีสิทธิ์ใหม่ให้เป็น /Type /ObjStm ตัวเดียว กฎที่มันปฏิบัติตามมาจากมาตรฐานโดยตรง: อ็อบเจกต์ที่มีสิทธิ์คืออ็อบเจกต์ที่เป็น generation-zero และไม่ใช่สตรีม หลังจากกันอ็อบเจกต์พิเศษที่ต้องคงเข้าถึงได้โดยตรงออก §7.5.7 ห้ามจัดเก็บอ็อบเจกต์สตรีมไว้ภายใน object stream ดังนั้นอ็อบเจกต์สตรีม — เนื้อหา ฟอนต์ รูปภาพ — จึงเก็บรายการของตัวเองไว้ และ ObjectStreamPacker ยังปฏิเสธ cross-reference stream object ของเอกสารเอง (มันถูกเขียนใหม่) และ /Encrypt dictionary ซึ่งทั้งคู่ต้องคงเข้าถึงได้โดยตรง ทุกสิ่งอื่นที่เป็น generation-zero และไม่ใช่สตรีมจะถูกแพ็ก
| Edition | Availability |
|---|---|
| Core | Full support in the open-source core via ObjectStreamPacker. It is opt-in: the single-pass writer’s default emits classic in-use entries, so output stays byte-identical unless you enable packing. The packer is deterministic, with its own reproducible golden baseline. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
การ opt-in เป็นจุดยืนที่ตั้งใจ ไม่ใช่ข้อจำกัดที่ซ่อนอยู่ เอาต์พุตเริ่มต้นเสถียรในระดับไบต์และตรงกับ golden baseline ที่มีอยู่ การเปิดการแพ็กคือการเลือกเข้าสู่เลย์เอาต์ที่ต่างออกไป เล็กกว่า และกำหนดผลแน่นอนเท่ากัน คุณเลือกการแลกเปลี่ยน และเอนจินไม่เคยตัดสินใจมันลับหลังคุณ
คันโยกที่ 2 — ดัชนีก็เป็นสตรีมได้เช่นกัน
หัวข้อที่มีชื่อว่า “คันโยกที่ 2 — ดัชนีก็เป็นสตรีมได้เช่นกัน”เมื่ออ็อบเจกต์อยู่ภายใน object stream ดัชนีที่ชี้ไปยังมันก็เปลี่ยนรูปร่าง โปรแกรมอ่านหาอ็อบเจกต์ที่แพ็กไว้ผ่าน cross-reference entry แบบ บีบอัด — type-2 entry ที่ระบุชื่อ object stream และดัชนีภายในมัน (Spec: ISO 32000-2, §7.5.8.3ISO 32000-2 §7.5.8.3) เนื่องจาก cross-reference ทั้งหมดเองเป็นสตรีม /Type /XRef ดัชนีสำหรับอ็อบเจกต์นับพันจึงถูกแพ็กแบบไบนารีและ deflate แทนที่จะถูกเขียนเป็นแถวข้อความธรรมดา แผนที่หดลงพร้อมกับดินแดน
ObjectStreamPacker สร้างสิ่งนี้ขึ้นใหม่พอดี: มันปล่อย object stream ตัวเดียว จากนั้น cross-reference stream ที่เขียนใหม่ซึ่งพก type-1 entry ที่กะทัดรัดสำหรับแต่ละอ็อบเจกต์ที่คงไว้และ type-2 entry สำหรับแต่ละอ็อบเจกต์ที่แพ็ก โดยรักษาทุกหมายเลขอ็อบเจกต์เพื่อให้ reference ที่มีอยู่ยังคงใช้ได้
คันโยกที่ 3 — การเลือกฟิลเตอร์รูปภาพ
หัวข้อที่มีชื่อว่า “คันโยกที่ 3 — การเลือกฟิลเตอร์รูปภาพ”สำหรับเอกสารใดก็ตามที่มีรูปภาพจริง คันโยกนี้ทำให้ตัวอื่นเล็กกระจ้อยร่อย ไบต์คือภาพเดียวกัน codec คืองบประมาณ ชื่อ ฟิลเตอร์อยู่ในชุดฟิลเตอร์มาตรฐาน (Spec: ISO 32000-2, §7.4ISO 32000-2 §7.4) และการเลือกระหว่างมันคือการตัดสินใจเรื่องขนาด:
- FlateDecode ไม่สูญเสียข้อมูล สมบูรณ์แบบสำหรับ line art ภาพหน้าจอ และอะไรก็ตามที่มีสีแบน — และหายนะสำหรับภาพถ่าย ที่ซึ่งไม่สูญเสียข้อมูลหมายถึง ทุกไบต์ของต้นฉบับ
- DCTDecode คือ JPEG: สูญเสียข้อมูล และสำหรับภาพถ่ายเป็นทางเลือกที่ถูกต้องด้วยส่วนต่างที่กว้าง มักลดลงสิบเท่าสำหรับคุณภาพที่ด้อยลงโดยไม่มีใครสังเกต
- JPXDecode คือ JPEG 2000: การบีบอัดแบบเวฟเล็ตที่มีเส้นโค้งคุณภาพต่อขนาดต่างออกไป แต่การรองรับไม่สม่ำเสมอและถูกห้ามโดยบางโปรไฟล์การเก็บถาวร
นี่คือจุดที่ วิธีที่ฟิลเตอร์ถอดรหัส สำคัญพอดี และนั่นคืองานของบทความ สตรีมและฟิลเตอร์ ประเด็นเศรษฐศาสตร์แคบกว่า: ภาพถ่ายที่จัดเก็บแบบไม่สูญเสียข้อมูลเป็นสาเหตุเดี่ยวที่พบบ่อยที่สุดของ PDF ที่ใหญ่โตอย่างไม่จำเป็น และไม่ว่าจะแพ็ก object stream มากเพียงใดก็ไม่อาจกอบกู้ไฟล์ที่น้ำหนักจริงคือภาพสแกนที่ไม่ผ่าน JPEG หนึ่งภาพได้
คันโยกที่ 4 — การทำ subset ฟอนต์
หัวข้อที่มีชื่อว่า “คันโยกที่ 4 — การทำ subset ฟอนต์”ฟอนต์ฝังตัวคือโปรแกรม ฟอนต์เต็มตัวอาจหลายร้อยกิโลไบต์ เพราะมันพกทุก glyph ที่นักออกแบบ typeface เคยวาด — อักขระนับพันข้ามภาษาที่คุณจะไม่มีวันใช้ในเอกสารนี้ Subsetting ฝังไว้เฉพาะ glyph ที่เอกสารวาดจริง เปลี่ยนโปรแกรมนั้นให้เป็นเศษเสี้ยวเล็กๆของตัวมันเอง จดหมายหน้าเดียวไม่ต้องการฟอนต์ที่รองรับ CJK ทั้งตัว มันต้องการ glyph ไม่กี่สิบตัวที่มันใช้ กลไกว่า glyph ถูกเลือกและทำดัชนีใหม่อย่างไรเป็นหัวข้อของตัวเอง — ดู ฟอนต์: ส่วนที่ยาก
- Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
- Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
- Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
- The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
ตัวอย่างเชิงปฏิบัติ
หัวข้อที่มีชื่อว่า “ตัวอย่างเชิงปฏิบัติ”ไม่มี API ที่ปั้นขึ้นมาให้แสดงตรงนี้ เพราะการตัดสินใจเรื่องขนาดที่สำคัญที่สุดเกิดขึ้น ก่อน ที่ไบต์จะไปถึงตัวเขียน — และคันโยกเชิงโครงสร้างเพียงตัวเดียวที่ NextPDF เปิดเผยคือ opt-in ตัวเดียว ในเชิงแนวคิด งบประมาณอ่านได้เช่นนี้:
- ป้อนภาพถ่ายเป็น JPEG เพื่อให้มันลงตัวภายใต้
DCTDecodeไม่ใช่ถูกเข้ารหัสใหม่แบบไม่สูญเสียข้อมูล ชัยชนะที่ใหญ่ที่สุดคือการเลือกเกี่ยวกับ ข้อมูลต้นทาง ไม่ใช่แฟล็กของตัวเขียน - ให้ตัวเขียนทำ subset ฟอนต์ฝังตัวเพื่อให้ส่งเฉพาะ glyph ที่วาด
- สำหรับเอกสารที่มีอ็อบเจกต์เล็กๆจำนวนมาก จง opt into การแพ็ก object stream ซึ่งส่งไฟล์ที่เสร็จแล้วผ่าน
ObjectStreamPackerเพื่อจัดกลุ่มอ็อบเจกต์ที่ไม่ใช่สตรีมขนาดเล็กและเขียน cross-reference stream ใหม่
เส้นทางเริ่มต้น — in-use entry แบบดั้งเดิม ไม่มี ObjStm — คือ baseline ที่ถูกต้อง: กำหนดผลแน่นอน เสถียรในระดับไบต์ และตรวจสอบง่าย การแพ็กคือการอัปเกรดที่พิจารณาแล้วเมื่อจำนวนอ็อบเจกต์ ไม่ใช่น้ำหนักรูปภาพ คือสิ่งที่ทำให้ไฟล์บวม
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”กับดักคือการเอื้อมไปหาปุ่ม “บีบอัด PDF” และคาดหวังว่ามันจะแก้ทุกอย่าง การบีบอัดไม่ใช่คันโยกเดียว มันคือสี่ตัว และมันไม่ทดแทนกัน การแพ็ก object stream ไม่อาจหดภาพถ่ายได้ — นั่นคืองานของฟิลเตอร์รูปภาพ JPEG ที่สมบูรณ์แบบไม่อาจชดเชยฟอนต์ที่คุณลืมทำ subset และไม่มีอะไรช่วยได้หากความบวมที่แท้จริงคือภาพสแกน 10 MB ที่จัดเก็บแบบไม่สูญเสียข้อมูลเพราะไม่มีใครเลือก DCTDecode ให้มัน
ความเข้าใจผิดข้อที่สองคือยิ่งเล็กยิ่งดีเสมอ มันไม่ object stream เข้ากันไม่ได้กับ linearization — เลย์เอาต์ fast-web-view ที่ตรึงตำแหน่งอ็อบเจกต์แบบสัมบูรณ์เพื่อให้หน้าแรกสตรีมเข้ามาแต่เนิ่นๆ และบางโปรไฟล์การเก็บถาวรจำกัดว่าฟิลเตอร์รูปภาพใดที่อนุญาตด้วยซ้ำ ขนาดเป็นแกนหนึ่ง มันแลกกับการสตรีม ความสอดคล้องการเก็บถาวร และความสามารถในการทำซ้ำ และจุดที่ถูกต้องบนเส้นโค้งขึ้นอยู่กับว่าเอกสารมีไว้เพื่ออะไร
ขีดจำกัดและขอบเขต
หัวข้อที่มีชื่อว่า “ขีดจำกัดและขอบเขต”คันโยกสี่ตัวคือเศรษฐศาสตร์ เชิงโครงสร้าง ของขนาดไฟล์ มันไม่ใช่การรับประกัน “ทำให้เล็กลง” แบบสากล และ NextPDF ไม่แกล้งทำเป็นตัวปรับให้เหมาะสมใหม่สำหรับ PDF ขาเข้าตามอำเภอใจ
ObjectStreamPacker คือการปรับให้เหมาะสมแบบ best-effort ที่ไม่เคยเสี่ยงต่อความถูกต้อง มันปฏิเสธ — โดยคืนอินพุตที่ไม่เปลี่ยนแปลง — เมื่อไฟล์ไม่ใช่ PDF แบบ cross-reference-stream เมื่อมันถูกเข้ารหัส เมื่อมันพกลายเซ็นดิจิทัล (การจัดวางอ็อบเจกต์ใหม่จะขยับ byte range ที่ลายเซ็นปกป้อง) เมื่อมันมี object stream อยู่แล้ว หรือเมื่อไม่มีอ็อบเจกต์ที่มีสิทธิ์ให้แพ็ก มันปล่อย object stream ตัวเดียวพอดีสำหรับทั้งเอกสาร มันไม่แยกออกเป็นคอลเลกชัน /Extends ซึ่งอยู่นอกขอบเขตและสอดคล้องกับมาตรฐานสำหรับขนาดเอกสารที่ NextPDF ผลิต
คันโยกรูปภาพและฟอนต์ส่วนใหญ่เป็นการตัดสินใจเกี่ยวกับ อินพุต ตัวเขียนของ NextPDF ไม่ทรานส์โค้ด bitmap แบบไม่สูญเสียข้อมูลให้เป็นสตรีมรูปภาพแบบสูญเสียข้อมูลโดยปริยาย — การเข้ารหัสภาพถ่ายใหม่เป็น DCTDecode หรือ JPXDecode เป็นการตัดสินใจเรื่องการเข้ารหัสรูปภาพต้นน้ำที่ชัดเจน ไม่ใช่สิ่งที่ serializer ทำลับหลังคุณ — และมันก็ไม่เรียกคืน glyph จากฟอนต์ที่ผู้เรียกขอให้ฝังเต็มตัว ชัยชนะเรื่องขนาดที่ใหญ่ที่สุดเกิดขึ้นต้นน้ำของตัว serialize ไบต์ งานของเอนจินคือไม่เสียงบประมาณที่คุณนำมาให้มัน
เอกสารที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เอกสารที่เกี่ยวข้อง”- สตรีมและฟิลเตอร์ — เพื่อนคู่กันเรื่อง วิธีที่ฟิลเตอร์ถอดรหัส หน้านี้เสริมมันโดยตั้งใจ ไม่ใช่ทำซ้ำมัน
- PDF จริงๆแล้วคืออะไร — โมเดล indirect-object ที่โอเวอร์เฮดต่ออ็อบเจกต์ของมันถูกคันโยก object stream ลดลง
- กายวิภาคของไฟล์ PDF — โครงสร้าง cross-reference ที่กลายเป็นสตรีมที่บีบอัดแล้ว
- ฟอนต์: ส่วนที่ยาก — วิธีที่ subsetting เลือกและทำดัชนีใหม่ให้ glyph ที่เอกสารวาด
อภิธานศัพท์
หัวข้อที่มีชื่อว่า “อภิธานศัพท์”- Object stream (ObjStm) — สตรีมเดียวที่เก็บ indirect object ที่ไม่ใช่สตรีมขนาดเล็กจำนวนมาก บีบอัดด้วยกัน เพื่อให้ซองจดหมาย
obj/endobjถูกทิ้งไปสำหรับทั้งกลุ่ม แต่ละอ็อบเจกต์ที่แพ็กไว้ยังคงเก็บ cross-reference entry ของตัวเอง ในรูปของ type-2 entry ที่กะทัดรัด เป็น opt-in ใน NextPDF - Indirect object — อ็อบเจกต์ที่มีหมายเลขในกราฟของ PDF ห่อหุ้มไว้ในซองจดหมาย
obj/endobjและติดตามด้วยดัชนี cross-reference ซองจดหมายคือโอเวอร์เฮดต่ออ็อบเจกต์ - Cross-reference stream — สตรีม
/Type /XRefที่ทำดัชนีให้ทุกอ็อบเจกต์ แพ็กแบบไบนารีและ deflate แทนที่จะเขียนเป็นแถวข้อความธรรมดา - Compressed (type-2) entry — cross-reference entry ที่ชี้ไปยังอ็อบเจกต์ที่อยู่ภายใน object stream โดยระบุชื่อสตรีมและดัชนีภายในมัน
- Font subsetting — การฝังไว้เฉพาะ glyph ที่เอกสารวาดจริง แทนที่จะเป็น typeface เต็มตัว ทำให้โปรแกรมฟอนต์ฝังตัวหดลง
- Lossless vs lossy filter — codec แบบไม่สูญเสียข้อมูล (FlateDecode) สร้างทุกไบต์ขึ้นใหม่ ส่วน codec รูปภาพแบบสูญเสียข้อมูล (DCTDecode หรือ JPXDecode ในโหมดสูญเสียข้อมูล) ทิ้งรายละเอียดที่ไม่อาจรับรู้ได้เพื่อผลลัพธ์ที่เล็กกว่ามาก (JPEG 2000 / JPX ยังกำหนดค่าเป็นแบบไม่สูญเสียข้อมูลได้ด้วย) การเลือกคือคันโยกหลักของไฟล์ที่มีสื่อหนัก