Skip to main content

easyofd_core/graph/
abbreviated_data.rs

1//! AbbreviatedData 路径缩写数据。
2
3use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
4
5/// 路径操作命令类型。
6#[derive(Debug, Clone, PartialEq)]
7pub enum PathCommand {
8    /// 移动到 (x, y)。
9    M(f64, f64),
10    /// 直线到 (x, y)。
11    L(f64, f64),
12    /// 二次贝塞尔曲线 (cx, cy, x, y)。
13    Q(f64, f64, f64, f64),
14    /// 三次贝塞尔曲线 (c1x, c1y, c2x, c2y, x, y)。
15    B(f64, f64, f64, f64, f64, f64),
16    /// 椭圆弧 (rx, ry, angle, large, sweep, x, y)。
17    A(f64, f64, f64, i32, i32, f64, f64),
18    /// 闭合路径。
19    C,
20}
21
22/// 对应 Java: org.ofdrw.core.graph.pathObj.AbbreviatedData
23///
24/// 图形轮廓数据,由一系列紧缩的操作符和操作数构成。
25/// 支持 M(移动)、L(直线)、Q(二次贝塞尔)、B(三次贝塞尔)、
26/// A(椭圆弧)、C(闭合) 命令。
27/// 对应 GB/T 33190-2016 第 9.1 节表 35-36。
28#[derive(Debug, Clone)]
29pub struct AbbreviatedData {
30    /// 命令序列。
31    pub commands: Vec<PathCommand>,
32}
33
34impl AbbreviatedData {
35    /// 创建空的缩写数据。
36    #[must_use]
37    pub fn new() -> Self {
38        Self {
39            commands: Vec::new(),
40        }
41    }
42
43    /// 从命令字符串解析缩写数据。
44    ///
45    /// 支持格式: "M x y L x y Q cx cy x y B c1x c1y c2x c2y x y A rx ry angle large sweep x y C"
46    #[must_use]
47    pub fn parse(data: &str) -> Self {
48        let mut commands = Vec::new();
49        let mut chars = data.chars().peekable();
50        while let Some(&ch) = chars.peek() {
51            match ch {
52                'M' | 'm' => {
53                    chars.next();
54                    if let (Some(x), Some(y)) = (next_f64(&mut chars), next_f64(&mut chars)) {
55                        commands.push(PathCommand::M(x, y));
56                    }
57                }
58                'L' | 'l' => {
59                    chars.next();
60                    if let (Some(x), Some(y)) = (next_f64(&mut chars), next_f64(&mut chars)) {
61                        commands.push(PathCommand::L(x, y));
62                    }
63                }
64                'Q' | 'q' => {
65                    chars.next();
66                    if let (Some(cx), Some(cy), Some(x), Some(y)) = (
67                        next_f64(&mut chars),
68                        next_f64(&mut chars),
69                        next_f64(&mut chars),
70                        next_f64(&mut chars),
71                    ) {
72                        commands.push(PathCommand::Q(cx, cy, x, y));
73                    }
74                }
75                'B' | 'b' => {
76                    chars.next();
77                    if let (Some(c1x), Some(c1y), Some(c2x), Some(c2y), Some(x), Some(y)) = (
78                        next_f64(&mut chars),
79                        next_f64(&mut chars),
80                        next_f64(&mut chars),
81                        next_f64(&mut chars),
82                        next_f64(&mut chars),
83                        next_f64(&mut chars),
84                    ) {
85                        commands.push(PathCommand::B(c1x, c1y, c2x, c2y, x, y));
86                    }
87                }
88                'A' | 'a' => {
89                    chars.next();
90                    if let (
91                        Some(rx),
92                        Some(ry),
93                        Some(angle),
94                        Some(large),
95                        Some(sweep),
96                        Some(x),
97                        Some(y),
98                    ) = (
99                        next_f64(&mut chars),
100                        next_f64(&mut chars),
101                        next_f64(&mut chars),
102                        next_i32(&mut chars),
103                        next_i32(&mut chars),
104                        next_f64(&mut chars),
105                        next_f64(&mut chars),
106                    ) {
107                        commands.push(PathCommand::A(rx, ry, angle, large, sweep, x, y));
108                    }
109                }
110                'C' | 'c' => {
111                    chars.next();
112                    commands.push(PathCommand::C);
113                }
114                _ => {
115                    // Skip whitespace or unknown chars.
116                    chars.next();
117                }
118            }
119        }
120        Self { commands }
121    }
122
123    /// 添加移动命令。
124    #[must_use]
125    pub fn move_to(mut self, x: f64, y: f64) -> Self {
126        self.commands.push(PathCommand::M(x, y));
127        self
128    }
129
130    /// 添加直线命令。
131    #[must_use]
132    pub fn line_to(mut self, x: f64, y: f64) -> Self {
133        self.commands.push(PathCommand::L(x, y));
134        self
135    }
136
137    /// 添加二次贝塞尔曲线命令。
138    #[must_use]
139    pub fn quad_to(mut self, cx: f64, cy: f64, x: f64, y: f64) -> Self {
140        self.commands.push(PathCommand::Q(cx, cy, x, y));
141        self
142    }
143
144    /// 添加三次贝塞尔曲线命令。
145    #[must_use]
146    pub fn cubic_to(mut self, c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64) -> Self {
147        self.commands.push(PathCommand::B(c1x, c1y, c2x, c2y, x, y));
148        self
149    }
150
151    /// 添加椭圆弧命令。
152    #[must_use]
153    #[allow(clippy::too_many_arguments)]
154    pub fn arc_to(
155        mut self,
156        rx: f64,
157        ry: f64,
158        angle: f64,
159        large: i32,
160        sweep: i32,
161        x: f64,
162        y: f64,
163    ) -> Self {
164        self.commands
165            .push(PathCommand::A(rx, ry, angle, large, sweep, x, y));
166        self
167    }
168
169    /// 添加闭合命令。
170    #[must_use]
171    pub fn close(mut self) -> Self {
172        self.commands.push(PathCommand::C);
173        self
174    }
175
176    /// 命令数量。
177    #[must_use]
178    pub fn len(&self) -> usize {
179        self.commands.len()
180    }
181
182    /// 是否为空。
183    #[must_use]
184    pub fn is_empty(&self) -> bool {
185        self.commands.is_empty()
186    }
187
188    /// 追加另一组缩写数据。
189    pub fn append(&mut self, other: &AbbreviatedData) {
190        self.commands.extend_from_slice(&other.commands);
191    }
192
193    /// 序列化为 OFD XML 字符串。
194    #[must_use]
195    pub fn to_xml_string(&self) -> String {
196        format!(
197            "<ofd:AbbreviatedData>{}</ofd:AbbreviatedData>",
198            self.to_data_string()
199        )
200    }
201
202    /// 序列化为路径数据字符串。
203    #[must_use]
204    pub fn to_data_string(&self) -> String {
205        use std::fmt::Write;
206        let mut s = String::new();
207        for cmd in &self.commands {
208            match cmd {
209                PathCommand::M(x, y) => write!(s, "M {x} {y} ").unwrap(),
210                PathCommand::L(x, y) => write!(s, "L {x} {y} ").unwrap(),
211                PathCommand::Q(cx, cy, x, y) => write!(s, "Q {cx} {cy} {x} {y} ").unwrap(),
212                PathCommand::B(c1x, c1y, c2x, c2y, x, y) => {
213                    write!(s, "B {c1x} {c1y} {c2x} {c2y} {x} {y} ")
214                        .expect("写入内存缓冲区不会失败");
215                }
216                PathCommand::A(rx, ry, angle, large, sweep, x, y) => {
217                    write!(s, "A {rx} {ry} {angle} {large} {sweep} {x} {y} ")
218                        .expect("写入内存缓冲区不会失败");
219                }
220                PathCommand::C => write!(s, "C ").unwrap(),
221            }
222        }
223        s.trim_end().to_string()
224    }
225}
226
227impl Default for AbbreviatedData {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233impl XmlElement for AbbreviatedData {
234    /// 对应 Java: AbbreviatedData 元素名 "AbbreviatedData"。
235    fn element_name(&self) -> &'static str {
236        "AbbreviatedData"
237    }
238
239    fn attributes(&self) -> Vec<(String, String)> {
240        Vec::new()
241    }
242
243    /// 覆写 write_xml:文本内容为路径命令字符串。
244    fn write_xml(&self, out: &mut String) {
245        if self.commands.is_empty() {
246            out.push_str("<AbbreviatedData/>");
247        } else {
248            out.push_str("<AbbreviatedData>");
249            out.push_str(&crate::xml_element::xml_escape(&self.to_data_string()));
250            out.push_str("</AbbreviatedData>");
251        }
252    }
253
254    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
255        let text = node.text.as_deref().unwrap_or("");
256        Ok(Self::parse(text))
257    }
258}
259
260/// 从字符迭代器中读取下一个 f64 值。
261fn next_f64(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Option<f64> {
262    skip_whitespace(chars);
263    let mut num = String::new();
264    // Handle negative sign.
265    if chars.peek() == Some(&'-') || chars.peek() == Some(&'+') {
266        num.push(chars.next()?);
267    }
268    while let Some(&ch) = chars.peek() {
269        if ch.is_ascii_digit() || ch == '.' {
270            num.push(chars.next()?);
271        } else {
272            break;
273        }
274    }
275    num.parse::<f64>().ok()
276}
277
278/// 从字符迭代器中读取下一个 i32 值。
279fn next_i32(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Option<i32> {
280    skip_whitespace(chars);
281    let mut num = String::new();
282    if chars.peek() == Some(&'-') || chars.peek() == Some(&'+') {
283        num.push(chars.next()?);
284    }
285    while let Some(&ch) = chars.peek() {
286        if ch.is_ascii_digit() {
287            num.push(chars.next()?);
288        } else {
289            break;
290        }
291    }
292    num.parse::<i32>().ok()
293}
294
295/// 跳过空白字符。
296fn skip_whitespace(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) {
297    while let Some(&ch) = chars.peek() {
298        if ch.is_whitespace() || ch == ',' {
299            chars.next();
300        } else {
301            break;
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use crate::xml_parse::parse_xml_to_nodes;
310
311    #[test]
312    fn test_abbreviated_data_new() {
313        let ad = AbbreviatedData::new();
314        assert!(ad.is_empty());
315        assert_eq!(ad.len(), 0);
316    }
317
318    #[test]
319    fn test_abbreviated_data_builder() {
320        let ad = AbbreviatedData::new()
321            .move_to(0.0, 0.0)
322            .line_to(10.0, 10.0)
323            .close();
324        assert_eq!(ad.len(), 3);
325        assert!(!ad.is_empty());
326    }
327
328    #[test]
329    fn test_abbreviated_data_parse_simple() {
330        let ad = AbbreviatedData::parse("M 0 0 L 10 10 C");
331        assert_eq!(ad.len(), 3);
332        assert_eq!(ad.commands[0], PathCommand::M(0.0, 0.0));
333        assert_eq!(ad.commands[1], PathCommand::L(10.0, 10.0));
334        assert_eq!(ad.commands[2], PathCommand::C);
335    }
336
337    #[test]
338    fn test_abbreviated_data_parse_quad() {
339        let ad = AbbreviatedData::parse("M0 0 Q 5 5 10 0");
340        assert_eq!(ad.len(), 2);
341        assert_eq!(ad.commands[1], PathCommand::Q(5.0, 5.0, 10.0, 0.0));
342    }
343
344    #[test]
345    fn test_abbreviated_data_parse_cubic() {
346        let ad = AbbreviatedData::parse("M0 0 B 2 4 6 8 10 0");
347        assert_eq!(ad.len(), 2);
348        assert_eq!(
349            ad.commands[1],
350            PathCommand::B(2.0, 4.0, 6.0, 8.0, 10.0, 0.0)
351        );
352    }
353
354    #[test]
355    fn test_abbreviated_data_parse_arc() {
356        let ad = AbbreviatedData::parse("A 5 5 0 1 0 10 10");
357        assert_eq!(ad.len(), 1);
358        assert_eq!(
359            ad.commands[0],
360            PathCommand::A(5.0, 5.0, 0.0, 1, 0, 10.0, 10.0)
361        );
362    }
363
364    #[test]
365    fn test_abbreviated_data_parse_negative() {
366        let ad = AbbreviatedData::parse("M -1.5 -2.5");
367        assert_eq!(ad.len(), 1);
368        assert_eq!(ad.commands[0], PathCommand::M(-1.5, -2.5));
369    }
370
371    #[test]
372    fn test_abbreviated_data_to_data_string() {
373        let ad = AbbreviatedData::new()
374            .move_to(0.0, 0.0)
375            .line_to(100.0, 50.0)
376            .close();
377        let s = ad.to_data_string();
378        assert!(s.contains("M 0 0"));
379        assert!(s.contains("L 100 50"));
380        assert!(s.contains('C'));
381    }
382
383    #[test]
384    fn test_abbreviated_data_to_xml_string() {
385        let ad = AbbreviatedData::new().move_to(1.0, 2.0);
386        let xml = ad.to_xml_string();
387        assert!(xml.contains("<ofd:AbbreviatedData>"));
388        assert!(xml.contains("</ofd:AbbreviatedData>"));
389        assert!(xml.contains("M 1 2"));
390    }
391
392    #[test]
393    fn test_abbreviated_data_quad_to() {
394        let ad = AbbreviatedData::new().quad_to(5.0, 5.0, 10.0, 0.0);
395        assert_eq!(ad.len(), 1);
396        assert_eq!(ad.commands[0], PathCommand::Q(5.0, 5.0, 10.0, 0.0));
397    }
398
399    #[test]
400    fn test_abbreviated_data_cubic_to() {
401        let ad = AbbreviatedData::new().cubic_to(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
402        assert_eq!(ad.commands[0], PathCommand::B(1.0, 2.0, 3.0, 4.0, 5.0, 6.0));
403    }
404
405    #[test]
406    fn test_abbreviated_data_arc_to() {
407        let ad = AbbreviatedData::new().arc_to(5.0, 5.0, 0.0, 1, 0, 10.0, 10.0);
408        assert_eq!(
409            ad.commands[0],
410            PathCommand::A(5.0, 5.0, 0.0, 1, 0, 10.0, 10.0)
411        );
412    }
413
414    #[test]
415    fn test_abbreviated_data_append() {
416        let mut ad1 = AbbreviatedData::new().move_to(0.0, 0.0);
417        let ad2 = AbbreviatedData::new().line_to(10.0, 10.0).close();
418        ad1.append(&ad2);
419        assert_eq!(ad1.len(), 3);
420    }
421
422    #[test]
423    fn test_abbreviated_data_roundtrip() {
424        let original = "M 0 0 L 10 10 L 20 0 C";
425        let ad = AbbreviatedData::parse(original);
426        let result = ad.to_data_string();
427        assert_eq!(result, original);
428    }
429
430    #[test]
431    fn test_abbreviated_data_clone_debug() {
432        let ad = AbbreviatedData::new().move_to(1.0, 2.0);
433        let ad2 = ad.clone();
434        assert_eq!(ad2.len(), 1);
435        assert!(format!("{ad:?}").contains("AbbreviatedData"));
436    }
437
438    #[test]
439    fn test_xml_element_name() {
440        let ad = AbbreviatedData::new();
441        assert_eq!(ad.element_name(), "AbbreviatedData");
442    }
443
444    #[test]
445    fn test_xml_element_roundtrip() {
446        let ad = AbbreviatedData::new()
447            .move_to(0.0, 0.0)
448            .line_to(10.0, 10.0)
449            .quad_to(5.0, 5.0, 20.0, 0.0)
450            .cubic_to(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
451            .arc_to(5.0, 5.0, 0.0, 1, 0, 10.0, 10.0)
452            .close();
453        let xml = ad.to_xml();
454        assert!(xml.contains("<AbbreviatedData>"));
455        assert!(xml.contains("M 0 0"));
456        assert!(xml.contains("L 10 10"));
457        assert!(xml.contains('C'));
458        let node = parse_xml_to_nodes(&xml).unwrap();
459        let ad2 = AbbreviatedData::from_xml(&node).unwrap();
460        assert_eq!(ad.commands, ad2.commands);
461    }
462
463    #[test]
464    fn test_xml_element_roundtrip_empty() {
465        let ad = AbbreviatedData::new();
466        let xml = ad.to_xml();
467        assert_eq!(xml, "<AbbreviatedData/>");
468        let node = parse_xml_to_nodes(&xml).unwrap();
469        let ad2 = AbbreviatedData::from_xml(&node).unwrap();
470        assert!(ad2.is_empty());
471    }
472}