توصیفات صفحه را کنار بگذارید و شما با لایه نازکی از ساختار باقی میمانید که هیچکس آن را چاپ نمیکند اما هر خواننده، نمایهساز (indexer) و سیستم بایگانی به آن وابسته است. یک شیء صفحه هیچچیز درباره فصلی که به آن تعلق دارد، نویسندهای که آن را نوشته، یا پاورقی که به جای دیگری لینک میدهد نمیداند. آن دانش در یک سطح بالاتر، در سه ساختار متصل به کاتالوگ سند زندگی میکند: جریانهای فراداده (metadata streams)، درخت طرحنما (outline tree) و آرایههای حاشیهنویسی به ازای هر صفحه (per-page annotation arrays). آنها دارای ویژگی مشترکی هستند که باعث میشود به راحتی اشتباه شوند. هیچکدام علامتهای قابلرویتی را روی صفحه حمل نمیکنند، بنابراین یک فایل میتواند به طور کامل رندر شود اما همچنان فاقد نشانکهای خود باشد، با فیلد نویسنده خود در تضاد باشد، یا لینکی را به شیء صفحهای اشاره دهد که دیگر وجود ندارد
این لایهای است که یک کتابخانه PDF به عنوان ویژگیهای سند (document properties)، APIهای نشانک، و فراخوانیهای لینک یا حاشیهنویسی در معرض دید قرار میدهد، و لایهای است که یک خزنده جستجو (search crawler) آن را میخواند تا تصمیم بگیرد سند شما درباره چیست. مدل شیء در زیر آن در بررسی اجمالی ساختار سند PDF پوشش داده شده است. در اینجا تمرکز دقیقاً بر روی چیزهایی است که از کاتالوگ آویزان هستند
هر سه ساختار در کاتالوگ متصل میشوند. یک کاتالوگ کامل که آنها را به هم وصل میکند به این شکل است:
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
/Names << /EmbeddedFiles 4 0 R >>
/Metadata 5 0 R
>>
endobj
چهار ورودی، چهار زیرسیستم مستقل. /Pages سند قابلرویت است؛ /Outlines درخت نشانک است؛ /Metadata به جریان XMP اشاره میکند؛ /Names به دیکشنری نام در سطح کل سند میرسد که در میان چیزهای دیگر، پیوستهای فایل تعبیهشده را نگه میدارد. هر کدام اختیاری هستند، و خوانندهای که هیچکدام از آنها را پیدا نکند باز هم صفحات را نشان میدهد. آن اختیاری بودن دقیقاً همان دلیلی است که لایه ناوبری اولین چیزی است که هنگام ویرایش یک فایل توسط ابزارهایی که فقط صفحات را درک میکنند، فاسد میشود
دو مخزن فراداده که با هم اختلاف نظر دارند
PDF فرادادههای سند را همزمان در دو مکان حمل میکند، و دردسر زمانی آغاز میشود که آنها چیزهای متفاوتی میگویند. مکانیسم اصلی، دیکشنری اطلاعات سند است که توسط /Info در تریلر به آن ارجاع داده میشود: مجموعهای مسطح از جفتهای کلید-مقدار برای /Title، /Author، /Subject، /Keywords، /Creator، /Producer، و آن دو تاریخ. این مکانیسم ساده است و هر نمایشگری آن را میخواند. فرمت PDF 2.0 اکثر بخشهای آن را به نفع مکانیسم دوم، یعنی جریان فراداده XMP، منسوخ میکند
مکانیسم XMP یک سند XML مستقل است، که در RDF نوشته شده، به عنوان جریانی ذخیره میشود که کاتالوگ از طریق /Metadata به آن میرسد و با /Type /Metadata /Subtype /XML علامتگذاری شده است. بر خلاف دیکشنری Info که در داخل ساختار شیء PDF مدفون است، یک بسته XMP به گونهای طراحی شده است که توسط ابزارهایی که هیچچیز درباره PDF نمیدانند، به تنهایی استخراج و تجزیه شود. در اینجا یک بسته نمونه آورده شده است:
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
سه جزئیات در آن بلوک تصمیم میگیرند که آیا فراداده در تماس با ابزارهای واقعی زنده میماند یا خیر. دستورالعملهای پردازش xpacket تزئینی نیستند: آنها بسته را کادربندی میکنند تا یک استخراجکننده (extractor) بتواند آن را در داخل یک جریان بایت بزرگتر پیدا کند، و نویسندهای که <?xpacket end="w"?> پایانی را از قلم میاندازد، فایلی تولید میکند که به خوبی باز میشود اما اعتبارسنجهای سختگیر را با مشکل مواجه میسازد. انواع داده ویژگیها نیز مهم هستند. ویژگی dc:title یک جایگزین زبانی است که در rdf:Alt پیچیده شده است، در حالی که dc:creator یک لیست مرتب است و rdf:Seq را میگیرد؛ منتشر کردن هر یک به عنوان یک گره متنی برهنه رایجترین اشتباه XMP است، که توسط بیشتر نمایشگرها تحمل میشود درست تا زمانی که با نمایشگری مواجه شوید که آن را نمیپذیرد. پیشوندهای فضای نام مرسوم (conventional) هستند، اما URIهایی که به آنها مقید میشوند هنجاری (normative) هستند: یک تجزیهکننده کلید را از URI میگیرد، نه از پیشوند
قاعده سخت در مورد دو مخزن این است که آنها باید با هم موافق باشند. اگر /Info بگوید نویسنده یک شخص است و dc:creator نام دیگری را بیاورد، شما سندی را ارسال کردهاید که به یک سؤال به دو روش پاسخ میدهد، و اینکه کدام پاسخ برنده میشود بستگی به این دارد که ابزار مصرفکننده کدام فیلد را میخواند. یک کتابخانه معمولاً هر دو را برای شما مینویسد، اما به محض اینکه یکی را با دست ویرایش کنید، یا فایلها را از تولیدکنندههای مختلف ادغام نمایید، این دو از هم فاصله میگیرند. با دیکشنری Info به عنوان سازگاری موروثی (legacy) و با XMP به عنوان منبع حقیقت برخورد کنید، و به جای وصله زدن مستقل آنها، هر دو را از یک مجموعه ارزشها مجدداً تولید کنید. برای PDF/A این یک الزام انطباق (conformance requirement) میشود: ISO 19005 استفاده از XMP را الزامی میکند و هر ویژگی Info که با همتای XMP خود در تضاد باشد را ممنوع میسازد
درخت طرحنما در پشت پنل نشانک
آنچه یک نمایشگر به عنوان پنل نشانکها (bookmarks panel) نشان میدهد، در فایل، یک درخت دارای پیوند دوطرفه (doubly linked) از دیکشنریها است که طرحنمای سند (document outline) نامیده میشود. کاتالوگ از طریق /Outlines به دیکشنری طرحنمای ریشه اشاره میکند؛ ریشه به اولین و آخرین اقلام سطح بالای خود اشاره مینماید؛ و هر قلمی به همسایگان خود و والد خود رشته شده (threaded) است. در هیچکجا آرایهای از نشانکها وجود ندارد. کل این ساختار با دنبال کردن ارجاعات بازسازی میشود، که دقیقاً به همین دلیل است که یک لینک شکسته میتواند باعث شود یک شاخه کامل بدون هیچ خطایی از پنل ناپدید شود
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
لینکها را بخوانید و ناورداها (invariants) آشکار میشوند. هر قلمی به /Parent خود اشاره میکند. خواهر و برادرها از طریق /Prev و /Next یک زنجیره را تشکیل میدهند، به طوری که اولین قلم /Prev را حذف میکند و آخرین قلم /Next را. یک والد از طریق /First و /Last اولین و آخرین فرزندان خود را نام میبرد، و فرزندان بین آنها تنها با پیمایش زنجیره خواهر و برادر قابل دسترسی هستند. یکی را اشتباه انجام دهید و خرابی بیسروصدا رخ میدهد: یک /Next کهنه یک فصل را کوتاه میکند، والدی که /Last آن زنجیره را خاتمه نمیدهد، اقلام را یتیم (orphaned) رها میسازد، و نمایشگر هر آنچه را که بتواند به آن برسد رندر میکند
فیلد /Count قطعهای از وضعیت را حمل میکند که افراد را شگفتزده میسازد. در ریشه و روی هر قلم بازشدهای، تعداد فرزندان (descendants) در حال حاضر قابلرویت را نگه میدارد؛ در یک قلم جمعشده، یک عدد منفی است که قدر مطلق آن نشان میدهد چه تعداد فرزند در صورت باز شدن ظاهر خواهند شد. بنابراین /Count یک واقعیت ساختاری ثابت در مورد درخت نیست، بلکه وضعیت باز یا بسته ذخیرهشده پنل است، و تولیدکنندهای که آن را به صورت سختکد شده به عنوان یک مجموع مثبت در میآورد، هر شاخهای را که نویسنده قصد داشت بسته بماند مجدداً باز میکند
هر قلم جایگاه خود را با اشاره به جایی به دست میآورد. مقدار /Title چیزی است که پنل نشان میدهد؛ /Dest جایی است که یک کلیک در آن فرود میآید. یک مقصد (destination) میتواند به صورت درونخطی در قلم باشد، همانطور که در بالا نشان داده شده است، یا نامی باشد که از طریق دیکشنری نام سند حلوفصل میشود، که این انتخاب بهتری است وقتی تعداد زیادی نشانک و لینک اهداف یکسانی را هدف قرار میدهند، زیرا شما یک هدف جابجاشده را در یک مکان رفع میکنید. یک کتابخانه به طور کلی این درخت را در پشت یک دستگیره ریشه طرحنما (outline-root) و متدهایی که ورودیهای فرزند را اضافه میکنند پنهان میسازد؛ در HotPDF، سند یک OutlineRoot از نوع THPDFDocOutlineObject را در معرض دید قرار میدهد و همانطور که شما اقلام را الحاق میکنید، پیوندهای /Prev، /Next، /Parent و /Count را برای شما رشتهبندی میکند. این موضوع ارزش استفاده را دارد، زیرا حفظ این ناورداها به صورت دستی در بین ویرایشها، همانجایی است که طرحنماها میشکنند
مقصدها: گرامر محل فرود یک کلیک
هم نشانکها و هم حاشیهنویسیهای لینک به مقصدها اشاره میکنند، و یک مقصد چیزی بیشتر از یک شماره صفحه است. این یک آرایه است که یک شیء صفحه را نام میبرد و سپس، از طریق یک فعل در شکاف دوم، مشخص میکند که نمایشگر چگونه باید آن را قاب (frame) کند. رایجترین و بیشترین مورد سوءاستفاده /XYZ است، به شکل [page /XYZ left top zoom]. سه عملوند آن مستقل هستند، و هر کدام ممکن است null باشند به این معنی که "این را همانطور که خواننده داشت رها کن." بنابراین [page /XYZ null null null] بدون دست زدن به موقعیت اسکرول یا زوم به صفحه میپرد، که معمولاً همان چیزی است که شما از یک لینک "برو به صفحه" میخواهید. اعداد در فضای پیشفرض کاربر هستند که از پایین-چپ اندازهگیری میشوند و y به سمت بالا افزایش مییابد، که همان سیستم مختصاتی است که محتوای صفحه استفاده میکند. نویسندگانی که از طراحی صفحه نمایش میآیند به طور انعکاسی از بالا اندازهگیری میکنند و خواننده را به انتهای اشتباه صفحه میفرستند
خانواده /Fit موقعیتیابی دقیق را با انعطافپذیری معاوضه میکند. [page /Fit] کل صفحه را در پنجره مقیاسبندی میکند، [page /FitH top] عرض صفحه را با یک لبه بالایی مشخص تطبیق میدهد، و [page /FitR l b r t] یک مستطیل را زوم میکند تا نما را پر نماید. از آنجا که این موارد مقیاس را به جای مختصات ثابت از هندسه صفحه محاسبه میکنند، یک مقصد /Fit پس از تغییر اندازه صفحه همچنان کار معقولی انجام میدهد، در حالی که یک مقصد /XYZ با یک زوم پختهشده میتواند خواننده را در حال خیره شدن به حاشیه رها کند. برای فهرست مطالب، استفاده از /FitH با مختصات بالایی بخش به مرور زمان بهتر از /XYZ با یک زوم حدسزدهشده کار میکند
حاشیهنویسیها: هر چیز تعاملی که محتوای صفحه نیست
یک حاشیهنویسی شیئی است که روی صفحه را میپوشاند بدون اینکه بخشی از جریان محتوای آن باشد. لینکها، یادداشتهای چسبناک، هایلایتها، ویجتهای فرم، آیکونهای پیوست فایل، مهرها: همگی حاشیهنویسی هستند که در آرایه /Annots صفحهای که روی آن قرار دارند لیست شدهاند. حذف یک حاشیهنویسی از آن آرایه، آن را از صفحه حذف میکند حتی اگر محتوای زیرین دستنخورده باقی بماند. تمام نکته همین است: حاشیهنویسیها یک لایه ویرایشی هستند که جدا از علامتهایی که روی آنها نشستهاند میباشند
هر حاشیهنویسی در یک ستون فقرات کوچک مشترک است. /Subtype نوع آن را نام میبرد، /Rect جعبه مرزی آن را در مختصات صفحه میدهد، و /Contents متنی را نگه میدارد که در نقش توصیف قابلدسترسی نیز عمل میکند. حاشیهنویسی لینک موردی است که ارزش مطالعه را دارد، زیرا به دو شکل ارائه میشود: یک مقصد برهنه (bare destination)، و یک عمل (action)
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 یک نقطه داغ (hotspot) است؛ کلیک کردن در داخل آن خواننده را به مقصد میفرستد، و از همان گرامری استفاده میکند که طرحنما استفاده مینماید. /Border [0 0 0] کار واقعی انجام میدهد، و مستطیل پیشفرض زشتی را که نمایشگرها در اطراف لینکها میکشند سرکوب میکند. شکل دوم /Dest برهنه را با یک عمل /A معاوضه میکند، که زیرنوع (subtype) /S آن رفتار را انتخاب مینماید: /GoTo در داخل این فایل، /GoToR برای یک فایل دیگر، /URI برای یک آدرس وب، /Launch برای اجرای یک برنامه خارجی. مورد آخر سزاوار ظن (suspicion) است. یک /Launch که یک فایل اجرایی را شروع میکند رفتاری است که PDFها را به یک ناقل بدافزار تبدیل میسازد، بنابراین نمایشگرهای سازگار آن را مسدود میکنند یا با صدای بلند اعلان (prompt) میدهند و لینک برای اکثر خوانندگان شکست میخورد. به /URI و /GoTo تکیه کنید و /Launch را رها کنید
حاشیهنویسیهای نشانهگذاری (Markup) مانند هایلایتها و یادداشتهای چسبناک، و حاشیهنویسیهای شکل مانند /Square، یک پیچیدگی اضافه میکنند: ظاهر روی صفحه آنها توسط نوع آنها دلالت نمیشود. یک نمایشگر نسخه خودش را رندر میکند مگر اینکه شما ظاهر را با یک جریان ظاهر (appearance stream)، یعنی ورودی /AP که به یک XObject فرم که اپراتورهای ترسیم را نگه میدارد ارجاع میدهد، پین (pin) کنید. از آن بگذرید و همان هایلایت ممکن است در دو خواننده، یا قبل و بعد از رفتوبرگشت یک ویرایشگر متفاوت به نظر برسد. برای هر چیزی که ظاهر دقیق آن بخشی از سند است، /AP را ارائه دهید. اتفاقاً پیوستهای فایل نیز از همین ماشینآلات استفاده مجدد میکنند: یک جریان فایل تعبیهشده و یک دیکشنری مشخصات فایل، که یا به عنوان یک حاشیهنویسی /FileAttachment و یا از طریق درخت نام /EmbeddedFiles در زیر /Names کاتالوگ ظاهر میشوند
جایی که این لایه میشکند، و نحوه گرفتن آن
خرابی تکرارشونده در سراسر این موارد ارجاع معلق (dangling reference) است. نشانکها زمانی که کاتالوگ فاقد ورودی /Outlines باشد یا یک زنجیره خواهر و برادر در اواسط درخت بشکند، ظاهر نمیشوند؛ فرادادهها زمانی که جریان XMP فاقد نشانگذاری /Type /Metadata /Subtype /XML باشد یا بستهبند xpacket بدشکل شده باشد، نادیده گرفته میشوند. در هر مورد محتوای صفحه خوب است، بنابراین یک باز کردن معمولی درست به نظر میرسد و نقص تنها در پنلی ظاهر میشود که هیچکس آن را بررسی نکرده است
دو عادت ارزان بیشتر آنها را میگیرد. فایل نهایی را در یک نمایشگر واقعی باز کنید و روی پنل نشانکها و نمونهای از لینکها کلیک نمایید، که گراف ارجاع را به روشی که خواننده انجام خواهد داد، تمرین میدهد. سپس فراداده را با ابزار مجزایی بخوانید و تأیید کنید که دیکشنری Info و XMP با هم موافق هستند، که این تنها عدم توافقی است که هیچ مقدار کلیک کردنی آن را فاش نمیکند. این لایه را از طریق کتابخانهای تولید کنید که صاحب حسابداری لینک است و اکثر این تلهها هرگز باز نمیشوند. ابزار HotPDF Component برای Delphi و C++Builder ساختارهای طرحنما، حاشیهنویسی و فراداده را از طریق APIهای سطح سند در معرض دید قرار میدهد، بنابراین شما سلسله مراتب نشانک و لینکها را توصیف میکنید و اجازه میدهید آن را در ارجاعات رشتهبندی کند. برای مدل شیئی که این ساختارها به آن متصل میشوند، مقاله بررسی فنی ساختار فایل PDF کاتالوگ و جدول ارجاع متقاطع را که به آنها وابسته هستند، پوشش میدهد