aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/face_specs.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-02 23:01:54 -0400
committerCraig Jennings <c@cjennings.net>2026-07-02 23:01:54 -0400
commit630cfddc7060c7019815f8e82f87fb629aefebfa (patch)
treeb645a1c56616094eca74efdffe499a51250e1bb1 /scripts/theme-studio/face_specs.py
parentf9b379ed52e4b5947bb2a2fc8d2c54c872e39791 (diff)
downloaddotemacs-630cfddc7060c7019815f8e82f87fb629aefebfa.tar.gz
dotemacs-630cfddc7060c7019815f8e82f87fb629aefebfa.zip
feat(theme-studio): explicit absolute-vs-relative face height kind
JSON collapses 2.0 to 2 on save, so a height's number type can't say whether it's a fixed 1/10pt value or a relative multiplier. The face model now carries an explicit heightMode field (abs/rel) through seed, save/load, and export. build-theme.el coerces :height from the kind: abs exports an integer, rel a float, so a relative 2.0 renders as 2.0, never 2. Faces saved before the field existed infer the kind once on load (JS: integer to abs, fractional to rel; Python keeps the authored type, so a float 2.0 seed stays relative) and persist it on the next save. The mode-line seed carries abs explicitly, and WIP.json's eight seeded heights are stamped with their kinds. Regenerating the theme from the stamped WIP.json produces an identical WIP-theme.el, so the round-trip holds.
Diffstat (limited to 'scripts/theme-studio/face_specs.py')
-rw-r--r--scripts/theme-studio/face_specs.py13
1 files changed, 13 insertions, 0 deletions
diff --git a/scripts/theme-studio/face_specs.py b/scripts/theme-studio/face_specs.py
index 1b3e150d..12eff16b 100644
--- a/scripts/theme-studio/face_specs.py
+++ b/scripts/theme-studio/face_specs.py
@@ -34,6 +34,7 @@ FACE_ATTRS: list[dict[str, Any]] = [
{"model": "extend", "default": False, "capture": ":extend", "snapshot": "extend", "kind": "bool"},
{"model": "inherit", "default": None, "capture": ":inherit", "snapshot": "inherit", "kind": "scalar"},
{"model": "height", "default": None, "capture": ":height", "snapshot": "height", "kind": "height"},
+ {"model": "heightMode", "default": None, "capture": None, "snapshot": None, "kind": None},
]
# model key -> default, derived from the spec above (order preserved).
@@ -74,6 +75,18 @@ def migrate_legacy(spec: dict[str, Any]) -> dict[str, Any]:
out["strike"] = {"color": None}
elif strike is False:
out["strike"] = None
+ # Height-kind migration: a spec saved before heightMode existed carries
+ # only the number, so infer the kind once -- int -> absolute 1/10pt,
+ # float -> relative multiplier. Unlike JS (where JSON collapses 2.0 to 2
+ # and only integrality is left to test), Python keeps the authored number
+ # type, so a float 2.0 seed correctly infers relative. An explicit
+ # heightMode always wins. The identity 1 and non-numbers infer nothing.
+ height = out.get("height")
+ if (out.get("heightMode") is None
+ and isinstance(height, (int, float))
+ and not isinstance(height, bool)
+ and height != 1):
+ out["heightMode"] = "abs" if isinstance(height, int) else "rel"
return out