145 lines
3.1 KiB
JavaScript
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);
|
|
}
|