rustitch 0.2.2

PES embroidery file parser and thumbnail renderer
Documentation
use crate::error::Error;
use crate::types::{ResolvedDesign, StitchCommand};

const STITCH_DATA_OFFSET: usize = 0x1D78;

/// Janome SEW thread color palette (first 80 entries).
const SEW_PALETTE: [(u8, u8, u8); 80] = [
    (0, 0, 0),       //  0: Unknown
    (0, 0, 0),       //  1: Black
    (255, 255, 255), //  2: White
    (255, 255, 23),  //  3: Sunflower
    (250, 160, 96),  //  4: Hazel
    (92, 118, 73),   //  5: Green Dust
    (64, 192, 48),   //  6: Green
    (101, 194, 200), //  7: Sky
    (172, 128, 190), //  8: Purple
    (245, 188, 203), //  9: Pink
    (255, 0, 0),     // 10: Red
    (192, 128, 0),   // 11: Brown
    (0, 0, 240),     // 12: Blue
    (228, 195, 93),  // 13: Gold
    (165, 42, 42),   // 14: Dark Brown
    (213, 176, 212), // 15: Pale Violet
    (252, 242, 148), // 16: Pale Yellow
    (240, 208, 192), // 17: Pale Pink
    (255, 192, 0),   // 18: Peach
    (201, 164, 128), // 19: Beige
    (155, 61, 75),   // 20: Wine Red
    (160, 184, 204), // 21: Pale Sky
    (127, 194, 28),  // 22: Yellow Green
    (185, 185, 185), // 23: Silver Grey
    (160, 160, 160), // 24: Grey
    (152, 214, 189), // 25: Pale Aqua
    (184, 240, 240), // 26: Baby Blue
    (54, 139, 160),  // 27: Powder Blue
    (79, 131, 171),  // 28: Bright Blue
    (56, 106, 145),  // 29: Slate Blue
    (0, 32, 107),    // 30: Navy Blue
    (229, 197, 202), // 31: Salmon Pink
    (249, 103, 107), // 32: Coral
    (227, 49, 31),   // 33: Burnt Orange
    (226, 161, 136), // 34: Cinnamon
    (181, 148, 116), // 35: Umber
    (228, 207, 153), // 36: Blonde
    (225, 203, 0),   // 37: Sunflower
    (225, 173, 212), // 38: Orchid Pink
    (195, 0, 126),   // 39: Peony Purple
    (128, 0, 75),    // 40: Burgundy
    (160, 96, 176),  // 41: Royal Purple
    (192, 64, 32),   // 42: Cardinal Red
    (202, 224, 192), // 43: Opal Green
    (137, 152, 86),  // 44: Moss Green
    (0, 170, 0),     // 45: Meadow Green
    (33, 138, 33),   // 46: Dark Green
    (93, 174, 148),  // 47: Aquamarine
    (76, 191, 143),  // 48: Emerald Green
    (0, 119, 114),   // 49: Peacock Green
    (112, 112, 112), // 50: Dark Grey
    (242, 255, 255), // 51: Ivory White
    (177, 88, 24),   // 52: Hazel
    (203, 138, 7),   // 53: Toast
    (247, 146, 123), // 54: Salmon
    (152, 105, 45),  // 55: Cocoa Brown
    (162, 113, 72),  // 56: Sienna
    (123, 85, 74),   // 57: Sepia
    (79, 57, 70),    // 58: Dark Sepia
    (82, 58, 151),   // 59: Violet Blue
    (0, 0, 160),     // 60: Blue Ink
    (0, 150, 222),   // 61: Solar Blue
    (178, 221, 83),  // 62: Green Dust
    (250, 143, 187), // 63: Crimson
    (222, 100, 158), // 64: Floral Pink
    (181, 80, 102),  // 65: Wine
    (94, 87, 71),    // 66: Olive Drab
    (76, 136, 31),   // 67: Meadow
    (228, 220, 121), // 68: Canary Yellow
    (203, 138, 26),  // 69: Toast
    (198, 170, 66),  // 70: Beige
    (236, 176, 44),  // 71: Honey Dew
    (248, 128, 64),  // 72: Tangerine
    (255, 229, 5),   // 73: Ocean Blue
    (250, 122, 122), // 74: Sepia
    (209, 164, 255), // 75: Sepia (alt)
    (140, 90, 48),   // 76: Unknown
    (48, 80, 140),   // 77: Unknown
    (100, 160, 100), // 78: Unknown
    (200, 100, 50),  // 79: Unknown
];

/// Parse a SEW (Janome) embroidery file.
///
/// Format:
///   - u16 LE color count at offset 0x00
///   - color_count × u16 LE thread palette indices at offset 0x02
///   - Graphical preview bitmap
///   - Stitch data at fixed offset 0x1D78
///   - Escape byte 0x80, control in next byte:
///       - control & 1: color change (skip 2 bytes)
///       - 0x02/0x04: jump/move (read 2 signed bytes)
///       - 0x10: normal stitch (read 2 signed bytes)
///       - other: end
///   - Y is negated
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
    if data.len() < STITCH_DATA_OFFSET + 4 {
        return Err(Error::TooShort {
            expected: STITCH_DATA_OFFSET + 4,
            actual: data.len(),
        });
    }

    let color_count = u16::from_le_bytes([data[0], data[1]]) as usize;
    if color_count == 0 {
        return Err(Error::InvalidHeader("zero color count".into()));
    }

    // Read thread palette indices
    let colors: Vec<(u8, u8, u8)> = (0..color_count)
        .map(|i| {
            let off = 2 + i * 2;
            if off + 1 < data.len() {
                let idx = u16::from_le_bytes([data[off], data[off + 1]]) as usize;
                SEW_PALETTE[idx % SEW_PALETTE.len()]
            } else {
                (0, 0, 0)
            }
        })
        .collect();

    let mut commands = Vec::new();
    let mut i = STITCH_DATA_OFFSET;

    while i + 1 < data.len() {
        let b0 = data[i];
        let b1 = data[i + 1];
        i += 2;

        if b0 != 0x80 {
            let dx = b0 as i8 as i16;
            let dy = -(b1 as i8 as i16);
            commands.push(StitchCommand::Stitch { dx, dy });
            continue;
        }

        // Escape: b0 == 0x80, b1 is the control byte
        if i + 1 >= data.len() {
            break;
        }
        let c0 = data[i];
        let c1 = data[i + 1];
        i += 2;

        if b1 & 1 != 0 {
            // Color change
            commands.push(StitchCommand::ColorChange);
        } else if b1 == 0x04 || b1 == 0x02 {
            // Move/jump
            let dx = c0 as i8 as i16;
            let dy = -(c1 as i8 as i16);
            commands.push(StitchCommand::Jump { dx, dy });
        } else if b1 == 0x10 {
            // Stitch with preceding escape
            let dx = c0 as i8 as i16;
            let dy = -(c1 as i8 as i16);
            commands.push(StitchCommand::Stitch { dx, dy });
        } else {
            // Unknown control or end
            break;
        }
    }

    if commands.is_empty() {
        return Err(Error::NoStitchData);
    }

    if !matches!(commands.last(), Some(StitchCommand::End)) {
        commands.push(StitchCommand::End);
    }

    Ok((commands, colors))
}

/// Parse a SEW file and resolve to a renderable design.
pub fn parse_and_resolve(data: &[u8]) -> Result<ResolvedDesign, Error> {
    let (commands, colors) = parse(data)?;
    crate::resolve::resolve(&commands, colors)
}