مقاله فنی

جریان‌های آبجکت و به‌روزرسانی‌های افزایشی در دلفی با HotPDF

نسخه PDF 1.5 دو ساختار ذخیره‌سازی را معرفی کرد که فرمت فایل قبلی هیچ راهی برای بیان آنها نداشت: جریان آبجکت (object stream) و جریان ارجاع متقابل (cross-reference stream). یک جریان آبجکت یک محفظه فشرده شده با Flate است، با برچسب /Type /ObjStm، که بسیاری از اشیاء غیرمستقیم کوچک را به جای پراکنده کردن در بدنه فایل، به صورت متوالی بسته‌بندی می‌کند. جریان ارجاع متقابل (xref stream)، جدول جستجوی فایل است که به جای جدول ASCII با عرض ثابت که هر PDF را تا نسخه 1.4 می‌بست، به صورت باینری فشرده با فیلدهایی با عرض متغیر بازنویسی شده است. آنها با هم سفر می‌کنند. هنگامی که اشیاء درون یک جریان تا می‌شوند، جدول متنی قدیمی دیگر نمی‌تواند به آنها آدرس‌دهی کند، بنابراین xref باینری باید همراه آن بیاید

آن را در مقابل چیدمان کلاسیک قرار دهید و هزینه‌ای که حذف می‌کند به راحتی قابل مشاهده است. در یک فایل PDF 1.4 هر شیء غیرمستقیم به صورت غیرفشرده در پشت هدر obj خود قرار می‌گیرد، و جدول در انتهای فایل دقیقاً 20 بایت ASCII برای هر ورودی مصرف می‌کند، و فشرده‌سازی ممنوع است. سندی با 200,000 شیء پیش از اینکه حتی یک گلیف کشیده شود، تقریباً 4 مگابایت داده ارجاع متقابل را با خود حمل می‌کند، در حالی که تمام بدنه‌های غیرفشرده دیکشنری روی هم چیده شده‌اند. نسخه PDF 1.5 به طور همزمان به هر دو عدد حمله می‌کند: دیکشنری‌ها در محفظه‌های Flate تا می‌شوند، و جدول 4 مگابایتی به چند صد کیلوبایت باینری کاهش می‌یابد. استاندارد ISO 32000-1 این دو ساختار را در بخش 7.5.7 و 7.5.8 تعریف می‌کند

صرفه‌جویی واقعاً در کجا انجام می‌شود

جریان‌های آبجکت فقط اشیاء غیرجریانی را لمس می‌کنند، بنابراین آنها ساختار را فشرده می‌کنند، نه پیکسل‌ها را. محتوای صفحه قبل از 1.5 با Flate فشرده شده بود، و داده‌های تصویر کدک‌های خاص خود را حمل می‌کنند، به همین دلیل است که یک بروشور پر از تصویر به سختی تغییر حجم می‌دهد. فایل‌هایی که حجمشان به شدت کاهش می‌یابد فایل‌هایی با ساختار سنگین هستند: فرم‌های AcroForm با هزاران دیکشنری فیلد، درخت‌های طرح‌نمای (outline) عمیق، عناصر ساختار tagged-PDF. این اشیاء کوچک، متعدد، و تقریباً شبیه به یکدیگر هستند، و این تکرار دقیقاً همان چیزی است که Flate از آن بهره‌برداری می‌کند، زمانی که آنها در یک بافر واحد قرار می‌گیرند به جای اینکه در سراسر بدنه پراکنده شوند و هدرها بین آنها گنجانده شده باشند

به راحتی می‌توان میزان سربار (overhead) یک فایل قدیمی را دست کم گرفت. یک آرشیو فرم که سال‌ها ویرایش را به خود جذب کرده است، می‌تواند بیش از نیمی از بایت‌های خود را صرف هدرهای دیکشنری، پدینگ (padding) جدول xref، و بازبینی‌هایی کند که هیچ خواننده‌ای هرگز به آنها نگاه نخواهد کرد. دو ویژگی در اینجا، دو مورد اول را پس می‌گیرند. مورد سوم، بازبینی‌های انباشته شده، تنها به فشرده‌سازی (compaction) تن می‌دهد، زمانی که فایل دیگر مجبور نباشد تاریخچه خود را به خاطر بسپارد

در HotPDF شما هر دو را از طریق یک جفت پراپرتی روشن می‌کنید، و نحوه وابستگی آنها به یکدیگر مهمتر از ترتیبی است که آنها را می‌نویسید:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalog-2026.pdf';
    Pdf.UseXRefStream := True;      // binary xref, prerequisite for ObjStm
    Pdf.UseObjectStreams := True;   // pack objects into /Type /ObjStm
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
    Pdf.EndDoc;                     // emits XRefStm + ObjStm containers
  finally
    Pdf.Free;
  end;
end;

خاصیت UseObjectStreams نیازمند این است که UseXRefStream روی True تنظیم شود. یک شیء فشرده از طریق یک ورودی xref نوع 2 در دسترس قرار می‌گیرد، که شماره یک جریان آبجکت به علاوه یک ایندکس را ثبت می‌کند، و یک سطر متنی کلاسیک 20 بایتی جایی برای ذخیره این جفت ندارد. بنابراین UseObjectStreams به تنهایی هیچ کار قابل مشاهده‌ای انجام نمی‌دهد؛ هر دو پرچم (flag) که قبل از BeginDoc تنظیم شده‌اند، پیکربندی هستند که کار می‌کند. اگر آنها را پس از BeginDoc تنظیم کنید، HotPDF از قبل به چیدمان قدیمی‌تر متعهد شده است

چرا پیش‌فرض هر دو خاموش است

ابزار HotPDF به طور پیش‌فرض هر دو پراپرتی را False رها می‌کند، و دلیل آن در یکپارچه‌سازی‌ها با کدهای قدیمی پایین‌دستی مشخص می‌شود. خواننده‌ای که تنها PDF 1.4 را می‌فهمد، اعلام نمی‌کند که نمی‌تواند اشیاء فشرده را مدیریت کند. با یک جریان xref مواجه می‌شود، هیچ یک از کلمات کلیدی تریلر (trailer) را که انتظار دارد پیدا نمی‌کند، و گزارش یک جدول ارجاع متقابل آسیب دیده را می‌دهد یا به سادگی از باز کردن فایل خودداری می‌کند. اگر خروجی شما به یک دروازه فکس قدیمی، یک چاپگر سخت‌افزاری که یک مفسر تعبیه شده را اجرا می‌کند، یا تجزیه‌کننده‌ای که کسی یک دهه پیش بر اساس مشخصات 1.4 نوشته است سرازیر می‌شود، هر دو پرچم را برای آن کانال خاموش نگه دارید و با فایل بزرگتر بسازید. برای ذخیره‌سازی آرشیوی و تحویل تحت وب، جایی که هر نمایشگر اصلی بیست سال است که PDF 1.5 را می‌خواند، روشن کردن آنها فشرده‌سازی است که تقریباً بدون هیچ هزینه‌ای به دست می‌آورید

یک اثر ثانویه وجود دارد که ارزش دارد به تیم پشتیبانی خود بگویید. هنگامی که دیکشنری‌ها در جریان‌های آبجکت بسته‌بندی می‌شوند، مقایسه بایت به بایتِ دو فایل تولید شده دیگر معنایی ندارد، زیرا تغییر یک فیلد می‌تواند کل یک محفظه را دوباره با Flate فشرده کند و هر چیزی که بعد از آن است را به هم بریزد. تفاوت چنین فایل‌هایی را بر اساس محتوای آبجکت مقایسه کنید (Diff)، نه با یک مقایسه باینری

به‌روزرسانی‌های افزایشی و آفست‌های بایتی که آنها محافظت می‌کنند

یک امضای دیجیتال یک /ByteRange صریح را پوشش می‌دهد: دو بازه از فایل فیزیکی، که به عنوان آفست‌های بایت مطلق داده می‌شوند، که دایجست CMS روی آنها گرفته شده است. فایل را بازنویسی کنید، حتی به چیزی که روی صفحه یکسان به نظر می‌رسد، و همه آن آفست‌ها جابجا می‌شوند. دایجست دیگر مطابقت ندارد و امضا شکسته خوانده می‌شود. این دقیقاً همان مشکلی است که ISO 32000-1 بخش 7.5.6 با به‌روزرسانی‌های افزایشی حل می‌کند. اشیاء جدید و تغییر یافته پس از %%EOF موجود ضمیمه می‌شوند، سپس یک بخش ارجاع متقابل جدید نوشته می‌شود که ورودی /Prev آن به بخش قبلی خود اشاره می‌کند. بایت‌های اصلی هرگز دستکاری نمی‌شوند، بنابراین یک نسخه امضا شده قابل تایید باقی می‌ماند و Acrobat می‌تواند هر نسخه امضا شده را به تنهایی در پنل امضا ارائه دهد

ابزار HotPDF این را از طریق نقطه ورود خود در دسترس قرار می‌دهد:

Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf');  // appends the delta only

دو چیز افراد را به اشتباه می‌اندازد. متد BeginIncrementalUpdate باید نام فایل اصلی را دریافت کند، زیرا بخش ضمیمه شده xref آفست‌هایی را ثبت می‌کند که تنها در برابر آن بایت‌های اصلی دقیق معنا دارند؛ آن را به یک کپی تغییر نام یافته یا مجدداً ذخیره شده اشاره دهید و آفست‌ها فایلی را توصیف می‌کنند که دیگر وجود ندارد. و ذخیره به طور ساختاری فقط-ضمیمه (append-only) است، بنابراین خروجی همیشه بزرگتر از ورودی است. این رشد، ضایعاتی نیست که بتوان آن را با تنظیمات از بین برد. این همان خاصیتی است که نسخه‌های امضا شده قبلی را دست‌نخورده باقی می‌گذارد

تغییر یک فایل بارگذاری شده از طریق LoadFromFile انجام می‌شود

توسعه‌دهندگانی که برای اولین بار از طریق API تولید با HotPDF آشنا شدند، معمولاً به یک مانع خاص برخورد می‌کنند. متد BeginDoc یک سند کاملاً جدید را باز می‌کند، که ابزار اشتباهی است زمانی که قصد دارید سندی را که از قبل وجود دارد تغییر دهید. ویرایش یک فایل موجود به جای آن از طریق فراخوانی‌های سند بارگذاری‌شده اجرا می‌شود:

PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5);  // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');

این دو را با هم مخلوط کنید و نشانه آن یک فایل خروجی است که حاوی محتوای جدید شماست و هیچ چیز از فایل اصلی را در خود ندارد، زیرا BeginDoc با خوشحالی یک سند تازه را در کنار سندی که فکر می‌کردید در حال ویرایش آن هستید ساخته است. متدهای LoadFromFile به همراه SaveLoadedDocument را به عنوان یک واژگان و BeginDoc به همراه EndDoc را به عنوان واژگان دیگر در نظر بگیرید. روالی که برای هر دو در برابر یک فایل واحد دست‌درازی می‌کند تقریباً همیشه اشتباه است

چه زمانی یک فایل ضمیمه شده را فشرده (compact) کنیم

ذخیره‌سازی فقط-ضمیمه (Append-only) هزینه کندی به همراه دارد. یک کار شبانه که یک خط وضعیت را روی همان PDF مهر می‌کند، 365 نسخه در طول یک سال تولید می‌کند، و هر نسخه یک بخش xref جدید را به دنبال خود می‌کشد. هنگامی که آن تاریخچه دیگر کاربردی ندارد، و هیچ امضایی در فایل نیازی به بقا ندارد، می‌توانید کل آن را با سریال‌سازی مجدد از طریق مسیر سند بارگذاری‌شده، مسطح (flatten) کنید:

Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');

این ذخیره مجدد یک بازنویسی کامل است. این کار به طور عمدی بازبینی‌های قبلی را دور می‌اندازد و هر امضایی را که هنوز در فایل است می‌شکند، بنابراین آن را پشت همان دروازه سیاستی قرار دهید که برای هر مرحله مخرب دیگری اعمال می‌کنید. یک قانون تولید که پابرجا می‌ماند: زمانی فشرده‌سازی کنید که تعداد نسخه‌ها از یک آستانه عبور کند، یا زمانی که سربار ضمیمه شده از سهمی از فایل پایه بیشتر شود، و هرگز سندی را که پنل امضای آن حاوی چیزی است، فشرده نکنید

بررسی خروجی قبل از ارسال آن

تأیید این جفت ویژگی به شکل نشاط‌آوری ملموس است. نتیجه را در Adobe Acrobat باز کنید و سه نکته را تأیید کنید: ویژگی‌های سند پس از روشن شدن جریان‌های آبجکت، PDF 1.5 یا بالاتر را گزارش می‌دهند؛ پنل امضا همچنان پس از یک به‌روزرسانی افزایشی هر نسخه امضا شده قبلی را اعتبارسنجی می‌کند؛ و تعداد صفحات و نشانک‌ها (bookmarks) در یک چرخه بارگذاری، تغییر و ذخیره دست‌نخورده باقی مانده‌اند. برای خروجی آرشیوی، فایل را از طریق veraPDF نیز بگذرانید، زیرا یک xref فشرده شده دقیقاً همان نوع ساختاری است که یک اعتبارسنجِ سخت‌گیر با دقت بیشتری نسبت به یک نمایشگر باگذشت بررسی می‌کند. اگر کار شما با ورودی‌های بسیار بزرگ نیز سروکار دارد، روش‌های بازرسی در آموزش گام‌به‌گام ما از Direct File API برای گردش‌کارهای بزرگ PDF به طور طبیعی با ذخیره افزایشی جفت می‌شوند، و مکانیک امضا در پشت محدوده‌های بایت (byte ranges) ذکر شده در بالا در مقاله امضاهای دیجیتال HotPDF و PAdES به تفصیل بررسی شده‌اند

هر دو ویژگی به عنوان بخشی از کامپوننت HotPDF برای دلفی و C++Builder، در کنار APIهای تولید، فرم، رمزگذاری و امضا که در جای دیگری از این وبلاگ پوشش داده شده‌اند، عرضه می‌شوند. اگر می‌خواهید فراخوانی‌های بالا را در برابر پایپ‌لاین سند خود تنظیم کنید، صفحه محصول به مرجع کامل API لینک می‌دهد