Skip to main content

jagua_rs/io/svg/
svg_util.rs

1use crate::entities::N_QUALITIES;
2use crate::geometry;
3use crate::geometry::OriginalShape;
4use crate::geometry::primitives::{Edge, Point, SPolygon};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::fmt::{Display, Formatter};
7use svg::node::element::path::Data;
8use svg::node::element::{Circle, Path};
9
10#[allow(clippy::struct_excessive_bools)]
11#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Copy)]
12#[serde(default)]
13pub struct SvgDrawOptions {
14    ///The theme to use for the svg
15    pub theme: SvgLayoutTheme,
16    ///Draw the fail fast surrogate on top of each item
17    pub surrogate: bool,
18    ///Draw dashed lines between colliding items
19    pub highlight_collisions: bool,
20    ///Draw the modified shapes used internally instead of the original ones
21    pub draw_cd_shapes: bool,
22    ///Highlights the shapes used for collision detection with a dashed border
23    pub highlight_cd_shapes: bool,
24}
25
26impl Default for SvgDrawOptions {
27    fn default() -> Self {
28        Self {
29            theme: SvgLayoutTheme::default(),
30            surrogate: false,
31            highlight_collisions: true,
32            draw_cd_shapes: false,
33            highlight_cd_shapes: true,
34        }
35    }
36}
37
38#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Copy)]
39pub struct SvgLayoutTheme {
40    pub stroke_width_multiplier: f32,
41    pub container_fill: Color,
42    pub item_fill: Color,
43    pub hole_fill: Color,
44    pub qz_fill: [Color; N_QUALITIES],
45    pub qz_stroke_opac: f32,
46    pub collision_highlight_color: Color,
47}
48
49impl Default for SvgLayoutTheme {
50    fn default() -> Self {
51        SvgLayoutTheme::EARTH_TONES
52    }
53}
54
55impl SvgLayoutTheme {
56    pub const EARTH_TONES: SvgLayoutTheme = SvgLayoutTheme {
57        stroke_width_multiplier: 2.0,
58        container_fill: Color(0xCC, 0x82, 0x4A),
59        item_fill: Color(0xFF, 0xC8, 0x79),
60        hole_fill: Color(0x2D, 0x2D, 0x2D),
61        qz_fill: [
62            Color(0x00, 0x00, 0x00), // BLACK
63            Color(0xFF, 0x00, 0x00), // RED
64            Color(0xFF, 0x5E, 0x00), // ORANGE
65            Color(0xFF, 0xA5, 0x00), // LIGHT ORANGE
66            Color(0xC7, 0xA9, 0x00), // DARK YELLOW
67            Color(0xFF, 0xFF, 0x00), // YELLOW
68            Color(0xCB, 0xFF, 0x00), // GREEN
69            Color(0xCB, 0xFF, 0x00), // GREEN
70            Color(0xCB, 0xFF, 0x00), // GREEN
71            Color(0xCB, 0xFF, 0x00), // GREEN
72        ],
73        qz_stroke_opac: 0.5,
74        collision_highlight_color: Color(0x00, 0xFF, 0x00), // LIME
75    };
76
77    pub const GRAY: SvgLayoutTheme = SvgLayoutTheme {
78        stroke_width_multiplier: 2.5,
79        container_fill: Color(0xD3, 0xD3, 0xD3),
80        item_fill: Color(0x7A, 0x7A, 0x7A),
81        hole_fill: Color(0xFF, 0xFF, 0xFF),
82        qz_fill: [
83            Color(0x63, 0x63, 0x63), //GRAY
84            Color(0x63, 0x63, 0x63), //GRAY
85            Color(0x63, 0x63, 0x63), //GRAY
86            Color(0x63, 0x63, 0x63), //GRAY
87            Color(0x63, 0x63, 0x63), //GRAY
88            Color(0x63, 0x63, 0x63), //GRAY
89            Color(0x63, 0x63, 0x63), //GRAY
90            Color(0x63, 0x63, 0x63), //GRAY
91            Color(0x63, 0x63, 0x63), //GRAY
92            Color(0x63, 0x63, 0x63), //GRAY
93        ],
94        qz_stroke_opac: 0.9,
95        collision_highlight_color: Color(0xD0, 0x00, 0x00), //LIME
96    };
97}
98
99#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
100pub fn change_brightness(color: Color, fraction: f32) -> Color {
101    let Color(r, g, b) = color;
102
103    let r = (f32::from(r) * fraction) as u8;
104    let g = (f32::from(g) * fraction) as u8;
105    let b = (f32::from(b) * fraction) as u8;
106    Color(r, g, b)
107}
108
109#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
110pub fn blend_colors(color_1: Color, color_2: Color) -> Color {
111    //blend color_1 and color_2
112    let Color(r_1, g_1, b_1) = color_1;
113    let Color(r_2, g_2, b_2) = color_2;
114
115    let r = ((f32::from(r_1) * 0.5) + (f32::from(r_2) * 0.5)) as u8;
116    let g = ((f32::from(g_1) * 0.5) + (f32::from(g_2) * 0.5)) as u8;
117    let b = ((f32::from(b_1) * 0.5) + (f32::from(b_2) * 0.5)) as u8;
118
119    Color(r, g, b)
120}
121
122#[derive(Copy, Clone, PartialEq, Debug)]
123pub struct Color(u8, u8, u8);
124
125impl Display for Color {
126    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127        write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
128    }
129}
130
131impl From<String> for Color {
132    fn from(mut s: String) -> Self {
133        if s.starts_with('#') {
134            s.remove(0);
135        }
136        let r = u8::from_str_radix(&s[0..2], 16).unwrap();
137        let g = u8::from_str_radix(&s[2..4], 16).unwrap();
138        let b = u8::from_str_radix(&s[4..6], 16).unwrap();
139        Color(r, g, b)
140    }
141}
142
143impl From<&str> for Color {
144    fn from(s: &str) -> Self {
145        Color::from(s.to_owned())
146    }
147}
148
149impl Serialize for Color {
150    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
151    where
152        S: Serializer,
153    {
154        serializer.serialize_str(&format!("{self}"))
155    }
156}
157
158impl<'de> Deserialize<'de> for Color {
159    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
160    where
161        D: Deserializer<'de>,
162    {
163        let s = String::deserialize(deserializer)?;
164        Ok(Color::from(s))
165    }
166}
167
168pub fn original_shape_data(
169    original: &OriginalShape,
170    internal: &SPolygon,
171    draw_internal: bool,
172) -> Data {
173    if draw_internal {
174        simple_polygon_data(internal)
175    } else {
176        simple_polygon_data(&original.shape)
177    }
178}
179
180pub fn simple_polygon_data(s_poly: &SPolygon) -> Data {
181    let mut data = Data::new().move_to::<(f32, f32)>(s_poly.vertex(0).into());
182    for i in 1..s_poly.n_vertices() {
183        data = data.line_to::<(f32, f32)>(s_poly.vertex(i).into());
184    }
185    data.close()
186}
187
188pub fn data_to_path(data: Data, params: &[(&str, &str)]) -> Path {
189    let mut path = Path::new();
190    for param in params {
191        path = path.set(param.0, param.1);
192    }
193    path.set("d", data)
194}
195
196pub fn point(Point(x, y): Point, fill: Option<&str>, rad: Option<f32>) -> Circle {
197    Circle::new()
198        .set("cx", x)
199        .set("cy", y)
200        .set("r", rad.unwrap_or(0.5))
201        .set("fill", fill.unwrap_or("black"))
202}
203
204pub fn circle(circle: geometry::primitives::Circle, params: &[(&str, &str)]) -> Circle {
205    let mut circle = Circle::new()
206        .set("cx", circle.center.0)
207        .set("cy", circle.center.1)
208        .set("r", circle.radius);
209    for param in params {
210        circle = circle.set(param.0, param.1);
211    }
212    circle
213}
214
215pub fn edge_data(edge: Edge) -> Data {
216    Data::new()
217        .move_to((edge.start.0, edge.start.1))
218        .line_to((edge.end.0, edge.end.1))
219}
220
221#[allow(dead_code)]
222pub fn aa_rect_data(rect: geometry::primitives::Rect) -> Data {
223    Data::new()
224        .move_to((rect.x_min, rect.y_min))
225        .line_to((rect.x_max, rect.y_min))
226        .line_to((rect.x_max, rect.y_max))
227        .line_to((rect.x_min, rect.y_max))
228        .close()
229}