Teknisk artikel

Textextraktion i strukturordning ur PDF i Delphi med HotPDF

Varje geometrisk textextraktor gissar. Den läser glyferna en sida ritar, sorterar dem efter baslinje och horisontell position och hoppas att den visuella anordningen matchar den ordning en människa skulle läsa. På en enspaltsrapport är den gissningen rätt. På en tvåspaltig tidskriftsartikel, ett formulär med sidopanel eller en tabell vars celler avgavs kolumnvis är den fel på sätt som är svåra att lägga märke till och dyra att upptäcka nedströms. HotPDF svarar på detta med ExtractLoadedPageStructureText, som ignorerar geometrin helt: den vandrar dokumentets strukturträd i författarordning enligt definitionen i ISO 32000-1 §14.8.4 och återmonterar sedan sidans glyfer efter deras marked-content-identifierare. För en taggad PDF är det inte en heuristik, det är ordningen det producerande programmet deklarerade

Funktionen returnerar False när sidan inte har något användbart strukturträd, vilket är signalen att falla tillbaka till den geometriska extraktorn i stället för att misslyckas. Den tvåvägsdesignen betyder mer än algoritmen: verklig dokumentintag ser taggade myndighetsformulär och skannerutdata i samma mapp, och en pipeline som bara hanterar en av dem är ingen pipeline

Varför får geometrisk extraktion läsningsordningen fel?

För en PDF-innehållsström bär ingen läsordning alls. Den är en sekvens av ritoperatorer, och en producent är fri att avge dem i vilken sekvens som passar dess egen layoutmotor. Ordbehandlare avger vanligtvis i flödesordning och geometrisk sortering ser bra ut. Layoutverktyg, formulärdesigners och rapportgeneratorer gör det ofta inte: en sidfot kan skickas ut före brödtexten, en tabell kan fyllas kolumnvis och en tvåspaltig sida kan sammanfläta rader från båda kolumnerna eftersom sättaren löste dem tillsammans

Jämförelse av tvåspaltig PDF-sida som visar baslinjesorterad geometrisk extraktion som hopspikrar kolumner mot strukturordnings-MCID-extraktion i HotPDF
Att sortera glyfer efter baslinje sammanflätar två kolumner till meningslöshet, medan strukturträdet spelar upp ordningen producenten deklarerade

Felläget är tyst. En geometrisk extraktor rapporterar aldrig ett fel, den lämnar bara tillbaka prosa vars meningar är hopspikrade ur två kolumner. Vadsomhelst som konsumerar texten, ett sökindex, en e-fakturafältmappare, en hämtningspipeline som matar en språkmodell, ärver skadan utan en varning. HotPDF levererar också de geometriska extraktorerna för inlästa dokument, och de förblir rätt verktyg för otaggade filer; poängen med strukturordningsvägen är att sluta gissa när dokumentet redan bär svaret

Vad strukturträdet egentligen lagrar

En taggad PDF bär på en andra, parallell beskrivning av sidan. Katalogen pekar på en /StructTreeRoot, vars /K-barn bildar ett träd av strukturelement: /Document, /Sect, /P, /Table, /TR, /TD och så vidare. Löven i det trädet är marked-content-referenser, heltal som namnger ett stycke av sidans innehållsström. På innehållssidan öppnas de styckena med en BDC-operator som bär en /MCID och stängs med EMC. Vart och ett strukturelement bär också en /Pg-post som namnger sidan det tillhör, vilket är det som gör genomgång per sida möjlig i ett dokument vars strukturträd spänner över hundratals sidor

Anatomi hos PDF-strukturträdet som länkar StructTreeRoot-element som Sect, Table, TR och TD till BDC MCID-stycken i HotPDF:s sidinnehållsström
Trädets löv är marked-content-referenser, och vart och ett element bär en Pg-post som låter vandringen filtrera till den aktuella sidan

HotPDF genomgår det trädet med ett djuptak på 128 nivåer och filtrerar på /Pg så att bara den aktuella sidan bidrar. Utmatningen av genomgången är inte text, det är en ordnad lista av MCID-värden: författarordningen för marked-content-styckena på denna sida. Att återmontera text är sedan en fråga om att spela upp glyferna i den ordningen

MCID:n spelas in under glyfextraktion, inte slås upp efteråt

Detta är implementeringsdetaljen som gör funktionen billig. HotPDF spelar redan in den aktiva marked-content-identifieraren på varje glyf den extraherar, i fältet MCID hos THPDFGlyphRecord, eftersom innehållsströmstolken vet vilken BDC-omfattning som är öppen i stund den bearbetar varje Tj- eller TJ-operator. Extraktion i strukturordning behöver därför inget andra pass över innehållsströmmen. Den samlar in MCID-sekvensen från strukturträdet, bucketar sedan de redan extraherade glyferna per MCID och avger dem i den sekvensen

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // anroparägd diagnostikmottagare
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('accessible-form.pdf');
    AllText := '';
    for I := 0 to PageCount - 1 do
    begin
      if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
      begin
        // Författarordning rakt ur strukturträdet
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Inget användbart strukturträd på denna sida: geometrisk reservväg
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

Otaggade glyfer räknas, kastas aldrig tyst bort

En sida kan vara delvis taggad. Producenter lägger till en dekorativ linje, ett sidnummer eller en vattenstämpel i sent skede utanför varje BDC-omfattning, och de glyferna tillhör ingen MCID. Att kasta dem skulle vara den prydliga implementeringen och den felaktiga, eftersom samma gap också visar sig när en producent taggar brödtexten men glömmer tabellen, och du skulle tappa tabellen utan att märka det

HotPDF bifogar oanspråkta glyfer som en geometrisk svans efter den strukturordnade texten och rapporterar deras antal via utdataparametern UntaggedGlyphCount. Det antalet är en kvalitetssignal du kan agera på. En handfull glyfer på en sida av två tusen är sidomöblering och kan ignoreras. Fyrtio procent av sidan utanför strukturträdet betyder att taggningen är dekorativ och att den geometriska extraktorn är det ärligare svaret för den filen

Beslutsflöde för HotPDF strukturtextextraktion med geometrisk reservväg när en sida saknar användbart strukturträd eller har dekorativ taggning
True betyder strukturordning med den otaggade svansen bifogad, och False dirigerar sidan till den geometriska extraktorn i stället för att misslyckas
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
  out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
  Untagged, TotalGlyphs: Integer;
  Glyphs: THPDFGlyphArray;
begin
  UsedStructure := False;
  if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
  begin
    TotalGlyphs := 0;
    if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
      TotalGlyphs := Length(Glyphs);
    // Lita på strukturträdet bara när det gör anspråk på det mesta av sidan
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Vad som får funktionen att returnera False

Tre fall, och de är värda att skilja åt eftersom bara en av dem är en defekt i dokumentet. Det första är en vanlig otaggad PDF: ingen /StructTreeRoot, inget att vandra och False är helt enkelt sanningen. Det andra är en skannad sida vars text kommer från ett OCR-lager som aldrig taggades. Det tredje är det intressanta: innehåll som bär BDC-operatorer med /MCID-värden men vars sida saknar /StructParents-post och vars strukturträd aldrig refererar de identifierarna. Det markerade innehållet finns, struktursidan gör det inte, och det finns ingen ordning att återvinna. HotPDF rapporterar False i stället för att hitta på en

Det sista fallet visar sig i handredigerade filer och i utdata från verktyg som avger markerat innehåll för optional-content- eller artefaktändamål utan att bygga ett strukturträd. Om du själv producerar taggade PDF:er är samma asymmetri vad PDF/UA-validering kontrollerar, och motsvarigheten på skrivarsidan tas upp i layout-DOM:en som avger taggad, paginerad utmatning

Där strukturordning betalar för sig

Tillgänglighetsgranskning är det uppenbara: om du certifierar ett dokument mot PDF/UA är läsningsordningen en skärmläsare kommer annonsera exakt strukturordningen, så att extrahera den är hur du granskar den utan en skärmläsare. Datainsamling är det större kommersiella fallet. Taggade myndighetsformulär, reglerade informationsskyldigheter och e-fakturabilagor bär fältetiketter och värden i deklarerad ordning, och att läsa dem i den ordningen tar bort en hel klass av mappningsbuggar som geometrisk extraktion skapar på flerkolumnslayouter

Den nyaste konsumenten är hämtning för språkmodeller. Att stycka upp ett dokument för inbäddning är bara så bra som textordningen, och ett stycke som hopspikrar två kolumner producerar meningar som aldrig funnits. Extraktion i strukturordning är den billigaste tillgängliga fixen för det, eftersom för taggade dokument den korrekta ordningen redan finns i filen och bara behöver läsas

HotPDF är en nativ VCL-komponent för Delphi och C++Builder, så strukturträdsgenomgången och glyfuppspelningen körs båda inuti processen mot ett inläst dokument utan någon extern renderare inblandad. Fullständiga API-detaljer för extraktionsfamiljen för inlästa dokument finns på produktsidan för HotPDF Delphi PDF component