Skip to main content

easyofd_core/page_description/
clips.rs

1//! 裁剪区域类型。
2//!
3//! 对应 Java: org.ofdrw.core.pageDescription.clips.CT_Clip
4
5use crate::basic_type::ST_Array;
6use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
7
8/// 裁剪区域。
9///
10/// 对应 Java: org.ofdrw.core.pageDescription.clips.CT_Clip
11#[allow(non_camel_case_types)]
12#[derive(Debug, Clone, PartialEq)]
13pub struct CT_Clip {
14    /// 裁剪路径
15    path: Vec<ClipPath>,
16}
17
18/// 裁剪路径
19#[derive(Debug, Clone, PartialEq)]
20pub struct ClipPath {
21    /// 路径数据
22    data: String,
23    /// 变换矩阵
24    transform: Option<ST_Array>,
25    /// 绘制参数
26    draw_param: Option<u64>,
27}
28
29impl CT_Clip {
30    /// 创建空裁剪区域。
31    pub fn new() -> Self {
32        Self { path: Vec::new() }
33    }
34
35    /// 添加裁剪路径。
36    pub fn add_path(&mut self, path: ClipPath) -> &mut Self {
37        self.path.push(path);
38        self
39    }
40
41    /// 获取裁剪路径列表。
42    pub fn paths(&self) -> &[ClipPath] {
43        &self.path
44    }
45
46    /// 序列化为 OFD XML 字符串表示。
47    pub fn to_xml_string(&self) -> String {
48        format!("<Clip />")
49    }
50
51    /// 从字符串解析 CT_Clip。
52    pub fn from_str(s: &str) -> Result<Self, String> {
53        let s = s.trim();
54        if s.is_empty() {
55            return Err("CT_Clip 不能为空".to_string());
56        }
57        Ok(Self::new())
58    }
59}
60
61impl ClipPath {
62    /// 创建裁剪路径。
63    pub fn new(data: &str) -> Self {
64        Self {
65            data: data.to_string(),
66            transform: None,
67            draw_param: None,
68        }
69    }
70
71    /// 获取路径数据。
72    pub fn data(&self) -> &str {
73        &self.data
74    }
75
76    /// 设置变换矩阵。
77    pub fn set_transform(&mut self, transform: ST_Array) -> &mut Self {
78        self.transform = Some(transform);
79        self
80    }
81
82    /// 获取变换矩阵。
83    pub fn transform(&self) -> Option<&ST_Array> {
84        self.transform.as_ref()
85    }
86
87    /// 设置绘制参数引用。
88    pub fn set_draw_param(&mut self, draw_param: u64) -> &mut Self {
89        self.draw_param = Some(draw_param);
90        self
91    }
92
93    /// 获取绘制参数引用。
94    pub fn draw_param(&self) -> Option<u64> {
95        self.draw_param
96    }
97}
98
99impl Default for CT_Clip {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl XmlElement for CT_Clip {
106    /// 对应 Java: CT_Clip 元素名 "Clip"。
107    fn element_name(&self) -> &'static str {
108        "Clip"
109    }
110
111    fn attributes(&self) -> Vec<(String, String)> {
112        Vec::new()
113    }
114
115    fn child_nodes(&self) -> Vec<XmlNode> {
116        self.path
117            .iter()
118            .map(|p| {
119                let mut node = XmlNode::element("Path");
120                node.attrs.push(("Data".to_string(), p.data.clone()));
121                if let Some(ref tf) = p.transform {
122                    node.attrs
123                        .push(("Transform".to_string(), tf.to_xml_string()));
124                }
125                if let Some(dp) = p.draw_param {
126                    node.attrs.push(("DrawParam".to_string(), dp.to_string()));
127                }
128                node
129            })
130            .collect()
131    }
132
133    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
134        let path = node
135            .children_named("Path")
136            .map(|child| {
137                let data = child
138                    .get_attr("Data")
139                    .ok_or_else(|| XmlElementError("Path 缺少 Data 属性".to_string()))?
140                    .to_string();
141                let transform = child
142                    .get_attr("Transform")
143                    .map(|s| {
144                        ST_Array::from_str(s)
145                            .map_err(|e| XmlElementError(format!("解析 Path.Transform 失败: {e}")))
146                    })
147                    .transpose()?;
148                let draw_param = child
149                    .get_attr("DrawParam")
150                    .map(|s| {
151                        s.parse::<u64>()
152                            .map_err(|e| XmlElementError(format!("解析 Path.DrawParam 失败: {e}")))
153                    })
154                    .transpose()?;
155                Ok(ClipPath {
156                    data,
157                    transform,
158                    draw_param,
159                })
160            })
161            .collect::<Result<Vec<_>, XmlElementError>>()?;
162        Ok(Self { path })
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::xml_parse::parse_xml_to_nodes;
170
171    #[test]
172    fn test_basic_creation() {
173        let clip = CT_Clip::new();
174        assert!(clip.paths().is_empty());
175    }
176
177    #[test]
178    fn test_add_path() {
179        let mut clip = CT_Clip::new();
180        clip.add_path(ClipPath::new("M 0 0 L 100 0 L 100 100 Z"));
181        assert_eq!(clip.paths().len(), 1);
182        assert_eq!(clip.paths()[0].data(), "M 0 0 L 100 0 L 100 100 Z");
183    }
184
185    #[test]
186    fn test_clip_path_transform() {
187        let mut path = ClipPath::new("M 0 0 L 100 0");
188        let transform = ST_Array::transform(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
189        path.set_transform(transform);
190        assert!(path.transform().is_some());
191    }
192
193    #[test]
194    fn test_to_xml_string() {
195        let clip = CT_Clip::new();
196        let xml = clip.to_xml_string();
197        assert!(xml.contains("Clip"));
198    }
199
200    #[test]
201    fn test_from_str() {
202        let clip = CT_Clip::from_str("dummy").unwrap();
203        assert!(clip.paths().is_empty());
204    }
205
206    #[test]
207    fn test_from_str_empty() {
208        assert!(CT_Clip::from_str("").is_err());
209    }
210
211    #[test]
212    fn test_xml_element_name() {
213        let clip = CT_Clip::new();
214        assert_eq!(clip.element_name(), "Clip");
215    }
216
217    #[test]
218    fn test_xml_element_roundtrip_empty() {
219        let clip = CT_Clip::new();
220        let xml = clip.to_xml();
221        assert_eq!(xml, "<Clip/>");
222        let node = parse_xml_to_nodes(&xml).unwrap();
223        let clip2 = CT_Clip::from_xml(&node).unwrap();
224        assert_eq!(clip, clip2);
225    }
226
227    #[test]
228    fn test_xml_element_roundtrip_with_paths() {
229        let mut clip = CT_Clip::new();
230        let mut p1 = ClipPath::new("M 0 0 L 100 0 L 100 100 Z");
231        p1.set_draw_param(5);
232        clip.add_path(p1);
233        let mut p2 = ClipPath::new("M 0 0 L 50 50");
234        p2.set_transform(ST_Array::transform(1.0, 0.0, 0.0, 1.0, 10.0, 20.0));
235        clip.add_path(p2);
236
237        let xml = clip.to_xml();
238        assert!(xml.contains("Path"));
239        assert!(xml.contains("Data=\"M 0 0 L 100 0 L 100 100 Z\""));
240        assert!(xml.contains("DrawParam=\"5\""));
241
242        let node = parse_xml_to_nodes(&xml).unwrap();
243        let clip2 = CT_Clip::from_xml(&node).unwrap();
244        assert_eq!(clip, clip2);
245    }
246}