1use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum PathCommand {
8 M(f64, f64),
10 L(f64, f64),
12 Q(f64, f64, f64, f64),
14 B(f64, f64, f64, f64, f64, f64),
16 A(f64, f64, f64, i32, i32, f64, f64),
18 C,
20}
21
22#[derive(Debug, Clone)]
29pub struct AbbreviatedData {
30 pub commands: Vec<PathCommand>,
32}
33
34impl AbbreviatedData {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 commands: Vec::new(),
40 }
41 }
42
43 #[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 chars.next();
117 }
118 }
119 }
120 Self { commands }
121 }
122
123 #[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 #[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 #[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 #[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 #[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 #[must_use]
171 pub fn close(mut self) -> Self {
172 self.commands.push(PathCommand::C);
173 self
174 }
175
176 #[must_use]
178 pub fn len(&self) -> usize {
179 self.commands.len()
180 }
181
182 #[must_use]
184 pub fn is_empty(&self) -> bool {
185 self.commands.is_empty()
186 }
187
188 pub fn append(&mut self, other: &AbbreviatedData) {
190 self.commands.extend_from_slice(&other.commands);
191 }
192
193 #[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 #[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 fn element_name(&self) -> &'static str {
236 "AbbreviatedData"
237 }
238
239 fn attributes(&self) -> Vec<(String, String)> {
240 Vec::new()
241 }
242
243 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
260fn next_f64(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Option<f64> {
262 skip_whitespace(chars);
263 let mut num = String::new();
264 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
278fn 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
295fn 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}