ag-psd 31.0.1 → 31.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.v8-cache/v24.13.1-x64-cf738c9d/03e19709 +0 -0
  2. package/.v8-cache/v24.13.1-x64-cf738c9d/1130885f +0 -0
  3. package/.v8-cache/v24.13.1-x64-cf738c9d/4511bacf +0 -0
  4. package/.v8-cache/v24.13.1-x64-cf738c9d/7ea06cea +0 -0
  5. package/.v8-cache/v24.13.1-x64-cf738c9d/932f0770 +0 -0
  6. package/.v8-cache/v24.13.1-x64-cf738c9d/9ed80ba5 +0 -0
  7. package/.v8-cache/v24.13.1-x64-cf738c9d/a7e80865 +0 -0
  8. package/.v8-cache/v24.13.1-x64-cf738c9d/acc36e66 +0 -0
  9. package/.v8-cache/v24.13.1-x64-cf738c9d/b3c2fab7 +0 -0
  10. package/.v8-cache/v24.13.1-x64-cf738c9d/c314aece +0 -0
  11. package/.v8-cache/v24.13.1-x64-cf738c9d/cfc49f4f +0 -0
  12. package/.v8-cache/v24.13.1-x64-cf738c9d/cff9434a +0 -0
  13. package/.v8-cache/v24.13.1-x64-cf738c9d/e03e2acd +0 -0
  14. package/.v8-cache/v24.13.1-x64-cf738c9d/eeeebdfe +0 -0
  15. package/dist/abr.js +2 -6
  16. package/dist/abr.js.map +1 -1
  17. package/dist/additionalInfo.js +6 -6
  18. package/dist/additionalInfo.js.map +1 -1
  19. package/dist/ase.js +2 -0
  20. package/dist/ase.js.map +1 -1
  21. package/dist/bundle.js +213 -2273
  22. package/dist/descriptor.d.ts +2 -2
  23. package/dist/descriptor.js +4 -3
  24. package/dist/descriptor.js.map +1 -1
  25. package/dist/engineData.js +1 -1
  26. package/dist/helpers.d.ts +3 -1
  27. package/dist/helpers.js +1 -1
  28. package/dist/helpers.js.map +1 -1
  29. package/dist/imageResources.js +12 -7
  30. package/dist/imageResources.js.map +1 -1
  31. package/dist/jpeg.js +18 -6
  32. package/dist/jpeg.js.map +1 -1
  33. package/dist/psd.d.ts +2 -2
  34. package/dist/psd.js +2 -0
  35. package/dist/psd.js.map +1 -1
  36. package/dist/psdReader.js +40 -10
  37. package/dist/psdReader.js.map +1 -1
  38. package/dist/psdWriter.js +9 -3
  39. package/dist/psdWriter.js.map +1 -1
  40. package/dist/utf8.js +92 -81
  41. package/dist/utf8.js.map +1 -1
  42. package/dist-es/abr.js +2 -6
  43. package/dist-es/abr.js.map +1 -1
  44. package/dist-es/additionalInfo.js +6 -6
  45. package/dist-es/additionalInfo.js.map +1 -1
  46. package/dist-es/ase.js +2 -0
  47. package/dist-es/ase.js.map +1 -1
  48. package/dist-es/descriptor.d.ts +2 -2
  49. package/dist-es/descriptor.js +4 -3
  50. package/dist-es/descriptor.js.map +1 -1
  51. package/dist-es/engineData.js +1 -1
  52. package/dist-es/helpers.d.ts +3 -1
  53. package/dist-es/helpers.js +1 -1
  54. package/dist-es/helpers.js.map +1 -1
  55. package/dist-es/imageResources.js +12 -7
  56. package/dist-es/imageResources.js.map +1 -1
  57. package/dist-es/jpeg.js +18 -6
  58. package/dist-es/jpeg.js.map +1 -1
  59. package/dist-es/psd.d.ts +2 -2
  60. package/dist-es/psd.js +2 -0
  61. package/dist-es/psd.js.map +1 -1
  62. package/dist-es/psdReader.js +40 -10
  63. package/dist-es/psdReader.js.map +1 -1
  64. package/dist-es/psdWriter.js +9 -3
  65. package/dist-es/psdWriter.js.map +1 -1
  66. package/dist-es/utf8.js +92 -81
  67. package/dist-es/utf8.js.map +1 -1
  68. package/package.json +1 -1
  69. package/src/abr.ts +2 -4
  70. package/src/additionalInfo.ts +6 -6
  71. package/src/ase.ts +2 -1
  72. package/src/descriptor.ts +5 -4
  73. package/src/engineData.ts +1 -1
  74. package/src/helpers.ts +6 -3
  75. package/src/imageResources.ts +10 -6
  76. package/src/jpeg.ts +19 -7
  77. package/src/psd.ts +4 -2
  78. package/src/psdReader.ts +44 -9
  79. package/src/psdWriter.ts +11 -3
  80. package/src/utf8.ts +178 -168
@@ -38,7 +38,9 @@ const MEASUREMENT_UNITS = [undefined, 'Inches', 'Centimeters', 'Points', 'Picas'
38
38
  const hex = '0123456789abcdef';
39
39
 
40
40
  function charToNibble(code: number) {
41
- return code <= 57 ? code - 48 : code - 87;
41
+ if (code <= 57) return code - 48; // '0'-'9'
42
+ if (code >= 97) return code - 87; // 'a'-'f'
43
+ return code - 55; // 'A'-'F'
42
44
  }
43
45
 
44
46
  function byteAt(value: string, index: number) {
@@ -68,11 +70,13 @@ function readEncodedString(reader: PsdReader) {
68
70
  }
69
71
 
70
72
  if (notAscii) {
71
- const decoder = new TextDecoder('gbk');
72
- return decoder.decode(buffer)
73
- } else {
74
- return decodeString(buffer);
73
+ try {
74
+ const decoder = new TextDecoder('gbk');
75
+ return decoder.decode(buffer)
76
+ } catch { }
75
77
  }
78
+
79
+ return decodeString(buffer);
76
80
  }
77
81
 
78
82
  function writeEncodedString(writer: PsdWriter, value: string) {
@@ -1397,7 +1401,7 @@ MOCK_HANDLERS && addHandler(
1397
1401
  },
1398
1402
  );
1399
1403
 
1400
- const FrmD = createEnum<'auto' | 'none' | 'dispose'>('FrmD', '', {
1404
+ const FrmD = createEnum<'auto' | 'none' | 'dispose'>('FrmD', 'auto', {
1401
1405
  auto: 'Auto',
1402
1406
  none: 'None',
1403
1407
  dispose: 'Disp',
package/src/jpeg.ts CHANGED
@@ -24,7 +24,6 @@ interface DecodedComponent {
24
24
  interface Decoded {
25
25
  width: number;
26
26
  height: number;
27
- comments: string[];
28
27
  exifBuffer: Uint8Array | undefined;
29
28
  jfif: any;
30
29
  adobe: any;
@@ -623,7 +622,6 @@ function parse(data: Uint8Array) {
623
622
  const self: Decoded = {
624
623
  width: 0,
625
624
  height: 0,
626
- comments: [],
627
625
  adobe: undefined,
628
626
  components: [],
629
627
  exifBuffer: undefined,
@@ -731,11 +729,12 @@ function parse(data: Uint8Array) {
731
729
  const appData = readDataBlock();
732
730
 
733
731
  if (fileMarker === 0xFFFE) {
734
- let comment = '';
735
- for (let ii = 0; ii < appData.byteLength; ii++) {
736
- comment += String.fromCharCode(appData[ii]);
737
- }
738
- self.comments.push(comment);
732
+ // ignore comments
733
+ // let comment = '';
734
+ // for (let ii = 0; ii < appData.byteLength; ii++) {
735
+ // comment += String.fromCharCode(appData[ii]);
736
+ // }
737
+ // self.comments.push(comment);
739
738
  }
740
739
 
741
740
  if (fileMarker === 0xFFE0) {
@@ -1125,6 +1124,19 @@ export function decodeJpeg(encoded: Uint8Array, createImageData: (width: number,
1125
1124
  }
1126
1125
  }
1127
1126
  break;
1127
+ case 2:
1128
+ for (let y = 0; y < height; y++) {
1129
+ for (let x = 0; x < width; x++) {
1130
+ const Y = data[i++];
1131
+ const A = data[i++];
1132
+
1133
+ imageDataArray[j++] = Y;
1134
+ imageDataArray[j++] = Y;
1135
+ imageDataArray[j++] = Y;
1136
+ imageDataArray[j++] = A;
1137
+ }
1138
+ }
1139
+ break;
1128
1140
  case 3:
1129
1141
  for (let y = 0; y < height; y++) {
1130
1142
  for (let x = 0; x < width; x++) {
package/src/psd.ts CHANGED
@@ -2,7 +2,7 @@ export type BlendMode = 'pass through' | 'normal' | 'dissolve' | 'darken' | 'mul
2
2
  'color burn' | 'linear burn' | 'darker color' | 'lighten' | 'screen' | 'color dodge' |
3
3
  'linear dodge' | 'lighter color' | 'overlay' | 'soft light' | 'hard light' |
4
4
  'vivid light' | 'linear light' | 'pin light' | 'hard mix' | 'difference' | 'exclusion' |
5
- 'subtract' | 'divide' | 'hue' | 'saturation' | 'color' | 'luminosity';
5
+ 'subtract' | 'divide' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'linear height' | 'height' | 'subtraction';
6
6
 
7
7
  export const enum ColorMode {
8
8
  Bitmap = 0,
@@ -10,6 +10,8 @@ export const enum ColorMode {
10
10
  Indexed = 2,
11
11
  RGB = 3,
12
12
  CMYK = 4,
13
+ // 5
14
+ // 6
13
15
  Multichannel = 7,
14
16
  Duotone = 8,
15
17
  Lab = 9,
@@ -183,7 +185,7 @@ export interface EffectNoiseGradient {
183
185
  name: string;
184
186
  type: 'noise';
185
187
  roughness?: number;
186
- colorModel?: 'rgb' | 'hsb' | 'lab';
188
+ colorModel?: 'rgb' | 'hsb' | 'lab' | 'hsl';
187
189
  randomSeed?: number;
188
190
  restrictColors?: boolean;
189
191
  addTransparency?: boolean;
package/src/psdReader.ts CHANGED
@@ -10,7 +10,7 @@ interface ChannelInfo {
10
10
  }
11
11
 
12
12
  export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed];
13
- const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', 'multichannel', 'duotone', 'lab'];
13
+ const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
14
14
 
15
15
  function setupGrayscale(data: PixelData) {
16
16
  const size = data.width * data.height * 4;
@@ -234,7 +234,9 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
234
234
  Object.assign(reader, readOptions);
235
235
  reader.large = version === 2;
236
236
  reader.globalAlpha = false;
237
- if (!reader.totalMemoryLimit) reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
237
+ if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
238
+ reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
239
+ }
238
240
 
239
241
  // color mode data
240
242
  readSection(reader, 1, left => {
@@ -287,7 +289,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
287
289
 
288
290
  const { layersGroup, layerGroupsEnabledId, ...rest } = imageResources;
289
291
 
290
- if (Object.keys(rest)) {
292
+ if (Object.keys(rest).length) {
291
293
  psd.imageResources = rest;
292
294
  }
293
295
 
@@ -408,6 +410,7 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
408
410
  layer.left = readInt32(reader);
409
411
  layer.bottom = readInt32(reader);
410
412
  layer.right = readInt32(reader);
413
+ if (!isValidBoxSize(layer, reader)) throw new Error('Invalid layer size');
411
414
 
412
415
  const channelCount = readUint16(reader);
413
416
  const channels: ChannelInfo[] = [];
@@ -461,6 +464,13 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
461
464
  return { layer, channels };
462
465
  }
463
466
 
467
+ function isValidBoxSize(box: { top?: number; left?: number; bottom?: number; right?: number; }, reader: PsdReader) {
468
+ const width = (box.right || 0) - (box.left || 0);
469
+ const height = (box.bottom || 0) - (box.top || 0);
470
+ const maxSize = reader.large ? 300000 : 30000;
471
+ return width >= 0 && height >= 0 && width <= maxSize && height <= maxSize;
472
+ }
473
+
464
474
  function readLayerMaskData(reader: PsdReader, layer: Layer) {
465
475
  return readSection<LayerMaskData | undefined>(reader, 1, left => {
466
476
  if (!left()) return undefined;
@@ -472,6 +482,8 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
472
482
  mask.left = readInt32(reader);
473
483
  mask.bottom = readInt32(reader);
474
484
  mask.right = readInt32(reader);
485
+ if (!isValidBoxSize(mask, reader)) throw new Error('Invalid mask size');
486
+
475
487
  mask.defaultColor = readUint8(reader);
476
488
 
477
489
  const flags = readUint8(reader);
@@ -493,6 +505,7 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
493
505
  realMask.left = readInt32(reader);
494
506
  realMask.bottom = readInt32(reader);
495
507
  realMask.right = readInt32(reader);
508
+ if (!isValidBoxSize(realMask, reader)) throw new Error('Invalid realMask size');
496
509
  }
497
510
 
498
511
  if (flags & LayerMaskFlags.MaskHasParametersAppliedToIt) {
@@ -668,8 +681,8 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
668
681
  if (!layer.rawData) return undefined;
669
682
 
670
683
  const { colorMode, bitsPerChannel, channels, large } = layer.rawData;
671
- const layerWidth = (layer.right || 0) - (layer.left || 0);
672
- const layerHeight = (layer.bottom || 0) - (layer.top || 0);
684
+ const layerWidth = Math.max(0, (layer.right || 0) - (layer.left || 0));
685
+ const layerHeight = Math.max(0, (layer.bottom || 0) - (layer.top || 0));
673
686
  const cmyk = colorMode === ColorMode.CMYK;
674
687
 
675
688
  let imageData: PixelData | undefined;
@@ -702,9 +715,8 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
702
715
  const mask = id === ChannelID.UserMask ? layer.mask : layer.realMask;
703
716
  if (!mask) throw new Error(`Missing layer ${id === ChannelID.UserMask ? 'mask' : 'real mask'} data`);
704
717
 
705
- const maskWidth = (mask.right || 0) - (mask.left || 0);
706
- const maskHeight = (mask.bottom || 0) - (mask.top || 0);
707
- if (maskWidth < 0 || maskHeight < 0 || maskWidth > 30000 || maskHeight > 30000) throw new Error('Invalid mask size');
718
+ const maskWidth = Math.max(0, (mask.right || 0) - (mask.left || 0));
719
+ const maskHeight = Math.max(0, (mask.bottom || 0) - (mask.top || 0));
708
720
 
709
721
  if (maskWidth && maskHeight) {
710
722
  maskData = createImageDataBitDepth(maskWidth, maskHeight, bitsPerChannel, 4, memoryLimit);
@@ -1174,6 +1186,7 @@ export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined,
1174
1186
  let lengths: Uint16Array | Uint32Array;
1175
1187
 
1176
1188
  if (large) {
1189
+ consumeMemory(reader, offsets.length * height * 4);
1177
1190
  lengths = new Uint32Array(offsets.length * height);
1178
1191
 
1179
1192
  for (let o = 0, li = 0; o < offsets.length; o++) {
@@ -1182,6 +1195,7 @@ export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined,
1182
1195
  }
1183
1196
  }
1184
1197
  } else {
1198
+ consumeMemory(reader, offsets.length * height * 2);
1185
1199
  lengths = new Uint16Array(offsets.length * height);
1186
1200
 
1187
1201
  for (let o = 0, li = 0; o < offsets.length; o++) {
@@ -1234,6 +1248,8 @@ export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined,
1234
1248
  }
1235
1249
  }
1236
1250
  }
1251
+
1252
+ recoverMemory(reader, lengths.byteLength);
1237
1253
  }
1238
1254
 
1239
1255
  export function readSection<T>(
@@ -1357,7 +1373,9 @@ export function readPattern(reader: PsdReader): PatternInfo {
1357
1373
  const channelsCount = readUint32(reader);
1358
1374
  const width = right - left;
1359
1375
  const height = bottom - top;
1360
- const data = new Uint8Array(width * height * 4);
1376
+ const size = width * height * 4;
1377
+ consumeMemory(reader, size);
1378
+ const data = new Uint8Array(size);
1361
1379
 
1362
1380
  for (let i = 3; i < data.byteLength; i += 4) {
1363
1381
  data[i] = 255;
@@ -1423,6 +1441,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1423
1441
  if (reader.throwForMissingFeatures) throw new Error('Invalid color pattern');
1424
1442
  }
1425
1443
  } else if (compressionMode === 1) {
1444
+ consumeMemory(reader, w * h);
1445
+
1426
1446
  const pixelData: PixelData = { data, width, height };
1427
1447
  const tempData: PixelData = { data: new Uint8Array(w * h), width: w, height: h };
1428
1448
  const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
@@ -1442,6 +1462,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1442
1462
  // TODO:
1443
1463
  throw new Error('Indexed pattern color mode not implemented');
1444
1464
  }
1465
+
1466
+ recoverMemory(reader, w * h);
1445
1467
  } else {
1446
1468
  throw new Error('Invalid pattern compression mode');
1447
1469
  }
@@ -1468,3 +1490,16 @@ function copyChannelToRGBA(srcData: PixelData, dstData: PixelData, ox: number, o
1468
1490
  }
1469
1491
  }
1470
1492
  }
1493
+
1494
+ function consumeMemory(reader: PsdReader, size: number) {
1495
+ if (reader.totalMemoryLimit !== undefined) {
1496
+ if (reader.totalMemoryLimit < size) throw new Error('Exceeded memory limit');
1497
+ reader.totalMemoryLimit -= size;
1498
+ }
1499
+ }
1500
+
1501
+ function recoverMemory(reader: PsdReader, size: number) {
1502
+ if (reader.totalMemoryLimit !== undefined) {
1503
+ reader.totalMemoryLimit += size;
1504
+ }
1505
+ }
package/src/psdWriter.ts CHANGED
@@ -157,8 +157,16 @@ function getLargestLayerSize(layers: Layer[] = []): number {
157
157
  let max = 0;
158
158
 
159
159
  for (const layer of layers) {
160
- if (layer.canvas || layer.imageData) {
161
- const { width, height } = getLayerDimentions(layer);
160
+ const { width, height } = getLayerDimentions(layer);
161
+ max = Math.max(max, 2 * height + 2 * width * height);
162
+
163
+ if (layer.mask) {
164
+ const { width, height } = getLayerDimentions(layer.mask);
165
+ max = Math.max(max, 2 * height + 2 * width * height);
166
+ }
167
+
168
+ if (layer.realMask) {
169
+ const { width, height } = getLayerDimentions(layer.realMask);
162
170
  max = Math.max(max, 2 * height + 2 * width * height);
163
171
  }
164
172
 
@@ -472,7 +480,7 @@ function writeGlobalLayerMaskInfo(writer: PsdWriter, info: GlobalLayerMaskInfo |
472
480
  writeUint16(writer, info.colorSpace2);
473
481
  writeUint16(writer, info.colorSpace3);
474
482
  writeUint16(writer, info.colorSpace4);
475
- writeUint16(writer, info.opacity * 0xff);
483
+ writeUint16(writer, Math.round(info.opacity * 0xff));
476
484
  writeUint8(writer, info.kind);
477
485
  writeZeros(writer, 3);
478
486
  }
package/src/utf8.ts CHANGED
@@ -1,168 +1,178 @@
1
- function charLengthInBytes(code: number): number {
2
- if ((code & 0xffffff80) === 0) {
3
- return 1;
4
- } else if ((code & 0xfffff800) === 0) {
5
- return 2;
6
- } else if ((code & 0xffff0000) === 0) {
7
- return 3;
8
- } else {
9
- return 4;
10
- }
11
- }
12
-
13
- export function stringLengthInBytes(value: string): number {
14
- let result = 0;
15
-
16
- for (let i = 0; i < value.length; i++) {
17
- const code = value.charCodeAt(i);
18
-
19
- // high surrogate
20
- if (code >= 0xd800 && code <= 0xdbff) {
21
- if ((i + 1) < value.length) {
22
- const extra = value.charCodeAt(i + 1);
23
-
24
- // low surrogate
25
- if ((extra & 0xfc00) === 0xdc00) {
26
- i++;
27
- result += charLengthInBytes(((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);
28
- }
29
- }
30
- } else {
31
- result += charLengthInBytes(code);
32
- }
33
- }
34
-
35
- return result;
36
- }
37
-
38
- function writeCharacter(buffer: Uint8Array | Buffer, offset: number, code: number): number {
39
- const length = charLengthInBytes(code);
40
-
41
- switch (length) {
42
- case 1:
43
- buffer[offset] = code;
44
- break;
45
- case 2:
46
- buffer[offset] = ((code >> 6) & 0x1f) | 0xc0;
47
- buffer[offset + 1] = (code & 0x3f) | 0x80;
48
- break;
49
- case 3:
50
- buffer[offset] = ((code >> 12) & 0x0f) | 0xe0;
51
- buffer[offset + 1] = ((code >> 6) & 0x3f) | 0x80;
52
- buffer[offset + 2] = (code & 0x3f) | 0x80;
53
- break;
54
- default:
55
- buffer[offset] = ((code >> 18) & 0x07) | 0xf0;
56
- buffer[offset + 1] = ((code >> 12) & 0x3f) | 0x80;
57
- buffer[offset + 2] = ((code >> 6) & 0x3f) | 0x80;
58
- buffer[offset + 3] = (code & 0x3f) | 0x80;
59
- break;
60
- }
61
-
62
- return length;
63
- }
64
-
65
- export function encodeStringTo(buffer: Uint8Array | Buffer, offset: number, value: string): number {
66
- for (let i = 0; i < value.length; i++) {
67
- const code = value.charCodeAt(i);
68
-
69
- // high surrogate
70
- if (code >= 0xd800 && code <= 0xdbff) {
71
- if ((i + 1) < value.length) {
72
- const extra = value.charCodeAt(i + 1);
73
-
74
- // low surrogate
75
- if ((extra & 0xfc00) === 0xdc00) {
76
- i++;
77
- const fullCode = ((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
78
- offset += writeCharacter(buffer, offset, fullCode);
79
- }
80
- }
81
- } else {
82
- offset += writeCharacter(buffer, offset, code);
83
- }
84
- }
85
-
86
- return offset;
87
- }
88
-
89
- export function encodeString(value: string): Uint8Array {
90
- if (value.length > 1000 && typeof TextEncoder !== 'undefined') {
91
- return (new TextEncoder()).encode(value);
92
- }
93
-
94
- const buffer = new Uint8Array(stringLengthInBytes(value));
95
- encodeStringTo(buffer, 0, value);
96
- return buffer;
97
- }
98
-
99
- function continuationByte(buffer: Uint8Array, index: number): number {
100
- if (index >= buffer.length) {
101
- throw Error('Invalid byte index');
102
- }
103
-
104
- const continuationByte = buffer[index];
105
-
106
- if ((continuationByte & 0xC0) === 0x80) {
107
- return continuationByte & 0x3F;
108
- } else {
109
- throw Error('Invalid continuation byte');
110
- }
111
- }
112
-
113
- export function decodeString(value: Uint8Array): string {
114
- if (value.byteLength > 1000 && typeof TextDecoder !== 'undefined') {
115
- return (new TextDecoder()).decode(value);
116
- }
117
-
118
- let result: string[] = [];
119
-
120
- for (let i = 0; i < value.length;) {
121
- const byte1 = value[i++];
122
- let code: number;
123
-
124
- if ((byte1 & 0x80) === 0) {
125
- code = byte1;
126
- } else if ((byte1 & 0xe0) === 0xc0) {
127
- const byte2 = continuationByte(value, i++);
128
- code = ((byte1 & 0x1f) << 6) | byte2;
129
-
130
- if (code < 0x80) {
131
- throw Error('Invalid continuation byte');
132
- }
133
- } else if ((byte1 & 0xf0) === 0xe0) {
134
- const byte2 = continuationByte(value, i++);
135
- const byte3 = continuationByte(value, i++);
136
- code = ((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3;
137
-
138
- if (code < 0x0800) {
139
- throw Error('Invalid continuation byte');
140
- }
141
-
142
- if (code >= 0xd800 && code <= 0xdfff) {
143
- throw Error(`Lone surrogate U+${code.toString(16).toUpperCase()} is not a scalar value`);
144
- }
145
- } else if ((byte1 & 0xf8) === 0xf0) {
146
- const byte2 = continuationByte(value, i++);
147
- const byte3 = continuationByte(value, i++);
148
- const byte4 = continuationByte(value, i++);
149
- code = ((byte1 & 0x0f) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
150
-
151
- if (code < 0x010000 || code > 0x10ffff) {
152
- throw Error('Invalid continuation byte');
153
- }
154
- } else {
155
- throw Error('Invalid UTF-8 detected');
156
- }
157
-
158
- if (code > 0xffff) {
159
- code -= 0x10000;
160
- result.push(String.fromCharCode(code >>> 10 & 0x3ff | 0xd800));
161
- code = 0xdc00 | code & 0x3ff;
162
- }
163
-
164
- result.push(String.fromCharCode(code));
165
- }
166
-
167
- return result.join('');
168
- }
1
+ function charLengthInBytes(code: number): number {
2
+ if ((code & 0xffffff80) === 0) {
3
+ return 1;
4
+ } else if ((code & 0xfffff800) === 0) {
5
+ return 2;
6
+ } else if ((code & 0xffff0000) === 0) {
7
+ return 3;
8
+ } else {
9
+ return 4;
10
+ }
11
+ }
12
+
13
+ // Reads a scalar value at index `i`, replacing unpaired surrogates with U+FFFD,
14
+ // matching how TextEncoder converts a string to a sequence of Unicode scalar values.
15
+ function codePointAt(value: string, i: number): { code: number; size: number } {
16
+ const code = value.charCodeAt(i);
17
+
18
+ // high surrogate
19
+ if (code >= 0xd800 && code <= 0xdbff && (i + 1) < value.length) {
20
+ const extra = value.charCodeAt(i + 1);
21
+
22
+ // low surrogate
23
+ if (extra >= 0xdc00 && extra <= 0xdfff) {
24
+ return { code: ((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000, size: 2 };
25
+ }
26
+ }
27
+
28
+ // unpaired surrogate (lone high or lone low)
29
+ if (code >= 0xd800 && code <= 0xdfff) {
30
+ return { code: 0xfffd, size: 1 };
31
+ }
32
+
33
+ return { code, size: 1 };
34
+ }
35
+
36
+ export function stringLengthInBytes(value: string): number {
37
+ let result = 0;
38
+
39
+ for (let i = 0; i < value.length;) {
40
+ const { code, size } = codePointAt(value, i);
41
+ result += charLengthInBytes(code);
42
+ i += size;
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ function writeCharacter(buffer: Uint8Array | Buffer, offset: number, code: number): number {
49
+ const length = charLengthInBytes(code);
50
+
51
+ switch (length) {
52
+ case 1:
53
+ buffer[offset] = code;
54
+ break;
55
+ case 2:
56
+ buffer[offset] = ((code >> 6) & 0x1f) | 0xc0;
57
+ buffer[offset + 1] = (code & 0x3f) | 0x80;
58
+ break;
59
+ case 3:
60
+ buffer[offset] = ((code >> 12) & 0x0f) | 0xe0;
61
+ buffer[offset + 1] = ((code >> 6) & 0x3f) | 0x80;
62
+ buffer[offset + 2] = (code & 0x3f) | 0x80;
63
+ break;
64
+ default:
65
+ buffer[offset] = ((code >> 18) & 0x07) | 0xf0;
66
+ buffer[offset + 1] = ((code >> 12) & 0x3f) | 0x80;
67
+ buffer[offset + 2] = ((code >> 6) & 0x3f) | 0x80;
68
+ buffer[offset + 3] = (code & 0x3f) | 0x80;
69
+ break;
70
+ }
71
+
72
+ return length;
73
+ }
74
+
75
+ export function encodeStringTo(buffer: Uint8Array | Buffer, offset: number, value: string): number {
76
+ for (let i = 0; i < value.length;) {
77
+ const { code, size } = codePointAt(value, i);
78
+ offset += writeCharacter(buffer, offset, code);
79
+ i += size;
80
+ }
81
+
82
+ return offset;
83
+ }
84
+
85
+ export function encodeString(value: string): Uint8Array {
86
+ if (value.length > 1000 && typeof TextEncoder !== 'undefined') {
87
+ return (new TextEncoder()).encode(value);
88
+ }
89
+
90
+ const buffer = new Uint8Array(stringLengthInBytes(value));
91
+ encodeStringTo(buffer, 0, value);
92
+ return buffer;
93
+ }
94
+
95
+ // UTF-8 decoder implementing the WHATWG Encoding Standard's non-fatal error handling,
96
+ // so malformed byte sequences are replaced with U+FFFD instead of throwing, matching TextDecoder.
97
+ export function decodeString(value: Uint8Array): string {
98
+ if (value.byteLength > 1000 && typeof TextDecoder !== 'undefined') {
99
+ return (new TextDecoder()).decode(value);
100
+ }
101
+
102
+ const result: string[] = [];
103
+
104
+ function pushCodePoint(code: number) {
105
+ if (code > 0xffff) {
106
+ code -= 0x10000;
107
+ result.push(String.fromCharCode((code >>> 10 & 0x3ff) | 0xd800));
108
+ code = 0xdc00 | (code & 0x3ff);
109
+ }
110
+
111
+ result.push(String.fromCharCode(code));
112
+ }
113
+
114
+ let codePoint = 0;
115
+ let bytesSeen = 0;
116
+ let bytesNeeded = 0;
117
+ let lowerBoundary = 0x80;
118
+ let upperBoundary = 0xbf;
119
+
120
+ for (let i = 0; i < value.length; i++) {
121
+ const byte = value[i];
122
+
123
+ if (bytesNeeded === 0) {
124
+ if (byte <= 0x7f) {
125
+ pushCodePoint(byte);
126
+ } else if (byte >= 0xc2 && byte <= 0xdf) {
127
+ bytesNeeded = 1;
128
+ codePoint = byte & 0x1f;
129
+ } else if (byte >= 0xe0 && byte <= 0xef) {
130
+ if (byte === 0xe0) lowerBoundary = 0xa0;
131
+ if (byte === 0xed) upperBoundary = 0x9f;
132
+ bytesNeeded = 2;
133
+ codePoint = byte & 0x0f;
134
+ } else if (byte >= 0xf0 && byte <= 0xf4) {
135
+ if (byte === 0xf0) lowerBoundary = 0x90;
136
+ if (byte === 0xf4) upperBoundary = 0x8f;
137
+ bytesNeeded = 3;
138
+ codePoint = byte & 0x07;
139
+ } else {
140
+ // invalid leading byte
141
+ pushCodePoint(0xfffd);
142
+ }
143
+
144
+ continue;
145
+ }
146
+
147
+ if (byte < lowerBoundary || byte > upperBoundary) {
148
+ // invalid continuation byte: emit replacement and reprocess this byte as a new sequence start
149
+ codePoint = 0;
150
+ bytesNeeded = 0;
151
+ bytesSeen = 0;
152
+ lowerBoundary = 0x80;
153
+ upperBoundary = 0xbf;
154
+ pushCodePoint(0xfffd);
155
+ i--;
156
+ continue;
157
+ }
158
+
159
+ lowerBoundary = 0x80;
160
+ upperBoundary = 0xbf;
161
+ codePoint = (codePoint << 6) | (byte & 0x3f);
162
+ bytesSeen++;
163
+
164
+ if (bytesSeen !== bytesNeeded) continue;
165
+
166
+ pushCodePoint(codePoint);
167
+ codePoint = 0;
168
+ bytesNeeded = 0;
169
+ bytesSeen = 0;
170
+ }
171
+
172
+ // truncated sequence at end of input
173
+ if (bytesNeeded !== 0) {
174
+ pushCodePoint(0xfffd);
175
+ }
176
+
177
+ return result.join('');
178
+ }