1use arborium::theme::builtin;
2use arborium_theme::highlights::HIGHLIGHTS;
3use arborium_theme::theme::{Style, Theme};
4use axum::http::header;
5use axum::response::IntoResponse;
6use std::collections::{HashMap, HashSet};
7use std::fmt::Write;
8
9fn theme_to_css_vars(theme: &Theme) -> String {
12 let mut css = String::new();
13
14 let mut name_to_idx: HashMap<&str, usize> = HashMap::new();
16 for (i, def) in HIGHLIGHTS.iter().enumerate() {
17 name_to_idx.insert(def.name, i);
18 for alias in def.aliases {
19 name_to_idx.entry(alias).or_insert(i);
20 }
21 }
22
23 let mut tag_to_style: HashMap<&str, &Style> = HashMap::new();
25 for (i, def) in HIGHLIGHTS.iter().enumerate() {
26 if !def.tag.is_empty() && !theme.styles[i].is_empty() {
27 tag_to_style.insert(def.tag, &theme.styles[i]);
28 }
29 }
30
31 if let Some(bg) = &theme.background {
32 writeln!(css, " --theme-bg: {};", bg.to_hex()).unwrap();
33 }
34 if let Some(fg) = &theme.foreground {
35 writeln!(css, " --theme-fg: {};", fg.to_hex()).unwrap();
36 }
37
38 let mut emitted: HashSet<&str> = HashSet::new();
39 for (i, def) in HIGHLIGHTS.iter().enumerate() {
40 if def.tag.is_empty() || emitted.contains(def.tag) {
41 continue;
42 }
43
44 let style = if !theme.styles[i].is_empty() {
46 &theme.styles[i]
47 } else if let Some(s) = def.aliases.iter().find_map(|a| {
48 name_to_idx.get(a).and_then(|&j| {
49 let s = &theme.styles[j];
50 if !s.is_empty() { Some(s) } else { None }
51 })
52 }) {
53 s
54 } else if !def.parent_tag.is_empty() {
55 match tag_to_style.get(def.parent_tag) {
56 Some(s) => s,
57 None => continue,
58 }
59 } else {
60 continue;
61 };
62
63 if style.is_empty() {
64 continue;
65 }
66
67 emitted.insert(def.tag);
68
69 if let Some(fg) = &style.fg {
70 writeln!(css, " --hl-{}: {};", def.tag, fg.to_hex()).unwrap();
71 }
72 }
73
74 css
75}
76
77fn element_rules() -> String {
80 let mut css = String::new();
81 let mut emitted: HashSet<&str> = HashSet::new();
82
83 for def in HIGHLIGHTS.iter() {
84 if def.tag.is_empty() || emitted.contains(def.tag) {
85 continue;
86 }
87 emitted.insert(def.tag);
88 writeln!(css, "a-{} {{ color: var(--hl-{}); }}", def.tag, def.tag).unwrap();
89 }
90
91 css
92}
93
94pub async fn api_arborium_theme_css() -> impl IntoResponse {
100 let light = builtin::github_light();
101 let dark = builtin::catppuccin_mocha();
102
103 let mut css = String::new();
104
105 writeln!(css, ":root {{").unwrap();
107 css.push_str(&theme_to_css_vars(&light));
108 writeln!(css, "}}").unwrap();
109
110 writeln!(css, "@media (prefers-color-scheme: dark) {{").unwrap();
112 writeln!(css, " :root {{").unwrap();
113 for line in theme_to_css_vars(&dark).lines() {
114 writeln!(css, " {line}").unwrap();
115 }
116 writeln!(css, " }}").unwrap();
117 writeln!(css, "}}").unwrap();
118
119 css.push('\n');
121 css.push_str(&element_rules());
122
123 ([(header::CONTENT_TYPE, "text/css; charset=utf-8")], css)
124}