1use std::cell::{Cell, RefCell};
30use std::collections::HashMap;
31
32use crate::xml::{Element, Ns};
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct ViewBox {
37 pub x: f32,
39 pub y: f32,
41 pub width: f32,
43 pub height: f32,
45}
46
47impl ViewBox {
48 fn parse(text: &str) -> Option<Self> {
49 let numbers: Vec<f32> = text
50 .split_whitespace()
51 .filter_map(|n| n.parse().ok())
52 .collect();
53 let [x, y, width, height] = numbers[..] else {
54 return None;
55 };
56 (width > 0.0 && height > 0.0).then_some(Self {
57 x,
58 y,
59 width,
60 height,
61 })
62 }
63}
64
65#[derive(Debug, Clone, PartialEq)]
67pub struct SubPath {
68 pub points: Vec<(f32, f32)>,
70 pub closed: bool,
72 pub fill: bool,
75 pub stroke: bool,
77}
78
79#[derive(Debug, Clone, PartialEq)]
81pub struct Geometry {
82 pub view: ViewBox,
84 pub paths: Vec<SubPath>,
86}
87
88const CURVE_SEGMENTS: usize = 16;
95const DEGREES_PER_SEGMENT: f32 = 6.0;
96
97impl Geometry {
98 pub fn read(geometry: &Element) -> Option<Self> {
103 let view = ViewBox::parse(geometry.attr(&Ns::Svg, "viewBox")?)?;
104 let path = geometry.attr(&Ns::Draw, "enhanced-path")?;
105
106 let formulas = Formulas::new(geometry, view);
107 let mut pen = Pen::new();
108 pen.run(path, &formulas);
109 let mut paths = pen.finish();
110
111 let flip_x = geometry.attr(&Ns::Draw, "mirror-horizontal") == Some("true");
113 let flip_y = geometry.attr(&Ns::Draw, "mirror-vertical") == Some("true");
114 if flip_x || flip_y {
115 for path in &mut paths {
116 for (x, y) in &mut path.points {
117 if flip_x {
118 *x = view.x + view.width - (*x - view.x);
119 }
120 if flip_y {
121 *y = view.y + view.height - (*y - view.y);
122 }
123 }
124 }
125 }
126
127 Some(Self { view, paths })
128 }
129}
130
131struct Formulas<'a> {
133 by_name: HashMap<&'a str, &'a str>,
134 modifiers: Vec<f32>,
135 view: ViewBox,
136 known: RefCell<HashMap<String, f32>>,
139 depth: Cell<u32>,
140}
141
142const MAX_DEPTH: u32 = 64;
147
148impl<'a> Formulas<'a> {
149 fn new(geometry: &'a Element, view: ViewBox) -> Self {
150 let by_name = geometry
151 .elements()
152 .filter(|e| e.is(&Ns::Draw, "equation"))
153 .filter_map(|e| Some((e.attr(&Ns::Draw, "name")?, e.attr(&Ns::Draw, "formula")?)))
154 .collect();
155 let modifiers = geometry
156 .attr(&Ns::Draw, "modifiers")
157 .unwrap_or_default()
158 .split_whitespace()
159 .filter_map(|n| n.parse().ok())
160 .collect();
161 Self {
162 by_name,
163 modifiers,
164 view,
165 known: RefCell::new(HashMap::new()),
166 depth: Cell::new(0),
167 }
168 }
169
170 fn named(&self, name: &str) -> f32 {
172 if let Some(value) = self.known.borrow().get(name) {
173 return *value;
174 }
175 if self.depth.get() >= MAX_DEPTH {
176 return 0.0;
177 }
178 let Some(text) = self.by_name.get(name) else {
179 return 0.0;
180 };
181 self.depth.set(self.depth.get() + 1);
182 let value = self.eval(text);
183 self.depth.set(self.depth.get() - 1);
184 self.known.borrow_mut().insert(name.to_owned(), value);
185 value
186 }
187
188 fn modifier(&self, index: usize) -> f32 {
191 self.modifiers.get(index).copied().unwrap_or(0.0)
192 }
193
194 fn constant(&self, name: &str) -> Option<f32> {
196 Some(match name {
197 "left" => self.view.x,
198 "top" => self.view.y,
199 "right" => self.view.x + self.view.width,
200 "bottom" => self.view.y + self.view.height,
201 "width" | "logwidth" => self.view.width,
202 "height" | "logheight" => self.view.height,
203 "pi" => std::f32::consts::PI,
204 "hasstroke" | "hasfill" => 1.0,
207 "xstretch" | "ystretch" => 0.0,
208 _ => return None,
209 })
210 }
211
212 fn eval(&self, text: &str) -> f32 {
213 Expression {
214 text: text.as_bytes(),
215 at: 0,
216 formulas: self,
217 }
218 .expression()
219 }
220}
221
222struct Expression<'a, 'f> {
224 text: &'a [u8],
225 at: usize,
226 formulas: &'a Formulas<'f>,
227}
228
229impl Expression<'_, '_> {
230 fn skip(&mut self) {
231 while self.at < self.text.len() && self.text[self.at].is_ascii_whitespace() {
232 self.at += 1;
233 }
234 }
235
236 fn peek(&mut self) -> Option<u8> {
237 self.skip();
238 self.text.get(self.at).copied()
239 }
240
241 fn take(&mut self, byte: u8) -> bool {
242 if self.peek() == Some(byte) {
243 self.at += 1;
244 return true;
245 }
246 false
247 }
248
249 fn expression(&mut self) -> f32 {
250 let mut value = self.term();
251 loop {
252 if self.take(b'+') {
253 value += self.term();
254 } else if self.take(b'-') {
255 value -= self.term();
256 } else {
257 return value;
258 }
259 }
260 }
261
262 fn term(&mut self) -> f32 {
263 let mut value = self.factor();
264 loop {
265 if self.take(b'*') {
266 value *= self.factor();
267 } else if self.take(b'/') {
268 let divisor = self.factor();
269 value = if divisor == 0.0 { 0.0 } else { value / divisor };
272 } else {
273 return value;
274 }
275 }
276 }
277
278 fn factor(&mut self) -> f32 {
279 if self.take(b'-') {
280 return -self.factor();
281 }
282 if self.take(b'+') {
283 return self.factor();
284 }
285 if self.take(b'(') {
286 let value = self.expression();
287 self.take(b')');
288 return value;
289 }
290 if self.take(b'?') {
291 let name = self.word();
292 return self.formulas.named(&name);
293 }
294 if self.take(b'$') {
295 let index = self.word().parse().unwrap_or(0);
296 return self.formulas.modifier(index);
297 }
298 match self.peek() {
299 Some(byte) if byte.is_ascii_alphabetic() => {
300 let name = self.word();
301 if self.take(b'(') {
302 let arguments = self.arguments();
303 return call(&name, &arguments);
304 }
305 self.formulas.constant(&name).unwrap_or(0.0)
306 }
307 _ => self.number(),
308 }
309 }
310
311 fn arguments(&mut self) -> Vec<f32> {
312 let mut arguments = Vec::new();
313 if self.take(b')') {
314 return arguments;
315 }
316 loop {
317 arguments.push(self.expression());
318 if !self.take(b',') {
319 self.take(b')');
320 return arguments;
321 }
322 }
323 }
324
325 fn word(&mut self) -> String {
327 self.skip();
328 let start = self.at;
329 while self
330 .text
331 .get(self.at)
332 .is_some_and(|b| b.is_ascii_alphanumeric() || *b == b'_')
333 {
334 self.at += 1;
335 }
336 String::from_utf8_lossy(&self.text[start..self.at]).into_owned()
337 }
338
339 fn number(&mut self) -> f32 {
340 self.skip();
341 let start = self.at;
342 while self
343 .text
344 .get(self.at)
345 .is_some_and(|b| b.is_ascii_digit() || *b == b'.')
346 {
347 self.at += 1;
348 }
349 if start == self.at {
350 self.at += 1;
353 return 0.0;
354 }
355 String::from_utf8_lossy(&self.text[start..self.at])
356 .parse()
357 .unwrap_or(0.0)
358 }
359}
360
361fn call(name: &str, arguments: &[f32]) -> f32 {
362 let argument = |n: usize| arguments.get(n).copied().unwrap_or(0.0);
363 match name {
364 "abs" => argument(0).abs(),
365 "sqrt" => argument(0).max(0.0).sqrt(),
366 "sin" => argument(0).sin(),
369 "cos" => argument(0).cos(),
370 "tan" => argument(0).tan(),
371 "atan" => argument(0).atan(),
372 "atan2" => argument(0).atan2(argument(1)),
373 "min" => argument(0).min(argument(1)),
374 "max" => argument(0).max(argument(1)),
375 "if" => {
378 if argument(0) > 0.0 {
379 argument(1)
380 } else {
381 argument(2)
382 }
383 }
384 _ => 0.0,
385 }
386}
387
388struct Pen {
390 done: Vec<SubPath>,
391 points: Vec<(f32, f32)>,
392 closed: bool,
393 fill: bool,
394 stroke: bool,
395}
396
397impl Pen {
398 fn new() -> Self {
399 Self {
400 done: Vec::new(),
401 points: Vec::new(),
402 closed: false,
403 fill: true,
404 stroke: true,
405 }
406 }
407
408 fn at(&self) -> (f32, f32) {
409 self.points.last().copied().unwrap_or((0.0, 0.0))
410 }
411
412 fn brk(&mut self) {
414 if self.points.len() >= 2 {
415 self.done.push(SubPath {
416 points: std::mem::take(&mut self.points),
417 closed: self.closed,
418 fill: self.fill,
419 stroke: self.stroke,
420 });
421 } else {
422 self.points.clear();
423 }
424 self.closed = false;
425 }
426
427 fn finish(mut self) -> Vec<SubPath> {
428 self.brk();
429 self.done
430 }
431
432 fn run(&mut self, path: &str, formulas: &Formulas<'_>) {
434 let mut tokens = Tokens {
435 text: path.as_bytes(),
436 at: 0,
437 formulas,
438 pushed: None,
439 };
440 let mut command = None;
441 loop {
442 match tokens.next() {
443 Some(Token::Command(letter)) => {
444 command = Some(letter);
445 self.command(letter, &mut tokens);
446 }
447 Some(Token::Number(first)) => match command {
450 Some(letter) => {
451 tokens.pushed = Some(first);
452 self.command(letter, &mut tokens);
453 }
454 None => return,
455 },
456 None => return,
457 }
458 }
459 }
460
461 fn command(&mut self, letter: char, tokens: &mut Tokens<'_, '_>) {
462 match letter {
463 'M' => {
464 let point = tokens.point();
465 self.brk();
466 self.points.push(point);
467 }
468 'L' => {
469 let point = tokens.point();
470 self.points.push(point);
471 }
472 'C' => {
473 let (a, b, end) = (tokens.point(), tokens.point(), tokens.point());
474 self.cubic(a, b, end);
475 }
476 'Q' => {
477 let (control, end) = (tokens.point(), tokens.point());
478 let from = self.at();
481 let third = |a: f32, b: f32| a + 2.0 / 3.0 * (b - a);
482 self.cubic(
483 (third(from.0, control.0), third(from.1, control.1)),
484 (third(end.0, control.0), third(end.1, control.1)),
485 end,
486 );
487 }
488 'Z' => {
489 self.closed = true;
490 self.brk();
491 }
492 'N' => self.brk(),
493 'F' => self.fill = false,
494 'S' => self.stroke = false,
495 'T' | 'U' => {
496 let (centre, radii) = (tokens.point(), tokens.point());
497 let (from, to) = (tokens.number(), tokens.number());
498 if letter == 'U' {
499 self.brk();
500 }
501 self.arc(centre, radii, from, to);
502 }
503 'X' | 'Y' => {
504 let to = tokens.point();
505 self.quadrant(to, letter == 'X');
506 }
507 'A' | 'B' | 'W' | 'V' => {
508 let (corner, opposite) = (tokens.point(), tokens.point());
509 let (from, to) = (tokens.point(), tokens.point());
510 if letter == 'B' || letter == 'V' {
511 self.brk();
512 }
513 self.box_arc(corner, opposite, from, to, letter == 'W' || letter == 'V');
514 }
515 _ => {}
516 }
517 }
518
519 fn cubic(&mut self, a: (f32, f32), b: (f32, f32), end: (f32, f32)) {
520 let from = self.at();
521 for step in 1..=CURVE_SEGMENTS {
522 #[allow(clippy::cast_precision_loss)]
523 let t = step as f32 / CURVE_SEGMENTS as f32;
524 let u = 1.0 - t;
525 let blend = |p0: f32, p1: f32, p2: f32, p3: f32| {
526 u * u * u * p0 + 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t * p3
527 };
528 self.points.push((
529 blend(from.0, a.0, b.0, end.0),
530 blend(from.1, a.1, b.1, end.1),
531 ));
532 }
533 }
534
535 fn arc(&mut self, centre: (f32, f32), radii: (f32, f32), from: f32, to: f32) {
537 let mut sweep = to - from;
540 if sweep <= 0.0 {
541 sweep += 360.0;
542 }
543 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
544 let steps = ((sweep / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
545 for step in 0..=steps {
546 #[allow(clippy::cast_precision_loss)]
547 let angle = (from + sweep * step as f32 / steps as f32).to_radians();
548 self.points.push((
549 centre.0 + radii.0 * angle.cos(),
550 centre.1 + radii.1 * angle.sin(),
551 ));
552 }
553 }
554
555 fn quadrant(&mut self, to: (f32, f32), x_first: bool) {
560 let from = self.at();
561 let centre = if x_first {
562 (to.0, from.1)
563 } else {
564 (from.0, to.1)
565 };
566 let radii = ((to.0 - from.0).abs(), (to.1 - from.1).abs());
567 if radii.0 == 0.0 || radii.1 == 0.0 {
568 self.points.push(to);
569 return;
570 }
571 let angle_of = |p: (f32, f32)| (p.1 - centre.1).atan2(p.0 - centre.0).to_degrees();
572 let (start, end) = (angle_of(from), angle_of(to));
573 let mut sweep = end - start;
575 while sweep > 180.0 {
576 sweep -= 360.0;
577 }
578 while sweep < -180.0 {
579 sweep += 360.0;
580 }
581 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
582 let steps = ((sweep.abs() / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
583 for step in 1..=steps {
584 #[allow(clippy::cast_precision_loss)]
585 let angle = (start + sweep * step as f32 / steps as f32).to_radians();
586 self.points.push((
587 centre.0 + radii.0 * angle.cos(),
588 centre.1 + radii.1 * angle.sin(),
589 ));
590 }
591 }
592
593 fn box_arc(
599 &mut self,
600 corner: (f32, f32),
601 opposite: (f32, f32),
602 from: (f32, f32),
603 to: (f32, f32),
604 clockwise: bool,
605 ) {
606 let centre = (
607 f32::midpoint(corner.0, opposite.0),
608 f32::midpoint(corner.1, opposite.1),
609 );
610 let radii = (
611 (opposite.0 - corner.0).abs() / 2.0,
612 (opposite.1 - corner.1).abs() / 2.0,
613 );
614 if radii.0 == 0.0 || radii.1 == 0.0 {
615 self.points.push(to);
616 return;
617 }
618 let angle_of = |p: (f32, f32)| {
619 ((p.1 - centre.1) / radii.1)
620 .atan2((p.0 - centre.0) / radii.0)
621 .to_degrees()
622 };
623 let (start, end) = (angle_of(from), angle_of(to));
624 let sweep = if clockwise { start - end } else { end - start };
625 let sweep = if sweep <= 0.0 { sweep + 360.0 } else { sweep };
626 let (a, b) = if clockwise {
627 (start, start - sweep)
628 } else {
629 (start, start + sweep)
630 };
631 self.arc_between(centre, radii, a, b);
632 }
633
634 fn arc_between(&mut self, centre: (f32, f32), radii: (f32, f32), from: f32, to: f32) {
635 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
636 let steps = (((to - from).abs() / DEGREES_PER_SEGMENT).ceil() as usize).max(2);
637 for step in 0..=steps {
638 #[allow(clippy::cast_precision_loss)]
639 let angle = (from + (to - from) * step as f32 / steps as f32).to_radians();
640 self.points.push((
641 centre.0 + radii.0 * angle.cos(),
642 centre.1 + radii.1 * angle.sin(),
643 ));
644 }
645 }
646}
647
648enum Token {
649 Command(char),
650 Number(f32),
651}
652
653struct Tokens<'a, 'f> {
655 text: &'a [u8],
656 at: usize,
657 formulas: &'a Formulas<'f>,
658 pushed: Option<f32>,
660}
661
662impl Tokens<'_, '_> {
663 fn next(&mut self) -> Option<Token> {
664 if let Some(number) = self.pushed.take() {
665 return Some(Token::Number(number));
666 }
667 while self
668 .text
669 .get(self.at)
670 .is_some_and(|b| b.is_ascii_whitespace() || *b == b',')
671 {
672 self.at += 1;
673 }
674 let byte = *self.text.get(self.at)?;
675 if byte.is_ascii_alphabetic() {
676 self.at += 1;
677 return Some(Token::Command(char::from(byte)));
678 }
679 Some(Token::Number(self.value()))
680 }
681
682 fn value(&mut self) -> f32 {
685 let mut expression = Expression {
686 text: self.text,
687 at: self.at,
688 formulas: self.formulas,
689 };
690 let value = expression.factor();
693 self.at = expression.at;
694 value
695 }
696
697 fn number(&mut self) -> f32 {
698 match self.next() {
699 Some(Token::Number(number)) => number,
700 _ => 0.0,
703 }
704 }
705
706 fn point(&mut self) -> (f32, f32) {
707 (self.number(), self.number())
708 }
709}