Read SVG arc flags as single characters in path data (#1726)

Found while testing crates/io/src/svg_read.rs:892-894 (arc flags read via Numbers::next_num, 675-709)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-24 01:49:12 -04:00
committed by GitHub
co-authored by Claude Haiku 5.5
parent 4251f1e253
commit 8dd8635e10
+51 -2
View File
@@ -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<f64> {
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<Tok> {
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<Vec<Subpath>> {
_ => 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);
}
}