مقاله فنی

جاسازی فونت‌های گمشده در PDFهای موجود برای PDF/A در دلفی

کتابخانه losLab PDF می‌تواند برنامه‌های فونت گمشده یک PDF بارگذاری‌شده را تنها با یک فراخوانی جاسازی کند: EmbedMissingFonts هر دیکشنری فونت را در سند پیمایش می‌کند، فونت سیستم نصب‌شده مطابق را با نام BaseFont آن مکان‌یابی می‌کند و برنامه فونت را دوباره در فایل می‌نویسد. برای تیم‌هایی که در حال اصلاح اسناد شخص ثالثی هستند که در اعتبارسنجی PDF/A در مورد جاسازی فونت ناموفق عمل می‌کنند، این همان راه‌حلی است که خطای preflight 00030 را از بین می‌برد

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

چرا PDF/A نیاز دارد که هر فونت جاسازی شود؟

استاندارد ISO 19005-1 §6.3.4 ایجاب می‌کند که هر فونت مورد استفاده در یک سند منطبق، برنامه فونت خود را در داخل فایل حمل کند، زیرا کل وعده PDF/A قابلیت بازتولید است: سند باید پنجاه سال بعد روی دستگاهی که هیچ فونت مشترکی با دستگاه تولیدکننده آن ندارد، به طور یکسان رندر شود. یک فونت غیرجاسازی‌شده، دستوری برای پیدا کردن Arial در جایی از سیستم نمایش است، و دیدگاه استاندارد این است که "جایی در سیستم نمایش" یک تضمین آرشیوی نیست. فونت جایگزین هر گلیف، متریک و پوششی داشته باشد، همان چیزی است که خواننده دریافت می‌کند و ممکن است آن چیزی نباشد که نویسنده دیده است

مقصر تاریخی، کنوانسیون Standard 14 است. استاندارد PDF 1.0 وعده داده بود که هر نمایشگری Helvetica، Times، Courier، Symbol و ZapfDingbats را به همراه دارد، بنابراین ابزارهای تولید یاد گرفتند که به آن فونت‌ها با نام اشاره کنند و چیزی را جاسازی نکنند، و سی سال ابزارسازی هنوز هم دقیقاً همین کار را انجام می‌دهد. کتابخانه losLab PDF این الزام را به اندازه‌ای جدی می‌گیرد که در حالت ایجاد PDF/A، متد AddStandardFont عمداً یک دستور بدون اثر (no-op) است: کتابخانه برنامه‌های فونت Standard 14 را ارسال نمی‌کند، نمی‌تواند چیزی را که ندارد جاسازی کند و از نوشتن یک ارجاع غیرجاسازی‌شده در سندی که ادعای انطباق دارد خودداری می‌کند. این متد 0 را بدون انتخاب فونت برمی‌گرداند، بنابراین یک سند PDF/A باید از AddTrueTypeFont با جاسازی استفاده کند و هر درخواست Embed=0 در زمان فعال بودن حالت PDF/A به طور خاموش به Embed=1 ارتقا می‌یابد. این بخش مربوط به نویسنده است. مشکل سخت‌تر بخش خواننده است: سندی که شخص دیگری قبلاً نوشته است و پر از دیکشنری‌های فونتی است که شما ایجاد نکرده‌اید

چگونه EmbedMissingFonts یک سند بارگذاری‌شده را اصلاح می‌کند؟

کتابخانه losLab PDF فونت‌ها را در محل خود اصلاح می‌کند تا اینکه آن‌ها را بازسازی کند. هنگامی که یک تولیدکننده PDF یک فونت TrueType غیرجاسازی‌شده می‌نویسد، دیکشنری FontDescriptor که تولید می‌کند از قبل کامل است: FontName، FontBBox، Flags، Ascent، Descent، StemV، همه حضور دارند. تنها چیزی که آن را از یک فونت جاسازی‌شده جدا می‌کند، نبود یک ورودی است: مرجع جریان /FontFile2 که برنامه فونت واقعی را نگه می‌دارد. بنابراین، EmbedMissingFonts به دیکشنری فونت، رمزگذاری، آرایه پهنا یا هیچ جریان محتوایی که با نام منبع به فونت ارجاع می‌دهد، دست نمی‌زند. برنامه فونت مطابق را از سیستم می‌خواند، آن را در یک شیء جریان جدید فشرده می‌کند و یک مرجع تکی /FontFile2 (یا /FontFile3 برای فونت‌های CIDFontType0) به FontDescriptor که از قبل وجود دارد، ضمیمه می‌کند. هر چیزی که صفحات سند به آن اشاره می‌کنند دقیقاً در همان جایی که بود باقی می‌ماند، که این امر اجرای عملیات را روی فایل‌هایی که کنترل نمی‌کنید ایمن می‌سازد

پوشش شامل هر دو معماری فونتی است که در عمل با آن‌ها مواجه خواهید شد: فونت‌های ساده TrueType و فونت‌های ترکیبی Type0/CID، از نوع تولید شده برای متن‌های CJK و خروجی‌های مدرن یونیکد. این پیمایش به جای تکیه بر بررسی گام‌به‌گام منابع هر صفحه، عمداً هر دیکشنری Font را در درخت شیء سند فهرست می‌کند، بنابراین فونت‌های ارجاع‌داده‌شده از یادداشت‌ها یا به اشتراک گذاشته شده در صفحات نیز شناسایی می‌شوند. API یک فراخوانی واحد روی سند بارگذاری‌شده است

var
  PDF: TPDFlib;
  Repaired: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
      raise Exception.Create('Could not load PDF');

    // تمام دیکشنری‌های فونت را پیمایش می‌کند؛ تعداد فونت‌هایی که
    // برنامه فونت دریافت کرده‌اند را برمی‌گرداند. فونت‌هایی که برنامه آن‌ها
    // در سیستم پیدا نشود، نادیده گرفته می‌شوند و شکست نمی‌خورند.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

    PDF.SaveToFile('supplier-invoice-repaired.pdf');
  finally
    PDF.Free;
  end;
end;

یک جزئیات که ارزش دانستن دارد زیرا توضیح می‌دهد چرا تطبیق نام بهتر از مقایسه ساده رشته‌ها کار می‌کند: کتابخانه نام‌های BaseFont را قبل از جستجو نرمال‌سازی می‌کند. پیشوندهای زیرمجموعه (الگوی ABCDEF+ متشکل از شش حرف بزرگ انگلیسی و یک علامت مثبت) حذف می‌شوند، پسوندهای سبک PostScript مانند ArialMT به Arial حل می‌شوند و فایل‌های TrueType Collection شناسایی و باز می‌شوند تا فونتی که داخل یک فایل .ttc قرار دارد همچنان به درستی جاسازی شود

تایید اصلاح با گزارش preflight

CreatePreflightReport مرحله تایید است و حلقه به طور عمدی بسته شده است: همان ممیزی که فایل را محکوم کرده است باید همان ممیزی باشد که آن را تبرئه می‌کند. کد خطای 00030 یافته ممیزی عمیق PDF/A است که می‌گوید "حداقل یک فونت جاسازی نشده است (FontFile/FontFile2/FontFile3 مفقود است)" و این خطا برای کل فایل گزارش می‌شود، بنابراین یک فونت نادیده گرفته شده آن را زنده نگه می‌دارد. گزارش را روی فایل منبع اجرا کنید، اصلاح کنید، ذخیره کنید و دوباره آن را روی خروجی اجرا کنید

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 بررسی‌های PDF/A را انتخاب می‌کند
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

برای مشاهده وضعیت هر فونت به جای حکم کل فایل، سند اصلاح‌شده را دوباره بارگذاری و شمارش کنید: FindFonts و به دنبال آن SelectFont و GetFontIsEmbedded وضعیت جاسازی را فونت به فونت گزارش می‌دهند، که این ابزار مناسبی است برای زمانی که یک کار دسته‌ای نیاز به ثبت دقیق نام فونت در فایلی دارد که اصلاح نشده است. همین الگوی شمارش در مقاله استخراج متن، تصویر و فونت از PDFهای بارگذاری‌شده نیز ظاهر می‌شود، جایی که به جای اصلاح، تغذیه‌کننده فرآیند استخراج است

چه اتفاقی می‌افتد اگر فونت روی سیستم نصب نباشد؟

متد EmbedMissingFonts هر فونتی را که نتواند برنامه آن را پیدا کند، نادیده می‌گیرد و این نادیده‌انگاری را از طریق مقدار بازگشتی خود گزارش می‌دهد: اگر تعداد برگشتی کمتر از تعداد فونت‌های غیرجاسازی‌شده شمارش‌شده شما باشد، مابه‌التفاوت آن فونت‌هایی است که سیستم فاقد آن‌ها است. این حالت شکست صادقانه است و بهتر از جایگزین‌های دیگر است، زیرا ابداع یک برنامه جایگزین برای فونت نام‌گذاری‌شده در سند، رندر را تغییر می‌دهد، که این دقیقاً همان چیزی است که یک اصلاح آرشیوی هرگز نباید انجام دهد. برای این موارد، کتابخانه losLab PDF متد EmbedFontProgramFromFile را ارائه می‌دهد که یک فایل .ttf یا .otf ارائه‌شده توسط فراخوان را در فونت نام‌گذاری‌شده جاسازی می‌کند، بنابراین یک خط لوله می‌تواند فونت‌های سازمانی را که انتظار دارد با آن‌ها مواجه شود، ارسال کند و عمداً به آن‌ها رجوع کند

var
  I, FontID: Integer;
begin
  PDF.FindFonts;
  for I := 1 to PDF.FontCount do
  begin
    FontID := PDF.GetFontID(I);
    if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
      if PDF.GetFontIsEmbedded = 0 then
        // ابتدا فونت نصب‌شده سیستم را امتحان کنید، سپس به
        // فایل فونت ارسال شده در کنار برنامه رجوع کنید
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

دو مرز شایسته است که به وضوح بیان شوند. اول اینکه، فونت‌های Type1 در پیاده‌سازی فعلی اصلاح نمی‌شوند: ورودی /FontFile آن‌ها به ساختار سه بخشی PFB با کلیدهای طول صریح نیاز دارد و کتابخانه به جای نوشتن جریان نامعتبر، آن‌ها را نادیده می‌گیرد؛ آن‌ها در اسناد مدرن نادر هستند اما در آرشیوهای قدیمی ظاهر می‌شوند. دوم اینکه، جاسازی یک فونت یک اقدام تحت مجوز (لایسنس) است. مجوزهای جاسازی یک فونت TrueType متعلق به شرکت طراح آن است و یک خط لوله اصلاح که برنامه‌های فونت دارای مجوز را در اسناد خروجی از سازمان قرار می‌دهد، باید تایید کند که مجوزهای فونت در واقع اجازه این کار را می‌دهند. کتابخانه کاری را که می‌خواهید انجام می‌دهد؛ اما اینکه آیا مجاز به درخواست هستید، سوالی برای بخش حقوقی شماست، نه کامپایلر شما

جاسازی لازم است، اما کافی نیست

اصلاح فونت‌ها خطای 00030 را برطرف می‌کند و نه چیز دیگری. سندی که در PDF/A به دلیل رمزگذاری، فقدان متادیتای XMP، فضای رنگی وابسته به دستگاه بدون OutputIntent یا عدم وجود نقشه‌های ToUnicode رد می‌شود، حتی پس از جاسازی تمام فونت‌ها همچنان با شکست مواجه خواهد شد، به همین دلیل است که اصلاح باید درون یک حلقه مبتنی بر preflight قرار گیرد تا اینکه جایگزین آن شود. گزارش کامل را اجرا کنید، مواردی را که نام برده اصلاح کنید و اجازه دهید گزارش به شما بگوید چه زمانی کار تمام شده است. بعد هزینه نیز وجود دارد: یک برنامه فونت کامل CJK به چندین مگابایت می‌رسد، بنابراین جاسازی چندین مورد از آن‌ها می‌تواند یک سند کوچک را به طرز چشمگیری حجیم کند. راهکار متقابل، زیرمجموعه‌سازی (subsetting) است که در مقاله بهینه‌سازی حجم فایل PDF و زیرمجموعه‌سازی فونت پوشش داده شده است که برنامه جاسازی‌شده را فقط به گلیف‌هایی که سند واقعاً رندر می‌کند کاهش می‌دهد

جلوگیری از پسرفت اسناد جدید

متد SetEmbedAllFonts بخش پیشگیری از همین ویژگی است: یک محافظ در سمت نویسنده که مانع از تولید اسنادی توسط کد شما می‌شود که این مقاله آن‌ها را اصلاح می‌کند. با فعال بودن SetEmbedAllFonts(1)، هر فراخوانی بعدی AddTrueTypeFont که درخواست Embed=0 کند به یک ارجاع جاسازی‌شده ارتقا می‌یابد، که این امر تضمینی را که حالت PDF/A از قبل اعمال می‌کند به هر سند تعمیم می‌دهد. این متد بر فونت‌های اضافه‌شده پس از فراخوانی تاثیر می‌گذارد، نه فونت‌هایی که از قبل در فایل بارگذاری شده وجود دارند، بنابراین تقسیم وظایف مشخص است: SetEmbedAllFonts برای اسنادی که ایجاد می‌کنید، EmbedMissingFonts برای اسنادی که به ارث می‌برید

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// از این به بعد، AddTrueTypeFont(Name, 0) مانند
// AddTrueTypeFont(Name, 1) رفتار می‌کند: هیچ ارجاع
// غیرجاسازی‌شدی نمی‌تواند به فایل خروجی راه یابد

هر دو بخش، یعنی محافظ سمت نویسنده و مسیر بارگذاری-اصلاح-ذخیره، بخشی از کتابخانه PDF losLab برای دلفی، سی‌شارپ و VB.NET در کنار موتور preflight هستند که نتیجه را تایید می‌کند؛ صفحه محصول شامل مرجع کامل API فونت از جمله فراخوانی‌های جاسازی و زیرمجموعه‌سازی برای هر فونت است