1use crate::interpreter::graphics::par::{FontFace, LineType};
11
12#[derive(Clone, Debug, Default)]
20pub struct Gpar {
21 pub col: Option<[u8; 4]>,
23 pub fill: Option<[u8; 4]>,
25 pub lwd: Option<f64>,
27 pub lty: Option<LineType>,
29 pub fontsize: Option<f64>,
31 pub lineheight: Option<f64>,
33 pub font: Option<FontFace>,
35 pub fontfamily: Option<String>,
37 pub cex: Option<f64>,
39 pub alpha: Option<f64>,
41 pub lineend: Option<LineEnd>,
43 pub linejoin: Option<LineJoin>,
45 pub linemitre: Option<f64>,
47 pub just_x: Option<f64>,
49 pub just_y: Option<f64>,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum LineEnd {
56 Round,
57 Butt,
58 Square,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum LineJoin {
64 Round,
65 Mitre,
66 Bevel,
67}
68
69const BLACK_RGBA: [u8; 4] = [0, 0, 0, 255];
71const TRANSPARENT_RGBA: [u8; 4] = [255, 255, 255, 0];
72
73impl Gpar {
74 pub fn new() -> Self {
76 Gpar::default()
77 }
78
79 pub fn inherit_from(&mut self, parent: &Gpar) {
84 if self.col.is_none() {
85 self.col = parent.col;
86 }
87 if self.fill.is_none() {
88 self.fill = parent.fill;
89 }
90 if self.lwd.is_none() {
91 self.lwd = parent.lwd;
92 }
93 if self.lty.is_none() {
94 self.lty = parent.lty;
95 }
96 if self.fontsize.is_none() {
97 self.fontsize = parent.fontsize;
98 }
99 if self.lineheight.is_none() {
100 self.lineheight = parent.lineheight;
101 }
102 if self.font.is_none() {
103 self.font = parent.font;
104 }
105 if self.fontfamily.is_none() {
106 self.fontfamily.clone_from(&parent.fontfamily);
107 }
108 if self.cex.is_none() {
109 self.cex = parent.cex;
110 }
111 if self.alpha.is_none() {
112 self.alpha = parent.alpha;
113 }
114 if self.lineend.is_none() {
115 self.lineend = parent.lineend;
116 }
117 if self.linejoin.is_none() {
118 self.linejoin = parent.linejoin;
119 }
120 if self.linemitre.is_none() {
121 self.linemitre = parent.linemitre;
122 }
123 if self.just_x.is_none() {
124 self.just_x = parent.just_x;
125 }
126 if self.just_y.is_none() {
127 self.just_y = parent.just_y;
128 }
129 }
130
131 pub fn with_parent(&self, parent: &Gpar) -> Gpar {
133 let mut result = self.clone();
134 result.inherit_from(parent);
135 result
136 }
137
138 pub fn effective_col(&self) -> [u8; 4] {
140 self.col.unwrap_or(BLACK_RGBA)
141 }
142
143 pub fn effective_fill(&self) -> [u8; 4] {
145 self.fill.unwrap_or(TRANSPARENT_RGBA)
146 }
147
148 pub fn effective_lwd(&self) -> f64 {
150 self.lwd.unwrap_or(1.0)
151 }
152
153 pub fn effective_lty(&self) -> LineType {
155 self.lty.unwrap_or(LineType::Solid)
156 }
157
158 pub fn effective_fontsize(&self) -> f64 {
160 self.fontsize.unwrap_or(12.0)
161 }
162
163 pub fn effective_lineheight(&self) -> f64 {
165 self.lineheight.unwrap_or(1.2)
166 }
167
168 pub fn effective_font(&self) -> FontFace {
170 self.font.unwrap_or(FontFace::Plain)
171 }
172
173 pub fn effective_fontfamily(&self) -> &str {
175 self.fontfamily.as_deref().unwrap_or("sans")
176 }
177
178 pub fn effective_cex(&self) -> f64 {
180 self.cex.unwrap_or(1.0)
181 }
182
183 pub fn effective_alpha(&self) -> f64 {
185 self.alpha.unwrap_or(1.0)
186 }
187
188 pub fn effective_lineend(&self) -> LineEnd {
190 self.lineend.unwrap_or(LineEnd::Round)
191 }
192
193 pub fn effective_linejoin(&self) -> LineJoin {
195 self.linejoin.unwrap_or(LineJoin::Round)
196 }
197
198 pub fn effective_linemitre(&self) -> f64 {
200 self.linemitre.unwrap_or(10.0)
201 }
202}
203
204#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn gpar_defaults() {
212 let g = Gpar::new();
213 assert_eq!(g.effective_col(), BLACK_RGBA);
214 assert_eq!(g.effective_fill(), TRANSPARENT_RGBA);
215 assert!((g.effective_lwd() - 1.0).abs() < f64::EPSILON);
216 assert!((g.effective_fontsize() - 12.0).abs() < f64::EPSILON);
217 assert!((g.effective_lineheight() - 1.2).abs() < f64::EPSILON);
218 assert_eq!(g.effective_font(), FontFace::Plain);
219 assert_eq!(g.effective_fontfamily(), "sans");
220 assert!((g.effective_cex() - 1.0).abs() < f64::EPSILON);
221 assert!((g.effective_alpha() - 1.0).abs() < f64::EPSILON);
222 assert_eq!(g.effective_lty(), LineType::Solid);
223 assert_eq!(g.effective_lineend(), LineEnd::Round);
224 assert_eq!(g.effective_linejoin(), LineJoin::Round);
225 assert!((g.effective_linemitre() - 10.0).abs() < f64::EPSILON);
226 }
227
228 #[test]
229 fn gpar_inherit_fills_none_fields() {
230 let parent = Gpar {
231 col: Some([255, 0, 0, 255]),
232 fontsize: Some(16.0),
233 lwd: Some(2.5),
234 fontfamily: Some("serif".to_string()),
235 ..Default::default()
236 };
237
238 let mut child = Gpar {
239 fontsize: Some(10.0), ..Default::default()
241 };
242
243 child.inherit_from(&parent);
244
245 assert_eq!(child.col, Some([255, 0, 0, 255]));
247 assert_eq!(child.lwd, Some(2.5));
248 assert_eq!(child.fontfamily, Some("serif".to_string()));
249
250 assert_eq!(child.fontsize, Some(10.0));
252 }
253
254 #[test]
255 fn gpar_with_parent_does_not_mutate_original() {
256 let parent = Gpar {
257 col: Some([0, 255, 0, 255]),
258 ..Default::default()
259 };
260 let child = Gpar {
261 lwd: Some(3.0),
262 ..Default::default()
263 };
264
265 let resolved = child.with_parent(&parent);
266
267 assert_eq!(resolved.col, Some([0, 255, 0, 255]));
269 assert_eq!(resolved.lwd, Some(3.0));
270
271 assert!(child.col.is_none());
273 }
274
275 #[test]
276 fn gpar_child_overrides_parent() {
277 let parent = Gpar {
278 col: Some([255, 0, 0, 255]),
279 fontsize: Some(16.0),
280 ..Default::default()
281 };
282
283 let child = Gpar {
284 col: Some([0, 0, 255, 255]),
285 ..Default::default()
286 };
287
288 let resolved = child.with_parent(&parent);
289
290 assert_eq!(resolved.col, Some([0, 0, 255, 255]));
292 assert_eq!(resolved.fontsize, Some(16.0));
294 }
295}