1use super::AbbreviatedData;
4use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum FillRule {
9 NonZero,
11 EvenOdd,
13}
14
15impl FillRule {
16 #[must_use]
18 pub fn as_str(&self) -> &'static str {
19 match self {
20 Self::NonZero => "NonZero",
21 Self::EvenOdd => "EvenOdd",
22 }
23 }
24}
25
26impl std::fmt::Display for FillRule {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.write_str(self.as_str())
29 }
30}
31
32#[allow(non_camel_case_types)]
38#[derive(Debug, Clone)]
39pub struct CT_Path {
40 pub id: u32,
42 pub boundary: String,
44 pub stroke: bool,
46 pub fill: bool,
48 pub rule: FillRule,
50 pub line_width: Option<f64>,
52 pub stroke_color: Option<u32>,
54 pub fill_color: Option<u32>,
56 pub abbreviated_data: Option<AbbreviatedData>,
58}
59
60impl CT_Path {
61 #[must_use]
63 pub fn new(id: u32, boundary: impl Into<String>) -> Self {
64 Self {
65 id,
66 boundary: boundary.into(),
67 stroke: true,
68 fill: false,
69 rule: FillRule::NonZero,
70 line_width: None,
71 stroke_color: None,
72 fill_color: None,
73 abbreviated_data: None,
74 }
75 }
76
77 #[must_use]
79 pub fn stroke(mut self, stroke: bool) -> Self {
80 self.stroke = stroke;
81 self
82 }
83
84 #[must_use]
86 pub fn fill(mut self, fill: bool) -> Self {
87 self.fill = fill;
88 self
89 }
90
91 #[must_use]
93 pub fn rule(mut self, rule: FillRule) -> Self {
94 self.rule = rule;
95 self
96 }
97
98 #[must_use]
100 pub fn line_width(mut self, width: f64) -> Self {
101 self.line_width = Some(width);
102 self
103 }
104
105 #[must_use]
107 pub fn stroke_color(mut self, color: u32) -> Self {
108 self.stroke_color = Some(color);
109 self
110 }
111
112 #[must_use]
114 pub fn fill_color(mut self, color: u32) -> Self {
115 self.fill_color = Some(color);
116 self
117 }
118
119 #[must_use]
121 pub fn abbreviated_data(mut self, data: AbbreviatedData) -> Self {
122 self.abbreviated_data = Some(data);
123 self
124 }
125
126 #[must_use]
128 pub fn get_abbreviated_data(&self) -> Option<String> {
129 self.abbreviated_data.as_ref().map(|d| d.to_data_string())
130 }
131
132 #[must_use]
134 pub fn get_stroke_color(&self) -> Option<u32> {
135 self.stroke_color
136 }
137
138 #[must_use]
140 pub fn get_fill_color(&self) -> Option<u32> {
141 self.fill_color
142 }
143
144 #[must_use]
146 pub fn to_xml_string(&self) -> String {
147 use std::fmt::Write;
148 let mut xml = format!(
149 "<ofd:PathObject ID=\"{}\" Boundary=\"{}\"",
150 self.id, self.boundary
151 );
152 if !self.stroke {
153 xml.push_str(" Stroke=\"false\"");
154 }
155 if self.fill {
156 xml.push_str(" Fill=\"true\"");
157 }
158 if self.rule != FillRule::NonZero {
159 write!(xml, " Rule=\"{}\"", self.rule.as_str()).expect("写入内存缓冲区不会失败");
160 }
161 if let Some(lw) = self.line_width {
162 write!(xml, " LineWidth=\"{lw}\"").expect("写入内存缓冲区不会失败");
163 }
164 if let Some(sc) = self.stroke_color {
165 write!(xml, " StrokeColor=\"{sc}\"").expect("写入内存缓冲区不会失败");
166 }
167 if let Some(fc) = self.fill_color {
168 write!(xml, " FillColor=\"{fc}\"").expect("写入内存缓冲区不会失败");
169 }
170 if let Some(ref ad) = self.abbreviated_data {
171 xml.push_str(">\n ");
172 xml.push_str(&ad.to_xml_string());
173 xml.push_str("\n</ofd:PathObject>\n");
174 } else {
175 xml.push_str(" />\n");
176 }
177 xml
178 }
179}
180
181impl XmlElement for CT_Path {
182 fn element_name(&self) -> &'static str {
184 "Path"
185 }
186
187 fn attributes(&self) -> Vec<(String, String)> {
188 let mut attrs = Vec::new();
189 attrs.push(("ID".to_string(), self.id.to_string()));
190 attrs.push(("Boundary".to_string(), self.boundary.clone()));
191 if !self.stroke {
192 attrs.push(("Stroke".to_string(), "false".to_string()));
193 }
194 if self.fill {
195 attrs.push(("Fill".to_string(), "true".to_string()));
196 }
197 if self.rule != FillRule::NonZero {
198 attrs.push(("Rule".to_string(), self.rule.as_str().to_string()));
199 }
200 if let Some(lw) = self.line_width {
201 attrs.push(("LineWidth".to_string(), lw.to_string()));
202 }
203 if let Some(sc) = self.stroke_color {
204 attrs.push(("StrokeColor".to_string(), sc.to_string()));
205 }
206 if let Some(fc) = self.fill_color {
207 attrs.push(("FillColor".to_string(), fc.to_string()));
208 }
209 attrs
210 }
211
212 fn child_nodes(&self) -> Vec<XmlNode> {
213 let mut children = Vec::new();
214 if let Some(ref ad) = self.abbreviated_data {
215 let mut node = XmlNode::element("AbbreviatedData");
216 node.push_child(XmlNode::text_node(ad.to_data_string()));
217 children.push(node);
218 }
219 children
220 }
221
222 fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
223 let id = node
224 .get_attr("ID")
225 .ok_or_else(|| XmlElementError("Path 缺少 ID 属性".to_string()))?
226 .parse::<u32>()
227 .map_err(|e| XmlElementError(format!("解析 Path.ID 失败: {e}")))?;
228 let boundary = node
229 .get_attr("Boundary")
230 .ok_or_else(|| XmlElementError("Path 缺少 Boundary 属性".to_string()))?
231 .to_string();
232 let stroke = node.get_attr("Stroke").map_or(true, |s| s != "false");
233 let fill = node.get_attr("Fill").is_some_and(|s| s == "true");
234 let rule = match node.get_attr("Rule") {
235 Some("EvenOdd") => FillRule::EvenOdd,
236 _ => FillRule::NonZero,
237 };
238 let line_width = node
239 .get_attr("LineWidth")
240 .map(|s| {
241 s.parse::<f64>()
242 .map_err(|e| XmlElementError(format!("解析 Path.LineWidth 失败: {e}")))
243 })
244 .transpose()?;
245 let stroke_color = node
246 .get_attr("StrokeColor")
247 .map(|s| {
248 s.parse::<u32>()
249 .map_err(|e| XmlElementError(format!("解析 Path.StrokeColor 失败: {e}")))
250 })
251 .transpose()?;
252 let fill_color = node
253 .get_attr("FillColor")
254 .map(|s| {
255 s.parse::<u32>()
256 .map_err(|e| XmlElementError(format!("解析 Path.FillColor 失败: {e}")))
257 })
258 .transpose()?;
259 let abbreviated_data = node.child("AbbreviatedData").map(|child| {
260 let text = child.text.as_deref().unwrap_or("");
261 AbbreviatedData::parse(text)
262 });
263 Ok(Self {
264 id,
265 boundary,
266 stroke,
267 fill,
268 rule,
269 line_width,
270 stroke_color,
271 fill_color,
272 abbreviated_data,
273 })
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::xml_parse::parse_xml_to_nodes;
281
282 #[test]
283 fn test_ct_path_new() {
284 let p = CT_Path::new(1, "0 0 100 50");
285 assert_eq!(p.id, 1);
286 assert!(p.stroke);
287 assert!(!p.fill);
288 assert_eq!(p.rule, FillRule::NonZero);
289 assert!(p.abbreviated_data.is_none());
290 }
291
292 #[test]
293 fn test_ct_path_builder() {
294 let p = CT_Path::new(2, "10 20 50 50")
295 .stroke(false)
296 .fill(true)
297 .rule(FillRule::EvenOdd)
298 .line_width(2.0)
299 .stroke_color(0xFF_0000)
300 .fill_color(0x00_FF00);
301 assert!(!p.stroke);
302 assert!(p.fill);
303 assert_eq!(p.rule, FillRule::EvenOdd);
304 assert!((p.line_width.unwrap() - 2.0).abs() < f64::EPSILON);
305 assert_eq!(p.stroke_color, Some(0xFF_0000));
306 assert_eq!(p.fill_color, Some(0x00_FF00));
307 }
308
309 #[test]
310 fn test_ct_path_with_data() {
311 let data = AbbreviatedData::new()
312 .move_to(0.0, 0.0)
313 .line_to(100.0, 100.0)
314 .close();
315 let p = CT_Path::new(3, "0 0 100 100").abbreviated_data(data);
316 assert!(p.abbreviated_data.is_some());
317 let data_str = p.get_abbreviated_data().unwrap();
318 assert!(data_str.contains("M 0 0"));
319 assert!(data_str.contains("L 100 100"));
320 }
321
322 #[test]
323 fn test_fill_rule_display() {
324 assert_eq!(FillRule::NonZero.to_string(), "NonZero");
325 assert_eq!(FillRule::EvenOdd.to_string(), "EvenOdd");
326 }
327
328 #[test]
329 fn test_ct_path_to_xml_basic() {
330 let p = CT_Path::new(1, "0 0 50 50");
331 let xml = p.to_xml_string();
332 assert!(xml.contains("ID=\"1\""));
333 assert!(xml.contains("Boundary=\"0 0 50 50\""));
334 assert!(xml.contains("<ofd:PathObject"));
335 assert!(xml.ends_with(" />\n"));
336 }
337
338 #[test]
339 fn test_ct_path_to_xml_with_data() {
340 let data = AbbreviatedData::new().move_to(0.0, 0.0).line_to(10.0, 10.0);
341 let p = CT_Path::new(5, "0 0 10 10")
342 .fill(true)
343 .stroke(false)
344 .fill_color(0x00_00FF)
345 .abbreviated_data(data);
346 let xml = p.to_xml_string();
347 assert!(xml.contains("Fill=\"true\""));
348 assert!(xml.contains("Stroke=\"false\""));
349 assert!(xml.contains("FillColor=\"255\""));
350 assert!(xml.contains("ofd:AbbreviatedData"));
351 assert!(xml.contains("M 0 0"));
352 assert!(xml.contains("L 10 10"));
353 }
354
355 #[test]
356 fn test_ct_path_to_xml_evenodd() {
357 let p = CT_Path::new(6, "0 0 10 10").rule(FillRule::EvenOdd);
358 let xml = p.to_xml_string();
359 assert!(xml.contains("Rule=\"EvenOdd\""));
360 }
361
362 #[test]
363 fn test_ct_path_clone_debug() {
364 let p = CT_Path::new(1, "0 0 1 1");
365 let p2 = p.clone();
366 assert_eq!(p2.id, 1);
367 assert!(format!("{p:?}").contains("CT_Path"));
368 }
369
370 #[test]
371 fn test_xml_element_name() {
372 let p = CT_Path::new(1, "0 0 1 1");
373 assert_eq!(p.element_name(), "Path");
374 }
375
376 #[test]
377 fn test_xml_element_to_xml_contains_attrs() {
378 let p = CT_Path::new(3, "10 20 50 50")
379 .stroke(false)
380 .fill(true)
381 .rule(FillRule::EvenOdd)
382 .line_width(2.5)
383 .stroke_color(0xFF_0000)
384 .fill_color(0x00_FF00);
385 let xml = p.to_xml();
386 assert!(xml.contains("ID=\"3\""));
387 assert!(xml.contains("Boundary=\"10 20 50 50\""));
388 assert!(xml.contains("Stroke=\"false\""));
389 assert!(xml.contains("Fill=\"true\""));
390 assert!(xml.contains("Rule=\"EvenOdd\""));
391 assert!(xml.contains("LineWidth=\"2.5\""));
392 assert!(xml.contains("StrokeColor=\"16711680\""));
393 assert!(xml.contains("FillColor=\"65280\""));
394 }
395
396 #[test]
397 fn test_xml_element_roundtrip_full() {
398 let data = AbbreviatedData::new()
399 .move_to(0.0, 0.0)
400 .line_to(10.0, 10.0)
401 .close();
402 let p = CT_Path::new(5, "0 0 100 100")
403 .stroke(false)
404 .fill(true)
405 .rule(FillRule::EvenOdd)
406 .line_width(2.0)
407 .stroke_color(0xFF_0000)
408 .fill_color(0x00_FF00)
409 .abbreviated_data(data);
410 let xml = p.to_xml();
411 let node = parse_xml_to_nodes(&xml).unwrap();
412 let p2 = CT_Path::from_xml(&node).unwrap();
413 assert_eq!(p.id, p2.id);
414 assert_eq!(p.boundary, p2.boundary);
415 assert_eq!(p.stroke, p2.stroke);
416 assert_eq!(p.fill, p2.fill);
417 assert_eq!(p.rule, p2.rule);
418 assert_eq!(p.line_width, p2.line_width);
419 assert_eq!(p.stroke_color, p2.stroke_color);
420 assert_eq!(p.fill_color, p2.fill_color);
421 assert_eq!(p.get_abbreviated_data(), p2.get_abbreviated_data());
422 }
423
424 #[test]
425 fn test_xml_element_roundtrip_minimal() {
426 let p = CT_Path::new(1, "0 0 10 10");
427 let xml = p.to_xml();
428 let node = parse_xml_to_nodes(&xml).unwrap();
429 let p2 = CT_Path::from_xml(&node).unwrap();
430 assert_eq!(p.id, p2.id);
431 assert_eq!(p.boundary, p2.boundary);
432 assert!(p2.stroke);
433 assert!(!p2.fill);
434 }
435}