@yft-design/psd-lib 1.0.1

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.
@@ -0,0 +1,3891 @@
1
+ import { fromByteArray, toByteArray } from 'base64-js';
2
+ import { readEffects, writeEffects } from './effectsHelpers';
3
+ import { clamp, createEnum, layerColors, MOCK_HANDLERS } from './helpers';
4
+ import { readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString, readPattern, readLayerInfo } from './psdReader';
5
+ import { writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32 } from './psdWriter';
6
+ import { Annt, BlnM, parsePercent, parseUnits, parseUnitsOrNumber, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, textGridding, unitsPercent, unitsValue, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, Ornt, horzVrtcToXY, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType } from './descriptor';
7
+ import { serializeEngineData, parseEngineData } from './engineData';
8
+ import { encodeEngineData, decodeEngineData } from './text';
9
+ import { decodeEngineData2 } from './engineData2';
10
+ const fromAtoZ = 'abcdefghijklmnopqrstuvwxyz';
11
+ export const infoHandlers = [];
12
+ export const infoHandlersMap = {};
13
+ function addHandler(key, has, read, write) {
14
+ const handler = { key, has, read, write };
15
+ infoHandlers.push(handler);
16
+ infoHandlersMap[handler.key] = handler;
17
+ }
18
+ function addHandlerAlias(key, target) {
19
+ infoHandlersMap[key] = infoHandlersMap[target];
20
+ }
21
+ function hasKey(key) {
22
+ return (target) => target[key] !== undefined;
23
+ }
24
+ function readLength64(reader) {
25
+ if (readUint32(reader))
26
+ throw new Error(`Resource size above 4 GB limit at ${reader.offset.toString(16)}`);
27
+ return readUint32(reader);
28
+ }
29
+ function writeLength64(writer, length) {
30
+ writeUint32(writer, 0);
31
+ writeUint32(writer, length);
32
+ }
33
+ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
34
+ if (readInt16(reader) !== 1)
35
+ throw new Error(`Invalid TySh version`);
36
+ const transform = [];
37
+ for (let i = 0; i < 6; i++)
38
+ transform.push(readFloat64(reader));
39
+ if (readInt16(reader) !== 50)
40
+ throw new Error(`Invalid TySh text version`);
41
+ const text = readVersionAndDescriptor(reader);
42
+ // console.log(require('util').inspect(text, false, 99, false), 'utf8');
43
+ if (readInt16(reader) !== 1)
44
+ throw new Error(`Invalid TySh warp version`);
45
+ const warp = readVersionAndDescriptor(reader);
46
+ // console.log(require('util').inspect(warp, false, 99, false), 'utf8');
47
+ target.text = {
48
+ transform,
49
+ left: readFloat32(reader),
50
+ top: readFloat32(reader),
51
+ right: readFloat32(reader),
52
+ bottom: readFloat32(reader),
53
+ text: text['Txt '].replace(/\r/g, '\n'),
54
+ index: text.TextIndex || 0,
55
+ gridding: textGridding.decode(text.textGridding),
56
+ antiAlias: Annt.decode(text.AntA),
57
+ orientation: Ornt.decode(text.Ornt),
58
+ warp: {
59
+ style: warpStyle.decode(warp.warpStyle),
60
+ value: warp.warpValue || 0,
61
+ perspective: warp.warpPerspective || 0,
62
+ perspectiveOther: warp.warpPerspectiveOther || 0,
63
+ rotate: Ornt.decode(warp.warpRotate),
64
+ },
65
+ };
66
+ if (text.bounds)
67
+ target.text.bounds = descBoundsToBounds(text.bounds);
68
+ if (text.boundingBox)
69
+ target.text.boundingBox = descBoundsToBounds(text.boundingBox);
70
+ if (text.EngineData) {
71
+ const engineData = parseEngineData(text.EngineData);
72
+ const textData = decodeEngineData(engineData);
73
+ // console.log(require('util').inspect(engineData, false, 99, false), 'utf8');
74
+ // require('fs').writeFileSync(`layer-${target.name}.txt`, require('util').inspect(engineData, false, 99, false), 'utf8');
75
+ // const before = parseEngineData(text.EngineData);
76
+ // const after = encodeEngineData(engineData);
77
+ // require('fs').writeFileSync('before.txt', require('util').inspect(before, false, 99, false), 'utf8');
78
+ // require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
79
+ // console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
80
+ target.text = { ...target.text, ...textData };
81
+ // console.log(require('util').inspect(target.text, false, 99, true));
82
+ }
83
+ skipBytes(reader, leftBytes());
84
+ }, (writer, target) => {
85
+ const text = target.text;
86
+ const warp = text.warp || {};
87
+ const transform = text.transform || [1, 0, 0, 1, 0, 0];
88
+ const textDescriptor = {
89
+ 'Txt ': (text.text || '').replace(/\r?\n/g, '\r'),
90
+ textGridding: textGridding.encode(text.gridding),
91
+ Ornt: Ornt.encode(text.orientation),
92
+ AntA: Annt.encode(text.antiAlias),
93
+ ...(text.bounds ? { bounds: boundsToDescBounds(text.bounds) } : {}),
94
+ ...(text.boundingBox ? { boundingBox: boundsToDescBounds(text.boundingBox) } : {}),
95
+ TextIndex: text.index || 0,
96
+ EngineData: serializeEngineData(encodeEngineData(text)),
97
+ };
98
+ writeInt16(writer, 1); // version
99
+ for (let i = 0; i < 6; i++) {
100
+ writeFloat64(writer, transform[i]);
101
+ }
102
+ writeInt16(writer, 50); // text version
103
+ writeVersionAndDescriptor(writer, '', 'TxLr', textDescriptor, 'text');
104
+ writeInt16(writer, 1); // warp version
105
+ writeVersionAndDescriptor(writer, '', 'warp', encodeWarp(warp));
106
+ writeFloat32(writer, text.left);
107
+ writeFloat32(writer, text.top);
108
+ writeFloat32(writer, text.right);
109
+ writeFloat32(writer, text.bottom);
110
+ // writeZeros(writer, 2);
111
+ });
112
+ // vector fills
113
+ addHandler('SoCo', target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
114
+ target.vectorFill.type === 'color', (reader, target) => {
115
+ const descriptor = readVersionAndDescriptor(reader);
116
+ target.vectorFill = parseVectorContent(descriptor);
117
+ }, (writer, target) => {
118
+ const { descriptor } = serializeVectorContent(target.vectorFill);
119
+ writeVersionAndDescriptor(writer, '', 'null', descriptor);
120
+ });
121
+ addHandler('GdFl', target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
122
+ (target.vectorFill.type === 'solid' || target.vectorFill.type === 'noise'), (reader, target, left) => {
123
+ const descriptor = readVersionAndDescriptor(reader);
124
+ target.vectorFill = parseVectorContent(descriptor);
125
+ skipBytes(reader, left());
126
+ }, (writer, target) => {
127
+ const { descriptor } = serializeVectorContent(target.vectorFill);
128
+ writeVersionAndDescriptor(writer, '', 'null', descriptor);
129
+ });
130
+ addHandler('PtFl', target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
131
+ target.vectorFill.type === 'pattern', (reader, target) => {
132
+ const descriptor = readVersionAndDescriptor(reader);
133
+ target.vectorFill = parseVectorContent(descriptor);
134
+ }, (writer, target) => {
135
+ const { descriptor } = serializeVectorContent(target.vectorFill);
136
+ writeVersionAndDescriptor(writer, '', 'null', descriptor);
137
+ });
138
+ addHandler('vscg', target => target.vectorFill !== undefined && target.vectorStroke !== undefined, (reader, target, left) => {
139
+ readSignature(reader); // key
140
+ const desc = readVersionAndDescriptor(reader);
141
+ target.vectorFill = parseVectorContent(desc);
142
+ skipBytes(reader, left());
143
+ }, (writer, target) => {
144
+ const { descriptor, key } = serializeVectorContent(target.vectorFill);
145
+ writeSignature(writer, key);
146
+ writeVersionAndDescriptor(writer, '', 'null', descriptor);
147
+ });
148
+ export function readBezierKnot(reader, width, height) {
149
+ const y0 = readFixedPointPath32(reader) * height;
150
+ const x0 = readFixedPointPath32(reader) * width;
151
+ const y1 = readFixedPointPath32(reader) * height;
152
+ const x1 = readFixedPointPath32(reader) * width;
153
+ const y2 = readFixedPointPath32(reader) * height;
154
+ const x2 = readFixedPointPath32(reader) * width;
155
+ return [x0, y0, x1, y1, x2, y2];
156
+ }
157
+ function writeBezierKnot(writer, points, width, height) {
158
+ writeFixedPointPath32(writer, points[1] / height); // y0
159
+ writeFixedPointPath32(writer, points[0] / width); // x0
160
+ writeFixedPointPath32(writer, points[3] / height); // y1
161
+ writeFixedPointPath32(writer, points[2] / width); // x1
162
+ writeFixedPointPath32(writer, points[5] / height); // y2
163
+ writeFixedPointPath32(writer, points[4] / width); // x2
164
+ }
165
+ export const booleanOperations = ['exclude', 'combine', 'subtract', 'intersect'];
166
+ export function readVectorMask(reader, vectorMask, width, height, size) {
167
+ const end = reader.offset + size;
168
+ const paths = vectorMask.paths;
169
+ let path = undefined;
170
+ while ((end - reader.offset) >= 26) {
171
+ const selector = readUint16(reader);
172
+ switch (selector) {
173
+ case 0: // Closed subpath length record
174
+ case 3: { // Open subpath length record
175
+ readUint16(reader); // count
176
+ const boolOp = readInt16(reader);
177
+ const flags = readUint16(reader); // bit 1 always 1 ?
178
+ skipBytes(reader, 18);
179
+ path = {
180
+ open: selector === 3,
181
+ knots: [],
182
+ fillRule: flags === 2 ? 'non-zero' : 'even-odd',
183
+ };
184
+ if (boolOp !== -1)
185
+ path.operation = booleanOperations[boolOp];
186
+ paths.push(path);
187
+ break;
188
+ }
189
+ case 1: // Closed subpath Bezier knot, linked
190
+ case 2: // Closed subpath Bezier knot, unlinked
191
+ case 4: // Open subpath Bezier knot, linked
192
+ case 5: // Open subpath Bezier knot, unlinked
193
+ path.knots.push({ linked: (selector === 1 || selector === 4), points: readBezierKnot(reader, width, height) });
194
+ break;
195
+ case 6: // Path fill rule record
196
+ skipBytes(reader, 24);
197
+ break;
198
+ case 7: { // Clipboard record
199
+ // TODO: check if these need to be multiplied by document size
200
+ const top = readFixedPointPath32(reader);
201
+ const left = readFixedPointPath32(reader);
202
+ const bottom = readFixedPointPath32(reader);
203
+ const right = readFixedPointPath32(reader);
204
+ const resolution = readFixedPointPath32(reader);
205
+ skipBytes(reader, 4);
206
+ vectorMask.clipboard = { top, left, bottom, right, resolution };
207
+ break;
208
+ }
209
+ case 8: // Initial fill rule record
210
+ vectorMask.fillStartsWithAllPixels = !!readUint16(reader);
211
+ skipBytes(reader, 22);
212
+ break;
213
+ default: throw new Error('Invalid vmsk section');
214
+ }
215
+ }
216
+ return paths;
217
+ }
218
+ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
219
+ if (readUint32(reader) !== 3)
220
+ throw new Error('Invalid vmsk version');
221
+ target.vectorMask = { paths: [] };
222
+ const vectorMask = target.vectorMask;
223
+ const flags = readUint32(reader);
224
+ vectorMask.invert = (flags & 1) !== 0;
225
+ vectorMask.notLink = (flags & 2) !== 0;
226
+ vectorMask.disable = (flags & 4) !== 0;
227
+ readVectorMask(reader, vectorMask, width, height, left());
228
+ // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
229
+ skipBytes(reader, left());
230
+ }, (writer, target, { width, height }) => {
231
+ const vectorMask = target.vectorMask;
232
+ const flags = (vectorMask.invert ? 1 : 0) |
233
+ (vectorMask.notLink ? 2 : 0) |
234
+ (vectorMask.disable ? 4 : 0);
235
+ writeUint32(writer, 3); // version
236
+ writeUint32(writer, flags);
237
+ // initial entry
238
+ writeUint16(writer, 6);
239
+ writeZeros(writer, 24);
240
+ const clipboard = vectorMask.clipboard;
241
+ if (clipboard) {
242
+ writeUint16(writer, 7);
243
+ writeFixedPointPath32(writer, clipboard.top);
244
+ writeFixedPointPath32(writer, clipboard.left);
245
+ writeFixedPointPath32(writer, clipboard.bottom);
246
+ writeFixedPointPath32(writer, clipboard.right);
247
+ writeFixedPointPath32(writer, clipboard.resolution);
248
+ writeZeros(writer, 4);
249
+ }
250
+ writeUint16(writer, 8);
251
+ writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
252
+ writeZeros(writer, 22);
253
+ for (const path of vectorMask.paths) {
254
+ writeUint16(writer, path.open ? 3 : 0);
255
+ writeUint16(writer, path.knots.length);
256
+ writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
257
+ writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
258
+ writeZeros(writer, 18); // TODO: these are sometimes non-zero
259
+ const linkedKnot = path.open ? 4 : 1;
260
+ const unlinkedKnot = path.open ? 5 : 2;
261
+ for (const { linked, points } of path.knots) {
262
+ writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
263
+ writeBezierKnot(writer, points, width, height);
264
+ }
265
+ }
266
+ });
267
+ // TODO: need to write vmsk if has outline ?
268
+ addHandlerAlias('vsms', 'vmsk');
269
+ // addHandlerAlias('vmsk', 'vsms');
270
+ addHandler('vowv', // something with vectors?
271
+ hasKey('vowv'), (reader, target) => {
272
+ target.vowv = readUint32(reader); // always 2 ????
273
+ }, (writer, target) => {
274
+ writeUint32(writer, target.vowv);
275
+ });
276
+ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
277
+ if (readInt32(reader) !== 1)
278
+ throw new Error(`Invalid vogk version`);
279
+ const desc = readVersionAndDescriptor(reader);
280
+ // console.log(require('util').inspect(desc, false, 99, true));
281
+ target.vectorOrigination = { keyDescriptorList: [] };
282
+ for (const i of desc.keyDescriptorList) {
283
+ const item = {};
284
+ if (i.keyShapeInvalidated != null)
285
+ item.keyShapeInvalidated = i.keyShapeInvalidated;
286
+ if (i.keyOriginType != null)
287
+ item.keyOriginType = i.keyOriginType;
288
+ if (i.keyOriginResolution != null)
289
+ item.keyOriginResolution = i.keyOriginResolution;
290
+ if (i.keyOriginShapeBBox) {
291
+ item.keyOriginShapeBoundingBox = {
292
+ top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
293
+ left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
294
+ bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
295
+ right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
296
+ };
297
+ }
298
+ const rectRadii = i.keyOriginRRectRadii;
299
+ if (rectRadii) {
300
+ item.keyOriginRRectRadii = {
301
+ topRight: parseUnits(rectRadii.topRight),
302
+ topLeft: parseUnits(rectRadii.topLeft),
303
+ bottomLeft: parseUnits(rectRadii.bottomLeft),
304
+ bottomRight: parseUnits(rectRadii.bottomRight),
305
+ };
306
+ }
307
+ const corners = i.keyOriginBoxCorners;
308
+ if (corners) {
309
+ item.keyOriginBoxCorners = [
310
+ { x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
311
+ { x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
312
+ { x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
313
+ { x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
314
+ ];
315
+ }
316
+ const trnf = i.Trnf;
317
+ if (trnf) {
318
+ item.transform = [trnf.xx, trnf.xy, trnf.xy, trnf.yy, trnf.tx, trnf.ty];
319
+ }
320
+ target.vectorOrigination.keyDescriptorList.push(item);
321
+ }
322
+ skipBytes(reader, left());
323
+ }, (writer, target) => {
324
+ target;
325
+ const orig = target.vectorOrigination;
326
+ const desc = { keyDescriptorList: [] };
327
+ for (let i = 0; i < orig.keyDescriptorList.length; i++) {
328
+ const item = orig.keyDescriptorList[i];
329
+ desc.keyDescriptorList.push({}); // we're adding keyOriginIndex at the end
330
+ const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
331
+ if (item.keyOriginType != null)
332
+ out.keyOriginType = item.keyOriginType;
333
+ if (item.keyOriginResolution != null)
334
+ out.keyOriginResolution = item.keyOriginResolution;
335
+ const radii = item.keyOriginRRectRadii;
336
+ if (radii) {
337
+ out.keyOriginRRectRadii = {
338
+ unitValueQuadVersion: 1,
339
+ topRight: unitsValue(radii.topRight, 'topRight'),
340
+ topLeft: unitsValue(radii.topLeft, 'topLeft'),
341
+ bottomLeft: unitsValue(radii.bottomLeft, 'bottomLeft'),
342
+ bottomRight: unitsValue(radii.bottomRight, 'bottomRight'),
343
+ };
344
+ }
345
+ const box = item.keyOriginShapeBoundingBox;
346
+ if (box) {
347
+ out.keyOriginShapeBBox = {
348
+ unitValueQuadVersion: 1,
349
+ 'Top ': unitsValue(box.top, 'top'),
350
+ Left: unitsValue(box.left, 'left'),
351
+ Btom: unitsValue(box.bottom, 'bottom'),
352
+ Rght: unitsValue(box.right, 'right'),
353
+ };
354
+ }
355
+ const corners = item.keyOriginBoxCorners;
356
+ if (corners && corners.length === 4) {
357
+ out.keyOriginBoxCorners = {
358
+ rectangleCornerA: { Hrzn: corners[0].x, Vrtc: corners[0].y },
359
+ rectangleCornerB: { Hrzn: corners[1].x, Vrtc: corners[1].y },
360
+ rectangleCornerC: { Hrzn: corners[2].x, Vrtc: corners[2].y },
361
+ rectangleCornerD: { Hrzn: corners[3].x, Vrtc: corners[3].y },
362
+ };
363
+ }
364
+ const transform = item.transform;
365
+ if (transform && transform.length === 6) {
366
+ out.Trnf = {
367
+ xx: transform[0],
368
+ xy: transform[1],
369
+ yx: transform[2],
370
+ yy: transform[3],
371
+ tx: transform[4],
372
+ ty: transform[5],
373
+ };
374
+ }
375
+ if (item.keyShapeInvalidated != null)
376
+ out.keyShapeInvalidated = item.keyShapeInvalidated;
377
+ out.keyOriginIndex = i;
378
+ }
379
+ writeInt32(writer, 1); // version
380
+ writeVersionAndDescriptor(writer, '', 'null', desc);
381
+ });
382
+ addHandler('lmfx', target => target.effects !== undefined && hasMultiEffects(target.effects), (reader, target, left) => {
383
+ const version = readUint32(reader);
384
+ if (version !== 0)
385
+ throw new Error('Invalid lmfx version');
386
+ const desc = readVersionAndDescriptor(reader);
387
+ // console.log('READ', require('util').inspect(desc, false, 99, true));
388
+ // discard if read in 'lrFX' or 'lfx2' section
389
+ target.effects = parseEffects(desc, !!reader.logMissingFeatures);
390
+ skipBytes(reader, left());
391
+ }, (writer, target, _, options) => {
392
+ const desc = serializeEffects(target.effects, !!options.logMissingFeatures, true);
393
+ // console.log('WRITE', require('util').inspect(desc, false, 99, true));
394
+ writeUint32(writer, 0); // version
395
+ writeVersionAndDescriptor(writer, '', 'null', desc);
396
+ });
397
+ addHandler('lrFX', hasKey('effects'), (reader, target, left) => {
398
+ if (!target.effects)
399
+ target.effects = readEffects(reader);
400
+ skipBytes(reader, left());
401
+ }, (writer, target) => {
402
+ writeEffects(writer, target.effects);
403
+ });
404
+ addHandler('luni', hasKey('name'), (reader, target, left) => {
405
+ if (left() > 4) {
406
+ const length = readUint32(reader);
407
+ if (left() >= (length * 2)) {
408
+ target.name = readUnicodeStringWithLength(reader, length);
409
+ }
410
+ else {
411
+ if (reader.logDevFeatures)
412
+ reader.log('name in luni section is too long');
413
+ }
414
+ }
415
+ else {
416
+ if (reader.logDevFeatures)
417
+ reader.log('empty luni section');
418
+ }
419
+ skipBytes(reader, left());
420
+ }, (writer, target) => {
421
+ writeUnicodeString(writer, target.name);
422
+ // writeUint16(writer, 0); // padding (but not extending string length)
423
+ });
424
+ addHandler('lnsr', hasKey('nameSource'), (reader, target) => target.nameSource = readSignature(reader), (writer, target) => writeSignature(writer, target.nameSource));
425
+ addHandler('lyid', hasKey('id'), (reader, target) => {
426
+ target.id = readUint32(reader);
427
+ }, (writer, target, _psd, options) => {
428
+ let id = target.id;
429
+ while (options.layerIds.has(id))
430
+ id += 100; // make sure we don't have duplicate layer ids
431
+ writeUint32(writer, id);
432
+ options.layerIds.add(id);
433
+ options.layerToId.set(target, id);
434
+ });
435
+ addHandler('lsct', hasKey('sectionDivider'), (reader, target, left) => {
436
+ target.sectionDivider = { type: readUint32(reader) };
437
+ if (left()) {
438
+ checkSignature(reader, '8BIM');
439
+ target.sectionDivider.key = readSignature(reader);
440
+ }
441
+ if (left()) {
442
+ target.sectionDivider.subType = readUint32(reader);
443
+ }
444
+ }, (writer, target) => {
445
+ writeUint32(writer, target.sectionDivider.type);
446
+ if (target.sectionDivider.key) {
447
+ writeSignature(writer, '8BIM');
448
+ writeSignature(writer, target.sectionDivider.key);
449
+ if (target.sectionDivider.subType !== undefined) {
450
+ writeUint32(writer, target.sectionDivider.subType);
451
+ }
452
+ }
453
+ });
454
+ // it seems lsdk is used when there's a layer is nested more than 6 levels, but I don't know why?
455
+ // maybe some limitation of old version of PS?
456
+ addHandlerAlias('lsdk', 'lsct');
457
+ addHandler('clbl', hasKey('blendClippendElements'), (reader, target) => {
458
+ target.blendClippendElements = !!readUint8(reader);
459
+ skipBytes(reader, 3);
460
+ }, (writer, target) => {
461
+ writeUint8(writer, target.blendClippendElements ? 1 : 0);
462
+ writeZeros(writer, 3);
463
+ });
464
+ addHandler('infx', hasKey('blendInteriorElements'), (reader, target) => {
465
+ target.blendInteriorElements = !!readUint8(reader);
466
+ skipBytes(reader, 3);
467
+ }, (writer, target) => {
468
+ writeUint8(writer, target.blendInteriorElements ? 1 : 0);
469
+ writeZeros(writer, 3);
470
+ });
471
+ addHandler('knko', hasKey('knockout'), (reader, target) => {
472
+ target.knockout = !!readUint8(reader);
473
+ skipBytes(reader, 3);
474
+ }, (writer, target) => {
475
+ writeUint8(writer, target.knockout ? 1 : 0);
476
+ writeZeros(writer, 3);
477
+ });
478
+ addHandler('lmgm', hasKey('layerMaskAsGlobalMask'), (reader, target) => {
479
+ target.layerMaskAsGlobalMask = !!readUint8(reader);
480
+ skipBytes(reader, 3);
481
+ }, (writer, target) => {
482
+ writeUint8(writer, target.layerMaskAsGlobalMask ? 1 : 0);
483
+ writeZeros(writer, 3);
484
+ });
485
+ addHandler('lspf', hasKey('protected'), (reader, target) => {
486
+ const flags = readUint32(reader);
487
+ target.protected = {
488
+ transparency: (flags & 0x01) !== 0,
489
+ composite: (flags & 0x02) !== 0,
490
+ position: (flags & 0x04) !== 0,
491
+ };
492
+ if (flags & 0x08)
493
+ target.protected.artboards = true;
494
+ }, (writer, target) => {
495
+ const flags = (target.protected.transparency ? 0x01 : 0) |
496
+ (target.protected.composite ? 0x02 : 0) |
497
+ (target.protected.position ? 0x04 : 0) |
498
+ (target.protected.artboards ? 0x08 : 0);
499
+ writeUint32(writer, flags);
500
+ });
501
+ addHandler('lclr', hasKey('layerColor'), (reader, target) => {
502
+ const color = readUint16(reader);
503
+ skipBytes(reader, 6);
504
+ target.layerColor = layerColors[color];
505
+ }, (writer, target) => {
506
+ const index = layerColors.indexOf(target.layerColor);
507
+ writeUint16(writer, index === -1 ? 0 : index);
508
+ writeZeros(writer, 6);
509
+ });
510
+ addHandler('shmd', // Metadata setting
511
+ // Metadata setting
512
+ target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined, (reader, target, left) => {
513
+ const count = readUint32(reader);
514
+ for (let i = 0; i < count; i++) {
515
+ checkSignature(reader, '8BIM');
516
+ const key = readSignature(reader);
517
+ readUint8(reader); // copy
518
+ skipBytes(reader, 3);
519
+ readSection(reader, 1, left => {
520
+ if (key === 'cust') {
521
+ const desc = readVersionAndDescriptor(reader);
522
+ // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
523
+ if (desc.layerTime !== undefined)
524
+ target.timestamp = desc.layerTime;
525
+ }
526
+ else if (key === 'mlst') {
527
+ const desc = readVersionAndDescriptor(reader);
528
+ // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
529
+ target.animationFrames = [];
530
+ for (let i = 0; i < desc.LaSt.length; i++) {
531
+ const f = desc.LaSt[i];
532
+ const frame = { frames: f.FrLs };
533
+ if (f.enab !== undefined)
534
+ frame.enable = f.enab;
535
+ if (f.Ofst)
536
+ frame.offset = horzVrtcToXY(f.Ofst);
537
+ if (f.FXRf)
538
+ frame.referencePoint = horzVrtcToXY(f.FXRf);
539
+ if (f.Lefx)
540
+ frame.effects = parseEffects(f.Lefx, !!reader.logMissingFeatures);
541
+ if (f.blendOptions && f.blendOptions.Opct)
542
+ frame.opacity = parsePercent(f.blendOptions.Opct);
543
+ target.animationFrames.push(frame);
544
+ }
545
+ }
546
+ else if (key === 'mdyn') {
547
+ // frame flags
548
+ readUint16(reader); // unknown
549
+ const propagate = readUint8(reader);
550
+ const flags = readUint8(reader);
551
+ target.animationFrameFlags = {
552
+ propagateFrameOne: !propagate,
553
+ unifyLayerPosition: (flags & 1) !== 0,
554
+ unifyLayerStyle: (flags & 2) !== 0,
555
+ unifyLayerVisibility: (flags & 4) !== 0,
556
+ };
557
+ }
558
+ else if (key === 'tmln') {
559
+ const desc = readVersionAndDescriptor(reader);
560
+ const timeScope = desc.timeScope;
561
+ // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
562
+ const timeline = {
563
+ start: frac(timeScope.Strt),
564
+ duration: frac(timeScope.duration),
565
+ inTime: frac(timeScope.inTime),
566
+ outTime: frac(timeScope.outTime),
567
+ autoScope: desc.autoScope,
568
+ audioLevel: desc.audioLevel,
569
+ };
570
+ if (desc.trackList) {
571
+ timeline.tracks = parseTrackList(desc.trackList, !!reader.logMissingFeatures);
572
+ }
573
+ target.timeline = timeline;
574
+ // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
575
+ }
576
+ else if (key === 'cmls') {
577
+ const desc = readVersionAndDescriptor(reader);
578
+ // console.log('cmls', require('util').inspect(desc, false, 99, true));
579
+ target.comps = {
580
+ settings: [],
581
+ };
582
+ if (desc.origFXRefPoint)
583
+ target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
584
+ for (const item of desc.layerSettings) {
585
+ target.comps.settings.push({ compList: item.compList });
586
+ const t = target.comps.settings[target.comps.settings.length - 1];
587
+ if ('enab' in item)
588
+ t.enabled = item.enab;
589
+ if (item.Ofst)
590
+ t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
591
+ if (item.FXRefPoint)
592
+ t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
593
+ }
594
+ }
595
+ else if (key === 'extn') {
596
+ const desc = readVersionAndDescriptor(reader);
597
+ // console.log(require('util').inspect(desc, false, 99, true));
598
+ desc; // TODO: save this
599
+ reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
600
+ }
601
+ else {
602
+ reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
603
+ }
604
+ skipBytes(reader, left());
605
+ });
606
+ }
607
+ skipBytes(reader, left());
608
+ }, (writer, target, _, options) => {
609
+ const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
610
+ let count = 0;
611
+ if (animationFrames)
612
+ count++;
613
+ if (animationFrameFlags)
614
+ count++;
615
+ if (timeline)
616
+ count++;
617
+ if (timestamp !== undefined)
618
+ count++;
619
+ if (comps)
620
+ count++;
621
+ writeUint32(writer, count);
622
+ if (animationFrames) {
623
+ writeSignature(writer, '8BIM');
624
+ writeSignature(writer, 'mlst');
625
+ writeUint8(writer, 0); // copy (always false)
626
+ writeZeros(writer, 3);
627
+ writeSection(writer, 2, () => {
628
+ const desc = {
629
+ LaID: target.id ?? 0,
630
+ LaSt: [],
631
+ };
632
+ for (let i = 0; i < animationFrames.length; i++) {
633
+ const f = animationFrames[i];
634
+ const frame = {};
635
+ if (f.enable !== undefined)
636
+ frame.enab = f.enable;
637
+ frame.FrLs = f.frames;
638
+ if (f.offset)
639
+ frame.Ofst = xyToHorzVrtc(f.offset);
640
+ if (f.referencePoint)
641
+ frame.FXRf = xyToHorzVrtc(f.referencePoint);
642
+ if (f.effects)
643
+ frame.Lefx = serializeEffects(f.effects, false, false);
644
+ if (f.opacity !== undefined)
645
+ frame.blendOptions = { Opct: unitsPercent(f.opacity) };
646
+ desc.LaSt.push(frame);
647
+ }
648
+ writeVersionAndDescriptor(writer, '', 'null', desc);
649
+ }, true);
650
+ }
651
+ if (animationFrameFlags) {
652
+ writeSignature(writer, '8BIM');
653
+ writeSignature(writer, 'mdyn');
654
+ writeUint8(writer, 0); // copy (always false)
655
+ writeZeros(writer, 3);
656
+ writeSection(writer, 2, () => {
657
+ writeUint16(writer, 0); // unknown
658
+ writeUint8(writer, animationFrameFlags.propagateFrameOne ? 0x0 : 0xf);
659
+ writeUint8(writer, (animationFrameFlags.unifyLayerPosition ? 1 : 0) |
660
+ (animationFrameFlags.unifyLayerStyle ? 2 : 0) |
661
+ (animationFrameFlags.unifyLayerVisibility ? 4 : 0));
662
+ });
663
+ }
664
+ if (timeline) {
665
+ writeSignature(writer, '8BIM');
666
+ writeSignature(writer, 'tmln');
667
+ writeUint8(writer, 0); // copy (always false)
668
+ writeZeros(writer, 3);
669
+ writeSection(writer, 2, () => {
670
+ const desc = {
671
+ Vrsn: 1,
672
+ timeScope: {
673
+ Vrsn: 1,
674
+ Strt: timeline.start,
675
+ duration: timeline.duration,
676
+ inTime: timeline.inTime,
677
+ outTime: timeline.outTime,
678
+ },
679
+ autoScope: timeline.autoScope,
680
+ audioLevel: timeline.audioLevel,
681
+ };
682
+ if (timeline.tracks) {
683
+ desc.trackList = serializeTrackList(timeline.tracks);
684
+ }
685
+ const id = options.layerToId.get(target) || target.id;
686
+ if (!id)
687
+ throw new Error('You need to provide layer.id value whan writing document with animations');
688
+ desc.LyrI = id;
689
+ // console.log('WRITE:tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
690
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'anim');
691
+ }, true);
692
+ }
693
+ if (timestamp !== undefined) {
694
+ writeSignature(writer, '8BIM');
695
+ writeSignature(writer, 'cust');
696
+ writeUint8(writer, 0); // copy (always false)
697
+ writeZeros(writer, 3);
698
+ writeSection(writer, 2, () => {
699
+ const desc = {
700
+ layerTime: timestamp,
701
+ };
702
+ writeVersionAndDescriptor(writer, '', 'metadata', desc);
703
+ }, true);
704
+ }
705
+ if (comps) {
706
+ writeSignature(writer, '8BIM');
707
+ writeSignature(writer, 'cmls');
708
+ writeUint8(writer, 0); // copy (always false)
709
+ writeZeros(writer, 3);
710
+ writeSection(writer, 2, () => {
711
+ const id = options.layerToId.get(target) || target.id;
712
+ if (!id)
713
+ throw new Error('You need to provide layer.id value whan writing document with layer comps');
714
+ const desc = {};
715
+ if (comps.originalEffectsReferencePoint) {
716
+ desc.origFXRefPoint = { Hrzn: comps.originalEffectsReferencePoint.x, Vrtc: comps.originalEffectsReferencePoint.y };
717
+ }
718
+ desc.LyrI = id;
719
+ desc.layerSettings = [];
720
+ for (const item of comps.settings) {
721
+ const t = {};
722
+ if (item.enabled !== undefined)
723
+ t.enab = item.enabled;
724
+ if (item.offset)
725
+ t.Ofst = { Hrzn: item.offset.x, Vrtc: item.offset.y };
726
+ if (item.effectsReferencePoint)
727
+ t.FXRefPoint = { Hrzn: item.effectsReferencePoint.x, Vrtc: item.effectsReferencePoint.y };
728
+ t.compList = item.compList;
729
+ desc.layerSettings.push(t);
730
+ }
731
+ // console.log('cmls', require('util').inspect(desc, false, 99, true));
732
+ writeVersionAndDescriptor(writer, '', 'null', desc);
733
+ }, true);
734
+ }
735
+ });
736
+ addHandler('vstk', hasKey('vectorStroke'), (reader, target, left) => {
737
+ const desc = readVersionAndDescriptor(reader);
738
+ // console.log(require('util').inspect(desc, false, 99, true));
739
+ target.vectorStroke = {
740
+ strokeEnabled: desc.strokeEnabled,
741
+ fillEnabled: desc.fillEnabled,
742
+ lineWidth: parseUnits(desc.strokeStyleLineWidth),
743
+ lineDashOffset: parseUnits(desc.strokeStyleLineDashOffset),
744
+ miterLimit: desc.strokeStyleMiterLimit,
745
+ lineCapType: strokeStyleLineCapType.decode(desc.strokeStyleLineCapType),
746
+ lineJoinType: strokeStyleLineJoinType.decode(desc.strokeStyleLineJoinType),
747
+ lineAlignment: strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
748
+ scaleLock: desc.strokeStyleScaleLock,
749
+ strokeAdjust: desc.strokeStyleStrokeAdjust,
750
+ lineDashSet: desc.strokeStyleLineDashSet.map(parseUnits),
751
+ blendMode: BlnM.decode(desc.strokeStyleBlendMode),
752
+ opacity: parsePercent(desc.strokeStyleOpacity),
753
+ content: parseVectorContent(desc.strokeStyleContent),
754
+ resolution: desc.strokeStyleResolution,
755
+ };
756
+ skipBytes(reader, left());
757
+ }, (writer, target) => {
758
+ const stroke = target.vectorStroke;
759
+ const desc = {
760
+ strokeStyleVersion: 2,
761
+ strokeEnabled: !!stroke.strokeEnabled,
762
+ fillEnabled: !!stroke.fillEnabled,
763
+ strokeStyleLineWidth: stroke.lineWidth || { value: 3, units: 'Points' },
764
+ strokeStyleLineDashOffset: stroke.lineDashOffset || { value: 0, units: 'Points' },
765
+ strokeStyleMiterLimit: stroke.miterLimit ?? 100,
766
+ strokeStyleLineCapType: strokeStyleLineCapType.encode(stroke.lineCapType),
767
+ strokeStyleLineJoinType: strokeStyleLineJoinType.encode(stroke.lineJoinType),
768
+ strokeStyleLineAlignment: strokeStyleLineAlignment.encode(stroke.lineAlignment),
769
+ strokeStyleScaleLock: !!stroke.scaleLock,
770
+ strokeStyleStrokeAdjust: !!stroke.strokeAdjust,
771
+ strokeStyleLineDashSet: stroke.lineDashSet || [],
772
+ strokeStyleBlendMode: BlnM.encode(stroke.blendMode),
773
+ strokeStyleOpacity: unitsPercent(stroke.opacity ?? 1),
774
+ strokeStyleContent: serializeVectorContent(stroke.content || { type: 'color', color: { r: 0, g: 0, b: 0 } }).descriptor,
775
+ strokeStyleResolution: stroke.resolution ?? 72,
776
+ };
777
+ writeVersionAndDescriptor(writer, '', 'strokeStyle', desc);
778
+ });
779
+ addHandler('artb', // per-layer arboard info
780
+ hasKey('artboard'), (reader, target, left) => {
781
+ const desc = readVersionAndDescriptor(reader);
782
+ const rect = desc.artboardRect;
783
+ target.artboard = {
784
+ rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
785
+ guideIndices: desc.guideIndeces,
786
+ presetName: desc.artboardPresetName,
787
+ color: parseColor(desc['Clr ']),
788
+ backgroundType: desc.artboardBackgroundType,
789
+ };
790
+ skipBytes(reader, left());
791
+ }, (writer, target) => {
792
+ const artboard = target.artboard;
793
+ const rect = artboard.rect;
794
+ const desc = {
795
+ artboardRect: { 'Top ': rect.top, Left: rect.left, Btom: rect.bottom, Rght: rect.right },
796
+ guideIndeces: artboard.guideIndices || [],
797
+ artboardPresetName: artboard.presetName || '',
798
+ 'Clr ': serializeColor(artboard.color),
799
+ artboardBackgroundType: artboard.backgroundType ?? 1,
800
+ };
801
+ writeVersionAndDescriptor(writer, '', 'artboard', desc);
802
+ });
803
+ addHandler('sn2P', hasKey('usingAlignedRendering'), (reader, target) => target.usingAlignedRendering = !!readUint32(reader), (writer, target) => writeUint32(writer, target.usingAlignedRendering ? 1 : 0));
804
+ const placedLayerTypes = ['unknown', 'vector', 'raster', 'image stack'];
805
+ function parseWarp(warp) {
806
+ const result = {
807
+ style: warpStyle.decode(warp.warpStyle),
808
+ ...(warp.warpValues ? { values: warp.warpValues } : { value: warp.warpValue || 0 }),
809
+ perspective: warp.warpPerspective || 0,
810
+ perspectiveOther: warp.warpPerspectiveOther || 0,
811
+ rotate: Ornt.decode(warp.warpRotate),
812
+ bounds: warp.bounds && {
813
+ top: parseUnitsOrNumber(warp.bounds['Top ']),
814
+ left: parseUnitsOrNumber(warp.bounds.Left),
815
+ bottom: parseUnitsOrNumber(warp.bounds.Btom),
816
+ right: parseUnitsOrNumber(warp.bounds.Rght),
817
+ },
818
+ uOrder: warp.uOrder,
819
+ vOrder: warp.vOrder,
820
+ };
821
+ if (warp.deformNumRows != null || warp.deformNumCols != null) {
822
+ result.deformNumRows = warp.deformNumRows;
823
+ result.deformNumCols = warp.deformNumCols;
824
+ }
825
+ const envelopeWarp = warp.customEnvelopeWarp;
826
+ if (envelopeWarp) {
827
+ result.customEnvelopeWarp = {
828
+ meshPoints: [],
829
+ };
830
+ const xs = envelopeWarp.meshPoints.find(i => i.type === 'Hrzn')?.values || [];
831
+ const ys = envelopeWarp.meshPoints.find(i => i.type === 'Vrtc')?.values || [];
832
+ for (let i = 0; i < xs.length; i++) {
833
+ result.customEnvelopeWarp.meshPoints.push({ x: xs[i], y: ys[i] });
834
+ }
835
+ if (envelopeWarp.quiltSliceX || envelopeWarp.quiltSliceY) {
836
+ result.customEnvelopeWarp.quiltSliceX = envelopeWarp.quiltSliceX?.[0]?.values || [];
837
+ result.customEnvelopeWarp.quiltSliceY = envelopeWarp.quiltSliceY?.[0]?.values || [];
838
+ }
839
+ }
840
+ return result;
841
+ }
842
+ function isQuiltWarp(warp) {
843
+ return warp.deformNumCols != null || warp.deformNumRows != null ||
844
+ warp.customEnvelopeWarp?.quiltSliceX || warp.customEnvelopeWarp?.quiltSliceY;
845
+ }
846
+ function encodeWarp(warp) {
847
+ const bounds = warp.bounds;
848
+ const desc = {
849
+ warpStyle: warpStyle.encode(warp.style),
850
+ ...(warp.values ? { warpValues: warp.values } : { warpValue: warp.value || 0 }),
851
+ warpPerspective: warp.perspective || 0,
852
+ warpPerspectiveOther: warp.perspectiveOther || 0,
853
+ warpRotate: Ornt.encode(warp.rotate),
854
+ bounds: /*1 ? { // testing
855
+ _classID: 'classFloatRect',
856
+ 'Top ': bounds && bounds.top && bounds.top.value || 0,
857
+ Left: bounds && bounds.left && bounds.left.value || 0,
858
+ Btom: bounds && bounds.bottom && bounds.bottom.value || 0,
859
+ Rght: bounds && bounds.right && bounds.right.value || 0,
860
+ } :*/ {
861
+ 'Top ': unitsValue(bounds && bounds.top || { units: 'Pixels', value: 0 }, 'bounds.top'),
862
+ Left: unitsValue(bounds && bounds.left || { units: 'Pixels', value: 0 }, 'bounds.left'),
863
+ Btom: unitsValue(bounds && bounds.bottom || { units: 'Pixels', value: 0 }, 'bounds.bottom'),
864
+ Rght: unitsValue(bounds && bounds.right || { units: 'Pixels', value: 0 }, 'bounds.right'),
865
+ },
866
+ uOrder: warp.uOrder || 0,
867
+ vOrder: warp.vOrder || 0,
868
+ };
869
+ const isQuilt = isQuiltWarp(warp);
870
+ if (isQuilt) {
871
+ const desc2 = desc;
872
+ desc2.deformNumRows = warp.deformNumRows || 0;
873
+ desc2.deformNumCols = warp.deformNumCols || 0;
874
+ }
875
+ const customEnvelopeWarp = warp.customEnvelopeWarp;
876
+ if (customEnvelopeWarp) {
877
+ const meshPoints = customEnvelopeWarp.meshPoints || [];
878
+ if (isQuilt) {
879
+ const desc2 = desc;
880
+ desc2.customEnvelopeWarp = {
881
+ _name: '',
882
+ _classID: 'customEnvelopeWarp',
883
+ quiltSliceX: [{
884
+ type: 'quiltSliceX',
885
+ values: customEnvelopeWarp.quiltSliceX || [],
886
+ }],
887
+ quiltSliceY: [{
888
+ type: 'quiltSliceY',
889
+ values: customEnvelopeWarp.quiltSliceY || [],
890
+ }],
891
+ meshPoints: [
892
+ { type: 'Hrzn', values: meshPoints.map(p => p.x) },
893
+ { type: 'Vrtc', values: meshPoints.map(p => p.y) },
894
+ ],
895
+ };
896
+ }
897
+ else {
898
+ desc.customEnvelopeWarp = {
899
+ _name: '',
900
+ _classID: 'customEnvelopeWarp',
901
+ meshPoints: [
902
+ { type: 'Hrzn', values: meshPoints.map(p => p.x) },
903
+ { type: 'Vrtc', values: meshPoints.map(p => p.y) },
904
+ ],
905
+ };
906
+ }
907
+ }
908
+ return desc;
909
+ }
910
+ addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
911
+ if (readSignature(reader) !== 'plcL')
912
+ throw new Error(`Invalid PlLd signature`);
913
+ if (readInt32(reader) !== 3)
914
+ throw new Error(`Invalid PlLd version`);
915
+ const id = readPascalString(reader, 1);
916
+ const pageNumber = readInt32(reader);
917
+ const totalPages = readInt32(reader); // TODO: check how this works ?
918
+ readInt32(reader); // anitAliasPolicy 16
919
+ const placedLayerType = readInt32(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
920
+ if (!placedLayerTypes[placedLayerType])
921
+ throw new Error('Invalid PlLd type');
922
+ const transform = [];
923
+ for (let i = 0; i < 8; i++)
924
+ transform.push(readFloat64(reader)); // x, y of 4 corners of the transform
925
+ const warpVersion = readInt32(reader);
926
+ if (warpVersion !== 0)
927
+ throw new Error(`Invalid Warp version ${warpVersion}`);
928
+ const warp = readVersionAndDescriptor(reader);
929
+ target.placedLayer = target.placedLayer || {
930
+ id,
931
+ type: placedLayerTypes[placedLayerType],
932
+ pageNumber,
933
+ totalPages,
934
+ transform,
935
+ warp: parseWarp(warp),
936
+ };
937
+ // console.log('PlLd warp', require('util').inspect(warp, false, 99, true));
938
+ // console.log('PlLd', require('util').inspect(target.placedLayer, false, 99, true));
939
+ skipBytes(reader, left());
940
+ }, (writer, target) => {
941
+ const placed = target.placedLayer;
942
+ writeSignature(writer, 'plcL');
943
+ writeInt32(writer, 3); // version
944
+ if (!placed.id || typeof placed.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(placed.id)) {
945
+ throw new Error('Placed layer ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
946
+ }
947
+ writePascalString(writer, placed.id, 1);
948
+ writeInt32(writer, 1); // pageNumber
949
+ writeInt32(writer, 1); // totalPages
950
+ writeInt32(writer, 16); // anitAliasPolicy
951
+ if (placedLayerTypes.indexOf(placed.type) === -1)
952
+ throw new Error('Invalid placedLayer type');
953
+ writeInt32(writer, placedLayerTypes.indexOf(placed.type));
954
+ for (let i = 0; i < 8; i++)
955
+ writeFloat64(writer, placed.transform[i]);
956
+ writeInt32(writer, 0); // warp version
957
+ const warp = getWarpFromPlacedLayer(placed);
958
+ const isQuilt = isQuiltWarp(warp);
959
+ const type = isQuilt ? 'quiltWarp' : 'warp';
960
+ writeVersionAndDescriptor(writer, '', type, encodeWarp(warp), type);
961
+ });
962
+ function uint8ToFloat32(array) {
963
+ return new Float32Array(array.buffer.slice(array.byteOffset), 0, array.byteLength / 4);
964
+ }
965
+ function uint8ToUint32(array) {
966
+ return new Uint32Array(array.buffer.slice(array.byteOffset), 0, array.byteLength / 4);
967
+ }
968
+ function toUint8(array) {
969
+ return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
970
+ }
971
+ function arrayToPoints(array) {
972
+ const points = [];
973
+ for (let i = 0; i < array.length; i += 2) {
974
+ points.push({ x: array[i], y: array[i + 1] });
975
+ }
976
+ return points;
977
+ }
978
+ function pointsToArray(points) {
979
+ const array = [];
980
+ for (let i = 0; i < points.length; i++) {
981
+ array.push(points[i].x, points[i].y);
982
+ }
983
+ return array;
984
+ }
985
+ function uint8ToPoints(array) {
986
+ return arrayToPoints(uint8ToFloat32(array));
987
+ }
988
+ function hrznVrtcToPoint(desc) {
989
+ return {
990
+ x: parseUnits(desc.Hrzn),
991
+ y: parseUnits(desc.Vrtc),
992
+ };
993
+ }
994
+ function pointToHrznVrtc(point) {
995
+ return {
996
+ _name: '',
997
+ _classID: 'Pnt ',
998
+ Hrzn: unitsValue(point.x, 'x'),
999
+ Vrtc: unitsValue(point.y, 'y'),
1000
+ };
1001
+ }
1002
+ function parseFilterFXItem(f, options) {
1003
+ const base = {
1004
+ name: f['Nm '],
1005
+ opacity: parsePercent(f.blendOptions.Opct),
1006
+ blendMode: BlnM.decode(f.blendOptions['Md ']),
1007
+ enabled: f.enab,
1008
+ hasOptions: f.hasoptions,
1009
+ foregroundColor: parseColor(f.FrgC),
1010
+ backgroundColor: parseColor(f.BckC),
1011
+ };
1012
+ if ('Fltr' in f) {
1013
+ switch (f.Fltr._classID) {
1014
+ case 'boxblur': return {
1015
+ ...base,
1016
+ type: 'box blur',
1017
+ filter: {
1018
+ radius: parseUnits(f.Fltr['Rds ']),
1019
+ },
1020
+ };
1021
+ case 'GsnB': return {
1022
+ ...base,
1023
+ type: 'gaussian blur',
1024
+ filter: {
1025
+ radius: parseUnits(f.Fltr['Rds ']),
1026
+ },
1027
+ };
1028
+ case 'MtnB': return {
1029
+ ...base,
1030
+ type: 'motion blur',
1031
+ filter: {
1032
+ angle: f.Fltr.Angl,
1033
+ distance: parseUnits(f.Fltr.Dstn),
1034
+ },
1035
+ };
1036
+ case 'RdlB': return {
1037
+ ...base,
1038
+ type: 'radial blur',
1039
+ filter: {
1040
+ amount: f.Fltr.Amnt,
1041
+ method: BlrM.decode(f.Fltr.BlrM),
1042
+ quality: BlrQ.decode(f.Fltr.BlrQ),
1043
+ },
1044
+ };
1045
+ case 'shapeBlur': return {
1046
+ ...base,
1047
+ type: 'shape blur',
1048
+ filter: {
1049
+ radius: parseUnits(f.Fltr['Rds ']),
1050
+ customShape: { name: f.Fltr.customShape['Nm '], id: f.Fltr.customShape.Idnt },
1051
+ },
1052
+ };
1053
+ case 'SmrB': return {
1054
+ ...base,
1055
+ type: 'smart blur',
1056
+ filter: {
1057
+ radius: f.Fltr['Rds '],
1058
+ threshold: f.Fltr.Thsh,
1059
+ quality: SmBQ.decode(f.Fltr.SmBQ),
1060
+ mode: SmBM.decode(f.Fltr.SmBM),
1061
+ },
1062
+ };
1063
+ case 'surfaceBlur': return {
1064
+ ...base,
1065
+ type: 'surface blur',
1066
+ filter: {
1067
+ radius: parseUnits(f.Fltr['Rds ']),
1068
+ threshold: f.Fltr.Thsh,
1069
+ },
1070
+ };
1071
+ case 'Dspl': return {
1072
+ ...base,
1073
+ type: 'displace',
1074
+ filter: {
1075
+ horizontalScale: f.Fltr.HrzS,
1076
+ verticalScale: f.Fltr.VrtS,
1077
+ displacementMap: DspM.decode(f.Fltr.DspM),
1078
+ undefinedAreas: UndA.decode(f.Fltr.UndA),
1079
+ displacementFile: {
1080
+ signature: f.Fltr.DspF.sig,
1081
+ path: f.Fltr.DspF.path, // TODO: this is decoded incorrectly ???
1082
+ },
1083
+ },
1084
+ };
1085
+ case 'Pnch': return {
1086
+ ...base,
1087
+ type: 'pinch',
1088
+ filter: {
1089
+ amount: f.Fltr.Amnt,
1090
+ },
1091
+ };
1092
+ case 'Plr ': return {
1093
+ ...base,
1094
+ type: 'polar coordinates',
1095
+ filter: {
1096
+ conversion: Cnvr.decode(f.Fltr.Cnvr),
1097
+ },
1098
+ };
1099
+ case 'Rple': return {
1100
+ ...base,
1101
+ type: 'ripple',
1102
+ filter: {
1103
+ amount: f.Fltr.Amnt,
1104
+ size: RplS.decode(f.Fltr.RplS),
1105
+ },
1106
+ };
1107
+ case 'Shr ': return {
1108
+ ...base,
1109
+ type: 'shear',
1110
+ filter: {
1111
+ shearPoints: f.Fltr.ShrP.map(p => ({ x: p.Hrzn, y: p.Vrtc })),
1112
+ shearStart: f.Fltr.ShrS,
1113
+ shearEnd: f.Fltr.ShrE,
1114
+ undefinedAreas: UndA.decode(f.Fltr.UndA),
1115
+ },
1116
+ };
1117
+ case 'Sphr': return {
1118
+ ...base,
1119
+ type: 'spherize',
1120
+ filter: {
1121
+ amount: f.Fltr.Amnt,
1122
+ mode: SphM.decode(f.Fltr.SphM),
1123
+ },
1124
+ };
1125
+ case 'Twrl': return {
1126
+ ...base,
1127
+ type: 'twirl',
1128
+ filter: {
1129
+ angle: f.Fltr.Angl,
1130
+ },
1131
+ };
1132
+ case 'Wave': return {
1133
+ ...base,
1134
+ type: 'wave',
1135
+ filter: {
1136
+ numberOfGenerators: f.Fltr.NmbG,
1137
+ type: Wvtp.decode(f.Fltr.Wvtp),
1138
+ wavelength: { min: f.Fltr.WLMn, max: f.Fltr.WLMx },
1139
+ amplitude: { min: f.Fltr.AmMn, max: f.Fltr.AmMx },
1140
+ scale: { x: f.Fltr.SclH, y: f.Fltr.SclV },
1141
+ randomSeed: f.Fltr.RndS,
1142
+ undefinedAreas: UndA.decode(f.Fltr.UndA),
1143
+ },
1144
+ };
1145
+ case 'ZgZg': return {
1146
+ ...base,
1147
+ type: 'zigzag',
1148
+ filter: {
1149
+ amount: f.Fltr.Amnt,
1150
+ ridges: f.Fltr.NmbR,
1151
+ style: ZZTy.decode(f.Fltr.ZZTy),
1152
+ },
1153
+ };
1154
+ case 'AdNs': return {
1155
+ ...base,
1156
+ type: 'add noise',
1157
+ filter: {
1158
+ amount: parsePercent(f.Fltr.Nose),
1159
+ distribution: Dstr.decode(f.Fltr.Dstr),
1160
+ monochromatic: f.Fltr.Mnch,
1161
+ randomSeed: f.Fltr.FlRs,
1162
+ },
1163
+ };
1164
+ case 'DstS': return {
1165
+ ...base,
1166
+ type: 'dust and scratches',
1167
+ filter: {
1168
+ radius: f.Fltr['Rds '],
1169
+ threshold: f.Fltr.Thsh,
1170
+ },
1171
+ };
1172
+ case 'Mdn ': return {
1173
+ ...base,
1174
+ type: 'median',
1175
+ filter: {
1176
+ radius: parseUnits(f.Fltr['Rds ']),
1177
+ },
1178
+ };
1179
+ case 'denoise': return {
1180
+ ...base,
1181
+ type: 'reduce noise',
1182
+ filter: {
1183
+ preset: f.Fltr.preset,
1184
+ removeJpegArtifact: f.Fltr.removeJPEGArtifact,
1185
+ reduceColorNoise: parsePercent(f.Fltr.ClNs),
1186
+ sharpenDetails: parsePercent(f.Fltr.Shrp),
1187
+ channelDenoise: f.Fltr.channelDenoise.map(c => ({
1188
+ channels: c.Chnl.map(Chnl.decode),
1189
+ amount: c.Amnt,
1190
+ ...(c.EdgF ? { preserveDetails: c.EdgF } : {}),
1191
+ })),
1192
+ },
1193
+ };
1194
+ case 'ClrH': return {
1195
+ ...base,
1196
+ type: 'color halftone',
1197
+ filter: {
1198
+ radius: f.Fltr['Rds '],
1199
+ angle1: f.Fltr.Ang1,
1200
+ angle2: f.Fltr.Ang2,
1201
+ angle3: f.Fltr.Ang3,
1202
+ angle4: f.Fltr.Ang4,
1203
+ },
1204
+ };
1205
+ case 'Crst': return {
1206
+ ...base,
1207
+ type: 'crystallize',
1208
+ filter: {
1209
+ cellSize: f.Fltr.ClSz,
1210
+ randomSeed: f.Fltr.FlRs,
1211
+ },
1212
+ };
1213
+ case 'Mztn': return {
1214
+ ...base,
1215
+ type: 'mezzotint',
1216
+ filter: {
1217
+ type: MztT.decode(f.Fltr.MztT),
1218
+ randomSeed: f.Fltr.FlRs,
1219
+ },
1220
+ };
1221
+ case 'Msc ': return {
1222
+ ...base,
1223
+ type: 'mosaic',
1224
+ filter: {
1225
+ cellSize: parseUnits(f.Fltr.ClSz),
1226
+ },
1227
+ };
1228
+ case 'Pntl': return {
1229
+ ...base,
1230
+ type: 'pointillize',
1231
+ filter: {
1232
+ cellSize: f.Fltr.ClSz,
1233
+ randomSeed: f.Fltr.FlRs,
1234
+ },
1235
+ };
1236
+ case 'Clds': return {
1237
+ ...base,
1238
+ type: 'clouds',
1239
+ filter: {
1240
+ randomSeed: f.Fltr.FlRs,
1241
+ },
1242
+ };
1243
+ case 'DfrC': return {
1244
+ ...base,
1245
+ type: 'difference clouds',
1246
+ filter: {
1247
+ randomSeed: f.Fltr.FlRs,
1248
+ },
1249
+ };
1250
+ case 'Fbrs': return {
1251
+ ...base,
1252
+ type: 'fibers',
1253
+ filter: {
1254
+ variance: f.Fltr.Vrnc,
1255
+ strength: f.Fltr.Strg,
1256
+ randomSeed: f.Fltr.RndS,
1257
+ },
1258
+ };
1259
+ case 'LnsF': return {
1260
+ ...base,
1261
+ type: 'lens flare',
1262
+ filter: {
1263
+ brightness: f.Fltr.Brgh,
1264
+ position: { x: f.Fltr.FlrC.Hrzn, y: f.Fltr.FlrC.Vrtc },
1265
+ lensType: Lns.decode(f.Fltr['Lns ']),
1266
+ },
1267
+ };
1268
+ case 'smartSharpen': return {
1269
+ ...base,
1270
+ type: 'smart sharpen',
1271
+ filter: {
1272
+ amount: parsePercent(f.Fltr.Amnt),
1273
+ radius: parseUnits(f.Fltr['Rds ']),
1274
+ threshold: f.Fltr.Thsh,
1275
+ angle: f.Fltr.Angl,
1276
+ moreAccurate: f.Fltr.moreAccurate,
1277
+ blur: blurType.decode(f.Fltr.blur),
1278
+ preset: f.Fltr.preset,
1279
+ shadow: {
1280
+ fadeAmount: parsePercent(f.Fltr.sdwM.Amnt),
1281
+ tonalWidth: parsePercent(f.Fltr.sdwM.Wdth),
1282
+ radius: f.Fltr.sdwM['Rds '],
1283
+ },
1284
+ highlight: {
1285
+ fadeAmount: parsePercent(f.Fltr.hglM.Amnt),
1286
+ tonalWidth: parsePercent(f.Fltr.hglM.Wdth),
1287
+ radius: f.Fltr.hglM['Rds '],
1288
+ },
1289
+ },
1290
+ };
1291
+ case 'UnsM': return {
1292
+ ...base,
1293
+ type: 'unsharp mask',
1294
+ filter: {
1295
+ amount: parsePercent(f.Fltr.Amnt),
1296
+ radius: parseUnits(f.Fltr['Rds ']),
1297
+ threshold: f.Fltr.Thsh,
1298
+ },
1299
+ };
1300
+ case 'Dfs ': return {
1301
+ ...base,
1302
+ type: 'diffuse',
1303
+ filter: {
1304
+ mode: DfsM.decode(f.Fltr['Md ']),
1305
+ randomSeed: f.Fltr.FlRs,
1306
+ },
1307
+ };
1308
+ case 'Embs': return {
1309
+ ...base,
1310
+ type: 'emboss',
1311
+ filter: {
1312
+ angle: f.Fltr.Angl,
1313
+ height: f.Fltr.Hght,
1314
+ amount: f.Fltr.Amnt,
1315
+ },
1316
+ };
1317
+ case 'Extr': return {
1318
+ ...base,
1319
+ type: 'extrude',
1320
+ filter: {
1321
+ type: ExtT.decode(f.Fltr.ExtT),
1322
+ size: f.Fltr.ExtS,
1323
+ depth: f.Fltr.ExtD,
1324
+ depthMode: ExtR.decode(f.Fltr.ExtR),
1325
+ randomSeed: f.Fltr.FlRs,
1326
+ solidFrontFaces: f.Fltr.ExtF,
1327
+ maskIncompleteBlocks: f.Fltr.ExtM,
1328
+ },
1329
+ };
1330
+ case 'Tls ': return {
1331
+ ...base,
1332
+ type: 'tiles',
1333
+ filter: {
1334
+ numberOfTiles: f.Fltr.TlNm,
1335
+ maximumOffset: f.Fltr.TlOf,
1336
+ fillEmptyAreaWith: FlCl.decode(f.Fltr.FlCl),
1337
+ randomSeed: f.Fltr.FlRs,
1338
+ },
1339
+ };
1340
+ case 'TrcC': return {
1341
+ ...base,
1342
+ type: 'trace contour',
1343
+ filter: {
1344
+ level: f.Fltr['Lvl '],
1345
+ edge: CntE.decode(f.Fltr['Edg ']),
1346
+ },
1347
+ };
1348
+ case 'Wnd ': return {
1349
+ ...base,
1350
+ type: 'wind',
1351
+ filter: {
1352
+ method: WndM.decode(f.Fltr.WndM),
1353
+ direction: Drct.decode(f.Fltr.Drct),
1354
+ },
1355
+ };
1356
+ case 'Dntr': return {
1357
+ ...base,
1358
+ type: 'de-interlace',
1359
+ filter: {
1360
+ eliminate: IntE.decode(f.Fltr.IntE),
1361
+ newFieldsBy: IntC.decode(f.Fltr.IntC),
1362
+ },
1363
+ };
1364
+ case 'Cstm': return {
1365
+ ...base,
1366
+ type: 'custom',
1367
+ filter: {
1368
+ scale: f.Fltr['Scl '],
1369
+ offset: f.Fltr.Ofst,
1370
+ matrix: f.Fltr.Mtrx,
1371
+ },
1372
+ };
1373
+ case 'HghP': return {
1374
+ ...base,
1375
+ type: 'high pass',
1376
+ filter: {
1377
+ radius: parseUnits(f.Fltr['Rds ']),
1378
+ },
1379
+ };
1380
+ case 'Mxm ': return {
1381
+ ...base,
1382
+ type: 'maximum',
1383
+ filter: {
1384
+ radius: parseUnits(f.Fltr['Rds ']),
1385
+ },
1386
+ };
1387
+ case 'Mnm ': return {
1388
+ ...base,
1389
+ type: 'minimum',
1390
+ filter: {
1391
+ radius: parseUnits(f.Fltr['Rds ']),
1392
+ },
1393
+ };
1394
+ case 'Ofst': return {
1395
+ ...base,
1396
+ type: 'offset',
1397
+ filter: {
1398
+ horizontal: f.Fltr.Hrzn,
1399
+ vertical: f.Fltr.Vrtc,
1400
+ undefinedAreas: FlMd.decode(f.Fltr['Fl ']),
1401
+ },
1402
+ };
1403
+ case 'rigidTransform': return {
1404
+ ...base,
1405
+ type: 'puppet',
1406
+ filter: {
1407
+ rigidType: f.Fltr.rigidType,
1408
+ bounds: [
1409
+ { x: f.Fltr.PuX0, y: f.Fltr.PuY0, },
1410
+ { x: f.Fltr.PuX1, y: f.Fltr.PuY1, },
1411
+ { x: f.Fltr.PuX2, y: f.Fltr.PuY2, },
1412
+ { x: f.Fltr.PuX3, y: f.Fltr.PuY3, },
1413
+ ],
1414
+ puppetShapeList: f.Fltr.puppetShapeList.map(p => ({
1415
+ rigidType: p.rigidType,
1416
+ // TODO: VrsM
1417
+ // TODO: VrsN
1418
+ originalVertexArray: uint8ToPoints(p.originalVertexArray),
1419
+ deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
1420
+ indexArray: Array.from(uint8ToUint32(p.indexArray)),
1421
+ pinOffsets: arrayToPoints(p.pinOffsets),
1422
+ posFinalPins: arrayToPoints(p.posFinalPins),
1423
+ pinVertexIndices: p.pinVertexIndices,
1424
+ selectedPin: p.selectedPin,
1425
+ pinPosition: arrayToPoints(p.PinP),
1426
+ pinRotation: p.PnRt,
1427
+ pinOverlay: p.PnOv,
1428
+ pinDepth: p.PnDp,
1429
+ meshQuality: p.meshQuality,
1430
+ meshExpansion: p.meshExpansion,
1431
+ meshRigidity: p.meshRigidity,
1432
+ imageResolution: p.imageResolution,
1433
+ meshBoundaryPath: {
1434
+ pathComponents: p.meshBoundaryPath.pathComponents.map(c => ({
1435
+ shapeOperation: c.shapeOperation.split('.')[1],
1436
+ paths: c.SbpL.map(t => ({
1437
+ closed: t.Clsp,
1438
+ points: t['Pts '].map(pt => ({
1439
+ anchor: hrznVrtcToPoint(pt.Anch),
1440
+ forward: hrznVrtcToPoint(pt['Fwd ']),
1441
+ backward: hrznVrtcToPoint(pt['Bwd ']),
1442
+ smooth: pt.Smoo,
1443
+ })),
1444
+ })),
1445
+ })),
1446
+ },
1447
+ })),
1448
+ },
1449
+ };
1450
+ case 'PbPl': {
1451
+ const parameters = [];
1452
+ const Flrt = f.Fltr;
1453
+ for (let i = 0; i < fromAtoZ.length; i++) {
1454
+ if (!Flrt[`PN${fromAtoZ[i]}a`])
1455
+ break;
1456
+ for (let j = 0; j < fromAtoZ.length; j++) {
1457
+ if (!Flrt[`PN${fromAtoZ[i]}${fromAtoZ[j]}`])
1458
+ break;
1459
+ parameters.push({
1460
+ name: Flrt[`PN${fromAtoZ[i]}${fromAtoZ[j]}`],
1461
+ value: Flrt[`PF${fromAtoZ[i]}${fromAtoZ[j]}`]
1462
+ });
1463
+ }
1464
+ }
1465
+ return {
1466
+ ...base,
1467
+ type: 'oil paint plugin',
1468
+ filter: {
1469
+ name: f.Fltr.KnNm,
1470
+ gpu: f.Fltr.GpuY,
1471
+ lighting: f.Fltr.LIWy,
1472
+ parameters,
1473
+ },
1474
+ };
1475
+ }
1476
+ // case 2089: return {
1477
+ // ...base,
1478
+ // type: 'adaptive wide angle',
1479
+ // params: {
1480
+ // correction: prjM.decode(f.Fltr.prjM),
1481
+ // focalLength: f.Fltr.focL,
1482
+ // cropFactor: f.Fltr.CrpF,
1483
+ // imageScale: f.Fltr.imgS,
1484
+ // imageX: f.Fltr.imgX,
1485
+ // imageY: f.Fltr.imgY,
1486
+ // },
1487
+ // };
1488
+ case 'HsbP': return {
1489
+ ...base,
1490
+ type: 'hsb/hsl',
1491
+ filter: {
1492
+ inputMode: ClrS.decode(f.Fltr.Inpt),
1493
+ rowOrder: ClrS.decode(f.Fltr.Otpt),
1494
+ },
1495
+ };
1496
+ case 'oilPaint': return {
1497
+ ...base,
1498
+ type: 'oil paint',
1499
+ filter: {
1500
+ lightingOn: f.Fltr.lightingOn,
1501
+ stylization: f.Fltr.stylization,
1502
+ cleanliness: f.Fltr.cleanliness,
1503
+ brushScale: f.Fltr.brushScale,
1504
+ microBrush: f.Fltr.microBrush,
1505
+ lightDirection: f.Fltr.LghD,
1506
+ specularity: f.Fltr.specularity,
1507
+ },
1508
+ };
1509
+ case 'LqFy':
1510
+ {
1511
+ return {
1512
+ ...base,
1513
+ type: 'liquify',
1514
+ filter: {
1515
+ liquifyMesh: f.Fltr.LqMe,
1516
+ },
1517
+ };
1518
+ }
1519
+ ;
1520
+ case 'perspectiveWarpTransform':
1521
+ {
1522
+ return {
1523
+ ...base,
1524
+ type: 'perspective warp',
1525
+ filter: {
1526
+ vertices: f.Fltr.vertices.map(hrznVrtcToPoint),
1527
+ warpedVertices: f.Fltr.warpedVertices.map(hrznVrtcToPoint),
1528
+ quads: f.Fltr.quads.map(q => q.indices),
1529
+ },
1530
+ };
1531
+ }
1532
+ ;
1533
+ case 'Crvs':
1534
+ {
1535
+ return {
1536
+ ...base,
1537
+ type: 'curves',
1538
+ filter: {
1539
+ presetKind: presetKindType.decode(f.Fltr.presetKind),
1540
+ ...(f.Fltr.Adjs ? {
1541
+ adjustments: f.Fltr.Adjs.map(a => {
1542
+ const channels = a.Chnl.map(Chnl.decode);
1543
+ if (a['Crv ']) {
1544
+ return {
1545
+ channels,
1546
+ curve: a['Crv '].map(c => {
1547
+ const point = { x: c.Hrzn, y: c.Vrtc };
1548
+ if (c.Cnty)
1549
+ point.curved = true;
1550
+ return point;
1551
+ }),
1552
+ };
1553
+ }
1554
+ else if (a.Mpng) {
1555
+ return { channels, values: a.Mpng };
1556
+ }
1557
+ else {
1558
+ throw new Error(`Unknown curve adjustment`);
1559
+ }
1560
+ })
1561
+ } : {}),
1562
+ },
1563
+ };
1564
+ }
1565
+ ;
1566
+ case 'BrgC':
1567
+ {
1568
+ return {
1569
+ ...base,
1570
+ type: 'brightness/contrast',
1571
+ filter: {
1572
+ brightness: f.Fltr.Brgh,
1573
+ contrast: f.Fltr.Cntr,
1574
+ useLegacy: !!f.Fltr.useLegacy,
1575
+ },
1576
+ };
1577
+ }
1578
+ ;
1579
+ default:
1580
+ if (options.throwForMissingFeatures) {
1581
+ // console.log('FILTER', require('util').inspect(f, false, 99, true));
1582
+ throw new Error(`Unknown filter classId: ${f.Fltr._classID}`);
1583
+ }
1584
+ return undefined;
1585
+ }
1586
+ }
1587
+ else {
1588
+ switch (f.filterID) {
1589
+ case 1098281575: return { ...base, type: 'average' };
1590
+ case 1114403360: return { ...base, type: 'blur' };
1591
+ case 1114403405: return { ...base, type: 'blur more' };
1592
+ case 1148416099: return { ...base, type: 'despeckle' };
1593
+ case 1180922912: return { ...base, type: 'facet' };
1594
+ case 1181902701: return { ...base, type: 'fragment' };
1595
+ case 1399353968: return { ...base, type: 'sharpen' };
1596
+ case 1399353925: return { ...base, type: 'sharpen edges' };
1597
+ case 1399353933: return { ...base, type: 'sharpen more' };
1598
+ case 1181639749: return { ...base, type: 'find edges' };
1599
+ case 1399616122: return { ...base, type: 'solarize' };
1600
+ case 1314149187: return { ...base, type: 'ntsc colors' };
1601
+ case 1231976050: return { ...base, type: 'invert' };
1602
+ default:
1603
+ if (options.throwForMissingFeatures) {
1604
+ // console.log('FILTER', require('util').inspect(f, false, 99, true));
1605
+ throw new Error(`Unknown filterID: ${f.filterID}`);
1606
+ }
1607
+ }
1608
+ }
1609
+ }
1610
+ function parseFilterFX(desc, options) {
1611
+ return {
1612
+ enabled: desc.enab,
1613
+ validAtPosition: desc.validAtPosition,
1614
+ maskEnabled: desc.filterMaskEnable,
1615
+ maskLinked: desc.filterMaskLinked,
1616
+ maskExtendWithWhite: desc.filterMaskExtendWithWhite,
1617
+ list: desc.filterFXList.map(x => parseFilterFXItem(x, options)).filter((x) => !!x),
1618
+ };
1619
+ }
1620
+ function uvRadius(t) {
1621
+ return unitsValue(t.radius, 'radius');
1622
+ }
1623
+ function serializeFilterFXItem(f) {
1624
+ const base = {
1625
+ _name: '',
1626
+ _classID: 'filterFX',
1627
+ 'Nm ': f.name,
1628
+ blendOptions: {
1629
+ _name: '',
1630
+ _classID: 'blendOptions',
1631
+ Opct: unitsPercentF(f.opacity),
1632
+ 'Md ': BlnM.encode(f.blendMode),
1633
+ },
1634
+ enab: f.enabled,
1635
+ hasoptions: f.hasOptions,
1636
+ FrgC: serializeColor(f.foregroundColor),
1637
+ BckC: serializeColor(f.backgroundColor),
1638
+ };
1639
+ switch (f.type) {
1640
+ case 'average': return { ...base, filterID: 1098281575 };
1641
+ case 'blur': return { ...base, filterID: 1114403360 };
1642
+ case 'blur more': return { ...base, filterID: 1114403405 };
1643
+ case 'box blur': return {
1644
+ ...base,
1645
+ Fltr: {
1646
+ _name: 'Box Blur',
1647
+ _classID: 'boxblur',
1648
+ 'Rds ': uvRadius(f.filter),
1649
+ },
1650
+ filterID: 697,
1651
+ };
1652
+ case 'gaussian blur': return {
1653
+ ...base,
1654
+ Fltr: {
1655
+ // _name: '高斯模糊', // Testing
1656
+ _name: 'Gaussian Blur',
1657
+ _classID: 'GsnB',
1658
+ 'Rds ': uvRadius(f.filter),
1659
+ },
1660
+ filterID: 1198747202,
1661
+ };
1662
+ case 'motion blur': return {
1663
+ ...base,
1664
+ Fltr: {
1665
+ _name: 'Motion Blur',
1666
+ _classID: 'MtnB',
1667
+ Angl: f.filter.angle,
1668
+ Dstn: unitsValue(f.filter.distance, 'distance'),
1669
+ },
1670
+ filterID: 1299476034,
1671
+ };
1672
+ case 'radial blur': return {
1673
+ ...base,
1674
+ Fltr: {
1675
+ _name: 'Radial Blur',
1676
+ _classID: 'RdlB',
1677
+ Amnt: f.filter.amount,
1678
+ BlrM: BlrM.encode(f.filter.method),
1679
+ BlrQ: BlrQ.encode(f.filter.quality),
1680
+ },
1681
+ filterID: 1382313026,
1682
+ };
1683
+ case 'shape blur': return {
1684
+ ...base,
1685
+ Fltr: {
1686
+ _name: 'Shape Blur',
1687
+ _classID: 'shapeBlur',
1688
+ 'Rds ': uvRadius(f.filter),
1689
+ customShape: {
1690
+ _name: '',
1691
+ _classID: 'customShape',
1692
+ 'Nm ': f.filter.customShape.name,
1693
+ Idnt: f.filter.customShape.id,
1694
+ }
1695
+ },
1696
+ filterID: 702,
1697
+ };
1698
+ case 'smart blur': return {
1699
+ ...base,
1700
+ Fltr: {
1701
+ _name: 'Smart Blur',
1702
+ _classID: 'SmrB',
1703
+ 'Rds ': f.filter.radius,
1704
+ Thsh: f.filter.threshold,
1705
+ SmBQ: SmBQ.encode(f.filter.quality),
1706
+ SmBM: SmBM.encode(f.filter.mode),
1707
+ },
1708
+ filterID: 1399681602,
1709
+ };
1710
+ case 'surface blur': return {
1711
+ ...base,
1712
+ Fltr: {
1713
+ _name: 'Surface Blur',
1714
+ _classID: 'surfaceBlur',
1715
+ 'Rds ': uvRadius(f.filter),
1716
+ Thsh: f.filter.threshold,
1717
+ },
1718
+ filterID: 701,
1719
+ };
1720
+ case 'displace': return {
1721
+ ...base,
1722
+ Fltr: {
1723
+ _name: 'Displace',
1724
+ _classID: 'Dspl',
1725
+ HrzS: f.filter.horizontalScale,
1726
+ VrtS: f.filter.verticalScale,
1727
+ DspM: DspM.encode(f.filter.displacementMap),
1728
+ UndA: UndA.encode(f.filter.undefinedAreas),
1729
+ DspF: {
1730
+ sig: f.filter.displacementFile.signature,
1731
+ path: f.filter.displacementFile.path,
1732
+ },
1733
+ },
1734
+ filterID: 1148416108,
1735
+ };
1736
+ case 'pinch': return {
1737
+ ...base,
1738
+ Fltr: {
1739
+ _name: 'Pinch',
1740
+ _classID: 'Pnch',
1741
+ Amnt: f.filter.amount,
1742
+ },
1743
+ filterID: 1349411688,
1744
+ };
1745
+ case 'polar coordinates': return {
1746
+ ...base,
1747
+ Fltr: {
1748
+ _name: 'Polar Coordinates',
1749
+ _classID: 'Plr ',
1750
+ Cnvr: Cnvr.encode(f.filter.conversion),
1751
+ },
1752
+ filterID: 1349284384,
1753
+ };
1754
+ case 'ripple': return {
1755
+ ...base,
1756
+ Fltr: {
1757
+ _name: 'Ripple',
1758
+ _classID: 'Rple',
1759
+ Amnt: f.filter.amount,
1760
+ RplS: RplS.encode(f.filter.size),
1761
+ },
1762
+ filterID: 1383099493,
1763
+ };
1764
+ case 'shear': return {
1765
+ ...base,
1766
+ Fltr: {
1767
+ _name: 'Shear',
1768
+ _classID: 'Shr ',
1769
+ ShrP: f.filter.shearPoints.map(p => ({ _name: '', _classID: 'Pnt ', Hrzn: p.x, Vrtc: p.y })),
1770
+ UndA: UndA.encode(f.filter.undefinedAreas),
1771
+ ShrS: f.filter.shearStart,
1772
+ ShrE: f.filter.shearEnd,
1773
+ },
1774
+ filterID: 1399353888,
1775
+ };
1776
+ case 'spherize': return {
1777
+ ...base,
1778
+ Fltr: {
1779
+ _name: 'Spherize',
1780
+ _classID: 'Sphr',
1781
+ Amnt: f.filter.amount,
1782
+ SphM: SphM.encode(f.filter.mode),
1783
+ },
1784
+ filterID: 1399875698,
1785
+ };
1786
+ case 'twirl': return {
1787
+ ...base,
1788
+ Fltr: {
1789
+ _name: 'Twirl',
1790
+ _classID: 'Twrl',
1791
+ Angl: f.filter.angle,
1792
+ },
1793
+ filterID: 1417114220,
1794
+ };
1795
+ case 'wave': return {
1796
+ ...base,
1797
+ Fltr: {
1798
+ _name: 'Wave',
1799
+ _classID: 'Wave',
1800
+ Wvtp: Wvtp.encode(f.filter.type),
1801
+ NmbG: f.filter.numberOfGenerators,
1802
+ WLMn: f.filter.wavelength.min,
1803
+ WLMx: f.filter.wavelength.max,
1804
+ AmMn: f.filter.amplitude.min,
1805
+ AmMx: f.filter.amplitude.max,
1806
+ SclH: f.filter.scale.x,
1807
+ SclV: f.filter.scale.y,
1808
+ UndA: UndA.encode(f.filter.undefinedAreas),
1809
+ RndS: f.filter.randomSeed,
1810
+ },
1811
+ filterID: 1466005093,
1812
+ };
1813
+ case 'zigzag': return {
1814
+ ...base,
1815
+ Fltr: {
1816
+ _name: 'ZigZag',
1817
+ _classID: 'ZgZg',
1818
+ Amnt: f.filter.amount,
1819
+ NmbR: f.filter.ridges,
1820
+ ZZTy: ZZTy.encode(f.filter.style),
1821
+ },
1822
+ filterID: 1516722791,
1823
+ };
1824
+ case 'add noise': return {
1825
+ ...base,
1826
+ Fltr: {
1827
+ _name: 'Add Noise',
1828
+ _classID: 'AdNs',
1829
+ Dstr: Dstr.encode(f.filter.distribution),
1830
+ Nose: unitsPercentF(f.filter.amount),
1831
+ Mnch: f.filter.monochromatic,
1832
+ FlRs: f.filter.randomSeed,
1833
+ },
1834
+ filterID: 1097092723,
1835
+ };
1836
+ case 'despeckle': return { ...base, filterID: 1148416099 };
1837
+ case 'dust and scratches': return {
1838
+ ...base,
1839
+ Fltr: {
1840
+ _name: 'Dust & Scratches',
1841
+ _classID: 'DstS',
1842
+ 'Rds ': f.filter.radius,
1843
+ Thsh: f.filter.threshold,
1844
+ },
1845
+ filterID: 1148417107,
1846
+ };
1847
+ case 'median': return {
1848
+ ...base,
1849
+ Fltr: {
1850
+ _name: 'Median',
1851
+ _classID: 'Mdn ',
1852
+ 'Rds ': uvRadius(f.filter),
1853
+ },
1854
+ filterID: 1298427424,
1855
+ };
1856
+ case 'reduce noise': return {
1857
+ ...base,
1858
+ Fltr: {
1859
+ _name: 'Reduce Noise',
1860
+ _classID: 'denoise',
1861
+ ClNs: unitsPercentF(f.filter.reduceColorNoise),
1862
+ Shrp: unitsPercentF(f.filter.sharpenDetails),
1863
+ removeJPEGArtifact: f.filter.removeJpegArtifact,
1864
+ channelDenoise: f.filter.channelDenoise.map(c => ({
1865
+ _name: '',
1866
+ _classID: 'channelDenoiseParams',
1867
+ Chnl: c.channels.map(i => Chnl.encode(i)),
1868
+ Amnt: c.amount,
1869
+ ...(c.preserveDetails ? { EdgF: c.preserveDetails } : {}),
1870
+ })),
1871
+ preset: f.filter.preset,
1872
+ },
1873
+ filterID: 633,
1874
+ };
1875
+ case 'color halftone': return {
1876
+ ...base,
1877
+ Fltr: {
1878
+ _name: 'Color Halftone',
1879
+ _classID: 'ClrH',
1880
+ 'Rds ': f.filter.radius,
1881
+ Ang1: f.filter.angle1,
1882
+ Ang2: f.filter.angle2,
1883
+ Ang3: f.filter.angle3,
1884
+ Ang4: f.filter.angle4,
1885
+ },
1886
+ filterID: 1131180616,
1887
+ };
1888
+ case 'crystallize': return {
1889
+ ...base,
1890
+ Fltr: {
1891
+ _name: 'Crystallize',
1892
+ _classID: 'Crst',
1893
+ ClSz: f.filter.cellSize,
1894
+ FlRs: f.filter.randomSeed,
1895
+ },
1896
+ filterID: 1131574132,
1897
+ };
1898
+ case 'facet': return { ...base, filterID: 1180922912 };
1899
+ case 'fragment': return { ...base, filterID: 1181902701 };
1900
+ case 'mezzotint': return {
1901
+ ...base,
1902
+ Fltr: {
1903
+ _name: 'Mezzotint',
1904
+ _classID: 'Mztn',
1905
+ MztT: MztT.encode(f.filter.type),
1906
+ FlRs: f.filter.randomSeed,
1907
+ },
1908
+ filterID: 1299870830,
1909
+ };
1910
+ case 'mosaic': return {
1911
+ ...base,
1912
+ Fltr: {
1913
+ _name: 'Mosaic',
1914
+ _classID: 'Msc ',
1915
+ ClSz: unitsValue(f.filter.cellSize, 'cellSize'),
1916
+ },
1917
+ filterID: 1299407648,
1918
+ };
1919
+ case 'pointillize': return {
1920
+ ...base,
1921
+ Fltr: {
1922
+ _name: 'Pointillize',
1923
+ _classID: 'Pntl',
1924
+ ClSz: f.filter.cellSize,
1925
+ FlRs: f.filter.randomSeed,
1926
+ },
1927
+ filterID: 1349416044,
1928
+ };
1929
+ case 'clouds': return {
1930
+ ...base,
1931
+ Fltr: {
1932
+ _name: 'Clouds',
1933
+ _classID: 'Clds',
1934
+ FlRs: f.filter.randomSeed,
1935
+ },
1936
+ filterID: 1131177075,
1937
+ };
1938
+ case 'difference clouds': return {
1939
+ ...base,
1940
+ Fltr: {
1941
+ _name: 'Difference Clouds',
1942
+ _classID: 'DfrC',
1943
+ FlRs: f.filter.randomSeed,
1944
+ },
1945
+ filterID: 1147564611,
1946
+ };
1947
+ case 'fibers': return {
1948
+ ...base,
1949
+ Fltr: {
1950
+ _name: 'Fibers',
1951
+ _classID: 'Fbrs',
1952
+ Vrnc: f.filter.variance,
1953
+ Strg: f.filter.strength,
1954
+ RndS: f.filter.randomSeed,
1955
+ },
1956
+ filterID: 1180856947,
1957
+ };
1958
+ case 'lens flare': return {
1959
+ ...base,
1960
+ Fltr: {
1961
+ _name: 'Lens Flare',
1962
+ _classID: 'LnsF',
1963
+ Brgh: f.filter.brightness,
1964
+ FlrC: {
1965
+ _name: '',
1966
+ _classID: 'Pnt ',
1967
+ Hrzn: f.filter.position.x,
1968
+ Vrtc: f.filter.position.y,
1969
+ },
1970
+ 'Lns ': Lns.encode(f.filter.lensType),
1971
+ },
1972
+ filterID: 1282306886,
1973
+ };
1974
+ case 'sharpen': return { ...base, filterID: 1399353968 };
1975
+ case 'sharpen edges': return { ...base, filterID: 1399353925 };
1976
+ case 'sharpen more': return { ...base, filterID: 1399353933 };
1977
+ case 'smart sharpen': return {
1978
+ ...base,
1979
+ Fltr: {
1980
+ _name: 'Smart Sharpen',
1981
+ _classID: 'smartSharpen',
1982
+ Amnt: unitsPercentF(f.filter.amount),
1983
+ 'Rds ': uvRadius(f.filter),
1984
+ Thsh: f.filter.threshold,
1985
+ Angl: f.filter.angle,
1986
+ moreAccurate: f.filter.moreAccurate,
1987
+ blur: blurType.encode(f.filter.blur),
1988
+ preset: f.filter.preset,
1989
+ sdwM: {
1990
+ _name: 'Parameters',
1991
+ _classID: 'adaptCorrectTones',
1992
+ Amnt: unitsPercentF(f.filter.shadow.fadeAmount),
1993
+ Wdth: unitsPercentF(f.filter.shadow.tonalWidth),
1994
+ 'Rds ': f.filter.shadow.radius,
1995
+ },
1996
+ hglM: {
1997
+ _name: 'Parameters',
1998
+ _classID: 'adaptCorrectTones',
1999
+ Amnt: unitsPercentF(f.filter.highlight.fadeAmount),
2000
+ Wdth: unitsPercentF(f.filter.highlight.tonalWidth),
2001
+ 'Rds ': f.filter.highlight.radius,
2002
+ },
2003
+ },
2004
+ filterID: 698,
2005
+ };
2006
+ case 'unsharp mask': return {
2007
+ ...base,
2008
+ Fltr: {
2009
+ _name: 'Unsharp Mask',
2010
+ _classID: 'UnsM',
2011
+ Amnt: unitsPercentF(f.filter.amount),
2012
+ 'Rds ': uvRadius(f.filter),
2013
+ Thsh: f.filter.threshold,
2014
+ },
2015
+ filterID: 1433301837,
2016
+ };
2017
+ case 'diffuse': return {
2018
+ ...base,
2019
+ Fltr: {
2020
+ _name: 'Diffuse',
2021
+ _classID: 'Dfs ',
2022
+ 'Md ': DfsM.encode(f.filter.mode),
2023
+ FlRs: f.filter.randomSeed,
2024
+ },
2025
+ filterID: 1147564832,
2026
+ };
2027
+ case 'emboss': return {
2028
+ ...base,
2029
+ Fltr: {
2030
+ _name: 'Emboss',
2031
+ _classID: 'Embs',
2032
+ Angl: f.filter.angle,
2033
+ Hght: f.filter.height,
2034
+ Amnt: f.filter.amount,
2035
+ },
2036
+ filterID: 1164796531,
2037
+ };
2038
+ case 'extrude': return {
2039
+ ...base,
2040
+ Fltr: {
2041
+ _name: 'Extrude',
2042
+ _classID: 'Extr',
2043
+ ExtS: f.filter.size,
2044
+ ExtD: f.filter.depth,
2045
+ ExtF: f.filter.solidFrontFaces,
2046
+ ExtM: f.filter.maskIncompleteBlocks,
2047
+ ExtT: ExtT.encode(f.filter.type),
2048
+ ExtR: ExtR.encode(f.filter.depthMode),
2049
+ FlRs: f.filter.randomSeed,
2050
+ },
2051
+ filterID: 1165522034,
2052
+ };
2053
+ case 'find edges': return { ...base, filterID: 1181639749 };
2054
+ case 'solarize': return { ...base, filterID: 1399616122 };
2055
+ case 'tiles': return {
2056
+ ...base,
2057
+ Fltr: {
2058
+ _name: 'Tiles',
2059
+ _classID: 'Tls ',
2060
+ TlNm: f.filter.numberOfTiles,
2061
+ TlOf: f.filter.maximumOffset,
2062
+ FlCl: FlCl.encode(f.filter.fillEmptyAreaWith),
2063
+ FlRs: f.filter.randomSeed,
2064
+ },
2065
+ filterID: 1416393504,
2066
+ };
2067
+ case 'trace contour': return {
2068
+ ...base,
2069
+ Fltr: {
2070
+ _name: 'Trace Contour',
2071
+ _classID: 'TrcC',
2072
+ 'Lvl ': f.filter.level,
2073
+ 'Edg ': CntE.encode(f.filter.edge),
2074
+ },
2075
+ filterID: 1416782659,
2076
+ };
2077
+ case 'wind': return {
2078
+ ...base,
2079
+ Fltr: {
2080
+ _name: 'Wind',
2081
+ _classID: 'Wnd ',
2082
+ WndM: WndM.encode(f.filter.method),
2083
+ Drct: Drct.encode(f.filter.direction),
2084
+ },
2085
+ filterID: 1466852384,
2086
+ };
2087
+ case 'de-interlace': return {
2088
+ ...base,
2089
+ Fltr: {
2090
+ _name: 'De-Interlace',
2091
+ _classID: 'Dntr',
2092
+ IntE: IntE.encode(f.filter.eliminate),
2093
+ IntC: IntC.encode(f.filter.newFieldsBy),
2094
+ },
2095
+ filterID: 1148089458,
2096
+ };
2097
+ case 'ntsc colors': return { ...base, filterID: 1314149187 };
2098
+ case 'invert': return { ...base, filterID: 1231976050 };
2099
+ case 'custom': return {
2100
+ ...base,
2101
+ Fltr: {
2102
+ _name: 'Custom',
2103
+ _classID: 'Cstm',
2104
+ 'Scl ': f.filter.scale,
2105
+ Ofst: f.filter.offset,
2106
+ Mtrx: f.filter.matrix,
2107
+ },
2108
+ filterID: 1131639917,
2109
+ };
2110
+ case 'high pass': return {
2111
+ ...base,
2112
+ Fltr: {
2113
+ _name: 'High Pass',
2114
+ _classID: 'HghP',
2115
+ 'Rds ': uvRadius(f.filter),
2116
+ },
2117
+ filterID: 1214736464,
2118
+ };
2119
+ case 'maximum': return {
2120
+ ...base,
2121
+ Fltr: {
2122
+ _name: 'Maximum',
2123
+ _classID: 'Mxm ',
2124
+ 'Rds ': uvRadius(f.filter),
2125
+ },
2126
+ filterID: 1299737888,
2127
+ };
2128
+ case 'minimum': return {
2129
+ ...base,
2130
+ Fltr: {
2131
+ _name: 'Minimum',
2132
+ _classID: 'Mnm ',
2133
+ 'Rds ': uvRadius(f.filter),
2134
+ },
2135
+ filterID: 1299082528,
2136
+ };
2137
+ case 'offset': return {
2138
+ ...base,
2139
+ Fltr: {
2140
+ _name: 'Offset',
2141
+ _classID: 'Ofst',
2142
+ Hrzn: f.filter.horizontal,
2143
+ Vrtc: f.filter.vertical,
2144
+ 'Fl ': FlMd.encode(f.filter.undefinedAreas),
2145
+ },
2146
+ filterID: 1332114292,
2147
+ };
2148
+ case 'puppet': return {
2149
+ ...base,
2150
+ Fltr: {
2151
+ _name: 'Rigid Transform',
2152
+ _classID: 'rigidTransform',
2153
+ 'null': ['Ordn.Trgt'], // TODO: ???
2154
+ rigidType: f.filter.rigidType,
2155
+ puppetShapeList: f.filter.puppetShapeList.map(p => ({
2156
+ _name: '',
2157
+ _classID: 'puppetShape',
2158
+ rigidType: p.rigidType,
2159
+ VrsM: 1, // TODO: ???
2160
+ VrsN: 0, // TODO: ???
2161
+ originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
2162
+ deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
2163
+ indexArray: toUint8(new Uint32Array(p.indexArray)),
2164
+ pinOffsets: pointsToArray(p.pinOffsets),
2165
+ posFinalPins: pointsToArray(p.posFinalPins),
2166
+ pinVertexIndices: p.pinVertexIndices,
2167
+ PinP: pointsToArray(p.pinPosition),
2168
+ PnRt: p.pinRotation,
2169
+ PnOv: p.pinOverlay,
2170
+ PnDp: p.pinDepth,
2171
+ meshQuality: p.meshQuality,
2172
+ meshExpansion: p.meshExpansion,
2173
+ meshRigidity: p.meshRigidity,
2174
+ imageResolution: p.imageResolution,
2175
+ meshBoundaryPath: {
2176
+ _name: '',
2177
+ _classID: 'pathClass',
2178
+ pathComponents: p.meshBoundaryPath.pathComponents.map(c => ({
2179
+ _name: '',
2180
+ _classID: 'PaCm',
2181
+ shapeOperation: `shapeOperation.${c.shapeOperation}`,
2182
+ SbpL: c.paths.map(path => ({
2183
+ _name: '',
2184
+ _classID: 'Sbpl',
2185
+ Clsp: path.closed,
2186
+ 'Pts ': path.points.map(pt => ({
2187
+ _name: '',
2188
+ _classID: 'Pthp',
2189
+ Anch: pointToHrznVrtc(pt.anchor),
2190
+ 'Fwd ': pointToHrznVrtc(pt.forward),
2191
+ 'Bwd ': pointToHrznVrtc(pt.backward),
2192
+ Smoo: pt.smooth,
2193
+ })),
2194
+ })),
2195
+ })),
2196
+ },
2197
+ selectedPin: p.selectedPin,
2198
+ })),
2199
+ PuX0: f.filter.bounds[0].x,
2200
+ PuX1: f.filter.bounds[1].x,
2201
+ PuX2: f.filter.bounds[2].x,
2202
+ PuX3: f.filter.bounds[3].x,
2203
+ PuY0: f.filter.bounds[0].y,
2204
+ PuY1: f.filter.bounds[1].y,
2205
+ PuY2: f.filter.bounds[2].y,
2206
+ PuY3: f.filter.bounds[3].y,
2207
+ },
2208
+ filterID: 991,
2209
+ };
2210
+ case 'oil paint plugin': {
2211
+ const params = {};
2212
+ for (let i = 0; i < f.filter.parameters.length; i++) {
2213
+ const { name, value } = f.filter.parameters[i];
2214
+ const suffix = `${fromAtoZ[Math.floor(i / fromAtoZ.length)]}${fromAtoZ[i % fromAtoZ.length]}`;
2215
+ params[`PN${suffix}`] = name;
2216
+ params[`PT${suffix}`] = 0;
2217
+ params[`PF${suffix}`] = value;
2218
+ }
2219
+ return {
2220
+ ...base,
2221
+ Fltr: {
2222
+ _name: 'Oil Paint Plugin',
2223
+ _classID: 'PbPl',
2224
+ KnNm: f.filter.name,
2225
+ GpuY: f.filter.gpu,
2226
+ LIWy: f.filter.lighting,
2227
+ FPth: '1',
2228
+ ...params,
2229
+ },
2230
+ filterID: 1348620396,
2231
+ };
2232
+ }
2233
+ case 'oil paint': return {
2234
+ ...base,
2235
+ Fltr: {
2236
+ _name: 'Oil Paint',
2237
+ _classID: 'oilPaint',
2238
+ lightingOn: f.filter.lightingOn,
2239
+ stylization: f.filter.stylization,
2240
+ cleanliness: f.filter.cleanliness,
2241
+ brushScale: f.filter.brushScale,
2242
+ microBrush: f.filter.microBrush,
2243
+ LghD: f.filter.lightDirection,
2244
+ specularity: f.filter.specularity,
2245
+ },
2246
+ filterID: 1122,
2247
+ };
2248
+ case 'liquify': return {
2249
+ ...base,
2250
+ Fltr: {
2251
+ _name: 'Liquify',
2252
+ _classID: 'LqFy',
2253
+ LqMe: f.filter.liquifyMesh,
2254
+ },
2255
+ filterID: 1282492025,
2256
+ };
2257
+ case 'perspective warp': return {
2258
+ ...base,
2259
+ Fltr: {
2260
+ _name: 'Perspective Warp',
2261
+ _classID: 'perspectiveWarpTransform',
2262
+ vertices: f.filter.vertices.map(pointToHrznVrtc),
2263
+ warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
2264
+ quads: f.filter.quads.map(indices => ({ indices })),
2265
+ },
2266
+ filterID: 442,
2267
+ };
2268
+ case 'curves': return {
2269
+ ...base,
2270
+ Fltr: {
2271
+ _name: 'Curves',
2272
+ _classID: 'Crvs',
2273
+ presetKind: presetKindType.encode(f.filter.presetKind),
2274
+ ...(f.filter.adjustments ? {
2275
+ Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
2276
+ _name: '',
2277
+ _classID: 'CrvA',
2278
+ Chnl: a.channels.map(Chnl.encode),
2279
+ 'Crv ': a.curve.map(c => ({
2280
+ _name: '',
2281
+ _classID: 'Pnt ',
2282
+ Hrzn: c.x,
2283
+ Vrtc: c.y,
2284
+ ...(c.curved ? { Cnty: true } : {}),
2285
+ })),
2286
+ } : {
2287
+ _name: '',
2288
+ _classID: 'CrvA',
2289
+ Chnl: a.channels.map(Chnl.encode),
2290
+ Mpng: a.values,
2291
+ })
2292
+ } : {}),
2293
+ },
2294
+ filterID: 1131574899,
2295
+ };
2296
+ case 'brightness/contrast': return {
2297
+ ...base,
2298
+ Fltr: {
2299
+ _name: 'Brightness/Contrast',
2300
+ _classID: 'BrgC',
2301
+ Brgh: f.filter.brightness,
2302
+ Cntr: f.filter.contrast,
2303
+ useLegacy: !!f.filter.useLegacy,
2304
+ },
2305
+ filterID: 1114793795,
2306
+ };
2307
+ // case 'hsb/hsl': return {
2308
+ // TODO: ...
2309
+ // };
2310
+ default: throw new Error(`Unknow filter type: ${f.type}`);
2311
+ }
2312
+ }
2313
+ // let t: any;
2314
+ function getWarpFromPlacedLayer(placed) {
2315
+ if (placed.warp)
2316
+ return placed.warp;
2317
+ if (!placed.width || !placed.height)
2318
+ throw new Error('You must provide width and height of the linked image in placedLayer');
2319
+ const w = placed.width;
2320
+ const h = placed.height;
2321
+ const x0 = 0, x1 = w / 3, x2 = w * 2 / 3, x3 = w;
2322
+ const y0 = 0, y1 = h / 3, y2 = h * 2 / 3, y3 = h;
2323
+ return {
2324
+ style: 'custom',
2325
+ value: 0,
2326
+ perspective: 0,
2327
+ perspectiveOther: 0,
2328
+ rotate: 'horizontal',
2329
+ bounds: {
2330
+ top: { value: 0, units: 'Pixels' },
2331
+ left: { value: 0, units: 'Pixels' },
2332
+ bottom: { value: h, units: 'Pixels' },
2333
+ right: { value: w, units: 'Pixels' },
2334
+ },
2335
+ uOrder: 4,
2336
+ vOrder: 4,
2337
+ customEnvelopeWarp: {
2338
+ meshPoints: [
2339
+ { x: x0, y: y0 }, { x: x1, y: y0 }, { x: x2, y: y0 }, { x: x3, y: y0 },
2340
+ { x: x0, y: y1 }, { x: x1, y: y1 }, { x: x2, y: y1 }, { x: x3, y: y1 },
2341
+ { x: x0, y: y2 }, { x: x1, y: y2 }, { x: x2, y: y2 }, { x: x3, y: y2 },
2342
+ { x: x0, y: y3 }, { x: x1, y: y3 }, { x: x2, y: y3 }, { x: x3, y: y3 },
2343
+ ],
2344
+ },
2345
+ };
2346
+ }
2347
+ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2348
+ if (readSignature(reader) !== 'soLD')
2349
+ throw new Error(`Invalid SoLd type`);
2350
+ const version = readInt32(reader);
2351
+ if (version !== 4 && version !== 5)
2352
+ throw new Error(`Invalid SoLd version`);
2353
+ const desc = readVersionAndDescriptor(reader, true);
2354
+ // console.log('SoLd', require('util').inspect(desc, false, 99, true));
2355
+ // console.log('SoLd.warp', require('util').inspect(desc.warp, false, 99, true));
2356
+ // console.log('SoLd.quiltWarp', require('util').inspect(desc.quiltWarp, false, 99, true));
2357
+ // desc.filterFX!.filterFXList[0].Fltr.puppetShapeList[0].meshBoundaryPath.pathComponents[0].SbpL[0]['Pts '] = [];
2358
+ // console.log('read', require('util').inspect(desc.filterFX, false, 99, true));
2359
+ // console.log('filterFXList[0]', require('util').inspect((desc as any).filterFX.filterFXList[0], false, 99, true));
2360
+ // t = desc;
2361
+ target.placedLayer = {
2362
+ id: desc.Idnt,
2363
+ placed: desc.placed,
2364
+ type: placedLayerTypes[desc.Type],
2365
+ pageNumber: desc.PgNm,
2366
+ totalPages: desc.totalPages,
2367
+ frameStep: frac(desc.frameStep),
2368
+ duration: frac(desc.duration),
2369
+ frameCount: desc.frameCount,
2370
+ transform: desc.Trnf,
2371
+ width: desc['Sz '].Wdth,
2372
+ height: desc['Sz '].Hght,
2373
+ resolution: parseUnits(desc.Rslt),
2374
+ warp: parseWarp((desc.quiltWarp || desc.warp)),
2375
+ };
2376
+ if (desc.nonAffineTransform && desc.nonAffineTransform.some((x, i) => x !== desc.Trnf[i])) {
2377
+ target.placedLayer.nonAffineTransform = desc.nonAffineTransform;
2378
+ }
2379
+ if (desc.Crop)
2380
+ target.placedLayer.crop = desc.Crop;
2381
+ if (desc.comp)
2382
+ target.placedLayer.comp = desc.comp;
2383
+ if (desc.compInfo) {
2384
+ target.placedLayer.compInfo = {
2385
+ compID: desc.compInfo.compID,
2386
+ originalCompID: desc.compInfo.originalCompID,
2387
+ };
2388
+ }
2389
+ if (desc.filterFX)
2390
+ target.placedLayer.filter = parseFilterFX(desc.filterFX, reader);
2391
+ // console.log('filter', require('util').inspect(target.placedLayer.filter, false, 99, true));
2392
+ skipBytes(reader, left()); // HACK
2393
+ }, (writer, target) => {
2394
+ writeSignature(writer, 'soLD');
2395
+ writeInt32(writer, 4); // version
2396
+ const placed = target.placedLayer;
2397
+ if (!placed.id || typeof placed.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(placed.id)) {
2398
+ throw new Error('Placed layer ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
2399
+ }
2400
+ const desc = {
2401
+ Idnt: placed.id,
2402
+ placed: placed.placed ?? placed.id,
2403
+ PgNm: placed.pageNumber || 1,
2404
+ totalPages: placed.totalPages || 1,
2405
+ ...(placed.crop ? { Crop: placed.crop } : {}),
2406
+ frameStep: placed.frameStep || { numerator: 0, denominator: 600 },
2407
+ duration: placed.duration || { numerator: 0, denominator: 600 },
2408
+ frameCount: placed.frameCount || 0,
2409
+ Annt: 16,
2410
+ Type: placedLayerTypes.indexOf(placed.type),
2411
+ Trnf: placed.transform,
2412
+ nonAffineTransform: placed.nonAffineTransform ?? placed.transform,
2413
+ // quiltWarp: {} as any,
2414
+ warp: encodeWarp(getWarpFromPlacedLayer(placed)),
2415
+ 'Sz ': {
2416
+ _name: '',
2417
+ _classID: 'Pnt ',
2418
+ Wdth: placed.width || 0, // TODO: find size ?
2419
+ Hght: placed.height || 0, // TODO: find size ?
2420
+ },
2421
+ Rslt: placed.resolution ? unitsValue(placed.resolution, 'resolution') : { units: 'Density', value: 72 },
2422
+ };
2423
+ if (placed.filter) {
2424
+ desc.filterFX = {
2425
+ _name: '',
2426
+ _classID: 'filterFXStyle',
2427
+ enab: placed.filter.enabled,
2428
+ validAtPosition: placed.filter.validAtPosition,
2429
+ filterMaskEnable: placed.filter.maskEnabled,
2430
+ filterMaskLinked: placed.filter.maskLinked,
2431
+ filterMaskExtendWithWhite: placed.filter.maskExtendWithWhite,
2432
+ filterFXList: placed.filter.list.map(f => serializeFilterFXItem(f)),
2433
+ };
2434
+ }
2435
+ // TODO:
2436
+ // desc.comp = -1;
2437
+ // desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
2438
+ // desc.ClMg = {
2439
+ // _name: '',
2440
+ // _classID: 'ClMg',
2441
+ // placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
2442
+ // } as any;
2443
+ // if (JSON.stringify(t) !== JSON.stringify(desc)) {
2444
+ // console.log('read', require('util').inspect(t, false, 99, true));
2445
+ // console.log('write', require('util').inspect(desc, false, 99, true));
2446
+ // console.error('DIFFERENT');
2447
+ // // throw new Error('DIFFERENT');
2448
+ // }
2449
+ if (placed.warp && isQuiltWarp(placed.warp)) {
2450
+ const quiltWarp = encodeWarp(placed.warp);
2451
+ desc.quiltWarp = quiltWarp;
2452
+ desc.warp = {
2453
+ warpStyle: 'warpStyle.warpNone',
2454
+ warpValue: quiltWarp.warpValue,
2455
+ warpPerspective: quiltWarp.warpPerspective,
2456
+ warpPerspectiveOther: quiltWarp.warpPerspectiveOther,
2457
+ warpRotate: quiltWarp.warpRotate,
2458
+ bounds: quiltWarp.bounds,
2459
+ uOrder: quiltWarp.uOrder,
2460
+ vOrder: quiltWarp.vOrder,
2461
+ };
2462
+ }
2463
+ else {
2464
+ delete desc.quiltWarp;
2465
+ }
2466
+ if (placed.comp)
2467
+ desc.comp = placed.comp;
2468
+ if (placed.compInfo)
2469
+ desc.compInfo = placed.compInfo;
2470
+ writeVersionAndDescriptor(writer, '', 'null', desc, desc.quiltWarp ? 'quiltWarp' : 'warp');
2471
+ });
2472
+ addHandlerAlias('SoLE', 'SoLd');
2473
+ addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2474
+ target.referencePoint = {
2475
+ x: readFloat64(reader),
2476
+ y: readFloat64(reader),
2477
+ };
2478
+ }, (writer, target) => {
2479
+ writeFloat64(writer, target.referencePoint.x);
2480
+ writeFloat64(writer, target.referencePoint.y);
2481
+ });
2482
+ addHandler('Lr16', () => false, (reader, _target, _left, psd, imageResources) => {
2483
+ readLayerInfo(reader, psd, imageResources);
2484
+ }, (_writer, _target) => {
2485
+ });
2486
+ addHandler('Lr32', () => false, (reader, _target, _left, psd, imageResources) => {
2487
+ readLayerInfo(reader, psd, imageResources);
2488
+ }, (_writer, _target) => {
2489
+ });
2490
+ addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2491
+ target.userMask = {
2492
+ colorSpace: readColor(reader),
2493
+ opacity: readUint16(reader) / 0xff,
2494
+ };
2495
+ const flag = readUint8(reader);
2496
+ if (flag !== 128)
2497
+ throw new Error('Invalid flag value');
2498
+ skipBytes(reader, 1);
2499
+ }, (writer, target) => {
2500
+ const userMask = target.userMask;
2501
+ writeColor(writer, userMask.colorSpace);
2502
+ writeUint16(writer, clamp(userMask.opacity, 0, 1) * 0xff);
2503
+ writeUint8(writer, 128);
2504
+ writeZeros(writer, 1);
2505
+ });
2506
+ if (MOCK_HANDLERS) {
2507
+ addHandler('Patt', target => target._Patt !== undefined, (reader, target, left) => {
2508
+ // console.log('additional info: Patt');
2509
+ target._Patt = readBytes(reader, left());
2510
+ }, (writer, target) => false && writeBytes(writer, target._Patt));
2511
+ }
2512
+ else {
2513
+ addHandler('Patt', // TODO: handle also Pat2 & Pat3
2514
+ // TODO: handle also Pat2 & Pat3
2515
+ target => !target, (reader, target, left) => {
2516
+ if (!left())
2517
+ return;
2518
+ skipBytes(reader, left());
2519
+ return; // not supported yet
2520
+ target;
2521
+ readPattern;
2522
+ // if (!target.patterns) target.patterns = [];
2523
+ // target.patterns.push(readPattern(reader));
2524
+ // skipBytes(reader, left());
2525
+ }, (_writer, _target) => {
2526
+ });
2527
+ }
2528
+ /*
2529
+ interface CAIDesc {
2530
+ enab: boolean;
2531
+ generationalGuid: string;
2532
+ }
2533
+
2534
+ addHandler(
2535
+ 'CAI ', // content credentials ? something to do with generative tech
2536
+ () => false,
2537
+ (reader, _target, left) => {
2538
+ const version = readUint32(reader); // 3
2539
+ const desc = readVersionAndDescriptor(reader) as CAIDesc;
2540
+ console.log('CAI', require('util').inspect(desc, false, 99, true));
2541
+ console.log('CAI', { version });
2542
+ console.log('CAI left', readBytes(reader, left())); // 8 bytes left, all zeroes
2543
+ },
2544
+ (_writer, _target) => {
2545
+ },
2546
+ );
2547
+ */
2548
+ if (MOCK_HANDLERS) {
2549
+ addHandler('CAI ', target => target._CAI_ !== undefined, (reader, target, left) => {
2550
+ target._CAI_ = readBytes(reader, left());
2551
+ }, (writer, target) => {
2552
+ writeBytes(writer, target._CAI_);
2553
+ });
2554
+ }
2555
+ if (MOCK_HANDLERS) {
2556
+ addHandler('OCIO', // generative tech?
2557
+ // generative tech?
2558
+ target => target._OCIO !== undefined, (reader, target, left) => {
2559
+ target._OCIO = readBytes(reader, left());
2560
+ }, (writer, target) => {
2561
+ writeBytes(writer, target._OCIO);
2562
+ });
2563
+ }
2564
+ // interface GenIDesc {
2565
+ // isUsingGenTech: number;
2566
+ // }
2567
+ if (MOCK_HANDLERS) {
2568
+ addHandler('GenI', // generative tech
2569
+ // generative tech
2570
+ target => target._GenI !== undefined, (reader, target, left) => {
2571
+ target._GenI = readBytes(reader, left());
2572
+ // const desc = readVersionAndDescriptor(reader) as GenIDesc;
2573
+ // console.log('GenI', require('util').inspect(desc, false, 99, true));
2574
+ }, (writer, target) => {
2575
+ writeBytes(writer, target._GenI);
2576
+ });
2577
+ }
2578
+ function readRect(reader) {
2579
+ const top = readInt32(reader);
2580
+ const left = readInt32(reader);
2581
+ const bottom = readInt32(reader);
2582
+ const right = readInt32(reader);
2583
+ return { top, left, bottom, right };
2584
+ }
2585
+ function writeRect(writer, rect) {
2586
+ writeInt32(writer, rect.top);
2587
+ writeInt32(writer, rect.left);
2588
+ writeInt32(writer, rect.bottom);
2589
+ writeInt32(writer, rect.right);
2590
+ }
2591
+ addHandler('Anno', target => target.annotations !== undefined, (reader, target, left) => {
2592
+ const major = readUint16(reader);
2593
+ const minor = readUint16(reader);
2594
+ if (major !== 2 || minor !== 1)
2595
+ throw new Error('Invalid Anno version');
2596
+ const count = readUint32(reader);
2597
+ const annotations = [];
2598
+ for (let i = 0; i < count; i++) {
2599
+ /*const length =*/ readUint32(reader);
2600
+ const type = readSignature(reader);
2601
+ const open = !!readUint8(reader);
2602
+ /*const flags =*/ readUint8(reader); // always 28
2603
+ /*const optionalBlocks =*/ readUint16(reader);
2604
+ const iconLocation = readRect(reader);
2605
+ const popupLocation = readRect(reader);
2606
+ const color = readColor(reader);
2607
+ const author = readPascalString(reader, 2);
2608
+ const name = readPascalString(reader, 2);
2609
+ const date = readPascalString(reader, 2);
2610
+ /*const contentLength =*/ readUint32(reader);
2611
+ /*const dataType =*/ readSignature(reader);
2612
+ const dataLength = readUint32(reader);
2613
+ let data;
2614
+ if (type === 'txtA') {
2615
+ if (dataLength >= 2 && readUint16(reader) === 0xfeff) {
2616
+ data = readUnicodeStringWithLength(reader, (dataLength - 2) / 2);
2617
+ }
2618
+ else {
2619
+ reader.offset -= 2;
2620
+ data = readAsciiString(reader, dataLength);
2621
+ }
2622
+ data = data.replace(/\r/g, '\n');
2623
+ }
2624
+ else if (type === 'sndA') {
2625
+ data = readBytes(reader, dataLength);
2626
+ }
2627
+ else {
2628
+ throw new Error('Unknown annotation type');
2629
+ }
2630
+ annotations.push({
2631
+ type: type === 'txtA' ? 'text' : 'sound', open, iconLocation, popupLocation, color, author, name, date, data,
2632
+ });
2633
+ }
2634
+ target.annotations = annotations;
2635
+ skipBytes(reader, left());
2636
+ }, (writer, target) => {
2637
+ const annotations = target.annotations;
2638
+ writeUint16(writer, 2);
2639
+ writeUint16(writer, 1);
2640
+ writeUint32(writer, annotations.length);
2641
+ for (const annotation of annotations) {
2642
+ const sound = annotation.type === 'sound';
2643
+ if (sound && !(annotation.data instanceof Uint8Array))
2644
+ throw new Error('Sound annotation data should be Uint8Array');
2645
+ if (!sound && typeof annotation.data !== 'string')
2646
+ throw new Error('Text annotation data should be string');
2647
+ const lengthOffset = writer.offset;
2648
+ writeUint32(writer, 0); // length
2649
+ writeSignature(writer, sound ? 'sndA' : 'txtA');
2650
+ writeUint8(writer, annotation.open ? 1 : 0);
2651
+ writeUint8(writer, 28);
2652
+ writeUint16(writer, 1);
2653
+ writeRect(writer, annotation.iconLocation);
2654
+ writeRect(writer, annotation.popupLocation);
2655
+ writeColor(writer, annotation.color);
2656
+ writePascalString(writer, annotation.author || '', 2);
2657
+ writePascalString(writer, annotation.name || '', 2);
2658
+ writePascalString(writer, annotation.date || '', 2);
2659
+ const contentOffset = writer.offset;
2660
+ writeUint32(writer, 0); // content length
2661
+ writeSignature(writer, sound ? 'sndM' : 'txtC');
2662
+ writeUint32(writer, 0); // data length
2663
+ const dataOffset = writer.offset;
2664
+ if (sound) {
2665
+ writeBytes(writer, annotation.data);
2666
+ }
2667
+ else {
2668
+ writeUint16(writer, 0xfeff); // unicode string indicator
2669
+ const text = annotation.data.replace(/\n/g, '\r');
2670
+ for (let i = 0; i < text.length; i++)
2671
+ writeUint16(writer, text.charCodeAt(i));
2672
+ }
2673
+ writer.view.setUint32(lengthOffset, writer.offset - lengthOffset, false);
2674
+ writer.view.setUint32(contentOffset, writer.offset - contentOffset, false);
2675
+ writer.view.setUint32(dataOffset - 4, writer.offset - dataOffset, false);
2676
+ }
2677
+ });
2678
+ function createLnkHandler(tag) {
2679
+ addHandler(tag, (target) => {
2680
+ const psd = target;
2681
+ if (!psd.linkedFiles || !psd.linkedFiles.length)
2682
+ return false;
2683
+ if (tag === 'lnkE' && !psd.linkedFiles.some(f => f.linkedFile))
2684
+ return false;
2685
+ return true;
2686
+ }, (reader, target, left, _psd) => {
2687
+ const psd = target;
2688
+ psd.linkedFiles = psd.linkedFiles || [];
2689
+ while (left() > 8) {
2690
+ let size = readLength64(reader);
2691
+ const startOffset = reader.offset;
2692
+ const type = readSignature(reader);
2693
+ // liFD - linked file data
2694
+ // liFE - linked file external
2695
+ // liFA - linked file alias
2696
+ const version = readInt32(reader);
2697
+ const id = readPascalString(reader, 1);
2698
+ const name = readUnicodeString(reader);
2699
+ const fileType = readSignature(reader).trim(); // ' ' if empty
2700
+ const fileCreator = readSignature(reader).trim(); // ' ' or '\0\0\0\0' if empty
2701
+ const dataSize = readLength64(reader);
2702
+ const hasFileOpenDescriptor = readUint8(reader);
2703
+ const fileOpenDescriptor = hasFileOpenDescriptor ? readVersionAndDescriptor(reader) : undefined;
2704
+ const linkedFileDescriptor = type === 'liFE' ? readVersionAndDescriptor(reader) : undefined;
2705
+ const file = { id, name };
2706
+ if (fileType)
2707
+ file.type = fileType;
2708
+ if (fileCreator)
2709
+ file.creator = fileCreator;
2710
+ if (fileOpenDescriptor) {
2711
+ file.descriptor = {
2712
+ compInfo: {
2713
+ compID: fileOpenDescriptor.compInfo.compID,
2714
+ originalCompID: fileOpenDescriptor.compInfo.originalCompID,
2715
+ }
2716
+ };
2717
+ }
2718
+ if (type === 'liFE' && version > 3) {
2719
+ const year = readInt32(reader);
2720
+ const month = readUint8(reader);
2721
+ const day = readUint8(reader);
2722
+ const hour = readUint8(reader);
2723
+ const minute = readUint8(reader);
2724
+ const seconds = readFloat64(reader);
2725
+ const wholeSeconds = Math.floor(seconds);
2726
+ const ms = (seconds - wholeSeconds) * 1000;
2727
+ file.time = (new Date(Date.UTC(year, month, day, hour, minute, wholeSeconds, ms))).toISOString();
2728
+ }
2729
+ const fileSize = type === 'liFE' ? readLength64(reader) : 0;
2730
+ if (type === 'liFA')
2731
+ skipBytes(reader, 8);
2732
+ if (type === 'liFD')
2733
+ file.data = readBytes(reader, dataSize); // seems to be a typo in docs
2734
+ if (version >= 5)
2735
+ file.childDocumentID = readUnicodeString(reader);
2736
+ if (version >= 6)
2737
+ file.assetModTime = readFloat64(reader);
2738
+ if (version >= 7)
2739
+ file.assetLockedState = readUint8(reader);
2740
+ if (type === 'liFE' && version === 2)
2741
+ file.data = readBytes(reader, fileSize);
2742
+ if (reader.skipLinkedFilesData)
2743
+ file.data = undefined;
2744
+ if (tag === 'lnkE') {
2745
+ file.linkedFile = {
2746
+ fileSize,
2747
+ name: linkedFileDescriptor?.['Nm '] || '',
2748
+ fullPath: linkedFileDescriptor?.fullPath || '',
2749
+ originalPath: linkedFileDescriptor?.originalPath || '',
2750
+ relativePath: linkedFileDescriptor?.relPath || '',
2751
+ };
2752
+ }
2753
+ psd.linkedFiles.push(file);
2754
+ while (size % 4)
2755
+ size++;
2756
+ reader.offset = startOffset + size;
2757
+ }
2758
+ skipBytes(reader, left()); // ?
2759
+ }, (writer, target) => {
2760
+ const psd = target;
2761
+ for (const file of psd.linkedFiles) {
2762
+ if ((tag === 'lnkE') !== !!file.linkedFile)
2763
+ continue;
2764
+ let version = 2;
2765
+ if (file.assetLockedState != null)
2766
+ version = 7;
2767
+ else if (file.assetModTime != null)
2768
+ version = 6;
2769
+ else if (file.childDocumentID != null)
2770
+ version = 5;
2771
+ else if (tag == 'lnkE')
2772
+ version = 3;
2773
+ writeLength64(writer, 0);
2774
+ const sizeOffset = writer.offset;
2775
+ writeSignature(writer, (tag === 'lnkE') ? 'liFE' : (file.data ? 'liFD' : 'liFA'));
2776
+ writeInt32(writer, version);
2777
+ if (!file.id || typeof file.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(file.id)) {
2778
+ throw new Error('Linked file ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
2779
+ }
2780
+ writePascalString(writer, file.id, 1);
2781
+ writeUnicodeStringWithPadding(writer, file.name || '');
2782
+ writeSignature(writer, file.type ? `${file.type} `.substring(0, 4) : ' ');
2783
+ writeSignature(writer, file.creator ? `${file.creator} `.substring(0, 4) : '\0\0\0\0');
2784
+ writeLength64(writer, file.data ? file.data.byteLength : 0);
2785
+ if (file.descriptor && file.descriptor.compInfo) {
2786
+ const desc = {
2787
+ compInfo: {
2788
+ compID: file.descriptor.compInfo.compID,
2789
+ originalCompID: file.descriptor.compInfo.originalCompID,
2790
+ },
2791
+ };
2792
+ writeUint8(writer, 1);
2793
+ writeVersionAndDescriptor(writer, '', 'null', desc);
2794
+ }
2795
+ else {
2796
+ writeUint8(writer, 0);
2797
+ }
2798
+ if (tag === 'lnkE') {
2799
+ const desc = {
2800
+ descVersion: 2,
2801
+ 'Nm ': file.linkedFile?.name ?? '',
2802
+ fullPath: file.linkedFile?.fullPath ?? '',
2803
+ originalPath: file.linkedFile?.originalPath ?? '',
2804
+ relPath: file.linkedFile?.relativePath ?? '',
2805
+ };
2806
+ writeVersionAndDescriptor(writer, '', 'ExternalFileLink', desc);
2807
+ const time = file.time ? new Date(file.time) : new Date();
2808
+ writeInt32(writer, time.getUTCFullYear());
2809
+ writeUint8(writer, time.getUTCMonth());
2810
+ writeUint8(writer, time.getUTCDate());
2811
+ writeUint8(writer, time.getUTCHours());
2812
+ writeUint8(writer, time.getUTCMinutes());
2813
+ writeFloat64(writer, time.getUTCSeconds() + time.getUTCMilliseconds() / 1000);
2814
+ }
2815
+ if (file.data) {
2816
+ writeBytes(writer, file.data);
2817
+ }
2818
+ else {
2819
+ writeLength64(writer, file.linkedFile?.fileSize || 0);
2820
+ }
2821
+ if (version >= 5)
2822
+ writeUnicodeStringWithPadding(writer, file.childDocumentID || '');
2823
+ if (version >= 6)
2824
+ writeFloat64(writer, file.assetModTime || 0);
2825
+ if (version >= 7)
2826
+ writeUint8(writer, file.assetLockedState || 0);
2827
+ let size = writer.offset - sizeOffset;
2828
+ writer.view.setUint32(sizeOffset - 4, size, false); // write size
2829
+ while (size % 4) {
2830
+ size++;
2831
+ writeUint8(writer, 0);
2832
+ }
2833
+ }
2834
+ });
2835
+ }
2836
+ createLnkHandler('lnk2');
2837
+ createLnkHandler('lnkE');
2838
+ addHandlerAlias('lnkD', 'lnk2');
2839
+ addHandlerAlias('lnk3', 'lnk2');
2840
+ addHandler('pths', hasKey('pathList'), (reader, target) => {
2841
+ const desc = readVersionAndDescriptor(reader, true);
2842
+ // console.log(require('util').inspect(desc, false, 99, true));
2843
+ // if (options.throwForMissingFeatures && desc?.pathList?.length) throw new Error('non-empty pathList in `pths`');
2844
+ desc;
2845
+ target.pathList = []; // TODO: read paths
2846
+ }, (writer, _target) => {
2847
+ const desc = {
2848
+ pathList: [], // TODO: write paths
2849
+ };
2850
+ writeVersionAndDescriptor(writer, '', 'pathsDataClass', desc);
2851
+ });
2852
+ addHandler('lyvr', hasKey('version'), (reader, target) => target.version = readUint32(reader), (writer, target) => writeUint32(writer, target.version));
2853
+ addHandler('lfxs', () => false, // TODO: not sure when we actually need to write this section
2854
+ // NOTE: this might be insufficient
2855
+ // target => target.effects !== undefined && (
2856
+ // !!target.effects.dropShadow?.some(e => e.choke) ||
2857
+ // !!target.effects.innerShadow?.some(e => e.choke) ||
2858
+ // !!target.effects.outerGlow?.choke ||
2859
+ // !!target.effects.innerGlow?.choke
2860
+ // ),
2861
+ (reader, target, left) => {
2862
+ const version = readUint32(reader);
2863
+ if (version !== 0)
2864
+ throw new Error(`Invalid lfxs version`);
2865
+ const desc = readVersionAndDescriptor(reader);
2866
+ target.effects = parseEffects(desc, !!reader.logMissingFeatures);
2867
+ skipBytes(reader, left());
2868
+ }, (writer, target, _, options) => {
2869
+ const desc = serializeEffects(target.effects, !!options.logMissingFeatures, true);
2870
+ writeUint32(writer, 0); // version
2871
+ writeVersionAndDescriptor(writer, '', 'null', desc);
2872
+ });
2873
+ function adjustmentType(type) {
2874
+ return (target) => !!target.adjustment && target.adjustment.type === type;
2875
+ }
2876
+ addHandler('brit', adjustmentType('brightness/contrast'), (reader, target, left) => {
2877
+ if (!target.adjustment) { // ignore if got one from CgEd block
2878
+ target.adjustment = {
2879
+ type: 'brightness/contrast',
2880
+ brightness: readInt16(reader),
2881
+ contrast: readInt16(reader),
2882
+ meanValue: readInt16(reader),
2883
+ labColorOnly: !!readUint8(reader),
2884
+ useLegacy: true,
2885
+ };
2886
+ }
2887
+ skipBytes(reader, left());
2888
+ }, (writer, target) => {
2889
+ const info = target.adjustment;
2890
+ writeInt16(writer, info.brightness || 0);
2891
+ writeInt16(writer, info.contrast || 0);
2892
+ writeInt16(writer, info.meanValue ?? 127);
2893
+ writeUint8(writer, info.labColorOnly ? 1 : 0);
2894
+ writeZeros(writer, 1);
2895
+ });
2896
+ function readLevelsChannel(reader) {
2897
+ const shadowInput = readInt16(reader);
2898
+ const highlightInput = readInt16(reader);
2899
+ const shadowOutput = readInt16(reader);
2900
+ const highlightOutput = readInt16(reader);
2901
+ const midtoneInput = readInt16(reader) / 100;
2902
+ return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
2903
+ }
2904
+ function writeLevelsChannel(writer, channel) {
2905
+ writeInt16(writer, channel.shadowInput);
2906
+ writeInt16(writer, channel.highlightInput);
2907
+ writeInt16(writer, channel.shadowOutput);
2908
+ writeInt16(writer, channel.highlightOutput);
2909
+ writeInt16(writer, Math.round(channel.midtoneInput * 100));
2910
+ }
2911
+ addHandler('levl', adjustmentType('levels'), (reader, target, left) => {
2912
+ if (readUint16(reader) !== 2)
2913
+ throw new Error('Invalid levl version');
2914
+ target.adjustment = {
2915
+ ...target.adjustment,
2916
+ type: 'levels',
2917
+ rgb: readLevelsChannel(reader),
2918
+ red: readLevelsChannel(reader),
2919
+ green: readLevelsChannel(reader),
2920
+ blue: readLevelsChannel(reader),
2921
+ };
2922
+ skipBytes(reader, left());
2923
+ }, (writer, target) => {
2924
+ const info = target.adjustment;
2925
+ const defaultChannel = {
2926
+ shadowInput: 0,
2927
+ highlightInput: 255,
2928
+ shadowOutput: 0,
2929
+ highlightOutput: 255,
2930
+ midtoneInput: 1,
2931
+ };
2932
+ writeUint16(writer, 2); // version
2933
+ writeLevelsChannel(writer, info.rgb || defaultChannel);
2934
+ writeLevelsChannel(writer, info.red || defaultChannel);
2935
+ writeLevelsChannel(writer, info.blue || defaultChannel);
2936
+ writeLevelsChannel(writer, info.green || defaultChannel);
2937
+ for (let i = 0; i < 59; i++)
2938
+ writeLevelsChannel(writer, defaultChannel);
2939
+ });
2940
+ function readCurveChannel(reader) {
2941
+ const nodes = readUint16(reader);
2942
+ const channel = [];
2943
+ for (let j = 0; j < nodes; j++) {
2944
+ const output = readInt16(reader);
2945
+ const input = readInt16(reader);
2946
+ channel.push({ input, output });
2947
+ }
2948
+ return channel;
2949
+ }
2950
+ function writeCurveChannel(writer, channel) {
2951
+ writeUint16(writer, channel.length);
2952
+ for (const n of channel) {
2953
+ writeUint16(writer, n.output);
2954
+ writeUint16(writer, n.input);
2955
+ }
2956
+ }
2957
+ addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
2958
+ readUint8(reader);
2959
+ if (readUint16(reader) !== 1)
2960
+ throw new Error('Invalid curv version');
2961
+ readUint16(reader);
2962
+ const channels = readUint16(reader);
2963
+ const info = { type: 'curves' };
2964
+ if (channels & 1)
2965
+ info.rgb = readCurveChannel(reader);
2966
+ if (channels & 2)
2967
+ info.red = readCurveChannel(reader);
2968
+ if (channels & 4)
2969
+ info.green = readCurveChannel(reader);
2970
+ if (channels & 8)
2971
+ info.blue = readCurveChannel(reader);
2972
+ target.adjustment = {
2973
+ ...target.adjustment,
2974
+ ...info,
2975
+ };
2976
+ // ignoring, duplicate information
2977
+ // checkSignature(reader, 'Crv ');
2978
+ // const cVersion = readUint16(reader);
2979
+ // readUint16(reader);
2980
+ // const channelCount = readUint16(reader);
2981
+ // for (let i = 0; i < channelCount; i++) {
2982
+ // const index = readUint16(reader);
2983
+ // const nodes = readUint16(reader);
2984
+ // for (let j = 0; j < nodes; j++) {
2985
+ // const output = readInt16(reader);
2986
+ // const input = readInt16(reader);
2987
+ // }
2988
+ // }
2989
+ skipBytes(reader, left());
2990
+ }, (writer, target) => {
2991
+ const info = target.adjustment;
2992
+ const { rgb, red, green, blue } = info;
2993
+ let channels = 0;
2994
+ let channelCount = 0;
2995
+ if (rgb && rgb.length) {
2996
+ channels |= 1;
2997
+ channelCount++;
2998
+ }
2999
+ if (red && red.length) {
3000
+ channels |= 2;
3001
+ channelCount++;
3002
+ }
3003
+ if (green && green.length) {
3004
+ channels |= 4;
3005
+ channelCount++;
3006
+ }
3007
+ if (blue && blue.length) {
3008
+ channels |= 8;
3009
+ channelCount++;
3010
+ }
3011
+ writeUint8(writer, 0);
3012
+ writeUint16(writer, 1); // version
3013
+ writeUint16(writer, 0);
3014
+ writeUint16(writer, channels);
3015
+ if (rgb && rgb.length)
3016
+ writeCurveChannel(writer, rgb);
3017
+ if (red && red.length)
3018
+ writeCurveChannel(writer, red);
3019
+ if (green && green.length)
3020
+ writeCurveChannel(writer, green);
3021
+ if (blue && blue.length)
3022
+ writeCurveChannel(writer, blue);
3023
+ writeSignature(writer, 'Crv ');
3024
+ writeUint16(writer, 4); // version
3025
+ writeUint16(writer, 0);
3026
+ writeUint16(writer, channelCount);
3027
+ if (rgb && rgb.length) {
3028
+ writeUint16(writer, 0);
3029
+ writeCurveChannel(writer, rgb);
3030
+ }
3031
+ if (red && red.length) {
3032
+ writeUint16(writer, 1);
3033
+ writeCurveChannel(writer, red);
3034
+ }
3035
+ if (green && green.length) {
3036
+ writeUint16(writer, 2);
3037
+ writeCurveChannel(writer, green);
3038
+ }
3039
+ if (blue && blue.length) {
3040
+ writeUint16(writer, 3);
3041
+ writeCurveChannel(writer, blue);
3042
+ }
3043
+ });
3044
+ addHandler('expA', adjustmentType('exposure'), (reader, target, left) => {
3045
+ if (readUint16(reader) !== 1)
3046
+ throw new Error('Invalid expA version');
3047
+ target.adjustment = {
3048
+ ...target.adjustment,
3049
+ type: 'exposure',
3050
+ exposure: readFloat32(reader),
3051
+ offset: readFloat32(reader),
3052
+ gamma: readFloat32(reader),
3053
+ };
3054
+ skipBytes(reader, left());
3055
+ }, (writer, target) => {
3056
+ const info = target.adjustment;
3057
+ writeUint16(writer, 1); // version
3058
+ writeFloat32(writer, info.exposure);
3059
+ writeFloat32(writer, info.offset);
3060
+ writeFloat32(writer, info.gamma);
3061
+ writeZeros(writer, 2);
3062
+ });
3063
+ addHandler('vibA', adjustmentType('vibrance'), (reader, target, left) => {
3064
+ const desc = readVersionAndDescriptor(reader);
3065
+ target.adjustment = { type: 'vibrance' };
3066
+ if (desc.vibrance !== undefined)
3067
+ target.adjustment.vibrance = desc.vibrance;
3068
+ if (desc.Strt !== undefined)
3069
+ target.adjustment.saturation = desc.Strt;
3070
+ skipBytes(reader, left());
3071
+ }, (writer, target) => {
3072
+ const info = target.adjustment;
3073
+ const desc = {};
3074
+ if (info.vibrance !== undefined)
3075
+ desc.vibrance = info.vibrance;
3076
+ if (info.saturation !== undefined)
3077
+ desc.Strt = info.saturation;
3078
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3079
+ });
3080
+ function readHueChannel(reader) {
3081
+ return {
3082
+ a: readInt16(reader),
3083
+ b: readInt16(reader),
3084
+ c: readInt16(reader),
3085
+ d: readInt16(reader),
3086
+ hue: readInt16(reader),
3087
+ saturation: readInt16(reader),
3088
+ lightness: readInt16(reader),
3089
+ };
3090
+ }
3091
+ function writeHueChannel(writer, channel) {
3092
+ const c = channel || {};
3093
+ writeInt16(writer, c.a || 0);
3094
+ writeInt16(writer, c.b || 0);
3095
+ writeInt16(writer, c.c || 0);
3096
+ writeInt16(writer, c.d || 0);
3097
+ writeInt16(writer, c.hue || 0);
3098
+ writeInt16(writer, c.saturation || 0);
3099
+ writeInt16(writer, c.lightness || 0);
3100
+ }
3101
+ addHandler('hue2', adjustmentType('hue/saturation'), (reader, target, left) => {
3102
+ if (readUint16(reader) !== 2)
3103
+ throw new Error('Invalid hue2 version');
3104
+ target.adjustment = {
3105
+ ...target.adjustment,
3106
+ type: 'hue/saturation',
3107
+ master: readHueChannel(reader),
3108
+ reds: readHueChannel(reader),
3109
+ yellows: readHueChannel(reader),
3110
+ greens: readHueChannel(reader),
3111
+ cyans: readHueChannel(reader),
3112
+ blues: readHueChannel(reader),
3113
+ magentas: readHueChannel(reader),
3114
+ };
3115
+ skipBytes(reader, left());
3116
+ }, (writer, target) => {
3117
+ const info = target.adjustment;
3118
+ writeUint16(writer, 2); // version
3119
+ writeHueChannel(writer, info.master);
3120
+ writeHueChannel(writer, info.reds);
3121
+ writeHueChannel(writer, info.yellows);
3122
+ writeHueChannel(writer, info.greens);
3123
+ writeHueChannel(writer, info.cyans);
3124
+ writeHueChannel(writer, info.blues);
3125
+ writeHueChannel(writer, info.magentas);
3126
+ });
3127
+ function readColorBalance(reader) {
3128
+ return {
3129
+ cyanRed: readInt16(reader),
3130
+ magentaGreen: readInt16(reader),
3131
+ yellowBlue: readInt16(reader),
3132
+ };
3133
+ }
3134
+ function writeColorBalance(writer, value) {
3135
+ writeInt16(writer, value.cyanRed || 0);
3136
+ writeInt16(writer, value.magentaGreen || 0);
3137
+ writeInt16(writer, value.yellowBlue || 0);
3138
+ }
3139
+ addHandler('blnc', adjustmentType('color balance'), (reader, target, left) => {
3140
+ target.adjustment = {
3141
+ type: 'color balance',
3142
+ shadows: readColorBalance(reader),
3143
+ midtones: readColorBalance(reader),
3144
+ highlights: readColorBalance(reader),
3145
+ preserveLuminosity: !!readUint8(reader),
3146
+ };
3147
+ skipBytes(reader, left());
3148
+ }, (writer, target) => {
3149
+ const info = target.adjustment;
3150
+ writeColorBalance(writer, info.shadows || {});
3151
+ writeColorBalance(writer, info.midtones || {});
3152
+ writeColorBalance(writer, info.highlights || {});
3153
+ writeUint8(writer, info.preserveLuminosity ? 1 : 0);
3154
+ writeZeros(writer, 1);
3155
+ });
3156
+ addHandler('blwh', adjustmentType('black & white'), (reader, target, left) => {
3157
+ const desc = readVersionAndDescriptor(reader);
3158
+ target.adjustment = {
3159
+ type: 'black & white',
3160
+ reds: desc['Rd '],
3161
+ yellows: desc.Yllw,
3162
+ greens: desc['Grn '],
3163
+ cyans: desc['Cyn '],
3164
+ blues: desc['Bl '],
3165
+ magentas: desc.Mgnt,
3166
+ useTint: !!desc.useTint,
3167
+ presetKind: desc.bwPresetKind,
3168
+ presetFileName: desc.blackAndWhitePresetFileName,
3169
+ };
3170
+ if (desc.tintColor !== undefined)
3171
+ target.adjustment.tintColor = parseColor(desc.tintColor);
3172
+ skipBytes(reader, left());
3173
+ }, (writer, target) => {
3174
+ const info = target.adjustment;
3175
+ const desc = {
3176
+ 'Rd ': info.reds || 0,
3177
+ Yllw: info.yellows || 0,
3178
+ 'Grn ': info.greens || 0,
3179
+ 'Cyn ': info.cyans || 0,
3180
+ 'Bl ': info.blues || 0,
3181
+ Mgnt: info.magentas || 0,
3182
+ useTint: !!info.useTint,
3183
+ tintColor: serializeColor(info.tintColor),
3184
+ bwPresetKind: info.presetKind || 0,
3185
+ blackAndWhitePresetFileName: info.presetFileName || '',
3186
+ };
3187
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3188
+ });
3189
+ addHandler('phfl', adjustmentType('photo filter'), (reader, target, left) => {
3190
+ const version = readUint16(reader);
3191
+ if (version !== 2 && version !== 3)
3192
+ throw new Error('Invalid phfl version');
3193
+ let color;
3194
+ if (version === 2) {
3195
+ color = readColor(reader);
3196
+ }
3197
+ else { // version 3
3198
+ // TODO: test this, this is probably wrong
3199
+ color = {
3200
+ l: readInt32(reader) / 100,
3201
+ a: readInt32(reader) / 100,
3202
+ b: readInt32(reader) / 100,
3203
+ };
3204
+ }
3205
+ target.adjustment = {
3206
+ type: 'photo filter',
3207
+ color,
3208
+ density: readUint32(reader) / 100,
3209
+ preserveLuminosity: !!readUint8(reader),
3210
+ };
3211
+ skipBytes(reader, left());
3212
+ }, (writer, target) => {
3213
+ const info = target.adjustment;
3214
+ writeUint16(writer, 2); // version
3215
+ writeColor(writer, info.color || { l: 0, a: 0, b: 0 });
3216
+ writeUint32(writer, (info.density || 0) * 100);
3217
+ writeUint8(writer, info.preserveLuminosity ? 1 : 0);
3218
+ writeZeros(writer, 3);
3219
+ });
3220
+ function readMixrChannel(reader) {
3221
+ const red = readInt16(reader);
3222
+ const green = readInt16(reader);
3223
+ const blue = readInt16(reader);
3224
+ skipBytes(reader, 2);
3225
+ const constant = readInt16(reader);
3226
+ return { red, green, blue, constant };
3227
+ }
3228
+ function writeMixrChannel(writer, channel) {
3229
+ const c = channel || {};
3230
+ writeInt16(writer, c.red);
3231
+ writeInt16(writer, c.green);
3232
+ writeInt16(writer, c.blue);
3233
+ writeZeros(writer, 2);
3234
+ writeInt16(writer, c.constant);
3235
+ }
3236
+ addHandler('mixr', adjustmentType('channel mixer'), (reader, target, left) => {
3237
+ if (readUint16(reader) !== 1)
3238
+ throw new Error('Invalid mixr version');
3239
+ const adjustment = target.adjustment = {
3240
+ ...target.adjustment,
3241
+ type: 'channel mixer',
3242
+ monochrome: !!readUint16(reader),
3243
+ };
3244
+ if (!adjustment.monochrome) {
3245
+ adjustment.red = readMixrChannel(reader);
3246
+ adjustment.green = readMixrChannel(reader);
3247
+ adjustment.blue = readMixrChannel(reader);
3248
+ }
3249
+ adjustment.gray = readMixrChannel(reader);
3250
+ skipBytes(reader, left());
3251
+ }, (writer, target) => {
3252
+ const info = target.adjustment;
3253
+ writeUint16(writer, 1); // version
3254
+ writeUint16(writer, info.monochrome ? 1 : 0);
3255
+ if (info.monochrome) {
3256
+ writeMixrChannel(writer, info.gray);
3257
+ writeZeros(writer, 3 * 5 * 2);
3258
+ }
3259
+ else {
3260
+ writeMixrChannel(writer, info.red);
3261
+ writeMixrChannel(writer, info.green);
3262
+ writeMixrChannel(writer, info.blue);
3263
+ writeMixrChannel(writer, info.gray);
3264
+ }
3265
+ });
3266
+ const colorLookupType = createEnum('colorLookupType', '3DLUT', {
3267
+ '3dlut': '3DLUT',
3268
+ abstractProfile: 'abstractProfile',
3269
+ deviceLinkProfile: 'deviceLinkProfile',
3270
+ });
3271
+ const LUTFormatType = createEnum('LUTFormatType', 'look', {
3272
+ look: 'LUTFormatLOOK',
3273
+ cube: 'LUTFormatCUBE',
3274
+ '3dl': 'LUTFormat3DL',
3275
+ });
3276
+ const colorLookupOrder = createEnum('colorLookupOrder', 'rgb', {
3277
+ rgb: 'rgbOrder',
3278
+ bgr: 'bgrOrder',
3279
+ });
3280
+ addHandler('clrL', adjustmentType('color lookup'), (reader, target, left) => {
3281
+ if (readUint16(reader) !== 1)
3282
+ throw new Error('Invalid clrL version');
3283
+ const desc = readVersionAndDescriptor(reader);
3284
+ target.adjustment = { type: 'color lookup' };
3285
+ const info = target.adjustment;
3286
+ if (desc.lookupType !== undefined)
3287
+ info.lookupType = colorLookupType.decode(desc.lookupType);
3288
+ if (desc['Nm '] !== undefined)
3289
+ info.name = desc['Nm '];
3290
+ if (desc.Dthr !== undefined)
3291
+ info.dither = desc.Dthr;
3292
+ if (desc.profile !== undefined)
3293
+ info.profile = desc.profile;
3294
+ if (desc.LUTFormat !== undefined)
3295
+ info.lutFormat = LUTFormatType.decode(desc.LUTFormat);
3296
+ if (desc.dataOrder !== undefined)
3297
+ info.dataOrder = colorLookupOrder.decode(desc.dataOrder);
3298
+ if (desc.tableOrder !== undefined)
3299
+ info.tableOrder = colorLookupOrder.decode(desc.tableOrder);
3300
+ if (desc.LUT3DFileData !== undefined)
3301
+ info.lut3DFileData = desc.LUT3DFileData;
3302
+ if (desc.LUT3DFileName !== undefined)
3303
+ info.lut3DFileName = desc.LUT3DFileName;
3304
+ skipBytes(reader, left());
3305
+ }, (writer, target) => {
3306
+ const info = target.adjustment;
3307
+ const desc = {};
3308
+ if (info.lookupType !== undefined)
3309
+ desc.lookupType = colorLookupType.encode(info.lookupType);
3310
+ if (info.name !== undefined)
3311
+ desc['Nm '] = info.name;
3312
+ if (info.dither !== undefined)
3313
+ desc.Dthr = info.dither;
3314
+ if (info.profile !== undefined)
3315
+ desc.profile = info.profile;
3316
+ if (info.lutFormat !== undefined)
3317
+ desc.LUTFormat = LUTFormatType.encode(info.lutFormat);
3318
+ if (info.dataOrder !== undefined)
3319
+ desc.dataOrder = colorLookupOrder.encode(info.dataOrder);
3320
+ if (info.tableOrder !== undefined)
3321
+ desc.tableOrder = colorLookupOrder.encode(info.tableOrder);
3322
+ if (info.lut3DFileData !== undefined)
3323
+ desc.LUT3DFileData = info.lut3DFileData;
3324
+ if (info.lut3DFileName !== undefined)
3325
+ desc.LUT3DFileName = info.lut3DFileName;
3326
+ writeUint16(writer, 1); // version
3327
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3328
+ });
3329
+ addHandler('nvrt', adjustmentType('invert'), (reader, target, left) => {
3330
+ target.adjustment = { type: 'invert' };
3331
+ skipBytes(reader, left());
3332
+ }, () => {
3333
+ // nothing to write here
3334
+ });
3335
+ addHandler('post', adjustmentType('posterize'), (reader, target, left) => {
3336
+ target.adjustment = {
3337
+ type: 'posterize',
3338
+ levels: readUint16(reader),
3339
+ };
3340
+ skipBytes(reader, left());
3341
+ }, (writer, target) => {
3342
+ const info = target.adjustment;
3343
+ writeUint16(writer, info.levels ?? 4);
3344
+ writeZeros(writer, 2);
3345
+ });
3346
+ addHandler('thrs', adjustmentType('threshold'), (reader, target, left) => {
3347
+ target.adjustment = {
3348
+ type: 'threshold',
3349
+ level: readUint16(reader),
3350
+ };
3351
+ skipBytes(reader, left());
3352
+ }, (writer, target) => {
3353
+ const info = target.adjustment;
3354
+ writeUint16(writer, info.level ?? 128);
3355
+ writeZeros(writer, 2);
3356
+ });
3357
+ const grdmColorModels = ['', '', '', 'rgb', 'hsb', '', 'lab'];
3358
+ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3359
+ const version = readUint16(reader);
3360
+ if (version !== 1 && version !== 3)
3361
+ throw new Error('Invalid grdm version');
3362
+ const info = {
3363
+ type: 'gradient map',
3364
+ gradientType: 'solid',
3365
+ };
3366
+ info.reverse = !!readUint8(reader);
3367
+ info.dither = !!readUint8(reader);
3368
+ const hasMethod = !!readUint8(reader);
3369
+ reader.offset--;
3370
+ if (hasMethod) {
3371
+ const method = readSignature(reader);
3372
+ info.method = gradientInterpolationMethodType.decode(method);
3373
+ }
3374
+ info.name = readUnicodeString(reader);
3375
+ info.colorStops = [];
3376
+ info.opacityStops = [];
3377
+ const stopsCount = readUint16(reader);
3378
+ for (let i = 0; i < stopsCount; i++) {
3379
+ info.colorStops.push({
3380
+ location: readUint32(reader),
3381
+ midpoint: readUint32(reader) / 100,
3382
+ color: readColor(reader),
3383
+ });
3384
+ skipBytes(reader, 2);
3385
+ }
3386
+ const opacityStopsCount = readUint16(reader);
3387
+ for (let i = 0; i < opacityStopsCount; i++) {
3388
+ info.opacityStops.push({
3389
+ location: readUint32(reader),
3390
+ midpoint: readUint32(reader) / 100,
3391
+ opacity: readUint16(reader) / 0xff,
3392
+ });
3393
+ }
3394
+ const expansionCount = readUint16(reader);
3395
+ if (expansionCount !== 2)
3396
+ throw new Error('Invalid grdm expansion count');
3397
+ const interpolation = readUint16(reader);
3398
+ info.smoothness = interpolation / 4096;
3399
+ const length = readUint16(reader);
3400
+ if (length !== 32)
3401
+ throw new Error('Invalid grdm length');
3402
+ info.gradientType = readUint16(reader) ? 'noise' : 'solid';
3403
+ info.randomSeed = readUint32(reader);
3404
+ info.addTransparency = !!readUint16(reader);
3405
+ info.restrictColors = !!readUint16(reader);
3406
+ info.roughness = readUint32(reader) / 4096;
3407
+ info.colorModel = (grdmColorModels[readUint16(reader)] || 'rgb');
3408
+ info.min = [
3409
+ readUint16(reader) / 0x8000,
3410
+ readUint16(reader) / 0x8000,
3411
+ readUint16(reader) / 0x8000,
3412
+ readUint16(reader) / 0x8000,
3413
+ ];
3414
+ info.max = [
3415
+ readUint16(reader) / 0x8000,
3416
+ readUint16(reader) / 0x8000,
3417
+ readUint16(reader) / 0x8000,
3418
+ readUint16(reader) / 0x8000,
3419
+ ];
3420
+ skipBytes(reader, left());
3421
+ for (const s of info.colorStops)
3422
+ s.location /= interpolation;
3423
+ for (const s of info.opacityStops)
3424
+ s.location /= interpolation;
3425
+ target.adjustment = info;
3426
+ }, (writer, target) => {
3427
+ const info = target.adjustment;
3428
+ writeUint16(writer, info.method !== undefined ? 3 : 1); // version
3429
+ writeUint8(writer, info.reverse ? 1 : 0);
3430
+ writeUint8(writer, info.dither ? 1 : 0);
3431
+ if (info.method !== undefined) {
3432
+ writeSignature(writer, gradientInterpolationMethodType.encode(info.method));
3433
+ }
3434
+ writeUnicodeStringWithPadding(writer, info.name || '');
3435
+ writeUint16(writer, info.colorStops && info.colorStops.length || 0);
3436
+ const interpolation = Math.round((info.smoothness ?? 1) * 4096);
3437
+ for (const s of info.colorStops || []) {
3438
+ writeUint32(writer, Math.round(s.location * interpolation));
3439
+ writeUint32(writer, Math.round(s.midpoint * 100));
3440
+ writeColor(writer, s.color);
3441
+ writeZeros(writer, 2);
3442
+ }
3443
+ writeUint16(writer, info.opacityStops && info.opacityStops.length || 0);
3444
+ for (const s of info.opacityStops || []) {
3445
+ writeUint32(writer, Math.round(s.location * interpolation));
3446
+ writeUint32(writer, Math.round(s.midpoint * 100));
3447
+ writeUint16(writer, Math.round(s.opacity * 0xff));
3448
+ }
3449
+ writeUint16(writer, 2); // expansion count
3450
+ writeUint16(writer, interpolation);
3451
+ writeUint16(writer, 32); // length
3452
+ writeUint16(writer, info.gradientType === 'noise' ? 1 : 0);
3453
+ writeUint32(writer, info.randomSeed || 0);
3454
+ writeUint16(writer, info.addTransparency ? 1 : 0);
3455
+ writeUint16(writer, info.restrictColors ? 1 : 0);
3456
+ writeUint32(writer, Math.round((info.roughness ?? 1) * 4096));
3457
+ const colorModel = grdmColorModels.indexOf(info.colorModel ?? 'rgb');
3458
+ writeUint16(writer, colorModel === -1 ? 3 : colorModel);
3459
+ for (let i = 0; i < 4; i++)
3460
+ writeUint16(writer, Math.round((info.min && info.min[i] || 0) * 0x8000));
3461
+ for (let i = 0; i < 4; i++)
3462
+ writeUint16(writer, Math.round((info.max && info.max[i] || 0) * 0x8000));
3463
+ writeZeros(writer, 4);
3464
+ });
3465
+ function readSelectiveColors(reader) {
3466
+ return {
3467
+ c: readInt16(reader),
3468
+ m: readInt16(reader),
3469
+ y: readInt16(reader),
3470
+ k: readInt16(reader),
3471
+ };
3472
+ }
3473
+ function writeSelectiveColors(writer, cmyk) {
3474
+ const c = cmyk || {};
3475
+ writeInt16(writer, c.c);
3476
+ writeInt16(writer, c.m);
3477
+ writeInt16(writer, c.y);
3478
+ writeInt16(writer, c.k);
3479
+ }
3480
+ addHandler('selc', adjustmentType('selective color'), (reader, target) => {
3481
+ if (readUint16(reader) !== 1)
3482
+ throw new Error('Invalid selc version');
3483
+ const mode = readUint16(reader) ? 'absolute' : 'relative';
3484
+ skipBytes(reader, 8);
3485
+ target.adjustment = {
3486
+ type: 'selective color',
3487
+ mode,
3488
+ reds: readSelectiveColors(reader),
3489
+ yellows: readSelectiveColors(reader),
3490
+ greens: readSelectiveColors(reader),
3491
+ cyans: readSelectiveColors(reader),
3492
+ blues: readSelectiveColors(reader),
3493
+ magentas: readSelectiveColors(reader),
3494
+ whites: readSelectiveColors(reader),
3495
+ neutrals: readSelectiveColors(reader),
3496
+ blacks: readSelectiveColors(reader),
3497
+ };
3498
+ }, (writer, target) => {
3499
+ const info = target.adjustment;
3500
+ writeUint16(writer, 1); // version
3501
+ writeUint16(writer, info.mode === 'absolute' ? 1 : 0);
3502
+ writeZeros(writer, 8);
3503
+ writeSelectiveColors(writer, info.reds);
3504
+ writeSelectiveColors(writer, info.yellows);
3505
+ writeSelectiveColors(writer, info.greens);
3506
+ writeSelectiveColors(writer, info.cyans);
3507
+ writeSelectiveColors(writer, info.blues);
3508
+ writeSelectiveColors(writer, info.magentas);
3509
+ writeSelectiveColors(writer, info.whites);
3510
+ writeSelectiveColors(writer, info.neutrals);
3511
+ writeSelectiveColors(writer, info.blacks);
3512
+ });
3513
+ addHandler('CgEd', target => {
3514
+ const a = target.adjustment;
3515
+ if (!a)
3516
+ return false;
3517
+ return (a.type === 'brightness/contrast' && !a.useLegacy) ||
3518
+ ((a.type === 'levels' || a.type === 'curves' || a.type === 'exposure' || a.type === 'channel mixer' ||
3519
+ a.type === 'hue/saturation') && a.presetFileName !== undefined);
3520
+ }, (reader, target, left) => {
3521
+ const desc = readVersionAndDescriptor(reader);
3522
+ if (desc.Vrsn !== 1)
3523
+ throw new Error('Invalid CgEd version');
3524
+ // this section can specify preset file name for other adjustment types
3525
+ if ('presetFileName' in desc) {
3526
+ target.adjustment = {
3527
+ ...target.adjustment,
3528
+ presetKind: desc.presetKind,
3529
+ presetFileName: desc.presetFileName,
3530
+ };
3531
+ }
3532
+ else if ('curvesPresetFileName' in desc) {
3533
+ target.adjustment = {
3534
+ ...target.adjustment,
3535
+ presetKind: desc.curvesPresetKind,
3536
+ presetFileName: desc.curvesPresetFileName,
3537
+ };
3538
+ }
3539
+ else if ('mixerPresetFileName' in desc) {
3540
+ target.adjustment = {
3541
+ ...target.adjustment,
3542
+ presetKind: desc.mixerPresetKind,
3543
+ presetFileName: desc.mixerPresetFileName,
3544
+ };
3545
+ }
3546
+ else {
3547
+ target.adjustment = {
3548
+ type: 'brightness/contrast',
3549
+ brightness: desc.Brgh,
3550
+ contrast: desc.Cntr,
3551
+ meanValue: desc.means,
3552
+ useLegacy: !!desc.useLegacy,
3553
+ labColorOnly: !!desc['Lab '],
3554
+ auto: !!desc.Auto,
3555
+ };
3556
+ }
3557
+ skipBytes(reader, left());
3558
+ }, (writer, target) => {
3559
+ const info = target.adjustment;
3560
+ if (info.type === 'levels' || info.type === 'exposure' || info.type === 'hue/saturation') {
3561
+ const desc = {
3562
+ Vrsn: 1,
3563
+ presetKind: info.presetKind ?? 1,
3564
+ presetFileName: info.presetFileName || '',
3565
+ };
3566
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3567
+ }
3568
+ else if (info.type === 'curves') {
3569
+ const desc = {
3570
+ Vrsn: 1,
3571
+ curvesPresetKind: info.presetKind ?? 1,
3572
+ curvesPresetFileName: info.presetFileName || '',
3573
+ };
3574
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3575
+ }
3576
+ else if (info.type === 'channel mixer') {
3577
+ const desc = {
3578
+ Vrsn: 1,
3579
+ mixerPresetKind: info.presetKind ?? 1,
3580
+ mixerPresetFileName: info.presetFileName || '',
3581
+ };
3582
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3583
+ }
3584
+ else if (info.type === 'brightness/contrast') {
3585
+ const desc = {
3586
+ Vrsn: 1,
3587
+ Brgh: info.brightness || 0,
3588
+ Cntr: info.contrast || 0,
3589
+ means: info.meanValue ?? 127,
3590
+ 'Lab ': !!info.labColorOnly,
3591
+ useLegacy: !!info.useLegacy,
3592
+ Auto: !!info.auto,
3593
+ };
3594
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3595
+ }
3596
+ else {
3597
+ throw new Error('Unhandled CgEd case');
3598
+ }
3599
+ });
3600
+ function getTextLayersSortedByIndex(psd) {
3601
+ const layers = [];
3602
+ function collect(layer) {
3603
+ if (layer.children) {
3604
+ for (const child of layer.children) {
3605
+ if (child.text?.index !== undefined) {
3606
+ layers[child.text.index] = child;
3607
+ }
3608
+ collect(child);
3609
+ }
3610
+ }
3611
+ }
3612
+ collect(psd);
3613
+ return layers;
3614
+ }
3615
+ addHandler('Txt2', hasKey('engineData'), (reader, target, left, psd) => {
3616
+ const data = readBytes(reader, left());
3617
+ target.engineData = fromByteArray(data);
3618
+ const layersByIndex = getTextLayersSortedByIndex(psd);
3619
+ const engineData = parseEngineData(data);
3620
+ const engineData2 = decodeEngineData2(engineData);
3621
+ const TextFrameSet = engineData2.ResourceDict.TextFrameSet;
3622
+ if (TextFrameSet) {
3623
+ for (let i = 0; i < TextFrameSet.length; i++) {
3624
+ const layer = layersByIndex[i];
3625
+ if (TextFrameSet[i].path && layer?.text) {
3626
+ layer.text.textPath = TextFrameSet[i].path;
3627
+ }
3628
+ }
3629
+ }
3630
+ // console.log(require('util').inspect(engineData, false, 99, true));
3631
+ // require('fs').writeFileSync('test_data.bin', data);
3632
+ // require('fs').writeFileSync('test_data.txt', require('util').inspect(engineData, false, 99, false), 'utf8');
3633
+ // require('fs').writeFileSync('test_data.json', JSON.stringify(engineData2, null, 2), 'utf8');
3634
+ }, (writer, target) => {
3635
+ const buffer = toByteArray(target.engineData);
3636
+ writeBytes(writer, buffer);
3637
+ });
3638
+ addHandler('FEid', hasKey('filterEffectsMasks'), (reader, target, leftBytes) => {
3639
+ const version = readInt32(reader);
3640
+ if (version < 1 || version > 3)
3641
+ throw new Error(`Invalid filterEffects version ${version}`);
3642
+ target.filterEffectsMasks = [];
3643
+ while (leftBytes() > 8) {
3644
+ if (readUint32(reader))
3645
+ throw new Error('filterEffects: 64 bit length is not supported');
3646
+ const length = readUint32(reader);
3647
+ const end = reader.offset + length;
3648
+ const id = readPascalString(reader, 1);
3649
+ const effectVersion = readInt32(reader);
3650
+ if (effectVersion !== 1)
3651
+ throw new Error(`Invalid filterEffect version ${effectVersion}`);
3652
+ if (readUint32(reader))
3653
+ throw new Error('filterEffect: 64 bit length is not supported');
3654
+ /*const effectLength =*/ readUint32(reader);
3655
+ // const endOfEffect = reader.offset + effectLength;
3656
+ const top = readInt32(reader);
3657
+ const left = readInt32(reader);
3658
+ const bottom = readInt32(reader);
3659
+ const right = readInt32(reader);
3660
+ const depth = readInt32(reader);
3661
+ const maxChannels = readInt32(reader);
3662
+ const channels = [];
3663
+ // 0 -> R, 1 -> G, 2 -> B, 25 -> A
3664
+ for (let i = 0; i < (maxChannels + 2); i++) { // channels + user mask + sheet mask
3665
+ const exists = readInt32(reader);
3666
+ if (exists) {
3667
+ if (readUint32(reader))
3668
+ throw new Error('filterEffect: 64 bit length is not supported');
3669
+ const channelLength = readUint32(reader);
3670
+ if (!channelLength)
3671
+ throw new Error('filterEffect: Empty channel');
3672
+ const compressionMode = readUint16(reader);
3673
+ const data = readBytes(reader, channelLength - 2);
3674
+ channels.push({ compressionMode, data });
3675
+ }
3676
+ else {
3677
+ channels.push(undefined);
3678
+ }
3679
+ }
3680
+ target.filterEffectsMasks.push({ id, top, left, bottom, right, depth, channels });
3681
+ if (reader.offset < end && readUint8(reader)) {
3682
+ const top = readInt32(reader);
3683
+ const left = readInt32(reader);
3684
+ const bottom = readInt32(reader);
3685
+ const right = readInt32(reader);
3686
+ if (readUint32(reader))
3687
+ throw new Error('filterEffect: 64 bit length is not supported');
3688
+ const extraLength = readUint32(reader);
3689
+ const compressionMode = readUint16(reader);
3690
+ const data = readBytes(reader, extraLength - 2);
3691
+ target.filterEffectsMasks[target.filterEffectsMasks.length - 1].extra = { top, left, bottom, right, compressionMode, data };
3692
+ }
3693
+ reader.offset = end;
3694
+ let len = length;
3695
+ while (len % 4) {
3696
+ reader.offset++;
3697
+ len++;
3698
+ }
3699
+ }
3700
+ }, (writer, target) => {
3701
+ writeInt32(writer, 3); // version
3702
+ for (const mask of target.filterEffectsMasks) {
3703
+ writeUint32(writer, 0);
3704
+ writeUint32(writer, 0);
3705
+ const lengthOffset = writer.offset;
3706
+ writePascalString(writer, mask.id, 1);
3707
+ writeInt32(writer, 1); // version
3708
+ writeUint32(writer, 0);
3709
+ writeUint32(writer, 0);
3710
+ const length2Offset = writer.offset;
3711
+ writeInt32(writer, mask.top);
3712
+ writeInt32(writer, mask.left);
3713
+ writeInt32(writer, mask.bottom);
3714
+ writeInt32(writer, mask.right);
3715
+ writeInt32(writer, mask.depth);
3716
+ const maxChannels = Math.max(0, mask.channels.length - 2);
3717
+ writeInt32(writer, maxChannels);
3718
+ for (let i = 0; i < (maxChannels + 2); i++) {
3719
+ const channel = mask.channels[i];
3720
+ writeInt32(writer, channel ? 1 : 0);
3721
+ if (channel) {
3722
+ writeUint32(writer, 0);
3723
+ writeUint32(writer, channel.data.length + 2);
3724
+ writeUint16(writer, channel.compressionMode);
3725
+ writeBytes(writer, channel.data);
3726
+ }
3727
+ }
3728
+ writer.view.setUint32(length2Offset - 4, writer.offset - length2Offset, false);
3729
+ const extra = target.filterEffectsMasks[target.filterEffectsMasks.length - 1]?.extra;
3730
+ if (extra) {
3731
+ writeUint8(writer, 1);
3732
+ writeInt32(writer, extra.top);
3733
+ writeInt32(writer, extra.left);
3734
+ writeInt32(writer, extra.bottom);
3735
+ writeInt32(writer, extra.right);
3736
+ writeUint32(writer, 0);
3737
+ writeUint32(writer, extra.data.byteLength + 2);
3738
+ writeUint16(writer, extra.compressionMode);
3739
+ writeBytes(writer, extra.data);
3740
+ }
3741
+ let length = writer.offset - lengthOffset;
3742
+ writer.view.setUint32(lengthOffset - 4, length, false);
3743
+ while (length % 4) {
3744
+ writeZeros(writer, 1);
3745
+ length++;
3746
+ }
3747
+ }
3748
+ });
3749
+ addHandlerAlias('FXid', 'FEid');
3750
+ addHandler('FMsk', hasKey('filterMask'), (reader, target) => {
3751
+ target.filterMask = {
3752
+ colorSpace: readColor(reader),
3753
+ opacity: readUint16(reader) / 0xff,
3754
+ };
3755
+ }, (writer, target) => {
3756
+ writeColor(writer, target.filterMask.colorSpace);
3757
+ writeUint16(writer, clamp(target.filterMask.opacity ?? 1, 0, 1) * 0xff);
3758
+ });
3759
+ addHandler('artd', // document-wide artboard info
3760
+ // document-wide artboard info
3761
+ target => target.artboards !== undefined, (reader, target, left) => {
3762
+ const desc = readVersionAndDescriptor(reader);
3763
+ target.artboards = {
3764
+ count: desc['Cnt '],
3765
+ autoExpandOffset: { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc },
3766
+ origin: { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc },
3767
+ autoExpandEnabled: desc.autoExpandEnabled,
3768
+ autoNestEnabled: desc.autoNestEnabled,
3769
+ autoPositionEnabled: desc.autoPositionEnabled,
3770
+ shrinkwrapOnSaveEnabled: !!desc.shrinkwrapOnSaveEnabled,
3771
+ docDefaultNewArtboardBackgroundColor: parseColor(desc.docDefaultNewArtboardBackgroundColor),
3772
+ docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
3773
+ };
3774
+ skipBytes(reader, left());
3775
+ }, (writer, target) => {
3776
+ const artb = target.artboards;
3777
+ const desc = {
3778
+ 'Cnt ': artb.count,
3779
+ autoExpandOffset: artb.autoExpandOffset ? { Hrzn: artb.autoExpandOffset.horizontal, Vrtc: artb.autoExpandOffset.vertical } : { Hrzn: 0, Vrtc: 0 },
3780
+ origin: artb.origin ? { Hrzn: artb.origin.horizontal, Vrtc: artb.origin.vertical } : { Hrzn: 0, Vrtc: 0 },
3781
+ autoExpandEnabled: artb.autoExpandEnabled ?? true,
3782
+ autoNestEnabled: artb.autoNestEnabled ?? true,
3783
+ autoPositionEnabled: artb.autoPositionEnabled ?? true,
3784
+ shrinkwrapOnSaveEnabled: artb.shrinkwrapOnSaveEnabled ?? true,
3785
+ docDefaultNewArtboardBackgroundColor: serializeColor(artb.docDefaultNewArtboardBackgroundColor),
3786
+ docDefaultNewArtboardBackgroundType: artb.docDefaultNewArtboardBackgroundType ?? 1,
3787
+ };
3788
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'artd');
3789
+ });
3790
+ export function hasMultiEffects(effects) {
3791
+ return Object.keys(effects).map(key => effects[key]).some(v => Array.isArray(v) && v.length > 1);
3792
+ }
3793
+ addHandler('lfx2', target => target.effects !== undefined && !hasMultiEffects(target.effects), (reader, target, left) => {
3794
+ const version = readUint32(reader);
3795
+ if (version !== 0)
3796
+ throw new Error(`Invalid lfx2 version`);
3797
+ const desc = readVersionAndDescriptor(reader);
3798
+ // console.log('READ', require('util').inspect(desc, false, 99, true));
3799
+ // TODO: don't discard if we got it from lmfx
3800
+ // discard if read in 'lrFX' section
3801
+ target.effects = parseEffects(desc, !!reader.logMissingFeatures);
3802
+ skipBytes(reader, left());
3803
+ }, (writer, target, _, options) => {
3804
+ const desc = serializeEffects(target.effects, !!options.logMissingFeatures, true);
3805
+ // console.log('WRITE', require('util').inspect(desc, false, 99, true));
3806
+ writeUint32(writer, 0); // version
3807
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3808
+ });
3809
+ addHandler('cinf', hasKey('compositorUsed'), (reader, target, left) => {
3810
+ const desc = readVersionAndDescriptor(reader);
3811
+ // console.log(require('util').inspect(desc, false, 99, true));
3812
+ function enumValue(desc) {
3813
+ return desc.split('.')[1];
3814
+ }
3815
+ target.compositorUsed = {
3816
+ description: desc.description,
3817
+ reason: desc.reason,
3818
+ engine: enumValue(desc.Engn),
3819
+ };
3820
+ if (desc.Vrsn)
3821
+ target.compositorUsed.version = desc.Vrsn;
3822
+ if (desc.psVersion)
3823
+ target.compositorUsed.photoshopVersion = desc.psVersion;
3824
+ if (desc.enableCompCore)
3825
+ target.compositorUsed.enableCompCore = enumValue(desc.enableCompCore);
3826
+ if (desc.enableCompCoreGPU)
3827
+ target.compositorUsed.enableCompCoreGPU = enumValue(desc.enableCompCoreGPU);
3828
+ if (desc.enableCompCoreThreads)
3829
+ target.compositorUsed.enableCompCoreThreads = enumValue(desc.enableCompCoreThreads);
3830
+ if (desc.compCoreSupport)
3831
+ target.compositorUsed.compCoreSupport = enumValue(desc.compCoreSupport);
3832
+ if (desc.compCoreGPUSupport)
3833
+ target.compositorUsed.compCoreGPUSupport = enumValue(desc.compCoreGPUSupport);
3834
+ skipBytes(reader, left());
3835
+ }, (writer, target) => {
3836
+ const cinf = target.compositorUsed;
3837
+ const desc = {
3838
+ Vrsn: cinf.version || { major: 1, minor: 0, fix: 0 },
3839
+ };
3840
+ if (cinf.photoshopVersion)
3841
+ desc.psVersion = cinf.photoshopVersion;
3842
+ desc.description = cinf.description;
3843
+ desc.reason = cinf.reason;
3844
+ desc.Engn = `Engn.${cinf.engine}`;
3845
+ if (cinf.enableCompCore)
3846
+ desc.enableCompCore = `enable.${cinf.enableCompCore}`;
3847
+ if (cinf.enableCompCoreGPU)
3848
+ desc.enableCompCoreGPU = `enable.${cinf.enableCompCoreGPU}`;
3849
+ if (cinf.enableCompCoreThreads)
3850
+ desc.enableCompCoreThreads = `enable.${cinf.enableCompCoreThreads}`;
3851
+ if (cinf.compCoreSupport)
3852
+ desc.compCoreSupport = `reason.${cinf.compCoreSupport}`;
3853
+ if (cinf.compCoreGPUSupport)
3854
+ desc.compCoreGPUSupport = `reason.${cinf.compCoreGPUSupport}`;
3855
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3856
+ });
3857
+ // extension settings ?, ignore it
3858
+ addHandler('extn', target => target._extn !== undefined, (reader, target) => {
3859
+ const desc = readVersionAndDescriptor(reader);
3860
+ // console.log(require('util').inspect(desc, false, 99, true));
3861
+ if (MOCK_HANDLERS)
3862
+ target._extn = desc;
3863
+ }, (writer, target) => {
3864
+ // TODO: need to add correct types for desc fields (resources/src.psd)
3865
+ if (MOCK_HANDLERS)
3866
+ writeVersionAndDescriptor(writer, '', 'null', target._extn);
3867
+ });
3868
+ addHandler('iOpa', hasKey('fillOpacity'), (reader, target) => {
3869
+ target.fillOpacity = readUint8(reader) / 0xff;
3870
+ skipBytes(reader, 3);
3871
+ }, (writer, target) => {
3872
+ writeUint8(writer, target.fillOpacity * 0xff);
3873
+ writeZeros(writer, 3);
3874
+ });
3875
+ addHandler('brst', hasKey('channelBlendingRestrictions'), (reader, target, left) => {
3876
+ target.channelBlendingRestrictions = [];
3877
+ while (left() > 4) {
3878
+ target.channelBlendingRestrictions.push(readInt32(reader));
3879
+ }
3880
+ }, (writer, target) => {
3881
+ for (const channel of target.channelBlendingRestrictions) {
3882
+ writeInt32(writer, channel);
3883
+ }
3884
+ });
3885
+ addHandler('tsly', hasKey('transparencyShapesLayer'), (reader, target) => {
3886
+ target.transparencyShapesLayer = !!readUint8(reader);
3887
+ skipBytes(reader, 3);
3888
+ }, (writer, target) => {
3889
+ writeUint8(writer, target.transparencyShapesLayer ? 1 : 0);
3890
+ writeZeros(writer, 3);
3891
+ });