From 7c73fd6ed35c0894fd46b926afc286ab3cf92a65 Mon Sep 17 00:00:00 2001 From: elvis Date: Sun, 2 Nov 2025 17:45:54 +0100 Subject: [PATCH] Fixed bugs for svg --- reaction_systems_gui/src/app.rs | 31 ++++++++++++------ reaction_systems_gui/src/app_logic.rs | 45 +++++++++++++++++++++++---- reaction_systems_gui/src/lib.rs | 2 +- reaction_systems_gui/src/svg.rs | 21 ++++++++++++- 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/reaction_systems_gui/src/app.rs b/reaction_systems_gui/src/app.rs index 0eddb52..b4e8515 100644 --- a/reaction_systems_gui/src/app.rs +++ b/reaction_systems_gui/src/app.rs @@ -75,9 +75,9 @@ pub enum BasicDataType { derive(serde::Serialize, serde::Deserialize) )] pub enum BasicValue { - SaveString { + SaveBytes { path: String, - value: String, + value: Vec, }, Error { value: LayoutJob, @@ -225,7 +225,7 @@ impl Hash for BasicValue { ); match self { - | Self::SaveString { path, value } => { + | Self::SaveBytes { path, value } => { path.hash(state); value.hash(state); }, @@ -284,6 +284,7 @@ pub enum NodeInstruction { ColorNode, ColorEdge, StringToSvg, + SaveSvg, // convert basic data types ToPositiveSet, @@ -493,6 +494,7 @@ impl NodeInstruction { ], | Self::Sleep => vec![("seconds", PositiveInt)], | Self::StringToSvg => vec![("value", String)], + | Self::SaveSvg => vec![("path", Path), ("value", Svg)] } .into_iter() .map(|e| (e.0.to_string(), e.1)) @@ -589,6 +591,7 @@ impl NodeInstruction { | Self::PositiveBisimilarityPaigeTarjan => vec![("out", String)], | Self::Sleep => vec![("out", PositiveInt)], | Self::StringToSvg => vec![("out", Svg)], + | Self::SaveSvg => vec![], }; res.into_iter() .map(|res| (res.0.to_string(), res.1)) @@ -1087,6 +1090,7 @@ impl NodeTemplateTrait for NodeInstruction { "Positive Paige & Torjan", | Self::Sleep => "Sleep", | Self::StringToSvg => "String to SVG", + | Self::SaveSvg => "Save SVG", }) } @@ -1169,7 +1173,8 @@ impl NodeTemplateTrait for NodeInstruction { | Self::PositiveBisimilarityPaigeTarjan => vec!["Positive Graph", "Positive Bisimilarity"], | Self::Sleep - | Self::StringToSvg => vec!["General"], + | Self::StringToSvg + | Self::SaveSvg => vec!["General"], } } @@ -1276,6 +1281,7 @@ impl NodeTemplateIter for AllInstructions { NodeInstruction::PositiveBisimilarityPaigeTarjan, NodeInstruction::Sleep, NodeInstruction::StringToSvg, + NodeInstruction::SaveSvg, ] } } @@ -1298,7 +1304,7 @@ impl WidgetValueTrait for BasicValue { match self { // Dummy values used to save files, no ui since not needed - | BasicValue::SaveString { path: _, value: _ } => {}, + | BasicValue::SaveBytes { path: _, value: _ } => {}, | BasicValue::Error { value: _ } => {}, | BasicValue::String { value } => { @@ -1461,6 +1467,13 @@ impl NodeDataTrait for NodeData { )); } }, + | (_, NodeInstruction::SaveSvg) => { + if ui.button("Write").clicked() { + responses.push(NodeResponse::User( + CustomResponse::SaveToFile(node_id), + )); + } + } | (true, NodeInstruction::ReadPath) => { // since no filewatcher we simply give the option to reload the // file @@ -1936,7 +1949,7 @@ impl eframe::App for AppHandle { let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx); self.cached_last_value = Some(text); } else if let Some(l_b_v) = self.cache.get_last_state() { - if let BasicValue::SaveString { path, value } = &l_b_v { + if let BasicValue::SaveBytes { path, value } = &l_b_v { use std::io::Write; let mut f = match std::fs::File::create(path) { Ok(f) => f, @@ -1945,7 +1958,7 @@ impl eframe::App for AppHandle { return; } }; - if let Err(e) = write!(f, "{}", value) { + if let Err(e) = f.write_all(value) { println!("Error writing to file {path}: {e}"); return; } @@ -1970,7 +1983,7 @@ impl eframe::App for AppHandle { let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx); self.cached_last_value = Some(content.clone()); } else if let Some(l_b_v) = self.cache.get_last_state() { - if let BasicValue::SaveString { path, value } = &l_b_v { + if let BasicValue::SaveBytes { path, value } = &l_b_v { use std::io::Write; let mut f = match std::fs::File::create(path) { Ok(f) => f, @@ -2103,7 +2116,7 @@ fn get_layout( match value { | Ok(value) => match value { - | BasicValue::SaveString { path, value: _ } => text.append( + | BasicValue::SaveBytes { path, value: _ } => text.append( &format!("Saving to file \"{}\"", path), 0., Default::default(), ), diff --git a/reaction_systems_gui/src/app_logic.rs b/reaction_systems_gui/src/app_logic.rs index 9cb913c..94f34de 100644 --- a/reaction_systems_gui/src/app_logic.rs +++ b/reaction_systems_gui/src/app_logic.rs @@ -108,11 +108,14 @@ fn generate_to_evaluate( for n_id in dependencies { let mut input_hashes = vec![]; - if let NodeInstruction::SaveString = graph[n_id].user_data.template { - res.push(n_id); - invalid_ids.insert(n_id); - outputs_cache.invalidate_outputs(graph, n_id); - continue; + match graph[n_id].user_data.template { + NodeInstruction::SaveString | NodeInstruction::SaveSvg => { + res.push(n_id); + invalid_ids.insert(n_id); + outputs_cache.invalidate_outputs(graph, n_id); + continue; + }, + _ => {} } let first_output = @@ -848,7 +851,7 @@ fn process_template( BasicValue::Path { value: path }, BasicValue::String { value }, ) => { - *to_ret = Some(BasicValue::SaveString { path, value }); + *to_ret = Some(BasicValue::SaveBytes { path, value: value.into() }); }, | (BasicValue::Path { .. }, _) => { anyhow::bail!("Not a string"); @@ -2513,6 +2516,36 @@ fn process_template( anyhow::bail!("Not a string"); } }, + | NodeInstruction::SaveSvg => { + let (path, svg) = retrieve_from_cache![2]; + + match (path, svg) { + | ( + BasicValue::Path { value: path }, + BasicValue::Svg { value }, + ) => { + let mut path = path.to_string(); + if !path.ends_with(".png") { + path.push_str(".png"); + } + let svg = match value.rasterize() { + Ok(svg) => svg, + Err(e) => anyhow::bail!(e), + }; + let raw = svg.into_raw(); + *to_ret = Some(BasicValue::SaveBytes { path, value: raw }); + }, + | (BasicValue::Path { .. }, _) => { + anyhow::bail!("Not an svg"); + }, + | (_, BasicValue::Svg { .. }) => { + anyhow::bail!("Not a path"); + }, + | (_, _) => { + anyhow::bail!("Values of wrong type"); + }, + } + } } Ok(None) } diff --git a/reaction_systems_gui/src/lib.rs b/reaction_systems_gui/src/lib.rs index 7bfd0ba..dd7c873 100644 --- a/reaction_systems_gui/src/lib.rs +++ b/reaction_systems_gui/src/lib.rs @@ -1,4 +1,4 @@ -#![forbid(unsafe_code)] +#![deny(unsafe_code)] #![warn(clippy::all, rust_2018_idioms)] // Forbid warnings in release builds #![cfg_attr(not(debug_assertions), deny(warnings))] diff --git a/reaction_systems_gui/src/svg.rs b/reaction_systems_gui/src/svg.rs index 38cb37e..983feef 100644 --- a/reaction_systems_gui/src/svg.rs +++ b/reaction_systems_gui/src/svg.rs @@ -13,6 +13,8 @@ pub(crate) struct Svg { /// original size of the svg svg_size: egui::Vec2, + original: String, + #[cfg_attr(feature = "persistence", serde(skip))] svg_texture: Arc>>, } @@ -53,7 +55,10 @@ impl Svg { let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], &data); - let svg = Svg { image, svg_size, svg_texture: Arc::new(Mutex::new(None)) }; + let svg = Svg { image, + original: content, + svg_size, + svg_texture: Arc::new(Mutex::new(None)) }; Ok(svg) } @@ -69,6 +74,19 @@ impl Svg { svg_texture } } + + pub(crate) fn rasterize(&self) -> Result, Vec>, String> { + let svg = match nsvg::parse_str(&self.original, nsvg::Units::Pixel, 96.0) { + Ok(svg) => svg, + Err(nsvg_err) => return Err(format!("{}", nsvg_err)), + }; + let data = match svg.rasterize(1.) { + Ok(o) => o, + Err(e) => return Err(format!("{}", e)), + }; + + Ok(data) + } } impl Debug for Svg { @@ -105,6 +123,7 @@ impl Hash for Svg { hash_float!(self.svg_size.y); self.image.pixels.hash(state); self.image.size.hash(state); + self.original.hash(state); hash_float!(self.image.source_size.x); hash_float!(self.image.source_size.y); self.svg_texture.lock().expect("Poisoned").hash(state);