مقاله فنی

خواندن ویژگی‌های فونت PDF با PDFium Component در دلفی

هر کاراکتر قابل مشاهده در یک PDF ارجاعی به فونتی که آن را ترسیم کرده است به همراه دارد، و PDFium Component به شما امکان می‌دهد آن ارجاع را تا آبجکت فونت دنبال کنید و آنچه را که می‌داند بخوانید. واحد دسترسی کاراکتر است، نه سند: شما یک کاراکتر را بر اساس نمایه (index) آن در متن صفحه انتخاب می‌کنید و نام خانواده، نام پایه، وزن، زاویه ایتالیک، و اینکه آیا فونت اصلی در واقع در داخل فایل حمل می‌شود را می‌پرسید. این ویژگی آخر همان چیزی است که اکثر تجزیه و تحلیل‌ها واقعاً به دنبال آن هستند، زیرا یک فونت جاسازی شده با سند سفر می‌کند و فونتی که جاسازی نشده قولی است که دستگاه خواننده اتفاقاً همان فونت را نصب کرده است

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

خواندن فونت پشت یک کاراکتر

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

procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
  Report: TStringList;
  PtSize: Single;
begin
  Report := TStringList.Create;
  try
    PtSize := Pdf.FontSize[CharIndex];

    Report.Add('Character : ' + Pdf.Character[CharIndex]);
    Report.Add('Family    : ' + Pdf.FontFamilyName[CharIndex]);
    Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
    Report.Add('Weight    : ' + IntToStr(Pdf.FontWeight[CharIndex]));
    Report.Add('Italic    : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
    Report.Add('Size      : ' + FormatFloat('0.0', PtSize) + ' pt');
    Report.Add('Ascent    : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
    Report.Add('Descent   : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
    Report.Add('Embedded  : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));

    ShowMessage(Report.Text);
  finally
    Report.Free;
  end;
end;

چند مورد از امضاها (signatures) افرادی را که از کتابخانه‌های دیگر می‌آیند غافلگیر می‌کند. FontAscent و FontDescent دو آرگومان می‌گیرند، نمایه کاراکتر و اندازه پوینت، زیرا PDFium آن معیارها را در واحدهای فضای گلیف گزارش می‌کند که تنها زمانی تبدیل به پیکسل می‌شوند که آنها را با اندازه‌ای که متن در آن تنظیم شده است مقیاس‌بندی کنید. مقداری را که از قبل از FontSize[CharIndex] خوانده‌اید ارسال کنید و بالاروندگی (ascent) و پایین‌روندگی (descent) را در همان پوینت‌هایی که بقیه چیدمان دارند دریافت می‌کنید. پایین‌روندگی منفی برمی‌گردد، زیرا زیر خط کرسی (baseline) اندازه‌گیری می‌کند. نام خانواده و نام پایه عمداً رشته‌های جداگانه‌ای هستند: نام پایه ورودی خام /BaseFont از PDF است که اغلب دارای پیشوند زیرمجموعه مانند ABCDEF+ است، در حالی که نام خانواده نام پاک‌شده‌ای است که رندرکننده آن را حل می‌کند

تبدیل کلیک به نمایه کاراکتر

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

procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Index: Integer;
begin
  if not PdfView.Active then
    Exit;

  // 4 px of slack in each direction so a near-miss still hits the glyph.
  Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
  if Index < 0 then
    Exit;                      // clicked between glyphs; leave the panel alone

  PdfView.CurrentCharIndex := Index;
  DescribeFontAt(PdfView.Pdf, Index);
end;

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

جاسازی مهمترین ویژگی است

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

procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
  Embedded, External: TStringList;
  I: Integer;
  Name: string;
begin
  Embedded := TStringList.Create;
  External := TStringList.Create;
  try
    Embedded.Sorted := True;
    Embedded.Duplicates := dupIgnore;
    External.Sorted := True;
    External.Duplicates := dupIgnore;

    for I := 0 to Pdf.CharacterCount - 1 do
    begin
      Name := Pdf.FontBaseName[I];
      if Name = '' then
        Continue;              // generated spaces and the like have no font
      if Pdf.FontIsEmbedded[I] then
        Embedded.Add(Name)
      else
        External.Add(Name);
    end;

    if External.Count > 0 then
      ShowMessage(IntToStr(External.Count) +
        ' non-embedded font(s):' + sLineBreak + External.Text)
    else
      ShowMessage('All ' + IntToStr(Embedded.Count) +
        ' font(s) on this page are embedded.');
  finally
    Embedded.Free;
    External.Free;
  end;
end;

دو جزئیات این موضوع را صادقانه نگه می‌دارند. اول، CharacterCount به ازای هر صفحه است، بنابراین ممیزی کل سند به این معنی است که Pdf.PageNumber را به نوبه خود روی هر صفحه تنظیم کنید و حلقه را دوباره اجرا کنید و نتایج را ادغام کنید. دوم، لایه متنی شامل کاراکترهای تولید شده مانند فاصله‌هایی است که خواننده بین کلمات استنباط می‌کند و هیچ آبجکت فونتی در پشت آنها وجود ندارد. بررسی نام پایه خالی از آنها صرف نظر می‌کند تا از ثبت یک مورد خیالی (phantom) جلوگیری کند. نام پایه کلید مناسب برای حذف موارد تکراری در اینجا است، زیرا پیشوند زیرمجموعه‌ای که به همراه دارد، دو زیرمجموعه مختلف از یک خانواده را متمایز می‌کند، که این معمولاً همان چیزی است که می‌خواهید بدانید

بیرون کشیدن فونت جاسازی شده

هنگامی که یک فونت جاسازی شده است، می‌توانید بایت‌های آن را مستقیماً بخوانید. FontData برنامه فونت خام، همان داده‌های TrueType یا CFF که PDF حمل می‌کند را برمی‌گرداند، که برای نوشتن یک فایل فونت مستقل یا برای اثرانگشت‌نگاری فونت در برابر یک کتابخانه شناخته شده کافی است. وقتی فونت جاسازی نشده باشد یک آرایه خالی برمی‌گرداند، بنابراین بررسی جاسازی و بررسی طول با هم از نوشتن محافظت می‌کنند

procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
  const OutputFile: string);
var
  Data: TBytes;
  Stream: TFileStream;
begin
  if not Pdf.FontIsEmbedded[CharIndex] then
  begin
    ShowMessage('That glyph''s font is not embedded; nothing to extract.');
    Exit;
  end;

  Data := Pdf.FontData[CharIndex];
  if Length(Data) = 0 then
    Exit;

  Stream := TFileStream.Create(OutputFile, fmCreate);
  try
    Stream.WriteBuffer(Data[0], Length(Data));
  finally
    Stream.Free;
  end;
  ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;

بایت‌ها زیرمجموعه جاسازی شده هستند، نه فونت خرده‌فروشی اصلی، بنابراین آنچه دریافت می‌کنید معمولاً فقط گلیف‌هایی را پوشش می‌دهد که سند در واقع استفاده کرده است. این امر دقیقاً برای امور قانونی (forensics) و تأیید درست است و برای استفاده مجدد نامناسب است؛ زیرمجموعه‌ای از Times New Roman که شامل سی گلیف است فونتی نیست که بتوانید با آن نصب کرده و تایپ کنید. با استخراج به عنوان راهی برای بررسی آنچه ارسال شده است برخورد کنید، نه به عنوان یک ابزار بازیابی فونت. اگر به نام پایه منطبق برای برچسب‌گذاری خروجی نیاز دارید، FontBaseName[CharIndex] را در کنار داده‌ها بخوانید و اگر می‌خواهید فقط نام خانواده را داشته باشید، تگ پیشروی زیرمجموعه را حذف کنید

درک عدد وزن

FontWeight کلاس وزن عددی را برمی‌گرداند، همان مقیاس ۱۰۰ تا ۹۰۰ که CSS استفاده می‌کند، که در آن ۴۰۰ معمولی و ۷۰۰ بولد (bold) است. PDFium هر چیزی را که فونت اعلام می‌کند گزارش می‌دهد، که همیشه یک صد گرد (round) نیست؛ یک فونت می‌تواند ۳۵۰ یا ۶۵۰ را اعلام کند، و در نظر گرفتن هر چیزی برابر یا بالاتر از ۶۰۰ به عنوان "به اندازه کافی بولد که مهم باشد" بهتر از آزمایش دقیقاً ۷۰۰ عمل می‌کند. زاویه ایتالیک یک سیگنال همراه است: یک مقدار غیر صفر، که معمولاً منفی است، به این معنی است که فونت دارای طراحی اریب یا ایتالیک واقعی است و صفر به معنای عمودی است. آنها با هم به شما اجازه می‌دهند بدون رندر کردن چیزی یک اجرای bold-italic را از اجرای معمولی تشخیص دهید، که این نوعی بررسی است که یک عبور پیش‌پرواز (preflight pass) یا ممیزی دسترسی‌پذیری می‌خواهد به صورت عمده انجام دهد

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

ویژگی‌های فونت نشان داده شده در اینجا بخشی از کامپوننت PDFium Delphi VCL Component هستند