Teknisk artikel

Strukturordnet PDF-tekstudtrækning i Delphi med HotPDF

Enhver geometrisk tekstudtrækker gætter. Den læser de glyphs, en side tegner, sorterer dem efter baseline og vandret position og håber, at det visuelle arrangement matcher den rækkefølge, et menneske ville læse. På en rapport med én spalte er det gæt rigtigt. På en tidsskriftsartikel med to spalter, en formular med en sidebjælke eller en tabel, hvis celler blev udsendt kolonne for kolonne, er det forkert på måder, der er svære at lægge mærke til og dyre at opdage længere nede i kæden. HotPDF besvarer dette med ExtractLoadedPageStructureText, som ignorerer geometrien fuldstændigt: den går dokumentstrukturtræet igennem i forfatterrækkefølge som defineret i ISO 32000-1 §14.8.4 og samler derefter sideglyphs efter deres marked-content-identifikator. For en tagged PDF er det ikke en heuristik, det er den rækkefølge, det producerende program erklærede

Funktionen returnerer False, når siden ikke har et brugbart strukturtræ, hvilket er signalet til at falde tilbage til den geometriske udtrækker frem for at fejle. Det tovejsdesign betyder mere end algoritmen: rigtig dokumentindtagelse ser taggede offentlige formularer og scanner-output i samme mappe, og en pipeline, der kun håndterer én af dem, er ikke en pipeline

Hvorfor får geometrisk udtrækning læserækkefølgen forkert?

Fordi en PDF content stream slet ikke bærer nogen læserækkefølge. Den er en sekvens af tegneoperatorer, og en producent står frit for at udsende dem i den sekvens, der passer til dens eget layoutprogram. Tekstbehandlere udsender normalt i flowrækkefølge, og geometrisk sortering ser fin ud. Layoutværktøjer, formulardesignere og rapportgeneratorer gør det ofte ikke: en sidefod kan udsendes før brødteksten, en tabel kan fyldes kolonne-major, og en tospaltet side kan flette linjer fra begge spalter, fordi komponisten løste dem sammen

Sammenligning af tospaltet PDF-side, der viser baseline-sorteret geometrisk udtrækning flette spalter imod strukturordnet MCID-udtrækning i HotPDF
At sortere glyphs efter baseline fletter to spalter til vrøvl, mens strukturtræet genafspiller den rækkefølge, producenten erklærede

Fejltilstanden er stille. En geometrisk udtrækker rapporterer aldrig en fejl, den giver bare prosa tilbage, hvis sætninger er flettet sammen fra to spalter. Alt, der indtager den tekst — en søgeindeks, en e-faktura-feltmapper, en retrieval-pipeline, der fodrer en sprogmodel — arver skaden uden en advarsel. HotPDF leverer også de geometriske udtrækkere til indlæste dokumenter, og de forbliver det rigtige værktøj til utaggede filer; pointen med strukturordnet-vejen er at holde op med at gætte, når dokumentet allerede bærer svaret

Hvad strukturtræet reelt gemmer

En tagged PDF rummer en anden, parallel beskrivelse af siden. Katalogen peger på en /StructTreeRoot, hvis /K-børn danner et træ af strukturelementer: /Document, /Sect, /P, /Table, /TR, /TD og så videre. Træets blade er marked-content-referencer, heltal, der navngiver et stykke af sidens content stream. På indholdssiden åbnes de stykker med en BDC-operator, der bærer en /MCID, og lukkes med EMC. Hvert strukturelement bærer også en /Pg-post, der navngiver den side, det tilhører, hvilket er det, der gør pr. side gældende gennemløb muligt i et dokument, hvis strukturtræ spænder over hundredvis af sider

PDF-strukturtræets anatomi, der kobler StructTreeRoot-elementer som Sect, Table, TR og TD til BDC MCID-stykker i HotPDF sidens content stream
Træets blade er marked-content-referencer, og hvert element bærer en Pg-post, der lader gennemløbet filtrere til den aktuelle side

HotPDF gennemløber det træ med en dybdetag på 128 niveauer og filtrerer på /Pg, så kun den aktuelle side bidrager. Gennemløbets output er ikke tekst, det er en ordnet liste af MCID-værdier: forfatterrækkefølgen af de marked-content-stykker, der findes på denne side. At samle tekst igen er så et spørgsmål om at genafspille glyphs i den rækkefølge

MCID'en registreres under glyph-udtrækning, ikke slås op bagefter

Dette er implementeringsdetaljen, der gør funktionen billig. HotPDF registrerer allerede den aktive marked-content-identifikator på hver glyph, den udtrækker, i MCID-feltet på THPDFGlyphRecord, fordi content stream-fortolkeren ved, hvilket BDC-scope der er åbent i det øjeblik, den behandler hver Tj- eller TJ-operator. Strukturordnet-udtrækning behøver derfor ikke et andet gennemløb af content streamen. Den indsamler MCID-sekvensen fra strukturtræet, sorterer derefter de allerede udtrukne glyphs i spande efter MCID og udsender dem i den sekvens

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // kalder-ejet diagnostik-sink
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
        // Forfatterrækkefølge direkte fra strukturtræet
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Intet brugbart strukturtræ på denne side: geometrisk fallback
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

Utagede glyphs tælles, droppes aldrig i stilhed

En side kan være delvist tagget. Producenter tilføjer en dekorativ linje, et sidetal eller et sent vandmærke uden for ethvert BDC-scope, og de glyphs tilhører ingen MCID. At droppe dem ville være den pæne implementation og den forkerte, for samme hul optræder også, når en producent tagger brødteksten men glemmer tabellen, og du ville miste tabellen uden at lægge mærke til det

HotPDF føjer ikke-claimede glyphs til som en geometrisk hale efter den strukturnordnede tekst og rapporterer deres antal gennem outputparameteren UntaggedGlyphCount. Det tal er et kvalitetssignal, du kan handle på. En håndfuld glyphs på en side med to tusinde er sideinventar og kan ignoreres. Fyrre procent af siden uden for strukturtræet betyder, at tagningen er dekorativ, og den geometriske udtrækker er det mere ærlige svar for den fil

Beslutningsflow for HotPDF strukturtekstudtrækning med geometrisk fallback, når en side ikke har noget brugbart strukturtræ eller dekorativ tagning
True betyder strukturorden med den utaggede hale tilføjet, og False dirigerer siden til den geometriske udtrækker i stedet for at fejle
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);
    // Stol kun på strukturtræet, når det gør krav på det meste af siden
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Hvad får funktionen til at returnere False

Tre tilfælde, og de er værd at skelne, for kun ét af dem er en defekt i dokumentet. Det første er en almindelig utagged PDF: ingen /StructTreeRoot, intet at gå igennem, og False er simpelthen sandheden. Det andet er en scannet side, hvis tekst kommer fra et OCR-lag, der aldrig blev tagget. Det tredje er det interessante: indhold, der bærer BDC-operatorer med /MCID-værdier, men hvis side ikke har nogen /StructParents-post, og hvis strukturtræ aldrig refererer de identifikatorer. Det markerede indhold findes, structuresiden gør ikke, og der er ingen rækkefølge at gendanne. HotPDF rapporterer False frem for at digte en

Det sidste tilfælde optræder i håndredigerede filer og i output fra værktøjer, der udsender markeret indhold til optional-content- eller artifact-formål uden at bygge et strukturtræ. Hvis du selv producerer taggede PDF-filer, er samme asymmetri det, PDF/UA-validering tjekker for, og modstykket på writersiden er dækket i layout-DOM'en, der udsender tagget, pagineret output

Hvor strukturorden betaler sig selv hjem

Tilgængelighedsrevision er den oplagte: hvis du certifierer et dokument mod PDF/UA, er læserækkefølgen, en skærmlæser vil annoncere, præcis strukturordenen, så at udtrække den er måden at gennemse den på uden en skærmlæser. Dataindfangning er det større kommercielle tilfælde. Taggede offentlige formularer, regulerede oplysninger og e-fakturabilag bærer feltlabels og værdier i erklæret rækkefølge, og at læse dem i den rækkefølge fjerner en hel klasse af mappingsfejl, som geometrisk udtrækning skaber på flerspaltede layouts

Den nyeste forbruger er retrieval til sprogmodeller. At opdele et dokument i chunks til embedding er kun så godt som tekstordnen, og en chunk, der fletter to spalter sammen, producerer sætninger, der aldrig har eksisteret. Strukturordnet-udtrækning er den billigste tilgængelige løsning på det, for ved taggede dokumenter står den korrekte rækkefølge allerede i filen og behøver blot at blive læst

HotPDF er en native VCL-komponent til Delphi og C++Builder, så strukturtrægennemløbet og glyph-genafspilningen kører begge in-process mod et indlæst dokument uden nogen ekstern renderer involveret. Fuld API-detaljer for udtrækningsfamilien til indlæste dokumenter findes på produktsiden HotPDF Delphi PDF component