| 1 | //! SGR (Select Graphic Rendition) parsing: the colour and underline forms that |
| 2 | //! need more than a single parameter. Plain attribute toggles (bold, reverse, |
| 3 | //! ...) stay in the dispatcher; what lives here is the multi-parameter parsing |
| 4 | //! that benefits from being testable in isolation. |
| 5 | |
| 6 | use crate::style::{Color, Underline}; |
| 7 | |
| 8 | /// Map an SGR 4 parameter (`4` or `4:x`) to an underline style. |
| 9 | pub fn underline_from(param: &[u16]) -> Underline { |
| 10 | match param.get(1).copied().unwrap_or(1) { |
| 11 | 0 => Underline::None, |
| 12 | 2 => Underline::Double, |
| 13 | 3 => Underline::Curly, |
| 14 | 4 => Underline::Dotted, |
| 15 | 5 => Underline::Dashed, |
| 16 | _ => Underline::Single, |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | /// Parse an SGR 38/48/58 extended colour, given the full parameter list and the |
| 21 | /// index of the introducer. Returns the colour and how many top-level |
| 22 | /// parameters it consumed (1 for the colon-subparameter form, more for the |
| 23 | /// legacy semicolon form). |
| 24 | pub fn ext_color(items: &[&[u16]], i: usize) -> (Option<Color>, usize) { |
| 25 | let head = items[i]; |
| 26 | if head.len() >= 2 { |
| 27 | return (color_from_subparams(&head[1..]), 1); |
| 28 | } |
| 29 | match items.get(i + 1).and_then(|s| s.first().copied()) { |
| 30 | Some(5) => { |
| 31 | let idx = items |
| 32 | .get(i + 2) |
| 33 | .and_then(|s| s.first().copied()) |
| 34 | .unwrap_or(0); |
| 35 | (Some(Color::Indexed(idx as u8)), 3) |
| 36 | }, |
| 37 | Some(2) => { |
| 38 | let get = |k: usize| { |
| 39 | items |
| 40 | .get(i + k) |
| 41 | .and_then(|s| s.first().copied()) |
| 42 | .unwrap_or(0) |
| 43 | }; |
| 44 | ( |
| 45 | Some(Color::Rgb(get(2) as u8, get(3) as u8, get(4) as u8)), |
| 46 | 5, |
| 47 | ) |
| 48 | }, |
| 49 | _ => (None, 1), |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | /// Parse the colon-subparameter colour form: `5:idx` (indexed) or |
| 54 | /// `2[:cs]:r:g:b` (direct RGB, with an optional colour-space id that is |
| 55 | /// ignored). |
| 56 | fn color_from_subparams(sub: &[u16]) -> Option<Color> { |
| 57 | match sub.first().copied() { |
| 58 | Some(5) => sub.get(1).map(|&i| Color::Indexed(i as u8)), |
| 59 | Some(2) => { |
| 60 | // Either `2:r:g:b` or `2:colorspace:r:g:b`. |
| 61 | let rgb = if sub.len() >= 5 { |
| 62 | &sub[2..5] |
| 63 | } else { |
| 64 | &sub[1..] |
| 65 | }; |
| 66 | match rgb { |
| 67 | [r, g, b, ..] => Some(Color::Rgb(*r as u8, *g as u8, *b as u8)), |
| 68 | _ => None, |
| 69 | } |
| 70 | }, |
| 71 | _ => None, |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | #[cfg(test)] |
| 76 | mod tests { |
| 77 | use super::*; |
| 78 | |
| 79 | #[test] |
| 80 | fn underline_styles() { |
| 81 | assert_eq!(underline_from(&[4]), Underline::Single); |
| 82 | assert_eq!(underline_from(&[4, 3]), Underline::Curly); |
| 83 | assert_eq!(underline_from(&[4, 0]), Underline::None); |
| 84 | } |
| 85 | |
| 86 | #[test] |
| 87 | fn extended_colour_semicolon_and_colon() { |
| 88 | // `38;2;10;20;30` direct RGB. |
| 89 | let items: Vec<&[u16]> = vec![&[38], &[2], &[10], &[20], &[30]]; |
| 90 | assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(10, 20, 30)), 5)); |
| 91 | // `38:2:40:50:60` colon subparameters. |
| 92 | let items: Vec<&[u16]> = vec![&[38, 2, 40, 50, 60]]; |
| 93 | assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(40, 50, 60)), 1)); |
| 94 | // `38;5;1` indexed. |
| 95 | let items: Vec<&[u16]> = vec![&[38], &[5], &[1]]; |
| 96 | assert_eq!(ext_color(&items, 0), (Some(Color::Indexed(1)), 3)); |
| 97 | } |
| 98 | } |