От символов к глифам: как движок набора собирает сложный текст

Движок набора (shaping engine) превращает последовательность Unicode-символов в последовательность глифов выбранного шрифта, задаёт им порядок и позиции. Поэтому копируемый текст и видимая строка связаны, но не совпадают один к одному: несколько символов могут стать одной лигатурой, а один знак — набором компонентов. Понимание этих этапов помогает отличить ошибку кодировки от сбоя шрифта или приложения.
Почему текст — это ещё не готовые буквы
В текстовом файле обычно записаны символы, например буквы и знаки препинания. Шрифт сопоставляет поддерживаемым символам глифы — конкретные рисунки внутри файла. Таблица cmap даёт базовое соответствие кодовой точки и глифа, но сложная строка требует дополнительных решений: определить направление и язык, найти контекстные варианты, расставить диакритические знаки, вычислить интервалы и понять, где текст допустимо переносить.
Представьте латинскую последовательность f + i. В исходном тексте остаются две буквы, но включённая функция стандартной лигатуры может заменить пару одним глифом fi. При этом система должна сохранить связь результата с исходной последовательностью, чтобы выделение, поиск и курсор не ломались. В арабском письме контекст способен влиять на форму и соединение букв; у комбинируемых знаков важна позиция относительно основы. Это задачи набора, а не простого выбора рисунка.
Путь строки: от Unicode до позиции на странице
У разных систем есть свои внутренние детали, но полезно мыслить несколькими этапами. Приложение сначала делит абзац на фрагменты, для которых согласованы шрифт, письмо, язык и направление. Затем движок подбирает глифы и применяет правила шрифта, возвращая не только индексы глифов, но и их позиции. Редактор строк раскладывает результат по доступной ширине и передаёт очертания на отрисовку. Это упрощённая модель: реальные конвейеры могут объединять или повторять этапы.
- Кодировка и сегментация: программа читает Unicode, выделяет участки текста и передаёт параметры письма и языка.
- Выбор шрифта: для каждого фрагмента выбирается гарнитура, содержащая нужные глифы и таблицы. Если символа нет, механизм резервного шрифта должен сохранить связную группу знаков.
- Шейпинг: движок анализирует последовательность с учётом выбранного письма и применяет доступные правила замены и позиционирования.
- Компоновка строки: программа использует ширины и признаки границ, чтобы расставить пробелы и переносы, не разрывая связные группы там, где это повредит форме.
- Отрисовка: готовые глифы превращаются в пиксели или контуры для экрана, печати либо векторного экспорта.
В документации HarfBuzz это различие видно буквально: hb_shape() получает шрифт, буфер Unicode, направление, письмо, язык и при необходимости список функций, а на выходе содержит глифы и их позиции. HarfBuzz — пример широко используемого открытого движка, а не название единого компонента во всех приложениях. Другие программы могут использовать собственные библиотеки и иначе распределять обязанности между шейпингом, переносом строк и отрисовкой.
Что делают GSUB и GPOS
OpenType Layout — набор таблиц, в которых шрифт описывает операции для конкретных писем и языков. GSUB отвечает за замену глифов: одна форма может замениться другой, несколько входных глифов — соединиться в лигатуру, а выбор варианта может зависеть от окружения. В латинице это, например, декоративная лигатура или локальная форма. В письмах с контекстными соединениями замены участвуют в формировании правильной формы буквы.
GPOS корректирует расположение уже выбранных глифов. Это не только привычный кернинг пары, но и привязка акцента к базовой букве, позиционирование надстрочного знака или соединение курсивных форм. Для набора диакритики фиксированные интервалы часто недостаточны: знак должен следовать за шириной конкретной буквы и занимать свободную область над ней. Спецификация OpenType описывает для этого отдельные типы позиционирования, в том числе MarkToBase и MarkToMark.
Важно, что наличие таблицы не гарантирует видимый эффект в любой строке. Приложению нужно определить письмо, язык и набор активных функций; таблице шрифта — содержать подходящие правила; а движку — корректно применить их в контексте. Если один из слоёв не совпал, в панели функций может быть включена лигатура, но строка всё равно не изменится.
Направление, кластеры и безопасный перенос
Смешанный текст требует отдельного определения направления. В строке справа налево могут встречаться цифры, латинский бренд, скобки и знаки пунктуации с другим поведением. Алгоритм Unicode Bidirectional Algorithm рассчитывает порядок отображения для таких фрагментов, при этом хранимый текст остаётся в логическом порядке. Само расположение строк и переносы в этот алгоритм не входят; после вычисления направления к подходящим фрагментам применяется шейпинг.
Другой важный термин — кластер. Он связывает выходной глиф или группу глифов с участком исходной строки. Лигатура может объединить несколько кодовых точек в один глиф; комбинируемый знак может визуально присоединиться к соседней букве; сложная последовательность может получить несколько глифов. Кластер помогает интерфейсу не поставить курсор в визуально невозможное место и предупреждает разрыв там, где сочетание нужно перенабрать вместе. Кластер не следует путать с графемой: это связанные, но разные понятия.
Практический пример: почему диакритика оказалась не там
Допустим, в слове с несколькими надстрочными знаками акценты сталкиваются или один знак висит над пустым местом. Проверьте проблему по слоям. Если акцент заменён на пустой прямоугольник, вероятен пробел в покрытии шрифта или резервной подстановке. Если глиф присутствует, но оторван от основы, проверьте метрики и якоря GPOS. Если сбой возникает только при сочетании нескольких знаков, проверьте MarkToMark и конкретный порядок их применения. Если неправильно только в одном редакторе, сравните язык, выбранный шрифт и обработчик набора в других программах.
Для латинской типографики диагностика похожа. Когда f и i не превращаются в желаемую лигатуру, сначала убедитесь, что выбранный шрифт содержит её и нужную функцию liga. Проверьте, не отключает ли функцию приложение для данного участка, например в настройках табличных или кодовых цифр, и не стоит ли между буквами невидимый разделитель. Лигатура — необязательная типографическая форма; для поиска или корректного текстового копирования символы должны оставаться доступными как исходная последовательность.
Как локализовать сбой шейпинга
Диагностируйте строку как воспроизводимый случай. Скопируйте только проблемный фрагмент, запишите название и версию шрифта, приложение, язык и направление текста, а также ожидаемое отображение. Уберите декоративные настройки и сначала проверьте обычный набор. Если приложение показывает кодовые точки или текст можно безопасно сравнить в редакторе кода, убедитесь, что там нет неожиданного ZWJ, ZWNJ, пробела или комбинируемого знака в неверной последовательности.
- Сравните один и тот же фрагмент в другом приложении с актуальной поддержкой нужного письма.
- Проверьте, что основной шрифт покрывает текст; включённый fallback может менять отдельные знаки и их метрики.
- Укажите корректные язык и направление: автоматическое определение не всегда подходит короткому фрагменту.
- Включайте нужные OpenType-функции точечно и смотрите на конкретный результат, а не только на переключатель.
- Разделите проблему: неверная форма указывает на подстановку или поддержку письма, неверный интервал — на позиционирование и метрики, неверный порядок — на направление или разметку.
- Для шрифта собственного производства протестируйте характерные сочетания в нескольких движках и проверьте таблицы шейпинга специальным анализатором OpenType.
Типичные ошибки диагностики
- Считать, что одна кодовая точка всегда соответствует одному глифу. Это неверно для лигатур, комбинируемых знаков и контекстной формы.
- Приписывать любой визуальный дефект шрифту. Источником могут быть язык, направление, текстовый движок, неподходящий резервный шрифт или настройки функции.
- Смешивать порядок хранения с порядком отображения. В тексте справа налево нельзя «починить» всю строку ручной перестановкой символов.
- Переносить абзац до шейпинга без повторного расчёта. Если строка меняется, контекстные формы и ширины могут потребовать нового результата.
- Проверять только словом из одного знака. Для связи глифов нужны реальные сочетания, диакритика, смешанная пунктуация и примеры из конкретного языка.
Чек-лист проверки шрифта и текстового движка
- Составьте набор из базовых букв, диакритики, контекстных форм, лигатур и смешанных строк, необходимых целевому письму.
- Сверьте кодовые точки теста с ожидаемой последовательностью Unicode и удалите случайные невидимые символы только после их идентификации.
- Проверьте покрытие глифов и OpenType-функции в самом шрифте, а не только список символов, показанный редактором.
- Сравните результат в приложении, браузере или текстовом движке, где будет использоваться шрифт.
- Запишите входной текст, параметры письма и языка, версию шрифта и изображение ожидаемого результата — так ошибку можно повторить после правки.
Часто задаваемые вопросы
Чем символ отличается от глифа?
Символ — единица текста с кодовой точкой Unicode; глиф — рисунок, который шрифт и система набора выбирают для отображения. Один символ может иметь контекстные варианты, а несколько символов могут отображаться одним глифом, например лигатурой.
Что такое shaping простыми словами?
Shaping, или шейпинг, — обработка фрагмента текста, которая подбирает и заменяет глифы, задаёт их позиции и сохраняет связь с исходными знаками. Она нужна для лигатур, диакритики и письма, где форма зависит от окружения.
GSUB и GPOS — одно и то же?
Нет. GSUB описывает замены глифов, а GPOS — их позиционирование и взаимное прикрепление. В сложном наборе движок может применить обе таблицы: сначала получить нужные формы, затем расставить знаки или интервалы.
Почему шрифт отображает акцент отдельно от буквы?
Проверьте, покрывает ли выбранный шрифт и букву, и комбинируемый знак, есть ли в нём правила GPOS для привязки, а также правильно ли движок определил язык и письмо. Сравнение в другом приложении поможет отделить проблему шрифта от поведения конкретного редактора.
Может ли один символ Unicode превратиться в несколько глифов?
Да. Некоторые письма используют последовательности компонентов, а комбинируемые знаки могут размещаться отдельно от базового глифа. Обратная ситуация также возможна: несколько исходных символов формируют одну лигатуру. Поэтому соответствие текста и глифов не всегда один-к-одному.
Означает ли включённая лигатура, что все пары заменятся?
Нет. В шрифте должна быть соответствующая подстановка; приложение должно поддерживать её и не отключать для данного участка; последовательность должна совпадать с условиями правила. Кроме того, часть лигатур факультативна и зависит от языка, функции и редакторских настроек.
Коротко о проверяемых источниках
Первичные описания: спецификация Microsoft о таблице GSUB (Спецификация OpenType: GSUB) и таблице GPOS (Спецификация OpenType: GPOS); руководство HarfBuzz по шейпингу (руководство HarfBuzz по шейпингу) и кластерам (руководство HarfBuzz по кластерам); нормативное описание Unicode Bidirectional Algorithm (Unicode Bidirectional Algorithm). Сверяйте реализацию с актуальной версией документации и конкретным приложением: шрифтовой формат описывает данные, а приложение определяет, как включить их в свой набор текста.
Вывод
Видимая строка рождается не в одном шрифтовом файле: Unicode задаёт текст, шейпер подбирает формы, GSUB и GPOS описывают операции, а компоновщик рассчитывает строки. Если разделять эти роли, проблема становится проверяемой. Создавая собственный шрифт, подготовьте реальные тестовые слова и проверьте их в целевых программах; начать с рукописного алфавита можно в Fontgenerator.