Tehnički članak

Strukturirano izdvajanje PDF teksta u Delphiju s PDFium VCL

PDFiumPas vraća tekst stranice kao strukturu, a ne kao niz znakova. GetStructuredText proizvodi TPdfStructuredTextPage koji sadrži blokove, svaki od kojih sadrži retke, svaki od kojih sadrži stilizirane tekstualne odsječke, s granicama u prostoru stranice na svakoj razini i sačuvanim indeksima izvornih znakova, tako da se svaki fragment može mapirati natrag na temeljnu tekstualnu stranicu

Izdvajanje u obliku ravnog niza znakova, s kojim većina koda počinje, i dalje postoji te je i dalje ispravno za svoju namjenu. Prestaje biti dovoljno u trenutku kada trebate znati koje su riječi bile naslov, koje su pripadale lijevom stupcu, ili gdje se na stranici zapravo nalazi podudaranje

Zašto je ravni niz znakova pogrešan izlaz za većinu zadataka?

Zato što su pitanja koja ljudi postavljaju o izdvojenom tekstu gotovo nikad „koji su znakovi na ovoj stranici”. Ona su „koji je naslov”, „je li ovo tablica”, „pripada li ovaj odlomak poglavlju 4”, „gdje trebam nacrtati istaknuti dio”. Jedan niz znakova ne odgovara ni na jedno od toga, a svaki odgovor koji iz njega rekonstruirate heuristika je koju sada posjedujete

Dvostupčani rasporedi to čine konkretnim. Izdvojite dvostupčani članak kao niz znakova i, ovisno o tome kako je proizvođač napisao tok sadržaja, možete dobiti prvi stupac praćen drugim stupcem, ili možete dobiti prvi redak prvog stupca, prvi redak drugog stupca, drugi redak prvog stupca, i tako redom niz stranicu. Oboje proizlazi iz sukladnog PDF-a. Nijedno nije pogrešno na razini formata, jer PDF opisuje oznake na stranici, a ne strukturu dokumenta. Model temeljen na blokovima omogućuje ekstraktoru da eksplicitno donese odluku o redoslijedu i kaže vam koju je odluku donio

Redoslijed sadržaja ili fizički raspored?

TPdfStructuredTextOptions.ReadingOrder bira između roContentOrder i roPhysicalLayout, a ispravan odgovor ovisi o tome čemu više vjerujete, proizvođaču ili geometriji

Redoslijed sadržaja vraća tekst redoslijedom kojim ga tok sadržaja crta. To je brzo, a za dokumente koje generira pristojan proizvođač, obično i namijenjeni redoslijed čitanja. Fizički raspored ignorira redoslijed toka i rekonstruira redoslijed iz stvarnog položaja znakova, grupirajući ih u retke, a zatim u stupce. To je ono što želite za skenirane pa OCR-om obrađene stranice, za izlaz alata koji emitiraju tekst redoslijedom fonta, a ne redoslijedom čitanja, i za sve gdje je vizualni rezultat jedino na što se možete osloniti

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;                     // indeksirano od 1

    Options := TPdfStructuredTextOptions.Default;
    Options.ReadingOrder := roPhysicalLayout;
    Options.IncludeFontInfo := True;
    Options.IncludeSemantics := True;
    Options.MaxCharacters := 200000;         // fail-closed proračun

    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;

Što označavanje dodaje, a geometrija ne može?

Namjeru. Kada je IncludeSemantics uključen, blokovi iz označenog PDF-a nose Kind izveden iz stabla strukture, pa je naslov naslov zato što je to proizvođač tako rekao, a ne zato što mu je font bio veći od prosjeka. Vrste pokrivaju oblike koji su bitni za ponovnu upotrebu: cfParagraph, cfHeading s HeadingLevel, cfListItem, cfTableCell, cfCaption, cfFigure i fallback za neoznačeno cfPlain

Polje Source bilježi odakle je svaka klasifikacija došla, rosStructure za stablo strukture i rosHeuristic za zaključivanje, a to je polje koje treba bilježiti kada odlučujete koliko vjerovati pipelineu za izdvajanje kroz skup dokumenata. Slike su poseban slučaj vrijedan poznavanja: za blok cfFigure tekst dolazi iz alternativnog opisa, a ne iz bilo kojih glifova, budući da slika nema vlastitih znakova. Nepovezan alternativni tekst i dalje je prikazan umjesto da bude odbačen, što reviziji pristupačnosti omogućuje da vidi da opis postoji čak i kada ga ništa na stranici ne crta. Sam model označavanja obrađen je u članku validacija stabla strukture PDF/UA

Odsječci nose stiliziranje i porijeklo

Svaki TPdfStructuredTextSpan sadrži svoj tekst, svoje granice u prostoru stranice, FontName, FontSize, FontWeight i Angle, plus SourceStartIndex i SourceCharacterCount. Odsječci se prekidaju ondje gdje se stiliziranje mijenja, pa rečenica s tri podebljane riječi postaje tri odsječka, a rekonstrukcija naglaska u HTML-u ili Markdownu postaje pitanje čitanja svojstava, a ne pogađanja iz imena fontova

Ta su dva polja izvornog indeksa ono što izdvajanje pretvara u značajku, a ne u izvještaj. Pokazuju natrag u niz znakova stranice, što znači da se blok koji ste pronašli u pretrazi može pretvoriti u geometriju odabira na razini znaka ili pravokutnik isticanja bez drugog, drukčije uređenog prolaska kroz tekst; mehanika je opisana u članku vizualni odabir teksta u retku pomoću okvira znakova. Polje Angle znači više nego što izgleda: rotirani tekst u pečatu ili vodenom žigu slijeće u isti koordinatni prostor kao i osnovni tekst, a pipeline koji ignorira kut sasvim će spremno spojiti dijagonalni „DRAFT” usred odlomka

Proračun, i dva brojača kvalitete

MaxCharacters je fail-closed proračun, a ne postavka skraćivanja: stranica koja ga premaši zaustavlja se umjesto da tiho vrati dio sadržaja. Na putanji prihvata nepouzdanih datoteka to je ponašanje koje želite, jer je stranica s milijun znakova ili strojno generirano čudovište ili pokušaj da vaš ekstraktor postane najsporiji dio sustava

Dva brojača na vraćenoj stranici izravno opisuju kvalitetu izdvajanja. UnmappedCharacterCount broji znakove bez upotrebljivog Unicode mapiranja, što je klasičan simptom podskupa fonta ugrađenog bez /ToUnicode CMap-a; takav se tekst savršeno renderira, a izdvaja se kao potpuno beskorisno. GeometryFailureCount broji znakove čiji se granični okvir nije mogao odrediti, što narušava redoslijed fizičkog rasporeda. Bilježite oba. Skup dokumenata kod kojeg su ti brojevi dosljedno blizu nule može se indeksirati s povjerenjem, a onaj kod kojeg nisu govori vam da nekim proizvođačima u vašem pipelineu treba pažnja prije nego što ijedan rezultat nizvodno postane pouzdan

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;

Performanse na stvarnim stranicama

Izdvajanje s fizičkim rasporedom skuplji je način rada, a implementacija je izgrađena za stranice koje su stvarno velike: uređivanje znakova izvodi se u O(n log n) umjesto ponovljenim skeniranjem, međuspremnici redaka i odsječaka rastu geometrijski umjesto da se realociraju po svakom znaku, Unicode tekst gradi se u međuspremnicima umjesto konkatenacijom nizova, a pretrage fontova za susjedne tekstualne objekte predmemoriraju se. Ta kombinacija drži gustu stranicu od 5000 znakova predvidljivom, a ne kvadratnom

Za zadatak s velikim brojem stranica i dalje se isplati birati jeftiniji način rada gdje god možete. Koristite roContentOrder uz uključenu semantiku za označene dokumente kojima vjerujete, a roPhysicalLayout rezervirajte za skenirani i naslijeđeni materijal gdje je geometrija jedini signal. Ako vam treba samo obični niz znakova, jednostavniji API opisan u članku izdvajanje teksta iz PDF dokumenata ostaje brži put, a kada trebate pratiti tekst natrag do identifikatora označenog sadržaja, čitanje i pisanje BDC i MCID označenog sadržaja pokriva taj sloj

Model blokova također se čisto preslikava na ono što pipelineovi za dohvat žele: naslov sa svojim odlomcima jest komad teksta s naslovom, a granice omogućuju da citat pokazuje na lokaciju na stranici, a ne na cijeli dokument. PDFiumPas je Delphi i Lazarus komponenta izgrađena oko PDFium engine-a, dokumentirana s primjerima na stranici PDFium Delphi komponente