diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2023-02-18 01:55:26 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-18 01:55:26 -0500 |
commit | f89b243589b5d0e0a3dfe75337a66fa1091d0434 (patch) | |
tree | ddc401aac04839252a55d1e46a9d246ab2c0366c /src | |
parent | edc61d428c9045982030cf2b54ea20ef8cd5baab (diff) |
feature: support 3-char hex colours (#1022)
Diffstat (limited to 'src')
-rw-r--r-- | src/canvas/canvas_styling/colour_utils.rs | 172 |
1 files changed, 124 insertions, 48 deletions
diff --git a/src/canvas/canvas_styling/colour_utils.rs b/src/canvas/canvas_styling/colour_utils.rs index 89b1b170..2814c580 100644 --- a/src/canvas/canvas_styling/colour_utils.rs +++ b/src/canvas/canvas_styling/colour_utils.rs @@ -1,4 +1,7 @@ +use concat_string::concat_string; +use itertools::Itertools; use tui::style::{Color, Style}; +use unicode_segmentation::UnicodeSegmentation; use crate::utils::error; @@ -10,41 +13,44 @@ pub const HIGHLIGHT_COLOUR: Color = Color::LightBlue; pub const AVG_COLOUR: Color = Color::Red; pub const ALL_COLOUR: Color = Color::Green; +/// Convert a hex string to a colour. fn convert_hex_to_color(hex: &str) -> error::Result<Color> { - fn hex_err(hex: &str) -> error::Result<u8> { - Err( + fn hex_component_to_int(hex: &str, first: &str, second: &str) -> error::Result<u8> { + u8::from_str_radix(&concat_string!(first, second), 16).map_err(|_| { error::BottomError::ConfigError(format!( - "\"{}\" is an invalid hex colour. It must be a valid 7 character hex string of the (ie: \"#112233\")." - , hex)) - ) + "\"{hex}\" is an invalid hex color, could not decode." + )) + }) } - fn convert_hex_to_rgb(hex: &str) -> error::Result<(u8, u8, u8)> { - let hex_components: Vec<char> = hex.chars().collect(); + fn invalid_hex_format(hex: &str) -> error::BottomError { + error::BottomError::ConfigError(format!( + "\"{hex}\" is an invalid hex color. It must be either a 7 character hex string of the form \"#12ab3c\" or a 3 character hex string of the form \"#1a2\".", + )) + } - if hex_components.len() == 7 { - let mut r_string = hex_components[1].to_string(); - r_string.push(hex_components[2]); - let mut g_string = hex_components[3].to_string(); - g_string.push(hex_components[4]); - let mut b_string = hex_components[5].to_string(); - b_string.push(hex_components[6]); + if !hex.starts_with('#') { + return Err(invalid_hex_format(hex)); + } - let r = u8::from_str_radix(&r_string, 16).or_else(|_err| hex_err(hex))?; - let g = u8::from_str_radix(&g_string, 16).or_else(|_err| hex_err(hex))?; - let b = u8::from_str_radix(&b_string, 16).or_else(|_err| hex_err(hex))?; + let components = hex.graphemes(true).collect_vec(); + if components.len() == 7 { + // A 6-long hex. + let r = hex_component_to_int(hex, components[1], components[2])?; + let g = hex_component_to_int(hex, components[3], components[4])?; + let b = hex_component_to_int(hex, components[5], components[6])?; - return Ok((r, g, b)); - } + Ok(Color::Rgb(r, g, b)) + } else if components.len() == 4 { + // A 3-long hex. + let r = hex_component_to_int(hex, components[1], components[1])?; + let g = hex_component_to_int(hex, components[2], components[2])?; + let b = hex_component_to_int(hex, components[3], components[3])?; - Err(error::BottomError::ConfigError(format!( - "\"{}\" is an invalid hex colour. It must be a 7 character string of the form \"#112233\".", - hex - ))) + Ok(Color::Rgb(r, g, b)) + } else { + Err(invalid_hex_format(hex)) } - - let rgb = convert_hex_to_rgb(hex)?; - Ok(Color::Rgb(rgb.0, rgb.1, rgb.2)) } pub fn str_to_fg(input_val: &str) -> error::Result<Style> { @@ -58,7 +64,7 @@ pub fn str_to_colour(input_val: &str) -> error::Result<Color> { } else if input_val.contains(',') { convert_rgb_to_color(input_val) } else { - convert_name_to_color(input_val) + convert_name_to_colour(input_val) } } else { Err(error::BottomError::ConfigError(format!( @@ -72,7 +78,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> { let rgb_list = rgb_str.split(',').collect::<Vec<&str>>(); if rgb_list.len() != 3 { return Err(error::BottomError::ConfigError(format!( - "value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", + "value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", rgb_str ))); } @@ -91,13 +97,13 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> { Ok(Color::Rgb(rgb[0], rgb[1], rgb[2])) } else { Err(error::BottomError::ConfigError(format!( - "value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", + "value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", rgb_str ))) } } -fn convert_name_to_color(color_name: &str) -> error::Result<Color> { +fn convert_name_to_colour(color_name: &str) -> error::Result<Color> { match color_name.to_lowercase().trim() { "reset" => Ok(Color::Reset), "black" => Ok(Color::Black), @@ -117,7 +123,7 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> { "lightcyan" | "light cyan" => Ok(Color::LightCyan), "white" => Ok(Color::White), _ => Err(error::BottomError::ConfigError(format!( - "\"{}\" is an invalid named colour. + "\"{}\" is an invalid named color. The following are supported strings: +--------+-------------+---------------------+ @@ -144,49 +150,119 @@ mod test { use super::*; #[test] - fn test_invalid_colours() { + fn invalid_colour_names() { // Test invalid spacing in single word. - assert!(convert_name_to_color("bl ack").is_err()); + assert!(convert_name_to_colour("bl ack").is_err()); // Test invalid spacing in dual word. - assert!(convert_name_to_color("darkg ray").is_err()); + assert!(convert_name_to_colour("darkg ray").is_err()); // Test completely invalid colour. - assert!(convert_name_to_color("darkreset").is_err()); + assert!(convert_name_to_colour("darkreset").is_err()); } #[test] - fn test_valid_colours() { + fn valid_colour_names() { // Standard color should work - assert_eq!(convert_name_to_color("red"), Ok(Color::Red)); + assert_eq!(convert_name_to_colour("red"), Ok(Color::Red)); // Capitalizing should be fine. - assert_eq!(convert_name_to_color("RED"), Ok(Color::Red)); + assert_eq!(convert_name_to_colour("RED"), Ok(Color::Red)); // Spacing shouldn't be an issue now. - assert_eq!(convert_name_to_color(" red "), Ok(Color::Red)); + assert_eq!(convert_name_to_colour(" red "), Ok(Color::Red)); // The following are all equivalent. - assert_eq!(convert_name_to_color("darkgray"), Ok(Color::DarkGray)); - assert_eq!(convert_name_to_color("darkgrey"), Ok(Color::DarkGray)); - assert_eq!(convert_name_to_color("dark grey"), Ok(Color::DarkGray)); - assert_eq!(convert_name_to_color("dark gray"), Ok(Color::DarkGray)); + assert_eq!(convert_name_to_colour("darkgray"), Ok(Color::DarkGray)); + assert_eq!(convert_name_to_colour("darkgrey"), Ok(Color::DarkGray)); + assert_eq!(convert_name_to_colour("dark grey"), Ok(Color::DarkGray)); + assert_eq!(convert_name_to_colour("dark gray"), Ok(Color::DarkGray)); - assert_eq!(convert_name_to_color("grey"), Ok(Color::Gray)); - assert_eq!(convert_name_to_color("gray"), Ok(Color::Gray)); + assert_eq!(convert_name_to_colour("grey"), Ok(Color::Gray)); + assert_eq!(convert_name_to_colour("gray"), Ok(Color::Gray)); // One more test with spacing. assert_eq!( - convert_name_to_color(" lightmagenta "), + convert_name_to_colour(" lightmagenta "), Ok(Color::LightMagenta) ); assert_eq!( - convert_name_to_color("light magenta"), + convert_name_to_colour("light magenta"), Ok(Color::LightMagenta) ); assert_eq!( - convert_name_to_color(" light magenta "), + convert_name_to_colour(" light magenta "), Ok(Color::LightMagenta) ); } + + #[test] + fn valid_hex_colours() { + assert_eq!( + convert_hex_to_color("#ffffff").unwrap(), + Color::Rgb(255, 255, 255) + ); + assert_eq!( + convert_hex_to_color("#000000").unwrap(), + Color::Rgb(0, 0, 0) + ); + convert_hex_to_color("#111111").unwrap(); + convert_hex_to_color("#11ff11").unwrap(); + convert_hex_to_color("#1f1f1f").unwrap(); + assert_eq!( + convert_hex_to_color("#123abc").unwrap(), + Color::Rgb(18, 58, 188) + ); + + assert_eq!( + convert_hex_to_color("#fff").unwrap(), + Color::Rgb(255, 255, 255) + ); + assert_eq!(convert_hex_to_color("#000").unwrap(), Color::Rgb(0, 0, 0)); + convert_hex_to_color("#111").unwrap(); + convert_hex_to_color("#1f1").unwrap(); + convert_hex_to_color("#f1f").unwrap(); + convert_hex_to_color("#ff1").unwrap(); + convert_hex_to_color("#1ab").unwrap(); + assert_eq!( + convert_hex_to_color("#1ab").unwrap(), + Color::Rgb(17, 170, 187) + ); + } + + #[test] + fn invalid_hex_colours() { + assert!(convert_hex_to_color("ffffff").is_err()); + assert!(convert_hex_to_color("111111").is_err()); + + assert!(convert_hex_to_color("fff").is_err()); + assert!(convert_hex_to_color("111").is_err()); + assert!(convert_hex_to_color("fffffff").is_err()); + assert!(convert_hex_to_color("1234567").is_err()); + + assert!(convert_hex_to_color("#fffffff").is_err()); + assert!(convert_hex_to_color("#1234567").is_err()); + assert!(convert_hex_to_color("#ff").is_err()); + assert!(convert_hex_to_color("#12").is_err()); + assert!(convert_hex_to_color("").is_err()); + + assert!(convert_hex_to_color("#pppppp").is_err()); + assert!(convert_hex_to_color("#00000p").is_err()); + assert!(convert_hex_to_color("#ppp").is_err()); + + assert!(convert_hex_to_color("#一").is_err()); + assert!(convert_hex_to_color("#一二").is_err()); + assert!(convert_hex_to_color("#一二三").is_err()); + assert!(convert_hex_to_color("#一二三四").is_err()); + + assert!(convert_hex_to_color("#f一f").is_err()); + assert!(convert_hex_to_color("#ff一11").is_err()); + + assert!(convert_hex_to_color("#🇨🇦").is_err()); + assert!(convert_hex_to_color("#🇨🇦🇨🇦").is_err()); + assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦").is_err()); + assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦🇨🇦").is_err()); + + assert!(convert_hex_to_color("#हिन्दी").is_err()); + } } |