#!/usr/bin/env python3
"""Port the daisyUI themes from waxx's app.css into the moire wallpaper.
tools/themes.py ../waxx/assets/css/app.css
writes moire/theme/contents/ui/themes.js. The file is committed, so
building needs neither waxx nor this script; re-run it when waxx's
palette changes.
Colours are kept in OKLCH, as the CSS defines them, so the wallpaper
can blend between themes by turning the hue rather than mixing sRGB,
where a crossfade between two saturated colours goes muddy halfway.
The sRGB hex beside each is for people reading the file.
"""
import math
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "moire/theme/contents/ui/themes.js"
# The wallpaper's four roles, and the daisyUI variable each is taken from.
ROLES = {
"base": "--color-base-100", # the ground
"deep": "--color-base-300", # the ground at the edges
"ink": "--color-accent", # the moire
"tone": "--color-primary", # the halftone
}
BLOCK = re.compile(r'@plugin\s+"[^"]*daisyui-theme"\s*\{(.*?)\}', re.S)
NAME = re.compile(r'name:\s*"([^"]+)"')
VAR = re.compile(r"(--color-[a-z0-9-]+):\s*oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)")
def oklch_to_srgb(l, c, h):
a = c * math.cos(math.radians(h))
b = c * math.sin(math.radians(h))
l_ = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3
m_ = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3
s_ = (l - 0.0894841775 * a - 1.2914855480 * b) ** 3
rgb = (
4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
-1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
-0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_,
)
def encode(x):
x = min(max(x, 0.0), 1.0)
return 12.92 * x if x <= 0.0031308 else 1.055 * x ** (1 / 2.4) - 0.055
return "#" + "".join(f"{round(encode(x) * 255):02x}" for x in rgb)
def parse(css):
themes = []
for block in BLOCK.findall(css):
name = NAME.search(block)
if not name:
continue
colours = {m[0]: (float(m[1]) / 100, float(m[2]), float(m[3])) for m in VAR.findall(block)}
missing = [v for v in ROLES.values() if v not in colours]
if missing:
sys.exit(f"theme {name[1]}: missing {', '.join(missing)}")
themes.append((name[1], {role: colours[var] for role, var in ROLES.items()}))
return themes
def render(themes, source):
out = [
f"// Generated by tools/themes.py from waxx's {source}; do not edit.",
"// OKLCH as [lightness 0-1, chroma, hue degrees]; hex is for reading.",
".pragma library",
"",
"var themes = {",
]
for name, roles in themes:
out.append(f' "{name}": {{')
for role, (l, c, h) in roles.items():
out.append(f" {role}: [{l:.4f}, {c:.3f}, {h:.3f}], // {oklch_to_srgb(l, c, h)}")
out.append(" },")
out += ["}", "", "var names = Object.keys(themes)", ""]
return "\n".join(out)
def main():
if len(sys.argv) != 2:
sys.exit(__doc__.strip().splitlines()[2].strip())
css = Path(sys.argv[1])
themes = parse(css.read_text())
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(render(themes, css.name))
print(f"wrote {OUT.relative_to(ROOT)}: {', '.join(n for n, _ in themes)}")
if __name__ == "__main__":
main()
neiam
/neiam-devices
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
3.3 KiB · text
History
f629a67