expo 57.0.12 → 57.0.13
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/android/build.gradle +2 -2
- package/build/winter/TextDecoder.d.ts +2 -8
- package/build/winter/TextDecoder.d.ts.map +1 -1
- package/bundledNativeModules.json +34 -34
- package/ios/AppDelegates/EXBundleConfiguration.h +23 -0
- package/ios/AppDelegates/EXBundleConfiguration.mm +85 -0
- package/ios/AppDelegates/ExpoReactNativeFactory.swift +54 -2
- package/ios/Expo.h +1 -0
- package/package.json +12 -12
- package/src/winter/TextDecoder.ts +361 -361
- package/src/winter/__tests__/TextDecoder.test.native.ts +142 -0
- package/template.tgz +0 -0
|
@@ -1,427 +1,427 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `TextEncoder` is in Hermes and we only need utf-8 decoder for React Server Components.
|
|
3
|
-
//
|
|
4
|
-
// https://github.com/inexorabletash/text-encoding/blob/3f330964c0e97e1ed344c2a3e963f4598610a7ad/lib/encoding.js#L1
|
|
1
|
+
// UTF-8-only TextDecoder fallback for runtimes that do not provide one.
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
* Checks if a number is within a specified range.
|
|
8
|
-
* @param a The number to test.
|
|
9
|
-
* @param min The minimum value in the range, inclusive.
|
|
10
|
-
* @param max The maximum value in the range, inclusive.
|
|
11
|
-
* @returns `true` if a passed number is within the specified range.
|
|
12
|
-
*/
|
|
13
|
-
function inRange(a: number, min: number, max: number): boolean {
|
|
14
|
-
return min <= a && a <= max;
|
|
15
|
-
}
|
|
3
|
+
const EMPTY_BYTES = new Uint8Array(0);
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < codePoints.length; ++i) {
|
|
25
|
-
let cp = codePoints[i];
|
|
5
|
+
interface TextDecoderState {
|
|
6
|
+
ignoreBOM: boolean;
|
|
7
|
+
fatal: boolean;
|
|
8
|
+
bomSeen: boolean;
|
|
9
|
+
pending: Uint8Array;
|
|
10
|
+
streaming: boolean;
|
|
11
|
+
}
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
13
|
+
function assertValidUTF8Label(label: unknown): void {
|
|
14
|
+
const normalizedLabel = String(label).trim().toLowerCase();
|
|
15
|
+
switch (normalizedLabel) {
|
|
16
|
+
case 'unicode-1-1-utf-8':
|
|
17
|
+
case 'unicode11utf8':
|
|
18
|
+
case 'unicode20utf8':
|
|
19
|
+
case 'utf-8':
|
|
20
|
+
case 'utf8':
|
|
21
|
+
case 'x-unicode20utf8':
|
|
22
|
+
return;
|
|
23
|
+
default:
|
|
24
|
+
throw new RangeError(`Unknown encoding: ${label} (normalized: ${normalizedLabel})`);
|
|
35
25
|
}
|
|
36
|
-
return s;
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
function normalizeBytes(input
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
28
|
+
function normalizeBytes(input: ArrayBuffer | ArrayBufferView | undefined): Uint8Array {
|
|
29
|
+
if (input === undefined) {
|
|
30
|
+
return EMPTY_BYTES;
|
|
31
|
+
} else if (input instanceof Uint8Array) {
|
|
32
|
+
return input;
|
|
33
|
+
} else if (ArrayBuffer.isView(input)) {
|
|
34
|
+
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
42
35
|
} else if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
input.buffer instanceof ArrayBuffer
|
|
36
|
+
(input[Symbol.toStringTag] as string) === 'ArrayBuffer' ||
|
|
37
|
+
(input[Symbol.toStringTag] as string) === 'SharedArrayBuffer'
|
|
46
38
|
) {
|
|
47
|
-
return new Uint8Array(input
|
|
39
|
+
return new Uint8Array(input as ArrayBuffer);
|
|
40
|
+
} else {
|
|
41
|
+
throw new TypeError('The input must be an ArrayBuffer or ArrayBufferView');
|
|
48
42
|
}
|
|
49
|
-
return new Uint8Array(0);
|
|
50
43
|
}
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*/
|
|
56
|
-
const END_OF_STREAM = -1;
|
|
57
|
-
|
|
58
|
-
const FINISHED = -1;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A stream represents an ordered sequence of tokens.
|
|
62
|
-
*
|
|
63
|
-
* @constructor
|
|
64
|
-
* @param {!(number[]|Uint8Array)} tokens Array of tokens that provide the stream.
|
|
65
|
-
*/
|
|
66
|
-
class Stream {
|
|
67
|
-
private tokens: number[];
|
|
68
|
-
|
|
69
|
-
constructor(tokens: number[] | Uint8Array) {
|
|
70
|
-
this.tokens = Array.prototype.slice.call(tokens);
|
|
71
|
-
// Reversed as push/pop is more efficient than shift/unshift.
|
|
72
|
-
this.tokens.reverse();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @return {boolean} True if end-of-stream has been hit.
|
|
77
|
-
*/
|
|
78
|
-
endOfStream(): boolean {
|
|
79
|
-
return !this.tokens.length;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* When a token is read from a stream, the first token in the
|
|
84
|
-
* stream must be returned and subsequently removed, and
|
|
85
|
-
* end-of-stream must be returned otherwise.
|
|
86
|
-
*
|
|
87
|
-
* @return {number} Get the next token from the stream, or
|
|
88
|
-
* end_of_stream.
|
|
89
|
-
*/
|
|
90
|
-
read(): number {
|
|
91
|
-
if (!this.tokens.length) return END_OF_STREAM;
|
|
92
|
-
return this.tokens.pop()!;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* When one or more tokens are prepended to a stream, those tokens
|
|
97
|
-
* must be inserted, in given order, before the first token in the
|
|
98
|
-
* stream.
|
|
99
|
-
*
|
|
100
|
-
* @param token The token(s) to prepend to the stream.
|
|
101
|
-
*/
|
|
102
|
-
prepend(token: number | number[]): void {
|
|
103
|
-
if (Array.isArray(token)) {
|
|
104
|
-
while (token.length) this.tokens.push(token.pop()!);
|
|
105
|
-
} else {
|
|
106
|
-
this.tokens.push(token);
|
|
107
|
-
}
|
|
45
|
+
function decoderError(state: TextDecoderState): string {
|
|
46
|
+
if (state.fatal) {
|
|
47
|
+
throw new TypeError('Decoder error');
|
|
108
48
|
}
|
|
49
|
+
return '\ufffd';
|
|
50
|
+
}
|
|
109
51
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
52
|
+
function appendASCII(output: string, bytes: Uint8Array, start: number, end: number): string {
|
|
53
|
+
// NOTE(@kitten): For longer strings, direct `apply` + `subarray` conversion w/o byte-for-byte copy or spreads is
|
|
54
|
+
// the most efficient by far (3x), leaning on native conversion below the variadic limit
|
|
55
|
+
const HERMES_VARIADIC_ARGUMENT_LIMIT = 4096;
|
|
56
|
+
while (start < end) {
|
|
57
|
+
output += String.fromCharCode.apply(
|
|
58
|
+
null,
|
|
59
|
+
bytes.subarray(
|
|
60
|
+
start,
|
|
61
|
+
Math.min(start + HERMES_VARIADIC_ARGUMENT_LIMIT, end)
|
|
62
|
+
) as unknown as number[]
|
|
63
|
+
);
|
|
64
|
+
start += HERMES_VARIADIC_ARGUMENT_LIMIT;
|
|
123
65
|
}
|
|
66
|
+
return output;
|
|
124
67
|
}
|
|
125
68
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
69
|
+
interface DecodeFallback {
|
|
70
|
+
output: string;
|
|
71
|
+
index: number;
|
|
129
72
|
}
|
|
130
73
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
labels: string[];
|
|
74
|
+
function fallback(output: string, index: number): DecodeFallback {
|
|
75
|
+
return { output, index };
|
|
134
76
|
}
|
|
135
77
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
78
|
+
function decodeUTF8Fast(bytes: Uint8Array, start: number): string | DecodeFallback {
|
|
79
|
+
let output = '';
|
|
80
|
+
let i = start;
|
|
81
|
+
const length = bytes.length;
|
|
82
|
+
while (i < length) {
|
|
83
|
+
const b0 = bytes[i]!;
|
|
84
|
+
if (b0 < 0x80) {
|
|
85
|
+
// NOTE(@kitten): For ASCII text, it's fastest to process the first 32 chars directly
|
|
86
|
+
// After, `appendASCII`'s variadic apply wins out
|
|
87
|
+
const directEnd = Math.min(i + 32, length);
|
|
88
|
+
do {
|
|
89
|
+
output += String.fromCharCode(bytes[i++]!);
|
|
90
|
+
} while (i < directEnd && bytes[i]! < 0x80);
|
|
91
|
+
if (i === directEnd && i < length && bytes[i]! < 0x80) {
|
|
92
|
+
const asciiStart = i;
|
|
93
|
+
do {
|
|
94
|
+
i++;
|
|
95
|
+
} while (i < length && bytes[i]! < 0x80);
|
|
96
|
+
output = appendASCII(output, bytes, asciiStart, i);
|
|
97
|
+
}
|
|
98
|
+
} else if (b0 >= 0xc2 && b0 <= 0xdf) {
|
|
99
|
+
if (i + 1 >= length) return fallback(output, i);
|
|
100
|
+
const b1 = bytes[i + 1]!;
|
|
101
|
+
if ((b1 & 0xc0) !== 0x80) return fallback(output, i);
|
|
102
|
+
output += String.fromCharCode(((b0 & 0x1f) << 6) | (b1 & 0x3f));
|
|
103
|
+
i += 2;
|
|
104
|
+
} else if (b0 >= 0xe0 && b0 <= 0xef) {
|
|
105
|
+
if (i + 2 >= length) return fallback(output, i);
|
|
106
|
+
const b1 = bytes[i + 1]!;
|
|
107
|
+
const b2 = bytes[i + 2]!;
|
|
108
|
+
if (
|
|
109
|
+
b1 < (b0 === 0xe0 ? 0xa0 : 0x80) ||
|
|
110
|
+
b1 > (b0 === 0xed ? 0x9f : 0xbf) ||
|
|
111
|
+
(b2 & 0xc0) !== 0x80
|
|
112
|
+
) {
|
|
113
|
+
return fallback(output, i);
|
|
114
|
+
}
|
|
115
|
+
output += String.fromCharCode(((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f));
|
|
116
|
+
i += 3;
|
|
117
|
+
} else if (b0 >= 0xf0 && b0 <= 0xf4) {
|
|
118
|
+
if (i + 3 >= length) return fallback(output, i);
|
|
119
|
+
const b1 = bytes[i + 1]!;
|
|
120
|
+
const b2 = bytes[i + 2]!;
|
|
121
|
+
const b3 = bytes[i + 3]!;
|
|
122
|
+
if (
|
|
123
|
+
b1 < (b0 === 0xf0 ? 0x90 : 0x80) ||
|
|
124
|
+
b1 > (b0 === 0xf4 ? 0x8f : 0xbf) ||
|
|
125
|
+
(b2 & 0xc0) !== 0x80 ||
|
|
126
|
+
(b3 & 0xc0) !== 0x80
|
|
127
|
+
) {
|
|
128
|
+
return fallback(output, i);
|
|
129
|
+
}
|
|
130
|
+
const codePoint =
|
|
131
|
+
(((b0 & 0x07) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f)) - 0x10000;
|
|
132
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
133
|
+
i += 4;
|
|
134
|
+
} else {
|
|
135
|
+
return fallback(output, i);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
177
138
|
|
|
178
|
-
|
|
179
|
-
handler: (stream: Stream, bite: number) => number | number[] | null | -1;
|
|
139
|
+
return output;
|
|
180
140
|
}
|
|
181
141
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (bite === END_OF_STREAM && this.utf8BytesNeeded !== 0) {
|
|
204
|
-
this.utf8BytesNeeded = 0;
|
|
205
|
-
return decoderError(this.options.fatal);
|
|
142
|
+
function decodeUTF8General(
|
|
143
|
+
bytes: Uint8Array,
|
|
144
|
+
state: TextDecoderState,
|
|
145
|
+
stream: boolean,
|
|
146
|
+
output = '',
|
|
147
|
+
start = 0
|
|
148
|
+
): string {
|
|
149
|
+
const { ignoreBOM } = state;
|
|
150
|
+
let { pending, bomSeen } = state;
|
|
151
|
+
let i = start;
|
|
152
|
+
const length = bytes.length;
|
|
153
|
+
|
|
154
|
+
if (pending.length > 0) {
|
|
155
|
+
const b0 = pending[0]!;
|
|
156
|
+
const bytesNeeded = b0 <= 0xdf ? 1 : b0 <= 0xef ? 2 : 3;
|
|
157
|
+
let codePoint = b0 & (bytesNeeded === 1 ? 0x1f : bytesNeeded === 2 ? 0x0f : 0x07);
|
|
158
|
+
let bytesSeen = 0;
|
|
159
|
+
|
|
160
|
+
for (let p = 1; p < pending.length; p++) {
|
|
161
|
+
codePoint = (codePoint << 6) | (pending[p]! & 0x3f);
|
|
162
|
+
bytesSeen++;
|
|
206
163
|
}
|
|
207
164
|
|
|
208
|
-
|
|
209
|
-
|
|
165
|
+
while (bytesSeen < bytesNeeded && i < length) {
|
|
166
|
+
const byte = bytes[i]!;
|
|
167
|
+
if (
|
|
168
|
+
(byte & 0xc0) !== 0x80 ||
|
|
169
|
+
(bytesSeen === 0 &&
|
|
170
|
+
((b0 === 0xe0 && byte < 0xa0) ||
|
|
171
|
+
(b0 === 0xed && byte > 0x9f) ||
|
|
172
|
+
(b0 === 0xf0 && byte < 0x90) ||
|
|
173
|
+
(b0 === 0xf4 && byte > 0x8f)))
|
|
174
|
+
) {
|
|
175
|
+
pending = EMPTY_BYTES;
|
|
176
|
+
output += decoderError(state);
|
|
177
|
+
bomSeen = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
codePoint = (codePoint << 6) | (byte & 0x3f);
|
|
181
|
+
bytesSeen++;
|
|
182
|
+
i++;
|
|
183
|
+
}
|
|
210
184
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
185
|
+
if (bytesSeen === bytesNeeded) {
|
|
186
|
+
pending = EMPTY_BYTES;
|
|
187
|
+
if (bomSeen || ignoreBOM || codePoint !== 0xfeff) {
|
|
188
|
+
if (codePoint <= 0xffff) {
|
|
189
|
+
output += String.fromCharCode(codePoint);
|
|
190
|
+
} else {
|
|
191
|
+
codePoint -= 0x10000;
|
|
192
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
bomSeen = true;
|
|
196
|
+
} else if (i === length) {
|
|
197
|
+
if (stream && i > 0) {
|
|
198
|
+
const nextPending = new Uint8Array(pending.length + i);
|
|
199
|
+
nextPending.set(pending);
|
|
200
|
+
nextPending.set(bytes.subarray(0, i), pending.length);
|
|
201
|
+
pending = nextPending;
|
|
202
|
+
} else if (!stream) {
|
|
203
|
+
pending = EMPTY_BYTES;
|
|
204
|
+
output += decoderError(state);
|
|
205
|
+
bomSeen = true;
|
|
217
206
|
}
|
|
207
|
+
}
|
|
218
208
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
209
|
+
if (pending.length > 0) {
|
|
210
|
+
state.pending = pending;
|
|
211
|
+
state.bomSeen = bomSeen;
|
|
212
|
+
return output;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
223
215
|
|
|
224
|
-
|
|
225
|
-
|
|
216
|
+
while (i < length) {
|
|
217
|
+
const b0 = bytes[i]!;
|
|
218
|
+
if (b0 < 0x80) {
|
|
219
|
+
bomSeen = true;
|
|
220
|
+
const directEnd = Math.min(i + 32, length);
|
|
221
|
+
do {
|
|
222
|
+
output += String.fromCharCode(bytes[i++]!);
|
|
223
|
+
} while (i < directEnd && bytes[i]! < 0x80);
|
|
224
|
+
if (i === directEnd && i < length && bytes[i]! < 0x80) {
|
|
225
|
+
const asciiStart = i;
|
|
226
|
+
do {
|
|
227
|
+
i++;
|
|
228
|
+
} while (i < length && bytes[i]! < 0x80);
|
|
229
|
+
output = appendASCII(output, bytes, asciiStart, i);
|
|
226
230
|
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
227
233
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
let codePoint: number;
|
|
235
|
+
const remaining = length - i;
|
|
236
|
+
if (b0 >= 0xc2 && b0 <= 0xdf) {
|
|
237
|
+
if (remaining < 2) {
|
|
238
|
+
if (stream) {
|
|
239
|
+
pending = bytes.slice(i);
|
|
240
|
+
} else {
|
|
241
|
+
output += decoderError(state);
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
238
244
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (bite === 0xf4) this.utf8UpperBoundary = 0x8f;
|
|
246
|
-
// 3. Set utf-8 bytes needed to 3.
|
|
247
|
-
this.utf8BytesNeeded = 3;
|
|
248
|
-
// 4. Set UTF-8 code point to byte & 0x7.
|
|
249
|
-
this.utf8CodePoint = bite & 0x7;
|
|
245
|
+
const b1 = bytes[i + 1]!;
|
|
246
|
+
if ((b1 & 0xc0) !== 0x80) {
|
|
247
|
+
output += decoderError(state);
|
|
248
|
+
bomSeen = true;
|
|
249
|
+
i++;
|
|
250
|
+
continue;
|
|
250
251
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
codePoint = ((b0 & 0x1f) << 6) | (b1 & 0x3f);
|
|
253
|
+
i += 2;
|
|
254
|
+
} else if (b0 >= 0xe0 && b0 <= 0xef) {
|
|
255
|
+
if (remaining < 2) {
|
|
256
|
+
if (stream) {
|
|
257
|
+
pending = bytes.slice(i);
|
|
258
|
+
} else {
|
|
259
|
+
output += decoderError(state);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
256
262
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
const b1 = bytes[i + 1]!;
|
|
264
|
+
if ((b1 & 0xc0) !== 0x80 || (b0 === 0xe0 && b1 < 0xa0) || (b0 === 0xed && b1 > 0x9f)) {
|
|
265
|
+
output += decoderError(state);
|
|
266
|
+
bomSeen = true;
|
|
267
|
+
i++;
|
|
268
|
+
continue;
|
|
269
|
+
} else if (remaining < 3) {
|
|
270
|
+
if (stream) {
|
|
271
|
+
pending = bytes.slice(i);
|
|
272
|
+
} else {
|
|
273
|
+
output += decoderError(state);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const b2 = bytes[i + 2]!;
|
|
278
|
+
if ((b2 & 0xc0) !== 0x80) {
|
|
279
|
+
output += decoderError(state);
|
|
280
|
+
bomSeen = true;
|
|
281
|
+
i += 2;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
codePoint = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
|
|
285
|
+
i += 3;
|
|
286
|
+
} else if (b0 >= 0xf0 && b0 <= 0xf4) {
|
|
287
|
+
if (remaining < 2) {
|
|
288
|
+
if (stream) {
|
|
289
|
+
pending = bytes.slice(i);
|
|
290
|
+
} else {
|
|
291
|
+
output += decoderError(state);
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
const b1 = bytes[i + 1]!;
|
|
296
|
+
if ((b1 & 0xc0) !== 0x80 || (b0 === 0xf0 && b1 < 0x90) || (b0 === 0xf4 && b1 > 0x8f)) {
|
|
297
|
+
output += decoderError(state);
|
|
298
|
+
bomSeen = true;
|
|
299
|
+
i++;
|
|
300
|
+
continue;
|
|
301
|
+
} else if (remaining < 3) {
|
|
302
|
+
if (stream) {
|
|
303
|
+
pending = bytes.slice(i);
|
|
304
|
+
} else {
|
|
305
|
+
output += decoderError(state);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
const b2 = bytes[i + 2]!;
|
|
310
|
+
if ((b2 & 0xc0) !== 0x80) {
|
|
311
|
+
output += decoderError(state);
|
|
312
|
+
bomSeen = true;
|
|
313
|
+
i += 2;
|
|
314
|
+
continue;
|
|
315
|
+
} else if (remaining < 4) {
|
|
316
|
+
if (stream) {
|
|
317
|
+
pending = bytes.slice(i);
|
|
318
|
+
} else {
|
|
319
|
+
output += decoderError(state);
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
const b3 = bytes[i + 3]!;
|
|
324
|
+
if ((b3 & 0xc0) !== 0x80) {
|
|
325
|
+
output += decoderError(state);
|
|
326
|
+
bomSeen = true;
|
|
327
|
+
i += 3;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
codePoint = ((b0 & 0x07) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
|
|
331
|
+
i += 4;
|
|
332
|
+
} else {
|
|
333
|
+
output += decoderError(state);
|
|
334
|
+
bomSeen = true;
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
260
337
|
}
|
|
261
338
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.utf8BytesNeeded = 0;
|
|
270
|
-
this.utf8BytesSeen = 0;
|
|
271
|
-
this.utf8LowerBoundary = 0x80;
|
|
272
|
-
this.utf8UpperBoundary = 0xbf;
|
|
273
|
-
|
|
274
|
-
// 2. Prepend byte to stream.
|
|
275
|
-
stream.prepend(bite);
|
|
276
|
-
|
|
277
|
-
// 3. Return error.
|
|
278
|
-
return decoderError(this.options.fatal);
|
|
339
|
+
if (bomSeen || ignoreBOM || codePoint !== 0xfeff) {
|
|
340
|
+
if (codePoint <= 0xffff) {
|
|
341
|
+
output += String.fromCharCode(codePoint);
|
|
342
|
+
} else {
|
|
343
|
+
codePoint -= 0x10000;
|
|
344
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
345
|
+
}
|
|
279
346
|
}
|
|
347
|
+
bomSeen = true;
|
|
348
|
+
}
|
|
280
349
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// 6. Set UTF-8 code point to (UTF-8 code point << 6) | (byte &
|
|
287
|
-
// 0x3F)
|
|
288
|
-
this.utf8CodePoint = (this.utf8CodePoint << 6) | (bite & 0x3f);
|
|
289
|
-
|
|
290
|
-
// 7. Increase utf-8 bytes seen by one.
|
|
291
|
-
this.utf8BytesSeen += 1;
|
|
292
|
-
|
|
293
|
-
// 8. If utf-8 bytes seen is not equal to utf-8 bytes needed,
|
|
294
|
-
// continue.
|
|
295
|
-
if (this.utf8BytesSeen !== this.utf8BytesNeeded) return null;
|
|
296
|
-
|
|
297
|
-
// 9. Let code point be utf-8 code point.
|
|
298
|
-
const code_point = this.utf8CodePoint;
|
|
299
|
-
|
|
300
|
-
// 10. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes
|
|
301
|
-
// seen to 0.
|
|
302
|
-
this.utf8CodePoint = 0;
|
|
303
|
-
this.utf8BytesNeeded = 0;
|
|
304
|
-
this.utf8BytesSeen = 0;
|
|
350
|
+
state.pending = pending;
|
|
351
|
+
state.bomSeen = bomSeen;
|
|
352
|
+
return output;
|
|
353
|
+
}
|
|
305
354
|
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
function decodeUTF8(bytes: Uint8Array, state: TextDecoderState, stream: boolean): string {
|
|
356
|
+
if (state.pending.length === 0) {
|
|
357
|
+
if (!stream && !state.bomSeen) {
|
|
358
|
+
const skipBOM =
|
|
359
|
+
!state.ignoreBOM && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
360
|
+
const result = decodeUTF8Fast(bytes, skipBOM);
|
|
361
|
+
if (typeof result === 'string') {
|
|
362
|
+
state.bomSeen = bytes.length > 0;
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
state.bomSeen = skipBOM > 0 || result.index > skipBOM;
|
|
366
|
+
return decodeUTF8General(bytes, state, false, result.output, result.index);
|
|
367
|
+
}
|
|
308
368
|
}
|
|
369
|
+
|
|
370
|
+
return decodeUTF8General(bytes, state, stream);
|
|
309
371
|
}
|
|
310
372
|
|
|
311
|
-
// 8.1 Interface TextDecoder
|
|
312
373
|
// @docsMissing
|
|
313
374
|
export class TextDecoder {
|
|
314
|
-
private
|
|
315
|
-
private _ignoreBOM: boolean;
|
|
316
|
-
private _errorMode: string;
|
|
317
|
-
private _BOMseen: boolean = false;
|
|
318
|
-
private _doNotFlush: boolean = false;
|
|
319
|
-
private _decoder: UTF8Decoder | null = null;
|
|
375
|
+
private readonly _state: TextDecoderState;
|
|
320
376
|
|
|
321
|
-
constructor(
|
|
322
|
-
|
|
323
|
-
options: {
|
|
324
|
-
fatal?: boolean;
|
|
325
|
-
ignoreBOM?: boolean;
|
|
326
|
-
} = {}
|
|
327
|
-
) {
|
|
328
|
-
if (options != null && typeof options !== 'object') {
|
|
377
|
+
constructor(label: string = 'utf-8', options: { fatal?: boolean; ignoreBOM?: boolean } = {}) {
|
|
378
|
+
if (options == null || (typeof options !== 'object' && typeof options !== 'function')) {
|
|
329
379
|
throw new TypeError(
|
|
330
380
|
'Second argument of TextDecoder must be undefined or an object, e.g. { fatal: true }'
|
|
331
381
|
);
|
|
332
382
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
throw new Error(`Decoder not present: ${encoding.name}`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this._encoding = encoding;
|
|
345
|
-
this._ignoreBOM = !!options.ignoreBOM;
|
|
346
|
-
this._errorMode = options.fatal ? 'fatal' : 'replacement';
|
|
383
|
+
assertValidUTF8Label(label);
|
|
384
|
+
this._state = {
|
|
385
|
+
fatal: Boolean(options.fatal),
|
|
386
|
+
ignoreBOM: Boolean(options.ignoreBOM),
|
|
387
|
+
bomSeen: false,
|
|
388
|
+
pending: EMPTY_BYTES,
|
|
389
|
+
streaming: false,
|
|
390
|
+
};
|
|
347
391
|
}
|
|
348
392
|
|
|
349
|
-
// Getter methods for encoding, fatal, and ignoreBOM
|
|
350
393
|
get encoding(): string {
|
|
351
|
-
return
|
|
394
|
+
return 'utf-8';
|
|
352
395
|
}
|
|
353
396
|
|
|
354
397
|
get fatal(): boolean {
|
|
355
|
-
return this.
|
|
398
|
+
return this._state.fatal;
|
|
356
399
|
}
|
|
357
400
|
|
|
358
401
|
get ignoreBOM(): boolean {
|
|
359
|
-
return this.
|
|
402
|
+
return this._state.ignoreBOM;
|
|
360
403
|
}
|
|
361
404
|
|
|
362
|
-
decode(input?: ArrayBuffer |
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// 1. If the do not flush flag is unset, set decoder to a new
|
|
366
|
-
// encoding's decoder, set stream to a new stream, and unset the
|
|
367
|
-
// BOM seen flag.
|
|
368
|
-
if (!this._doNotFlush) {
|
|
369
|
-
this._decoder = DECODERS[this._encoding!.name]?.({ fatal: this.fatal }) ?? null;
|
|
370
|
-
this._BOMseen = false;
|
|
405
|
+
decode(input?: ArrayBuffer | ArrayBufferView, options: { stream?: boolean } = {}): string {
|
|
406
|
+
if (options == null || (typeof options !== 'object' && typeof options !== 'function')) {
|
|
407
|
+
throw new TypeError('The options argument must be undefined or an object');
|
|
371
408
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// TODO: Align with spec algorithm - maintain stream on instance.
|
|
379
|
-
const input_stream = new Stream(bytes);
|
|
380
|
-
|
|
381
|
-
// 4. Let output be a new stream.
|
|
382
|
-
const output: number[] = [];
|
|
383
|
-
|
|
384
|
-
while (true) {
|
|
385
|
-
const token = input_stream.read();
|
|
386
|
-
|
|
387
|
-
if (token === END_OF_STREAM) break;
|
|
388
|
-
|
|
389
|
-
const result = this._decoder!.handler(input_stream, token);
|
|
390
|
-
|
|
391
|
-
if (result === FINISHED) break;
|
|
392
|
-
|
|
393
|
-
if (result !== null) {
|
|
394
|
-
output.push(result);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (!this._doNotFlush) {
|
|
399
|
-
do {
|
|
400
|
-
const result = this._decoder!.handler(input_stream, input_stream.read());
|
|
401
|
-
if (result === FINISHED) break;
|
|
402
|
-
if (result === null) continue;
|
|
403
|
-
if (Array.isArray(result)) output.push(...result);
|
|
404
|
-
else output.push(result);
|
|
405
|
-
} while (!input_stream.endOfStream());
|
|
406
|
-
this._decoder = null;
|
|
409
|
+
const bytes = normalizeBytes(input);
|
|
410
|
+
const stream = Boolean(options.stream);
|
|
411
|
+
const state = this._state;
|
|
412
|
+
if (!state.streaming) {
|
|
413
|
+
state.bomSeen = false;
|
|
414
|
+
state.pending = EMPTY_BYTES;
|
|
407
415
|
}
|
|
408
416
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
this._BOMseen = true;
|
|
418
|
-
stream.shift(); // Remove the BOM
|
|
419
|
-
} else if (stream.length > 0) {
|
|
420
|
-
this._BOMseen = true;
|
|
421
|
-
}
|
|
417
|
+
try {
|
|
418
|
+
const output = decodeUTF8(bytes, state, stream);
|
|
419
|
+
state.streaming = stream;
|
|
420
|
+
return output;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
state.pending = EMPTY_BYTES;
|
|
423
|
+
state.streaming = false;
|
|
424
|
+
throw error;
|
|
422
425
|
}
|
|
423
|
-
|
|
424
|
-
// Convert the stream of code points to a string
|
|
425
|
-
return codePointsToString(stream);
|
|
426
426
|
}
|
|
427
427
|
}
|