ag-psd 32.0.0 → 34.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  25. package/CHANGELOG.md +100 -0
  26. package/LICENSE +5 -0
  27. package/README.md +4 -7
  28. package/README_PSD.md +261 -84
  29. package/TODO.md +13 -84
  30. package/dist/abr.d.ts +11 -4
  31. package/dist/abr.js +362 -230
  32. package/dist/abr.js.map +1 -1
  33. package/dist/additionalInfo.d.ts +56 -2
  34. package/dist/additionalInfo.js +828 -361
  35. package/dist/additionalInfo.js.map +1 -1
  36. package/dist/ase.d.ts +6 -1
  37. package/dist/ase.js +61 -61
  38. package/dist/ase.js.map +1 -1
  39. package/dist/bundle.js +3321 -1364
  40. package/dist/descriptor.d.ts +28 -13
  41. package/dist/descriptor.js +295 -241
  42. package/dist/descriptor.js.map +1 -1
  43. package/dist/helpers.d.ts +7 -1
  44. package/dist/helpers.js +246 -4
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/imageResources.d.ts +15 -3
  47. package/dist/imageResources.js +586 -144
  48. package/dist/imageResources.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/jpeg.js +8 -8
  53. package/dist/jpeg.js.map +1 -1
  54. package/dist/psd.d.ts +292 -19
  55. package/dist/psd.js.map +1 -1
  56. package/dist/psdReader.d.ts +10 -0
  57. package/dist/psdReader.js +424 -216
  58. package/dist/psdReader.js.map +1 -1
  59. package/dist/psdWriter.d.ts +2 -0
  60. package/dist/psdWriter.js +373 -113
  61. package/dist/psdWriter.js.map +1 -1
  62. package/dist/text.d.ts +32 -1
  63. package/dist/text.js +75 -6
  64. package/dist/text.js.map +1 -1
  65. package/dist/utf8.js +0 -3
  66. package/dist/utf8.js.map +1 -1
  67. package/dist-es/abr.d.ts +11 -4
  68. package/dist-es/abr.js +362 -230
  69. package/dist-es/abr.js.map +1 -1
  70. package/dist-es/additionalInfo.d.ts +56 -2
  71. package/dist-es/additionalInfo.js +828 -361
  72. package/dist-es/additionalInfo.js.map +1 -1
  73. package/dist-es/ase.d.ts +6 -1
  74. package/dist-es/ase.js +61 -61
  75. package/dist-es/ase.js.map +1 -1
  76. package/dist-es/descriptor.d.ts +28 -13
  77. package/dist-es/descriptor.js +295 -241
  78. package/dist-es/descriptor.js.map +1 -1
  79. package/dist-es/helpers.d.ts +7 -1
  80. package/dist-es/helpers.js +246 -4
  81. package/dist-es/helpers.js.map +1 -1
  82. package/dist-es/imageResources.d.ts +15 -3
  83. package/dist-es/imageResources.js +586 -144
  84. package/dist-es/imageResources.js.map +1 -1
  85. package/dist-es/index.d.ts +1 -0
  86. package/dist-es/index.js +1 -0
  87. package/dist-es/index.js.map +1 -1
  88. package/dist-es/jpeg.js +8 -8
  89. package/dist-es/jpeg.js.map +1 -1
  90. package/dist-es/psd.d.ts +292 -19
  91. package/dist-es/psd.js.map +1 -1
  92. package/dist-es/psdReader.d.ts +10 -0
  93. package/dist-es/psdReader.js +424 -216
  94. package/dist-es/psdReader.js.map +1 -1
  95. package/dist-es/psdWriter.d.ts +2 -0
  96. package/dist-es/psdWriter.js +373 -113
  97. package/dist-es/psdWriter.js.map +1 -1
  98. package/dist-es/text.d.ts +32 -1
  99. package/dist-es/text.js +75 -6
  100. package/dist-es/text.js.map +1 -1
  101. package/dist-es/utf8.js +0 -3
  102. package/dist-es/utf8.js.map +1 -1
  103. package/package.json +2 -2
  104. package/perf.js +312 -9
  105. package/src/abr.ts +382 -235
  106. package/src/additionalInfo.ts +1071 -482
  107. package/src/ase.ts +66 -61
  108. package/src/descriptor.ts +291 -242
  109. package/src/helpers.ts +266 -3
  110. package/src/imageResources.ts +736 -169
  111. package/src/index.ts +1 -0
  112. package/src/jpeg.ts +8 -8
  113. package/src/psd.ts +271 -32
  114. package/src/psdReader.ts +440 -221
  115. package/src/psdWriter.ts +402 -110
  116. package/src/text.ts +93 -7
  117. package/src/utf8.ts +0 -4
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.booleanOperations = exports.infoHandlersMap = exports.infoHandlers = void 0;
4
4
  exports.readBezierKnot = readBezierKnot;
5
5
  exports.readVectorMask = readVectorMask;
6
+ exports.writeVectorMask = writeVectorMask;
7
+ exports.parseKeyDescriptorList = parseKeyDescriptorList;
8
+ exports.serializeKeyDescriptorList = serializeKeyDescriptorList;
6
9
  exports.hasMultiEffects = hasMultiEffects;
7
10
  const base64_js_1 = require("base64-js");
8
11
  const effectsHelpers_1 = require("./effectsHelpers");
@@ -33,6 +36,7 @@ function readLength64(reader) {
33
36
  return (0, psdReader_1.readUint32)(reader);
34
37
  }
35
38
  addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
39
+ var _a;
36
40
  if ((0, psdReader_1.readInt16)(reader) !== 1)
37
41
  throw new Error(`Invalid TySh version`);
38
42
  const transform = [];
@@ -79,7 +83,8 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
79
83
  // require('fs').writeFileSync('before.txt', require('util').inspect(before, false, 99, false), 'utf8');
80
84
  // require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
81
85
  // console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
82
- target.text = Object.assign(Object.assign({}, target.text), textData);
86
+ // descriptor anti-alias value is more reliable than the one from engine data
87
+ target.text = Object.assign(Object.assign(Object.assign({}, target.text), textData), { antiAlias: (_a = target.text.antiAlias) !== null && _a !== void 0 ? _a : textData.antiAlias });
83
88
  // console.log(require('util').inspect(target.text, false, 99, true));
84
89
  }
85
90
  (0, psdReader_1.skipBytes)(reader, leftBytes());
@@ -103,11 +108,77 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
103
108
  (0, psdWriter_1.writeFloat32)(writer, (_d = text.bottom) !== null && _d !== void 0 ? _d : 0);
104
109
  // writeZeros(writer, 2);
105
110
  });
111
+ // type tool info from Photoshop 5.0 and 5.5, converted to regular text data (written as 'TySh')
112
+ addHandler('tySh', () => false, (reader, target, left) => {
113
+ if ((0, psdReader_1.readInt16)(reader) !== 1)
114
+ throw new Error(`Invalid tySh version`);
115
+ const transform = [];
116
+ for (let i = 0; i < 6; i++)
117
+ transform.push((0, psdReader_1.readFloat64)(reader));
118
+ if ((0, psdReader_1.readInt16)(reader) !== 6)
119
+ throw new Error(`Invalid tySh font version`);
120
+ const data = { transform, faces: [], styles: [], lines: [], color: { r: 0, g: 0, b: 0 }, antiAlias: false };
121
+ const facesCount = (0, psdReader_1.readInt16)(reader);
122
+ for (let i = 0; i < facesCount; i++) {
123
+ const mark = (0, psdReader_1.readInt16)(reader);
124
+ (0, psdReader_1.readInt32)(reader); // font type
125
+ const name = (0, psdReader_1.readPascalString)(reader, 1);
126
+ const familyName = (0, psdReader_1.readPascalString)(reader, 1);
127
+ const styleName = (0, psdReader_1.readPascalString)(reader, 1);
128
+ const script = (0, psdReader_1.readInt16)(reader);
129
+ const axesCount = (0, psdReader_1.readInt32)(reader);
130
+ if (axesCount < 0 || axesCount * 4 > left())
131
+ throw new Error(`Invalid tySh design axes count`);
132
+ (0, psdReader_1.skipBytes)(reader, axesCount * 4);
133
+ data.faces.push({ mark, name, familyName, styleName, script });
134
+ }
135
+ const stylesCount = (0, psdReader_1.readInt16)(reader);
136
+ for (let i = 0; i < stylesCount; i++) {
137
+ const mark = (0, psdReader_1.readInt16)(reader);
138
+ const faceMark = (0, psdReader_1.readInt16)(reader);
139
+ const size = (0, psdReader_1.readInt32)(reader);
140
+ const tracking = (0, psdReader_1.readInt32)(reader);
141
+ const kerning = (0, psdReader_1.readInt32)(reader);
142
+ const leading = (0, psdReader_1.readInt32)(reader);
143
+ const baselineShift = (0, psdReader_1.readInt32)(reader);
144
+ const autoKerning = !!(0, psdReader_1.readUint8)(reader);
145
+ (0, psdReader_1.readUint8)(reader); // rotate
146
+ data.styles.push({ mark, faceMark, size, tracking, kerning, leading, baselineShift, autoKerning });
147
+ }
148
+ (0, psdReader_1.readInt16)(reader); // type (0 = point, 1 = paragraph)
149
+ (0, psdReader_1.readInt32)(reader); // scaling factor
150
+ (0, psdReader_1.readInt32)(reader); // character count
151
+ (0, psdReader_1.readInt32)(reader); // horizontal placement
152
+ (0, psdReader_1.readInt32)(reader); // vertical placement
153
+ (0, psdReader_1.readInt32)(reader); // select start
154
+ (0, psdReader_1.readInt32)(reader); // select end
155
+ const linesCount = (0, psdReader_1.readInt16)(reader);
156
+ for (let i = 0; i < linesCount; i++) {
157
+ const charCount = (0, psdReader_1.readInt32)(reader);
158
+ const orientation = (0, psdReader_1.readInt16)(reader);
159
+ const alignment = (0, psdReader_1.readInt16)(reader);
160
+ if (charCount < 0 || charCount * 4 > left())
161
+ throw new Error(`Invalid tySh character count`);
162
+ const chars = [];
163
+ for (let j = 0; j < charCount; j++) {
164
+ const code = (0, psdReader_1.readUint16)(reader);
165
+ const style = (0, psdReader_1.readInt16)(reader);
166
+ chars.push({ code, style });
167
+ }
168
+ data.lines.push({ orientation, alignment, chars });
169
+ }
170
+ data.color = (0, psdReader_1.readColor)(reader);
171
+ data.antiAlias = !!(0, psdReader_1.readUint8)(reader);
172
+ if (!target.text)
173
+ target.text = (0, text_1.decodeLegacyTextData)(data);
174
+ (0, psdReader_1.skipBytes)(reader, left());
175
+ }, () => { });
106
176
  // vector fills
107
177
  addHandler('SoCo', target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
108
- target.vectorFill.type === 'color', (reader, target) => {
178
+ target.vectorFill.type === 'color', (reader, target, left) => {
109
179
  const descriptor = (0, descriptor_1.readVersionAndDescriptor)(reader);
110
180
  target.vectorFill = (0, descriptor_1.parseVectorContent)(descriptor);
181
+ (0, psdReader_1.skipBytes)(reader, left());
111
182
  }, (writer, target) => {
112
183
  const { descriptor } = (0, descriptor_1.serializeVectorContent)(target.vectorFill);
113
184
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', descriptor);
@@ -209,25 +280,8 @@ function readVectorMask(reader, vectorMask, width, height, size) {
209
280
  }
210
281
  return paths;
211
282
  }
212
- addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
213
- if ((0, psdReader_1.readUint32)(reader) !== 3)
214
- throw new Error('Invalid vmsk version');
215
- target.vectorMask = { paths: [] };
216
- const vectorMask = target.vectorMask;
217
- const flags = (0, psdReader_1.readUint32)(reader);
218
- vectorMask.invert = (flags & 1) !== 0;
219
- vectorMask.notLink = (flags & 2) !== 0;
220
- vectorMask.disable = (flags & 4) !== 0;
221
- readVectorMask(reader, vectorMask, width, height, left());
222
- // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
223
- (0, psdReader_1.skipBytes)(reader, left());
224
- }, (writer, target, { width, height }) => {
225
- const vectorMask = target.vectorMask;
226
- const flags = (vectorMask.invert ? 1 : 0) |
227
- (vectorMask.notLink ? 2 : 0) |
228
- (vectorMask.disable ? 4 : 0);
229
- (0, psdWriter_1.writeUint32)(writer, 3); // version
230
- (0, psdWriter_1.writeUint32)(writer, flags);
283
+ // componentIndices - write index of path component for each subpath (Photoshop writes them for layer vector masks)
284
+ function writeVectorMask(writer, vectorMask, width, height, componentIndices = false) {
231
285
  // initial entry
232
286
  (0, psdWriter_1.writeUint16)(writer, 6);
233
287
  (0, psdWriter_1.writeZeros)(writer, 24);
@@ -244,12 +298,18 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
244
298
  (0, psdWriter_1.writeUint16)(writer, 8);
245
299
  (0, psdWriter_1.writeUint16)(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
246
300
  (0, psdWriter_1.writeZeros)(writer, 22);
301
+ let componentIndex = -1;
247
302
  for (const path of vectorMask.paths) {
303
+ // subpaths without operation are part of the same path component as previous subpath
304
+ if (path.operation || componentIndex < 0)
305
+ componentIndex++;
248
306
  (0, psdWriter_1.writeUint16)(writer, path.open ? 3 : 0);
249
307
  (0, psdWriter_1.writeUint16)(writer, path.knots.length);
250
308
  (0, psdWriter_1.writeUint16)(writer, path.operation ? exports.booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
251
309
  (0, psdWriter_1.writeUint16)(writer, path.fillRule === 'non-zero' ? 2 : 1);
252
- (0, psdWriter_1.writeZeros)(writer, 18); // TODO: these are sometimes non-zero
310
+ (0, psdWriter_1.writeZeros)(writer, 4);
311
+ (0, psdWriter_1.writeUint32)(writer, componentIndices ? componentIndex : 0);
312
+ (0, psdWriter_1.writeZeros)(writer, 10);
253
313
  const linkedKnot = path.open ? 4 : 1;
254
314
  const unlinkedKnot = path.open ? 5 : 2;
255
315
  for (const { linked, points } of path.knots) {
@@ -257,8 +317,29 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
257
317
  writeBezierKnot(writer, points, width, height);
258
318
  }
259
319
  }
320
+ }
321
+ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
322
+ if ((0, psdReader_1.readUint32)(reader) !== 3)
323
+ throw new Error('Invalid vmsk version');
324
+ target.vectorMask = { paths: [] };
325
+ const vectorMask = target.vectorMask;
326
+ const flags = (0, psdReader_1.readUint32)(reader);
327
+ vectorMask.invert = (flags & 1) !== 0;
328
+ vectorMask.notLink = (flags & 2) !== 0;
329
+ vectorMask.disable = (flags & 4) !== 0;
330
+ readVectorMask(reader, vectorMask, width, height, left());
331
+ // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
332
+ (0, psdReader_1.skipBytes)(reader, left());
333
+ }, (writer, target, { width, height }) => {
334
+ const vectorMask = target.vectorMask;
335
+ const flags = (vectorMask.invert ? 1 : 0) |
336
+ (vectorMask.notLink ? 2 : 0) |
337
+ (vectorMask.disable ? 4 : 0);
338
+ (0, psdWriter_1.writeUint32)(writer, 3); // version
339
+ (0, psdWriter_1.writeUint32)(writer, flags);
340
+ writeVectorMask(writer, vectorMask, width, height, true);
260
341
  });
261
- // TODO: need to write vmsk if has outline ?
342
+ // written instead of 'vmsk' for layers with vector stroke ('vscg' section)
262
343
  addHandlerAlias('vsms', 'vmsk');
263
344
  // addHandlerAlias('vmsk', 'vsms');
264
345
  addHandler('vowv', // something with vectors?
@@ -267,12 +348,8 @@ hasKey('vowv'), (reader, target) => {
267
348
  }, (writer, target) => {
268
349
  (0, psdWriter_1.writeUint32)(writer, target.vowv);
269
350
  });
270
- addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
271
- if ((0, psdReader_1.readInt32)(reader) !== 1)
272
- throw new Error(`Invalid vogk version`);
273
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
274
- // console.log(require('util').inspect(desc, false, 99, true));
275
- target.vectorOrigination = { keyDescriptorList: [] };
351
+ function parseKeyDescriptorList(desc) {
352
+ const keyDescriptorList = [];
276
353
  for (const i of desc.keyDescriptorList) {
277
354
  const item = {};
278
355
  if (i.keyShapeInvalidated != null)
@@ -311,15 +388,14 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
311
388
  if (trnf) {
312
389
  item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
313
390
  }
314
- target.vectorOrigination.keyDescriptorList.push(item);
391
+ keyDescriptorList.push(item);
315
392
  }
316
- (0, psdReader_1.skipBytes)(reader, left());
317
- }, (writer, target) => {
318
- target;
319
- const orig = target.vectorOrigination;
393
+ return keyDescriptorList;
394
+ }
395
+ function serializeKeyDescriptorList(keyDescriptorList) {
320
396
  const desc = { keyDescriptorList: [] };
321
- for (let i = 0; i < orig.keyDescriptorList.length; i++) {
322
- const item = orig.keyDescriptorList[i];
397
+ for (let i = 0; i < keyDescriptorList.length; i++) {
398
+ const item = keyDescriptorList[i];
323
399
  desc.keyDescriptorList.push({}); // we're adding keyOriginIndex at the end
324
400
  const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
325
401
  if (item.keyOriginType != null)
@@ -370,20 +446,31 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
370
446
  out.keyShapeInvalidated = item.keyShapeInvalidated;
371
447
  out.keyOriginIndex = i;
372
448
  }
449
+ return desc;
450
+ }
451
+ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
452
+ if ((0, psdReader_1.readInt32)(reader) !== 1)
453
+ throw new Error(`Invalid vogk version`);
454
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
455
+ // console.log(require('util').inspect(desc, false, 99, true));
456
+ target.vectorOrigination = { keyDescriptorList: parseKeyDescriptorList(desc) };
457
+ (0, psdReader_1.skipBytes)(reader, left());
458
+ }, (writer, target) => {
459
+ const desc = serializeKeyDescriptorList(target.vectorOrigination.keyDescriptorList);
373
460
  (0, psdWriter_1.writeInt32)(writer, 1); // version
374
461
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
375
462
  });
376
- addHandler('lmfx', target => target.effects !== undefined && hasMultiEffects(target.effects), (reader, target, left) => {
463
+ addHandler('lmfx', target => target.effects !== undefined && !isGroupLayer(target) && hasMultiEffects(target.effects), (reader, target, left) => {
377
464
  const version = (0, psdReader_1.readUint32)(reader);
378
465
  if (version !== 0)
379
466
  throw new Error('Invalid lmfx version');
380
467
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
381
468
  // console.log('READ', require('util').inspect(desc, false, 99, true));
382
469
  // discard if read in 'lrFX' or 'lfx2' section
383
- target.effects = (0, descriptor_1.parseEffects)(desc, !!reader.logMissingFeatures);
470
+ target.effects = (0, descriptor_1.parseEffects)(desc, reader);
384
471
  (0, psdReader_1.skipBytes)(reader, left());
385
- }, (writer, target, _, options) => {
386
- const desc = (0, descriptor_1.serializeEffects)(target.effects, !!options.logMissingFeatures, true);
472
+ }, (writer, target) => {
473
+ const desc = (0, descriptor_1.serializeEffects)(target.effects, writer, true);
387
474
  // console.log('WRITE', require('util').inspect(desc, false, 99, true));
388
475
  (0, psdWriter_1.writeUint32)(writer, 0); // version
389
476
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
@@ -449,8 +536,12 @@ addHandler('lyid', target => target.id !== undefined || layerNeedsId(target), (r
449
536
  let id;
450
537
  if (target.id !== undefined) {
451
538
  id = target.id;
452
- while (options.layerIds.has(id))
453
- id += 100; // make sure we don't have duplicate layer ids
539
+ if (options.layerIds.has(id)) {
540
+ // make sure we don't have duplicate layer ids (and don't take id explicitly set on another layer)
541
+ const explicitIds = getExplicitLayerIds(psd, options);
542
+ while (options.layerIds.has(id) || explicitIds.has(id))
543
+ id += 100;
544
+ }
454
545
  }
455
546
  else {
456
547
  // generate id for layers that don't have it, if it's referenced by other sections
@@ -514,6 +605,58 @@ addHandler('lmgm', hasKey('layerMaskAsGlobalMask'), (reader, target) => {
514
605
  (0, psdWriter_1.writeUint8)(writer, target.layerMaskAsGlobalMask ? 1 : 0);
515
606
  (0, psdWriter_1.writeZeros)(writer, 3);
516
607
  });
608
+ addHandler('vmgm', hasKey('vectorMaskAsGlobalMask'), (reader, target) => {
609
+ target.vectorMaskAsGlobalMask = !!(0, psdReader_1.readUint8)(reader);
610
+ (0, psdReader_1.skipBytes)(reader, 3);
611
+ }, (writer, target) => {
612
+ (0, psdWriter_1.writeUint8)(writer, target.vectorMaskAsGlobalMask ? 1 : 0);
613
+ (0, psdWriter_1.writeZeros)(writer, 3);
614
+ });
615
+ addHandler('ffxi', hasKey('foreignEffectId'), (reader, target) => {
616
+ target.foreignEffectId = (0, psdReader_1.readInt32)(reader);
617
+ }, (writer, target) => {
618
+ (0, psdWriter_1.writeInt32)(writer, target.foreignEffectId);
619
+ });
620
+ addHandler('shpa', hasKey('patternData'), (reader, target, left) => {
621
+ if ((0, psdReader_1.readInt32)(reader) !== 0)
622
+ throw new Error(`Invalid shpa version`);
623
+ const count = (0, psdReader_1.readInt32)(reader);
624
+ target.patternData = [];
625
+ for (let i = 0; i < count && left() > 0; i++) {
626
+ const signature = (0, psdReader_1.readSignature)(reader);
627
+ const key = (0, psdReader_1.readSignature)(reader);
628
+ const patternsCount = (0, psdReader_1.readInt32)(reader);
629
+ const copyOnSheetDuplication = !!(0, psdReader_1.readUint8)(reader);
630
+ (0, psdReader_1.skipBytes)(reader, 3);
631
+ const patterns = [];
632
+ for (let j = 0; j < patternsCount && left() > 0; j++) {
633
+ const colorHandling = descriptor_1.patternColorHandling.decode('.' + (0, psdReader_1.readSignature)(reader));
634
+ (0, psdReader_1.readPascalString)(reader, 1); // name
635
+ const name = (0, psdReader_1.readUnicodeString)(reader);
636
+ const id = (0, psdReader_1.readPascalString)(reader, 1);
637
+ patterns.push({ colorHandling, name, id });
638
+ }
639
+ target.patternData.push({ signature, key, copyOnSheetDuplication, patterns });
640
+ }
641
+ (0, psdReader_1.skipBytes)(reader, left());
642
+ }, (writer, target) => {
643
+ const sets = target.patternData;
644
+ (0, psdWriter_1.writeInt32)(writer, 0); // version
645
+ (0, psdWriter_1.writeInt32)(writer, sets.length);
646
+ for (const set of sets) {
647
+ (0, psdWriter_1.writeSignature)(writer, set.signature);
648
+ (0, psdWriter_1.writeSignature)(writer, set.key);
649
+ (0, psdWriter_1.writeInt32)(writer, set.patterns.length);
650
+ (0, psdWriter_1.writeUint8)(writer, set.copyOnSheetDuplication ? 1 : 0);
651
+ (0, psdWriter_1.writeZeros)(writer, 3);
652
+ for (const pattern of set.patterns) {
653
+ (0, psdWriter_1.writeSignature)(writer, descriptor_1.patternColorHandling.encode(pattern.colorHandling).split('.')[1]);
654
+ (0, psdWriter_1.writePascalString)(writer, pattern.name.replace(/[^\x20-\x7e]/g, '?'), 1);
655
+ (0, psdWriter_1.writeUnicodeString)(writer, pattern.name);
656
+ (0, psdWriter_1.writePascalString)(writer, pattern.id, 1);
657
+ }
658
+ }
659
+ });
517
660
  addHandler('lspf', hasKey('protected'), (reader, target) => {
518
661
  const flags = (0, psdReader_1.readUint32)(reader);
519
662
  target.protected = {
@@ -523,12 +666,15 @@ addHandler('lspf', hasKey('protected'), (reader, target) => {
523
666
  };
524
667
  if (flags & 0x08)
525
668
  target.protected.artboards = true;
669
+ if (flags & 0x80000000)
670
+ target.protected.all = true;
526
671
  }, (writer, target) => {
527
672
  const flags = (target.protected.transparency ? 0x01 : 0) |
528
673
  (target.protected.composite ? 0x02 : 0) |
529
674
  (target.protected.position ? 0x04 : 0) |
530
- (target.protected.artboards ? 0x08 : 0);
531
- (0, psdWriter_1.writeUint32)(writer, flags);
675
+ (target.protected.artboards ? 0x08 : 0) |
676
+ (target.protected.all ? 0x80000000 : 0);
677
+ (0, psdWriter_1.writeUint32)(writer, flags >>> 0);
532
678
  });
533
679
  addHandler('lclr', hasKey('layerColor'), (reader, target) => {
534
680
  const color = (0, psdReader_1.readUint16)(reader);
@@ -539,106 +685,160 @@ addHandler('lclr', hasKey('layerColor'), (reader, target) => {
539
685
  (0, psdWriter_1.writeUint16)(writer, index === -1 ? 0 : index);
540
686
  (0, psdWriter_1.writeZeros)(writer, 6);
541
687
  });
688
+ function parseGeneratorSettings(settings) {
689
+ const result = {};
690
+ if (settings.exportAs) {
691
+ result.exportAs = {};
692
+ if (settings.exportAs.exportOption !== undefined)
693
+ result.exportAs.exportOption = settings.exportAs.exportOption;
694
+ if (settings.exportAs.scaleSettings !== undefined)
695
+ result.exportAs.scaleSettings = settings.exportAs.scaleSettings;
696
+ }
697
+ if (settings.generator_45_assets)
698
+ result.assets = settings.generator_45_assets.json;
699
+ if (settings.layerTime !== undefined)
700
+ result.layerTime = settings.layerTime;
701
+ if (settings.crema)
702
+ result.crema = settings.crema.json;
703
+ return result;
704
+ }
705
+ function serializeGeneratorSettings(generatorSettings) {
706
+ const settings = {
707
+ _name: '',
708
+ _classID: generatorSettings.assets !== undefined ? 'generatorSettings' : 'null',
709
+ };
710
+ if (generatorSettings.exportAs) {
711
+ settings.exportAs = { _name: '', _classID: 'null' };
712
+ if (generatorSettings.exportAs.exportOption !== undefined)
713
+ settings.exportAs.exportOption = generatorSettings.exportAs.exportOption;
714
+ if (generatorSettings.exportAs.scaleSettings !== undefined)
715
+ settings.exportAs.scaleSettings = generatorSettings.exportAs.scaleSettings;
716
+ }
717
+ if (generatorSettings.assets !== undefined)
718
+ settings.generator_45_assets = { _name: '', _classID: 'generatorSettings', json: generatorSettings.assets };
719
+ if (generatorSettings.layerTime !== undefined)
720
+ settings.layerTime = generatorSettings.layerTime;
721
+ if (generatorSettings.crema !== undefined)
722
+ settings.crema = { _name: '', _classID: 'null', json: generatorSettings.crema };
723
+ return { generatorSettings: settings };
724
+ }
725
+ // document (Psd object) stores generator settings in 'extn' section instead of 'shmd' section
726
+ function isDocument(target) {
727
+ return target.width !== undefined;
728
+ }
542
729
  addHandler('shmd', // Metadata setting
543
730
  // Metadata setting
544
- target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined, (reader, target, left) => {
731
+ target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined ||
732
+ (target.generatorSettings !== undefined && !isDocument(target)), (reader, target, left) => {
545
733
  const count = (0, psdReader_1.readUint32)(reader);
546
734
  for (let i = 0; i < count; i++) {
547
- (0, psdReader_1.checkSignature)(reader, '8BIM');
735
+ const signature = (0, psdReader_1.readSignature)(reader);
736
+ if (signature !== '8BIM' && signature !== '8ELE')
737
+ throw new Error(`Invalid shmd signature: '${signature}'`); // 8ELE in Photoshop Elements
548
738
  const key = (0, psdReader_1.readSignature)(reader);
549
739
  (0, psdReader_1.readUint8)(reader); // copy
550
740
  (0, psdReader_1.skipBytes)(reader, 3);
551
- (0, psdReader_1.readSection)(reader, 1, left => {
552
- if (key === 'cust') {
553
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
554
- // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
555
- if (desc.layerTime !== undefined)
556
- target.timestamp = desc.layerTime;
557
- }
558
- else if (key === 'mlst') {
559
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
560
- // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
561
- target.animationFrames = [];
562
- for (let i = 0; i < desc.LaSt.length; i++) {
563
- const f = desc.LaSt[i];
564
- const frame = { frames: f.FrLs };
565
- if (f.enab !== undefined)
566
- frame.enable = f.enab;
567
- if (f.Ofst)
568
- frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
569
- if (f.FXRf)
570
- frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
571
- if (f.Lefx)
572
- frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, !!reader.logMissingFeatures);
573
- if (f.blendOptions && f.blendOptions.Opct)
574
- frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
575
- target.animationFrames.push(frame);
741
+ const end = reader.offset + 4 + reader.view.getUint32(reader.offset);
742
+ try {
743
+ (0, psdReader_1.readSection)(reader, 1, left => {
744
+ if (key === 'cust') {
745
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
746
+ // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
747
+ if (desc.layerTime !== undefined)
748
+ target.timestamp = desc.layerTime;
576
749
  }
577
- }
578
- else if (key === 'mdyn') {
579
- // frame flags
580
- (0, psdReader_1.readUint16)(reader); // unknown
581
- const propagate = (0, psdReader_1.readUint8)(reader);
582
- const flags = (0, psdReader_1.readUint8)(reader);
583
- target.animationFrameFlags = {
584
- propagateFrameOne: !propagate,
585
- unifyLayerPosition: (flags & 1) !== 0,
586
- unifyLayerStyle: (flags & 2) !== 0,
587
- unifyLayerVisibility: (flags & 4) !== 0,
588
- };
589
- }
590
- else if (key === 'tmln') {
591
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
592
- const timeScope = desc.timeScope;
593
- // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
594
- const timeline = {
595
- start: (0, descriptor_1.frac)(timeScope.Strt),
596
- duration: (0, descriptor_1.frac)(timeScope.duration),
597
- inTime: (0, descriptor_1.frac)(timeScope.inTime),
598
- outTime: (0, descriptor_1.frac)(timeScope.outTime),
599
- autoScope: desc.autoScope,
600
- audioLevel: desc.audioLevel,
601
- };
602
- if (desc.trackList) {
603
- timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, !!reader.logMissingFeatures);
750
+ else if (key === 'mlst') {
751
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
752
+ // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
753
+ target.animationFrames = [];
754
+ for (let i = 0; i < desc.LaSt.length; i++) {
755
+ const f = desc.LaSt[i];
756
+ const frame = { frames: f.FrLs };
757
+ if (f.enab !== undefined)
758
+ frame.enable = f.enab;
759
+ if (f.Ofst)
760
+ frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
761
+ if (f.FXRf)
762
+ frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
763
+ if (f.Lefx)
764
+ frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, reader);
765
+ if (f.blendOptions && f.blendOptions.Opct)
766
+ frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
767
+ target.animationFrames.push(frame);
768
+ }
604
769
  }
605
- target.timeline = timeline;
606
- // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
607
- }
608
- else if (key === 'cmls') {
609
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
610
- // console.log('cmls', require('util').inspect(desc, false, 99, true));
611
- target.comps = {
612
- settings: [],
613
- };
614
- if (desc.origFXRefPoint)
615
- target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
616
- for (const item of desc.layerSettings) {
617
- target.comps.settings.push({ compList: item.compList });
618
- const t = target.comps.settings[target.comps.settings.length - 1];
619
- if ('enab' in item)
620
- t.enabled = item.enab;
621
- if (item.Ofst)
622
- t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
623
- if (item.FXRefPoint)
624
- t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
770
+ else if (key === 'mdyn') {
771
+ // frame flags
772
+ (0, psdReader_1.readUint16)(reader); // unknown
773
+ const propagate = (0, psdReader_1.readUint8)(reader);
774
+ const flags = (0, psdReader_1.readUint8)(reader);
775
+ target.animationFrameFlags = {
776
+ propagateFrameOne: !propagate,
777
+ unifyLayerPosition: (flags & 1) !== 0,
778
+ unifyLayerStyle: (flags & 2) !== 0,
779
+ unifyLayerVisibility: (flags & 4) !== 0,
780
+ };
625
781
  }
626
- }
627
- else if (key === 'extn') {
628
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
629
- // console.log(require('util').inspect(desc, false, 99, true));
630
- desc; // TODO: save this
631
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
632
- }
633
- else {
634
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
635
- }
636
- (0, psdReader_1.skipBytes)(reader, left());
637
- });
782
+ else if (key === 'tmln') {
783
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
784
+ const timeScope = desc.timeScope;
785
+ // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
786
+ const timeline = {
787
+ start: (0, descriptor_1.frac)(timeScope.Strt),
788
+ duration: (0, descriptor_1.frac)(timeScope.duration),
789
+ inTime: (0, descriptor_1.frac)(timeScope.inTime),
790
+ outTime: (0, descriptor_1.frac)(timeScope.outTime),
791
+ autoScope: desc.autoScope,
792
+ audioLevel: desc.audioLevel,
793
+ };
794
+ if (desc.trackList) {
795
+ timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, reader);
796
+ }
797
+ target.timeline = timeline;
798
+ // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
799
+ }
800
+ else if (key === 'cmls') {
801
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
802
+ // console.log('cmls', require('util').inspect(desc, false, 99, true));
803
+ target.comps = {
804
+ settings: [],
805
+ };
806
+ if (desc.origFXRefPoint)
807
+ target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
808
+ for (const item of desc.layerSettings) {
809
+ target.comps.settings.push({ compList: item.compList });
810
+ const t = target.comps.settings[target.comps.settings.length - 1];
811
+ if ('enab' in item)
812
+ t.enabled = item.enab;
813
+ if (item.Ofst)
814
+ t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
815
+ if (item.FXRefPoint)
816
+ t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
817
+ }
818
+ }
819
+ else if (key === 'extn') {
820
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
821
+ if (desc.generatorSettings)
822
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
823
+ }
824
+ else {
825
+ reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
826
+ }
827
+ (0, psdReader_1.skipBytes)(reader, left());
828
+ });
829
+ }
830
+ catch (e) {
831
+ // failure to read one item shouldn't drop all other items
832
+ if (reader.throwForMissingFeatures)
833
+ throw e;
834
+ reader.logMissingFeatures && reader.log(`Failed to read "shmd" section key ${key}: ${e.message}`);
835
+ reader.offset = end;
836
+ }
638
837
  }
639
838
  (0, psdReader_1.skipBytes)(reader, left());
640
839
  }, (writer, target, _, options) => {
641
840
  const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
841
+ const generatorSettings = isDocument(target) ? undefined : target.generatorSettings;
642
842
  let count = 0;
643
843
  if (animationFrames)
644
844
  count++;
@@ -650,6 +850,8 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
650
850
  count++;
651
851
  if (comps)
652
852
  count++;
853
+ if (generatorSettings)
854
+ count++;
653
855
  (0, psdWriter_1.writeUint32)(writer, count);
654
856
  if (animationFrames) {
655
857
  (0, psdWriter_1.writeSignature)(writer, '8BIM');
@@ -672,9 +874,9 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
672
874
  if (f.referencePoint)
673
875
  frame.FXRf = (0, descriptor_1.xyToHorzVrtc)(f.referencePoint);
674
876
  if (f.effects)
675
- frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, false, false);
877
+ frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, writer, hasMultiEffects(f.effects));
676
878
  if (f.opacity !== undefined)
677
- frame.blendOptions = { Opct: (0, descriptor_1.unitsPercent)(f.opacity) };
879
+ frame.blendOptions = { Opct: (0, descriptor_1.unitsPercentF)(f.opacity) };
678
880
  desc.LaSt.push(frame);
679
881
  }
680
882
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
@@ -712,7 +914,7 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
712
914
  audioLevel: timeline.audioLevel,
713
915
  };
714
916
  if (timeline.tracks) {
715
- desc.trackList = (0, descriptor_1.serializeTrackList)(timeline.tracks);
917
+ desc.trackList = (0, descriptor_1.serializeTrackList)(timeline.tracks, writer);
716
918
  }
717
919
  desc.LyrI = getLayerId(target, options);
718
920
  // console.log('WRITE:tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
@@ -758,6 +960,15 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
758
960
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
759
961
  }, true);
760
962
  }
963
+ if (generatorSettings) {
964
+ (0, psdWriter_1.writeSignature)(writer, '8BIM');
965
+ (0, psdWriter_1.writeSignature)(writer, 'extn');
966
+ (0, psdWriter_1.writeUint8)(writer, 0); // copy (always false)
967
+ (0, psdWriter_1.writeZeros)(writer, 3);
968
+ (0, psdWriter_1.writeSection)(writer, 2, () => {
969
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(generatorSettings));
970
+ }, true);
971
+ }
761
972
  });
762
973
  addHandler('PxSc', hasKey('pixelSource'), (reader, target) => {
763
974
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
@@ -837,7 +1048,7 @@ addHandler('vstk', hasKey('vectorStroke'), (reader, target, left) => {
837
1048
  lineAlignment: descriptor_1.strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
838
1049
  scaleLock: desc.strokeStyleScaleLock,
839
1050
  strokeAdjust: desc.strokeStyleStrokeAdjust,
840
- lineDashSet: desc.strokeStyleLineDashSet.map(descriptor_1.parseUnits),
1051
+ lineDashSet: (desc.strokeStyleLineDashSet || []).map(descriptor_1.parseUnits),
841
1052
  blendMode: descriptor_1.BlnM.decode(desc.strokeStyleBlendMode),
842
1053
  opacity: (0, descriptor_1.parsePercent)(desc.strokeStyleOpacity),
843
1054
  content: (0, descriptor_1.parseVectorContent)(desc.strokeStyleContent),
@@ -875,9 +1086,11 @@ hasKey('artboard'), (reader, target, left) => {
875
1086
  rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
876
1087
  guideIndices: desc.guideIndeces,
877
1088
  presetName: desc.artboardPresetName,
878
- color: (0, descriptor_1.parseColor)(desc['Clr ']),
879
- backgroundType: desc.artboardBackgroundType,
880
1089
  };
1090
+ if (desc['Clr '])
1091
+ target.artboard.color = (0, descriptor_1.parseColor)(desc['Clr ']);
1092
+ if (desc.artboardBackgroundType !== undefined)
1093
+ target.artboard.backgroundType = desc.artboardBackgroundType;
881
1094
  (0, psdReader_1.skipBytes)(reader, left());
882
1095
  }, (writer, target) => {
883
1096
  var _a;
@@ -983,14 +1196,15 @@ function encodeWarp(warp) {
983
1196
  }
984
1197
  return desc;
985
1198
  }
986
- addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1199
+ addHandler('PlLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
1200
+ (reader, target, left) => {
987
1201
  if ((0, psdReader_1.readSignature)(reader) !== 'plcL')
988
1202
  throw new Error(`Invalid PlLd signature`);
989
1203
  if ((0, psdReader_1.readInt32)(reader) !== 3)
990
1204
  throw new Error(`Invalid PlLd version`);
991
1205
  const id = (0, psdReader_1.readPascalString)(reader, 1);
992
1206
  const pageNumber = (0, psdReader_1.readInt32)(reader);
993
- const totalPages = (0, psdReader_1.readInt32)(reader); // TODO: check how this works ?
1207
+ const totalPages = (0, psdReader_1.readInt32)(reader); // for multi-page documents (PDF)
994
1208
  (0, psdReader_1.readInt32)(reader); // anitAliasPolicy 16
995
1209
  const placedLayerType = (0, psdReader_1.readInt32)(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
996
1210
  if (!placedLayerTypes[placedLayerType])
@@ -1035,6 +1249,8 @@ addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1035
1249
  const type = isQuilt ? 'quiltWarp' : 'warp';
1036
1250
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', type, encodeWarp(warp), type);
1037
1251
  });
1252
+ const levelsFilterKeys = ['_name', '_classID', 'presetKind', 'Adjs'];
1253
+ const levelsFilterAdjustmentKeys = ['_name', '_classID', 'Chnl', 'Inpt', 'Otpt', 'Gmm '];
1038
1254
  function uint8ToFloat32(array) {
1039
1255
  const length = Math.floor(array.byteLength / 4);
1040
1256
  return new Float32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
@@ -1077,7 +1293,87 @@ function pointToHrznVrtc(point) {
1077
1293
  Vrtc: (0, descriptor_1.unitsValue)(point.y, 'y'),
1078
1294
  };
1079
1295
  }
1080
- function parseFilterFXItem(f, options) {
1296
+ // Camera Raw Filter setting paths (in CameraRawFilterSettings) and their descriptor keys
1297
+ const cameraRawColorKeys = [
1298
+ ['red', 'R'], ['orange', 'O'], ['yellow', 'Y'], ['green', 'G'], ['aqua', 'A'], ['blue', 'B'], ['purple', 'P'], ['magenta', 'M'],
1299
+ ];
1300
+ const cameraRawSettings = [
1301
+ ['version', 'CrVe'], ['processVersion', 'PrVN'], ['processVersionCode', 'PrVe'], ['whiteBalance', 'WBal'],
1302
+ ['temperature', 'Temp'], ['tint', 'Tint'], ['exposure', 'Ex12'], ['contrast', 'Cr12'], ['highlights', 'Hi12'],
1303
+ ['shadows', 'Sh12'], ['whites', 'Wh12'], ['blacks', 'Bk12'], ['texture', 'CrTx'], ['clarity', 'Cl12'],
1304
+ ['dehaze', 'Dhze'], ['vibrance', 'Vibr'], ['saturation', 'Strt'],
1305
+ ['parametricCurve.shadows', 'PC_S'], ['parametricCurve.darks', 'PC_D'], ['parametricCurve.lights', 'PC_L'],
1306
+ ['parametricCurve.highlights', 'PC_H'], ['parametricCurve.shadowSplit', 'PC_1'], ['parametricCurve.midtoneSplit', 'PC_2'],
1307
+ ['parametricCurve.highlightSplit', 'PC_3'],
1308
+ ['pointCurve.composite', 'Crv '], ['pointCurve.red', 'CrvR'], ['pointCurve.green', 'CrvG'], ['pointCurve.blue', 'CrvB'],
1309
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.hue.${name}`, `HA_${key}`]),
1310
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.saturation.${name}`, `SA_${key}`]),
1311
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.luminance.${name}`, `LA_${key}`]),
1312
+ ['colorGrading.shadows.hue', 'STSH'], ['colorGrading.shadows.saturation', 'STSS'], ['colorGrading.shadows.luminance', 'CgSL'],
1313
+ ['colorGrading.midtones.hue', 'CgMH'], ['colorGrading.midtones.saturation', 'CgMS'], ['colorGrading.midtones.luminance', 'CgML'],
1314
+ ['colorGrading.highlights.hue', 'STHH'], ['colorGrading.highlights.saturation', 'STHS'], ['colorGrading.highlights.luminance', 'CgHL'],
1315
+ ['colorGrading.global.hue', 'CgGH'], ['colorGrading.global.saturation', 'CgGS'], ['colorGrading.global.luminance', 'CgGL'],
1316
+ ['colorGrading.blending', 'CgBl'], ['colorGrading.balance', 'STB '],
1317
+ ['sharpening.amount', 'Shrp'], ['sharpening.radius', 'ShpR'], ['sharpening.detail', 'ShpD'], ['sharpening.masking', 'ShpM'],
1318
+ ['noiseReduction.luminance', 'LNR '], ['noiseReduction.luminanceDetail', 'LNRD'], ['noiseReduction.luminanceContrast', 'LNRC'],
1319
+ ['noiseReduction.color', 'CNR '], ['noiseReduction.colorDetail', 'CNRD'], ['noiseReduction.colorSmoothness', 'CNRS'],
1320
+ ['grain.amount', 'GRNA'], ['grain.size', 'GRNS'], ['grain.roughness', 'GRNF'],
1321
+ ['postCropVignette.amount', 'PCVA'], ['postCropVignette.midpoint', 'PCVM'], ['postCropVignette.feather', 'PCVF'],
1322
+ ['postCropVignette.roundness', 'PCVR'],
1323
+ ];
1324
+ const cameraRawCurveKeys = ['Crv ', 'CrvR', 'CrvG', 'CrvB'];
1325
+ const cameraRawFilterID = 2783;
1326
+ function parseCameraRawSettings(desc, reader) {
1327
+ var _a;
1328
+ const settings = {};
1329
+ for (const [path, key] of cameraRawSettings) {
1330
+ let value = desc[key];
1331
+ if (value === undefined)
1332
+ continue;
1333
+ if (key === 'WBal') {
1334
+ value = value.split('.')[1];
1335
+ }
1336
+ else if (cameraRawCurveKeys.includes(key)) {
1337
+ const values = value;
1338
+ value = [];
1339
+ for (let i = 0; i + 1 < values.length; i += 2)
1340
+ value.push({ input: values[i], output: values[i + 1] });
1341
+ }
1342
+ const parts = path.split('.');
1343
+ let target = settings;
1344
+ for (let i = 0; i < parts.length - 1; i++)
1345
+ target = target[parts[i]] = (_a = target[parts[i]]) !== null && _a !== void 0 ? _a : {};
1346
+ target[parts[parts.length - 1]] = value;
1347
+ }
1348
+ for (const key of Object.keys(desc)) {
1349
+ if (key !== '_name' && key !== '_classID' && !cameraRawSettings.some(s => s[1] === key)) {
1350
+ reader.logMissingFeatures && reader.log(`Unhandled Camera Raw Filter setting: ${key}`);
1351
+ }
1352
+ }
1353
+ return settings;
1354
+ }
1355
+ function serializeCameraRawSettings(settings) {
1356
+ const desc = { _name: '', _classID: descriptor_1.cameraRawFilterClassID };
1357
+ for (const [path, key] of cameraRawSettings) {
1358
+ let value = settings;
1359
+ for (const part of path.split('.'))
1360
+ value = value === null || value === void 0 ? void 0 : value[part];
1361
+ if (value === undefined)
1362
+ continue;
1363
+ if (key === 'WBal') {
1364
+ value = `WBal.${value}`;
1365
+ }
1366
+ else if (cameraRawCurveKeys.includes(key)) {
1367
+ value = value.flatMap(p => [Math.round(p.input), Math.round(p.output)]);
1368
+ }
1369
+ else if (typeof value === 'number' && key !== 'Ex12' && key !== 'ShpR') {
1370
+ value = Math.round(value);
1371
+ }
1372
+ desc[key] = value;
1373
+ }
1374
+ return desc;
1375
+ }
1376
+ function parseFilterFXItem(f, reader) {
1081
1377
  const base = {
1082
1378
  name: f['Nm '],
1083
1379
  opacity: (0, descriptor_1.parsePercent)(f.blendOptions.Opct),
@@ -1087,6 +1383,9 @@ function parseFilterFXItem(f, options) {
1087
1383
  foregroundColor: (0, descriptor_1.parseColor)(f.FrgC),
1088
1384
  backgroundColor: (0, descriptor_1.parseColor)(f.BckC),
1089
1385
  };
1386
+ if ('Fltr' in f && f.Fltr._classID === descriptor_1.cameraRawFilterClassID) {
1387
+ return Object.assign(Object.assign({}, base), { type: 'camera raw', filter: parseCameraRawSettings(f.Fltr, reader) });
1388
+ }
1090
1389
  if ('Fltr' in f) {
1091
1390
  switch (f.Fltr._classID) {
1092
1391
  case 'boxblur': return Object.assign(Object.assign({}, base), { type: 'box blur', filter: {
@@ -1125,7 +1424,7 @@ function parseFilterFXItem(f, options) {
1125
1424
  undefinedAreas: descriptor_1.UndA.decode(f.Fltr.UndA),
1126
1425
  displacementFile: {
1127
1426
  signature: f.Fltr.DspF.sig,
1128
- path: f.Fltr.DspF.path, // TODO: this is decoded incorrectly ???
1427
+ path: f.Fltr.DspF.path,
1129
1428
  },
1130
1429
  } });
1131
1430
  case 'Pnch': return Object.assign(Object.assign({}, base), { type: 'pinch', filter: {
@@ -1312,8 +1611,7 @@ function parseFilterFXItem(f, options) {
1312
1611
  ],
1313
1612
  puppetShapeList: (f.Fltr.puppetShapeList || []).map(p => ({
1314
1613
  rigidType: p.rigidType,
1315
- // TODO: VrsM
1316
- // TODO: VrsN
1614
+ // VrsM / VrsN - version (always 1.0)
1317
1615
  originalVertexArray: uint8ToPoints(p.originalVertexArray),
1318
1616
  deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
1319
1617
  indexArray: Array.from(uint8ToUint32(p.indexArray)),
@@ -1410,7 +1708,7 @@ function parseFilterFXItem(f, options) {
1410
1708
  ;
1411
1709
  case 'Crvs':
1412
1710
  {
1413
- return Object.assign(Object.assign({}, base), { type: 'curves', filter: Object.assign({ presetKind: descriptor_1.presetKindType.decode(f.Fltr.presetKind) }, (f.Fltr.Adjs ? {
1711
+ return Object.assign(Object.assign({}, base), { type: 'curves', filter: Object.assign(Object.assign({}, (f.Fltr.presetKind !== undefined ? { presetKind: descriptor_1.presetKindType.decode(f.Fltr.presetKind) } : {})), (f.Fltr.Adjs ? {
1414
1712
  adjustments: f.Fltr.Adjs.map(a => {
1415
1713
  const channels = a.Chnl.map(descriptor_1.Chnl.decode);
1416
1714
  if (a['Crv ']) {
@@ -1434,6 +1732,35 @@ function parseFilterFXItem(f, options) {
1434
1732
  } : {})) });
1435
1733
  }
1436
1734
  ;
1735
+ case 'Lvls':
1736
+ {
1737
+ const adjustments = f.Fltr.Adjs;
1738
+ const isKnown = (obj, keys) => Object.keys(obj).every(k => keys.indexOf(k) !== -1);
1739
+ // skip filter with settings we don't know about
1740
+ if (!isKnown(f.Fltr, levelsFilterKeys) || (adjustments && !adjustments.every(a => isKnown(a, levelsFilterAdjustmentKeys)))) {
1741
+ if (reader.throwForMissingFeatures)
1742
+ throw new Error('Unknown levels filter settings');
1743
+ reader.logMissingFeatures && reader.log(`Skipped levels smart filter with unknown settings`);
1744
+ return undefined;
1745
+ }
1746
+ return Object.assign(Object.assign({}, base), { type: 'levels', filter: Object.assign(Object.assign({}, (f.Fltr.presetKind !== undefined ? { presetKind: descriptor_1.presetKindType.decode(f.Fltr.presetKind) } : {})), (adjustments ? {
1747
+ adjustments: adjustments.map(a => {
1748
+ const result = { channels: a.Chnl.map(descriptor_1.Chnl.decode) };
1749
+ if (a.Inpt) {
1750
+ result.shadowInput = a.Inpt[0];
1751
+ result.highlightInput = a.Inpt[1];
1752
+ }
1753
+ if (a.Otpt) {
1754
+ result.shadowOutput = a.Otpt[0];
1755
+ result.highlightOutput = a.Otpt[1];
1756
+ }
1757
+ if (a['Gmm '] !== undefined)
1758
+ result.midtoneInput = a['Gmm '];
1759
+ return result;
1760
+ }),
1761
+ } : {})) });
1762
+ }
1763
+ ;
1437
1764
  case 'BrgC':
1438
1765
  {
1439
1766
  return Object.assign(Object.assign({}, base), { type: 'brightness/contrast', filter: {
@@ -1444,10 +1771,11 @@ function parseFilterFXItem(f, options) {
1444
1771
  }
1445
1772
  ;
1446
1773
  default:
1447
- if (options.throwForMissingFeatures) {
1774
+ if (reader.throwForMissingFeatures) {
1448
1775
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1449
1776
  throw new Error(`Unknown filter classId: ${f.Fltr._classID}`);
1450
1777
  }
1778
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.Fltr._classID} (${f['Nm ']})`);
1451
1779
  return undefined;
1452
1780
  }
1453
1781
  }
@@ -1467,21 +1795,23 @@ function parseFilterFXItem(f, options) {
1467
1795
  case 1314149187: return Object.assign(Object.assign({}, base), { type: 'ntsc colors' });
1468
1796
  case 1231976050: return Object.assign(Object.assign({}, base), { type: 'invert' });
1469
1797
  default:
1470
- if (options.throwForMissingFeatures) {
1798
+ if (reader.throwForMissingFeatures) {
1471
1799
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1472
1800
  throw new Error(`Unknown filterID: ${f.filterID}`);
1473
1801
  }
1802
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.filterID} (${f['Nm ']})`);
1803
+ return undefined;
1474
1804
  }
1475
1805
  }
1476
1806
  }
1477
- function parseFilterFX(desc, options) {
1807
+ function parseFilterFX(desc, reader) {
1478
1808
  return {
1479
1809
  enabled: desc.enab,
1480
1810
  validAtPosition: desc.validAtPosition,
1481
1811
  maskEnabled: desc.filterMaskEnable,
1482
1812
  maskLinked: desc.filterMaskLinked,
1483
1813
  maskExtendWithWhite: desc.filterMaskExtendWithWhite,
1484
- list: desc.filterFXList.map(x => parseFilterFXItem(x, options)).filter((x) => !!x),
1814
+ list: desc.filterFXList.map(x => parseFilterFXItem(x, reader)).filter((x) => !!x),
1485
1815
  };
1486
1816
  }
1487
1817
  function uvRadius(t) {
@@ -1837,14 +2167,14 @@ function serializeFilterFXItem(f) {
1837
2167
  case 'puppet': return Object.assign(Object.assign({}, base), { Fltr: {
1838
2168
  _name: 'Rigid Transform',
1839
2169
  _classID: 'rigidTransform',
1840
- 'null': ['Ordn.Trgt'], // TODO: ???
2170
+ 'null': ['Ordn.Trgt'], // target layer reference
1841
2171
  rigidType: f.filter.rigidType,
1842
2172
  puppetShapeList: (f.filter.puppetShapeList || []).map(p => ({
1843
2173
  _name: '',
1844
2174
  _classID: 'puppetShape',
1845
2175
  rigidType: p.rigidType,
1846
- VrsM: 1, // TODO: ???
1847
- VrsN: 0, // TODO: ???
2176
+ VrsM: 1, // version major
2177
+ VrsN: 0, // version minor
1848
2178
  originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
1849
2179
  deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
1850
2180
  indexArray: toUint8(new Uint32Array(p.indexArray)),
@@ -1926,7 +2256,7 @@ function serializeFilterFXItem(f) {
1926
2256
  warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
1927
2257
  quads: f.filter.quads.map(indices => ({ _name: '', _classID: 'quadIndices', indices })),
1928
2258
  }, filterID: 442 });
1929
- case 'curves': return Object.assign(Object.assign({}, base), { Fltr: Object.assign({ _name: 'Curves', _classID: 'Crvs', presetKind: descriptor_1.presetKindType.encode(f.filter.presetKind) }, (f.filter.adjustments ? {
2259
+ case 'curves': return Object.assign(Object.assign({}, base), { Fltr: Object.assign(Object.assign({ _name: 'Curves', _classID: 'Crvs' }, (f.filter.presetKind !== undefined ? { presetKind: descriptor_1.presetKindType.encode(f.filter.presetKind) } : {})), (f.filter.adjustments ? {
1930
2260
  Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
1931
2261
  _name: '',
1932
2262
  _classID: 'CrvA',
@@ -1939,6 +2269,22 @@ function serializeFilterFXItem(f) {
1939
2269
  Mpng: a.values,
1940
2270
  })
1941
2271
  } : {})), filterID: 1131574899 });
2272
+ case 'levels': return Object.assign(Object.assign({}, base), { Fltr: Object.assign(Object.assign({ _name: 'Levels', _classID: 'Lvls' }, (f.filter.presetKind !== undefined ? { presetKind: descriptor_1.presetKindType.encode(f.filter.presetKind) } : {})), (f.filter.adjustments ? {
2273
+ Adjs: f.filter.adjustments.map(a => {
2274
+ var _a, _b, _c, _d;
2275
+ const adjustment = { _name: '', _classID: 'LvlA', Chnl: a.channels.map(descriptor_1.Chnl.encode) };
2276
+ if (a.shadowInput !== undefined || a.highlightInput !== undefined) {
2277
+ adjustment.Inpt = [Math.round((_a = a.shadowInput) !== null && _a !== void 0 ? _a : 0), Math.round((_b = a.highlightInput) !== null && _b !== void 0 ? _b : 255)];
2278
+ }
2279
+ if (a.shadowOutput !== undefined || a.highlightOutput !== undefined) {
2280
+ adjustment.Otpt = [Math.round((_c = a.shadowOutput) !== null && _c !== void 0 ? _c : 0), Math.round((_d = a.highlightOutput) !== null && _d !== void 0 ? _d : 255)];
2281
+ }
2282
+ if (a.midtoneInput !== undefined)
2283
+ adjustment['Gmm '] = a.midtoneInput;
2284
+ return adjustment;
2285
+ }),
2286
+ } : {})), filterID: 1282829427 });
2287
+ case 'camera raw': return Object.assign(Object.assign({}, base), { Fltr: serializeCameraRawSettings(f.filter), filterID: cameraRawFilterID });
1942
2288
  case 'brightness/contrast': return Object.assign(Object.assign({}, base), { Fltr: {
1943
2289
  _name: 'Brightness/Contrast',
1944
2290
  _classID: 'BrgC',
@@ -1989,7 +2335,8 @@ function getWarpFromPlacedLayer(placed) {
1989
2335
  },
1990
2336
  };
1991
2337
  }
1992
- addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2338
+ addHandler('SoLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
2339
+ (reader, target, left) => {
1993
2340
  if ((0, psdReader_1.readSignature)(reader) !== 'soLD')
1994
2341
  throw new Error(`Invalid SoLd type`);
1995
2342
  const version = (0, psdReader_1.readInt32)(reader);
@@ -2046,13 +2393,18 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2046
2393
  const placedType = placedLayerTypes.indexOf(placed.type);
2047
2394
  if (placedType === -1)
2048
2395
  throw new Error('Invalid placedLayer type');
2396
+ const warp = getWarpFromPlacedLayer(placed);
2397
+ // size of linked image matches warp bounds if it's not provided
2398
+ const bounds = warp.bounds;
2399
+ const width = placed.width || (bounds ? bounds.right.value - bounds.left.value : 0);
2400
+ const height = placed.height || (bounds ? bounds.bottom.value - bounds.top.value : 0);
2049
2401
  const desc = Object.assign(Object.assign({ Idnt: placed.id, placed: (_a = placed.placed) !== null && _a !== void 0 ? _a : placed.id, PgNm: placed.pageNumber || 1, totalPages: placed.totalPages || 1 }, (placed.crop ? { Crop: placed.crop } : {})), { frameStep: placed.frameStep || { numerator: 0, denominator: 600 }, duration: placed.duration || { numerator: 0, denominator: 600 }, frameCount: placed.frameCount || 0, Annt: 16, Type: placedType, Trnf: placed.transform, nonAffineTransform: (_b = placed.nonAffineTransform) !== null && _b !== void 0 ? _b : placed.transform,
2050
2402
  // quiltWarp: {} as any,
2051
- warp: encodeWarp(getWarpFromPlacedLayer(placed)), 'Sz ': {
2403
+ warp: encodeWarp(warp), 'Sz ': {
2052
2404
  _name: '',
2053
2405
  _classID: 'Pnt ',
2054
- Wdth: placed.width || 0, // TODO: find size ?
2055
- Hght: placed.height || 0, // TODO: find size ?
2406
+ Wdth: width,
2407
+ Hght: height,
2056
2408
  }, Rslt: placed.resolution ? (0, descriptor_1.unitsValue)(placed.resolution, 'resolution') : { units: 'Density', value: 72 } });
2057
2409
  if (placed.filter) {
2058
2410
  desc.filterFX = {
@@ -2066,14 +2418,6 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2066
2418
  filterFXList: placed.filter.list.map(f => serializeFilterFXItem(f)),
2067
2419
  };
2068
2420
  }
2069
- // TODO:
2070
- // desc.comp = -1;
2071
- // desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
2072
- // desc.ClMg = {
2073
- // _name: '',
2074
- // _classID: 'ClMg',
2075
- // placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
2076
- // } as any;
2077
2421
  // if (JSON.stringify(t) !== JSON.stringify(desc)) {
2078
2422
  // console.log('read', require('util').inspect(t, false, 99, true));
2079
2423
  // console.log('write', require('util').inspect(desc, false, 99, true));
@@ -2103,7 +2447,13 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2103
2447
  desc.compInfo = placed.compInfo;
2104
2448
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, desc.quiltWarp ? 'quiltWarp' : 'warp');
2105
2449
  });
2106
- addHandlerAlias('SoLE', 'SoLd');
2450
+ const soLdHandler = exports.infoHandlersMap['SoLd'];
2451
+ addHandler('SoLE', // linked smart object (Photoshop doesn't write PlLd for these)
2452
+ // linked smart object (Photoshop doesn't write PlLd for these)
2453
+ target => { var _a; return !!((_a = target.placedLayer) === null || _a === void 0 ? void 0 : _a.external); }, (reader, target, left, psd, imageResources) => {
2454
+ soLdHandler.read(reader, target, left, psd, imageResources);
2455
+ target.placedLayer.external = true;
2456
+ }, soLdHandler.write);
2107
2457
  addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2108
2458
  target.referencePoint = {
2109
2459
  x: (0, psdReader_1.readFloat64)(reader),
@@ -2113,14 +2463,37 @@ addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2113
2463
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.x);
2114
2464
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.y);
2115
2465
  });
2116
- addHandler('Lr16', () => false, (reader, _target, _left, psd, imageResources) => {
2117
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2118
- }, (_writer, _target) => {
2119
- });
2120
- addHandler('Lr32', () => false, (reader, _target, _left, psd, imageResources) => {
2121
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2122
- }, (_writer, _target) => {
2123
- });
2466
+ function createGlobalLayerInfoHandler(key, bitsPerChannel) {
2467
+ addHandler(key, () => false, (reader, _target, left, psd, imageResources) => {
2468
+ var _a;
2469
+ // Photoshop writes empty (or 8bit compatibility) layer info section alongside Lr16/Lr32 in 16/32bit documents,
2470
+ // layers from global info with matching bit depth replace layers from that section, other ones are only used
2471
+ // if the section is empty
2472
+ if (!reader.layersInGlobalInfo && (psd.bitsPerChannel === bitsPerChannel || !((_a = psd.children) === null || _a === void 0 ? void 0 : _a.length))) {
2473
+ const sectionLayers = psd.children;
2474
+ psd.children = [];
2475
+ try {
2476
+ (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2477
+ }
2478
+ catch (e) {
2479
+ psd.children = sectionLayers; // keep layers from layer info section if global info is broken
2480
+ throw e;
2481
+ }
2482
+ reader.layersInGlobalInfo = true;
2483
+ }
2484
+ (0, psdReader_1.skipBytes)(reader, left());
2485
+ }, () => { });
2486
+ }
2487
+ createGlobalLayerInfoHandler('Lr16', 16);
2488
+ createGlobalLayerInfoHandler('Lr32', 32);
2489
+ createGlobalLayerInfoHandler('Layr', undefined);
2490
+ // merged transparency markers, first extra channel of composite image is transparency
2491
+ for (const key of ['Mtrn', 'Mt16', 'Mt32']) {
2492
+ addHandler(key, () => false, (reader, _target, left) => {
2493
+ reader.globalAlpha = true;
2494
+ (0, psdReader_1.skipBytes)(reader, left());
2495
+ }, () => { });
2496
+ }
2124
2497
  addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2125
2498
  target.userMask = {
2126
2499
  colorSpace: (0, psdReader_1.readColor)(reader),
@@ -2137,29 +2510,23 @@ addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2137
2510
  (0, psdWriter_1.writeUint8)(writer, 128);
2138
2511
  (0, psdWriter_1.writeZeros)(writer, 1);
2139
2512
  });
2140
- if (helpers_1.MOCK_HANDLERS) {
2141
- addHandler('Patt', target => target._Patt !== undefined, (reader, target, left) => {
2142
- // console.log('additional info: Patt');
2143
- target._Patt = (0, psdReader_1.readBytes)(reader, left());
2144
- }, (writer, target) => false && (0, psdWriter_1.writeBytes)(writer, target._Patt));
2145
- }
2146
- else {
2147
- addHandler('Patt', // TODO: handle also Pat2 & Pat3
2148
- // TODO: handle also Pat2 & Pat3
2149
- target => !!(target.patterns && target.patterns.length > 0), (reader, target, left) => {
2150
- while (left() > 0) {
2151
- const pattern = (0, psdReader_1.readPattern)(reader);
2152
- if (target.patterns === undefined)
2153
- target.patterns = [];
2154
- target.patterns.push(pattern);
2155
- }
2156
- }, (writer, target, _, _options) => {
2157
- const patterns = target.patterns || [];
2158
- for (const pattern of patterns) {
2159
- (0, psdWriter_1.writePattern)(writer, pattern);
2160
- }
2161
- });
2162
- }
2513
+ addHandler('Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2514
+ // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2515
+ target => !!(target.patterns && target.patterns.length > 0), (reader, target, left) => {
2516
+ while (left() > 0) {
2517
+ const pattern = (0, psdReader_1.readPatternSafe)(reader);
2518
+ if (!pattern)
2519
+ continue;
2520
+ if (target.patterns === undefined)
2521
+ target.patterns = [];
2522
+ target.patterns.push(pattern);
2523
+ }
2524
+ }, (writer, target, _, _options) => {
2525
+ const patterns = target.patterns || [];
2526
+ for (const pattern of patterns) {
2527
+ (0, psdWriter_1.writePattern)(writer, pattern);
2528
+ }
2529
+ });
2163
2530
  addHandlerAlias('Pat2', 'Patt');
2164
2531
  addHandlerAlias('Pat3', 'Patt');
2165
2532
  /*
@@ -2336,6 +2703,10 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2336
2703
  writer.view.setUint32(dataOffset - 4, writer.offset - dataOffset, false);
2337
2704
  }
2338
2705
  });
2706
+ function readFileTypeSignature(reader) {
2707
+ const value = (0, psdReader_1.readSignature)(reader);
2708
+ return /^[\0 ]*$/.test(value) ? '' : value.trim(); // empty value is stored as spaces or zeros
2709
+ }
2339
2710
  function createLnkHandler(tag) {
2340
2711
  addHandler(tag, (target) => {
2341
2712
  const psd = target;
@@ -2357,8 +2728,8 @@ function createLnkHandler(tag) {
2357
2728
  const version = (0, psdReader_1.readInt32)(reader);
2358
2729
  const id = (0, psdReader_1.readPascalString)(reader, 1);
2359
2730
  const name = (0, psdReader_1.readUnicodeString)(reader);
2360
- const fileType = (0, psdReader_1.readSignature)(reader).trim(); // ' ' if empty
2361
- const fileCreator = (0, psdReader_1.readSignature)(reader).trim(); // ' ' or '\0\0\0\0' if empty
2731
+ const fileType = readFileTypeSignature(reader); // ' ' if empty
2732
+ const fileCreator = readFileTypeSignature(reader); // ' ' or '\0\0\0\0' if empty
2362
2733
  const dataSize = readLength64(reader);
2363
2734
  const hasFileOpenDescriptor = (0, psdReader_1.readUint8)(reader);
2364
2735
  const fileOpenDescriptor = hasFileOpenDescriptor ? (0, descriptor_1.readVersionAndDescriptor)(reader) : undefined;
@@ -2509,36 +2880,48 @@ createLnkHandler('lnk2');
2509
2880
  createLnkHandler('lnkE');
2510
2881
  addHandlerAlias('lnkD', 'lnk2');
2511
2882
  addHandlerAlias('lnk3', 'lnk2');
2512
- addHandler('pths', hasKey('pathList'), (reader, target) => {
2883
+ // unicode names of saved paths (path data is stored in image resources 2000-2998)
2884
+ addHandler('pths', target => { var _a; return ((_a = target.imageResources) === null || _a === void 0 ? void 0 : _a.savedPaths) !== undefined; }, (reader, _target, left, psd) => {
2885
+ var _a, _b;
2513
2886
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
2514
- // console.log(require('util').inspect(desc, false, 99, true));
2515
- // if (options.throwForMissingFeatures && desc?.pathList?.length) throw new Error('non-empty pathList in `pths`');
2516
- desc;
2517
- target.pathList = []; // TODO: read paths
2518
- }, (writer, _target) => {
2887
+ const imageResources = (_a = psd.imageResources) !== null && _a !== void 0 ? _a : (psd.imageResources = {});
2888
+ const savedPaths = (_b = imageResources.savedPaths) !== null && _b !== void 0 ? _b : (imageResources.savedPaths = []);
2889
+ for (let i = 0; i < desc.pathList.length && i < savedPaths.length; i++) {
2890
+ const { pathUnicodeName, pathSymmetryClass } = desc.pathList[i];
2891
+ const path = savedPaths[i];
2892
+ // clipping path is referenced by (non-unicode) name
2893
+ if (imageResources.clippingPath && imageResources.clippingPath.name === path.name) {
2894
+ imageResources.clippingPath.name = pathUnicodeName;
2895
+ }
2896
+ path.name = pathUnicodeName;
2897
+ if (pathSymmetryClass)
2898
+ path.symmetryMode = pathSymmetryClass.pathSymmetryMode.split('.')[1];
2899
+ }
2900
+ (0, psdReader_1.skipBytes)(reader, left());
2901
+ }, (writer, _target, psd) => {
2519
2902
  const desc = {
2520
- pathList: [], // TODO: write paths
2903
+ pathList: psd.imageResources.savedPaths.map(path => (Object.assign({ _name: '', _classID: 'pathInfoClass', pathUnicodeName: path.name }, (path.symmetryMode ? {
2904
+ pathSymmetryClass: {
2905
+ _name: '',
2906
+ _classID: 'pathSymmetryClass',
2907
+ pathSymmetryMode: `pathSymmetryModeEnum.${path.symmetryMode}`,
2908
+ },
2909
+ } : {})))),
2521
2910
  };
2522
2911
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'pathsDataClass', desc);
2523
2912
  });
2524
2913
  addHandler('lyvr', hasKey('version'), (reader, target) => target.version = (0, psdReader_1.readUint32)(reader), (writer, target) => (0, psdWriter_1.writeUint32)(writer, target.version));
2525
- addHandler('lfxs', () => false, // TODO: not sure when we actually need to write this section
2526
- // NOTE: this might be insufficient
2527
- // target => target.effects !== undefined && (
2528
- // !!target.effects.dropShadow?.some(e => e.choke) ||
2529
- // !!target.effects.innerShadow?.some(e => e.choke) ||
2530
- // !!target.effects.outerGlow?.choke ||
2531
- // !!target.effects.innerGlow?.choke
2532
- // ),
2533
- (reader, target, left) => {
2914
+ addHandler('lfxs', // effects of group layers (instead of 'lfx2' / 'lmfx')
2915
+ // effects of group layers (instead of 'lfx2' / 'lmfx')
2916
+ target => target.effects !== undefined && isGroupLayer(target), (reader, target, left) => {
2534
2917
  const version = (0, psdReader_1.readUint32)(reader);
2535
2918
  if (version !== 0)
2536
2919
  throw new Error(`Invalid lfxs version`);
2537
2920
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
2538
- target.effects = (0, descriptor_1.parseEffects)(desc, !!reader.logMissingFeatures);
2921
+ target.effects = (0, descriptor_1.parseEffects)(desc, reader);
2539
2922
  (0, psdReader_1.skipBytes)(reader, left());
2540
- }, (writer, target, _, options) => {
2541
- const desc = (0, descriptor_1.serializeEffects)(target.effects, !!options.logMissingFeatures, true);
2923
+ }, (writer, target) => {
2924
+ const desc = (0, descriptor_1.serializeEffects)(target.effects, writer, true);
2542
2925
  (0, psdWriter_1.writeUint32)(writer, 0); // version
2543
2926
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
2544
2927
  });
@@ -2574,6 +2957,10 @@ function readLevelsChannel(reader) {
2574
2957
  const midtoneInput = (0, psdReader_1.readInt16)(reader) / 100;
2575
2958
  return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
2576
2959
  }
2960
+ const defaultLevelsChannel = { shadowInput: 0, highlightInput: 255, shadowOutput: 0, highlightOutput: 255, midtoneInput: 1 };
2961
+ function isDefaultLevelsChannel(c) {
2962
+ return c.shadowInput === 0 && c.highlightInput === 255 && c.shadowOutput === 0 && c.highlightOutput === 255 && c.midtoneInput === 1;
2963
+ }
2577
2964
  function writeLevelsChannel(writer, channel) {
2578
2965
  (0, psdWriter_1.writeInt16)(writer, channel.shadowInput);
2579
2966
  (0, psdWriter_1.writeInt16)(writer, channel.highlightInput);
@@ -2584,48 +2971,44 @@ function writeLevelsChannel(writer, channel) {
2584
2971
  addHandler('levl', adjustmentType('levels'), (reader, target, left) => {
2585
2972
  if ((0, psdReader_1.readUint16)(reader) !== 2)
2586
2973
  throw new Error('Invalid levl version');
2587
- const levels = [];
2588
- for (let i = 0; i < 29 && left(); i++) {
2589
- levels.push(readLevelsChannel(reader));
2590
- }
2591
- const [rgb, red, green, blue] = levels;
2592
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', rgb,
2593
- red,
2594
- green,
2595
- blue });
2596
- if (left()) {
2597
- if ((0, psdReader_1.readSignature)(reader) !== 'Lvls')
2598
- throw new Error(`Invalid levl signature`);
2599
- if ((0, psdReader_1.readUint16)(reader) !== 3)
2600
- throw new Error(`Invalid levl version`);
2601
- const totalChannelRecords = (0, psdReader_1.readUint16)(reader);
2602
- for (let i = 0; i < (totalChannelRecords - levels.length) && left(); i++) {
2603
- levels.push(readLevelsChannel(reader));
2974
+ const channels = [];
2975
+ for (let i = 0; i < 29 && left() >= 10; i++) {
2976
+ channels.push(readLevelsChannel(reader));
2977
+ }
2978
+ if (left() >= 8) {
2979
+ const signature = (0, psdReader_1.readSignature)(reader);
2980
+ let totalChannelRecords;
2981
+ if (signature === 'ls\x00\x03') {
2982
+ // Clip Studio Paint writes 'ls' instead of 'Lvls' signature (version is part of the read signature)
2983
+ totalChannelRecords = (0, psdReader_1.readUint16)(reader);
2984
+ }
2985
+ else {
2986
+ if (signature !== 'Lvls')
2987
+ throw new Error(`Invalid levl signature`);
2988
+ if ((0, psdReader_1.readUint16)(reader) !== 3)
2989
+ throw new Error(`Invalid levl version`);
2990
+ totalChannelRecords = (0, psdReader_1.readUint16)(reader);
2991
+ }
2992
+ while (channels.length < totalChannelRecords && left() >= 10) {
2993
+ channels.push(readLevelsChannel(reader));
2604
2994
  }
2605
2995
  }
2996
+ // records for all possible channels are stored, skip unused ones
2997
+ while (channels.length && isDefaultLevelsChannel(channels[channels.length - 1]))
2998
+ channels.pop();
2999
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', channels });
2606
3000
  (0, psdReader_1.skipBytes)(reader, left());
2607
3001
  }, (writer, target) => {
2608
- const info = target.adjustment;
2609
- const defaultChannel = {
2610
- shadowInput: 0,
2611
- highlightInput: 255,
2612
- shadowOutput: 0,
2613
- highlightOutput: 255,
2614
- midtoneInput: 1,
2615
- };
3002
+ const channels = target.adjustment.channels || [];
2616
3003
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2617
- writeLevelsChannel(writer, info.rgb || defaultChannel);
2618
- writeLevelsChannel(writer, info.red || defaultChannel);
2619
- writeLevelsChannel(writer, info.green || defaultChannel);
2620
- writeLevelsChannel(writer, info.blue || defaultChannel);
2621
- for (let i = 0; i < (29 - 4); i++) {
2622
- writeLevelsChannel(writer, defaultChannel);
3004
+ for (let i = 0; i < 29; i++) {
3005
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2623
3006
  }
2624
3007
  (0, psdWriter_1.writeSignature)(writer, 'Lvls');
2625
3008
  (0, psdWriter_1.writeUint16)(writer, 3); // version
2626
3009
  (0, psdWriter_1.writeUint16)(writer, 62); // total count
2627
- for (let i = 0; i < (62 - 29); i++) {
2628
- writeLevelsChannel(writer, defaultChannel);
3010
+ for (let i = 29; i < 62; i++) {
3011
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2629
3012
  }
2630
3013
  });
2631
3014
  function readCurveChannel(reader) {
@@ -2646,87 +3029,78 @@ function writeCurveChannel(writer, channel) {
2646
3029
  }
2647
3030
  }
2648
3031
  addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
2649
- (0, psdReader_1.readUint8)(reader);
2650
- if ((0, psdReader_1.readUint16)(reader) !== 1)
3032
+ const isMap = !!(0, psdReader_1.readUint8)(reader); // curves drawn with pencil tool (lookup tables instead of points)
3033
+ const version = (0, psdReader_1.readUint16)(reader);
3034
+ if (version !== 1 && version !== 4)
2651
3035
  throw new Error('Invalid curv version');
2652
- (0, psdReader_1.readUint16)(reader);
2653
- const channels = (0, psdReader_1.readUint16)(reader);
2654
- const info = { type: 'curves' };
2655
- if (channels & 1)
2656
- info.rgb = readCurveChannel(reader);
2657
- if (channels & 2)
2658
- info.red = readCurveChannel(reader);
2659
- if (channels & 4)
2660
- info.green = readCurveChannel(reader);
2661
- if (channels & 8)
2662
- info.blue = readCurveChannel(reader);
2663
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), info);
2664
- // ignoring, duplicate information
2665
- // checkSignature(reader, 'Crv ');
2666
- // const cVersion = readUint16(reader);
2667
- // readUint16(reader);
2668
- // const channelCount = readUint16(reader);
2669
- // for (let i = 0; i < channelCount; i++) {
2670
- // const index = readUint16(reader);
2671
- // const nodes = readUint16(reader);
2672
- // for (let j = 0; j < nodes; j++) {
2673
- // const output = readInt16(reader);
2674
- // const input = readInt16(reader);
2675
- // }
2676
- // }
2677
- (0, psdReader_1.skipBytes)(reader, left());
2678
- }, (writer, target) => {
2679
- const info = target.adjustment;
2680
- const { rgb, red, green, blue } = info;
2681
- let channels = 0;
2682
- let channelCount = 0;
2683
- if (rgb && rgb.length) {
2684
- channels |= 1;
2685
- channelCount++;
2686
- }
2687
- if (red && red.length) {
2688
- channels |= 2;
2689
- channelCount++;
3036
+ const countOrMask = (0, psdReader_1.readUint32)(reader);
3037
+ const channels = [];
3038
+ const readChannel = () => isMap ? Array.from((0, psdReader_1.readBytes)(reader, 256)) : readCurveChannel(reader);
3039
+ if (version === 1) {
3040
+ for (let i = 0; i < 32; i++) {
3041
+ if (countOrMask & (1 << i)) {
3042
+ while (channels.length < i)
3043
+ channels.push([]);
3044
+ channels.push(readChannel());
3045
+ }
3046
+ }
2690
3047
  }
2691
- if (green && green.length) {
2692
- channels |= 4;
2693
- channelCount++;
3048
+ else {
3049
+ if (countOrMask > 64)
3050
+ throw new Error('Invalid curv channel count');
3051
+ for (let i = 0; i < countOrMask; i++)
3052
+ channels.push(readChannel());
3053
+ }
3054
+ // extra section stores curves for all channels (including ones that don't fit in the mask)
3055
+ if (version === 1 && left() >= 10 && (0, psdReader_1.readSignature)(reader) === 'Crv ') {
3056
+ (0, psdReader_1.readUint16)(reader); // version
3057
+ const count = (0, psdReader_1.readUint32)(reader);
3058
+ for (let i = 0; i < count && left() >= 2; i++) {
3059
+ const index = (0, psdReader_1.readUint16)(reader);
3060
+ const channel = readChannel();
3061
+ while (channels.length < index)
3062
+ channels.push([]);
3063
+ channels[index] = channel;
3064
+ }
2694
3065
  }
2695
- if (blue && blue.length) {
2696
- channels |= 8;
2697
- channelCount++;
3066
+ target.adjustment = Object.assign(Object.assign(Object.assign({}, target.adjustment), { type: 'curves' }), (isMap ? { values: channels } : { channels: channels }));
3067
+ (0, psdReader_1.skipBytes)(reader, left());
3068
+ }, (writer, target) => {
3069
+ var _a;
3070
+ const adjustment = target.adjustment;
3071
+ const isMap = !!adjustment.values;
3072
+ const channels = (isMap ? adjustment.values : adjustment.channels) || [];
3073
+ const used = [];
3074
+ let channelsMask = 0;
3075
+ for (let i = 0; i < channels.length; i++) {
3076
+ if ((_a = channels[i]) === null || _a === void 0 ? void 0 : _a.length) {
3077
+ if (i < 32)
3078
+ channelsMask |= 1 << i;
3079
+ used.push(i);
3080
+ }
2698
3081
  }
2699
- (0, psdWriter_1.writeUint8)(writer, 0);
3082
+ const writeChannel = (channel) => {
3083
+ var _a;
3084
+ if (isMap) {
3085
+ for (let i = 0; i < 256; i++)
3086
+ (0, psdWriter_1.writeUint8)(writer, (0, helpers_1.clamp)(Math.round((_a = channel[i]) !== null && _a !== void 0 ? _a : i), 0, 255));
3087
+ }
3088
+ else {
3089
+ writeCurveChannel(writer, channel);
3090
+ }
3091
+ };
3092
+ (0, psdWriter_1.writeUint8)(writer, isMap ? 1 : 0);
2700
3093
  (0, psdWriter_1.writeUint16)(writer, 1); // version
2701
- (0, psdWriter_1.writeUint16)(writer, 0);
2702
- (0, psdWriter_1.writeUint16)(writer, channels);
2703
- if (rgb && rgb.length)
2704
- writeCurveChannel(writer, rgb);
2705
- if (red && red.length)
2706
- writeCurveChannel(writer, red);
2707
- if (green && green.length)
2708
- writeCurveChannel(writer, green);
2709
- if (blue && blue.length)
2710
- writeCurveChannel(writer, blue);
3094
+ (0, psdWriter_1.writeUint32)(writer, channelsMask >>> 0);
3095
+ for (const i of used)
3096
+ if (i < 32)
3097
+ writeChannel(channels[i]);
2711
3098
  (0, psdWriter_1.writeSignature)(writer, 'Crv ');
2712
3099
  (0, psdWriter_1.writeUint16)(writer, 4); // version
2713
- (0, psdWriter_1.writeUint16)(writer, 0);
2714
- (0, psdWriter_1.writeUint16)(writer, channelCount);
2715
- if (rgb && rgb.length) {
2716
- (0, psdWriter_1.writeUint16)(writer, 0);
2717
- writeCurveChannel(writer, rgb);
2718
- }
2719
- if (red && red.length) {
2720
- (0, psdWriter_1.writeUint16)(writer, 1);
2721
- writeCurveChannel(writer, red);
2722
- }
2723
- if (green && green.length) {
2724
- (0, psdWriter_1.writeUint16)(writer, 2);
2725
- writeCurveChannel(writer, green);
2726
- }
2727
- if (blue && blue.length) {
2728
- (0, psdWriter_1.writeUint16)(writer, 3);
2729
- writeCurveChannel(writer, blue);
3100
+ (0, psdWriter_1.writeUint32)(writer, used.length);
3101
+ for (const i of used) {
3102
+ (0, psdWriter_1.writeUint16)(writer, i);
3103
+ writeChannel(channels[i]);
2730
3104
  }
2731
3105
  });
2732
3106
  addHandler('expA', adjustmentType('exposure'), (reader, target, left) => {
@@ -2783,12 +3157,34 @@ function writeHueChannel(writer, channel) {
2783
3157
  addHandler('hue2', adjustmentType('hue/saturation'), (reader, target, left) => {
2784
3158
  if ((0, psdReader_1.readUint16)(reader) !== 2)
2785
3159
  throw new Error('Invalid hue2 version');
2786
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'hue/saturation', master: readHueChannel(reader), reds: readHueChannel(reader), yellows: readHueChannel(reader), greens: readHueChannel(reader), cyans: readHueChannel(reader), blues: readHueChannel(reader), magentas: readHueChannel(reader) });
3160
+ const colorize = !!(0, psdReader_1.readUint8)(reader);
3161
+ (0, psdReader_1.skipBytes)(reader, 1);
3162
+ const colorizeHue = (0, psdReader_1.readInt16)(reader); // stored as -180..180
3163
+ const colorizeSaturation = (0, psdReader_1.readInt16)(reader);
3164
+ const colorizeLightness = (0, psdReader_1.readInt16)(reader);
3165
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'hue/saturation', colorize, colorization: {
3166
+ hue: colorizeHue < 0 ? colorizeHue + 360 : colorizeHue,
3167
+ saturation: colorizeSaturation,
3168
+ lightness: colorizeLightness,
3169
+ }, master: {
3170
+ hue: (0, psdReader_1.readInt16)(reader),
3171
+ saturation: (0, psdReader_1.readInt16)(reader),
3172
+ lightness: (0, psdReader_1.readInt16)(reader),
3173
+ }, reds: readHueChannel(reader), yellows: readHueChannel(reader), greens: readHueChannel(reader), cyans: readHueChannel(reader), blues: readHueChannel(reader), magentas: readHueChannel(reader) });
2787
3174
  (0, psdReader_1.skipBytes)(reader, left());
2788
3175
  }, (writer, target) => {
3176
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
2789
3177
  const info = target.adjustment;
2790
3178
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2791
- writeHueChannel(writer, info.master);
3179
+ (0, psdWriter_1.writeUint8)(writer, info.colorize ? 1 : 0);
3180
+ (0, psdWriter_1.writeUint8)(writer, 0);
3181
+ const colorizeHue = Math.round((_b = (_a = info.colorization) === null || _a === void 0 ? void 0 : _a.hue) !== null && _b !== void 0 ? _b : 0);
3182
+ (0, psdWriter_1.writeInt16)(writer, colorizeHue > 180 ? colorizeHue - 360 : colorizeHue);
3183
+ (0, psdWriter_1.writeInt16)(writer, Math.round((_d = (_c = info.colorization) === null || _c === void 0 ? void 0 : _c.saturation) !== null && _d !== void 0 ? _d : 0));
3184
+ (0, psdWriter_1.writeInt16)(writer, Math.round((_f = (_e = info.colorization) === null || _e === void 0 ? void 0 : _e.lightness) !== null && _f !== void 0 ? _f : 0));
3185
+ (0, psdWriter_1.writeInt16)(writer, Math.round((_h = (_g = info.master) === null || _g === void 0 ? void 0 : _g.hue) !== null && _h !== void 0 ? _h : 0));
3186
+ (0, psdWriter_1.writeInt16)(writer, Math.round((_k = (_j = info.master) === null || _j === void 0 ? void 0 : _j.saturation) !== null && _k !== void 0 ? _k : 0));
3187
+ (0, psdWriter_1.writeInt16)(writer, Math.round((_m = (_l = info.master) === null || _l === void 0 ? void 0 : _l.lightness) !== null && _m !== void 0 ? _m : 0));
2792
3188
  writeHueChannel(writer, info.reds);
2793
3189
  writeHueChannel(writer, info.yellows);
2794
3190
  writeHueChannel(writer, info.greens);
@@ -2867,16 +3263,18 @@ addHandler('phfl', adjustmentType('photo filter'), (reader, target, left) => {
2867
3263
  color = (0, psdReader_1.readColor)(reader);
2868
3264
  }
2869
3265
  else { // version 3
2870
- // TODO: test this, this is probably wrong
2871
- // values are stored as L (0..100), a, b (-128..127) multiplied by 100,
2872
- // normalize them to the same scale as readColor() so they can be written back as version 2
2873
- const l = (0, psdReader_1.readInt32)(reader) / 100;
2874
- const a = (0, psdReader_1.readInt32)(reader) / 100;
2875
- const b = (0, psdReader_1.readInt32)(reader) / 100;
3266
+ // XYZ color relative to D50 white point, 32768 = 1.0 (not documented, matches colors of Photoshop presets),
3267
+ // converted to Lab (same scale as readColor()) so it can be written back as version 2
3268
+ const x = (0, psdReader_1.readInt32)(reader) / 32768;
3269
+ const y = (0, psdReader_1.readInt32)(reader) / 32768;
3270
+ const z = (0, psdReader_1.readInt32)(reader) / 32768;
3271
+ if (!(x >= 0 && y >= 0 && z >= 0 && x <= 2 && y <= 2 && z <= 2))
3272
+ throw new Error('Invalid phfl XYZ color');
3273
+ const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
2876
3274
  color = {
2877
- l: l / 100,
2878
- a: (0, helpers_1.normalizeLabAB)(a),
2879
- b: (0, helpers_1.normalizeLabAB)(b),
3275
+ l: Math.max(0, 116 * f(y) - 16) / 100,
3276
+ a: (0, helpers_1.normalizeLabAB)(500 * (f(x) - f(y))),
3277
+ b: (0, helpers_1.normalizeLabAB)(200 * (f(y) - f(z))),
2880
3278
  };
2881
3279
  }
2882
3280
  target.adjustment = {
@@ -3044,7 +3442,7 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3044
3442
  reader.offset--;
3045
3443
  if (hasMethod) {
3046
3444
  const method = (0, psdReader_1.readSignature)(reader);
3047
- info.method = descriptor_1.gradientInterpolationMethodType.decode(method);
3445
+ info.method = descriptor_1.gradientInterpolationMethodType.decode(`gradientInterpolationMethodType.${method}`);
3048
3446
  }
3049
3447
  info.name = (0, psdReader_1.readUnicodeString)(reader);
3050
3448
  info.colorStops = [];
@@ -3106,7 +3504,7 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3106
3504
  (0, psdWriter_1.writeUint8)(writer, info.reverse ? 1 : 0);
3107
3505
  (0, psdWriter_1.writeUint8)(writer, info.dither ? 1 : 0);
3108
3506
  if (info.method !== undefined) {
3109
- (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method));
3507
+ (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method).split('.')[1]);
3110
3508
  }
3111
3509
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, info.name || '');
3112
3510
  (0, psdWriter_1.writeUint16)(writer, info.colorStops && info.colorStops.length || 0);
@@ -3414,6 +3812,10 @@ addHandler('FEid', hasKey('filterEffectsMasks'), (reader, target, leftBytes) =>
3414
3812
  }
3415
3813
  });
3416
3814
  addHandlerAlias('FXid', 'FEid');
3815
+ addHandlerAlias('FELS', 'FEid'); // filter effects 3
3816
+ addHandlerAlias('hue ', 'hue2'); // old hue/saturation (Photoshop 4.0), same structure as 'hue2'
3817
+ addHandlerAlias('plLd', 'PlLd'); // old placed layer key
3818
+ addHandlerAlias('abdd', 'artb'); // artboard data 3
3417
3819
  addHandler('FMsk', hasKey('filterMask'), (reader, target) => {
3418
3820
  target.filterMask = {
3419
3821
  colorSpace: (0, psdReader_1.readColor)(reader),
@@ -3428,17 +3830,22 @@ addHandler('artd', // document-wide artboard info
3428
3830
  // document-wide artboard info
3429
3831
  target => target.artboards !== undefined, (reader, target, left) => {
3430
3832
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3431
- target.artboards = {
3432
- count: desc['Cnt '],
3433
- autoExpandOffset: { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc },
3434
- origin: { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc },
3435
- autoExpandEnabled: desc.autoExpandEnabled,
3436
- autoNestEnabled: desc.autoNestEnabled,
3437
- autoPositionEnabled: desc.autoPositionEnabled,
3438
- shrinkwrapOnSaveEnabled: !!desc.shrinkwrapOnSaveEnabled,
3439
- docDefaultNewArtboardBackgroundColor: (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor),
3440
- docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
3441
- };
3833
+ const artboards = target.artboards = { count: desc['Cnt '] };
3834
+ if (desc.autoExpandOffset)
3835
+ artboards.autoExpandOffset = { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc };
3836
+ if (desc.origin)
3837
+ artboards.origin = { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc };
3838
+ if (desc.autoExpandEnabled !== undefined)
3839
+ artboards.autoExpandEnabled = desc.autoExpandEnabled;
3840
+ if (desc.autoNestEnabled !== undefined)
3841
+ artboards.autoNestEnabled = desc.autoNestEnabled;
3842
+ if (desc.autoPositionEnabled !== undefined)
3843
+ artboards.autoPositionEnabled = desc.autoPositionEnabled;
3844
+ artboards.shrinkwrapOnSaveEnabled = !!desc.shrinkwrapOnSaveEnabled;
3845
+ if (desc.docDefaultNewArtboardBackgroundColor)
3846
+ artboards.docDefaultNewArtboardBackgroundColor = (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor);
3847
+ if (desc.docDefaultNewArtboardBackgroundType !== undefined)
3848
+ artboards.docDefaultNewArtboardBackgroundType = desc.docDefaultNewArtboardBackgroundType;
3442
3849
  (0, psdReader_1.skipBytes)(reader, left());
3443
3850
  }, (writer, target) => {
3444
3851
  var _a, _b, _c, _d, _e;
@@ -3456,21 +3863,23 @@ target => target.artboards !== undefined, (reader, target, left) => {
3456
3863
  };
3457
3864
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, 'artd');
3458
3865
  });
3866
+ function isGroupLayer(target) {
3867
+ return !!target.children;
3868
+ }
3459
3869
  function hasMultiEffects(effects) {
3460
3870
  return Object.keys(effects).map(key => effects[key]).some(v => Array.isArray(v) && v.length > 1);
3461
3871
  }
3462
- addHandler('lfx2', target => target.effects !== undefined && !hasMultiEffects(target.effects), (reader, target, left) => {
3872
+ addHandler('lfx2', target => target.effects !== undefined && !isGroupLayer(target) && !hasMultiEffects(target.effects), (reader, target, left) => {
3463
3873
  const version = (0, psdReader_1.readUint32)(reader);
3464
3874
  if (version !== 0)
3465
3875
  throw new Error(`Invalid lfx2 version`);
3466
3876
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3467
3877
  // console.log('READ', require('util').inspect(desc, false, 99, true));
3468
- // TODO: don't discard if we got it from lmfx
3469
- // discard if read in 'lrFX' section
3470
- target.effects = (0, descriptor_1.parseEffects)(desc, !!reader.logMissingFeatures);
3878
+ // discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
3879
+ target.effects = (0, descriptor_1.parseEffects)(desc, reader);
3471
3880
  (0, psdReader_1.skipBytes)(reader, left());
3472
- }, (writer, target, _, options) => {
3473
- const desc = (0, descriptor_1.serializeEffects)(target.effects, !!options.logMissingFeatures, true);
3881
+ }, (writer, target) => {
3882
+ const desc = (0, descriptor_1.serializeEffects)(target.effects, writer, true);
3474
3883
  // console.log('WRITE', require('util').inspect(desc, false, 99, true));
3475
3884
  (0, psdWriter_1.writeUint32)(writer, 0); // version
3476
3885
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
@@ -3523,16 +3932,74 @@ addHandler('cinf', hasKey('compositorUsed'), (reader, target, left) => {
3523
3932
  desc.compCoreGPUSupport = `reason.${cinf.compCoreGPUSupport}`;
3524
3933
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
3525
3934
  });
3526
- // extension settings ?, ignore it
3527
- addHandler('extn', target => target._extn !== undefined, (reader, target) => {
3935
+ // generator settings of the document (layers store them in 'shmd' section)
3936
+ addHandler('extn', target => target.generatorSettings !== undefined && isDocument(target), (reader, target, left) => {
3528
3937
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3529
- // console.log(require('util').inspect(desc, false, 99, true));
3530
- if (helpers_1.MOCK_HANDLERS)
3531
- target._extn = desc;
3938
+ if (desc.generatorSettings)
3939
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
3940
+ (0, psdReader_1.skipBytes)(reader, left());
3532
3941
  }, (writer, target) => {
3533
- // TODO: need to add correct types for desc fields (resources/src.psd)
3534
- if (helpers_1.MOCK_HANDLERS)
3535
- (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', target._extn);
3942
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(target.generatorSettings));
3943
+ });
3944
+ // generator settings stored by older versions of Photoshop (written back in 'shmd' or 'extn' section)
3945
+ addHandler('extd', () => false, (reader, target, left) => {
3946
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3947
+ if (desc.generatorSettings && target.generatorSettings === undefined) {
3948
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
3949
+ }
3950
+ (0, psdReader_1.skipBytes)(reader, left());
3951
+ }, () => { });
3952
+ addHandler('frgb', // frame tool group
3953
+ hasKey('framedGroup'), (reader, target, left) => {
3954
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3955
+ const rect = desc.framedGroupRect;
3956
+ target.framedGroup = {
3957
+ rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
3958
+ type: desc.framedGroupType,
3959
+ };
3960
+ if (desc.LockPPI !== undefined)
3961
+ target.framedGroup.lockPPI = desc.LockPPI;
3962
+ if (desc.PreferredResolution !== undefined)
3963
+ target.framedGroup.preferredResolution = desc.PreferredResolution;
3964
+ if (desc.SavedIteratedName !== undefined)
3965
+ target.framedGroup.savedIteratedName = desc.SavedIteratedName;
3966
+ (0, psdReader_1.skipBytes)(reader, left());
3967
+ }, (writer, target) => {
3968
+ const group = target.framedGroup;
3969
+ const rect = group.rect;
3970
+ const desc = {
3971
+ framedGroupRect: { _name: '', _classID: 'classFloatRect', 'Top ': rect.top, Left: rect.left, Btom: rect.bottom, Rght: rect.right },
3972
+ framedGroupType: group.type,
3973
+ };
3974
+ if (group.lockPPI !== undefined)
3975
+ desc.LockPPI = group.lockPPI;
3976
+ if (group.preferredResolution !== undefined)
3977
+ desc.PreferredResolution = group.preferredResolution;
3978
+ if (group.savedIteratedName !== undefined)
3979
+ desc.SavedIteratedName = group.savedIteratedName;
3980
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'framedGroup', desc);
3981
+ });
3982
+ addHandler('anFX', hasKey('animationEffects'), (reader, target, left) => {
3983
+ target.animationEffects = (0, psdReader_1.readBytes)(reader, left());
3984
+ }, (writer, target) => {
3985
+ (0, psdWriter_1.writeBytes)(writer, target.animationEffects);
3986
+ });
3987
+ addHandler('PxSD', // pixel source data 2 (video layer frames)
3988
+ hasKey('pixelSourceData'), (reader, target, left) => {
3989
+ target.pixelSourceData = [];
3990
+ while (left() >= 8) {
3991
+ const length = readLength64(reader);
3992
+ if (length > left())
3993
+ throw new Error('Invalid PxSD data length');
3994
+ target.pixelSourceData.push((0, psdReader_1.readBytes)(reader, length));
3995
+ }
3996
+ (0, psdReader_1.skipBytes)(reader, left());
3997
+ }, (writer, target) => {
3998
+ for (const data of target.pixelSourceData) {
3999
+ (0, psdWriter_1.writeUint32)(writer, 0);
4000
+ (0, psdWriter_1.writeUint32)(writer, data.byteLength);
4001
+ (0, psdWriter_1.writeBytes)(writer, data);
4002
+ }
3536
4003
  });
3537
4004
  addHandler('iOpa', hasKey('fillOpacity'), (reader, target) => {
3538
4005
  target.fillOpacity = (0, psdReader_1.readUint8)(reader) / 0xff;