#!/usr/bin/env python3 """Write umtool/report-to-video/face-metrics.mjs: the advance of every character IBM Plex Sans maps, at the weights the rail / ledger / scroll / chart SVG text draws (400 and 700, wdth 100), measured from the VENDORED variable font. fit() and wrapPx() in render-cards.mjs measure a branded run with this table, so a column set in Plex Sans is truncated by what the run is, not by an average (see "Why a table, not an average" in the README). The font itself is read and never written: Plex carries a Reserved Font Name, so it ships unmodified. python3 umtool/report-to-video/fonts/gen-face-metrics.py # rewrite the module python3 umtool/report-to-video/fonts/gen-face-metrics.py --stdout # print it (the parity test) Needs fontTools (pip install fonttools). The module records the fontTools version it was generated with; the parity test skips under another one, because the instancer's rounding of variation deltas is fontTools's, not the font's. """ import hashlib import json import os import sys import fontTools from fontTools.ttLib import TTFont from fontTools.varLib import instancer HERE = os.path.dirname(os.path.abspath(__file__)) FONT = "IBMPlexSans[wdth,wght].ttf" OUT = os.path.join(os.path.dirname(HERE), "face-metrics.mjs") WEIGHTS = (400, 700) WDTH = 100 def runs(cps): """Contiguous [first, last] codepoint ranges, in order.""" out = [] for cp in cps: if out and cp == out[-1][1] + 1: out[-1][1] = cp else: out.append([cp, cp]) return out def wrap(values, indent=" ", width=100): lines, line = [], "" for v in values: s = f"{v}," if line and len(indent) + len(line) + 1 + len(s) > width: lines.append(indent + line) line = s else: line = f"{line} {s}" if line else s if line: lines.append(indent + line) return "\n".join(lines) def build(): path = os.path.join(HERE, FONT) raw = open(path, "rb").read() sha = hashlib.sha256(raw).hexdigest() base = TTFont(path) upm = base["head"].unitsPerEm cps = sorted(base.getBestCmap()) adv = {} for w in WEIGHTS: inst = instancer.instantiateVariableFont(TTFont(path), {"wght": w, "wdth": WDTH}) cmap = inst.getBestCmap() hmtx = inst["hmtx"].metrics adv[w] = [hmtx[cmap[cp]][0] for cp in cps] parts = [ "// GENERATED by umtool/report-to-video/fonts/gen-face-metrics.py -- do not edit; re-run it.", "//", f"// The advance, in font units, of every character {FONT} maps, at the", f"// weights the rail / ledger / scroll / chart SVG text draws (wght {' and '.join(map(str, WEIGHTS))},", f"// wdth {WDTH}): what fit() and wrapPx() measure a branded run by. svg-faces.mjs reads it.", "export const IBM_PLEX_SANS_METRICS = {", f" font: {json.dumps(FONT)},", f" sha256: {json.dumps(sha)},", f" fontTools: {json.dumps(fontTools.version)},", f" unitsPerEm: {upm},", f" wdth: {WDTH},", " // [first, last] codepoint ranges; the advances below follow them in order.", " ranges: [", wrap([f"[{a}, {b}]" for a, b in runs(cps)], indent=" "), " ],", " advances: {", ] for w in WEIGHTS: parts.append(f" {w}: [") parts.append(wrap(adv[w])) parts.append(" ],") parts += [" },", "};", ""] return "\n".join(parts) if __name__ == "__main__": text = build() if "--stdout" in sys.argv[1:]: sys.stdout.write(text) else: with open(OUT, "w") as f: f.write(text) print(f"wrote {os.path.relpath(OUT)}")