1/* 2 * Copyright (c) 2022-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, uint8 } from "./types" 17import { Array_from_int32 } from "./array" 18 19 20interface SystemTextEncoder { 21 encode(input?: string): Uint8Array; 22 encodeInto(src: string, dest: Uint8Array): void; 23} 24 25interface WithStreamOption { 26 stream: Boolean | undefined; 27} 28 29interface SystemTextDecoder { 30 decode( 31 input: ArrayBuffer | null | undefined | Uint8Array, 32 options: WithStreamOption | undefined 33 ): string; 34} 35 36export class CustomTextEncoder { 37 static readonly HeaderLen: int32 = Int32Array.BYTES_PER_ELEMENT 38 39 constructor(encoder:SystemTextEncoder|undefined = undefined) { 40 this.encoder = encoder 41 } 42 43 private readonly encoder: SystemTextEncoder|undefined 44 45 public static stringLength(input: string): int32 { 46 let length = 0 47 for (let i = 0; i < input.length; i++) { 48 length++ 49 let cp = input.codePointAt(i)! 50 if (cp >= 0x10000) { 51 i++ 52 } 53 } 54 return length 55 } 56 57 encodedLength(input: string): int32 { 58 let length = 0 59 for (let i = 0; i < input.length; i++) { 60 let cp = input.codePointAt(i)! 61 if (cp < 0x80) { 62 length += 1 63 } else if (cp < 0x800) { 64 length += 2 65 } else if (cp < 0x10000) { 66 length += 3 67 } else { 68 length += 4 69 i++ 70 } 71 } 72 return length 73 } 74 75 private addLength(array: Uint8Array, offset: int32, length: int32 | number): void { 76 const len = length as int32 77 array.set(offset, len & 0xff) 78 array.set(offset + 1, (len >> 8) & 0xff) 79 array.set(offset + 2, (len >> 16) & 0xff) 80 array.set(offset + 3, (len >> 24) & 0xff) 81 } 82 83 static getHeaderLength(array: Uint8Array, offset: int32 = 0): int32 { 84 return ( 85 (array.at(offset) as int32) | 86 (array.at(((offset + 1) << 8)) as int32) | 87 (array.at((offset + 2) << 16) as int32) | 88 (array.at((offset + 3) << 24)) as int32) 89 } 90 91 // Produces array of bytes with encoded string headed by 4 bytes (little endian) size information: 92 // [s0][s1][s2][s3] [c_0] ... [c_size-1] 93 encode(input: string | undefined, addLength: boolean = true): Uint8Array { 94 let headerLen = addLength ? CustomTextEncoder.HeaderLen : 0 95 let result: Uint8Array 96 if (!input) { 97 result = new Uint8Array(headerLen) 98 } else if (this.encoder !== undefined) { 99 result = this.encoder!.encode('s'.repeat(headerLen) + input) 100 } else { 101 let length = this.encodedLength(input) 102 result = new Uint8Array(length + headerLen) 103 this.encodeInto(input, result, headerLen) 104 } 105 if (addLength) { 106 this.addLength(result, 0, (result.length - headerLen) as int32) 107 } 108 return result 109 } 110 111 // Produces encoded array of strings with size information. 112 encodeArray(strings: Array<string>): Uint8Array { 113 let totalBytes = CustomTextEncoder.HeaderLen 114 let lengths = new Int32Array(strings.length) 115 for (let i = 0; i < lengths.length; i++) { 116 let len = this.encodedLength(strings[i]) 117 lengths[i] = len 118 totalBytes += len + CustomTextEncoder.HeaderLen 119 } 120 let array = new Uint8Array(totalBytes) 121 let position = 0 122 this.addLength(array, position, lengths.length as int32) 123 position += CustomTextEncoder.HeaderLen 124 for (let i = 0; i < lengths.length; i++) { 125 this.addLength(array, position, lengths[i] as int32) 126 position += CustomTextEncoder.HeaderLen 127 this.encodeInto(strings[i], array, position) 128 position += lengths[i] 129 } 130 return array 131 } 132 133 encodeInto(input: string, result: Uint8Array, position: int32): Uint8Array { 134 if (this.encoder !== undefined) { 135 this.encoder!.encodeInto(input, result.subarray(position, result.length)) 136 return result 137 } 138 let index = position 139 for (let stringPosition = 0; stringPosition < input.length; stringPosition++) { 140 let cp = input.codePointAt(stringPosition)! 141 if (cp < 0x80) { 142 result[index++] = (cp | 0) 143 } else if (cp < 0x800) { 144 result[index++] = ((cp >> 6) | 0xc0) 145 result[index++] = ((cp & 0x3f) | 0x80) 146 } else if (cp < 0x10000) { 147 result[index++] = ((cp >> 12) | 0xe0) 148 result[index++] = (((cp >> 6) & 0x3f) | 0x80) 149 result[index++] = ((cp & 0x3f) | 0x80) 150 } else { 151 result[index++] = ((cp >> 18) | 0xf0) 152 result[index++] = (((cp >> 12) & 0x3f) | 0x80) 153 result[index++] = (((cp >> 6) & 0x3f) | 0x80) 154 result[index++] = ((cp & 0x3f) | 0x80) 155 stringPosition++ 156 } 157 } 158 result[index] = 0 159 return result 160 } 161} 162 163export class CustomTextDecoder { 164 static cpArrayMaxSize = 128 165 constructor(decoder: SystemTextDecoder|undefined = undefined) { 166 this.decoder = decoder 167 } 168 169 private readonly decoder: SystemTextDecoder|undefined 170 171 decode(input: Uint8Array): string { 172 if (this.decoder !== undefined) { 173 return this.decoder!.decode(input, undefined) 174 } 175 176 const cpSize = Math.min(CustomTextDecoder.cpArrayMaxSize, input.length) 177 let codePoints = new Int32Array(cpSize) 178 let cpIndex = 0; 179 let index = 0 180 let result = "" 181 while (index < input.length) { 182 let elem = input[index] as uint8 183 let lead = elem & 0xff 184 let count = 0 185 let value = 0 186 if (lead < 0x80) { 187 count = 1 188 value = elem 189 } else if ((lead >> 5) == 0x6) { 190 value = (((elem << 6) & 0x7ff) + (input[index + 1] & 0x3f)) as int32 191 count = 2 192 } else if ((lead >> 4) == 0xe) { 193 value = (((elem << 12) & 0xffff) + ((input[index + 1] << 6) & 0xfff) + 194 (input[index + 2] & 0x3f)) as int32 195 count = 3 196 } else if ((lead >> 3) == 0x1e) { 197 value = (((elem << 18) & 0x1fffff) + ((input[index + 1] << 12) & 0x3ffff) + 198 ((input[index + 2] << 6) & 0xfff) + (input[index + 3] & 0x3f)) as int32 199 count = 4 200 } 201 codePoints[cpIndex++] = value 202 if (cpIndex == cpSize) { 203 cpIndex = 0 204 //result += String.fromCodePoint(...codePoints) 205 result += String.fromCodePoint(...Array_from_int32(codePoints)) 206 } 207 index += count 208 } 209 if (cpIndex > 0) { 210 result += String.fromCodePoint(...Array_from_int32(codePoints.slice(0, cpIndex))) 211 } 212 return result 213 } 214} 215