figura-tools/lib/tiny_lz4.js
2025-09-27 18:20:54 +03:00

145 lines
3.1 KiB
JavaScript

const MIN_MATCH = 3;
const MAX_OFFSET = 0xffff;
function writeExtLen(out, n) {
while (n >= 255) {
out.push(255);
n -= 255;
}
out.push(n);
}
function readExtLen(input, pos, base) {
let add = 0;
while (true) {
let b = input[pos++];
add += b;
if (b < 255) break;
}
return [base + add, pos];
}
function findLongestMatch(data, pos, window) {
let n = data.length;
let max_off = Math.min(window, pos);
let best_len = 0;
let best_off = 0;
let max_match_len = n - pos;
if (max_match_len < MIN_MATCH) return [0, 0];
let start = pos - max_off;
for (let j = start; j < pos; j++) {
if (data[j] === data[pos]) {
let k = 0;
while (k < max_match_len && data[j + k] === data[pos + k]) {
k++;
}
if (k >= MIN_MATCH && k > best_len) {
best_len = k;
best_off = pos - j;
if (best_len >= 255 + MIN_MATCH) break;
}
}
}
return [best_off, best_len];
}
export function compress(input) {
if (!(input instanceof Uint8Array))
throw new Error("compress expects Uint8Array");
let n = input.length;
let i = 0;
let anchor = 0;
let out = [];
let window = MAX_OFFSET;
while (i < n) {
let [off, match_len] = findLongestMatch(input, i, window);
if (match_len >= MIN_MATCH) {
let lit_len = i - anchor;
let lit_nibble = Math.min(lit_len, 15);
let match_nibble = Math.min(match_len - MIN_MATCH, 15);
let token = lit_nibble * 16 + match_nibble;
out.push(token);
if (lit_len >= 15) {
writeExtLen(out, lit_len - 15);
}
for (let k = anchor; k < i; k++) out.push(input[k]);
out.push(off & 0xff, (off >> 8) & 0xff);
let rem = match_len - MIN_MATCH;
if (rem >= 15) {
writeExtLen(out, rem - 15);
}
i += match_len;
anchor = i;
} else {
i++;
}
}
let final_lit = n - anchor;
let tok_lit = Math.min(final_lit, 15);
let token = tok_lit * 16;
out.push(token);
if (final_lit >= 15) {
writeExtLen(out, final_lit - 15);
}
for (let k = anchor; k < n; k++) out.push(input[k]);
return Uint8Array.from(out);
}
export function decompress(input) {
if (!(input instanceof Uint8Array))
throw new Error("decompress expects Uint8Array");
let pos = 0;
let n = input.length;
let out = [];
while (pos < n) {
let token = input[pos++];
if (token === undefined) break;
let lit_nibble = token >> 4;
let match_nibble = token & 0xf;
let lit_len = lit_nibble;
if (lit_len === 15) {
[lit_len, pos] = readExtLen(input, pos, 15);
}
for (let k = 0; k < lit_len; k++) {
out.push(input[pos++]);
}
if (pos >= n) break;
let off = input[pos] | (input[pos + 1] << 8);
pos += 2;
let match_len = match_nibble + MIN_MATCH;
if (match_nibble === 15) {
let extra;
[extra, pos] = readExtLen(input, pos, 0);
match_len += extra;
}
let sofarLen = out.length;
let match_start = sofarLen - off;
if (match_start < 0) throw new Error("Invalid offset");
for (let k = 0; k < match_len; k++) {
out.push(out[match_start + k]);
}
}
return Uint8Array.from(out);
}