مقاله فنی

استخراج ساختاریافته متن PDF در Delphi با PDFium VCL

PDFiumPas متن صفحه را به‌صورت یک ساختار بازمی‌گرداند نه یک رشته. GetStructuredText یک TPdfStructuredTextPage تولید می‌کند که شامل بلوک‌هاست، هرکدام حاوی خط‌ها، هرکدام حاوی قطعه‌های سبک‌دار، همراه با کران‌های فضای صفحه در هر سطح و اندیس‌های کاراکتر مبدأ حفظ‌شده، تا هر قطعه بتواند به صفحه متنی زیرین بازنگاشت شود

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

چرا یک رشته مسطح خروجی نادرستی برای بیشتر کارهاست؟

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

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

ترتیب محتوا یا چیدمان فیزیکی؟

TPdfStructuredTextOptions.ReadingOrder بین roContentOrder و roPhysicalLayout انتخاب می‌کند، و پاسخ درست به این بستگی دارد که به کدام بیشتر اعتماد دارید، تولیدکننده یا هندسه

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

uses
  PDFium;

var
  Pdf: TPdf;
  Options: TPdfStructuredTextOptions;
  Page: TPdfStructuredTextPage;
  B, L: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'article.pdf';
    Pdf.LoadDocument;
    Pdf.PageNumber := 1;                     // مبنای 1

    Options := TPdfStructuredTextOptions.Default;
    Options.ReadingOrder := roPhysicalLayout;
    Options.IncludeFontInfo := True;
    Options.IncludeSemantics := True;
    Options.MaxCharacters := 200000;         // بودجه شکست‌بسته

    Page := Pdf.GetStructuredText(Options);

    for B := 0 to High(Page.Blocks) do
    begin
      if Page.Blocks[B].Kind = cfHeading then
        Emit(Format('H%d: %s',
          [Page.Blocks[B].HeadingLevel, Page.Blocks[B].Text]))
      else
        for L := 0 to High(Page.Blocks[B].Lines) do
          Emit(Page.Blocks[B].Lines[L].Text);
    end;
  finally
    Pdf.Free;
  end;
end;

برچسب‌گذاری چه چیزی اضافه می‌کند که هندسه نمی‌تواند؟

نیت. با فعال‌بودن IncludeSemantics، بلوک‌ها از یک PDF برچسب‌گذاری‌شده یک Kind برگرفته از درخت ساختار حمل می‌کنند، پس یک تیتر یک تیتر است چون تولیدکننده این را گفته، نه چون فونتش بزرگ‌تر از میانگین بوده. این انواع، شکل‌هایی را که برای استفاده مجدد اهمیت دارند پوشش می‌دهند: cfParagraph، cfHeading همراه یک HeadingLevel، cfListItem، cfTableCell، cfCaption، cfFigure و بازگشت‌پذیر بدون‌برچسب cfPlain

فیلد Source ثبت می‌کند هر طبقه‌بندی از کجا آمده، rosStructure برای درخت ساختار و rosHeuristic برای استنتاج، فیلدی که باید هنگام تصمیم‌گیری درباره اینکه چقدر به یک پایپ‌لاین استخراج در سراسر یک مجموعه سند اعتماد کنید لاگ کنید. شکل‌ها یک مورد خاص هستند که ارزش دانستن دارند: برای یک بلوک cfFigure، متن از توضیح جایگزین می‌آید نه از هیچ گلیفی، چون یک شکل کاراکتر خودش را ندارد. متن جایگزین بدون‌تطابق همچنان نمایش داده می‌شود به‌جای اینکه دور ریخته شود، همان چیزی که به یک بازبینی دسترس‌پذیری اجازه می‌دهد ببیند یک توضیح وجود دارد حتی وقتی هیچ‌چیز روی صفحه آن را رسم نمی‌کند. خود مدل برچسب‌گذاری در اعتبارسنجی درخت ساختار PDF/UA پوشش داده شده است

قطعه‌ها سبک‌دهی و منشأ را حمل می‌کنند

هر TPdfStructuredTextSpan متن خود، کران‌های فضای صفحه‌اش، FontName، FontSize، FontWeight و Angle، به‌همراه SourceStartIndex و SourceCharacterCount را نگه می‌دارد. قطعه‌ها هرجا سبک‌دهی تغییر کند می‌شکنند، پس جمله‌ای با سه کلمه پررنگ به سه قطعه تبدیل می‌شود، و بازسازی تأکید در HTML یا Markdown مسئله خواندن ویژگی‌هاست نه حدس‌زدن از نام‌های فونت

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

بودجه، و دو شمارنده کیفیت

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

دو شمارنده روی صفحه بازگردانده‌شده کیفیت استخراج را مستقیماً توصیف می‌کنند. UnmappedCharacterCount کاراکترهایی بدون نگاشت یونیکد قابل‌استفاده را می‌شمارد، که همان نشانه کلاسیک یک فونت زیرمجموعه جاسازی‌شده بدون یک CMap از نوع /ToUnicode است؛ متنی مثل آن کاملاً رندر می‌شود و به‌عنوان چیزی بی‌فایده استخراج می‌شود. GeometryFailureCount کاراکترهایی که کادر کران آن‌ها قابل‌تعیین نبوده را می‌شمارد، که ترتیب‌دهی چیدمان فیزیکی را تنزل می‌دهد. هردو را لاگ کنید. یک مجموعه سند که این عددها به‌طور پیوسته نزدیک صفر هستند می‌تواند با اطمینان نمایه‌سازی شود، و یکی که این‌طور نیست به شما می‌گوید برخی تولیدکننده‌ها در پایپ‌لاین شما پیش از اینکه هر نتیجه پایین‌دستی قابل‌اعتماد باشد به توجه نیاز دارند

var
  Page: TPdfStructuredTextPage;
  B, S, L: Integer;
  Emphasised: Boolean;
begin
  Page := Pdf.GetStructuredText(Options);

  if Page.UnmappedCharacterCount > 0 then
    Log(Format('page %d: %d characters without a Unicode mapping',
      [Page.PageNumber, Page.UnmappedCharacterCount]));
  if Page.GeometryFailureCount > 0 then
    Log(Format('page %d: %d characters without geometry',
      [Page.PageNumber, Page.GeometryFailureCount]));

  for B := 0 to High(Page.Blocks) do
    for L := 0 to High(Page.Blocks[B].Lines) do
      for S := 0 to High(Page.Blocks[B].Lines[L].Spans) do
      begin
        Emphasised := Page.Blocks[B].Lines[L].Spans[S].FontWeight >= 600;
        AppendRun(Page.Blocks[B].Lines[L].Spans[S].Text, Emphasised,
          Page.Blocks[B].Lines[L].Spans[S].SourceStartIndex);
      end;
end;

کارایی روی صفحات واقعی

استخراج چیدمان فیزیکی حالت پرهزینه است، و پیاده‌سازی برای صفحاتی که واقعاً بزرگ‌اند ساخته شده: ترتیب‌دهی کاراکتر در O(n log n) اجرا می‌شود نه با اسکن تکراری، بافرهای خط و قطعه به‌طور هندسی رشد می‌کنند نه با تخصیص دوباره به‌ازای هر کاراکتر، متن یونیکد در بافرها ساخته می‌شود نه با الحاق رشته، و جست‌وجوی فونت برای اشیاء متنی مجاور کش می‌شود. آن ترکیب همان چیزی است که یک صفحه متراکم 5000 کاراکتری را قابل‌پیش‌بینی نگه می‌دارد به‌جای درجه‌دوم

برای یک کار با تعداد صفحه بالا، همچنان ارزش دارد جایی که می‌توانید حالت ارزان‌تر را انتخاب کنید. از roContentOrder همراه با فعال‌بودن معنایی برای اسناد برچسب‌گذاری‌شده‌ای که به آن‌ها اعتماد دارید استفاده کنید، و roPhysicalLayout را برای مواد اسکن‌شده و قدیمی که هندسه تنها سیگنال است رزرو کنید. اگر تنها چیزی که نیاز دارید یک رشته ساده است، API ساده‌تر توضیح داده‌شده در استخراج متن از اسناد PDF مسیر سریع‌تر باقی می‌ماند، و وقتی نیاز دارید متن را تا شناسه‌های محتوای علامت‌گذاری‌شده ردیابی کنید، خواندن و نوشتن محتوای علامت‌گذاری‌شده BDC و MCID آن لایه را پوشش می‌دهد

مدل بلوک همچنین به‌روشنی به آنچه پایپ‌لاین‌های بازیابی می‌خواهند نگاشت می‌شود: یک تیتر همراه پاراگراف‌هایش یک تکه با یک عنوان است، و کران‌ها اجازه می‌دهند یک ارجاع به یک مکان روی یک صفحه اشاره کند نه به یک سند. PDFiumPas یک کامپوننت Delphi و Lazarus حول موتور PDFium است، مستندشده همراه مثال‌ها در صفحه کامپوننت Delphi PDFium