مقاله فنی

ذخیره‌ی دقیق نسخه‌ی PDF در Delphi: مطابقت در PDFiumPas

PDFiumPas، wrapper Delphi و C++Builder دور موتور PDFium گوگل، یک سند را در یک نسخه‌ی دقیق PDF از ۱.۳ تا ۱.۷ از طریق پارامتر PdfVersion در متد TPdf.SaveAs ذخیره می‌کند. فراخوانی خودِ FPDF_SaveWithVersion در PDFium فقط هدر %PDF-M.m را بازمی‌نویسد، بدون بررسی اینکه آیا محتوای واقعی سند در آن نسخه قانونی است یا نه. PDFiumPas آن شکاف را با یک پاس مطابقت پس-از-ذخیره می‌بندد که زنجیره‌ی نسخه‌ی cross-reference فعال را می‌پیماید و اعلان‌های Adobe Extension Level را پیش از اینکه فایل متد را ترک کند بررسی می‌کند

آن تمایز بیشترین اهمیت را در تولید چاپ دارد، جایی که یک پروفایل PDF/X یک نسخه‌ی دقیق PDF را نام می‌برد و یک ابزار preflight یا RIP هر چیزی را که بی‌سروصدا با هدر خودش ناموافق باشد رد می‌کند، سناریویی که از سمت خروجی در اعتبارسنجی اسناد PDF/X آماده‌ی چاپ با PDFiumPas پوشش داده شده. SaveAs هدف را به‌عنوان enum از نوع TPdfVersion در معرض دید می‌گذارد، pv13 تا pv17 در کنار مقادیر قدیمی‌تر pv10 تا pv12، به‌علاوه یک TSaveOption مستقل برای بازنویسی‌های افزایشی یا کامل. PdfVersion را پاس دهید و PDFiumPas دو کار را در یک فراخوانی انجام می‌دهد: از PDFium می‌خواهد هدر درخواست‌شده را damage بزند، سپس بایت‌های تازه‌نوشته‌شده را دوباره می‌خواند و از پس‌دادن فایلی که محتوای فعالش نمی‌تواند به‌طور قانونی در آن نسخه وجود داشته باشد سر باز می‌زند

var
  Pdf: TPdf;
begin
  Pdf:= TPdf.Create(nil);
  try
    Pdf.FileName:= 'source.pdf';
    Pdf.Active:= True;
    try
      Pdf.SaveAs('press-ready.pdf', saNoIncremental, pv17);
    except
      on E: Exception do
        // E.Message names the offending feature and the version or
        // extension level it actually needs, for example:
        // "RichMedia annotations and RichMediaExecute actions require
        // /Extensions /ADBE with /BaseVersion /1.7 and /ExtensionLevel 3
        // or newer."
        raise;
    end;
  finally
    Pdf.Free;
  end;
end;

چرا آخرین تعریف شیء در فایل چیز اشتباهی برای اعتمادکردن است؟

آخرین شیء فیزیکی با یک شماره‌ی مشخص در یک فایل PDF لزوماً همان شیئی نیست که یک خواننده‌ی مطابق امروز برای آن شماره حل می‌کند. یک PDF که از چند به‌روزرسانی افزایشی عبور کرده یک گراف شیء ندارد، تاریخچه‌ای از آن‌ها لایه‌بندی‌شده درون یک فایل تکی دارد، و هر چرخه‌ی پیوست می‌تواند یک شیء را آزاد کند، آن را زیر یک شماره‌ی نسل جدید دوباره تعریف کند، یا بدنه‌ی فیزیکی قدیمی‌اش را بین دو نشانگر endobj رها کند بدون هیچ ورودی cross-reference که دیگر به آن اشاره کند

PDFiumPas دقیقاً به همان حالت شکست برخورد کرد پیش از اینکه نسخه‌های xref را صراحتاً پیگیری کند: یک حاشیه‌نویسی Redact که با یک بازنویسی شیء-صفحه‌ی بعدی یتیم شده، یا یک دیکشنری /MarkInfo که فیزیکی حضور دارد بدون هیچ ورودی xrefای که به آن اشاره کند، همچنان می‌توانست در یک اسکن بایت نمایان شود و همچنان یک بررسی ویژگی-نسخه را که دیگر برای سندی که یک خواننده واقعاً باز می‌کرد اعمال نمی‌شد به لغزش بیندازد. جهت شکست رد کاذب بود، نه پذیرش کاذب: فایلی که واقعاً از یک ویژگی در نسخه‌ی فعلی‌اش فراتر رفته بود، همچنان می‌توانست از ذخیره‌شدن در یک نسخه‌ی پایین‌تر به‌خاطر محتوایی که دیگر هیچ‌کس نمی‌توانست به آن برسد مسدود شود

PDFiumPas چطور تعیین می‌کند کدام تعریف‌های شیء واقعاً فعال هستند؟

PDFiumPas مجموعه‌ی شیء فعال را دقیقاً همان‌طور که یک خواننده‌ی مطابق حل می‌کند، با پیمایش زنجیره‌ی cross-reference به‌جای اسکن بایت‌ها به‌دنبال هدر شیء، حل می‌کند. resolver از آخرین افست startxref در فایل شروع می‌کند و هر لینک /Prev را به عقب در سراسر نسخه‌های قدیمی‌تر دنبال می‌کند، جدول‌های cross-reference کلاسیک، جریان‌های ترکیبی متصل-به-/XRefStm، و جریان‌های cross-reference خالص را در طول مسیر تجزیه می‌کند. این پیمایش از جدیدترین-به-قدیمی‌ترین اجرا می‌شود و هر شماره‌ی شیء را همان اولین باری که دیده می‌شود ساکن می‌کند، پس یک ورودی آزاد در یک نسخه‌ی بعدی به‌درستی یک بدنه‌ی شیء نوشته‌شده در یک نسخه‌ی قبلی را سایه می‌اندازد، و یک دوباره‌تعریف زیر یک افست یا نسل جدید همیشه بر آنچه جایگزین کرده پیروز می‌شود

اعضای جریان‌شیء یک بررسی اضافی می‌گیرند که یک جستجوی افست ساده نمی‌تواند به‌تنهایی ارائه دهد، سازوکاری که با عمق بیشتری در اعتبارسنجی جریان‌های شیء و cross-reference با PDFiumPas پوشش داده شده. یک شیء فشرده‌ی بازیابی‌شده از یک /ObjStm باید جریان والدش در همان پیمایش فعال تأیید شود، و اندیس آن باید با موقعیت خودِ عضو درون هدر آن جریان مطابقت داشته باشد پیش از اینکه PDFiumPas آن را به‌عنوان محتوای زنده در نظر بگیرد. بخش 7.5.8.4 از ISO 32000-1 حتی یک حالت ارجاع-ترکیبی را توصیف می‌کند که در آن یک جدول سازگاری کلاسیک یک شیء را آزاد علامت می‌زند درحالی‌که ورودی /XRefStm در trailer همزمان همان شیء را به‌عنوان یک عضو فشرده جای دیگری تعریف می‌کند؛ PDFiumPas جریان xref مکمل را در همان نسخه پیش از اینکه ورودی‌های کلاسیک اعمال شوند ادغام می‌کند، پس تعریف فشرده همان‌طور که مشخصات قصد دارد پیروز می‌شود

سطوح Extension در Adobe: دروازه‌ی بالای شماره‌ی نسخه

یک هدر %PDF-1.7 فقط مجموعه ویژگی‌ای را وعده می‌دهد که ISO 32000-1 در ۲۰۰۸ استاندارد کرد، درحالی‌که چند قابلیتی که تولیدکنندگان PDF امروز به آن‌ها تکیه می‌کنند بعداً به‌عنوان مکمل‌های اختصاصی Adobe لایه‌بندی‌شده روی همان شماره‌ی نسخه عرضه شدند. Adobe هر مکمل را به‌عنوان یک جفت BaseVersion و ExtensionLevel ثبت‌شده در دیکشنری /Extensions کاتالوگ سند زیر یک پیشوند توسعه‌دهنده، ADBE برای extensionهای خودِ Adobe، ثبت کرد، پس یک خواننده می‌تواند یک فایل PDF 1.7 ساده را از یکی که یک سطح extension شماره‌گذاری‌شده را هم پیاده‌سازی می‌کند تفکیک کند. ذخیره‌کردن در pv17 بدون آن اعلان به‌خودی‌خود یک خطا نیست؛ فقط همان لحظه‌ای یکی می‌شود که محتوای فعال واقعاً به یک ویژگی‌ای بستگی داشته باشد که اعلان قرار است آن را پوشش دهد

کدام ویژگی‌های نسخه-بالا دروازه‌ی نسخه-صریح را به لغزش می‌اندازند؟

PDFiumPas یک فهرست مشخص و مبتنی-بر-مشخصات را بررسی می‌کند به‌جای اینکه صرفاً از شماره‌ی نسخه حدس بزند. دیکشنری‌های تصویری که یک ورودی صریح /SMaskInData یا یک مقدار /BitsPerComponent برابر ۱۶ حمل می‌کنند هر دو به PDF 1.5 نیاز دارند، با حالت شانزده‌بیتی که مستقیماً از قواعد مؤلفه-تصویری بخش 4.8 از PDF Reference 1.5 پیروی می‌کند. حاشیه‌نویسی‌های RichMedia و اکشن‌های RichMediaExecute به /BaseVersion /1.7 با /ExtensionLevel 3 یا بالاتر نیاز دارند. جریان‌های سه‌بعدی PRC، شناسایی‌شده با یک دیکشنری که هم /Type /3D و هم /Subtype /PRC حمل می‌کند، به همان نسخه‌ی پایه نیاز دارند اما فقط /ExtensionLevel 1. دیکشنری‌های Measure از نوع جغرافیایی-مکانی و حاشیه‌نویسی‌های Projection به /BaseVersion /1.7 با /ExtensionLevel 3 نیاز دارند، همان مکمل Adobe که RichMedia به آن بستگی دارد

بررسی جغرافیایی-مکانی یک جزئیات مشخصات-خوانی حمل می‌کند که ارزش دانستن دارد اگر هرگز منطق نسخه-دروازه‌بندی‌شده‌ی خودتان را روی PDFiumPas بسازید. جدول ۲۵۴ از ISO 32000-1 ورودی /Type دیکشنری Measure را اختیاری علامت می‌زند، فقط اشاره می‌کند که «اگر حاضر باشد، باید Measure باشد»، درحالی‌که جدول ۳۱۱ برای دیکشنری جریان سه‌بعدی که محتوای PRC در آن زندگی می‌کند /Type را الزامی می‌کند. خروجی GeoPDF واقعی از ابزارهای نقشه‌کشی به‌طور معمول /Type را روی دیکشنری Measure حذف می‌کند و فقط /Subtype /GEO می‌نویسد، پس آشکارساز جغرافیایی-مکانی PDFiumPas فقط روی /Subtype تطبیق می‌کند به‌جای اینکه هر دو کلید را همان‌طور که آشکارساز PRC سه‌بعدی آن به‌طور ایمن می‌تواند الزامی کند. الزامی‌کردن /Type روی هر دو دیکشنری اجازه می‌داد محتوای مطابق GeoPDF بدون شناسایی از دروازه بگذرد و در یک فایل PDF 1.7 ساده بدون هیچ اعلان سطح extensionای که پشتش را داشته باشد فرود بیاید

آیا PDFiumPas به‌طور خودکار ویژگی‌های پشتیبانی‌نشده را تنزل می‌دهد؟

نه به‌عنوان یک قابلیت عمومی، و فرض‌کردن خلاف آن اشتباهی است که باید اینجا از آن اجتناب کرد. SaveAs نسخه‌ی هدف را از طریق یک روال داخلی، ValidatePdfVersionCompliance، سرازیر می‌کند، و وقتی آن روال ویژگی‌ای پیدا کند که نسخه‌ی هدف یا اعلان سطح extensionاش نمی‌تواند پشتیبانی کند، SaveAs یک استثنا حامل متن خطای آن روال raise می‌کند به‌جای نوشتن فایل؛ فراخواننده یک دلیل دقیق و ویژگی-نام‌گذاری‌شده پس می‌گیرد، هرگز یک سند بی‌سروصدا بازنویسی‌شده. تنها جایی که PDFiumPas واقعاً محتوا را به‌طور خودکار بازمی‌نویسد یک هدف PDF 1.3 است، جایی که پیش‌فرض‌های شفافیت معنایی-خنثای /BM /Normal، /CA 1، و /ca 1 را که PDFium همیشه صرف‌نظر از نسخه‌ی هدف درون دیکشنری‌های ExtGState می‌نویسد لخت می‌کند، چون آن مقادیر مشخص هیچ معنای بصری حمل نمی‌کنند و PDF 1.3 کاملاً پیش از این کلیدها بوده

// PDF 1.3 targets rewrite the saved bytes to strip transparency
// defaults PDFium always emits, so incremental mode cannot apply
Pdf.SaveAs('legacy-archive.pdf', saIncremental, pv13);
// raises: PDF 1.3 normalization is incompatible with incremental
// save mode

شفافیت واقعی غیر-پیش‌فرض و ماسک‌های نرم تصویر همچنان کاملاً در یک هدف PDF 1.3 شکست می‌خورند، چون حذف‌کردنشان ظاهر واقعی صفحه را تغییر می‌دهد، و PDFiumPas آن تصمیم را از طرف شما نمی‌گیرد. دو محدودیت مرتبط ارزش برنامه‌ریزی دارند پیش از اینکه یک نسخه‌ی دقیق وارد یک خط لوله‌ی دسته‌ای شود. خروجی نسخه-صریح هرگز یک دیکشنری /Encrypt حمل نمی‌کند؛ ذخیره فوراً شکست می‌خورد اگر منبع محافظت‌شده باشد، که اتفاقاً با پروفایل‌های PDF/X و PDF/A که رمزنگاری را در هر صورت ممنوع می‌کنند هم‌راستا می‌شود، اما به این معناست که رمزگشایی یک گام جداگانه در گردش‌کار شماست نه چیزی که SaveAs برایتان انجام دهد. PDFiumPas همچنین هیچ متد عمومی‌ای برای نوشتن یک اعلان /Extensions /ADBE روی یک کاتالوگ ندارد، پس یک فایل منبع که RichMedia، PRC سه‌بعدی، یا محتوای جغرافیایی-مکانی دارد اما آن اعلان را ندارد، هر PdfVersionای که درخواست کنید از دروازه عبور نمی‌کند؛ اعلان باید از پیش در منبع وجود داشته باشد، معمولاً چون ابزار نویسندگی آن را نوشته، یا ویژگی باید پیش از ذخیره بیرون بیاید. ویژگی فقط‌خواندنی TPdf.PdfVersion ارزش بررسی‌کردن دارد پیش از اینکه اصلاً یک ذخیره‌ی نسخه-دقیق امتحان شود، چون همان نسخه‌ی مؤثر آگاه-از-کاتالوگ را حل می‌کند، هدر یا override از /Version، هرکدام که فعلی است، که خودِ اعتبارسنج زمان-ذخیره به آن تکیه می‌کند

Pdf.FileName:= 'incoming.pdf';
Pdf.Active:= True;
// PdfVersion resolves the same catalog-aware effective version the
// save-time validator uses, so a mismatch here is worth investigating
// before spending a full SaveAs attempt on it
LogSourceVersion('incoming.pdf', Pdf.PdfVersion);

یک استثنای SaveAs روی یک هدف نسخه‌ی دقیق را به‌عنوان یک گزارش preflight در نظر بگیرید نه یک باگ: پیام دقیقاً نام می‌برد سند منبع کدام بند را نقض می‌کند، که دقیقاً همان اطلاعاتی است که یک چاپخانه یا خط لوله‌ی آرشیو پیش از اینکه یک فایل هرگونه پیشتر برود نیاز دارد. مسیر ذخیره‌ی نسخه-صریح، resolver نسخه‌ی xref فعال، و بررسی‌های Adobe Extension Level که در اینجا توصیف شد، همگی به‌عنوان بخشی از کامپوننت استاندارد PDFiumPas برای Delphi و C++Builder عرضه می‌شوند؛ صفحه‌ی محصول مرجع کامل TPdf.SaveAs را در کنار بقیه‌ی API مطابقت و فرم حمل می‌کند