HotPDF Delphi Component od wersji v2.768.15 usuwa zduplikowane znaki, które fałszywy bold i nadrukowane logo zostawiają w wyekstrahowanym tekście PDF. Gdy producent maluje ten sam glif dwa razy w odległości ułamka punktu, ExtractLoadedPageText, ExtractLoadedPageTextLayout i ExtractLoadedPageTextColumns czytają teraz ten znak raz, więc "Bold words" nie wychodzi już jako "Bold wordsBold words"
To jeden z tych błędów, które wyglądają jak awaria dekodera i nią nie są. Każdy glif dekoduje się poprawnie; tekst naprawdę jest na stronie dwa razy. Poprawka musi zdecydować, który z dwóch kompletnie poprawnych glifów jest kopią, i zrobić to bez zjedzenia legalnie powtórzonych liter, jak podwójne l w "fill", czy identycznych komórek tabeli. Reszta artykułu wyjaśnia regułę, której używa HotPDF, progi za nią, miejsca, w których świadomie różni się od innych narzędzi ekstrakcji, oraz drugi, niezwiązany błąd ekstrakcji z tej samej fali wydań, który też ucinał tekst na końcach wierszy
Dlaczego fałszywy bold daje podwojony tekst w ekstrakcji PDF?
Fałszywy bold podwaja wyekstrahowany tekst, bo producent rysuje string dwa razy, a obie kopie są prawdziwymi operacjami pokazywania tekstu. Gdy rodzina fontów nie ma kroju bold, albo narzędzie autorskie po prostu się nie trudzi, wiele generatorów emuluje bold, pokazując ten sam string przez Tj albo TJ, przesuwając pozycję tekstu w prawo o kilka dziesiątych punktu i pokazując go jeszcze raz. Na ekranie obie kopie zlewają się w grubszy kontur. W strumieniu treści są dwa kompletne przebiegi kodów znaków, a ISO 32000-1 §9.4 nie daje ekstraktorowi żadnej flagi typu "to jest dekoracja"
Nadrukowane logo uderzają w ten sam problem z innej strony. Logotyp bywa namalowany raz jako wypełnienie, a drugi raz jako kontur albo lekko przesunięty cień, tym samym fontem i tymi samymi znakami. Naiwny ekstraktor, który idzie strumieniem treści w kolejności malowania, emituje cały pierwszy przebieg, a potem cały drugi, dlatego wynik czyta się jako "Bold wordsBold words", a nie "BBoolldd". Przed v2.768.15 składanie tekstu w HotPDF trzymało obie kopie, a duplikat psuł to, na czym najbardziej zależy wołającym: szukanie "Bold words" po tekście strony mogło się nie powieść, bo granica słowa między dwoma przebiegami znikła
Prawdziwy tryb renderowania tekstu 2 (wypełnienie, potem kontur, ISO 32000-1 §9.3.6) nie ma tego problemu, bo to jedna operacja pokazania tekstu, która produkuje jeden zestaw glifów. Duplikaty pojawiają się tylko wtedy, gdy producent wypuszcza tekst dwa razy
Jak HotPDF decyduje, który glif jest duplikatem?
HotPDF traktuje dwa glify jako jeden namalowany znak, gdy niosą tę samą wartość Unicode, mają ten sam kierunek pisania, różnią się rozmiarem o mniej niż 5% i siedzą prawie jeden na drugim: nie dalej niż 0.1 rozmiaru glifa wzdłuż linii bazowej i nie dalej niż 0.2 rozmiaru glifa w poprzek niej. Z każdej takiej pary zachowany zostaje glif namalowany pierwszy, a późniejszy jest wyrzucany. To wartości z implementacji v2.768.15 i każda jest tam z powodu
- Ten sam Unicode, ten sam kierunek. Duplikatami mogą być tylko identyczne znaki, a glif poziomy nigdy nie jest porównywany z pionowym (WMode 1)
- Rozmiar w granicach 5%. Duża litera nagłówka i mała litera podpisu, które przypadkiem na siebie nachodzą, to nie ten sam tekst, więc niezgodność rozmiaru dyskwalifikuje parę
- 0.1 rozmiaru wzdłuż linii bazowej. Przesunięcia fałszywego boldu to kilka dziesiątych punktu, kilka procent normalnego rozmiaru tekstu. Dwie prawdziwe sąsiednie litery, nawet ciasno podkernione "ll", dzieli mniej więcej pełna szerokość postępu, rząd wielkości więcej niż ta tolerancja
- 0.2 rozmiaru w poprzek linii bazowej. Cienie i przebiegi konturowe często przesuwają się w dół, jak i w prawo, więc tolerancja pionowa jest luźniejsza, ale wciąż daleko poniżej odległości do następnej linii
- Wygra namalowany pierwszy. Zachowanie wcześniejszego glifa chroni kolejność czytania pierwszego przebiegu, więc ocalały tekst płynie tak, jak zapisał go producent
Odległości są mierzone we własnej ramie linii bazowej glifa, a nie w surowych X i Y strony, więc obrócony tekst i tekst na skośnej linii bazowej używają tych samych tolerancji co zwykłe poziome linie. Używany rozmiar to rozmiar glifa namalowany na stronie, wyprowadzony z jego quadu, a nie operand rozmiaru podany do Tf; to ma znaczenie w dokumentach, które ustawiają Tf na 1 i skalują wszystko przez macierz tekstu, gdzie surowy rozmiar fontu uczyniłby każdą tolerancję bezużyteczną
Spacje nigdy nie są kandydatami. Punkty kodowe do U+0020, twarda spacja U+00A0 i spacja ideograficzna U+3000 są pomijane, bo puste glify nie niosą widocznego tuszu do zduplikowania, a wyrzucenie którejś skleiłoby słowa
Utrzymanie sprawdzania szybkim na gęstych stronach
Sprawdzanie duplikatów w HotPDF działa w czasie mniej więcej n log n, nie n kwadrat, bo najpierw sortuje i porównuje glify tylko w małym oknie. Porównywanie każdego glifa z każdym byłoby poprawne i bezużyteczne: gęsta strona drobnego druku potrafi nieść dziesiątki tysięcy glifów, a sprawdzenie uruchamia się przy każdym wywołaniu ekstrakcji tekstu
Wewnętrzna procedura sortuje glify bez spacji po wartości Unicode, potem po pochodzeniu X, potem po kolejności malowania, stabilnym sortowaniem przez scalanie. Po sortowaniu każdy potencjalny duplikat glifa siedzi bezpośrednio po nim w tej kolejności, więc skan idzie tylko w przód, dopóki wartość Unicode pasuje, a różnica X mieści się w 0.25 rozmiaru glifa. Okno 0.25 jest świadomie szersze niż tolerancja 0.1 wzdłuż linii bazowej, więc para na lekko skośnej linii bazowej wciąż w nie wpada, zanim ruszy precyzyjny test w ramie linii bazowej. Jeden subtelny przypadek: jeśli skan od glifa A spotyka pasujący glif namalowany przed A, to A jest kopią, więc A pada, a skan się zatrzymuje, zostawiając wcześniejszemu glifowi kontynuowanie porównania z czymkolwiek dalej. Ta reguła sprawia, że "wygra namalowany pierwszy" działa dla potrójnych uderzeń tak samo jak dla par
Porównanie jest po wartości Unicode, nie po foncie ani kodzie znaku. Dwa glify mapowane na ten sam Unicode przez różne fonty, na przykład przebieg wypełnienia w jednym osadzonym subsecie i przebieg konturu w drugim, są nadal rozpoznawane jako jeden znak. Druga strona medalu: glif, którego kod rozwiązuje się do Unicode 0, nie ma nic pewnego do porównania, co jest kolejnym powodem, by mieć oko na licznik nierozwiązanych, omówiony w ekstrakcji tekstu z wczytanych PDF-ów w HotPDF
Które wywołania ekstrakcji HotPDF usuwają zduplikowane glify?
Wszystkie wywołania produkujące tekst w HotPDF usuwają duplikaty, a surowe wywołanie glifów nie. Deduplikacja mieszka w THotPDF.ExtractVisibleLoadedPageGlyphs, kroku, który wyrzuca też glify poza widocznym polem strony, a zwykły tekst, kolumny, layout i semantyczna ekstrakcja tekstu czytają swoje glify z niego. THotPDF.ExtractLoadedPageGlyphs wciąż zwraca każdy glif namalowany przez strumień treści, nadruki włącznie, bo wołający budujący podświetlanie, redakcję czy narzędzia kryminalistyczne potrzebują pełnego zapisu tego, co było rysowane
Przy zwykłej pracy z tekstem nic się nie zmienia w kodzie wołającym. Ta sama pętla, która przed v2.768.15 ekstrahowała podwojony tekst, teraz ekstrahuje każde słowo raz
var
Pdf: THotPDF;
PageCount, I: Integer;
PageText: UnicodeString;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('brochure.pdf');
for I := 0 to PageCount - 1 do
if Pdf.ExtractLoadedPageText(I, PageText) then
if Pos('Bold words', PageText) > 0 then
Writeln('Found on page ', I + 1);
finally
Pdf.Free;
end;
end;
Wyjście layoutu i kolumn korzysta tym samym, bo zduplikowany przebieg potrafił zepchnąć resztę linii w bok na siatce znaków. ExtractLoadedPageTextColumns zachowuje swój opcjonalny mnożnik odstępu; filtr duplikatów rusza przed podziałem na kolumny, więc z tym ustawieniem nie wchodzi w interakcje
var
Grid, Columns: UnicodeString;
begin
if Pdf.ExtractLoadedPageTextLayout(0, Grid) then
TFile.WriteAllText('page1-layout.txt', Grid, TEncoding.UTF8);
// Kolumny są dzielone tam, gdzie odstęp przekracza 3x medianowy postęp glifa
if Pdf.ExtractLoadedPageTextColumns(0, Columns, 3.0) then
TFile.WriteAllText('page1-columns.txt', Columns, TEncoding.UTF8);
end;
Jeśli chcesz wiedzieć, czy strona w ogóle zawiera nadrukowany tekst, porównaj oba wywołania glifów. Miej świadomość, że różnica liczy też glify wypadające poza pole crop, więc traktuj ją jako górne ograniczenie liczby duplikatów, nie dokładną wartość
var
AllGlyphs, Visible: THPDFGlyphArray;
begin
if Pdf.ExtractLoadedPageGlyphs(0, AllGlyphs) and
Pdf.ExtractVisibleLoadedPageGlyphs(0, Visible) then
Writeln(Format('%d painted, %d read as text (%d overprinted or off-page)',
[Length(AllGlyphs), Length(Visible),
Length(AllGlyphs) - Length(Visible)]));
end;
Reguły odstępów między słowami i łamania wierszy, które zamieniają te glify w tekst, nie zmieniają się przez filtr; są szczegółowo omówione w tym, jak HotPDF stawia spacje między słowami i łamania wierszy w tekście strony
Gdzie reguła świadomie rozmija się z innymi narzędziami
HotPDF wyrzuca dokładne duplikaty, które niektóre inne narzędzia ekstrakcji zachowują, i ta różnica jest zamierzona. Gdy zmiana z v2.768.15 była sprawdzana na referencyjnym korpusie, jedna stopka strony namalowana dwa razy w dokładnie tej samej pozycji ekstrahowała się raz w HotPDF i dwa razy w referencyjnym ekstraktorze. Glif namalowany na identycznym glifie jest niewidoczny jako osobny znak, więc przeczytanie go raz to to, co strona faktycznie pokazuje
Uczciwa granica biegnie w drugą stronę. Fałszywy bold, który przesuwa drugi przebieg o więcej niż 0.1 rozmiaru glifa wzdłuż linii bazowej, nie zostaje wyłapany, podobnie jak przebieg cienia rysowany w wyraźnie innym rozmiarze. Te layouty są w praktyce rzadkie, bo efekt wizualny przestaje wyglądać jak bold, gdy przesunięcie rośnie do takich rozmiarów, ale jeśli widzisz podwojony tekst z ozdobnego nagłówka, porównaj wartości X glifów z ExtractLoadedPageGlyphs, zanim uznasz filtr za zepsuty. Filtr nie robi też nic z duplikatami, które nie są geometrycznie współlokalizowane, jak powtarzany nagłówek biegający w ukrytej warstwie gdzie indziej na stronie; to pytanie o treść, nie o nadruk
Dlaczego końcówki wierszy ginęły w tekście fontów CID?
Osobny błąd naprawiony w HotPDF v2.768.12 ucinał końce wierszy w tekście złożonym fontami CID (Type 0), których tablica szerokości /W nie była posortowana po CID. ISO 32000-1 §9.7.4.3 definiuje format tablicy /W, ale nie wymaga, by jej wpisy były w rosnącej kolejności CID, a niektóre generatory zapisują je w kolejności pierwszego użycia glifów w dokumencie
Dekoder ekstrakcji tekstu szukał szerokości wyszukiwaniem binarnym, które po cichu zakłada posortowane wejście. Na nieposortowanej tablicy większość zapytań chybiała i spadała do domyślnej szerokości /DW, czyli 1000 jednostek, pełnego em. Tekst łaciński w takim foncie był składany tak, jakby każda litera była pełnej szerokości, wiersze biegały daleko za prawą krawędzią strony, a glify, które wylądowały poza widocznym polem, były wyrzucane, więc końce wierszy znikały z wyekstrahowanego tekstu i z wyszukiwania. Nic nie padało i nic nie było raportowane, co utrudniało wychwycenie
HotPDF sortuje teraz wpisy /W stabilnym sortowaniem przez scalanie od razu po przeczytaniu, więc tam, gdzie dwa wpisy na siebie nachodzą, ten zapisany pierwszy wciąż wygrywa, co odpowiada temu, jak tablica czyta się w kolejności plikowej. Renderer strony nigdy nie był dotknięty, bo trzyma własną kopię danych szerokości i skanuje je liniowo, a pionowa tablica szerokości /W2 też jest skanowana liniowo. Ten podział jest wart zapamiętania, gdy strona wygląda idealnie, a ekstrahuje się źle: renderowanie i ekstrakcja w bibliotece PDF często dzielą znacznie mniej kodu, niż byś oczekiwał
FAQ
Czy HotPDF usuwa powtórzone litery, jak podwójne l w "fill"?
Nie. Dwie prawdziwe sąsiednie litery dzieli około jednej szerokości postępu, podczas gdy HotPDF scala wyłącznie identyczne znaki mieszczące się w 0.1 rozmiaru glifa wzdłuż linii bazowej i 0.2 w poprzek niej, więc legalne powtórzenia zostają
Jak dostać każdy glif, w tym nadrukowane duplikaty?
Wywołaj THotPDF.ExtractLoadedPageGlyphs, które zwraca każdy glif malowany przez stronę w kolejności malowania. THotPDF.ExtractVisibleLoadedPageGlyphs i wszystkie wywołania ekstrakcji tekstu zwracają zdeduplikowany, widoczny zestaw
Która wersja HotPDF naprawia podwojony tekst z fałszywego boldu?
HotPDF v2.768.15 dodał usuwanie zduplikowanych glifów do ekstrakcji tekstu, layoutu, kolumn i tekstu semantycznego, a v2.768.12 naprawił brakujące końce wierszy w fontach CID z nieposortowaną tablicą /W
Obie poprawki są w bieżącym HotPDF Delphi PDF component, który ekstrahuje tekst z wczytanych PDF-ów w Delphi i C++Builder obok swoich funkcji tworzenia, edycji i renderowania PDF