use super::*;
use std::collections::BTreeMap;
use std::io::Write;
#[derive(Debug)]
pub struct SVGScreenshotFilter {
save_screenshot: bool,
}
impl fmt::Display for SVGScreenshotFilter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "svg screenshot filter")
}
}
impl SVGScreenshotFilter {
pub fn new() -> Self {
SVGScreenshotFilter {
save_screenshot: false,
}
}
}
impl Component for SVGScreenshotFilter {
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, context: &mut Context) {
if !self.save_screenshot {
return;
}
self.save_screenshot = false;
let grid: &CellBuffer = _grid;
use svg_crate::node::element::{Definitions, Group, Rectangle, Style, Text, Use};
use svg_crate::node::Text as TextNode;
use svg_crate::Document;
let (width, height) = grid.size();
let mut definitions = Definitions::new();
let mut rows_group = Group::new();
let mut text = String::with_capacity(width);
let mut escaped_text = String::with_capacity(width);
let mut classes: BTreeMap<(u8, u8, u8), usize> = BTreeMap::new();
for (row_idx, row) in grid.bounds_iter(((0, 0), (width, height))).enumerate() {
text.clear();
escaped_text.clear();
let mut row_group = Group::new().set("id", format!("{:x}", row_idx + 1));
let mut cur_fg = Color::Default;
let mut cur_bg = Color::Default;
let mut cur_attrs = Attr::DEFAULT;
let mut prev_x_fg = 0;
let mut is_start = true;
let mut prev_x_bg = 0;
for (x, c) in row.enumerate() {
if cur_bg != grid[c].bg() || cur_fg != grid[c].fg() || cur_attrs != grid[c].attrs()
{
if cur_bg != Color::Default {
let mut rect = Rectangle::new()
.set("x", prev_x_bg * 8)
.set("y", 17 * row_idx)
.set("width", (x - prev_x_bg) * 8 + 1)
.set("height", 18);
match cur_bg {
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
Color::Default => {
unreachable!();
}
c if c.as_byte() < 16 => {
rect = rect.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
}
rows_group = rows_group.add(rect);
}
prev_x_bg = x;
cur_bg = grid[c].bg();
if !text.is_empty() {
let text_length = text.grapheme_width();
for c in text.chars() {
match c {
'"' => escaped_text.push_str("""),
'&' => escaped_text.push_str("&"),
'\'' => escaped_text.push_str("'"),
'<' => escaped_text.push_str("<"),
'>' => escaped_text.push_str(">"),
c => escaped_text.push(c),
}
}
let mut text_el = Text::new()
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8)
.set("textLength", text_length * 8);
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
if cur_attrs.intersects(Attr::ITALICS) {
text_el = text_el.set("font-style", "italic");
}
if cur_attrs.intersects(Attr::UNDERLINE) {
text_el = text_el.set("text-decoration", "underline");
}
if cur_attrs.intersects(Attr::DIM) {
text_el = text_el.set("font-weight", "lighter");
}
if cur_attrs.intersects(Attr::HIDDEN) {
text_el = text_el.set("display", "none");
}
match cur_fg {
Color::Default if cur_attrs.intersects(Attr::REVERSE) => {
text_el = text_el.set("class", "b");
}
Color::Default => {
text_el = text_el.set("class", "f");
}
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
c if c.as_byte() < 16 => {
text_el =
text_el.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
};
row_group = row_group.add(text_el);
text.clear();
escaped_text.clear();
}
prev_x_fg = x;
cur_fg = grid[c].fg();
cur_attrs = grid[c].attrs();
}
match grid[c].ch() {
' ' if is_start => {
prev_x_fg = x + 1;
}
c => text.push(c),
}
if grid[c].ch() != ' ' {
is_start = false;
}
}
if cur_bg != Color::Default {
let mut rect = Rectangle::new()
.set("x", prev_x_bg * 8)
.set("y", 17 * row_idx)
.set("width", (width - prev_x_bg) * 8 + 1)
.set("height", 18);
match cur_bg {
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
Color::Default => {
unreachable!();
}
c if c.as_byte() < 16 => {
rect = rect.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
}
rows_group = rows_group.add(rect);
}
if !text.is_empty() {
let text_length = text.grapheme_width();
for c in text.chars() {
match c {
'"' => escaped_text.push_str("""),
'&' => escaped_text.push_str("&"),
'\'' => escaped_text.push_str("'"),
'<' => escaped_text.push_str("<"),
'>' => escaped_text.push_str(">"),
c => escaped_text.push(c),
}
}
let mut text_el = Text::new()
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * 8)
.set("textLength", text_length * 8);
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
if cur_attrs.intersects(Attr::ITALICS) {
text_el = text_el.set("font-style", "italic");
}
if cur_attrs.intersects(Attr::UNDERLINE) {
text_el = text_el.set("text-decoration", "underline");
}
if cur_attrs.intersects(Attr::DIM) {
text_el = text_el.set("font-weight", "lighter");
}
if cur_attrs.intersects(Attr::HIDDEN) {
text_el = text_el.set("display", "none");
}
match cur_fg {
Color::Default if cur_attrs.intersects(Attr::REVERSE) => {
text_el = text_el.set("class", "b");
}
Color::Default => {
text_el = text_el.set("class", "f");
}
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
c if c.as_byte() < 16 => {
text_el = text_el.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
}
row_group = row_group.add(text_el);
text.clear();
escaped_text.clear();
}
definitions = definitions.add(row_group);
rows_group = rows_group.add(
Use::new()
.set("xlink:href", format!("#{:x}", row_idx + 1))
.set("y", 17 * row_idx),
);
}
let mut style_string = CSS_STYLE.to_string();
for ((r, g, b), name) in classes {
style_string
.extend(format!(".f{:x}{{fill:#{:02x}{:02x}{:02x};}}", name, r, g, b).chars());
}
let document = Document::new()
.set("viewBox", (0, 0, width * 8, height * 17 + 2))
.set("width", width * 8)
.set("height", height * 17 + 2)
.add(Definitions::new().add(Style::new(&style_string).set("type", "text/css")))
.add(
Document::new()
.set("id", "t")
.set("preserveAspectRatio", "xMidYMin slice")
.set("viewBox", (0, 0, width * 8, height * 17))
.set("width", width * 8)
.set("height", height * 17)
.add(
Rectangle::new()
.set("class", "b")
.set("height", "100%")
.set("width", "100%")
.set("x", 0)
.set("y", 0),
)
.add(definitions)
.add(rows_group),
)
.set("xmlns", "http://www.w3.org/2000/svg")
.set("baseProfile", "full")
.set("xmlns:xlink", "http://www.w3.org/1999/xlink")
.set("version", "1.1");
let mut s = Vec::new();
svg_crate::write(&mut s, &document).unwrap();
let mut res = Vec::new();
for b in s {
if b == b'\n' {
continue;
}
res.push(b);
}
let mut filename = melib::datetime::timestamp_to_string(
melib::datetime::now(),
Some("meli Screenshot - %e %h %Y %H:%M:%S.svg"),
);
while std::path::Path::new(&filename).exists() {
filename.pop();
filename.pop();
filename.pop();
filename.pop();
filename.push_str("_.svg");
}
std::fs::File::create(&filename)
.unwrap()
.write_all(&res)
.unwrap();
context.replies.push_back(UIEvent::Notification(
Some("Screenshot saved".into()),
format!("Screenshot saved to {}", filename),
None,
));
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
if let UIEvent::Input(Key::F(6)) = event {
self.save_screenshot = true;
true
} else if let UIEvent::CmdInput(Key::F(6)) = event {
self.save_screenshot = true;
true
} else if let UIEvent::EmbedInput((Key::F(6), _)) = event {
self.save_screenshot = true;
false
} else {
false
}
}
fn set_dirty(&mut self, _value: bool) {}
fn is_dirty(&self) -> bool {
self.save_screenshot
}
fn id(&self) -> ComponentId {
ComponentId::nil()
}
fn set_id(&mut self, _id: ComponentId) {}
}
const CSS_STYLE: &str = r#"#t{font-family:'DejaVu Sans Mono',monospace;font-style:normal;font-size:14px;} text {dominant-baseline: text-before-edge; white-space: pre;} .f{fill:#e5e5e5;} .b{fill:#000;} .c0 {fill:#000;} .c1 {fill:#cd0000;} .c2 {fill:#00cd00;} .c3 {fill:#cdcd00;} .c4 {fill:#00e;} .c5 {fill:#cd00cd;} .c6 {fill:#00cdcd;} .c7 {fill:#e5e5e5;} .c8 {fill:#7f7f7f;} .c9 {fill:#f00;} .c10 {fill:#0f0;} .c11 {fill:#ff0;} .c12 {fill:#5c5cff;} .c13 {fill:#f0f;} .c14 {fill:#0ff;} .c15 {fill:#fff;}"#;
const XTERM_COLORS: &[(u8, u8, u8)] = &[
(0, 0, 0),
(128, 0, 0),
(0, 128, 0),
(128, 128, 0),
(0, 0, 128),
(128, 0, 128),
(0, 128, 128),
(192, 192, 192),
(128, 128, 128),
(255, 0, 0),
(0, 255, 0),
(255, 255, 0),
(0, 0, 255),
(255, 0, 255),
(0, 255, 255),
(255, 255, 255),
(0, 0, 0),
(0, 0, 95),
(0, 0, 135),
(0, 0, 175),
(0, 0, 215),
(0, 0, 255),
(0, 95, 0),
(0, 95, 95),
(0, 95, 135),
(0, 95, 175),
(0, 95, 215),
(0, 95, 255),
(0, 135, 0),
(0, 135, 95),
(0, 135, 135),
(0, 135, 175),
(0, 135, 215),
(0, 135, 255),
(0, 175, 0),
(0, 175, 95),
(0, 175, 135),
(0, 175, 175),
(0, 175, 215),
(0, 175, 255),
(0, 215, 0),
(0, 215, 95),
(0, 215, 135),
(0, 215, 175),
(0, 215, 215),
(0, 215, 255),
(0, 255, 0),
(0, 255, 95),
(0, 255, 135),
(0, 255, 175),
(0, 255, 215),
(0, 255, 255),
(95, 0, 0),
(95, 0, 95),
(95, 0, 135),
(95, 0, 175),
(95, 0, 215),
(95, 0, 255),
(95, 95, 0),
(95, 95, 95),
(95, 95, 135),
(95, 95, 175),
(95, 95, 215),
(95, 95, 255),
(95, 135, 0),
(95, 135, 95),
(95, 135, 135),
(95, 135, 175),
(95, 135, 215),
(95, 135, 255),
(95, 175, 0),
(95, 175, 95),
(95, 175, 135),
(95, 175, 175),
(95, 175, 215),
(95, 175, 255),
(95, 215, 0),
(95, 215, 95),
(95, 215, 135),
(95, 215, 175),
(95, 215, 215),
(95, 215, 255),
(95, 255, 0),
(95, 255, 95),
(95, 255, 135),
(95, 255, 175),
(95, 255, 215),
(95, 255, 255),
(135, 0, 0),
(135, 0, 95),
(135, 0, 135),
(135, 0, 175),
(135, 0, 215),
(135, 0, 255),
(135, 95, 0),
(135, 95, 95),
(135, 95, 135),
(135, 95, 175),
(135, 95, 215),
(135, 95, 255),
(135, 135, 0),
(135, 135, 95),
(135, 135, 135),
(135, 135, 175),
(135, 135, 215),
(135, 135, 255),
(135, 175, 0),
(135, 175, 95),
(135, 175, 135),
(135, 175, 175),
(135, 175, 215),
(135, 175, 255),
(135, 215, 0),
(135, 215, 95),
(135, 215, 135),
(135, 215, 175),
(135, 215, 215),
(135, 215, 255),
(135, 255, 0),
(135, 255, 95),
(135, 255, 135),
(135, 255, 175),
(135, 255, 215),
(135, 255, 255),
(175, 0, 0),
(175, 0, 95),
(175, 0, 135),
(175, 0, 175),
(175, 0, 215),
(175, 0, 255),
(175, 95, 0),
(175, 95, 95),
(175, 95, 135),
(175, 95, 175),
(175, 95, 215),
(175, 95, 255),
(175, 135, 0),
(175, 135, 95),
(175, 135, 135),
(175, 135, 175),
(175, 135, 215),
(175, 135, 255),
(175, 175, 0),
(175, 175, 95),
(175, 175, 135),
(175, 175, 175),
(175, 175, 215),
(175, 175, 255),
(175, 215, 0),
(175, 215, 95),
(175, 215, 135),
(175, 215, 175),
(175, 215, 215),
(175, 215, 255),
(175, 255, 0),
(175, 255, 95),
(175, 255, 135),
(175, 255, 175),
(175, 255, 215),
(175, 255, 255),
(215, 0, 0),
(215, 0, 95),
(215, 0, 135),
(215, 0, 175),
(215, 0, 215),
(215, 0, 255),
(215, 95, 0),
(215, 95, 95),
(215, 95, 135),
(215, 95, 175),
(215, 95, 215),
(215, 95, 255),
(215, 135, 0),
(215, 135, 95),
(215, 135, 135),
(215, 135, 175),
(215, 135, 215),
(215, 135, 255),
(215, 175, 0),
(215, 175, 95),
(215, 175, 135),
(215, 175, 175),
(215, 175, 215),
(215, 175, 255),
(215, 215, 0),
(215, 215, 95),
(215, 215, 135),
(215, 215, 175),
(215, 215, 215),
(215, 215, 255),
(215, 255, 0),
(215, 255, 95),
(215, 255, 135),
(215, 255, 175),
(215, 255, 215),
(215, 255, 255),
(255, 0, 0),
(255, 0, 95),
(255, 0, 135),
(255, 0, 175),
(255, 0, 215),
(255, 0, 255),
(255, 95, 0),
(255, 95, 95),
(255, 95, 135),
(255, 95, 175),
(255, 95, 215),
(255, 95, 255),
(255, 135, 0),
(255, 135, 95),
(255, 135, 135),
(255, 135, 175),
(255, 135, 215),
(255, 135, 255),
(255, 175, 0),
(255, 175, 95),
(255, 175, 135),
(255, 175, 175),
(255, 175, 215),
(255, 175, 255),
(255, 215, 0),
(255, 215, 95),
(255, 215, 135),
(255, 215, 175),
(255, 215, 215),
(255, 215, 255),
(255, 255, 0),
(255, 255, 95),
(255, 255, 135),
(255, 255, 175),
(255, 255, 215),
(255, 255, 255),
(8, 8, 8),
(18, 18, 18),
(28, 28, 28),
(38, 38, 38),
(48, 48, 48),
(58, 58, 58),
(68, 68, 68),
(78, 78, 78),
(88, 88, 88),
(98, 98, 98),
(108, 108, 108),
(118, 118, 118),
(128, 128, 128),
(138, 138, 138),
(148, 148, 148),
(158, 158, 158),
(168, 168, 168),
(178, 178, 178),
(188, 188, 188),
(198, 198, 198),
(208, 208, 208),
(218, 218, 218),
(228, 228, 228),
(238, 238, 238),
];