From 630cfddc7060c7019815f8e82f87fb629aefebfa Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 2 Jul 2026 23:01:54 -0400 Subject: 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. --- scripts/theme-studio/build-theme.el | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'scripts/theme-studio/build-theme.el') 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))) -- cgit v1.2.3