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 pub theme: SvgLayoutTheme,
16 pub surrogate: bool,
18 pub highlight_collisions: bool,
20 pub draw_cd_shapes: bool,
22 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), Color(0xFF, 0x00, 0x00), Color(0xFF, 0x5E, 0x00), Color(0xFF, 0xA5, 0x00), Color(0xC7, 0xA9, 0x00), Color(0xFF, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), ],
73 qz_stroke_opac: 0.5,
74 collision_highlight_color: Color(0x00, 0xFF, 0x00), };
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), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), ],
94 qz_stroke_opac: 0.9,
95 collision_highlight_color: Color(0xD0, 0x00, 0x00), };
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 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}