From 15cd990e3351b75a2a15ba20645ad397871109f9 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 27 Mar 2022 13:38:33 -0700 Subject: [PATCH] refactor: move sixel parser/builder to own file --- termwiz/src/escape/parser/mod.rs | 310 +--------------------------- termwiz/src/escape/parser/sixel.rs | 314 +++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 306 deletions(-) create mode 100644 termwiz/src/escape/parser/sixel.rs diff --git a/termwiz/src/escape/parser/mod.rs b/termwiz/src/escape/parser/mod.rs index c78a51dc9..32565f1dc 100644 --- a/termwiz/src/escape/parser/mod.rs +++ b/termwiz/src/escape/parser/mod.rs @@ -1,25 +1,17 @@ #![allow(clippy::many_single_char_names)] -use crate::color::RgbColor; use crate::escape::{ Action, DeviceControlMode, EnterDeviceControlMode, Esc, OperatingSystemCommand, - ShortDeviceControl, Sixel, SixelData, CSI, + ShortDeviceControl, CSI, }; use crate::tmux_cc::Event; use log::error; use num_traits::FromPrimitive; -use regex::bytes::Regex; use std::borrow::BorrowMut; use std::cell::RefCell; use vtparse::{CsiParam, VTActor, VTParser}; -struct SixelBuilder { - sixel: Sixel, - buf: Vec, - repeat_re: Regex, - raster_re: Regex, - colordef_re: Regex, - coloruse_re: Regex, -} +mod sixel; +use sixel::SixelBuilder; #[derive(Default)] struct GetTcapBuilder { @@ -341,195 +333,11 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> { } } -impl SixelBuilder { - fn new(params: &[i64]) -> Self { - let pan = match params.get(0).unwrap_or(&0) { - 7 | 8 | 9 => 1, - 0 | 1 | 5 | 6 => 2, - 3 | 4 => 3, - 2 => 5, - _ => 2, - }; - let background_is_transparent = match params.get(1).unwrap_or(&0) { - 1 => true, - _ => false, - }; - let horizontal_grid_size = params.get(2).map(|&x| x); - - let repeat_re = Regex::new("^!(\\d+)([\x3f-\x7e])").unwrap(); - let raster_re = Regex::new("^\"(\\d+);(\\d+)(;(\\d+))?(;(\\d+))?").unwrap(); - let colordef_re = Regex::new("^#(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);?").unwrap(); - let coloruse_re = Regex::new("^#(\\d+)([^;\\d]|$)").unwrap(); - - Self { - sixel: Sixel { - pan, - pad: 1, - pixel_width: None, - pixel_height: None, - background_is_transparent, - horizontal_grid_size, - data: vec![], - }, - buf: vec![], - repeat_re, - raster_re, - colordef_re, - coloruse_re, - } - } - - fn push(&mut self, data: u8) { - self.buf.push(data); - } - - fn finish(&mut self) { - fn cap_int(m: regex::bytes::Match) -> Option { - let bytes = m.as_bytes(); - // Safe because we matched digits from the regex - let s = unsafe { std::str::from_utf8_unchecked(bytes) }; - s.parse::().ok() - } - - let mut remainder = &self.buf[..]; - - while !remainder.is_empty() { - let data = remainder[0]; - - if data == b'$' { - self.sixel.data.push(SixelData::CarriageReturn); - remainder = &remainder[1..]; - continue; - } - - if data == b'-' { - self.sixel.data.push(SixelData::NewLine); - remainder = &remainder[1..]; - continue; - } - - if data >= 0x3f && data <= 0x7e { - self.sixel.data.push(SixelData::Data(data - 0x3f)); - remainder = &remainder[1..]; - continue; - } - - if let Some(c) = self.raster_re.captures(remainder) { - let all = c.get(0).unwrap(); - let matched_len = all.as_bytes().len(); - - let pan = cap_int(c.get(1).unwrap()).unwrap_or(2); - let pad = cap_int(c.get(2).unwrap()).unwrap_or(1); - let pixel_width = c.get(4).and_then(cap_int); - let pixel_height = c.get(6).and_then(cap_int); - - self.sixel.pan = pan; - self.sixel.pad = pad; - self.sixel.pixel_width = pixel_width; - self.sixel.pixel_height = pixel_height; - - if let (Some(w), Some(h)) = (pixel_width, pixel_height) { - let size = w as usize * h as usize; - // Ideally we'd just use `try_reserve` here, but that is - // nightly Rust only at the time of writing this comment: - // - const MAX_SIXEL_SIZE: usize = 100_000_000; - if size > MAX_SIXEL_SIZE { - log::error!( - "Ignoring sixel data {}x{} because {} bytes > max allowed {}", - w, - h, - size, - MAX_SIXEL_SIZE - ); - self.sixel.pixel_width = None; - self.sixel.pixel_height = None; - self.sixel.data.clear(); - return; - } - self.sixel.data.reserve(size); - } - - remainder = &remainder[matched_len..]; - continue; - } - - if let Some(c) = self.coloruse_re.captures(remainder) { - let all = c.get(0).unwrap(); - let matched_len = all.as_bytes().len(); - - let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0); - - self.sixel - .data - .push(SixelData::SelectColorMapEntry(color_number)); - - let pop_len = matched_len - c.get(2).unwrap().as_bytes().len(); - - remainder = &remainder[pop_len..]; - continue; - } - - if let Some(c) = self.colordef_re.captures(remainder) { - let all = c.get(0).unwrap(); - let matched_len = all.as_bytes().len(); - - let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0); - let system = cap_int(c.get(2).unwrap()).unwrap_or(1); - let a = cap_int(c.get(3).unwrap()).unwrap_or(0); - let b = cap_int(c.get(4).unwrap()).unwrap_or(0); - let c = cap_int(c.get(5).unwrap()).unwrap_or(0); - - if system == 1 { - self.sixel.data.push(SixelData::DefineColorMapHSL { - color_number, - hue_angle: a, - lightness: b, - saturation: c, - }); - } else { - let r = a as f32 * 255.0 / 100.; - let g = b as f32 * 255.0 / 100.; - let b = c as f32 * 255.0 / 100.; - let rgb = RgbColor::new_8bpc(r as u8, g as u8, b as u8); // FIXME: from linear - self.sixel - .data - .push(SixelData::DefineColorMapRGB { color_number, rgb }); - } - - remainder = &remainder[matched_len..]; - continue; - } - - if let Some(c) = self.repeat_re.captures(remainder) { - let all = c.get(0).unwrap(); - let matched_len = all.as_bytes().len(); - - let repeat_count = cap_int(c.get(1).unwrap()).unwrap_or(1); - let data = c.get(2).unwrap().as_bytes()[0] - 0x3f; - self.sixel - .data - .push(SixelData::Repeat { repeat_count, data }); - remainder = &remainder[matched_len..]; - continue; - } - - log::error!( - "finished sixel parse with {} bytes pending {:?}", - remainder.len(), - std::str::from_utf8(&remainder[0..24.min(remainder.len())]) - ); - - break; - } - } -} - #[cfg(test)] mod test { use super::*; use crate::cell::{Intensity, Underline}; - use crate::color::ColorSpec; + use crate::color::{ColorSpec, RgbColor}; use crate::escape::csi::{ CharacterPath, DecPrivateMode, DecPrivateModeCode, Device, Mode, Sgr, Window, XtSmGraphics, XtSmGraphicsItem, XtermKeyModifierResource, @@ -703,116 +511,6 @@ mod test { assert_eq!(encode(&actions), "\x1b%H"); } - #[test] - fn sixel() { - let mut p = Parser::new(); - let actions = p.parse_as_vec(b"\x1bP1;2;3;q@\x1b\\"); - assert_eq!( - vec![ - Action::Sixel(Box::new(Sixel { - pan: 2, - pad: 1, - pixel_width: None, - pixel_height: None, - background_is_transparent: false, - horizontal_grid_size: Some(3), - data: vec![SixelData::Data(1)] - })), - Action::Esc(Esc::Code(EscCode::StringTerminator)), - ], - actions - ); - - assert_eq!(format!("{}", actions[0]), "\x1bP0;0;3q@"); - - // This is the "HI" example from wikipedia - let mut p = Parser::new(); - let actions = p.parse_as_vec( - b"\x1bPq\ - #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0\ - #1~~@@vv@@~~@@~~$\ - #2??}}GG}}??}}??-\ - #1!14@\ - \x1b\\", - ); - - assert_eq!( - format!("{}", actions[0]), - "\x1bP0;0q\ - #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0\ - #1~~@@vv@@~~@@~~$\ - #2??}}GG}}??}}??-\ - #1!14@" - ); - - use SixelData::*; - assert_eq!( - vec![ - Action::Sixel(Box::new(Sixel { - pan: 2, - pad: 1, - pixel_width: None, - pixel_height: None, - background_is_transparent: false, - horizontal_grid_size: None, - data: vec![ - DefineColorMapRGB { - color_number: 0, - rgb: RgbColor::new_8bpc(0, 0, 0) - }, - DefineColorMapRGB { - color_number: 1, - rgb: RgbColor::new_8bpc(255, 255, 0) - }, - DefineColorMapRGB { - color_number: 2, - rgb: RgbColor::new_8bpc(0, 255, 0) - }, - SelectColorMapEntry(1), - Data(63), - Data(63), - Data(1), - Data(1), - Data(55), - Data(55), - Data(1), - Data(1), - Data(63), - Data(63), - Data(1), - Data(1), - Data(63), - Data(63), - CarriageReturn, - SelectColorMapEntry(2), - Data(0), - Data(0), - Data(62), - Data(62), - Data(8), - Data(8), - Data(62), - Data(62), - Data(0), - Data(0), - Data(62), - Data(62), - Data(0), - Data(0), - NewLine, - SelectColorMapEntry(1), - Repeat { - repeat_count: 14, - data: 1 - } - ] - })), - Action::Esc(Esc::Code(EscCode::StringTerminator)), - ], - actions - ); - } - #[test] fn soft_reset() { let mut p = Parser::new(); diff --git a/termwiz/src/escape/parser/sixel.rs b/termwiz/src/escape/parser/sixel.rs new file mode 100644 index 000000000..5672c97d2 --- /dev/null +++ b/termwiz/src/escape/parser/sixel.rs @@ -0,0 +1,314 @@ +use crate::color::RgbColor; +use crate::escape::{Sixel, SixelData}; +use regex::bytes::Regex; + +pub struct SixelBuilder { + pub sixel: Sixel, + buf: Vec, + repeat_re: Regex, + raster_re: Regex, + colordef_re: Regex, + coloruse_re: Regex, +} + +impl SixelBuilder { + pub fn new(params: &[i64]) -> Self { + let pan = match params.get(0).unwrap_or(&0) { + 7 | 8 | 9 => 1, + 0 | 1 | 5 | 6 => 2, + 3 | 4 => 3, + 2 => 5, + _ => 2, + }; + let background_is_transparent = match params.get(1).unwrap_or(&0) { + 1 => true, + _ => false, + }; + let horizontal_grid_size = params.get(2).map(|&x| x); + + let repeat_re = Regex::new("^!(\\d+)([\x3f-\x7e])").unwrap(); + let raster_re = Regex::new("^\"(\\d+);(\\d+)(;(\\d+))?(;(\\d+))?").unwrap(); + let colordef_re = Regex::new("^#(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);?").unwrap(); + let coloruse_re = Regex::new("^#(\\d+)([^;\\d]|$)").unwrap(); + + Self { + sixel: Sixel { + pan, + pad: 1, + pixel_width: None, + pixel_height: None, + background_is_transparent, + horizontal_grid_size, + data: vec![], + }, + buf: vec![], + repeat_re, + raster_re, + colordef_re, + coloruse_re, + } + } + + pub fn push(&mut self, data: u8) { + self.buf.push(data); + } + + pub fn finish(&mut self) { + fn cap_int(m: regex::bytes::Match) -> Option { + let bytes = m.as_bytes(); + // Safe because we matched digits from the regex + let s = unsafe { std::str::from_utf8_unchecked(bytes) }; + s.parse::().ok() + } + + let mut remainder = &self.buf[..]; + + while !remainder.is_empty() { + let data = remainder[0]; + + if data == b'$' { + self.sixel.data.push(SixelData::CarriageReturn); + remainder = &remainder[1..]; + continue; + } + + if data == b'-' { + self.sixel.data.push(SixelData::NewLine); + remainder = &remainder[1..]; + continue; + } + + if data >= 0x3f && data <= 0x7e { + self.sixel.data.push(SixelData::Data(data - 0x3f)); + remainder = &remainder[1..]; + continue; + } + + if let Some(c) = self.raster_re.captures(remainder) { + let all = c.get(0).unwrap(); + let matched_len = all.as_bytes().len(); + + let pan = cap_int(c.get(1).unwrap()).unwrap_or(2); + let pad = cap_int(c.get(2).unwrap()).unwrap_or(1); + let pixel_width = c.get(4).and_then(cap_int); + let pixel_height = c.get(6).and_then(cap_int); + + self.sixel.pan = pan; + self.sixel.pad = pad; + self.sixel.pixel_width = pixel_width; + self.sixel.pixel_height = pixel_height; + + if let (Some(w), Some(h)) = (pixel_width, pixel_height) { + let size = w as usize * h as usize; + // Ideally we'd just use `try_reserve` here, but that is + // nightly Rust only at the time of writing this comment: + // + const MAX_SIXEL_SIZE: usize = 100_000_000; + if size > MAX_SIXEL_SIZE { + log::error!( + "Ignoring sixel data {}x{} because {} bytes > max allowed {}", + w, + h, + size, + MAX_SIXEL_SIZE + ); + self.sixel.pixel_width = None; + self.sixel.pixel_height = None; + self.sixel.data.clear(); + return; + } + self.sixel.data.reserve(size); + } + + remainder = &remainder[matched_len..]; + continue; + } + + if let Some(c) = self.coloruse_re.captures(remainder) { + let all = c.get(0).unwrap(); + let matched_len = all.as_bytes().len(); + + let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0); + + self.sixel + .data + .push(SixelData::SelectColorMapEntry(color_number)); + + let pop_len = matched_len - c.get(2).unwrap().as_bytes().len(); + + remainder = &remainder[pop_len..]; + continue; + } + + if let Some(c) = self.colordef_re.captures(remainder) { + let all = c.get(0).unwrap(); + let matched_len = all.as_bytes().len(); + + let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0); + let system = cap_int(c.get(2).unwrap()).unwrap_or(1); + let a = cap_int(c.get(3).unwrap()).unwrap_or(0); + let b = cap_int(c.get(4).unwrap()).unwrap_or(0); + let c = cap_int(c.get(5).unwrap()).unwrap_or(0); + + if system == 1 { + self.sixel.data.push(SixelData::DefineColorMapHSL { + color_number, + hue_angle: a, + lightness: b, + saturation: c, + }); + } else { + let r = a as f32 * 255.0 / 100.; + let g = b as f32 * 255.0 / 100.; + let b = c as f32 * 255.0 / 100.; + let rgb = RgbColor::new_8bpc(r as u8, g as u8, b as u8); // FIXME: from linear + self.sixel + .data + .push(SixelData::DefineColorMapRGB { color_number, rgb }); + } + + remainder = &remainder[matched_len..]; + continue; + } + + if let Some(c) = self.repeat_re.captures(remainder) { + let all = c.get(0).unwrap(); + let matched_len = all.as_bytes().len(); + + let repeat_count = cap_int(c.get(1).unwrap()).unwrap_or(1); + let data = c.get(2).unwrap().as_bytes()[0] - 0x3f; + self.sixel + .data + .push(SixelData::Repeat { repeat_count, data }); + remainder = &remainder[matched_len..]; + continue; + } + + log::error!( + "finished sixel parse with {} bytes pending {:?}", + remainder.len(), + std::str::from_utf8(&remainder[0..24.min(remainder.len())]) + ); + + break; + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::escape::parser::Parser; + use crate::escape::{Action, Esc, EscCode}; + use pretty_assertions::assert_eq; + + #[test] + fn sixel() { + let mut p = Parser::new(); + let actions = p.parse_as_vec(b"\x1bP1;2;3;q@\x1b\\"); + assert_eq!( + vec![ + Action::Sixel(Box::new(Sixel { + pan: 2, + pad: 1, + pixel_width: None, + pixel_height: None, + background_is_transparent: false, + horizontal_grid_size: Some(3), + data: vec![SixelData::Data(1)] + })), + Action::Esc(Esc::Code(EscCode::StringTerminator)), + ], + actions + ); + + assert_eq!(format!("{}", actions[0]), "\x1bP0;0;3q@"); + + // This is the "HI" example from wikipedia + let mut p = Parser::new(); + let actions = p.parse_as_vec( + b"\x1bPq\ + #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0\ + #1~~@@vv@@~~@@~~$\ + #2??}}GG}}??}}??-\ + #1!14@\ + \x1b\\", + ); + + assert_eq!( + format!("{}", actions[0]), + "\x1bP0;0q\ + #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0\ + #1~~@@vv@@~~@@~~$\ + #2??}}GG}}??}}??-\ + #1!14@" + ); + + use SixelData::*; + assert_eq!( + vec![ + Action::Sixel(Box::new(Sixel { + pan: 2, + pad: 1, + pixel_width: None, + pixel_height: None, + background_is_transparent: false, + horizontal_grid_size: None, + data: vec![ + DefineColorMapRGB { + color_number: 0, + rgb: RgbColor::new_8bpc(0, 0, 0) + }, + DefineColorMapRGB { + color_number: 1, + rgb: RgbColor::new_8bpc(255, 255, 0) + }, + DefineColorMapRGB { + color_number: 2, + rgb: RgbColor::new_8bpc(0, 255, 0) + }, + SelectColorMapEntry(1), + Data(63), + Data(63), + Data(1), + Data(1), + Data(55), + Data(55), + Data(1), + Data(1), + Data(63), + Data(63), + Data(1), + Data(1), + Data(63), + Data(63), + CarriageReturn, + SelectColorMapEntry(2), + Data(0), + Data(0), + Data(62), + Data(62), + Data(8), + Data(8), + Data(62), + Data(62), + Data(0), + Data(0), + Data(62), + Data(62), + Data(0), + Data(0), + NewLine, + SelectColorMapEntry(1), + Repeat { + repeat_count: 14, + data: 1 + } + ] + })), + Action::Esc(Esc::Code(EscCode::StringTerminator)), + ], + actions + ); + } +}