aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/build-theme.el
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/build-theme.el
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/build-theme.el')
-rw-r--r--scripts/theme-studio/build-theme.el19
1 files changed, 16 insertions, 3 deletions
diff --git a/scripts/theme-studio/build-theme.el b/scripts/theme-studio/build-theme.el
index e3721499..3e488d36 100644
--- a/scripts/theme-studio/build-theme.el
+++ b/scripts/theme-studio/build-theme.el
@@ -137,14 +137,27 @@ yields a (:color C :style S) plist. Tolerates the legacy boolean form."
(t t))))
(t t))))
+(defun build-theme/--height (obj)
+ "Coerce OBJ's height from its explicit kind field, or nil when unset.
+heightMode \"abs\" yields an integer :height (fixed 1/10pt); \"rel\" yields a
+float multiplier -- even when the number arrives integral, because JSON
+collapses 2.0 to 2 on save, so the number type can never carry the kind. A
+spec without a recognized heightMode keeps the number as parsed (legacy).
+The identity height 1/1.0 emits nothing under every kind."
+ (let ((height (build-theme/--obj-get obj 'height))
+ (kind (build-theme/--obj-get obj 'heightMode)))
+ (when (and (numberp height) (/= height 1.0))
+ (cond ((equal kind "abs") (truncate height))
+ ((equal kind "rel") (float height))
+ (t height)))))
+
(defun build-theme/--attrs (obj)
"Build a face-attribute plist from face-spec alist OBJ, in canonical order.
Reads the full attribute model -- inherit, family, fg/bg, distant-foreground,
weight, slant, height, underline, overline, strike-through, box, inverse-video,
extend -- and tolerates the older boolean bold/italic/underline/strike fields.
Only attributes that are set appear, so a blank face yields nil."
- (let* ((height (build-theme/--obj-get obj 'height))
- (family (build-theme/--obj-get obj 'family))
+ (let* ((family (build-theme/--obj-get obj 'family))
(pairs
(list
(cons :inherit (build-theme/--inherit-symbol (build-theme/--obj-get obj 'inherit)))
@@ -154,7 +167,7 @@ Only attributes that are set appear, so a blank face yields nil."
(cons :distant-foreground (build-theme/--obj-get obj 'distant-fg))
(cons :weight (build-theme/--weight obj))
(cons :slant (build-theme/--slant obj))
- (cons :height (and (numberp height) (/= height 1.0) height))
+ (cons :height (build-theme/--height obj))
(cons :underline (build-theme/--underline obj))
(cons :overline (build-theme/--line-attr (build-theme/--obj-get obj 'overline)))
(cons :strike-through (build-theme/--line-attr (build-theme/--obj-get obj 'strike)))