typescript
Version:
TypeScript is a language for application scale JavaScript development
130 lines • 4.76 kB
JavaScript
import { Buffer } from "node:buffer";
const surrogateLeadByte = 0xED;
const surrogateSecondByteMin = 0xA0;
const surrogateSecondByteMax = 0xBF;
const continuationByteMin = 0x80;
const continuationByteMax = 0xBF;
const textEncoder = new TextEncoder();
const replacementCharacterUtf8 = Buffer.from([0xEF, 0xBF, 0xBD]);
const loneSurrogateRegExp = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
const shortStringLength = 64;
function isWtf8Surrogate(bytes, index) {
return index + 2 < bytes.length
&& bytes[index] === surrogateLeadByte
&& bytes[index + 1] >= surrogateSecondByteMin
&& bytes[index + 1] <= surrogateSecondByteMax
&& bytes[index + 2] >= continuationByteMin
&& bytes[index + 2] <= continuationByteMax;
}
function getSurrogateCodeUnit(bytes, index) {
return 0xD000 | ((bytes[index + 1] & 0x3F) << 6) | (bytes[index + 2] & 0x3F);
}
function hasSurrogateLeadByte(bytes) {
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).indexOf(surrogateLeadByte) >= 0;
}
function toUint8Array(input) {
if (input instanceof Uint8Array) {
return input;
}
if (ArrayBuffer.isView(input)) {
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
}
return new Uint8Array(input);
}
export function encodeWtf8(text) {
if (text.length > shortStringLength) {
const utf8 = textEncoder.encode(text);
if (Buffer.from(utf8.buffer, utf8.byteOffset, utf8.byteLength).indexOf(replacementCharacterUtf8) < 0
|| !loneSurrogateRegExp.test(text)) {
return utf8;
}
}
let byteLength = 0;
for (let i = 0; i < text.length; i++) {
const codeUnit = text.charCodeAt(i);
if (codeUnit < 0x80) {
byteLength++;
}
else if (codeUnit < 0x800) {
byteLength += 2;
}
else if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF && i + 1 < text.length) {
const low = text.charCodeAt(i + 1);
if (low >= 0xDC00 && low <= 0xDFFF) {
byteLength += 4;
i++;
}
else {
byteLength += 3;
}
}
else {
byteLength += 3;
}
}
const bytes = new Uint8Array(byteLength);
let offset = 0;
for (let i = 0; i < text.length; i++) {
const codeUnit = text.charCodeAt(i);
if (codeUnit < 0x80) {
bytes[offset++] = codeUnit;
}
else if (codeUnit < 0x800) {
bytes[offset++] = 0xC0 | (codeUnit >> 6);
bytes[offset++] = 0x80 | (codeUnit & 0x3F);
}
else if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF && i + 1 < text.length) {
const low = text.charCodeAt(i + 1);
if (low >= 0xDC00 && low <= 0xDFFF) {
const codePoint = 0x10000 + ((codeUnit - 0xD800) << 10) + (low - 0xDC00);
bytes[offset++] = 0xF0 | (codePoint >> 18);
bytes[offset++] = 0x80 | ((codePoint >> 12) & 0x3F);
bytes[offset++] = 0x80 | ((codePoint >> 6) & 0x3F);
bytes[offset++] = 0x80 | (codePoint & 0x3F);
i++;
continue;
}
bytes[offset++] = 0xE0 | (codeUnit >> 12);
bytes[offset++] = 0x80 | ((codeUnit >> 6) & 0x3F);
bytes[offset++] = 0x80 | (codeUnit & 0x3F);
}
else {
bytes[offset++] = 0xE0 | (codeUnit >> 12);
bytes[offset++] = 0x80 | ((codeUnit >> 6) & 0x3F);
bytes[offset++] = 0x80 | (codeUnit & 0x3F);
}
}
return bytes;
}
export class Wtf8Decoder extends TextDecoder {
decode(input, options) {
if (input == null) {
return super.decode(undefined, options);
}
const bytes = toUint8Array(input);
if (!hasSurrogateLeadByte(bytes)) {
return super.decode(bytes, options);
}
const parts = [];
let segmentStart = 0;
for (let i = 0; i < bytes.length; i++) {
if (!isWtf8Surrogate(bytes, i)) {
continue;
}
if (segmentStart < i) {
parts.push(super.decode(bytes.subarray(segmentStart, i), options));
}
parts.push(String.fromCharCode(getSurrogateCodeUnit(bytes, i)));
i += 2;
segmentStart = i + 1;
}
if (segmentStart === 0) {
return super.decode(bytes, options);
}
if (segmentStart < bytes.length) {
parts.push(super.decode(bytes.subarray(segmentStart), options));
}
return parts.join("");
}
}
//# sourceMappingURL=wtf8.js.map