lzma1 0.2.0 → 0.3.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/README.md +10 -3
- package/lib/decoder.js +84 -85
- package/lib/encoder.js +794 -143
- package/lib/index.js +0 -1
- package/lib/lz-window.js +3 -4
- package/lib/lzma.js +50 -1383
- package/lib/match-finder.js +402 -0
- package/lib/range-decoder.js +3 -16
- package/lib/range-encoder.js +14 -24
- package/lib/streams.js +64 -1
- package/lib/utils.js +4 -102
- package/package.json +24 -13
- package/src/decoder.ts +604 -0
- package/src/encoder.ts +2108 -0
- package/src/index.ts +71 -0
- package/src/len-coder.ts +217 -0
- package/src/lit-coder.ts +196 -0
- package/src/lz-window.ts +99 -0
- package/src/lzma.ts +142 -0
- package/src/match-finder.ts +548 -0
- package/src/range-bit-tree-coder.ts +109 -0
- package/src/range-decoder.ts +98 -0
- package/src/range-encoder.ts +136 -0
- package/src/streams.ts +73 -0
- package/src/utils.ts +263 -0
- package/lib/chunker.d.ts +0 -46
- package/lib/chunker.js +0 -68
- package/lib/decoder.d.ts +0 -80
- package/lib/encoder.d.ts +0 -266
- package/lib/index.d.ts +0 -38
- package/lib/len-coder.d.ts +0 -70
- package/lib/lit-coder.d.ts +0 -63
- package/lib/lz-in-window.d.ts +0 -43
- package/lib/lz-in-window.js +0 -132
- package/lib/lz-window.d.ts +0 -35
- package/lib/lzma.d.ts +0 -107
- package/lib/match-finder-config.d.ts +0 -34
- package/lib/match-finder-config.js +0 -63
- package/lib/range-bit-tree-coder.d.ts +0 -34
- package/lib/range-decoder.d.ts +0 -34
- package/lib/range-encoder.d.ts +0 -46
- package/lib/streams.d.ts +0 -32
- package/lib/utils.d.ts +0 -127
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Filip Seman
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type CompressionMode,
|
|
9
|
+
LZMA,
|
|
10
|
+
} from "./lzma.js";
|
|
11
|
+
|
|
12
|
+
export type { CompressionMode } from "./lzma.js";
|
|
13
|
+
export { CRC32_TABLE } from "./utils.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compresses data using LZMA algorithm
|
|
17
|
+
*
|
|
18
|
+
* @param data Data to compress - can be Uint8Array or ArrayBuffer
|
|
19
|
+
* @param mode Compression mode (1-9), defaults to 5
|
|
20
|
+
* @returns Compressed data as a byte array
|
|
21
|
+
*/
|
|
22
|
+
export function compress(
|
|
23
|
+
data: Uint8Array | ArrayBuffer,
|
|
24
|
+
mode: CompressionMode = 5,
|
|
25
|
+
): Uint8Array {
|
|
26
|
+
// Convert ArrayBuffer to Uint8Array if needed
|
|
27
|
+
const input = data instanceof ArrayBuffer
|
|
28
|
+
? new Uint8Array(data)
|
|
29
|
+
: data;
|
|
30
|
+
const result = new LZMA().compress(input, mode);
|
|
31
|
+
return new Uint8Array(result);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compresses data using LZMA algorithm
|
|
36
|
+
*
|
|
37
|
+
* @param data String to compress
|
|
38
|
+
* @param mode Compression mode (1-9), defaults to 5
|
|
39
|
+
* @returns Compressed data as byte array
|
|
40
|
+
*/
|
|
41
|
+
export function compressString(
|
|
42
|
+
data: string,
|
|
43
|
+
mode: CompressionMode = 5,
|
|
44
|
+
): Uint8Array {
|
|
45
|
+
const compressedData = new LZMA().compressString(data, mode);
|
|
46
|
+
return new Uint8Array(compressedData);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Decompresses LZMA compressed data
|
|
51
|
+
*
|
|
52
|
+
* @param data Compressed data as Uint8Array or ArrayBuffer
|
|
53
|
+
* @returns Decompressed data
|
|
54
|
+
*/
|
|
55
|
+
export function decompress(data: Uint8Array | ArrayBuffer): Uint8Array {
|
|
56
|
+
const input = data instanceof ArrayBuffer
|
|
57
|
+
? new Uint8Array(data)
|
|
58
|
+
: data;
|
|
59
|
+
const decompressedData = new LZMA().decompress(input);
|
|
60
|
+
return new Uint8Array(decompressedData);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Decompresses LZMA compressed data
|
|
65
|
+
*
|
|
66
|
+
* @param data Compressed data as Uint8Array or ArrayBuffer
|
|
67
|
+
* @returns Decompressed data as string
|
|
68
|
+
*/
|
|
69
|
+
export function decompressString(data: Uint8Array | ArrayBuffer): string {
|
|
70
|
+
return new LZMA().decompressString(data);
|
|
71
|
+
}
|
package/src/len-coder.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BitTree,
|
|
3
|
+
createBitTree,
|
|
4
|
+
getBitPrice,
|
|
5
|
+
initArray,
|
|
6
|
+
initBitModels,
|
|
7
|
+
} from "./utils.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Range encoder interface for LenEncoder to communicate with
|
|
11
|
+
*/
|
|
12
|
+
export interface RangeEncoder {
|
|
13
|
+
encodeBit(probs: number[], index: number, symbol: number): void;
|
|
14
|
+
encodeBitTree(encoder: BitTree, symbol: number): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Length encoder class for LZMA compression
|
|
19
|
+
* Handles encoding of match lengths with price optimization
|
|
20
|
+
*/
|
|
21
|
+
export class LenEncoder {
|
|
22
|
+
// Choice probability arrays for length range selection
|
|
23
|
+
private choice: number[] = initArray(2);
|
|
24
|
+
|
|
25
|
+
// Low range coders (for lengths 2-9)
|
|
26
|
+
private lowCoder: BitTree[] = [];
|
|
27
|
+
|
|
28
|
+
// Mid range coders (for lengths 10-17)
|
|
29
|
+
private midCoder: BitTree[] = [];
|
|
30
|
+
|
|
31
|
+
// High range coder (for lengths 18+)
|
|
32
|
+
private highCoder: BitTree = createBitTree(8);
|
|
33
|
+
|
|
34
|
+
// Price optimization properties
|
|
35
|
+
private tableSize: number = 0;
|
|
36
|
+
private prices: number[] = [];
|
|
37
|
+
private counters: number[] = [];
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
// Initialize low and mid coders for all position states (up to 16)
|
|
41
|
+
for (let posState = 0; posState < 16; ++posState) {
|
|
42
|
+
this.lowCoder[posState] = createBitTree(3);
|
|
43
|
+
this.midCoder[posState] = createBitTree(3);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the encoder with specified number of position states
|
|
49
|
+
*/
|
|
50
|
+
init(numPosStates: number): void {
|
|
51
|
+
// Initialize choice probability models
|
|
52
|
+
initBitModels(this.choice);
|
|
53
|
+
|
|
54
|
+
// Initialize low and mid coders for each position state
|
|
55
|
+
for (let posState = 0; posState < numPosStates; ++posState) {
|
|
56
|
+
initBitModels(this.lowCoder[posState].models);
|
|
57
|
+
initBitModels(this.midCoder[posState].models);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Initialize high coder
|
|
61
|
+
initBitModels(this.highCoder.models);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Encode a length value using the provided range encoder
|
|
66
|
+
*/
|
|
67
|
+
encode(symbol: number, posState: number, rangeEncoder: RangeEncoder): void {
|
|
68
|
+
if (symbol < 8) {
|
|
69
|
+
// Length 2-9: use low coder
|
|
70
|
+
rangeEncoder.encodeBit(this.choice, 0, 0);
|
|
71
|
+
rangeEncoder.encodeBitTree(this.lowCoder[posState], symbol);
|
|
72
|
+
} else {
|
|
73
|
+
symbol -= 8;
|
|
74
|
+
rangeEncoder.encodeBit(this.choice, 0, 1);
|
|
75
|
+
|
|
76
|
+
if (symbol < 8) {
|
|
77
|
+
// Length 10-17: use mid coder
|
|
78
|
+
rangeEncoder.encodeBit(this.choice, 1, 0);
|
|
79
|
+
rangeEncoder.encodeBitTree(this.midCoder[posState], symbol);
|
|
80
|
+
} else {
|
|
81
|
+
// Length 18+: use high coder
|
|
82
|
+
rangeEncoder.encodeBit(this.choice, 1, 1);
|
|
83
|
+
rangeEncoder.encodeBitTree(this.highCoder, symbol - 8);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Encode with price table update
|
|
90
|
+
*/
|
|
91
|
+
encodeWithUpdate(symbol: number, posState: number, rangeEncoder: RangeEncoder): void {
|
|
92
|
+
this.encode(symbol, posState, rangeEncoder);
|
|
93
|
+
|
|
94
|
+
if (this.counters && (this.counters[posState] -= 1) == 0) {
|
|
95
|
+
// Reset counter and update prices if needed
|
|
96
|
+
this.counters[posState] = this.tableSize;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get price for encoding a symbol at the given position state
|
|
102
|
+
*/
|
|
103
|
+
getPrice(symbol: number, posState: number): number {
|
|
104
|
+
return this.prices[posState * 0x110 + symbol];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Initialize as a price table encoder
|
|
109
|
+
*/
|
|
110
|
+
initPriceTable(): void {
|
|
111
|
+
this.prices = [];
|
|
112
|
+
this.counters = [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set table size for price optimization
|
|
117
|
+
*/
|
|
118
|
+
setTableSize(size: number): void {
|
|
119
|
+
this.tableSize = size;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set table size and update internal counters
|
|
124
|
+
*/
|
|
125
|
+
setTableSizeAndInitCounters(size: number, numPosStates: number): void {
|
|
126
|
+
this.tableSize = size;
|
|
127
|
+
if (this.counters) {
|
|
128
|
+
for (let posState = 0; posState < numPosStates; ++posState) {
|
|
129
|
+
this.counters[posState] = size;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get table size
|
|
136
|
+
*/
|
|
137
|
+
getTableSize(): number {
|
|
138
|
+
return this.tableSize;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update price tables for all position states
|
|
143
|
+
*/
|
|
144
|
+
updateTables(numPosStates: number): void {
|
|
145
|
+
if (!this.prices || !this.counters) {
|
|
146
|
+
this.initPriceTable();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (let posState = 0; posState < numPosStates; ++posState) {
|
|
150
|
+
this.setPrices(
|
|
151
|
+
posState,
|
|
152
|
+
this.tableSize,
|
|
153
|
+
this.prices,
|
|
154
|
+
0,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (this.counters) {
|
|
158
|
+
this.counters[posState] = this.tableSize;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Private methods for internal state management
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Calculate price for bit tree encoder
|
|
167
|
+
*/
|
|
168
|
+
private getBitTreePrice(encoder: BitTree, symbol: number): number {
|
|
169
|
+
let bit, bitIndex, m = 1, price = 0;
|
|
170
|
+
|
|
171
|
+
for (bitIndex = encoder.numBitLevels; bitIndex != 0;) {
|
|
172
|
+
bitIndex -= 1;
|
|
173
|
+
bit = symbol >>> bitIndex & 1;
|
|
174
|
+
price += this.getBitPrice(encoder.models[m], bit);
|
|
175
|
+
m = (m << 1) + bit;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return price;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get price for a single bit
|
|
183
|
+
*/
|
|
184
|
+
private getBitPrice(prob: number, symbol: number): number {
|
|
185
|
+
return getBitPrice(prob, symbol);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set prices for all symbols in a position state range
|
|
190
|
+
*/
|
|
191
|
+
private setPrices(posState: number, numSymbols: number, prices: number[], priceIndex: number): void {
|
|
192
|
+
const a0 = this.getBitPrice(this.choice[0], 0);
|
|
193
|
+
const a1 = this.getBitPrice(this.choice[0], 1);
|
|
194
|
+
const b0 = a1 + this.getBitPrice(this.choice[1], 0);
|
|
195
|
+
const b1 = a1 + this.getBitPrice(this.choice[1], 1);
|
|
196
|
+
|
|
197
|
+
let i = 0;
|
|
198
|
+
const st = priceIndex + posState * 0x110;
|
|
199
|
+
|
|
200
|
+
// Set prices for low range (lengths 2-9)
|
|
201
|
+
for (i = 0; i < 8; ++i) {
|
|
202
|
+
if (i >= numSymbols) return;
|
|
203
|
+
prices[st + i] = a0 + this.getBitTreePrice(this.lowCoder[posState], i);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Set prices for mid range (lengths 10-17)
|
|
207
|
+
for (; i < 16; ++i) {
|
|
208
|
+
if (i >= numSymbols) return;
|
|
209
|
+
prices[st + i] = b0 + this.getBitTreePrice(this.midCoder[posState], i - 8);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Set prices for high range (lengths 18+)
|
|
213
|
+
for (; i < numSymbols; ++i) {
|
|
214
|
+
prices[st + i] = b1 + this.getBitTreePrice(this.highCoder, i - 8 - 8);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/lit-coder.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BasicRangeDecoder,
|
|
3
|
+
type BasicRangeEncoder,
|
|
4
|
+
getBitPrice,
|
|
5
|
+
initArray,
|
|
6
|
+
} from "./utils.js";
|
|
7
|
+
|
|
8
|
+
export class LitSubCoder {
|
|
9
|
+
private coders: number[];
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.coders = initArray(0x300, 0x400);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decode normal literal symbol
|
|
17
|
+
*/
|
|
18
|
+
decodeNormal(rd: BasicRangeDecoder): number {
|
|
19
|
+
let symbol = 1;
|
|
20
|
+
while (symbol < 0x100) {
|
|
21
|
+
const i = rd.decodeBit(this.coders, symbol);
|
|
22
|
+
symbol = (symbol << 1) | i;
|
|
23
|
+
}
|
|
24
|
+
return symbol & 0xFF;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decode literal symbol with match byte context
|
|
29
|
+
*/
|
|
30
|
+
decodeWithMatchByte(rd: BasicRangeDecoder, matchByte: number): number {
|
|
31
|
+
let uMatchByte = matchByte;
|
|
32
|
+
let symbol = 1;
|
|
33
|
+
|
|
34
|
+
while (symbol < 0x100) {
|
|
35
|
+
const matchBit = (uMatchByte >> 7) & 1;
|
|
36
|
+
uMatchByte <<= 1;
|
|
37
|
+
const bit = rd.decodeBit(this.coders, ((1 + matchBit) << 8) + symbol);
|
|
38
|
+
symbol = (symbol << 1) | bit;
|
|
39
|
+
|
|
40
|
+
if (matchBit !== bit) {
|
|
41
|
+
while (symbol < 0x100) {
|
|
42
|
+
const i = rd.decodeBit(this.coders, symbol);
|
|
43
|
+
symbol = (symbol << 1) | i;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return symbol & 0xFF;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encode literal symbol
|
|
53
|
+
*/
|
|
54
|
+
encode(re: BasicRangeEncoder, symbol: number): void {
|
|
55
|
+
let context = 1;
|
|
56
|
+
for (let i = 7; i >= 0; i--) {
|
|
57
|
+
const bit = (symbol >> i) & 1;
|
|
58
|
+
re.encodeBit(this.coders, context, bit);
|
|
59
|
+
context = (context << 1) | bit;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Encode literal symbol with match byte context
|
|
65
|
+
*/
|
|
66
|
+
encodeMatched(re: BasicRangeEncoder, matchByte: number, symbol: number): void {
|
|
67
|
+
let uMatchByte = matchByte;
|
|
68
|
+
let context = 1;
|
|
69
|
+
let same = true;
|
|
70
|
+
|
|
71
|
+
for (let i = 7; i >= 0; i--) {
|
|
72
|
+
const bit = (symbol >> i) & 1;
|
|
73
|
+
let state = context;
|
|
74
|
+
|
|
75
|
+
if (same) {
|
|
76
|
+
const matchBit = (uMatchByte >> i) & 1;
|
|
77
|
+
state += (1 + matchBit) << 8;
|
|
78
|
+
same = matchBit === bit;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
re.encodeBit(this.coders, state, bit);
|
|
82
|
+
context = (context << 1) | bit;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get price for encoding literal symbol
|
|
88
|
+
*/
|
|
89
|
+
getPrice(matchMode: boolean, matchByte: number, symbol: number): number {
|
|
90
|
+
let uMatchByte = matchByte;
|
|
91
|
+
let price = 0;
|
|
92
|
+
let context = 1;
|
|
93
|
+
let i = 7;
|
|
94
|
+
|
|
95
|
+
if (matchMode) {
|
|
96
|
+
while (i >= 0) {
|
|
97
|
+
const matchBit = (uMatchByte >> i) & 1;
|
|
98
|
+
const bit = (symbol >> i) & 1;
|
|
99
|
+
price += getBitPrice(this.coders[(1 + matchBit) << 8 + context], bit);
|
|
100
|
+
context = (context << 1) | bit;
|
|
101
|
+
|
|
102
|
+
if (matchBit !== bit) {
|
|
103
|
+
i--;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
i--;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
while (i >= 0) {
|
|
111
|
+
const bit = (symbol >> i) & 1;
|
|
112
|
+
price += getBitPrice(this.coders[context], bit);
|
|
113
|
+
context = (context << 1) | bit;
|
|
114
|
+
i--;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return price;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reset coder to initial state
|
|
122
|
+
*/
|
|
123
|
+
reset(): void {
|
|
124
|
+
this.coders.fill(1024);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get decoders array (for compatibility with LiteralDecoderEncoder2)
|
|
129
|
+
*/
|
|
130
|
+
get decoders(): number[] {
|
|
131
|
+
return this.coders;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class LitCoder {
|
|
136
|
+
private _coders: LitSubCoder[];
|
|
137
|
+
private _numPrevBits: number;
|
|
138
|
+
private _posMask: number;
|
|
139
|
+
|
|
140
|
+
constructor(numPosBits: number, numPrevBits: number) {
|
|
141
|
+
const numStates = 1 << (numPrevBits + numPosBits);
|
|
142
|
+
this._coders = [];
|
|
143
|
+
this._numPrevBits = numPrevBits;
|
|
144
|
+
this._posMask = (1 << numPosBits) - 1;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < numStates; i++) {
|
|
147
|
+
this._coders[i] = new LitSubCoder();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get sub-coder for position and previous byte
|
|
153
|
+
*/
|
|
154
|
+
getSubCoder(pos: number, prevByte: number): LitSubCoder {
|
|
155
|
+
return this._coders[
|
|
156
|
+
((pos & this._posMask) << this._numPrevBits)
|
|
157
|
+
+ (prevByte >> (8 - this._numPrevBits))
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reset all sub-coders
|
|
163
|
+
*/
|
|
164
|
+
reset(): void {
|
|
165
|
+
this._coders.forEach((coder) => coder.reset());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get number of previous bits (for compatibility)
|
|
170
|
+
*/
|
|
171
|
+
get numPrevBits(): number {
|
|
172
|
+
return this._numPrevBits;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get number of position bits (for compatibility)
|
|
177
|
+
*/
|
|
178
|
+
get numPosBits(): number {
|
|
179
|
+
// Calculate from posMask
|
|
180
|
+
return Math.log2(this._posMask + 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get position mask (for compatibility)
|
|
185
|
+
*/
|
|
186
|
+
get posMask(): number {
|
|
187
|
+
return this._posMask;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get coders array (for compatibility)
|
|
192
|
+
*/
|
|
193
|
+
get coders(): LitSubCoder[] {
|
|
194
|
+
return this._coders;
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/lz-window.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { OutputBuffer } from "./streams.js";
|
|
2
|
+
|
|
3
|
+
export class LzOutWindow {
|
|
4
|
+
buffer: Uint8Array | null = null;
|
|
5
|
+
pos: number = 0;
|
|
6
|
+
streamPos: number = 0;
|
|
7
|
+
stream: OutputBuffer | null = null;
|
|
8
|
+
windowSize: number = 0;
|
|
9
|
+
|
|
10
|
+
// Private Go-style properties
|
|
11
|
+
private w: OutputBuffer | null = null;
|
|
12
|
+
private buf: Uint8Array;
|
|
13
|
+
|
|
14
|
+
constructor(writer: OutputBuffer | null = null, windowSize: number = 4096) {
|
|
15
|
+
this.w = writer;
|
|
16
|
+
this.stream = writer;
|
|
17
|
+
this.windowSize = windowSize;
|
|
18
|
+
this.buf = new Uint8Array(windowSize);
|
|
19
|
+
this.buffer = this.buf;
|
|
20
|
+
this.pos = 0;
|
|
21
|
+
this.streamPos = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Copy a block of data from a previous position (LZ77-style)
|
|
26
|
+
*/
|
|
27
|
+
copyBlock(distance: number, length: number): void {
|
|
28
|
+
if (!this.buffer) return;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < length; i++) {
|
|
31
|
+
// Get byte from previous position
|
|
32
|
+
let sourcePos = this.pos - distance - 1;
|
|
33
|
+
if (sourcePos < 0) {
|
|
34
|
+
sourcePos += this.windowSize;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const byte = this.buffer[sourcePos];
|
|
38
|
+
this.putByte(byte);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Put a single byte into the window
|
|
44
|
+
*/
|
|
45
|
+
putByte(byte: number): void {
|
|
46
|
+
if (!this.buffer) return;
|
|
47
|
+
|
|
48
|
+
this.buffer[this.pos] = byte;
|
|
49
|
+
this.pos++;
|
|
50
|
+
this.streamPos++;
|
|
51
|
+
|
|
52
|
+
if (this.pos >= this.windowSize) {
|
|
53
|
+
this.flush();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get a byte from a relative position
|
|
59
|
+
*/
|
|
60
|
+
getByte(relativePos: number): number {
|
|
61
|
+
if (!this.buffer) return 0;
|
|
62
|
+
|
|
63
|
+
let pos = this.pos + relativePos;
|
|
64
|
+
if (pos < 0) {
|
|
65
|
+
pos += this.windowSize;
|
|
66
|
+
} else if (pos >= this.windowSize) {
|
|
67
|
+
pos -= this.windowSize;
|
|
68
|
+
}
|
|
69
|
+
return this.buffer[pos];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Flush buffered data to output writer
|
|
74
|
+
*/
|
|
75
|
+
flush(): void {
|
|
76
|
+
if (this.w && this.buffer && this.pos > 0) {
|
|
77
|
+
this.w.writeBytes(this.buffer, 0, this.pos);
|
|
78
|
+
this.pos = 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if the window is empty
|
|
84
|
+
*/
|
|
85
|
+
isEmpty(): boolean {
|
|
86
|
+
return this.streamPos === 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reset the window
|
|
91
|
+
*/
|
|
92
|
+
reset(): void {
|
|
93
|
+
this.pos = 0;
|
|
94
|
+
this.streamPos = 0;
|
|
95
|
+
if (this.buffer) {
|
|
96
|
+
this.buffer.fill(0);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/lzma.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Decoder } from "./decoder.js";
|
|
2
|
+
import { Encoder } from "./encoder.js";
|
|
3
|
+
import {
|
|
4
|
+
InputBuffer,
|
|
5
|
+
OutputBuffer,
|
|
6
|
+
} from "./streams.js";
|
|
7
|
+
|
|
8
|
+
interface Mode {
|
|
9
|
+
searchDepth: number;
|
|
10
|
+
filterStrength: number;
|
|
11
|
+
modeIndex: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* LZMA compression mode levels (1-9)
|
|
16
|
+
* Higher values provide better compression but are slower
|
|
17
|
+
*/
|
|
18
|
+
export type CompressionMode = keyof typeof MODES;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compression modes
|
|
22
|
+
*/
|
|
23
|
+
export const MODES = {
|
|
24
|
+
1: { searchDepth: 0x10, filterStrength: 0x40, modeIndex: 0x00 },
|
|
25
|
+
2: { searchDepth: 0x14, filterStrength: 0x40, modeIndex: 0x00 },
|
|
26
|
+
3: { searchDepth: 0x13, filterStrength: 0x40, modeIndex: 0x01 },
|
|
27
|
+
4: { searchDepth: 0x14, filterStrength: 0x40, modeIndex: 0x01 },
|
|
28
|
+
5: { searchDepth: 0x15, filterStrength: 0x80, modeIndex: 0x01 },
|
|
29
|
+
6: { searchDepth: 0x16, filterStrength: 0x80, modeIndex: 0x01 },
|
|
30
|
+
7: { searchDepth: 0x17, filterStrength: 0x80, modeIndex: 0x01 },
|
|
31
|
+
8: { searchDepth: 0x18, filterStrength: 0xFF, modeIndex: 0x01 },
|
|
32
|
+
9: { searchDepth: 0x19, filterStrength: 0xFF, modeIndex: 0x01 },
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export class LZMA {
|
|
36
|
+
#encoder = new Encoder();
|
|
37
|
+
#decoder = new Decoder();
|
|
38
|
+
|
|
39
|
+
public compress(
|
|
40
|
+
data: Uint8Array | ArrayBuffer,
|
|
41
|
+
mode: CompressionMode = 5,
|
|
42
|
+
): Int8Array {
|
|
43
|
+
const inputData = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
44
|
+
const output = new OutputBuffer(Math.max(32, Math.ceil(inputData.length * 1.2)));
|
|
45
|
+
const input = new InputBuffer(inputData);
|
|
46
|
+
|
|
47
|
+
this.#encoder.compress(input, output, MODES[mode]);
|
|
48
|
+
|
|
49
|
+
const result = output.toArray();
|
|
50
|
+
return new Int8Array(result.buffer, result.byteOffset, result.byteLength);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public compressString(
|
|
54
|
+
data: string,
|
|
55
|
+
mode: CompressionMode = 5,
|
|
56
|
+
): Int8Array {
|
|
57
|
+
return this.compress(new Uint8Array(this.#encodeString(data)), mode);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public decompress(bytearray: Uint8Array | ArrayBuffer): Uint8Array {
|
|
61
|
+
const inputData = bytearray instanceof ArrayBuffer ? new Uint8Array(bytearray) : bytearray;
|
|
62
|
+
const output = new OutputBuffer(Math.max(32, inputData.length * 2));
|
|
63
|
+
const input = new InputBuffer(inputData);
|
|
64
|
+
|
|
65
|
+
this.#decoder.decompress(input, output);
|
|
66
|
+
|
|
67
|
+
return output.toArray();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public decompressString(bytearray: Uint8Array | ArrayBuffer): string {
|
|
71
|
+
const decodedByteArray = this.decompress(bytearray);
|
|
72
|
+
const result = this.#decodeUTF8(decodedByteArray);
|
|
73
|
+
|
|
74
|
+
if (typeof result === "string") {
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
return String.fromCharCode(...result);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#encodeString(inputString: string): number[] {
|
|
81
|
+
const l = inputString.length;
|
|
82
|
+
const chars: number[] = [];
|
|
83
|
+
for (let i = 0; i < l; ++i) {
|
|
84
|
+
chars[i] = inputString.charCodeAt(i);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const data: number[] = [];
|
|
88
|
+
let elen = 0;
|
|
89
|
+
for (let i = 0; i < l; ++i) {
|
|
90
|
+
const ch = chars[i];
|
|
91
|
+
if (ch >= 1 && ch <= 0x7F) {
|
|
92
|
+
data[elen++] = ch << 24 >> 24;
|
|
93
|
+
} else if (!ch || ch >= 0x80 && ch <= 0x7FF) {
|
|
94
|
+
data[elen++] = (0xC0 | ch >> 6 & 0x1F) << 24 >> 24;
|
|
95
|
+
data[elen++] = (0x80 | ch & 0x3F) << 24 >> 24;
|
|
96
|
+
} else {
|
|
97
|
+
data[elen++] = (0xE0 | ch >> 12 & 0x0F) << 24 >> 24;
|
|
98
|
+
data[elen++] = (0x80 | ch >> 6 & 0x3F) << 24 >> 24;
|
|
99
|
+
data[elen++] = (0x80 | ch & 0x3F) << 24 >> 24;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#decodeUTF8(utf: Uint8Array): string | Uint8Array {
|
|
107
|
+
let j = 0, x, y, z, l = utf.length, buf: string[] = [], charCodes: number[] = [];
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < l; ++i, ++j) {
|
|
110
|
+
x = utf[i] & 0xFF;
|
|
111
|
+
if (!(x & 0x80)) {
|
|
112
|
+
if (!x) return utf;
|
|
113
|
+
charCodes[j] = x;
|
|
114
|
+
} else if ((x & 0xE0) == 0xC0) {
|
|
115
|
+
if (i + 1 >= l) return String.fromCharCode(...utf);
|
|
116
|
+
y = utf[++i] & 0xFF;
|
|
117
|
+
if ((y & 0xC0) != 0x80) return String.fromCharCode(...utf);
|
|
118
|
+
charCodes[j] = ((x & 0x1F) << 6) | (y & 0x3F);
|
|
119
|
+
} else if ((x & 0xF0) == 0xE0) {
|
|
120
|
+
if (i + 2 >= l) return utf;
|
|
121
|
+
y = utf[++i] & 0xFF;
|
|
122
|
+
if ((y & 0xC0) != 0x80) return utf;
|
|
123
|
+
z = utf[++i] & 0xFF;
|
|
124
|
+
if ((z & 0xC0) != 0x80) return utf;
|
|
125
|
+
charCodes[j] = ((x & 0x0F) << 0x0C) | ((y & 0x3F) << 6) | (z & 0x3F);
|
|
126
|
+
} else {
|
|
127
|
+
return utf;
|
|
128
|
+
}
|
|
129
|
+
if (j == 0x3FFF) {
|
|
130
|
+
buf.push(String.fromCharCode.apply(String, charCodes));
|
|
131
|
+
j = -1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (j > 0) {
|
|
136
|
+
charCodes.length = j;
|
|
137
|
+
buf.push(String.fromCharCode.apply(String, charCodes));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return buf.join("");
|
|
141
|
+
}
|
|
142
|
+
}
|