ag-psd 31.0.3 → 33.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 (159) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  25. package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
  26. package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
  27. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  28. package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
  29. package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
  30. package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
  31. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  32. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  33. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
  34. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
  35. package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
  36. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  37. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  38. package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
  39. package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
  40. package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
  41. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  42. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  43. package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
  44. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  45. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  46. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
  47. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  48. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  49. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  50. package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
  51. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  52. package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
  53. package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
  54. package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
  55. package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
  56. package/CHANGELOG.md +107 -0
  57. package/LICENSE +5 -0
  58. package/README.md +3 -6
  59. package/README_PSD.md +242 -81
  60. package/dist/abr.d.ts +11 -4
  61. package/dist/abr.js +363 -229
  62. package/dist/abr.js.map +1 -1
  63. package/dist/additionalInfo.d.ts +56 -2
  64. package/dist/additionalInfo.js +600 -364
  65. package/dist/additionalInfo.js.map +1 -1
  66. package/dist/ase.d.ts +6 -1
  67. package/dist/ase.js +61 -61
  68. package/dist/ase.js.map +1 -1
  69. package/dist/bundle.js +3096 -1541
  70. package/dist/descriptor.d.ts +25 -6
  71. package/dist/descriptor.js +380 -135
  72. package/dist/descriptor.js.map +1 -1
  73. package/dist/effectsHelpers.js +12 -12
  74. package/dist/effectsHelpers.js.map +1 -1
  75. package/dist/engineData.d.ts +3 -1
  76. package/dist/engineData.js +34 -13
  77. package/dist/engineData.js.map +1 -1
  78. package/dist/engineData2.js +50 -21
  79. package/dist/engineData2.js.map +1 -1
  80. package/dist/helpers.d.ts +6 -3
  81. package/dist/helpers.js +22 -6
  82. package/dist/helpers.js.map +1 -1
  83. package/dist/imageResources.d.ts +15 -3
  84. package/dist/imageResources.js +529 -144
  85. package/dist/imageResources.js.map +1 -1
  86. package/dist/index.js +1 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/jpeg.js +30 -10
  89. package/dist/jpeg.js.map +1 -1
  90. package/dist/psd.d.ts +154 -18
  91. package/dist/psd.js.map +1 -1
  92. package/dist/psdReader.d.ts +12 -2
  93. package/dist/psdReader.js +720 -421
  94. package/dist/psdReader.js.map +1 -1
  95. package/dist/psdWriter.d.ts +3 -0
  96. package/dist/psdWriter.js +167 -130
  97. package/dist/psdWriter.js.map +1 -1
  98. package/dist/text.js +143 -29
  99. package/dist/text.js.map +1 -1
  100. package/dist/utf8.js +36 -18
  101. package/dist/utf8.js.map +1 -1
  102. package/dist-es/abr.d.ts +11 -4
  103. package/dist-es/abr.js +363 -229
  104. package/dist-es/abr.js.map +1 -1
  105. package/dist-es/additionalInfo.d.ts +56 -2
  106. package/dist-es/additionalInfo.js +600 -364
  107. package/dist-es/additionalInfo.js.map +1 -1
  108. package/dist-es/ase.d.ts +6 -1
  109. package/dist-es/ase.js +61 -61
  110. package/dist-es/ase.js.map +1 -1
  111. package/dist-es/descriptor.d.ts +25 -6
  112. package/dist-es/descriptor.js +380 -135
  113. package/dist-es/descriptor.js.map +1 -1
  114. package/dist-es/effectsHelpers.js +12 -12
  115. package/dist-es/effectsHelpers.js.map +1 -1
  116. package/dist-es/engineData.d.ts +3 -1
  117. package/dist-es/engineData.js +34 -13
  118. package/dist-es/engineData.js.map +1 -1
  119. package/dist-es/engineData2.js +50 -21
  120. package/dist-es/engineData2.js.map +1 -1
  121. package/dist-es/helpers.d.ts +6 -3
  122. package/dist-es/helpers.js +22 -6
  123. package/dist-es/helpers.js.map +1 -1
  124. package/dist-es/imageResources.d.ts +15 -3
  125. package/dist-es/imageResources.js +529 -144
  126. package/dist-es/imageResources.js.map +1 -1
  127. package/dist-es/index.js +1 -0
  128. package/dist-es/index.js.map +1 -1
  129. package/dist-es/jpeg.js +30 -10
  130. package/dist-es/jpeg.js.map +1 -1
  131. package/dist-es/psd.d.ts +154 -18
  132. package/dist-es/psd.js.map +1 -1
  133. package/dist-es/psdReader.d.ts +12 -2
  134. package/dist-es/psdReader.js +720 -421
  135. package/dist-es/psdReader.js.map +1 -1
  136. package/dist-es/psdWriter.d.ts +3 -0
  137. package/dist-es/psdWriter.js +167 -130
  138. package/dist-es/psdWriter.js.map +1 -1
  139. package/dist-es/text.js +143 -29
  140. package/dist-es/text.js.map +1 -1
  141. package/dist-es/utf8.js +36 -18
  142. package/dist-es/utf8.js.map +1 -1
  143. package/package.json +2 -2
  144. package/src/abr.ts +380 -232
  145. package/src/additionalInfo.ts +781 -477
  146. package/src/ase.ts +66 -61
  147. package/src/descriptor.ts +386 -142
  148. package/src/effectsHelpers.ts +9 -9
  149. package/src/engineData.ts +36 -13
  150. package/src/engineData2.ts +468 -432
  151. package/src/helpers.ts +23 -5
  152. package/src/imageResources.ts +658 -167
  153. package/src/index.ts +1 -0
  154. package/src/jpeg.ts +27 -12
  155. package/src/psd.ts +158 -30
  156. package/src/psdReader.ts +732 -414
  157. package/src/psdWriter.ts +186 -132
  158. package/src/text.ts +139 -18
  159. package/src/utf8.ts +35 -21
@@ -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");
@@ -32,11 +35,8 @@ function readLength64(reader) {
32
35
  throw new Error(`Resource size above 4 GB limit at ${reader.offset.toString(16)}`);
33
36
  return (0, psdReader_1.readUint32)(reader);
34
37
  }
35
- function writeLength64(writer, length) {
36
- (0, psdWriter_1.writeUint32)(writer, 0);
37
- (0, psdWriter_1.writeUint32)(writer, length);
38
- }
39
38
  addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
39
+ var _a;
40
40
  if ((0, psdReader_1.readInt16)(reader) !== 1)
41
41
  throw new Error(`Invalid TySh version`);
42
42
  const transform = [];
@@ -74,7 +74,7 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
74
74
  if (text.boundingBox)
75
75
  target.text.boundingBox = (0, descriptor_1.descBoundsToBounds)(text.boundingBox);
76
76
  if (text.EngineData) {
77
- const engineData = (0, engineData_1.parseEngineData)(text.EngineData);
77
+ const engineData = (0, engineData_1.parseEngineData)(text.EngineData, reader);
78
78
  const textData = (0, text_1.decodeEngineData)(engineData);
79
79
  // console.log(require('util').inspect(engineData, false, 99, false), 'utf8');
80
80
  // require('fs').writeFileSync(`layer-${target.name}.txt`, require('util').inspect(engineData, false, 99, false), 'utf8');
@@ -83,11 +83,13 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
83
83
  // require('fs').writeFileSync('before.txt', require('util').inspect(before, false, 99, false), 'utf8');
84
84
  // require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
85
85
  // console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
86
- 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 });
87
88
  // console.log(require('util').inspect(target.text, false, 99, true));
88
89
  }
89
90
  (0, psdReader_1.skipBytes)(reader, leftBytes());
90
91
  }, (writer, target) => {
92
+ var _a, _b, _c, _d;
91
93
  const text = target.text;
92
94
  const warp = text.warp || {};
93
95
  const transform = text.transform || [1, 0, 0, 1, 0, 0];
@@ -100,10 +102,10 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
100
102
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'TxLr', textDescriptor, 'text');
101
103
  (0, psdWriter_1.writeInt16)(writer, 1); // warp version
102
104
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'warp', encodeWarp(warp));
103
- (0, psdWriter_1.writeFloat32)(writer, text.left);
104
- (0, psdWriter_1.writeFloat32)(writer, text.top);
105
- (0, psdWriter_1.writeFloat32)(writer, text.right);
106
- (0, psdWriter_1.writeFloat32)(writer, text.bottom);
105
+ (0, psdWriter_1.writeFloat32)(writer, (_a = text.left) !== null && _a !== void 0 ? _a : 0);
106
+ (0, psdWriter_1.writeFloat32)(writer, (_b = text.top) !== null && _b !== void 0 ? _b : 0);
107
+ (0, psdWriter_1.writeFloat32)(writer, (_c = text.right) !== null && _c !== void 0 ? _c : 0);
108
+ (0, psdWriter_1.writeFloat32)(writer, (_d = text.bottom) !== null && _d !== void 0 ? _d : 0);
107
109
  // writeZeros(writer, 2);
108
110
  });
109
111
  // vector fills
@@ -212,25 +214,8 @@ function readVectorMask(reader, vectorMask, width, height, size) {
212
214
  }
213
215
  return paths;
214
216
  }
215
- addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
216
- if ((0, psdReader_1.readUint32)(reader) !== 3)
217
- throw new Error('Invalid vmsk version');
218
- target.vectorMask = { paths: [] };
219
- const vectorMask = target.vectorMask;
220
- const flags = (0, psdReader_1.readUint32)(reader);
221
- vectorMask.invert = (flags & 1) !== 0;
222
- vectorMask.notLink = (flags & 2) !== 0;
223
- vectorMask.disable = (flags & 4) !== 0;
224
- readVectorMask(reader, vectorMask, width, height, left());
225
- // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
226
- (0, psdReader_1.skipBytes)(reader, left());
227
- }, (writer, target, { width, height }) => {
228
- const vectorMask = target.vectorMask;
229
- const flags = (vectorMask.invert ? 1 : 0) |
230
- (vectorMask.notLink ? 2 : 0) |
231
- (vectorMask.disable ? 4 : 0);
232
- (0, psdWriter_1.writeUint32)(writer, 3); // version
233
- (0, psdWriter_1.writeUint32)(writer, flags);
217
+ // componentIndices - write index of path component for each subpath (Photoshop writes them for layer vector masks)
218
+ function writeVectorMask(writer, vectorMask, width, height, componentIndices = false) {
234
219
  // initial entry
235
220
  (0, psdWriter_1.writeUint16)(writer, 6);
236
221
  (0, psdWriter_1.writeZeros)(writer, 24);
@@ -247,12 +232,18 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
247
232
  (0, psdWriter_1.writeUint16)(writer, 8);
248
233
  (0, psdWriter_1.writeUint16)(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
249
234
  (0, psdWriter_1.writeZeros)(writer, 22);
235
+ let componentIndex = -1;
250
236
  for (const path of vectorMask.paths) {
237
+ // subpaths without operation are part of the same path component as previous subpath
238
+ if (path.operation || componentIndex < 0)
239
+ componentIndex++;
251
240
  (0, psdWriter_1.writeUint16)(writer, path.open ? 3 : 0);
252
241
  (0, psdWriter_1.writeUint16)(writer, path.knots.length);
253
242
  (0, psdWriter_1.writeUint16)(writer, path.operation ? exports.booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
254
243
  (0, psdWriter_1.writeUint16)(writer, path.fillRule === 'non-zero' ? 2 : 1);
255
- (0, psdWriter_1.writeZeros)(writer, 18); // TODO: these are sometimes non-zero
244
+ (0, psdWriter_1.writeZeros)(writer, 4);
245
+ (0, psdWriter_1.writeUint32)(writer, componentIndices ? componentIndex : 0);
246
+ (0, psdWriter_1.writeZeros)(writer, 10);
256
247
  const linkedKnot = path.open ? 4 : 1;
257
248
  const unlinkedKnot = path.open ? 5 : 2;
258
249
  for (const { linked, points } of path.knots) {
@@ -260,8 +251,29 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
260
251
  writeBezierKnot(writer, points, width, height);
261
252
  }
262
253
  }
254
+ }
255
+ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
256
+ if ((0, psdReader_1.readUint32)(reader) !== 3)
257
+ throw new Error('Invalid vmsk version');
258
+ target.vectorMask = { paths: [] };
259
+ const vectorMask = target.vectorMask;
260
+ const flags = (0, psdReader_1.readUint32)(reader);
261
+ vectorMask.invert = (flags & 1) !== 0;
262
+ vectorMask.notLink = (flags & 2) !== 0;
263
+ vectorMask.disable = (flags & 4) !== 0;
264
+ readVectorMask(reader, vectorMask, width, height, left());
265
+ // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
266
+ (0, psdReader_1.skipBytes)(reader, left());
267
+ }, (writer, target, { width, height }) => {
268
+ const vectorMask = target.vectorMask;
269
+ const flags = (vectorMask.invert ? 1 : 0) |
270
+ (vectorMask.notLink ? 2 : 0) |
271
+ (vectorMask.disable ? 4 : 0);
272
+ (0, psdWriter_1.writeUint32)(writer, 3); // version
273
+ (0, psdWriter_1.writeUint32)(writer, flags);
274
+ writeVectorMask(writer, vectorMask, width, height, true);
263
275
  });
264
- // TODO: need to write vmsk if has outline ?
276
+ // written instead of 'vmsk' for layers with vector stroke ('vscg' section)
265
277
  addHandlerAlias('vsms', 'vmsk');
266
278
  // addHandlerAlias('vmsk', 'vsms');
267
279
  addHandler('vowv', // something with vectors?
@@ -270,12 +282,8 @@ hasKey('vowv'), (reader, target) => {
270
282
  }, (writer, target) => {
271
283
  (0, psdWriter_1.writeUint32)(writer, target.vowv);
272
284
  });
273
- addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
274
- if ((0, psdReader_1.readInt32)(reader) !== 1)
275
- throw new Error(`Invalid vogk version`);
276
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
277
- // console.log(require('util').inspect(desc, false, 99, true));
278
- target.vectorOrigination = { keyDescriptorList: [] };
285
+ function parseKeyDescriptorList(desc) {
286
+ const keyDescriptorList = [];
279
287
  for (const i of desc.keyDescriptorList) {
280
288
  const item = {};
281
289
  if (i.keyShapeInvalidated != null)
@@ -314,15 +322,14 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
314
322
  if (trnf) {
315
323
  item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
316
324
  }
317
- target.vectorOrigination.keyDescriptorList.push(item);
325
+ keyDescriptorList.push(item);
318
326
  }
319
- (0, psdReader_1.skipBytes)(reader, left());
320
- }, (writer, target) => {
321
- target;
322
- const orig = target.vectorOrigination;
327
+ return keyDescriptorList;
328
+ }
329
+ function serializeKeyDescriptorList(keyDescriptorList) {
323
330
  const desc = { keyDescriptorList: [] };
324
- for (let i = 0; i < orig.keyDescriptorList.length; i++) {
325
- const item = orig.keyDescriptorList[i];
331
+ for (let i = 0; i < keyDescriptorList.length; i++) {
332
+ const item = keyDescriptorList[i];
326
333
  desc.keyDescriptorList.push({}); // we're adding keyOriginIndex at the end
327
334
  const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
328
335
  if (item.keyOriginType != null)
@@ -373,10 +380,21 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
373
380
  out.keyShapeInvalidated = item.keyShapeInvalidated;
374
381
  out.keyOriginIndex = i;
375
382
  }
383
+ return desc;
384
+ }
385
+ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
386
+ if ((0, psdReader_1.readInt32)(reader) !== 1)
387
+ throw new Error(`Invalid vogk version`);
388
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
389
+ // console.log(require('util').inspect(desc, false, 99, true));
390
+ target.vectorOrigination = { keyDescriptorList: parseKeyDescriptorList(desc) };
391
+ (0, psdReader_1.skipBytes)(reader, left());
392
+ }, (writer, target) => {
393
+ const desc = serializeKeyDescriptorList(target.vectorOrigination.keyDescriptorList);
376
394
  (0, psdWriter_1.writeInt32)(writer, 1); // version
377
395
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
378
396
  });
379
- addHandler('lmfx', target => target.effects !== undefined && hasMultiEffects(target.effects), (reader, target, left) => {
397
+ addHandler('lmfx', target => target.effects !== undefined && !isGroupLayer(target) && hasMultiEffects(target.effects), (reader, target, left) => {
380
398
  const version = (0, psdReader_1.readUint32)(reader);
381
399
  if (version !== 0)
382
400
  throw new Error('Invalid lmfx version');
@@ -419,12 +437,54 @@ addHandler('luni', hasKey('name'), (reader, target, left) => {
419
437
  // writeUint16(writer, 0); // padding (but not extending string length)
420
438
  });
421
439
  addHandler('lnsr', hasKey('nameSource'), (reader, target) => target.nameSource = (0, psdReader_1.readSignature)(reader), (writer, target) => (0, psdWriter_1.writeSignature)(writer, target.nameSource));
422
- addHandler('lyid', hasKey('id'), (reader, target) => {
440
+ // these sections reference layer id
441
+ function layerNeedsId(target) {
442
+ return !!(target.animationFrames || target.timeline || target.comps);
443
+ }
444
+ function getExplicitLayerIds(psd, options) {
445
+ if (!options.explicitLayerIds) {
446
+ const ids = new Set();
447
+ const addIds = (layers) => {
448
+ for (const layer of layers !== null && layers !== void 0 ? layers : []) {
449
+ if (layer.id !== undefined)
450
+ ids.add(layer.id);
451
+ addIds(layer.children);
452
+ }
453
+ };
454
+ addIds(psd.children);
455
+ options.explicitLayerIds = ids;
456
+ }
457
+ return options.explicitLayerIds;
458
+ }
459
+ function getLayerId(target, options) {
460
+ var _a;
461
+ const id = (_a = options.layerToId.get(target)) !== null && _a !== void 0 ? _a : target.id;
462
+ if (id === undefined)
463
+ throw new Error('Missing layer id');
464
+ return id;
465
+ }
466
+ addHandler('lyid', target => target.id !== undefined || layerNeedsId(target), (reader, target) => {
423
467
  target.id = (0, psdReader_1.readUint32)(reader);
424
- }, (writer, target, _psd, options) => {
425
- let id = target.id;
426
- while (options.layerIds.has(id))
427
- id += 100; // make sure we don't have duplicate layer ids
468
+ }, (writer, target, psd, options) => {
469
+ var _a;
470
+ let id;
471
+ if (target.id !== undefined) {
472
+ id = target.id;
473
+ if (options.layerIds.has(id)) {
474
+ // make sure we don't have duplicate layer ids (and don't take id explicitly set on another layer)
475
+ const explicitIds = getExplicitLayerIds(psd, options);
476
+ while (options.layerIds.has(id) || explicitIds.has(id))
477
+ id += 100;
478
+ }
479
+ }
480
+ else {
481
+ // generate id for layers that don't have it, if it's referenced by other sections
482
+ const explicitIds = getExplicitLayerIds(psd, options);
483
+ id = (_a = options.nextGeneratedLayerId) !== null && _a !== void 0 ? _a : 1;
484
+ while (options.layerIds.has(id) || explicitIds.has(id))
485
+ id++;
486
+ options.nextGeneratedLayerId = id + 1;
487
+ }
428
488
  (0, psdWriter_1.writeUint32)(writer, id);
429
489
  options.layerIds.add(id);
430
490
  options.layerToId.set(target, id);
@@ -488,12 +548,15 @@ addHandler('lspf', hasKey('protected'), (reader, target) => {
488
548
  };
489
549
  if (flags & 0x08)
490
550
  target.protected.artboards = true;
551
+ if (flags & 0x80000000)
552
+ target.protected.all = true;
491
553
  }, (writer, target) => {
492
554
  const flags = (target.protected.transparency ? 0x01 : 0) |
493
555
  (target.protected.composite ? 0x02 : 0) |
494
556
  (target.protected.position ? 0x04 : 0) |
495
- (target.protected.artboards ? 0x08 : 0);
496
- (0, psdWriter_1.writeUint32)(writer, flags);
557
+ (target.protected.artboards ? 0x08 : 0) |
558
+ (target.protected.all ? 0x80000000 : 0);
559
+ (0, psdWriter_1.writeUint32)(writer, flags >>> 0);
497
560
  });
498
561
  addHandler('lclr', hasKey('layerColor'), (reader, target) => {
499
562
  const color = (0, psdReader_1.readUint16)(reader);
@@ -504,106 +567,160 @@ addHandler('lclr', hasKey('layerColor'), (reader, target) => {
504
567
  (0, psdWriter_1.writeUint16)(writer, index === -1 ? 0 : index);
505
568
  (0, psdWriter_1.writeZeros)(writer, 6);
506
569
  });
570
+ function parseGeneratorSettings(settings) {
571
+ const result = {};
572
+ if (settings.exportAs) {
573
+ result.exportAs = {};
574
+ if (settings.exportAs.exportOption !== undefined)
575
+ result.exportAs.exportOption = settings.exportAs.exportOption;
576
+ if (settings.exportAs.scaleSettings !== undefined)
577
+ result.exportAs.scaleSettings = settings.exportAs.scaleSettings;
578
+ }
579
+ if (settings.generator_45_assets)
580
+ result.assets = settings.generator_45_assets.json;
581
+ if (settings.layerTime !== undefined)
582
+ result.layerTime = settings.layerTime;
583
+ if (settings.crema)
584
+ result.crema = settings.crema.json;
585
+ return result;
586
+ }
587
+ function serializeGeneratorSettings(generatorSettings) {
588
+ const settings = {
589
+ _name: '',
590
+ _classID: generatorSettings.assets !== undefined ? 'generatorSettings' : 'null',
591
+ };
592
+ if (generatorSettings.exportAs) {
593
+ settings.exportAs = { _name: '', _classID: 'null' };
594
+ if (generatorSettings.exportAs.exportOption !== undefined)
595
+ settings.exportAs.exportOption = generatorSettings.exportAs.exportOption;
596
+ if (generatorSettings.exportAs.scaleSettings !== undefined)
597
+ settings.exportAs.scaleSettings = generatorSettings.exportAs.scaleSettings;
598
+ }
599
+ if (generatorSettings.assets !== undefined)
600
+ settings.generator_45_assets = { _name: '', _classID: 'generatorSettings', json: generatorSettings.assets };
601
+ if (generatorSettings.layerTime !== undefined)
602
+ settings.layerTime = generatorSettings.layerTime;
603
+ if (generatorSettings.crema !== undefined)
604
+ settings.crema = { _name: '', _classID: 'null', json: generatorSettings.crema };
605
+ return { generatorSettings: settings };
606
+ }
607
+ // document (Psd object) stores generator settings in 'extn' section instead of 'shmd' section
608
+ function isDocument(target) {
609
+ return target.width !== undefined;
610
+ }
507
611
  addHandler('shmd', // Metadata setting
508
612
  // Metadata setting
509
- target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined, (reader, target, left) => {
613
+ target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined ||
614
+ (target.generatorSettings !== undefined && !isDocument(target)), (reader, target, left) => {
510
615
  const count = (0, psdReader_1.readUint32)(reader);
511
616
  for (let i = 0; i < count; i++) {
512
- (0, psdReader_1.checkSignature)(reader, '8BIM');
617
+ const signature = (0, psdReader_1.readSignature)(reader);
618
+ if (signature !== '8BIM' && signature !== '8ELE')
619
+ throw new Error(`Invalid shmd signature: '${signature}'`); // 8ELE in Photoshop Elements
513
620
  const key = (0, psdReader_1.readSignature)(reader);
514
621
  (0, psdReader_1.readUint8)(reader); // copy
515
622
  (0, psdReader_1.skipBytes)(reader, 3);
516
- (0, psdReader_1.readSection)(reader, 1, left => {
517
- if (key === 'cust') {
518
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
519
- // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
520
- if (desc.layerTime !== undefined)
521
- target.timestamp = desc.layerTime;
522
- }
523
- else if (key === 'mlst') {
524
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
525
- // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
526
- target.animationFrames = [];
527
- for (let i = 0; i < desc.LaSt.length; i++) {
528
- const f = desc.LaSt[i];
529
- const frame = { frames: f.FrLs };
530
- if (f.enab !== undefined)
531
- frame.enable = f.enab;
532
- if (f.Ofst)
533
- frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
534
- if (f.FXRf)
535
- frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
536
- if (f.Lefx)
537
- frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, !!reader.logMissingFeatures);
538
- if (f.blendOptions && f.blendOptions.Opct)
539
- frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
540
- target.animationFrames.push(frame);
623
+ const end = reader.offset + 4 + reader.view.getUint32(reader.offset);
624
+ try {
625
+ (0, psdReader_1.readSection)(reader, 1, left => {
626
+ if (key === 'cust') {
627
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
628
+ // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
629
+ if (desc.layerTime !== undefined)
630
+ target.timestamp = desc.layerTime;
541
631
  }
542
- }
543
- else if (key === 'mdyn') {
544
- // frame flags
545
- (0, psdReader_1.readUint16)(reader); // unknown
546
- const propagate = (0, psdReader_1.readUint8)(reader);
547
- const flags = (0, psdReader_1.readUint8)(reader);
548
- target.animationFrameFlags = {
549
- propagateFrameOne: !propagate,
550
- unifyLayerPosition: (flags & 1) !== 0,
551
- unifyLayerStyle: (flags & 2) !== 0,
552
- unifyLayerVisibility: (flags & 4) !== 0,
553
- };
554
- }
555
- else if (key === 'tmln') {
556
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
557
- const timeScope = desc.timeScope;
558
- // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
559
- const timeline = {
560
- start: (0, descriptor_1.frac)(timeScope.Strt),
561
- duration: (0, descriptor_1.frac)(timeScope.duration),
562
- inTime: (0, descriptor_1.frac)(timeScope.inTime),
563
- outTime: (0, descriptor_1.frac)(timeScope.outTime),
564
- autoScope: desc.autoScope,
565
- audioLevel: desc.audioLevel,
566
- };
567
- if (desc.trackList) {
568
- timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, !!reader.logMissingFeatures);
632
+ else if (key === 'mlst') {
633
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
634
+ // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
635
+ target.animationFrames = [];
636
+ for (let i = 0; i < desc.LaSt.length; i++) {
637
+ const f = desc.LaSt[i];
638
+ const frame = { frames: f.FrLs };
639
+ if (f.enab !== undefined)
640
+ frame.enable = f.enab;
641
+ if (f.Ofst)
642
+ frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
643
+ if (f.FXRf)
644
+ frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
645
+ if (f.Lefx)
646
+ frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, !!reader.logMissingFeatures);
647
+ if (f.blendOptions && f.blendOptions.Opct)
648
+ frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
649
+ target.animationFrames.push(frame);
650
+ }
569
651
  }
570
- target.timeline = timeline;
571
- // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
572
- }
573
- else if (key === 'cmls') {
574
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
575
- // console.log('cmls', require('util').inspect(desc, false, 99, true));
576
- target.comps = {
577
- settings: [],
578
- };
579
- if (desc.origFXRefPoint)
580
- target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
581
- for (const item of desc.layerSettings) {
582
- target.comps.settings.push({ compList: item.compList });
583
- const t = target.comps.settings[target.comps.settings.length - 1];
584
- if ('enab' in item)
585
- t.enabled = item.enab;
586
- if (item.Ofst)
587
- t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
588
- if (item.FXRefPoint)
589
- t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
652
+ else if (key === 'mdyn') {
653
+ // frame flags
654
+ (0, psdReader_1.readUint16)(reader); // unknown
655
+ const propagate = (0, psdReader_1.readUint8)(reader);
656
+ const flags = (0, psdReader_1.readUint8)(reader);
657
+ target.animationFrameFlags = {
658
+ propagateFrameOne: !propagate,
659
+ unifyLayerPosition: (flags & 1) !== 0,
660
+ unifyLayerStyle: (flags & 2) !== 0,
661
+ unifyLayerVisibility: (flags & 4) !== 0,
662
+ };
590
663
  }
591
- }
592
- else if (key === 'extn') {
593
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
594
- // console.log(require('util').inspect(desc, false, 99, true));
595
- desc; // TODO: save this
596
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
597
- }
598
- else {
599
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
600
- }
601
- (0, psdReader_1.skipBytes)(reader, left());
602
- });
664
+ else if (key === 'tmln') {
665
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
666
+ const timeScope = desc.timeScope;
667
+ // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
668
+ const timeline = {
669
+ start: (0, descriptor_1.frac)(timeScope.Strt),
670
+ duration: (0, descriptor_1.frac)(timeScope.duration),
671
+ inTime: (0, descriptor_1.frac)(timeScope.inTime),
672
+ outTime: (0, descriptor_1.frac)(timeScope.outTime),
673
+ autoScope: desc.autoScope,
674
+ audioLevel: desc.audioLevel,
675
+ };
676
+ if (desc.trackList) {
677
+ timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, !!reader.logMissingFeatures);
678
+ }
679
+ target.timeline = timeline;
680
+ // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
681
+ }
682
+ else if (key === 'cmls') {
683
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
684
+ // console.log('cmls', require('util').inspect(desc, false, 99, true));
685
+ target.comps = {
686
+ settings: [],
687
+ };
688
+ if (desc.origFXRefPoint)
689
+ target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
690
+ for (const item of desc.layerSettings) {
691
+ target.comps.settings.push({ compList: item.compList });
692
+ const t = target.comps.settings[target.comps.settings.length - 1];
693
+ if ('enab' in item)
694
+ t.enabled = item.enab;
695
+ if (item.Ofst)
696
+ t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
697
+ if (item.FXRefPoint)
698
+ t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
699
+ }
700
+ }
701
+ else if (key === 'extn') {
702
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
703
+ if (desc.generatorSettings)
704
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
705
+ }
706
+ else {
707
+ reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
708
+ }
709
+ (0, psdReader_1.skipBytes)(reader, left());
710
+ });
711
+ }
712
+ catch (e) {
713
+ // failure to read one item shouldn't drop all other items
714
+ if (reader.throwForMissingFeatures)
715
+ throw e;
716
+ reader.logMissingFeatures && reader.log(`Failed to read "shmd" section key ${key}: ${e.message}`);
717
+ reader.offset = end;
718
+ }
603
719
  }
604
720
  (0, psdReader_1.skipBytes)(reader, left());
605
721
  }, (writer, target, _, options) => {
606
722
  const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
723
+ const generatorSettings = isDocument(target) ? undefined : target.generatorSettings;
607
724
  let count = 0;
608
725
  if (animationFrames)
609
726
  count++;
@@ -615,6 +732,8 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
615
732
  count++;
616
733
  if (comps)
617
734
  count++;
735
+ if (generatorSettings)
736
+ count++;
618
737
  (0, psdWriter_1.writeUint32)(writer, count);
619
738
  if (animationFrames) {
620
739
  (0, psdWriter_1.writeSignature)(writer, '8BIM');
@@ -622,9 +741,8 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
622
741
  (0, psdWriter_1.writeUint8)(writer, 0); // copy (always false)
623
742
  (0, psdWriter_1.writeZeros)(writer, 3);
624
743
  (0, psdWriter_1.writeSection)(writer, 2, () => {
625
- var _a;
626
744
  const desc = {
627
- LaID: (_a = target.id) !== null && _a !== void 0 ? _a : 0,
745
+ LaID: getLayerId(target, options),
628
746
  LaSt: [],
629
747
  };
630
748
  for (let i = 0; i < animationFrames.length; i++) {
@@ -638,9 +756,9 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
638
756
  if (f.referencePoint)
639
757
  frame.FXRf = (0, descriptor_1.xyToHorzVrtc)(f.referencePoint);
640
758
  if (f.effects)
641
- frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, false, false);
759
+ frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, false, hasMultiEffects(f.effects));
642
760
  if (f.opacity !== undefined)
643
- frame.blendOptions = { Opct: (0, descriptor_1.unitsPercent)(f.opacity) };
761
+ frame.blendOptions = { Opct: (0, descriptor_1.unitsPercentF)(f.opacity) };
644
762
  desc.LaSt.push(frame);
645
763
  }
646
764
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
@@ -680,10 +798,7 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
680
798
  if (timeline.tracks) {
681
799
  desc.trackList = (0, descriptor_1.serializeTrackList)(timeline.tracks);
682
800
  }
683
- const id = options.layerToId.get(target) || target.id;
684
- if (!id)
685
- throw new Error('You need to provide layer.id value whan writing document with animations');
686
- desc.LyrI = id;
801
+ desc.LyrI = getLayerId(target, options);
687
802
  // console.log('WRITE:tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
688
803
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, 'anim');
689
804
  }, true);
@@ -706,14 +821,11 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
706
821
  (0, psdWriter_1.writeUint8)(writer, 0); // copy (always false)
707
822
  (0, psdWriter_1.writeZeros)(writer, 3);
708
823
  (0, psdWriter_1.writeSection)(writer, 2, () => {
709
- const id = options.layerToId.get(target) || target.id;
710
- if (!id)
711
- throw new Error('You need to provide layer.id value whan writing document with layer comps');
712
824
  const desc = {};
713
825
  if (comps.originalEffectsReferencePoint) {
714
826
  desc.origFXRefPoint = { Hrzn: comps.originalEffectsReferencePoint.x, Vrtc: comps.originalEffectsReferencePoint.y };
715
827
  }
716
- desc.LyrI = id;
828
+ desc.LyrI = getLayerId(target, options);
717
829
  desc.layerSettings = [];
718
830
  for (const item of comps.settings) {
719
831
  const t = {};
@@ -730,8 +842,17 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
730
842
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
731
843
  }, true);
732
844
  }
845
+ if (generatorSettings) {
846
+ (0, psdWriter_1.writeSignature)(writer, '8BIM');
847
+ (0, psdWriter_1.writeSignature)(writer, 'extn');
848
+ (0, psdWriter_1.writeUint8)(writer, 0); // copy (always false)
849
+ (0, psdWriter_1.writeZeros)(writer, 3);
850
+ (0, psdWriter_1.writeSection)(writer, 2, () => {
851
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(generatorSettings));
852
+ }, true);
853
+ }
733
854
  });
734
- addHandler('PxSc', () => false, (reader, target) => {
855
+ addHandler('PxSc', hasKey('pixelSource'), (reader, target) => {
735
856
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
736
857
  // console.log('PxSc', require('util').inspect(desc, false, 99, true));
737
858
  if (desc.pixelSourceType === 1986285651) {
@@ -766,7 +887,7 @@ addHandler('PxSc', () => false, (reader, target) => {
766
887
  _classID: 'PixelSource',
767
888
  pixelSourceType: 1986285651, // vdP
768
889
  descVersion: 1,
769
- origin: { Hrzn: source.origin.x, Vrtc: source.origin.y },
890
+ origin: { Hrzn: source.origin.x, Vrtc: source.origin.y }, // written as 'null' class with 'long' values (see writeDescriptorStructure)
770
891
  interpretation: {
771
892
  _name: '',
772
893
  _classID: 'footageInterpretation',
@@ -809,7 +930,7 @@ addHandler('vstk', hasKey('vectorStroke'), (reader, target, left) => {
809
930
  lineAlignment: descriptor_1.strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
810
931
  scaleLock: desc.strokeStyleScaleLock,
811
932
  strokeAdjust: desc.strokeStyleStrokeAdjust,
812
- lineDashSet: desc.strokeStyleLineDashSet.map(descriptor_1.parseUnits),
933
+ lineDashSet: (desc.strokeStyleLineDashSet || []).map(descriptor_1.parseUnits),
813
934
  blendMode: descriptor_1.BlnM.decode(desc.strokeStyleBlendMode),
814
935
  opacity: (0, descriptor_1.parsePercent)(desc.strokeStyleOpacity),
815
936
  content: (0, descriptor_1.parseVectorContent)(desc.strokeStyleContent),
@@ -847,9 +968,11 @@ hasKey('artboard'), (reader, target, left) => {
847
968
  rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
848
969
  guideIndices: desc.guideIndeces,
849
970
  presetName: desc.artboardPresetName,
850
- color: (0, descriptor_1.parseColor)(desc['Clr ']),
851
- backgroundType: desc.artboardBackgroundType,
852
971
  };
972
+ if (desc['Clr '])
973
+ target.artboard.color = (0, descriptor_1.parseColor)(desc['Clr ']);
974
+ if (desc.artboardBackgroundType !== undefined)
975
+ target.artboard.backgroundType = desc.artboardBackgroundType;
853
976
  (0, psdReader_1.skipBytes)(reader, left());
854
977
  }, (writer, target) => {
855
978
  var _a;
@@ -955,14 +1078,15 @@ function encodeWarp(warp) {
955
1078
  }
956
1079
  return desc;
957
1080
  }
958
- addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1081
+ addHandler('PlLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
1082
+ (reader, target, left) => {
959
1083
  if ((0, psdReader_1.readSignature)(reader) !== 'plcL')
960
1084
  throw new Error(`Invalid PlLd signature`);
961
1085
  if ((0, psdReader_1.readInt32)(reader) !== 3)
962
1086
  throw new Error(`Invalid PlLd version`);
963
1087
  const id = (0, psdReader_1.readPascalString)(reader, 1);
964
1088
  const pageNumber = (0, psdReader_1.readInt32)(reader);
965
- const totalPages = (0, psdReader_1.readInt32)(reader); // TODO: check how this works ?
1089
+ const totalPages = (0, psdReader_1.readInt32)(reader); // for multi-page documents (PDF)
966
1090
  (0, psdReader_1.readInt32)(reader); // anitAliasPolicy 16
967
1091
  const placedLayerType = (0, psdReader_1.readInt32)(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
968
1092
  if (!placedLayerTypes[placedLayerType])
@@ -1007,11 +1131,15 @@ addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1007
1131
  const type = isQuilt ? 'quiltWarp' : 'warp';
1008
1132
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', type, encodeWarp(warp), type);
1009
1133
  });
1134
+ const levelsFilterKeys = ['_name', '_classID', 'presetKind', 'Adjs'];
1135
+ const levelsFilterAdjustmentKeys = ['_name', '_classID', 'Chnl', 'Inpt', 'Otpt', 'Gmm '];
1010
1136
  function uint8ToFloat32(array) {
1011
- return new Float32Array(array.buffer.slice(array.byteOffset), 0, array.byteLength / 4);
1137
+ const length = Math.floor(array.byteLength / 4);
1138
+ return new Float32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
1012
1139
  }
1013
1140
  function uint8ToUint32(array) {
1014
- return new Uint32Array(array.buffer.slice(array.byteOffset), 0, array.byteLength / 4);
1141
+ const length = Math.floor(array.byteLength / 4);
1142
+ return new Uint32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
1015
1143
  }
1016
1144
  function toUint8(array) {
1017
1145
  return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
@@ -1047,7 +1175,7 @@ function pointToHrznVrtc(point) {
1047
1175
  Vrtc: (0, descriptor_1.unitsValue)(point.y, 'y'),
1048
1176
  };
1049
1177
  }
1050
- function parseFilterFXItem(f, options) {
1178
+ function parseFilterFXItem(f, reader) {
1051
1179
  const base = {
1052
1180
  name: f['Nm '],
1053
1181
  opacity: (0, descriptor_1.parsePercent)(f.blendOptions.Opct),
@@ -1095,7 +1223,7 @@ function parseFilterFXItem(f, options) {
1095
1223
  undefinedAreas: descriptor_1.UndA.decode(f.Fltr.UndA),
1096
1224
  displacementFile: {
1097
1225
  signature: f.Fltr.DspF.sig,
1098
- path: f.Fltr.DspF.path, // TODO: this is decoded incorrectly ???
1226
+ path: f.Fltr.DspF.path,
1099
1227
  },
1100
1228
  } });
1101
1229
  case 'Pnch': return Object.assign(Object.assign({}, base), { type: 'pinch', filter: {
@@ -1280,10 +1408,9 @@ function parseFilterFXItem(f, options) {
1280
1408
  { x: f.Fltr.PuX2, y: f.Fltr.PuY2, },
1281
1409
  { x: f.Fltr.PuX3, y: f.Fltr.PuY3, },
1282
1410
  ],
1283
- puppetShapeList: f.Fltr.puppetShapeList.map(p => ({
1411
+ puppetShapeList: (f.Fltr.puppetShapeList || []).map(p => ({
1284
1412
  rigidType: p.rigidType,
1285
- // TODO: VrsM
1286
- // TODO: VrsN
1413
+ // VrsM / VrsN - version (always 1.0)
1287
1414
  originalVertexArray: uint8ToPoints(p.originalVertexArray),
1288
1415
  deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
1289
1416
  indexArray: Array.from(uint8ToUint32(p.indexArray)),
@@ -1380,7 +1507,7 @@ function parseFilterFXItem(f, options) {
1380
1507
  ;
1381
1508
  case 'Crvs':
1382
1509
  {
1383
- return Object.assign(Object.assign({}, base), { type: 'curves', filter: Object.assign({ presetKind: descriptor_1.presetKindType.decode(f.Fltr.presetKind) }, (f.Fltr.Adjs ? {
1510
+ 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 ? {
1384
1511
  adjustments: f.Fltr.Adjs.map(a => {
1385
1512
  const channels = a.Chnl.map(descriptor_1.Chnl.decode);
1386
1513
  if (a['Crv ']) {
@@ -1404,6 +1531,35 @@ function parseFilterFXItem(f, options) {
1404
1531
  } : {})) });
1405
1532
  }
1406
1533
  ;
1534
+ case 'Lvls':
1535
+ {
1536
+ const adjustments = f.Fltr.Adjs;
1537
+ const isKnown = (obj, keys) => Object.keys(obj).every(k => keys.indexOf(k) !== -1);
1538
+ // skip filter with settings we don't know about
1539
+ if (!isKnown(f.Fltr, levelsFilterKeys) || (adjustments && !adjustments.every(a => isKnown(a, levelsFilterAdjustmentKeys)))) {
1540
+ if (reader.throwForMissingFeatures)
1541
+ throw new Error('Unknown levels filter settings');
1542
+ reader.logMissingFeatures && reader.log(`Skipped levels smart filter with unknown settings`);
1543
+ return undefined;
1544
+ }
1545
+ 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 ? {
1546
+ adjustments: adjustments.map(a => {
1547
+ const result = { channels: a.Chnl.map(descriptor_1.Chnl.decode) };
1548
+ if (a.Inpt) {
1549
+ result.shadowInput = a.Inpt[0];
1550
+ result.highlightInput = a.Inpt[1];
1551
+ }
1552
+ if (a.Otpt) {
1553
+ result.shadowOutput = a.Otpt[0];
1554
+ result.highlightOutput = a.Otpt[1];
1555
+ }
1556
+ if (a['Gmm '] !== undefined)
1557
+ result.midtoneInput = a['Gmm '];
1558
+ return result;
1559
+ }),
1560
+ } : {})) });
1561
+ }
1562
+ ;
1407
1563
  case 'BrgC':
1408
1564
  {
1409
1565
  return Object.assign(Object.assign({}, base), { type: 'brightness/contrast', filter: {
@@ -1414,10 +1570,11 @@ function parseFilterFXItem(f, options) {
1414
1570
  }
1415
1571
  ;
1416
1572
  default:
1417
- if (options.throwForMissingFeatures) {
1573
+ if (reader.throwForMissingFeatures) {
1418
1574
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1419
1575
  throw new Error(`Unknown filter classId: ${f.Fltr._classID}`);
1420
1576
  }
1577
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.Fltr._classID} (${f['Nm ']})`);
1421
1578
  return undefined;
1422
1579
  }
1423
1580
  }
@@ -1437,21 +1594,23 @@ function parseFilterFXItem(f, options) {
1437
1594
  case 1314149187: return Object.assign(Object.assign({}, base), { type: 'ntsc colors' });
1438
1595
  case 1231976050: return Object.assign(Object.assign({}, base), { type: 'invert' });
1439
1596
  default:
1440
- if (options.throwForMissingFeatures) {
1597
+ if (reader.throwForMissingFeatures) {
1441
1598
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1442
1599
  throw new Error(`Unknown filterID: ${f.filterID}`);
1443
1600
  }
1601
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.filterID} (${f['Nm ']})`);
1602
+ return undefined;
1444
1603
  }
1445
1604
  }
1446
1605
  }
1447
- function parseFilterFX(desc, options) {
1606
+ function parseFilterFX(desc, reader) {
1448
1607
  return {
1449
1608
  enabled: desc.enab,
1450
1609
  validAtPosition: desc.validAtPosition,
1451
1610
  maskEnabled: desc.filterMaskEnable,
1452
1611
  maskLinked: desc.filterMaskLinked,
1453
1612
  maskExtendWithWhite: desc.filterMaskExtendWithWhite,
1454
- list: desc.filterFXList.map(x => parseFilterFXItem(x, options)).filter((x) => !!x),
1613
+ list: desc.filterFXList.map(x => parseFilterFXItem(x, reader)).filter((x) => !!x),
1455
1614
  };
1456
1615
  }
1457
1616
  function uvRadius(t) {
@@ -1807,14 +1966,14 @@ function serializeFilterFXItem(f) {
1807
1966
  case 'puppet': return Object.assign(Object.assign({}, base), { Fltr: {
1808
1967
  _name: 'Rigid Transform',
1809
1968
  _classID: 'rigidTransform',
1810
- 'null': ['Ordn.Trgt'], // TODO: ???
1969
+ 'null': ['Ordn.Trgt'], // target layer reference
1811
1970
  rigidType: f.filter.rigidType,
1812
- puppetShapeList: f.filter.puppetShapeList.map(p => ({
1971
+ puppetShapeList: (f.filter.puppetShapeList || []).map(p => ({
1813
1972
  _name: '',
1814
1973
  _classID: 'puppetShape',
1815
1974
  rigidType: p.rigidType,
1816
- VrsM: 1, // TODO: ???
1817
- VrsN: 0, // TODO: ???
1975
+ VrsM: 1, // version major
1976
+ VrsN: 0, // version minor
1818
1977
  originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
1819
1978
  deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
1820
1979
  indexArray: toUint8(new Uint32Array(p.indexArray)),
@@ -1894,9 +2053,9 @@ function serializeFilterFXItem(f) {
1894
2053
  _classID: 'perspectiveWarpTransform',
1895
2054
  vertices: f.filter.vertices.map(pointToHrznVrtc),
1896
2055
  warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
1897
- quads: f.filter.quads.map(indices => ({ indices })),
2056
+ quads: f.filter.quads.map(indices => ({ _name: '', _classID: 'quadIndices', indices })),
1898
2057
  }, filterID: 442 });
1899
- 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 ? {
2058
+ 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 ? {
1900
2059
  Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
1901
2060
  _name: '',
1902
2061
  _classID: 'CrvA',
@@ -1909,6 +2068,21 @@ function serializeFilterFXItem(f) {
1909
2068
  Mpng: a.values,
1910
2069
  })
1911
2070
  } : {})), filterID: 1131574899 });
2071
+ 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 ? {
2072
+ Adjs: f.filter.adjustments.map(a => {
2073
+ var _a, _b, _c, _d;
2074
+ const adjustment = { _name: '', _classID: 'LvlA', Chnl: a.channels.map(descriptor_1.Chnl.encode) };
2075
+ if (a.shadowInput !== undefined || a.highlightInput !== undefined) {
2076
+ adjustment.Inpt = [Math.round((_a = a.shadowInput) !== null && _a !== void 0 ? _a : 0), Math.round((_b = a.highlightInput) !== null && _b !== void 0 ? _b : 255)];
2077
+ }
2078
+ if (a.shadowOutput !== undefined || a.highlightOutput !== undefined) {
2079
+ adjustment.Otpt = [Math.round((_c = a.shadowOutput) !== null && _c !== void 0 ? _c : 0), Math.round((_d = a.highlightOutput) !== null && _d !== void 0 ? _d : 255)];
2080
+ }
2081
+ if (a.midtoneInput !== undefined)
2082
+ adjustment['Gmm '] = a.midtoneInput;
2083
+ return adjustment;
2084
+ }),
2085
+ } : {})), filterID: 1282829427 });
1912
2086
  case 'brightness/contrast': return Object.assign(Object.assign({}, base), { Fltr: {
1913
2087
  _name: 'Brightness/Contrast',
1914
2088
  _classID: 'BrgC',
@@ -1916,10 +2090,13 @@ function serializeFilterFXItem(f) {
1916
2090
  Cntr: f.filter.contrast,
1917
2091
  useLegacy: !!f.filter.useLegacy,
1918
2092
  }, filterID: 1114793795 });
1919
- // case 'hsb/hsl': return {
1920
- // TODO: ...
1921
- // };
1922
- default: throw new Error(`Unknow filter type: ${f.type}`);
2093
+ case 'hsb/hsl': return Object.assign(Object.assign({}, base), { Fltr: {
2094
+ _name: 'HSB/HSL',
2095
+ _classID: 'HsbP',
2096
+ Inpt: descriptor_1.ClrS.encode(f.filter.inputMode),
2097
+ Otpt: descriptor_1.ClrS.encode(f.filter.rowOrder),
2098
+ }, filterID: 1215521360 });
2099
+ default: throw new Error(`Unknown filter type: ${f.type}`);
1923
2100
  }
1924
2101
  }
1925
2102
  // let t: any;
@@ -1956,7 +2133,8 @@ function getWarpFromPlacedLayer(placed) {
1956
2133
  },
1957
2134
  };
1958
2135
  }
1959
- addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2136
+ addHandler('SoLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
2137
+ (reader, target, left) => {
1960
2138
  if ((0, psdReader_1.readSignature)(reader) !== 'soLD')
1961
2139
  throw new Error(`Invalid SoLd type`);
1962
2140
  const version = (0, psdReader_1.readInt32)(reader);
@@ -2010,13 +2188,21 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2010
2188
  if (!placed.id || typeof placed.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(placed.id)) {
2011
2189
  throw new Error('Placed layer ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
2012
2190
  }
2013
- 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: placedLayerTypes.indexOf(placed.type), Trnf: placed.transform, nonAffineTransform: (_b = placed.nonAffineTransform) !== null && _b !== void 0 ? _b : placed.transform,
2191
+ const placedType = placedLayerTypes.indexOf(placed.type);
2192
+ if (placedType === -1)
2193
+ throw new Error('Invalid placedLayer type');
2194
+ const warp = getWarpFromPlacedLayer(placed);
2195
+ // size of linked image matches warp bounds if it's not provided
2196
+ const bounds = warp.bounds;
2197
+ const width = placed.width || (bounds ? bounds.right.value - bounds.left.value : 0);
2198
+ const height = placed.height || (bounds ? bounds.bottom.value - bounds.top.value : 0);
2199
+ 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,
2014
2200
  // quiltWarp: {} as any,
2015
- warp: encodeWarp(getWarpFromPlacedLayer(placed)), 'Sz ': {
2201
+ warp: encodeWarp(warp), 'Sz ': {
2016
2202
  _name: '',
2017
2203
  _classID: 'Pnt ',
2018
- Wdth: placed.width || 0, // TODO: find size ?
2019
- Hght: placed.height || 0, // TODO: find size ?
2204
+ Wdth: width,
2205
+ Hght: height,
2020
2206
  }, Rslt: placed.resolution ? (0, descriptor_1.unitsValue)(placed.resolution, 'resolution') : { units: 'Density', value: 72 } });
2021
2207
  if (placed.filter) {
2022
2208
  desc.filterFX = {
@@ -2030,14 +2216,6 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2030
2216
  filterFXList: placed.filter.list.map(f => serializeFilterFXItem(f)),
2031
2217
  };
2032
2218
  }
2033
- // TODO:
2034
- // desc.comp = -1;
2035
- // desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
2036
- // desc.ClMg = {
2037
- // _name: '',
2038
- // _classID: 'ClMg',
2039
- // placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
2040
- // } as any;
2041
2219
  // if (JSON.stringify(t) !== JSON.stringify(desc)) {
2042
2220
  // console.log('read', require('util').inspect(t, false, 99, true));
2043
2221
  // console.log('write', require('util').inspect(desc, false, 99, true));
@@ -2067,7 +2245,13 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2067
2245
  desc.compInfo = placed.compInfo;
2068
2246
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, desc.quiltWarp ? 'quiltWarp' : 'warp');
2069
2247
  });
2070
- addHandlerAlias('SoLE', 'SoLd');
2248
+ const soLdHandler = exports.infoHandlersMap['SoLd'];
2249
+ addHandler('SoLE', // linked smart object (Photoshop doesn't write PlLd for these)
2250
+ // linked smart object (Photoshop doesn't write PlLd for these)
2251
+ target => { var _a; return !!((_a = target.placedLayer) === null || _a === void 0 ? void 0 : _a.external); }, (reader, target, left, psd, imageResources) => {
2252
+ soLdHandler.read(reader, target, left, psd, imageResources);
2253
+ target.placedLayer.external = true;
2254
+ }, soLdHandler.write);
2071
2255
  addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2072
2256
  target.referencePoint = {
2073
2257
  x: (0, psdReader_1.readFloat64)(reader),
@@ -2077,18 +2261,41 @@ addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2077
2261
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.x);
2078
2262
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.y);
2079
2263
  });
2080
- addHandler('Lr16', () => false, (reader, _target, _left, psd, imageResources) => {
2081
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2082
- }, (_writer, _target) => {
2083
- });
2084
- addHandler('Lr32', () => false, (reader, _target, _left, psd, imageResources) => {
2085
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2086
- }, (_writer, _target) => {
2087
- });
2264
+ function createGlobalLayerInfoHandler(key, bitsPerChannel) {
2265
+ addHandler(key, () => false, (reader, _target, left, psd, imageResources) => {
2266
+ var _a;
2267
+ // Photoshop writes empty (or 8bit compatibility) layer info section alongside Lr16/Lr32 in 16/32bit documents,
2268
+ // layers from global info with matching bit depth replace layers from that section, other ones are only used
2269
+ // if the section is empty
2270
+ if (!reader.layersInGlobalInfo && (psd.bitsPerChannel === bitsPerChannel || !((_a = psd.children) === null || _a === void 0 ? void 0 : _a.length))) {
2271
+ const sectionLayers = psd.children;
2272
+ psd.children = [];
2273
+ try {
2274
+ (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2275
+ }
2276
+ catch (e) {
2277
+ psd.children = sectionLayers; // keep layers from layer info section if global info is broken
2278
+ throw e;
2279
+ }
2280
+ reader.layersInGlobalInfo = true;
2281
+ }
2282
+ (0, psdReader_1.skipBytes)(reader, left());
2283
+ }, () => { });
2284
+ }
2285
+ createGlobalLayerInfoHandler('Lr16', 16);
2286
+ createGlobalLayerInfoHandler('Lr32', 32);
2287
+ createGlobalLayerInfoHandler('Layr', undefined);
2288
+ // merged transparency markers, first extra channel of composite image is transparency
2289
+ for (const key of ['Mtrn', 'Mt16', 'Mt32']) {
2290
+ addHandler(key, () => false, (reader, _target, left) => {
2291
+ reader.globalAlpha = true;
2292
+ (0, psdReader_1.skipBytes)(reader, left());
2293
+ }, () => { });
2294
+ }
2088
2295
  addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2089
2296
  target.userMask = {
2090
2297
  colorSpace: (0, psdReader_1.readColor)(reader),
2091
- opacity: (0, psdReader_1.readUint16)(reader) / 0xff,
2298
+ opacity: (0, psdReader_1.readUint16)(reader) / 100,
2092
2299
  };
2093
2300
  const flag = (0, psdReader_1.readUint8)(reader);
2094
2301
  if (flag !== 128)
@@ -2097,7 +2304,7 @@ addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2097
2304
  }, (writer, target) => {
2098
2305
  const userMask = target.userMask;
2099
2306
  (0, psdWriter_1.writeColor)(writer, userMask.colorSpace);
2100
- (0, psdWriter_1.writeUint16)(writer, (0, helpers_1.clamp)(userMask.opacity, 0, 1) * 0xff);
2307
+ (0, psdWriter_1.writeUint16)(writer, Math.round((0, helpers_1.clamp)(userMask.opacity, 0, 1) * 100));
2101
2308
  (0, psdWriter_1.writeUint8)(writer, 128);
2102
2309
  (0, psdWriter_1.writeZeros)(writer, 1);
2103
2310
  });
@@ -2108,11 +2315,13 @@ if (helpers_1.MOCK_HANDLERS) {
2108
2315
  }, (writer, target) => false && (0, psdWriter_1.writeBytes)(writer, target._Patt));
2109
2316
  }
2110
2317
  else {
2111
- addHandler('Patt', // TODO: handle also Pat2 & Pat3
2112
- // TODO: handle also Pat2 & Pat3
2318
+ addHandler('Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2319
+ // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2113
2320
  target => !!(target.patterns && target.patterns.length > 0), (reader, target, left) => {
2114
2321
  while (left() > 0) {
2115
- const pattern = (0, psdReader_1.readPattern)(reader);
2322
+ const pattern = (0, psdReader_1.readPatternSafe)(reader);
2323
+ if (!pattern)
2324
+ continue;
2116
2325
  if (target.patterns === undefined)
2117
2326
  target.patterns = [];
2118
2327
  target.patterns.push(pattern);
@@ -2217,7 +2426,8 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2217
2426
  const count = (0, psdReader_1.readUint32)(reader);
2218
2427
  const annotations = [];
2219
2428
  for (let i = 0; i < count; i++) {
2220
- /*const length =*/ (0, psdReader_1.readUint32)(reader);
2429
+ const recordStart = reader.offset;
2430
+ const length = (0, psdReader_1.readUint32)(reader); // includes the length field itself
2221
2431
  const type = (0, psdReader_1.readSignature)(reader);
2222
2432
  const open = !!(0, psdReader_1.readUint8)(reader);
2223
2433
  /*const flags =*/ (0, psdReader_1.readUint8)(reader); // always 28
@@ -2233,11 +2443,12 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2233
2443
  const dataLength = (0, psdReader_1.readUint32)(reader);
2234
2444
  let data;
2235
2445
  if (type === 'txtA') {
2446
+ const dataStart = reader.offset;
2236
2447
  if (dataLength >= 2 && (0, psdReader_1.readUint16)(reader) === 0xfeff) {
2237
- data = (0, psdReader_1.readUnicodeStringWithLength)(reader, (dataLength - 2) / 2);
2448
+ data = (0, psdReader_1.readUnicodeStringWithLength)(reader, Math.floor((dataLength - 2) / 2));
2238
2449
  }
2239
2450
  else {
2240
- reader.offset -= 2;
2451
+ reader.offset = dataStart;
2241
2452
  data = (0, psdReader_1.readAsciiString)(reader, dataLength);
2242
2453
  }
2243
2454
  data = data.replace(/\r/g, '\n');
@@ -2251,6 +2462,8 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2251
2462
  annotations.push({
2252
2463
  type: type === 'txtA' ? 'text' : 'sound', open, iconLocation, popupLocation, color, author, name, date, data,
2253
2464
  });
2465
+ if (length >= (reader.offset - recordStart))
2466
+ reader.offset = recordStart + length;
2254
2467
  }
2255
2468
  target.annotations = annotations;
2256
2469
  (0, psdReader_1.skipBytes)(reader, left());
@@ -2296,6 +2509,10 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2296
2509
  writer.view.setUint32(dataOffset - 4, writer.offset - dataOffset, false);
2297
2510
  }
2298
2511
  });
2512
+ function readFileTypeSignature(reader) {
2513
+ const value = (0, psdReader_1.readSignature)(reader);
2514
+ return /^[\0 ]*$/.test(value) ? '' : value.trim(); // empty value is stored as spaces or zeros
2515
+ }
2299
2516
  function createLnkHandler(tag) {
2300
2517
  addHandler(tag, (target) => {
2301
2518
  const psd = target;
@@ -2317,8 +2534,8 @@ function createLnkHandler(tag) {
2317
2534
  const version = (0, psdReader_1.readInt32)(reader);
2318
2535
  const id = (0, psdReader_1.readPascalString)(reader, 1);
2319
2536
  const name = (0, psdReader_1.readUnicodeString)(reader);
2320
- const fileType = (0, psdReader_1.readSignature)(reader).trim(); // ' ' if empty
2321
- const fileCreator = (0, psdReader_1.readSignature)(reader).trim(); // ' ' or '\0\0\0\0' if empty
2537
+ const fileType = readFileTypeSignature(reader); // ' ' if empty
2538
+ const fileCreator = readFileTypeSignature(reader); // ' ' or '\0\0\0\0' if empty
2322
2539
  const dataSize = readLength64(reader);
2323
2540
  const hasFileOpenDescriptor = (0, psdReader_1.readUint8)(reader);
2324
2541
  const fileOpenDescriptor = hasFileOpenDescriptor ? (0, descriptor_1.readVersionAndDescriptor)(reader) : undefined;
@@ -2378,11 +2595,12 @@ function createLnkHandler(tag) {
2378
2595
  }
2379
2596
  (0, psdReader_1.skipBytes)(reader, left()); // ?
2380
2597
  }, (writer, target) => {
2381
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2598
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2382
2599
  const psd = target;
2383
2600
  for (const file of psd.linkedFiles) {
2384
2601
  if ((tag === 'lnkE') !== !!file.linkedFile)
2385
2602
  continue;
2603
+ const external = tag === 'lnkE';
2386
2604
  let version = 2;
2387
2605
  if (file.assetLockedState != null)
2388
2606
  version = 7;
@@ -2390,11 +2608,13 @@ function createLnkHandler(tag) {
2390
2608
  version = 6;
2391
2609
  else if (file.childDocumentID != null)
2392
2610
  version = 5;
2393
- else if (tag === 'lnkE')
2394
- version = 3;
2395
- writeLength64(writer, 0);
2611
+ // liFE version 2 is the only one with embedded data, it has no date and its file size is the size of the data,
2612
+ // versions 5+ can't store the data, so it's not written in that case
2613
+ else if (external && !file.data)
2614
+ version = 4;
2615
+ (0, psdWriter_1.writeUint64)(writer, 0);
2396
2616
  const sizeOffset = writer.offset;
2397
- (0, psdWriter_1.writeSignature)(writer, (tag === 'lnkE') ? 'liFE' : (file.data ? 'liFD' : 'liFA'));
2617
+ (0, psdWriter_1.writeSignature)(writer, external ? 'liFE' : (file.data ? 'liFD' : 'liFA'));
2398
2618
  (0, psdWriter_1.writeInt32)(writer, version);
2399
2619
  if (!file.id || typeof file.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(file.id)) {
2400
2620
  throw new Error('Linked file ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
@@ -2403,7 +2623,7 @@ function createLnkHandler(tag) {
2403
2623
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, file.name || '');
2404
2624
  (0, psdWriter_1.writeSignature)(writer, file.type ? `${file.type} `.substring(0, 4) : ' ');
2405
2625
  (0, psdWriter_1.writeSignature)(writer, file.creator ? `${file.creator} `.substring(0, 4) : '\0\0\0\0');
2406
- writeLength64(writer, file.data ? file.data.byteLength : 0);
2626
+ (0, psdWriter_1.writeUint64)(writer, (!external && file.data) ? file.data.byteLength : 0);
2407
2627
  if (file.descriptor && file.descriptor.compInfo) {
2408
2628
  const desc = {
2409
2629
  compInfo: {
@@ -2417,7 +2637,7 @@ function createLnkHandler(tag) {
2417
2637
  else {
2418
2638
  (0, psdWriter_1.writeUint8)(writer, 0);
2419
2639
  }
2420
- if (tag === 'lnkE') {
2640
+ if (external) {
2421
2641
  const desc = {
2422
2642
  descVersion: 2,
2423
2643
  'Nm ': (_b = (_a = file.linkedFile) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '',
@@ -2426,6 +2646,8 @@ function createLnkHandler(tag) {
2426
2646
  relPath: (_h = (_g = file.linkedFile) === null || _g === void 0 ? void 0 : _g.relativePath) !== null && _h !== void 0 ? _h : '',
2427
2647
  };
2428
2648
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'ExternalFileLink', desc);
2649
+ }
2650
+ if (external && version > 3) {
2429
2651
  const time = file.time ? new Date(file.time) : new Date();
2430
2652
  (0, psdWriter_1.writeInt32)(writer, time.getUTCFullYear());
2431
2653
  (0, psdWriter_1.writeUint8)(writer, time.getUTCMonth());
@@ -2434,11 +2656,14 @@ function createLnkHandler(tag) {
2434
2656
  (0, psdWriter_1.writeUint8)(writer, time.getUTCMinutes());
2435
2657
  (0, psdWriter_1.writeFloat64)(writer, time.getUTCSeconds() + time.getUTCMilliseconds() / 1000);
2436
2658
  }
2437
- if (file.data) {
2438
- (0, psdWriter_1.writeBytes)(writer, file.data);
2659
+ if (external) {
2660
+ (0, psdWriter_1.writeUint64)(writer, (version === 2 ? (_j = file.data) === null || _j === void 0 ? void 0 : _j.byteLength : (_k = file.linkedFile) === null || _k === void 0 ? void 0 : _k.fileSize) || 0); // file size
2661
+ }
2662
+ else if (file.data) {
2663
+ (0, psdWriter_1.writeBytes)(writer, file.data); // liFD
2439
2664
  }
2440
2665
  else {
2441
- writeLength64(writer, ((_j = file.linkedFile) === null || _j === void 0 ? void 0 : _j.fileSize) || 0);
2666
+ (0, psdWriter_1.writeZeros)(writer, 8); // liFA
2442
2667
  }
2443
2668
  if (version >= 5)
2444
2669
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, file.childDocumentID || '');
@@ -2446,6 +2671,8 @@ function createLnkHandler(tag) {
2446
2671
  (0, psdWriter_1.writeFloat64)(writer, file.assetModTime || 0);
2447
2672
  if (version >= 7)
2448
2673
  (0, psdWriter_1.writeUint8)(writer, file.assetLockedState || 0);
2674
+ if (external && version === 2 && file.data)
2675
+ (0, psdWriter_1.writeBytes)(writer, file.data);
2449
2676
  let size = writer.offset - sizeOffset;
2450
2677
  writer.view.setUint32(sizeOffset - 4, size, false); // write size
2451
2678
  while (size % 4) {
@@ -2459,28 +2686,40 @@ createLnkHandler('lnk2');
2459
2686
  createLnkHandler('lnkE');
2460
2687
  addHandlerAlias('lnkD', 'lnk2');
2461
2688
  addHandlerAlias('lnk3', 'lnk2');
2462
- addHandler('pths', hasKey('pathList'), (reader, target) => {
2689
+ // unicode names of saved paths (path data is stored in image resources 2000-2998)
2690
+ addHandler('pths', target => { var _a; return ((_a = target.imageResources) === null || _a === void 0 ? void 0 : _a.savedPaths) !== undefined; }, (reader, _target, left, psd) => {
2691
+ var _a, _b;
2463
2692
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
2464
- // console.log(require('util').inspect(desc, false, 99, true));
2465
- // if (options.throwForMissingFeatures && desc?.pathList?.length) throw new Error('non-empty pathList in `pths`');
2466
- desc;
2467
- target.pathList = []; // TODO: read paths
2468
- }, (writer, _target) => {
2693
+ const imageResources = (_a = psd.imageResources) !== null && _a !== void 0 ? _a : (psd.imageResources = {});
2694
+ const savedPaths = (_b = imageResources.savedPaths) !== null && _b !== void 0 ? _b : (imageResources.savedPaths = []);
2695
+ for (let i = 0; i < desc.pathList.length && i < savedPaths.length; i++) {
2696
+ const { pathUnicodeName, pathSymmetryClass } = desc.pathList[i];
2697
+ const path = savedPaths[i];
2698
+ // clipping path is referenced by (non-unicode) name
2699
+ if (imageResources.clippingPath && imageResources.clippingPath.name === path.name) {
2700
+ imageResources.clippingPath.name = pathUnicodeName;
2701
+ }
2702
+ path.name = pathUnicodeName;
2703
+ if (pathSymmetryClass)
2704
+ path.symmetryMode = pathSymmetryClass.pathSymmetryMode.split('.')[1];
2705
+ }
2706
+ (0, psdReader_1.skipBytes)(reader, left());
2707
+ }, (writer, _target, psd) => {
2469
2708
  const desc = {
2470
- pathList: [], // TODO: write paths
2709
+ pathList: psd.imageResources.savedPaths.map(path => (Object.assign({ _name: '', _classID: 'pathInfoClass', pathUnicodeName: path.name }, (path.symmetryMode ? {
2710
+ pathSymmetryClass: {
2711
+ _name: '',
2712
+ _classID: 'pathSymmetryClass',
2713
+ pathSymmetryMode: `pathSymmetryModeEnum.${path.symmetryMode}`,
2714
+ },
2715
+ } : {})))),
2471
2716
  };
2472
2717
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'pathsDataClass', desc);
2473
2718
  });
2474
2719
  addHandler('lyvr', hasKey('version'), (reader, target) => target.version = (0, psdReader_1.readUint32)(reader), (writer, target) => (0, psdWriter_1.writeUint32)(writer, target.version));
2475
- addHandler('lfxs', () => false, // TODO: not sure when we actually need to write this section
2476
- // NOTE: this might be insufficient
2477
- // target => target.effects !== undefined && (
2478
- // !!target.effects.dropShadow?.some(e => e.choke) ||
2479
- // !!target.effects.innerShadow?.some(e => e.choke) ||
2480
- // !!target.effects.outerGlow?.choke ||
2481
- // !!target.effects.innerGlow?.choke
2482
- // ),
2483
- (reader, target, left) => {
2720
+ addHandler('lfxs', // effects of group layers (instead of 'lfx2' / 'lmfx')
2721
+ // effects of group layers (instead of 'lfx2' / 'lmfx')
2722
+ target => target.effects !== undefined && isGroupLayer(target), (reader, target, left) => {
2484
2723
  const version = (0, psdReader_1.readUint32)(reader);
2485
2724
  if (version !== 0)
2486
2725
  throw new Error(`Invalid lfxs version`);
@@ -2524,6 +2763,10 @@ function readLevelsChannel(reader) {
2524
2763
  const midtoneInput = (0, psdReader_1.readInt16)(reader) / 100;
2525
2764
  return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
2526
2765
  }
2766
+ const defaultLevelsChannel = { shadowInput: 0, highlightInput: 255, shadowOutput: 0, highlightOutput: 255, midtoneInput: 1 };
2767
+ function isDefaultLevelsChannel(c) {
2768
+ return c.shadowInput === 0 && c.highlightInput === 255 && c.shadowOutput === 0 && c.highlightOutput === 255 && c.midtoneInput === 1;
2769
+ }
2527
2770
  function writeLevelsChannel(writer, channel) {
2528
2771
  (0, psdWriter_1.writeInt16)(writer, channel.shadowInput);
2529
2772
  (0, psdWriter_1.writeInt16)(writer, channel.highlightInput);
@@ -2534,48 +2777,36 @@ function writeLevelsChannel(writer, channel) {
2534
2777
  addHandler('levl', adjustmentType('levels'), (reader, target, left) => {
2535
2778
  if ((0, psdReader_1.readUint16)(reader) !== 2)
2536
2779
  throw new Error('Invalid levl version');
2537
- const levels = [];
2538
- for (let i = 0; i < 29 && left(); i++) {
2539
- levels.push(readLevelsChannel(reader));
2540
- }
2541
- const [rgb, red, green, blue] = levels;
2542
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', rgb,
2543
- red,
2544
- green,
2545
- blue });
2546
- if (left()) {
2780
+ const channels = [];
2781
+ for (let i = 0; i < 29 && left() >= 10; i++) {
2782
+ channels.push(readLevelsChannel(reader));
2783
+ }
2784
+ if (left() >= 8) {
2547
2785
  if ((0, psdReader_1.readSignature)(reader) !== 'Lvls')
2548
2786
  throw new Error(`Invalid levl signature`);
2549
2787
  if ((0, psdReader_1.readUint16)(reader) !== 3)
2550
2788
  throw new Error(`Invalid levl version`);
2551
2789
  const totalChannelRecords = (0, psdReader_1.readUint16)(reader);
2552
- for (let i = 0; i < (totalChannelRecords - levels.length) && left(); i++) {
2553
- levels.push(readLevelsChannel(reader));
2790
+ while (channels.length < totalChannelRecords && left() >= 10) {
2791
+ channels.push(readLevelsChannel(reader));
2554
2792
  }
2555
2793
  }
2794
+ // records for all possible channels are stored, skip unused ones
2795
+ while (channels.length && isDefaultLevelsChannel(channels[channels.length - 1]))
2796
+ channels.pop();
2797
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', channels });
2556
2798
  (0, psdReader_1.skipBytes)(reader, left());
2557
2799
  }, (writer, target) => {
2558
- const info = target.adjustment;
2559
- const defaultChannel = {
2560
- shadowInput: 0,
2561
- highlightInput: 255,
2562
- shadowOutput: 0,
2563
- highlightOutput: 255,
2564
- midtoneInput: 1,
2565
- };
2800
+ const channels = target.adjustment.channels || [];
2566
2801
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2567
- writeLevelsChannel(writer, info.rgb || defaultChannel);
2568
- writeLevelsChannel(writer, info.red || defaultChannel);
2569
- writeLevelsChannel(writer, info.green || defaultChannel);
2570
- writeLevelsChannel(writer, info.blue || defaultChannel);
2571
- for (let i = 0; i < (29 - 4); i++) {
2572
- writeLevelsChannel(writer, defaultChannel);
2802
+ for (let i = 0; i < 29; i++) {
2803
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2573
2804
  }
2574
2805
  (0, psdWriter_1.writeSignature)(writer, 'Lvls');
2575
2806
  (0, psdWriter_1.writeUint16)(writer, 3); // version
2576
2807
  (0, psdWriter_1.writeUint16)(writer, 62); // total count
2577
- for (let i = 0; i < (62 - 29); i++) {
2578
- writeLevelsChannel(writer, defaultChannel);
2808
+ for (let i = 29; i < 62; i++) {
2809
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2579
2810
  }
2580
2811
  });
2581
2812
  function readCurveChannel(reader) {
@@ -2600,17 +2831,16 @@ addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
2600
2831
  if ((0, psdReader_1.readUint16)(reader) !== 1)
2601
2832
  throw new Error('Invalid curv version');
2602
2833
  (0, psdReader_1.readUint16)(reader);
2603
- const channels = (0, psdReader_1.readUint16)(reader);
2604
- const info = { type: 'curves' };
2605
- if (channels & 1)
2606
- info.rgb = readCurveChannel(reader);
2607
- if (channels & 2)
2608
- info.red = readCurveChannel(reader);
2609
- if (channels & 4)
2610
- info.green = readCurveChannel(reader);
2611
- if (channels & 8)
2612
- info.blue = readCurveChannel(reader);
2613
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), info);
2834
+ const channelsMask = (0, psdReader_1.readUint16)(reader);
2835
+ const channels = [];
2836
+ for (let i = 0; i < 16; i++) {
2837
+ if (channelsMask & (1 << i)) {
2838
+ while (channels.length < i)
2839
+ channels.push([]);
2840
+ channels.push(readCurveChannel(reader));
2841
+ }
2842
+ }
2843
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'curves', channels });
2614
2844
  // ignoring, duplicate information
2615
2845
  // checkSignature(reader, 'Crv ');
2616
2846
  // const cVersion = readUint16(reader);
@@ -2626,57 +2856,29 @@ addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
2626
2856
  // }
2627
2857
  (0, psdReader_1.skipBytes)(reader, left());
2628
2858
  }, (writer, target) => {
2629
- const info = target.adjustment;
2630
- const { rgb, red, green, blue } = info;
2631
- let channels = 0;
2632
- let channelCount = 0;
2633
- if (rgb && rgb.length) {
2634
- channels |= 1;
2635
- channelCount++;
2636
- }
2637
- if (red && red.length) {
2638
- channels |= 2;
2639
- channelCount++;
2640
- }
2641
- if (green && green.length) {
2642
- channels |= 4;
2643
- channelCount++;
2644
- }
2645
- if (blue && blue.length) {
2646
- channels |= 8;
2647
- channelCount++;
2859
+ var _a;
2860
+ const channels = (target.adjustment.channels || []).slice(0, 16);
2861
+ const used = [];
2862
+ let channelsMask = 0;
2863
+ for (let i = 0; i < channels.length; i++) {
2864
+ if ((_a = channels[i]) === null || _a === void 0 ? void 0 : _a.length) {
2865
+ channelsMask |= 1 << i;
2866
+ used.push(i);
2867
+ }
2648
2868
  }
2649
2869
  (0, psdWriter_1.writeUint8)(writer, 0);
2650
2870
  (0, psdWriter_1.writeUint16)(writer, 1); // version
2651
2871
  (0, psdWriter_1.writeUint16)(writer, 0);
2652
- (0, psdWriter_1.writeUint16)(writer, channels);
2653
- if (rgb && rgb.length)
2654
- writeCurveChannel(writer, rgb);
2655
- if (red && red.length)
2656
- writeCurveChannel(writer, red);
2657
- if (green && green.length)
2658
- writeCurveChannel(writer, green);
2659
- if (blue && blue.length)
2660
- writeCurveChannel(writer, blue);
2872
+ (0, psdWriter_1.writeUint16)(writer, channelsMask);
2873
+ for (const i of used)
2874
+ writeCurveChannel(writer, channels[i]);
2661
2875
  (0, psdWriter_1.writeSignature)(writer, 'Crv ');
2662
2876
  (0, psdWriter_1.writeUint16)(writer, 4); // version
2663
2877
  (0, psdWriter_1.writeUint16)(writer, 0);
2664
- (0, psdWriter_1.writeUint16)(writer, channelCount);
2665
- if (rgb && rgb.length) {
2666
- (0, psdWriter_1.writeUint16)(writer, 0);
2667
- writeCurveChannel(writer, rgb);
2668
- }
2669
- if (red && red.length) {
2670
- (0, psdWriter_1.writeUint16)(writer, 1);
2671
- writeCurveChannel(writer, red);
2672
- }
2673
- if (green && green.length) {
2674
- (0, psdWriter_1.writeUint16)(writer, 2);
2675
- writeCurveChannel(writer, green);
2676
- }
2677
- if (blue && blue.length) {
2678
- (0, psdWriter_1.writeUint16)(writer, 3);
2679
- writeCurveChannel(writer, blue);
2878
+ (0, psdWriter_1.writeUint16)(writer, used.length);
2879
+ for (const i of used) {
2880
+ (0, psdWriter_1.writeUint16)(writer, i);
2881
+ writeCurveChannel(writer, channels[i]);
2680
2882
  }
2681
2883
  });
2682
2884
  addHandler('expA', adjustmentType('exposure'), (reader, target, left) => {
@@ -2733,12 +2935,34 @@ function writeHueChannel(writer, channel) {
2733
2935
  addHandler('hue2', adjustmentType('hue/saturation'), (reader, target, left) => {
2734
2936
  if ((0, psdReader_1.readUint16)(reader) !== 2)
2735
2937
  throw new Error('Invalid hue2 version');
2736
- 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) });
2938
+ const colorize = !!(0, psdReader_1.readUint8)(reader);
2939
+ (0, psdReader_1.skipBytes)(reader, 1);
2940
+ const colorizeHue = (0, psdReader_1.readInt16)(reader); // stored as -180..180
2941
+ const colorizeSaturation = (0, psdReader_1.readInt16)(reader);
2942
+ const colorizeLightness = (0, psdReader_1.readInt16)(reader);
2943
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'hue/saturation', colorize, colorization: {
2944
+ hue: colorizeHue < 0 ? colorizeHue + 360 : colorizeHue,
2945
+ saturation: colorizeSaturation,
2946
+ lightness: colorizeLightness,
2947
+ }, master: {
2948
+ hue: (0, psdReader_1.readInt16)(reader),
2949
+ saturation: (0, psdReader_1.readInt16)(reader),
2950
+ lightness: (0, psdReader_1.readInt16)(reader),
2951
+ }, reds: readHueChannel(reader), yellows: readHueChannel(reader), greens: readHueChannel(reader), cyans: readHueChannel(reader), blues: readHueChannel(reader), magentas: readHueChannel(reader) });
2737
2952
  (0, psdReader_1.skipBytes)(reader, left());
2738
2953
  }, (writer, target) => {
2954
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
2739
2955
  const info = target.adjustment;
2740
2956
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2741
- writeHueChannel(writer, info.master);
2957
+ (0, psdWriter_1.writeUint8)(writer, info.colorize ? 1 : 0);
2958
+ (0, psdWriter_1.writeUint8)(writer, 0);
2959
+ const colorizeHue = Math.round((_b = (_a = info.colorization) === null || _a === void 0 ? void 0 : _a.hue) !== null && _b !== void 0 ? _b : 0);
2960
+ (0, psdWriter_1.writeInt16)(writer, colorizeHue > 180 ? colorizeHue - 360 : colorizeHue);
2961
+ (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));
2962
+ (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));
2963
+ (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));
2964
+ (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));
2965
+ (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));
2742
2966
  writeHueChannel(writer, info.reds);
2743
2967
  writeHueChannel(writer, info.yellows);
2744
2968
  writeHueChannel(writer, info.greens);
@@ -2817,11 +3041,18 @@ addHandler('phfl', adjustmentType('photo filter'), (reader, target, left) => {
2817
3041
  color = (0, psdReader_1.readColor)(reader);
2818
3042
  }
2819
3043
  else { // version 3
2820
- // TODO: test this, this is probably wrong
3044
+ // XYZ color relative to D50 white point, 32768 = 1.0 (not documented, matches colors of Photoshop presets),
3045
+ // converted to Lab (same scale as readColor()) so it can be written back as version 2
3046
+ const x = (0, psdReader_1.readInt32)(reader) / 32768;
3047
+ const y = (0, psdReader_1.readInt32)(reader) / 32768;
3048
+ const z = (0, psdReader_1.readInt32)(reader) / 32768;
3049
+ if (!(x >= 0 && y >= 0 && z >= 0 && x <= 2 && y <= 2 && z <= 2))
3050
+ throw new Error('Invalid phfl XYZ color');
3051
+ const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
2821
3052
  color = {
2822
- l: (0, psdReader_1.readInt32)(reader) / 100,
2823
- a: (0, psdReader_1.readInt32)(reader) / 100,
2824
- b: (0, psdReader_1.readInt32)(reader) / 100,
3053
+ l: Math.max(0, 116 * f(y) - 16) / 100,
3054
+ a: (0, helpers_1.normalizeLabAB)(500 * (f(x) - f(y))),
3055
+ b: (0, helpers_1.normalizeLabAB)(200 * (f(y) - f(z))),
2825
3056
  };
2826
3057
  }
2827
3058
  target.adjustment = {
@@ -2835,7 +3066,7 @@ addHandler('phfl', adjustmentType('photo filter'), (reader, target, left) => {
2835
3066
  const info = target.adjustment;
2836
3067
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2837
3068
  (0, psdWriter_1.writeColor)(writer, info.color || { l: 0, a: 0, b: 0 });
2838
- (0, psdWriter_1.writeUint32)(writer, (info.density || 0) * 100);
3069
+ (0, psdWriter_1.writeUint32)(writer, Math.round(Math.max(0, info.density || 0) * 100));
2839
3070
  (0, psdWriter_1.writeUint8)(writer, info.preserveLuminosity ? 1 : 0);
2840
3071
  (0, psdWriter_1.writeZeros)(writer, 3);
2841
3072
  });
@@ -2989,7 +3220,7 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
2989
3220
  reader.offset--;
2990
3221
  if (hasMethod) {
2991
3222
  const method = (0, psdReader_1.readSignature)(reader);
2992
- info.method = descriptor_1.gradientInterpolationMethodType.decode(method);
3223
+ info.method = descriptor_1.gradientInterpolationMethodType.decode(`gradientInterpolationMethodType.${method}`);
2993
3224
  }
2994
3225
  info.name = (0, psdReader_1.readUnicodeString)(reader);
2995
3226
  info.colorStops = [];
@@ -3038,10 +3269,11 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3038
3269
  (0, psdReader_1.readUint16)(reader) / 0x8000,
3039
3270
  ];
3040
3271
  (0, psdReader_1.skipBytes)(reader, left());
3272
+ // stop locations are always in 0..4096 range, independent of smoothness
3041
3273
  for (const s of info.colorStops)
3042
- s.location /= interpolation;
3274
+ s.location /= 4096;
3043
3275
  for (const s of info.opacityStops)
3044
- s.location /= interpolation;
3276
+ s.location /= 4096;
3045
3277
  target.adjustment = info;
3046
3278
  }, (writer, target) => {
3047
3279
  var _a, _b, _c;
@@ -3050,22 +3282,22 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3050
3282
  (0, psdWriter_1.writeUint8)(writer, info.reverse ? 1 : 0);
3051
3283
  (0, psdWriter_1.writeUint8)(writer, info.dither ? 1 : 0);
3052
3284
  if (info.method !== undefined) {
3053
- (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method));
3285
+ (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method).split('.')[1]);
3054
3286
  }
3055
3287
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, info.name || '');
3056
3288
  (0, psdWriter_1.writeUint16)(writer, info.colorStops && info.colorStops.length || 0);
3057
- const interpolation = Math.round(((_a = info.smoothness) !== null && _a !== void 0 ? _a : 1) * 4096);
3289
+ const interpolation = Math.round((0, helpers_1.clamp)((_a = info.smoothness) !== null && _a !== void 0 ? _a : 1, 0, 1) * 4096);
3058
3290
  for (const s of info.colorStops || []) {
3059
- (0, psdWriter_1.writeUint32)(writer, Math.round(s.location * interpolation));
3291
+ (0, psdWriter_1.writeUint32)(writer, Math.round((0, helpers_1.clamp)(s.location, 0, 1) * 4096));
3060
3292
  (0, psdWriter_1.writeUint32)(writer, Math.round(s.midpoint * 100));
3061
3293
  (0, psdWriter_1.writeColor)(writer, s.color);
3062
3294
  (0, psdWriter_1.writeZeros)(writer, 2);
3063
3295
  }
3064
3296
  (0, psdWriter_1.writeUint16)(writer, info.opacityStops && info.opacityStops.length || 0);
3065
3297
  for (const s of info.opacityStops || []) {
3066
- (0, psdWriter_1.writeUint32)(writer, Math.round(s.location * interpolation));
3298
+ (0, psdWriter_1.writeUint32)(writer, Math.round((0, helpers_1.clamp)(s.location, 0, 1) * 4096));
3067
3299
  (0, psdWriter_1.writeUint32)(writer, Math.round(s.midpoint * 100));
3068
- (0, psdWriter_1.writeUint16)(writer, Math.round(s.opacity * 0xff));
3300
+ (0, psdWriter_1.writeUint16)(writer, Math.round((0, helpers_1.clamp)(s.opacity, 0, 1) * 0xff));
3069
3301
  }
3070
3302
  (0, psdWriter_1.writeUint16)(writer, 2); // expansion count
3071
3303
  (0, psdWriter_1.writeUint16)(writer, interpolation);
@@ -3227,7 +3459,7 @@ addHandler('Txt2', hasKey('engineData'), (reader, target, left, psd) => {
3227
3459
  const data = (0, psdReader_1.readBytes)(reader, left());
3228
3460
  target.engineData = (0, base64_js_1.fromByteArray)(data);
3229
3461
  const layersByIndex = getTextLayersSortedByIndex(psd);
3230
- const engineData = (0, engineData_1.parseEngineData)(data);
3462
+ const engineData = (0, engineData_1.parseEngineData)(data, reader);
3231
3463
  const engineData2 = (0, engineData2_1.decodeEngineData2)(engineData);
3232
3464
  const TextFrameSet = engineData2.ResourceDict.TextFrameSet;
3233
3465
  if (TextFrameSet) {
@@ -3309,7 +3541,6 @@ addHandler('FEid', hasKey('filterEffectsMasks'), (reader, target, leftBytes) =>
3309
3541
  }
3310
3542
  }
3311
3543
  }, (writer, target) => {
3312
- var _a;
3313
3544
  (0, psdWriter_1.writeInt32)(writer, 3); // version
3314
3545
  for (const mask of target.filterEffectsMasks) {
3315
3546
  (0, psdWriter_1.writeUint32)(writer, 0);
@@ -3338,7 +3569,7 @@ addHandler('FEid', hasKey('filterEffectsMasks'), (reader, target, leftBytes) =>
3338
3569
  }
3339
3570
  }
3340
3571
  writer.view.setUint32(length2Offset - 4, writer.offset - length2Offset, false);
3341
- const extra = (_a = target.filterEffectsMasks[target.filterEffectsMasks.length - 1]) === null || _a === void 0 ? void 0 : _a.extra;
3572
+ const extra = mask.extra;
3342
3573
  if (extra) {
3343
3574
  (0, psdWriter_1.writeUint8)(writer, 1);
3344
3575
  (0, psdWriter_1.writeInt32)(writer, extra.top);
@@ -3362,28 +3593,33 @@ addHandlerAlias('FXid', 'FEid');
3362
3593
  addHandler('FMsk', hasKey('filterMask'), (reader, target) => {
3363
3594
  target.filterMask = {
3364
3595
  colorSpace: (0, psdReader_1.readColor)(reader),
3365
- opacity: (0, psdReader_1.readUint16)(reader) / 0xff,
3596
+ opacity: (0, psdReader_1.readUint16)(reader) / 100,
3366
3597
  };
3367
3598
  }, (writer, target) => {
3368
3599
  var _a;
3369
3600
  (0, psdWriter_1.writeColor)(writer, target.filterMask.colorSpace);
3370
- (0, psdWriter_1.writeUint16)(writer, (0, helpers_1.clamp)((_a = target.filterMask.opacity) !== null && _a !== void 0 ? _a : 1, 0, 1) * 0xff);
3601
+ (0, psdWriter_1.writeUint16)(writer, Math.round((0, helpers_1.clamp)((_a = target.filterMask.opacity) !== null && _a !== void 0 ? _a : 1, 0, 1) * 100));
3371
3602
  });
3372
3603
  addHandler('artd', // document-wide artboard info
3373
3604
  // document-wide artboard info
3374
3605
  target => target.artboards !== undefined, (reader, target, left) => {
3375
3606
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3376
- target.artboards = {
3377
- count: desc['Cnt '],
3378
- autoExpandOffset: { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc },
3379
- origin: { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc },
3380
- autoExpandEnabled: desc.autoExpandEnabled,
3381
- autoNestEnabled: desc.autoNestEnabled,
3382
- autoPositionEnabled: desc.autoPositionEnabled,
3383
- shrinkwrapOnSaveEnabled: !!desc.shrinkwrapOnSaveEnabled,
3384
- docDefaultNewArtboardBackgroundColor: (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor),
3385
- docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
3386
- };
3607
+ const artboards = target.artboards = { count: desc['Cnt '] };
3608
+ if (desc.autoExpandOffset)
3609
+ artboards.autoExpandOffset = { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc };
3610
+ if (desc.origin)
3611
+ artboards.origin = { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc };
3612
+ if (desc.autoExpandEnabled !== undefined)
3613
+ artboards.autoExpandEnabled = desc.autoExpandEnabled;
3614
+ if (desc.autoNestEnabled !== undefined)
3615
+ artboards.autoNestEnabled = desc.autoNestEnabled;
3616
+ if (desc.autoPositionEnabled !== undefined)
3617
+ artboards.autoPositionEnabled = desc.autoPositionEnabled;
3618
+ artboards.shrinkwrapOnSaveEnabled = !!desc.shrinkwrapOnSaveEnabled;
3619
+ if (desc.docDefaultNewArtboardBackgroundColor)
3620
+ artboards.docDefaultNewArtboardBackgroundColor = (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor);
3621
+ if (desc.docDefaultNewArtboardBackgroundType !== undefined)
3622
+ artboards.docDefaultNewArtboardBackgroundType = desc.docDefaultNewArtboardBackgroundType;
3387
3623
  (0, psdReader_1.skipBytes)(reader, left());
3388
3624
  }, (writer, target) => {
3389
3625
  var _a, _b, _c, _d, _e;
@@ -3401,17 +3637,19 @@ target => target.artboards !== undefined, (reader, target, left) => {
3401
3637
  };
3402
3638
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, 'artd');
3403
3639
  });
3640
+ function isGroupLayer(target) {
3641
+ return !!target.children;
3642
+ }
3404
3643
  function hasMultiEffects(effects) {
3405
3644
  return Object.keys(effects).map(key => effects[key]).some(v => Array.isArray(v) && v.length > 1);
3406
3645
  }
3407
- addHandler('lfx2', target => target.effects !== undefined && !hasMultiEffects(target.effects), (reader, target, left) => {
3646
+ addHandler('lfx2', target => target.effects !== undefined && !isGroupLayer(target) && !hasMultiEffects(target.effects), (reader, target, left) => {
3408
3647
  const version = (0, psdReader_1.readUint32)(reader);
3409
3648
  if (version !== 0)
3410
3649
  throw new Error(`Invalid lfx2 version`);
3411
3650
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3412
3651
  // console.log('READ', require('util').inspect(desc, false, 99, true));
3413
- // TODO: don't discard if we got it from lmfx
3414
- // discard if read in 'lrFX' section
3652
+ // discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
3415
3653
  target.effects = (0, descriptor_1.parseEffects)(desc, !!reader.logMissingFeatures);
3416
3654
  (0, psdReader_1.skipBytes)(reader, left());
3417
3655
  }, (writer, target, _, options) => {
@@ -3468,27 +3706,25 @@ addHandler('cinf', hasKey('compositorUsed'), (reader, target, left) => {
3468
3706
  desc.compCoreGPUSupport = `reason.${cinf.compCoreGPUSupport}`;
3469
3707
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
3470
3708
  });
3471
- // extension settings ?, ignore it
3472
- addHandler('extn', target => target._extn !== undefined, (reader, target) => {
3709
+ // generator settings of the document (layers store them in 'shmd' section)
3710
+ addHandler('extn', target => target.generatorSettings !== undefined && isDocument(target), (reader, target, left) => {
3473
3711
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3474
- // console.log(require('util').inspect(desc, false, 99, true));
3475
- if (helpers_1.MOCK_HANDLERS)
3476
- target._extn = desc;
3712
+ if (desc.generatorSettings)
3713
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
3714
+ (0, psdReader_1.skipBytes)(reader, left());
3477
3715
  }, (writer, target) => {
3478
- // TODO: need to add correct types for desc fields (resources/src.psd)
3479
- if (helpers_1.MOCK_HANDLERS)
3480
- (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', target._extn);
3716
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(target.generatorSettings));
3481
3717
  });
3482
3718
  addHandler('iOpa', hasKey('fillOpacity'), (reader, target) => {
3483
3719
  target.fillOpacity = (0, psdReader_1.readUint8)(reader) / 0xff;
3484
3720
  (0, psdReader_1.skipBytes)(reader, 3);
3485
3721
  }, (writer, target) => {
3486
- (0, psdWriter_1.writeUint8)(writer, target.fillOpacity * 0xff);
3722
+ (0, psdWriter_1.writeUint8)(writer, Math.round((0, helpers_1.clamp)(target.fillOpacity, 0, 1) * 0xff));
3487
3723
  (0, psdWriter_1.writeZeros)(writer, 3);
3488
3724
  });
3489
3725
  addHandler('brst', hasKey('channelBlendingRestrictions'), (reader, target, left) => {
3490
3726
  target.channelBlendingRestrictions = [];
3491
- while (left() > 4) {
3727
+ while (left() >= 4) {
3492
3728
  target.channelBlendingRestrictions.push((0, psdReader_1.readInt32)(reader));
3493
3729
  }
3494
3730
  }, (writer, target) => {