// Minimal tinyuz encoder (greedy LZ77), stream format of github.com/sisong/tinyuz. // Output decodes with the reference tuz_decompress_mem. const kMinDictMatchLen = 2; const kBigPosForLen = (1 << 11) + (1 << 9) + (1 << 7) - 1; const kMaxDist = 4096; const kCtrlStreamEnd = 3; function tuzCompress(data) { const code = [0, 0, 0, 0]; // dict size, patched at the end let typesIndex = 0; let typeCount = 0; let haveDataBack = false; let posBack = 1; let dictSizeMax = 1; const outType = (bit) => { if (typeCount === 0) { typesIndex = code.length; code.push(0); } code[typesIndex] |= bit << typeCount; typeCount = (typeCount + 1) & 7; }; const outLen = (v, packBit) => { let count = 1; while (v >= 2 ** (count * packBit)) { v -= 2 ** (count * packBit); count++; } while (count--) { for (let i = 0; i < packBit; i++) { outType(Math.floor(v / 2 ** (count * packBit + i)) & 1); } outType(count > 0 ? 1 : 0); } }; const outDictPos = (pos) => { const big = pos >= 128 ? 1 : 0; if (big) { pos -= 128; } code.push((pos & 0x7f) | (big << 7)); if (big) { outLen(pos >> 7, 2); } }; const outLiteral = (b) => { outType(1); code.push(b); haveDataBack = true; }; const outDict = (len, dist) => { outType(0); const same = haveDataBack && dist === posBack; let saved = len - kMinDictMatchLen; if (!same && dist > kBigPosForLen) { saved--; } outLen(saved, 1); if (haveDataBack) { outType(same ? 1 : 0); } if (!same) { outDictPos(dist); } dictSizeMax = Math.max(dictSizeMax, dist); haveDataBack = false; posBack = dist; }; let i = 0; while (i < data.length) { let bestLen = 0; let bestDist = 0; for (let dist = 1; dist <= Math.min(i, kMaxDist); dist++) { let len = 0; while (i + len < data.length && data[i + len] === data[i + len - dist]) { len++; } if (len > bestLen) { bestLen = len; bestDist = dist; } } const minLen = bestDist > kBigPosForLen ? 3 : kMinDictMatchLen; if (bestLen >= minLen && (bestLen > 2 || bestDist < 128)) { outDict(bestLen, bestDist); i += bestLen; } else { outLiteral(data[i]); i++; } } // stream end control code outType(0); outLen(kCtrlStreamEnd, 1); if (haveDataBack) { outType(0); } outDictPos(0); for (let k = 0; k < 4; k++) { code[k] = (dictSizeMax >> (8 * k)) & 0xff; } return code; } if (typeof module !== "undefined") { module.exports = { tuzCompress }; }