Odborný článok

Štruktúrovaná extrakcia textu PDF v Delphi s PDFium VCL

PDFiumPas vracia text stránky ako štruktúru namiesto reťazca. GetStructuredText vyprodukuje TPdfStructuredTextPage obsahujúci bloky, z ktorých každý drží riadky, a každý riadok drží štýlované spany, s hranicami v priestore stránky na každej úrovni a so zachovanými indexmi zdrojových znakov, takže sa dá ľubovoľný fragment namapovať späť na podkladovú textovú stránku

Extrakcia do plochého reťazca, ktorou väčšina kódu začína, je stále k dispozícii a stále správna na svoj účel. Prestáva stačiť vo chvíli, keď potrebujete vedieť, ktoré slová boli nadpis, ktoré patrili do ľavého stĺpca, alebo kde na stránke sa zhoda skutočne nachádza

Prečo je plochý reťazec nesprávnym výstupom pre väčšinu úloh?

Pretože otázky, ktoré si ľudia kladú o extrahovanom texte, takmer nikdy nie sú „aké znaky sú na tejto stránke“. Sú to „aký je nadpis“, „je toto tabuľka“, „patrí tento odstavec do sekcie 4“, „kam mám nakresliť zvýraznenie“. Jediný reťazec neodpovie na žiadnu z nich, a každá odpoveď, ktorú z neho zrekonštruujete, je heuristika, ktorú teraz vlastníte

Dvojstĺpcové rozloženia robia tento bod konkrétnym. Extrahujte dvojstĺpcový článok ako reťazec a v závislosti od toho, ako producent napísal obsahový tok, môžete dostať prvý stĺpec nasledovaný druhým stĺpcom, alebo môžete dostať prvý riadok prvého stĺpca, prvý riadok druhého stĺpca, druhý riadok prvého stĺpca, a tak ďalej po celej stránke. Oboje vychádza z vyhovujúceho PDF. Ani jedno nie je na úrovni formátu nesprávne, pretože PDF opisuje značky na stránke, nie osnovu dokumentu. Model založený na blokoch umožňuje extraktoru urobiť rozhodnutie o poradí explicitne a povedať vám, ktoré rozhodnutie urobil

Poradie obsahu, alebo fyzické rozloženie?

TPdfStructuredTextOptions.ReadingOrder vyberá medzi roContentOrder a roPhysicalLayout, a správna odpoveď závisí od toho, čomu dôverujete viac — producentovi, alebo geometrii

Poradie obsahu vracia text v poradí, v akom ho kreslí obsahový tok. To je rýchle, a pri dokumentoch vygenerovaných slušne sa správajúcim producentom to typicky býva zamýšľané poradie čítania. Fyzické rozloženie ignoruje poradie toku a rekonštruuje poradie z toho, kde znaky skutočne sedia, zoskupovaním do riadkov a potom do stĺpcov. To je to, čo chcete pri skenovaných a následne OCR-ovaných stránkach, pri výstupe z nástrojov, ktoré vypisujú text v poradí písma namiesto poradia čítania, a pri čomkoľvek, kde je vizuálny výsledok jediné, na čo sa môžete spoľahnúť

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;                     // číslované od 1

    Options := TPdfStructuredTextOptions.Default;
    Options.ReadingOrder := roPhysicalLayout;
    Options.IncludeFontInfo := True;
    Options.IncludeSemantics := True;
    Options.MaxCharacters := 200000;         // fail-closed rozpoč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;

Čo pridáva označovanie, čo geometria nedokáže?

Zámer. Keď je zapnuté IncludeSemantics, bloky z označeného PDF nesú Kind prevzatý zo stromu štruktúry, takže nadpis je nadpisom preto, že to tak povedal producent, nie preto, že jeho písmo bolo väčšie než priemer. Druhy pokrývajú tvary, na ktorých záleží pri opätovnom použití: cfParagraph, cfHeading s HeadingLevel, cfListItem, cfTableCell, cfCaption, cfFigure a neoznačený fallback cfPlain

Pole Source zaznamenáva, odkiaľ pochádza každá klasifikácia — rosStructure pre strom štruktúry a rosHeuristic pre odvodenie — čo je pole, ktoré sa oplatí logovať pri rozhodovaní, nakoľko dôverovať pipeline extrakcie naprieč súborom dokumentov. Obrázky sú špeciálny prípad, ktorý stojí za to poznať: pri bloku cfFigure pochádza text z alternatívneho popisu, nie z akýchkoľvek glyfov, keďže obrázok nemá vlastné znaky. Nepriradený alternatívny text sa aj tak reprezentuje namiesto toho, aby sa zahodil, čo umožňuje auditu prístupnosti vidieť, že popis existuje, aj keď ho na stránke nič nekreslí. Samotný model označovania je opísaný v článku validácia stromu štruktúry PDF/UA

Spany nesú štýlovanie a pôvod

Každý TPdfStructuredTextSpan nesie svoj text, svoje hranice v priestore stránky, FontName, FontSize, FontWeight a Angle, plus SourceStartIndex a SourceCharacterCount. Spany sa lámu tam, kde sa mení štýlovanie, takže veta s tromi tučnými slovami sa zmení na tri spany, a rekonštrukcia zvýraznenia v HTML alebo Markdown je otázkou čítania vlastností, nie hádania z mien písiem

Práve tieto dve polia zdrojového indexu menia extrakciu na funkciu, nie len na report. Ukazujú späť do postupnosti znakov stránky, čo znamená, že blok, ktorý ste našli pri vyhľadávaní, sa dá previesť na geometriu výberu na úrovni znakov alebo na obdĺžnik zvýraznenia bez druhého, inak usporiadaného prechodu cez text; mechanika je opísaná v článku vizuálny výber textového riadku pomocou rámčekov znakov. Pole Angle je dôležitejšie, než vyzerá: otočený text v pečiatke alebo vodoznaku pristane v tom istom súradnicovom priestore ako telo textu, a pipeline, ktorý ignoruje uhol, ochotne zlúči diagonálne „DRAFT“ doprostred odstavca

Rozpočet a dve počítadlá kvality

MaxCharacters je fail-closed rozpočet, nie nastavenie orezávania: stránka, ktorá ho prekročí, sa zastaví namiesto toho, aby ticho vrátila časť obsahu. Na nedôveryhodnej vstupnej ceste je toto správanie, ktoré chcete, pretože stránka s miliónom znakov je buď strojovo generovaná príšera, alebo pokus urobiť z vášho extraktora najpomalšiu časť systému

Dve počítadlá na vrátenej stránke priamo opisujú kvalitu extrakcie. UnmappedCharacterCount počíta znaky bez použiteľného mapovania Unicode, čo je klasický príznak subsetovaného písma vloženého bez CMap /ToUnicode; taký text sa vykreslí dokonale a extrahuje sa ako nič užitočné. GeometryFailureCount počíta znaky, ktorých ohraničujúci rámček sa nepodarilo určiť, čo zhoršuje poradie fyzického rozloženia. Logujte oboje. Súbor dokumentov, kde sú tieto čísla trvalo blízko nuly, sa dá indexovať s dôverou, a taký, kde nie sú, vám hovorí, že niektorí producenti vo vašom pipeline potrebujú pozornosť skôr, než bude akýkoľvek následný výsledok dôveryhodný

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;

Výkon na reálnych stránkach

Extrakcia s fyzickým rozložením je nákladný režim, a implementácia je postavená pre stránky, ktoré sú naozaj veľké: usporiadanie znakov beží v O(n log n) namiesto opakovaného skenovania, buffery riadkov a spanov rastú geometricky namiesto realokácie na každý znak, unikódový text sa buduje v bufferoch namiesto zreťazovaním reťazcov, a vyhľadávania písma pre susediace textové objekty sa cachujú. Práve táto kombinácia udržiava hustú 5 000-znakovú stránku predvídateľnou namiesto kvadratickej

Pri úlohe s vysokým počtom stránok sa stále oplatí voliť lacnejší režim tam, kde sa dá. Použite roContentOrder so zapnutou sémantikou pre označené dokumenty, ktorým dôverujete, a roPhysicalLayout vyhraďte pre skenovaný a starší materiál, kde je geometria jediným signálom. Ak potrebujete len obyčajný reťazec, jednoduchšie API opísané v článku extrahovanie textu z PDF dokumentov zostáva rýchlejšou cestou, a keď potrebujete vystopovať text späť k identifikátorom označeného obsahu, túto vrstvu pokrýva článok čítanie a zápis označeného obsahu BDC a MCID

Model blokov sa aj čisto mapuje na to, čo chcú pipeline vyhľadávania: nadpis s jeho odstavcami je blok s titulkom, a hranice umožňujú, aby citácia ukazovala na miesto na stránke, nie na celý dokument. PDFiumPas je komponent pre Delphi a Lazarus postavený nad enginom PDFium, zdokumentovaný s príkladmi na stránke PDFium Delphi component