ag-psd 32.0.0 → 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 (103) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  22. package/CHANGELOG.md +78 -0
  23. package/LICENSE +5 -0
  24. package/README.md +3 -6
  25. package/README_PSD.md +242 -81
  26. package/dist/abr.d.ts +11 -4
  27. package/dist/abr.js +357 -230
  28. package/dist/abr.js.map +1 -1
  29. package/dist/additionalInfo.d.ts +54 -2
  30. package/dist/additionalInfo.js +487 -306
  31. package/dist/additionalInfo.js.map +1 -1
  32. package/dist/ase.d.ts +6 -1
  33. package/dist/ase.js +61 -61
  34. package/dist/ase.js.map +1 -1
  35. package/dist/bundle.js +1937 -1115
  36. package/dist/descriptor.d.ts +14 -8
  37. package/dist/descriptor.js +202 -186
  38. package/dist/descriptor.js.map +1 -1
  39. package/dist/helpers.d.ts +1 -1
  40. package/dist/helpers.js +4 -2
  41. package/dist/helpers.js.map +1 -1
  42. package/dist/imageResources.d.ts +15 -3
  43. package/dist/imageResources.js +503 -142
  44. package/dist/imageResources.js.map +1 -1
  45. package/dist/jpeg.js +8 -8
  46. package/dist/jpeg.js.map +1 -1
  47. package/dist/psd.d.ts +148 -17
  48. package/dist/psd.js.map +1 -1
  49. package/dist/psdReader.d.ts +10 -0
  50. package/dist/psdReader.js +278 -166
  51. package/dist/psdReader.js.map +1 -1
  52. package/dist/psdWriter.d.ts +1 -0
  53. package/dist/psdWriter.js +77 -62
  54. package/dist/psdWriter.js.map +1 -1
  55. package/dist/text.js +17 -6
  56. package/dist/text.js.map +1 -1
  57. package/dist/utf8.js +0 -3
  58. package/dist/utf8.js.map +1 -1
  59. package/dist-es/abr.d.ts +11 -4
  60. package/dist-es/abr.js +357 -230
  61. package/dist-es/abr.js.map +1 -1
  62. package/dist-es/additionalInfo.d.ts +54 -2
  63. package/dist-es/additionalInfo.js +487 -306
  64. package/dist-es/additionalInfo.js.map +1 -1
  65. package/dist-es/ase.d.ts +6 -1
  66. package/dist-es/ase.js +61 -61
  67. package/dist-es/ase.js.map +1 -1
  68. package/dist-es/descriptor.d.ts +14 -8
  69. package/dist-es/descriptor.js +202 -186
  70. package/dist-es/descriptor.js.map +1 -1
  71. package/dist-es/helpers.d.ts +1 -1
  72. package/dist-es/helpers.js +4 -2
  73. package/dist-es/helpers.js.map +1 -1
  74. package/dist-es/imageResources.d.ts +15 -3
  75. package/dist-es/imageResources.js +503 -142
  76. package/dist-es/imageResources.js.map +1 -1
  77. package/dist-es/jpeg.js +8 -8
  78. package/dist-es/jpeg.js.map +1 -1
  79. package/dist-es/psd.d.ts +148 -17
  80. package/dist-es/psd.js.map +1 -1
  81. package/dist-es/psdReader.d.ts +10 -0
  82. package/dist-es/psdReader.js +278 -166
  83. package/dist-es/psdReader.js.map +1 -1
  84. package/dist-es/psdWriter.d.ts +1 -0
  85. package/dist-es/psdWriter.js +77 -62
  86. package/dist-es/psdWriter.js.map +1 -1
  87. package/dist-es/text.js +17 -6
  88. package/dist-es/text.js.map +1 -1
  89. package/dist-es/utf8.js +0 -3
  90. package/dist-es/utf8.js.map +1 -1
  91. package/package.json +2 -2
  92. package/src/abr.ts +375 -235
  93. package/src/additionalInfo.ts +663 -420
  94. package/src/ase.ts +66 -61
  95. package/src/descriptor.ts +199 -195
  96. package/src/helpers.ts +4 -1
  97. package/src/imageResources.ts +632 -168
  98. package/src/jpeg.ts +8 -8
  99. package/src/psd.ts +156 -30
  100. package/src/psdReader.ts +273 -172
  101. package/src/psdWriter.ts +95 -58
  102. package/src/text.ts +17 -6
  103. package/src/utf8.ts +0 -4
package/dist/bundle.js CHANGED
@@ -99,261 +99,92 @@ function parseHeightmap(array) {
99
99
  // return result;
100
100
  }
101
101
  function readAbr(buffer, options = {}) {
102
- var _a, _b, _c, _d;
103
102
  const reader = (0, psdReader_1.createReader)(buffer.buffer, buffer.byteOffset, buffer.byteLength);
103
+ reader.logMissingFeatures = options.logMissingFeatures;
104
+ reader.throwForMissingFeatures = options.throwForMissingFeatures;
105
+ reader.totalMemoryLimit = 'totalMemoryLimit' in options ? options.totalMemoryLimit : 2 * 1024 * 1024 * 1024;
106
+ // skips invalid or unsupported part of the file
107
+ const skip = (e, what) => {
108
+ if (options.throwForMissingFeatures)
109
+ throw e;
110
+ options.logMissingFeatures && reader.log(`Skipped ${what}: ${e.message}`);
111
+ };
112
+ if (reader.view.byteLength < 4)
113
+ throw new Error('Invalid ABR file');
104
114
  const version = (0, psdReader_1.readInt16)(reader);
105
115
  const samples = [];
106
116
  const brushes = [];
107
117
  const patterns = [];
118
+ const brushByPreset = []; // brush for each preset in 'desc' section (brushes that fail to parse are skipped)
119
+ let hierarchy;
108
120
  if (version === 1 || version === 2) {
109
- throw new Error(`Unsupported ABR version (${version})`); // TODO: ...
121
+ readLegacyBrushes(reader, version, samples, brushes, skip);
110
122
  }
111
123
  else if (version === 6 || version === 7 || version === 9 || version === 10) {
112
124
  const minorVersion = (0, psdReader_1.readInt16)(reader);
113
125
  if (minorVersion !== 1 && minorVersion !== 2)
114
126
  throw new Error('Unsupported ABR minor version');
115
- while (reader.offset < reader.view.byteLength) {
127
+ // section header is 12 bytes, ignore any shorter trailing data
128
+ while (reader.offset + 12 <= reader.view.byteLength) {
116
129
  (0, psdReader_1.checkSignature)(reader, '8BIM');
117
130
  const type = (0, psdReader_1.readSignature)(reader);
118
131
  const size = (0, psdReader_1.readUint32)(reader);
119
- const end = reader.offset + size;
120
- switch (type) {
121
- case 'samp': {
122
- while (reader.offset < end) {
123
- let brushLength = (0, psdReader_1.readUint32)(reader);
124
- while (brushLength & 0b11)
125
- brushLength++; // pad to 4 byte alignment
126
- const brushEnd = reader.offset + brushLength;
127
- const id = (0, psdReader_1.readPascalString)(reader, 1);
128
- // v1 - Skip the Int16 bounds rectangle and the unknown Int16.
129
- // v2 - Skip the unknown bytes.
130
- (0, psdReader_1.skipBytes)(reader, minorVersion === 1 ? 10 : 264);
131
- const y = (0, psdReader_1.readInt32)(reader);
132
- const x = (0, psdReader_1.readInt32)(reader);
133
- const h = (0, psdReader_1.readInt32)(reader) - y;
134
- const w = (0, psdReader_1.readInt32)(reader) - x;
135
- if (w <= 0 || h <= 0)
136
- throw new Error('Invalid bounds');
137
- if (w > MAX_BRUSH_SIZE || h > MAX_BRUSH_SIZE) {
138
- throw new Error(`Brush sample too large (${w}x${h})`);
139
- }
140
- const bithDepth = (0, psdReader_1.readInt16)(reader);
141
- const compression = (0, psdReader_1.readUint8)(reader); // 0 - raw, 1 - RLE
142
- if (compression === 0 && w * h * (bithDepth === 16 ? 2 : 1) > brushEnd - reader.offset) {
143
- throw new Error('Brush sample data out of bounds');
144
- }
145
- const alpha = new Uint8Array(w * h);
146
- if (bithDepth === 8) {
147
- if (compression === 0) {
148
- alpha.set((0, psdReader_1.readBytes)(reader, alpha.byteLength));
149
- }
150
- else if (compression === 1) {
151
- (0, psdReader_1.readDataRLE)(reader, { width: w, height: h, data: alpha }, w, h, bithDepth, 1, [0], false);
132
+ const end = Math.min(reader.offset + size, reader.view.byteLength);
133
+ try {
134
+ switch (type) {
135
+ case 'samp': {
136
+ while (reader.offset + 4 <= end) {
137
+ let brushLength = (0, psdReader_1.readUint32)(reader);
138
+ while (brushLength & 0b11)
139
+ brushLength++; // pad to 4 byte alignment
140
+ const brushEnd = Math.min(reader.offset + brushLength, end);
141
+ try {
142
+ const sample = readSample(reader, brushEnd, minorVersion);
143
+ samples.push(sample);
152
144
  }
153
- else {
154
- throw new Error('Invalid compression');
145
+ catch (e) {
146
+ skip(e, 'brush sample');
155
147
  }
148
+ reader.offset = brushEnd;
156
149
  }
157
- else if (bithDepth === 16) {
158
- if (compression === 0) {
159
- for (let i = 0; i < alpha.byteLength; i++) {
160
- alpha[i] = (0, psdReader_1.readUint16)(reader) >> 8; // convert to 8bit values
161
- }
162
- }
163
- else if (compression === 1) {
164
- throw new Error('not implemented (16bit RLE)'); // TODO: ...
150
+ break;
151
+ }
152
+ case 'desc': {
153
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
154
+ for (const [index, brush] of (desc.Brsh || []).entries()) {
155
+ try {
156
+ const parsed = parseBrush(brush);
157
+ brushes.push(parsed);
158
+ brushByPreset[index] = parsed;
165
159
  }
166
- else {
167
- throw new Error('Invalid compression');
160
+ catch (e) {
161
+ skip(e, `brush "${brush['Nm ']}"`);
168
162
  }
169
163
  }
170
- else {
171
- throw new Error('Invalid depth');
172
- }
173
- samples.push({ id, bounds: { x, y, w, h }, alpha });
174
- reader.offset = brushEnd;
164
+ break;
175
165
  }
176
- break;
177
- }
178
- case 'desc': {
179
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
180
- // console.log(require('util').inspect(desc, false, 99, true));
181
- // require('fs').writeFileSync('test.log', require('util').inspect(desc, false, 99, false), 'utf8');
182
- for (const brush of desc.Brsh) {
183
- const b = {
184
- name: brush['Nm '],
185
- shape: parseBrushShape(brush.Brsh),
186
- spacing: (0, descriptor_1.parsePercent)(brush.Spcn),
187
- // TODO: brushGroup ???
188
- wetEdges: brush.Wtdg,
189
- noise: brush.Nose,
190
- // TODO: TxtC ??? smoothing / build-up ?
191
- // TODO: 'Rpt ' ???
192
- useBrushSize: brush.useBrushSize, // ???
193
- };
194
- if (brush.interpretation != null)
195
- b.interpretation = brush.interpretation;
196
- if (brush.protectTexture != null)
197
- b.protectTexture = brush.protectTexture;
198
- if (brush.useTipDynamics) {
199
- b.shapeDynamics = {
200
- tiltScale: (0, descriptor_1.parsePercent)(brush.tiltScale),
201
- sizeDynamics: parseDynamics(brush.szVr),
202
- angleDynamics: parseDynamics(brush.angleDynamics),
203
- roundnessDynamics: parseDynamics(brush.roundnessDynamics),
204
- flipX: brush.flipX,
205
- flipY: brush.flipY,
206
- brushProjection: brush.brushProjection,
207
- minimumDiameter: (0, descriptor_1.parsePercent)(brush.minimumDiameter),
208
- minimumRoundness: (0, descriptor_1.parsePercent)(brush.minimumRoundness),
209
- };
210
- }
211
- if (brush.useScatter) {
212
- b.scatter = {
213
- count: brush['Cnt '],
214
- bothAxes: brush.bothAxes,
215
- countDynamics: parseDynamics(brush.countDynamics),
216
- scatterDynamics: parseDynamics(brush.scatterDynamics),
217
- };
218
- }
219
- if (brush.useTexture && brush.Txtr) {
220
- b.texture = {
221
- id: brush.Txtr.Idnt,
222
- name: brush.Txtr['Nm '],
223
- blendMode: descriptor_1.BlnM.decode(brush.textureBlendMode),
224
- depth: (0, descriptor_1.parsePercent)(brush.textureDepth),
225
- depthMinimum: (0, descriptor_1.parsePercent)(brush.minimumDepth),
226
- depthDynamics: parseDynamics(brush.textureDepthDynamics),
227
- scale: (0, descriptor_1.parsePercent)(brush.textureScale),
228
- invert: brush.InvT,
229
- brightness: brush.textureBrightness,
230
- contrast: brush.textureContrast,
231
- textureEachTip: !!brush.TxtC,
232
- };
233
- }
234
- const db = brush.dualBrush;
235
- if (db && db.useDualBrush) {
236
- b.dualBrush = {
237
- flip: db.Flip,
238
- shape: parseBrushShape(db.Brsh),
239
- blendMode: descriptor_1.BlnM.decode(db.BlnM),
240
- useScatter: db.useScatter,
241
- spacing: (0, descriptor_1.parsePercent)(db.Spcn),
242
- count: db['Cnt '],
243
- bothAxes: db.bothAxes,
244
- countDynamics: parseDynamics(db.countDynamics),
245
- scatterDynamics: parseDynamics(db.scatterDynamics),
246
- };
166
+ case 'patt': {
167
+ while (reader.offset + 4 <= end) {
168
+ const pattern = (0, psdReader_1.readPatternSafe)(reader);
169
+ if (pattern)
170
+ patterns.push(pattern);
247
171
  }
248
- if (brush.useColorDynamics) {
249
- b.colorDynamics = {
250
- foregroundBackground: parseDynamics(brush.clVr),
251
- hue: (0, descriptor_1.parsePercent)(brush['H ']),
252
- saturation: (0, descriptor_1.parsePercent)(brush.Strt),
253
- brightness: (0, descriptor_1.parsePercent)(brush.Brgh),
254
- purity: (0, descriptor_1.parsePercent)(brush.purity),
255
- perTip: brush.colorDynamicsPerTip,
256
- };
257
- }
258
- if (brush.usePaintDynamics) {
259
- b.transfer = {
260
- flowDynamics: parseDynamics(brush.prVr),
261
- opacityDynamics: parseDynamics(brush.opVr),
262
- wetnessDynamics: parseDynamics(brush.wtVr),
263
- mixDynamics: parseDynamics(brush.mxVr),
264
- };
265
- }
266
- if (brush.useBrushPose) {
267
- b.brushPose = {
268
- overrideAngle: brush.overridePoseAngle,
269
- overrideTiltX: brush.overridePoseTiltX,
270
- overrideTiltY: brush.overridePoseTiltY,
271
- overridePressure: brush.overridePosePressure,
272
- pressure: (0, descriptor_1.parsePercent)(brush.brushPosePressure),
273
- tiltX: brush.brushPoseTiltX,
274
- tiltY: brush.brushPoseTiltY,
275
- angle: brush.brushPoseAngle,
276
- };
277
- }
278
- const to = brush.toolOptions;
279
- if (to) {
280
- b.toolOptions = {
281
- type: toBrushType[to._classID] || 'brush',
282
- brushPreset: to.brushPreset,
283
- flow: (_a = to.flow) !== null && _a !== void 0 ? _a : 100,
284
- smooth: (_b = to.Smoo) !== null && _b !== void 0 ? _b : 0,
285
- mode: descriptor_1.BlnM.decode(to['Md '] || 'BlnM.Nrml'), // sometimes mode is missing
286
- opacity: (_c = to.Opct) !== null && _c !== void 0 ? _c : 100,
287
- smoothing: !!to.smoothing,
288
- smoothingValue: to.smoothingValue || 0,
289
- smoothingRadiusMode: !!to.smoothingRadiusMode,
290
- smoothingCatchup: !!to.smoothingCatchup,
291
- smoothingCatchupAtEnd: !!to.smoothingCatchupAtEnd,
292
- smoothingZoomCompensation: !!to.smoothingZoomCompensation,
293
- pressureSmoothing: !!to.pressureSmoothing,
294
- usePressureOverridesSize: !!to.usePressureOverridesSize,
295
- usePressureOverridesOpacity: !!to.usePressureOverridesOpacity,
296
- useLegacy: !!to.useLegacy,
297
- };
298
- if (to.prVr)
299
- b.toolOptions.flowDynamics = parseDynamics(to.prVr);
300
- if (to.opVr)
301
- b.toolOptions.opacityDynamics = parseDynamics(to.opVr);
302
- if (to.szVr)
303
- b.toolOptions.sizeDynamics = parseDynamics(to.szVr);
304
- if ('wetness' in to)
305
- b.toolOptions.wetness = to.wetness;
306
- if ('dryness' in to)
307
- b.toolOptions.dryness = to.dryness;
308
- if ('mix' in to)
309
- b.toolOptions.mix = to.mix;
310
- if ('autoFill' in to)
311
- b.toolOptions.autoFill = to.autoFill;
312
- if ('autoClean' in to)
313
- b.toolOptions.autoClean = to.autoClean;
314
- if ('loadSolidColorOnly' in to)
315
- b.toolOptions.loadSolidColorOnly = to.loadSolidColorOnly;
316
- if ('sampleAllLayers' in to)
317
- b.toolOptions.sampleAllLayers = to.sampleAllLayers;
318
- if ('SmdF' in to)
319
- b.toolOptions.smudgeFingerPainting = to.SmdF;
320
- if ('SmdS' in to)
321
- b.toolOptions.smudgeSampleAllLayers = to.SmdS;
322
- if ('Prs ' in to)
323
- b.toolOptions.strength = to['Prs '];
324
- }
325
- brushes.push(b);
326
- }
327
- break;
328
- }
329
- case 'patt': {
330
- while (reader.offset < end) {
331
- patterns.push((0, psdReader_1.readPattern)(reader));
172
+ break;
332
173
  }
333
- reader.offset = end;
334
- break;
335
- }
336
- case 'phry': {
337
- // TODO: what is this ?
338
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
339
- // example:
340
- // hierarchy: [
341
- // {
342
- // 'Nm ': 'PRE_EXPORT ',
343
- // zuid: '965209f2-6f35-9a40-aa43-485684382172'
344
- // },
345
- // {},
346
- // ...
347
- // ]
348
- if (options.logMissingFeatures) {
349
- if ((_d = desc.hierarchy) === null || _d === void 0 ? void 0 : _d.length) {
350
- // console.log('unhandled phry section', desc);
351
- }
174
+ case 'phry': { // brush groups (folders in brushes panel)
175
+ // flat list of group starts, presets (in the same order as in 'desc' section) and group ends
176
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
177
+ hierarchy = desc.hierarchy;
178
+ break;
352
179
  }
353
- break;
180
+ default:
181
+ // 'lPdc' (tool presets) and other unknown sections
182
+ options.logMissingFeatures && reader.log(`Unhandled brush section: ${type}`);
183
+ break;
354
184
  }
355
- default:
356
- throw new Error(`Invalid brush type: ${type}`);
185
+ }
186
+ catch (e) {
187
+ skip(e, `brush section ${type}`);
357
188
  }
358
189
  // skip any unread data in the section and align to 4 bytes
359
190
  reader.offset = end;
@@ -364,8 +195,304 @@ function readAbr(buffer, options = {}) {
364
195
  else {
365
196
  throw new Error(`Unsupported ABR version (${version})`);
366
197
  }
198
+ if (hierarchy) {
199
+ const groups = [];
200
+ let presetIndex = 0;
201
+ for (const item of hierarchy) {
202
+ if (item._classID === 'Grup') {
203
+ groups.push(item['Nm ']);
204
+ }
205
+ else if (item._classID === 'groupEnd') {
206
+ groups.pop();
207
+ }
208
+ else if (item._classID === 'preset') {
209
+ const brush = brushByPreset[presetIndex];
210
+ if (brush && groups.length)
211
+ brush.group = groups.slice();
212
+ presetIndex++;
213
+ }
214
+ }
215
+ }
367
216
  return { samples, patterns, brushes };
368
217
  }
218
+ function readSample(reader, brushEnd, minorVersion) {
219
+ const id = (0, psdReader_1.readPascalString)(reader, 1);
220
+ // v1 - Skip the Int16 bounds rectangle and the unknown Int16.
221
+ // v2 - Skip the unknown bytes.
222
+ (0, psdReader_1.skipBytes)(reader, minorVersion === 1 ? 10 : 264);
223
+ return readSampleBitmap(reader, brushEnd, id);
224
+ }
225
+ // bounds, bit depth, compression and image data of brush sample
226
+ function readSampleBitmap(reader, brushEnd, id) {
227
+ const y = (0, psdReader_1.readInt32)(reader);
228
+ const x = (0, psdReader_1.readInt32)(reader);
229
+ const h = (0, psdReader_1.readInt32)(reader) - y;
230
+ const w = (0, psdReader_1.readInt32)(reader) - x;
231
+ if (w <= 0 || h <= 0)
232
+ throw new Error('Invalid bounds');
233
+ if (w > MAX_BRUSH_SIZE || h > MAX_BRUSH_SIZE) {
234
+ throw new Error(`Brush sample too large (${w}x${h})`);
235
+ }
236
+ const bithDepth = (0, psdReader_1.readInt16)(reader);
237
+ const compression = (0, psdReader_1.readUint8)(reader); // 0 - raw, 1 - RLE
238
+ if (bithDepth !== 8 && bithDepth !== 16)
239
+ throw new Error('Invalid depth');
240
+ if (compression !== 0 && compression !== 1)
241
+ throw new Error('Invalid compression');
242
+ const rowSize = w * (bithDepth === 16 ? 2 : 1);
243
+ // RLE needs at least 2 bytes for row length and 2 bytes per each 128 byte run
244
+ const minDataSize = compression === 0 ? h * rowSize : h * (2 + Math.ceil(rowSize / 128) * 2);
245
+ if (minDataSize > brushEnd - reader.offset) {
246
+ throw new Error('Brush sample data out of bounds');
247
+ }
248
+ (0, psdReader_1.consumeMemory)(reader, w * h * (compression === 1 && bithDepth === 16 ? 3 : 1));
249
+ const alpha = new Uint8Array(w * h);
250
+ if (bithDepth === 8) {
251
+ if (compression === 0) {
252
+ alpha.set((0, psdReader_1.readBytes)(reader, alpha.byteLength));
253
+ }
254
+ else if (compression === 1) {
255
+ (0, psdReader_1.readDataRLE)(reader, { width: w, height: h, data: alpha }, w, h, bithDepth, 1, [0], false);
256
+ }
257
+ else {
258
+ throw new Error('Invalid compression');
259
+ }
260
+ }
261
+ else if (bithDepth === 16) {
262
+ if (compression === 0) {
263
+ for (let i = 0; i < alpha.byteLength; i++) {
264
+ alpha[i] = (0, psdReader_1.readUint16)(reader) >> 8; // convert to 8bit values
265
+ }
266
+ }
267
+ else if (compression === 1) {
268
+ const data = new Uint16Array(w * h);
269
+ (0, psdReader_1.readDataRLE)(reader, { width: w, height: h, data }, w, h, bithDepth, 1, [0], false);
270
+ for (let i = 0; i < alpha.byteLength; i++) {
271
+ alpha[i] = data[i] >> 8; // convert to 8bit values
272
+ }
273
+ }
274
+ else {
275
+ throw new Error('Invalid compression');
276
+ }
277
+ }
278
+ else {
279
+ throw new Error('Invalid depth');
280
+ }
281
+ return { id, bounds: { x, y, w, h }, alpha };
282
+ }
283
+ // ABR versions 1 and 2 (Photoshop 6 and earlier) contain a list of brushes with brush tips in them
284
+ function readLegacyBrushes(reader, version, samples, brushes, skip) {
285
+ const count = (0, psdReader_1.readUint16)(reader);
286
+ for (let i = 0; i < count; i++) {
287
+ const type = (0, psdReader_1.readUint16)(reader);
288
+ const size = (0, psdReader_1.readUint32)(reader);
289
+ const end = reader.offset + size;
290
+ if (end > reader.view.byteLength) { // truncated file
291
+ skip(new Error('Brush data out of bounds'), `brush ${i + 1}`);
292
+ break;
293
+ }
294
+ try {
295
+ if (type === 1) { // computed brush
296
+ (0, psdReader_1.skipBytes)(reader, 4); // misc
297
+ const spacing = (0, psdReader_1.readUint16)(reader);
298
+ const diameter = (0, psdReader_1.readUint16)(reader);
299
+ const roundness = (0, psdReader_1.readUint16)(reader);
300
+ const angle = (0, psdReader_1.readInt16)(reader);
301
+ const hardness = (0, psdReader_1.readUint16)(reader);
302
+ if (reader.offset > end)
303
+ throw new Error('Brush data out of bounds');
304
+ brushes.push(createLegacyBrush('', {
305
+ type: 'computed',
306
+ size: diameter,
307
+ angle,
308
+ roundness: roundness / 100,
309
+ spacingOn: spacing !== 0,
310
+ spacing: spacing / 100,
311
+ flipX: false,
312
+ flipY: false,
313
+ hardness: hardness / 100,
314
+ }));
315
+ }
316
+ else if (type === 2) { // sampled brush
317
+ (0, psdReader_1.skipBytes)(reader, 4); // misc
318
+ const spacing = (0, psdReader_1.readUint16)(reader);
319
+ const name = version === 2 ? (0, psdReader_1.readUnicodeString)(reader) : '';
320
+ (0, psdReader_1.skipBytes)(reader, 1 + 8); // antialiasing, Int16 bounds rectangle (same as Int32 one)
321
+ // legacy brushes don't have sample ids
322
+ const sample = readSampleBitmap(reader, end, `brush-${i + 1}`);
323
+ samples.push(sample);
324
+ brushes.push(createLegacyBrush(name, {
325
+ type: 'sampled',
326
+ size: Math.max(sample.bounds.w, sample.bounds.h),
327
+ angle: 0,
328
+ roundness: 1,
329
+ spacingOn: spacing !== 0,
330
+ spacing: spacing / 100,
331
+ flipX: false,
332
+ flipY: false,
333
+ name,
334
+ sampledData: sample.id,
335
+ }));
336
+ }
337
+ else {
338
+ throw new Error(`Unknown brush type: ${type}`);
339
+ }
340
+ }
341
+ catch (e) {
342
+ skip(e, `brush ${i + 1}`);
343
+ }
344
+ reader.offset = end;
345
+ }
346
+ }
347
+ function createLegacyBrush(name, shape) {
348
+ return { name, shape, spacing: 1, wetEdges: false, noise: false, buildUp: false, useBrushSize: true };
349
+ }
350
+ function parseBrush(brush) {
351
+ var _a, _b, _c;
352
+ const b = {
353
+ name: brush['Nm '],
354
+ shape: parseBrushShape(brush.Brsh),
355
+ spacing: (0, descriptor_1.parsePercent)(brush.Spcn),
356
+ wetEdges: brush.Wtdg,
357
+ noise: brush.Nose,
358
+ buildUp: !!brush['Rpt '],
359
+ useBrushSize: brush.useBrushSize, // ???
360
+ };
361
+ if (brush.interpretation != null)
362
+ b.interpretation = brush.interpretation;
363
+ if (brush.protectTexture != null)
364
+ b.protectTexture = brush.protectTexture;
365
+ if (brush.useTipDynamics) {
366
+ b.shapeDynamics = {
367
+ tiltScale: (0, descriptor_1.parsePercent)(brush.tiltScale),
368
+ sizeDynamics: parseDynamics(brush.szVr),
369
+ angleDynamics: parseDynamics(brush.angleDynamics),
370
+ roundnessDynamics: parseDynamics(brush.roundnessDynamics),
371
+ flipX: brush.flipX,
372
+ flipY: brush.flipY,
373
+ brushProjection: brush.brushProjection,
374
+ minimumDiameter: (0, descriptor_1.parsePercent)(brush.minimumDiameter),
375
+ minimumRoundness: (0, descriptor_1.parsePercent)(brush.minimumRoundness),
376
+ };
377
+ }
378
+ if (brush.useScatter) {
379
+ b.scatter = {
380
+ count: brush['Cnt '],
381
+ bothAxes: brush.bothAxes,
382
+ countDynamics: parseDynamics(brush.countDynamics),
383
+ scatterDynamics: parseDynamics(brush.scatterDynamics),
384
+ };
385
+ }
386
+ if (brush.useTexture && brush.Txtr) {
387
+ b.texture = {
388
+ id: brush.Txtr.Idnt,
389
+ name: brush.Txtr['Nm '],
390
+ blendMode: descriptor_1.BlnM.decode(brush.textureBlendMode),
391
+ depth: (0, descriptor_1.parsePercent)(brush.textureDepth),
392
+ depthMinimum: (0, descriptor_1.parsePercent)(brush.minimumDepth),
393
+ depthDynamics: parseDynamics(brush.textureDepthDynamics),
394
+ scale: (0, descriptor_1.parsePercent)(brush.textureScale),
395
+ invert: brush.InvT,
396
+ brightness: brush.textureBrightness,
397
+ contrast: brush.textureContrast,
398
+ textureEachTip: !!brush.TxtC,
399
+ };
400
+ }
401
+ const db = brush.dualBrush;
402
+ if (db && db.useDualBrush) {
403
+ b.dualBrush = {
404
+ flip: db.Flip,
405
+ shape: parseBrushShape(db.Brsh),
406
+ blendMode: descriptor_1.BlnM.decode(db.BlnM),
407
+ useScatter: db.useScatter,
408
+ spacing: (0, descriptor_1.parsePercent)(db.Spcn),
409
+ count: db['Cnt '],
410
+ bothAxes: db.bothAxes,
411
+ countDynamics: parseDynamics(db.countDynamics),
412
+ scatterDynamics: parseDynamics(db.scatterDynamics),
413
+ };
414
+ }
415
+ if (brush.useColorDynamics) {
416
+ b.colorDynamics = {
417
+ foregroundBackground: parseDynamics(brush.clVr),
418
+ hue: (0, descriptor_1.parsePercent)(brush['H ']),
419
+ saturation: (0, descriptor_1.parsePercent)(brush.Strt),
420
+ brightness: (0, descriptor_1.parsePercent)(brush.Brgh),
421
+ purity: (0, descriptor_1.parsePercent)(brush.purity),
422
+ perTip: brush.colorDynamicsPerTip,
423
+ };
424
+ }
425
+ if (brush.usePaintDynamics) {
426
+ b.transfer = {
427
+ flowDynamics: parseDynamics(brush.prVr),
428
+ opacityDynamics: parseDynamics(brush.opVr),
429
+ };
430
+ if (brush.wtVr)
431
+ b.transfer.wetnessDynamics = parseDynamics(brush.wtVr);
432
+ if (brush.mxVr)
433
+ b.transfer.mixDynamics = parseDynamics(brush.mxVr);
434
+ }
435
+ if (brush.useBrushPose) {
436
+ b.brushPose = {
437
+ overrideAngle: brush.overridePoseAngle,
438
+ overrideTiltX: brush.overridePoseTiltX,
439
+ overrideTiltY: brush.overridePoseTiltY,
440
+ overridePressure: brush.overridePosePressure,
441
+ pressure: (0, descriptor_1.parsePercent)(brush.brushPosePressure),
442
+ tiltX: brush.brushPoseTiltX,
443
+ tiltY: brush.brushPoseTiltY,
444
+ angle: brush.brushPoseAngle,
445
+ };
446
+ }
447
+ const to = brush.toolOptions;
448
+ if (to) {
449
+ b.toolOptions = {
450
+ type: toBrushType[to._classID] || 'brush',
451
+ brushPreset: to.brushPreset,
452
+ flow: (_a = to.flow) !== null && _a !== void 0 ? _a : 100,
453
+ smooth: (_b = to.Smoo) !== null && _b !== void 0 ? _b : 0,
454
+ mode: descriptor_1.BlnM.decode(to['Md '] || 'BlnM.Nrml'), // sometimes mode is missing
455
+ opacity: (_c = to.Opct) !== null && _c !== void 0 ? _c : 100,
456
+ smoothing: !!to.smoothing,
457
+ smoothingValue: to.smoothingValue || 0,
458
+ smoothingRadiusMode: !!to.smoothingRadiusMode,
459
+ smoothingCatchup: !!to.smoothingCatchup,
460
+ smoothingCatchupAtEnd: !!to.smoothingCatchupAtEnd,
461
+ smoothingZoomCompensation: !!to.smoothingZoomCompensation,
462
+ pressureSmoothing: !!to.pressureSmoothing,
463
+ usePressureOverridesSize: !!to.usePressureOverridesSize,
464
+ usePressureOverridesOpacity: !!to.usePressureOverridesOpacity,
465
+ useLegacy: !!to.useLegacy,
466
+ };
467
+ if (to.prVr)
468
+ b.toolOptions.flowDynamics = parseDynamics(to.prVr);
469
+ if (to.opVr)
470
+ b.toolOptions.opacityDynamics = parseDynamics(to.opVr);
471
+ if (to.szVr)
472
+ b.toolOptions.sizeDynamics = parseDynamics(to.szVr);
473
+ if ('wetness' in to)
474
+ b.toolOptions.wetness = to.wetness;
475
+ if ('dryness' in to)
476
+ b.toolOptions.dryness = to.dryness;
477
+ if ('mix' in to)
478
+ b.toolOptions.mix = to.mix;
479
+ if ('autoFill' in to)
480
+ b.toolOptions.autoFill = to.autoFill;
481
+ if ('autoClean' in to)
482
+ b.toolOptions.autoClean = to.autoClean;
483
+ if ('loadSolidColorOnly' in to)
484
+ b.toolOptions.loadSolidColorOnly = to.loadSolidColorOnly;
485
+ if ('sampleAllLayers' in to)
486
+ b.toolOptions.sampleAllLayers = to.sampleAllLayers;
487
+ if ('SmdF' in to)
488
+ b.toolOptions.smudgeFingerPainting = to.SmdF;
489
+ if ('SmdS' in to)
490
+ b.toolOptions.smudgeSampleAllLayers = to.SmdS;
491
+ if ('Prs ' in to)
492
+ b.toolOptions.strength = to['Prs '];
493
+ }
494
+ return b;
495
+ }
369
496
 
370
497
  },{"./descriptor":4,"./psdReader":13}],2:[function(require,module,exports){
371
498
  "use strict";
@@ -373,6 +500,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
373
500
  exports.booleanOperations = exports.infoHandlersMap = exports.infoHandlers = void 0;
374
501
  exports.readBezierKnot = readBezierKnot;
375
502
  exports.readVectorMask = readVectorMask;
503
+ exports.writeVectorMask = writeVectorMask;
504
+ exports.parseKeyDescriptorList = parseKeyDescriptorList;
505
+ exports.serializeKeyDescriptorList = serializeKeyDescriptorList;
376
506
  exports.hasMultiEffects = hasMultiEffects;
377
507
  const base64_js_1 = require("base64-js");
378
508
  const effectsHelpers_1 = require("./effectsHelpers");
@@ -403,6 +533,7 @@ function readLength64(reader) {
403
533
  return (0, psdReader_1.readUint32)(reader);
404
534
  }
405
535
  addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
536
+ var _a;
406
537
  if ((0, psdReader_1.readInt16)(reader) !== 1)
407
538
  throw new Error(`Invalid TySh version`);
408
539
  const transform = [];
@@ -449,7 +580,8 @@ addHandler('TySh', hasKey('text'), (reader, target, leftBytes) => {
449
580
  // require('fs').writeFileSync('before.txt', require('util').inspect(before, false, 99, false), 'utf8');
450
581
  // require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
451
582
  // console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
452
- target.text = Object.assign(Object.assign({}, target.text), textData);
583
+ // descriptor anti-alias value is more reliable than the one from engine data
584
+ target.text = Object.assign(Object.assign(Object.assign({}, target.text), textData), { antiAlias: (_a = target.text.antiAlias) !== null && _a !== void 0 ? _a : textData.antiAlias });
453
585
  // console.log(require('util').inspect(target.text, false, 99, true));
454
586
  }
455
587
  (0, psdReader_1.skipBytes)(reader, leftBytes());
@@ -579,25 +711,8 @@ function readVectorMask(reader, vectorMask, width, height, size) {
579
711
  }
580
712
  return paths;
581
713
  }
582
- addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
583
- if ((0, psdReader_1.readUint32)(reader) !== 3)
584
- throw new Error('Invalid vmsk version');
585
- target.vectorMask = { paths: [] };
586
- const vectorMask = target.vectorMask;
587
- const flags = (0, psdReader_1.readUint32)(reader);
588
- vectorMask.invert = (flags & 1) !== 0;
589
- vectorMask.notLink = (flags & 2) !== 0;
590
- vectorMask.disable = (flags & 4) !== 0;
591
- readVectorMask(reader, vectorMask, width, height, left());
592
- // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
593
- (0, psdReader_1.skipBytes)(reader, left());
594
- }, (writer, target, { width, height }) => {
595
- const vectorMask = target.vectorMask;
596
- const flags = (vectorMask.invert ? 1 : 0) |
597
- (vectorMask.notLink ? 2 : 0) |
598
- (vectorMask.disable ? 4 : 0);
599
- (0, psdWriter_1.writeUint32)(writer, 3); // version
600
- (0, psdWriter_1.writeUint32)(writer, flags);
714
+ // componentIndices - write index of path component for each subpath (Photoshop writes them for layer vector masks)
715
+ function writeVectorMask(writer, vectorMask, width, height, componentIndices = false) {
601
716
  // initial entry
602
717
  (0, psdWriter_1.writeUint16)(writer, 6);
603
718
  (0, psdWriter_1.writeZeros)(writer, 24);
@@ -614,12 +729,18 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
614
729
  (0, psdWriter_1.writeUint16)(writer, 8);
615
730
  (0, psdWriter_1.writeUint16)(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
616
731
  (0, psdWriter_1.writeZeros)(writer, 22);
732
+ let componentIndex = -1;
617
733
  for (const path of vectorMask.paths) {
734
+ // subpaths without operation are part of the same path component as previous subpath
735
+ if (path.operation || componentIndex < 0)
736
+ componentIndex++;
618
737
  (0, psdWriter_1.writeUint16)(writer, path.open ? 3 : 0);
619
738
  (0, psdWriter_1.writeUint16)(writer, path.knots.length);
620
739
  (0, psdWriter_1.writeUint16)(writer, path.operation ? exports.booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
621
740
  (0, psdWriter_1.writeUint16)(writer, path.fillRule === 'non-zero' ? 2 : 1);
622
- (0, psdWriter_1.writeZeros)(writer, 18); // TODO: these are sometimes non-zero
741
+ (0, psdWriter_1.writeZeros)(writer, 4);
742
+ (0, psdWriter_1.writeUint32)(writer, componentIndices ? componentIndex : 0);
743
+ (0, psdWriter_1.writeZeros)(writer, 10);
623
744
  const linkedKnot = path.open ? 4 : 1;
624
745
  const unlinkedKnot = path.open ? 5 : 2;
625
746
  for (const { linked, points } of path.knots) {
@@ -627,8 +748,29 @@ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height
627
748
  writeBezierKnot(writer, points, width, height);
628
749
  }
629
750
  }
751
+ }
752
+ addHandler('vmsk', hasKey('vectorMask'), (reader, target, left, { width, height }) => {
753
+ if ((0, psdReader_1.readUint32)(reader) !== 3)
754
+ throw new Error('Invalid vmsk version');
755
+ target.vectorMask = { paths: [] };
756
+ const vectorMask = target.vectorMask;
757
+ const flags = (0, psdReader_1.readUint32)(reader);
758
+ vectorMask.invert = (flags & 1) !== 0;
759
+ vectorMask.notLink = (flags & 2) !== 0;
760
+ vectorMask.disable = (flags & 4) !== 0;
761
+ readVectorMask(reader, vectorMask, width, height, left());
762
+ // drawBezierPaths(vectorMask.paths, width, height, 'out.png');
763
+ (0, psdReader_1.skipBytes)(reader, left());
764
+ }, (writer, target, { width, height }) => {
765
+ const vectorMask = target.vectorMask;
766
+ const flags = (vectorMask.invert ? 1 : 0) |
767
+ (vectorMask.notLink ? 2 : 0) |
768
+ (vectorMask.disable ? 4 : 0);
769
+ (0, psdWriter_1.writeUint32)(writer, 3); // version
770
+ (0, psdWriter_1.writeUint32)(writer, flags);
771
+ writeVectorMask(writer, vectorMask, width, height, true);
630
772
  });
631
- // TODO: need to write vmsk if has outline ?
773
+ // written instead of 'vmsk' for layers with vector stroke ('vscg' section)
632
774
  addHandlerAlias('vsms', 'vmsk');
633
775
  // addHandlerAlias('vmsk', 'vsms');
634
776
  addHandler('vowv', // something with vectors?
@@ -637,12 +779,8 @@ hasKey('vowv'), (reader, target) => {
637
779
  }, (writer, target) => {
638
780
  (0, psdWriter_1.writeUint32)(writer, target.vowv);
639
781
  });
640
- addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
641
- if ((0, psdReader_1.readInt32)(reader) !== 1)
642
- throw new Error(`Invalid vogk version`);
643
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
644
- // console.log(require('util').inspect(desc, false, 99, true));
645
- target.vectorOrigination = { keyDescriptorList: [] };
782
+ function parseKeyDescriptorList(desc) {
783
+ const keyDescriptorList = [];
646
784
  for (const i of desc.keyDescriptorList) {
647
785
  const item = {};
648
786
  if (i.keyShapeInvalidated != null)
@@ -681,15 +819,14 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
681
819
  if (trnf) {
682
820
  item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
683
821
  }
684
- target.vectorOrigination.keyDescriptorList.push(item);
822
+ keyDescriptorList.push(item);
685
823
  }
686
- (0, psdReader_1.skipBytes)(reader, left());
687
- }, (writer, target) => {
688
- target;
689
- const orig = target.vectorOrigination;
824
+ return keyDescriptorList;
825
+ }
826
+ function serializeKeyDescriptorList(keyDescriptorList) {
690
827
  const desc = { keyDescriptorList: [] };
691
- for (let i = 0; i < orig.keyDescriptorList.length; i++) {
692
- const item = orig.keyDescriptorList[i];
828
+ for (let i = 0; i < keyDescriptorList.length; i++) {
829
+ const item = keyDescriptorList[i];
693
830
  desc.keyDescriptorList.push({}); // we're adding keyOriginIndex at the end
694
831
  const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
695
832
  if (item.keyOriginType != null)
@@ -740,10 +877,21 @@ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
740
877
  out.keyShapeInvalidated = item.keyShapeInvalidated;
741
878
  out.keyOriginIndex = i;
742
879
  }
880
+ return desc;
881
+ }
882
+ addHandler('vogk', hasKey('vectorOrigination'), (reader, target, left) => {
883
+ if ((0, psdReader_1.readInt32)(reader) !== 1)
884
+ throw new Error(`Invalid vogk version`);
885
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
886
+ // console.log(require('util').inspect(desc, false, 99, true));
887
+ target.vectorOrigination = { keyDescriptorList: parseKeyDescriptorList(desc) };
888
+ (0, psdReader_1.skipBytes)(reader, left());
889
+ }, (writer, target) => {
890
+ const desc = serializeKeyDescriptorList(target.vectorOrigination.keyDescriptorList);
743
891
  (0, psdWriter_1.writeInt32)(writer, 1); // version
744
892
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
745
893
  });
746
- addHandler('lmfx', target => target.effects !== undefined && hasMultiEffects(target.effects), (reader, target, left) => {
894
+ addHandler('lmfx', target => target.effects !== undefined && !isGroupLayer(target) && hasMultiEffects(target.effects), (reader, target, left) => {
747
895
  const version = (0, psdReader_1.readUint32)(reader);
748
896
  if (version !== 0)
749
897
  throw new Error('Invalid lmfx version');
@@ -819,8 +967,12 @@ addHandler('lyid', target => target.id !== undefined || layerNeedsId(target), (r
819
967
  let id;
820
968
  if (target.id !== undefined) {
821
969
  id = target.id;
822
- while (options.layerIds.has(id))
823
- id += 100; // make sure we don't have duplicate layer ids
970
+ if (options.layerIds.has(id)) {
971
+ // make sure we don't have duplicate layer ids (and don't take id explicitly set on another layer)
972
+ const explicitIds = getExplicitLayerIds(psd, options);
973
+ while (options.layerIds.has(id) || explicitIds.has(id))
974
+ id += 100;
975
+ }
824
976
  }
825
977
  else {
826
978
  // generate id for layers that don't have it, if it's referenced by other sections
@@ -893,12 +1045,15 @@ addHandler('lspf', hasKey('protected'), (reader, target) => {
893
1045
  };
894
1046
  if (flags & 0x08)
895
1047
  target.protected.artboards = true;
1048
+ if (flags & 0x80000000)
1049
+ target.protected.all = true;
896
1050
  }, (writer, target) => {
897
1051
  const flags = (target.protected.transparency ? 0x01 : 0) |
898
1052
  (target.protected.composite ? 0x02 : 0) |
899
1053
  (target.protected.position ? 0x04 : 0) |
900
- (target.protected.artboards ? 0x08 : 0);
901
- (0, psdWriter_1.writeUint32)(writer, flags);
1054
+ (target.protected.artboards ? 0x08 : 0) |
1055
+ (target.protected.all ? 0x80000000 : 0);
1056
+ (0, psdWriter_1.writeUint32)(writer, flags >>> 0);
902
1057
  });
903
1058
  addHandler('lclr', hasKey('layerColor'), (reader, target) => {
904
1059
  const color = (0, psdReader_1.readUint16)(reader);
@@ -909,106 +1064,160 @@ addHandler('lclr', hasKey('layerColor'), (reader, target) => {
909
1064
  (0, psdWriter_1.writeUint16)(writer, index === -1 ? 0 : index);
910
1065
  (0, psdWriter_1.writeZeros)(writer, 6);
911
1066
  });
1067
+ function parseGeneratorSettings(settings) {
1068
+ const result = {};
1069
+ if (settings.exportAs) {
1070
+ result.exportAs = {};
1071
+ if (settings.exportAs.exportOption !== undefined)
1072
+ result.exportAs.exportOption = settings.exportAs.exportOption;
1073
+ if (settings.exportAs.scaleSettings !== undefined)
1074
+ result.exportAs.scaleSettings = settings.exportAs.scaleSettings;
1075
+ }
1076
+ if (settings.generator_45_assets)
1077
+ result.assets = settings.generator_45_assets.json;
1078
+ if (settings.layerTime !== undefined)
1079
+ result.layerTime = settings.layerTime;
1080
+ if (settings.crema)
1081
+ result.crema = settings.crema.json;
1082
+ return result;
1083
+ }
1084
+ function serializeGeneratorSettings(generatorSettings) {
1085
+ const settings = {
1086
+ _name: '',
1087
+ _classID: generatorSettings.assets !== undefined ? 'generatorSettings' : 'null',
1088
+ };
1089
+ if (generatorSettings.exportAs) {
1090
+ settings.exportAs = { _name: '', _classID: 'null' };
1091
+ if (generatorSettings.exportAs.exportOption !== undefined)
1092
+ settings.exportAs.exportOption = generatorSettings.exportAs.exportOption;
1093
+ if (generatorSettings.exportAs.scaleSettings !== undefined)
1094
+ settings.exportAs.scaleSettings = generatorSettings.exportAs.scaleSettings;
1095
+ }
1096
+ if (generatorSettings.assets !== undefined)
1097
+ settings.generator_45_assets = { _name: '', _classID: 'generatorSettings', json: generatorSettings.assets };
1098
+ if (generatorSettings.layerTime !== undefined)
1099
+ settings.layerTime = generatorSettings.layerTime;
1100
+ if (generatorSettings.crema !== undefined)
1101
+ settings.crema = { _name: '', _classID: 'null', json: generatorSettings.crema };
1102
+ return { generatorSettings: settings };
1103
+ }
1104
+ // document (Psd object) stores generator settings in 'extn' section instead of 'shmd' section
1105
+ function isDocument(target) {
1106
+ return target.width !== undefined;
1107
+ }
912
1108
  addHandler('shmd', // Metadata setting
913
1109
  // Metadata setting
914
- target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined, (reader, target, left) => {
1110
+ target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined ||
1111
+ (target.generatorSettings !== undefined && !isDocument(target)), (reader, target, left) => {
915
1112
  const count = (0, psdReader_1.readUint32)(reader);
916
1113
  for (let i = 0; i < count; i++) {
917
- (0, psdReader_1.checkSignature)(reader, '8BIM');
1114
+ const signature = (0, psdReader_1.readSignature)(reader);
1115
+ if (signature !== '8BIM' && signature !== '8ELE')
1116
+ throw new Error(`Invalid shmd signature: '${signature}'`); // 8ELE in Photoshop Elements
918
1117
  const key = (0, psdReader_1.readSignature)(reader);
919
1118
  (0, psdReader_1.readUint8)(reader); // copy
920
1119
  (0, psdReader_1.skipBytes)(reader, 3);
921
- (0, psdReader_1.readSection)(reader, 1, left => {
922
- if (key === 'cust') {
923
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
924
- // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
925
- if (desc.layerTime !== undefined)
926
- target.timestamp = desc.layerTime;
927
- }
928
- else if (key === 'mlst') {
929
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
930
- // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
931
- target.animationFrames = [];
932
- for (let i = 0; i < desc.LaSt.length; i++) {
933
- const f = desc.LaSt[i];
934
- const frame = { frames: f.FrLs };
935
- if (f.enab !== undefined)
936
- frame.enable = f.enab;
937
- if (f.Ofst)
938
- frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
939
- if (f.FXRf)
940
- frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
941
- if (f.Lefx)
942
- frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, !!reader.logMissingFeatures);
943
- if (f.blendOptions && f.blendOptions.Opct)
944
- frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
945
- target.animationFrames.push(frame);
1120
+ const end = reader.offset + 4 + reader.view.getUint32(reader.offset);
1121
+ try {
1122
+ (0, psdReader_1.readSection)(reader, 1, left => {
1123
+ if (key === 'cust') {
1124
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
1125
+ // console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
1126
+ if (desc.layerTime !== undefined)
1127
+ target.timestamp = desc.layerTime;
946
1128
  }
947
- }
948
- else if (key === 'mdyn') {
949
- // frame flags
950
- (0, psdReader_1.readUint16)(reader); // unknown
951
- const propagate = (0, psdReader_1.readUint8)(reader);
952
- const flags = (0, psdReader_1.readUint8)(reader);
953
- target.animationFrameFlags = {
954
- propagateFrameOne: !propagate,
955
- unifyLayerPosition: (flags & 1) !== 0,
956
- unifyLayerStyle: (flags & 2) !== 0,
957
- unifyLayerVisibility: (flags & 4) !== 0,
958
- };
959
- }
960
- else if (key === 'tmln') {
961
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
962
- const timeScope = desc.timeScope;
963
- // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
964
- const timeline = {
965
- start: (0, descriptor_1.frac)(timeScope.Strt),
966
- duration: (0, descriptor_1.frac)(timeScope.duration),
967
- inTime: (0, descriptor_1.frac)(timeScope.inTime),
968
- outTime: (0, descriptor_1.frac)(timeScope.outTime),
969
- autoScope: desc.autoScope,
970
- audioLevel: desc.audioLevel,
971
- };
972
- if (desc.trackList) {
973
- timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, !!reader.logMissingFeatures);
1129
+ else if (key === 'mlst') {
1130
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
1131
+ // console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
1132
+ target.animationFrames = [];
1133
+ for (let i = 0; i < desc.LaSt.length; i++) {
1134
+ const f = desc.LaSt[i];
1135
+ const frame = { frames: f.FrLs };
1136
+ if (f.enab !== undefined)
1137
+ frame.enable = f.enab;
1138
+ if (f.Ofst)
1139
+ frame.offset = (0, descriptor_1.horzVrtcToXY)(f.Ofst);
1140
+ if (f.FXRf)
1141
+ frame.referencePoint = (0, descriptor_1.horzVrtcToXY)(f.FXRf);
1142
+ if (f.Lefx)
1143
+ frame.effects = (0, descriptor_1.parseEffects)(f.Lefx, !!reader.logMissingFeatures);
1144
+ if (f.blendOptions && f.blendOptions.Opct)
1145
+ frame.opacity = (0, descriptor_1.parsePercent)(f.blendOptions.Opct);
1146
+ target.animationFrames.push(frame);
1147
+ }
974
1148
  }
975
- target.timeline = timeline;
976
- // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
977
- }
978
- else if (key === 'cmls') {
979
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
980
- // console.log('cmls', require('util').inspect(desc, false, 99, true));
981
- target.comps = {
982
- settings: [],
983
- };
984
- if (desc.origFXRefPoint)
985
- target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
986
- for (const item of desc.layerSettings) {
987
- target.comps.settings.push({ compList: item.compList });
988
- const t = target.comps.settings[target.comps.settings.length - 1];
989
- if ('enab' in item)
990
- t.enabled = item.enab;
991
- if (item.Ofst)
992
- t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
993
- if (item.FXRefPoint)
994
- t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
1149
+ else if (key === 'mdyn') {
1150
+ // frame flags
1151
+ (0, psdReader_1.readUint16)(reader); // unknown
1152
+ const propagate = (0, psdReader_1.readUint8)(reader);
1153
+ const flags = (0, psdReader_1.readUint8)(reader);
1154
+ target.animationFrameFlags = {
1155
+ propagateFrameOne: !propagate,
1156
+ unifyLayerPosition: (flags & 1) !== 0,
1157
+ unifyLayerStyle: (flags & 2) !== 0,
1158
+ unifyLayerVisibility: (flags & 4) !== 0,
1159
+ };
995
1160
  }
996
- }
997
- else if (key === 'extn') {
998
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
999
- // console.log(require('util').inspect(desc, false, 99, true));
1000
- desc; // TODO: save this
1001
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
1002
- }
1003
- else {
1004
- reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
1005
- }
1006
- (0, psdReader_1.skipBytes)(reader, left());
1007
- });
1161
+ else if (key === 'tmln') {
1162
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
1163
+ const timeScope = desc.timeScope;
1164
+ // console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
1165
+ const timeline = {
1166
+ start: (0, descriptor_1.frac)(timeScope.Strt),
1167
+ duration: (0, descriptor_1.frac)(timeScope.duration),
1168
+ inTime: (0, descriptor_1.frac)(timeScope.inTime),
1169
+ outTime: (0, descriptor_1.frac)(timeScope.outTime),
1170
+ autoScope: desc.autoScope,
1171
+ audioLevel: desc.audioLevel,
1172
+ };
1173
+ if (desc.trackList) {
1174
+ timeline.tracks = (0, descriptor_1.parseTrackList)(desc.trackList, !!reader.logMissingFeatures);
1175
+ }
1176
+ target.timeline = timeline;
1177
+ // console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
1178
+ }
1179
+ else if (key === 'cmls') {
1180
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
1181
+ // console.log('cmls', require('util').inspect(desc, false, 99, true));
1182
+ target.comps = {
1183
+ settings: [],
1184
+ };
1185
+ if (desc.origFXRefPoint)
1186
+ target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
1187
+ for (const item of desc.layerSettings) {
1188
+ target.comps.settings.push({ compList: item.compList });
1189
+ const t = target.comps.settings[target.comps.settings.length - 1];
1190
+ if ('enab' in item)
1191
+ t.enabled = item.enab;
1192
+ if (item.Ofst)
1193
+ t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
1194
+ if (item.FXRefPoint)
1195
+ t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
1196
+ }
1197
+ }
1198
+ else if (key === 'extn') {
1199
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
1200
+ if (desc.generatorSettings)
1201
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
1202
+ }
1203
+ else {
1204
+ reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
1205
+ }
1206
+ (0, psdReader_1.skipBytes)(reader, left());
1207
+ });
1208
+ }
1209
+ catch (e) {
1210
+ // failure to read one item shouldn't drop all other items
1211
+ if (reader.throwForMissingFeatures)
1212
+ throw e;
1213
+ reader.logMissingFeatures && reader.log(`Failed to read "shmd" section key ${key}: ${e.message}`);
1214
+ reader.offset = end;
1215
+ }
1008
1216
  }
1009
1217
  (0, psdReader_1.skipBytes)(reader, left());
1010
1218
  }, (writer, target, _, options) => {
1011
1219
  const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
1220
+ const generatorSettings = isDocument(target) ? undefined : target.generatorSettings;
1012
1221
  let count = 0;
1013
1222
  if (animationFrames)
1014
1223
  count++;
@@ -1020,6 +1229,8 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
1020
1229
  count++;
1021
1230
  if (comps)
1022
1231
  count++;
1232
+ if (generatorSettings)
1233
+ count++;
1023
1234
  (0, psdWriter_1.writeUint32)(writer, count);
1024
1235
  if (animationFrames) {
1025
1236
  (0, psdWriter_1.writeSignature)(writer, '8BIM');
@@ -1042,9 +1253,9 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
1042
1253
  if (f.referencePoint)
1043
1254
  frame.FXRf = (0, descriptor_1.xyToHorzVrtc)(f.referencePoint);
1044
1255
  if (f.effects)
1045
- frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, false, false);
1256
+ frame.Lefx = (0, descriptor_1.serializeEffects)(f.effects, false, hasMultiEffects(f.effects));
1046
1257
  if (f.opacity !== undefined)
1047
- frame.blendOptions = { Opct: (0, descriptor_1.unitsPercent)(f.opacity) };
1258
+ frame.blendOptions = { Opct: (0, descriptor_1.unitsPercentF)(f.opacity) };
1048
1259
  desc.LaSt.push(frame);
1049
1260
  }
1050
1261
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
@@ -1128,6 +1339,15 @@ target => target.timestamp !== undefined || target.animationFrames !== undefined
1128
1339
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
1129
1340
  }, true);
1130
1341
  }
1342
+ if (generatorSettings) {
1343
+ (0, psdWriter_1.writeSignature)(writer, '8BIM');
1344
+ (0, psdWriter_1.writeSignature)(writer, 'extn');
1345
+ (0, psdWriter_1.writeUint8)(writer, 0); // copy (always false)
1346
+ (0, psdWriter_1.writeZeros)(writer, 3);
1347
+ (0, psdWriter_1.writeSection)(writer, 2, () => {
1348
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(generatorSettings));
1349
+ }, true);
1350
+ }
1131
1351
  });
1132
1352
  addHandler('PxSc', hasKey('pixelSource'), (reader, target) => {
1133
1353
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
@@ -1207,7 +1427,7 @@ addHandler('vstk', hasKey('vectorStroke'), (reader, target, left) => {
1207
1427
  lineAlignment: descriptor_1.strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
1208
1428
  scaleLock: desc.strokeStyleScaleLock,
1209
1429
  strokeAdjust: desc.strokeStyleStrokeAdjust,
1210
- lineDashSet: desc.strokeStyleLineDashSet.map(descriptor_1.parseUnits),
1430
+ lineDashSet: (desc.strokeStyleLineDashSet || []).map(descriptor_1.parseUnits),
1211
1431
  blendMode: descriptor_1.BlnM.decode(desc.strokeStyleBlendMode),
1212
1432
  opacity: (0, descriptor_1.parsePercent)(desc.strokeStyleOpacity),
1213
1433
  content: (0, descriptor_1.parseVectorContent)(desc.strokeStyleContent),
@@ -1245,9 +1465,11 @@ hasKey('artboard'), (reader, target, left) => {
1245
1465
  rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
1246
1466
  guideIndices: desc.guideIndeces,
1247
1467
  presetName: desc.artboardPresetName,
1248
- color: (0, descriptor_1.parseColor)(desc['Clr ']),
1249
- backgroundType: desc.artboardBackgroundType,
1250
1468
  };
1469
+ if (desc['Clr '])
1470
+ target.artboard.color = (0, descriptor_1.parseColor)(desc['Clr ']);
1471
+ if (desc.artboardBackgroundType !== undefined)
1472
+ target.artboard.backgroundType = desc.artboardBackgroundType;
1251
1473
  (0, psdReader_1.skipBytes)(reader, left());
1252
1474
  }, (writer, target) => {
1253
1475
  var _a;
@@ -1353,14 +1575,15 @@ function encodeWarp(warp) {
1353
1575
  }
1354
1576
  return desc;
1355
1577
  }
1356
- addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1578
+ addHandler('PlLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
1579
+ (reader, target, left) => {
1357
1580
  if ((0, psdReader_1.readSignature)(reader) !== 'plcL')
1358
1581
  throw new Error(`Invalid PlLd signature`);
1359
1582
  if ((0, psdReader_1.readInt32)(reader) !== 3)
1360
1583
  throw new Error(`Invalid PlLd version`);
1361
1584
  const id = (0, psdReader_1.readPascalString)(reader, 1);
1362
1585
  const pageNumber = (0, psdReader_1.readInt32)(reader);
1363
- const totalPages = (0, psdReader_1.readInt32)(reader); // TODO: check how this works ?
1586
+ const totalPages = (0, psdReader_1.readInt32)(reader); // for multi-page documents (PDF)
1364
1587
  (0, psdReader_1.readInt32)(reader); // anitAliasPolicy 16
1365
1588
  const placedLayerType = (0, psdReader_1.readInt32)(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
1366
1589
  if (!placedLayerTypes[placedLayerType])
@@ -1405,6 +1628,8 @@ addHandler('PlLd', hasKey('placedLayer'), (reader, target, left) => {
1405
1628
  const type = isQuilt ? 'quiltWarp' : 'warp';
1406
1629
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', type, encodeWarp(warp), type);
1407
1630
  });
1631
+ const levelsFilterKeys = ['_name', '_classID', 'presetKind', 'Adjs'];
1632
+ const levelsFilterAdjustmentKeys = ['_name', '_classID', 'Chnl', 'Inpt', 'Otpt', 'Gmm '];
1408
1633
  function uint8ToFloat32(array) {
1409
1634
  const length = Math.floor(array.byteLength / 4);
1410
1635
  return new Float32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
@@ -1447,7 +1672,7 @@ function pointToHrznVrtc(point) {
1447
1672
  Vrtc: (0, descriptor_1.unitsValue)(point.y, 'y'),
1448
1673
  };
1449
1674
  }
1450
- function parseFilterFXItem(f, options) {
1675
+ function parseFilterFXItem(f, reader) {
1451
1676
  const base = {
1452
1677
  name: f['Nm '],
1453
1678
  opacity: (0, descriptor_1.parsePercent)(f.blendOptions.Opct),
@@ -1495,7 +1720,7 @@ function parseFilterFXItem(f, options) {
1495
1720
  undefinedAreas: descriptor_1.UndA.decode(f.Fltr.UndA),
1496
1721
  displacementFile: {
1497
1722
  signature: f.Fltr.DspF.sig,
1498
- path: f.Fltr.DspF.path, // TODO: this is decoded incorrectly ???
1723
+ path: f.Fltr.DspF.path,
1499
1724
  },
1500
1725
  } });
1501
1726
  case 'Pnch': return Object.assign(Object.assign({}, base), { type: 'pinch', filter: {
@@ -1682,8 +1907,7 @@ function parseFilterFXItem(f, options) {
1682
1907
  ],
1683
1908
  puppetShapeList: (f.Fltr.puppetShapeList || []).map(p => ({
1684
1909
  rigidType: p.rigidType,
1685
- // TODO: VrsM
1686
- // TODO: VrsN
1910
+ // VrsM / VrsN - version (always 1.0)
1687
1911
  originalVertexArray: uint8ToPoints(p.originalVertexArray),
1688
1912
  deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
1689
1913
  indexArray: Array.from(uint8ToUint32(p.indexArray)),
@@ -1780,7 +2004,7 @@ function parseFilterFXItem(f, options) {
1780
2004
  ;
1781
2005
  case 'Crvs':
1782
2006
  {
1783
- return Object.assign(Object.assign({}, base), { type: 'curves', filter: Object.assign({ presetKind: descriptor_1.presetKindType.decode(f.Fltr.presetKind) }, (f.Fltr.Adjs ? {
2007
+ 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 ? {
1784
2008
  adjustments: f.Fltr.Adjs.map(a => {
1785
2009
  const channels = a.Chnl.map(descriptor_1.Chnl.decode);
1786
2010
  if (a['Crv ']) {
@@ -1804,6 +2028,35 @@ function parseFilterFXItem(f, options) {
1804
2028
  } : {})) });
1805
2029
  }
1806
2030
  ;
2031
+ case 'Lvls':
2032
+ {
2033
+ const adjustments = f.Fltr.Adjs;
2034
+ const isKnown = (obj, keys) => Object.keys(obj).every(k => keys.indexOf(k) !== -1);
2035
+ // skip filter with settings we don't know about
2036
+ if (!isKnown(f.Fltr, levelsFilterKeys) || (adjustments && !adjustments.every(a => isKnown(a, levelsFilterAdjustmentKeys)))) {
2037
+ if (reader.throwForMissingFeatures)
2038
+ throw new Error('Unknown levels filter settings');
2039
+ reader.logMissingFeatures && reader.log(`Skipped levels smart filter with unknown settings`);
2040
+ return undefined;
2041
+ }
2042
+ 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 ? {
2043
+ adjustments: adjustments.map(a => {
2044
+ const result = { channels: a.Chnl.map(descriptor_1.Chnl.decode) };
2045
+ if (a.Inpt) {
2046
+ result.shadowInput = a.Inpt[0];
2047
+ result.highlightInput = a.Inpt[1];
2048
+ }
2049
+ if (a.Otpt) {
2050
+ result.shadowOutput = a.Otpt[0];
2051
+ result.highlightOutput = a.Otpt[1];
2052
+ }
2053
+ if (a['Gmm '] !== undefined)
2054
+ result.midtoneInput = a['Gmm '];
2055
+ return result;
2056
+ }),
2057
+ } : {})) });
2058
+ }
2059
+ ;
1807
2060
  case 'BrgC':
1808
2061
  {
1809
2062
  return Object.assign(Object.assign({}, base), { type: 'brightness/contrast', filter: {
@@ -1814,10 +2067,11 @@ function parseFilterFXItem(f, options) {
1814
2067
  }
1815
2068
  ;
1816
2069
  default:
1817
- if (options.throwForMissingFeatures) {
2070
+ if (reader.throwForMissingFeatures) {
1818
2071
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1819
2072
  throw new Error(`Unknown filter classId: ${f.Fltr._classID}`);
1820
2073
  }
2074
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.Fltr._classID} (${f['Nm ']})`);
1821
2075
  return undefined;
1822
2076
  }
1823
2077
  }
@@ -1837,21 +2091,23 @@ function parseFilterFXItem(f, options) {
1837
2091
  case 1314149187: return Object.assign(Object.assign({}, base), { type: 'ntsc colors' });
1838
2092
  case 1231976050: return Object.assign(Object.assign({}, base), { type: 'invert' });
1839
2093
  default:
1840
- if (options.throwForMissingFeatures) {
2094
+ if (reader.throwForMissingFeatures) {
1841
2095
  // console.log('FILTER', require('util').inspect(f, false, 99, true));
1842
2096
  throw new Error(`Unknown filterID: ${f.filterID}`);
1843
2097
  }
2098
+ reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${f.filterID} (${f['Nm ']})`);
2099
+ return undefined;
1844
2100
  }
1845
2101
  }
1846
2102
  }
1847
- function parseFilterFX(desc, options) {
2103
+ function parseFilterFX(desc, reader) {
1848
2104
  return {
1849
2105
  enabled: desc.enab,
1850
2106
  validAtPosition: desc.validAtPosition,
1851
2107
  maskEnabled: desc.filterMaskEnable,
1852
2108
  maskLinked: desc.filterMaskLinked,
1853
2109
  maskExtendWithWhite: desc.filterMaskExtendWithWhite,
1854
- list: desc.filterFXList.map(x => parseFilterFXItem(x, options)).filter((x) => !!x),
2110
+ list: desc.filterFXList.map(x => parseFilterFXItem(x, reader)).filter((x) => !!x),
1855
2111
  };
1856
2112
  }
1857
2113
  function uvRadius(t) {
@@ -2207,14 +2463,14 @@ function serializeFilterFXItem(f) {
2207
2463
  case 'puppet': return Object.assign(Object.assign({}, base), { Fltr: {
2208
2464
  _name: 'Rigid Transform',
2209
2465
  _classID: 'rigidTransform',
2210
- 'null': ['Ordn.Trgt'], // TODO: ???
2466
+ 'null': ['Ordn.Trgt'], // target layer reference
2211
2467
  rigidType: f.filter.rigidType,
2212
2468
  puppetShapeList: (f.filter.puppetShapeList || []).map(p => ({
2213
2469
  _name: '',
2214
2470
  _classID: 'puppetShape',
2215
2471
  rigidType: p.rigidType,
2216
- VrsM: 1, // TODO: ???
2217
- VrsN: 0, // TODO: ???
2472
+ VrsM: 1, // version major
2473
+ VrsN: 0, // version minor
2218
2474
  originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
2219
2475
  deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
2220
2476
  indexArray: toUint8(new Uint32Array(p.indexArray)),
@@ -2296,7 +2552,7 @@ function serializeFilterFXItem(f) {
2296
2552
  warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
2297
2553
  quads: f.filter.quads.map(indices => ({ _name: '', _classID: 'quadIndices', indices })),
2298
2554
  }, filterID: 442 });
2299
- 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 ? {
2555
+ 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 ? {
2300
2556
  Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
2301
2557
  _name: '',
2302
2558
  _classID: 'CrvA',
@@ -2309,6 +2565,21 @@ function serializeFilterFXItem(f) {
2309
2565
  Mpng: a.values,
2310
2566
  })
2311
2567
  } : {})), filterID: 1131574899 });
2568
+ 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 ? {
2569
+ Adjs: f.filter.adjustments.map(a => {
2570
+ var _a, _b, _c, _d;
2571
+ const adjustment = { _name: '', _classID: 'LvlA', Chnl: a.channels.map(descriptor_1.Chnl.encode) };
2572
+ if (a.shadowInput !== undefined || a.highlightInput !== undefined) {
2573
+ adjustment.Inpt = [Math.round((_a = a.shadowInput) !== null && _a !== void 0 ? _a : 0), Math.round((_b = a.highlightInput) !== null && _b !== void 0 ? _b : 255)];
2574
+ }
2575
+ if (a.shadowOutput !== undefined || a.highlightOutput !== undefined) {
2576
+ adjustment.Otpt = [Math.round((_c = a.shadowOutput) !== null && _c !== void 0 ? _c : 0), Math.round((_d = a.highlightOutput) !== null && _d !== void 0 ? _d : 255)];
2577
+ }
2578
+ if (a.midtoneInput !== undefined)
2579
+ adjustment['Gmm '] = a.midtoneInput;
2580
+ return adjustment;
2581
+ }),
2582
+ } : {})), filterID: 1282829427 });
2312
2583
  case 'brightness/contrast': return Object.assign(Object.assign({}, base), { Fltr: {
2313
2584
  _name: 'Brightness/Contrast',
2314
2585
  _classID: 'BrgC',
@@ -2359,7 +2630,8 @@ function getWarpFromPlacedLayer(placed) {
2359
2630
  },
2360
2631
  };
2361
2632
  }
2362
- addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2633
+ addHandler('SoLd', target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
2634
+ (reader, target, left) => {
2363
2635
  if ((0, psdReader_1.readSignature)(reader) !== 'soLD')
2364
2636
  throw new Error(`Invalid SoLd type`);
2365
2637
  const version = (0, psdReader_1.readInt32)(reader);
@@ -2416,13 +2688,18 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2416
2688
  const placedType = placedLayerTypes.indexOf(placed.type);
2417
2689
  if (placedType === -1)
2418
2690
  throw new Error('Invalid placedLayer type');
2691
+ const warp = getWarpFromPlacedLayer(placed);
2692
+ // size of linked image matches warp bounds if it's not provided
2693
+ const bounds = warp.bounds;
2694
+ const width = placed.width || (bounds ? bounds.right.value - bounds.left.value : 0);
2695
+ const height = placed.height || (bounds ? bounds.bottom.value - bounds.top.value : 0);
2419
2696
  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,
2420
2697
  // quiltWarp: {} as any,
2421
- warp: encodeWarp(getWarpFromPlacedLayer(placed)), 'Sz ': {
2698
+ warp: encodeWarp(warp), 'Sz ': {
2422
2699
  _name: '',
2423
2700
  _classID: 'Pnt ',
2424
- Wdth: placed.width || 0, // TODO: find size ?
2425
- Hght: placed.height || 0, // TODO: find size ?
2701
+ Wdth: width,
2702
+ Hght: height,
2426
2703
  }, Rslt: placed.resolution ? (0, descriptor_1.unitsValue)(placed.resolution, 'resolution') : { units: 'Density', value: 72 } });
2427
2704
  if (placed.filter) {
2428
2705
  desc.filterFX = {
@@ -2436,14 +2713,6 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2436
2713
  filterFXList: placed.filter.list.map(f => serializeFilterFXItem(f)),
2437
2714
  };
2438
2715
  }
2439
- // TODO:
2440
- // desc.comp = -1;
2441
- // desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
2442
- // desc.ClMg = {
2443
- // _name: '',
2444
- // _classID: 'ClMg',
2445
- // placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
2446
- // } as any;
2447
2716
  // if (JSON.stringify(t) !== JSON.stringify(desc)) {
2448
2717
  // console.log('read', require('util').inspect(t, false, 99, true));
2449
2718
  // console.log('write', require('util').inspect(desc, false, 99, true));
@@ -2473,7 +2742,13 @@ addHandler('SoLd', hasKey('placedLayer'), (reader, target, left) => {
2473
2742
  desc.compInfo = placed.compInfo;
2474
2743
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, desc.quiltWarp ? 'quiltWarp' : 'warp');
2475
2744
  });
2476
- addHandlerAlias('SoLE', 'SoLd');
2745
+ const soLdHandler = exports.infoHandlersMap['SoLd'];
2746
+ addHandler('SoLE', // linked smart object (Photoshop doesn't write PlLd for these)
2747
+ // linked smart object (Photoshop doesn't write PlLd for these)
2748
+ target => { var _a; return !!((_a = target.placedLayer) === null || _a === void 0 ? void 0 : _a.external); }, (reader, target, left, psd, imageResources) => {
2749
+ soLdHandler.read(reader, target, left, psd, imageResources);
2750
+ target.placedLayer.external = true;
2751
+ }, soLdHandler.write);
2477
2752
  addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2478
2753
  target.referencePoint = {
2479
2754
  x: (0, psdReader_1.readFloat64)(reader),
@@ -2483,14 +2758,37 @@ addHandler('fxrp', hasKey('referencePoint'), (reader, target) => {
2483
2758
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.x);
2484
2759
  (0, psdWriter_1.writeFloat64)(writer, target.referencePoint.y);
2485
2760
  });
2486
- addHandler('Lr16', () => false, (reader, _target, _left, psd, imageResources) => {
2487
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2488
- }, (_writer, _target) => {
2489
- });
2490
- addHandler('Lr32', () => false, (reader, _target, _left, psd, imageResources) => {
2491
- (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2492
- }, (_writer, _target) => {
2493
- });
2761
+ function createGlobalLayerInfoHandler(key, bitsPerChannel) {
2762
+ addHandler(key, () => false, (reader, _target, left, psd, imageResources) => {
2763
+ var _a;
2764
+ // Photoshop writes empty (or 8bit compatibility) layer info section alongside Lr16/Lr32 in 16/32bit documents,
2765
+ // layers from global info with matching bit depth replace layers from that section, other ones are only used
2766
+ // if the section is empty
2767
+ if (!reader.layersInGlobalInfo && (psd.bitsPerChannel === bitsPerChannel || !((_a = psd.children) === null || _a === void 0 ? void 0 : _a.length))) {
2768
+ const sectionLayers = psd.children;
2769
+ psd.children = [];
2770
+ try {
2771
+ (0, psdReader_1.readLayerInfo)(reader, psd, imageResources);
2772
+ }
2773
+ catch (e) {
2774
+ psd.children = sectionLayers; // keep layers from layer info section if global info is broken
2775
+ throw e;
2776
+ }
2777
+ reader.layersInGlobalInfo = true;
2778
+ }
2779
+ (0, psdReader_1.skipBytes)(reader, left());
2780
+ }, () => { });
2781
+ }
2782
+ createGlobalLayerInfoHandler('Lr16', 16);
2783
+ createGlobalLayerInfoHandler('Lr32', 32);
2784
+ createGlobalLayerInfoHandler('Layr', undefined);
2785
+ // merged transparency markers, first extra channel of composite image is transparency
2786
+ for (const key of ['Mtrn', 'Mt16', 'Mt32']) {
2787
+ addHandler(key, () => false, (reader, _target, left) => {
2788
+ reader.globalAlpha = true;
2789
+ (0, psdReader_1.skipBytes)(reader, left());
2790
+ }, () => { });
2791
+ }
2494
2792
  addHandler('LMsk', hasKey('userMask'), (reader, target) => {
2495
2793
  target.userMask = {
2496
2794
  colorSpace: (0, psdReader_1.readColor)(reader),
@@ -2514,11 +2812,13 @@ if (helpers_1.MOCK_HANDLERS) {
2514
2812
  }, (writer, target) => false && (0, psdWriter_1.writeBytes)(writer, target._Patt));
2515
2813
  }
2516
2814
  else {
2517
- addHandler('Patt', // TODO: handle also Pat2 & Pat3
2518
- // TODO: handle also Pat2 & Pat3
2815
+ addHandler('Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2816
+ // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
2519
2817
  target => !!(target.patterns && target.patterns.length > 0), (reader, target, left) => {
2520
2818
  while (left() > 0) {
2521
- const pattern = (0, psdReader_1.readPattern)(reader);
2819
+ const pattern = (0, psdReader_1.readPatternSafe)(reader);
2820
+ if (!pattern)
2821
+ continue;
2522
2822
  if (target.patterns === undefined)
2523
2823
  target.patterns = [];
2524
2824
  target.patterns.push(pattern);
@@ -2706,6 +3006,10 @@ addHandler('Anno', target => target.annotations !== undefined, (reader, target,
2706
3006
  writer.view.setUint32(dataOffset - 4, writer.offset - dataOffset, false);
2707
3007
  }
2708
3008
  });
3009
+ function readFileTypeSignature(reader) {
3010
+ const value = (0, psdReader_1.readSignature)(reader);
3011
+ return /^[\0 ]*$/.test(value) ? '' : value.trim(); // empty value is stored as spaces or zeros
3012
+ }
2709
3013
  function createLnkHandler(tag) {
2710
3014
  addHandler(tag, (target) => {
2711
3015
  const psd = target;
@@ -2727,8 +3031,8 @@ function createLnkHandler(tag) {
2727
3031
  const version = (0, psdReader_1.readInt32)(reader);
2728
3032
  const id = (0, psdReader_1.readPascalString)(reader, 1);
2729
3033
  const name = (0, psdReader_1.readUnicodeString)(reader);
2730
- const fileType = (0, psdReader_1.readSignature)(reader).trim(); // ' ' if empty
2731
- const fileCreator = (0, psdReader_1.readSignature)(reader).trim(); // ' ' or '\0\0\0\0' if empty
3034
+ const fileType = readFileTypeSignature(reader); // ' ' if empty
3035
+ const fileCreator = readFileTypeSignature(reader); // ' ' or '\0\0\0\0' if empty
2732
3036
  const dataSize = readLength64(reader);
2733
3037
  const hasFileOpenDescriptor = (0, psdReader_1.readUint8)(reader);
2734
3038
  const fileOpenDescriptor = hasFileOpenDescriptor ? (0, descriptor_1.readVersionAndDescriptor)(reader) : undefined;
@@ -2879,28 +3183,40 @@ createLnkHandler('lnk2');
2879
3183
  createLnkHandler('lnkE');
2880
3184
  addHandlerAlias('lnkD', 'lnk2');
2881
3185
  addHandlerAlias('lnk3', 'lnk2');
2882
- addHandler('pths', hasKey('pathList'), (reader, target) => {
3186
+ // unicode names of saved paths (path data is stored in image resources 2000-2998)
3187
+ addHandler('pths', target => { var _a; return ((_a = target.imageResources) === null || _a === void 0 ? void 0 : _a.savedPaths) !== undefined; }, (reader, _target, left, psd) => {
3188
+ var _a, _b;
2883
3189
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
2884
- // console.log(require('util').inspect(desc, false, 99, true));
2885
- // if (options.throwForMissingFeatures && desc?.pathList?.length) throw new Error('non-empty pathList in `pths`');
2886
- desc;
2887
- target.pathList = []; // TODO: read paths
2888
- }, (writer, _target) => {
3190
+ const imageResources = (_a = psd.imageResources) !== null && _a !== void 0 ? _a : (psd.imageResources = {});
3191
+ const savedPaths = (_b = imageResources.savedPaths) !== null && _b !== void 0 ? _b : (imageResources.savedPaths = []);
3192
+ for (let i = 0; i < desc.pathList.length && i < savedPaths.length; i++) {
3193
+ const { pathUnicodeName, pathSymmetryClass } = desc.pathList[i];
3194
+ const path = savedPaths[i];
3195
+ // clipping path is referenced by (non-unicode) name
3196
+ if (imageResources.clippingPath && imageResources.clippingPath.name === path.name) {
3197
+ imageResources.clippingPath.name = pathUnicodeName;
3198
+ }
3199
+ path.name = pathUnicodeName;
3200
+ if (pathSymmetryClass)
3201
+ path.symmetryMode = pathSymmetryClass.pathSymmetryMode.split('.')[1];
3202
+ }
3203
+ (0, psdReader_1.skipBytes)(reader, left());
3204
+ }, (writer, _target, psd) => {
2889
3205
  const desc = {
2890
- pathList: [], // TODO: write paths
3206
+ pathList: psd.imageResources.savedPaths.map(path => (Object.assign({ _name: '', _classID: 'pathInfoClass', pathUnicodeName: path.name }, (path.symmetryMode ? {
3207
+ pathSymmetryClass: {
3208
+ _name: '',
3209
+ _classID: 'pathSymmetryClass',
3210
+ pathSymmetryMode: `pathSymmetryModeEnum.${path.symmetryMode}`,
3211
+ },
3212
+ } : {})))),
2891
3213
  };
2892
3214
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'pathsDataClass', desc);
2893
3215
  });
2894
3216
  addHandler('lyvr', hasKey('version'), (reader, target) => target.version = (0, psdReader_1.readUint32)(reader), (writer, target) => (0, psdWriter_1.writeUint32)(writer, target.version));
2895
- addHandler('lfxs', () => false, // TODO: not sure when we actually need to write this section
2896
- // NOTE: this might be insufficient
2897
- // target => target.effects !== undefined && (
2898
- // !!target.effects.dropShadow?.some(e => e.choke) ||
2899
- // !!target.effects.innerShadow?.some(e => e.choke) ||
2900
- // !!target.effects.outerGlow?.choke ||
2901
- // !!target.effects.innerGlow?.choke
2902
- // ),
2903
- (reader, target, left) => {
3217
+ addHandler('lfxs', // effects of group layers (instead of 'lfx2' / 'lmfx')
3218
+ // effects of group layers (instead of 'lfx2' / 'lmfx')
3219
+ target => target.effects !== undefined && isGroupLayer(target), (reader, target, left) => {
2904
3220
  const version = (0, psdReader_1.readUint32)(reader);
2905
3221
  if (version !== 0)
2906
3222
  throw new Error(`Invalid lfxs version`);
@@ -2944,6 +3260,10 @@ function readLevelsChannel(reader) {
2944
3260
  const midtoneInput = (0, psdReader_1.readInt16)(reader) / 100;
2945
3261
  return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
2946
3262
  }
3263
+ const defaultLevelsChannel = { shadowInput: 0, highlightInput: 255, shadowOutput: 0, highlightOutput: 255, midtoneInput: 1 };
3264
+ function isDefaultLevelsChannel(c) {
3265
+ return c.shadowInput === 0 && c.highlightInput === 255 && c.shadowOutput === 0 && c.highlightOutput === 255 && c.midtoneInput === 1;
3266
+ }
2947
3267
  function writeLevelsChannel(writer, channel) {
2948
3268
  (0, psdWriter_1.writeInt16)(writer, channel.shadowInput);
2949
3269
  (0, psdWriter_1.writeInt16)(writer, channel.highlightInput);
@@ -2954,48 +3274,36 @@ function writeLevelsChannel(writer, channel) {
2954
3274
  addHandler('levl', adjustmentType('levels'), (reader, target, left) => {
2955
3275
  if ((0, psdReader_1.readUint16)(reader) !== 2)
2956
3276
  throw new Error('Invalid levl version');
2957
- const levels = [];
2958
- for (let i = 0; i < 29 && left(); i++) {
2959
- levels.push(readLevelsChannel(reader));
2960
- }
2961
- const [rgb, red, green, blue] = levels;
2962
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', rgb,
2963
- red,
2964
- green,
2965
- blue });
2966
- if (left()) {
3277
+ const channels = [];
3278
+ for (let i = 0; i < 29 && left() >= 10; i++) {
3279
+ channels.push(readLevelsChannel(reader));
3280
+ }
3281
+ if (left() >= 8) {
2967
3282
  if ((0, psdReader_1.readSignature)(reader) !== 'Lvls')
2968
3283
  throw new Error(`Invalid levl signature`);
2969
3284
  if ((0, psdReader_1.readUint16)(reader) !== 3)
2970
3285
  throw new Error(`Invalid levl version`);
2971
3286
  const totalChannelRecords = (0, psdReader_1.readUint16)(reader);
2972
- for (let i = 0; i < (totalChannelRecords - levels.length) && left(); i++) {
2973
- levels.push(readLevelsChannel(reader));
3287
+ while (channels.length < totalChannelRecords && left() >= 10) {
3288
+ channels.push(readLevelsChannel(reader));
2974
3289
  }
2975
3290
  }
3291
+ // records for all possible channels are stored, skip unused ones
3292
+ while (channels.length && isDefaultLevelsChannel(channels[channels.length - 1]))
3293
+ channels.pop();
3294
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'levels', channels });
2976
3295
  (0, psdReader_1.skipBytes)(reader, left());
2977
3296
  }, (writer, target) => {
2978
- const info = target.adjustment;
2979
- const defaultChannel = {
2980
- shadowInput: 0,
2981
- highlightInput: 255,
2982
- shadowOutput: 0,
2983
- highlightOutput: 255,
2984
- midtoneInput: 1,
2985
- };
3297
+ const channels = target.adjustment.channels || [];
2986
3298
  (0, psdWriter_1.writeUint16)(writer, 2); // version
2987
- writeLevelsChannel(writer, info.rgb || defaultChannel);
2988
- writeLevelsChannel(writer, info.red || defaultChannel);
2989
- writeLevelsChannel(writer, info.green || defaultChannel);
2990
- writeLevelsChannel(writer, info.blue || defaultChannel);
2991
- for (let i = 0; i < (29 - 4); i++) {
2992
- writeLevelsChannel(writer, defaultChannel);
3299
+ for (let i = 0; i < 29; i++) {
3300
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2993
3301
  }
2994
3302
  (0, psdWriter_1.writeSignature)(writer, 'Lvls');
2995
3303
  (0, psdWriter_1.writeUint16)(writer, 3); // version
2996
3304
  (0, psdWriter_1.writeUint16)(writer, 62); // total count
2997
- for (let i = 0; i < (62 - 29); i++) {
2998
- writeLevelsChannel(writer, defaultChannel);
3305
+ for (let i = 29; i < 62; i++) {
3306
+ writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
2999
3307
  }
3000
3308
  });
3001
3309
  function readCurveChannel(reader) {
@@ -3020,17 +3328,16 @@ addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
3020
3328
  if ((0, psdReader_1.readUint16)(reader) !== 1)
3021
3329
  throw new Error('Invalid curv version');
3022
3330
  (0, psdReader_1.readUint16)(reader);
3023
- const channels = (0, psdReader_1.readUint16)(reader);
3024
- const info = { type: 'curves' };
3025
- if (channels & 1)
3026
- info.rgb = readCurveChannel(reader);
3027
- if (channels & 2)
3028
- info.red = readCurveChannel(reader);
3029
- if (channels & 4)
3030
- info.green = readCurveChannel(reader);
3031
- if (channels & 8)
3032
- info.blue = readCurveChannel(reader);
3033
- target.adjustment = Object.assign(Object.assign({}, target.adjustment), info);
3331
+ const channelsMask = (0, psdReader_1.readUint16)(reader);
3332
+ const channels = [];
3333
+ for (let i = 0; i < 16; i++) {
3334
+ if (channelsMask & (1 << i)) {
3335
+ while (channels.length < i)
3336
+ channels.push([]);
3337
+ channels.push(readCurveChannel(reader));
3338
+ }
3339
+ }
3340
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'curves', channels });
3034
3341
  // ignoring, duplicate information
3035
3342
  // checkSignature(reader, 'Crv ');
3036
3343
  // const cVersion = readUint16(reader);
@@ -3046,57 +3353,29 @@ addHandler('curv', adjustmentType('curves'), (reader, target, left) => {
3046
3353
  // }
3047
3354
  (0, psdReader_1.skipBytes)(reader, left());
3048
3355
  }, (writer, target) => {
3049
- const info = target.adjustment;
3050
- const { rgb, red, green, blue } = info;
3051
- let channels = 0;
3052
- let channelCount = 0;
3053
- if (rgb && rgb.length) {
3054
- channels |= 1;
3055
- channelCount++;
3056
- }
3057
- if (red && red.length) {
3058
- channels |= 2;
3059
- channelCount++;
3060
- }
3061
- if (green && green.length) {
3062
- channels |= 4;
3063
- channelCount++;
3064
- }
3065
- if (blue && blue.length) {
3066
- channels |= 8;
3067
- channelCount++;
3356
+ var _a;
3357
+ const channels = (target.adjustment.channels || []).slice(0, 16);
3358
+ const used = [];
3359
+ let channelsMask = 0;
3360
+ for (let i = 0; i < channels.length; i++) {
3361
+ if ((_a = channels[i]) === null || _a === void 0 ? void 0 : _a.length) {
3362
+ channelsMask |= 1 << i;
3363
+ used.push(i);
3364
+ }
3068
3365
  }
3069
3366
  (0, psdWriter_1.writeUint8)(writer, 0);
3070
3367
  (0, psdWriter_1.writeUint16)(writer, 1); // version
3071
3368
  (0, psdWriter_1.writeUint16)(writer, 0);
3072
- (0, psdWriter_1.writeUint16)(writer, channels);
3073
- if (rgb && rgb.length)
3074
- writeCurveChannel(writer, rgb);
3075
- if (red && red.length)
3076
- writeCurveChannel(writer, red);
3077
- if (green && green.length)
3078
- writeCurveChannel(writer, green);
3079
- if (blue && blue.length)
3080
- writeCurveChannel(writer, blue);
3369
+ (0, psdWriter_1.writeUint16)(writer, channelsMask);
3370
+ for (const i of used)
3371
+ writeCurveChannel(writer, channels[i]);
3081
3372
  (0, psdWriter_1.writeSignature)(writer, 'Crv ');
3082
3373
  (0, psdWriter_1.writeUint16)(writer, 4); // version
3083
3374
  (0, psdWriter_1.writeUint16)(writer, 0);
3084
- (0, psdWriter_1.writeUint16)(writer, channelCount);
3085
- if (rgb && rgb.length) {
3086
- (0, psdWriter_1.writeUint16)(writer, 0);
3087
- writeCurveChannel(writer, rgb);
3088
- }
3089
- if (red && red.length) {
3090
- (0, psdWriter_1.writeUint16)(writer, 1);
3091
- writeCurveChannel(writer, red);
3092
- }
3093
- if (green && green.length) {
3094
- (0, psdWriter_1.writeUint16)(writer, 2);
3095
- writeCurveChannel(writer, green);
3096
- }
3097
- if (blue && blue.length) {
3098
- (0, psdWriter_1.writeUint16)(writer, 3);
3099
- writeCurveChannel(writer, blue);
3375
+ (0, psdWriter_1.writeUint16)(writer, used.length);
3376
+ for (const i of used) {
3377
+ (0, psdWriter_1.writeUint16)(writer, i);
3378
+ writeCurveChannel(writer, channels[i]);
3100
3379
  }
3101
3380
  });
3102
3381
  addHandler('expA', adjustmentType('exposure'), (reader, target, left) => {
@@ -3153,12 +3432,34 @@ function writeHueChannel(writer, channel) {
3153
3432
  addHandler('hue2', adjustmentType('hue/saturation'), (reader, target, left) => {
3154
3433
  if ((0, psdReader_1.readUint16)(reader) !== 2)
3155
3434
  throw new Error('Invalid hue2 version');
3156
- 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) });
3435
+ const colorize = !!(0, psdReader_1.readUint8)(reader);
3436
+ (0, psdReader_1.skipBytes)(reader, 1);
3437
+ const colorizeHue = (0, psdReader_1.readInt16)(reader); // stored as -180..180
3438
+ const colorizeSaturation = (0, psdReader_1.readInt16)(reader);
3439
+ const colorizeLightness = (0, psdReader_1.readInt16)(reader);
3440
+ target.adjustment = Object.assign(Object.assign({}, target.adjustment), { type: 'hue/saturation', colorize, colorization: {
3441
+ hue: colorizeHue < 0 ? colorizeHue + 360 : colorizeHue,
3442
+ saturation: colorizeSaturation,
3443
+ lightness: colorizeLightness,
3444
+ }, master: {
3445
+ hue: (0, psdReader_1.readInt16)(reader),
3446
+ saturation: (0, psdReader_1.readInt16)(reader),
3447
+ lightness: (0, psdReader_1.readInt16)(reader),
3448
+ }, reds: readHueChannel(reader), yellows: readHueChannel(reader), greens: readHueChannel(reader), cyans: readHueChannel(reader), blues: readHueChannel(reader), magentas: readHueChannel(reader) });
3157
3449
  (0, psdReader_1.skipBytes)(reader, left());
3158
3450
  }, (writer, target) => {
3451
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
3159
3452
  const info = target.adjustment;
3160
3453
  (0, psdWriter_1.writeUint16)(writer, 2); // version
3161
- writeHueChannel(writer, info.master);
3454
+ (0, psdWriter_1.writeUint8)(writer, info.colorize ? 1 : 0);
3455
+ (0, psdWriter_1.writeUint8)(writer, 0);
3456
+ const colorizeHue = Math.round((_b = (_a = info.colorization) === null || _a === void 0 ? void 0 : _a.hue) !== null && _b !== void 0 ? _b : 0);
3457
+ (0, psdWriter_1.writeInt16)(writer, colorizeHue > 180 ? colorizeHue - 360 : colorizeHue);
3458
+ (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));
3459
+ (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));
3460
+ (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));
3461
+ (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));
3462
+ (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));
3162
3463
  writeHueChannel(writer, info.reds);
3163
3464
  writeHueChannel(writer, info.yellows);
3164
3465
  writeHueChannel(writer, info.greens);
@@ -3237,16 +3538,18 @@ addHandler('phfl', adjustmentType('photo filter'), (reader, target, left) => {
3237
3538
  color = (0, psdReader_1.readColor)(reader);
3238
3539
  }
3239
3540
  else { // version 3
3240
- // TODO: test this, this is probably wrong
3241
- // values are stored as L (0..100), a, b (-128..127) multiplied by 100,
3242
- // normalize them to the same scale as readColor() so they can be written back as version 2
3243
- const l = (0, psdReader_1.readInt32)(reader) / 100;
3244
- const a = (0, psdReader_1.readInt32)(reader) / 100;
3245
- const b = (0, psdReader_1.readInt32)(reader) / 100;
3541
+ // XYZ color relative to D50 white point, 32768 = 1.0 (not documented, matches colors of Photoshop presets),
3542
+ // converted to Lab (same scale as readColor()) so it can be written back as version 2
3543
+ const x = (0, psdReader_1.readInt32)(reader) / 32768;
3544
+ const y = (0, psdReader_1.readInt32)(reader) / 32768;
3545
+ const z = (0, psdReader_1.readInt32)(reader) / 32768;
3546
+ if (!(x >= 0 && y >= 0 && z >= 0 && x <= 2 && y <= 2 && z <= 2))
3547
+ throw new Error('Invalid phfl XYZ color');
3548
+ const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
3246
3549
  color = {
3247
- l: l / 100,
3248
- a: (0, helpers_1.normalizeLabAB)(a),
3249
- b: (0, helpers_1.normalizeLabAB)(b),
3550
+ l: Math.max(0, 116 * f(y) - 16) / 100,
3551
+ a: (0, helpers_1.normalizeLabAB)(500 * (f(x) - f(y))),
3552
+ b: (0, helpers_1.normalizeLabAB)(200 * (f(y) - f(z))),
3250
3553
  };
3251
3554
  }
3252
3555
  target.adjustment = {
@@ -3414,7 +3717,7 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3414
3717
  reader.offset--;
3415
3718
  if (hasMethod) {
3416
3719
  const method = (0, psdReader_1.readSignature)(reader);
3417
- info.method = descriptor_1.gradientInterpolationMethodType.decode(method);
3720
+ info.method = descriptor_1.gradientInterpolationMethodType.decode(`gradientInterpolationMethodType.${method}`);
3418
3721
  }
3419
3722
  info.name = (0, psdReader_1.readUnicodeString)(reader);
3420
3723
  info.colorStops = [];
@@ -3476,7 +3779,7 @@ addHandler('grdm', adjustmentType('gradient map'), (reader, target, left) => {
3476
3779
  (0, psdWriter_1.writeUint8)(writer, info.reverse ? 1 : 0);
3477
3780
  (0, psdWriter_1.writeUint8)(writer, info.dither ? 1 : 0);
3478
3781
  if (info.method !== undefined) {
3479
- (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method));
3782
+ (0, psdWriter_1.writeSignature)(writer, descriptor_1.gradientInterpolationMethodType.encode(info.method).split('.')[1]);
3480
3783
  }
3481
3784
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, info.name || '');
3482
3785
  (0, psdWriter_1.writeUint16)(writer, info.colorStops && info.colorStops.length || 0);
@@ -3798,17 +4101,22 @@ addHandler('artd', // document-wide artboard info
3798
4101
  // document-wide artboard info
3799
4102
  target => target.artboards !== undefined, (reader, target, left) => {
3800
4103
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3801
- target.artboards = {
3802
- count: desc['Cnt '],
3803
- autoExpandOffset: { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc },
3804
- origin: { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc },
3805
- autoExpandEnabled: desc.autoExpandEnabled,
3806
- autoNestEnabled: desc.autoNestEnabled,
3807
- autoPositionEnabled: desc.autoPositionEnabled,
3808
- shrinkwrapOnSaveEnabled: !!desc.shrinkwrapOnSaveEnabled,
3809
- docDefaultNewArtboardBackgroundColor: (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor),
3810
- docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
3811
- };
4104
+ const artboards = target.artboards = { count: desc['Cnt '] };
4105
+ if (desc.autoExpandOffset)
4106
+ artboards.autoExpandOffset = { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc };
4107
+ if (desc.origin)
4108
+ artboards.origin = { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc };
4109
+ if (desc.autoExpandEnabled !== undefined)
4110
+ artboards.autoExpandEnabled = desc.autoExpandEnabled;
4111
+ if (desc.autoNestEnabled !== undefined)
4112
+ artboards.autoNestEnabled = desc.autoNestEnabled;
4113
+ if (desc.autoPositionEnabled !== undefined)
4114
+ artboards.autoPositionEnabled = desc.autoPositionEnabled;
4115
+ artboards.shrinkwrapOnSaveEnabled = !!desc.shrinkwrapOnSaveEnabled;
4116
+ if (desc.docDefaultNewArtboardBackgroundColor)
4117
+ artboards.docDefaultNewArtboardBackgroundColor = (0, descriptor_1.parseColor)(desc.docDefaultNewArtboardBackgroundColor);
4118
+ if (desc.docDefaultNewArtboardBackgroundType !== undefined)
4119
+ artboards.docDefaultNewArtboardBackgroundType = desc.docDefaultNewArtboardBackgroundType;
3812
4120
  (0, psdReader_1.skipBytes)(reader, left());
3813
4121
  }, (writer, target) => {
3814
4122
  var _a, _b, _c, _d, _e;
@@ -3826,17 +4134,19 @@ target => target.artboards !== undefined, (reader, target, left) => {
3826
4134
  };
3827
4135
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc, 'artd');
3828
4136
  });
4137
+ function isGroupLayer(target) {
4138
+ return !!target.children;
4139
+ }
3829
4140
  function hasMultiEffects(effects) {
3830
4141
  return Object.keys(effects).map(key => effects[key]).some(v => Array.isArray(v) && v.length > 1);
3831
4142
  }
3832
- addHandler('lfx2', target => target.effects !== undefined && !hasMultiEffects(target.effects), (reader, target, left) => {
4143
+ addHandler('lfx2', target => target.effects !== undefined && !isGroupLayer(target) && !hasMultiEffects(target.effects), (reader, target, left) => {
3833
4144
  const version = (0, psdReader_1.readUint32)(reader);
3834
4145
  if (version !== 0)
3835
4146
  throw new Error(`Invalid lfx2 version`);
3836
4147
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3837
4148
  // console.log('READ', require('util').inspect(desc, false, 99, true));
3838
- // TODO: don't discard if we got it from lmfx
3839
- // discard if read in 'lrFX' section
4149
+ // discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
3840
4150
  target.effects = (0, descriptor_1.parseEffects)(desc, !!reader.logMissingFeatures);
3841
4151
  (0, psdReader_1.skipBytes)(reader, left());
3842
4152
  }, (writer, target, _, options) => {
@@ -3893,16 +4203,14 @@ addHandler('cinf', hasKey('compositorUsed'), (reader, target, left) => {
3893
4203
  desc.compCoreGPUSupport = `reason.${cinf.compCoreGPUSupport}`;
3894
4204
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
3895
4205
  });
3896
- // extension settings ?, ignore it
3897
- addHandler('extn', target => target._extn !== undefined, (reader, target) => {
4206
+ // generator settings of the document (layers store them in 'shmd' section)
4207
+ addHandler('extn', target => target.generatorSettings !== undefined && isDocument(target), (reader, target, left) => {
3898
4208
  const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
3899
- // console.log(require('util').inspect(desc, false, 99, true));
3900
- if (helpers_1.MOCK_HANDLERS)
3901
- target._extn = desc;
4209
+ if (desc.generatorSettings)
4210
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
4211
+ (0, psdReader_1.skipBytes)(reader, left());
3902
4212
  }, (writer, target) => {
3903
- // TODO: need to add correct types for desc fields (resources/src.psd)
3904
- if (helpers_1.MOCK_HANDLERS)
3905
- (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', target._extn);
4213
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', serializeGeneratorSettings(target.generatorSettings));
3906
4214
  });
3907
4215
  addHandler('iOpa', hasKey('fillOpacity'), (reader, target) => {
3908
4216
  target.fillOpacity = (0, psdReader_1.readUint8)(reader) / 0xff;
@@ -3980,14 +4288,13 @@ var __rest = (this && this.__rest) || function (s, e) {
3980
4288
  return t;
3981
4289
  };
3982
4290
  Object.defineProperty(exports, "__esModule", { value: true });
3983
- exports.IntC = exports.IntE = exports.Drct = exports.WndM = exports.CntE = exports.FlCl = exports.ExtR = exports.ExtT = exports.DfsM = exports.blurType = exports.Lns = exports.MztT = exports.Chnl = exports.Dstr = exports.ZZTy = exports.Wvtp = exports.SphM = exports.RplS = exports.Cnvr = exports.UndA = exports.DspM = exports.SmBQ = exports.SmBM = exports.BlrQ = exports.BlrM = exports.strokeStyleLineAlignment = exports.strokeStyleLineJoinType = exports.strokeStyleLineCapType = exports.ESliceBGColorType = exports.ESliceOrigin = exports.ESliceVertAlign = exports.ESliceHorzAlign = exports.ESliceType = exports.FrFl = exports.FStl = exports.ClrS = exports.gradientInterpolationMethodType = exports.stdTrackID = exports.animInterpStyleEnum = exports.GrdT = exports.IGSr = exports.BETE = exports.BESs = exports.bvlT = exports.BESl = exports.BlnM = exports.warpStyle = exports.Annt = exports.Ornt = exports.textGridding = void 0;
3984
- exports.presetKindType = exports.prjM = exports.FlMd = void 0;
4291
+ exports.Drct = exports.WndM = exports.CntE = exports.FlCl = exports.ExtR = exports.ExtT = exports.DfsM = exports.blurType = exports.Lns = exports.MztT = exports.Chnl = exports.Dstr = exports.ZZTy = exports.Wvtp = exports.SphM = exports.RplS = exports.Cnvr = exports.UndA = exports.DspM = exports.SmBQ = exports.SmBM = exports.BlrQ = exports.BlrM = exports.strokeStyleLineAlignment = exports.strokeStyleLineJoinType = exports.strokeStyleLineCapType = exports.ESliceBGColorType = exports.ESliceOrigin = exports.ESliceVertAlign = exports.ESliceHorzAlign = exports.ESliceType = exports.FrFl = exports.FStl = exports.ClrS = exports.Clry = exports.gradientInterpolationMethodType = exports.stdTrackID = exports.animInterpStyleEnum = exports.GrdT = exports.IGSr = exports.BETE = exports.BESs = exports.bvlT = exports.BESl = exports.BlnM = exports.warpStyle = exports.Annt = exports.Ornt = exports.textGridding = exports.outputSettingsRoot = void 0;
4292
+ exports.presetKindType = exports.prjM = exports.FlMd = exports.IntC = exports.IntE = void 0;
3985
4293
  exports.setLogErrors = setLogErrors;
3986
4294
  exports.readAsciiStringOrClassId = readAsciiStringOrClassId;
3987
4295
  exports.writeAsciiStringOrClassId = writeAsciiStringOrClassId;
3988
4296
  exports.readDescriptorStructure = readDescriptorStructure;
3989
4297
  exports.writeDescriptorStructure = writeDescriptorStructure;
3990
- exports.createDescriptorReference = createDescriptorReference;
3991
4298
  exports.pathSelectionStateToReference = pathSelectionStateToReference;
3992
4299
  exports.writeClassStructure = writeClassStructure;
3993
4300
  exports.readVersionAndDescriptor = readVersionAndDescriptor;
@@ -4136,6 +4443,7 @@ const fieldToArrayExtType = {
4136
4443
  Clrs: makeType('', 'Clrt'),
4137
4444
  Trns: makeType('', 'TrnS'),
4138
4445
  keyDescriptorList: nullType,
4446
+ keyRootDescriptorList: nullType,
4139
4447
  solidFillMulti: makeType('', 'SoFi'),
4140
4448
  gradientFillMulti: makeType('', 'GrFl'),
4141
4449
  dropShadowMulti: makeType('', 'DrSh'),
@@ -4169,17 +4477,23 @@ const fieldToArrayExtType = {
4169
4477
  };
4170
4478
  const typeToField = {
4171
4479
  'TEXT': [
4172
- 'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName',
4480
+ 'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName', 'exportOption', 'scaleSettings',
4481
+ 'BackgroundImagePath', 'ImageSubfolderName',
4173
4482
  'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
4174
4483
  'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
4175
- 'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath',
4484
+ 'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath', 'Bk ', 'pathUnicodeName',
4176
4485
  ],
4177
4486
  'tdta': [
4178
4487
  'EngineData', 'LUT3DFileData', 'indexArray', 'originalVertexArray', 'deformedVertexArray',
4179
- 'LqMe',
4488
+ 'LqMe', 'bookKey',
4180
4489
  ],
4181
4490
  'long': [
4182
- 'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ',
4491
+ 'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ', 'bookID',
4492
+ 'cropRectBottom', 'cropRectLeft', 'cropRectRight', 'cropRectTop',
4493
+ // save for web output settings
4494
+ 'Version', 'BackgroundColorBlue', 'BackgroundColorGreen', 'BackgroundColorRed', 'BackgroundColorState', 'AttributeCase', 'Encoding',
4495
+ 'DuplicateFileNameBehavior', 'ImageMapLocation', 'ImageMapType', 'Indent', 'LineEndings', 'SpacersEmptyCells',
4496
+ 'SpacersHorizontal', 'SpacersVertical', 'StylesFormat', 'TDWidthHeight', 'TagCase', 'ColorPolicy', 'MetadataPolicy',
4183
4497
  'Brgh', 'Cntr', 'means', 'vibrance', 'Strt', 'bwPresetKind', 'comp', 'compID', 'originalCompID',
4184
4498
  'curvesPresetKind', 'mixerPresetKind', 'uOrder', 'vOrder', 'PgNm', 'totalPages', 'Crop',
4185
4499
  'numerator', 'denominator', 'frameCount', 'Annt', 'keyOriginType', 'unitValueQuadVersion',
@@ -4201,7 +4515,7 @@ const typeToField = {
4201
4515
  'tableOrder', 'enableCompCore', 'enableCompCoreGPU', 'compCoreSupport', 'compCoreGPUSupport', 'Engn',
4202
4516
  'enableCompCoreThreads', 'gs99', 'FrDs', 'trackID', 'animInterpStyle', 'horzAlign',
4203
4517
  'vertAlign', 'bgColorType', 'shapeOperation', 'UndA', 'Wvtp', 'Drct', 'WndM', 'Edg ', 'FlCl', 'IntE',
4204
- 'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod',
4518
+ 'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod', 'pathSymmetryMode',
4205
4519
  'interpretAlpha',
4206
4520
  ],
4207
4521
  'bool': [
@@ -4218,7 +4532,7 @@ const typeToField = {
4218
4532
  'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
4219
4533
  ],
4220
4534
  'doub': [
4221
- 'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght',
4535
+ 'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght', 'Gmm ',
4222
4536
  'strokeStyleMiterLimit', 'strokeStyleResolution', 'layerTime', 'keyOriginResolution',
4223
4537
  'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
4224
4538
  'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
@@ -4232,7 +4546,7 @@ const typeToField = {
4232
4546
  'topRight', 'topLeft', 'bottomLeft', 'bottomRight', 'ClNs', 'Shrp',
4233
4547
  ],
4234
4548
  'VlLs': [
4235
- 'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
4549
+ 'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'keyRootDescriptorList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
4236
4550
  'LaSt', 'Trnf', 'nonAffineTransform', 'keyDescriptorList', 'guideIndeces', 'gradientFillMulti',
4237
4551
  'solidFillMulti', 'frameFXMulti', 'innerShadowMulti', 'dropShadowMulti', 'FrIn', 'FSts', 'FsFr',
4238
4552
  'sheetTimelineOptions', 'audioClipList', 'trackList', 'globalTrackList', 'keyList', 'audioClipList',
@@ -4251,25 +4565,6 @@ const channels = [
4251
4565
  ];
4252
4566
  // color classes whose channel values are written as doubles ('GRYC' and 'LABC' are kept for backwards compatibility)
4253
4567
  const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
4254
- // Hidden (non-enumerable) markers used to remember how values were stored in the file, so they can be
4255
- // written back the same way. They don't change the shape of the objects returned by the reader.
4256
- const osTypeKey = typeof Symbol !== 'undefined' ? Symbol('agPsdOSType') : '__agPsdOSType';
4257
- const obArInfoKey = typeof Symbol !== 'undefined' ? Symbol('agPsdObAr') : '__agPsdObAr';
4258
- const referenceInfoKey = typeof Symbol !== 'undefined' ? Symbol('agPsdReference') : '__agPsdReference';
4259
- function setHidden(target, key, value) {
4260
- Object.defineProperty(target, key, { value, enumerable: false, configurable: true, writable: true });
4261
- return target;
4262
- }
4263
- function getHidden(target, key) {
4264
- return (target && typeof target === 'object') ? target[key] : undefined;
4265
- }
4266
- const osTypeFamilies = {
4267
- UntF: 'UntF', UnFl: 'UntF',
4268
- Objc: 'Objc', GlbO: 'Objc',
4269
- type: 'type', GlbC: 'type',
4270
- comp: 'comp',
4271
- };
4272
- // returns the OS type the value was originally read with, if it's compatible with the type we'd write it as
4273
4568
  // fallback for unknown keys, based on the type of the value
4274
4569
  function guessOSType(value) {
4275
4570
  if (typeof value === 'boolean')
@@ -4278,26 +4573,33 @@ function guessOSType(value) {
4278
4573
  return 'doub';
4279
4574
  if (Array.isArray(value))
4280
4575
  return 'VlLs';
4281
- if (value && typeof value === 'object' && typeof value._classID === 'string')
4282
- return 'Objc';
4576
+ if (value && typeof value === 'object') {
4577
+ if (typeof value._classID === 'string')
4578
+ return 'Objc';
4579
+ if (typeof value.classID === 'string')
4580
+ return 'type'; // class read from 'type' or 'GlbC'
4581
+ if (typeof value.low === 'number' && typeof value.high === 'number')
4582
+ return 'comp';
4583
+ if (typeof value.units === 'string' && typeof value.value === 'number')
4584
+ return 'UntF';
4585
+ }
4283
4586
  return undefined;
4284
4587
  }
4285
- function getRecordedOSType(value, type) {
4286
- const recorded = getHidden(value, osTypeKey);
4287
- if (!recorded)
4288
- return type;
4289
- if (!type || recorded === 'comp' || recorded === 'type' || recorded === 'GlbC')
4290
- return recorded;
4291
- return osTypeFamilies[recorded] === osTypeFamilies[type] ? recorded : type;
4292
- }
4293
4588
  const fieldToArrayType = {
4294
4589
  'Mnm ': 'long',
4295
4590
  'Mxm ': 'long',
4591
+ HtmlFileNameComponents: 'long', // save for web output settings
4592
+ SavingFileNameComponents: 'long',
4593
+ SliceFileNameComponents: 'long',
4594
+ Inpt: 'long', // levels filter
4595
+ Otpt: 'long', // levels filter
4296
4596
  FrLs: 'long',
4297
4597
  strokeStyleLineDashSet: 'UntF',
4298
4598
  Trnf: 'doub',
4299
4599
  nonAffineTransform: 'doub',
4300
4600
  keyDescriptorList: 'Objc',
4601
+ keyRootDescriptorList: 'Objc',
4602
+ pathList: 'Objc',
4301
4603
  gradientFillMulti: 'Objc',
4302
4604
  solidFillMulti: 'Objc',
4303
4605
  frameFXMulti: 'Objc',
@@ -4388,6 +4690,15 @@ function getTypeByKey(key, value, root, parent) {
4388
4690
  else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'HsbP') {
4389
4691
  return 'enum';
4390
4692
  }
4693
+ else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'LvlA') {
4694
+ return 'VlLs';
4695
+ }
4696
+ else if (key === 'Intr' && typeof value === 'boolean') {
4697
+ return 'bool'; // interpolation in print style
4698
+ }
4699
+ else if (key === 'PgPs') {
4700
+ return 'enum';
4701
+ }
4391
4702
  else if (key === 'LghD' && parent._classID === 'oilPaint') {
4392
4703
  return 'long';
4393
4704
  }
@@ -4400,7 +4711,7 @@ function readAsciiStringOrClassId(reader) {
4400
4711
  return (0, psdReader_1.readAsciiString)(reader, length || 4);
4401
4712
  }
4402
4713
  function writeAsciiStringOrClassId(writer, value) {
4403
- if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list') {
4714
+ if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list' && value !== 'json') {
4404
4715
  // write classId
4405
4716
  (0, psdWriter_1.writeInt32)(writer, 0);
4406
4717
  (0, psdWriter_1.writeSignature)(writer, value);
@@ -4428,12 +4739,20 @@ function readDescriptorStructure(reader, includeClass) {
4428
4739
  }
4429
4740
  return object;
4430
4741
  }
4742
+ exports.outputSettingsRoot = 'outputSettings';
4431
4743
  function writeDescriptorStructure(writer, name, classId, value, root) {
4432
4744
  if (logErrors && !classId)
4433
4745
  console.log('Missing classId for: ', name, classId, value);
4434
4746
  // write class structure
4435
4747
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, name);
4436
- writeAsciiStringOrClassId(writer, classId);
4748
+ if (root === exports.outputSettingsRoot && classId === 'null') {
4749
+ // Photoshop writes 'null' class id as 4 character string in save for web output settings
4750
+ (0, psdWriter_1.writeInt32)(writer, 4);
4751
+ (0, psdWriter_1.writeSignature)(writer, classId);
4752
+ }
4753
+ else {
4754
+ writeAsciiStringOrClassId(writer, classId);
4755
+ }
4437
4756
  const allKeys = Object.keys(value);
4438
4757
  const keys = allKeys.filter(key => key !== '_name' && key !== '_classID' && value[key] !== undefined);
4439
4758
  (0, psdWriter_1.writeUint32)(writer, keys.length);
@@ -4543,12 +4862,17 @@ function writeDescriptorStructure(writer, name, classId, value, root) {
4543
4862
  else if (key === 'bounds' && root === 'quiltWarp') {
4544
4863
  extType = makeType('', 'classFloatRect');
4545
4864
  }
4865
+ // guess color class from its fields (colors created by serializeColor always have _classID set)
4546
4866
  if (extType && extType.classID === 'RGBC') {
4547
4867
  if ('H ' in value[key])
4548
4868
  extType = { classID: 'HSBC', name: '' };
4549
- // TODO: other color spaces
4869
+ else if ('Cyn ' in value[key])
4870
+ extType = { classID: 'CMYC', name: '' };
4871
+ else if ('Lmnc' in value[key])
4872
+ extType = { classID: 'LbCl', name: '' };
4873
+ else if ('Gry ' in value[key])
4874
+ extType = { classID: 'Grsc', name: '' };
4550
4875
  }
4551
- type = getRecordedOSType(value[key], type);
4552
4876
  if (!type)
4553
4877
  type = guessOSType(value[key]);
4554
4878
  writeAsciiStringOrClassId(writer, key);
@@ -4563,9 +4887,8 @@ function readOSType(reader, type, includeClass) {
4563
4887
  case 'obj ': // Reference
4564
4888
  return readReferenceStructure(reader);
4565
4889
  case 'Objc': // Descriptor
4566
- return readDescriptorStructure(reader, includeClass);
4567
4890
  case 'GlbO': // GlobalObject same as Descriptor
4568
- return setHidden(readDescriptorStructure(reader, includeClass), osTypeKey, type);
4891
+ return readDescriptorStructure(reader, includeClass);
4569
4892
  case 'VlLs': { // List
4570
4893
  const length = (0, psdReader_1.readInt32)(reader);
4571
4894
  const items = [];
@@ -4590,7 +4913,7 @@ function readOSType(reader, type, includeClass) {
4590
4913
  const value = (0, psdReader_1.readFloat32)(reader);
4591
4914
  if (!unitsMap[units])
4592
4915
  throw new Error(`Invalid units: ${units}`);
4593
- return setHidden({ units: unitsMap[units], value }, osTypeKey, type);
4916
+ return { units: unitsMap[units], value };
4594
4917
  }
4595
4918
  case 'TEXT': // String
4596
4919
  return (0, psdReader_1.readUnicodeString)(reader);
@@ -4604,13 +4927,13 @@ function readOSType(reader, type, includeClass) {
4604
4927
  case 'comp': { // Large Integer
4605
4928
  const low = (0, psdReader_1.readUint32)(reader);
4606
4929
  const high = (0, psdReader_1.readUint32)(reader);
4607
- return setHidden({ low, high }, osTypeKey, type);
4930
+ return { low, high };
4608
4931
  }
4609
4932
  case 'bool': // Boolean
4610
4933
  return !!(0, psdReader_1.readUint8)(reader);
4611
4934
  case 'type': // Class
4612
4935
  case 'GlbC': // Class
4613
- return setHidden(readClassStructure(reader), osTypeKey, type);
4936
+ return readClassStructure(reader);
4614
4937
  case 'alis': { // Alias
4615
4938
  const length = (0, psdReader_1.readInt32)(reader);
4616
4939
  return (0, psdReader_1.readAsciiString)(reader, length);
@@ -4622,21 +4945,21 @@ function readOSType(reader, type, includeClass) {
4622
4945
  case 'ObAr': { // Object array
4623
4946
  (0, psdReader_1.readInt32)(reader); // version: 16
4624
4947
  (0, psdReader_1.readUnicodeString)(reader); // name: ''
4625
- const classID = readAsciiStringOrClassId(reader); // 'rationalPoint'
4948
+ readAsciiStringOrClassId(reader); // classID: 'rationalPoint'
4626
4949
  const length = (0, psdReader_1.readInt32)(reader);
4627
4950
  const items = [];
4628
4951
  for (let i = 0; i < length; i++) {
4629
4952
  const type1 = readAsciiStringOrClassId(reader); // type Hrzn | Vrtc
4630
- const valueType = (0, psdReader_1.readSignature)(reader); // UnFl
4631
- const units = (0, psdReader_1.readSignature)(reader); // units ? '#Pxl'
4953
+ (0, psdReader_1.readSignature)(reader); // UnFl
4954
+ (0, psdReader_1.readSignature)(reader); // units ? '#Pxl'
4632
4955
  const valuesCount = (0, psdReader_1.readInt32)(reader);
4633
4956
  const values = [];
4634
4957
  for (let j = 0; j < valuesCount; j++) {
4635
4958
  values.push((0, psdReader_1.readFloat64)(reader));
4636
4959
  }
4637
- items.push(setHidden({ type: type1, values }, obArInfoKey, { valueType, units }));
4960
+ items.push({ type: type1, values });
4638
4961
  }
4639
- return setHidden(items, obArInfoKey, { classID });
4962
+ return items;
4640
4963
  }
4641
4964
  case 'Pth ': { // File path
4642
4965
  /*const length =*/ (0, psdReader_1.readInt32)(reader); // total size of all fields below
@@ -4656,7 +4979,7 @@ const ObArTypes = {
4656
4979
  quiltSliceY: 'UntF',
4657
4980
  };
4658
4981
  function writeOSType(writer, type, value, key, extType, root) {
4659
- var _a, _b, _c, _d, _e;
4982
+ var _a;
4660
4983
  switch (type) {
4661
4984
  case 'obj ': // Reference
4662
4985
  writeReferenceStructure(writer, key, value);
@@ -4677,7 +5000,7 @@ function writeOSType(writer, type, value, key, extType, root) {
4677
5000
  throw new Error(`Invalid list value: ${JSON.stringify(value)}, key: ${key}`);
4678
5001
  (0, psdWriter_1.writeInt32)(writer, value.length);
4679
5002
  for (let i = 0; i < value.length; i++) {
4680
- const type = (_a = getRecordedOSType(value[i], fieldToArrayType[key])) !== null && _a !== void 0 ? _a : guessOSType(value[i]);
5003
+ const type = (_a = fieldToArrayType[key]) !== null && _a !== void 0 ? _a : guessOSType(value[i]);
4681
5004
  (0, psdWriter_1.writeSignature)(writer, type || 'long');
4682
5005
  writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
4683
5006
  if (logErrors && !type)
@@ -4751,16 +5074,15 @@ function writeOSType(writer, type, value, key, extType, root) {
4751
5074
  case 'ObAr': { // Object array
4752
5075
  (0, psdWriter_1.writeInt32)(writer, 16); // version
4753
5076
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, ''); // name
4754
- const type = (_c = (_b = getHidden(value, obArInfoKey)) === null || _b === void 0 ? void 0 : _b.classID) !== null && _c !== void 0 ? _c : ObArTypes[key];
5077
+ const type = ObArTypes[key];
4755
5078
  if (!type)
4756
5079
  throw new Error(`Not implemented ObArType for: ${key}`);
4757
5080
  writeAsciiStringOrClassId(writer, type);
4758
5081
  (0, psdWriter_1.writeInt32)(writer, value.length);
4759
5082
  for (let i = 0; i < value.length; i++) {
4760
- const info = getHidden(value[i], obArInfoKey);
4761
5083
  writeAsciiStringOrClassId(writer, value[i].type); // Hrzn | Vrtc
4762
- (0, psdWriter_1.writeSignature)(writer, (_d = info === null || info === void 0 ? void 0 : info.valueType) !== null && _d !== void 0 ? _d : 'UnFl');
4763
- (0, psdWriter_1.writeSignature)(writer, (_e = info === null || info === void 0 ? void 0 : info.units) !== null && _e !== void 0 ? _e : '#Pxl');
5084
+ (0, psdWriter_1.writeSignature)(writer, 'UnFl');
5085
+ (0, psdWriter_1.writeSignature)(writer, '#Pxl');
4764
5086
  (0, psdWriter_1.writeInt32)(writer, value[i].values.length);
4765
5087
  for (let j = 0; j < value[i].values.length; j++) {
4766
5088
  (0, psdWriter_1.writeFloat64)(writer, value[i].values[j]);
@@ -4769,83 +5091,60 @@ function writeOSType(writer, type, value, key, extType, root) {
4769
5091
  break;
4770
5092
  }
4771
5093
  case 'Pth ': { // File path
4772
- const length = 4 + 4 + 4 + value.path.length * 2;
5094
+ const path = value.path + '\0'; // Photoshop stores trailing null character (stripped when reading)
5095
+ const length = 4 + 4 + 4 + path.length * 2;
4773
5096
  (0, psdWriter_1.writeInt32)(writer, length);
4774
5097
  (0, psdWriter_1.writeSignature)(writer, value.sig);
4775
5098
  (0, psdWriter_1.writeInt32LE)(writer, length);
4776
- (0, psdWriter_1.writeInt32LE)(writer, value.path.length);
4777
- (0, psdWriter_1.writeUnicodeStringWithoutLengthLE)(writer, value.path);
5099
+ (0, psdWriter_1.writeInt32LE)(writer, path.length);
5100
+ (0, psdWriter_1.writeUnicodeStringWithoutLengthLE)(writer, path);
4778
5101
  break;
4779
5102
  }
4780
5103
  default:
4781
5104
  throw new Error(`Not implemented descriptor OSType: ${type}`);
4782
5105
  }
4783
5106
  }
4784
- function referenceItemToValue(item) {
4785
- switch (item.type) {
4786
- case 'prop': return item.keyID;
4787
- case 'Clss': return { name: item.name || '', classID: item.classID };
4788
- case 'Enmr': return `${item.typeID}.${item.enumValue}`;
4789
- default: return item.value;
4790
- }
4791
- }
4792
- function referenceItemMatches(item, value) {
4793
- if (!item)
4794
- return false;
4795
- const expected = referenceItemToValue(item);
4796
- if (expected && typeof expected === 'object') {
4797
- return !!value && typeof value === 'object' && value.name === expected.name && value.classID === expected.classID;
4798
- }
4799
- return expected === value;
4800
- }
4801
- // Attaches information about how each item of reference should be written (class IDs, reference types),
4802
- // the public value of the array stays the same (array of strings/numbers/class objects)
4803
- function createDescriptorReference(items) {
4804
- return setHidden(items.map(referenceItemToValue), referenceInfoKey, items);
5107
+ const referenceTypes = ['prop', 'Clss', 'Enmr', 'rele', 'Idnt', 'indx', 'name'];
5108
+ function isReferenceItem(value) {
5109
+ return !!value && typeof value === 'object' && referenceTypes.indexOf(value.type) !== -1;
4805
5110
  }
4806
5111
  function readReferenceStructure(reader) {
4807
5112
  const itemsCount = (0, psdReader_1.readInt32)(reader);
4808
- const infos = [];
5113
+ const items = [];
4809
5114
  for (let i = 0; i < itemsCount; i++) {
4810
5115
  const type = (0, psdReader_1.readSignature)(reader);
4811
5116
  switch (type) {
4812
- case 'prop': { // Property
4813
- const { name, classID } = readClassStructure(reader);
4814
- const keyID = readAsciiStringOrClassId(reader);
4815
- infos.push({ type, name, classID, keyID });
5117
+ case 'prop': // Property
5118
+ readClassStructure(reader);
5119
+ items.push(readAsciiStringOrClassId(reader));
4816
5120
  break;
4817
- }
4818
- case 'Clss': { // Class
4819
- const { name, classID } = readClassStructure(reader);
4820
- infos.push({ type, name, classID });
5121
+ case 'Clss': // Class
5122
+ items.push(readClassStructure(reader));
4821
5123
  break;
4822
- }
4823
5124
  case 'Enmr': { // Enumerated Reference
4824
- const { name, classID } = readClassStructure(reader);
5125
+ readClassStructure(reader);
4825
5126
  const typeID = readAsciiStringOrClassId(reader);
4826
5127
  const enumValue = readAsciiStringOrClassId(reader);
4827
- infos.push({ type, name, classID, typeID, enumValue });
5128
+ items.push(`${typeID}.${enumValue}`);
4828
5129
  break;
4829
5130
  }
4830
- case 'rele': { // Offset
4831
- const { name, classID } = readClassStructure(reader);
4832
- infos.push({ type, name, classID, value: (0, psdReader_1.readUint32)(reader) });
5131
+ case 'rele': // Offset
5132
+ readClassStructure(reader);
5133
+ items.push((0, psdReader_1.readUint32)(reader));
4833
5134
  break;
4834
- }
4835
5135
  case 'Idnt': // Identifier
4836
5136
  case 'indx': // Index
4837
- infos.push({ type, value: (0, psdReader_1.readInt32)(reader) });
5137
+ items.push((0, psdReader_1.readInt32)(reader));
4838
5138
  break;
4839
- case 'name': { // Name
4840
- const { name, classID } = readClassStructure(reader);
4841
- infos.push({ type, name, classID, value: (0, psdReader_1.readUnicodeString)(reader) });
5139
+ case 'name': // Name
5140
+ readClassStructure(reader);
5141
+ items.push((0, psdReader_1.readUnicodeString)(reader));
4842
5142
  break;
4843
- }
4844
5143
  default:
4845
5144
  throw new Error(`Invalid descriptor reference type: ${type}`);
4846
5145
  }
4847
5146
  }
4848
- return createDescriptorReference(infos);
5147
+ return items;
4849
5148
  }
4850
5149
  function guessReferenceItem(value) {
4851
5150
  if (typeof value === 'string') {
@@ -4853,17 +5152,17 @@ function guessReferenceItem(value) {
4853
5152
  // (values like 'My.Path' or 'Logo.v2' can't be typeID/enum value, so they are written as name references)
4854
5153
  const match = /^([a-z ]{4}|[a-z]{5,})\.([a-z ]+)$/i.exec(value);
4855
5154
  if (match) {
4856
- return { type: 'Enmr', name: '', classID: match[1], typeID: match[1], enumValue: match[2] };
5155
+ return { type: 'Enmr', _name: '', _classID: match[1], typeID: match[1], enumValue: match[2] };
4857
5156
  }
4858
5157
  else {
4859
- return { type: 'name', name: '', classID: 'Lyr ', value };
5158
+ return { type: 'name', _name: '', _classID: 'Lyr ', value };
4860
5159
  }
4861
5160
  }
4862
5161
  else if (typeof value === 'number') {
4863
5162
  return { type: 'Idnt', value };
4864
5163
  }
4865
5164
  else if (value && typeof value === 'object' && typeof value.classID === 'string') {
4866
- return { type: 'Clss', name: value.name || '', classID: value.classID };
5165
+ return { type: 'Clss', _name: value.name || '', _classID: value.classID };
4867
5166
  }
4868
5167
  else {
4869
5168
  throw new Error(`Invalid descriptor reference value: ${JSON.stringify(value)}`);
@@ -4872,41 +5171,35 @@ function guessReferenceItem(value) {
4872
5171
  // path selection state (image resource 1088) is a reference to paths, enumerated references there use 'Path' class
4873
5172
  // (name references use 'Lyr ' class, they reference layer of the vector mask)
4874
5173
  function pathSelectionStateToReference(items) {
4875
- const infos = getHidden(items, referenceInfoKey);
4876
- return createDescriptorReference(items.map((value, i) => {
4877
- if (referenceItemMatches(infos === null || infos === void 0 ? void 0 : infos[i], value))
4878
- return infos[i]; // keep information read from the file
5174
+ return items.map(value => {
4879
5175
  const item = guessReferenceItem(value);
4880
5176
  if (item.type === 'Enmr')
4881
- item.classID = 'Path';
5177
+ item._classID = 'Path';
4882
5178
  return item;
4883
- }));
5179
+ });
4884
5180
  }
4885
5181
  function writeReferenceStructure(writer, _key, items) {
4886
5182
  if (!Array.isArray(items))
4887
5183
  throw new Error(`Invalid reference value: ${JSON.stringify(items)}, key: ${_key}`);
4888
- const infos = getHidden(items, referenceInfoKey);
4889
5184
  (0, psdWriter_1.writeInt32)(writer, items.length);
4890
- for (let i = 0; i < items.length; i++) {
4891
- const value = items[i];
4892
- const recorded = infos === null || infos === void 0 ? void 0 : infos[i];
4893
- const item = referenceItemMatches(recorded, value) ? recorded : guessReferenceItem(value);
5185
+ for (const value of items) {
5186
+ const item = isReferenceItem(value) ? value : guessReferenceItem(value);
4894
5187
  (0, psdWriter_1.writeSignature)(writer, item.type);
4895
5188
  switch (item.type) {
4896
5189
  case 'prop': // Property
4897
- writeClassStructure(writer, item.name || '', item.classID);
5190
+ writeClassStructure(writer, item._name || '', item._classID);
4898
5191
  writeAsciiStringOrClassId(writer, item.keyID);
4899
5192
  break;
4900
5193
  case 'Clss': // Class
4901
- writeClassStructure(writer, item.name || '', item.classID);
5194
+ writeClassStructure(writer, item._name || '', item._classID);
4902
5195
  break;
4903
5196
  case 'Enmr': // Enumerated Reference
4904
- writeClassStructure(writer, item.name || '', item.classID);
5197
+ writeClassStructure(writer, item._name || '', item._classID);
4905
5198
  writeAsciiStringOrClassId(writer, item.typeID);
4906
5199
  writeAsciiStringOrClassId(writer, item.enumValue);
4907
5200
  break;
4908
5201
  case 'rele': // Offset
4909
- writeClassStructure(writer, item.name || '', item.classID);
5202
+ writeClassStructure(writer, item._name || '', item._classID);
4910
5203
  (0, psdWriter_1.writeUint32)(writer, item.value);
4911
5204
  break;
4912
5205
  case 'Idnt': // Identifier
@@ -4914,7 +5207,7 @@ function writeReferenceStructure(writer, _key, items) {
4914
5207
  (0, psdWriter_1.writeInt32)(writer, item.value);
4915
5208
  break;
4916
5209
  case 'name': // Name
4917
- writeClassStructure(writer, item.name || '', item.classID);
5210
+ writeClassStructure(writer, item._name || '', item._classID);
4918
5211
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, `${item.value}`);
4919
5212
  break;
4920
5213
  default:
@@ -4973,7 +5266,7 @@ function parseFxObject(fx) {
4973
5266
  fillType: exports.FrFl.decode(fx.PntT),
4974
5267
  blendMode: exports.BlnM.decode(fx['Md ']),
4975
5268
  opacity: parsePercent(fx.Opct),
4976
- size: parseUnits(fx['Sz ']),
5269
+ size: parseUnitsOrNumber(fx['Sz ']), // stored as number (pixels) in old versions
4977
5270
  };
4978
5271
  if (fx.present !== undefined)
4979
5272
  stroke.present = fx.present;
@@ -5078,11 +5371,11 @@ function serializeEffects(e, log, multi) {
5078
5371
  }
5079
5372
  function parseEffects(info, log) {
5080
5373
  const effects = {};
5081
- const { masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX } = info, rest = __rest(info, ["masterFXSwitch", "DrSh", "dropShadowMulti", "IrSh", "innerShadowMulti", "OrGl", "IrGl", "ebbl", "SoFi", "solidFillMulti", "patternFill", "GrFl", "gradientFillMulti", "ChFX", "FrFX", "frameFXMulti", "numModifyingFX"]);
5374
+ const _a = info, { _name, _classID, 'Scl ': scale, masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX } = _a, rest = __rest(_a, ["_name", "_classID", 'Scl ', "masterFXSwitch", "DrSh", "dropShadowMulti", "IrSh", "innerShadowMulti", "OrGl", "IrGl", "ebbl", "SoFi", "solidFillMulti", "patternFill", "GrFl", "gradientFillMulti", "ChFX", "FrFX", "frameFXMulti", "numModifyingFX"]);
5082
5375
  if (!masterFXSwitch)
5083
5376
  effects.disabled = true;
5084
- if (info['Scl '])
5085
- effects.scale = parsePercent(info['Scl ']);
5377
+ if (scale)
5378
+ effects.scale = parsePercent(scale);
5086
5379
  if (DrSh)
5087
5380
  effects.dropShadow = [parseEffectObject(DrSh, log)];
5088
5381
  if (dropShadowMulti)
@@ -5096,7 +5389,7 @@ function parseEffects(info, log) {
5096
5389
  if (IrGl)
5097
5390
  effects.innerGlow = parseEffectObject(IrGl, log);
5098
5391
  if (ebbl)
5099
- effects.bevel = parseEffectObject(ebbl, log);
5392
+ effects.bevel = parseEffectObject(ebbl, log, 'bevel');
5100
5393
  if (SoFi)
5101
5394
  effects.solidFill = [parseEffectObject(SoFi, log)];
5102
5395
  if (solidFillMulti)
@@ -5113,7 +5406,7 @@ function parseEffects(info, log) {
5113
5406
  effects.stroke = [parseFxObject(FrFX)];
5114
5407
  if (frameFXMulti)
5115
5408
  effects.stroke = frameFXMulti.map(i => parseFxObject(i));
5116
- if (log && Object.keys(rest).length > 1)
5409
+ if (log && Object.keys(rest).length)
5117
5410
  console.log('Unhandled effect keys:', rest);
5118
5411
  return effects;
5119
5412
  }
@@ -5222,7 +5515,7 @@ function serializeTrackList(tracks) {
5222
5515
  }
5223
5516
  return trackList;
5224
5517
  }
5225
- function parseEffectObject(obj, reportErrors) {
5518
+ function parseEffectObject(obj, reportErrors, objName) {
5226
5519
  const result = {};
5227
5520
  for (const key of Object.keys(obj)) {
5228
5521
  const val = obj[key];
@@ -5287,7 +5580,8 @@ function parseEffectObject(obj, reportErrors) {
5287
5580
  case 'Type':
5288
5581
  result.type = exports.GrdT.decode(val);
5289
5582
  break;
5290
- case 'gs99':
5583
+ case 'gs99': // gradient overlay
5584
+ case 'gradientsInterpolationMethod': // glows
5291
5585
  result.interpolationMethod = exports.gradientInterpolationMethodType.decode(val);
5292
5586
  break;
5293
5587
  case 'Opct':
@@ -5339,18 +5633,26 @@ function parseEffectObject(obj, reportErrors) {
5339
5633
  result.pattern = { name: val['Nm '], id: val.Idnt };
5340
5634
  break;
5341
5635
  case 'phase':
5342
- result.phase = { x: val.Hrzn, y: val.Vrtc };
5636
+ result.phase = parsePhase(val);
5343
5637
  break;
5344
5638
  case 'Ofst':
5345
5639
  result.offset = { x: parsePercent(val.Hrzn), y: parsePercent(val.Vrtc) };
5346
5640
  break;
5347
5641
  case 'MpgS':
5348
- case 'TrnS':
5349
- result.contour = {
5642
+ case 'TrnS': {
5643
+ const contour = {
5350
5644
  name: val['Nm '],
5351
5645
  curve: val['Crv '].map(p => ({ x: p.Hrzn, y: p.Vrtc })),
5352
5646
  };
5647
+ // bevel can have both: gloss contour (TrnS) and contour of the shape (MpgS)
5648
+ if (objName === 'bevel' && key === 'MpgS') {
5649
+ result.shapeContour = contour;
5650
+ }
5651
+ else {
5652
+ result.contour = contour;
5653
+ }
5353
5654
  break;
5655
+ }
5354
5656
  case 'Grad':
5355
5657
  result.gradient = parseGradient(val);
5356
5658
  break;
@@ -5445,7 +5747,7 @@ function serializeEffectObject(obj, objName, reportErrors) {
5445
5747
  result.Type = exports.GrdT.encode(val);
5446
5748
  break;
5447
5749
  case 'interpolationMethod':
5448
- result.gs99 = exports.gradientInterpolationMethodType.encode(val);
5750
+ result[(objName === 'outerGlow' || objName === 'innerGlow') ? 'gradientsInterpolationMethod' : 'gs99'] = exports.gradientInterpolationMethodType.encode(val);
5449
5751
  break;
5450
5752
  case 'opacity':
5451
5753
  result.Opct = unitsPercent(val);
@@ -5501,15 +5803,14 @@ function serializeEffectObject(obj, objName, reportErrors) {
5501
5803
  result.phase = { Hrzn: val.x, Vrtc: val.y };
5502
5804
  break;
5503
5805
  case 'offset':
5504
- result.Ofst = { Hrzn: unitsPercent(val.x), Vrtc: unitsPercent(val.y) };
5806
+ result.Ofst = { Hrzn: unitsPercentF(val.x), Vrtc: unitsPercentF(val.y) };
5505
5807
  break;
5506
- case 'contour': {
5507
- result[objName === 'satin' ? 'MpgS' : 'TrnS'] = {
5508
- 'Nm ': val.name,
5509
- 'Crv ': val.curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
5510
- };
5808
+ case 'contour':
5809
+ result[objName === 'satin' ? 'MpgS' : 'TrnS'] = serializeContour(val);
5810
+ break;
5811
+ case 'shapeContour':
5812
+ result.MpgS = serializeContour(val);
5511
5813
  break;
5512
- }
5513
5814
  case 'gradient':
5514
5815
  result.Grad = serializeGradient(val);
5515
5816
  break;
@@ -5527,6 +5828,12 @@ function serializeEffectObject(obj, objName, reportErrors) {
5527
5828
  }
5528
5829
  return result;
5529
5830
  }
5831
+ function serializeContour(contour) {
5832
+ return {
5833
+ 'Nm ': contour.name,
5834
+ 'Crv ': contour.curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
5835
+ };
5836
+ }
5530
5837
  function parseGradient(grad) {
5531
5838
  if (grad.GrdF === 'GrdF.CstS') {
5532
5839
  // stop locations are always in 0..4096 range, independent of smoothness (Intr)
@@ -5535,11 +5842,22 @@ function parseGradient(grad) {
5535
5842
  type: 'solid',
5536
5843
  name: grad['Nm '],
5537
5844
  smoothness: grad.Intr / 4096,
5538
- colorStops: grad.Clrs.map(s => ({
5539
- color: parseColor(s['Clr ']),
5540
- location: s.Lctn / samples,
5541
- midpoint: s.Mdpn / 100,
5542
- })),
5845
+ colorStops: grad.Clrs.map(s => {
5846
+ const stop = {
5847
+ color: s['Clr '] ? parseColor(s['Clr ']) : { r: 0, g: 0, b: 0 },
5848
+ location: s.Lctn / samples,
5849
+ midpoint: s.Mdpn / 100,
5850
+ };
5851
+ if (s.Type) {
5852
+ try {
5853
+ const type = exports.Clry.decode(s.Type);
5854
+ if (type !== 'user')
5855
+ stop.type = type;
5856
+ }
5857
+ catch (_a) { } // unknown stop type, read as user color
5858
+ }
5859
+ return stop;
5860
+ }),
5543
5861
  opacityStops: grad.Trns.map(s => ({
5544
5862
  opacity: parsePercent(s.Opct),
5545
5863
  location: s.Lctn / samples,
@@ -5571,18 +5889,18 @@ function serializeGradient(grad) {
5571
5889
  GrdF: 'GrdF.CstS',
5572
5890
  Intr: Math.round(((_a = grad.smoothness) !== null && _a !== void 0 ? _a : 1) * 4096),
5573
5891
  Clrs: grad.colorStops.map(s => {
5574
- var _a;
5892
+ var _a, _b;
5575
5893
  return ({
5576
5894
  'Clr ': serializeColor(s.color),
5577
- Type: 'Clry.UsrS',
5895
+ Type: exports.Clry.encode((_a = s.type) !== null && _a !== void 0 ? _a : 'user'),
5578
5896
  Lctn: Math.round(s.location * samples),
5579
- Mdpn: Math.round(((_a = s.midpoint) !== null && _a !== void 0 ? _a : 0.5) * 100),
5897
+ Mdpn: Math.round(((_b = s.midpoint) !== null && _b !== void 0 ? _b : 0.5) * 100),
5580
5898
  });
5581
5899
  }),
5582
5900
  Trns: grad.opacityStops.map(s => {
5583
5901
  var _a;
5584
5902
  return ({
5585
- Opct: unitsPercent(s.opacity),
5903
+ Opct: unitsPercentF(s.opacity),
5586
5904
  Lctn: Math.round(s.location * samples),
5587
5905
  Mdpn: Math.round(((_a = s.midpoint) !== null && _a !== void 0 ? _a : 0.5) * 100),
5588
5906
  });
@@ -5626,6 +5944,13 @@ function parseGradientContent(descriptor) {
5626
5944
  }
5627
5945
  return result;
5628
5946
  }
5947
+ // phase is usually stored as plain numbers, but some files store it as unit values
5948
+ function parsePhase({ Hrzn, Vrtc }) {
5949
+ return {
5950
+ x: typeof Hrzn === 'number' ? Hrzn : Hrzn.value,
5951
+ y: typeof Vrtc === 'number' ? Vrtc : Vrtc.value,
5952
+ };
5953
+ }
5629
5954
  function parsePatternContent(descriptor) {
5630
5955
  const result = {
5631
5956
  name: descriptor.Ptrn['Nm '],
@@ -5634,7 +5959,11 @@ function parsePatternContent(descriptor) {
5634
5959
  if (descriptor.Lnkd !== undefined)
5635
5960
  result.linked = descriptor.Lnkd;
5636
5961
  if (descriptor.phase !== undefined)
5637
- result.phase = { x: descriptor.phase.Hrzn, y: descriptor.phase.Vrtc };
5962
+ result.phase = parsePhase(descriptor.phase);
5963
+ if (descriptor['Scl '] !== undefined)
5964
+ result.scale = parsePercent(descriptor['Scl ']);
5965
+ if (descriptor.Angl !== undefined)
5966
+ result.angle = parseAngle(descriptor.Angl);
5638
5967
  return result;
5639
5968
  }
5640
5969
  function parseVectorContent(descriptor) {
@@ -5665,11 +5994,11 @@ function serializeGradientContent(content) {
5665
5994
  if (content.align !== undefined)
5666
5995
  result.Algn = content.align;
5667
5996
  if (content.scale !== undefined)
5668
- result['Scl '] = unitsPercent(content.scale);
5997
+ result['Scl '] = unitsPercentF(content.scale);
5669
5998
  if (content.offset) {
5670
5999
  result.Ofst = {
5671
- Hrzn: unitsPercent(content.offset.x),
5672
- Vrtc: unitsPercent(content.offset.y),
6000
+ Hrzn: unitsPercentF(content.offset.x),
6001
+ Vrtc: unitsPercentF(content.offset.y),
5673
6002
  };
5674
6003
  }
5675
6004
  result.Grad = serializeGradient(content);
@@ -5686,6 +6015,10 @@ function serializePatternContent(content) {
5686
6015
  result.Lnkd = !!content.linked;
5687
6016
  if (content.phase !== undefined)
5688
6017
  result.phase = { Hrzn: content.phase.x, Vrtc: content.phase.y };
6018
+ if (content.scale !== undefined)
6019
+ result['Scl '] = unitsPercentF(content.scale);
6020
+ if (content.angle !== undefined)
6021
+ result.Angl = unitsAngle(content.angle);
5689
6022
  return result;
5690
6023
  }
5691
6024
  function serializeVectorContent(content) {
@@ -5701,9 +6034,9 @@ function serializeVectorContent(content) {
5701
6034
  }
5702
6035
  // Descriptor color values: HSB saturation/brightness 0..100, CMYK 0..100, Gray 0..100, Lab L 0..100, a/b -128..127
5703
6036
  // Color values (see psd.ts): HSB 0..1, CMYK 0..255, Gray 0..255, Lab L 0..1, a/b -1..1 (same as readColor/writeColor)
5704
- const colorSourceKey = typeof Symbol !== 'undefined' ? Symbol('agPsdColorSource') : '__agPsdColorSource';
5705
6037
  const colorConversions = {
5706
6038
  // key: [descriptor -> color, color -> descriptor]
6039
+ 'H ': [x => x / 360, x => x * 360], // angle
5707
6040
  Strt: [x => x / 100, x => x * 100],
5708
6041
  Brgh: [x => x / 100, x => x * 100],
5709
6042
  'Cyn ': [x => x * 255 / 100, x => x * 100 / 255],
@@ -5731,22 +6064,18 @@ function colorToDescriptorValue(key, value) {
5731
6064
  }
5732
6065
  return result;
5733
6066
  }
5734
- function shallowEqual(a, b) {
5735
- const keys = Object.keys(a);
5736
- if (keys.length !== Object.keys(b).length)
5737
- return false;
5738
- return keys.every(key => a[key] === b[key]);
6067
+ function isBookColor(color) {
6068
+ return color._classID === 'BkCl' || 'Bk ' in color || 'bookID' in color;
5739
6069
  }
5740
6070
  function parseColor(color) {
5741
- let result;
5742
6071
  if ('H ' in color) {
5743
- result = { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
6072
+ return { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
5744
6073
  }
5745
6074
  else if ('Rd ' in color) {
5746
6075
  return { r: color['Rd '], g: color['Grn '], b: color['Bl '] };
5747
6076
  }
5748
6077
  else if ('Cyn ' in color) {
5749
- result = {
6078
+ return {
5750
6079
  c: color['Cyn '] * 255 / 100,
5751
6080
  m: color.Mgnt * 255 / 100,
5752
6081
  y: color['Ylw '] * 255 / 100,
@@ -5754,21 +6083,22 @@ function parseColor(color) {
5754
6083
  };
5755
6084
  }
5756
6085
  else if ('Gry ' in color) {
5757
- result = { k: color['Gry '] * 255 / 100 };
6086
+ return { k: color['Gry '] * 255 / 100 };
5758
6087
  }
5759
6088
  else if ('Lmnc' in color) {
5760
- result = { l: color.Lmnc / 100, a: (0, helpers_1.normalizeLabAB)(color['A ']), b: (0, helpers_1.normalizeLabAB)(color['B ']) };
6089
+ return { l: color.Lmnc / 100, a: (0, helpers_1.normalizeLabAB)(color['A ']), b: (0, helpers_1.normalizeLabAB)(color['B ']) };
5761
6090
  }
5762
6091
  else if ('redFloat' in color) {
5763
6092
  return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
5764
6093
  }
6094
+ else if (isBookColor(color)) {
6095
+ return { r: 0, g: 0, b: 0 }; // book (Pantone) colors are not supported
6096
+ }
5765
6097
  else {
5766
6098
  throw new Error('Unsupported color descriptor');
5767
6099
  }
5768
- // remember original descriptor values, so unmodified colors can be written back without conversion errors
5769
- return setHidden(result, colorSourceKey, { color: Object.assign({}, result), descriptor: color });
5770
6100
  }
5771
- function serializeColorValues(color) {
6101
+ function serializeColor(color) {
5772
6102
  if (!color) {
5773
6103
  return { _name: '', _classID: 'RGBC', 'Rd ': 0, 'Grn ': 0, 'Bl ': 0 };
5774
6104
  }
@@ -5781,7 +6111,7 @@ function serializeColorValues(color) {
5781
6111
  else if ('h' in color) {
5782
6112
  return {
5783
6113
  _name: '', _classID: 'HSBC',
5784
- 'H ': unitsAngle((color.h || 0) * 360),
6114
+ 'H ': unitsAngle(colorToDescriptorValue('H ', color.h || 0)),
5785
6115
  Strt: colorToDescriptorValue('Strt', color.s || 0),
5786
6116
  Brgh: colorToDescriptorValue('Brgh', color.b || 0),
5787
6117
  };
@@ -5807,21 +6137,8 @@ function serializeColorValues(color) {
5807
6137
  return { _name: '', _classID: 'Grsc', 'Gry ': colorToDescriptorValue('Gry ', color.k || 0) };
5808
6138
  }
5809
6139
  else {
5810
- throw new Error('Invalid color value');
5811
- }
5812
- }
5813
- function serializeColor(color) {
5814
- const result = serializeColorValues(color);
5815
- const source = getHidden(color, colorSourceKey);
5816
- if (source && shallowEqual(source.color, color)) {
5817
- // color was not modified since it was read, use exact original values
5818
- for (const key of Object.keys(result)) {
5819
- if (colorConversions[key] && typeof source.descriptor[key] === 'number') {
5820
- result[key] = source.descriptor[key];
5821
- }
5822
- }
6140
+ throw new Error('Invalid color value');
5823
6141
  }
5824
- return result;
5825
6142
  }
5826
6143
  function parseAngle(x) {
5827
6144
  if (x === undefined)
@@ -5848,7 +6165,8 @@ function parsePercentOrAngle(x) {
5848
6165
  }
5849
6166
  function parseUnits({ units, value }) {
5850
6167
  if (units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
5851
- units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density') {
6168
+ units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
6169
+ units !== 'Distance') {
5852
6170
  throw new Error(`Invalid units: ${JSON.stringify({ units, value })}`);
5853
6171
  }
5854
6172
  return { value, units };
@@ -5881,7 +6199,8 @@ function unitsValue(x, key) {
5881
6199
  if (typeof value !== 'number')
5882
6200
  throw new Error(`Invalid value in ${JSON.stringify(x)} (key: ${key})`);
5883
6201
  if (units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
5884
- units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density') {
6202
+ units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
6203
+ units !== 'Distance') {
5885
6204
  throw new Error(`Invalid units in ${JSON.stringify(x)} (key: ${key})`);
5886
6205
  }
5887
6206
  return { units, value };
@@ -6009,6 +6328,11 @@ exports.gradientInterpolationMethodType = (0, helpers_1.createEnum)('gradientInt
6009
6328
  smooth: 'Smoo',
6010
6329
  // TODO: stripes
6011
6330
  });
6331
+ exports.Clry = (0, helpers_1.createEnum)('Clry', 'user', {
6332
+ user: 'UsrS',
6333
+ foreground: 'FrgC',
6334
+ background: 'BckC',
6335
+ });
6012
6336
  exports.ClrS = (0, helpers_1.createEnum)('ClrS', 'rgb', {
6013
6337
  rgb: 'RGBC',
6014
6338
  hsb: 'HSBl',
@@ -7293,7 +7617,7 @@ function decodeEngineData2(data) {
7293
7617
  },{"./engineData":6}],8:[function(require,module,exports){
7294
7618
  "use strict";
7295
7619
  Object.defineProperty(exports, "__esModule", { value: true });
7296
- exports.createImageData = exports.createCanvas = exports.MaskParams = exports.LayerMaskFlags = exports.ColorSpace = exports.largeAdditionalInfoKeys = exports.layerColors = exports.toBlendMode = exports.fromBlendMode = exports.RAW_IMAGE_DATA = exports.MOCK_HANDLERS = void 0;
7620
+ exports.createImageData = exports.createCanvas = exports.MaskParams = exports.LayerMaskFlags = exports.ColorSpace = exports.largeAdditionalInfoKeys = exports.largeAdditionalInfoKeysObserved = exports.layerColors = exports.toBlendMode = exports.fromBlendMode = exports.MOCK_HANDLERS = void 0;
7297
7621
  exports.revMap = revMap;
7298
7622
  exports.createEnum = createEnum;
7299
7623
  exports.offsetForChannel = offsetForChannel;
@@ -7312,7 +7636,6 @@ exports.initializeCanvas = initializeCanvas;
7312
7636
  const pako_1 = require("pako");
7313
7637
  const jpeg_1 = require("./jpeg");
7314
7638
  exports.MOCK_HANDLERS = false;
7315
- exports.RAW_IMAGE_DATA = false;
7316
7639
  exports.fromBlendMode = {};
7317
7640
  exports.toBlendMode = {
7318
7641
  'pass': 'pass through',
@@ -7348,11 +7671,14 @@ Object.keys(exports.toBlendMode).forEach(key => exports.fromBlendMode[exports.to
7348
7671
  exports.layerColors = [
7349
7672
  'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray'
7350
7673
  ];
7674
+ // keys not listed in documentation that Photoshop writes with 64bit length in PSB files (with 8B64 signature)
7675
+ exports.largeAdditionalInfoKeysObserved = ['lnk3', 'lnkE', 'pths', 'extd', 'extn', 'FELS', 'artd'];
7351
7676
  exports.largeAdditionalInfoKeys = [
7352
7677
  // from documentation
7353
7678
  'LMsk', 'Lr16', 'Lr32', 'Layr', 'Mt16', 'Mt32', 'Mtrn', 'Alph', 'FMsk', 'lnk2', 'FEid', 'FXid', 'PxSD',
7354
7679
  // from guessing
7355
7680
  'cinf',
7681
+ ...exports.largeAdditionalInfoKeysObserved,
7356
7682
  ];
7357
7683
  function revMap(map) {
7358
7684
  const result = {};
@@ -7664,6 +7990,7 @@ function initializeCanvas(createCanvasMethod, createImageDataMethod) {
7664
7990
  Object.defineProperty(exports, "__esModule", { value: true });
7665
7991
  exports.resourceHandlersMap = exports.resourceHandlers = void 0;
7666
7992
  const base64_js_1 = require("base64-js");
7993
+ const additionalInfo_1 = require("./additionalInfo");
7667
7994
  const psdReader_1 = require("./psdReader");
7668
7995
  const psdWriter_1 = require("./psdWriter");
7669
7996
  const helpers_1 = require("./helpers");
@@ -7671,10 +7998,12 @@ const utf8_1 = require("./utf8");
7671
7998
  const descriptor_1 = require("./descriptor");
7672
7999
  exports.resourceHandlers = [];
7673
8000
  exports.resourceHandlersMap = {};
7674
- function addHandler(key, has, read, write) {
7675
- const handler = { key, has, read, write };
8001
+ function addHandler(key, has, read, write, options = {}) {
8002
+ var _a;
8003
+ const handler = { key, keyCount: (_a = options.keyCount) !== null && _a !== void 0 ? _a : 1, has, read, write, name: options.name };
7676
8004
  exports.resourceHandlers.push(handler);
7677
- exports.resourceHandlersMap[handler.key] = handler;
8005
+ for (let i = 0; i < handler.keyCount; i++)
8006
+ exports.resourceHandlersMap[handler.key + i] = handler;
7678
8007
  }
7679
8008
  const LOG_MOCK_HANDLERS = false;
7680
8009
  const RESOLUTION_UNITS = [undefined, 'PPI', 'PPCM'];
@@ -7692,27 +8021,18 @@ function byteAt(value, index) {
7692
8021
  }
7693
8022
  function readUtf8String(reader, length) {
7694
8023
  const buffer = (0, psdReader_1.readBytes)(reader, length);
7695
- return (0, utf8_1.decodeString)(buffer);
8024
+ return (0, psdReader_1.decodeUtf8)(reader, buffer);
7696
8025
  }
7697
8026
  function writeUtf8String(writer, value) {
7698
8027
  const buffer = (0, utf8_1.encodeString)(value);
7699
8028
  (0, psdWriter_1.writeBytes)(writer, buffer);
7700
8029
  }
7701
- let gbkDecoder;
7702
- function getGbkDecoder() {
7703
- if (gbkDecoder === undefined) {
7704
- try {
7705
- gbkDecoder = new TextDecoder('gbk');
7706
- }
7707
- catch (_a) {
7708
- gbkDecoder = null;
7709
- }
7710
- }
7711
- return gbkDecoder;
7712
- }
7713
8030
  function readEncodedString(reader) {
7714
8031
  const length = (0, psdReader_1.readUint8)(reader);
7715
8032
  const buffer = (0, psdReader_1.readBytes)(reader, length);
8033
+ return decodeEncodedString(reader, buffer);
8034
+ }
8035
+ function decodeEncodedString(reader, buffer) {
7716
8036
  let notAscii = false;
7717
8037
  for (let i = 0; i < buffer.byteLength; i++) {
7718
8038
  if (buffer[i] & 0x80) {
@@ -7721,17 +8041,20 @@ function readEncodedString(reader) {
7721
8041
  }
7722
8042
  }
7723
8043
  if (notAscii) {
7724
- const decoder = getGbkDecoder();
7725
- if (decoder) {
7726
- try {
7727
- return decoder.decode(buffer);
8044
+ // encoding is ambiguous, many GBK strings are also valid UTF-8 and vice versa, GBK is preferred,
8045
+ // UTF-8 is only used if the string is not valid GBK
8046
+ for (const decoder of [(0, psdReader_1.getGbkStrictDecoder)(reader), (0, psdReader_1.getUtf8StrictDecoder)(reader)]) {
8047
+ if (decoder) {
8048
+ try {
8049
+ return decoder.decode(buffer);
8050
+ }
8051
+ catch (_a) { }
7728
8052
  }
7729
- catch (_a) { }
7730
8053
  }
7731
8054
  }
7732
- return (0, utf8_1.decodeString)(buffer);
8055
+ return (0, psdReader_1.decodeUtf8)(reader, buffer);
7733
8056
  }
7734
- function writeEncodedString(writer, value) {
8057
+ function toShortAsciiString(value) {
7735
8058
  let ascii = '';
7736
8059
  for (let i = 0; i < value.length;) {
7737
8060
  const code = value.codePointAt(i);
@@ -7739,19 +8062,19 @@ function writeEncodedString(writer, value) {
7739
8062
  i += code > 0xffff ? 2 : 1; // skip low surrogate of astral characters
7740
8063
  }
7741
8064
  // length is stored in a single byte
7742
- if (ascii.length > 255)
7743
- ascii = ascii.substring(0, 255);
7744
- const buffer = (0, utf8_1.encodeString)(ascii);
8065
+ return ascii.length > 255 ? ascii.substring(0, 255) : ascii;
8066
+ }
8067
+ function writeEncodedString(writer, value) {
8068
+ const buffer = (0, utf8_1.encodeString)(toShortAsciiString(value));
7745
8069
  (0, psdWriter_1.writeUint8)(writer, buffer.byteLength);
7746
8070
  (0, psdWriter_1.writeBytes)(writer, buffer);
7747
8071
  }
7748
- helpers_1.MOCK_HANDLERS && addHandler(1028, // IPTC-NAA record
8072
+ addHandler(1028, // IPTC-NAA record
7749
8073
  // IPTC-NAA record
7750
- target => target._ir1028 !== undefined, (reader, target, left) => {
7751
- LOG_MOCK_HANDLERS && console.log('image resource 1028', left());
7752
- target._ir1028 = (0, psdReader_1.readBytes)(reader, left());
8074
+ target => target.iptcData !== undefined, (reader, target, left) => {
8075
+ target.iptcData = (0, psdReader_1.readBytes)(reader, left());
7753
8076
  }, (writer, target) => {
7754
- (0, psdWriter_1.writeBytes)(writer, target._ir1028);
8077
+ (0, psdWriter_1.writeBytes)(writer, target.iptcData);
7755
8078
  });
7756
8079
  addHandler(1061, target => target.captionDigest !== undefined, (reader, target) => {
7757
8080
  let captionDigest = '';
@@ -7777,12 +8100,12 @@ const Inte = (0, helpers_1.createEnum)('Inte', 'perceptual', {
7777
8100
  'relative colorimetric': 'Clrm',
7778
8101
  'absolute colorimetric': 'AClr',
7779
8102
  });
7780
- helpers_1.MOCK_HANDLERS && addHandler(1085, // Windows DEVMODE. Variable OS specific info for Windows.
8103
+ addHandler(1085, // Windows DEVMODE. Variable OS specific info for Windows.
7781
8104
  // Windows DEVMODE. Variable OS specific info for Windows.
7782
- target => target._ir1085 !== undefined, (reader, target, left) => {
7783
- target._ir1085 = (0, psdReader_1.readBytes)(reader, left());
8105
+ target => target.windowsDevMode !== undefined, (reader, target, left) => {
8106
+ target.windowsDevMode = (0, psdReader_1.readBytes)(reader, left());
7784
8107
  }, (writer, target) => {
7785
- (0, psdWriter_1.writeBytes)(writer, target._ir1085);
8108
+ (0, psdWriter_1.writeBytes)(writer, target.windowsDevMode);
7786
8109
  });
7787
8110
  addHandler(1082, target => target.printInformation !== undefined, (reader, target) => {
7788
8111
  var _a, _b;
@@ -7794,6 +8117,8 @@ addHandler(1082, target => target.printInformation !== undefined, (reader, targe
7794
8117
  const info = target.printInformation;
7795
8118
  if (desc.PstS !== undefined)
7796
8119
  info.printerManagesColors = desc.PstS;
8120
+ if (desc.ClrS === 'ClrS.Grys')
8121
+ info.printerProfileColorSpace = 'grayscale';
7797
8122
  if (desc['Nm '] !== undefined)
7798
8123
  info.printerProfile = desc['Nm '];
7799
8124
  if (desc.MpBl !== undefined)
@@ -7816,7 +8141,6 @@ addHandler(1082, target => target.printInformation !== undefined, (reader, targe
7816
8141
  }
7817
8142
  }
7818
8143
  }, (writer, target) => {
7819
- var _a, _b;
7820
8144
  const info = target.printInformation;
7821
8145
  const desc = {};
7822
8146
  if (info.printerManagesColors) {
@@ -7825,11 +8149,12 @@ addHandler(1082, target => target.printInformation !== undefined, (reader, targe
7825
8149
  else {
7826
8150
  if (info.hardProof !== undefined)
7827
8151
  desc.hardProof = !!info.hardProof;
7828
- desc.ClrS = 'ClrS.RGBC'; // TODO: ???
7829
- desc['Nm '] = (_a = info.printerProfile) !== null && _a !== void 0 ? _a : 'CIE RGB';
8152
+ desc.ClrS = info.printerProfileColorSpace === 'grayscale' ? 'ClrS.Grys' : 'ClrS.RGBC';
8153
+ if (info.printerProfile !== undefined)
8154
+ desc['Nm '] = info.printerProfile;
7830
8155
  }
7831
8156
  desc.Inte = Inte.encode(info.renderingIntent);
7832
- if (!info.printerManagesColors)
8157
+ if (!info.printerManagesColors && info.blackPointCompensation !== undefined)
7833
8158
  desc.MpBl = !!info.blackPointCompensation;
7834
8159
  desc.printSixteenBit = !!info.printSixteenBit;
7835
8160
  desc.printerName = info.printerName || '';
@@ -7841,23 +8166,77 @@ addHandler(1082, target => target.printInformation !== undefined, (reader, targe
7841
8166
  paperWhite: !!info.proofSetup.paperWhite,
7842
8167
  };
7843
8168
  }
7844
- else {
8169
+ else if (info.proofSetup) {
7845
8170
  desc.printProofSetup = {
7846
- Bltn: ((_b = info.proofSetup) === null || _b === void 0 ? void 0 : _b.builtin) ? `builtinProof.${info.proofSetup.builtin}` : 'builtinProof.proofCMYK',
8171
+ Bltn: `builtinProof.${info.proofSetup.builtin || 'proofCMYK'}`,
7847
8172
  };
7848
8173
  }
7849
8174
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'printOutput', desc);
7850
8175
  });
7851
- helpers_1.MOCK_HANDLERS && addHandler(1083, // Print style
8176
+ addHandler(1083, // Print style
7852
8177
  // Print style
7853
- target => target._ir1083 !== undefined, (reader, target, left) => {
7854
- LOG_MOCK_HANDLERS && console.log('image resource 1083', left());
7855
- target._ir1083 = (0, psdReader_1.readBytes)(reader, left());
7856
- // TODO:
7857
- // const desc = readVersionAndDescriptor(reader);
7858
- // console.log('1083', require('util').inspect(desc, false, 99, true));
8178
+ target => target.printStyle !== undefined, (reader, target) => {
8179
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
8180
+ target.printStyle = {
8181
+ caption: desc.Cptn,
8182
+ calibrationBars: desc.Clbr,
8183
+ registrationMarks: desc.RgsM,
8184
+ cornerCropMarks: desc.CrnC,
8185
+ centerCropMarks: desc.CntC,
8186
+ labels: desc.Lbls,
8187
+ negative: desc.Ngtv,
8188
+ emulsionDown: desc.EmlD,
8189
+ interpolate: desc.Intr,
8190
+ backgroundColor: (0, descriptor_1.parseColor)(desc.Bckg),
8191
+ border: (0, descriptor_1.parseUnits)(desc.BrdT),
8192
+ bleed: (0, descriptor_1.parseUnits)(desc['Bld ']),
8193
+ resolution: (0, descriptor_1.parseUnits)(desc.Rslt),
8194
+ vectorData: desc.vectorData,
8195
+ pagePosition: desc.PgPs === 'PgPs.PgPC' ? 'centered' : desc.PgPs,
8196
+ left: (0, descriptor_1.parseUnits)(desc.Left),
8197
+ top: (0, descriptor_1.parseUnits)(desc['Top ']),
8198
+ scale: (0, descriptor_1.parsePercent)(desc['Scl ']),
8199
+ };
8200
+ if (desc.cropWhenPrinting !== undefined) {
8201
+ target.printStyle.cropWhenPrinting = desc.cropWhenPrinting;
8202
+ target.printStyle.cropRect = {
8203
+ top: desc.cropRectTop || 0,
8204
+ left: desc.cropRectLeft || 0,
8205
+ bottom: desc.cropRectBottom || 0,
8206
+ right: desc.cropRectRight || 0,
8207
+ };
8208
+ }
7859
8209
  }, (writer, target) => {
7860
- (0, psdWriter_1.writeBytes)(writer, target._ir1083);
8210
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
8211
+ const style = target.printStyle;
8212
+ const desc = {
8213
+ Cptn: !!style.caption,
8214
+ Clbr: !!style.calibrationBars,
8215
+ RgsM: !!style.registrationMarks,
8216
+ CrnC: !!style.cornerCropMarks,
8217
+ CntC: !!style.centerCropMarks,
8218
+ Lbls: !!style.labels,
8219
+ Ngtv: !!style.negative,
8220
+ EmlD: !!style.emulsionDown,
8221
+ Intr: !!style.interpolate,
8222
+ Bckg: (0, descriptor_1.serializeColor)(style.backgroundColor || { r: 255, g: 255, b: 255 }),
8223
+ BrdT: (0, descriptor_1.unitsValue)(style.border || { units: 'Distance', value: 0 }, 'border'),
8224
+ 'Bld ': (0, descriptor_1.unitsValue)(style.bleed || { units: 'Distance', value: 0 }, 'bleed'),
8225
+ Rslt: (0, descriptor_1.unitsValue)(style.resolution || { units: 'Pixels', value: 72 }, 'resolution'),
8226
+ vectorData: (_a = style.vectorData) !== null && _a !== void 0 ? _a : true,
8227
+ PgPs: (!style.pagePosition || style.pagePosition === 'centered') ? 'PgPs.PgPC' : style.pagePosition,
8228
+ Left: (0, descriptor_1.unitsValue)(style.left || { units: 'Distance', value: 0 }, 'left'),
8229
+ 'Top ': (0, descriptor_1.unitsValue)(style.top || { units: 'Distance', value: 0 }, 'top'),
8230
+ 'Scl ': (0, descriptor_1.unitsPercentF)((_b = style.scale) !== null && _b !== void 0 ? _b : 1),
8231
+ };
8232
+ if (style.cropWhenPrinting !== undefined || style.cropRect) {
8233
+ desc.cropWhenPrinting = !!style.cropWhenPrinting;
8234
+ desc.cropRectBottom = (_d = (_c = style.cropRect) === null || _c === void 0 ? void 0 : _c.bottom) !== null && _d !== void 0 ? _d : 0;
8235
+ desc.cropRectLeft = (_f = (_e = style.cropRect) === null || _e === void 0 ? void 0 : _e.left) !== null && _f !== void 0 ? _f : 0;
8236
+ desc.cropRectRight = (_h = (_g = style.cropRect) === null || _g === void 0 ? void 0 : _g.right) !== null && _h !== void 0 ? _h : 0;
8237
+ desc.cropRectTop = (_k = (_j = style.cropRect) === null || _j === void 0 ? void 0 : _j.top) !== null && _k !== void 0 ? _k : 0;
8238
+ }
8239
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'printOutputOptions', desc);
7861
8240
  });
7862
8241
  addHandler(1005, target => target.resolutionInfo !== undefined, (reader, target) => {
7863
8242
  const horizontalResolution = (0, psdReader_1.readFixedPoint32)(reader);
@@ -7926,11 +8305,61 @@ addHandler(1045, target => target.alphaChannelNames !== undefined, (reader, targ
7926
8305
  (0, psdWriter_1.writeUnicodeStringWithPadding)(writer, name);
7927
8306
  }
7928
8307
  });
7929
- helpers_1.MOCK_HANDLERS && addHandler(1077, target => target._ir1077 !== undefined, (reader, target, left) => {
7930
- LOG_MOCK_HANDLERS && console.log('image resource 1077', left());
7931
- target._ir1077 = (0, psdReader_1.readBytes)(reader, left());
8308
+ const displayInfoKinds = ['selected', 'protected', 'spot'];
8309
+ const supportedColorSpaces = [0 /* ColorSpace.RGB */, 1 /* ColorSpace.HSB */, 2 /* ColorSpace.CMYK */, 7 /* ColorSpace.Lab */, 8 /* ColorSpace.Grayscale */];
8310
+ function readDisplayInfo(reader) {
8311
+ const info = { opacity: 0, kind: 'selected' };
8312
+ const colorSpace = reader.view.getUint16(reader.offset);
8313
+ if (supportedColorSpaces.indexOf(colorSpace) !== -1) {
8314
+ info.color = (0, psdReader_1.readColor)(reader);
8315
+ }
8316
+ else {
8317
+ // keep color in unknown color space as it is, so the channel info is not lost or misaligned
8318
+ (0, psdReader_1.skipBytes)(reader, 2);
8319
+ info.rawColor = { colorSpace, values: [(0, psdReader_1.readUint16)(reader), (0, psdReader_1.readUint16)(reader), (0, psdReader_1.readUint16)(reader), (0, psdReader_1.readUint16)(reader)] };
8320
+ }
8321
+ info.opacity = (0, psdReader_1.readUint16)(reader) / 100;
8322
+ const kind = displayInfoKinds[(0, psdReader_1.readUint8)(reader)];
8323
+ if (!kind)
8324
+ throw new Error('Invalid display info kind');
8325
+ info.kind = kind;
8326
+ return info;
8327
+ }
8328
+ addHandler(1007, // Display info (obsolete, replaced by 1077), written back as 1077
8329
+ () => false, (reader, target, left) => {
8330
+ const info = [];
8331
+ while (left() >= 14) {
8332
+ info.push(readDisplayInfo(reader));
8333
+ (0, psdReader_1.skipBytes)(reader, 1); // padding
8334
+ }
8335
+ // 1077 resource has more precise data and is used if present
8336
+ if (!target.alphaChannelDisplayInfo)
8337
+ target.alphaChannelDisplayInfo = info;
8338
+ }, () => { });
8339
+ addHandler(1077, // Display info (alpha and spot channels)
8340
+ // Display info (alpha and spot channels)
8341
+ target => target.alphaChannelDisplayInfo !== undefined, (reader, target, left) => {
8342
+ const version = (0, psdReader_1.readUint32)(reader);
8343
+ if (version !== 1)
8344
+ throw new Error(`Invalid display info version: ${version}`);
8345
+ target.alphaChannelDisplayInfo = [];
8346
+ while (left() >= 13) {
8347
+ target.alphaChannelDisplayInfo.push(readDisplayInfo(reader));
8348
+ }
7932
8349
  }, (writer, target) => {
7933
- (0, psdWriter_1.writeBytes)(writer, target._ir1077);
8350
+ (0, psdWriter_1.writeUint32)(writer, 1); // version
8351
+ for (const info of target.alphaChannelDisplayInfo) {
8352
+ if (!info.color && info.rawColor) {
8353
+ (0, psdWriter_1.writeUint16)(writer, info.rawColor.colorSpace);
8354
+ for (let i = 0; i < 4; i++)
8355
+ (0, psdWriter_1.writeUint16)(writer, info.rawColor.values[i] | 0);
8356
+ }
8357
+ else {
8358
+ (0, psdWriter_1.writeColor)(writer, info.color || { r: 255, g: 0, b: 0 });
8359
+ }
8360
+ (0, psdWriter_1.writeUint16)(writer, Math.round((0, helpers_1.clamp)(info.opacity, 0, 1) * 100));
8361
+ (0, psdWriter_1.writeUint8)(writer, Math.max(0, displayInfoKinds.indexOf(info.kind)));
8362
+ }
7934
8363
  });
7935
8364
  addHandler(1053, target => target.alphaIdentifiers !== undefined, (reader, target, left) => {
7936
8365
  target.alphaIdentifiers = [];
@@ -7983,30 +8412,101 @@ target => target.url !== undefined, (reader, target, left) => {
7983
8412
  }, (writer, target) => {
7984
8413
  (0, psdWriter_1.writeAsciiString)(writer, target.url);
7985
8414
  });
7986
- helpers_1.MOCK_HANDLERS && addHandler(10000, // Print flags
7987
- // Print flags
7988
- target => target._ir10000 !== undefined, (reader, target, left) => {
7989
- LOG_MOCK_HANDLERS && console.log('image resource 10000', left());
7990
- target._ir10000 = (0, psdReader_1.readBytes)(reader, left());
8415
+ addHandler(10000, // Print flags information
8416
+ // Print flags information
8417
+ target => target.printFlagsInfo !== undefined, (reader, target) => {
8418
+ const version = (0, psdReader_1.readUint16)(reader);
8419
+ if (version !== 1)
8420
+ throw new Error(`Invalid print flags information version: ${version}`);
8421
+ const centerCropMarks = !!(0, psdReader_1.readUint8)(reader);
8422
+ (0, psdReader_1.skipBytes)(reader, 1);
8423
+ const bleedWidth = (0, psdReader_1.readUint32)(reader);
8424
+ const bleedWidthScale = (0, psdReader_1.readUint16)(reader);
8425
+ target.printFlagsInfo = { centerCropMarks, bleedWidth, bleedWidthScale };
7991
8426
  }, (writer, target) => {
7992
- (0, psdWriter_1.writeBytes)(writer, target._ir10000);
8427
+ const info = target.printFlagsInfo;
8428
+ (0, psdWriter_1.writeUint16)(writer, 1); // version
8429
+ (0, psdWriter_1.writeUint8)(writer, info.centerCropMarks ? 1 : 0);
8430
+ (0, psdWriter_1.writeUint8)(writer, 0);
8431
+ (0, psdWriter_1.writeUint32)(writer, info.bleedWidth | 0);
8432
+ (0, psdWriter_1.writeUint16)(writer, info.bleedWidthScale | 0);
7993
8433
  });
7994
- helpers_1.MOCK_HANDLERS && addHandler(1013, // Color halftoning
7995
- // Color halftoning
7996
- target => target._ir1013 !== undefined, (reader, target, left) => {
7997
- LOG_MOCK_HANDLERS && console.log('image resource 1013', left());
7998
- target._ir1013 = (0, psdReader_1.readBytes)(reader, left());
8434
+ addHandler(1046, // Indexed color table count
8435
+ // Indexed color table count
8436
+ target => target.indexedColorCount !== undefined, (reader, target) => {
8437
+ target.indexedColorCount = (0, psdReader_1.readUint16)(reader);
7999
8438
  }, (writer, target) => {
8000
- (0, psdWriter_1.writeBytes)(writer, target._ir1013);
8439
+ (0, psdWriter_1.writeUint16)(writer, target.indexedColorCount);
8001
8440
  });
8002
- helpers_1.MOCK_HANDLERS && addHandler(1016, // Color transfer functions
8003
- // Color transfer functions
8004
- target => target._ir1016 !== undefined, (reader, target, left) => {
8005
- LOG_MOCK_HANDLERS && console.log('image resource 1016', left());
8006
- target._ir1016 = (0, psdReader_1.readBytes)(reader, left());
8441
+ addHandler(1047, // Transparency index
8442
+ // Transparency index
8443
+ target => target.transparencyIndex !== undefined, (reader, target) => {
8444
+ target.transparencyIndex = (0, psdReader_1.readUint16)(reader);
8007
8445
  }, (writer, target) => {
8008
- (0, psdWriter_1.writeBytes)(writer, target._ir1016);
8446
+ (0, psdWriter_1.writeUint16)(writer, target.transparencyIndex);
8009
8447
  });
8448
+ const halftoneFrequencyUnits = ['lines/inch', 'lines/cm']; // 1, 2
8449
+ const halftoneShapes = ['round', 'ellipse', 'line', 'square', 'cross', undefined, 'diamond']; // 0 - 6
8450
+ function readHalftoneScreens(reader, left) {
8451
+ const screens = [];
8452
+ while (left() >= 18) {
8453
+ const frequency = (0, psdReader_1.readFixedPoint32)(reader);
8454
+ const frequencyUnits = halftoneFrequencyUnits[(0, psdReader_1.readUint16)(reader) - 1];
8455
+ if (!frequencyUnits)
8456
+ throw new Error('Invalid halftone screen frequency units');
8457
+ const angle = (0, psdReader_1.readFixedPoint32)(reader);
8458
+ const shape = halftoneShapes[(0, psdReader_1.readInt16)(reader)];
8459
+ if (!shape)
8460
+ throw new Error('Invalid halftone screen shape');
8461
+ (0, psdReader_1.skipBytes)(reader, 4);
8462
+ const accurateScreens = !!(0, psdReader_1.readUint8)(reader);
8463
+ const printerDefaultScreens = !!(0, psdReader_1.readUint8)(reader);
8464
+ screens.push({ frequency, frequencyUnits, angle, shape, accurateScreens, printerDefaultScreens });
8465
+ }
8466
+ return screens;
8467
+ }
8468
+ function writeHalftoneScreens(writer, screens) {
8469
+ for (const screen of screens) {
8470
+ (0, psdWriter_1.writeFixedPoint32)(writer, screen.frequency);
8471
+ (0, psdWriter_1.writeUint16)(writer, Math.max(0, halftoneFrequencyUnits.indexOf(screen.frequencyUnits)) + 1);
8472
+ (0, psdWriter_1.writeFixedPoint32)(writer, screen.angle);
8473
+ (0, psdWriter_1.writeInt16)(writer, Math.max(0, halftoneShapes.indexOf(screen.shape)));
8474
+ (0, psdWriter_1.writeZeros)(writer, 4);
8475
+ (0, psdWriter_1.writeUint8)(writer, screen.accurateScreens ? 1 : 0);
8476
+ (0, psdWriter_1.writeUint8)(writer, screen.printerDefaultScreens ? 1 : 0);
8477
+ }
8478
+ }
8479
+ addHandler(1012, // Grayscale and multichannel halftoning information
8480
+ // Grayscale and multichannel halftoning information
8481
+ target => target.grayscaleHalftoneScreens !== undefined, (reader, target, left) => target.grayscaleHalftoneScreens = readHalftoneScreens(reader, left), (writer, target) => writeHalftoneScreens(writer, target.grayscaleHalftoneScreens));
8482
+ addHandler(1013, // Color halftoning information
8483
+ // Color halftoning information
8484
+ target => target.colorHalftoneScreens !== undefined, (reader, target, left) => target.colorHalftoneScreens = readHalftoneScreens(reader, left), (writer, target) => writeHalftoneScreens(writer, target.colorHalftoneScreens));
8485
+ function readTransferFunctions(reader, left) {
8486
+ const functions = [];
8487
+ while (left() >= 28) {
8488
+ const curve = [];
8489
+ for (let i = 0; i < 13; i++)
8490
+ curve.push((0, psdReader_1.readInt16)(reader));
8491
+ const override = !!(0, psdReader_1.readUint16)(reader);
8492
+ functions.push({ curve, override });
8493
+ }
8494
+ return functions;
8495
+ }
8496
+ function writeTransferFunctions(writer, functions) {
8497
+ var _a;
8498
+ for (const f of functions) {
8499
+ for (let i = 0; i < 13; i++)
8500
+ (0, psdWriter_1.writeInt16)(writer, (_a = f.curve[i]) !== null && _a !== void 0 ? _a : -1);
8501
+ (0, psdWriter_1.writeUint16)(writer, f.override ? 1 : 0);
8502
+ }
8503
+ }
8504
+ addHandler(1015, // Grayscale and multichannel transfer function
8505
+ // Grayscale and multichannel transfer function
8506
+ target => target.grayscaleTransferFunctions !== undefined, (reader, target, left) => target.grayscaleTransferFunctions = readTransferFunctions(reader, left), (writer, target) => writeTransferFunctions(writer, target.grayscaleTransferFunctions));
8507
+ addHandler(1016, // Color transfer functions
8508
+ // Color transfer functions
8509
+ target => target.colorTransferFunctions !== undefined, (reader, target, left) => target.colorTransferFunctions = readTransferFunctions(reader, left), (writer, target) => writeTransferFunctions(writer, target.colorTransferFunctions));
8010
8510
  addHandler(1080, // Count Information
8011
8511
  // Count Information
8012
8512
  target => target.countInformation !== undefined, (reader, target) => {
@@ -8069,22 +8569,27 @@ addHandler(1069, target => target.layerSelectionIds !== undefined, (reader, targ
8069
8569
  }
8070
8570
  });
8071
8571
  addHandler(1032, target => target.gridAndGuidesInformation !== undefined, (reader, target) => {
8572
+ var _a;
8072
8573
  const version = (0, psdReader_1.readUint32)(reader);
8073
8574
  const horizontal = (0, psdReader_1.readUint32)(reader);
8074
8575
  const vertical = (0, psdReader_1.readUint32)(reader);
8075
8576
  const count = (0, psdReader_1.readUint32)(reader);
8076
8577
  if (version !== 1)
8077
8578
  throw new Error(`Invalid 1032 resource version: ${version}`);
8078
- target.gridAndGuidesInformation = {
8079
- grid: { horizontal, vertical },
8080
- guides: [],
8081
- };
8579
+ const previous = (_a = target.gridAndGuidesInformation) === null || _a === void 0 ? void 0 : _a.guides; // in case guides were already read from 1092
8580
+ const guides = [];
8082
8581
  for (let i = 0; i < count; i++) {
8083
- target.gridAndGuidesInformation.guides.push({
8582
+ const guide = {
8084
8583
  location: (0, psdReader_1.readInt32)(reader) / 32,
8085
8584
  direction: (0, psdReader_1.readUint8)(reader) ? 'horizontal' : 'vertical'
8086
- });
8585
+ };
8586
+ if ((previous === null || previous === void 0 ? void 0 : previous.length) === count && previous[i].color)
8587
+ guide.color = previous[i].color;
8588
+ if ((previous === null || previous === void 0 ? void 0 : previous.length) === count && previous[i].id)
8589
+ guide.id = previous[i].id;
8590
+ guides.push(guide);
8087
8591
  }
8592
+ target.gridAndGuidesInformation = { grid: { horizontal, vertical }, guides };
8088
8593
  }, (writer, target) => {
8089
8594
  const info = target.gridAndGuidesInformation;
8090
8595
  const grid = info.grid || { horizontal: 18 * 32, vertical: 18 * 32 };
@@ -8098,6 +8603,76 @@ addHandler(1032, target => target.gridAndGuidesInformation !== undefined, (reade
8098
8603
  (0, psdWriter_1.writeUint8)(writer, g.direction === 'horizontal' ? 1 : 0);
8099
8604
  }
8100
8605
  });
8606
+ const defaultGuideColor = { r: 74, g: 255, b: 255 }; // Photoshop default (cyan)
8607
+ addHandler(1092, // Grid and guides information with guide colors (same as 1032 with color of each guide)
8608
+ // Grid and guides information with guide colors (same as 1032 with color of each guide)
8609
+ target => target.gridAndGuidesInformation !== undefined, (reader, target) => {
8610
+ var _a;
8611
+ const version = (0, psdReader_1.readUint32)(reader);
8612
+ if (version !== 2)
8613
+ throw new Error(`Invalid 1092 resource version: ${version}`);
8614
+ const horizontal = (0, psdReader_1.readUint32)(reader);
8615
+ const vertical = (0, psdReader_1.readUint32)(reader);
8616
+ const count = (0, psdReader_1.readUint32)(reader);
8617
+ const guides = [];
8618
+ for (let i = 0; i < count; i++) {
8619
+ const location = (0, psdReader_1.readInt32)(reader) / 32;
8620
+ const direction = (0, psdReader_1.readUint8)(reader) ? 'horizontal' : 'vertical';
8621
+ (0, psdReader_1.skipBytes)(reader, 1);
8622
+ const color = { r: (0, psdReader_1.readUint8)(reader), g: (0, psdReader_1.readUint8)(reader), b: (0, psdReader_1.readUint8)(reader) };
8623
+ guides.push({ location, direction, color });
8624
+ }
8625
+ const existing = (_a = target.gridAndGuidesInformation) === null || _a === void 0 ? void 0 : _a.guides;
8626
+ if (existing && existing.length === count) {
8627
+ existing.forEach((guide, i) => guide.color = guides[i].color);
8628
+ }
8629
+ else {
8630
+ target.gridAndGuidesInformation = { grid: { horizontal, vertical }, guides };
8631
+ }
8632
+ }, (writer, target) => {
8633
+ const info = target.gridAndGuidesInformation;
8634
+ const grid = info.grid || { horizontal: 18 * 32, vertical: 18 * 32 };
8635
+ const guides = info.guides || [];
8636
+ (0, psdWriter_1.writeUint32)(writer, 2); // version
8637
+ (0, psdWriter_1.writeUint32)(writer, Math.round(grid.horizontal));
8638
+ (0, psdWriter_1.writeUint32)(writer, Math.round(grid.vertical));
8639
+ (0, psdWriter_1.writeUint32)(writer, guides.length);
8640
+ for (const g of guides) {
8641
+ const color = g.color || defaultGuideColor;
8642
+ (0, psdWriter_1.writeInt32)(writer, Math.round(g.location * 32));
8643
+ (0, psdWriter_1.writeUint8)(writer, g.direction === 'horizontal' ? 1 : 0);
8644
+ (0, psdWriter_1.writeUint8)(writer, 0);
8645
+ (0, psdWriter_1.writeUint8)(writer, Math.round(color.r));
8646
+ (0, psdWriter_1.writeUint8)(writer, Math.round(color.g));
8647
+ (0, psdWriter_1.writeUint8)(writer, Math.round(color.b));
8648
+ }
8649
+ });
8650
+ const emptyGuid = '00000000-0000-0000-0000-000000000000';
8651
+ addHandler(1097, // Guide ids
8652
+ // Guide ids
8653
+ target => !!target.gridAndGuidesInformation && (target.gridAndGuidesInformation.guides || []).every(g => g.id), (reader, target) => {
8654
+ var _a;
8655
+ const count = (0, psdReader_1.readUint32)(reader);
8656
+ const ids = [];
8657
+ for (let i = 0; i < count; i++) {
8658
+ ids.push((0, psdReader_1.readAsciiString)(reader, (0, psdReader_1.readUint32)(reader)));
8659
+ (0, psdReader_1.readUint32)(reader); // unknown, always 0
8660
+ (0, psdReader_1.readAsciiString)(reader, (0, psdReader_1.readUint32)(reader)); // unknown, always empty guid
8661
+ }
8662
+ const guides = (_a = target.gridAndGuidesInformation) === null || _a === void 0 ? void 0 : _a.guides;
8663
+ if (guides && guides.length === count)
8664
+ guides.forEach((guide, i) => guide.id = ids[i]);
8665
+ }, (writer, target) => {
8666
+ const guides = target.gridAndGuidesInformation.guides || [];
8667
+ (0, psdWriter_1.writeUint32)(writer, guides.length);
8668
+ for (const g of guides) {
8669
+ (0, psdWriter_1.writeUint32)(writer, g.id.length);
8670
+ (0, psdWriter_1.writeAsciiString)(writer, g.id);
8671
+ (0, psdWriter_1.writeUint32)(writer, 0);
8672
+ (0, psdWriter_1.writeUint32)(writer, emptyGuid.length);
8673
+ (0, psdWriter_1.writeAsciiString)(writer, emptyGuid);
8674
+ }
8675
+ });
8101
8676
  addHandler(1065, // Layer Comps
8102
8677
  // Layer Comps
8103
8678
  target => target.layerComps !== undefined, (reader, target) => {
@@ -8478,27 +9053,78 @@ addHandler(1041, target => target.iccUntaggedProfile !== undefined, (reader, tar
8478
9053
  }, (writer, target) => {
8479
9054
  (0, psdWriter_1.writeUint8)(writer, target.iccUntaggedProfile ? 1 : 0);
8480
9055
  });
8481
- helpers_1.MOCK_HANDLERS && addHandler(1039, // ICC Profile
9056
+ addHandler(1039, // ICC Profile
8482
9057
  // ICC Profile
8483
- target => target._ir1039 !== undefined, (reader, target, left) => {
8484
- // TODO: this is raw bytes, just return as a byte array
8485
- LOG_MOCK_HANDLERS && console.log('image resource 1039', left());
8486
- target._ir1039 = (0, psdReader_1.readBytes)(reader, left());
9058
+ target => target.iccProfile !== undefined, (reader, target, left) => {
9059
+ target.iccProfile = (0, psdReader_1.readBytes)(reader, left());
8487
9060
  }, (writer, target) => {
8488
- (0, psdWriter_1.writeBytes)(writer, target._ir1039);
9061
+ (0, psdWriter_1.writeBytes)(writer, target.iccProfile);
8489
9062
  });
8490
9063
  addHandler(1044, target => target.idsSeedNumber !== undefined, (reader, target) => target.idsSeedNumber = (0, psdReader_1.readUint32)(reader), (writer, target) => (0, psdWriter_1.writeUint32)(writer, target.idsSeedNumber));
9064
+ addHandler(1038, // Color samplers (Photoshop 5.0, replaced by 1073 in CS3)
9065
+ // Color samplers (Photoshop 5.0, replaced by 1073 in CS3)
9066
+ target => target.colorSamplers !== undefined, (reader, target, left) => {
9067
+ const version = (0, psdReader_1.readUint32)(reader);
9068
+ if (version !== 1)
9069
+ throw new Error(`Invalid color samplers version: ${version}`);
9070
+ const count = (0, psdReader_1.readUint32)(reader);
9071
+ target.colorSamplers = [];
9072
+ for (let i = 0; i < count && left() >= 10; i++) {
9073
+ // vertical and horizontal position in 1/16 pixel (inferred from sample files, the specification describes
9074
+ // these values as horizontal and vertical fixed point numbers)
9075
+ const y = (0, psdReader_1.readInt32)(reader) / 16;
9076
+ const x = (0, psdReader_1.readInt32)(reader) / 16;
9077
+ const colorSpace = (0, psdReader_1.readInt16)(reader);
9078
+ target.colorSamplers.push({ x, y, colorSpace });
9079
+ }
9080
+ }, (writer, target) => {
9081
+ (0, psdWriter_1.writeUint32)(writer, 1); // version
9082
+ (0, psdWriter_1.writeUint32)(writer, target.colorSamplers.length);
9083
+ for (const sampler of target.colorSamplers) {
9084
+ (0, psdWriter_1.writeInt32)(writer, Math.round(sampler.y * 16));
9085
+ (0, psdWriter_1.writeInt32)(writer, Math.round(sampler.x * 16));
9086
+ (0, psdWriter_1.writeInt16)(writer, sampler.colorSpace);
9087
+ }
9088
+ });
9089
+ addHandler(1042, // Effects visible (only present when effects are hidden)
9090
+ // Effects visible (only present when effects are hidden)
9091
+ target => target.effectsVisible !== undefined, (reader, target) => {
9092
+ target.effectsVisible = !!(0, psdReader_1.readUint8)(reader);
9093
+ }, (writer, target) => {
9094
+ (0, psdWriter_1.writeUint8)(writer, target.effectsVisible ? 1 : 0);
9095
+ });
8491
9096
  addHandler(1036, target => target.thumbnail !== undefined || target.thumbnailRaw !== undefined, (reader, target, left) => {
8492
9097
  const format = (0, psdReader_1.readUint32)(reader); // 1 = kJpegRGB, 0 = kRawRGB
8493
9098
  const width = (0, psdReader_1.readUint32)(reader);
8494
9099
  const height = (0, psdReader_1.readUint32)(reader);
8495
- (0, psdReader_1.readUint32)(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
9100
+ const widthBytes = (0, psdReader_1.readUint32)(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
8496
9101
  (0, psdReader_1.readUint32)(reader); // totalSize = widthBytes * height * planes
8497
9102
  (0, psdReader_1.readUint32)(reader); // sizeAfterCompression
8498
9103
  const bitsPerPixel = (0, psdReader_1.readUint16)(reader); // 24
8499
- const planes = (0, psdReader_1.readUint16)(reader); // 1
8500
- if (format !== 1 || bitsPerPixel !== 24 || planes !== 1) {
8501
- reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel}, planes: ${planes})`);
9104
+ (0, psdReader_1.readUint16)(reader); // planes, should be 1 (some applications write 3)
9105
+ if (format === 0 && bitsPerPixel === 24 && widthBytes >= width * 3 && widthBytes * height <= left()) {
9106
+ // raw RGB data, rows are padded to 4 bytes
9107
+ if (reader.useRawThumbnail) {
9108
+ reader.logMissingFeatures && reader.log('Raw thumbnail data is not supported with useRawThumbnail option');
9109
+ (0, psdReader_1.skipBytes)(reader, left());
9110
+ return;
9111
+ }
9112
+ const data = (0, psdReader_1.readBytes)(reader, widthBytes * height);
9113
+ const imageData = (0, helpers_1.createImageData)(width, height);
9114
+ for (let y = 0, dst = 0; y < height; y++) {
9115
+ for (let x = 0, src = y * widthBytes; x < width; x++, src += 3, dst += 4) {
9116
+ imageData.data[dst + 0] = data[src + 0];
9117
+ imageData.data[dst + 1] = data[src + 1];
9118
+ imageData.data[dst + 2] = data[src + 2];
9119
+ imageData.data[dst + 3] = 255;
9120
+ }
9121
+ }
9122
+ target.thumbnail = (0, helpers_1.imageDataToCanvas)(imageData);
9123
+ (0, psdReader_1.skipBytes)(reader, left());
9124
+ return;
9125
+ }
9126
+ if (format !== 1 || bitsPerPixel !== 24) {
9127
+ reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel})`);
8502
9128
  (0, psdReader_1.skipBytes)(reader, left());
8503
9129
  return;
8504
9130
  }
@@ -8565,13 +9191,12 @@ addHandler(1057, target => target.versionInfo !== undefined, (reader, target, le
8565
9191
  (0, psdWriter_1.writeUnicodeString)(writer, versionInfo.readerName);
8566
9192
  (0, psdWriter_1.writeUint32)(writer, versionInfo.fileVersion);
8567
9193
  });
8568
- helpers_1.MOCK_HANDLERS && addHandler(1058, // EXIF data 1.
8569
- // EXIF data 1.
8570
- target => target._ir1058 !== undefined, (reader, target, left) => {
8571
- LOG_MOCK_HANDLERS && console.log('image resource 1058', left());
8572
- target._ir1058 = (0, psdReader_1.readBytes)(reader, left());
9194
+ addHandler(1058, // EXIF data 1
9195
+ // EXIF data 1
9196
+ target => target.exifData !== undefined, (reader, target, left) => {
9197
+ target.exifData = (0, psdReader_1.readBytes)(reader, left());
8573
9198
  }, (writer, target) => {
8574
- (0, psdWriter_1.writeBytes)(writer, target._ir1058);
9199
+ (0, psdWriter_1.writeBytes)(writer, target.exifData);
8575
9200
  });
8576
9201
  addHandler(7000, target => target.imageReadyVariables !== undefined, (reader, target, left) => {
8577
9202
  target.imageReadyVariables = readUtf8String(reader, left());
@@ -8590,22 +9215,101 @@ addHandler(1088, target => target.pathSelectionState !== undefined, (reader, tar
8590
9215
  const desc = { 'null': (0, descriptor_1.pathSelectionStateToReference)(target.pathSelectionState) };
8591
9216
  (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
8592
9217
  });
8593
- helpers_1.MOCK_HANDLERS && addHandler(1025, target => target._ir1025 !== undefined, (reader, target, left) => {
8594
- LOG_MOCK_HANDLERS && console.log('image resource 1025', left());
8595
- target._ir1025 = (0, psdReader_1.readBytes)(reader, left());
9218
+ addHandler(1025, // Work path
9219
+ // Work path
9220
+ target => target.workPath !== undefined, (reader, target, left, { width, height }) => {
9221
+ target.workPath = { paths: [] };
9222
+ (0, additionalInfo_1.readVectorMask)(reader, target.workPath, width, height, left());
9223
+ (0, psdReader_1.skipBytes)(reader, left());
9224
+ }, (writer, target, _index, { width, height }) => {
9225
+ (0, additionalInfo_1.writeVectorMask)(writer, target.workPath, width, height);
9226
+ });
9227
+ const MAX_SAVED_PATHS = 999; // ids 2000-2998
9228
+ addHandler(2000, // Saved paths (one resource for each path, named after the path)
9229
+ // Saved paths (one resource for each path, named after the path)
9230
+ target => target.savedPaths ? Math.min(target.savedPaths.length, MAX_SAVED_PATHS) : 0, (reader, target, left, { signature, id, name, width, height }) => {
9231
+ var _a, _b;
9232
+ if (signature !== '8BIM') { // ImageReady ('MeSa') resources use the same ids for other data
9233
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${signature} ${id}`);
9234
+ (0, psdReader_1.skipBytes)(reader, left());
9235
+ return;
9236
+ }
9237
+ const savedPaths = (_a = target.savedPaths) !== null && _a !== void 0 ? _a : (target.savedPaths = []);
9238
+ const path = { name: decodeEncodedString(reader, name), paths: [] };
9239
+ (0, additionalInfo_1.readVectorMask)(reader, path, width, height, left());
9240
+ (0, psdReader_1.skipBytes)(reader, left());
9241
+ const keyDescriptorList = (_b = target.savedPathsOrigination) === null || _b === void 0 ? void 0 : _b[savedPaths.length];
9242
+ if (keyDescriptorList)
9243
+ path.vectorOrigination = { keyDescriptorList };
9244
+ savedPaths.push(path);
9245
+ }, (writer, target, index, { width, height }) => {
9246
+ (0, additionalInfo_1.writeVectorMask)(writer, target.savedPaths[index], width, height);
9247
+ }, { keyCount: MAX_SAVED_PATHS, name: (target, index) => toShortAsciiString(target.savedPaths[index].name) });
9248
+ addHandler(3000, // Origin path info (origination data of saved paths)
9249
+ // Origin path info (origination data of saved paths)
9250
+ target => { var _a; return !!((_a = target.savedPaths) === null || _a === void 0 ? void 0 : _a.some(path => path.vectorOrigination)); }, (reader, target, left) => {
9251
+ const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
9252
+ desc.keyRootDescriptorList.forEach((item, i) => {
9253
+ var _a, _b;
9254
+ const keyDescriptorList = (0, additionalInfo_1.parseKeyDescriptorList)(item);
9255
+ if (!keyDescriptorList.length)
9256
+ return; // written for paths without origination data
9257
+ const path = (_a = target.savedPaths) === null || _a === void 0 ? void 0 : _a[i];
9258
+ if (path) {
9259
+ path.vectorOrigination = { keyDescriptorList };
9260
+ }
9261
+ else { // saved paths are stored after this resource
9262
+ ((_b = target.savedPathsOrigination) !== null && _b !== void 0 ? _b : (target.savedPathsOrigination = []))[i] = keyDescriptorList;
9263
+ }
9264
+ });
9265
+ (0, psdReader_1.skipBytes)(reader, left());
9266
+ }, (writer, target) => {
9267
+ const desc = {
9268
+ keyRootDescriptorList: target.savedPaths.slice(0, MAX_SAVED_PATHS)
9269
+ .map(path => { var _a, _b; return (0, additionalInfo_1.serializeKeyDescriptorList)((_b = (_a = path.vectorOrigination) === null || _a === void 0 ? void 0 : _a.keyDescriptorList) !== null && _b !== void 0 ? _b : []); }),
9270
+ };
9271
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', desc);
9272
+ }, { name: () => 'OriginDataIRB' });
9273
+ addHandler(2999, // Clipping path
9274
+ // Clipping path
9275
+ target => target.clippingPath !== undefined, (reader, target, left) => {
9276
+ const name = readEncodedString(reader);
9277
+ const flatness = (0, psdReader_1.readFixedPoint32)(reader);
9278
+ (0, psdReader_1.skipBytes)(reader, left()); // fill rule (ignored by Photoshop)
9279
+ target.clippingPath = { name, flatness };
8596
9280
  }, (writer, target) => {
8597
- (0, psdWriter_1.writeBytes)(writer, target._ir1025);
9281
+ writeEncodedString(writer, target.clippingPath.name);
9282
+ (0, psdWriter_1.writeFixedPoint32)(writer, target.clippingPath.flatness);
9283
+ (0, psdWriter_1.writeUint16)(writer, 1); // fill rule (ignored by Photoshop)
8598
9284
  });
8599
9285
  const FrmD = (0, helpers_1.createEnum)('FrmD', 'auto', {
8600
9286
  auto: 'Auto',
8601
9287
  none: 'None',
8602
9288
  dispose: 'Disp',
8603
9289
  });
8604
- addHandler(4000, // Plug-In resource(s)
8605
- // Plug-In resource(s)
8606
- target => target.animations !== undefined, (reader, target, left) => {
9290
+ // plug-in resources are written with consecutive ids starting at 4000, in the same order as Photoshop writes them
9291
+ function pluginResourceKeys(target) {
9292
+ const keys = [];
9293
+ if (target.outputSettings !== undefined)
9294
+ keys.push('mset');
9295
+ if (target.animations !== undefined)
9296
+ keys.push('mani', 'mfri');
9297
+ return keys;
9298
+ }
9299
+ addHandler(4000, // Plug-In resource(s), identified by key stored in the data (ids 4000-4999)
9300
+ // Plug-In resource(s), identified by key stored in the data (ids 4000-4999)
9301
+ target => pluginResourceKeys(target).length, (reader, target, left) => {
8607
9302
  const key = (0, psdReader_1.readSignature)(reader);
8608
- if (key === 'mani') {
9303
+ if (key === 'mset') {
9304
+ // Save for Web output settings
9305
+ target.outputSettings = (0, descriptor_1.readVersionAndDescriptor)(reader, true);
9306
+ (0, psdReader_1.skipBytes)(reader, left());
9307
+ }
9308
+ else if (key === 'mfri') {
9309
+ // frame count, active frame and animation count, same data as in animations (mani)
9310
+ (0, psdReader_1.skipBytes)(reader, left());
9311
+ }
9312
+ else if (key === 'mani') {
8609
9313
  (0, psdReader_1.checkSignature)(reader, 'IRFR');
8610
9314
  (0, psdReader_1.readSection)(reader, 1, left => {
8611
9315
  while (left() > 0) {
@@ -8631,6 +9335,7 @@ target => target.animations !== undefined, (reader, target, left) => {
8631
9335
  };
8632
9336
  // console.log('#4000 AnDs', require('util').inspect(desc, false, 99, true));
8633
9337
  // console.log('#4000 AnDs:result', require('util').inspect(target.animations, false, 99, true));
9338
+ (0, psdReader_1.skipBytes)(reader, left()); // padding
8634
9339
  }
8635
9340
  else if (key === 'Roll') {
8636
9341
  const bytes = (0, psdReader_1.readBytes)(reader, left());
@@ -8638,20 +9343,39 @@ target => target.animations !== undefined, (reader, target, left) => {
8638
9343
  }
8639
9344
  else {
8640
9345
  reader.logMissingFeatures && reader.log('Unhandled subsection in #4000', key);
9346
+ (0, psdReader_1.skipBytes)(reader, left());
8641
9347
  }
8642
9348
  });
8643
9349
  }
8644
9350
  });
9351
+ (0, psdReader_1.skipBytes)(reader, left()); // unknown data after animations section
8645
9352
  }
8646
9353
  else if (key === 'mopt') {
8647
9354
  const bytes = (0, psdReader_1.readBytes)(reader, left());
8648
9355
  reader.logDevFeatures && reader.log('#4000 mopt', bytes);
8649
9356
  }
8650
9357
  else {
8651
- reader.logMissingFeatures && reader.log('Unhandled key in #4000:', key);
9358
+ reader.logMissingFeatures && reader.log(`Unhandled plug-in resource key: ${key}`);
9359
+ (0, psdReader_1.skipBytes)(reader, left());
8652
9360
  }
8653
- }, (writer, target) => {
8654
- if (target.animations) {
9361
+ }, (writer, target, index) => {
9362
+ var _a, _b;
9363
+ const key = pluginResourceKeys(target)[index];
9364
+ if (key === 'mset') {
9365
+ (0, psdWriter_1.writeSignature)(writer, 'mset');
9366
+ (0, descriptor_1.writeVersionAndDescriptor)(writer, '', 'null', target.outputSettings, descriptor_1.outputSettingsRoot);
9367
+ }
9368
+ else if (key === 'mfri') {
9369
+ const animations = target.animations;
9370
+ (0, psdWriter_1.writeSignature)(writer, 'mfri');
9371
+ (0, psdWriter_1.writeUint32)(writer, 2); // version
9372
+ (0, psdWriter_1.writeUint32)(writer, 16); // length
9373
+ (0, psdWriter_1.writeUint32)(writer, animations.frames.length);
9374
+ (0, psdWriter_1.writeUint32)(writer, (_b = (_a = animations.animations[0]) === null || _a === void 0 ? void 0 : _a.activeFrame) !== null && _b !== void 0 ? _b : 0);
9375
+ (0, psdWriter_1.writeUint32)(writer, animations.animations.length);
9376
+ (0, psdWriter_1.writeUint32)(writer, 0);
9377
+ }
9378
+ else if (key === 'mani') {
8655
9379
  (0, psdWriter_1.writeSignature)(writer, 'mani');
8656
9380
  (0, psdWriter_1.writeSignature)(writer, 'IRFR');
8657
9381
  (0, psdWriter_1.writeSection)(writer, 1, () => {
@@ -8693,46 +9417,9 @@ target => target.animations !== undefined, (reader, target, left) => {
8693
9417
  // });
8694
9418
  });
8695
9419
  }
8696
- });
8697
- // TODO: Unfinished
8698
- helpers_1.MOCK_HANDLERS && addHandler(4001, // Plug-In resource(s)
8699
- // Plug-In resource(s)
8700
- target => target._ir4001 !== undefined, (reader, target, left) => {
8701
- if (helpers_1.MOCK_HANDLERS) {
8702
- LOG_MOCK_HANDLERS && console.log('image resource 4001', left());
8703
- target._ir4001 = (0, psdReader_1.readBytes)(reader, left());
8704
- return;
8705
- }
8706
- const key = (0, psdReader_1.readSignature)(reader);
8707
- if (key === 'mfri') {
8708
- const version = (0, psdReader_1.readUint32)(reader);
8709
- if (version !== 2)
8710
- throw new Error('Invalid mfri version');
8711
- const length = (0, psdReader_1.readUint32)(reader);
8712
- const bytes = (0, psdReader_1.readBytes)(reader, length);
8713
- reader.logDevFeatures && reader.log('mfri', bytes);
8714
- }
8715
- else if (key === 'mset') {
8716
- const desc = (0, descriptor_1.readVersionAndDescriptor)(reader);
8717
- reader.logDevFeatures && reader.log('mset', desc);
8718
- }
8719
- else {
8720
- reader.logMissingFeatures && reader.log('Unhandled key in #4001', key);
8721
- }
8722
- }, (writer, target) => {
8723
- (0, psdWriter_1.writeBytes)(writer, target._ir4001);
8724
- });
8725
- // TODO: Unfinished
8726
- helpers_1.MOCK_HANDLERS && addHandler(4002, // Plug-In resource(s)
8727
- // Plug-In resource(s)
8728
- target => target._ir4002 !== undefined, (reader, target, left) => {
8729
- LOG_MOCK_HANDLERS && console.log('image resource 4002', left());
8730
- target._ir4002 = (0, psdReader_1.readBytes)(reader, left());
8731
- }, (writer, target) => {
8732
- (0, psdWriter_1.writeBytes)(writer, target._ir4002);
8733
- });
9420
+ }, { keyCount: 1000 });
8734
9421
 
8735
- },{"./descriptor":4,"./helpers":8,"./psdReader":13,"./psdWriter":14,"./utf8":16,"base64-js":17}],10:[function(require,module,exports){
9422
+ },{"./additionalInfo":2,"./descriptor":4,"./helpers":8,"./psdReader":13,"./psdWriter":14,"./utf8":16,"base64-js":17}],10:[function(require,module,exports){
8736
9423
  (function (Buffer){(function (){
8737
9424
  "use strict";
8738
9425
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -9458,7 +10145,7 @@ function parse(data) {
9458
10145
  };
9459
10146
  }
9460
10147
  }
9461
- // TODO APP1 - Exif
10148
+ // APP1 - Exif
9462
10149
  if (fileMarker === 0xFFE1) {
9463
10150
  if (appData[0] === 0x45 &&
9464
10151
  appData[1] === 0x78 &&
@@ -9641,12 +10328,12 @@ function parse(data) {
9641
10328
  // set each frame's components quantization table
9642
10329
  for (let i = 0; i < frames.length; i++) {
9643
10330
  const cp = frames[i].components;
9644
- for (let j in cp) { // TODO: don't use `in`
9645
- const table = quantizationTables[cp[j].quantizationIdx];
10331
+ for (const key of Object.keys(cp)) {
10332
+ const component = cp[+key];
10333
+ const table = quantizationTables[component.quantizationIdx];
9646
10334
  if (!table)
9647
- throw new Error(`Missing quantization table ${cp[j].quantizationIdx}`);
9648
- cp[j].quantizationTable = table;
9649
- delete cp[j].quantizationIdx; // TODO: why ???
10335
+ throw new Error(`Missing quantization table ${component.quantizationIdx}`);
10336
+ component.quantizationTable = table;
9650
10337
  }
9651
10338
  }
9652
10339
  self.width = frame.samplesPerLine;
@@ -9701,8 +10388,8 @@ function getData(decoded) {
9701
10388
  break;
9702
10389
  }
9703
10390
  case 3: {
9704
- // The default transform for three components is true, the adobe transform marker overrides it
9705
- colorTransform = decoded.adobe ? !!decoded.adobe.transformCode : true;
10391
+ // The default transform for three components is true, JFIF marker implies YCbCr, otherwise the adobe transform marker overrides it
10392
+ colorTransform = decoded.jfif ? true : decoded.adobe ? !!decoded.adobe.transformCode : true;
9706
10393
  const component1 = decoded.components[0];
9707
10394
  const component2 = decoded.components[1];
9708
10395
  const component3 = decoded.components[2];
@@ -9916,6 +10603,10 @@ var __rest = (this && this.__rest) || function (s, e) {
9916
10603
  };
9917
10604
  Object.defineProperty(exports, "__esModule", { value: true });
9918
10605
  exports.supportedColorModes = void 0;
10606
+ exports.getUtf8Decoder = getUtf8Decoder;
10607
+ exports.getUtf8StrictDecoder = getUtf8StrictDecoder;
10608
+ exports.getGbkStrictDecoder = getGbkStrictDecoder;
10609
+ exports.decodeUtf8 = decodeUtf8;
9919
10610
  exports.createReader = createReader;
9920
10611
  exports.warnOrThrow = warnOrThrow;
9921
10612
  exports.readUint8 = readUint8;
@@ -9957,12 +10648,15 @@ exports.readDataZip = readDataZip;
9957
10648
  exports.readDataRLE = readDataRLE;
9958
10649
  exports.readSection = readSection;
9959
10650
  exports.readColor = readColor;
10651
+ exports.readPatternSafe = readPatternSafe;
9960
10652
  exports.readPattern = readPattern;
10653
+ exports.consumeMemory = consumeMemory;
9961
10654
  const pako_1 = require("pako");
9962
10655
  const helpers_1 = require("./helpers");
9963
10656
  const additionalInfo_1 = require("./additionalInfo");
10657
+ const utf8_1 = require("./utf8");
9964
10658
  const imageResources_1 = require("./imageResources");
9965
- exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */];
10659
+ exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */, 4 /* ColorMode.CMYK */];
9966
10660
  const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
9967
10661
  function setupGrayscale(data) {
9968
10662
  const size = data.width * data.height * 4;
@@ -9972,6 +10666,37 @@ function setupGrayscale(data) {
9972
10666
  data.data[i + 2] = c;
9973
10667
  }
9974
10668
  }
10669
+ function createTextDecoder(label, options) {
10670
+ try {
10671
+ return typeof TextDecoder !== 'undefined' ? new TextDecoder(label, options) : null;
10672
+ }
10673
+ catch (_a) {
10674
+ return null; // encoding not supported
10675
+ }
10676
+ }
10677
+ // non-fatal UTF-8 decoder, malformed byte sequences are replaced with U+FFFD
10678
+ function getUtf8Decoder(reader) {
10679
+ if (reader.utf8Decoder === undefined)
10680
+ reader.utf8Decoder = createTextDecoder('utf-8', { ignoreBOM: true });
10681
+ return reader.utf8Decoder;
10682
+ }
10683
+ // UTF-8 decoder that throws on malformed byte sequences
10684
+ function getUtf8StrictDecoder(reader) {
10685
+ if (reader.utf8StrictDecoder === undefined)
10686
+ reader.utf8StrictDecoder = createTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
10687
+ return reader.utf8StrictDecoder;
10688
+ }
10689
+ // GBK decoder that throws on malformed byte sequences
10690
+ function getGbkStrictDecoder(reader) {
10691
+ if (reader.gbkDecoder === undefined)
10692
+ reader.gbkDecoder = createTextDecoder('gbk', { fatal: true });
10693
+ return reader.gbkDecoder;
10694
+ }
10695
+ function decodeUtf8(reader, buffer) {
10696
+ // native decoder is faster only for longer strings
10697
+ const decoder = buffer.byteLength > 1000 ? getUtf8Decoder(reader) : null;
10698
+ return decoder ? decoder.decode(buffer) : (0, utf8_1.decodeString)(buffer);
10699
+ }
9975
10700
  function createReader(buffer, offset, length) {
9976
10701
  const view = new DataView(buffer, offset, length);
9977
10702
  return { view, offset: 0, strict: false, debug: false, large: false, globalAlpha: false, log: console.log };
@@ -10151,7 +10876,8 @@ function readPsd(reader, readOptions = {}) {
10151
10876
  const maxSize = version === 1 ? 30000 : 300000;
10152
10877
  if (width > maxSize || height > maxSize)
10153
10878
  throw new Error(`Invalid size: ${width}x${height}`);
10154
- if (channels < 1 || channels > 56)
10879
+ // up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
10880
+ if (channels < 1 || channels > 57)
10155
10881
  throw new Error(`Invalid channel count: ${channels}`);
10156
10882
  if (![1, 8, 16, 32].includes(bitsPerChannel))
10157
10883
  throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
@@ -10161,6 +10887,7 @@ function readPsd(reader, readOptions = {}) {
10161
10887
  Object.assign(reader, readOptions);
10162
10888
  reader.large = version === 2;
10163
10889
  reader.globalAlpha = false;
10890
+ reader.layersInGlobalInfo = false;
10164
10891
  if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
10165
10892
  reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
10166
10893
  }
@@ -10188,17 +10915,21 @@ function readPsd(reader, readOptions = {}) {
10188
10915
  });
10189
10916
  // image resources
10190
10917
  const imageResources = {};
10918
+ let unreadResourceBytes = 0;
10191
10919
  readSection(reader, 1, left => {
10192
- while (left() > 0) {
10193
- realignWithSignature(reader, isValidSignature);
10920
+ // stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
10921
+ while (left() >= 12) {
10922
+ const signature = realignWithSignature(reader, isValidSignature);
10194
10923
  const id = readUint16(reader);
10195
- readPascalString(reader, 2); // name
10924
+ const nameLength = readUint8(reader);
10925
+ const name = readBytes(reader, nameLength);
10926
+ skipBytes(reader, (nameLength + 1) % 2); // pad to even size
10196
10927
  readSection(reader, 2, left => {
10197
10928
  const handler = imageResources_1.resourceHandlersMap[id];
10198
10929
  const skip = id === 1036 && !!reader.skipThumbnail;
10199
10930
  if (handler && !skip) {
10200
10931
  try {
10201
- handler.read(reader, imageResources, left);
10932
+ handler.read(reader, imageResources, left, { signature, id, name, width, height });
10202
10933
  }
10203
10934
  catch (e) {
10204
10935
  if (reader.throwForMissingFeatures)
@@ -10207,13 +10938,20 @@ function readPsd(reader, readOptions = {}) {
10207
10938
  }
10208
10939
  }
10209
10940
  else {
10210
- // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
10941
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
10211
10942
  skipBytes(reader, left());
10212
10943
  }
10213
10944
  });
10214
10945
  }
10946
+ unreadResourceBytes = left();
10947
+ skipBytes(reader, left());
10215
10948
  });
10216
- const { layersGroup, layerGroupsEnabledId } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId"]);
10949
+ // some files have image resources section length few bytes larger than the data, layer and mask info section
10950
+ // starts right after the last resource in that case
10951
+ if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
10952
+ reader.offset -= unreadResourceBytes;
10953
+ }
10954
+ const { layersGroup, layerGroupsEnabledId, savedPathsOrigination } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId", "savedPathsOrigination"]);
10217
10955
  if (Object.keys(rest).length) {
10218
10956
  psd.imageResources = rest;
10219
10957
  }
@@ -10257,6 +10995,8 @@ function readPsd(reader, readOptions = {}) {
10257
10995
  const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
10258
10996
  psd.rawCompositeData = rawCompositeData;
10259
10997
  psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
10998
+ if (reader.layersInGlobalInfo)
10999
+ psd.rawCompositeDataInfo.layersInGlobalInfo = true;
10260
11000
  }
10261
11001
  else {
10262
11002
  const imageData = readImageData(reader, psd);
@@ -10268,9 +11008,6 @@ function readPsd(reader, readOptions = {}) {
10268
11008
  }
10269
11009
  }
10270
11010
  }
10271
- // TODO: show converted color mode instead of original PSD file color mode
10272
- // but add option to preserve file color mode (need to return image data instead of canvas in that case)
10273
- // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
10274
11011
  return psd;
10275
11012
  }
10276
11013
  function readLayerInfo(reader, psd, imageResources) {
@@ -10346,9 +11083,12 @@ function readLayerRecord(reader, psd, imageResources) {
10346
11083
  }
10347
11084
  checkSignature(reader, '8BIM');
10348
11085
  const blendMode = readSignature(reader);
10349
- if (!helpers_1.toBlendMode[blendMode])
10350
- throw new Error(`Invalid blend mode: '${blendMode}'`);
10351
- layer.blendMode = helpers_1.toBlendMode[blendMode];
11086
+ if (!helpers_1.toBlendMode[blendMode]) {
11087
+ if (reader.throwForMissingFeatures)
11088
+ throw new Error(`Invalid blend mode: '${blendMode}'`);
11089
+ reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
11090
+ }
11091
+ layer.blendMode = helpers_1.toBlendMode[blendMode] || 'normal';
10352
11092
  layer.opacity = readUint8(reader) / 0xff;
10353
11093
  layer.clipping = readUint8(reader) === 1;
10354
11094
  const flags = readUint8(reader);
@@ -10376,11 +11116,20 @@ function readLayerRecord(reader, psd, imageResources) {
10376
11116
  });
10377
11117
  return { layer, channels };
10378
11118
  }
11119
+ // negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
11120
+ // checks if length of section (layer and mask info) at given offset fits in the file
11121
+ function sectionFits(reader, offset) {
11122
+ const lengthSize = reader.large ? 8 : 4;
11123
+ if (offset < 0 || offset + lengthSize > reader.view.byteLength)
11124
+ return false;
11125
+ const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
11126
+ return offset + lengthSize + length <= reader.view.byteLength;
11127
+ }
10379
11128
  function isValidBoxSize(box, reader) {
10380
11129
  const width = (box.right || 0) - (box.left || 0);
10381
11130
  const height = (box.bottom || 0) - (box.top || 0);
10382
11131
  const maxSize = reader.large ? 300000 : 30000;
10383
- return width >= 0 && height >= 0 && width <= maxSize && height <= maxSize;
11132
+ return width <= maxSize && height <= maxSize;
10384
11133
  }
10385
11134
  function readLayerMaskData(reader, layer) {
10386
11135
  return readSection(reader, 1, left => {
@@ -10499,9 +11248,9 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
10499
11248
  reader.offset -= 1;
10500
11249
  compression = readUint16(reader);
10501
11250
  }
10502
- // try to fix broken files where there's 1 byte shift of channel
11251
+ // try to fix broken files where there's 1 byte shift of channel (in the other direction)
10503
11252
  if (compression > 3) {
10504
- reader.offset -= 3;
11253
+ reader.offset -= 4;
10505
11254
  compression = readUint16(reader);
10506
11255
  }
10507
11256
  if (compression > 3)
@@ -10535,6 +11284,7 @@ function getCompositeImageData(psd) {
10535
11284
  if (info) {
10536
11285
  reader.large = info.large;
10537
11286
  reader.globalAlpha = info.globalAlpha;
11287
+ reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
10538
11288
  }
10539
11289
  return readImageData(reader, psd);
10540
11290
  }
@@ -10615,18 +11365,14 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
10615
11365
  let initializedAlpha = false;
10616
11366
  if (layerWidth && layerHeight && read === LayerDataType.Layer) {
10617
11367
  if (cmyk) {
10618
- if (bitsPerChannel !== 8)
11368
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
10619
11369
  throw new Error('bitsPerChannel not supported');
10620
- imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) };
11370
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
10621
11371
  }
10622
11372
  else {
10623
11373
  imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
10624
11374
  }
10625
11375
  }
10626
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
10627
- layer.imageDataRaw = [];
10628
- layer.imageDataRawCompression = [];
10629
- }
10630
11376
  for (const { id, compression, data } of channels) {
10631
11377
  if (!data)
10632
11378
  continue;
@@ -10651,16 +11397,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
10651
11397
  if (memoryLimit !== undefined)
10652
11398
  dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit - maskData.data.byteLength);
10653
11399
  readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
10654
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
10655
- if (id === -2 /* ChannelID.UserMask */) {
10656
- layer.maskDataRawCompression = compression;
10657
- layer.maskDataRaw = data;
10658
- }
10659
- else {
10660
- layer.realMaskDataRawCompression = compression;
10661
- layer.realMaskDataRaw = data;
10662
- }
10663
- }
10664
11400
  setupGrayscale(maskData);
10665
11401
  resetAlpha(maskData, false);
10666
11402
  }
@@ -10677,10 +11413,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
10677
11413
  }
10678
11414
  }
10679
11415
  readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
10680
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
10681
- layer.imageDataRawCompression[id] = compression;
10682
- layer.imageDataRaw[id] = data;
10683
- }
10684
11416
  }
10685
11417
  if (id === -1 /* ChannelID.Transparency */) {
10686
11418
  initializedAlpha = true;
@@ -10693,7 +11425,8 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
10693
11425
  resetAlpha(imageData, cmyk);
10694
11426
  if (cmyk) {
10695
11427
  const cmykData = imageData;
10696
- imageData = (0, helpers_1.createImageData)(cmykData.width, cmykData.height);
11428
+ const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
11429
+ imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
10697
11430
  cmykToRgb(cmykData, imageData, false);
10698
11431
  }
10699
11432
  }
@@ -10766,7 +11499,15 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
10766
11499
  const sig = realignWithSignature(reader, isValidAdditionalInfoSignature);
10767
11500
  const key = readSignature(reader);
10768
11501
  // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
10769
- const u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
11502
+ let u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
11503
+ // these keys were written by older versions of this library with 32bit length and 8BIM signature,
11504
+ // 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
11505
+ if (reader.large && sig === '8BIM' && helpers_1.largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
11506
+ // 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
11507
+ const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
11508
+ const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
11509
+ u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
11510
+ }
10770
11511
  readSection(reader, 2, left => {
10771
11512
  const handler = additionalInfo_1.infoHandlersMap[key];
10772
11513
  if (handler) {
@@ -10779,7 +11520,7 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
10779
11520
  }
10780
11521
  }
10781
11522
  else {
10782
- reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
11523
+ reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
10783
11524
  skipBytes(reader, left());
10784
11525
  }
10785
11526
  if (left()) {
@@ -10811,7 +11552,7 @@ function createImageDataBitDepth(width, height, bitDepth, channels, memoryLimit)
10811
11552
  }
10812
11553
  }
10813
11554
  function readImageData(reader, psd) {
10814
- var _a;
11555
+ var _a, _b, _c;
10815
11556
  const compression = readUint16(reader);
10816
11557
  const bitsPerChannel = (_a = psd.bitsPerChannel) !== null && _a !== void 0 ? _a : 8;
10817
11558
  const { width, height } = psd;
@@ -10843,11 +11584,7 @@ function readImageData(reader, psd) {
10843
11584
  case 1 /* ColorMode.Grayscale */: {
10844
11585
  const colorChannels = psd.colorMode === 1 /* ColorMode.Grayscale */ ? 1 : 3;
10845
11586
  const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
10846
- const start = reader.offset;
10847
11587
  readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
10848
- if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
10849
- psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
10850
- }
10851
11588
  if (psd.colorMode === 1 /* ColorMode.Grayscale */) {
10852
11589
  setupGrayscale(imageData);
10853
11590
  }
@@ -10861,41 +11598,42 @@ function readImageData(reader, psd) {
10861
11598
  const offsets = compositeChannelOffsets(reader, psd, 1, -1);
10862
11599
  const indexedImageData = { width, height, data: new Uint8Array(width * height) };
10863
11600
  readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
10864
- indexedToRgb(indexedImageData, imageData, psd.palette);
11601
+ indexedToRgb(indexedImageData, imageData, psd.palette, (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.transparencyIndex) !== null && _c !== void 0 ? _c : -1);
10865
11602
  break;
10866
11603
  }
10867
11604
  case 4 /* ColorMode.CMYK */: {
10868
- if (bitsPerChannel !== 8)
11605
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
10869
11606
  throw new Error('bitsPerChannel not supported');
10870
11607
  const offsets = compositeChannelOffsets(reader, psd, 4, 4);
10871
- const cmykImageData = { width, height, data: new Uint8Array(width * height * 5) };
10872
- const start = reader.offset;
10873
- readCompositeChannels(reader, compression, cmykImageData, 8, 5, offsets);
10874
- cmykToRgb(cmykImageData, imageData, true);
10875
- if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
10876
- psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
10877
- }
11608
+ const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
11609
+ resetAlpha(cmykImageData, true);
11610
+ readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
11611
+ cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
10878
11612
  break;
10879
11613
  }
10880
11614
  default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
10881
11615
  }
10882
- // remove weird white matte
10883
- if (reader.globalAlpha) {
11616
+ // remove weird white matte (already removed when converting from CMYK)
11617
+ if (reader.globalAlpha && psd.colorMode !== 4 /* ColorMode.CMYK */) {
10884
11618
  removeWhiteMatte(imageData);
10885
11619
  }
10886
11620
  return imageData;
10887
11621
  }
10888
11622
  // returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
10889
11623
  function compositeChannelOffsets(reader, psd, colorChannels, alphaOffset) {
10890
- var _a, _b, _c, _d;
11624
+ var _a, _b, _c;
10891
11625
  const channelCount = (_a = psd.channels) !== null && _a !== void 0 ? _a : (colorChannels + (reader.globalAlpha ? 1 : 0));
10892
11626
  if (channelCount < colorChannels)
10893
11627
  throw new Error(`Invalid channel count: ${channelCount}`);
10894
11628
  // First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
10895
11629
  // applications write transparency channel without setting global alpha, in that case extra channel is not
10896
11630
  // listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
10897
- const namedChannels = (_d = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
10898
- const hasTransparency = reader.globalAlpha || (channelCount - colorChannels) > namedChannels;
11631
+ // Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
11632
+ // Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
11633
+ const alphaNames = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) !== null && _c !== void 0 ? _c : [];
11634
+ const namedChannels = alphaNames.length;
11635
+ const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
11636
+ const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
10899
11637
  const offsets = [];
10900
11638
  for (let i = 0; i < channelCount; i++) {
10901
11639
  if (i < colorChannels) {
@@ -10974,41 +11712,67 @@ function removeWhiteMatte({ data, width, height }) {
10974
11712
  }
10975
11713
  }
10976
11714
  }
10977
- function cmykToRgb(cmyk, rgb, reverseAlpha) {
11715
+ // converts value in 0-255 range to target range, rounding and clamping it
11716
+ function toRange(value, max) {
11717
+ value = Math.round(value * (max / 255));
11718
+ return value <= 0 ? 0 : (value >= max ? max : value);
11719
+ }
11720
+ // Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
11721
+ // Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
11722
+ // Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
11723
+ // (http://www.apache.org/licenses/LICENSE-2.0)
11724
+ // cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
11725
+ // removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
11726
+ function cmykToRgb(cmyk, rgb, removeMatte) {
10978
11727
  const size = rgb.width * rgb.height * 4;
10979
11728
  const srcData = cmyk.data;
10980
11729
  const dstData = rgb.data;
11730
+ const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
11731
+ const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
10981
11732
  for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
10982
- const c = srcData[src];
10983
- const m = srcData[src + 1];
10984
- const y = srcData[src + 2];
10985
- const k = srcData[src + 3];
10986
- dstData[dst] = ((((c * k) | 0) / 255) | 0);
10987
- dstData[dst + 1] = ((((m * k) | 0) / 255) | 0);
10988
- dstData[dst + 2] = ((((y * k) | 0) / 255) | 0);
10989
- dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
10990
- }
10991
- // for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
10992
- // const c = 1 - (srcData[src + 0] / 255);
10993
- // const m = 1 - (srcData[src + 1] / 255);
10994
- // const y = 1 - (srcData[src + 2] / 255);
10995
- // // const k = srcData[src + 3] / 255;
10996
- // dstData[dst + 0] = ((1 - c * 0.8) * 255) | 0;
10997
- // dstData[dst + 1] = ((1 - m * 0.8) * 255) | 0;
10998
- // dstData[dst + 2] = ((1 - y * 0.8) * 255) | 0;
10999
- // dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
11000
- // }
11001
- }
11002
- function indexedToRgb(indexed, rgb, palette) {
11733
+ const a = srcData[src + 4];
11734
+ // image data stores inverted values (0 = 100% ink)
11735
+ let c = 1 - srcData[src] / srcMax;
11736
+ let m = 1 - srcData[src + 1] / srcMax;
11737
+ let y = 1 - srcData[src + 2] / srcMax;
11738
+ let k = 1 - srcData[src + 3] / srcMax;
11739
+ if (removeMatte && a > 0 && a < srcMax) {
11740
+ const ra = srcMax / a;
11741
+ c = Math.min(1, c * ra);
11742
+ m = Math.min(1, m * ra);
11743
+ y = Math.min(1, y * ra);
11744
+ k = Math.min(1, k * ra);
11745
+ }
11746
+ dstData[dst] = toRange(255 +
11747
+ c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
11748
+ m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
11749
+ y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
11750
+ k * (-21.86122147463605 * k - 189.48180835922747), dstMax);
11751
+ dstData[dst + 1] = toRange(255 +
11752
+ c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
11753
+ m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
11754
+ y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
11755
+ k * (-20.737325471181034 * k - 187.80453709719578), dstMax);
11756
+ dstData[dst + 2] = toRange(255 +
11757
+ c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
11758
+ m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
11759
+ y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
11760
+ k * (-22.33816807309886 * k - 180.12613974708367), dstMax);
11761
+ dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
11762
+ }
11763
+ }
11764
+ // transparencyIndex - palette index of transparent color (-1 if there's none)
11765
+ function indexedToRgb(indexed, rgb, palette, transparencyIndex) {
11003
11766
  const size = indexed.width * indexed.height;
11004
11767
  const srcData = indexed.data;
11005
11768
  const dstData = rgb.data;
11006
11769
  for (let src = 0, dst = 0; src < size; src++, dst += 4) {
11007
- const c = palette[srcData[src]];
11770
+ const index = srcData[src];
11771
+ const c = palette[index];
11008
11772
  dstData[dst + 0] = c.r;
11009
11773
  dstData[dst + 1] = c.g;
11010
11774
  dstData[dst + 2] = c.b;
11011
- dstData[dst + 3] = 255;
11775
+ dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
11012
11776
  }
11013
11777
  }
11014
11778
  // number of bytes used to store single channel value, 1-bit data is handled as bytes
@@ -11319,11 +12083,11 @@ function readColor(reader) {
11319
12083
  skipBytes(reader, 2);
11320
12084
  return { h, s, b };
11321
12085
  }
11322
- case 2 /* ColorSpace.CMYK */: {
11323
- const c = readUint16(reader) / 257;
11324
- const m = readUint16(reader) / 257;
11325
- const y = readUint16(reader) / 257;
11326
- const k = readUint16(reader) / 257;
12086
+ case 2 /* ColorSpace.CMYK */: { // 0 = 100% ink
12087
+ const c = (0xffff - readUint16(reader)) / 257;
12088
+ const m = (0xffff - readUint16(reader)) / 257;
12089
+ const y = (0xffff - readUint16(reader)) / 257;
12090
+ const k = (0xffff - readUint16(reader)) / 257;
11327
12091
  return { c, m, y, k };
11328
12092
  }
11329
12093
  case 7 /* ColorSpace.Lab */: {
@@ -11344,6 +12108,24 @@ function readColor(reader) {
11344
12108
  throw new Error('Invalid color space');
11345
12109
  }
11346
12110
  }
12111
+ const MAX_PATTERN_SIZE = 30000;
12112
+ // reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
12113
+ function readPatternSafe(reader) {
12114
+ let length = reader.view.getUint32(reader.offset);
12115
+ while (length % 4)
12116
+ length++;
12117
+ const end = reader.offset + 4 + length;
12118
+ try {
12119
+ return readPattern(reader);
12120
+ }
12121
+ catch (e) {
12122
+ if (reader.throwForMissingFeatures)
12123
+ throw e;
12124
+ reader.logMissingFeatures && reader.log(`Skipped pattern: ${e.message}`);
12125
+ reader.offset = end;
12126
+ return undefined;
12127
+ }
12128
+ }
11347
12129
  function readPattern(reader) {
11348
12130
  let length = readUint32(reader);
11349
12131
  while (length % 4)
@@ -11355,8 +12137,8 @@ function readPattern(reader) {
11355
12137
  const colorMode = readUint32(reader);
11356
12138
  const x = readInt16(reader);
11357
12139
  const y = readInt16(reader);
11358
- // we only support RGB and grayscale for now
11359
- if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */) {
12140
+ // we only support RGB, grayscale, indexed and CMYK for now
12141
+ if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */ && colorMode !== 4 /* ColorMode.CMYK */) {
11360
12142
  throw new Error(`Unsupported pattern color mode: ${colorMode}`);
11361
12143
  }
11362
12144
  let name = readUnicodeString(reader);
@@ -11370,7 +12152,9 @@ function readPattern(reader) {
11370
12152
  b: readUint8(reader),
11371
12153
  });
11372
12154
  }
11373
- skipBytes(reader, 4); // no idea what this is
12155
+ // some writers follow the palette with 4 extra bytes (unknown)
12156
+ if (reader.view.getUint32(reader.offset) !== 3)
12157
+ skipBytes(reader, 4);
11374
12158
  }
11375
12159
  // virtual memory array list
11376
12160
  const version2 = readUint32(reader);
@@ -11384,17 +12168,37 @@ function readPattern(reader) {
11384
12168
  const channelsCount = readUint32(reader);
11385
12169
  const width = right - left;
11386
12170
  const height = bottom - top;
12171
+ if (width < 0 || height < 0)
12172
+ throw new Error('Invalid pattern bounds');
12173
+ if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE)
12174
+ throw new Error(`Pattern too large (${width}x${height})`);
12175
+ if (channelsCount > 64)
12176
+ throw new Error(`Invalid pattern channel count: ${channelsCount}`);
12177
+ // channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
12178
+ if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
12179
+ throw new Error('Pattern data out of bounds');
11387
12180
  const size = width * height * 4;
11388
12181
  consumeMemory(reader, size);
11389
12182
  const data = new Uint8Array(size);
11390
12183
  for (let i = 3; i < data.byteLength; i += 4) {
11391
12184
  data[i] = 255;
11392
12185
  }
11393
- for (let i = 0, ch = 0; i < (channelsCount + 2); i++) {
12186
+ // first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
12187
+ const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
12188
+ // CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
12189
+ const cmyk = colorMode === 4 /* ColorMode.CMYK */ ? new Uint8Array(width * height * 5).fill(255) : undefined;
12190
+ if (cmyk)
12191
+ consumeMemory(reader, cmyk.byteLength);
12192
+ const alphaTarget = cmyk ? 4 : 3;
12193
+ for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
11394
12194
  const has = readUint32(reader);
11395
12195
  if (!has)
11396
12196
  continue;
11397
12197
  const length = readUint32(reader);
12198
+ if (!length)
12199
+ continue;
12200
+ if (length < 23)
12201
+ throw new Error('Invalid pattern channel length');
11398
12202
  const pixelDepth = readUint32(reader);
11399
12203
  const ctop = readUint32(reader);
11400
12204
  const cleft = readUint32(reader);
@@ -11402,97 +12206,92 @@ function readPattern(reader) {
11402
12206
  const cright = readUint32(reader);
11403
12207
  const pixelDepth2 = readUint16(reader);
11404
12208
  const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
11405
- const dataLength = length - (4 + 16 + 2 + 1);
11406
- const cdata = readBytes(reader, dataLength);
11407
- if (pixelDepth !== 8 || pixelDepth2 !== 8) {
11408
- throw new Error('16bit pixel depth not supported for patterns');
11409
- }
12209
+ const cdata = readBytes(reader, length - 23);
12210
+ const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
12211
+ if (depth !== 8 && depth !== 16 && depth !== 32)
12212
+ throw new Error(`Unsupported pattern pixel depth: ${depth}`);
12213
+ if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
12214
+ throw new Error('Pattern channel bounds outside of pattern bounds');
12215
+ }
12216
+ const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
12217
+ ch++;
12218
+ if (target === -1)
12219
+ continue;
11410
12220
  const w = cright - cleft;
11411
12221
  const h = cbottom - ctop;
11412
- const ox = cleft - left;
11413
- const oy = ctop - top;
12222
+ const rowBytes = w * (depth / 8);
12223
+ const channelSize = rowBytes * h;
12224
+ let bytes;
11414
12225
  if (compressionMode === 0) {
11415
- if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
11416
- for (let y = 0; y < h; y++) {
11417
- for (let x = 0; x < w; x++) {
11418
- const src = x + y * w;
11419
- const dst = (ox + x + (y + oy) * width) * 4;
11420
- data[dst + ch] = cdata[src];
11421
- }
11422
- }
11423
- }
11424
- else if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
11425
- for (let y = 0; y < h; y++) {
11426
- for (let x = 0; x < w; x++) {
11427
- const src = x + y * w;
11428
- const dst = (ox + x + (y + oy) * width) * 4;
11429
- const value = cdata[src];
11430
- data[dst + 0] = value;
11431
- data[dst + 1] = value;
11432
- data[dst + 2] = value;
11433
- }
11434
- }
11435
- }
11436
- else if (colorMode === 2 /* ColorMode.Indexed */) {
11437
- for (let y = 0; y < h; y++) {
11438
- for (let x = 0; x < w; x++) {
11439
- const src = x + y * w;
11440
- const dst = (ox + x + (y + oy) * width) * 4;
11441
- const index = cdata[src];
11442
- const color = palette[index];
11443
- data[dst + 0] = color.r;
11444
- data[dst + 1] = color.g;
11445
- data[dst + 2] = color.b;
11446
- }
11447
- }
11448
- }
11449
- else {
11450
- if (reader.throwForMissingFeatures)
11451
- throw new Error('Invalid color pattern');
11452
- }
12226
+ if (cdata.byteLength < channelSize)
12227
+ throw new Error('Pattern channel data out of bounds');
12228
+ bytes = cdata;
11453
12229
  }
11454
12230
  else if (compressionMode === 1) {
11455
- consumeMemory(reader, w * h);
11456
- const pixelData = { data, width, height };
11457
- const tempData = { data: new Uint8Array(w * h), width: w, height: h };
12231
+ // each 2 bytes of RLE data can produce at most 128 bytes
12232
+ if (channelSize > cdata.byteLength * 64)
12233
+ throw new Error('Pattern channel data out of bounds');
12234
+ consumeMemory(reader, channelSize);
12235
+ bytes = new Uint8Array(channelSize);
11458
12236
  const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
11459
- if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
11460
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
11461
- copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
11462
- }
11463
- if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
11464
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
11465
- copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
11466
- setupGrayscale(pixelData);
11467
- }
11468
- if (colorMode === 2 /* ColorMode.Indexed */) {
11469
- // TODO:
11470
- throw new Error('Indexed pattern color mode not implemented');
11471
- }
11472
- recoverMemory(reader, w * h);
12237
+ readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
11473
12238
  }
11474
12239
  else {
11475
12240
  throw new Error('Invalid pattern compression mode');
11476
12241
  }
11477
- ch++;
12242
+ const ox = cleft - left;
12243
+ const oy = ctop - top;
12244
+ for (let y = 0; y < h; y++) {
12245
+ for (let x = 0; x < w; x++) {
12246
+ const src = x + y * w;
12247
+ const dst = (ox + x + (y + oy) * width) * 4;
12248
+ const value = depth === 8 ? bytes[src] : patternValueTo8bit(bytes, src, depth);
12249
+ if (cmyk) {
12250
+ cmyk[(ox + x + (y + oy) * width) * 5 + target] = value;
12251
+ }
12252
+ else if (target === 3) {
12253
+ data[dst + 3] = value;
12254
+ }
12255
+ else if (colorMode === 2 /* ColorMode.Indexed */) {
12256
+ const color = palette[value];
12257
+ data[dst + 0] = color.r;
12258
+ data[dst + 1] = color.g;
12259
+ data[dst + 2] = color.b;
12260
+ }
12261
+ else if (colorMode === 1 /* ColorMode.Grayscale */) {
12262
+ data[dst + 0] = value;
12263
+ data[dst + 1] = value;
12264
+ data[dst + 2] = value;
12265
+ }
12266
+ else {
12267
+ data[dst + target] = value;
12268
+ }
12269
+ }
12270
+ }
12271
+ if (compressionMode === 1)
12272
+ recoverMemory(reader, channelSize);
12273
+ }
12274
+ if (cmyk) {
12275
+ cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
12276
+ recoverMemory(reader, cmyk.byteLength);
11478
12277
  }
11479
12278
  reader.offset = end;
11480
12279
  return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
11481
12280
  }
11482
- function copyChannelToRGBA(srcData, dstData, ox, oy, offset) {
11483
- const w = srcData.width;
11484
- const h = srcData.height;
11485
- const width = dstData.width;
11486
- for (let y = 0; y < h; y++) {
11487
- for (let x = 0; x < w; x++) {
11488
- const src = x + y * w;
11489
- const dst = (ox + x + (y + oy) * width) * 4;
11490
- const value = srcData.data[src];
11491
- dstData.data[dst + offset] = value;
11492
- }
12281
+ // converts big-endian 16bit or 32bit (float) value at given index to 8bit value
12282
+ function patternValueTo8bit(bytes, index, depth) {
12283
+ if (depth === 16) {
12284
+ return Math.round(((bytes[index * 2] << 8) | bytes[index * 2 + 1]) / 257);
12285
+ }
12286
+ else {
12287
+ const view = new DataView(bytes.buffer, bytes.byteOffset + index * 4, 4);
12288
+ const value = view.getFloat32(0, false);
12289
+ return value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
11493
12290
  }
11494
12291
  }
11495
12292
  function consumeMemory(reader, size) {
12293
+ if (!(size >= 0))
12294
+ throw new Error('Invalid size');
11496
12295
  if (reader.totalMemoryLimit !== undefined) {
11497
12296
  if (reader.totalMemoryLimit < size)
11498
12297
  throw new Error('Exceeded memory limit');
@@ -11505,7 +12304,7 @@ function recoverMemory(reader, size) {
11505
12304
  }
11506
12305
  }
11507
12306
 
11508
- },{"./additionalInfo":2,"./helpers":8,"./imageResources":9,"pako":20}],14:[function(require,module,exports){
12307
+ },{"./additionalInfo":2,"./helpers":8,"./imageResources":9,"./utf8":16,"pako":20}],14:[function(require,module,exports){
11509
12308
  "use strict";
11510
12309
  Object.defineProperty(exports, "__esModule", { value: true });
11511
12310
  exports.createWriter = createWriter;
@@ -11540,11 +12339,12 @@ exports.writePattern = writePattern;
11540
12339
  const helpers_1 = require("./helpers");
11541
12340
  const additionalInfo_1 = require("./additionalInfo");
11542
12341
  const imageResources_1 = require("./imageResources");
12342
+ const psdReader_1 = require("./psdReader");
11543
12343
  function createWriter(size = 4096) {
11544
12344
  const buffer = new ArrayBuffer(size);
11545
12345
  const view = new DataView(buffer);
11546
12346
  const offset = 0;
11547
- return { buffer, view, offset, tempBuffer: undefined };
12347
+ return { buffer, view, offset, tempBuffer: undefined, log: console.log };
11548
12348
  }
11549
12349
  function getWriterBuffer(writer) {
11550
12350
  return writer.buffer.slice(0, writer.offset);
@@ -11731,6 +12531,8 @@ function writePsd(writer, psd, options = {}) {
11731
12531
  const bitsPerChannel = (_a = psd.bitsPerChannel) !== null && _a !== void 0 ? _a : 8;
11732
12532
  if (bitsPerChannel !== 8)
11733
12533
  throw new Error('bitsPerChannel other than 8 are not supported for writing');
12534
+ if (options.log)
12535
+ writer.log = options.log;
11734
12536
  const layers = [];
11735
12537
  addChildren(layers, psd.children);
11736
12538
  if (!layers.length)
@@ -11738,6 +12540,17 @@ function writePsd(writer, psd, options = {}) {
11738
12540
  verifyBitCount(psd);
11739
12541
  const imageResources = Object.assign({}, psd.imageResources);
11740
12542
  const opt = Object.assign(Object.assign({}, options), { layerIds: new Set(), layerToId: new Map() });
12543
+ // document is always written in RGB color mode, skip resources specific to original color mode
12544
+ if (psd.colorMode !== undefined && psd.colorMode !== 3 /* ColorMode.RGB */) {
12545
+ delete imageResources.iccProfile;
12546
+ delete imageResources.indexedColorCount;
12547
+ delete imageResources.transparencyIndex;
12548
+ delete imageResources.alphaChannelDisplayInfo;
12549
+ delete imageResources.colorHalftoneScreens;
12550
+ delete imageResources.grayscaleHalftoneScreens;
12551
+ delete imageResources.colorTransferFunctions;
12552
+ delete imageResources.grayscaleTransferFunctions;
12553
+ }
11741
12554
  if (opt.generateThumbnail) {
11742
12555
  imageResources.thumbnail = createThumbnail(psd);
11743
12556
  }
@@ -11745,6 +12558,9 @@ function writePsd(writer, psd, options = {}) {
11745
12558
  if (!imageData && psd.canvas) {
11746
12559
  imageData = psd.canvas.getContext('2d').getImageData(0, 0, psd.canvas.width, psd.canvas.height);
11747
12560
  }
12561
+ if (!imageData && psd.rawCompositeData) { // read with `useRawData` option
12562
+ imageData = (0, psdReader_1.getCompositeImageData)(psd);
12563
+ }
11748
12564
  if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
11749
12565
  throw new Error('Document canvas must have the same size as document');
11750
12566
  const globalAlpha = !!imageData && (0, helpers_1.hasAlpha)(imageData);
@@ -11759,19 +12575,8 @@ function writePsd(writer, psd, options = {}) {
11759
12575
  writeUint32(writer, psd.width);
11760
12576
  writeUint16(writer, bitsPerChannel); // bits per channel
11761
12577
  writeUint16(writer, 3 /* ColorMode.RGB */); // we only support saving RGB right now
11762
- // color mode data
11763
- writeSection(writer, 1, () => {
11764
- var _a, _b, _c;
11765
- if (psd.palette) {
11766
- for (let i = 0; i < 256; i++)
11767
- writeUint8(writer, ((_a = psd.palette[i]) === null || _a === void 0 ? void 0 : _a.r) || 0);
11768
- for (let i = 0; i < 256; i++)
11769
- writeUint8(writer, ((_b = psd.palette[i]) === null || _b === void 0 ? void 0 : _b.g) || 0);
11770
- for (let i = 0; i < 256; i++)
11771
- writeUint8(writer, ((_c = psd.palette[i]) === null || _c === void 0 ? void 0 : _c.b) || 0);
11772
- }
11773
- // TODO: other data?
11774
- });
12578
+ // color mode data (empty for RGB color mode)
12579
+ writeUint32(writer, 0);
11775
12580
  // image resources
11776
12581
  imageResources.layersGroup = layers.map(l => l.linkGroup || 0);
11777
12582
  imageResources.layerGroupsEnabledId = layers.map(l => l.linkGroupEnabled == false ? 0 : 1);
@@ -11780,10 +12585,12 @@ function writePsd(writer, psd, options = {}) {
11780
12585
  const has = handler.has(imageResources);
11781
12586
  const count = has === false ? 0 : (has === true ? 1 : has);
11782
12587
  for (let i = 0; i < count; i++) {
12588
+ const id = handler.keyCount > 1 ? handler.key + i : handler.key;
12589
+ const name = handler.name ? handler.name(imageResources, i) : '';
11783
12590
  writeSignature(writer, '8BIM');
11784
- writeUint16(writer, handler.key);
11785
- writePascalString(writer, '', 2);
11786
- writeSection(writer, 2, () => handler.write(writer, imageResources, i));
12591
+ writeUint16(writer, id);
12592
+ writePascalString(writer, name, 2);
12593
+ writeSection(writer, 2, () => handler.write(writer, imageResources, i, psd));
11787
12594
  }
11788
12595
  }
11789
12596
  });
@@ -11796,19 +12603,13 @@ function writePsd(writer, psd, options = {}) {
11796
12603
  writer.tempBuffer = undefined;
11797
12604
  // image data
11798
12605
  writeUint16(writer, 1 /* Compression.RleCompressed */); // Photoshop doesn't support zip compression of composite image data
11799
- if (helpers_1.RAW_IMAGE_DATA && psd.imageDataRaw) {
11800
- console.log('writing raw image data');
11801
- writeBytes(writer, psd.imageDataRaw);
11802
- }
11803
- else {
11804
- const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
11805
- const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
11806
- // encode directly into the writer buffer
11807
- const maxSize = channels.length * getRLEChannelBound(data.width, data.height, !!options.psb);
11808
- ensureSize(writer, writer.offset + maxSize);
11809
- const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
11810
- writer.offset += (0, helpers_1.writeDataRLE)(buffer, data, channels, !!options.psb).length;
11811
- }
12606
+ const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
12607
+ const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
12608
+ // encode directly into the writer buffer
12609
+ const maxSize = channels.length * getRLEChannelBound(data.width, data.height, !!options.psb);
12610
+ ensureSize(writer, writer.offset + maxSize);
12611
+ const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
12612
+ writer.offset += (0, helpers_1.writeDataRLE)(buffer, data, channels, !!options.psb).length;
11812
12613
  }
11813
12614
  function getCompositeData(imageData, width, height, globalAlpha) {
11814
12615
  if (!imageData)
@@ -11837,7 +12638,7 @@ function writeLayerInfo(writer, layers, psd, globalAlpha, options) {
11837
12638
  writeSection(writer, 4, () => {
11838
12639
  var _a;
11839
12640
  writeInt16(writer, globalAlpha ? -layers.length : layers.length);
11840
- const layersData = layers.map((l, i) => getChannels(writer.tempBuffer, l, i === 0, options));
12641
+ const layersData = layers.map((l, i) => getChannels(writer, l, i === 0, options));
11841
12642
  // layer records
11842
12643
  for (const layerData of layersData) {
11843
12644
  const { layer, top, left, bottom, right, channels } = layerData;
@@ -11889,7 +12690,12 @@ function writeLayerInfo(writer, layers, psd, globalAlpha, options) {
11889
12690
  }
11890
12691
  }, true, options.psb);
11891
12692
  }
11892
- function writeLayerMaskData(writer, { mask, realMask }, layerData) {
12693
+ // real mask is only stored separately if layer has both vector and pixel masks, otherwise it's stored as the main mask
12694
+ function getMasks(layer) {
12695
+ return layer.mask ? { mask: layer.mask, realMask: layer.realMask } : { mask: layer.realMask, realMask: undefined };
12696
+ }
12697
+ function writeLayerMaskData(writer, layer, layerData) {
12698
+ const { mask, realMask } = getMasks(layer);
11893
12699
  writeSection(writer, 4, () => {
11894
12700
  if (!mask && !realMask)
11895
12701
  return;
@@ -11985,13 +12791,13 @@ function writeAdditionalLayerInfo(writer, target, psd, options) {
11985
12791
  let key = handler.key;
11986
12792
  if (key === 'Txt2' && options.invalidateTextLayers)
11987
12793
  continue;
11988
- if (key === 'vmsk' && options.psb)
11989
- key = 'vsms';
12794
+ if (key === 'vmsk' && additionalInfo_1.infoHandlersMap['vscg'].has(target))
12795
+ key = 'vsms'; // Photoshop uses 'vsms' for layers with vector stroke
11990
12796
  if (handler.has(target)) {
11991
12797
  const large = options.psb && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1;
11992
12798
  const writeTotalLength = key !== 'Txt2' && key !== 'cinf' && key !== 'extn' && key !== 'CAI ' && key !== 'OCIO';
11993
12799
  const fourBytes = key === 'levl' || key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
11994
- key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
12800
+ key === 'vogk' || key === 'SoLd' || key === 'SoLE' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
11995
12801
  key === 'lmfx' || key === 'lrFX' || key === 'cinf' || key === 'PlLd' || key === 'Anno' || key === 'CAI ' || key === 'OCIO' || key === 'GenI' || key === 'FEid' || key === 'curv' || key === 'CgEd' || key === 'vibA' || key === 'blwh' || key === 'grdm';
11996
12802
  writeSignature(writer, large ? '8B64' : '8BIM');
11997
12803
  writeSignature(writer, key);
@@ -12084,7 +12890,7 @@ function createThumbnail(psd) {
12084
12890
  }
12085
12891
  return canvas;
12086
12892
  }
12087
- function getMaskChannels(tempBuffer, layerData, layer, mask, options, realMask) {
12893
+ function getMaskChannels(tempBuffer, layerData, mask, options, realMask) {
12088
12894
  let top = mask.top | 0;
12089
12895
  let left = mask.left | 0;
12090
12896
  let right = mask.right | 0;
@@ -12101,11 +12907,7 @@ function getMaskChannels(tempBuffer, layerData, layer, mask, options, realMask)
12101
12907
  bottom = top + height;
12102
12908
  let buffer;
12103
12909
  let compression;
12104
- if (helpers_1.RAW_IMAGE_DATA && layer[realMask ? 'realMaskDataRaw' : 'maskDataRaw']) {
12105
- buffer = layer[realMask ? 'realMaskDataRaw' : 'maskDataRaw'];
12106
- compression = layer[realMask ? 'realMaskDataRawCompression' : 'maskDataRawCompression'];
12107
- }
12108
- else if (!imageData) {
12910
+ if (!imageData) {
12109
12911
  buffer = new Uint8Array(0);
12110
12912
  compression = 1 /* Compression.RleCompressed */;
12111
12913
  }
@@ -12128,15 +12930,32 @@ function bounds(obj) {
12128
12930
  bottom: obj.bottom || 0,
12129
12931
  } : undefined;
12130
12932
  }
12131
- function getChannels(tempBuffer, layer, background, options) {
12933
+ function getChannels(writer, layer, background, options) {
12934
+ let source = layer;
12132
12935
  if (layer.rawData) {
12133
- return Object.assign(Object.assign({ layer, channels: layer.rawData.channels.map(c => { var _a, _b; return (Object.assign(Object.assign({}, c), { length: 2 + ((_b = (_a = c.data) === null || _a === void 0 ? void 0 : _a.byteLength) !== null && _b !== void 0 ? _b : 0) })); }) }, bounds(layer)), { mask: bounds(layer.mask), realMask: bounds(layer.realMask) });
12134
- }
12135
- const layerData = getLayerChannels(tempBuffer, layer, background, options);
12136
- if (layer.mask)
12137
- getMaskChannels(tempBuffer, layerData, layer, layer.mask, options, false);
12138
- if (layer.realMask)
12139
- getMaskChannels(tempBuffer, layerData, layer, layer.realMask, options, true);
12936
+ const { colorMode, bitsPerChannel, large } = layer.rawData;
12937
+ if (large === !!options.psb && colorMode === 3 /* ColorMode.RGB */ && bitsPerChannel === 8) {
12938
+ // same mask mapping as in writeLayerMaskData
12939
+ const { mask, realMask } = getMasks(layer);
12940
+ return Object.assign(Object.assign({ layer, channels: layer.rawData.channels.map(c => { var _a, _b; return (Object.assign(Object.assign({}, c), { length: 2 + ((_b = (_a = c.data) === null || _a === void 0 ? void 0 : _a.byteLength) !== null && _b !== void 0 ? _b : 0) })); }) }, bounds(layer)), { mask: bounds(mask), realMask: bounds(realMask) });
12941
+ }
12942
+ if (bitsPerChannel !== 8)
12943
+ throw new Error('Layer raw data with bitsPerChannel other than 8 is not supported for writing');
12944
+ // raw data is in different format than the one we're writing, decode it so it can be re-encoded
12945
+ source = Object.assign(Object.assign({}, layer), { rawData: undefined, canvas: undefined, imageData: (0, psdReader_1.getLayerImageData)(layer), mask: layer.mask && Object.assign(Object.assign({}, layer.mask), { canvas: undefined, imageData: (0, psdReader_1.getLayerMaskImageData)(layer) }), realMask: layer.realMask && Object.assign(Object.assign({}, layer.realMask), { canvas: undefined, imageData: (0, psdReader_1.getLayerRealMaskImageData)(layer) }) });
12946
+ // temp buffer is sized only for layers with image data, grow it and keep it for following layers
12947
+ const size = getLargestLayerSize([Object.assign(Object.assign({}, source), { children: undefined })], !!options.psb);
12948
+ if (writer.tempBuffer.byteLength < size)
12949
+ writer.tempBuffer = new Uint8Array(size);
12950
+ }
12951
+ const tempBuffer = writer.tempBuffer;
12952
+ const { mask, realMask } = getMasks(source);
12953
+ const layerData = getLayerChannels(tempBuffer, source, background, options);
12954
+ if (mask)
12955
+ getMaskChannels(tempBuffer, layerData, mask, options, false);
12956
+ if (realMask)
12957
+ getMaskChannels(tempBuffer, layerData, realMask, options, true);
12958
+ layerData.layer = layer;
12140
12959
  return layerData;
12141
12960
  }
12142
12961
  function getLayerDimentions({ canvas, imageData }) {
@@ -12166,7 +12985,6 @@ function cropImageData(data, left, top, width, height) {
12166
12985
  return croppedData;
12167
12986
  }
12168
12987
  function getLayerChannels(tempBuffer, layer, background, options) {
12169
- var _a;
12170
12988
  let top = layer.top | 0;
12171
12989
  let left = layer.left | 0;
12172
12990
  let right = layer.right | 0;
@@ -12205,19 +13023,14 @@ function getLayerChannels(tempBuffer, layer, background, options) {
12205
13023
  1 /* ChannelID.Color1 */,
12206
13024
  2 /* ChannelID.Color2 */,
12207
13025
  ];
12208
- if (!background || options.noBackground || layer.mask || (0, helpers_1.hasAlpha)(imageData) || (helpers_1.RAW_IMAGE_DATA && ((_a = layer.imageDataRaw) === null || _a === void 0 ? void 0 : _a['-1']))) {
13026
+ if (!background || options.noBackground || layer.mask || (0, helpers_1.hasAlpha)(imageData)) {
12209
13027
  channelIds.unshift(-1 /* ChannelID.Transparency */);
12210
13028
  }
12211
13029
  channels = channelIds.map(id => {
12212
- const offset = (0, helpers_1.offsetForChannel)(id, false); // TODO: psd.colorMode === ColorMode.CMYK);
13030
+ const offset = (0, helpers_1.offsetForChannel)(id, false); // image data is always RGBA
12213
13031
  let data;
12214
13032
  let compression;
12215
- if (helpers_1.RAW_IMAGE_DATA && layer.imageDataRaw) {
12216
- // console.log('written raw layer image data');
12217
- data = layer.imageDataRaw[id];
12218
- compression = layer.imageDataRawCompression[id];
12219
- }
12220
- else if (options.compress) {
13033
+ if (options.compress) {
12221
13034
  data = (0, helpers_1.writeDataZipWithoutPrediction)(imageData, [offset]);
12222
13035
  compression = 2 /* Compression.ZipWithoutPrediction */;
12223
13036
  }
@@ -12298,11 +13111,12 @@ function writeColor(writer, color) {
12298
13111
  writeUint16(writer, 0);
12299
13112
  }
12300
13113
  else if ('c' in color) {
13114
+ // 0 = 100% ink
12301
13115
  writeUint16(writer, 2 /* ColorSpace.CMYK */);
12302
- writeUint16(writer, Math.round(color.c * 257));
12303
- writeUint16(writer, Math.round(color.m * 257));
12304
- writeUint16(writer, Math.round(color.y * 257));
12305
- writeUint16(writer, Math.round(color.k * 257));
13116
+ writeUint16(writer, 0xffff - Math.round((0, helpers_1.clamp)(color.c, 0, 255) * 257));
13117
+ writeUint16(writer, 0xffff - Math.round((0, helpers_1.clamp)(color.m, 0, 255) * 257));
13118
+ writeUint16(writer, 0xffff - Math.round((0, helpers_1.clamp)(color.y, 0, 255) * 257));
13119
+ writeUint16(writer, 0xffff - Math.round((0, helpers_1.clamp)(color.k, 0, 255) * 257));
12306
13120
  }
12307
13121
  else {
12308
13122
  writeUint16(writer, 8 /* ColorSpace.Grayscale */);
@@ -12369,7 +13183,7 @@ function writePattern(writer, pattern) {
12369
13183
  writer.view.setUint32(pattsOffset - 4, pattsLength, false);
12370
13184
  }
12371
13185
 
12372
- },{"./additionalInfo":2,"./helpers":8,"./imageResources":9}],15:[function(require,module,exports){
13186
+ },{"./additionalInfo":2,"./helpers":8,"./imageResources":9,"./psdReader":13}],15:[function(require,module,exports){
12373
13187
  "use strict";
12374
13188
  Object.defineProperty(exports, "__esModule", { value: true });
12375
13189
  exports.decodeEngineData = decodeEngineData;
@@ -12463,7 +13277,16 @@ const styleKeys = [
12463
13277
  'strokeFlag', 'fillFirst', 'yUnderline', 'outlineWidth', 'characterDirection', 'hindiNumbers',
12464
13278
  'kashida', 'diacriticPos',
12465
13279
  ];
12466
- const antialias = ['none', 'crisp', 'strong', 'smooth', 'sharp'];
13280
+ const antialias = [
13281
+ 'none', // 0
13282
+ 'crisp', // 1
13283
+ 'strong', // 2
13284
+ 'smooth', // 3
13285
+ 'sharp', // 4
13286
+ undefined, // 5 - unknown
13287
+ 'platformLCD', // 6
13288
+ 'platform', // 7
13289
+ ];
12467
13290
  const justification = [
12468
13291
  'left', // 0
12469
13292
  'right', // 1
@@ -12556,8 +13379,9 @@ function objectsEqual(a, b) {
12556
13379
  return true;
12557
13380
  }
12558
13381
  function findOrAddFont(fonts, font) {
13382
+ // Photoshop writes separate entries for the same font with different synthetic (faux bold/italic) styles
12559
13383
  for (let i = 0; i < fonts.length; i++) {
12560
- if (fonts[i].name === font.name)
13384
+ if (fonts[i].name === font.name && (fonts[i].synthetic || 0) === (font.synthetic || 0))
12561
13385
  return i;
12562
13386
  }
12563
13387
  fonts.push(font);
@@ -12753,7 +13577,8 @@ function decodeEngineData(engineData) {
12753
13577
  result.paragraphStyleRuns.push({ length, style /*, adjustments*/ });
12754
13578
  }
12755
13579
  for (let counter = removedCharacters; result.paragraphStyleRuns.length && counter > 0; counter--) {
12756
- if (--result.paragraphStyleRuns[result.paragraphStyleRuns.length - 1].length === 0) {
13580
+ // keep 0 length run if it's for empty last paragraph, otherwise we lose its style
13581
+ if (--result.paragraphStyleRuns[result.paragraphStyleRuns.length - 1].length === 0 && result.paragraphStyleRuns.length === 1) {
12757
13582
  result.paragraphStyleRuns.pop();
12758
13583
  }
12759
13584
  }
@@ -12829,13 +13654,13 @@ function encodeEngineData(data) {
12829
13654
  for (const run of paragraphRuns) {
12830
13655
  let runLength = Math.min(run.length, leftLength);
12831
13656
  leftLength -= runLength;
12832
- if (!runLength)
12833
- continue; // ignore 0 size runs
12834
- // extend last run if it's only for trailing \r
13657
+ // extend last run if it's only for trailing \r (last run has 0 length if it's for empty last paragraph)
12835
13658
  if (leftLength === 1 && run === paragraphRuns[paragraphRuns.length - 1]) {
12836
13659
  runLength++;
12837
13660
  leftLength--;
12838
13661
  }
13662
+ if (!runLength)
13663
+ continue; // ignore 0 size runs
12839
13664
  paragraphRunLengthArray.push(runLength);
12840
13665
  paragraphRunArray.push({
12841
13666
  ParagraphSheet: {
@@ -13138,9 +13963,6 @@ function encodeString(value) {
13138
13963
  // UTF-8 decoder implementing the WHATWG Encoding Standard's non-fatal error handling,
13139
13964
  // so malformed byte sequences are replaced with U+FFFD instead of throwing, matching TextDecoder.
13140
13965
  function decodeString(value) {
13141
- if (value.byteLength > 1000 && typeof TextDecoder !== 'undefined') {
13142
- return (new TextDecoder()).decode(value);
13143
- }
13144
13966
  const result = [];
13145
13967
  function pushCodePoint(code) {
13146
13968
  if (code > 0xffff) {