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/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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }