diff --git a/crates/io/src/svg_read.rs b/crates/io/src/svg_read.rs index 4b2892c3..58d8dd11 100644 --- a/crates/io/src/svg_read.rs +++ b/crates/io/src/svg_read.rs @@ -756,17 +756,33 @@ enum Tok { struct Tokenizer<'a> { s: &'a [u8], i: usize, + /// Start of the most recently returned token. + tok_start: usize, } impl<'a> Tokenizer<'a> { fn new(s: &'a str) -> Self { - Tokenizer { s: s.as_bytes(), i: 0 } + Tokenizer { s: s.as_bytes(), i: 0, tok_start: 0 } + } + + /// Read an arc flag: a single `0` or `1` character, which needs no separator after it. + fn next_flag(&mut self) -> Option { + let mut nums = Numbers { s: self.s, i: self.i }; + nums.skip_seps(); + let v = match self.s.get(nums.i)? { + b'0' => 0.0, + b'1' => 1.0, + _ => return None, + }; + self.i = nums.i + 1; + Some(v) } fn next_tok(&mut self) -> Option { let mut nums = Numbers { s: self.s, i: self.i }; nums.skip_seps(); self.i = nums.i; + self.tok_start = self.i; let c = self.s.get(self.i).copied()?; if c.is_ascii_alphabetic() { self.i += 1; @@ -843,7 +859,18 @@ fn parse_path(d: &str) -> Result> { _ => return Err(IoError::Format(format!("SVG: unknown path command `{}`", cmd as char))), }; let mut args = [0.0f64; 7]; - for slot in args.iter_mut().take(count) { + for (idx, slot) in args.iter_mut().enumerate().take(count) { + if abs == b'A' && (idx == 3 || idx == 4) { + // Flags are single characters; re-scan a pending number token from its start. + if matches!(lookahead, Some(Tok::Num(_))) { + lookahead = None; + tk.i = tk.tok_start; + } + if lookahead.is_none() { + *slot = tk.next_flag().ok_or_else(|| IoError::Format("SVG: path command is missing coordinates".into()))?; + continue; + } + } *slot = next_num(&mut tk, &mut lookahead)?; } let point = |x: f64, y: f64| if rel { cur + Vec2::new(x, y) } else { Vec2::new(x, y) }; @@ -1030,3 +1057,25 @@ fn elliptical_params(p0: Vec2, p1: Vec2, rx: f64, ry: f64, phi: f64, large: bool let ccw = angle(ux, uy, vx, vy).rem_euclid(std::f64::consts::TAU); Some((center, theta, pick_sweep(ccw, large, sweep))) } + +#[cfg(test)] +mod tests { + use super::*; + + fn arc_of(d: &str) -> (bool, bool, Vec2) { + let subs = parse_path(d).unwrap_or_else(|e| panic!("{d}: {e:?}")); + match &subs[0].segs[0] { + Seg::Arc { large, sweep, end, .. } => (*large, *sweep, *end), + _ => panic!("not an arc"), + } + } + + #[test] + fn compact_arc_flags_read_as_single_characters() { + let spaced = arc_of("M10 10 a20 20 0 0 1 20 20"); + assert_eq!(arc_of("M10 10 a20 20 0 01 20 20"), spaced); + assert_eq!(arc_of("M10 10 a20 20 0 0120 20"), spaced); + assert_eq!(arc_of("M10 10 a20 20 0 1 0 20 20"), (true, false, spaced.2)); + assert!(arc_of("M10 10 a20 20 0 10-20 20").0); + } +}