Bài viết kỹ thuật

Thứ Tự Trang PDF: Cách Cây Trang Kiểm Soát Trình Tự Trang

Thứ tự trang không nằm trong số object hay xref; nó nằm trong cây /Pages. Khi duyệt Kids phải xử lý cả subtree lồng nhau và thuộc tính kế thừa, còn xref chỉ giúp tìm object. Bỏ qua traversal sẽ làm thứ tự hiển thị sai

Đối tượng số 1 không phải là trang 1. Sự thật đơn giản đó làm hỏng nhiều code xử lý PDF hơn bất kỳ khía cạnh nào khác của định dạng, và việc hiểu tại sao đòi hỏi nhìn qua những gì trình xem hiển thị và vào đồ thị đối tượng mà trình xem thực sự đọc

Tệp PDF là tập hợp các đối tượng gián tiếp được đánh số. Các trang nằm trong số những đối tượng đó, nhưng trình tự hiển thị của chúng không liên quan gì đến vị trí của chúng trong tệp hay số chúng mang. Thứ tự hiển thị được xác định hoàn toàn bởi cây /Pages, một cấu trúc liên kết có gốc tại document catalog. Nếu bạn bỏ qua cây và quét các đối tượng theo thứ tự số, bạn sẽ lắp ráp các trang theo thứ tự sai với một tỷ lệ đáng kể các tệp trong thực tế

Cây trang: thứ thực sự xác định thứ tự

Mọi PDF đều bắt đầu với document catalog (ISO 32000-2 §7.7.2). Catalog chứa mục /Pages trỏ đến nút gốc của cây trang. Nút gốc đó là từ điển với /Type /Pages, mảng /Kids gồm các tham chiếu gián tiếp, và /Count cho tổng số trang lá bên dưới nó. Thứ tự hiển thị là duyệt sâu-trước từ-trái-sang-phải của cây đó, hoàn toàn và không có gì khác

Tệp ba trang tối thiểu làm rõ điều này:

%PDF-1.7

1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

2 0 obj
<< /Type /Pages /Kids [20 0 R  4 0 R  9 0 R] /Count 3 >>
endobj

% Đối tượng 4 được lưu trữ thứ ba trong tệp nhưng là trang 2 theo thứ tự hiển thị
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 5 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

% Đối tượng 9 được lưu trữ thứ tư nhưng là trang 3
9 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 10 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

% Đối tượng 20 được lưu trữ cuối cùng nhưng là trang 1; Kids[0] quyết định, không phải số hiệu đối tượng
20 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 21 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

Mảng /Kids đọc [20 0 R 4 0 R 9 0 R], nên đối tượng 20 là trang 1, đối tượng 4 là trang 2, và đối tượng 9 là trang 3. Đánh số đối tượng không liên quan. Bất kỳ code nào lặp qua các đối tượng theo thứ tự số và thu thập những cái có /Type /Page sẽ tạo ra trình tự sai trên tệp này

Tại sao các bộ tạo tạo ra bố cục không tuần tự? Nhiều lý do. Thư viện cấp phát số đối tượng trước cho tất cả các trang trước khi ghi nội dung của chúng sẽ đánh số theo thứ tự tạo, rồi ghi byte thực tế theo bất kỳ thứ tự nào phù hợp với bộ nối tiếp. Công cụ ghép nối các tài liệu lại với nhau đánh số lại các đối tượng từ mỗi tài liệu nguồn để tránh xung đột; các đối tượng trang được đánh số lại kết thúc rải rác khắp bảng đối tượng kết hợp trong khi mảng /Kids gốc mới giữ trình tự hiển thị đúng. Các cập nhật gia tăng nối thêm các đối tượng mới ở cuối tệp với số mới, nên trang được thêm dưới dạng bản sửa đổi nằm gần cuối luồng byte dù nó thuộc vị trí 1 của thứ tự hiển thị

Cây phẳng và cây con lồng nhau

Spec cho phép hai hình dạng cho cây trang. Các bộ tạo đơn giản tạo ra cấu trúc phẳng: một nút /Pages gốc mà mảng /Kids của nó chỉ chứa các đối tượng lá /Page. Điều đó dễ duyệt: một cấp sâu, một lần duyệt

Tài liệu lớn thường sử dụng cây cân bằng thay thế. Mảng /Kids của nút /Pages gốc chứa các nút /Pages trung gian, mỗi nút lần lượt giữ mảng /Kids của riêng nó. /Count trên mỗi nút trung gian báo cáo tổng số trang lá trong cây con của nó, nên trình xem có thể bỏ qua toàn bộ cây con khi nhảy đến trang theo chỉ mục mà không cần phân tích mọi đối tượng. Tài liệu 1.000 trang được cấu trúc như cây cân bằng với 10 trang mỗi nút lá có thể định vị trang 750 bằng tìm kiếm nhị phân qua ba hoặc bốn tra cứu từ điển thay vì quét 750 mục /Kids

Hệ quả cho code xử lý: bạn không thể giả định cấp đầu tiên của /Kids chứa các đối tượng /Page. Mỗi con phải được kiểm tra. Nếu /Type của nó là /Pages, đệ quy vào nó. Nếu /Type của nó là /Page, nó là lá. Dừng ở cấp đầu tiên lặng lẽ bỏ toàn bộ cây con trên bất kỳ tài liệu nào bộ tạo chọn lồng nhau

Thuộc tính trang kế thừa

Cây trang cũng mang cơ chế chia sẻ tài nguyên. Một số thuộc tính trang nhất định: /MediaBox, /CropBox, /Resources, và /Rotate có thể kế thừa (ISO 32000-2 §7.7.3.4). Nếu từ điển /Page bỏ qua một trong số chúng, trình đọc đi lên chuỗi /Parent cho đến khi tìm thấy thuộc tính hoặc đến gốc. Đặt từ điển phông chữ chia sẻ trong nút /Pages gốc thay vì sao chép nó vào mọi trang lá có thể giảm kích thước tệp đáng kể cho tài liệu sử dụng cùng kiểu chữ xuyên suốt

Quy tắc kế thừa tạo ra sự tinh tế cho code đọc thuộc tính trang. Đọc /MediaBox trực tiếp từ đối tượng /Page và coi thiếu khóa là lỗi là sai; khóa có thể đơn giản là được kế thừa. Code giải quyết đúng hình học trang phải theo chuỗi parent. Nó cũng cần guard vòng lặp: tệp bị hỏng có thể có tham chiếu /Parent trỏ ngược về nút đã được truy cập, điều này sẽ lặp mãi mãi mà không có kiểm tra đối tượng đã thăm

Bảng xref và cross-reference stream

Tra cứu đối tượng gián tiếp đi qua bảng tham chiếu chéo (hoặc phiên kế nhiệm của nó, cross-reference stream được giới thiệu trong PDF 1.5). Xref ánh xạ mỗi số đối tượng đến độ lệch byte trong tệp. Trình đọc tuân thủ dùng xref để nhảy trực tiếp đến bất kỳ đối tượng nào; nó không quét tệp tuần tự. Thiết kế truy cập ngẫu nhiên đó là thứ làm cho nhảy trang nhanh có thể: trình xem đọc catalog, giải quyết tham chiếu /Pages qua xref, đọc nút /Pages gốc, giải quyết mục /Kids, và cứ thế, chỉ chạm vào các đối tượng nó cần

Các cập nhật gia tăng thêm phần xref mới ở cuối tệp với trailer xâu chuỗi ngược về phần trước đó. Đối tượng được cập nhật trong một bản sửa đổi có mục mới trong phần xref được nối thêm; các byte gốc ở nguyên chỗ nhưng bị thay thế. Đây là cách PDF được ký kỹ thuật số vẫn có thể xác minh ngay cả khi các bản sửa đổi chú thích hoặc điền biểu mẫu được thêm vào: phạm vi byte đã ký không bao giờ bị chạm, và nội dung mới nằm trong phần được nối thêm. Cây trang cũng có thể được cập nhật, nên thêm hoặc xóa trang trong một bản sửa đổi tạo ra /Pages gốc mới với mảng /Kids đã sửa đổi, trong khi đối tượng gốc cũ vẫn chiếm vị trí ban đầu của nó trong tệp

Điều gì xảy ra khi không duyệt cây

Chế độ lỗi cho các cách tiếp cận quét đối tượng là yên tĩnh. Tài liệu đầu ra trông hợp lý: nó có đúng số trang và mỗi trang chứa nội dung có thể nhận ra. Thứ tự chỉ sai, và sai theo cách phụ thuộc vào bộ tạo, số lần sửa đổi, và liệu có trang nào được ghép từ nguồn bên ngoài không. Kho kiểm tra tệp được tạo bởi một công cụ duy nhất có thể vượt qua hoàn toàn; tệp từ công cụ khác hoặc quy trình ghép sẽ thất bại. Sự không nhất quán đó là lý do tại sao các sửa chữa heuristic không bao giờ giữ được

Các tệp cập nhật gia tăng đặc biệt dễ bị ảnh hưởng vì các trang được thêm hoặc sắp xếp lại trong các bản sửa đổi sau mang số đối tượng cao trong khi thứ tự hiển thị được kiểm soát bởi mảng /Kids đã cập nhật. Lần quét xử lý các đối tượng theo thứ tự số sẽ đặt những trang được đánh số muộn đó ở cuối bất kể cây nói chúng thuộc đâu

Cách sửa không phức tạp. Bắt đầu tại catalog, giải quyết tham chiếu /Pages, duyệt mảng /Kids đệ quy, và xuất các lá theo thứ tự bạn gặp chúng. Đó là thứ tự hiển thị theo định nghĩa, bất kể số đối tượng, độ lệch byte, hay cấu trúc tệp. Hầu hết các thư viện PDF trưởng thành cung cấp số trang và bộ truy cập trang được lập chỉ mục đã làm điều này đúng; rủi ro nằm ở code bỏ qua mô hình trang của thư viện và chạm trực tiếp vào lớp đối tượng

Một bất thường cấu trúc đáng xử lý tường minh: giá trị /Count trên nút /Pages trung gian có thể sai trong các tệp bị lỗi. Tin tưởng /Count để kiểm tra giới hạn và sau đó dừng lại trước khi duyệt đầy đủ sẽ lặng lẽ bỏ sót các trang khi số đếm thấp hơn thực tế. Sử dụng /Count chỉ như gợi ý hiệu suất để cấp phát trước dung lượng hoặc tìm kiếm nhị phân, và lấy số đếm thực tế từ duyệt cây là mẫu an toàn hơn cho tài liệu quan trọng

Bài Viết Tiếp Theo