Tekninen artikkeli

PDF-tekstin rakenteellinen poiminta PDFium VCL:llä

PDFiumPas palauttaa sivun tekstin rakenteena eikä merkkijonona. GetStructuredText tuottaa TPdfStructuredTextPage-olion, joka sisältää lohkoja, joista jokainen sisältää rivejä, joista jokainen sisältää tyylitettyjä välejä, joissa on sivutilan rajat jokaisella tasolla ja lähdemerkkien indeksit säilytettyinä, jotta mikä tahansa fragmentti voidaan kartoittaa takaisin pohjana olevaan tekstisivuun

Tasainen merkkijonopoiminta, josta useimmat koodit aloittavat, on yhä olemassa ja yhä oikea tarkoitukseensa. Se lakkaa riittämästä sillä hetkellä, kun sinun täytyy tietää, mitkä sanat olivat otsikko, mitkä kuuluivat vasempaan palstaan, tai missä kohtaa sivua osuma todella sijaitsee

Miksi tasainen merkkijono on väärä tuloste useimmille tehtäville?

Koska kysymykset, joita ihmiset esittävät poimitusta tekstistä, eivät ole lähes koskaan "mitkä merkit ovat tällä sivulla". Ne ovat "mikä on otsikko", "onko tämä taulukko", "kuuluuko tämä kappale osioon 4", "mihin piirrän korostuksen". Yksittäinen merkkijono ei vastaa mihinkään näistä, ja jokainen vastaus, jonka rakennat siitä, on heuristiikka, jonka omistat nyt itse

Kaksipalstaiset asettelut tekevät asian konkreettiseksi. Poimi kaksipalstainen artikkeli merkkijonona, ja riippuen siitä, miten tuottaja kirjoitti sisältövirran, saatat saada palstan yksi ja sen jälkeen palstan kaksi, tai saatat saada palstan yksi rivin yksi, palstan kaksi rivin yksi, palstan yksi rivin kaksi, ja niin edelleen alas sivun. Molemmat tulevat standardinmukaisesta PDF:stä. Kumpikaan ei ole väärin formaattitasolla, koska PDF kuvaa merkkejä sivulla, ei asiakirjan runkoa. Lohkopohjainen malli antaa poimijan tehdä järjestyspäätöksen eksplisiittisesti ja kertoa sinulle, minkä päätöksen se teki

Sisältöjärjestys vai fyysinen asettelu?

TPdfStructuredTextOptions.ReadingOrder valitsee arvojen roContentOrder ja roPhysicalLayout väliltä, ja oikea vastaus riippuu siitä, kumpaan luotat enemmän, tuottajaan vai geometriaan

Sisältöjärjestys palauttaa tekstin siinä järjestyksessä, jossa sisältövirta piirtää sen. Se on nopea, ja hyvin käyttäytyvän tuottajan generoimissa asiakirjoissa tyypillisesti aiottu lukujärjestys. Fyysinen asettelu jättää huomiotta virran järjestyksen ja rakentaa järjestyksen uudelleen siitä, missä merkit todella sijaitsevat, ryhmitellen ne riveiksi ja sitten palstoiksi. Sitä haluat skannatuille ja sitten OCR-käsitellyille sivuille, työkalujen tulosteille, jotka lähettävät tekstiä fonttijärjestyksessä eikä lukujärjestyksessä, ja mihin tahansa, jossa visuaalinen tulos on ainoa asia, johon voit luottaa

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-based

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

    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;

Mitä tagaus tuo, mitä geometria ei voi?

Tarkoituksen. Kun IncludeSemantics on käytössä, tagatun PDF:n lohkot kantavat rakennepuusta poimitun Kind-arvon, joten otsikko on otsikko, koska tuottaja sanoi niin, ei siksi, että sen fontti oli keskimääräistä suurempi. Tyypit kattavat uudelleenkäytön kannalta tärkeät muodot: cfParagraph, cfHeading ja sen HeadingLevel, cfListItem, cfTableCell, cfCaption, cfFigure ja tagaamaton varamuoto cfPlain

Source-kenttä tallentaa, mistä kukin luokittelu tuli, rosStructure rakennepuulle ja rosHeuristic päättelylle, mikä on se kenttä, joka kannattaa kirjata lokiin, kun päätät, kuinka pitkälle luotat poimintaputkeen asiakirjajoukon yli. Kuvat ovat erikoistapaus, joka kannattaa tietää: cfFigure-lohkolle teksti tulee vaihtoehtoisesta kuvauksesta eikä miltään glyfeistä, koska kuvalla ei ole omia merkkejä. Täsmäämätön vaihtoehtoinen teksti esitetään silti sen sijaan, että se pudotettaisiin, mikä antaa esteettömyysauditoinnin nähdä, että kuvaus on olemassa, vaikka mikään sivulla ei piirrä sitä. Itse tagausmalli käsitellään artikkelissa PDF/UA-rakennepuun validointi

Välit kantavat tyylin ja alkuperän

Jokainen TPdfStructuredTextSpan kantaa tekstinsä, sivutilan rajansa, FontName-, FontSize-, FontWeight- ja Angle-arvot, sekä SourceStartIndex- ja SourceCharacterCount-arvot. Välit katkeavat siellä, missä tyyli muuttuu, joten lause, jossa on kolme lihavoitua sanaa, muuttuu kolmeksi väliksi, ja korostuksen uudelleenrakentaminen HTML:ssä tai Markdownissa on ominaisuuksien lukemista sen sijaan, että arvattaisiin fonttien nimistä

Kaksi lähdeindeksikenttää ovat niitä, jotka muuttavat poiminnan ominaisuudeksi eikä raportiksi. Ne osoittavat takaisin sivun merkkijärjestykseen, mikä tarkoittaa, että hakuosuman lohko voidaan muuttaa merkkitason valintageometriaksi tai korostussuorakulmioksi ilman toista, eri järjestyksessä olevaa läpikäyntiä tekstin yli; mekaniikka on kuvattu artikkelissa visuaalinen tekstirivin valinta merkkilaatikoilla. Angle-kentällä on enemmän merkitystä kuin miltä se näyttää: leimassa tai vesileimassa oleva kierretty teksti laskeutuu samaan koordinaattitilaan kuin leipäteksti, ja putki, joka jättää kulman huomiotta, sulauttaa mielellään diagonaalisen "DRAFT"-tekstin kappaleen keskelle

Budjetti, ja kaksi laatulaskuria

MaxCharacters on fail-closed-budjetti, ei katkaisuasetus: sen ylittävä sivu pysähtyy sen sijaan, että se palauttaisi hiljaisesti osan sisällöstä. Epäluotettavalla vastaanottopolulla se on käyttäytyminen, jonka haluat, koska sivu, jolla on miljoona merkkiä, on joko koneen generoima hirviö tai yritys tehdä poimijastasi järjestelmän hitain osa

Kaksi laskuria palautetulla sivulla kuvaavat poiminnan laatua suoraan. UnmappedCharacterCount laskee merkit, joilla ei ole käyttökelpoista Unicode-kartoitusta, mikä on klassinen oire osajoukkofontista, joka on upotettu ilman /ToUnicode-CMapia; sellainen teksti renderöityy täydellisesti ja poimitaan käyttökelvottomana. GeometryFailureCount laskee merkit, joiden rajaavaa laatikkoa ei voitu määrittää, mikä heikentää fyysisen asettelun järjestystä. Kirjaa molemmat lokiin. Asiakirjajoukko, jossa nämä luvut ovat johdonmukaisesti lähellä nollaa, voidaan indeksoida luottamuksella, ja joukko, jossa ne eivät ole, kertoo sinulle, että jotkut putkesi tuottajat tarvitsevat huomiota ennen kuin mikään jatkokäsittelyn tulos on luotettava

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;

Suorituskyky todellisilla sivuilla

Fyysisen asettelun poiminta on kallis tila, ja toteutus on rakennettu sivuille, jotka ovat todella suuria: merkkien järjestäminen toimii O(n log n) -aikavaativuudella toistuvan skannauksen sijaan, rivi- ja välipuskurit kasvavat geometrisesti sen sijaan, että ne uudelleenvarattaisiin merkki kerrallaan, Unicode-teksti rakennetaan puskureissa eikä merkkijonojen ketjutuksella, ja viereisten tekstiobjektien fonttihaut välimuistitetaan. Tuo yhdistelmä on se, mikä pitää tiheän 5 000 merkin sivun ennustettavana neliöllisen sijaan

Sivumäärältään raskaassa työssä kannattaa silti valita edullisempi tila, missä voit. Käytä roContentOrder-tilaa semantiikka käytössä luotettaville tagatuille asiakirjoille, ja varaa roPhysicalLayout skannatulle ja vanhalle materiaalille, jossa geometria on ainoa signaali. Jos tarvitset vain paljaan merkkijonon, artikkelissa tekstin poiminta PDF-asiakirjoista kuvattu yksinkertaisempi API pysyy nopeampana polkuna, ja kun sinun täytyy jäljittää teksti takaisin merkittyihin sisältötunnisteisiin, artikkeli BDC- ja MCID-merkityn sisällön lukeminen ja kirjoittaminen kattaa tuon kerroksen

Lohkomalli kartoittuu myös siististi siihen, mitä hakuputket haluavat: otsikko kappaleineen on pala, jolla on otsikko, ja rajat antavat viitteen osoittaa sijaintiin sivulla eikä asiakirjaan. PDFiumPas on Delphi- ja Lazarus-komponentti PDFium-moottorin ympärillä, dokumentoitu esimerkein sivulla PDFium Delphi component page