diff --git a/Cargo.lock b/Cargo.lock
index ff33dad..e0c2016 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -235,7 +235,7 @@ dependencies = [
[[package]]
name = "rustitch"
-version = "0.1.2"
+version = "0.2.0"
dependencies = [
"png",
"thiserror",
diff --git a/data/pes.xml b/data/pes.xml
index b188082..a9f3dae 100644
--- a/data/pes.xml
+++ b/data/pes.xml
@@ -7,4 +7,20 @@
+
+ DST Tajima embroidery file
+
+
+
+ EXP Melco embroidery file
+
+
+
+ JEF Janome embroidery file
+
+
+
+ VP3 Pfaff embroidery file
+
+
diff --git a/data/stitch-peek.thumbnailer b/data/stitch-peek.thumbnailer
index 7b0c6c7..30548e0 100644
--- a/data/stitch-peek.thumbnailer
+++ b/data/stitch-peek.thumbnailer
@@ -1,4 +1,4 @@
[Thumbnailer Entry]
TryExec=stitch-peek
Exec=stitch-peek -i %i -o %o -s %s
-MimeType=application/x-pes
+MimeType=application/x-pes;application/x-dst;application/x-exp;application/x-jef;application/x-vp3
diff --git a/rustitch/src/dst/mod.rs b/rustitch/src/dst/mod.rs
index a73ec6c..ca2cc8c 100644
--- a/rustitch/src/dst/mod.rs
+++ b/rustitch/src/dst/mod.rs
@@ -77,16 +77,36 @@ pub fn parse(data: &[u8]) -> Result, Error> {
/// Standard bit layout for dx across bytes b0, b1, b2.
fn decode_dx(b0: u8, b1: u8, b2: u8) -> i16 {
let mut x: i16 = 0;
- if b0 & 0x01 != 0 { x += 1; }
- if b0 & 0x02 != 0 { x -= 1; }
- if b0 & 0x04 != 0 { x += 9; }
- if b0 & 0x08 != 0 { x -= 9; }
- if b1 & 0x01 != 0 { x += 3; }
- if b1 & 0x02 != 0 { x -= 3; }
- if b1 & 0x04 != 0 { x += 27; }
- if b1 & 0x08 != 0 { x -= 27; }
- if b2 & 0x04 != 0 { x += 81; }
- if b2 & 0x08 != 0 { x -= 81; }
+ if b0 & 0x01 != 0 {
+ x += 1;
+ }
+ if b0 & 0x02 != 0 {
+ x -= 1;
+ }
+ if b0 & 0x04 != 0 {
+ x += 9;
+ }
+ if b0 & 0x08 != 0 {
+ x -= 9;
+ }
+ if b1 & 0x01 != 0 {
+ x += 3;
+ }
+ if b1 & 0x02 != 0 {
+ x -= 3;
+ }
+ if b1 & 0x04 != 0 {
+ x += 27;
+ }
+ if b1 & 0x08 != 0 {
+ x -= 27;
+ }
+ if b2 & 0x04 != 0 {
+ x += 81;
+ }
+ if b2 & 0x08 != 0 {
+ x -= 81;
+ }
x
}
@@ -94,16 +114,36 @@ fn decode_dx(b0: u8, b1: u8, b2: u8) -> i16 {
/// Standard bit layout for dy across bytes b0, b1, b2.
fn decode_dy(b0: u8, b1: u8, b2: u8) -> i16 {
let mut y: i16 = 0;
- if b0 & 0x80 != 0 { y += 1; }
- if b0 & 0x40 != 0 { y -= 1; }
- if b0 & 0x20 != 0 { y += 9; }
- if b0 & 0x10 != 0 { y -= 9; }
- if b1 & 0x80 != 0 { y += 3; }
- if b1 & 0x40 != 0 { y -= 3; }
- if b1 & 0x20 != 0 { y += 27; }
- if b1 & 0x10 != 0 { y -= 27; }
- if b2 & 0x20 != 0 { y += 81; }
- if b2 & 0x10 != 0 { y -= 81; }
+ if b0 & 0x80 != 0 {
+ y += 1;
+ }
+ if b0 & 0x40 != 0 {
+ y -= 1;
+ }
+ if b0 & 0x20 != 0 {
+ y += 9;
+ }
+ if b0 & 0x10 != 0 {
+ y -= 9;
+ }
+ if b1 & 0x80 != 0 {
+ y += 3;
+ }
+ if b1 & 0x40 != 0 {
+ y -= 3;
+ }
+ if b1 & 0x20 != 0 {
+ y += 27;
+ }
+ if b1 & 0x10 != 0 {
+ y -= 27;
+ }
+ if b2 & 0x20 != 0 {
+ y += 81;
+ }
+ if b2 & 0x10 != 0 {
+ y -= 81;
+ }
// DST Y axis is inverted (positive = up in machine coords, down in screen coords)
-y
}
@@ -166,7 +206,7 @@ mod tests {
assert_eq!(decode_dx(0x04, 0x00, 0x00), 9);
assert_eq!(decode_dx(0x00, 0x04, 0x00), 27);
assert_eq!(decode_dx(0x00, 0x00, 0x04), 81);
- assert_eq!(decode_dx(0x05, 0x05, 0x04), 1 + 9 + 27 + 81); // 118
+ assert_eq!(decode_dx(0x05, 0x05, 0x04), 1 + 9 + 3 + 27 + 81); // 121
}
#[test]
diff --git a/rustitch/src/exp.rs b/rustitch/src/exp.rs
index 6ff0b58..19bf7c6 100644
--- a/rustitch/src/exp.rs
+++ b/rustitch/src/exp.rs
@@ -88,10 +88,7 @@ mod tests {
fn parse_simple_stitches() {
let data = [0x0A, 0x14, 0x05, 0x03];
let cmds = parse(&data).unwrap();
- assert!(matches!(
- cmds[0],
- StitchCommand::Stitch { dx: 10, dy: 20 }
- ));
+ assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: 20 }));
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: 3 }));
assert!(matches!(cmds[2], StitchCommand::End));
}
@@ -120,10 +117,7 @@ mod tests {
fn parse_jump() {
let data = [0x80, 0x04, 0x0A, 0x14];
let cmds = parse(&data).unwrap();
- assert!(matches!(
- cmds[0],
- StitchCommand::Jump { dx: 10, dy: 20 }
- ));
+ assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: 20 }));
}
#[test]
diff --git a/rustitch/src/jef/mod.rs b/rustitch/src/jef/mod.rs
index bcedee5..22ce9d4 100644
--- a/rustitch/src/jef/mod.rs
+++ b/rustitch/src/jef/mod.rs
@@ -15,7 +15,9 @@ use palette::JEF_PALETTE;
///
/// Stitch data: 2 bytes per stitch (signed i8 dx, dy).
/// Control codes: 0x80 0x01 = color change, 0x80 0x02 = jump, 0x80 0x10 = end.
-pub fn parse(data: &[u8]) -> Result<(Vec, Vec<(u8, u8, u8)>), Error> {
+type ParseResult = Result<(Vec, Vec<(u8, u8, u8)>), Error>;
+
+pub fn parse(data: &[u8]) -> ParseResult {
if data.len() < 116 {
return Err(Error::TooShort {
expected: 116,
@@ -146,10 +148,7 @@ mod tests {
fn decode_simple_stitches() {
let data = [0x0A, 0x14, 0x05, 0x03, 0x80, 0x10];
let cmds = decode_stitches(&data).unwrap();
- assert!(matches!(
- cmds[0],
- StitchCommand::Stitch { dx: 10, dy: -20 }
- ));
+ assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: -20 }));
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: -3 }));
assert!(matches!(cmds[2], StitchCommand::End));
}
@@ -165,9 +164,6 @@ mod tests {
fn decode_jump() {
let data = [0x80, 0x02, 0x0A, 0x14, 0x80, 0x10];
let cmds = decode_stitches(&data).unwrap();
- assert!(matches!(
- cmds[0],
- StitchCommand::Jump { dx: 10, dy: -20 }
- ));
+ assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: -20 }));
}
}
diff --git a/rustitch/src/lib.rs b/rustitch/src/lib.rs
index a51b4c2..31587ee 100644
--- a/rustitch/src/lib.rs
+++ b/rustitch/src/lib.rs
@@ -3,7 +3,11 @@ pub mod format;
pub mod palette;
pub mod types;
+pub mod dst;
+pub mod exp;
+pub mod jef;
pub mod pes;
+pub mod vp3;
mod render;
mod resolve;
@@ -32,6 +36,9 @@ fn parse_and_resolve(data: &[u8], fmt: Format) -> Result
let design = pes::parse(data)?;
pes::resolve(&design)
}
- _ => Err(Error::UnsupportedFormat),
+ Format::Dst => dst::parse_and_resolve(data),
+ Format::Exp => exp::parse_and_resolve(data),
+ Format::Jef => jef::parse_and_resolve(data),
+ Format::Vp3 => vp3::parse_and_resolve(data),
}
}
diff --git a/rustitch/src/pes/pec.rs b/rustitch/src/pes/pec.rs
index 25cf2dd..c520f7d 100644
--- a/rustitch/src/pes/pec.rs
+++ b/rustitch/src/pes/pec.rs
@@ -64,6 +64,19 @@ pub fn decode_stitches(data: &[u8]) -> Result, Error> {
let (dx, dx_flags, bytes_dx) = decode_coordinate(data, i)?;
i += bytes_dx;
+ // Check for special bytes at dy position — color change or end markers
+ // can appear between dx and dy when the preceding stitch ends on an
+ // odd byte boundary relative to the next control byte.
+ if i < data.len() && data[i] == 0xFF {
+ commands.push(StitchCommand::End);
+ break;
+ }
+ if i < data.len() && data[i] == 0xFE {
+ commands.push(StitchCommand::ColorChange);
+ i += 2;
+ continue;
+ }
+
let (dy, dy_flags, bytes_dy) = decode_coordinate(data, i)?;
i += bytes_dy;
diff --git a/rustitch/src/vp3.rs b/rustitch/src/vp3.rs
index 902c02a..ce88b36 100644
--- a/rustitch/src/vp3.rs
+++ b/rustitch/src/vp3.rs
@@ -12,7 +12,9 @@ use crate::types::{ResolvedDesign, StitchCommand};
///
/// Byte order: mixed, but length-prefixed strings and section sizes use big-endian.
/// Stitch encoding: variable-length (1 byte for small moves, 3 bytes for large).
-pub fn parse(data: &[u8]) -> Result<(Vec, Vec<(u8, u8, u8)>), Error> {
+type ParseResult = Result<(Vec, Vec<(u8, u8, u8)>), Error>;
+
+pub fn parse(data: &[u8]) -> ParseResult {
if data.len() < 20 {
return Err(Error::TooShort {
expected: 20,
@@ -93,10 +95,11 @@ fn skip_vp3_header(reader: &mut Reader) -> Result<(), Error> {
// Skip another potential string
if reader.remaining() >= 2 {
let peek = reader.peek_u16_be();
- if let Ok(len) = peek {
- if len < 1000 && (len as usize) + 2 <= reader.remaining() {
- skip_string(reader)?;
- }
+ if let Ok(len) = peek
+ && len < 1000
+ && (len as usize) + 2 <= reader.remaining()
+ {
+ skip_string(reader)?;
}
}
diff --git a/stitch-peek/Cargo.toml b/stitch-peek/Cargo.toml
index 8c016a2..ba4449c 100644
--- a/stitch-peek/Cargo.toml
+++ b/stitch-peek/Cargo.toml
@@ -11,6 +11,6 @@ categories = ["graphics", "command-line-utilities"]
readme = "README.md"
[dependencies]
-rustitch = { version = "0.1.1", path = "../rustitch" }
+rustitch = { version = "0.2", path = "../rustitch" }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
diff --git a/stitch-peek/src/main.rs b/stitch-peek/src/main.rs
index a2dba8f..ac31467 100644
--- a/stitch-peek/src/main.rs
+++ b/stitch-peek/src/main.rs
@@ -3,9 +3,12 @@ use clap::Parser;
use std::fs;
#[derive(Parser)]
-#[command(name = "stitch-peek", about = "PES embroidery file thumbnailer")]
+#[command(
+ name = "stitch-peek",
+ about = "Embroidery file thumbnailer (PES, DST, EXP, JEF, VP3)"
+)]
struct Args {
- /// Input PES file path
+ /// Input embroidery file path
#[arg(short = 'i', long = "input")]
input: std::path::PathBuf,
@@ -21,11 +24,14 @@ struct Args {
fn main() -> Result<()> {
let args = Args::parse();
+ let format = rustitch::format::detect_from_extension(&args.input)
+ .with_context(|| format!("unsupported file extension: {}", args.input.display()))?;
+
let data = fs::read(&args.input)
.with_context(|| format!("failed to read {}", args.input.display()))?;
- let png =
- rustitch::thumbnail(&data, args.size).with_context(|| "failed to generate thumbnail")?;
+ let png = rustitch::thumbnail_format(&data, args.size, format)
+ .with_context(|| "failed to generate thumbnail")?;
fs::write(&args.output, &png)
.with_context(|| format!("failed to write {}", args.output.display()))?;