use crate::error::Error;
use crate::types::{ResolvedDesign, StitchCommand};
type ParseResult = Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error>;
pub fn parse(data: &[u8]) -> ParseResult {
if data.len() < 20 {
return Err(Error::TooShort {
expected: 20,
actual: data.len(),
});
}
let mut reader = Reader::new(data);
skip_vp3_header(&mut reader)?;
let mut colors = Vec::new();
let mut commands = Vec::new();
let color_section_count = reader.read_u16_be()?;
for _ in 0..color_section_count {
if reader.remaining() < 4 {
break;
}
let color = read_color_section(&mut reader, &mut commands)?;
colors.push(color);
}
if commands.is_empty() {
return Err(Error::NoStitchData);
}
if !matches!(commands.last(), Some(StitchCommand::End)) {
commands.push(StitchCommand::End);
}
Ok((commands, colors))
}
pub fn parse_and_resolve(data: &[u8]) -> Result<ResolvedDesign, Error> {
let (commands, colors) = parse(data)?;
crate::resolve::resolve(&commands, colors)
}
fn skip_vp3_header(reader: &mut Reader) -> Result<(), Error> {
skip_string(reader)?;
if reader.remaining() < 38 {
return Err(Error::TooShort {
expected: 38,
actual: reader.remaining(),
});
}
reader.skip(24)?;
skip_string(reader)?; skip_string(reader)?;
if reader.remaining() >= 6 {
reader.skip(6)?;
}
if reader.remaining() >= 2 {
let peek = reader.peek_u16_be();
if let Ok(len) = peek
&& len < 1000
&& (len as usize) + 2 <= reader.remaining()
{
skip_string(reader)?;
}
}
Ok(())
}
fn read_color_section(
reader: &mut Reader,
commands: &mut Vec<StitchCommand>,
) -> Result<(u8, u8, u8), Error> {
if !commands.is_empty() {
commands.push(StitchCommand::ColorChange);
}
if reader.remaining() < 12 {
return Err(Error::TooShort {
expected: 12,
actual: reader.remaining(),
});
}
reader.skip(8)?;
skip_string(reader)?;
if reader.remaining() < 3 {
return Err(Error::TooShort {
expected: 3,
actual: reader.remaining(),
});
}
let r = reader.read_u8()?;
let g = reader.read_u8()?;
let b = reader.read_u8()?;
skip_string(reader)?; skip_string(reader)?;
if reader.remaining() >= 18 {
reader.skip(18)?;
}
let stitch_byte_count = if reader.remaining() >= 4 {
reader.read_u32_be()? as usize
} else {
return Ok((r, g, b));
};
if stitch_byte_count == 0 || stitch_byte_count > reader.remaining() {
return Ok((r, g, b));
}
let stitch_end = reader.pos + stitch_byte_count;
decode_vp3_stitches(reader, commands, stitch_end);
if reader.pos < stitch_end {
reader.pos = stitch_end;
}
Ok((r, g, b))
}
fn decode_vp3_stitches(reader: &mut Reader, commands: &mut Vec<StitchCommand>, end: usize) {
while reader.pos < end && reader.remaining() >= 2 {
let b1 = reader.data[reader.pos];
if b1 & 0x80 != 0 {
if reader.remaining() < 4 {
break;
}
let dx = read_i16_be(reader.data, reader.pos);
reader.pos += 2;
let dy = read_i16_be(reader.data, reader.pos);
reader.pos += 2;
commands.push(StitchCommand::Jump { dx, dy: -dy });
} else {
let dx = reader.data[reader.pos] as i8 as i16;
reader.pos += 1;
if reader.pos >= end {
break;
}
let dy = -(reader.data[reader.pos] as i8 as i16);
reader.pos += 1;
if dx == 0 && dy == 0 {
commands.push(StitchCommand::Trim);
} else {
commands.push(StitchCommand::Stitch { dx, dy });
}
}
}
}
fn skip_string(reader: &mut Reader) -> Result<(), Error> {
if reader.remaining() < 2 {
return Err(Error::TooShort {
expected: reader.pos + 2,
actual: reader.data.len(),
});
}
let len = reader.read_u16_be()? as usize;
if len > reader.remaining() {
return Err(Error::InvalidHeader(format!(
"string length {} exceeds remaining data {}",
len,
reader.remaining()
)));
}
reader.skip(len)?;
Ok(())
}
fn read_i16_be(data: &[u8], pos: usize) -> i16 {
i16::from_be_bytes([data[pos], data[pos + 1]])
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn remaining(&self) -> usize {
self.data.len().saturating_sub(self.pos)
}
fn read_u8(&mut self) -> Result<u8, Error> {
if self.pos >= self.data.len() {
return Err(Error::TooShort {
expected: self.pos + 1,
actual: self.data.len(),
});
}
let v = self.data[self.pos];
self.pos += 1;
Ok(v)
}
fn read_u16_be(&mut self) -> Result<u16, Error> {
if self.pos + 2 > self.data.len() {
return Err(Error::TooShort {
expected: self.pos + 2,
actual: self.data.len(),
});
}
let v = u16::from_be_bytes([self.data[self.pos], self.data[self.pos + 1]]);
self.pos += 2;
Ok(v)
}
fn peek_u16_be(&self) -> Result<u16, Error> {
if self.pos + 2 > self.data.len() {
return Err(Error::TooShort {
expected: self.pos + 2,
actual: self.data.len(),
});
}
Ok(u16::from_be_bytes([
self.data[self.pos],
self.data[self.pos + 1],
]))
}
fn read_u32_be(&mut self) -> Result<u32, Error> {
if self.pos + 4 > self.data.len() {
return Err(Error::TooShort {
expected: self.pos + 4,
actual: self.data.len(),
});
}
let v = u32::from_be_bytes([
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
]);
self.pos += 4;
Ok(v)
}
fn skip(&mut self, n: usize) -> Result<(), Error> {
if self.pos + n > self.data.len() {
return Err(Error::TooShort {
expected: self.pos + n,
actual: self.data.len(),
});
}
self.pos += n;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_small_stitch() {
let mut commands = Vec::new();
let data = [0x0A, 0x14, 0x05, 0x03];
let mut reader = Reader::new(&data);
decode_vp3_stitches(&mut reader, &mut commands, data.len());
assert_eq!(commands.len(), 2);
assert!(matches!(
commands[0],
StitchCommand::Stitch { dx: 10, dy: -20 }
));
}
#[test]
fn decode_large_jump() {
let mut commands = Vec::new();
let data = [0x81, 0x00, 0x01, 0x00];
let mut reader = Reader::new(&data);
decode_vp3_stitches(&mut reader, &mut commands, data.len());
assert_eq!(commands.len(), 1);
assert!(matches!(commands[0], StitchCommand::Jump { .. }));
}
}