• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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