ag-psd 31.0.3 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  25. package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
  26. package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
  27. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  28. package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
  29. package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
  30. package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
  31. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  32. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  33. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
  34. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
  35. package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
  36. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  37. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  38. package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
  39. package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
  40. package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
  41. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  42. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  43. package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
  44. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  45. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  46. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
  47. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  48. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  49. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  50. package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
  51. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  52. package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
  53. package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
  54. package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
  55. package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
  56. package/CHANGELOG.md +107 -0
  57. package/LICENSE +5 -0
  58. package/README.md +3 -6
  59. package/README_PSD.md +242 -81
  60. package/dist/abr.d.ts +11 -4
  61. package/dist/abr.js +363 -229
  62. package/dist/abr.js.map +1 -1
  63. package/dist/additionalInfo.d.ts +56 -2
  64. package/dist/additionalInfo.js +600 -364
  65. package/dist/additionalInfo.js.map +1 -1
  66. package/dist/ase.d.ts +6 -1
  67. package/dist/ase.js +61 -61
  68. package/dist/ase.js.map +1 -1
  69. package/dist/bundle.js +3096 -1541
  70. package/dist/descriptor.d.ts +25 -6
  71. package/dist/descriptor.js +380 -135
  72. package/dist/descriptor.js.map +1 -1
  73. package/dist/effectsHelpers.js +12 -12
  74. package/dist/effectsHelpers.js.map +1 -1
  75. package/dist/engineData.d.ts +3 -1
  76. package/dist/engineData.js +34 -13
  77. package/dist/engineData.js.map +1 -1
  78. package/dist/engineData2.js +50 -21
  79. package/dist/engineData2.js.map +1 -1
  80. package/dist/helpers.d.ts +6 -3
  81. package/dist/helpers.js +22 -6
  82. package/dist/helpers.js.map +1 -1
  83. package/dist/imageResources.d.ts +15 -3
  84. package/dist/imageResources.js +529 -144
  85. package/dist/imageResources.js.map +1 -1
  86. package/dist/index.js +1 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/jpeg.js +30 -10
  89. package/dist/jpeg.js.map +1 -1
  90. package/dist/psd.d.ts +154 -18
  91. package/dist/psd.js.map +1 -1
  92. package/dist/psdReader.d.ts +12 -2
  93. package/dist/psdReader.js +720 -421
  94. package/dist/psdReader.js.map +1 -1
  95. package/dist/psdWriter.d.ts +3 -0
  96. package/dist/psdWriter.js +167 -130
  97. package/dist/psdWriter.js.map +1 -1
  98. package/dist/text.js +143 -29
  99. package/dist/text.js.map +1 -1
  100. package/dist/utf8.js +36 -18
  101. package/dist/utf8.js.map +1 -1
  102. package/dist-es/abr.d.ts +11 -4
  103. package/dist-es/abr.js +363 -229
  104. package/dist-es/abr.js.map +1 -1
  105. package/dist-es/additionalInfo.d.ts +56 -2
  106. package/dist-es/additionalInfo.js +600 -364
  107. package/dist-es/additionalInfo.js.map +1 -1
  108. package/dist-es/ase.d.ts +6 -1
  109. package/dist-es/ase.js +61 -61
  110. package/dist-es/ase.js.map +1 -1
  111. package/dist-es/descriptor.d.ts +25 -6
  112. package/dist-es/descriptor.js +380 -135
  113. package/dist-es/descriptor.js.map +1 -1
  114. package/dist-es/effectsHelpers.js +12 -12
  115. package/dist-es/effectsHelpers.js.map +1 -1
  116. package/dist-es/engineData.d.ts +3 -1
  117. package/dist-es/engineData.js +34 -13
  118. package/dist-es/engineData.js.map +1 -1
  119. package/dist-es/engineData2.js +50 -21
  120. package/dist-es/engineData2.js.map +1 -1
  121. package/dist-es/helpers.d.ts +6 -3
  122. package/dist-es/helpers.js +22 -6
  123. package/dist-es/helpers.js.map +1 -1
  124. package/dist-es/imageResources.d.ts +15 -3
  125. package/dist-es/imageResources.js +529 -144
  126. package/dist-es/imageResources.js.map +1 -1
  127. package/dist-es/index.js +1 -0
  128. package/dist-es/index.js.map +1 -1
  129. package/dist-es/jpeg.js +30 -10
  130. package/dist-es/jpeg.js.map +1 -1
  131. package/dist-es/psd.d.ts +154 -18
  132. package/dist-es/psd.js.map +1 -1
  133. package/dist-es/psdReader.d.ts +12 -2
  134. package/dist-es/psdReader.js +720 -421
  135. package/dist-es/psdReader.js.map +1 -1
  136. package/dist-es/psdWriter.d.ts +3 -0
  137. package/dist-es/psdWriter.js +167 -130
  138. package/dist-es/psdWriter.js.map +1 -1
  139. package/dist-es/text.js +143 -29
  140. package/dist-es/text.js.map +1 -1
  141. package/dist-es/utf8.js +36 -18
  142. package/dist-es/utf8.js.map +1 -1
  143. package/package.json +2 -2
  144. package/src/abr.ts +380 -232
  145. package/src/additionalInfo.ts +781 -477
  146. package/src/ase.ts +66 -61
  147. package/src/descriptor.ts +386 -142
  148. package/src/effectsHelpers.ts +9 -9
  149. package/src/engineData.ts +36 -13
  150. package/src/engineData2.ts +468 -432
  151. package/src/helpers.ts +23 -5
  152. package/src/imageResources.ts +658 -167
  153. package/src/index.ts +1 -0
  154. package/src/jpeg.ts +27 -12
  155. package/src/psd.ts +158 -30
  156. package/src/psdReader.ts +732 -414
  157. package/src/psdWriter.ts +186 -132
  158. package/src/text.ts +139 -18
  159. package/src/utf8.ts +35 -21
@@ -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, 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) {
@@ -60,7 +76,10 @@ function writeUtf8String(writer: PsdWriter, value: string) {
60
76
  function readEncodedString(reader: PsdReader) {
61
77
  const length = readUint8(reader);
62
78
  const buffer = readBytes(reader, length);
79
+ return decodeEncodedString(reader, buffer);
80
+ }
63
81
 
82
+ function decodeEncodedString(reader: PsdReader, buffer: Uint8Array) {
64
83
  let notAscii = false;
65
84
  for (let i = 0; i < buffer.byteLength; i++) {
66
85
  if (buffer[i] & 0x80) {
@@ -70,36 +89,47 @@ function readEncodedString(reader: PsdReader) {
70
89
  }
71
90
 
72
91
  if (notAscii) {
73
- try {
74
- const decoder = new TextDecoder('gbk');
75
- return decoder.decode(buffer)
76
- } 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
+ }
100
+ }
77
101
  }
78
102
 
79
- return decodeString(buffer);
103
+ return decodeUtf8(reader, buffer);
80
104
  }
81
105
 
82
- function writeEncodedString(writer: PsdWriter, value: string) {
106
+ function toShortAsciiString(value: string) {
83
107
  let ascii = '';
84
108
 
85
- for (let i = 0, code = value.codePointAt(i++); code !== undefined; code = value.codePointAt(i++)) {
86
- ascii += code > 0x7f ? '?' : String.fromCodePoint(code);
109
+ for (let i = 0; i < value.length;) {
110
+ const code = value.codePointAt(i)!;
111
+ ascii += code > 0x7f ? '?' : String.fromCharCode(code);
112
+ i += code > 0xffff ? 2 : 1; // skip low surrogate of astral characters
87
113
  }
88
114
 
89
- const buffer = encodeString(ascii);
115
+ // length is stored in a single byte
116
+ return ascii.length > 255 ? ascii.substring(0, 255) : ascii;
117
+ }
118
+
119
+ function writeEncodedString(writer: PsdWriter, value: string) {
120
+ const buffer = encodeString(toShortAsciiString(value));
90
121
  writeUint8(writer, buffer.byteLength);
91
122
  writeBytes(writer, buffer);
92
123
  }
93
124
 
94
- MOCK_HANDLERS && addHandler(
125
+ addHandler(
95
126
  1028, // IPTC-NAA record
96
- target => (target as any)._ir1028 !== undefined,
127
+ target => target.iptcData !== undefined,
97
128
  (reader, target, left) => {
98
- LOG_MOCK_HANDLERS && console.log('image resource 1028', left());
99
- (target as any)._ir1028 = readBytes(reader, left());
129
+ target.iptcData = readBytes(reader, left());
100
130
  },
101
131
  (writer, target) => {
102
- writeBytes(writer, (target as any)._ir1028);
132
+ writeBytes(writer, target.iptcData!);
103
133
  },
104
134
  );
105
135
 
@@ -161,14 +191,14 @@ interface PrintInformationDescriptor {
161
191
  };
162
192
  }
163
193
 
164
- MOCK_HANDLERS && addHandler(
194
+ addHandler(
165
195
  1085, // Windows DEVMODE. Variable OS specific info for Windows.
166
- target => (target as any)._ir1085 !== undefined,
196
+ target => target.windowsDevMode !== undefined,
167
197
  (reader, target, left) => {
168
- (target as any)._ir1085 = readBytes(reader, left());
198
+ target.windowsDevMode = readBytes(reader, left());
169
199
  },
170
200
  (writer, target) => {
171
- writeBytes(writer, (target as any)._ir1085);
201
+ writeBytes(writer, target.windowsDevMode!);
172
202
  }
173
203
  );
174
204
 
@@ -186,6 +216,7 @@ addHandler(
186
216
  const info = target.printInformation;
187
217
 
188
218
  if (desc.PstS !== undefined) info.printerManagesColors = desc.PstS;
219
+ if (desc.ClrS === 'ClrS.Grys') info.printerProfileColorSpace = 'grayscale';
189
220
  if (desc['Nm '] !== undefined) info.printerProfile = desc['Nm '];
190
221
  if (desc.MpBl !== undefined) info.blackPointCompensation = desc.MpBl;
191
222
  if (desc.printSixteenBit !== undefined) info.printSixteenBit = desc.printSixteenBit;
@@ -211,13 +242,13 @@ addHandler(
211
242
  desc.PstS = true;
212
243
  } else {
213
244
  if (info.hardProof !== undefined) desc.hardProof = !!info.hardProof;
214
- desc.ClrS = 'ClrS.RGBC'; // TODO: ???
215
- 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;
216
247
  }
217
248
 
218
249
  desc.Inte = Inte.encode(info.renderingIntent);
219
250
 
220
- if (!info.printerManagesColors) desc.MpBl = !!info.blackPointCompensation;
251
+ if (!info.printerManagesColors && info.blackPointCompensation !== undefined) desc.MpBl = !!info.blackPointCompensation;
221
252
 
222
253
  desc.printSixteenBit = !!info.printSixteenBit;
223
254
  desc.printerName = info.printerName || '';
@@ -229,9 +260,9 @@ addHandler(
229
260
  MpBl: !!info.proofSetup.blackPointCompensation,
230
261
  paperWhite: !!info.proofSetup.paperWhite,
231
262
  };
232
- } else {
263
+ } else if (info.proofSetup) {
233
264
  desc.printProofSetup = {
234
- Bltn: info.proofSetup?.builtin ? `builtinProof.${info.proofSetup.builtin}` : 'builtinProof.proofCMYK',
265
+ Bltn: `builtinProof.${info.proofSetup.builtin || 'proofCMYK'}`,
235
266
  };
236
267
  }
237
268
 
@@ -239,19 +270,101 @@ addHandler(
239
270
  },
240
271
  );
241
272
 
242
- 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(
243
300
  1083, // Print style
244
- target => (target as any)._ir1083 !== undefined,
245
- (reader, target, left) => {
246
- LOG_MOCK_HANDLERS && console.log('image resource 1083', left());
247
- (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
+ };
248
325
 
249
- // TODO:
250
- // const desc = readVersionAndDescriptor(reader);
251
- // 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
+ }
252
335
  },
253
336
  (writer, target) => {
254
- 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);
255
368
  },
256
369
  );
257
370
 
@@ -350,15 +463,72 @@ addHandler(
350
463
  },
351
464
  );
352
465
 
353
- MOCK_HANDLERS && addHandler(
354
- 1077,
355
- 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,
491
+ (reader, target, 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,
356
508
  (reader, target, left) => {
357
- LOG_MOCK_HANDLERS && console.log('image resource 1077', left());
358
- (target as any)._ir1077 = readBytes(reader, 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
+ }
359
517
  },
360
518
  (writer, target) => {
361
- 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
+ }
362
532
  },
363
533
  );
364
534
 
@@ -452,42 +622,132 @@ addHandler(
452
622
  },
453
623
  );
454
624
 
455
- MOCK_HANDLERS && addHandler(
456
- 10000, // Print flags
457
- target => (target as any)._ir10000 !== undefined,
458
- (reader, target, left) => {
459
- LOG_MOCK_HANDLERS && console.log('image resource 10000', left());
460
- (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 };
461
636
  },
462
637
  (writer, target) => {
463
- 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);
464
644
  },
465
645
  );
466
646
 
467
- MOCK_HANDLERS && addHandler(
468
- 1013, // Color halftoning
469
- target => (target as any)._ir1013 !== undefined,
470
- (reader, target, left) => {
471
- LOG_MOCK_HANDLERS && console.log('image resource 1013', left());
472
- (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);
473
652
  },
474
653
  (writer, target) => {
475
- writeBytes(writer, (target as any)._ir1013);
654
+ writeUint16(writer, target.indexedColorCount!);
476
655
  },
477
656
  );
478
657
 
479
- MOCK_HANDLERS && addHandler(
480
- 1016, // Color transfer functions
481
- target => (target as any)._ir1016 !== undefined,
482
- (reader, target, left) => {
483
- LOG_MOCK_HANDLERS && console.log('image resource 1016', left());
484
- (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);
485
663
  },
486
664
  (writer, target) => {
487
- writeBytes(writer, (target as any)._ir1016);
665
+ writeUint16(writer, target.transparencyIndex!);
488
666
  },
489
667
  );
490
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
+
491
751
  interface CountInformationDesc {
492
752
  Vrsn: 1;
493
753
  countGroupList: {
@@ -609,17 +869,20 @@ addHandler(
609
869
 
610
870
  if (version !== 1) throw new Error(`Invalid 1032 resource version: ${version}`);
611
871
 
612
- target.gridAndGuidesInformation = {
613
- grid: { horizontal, vertical },
614
- guides: [],
615
- };
872
+ const previous = target.gridAndGuidesInformation?.guides; // in case guides were already read from 1092
873
+ const guides: Guide[] = [];
616
874
 
617
875
  for (let i = 0; i < count; i++) {
618
- target.gridAndGuidesInformation.guides!.push({
619
- location: readUint32(reader) / 32,
876
+ const guide: Guide = {
877
+ location: readInt32(reader) / 32,
620
878
  direction: readUint8(reader) ? 'horizontal' : 'vertical'
621
- });
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);
622
883
  }
884
+
885
+ target.gridAndGuidesInformation = { grid: { horizontal, vertical }, guides };
623
886
  },
624
887
  (writer, target) => {
625
888
  const info = target.gridAndGuidesInformation!;
@@ -627,17 +890,102 @@ addHandler(
627
890
  const guides = info.guides || [];
628
891
 
629
892
  writeUint32(writer, 1);
630
- writeUint32(writer, grid.horizontal);
631
- writeUint32(writer, grid.vertical);
893
+ writeUint32(writer, Math.round(grid.horizontal));
894
+ writeUint32(writer, Math.round(grid.vertical));
632
895
  writeUint32(writer, guides.length);
633
896
 
634
897
  for (const g of guides) {
635
- writeUint32(writer, g.location * 32);
898
+ writeInt32(writer, Math.round(g.location * 32));
636
899
  writeUint8(writer, g.direction === 'horizontal' ? 1 : 0);
637
900
  }
638
901
  },
639
902
  );
640
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
+
641
989
  interface LayerCompsDescriptor {
642
990
  list: {
643
991
  _classID: 'Comp';
@@ -665,10 +1013,10 @@ addHandler(
665
1013
  capturedInfo: item.capturedInfo,
666
1014
  });
667
1015
 
668
- if ('comment' in item) target.layerComps.list[target.layerComps.list.length - 1].comment = item.comment;
1016
+ if (item.comment !== undefined) target.layerComps.list[target.layerComps.list.length - 1].comment = item.comment;
669
1017
  }
670
1018
 
671
- if ('lastAppliedComp' in desc) target.layerComps.lastApplied = desc.lastAppliedComp;
1019
+ if (desc.lastAppliedComp !== undefined) target.layerComps.lastApplied = desc.lastAppliedComp;
672
1020
  },
673
1021
  (writer, target) => {
674
1022
  const layerComps = target.layerComps!;
@@ -678,13 +1026,13 @@ addHandler(
678
1026
  const t: LayerCompsDescriptor['list'][0] = {} as any;
679
1027
  t._classID = 'Comp';
680
1028
  t['Nm '] = item.name;
681
- if ('comment' in item) t.comment = item.comment;
1029
+ if (item.comment !== undefined) t.comment = item.comment;
682
1030
  t.compID = item.id;
683
1031
  t.capturedInfo = item.capturedInfo;
684
1032
  desc.list.push(t);
685
1033
  }
686
1034
 
687
- if ('lastApplied' in layerComps) desc.lastAppliedComp = layerComps.lastApplied;
1035
+ if (layerComps.lastApplied !== undefined) desc.lastAppliedComp = layerComps.lastApplied;
688
1036
 
689
1037
  // console.log('CompList', require('util').inspect(desc, false, 99, true));
690
1038
  writeVersionAndDescriptor(writer, '', 'CompList', desc);
@@ -747,8 +1095,8 @@ addHandler(
747
1095
  numBefore: onionSkins.framesBefore,
748
1096
  numAfter: onionSkins.framesAfter,
749
1097
  Spcn: onionSkins.frameSpacing,
750
- minOpacity: (onionSkins.minOpacity * 100) | 0,
751
- maxOpacity: (onionSkins.maxOpacity * 100) | 0,
1098
+ minOpacity: Math.round(onionSkins.minOpacity * 100),
1099
+ maxOpacity: Math.round(onionSkins.maxOpacity * 100),
752
1100
  BlnM: Math.max(0, onionSkinsBlendModes.indexOf(onionSkins.blendMode)),
753
1101
  };
754
1102
 
@@ -855,7 +1203,7 @@ addHandler(
855
1203
  LCnt: timeline.repeats,
856
1204
  globalTrackList: serializeTrackList(timeline.globalTracks),
857
1205
  audioClipGroupList: {
858
- audioClipGroupList: timeline.audioClipGroups?.map(a => ({
1206
+ audioClipGroupList: (timeline.audioClipGroups ?? []).map(a => ({
859
1207
  groupID: a.id,
860
1208
  muted: a.muted,
861
1209
  audioClipList: a.audioClips.map<TimelineAudioClipDescriptor>(c => ({
@@ -1072,8 +1420,13 @@ addHandler(
1072
1420
  }
1073
1421
  if (leftBytes()) {
1074
1422
  const desc = readVersionAndDescriptor(reader) as SlicesDesc;
1423
+ const slicesById = new Map<number, typeof slices[0]>();
1424
+ for (const s of slices) {
1425
+ if (!slicesById.has(s.id)) slicesById.set(s.id, s);
1426
+ }
1427
+
1075
1428
  desc.slices.forEach(d => {
1076
- const slice = slices.find(s => d.sliceID == s.id);
1429
+ const slice = slicesById.get(d.sliceID);
1077
1430
  if (slice) {
1078
1431
  slice.topOutset = d.topOutset;
1079
1432
  slice.leftOutset = d.leftOutset;
@@ -1227,16 +1580,14 @@ addHandler(
1227
1580
  },
1228
1581
  );
1229
1582
 
1230
- MOCK_HANDLERS && addHandler(
1583
+ addHandler(
1231
1584
  1039, // ICC Profile
1232
- target => (target as any)._ir1039 !== undefined,
1585
+ target => target.iccProfile !== undefined,
1233
1586
  (reader, target, left) => {
1234
- // TODO: this is raw bytes, just return as a byte array
1235
- LOG_MOCK_HANDLERS && console.log('image resource 1039', left());
1236
- (target as any)._ir1039 = readBytes(reader, left());
1587
+ target.iccProfile = readBytes(reader, left());
1237
1588
  },
1238
1589
  (writer, target) => {
1239
- writeBytes(writer, (target as any)._ir1039);
1590
+ writeBytes(writer, target.iccProfile!);
1240
1591
  },
1241
1592
  );
1242
1593
 
@@ -1247,6 +1598,47 @@ addHandler(
1247
1598
  (writer, target) => writeUint32(writer, target.idsSeedNumber!),
1248
1599
  );
1249
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
+
1250
1642
  addHandler(
1251
1643
  1036,
1252
1644
  target => target.thumbnail !== undefined || target.thumbnailRaw !== undefined,
@@ -1254,14 +1646,39 @@ addHandler(
1254
1646
  const format = readUint32(reader); // 1 = kJpegRGB, 0 = kRawRGB
1255
1647
  const width = readUint32(reader);
1256
1648
  const height = readUint32(reader);
1257
- readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
1649
+ const widthBytes = readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
1258
1650
  readUint32(reader); // totalSize = widthBytes * height * planes
1259
1651
  readUint32(reader); // sizeAfterCompression
1260
1652
  const bitsPerPixel = readUint16(reader); // 24
1261
- 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
+ }
1262
1662
 
1263
- if (format !== 1 || bitsPerPixel !== 24 || planes !== 1) {
1264
- reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel}, planes: ${planes})`);
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
+ }
1679
+
1680
+ if (format !== 1 || bitsPerPixel !== 24) {
1681
+ reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel})`);
1265
1682
  skipBytes(reader, left());
1266
1683
  return;
1267
1684
  }
@@ -1272,7 +1689,7 @@ addHandler(
1272
1689
  if (reader.useRawThumbnail) {
1273
1690
  target.thumbnailRaw = { width, height, data };
1274
1691
  } else if (data.byteLength) {
1275
- target.thumbnail = createCanvasFromData(data);
1692
+ target.thumbnail = createCanvasFromData(data, reader);
1276
1693
  }
1277
1694
  },
1278
1695
  (writer, target) => {
@@ -1340,15 +1757,14 @@ addHandler(
1340
1757
  },
1341
1758
  );
1342
1759
 
1343
- MOCK_HANDLERS && addHandler(
1344
- 1058, // EXIF data 1.
1345
- target => (target as any)._ir1058 !== undefined,
1760
+ addHandler(
1761
+ 1058, // EXIF data 1
1762
+ target => target.exifData !== undefined,
1346
1763
  (reader, target, left) => {
1347
- LOG_MOCK_HANDLERS && console.log('image resource 1058', left());
1348
- (target as any)._ir1058 = readBytes(reader, left());
1764
+ target.exifData = readBytes(reader, left());
1349
1765
  },
1350
1766
  (writer, target) => {
1351
- writeBytes(writer, (target as any)._ir1058);
1767
+ writeBytes(writer, target.exifData!);
1352
1768
  },
1353
1769
  );
1354
1770
 
@@ -1374,32 +1790,116 @@ addHandler(
1374
1790
  },
1375
1791
  );
1376
1792
 
1377
- interface Descriptor1088 {
1793
+ interface Descriptor1088Read {
1378
1794
  'null': string[];
1379
1795
  }
1380
1796
 
1797
+ interface Descriptor1088Write {
1798
+ 'null': DescriptorReferenceItem[];
1799
+ }
1800
+
1381
1801
  addHandler(
1382
1802
  1088,
1383
1803
  target => target.pathSelectionState !== undefined,
1384
1804
  (reader, target, _left) => {
1385
- const desc: Descriptor1088 = readVersionAndDescriptor(reader);
1805
+ const desc: Descriptor1088Read = readVersionAndDescriptor(reader);
1386
1806
  target.pathSelectionState = desc['null'];
1387
1807
  },
1388
1808
  (writer, target) => {
1389
- const desc: Descriptor1088 = { 'null': target.pathSelectionState! };
1809
+ const desc: Descriptor1088Write = { 'null': pathSelectionStateToReference(target.pathSelectionState!) };
1390
1810
  writeVersionAndDescriptor(writer, '', 'null', desc);
1391
1811
  },
1392
1812
  );
1393
1813
 
1394
- MOCK_HANDLERS && addHandler(
1395
- 1025,
1396
- 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),
1397
1862
  (reader, target, left) => {
1398
- LOG_MOCK_HANDLERS && console.log('image resource 1025', left());
1399
- (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());
1400
1879
  },
1401
1880
  (writer, target) => {
1402
- 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)
1403
1903
  },
1404
1904
  );
1405
1905
 
@@ -1429,13 +1929,28 @@ interface AnimationsDescriptor {
1429
1929
  FSts: AnimationDescriptor[];
1430
1930
  }
1431
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
+
1432
1940
  addHandler(
1433
- 4000, // Plug-In resource(s)
1434
- 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,
1435
1943
  (reader, target, left) => {
1436
1944
  const key = readSignature(reader);
1437
1945
 
1438
- 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') {
1439
1954
  checkSignature(reader, 'IRFR');
1440
1955
  readSection(reader, 1, left => {
1441
1956
  while (left() > 0) {
@@ -1463,24 +1978,43 @@ addHandler(
1463
1978
 
1464
1979
  // console.log('#4000 AnDs', require('util').inspect(desc, false, 99, true));
1465
1980
  // console.log('#4000 AnDs:result', require('util').inspect(target.animations, false, 99, true));
1981
+ skipBytes(reader, left()); // padding
1466
1982
  } else if (key === 'Roll') {
1467
1983
  const bytes = readBytes(reader, left());
1468
1984
  reader.logDevFeatures && reader.log('#4000 Roll', bytes);
1469
1985
  } else {
1470
1986
  reader.logMissingFeatures && reader.log('Unhandled subsection in #4000', key);
1987
+ skipBytes(reader, left());
1471
1988
  }
1472
1989
  });
1473
1990
  }
1474
1991
  });
1992
+
1993
+ skipBytes(reader, left()); // unknown data after animations section
1475
1994
  } else if (key === 'mopt') {
1476
1995
  const bytes = readBytes(reader, left());
1477
1996
  reader.logDevFeatures && reader.log('#4000 mopt', bytes);
1478
1997
  } else {
1479
- reader.logMissingFeatures && reader.log('Unhandled key in #4000:', key);
1998
+ reader.logMissingFeatures && reader.log(`Unhandled plug-in resource key: ${key}`);
1999
+ skipBytes(reader, left());
1480
2000
  }
1481
2001
  },
1482
- (writer, target) => {
1483
- 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') {
1484
2018
  writeSignature(writer, 'mani');
1485
2019
  writeSignature(writer, 'IRFR');
1486
2020
  writeSection(writer, 1, () => {
@@ -1498,7 +2032,7 @@ addHandler(
1498
2032
  const frame: AnimationFrameDescriptor = {
1499
2033
  FrID: f.id,
1500
2034
  } as any;
1501
- if (f.delay) frame.FrDl = (f.delay * 100) | 0;
2035
+ if (f.delay) frame.FrDl = Math.round(f.delay * 100);
1502
2036
  frame.FrDs = FrmD.encode(f.dispose);
1503
2037
  // if (i === 0) frame.FrGA = 30; // ???
1504
2038
  desc.FrIn.push(frame);
@@ -1526,49 +2060,6 @@ addHandler(
1526
2060
  });
1527
2061
  }
1528
2062
  },
2063
+ { keyCount: 1000 },
1529
2064
  );
1530
2065
 
1531
- // TODO: Unfinished
1532
- MOCK_HANDLERS && addHandler(
1533
- 4001, // Plug-In resource(s)
1534
- target => (target as any)._ir4001 !== undefined,
1535
- (reader, target, left) => {
1536
- if (MOCK_HANDLERS) {
1537
- LOG_MOCK_HANDLERS && console.log('image resource 4001', left());
1538
- (target as any)._ir4001 = readBytes(reader, left());
1539
- return;
1540
- }
1541
-
1542
- const key = readSignature(reader);
1543
-
1544
- if (key === 'mfri') {
1545
- const version = readUint32(reader);
1546
- if (version !== 2) throw new Error('Invalid mfri version');
1547
-
1548
- const length = readUint32(reader);
1549
- const bytes = readBytes(reader, length);
1550
- reader.logDevFeatures && reader.log('mfri', bytes);
1551
- } else if (key === 'mset') {
1552
- const desc = readVersionAndDescriptor(reader);
1553
- reader.logDevFeatures && reader.log('mset', desc);
1554
- } else {
1555
- reader.logMissingFeatures && reader.log('Unhandled key in #4001', key);
1556
- }
1557
- },
1558
- (writer, target) => {
1559
- writeBytes(writer, (target as any)._ir4001);
1560
- },
1561
- );
1562
-
1563
- // TODO: Unfinished
1564
- MOCK_HANDLERS && addHandler(
1565
- 4002, // Plug-In resource(s)
1566
- target => (target as any)._ir4002 !== undefined,
1567
- (reader, target, left) => {
1568
- LOG_MOCK_HANDLERS && console.log('image resource 4002', left());
1569
- (target as any)._ir4002 = readBytes(reader, left());
1570
- },
1571
- (writer, target) => {
1572
- writeBytes(writer, (target as any)._ir4002);
1573
- },
1574
- );