diff --git a/package-lock.json b/package-lock.json index e0ac4e728..c454125c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@types/mocha": "^9.0.0", "@types/node": "^22.19.3", "@types/trusted-types": "^1.0.6", - "@types/utf8": "^3.0.0", "@types/webpack": "^5.28.0", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^8.50.1", @@ -47,7 +46,6 @@ "ts-loader": "^9.3.1", "typescript": "^5.9.3", "typescript-eslint": "^8.50.1", - "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", "ws": "^8.2.3", @@ -1726,12 +1724,6 @@ "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", "dev": true }, - "node_modules/@types/utf8": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/utf8/-/utf8-3.0.3.tgz", - "integrity": "sha512-+lqLGxWZsEe4Z6OrzBI7Ym4SMUTaMS5yOrHZ0/IL0bpIye1Qbs4PpobJL2mLDbftUXlPFZR7fu6d1yM+bHLX1w==", - "dev": true - }, "node_modules/@types/webpack": { "version": "5.28.5", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", @@ -7360,12 +7352,6 @@ "punycode": "^2.1.0" } }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index f664f5411..76287f33a 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "@types/mocha": "^9.0.0", "@types/node": "^22.19.3", "@types/trusted-types": "^1.0.6", - "@types/utf8": "^3.0.0", "@types/webpack": "^5.28.0", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^8.50.1", @@ -111,7 +110,6 @@ "ts-loader": "^9.3.1", "typescript": "^5.9.3", "typescript-eslint": "^8.50.1", - "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", "ws": "^8.2.3", diff --git a/src/common/input/TextDecoder.test.ts b/src/common/input/TextDecoder.test.ts index a0a4a3230..1959e1591 100644 --- a/src/common/input/TextDecoder.test.ts +++ b/src/common/input/TextDecoder.test.ts @@ -5,7 +5,6 @@ import { assert } from 'chai'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder'; -import { encode } from 'utf8'; // convert UTF32 codepoints to string @@ -29,6 +28,35 @@ function fromByteString(s: string): Uint8Array { return result; } +function stringToUtf8Bytes(s: string): Uint8Array { + const bytes: number[] = []; + for (let i = 0; i < s.length; i++) { + let cp = s.charCodeAt(i); + if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < s.length) { + const next = s.charCodeAt(i + 1); + if (next >= 0xDC00 && next <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (next - 0xDC00); + i++; + } + } + if (cp < 0x80) { + bytes.push(cp); + } else if (cp < 0x800) { + bytes.push(0xC0 | (cp >> 6), 0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + bytes.push(0xE0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3F), 0x80 | (cp & 0x3F)); + } else { + bytes.push( + 0xF0 | (cp >> 18), + 0x80 | ((cp >> 12) & 0x3F), + 0x80 | ((cp >> 6) & 0x3F), + 0x80 | (cp & 0x3F) + ); + } + } + return new Uint8Array(bytes); +} + function assertDecodedRange( min: number, max: number, @@ -172,7 +200,7 @@ describe('text encodings', () => { max, (i) => (i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF, (i) => String.fromCharCode(i), - (input, target) => decoder.decode(fromByteString(encode(input)), target), + (input, target) => decoder.decode(stringToUtf8Bytes(input), target), (data, length) => toString(data, length) ); }); @@ -187,7 +215,7 @@ describe('text encodings', () => { max, () => false, (i) => stringFromCodePoint(i), - (input, target) => decoder.decode(fromByteString(encode(input)), target), + (input, target) => decoder.decode(stringToUtf8Bytes(input), target), (data, length) => toString(data, length) ); }); @@ -196,7 +224,7 @@ describe('text encodings', () => { it('0xFEFF(BOM)', () => { const decoder = new Utf8ToUtf32(); const target = new Uint32Array(5); - const utf8Data = fromByteString(encode(String.fromCharCode(0xFEFF))); + const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF)); const length = decoder.decode(utf8Data, target); assert.equal(length, 0); decoder.clear(); @@ -207,7 +235,7 @@ describe('text encodings', () => { const decoder = new Utf8ToUtf32(); const target = new Uint32Array(500); for (let i = 0; i < TEST_STRINGS.length; ++i) { - const utf8Data = fromByteString(encode(TEST_STRINGS[i])); + const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]); const length = decoder.decode(utf8Data, target); assert.equal(toString(target, length), TEST_STRINGS[i]); decoder.clear();