ag-psd 31.0.3 → 32.0.0
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.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
- package/CHANGELOG.md +29 -0
- package/dist/abr.js +12 -5
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +2 -0
- package/dist/additionalInfo.js +123 -68
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +1365 -632
- package/dist/descriptor.d.ts +15 -2
- package/dist/descriptor.js +329 -100
- package/dist/descriptor.js.map +1 -1
- package/dist/effectsHelpers.js +12 -12
- package/dist/effectsHelpers.js.map +1 -1
- package/dist/engineData.d.ts +3 -1
- package/dist/engineData.js +34 -13
- package/dist/engineData.js.map +1 -1
- package/dist/engineData2.js +50 -21
- package/dist/engineData2.js.map +1 -1
- package/dist/helpers.d.ts +5 -2
- package/dist/helpers.js +18 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +45 -21
- package/dist/imageResources.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +27 -7
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +6 -1
- package/dist/psdReader.d.ts +2 -2
- package/dist/psdReader.js +457 -270
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +98 -76
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.js +126 -23
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +36 -15
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.js +12 -5
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +2 -0
- package/dist-es/additionalInfo.js +123 -68
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.d.ts +15 -2
- package/dist-es/descriptor.js +329 -100
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/effectsHelpers.js +12 -12
- package/dist-es/effectsHelpers.js.map +1 -1
- package/dist-es/engineData.d.ts +3 -1
- package/dist-es/engineData.js +34 -13
- package/dist-es/engineData.js.map +1 -1
- package/dist-es/engineData2.js +50 -21
- package/dist-es/engineData2.js.map +1 -1
- package/dist-es/helpers.d.ts +5 -2
- package/dist-es/helpers.js +18 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +45 -21
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +27 -7
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +6 -1
- package/dist-es/psdReader.d.ts +2 -2
- package/dist-es/psdReader.js +457 -270
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +98 -76
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.js +126 -23
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +36 -15
- package/dist-es/utf8.js.map +1 -1
- package/package.json +1 -1
- package/src/abr.ts +14 -6
- package/src/additionalInfo.ts +127 -66
- package/src/descriptor.ts +347 -107
- package/src/effectsHelpers.ts +9 -9
- package/src/engineData.ts +36 -13
- package/src/engineData2.ts +468 -432
- package/src/helpers.ts +19 -4
- package/src/imageResources.ts +49 -22
- package/src/index.ts +1 -0
- package/src/jpeg.ts +24 -9
- package/src/psd.ts +3 -1
- package/src/psdReader.ts +477 -260
- package/src/psdWriter.ts +100 -83
- package/src/text.ts +122 -12
- package/src/utf8.ts +35 -17
package/src/psdWriter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo, PixelData, LayerMaskData, Compression, ChannelID, PatternInfo } from './psd';
|
|
2
|
-
import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction, imageDataToCanvas } from './helpers';
|
|
2
|
+
import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction, imageDataToCanvas, denormalizeLabAB } from './helpers';
|
|
3
3
|
import { ExtendedWriteOptions, infoHandlers } from './additionalInfo';
|
|
4
4
|
import { InternalImageResources, resourceHandlers } from './imageResources';
|
|
5
5
|
|
|
@@ -153,39 +153,45 @@ export function writeUnicodeStringWithPadding(writer: PsdWriter, text: string) {
|
|
|
153
153
|
writeUint16(writer, 0);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
// Worst-case size of writeDataRLE output for a single channel: per row the byte count (2 bytes, 4 for PSB)
|
|
157
|
+
// plus PackBits data, which in the worst case is all literal runs of up to 128 bytes, each with 1 header byte.
|
|
158
|
+
export function getRLEChannelBound(width: number, height: number, large: boolean) {
|
|
159
|
+
return height * ((large ? 4 : 2) + width + Math.ceil(width / 128));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getLargestLayerSize(layers: Layer[] = [], large: boolean): number {
|
|
157
163
|
let max = 0;
|
|
158
164
|
|
|
159
165
|
for (const layer of layers) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
max = Math.max(max, 2 * height + 2 * width * height);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (layer.realMask) {
|
|
169
|
-
const { width, height } = getLayerDimentions(layer.realMask);
|
|
170
|
-
max = Math.max(max, 2 * height + 2 * width * height);
|
|
166
|
+
for (const target of [layer, layer.mask, layer.realMask]) {
|
|
167
|
+
if (target) {
|
|
168
|
+
const { width, height } = getLayerDimentions(target);
|
|
169
|
+
max = Math.max(max, getRLEChannelBound(width, height, large));
|
|
170
|
+
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
if (layer.children) {
|
|
174
|
-
max = Math.max(max, getLargestLayerSize(layer.children));
|
|
174
|
+
max = Math.max(max, getLargestLayerSize(layer.children, large));
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
return max;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
export function writeUint64(writer: PsdWriter, value: number) {
|
|
182
|
+
writeUint32(writer, Math.floor(value / 0x100000000));
|
|
183
|
+
writeUint32(writer, value >>> 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
181
186
|
export function writeSection(writer: PsdWriter, round: number, func: () => void, writeTotalLength = false, large = false) {
|
|
182
|
-
if (large) writeUint32(writer, 0);
|
|
183
187
|
const offset = writer.offset;
|
|
188
|
+
if (large) writeUint32(writer, 0);
|
|
184
189
|
writeUint32(writer, 0);
|
|
190
|
+
const start = writer.offset;
|
|
185
191
|
|
|
186
192
|
func();
|
|
187
193
|
|
|
188
|
-
let length = writer.offset -
|
|
194
|
+
let length = writer.offset - start;
|
|
189
195
|
let len = length;
|
|
190
196
|
|
|
191
197
|
while (len % round) {
|
|
@@ -193,32 +199,34 @@ export function writeSection(writer: PsdWriter, round: number, func: () => void,
|
|
|
193
199
|
len++;
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
// while (writer.offset % round) {
|
|
197
|
-
// writeUint8(writer, 0);
|
|
198
|
-
// len++;
|
|
199
|
-
// }
|
|
200
|
-
|
|
201
202
|
if (writeTotalLength) {
|
|
202
203
|
length = len;
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
if (large) {
|
|
207
|
+
writer.view.setUint32(offset, Math.floor(length / 0x100000000), false);
|
|
208
|
+
writer.view.setUint32(offset + 4, length >>> 0, false);
|
|
209
|
+
} else {
|
|
210
|
+
if (length > 0xffffffff) throw new Error('Section too large, use PSB format instead');
|
|
211
|
+
writer.view.setUint32(offset, length, false);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function verifyPixelData(data: PixelData | undefined, name: string) {
|
|
216
|
+
// check element size instead of instanceof, so arrays from other realms (iframes, workers, vm) are accepted
|
|
217
|
+
const bytesPerElement = data && (data.data as any).BYTES_PER_ELEMENT;
|
|
218
|
+
if (bytesPerElement !== undefined && bytesPerElement !== 1) {
|
|
219
|
+
throw new Error(`${name} has incorrect bitDepth (only 8-bit Uint8Array or Uint8ClampedArray data is supported for writing)`);
|
|
220
|
+
}
|
|
206
221
|
}
|
|
207
222
|
|
|
208
223
|
function verifyBitCount(target: Psd | Layer) {
|
|
209
224
|
target.children?.forEach(verifyBitCount);
|
|
210
225
|
|
|
211
|
-
|
|
212
|
-
if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
|
|
213
|
-
throw new Error('imageData has incorrect bitDepth');
|
|
214
|
-
}
|
|
226
|
+
verifyPixelData(target.imageData, 'imageData');
|
|
215
227
|
|
|
216
|
-
if ('mask' in target
|
|
217
|
-
|
|
218
|
-
if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
|
|
219
|
-
throw new Error('mask imageData has incorrect bitDepth');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
228
|
+
if ('mask' in target) verifyPixelData(target.mask?.imageData, 'mask imageData');
|
|
229
|
+
if ('realMask' in target) verifyPixelData(target.realMask?.imageData, 'realMask imageData');
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}) {
|
|
@@ -233,6 +241,10 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
233
241
|
if (bitsPerChannel !== 8)
|
|
234
242
|
throw new Error('bitsPerChannel other than 8 are not supported for writing');
|
|
235
243
|
|
|
244
|
+
const layers: Layer[] = [];
|
|
245
|
+
addChildren(layers, psd.children);
|
|
246
|
+
if (!layers.length) layers.push({});
|
|
247
|
+
|
|
236
248
|
verifyBitCount(psd);
|
|
237
249
|
|
|
238
250
|
const imageResources: InternalImageResources = { ...psd.imageResources };
|
|
@@ -252,8 +264,8 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
252
264
|
throw new Error('Document canvas must have the same size as document');
|
|
253
265
|
|
|
254
266
|
const globalAlpha = !!imageData && hasAlpha(imageData);
|
|
255
|
-
|
|
256
|
-
writer.tempBuffer = new Uint8Array(
|
|
267
|
+
// only used for encoding a single layer channel at a time, composite image is encoded directly into the writer buffer
|
|
268
|
+
writer.tempBuffer = new Uint8Array(getLargestLayerSize(psd.children, !!options.psb));
|
|
257
269
|
|
|
258
270
|
// header
|
|
259
271
|
writeSignature(writer, '8BPS');
|
|
@@ -276,10 +288,6 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
276
288
|
// TODO: other data?
|
|
277
289
|
});
|
|
278
290
|
|
|
279
|
-
const layers: Layer[] = [];
|
|
280
|
-
addChildren(layers, psd.children);
|
|
281
|
-
if (!layers.length) layers.push({});
|
|
282
|
-
|
|
283
291
|
// image resources
|
|
284
292
|
|
|
285
293
|
imageResources.layersGroup = layers.map(l => l.linkGroup || 0);
|
|
@@ -305,38 +313,49 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
305
313
|
writeAdditionalLayerInfo(writer, psd, psd, opt);
|
|
306
314
|
}, undefined, !!opt.psb);
|
|
307
315
|
|
|
308
|
-
|
|
309
|
-
const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
|
|
310
|
-
const width = imageData ? imageData.width : psd.width;
|
|
311
|
-
const height = imageData ? imageData.height : psd.height;
|
|
312
|
-
const data: PixelData = { data: new Uint8Array(width * height * 4), width, height };
|
|
316
|
+
writer.tempBuffer = undefined;
|
|
313
317
|
|
|
318
|
+
// image data
|
|
314
319
|
writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
|
|
315
320
|
|
|
316
321
|
if (RAW_IMAGE_DATA && (psd as any).imageDataRaw) {
|
|
317
322
|
console.log('writing raw image data');
|
|
318
323
|
writeBytes(writer, (psd as any).imageDataRaw);
|
|
319
324
|
} else {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
325
|
+
const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
|
|
326
|
+
const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
|
|
327
|
+
|
|
328
|
+
// encode directly into the writer buffer
|
|
329
|
+
const maxSize = channels.length * getRLEChannelBound(data.width, data.height, !!options.psb);
|
|
330
|
+
ensureSize(writer, writer.offset + maxSize);
|
|
331
|
+
const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
|
|
332
|
+
writer.offset += writeDataRLE(buffer, data, channels, !!options.psb).length;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function getCompositeData(imageData: PixelData | undefined, width: number, height: number, globalAlpha: boolean): PixelData {
|
|
337
|
+
if (!imageData) return { data: new Uint8Array(width * height * 4), width, height };
|
|
338
|
+
|
|
339
|
+
const size = width * height * 4;
|
|
340
|
+
const src = imageData.data;
|
|
341
|
+
let dst: Uint8Array | undefined = undefined;
|
|
342
|
+
|
|
343
|
+
// add weird white matte (copy the data only if there are semi-transparent pixels)
|
|
344
|
+
if (globalAlpha) {
|
|
345
|
+
for (let i = 0; i < size; i += 4) {
|
|
346
|
+
const pa = src[i + 3];
|
|
347
|
+
if (pa != 0 && pa != 255) {
|
|
348
|
+
if (!dst) dst = new Uint8Array(src.buffer, src.byteOffset, size).slice();
|
|
349
|
+
const a = pa / 255;
|
|
350
|
+
const ra = 255 * (1 - a);
|
|
351
|
+
dst[i + 0] = src[i + 0] * a + ra;
|
|
352
|
+
dst[i + 1] = src[i + 1] * a + ra;
|
|
353
|
+
dst[i + 2] = src[i + 2] * a + ra;
|
|
335
354
|
}
|
|
336
355
|
}
|
|
337
|
-
|
|
338
|
-
writeBytes(writer, writeDataRLE(writer.tempBuffer, data, channels, !!options.psb));
|
|
339
356
|
}
|
|
357
|
+
|
|
358
|
+
return dst ? { data: dst, width, height } : imageData;
|
|
340
359
|
}
|
|
341
360
|
|
|
342
361
|
function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
|
|
@@ -357,8 +376,11 @@ function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlph
|
|
|
357
376
|
|
|
358
377
|
for (const c of channels) {
|
|
359
378
|
writeInt16(writer, c.id);
|
|
360
|
-
if (options.psb)
|
|
361
|
-
|
|
379
|
+
if (options.psb) {
|
|
380
|
+
writeUint64(writer, c.length);
|
|
381
|
+
} else {
|
|
382
|
+
writeUint32(writer, c.length);
|
|
383
|
+
}
|
|
362
384
|
}
|
|
363
385
|
|
|
364
386
|
writeSignature(writer, '8BIM');
|
|
@@ -398,7 +420,7 @@ function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlph
|
|
|
398
420
|
}
|
|
399
421
|
|
|
400
422
|
function writeLayerMaskData(writer: PsdWriter, { mask, realMask }: Layer, layerData: LayerChannelData) {
|
|
401
|
-
writeSection(writer,
|
|
423
|
+
writeSection(writer, 4, () => {
|
|
402
424
|
if (!mask && !realMask) return;
|
|
403
425
|
|
|
404
426
|
let params = 0, flags = 0, realFlags = 0;
|
|
@@ -444,9 +466,7 @@ function writeLayerMaskData(writer: PsdWriter, { mask, realMask }: Layer, layerD
|
|
|
444
466
|
if (mask.vectorMaskDensity !== undefined) writeUint8(writer, Math.round(mask.vectorMaskDensity * 0xff));
|
|
445
467
|
if (mask.vectorMaskFeather !== undefined) writeFloat64(writer, mask.vectorMaskFeather);
|
|
446
468
|
}
|
|
447
|
-
|
|
448
|
-
writeZeros(writer, 2);
|
|
449
|
-
});
|
|
469
|
+
}, true); // photoshop pads mask data to multiple of 4 bytes and includes padding in the length
|
|
450
470
|
}
|
|
451
471
|
|
|
452
472
|
function writerBlendingRange(writer: PsdWriter, range: number[]) {
|
|
@@ -480,7 +500,7 @@ function writeGlobalLayerMaskInfo(writer: PsdWriter, info: GlobalLayerMaskInfo |
|
|
|
480
500
|
writeUint16(writer, info.colorSpace2);
|
|
481
501
|
writeUint16(writer, info.colorSpace3);
|
|
482
502
|
writeUint16(writer, info.colorSpace4);
|
|
483
|
-
writeUint16(writer, Math.round(info.opacity *
|
|
503
|
+
writeUint16(writer, Math.round(clamp(info.opacity, 0, 1) * 100)); // 0-100
|
|
484
504
|
writeUint8(writer, info.kind);
|
|
485
505
|
writeZeros(writer, 3);
|
|
486
506
|
}
|
|
@@ -555,11 +575,8 @@ function addChildren(layers: Layer[], children: Layer[] | undefined) {
|
|
|
555
575
|
}
|
|
556
576
|
|
|
557
577
|
function resizeBuffer(writer: PsdWriter, size: number) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
do {
|
|
561
|
-
newLength *= 2;
|
|
562
|
-
} while (size > newLength);
|
|
578
|
+
// grow at least 2x to keep small writes fast, but don't overshoot large reservations (like composite image data)
|
|
579
|
+
const newLength = Math.max(writer.buffer.byteLength * 2, size);
|
|
563
580
|
|
|
564
581
|
const newBuffer = new ArrayBuffer(newLength);
|
|
565
582
|
const newBytes = new Uint8Array(newBuffer);
|
|
@@ -587,11 +604,11 @@ function createThumbnail(psd: Psd) {
|
|
|
587
604
|
|
|
588
605
|
if (psd.width > psd.height) {
|
|
589
606
|
canvas.width = 160;
|
|
590
|
-
canvas.height = Math.floor(psd.height * (canvas.width / psd.width));
|
|
607
|
+
canvas.height = Math.max(1, Math.floor(psd.height * (canvas.width / psd.width)));
|
|
591
608
|
scale = canvas.width / psd.width;
|
|
592
609
|
} else {
|
|
593
610
|
canvas.height = 160;
|
|
594
|
-
canvas.width = Math.floor(psd.width * (canvas.height / psd.height));
|
|
611
|
+
canvas.width = Math.max(1, Math.floor(psd.width * (canvas.height / psd.height)));
|
|
595
612
|
scale = canvas.height / psd.height;
|
|
596
613
|
}
|
|
597
614
|
|
|
@@ -639,7 +656,7 @@ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, la
|
|
|
639
656
|
buffer = writeDataZipWithoutPrediction(imageData, [0]);
|
|
640
657
|
compression = Compression.ZipWithoutPrediction;
|
|
641
658
|
} else {
|
|
642
|
-
buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb)
|
|
659
|
+
buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb).slice(); // copy, temp buffer is reused
|
|
643
660
|
compression = Compression.RleCompressed;
|
|
644
661
|
}
|
|
645
662
|
|
|
@@ -768,7 +785,7 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
|
|
|
768
785
|
data = writeDataZipWithoutPrediction(imageData, [offset]);
|
|
769
786
|
compression = Compression.ZipWithoutPrediction;
|
|
770
787
|
} else {
|
|
771
|
-
data = writeDataRLE(tempBuffer, imageData, [offset], !!options.psb)
|
|
788
|
+
data = writeDataRLE(tempBuffer, imageData, [offset], !!options.psb).slice(); // copy, temp buffer is reused
|
|
772
789
|
compression = Compression.RleCompressed;
|
|
773
790
|
}
|
|
774
791
|
|
|
@@ -841,8 +858,8 @@ export function writeColor(writer: PsdWriter, color: Color | undefined) {
|
|
|
841
858
|
} else if ('l' in color) {
|
|
842
859
|
writeUint16(writer, ColorSpace.Lab);
|
|
843
860
|
writeInt16(writer, Math.round(color.l * 10000));
|
|
844
|
-
writeInt16(writer, Math.round(
|
|
845
|
-
writeInt16(writer, Math.round(
|
|
861
|
+
writeInt16(writer, Math.round(denormalizeLabAB(color.a, 100)));
|
|
862
|
+
writeInt16(writer, Math.round(denormalizeLabAB(color.b, 100)));
|
|
846
863
|
writeUint16(writer, 0);
|
|
847
864
|
} else if ('h' in color) {
|
|
848
865
|
writeUint16(writer, ColorSpace.HSB);
|
|
@@ -899,6 +916,10 @@ export function writePattern(writer: PsdWriter, pattern: PatternInfo) {
|
|
|
899
916
|
|
|
900
917
|
writeUint32(writer, 24); // channels count
|
|
901
918
|
|
|
919
|
+
// ponytail: pattern channels always use small (2-byte) RLE scanline headers, matching
|
|
920
|
+
// readPattern which reads them as non-large
|
|
921
|
+
const buffer = new Uint8Array(getRLEChannelBound(width, height, false));
|
|
922
|
+
|
|
902
923
|
// channels: RGB at indices 0,1,2 and alpha at index 25
|
|
903
924
|
for (let i = 0; i < 24 + 2; i++) {
|
|
904
925
|
const offset = i < 3 ? i : (i === 25 ? 3 : -1);
|
|
@@ -908,11 +929,7 @@ export function writePattern(writer: PsdWriter, pattern: PatternInfo) {
|
|
|
908
929
|
continue;
|
|
909
930
|
}
|
|
910
931
|
|
|
911
|
-
|
|
912
|
-
const buffer = new Uint8Array(width * height + 2 * height + 2 * width + 16);
|
|
913
|
-
// ponytail: pattern channels always use small (2-byte) RLE scanline headers, matching
|
|
914
|
-
// readPattern which reads them as non-large; `large` here only affects the header width.
|
|
915
|
-
const data = writeDataRLE(buffer, pixelData, [offset], false)!;
|
|
932
|
+
const data = writeDataRLE(buffer, pixelData, [offset], false);
|
|
916
933
|
|
|
917
934
|
writeUint32(writer, 1); // has
|
|
918
935
|
writeUint32(writer, data.length + 4 + 16 + 2 + 1); // length
|
package/src/text.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TextStyle, LayerTextData, ParagraphStyle, Font, AntiAlias, TextGridInfo, Justification, Color, TextPath } from './psd';
|
|
2
|
+
import { clamp } from './helpers';
|
|
2
3
|
|
|
3
4
|
interface Adjustments {
|
|
4
5
|
Axis: number[];
|
|
@@ -300,13 +301,43 @@ function decodeColor(color: TypeValues): Color {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
// h, s, b in 0..1 range, returns r, g, b in 0..1 range
|
|
305
|
+
function hsbToRgb(h: number, s: number, v: number): [number, number, number] {
|
|
306
|
+
h = clamp(h || 0, 0, 1);
|
|
307
|
+
s = clamp(s || 0, 0, 1);
|
|
308
|
+
v = clamp(v || 0, 0, 1);
|
|
309
|
+
|
|
310
|
+
const h6 = (h * 6) % 6;
|
|
311
|
+
const i = Math.floor(h6);
|
|
312
|
+
const f = h6 - i;
|
|
313
|
+
const p = v * (1 - s);
|
|
314
|
+
const q = v * (1 - s * f);
|
|
315
|
+
const t = v * (1 - s * (1 - f));
|
|
316
|
+
|
|
317
|
+
switch (i) {
|
|
318
|
+
case 0: return [v, t, p];
|
|
319
|
+
case 1: return [q, v, p];
|
|
320
|
+
case 2: return [p, v, t];
|
|
321
|
+
case 3: return [p, q, v];
|
|
322
|
+
case 4: return [t, p, v];
|
|
323
|
+
default: return [v, p, q];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
303
327
|
function encodeColor(color: Color | undefined): TypeValues {
|
|
304
328
|
if (!color) {
|
|
305
329
|
return { Type: 1, Values: [0, 0, 0, 0] };
|
|
306
330
|
} else if ('r' in color) {
|
|
307
331
|
return { Type: 1, Values: ['a' in color ? color.a / 255 : 1, color.r / 255, color.g / 255, color.b / 255] };
|
|
332
|
+
} else if ('fr' in color) {
|
|
333
|
+
return { Type: 1, Values: [1, clamp(color.fr || 0, 0, 1), clamp(color.fg || 0, 0, 1), clamp(color.fb || 0, 0, 1)] };
|
|
334
|
+
} else if ('h' in color) {
|
|
335
|
+
const [r, g, b] = hsbToRgb(color.h, color.s, color.b);
|
|
336
|
+
return { Type: 1, Values: [1, r, g, b] };
|
|
308
337
|
} else if ('c' in color) {
|
|
309
338
|
return { Type: 2, Values: [1, color.c / 255, color.m / 255, color.y / 255, color.k / 255] };
|
|
339
|
+
} else if ('l' in color) {
|
|
340
|
+
throw new Error('LAB colors are not supported in text layers, use RGB, CMYK or grayscale color instead');
|
|
310
341
|
} else if ('k' in color) {
|
|
311
342
|
return { Type: 0, Values: [1, color.k / 255] };
|
|
312
343
|
} else {
|
|
@@ -346,9 +377,10 @@ function decodeObject(obj: any, keys: string[], fonts: Font[]) {
|
|
|
346
377
|
if (obj[Key] === undefined) continue;
|
|
347
378
|
|
|
348
379
|
if (key === 'justification') {
|
|
349
|
-
result[key] = justification[obj[Key]];
|
|
380
|
+
result[key] = justification[obj[Key]] ?? 'left';
|
|
350
381
|
} else if (key === 'font') {
|
|
351
|
-
|
|
382
|
+
const font = fonts[obj[Key]];
|
|
383
|
+
if (font) result[key] = font; // invalid font index, leave the font unset so it falls back to default
|
|
352
384
|
} else if (key === 'fillColor' || key === 'strokeColor') {
|
|
353
385
|
result[key] = decodeColor(obj[Key]);
|
|
354
386
|
} else {
|
|
@@ -368,7 +400,7 @@ function encodeObject(obj: any, keys: string[], fonts: Font[]) {
|
|
|
368
400
|
if (obj[key] === undefined) continue;
|
|
369
401
|
|
|
370
402
|
if (key === 'justification') {
|
|
371
|
-
result[Key] = justification.indexOf(obj[key] ?? 'left');
|
|
403
|
+
result[Key] = Math.max(0, justification.indexOf(obj[key] ?? 'left')); // default to 'left' for unknown values
|
|
372
404
|
} else if (key === 'font') {
|
|
373
405
|
result[Key] = findOrAddFont(fonts, obj[key]);
|
|
374
406
|
} else if (key === 'fillColor' || key === 'strokeColor') {
|
|
@@ -443,6 +475,39 @@ function deduplicateValues<T>(base: T, runs: { style: T; }[], keys: (keyof T)[])
|
|
|
443
475
|
}
|
|
444
476
|
}
|
|
445
477
|
|
|
478
|
+
function isDefaultGridColor(color: Color | undefined) {
|
|
479
|
+
// Photoshop writes default grid colors as blue with 0 alpha, ignore alpha when comparing
|
|
480
|
+
return !!color && 'r' in color && color.r === 0 && color.g === 0 && color.b === 255;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function decodeGridColor(color: TypeValues | undefined, defaultColor: Color): Color {
|
|
484
|
+
try {
|
|
485
|
+
return color ? decodeColor(color) : defaultColor;
|
|
486
|
+
} catch {
|
|
487
|
+
return defaultColor; // don't fail whole text layer because of invalid grid color
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function decodeGridInfo(grid: EngineData['EngineDict']['GridInfo'] | undefined): TextGridInfo | undefined {
|
|
492
|
+
if (!grid || typeof grid !== 'object') return undefined;
|
|
493
|
+
|
|
494
|
+
const result: TextGridInfo = {
|
|
495
|
+
isOn: !!grid.GridIsOn,
|
|
496
|
+
show: !!grid.ShowGrid,
|
|
497
|
+
size: grid.GridSize ?? defaultGridInfo.size,
|
|
498
|
+
leading: grid.GridLeading ?? defaultGridInfo.leading,
|
|
499
|
+
color: decodeGridColor(grid.GridColor, defaultGridInfo.color!),
|
|
500
|
+
leadingFillColor: decodeGridColor(grid.GridLeadingFillColor, defaultGridInfo.leadingFillColor!),
|
|
501
|
+
alignLineHeightToGridFlags: !!grid.AlignLineHeightToGridFlags,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const isDefault = !result.isOn && !result.show && result.size === defaultGridInfo.size &&
|
|
505
|
+
result.leading === defaultGridInfo.leading && !result.alignLineHeightToGridFlags &&
|
|
506
|
+
isDefaultGridColor(result.color) && isDefaultGridColor(result.leadingFillColor);
|
|
507
|
+
|
|
508
|
+
return isDefault ? undefined : result;
|
|
509
|
+
}
|
|
510
|
+
|
|
446
511
|
export function decodeEngineData(engineData: EngineData) {
|
|
447
512
|
// console.log('engineData', require('util').inspect(engineData, false, 99, true));
|
|
448
513
|
const engineDict = engineData.EngineDict;
|
|
@@ -458,9 +523,11 @@ export function decodeEngineData(engineData: EngineData) {
|
|
|
458
523
|
let text = engineDict.Editor.Text.replace(/\r/g, '\n');
|
|
459
524
|
let removedCharacters = 0;
|
|
460
525
|
|
|
461
|
-
|
|
526
|
+
// Photoshop always appends exactly one trailing \r (and encoder adds one back), any additional
|
|
527
|
+
// trailing new lines are empty paragraphs that are part of the text
|
|
528
|
+
if (/\n$/.test(text)) {
|
|
462
529
|
text = text.substring(0, text.length - 1);
|
|
463
|
-
removedCharacters
|
|
530
|
+
removedCharacters = 1;
|
|
464
531
|
}
|
|
465
532
|
|
|
466
533
|
const result: LayerTextData = {
|
|
@@ -484,6 +551,11 @@ export function decodeEngineData(engineData: EngineData) {
|
|
|
484
551
|
if (photoshop.BoxBounds) result.boxBounds = photoshop.BoxBounds;
|
|
485
552
|
}
|
|
486
553
|
|
|
554
|
+
// grid info (only included when it differs from defaults, to avoid noise in common case)
|
|
555
|
+
|
|
556
|
+
const gridInfo = decodeGridInfo(engineDict.GridInfo);
|
|
557
|
+
if (gridInfo) result.gridInfo = gridInfo;
|
|
558
|
+
|
|
487
559
|
// paragraph style
|
|
488
560
|
|
|
489
561
|
// const theNormalParagraphSheet = resourceDict.TheNormalParagraphSheet;
|
|
@@ -522,13 +594,18 @@ export function decodeEngineData(engineData: EngineData) {
|
|
|
522
594
|
// const styleSheetData = styleSheetSet[theNormalStyleSheet].StyleSheetData;
|
|
523
595
|
const styleRun = engineDict.StyleRun;
|
|
524
596
|
|
|
597
|
+
// style runs without a font use the font of the normal style sheet
|
|
598
|
+
const normalStyleSheet = resourceDict.StyleSheetSet?.[resourceDict.TheNormalStyleSheet ?? 0]?.StyleSheetData;
|
|
599
|
+
const normalFontIndex = normalStyleSheet?.Font;
|
|
600
|
+
const normalFont = (normalFontIndex !== undefined ? fonts[normalFontIndex] : undefined) ?? fonts[0];
|
|
601
|
+
|
|
525
602
|
result.style = {}; // decodeStyle(styleSheetData, fonts);
|
|
526
603
|
result.styleRuns = [];
|
|
527
604
|
|
|
528
605
|
for (let i = 0; i < styleRun.RunArray.length; i++) {
|
|
529
606
|
const length = styleRun.RunLengthArray[i];
|
|
530
607
|
const style = decodeStyle(styleRun.RunArray[i].StyleSheet.StyleSheetData, fonts);
|
|
531
|
-
if (!style.font) style.font =
|
|
608
|
+
if (!style.font && normalFont) style.font = normalFont;
|
|
532
609
|
result.styleRuns.push({ length, style });
|
|
533
610
|
}
|
|
534
611
|
|
|
@@ -545,8 +622,38 @@ export function decodeEngineData(engineData: EngineData) {
|
|
|
545
622
|
return result;
|
|
546
623
|
}
|
|
547
624
|
|
|
625
|
+
// Adjusts run lengths (specified in terms of the original text) for characters removed from the text
|
|
626
|
+
// (\r characters of \r\n pairs that are collapsed into single \r)
|
|
627
|
+
function adjustRunLengths<T extends { length: number; }>(runs: T[] | undefined, removed: number[]): T[] | undefined {
|
|
628
|
+
if (!runs || !removed.length) return runs;
|
|
629
|
+
|
|
630
|
+
let start = 0;
|
|
631
|
+
let removedIndex = 0;
|
|
632
|
+
|
|
633
|
+
return runs.map(run => {
|
|
634
|
+
const length = Math.max(0, run.length || 0);
|
|
635
|
+
const end = start + length;
|
|
636
|
+
let count = 0;
|
|
637
|
+
|
|
638
|
+
while (removedIndex < removed.length && removed[removedIndex] < end) {
|
|
639
|
+
if (removed[removedIndex] >= start) count++;
|
|
640
|
+
removedIndex++;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
start = end;
|
|
644
|
+
return { ...run, length: length - count };
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
548
648
|
export function encodeEngineData(data: LayerTextData) {
|
|
549
|
-
const
|
|
649
|
+
const sourceText = data.text || '';
|
|
650
|
+
const removedIndices: number[] = []; // indices of \r characters removed from \r\n pairs
|
|
651
|
+
|
|
652
|
+
for (let i = 0; i < sourceText.length - 1; i++) {
|
|
653
|
+
if (sourceText.charCodeAt(i) === 13 && sourceText.charCodeAt(i + 1) === 10) removedIndices.push(i);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const text = `${sourceText.replace(/\r?\n/g, '\r')}\r`;
|
|
550
657
|
|
|
551
658
|
const fonts: Font[] = [
|
|
552
659
|
{ name: 'AdobeInvisFont', script: 0, type: 0, synthetic: 0 },
|
|
@@ -555,7 +662,7 @@ export function encodeEngineData(data: LayerTextData) {
|
|
|
555
662
|
const defFont = data.style?.font || data.styleRuns?.find(s => s.style.font)?.style.font || defaultFont;
|
|
556
663
|
const paragraphRunArray: ParagraphRun[] = [];
|
|
557
664
|
const paragraphRunLengthArray: number[] = [];
|
|
558
|
-
const paragraphRuns = data.paragraphStyleRuns;
|
|
665
|
+
const paragraphRuns = adjustRunLengths(data.paragraphStyleRuns, removedIndices);
|
|
559
666
|
|
|
560
667
|
if (paragraphRuns && paragraphRuns.length) {
|
|
561
668
|
let leftLength = text.length;
|
|
@@ -609,7 +716,9 @@ export function encodeEngineData(data: LayerTextData) {
|
|
|
609
716
|
}
|
|
610
717
|
|
|
611
718
|
const styleSheetData = encodeStyle({ ...defaultStyle, font: defFont }, fonts);
|
|
612
|
-
const styleRuns = data.styleRuns
|
|
719
|
+
const styleRuns = data.styleRuns?.length ?
|
|
720
|
+
adjustRunLengths(data.styleRuns, removedIndices)! :
|
|
721
|
+
[{ length: text.length, style: data.style || {} }];
|
|
613
722
|
const styleRunArray: StyleRun[] = [];
|
|
614
723
|
const styleRunLengthArray: number[] = [];
|
|
615
724
|
|
|
@@ -642,7 +751,7 @@ export function encodeEngineData(data: LayerTextData) {
|
|
|
642
751
|
}
|
|
643
752
|
|
|
644
753
|
// add extra run to the end if existing ones didn't fill it up
|
|
645
|
-
if (leftLength
|
|
754
|
+
if (leftLength) {
|
|
646
755
|
styleRunLengthArray.push(leftLength);
|
|
647
756
|
styleRunArray.push({
|
|
648
757
|
StyleSheet: {
|
|
@@ -657,6 +766,7 @@ export function encodeEngineData(data: LayerTextData) {
|
|
|
657
766
|
}
|
|
658
767
|
|
|
659
768
|
const gridInfo = { ...defaultGridInfo, ...data.gridInfo };
|
|
769
|
+
const antiAliasIndex = antialias.indexOf(data.antiAlias ?? 'sharp');
|
|
660
770
|
const WritingDirection = data.orientation === 'vertical' ? 2 : 0;
|
|
661
771
|
const Procession = data.orientation === 'vertical' ? 1 : 0;
|
|
662
772
|
const ShapeType = data.shapeType === 'box' ? 1 : 0;
|
|
@@ -753,10 +863,10 @@ export function encodeEngineData(data: LayerTextData) {
|
|
|
753
863
|
GridSize: gridInfo.size ?? 18,
|
|
754
864
|
GridLeading: gridInfo.leading ?? 22,
|
|
755
865
|
GridColor: encodeColor(gridInfo.color),
|
|
756
|
-
GridLeadingFillColor: encodeColor(gridInfo.
|
|
866
|
+
GridLeadingFillColor: encodeColor(gridInfo.leadingFillColor),
|
|
757
867
|
AlignLineHeightToGridFlags: !!gridInfo.alignLineHeightToGridFlags,
|
|
758
868
|
},
|
|
759
|
-
AntiAlias: antialias.indexOf(
|
|
869
|
+
AntiAlias: antiAliasIndex === -1 ? antialias.indexOf('sharp') : antiAliasIndex,
|
|
760
870
|
UseFractionalGlyphWidths: data.useFractionalGlyphWidths ?? true,
|
|
761
871
|
Rendered: {
|
|
762
872
|
Version: 1,
|
package/src/utf8.ts
CHANGED
|
@@ -10,36 +10,48 @@ function charLengthInBytes(code: number): number {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// Returns scalar value at index `i` (combining surrogate pairs), replacing unpaired surrogates with U+FFFD,
|
|
14
14
|
// matching how TextEncoder converts a string to a sequence of Unicode scalar values.
|
|
15
|
-
|
|
15
|
+
// Values above 0xffff always take 2 UTF-16 code units.
|
|
16
|
+
function codePointAt(value: string, i: number): number {
|
|
16
17
|
const code = value.charCodeAt(i);
|
|
17
18
|
|
|
19
|
+
if (code < 0xd800 || code > 0xdfff) return code;
|
|
20
|
+
|
|
18
21
|
// high surrogate
|
|
19
|
-
if (code
|
|
22
|
+
if (code <= 0xdbff && (i + 1) < value.length) {
|
|
20
23
|
const extra = value.charCodeAt(i + 1);
|
|
21
24
|
|
|
22
25
|
// low surrogate
|
|
23
26
|
if (extra >= 0xdc00 && extra <= 0xdfff) {
|
|
24
|
-
return
|
|
27
|
+
return ((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
// unpaired surrogate (lone high or lone low)
|
|
29
|
-
|
|
30
|
-
return { code: 0xfffd, size: 1 };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { code, size: 1 };
|
|
32
|
+
return 0xfffd;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
export function stringLengthInBytes(value: string): number {
|
|
37
36
|
let result = 0;
|
|
38
37
|
|
|
39
|
-
for (let i = 0; i < value.length;) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
for (let i = 0; i < value.length; i++) {
|
|
39
|
+
const code = value.charCodeAt(i);
|
|
40
|
+
|
|
41
|
+
if (code < 0x80) {
|
|
42
|
+
result += 1;
|
|
43
|
+
} else if (code < 0x800) {
|
|
44
|
+
result += 2;
|
|
45
|
+
} else {
|
|
46
|
+
const scalar = codePointAt(value, i);
|
|
47
|
+
|
|
48
|
+
if (scalar > 0xffff) {
|
|
49
|
+
result += 4;
|
|
50
|
+
i++;
|
|
51
|
+
} else {
|
|
52
|
+
result += 3;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
return result;
|
|
@@ -73,10 +85,16 @@ function writeCharacter(buffer: Uint8Array | Buffer, offset: number, code: numbe
|
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
export function encodeStringTo(buffer: Uint8Array | Buffer, offset: number, value: string): number {
|
|
76
|
-
for (let i = 0; i < value.length;) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
for (let i = 0; i < value.length; i++) {
|
|
89
|
+
const code = value.charCodeAt(i);
|
|
90
|
+
|
|
91
|
+
if (code < 0x80) {
|
|
92
|
+
buffer[offset++] = code;
|
|
93
|
+
} else {
|
|
94
|
+
const scalar = codePointAt(value, i);
|
|
95
|
+
if (scalar > 0xffff) i++;
|
|
96
|
+
offset += writeCharacter(buffer, offset, scalar);
|
|
97
|
+
}
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
return offset;
|