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(),
});
}
if &data[0..5] != b"%vsm%" {
return Err(Error::InvalidHeader("missing %vsm% magic".into()));
}
let xxpp_pos = find_marker(data, b"xxPP")
.ok_or_else(|| Error::InvalidHeader("missing xxPP section marker".into()))?;
let mut reader = Reader::new(data);
reader.pos = xxpp_pos + 4;
reader.skip(2)?;
skip_string(&mut reader)?;
let color_count = reader.read_u16_be()? as usize;
let mut colors = Vec::new();
let mut commands = Vec::new();
let mut cursor = (0i32, 0i32);
for ci in 0..color_count {
let color = read_color_block(&mut reader, &mut commands, &mut cursor, ci > 0)?;
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 find_marker(data: &[u8], marker: &[u8]) -> Option<usize> {
data.windows(marker.len()).position(|w| w == marker)
}
fn read_color_block(
reader: &mut Reader,
commands: &mut Vec<StitchCommand>,
cursor: &mut (i32, i32),
add_color_change: bool,
) -> Result<(u8, u8, u8), Error> {
reader.skip(3)?;
let block_size = reader.read_u32_be()? as usize;
let block_end = reader.pos + block_size;
let start_x_raw = reader.read_i32_be()?;
let start_y_raw = reader.read_i32_be()?;
let start_x = start_x_raw / 100;
let start_y = -(start_y_raw / 100);
let jump_dx = start_x - cursor.0;
let jump_dy = start_y - cursor.1;
if jump_dx != 0 || jump_dy != 0 {
commands.push(StitchCommand::Trim);
commands.push(StitchCommand::Jump {
dx: jump_dx.clamp(-32768, 32767) as i16,
dy: jump_dy.clamp(-32768, 32767) as i16,
});
cursor.0 = start_x;
cursor.1 = start_y;
}
if add_color_change {
commands.push(StitchCommand::ColorChange);
}
let (r, g, b) = read_thread_info(reader)?;
reader.skip(18)?;
decode_vp3_stitches(reader, commands, block_end, cursor);
reader.pos = block_end;
Ok((r, g, b))
}
fn read_thread_info(reader: &mut Reader) -> Result<(u8, u8, u8), Error> {
let colors_count = reader.read_u8()?;
let _transition = reader.read_u8()?;
let mut r = 0u8;
let mut g = 0u8;
let mut b = 0u8;
for _ in 0..colors_count {
r = reader.read_u8()?;
g = reader.read_u8()?;
b = reader.read_u8()?;
let _parts = reader.read_u8()?;
let _color_length = reader.read_u16_be()?;
}
reader.skip(2)?;
skip_string(reader)?;
skip_string(reader)?;
skip_string(reader)?;
Ok((r, g, b))
}
fn decode_vp3_stitches(
reader: &mut Reader,
commands: &mut Vec<StitchCommand>,
end: usize,
cursor: &mut (i32, i32),
) {
while reader.pos + 1 < end && reader.pos + 1 < reader.data.len() {
let bx = reader.data[reader.pos] as i8;
let by = reader.data[reader.pos + 1] as i8;
reader.pos += 2;
if (bx as u8) != 0x80 {
let dx = bx as i16;
let dy = by as i16;
cursor.0 += dx as i32;
cursor.1 += dy as i32;
commands.push(StitchCommand::Stitch { dx, dy });
continue;
}
match by as u8 {
0x01 => {
if reader.pos + 4 <= end {
let dx = read_i16_be(reader.data, reader.pos);
reader.pos += 2;
let dy = read_i16_be(reader.data, reader.pos);
reader.pos += 2;
cursor.0 += dx as i32;
cursor.1 += dy as i32;
commands.push(StitchCommand::Stitch { dx, dy });
if reader.pos + 2 <= end {
reader.pos += 2;
}
}
}
0x03 => {
commands.push(StitchCommand::Trim);
}
_ => {
}
}
}
}
fn skip_string(reader: &mut Reader) -> Result<(), Error> {
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 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 read_i32_be(&mut self) -> Result<i32, Error> {
if self.pos + 4 > self.data.len() {
return Err(Error::TooShort {
expected: self.pos + 4,
actual: self.data.len(),
});
}
let v = i32::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);
let mut cursor = (0i32, 0i32);
decode_vp3_stitches(&mut reader, &mut commands, data.len(), &mut cursor);
assert_eq!(commands.len(), 2);
assert!(matches!(
commands[0],
StitchCommand::Stitch { dx: 10, dy: 20 }
));
}
#[test]
fn decode_escape_trim() {
let mut commands = Vec::new();
let data = [0x80, 0x03, 0x05, 0x03];
let mut reader = Reader::new(&data);
let mut cursor = (0i32, 0i32);
decode_vp3_stitches(&mut reader, &mut commands, data.len(), &mut cursor);
assert_eq!(commands.len(), 2);
assert!(matches!(commands[0], StitchCommand::Trim));
assert!(matches!(
commands[1],
StitchCommand::Stitch { dx: 5, dy: 3 }
));
}
#[test]
fn decode_extended_move() {
let data = [0x80, 0x01, 0x01, 0x00, 0xFF, 0x00, 0x80, 0x02];
let mut commands = Vec::new();
let mut reader = Reader::new(&data);
let mut cursor = (0i32, 0i32);
decode_vp3_stitches(&mut reader, &mut commands, data.len(), &mut cursor);
assert_eq!(commands.len(), 1);
assert!(matches!(
commands[0],
StitchCommand::Stitch { dx: 256, dy: -256 }
));
}
}