مقاله فنی

جایگزینی خودکار فونت در PDFlibPas برای متن CJK و ایموجی

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

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

چرا متن پشتیبانی‌نشده به‌جای صدور خطا ناپدید می‌شود؟

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

هیچ‌چیز در ISO 32000-1 تولیدکننده را ملزم به توجه به این موضوع نمی‌کند. تولیدکننده‌ای که متن را بدون بررسی پوشش می‌نویسد، یک PDF از نظر فنی منطبق تولید می‌کند که به‌طور بی‌صدا محتوا از دست داده، و این افت هفته‌ها بعد روی صفحه مشتری نمایان می‌شود. به همین دلیل است که قابلیت جایگزینی فونت و گزارش گلیف‌های گمشده با هم عرضه می‌شوند: حل‌کردن آنچه قابل حل است تنها نیمی از کار است، و گزارش‌دادن آنچه قابل حل نبود نیمه دیگر آن است

جایگزینی بر اساس خوشه انجام می‌شود، نه بر اساس نقطه کد

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

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

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.SetAutomaticFontFallback(1);

    // ترتیب جستجو: اولین تطبیق برنده است، پس گسترده‌ترین فونت‌ها را در انتها قرار دهید
    Lib.AddFontFallback('Microsoft YaHei');   // چینی ساده‌شده
    Lib.AddFontFallback('Meiryo');            // ژاپنی
    Lib.AddFontFallback('Segoe UI Symbol');
    Lib.AddFontFallback('Segoe UI Emoji');

    Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);

    Lib.AddTrueTypeFont('Arial', 1);          // 1 = جاسازی فونت
    Lib.SetTextSize(11);
    Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
    Lib.DrawText(72, 700, 'Delivery status: on time');

    Lib.SaveToFile('invoice.pdf');
  finally
    Lib.Free;
  end;
end;

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

گزارش یا توقف: کدام نوع خرابی را می‌خواهید؟

SetMissingGlyphPolicy مقدار PDF_MISSING_GLYPH_REPORT، که پیش‌فرض سازگار است، یا PDF_MISSING_GLYPH_ABORT را می‌پذیرد. تحت سیاست گزارش، عملیات متن ادامه پیدا می‌کند، نقاط کد حل‌نشدنی مانند قبل حذف می‌شوند و هر کدام ثبت می‌شود. تحت سیاست توقف، عملیات متن پیش از نوشتن هرگونه محتوا رد می‌شود و LastErrorCode روی 521 تنظیم می‌شود

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

var
  Lib: TPDFlib;
  Report: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
    // ... ساخت سند ...

    if Lib.DrawText(72, 660, CustomerName) <> 1 then
      if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
      begin
        Report := Lib.GetMissingGlyphReportJSON;
        // {"valid":false,"policy":1,"eventCount":1,"events":[
        //   {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
        //    "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
        //    "fontType":"TrueType","operation":"DrawText"}]}
        EscalateToOperator(Report);
      end;
  finally
    Lib.Free;
  end;
end;

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

اندازه‌گیری و رسم باید هم‌خوانی داشته باشند

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

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

فقط آنچه استفاده کرده‌اید جاسازی می‌شود

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

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

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

PDFlibPas یک کتابخانه PDF برای Delphi، C++Builder و Lazarus است که رابط‌های DLL و ActiveX متناظر نیز دارد، بنابراین APIهای جایگزینی و گلیف گمشده از فراخوانی‌کننده‌های غیر Pascal نیز در دسترس هستند. مستندات کامل در صفحه کتابخانه PDF Delphi PDFlibPas موجود است