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