use crate::{Coord, Point};
use bitflags::bitflags;
use nom::{multi::length_count, number::complete::u8, IResult};
bitflags! {
pub struct PathFlags: u8 {
const CLOSED = 0b00000010;
const USES_COMMANDS = 0b00000100;
const NO_CURVES = 0b00001000;
}
}
impl PathFlags {
fn parse(i: &[u8]) -> IResult<&[u8], PathFlags> {
let (i, flags) = u8(i)?;
Ok((i, PathFlags::from_bits_truncate(flags)))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Curve {
pub point: Point,
pub point_in: Point,
pub point_out: Point,
}
impl Curve {
fn parse(i: &[u8]) -> IResult<&[u8], Curve> {
let (i, point) = Point::parse(i)?;
let (i, point_in) = Point::parse(i)?;
let (i, point_out) = Point::parse(i)?;
Ok((
i,
Curve {
point,
point_in,
point_out,
},
))
}
}
enum PathCommand {
HLine = 0b00,
VLine = 0b01,
Line = 0b10,
Curve = 0b11,
}
impl From<u8> for PathCommand {
fn from(bits: u8) -> PathCommand {
use PathCommand::*;
match bits {
0b00 => HLine,
0b01 => VLine,
0b10 => Line,
0b11 => Curve,
_ => unreachable!("Only two bits must be passed to this function."),
}
}
}
impl PathCommand {
fn parse_list(i: &[u8]) -> IResult<&[u8], Vec<PathCommand>> {
let (i, mut num_points) = u8(i)?;
let (mut i, mut buf) = u8(i)?;
let mut count = 4;
let mut commands = Vec::new();
loop {
if num_points == 0 {
break;
}
let command = PathCommand::from(buf & 0x03);
commands.push(command);
buf = buf >> 2;
count -= 1;
num_points -= 1;
if count == 0 && num_points != 0 {
let next = u8(i)?;
i = next.0;
buf = next.1;
count = 4;
}
}
Ok((i, commands))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
HLine(Coord),
VLine(Coord),
Line(Point),
Curve(Curve),
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathContent {
Lines(Vec<Point>),
Curves(Vec<Curve>),
Commands(Vec<Command>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Path {
pub flags: PathFlags,
pub content: PathContent,
}
impl Path {
pub fn parse(i: &[u8]) -> IResult<&[u8], Path> {
let (i, flags) = PathFlags::parse(i)?;
let (i, content) = if flags.contains(PathFlags::NO_CURVES) {
let (i, points) = length_count(u8, Point::parse)(i)?;
(i, PathContent::Lines(points))
} else if flags.contains(PathFlags::USES_COMMANDS) {
let (mut i, command_list) = PathCommand::parse_list(i)?;
let mut commands = Vec::new();
for command in command_list {
let (i2, command) = match command {
PathCommand::HLine => {
let (i, coord) = Coord::parse(i)?;
(i, Command::HLine(coord))
}
PathCommand::VLine => {
let (i, coord) = Coord::parse(i)?;
(i, Command::VLine(coord))
}
PathCommand::Line => {
let (i, point) = Point::parse(i)?;
(i, Command::Line(point))
}
PathCommand::Curve => {
let (i, curve) = Curve::parse(i)?;
(i, Command::Curve(curve))
}
};
commands.push(command);
i = i2;
}
(i, PathContent::Commands(commands))
} else {
let (i, points) = length_count(u8, Curve::parse)(i)?;
(i, PathContent::Curves(points))
};
Ok((i, Path { flags, content }))
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
);
);
#[test]
fn sizes() {
assert_size!(Curve, 24);
assert_size!(PathFlags, 1);
assert_size!(Command, 28);
assert_size!(PathContent, 32);
assert_size!(Path, 40);
}
#[test]
fn empty() {
let empty = b"\x0a\x00";
let (i, path) = Path::parse(empty).unwrap();
assert!(i.is_empty());
assert_eq!(path.flags, PathFlags::NO_CURVES | PathFlags::CLOSED);
}
#[test]
fn lines() {
let data = b"\x08\x02\x20\x20\x60\x60";
let (i, path) = Path::parse(data).unwrap();
assert!(i.is_empty());
assert_eq!(path.flags, PathFlags::NO_CURVES);
if let PathContent::Lines(points) = path.content {
assert_eq!(points.len(), 2);
assert_eq!(
points[0],
Point {
x: Coord(0.),
y: Coord(0.)
}
);
assert_eq!(
points[1],
Point {
x: Coord(64.),
y: Coord(64.)
}
);
} else {
panic!("Parsed path doesn’t contain lines.");
}
}
#[test]
fn commands() {
let data = b"\x06\x04\xd2\x20\x20\x60\x60\x40\x40\x20\x40\x60\x40";
let (i, path) = Path::parse(data).unwrap();
assert!(i.is_empty());
assert_eq!(path.flags, PathFlags::USES_COMMANDS | PathFlags::CLOSED);
if let PathContent::Commands(commands) = path.content {
assert_eq!(commands.len(), 4);
assert_eq!(
commands[0],
Command::Line(Point {
x: Coord(0.),
y: Coord(0.)
})
);
assert_eq!(commands[1], Command::HLine(Coord(64.)));
assert_eq!(commands[2], Command::VLine(Coord(64.)));
assert_eq!(
commands[3],
Command::Curve(Curve {
point: Point {
x: Coord(32.),
y: Coord(32.)
},
point_in: Point {
x: Coord(0.),
y: Coord(32.)
},
point_out: Point {
x: Coord(64.),
y: Coord(32.)
},
})
);
} else {
panic!("Parsed path doesn’t contain commands.");
}
}
}