diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/theme-studio/capture-default-faces.py | 22 | ||||
| -rw-r--r-- | scripts/theme-studio/default_faces.py | 79 | ||||
| -rw-r--r-- | scripts/theme-studio/face_specs.py | 54 |
3 files changed, 79 insertions, 76 deletions
diff --git a/scripts/theme-studio/capture-default-faces.py b/scripts/theme-studio/capture-default-faces.py index 3de09941b..8c8fd6679 100644 --- a/scripts/theme-studio/capture-default-faces.py +++ b/scripts/theme-studio/capture-default-faces.py @@ -17,6 +17,8 @@ import re import subprocess import tempfile +from face_specs import FACE_ATTRS + HERE = pathlib.Path(__file__).resolve().parent ROOT = HERE.parents[1] OUT = HERE / "emacs-default-faces.json" @@ -85,21 +87,11 @@ BUILTIN_FEATURES = [ "shr", ] -ATTRS = { - ":foreground": "foreground", - ":background": "background", - ":weight": "weight", - ":slant": "slant", - ":underline": "underline", - ":strike-through": "strike", - ":overline": "overline", - ":box": "box", - ":height": "height", - ":inherit": "inherit", - ":inverse-video": "inverseVideo", - ":extend": "extend", - ":distant-foreground": "distantForeground", -} +# Emacs face :attribute keyword -> snapshot field name, derived from the shared +# face-attribute spec so the capture, the seed extraction, and STYLE_DEFAULTS all +# stay in step. Attributes the snapshot doesn't carry (e.g. family) have no +# capture keyword and are skipped. +ATTRS = {a["capture"]: a["snapshot"] for a in FACE_ATTRS if a["capture"]} def x11_colors() -> dict[str, str]: diff --git a/scripts/theme-studio/default_faces.py b/scripts/theme-studio/default_faces.py index 9a0dba5e3..70ceb39e6 100644 --- a/scripts/theme-studio/default_faces.py +++ b/scripts/theme-studio/default_faces.py @@ -6,6 +6,8 @@ import json import pathlib from typing import Any +from face_specs import FACE_ATTRS + class DefaultFaces: def __init__(self, data: dict[str, Any] | None): @@ -35,49 +37,46 @@ class DefaultFaces: data = self.face(face, effective) return data.get(attr + "Hex") or data.get(attr) + def _seed_value(self, attr: dict[str, Any], data: dict[str, Any]) -> Any: + """Turn a snapshot field into a model value per the attribute's kind. + + The snapshot speaks a different dialect than the model: colors carry a + Hex variant with a name fallback; weight/slant are value-narrowed to the + legacy bold/italic until the snapshot refresh; underline/strike/overline + are truthy flags that become objects; inverse/extend coerce Emacs's "t". + Returns None (skip) when the attribute is unset or not seedable. + """ + kind, snap = attr["kind"], attr["snapshot"] + if not kind: + return None + if kind == "color": + return data.get(snap + "Hex") or data.get(snap) + if kind == "weight-bold": + return "bold" if data.get(snap) == "bold" else None + if kind == "slant-italic": + return "italic" if data.get(snap) == "italic" else None + if kind == "underline-obj": + return {"style": "line", "color": None} if data.get(snap) else None + if kind == "color-obj": + return {"color": None} if data.get(snap) else None + if kind == "bool": + return True if data.get(snap) in (True, "t") else None + if kind == "scalar": + return data.get(snap) or None + if kind == "height": + h = data.get(snap) + return h if (h and h != 1) else None + if kind == "box": + return self.box_to_theme(data.get(snap)) + return None + def seed(self, face: str, effective: bool = False) -> dict[str, Any]: data = self.face(face, effective) out: dict[str, Any] = {} - fg = data.get("foregroundHex") or data.get("foreground") - bg = data.get("backgroundHex") or data.get("background") - if fg: - out["fg"] = fg - if bg: - out["bg"] = bg - # Representation-only cutover: the snapshot's bold/italic become the new - # weight/slant shape, and underline/strike become objects. The same - # narrowing as before (only "bold"/"italic" survive; richer weights and - # underline colors wait for the snapshot refresh), so the emitted theme - # is byte-identical. - if data.get("weight") == "bold": - out["weight"] = "bold" - if data.get("slant") == "italic": - out["slant"] = "italic" - if data.get("underline"): - out["underline"] = {"style": "line", "color": None} - if data.get("strike"): - out["strike"] = {"color": None} - # Additive attrs the snapshot already carries for the faces that set them: - # distant-foreground (e.g. lazy-highlight), inverse-video, and extend. - # overline is captured too once the snapshot is refreshed; stock faces - # almost never set it, so it is usually absent. These seed faces with the - # attrs Emacs gives them by default, so the studio opens closer to reality. - df = data.get("distantForegroundHex") or data.get("distantForeground") - if df: - out["distant-fg"] = df - if data.get("overline"): - out["overline"] = {"color": None} - if data.get("inverseVideo") in (True, "t"): - out["inverse"] = True - if data.get("extend") in (True, "t"): - out["extend"] = True - if data.get("inherit"): - out["inherit"] = data.get("inherit") - if data.get("height") and data.get("height") != 1: - out["height"] = data.get("height") - box = self.box_to_theme(data.get("box")) - if box: - out["box"] = box + for attr in FACE_ATTRS: + v = self._seed_value(attr, data) + if v: + out[attr["model"]] = v return out def box_to_theme(self, box: Any) -> dict[str, Any] | None: diff --git a/scripts/theme-studio/face_specs.py b/scripts/theme-studio/face_specs.py index 5fa038068..1b3e150d6 100644 --- a/scripts/theme-studio/face_specs.py +++ b/scripts/theme-studio/face_specs.py @@ -5,27 +5,39 @@ from __future__ import annotations from typing import Any -# The full per-face attribute model, in its final shape. weight and slant replace -# the legacy bold/italic booleans (weight is one of light/normal/medium/semibold/ -# bold/heavy; slant is normal/italic/oblique). underline and strike are objects: -# underline is {style: line|wave, color} and strike is {color}; null means unset. -# inherit and height are no longer package-only — every tier can set them. -STYLE_DEFAULTS: dict[str, Any] = { - "fg": None, - "bg": None, - "distant-fg": None, - "family": None, - "weight": None, - "slant": None, - "underline": None, - "strike": None, - "overline": None, - "box": None, - "inverse": False, - "extend": False, - "inherit": None, - "height": None, -} +# The full per-face attribute model, in its final shape, as one spec list that is +# the single source for: STYLE_DEFAULTS (model key -> default), the capture probe +# map in capture-default-faces.py (emacs :attr -> snapshot field), and the +# snapshot extraction in default_faces.seed (snapshot field + kind -> model value). +# Per row: +# model : theme-model key +# default : value when unset +# capture : the Emacs face :attribute keyword the capture probes (None = not +# captured, e.g. family, which the model carries but the snapshot omits) +# snapshot : the field name the capture writes (and seed reads) +# kind : how seed turns the snapshot field into a model value (None = not seeded) +# weight/slant replaced the legacy bold/italic booleans; underline/strike/overline +# are objects ({style: line|wave, color} / {color}); inherit and height apply to +# every tier. Keep this in step with app-core.js FACE_ATTRS (separate runtime). +FACE_ATTRS: list[dict[str, Any]] = [ + {"model": "fg", "default": None, "capture": ":foreground", "snapshot": "foreground", "kind": "color"}, + {"model": "bg", "default": None, "capture": ":background", "snapshot": "background", "kind": "color"}, + {"model": "distant-fg", "default": None, "capture": ":distant-foreground", "snapshot": "distantForeground", "kind": "color"}, + {"model": "family", "default": None, "capture": None, "snapshot": None, "kind": None}, + {"model": "weight", "default": None, "capture": ":weight", "snapshot": "weight", "kind": "weight-bold"}, + {"model": "slant", "default": None, "capture": ":slant", "snapshot": "slant", "kind": "slant-italic"}, + {"model": "underline", "default": None, "capture": ":underline", "snapshot": "underline", "kind": "underline-obj"}, + {"model": "strike", "default": None, "capture": ":strike-through", "snapshot": "strike", "kind": "color-obj"}, + {"model": "overline", "default": None, "capture": ":overline", "snapshot": "overline", "kind": "color-obj"}, + {"model": "box", "default": None, "capture": ":box", "snapshot": "box", "kind": "box"}, + {"model": "inverse", "default": False, "capture": ":inverse-video", "snapshot": "inverseVideo", "kind": "bool"}, + {"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 key -> default, derived from the spec above (order preserved). +STYLE_DEFAULTS: dict[str, Any] = {a["model"]: a["default"] for a in FACE_ATTRS} # Kept as a distinct name for callers, but inherit/height are no longer # package-only, so the package defaults are now the same full set. |
