@yft-design/psd-lib 1.0.1

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.
@@ -0,0 +1,1070 @@
1
+ import { toByteArray } from 'base64-js';
2
+ import { readUnicodeString, readUint32, readUint16, readUint8, readFloat64, readBytes, skipBytes, readFloat32, readInt16, readFixedPoint32, readSignature, checkSignature, readSection, readColor, readInt32, readAsciiString } from './psdReader';
3
+ import { writeUnicodeString, writeUint32, writeUint8, writeFloat64, writeUint16, writeBytes, writeInt16, writeFloat32, writeFixedPoint32, writeUnicodeStringWithPadding, writeColor, writeSignature, writeSection, writeInt32, writeAsciiString, } from './psdWriter';
4
+ import { createCanvasFromData, createEnum, MOCK_HANDLERS } from './helpers';
5
+ import { decodeString, encodeString } from './utf8';
6
+ import { ESliceBGColorType, ESliceHorzAlign, ESliceOrigin, ESliceType, ESliceVertAlign, frac, parseTrackList, readVersionAndDescriptor, serializeTrackList, writeVersionAndDescriptor } from './descriptor';
7
+ export const resourceHandlers = [];
8
+ export const resourceHandlersMap = {};
9
+ function addHandler(key, has, read, write) {
10
+ const handler = { key, has, read, write };
11
+ resourceHandlers.push(handler);
12
+ resourceHandlersMap[handler.key] = handler;
13
+ }
14
+ const LOG_MOCK_HANDLERS = false;
15
+ const RESOLUTION_UNITS = [undefined, 'PPI', 'PPCM'];
16
+ const MEASUREMENT_UNITS = [undefined, 'Inches', 'Centimeters', 'Points', 'Picas', 'Columns'];
17
+ const hex = '0123456789abcdef';
18
+ function charToNibble(code) {
19
+ return code <= 57 ? code - 48 : code - 87;
20
+ }
21
+ function byteAt(value, index) {
22
+ return (charToNibble(value.charCodeAt(index)) << 4) | charToNibble(value.charCodeAt(index + 1));
23
+ }
24
+ function readUtf8String(reader, length) {
25
+ const buffer = readBytes(reader, length);
26
+ return decodeString(buffer);
27
+ }
28
+ function writeUtf8String(writer, value) {
29
+ const buffer = encodeString(value);
30
+ writeBytes(writer, buffer);
31
+ }
32
+ function readEncodedString(reader) {
33
+ const length = readUint8(reader);
34
+ const buffer = readBytes(reader, length);
35
+ let notAscii = false;
36
+ for (let i = 0; i < buffer.byteLength; i++) {
37
+ if (buffer[i] & 0x80) {
38
+ notAscii = true;
39
+ break;
40
+ }
41
+ }
42
+ if (notAscii) {
43
+ const decoder = new TextDecoder('gbk');
44
+ return decoder.decode(buffer);
45
+ }
46
+ else {
47
+ return decodeString(buffer);
48
+ }
49
+ }
50
+ function writeEncodedString(writer, value) {
51
+ let ascii = '';
52
+ for (let i = 0, code = value.codePointAt(i++); code !== undefined; code = value.codePointAt(i++)) {
53
+ ascii += code > 0x7f ? '?' : String.fromCodePoint(code);
54
+ }
55
+ const buffer = encodeString(ascii);
56
+ writeUint8(writer, buffer.byteLength);
57
+ writeBytes(writer, buffer);
58
+ }
59
+ MOCK_HANDLERS && addHandler(1028, // IPTC-NAA record
60
+ // IPTC-NAA record
61
+ target => target._ir1028 !== undefined, (reader, target, left) => {
62
+ LOG_MOCK_HANDLERS && console.log('image resource 1028', left());
63
+ target._ir1028 = readBytes(reader, left());
64
+ }, (writer, target) => {
65
+ writeBytes(writer, target._ir1028);
66
+ });
67
+ addHandler(1061, target => target.captionDigest !== undefined, (reader, target) => {
68
+ let captionDigest = '';
69
+ for (let i = 0; i < 16; i++) {
70
+ const byte = readUint8(reader);
71
+ captionDigest += hex[byte >> 4];
72
+ captionDigest += hex[byte & 0xf];
73
+ }
74
+ target.captionDigest = captionDigest;
75
+ }, (writer, target) => {
76
+ for (let i = 0; i < 16; i++) {
77
+ writeUint8(writer, byteAt(target.captionDigest, i * 2));
78
+ }
79
+ });
80
+ addHandler(1060, target => target.xmpMetadata !== undefined, (reader, target, left) => {
81
+ target.xmpMetadata = readUtf8String(reader, left());
82
+ }, (writer, target) => {
83
+ writeUtf8String(writer, target.xmpMetadata);
84
+ });
85
+ const Inte = createEnum('Inte', 'perceptual', {
86
+ 'perceptual': 'Img ',
87
+ 'saturation': 'Grp ',
88
+ 'relative colorimetric': 'Clrm',
89
+ 'absolute colorimetric': 'AClr',
90
+ });
91
+ MOCK_HANDLERS && addHandler(1085, // Windows DEVMODE. Variable OS specific info for Windows.
92
+ // Windows DEVMODE. Variable OS specific info for Windows.
93
+ target => target._ir1085 !== undefined, (reader, target, left) => {
94
+ target._ir1085 = readBytes(reader, left());
95
+ }, (writer, target) => {
96
+ writeBytes(writer, target._ir1085);
97
+ });
98
+ addHandler(1082, target => target.printInformation !== undefined, (reader, target) => {
99
+ const desc = readVersionAndDescriptor(reader);
100
+ target.printInformation = {
101
+ printerName: desc.printerName || '',
102
+ renderingIntent: Inte.decode(desc.Inte ?? 'Inte.Img '),
103
+ };
104
+ const info = target.printInformation;
105
+ if (desc.PstS !== undefined)
106
+ info.printerManagesColors = desc.PstS;
107
+ if (desc['Nm '] !== undefined)
108
+ info.printerProfile = desc['Nm '];
109
+ if (desc.MpBl !== undefined)
110
+ info.blackPointCompensation = desc.MpBl;
111
+ if (desc.printSixteenBit !== undefined)
112
+ info.printSixteenBit = desc.printSixteenBit;
113
+ if (desc.hardProof !== undefined)
114
+ info.hardProof = desc.hardProof;
115
+ if (desc.printProofSetup) {
116
+ if ('Bltn' in desc.printProofSetup) {
117
+ info.proofSetup = { builtin: desc.printProofSetup.Bltn.split('.')[1] };
118
+ }
119
+ else {
120
+ info.proofSetup = {
121
+ profile: desc.printProofSetup.profile,
122
+ renderingIntent: Inte.decode(desc.printProofSetup.Inte ?? 'Inte.Img '),
123
+ blackPointCompensation: !!desc.printProofSetup.MpBl,
124
+ paperWhite: !!desc.printProofSetup.paperWhite,
125
+ };
126
+ }
127
+ }
128
+ }, (writer, target) => {
129
+ const info = target.printInformation;
130
+ const desc = {};
131
+ if (info.printerManagesColors) {
132
+ desc.PstS = true;
133
+ }
134
+ else {
135
+ if (info.hardProof !== undefined)
136
+ desc.hardProof = !!info.hardProof;
137
+ desc.ClrS = 'ClrS.RGBC'; // TODO: ???
138
+ desc['Nm '] = info.printerProfile ?? 'CIE RGB';
139
+ }
140
+ desc.Inte = Inte.encode(info.renderingIntent);
141
+ if (!info.printerManagesColors)
142
+ desc.MpBl = !!info.blackPointCompensation;
143
+ desc.printSixteenBit = !!info.printSixteenBit;
144
+ desc.printerName = info.printerName || '';
145
+ if (info.proofSetup && 'profile' in info.proofSetup) {
146
+ desc.printProofSetup = {
147
+ profile: info.proofSetup.profile || '',
148
+ Inte: Inte.encode(info.proofSetup.renderingIntent),
149
+ MpBl: !!info.proofSetup.blackPointCompensation,
150
+ paperWhite: !!info.proofSetup.paperWhite,
151
+ };
152
+ }
153
+ else {
154
+ desc.printProofSetup = {
155
+ Bltn: info.proofSetup?.builtin ? `builtinProof.${info.proofSetup.builtin}` : 'builtinProof.proofCMYK',
156
+ };
157
+ }
158
+ writeVersionAndDescriptor(writer, '', 'printOutput', desc);
159
+ });
160
+ MOCK_HANDLERS && addHandler(1083, // Print style
161
+ // Print style
162
+ target => target._ir1083 !== undefined, (reader, target, left) => {
163
+ LOG_MOCK_HANDLERS && console.log('image resource 1083', left());
164
+ target._ir1083 = readBytes(reader, left());
165
+ // TODO:
166
+ // const desc = readVersionAndDescriptor(reader);
167
+ // console.log('1083', require('util').inspect(desc, false, 99, true));
168
+ }, (writer, target) => {
169
+ writeBytes(writer, target._ir1083);
170
+ });
171
+ addHandler(1005, target => target.resolutionInfo !== undefined, (reader, target) => {
172
+ const horizontalResolution = readFixedPoint32(reader);
173
+ const horizontalResolutionUnit = readUint16(reader);
174
+ const widthUnit = readUint16(reader);
175
+ const verticalResolution = readFixedPoint32(reader);
176
+ const verticalResolutionUnit = readUint16(reader);
177
+ const heightUnit = readUint16(reader);
178
+ target.resolutionInfo = {
179
+ horizontalResolution,
180
+ horizontalResolutionUnit: RESOLUTION_UNITS[horizontalResolutionUnit] || 'PPI',
181
+ widthUnit: MEASUREMENT_UNITS[widthUnit] || 'Inches',
182
+ verticalResolution,
183
+ verticalResolutionUnit: RESOLUTION_UNITS[verticalResolutionUnit] || 'PPI',
184
+ heightUnit: MEASUREMENT_UNITS[heightUnit] || 'Inches',
185
+ };
186
+ }, (writer, target) => {
187
+ const info = target.resolutionInfo;
188
+ writeFixedPoint32(writer, info.horizontalResolution || 0);
189
+ writeUint16(writer, Math.max(1, RESOLUTION_UNITS.indexOf(info.horizontalResolutionUnit)));
190
+ writeUint16(writer, Math.max(1, MEASUREMENT_UNITS.indexOf(info.widthUnit)));
191
+ writeFixedPoint32(writer, info.verticalResolution || 0);
192
+ writeUint16(writer, Math.max(1, RESOLUTION_UNITS.indexOf(info.verticalResolutionUnit)));
193
+ writeUint16(writer, Math.max(1, MEASUREMENT_UNITS.indexOf(info.heightUnit)));
194
+ });
195
+ const printScaleStyles = ['centered', 'size to fit', 'user defined'];
196
+ addHandler(1062, target => target.printScale !== undefined, (reader, target) => {
197
+ target.printScale = {
198
+ style: printScaleStyles[readInt16(reader)],
199
+ x: readFloat32(reader),
200
+ y: readFloat32(reader),
201
+ scale: readFloat32(reader),
202
+ };
203
+ }, (writer, target) => {
204
+ const { style, x, y, scale } = target.printScale;
205
+ writeInt16(writer, Math.max(0, printScaleStyles.indexOf(style)));
206
+ writeFloat32(writer, x || 0);
207
+ writeFloat32(writer, y || 0);
208
+ writeFloat32(writer, scale || 0);
209
+ });
210
+ addHandler(1006, target => target.alphaChannelNames !== undefined, (reader, target, left) => {
211
+ if (!target.alphaChannelNames) { // skip if the unicode versions are already read
212
+ target.alphaChannelNames = [];
213
+ while (left() > 0) {
214
+ const value = readEncodedString(reader);
215
+ // const value = readPascalString(reader, 1);
216
+ target.alphaChannelNames.push(value);
217
+ }
218
+ }
219
+ else {
220
+ skipBytes(reader, left());
221
+ }
222
+ }, (writer, target) => {
223
+ for (const name of target.alphaChannelNames) {
224
+ writeEncodedString(writer, name);
225
+ // writePascalString(writer, name, 1);
226
+ }
227
+ });
228
+ addHandler(1045, target => target.alphaChannelNames !== undefined, (reader, target, left) => {
229
+ target.alphaChannelNames = [];
230
+ while (left() > 0) {
231
+ target.alphaChannelNames.push(readUnicodeString(reader));
232
+ }
233
+ }, (writer, target) => {
234
+ for (const name of target.alphaChannelNames) {
235
+ writeUnicodeStringWithPadding(writer, name);
236
+ }
237
+ });
238
+ MOCK_HANDLERS && addHandler(1077, target => target._ir1077 !== undefined, (reader, target, left) => {
239
+ LOG_MOCK_HANDLERS && console.log('image resource 1077', left());
240
+ target._ir1077 = readBytes(reader, left());
241
+ }, (writer, target) => {
242
+ writeBytes(writer, target._ir1077);
243
+ });
244
+ addHandler(1053, target => target.alphaIdentifiers !== undefined, (reader, target, left) => {
245
+ target.alphaIdentifiers = [];
246
+ while (left() >= 4) {
247
+ target.alphaIdentifiers.push(readUint32(reader));
248
+ }
249
+ }, (writer, target) => {
250
+ for (const id of target.alphaIdentifiers) {
251
+ writeUint32(writer, id);
252
+ }
253
+ });
254
+ addHandler(1010, target => target.backgroundColor !== undefined, (reader, target) => target.backgroundColor = readColor(reader), (writer, target) => writeColor(writer, target.backgroundColor));
255
+ addHandler(1037, target => target.globalAngle !== undefined, (reader, target) => target.globalAngle = readInt32(reader), (writer, target) => writeInt32(writer, target.globalAngle));
256
+ addHandler(1049, target => target.globalAltitude !== undefined, (reader, target) => target.globalAltitude = readUint32(reader), (writer, target) => writeUint32(writer, target.globalAltitude));
257
+ addHandler(1011, target => target.printFlags !== undefined, (reader, target) => {
258
+ target.printFlags = {
259
+ labels: !!readUint8(reader),
260
+ cropMarks: !!readUint8(reader),
261
+ colorBars: !!readUint8(reader),
262
+ registrationMarks: !!readUint8(reader),
263
+ negative: !!readUint8(reader),
264
+ flip: !!readUint8(reader),
265
+ interpolate: !!readUint8(reader),
266
+ caption: !!readUint8(reader),
267
+ printFlags: !!readUint8(reader),
268
+ };
269
+ }, (writer, target) => {
270
+ const flags = target.printFlags;
271
+ writeUint8(writer, flags.labels ? 1 : 0);
272
+ writeUint8(writer, flags.cropMarks ? 1 : 0);
273
+ writeUint8(writer, flags.colorBars ? 1 : 0);
274
+ writeUint8(writer, flags.registrationMarks ? 1 : 0);
275
+ writeUint8(writer, flags.negative ? 1 : 0);
276
+ writeUint8(writer, flags.flip ? 1 : 0);
277
+ writeUint8(writer, flags.interpolate ? 1 : 0);
278
+ writeUint8(writer, flags.caption ? 1 : 0);
279
+ writeUint8(writer, flags.printFlags ? 1 : 0);
280
+ });
281
+ addHandler(1034, // Copyright flag
282
+ // Copyright flag
283
+ target => target.copyrighted !== undefined, (reader, target) => {
284
+ target.copyrighted = !!readUint8(reader);
285
+ }, (writer, target) => {
286
+ writeUint8(writer, target.copyrighted ? 1 : 0);
287
+ });
288
+ addHandler(1035, // URL
289
+ // URL
290
+ target => target.url !== undefined, (reader, target, left) => {
291
+ target.url = readAsciiString(reader, left());
292
+ }, (writer, target) => {
293
+ writeAsciiString(writer, target.url);
294
+ });
295
+ MOCK_HANDLERS && addHandler(10000, // Print flags
296
+ // Print flags
297
+ target => target._ir10000 !== undefined, (reader, target, left) => {
298
+ LOG_MOCK_HANDLERS && console.log('image resource 10000', left());
299
+ target._ir10000 = readBytes(reader, left());
300
+ }, (writer, target) => {
301
+ writeBytes(writer, target._ir10000);
302
+ });
303
+ MOCK_HANDLERS && addHandler(1013, // Color halftoning
304
+ // Color halftoning
305
+ target => target._ir1013 !== undefined, (reader, target, left) => {
306
+ LOG_MOCK_HANDLERS && console.log('image resource 1013', left());
307
+ target._ir1013 = readBytes(reader, left());
308
+ }, (writer, target) => {
309
+ writeBytes(writer, target._ir1013);
310
+ });
311
+ MOCK_HANDLERS && addHandler(1016, // Color transfer functions
312
+ // Color transfer functions
313
+ target => target._ir1016 !== undefined, (reader, target, left) => {
314
+ LOG_MOCK_HANDLERS && console.log('image resource 1016', left());
315
+ target._ir1016 = readBytes(reader, left());
316
+ }, (writer, target) => {
317
+ writeBytes(writer, target._ir1016);
318
+ });
319
+ addHandler(1080, // Count Information
320
+ // Count Information
321
+ target => target.countInformation !== undefined, (reader, target) => {
322
+ const desc = readVersionAndDescriptor(reader);
323
+ target.countInformation = desc.countGroupList.map(g => ({
324
+ color: { r: g['Rd '], g: g['Grn '], b: g['Bl '] },
325
+ name: g['Nm '],
326
+ size: g['Rds '],
327
+ fontSize: g.fontSize,
328
+ visible: g.Vsbl,
329
+ points: g.countObjectList.map(p => ({ x: p['X '], y: p['Y '] })),
330
+ }));
331
+ }, (writer, target) => {
332
+ const desc = {
333
+ Vrsn: 1,
334
+ countGroupList: target.countInformation.map(g => ({
335
+ 'Rd ': g.color.r,
336
+ 'Grn ': g.color.g,
337
+ 'Bl ': g.color.b,
338
+ 'Nm ': g.name,
339
+ 'Rds ': g.size,
340
+ fontSize: g.fontSize,
341
+ Vsbl: g.visible,
342
+ countObjectList: g.points.map(p => ({ 'X ': p.x, 'Y ': p.y })),
343
+ })),
344
+ };
345
+ writeVersionAndDescriptor(writer, '', 'Cnt ', desc);
346
+ });
347
+ addHandler(1024, target => target.layerState !== undefined, (reader, target) => target.layerState = readUint16(reader), (writer, target) => writeUint16(writer, target.layerState));
348
+ addHandler(1026, target => target.layersGroup !== undefined, (reader, target, left) => {
349
+ target.layersGroup = [];
350
+ while (left() > 0) {
351
+ target.layersGroup.push(readUint16(reader));
352
+ }
353
+ }, (writer, target) => {
354
+ for (const g of target.layersGroup) {
355
+ writeUint16(writer, g);
356
+ }
357
+ });
358
+ addHandler(1072, target => target.layerGroupsEnabledId !== undefined, (reader, target, left) => {
359
+ target.layerGroupsEnabledId = [];
360
+ while (left() > 0) {
361
+ target.layerGroupsEnabledId.push(readUint8(reader));
362
+ }
363
+ }, (writer, target) => {
364
+ for (const id of target.layerGroupsEnabledId) {
365
+ writeUint8(writer, id);
366
+ }
367
+ });
368
+ addHandler(1069, target => target.layerSelectionIds !== undefined, (reader, target) => {
369
+ let count = readUint16(reader);
370
+ target.layerSelectionIds = [];
371
+ while (count--) {
372
+ target.layerSelectionIds.push(readUint32(reader));
373
+ }
374
+ }, (writer, target) => {
375
+ writeUint16(writer, target.layerSelectionIds.length);
376
+ for (const id of target.layerSelectionIds) {
377
+ writeUint32(writer, id);
378
+ }
379
+ });
380
+ addHandler(1032, target => target.gridAndGuidesInformation !== undefined, (reader, target) => {
381
+ const version = readUint32(reader);
382
+ const horizontal = readUint32(reader);
383
+ const vertical = readUint32(reader);
384
+ const count = readUint32(reader);
385
+ if (version !== 1)
386
+ throw new Error(`Invalid 1032 resource version: ${version}`);
387
+ target.gridAndGuidesInformation = {
388
+ grid: { horizontal, vertical },
389
+ guides: [],
390
+ };
391
+ for (let i = 0; i < count; i++) {
392
+ target.gridAndGuidesInformation.guides.push({
393
+ location: readUint32(reader) / 32,
394
+ direction: readUint8(reader) ? 'horizontal' : 'vertical'
395
+ });
396
+ }
397
+ }, (writer, target) => {
398
+ const info = target.gridAndGuidesInformation;
399
+ const grid = info.grid || { horizontal: 18 * 32, vertical: 18 * 32 };
400
+ const guides = info.guides || [];
401
+ writeUint32(writer, 1);
402
+ writeUint32(writer, grid.horizontal);
403
+ writeUint32(writer, grid.vertical);
404
+ writeUint32(writer, guides.length);
405
+ for (const g of guides) {
406
+ writeUint32(writer, g.location * 32);
407
+ writeUint8(writer, g.direction === 'horizontal' ? 1 : 0);
408
+ }
409
+ });
410
+ addHandler(1065, // Layer Comps
411
+ // Layer Comps
412
+ target => target.layerComps !== undefined, (reader, target) => {
413
+ const desc = readVersionAndDescriptor(reader, true);
414
+ // console.log('CompList', require('util').inspect(desc, false, 99, true));
415
+ target.layerComps = { list: [] };
416
+ for (const item of desc.list) {
417
+ target.layerComps.list.push({
418
+ id: item.compID,
419
+ name: item['Nm '],
420
+ capturedInfo: item.capturedInfo,
421
+ });
422
+ if ('comment' in item)
423
+ target.layerComps.list[target.layerComps.list.length - 1].comment = item.comment;
424
+ }
425
+ if ('lastAppliedComp' in desc)
426
+ target.layerComps.lastApplied = desc.lastAppliedComp;
427
+ }, (writer, target) => {
428
+ const layerComps = target.layerComps;
429
+ const desc = { list: [] };
430
+ for (const item of layerComps.list) {
431
+ const t = {};
432
+ t._classID = 'Comp';
433
+ t['Nm '] = item.name;
434
+ if ('comment' in item)
435
+ t.comment = item.comment;
436
+ t.compID = item.id;
437
+ t.capturedInfo = item.capturedInfo;
438
+ desc.list.push(t);
439
+ }
440
+ if ('lastApplied' in layerComps)
441
+ desc.lastAppliedComp = layerComps.lastApplied;
442
+ // console.log('CompList', require('util').inspect(desc, false, 99, true));
443
+ writeVersionAndDescriptor(writer, '', 'CompList', desc);
444
+ });
445
+ MOCK_HANDLERS && addHandler(1092, // ???
446
+ // ???
447
+ target => target._ir1092 !== undefined, (reader, target, left) => {
448
+ LOG_MOCK_HANDLERS && console.log('image resource 1092', left());
449
+ // 16 bytes, seems to be 4 integers
450
+ target._ir1092 = readBytes(reader, left());
451
+ }, (writer, target) => {
452
+ writeBytes(writer, target._ir1092);
453
+ });
454
+ // 0 - normal, 7 - multiply, 8 - screen, 23 - difference
455
+ const onionSkinsBlendModes = [
456
+ 'normal', undefined, undefined, undefined, undefined, undefined, undefined, 'multiply',
457
+ 'screen', undefined, undefined, undefined, undefined, undefined, undefined, undefined,
458
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, 'difference',
459
+ ];
460
+ addHandler(1078, // Onion Skins
461
+ // Onion Skins
462
+ target => target.onionSkins !== undefined, (reader, target) => {
463
+ const desc = readVersionAndDescriptor(reader);
464
+ // console.log('1078', require('util').inspect(desc, false, 99, true));
465
+ target.onionSkins = {
466
+ enabled: desc.enab,
467
+ framesBefore: desc.numBefore,
468
+ framesAfter: desc.numAfter,
469
+ frameSpacing: desc.Spcn,
470
+ minOpacity: desc.minOpacity / 100,
471
+ maxOpacity: desc.maxOpacity / 100,
472
+ blendMode: onionSkinsBlendModes[desc.BlnM] || 'normal',
473
+ };
474
+ }, (writer, target) => {
475
+ const onionSkins = target.onionSkins;
476
+ const desc = {
477
+ Vrsn: 1,
478
+ enab: onionSkins.enabled,
479
+ numBefore: onionSkins.framesBefore,
480
+ numAfter: onionSkins.framesAfter,
481
+ Spcn: onionSkins.frameSpacing,
482
+ minOpacity: (onionSkins.minOpacity * 100) | 0,
483
+ maxOpacity: (onionSkins.maxOpacity * 100) | 0,
484
+ BlnM: Math.max(0, onionSkinsBlendModes.indexOf(onionSkins.blendMode)),
485
+ };
486
+ writeVersionAndDescriptor(writer, '', 'null', desc);
487
+ });
488
+ addHandler(1075, // Timeline Information
489
+ // Timeline Information
490
+ target => target.timelineInformation !== undefined, (reader, target) => {
491
+ const desc = readVersionAndDescriptor(reader);
492
+ target.timelineInformation = {
493
+ enabled: desc.enab,
494
+ frameStep: frac(desc.frameStep),
495
+ frameRate: desc.frameRate,
496
+ time: frac(desc.time),
497
+ duration: frac(desc.duration),
498
+ workInTime: frac(desc.workInTime),
499
+ workOutTime: frac(desc.workOutTime),
500
+ repeats: desc.LCnt,
501
+ hasMotion: desc.hasMotion,
502
+ globalTracks: parseTrackList(desc.globalTrackList, !!reader.logMissingFeatures),
503
+ };
504
+ if (desc.audioClipGroupList?.audioClipGroupList?.length) {
505
+ target.timelineInformation.audioClipGroups = desc.audioClipGroupList.audioClipGroupList.map(g => ({
506
+ id: g.groupID,
507
+ muted: g.muted,
508
+ audioClips: g.audioClipList.map(({ clipID, timeScope, muted, audioLevel, frameReader }) => ({
509
+ id: clipID,
510
+ start: frac(timeScope.Strt),
511
+ duration: frac(timeScope.duration),
512
+ inTime: frac(timeScope.inTime),
513
+ outTime: frac(timeScope.outTime),
514
+ muted: muted,
515
+ audioLevel: audioLevel,
516
+ frameReader: {
517
+ type: frameReader.frameReaderType,
518
+ mediaDescriptor: frameReader.mediaDescriptor,
519
+ link: {
520
+ name: frameReader['Lnk ']['Nm '],
521
+ fullPath: frameReader['Lnk '].fullPath,
522
+ relativePath: frameReader['Lnk '].relPath,
523
+ },
524
+ },
525
+ })),
526
+ }));
527
+ }
528
+ }, (writer, target) => {
529
+ const timeline = target.timelineInformation;
530
+ const desc = {
531
+ Vrsn: 1,
532
+ enab: timeline.enabled,
533
+ frameStep: timeline.frameStep,
534
+ frameRate: timeline.frameRate,
535
+ time: timeline.time,
536
+ duration: timeline.duration,
537
+ workInTime: timeline.workInTime,
538
+ workOutTime: timeline.workOutTime,
539
+ LCnt: timeline.repeats,
540
+ globalTrackList: serializeTrackList(timeline.globalTracks),
541
+ audioClipGroupList: {
542
+ audioClipGroupList: timeline.audioClipGroups?.map(a => ({
543
+ groupID: a.id,
544
+ muted: a.muted,
545
+ audioClipList: a.audioClips.map(c => ({
546
+ clipID: c.id,
547
+ timeScope: {
548
+ Vrsn: 1,
549
+ Strt: c.start,
550
+ duration: c.duration,
551
+ inTime: c.inTime,
552
+ outTime: c.outTime,
553
+ },
554
+ frameReader: {
555
+ frameReaderType: c.frameReader.type,
556
+ descVersion: 1,
557
+ 'Lnk ': {
558
+ descVersion: 1,
559
+ 'Nm ': c.frameReader.link.name,
560
+ fullPath: c.frameReader.link.fullPath,
561
+ relPath: c.frameReader.link.relativePath,
562
+ },
563
+ mediaDescriptor: c.frameReader.mediaDescriptor,
564
+ },
565
+ muted: c.muted,
566
+ audioLevel: c.audioLevel,
567
+ })),
568
+ })),
569
+ },
570
+ hasMotion: timeline.hasMotion,
571
+ };
572
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'anim');
573
+ });
574
+ addHandler(1076, // Sheet Disclosure
575
+ // Sheet Disclosure
576
+ target => target.sheetDisclosure !== undefined, (reader, target) => {
577
+ const desc = readVersionAndDescriptor(reader);
578
+ target.sheetDisclosure = {};
579
+ if (desc.sheetTimelineOptions) {
580
+ target.sheetDisclosure.sheetTimelineOptions = desc.sheetTimelineOptions.map(o => ({
581
+ sheetID: o.sheetID,
582
+ sheetDisclosed: o.sheetDisclosed,
583
+ lightsDisclosed: o.lightsDisclosed,
584
+ meshesDisclosed: o.meshesDisclosed,
585
+ materialsDisclosed: o.materialsDisclosed,
586
+ }));
587
+ }
588
+ }, (writer, target) => {
589
+ const disclosure = target.sheetDisclosure;
590
+ const desc = { Vrsn: 1 };
591
+ if (disclosure.sheetTimelineOptions) {
592
+ desc.sheetTimelineOptions = disclosure.sheetTimelineOptions.map(d => ({
593
+ Vrsn: 2,
594
+ sheetID: d.sheetID,
595
+ sheetDisclosed: d.sheetDisclosed,
596
+ lightsDisclosed: d.lightsDisclosed,
597
+ meshesDisclosed: d.meshesDisclosed,
598
+ materialsDisclosed: d.materialsDisclosed,
599
+ }));
600
+ }
601
+ writeVersionAndDescriptor(writer, '', 'null', desc);
602
+ });
603
+ addHandler(1054, // URL List
604
+ // URL List
605
+ target => target.urlsList !== undefined, (reader, target) => {
606
+ const count = readUint32(reader);
607
+ target.urlsList = [];
608
+ for (let i = 0; i < count; i++) {
609
+ const long = readSignature(reader);
610
+ if (long !== 'slic' && reader.throwForMissingFeatures)
611
+ throw new Error('Unknown long');
612
+ const id = readUint32(reader);
613
+ const url = readUnicodeString(reader);
614
+ target.urlsList.push({ id, url, ref: 'slice' });
615
+ }
616
+ }, (writer, target) => {
617
+ const list = target.urlsList;
618
+ writeUint32(writer, list.length);
619
+ for (let i = 0; i < list.length; i++) {
620
+ writeSignature(writer, 'slic');
621
+ writeUint32(writer, list[i].id);
622
+ writeUnicodeString(writer, list[i].url);
623
+ }
624
+ });
625
+ function boundsToBounds(bounds) {
626
+ return { 'Top ': bounds.top, Left: bounds.left, Btom: bounds.bottom, Rght: bounds.right };
627
+ }
628
+ function boundsFromBounds(bounds) {
629
+ return { top: bounds['Top '], left: bounds.Left, bottom: bounds.Btom, right: bounds.Rght };
630
+ }
631
+ function clamped(array, index) {
632
+ return array[Math.max(0, Math.min(array.length - 1, index))];
633
+ }
634
+ const sliceOrigins = ['autoGenerated', 'layer', 'userGenerated'];
635
+ const sliceTypes = ['noImage', 'image'];
636
+ const sliceAlignments = ['default'];
637
+ addHandler(1050, // Slices
638
+ // Slices
639
+ target => target.slices ? target.slices.length : 0, (reader, target) => {
640
+ const version = readUint32(reader);
641
+ if (version === 6) {
642
+ if (!target.slices)
643
+ target.slices = [];
644
+ const top = readInt32(reader);
645
+ const left = readInt32(reader);
646
+ const bottom = readInt32(reader);
647
+ const right = readInt32(reader);
648
+ const groupName = readUnicodeString(reader);
649
+ const count = readUint32(reader);
650
+ target.slices.push({ bounds: { top, left, bottom, right }, groupName, slices: [] });
651
+ const slices = target.slices[target.slices.length - 1].slices;
652
+ for (let i = 0; i < count; i++) {
653
+ const id = readUint32(reader);
654
+ const groupId = readUint32(reader);
655
+ const origin = clamped(sliceOrigins, readUint32(reader));
656
+ const associatedLayerId = origin == 'layer' ? readUint32(reader) : 0;
657
+ const name = readUnicodeString(reader);
658
+ const type = clamped(sliceTypes, readUint32(reader));
659
+ const left = readInt32(reader);
660
+ const top = readInt32(reader);
661
+ const right = readInt32(reader);
662
+ const bottom = readInt32(reader);
663
+ const url = readUnicodeString(reader);
664
+ const target = readUnicodeString(reader);
665
+ const message = readUnicodeString(reader);
666
+ const altTag = readUnicodeString(reader);
667
+ const cellTextIsHTML = !!readUint8(reader);
668
+ const cellText = readUnicodeString(reader);
669
+ const horizontalAlignment = clamped(sliceAlignments, readUint32(reader));
670
+ const verticalAlignment = clamped(sliceAlignments, readUint32(reader));
671
+ const a = readUint8(reader);
672
+ const r = readUint8(reader);
673
+ const g = readUint8(reader);
674
+ const b = readUint8(reader);
675
+ const backgroundColorType = ((a + r + g + b) === 0) ? 'none' : (a === 0 ? 'matte' : 'color');
676
+ slices.push({
677
+ id, groupId, origin, associatedLayerId, name, target, message, altTag, cellTextIsHTML, cellText,
678
+ horizontalAlignment, verticalAlignment, type, url,
679
+ bounds: { top, left, bottom, right },
680
+ backgroundColorType, backgroundColor: { r, g, b, a },
681
+ });
682
+ }
683
+ const desc = readVersionAndDescriptor(reader);
684
+ desc.slices.forEach(d => {
685
+ const slice = slices.find(s => d.sliceID == s.id);
686
+ if (slice) {
687
+ slice.topOutset = d.topOutset;
688
+ slice.leftOutset = d.leftOutset;
689
+ slice.bottomOutset = d.bottomOutset;
690
+ slice.rightOutset = d.rightOutset;
691
+ }
692
+ });
693
+ }
694
+ else if (version === 7 || version === 8) {
695
+ const desc = readVersionAndDescriptor(reader);
696
+ if (!target.slices)
697
+ target.slices = [];
698
+ target.slices.push({
699
+ groupName: desc.baseName,
700
+ bounds: boundsFromBounds(desc.bounds),
701
+ slices: desc.slices.map(s => ({
702
+ ...(s['Nm '] ? { name: s['Nm '] } : {}),
703
+ id: s.sliceID,
704
+ groupId: s.groupID,
705
+ associatedLayerId: 0,
706
+ origin: ESliceOrigin.decode(s.origin),
707
+ type: ESliceType.decode(s.Type),
708
+ bounds: boundsFromBounds(s.bounds),
709
+ url: s.url,
710
+ target: s.null,
711
+ message: s.Msge,
712
+ altTag: s.altTag,
713
+ cellTextIsHTML: s.cellTextIsHTML,
714
+ cellText: s.cellText,
715
+ horizontalAlignment: ESliceHorzAlign.decode(s.horzAlign),
716
+ verticalAlignment: ESliceVertAlign.decode(s.vertAlign),
717
+ backgroundColorType: ESliceBGColorType.decode(s.bgColorType),
718
+ backgroundColor: s.bgColor ? { r: s.bgColor['Rd '], g: s.bgColor['Grn '], b: s.bgColor['Bl '], a: s.bgColor.alpha } : { r: 0, g: 0, b: 0, a: 0 },
719
+ topOutset: s.topOutset || 0,
720
+ leftOutset: s.leftOutset || 0,
721
+ bottomOutset: s.bottomOutset || 0,
722
+ rightOutset: s.rightOutset || 0,
723
+ })),
724
+ });
725
+ }
726
+ else {
727
+ throw new Error(`Invalid slices version (${version})`);
728
+ }
729
+ }, (writer, target, index) => {
730
+ const { bounds, groupName, slices } = target.slices[index];
731
+ writeUint32(writer, 6); // version
732
+ writeInt32(writer, bounds.top);
733
+ writeInt32(writer, bounds.left);
734
+ writeInt32(writer, bounds.bottom);
735
+ writeInt32(writer, bounds.right);
736
+ writeUnicodeString(writer, groupName);
737
+ writeUint32(writer, slices.length);
738
+ for (let i = 0; i < slices.length; i++) {
739
+ const slice = slices[i];
740
+ let { a, r, g, b } = slice.backgroundColor;
741
+ if (slice.backgroundColorType === 'none') {
742
+ a = r = g = b = 0;
743
+ }
744
+ else if (slice.backgroundColorType === 'matte') {
745
+ a = 0;
746
+ r = g = b = 255;
747
+ }
748
+ writeUint32(writer, slice.id);
749
+ writeUint32(writer, slice.groupId);
750
+ writeUint32(writer, sliceOrigins.indexOf(slice.origin));
751
+ if (slice.origin === 'layer')
752
+ writeUint32(writer, slice.associatedLayerId);
753
+ writeUnicodeString(writer, slice.name || '');
754
+ writeUint32(writer, sliceTypes.indexOf(slice.type));
755
+ writeInt32(writer, slice.bounds.left);
756
+ writeInt32(writer, slice.bounds.top);
757
+ writeInt32(writer, slice.bounds.right);
758
+ writeInt32(writer, slice.bounds.bottom);
759
+ writeUnicodeString(writer, slice.url);
760
+ writeUnicodeString(writer, slice.target);
761
+ writeUnicodeString(writer, slice.message);
762
+ writeUnicodeString(writer, slice.altTag);
763
+ writeUint8(writer, slice.cellTextIsHTML ? 1 : 0);
764
+ writeUnicodeString(writer, slice.cellText);
765
+ writeUint32(writer, sliceAlignments.indexOf(slice.horizontalAlignment));
766
+ writeUint32(writer, sliceAlignments.indexOf(slice.verticalAlignment));
767
+ writeUint8(writer, a);
768
+ writeUint8(writer, r);
769
+ writeUint8(writer, g);
770
+ writeUint8(writer, b);
771
+ }
772
+ const desc = {
773
+ bounds: boundsToBounds(bounds),
774
+ slices: [],
775
+ };
776
+ slices.forEach(s => {
777
+ const slice = {
778
+ sliceID: s.id,
779
+ groupID: s.groupId,
780
+ origin: ESliceOrigin.encode(s.origin),
781
+ Type: ESliceType.encode(s.type),
782
+ bounds: boundsToBounds(s.bounds),
783
+ ...(s.name ? { 'Nm ': s.name } : {}),
784
+ url: s.url,
785
+ null: s.target,
786
+ Msge: s.message,
787
+ altTag: s.altTag,
788
+ cellTextIsHTML: s.cellTextIsHTML,
789
+ cellText: s.cellText,
790
+ horzAlign: ESliceHorzAlign.encode(s.horizontalAlignment),
791
+ vertAlign: ESliceVertAlign.encode(s.verticalAlignment),
792
+ bgColorType: ESliceBGColorType.encode(s.backgroundColorType),
793
+ };
794
+ if (s.backgroundColorType === 'color') {
795
+ const { r, g, b, a } = s.backgroundColor;
796
+ slice.bgColor = { 'Rd ': r, 'Grn ': g, 'Bl ': b, alpha: a };
797
+ }
798
+ slice.topOutset = s.topOutset || 0;
799
+ slice.leftOutset = s.leftOutset || 0;
800
+ slice.bottomOutset = s.bottomOutset || 0;
801
+ slice.rightOutset = s.rightOutset || 0;
802
+ desc.slices.push(slice);
803
+ });
804
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'slices');
805
+ });
806
+ addHandler(1064, target => target.pixelAspectRatio !== undefined, (reader, target) => {
807
+ if (readUint32(reader) > 2)
808
+ throw new Error('Invalid pixelAspectRatio version');
809
+ target.pixelAspectRatio = { aspect: readFloat64(reader) };
810
+ }, (writer, target) => {
811
+ writeUint32(writer, 2); // version
812
+ writeFloat64(writer, target.pixelAspectRatio.aspect);
813
+ });
814
+ addHandler(1041, target => target.iccUntaggedProfile !== undefined, (reader, target) => {
815
+ target.iccUntaggedProfile = !!readUint8(reader);
816
+ }, (writer, target) => {
817
+ writeUint8(writer, target.iccUntaggedProfile ? 1 : 0);
818
+ });
819
+ MOCK_HANDLERS && addHandler(1039, // ICC Profile
820
+ // ICC Profile
821
+ target => target._ir1039 !== undefined, (reader, target, left) => {
822
+ // TODO: this is raw bytes, just return as a byte array
823
+ LOG_MOCK_HANDLERS && console.log('image resource 1039', left());
824
+ target._ir1039 = readBytes(reader, left());
825
+ }, (writer, target) => {
826
+ writeBytes(writer, target._ir1039);
827
+ });
828
+ addHandler(1044, target => target.idsSeedNumber !== undefined, (reader, target) => target.idsSeedNumber = readUint32(reader), (writer, target) => writeUint32(writer, target.idsSeedNumber));
829
+ addHandler(1036, target => target.thumbnail !== undefined || target.thumbnailRaw !== undefined, (reader, target, left) => {
830
+ const format = readUint32(reader); // 1 = kJpegRGB, 0 = kRawRGB
831
+ const width = readUint32(reader);
832
+ const height = readUint32(reader);
833
+ readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
834
+ readUint32(reader); // totalSize = widthBytes * height * planes
835
+ readUint32(reader); // sizeAfterCompression
836
+ const bitsPerPixel = readUint16(reader); // 24
837
+ const planes = readUint16(reader); // 1
838
+ if (format !== 1 || bitsPerPixel !== 24 || planes !== 1) {
839
+ reader.logMissingFeatures && reader.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel}, planes: ${planes})`);
840
+ skipBytes(reader, left());
841
+ return;
842
+ }
843
+ const size = left();
844
+ const data = readBytes(reader, size);
845
+ if (reader.useRawThumbnail) {
846
+ target.thumbnailRaw = { width, height, data };
847
+ }
848
+ else if (data.byteLength) {
849
+ target.thumbnail = createCanvasFromData(data);
850
+ }
851
+ }, (writer, target) => {
852
+ let width = 0;
853
+ let height = 0;
854
+ let data = new Uint8Array(0);
855
+ if (target.thumbnailRaw) {
856
+ width = target.thumbnailRaw.width;
857
+ height = target.thumbnailRaw.height;
858
+ data = target.thumbnailRaw.data;
859
+ }
860
+ else {
861
+ try {
862
+ const dataUrl = target.thumbnail.toDataURL('image/jpeg', 1)?.substring('data:image/jpeg;base64,'.length);
863
+ if (dataUrl) {
864
+ data = toByteArray(dataUrl); // this sometimes fails for some reason, maybe some browser bugs
865
+ width = target.thumbnail.width;
866
+ height = target.thumbnail.height;
867
+ }
868
+ }
869
+ catch { }
870
+ }
871
+ const bitsPerPixel = 24;
872
+ const widthBytes = Math.floor((width * bitsPerPixel + 31) / 32) * 4;
873
+ const planes = 1;
874
+ const totalSize = widthBytes * height * planes;
875
+ const sizeAfterCompression = data.length;
876
+ writeUint32(writer, 1); // 1 = kJpegRGB
877
+ writeUint32(writer, width);
878
+ writeUint32(writer, height);
879
+ writeUint32(writer, widthBytes);
880
+ writeUint32(writer, totalSize);
881
+ writeUint32(writer, sizeAfterCompression);
882
+ writeUint16(writer, bitsPerPixel);
883
+ writeUint16(writer, planes);
884
+ writeBytes(writer, data);
885
+ });
886
+ addHandler(1057, target => target.versionInfo !== undefined, (reader, target, left) => {
887
+ const version = readUint32(reader);
888
+ if (version !== 1)
889
+ throw new Error('Invalid versionInfo version');
890
+ target.versionInfo = {
891
+ hasRealMergedData: !!readUint8(reader),
892
+ writerName: readUnicodeString(reader),
893
+ readerName: readUnicodeString(reader),
894
+ fileVersion: readUint32(reader),
895
+ };
896
+ skipBytes(reader, left());
897
+ }, (writer, target) => {
898
+ const versionInfo = target.versionInfo;
899
+ writeUint32(writer, 1); // version
900
+ writeUint8(writer, versionInfo.hasRealMergedData ? 1 : 0);
901
+ writeUnicodeString(writer, versionInfo.writerName);
902
+ writeUnicodeString(writer, versionInfo.readerName);
903
+ writeUint32(writer, versionInfo.fileVersion);
904
+ });
905
+ MOCK_HANDLERS && addHandler(1058, // EXIF data 1.
906
+ // EXIF data 1.
907
+ target => target._ir1058 !== undefined, (reader, target, left) => {
908
+ LOG_MOCK_HANDLERS && console.log('image resource 1058', left());
909
+ target._ir1058 = readBytes(reader, left());
910
+ }, (writer, target) => {
911
+ writeBytes(writer, target._ir1058);
912
+ });
913
+ addHandler(7000, target => target.imageReadyVariables !== undefined, (reader, target, left) => {
914
+ target.imageReadyVariables = readUtf8String(reader, left());
915
+ }, (writer, target) => {
916
+ writeUtf8String(writer, target.imageReadyVariables);
917
+ });
918
+ addHandler(7001, target => target.imageReadyDataSets !== undefined, (reader, target, left) => {
919
+ target.imageReadyDataSets = readUtf8String(reader, left());
920
+ }, (writer, target) => {
921
+ writeUtf8String(writer, target.imageReadyDataSets);
922
+ });
923
+ addHandler(1088, target => target.pathSelectionState !== undefined, (reader, target, _left) => {
924
+ const desc = readVersionAndDescriptor(reader);
925
+ target.pathSelectionState = desc['null'];
926
+ }, (writer, target) => {
927
+ const desc = { 'null': target.pathSelectionState };
928
+ writeVersionAndDescriptor(writer, '', 'null', desc);
929
+ });
930
+ MOCK_HANDLERS && addHandler(1025, target => target._ir1025 !== undefined, (reader, target, left) => {
931
+ LOG_MOCK_HANDLERS && console.log('image resource 1025', left());
932
+ target._ir1025 = readBytes(reader, left());
933
+ }, (writer, target) => {
934
+ writeBytes(writer, target._ir1025);
935
+ });
936
+ const FrmD = createEnum('FrmD', '', {
937
+ auto: 'Auto',
938
+ none: 'None',
939
+ dispose: 'Disp',
940
+ });
941
+ addHandler(4000, // Plug-In resource(s)
942
+ // Plug-In resource(s)
943
+ target => target.animations !== undefined, (reader, target, left) => {
944
+ const key = readSignature(reader);
945
+ if (key === 'mani') {
946
+ checkSignature(reader, 'IRFR');
947
+ readSection(reader, 1, left => {
948
+ while (left() > 0) {
949
+ checkSignature(reader, '8BIM');
950
+ const key = readSignature(reader);
951
+ readSection(reader, 1, left => {
952
+ if (key === 'AnDs') {
953
+ const desc = readVersionAndDescriptor(reader);
954
+ target.animations = {
955
+ // desc.AFSt ???
956
+ frames: desc.FrIn.map(x => ({
957
+ id: x.FrID,
958
+ delay: (x.FrDl || 0) / 100,
959
+ dispose: x.FrDs ? FrmD.decode(x.FrDs) : 'auto', // missing == auto
960
+ // x.FrGA ???
961
+ })),
962
+ animations: desc.FSts.map(x => ({
963
+ id: x.FsID,
964
+ frames: x.FsFr,
965
+ repeats: x.LCnt,
966
+ activeFrame: x.AFrm || 0,
967
+ })),
968
+ };
969
+ // console.log('#4000 AnDs', require('util').inspect(desc, false, 99, true));
970
+ // console.log('#4000 AnDs:result', require('util').inspect(target.animations, false, 99, true));
971
+ }
972
+ else if (key === 'Roll') {
973
+ const bytes = readBytes(reader, left());
974
+ reader.logDevFeatures && reader.log('#4000 Roll', bytes);
975
+ }
976
+ else {
977
+ reader.logMissingFeatures && reader.log('Unhandled subsection in #4000', key);
978
+ }
979
+ });
980
+ }
981
+ });
982
+ }
983
+ else if (key === 'mopt') {
984
+ const bytes = readBytes(reader, left());
985
+ reader.logDevFeatures && reader.log('#4000 mopt', bytes);
986
+ }
987
+ else {
988
+ reader.logMissingFeatures && reader.log('Unhandled key in #4000:', key);
989
+ }
990
+ }, (writer, target) => {
991
+ if (target.animations) {
992
+ writeSignature(writer, 'mani');
993
+ writeSignature(writer, 'IRFR');
994
+ writeSection(writer, 1, () => {
995
+ writeSignature(writer, '8BIM');
996
+ writeSignature(writer, 'AnDs');
997
+ writeSection(writer, 1, () => {
998
+ const desc = {
999
+ // AFSt: 0, // ???
1000
+ FrIn: [],
1001
+ FSts: [],
1002
+ };
1003
+ for (let i = 0; i < target.animations.frames.length; i++) {
1004
+ const f = target.animations.frames[i];
1005
+ const frame = {
1006
+ FrID: f.id,
1007
+ };
1008
+ if (f.delay)
1009
+ frame.FrDl = (f.delay * 100) | 0;
1010
+ frame.FrDs = FrmD.encode(f.dispose);
1011
+ // if (i === 0) frame.FrGA = 30; // ???
1012
+ desc.FrIn.push(frame);
1013
+ }
1014
+ for (let i = 0; i < target.animations.animations.length; i++) {
1015
+ const a = target.animations.animations[i];
1016
+ const anim = {
1017
+ FsID: a.id,
1018
+ AFrm: a.activeFrame | 0,
1019
+ FsFr: a.frames,
1020
+ LCnt: a.repeats | 0,
1021
+ };
1022
+ desc.FSts.push(anim);
1023
+ }
1024
+ writeVersionAndDescriptor(writer, '', 'null', desc);
1025
+ });
1026
+ // writeSignature(writer, '8BIM');
1027
+ // writeSignature(writer, 'Roll');
1028
+ // writeSection(writer, 1, () => {
1029
+ // writeZeros(writer, 8);
1030
+ // });
1031
+ });
1032
+ }
1033
+ });
1034
+ // TODO: Unfinished
1035
+ MOCK_HANDLERS && addHandler(4001, // Plug-In resource(s)
1036
+ // Plug-In resource(s)
1037
+ target => target._ir4001 !== undefined, (reader, target, left) => {
1038
+ if (MOCK_HANDLERS) {
1039
+ LOG_MOCK_HANDLERS && console.log('image resource 4001', left());
1040
+ target._ir4001 = readBytes(reader, left());
1041
+ return;
1042
+ }
1043
+ const key = readSignature(reader);
1044
+ if (key === 'mfri') {
1045
+ const version = readUint32(reader);
1046
+ if (version !== 2)
1047
+ throw new Error('Invalid mfri version');
1048
+ const length = readUint32(reader);
1049
+ const bytes = readBytes(reader, length);
1050
+ reader.logDevFeatures && reader.log('mfri', bytes);
1051
+ }
1052
+ else if (key === 'mset') {
1053
+ const desc = readVersionAndDescriptor(reader);
1054
+ reader.logDevFeatures && reader.log('mset', desc);
1055
+ }
1056
+ else {
1057
+ reader.logMissingFeatures && reader.log('Unhandled key in #4001', key);
1058
+ }
1059
+ }, (writer, target) => {
1060
+ writeBytes(writer, target._ir4001);
1061
+ });
1062
+ // TODO: Unfinished
1063
+ MOCK_HANDLERS && addHandler(4002, // Plug-In resource(s)
1064
+ // Plug-In resource(s)
1065
+ target => target._ir4002 !== undefined, (reader, target, left) => {
1066
+ LOG_MOCK_HANDLERS && console.log('image resource 4002', left());
1067
+ target._ir4002 = readBytes(reader, left());
1068
+ }, (writer, target) => {
1069
+ writeBytes(writer, target._ir4002);
1070
+ });