easyofd_core/page_description/
draw_param.rs1use crate::basic_type::{ST_Array, ST_ID};
6use crate::page_description::color::CT_Color;
7use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
8
9#[allow(non_camel_case_types)]
13#[derive(Debug, Clone, PartialEq)]
14pub struct CT_DrawParam {
15 id: Option<ST_ID>,
17 line_width: Option<f64>,
19 line_cap: Option<LineCap>,
21 line_join: Option<LineJoin>,
23 dash_offset: Option<f64>,
25 dash_pattern: Option<ST_Array>,
27 miter_limit: Option<f64>,
29 fill_color: Option<CT_Color>,
31 stroke_color: Option<CT_Color>,
33 transform: Option<ST_Array>,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum LineCap {
40 Butt,
42 Round,
44 Square,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum LineJoin {
51 Miter,
53 Round,
55 Bevel,
57}
58
59impl CT_DrawParam {
60 pub fn new() -> Self {
62 Self {
63 id: None,
64 line_width: None,
65 line_cap: None,
66 line_join: None,
67 dash_offset: None,
68 dash_pattern: None,
69 miter_limit: None,
70 fill_color: None,
71 stroke_color: None,
72 transform: None,
73 }
74 }
75
76 pub fn set_id(&mut self, id: ST_ID) -> &mut Self {
78 self.id = Some(id);
79 self
80 }
81
82 pub fn id(&self) -> Option<ST_ID> {
84 self.id
85 }
86
87 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
89 self.line_width = Some(width);
90 self
91 }
92
93 pub fn line_width(&self) -> Option<f64> {
95 self.line_width
96 }
97
98 pub fn set_line_cap(&mut self, line_cap: LineCap) -> &mut Self {
100 self.line_cap = Some(line_cap);
101 self
102 }
103
104 pub fn line_cap(&self) -> Option<LineCap> {
106 self.line_cap
107 }
108
109 pub fn set_line_join(&mut self, line_join: LineJoin) -> &mut Self {
111 self.line_join = Some(line_join);
112 self
113 }
114
115 pub fn line_join(&self) -> Option<LineJoin> {
117 self.line_join
118 }
119
120 pub fn set_dash_offset(&mut self, offset: f64) -> &mut Self {
122 self.dash_offset = Some(offset);
123 self
124 }
125
126 pub fn dash_offset(&self) -> Option<f64> {
128 self.dash_offset
129 }
130
131 pub fn set_dash_pattern(&mut self, pattern: ST_Array) -> &mut Self {
133 self.dash_pattern = Some(pattern);
134 self
135 }
136
137 pub fn dash_pattern(&self) -> Option<&ST_Array> {
139 self.dash_pattern.as_ref()
140 }
141
142 pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
144 self.miter_limit = Some(limit);
145 self
146 }
147
148 pub fn miter_limit(&self) -> Option<f64> {
150 self.miter_limit
151 }
152
153 pub fn set_fill_color(&mut self, color: CT_Color) -> &mut Self {
155 self.fill_color = Some(color);
156 self
157 }
158
159 pub fn fill_color(&self) -> Option<&CT_Color> {
161 self.fill_color.as_ref()
162 }
163
164 pub fn set_stroke_color(&mut self, color: CT_Color) -> &mut Self {
166 self.stroke_color = Some(color);
167 self
168 }
169
170 pub fn stroke_color(&self) -> Option<&CT_Color> {
172 self.stroke_color.as_ref()
173 }
174
175 pub fn set_transform(&mut self, transform: ST_Array) -> &mut Self {
177 self.transform = Some(transform);
178 self
179 }
180
181 pub fn transform(&self) -> Option<&ST_Array> {
183 self.transform.as_ref()
184 }
185
186 pub fn to_xml_string(&self) -> String {
188 let mut attrs = Vec::new();
189 if let Some(id) = self.id {
190 attrs.push(format!("ID=\"{}\"", id.to_xml_string()));
191 }
192 if let Some(lw) = self.line_width {
193 attrs.push(format!("LineWidth=\"{lw}\""));
194 }
195 if let Some(ml) = self.miter_limit {
196 attrs.push(format!("MiterLimit=\"{ml}\""));
197 }
198 format!("<DrawParam {} />", attrs.join(" "))
199 }
200
201 pub fn from_str(s: &str) -> Result<Self, String> {
203 let s = s.trim();
204 if s.is_empty() {
205 return Err("CT_DrawParam 不能为空".to_string());
206 }
207 let width: f64 = s
208 .parse()
209 .map_err(|e| format!("解析 line_width 失败: {e}"))?;
210 let mut dp = Self::new();
211 dp.set_line_width(width);
212 Ok(dp)
213 }
214}
215
216impl Default for CT_DrawParam {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222impl XmlElement for CT_DrawParam {
223 fn element_name(&self) -> &'static str {
225 "DrawParam"
226 }
227
228 fn attributes(&self) -> Vec<(String, String)> {
229 let mut attrs = Vec::new();
230 if let Some(id) = self.id {
231 attrs.push(("ID".to_string(), id.to_xml_string()));
232 }
233 if let Some(lw) = self.line_width {
234 attrs.push(("LineWidth".to_string(), lw.to_string()));
235 }
236 if let Some(lc) = self.line_cap {
237 attrs.push((
238 "LineCap".to_string(),
239 match lc {
240 LineCap::Butt => "Butt",
241 LineCap::Round => "Round",
242 LineCap::Square => "Square",
243 }
244 .to_string(),
245 ));
246 }
247 if let Some(lj) = self.line_join {
248 attrs.push((
249 "LineJoin".to_string(),
250 match lj {
251 LineJoin::Miter => "Miter",
252 LineJoin::Round => "Round",
253 LineJoin::Bevel => "Bevel",
254 }
255 .to_string(),
256 ));
257 }
258 if let Some(doff) = self.dash_offset {
259 attrs.push(("DashOffset".to_string(), doff.to_string()));
260 }
261 if let Some(ref dp) = self.dash_pattern {
262 attrs.push(("DashPattern".to_string(), dp.to_xml_string()));
263 }
264 if let Some(ml) = self.miter_limit {
265 attrs.push(("MiterLimit".to_string(), ml.to_string()));
266 }
267 if let Some(ref tf) = self.transform {
268 attrs.push(("Transform".to_string(), tf.to_xml_string()));
269 }
270 attrs
271 }
272
273 fn child_nodes(&self) -> Vec<XmlNode> {
274 let mut children = Vec::new();
275 if let Some(ref fc) = self.fill_color {
276 let mut node = XmlNode::element("FillColor");
277 for (k, v) in fc.attributes() {
278 node.attrs.push((k, v));
279 }
280 children.push(node);
281 }
282 if let Some(ref sc) = self.stroke_color {
283 let mut node = XmlNode::element("StrokeColor");
284 for (k, v) in sc.attributes() {
285 node.attrs.push((k, v));
286 }
287 children.push(node);
288 }
289 children
290 }
291
292 fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
293 let id = node
294 .get_attr("ID")
295 .map(|s| {
296 ST_ID::from_str(s)
297 .map_err(|e| XmlElementError(format!("解析 DrawParam.ID 失败: {e}")))
298 })
299 .transpose()?;
300 let line_width = node
301 .get_attr("LineWidth")
302 .map(|s| {
303 s.parse::<f64>()
304 .map_err(|e| XmlElementError(format!("解析 DrawParam.LineWidth 失败: {e}")))
305 })
306 .transpose()?;
307 let line_cap = node
308 .get_attr("LineCap")
309 .map(|s| match s {
310 "Butt" => Ok(LineCap::Butt),
311 "Round" => Ok(LineCap::Round),
312 "Square" => Ok(LineCap::Square),
313 other => Err(XmlElementError(format!("未知 LineCap 值: {other}"))),
314 })
315 .transpose()?;
316 let line_join = node
317 .get_attr("LineJoin")
318 .map(|s| match s {
319 "Miter" => Ok(LineJoin::Miter),
320 "Round" => Ok(LineJoin::Round),
321 "Bevel" => Ok(LineJoin::Bevel),
322 other => Err(XmlElementError(format!("未知 LineJoin 值: {other}"))),
323 })
324 .transpose()?;
325 let dash_offset = node
326 .get_attr("DashOffset")
327 .map(|s| {
328 s.parse::<f64>()
329 .map_err(|e| XmlElementError(format!("解析 DrawParam.DashOffset 失败: {e}")))
330 })
331 .transpose()?;
332 let dash_pattern = node
333 .get_attr("DashPattern")
334 .map(|s| {
335 ST_Array::from_str(s)
336 .map_err(|e| XmlElementError(format!("解析 DrawParam.DashPattern 失败: {e}")))
337 })
338 .transpose()?;
339 let miter_limit = node
340 .get_attr("MiterLimit")
341 .map(|s| {
342 s.parse::<f64>()
343 .map_err(|e| XmlElementError(format!("解析 DrawParam.MiterLimit 失败: {e}")))
344 })
345 .transpose()?;
346 let transform = node
347 .get_attr("Transform")
348 .map(|s| {
349 ST_Array::from_str(s)
350 .map_err(|e| XmlElementError(format!("解析 DrawParam.Transform 失败: {e}")))
351 })
352 .transpose()?;
353 let fill_color = node
354 .child("FillColor")
355 .map(CT_Color::from_xml)
356 .transpose()?;
357 let stroke_color = node
358 .child("StrokeColor")
359 .map(CT_Color::from_xml)
360 .transpose()?;
361 Ok(Self {
362 id,
363 line_width,
364 line_cap,
365 line_join,
366 dash_offset,
367 dash_pattern,
368 miter_limit,
369 fill_color,
370 stroke_color,
371 transform,
372 })
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::xml_parse::parse_xml_to_nodes;
380
381 #[test]
382 fn test_basic_creation() {
383 let mut dp = CT_DrawParam::new();
384 dp.set_line_width(2.0);
385 assert_eq!(dp.line_width(), Some(2.0));
386 }
387
388 #[test]
389 fn test_line_cap_and_join() {
390 let mut dp = CT_DrawParam::new();
391 dp.set_line_cap(LineCap::Round)
392 .set_line_join(LineJoin::Bevel);
393 assert_eq!(dp.line_cap(), Some(LineCap::Round));
394 assert_eq!(dp.line_join(), Some(LineJoin::Bevel));
395 }
396
397 #[test]
398 fn test_fill_and_stroke_color() {
399 let mut dp = CT_DrawParam::new();
400 dp.set_fill_color(CT_Color::rgb(255, 0, 0))
401 .set_stroke_color(CT_Color::rgb(0, 0, 0));
402 assert!(dp.fill_color().is_some());
403 assert!(dp.stroke_color().is_some());
404 }
405
406 #[test]
407 fn test_to_xml_string() {
408 let mut dp = CT_DrawParam::new();
409 dp.set_line_width(2.0);
410 let xml = dp.to_xml_string();
411 assert!(xml.contains("DrawParam"));
412 assert!(xml.contains("LineWidth"));
413 }
414
415 #[test]
416 fn test_from_str() {
417 let dp = CT_DrawParam::from_str("2.0").unwrap();
418 assert_eq!(dp.line_width(), Some(2.0));
419 }
420
421 #[test]
422 fn test_from_str_empty() {
423 assert!(CT_DrawParam::from_str("").is_err());
424 }
425
426 #[test]
427 fn test_xml_element_name() {
428 let dp = CT_DrawParam::new();
429 assert_eq!(dp.element_name(), "DrawParam");
430 }
431
432 #[test]
433 fn test_xml_element_to_xml_attrs() {
434 let mut dp = CT_DrawParam::new();
435 dp.set_id(ST_ID::new(10).unwrap());
436 dp.set_line_width(2.5);
437 dp.set_line_cap(LineCap::Round);
438 dp.set_line_join(LineJoin::Bevel);
439 dp.set_miter_limit(4.0);
440 let xml = dp.to_xml();
441 assert!(xml.contains("ID=\"10\""));
442 assert!(xml.contains("LineWidth=\"2.5\""));
443 assert!(xml.contains("LineCap=\"Round\""));
444 assert!(xml.contains("LineJoin=\"Bevel\""));
445 assert!(xml.contains("MiterLimit=\"4\""));
446 }
447
448 #[test]
449 fn test_xml_element_roundtrip_basic() {
450 let mut dp = CT_DrawParam::new();
451 dp.set_id(ST_ID::new(5).unwrap());
452 dp.set_line_width(3.0);
453 dp.set_line_cap(LineCap::Square);
454 dp.set_line_join(LineJoin::Miter);
455 dp.set_miter_limit(10.0);
456 let xml = dp.to_xml();
457 let node = parse_xml_to_nodes(&xml).unwrap();
458 let dp2 = CT_DrawParam::from_xml(&node).unwrap();
459 assert_eq!(dp.id(), dp2.id());
460 assert_eq!(dp.line_width(), dp2.line_width());
461 assert_eq!(dp.line_cap(), dp2.line_cap());
462 assert_eq!(dp.line_join(), dp2.line_join());
463 assert_eq!(dp.miter_limit(), dp2.miter_limit());
464 }
465
466 #[test]
467 fn test_xml_element_roundtrip_with_colors() {
468 let mut dp = CT_DrawParam::new();
469 dp.set_line_width(1.5);
470 dp.set_fill_color(CT_Color::rgb(255, 0, 0));
471 dp.set_stroke_color(CT_Color::rgb(0, 0, 255));
472 let xml = dp.to_xml();
473 assert!(xml.contains("FillColor"));
474 assert!(xml.contains("StrokeColor"));
475 let node = parse_xml_to_nodes(&xml).unwrap();
476 let dp2 = CT_DrawParam::from_xml(&node).unwrap();
477 assert_eq!(dp.line_width(), dp2.line_width());
478 assert!(dp2.fill_color().is_some());
479 assert!(dp2.stroke_color().is_some());
480 }
481
482 #[test]
483 fn test_xml_element_roundtrip_empty() {
484 let dp = CT_DrawParam::new();
485 let xml = dp.to_xml();
486 assert_eq!(xml, "<DrawParam/>");
487 let node = parse_xml_to_nodes(&xml).unwrap();
488 let dp2 = CT_DrawParam::from_xml(&node).unwrap();
489 assert_eq!(dp.line_width(), dp2.line_width());
490 }
491}