Tehnički članak

Strukturisano izvlačenje PDF teksta u Delphi-ju sa PDFium

PDFiumPas vraća tekst stranice kao strukturu, a ne kao nisku. GetStructuredText proizvodi TPdfStructuredTextPage koji sadrži blokove, od kojih svaki sadrži redove, od kojih svaki sadrži stilizovane raspone, sa granicama u prostoru stranice na svakom nivou, dok su indeksi izvornih karaktera očuvani tako da se svaki fragment može mapirati nazad na osnovnu tekstualnu stranicu

Izvlačenje u obliku ravne niske, sa kojim većina koda počinje, i dalje postoji i ispravno je za svoju svrhu. Prestaje da bude dovoljno u trenutku kada morate da znate koje su reči bile naslov, koje su pripadale levoj koloni, ili gde se na stranici zapravo nalazi poklapanje

Zašto je ravna niska pogrešan izlaz za većinu poslova?

Zato što pitanja koja ljudi postavljaju o izvučenom tekstu gotovo nikada nisu „koji su karakteri na ovoj stranici”. Ona su „šta je naslov”, „da li je ovo tabela”, „da li ovaj pasus pripada odeljku 4”, „gde da nacrtam isticanje”. Jedna niska ne odgovara ni na jedno od njih, a svaki odgovor koji iz nje rekonstruišete jeste heuristika koju sada posedujete

Rasporedi sa dve kolone čine ovu poentu konkretnom. Izvucite dvokolonski članak kao nisku i, u zavisnosti od toga kako je proizvođač napisao tok sadržaja, možete dobiti kolonu jedan praćenu kolonom dva, ili možete dobiti red jedan kolone jedan, red jedan kolone dva, red dva kolone jedan, i tako niz stranicu. Oba izlaze iz usaglašenog PDF-a. Nijedno nije pogrešno na nivou formata, jer PDF opisuje oznake na stranici, a ne kostur dokumenta. Model zasnovan na blokovima omogućava ekstraktoru da eksplicitno donese odluku o redosledu i kaže vam koju je odluku doneo

Redosled sadržaja ili fizički raspored?

TPdfStructuredTextOptions.ReadingOrder bira između roContentOrder i roPhysicalLayout, a ispravan odgovor zavisi od toga čemu više verujete, proizvođaču ili geometriji

Redosled sadržaja vraća tekst u sekvenci u kojoj ga crta tok sadržaja. To je brzo, a za dokumente generisane od strane dobronamernog proizvođača obično je i nameravani redosled čitanja. Fizički raspored ignoriše sekvencu toka i rekonstruiše redosled na osnovu toga gde karakteri zapravo sede, grupišući ih u redove, a zatim u kolone. To je ono što želite za stranice koje su skenirane pa OCR-ovane, za izlaz iz alata koji emituju tekst po redosledu fonta, a ne po redosledu čitanja, i za sve gde je vizuelni rezultat jedino na šta možete da se oslonite

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 budžet

    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;

Šta označavanje (tagging) dodaje što geometrija ne može?

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

Polje Source beleži odakle je svaka klasifikacija potekla, rosStructure za stablo strukture i rosHeuristic za zaključivanje, a to je polje koje treba logovati kada odlučujete koliko duboko da verujete pipeline-u za izvlačenje kroz skup dokumenata. Figure su poseban slučaj koji vredi znati: za blok cfFigure tekst dolazi iz alternativnog opisa, a ne iz bilo kojih glifova, pošto figura nema sopstvene karaktere. Nespojen alternativni tekst se i dalje predstavlja umesto da bude odbačen, što omogućava reviziji pristupačnosti da vidi da opis postoji čak i kada ga ništa na stranici ne crta. Sam model označavanja pokriven je u validaciji stabla strukture PDF/UA

Rasponi nose stilizovanje i poreklo

Svaki TPdfStructuredTextSpan nosi svoj tekst, svoje granice u prostoru stranice, FontName, FontSize, FontWeight i Angle, plus SourceStartIndex i SourceCharacterCount. Rasponi se prekidaju tamo gde se stilizovanje menja, tako da rečenica sa tri podebljane reči postaje tri raspona, a rekonstrukcija isticanja u HTML-u ili Markdown-u postaje pitanje čitanja svojstava, a ne pogađanja na osnovu imena fontova

Ta dva polja izvornog indeksa su ona koja pretvaraju izvlačenje u funkciju, a ne u izveštaj. Ona pokazuju nazad u sekvencu karaktera stranice, što znači da blok koji ste poklopili u pretrazi može da se pretvori u geometriju selekcije na nivou karaktera ili u pravougaonik za isticanje bez drugog, drugačije poređanog prolaska kroz tekst; mehanika je opisana u vizuelnoj selekciji linije teksta pomoću kutija karaktera. Polje Angle je važnije nego što deluje: rotiran tekst u pečatu ili vodenom žigu sleti u isti koordinatni prostor kao i telo teksta, a pipeline koji ignoriše ugao rado će spojiti dijagonalan „DRAFT” usred pasusa

Budžet, i dva brojača kvaliteta

MaxCharacters je fail-closed budžet, a ne podešavanje za skraćivanje: stranica koja ga premaši se zaustavlja umesto da tiho vrati deo sadržaja. Na putanji nepouzdanog prijema to je ponašanje koje želite, jer je stranica sa milion karaktera ili mašinski generisano čudovište ili pokušaj da vaš ekstraktor postane najsporiji deo sistema

Dva brojača na vraćenoj stranici direktno opisuju kvalitet izvlačenja. UnmappedCharacterCount broji karaktere bez upotrebljivog Unicode mapiranja, što je klasičan simptom podskupljenog fonta ugrađenog bez /ToUnicode CMap-a; takav tekst se renderuje savršeno, a izvlači se kao ništa korisno. GeometryFailureCount broji karaktere čija granična kutija nije mogla da se odredi, što degradira redosled fizičkog rasporeda. Logujte oba. Skup dokumenata kod koga su ti brojevi dosledno blizu nule može se indeksirati sa poverenjem, a onaj kod koga nisu govori vam da nekim proizvođačima u vašem pipeline-u treba pažnja pre nego što ijedan nizvodni rezultat 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

Izvlačenje sa fizičkim rasporedom je skup režim, a implementacija je izgrađena za stranice koje su zaista velike: redosled karaktera se izvršava u O(n log n) umesto ponovljenim skeniranjem, baferi redova i raspona rastu geometrijski umesto da se realociraju po karakteru, Unicode tekst se gradi u baferima umesto konkatenacijom niski, a pretrage fontova za susedne tekstualne objekte se keširaju. Ta kombinacija je ono što drži gustu stranicu od 5.000 karaktera predvidljivom, a ne kvadratnom

Za posao intenzivan brojem stranica i dalje vredi birati jeftiniji režim gde god možete. Koristite roContentOrder sa uključenom semantikom za označene dokumente kojima verujete, a rezervišite roPhysicalLayout za skeniran i zastareo materijal gde je geometrija jedini signal. Ako vam je potrebna samo obična niska, jednostavniji API opisan u izvlačenju teksta iz PDF dokumenata ostaje brža putanja, a kada treba da pratite tekst nazad do identifikatora obeleženog sadržaja, čitanje i pisanje BDC i MCID obeleženog sadržaja pokriva taj sloj

Model blokova se takođe čisto preslikava na ono što žele pipeline-ovi za pretragu (retrieval): naslov sa svojim pasusima je deo (chunk) sa naslovom, a granice omogućavaju da citat pokazuje na lokaciju na stranici, a ne na dokument. PDFiumPas je Delphi i Lazarus komponenta oko PDFium mehanizma, dokumentovana sa primerima na stranici PDFium Delphi komponente