Почему текст в PDF на самом деле не текст
Когда вы читаете PDF, вы видите слова. Файл не содержит слов. Он содержит инструкции рисовать фигуры по координатам, и эти фигуры просто оказываются похожими на буквы. Разрыв между тем, что PDF рисует, и тем, что он означает, — причина того, что страница может выглядеть безупречно и при этом копироваться как бессмыслица. Эта страница — об этом разрыве и о маленьком отдельном отображении, которое его закрывает.
Почему это важно
Заголовок раздела «Почему это важно»Всё, что вы делаете с PDF как с текстом — выделяете его, копируете, ищете в
нём, индексируете его для поиска, читаете вслух с помощью программы чтения с
экрана, — зависит от восстановления символов, которые файл никогда напрямую не
хранил. Если это восстановление не удаётся, сбой невидим. Страница всё равно
отрисовывается. Никто не замечает, пока кто-нибудь не скопирует абзац в письмо и
не получит □□□□ или не выполнит поиск в 400-страничном договоре по пункту,
который явно там есть, и не найдёт ничего.
Это дорогостоящий класс ошибок именно потому, что он переживает любую визуальную проверку. Видеть нечего. Документ выглядит авторитетно и для машинных целей нем.
Если коротко
Заголовок раздела «Если коротко»- PDF рисует глифы — визуальные фигуры, выбираемые числовыми кодами в шрифте, — а не символы Unicode.
- Одна и та же фигура может означать разные символы, и один и тот же символ может рисоваться разными глифами. Внешний вид и смысл разделены по построению.
- Чтобы получить текст обратно, читатель проходит путь отрисовки в обратную
сторону: код → символ. CMap
/ToUnicodeшрифта — это таблица соответствия для этого обратного шага (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10). - Нет
/ToUnicodeили он неправильный — значит, искажённое копирование и неудачный поиск, — при том что страница по-прежнему попиксельно идеальна. - Это проблема смысла. Выглядит ли глиф правильно — отдельная проблема
внешнего вида, рассмотренная в
Шрифты: самая сложная часть. NextPDF
выдаёт правильный
/ToUnicode, чтобы обход туда-обратно был достоверным.
Как к этому подходит NextPDF
Заголовок раздела «Как к этому подходит NextPDF»Начнём с того, как текст вообще попадает на страницу. Поток содержимого не
говорит «напиши слово file». Он выбирает шрифт, а затем передаёт строку
кодов оператору отрисовки текста
(Spec: ISO 32000-2, §9ISO 32000-2 §9). Каждый код — это индекс,
позиция в программе шрифта, и шрифт превращает этот индекс в контур глифа и
рисует его. Код 70 — это не символ F. Это «слот 70 в этом шрифте», и слот
70 просто оказывается содержащим кривую в форме F. Выберите другой шрифт — и
слот 70 может оказаться снежинкой.
Так что код что-то означает только относительно кодировки своего шрифта. Для простого шрифта эта кодировка — это примерно один байт на глиф. Для письменностей, которым нужны тысячи глифов, — CJK или любой документ с полным Unicode — этого места недостаточно, и PDF обращается к составному (Type 0) шрифту (Spec: ISO 32000-2, §9.7ISO 32000-2 §9.7). Составной шрифт читает многобайтовые коды через CMap, превращает их в CID (идентификаторы символов) и отображает CID в глифы. Это изящная косвенность, которая позволяет одному шрифту адресовать огромный набор глифов. Это также ещё одно место, где след от «что показано» к «что это означает» может оборваться.
Теперь пройдём это в обратную сторону. Чтобы извлечь текст, читатель берёт коды, которые он находит в потоке содержимого, и должен восстановить символы (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10). Кодировка, нарисовавшая глиф, шла символ → глиф; извлечению нужно код глифа → символ, и это направление не гарантированно обратимо. Шрифт-подмножество мог перенумеровать свои глифы. CID составного шрифта могут быть приватными. Фигура на странице не несёт никакого встроенного смысла Unicode.
Решение — поставлять обратное отображение вместе со шрифтом. Это отображение —
CMap /ToUnicode: для каждого кода, который использует документ, оно
записывает значение (или значения) Unicode, которое этот код обозначает. С ним
извлечение — это чистый поиск по таблице. Без него читателю остаётся гадать по
кодировкам шрифта и эвристикам, — а гадание — это ровно то место, где fi
становится вопросительным знаком.
- Your charactersThe Unicode text you set, for example the word 'file'.
- Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
- Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
- Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
- Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
NextPDF пишет обратное отображение как нечто само собой разумеющееся. Когда он
встраивает составной шрифт, он выдаёт потомка CIDFontType2, родителя Type0 и
CMap /ToUnicode, чтобы коды разрешались обратно в Unicode, — работа с
кодировкой, описанная в
Шрифты: самая сложная часть. Здесь стоит
отдельно выделить почему: поток /ToUnicode — не о том, чтобы страница
выглядела правильно. Глифы и без него уже выглядят правильно. Это единственный
артефакт, который сохраняет текст извлекаемым.
Практический пример
Заголовок раздела «Практический пример»Самое известное место, где это проявляется, — лигатура fi. Многие шрифты
рисуют f и i как один объединённый глиф, потому что точка над i
сталкивается с крючком f. На странице это одна фигура, нарисованная одним
кодом. Вопрос в том, что происходит, когда вы её копируете.
% 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Эта одна запись — разница между совпадающим поиском по «file» и поиском по
«file», который молча пропускает каждое вхождение, отрисованное с лигатурой.
Отобразите код лигатуры в двухсимвольную последовательность f + i — и слово
становится пригодным для поиска и копирования. Оставьте его без отображения — и
страница по-прежнему показывает file безупречно, при том что текст тихо не
говорит ничего, что машина могла бы прочитать.
Вот почему «fi» может извлекаться как один символ или как два, и почему эта
разница не случайна — это то, что отображению /ToUnicode велели говорить.
Правильное отображение разбирает лигатуру обратно на её символы. NextPDF выдаёт
ровно такую запись, поэтому лигатура на странице — это две обычные буквы в буфере
обмена.
Распространённое заблуждение
Заголовок раздела «Распространённое заблуждение»Названная ловушка: «текст отрисовывается, значит, текст в порядке». Отрисовка
и извлечение — это разные механизмы, читающие разные данные. Отрисовка идёт по
пути «код → глиф» и есть то, что проверяют ваши глаза. Извлечение идёт по пути
«код → символ» через /ToUnicode и есть то, что проверяет каждый машинный
потребитель. Документ может с блеском пройти первое и полностью провалить второе,
потому что по построению нет ничего визуального, что бы это выдало.
Вторая, более тонкая ловушка: предположение, что глиф «очевидно» знает, какой он символ. Он не знает. Глиф — это фигура с индексом. Отображение обратно в символ — это дополнительная информация, которую должен предоставить производитель. Если производитель её так и не записал, ни один читатель не сможет достоверно её восстановить — он может только гадать.
Ограничения и границы
Заголовок раздела «Ограничения и границы»| Edition | Availability |
|---|---|
| Core | NextPDF выдаёт правильную CMap /ToUnicode для встраиваемых им шрифтов, включая случаи лигатур и составных шрифтов, поэтому создаваемые им документы пригодны для поиска и копирования. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Отображение /ToUnicode может нести лишь ту информацию, которую исходный шрифт
действительно раскрывает. Там, где у глифа нет определимого символа —
исключительно декоративный знак, символ из области частного использования без
назначения Unicode, — никакое отображение не может выдумать смысл, которого
никогда не было. NextPDF выдаёт правдивое отображение, которое может вывести; он
не фабрикует символы, чтобы заполнить пробел.
Эта страница — о документах, которые NextPDF пишет. Это не инструмент
восстановления произвольных входящих PDF, чей /ToUnicode уже отсутствует или
ошибочен; восстановление текста из таких — отдельная, эвристическая проблема. И
достоверное извлечение необходимо для доступности, но это не вся доступность —
порядок чтения, теги и альтернативный текст рассмотрены в
Что делает PDF доступным.
Мини-FAQ
Заголовок раздела «Мини-FAQ»Почему один и тот же PDF копируется нормально в одном средстве просмотра и
плохо в другом?
Разные средства просмотра по-разному откатываются, когда /ToUnicode
отсутствует. Одни гадают по встроенной кодировке шрифта и угадывают на обычном
латинском тексте; другие нет. Правильный /ToUnicode убирает лотерею — каждое
соответствующее стандарту средство просмотра получает одни и те же символы.
Это то же самое, что невстроенный шрифт?
Нет. Встраивание — о внешнем виде: рисуется ли глиф без установленного шрифта.
/ToUnicode — о смысле: отображается ли код обратно в символ. Шрифт может быть
идеально встроен и при этом не иметь пригодного /ToUnicode — это сбой «красиво,
но не находится поиском».
Нужно ли извлечению когда-нибудь больше одного символа на код?
Да — случай лигатуры именно таков: один код отображается в два символа. Запись
/ToUnicode может отобразить один код в короткую последовательность, и именно так
лигатуры и некоторые составные формы возвращаются как составляющие их буквы.
Связанная документация
Заголовок раздела «Связанная документация»- Шрифты: самая сложная часть — сопутствующая статья со стороны внешнего вида: встраивание, создание подмножеств и как строится кодировка. Эта страница — сторона смысла той же монеты.
- Что делает PDF доступным — достоверный текст — один из ингредиентов; порядок чтения, теги и альтернативный текст — остальное.
- Что такое PDF на самом деле —
объектная модель, внутри которой живут шрифты, кодировки и потоки
/ToUnicode.
Глоссарий
Заголовок раздела «Глоссарий»- Глиф — визуальная фигура в шрифте (контур). То, что PDF на самом деле рисует. У глифа есть индекс, но нет встроенного смысла символа.
- Символ — единица письменного языка, идентифицируемая кодовой точкой Unicode. То, что вы имеете в виду, и то, что пытается восстановить извлечение.
- Код — числовое значение в потоке содержимого, которое выбирает глиф из текущего шрифта. Не символ Unicode; осмыслен только относительно шрифта.
- CID — идентификатор символа, используемый составным шрифтом для адресации глифа в большом наборе; промежуточный шаг между кодом и глифом.
- Составной (Type 0) шрифт — шрифт, который читает многобайтовые коды через CMap, чтобы добраться до CID и глифов, используемый, когда один шрифт должен адресовать тысячи глифов.
/ToUnicode— поток CMap, отображающий используемые документом коды обратно в значения Unicode; то, что делает текст PDF пригодным для поиска, копирования и доступным.- Лигатура — единый глиф, рисующий две или более букв как одну фигуру
(например,
fi); его запись/ToUnicodeразбирает его обратно на составляющие символы.