Każdy geometryczny ekstraktor tekstu zgaduje. Czyta glify, które strona rysuje, sortuje je po linii bazowej i pozycji poziomej i ma nadzieję, że układ wizualny zgadza się z kolejnością, jaką przeczytałby człowiek. W raporcie jednokolumnowym to zgadywanie jest trafne. W dwukolumnowym artykule czasopisma, formularzu z panelem bocznym albo tabeli, której komórki były emitowane kolumna po kolumnie, jest złe w sposób trudny do zauważenia i kosztowny w odkryciu dalej w potoku. HotPDF odpowiada na to ExtractLoadedPageStructureText, które ignoruje geometrię całkowicie: przechodzi drzewo struktury dokumentu w kolejności tworzenia, zdefiniowanej w ISO 32000-1 §14.8.4, a potem składa glify strony po ich identyfikatorze oznaczonej zawartości. Dla otagowanego PDF to nie jest heurystyka — to kolejność, którą zadeklarowała aplikacja produkująca
Funkcja zwraca False, gdy strona nie ma używalnego drzewa struktury, co jest sygnałem do cofnięcia się na ekstraktor geometryczny, a nie do zawieszenia się. Ten projekt dwóch ścieżek znaczy więcej niż algorytm: prawdziwy napływ dokumentów widzi otagowane formularze rządowe i wyjście skanerów w tym samym folderze, a potok obsługujący tylko jedno z nich nie jest potokiem
Dlaczego ekstrakcja geometryczna myli kolejność czytania?
Bo strumień zawartości PDF nie niesie żadnej kolejności czytania. Jest sekwencją operatorów rysowania, a producent może emitować je w dowolnej sekwencji, jaka pasuje jego silnikowi układu. Procesory tekstu zwykle emitują w kolejności przepływu i sortowanie geometryczne wygląda dobrze. Narzędzia składu, projektanci formularzy i generatory raportów często nie: stopka strony może zostać wyemitowana przed treścią, tabela może być wypełniana kolumnami, a strona dwukolumnowa może przeplatać linie z obu kolumn, bo kompozytor rozwiązywał je razem
Tryb awarii jest cichy. Geometryczny ekstraktor nigdy nie zgłasza błędu, po prostu oddaje prozę, której zdania są zszyte z dwóch kolumn. Cokolwiek konsumuje ten tekst — indeks wyszukiwania, mapator pól e-faktur, potok wyszukiwania karmiący model językowy — dziedziczy szkody bez ostrzeżenia. HotPDF dostarcza też ekstraktory geometryczne dla wczytanych dokumentów i one pozostają właściwym narzędziem dla plików bez tagów; sens ścieżki kolejności struktury to koniec zgadywania, gdy dokument już niesie odpowiedź
Co drzewo struktury faktycznie przechowuje
Otagowany PDF przechowuje drugi, równoległy opis strony. Katalog wskazuje na /StructTreeRoot, którego dzieci /K tworzą drzewo elementów struktury: /Document, /Sect, /P, /Table, /TR, /TD i tak dalej. Liśćmi tego drzewa są referencje oznaczonej zawartości, liczby całkowite nazywające wycinek strumienia zawartości strony. Po stronie zawartości te wycinki są otwierane operatorem BDC niosącym /MCID i zamykane EMC. Każdy element struktury niesie też wpis /Pg nazywający stronę, do której należy, co czyni możliwym przejście per strona w dokumencie, którego drzewo struktury rozciąga się na setki stron
HotPDF przechodzi to drzewo z sufitem głębokości 128 poziomów i filtruje po /Pg, więc tylko bieżąca strona wnosi wkład. Wyjściem przejścia nie jest tekst, tylko uporządkowana lista wartości MCID: kolejność tworzenia wycinków oznaczonej zawartości na tej stronie. Złożenie tekstu jest potem kwestią odtworzenia glifów w tej kolejności
MCID jest zapisywany podczas ekstrakcji glifów, nie wyszukiwany po fakcie
To jest szczegół implementacyjny, który czyni tę funkcję tanim. HotPDF już zapisuje aktywny identyfikator oznaczonej zawartości na każdym glifie, który ekstrahuje, w polu MCID rekordu THPDFGlyphRecord, bo interpreter strumienia zawartości wie, który zakres BDC jest otwarty w chwili przetwarzania każdego operatora Tj albo TJ. Ekstrakcja w kolejności struktury nie potrzebuje więc drugiego przebiegu po strumieniu zawartości. Zbiera sekwencję MCID z drzewa struktury, potem rozdziela już wyekstrahowane glify do wiader po MCID i emituje je w tej sekwencji
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // diagnostyczne ujście należące do wywołującego
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
// Kolejność tworzenia prosto z drzewa struktury
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// Brak używalnego drzewa struktury na tej stronie: fallback geometryczny
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
Glify bez tagów są liczone, nigdy po cichu wyrzucane
Strona może być częściowo otagowana. Producenci dodają ozdobną linię, numer strony albo późny znak wodny poza jakimkolwiek zakresem BDC, a te glify nie należą do żadnego MCID. Wyrzucenie ich byłoby schludną implementacją i złą, bo ta sama luka pojawia się też wtedy, gdy producent otaguje treść, a zapomni tabeli, i tabela przepadłaby bez zauważenia
HotPDF dokleja nieprzypisane glify jako geometryczny ogon po tekście w kolejności struktury i zgłasza ich liczbę przez parametr wyjściowy UntaggedGlyphCount. Ta liczba to sygnał jakości, na który możesz zareagować. Garść glifów na stronie z dwóch tysięcy to wyposażenie strony i można je zignorować. Czterdzieści procent strony poza drzewem struktury znaczy, że tagowanie jest dekoracyjne, a geometryczny ekstraktor jest bardziej uczciwą odpowiedzią dla tego pliku
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);
// Ufaj drzewu struktury tylko, gdy rości sobie większość strony
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
Co sprawia, że funkcja zwraca False
Trzy przypadki i warte rozróżnienia, bo tylko jeden z nich to defekt dokumentu. Pierwszy to zwyczajny PDF bez tagów: brak /StructTreeRoot, nic do przejścia, a False to po prostu prawda. Drugi to zeskanowana strona, której tekst pochodzi z warstwy OCR nigdy nie otagowanej. Trzeci jest ciekawy: zawartość niosąca operatory BDC z wartościami /MCID, ale jej strona nie ma wpisu /StructParents, a drzewo struktury nigdy nie odwołuje się do tych identyfikatorów. Oznaczona zawartość istnieje, strona struktury nie, i nie ma kolejności do odzyskania. HotPDF zgłasza False, zamiast jedną wymyślić
Ten ostatni przypadek pojawia się w plikach edytowanych ręcznie i w wyjściu narzędzi emitujących oznaczoną zawartość dla celów zawartości opcjonalnej albo artefaktów bez budowania drzewa struktury. Jeśli sam produkujesz otagowane PDF-y, ta sama asymetria jest tym, co sprawdza walidacja PDF/UA, a odpowiednik po stronie zapisu jest omówiony w DOM-ie układu emitującym otagowane, paginowane wyjście
Gdzie kolejność struktury zwraca się sama
Audyt dostępności to oczywisty: jeśli certyfikujesz dokument względem PDF/UA, kolejność czytania, którą ogłosi czytnik ekranu, to dokładnie kolejność struktury, więc jej ekstrakcja to sposób przeglądu bez czytnika ekranu. Przechwytywanie danych to większy przypadek komercyjny. Otagowane formularze rządowe, regulowane ujawnienia i załączniki e-faktur niosą etykiety pól i wartości w zadeklarowanej kolejności, a czytanie ich w tej kolejności usuwa całą klasę błędów mapowania, które ekstrakcja geometryczna tworzy na układach wielokolumnowych
Najnowszym konsumentem jest wyszukiwanie dla modeli językowych. Cięcie dokumentu na fragmenty pod embedding jest tylko tak dobre, jak kolejność tekstu, a fragment zszywający dwie kolumny produkuje zdania, które nigdy nie istniały. Ekstrakcja w kolejności struktury to najtańsza dostępna poprawka na to, bo w otagowanych dokumentach poprawna kolejność już jest w pliku i trzeba ją tylko przeczytać
HotPDF to natywny komponent VCL dla Delphi i C++Builder, więc przejście drzewa struktury i odtwarzanie glifów oba działają w procesie na wczytanym dokumencie bez udziału zewnętrznego renderera. Pełne szczegóły API rodziny ekstrakcji z wczytanych dokumentów są na stronie produktu HotPDF Delphi PDF component