One font merging

After localization, the most painful part is fonts. I want one shared font for the entire UI—no manual switches and no “tofu” squares. Below is my working, fast, and legally clean pipeline.

1) The base: Google Fonts

I pick typefaces on [fonts.google.com]—licenses are transparent, language coverage is visible, and style consistency is easy to judge. I localize into:
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
Note: you almost always need separate families for th, vi, hi (Devanagari), zh, ja, ko.

2) Building a “combine harvester” from multiple TTF/OTF files

The goal is one file with all glyphs. From a dozen files (by script), I assemble a single TTF like this:

  1. Open all families in tabs in FontCreator.

  2. In each tab, select all glyphs → Edit → Paste Special… into the “main” font.

  3. Enable Glyphs with the same name and Add non-matching — missing symbols get added and existing ones aren’t overwritten.

  1. If I need to replace certain glyphs (usually digits), I do a regular paste into the target glyph I want to override.

Result: one TTF “for everything.” It works in Defold, but it tends to be heavy. Next step—optimization.

3) A whitelist of characters from the localizations

I want exactly the set of characters that actually appears in the game’s text. I gather it with a Google Apps Script (in the localization spreadsheet):

/**
 * Prints a single line: PREFIX + all unique characters from all sheets (no separators).
 */
function printCharsInlineAllSheets() {
  const PREFIX = "-=.1234567890";               // your “must-have” symbols: HUD, counters, etc.
  const INCLUDE_HIDDEN_SHEETS = false;          // true — include hidden sheets
  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) {                  // iterate over Unicode characters
        if (IGNORED_CHARS.has(ch)) continue;
        if (!seen.has(ch)) { seen.add(ch); extra.push(ch); }
      }
    }));
  });

  // stable locale-aware sort — convenient for reproducibility
  const collator = new Intl.Collator(undefined, { usage: 'sort', sensitivity: 'variant' });
  extra.sort(collator.compare);

  const line = PREFIX + extra.join('');
  Logger.log(line);                             // open Logs and copy the line
}

I run it, copy the line from the logs—that’s my whitelist.

4) Hooking it up in Defold

In the .font resource I point to the combined TTF/OTF, set the size, and paste the string from the script into Characters. I get a neat atlas with only the glyphs I need—no extra weight.

9 Likes