use std::io::Error;
use crate::{tokenize::*, types::*};
use PathCommandKindDiscriminants::*;
pub fn parse(path: &str) -> Result<PathCommands, Error> {
let mut result: PathCommands = Vec::new();
let tokens = tokenize(path);
for token in tokens {
let (abs_rel, command) = char_to_command(token[0].chars().next().unwrap()).unwrap();
let num: Vec<f64> = token[1..]
.iter()
.map(|s| s.parse::<f64>().unwrap())
.collect();
let command_kind = match command {
MoveTo => {
let points = parse_chunks(&num, 2, |chunk| Point {
x: chunk[0],
y: chunk[1],
});
PathCommandKind::MoveTo(points)
}
LineTo => {
let points = parse_chunks(&num, 2, |chunk| Point {
x: chunk[0],
y: chunk[1],
});
PathCommandKind::LineTo(points)
}
CurveTo => {
let cubics = parse_chunks(&num, 6, |chunk| Cubic {
ctrl1: Point {
x: chunk[0],
y: chunk[1],
},
ctrl2: Point {
x: chunk[2],
y: chunk[3],
},
to: Point {
x: chunk[4],
y: chunk[5],
},
});
PathCommandKind::CurveTo(cubics)
}
QuadraticCurveTo => {
let quads = parse_chunks(&num, 4, |chunk| Bezier2 {
ctrl1: Point {
x: chunk[0],
y: chunk[1],
},
to: Point {
x: chunk[2],
y: chunk[3],
},
});
PathCommandKind::QuadraticCurveTo(quads)
}
SmoothQuadraticCurveTo => {
let points = parse_chunks(&num, 2, |chunk| Point {
x: chunk[0],
y: chunk[1],
});
PathCommandKind::SmoothQuadraticCurveTo(points)
}
EllipticalArcTo => {
let arcs = parse_chunks(&num, 7, |chunk| Arc {
radii: Point {
x: chunk[0],
y: chunk[1],
},
x_axis_rotation: chunk[2],
large_arc: chunk[3] != 0.0,
sweep: chunk[4] != 0.0,
to: Point {
x: chunk[5],
y: chunk[6],
},
});
PathCommandKind::EllipticalArcTo(arcs)
}
HorizontalLineTo => PathCommandKind::HorizontalLineTo(num),
VerticalLineTo => PathCommandKind::VerticalLineTo(num),
ClosePath => PathCommandKind::ClosePath,
SmoothCurveTo => {
let bezier2 = parse_chunks(&num, 4, |chunk| Bezier2 {
ctrl1: Point {
x: chunk[0],
y: chunk[1],
},
to: Point {
x: chunk[2],
y: chunk[3],
},
});
PathCommandKind::SmoothCurveTo(bezier2)
}
};
result.push((abs_rel, command_kind));
}
Ok(result)
}
fn parse_chunks<T, F>(num: &[f64], chunk_size: usize, f: F) -> Vec<T>
where
F: Fn(&[f64]) -> T,
{
if num.len() % chunk_size != 0 {
panic!(
"Number of coordinates {} is not a multiple of {}: {:?}",
num.len(),
chunk_size,
num
);
}
num.chunks(chunk_size).map(|chunk| f(chunk)).collect()
}
fn char_to_command(c: char) -> Option<(AbsRel, PathCommandKindDiscriminants)> {
let abs_rel = match c.is_uppercase() {
true => AbsRel::Absolute,
false => AbsRel::Relative,
};
let path_command = match c.to_ascii_uppercase() {
'M' => Some(MoveTo),
'L' => Some(LineTo),
'H' => Some(HorizontalLineTo),
'V' => Some(VerticalLineTo),
'Z' => Some(ClosePath),
'C' => Some(CurveTo),
'S' => Some(SmoothCurveTo),
'Q' => Some(QuadraticCurveTo),
'T' => Some(SmoothQuadraticCurveTo),
'A' => Some(EllipticalArcTo),
_ => None,
};
match path_command {
Some(cmd) => Some((abs_rel, cmd)),
None => None,
}
}
#[cfg(test)]
use AbsRel::*;
#[test]
fn test_char_to_command() {
assert_eq!(char_to_command('M'), Some((Absolute, MoveTo)));
assert_eq!(char_to_command('L'), Some((Absolute, LineTo)));
assert_eq!(char_to_command('H'), Some((Absolute, HorizontalLineTo)));
assert_eq!(char_to_command('V'), Some((Absolute, VerticalLineTo)));
assert_eq!(char_to_command('Z'), Some((Absolute, ClosePath)));
assert_eq!(char_to_command('C'), Some((Absolute, CurveTo)));
assert_eq!(char_to_command('S'), Some((Absolute, SmoothCurveTo)));
assert_eq!(char_to_command('Q'), Some((Absolute, QuadraticCurveTo)));
assert_eq!(
char_to_command('T'),
Some((Absolute, SmoothQuadraticCurveTo))
);
assert_eq!(char_to_command('A'), Some((Absolute, EllipticalArcTo)));
assert_eq!(char_to_command('m'), Some((Relative, MoveTo)));
assert_eq!(char_to_command('l'), Some((Relative, LineTo)));
assert_eq!(char_to_command('h'), Some((Relative, HorizontalLineTo)));
assert_eq!(char_to_command('v'), Some((Relative, VerticalLineTo)));
assert_eq!(char_to_command('z'), Some((Relative, ClosePath)));
assert_eq!(char_to_command('c'), Some((Relative, CurveTo)));
assert_eq!(char_to_command('s'), Some((Relative, SmoothCurveTo)));
assert_eq!(char_to_command('q'), Some((Relative, QuadraticCurveTo)));
assert_eq!(
char_to_command('t'),
Some((Relative, SmoothQuadraticCurveTo))
);
assert_eq!(char_to_command('a'), Some((Relative, EllipticalArcTo)));
assert_eq!(char_to_command('N'), None);
}
#[test]
fn test_parse() {
assert_eq!(
parse("M 10 20 L 30 40 H 50 V 60 Z").unwrap(),
vec![
(
Absolute,
PathCommandKind::MoveTo(vec![Point { x: 10.0, y: 20.0 }])
),
(
Absolute,
PathCommandKind::LineTo(vec![Point { x: 30.0, y: 40.0 }])
),
(Absolute, PathCommandKind::HorizontalLineTo(vec![50.0])),
(Absolute, PathCommandKind::VerticalLineTo(vec![60.0])),
(Absolute, PathCommandKind::ClosePath),
]
);
assert_eq!(
parse("C 10 10 20 20 30 30 S 40 40 50 50 Q 60 60 70 70 T 80 80 A 10 20 0 1 0 90 100")
.unwrap(),
vec![
(
Absolute,
PathCommandKind::CurveTo(vec![Cubic {
ctrl1: Point { x: 10.0, y: 10.0 },
ctrl2: Point { x: 20.0, y: 20.0 },
to: Point { x: 30.0, y: 30.0 },
}])
),
(
Absolute,
PathCommandKind::SmoothCurveTo(vec![Bezier2 {
ctrl1: Point { x: 40.0, y: 40.0 },
to: Point { x: 50.0, y: 50.0 },
}])
),
(
Absolute,
PathCommandKind::QuadraticCurveTo(vec![Bezier2 {
ctrl1: Point { x: 60.0, y: 60.0 },
to: Point { x: 70.0, y: 70.0 },
}])
),
(
Absolute,
PathCommandKind::SmoothQuadraticCurveTo(vec![Point { x: 80.0, y: 80.0 }])
),
(
Absolute,
PathCommandKind::EllipticalArcTo(vec![Arc {
radii: Point { x: 10.0, y: 20.0 },
x_axis_rotation: 0.0,
large_arc: true,
sweep: false,
to: Point { x: 90.0, y: 100.0 },
}])
),
]
);
}