Przejdź do głównej zawartości
getnextpdf.com

Dlaczego tekst w pliku PDF nie jest tak naprawdę tekstem

Kiedy czytasz plik PDF, widzisz słowa. Plik nie zawiera słów. Zawiera instrukcje namalowania kształtów we współrzędnych, a te kształty akurat wyglądają jak litery. Przepaść między tym, co PDF rysuje, a tym, co oznacza, jest powodem, dla którego strona może wyglądać nienagannie, a mimo to dać się skopiować jako nonsens. Ta strona dotyczy tej przepaści oraz niewielkiego, osobnego odwzorowania, które ją zamyka.

Wszystko, co robisz z plikiem PDF jako z tekstem — zaznaczanie, kopiowanie, wyszukiwanie, indeksowanie do wyszukiwania, odczytywanie na głos czytnikiem ekranu — zależy od odzyskania znaków, których plik nigdy bezpośrednio nie przechował. Jeśli to odzyskiwanie zawiedzie, awaria jest niewidoczna. Strona nadal się renderuje. Nikt nie zauważa, dopóki ktoś nie skopiuje akapitu do wiadomości e-mail i nie dostanie □□□□, albo nie przeszuka 400-stronicowej umowy w poszukiwaniu klauzuli, która ewidentnie tam jest, i nie znajdzie niczego.

To kosztowna klasa błędów właśnie dlatego, że przetrwa każdą recenzję wizualną. Nie ma nic do zobaczenia. Dokument wygląda wiarygodnie i jest — dla potrzeb maszyny — niemy.

  • PDF rysuje glify — kształty wizualne dobierane za pomocą numerycznych kodów do czcionki — a nie znaki Unicode.
  • Ten sam kształt może oznaczać różne znaki, a ten sam znak może być rysowany przez różne glify. Wygląd i znaczenie są z założenia rozdzielone.
  • Aby odzyskać tekst, czytnik przebiega ścieżkę wyświetlania wstecz: od kodu do znaku. CMap /ToUnicode czcionki to tablica wyszukiwania dla tego kroku wstecznego (Spec: ISO 32000-2, §9.10).
  • Brak /ToUnicode albo błędny /ToUnicode oznacza zniekształcone kopiowanie i wklejanie oraz nieudane wyszukiwanie — przy stronie wciąż idealnej co do piksela.
  • To problem znaczenia. To, czy glif wygląda poprawnie, jest osobnym problemem wyglądu, omówionym w Czcionki: ta trudna część. NextPDF emituje poprawny /ToUnicode, dzięki czemu obieg w obie strony jest wierny.

Zacznij od tego, jak tekst w ogóle trafia na stronę. Strumień zawartości nie mówi „napisz słowo file”. Wybiera czcionkę, a następnie podaje ciąg kodów operatorowi wyświetlania tekstu (Spec: ISO 32000-2, §9). Każdy kod jest indeksem — pozycją w programie czcionki — a czcionka zamienia ten indeks w obrys glifu i go maluje. Kod 70 to nie znak F. To „slot 70 w tej czcionce”, a slot 70 akurat mieści krzywą w kształcie F. Wybierz inną czcionkę, a slot 70 mógłby być płatkiem śniegu.

Kod znaczy więc coś tylko względem kodowania swojej czcionki. Dla prostej czcionki to kodowanie jest w przybliżeniu jednym bajtem na glif. Dla pism, które potrzebują tysięcy glifów — CJK lub dowolnego dokumentu w pełnym Unicode — to za mało miejsca, więc PDF sięga po czcionkę złożoną (Type 0) (Spec: ISO 32000-2, §9.7). Czcionka złożona odczytuje wielobajtowe kody przez CMap, zamienia je w CID (identyfikatory znaków) i odwzorowuje CID-y na glify. To eleganckie pośrednictwo, które pozwala jednej czcionce zaadresować ogromny zestaw glifów. Jest to też jeszcze jedno miejsce, w którym trop od „tego, co pokazane” do „tego, co oznacza” może się urwać.

Teraz przebiegnij to wstecz. Aby wyodrębnić tekst, czytnik bierze kody, które znajdzie w strumieniu zawartości, i musi odzyskać znaki (Spec: ISO 32000-2, §9.10). Kodowanie, które narysowało glif, szło od znaku do glifu; ekstrakcja potrzebuje od kodu glifu do znaku, a ten kierunek nie ma gwarancji odwracalności. Czcionka będąca podzbiorem mogła przenumerować swoje glify. CID-y czcionki złożonej mogą być prywatne. Kształt na stronie nie niesie wewnętrznego znaczenia Unicode.

Rozwiązaniem jest dostarczenie wraz z czcionką odwzorowania wstecznego. Tym odwzorowaniem jest CMap /ToUnicode: dla każdego kodu używanego przez dokument zapisuje wartość (lub wartości) Unicode, którą ten kod reprezentuje. Z nim ekstrakcja jest czystym wyszukiwaniem. Bez niego czytnikowi pozostaje zgadywanie na podstawie kodowań czcionek i heurystyk — a zgadywanie jest dokładnie tym, gdzie fi staje się znakiem zapytania.

  1. Your charactersThe Unicode text you set, for example the word 'file'.
  2. Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
  3. Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
  4. Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
  5. Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
Dwa kierunki tekstu w PDF. Renderowanie idzie od znaku do glifu i jest tym, co widzisz. Ekstrakcja idzie od kodu do znaku i jest tym, co możesz skopiować, wyszukać i odczytać na głos — a działa tylko wtedy, gdy odwzorowanie /ToUnicode zamyka pętlę.

NextPDF zapisuje odwzorowanie wsteczne rutynowo. Gdy osadza czcionkę złożoną, emituje potomka CIDFontType2, rodzica Type0 oraz CMap /ToUnicode, dzięki czemu kody odwzorowują się z powrotem na Unicode — to praca nad kodowaniem opisana w Czcionki: ta trudna część. Warto tu wyodrębnić dlaczego: strumień /ToUnicode nie służy sprawieniu, by strona wyglądała poprawnie. Glify już bez niego wyglądają poprawnie. To jedyny artefakt, który utrzymuje tekst możliwym do wyodrębnienia.

Najsłynniejszym miejscem, w którym to się ujawnia, jest ligatura fi. Wiele czcionek rysuje f i i jako jeden połączony glif, ponieważ kropka nad i koliduje z haczykiem f. Na stronie to jeden kształt, narysowany jednym kodem. Pytanie brzmi, co się stanie, gdy go skopiujesz.

% A /ToUnicode entry that maps the single ligature code to TWO characters,
% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.
1 beginbfchar
<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'
endbfchar

Ten jeden wpis to różnica między wyszukaniem słowa „file”, które trafia, a wyszukaniem słowa „file”, które po cichu mija każde wystąpienie wyrenderowane z ligaturą. Odwzoruj kod ligatury na dwuznakową sekwencję f + i, a słowo da się wyszukać i skopiować. Pozostaw je bez odwzorowania, a strona wciąż pokazuje file idealnie, podczas gdy tekst po cichu nie mówi niczego, co maszyna mogłaby odczytać.

Dlatego „fi” może wyodrębnić się jako jeden znak lub dwa i dlatego ta różnica nie jest przypadkowa — jest tym, co odwzorowaniu /ToUnicode kazano powiedzieć. Poprawne odwzorowanie rozkłada ligaturę z powrotem na jej znaki. NextPDF emituje dokładnie taki wpis, dzięki czemu ligatura na stronie to dwie zwykłe litery w schowku.

Pułapka, nazwana: „tekst się renderuje, więc tekst jest w porządku”. Renderowanie i ekstrakcja to różne mechanizmy odczytujące różne dane. Renderowanie idzie ścieżką od kodu do glifu i jest tym, co sprawdzają twoje oczy. Ekstrakcja idzie ścieżką od kodu do znaku przez /ToUnicode i jest tym, co sprawdza każdy maszynowy konsument. Dokument może zaliczyć pierwsze i całkowicie nie spełnić drugiego, ponieważ z założenia nie ma niczego wizualnego, co by to ujawniło.

Druga, subtelniejsza pułapka: założenie, że glif „oczywiście” wie, którym jest znakiem. Nie wie. Glif to kształt z indeksem. Odwzorowanie z powrotem na znak to dodatkowa informacja, którą musi dostarczyć producent. Jeśli producent nigdy jej nie zapisał, żaden czytnik nie odzyska jej wiernie — może jedynie zgadywać.

Faithful text extraction from documents NextPDF writes — edition availability
EditionAvailability
CoreNextPDF emituje poprawną CMap /ToUnicode dla osadzanych czcionek, w tym dla przypadków ligatur i czcionek złożonych, dzięki czemu tworzone przez niego dokumenty da się wyszukiwać i kopiować.
ProNot in this edition
EnterpriseNot in this edition

Odwzorowanie /ToUnicode może nieść tylko informację, którą faktycznie odsłania czcionka źródłowa. Tam, gdzie glif nie ma dającego się ustalić znaku — czysto ozdobny znak, symbol z obszaru prywatnego użytku bez przypisania Unicode — żadne odwzorowanie nie wymyśli znaczenia, którego nigdy tam nie było. NextPDF emituje prawdziwe odwzorowanie, które potrafi wyprowadzić; nie fabrykuje znaków, by wypełnić lukę.

Ta strona dotyczy dokumentów, które NextPDF zapisuje. Nie jest to narzędzie do naprawy dowolnych przychodzących plików PDF, których /ToUnicode jest już nieobecny lub błędny; odzyskiwanie tekstu z takich plików to osobny, heurystyczny problem. A wierna ekstrakcja jest konieczna dla dostępności, ale nie jest jej całością — kolejność odczytu, znaczniki i tekst alternatywny są omówione w Co czyni PDF dostępnym.

Dlaczego ten sam plik PDF kopiuje się dobrze w jednym czytniku, a źle w innym? Różne czytniki różnie radzą sobie, gdy brakuje /ToUnicode. Niektóre zgadują na podstawie wbudowanego kodowania czcionki i mają szczęście przy zwykłym tekście łacińskim; inne nie. Poprawny /ToUnicode usuwa tę loterię — każdy zgodny czytnik dostaje te same znaki.

Czy to to samo, co nieosadzona czcionka? Nie. Osadzanie dotyczy wyglądu — tego, czy glif maluje się bez zainstalowanej czcionki. /ToUnicode dotyczy znaczenia — tego, czy kod odwzorowuje się z powrotem na znak. Czcionka może być idealnie osadzona, a mimo to nie mieć użytecznego /ToUnicode, co jest awarią typu „niewyszukiwalne, ale ładne”.

Czy ekstrakcja kiedykolwiek potrzebuje więcej niż jednego znaku na kod? Tak — przypadek ligatury jest dokładnie taki: jeden kod odwzorowuje się na dwa znaki. Wpis /ToUnicode może odwzorować pojedynczy kod na krótką sekwencję, co jest sposobem, w jaki ligatury oraz niektóre formy złożone wracają jako litery składowe.

  • Czcionki: ta trudna część — towarzysząca część po stronie wyglądu: osadzanie, tworzenie podzbiorów i sposób budowy kodowania. Ta strona to strona znaczenia tej samej monety.
  • Co czyni PDF dostępnym — wierny tekst to jeden składnik; kolejność odczytu, znaczniki i tekst alternatywny to reszta.
  • Czym właściwie jest PDF — model obiektowy, w którym żyją czcionki, kodowania i strumienie /ToUnicode.
  • Glif — kształt wizualny w czcionce (obrys). To, co PDF faktycznie maluje. Glif ma indeks, ale nie ma wewnętrznego znaczenia znakowego.
  • Znak — jednostka języka pisanego, identyfikowana przez punkt kodowy Unicode. To, co masz na myśli, i to, co ekstrakcja próbuje odzyskać.
  • Kod — wartość numeryczna w strumieniu zawartości, która wybiera glif z bieżącej czcionki. Nie jest to znak Unicode; ma znaczenie tylko względem czcionki.
  • CID — identyfikator znaku używany przez czcionkę złożoną do zaadresowania glifu w dużym zestawie; krok pośredni między kodem a glifem.
  • Czcionka złożona (Type 0) — czcionka, która odczytuje wielobajtowe kody przez CMap, aby dotrzeć do CID-ów i glifów, używana, gdy jedna czcionka musi zaadresować tysiące glifów.
  • /ToUnicode — strumień CMap, który odwzorowuje kody używane przez dokument z powrotem na wartości Unicode; mechanizm, który sprawia, że tekst PDF da się wyszukiwać, kopiować i jest dostępny.
  • Ligatura — pojedynczy glif, który rysuje dwie lub więcej liter jako jeden kształt (na przykład fi); jego wpis /ToUnicode rozkłada go z powrotem na jego znaki.