Skip to main content

easyofd_core/page_description/
draw_param.rs

1//! 绘制参数类型。
2//!
3//! 对应 Java: org.ofdrw.core.pageDescription.drawParam.CT_DrawParam
4
5use crate::basic_type::{ST_Array, ST_ID};
6use crate::page_description::color::CT_Color;
7use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
8
9/// 绘制参数。
10///
11/// 对应 Java: org.ofdrw.core.pageDescription.drawParam.CT_DrawParam
12#[allow(non_camel_case_types)]
13#[derive(Debug, Clone, PartialEq)]
14pub struct CT_DrawParam {
15    /// 对象 ID
16    id: Option<ST_ID>,
17    /// 线宽
18    line_width: Option<f64>,
19    /// 线端帽
20    line_cap: Option<LineCap>,
21    /// 线连接样式
22    line_join: Option<LineJoin>,
23    /// 虚线偏移
24    dash_offset: Option<f64>,
25    /// 虚线模式
26    dash_pattern: Option<ST_Array>,
27    /// 斜接限制
28    miter_limit: Option<f64>,
29    /// 填充颜色
30    fill_color: Option<CT_Color>,
31    /// 描边颜色
32    stroke_color: Option<CT_Color>,
33    /// 变换矩阵
34    transform: Option<ST_Array>,
35}
36
37/// 线端帽样式
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum LineCap {
40    /// 平头
41    Butt,
42    /// 圆头
43    Round,
44    /// 方头
45    Square,
46}
47
48/// 线连接样式
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum LineJoin {
51    /// 尖角
52    Miter,
53    /// 圆角
54    Round,
55    /// 平角
56    Bevel,
57}
58
59impl CT_DrawParam {
60    /// 创建空绘制参数。
61    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    /// 设置对象 ID。
77    pub fn set_id(&mut self, id: ST_ID) -> &mut Self {
78        self.id = Some(id);
79        self
80    }
81
82    /// 获取对象 ID。
83    pub fn id(&self) -> Option<ST_ID> {
84        self.id
85    }
86
87    /// 设置线宽。
88    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
89        self.line_width = Some(width);
90        self
91    }
92
93    /// 获取线宽。
94    pub fn line_width(&self) -> Option<f64> {
95        self.line_width
96    }
97
98    /// 设置线端帽样式。
99    pub fn set_line_cap(&mut self, line_cap: LineCap) -> &mut Self {
100        self.line_cap = Some(line_cap);
101        self
102    }
103
104    /// 获取线端帽样式。
105    pub fn line_cap(&self) -> Option<LineCap> {
106        self.line_cap
107    }
108
109    /// 设置线连接样式。
110    pub fn set_line_join(&mut self, line_join: LineJoin) -> &mut Self {
111        self.line_join = Some(line_join);
112        self
113    }
114
115    /// 获取线连接样式。
116    pub fn line_join(&self) -> Option<LineJoin> {
117        self.line_join
118    }
119
120    /// 设置虚线偏移。
121    pub fn set_dash_offset(&mut self, offset: f64) -> &mut Self {
122        self.dash_offset = Some(offset);
123        self
124    }
125
126    /// 获取虚线偏移。
127    pub fn dash_offset(&self) -> Option<f64> {
128        self.dash_offset
129    }
130
131    /// 设置虚线模式。
132    pub fn set_dash_pattern(&mut self, pattern: ST_Array) -> &mut Self {
133        self.dash_pattern = Some(pattern);
134        self
135    }
136
137    /// 获取虚线模式。
138    pub fn dash_pattern(&self) -> Option<&ST_Array> {
139        self.dash_pattern.as_ref()
140    }
141
142    /// 设置斜接限制。
143    pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
144        self.miter_limit = Some(limit);
145        self
146    }
147
148    /// 获取斜接限制。
149    pub fn miter_limit(&self) -> Option<f64> {
150        self.miter_limit
151    }
152
153    /// 设置填充颜色。
154    pub fn set_fill_color(&mut self, color: CT_Color) -> &mut Self {
155        self.fill_color = Some(color);
156        self
157    }
158
159    /// 获取填充颜色。
160    pub fn fill_color(&self) -> Option<&CT_Color> {
161        self.fill_color.as_ref()
162    }
163
164    /// 设置描边颜色。
165    pub fn set_stroke_color(&mut self, color: CT_Color) -> &mut Self {
166        self.stroke_color = Some(color);
167        self
168    }
169
170    /// 获取描边颜色。
171    pub fn stroke_color(&self) -> Option<&CT_Color> {
172        self.stroke_color.as_ref()
173    }
174
175    /// 设置变换矩阵。
176    pub fn set_transform(&mut self, transform: ST_Array) -> &mut Self {
177        self.transform = Some(transform);
178        self
179    }
180
181    /// 获取变换矩阵。
182    pub fn transform(&self) -> Option<&ST_Array> {
183        self.transform.as_ref()
184    }
185
186    /// 序列化为 OFD XML 字符串表示。
187    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    /// 从字符串解析 CT_DrawParam(简化格式:line_width)。
202    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    /// 对应 Java: CT_DrawParam 元素名 "DrawParam"。
224    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}