Elke geometrische tekstextractor gokt. Die leest de glyphs die een pagina tekent, sorteert ze op basislijn en horizontale positie, en hoopt dat de visuele schikking overeenkomt met de volgorde waarin een mens zou lezen. Bij een rapport met één kolom is die gok juist. Bij een tijdschriftartikel met twee kolommen, een formulier met een zijbalk of een tabel waarvan de cellen kolom voor kolom zijn uitgezonden, zit die ernaast op manieren die moeilijk op te merken zijn en duur om stroomafwaarts te ontdekken. HotPDF antwoordt daarop met ExtractLoadedPageStructureText, dat geometrie volledig negeert: het loopt de documentstructuurboom af in de auteursvolgorde zoals gedefinieerd in ISO 32000-1 §14.8.4 en bouwt daarna de paginaglyphs opnieuw op aan de hand van hun marked-content identifier. Voor een getagde PDF is dat geen heuristiek, het is de volgorde die de producerende applicatie heeft gedeclareerd
De functie geeft False terug wanneer de pagina geen bruikbare structuurboom heeft, en dat is het signaal om terug te vallen op de geometrische extractor in plaats van te falen. Dat tweewegenontwerp doet er meer toe dan het algoritme: echte documentintake ziet getagde overheidsformulieren en scanneruitvoer in dezelfde map, en een pijplijn die er maar één van aankan is geen pijplijn
Waarom krijgt geometrische extractie de leesvolgorde verkeerd?
Omdat een PDF-contentstream helemaal geen leesvolgorde meedraagt. Het is een reeks tekenoperatoren, en een producent mag die uitzenden in welke volgorde ook bij zijn eigen layout-engine past. Tekstverwerkers zenden meestal uit in flowvolgorde en geometrisch sorteren oogt prima. Layouttools, formulierontwerpers en rapportgeneratoren vaak niet: een paginavoettekst kan vóór de body worden uitgezonden, een tabel kan kolomgewijs worden gevuld, en een pagina met twee kolommen kan regels uit beide kolommen verweven omdat de componist ze samen heeft opgelost
De faalmodus is stil. Een geometrische extractor meldt nooit een fout, die geeft gewoon proza terug waarvan de zinnen uit twee kolommen zijn gespijst. Alles wat die tekst verbruikt, een zoekindex, een veldmapper voor e-facturen, een retrievalpijplijn die een taalmodel voedt, erft de schade zonder waarschuwing. HotPDF levert ook de geometrische extractors voor geladen documenten, en die blijven het juiste gereedschap voor niet-getagde bestanden; het punt van het pad in structuurvolgorde is stoppen met gokken wanneer het document het antwoord al meedraagt
Wat de structuurboom werkelijk opslaat
Een getagde PDF bewaart een tweede, parallelle omschrijving van de pagina. De catalog wijst naar een /StructTreeRoot, waarvan de /K-kinderen een boom van structuurelementen vormen: /Document, /Sect, /P, /Table, /TR, /TD enzovoort. De bladeren van die boom zijn marked-content references, integers die een reikwijdte van de paginacontentstream benoemen. Aan de inhoudskant worden die reikwijdtes geopend met een BDC-operator die een /MCID meedraagt en gesloten met EMC. Elk structuurelement draagt ook een /Pg-ingang die de pagina benoemt waartoe het behoort, en juist daardoor is per-pagina-traversatie mogelijk in een document waarvan de structuurboom honderden pagina's beslaat
HotPDF doorloopt die boom met een dieptecap van 128 niveaus en filtert op /Pg zodat alleen de huidige pagina bijdraagt. De uitvoer van de traversatie is geen tekst, het is een geordende lijst van MCID-waarden: de auteursvolgorde van de marked-content reikwijdtes op deze pagina. Tekst weer opbouwen is dan een kwestie van de glyphs in die volgorde herafspelen
De MCID wordt vastgelegd tijdens glyph-extractie, niet achteraf opgezocht
Dit is het implementatiedetail dat de functie goedkoop maakt. HotPDF legt de actieve marked-content identifier al vast op elke glyph die die extraheert, in het veld MCID van THPDFGlyphRecord, want de contentstream-interpreter weet welke BDC-scope open staat op het moment dat die elke operator Tj of TJ verwerkt. Extractie in structuurvolgorde heeft dus geen tweede passe over de contentstream nodig. Die verzamelt de MCID-reeks uit de structuurboom, sorteert daarna de al geëxtraheerde glyphs in bakken op MCID en zendt ze in die reeks uit
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // diagnostiek-sink in beheer van de aanroeper
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
// Auteursvolgorde rechtstreeks uit de structuurboom
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// Geen bruikbare structuurboom op deze pagina: geometrische fallback
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
Niet-getagde glyphs worden geteld, nooit geruisloos weggegooid
Een pagina kan deels getagd zijn. Producenten voegen een decoratieve lijn, een paginanummer of een laat-stadium-watermerk toe buiten elke BDC-scope, en die glyphs behoren tot geen enkele MCID. Ze weglaten zou de nette implementatie zijn en de verkeerde, want hetzelfde gat verschijnt ook wanneer een producent de body tagt maar de tabel vergeet, en dan verliest u de tabel zonder het te merken
HotPDF voegt niet-opgeëiste glyphs toe als een geometrische staart na de tekst in structuurvolgorde en meldt hun aantal via de outputparameter UntaggedGlyphCount. Dat aantal is een kwaliteitssignaal waarop u kunt handelen. Een handjevol glyphs op een pagina van tweeduizend is paginainboel en kan worden genegeerd. Veertig procent van de pagina buiten de structuurboom betekent dat het taggen decoratief is en dat de geometrische extractor het eerlijkere antwoord is voor dat bestand
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);
// Vertrouw de structuurboom alleen wanneer die het grootste deel van de pagina opeist
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
Wat de functie False laat teruggeven
Drie gevallen, en ze zijn het onderscheiden waard want slechts één ervan is een defect in het document. De eerste is een gewone niet-getagde PDF: geen /StructTreeRoot, niets om te doorlopen, en False is simpelweg de waarheid. De tweede is een gescande pagina waarvan de tekst komt uit een OCR-laag die nooit is getagd. Het derde is het interessante: inhoud die BDC-operatoren met /MCID-waarden meedraagt maar waarvan de pagina geen /StructParents-ingang heeft en waarvan de structuurboom die identifiers nooit refereert. De marked content bestaat, de structuurkant niet, en er is geen volgorde terug te halen. HotPDF meldt False in plaats van er een te verzinnen
Dat laatste geval verschijnt in met de hand bewerkte bestanden en in uitvoer van tools die marked content uitzenden voor optional-content- of artefactdoelen zonder een structuurboom op te bouwen. Als u zelf getagde PDFs produceert, dan is dezelfde asymmetrie wat PDF/UA-validatie controleert, en de tegenhanger aan de schrijfkant wordt behandeld in de layout-DOM die getagde, gepagineerde uitvoer uitzendt
Waar structuurvolgorde zichzelf terugverdient
Toegankelijkheidsaudit is de voor de hand liggende: certificeert u een document tegen PDF/UA, dan is de leesvolgorde die een schermlezer zal aankondigen precies de structuurvolgorde, dus die extraheren is hoe u die beoordeelt zonder schermlezer. Gegevensvastlegging is het grotere commerciële geval. Getagde overheidsformulieren, gereguleerde openbaarmakingen en e-factuurbijlagen dragen veldlabels en -waarden in gedeclareerde volgorde, en ze in die volgorde lezen verwijdert een hele klasse mapping-bugs die geometrische extractie veroorzaakt op meerkolomlay-outs
De nieuwste verbruiker is retrieval voor taalmodellen. Een document in chunks verdelen voor embedding is slechts zo goed als de tekstvolgorde, en een chunk die twee kolommen samenvoegt produceert zinnen die nooit hebben bestaan. Extractie in structuurvolgorde is de goedkoopste beschikbare oplossing daarvoor, want voor getagde documenten staat de juiste volgorde al in het bestand en hoeft alleen gelezen te worden
HotPDF is een native VCL-component voor Delphi en C++Builder, dus de structuurboom-traversatie en het herafspelen van glyphs draaien beide in-proces tegen een geladen document zonder externe renderer. Volledige API-details voor de extractiefamilie van geladen documenten staan op de productpagina van de HotPDF Delphi PDF component