مقاله فنی

کنترل جایگزینی فونت PDF در دلفی با PDFium

PDFium Component به یک برنامه دلفی اجازه می‌دهد تصمیم بگیرد کدام بایت‌های فونت استفاده شوند وقتی یک PDF به فونتی ارجاع می‌دهد که تعبیه نمی‌کند. ConfigureSystemFontProvider یک پیاده‌سازی IPdfSystemFontProvider را نصب می‌کند که هر درخواست نگاشت فونتی را که PDFium می‌سازد دریافت می‌کند، همراه با نام چهره، وزن، پرچم ایتالیک، مجموعه‌نویسه و خانواده گام، و با بایت‌های TrueType، مجموعه TrueType یا OpenType‌ای که باید استفاده شوند پاسخ می‌دهد

این ویژگی وجود دارد زیرا فونت‌های تعبیه‌نشده یک قرعه‌کشی رندر هستند. یک PDF که Arial را نام می‌برد و چیزی را تعبیه نمی‌کند، روی یک ایستگاه کاری با Arial رندر می‌شود، روی یک سرور لینوکس با یک جایگزین متریک‌سازگار، و روی هر چیزی که نگاشت‌گر میزبان روی یک تصویر کانتینر قفل‌شده بیابد. همان فاکتور روی هرکدام متفاوت به نظر می‌رسد، شکست‌های خط جابه‌جا می‌شوند، و یک مشتری سندی دریافت می‌کند که با نسخه بایگانی‌شده مطابقت ندارد

چرا فقط فونت‌ها را روی سرور نصب نکنیم؟

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

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

نصب یک ارائه‌دهنده

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

uses
  PDFium;

type
  TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
  public
    function ResolveFont(const Request: TPdfSystemFontRequest;
      out Font: TPdfSystemFontData): Boolean;
  end;

function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
  out Font: TPdfSystemFontData): Boolean;
var
  Path: string;
begin
  // نگاشت قطعی: نام چهره به‌علاوه وزن و ایتالیک تعیین می‌کنند
  // کدام فایل را برای این درخواست عرضه کنیم
  Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
    Request.Italic, Request.Charset);
  Result := Path <> '';
  if not Result then
    Exit;
  Font.FaceName := Request.FaceName;
  Font.FontData := LoadFileBytes(Path);   // بایت‌های کامل sfnt یا TTC
  Font.Charset := Request.Charset;
  Font.TTCIndex := 0;                     // اندیس درون یک مجموعه
end;

var
  Policy: TPdfSystemFontPolicy;
begin
  Policy := TPdfSystemFontPolicy.Default;
  Policy.AllowDefaultFallback := False;   // میزبان همه‌چیز را تصمیم می‌گیرد
  Policy.AllowFaceSubstitution := False;  // یک نام چهره متفاوت را رد کن
  Policy.MaxFontBytes := 32 * 1024 * 1024;
  Policy.MaxCacheEntries := 64;

  ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
  // فقط اکنون کتابخانه را بارگذاری کن و اسناد را باز کن
end;

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

پرچم‌های خط‌مشی واقعاً چه چیزی را تصمیم می‌گیرند؟

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

AllowFaceSubstitution این را تعیین می‌کند که آیا یک ارائه‌دهنده می‌تواند با نام چهره‌ای متفاوت از آنچه درخواست شده پاسخ دهد. خاموش‌کردن آن، جایگزینی را به یک تصمیم صریح تبدیل می‌کند نه یک تصادف، که وقتی یک سند فونتی را نام می‌برد که متریک‌هایش به‌اندازه‌ای متفاوت است که صفحه‌بندی را تغییر می‌دهد، اهمیت دارد

مؤلفه هر پاسخ ارائه‌دهنده را پیش از رسیدن به PDFium اعتبارسنجی می‌کند: داده خالی رد می‌شود، فونت‌های بیش‌ازحدبزرگ در برابر MaxFontBytes رد می‌شوند، اندیس TTC بررسی می‌شود، و جدول‌های sfnt جداگانه از دایرکتوری فونت سرویس‌دهی می‌شوند وقتی PDFium یک جدول را می‌خواهد نه کل فایل را. آن آخرین قابلیت یعنی یک ارائه‌دهنده می‌تواند یک فایل فونت کامل را تحویل دهد و بگذارد مؤلفه به پرسش‌های سطح-جدول پاسخ دهد، به‌جای افشای اشیای خام پاسکالی در سراسر ABI سی

کش‌کردن بدون داده فونت آویزان

درخواست‌های نگاشت فونت در حین رندر مکرراً تکرار می‌شوند، بنابراین پاسخ‌ها با یک کلید که هر پارامتر انتخاب فونت را پوشش می‌دهد کش می‌شوند، و با ترتیب کم‌استفاده‌ترین-اخیر (LRU) کران‌دار، حذف می‌شوند. ظرافت آن، طول عمر است: PDFium ممکن است هنوز درحال خواندن بایت‌های فونتی باشد که ورودی کش آن به‌تازگی حذف شده

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

آیا ارائه‌دهنده روی نخ من فراخوانی می‌شود؟

نه، نه لزوماً. PDFium ممکن است نگاشت‌گر را از نخ‌های کارگر خودش فراخوانی کند، بنابراین یک پیاده‌سازی باید نخ-ایمن باشد. شمارنده‌های مشترک، کش و مشاهده پیکربندی هرکدام درون مؤلفه با بخش بحرانی مخصوص به خود محافظت می‌شوند، اما کد درون ResolveFont بر عهده شماست که آن را ایمن کنید

ایمن‌ترین شکل، ارائه‌دهنده‌ای است که به هیچ وضعیت مشترک قابل‌تغییری دست نمی‌زند: از یک جدول ساخته‌شده در زمان راه‌اندازی بخوانید، بایت‌ها را از یک فایل یا یک منبع بارگذاری کنید، برگردانید. اگر یک جست‌وجو به یک کش مشترک مخصوص خودتان نیاز دارد، آن را محافظت کنید. و استثناها را درون پیاده‌سازی خود نگه دارید، زیرا یک استثنای پاسکالی هرگز نباید از پشته PDFium بازکشیده (unwind) شود؛ مؤلفه در مرز ABI سی می‌گیرد و به یک شکست یا یک بازگشت پیش‌فرض اختیاری تبدیل می‌کند، اما تکیه‌بر آن به‌عنوان جریان کنترل عادی، کارایی را هزینه می‌کند و اشکالات را پنهان می‌کند. قواعد نخ‌مندی برای بقیه مؤلفه از همان اصولی که در انضباط قفل رندر آمده پیروی می‌کند

اثبات نگاشت در تولید

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

var
  Stats: TPdfSystemFontStatistics;
begin
  Stats := GetSystemFontProviderStatistics;
  Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
    [Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
     Stats.DefaultFallbackHits]));
  Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
    [Stats.RejectedProviderResponses, Stats.FailedRequests,
     Stats.ActiveHandles, Stats.CachedFonts]));

  // در یک اجرای انطباق با بازگشت پیش‌فرض غیرفعال، هر برخورد بازگشتی یا
  // درخواست شکست‌خورده یعنی یک سند به فونتی ارجاع داده که ما عرضه نمی‌کنیم
  if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
    raise Exception.Create('unmapped font encountered - update the font set');
end;

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

تأمین فونت، رندر و استخراج متن، همان نمونه کتابخانه را در سراسر دلفی، C++Builder و Lazarus به اشتراک می‌گذارند؛ جزئیات استقرار در صفحه PDFium Component برای دلفی شرح داده شده