Panel fixes: tall card row_span, room tab styling, vacuum card image+value, AC airflow labels, AC fan speed sections (auto 3-btn + manual 5-step slider), light subtitle fallback, curtain pane dividers, dimmable light presets, LED panel brightness + reorder. New assets: 34 ui-icons (climate modes, cover, effects, battery, led-strip, switch states), 3 logos, 90 app surface PNGs, design system specs (components, ux-mechanics, tokens). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
3.1 KiB
Python
65 lines
3.1 KiB
Python
"""Freeze the bundled variable Inter font at the web UI's weights/optical sizes."""
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
from fontTools.ttLib import TTFont
|
|
from fontTools.varLib.instancer import instantiateVariableFont
|
|
from fontTools import subset
|
|
|
|
directory = Path(sys.argv[1])
|
|
font = TTFont(directory / "font.ttf")
|
|
axes = {axis.axisTag for axis in font["fvar"].axes} if "fvar" in font else set()
|
|
metrics = {}
|
|
for weight in (350, 400, 450, 500, 550, 600, 650, 700, 800):
|
|
for display in (False, True):
|
|
values = {"wght": weight} if "wght" in axes else {}
|
|
if "opsz" in axes:
|
|
values["opsz"] = 32 if display else 14
|
|
instance = instantiateVariableFont(font, values, inplace=False) if axes else font
|
|
instance.save(directory / f"font-{'display-' if display else ''}{weight}.ttf")
|
|
units = instance["head"].unitsPerEm
|
|
advances = instance["hmtx"].metrics
|
|
metrics[f"{'display-' if display else ''}{weight}"] = {
|
|
chr(codepoint): advances[glyph][0] / units
|
|
for codepoint, glyph in instance.getBestCmap().items()
|
|
}
|
|
def is_hangul(codepoint):
|
|
# Keep in sync with is_hangul() in src/renderer.rs, which routes these to font-kr.
|
|
return (0x1100 <= codepoint <= 0x11FF or 0x3130 <= codepoint <= 0x318F
|
|
or 0xA960 <= codepoint <= 0xA97F or 0xAC00 <= codepoint <= 0xD7A3
|
|
or 0xD7B0 <= codepoint <= 0xD7FF)
|
|
|
|
|
|
def fallback(path, keep, family, prefix):
|
|
font = TTFont(path)
|
|
options = subset.Options()
|
|
options.layout_features = []
|
|
options.name_IDs = ["*"]
|
|
subsetter = subset.Subsetter(options=options)
|
|
subsetter.populate(unicodes=[codepoint for codepoint in font.getBestCmap() if keep(codepoint)])
|
|
subsetter.subset(font)
|
|
advances_by_weight = {}
|
|
for weight in (400, 500):
|
|
instance = instantiateVariableFont(font, {"wght": weight}, inplace=False)
|
|
# Derived subsets use their own family name while retaining copyright/license tables.
|
|
style = "Regular" if weight == 400 else "Medium"
|
|
for name_id, value in [(1, family), (2, style), (4, f"{family} {style}"),
|
|
(6, f"{family.replace(' ', '')}-{style}"), (16, family), (17, style)]:
|
|
instance["name"].setName(value, name_id, 3, 1, 0x409)
|
|
instance.save(directory / f"{prefix}-{weight}.ttf")
|
|
units = instance["head"].unitsPerEm
|
|
advances = instance["hmtx"].metrics
|
|
advances_by_weight[weight] = {chr(cp): advances[glyph][0] / units
|
|
for cp, glyph in instance.getBestCmap().items()}
|
|
return advances_by_weight
|
|
|
|
|
|
cjk = fallback(sys.argv[2],
|
|
lambda cp: cp < 0xA000 or 0xF900 <= cp <= 0xFAFF or 0xFF00 <= cp <= 0xFFEF,
|
|
"Luma CJK", "font-cjk")
|
|
# Noto Sans SC carries no Hangul syllables; Korean comes from Noto Sans KR.
|
|
hangul = fallback(sys.argv[3], is_hangul, "Luma KR", "font-kr")
|
|
for weight in (400, 500):
|
|
metrics[f"fallback-{weight}"] = {**cjk[weight], **hangul[weight]}
|
|
(directory / "font-metrics.json").write_text(json.dumps(metrics, ensure_ascii=False), encoding="utf8")
|