1use alloc::rc::Rc;
35use alloc::vec::Vec;
36
37use gfx::pathdata::PData;
38use gfx::xform::Matrix;
39use gfx::{Surface, FMT_PARGB};
40use rt::fx::{ceil_i, div255};
41
42use crate::cmd::Gfx;
43use crate::consts::*;
44use crate::theme::color_mix;
45use crate::vicon::layer_head;
46use crate::Ui;
47
48pub const SYM_INK: u8 = 0; pub const SYM_BODY: u8 = 1; pub const SYM_SHADE: u8 = 2; pub const SYM_SCREEN: u8 = 3; pub const SYM_TINT: u8 = 4; pub const SYM_PORT: u8 = 5; pub const SYM_OK: u8 = 6; pub const SYM_WARN: u8 = 7;
56pub const SYM_CRIT: u8 = 8;
57pub const SYM_HI: u8 = 9; pub const SYM_ROLES: usize = 10;
59pub const SYM_CACHE_MAX_W: i32 = 256;
61
62const CACHE_N: usize = 256;
63const STROKE_DEFAULT: f32 = 1.5;
64const STROKE_MIN_PX: f32 = 0.8;
65
66struct Layer {
67 pd: PData,
68 role: u8,
69 fill: u8,
71 width: f32,
73}
74
75pub struct Symbol {
77 layers: Vec<Layer>,
78 pub vb: [f32; 2],
80}
81
82impl Symbol {
83 pub fn parse(art: &str, vb: [f32; 2]) -> Symbol {
85 let mut layers = Vec::new();
86 for layer in art.split('|') {
87 let layer = layer.trim();
88 if layer.is_empty() {
89 continue;
90 }
91 let (h, d) = layer_head(layer);
92 let mut pd = PData::new();
93 if pd.parse(d.as_bytes()).0 != 0 {
94 continue;
95 }
96 let role = h.role.unwrap_or(if h.accent { SYM_TINT } else { SYM_INK });
97 layers.push(Layer { pd, role: role.min(SYM_ROLES as u8 - 1), fill: h.fill, width: h.stroke.unwrap_or(STROKE_DEFAULT) });
98 }
99 let vb = [if vb[0] > 0.0 { vb[0] } else { 1.0 }, if vb[1] > 0.0 { vb[1] } else { 1.0 }];
100 Symbol { layers, vb }
101 }
102
103 pub fn layers(&self) -> usize {
105 self.layers.len()
106 }
107
108 pub fn height(&self, w: i32) -> i32 {
110 ceil_i(w as f32 * self.vb[1] / self.vb[0]).max(0)
111 }
112
113 pub fn paint(&self, g: &mut Gfx, x: f32, y: f32, scale: f32, pal: &SymPalette) {
116 if scale <= 0.0 || !scale.is_finite() {
117 return;
118 }
119 let s = scale as f64;
120 let m = Matrix { a: s, b: 0.0, c: x as f64 + g.dc.ox as f64, d: 0.0, e: s, f: y as f64 + g.dc.oy as f64 };
121 let color = g.dc.get_color();
122 for l in &self.layers {
123 let path = &mut g.text.path;
124 path.reset();
125 path.set_origin(0, 0);
126 if l.pd.emit(path, Some(&m)) != 0 {
127 continue;
128 }
129 g.dc.set_color(pal.c[l.role as usize]);
130 if l.fill != 0 {
131 path.fill(&mut g.text.ras, g.dc, g.sf, if l.fill == 2 { gfx::RULE_EVENODD } else { gfx::RULE_NONZERO });
132 } else {
133 let w = ((l.width * scale).max(STROKE_MIN_PX) * 256.0) as i32;
134 path.stroke(&mut g.text.ras, g.dc, g.sf, w, gfx::JOIN_ROUND | gfx::CAP_ROUND << 4);
135 }
136 }
137 g.dc.set_color(color);
138 }
139
140 pub fn raster(&self, g: &mut Gfx, w: i32, pal: &SymPalette) -> Surface {
142 let mut sf = Surface::new(w.max(0), self.height(w), FMT_PARGB);
143 if sf.px.is_empty() {
144 return sf;
145 }
146 g.dc.begin(&sf);
148 g.dc.set_opacity(255);
149 {
150 let mut tg = Gfx { dc: &mut *g.dc, sf: &mut sf, text: &mut *g.text };
151 self.paint(&mut tg, 0.0, 0.0, w as f32 / self.vb[0], pal);
152 }
153 g.dc.end();
154 sf
155 }
156}
157
158fn over(fg: u32, bg: u32) -> u32 {
160 let a = fg >> 24;
161 color_mix(bg | 0xFF00_0000, fg | 0xFF00_0000, ((a * 256 + 127) / 255) as i32)
162}
163
164#[derive(Clone, Copy, PartialEq, Eq, Debug)]
166pub struct SymPalette {
167 pub c: [u32; SYM_ROLES],
168}
169
170impl SymPalette {
171 pub fn from_theme(ui: &Ui, tint: u32) -> SymPalette {
174 if ui.high_contrast() {
175 return SymPalette::contrast(ui);
176 }
177 let dark = ui.theme_dark();
178 let bg = over(ui.color(T_BG), if dark { 0xFF00_0000 } else { 0xFFFF_FFFF });
179 let text = over(ui.color(T_TEXT), bg);
180 if dark { SymPalette::dark(text, bg, tint) } else { SymPalette::light(text, bg, tint) }
181 }
182
183 pub fn light(text: u32, bg: u32, tint: u32) -> SymPalette {
186 let ink = color_mix(text, bg, 40);
187 let body = color_mix(0xFFFF_FFFF, bg, 96);
188 let port = color_mix(ink, body, 40);
189 SymPalette {
190 c: [
191 ink,
192 body,
193 color_mix(body, ink, 36),
194 color_mix(body, 0xFF3D_6A96, 64),
195 tint | 0xFF00_0000,
196 port,
197 0xFF3C_9A5F,
198 0xFFD3_9A2C,
199 0xFFC8_473D,
200 0xFFFF_FFFF,
201 ],
202 }
203 }
204
205 pub fn dark(text: u32, bg: u32, tint: u32) -> SymPalette {
208 let ink = color_mix(text, bg, 64);
209 let body = color_mix(bg, 0xFFFF_FFFF, 36);
210 SymPalette {
211 c: [
212 ink,
213 body,
214 color_mix(body, bg, 128),
215 color_mix(bg, 0xFF3D_6A96, 90),
216 color_mix(tint | 0xFF00_0000, 0xFFC8_CDD3, 56),
217 color_mix(bg, 0xFF00_0000, 64),
218 0xFF52_B072,
219 0xFFE0_A83E,
220 0xFFE0_645A,
221 color_mix(body, ink, 128),
222 ],
223 }
224 }
225
226 pub fn contrast(ui: &Ui) -> SymPalette {
229 let bg = over(ui.color(T_BG), 0xFF00_0000);
230 let text = over(ui.color(T_TEXT), bg);
231 let tok = |t: u32| over(ui.color(t), bg);
232 SymPalette { c: [text, bg, bg, bg, tok(T_ACCENT), text, tok(T_OK), tok(T_WARN), tok(T_CRIT), bg] }
233 }
234
235 pub fn powered_off(&self) -> SymPalette {
237 let mut p = *self;
238 let off = color_mix(p.c[SYM_SHADE as usize], p.c[SYM_PORT as usize], 96);
239 p.c[SYM_OK as usize] = off;
240 p.c[SYM_WARN as usize] = off;
241 p.c[SYM_CRIT as usize] = off;
242 p.c[SYM_SCREEN as usize] = p.c[SYM_SHADE as usize];
243 p
244 }
245
246 pub fn hash(&self) -> u32 {
248 let mut h: u32 = 0x811C_9DC5;
249 for c in self.c {
250 for b in c.to_le_bytes() {
251 h = (h ^ b as u32).wrapping_mul(0x0100_0193);
252 }
253 }
254 h
255 }
256}
257
258struct Entry {
259 id: u32,
260 w: i32,
261 hash: u32,
262 pal: [u32; SYM_ROLES],
263 sf: Rc<Surface>,
264 used: u32,
265}
266
267#[derive(Default)]
270pub struct SymbolCache {
271 list: Vec<Entry>,
272 tick: u32,
273}
274
275impl SymbolCache {
276 pub fn new() -> SymbolCache {
277 SymbolCache::default()
278 }
279
280 pub fn len(&self) -> usize {
282 self.list.len()
283 }
284
285 pub fn is_empty(&self) -> bool {
286 self.list.is_empty()
287 }
288
289 pub fn clear(&mut self) {
291 self.list.clear();
292 }
293
294 pub fn get(&mut self, g: &mut Gfx, id: u32, sym: &Symbol, w: i32, pal: &SymPalette) -> Option<Rc<Surface>> {
297 if w <= 0 || w > SYM_CACHE_MAX_W {
298 return None;
299 }
300 let hash = pal.hash();
301 self.tick = self.tick.wrapping_add(1);
302 let tick = self.tick;
303 if let Some(e) = self.list.iter_mut().find(|e| e.id == id && e.w == w && e.hash == hash && e.pal == pal.c) {
304 e.used = tick;
305 return Some(e.sf.clone());
306 }
307 let sf = Rc::new(sym.raster(g, w, pal));
308 if self.list.len() >= CACHE_N {
309 let mut old = 0;
310 for (i, e) in self.list.iter().enumerate() {
311 if e.used < self.list[old].used {
312 old = i;
313 }
314 }
315 self.list.swap_remove(old);
316 }
317 self.list.push(Entry { id, w, hash, pal: pal.c, sf: sf.clone(), used: tick });
318 Some(sf)
319 }
320
321 #[allow(clippy::too_many_arguments)]
325 pub fn draw(&mut self, g: &mut Gfx, id: u32, sym: &Symbol, x: i32, y: i32, w: i32, pal: &SymPalette, opacity: u32) {
326 if w <= 0 || opacity == 0 {
327 return;
328 }
329 let op = opacity.min(255);
330 let sf = if w <= SYM_CACHE_MAX_W {
331 self.get(g, id, sym, w, pal)
332 } else if op == 255 {
333 sym.paint(g, x as f32, y as f32, w as f32 / sym.vb[0], pal);
334 None
335 } else {
336 Some(Rc::new(sym.raster(g, w, pal)))
337 };
338 if let Some(sf) = sf {
339 let op0 = g.dc.opac;
340 g.dc.set_opacity(div255(op * op0));
341 g.dc.blit(g.sf, &sf, x, y);
342 g.dc.set_opacity(op0);
343 }
344 }
345}