Bài viết kỹ thuật

Giải thích về Siêu dữ liệu, Outline và Annotation trong PDF

Lược bỏ các mô tả trang và bạn sẽ còn lại một lớp cấu trúc mỏng mà không ai in ra nhưng mọi trình đọc, bộ lập chỉ mục và hệ thống lưu trữ đều phụ thuộc vào. Một đối tượng trang không biết gì về chương mà nó thuộc về, tác giả đã viết nó, hoặc chú thích cuối trang liên kết đến nơi khác. Kiến thức đó nằm ở một cấp độ cao hơn, trong ba cấu trúc được đính kèm vào catalog tài liệu: luồng siêu dữ liệu (metadata), cây outline và các mảng annotation (chú giải) trên mỗi trang. Chúng có chung một đặc điểm khiến chúng dễ bị sai sót. Không có cấu trúc nào mang dấu vết hiển thị trên trang, vì vậy một tệp có thể render hoàn hảo nhưng vẫn thiếu bookmark, mâu thuẫn với trường tác giả của chính nó, hoặc trỏ một liên kết đến đối tượng trang không còn tồn tại

Đây là lớp mà một thư viện PDF bộc lộ dưới dạng thuộc tính tài liệu, API bookmark, và lệnh gọi liên kết hoặc annotation, và là lớp mà trình thu thập tìm kiếm đọc để quyết định tài liệu của bạn nói về điều gì. Mô hình đối tượng bên dưới nó được đề cập trong hướng dẫn về cấu trúc tài liệu PDF. Ở đây trọng tâm hoàn toàn tập trung vào những gì gắn liền với catalog

Cả ba cấu trúc đều đính kèm tại catalog. Một catalog hoàn chỉnh kết nối chúng lại với nhau trông như thế này:

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
   /Outlines 3 0 R
   /Names << /EmbeddedFiles 4 0 R >>
   /Metadata 5 0 R
>>
endobj

Bốn mục nhập, bốn hệ thống con độc lập. /Pages là tài liệu hiển thị; /Outlines là cây bookmark; /Metadata trỏ tới luồng XMP; /Names tiếp cận từ điển tên toàn tài liệu, trong đó nắm giữ các tệp đính kèm nhúng. Mỗi mục là tùy chọn và một trình đọc không tìm thấy mục nào trong số đó vẫn có thể hiển thị các trang. Tính tùy chọn đó chính xác là lý do tại sao lớp điều hướng lại là thứ đầu tiên bị hỏng khi tệp được chỉnh sửa bằng các công cụ chỉ hiểu về trang

Hai kho lưu trữ siêu dữ liệu không đồng nhất

PDF chứa siêu dữ liệu tài liệu ở hai nơi cùng một lúc và rắc rối bắt đầu khi chúng nói những điều khác nhau. Cơ chế gốc là từ điển thông tin tài liệu, được tham chiếu bởi /Info trong trailer: một tập hợp phẳng các cặp khóa-giá trị cho /Title, /Author, /Subject, /Keywords, /Creator, /Producer, và hai ngày tháng. Nó đơn giản và mọi trình xem đều đọc nó. PDF 2.0 phản đối hầu hết nó để ủng hộ cơ chế thứ hai, luồng siêu dữ liệu XMP

XMP là một tài liệu XML độc lập, được viết bằng RDF, được lưu trữ dưới dạng luồng mà catalog tiếp cận thông qua /Metadata và được đánh dấu /Type /Metadata /Subtype /XML. Không giống như từ điển Info bị chôn vùi bên trong cấu trúc đối tượng PDF, một gói XMP được thiết kế để trích xuất và phân tích riêng bởi các công cụ không biết gì về PDF. Đây là một gói đại diện:

5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
      <dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
      <dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
      <xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
      <xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
      <pdf:Producer>losLab PDF Library</pdf:Producer>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj

Ba chi tiết trong khối đó quyết định liệu siêu dữ liệu có sống sót khi tiếp xúc với các công cụ thực tế hay không. Các lệnh xử lý xpacket không phải là trang trí: chúng đóng khung gói để trình trích xuất có thể tìm thấy nó bên trong một luồng byte lớn hơn, và một bộ ghi bỏ qua phần đóng <?xpacket end="w"?> sẽ tạo ra một tệp mở tốt nhưng gặp trục trặc với các trình xác thực nghiêm ngặt. Kiểu dữ liệu thuộc tính cũng quan trọng. dc:title là một lựa chọn thay thế ngôn ngữ được bọc trong rdf:Alt, trong khi dc:creator là một danh sách có thứ tự và cần rdf:Seq; phát ra một trong hai dạng một node văn bản trần là lỗi XMP phổ biến nhất, được hầu hết các trình xem chấp nhận cho đến khi gặp trình xem không cho phép điều đó. Tiền tố không gian tên (namespace) là quy ước, nhưng các URI mà chúng liên kết đến là quy phạm: một trình phân tích phân tích dựa trên URI, không phải tiền tố

Quy tắc cứng với hai kho lưu trữ là chúng phải đồng nhất. Nếu /Info nói tác giả là người này và dc:creator nêu tên người khác, bạn đã xuất xưởng một tài liệu trả lời cùng một câu hỏi theo hai cách, và câu trả lời nào thắng phụ thuộc vào việc công cụ tiêu thụ đọc trường nào. Một thư viện thường ghi cả hai cho bạn, nhưng khoảnh khắc bạn chỉnh sửa một bằng tay, hoặc hợp nhất các tệp từ các bộ tạo khác nhau, cả hai sẽ trôi dạt ra xa. Hãy coi từ điển Info như khả năng tương thích kế thừa và XMP là nguồn sự thật, và tạo lại cả hai từ một bộ giá trị thay vì vá chúng độc lập. Đối với PDF/A, điều này trở thành một yêu cầu tuân thủ: ISO 19005 bắt buộc XMP và cấm bất kỳ thuộc tính Info nào mâu thuẫn với đối tác XMP của nó

Cây outline đằng sau bảng bookmark

Những gì một trình xem hiển thị như bảng bookmark, trong tệp, là một cây từ điển được liên kết đôi gọi là outline tài liệu. Catalog trỏ đến từ điển outline gốc thông qua /Outlines; từ điển gốc trỏ đến các mục nhập mức cao nhất đầu tiên và cuối cùng của nó; và mọi mục nhập đều được liên kết với các mục lân cận và cha mẹ của nó. Không có mảng bookmark ở bất cứ đâu. Toàn bộ cấu trúc được tái tạo lại bằng cách theo dõi các tham chiếu, đó chính xác là lý do tại sao một liên kết bị hỏng có thể làm cho toàn bộ một nhánh biến mất khỏi bảng mà không có bất kỳ lỗi nào

8 0 obj                                    % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj                                    % top-level: a chapter
<< /Title (Chapter 1: Results)
   /Parent 8 0 R /Count 2
   /First 12 0 R /Last 15 0 R >>
endobj
12 0 obj                                   % first child
<< /Title (Introduction)
   /Parent 9 0 R /Next 15 0 R
   /Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj                                   % second child, last sibling
<< /Title (Methodology)
   /Parent 9 0 R /Prev 12 0 R
   /Dest [3 0 R /Fit] >>
endobj

Đọc các liên kết và các yếu tố bất biến trở nên rõ ràng. Mọi mục nhập đều trỏ ngược về /Parent của nó. Các anh chị em tạo thành một chuỗi thông qua /Prev/Next, mục nhập đầu tiên bỏ qua /Prev và mục nhập cuối cùng bỏ qua /Next. Một cha mẹ đặt tên cho con đầu lòng và con út của nó thông qua /First/Last, và các con ở giữa chỉ có thể tiếp cận được bằng cách đi qua chuỗi anh chị em. Hiểu sai một chỗ và sự thất bại sẽ im lặng: một /Next cũ kỹ làm cụt một chương, một cha mẹ có /Last không kết thúc chuỗi để lại các mục nhập mồ côi, và trình xem hiển thị bất cứ thứ gì nó có thể tiếp cận

Trường /Count mang một mẩu trạng thái làm mọi người ngạc nhiên. Trên gốc và trên bất kỳ mục nhập mở rộng nào, nó chứa số lượng con cháu hiện đang hiển thị; trên một mục nhập bị thu gọn, nó là một số âm có độ lớn bằng số lượng con cháu sẽ xuất hiện khi mở rộng. Vì vậy, /Count không phải là một sự kiện cấu trúc cố định về cây, nó là trạng thái đã lưu mở hoặc đóng của bảng, và một bộ tạo mã cứng nó thành tổng số dương sẽ mở lại mọi nhánh mà tác giả có ý định đóng lại

Mỗi mục nhập giành được vị trí của mình bằng cách trỏ đến một nơi nào đó. /Title là những gì bảng hiển thị; /Dest là nơi một cú nhấp chuột hạ cánh. Đích đến có thể ở ngay trong mục nhập, như trên, hoặc một cái tên giải quyết qua từ điển tên của tài liệu, đây là lựa chọn tốt hơn khi nhiều bookmark và liên kết nhắm mục tiêu vào cùng một điểm, vì bạn cố định một mục tiêu di chuyển ở một vị trí. Một thư viện thường ẩn cây này đằng sau một xử lý thư mục outline gốc và các phương thức thêm mục con; trong HotPDF, tài liệu bộc lộ một OutlineRoot của kiểu THPDFDocOutlineObject và xâu chuỗi các liên kết /Prev, /Next, /Parent/Count cho bạn khi bạn thêm các mục. Điều đó đáng để tận dụng, bởi vì duy trì các bất biến đó bằng tay qua các bản chỉnh sửa là nơi outline thường bị phá vỡ

Đích đến: ngữ pháp về nơi một cú nhấp chuột đi tới

Cả bookmark và annotation liên kết đều trỏ đến đích đến, và đích đến là hơn một số trang. Đó là một mảng đặt tên một đối tượng trang và sau đó xác định, thông qua một động từ trong ô thứ hai, cách trình xem nên định hình nó. /XYZ là phổ biến nhất và bị lạm dụng nhiều nhất, có dạng [page /XYZ left top zoom]. Ba toán hạng của nó độc lập và bất kỳ cái nào cũng có thể là null với ý nghĩa "để lại cái này như người đọc đã có." Vì vậy, [page /XYZ null null null] nhảy tới trang mà không chạm vào vị trí cuộn hoặc thu phóng, thường là những gì bạn muốn từ liên kết "đi tới trang". Các con số nằm trong không gian người dùng mặc định, được đo từ dưới cùng bên trái với y tăng lên trên, cùng hệ tọa độ mà nội dung trang sử dụng. Các tác giả đến từ bố cục màn hình thường phản xạ đo từ trên xuống và đưa người đọc đến nhầm cuối trang

Họ /Fit đánh đổi định vị chính xác để lấy khả năng phục hồi. [page /Fit] thu phóng toàn bộ trang vào cửa sổ, [page /FitH top] khớp với chiều rộng trang có một cạnh trên cho trước và [page /FitR l b r t] thu phóng một hình chữ nhật để lấp đầy khung nhìn. Vì các chỉ số này tính toán quy mô từ hình học trang thay vì tọa độ cố định, một đích đến /Fit vẫn thực hiện điều hợp lý sau khi trang được thay đổi kích thước, trong khi một đích đến /XYZ với bộ thu phóng cố định có thể để người đọc nhìn chằm chằm vào lề. Đối với mục lục, /FitH với tọa độ trên của phần già đi tốt hơn so với /XYZ với bộ thu phóng suy đoán

Annotation: mọi thứ tương tác không phải là nội dung trang

Annotation là một đối tượng phủ lên trang mà không thuộc một phần của luồng nội dung của nó. Liên kết, ghi chú dính (sticky note), làm nổi bật, widget biểu mẫu, biểu tượng tệp đính kèm, con dấu: tất cả đều là annotation, được liệt kê trong mảng /Annots của trang mà chúng nằm trên. Việc xóa annotation khỏi mảng đó sẽ xóa nó khỏi trang ngay cả khi nội dung cơ sở không bị chạm vào. Đó chính là quan điểm: annotation là một lớp chỉnh sửa, tách biệt khỏi các dấu hiệu mà chúng nằm trên

Mỗi annotation dùng chung một cột sống nhỏ. /Subtype đặt tên loại, /Rect cung cấp hộp giới hạn của nó theo tọa độ trang và /Contents chứa văn bản nhân đôi vai trò là mô tả có thể tiếp cận. Annotation liên kết là trường hợp đáng để nghiên cứu, bởi vì nó có hai hình thức: một đích đến trần và một hành động

12 0 obj                                    % link to a destination
<< /Type /Annot /Subtype /Link
   /Rect [100 200 300 250]
   /Border [0 0 0]
   /Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj                                    % link that runs an action
<< /Type /Annot /Subtype /Link
   /Rect [50 50 200 100]
   /Border [0 0 0]
   /A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj

/Rect là một điểm truy cập (hotspot); nhấp vào bên trong nó gửi người đọc đến đích đến, sử dụng lại cùng ngữ pháp mà outline sử dụng. /Border [0 0 0] đang làm công việc thực sự, triệt tiêu hình chữ nhật mặc định xấu xí mà các trình xem vẽ xung quanh liên kết. Hình thức thứ hai hoán đổi /Dest trần cho một hành động /A, có kiểu con /S chọn hành vi: /GoTo trong tệp này, /GoToR cho một tệp khác, /URI cho một địa chỉ web, /Launch để chạy một chương trình bên ngoài. Cái cuối cùng đáng bị nghi ngờ. Một /Launch bắt đầu tệp thực thi là hành vi biến PDF thành vector phần mềm độc hại, vì vậy các trình xem tuân thủ sẽ chặn nó hoặc nhắc nhở lớn và liên kết không thành công đối với hầu hết người đọc. Hãy tìm tới /URI/GoTo và để /Launch yên

Annotation đánh dấu (markup) như làm nổi bật và ghi chú dính, và annotation hình học như /Square, thêm một nếp nhăn: giao diện trên màn hình của chúng không bị ngụ ý bởi loại của chúng. Một trình xem hiển thị phiên bản riêng của nó trừ khi bạn ghim giao diện bằng một luồng giao diện, mục nhập /AP, liên kết đến biểu mẫu XObject giữ các toán tử vẽ. Bỏ qua nó và cùng một phần làm nổi bật có thể trông khác nhau ở hai trình xem hoặc trước và sau một chuyến đi vòng của biên tập viên. Đối với bất cứ thứ gì có giao diện chính xác là một phần của tài liệu, hãy cung cấp /AP. Các tệp đính kèm, ngẫu nhiên, tái sử dụng cùng bộ máy này: một luồng tệp nhúng và từ điển đặc điểm kỹ thuật tệp, được bộc lộ dưới dạng annotation /FileAttachment hoặc thông qua cây tên /EmbeddedFiles thuộc /Names của catalog

Nơi lớp này bị hỏng và cách phát hiện nó

Lỗi lặp lại xuyên suốt tất cả điều này là tham chiếu lơ lửng. Bookmark ngừng xuất hiện khi catalog không có mục nhập /Outlines hoặc chuỗi anh chị em bị đứt gãy giữa cây; siêu dữ liệu bị bỏ qua khi luồng XMP thiếu nhãn /Type /Metadata /Subtype /XML hoặc vỏ bọc xpacket bị sai định dạng. Trong mọi trường hợp, nội dung trang đều ổn, vì vậy việc mở thông thường trông có vẻ đúng và lỗi chỉ nổi lên trong bảng mà không ai kiểm tra

Hai thói quen giá rẻ nắm bắt hầu hết điều đó. Mở tệp đã hoàn thành trong một trình xem thực tế và nhấp qua bảng bookmark và một mẫu các liên kết, thực hiện biểu đồ tham chiếu theo cách người đọc sẽ làm. Sau đó đọc siêu dữ liệu trở lại bằng một công cụ riêng biệt và xác nhận từ điển Info và XMP đồng nhất, một sự không đồng nhất mà không số lần nhấp nào có thể tiết lộ. Việc tạo ra lớp này thông qua một thư viện sở hữu hệ thống kế toán liên kết và hầu hết những cái bẫy này sẽ không bao giờ mở ra. HotPDF Component cho Delphi và C++Builder làm lộ ra các cấu trúc outline, annotation và siêu dữ liệu thông qua các API mức độ tài liệu, để bạn mô tả hệ thống phân cấp bookmark và các liên kết và để nó tự kết nối các tham chiếu. Đối với mô hình đối tượng nơi các cấu trúc này đính kèm vào, tổng quan kỹ thuật về cấu trúc tệp PDF bao gồm catalog và bảng tham chiếu chéo (cross-reference) mà chúng phụ thuộc vào