EmbCompress — Husqvarna compression (Greg-Hus)
The compression used by HUS, VIP and VP3. The classic scheme known as
"Greg's HUS compression": two layers — static Huffman + RLE — implemented
in packages/core/src/embcompress.ts, a faithful port of
pyembroidery/EmbCompress.py (MIT license).
API
// Decompression (for reading VIP/HUS/VP3)
expand(data: Uint8Array, count: number): Uint8Array
// Compression (for writing HUS/VIP/VP3)
compress(data: Uint8Array): Uint8Array expand() takes a compressed block and the expected record
count, and returns the decompressed bytes. compress()
produces a stored block (a degenerate literal Huffman table): the key to
avoiding edge cases on variable sizes.
Block structure
- Header: number of elements (4 bytes, read MSB-first by decoders).
- Huffman table: bit lengths for each symbol.
- RLE layer: handles byte repetitions.
- Payload: encoded sequences.
Known bug in pyembroidery
pyembroidery.EmbCompress.compresswrites the block count little-endian, while every decoder reads it MSB-first. It only works because certain sizes happen to coincide in both byte orders. Our writer emits it in decoder order — verified against pyembroidery'sexpandfor all sizes, not just the lucky ones.
Tests
packages/core/test/embcompress.test.ts covers:
compress → expandround-trips on real HUS/VIP/VP3 fixtures.- Byte-for-byte comparison with pyembroidery's output for the same inputs.
- Edge cases: 1 record, 65535 records (gate), null bytes, ctrl deltas.
See also Tests & fixtures.