مقاله فنی

جریان‌های آبجکت و جریان‌های مرجع متقابل PDF در Delphi

جریان‌های آبجکت PDF 1.5 بسیاری آبجکت غیرمستقیم کوچک را در یک ظرف فشرده-Flate بسته‌بندی می‌کنند، و losLab PDF Library آن‌ها را روی یک ذخیره کامل از راه پرچم PackObjectStreams منتشر می‌کند. سودش واقعی است: صدها دیکشنری صفحه، فونت و حاشیه‌نویسی که هرکدام ده‌ها بایت فشرده‌نشده هزینه می‌کنند به چند بلاب فشرده فرومی‌ریزند. هزینه این است که هر آبجکت بسته‌بندی‌شده اکنون به یک جریان مرجع متقابل نیاز دارد تا آن را توصیف کند

آن نیمه دوم جایی است که نویسنده‌ها می‌شکنند. ساختن یک ظرف /ObjStm حسابگری است؛ آموزش‌دادن ماشین مرجع متقابل که به درونش اشاره کند یک بازطراحی است. نویسنده‌ای که یک ظرف کاملاً معتبر تولید می‌کند و سپس اعضایش را با آفست‌های معمولی نوع-۱ توصیف می‌کند فایلی تولید کرده که Acrobat به‌اندازه کافی باز می‌کند تا خراب اعلامش کند. این دو ویژگی یک ویژگی‌اند، و این مقاله سمت نوشتن هر دو را پوشش می‌دهد، همان‌طور که در ISO 32000-1 §7.5.7 و §7.5.8 تعریف شده

یک ظرف ObjStm واقعاً چه چیزی حمل می‌کند

یک جریان آبجکت یک جریان است که بایت‌های رمزگشایی‌شده‌اش دو ناحیه به‌هم‌پیوسته‌اند، و ISO 32000-1 §7.5.7 دقیقاً سه کلیدی که برای ساخت اهمیت دارند به دیکشنری می‌دهد. /Type /ObjStm آن را شناسایی می‌کند، /N تعداد اعضا را می‌دهد، و /First طول بایتی ناحیه سربرگ را می‌دهد — معادلاً، آفستی که بدنه از آن شروع می‌شود. سربرگ جفت‌های شماره آبجکت و آفست جداشده با فاصله سفید است؛ بدنه اعضای سریال‌شده پشت‌سرهم است، هرکدام با آفست اندازه‌گیری‌شده از شروع بدنه نه از شروع بار محتوایی رمزگشایی‌شده. خواندن یک ظرف کاملاً رمزگشایی‌شده آن را واضح می‌کند: در زیر، /First برابر ۱۴ است چون سه خط سربرگ چهارده بایت اشغال می‌کنند، و آبجکت ۷ در ۵۵ بایت داخل بدنه می‌نشیند چون آبجکت ۴ به ۵۴ کاراکتر به‌علاوه یک جداکننده سریال شده

// Decoded payload of: 12 0 obj << /Type /ObjStm /N 3 /First 14
//                        /Filter /FlateDecode /Length 118 >> stream
4 0
7 55
9 90
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
<< /Type /ExtGState /CA 1 /ca 1 >>
[ 0 0 595 842 ]

دو قاعده عضویت مطلق‌اند و هر دو مستقیماً از §7.5.7 می‌آیند. یک آبجکت جریان هرگز نمی‌تواند یک عضو باشد، چون یک جریان بایت‌های خام حمل می‌کند که باید درون یک جریان دیگر تودرتو شوند. و یک عضو باید یک مقدار آبجکت کامل باشد، هرگز یک ارجاع غیرمستقیم خالص — یک آبجکت فشرده که صرفاً 5 0 R است یک واسطه‌ای می‌سازد که خواننده نمی‌تواند بدون از‌قبل‌دانستن به کجا اشاره می‌کند حل کند. losLab PDF Library هر دو مورد را در طول جمع‌آوری نامزد فیلتر می‌کند، در کنار دیکشنری رمزنگاری و آبجکت ۰، سپس هرچه جان‌سالم‌مانده را در گروه‌های ۲۰۰تایی به‌ازای هر ظرف بسته‌بندی می‌کند. آن سقف یک تصمیم دسترسی‌تصادفی است نه یک محدودیت مشخصات: خواننده‌ای که یک عضو می‌خواهد باید کل ظرف را inflate کند، پس ظرف‌های بیش‌ازحد‌بزرگ جست‌وجوهای کوچک را پرهزینه می‌کنند

چرا اعضای ObjStm باید مدخل‌های مرجع متقابل نوع-۲ استفاده کنند؟

چون یک آبجکت بسته‌بندی‌شده هیچ آفست فایلی برای ثبت ندارد. ISO 32000-1 §7.5.8 به این با سه نوع مدخل در یک جریان مرجع متقابل دودویی پاسخ می‌دهد: نوع ۰ برای آبجکت‌های آزاد، نوع ۱ برای آبجکت‌های معمولی در‌حال‌استفاده که در یک آفست بایتی ذخیره شده‌اند، و نوع ۲ برای آبجکت‌های فشرده، که دو فیلد داده‌شان شماره آبجکت ظرف و شاخص عضو درونش را نگه می‌دارند. هیچ راهی برای بیان یک آبجکت بسته‌بندی‌شده در جدول xref متن‌ساده کلاسیک وجود ندارد، که دقیقاً چرا PDF 1.5 هر دو ویژگی را با هم معرفی کرد

ترتیبی که دنبال می‌شود تقریباً هر پیاده‌سازی اول را، شامل ما، می‌لغزاند. آبجکت‌های معمولی مدخل‌های نوع-۱ می‌گیرند. خود ظرف‌های /ObjStm مدخل‌های نوع-۱ می‌گیرند، چون یک ظرف یک آبجکت جریان کاملاً معمولی است که در یک آفست واقعی نوشته شده. فقط اعضا مدخل‌های نوع-۲ می‌گیرند. و جریان مرجع متقابل خودش یک آبجکت غیرمستقیم در فایل است، پس به مدخل نوع-۱ خودش که به آفستی که تازه رویش نوشته شده اشاره می‌کند نیاز دارد — همان آفستی که startxref ثبت می‌کند. یک نسخه اولیه نویسنده ما شماره‌های آبجکت ظرف را از حلقه نوشتن رد می‌کرد به‌جای رد‌کردن اعضا، و نتیجه فایلی بود با یک جریان مرجع متقابل و بدون هیچ جریان آبجکتی: به‌لحاظ ساختاری منسجم، به‌لحاظ معنایی خالی، ردشده پایین‌دست. مقدار /Size یک خطای یک‌عدد متناظر پنهان می‌کند، چون بالاترین شماره آبجکت به‌علاوه یک است و جریان مرجع متقابل به‌عنوان بالاترین شماره آبجکت تخصیص می‌یابد، پس باید هم شمرده شود

اندازه‌دهی آرایه /W: چرا چهار بایت کافی نیست

آرایه /W عرض بایتی هر یک از سه فیلد را اعلام می‌کند، و losLab PDF Library آن را به‌صورت /W [1 Field2 Field3] با فیلد ۱ ثابت‌شده روی یک بایت برای کد نوع و فیلد ۳ ثابت‌شده روی دو بایت می‌نویسد، که شماره‌های نسل تا ۶۵۵۳۵ و شاخص‌های عضو را به‌یکسان پوشش می‌دهد. فیلد ۲ آنی است که نمی‌تواند ثابت باشد، چون دو کمیت نامرتبط را حمل می‌کند: در یک مدخل نوع-۱ یک آفست بایتی است که فقط با اندازه فایل محدود می‌شود، در حالی که در یک مدخل نوع-۲ یک شماره آبجکت ظرف است و در یک مدخل نوع-۰ آبجکت آزاد بعدی در زنجیره است. یک فیلد ۲ ثابت چهاربایتی خوب کار می‌کند تا وقتی فایل از ۴ گیگابایت عبور کند، که در آن نقطه هر آفست پس از مرز در سکوت کوتاه می‌شود و کل جدول به آشغال تبدیل می‌شود. نویسنده بنابراین جدول مونتاژشده را برای بزرگ‌ترین مقداری که هر جای‌گاه فیلد-۲ خواهد داشت اسکن می‌کند، شامل آفست خود جریان مرجع متقابل، و فیلد را تا هشت بایت گشاد می‌کند

// Field 2 must hold the largest byte offset AND the largest
// ObjStm container number AND the largest free-chain target.
MaxField2Value := XRefStart;
for X := 0 to MaxObj do
begin
  if XRefTable[X].InUse and (XRefTable[X].ObjStrNum > 0) then
    Field2Value := XRefTable[X].ObjStrNum   // type-2: container number
  else
    Field2Value := XRefTable[X].ObjPos;     // type-1 offset / type-0 next-free
  if Field2Value > MaxField2Value then
    MaxField2Value := Field2Value;
end;

Field2 := 4;
while (Field2 < 8) and
      (MaxField2Value > ((Int64(1) shl (Field2 * 8)) - 1)) do
  Inc(Field2);
Field3 := 2;   // generation numbers and member indices both fit

وقتی عرض‌ها معلوم شوند، اندازه بار محتوایی دقیقاً معلوم است، پس نویسنده کل بافر را پیش‌تخصیص می‌دهد و آن را با نمایه پر می‌کند؛ ضمیمه‌کردن مدخل‌ها بایت‌به‌بایت به یک AnsiString ساخت جدول را درجه‌دوم می‌کند، که هیچ‌کس روی یک فاکتور ده‌صفحه‌ای متوجه نمی‌شود و همه روی یک سند با دویست‌هزار آبجکت متوجه می‌شوند. دو جزئیات دیگر خوانندگان سخت‌گیر را راضی نگه می‌دارند. /Index اعلام می‌کند جدول کدام بازه‌های شماره‌آبجکت را می‌پوشاند، و برای یک بازنویسی کامل این به‌سادگی [0 N] بدون شکاف است. و هر جای‌گاهی که نویسنده واقعاً منتشر نکرده باید به‌طور پیش‌فرض آزاد باشد نه در‌حال‌استفاده: آبجکت ۰ سرزنجیره آزاد را می‌گیرد، هر جای‌گاه آزاد به بعدی زنجیر می‌شود، و جای‌گاهی که زمانی یک آبجکت حذف‌شده را نگه داشته شماره نسلش را افزایش‌یافته نگه می‌دارد. یادداشت همراه درباره امنیت حافظه هنگام تجزیه PDFهای نامعتمد همان استدلال کران را از سمت خواندن می‌سازد

چرا جریان مرجع متقابل هرگز نباید رمزنگاری شود؟

چون یک خواننده باید آن را پیش از آنکه بداند چگونه هرچیزی را رمزگشایی کند تجزیه کند. جریان مرجع متقابل چیزی است که به خواننده می‌گوید دیکشنری /Encrypt کجا زندگی می‌کند؛ اگر بایت‌های خودش رمزنگاری‌شده بودند، خواننده به کلید فایل نیاز داشت تا آبجکتی که کلید فایل را توصیف می‌کند بیابد. losLab PDF Library این را در یک پیش‌بین واحد اعمال می‌کند: ShouldCryptStreamData هرجا دیکشنری جریان /Type /XRef حمل کند False برمی‌گرداند، پس معافیت برقرار می‌ماند صرف‌نظر از اینکه کدام مسیر به سریال‌ساز برسد

ظرف /ObjStm رفتار مخالف را می‌گیرد، و عدم‌تقارن عمدی است. یک ظرف کاملاً رمزنگاری می‌شود، کلیدگذاری‌شده روی شماره آبجکت خودش، دقیقاً مانند هر جریان دیگری. اعضایش منفرداً رمزنگاری نمی‌شوند — آن‌ها در فرم رمزگشایی‌شده تحت‌اللفظی‌شان بسته‌بندی می‌شوند، و گذر منفرد روی ظرف مونتاژشده آن‌ها را می‌پوشاند، شامل رشته‌ها. رمزنگاری‌دوباره اعضا فایلی تولید می‌کند که به رمزنگاری رمزگشایی می‌شود، و چون لایه بیرونی موفق می‌شود، شکست عمیق در گراف آبجکت به‌عنوان یک خطای تجزیه ظاهر می‌شود نه یک شکست احراز هویت. یک آبجکت سپس کاملاً بیرون از این طرح می‌ماند: در یک سند رمزنگاری‌شده، Catalog به‌عنوان یک آبجکت مستقیم نوع-۱ نگه داشته می‌شود و هرگز بسته‌بندی نمی‌شود، چون بسته‌بندی‌اش بارگذار را مجبور می‌کرد یک جریان آبجکت را inflate و رمزگشایی کند تا به ریشه سند برسد، پیش از آنکه زمینه رمزگشایی که ریشه به ساختنش کمک می‌کند کاملاً ساخته شده باشد

روشن‌کردن بسته‌بندی از Delphi

سوییچ عمومی PackObjectStreams است، که به‌عنوان یک فیلد روی TPDFlibSaveOptions، به‌عنوان تنظیم‌کننده مستقل SetPackObjectStreams، و به‌عنوان یک ویژگی روی آبجکت سند نمایش داده شده. به‌طور پیش‌فرض فعال است و به‌طور خودکار با نسخه دروازه‌بندی می‌شود: نویسنده فقط وقتی سند از قبل PDF 1.5 یا جدیدتر است بسته‌بندی می‌کند، و نگهبان حداقل-نسخه داخلی را فراخوانی می‌کند پس یک سند بسته‌بندی‌شده به ۱.۵ ارتقا می‌یابد نه غلط‌برچسب‌گذاری شود. پس از ذخیره، GetLastSaveUsedObjectStreams گزارش می‌کند آیا دروازه واقعاً باز شد، که assertion‌ای است که در یک آزمون رگرسیون می‌خواهید نه مقایسه اندازه بایتی

var
  Doc: TPDFlib;
  Options: TPDFlibSaveOptions;
begin
  Doc := TPDFlib.Create;
  try
    if Doc.LoadFromFile('report.pdf', '') <= 0 then
      Exit;

    Doc.SetInformation(0, '1.5');        // packing is gated on PDF 1.5+

    FillChar(Options, SizeOf(Options), 0);
    Options.CompressContent    := True;
    Options.GarbageCollect     := True;  // drop orphans before packing
    Options.PackObjectStreams  := True;

    if Doc.SaveToFileOptions('report-packed.pdf', Options) = 1 then
      if Doc.GetLastSaveUsedObjectStreams = 1 then
        Writeln('Saved with ObjStm containers and an xref stream');
  finally
    Doc.Free;
  end;
end;

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

مرزهایی که ارزش دانستن پیش از فعال‌کردنش را دارند

ذخیره‌های افزایشی هرگز بسته‌بندی نمی‌کنند. یک به‌روزرسانی افزایشی آبجکت‌های جدید و یک بخش مرجع متقابل جدید را ضمیمه می‌کند در حالی که نسخه‌های قبلی را به‌لحاظ فیزیکی دست‌نخورده می‌گذارد، پس بسته‌بندی‌مجدد آبجکت‌های موجود در ظرف‌های تازه مدخل‌های نوع-۱ که نسخه قبلی همچنان ارجاع می‌دهد را یتیم می‌کند؛ losLab PDF Library بسته‌بندی را هرجا حالت ضمیمه فعال باشد غیرفعال می‌کند، و مقاله به‌روزرسانی‌های افزایشی و استریمینگ حالت-ضمیمه آن مسیر را کاملاً پوشش می‌دهد. اسناد زیر PDF 1.5 بدون قید‌وشرط جدول مرجع متقابل متن‌ساده را نگه می‌دارند: یک مصرف‌کننده ۱.۴ هیچ ایده‌ای درباره معنای /ObjStm ندارد، و ارتقادادن در سکوت یک سند چون نویسنده یک فایل کوچک‌تر را ترجیح داده معامله اشتباهی است که به‌جای فراخوان‌کننده گرفته شود. یک کلید اختیاری که عمداً منتشر نمی‌کنیم /Extends است، که ISO 32000-1 §7.5.7 تعریف می‌کند تا یک ظرف بتواند یک پیشین را نام ببرد و خوانندگان بتوانند یک زنجیره ظرف را به‌عنوان یک گروه منطقی رفتار کنند. این واقعاً اختیاری است، هر ظرفی که می‌نویسیم خودکفا و مستقلاً رمزگشایی‌پذیر است، و رد‌کردنش یک کلاس چرخه و باگ ارجاع آویزان را از نویسنده حذف می‌کند — هرچند خوانندگان البته همچنان باید /Extends را وقتی در فایل‌های تولیدکنندگان دیگر با آن مواجه می‌شوند رعایت کنند

بسته‌بندی جریان آبجکت و خروجی جریان مرجع متقابل به‌عنوان بخشی از losLab PDF Library برای Delphi و C++Builder ارائه می‌شود، در کنار جمع‌کننده زباله و بهینه‌ساز جریان محتوا که با آن‌ها ترکیب می‌شود؛ صفحه محصول مرجع کامل گزینه‌های ذخیره را در بر دارد