Skip to main content

easyofd_core/page_description/
area.rs

1//! 元素区域(ofd:Area)。
2//!
3//! 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.area.Area
4
5use crate::basic_type::ST_Array;
6use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
7
8/// 元素区域(ofd:Area),定义绘制参数、变换矩阵与裁剪。
9///
10/// 对应 Java: ofdrw Area。
11#[derive(Debug, Clone, PartialEq, Default)]
12pub struct Area {
13    /// 绘制参数引用(ST_RefID,可选)。
14    pub draw_param: Option<u32>,
15    /// 变换矩阵(ST_Array,6 元素 a b c d e f,可选)。
16    pub ctm: Option<ST_Array>,
17    /// 裁剪区域(原始 XML 内容,可选)。
18    pub clip: Option<String>,
19}
20
21impl Area {
22    /// 创建空区域(对应 Java: Area())。
23    #[must_use]
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// 设置绘制参数引用(对应 Java: Area#setDrawParam)。
29    #[must_use]
30    pub fn draw_param(mut self, id: u32) -> Self {
31        self.draw_param = Some(id);
32        self
33    }
34
35    /// 设置变换矩阵(对应 Java: Area#setCTM)。
36    #[must_use]
37    pub fn ctm(mut self, ctm: ST_Array) -> Self {
38        self.ctm = Some(ctm);
39        self
40    }
41
42    /// 设置裁剪区域(对应 Java: Area#setClipObj)。
43    #[must_use]
44    pub fn clip(mut self, clip: impl Into<String>) -> Self {
45        self.clip = Some(clip.into());
46        self
47    }
48}
49
50impl XmlElement for Area {
51    /// 对应 Java: Area 元素名 "Area"。
52    fn element_name(&self) -> &'static str {
53        "Area"
54    }
55
56    fn attributes(&self) -> Vec<(String, String)> {
57        let mut attrs = Vec::new();
58        if let Some(dp) = self.draw_param {
59            attrs.push(("DrawParam".to_string(), dp.to_string()));
60        }
61        if let Some(ref ctm) = self.ctm {
62            attrs.push(("CTM".to_string(), ctm.to_xml_string()));
63        }
64        attrs
65    }
66
67    fn child_nodes(&self) -> Vec<XmlNode> {
68        let mut children = Vec::new();
69        if let Some(ref clip) = self.clip {
70            let mut clip_node = XmlNode::element("Clip");
71            clip_node.push_child(XmlNode::text_node(clip.clone()));
72            children.push(clip_node);
73        }
74        children
75    }
76
77    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
78        let draw_param = node
79            .get_attr("DrawParam")
80            .map(|s| {
81                s.parse::<u32>()
82                    .map_err(|e| XmlElementError(format!("解析 Area.DrawParam 失败: {e}")))
83            })
84            .transpose()?;
85        let ctm = node
86            .get_attr("CTM")
87            .map(|s| {
88                ST_Array::from_str(s)
89                    .map_err(|e| XmlElementError(format!("解析 Area.CTM 失败: {e}")))
90            })
91            .transpose()?;
92        let clip = node.child("Clip").and_then(|c| c.text.clone());
93        Ok(Self {
94            draw_param,
95            ctm,
96            clip,
97        })
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::xml_parse::parse_xml_to_nodes;
105
106    #[test]
107    fn test_area_default() {
108        let a = Area::new();
109        assert!(a.draw_param.is_none());
110        assert!(a.ctm.is_none());
111    }
112
113    #[test]
114    fn test_area_builders() {
115        let a = Area::new()
116            .draw_param(5)
117            .ctm(ST_Array::transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0))
118            .clip("M0 0 L10 0 L10 10 Z");
119        assert_eq!(a.draw_param, Some(5));
120        assert!(a.ctm.is_some());
121        assert_eq!(a.clip.as_deref(), Some("M0 0 L10 0 L10 10 Z"));
122    }
123
124    #[test]
125    fn test_xml_element_name() {
126        let a = Area::new();
127        assert_eq!(a.element_name(), "Area");
128    }
129
130    #[test]
131    fn test_xml_element_to_xml_contains_attrs() {
132        let a = Area::new()
133            .draw_param(3)
134            .ctm(ST_Array::transform(1.0, 0.0, 0.0, 1.0, 10.0, 20.0));
135        let xml = a.to_xml();
136        assert!(xml.contains("DrawParam=\"3\""));
137        assert!(xml.contains("CTM=\""));
138    }
139
140    #[test]
141    fn test_xml_element_roundtrip_full() {
142        let a = Area::new()
143            .draw_param(5)
144            .ctm(ST_Array::transform(1.0, 0.0, 0.0, 1.0, 10.0, 20.0))
145            .clip("M0 0 L10 0 L10 10 Z");
146        let xml = a.to_xml();
147        let node = parse_xml_to_nodes(&xml).unwrap();
148        let a2 = Area::from_xml(&node).unwrap();
149        assert_eq!(a, a2);
150    }
151
152    #[test]
153    fn test_xml_element_roundtrip_empty() {
154        let a = Area::new();
155        let xml = a.to_xml();
156        assert_eq!(xml, "<Area/>");
157        let node = parse_xml_to_nodes(&xml).unwrap();
158        let a2 = Area::from_xml(&node).unwrap();
159        assert_eq!(a, a2);
160    }
161}