1mod palette_map;
4mod palettes;
5
6use std::borrow::Cow;
7use std::cmp::Ordering;
8use std::fmt;
9use std::str::FromStr;
10
11use rgb::RGB8;
12
13pub use self::palette_map::PaletteMap;
14
15pub type Color = RGB8;
17
18pub(super) const VDGREY: Color = Color {
19 r: 160,
20 g: 160,
21 b: 160,
22};
23pub(super) const DGREY: Color = Color {
24 r: 200,
25 g: 200,
26 b: 200,
27};
28
29const YELLOW_GRADIENT: (&str, &str) = ("#eeeeee", "#eeeeb0");
30const BLUE_GRADIENT: (&str, &str) = ("#eeeeee", "#e0e0ff");
31const GREEN_GRADIENT: (&str, &str) = ("#eef2ee", "#e0ffe0");
32const GRAY_GRADIENT: (&str, &str) = ("#f8f8f8", "#e8e8e8");
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
46pub enum BackgroundColor {
47 #[default]
49 Yellow,
50 Blue,
52 Green,
54 Grey,
56 Flat(Color),
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum Palette {
67 Basic(BasicPalette),
71 Multi(MultiPalette),
74}
75
76impl Palette {
77 pub const VARIANTS: &'static [&'static str] = &[
79 "aqua", "blue", "green", "hot", "io", "java", "js", "mem", "orange", "perl", "python",
80 "purple", "red", "rust", "wakeup", "yellow",
81 ];
82}
83
84impl Default for Palette {
85 fn default() -> Self {
86 Palette::Basic(BasicPalette::Hot)
87 }
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum BasicPalette {
98 Hot,
100 Mem,
102 Io,
104 Red,
106 Green,
108 Blue,
110 Aqua,
112 Yellow,
114 Purple,
116 Orange,
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub enum MultiPalette {
124 Java,
126 Js,
128 Perl,
130 Python,
132 Rust,
134 Wakeup,
136}
137
138impl FromStr for BackgroundColor {
139 type Err = String;
140
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 match s {
143 "yellow" => Ok(BackgroundColor::Yellow),
144 "blue" => Ok(BackgroundColor::Blue),
145 "green" => Ok(BackgroundColor::Green),
146 "grey" => Ok(BackgroundColor::Grey),
147 flat => parse_hex_color(flat)
148 .map(BackgroundColor::Flat)
149 .ok_or_else(|| format!("unknown background color: {}", flat)),
150 }
151 }
152}
153
154macro_rules! u8_from_hex_iter {
155 ($slice:expr) => {
156 (($slice.next()?.to_digit(16)? as u8) << 4) | ($slice.next()?.to_digit(16)? as u8)
157 };
158}
159
160pub fn parse_hex_color(s: &str) -> Option<Color> {
162 if !s.starts_with('#') || (s.len() != 7) {
163 None
164 } else {
165 let mut s = s[1..].chars();
166
167 let r = u8_from_hex_iter!(s);
168 let g = u8_from_hex_iter!(s);
169 let b = u8_from_hex_iter!(s);
170
171 Some(Color { r, g, b })
172 }
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct SearchColor(Color);
178
179impl FromStr for SearchColor {
180 type Err = String;
181
182 fn from_str(s: &str) -> Result<Self, Self::Err> {
183 parse_hex_color(s)
184 .map(SearchColor)
185 .ok_or_else(|| format!("unknown color: {}", s))
186 }
187}
188
189impl fmt::Display for SearchColor {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 write!(f, "rgb({},{},{})", self.0.r, self.0.g, self.0.b)
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum StrokeColor {
198 Color(Color),
200 None,
202}
203
204impl FromStr for StrokeColor {
205 type Err = String;
206
207 fn from_str(s: &str) -> Result<Self, Self::Err> {
208 if s == "none" {
209 return Ok(StrokeColor::None);
210 }
211 parse_hex_color(s)
212 .map(StrokeColor::Color)
213 .ok_or_else(|| format!("unknown color: {}", s))
214 }
215}
216
217impl FromStr for Palette {
218 type Err = String;
219
220 fn from_str(s: &str) -> Result<Self, Self::Err> {
221 match s {
222 "hot" => Ok(Palette::Basic(BasicPalette::Hot)),
223 "mem" => Ok(Palette::Basic(BasicPalette::Mem)),
224 "io" => Ok(Palette::Basic(BasicPalette::Io)),
225 "wakeup" => Ok(Palette::Multi(MultiPalette::Wakeup)),
226 "java" => Ok(Palette::Multi(MultiPalette::Java)),
227 "js" => Ok(Palette::Multi(MultiPalette::Js)),
228 "perl" => Ok(Palette::Multi(MultiPalette::Perl)),
229 "python" => Ok(Palette::Multi(MultiPalette::Python)),
230 "rust" => Ok(Palette::Multi(MultiPalette::Rust)),
231 "red" => Ok(Palette::Basic(BasicPalette::Red)),
232 "green" => Ok(Palette::Basic(BasicPalette::Green)),
233 "blue" => Ok(Palette::Basic(BasicPalette::Blue)),
234 "aqua" => Ok(Palette::Basic(BasicPalette::Aqua)),
235 "yellow" => Ok(Palette::Basic(BasicPalette::Yellow)),
236 "purple" => Ok(Palette::Basic(BasicPalette::Purple)),
237 "orange" => Ok(Palette::Basic(BasicPalette::Orange)),
238 unknown => Err(format!("unknown color palette: {}", unknown)),
239 }
240 }
241}
242
243struct NamehashVariables {
244 vector: f32,
245 weight: f32,
246 max: f32,
247 modulo: u8,
248}
249
250impl NamehashVariables {
251 fn init() -> Self {
252 NamehashVariables {
253 vector: 0.0,
254 weight: 1.0,
255 max: 1.0,
256 modulo: 10,
257 }
258 }
259
260 fn update(&mut self, character: u8) {
261 let i = f32::from(character % self.modulo);
262 self.vector += (i / f32::from(self.modulo - 1)) * self.weight;
263 self.modulo += 1;
264 self.max += self.weight;
265 self.weight *= 0.70;
266 }
267
268 fn result(&self) -> f32 {
269 1.0 - self.vector / self.max
270 }
271}
272
273fn namehash<I: Iterator<Item = u8>>(mut name: I) -> f32 {
277 let mut namehash_variables = NamehashVariables::init();
278 let mut module_name_found = false;
279
280 match name.next() {
294 None => return namehash_variables.result(),
295 Some(first_char) => namehash_variables.update(first_char),
296 }
297
298 for character in name.by_ref().take(2) {
299 if character == b'`' {
300 module_name_found = true;
301 break;
302 }
303
304 namehash_variables.update(character);
305 }
306
307 module_name_found = module_name_found || name.any(|c| c == b'`');
308
309 if module_name_found {
310 namehash_variables = NamehashVariables::init();
311
312 for character in name.take(3) {
313 namehash_variables.update(character)
314 }
315 }
316
317 namehash_variables.result()
318}
319
320macro_rules! t {
321 ($b:expr, $a:expr, $x:expr) => {
322 $b + ($a * $x) as u8
323 };
324}
325
326macro_rules! color {
327 ($r:expr, $g:expr, $b:expr) => {
328 Color {
329 r: $r,
330 g: $g,
331 b: $b,
332 }
333 };
334}
335
336fn rgb_components_for_palette(palette: Palette, name: &str, v1: f32, v2: f32, v3: f32) -> Color {
337 let basic_palette = match palette {
338 Palette::Basic(basic) => basic,
339 Palette::Multi(MultiPalette::Java) => palettes::java::resolve(name),
340 Palette::Multi(MultiPalette::Perl) => palettes::perl::resolve(name),
341 Palette::Multi(MultiPalette::Python) => palettes::python::resolve(name),
342 Palette::Multi(MultiPalette::Js) => palettes::js::resolve(name),
343 Palette::Multi(MultiPalette::Wakeup) => palettes::wakeup::resolve(name),
344 Palette::Multi(MultiPalette::Rust) => palettes::rust::resolve(name),
345 };
346
347 match basic_palette {
348 BasicPalette::Hot => color!(t!(205, 50_f32, v3), t!(0, 230_f32, v1), t!(0, 55_f32, v2)),
349 BasicPalette::Mem => color!(t!(0, 0_f32, v3), t!(190, 50_f32, v2), t!(0, 210_f32, v1)),
350 BasicPalette::Io => color!(t!(80, 60_f32, v1), t!(80, 60_f32, v1), t!(190, 55_f32, v2)),
351 BasicPalette::Red => color!(t!(200, 55_f32, v1), t!(50, 80_f32, v1), t!(50, 80_f32, v1)),
352 BasicPalette::Green => color!(t!(50, 60_f32, v1), t!(200, 55_f32, v1), t!(50, 60_f32, v1)),
353 BasicPalette::Blue => color!(t!(80, 60_f32, v1), t!(80, 60_f32, v1), t!(205, 50_f32, v1)),
354 BasicPalette::Yellow => {
355 color!(t!(175, 55_f32, v1), t!(175, 55_f32, v1), t!(50, 20_f32, v1))
356 }
357 BasicPalette::Purple => {
358 color!(t!(190, 65_f32, v1), t!(80, 60_f32, v1), t!(190, 65_f32, v1))
359 }
360 BasicPalette::Aqua => color!(t!(50, 60_f32, v1), t!(165, 55_f32, v1), t!(165, 55_f32, v1)),
361 BasicPalette::Orange => color!(t!(190, 65_f32, v1), t!(90, 65_f32, v1), t!(0, 0_f32, v1)),
362 }
363}
364
365pub(super) fn color(
366 palette: Palette,
367 hash: bool,
368 deterministic: bool,
369 name: &str,
370 mut rng: impl FnMut() -> f32,
371) -> Color {
372 let (v1, v2, v3) = if hash {
373 let name_hash = namehash(name.bytes());
374 let reverse_name_hash = namehash(name.bytes().rev());
375
376 (name_hash, reverse_name_hash, reverse_name_hash)
377 } else if deterministic {
378 let mut hash: u64 = 0xcbf29ce484222325;
382 for byte in name.as_bytes() {
384 hash ^= *byte as u64;
385 hash = hash.wrapping_mul(0x100000001b3);
386 }
387 let hash1 = (hash as f64 / u64::MAX as f64) as f32;
388
389 hash ^= 0;
391 hash = hash.wrapping_mul(0x100000001b3);
392 let hash2 = (hash as f64 / u64::MAX as f64) as f32;
393 hash ^= 0;
394 hash = hash.wrapping_mul(0x100000001b3);
395 let hash3 = (hash as f64 / u64::MAX as f64) as f32;
396
397 (hash1, hash2, hash3)
398 } else {
399 (rng(), rng(), rng())
400 };
401
402 rgb_components_for_palette(palette, name, v1, v2, v3)
403}
404
405pub(super) fn color_scale(value: isize, max: usize) -> Color {
406 match value.cmp(&0) {
407 Ordering::Equal => Color {
408 r: 250,
409 g: 250,
410 b: 250,
411 },
412 Ordering::Greater => {
413 let c = 100 + (150 * (max as isize - value) / max as isize) as u8;
416 Color { r: 255, g: c, b: c }
417 }
418 Ordering::Less => {
419 let c = 100 + (150 * (max as isize + value) / max as isize) as u8;
422 Color { r: c, g: c, b: 255 }
423 }
424 }
425}
426
427fn default_bg_color_for(palette: Palette) -> BackgroundColor {
428 match palette {
429 Palette::Basic(BasicPalette::Mem) => BackgroundColor::Green,
430 Palette::Basic(BasicPalette::Io) | Palette::Multi(MultiPalette::Wakeup) => {
431 BackgroundColor::Blue
432 }
433 Palette::Basic(BasicPalette::Red)
434 | Palette::Basic(BasicPalette::Green)
435 | Palette::Basic(BasicPalette::Blue)
436 | Palette::Basic(BasicPalette::Aqua)
437 | Palette::Basic(BasicPalette::Yellow)
438 | Palette::Basic(BasicPalette::Purple)
439 | Palette::Basic(BasicPalette::Orange) => BackgroundColor::Grey,
440 _ => BackgroundColor::Yellow,
441 }
442}
443
444macro_rules! cow {
445 ($gradient:expr) => {
446 (Cow::from($gradient.0), Cow::from($gradient.1))
447 };
448}
449
450pub(super) fn bgcolor_for<'a>(
451 bgcolor: Option<BackgroundColor>,
452 palette: Palette,
453) -> (Cow<'a, str>, Cow<'a, str>) {
454 let bgcolor = bgcolor.unwrap_or_else(|| default_bg_color_for(palette));
455
456 match bgcolor {
457 BackgroundColor::Yellow => cow!(YELLOW_GRADIENT),
458 BackgroundColor::Blue => cow!(BLUE_GRADIENT),
459 BackgroundColor::Green => cow!(GREEN_GRADIENT),
460 BackgroundColor::Grey => cow!(GRAY_GRADIENT),
461 BackgroundColor::Flat(color) => {
462 let color = format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b);
463 let first = Cow::from(color);
464 let second = first.clone();
465 (first, second)
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::namehash;
473 use super::parse_hex_color;
474 use super::Color;
475 use pretty_assertions::assert_eq;
476
477 #[test]
478 fn bgcolor_parse_test() {
479 assert_eq!(parse_hex_color("#ffffff"), Some(color!(0xff, 0xff, 0xff)));
480 assert_eq!(parse_hex_color("#000000"), Some(color!(0x00, 0x00, 0x00)));
481 assert_eq!(parse_hex_color("#abcdef"), Some(color!(0xab, 0xcd, 0xef)));
482 assert_eq!(parse_hex_color("#123456"), Some(color!(0x12, 0x34, 0x56)));
483 assert_eq!(parse_hex_color("#789000"), Some(color!(0x78, 0x90, 0x00)));
484 assert_eq!(parse_hex_color("ffffff"), None);
485 assert_eq!(parse_hex_color("#fffffff"), None);
486 assert_eq!(parse_hex_color("#xfffff"), None);
487 assert_eq!(parse_hex_color("# fffff"), None);
488 }
489
490 macro_rules! test_hash {
491 ($name:expr, $expected:expr) => {
492 assert!((dbg!(namehash($name.bytes())) - $expected).abs() < f32::EPSILON);
493 };
494 }
495
496 #[test]
497 fn namehash_test() {
498 test_hash!(
499 "org/mozilla/javascript/NativeFunction:.initScriptFunction_[j]",
500 0.779_646_04
501 );
502 test_hash!(
503 "]j[_noitcnuFtpircStini.:noitcnuFevitaN/tpircsavaj/allizom/gro",
504 0.644_153_1
505 );
506 test_hash!("genunix`kmem_cache_free", 0.466_926_34);
507 test_hash!("eerf_ehcac_memk`xinuneg", 0.840_410_3);
508 test_hash!("unix`0xfffffffffb8001d6", 0.418_131_17);
509 test_hash!("6d1008bfffffffffx0`xinu", 0.840_410_3);
510 test_hash!("un`0xfffffffffb8001d6", 0.418_131_17);
511 test_hash!("``0xfffffffffb8001d6", 0.418_131_17);
512 test_hash!("", 1.0);
513 }
514}