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
@@ -1,21 +1,36 @@
1
1
  import { toByteArray } from 'base64-js';
2
- import { BlendMode, ImageResources, RenderingIntent } from './psd';
3
- import { PsdReader, readUnicodeString, readUint32, readUint16, readUint8, readFloat64, readBytes, skipBytes, readFloat32, readInt16, readFixedPoint32, readSignature, checkSignature, readSection, readColor, readInt32, readAsciiString } from './psdReader';
4
- import { PsdWriter, writeUnicodeString, writeUint32, writeUint8, writeFloat64, writeUint16, writeBytes, writeInt16, writeFloat32, writeFixedPoint32, writeUnicodeStringWithPadding, writeColor, writeSignature, writeSection, writeInt32, writeAsciiString, } from './psdWriter';
5
- import { createCanvasFromData, createEnum, MOCK_HANDLERS } from './helpers';
6
- import { decodeString, encodeString } from './utf8';
7
- import { ESliceBGColorType, ESliceHorzAlign, ESliceOrigin, ESliceType, ESliceVertAlign, frac, FractionDescriptor, parseTrackList, pathSelectionStateToReference, readVersionAndDescriptor, serializeTrackList, TimelineTrackDescriptor, TimeScopeDescriptor, writeVersionAndDescriptor } from './descriptor';
2
+ import { BlendMode, HalftoneScreen, ImageResources, KeyDescriptorItem, PrintTransferFunction, RenderingIntent, SavedPath } from './psd';
3
+ import { KeyDescriptorListDescriptor, parseKeyDescriptorList, readVectorMask, serializeKeyDescriptorList, writeVectorMask } from './additionalInfo';
4
+ import { PsdReader, readUnicodeString, readUint32, readUint16, readUint8, readFloat64, readBytes, skipBytes, readFloat32, readInt16, readFixedPoint32, readSignature, checkSignature, readSection, readColor, readInt32, readAsciiString, decodeUtf8, getUtf8StrictDecoder, getGbkStrictDecoder } from './psdReader';
5
+ import { PsdWriter, writeUnicodeString, writeUint32, writeUint8, writeFloat64, writeUint16, writeBytes, writeInt16, writeFloat32, writeFixedPoint32, writeUnicodeStringWithPadding, writeColor, writeSignature, writeSection, writeInt32, writeAsciiString, writeZeros } from './psdWriter';
6
+ import { clamp, ColorSpace, createCanvasFromData, createEnum, createImageData, imageDataToCanvas, MOCK_HANDLERS } from './helpers';
7
+ import { encodeString } from './utf8';
8
+ import { ESliceBGColorType, ESliceHorzAlign, ESliceOrigin, ESliceType, ESliceVertAlign, frac, FractionDescriptor, parseTrackList, pathSelectionStateToReference, DescriptorReferenceItem, readVersionAndDescriptor, serializeTrackList, TimelineTrackDescriptor, TimeScopeDescriptor, writeVersionAndDescriptor, DescriptorColor, DescriptorUnitsValue, parseColor, serializeColor, parseUnits, unitsValue, parsePercent, unitsPercentF , outputSettingsRoot } from './descriptor';
8
9
 
9
10
  export interface InternalImageResources extends ImageResources {
10
11
  layersGroup?: number[];
11
12
  layerGroupsEnabledId?: number[];
13
+ savedPathsOrigination?: KeyDescriptorItem[][];
14
+ }
15
+
16
+ export interface ResourceDocumentInfo {
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ export interface ResourceInfo extends ResourceDocumentInfo {
22
+ signature: string; // '8BIM', or 'MeSa' etc. for resources from other applications
23
+ id: number;
24
+ name: Uint8Array; // raw pascal string bytes
12
25
  }
13
26
 
14
27
  export interface ResourceHandler {
15
28
  key: number;
29
+ keyCount: number; // number of consecutive resource ids handled, starting at `key` (index is added to `key` when writing)
16
30
  has: (target: InternalImageResources) => boolean | number;
17
- read: (reader: PsdReader, target: InternalImageResources, left: () => number) => void;
18
- write: (writer: PsdWriter, target: InternalImageResources, index: number) => void;
31
+ read: (reader: PsdReader, target: InternalImageResources, left: () => number, info: ResourceInfo) => void;
32
+ write: (writer: PsdWriter, target: InternalImageResources, index: number, document: ResourceDocumentInfo) => void;
33
+ name?: (target: InternalImageResources, index: number) => string;
19
34
  }
20
35
 
21
36
  export const resourceHandlers: ResourceHandler[] = [];
@@ -24,12 +39,13 @@ export const resourceHandlersMap: { [key: number]: ResourceHandler } = {};
24
39
  function addHandler(
25
40
  key: number,
26
41
  has: (target: InternalImageResources) => boolean | number,
27
- read: (reader: PsdReader, target: InternalImageResources, left: () => number) => void,
28
- write: (writer: PsdWriter, target: InternalImageResources, index: number) => void,
42
+ read: (reader: PsdReader, target: InternalImageResources, left: () => number, info: ResourceInfo) => void,
43
+ write: (writer: PsdWriter, target: InternalImageResources, index: number, document: ResourceDocumentInfo) => void,
44
+ options: { keyCount?: number; name?: (target: InternalImageResources, index: number) => string; } = {},
29
45
  ) {
30
- const handler: ResourceHandler = { key, has, read, write };
46
+ const handler: ResourceHandler = { key, keyCount: options.keyCount ?? 1, has, read, write, name: options.name };
31
47
  resourceHandlers.push(handler);
32
- resourceHandlersMap[handler.key] = handler;
48
+ for (let i = 0; i < handler.keyCount; i++) resourceHandlersMap[handler.key + i] = handler;
33
49
  }
34
50
 
35
51
  const LOG_MOCK_HANDLERS = false;
@@ -49,7 +65,7 @@ function byteAt(value: string, index: number) {
49
65
 
50
66
  function readUtf8String(reader: PsdReader, length: number) {
51
67
  const buffer = readBytes(reader, length);
52
- return decodeString(buffer);
68
+ return decodeUtf8(reader, buffer);
53
69
  }
54
70
 
55
71
  function writeUtf8String(writer: PsdWriter, value: string) {
@@ -57,24 +73,13 @@ function writeUtf8String(writer: PsdWriter, value: string) {
57
73
  writeBytes(writer, buffer);
58
74
  }
59
75
 
60
- let gbkDecoder: TextDecoder | null | undefined;
61
-
62
- function getGbkDecoder() {
63
- if (gbkDecoder === undefined) {
64
- try {
65
- gbkDecoder = new TextDecoder('gbk');
66
- } catch {
67
- gbkDecoder = null;
68
- }
69
- }
70
-
71
- return gbkDecoder;
72
- }
73
-
74
76
  function readEncodedString(reader: PsdReader) {
75
77
  const length = readUint8(reader);
76
78
  const buffer = readBytes(reader, length);
79
+ return decodeEncodedString(reader, buffer);
80
+ }
77
81
 
82
+ function decodeEncodedString(reader: PsdReader, buffer: Uint8Array) {
78
83
  let notAscii = false;
79
84
  for (let i = 0; i < buffer.byteLength; i++) {
80
85
  if (buffer[i] & 0x80) {
@@ -84,19 +89,21 @@ function readEncodedString(reader: PsdReader) {
84
89
  }
85
90
 
86
91
  if (notAscii) {
87
- const decoder = getGbkDecoder();
88
-
89
- if (decoder) {
90
- try {
91
- return decoder.decode(buffer);
92
- } catch { }
92
+ // encoding is ambiguous, many GBK strings are also valid UTF-8 and vice versa, GBK is preferred,
93
+ // UTF-8 is only used if the string is not valid GBK
94
+ for (const decoder of [getGbkStrictDecoder(reader), getUtf8StrictDecoder(reader)]) {
95
+ if (decoder) {
96
+ try {
97
+ return decoder.decode(buffer);
98
+ } catch { }
99
+ }
93
100
  }
94
101
  }
95
102
 
96
- return decodeString(buffer);
103
+ return decodeUtf8(reader, buffer);
97
104
  }
98
105
 
99
- function writeEncodedString(writer: PsdWriter, value: string) {
106
+ function toShortAsciiString(value: string) {
100
107
  let ascii = '';
101
108
 
102
109
  for (let i = 0; i < value.length;) {
@@ -106,22 +113,23 @@ function writeEncodedString(writer: PsdWriter, value: string) {
106
113
  }
107
114
 
108
115
  // length is stored in a single byte
109
- if (ascii.length > 255) ascii = ascii.substring(0, 255);
116
+ return ascii.length > 255 ? ascii.substring(0, 255) : ascii;
117
+ }
110
118
 
111
- const buffer = encodeString(ascii);
119
+ function writeEncodedString(writer: PsdWriter, value: string) {
120
+ const buffer = encodeString(toShortAsciiString(value));
112
121
  writeUint8(writer, buffer.byteLength);
113
122
  writeBytes(writer, buffer);
114
123
  }
115
124
 
116
- MOCK_HANDLERS && addHandler(
125
+ addHandler(
117
126
  1028, // IPTC-NAA record
118
- target => (target as any)._ir1028 !== undefined,
127
+ target => target.iptcData !== undefined,
119
128
  (reader, target, left) => {
120
- LOG_MOCK_HANDLERS && console.log('image resource 1028', left());
121
- (target as any)._ir1028 = readBytes(reader, left());
129
+ target.iptcData = readBytes(reader, left());
122
130
  },
123
131
  (writer, target) => {
124
- writeBytes(writer, (target as any)._ir1028);
132
+ writeBytes(writer, target.iptcData!);
125
133
  },
126
134
  );
127
135
 
@@ -183,14 +191,14 @@ interface PrintInformationDescriptor {
183
191
  };
184
192
  }
185
193
 
186
- MOCK_HANDLERS && addHandler(
194
+ addHandler(
187
195
  1085, // Windows DEVMODE. Variable OS specific info for Windows.
188
- target => (target as any)._ir1085 !== undefined,
196
+ target => target.windowsDevMode !== undefined,
189
197
  (reader, target, left) => {
190
- (target as any)._ir1085 = readBytes(reader, left());
198
+ target.windowsDevMode = readBytes(reader, left());
191
199
  },
192
200
  (writer, target) => {
193
- writeBytes(writer, (target as any)._ir1085);
201
+ writeBytes(writer, target.windowsDevMode!);
194
202
  }
195
203
  );
196
204
 
@@ -208,6 +216,7 @@ addHandler(
208
216
  const info = target.printInformation;
209
217
 
210
218
  if (desc.PstS !== undefined) info.printerManagesColors = desc.PstS;
219
+ if (desc.ClrS === 'ClrS.Grys') info.printerProfileColorSpace = 'grayscale';
211
220
  if (desc['Nm '] !== undefined) info.printerProfile = desc['Nm '];
212
221
  if (desc.MpBl !== undefined) info.blackPointCompensation = desc.MpBl;
213
222
  if (desc.printSixteenBit !== undefined) info.printSixteenBit = desc.printSixteenBit;
@@ -233,13 +242,13 @@ addHandler(
233
242
  desc.PstS = true;
234
243
  } else {
235
244
  if (info.hardProof !== undefined) desc.hardProof = !!info.hardProof;
236
- desc.ClrS = 'ClrS.RGBC'; // TODO: ???
237
- desc['Nm '] = info.printerProfile ?? 'CIE RGB';
245
+ desc.ClrS = info.printerProfileColorSpace === 'grayscale' ? 'ClrS.Grys' : 'ClrS.RGBC';
246
+ if (info.printerProfile !== undefined) desc['Nm '] = info.printerProfile;
238
247
  }
239
248
 
240
249
  desc.Inte = Inte.encode(info.renderingIntent);
241
250
 
242
- if (!info.printerManagesColors) desc.MpBl = !!info.blackPointCompensation;
251
+ if (!info.printerManagesColors && info.blackPointCompensation !== undefined) desc.MpBl = !!info.blackPointCompensation;
243
252
 
244
253
  desc.printSixteenBit = !!info.printSixteenBit;
245
254
  desc.printerName = info.printerName || '';
@@ -251,9 +260,9 @@ addHandler(
251
260
  MpBl: !!info.proofSetup.blackPointCompensation,
252
261
  paperWhite: !!info.proofSetup.paperWhite,
253
262
  };
254
- } else {
263
+ } else if (info.proofSetup) {
255
264
  desc.printProofSetup = {
256
- Bltn: info.proofSetup?.builtin ? `builtinProof.${info.proofSetup.builtin}` : 'builtinProof.proofCMYK',
265
+ Bltn: `builtinProof.${info.proofSetup.builtin || 'proofCMYK'}`,
257
266
  };
258
267
  }
259
268
 
@@ -261,19 +270,101 @@ addHandler(
261
270
  },
262
271
  );
263
272
 
264
- MOCK_HANDLERS && addHandler(
273
+ interface PrintStyleDescriptor {
274
+ Cptn: boolean;
275
+ Clbr: boolean;
276
+ RgsM: boolean;
277
+ CrnC: boolean;
278
+ CntC: boolean;
279
+ Lbls: boolean;
280
+ Ngtv: boolean;
281
+ EmlD: boolean;
282
+ Intr: boolean;
283
+ Bckg: DescriptorColor;
284
+ BrdT: DescriptorUnitsValue;
285
+ 'Bld ': DescriptorUnitsValue;
286
+ Rslt: DescriptorUnitsValue;
287
+ vectorData: boolean;
288
+ PgPs: string;
289
+ Left: DescriptorUnitsValue;
290
+ 'Top ': DescriptorUnitsValue;
291
+ 'Scl ': DescriptorUnitsValue;
292
+ cropWhenPrinting?: boolean;
293
+ cropRectBottom?: number;
294
+ cropRectLeft?: number;
295
+ cropRectRight?: number;
296
+ cropRectTop?: number;
297
+ }
298
+
299
+ addHandler(
265
300
  1083, // Print style
266
- target => (target as any)._ir1083 !== undefined,
267
- (reader, target, left) => {
268
- LOG_MOCK_HANDLERS && console.log('image resource 1083', left());
269
- (target as any)._ir1083 = readBytes(reader, left());
301
+ target => target.printStyle !== undefined,
302
+ (reader, target) => {
303
+ const desc: PrintStyleDescriptor = readVersionAndDescriptor(reader);
304
+
305
+ target.printStyle = {
306
+ caption: desc.Cptn,
307
+ calibrationBars: desc.Clbr,
308
+ registrationMarks: desc.RgsM,
309
+ cornerCropMarks: desc.CrnC,
310
+ centerCropMarks: desc.CntC,
311
+ labels: desc.Lbls,
312
+ negative: desc.Ngtv,
313
+ emulsionDown: desc.EmlD,
314
+ interpolate: desc.Intr,
315
+ backgroundColor: parseColor(desc.Bckg),
316
+ border: parseUnits(desc.BrdT),
317
+ bleed: parseUnits(desc['Bld ']),
318
+ resolution: parseUnits(desc.Rslt),
319
+ vectorData: desc.vectorData,
320
+ pagePosition: desc.PgPs === 'PgPs.PgPC' ? 'centered' : desc.PgPs,
321
+ left: parseUnits(desc.Left),
322
+ top: parseUnits(desc['Top ']),
323
+ scale: parsePercent(desc['Scl ']),
324
+ };
270
325
 
271
- // TODO:
272
- // const desc = readVersionAndDescriptor(reader);
273
- // console.log('1083', require('util').inspect(desc, false, 99, true));
326
+ if (desc.cropWhenPrinting !== undefined) {
327
+ target.printStyle.cropWhenPrinting = desc.cropWhenPrinting;
328
+ target.printStyle.cropRect = {
329
+ top: desc.cropRectTop || 0,
330
+ left: desc.cropRectLeft || 0,
331
+ bottom: desc.cropRectBottom || 0,
332
+ right: desc.cropRectRight || 0,
333
+ };
334
+ }
274
335
  },
275
336
  (writer, target) => {
276
- writeBytes(writer, (target as any)._ir1083);
337
+ const style = target.printStyle!;
338
+ const desc: PrintStyleDescriptor = {
339
+ Cptn: !!style.caption,
340
+ Clbr: !!style.calibrationBars,
341
+ RgsM: !!style.registrationMarks,
342
+ CrnC: !!style.cornerCropMarks,
343
+ CntC: !!style.centerCropMarks,
344
+ Lbls: !!style.labels,
345
+ Ngtv: !!style.negative,
346
+ EmlD: !!style.emulsionDown,
347
+ Intr: !!style.interpolate,
348
+ Bckg: serializeColor(style.backgroundColor || { r: 255, g: 255, b: 255 }),
349
+ BrdT: unitsValue(style.border || { units: 'Distance', value: 0 }, 'border'),
350
+ 'Bld ': unitsValue(style.bleed || { units: 'Distance', value: 0 }, 'bleed'),
351
+ Rslt: unitsValue(style.resolution || { units: 'Pixels', value: 72 }, 'resolution'),
352
+ vectorData: style.vectorData ?? true,
353
+ PgPs: (!style.pagePosition || style.pagePosition === 'centered') ? 'PgPs.PgPC' : style.pagePosition,
354
+ Left: unitsValue(style.left || { units: 'Distance', value: 0 }, 'left'),
355
+ 'Top ': unitsValue(style.top || { units: 'Distance', value: 0 }, 'top'),
356
+ 'Scl ': unitsPercentF(style.scale ?? 1),
357
+ };
358
+
359
+ if (style.cropWhenPrinting !== undefined || style.cropRect) {
360
+ desc.cropWhenPrinting = !!style.cropWhenPrinting;
361
+ desc.cropRectBottom = style.cropRect?.bottom ?? 0;
362
+ desc.cropRectLeft = style.cropRect?.left ?? 0;
363
+ desc.cropRectRight = style.cropRect?.right ?? 0;
364
+ desc.cropRectTop = style.cropRect?.top ?? 0;
365
+ }
366
+
367
+ writeVersionAndDescriptor(writer, '', 'printOutputOptions', desc);
277
368
  },
278
369
  );
279
370
 
@@ -372,15 +463,72 @@ addHandler(
372
463
  },
373
464
  );
374
465
 
375
- MOCK_HANDLERS && addHandler(
376
- 1077,
377
- target => (target as any)._ir1077 !== undefined,
466
+ const displayInfoKinds = ['selected', 'protected', 'spot'] as const;
467
+ const supportedColorSpaces: number[] = [ColorSpace.RGB, ColorSpace.HSB, ColorSpace.CMYK, ColorSpace.Lab, ColorSpace.Grayscale];
468
+
469
+ function readDisplayInfo(reader: PsdReader) {
470
+ const info: NonNullable<ImageResources['alphaChannelDisplayInfo']>[number] = { opacity: 0, kind: 'selected' };
471
+ const colorSpace = reader.view.getUint16(reader.offset);
472
+
473
+ if (supportedColorSpaces.indexOf(colorSpace) !== -1) {
474
+ info.color = readColor(reader);
475
+ } else {
476
+ // keep color in unknown color space as it is, so the channel info is not lost or misaligned
477
+ skipBytes(reader, 2);
478
+ info.rawColor = { colorSpace, values: [readUint16(reader), readUint16(reader), readUint16(reader), readUint16(reader)] };
479
+ }
480
+
481
+ info.opacity = readUint16(reader) / 100;
482
+ const kind = displayInfoKinds[readUint8(reader)];
483
+ if (!kind) throw new Error('Invalid display info kind');
484
+ info.kind = kind;
485
+ return info;
486
+ }
487
+
488
+ addHandler(
489
+ 1007, // Display info (obsolete, replaced by 1077), written back as 1077
490
+ () => false,
378
491
  (reader, target, left) => {
379
- LOG_MOCK_HANDLERS && console.log('image resource 1077', left());
380
- (target as any)._ir1077 = readBytes(reader, left());
492
+ const info: NonNullable<ImageResources['alphaChannelDisplayInfo']> = [];
493
+
494
+ while (left() >= 14) {
495
+ info.push(readDisplayInfo(reader));
496
+ skipBytes(reader, 1); // padding
497
+ }
498
+
499
+ // 1077 resource has more precise data and is used if present
500
+ if (!target.alphaChannelDisplayInfo) target.alphaChannelDisplayInfo = info;
501
+ },
502
+ () => { },
503
+ );
504
+
505
+ addHandler(
506
+ 1077, // Display info (alpha and spot channels)
507
+ target => target.alphaChannelDisplayInfo !== undefined,
508
+ (reader, target, left) => {
509
+ const version = readUint32(reader);
510
+ if (version !== 1) throw new Error(`Invalid display info version: ${version}`);
511
+
512
+ target.alphaChannelDisplayInfo = [];
513
+
514
+ while (left() >= 13) {
515
+ target.alphaChannelDisplayInfo.push(readDisplayInfo(reader));
516
+ }
381
517
  },
382
518
  (writer, target) => {
383
- writeBytes(writer, (target as any)._ir1077);
519
+ writeUint32(writer, 1); // version
520
+
521
+ for (const info of target.alphaChannelDisplayInfo!) {
522
+ if (!info.color && info.rawColor) {
523
+ writeUint16(writer, info.rawColor.colorSpace);
524
+ for (let i = 0; i < 4; i++) writeUint16(writer, info.rawColor.values[i] | 0);
525
+ } else {
526
+ writeColor(writer, info.color || { r: 255, g: 0, b: 0 });
527
+ }
528
+
529
+ writeUint16(writer, Math.round(clamp(info.opacity, 0, 1) * 100));
530
+ writeUint8(writer, Math.max(0, displayInfoKinds.indexOf(info.kind)));
531
+ }
384
532
  },
385
533
  );
386
534
 
@@ -474,42 +622,132 @@ addHandler(
474
622
  },
475
623
  );
476
624
 
477
- MOCK_HANDLERS && addHandler(
478
- 10000, // Print flags
479
- target => (target as any)._ir10000 !== undefined,
480
- (reader, target, left) => {
481
- LOG_MOCK_HANDLERS && console.log('image resource 10000', left());
482
- (target as any)._ir10000 = readBytes(reader, left());
625
+ addHandler(
626
+ 10000, // Print flags information
627
+ target => target.printFlagsInfo !== undefined,
628
+ (reader, target) => {
629
+ const version = readUint16(reader);
630
+ if (version !== 1) throw new Error(`Invalid print flags information version: ${version}`);
631
+ const centerCropMarks = !!readUint8(reader);
632
+ skipBytes(reader, 1);
633
+ const bleedWidth = readUint32(reader);
634
+ const bleedWidthScale = readUint16(reader);
635
+ target.printFlagsInfo = { centerCropMarks, bleedWidth, bleedWidthScale };
483
636
  },
484
637
  (writer, target) => {
485
- writeBytes(writer, (target as any)._ir10000);
638
+ const info = target.printFlagsInfo!;
639
+ writeUint16(writer, 1); // version
640
+ writeUint8(writer, info.centerCropMarks ? 1 : 0);
641
+ writeUint8(writer, 0);
642
+ writeUint32(writer, info.bleedWidth | 0);
643
+ writeUint16(writer, info.bleedWidthScale | 0);
486
644
  },
487
645
  );
488
646
 
489
- MOCK_HANDLERS && addHandler(
490
- 1013, // Color halftoning
491
- target => (target as any)._ir1013 !== undefined,
492
- (reader, target, left) => {
493
- LOG_MOCK_HANDLERS && console.log('image resource 1013', left());
494
- (target as any)._ir1013 = readBytes(reader, left());
647
+ addHandler(
648
+ 1046, // Indexed color table count
649
+ target => target.indexedColorCount !== undefined,
650
+ (reader, target) => {
651
+ target.indexedColorCount = readUint16(reader);
495
652
  },
496
653
  (writer, target) => {
497
- writeBytes(writer, (target as any)._ir1013);
654
+ writeUint16(writer, target.indexedColorCount!);
498
655
  },
499
656
  );
500
657
 
501
- MOCK_HANDLERS && addHandler(
502
- 1016, // Color transfer functions
503
- target => (target as any)._ir1016 !== undefined,
504
- (reader, target, left) => {
505
- LOG_MOCK_HANDLERS && console.log('image resource 1016', left());
506
- (target as any)._ir1016 = readBytes(reader, left());
658
+ addHandler(
659
+ 1047, // Transparency index
660
+ target => target.transparencyIndex !== undefined,
661
+ (reader, target) => {
662
+ target.transparencyIndex = readUint16(reader);
507
663
  },
508
664
  (writer, target) => {
509
- writeBytes(writer, (target as any)._ir1016);
665
+ writeUint16(writer, target.transparencyIndex!);
510
666
  },
511
667
  );
512
668
 
669
+ const halftoneFrequencyUnits: HalftoneScreen['frequencyUnits'][] = ['lines/inch', 'lines/cm']; // 1, 2
670
+ const halftoneShapes: (HalftoneScreen['shape'] | undefined)[] = ['round', 'ellipse', 'line', 'square', 'cross', undefined, 'diamond']; // 0 - 6
671
+
672
+ function readHalftoneScreens(reader: PsdReader, left: () => number) {
673
+ const screens: HalftoneScreen[] = [];
674
+
675
+ while (left() >= 18) {
676
+ const frequency = readFixedPoint32(reader);
677
+ const frequencyUnits = halftoneFrequencyUnits[readUint16(reader) - 1];
678
+ if (!frequencyUnits) throw new Error('Invalid halftone screen frequency units');
679
+ const angle = readFixedPoint32(reader);
680
+ const shape = halftoneShapes[readInt16(reader)];
681
+ if (!shape) throw new Error('Invalid halftone screen shape');
682
+ skipBytes(reader, 4);
683
+ const accurateScreens = !!readUint8(reader);
684
+ const printerDefaultScreens = !!readUint8(reader);
685
+ screens.push({ frequency, frequencyUnits, angle, shape, accurateScreens, printerDefaultScreens });
686
+ }
687
+
688
+ return screens;
689
+ }
690
+
691
+ function writeHalftoneScreens(writer: PsdWriter, screens: HalftoneScreen[]) {
692
+ for (const screen of screens) {
693
+ writeFixedPoint32(writer, screen.frequency);
694
+ writeUint16(writer, Math.max(0, halftoneFrequencyUnits.indexOf(screen.frequencyUnits)) + 1);
695
+ writeFixedPoint32(writer, screen.angle);
696
+ writeInt16(writer, Math.max(0, halftoneShapes.indexOf(screen.shape)));
697
+ writeZeros(writer, 4);
698
+ writeUint8(writer, screen.accurateScreens ? 1 : 0);
699
+ writeUint8(writer, screen.printerDefaultScreens ? 1 : 0);
700
+ }
701
+ }
702
+
703
+ addHandler(
704
+ 1012, // Grayscale and multichannel halftoning information
705
+ target => target.grayscaleHalftoneScreens !== undefined,
706
+ (reader, target, left) => target.grayscaleHalftoneScreens = readHalftoneScreens(reader, left),
707
+ (writer, target) => writeHalftoneScreens(writer, target.grayscaleHalftoneScreens!),
708
+ );
709
+
710
+ addHandler(
711
+ 1013, // Color halftoning information
712
+ target => target.colorHalftoneScreens !== undefined,
713
+ (reader, target, left) => target.colorHalftoneScreens = readHalftoneScreens(reader, left),
714
+ (writer, target) => writeHalftoneScreens(writer, target.colorHalftoneScreens!),
715
+ );
716
+
717
+ function readTransferFunctions(reader: PsdReader, left: () => number) {
718
+ const functions: PrintTransferFunction[] = [];
719
+
720
+ while (left() >= 28) {
721
+ const curve: number[] = [];
722
+ for (let i = 0; i < 13; i++) curve.push(readInt16(reader));
723
+ const override = !!readUint16(reader);
724
+ functions.push({ curve, override });
725
+ }
726
+
727
+ return functions;
728
+ }
729
+
730
+ function writeTransferFunctions(writer: PsdWriter, functions: PrintTransferFunction[]) {
731
+ for (const f of functions) {
732
+ for (let i = 0; i < 13; i++) writeInt16(writer, f.curve[i] ?? -1);
733
+ writeUint16(writer, f.override ? 1 : 0);
734
+ }
735
+ }
736
+
737
+ addHandler(
738
+ 1015, // Grayscale and multichannel transfer function
739
+ target => target.grayscaleTransferFunctions !== undefined,
740
+ (reader, target, left) => target.grayscaleTransferFunctions = readTransferFunctions(reader, left),
741
+ (writer, target) => writeTransferFunctions(writer, target.grayscaleTransferFunctions!),
742
+ );
743
+
744
+ addHandler(
745
+ 1016, // Color transfer functions
746
+ target => target.colorTransferFunctions !== undefined,
747
+ (reader, target, left) => target.colorTransferFunctions = readTransferFunctions(reader, left),
748
+ (writer, target) => writeTransferFunctions(writer, target.colorTransferFunctions!),
749
+ );
750
+
513
751
  interface CountInformationDesc {
514
752
  Vrsn: 1;
515
753
  countGroupList: {
@@ -631,17 +869,20 @@ addHandler(
631
869
 
632
870
  if (version !== 1) throw new Error(`Invalid 1032 resource version: ${version}`);
633
871
 
634
- target.gridAndGuidesInformation = {
635
- grid: { horizontal, vertical },
636
- guides: [],
637
- };
872
+ const previous = target.gridAndGuidesInformation?.guides; // in case guides were already read from 1092
873
+ const guides: Guide[] = [];
638
874
 
639
875
  for (let i = 0; i < count; i++) {
640
- target.gridAndGuidesInformation.guides!.push({
876
+ const guide: Guide = {
641
877
  location: readInt32(reader) / 32,
642
878
  direction: readUint8(reader) ? 'horizontal' : 'vertical'
643
- });
879
+ };
880
+ if (previous?.length === count && previous[i].color) guide.color = previous[i].color;
881
+ if (previous?.length === count && previous[i].id) guide.id = previous[i].id;
882
+ guides.push(guide);
644
883
  }
884
+
885
+ target.gridAndGuidesInformation = { grid: { horizontal, vertical }, guides };
645
886
  },
646
887
  (writer, target) => {
647
888
  const info = target.gridAndGuidesInformation!;
@@ -660,6 +901,91 @@ addHandler(
660
901
  },
661
902
  );
662
903
 
904
+ type Guide = NonNullable<NonNullable<ImageResources['gridAndGuidesInformation']>['guides']>[number];
905
+
906
+ const defaultGuideColor = { r: 74, g: 255, b: 255 }; // Photoshop default (cyan)
907
+
908
+ addHandler(
909
+ 1092, // Grid and guides information with guide colors (same as 1032 with color of each guide)
910
+ target => target.gridAndGuidesInformation !== undefined,
911
+ (reader, target) => {
912
+ const version = readUint32(reader);
913
+ if (version !== 2) throw new Error(`Invalid 1092 resource version: ${version}`);
914
+ const horizontal = readUint32(reader);
915
+ const vertical = readUint32(reader);
916
+ const count = readUint32(reader);
917
+ const guides: Guide[] = [];
918
+
919
+ for (let i = 0; i < count; i++) {
920
+ const location = readInt32(reader) / 32;
921
+ const direction = readUint8(reader) ? 'horizontal' : 'vertical';
922
+ skipBytes(reader, 1);
923
+ const color = { r: readUint8(reader), g: readUint8(reader), b: readUint8(reader) };
924
+ guides.push({ location, direction, color });
925
+ }
926
+
927
+ const existing = target.gridAndGuidesInformation?.guides;
928
+
929
+ if (existing && existing.length === count) {
930
+ existing.forEach((guide, i) => guide.color = guides[i].color);
931
+ } else {
932
+ target.gridAndGuidesInformation = { grid: { horizontal, vertical }, guides };
933
+ }
934
+ },
935
+ (writer, target) => {
936
+ const info = target.gridAndGuidesInformation!;
937
+ const grid = info.grid || { horizontal: 18 * 32, vertical: 18 * 32 };
938
+ const guides = info.guides || [];
939
+
940
+ writeUint32(writer, 2); // version
941
+ writeUint32(writer, Math.round(grid.horizontal));
942
+ writeUint32(writer, Math.round(grid.vertical));
943
+ writeUint32(writer, guides.length);
944
+
945
+ for (const g of guides) {
946
+ const color = g.color || defaultGuideColor;
947
+ writeInt32(writer, Math.round(g.location * 32));
948
+ writeUint8(writer, g.direction === 'horizontal' ? 1 : 0);
949
+ writeUint8(writer, 0);
950
+ writeUint8(writer, Math.round(color.r));
951
+ writeUint8(writer, Math.round(color.g));
952
+ writeUint8(writer, Math.round(color.b));
953
+ }
954
+ },
955
+ );
956
+
957
+ const emptyGuid = '00000000-0000-0000-0000-000000000000';
958
+
959
+ addHandler(
960
+ 1097, // Guide ids
961
+ target => !!target.gridAndGuidesInformation && (target.gridAndGuidesInformation.guides || []).every(g => g.id),
962
+ (reader, target) => {
963
+ const count = readUint32(reader);
964
+ const ids: string[] = [];
965
+
966
+ for (let i = 0; i < count; i++) {
967
+ ids.push(readAsciiString(reader, readUint32(reader)));
968
+ readUint32(reader); // unknown, always 0
969
+ readAsciiString(reader, readUint32(reader)); // unknown, always empty guid
970
+ }
971
+
972
+ const guides = target.gridAndGuidesInformation?.guides;
973
+ if (guides && guides.length === count) guides.forEach((guide, i) => guide.id = ids[i]);
974
+ },
975
+ (writer, target) => {
976
+ const guides = target.gridAndGuidesInformation!.guides || [];
977
+ writeUint32(writer, guides.length);
978
+
979
+ for (const g of guides) {
980
+ writeUint32(writer, g.id!.length);
981
+ writeAsciiString(writer, g.id!);
982
+ writeUint32(writer, 0);
983
+ writeUint32(writer, emptyGuid.length);
984
+ writeAsciiString(writer, emptyGuid);
985
+ }
986
+ },
987
+ );
988
+
663
989
  interface LayerCompsDescriptor {
664
990
  list: {
665
991
  _classID: 'Comp';
@@ -1254,16 +1580,14 @@ addHandler(
1254
1580
  },
1255
1581
  );
1256
1582
 
1257
- MOCK_HANDLERS && addHandler(
1583
+ addHandler(
1258
1584
  1039, // ICC Profile
1259
- target => (target as any)._ir1039 !== undefined,
1585
+ target => target.iccProfile !== undefined,
1260
1586
  (reader, target, left) => {
1261
- // TODO: this is raw bytes, just return as a byte array
1262
- LOG_MOCK_HANDLERS && console.log('image resource 1039', left());
1263
- (target as any)._ir1039 = readBytes(reader, left());
1587
+ target.iccProfile = readBytes(reader, left());
1264
1588
  },
1265
1589
  (writer, target) => {
1266
- writeBytes(writer, (target as any)._ir1039);
1590
+ writeBytes(writer, target.iccProfile!);
1267
1591
  },
1268
1592
  );
1269
1593
 
@@ -1274,6 +1598,47 @@ addHandler(
1274
1598
  (writer, target) => writeUint32(writer, target.idsSeedNumber!),
1275
1599
  );
1276
1600
 
1601
+ addHandler(
1602
+ 1038, // Color samplers (Photoshop 5.0, replaced by 1073 in CS3)
1603
+ target => target.colorSamplers !== undefined,
1604
+ (reader, target, left) => {
1605
+ const version = readUint32(reader);
1606
+ if (version !== 1) throw new Error(`Invalid color samplers version: ${version}`);
1607
+ const count = readUint32(reader);
1608
+ target.colorSamplers = [];
1609
+
1610
+ for (let i = 0; i < count && left() >= 10; i++) {
1611
+ // vertical and horizontal position in 1/16 pixel (inferred from sample files, the specification describes
1612
+ // these values as horizontal and vertical fixed point numbers)
1613
+ const y = readInt32(reader) / 16;
1614
+ const x = readInt32(reader) / 16;
1615
+ const colorSpace = readInt16(reader);
1616
+ target.colorSamplers.push({ x, y, colorSpace });
1617
+ }
1618
+ },
1619
+ (writer, target) => {
1620
+ writeUint32(writer, 1); // version
1621
+ writeUint32(writer, target.colorSamplers!.length);
1622
+
1623
+ for (const sampler of target.colorSamplers!) {
1624
+ writeInt32(writer, Math.round(sampler.y * 16));
1625
+ writeInt32(writer, Math.round(sampler.x * 16));
1626
+ writeInt16(writer, sampler.colorSpace);
1627
+ }
1628
+ },
1629
+ );
1630
+
1631
+ addHandler(
1632
+ 1042, // Effects visible (only present when effects are hidden)
1633
+ target => target.effectsVisible !== undefined,
1634
+ (reader, target) => {
1635
+ target.effectsVisible = !!readUint8(reader);
1636
+ },
1637
+ (writer, target) => {
1638
+ writeUint8(writer, target.effectsVisible ? 1 : 0);
1639
+ },
1640
+ );
1641
+
1277
1642
  addHandler(
1278
1643
  1036,
1279
1644
  target => target.thumbnail !== undefined || target.thumbnailRaw !== undefined,
@@ -1281,14 +1646,39 @@ addHandler(
1281
1646
  const format = readUint32(reader); // 1 = kJpegRGB, 0 = kRawRGB
1282
1647
  const width = readUint32(reader);
1283
1648
  const height = readUint32(reader);
1284
- readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
1649
+ const widthBytes = readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
1285
1650
  readUint32(reader); // totalSize = widthBytes * height * planes
1286
1651
  readUint32(reader); // sizeAfterCompression
1287
1652
  const bitsPerPixel = readUint16(reader); // 24
1288
- const planes = readUint16(reader); // 1
1653
+ readUint16(reader); // planes, should be 1 (some applications write 3)
1654
+
1655
+ if (format === 0 && bitsPerPixel === 24 && widthBytes >= width * 3 && widthBytes * height <= left()) {
1656
+ // raw RGB data, rows are padded to 4 bytes
1657
+ if (reader.useRawThumbnail) {
1658
+ reader.logMissingFeatures && reader.log('Raw thumbnail data is not supported with useRawThumbnail option');
1659
+ skipBytes(reader, left());
1660
+ return;
1661
+ }
1662
+
1663
+ const data = readBytes(reader, widthBytes * height);
1664
+ const imageData = createImageData(width, height);
1665
+
1666
+ for (let y = 0, dst = 0; y < height; y++) {
1667
+ for (let x = 0, src = y * widthBytes; x < width; x++, src += 3, dst += 4) {
1668
+ imageData.data[dst + 0] = data[src + 0];
1669
+ imageData.data[dst + 1] = data[src + 1];
1670
+ imageData.data[dst + 2] = data[src + 2];
1671
+ imageData.data[dst + 3] = 255;
1672
+ }
1673
+ }
1674
+
1675
+ target.thumbnail = imageDataToCanvas(imageData);
1676
+ skipBytes(reader, left());
1677
+ return;
1678
+ }
1289
1679
 
1290
- if (format !== 1 || bitsPerPixel !== 24 || planes !== 1) {
1291
- reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel}, planes: ${planes})`);
1680
+ if (format !== 1 || bitsPerPixel !== 24) {
1681
+ reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel})`);
1292
1682
  skipBytes(reader, left());
1293
1683
  return;
1294
1684
  }
@@ -1367,15 +1757,14 @@ addHandler(
1367
1757
  },
1368
1758
  );
1369
1759
 
1370
- MOCK_HANDLERS && addHandler(
1371
- 1058, // EXIF data 1.
1372
- target => (target as any)._ir1058 !== undefined,
1760
+ addHandler(
1761
+ 1058, // EXIF data 1
1762
+ target => target.exifData !== undefined,
1373
1763
  (reader, target, left) => {
1374
- LOG_MOCK_HANDLERS && console.log('image resource 1058', left());
1375
- (target as any)._ir1058 = readBytes(reader, left());
1764
+ target.exifData = readBytes(reader, left());
1376
1765
  },
1377
1766
  (writer, target) => {
1378
- writeBytes(writer, (target as any)._ir1058);
1767
+ writeBytes(writer, target.exifData!);
1379
1768
  },
1380
1769
  );
1381
1770
 
@@ -1401,32 +1790,116 @@ addHandler(
1401
1790
  },
1402
1791
  );
1403
1792
 
1404
- interface Descriptor1088 {
1793
+ interface Descriptor1088Read {
1405
1794
  'null': string[];
1406
1795
  }
1407
1796
 
1797
+ interface Descriptor1088Write {
1798
+ 'null': DescriptorReferenceItem[];
1799
+ }
1800
+
1408
1801
  addHandler(
1409
1802
  1088,
1410
1803
  target => target.pathSelectionState !== undefined,
1411
1804
  (reader, target, _left) => {
1412
- const desc: Descriptor1088 = readVersionAndDescriptor(reader);
1805
+ const desc: Descriptor1088Read = readVersionAndDescriptor(reader);
1413
1806
  target.pathSelectionState = desc['null'];
1414
1807
  },
1415
1808
  (writer, target) => {
1416
- const desc: Descriptor1088 = { 'null': pathSelectionStateToReference(target.pathSelectionState!) };
1809
+ const desc: Descriptor1088Write = { 'null': pathSelectionStateToReference(target.pathSelectionState!) };
1417
1810
  writeVersionAndDescriptor(writer, '', 'null', desc);
1418
1811
  },
1419
1812
  );
1420
1813
 
1421
- MOCK_HANDLERS && addHandler(
1422
- 1025,
1423
- target => (target as any)._ir1025 !== undefined,
1814
+ addHandler(
1815
+ 1025, // Work path
1816
+ target => target.workPath !== undefined,
1817
+ (reader, target, left, { width, height }) => {
1818
+ target.workPath = { paths: [] };
1819
+ readVectorMask(reader, target.workPath, width, height, left());
1820
+ skipBytes(reader, left());
1821
+ },
1822
+ (writer, target, _index, { width, height }) => {
1823
+ writeVectorMask(writer, target.workPath!, width, height);
1824
+ },
1825
+ );
1826
+
1827
+ const MAX_SAVED_PATHS = 999; // ids 2000-2998
1828
+
1829
+ addHandler(
1830
+ 2000, // Saved paths (one resource for each path, named after the path)
1831
+ target => target.savedPaths ? Math.min(target.savedPaths.length, MAX_SAVED_PATHS) : 0,
1832
+ (reader, target, left, { signature, id, name, width, height }) => {
1833
+ if (signature !== '8BIM') { // ImageReady ('MeSa') resources use the same ids for other data
1834
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${signature} ${id}`);
1835
+ skipBytes(reader, left());
1836
+ return;
1837
+ }
1838
+
1839
+ const savedPaths = target.savedPaths ??= [];
1840
+ const path: SavedPath = { name: decodeEncodedString(reader, name), paths: [] };
1841
+ readVectorMask(reader, path, width, height, left());
1842
+ skipBytes(reader, left());
1843
+
1844
+ const keyDescriptorList = target.savedPathsOrigination?.[savedPaths.length];
1845
+ if (keyDescriptorList) path.vectorOrigination = { keyDescriptorList };
1846
+
1847
+ savedPaths.push(path);
1848
+ },
1849
+ (writer, target, index, { width, height }) => {
1850
+ writeVectorMask(writer, target.savedPaths![index], width, height);
1851
+ },
1852
+ { keyCount: MAX_SAVED_PATHS, name: (target, index) => toShortAsciiString(target.savedPaths![index].name) },
1853
+ );
1854
+
1855
+ interface OriginPathInfoDescriptor {
1856
+ keyRootDescriptorList: KeyDescriptorListDescriptor[];
1857
+ }
1858
+
1859
+ addHandler(
1860
+ 3000, // Origin path info (origination data of saved paths)
1861
+ target => !!target.savedPaths?.some(path => path.vectorOrigination),
1424
1862
  (reader, target, left) => {
1425
- LOG_MOCK_HANDLERS && console.log('image resource 1025', left());
1426
- (target as any)._ir1025 = readBytes(reader, left());
1863
+ const desc = readVersionAndDescriptor(reader) as OriginPathInfoDescriptor;
1864
+
1865
+ desc.keyRootDescriptorList.forEach((item, i) => {
1866
+ const keyDescriptorList = parseKeyDescriptorList(item);
1867
+ if (!keyDescriptorList.length) return; // written for paths without origination data
1868
+
1869
+ const path = target.savedPaths?.[i];
1870
+
1871
+ if (path) {
1872
+ path.vectorOrigination = { keyDescriptorList };
1873
+ } else { // saved paths are stored after this resource
1874
+ (target.savedPathsOrigination ??= [])[i] = keyDescriptorList;
1875
+ }
1876
+ });
1877
+
1878
+ skipBytes(reader, left());
1427
1879
  },
1428
1880
  (writer, target) => {
1429
- writeBytes(writer, (target as any)._ir1025);
1881
+ const desc: OriginPathInfoDescriptor = {
1882
+ keyRootDescriptorList: target.savedPaths!.slice(0, MAX_SAVED_PATHS)
1883
+ .map(path => serializeKeyDescriptorList(path.vectorOrigination?.keyDescriptorList ?? [])),
1884
+ };
1885
+ writeVersionAndDescriptor(writer, '', 'null', desc);
1886
+ },
1887
+ { name: () => 'OriginDataIRB' },
1888
+ );
1889
+
1890
+ addHandler(
1891
+ 2999, // Clipping path
1892
+ target => target.clippingPath !== undefined,
1893
+ (reader, target, left) => {
1894
+ const name = readEncodedString(reader);
1895
+ const flatness = readFixedPoint32(reader);
1896
+ skipBytes(reader, left()); // fill rule (ignored by Photoshop)
1897
+ target.clippingPath = { name, flatness };
1898
+ },
1899
+ (writer, target) => {
1900
+ writeEncodedString(writer, target.clippingPath!.name);
1901
+ writeFixedPoint32(writer, target.clippingPath!.flatness);
1902
+ writeUint16(writer, 1); // fill rule (ignored by Photoshop)
1430
1903
  },
1431
1904
  );
1432
1905
 
@@ -1456,13 +1929,28 @@ interface AnimationsDescriptor {
1456
1929
  FSts: AnimationDescriptor[];
1457
1930
  }
1458
1931
 
1932
+ // plug-in resources are written with consecutive ids starting at 4000, in the same order as Photoshop writes them
1933
+ function pluginResourceKeys(target: InternalImageResources) {
1934
+ const keys: string[] = [];
1935
+ if (target.outputSettings !== undefined) keys.push('mset');
1936
+ if (target.animations !== undefined) keys.push('mani', 'mfri');
1937
+ return keys;
1938
+ }
1939
+
1459
1940
  addHandler(
1460
- 4000, // Plug-In resource(s)
1461
- target => target.animations !== undefined,
1941
+ 4000, // Plug-In resource(s), identified by key stored in the data (ids 4000-4999)
1942
+ target => pluginResourceKeys(target).length,
1462
1943
  (reader, target, left) => {
1463
1944
  const key = readSignature(reader);
1464
1945
 
1465
- if (key === 'mani') {
1946
+ if (key === 'mset') {
1947
+ // Save for Web output settings
1948
+ target.outputSettings = readVersionAndDescriptor(reader, true);
1949
+ skipBytes(reader, left());
1950
+ } else if (key === 'mfri') {
1951
+ // frame count, active frame and animation count, same data as in animations (mani)
1952
+ skipBytes(reader, left());
1953
+ } else if (key === 'mani') {
1466
1954
  checkSignature(reader, 'IRFR');
1467
1955
  readSection(reader, 1, left => {
1468
1956
  while (left() > 0) {
@@ -1490,24 +1978,43 @@ addHandler(
1490
1978
 
1491
1979
  // console.log('#4000 AnDs', require('util').inspect(desc, false, 99, true));
1492
1980
  // console.log('#4000 AnDs:result', require('util').inspect(target.animations, false, 99, true));
1981
+ skipBytes(reader, left()); // padding
1493
1982
  } else if (key === 'Roll') {
1494
1983
  const bytes = readBytes(reader, left());
1495
1984
  reader.logDevFeatures && reader.log('#4000 Roll', bytes);
1496
1985
  } else {
1497
1986
  reader.logMissingFeatures && reader.log('Unhandled subsection in #4000', key);
1987
+ skipBytes(reader, left());
1498
1988
  }
1499
1989
  });
1500
1990
  }
1501
1991
  });
1992
+
1993
+ skipBytes(reader, left()); // unknown data after animations section
1502
1994
  } else if (key === 'mopt') {
1503
1995
  const bytes = readBytes(reader, left());
1504
1996
  reader.logDevFeatures && reader.log('#4000 mopt', bytes);
1505
1997
  } else {
1506
- reader.logMissingFeatures && reader.log('Unhandled key in #4000:', key);
1998
+ reader.logMissingFeatures && reader.log(`Unhandled plug-in resource key: ${key}`);
1999
+ skipBytes(reader, left());
1507
2000
  }
1508
2001
  },
1509
- (writer, target) => {
1510
- if (target.animations) {
2002
+ (writer, target, index) => {
2003
+ const key = pluginResourceKeys(target)[index];
2004
+
2005
+ if (key === 'mset') {
2006
+ writeSignature(writer, 'mset');
2007
+ writeVersionAndDescriptor(writer, '', 'null', target.outputSettings, outputSettingsRoot);
2008
+ } else if (key === 'mfri') {
2009
+ const animations = target.animations!;
2010
+ writeSignature(writer, 'mfri');
2011
+ writeUint32(writer, 2); // version
2012
+ writeUint32(writer, 16); // length
2013
+ writeUint32(writer, animations.frames.length);
2014
+ writeUint32(writer, animations.animations[0]?.activeFrame ?? 0);
2015
+ writeUint32(writer, animations.animations.length);
2016
+ writeUint32(writer, 0);
2017
+ } else if (key === 'mani') {
1511
2018
  writeSignature(writer, 'mani');
1512
2019
  writeSignature(writer, 'IRFR');
1513
2020
  writeSection(writer, 1, () => {
@@ -1553,49 +2060,6 @@ addHandler(
1553
2060
  });
1554
2061
  }
1555
2062
  },
2063
+ { keyCount: 1000 },
1556
2064
  );
1557
2065
 
1558
- // TODO: Unfinished
1559
- MOCK_HANDLERS && addHandler(
1560
- 4001, // Plug-In resource(s)
1561
- target => (target as any)._ir4001 !== undefined,
1562
- (reader, target, left) => {
1563
- if (MOCK_HANDLERS) {
1564
- LOG_MOCK_HANDLERS && console.log('image resource 4001', left());
1565
- (target as any)._ir4001 = readBytes(reader, left());
1566
- return;
1567
- }
1568
-
1569
- const key = readSignature(reader);
1570
-
1571
- if (key === 'mfri') {
1572
- const version = readUint32(reader);
1573
- if (version !== 2) throw new Error('Invalid mfri version');
1574
-
1575
- const length = readUint32(reader);
1576
- const bytes = readBytes(reader, length);
1577
- reader.logDevFeatures && reader.log('mfri', bytes);
1578
- } else if (key === 'mset') {
1579
- const desc = readVersionAndDescriptor(reader);
1580
- reader.logDevFeatures && reader.log('mset', desc);
1581
- } else {
1582
- reader.logMissingFeatures && reader.log('Unhandled key in #4001', key);
1583
- }
1584
- },
1585
- (writer, target) => {
1586
- writeBytes(writer, (target as any)._ir4001);
1587
- },
1588
- );
1589
-
1590
- // TODO: Unfinished
1591
- MOCK_HANDLERS && addHandler(
1592
- 4002, // Plug-In resource(s)
1593
- target => (target as any)._ir4002 !== undefined,
1594
- (reader, target, left) => {
1595
- LOG_MOCK_HANDLERS && console.log('image resource 4002', left());
1596
- (target as any)._ir4002 = readBytes(reader, left());
1597
- },
1598
- (writer, target) => {
1599
- writeBytes(writer, (target as any)._ir4002);
1600
- },
1601
- );