مقاله فنی

اسناد مهندسی PDF/E-1 در Delphi با PDFlibPas

PDF/E-1 پروفایل آرشیوی اسناد مهندسی است، و PDFlibPas آن را به‌صورت یک author mode پیاده می‌کند که با SetPDFEMode روشنش می‌کنید، به‌علاوه یک preflight محدوددامه که content streamها را عملگر به عملگر می‌خواند. این پروفایل PDF/A با برچسب متفاوت نیست: فضای نام شناسایی خودش را دارد، الزام metadata چرخه عمر خودش را، و یک قاعده که راستی‌آزمایی محتوا را سخت‌گیرانه‌تر از هر پروفایل آرشیوی می‌کند که تا حالا دیده‌اید

محصولات تحویلی مهندسی دلیل وجود این پروفایل‌اند. یک مجموعه نقشه که باید بیست سال دیگر هم خوانا باشد و به‌اثبات‌پذیری تغییرنکرده باشد، با تاریخچه بازنگری‌ای که دوام می‌آورد، و با رنگی که در plotter ساختمان دیگر هم همان معنا را دارد. این الزامات مشخصه‌ای تولید می‌کنند که مطالباتش بیشتر بیرون از محتوای صفحه می‌نشیند، در metadata و مدیریت رنگ، که دقیقاً همان جایی است که یک PDF writer عمومی اشتباهش می‌کند

شناسایی خودش، نه یک گونه از PDF/A

اولین چیزی که باید درست بگیرید این است که شناسایی PDF/E-1 را نمی‌شود با تطبیق دادن الگوی PDF/A یا PDF/X تولید کرد. از یک فضای نام XMP مجزا استفاده می‌کند، http://www.aim.org/pdfe/ns/id/، و مقدار نسخه باید در دو جا ظاهر شود: به‌عنوان یک مدخل document information و به‌عنوان property XMP با پیشوند فضای نام. خروجی دادن فقط property XMP، یا فقط مدخل information، فایلی تولید می‌کند که نیت را حمل می‌کند و در راستی‌آزمایی می‌افتد

output intent شکل به همان اندازه خاصی دارد. PDF/E-1 یک پروفایل ICC جاسازی‌شده با شناسه زیرگروه ISO_PDFE1 می‌خواهد، و پروفایل باید تعداد مؤلفه‌ای موافق خانواده رنگی دستگاهی که سند واقعاً استفاده می‌کند داشته باشد. همان بند آخر است که پیاده‌سازی‌ها بی‌سروصدا در آن گم می‌شوند، چون یعنی intent را نمی‌شود از قبل انتخاب کرد و بعد فراموش

چرا رنگ دستگاه به یک sweep کل سند نیاز دارد؟

چون فضاهای رنگی در دیکشنری‌های resource پنهان می‌شوند که اسکن سطح صفحه هرگز به آن‌ها نمی‌رسد. PDF/E-1 با DeviceRGB و DeviceCMYK به‌عنوان دو خانواده به‌طور متقابل انحصاری برای یک سند رفتار می‌کند، پس راستی‌آزمایی پروفایل یعنی دانستن هر فضای رنگ دستگاهی که هر چیزی در فایل استفاده می‌کند. یک form XObject resource خودش را دارد. pattern هم همین‌طور، و image هم. یک tiling pattern درون یک form XObject درون یک صفحه سه سطح عمق دارد، و validatorای که فقط resourceهای سطح بالای صفحه را چک کند سندی را که از هر دو خانواده استفاده می‌کند پاس می‌کند

پس sweep فضاها رنگی را حین گشتن صفحات، فرم‌ها، تصاویر و patternها به‌عنوان یک پیمایش واحد ثبت می‌کند، و فقط بعد از آن تصمیم می‌گیرد که آیا سند منسجم است و آیا output intent جور درمی‌آید. همان استدلال به‌طور کلی معماری preflight را پیش می‌برد: پیمایش جزئی پاس کاذب تولید می‌کند، و یک پاس کاذب روی چک انطباق بدتر از هیچ چکی است، چون به‌عنوان مدرک ثبت می‌شود

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('assembly-drawings.pdf');

    if Lib.SetPDFEMode(1) = 0 then
      raise Exception.Create('PDF/E author mode was refused');

    // author mode metadata چرخه عمر را در هر ذخیره همگام نگه می‌دارد.
    // پیش از ذخیره بپرسید که آیا سند از گیت خودش عبور می‌کند
    if not Lib.PDFEReadyForSave then
    begin
      Diag := Lib.GetPDFEDiagnostics;
      Writeln('PDF/E blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('assembly-drawings-pdfe.pdf');
  finally
    Lib.Free;
  end;
end;

metadata چرخه عمر یک الزام به‌ازای هر ذخیره است

PDF/E-1 بیشتر از یک شناسه سند می‌خواهد. مجموعه حداقلی شامل شناسه سند مدیریت رسانه، یک شناسه نسخه، یک rendition class، زمان ایجاد، زمان اصلاح، زمان metadata و یک عنوان است. این یک واژگان پیگیری بازنگری است، و وجودش برای این است که از یک محصول تحویلی مهندسی انتظار می‌رود بازانتشار شود نه اینکه یک بار نوشته شود

پیامدش برای پیاده‌سازی این است که این فیلدها را نمی‌شود در لحظه ساخت سند مقدار داد. اگر زمان اصلاح وقتی حالت را فعال می‌کنید نوشته شود و سند بعداً ویرایش شود، snapshot XMP و وضعیت واقعی سند از هم فاصله می‌گیرند، و validatorای که آن‌ها را مقایسه می‌کند یک ناسازگاری گزارش می‌کند که هیچ‌کس قصدش را نکرده بود. به همین دلیل author mode این فیلدها را بلافاصله پیش از هر ذخیره همگام می‌کند، تا metadata بایت‌هایی را توصیف کند که در آستانه نوشته شدن‌اند نه بایت‌هایی که وقتی حالت روشن شد وجود داشتند

این یک اصل کلی برای metadata انطباق است و ارزش دارد جدا از PDF/E گفته شود: metadata مشتق‌شده مال مسیر ذخیره است نه مسیر ویرایش. هر فیلدی که از وضعیت سند محاسبه می‌شود باید در لحظه انجماد وضعیت از نو محاسبه شود، وگرنه یک cache بدون invalidation است

نمودار PDF/E-1 در PDFlibPas از sweep رنگ دستگاهی کل سند که دیکشنری‌های resource صفحه، form XObject، tiling pattern و تصویر را می‌گردد و خانواده‌های DeviceRGB و DeviceCMYK را جمع می‌کند پیش از قضاوت درباره انسجام، در کنار فیلدهای metadata چرخه عمر که author mode بلافاصله پیش از هر ذخیره دوباره همگام می‌کند تا snapshot XMP با بایت‌های در آستانه نوشته شدن جور دربیاید
انسجام رنگ فقط بعد از یک پیمایش که به همه دیکشنری‌های resource برسد قابل قضاوت است، و metadata چرخه عمر مشتق‌شده در لحظه انجماد وضعیت سند دوباره محاسبه می‌شود، نه وقتی حالت روشن شد

قاعده‌ای که راستی‌آزمایی محتوا را سخت‌گیرانه می‌کند

PDF/E-1 اجازه نمی‌دهد عملگرهای بخش سازگاری محتوای ناشناخته را قورت بدهند. در PDF معمولی، BX و EX ناحیه‌ای را قاب می‌کنند که consumer در آن باید عملگرهایی را که نمی‌شناسد نادیده بگیرد، که همان دریچه گریز است که به producer اجازه می‌دهد سازه‌های جدیدتر را بدون شکستن readerهای قدیمی‌تر خروجی بدهد. زیر PDF/E-1 آن گریز بسته است، پس هر عملگری که preflight نشناسد بی‌قید و شرط گزارش می‌شود، چه داخل یک بخش سازگاری باشد چه نباشد

اثرش روی validator چشمگیر است. نمی‌تواند ناحیه‌هایی را که نمی‌فهمد رد کند، یعنی parser عملوندها باید واقعاً هر عملگر در هر content stream را parse کند. اینجاست که مرزها معنا پیدا می‌کنند. پیمایش روی ۱۲۸ سطح تو در تو، یک میلیون آبجکت و ۶۴ مگابایت محتوا سقف دارد، و این محدودیت‌ها تنظیم کارایی نیستند. یک فایل خصمانه یا صرفاً خراب می‌تواند گراف آبجکتی با cycle یا عمق تو در تویی ارائه دهد که یک validator بازگشتی را به stack overflow بکشاند، و همین محدودیت‌ها هستند که مانع می‌شوند یک گذر راستی‌آزمایی به بردار denial-of-service تبدیل شود. همان موضع دفاعی در parse امن PDFهای نامطمئن توصیف شده

// راستی‌آزمایی مستقل یک فایل که خودتان تولیدش نکرده‌اید، بدون لودکردن
// آن در یک instance سند
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/E: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

گیت ذخیره چه چیزهایی را تعمیر می‌کند و چه چیزهایی را رد

گیت کارش را به دو مرحله تقسیم می‌کند، و همین تقسیم به‌خودی‌خود یک ایده طراحی قابل استفاده است. اول چیزهایی را نرمال می‌کند که به‌طور امن قابل تعمیرند: پرچم‌های چاپ annotation، پرچم‌های no-zoom و no-rotate روی annotationهای متنی، و پرچم تولید ظاهر روی دیکشنری فرم. این‌ها تنظیماتی هستند با یک مقدار درست زیر پروفایل و بدون هیچ محتوای اطلاعاتی، پس تعمیر بی‌سروصدایشان درست است و ردکردن به‌خاطرشان pedantry می‌شود

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

نمودار گیت ذخیره PDF/E-1 در PDFlibPas برای Delphi که preflight محدوددامه را نشان می‌دهد که هر عملگر content stream را زیر سقف ۱۲۸ سطح تو در تو، یک میلیون آبجکت و ۶۴ مگابایت اسکن می‌کند، پرچم‌های چاپ، zoom و rotate annotation را بی‌سروصدا تعمیر می‌کند، نسخه غلط، شناسایی، رمزگذاری، output intent، رنگ دستگاهی یا محتوای فرم دینامیک را رد می‌کند، و بلاکرها را از طریق GetPDFEDiagnostics گزارش می‌دهد
گیت فقط چیزی را بی‌سروصدا تعمیر می‌کند که اطلاعات حمل نکند، هر محدودیتی را که تعمیرش آن را تحریف کند رد می‌کند، و پیش از آنکه حتی یک بایت به دیسک برسد ردشدن را از طریق GetPDFEDiagnostics به یک لیست بلاکر تبدیل می‌کند

خواندن diagnostics از طریق GetPDFEDiagnostics پیش از ذخیره آن ردشدن را به یک لیست قابل اقدام تبدیل می‌کند نه یک عملیات شکست‌خورده. در یک خط لوله دسته‌ای، روی هر سند صدا بزنیدش، بلاکرها را به‌ازای هر فایل لاگ کنید، و شکست‌ها را به صفی بفرستید که یک انسان نگاهشان می‌کند. این به‌مراتب مفیدتر از ذخیره‌ای است که raise می‌کند، چون بلاکرها معمولاً خوشه‌ای‌اند: چهل سندی که برای یک output intent غایب یکسان fail می‌شوند یک fix هستند، نه چهل

انتخاب میان پروفایل‌های آرشیوی

وقتی محصول تحویلی، مستندات مهندسی با چرخه عمر بازنگری است، و مشخصاً وقتی انسجام رنگ دستگاهی مهم است چون خروجی به plotterها و چاپگرهای فرمت بزرگ می‌رود، PDF/E-1 تارگت درست است. وقتی هدف خوانایی بلندمدت اسناد به‌طور کلی است، PDF/A تارگت درست است، و همان پروفایلی است که پشتیبانی validator از آن گسترده‌ترین است. این دو جایگزین هم نیستند، و یک سند می‌تواند یکی را پاس کند و در دیگری بیفتد

نمودار تصمیم PDFlibPas که پروفایل‌های آرشیوی PDF/E-1 و PDF/A را برای Delphi مقایسه می‌کند: PDF/E-1 برای محصولات تحویلی مهندسی با چرخه عمر بازنگری، رنگ plotter و راستی‌آزمایی قراردادی زیر فضای نام XMP خودش با output intent دارای شناسه ISO_PDFE1، PDF/A برای خوانایی بلندمدت عمومی با گسترده‌ترین پشتیبانی validator
از این شروع کنید که سر خط چه کسی فایل را راستی‌آزمایی می‌کند: پروفایل‌ها الزامات شناسایی، metadata و رنگ متفاوتی می‌خواهند، و یک سند می‌تواند یکی را پاس کند و در دیگری بیفتد

اگر در حال انتخاب‌اید، از این شروع کنید که سر خط چه کسی فایل را راستی‌آزمایی می‌کند. ابزار راستی‌آزمایی PDF/A همه‌جا هست، و preflight متناظرش در PDFlibPas در preflight ‏PDF/A و PDF/UA توصیف شده. راستی‌آزمایی PDF/E تخصصی‌تر است و معمولاً یک الزام قراردادی است نه پیش‌فرض. وقتی یک آرشیو موجود باید به پروفایلی ارتقا پیدا کند که برایش نوشته نشده بود، مسیر تعمیر metadata در تبدیل به PDF/A با تعمیر metadata الگوی قابل پیروی است، و همین شکل اینجا هم صادق است: شناسایی کن، چیز امن را تعمیر کن، بقیه را با یک لیست رد کن

author mode، preflight محتوای محدوددامه و چک انطباق مستقل همگی همراه PDFlibPas Delphi PDF library عرضه می‌شوند، پس می‌شود سندی زیر پروفایل تولید کرد و بعداً به‌طور مستقل از یک مسیر کد جداگانه راستی‌آزمایی‌اش کرد، که برای یک ادعای انطباق تنها چیدمانی است که ارزش اعتماد دارد