@chr33s/pdf-upng 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +112 -0
- package/dist/bin.d.ts +12 -0
- package/dist/bin.js +56 -0
- package/dist/bin.js.map +1 -0
- package/dist/crc.d.ts +5 -0
- package/dist/crc.js +25 -0
- package/dist/crc.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/inflator.d.ts +39 -0
- package/dist/inflator.js +368 -0
- package/dist/inflator.js.map +1 -0
- package/dist/quantizer.d.ts +42 -0
- package/dist/quantizer.js +368 -0
- package/dist/quantizer.js.map +1 -0
- package/dist/upng.d.ts +60 -0
- package/dist/upng.js +1442 -0
- package/dist/upng.js.map +1 -0
- package/package.json +46 -0
- package/src/bin.ts +49 -0
- package/src/crc.ts +23 -0
- package/src/index.ts +3 -0
- package/src/inflator.ts +403 -0
- package/src/quantizer.ts +395 -0
- package/src/upng.ts +1567 -0
- package/test/bin.test.ts +173 -0
- package/test/crc.test.ts +71 -0
- package/test/index.test.ts +9 -0
- package/test/inflator.test.ts +227 -0
- package/test/quantizer.test.ts +323 -0
- package/test/upng.test.ts +242 -0
- package/tsconfig.json +9 -0
- package/tsconfig.typecheck.json +14 -0
- package/vitest.config.ts +8 -0
package/test/bin.test.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { Bin } from "../src/bin.js";
|
|
3
|
+
|
|
4
|
+
describe("nextZero", () => {
|
|
5
|
+
test("returns index of first zero starting at p", () => {
|
|
6
|
+
const data = new Uint8Array([1, 2, 3, 0, 4]);
|
|
7
|
+
expect(Bin.nextZero(data, 0)).toBe(3);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("returns p when it already points to zero", () => {
|
|
11
|
+
const data = new Uint8Array([1, 2, 0, 4]);
|
|
12
|
+
expect(Bin.nextZero(data, 2)).toBe(2);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("readUshort / writeUshort", () => {
|
|
17
|
+
test("reads a big-endian unsigned short written by writeUshort", () => {
|
|
18
|
+
const buff = new Uint8Array(4);
|
|
19
|
+
const value = 0x1234; // 4660
|
|
20
|
+
Bin.writeUshort(buff, 1, value);
|
|
21
|
+
expect(buff[1]).toBe(0x12);
|
|
22
|
+
expect(buff[2]).toBe(0x34);
|
|
23
|
+
|
|
24
|
+
const readValue = Bin.readUshort(buff, 1);
|
|
25
|
+
expect(readValue).toBe(value);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("handles minimum and maximum ushort values", () => {
|
|
29
|
+
const buff = new Uint8Array(4);
|
|
30
|
+
|
|
31
|
+
Bin.writeUshort(buff, 0, 0);
|
|
32
|
+
expect(Bin.readUshort(buff, 0)).toBe(0);
|
|
33
|
+
|
|
34
|
+
Bin.writeUshort(buff, 0, 0xffff);
|
|
35
|
+
expect(Bin.readUshort(buff, 0)).toBe(0xffff);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("readUint / writeUint", () => {
|
|
40
|
+
test("reads a big-endian unsigned int written by writeUint", () => {
|
|
41
|
+
const buff = new Uint8Array(8);
|
|
42
|
+
const value = 0x12345678; // 305419896
|
|
43
|
+
Bin.writeUint(buff, 2, value);
|
|
44
|
+
|
|
45
|
+
expect(buff[2]).toBe(0x12);
|
|
46
|
+
expect(buff[3]).toBe(0x34);
|
|
47
|
+
expect(buff[4]).toBe(0x56);
|
|
48
|
+
expect(buff[5]).toBe(0x78);
|
|
49
|
+
|
|
50
|
+
const readValue = Bin.readUint(buff, 2);
|
|
51
|
+
expect(readValue).toBe(value);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("handles minimum and maximum 32-bit unsigned values", () => {
|
|
55
|
+
const buff = new Uint8Array(4);
|
|
56
|
+
|
|
57
|
+
Bin.writeUint(buff, 0, 0);
|
|
58
|
+
expect(Bin.readUint(buff, 0)).toBe(0);
|
|
59
|
+
|
|
60
|
+
const max = 0xffffffff; // 4294967295
|
|
61
|
+
Bin.writeUint(buff, 0, max);
|
|
62
|
+
expect(Bin.readUint(buff, 0)).toBe(max);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("readASCII / writeASCII", () => {
|
|
67
|
+
test("writes and reads ASCII strings correctly", () => {
|
|
68
|
+
const buff = new Uint8Array(10);
|
|
69
|
+
const s = "Hello";
|
|
70
|
+
Bin.writeASCII(buff, 2, s);
|
|
71
|
+
|
|
72
|
+
// Raw bytes
|
|
73
|
+
expect(Array.from(buff.slice(2, 7))).toEqual([
|
|
74
|
+
"H".charCodeAt(0),
|
|
75
|
+
"e".charCodeAt(0),
|
|
76
|
+
"l".charCodeAt(0),
|
|
77
|
+
"l".charCodeAt(0),
|
|
78
|
+
"o".charCodeAt(0),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const read = Bin.readASCII(buff, 2, s.length);
|
|
82
|
+
expect(read).toBe(s);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("can write shorter strings without touching earlier bytes", () => {
|
|
86
|
+
const buff = new Uint8Array(5).fill(0xff);
|
|
87
|
+
Bin.writeASCII(buff, 1, "A");
|
|
88
|
+
expect(buff[0]).toBe(0xff);
|
|
89
|
+
expect(buff[1]).toBe("A".charCodeAt(0));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("readBytes", () => {
|
|
94
|
+
test("returns a JS array slice of the specified bytes", () => {
|
|
95
|
+
const buff = new Uint8Array([10, 20, 30, 40, 50]);
|
|
96
|
+
const arr = Bin.readBytes(buff, 1, 3);
|
|
97
|
+
expect(arr).toEqual([20, 30, 40]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("works with length 0", () => {
|
|
101
|
+
const buff = new Uint8Array([1, 2, 3]);
|
|
102
|
+
const arr = Bin.readBytes(buff, 1, 0);
|
|
103
|
+
expect(arr).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("pad", () => {
|
|
108
|
+
test("adds a leading zero for single-character strings", () => {
|
|
109
|
+
expect(Bin.pad("a")).toBe("0a");
|
|
110
|
+
expect(Bin.pad("1")).toBe("01");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("returns the original string if length >= 2", () => {
|
|
114
|
+
expect(Bin.pad("10")).toBe("10");
|
|
115
|
+
expect(Bin.pad("abc")).toBe("abc");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("readUTF8", () => {
|
|
120
|
+
test("decodes valid UTF-8 sequences", () => {
|
|
121
|
+
// "hé" in UTF-8
|
|
122
|
+
const encoder = new TextEncoder();
|
|
123
|
+
const s = "hé";
|
|
124
|
+
const bytes = encoder.encode(s); // Uint8Array
|
|
125
|
+
|
|
126
|
+
const buff = new Uint8Array(10);
|
|
127
|
+
buff.set(bytes, 2);
|
|
128
|
+
|
|
129
|
+
const decoded = Bin.readUTF8(buff, 2, bytes.length);
|
|
130
|
+
expect(decoded).toBe(s);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("falls back to ASCII when decodeURIComponent throws", () => {
|
|
134
|
+
// Create bytes that produce an invalid percent-escape sequence
|
|
135
|
+
// '%' followed by non-hex characters
|
|
136
|
+
const _buff = new Uint8Array([37, 71, 71]); // '%GG'
|
|
137
|
+
// readUTF8 will create string "%25%47%47", which is valid,
|
|
138
|
+
// so we instead force a bad sequence by hand:
|
|
139
|
+
// We'll directly test the catch path by constructing an impossible pattern:
|
|
140
|
+
//
|
|
141
|
+
// To get into catch, we need decodeURIComponent to throw.
|
|
142
|
+
// One way is to craft an incomplete escape at end: e.g. "%E2%82"
|
|
143
|
+
const invalid = new Uint8Array([
|
|
144
|
+
0xe2, // 226
|
|
145
|
+
0x82, // 130
|
|
146
|
+
]);
|
|
147
|
+
const bigBuff = new Uint8Array(10);
|
|
148
|
+
bigBuff.set(invalid, 0);
|
|
149
|
+
|
|
150
|
+
// Monkey-patch pad+readASCII behavior is not necessary,
|
|
151
|
+
// instead we rely on decodeURIComponent("%e2%82") throwing.
|
|
152
|
+
const result = Bin.readUTF8(bigBuff, 0, invalid.length);
|
|
153
|
+
|
|
154
|
+
// Fallback is ASCII for same bytes
|
|
155
|
+
const ascii = Bin.readASCII(bigBuff, 0, invalid.length);
|
|
156
|
+
expect(result).toBe(ascii);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("matches ASCII for plain ASCII strings", () => {
|
|
160
|
+
const text = "Hello";
|
|
161
|
+
const encoder = new TextEncoder();
|
|
162
|
+
const bytes = encoder.encode(text);
|
|
163
|
+
|
|
164
|
+
const buff = new Uint8Array(10);
|
|
165
|
+
buff.set(bytes, 1);
|
|
166
|
+
|
|
167
|
+
const utf8 = Bin.readUTF8(buff, 1, bytes.length);
|
|
168
|
+
const ascii = Bin.readASCII(buff, 1, bytes.length);
|
|
169
|
+
|
|
170
|
+
expect(utf8).toBe(text);
|
|
171
|
+
expect(utf8).toBe(ascii);
|
|
172
|
+
});
|
|
173
|
+
});
|
package/test/crc.test.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { CRC } from "../src/crc.js";
|
|
3
|
+
|
|
4
|
+
describe("table", () => {
|
|
5
|
+
test("has 256 entries", () => {
|
|
6
|
+
expect(CRC.table).toBeInstanceOf(Uint32Array);
|
|
7
|
+
expect(CRC.table.length).toBe(256);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("update", () => {
|
|
12
|
+
test("returns the same value when updating with zero-length buffer", () => {
|
|
13
|
+
const initial = 0x12345678;
|
|
14
|
+
const buf = new Uint8Array([1, 2, 3, 4]);
|
|
15
|
+
const result = CRC.update(initial, buf, 0, 0);
|
|
16
|
+
expect(result).toBe(initial);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("updates CRC incrementally equivalent to one-shot", () => {
|
|
20
|
+
const text = "Hello, world!";
|
|
21
|
+
const bytes = new TextEncoder().encode(text);
|
|
22
|
+
|
|
23
|
+
const full = CRC.update(0xffffffff, bytes, 0, bytes.length);
|
|
24
|
+
|
|
25
|
+
const mid = Math.floor(bytes.length / 2);
|
|
26
|
+
const part1 = CRC.update(0xffffffff, bytes, 0, mid);
|
|
27
|
+
const part2 = CRC.update(part1, bytes, mid, bytes.length - mid);
|
|
28
|
+
|
|
29
|
+
expect(part2 >>> 0).toBe(full >>> 0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("respects offset and length", () => {
|
|
33
|
+
const buf = new Uint8Array([0, 1, 2, 3, 4, 5]);
|
|
34
|
+
const c1 = CRC.update(0xffffffff, buf, 1, 3);
|
|
35
|
+
|
|
36
|
+
const slice = buf.slice(1, 4);
|
|
37
|
+
const c2 = CRC.update(0xffffffff, slice, 0, slice.length);
|
|
38
|
+
|
|
39
|
+
expect(c1 >>> 0).toBe(c2 >>> 0);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("crc", () => {
|
|
44
|
+
test("matches known CRC-32 values for test vectors", () => {
|
|
45
|
+
const enc = new TextEncoder();
|
|
46
|
+
|
|
47
|
+
// Empty buffer (standard CRC-32)
|
|
48
|
+
expect(CRC.crc(new Uint8Array([]), 0, 0) >>> 0).toBe(0x00000000);
|
|
49
|
+
|
|
50
|
+
// "123456789" -> 0xCBF43926
|
|
51
|
+
const v1 = enc.encode("123456789");
|
|
52
|
+
expect(CRC.crc(v1, 0, v1.length) >>> 0).toBe(0xcbf43926);
|
|
53
|
+
|
|
54
|
+
// Just verify that we return a valid 32-bit value for another string
|
|
55
|
+
const v2 = enc.encode("Hello, world!");
|
|
56
|
+
const crc2 = CRC.crc(v2, 0, v2.length) >>> 0;
|
|
57
|
+
expect(crc2).toBeGreaterThanOrEqual(0);
|
|
58
|
+
expect(crc2).toBeLessThanOrEqual(0xffffffff);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("computes CRC on a subrange using offset and length", () => {
|
|
62
|
+
const enc = new TextEncoder();
|
|
63
|
+
const full = enc.encode("ABCDEF");
|
|
64
|
+
const subCrc = CRC.crc(full, 1, 3); // "BCD"
|
|
65
|
+
|
|
66
|
+
const sub = enc.encode("BCD");
|
|
67
|
+
const expected = CRC.crc(sub, 0, sub.length);
|
|
68
|
+
|
|
69
|
+
expect(subCrc >>> 0).toBe(expected >>> 0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import UPNG from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
describe("UPNG public API", () => {
|
|
5
|
+
test("exposes encode/decode helpers", () => {
|
|
6
|
+
expect(typeof UPNG.encode).toBe("function");
|
|
7
|
+
expect(typeof UPNG.decode).toBe("function");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { deflate } from "@chr33s/pdf-common";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { Inflator } from "../src/inflator.js";
|
|
4
|
+
|
|
5
|
+
describe("Inflator tables initialization", () => {
|
|
6
|
+
const D = Inflator.D;
|
|
7
|
+
|
|
8
|
+
test("creates all expected table types with correct lengths", () => {
|
|
9
|
+
expect(D.m).toBeInstanceOf(Uint16Array);
|
|
10
|
+
expect(D.v).toBeInstanceOf(Uint16Array);
|
|
11
|
+
expect(D.B).toBeInstanceOf(Uint16Array);
|
|
12
|
+
expect(D.h).toBeInstanceOf(Uint32Array);
|
|
13
|
+
expect(D.g).toBeInstanceOf(Uint16Array);
|
|
14
|
+
expect(D.A).toBeInstanceOf(Uint16Array);
|
|
15
|
+
expect(D.k).toBeInstanceOf(Uint16Array);
|
|
16
|
+
expect(D.n).toBeInstanceOf(Uint16Array);
|
|
17
|
+
expect(D.C).toBeInstanceOf(Uint16Array);
|
|
18
|
+
expect(D.i).toBeInstanceOf(Uint16Array);
|
|
19
|
+
expect(D.r).toBeInstanceOf(Uint32Array);
|
|
20
|
+
expect(D.f).toBeInstanceOf(Uint32Array);
|
|
21
|
+
expect(D.l).toBeInstanceOf(Uint32Array);
|
|
22
|
+
expect(D.u).toBeInstanceOf(Uint32Array);
|
|
23
|
+
expect(D.q).toBeInstanceOf(Uint16Array);
|
|
24
|
+
expect(D.j).toBeInstanceOf(Uint16Array);
|
|
25
|
+
|
|
26
|
+
expect(D.m.length).toBe(16);
|
|
27
|
+
expect(D.v.length).toBe(16);
|
|
28
|
+
expect(D.B.length).toBe(32);
|
|
29
|
+
expect(D.h.length).toBe(32);
|
|
30
|
+
expect(D.g.length).toBe(512);
|
|
31
|
+
expect(D.A.length).toBe(32);
|
|
32
|
+
expect(D.k.length).toBe(32768);
|
|
33
|
+
expect(D.n.length).toBe(32768);
|
|
34
|
+
expect(D.C.length).toBe(512);
|
|
35
|
+
expect(D.i.length).toBe(1 << 15);
|
|
36
|
+
expect(D.r.length).toBe(286);
|
|
37
|
+
expect(D.f.length).toBe(30);
|
|
38
|
+
expect(D.l.length).toBe(19);
|
|
39
|
+
expect(D.u.length).toBe(15000);
|
|
40
|
+
expect(D.q.length).toBe(1 << 16);
|
|
41
|
+
expect(D.j.length).toBe(1 << 15);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("precomputes correct fixed Huffman length and distance tables for some entries", () => {
|
|
45
|
+
// For fixed Huffman, the first 144 literal/length symbols use 8-bit codes.
|
|
46
|
+
// `tables.s` is the canonical (code,length) pair array backing `g`.
|
|
47
|
+
// Check some sample lengths.
|
|
48
|
+
const s = D.s;
|
|
49
|
+
// s is [code0, len0, code1, len1, ...]
|
|
50
|
+
const len0 = s[1];
|
|
51
|
+
const len143 = s[(143 << 1) + 1];
|
|
52
|
+
const len144 = s[(144 << 1) + 1];
|
|
53
|
+
|
|
54
|
+
expect(len0).toBe(8);
|
|
55
|
+
expect(len143).toBe(8);
|
|
56
|
+
expect(len144).toBe(9); // start of the 9-bit block
|
|
57
|
+
|
|
58
|
+
// Distance codes for fixed Huffman use 5 bits each.
|
|
59
|
+
const t = D.t;
|
|
60
|
+
const distLen0 = t[1];
|
|
61
|
+
const distLen29 = t[(29 << 1) + 1];
|
|
62
|
+
expect(distLen0).toBe(5);
|
|
63
|
+
expect(distLen29).toBe(5);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("bit-reversal table i is self-consistent for a few sample entries", () => {
|
|
67
|
+
const i = D.i;
|
|
68
|
+
// For some simple patterns, reversing twice should give the original index
|
|
69
|
+
const samples = [0, 1, 0b101010101010101, 0b111000000000000, 0x7fff];
|
|
70
|
+
for (const v of samples) {
|
|
71
|
+
const rev = i[v];
|
|
72
|
+
const rev2 = i[rev];
|
|
73
|
+
expect(rev2).toBe(v);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("Inflator helpers", () => {
|
|
79
|
+
test("H expands buffer capacity when needed and preserves content", () => {
|
|
80
|
+
const original = new Uint8Array([1, 2, 3, 4]);
|
|
81
|
+
const same = Inflator.H(original, 4);
|
|
82
|
+
expect(same).toBe(original);
|
|
83
|
+
|
|
84
|
+
const expanded = Inflator.H(original, 10);
|
|
85
|
+
expect(expanded).not.toBe(original);
|
|
86
|
+
expect(expanded.length).toBeGreaterThanOrEqual(10);
|
|
87
|
+
expect(Array.from(expanded.slice(0, 4))).toEqual([1, 2, 3, 4]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("C_inner assigns canonical codes consistently with straightforward implementation", () => {
|
|
91
|
+
// Build a small length array: 4 symbols with lengths [2,3,3,1]
|
|
92
|
+
const lengths = [2, 3, 3, 1];
|
|
93
|
+
// o: [code0,len0,code1,len1,...] initially codes are 0
|
|
94
|
+
const o: number[] = [];
|
|
95
|
+
for (const len of lengths) {
|
|
96
|
+
o.push(0, len);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Run C_inner with max bits = 3
|
|
100
|
+
Inflator.C_inner(o, 3);
|
|
101
|
+
|
|
102
|
+
// Our own simple canonical-code computation for comparison
|
|
103
|
+
const maxBits = 3;
|
|
104
|
+
const blCount: number[] = Array.from({ length: maxBits + 1 }).fill(0) as number[];
|
|
105
|
+
lengths.forEach((l) => {
|
|
106
|
+
if (l > 0) blCount[l]++;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const nextCode: number[] = Array.from({ length: maxBits + 1 }).fill(0) as number[];
|
|
110
|
+
let code = 0;
|
|
111
|
+
for (let bits = 1; bits <= maxBits; bits++) {
|
|
112
|
+
code = (code + blCount[bits - 1]) << 1;
|
|
113
|
+
nextCode[bits] = code;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const expectedCodes: number[] = [];
|
|
117
|
+
for (const len of lengths) {
|
|
118
|
+
if (len === 0) {
|
|
119
|
+
expectedCodes.push(0);
|
|
120
|
+
} else {
|
|
121
|
+
const c = nextCode[len];
|
|
122
|
+
expectedCodes.push(c);
|
|
123
|
+
nextCode[len]++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const actualCodes = [];
|
|
128
|
+
for (let idx = 0; idx < o.length; idx += 2) {
|
|
129
|
+
actualCodes.push(o[idx]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
expect(actualCodes).toEqual(expectedCodes);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("d maps lengths into (code,length) pairs and returns max length", () => {
|
|
136
|
+
const lengths = [3, 0, 5, 1]; // I = 4 symbols
|
|
137
|
+
const target: number[] = Array.from({ length: 8 }).fill(0) as number[]; // length = 8 => 4 pairs
|
|
138
|
+
const maxBits = Inflator.d(lengths, 0, lengths.length, target);
|
|
139
|
+
|
|
140
|
+
// target: [code0,len0, code1,len1, ...] but C_inner hasn't run yet,
|
|
141
|
+
// so codes should all be 0 and lengths should match.
|
|
142
|
+
expect(target).toEqual([0, 3, 0, 0, 0, 5, 0, 1]);
|
|
143
|
+
expect(maxBits).toBe(5);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("Inflator.inflateRaw", () => {
|
|
148
|
+
test("returns empty output for special case header 0x03 0x00", () => {
|
|
149
|
+
const input = new Uint8Array([0x03, 0x00]); // triggers early-return branch
|
|
150
|
+
const result = Inflator.inflateRaw(input);
|
|
151
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
152
|
+
expect(result.length).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("inflates a stored (uncompressed) block correctly", () => {
|
|
156
|
+
// Deflate "ABC" as a single stored block:
|
|
157
|
+
// BFINAL=1, BTYPE=00
|
|
158
|
+
// LEN=3, NLEN=~3 (0xFFFC)
|
|
159
|
+
// raw data: 0x41,0x42,0x43
|
|
160
|
+
//
|
|
161
|
+
// Bits: 1 (final) + 00 (stored) => 001 => 0x01 at low bits
|
|
162
|
+
// We'll assemble minimal correct stored block bytes:
|
|
163
|
+
const stored = new Uint8Array([
|
|
164
|
+
0x01, // BFINAL=1,BTYPE=00,and padding
|
|
165
|
+
0x03,
|
|
166
|
+
0x00, // LEN = 3
|
|
167
|
+
0xfc,
|
|
168
|
+
0xff, // NLEN = ~3
|
|
169
|
+
0x41,
|
|
170
|
+
0x42,
|
|
171
|
+
0x43, // "ABC"
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
const out = Inflator.inflateRaw(stored);
|
|
175
|
+
expect(new TextDecoder().decode(out)).toBe("ABC");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("inflates a fixed Huffman block identically to deflateRaw", async () => {
|
|
179
|
+
const text = "Hello, world! Hello, world!";
|
|
180
|
+
const encoder = new TextEncoder();
|
|
181
|
+
const data = encoder.encode(text);
|
|
182
|
+
|
|
183
|
+
const compressed = await deflate(data, "deflate-raw");
|
|
184
|
+
const ours = Inflator.inflateRaw(compressed);
|
|
185
|
+
|
|
186
|
+
expect(new TextDecoder().decode(ours)).toBe(text);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("inflates a dynamic Huffman block identically to deflateRaw", async () => {
|
|
190
|
+
const text = "Dynamic Huffman blocks are used for better compression on varied data.";
|
|
191
|
+
const encoder = new TextEncoder();
|
|
192
|
+
const data = encoder.encode(text);
|
|
193
|
+
|
|
194
|
+
// deflateRaw uses dynamic Huffman by default
|
|
195
|
+
const compressed = await deflate(data, "deflate-raw"); // contains dynamic blocks
|
|
196
|
+
const ours = Inflator.inflateRaw(compressed);
|
|
197
|
+
|
|
198
|
+
expect(new TextDecoder().decode(ours)).toBe(text);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("supports inflating into a preallocated output buffer", async () => {
|
|
202
|
+
const text = "Preallocated output buffer test.";
|
|
203
|
+
const encoder = new TextEncoder();
|
|
204
|
+
const data = encoder.encode(text);
|
|
205
|
+
|
|
206
|
+
const compressed = await deflate(data, "deflate-raw");
|
|
207
|
+
|
|
208
|
+
// Preallocate a buffer with exactly the required size
|
|
209
|
+
const out = new Uint8Array(text.length);
|
|
210
|
+
const result = Inflator.inflateRaw(compressed, out);
|
|
211
|
+
|
|
212
|
+
// Using supplied buffer should either reuse or slice it exactly to size
|
|
213
|
+
expect(result.length).toBe(text.length);
|
|
214
|
+
expect(new TextDecoder().decode(result)).toBe(text);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("grows output buffer as needed when not provided", async () => {
|
|
218
|
+
const text = "X".repeat(100000); // big to force resizing via H()
|
|
219
|
+
const encoder = new TextEncoder();
|
|
220
|
+
const data = encoder.encode(text);
|
|
221
|
+
const compressed = await deflate(data, "deflate-raw");
|
|
222
|
+
|
|
223
|
+
const result = Inflator.inflateRaw(compressed);
|
|
224
|
+
expect(result.length).toBe(text.length);
|
|
225
|
+
expect(new TextDecoder().decode(result)).toBe(text);
|
|
226
|
+
});
|
|
227
|
+
});
|