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