Každý geometrický extraktor textu hádá. Čte glyfy, které stránka kreslí, řadí je podle baseline a vodorovné pozice a doufá, že vizuální uspořádání odpovídá pořadí, v jakém by člověk četl. U jednosloupcové sestavy je ten tip správně. U dvousloupcového článku časopisu, formuláře s postranní lištou nebo tabulky, jejíž buňky byly vypouštěny sloupec po sloupci, je chybně způsoby, které je těžké si všimnout a drahé odhalit downstream. HotPDF na to odpovídá ExtractLoadedPageStructureText, která ignoruje geometrii zcela: projde strom struktury dokumentu v pořadí autorství definovaném ISO 32000-1 §14.8.4 a poté složí glyfy stránky podle jejich marked-content identifikátoru. Pro tagované PDF to není heuristika, je to pořadí, které producentská aplikace deklarovala
Funkce vrací False, když stránka nemá použitelný strom struktury, což je signál spadnout zpět na geometrický extraktor, nikoli selhat. Toto dvoucestné uspořádání je důležitější než algoritmus: reálný příjem dokumentů vidí tagované vládní formuláře a výstup skeneru ve stejné složce a potrubí, které zvládá jen jedno z nich, není potrubí
Proč geometrická extrakce dostává pořadí čtení špatně?
Protože content stream PDF nenese žádné pořadí čtení. Je to sekvence kreslicích operátorů a producent je smí vypouštět v jakékoli sekvenci, která vyhovuje jeho vlastnímu layout enginu. Textové procesory obvykle vypouštějí v pořadí toku a geometrické řazení vypadá dobře. Layout nástroje, návrháři formulářů a generátory sestav často ne: zápatí stránky může být vypuštěno před tělem, tabulka může být plněna po sloupcích a dvousloupcová stránka může proplétat řádky z obou sloupců, protože composer je rozřešil dohromady
Režim selhání je tichý. Geometrický extraktor nikdy nehlásí chybu, jen odevzdá prózu, jejíž věty jsou splétané ze dvou sloupců. Cokoli, co tento text konzumuje — vyhledávací index, mapper polí e-faktury, retrieval potrubí živící jazykový model — dědí škodu bez varování. HotPDF dodává také geometrické extraktory pro načtené dokumenty a ty zůstávají správným nástrojem pro netagované soubory; smysl cesty v pořadí struktury je přestat hádat, když už dokument nese odpověď
Co strom struktury skutečně uchovává
Tagované PDF drží druhý, rovnoběžný popis stránky. Katalog ukazuje na /StructTreeRoot, jehož děti /K tvoří strom prvků struktury: /Document, /Sect, /P, /Table, /TR, /TD a tak dále. Listy toho stromu jsou reference na marked content, celá čísla pojmenovávající úsek content streamu stránky. Na straně obsahu jsou tyto úseky otevřeny operátorem BDC nesoucím /MCID a zavřeny EMC. Každý prvek struktury také nese položku /Pg pojmenovávající stránku, do níž patří, což umožňuje procházení po stránkách v dokumentu, jehož strom struktury zabírá stovky stránek
HotPDF projde ten strom se stropem hloubky 128 úrovní a filtruje na /Pg, takže přispívá jen aktuální stránka. Výstupem procházení není text, je to uspořádaný seznam hodnot MCID: pořadí autorství úseků marked content na této stránce. Složení textu je pak záležitostí přehrání glyfů v tom pořadí
MCID se zaznamenává při extrakci glyfů, nedohledává se potom
Toto je detail implementace, který činí funkci levnou. HotPDF už zaznamenává aktivní identifikátor marked content na každém glyfu, který extrahuje, v poli MCID struktury THPDFGlyphRecord, protože interpret content streamu ví, který rozsah BDC je otevřen v okamžiku, kdy zpracovává každý operátor Tj nebo TJ. Extrakce v pořadí struktury tedy nepotřebuje druhý průchod nad content streamem. Sesbírá sekvenci MCID ze stromu struktury, pak rozdělí už extrahované glyfy do košů podle MCID a vypustí je v té sekvenci
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // diagnostické úložiště vlastněné volajícím
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
// Pořadí autorství přímo ze stromu struktury
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// Na této stránce není použitelný strom struktury: geometrický fallback
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
Netagované glyfy se počítají, nikdy se potichu nezahazují
Stránka může být tagována částečně. Producenti přidávají ozdobnou linku, číslo stránky nebo pozdní vodoznak mimo jakýkoli rozsah BDC a tyto glyfy nepatří žádnému MCID. Zahodit je by byl uhlazená implementace a špatná, protože stejná mezera se objevuje i tehdy, když producent otaguje tělo a zapomene tabulku, a přišli byste o tabulku, aniž byste si toho všimli
HotPDF připojuje neuplatněné glyfy jako geometrický ocas za textem uspořádaným strukturou a hlásí jejich počet přes výstupní parametr UntaggedGlyphCount. To číslo je kvalitní signál, na který lze reagovat. Hrstka glyfů na stránce dvou tisíc je page furniture a lze ji ignorovat. Čtyřicet procent stránky mimo strom struktury znamená, že tagování je ozdobné a geometrický extraktor je pro ten soubor čestnější odpověď
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);
// Stromu struktury věřte jen tehdy, když si nárokuje většinu stránky
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
Co způsobuje, že funkce vrací False
Tři případy a stojí za rozlišení, protože jen jeden z nich je defekt dokumentu. První je obyčejné netagované PDF: žádný /StructTreeRoot, nic k procházení a False je prostě pravda. Druhé je skenovaná stránka, jejíž text pochází z OCR vrstvy, která nikdy nebyla tagována. Třetí je zajímavý: obsah nesoucí operátory BDC s hodnotami /MCID, ale jehož stránka nemá položku /StructParents a jehož strom struktury tyto identifikátory nikdy neodkazuje. Marked content existuje, strana struktury ne a žádné pořadí k obnovení tu není. HotPDF hlásí False namísto jednoho vynalézání
Ten poslední případ se objevuje v ručně upravovaných souborech a ve výstupu nástrojů, které vypouštějí marked content pro účely optional content nebo artifact bez budování stromu struktury. Pokud sami produkujete tagovaná PDF, totéž asymetrii kontroluje validace PDF/UA a stranu writeru pokrývá layout DOM vypouštějící tagovaný, stránkovaný výstup
Kde se pořadí struktury vyplatí
Audit přístupnosti je ten zjevný: certifikujete-li dokument proti PDF/UA, pořadí čtení, které oznámí čtečka obrazovky, je přesně pořadí struktury, takže jeho extrakce je způsob, jak jej revidovat bez čtečky obrazovky. Sběr dat je větší komerční případ. Tagované vládní formuláře, regulované disclosure a přílohy e-faktur nesou názvy polí a hodnoty v deklarovaném pořadí a čtení v tom pořadí odstraňuje celou třídu chyb mapování, kterou geometrická extrakce vytváří na vícesloupcových layoutech
Nejnovějším konzumentem je retrieval pro jazykové modely. Dělení dokumentu do chunků pro embedding je jen tak dobré, jaké je pořadí textu, a chunk splétající dva sloupce produkuje věty, které nikdy neexistovaly. Extrakce v pořadí struktury je nejlevnější dostupná oprava, protože u tagovaných dokumentů je správné pořadí už v souboru a stačí jej přečíst
HotPDF je nativní komponenta VCL pro Delphi a C++Builder, takže procházení stromu struktury i přehrávání glyfů běží v procesu nad načteným dokumentem bez zapojení externího rendereru. Kompletní detaily API rodiny extrakce z načtených dokumentů jsou na stránce produktu HotPDF Delphi PDF component