1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { int32 } from './InteropTypes'; 17 18interface SystemTextEncoder { 19 encode(input?: string): Uint8Array; 20 encodeInto(src: string, dest: Uint8Array): void; 21} 22 23interface WithStreamOption { 24 stream?: boolean | undefined; 25} 26 27interface SystemTextDecoder { 28 decode(input?: ArrayBuffer | null | Uint8Array, options?: WithStreamOption): string; 29} 30 31export class CustomTextEncoder { 32 static readonly HeaderLen: int32 = Int32Array.BYTES_PER_ELEMENT; 33 34 constructor( 35 encoder: SystemTextEncoder | undefined = typeof TextEncoder !== 'undefined' ? new TextEncoder() : undefined 36 ) { 37 this.encoder = encoder; 38 } 39 40 private readonly encoder: SystemTextEncoder | undefined; 41 42 public static stringLength(input: string): int32 { 43 let length = 0; 44 for (let i = 0; i < input.length; i++) { 45 length++; 46 let cp = input.codePointAt(i)!; 47 if (cp >= 0x10000) { 48 i++; 49 } 50 } 51 return length; 52 } 53 54 encodedLength(input: string): int32 { 55 let length = 0; 56 for (let i = 0; i < input.length; i++) { 57 let cp = input.codePointAt(i)!; 58 if (cp < 0x80) { 59 length += 1; 60 } else if (cp < 0x800) { 61 length += 2; 62 } else if (cp < 0x10000) { 63 length += 3; 64 } else { 65 length += 4; 66 i++; 67 } 68 } 69 return length; 70 } 71 72 private addLength(array: Uint8Array, offset: int32, len: int32): void { 73 array[offset] = len & 0xff; 74 array[offset + 1] = (len >> 8) & 0xff; 75 array[offset + 2] = (len >> 16) & 0xff; 76 array[offset + 3] = (len >> 24) & 0xff; 77 } 78 79 static getHeaderLength(array: Uint8Array, offset: int32 = 0): int32 { 80 return array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) | (array[offset + 3] << 24); 81 } 82 83 // Produces array of bytes with encoded string headed by 4 bytes (little endian) size information: 84 // [s0][s1][s2][s3] [c_0] ... [c_size-1] 85 encode(input: string | undefined, addLength: boolean = true): Uint8Array { 86 let headerLen = addLength ? CustomTextEncoder.HeaderLen : 0; 87 let result: Uint8Array; 88 if (!input) { 89 result = new Uint8Array(headerLen); 90 } else if (this.encoder !== undefined) { 91 result = this.encoder!.encode('s'.repeat(headerLen) + input); 92 } else { 93 let length = this.encodedLength(input); 94 result = new Uint8Array(length + headerLen); 95 this.encodeInto(input, result, headerLen); 96 } 97 if (addLength) { 98 this.addLength(result, 0, result.length - headerLen); 99 } 100 return result; 101 } 102 103 // Produces encoded array of strings with size information. 104 encodeArray(strings: Array<string>): Uint8Array { 105 let totalBytes = CustomTextEncoder.HeaderLen; 106 let lengths = new Int32Array(strings.length); 107 for (let i = 0; i < lengths.length; i++) { 108 let len = this.encodedLength(strings[i]); 109 lengths[i] = len; 110 totalBytes += len + CustomTextEncoder.HeaderLen; 111 } 112 let array = new Uint8Array(totalBytes); 113 let position = 0; 114 this.addLength(array, position, lengths.length); 115 position += CustomTextEncoder.HeaderLen; 116 for (let i = 0; i < lengths.length; i++) { 117 this.addLength(array, position, lengths[i]); 118 position += CustomTextEncoder.HeaderLen; 119 this.encodeInto(strings[i], array, position); 120 position += lengths[i]; 121 } 122 return array; 123 } 124 125 encodeInto(input: string, result: Uint8Array, position: int32): Uint8Array { 126 if (this.encoder !== undefined) { 127 this.encoder!.encodeInto(input, result.subarray(position, result.length)); 128 return result; 129 } 130 let index = position; 131 for (let stringPosition = 0; stringPosition < input.length; stringPosition++) { 132 let cp = input.codePointAt(stringPosition)!; 133 if (cp < 0x80) { 134 result[index++] = cp | 0; 135 } else if (cp < 0x800) { 136 result[index++] = (cp >> 6) | 0xc0; 137 result[index++] = (cp & 0x3f) | 0x80; 138 } else if (cp < 0x10000) { 139 result[index++] = (cp >> 12) | 0xe0; 140 result[index++] = ((cp >> 6) & 0x3f) | 0x80; 141 result[index++] = (cp & 0x3f) | 0x80; 142 } else { 143 result[index++] = (cp >> 18) | 0xf0; 144 result[index++] = ((cp >> 12) & 0x3f) | 0x80; 145 result[index++] = ((cp >> 6) & 0x3f) | 0x80; 146 result[index++] = (cp & 0x3f) | 0x80; 147 stringPosition++; 148 } 149 } 150 result[index] = 0; 151 return result; 152 } 153} 154 155export class CustomTextDecoder { 156 static cpArrayMaxSize = 128; 157 constructor( 158 decoder: SystemTextDecoder | TextDecoder | undefined = typeof TextDecoder !== 'undefined' 159 ? new TextDecoder() 160 : undefined 161 ) { 162 this.decoder = decoder; 163 } 164 165 private readonly decoder: SystemTextDecoder | TextDecoder | undefined; 166 167 decode(input: Uint8Array): string { 168 if (this.decoder !== undefined) { 169 return this.decoder!.decode(input); 170 } 171 const cpSize = Math.min(CustomTextDecoder.cpArrayMaxSize, input.length); 172 let codePoints = new Int32Array(cpSize); 173 let cpIndex = 0; 174 let index = 0; 175 let result = ''; 176 while (index < input.length) { 177 let elem = input[index]; 178 let lead = elem & 0xff; 179 let count = 0; 180 let value = 0; 181 if (lead < 0x80) { 182 count = 1; 183 value = elem; 184 } else if (lead >> 5 === 0x6) { 185 value = ((elem << 6) & 0x7ff) + (input[index + 1] & 0x3f); 186 count = 2; 187 } else if (lead >> 4 === 0xe) { 188 value = ((elem << 12) & 0xffff) + ((input[index + 1] << 6) & 0xfff) + (input[index + 2] & 0x3f); 189 count = 3; 190 } else if (lead >> 3 === 0x1e) { 191 value = 192 ((elem << 18) & 0x1fffff) + 193 ((input[index + 1] << 12) & 0x3ffff) + 194 ((input[index + 2] << 6) & 0xfff) + 195 (input[index + 3] & 0x3f); 196 count = 4; 197 } 198 codePoints[cpIndex++] = value; 199 if (cpIndex === cpSize) { 200 cpIndex = 0; 201 result += String.fromCodePoint(...codePoints); 202 } 203 index += count; 204 } 205 if (cpIndex > 0) { 206 result += String.fromCodePoint(...codePoints.slice(0, cpIndex)); 207 } 208 return result; 209 } 210} 211