مقاله فنی

شناسایی glyphهای غایب PDF در زمان ترسیم در Delphi

یک glyph غایب در PDF یک خطا نیست. تولیدکننده نویسه‌ای را می‌خواهد که فونت انتخابی نمی‌تواند نگاشت کند، فونت اندیس glyph صفر برمی‌گرداند، و فایلی که بیرون می‌آید از نظر ساختاری معتبر است، همه‌جا باز می‌شود، و جعبه خالی نشان می‌دهد جایی که باید نام یا مبلغی باشد. هیچ‌کس در خط لوله تولید نمی‌فهمد. گیرنده می‌فهمد. HotPDF آن حلقه را با TrackUnresolvedGlyphs می‌بندد: روشنش کنید و مسیر ترسیم متن هر نقطه کدی را که جست‌وجوی glyph آن به اندیس صفر می‌رسد ثبت می‌کند، و OnUnresolvedGlyph را به‌ازای هر یافته یکتا یک‌بار می‌آتشاند، همراه نقطه کد، فونتی که روی آن شکست خورد، خط نوشتاری که به آن تعلق دارد، و پیشنهادی از فونت‌هایی که آن را پوشش می‌دهند

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

چرا یک glyph غایب هیچ چیزی پرتاب نمی‌کند؟

چون ISO 32000 هیچ تعهدی بر تولیدکننده نمی‌گذارد که پوشش را راستی‌آزمایی کند، و اندیس glyph صفر یک glyph مشروع است. آن .notdef است، که طرح‌بیرونی‌اش را طراح فونت انتخاب می‌کند: معمولاً یک مستطیل خالی یا توخالی، گاهی هیچ‌چیز. بیننده‌ای که آن را می‌کشد درست رفتار می‌کند. استخراج متن حتی می‌تواند نویسه‌های درست را برگرداند، چون نگاشت /ToUnicode از متن مبدأ نوشته می‌شود نه از طرح‌های بیرونی، پس یک بررسی رفت‌وبرگشت خودکار با کمال میل سندی را که متن مرئی‌اش حفره دارد پاس می‌کند

نمودار اینکه چرا یک glyph غایب PDF بی‌صدا می‌ماند وقتی glyph صفر جعبه خالی می‌کشد و استخراج ToUnicode بررسی‌های رفت‌وبرگشت را پاس می‌کند
glyph صفر یک پاسخ مشروع .notdef است و /ToUnicode از متن مبدأ نوشته می‌شود، پس هیچ‌چیز در خط لوله از شکاف باخبر نمی‌شود

پیامد عملی این است که پوشش باید در لحظه ترسیم بررسی شود، وقتی کتابخانه هنوز می‌داند کدام نقطه کد درخواست شده و کدام glyph فونت واقعاً پیشنهاد کرده. بعدش اطلاعات رفته است

آشکارساز باید وضعیت زیرمجموعه را بپاید، نه device context را

اینجاست که اولین پیاده‌سازی غلط رفت، و دلیلش ارزش فهمیدن دارد چون به هر بررسی پوششی که به یک خط لوله متنی پیچ شود تعمیم می‌یابد. HotPDF دو مسیر متنی دارد. یکی از طریق یک فونت TrueType یونیکد ثبت‌شده با یک نگاشت نویسه درون‌حافظه‌ای که در زمان ثبت ساخته شده ساطع می‌کند. دیگری یک مسیر GDI قدیمی است که به‌ازای هر ران نویسه یک device context و هندل فونت تازه می‌سازد

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

یک تله دوم کنارش نشسته. نام خانواده GDI یک فونت و نام PostScript استخراج‌شده از باینری فونت در زمان ثبت رشته‌های متفاوتی‌اند، و نه به شکلی که بتوان نرمال‌سازی‌شان کرد: خانواده‌ای به نام Arial Unicode MS نام PostScript مربوط به ArialMT را حمل می‌کند. هر کنترلی که به‌صورت «آیا فونت انتخاب‌شده فعلی همان است که ثبت کردیم» نوشته شده و با نام مقایسه می‌کند، کد مرده‌ای است که هرگز آتش نمی‌گیرد. روی وضعیت کنترل کنید، هرگز روی نام فونت‌ها نه

جریان آشکارسازی glyph حل‌نشده در HotPDF که کنترل وضعیت زیرمجموعه، جست‌وجوی GetUnicodeGlyphForCodepoint و سیم‌کشی رخداد OnUnresolvedGlyph را نشان می‌دهد
پوشش از نگاشت فونت یونیکد ثبت‌شده داوری می‌شود نه از GDI، و هر نقطه کد یکتا یک رخداد با پیشنهاد فونت می‌آتشاند

آشکارساز glyph را با ایموجی تست نکنید

حالت تست بدیهی یک صورت خندان است، و شما را قانع می‌کند که آشکارساز خراب است. نقطه‌های کد ایموجی رایج در صفحه‌های astral از طریق یک مسیر سنتز private-use حل می‌شوند که مستقیم به یک اندیس glyph نگاشت‌شان می‌کند، پس هرگز به شاخه عمومی پوشش نمی‌رسند. آشکارساز درست رفتار می‌کند و تست مسیر غلط را می‌سنجد

به‌جایش از یک نقطه کد تخصیص‌نیافته استفاده کنید. U+0378 در یونیکد برای همیشه تخصیص‌نیافته است، پس هیچ فونتی نمی‌تواند به‌طور مشروع نگاشتش کند، و دقیقاً همان شاخه‌ای را که می‌خواهید راستی‌آزمایی کنید ورز می‌دهد. این تمایز میان «ویژگی خراب است» و «تست ورودی‌ای برداشته که از ویژگی عبور می‌کند» ساعتی واقعی خرج می‌گیرد، و نقطه‌های کد تخصیص‌نیافته ارزان‌ترین راه اجتناب از آن است

type
  TCoverageAudit = class
  private
    FFindings: TStringList;
  public
    procedure Handle(Sender: TObject;
      const Info: THPDFUnresolvedGlyphInfo);
    property Findings: TStringList read FFindings;
  end;

procedure TCoverageAudit.Handle(Sender: TObject;
  const Info: THPDFUnresolvedGlyphInfo);
begin
  // به‌ازای هر نقطه کد یکتا یک‌بار آتش می‌گیرد، نه به‌ازای هر وقوع
  FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
    [Info.CodePoint, String(Info.FontName), Ord(Info.Script),
     String(Info.SuggestedFonts)]));
end;

// سیم‌کشی آن به یک کار تولید
Pdf := THotPDF.Create(nil);
try
  Pdf.TrackUnresolvedGlyphs := True;
  Pdf.OnUnresolvedGlyph := Audit.Handle;
  Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
  Pdf.BeginDoc;
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
  Pdf.EndDoc;
  if Audit.Findings.Count > 0 then
    // کار را مردود کنید به‌جای حمل صفحه‌ای پر از جعبه
    raise Exception.Create(Audit.Findings.Text);
finally
  Pdf.Free;
end;

زنجیره‌های fallback به‌ازای هر خط نوشتاری‌اند، نه به‌ازای هر فونت

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

نمودار fallback فونت به‌ازای هر خط نوشتاری که خط‌های نوشتاری hfsCJK و hfsArabic و hfsEmoji و hfsOther را به زنجیره‌های فونت جایگزین مرتب در HotPDF نگاشت می‌کند
هر خط نوشتاری زنجیره مرتب خودش را می‌گیرد، پس فونت بدنه لاتینی که هان یا عربی یا ایموجی ندارد به فونتی که پوشش می‌دهد می‌افتد
// THPDFFontScript covers hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji and hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
  ['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);

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

چگونه رفتار را بدون به‌خطر انداختن مسیر موجود پس از نصب اضافه کنیم

همان انتشار یک fallback جدول kern قدیمی برای فاصله جفتی افزود، و نحوه قلمروگیریش الگویی ارزش کپی‌کردن است. به‌جای افزودن یک نقطه تصمیم جدید به منطق kerning، fallback از شاخه خروج-زودهنگامی آویخت که از قبل برای فونت‌های بدون جدول GPOS وجود داشت. یک فونت مدرن با GPOS هرگز به آن نمی‌رسد، پس رفتارش با ساخت تغییر نکرده نه با تست. مسیرهایی که فونت یونیکد ثبت نمی‌کنند دو آفست صفر تولید می‌کنند، پس آن‌ها هم تغییرنکرده‌اند

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

ازش یک کنترل بسازید، نه یک لاگ

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

در تولید همان handler بهتر است به‌عنوان تلمتری استفاده شود: نقطه کد و فونت را ثبت کنید، به سرو دادن سند ادامه دهید، و بگذارید جمع بگوید کدام خط نوشتاری را بعدی به مجموعه فونت استقرار اضافه کنید. رفتار رندر برای فونت‌های تعبیه‌شده و جایگزین‌شده بیشتر در رندر glyphهای فونت تعبیه‌شده پوشش داده شده، و فهرست کامل پراپرتی‌ها شامل TrackUnresolvedGlyphs در صفحه محصول HotPDF Delphi PDF component مستند شده است