Лигатура есть в проекте, но исчезла в файле шрифта: как проверить сборку GSUB

Лигатура может быть нарисована и присутствовать в проекте, но при наборе сочетание букв всё равно останется раздельным. Причина часто не в контуре: правило подстановки не попало в собранную таблицу GSUB, ссылается на другое имя глифа или выключено при формировании строки. Ниже — способ пройти цепочку от исходника до сформированной последовательности глифов и определить, на каком этапе теряется функция.
Сначала разделите три разных объекта
В шрифте важно различать буквы-коды, глифы и правила их замены. Символы текста, например f и i, сопоставляются с базовыми глифами через cmap. Нарисованный глиф fi может существовать рядом с ними, но сам по себе он не говорит программе, когда его нужно показать. Для автоматической замены последовательности глифов используется GSUB — таблица подстановок OpenType. Microsoft описывает её как слой, который позволяет заменить несколько входных глифов одним, в том числе при формировании лигатуры.
Это различие превращает расплывчатое «лигатура сломалась» в три проверяемых вопроса: есть ли целевой глиф в файле, есть ли правило для нужной последовательности, и применяет ли тестирующий наборщик функцию. Обычный просмотр списка глифов отвечает только на первый вопрос.
Запишите ожидаемое поведение до пересборки
Начните с конкретной строки и режима. Для стандартной лигатуры fi обычно проверяют обычный текст с включённой функцией liga; декоративная или редкая замена может быть размечена как discretionary ligature и проверяться отдельно. Не смешивайте эти сценарии: стандартная и необязательная функции имеют разные теги, а приложение может включать их по-разному. Если функция нужна только для одного контекста, запишите и контекст — например, обычное сочетание, слово или границы пунктуации.
- Укажите входной текст в Unicode, например «fi» или «ffi».
- Запишите ожидаемый выходной глиф и имя, которое ему присвоено в проекте.
- Отметьте тег функции и включённое состояние; для проверки сравните включённый и выключенный режим.
- Используйте одну и ту же строку для исходника, теста компилятора и целевой программы.
Такой мини-тест предотвращает частую ошибку: автор ищет лигатуру по видимому слову, а на самом деле проверяет другую функцию или другую последовательность. Для рукописной гарнитуры заранее решите, является ли конкретное соединение автоматическим правилом или отдельным символом, который пользователь должен вставлять вручную.
Проверьте глиф и его имя в исходнике
Откройте именно исходный проект, из которого собирается финальный файл. Убедитесь, что глиф лигатуры не просто виден на странице, но сохранён как отдельный глиф с устойчивым именем. Затем сравните имена каждого компонента правила с именами реальных базовых глифов. В одном проекте визуально одинаковые глифы могут называться по-разному: например, `f`, `f.alt` и `f.locl`. Правило `sub f i by f_i;` не найдёт последовательность, если базовая буква на самом деле называется `f.alt` и правило не содержит соответствующего контекста.
Проверьте и состав лигатуры. Если цель — ffi, все три входных глифа должны присутствовать, а итоговый глиф должен быть тем, который использует правило. Для варианта fi внутри более длинной последовательности порядок и устройство lookup могут влиять на результат; не исправляйте такое поведение простым переименованием, пока не увидели фактически сформированную последовательность. Название глифа является меткой внутри проекта, а не заменой самому правилу.
Убедитесь, что правило включено в сборку
Редакторы шрифтов и конвейеры сборки хранят OpenType-функции по-разному: в панели функций, feature-коде, отдельном `.fea`-файле или конфигурации команды сборки. Найдите действительный входной файл текущей сборки и проследите его до команды экспорта. Если код лежит рядом с проектом, но сборщик его не читает, в превью редактора правило может работать, а в выгруженном TTF его не будет.
Для простого примера feature-код может описывать соответствие примерно так: `feature liga { sub f i by f_i; } liga;`. Это только иллюстрация структуры правила; синтаксис и место хранения зависят от редактора и компилятора. В FontTools библиотека feaLib читает и записывает OpenType feature-файлы, которые предназначены, среди прочего, для описания данных GSUB и GPOS. Поэтому при использовании `.fea` убедитесь, что файл добавлен в команду или конфигурацию сборки, а не только сохранён в каталоге.
Посмотрите журнал компилятора. Предупреждение о неизвестном имени глифа, дублирующемся определении, пустом lookup или пропущенном файле — полезная улика. Не игнорируйте сообщения лишь потому, что экспорт завершился успешно: сборщик может выдать валидный файл без правила, которое не смог обработать. Сохраните лог рядом с конкретной сборкой, чтобы сравнить его до и после правки.
Проверьте скомпилированный файл, а не только окно редактора
Сначала откройте экспортированный файл в редакторе таблиц шрифта или проверочном инструменте и убедитесь, что в GSUB есть нужный feature tag, lookup и входная пара глифов. Наличие записи `liga` само по себе недостаточно: она может содержать другой набор правил или отсылать к другой последовательности. Для контрольного теста сравните сборку с исходным файлом, когда редактор умеет открывать оба варианта.
Затем проверьте shaping. Утилита HarfBuzz `hb-shape` позволяет сформировать текст в последовательность позиционированных глифов и показать результат в терминале. Пример команды: `hb-shape ./MyFont.ttf "fi" --features=liga=1`. Запустите ту же команду с `--features=liga=0`. Если при включённой функции вывод меняется на имя глифа лигатуры, правило работает в этом файле и в этом shaping-сценарии. Если выход одинаков, вернитесь к GSUB, именам компонентов и настройке команды, а не к контуру.
HarfBuzz проверяет свою реализацию shaping, а не поведение каждой настольной программы. Приложение может использовать другой движок, ограничивать функции своим интерфейсом, кешировать старый файл или применять свой список исключений. Поэтому терминальный результат локализует проблему, но не заменяет проверку в программе, для которой готовится шрифт.
Сравните включённый и выключенный режим в целевой программе
Установите финальный файл под новым временным именем либо откройте его без установки, если программа это поддерживает. Создайте небольшой образец с тестовыми строками и примените тот же стиль и размер в двух состояниях: стандартные лигатуры включены и выключены. Сравнивайте именно формы и границы замен, а не только визуальное впечатление. При корректной подстановке изменяется последовательность глифов; интервалы и видимый силуэт также могут измениться, но сама подстановка не должна незаметно менять соседние буквы.
Если `hb-shape` показывает лигатуру, а целевая программа — нет, проверьте загрузку файла. Временно удалите или переименуйте ранее установленную копию, перезапустите приложение и подтвердите полное имя и версию открытого шрифта. Это отделяет проблему экспорта от кеша или случайно выбранной гарнитуры. Не делайте вывод по одному текстовому полю: некоторые приложения имеют собственный переключатель OpenType-функций или отключают их для отдельных режимов.
Как быстро определить этап сбоя
- Целевого глифа нет в исходнике — создайте его или исправьте мастер, затем пересоберите.
- Глиф есть, но rule отсутствует в исходном проекте — добавьте или включите feature и проверьте имена компонентов.
- Правило есть в проекте, но нет в GSUB — проверьте, какие файлы и опции реально поданы компилятору.
- В GSUB правило есть, но `hb-shape` не заменяет последовательность — проверьте tag, входные glyph IDs, условия и настройку теста.
- HarfBuzz заменяет глифы, а приложение нет — проверьте включение функции, фактически загруженный файл и кэш приложения.
Меняйте одну причину за раз. Если одновременно переименовать глиф, переписать правило и включить новую опцию экспорта, успешный результат не покажет, что именно устранило проблему. Зафиксируйте имя исходного файла, команду экспорта и контрольную строку для каждой проверенной сборки.
Частые ошибки
- Проверять только наличие глифа. Без связанного правила он не будет автоматически подставляться в обычном тексте.
- Искать один тег, когда лигатура размечена другой функцией. Сначала уточните, стандартная это замена или декоративная.
- Собирать старый файл проекта или копию feature-кода. Сверьте путь к исходнику и время создания результата.
- Считать успешный экспорт доказательством успешного набора. Файл может быть технически валидным, хотя нужного lookup в нём нет.
- Оценивать подстановку только по скриншоту. В тесте shaping сравните фактические имена сформированных глифов.
- Удалять правило, если приложение его не показывает. Сначала отделите экспорт от настроек программы и её кеша.
Контрольный список перед передачей шрифта
Перед передачей сохраните чистую копию исходника, feature-код и журнал экспорта. В короткой карточке сборки зафиксируйте версию проекта, имя итогового файла, тег проверенной функции, контрольную строку и ожидаемые выходные глифы. Это не бюрократия: при следующей правке можно повторить тот же тест и понять, исчезло ли правило из-за изменения проекта, команды сборки или другого редактора.
- Целевой глиф присутствует в том проекте, который действительно собран.
- Правило ссылается на существующие имена компонентов и входит в сборочную конфигурацию.
- Журнал компиляции не содержит необъяснённых предупреждений по feature-файлу.
- В собранном файле есть нужная запись GSUB и соответствующий lookup.
- Тест shaping с включённой функцией даёт замену, а с выключенной — исходную последовательность.
- Целевое приложение показывает ту же версию шрифта после перезапуска.
Вопросы и ответы
Если глиф лигатуры есть в файле, зачем нужна GSUB?
Глиф — это форма, а GSUB — правило, которое указывает текстовому движку, когда заменить последовательность других глифов этой формой. В большинстве случаев обычный текст не содержит отдельного Unicode-символа «fi-лигатура», поэтому одной записи в cmap недостаточно.
Должна ли стандартная лигатура работать всегда?
Нет: результат зависит от того, включена ли соответствующая OpenType-функция и поддерживает ли её приложение в данном сценарии. Проверяйте свой целевой редактор и сравнивайте его с тестом shaping, где состояние функции задано явно.
Можно ли проверить GSUB без покупки редактора шрифтов?
Да. Инструменты семейства FontTools умеют инспектировать структуры OpenType, а HarfBuzz предоставляет `hb-shape` для просмотра результата формирования текста. Команду и способ просмотра подбирайте под установленную версию инструментов и храните вместе с заметкой о тесте.
Почему правило работает в редакторе проекта, но не после экспорта?
Превью редактора может применять незавершённые данные проекта напрямую. При экспорте код функции должен быть включён и корректно скомпилирован в итоговую таблицу GSUB. Проверьте журнал, входные пути сборки и сам экспортированный файл.
Если `hb-shape` показывает лигатуру, проблема решена?
Это подтверждает работу правила в конкретном файле и конфигурации HarfBuzz. Целевая программа может использовать другой движок, настройки функций или кешированный файл, поэтому контрольный набор всё равно нужно открыть в конечном приложении.
Нужно ли делать fi- и ffi-лигатуры для рукописного шрифта?
Это редакционное решение, а не обязательное требование формата. Добавляйте только те соединения, которые улучшают нужный почерк и не затрудняют распознавание слова; затем проверяйте короткие сочетания и реальные строки целевой аудитории.
Заключение
Диагностируйте лигатуру по цепочке: целевой глиф, исходное правило, конфигурация сборки, GSUB в финальном файле, затем shaping и целевое приложение. Так вы быстро отделите ошибку исходника от сбоя экспорта и от поведения программы. Если вы делаете собственный набор букв, начните с шаблона Fontgenerator, а после сборки проверяйте не только отдельные знаки, но и нужные сочетания в готовом шрифте.
Источники
Спецификация Microsoft OpenType: таблица GSUB и лигатурные подстановки — https://learn.microsoft.com/en-us/typography/opentype/spec/gsub
Документация FontTools feaLib — https://fonttools.readthedocs.io/en/stable/feaLib/index.html
Руководство HarfBuzz: утилита hb-shape — https://harfbuzz.github.io/harfbuzz-hb-shape.html
HarfBuzz: применение OpenType-функций — https://harfbuzz.github.io/shaping-opentype-features.html