ดิกชันนารี Catalog ของ PDF มีคีย์การนำทางที่จำเป็นต้องมีเพียงตัวเดียวเท่านั้นคือ /Pages คีย์นี้ต้องชี้ไปยังออบเจ็กต์ทางอ้อมประเภท /Pages ซึ่งจะเก็บอาร์เรย์ /Kids และ /Count หรือจำนวนหน้ารวมเอาไว้ หากไม่มีตัวชี้นี้ โปรแกรมอ่านที่ทำตามมาตรฐานจะไม่สามารถหาหน้าใดๆ ในไฟล์ได้เลย ISO 32000-1 §7.7.2 ระบุไว้ชัดเจนในประเด็นนี้ว่า: Catalog จะต้องมีรายการ /Pages และออบเจ็กต์ที่อ้างอิงถึงจะต้องเป็นประเภท /Pages ไฟล์ที่ละเมิดข้อกำหนดนี้ไม่ได้แค่ไม่เป็นไปตามมาตรฐานเท่านั้น แต่ยังมีโครงสร้างที่เสียหายในลักษณะที่พาร์สเซอร์ส่วนใหญ่รับมือได้ไม่ดีนัก
สิ่งที่ข้อกำหนดระบุไว้จริงๆ
PDF ที่ถูกต้องตามมาตรฐานขั้นต่ำสุดจะมีออบเจ็กต์อย่างน้อยสามตัว ออบเจ็กต์ 1 คือ Catalog ออบเจ็กต์ 2 คือ Pages ที่เป็นราก และออบเจ็กต์ 3 เป็นต้นไปคือดิกชันนารี Page แต่ละหน้า Catalog จะชี้ไปที่ราก Pages ส่วนราก Pages จะแสดงรายการลูกๆ ของมันใน /Kids และแต่ละ Page จะมีการอ้างอิงกลับ /Parent สายเชื่อมโยงทั้งหมดถูกออกแบบมาให้เป็นแบบสองทิศทาง ดังนั้นพาร์สเซอร์สามารถเริ่มจากฝั่งใดฝั่งหนึ่งและเดินทางไปยังหน้าใดก็ได้ในเวลา O(log n) สำหรับโครงสร้างต้นไม้ที่สมดุล
% Minimal conforming structure (ISO 32000-1 §7.7.2)
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << >> >>
endobj
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj
โครงสร้างต้นไม้ Pages สามารถซ้อนกันได้ เอกสารที่มีหน้าเป็นพันๆ หน้ามักจะจัดกลุ่มหน้าต่างๆ ลงในออบเจ็กต์โหนดระดับกลางที่เป็นประเภท /Pages ด้วย โดยแต่ละโหนดก็จะมี /Kids และ /Count ที่สะท้อนถึงโครงสร้างย่อยข้างใต้ของมัน /Count ของโหนดรากจะเท่ากับจำนวนหน้าทั้งหมดเสมอ ค่านี้คือสิ่งที่โปรแกรมดูนำไปแสดงในช่องหมายเลขหน้าก่อนที่จะพาร์สหน้าใดๆ เสียด้วยซ้ำ เพราะการอ่านตัวเลขจำนวนเต็มตัวเดียวจากออบเจ็กต์ 2 นั้นใช้ทรัพยากรน้อยกว่าการเดินหาทั่วทั้งโครงสร้างต้นไม้มาก
หน้าตาของไฟล์ที่ไม่มี Pages
ไฟล์ที่ขาดดิกชันนารี Pages มักจะมาจากเครื่องสร้าง PDF ที่เขียนออบเจ็กต์หน้าโดยตรงโดยไม่ประกอบมันเข้าเป็นโครงสร้างต้นไม้ หรือเกิดจากความเสียหายที่ลบโหนดรากทิ้งไปแต่ยังคงเหลือออบเจ็กต์ Page ที่เป็นใบเอาไว้ Catalog ในไฟล์แบบนี้อาจจะไม่มีคีย์ /Pages เลย หรือเก็บการอ้างอิงไปยังออบเจ็กต์ที่ไม่มีอยู่จริงในตารางการอ้างอิงข้าม (cross-reference table) แล้ว
% Non-conforming: Catalog with no /Pages reference
1 0 obj
<< /Type /Catalog >>
endobj
% Page objects exist but are unreachable from the Catalog
5 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj
15 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 16 0 R /Resources << >> >>
endobj
25 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 26 0 R /Resources << >> >>
endobj
พาร์สเซอร์ที่ทำตามข้อกำหนดจะอ่าน Catalog พยายามแก้ไข /Pages แล้วไม่พบอะไร (หรือพบการอ้างอิงที่ตายแล้ว) และจะแจ้งข้อผิดพลาดหรือรายงานว่ามีศูนย์หน้า สิ่งที่มันต้องไม่ทำก็คือการทำงานต่อไปเสมือนว่าไฟล์นั้นมีศูนย์หน้าและสำเร็จแบบเงียบๆ เพราะนั่นจะทำให้ได้ผลลัพธ์ที่ว่างเปล่าซึ่งดูเหมือนถูกต้องสำหรับเครื่องมืออัตโนมัติแต่ผิดสำหรับมนุษย์ทุกคนที่เปิดมัน
ทำไมพาร์สเซอร์ถึงแครช
พาร์สเซอร์ PDF ส่วนใหญ่จะจองหน่วยความจำตารางหน้าภายในขณะที่โหลดไฟล์โดยอิงจากค่า /Count จากราก Pages เมื่อรากนี้หายไป พาร์สเซอร์อาจจะอ่านค่าได้ศูนย์และไม่จองหน่วยความจำใดๆ แล้วก็เกิดการอ้างอิงพอยน์เตอร์ค่าว่าง (null pointer dereference) ในครั้งแรกที่มีโค้ดเรียกขอหน้า 1 หรืออาจจะอ่านค่าขยะและจองบัฟเฟอร์ที่ผิดพลาดอย่างรุนแรง ไม่ว่าจะทางใดก็ไม่ใช่ผลลัพธ์ที่ดี ข้อผิดพลาด access violation ที่ 0x008E5D78 ที่ปรากฏในล็อกการแครชจากการประมวลผลไฟล์แบบนี้ก็คือสิ่งนี้เอง: การอ้างอิงพอยน์เตอร์ค่าว่างภายในเส้นทางการเข้าถึงหน้า ซึ่งถูกกระตุ้นจากการไม่มีโครงสร้างที่พาร์สเซอร์ทึกทักเอาว่ามันจะต้องมีอยู่เสมอ
ข้อสันนิษฐานเบื้องหลังการออกแบบนี้ก็สมเหตุสมผล PDF ส่วนใหญ่ในโลกนี้มีดิกชันนารี Pages อยู่แล้ว พาร์สเซอร์ที่ข้ามการตรวจสอบว่ามีอยู่จริงไหมเพื่อประหยัดคำสั่งไม่กี่คำสั่งไม่ได้ทำไปอย่างประมาท แต่กำลังปรับปรุงประสิทธิภาพสำหรับกรณีที่พบบ่อย ไฟล์ที่ลงโทษการปรับปรุงประสิทธิภาพนี้มีน้อยมากจนโค้ดระดับใช้งานจริงอาจไม่เคยเจอเลยจนกว่าจะเจอเข้าจริงๆ ซึ่งเมื่อถึงจุดนั้นการแครชก็จะเกิดขึ้นซ้ำได้และชวนให้วิศวกรที่ไม่ได้อ่าน §7.7.2 ต้องงุนงง
การกู้คืนโดยไม่มีโครงสร้างต้นไม้ Pages
หากพาร์สเซอร์จำเป็นต้องจัดการไฟล์เหล่านี้แทนที่จะปฏิเสธมัน การกู้คืนจะทำตามเส้นทางที่คาดเดาได้: สแกนออบเจ็กต์ทางอ้อมทุกตัวในตารางการอ้างอิงข้าม รวบรวมตัวที่มี /Type /Page แล้วจัดเรียงตามหมายเลขออบเจ็กต์ ลำดับของหมายเลขออบเจ็กต์ไม่ได้รับประกันว่าจะตรงกับลำดับการอ่านในข้อกำหนด แต่ในทางปฏิบัติ เครื่องสร้างที่ละเว้นโครงสร้างต้นไม้ Pages มักจะปล่อยหน้าออกมาตามลำดับ ดังนั้นลำดับหมายเลขออบเจ็กต์จึงมักจะถูกต้องเสมอ
การตรวจสอบนั้นใช้ทรัพยากรน้อย ก่อนที่จะเดินตามพอยน์เตอร์ /Pages ของ Catalog ให้ยืนยันก่อนว่าพอยน์เตอร์มีอยู่จริง มันชี้ไปยังออบเจ็กต์ที่มีอยู่จริง และออบเจ็กต์ที่ถูกชี้ไปมี /Type เท่ากับ /Pages หากเงื่อนไขสามข้อนี้ข้อใดข้อหนึ่งล้มเหลว ก็ให้เปลี่ยนไปใช้การสแกนแบบเส้นตรง (linear scan) การสแกนจะช้ากว่าการเดินในโครงสร้างต้นไม้สำหรับเอกสารขนาดใหญ่ เพราะมันต้องอ่านส่วนหัวของออบเจ็กต์ทุกตัวแทนที่จะตามเส้นทางที่สมดุล แต่มันก็ใช้งานได้ และสำหรับไฟล์ที่ผิดรูปแบบมาตั้งแต่ต้น ความถูกต้องก็ย่อมสำคัญกว่าความเร็ว
มีกรณีขอบเขตหนึ่งที่การสแกนแบบเส้นตรงแก้ปัญหาไม่ได้โดยอัตโนมัติ: การจัดเรียงหน้า เมื่อไม่มีอาร์เรย์ /Kids มากำหนดลำดับ ลำดับที่ "ถูกต้อง" จึงไม่ได้ถูกนิยามไว้ในข้อกำหนด ลำดับตามหมายเลขออบเจ็กต์คือค่าเริ่มต้นที่เน้นการใช้งานจริง หากไฟล์นั้นสำคัญพอที่จะต้องประมวลผลอย่างระมัดระวัง การตรวจสอบว่าออบเจ็กต์ Page มี /StructParents ที่ชัดเจน หรือมีการอ้างอิงคำอธิบายประกอบที่บอกเป็นนัยถึงลำดับการอ่าน ก็คุ้มค่าที่จะลงแรงเพิ่ม
ผลกระทบต่อเครื่องสร้าง PDF
สำหรับคนที่เขียนโปรแกรมสร้าง PDF แทนที่จะเป็นพาร์สเซอร์ บทเรียนนั้นแคบมาก: จะต้องสร้างราก Pages เสมอก่อนที่จะปิดไฟล์ Catalog ที่ไม่มี /Pages นั้นไม่ใช่ PDF ที่ถูกต้องไม่ว่าจะอิงจากข้อกำหนดฉบับแก้ไขใดก็ตาม เครื่องสร้างที่สร้างออบเจ็กต์หน้าไปเรื่อยๆ และประกอบเป็นโครงสร้างต้นไม้ตอนจบ (แนวทางที่โปรแกรมเขียนสตรีมส่วนใหญ่ใช้) นั้นใช้ได้ตราบใดที่ขั้นตอนการจบทำงานได้จริง โหมดความล้มเหลวที่พบบ่อยคือ exception หรือการรีเทิร์นก่อนกำหนดซึ่งทำให้การเขียนยุติลงก่อนที่ trailer จะสมบูรณ์ ทิ้งไฟล์เอาไว้ซึ่งบางโปรแกรมดูก็เปิดได้ (พวกที่มีฮิวริสติกส์กู้คืน) และบางโปรแกรมดูก็เปิดไม่ได้ (พวกที่ไม่มี)
PDF/A และ PDF/UA บังคับใช้ข้อจำกัดเพิ่มเติมกับโครงสร้างต้นไม้หน้าเว็บนอกเหนือจากที่ข้อกำหนดพื้นฐานระบุ แต่ก็ไม่มีข้อใดที่ผ่อนปรนความต้องการของ /Pages ตัวตรวจสอบความถูกต้องที่เช็คความสอดคล้องตาม ISO 19005 หรือ ISO 14289 จะจับความผิดปกติเรื่องดิกชันนารี Pages ที่หายไปได้ในฐานะการละเมิดข้อกำหนดพื้นฐาน ก่อนที่มันจะไปถึงกฎเฉพาะของโปรไฟล์เสียอีก