Оптимизация шрифтов в веб играх

После локализаций самая болезненная часть — шрифты. Хотелось бы один общий шрифт для всего UI, без ручных переключений и сюрпризов «квадратиков». Ниже — мой рабочий, быстрый и юридически чистый пайплайн.

1) База: Google Fonts

Ищу гарнитуры на fonts.google.com – лицензии прозрачные, видно покрытие языков и доступность стилей. Локализую на:
en, ru, uk, ar, zh, cs, da, nl, fi, fr, de, he, hi, hu, id, it, ja, ko, nb, pl, pt, ro, sk, es, sv, th, tr, vi
Важно: отдельные семейства почти всегда нужны для th, vi, hi (Devanagari), zh, ja, ko.

2) Сборка «комбайна» из нескольких TTF/OTF

Задача — получить один файл со всеми глифами. Из десятка файлов (по скриптам) собираю общий TTF так:

  1. Открываю все гарнитуры во вкладках FontCreator.
  2. В каждой вкладке выделяю все глифы и копирую, а потом→ Edit → Paste Special… в «основной» шрифт.
  3. Включаю опции: Glyphs with the same name и Add non-matching — так недостающие символы добавляются, а существующие не затираются.
  1. Если нужно точечно заменить (обычно цифры) — обычная вставка в выбранный глиф целевого шрифта.

Результат — один TTF «на всё». В Defold он работает, но выходит тяжёлым. Дальше — оптимизация.

3) Белый список символов из локализаций

Мне нужен ровно тот набор символов, который реально встречается в тексте игры. Его собираю скриптом в Google Apps Script (в файле локализаций):

/**
 * Печатает строку: ПРЕФИКС + все уникальные символы из всех листов книги (в одну линию).
 */
function printCharsInlineAllSheets() {
  const PREFIX = "-=.1234567890";               // свои «обязательные» символы: HUD, счётчики и т.п.
  const INCLUDE_HIDDEN_SHEETS = false;          // true — если нужно учитывать скрытые листы
  const IGNORED_CHARS = new Set(['\n', '\r', '\t']);

  const ss = SpreadsheetApp.getActive();
  const sheets = ss.getSheets().filter(sh => INCLUDE_HIDDEN_SHEETS || !sh.isSheetHidden());

  const seen = new Set(PREFIX.split(''));
  const extra = [];

  sheets.forEach(sh => {
    const values = sh.getDataRange().getDisplayValues();
    values.forEach(row => row.forEach(cell => {
      for (const ch of cell) {                  // итерируем по юникод-символам
        if (IGNORED_CHARS.has(ch)) continue;
        if (!seen.has(ch)) { seen.add(ch); extra.push(ch); }
      }
    }));
  });

  // стабильная сортировка по локали — удобно для воспроизводимости
  const collator = new Intl.Collator(undefined, { usage: 'sort', sensitivity: 'variant' });
  extra.sort(collator.compare);

  const line = PREFIX + extra.join('');
  Logger.log(line);                             // откройте Логи и скопируйте строку
}

Запускаю, копирую строку из логов — это и есть мой whitelist.

4) Подключение в Defold

В .font указываю собранный TTF/OTF, размер, и в поле Characters вставляю строку из скрипта. Получаю аккуратный атлас только с нужными глифами — без лишнего веса.