Шрифты: самая сложная часть
ISO 32000-2 §9 Evidence: Mixed evidence
Шрифты — та область, где PDF может выглядеть совершенно правильно и при этом быть незаметно сломанным. Страница может отрисовывать нужные глифы, но не находиться поиском, не копироваться как текст и не соответствовать профилю архивного хранения. Всё это может случиться одновременно, и визуально ничто вас не предупредит. Эта страница о трёх вещах, которые должны быть сделаны правильно: встраивании, создании подмножеств и кодировании, — и о том, что NextPDF делает для каждой из них.
Почему это важно
Заголовок раздела «Почему это важно»“Выглядит нормально” — самая опасная фраза в работе с PDF, и именно со шрифтами она обходится дороже всего. Должны выполняться три независимых условия:
- Встраивание — программа шрифта переносится внутрь файла, поэтому он отрисовывается одинаково даже на машине, где этот шрифт не установлен.
- Создание подмножества — переносятся только реально используемые глифы, поэтому CJK-шрифт размером 20 MB не раздувает каждый документ.
- Кодирование — есть правильное отображение кодов символов на странице обратно в Unicode, поэтому текст можно искать, копировать, индексировать и читать с помощью вспомогательных технологий.
Визуальная отрисовка лишь частично подтверждает первое условие. Документ может показывать безупречные глифы и при этом полностью проваливать третье: текст оказывается изображением слов, а не словами. Именно такой сбой проходит любую проверку “выглядит нормально”, а затем проваливает аудит соответствия или запрос на раскрытие документов.
Если коротко
Заголовок раздела «Если коротко»- Шрифт в PDF — это словарь и, как правило, поток со встроенной программой шрифта.
- Создание подмножества переписывает эту программу так, чтобы в ней остались только используемые глифы. Имя шрифта-подмножества получает тег из шести заглавных букв и
+, чтобы средства просмотра воспринимали его как отдельный шрифт. - Кодирование — отдельная задача: отобразить коды символов в Unicode. CMap
/ToUnicodeделает текст пригодным для поиска и копирования и не зависит от того, правильно ли выглядят глифы. - Правильно выглядящий текст без
/ToUnicode(или с неправильным/ToUnicode) — классический незаметный сбой: на экране всё идеально, на практике поиск не работает. - NextPDF создаёт подмножества шрифтов TrueType, сохраняет идентичность глифов для правильной отрисовки и выдаёт CMap
/ToUnicode, чтобы извлечение работало, — а также может обеспечивать выполнение правила встраивания PDF 2.0, а не только предупреждать.
Как к этому подходит NextPDF
Заголовок раздела «Как к этому подходит NextPDF»Создание подмножества. FontSubsetter (src/Typography/FontSubsetter.php) разбирает каталог таблиц исходного шрифта TrueType и читает cmap, чтобы сопоставить кодовые точки Unicode с идентификаторами глифов. Он обрабатывает и BMP format 4, и format 12 с полным покрытием Unicode, который нужен для CJK. Затем он делает шаг, который наивные средства создания подмножеств пропускают: разрешает зависимости составных глифов по транзитивному замыканию. Глиф с диакритикой, построенный из базовой буквы и комбинируемого знака, ссылается на другие глифы как на компоненты. Если эти компоненты отбросить, глиф отрисуется неправильно. Средство создания подмножеств обходит этот граф, пока не перестанут появляться новые компоненты, с защитой от циклов, чтобы некорректный шрифт не мог зациклить процесс.
В этом файле стоит отметить два инженерных решения. Во-первых, идентификаторы глифов сохраняются, а не перенумеровываются: неиспользуемые слоты заполняются нулями в glyf/loca, поэтому исходные индексы глифов в потоке содержимого остаются действительными при CIDToGIDMap /Identity. Перенумерование было бы компактнее, но потребовало бы переписать каждую ссылку на глиф. Сохранение идентичности корректно по построению. Во-вторых, обход отсортирован (по возрастанию gid), поэтому подмножество побайтово детерминировано: один и тот же шрифт и одни и те же используемые глифы дают одни и те же байты подмножества, что и требуется для воспроизводимых сборок. Если создание подмножества сэкономило бы менее ~10 % файла, исходный шрифт возвращается без изменений. Накладные расходы не стоят такого небольшого выигрыша.
Встраивание. Явная политика решает, переносится ли программа шрифта вообще, — без догадок. Pdf20FontEmbeddingPolicy (src/Writer/Pdf20FontEmbeddingPolicy.php) имеет два режима. В профиле PDF 2.0 Strict отклоняет ссылку на невстроенный стандартный Type 1 (“Base14”) с типизированным исключением — это поведение корректно с точки зрения соответствия. AllowBase14 сохраняет исторический рекомендательный путь. На время миграции он выдаёт минимальный дескриптор шрифта, который стандарт всё ещё требует, и отправляет предупреждение, а не выбрасывает исключение. Вызывающая сторона выбирает режим явно на уровне документа; он никогда не выводится из самого шрифта.
Кодирование. Для составных (Type 0) шрифтов EmbeddedTtfFontDictBuilder (src/Writer/EmbeddedTtfFontDictBuilder.php) выдаёт потомка CIDFontType2, родителя Type0 и поток CMap /ToUnicode, чтобы коды символов разрешались обратно в Unicode. Поток /ToUnicode правомерно отсутствует только в одном случае: когда самоописываемая предопределённая CJK CMap уже даёт средству просмотра отображение символов в Unicode. В этом случае CMap и есть кодирование, поэтому простой профиль опускает избыточный поток /ToUnicode, чтобы сэкономить байты. Во всех остальных случаях именно поток /ToUnicode сохраняет текст текстом.
| Аспект | Что он гарантирует | Чего он не гарантирует | Незаметный сбой при ошибке |
|---|---|---|---|
| Встраивание | Одинаковая отрисовка без установленного шрифта | Что текст пригоден для поиска | Подставленный шрифт; неправильные метрики на другой машине |
| Создание подмножества | Маленький файл; только используемые глифы | Что-либо о кодировании | Отсутствующие компоненты составных глифов → сломанные глифы с диакритикой |
Кодирование (/ToUnicode) | Текст, пригодный для поиска, копирования и доступный для вспомогательных технологий | Что глифы отрисовываются правильно | Идеально выглядящая страница, непригодная для поиска / искажённая при копировании |
Три аспекта шрифтов независимы. Встраивание и создание подмножеств отвечают за внешний вид и размер; кодирование отвечает за смысл. Страница может пройти первые два условия и провалить третье, причём визуально это никак не проявится.
Что говорят свидетельства
Заголовок раздела «Что говорят свидетельства»Правило именования подмножеств нормативно и сформулировано точно.
Spec: ISO 32000-2, §9.9.2 ISO 32000-2 §9.9.2 требует, чтобы
PostScript-имя подмножества шрифта — BaseFont и FontName дескриптора —
начиналось с тега из ровно шести заглавных букв, затем знака плюс,
затем PostScript-имени исходного шрифта. Он также требует, чтобы разные
подмножества одного и того же шрифта в одном файле использовали разные теги. Именно это правило позволяет
средству просмотра различать два подмножества и правильно объединять документы. Evidence: Standard-backed
Кодирование — отдельный пункт стандарта, не связанный с отрисовкой.
Spec: ISO 32000-2, §9.10.3 ISO 32000-2 §9.10.3 определяет /ToUnicode
как поток, содержащий CMap, которая отображает коды символов в значения Unicode,
а процедура извлечения текста в
Spec: ISO 32000-2, §9.10.2 ISO 32000-2 §9.10.2 использует эту CMap для
преобразования кодов символов в Unicode при поиске и индексировании. Ничто в
механизме отрисовки глифов не затрагивает /ToUnicode — именно поэтому
текст может выглядеть правильно, а извлекаться неправильно.
Что касается встраивания, стандарт указывает, что большинство словарей шрифтов содержат дескриптор шрифта, чей поток встроенного файла шрифта необязателен, но настоятельно рекомендуется. PDF 2.0 ужесточает это специально для четырнадцати стандартных шрифтов Type 1. Политика Strict в NextPDF — корректное с точки зрения соответствия прочтение этого ужесточения. AllowBase14 — явный механизм обратной совместимости, включаемый по выбору: движок никогда не понижает требования незаметно.
| Edition | Availability |
|---|---|
| Core | Доступно. Создание подмножеств, выдача |
| Pro | Добавляет более глубокий контроль соответствия и отчётность по встраиванию шрифтов на уровне профиля. |
| Enterprise | Добавляет такой же контроль соответствия в рамках корпоративного эксплуатационного контура. |
Практический пример
Заголовок раздела «Практический пример»Вот две половины правильно встроенного составного шрифта с подмножеством, пригодного для поиска. Тег подмножества следует правилу шести букв из стандарта; ссылка /ToUnicode сохраняет текст извлекаемым.
% The Type 0 (composite) font dictionary20 0 obj<< /Type /Font /Subtype /Type0 /BaseFont /ABCDEF+NotoSans % six-letter subset tag + '+' /Encoding /Identity-H /DescendantFonts [21 0 R] /ToUnicode 23 0 R >> % the map that makes text searchableendobj
% The descendant CIDFontType2 (carries the subsetted program)21 0 obj<< /Type /Font /Subtype /CIDFontType2 /BaseFont /ABCDEF+NotoSans /CIDToGIDMap /Identity % glyph IDs preserved, not remapped /FontDescriptor 22 0 R >>endobjПоле /ToUnicode 23 0 R объекта 20 — это и есть разница между документом, пригодным для поиска, и его изображением. Уберите его (вне случая предопределённой CMap), и каждый глиф по-прежнему будет отрисовываться идеально, но поиск не найдёт на странице ни одного слова.
Распространённое заблуждение
Заголовок раздела «Распространённое заблуждение»Если сказать прямо, ловушка вот в чём: правильная отрисовка глифов ничего не говорит о том, является ли текст текстом. Отрисовка идёт по пути “кодирование → глиф”. Поиск и копирование идут по пути “код → Unicode” (/ToUnicode). Это разные механизмы, которые читают разные части словаря шрифта. Поэтому документ может иметь безупречный визуальный вывод и отсутствующий или неправильный /ToUnicode. В результате получается страница, которая выглядит убедительно, но функционально непригодна для поиска, — сбой, который проходит любую визуальную проверку, потому что по определению смотреть не на что.
Сопутствующая ловушка — предположение “шрифт встроен, значит, с архивным хранением всё в порядке”. Встраивание необходимо, но недостаточно. Профиль вроде PDF/A также ожидает подмножества, названные по правилу шести букв, и правильное кодирование. Встроенный, но непригодный для поиска шрифт всё равно приводит к сбою.
Ограничения и границы
Заголовок раздела «Ограничения и границы»Средство создания подмножеств в NextPDF — это именно средство создания подмножеств TrueType. Оно требует обязательных таблиц TrueType и возвращает исходный шрифт без изменений, если они отсутствуют или выигрыш ниже порога ~10 %. Создание подмножества и CMap /ToUnicode делают текст извлекаемым, но не могут спасти исходный шрифт, в котором нет информации для отображения глифа обратно в осмысленный символ. Там, где значение Unicode определить нельзя, никакая выдача CMap его не придумает.
Эта страница о создании правильной структуры шрифтов в документах, которые NextPDF записывает. Это не инструмент восстановления шрифтов в произвольных входящих PDF. И выдача соответствующего подмножества и кодирования сама по себе не сертифицирует документ по полному профилю архивного хранения — это отдельная, более широкая проверка.
Мини-FAQ
Заголовок раздела «Мини-FAQ»Почему тег из шести букв — почему не имя шрифта? Чтобы средство просмотра могло различать два разных подмножества одного и того же шрифта и объединять документы, не смешивая их наборы глифов. Разные подмножества — разные теги, по правилу.
Когда допустимо не иметь /ToUnicode? Когда самоописываемая предопределённая CJK CMap уже предоставляет отображение символов в Unicode. В этом случае CMap и есть кодирование. Отдельная /ToUnicode была бы избыточной. В остальных случаях её отсутствие — дефект.
Может ли создание подмножества когда-либо навредить? Только если сделать его неправильно. Отбрасывание компонентов составных глифов ломает глифы с диакритикой. Перенумерование идентификаторов глифов без переписывания ссылок ломает отрисовку. NextPDF избегает обоих сценариев: разрешает замыкание по компонентам и сохраняет идентичность глифов.
Связанная документация
Заголовок раздела «Связанная документация»- Потоки и фильтры — встроенные программы шрифтов — это отфильтрованные потоковые объекты со своим собственным контрактом декодирования.
- Что такое PDF на самом деле — объектная модель, в которой живут словари шрифтов и потоки программ.
- PDF 2.0: что изменилось — в том числе ужесточённые ожидания по встраиванию шрифтов в базовом стандарте 2.0.
Глоссарий
Заголовок раздела «Глоссарий»- Встроенная программа шрифта — собственно файл шрифта (TrueType/CFF/Type 1), переносимый внутрь PDF в виде потока, поэтому отрисовка не зависит от шрифтов, установленных у средства просмотра.
- Создание подмножества — переписывание программы шрифта так, чтобы для уменьшения размера она содержала только используемые документом глифы.
- Тег подмножества — обязательный префикс из шести заглавных букв плюс
+в имени шрифта-подмножества (например,ABCDEF+NotoSans). /ToUnicode— поток CMap, отображающий коды символов в значения Unicode; то, что делает текст PDF пригодным для поиска, копирования и доступным.- Составной глиф — глиф, построенный путём ссылок на другие глифы как на компоненты; его компоненты должны сохраняться при создании подмножества.
CIDToGIDMap /Identity— режим, в котором индексы глифов в потоке содержимого являются неизменёнными собственными идентификаторами глифов шрифта; NextPDF сохраняет идентичность глифов, чтобы это оставалось действительным.- Base14 — четырнадцать стандартных шрифтов Type 1; PDF 2.0 ожидает, что шрифты будут встроены, а не указаны по имени.