1use std::cell::{Cell, RefCell};
34use std::collections::HashMap;
35
36use crate::xml::{Element, Ns};
37
38#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct ViewBox {
41 pub x: f32,
43 pub y: f32,
45 pub width: f32,
47 pub height: f32,
49}
50
51impl ViewBox {
52 fn parse(text: &str) -> Option<Self> {
53 let numbers: Vec<f32> = text
54 .split_whitespace()
55 .filter_map(|n| n.parse().ok())
56 .collect();
57 let [x, y, width, height] = numbers[..] else {
58 return None;
59 };
60 (width > 0.0 && height > 0.0).then_some(Self {
61 x,
62 y,
63 width,
64 height,
65 })
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct SubPath {
72 pub points: Vec<(f32, f32)>,
74 pub closed: bool,
76 pub fill: bool,
79 pub stroke: bool,
81}
82
83#[derive(Debug, Clone, PartialEq)]
85pub struct Geometry {
86 pub view: ViewBox,
88 pub paths: Vec<SubPath>,
90}
91
92const CURVE_SEGMENTS: usize = 16;
99const DEGREES_PER_SEGMENT: f32 = 6.0;
100
101impl Geometry {
102 pub fn read_path(path: &Element) -> Option<Self> {
113 let view = ViewBox::parse(path.attr(&Ns::Svg, "viewBox")?)?;
114 let data = path.attr(&Ns::Svg, "d")?;
115 let mut pen = Pen::new();
116 pen.svg(data);
117 Some(Self {
118 view,
119 paths: pen.finish(),
120 })
121 }
122
123 pub fn refit(&mut self) {
134 let points = || self.paths.iter().flat_map(|path| path.points.iter());
135 let Some(&(x, y)) = points().next() else {
136 return;
137 };
138 let (mut left, mut right, mut top, mut bottom) = (x, x, y, y);
139 for &(x, y) in points() {
140 left = left.min(x);
141 right = right.max(x);
142 top = top.min(y);
143 bottom = bottom.max(y);
144 }
145 self.view = ViewBox {
146 x: left,
147 y: top,
148 width: (right - left).max(f32::EPSILON),
149 height: (bottom - top).max(f32::EPSILON),
150 };
151 }
152
153 pub fn read(geometry: &Element) -> Option<Self> {
158 let view = ViewBox::parse(geometry.attr(&Ns::Svg, "viewBox")?)?;
159 let path = geometry.attr(&Ns::Draw, "enhanced-path")?;
160
161 let formulas = Formulas::new(geometry, view);
162 let mut pen = Pen::new();
163 pen.run(path, &formulas);
164 let mut paths = pen.finish();
165
166 let flip_x = geometry.attr(&Ns::Draw, "mirror-horizontal") == Some("true");
168 let flip_y = geometry.attr(&Ns::Draw, "mirror-vertical") == Some("true");
169 if flip_x || flip_y {
170 for path in &mut paths {
171 for (x, y) in &mut path.points {
172 if flip_x {
173 *x = view.x + view.width - (*x - view.x);
174 }
175 if flip_y {
176 *y = view.y + view.height - (*y - view.y);
177 }
178 }
179 }
180 }
181
182 Some(Self { view, paths })
183 }
184}
185
186struct Formulas<'a> {
188 by_name: HashMap<&'a str, &'a str>,
189 modifiers: Vec<f32>,
190 view: ViewBox,
191 known: RefCell<HashMap<String, f32>>,
194 depth: Cell<u32>,
195}
196
197const MAX_DEPTH: u32 = 64;
202
203impl<'a> Formulas<'a> {
204 fn new(geometry: &'a Element, view: ViewBox) -> Self {
205 let by_name = geometry
206 .elements()
207 .filter(|e| e.is(&Ns::Draw, "equation"))
208 .filter_map(|e| Some((e.attr(&Ns::Draw, "name")?, e.attr(&Ns::Draw, "formula")?)))
209 .collect();
210 let modifiers = geometry
211 .attr(&Ns::Draw, "modifiers")
212 .unwrap_or_default()
213 .split_whitespace()
214 .filter_map(|n| n.parse().ok())
215 .collect();
216 Self {
217 by_name,
218 modifiers,
219 view,
220 known: RefCell::new(HashMap::new()),
221 depth: Cell::new(0),
222 }
223 }
224
225 fn named(&self, name: &str) -> f32 {
227 if let Some(value) = self.known.borrow().get(name) {
228 return *value;
229 }
230 if self.depth.get() >= MAX_DEPTH {
231 return 0.0;
232 }
233 let Some(text) = self.by_name.get(name) else {
234 return 0.0;
235 };
236 self.depth.set(self.depth.get() + 1);
237 let value = self.eval(text);
238 self.depth.set(self.depth.get() - 1);
239 self.known.borrow_mut().insert(name.to_owned(), value);
240 value
241 }
242
243 fn modifier(&self, index: usize) -> f32 {
246 self.modifiers.get(index).copied().unwrap_or(0.0)
247 }
248
249 fn constant(&self, name: &str) -> Option<f32> {
251 Some(match name {
252 "left" => self.view.x,
253 "top" => self.view.y,
254 "right" => self.view.x + self.view.width,
255 "bottom" => self.view.y + self.view.height,
256 "width" | "logwidth" => self.view.width,
257 "height" | "logheight" => self.view.height,
258 "pi" => std::f32::consts::PI,
259 "hasstroke" | "hasfill" => 1.0,
262 "xstretch" | "ystretch" => 0.0,
263 _ => return None,
264 })
265 }
266
267 fn eval(&self, text: &str) -> f32 {
268 Expression {
269 text: text.as_bytes(),
270 at: 0,
271 formulas: self,
272 }
273 .expression()
274 }
275}
276
277struct Expression<'a, 'f> {
279 text: &'a [u8],
280 at: usize,
281 formulas: &'a Formulas<'f>,
282}
283
284impl Expression<'_, '_> {
285 fn skip(&mut self) {
286 while self.at < self.text.len() && self.text[self.at].is_ascii_whitespace() {
287 self.at += 1;
288 }
289 }
290
291 fn peek(&mut self) -> Option<u8> {
292 self.skip();
293 self.text.get(self.at).copied()
294 }
295
296 fn take(&mut self, byte: u8) -> bool {
297 if self.peek() == Some(byte) {
298 self.at += 1;
299 return true;
300 }
301 false
302 }
303
304 fn expression(&mut self) -> f32 {
305 let mut value = self.term();
306 loop {
307 if self.take(b'+') {
308 value += self.term();
309 } else if self.take(b'-') {
310 value -= self.term();
311 } else {
312 return value;
313 }
314 }
315 }
316
317 fn term(&mut self) -> f32 {
318 let mut value = self.factor();
319 loop {
320 if self.take(b'*') {
321 value *= self.factor();
322 } else if self.take(b'/') {
323 let divisor = self.factor();
324 value = if divisor == 0.0 { 0.0 } else { value / divisor };
327 } else {
328 return value;
329 }
330 }
331 }
332
333 fn factor(&mut self) -> f32 {
334 if self.take(b'-') {
335 return -self.factor();
336 }
337 if self.take(b'+') {
338 return self.factor();
339 }
340 if self.take(b'(') {
341 let value = self.expression();
342 self.take(b')');
343 return value;
344 }
345 if self.take(b'?') {
346 let name = self.word();
347 return self.formulas.named(&name);
348 }
349 if self.take(b'$') {
350 let index = self.word().parse().unwrap_or(0);
351 return self.formulas.modifier(index);
352 }
353 match self.peek() {
354 Some(byte) if byte.is_ascii_alphabetic() => {
355 let name = self.word();
356 if self.take(b'(') {
357 let arguments = self.arguments();
358 return call(&name, &arguments);
359 }
360 self.formulas.constant(&name).unwrap_or(0.0)
361 }
362 _ => self.number(),
363 }
364 }
365
366 fn arguments(&mut self) -> Vec<f32> {
367 let mut arguments = Vec::new();
368 if self.take(b')') {
369 return arguments;
370 }
371 loop {
372 arguments.push(self.expression());
373 if !self.take(b',') {
374 self.take(b')');
375 return arguments;
376 }
377 }
378 }
379
380 fn word(&mut self) -> String {
382 self.skip();
383 let start = self.at;
384 while self
385 .text
386 .get(self.at)
387 .is_some_and(|b| b.is_ascii_alphanumeric() || *b == b'_')
388 {
389 self.at += 1;
390 }
391 String::from_utf8_lossy(&self.text[start..self.at]).into_owned()
392 }
393
394 fn number(&mut self) -> f32 {
395 self.skip();
396 let start = self.at;
397 while self
398 .text
399 .get(self.at)
400 .is_some_and(|b| b.is_ascii_digit() || *b == b'.')
401 {
402 self.at += 1;
403 }
404 if start == self.at {
405 self.at += 1;
408 return 0.0;
409 }
410 String::from_utf8_lossy(&self.text[start..self.at])
411 .parse()
412 .unwrap_or(0.0)
413 }
414}
415
416fn call(name: &str, arguments: &[f32]) -> f32 {
417 let argument = |n: usize| arguments.get(n).copied().unwrap_or(0.0);
418 match name {
419 "abs" => argument(0).abs(),
420 "sqrt" => argument(0).max(0.0).sqrt(),
421 "sin" => argument(0).sin(),
424 "cos" => argument(0).cos(),
425 "tan" => argument(0).tan(),
426 "atan" => argument(0).atan(),
427 "atan2" => argument(0).atan2(argument(1)),
428 "min" => argument(0).min(argument(1)),
429 "max" => argument(0).max(argument(1)),
430 "if" => {
433 if argument(0) > 0.0 {
434 argument(1)
435 } else {
436 argument(2)
437 }
438 }
439 _ => 0.0,
440 }
441}
442
443struct Pen {
445 done: Vec<SubPath>,
446 points: Vec<(f32, f32)>,
447 closed: bool,
448 fill: bool,
449 stroke: bool,
450}
451
452impl Pen {
453 fn new() -> Self {
454 Self {
455 done: Vec::new(),
456 points: Vec::new(),
457 closed: false,
458 fill: true,
459 stroke: true,
460 }
461 }
462
463 fn at(&self) -> (f32, f32) {
464 self.points.last().copied().unwrap_or((0.0, 0.0))
465 }
466
467 fn brk(&mut self) {
469 if self.points.len() >= 2 {
470 self.done.push(SubPath {
471 points: std::mem::take(&mut self.points),
472 closed: self.closed,
473 fill: self.fill,
474 stroke: self.stroke,
475 });
476 } else {
477 self.points.clear();
478 }
479 self.closed = false;
480 }
481
482 fn finish(mut self) -> Vec<SubPath> {
483 self.brk();
484 self.done
485 }
486
487 fn run(&mut self, path: &str, formulas: &Formulas<'_>) {
489 let mut tokens = Tokens {
490 text: path.as_bytes(),
491 at: 0,
492 formulas,
493 pushed: None,
494 };
495 let mut command = None;
496 loop {
497 match tokens.next() {
498 Some(Token::Command(letter)) => {
499 command = Some(letter);
500 self.command(letter, &mut tokens);
501 }
502 Some(Token::Number(first)) => match command {
505 Some(letter) => {
506 tokens.pushed = Some(first);
507 self.command(letter, &mut tokens);
508 }
509 None => return,
510 },
511 None => return,
512 }
513 }
514 }
515
516 fn command(&mut self, letter: char, tokens: &mut Tokens<'_, '_>) {
517 match letter {
518 'M' => {
519 let point = tokens.point();
520 self.brk();
521 self.points.push(point);
522 }
523 'L' => {
524 let point = tokens.point();
525 self.points.push(point);
526 }
527 'C' => {
528 let (a, b, end) = (tokens.point(), tokens.point(), tokens.point());
529 self.cubic(a, b, end);
530 }
531 'Q' => {
532 let (control, end) = (tokens.point(), tokens.point());
533 let from = self.at();
536 let third = |a: f32, b: f32| a + 2.0 / 3.0 * (b - a);
537 self.cubic(
538 (third(from.0, control.0), third(from.1, control.1)),
539 (third(end.0, control.0), third(end.1, control.1)),
540 end,
541 );
542 }
543 'Z' => {
544 self.closed = true;
545 self.brk();
546 }
547 'N' => self.brk(),
548 'F' => self.fill = false,
549 'S' => self.stroke = false,
550 'T' | 'U' => {
551 let (centre, radii) = (tokens.point(), tokens.point());
552 let (from, to) = (tokens.number(), tokens.number());
553 if letter == 'U' {
554 self.brk();
555 }
556 self.arc(centre, radii, from, to);
557 }
558 'X' | 'Y' => {
559 let to = tokens.point();
560 self.quadrant(to, letter == 'X');
561 }
562 'A' | 'B' | 'W' | 'V' => {
563 let (corner, opposite) = (tokens.point(), tokens.point());
564 let (from, to) = (tokens.point(), tokens.point());
565 if letter == 'B' || letter == 'V' {
566 self.brk();
567 }
568 self.box_arc(corner, opposite, from, to, letter == 'W' || letter == 'V');
569 }
570 _ => {}
571 }
572 }
573
574 fn svg_curve(
578 &mut self,
579 lower: u8,
580 scan: &mut Numbers,
581 offset: impl Fn((f32, f32)) -> (f32, f32),
582 reflected: Option<(f32, f32)>,
583 ) -> Option<(f32, f32)> {
584 let here = self.at();
585 let (first, second, end) = match lower {
586 b'c' => {
587 let (a, b, e) = (scan.point()?, scan.point()?, scan.point()?);
588 (offset(a), offset(b), offset(e))
589 }
590 b's' => {
591 let (b, e) = (scan.point()?, scan.point()?);
592 (reflected.unwrap_or(here), offset(b), offset(e))
595 }
596 b'q' => {
597 let (c, e) = (scan.point()?, scan.point()?);
598 let (c, e) = (offset(c), offset(e));
599 (quadratic(here, c), quadratic(e, c), e)
600 }
601 _ => {
604 let e = offset(scan.point()?);
605 let c = reflected.unwrap_or(here);
606 (quadratic(here, c), quadratic(e, c), e)
607 }
608 };
609 self.cubic(first, second, end);
610 Some((2.0 * end.0 - second.0, 2.0 * end.1 - second.1))
611 }
612
613 fn svg(&mut self, data: &str) {
620 let mut scan = Numbers {
621 text: data.as_bytes(),
622 at: 0,
623 };
624 let mut command = b' ';
625 let mut reflected: Option<(f32, f32)> = None;
628 let mut start = (0.0, 0.0);
629
630 loop {
631 if let Some(letter) = scan.command() {
632 command = letter;
633 } else if scan.peek_number().is_none() {
634 return;
635 }
636 let lower = command.to_ascii_lowercase();
637 let relative = command.is_ascii_lowercase();
638 let here = self.at();
639 let offset = |point: (f32, f32)| {
640 if relative {
641 (here.0 + point.0, here.1 + point.1)
642 } else {
643 point
644 }
645 };
646
647 match lower {
648 b'm' => {
649 let Some(to) = scan.point() else { return };
650 let to = offset(to);
651 self.brk();
652 self.points.push(to);
653 start = to;
654 reflected = None;
655 command = if relative { b'l' } else { b'L' };
657 }
658 b'l' => {
659 let Some(to) = scan.point() else { return };
660 self.points.push(offset(to));
661 reflected = None;
662 }
663 b'h' => {
664 let Some(x) = scan.number() else { return };
665 let x = if relative { here.0 + x } else { x };
666 self.points.push((x, here.1));
667 reflected = None;
668 }
669 b'v' => {
670 let Some(y) = scan.number() else { return };
671 let y = if relative { here.1 + y } else { y };
672 self.points.push((here.0, y));
673 reflected = None;
674 }
675 b'c' | b's' | b'q' | b't' => {
676 let Some(next) = self.svg_curve(lower, &mut scan, offset, reflected) else {
677 return;
678 };
679 reflected = Some(next);
680 }
681 b'a' => {
682 for _ in 0..3 {
684 if scan.number().is_none() {
685 return;
686 }
687 }
688 let (Some(_), Some(_)) = (scan.number(), scan.number()) else {
689 return;
690 };
691 let Some(to) = scan.point() else { return };
692 self.points.push(offset(to));
693 reflected = None;
694 }
695 b'z' => {
696 self.closed = true;
697 self.brk();
698 self.points.push(start);
701 reflected = None;
702 }
703 _ => return,
704 }
705 }
706 }
707
708 fn cubic(&mut self, a: (f32, f32), b: (f32, f32), end: (f32, f32)) {
709 let from = self.at();
710 for step in 1..=CURVE_SEGMENTS {
711 #[allow(clippy::cast_precision_loss)]
712 let t = step as f32 / CURVE_SEGMENTS as f32;
713 let u = 1.0 - t;
714 let blend = |p0: f32, p1: f32, p2: f32, p3: f32| {
715 u * u * u * p0 + 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t * p3
716 };
717 self.points.push((
718 blend(from.0, a.0, b.0, end.0),
719 blend(from.1, a.1, b.1, end.1),
720 ));
721 }
722 }
723
724 fn arc(&mut self, centre: (f32, f32), radii: (f32, f32), from: f32, to: f32) {
726 let mut sweep = to - from;
729 if sweep <= 0.0 {
730 sweep += 360.0;
731 }
732 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
733 let steps = ((sweep / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
734 for step in 0..=steps {
735 #[allow(clippy::cast_precision_loss)]
736 let angle = (from + sweep * step as f32 / steps as f32).to_radians();
737 self.points.push((
738 centre.0 + radii.0 * angle.cos(),
739 centre.1 + radii.1 * angle.sin(),
740 ));
741 }
742 }
743
744 fn quadrant(&mut self, to: (f32, f32), x_first: bool) {
749 let from = self.at();
750 let centre = if x_first {
751 (to.0, from.1)
752 } else {
753 (from.0, to.1)
754 };
755 let radii = ((to.0 - from.0).abs(), (to.1 - from.1).abs());
756 if radii.0 == 0.0 || radii.1 == 0.0 {
757 self.points.push(to);
758 return;
759 }
760 let angle_of = |p: (f32, f32)| (p.1 - centre.1).atan2(p.0 - centre.0).to_degrees();
761 let (start, end) = (angle_of(from), angle_of(to));
762 let mut sweep = end - start;
764 while sweep > 180.0 {
765 sweep -= 360.0;
766 }
767 while sweep < -180.0 {
768 sweep += 360.0;
769 }
770 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
771 let steps = ((sweep.abs() / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
772 for step in 1..=steps {
773 #[allow(clippy::cast_precision_loss)]
774 let angle = (start + sweep * step as f32 / steps as f32).to_radians();
775 self.points.push((
776 centre.0 + radii.0 * angle.cos(),
777 centre.1 + radii.1 * angle.sin(),
778 ));
779 }
780 }
781
782 fn box_arc(
788 &mut self,
789 corner: (f32, f32),
790 opposite: (f32, f32),
791 from: (f32, f32),
792 to: (f32, f32),
793 clockwise: bool,
794 ) {
795 let centre = (
796 f32::midpoint(corner.0, opposite.0),
797 f32::midpoint(corner.1, opposite.1),
798 );
799 let radii = (
800 (opposite.0 - corner.0).abs() / 2.0,
801 (opposite.1 - corner.1).abs() / 2.0,
802 );
803 if radii.0 == 0.0 || radii.1 == 0.0 {
804 self.points.push(to);
805 return;
806 }
807 let angle_of = |p: (f32, f32)| {
808 ((p.1 - centre.1) / radii.1)
809 .atan2((p.0 - centre.0) / radii.0)
810 .to_degrees()
811 };
812 let (start, end) = (angle_of(from), angle_of(to));
813 let sweep = if clockwise { start - end } else { end - start };
814 let sweep = if sweep <= 0.0 { sweep + 360.0 } else { sweep };
815 let (a, b) = if clockwise {
816 (start, start - sweep)
817 } else {
818 (start, start + sweep)
819 };
820 self.arc_between(centre, radii, a, b);
821 }
822
823 fn arc_between(&mut self, centre: (f32, f32), radii: (f32, f32), from: f32, to: f32) {
824 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
825 let steps = (((to - from).abs() / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
826 for step in 0..=steps {
827 #[allow(clippy::cast_precision_loss)]
828 let angle = (from + (to - from) * step as f32 / steps as f32).to_radians();
829 self.points.push((
830 centre.0 + radii.0 * angle.cos(),
831 centre.1 + radii.1 * angle.sin(),
832 ));
833 }
834 }
835}
836
837fn quadratic(end: (f32, f32), control: (f32, f32)) -> (f32, f32) {
840 (
841 end.0 + 2.0 / 3.0 * (control.0 - end.0),
842 end.1 + 2.0 / 3.0 * (control.1 - end.1),
843 )
844}
845
846struct Numbers<'a> {
852 text: &'a [u8],
853 at: usize,
854}
855
856impl Numbers<'_> {
857 fn skip(&mut self) {
858 while self
859 .text
860 .get(self.at)
861 .is_some_and(|b| b.is_ascii_whitespace() || *b == b',')
862 {
863 self.at += 1;
864 }
865 }
866
867 fn command(&mut self) -> Option<u8> {
869 self.skip();
870 let byte = *self.text.get(self.at)?;
871 if byte.is_ascii_alphabetic() && !matches!(byte, b'e' | b'E') {
872 self.at += 1;
873 return Some(byte);
874 }
875 None
876 }
877
878 fn peek_number(&mut self) -> Option<u8> {
879 self.skip();
880 self.text
881 .get(self.at)
882 .copied()
883 .filter(|b| b.is_ascii_digit() || matches!(b, b'-' | b'+' | b'.'))
884 }
885
886 fn number(&mut self) -> Option<f32> {
887 self.peek_number()?;
888 let start = self.at;
889 if matches!(self.text.get(self.at), Some(b'-' | b'+')) {
890 self.at += 1;
891 }
892 let mut seen_point = false;
893 while let Some(byte) = self.text.get(self.at) {
894 match byte {
895 b'0'..=b'9' => self.at += 1,
896 b'.' if !seen_point => {
898 seen_point = true;
899 self.at += 1;
900 }
901 b'e' | b'E' => {
902 self.at += 1;
903 if matches!(self.text.get(self.at), Some(b'-' | b'+')) {
904 self.at += 1;
905 }
906 }
907 _ => break,
908 }
909 }
910 String::from_utf8_lossy(&self.text[start..self.at])
911 .parse()
912 .ok()
913 }
914
915 fn point(&mut self) -> Option<(f32, f32)> {
916 Some((self.number()?, self.number()?))
917 }
918}
919
920enum Token {
921 Command(char),
922 Number(f32),
923}
924
925struct Tokens<'a, 'f> {
927 text: &'a [u8],
928 at: usize,
929 formulas: &'a Formulas<'f>,
930 pushed: Option<f32>,
932}
933
934impl Tokens<'_, '_> {
935 fn next(&mut self) -> Option<Token> {
936 if let Some(number) = self.pushed.take() {
937 return Some(Token::Number(number));
938 }
939 while self
940 .text
941 .get(self.at)
942 .is_some_and(|b| b.is_ascii_whitespace() || *b == b',')
943 {
944 self.at += 1;
945 }
946 let byte = *self.text.get(self.at)?;
947 if byte.is_ascii_alphabetic() {
948 self.at += 1;
949 return Some(Token::Command(char::from(byte)));
950 }
951 Some(Token::Number(self.value()))
952 }
953
954 fn value(&mut self) -> f32 {
957 let mut expression = Expression {
958 text: self.text,
959 at: self.at,
960 formulas: self.formulas,
961 };
962 let value = expression.factor();
965 self.at = expression.at;
966 value
967 }
968
969 fn number(&mut self) -> f32 {
970 match self.next() {
971 Some(Token::Number(number)) => number,
972 _ => 0.0,
975 }
976 }
977
978 fn point(&mut self) -> (f32, f32) {
979 (self.number(), self.number())
980 }
981}