ag-psd 15.0.4 → 15.1.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.
@@ -0,0 +1,1439 @@
1
+ import { toByteArray } from 'base64-js';
2
+ import { BlendMode, ImageResources, ReadOptions, RenderingIntent } from './psd';
3
+ import {
4
+ PsdReader, readPascalString, readUnicodeString, readUint32, readUint16, readUint8, readFloat64,
5
+ readBytes, skipBytes, readFloat32, readInt16, readFixedPoint32, readSignature, checkSignature,
6
+ readSection, readColor, readInt32
7
+ } from './psdReader';
8
+ import {
9
+ PsdWriter, writePascalString, writeUnicodeString, writeUint32, writeUint8, writeFloat64, writeUint16,
10
+ writeBytes, writeInt16, writeFloat32, writeFixedPoint32, writeUnicodeStringWithPadding, writeColor, writeSignature,
11
+ writeSection, writeInt32,
12
+ } from './psdWriter';
13
+ import { createCanvasFromData, createEnum, MOCK_HANDLERS } from './helpers';
14
+ import { decodeString, encodeString } from './utf8';
15
+ import { ESliceBGColorType, ESliceHorzAlign, ESliceOrigin, ESliceType, ESliceVertAlign, FractionDescriptor, parseTrackList, readVersionAndDescriptor, serializeTrackList, TimelineTrackDescriptor, TimeScopeDescriptor, writeVersionAndDescriptor } from './descriptor';
16
+
17
+ export interface ResourceHandler {
18
+ key: number;
19
+ has: (target: ImageResources) => boolean | number;
20
+ read: (reader: PsdReader, target: ImageResources, left: () => number, options: ReadOptions) => void;
21
+ write: (writer: PsdWriter, target: ImageResources, index: number) => void;
22
+ }
23
+
24
+ export const resourceHandlers: ResourceHandler[] = [];
25
+ export const resourceHandlersMap: { [key: number]: ResourceHandler } = {};
26
+
27
+ function addHandler(
28
+ key: number,
29
+ has: (target: ImageResources) => boolean | number,
30
+ read: (reader: PsdReader, target: ImageResources, left: () => number, options: ReadOptions) => void,
31
+ write: (writer: PsdWriter, target: ImageResources, index: number) => void,
32
+ ) {
33
+ const handler: ResourceHandler = { key, has, read, write };
34
+ resourceHandlers.push(handler);
35
+ resourceHandlersMap[handler.key] = handler;
36
+ }
37
+
38
+ const LOG_MOCK_HANDLERS = false;
39
+ const RESOLUTION_UNITS = [undefined, 'PPI', 'PPCM'];
40
+ const MEASUREMENT_UNITS = [undefined, 'Inches', 'Centimeters', 'Points', 'Picas', 'Columns'];
41
+ const hex = '0123456789abcdef';
42
+
43
+ function charToNibble(code: number) {
44
+ return code <= 57 ? code - 48 : code - 87;
45
+ }
46
+
47
+ function byteAt(value: string, index: number) {
48
+ return (charToNibble(value.charCodeAt(index)) << 4) | charToNibble(value.charCodeAt(index + 1));
49
+ }
50
+
51
+ function readUtf8String(reader: PsdReader, length: number) {
52
+ const buffer = readBytes(reader, length);
53
+ return decodeString(buffer);
54
+ }
55
+
56
+ function writeUtf8String(writer: PsdWriter, value: string) {
57
+ const buffer = encodeString(value);
58
+ writeBytes(writer, buffer);
59
+ }
60
+
61
+ MOCK_HANDLERS && addHandler(
62
+ 1028, // IPTC-NAA record
63
+ target => (target as any)._ir1028 !== undefined,
64
+ (reader, target, left) => {
65
+ LOG_MOCK_HANDLERS && console.log('image resource 1028', left());
66
+ (target as any)._ir1028 = readBytes(reader, left());
67
+ },
68
+ (writer, target) => {
69
+ writeBytes(writer, (target as any)._ir1028);
70
+ },
71
+ );
72
+
73
+ addHandler(
74
+ 1061,
75
+ target => target.captionDigest !== undefined,
76
+ (reader, target) => {
77
+ let captionDigest = '';
78
+
79
+ for (let i = 0; i < 16; i++) {
80
+ const byte = readUint8(reader);
81
+ captionDigest += hex[byte >> 4];
82
+ captionDigest += hex[byte & 0xf];
83
+ }
84
+
85
+ target.captionDigest = captionDigest;
86
+ },
87
+ (writer, target) => {
88
+ for (let i = 0; i < 16; i++) {
89
+ writeUint8(writer, byteAt(target.captionDigest!, i * 2));
90
+ }
91
+ },
92
+ );
93
+
94
+ addHandler(
95
+ 1060,
96
+ target => target.xmpMetadata !== undefined,
97
+ (reader, target, left) => target.xmpMetadata = readUtf8String(reader, left()),
98
+ (writer, target) => writeUtf8String(writer, target.xmpMetadata!),
99
+ );
100
+
101
+ const Inte = createEnum<RenderingIntent>('Inte', 'perceptual', {
102
+ 'perceptual': 'Img ',
103
+ 'saturation': 'Grp ',
104
+ 'relative colorimetric': 'Clrm',
105
+ 'absolute colorimetric': 'AClr',
106
+ });
107
+
108
+ interface PrintInformationDescriptor {
109
+ 'Nm '?: string;
110
+ ClrS?: string;
111
+ PstS?: boolean;
112
+ MpBl?: boolean;
113
+ Inte?: string;
114
+ hardProof?: boolean;
115
+ printSixteenBit?: boolean;
116
+ printerName?: string;
117
+ printProofSetup?: {
118
+ Bltn: string;
119
+ } | {
120
+ profile: string;
121
+ Inte: string;
122
+ MpBl: boolean;
123
+ paperWhite: boolean;
124
+ };
125
+ }
126
+
127
+ addHandler(
128
+ 1082,
129
+ target => target.printInformation !== undefined,
130
+ (reader, target) => {
131
+ const desc: PrintInformationDescriptor = readVersionAndDescriptor(reader);
132
+
133
+ target.printInformation = {
134
+ printerName: desc.printerName || '',
135
+ renderingIntent: Inte.decode(desc.Inte ?? 'Inte.Img '),
136
+ };
137
+
138
+ const info = target.printInformation;
139
+
140
+ if (desc.PstS !== undefined) info.printerManagesColors = desc.PstS;
141
+ if (desc['Nm '] !== undefined) info.printerProfile = desc['Nm '];
142
+ if (desc.MpBl !== undefined) info.blackPointCompensation = desc.MpBl;
143
+ if (desc.printSixteenBit !== undefined) info.printSixteenBit = desc.printSixteenBit;
144
+ if (desc.hardProof !== undefined) info.hardProof = desc.hardProof;
145
+ if (desc.printProofSetup) {
146
+ if ('Bltn' in desc.printProofSetup) {
147
+ info.proofSetup = { builtin: desc.printProofSetup.Bltn.split('.')[1] };
148
+ } else {
149
+ info.proofSetup = {
150
+ profile: desc.printProofSetup.profile,
151
+ renderingIntent: Inte.decode(desc.printProofSetup.Inte ?? 'Inte.Img '),
152
+ blackPointCompensation: !!desc.printProofSetup.MpBl,
153
+ paperWhite: !!desc.printProofSetup.paperWhite,
154
+ };
155
+ }
156
+ }
157
+ },
158
+ (writer, target) => {
159
+ const info = target.printInformation!;
160
+ const desc: PrintInformationDescriptor = {};
161
+
162
+ if (info.printerManagesColors) {
163
+ desc.PstS = true;
164
+ } else {
165
+ if (info.hardProof !== undefined) desc.hardProof = !!info.hardProof;
166
+ desc.ClrS = 'ClrS.RGBC'; // TODO: ???
167
+ desc['Nm '] = info.printerProfile ?? 'CIE RGB';
168
+ }
169
+
170
+ desc.Inte = Inte.encode(info.renderingIntent);
171
+
172
+ if (!info.printerManagesColors) desc.MpBl = !!info.blackPointCompensation;
173
+
174
+ desc.printSixteenBit = !!info.printSixteenBit;
175
+ desc.printerName = info.printerName || '';
176
+
177
+ if (info.proofSetup && 'profile' in info.proofSetup) {
178
+ desc.printProofSetup = {
179
+ profile: info.proofSetup.profile || '',
180
+ Inte: Inte.encode(info.proofSetup.renderingIntent),
181
+ MpBl: !!info.proofSetup.blackPointCompensation,
182
+ paperWhite: !!info.proofSetup.paperWhite,
183
+ };
184
+ } else {
185
+ desc.printProofSetup = {
186
+ Bltn: info.proofSetup?.builtin ? `builtinProof.${info.proofSetup.builtin}` : 'builtinProof.proofCMYK',
187
+ };
188
+ }
189
+
190
+ writeVersionAndDescriptor(writer, '', 'printOutput', desc);
191
+ },
192
+ );
193
+
194
+ MOCK_HANDLERS && addHandler(
195
+ 1083, // Print style
196
+ target => (target as any)._ir1083 !== undefined,
197
+ (reader, target, left) => {
198
+ LOG_MOCK_HANDLERS && console.log('image resource 1083', left());
199
+ (target as any)._ir1083 = readBytes(reader, left());
200
+
201
+ // TODO:
202
+ // const desc = readVersionAndDescriptor(reader);
203
+ // console.log('1083', require('util').inspect(desc, false, 99, true));
204
+ },
205
+ (writer, target) => {
206
+ writeBytes(writer, (target as any)._ir1083);
207
+ },
208
+ );
209
+
210
+ addHandler(
211
+ 1005,
212
+ target => target.resolutionInfo !== undefined,
213
+ (reader, target) => {
214
+ const horizontalResolution = readFixedPoint32(reader);
215
+ const horizontalResolutionUnit = readUint16(reader);
216
+ const widthUnit = readUint16(reader);
217
+ const verticalResolution = readFixedPoint32(reader);
218
+ const verticalResolutionUnit = readUint16(reader);
219
+ const heightUnit = readUint16(reader);
220
+
221
+ target.resolutionInfo = {
222
+ horizontalResolution,
223
+ horizontalResolutionUnit: RESOLUTION_UNITS[horizontalResolutionUnit] || 'PPI' as any,
224
+ widthUnit: MEASUREMENT_UNITS[widthUnit] || 'Inches' as any,
225
+ verticalResolution,
226
+ verticalResolutionUnit: RESOLUTION_UNITS[verticalResolutionUnit] || 'PPI' as any,
227
+ heightUnit: MEASUREMENT_UNITS[heightUnit] || 'Inches' as any,
228
+ };
229
+ },
230
+ (writer, target) => {
231
+ const info = target.resolutionInfo!;
232
+
233
+ writeFixedPoint32(writer, info.horizontalResolution || 0);
234
+ writeUint16(writer, Math.max(1, RESOLUTION_UNITS.indexOf(info.horizontalResolutionUnit)));
235
+ writeUint16(writer, Math.max(1, MEASUREMENT_UNITS.indexOf(info.widthUnit)));
236
+ writeFixedPoint32(writer, info.verticalResolution || 0);
237
+ writeUint16(writer, Math.max(1, RESOLUTION_UNITS.indexOf(info.verticalResolutionUnit)));
238
+ writeUint16(writer, Math.max(1, MEASUREMENT_UNITS.indexOf(info.heightUnit)));
239
+ },
240
+ );
241
+
242
+ const printScaleStyles = ['centered', 'size to fit', 'user defined'];
243
+
244
+ addHandler(
245
+ 1062,
246
+ target => target.printScale !== undefined,
247
+ (reader, target) => {
248
+ target.printScale = {
249
+ style: printScaleStyles[readInt16(reader)] as any,
250
+ x: readFloat32(reader),
251
+ y: readFloat32(reader),
252
+ scale: readFloat32(reader),
253
+ };
254
+ },
255
+ (writer, target) => {
256
+ const { style, x, y, scale } = target.printScale!;
257
+ writeInt16(writer, Math.max(0, printScaleStyles.indexOf(style!)));
258
+ writeFloat32(writer, x || 0);
259
+ writeFloat32(writer, y || 0);
260
+ writeFloat32(writer, scale || 0);
261
+ },
262
+ );
263
+
264
+ addHandler(
265
+ 1006,
266
+ target => target.alphaChannelNames !== undefined,
267
+ (reader, target, left) => {
268
+ target.alphaChannelNames = [];
269
+
270
+ while (left()) {
271
+ const value = readPascalString(reader, 1);
272
+ target.alphaChannelNames.push(value);
273
+ }
274
+ },
275
+ (writer, target) => {
276
+ for (const name of target.alphaChannelNames!) {
277
+ writePascalString(writer, name, 1);
278
+ }
279
+ },
280
+ );
281
+
282
+ addHandler(
283
+ 1045,
284
+ target => target.alphaChannelNames !== undefined,
285
+ (reader, target, left) => {
286
+ target.alphaChannelNames = [];
287
+
288
+ while (left()) {
289
+ target.alphaChannelNames.push(readUnicodeString(reader));
290
+ }
291
+ },
292
+ (writer, target) => {
293
+ for (const name of target.alphaChannelNames!) {
294
+ writeUnicodeStringWithPadding(writer, name);
295
+ }
296
+ },
297
+ );
298
+
299
+ MOCK_HANDLERS && addHandler(
300
+ 1077,
301
+ target => (target as any)._ir1077 !== undefined,
302
+ (reader, target, left) => {
303
+ LOG_MOCK_HANDLERS && console.log('image resource 1077', left());
304
+ (target as any)._ir1077 = readBytes(reader, left());
305
+ },
306
+ (writer, target) => {
307
+ writeBytes(writer, (target as any)._ir1077);
308
+ },
309
+ );
310
+
311
+ addHandler(
312
+ 1053,
313
+ target => target.alphaIdentifiers !== undefined,
314
+ (reader, target, left) => {
315
+ target.alphaIdentifiers = [];
316
+
317
+ while (left() >= 4) {
318
+ target.alphaIdentifiers.push(readUint32(reader));
319
+ }
320
+ },
321
+ (writer, target) => {
322
+ for (const id of target.alphaIdentifiers!) {
323
+ writeUint32(writer, id);
324
+ }
325
+ },
326
+ );
327
+
328
+ addHandler(
329
+ 1010,
330
+ target => target.backgroundColor !== undefined,
331
+ (reader, target) => target.backgroundColor = readColor(reader),
332
+ (writer, target) => writeColor(writer, target.backgroundColor!),
333
+ );
334
+
335
+ addHandler(
336
+ 1037,
337
+ target => target.globalAngle !== undefined,
338
+ (reader, target) => target.globalAngle = readUint32(reader),
339
+ (writer, target) => writeUint32(writer, target.globalAngle!),
340
+ );
341
+
342
+ addHandler(
343
+ 1049,
344
+ target => target.globalAltitude !== undefined,
345
+ (reader, target) => target.globalAltitude = readUint32(reader),
346
+ (writer, target) => writeUint32(writer, target.globalAltitude!),
347
+ );
348
+
349
+ addHandler(
350
+ 1011,
351
+ target => target.printFlags !== undefined,
352
+ (reader, target) => {
353
+ target.printFlags = {
354
+ labels: !!readUint8(reader),
355
+ cropMarks: !!readUint8(reader),
356
+ colorBars: !!readUint8(reader),
357
+ registrationMarks: !!readUint8(reader),
358
+ negative: !!readUint8(reader),
359
+ flip: !!readUint8(reader),
360
+ interpolate: !!readUint8(reader),
361
+ caption: !!readUint8(reader),
362
+ printFlags: !!readUint8(reader),
363
+ };
364
+ },
365
+ (writer, target) => {
366
+ const flags = target.printFlags!;
367
+ writeUint8(writer, flags.labels ? 1 : 0);
368
+ writeUint8(writer, flags.cropMarks ? 1 : 0);
369
+ writeUint8(writer, flags.colorBars ? 1 : 0);
370
+ writeUint8(writer, flags.registrationMarks ? 1 : 0);
371
+ writeUint8(writer, flags.negative ? 1 : 0);
372
+ writeUint8(writer, flags.flip ? 1 : 0);
373
+ writeUint8(writer, flags.interpolate ? 1 : 0);
374
+ writeUint8(writer, flags.caption ? 1 : 0);
375
+ writeUint8(writer, flags.printFlags ? 1 : 0);
376
+ },
377
+ );
378
+
379
+ MOCK_HANDLERS && addHandler(
380
+ 10000, // Print flags
381
+ target => (target as any)._ir10000 !== undefined,
382
+ (reader, target, left) => {
383
+ LOG_MOCK_HANDLERS && console.log('image resource 10000', left());
384
+ (target as any)._ir10000 = readBytes(reader, left());
385
+ },
386
+ (writer, target) => {
387
+ writeBytes(writer, (target as any)._ir10000);
388
+ },
389
+ );
390
+
391
+ MOCK_HANDLERS && addHandler(
392
+ 1013, // Color halftoning
393
+ target => (target as any)._ir1013 !== undefined,
394
+ (reader, target, left) => {
395
+ LOG_MOCK_HANDLERS && console.log('image resource 1013', left());
396
+ (target as any)._ir1013 = readBytes(reader, left());
397
+ },
398
+ (writer, target) => {
399
+ writeBytes(writer, (target as any)._ir1013);
400
+ },
401
+ );
402
+
403
+ MOCK_HANDLERS && addHandler(
404
+ 1016, // Color transfer functions
405
+ target => (target as any)._ir1016 !== undefined,
406
+ (reader, target, left) => {
407
+ LOG_MOCK_HANDLERS && console.log('image resource 1016', left());
408
+ (target as any)._ir1016 = readBytes(reader, left());
409
+ },
410
+ (writer, target) => {
411
+ writeBytes(writer, (target as any)._ir1016);
412
+ },
413
+ );
414
+
415
+ interface CountInformationDesc {
416
+ Vrsn: 1;
417
+ countGroupList: {
418
+ 'Rd ': number; // 0-255
419
+ 'Grn ': number;
420
+ 'Bl ': number;
421
+ 'Nm ': string;
422
+ 'Rds ': number; // Marker size
423
+ fontSize: number;
424
+ Vsbl: boolean;
425
+ countObjectList: {
426
+ 'X ': number;
427
+ 'Y ': number;
428
+ }[];
429
+ }[];
430
+ }
431
+
432
+ addHandler(
433
+ 1080, // Count Information
434
+ target => target.countInformation !== undefined,
435
+ (reader, target) => {
436
+ const desc = readVersionAndDescriptor(reader) as CountInformationDesc;
437
+ target.countInformation = desc.countGroupList.map(g => ({
438
+ color: { r: g['Rd '], g: g['Grn '], b: g['Bl '] },
439
+ name: g['Nm '],
440
+ size: g['Rds '],
441
+ fontSize: g.fontSize,
442
+ visible: g.Vsbl,
443
+ points: g.countObjectList.map(p => ({ x: p['X '], y: p['Y '] })),
444
+ }));
445
+ },
446
+ (writer, target) => {
447
+ const desc: CountInformationDesc = {
448
+ Vrsn: 1,
449
+ countGroupList: target.countInformation!.map(g => ({
450
+ 'Rd ': g.color.r,
451
+ 'Grn ': g.color.g,
452
+ 'Bl ': g.color.b,
453
+ 'Nm ': g.name,
454
+ 'Rds ': g.size,
455
+ fontSize: g.fontSize,
456
+ Vsbl: g.visible,
457
+ countObjectList: g.points.map(p => ({ 'X ': p.x, 'Y ': p.y })),
458
+ })),
459
+ };
460
+ writeVersionAndDescriptor(writer, '', 'Cnt ', desc);
461
+ },
462
+ );
463
+
464
+ addHandler(
465
+ 1024,
466
+ target => target.layerState !== undefined,
467
+ (reader, target) => target.layerState = readUint16(reader),
468
+ (writer, target) => writeUint16(writer, target.layerState!),
469
+ );
470
+
471
+ addHandler(
472
+ 1026,
473
+ target => target.layersGroup !== undefined,
474
+ (reader, target, left) => {
475
+ target.layersGroup = [];
476
+
477
+ while (left()) {
478
+ target.layersGroup.push(readUint16(reader));
479
+ }
480
+ },
481
+ (writer, target) => {
482
+ for (const g of target.layersGroup!) {
483
+ writeUint16(writer, g);
484
+ }
485
+ },
486
+ );
487
+
488
+ addHandler(
489
+ 1072,
490
+ target => target.layerGroupsEnabledId !== undefined,
491
+ (reader, target, left) => {
492
+ target.layerGroupsEnabledId = [];
493
+
494
+ while (left()) {
495
+ target.layerGroupsEnabledId.push(readUint8(reader));
496
+ }
497
+ },
498
+ (writer, target) => {
499
+ for (const id of target.layerGroupsEnabledId!) {
500
+ writeUint8(writer, id);
501
+ }
502
+ },
503
+ );
504
+
505
+ addHandler(
506
+ 1069,
507
+ target => target.layerSelectionIds !== undefined,
508
+ (reader, target) => {
509
+ let count = readUint16(reader);
510
+ target.layerSelectionIds = [];
511
+
512
+ while (count--) {
513
+ target.layerSelectionIds.push(readUint32(reader));
514
+ }
515
+ },
516
+ (writer, target) => {
517
+ writeUint16(writer, target.layerSelectionIds!.length);
518
+
519
+ for (const id of target.layerSelectionIds!) {
520
+ writeUint32(writer, id);
521
+ }
522
+ },
523
+ );
524
+
525
+ addHandler(
526
+ 1032,
527
+ target => target.gridAndGuidesInformation !== undefined,
528
+ (reader, target) => {
529
+ const version = readUint32(reader);
530
+ const horizontal = readUint32(reader);
531
+ const vertical = readUint32(reader);
532
+ const count = readUint32(reader);
533
+
534
+ if (version !== 1) throw new Error(`Invalid 1032 resource version: ${version}`);
535
+
536
+ target.gridAndGuidesInformation = {
537
+ grid: { horizontal, vertical },
538
+ guides: [],
539
+ };
540
+
541
+ for (let i = 0; i < count; i++) {
542
+ target.gridAndGuidesInformation.guides!.push({
543
+ location: readUint32(reader) / 32,
544
+ direction: readUint8(reader) ? 'horizontal' : 'vertical'
545
+ });
546
+ }
547
+ },
548
+ (writer, target) => {
549
+ const info = target.gridAndGuidesInformation!;
550
+ const grid = info.grid || { horizontal: 18 * 32, vertical: 18 * 32 };
551
+ const guides = info.guides || [];
552
+
553
+ writeUint32(writer, 1);
554
+ writeUint32(writer, grid.horizontal);
555
+ writeUint32(writer, grid.vertical);
556
+ writeUint32(writer, guides.length);
557
+
558
+ for (const g of guides) {
559
+ writeUint32(writer, g.location * 32);
560
+ writeUint8(writer, g.direction === 'horizontal' ? 1 : 0);
561
+ }
562
+ },
563
+ );
564
+
565
+ interface OnionSkinsDescriptor {
566
+ Vrsn: 1;
567
+ enab: boolean;
568
+ numBefore: number;
569
+ numAfter: number;
570
+ Spcn: number;
571
+ minOpacity: number;
572
+ maxOpacity: number;
573
+ BlnM: number;
574
+ }
575
+
576
+ // 0 - normal, 7 - multiply, 8 - screen, 23 - difference
577
+ const onionSkinsBlendModes: (BlendMode | undefined)[] = [
578
+ 'normal', undefined, undefined, undefined, undefined, undefined, undefined, 'multiply',
579
+ 'screen', undefined, undefined, undefined, undefined, undefined, undefined, undefined,
580
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, 'difference',
581
+ ];
582
+
583
+ addHandler(
584
+ 1078, // Onion Skins
585
+ target => target.onionSkins !== undefined,
586
+ (reader, target) => {
587
+ const desc = readVersionAndDescriptor(reader) as OnionSkinsDescriptor;
588
+ // console.log('1078', require('util').inspect(desc, false, 99, true));
589
+
590
+ target.onionSkins = {
591
+ enabled: desc.enab,
592
+ framesBefore: desc.numBefore,
593
+ framesAfter: desc.numAfter,
594
+ frameSpacing: desc.Spcn,
595
+ minOpacity: desc.minOpacity / 100,
596
+ maxOpacity: desc.maxOpacity / 100,
597
+ blendMode: onionSkinsBlendModes[desc.BlnM] || 'normal',
598
+ };
599
+ },
600
+ (writer, target) => {
601
+ const onionSkins = target.onionSkins!;
602
+ const desc: OnionSkinsDescriptor = {
603
+ Vrsn: 1,
604
+ enab: onionSkins.enabled,
605
+ numBefore: onionSkins.framesBefore,
606
+ numAfter: onionSkins.framesAfter,
607
+ Spcn: onionSkins.frameSpacing,
608
+ minOpacity: (onionSkins.minOpacity * 100) | 0,
609
+ maxOpacity: (onionSkins.maxOpacity * 100) | 0,
610
+ BlnM: Math.max(0, onionSkinsBlendModes.indexOf(onionSkins.blendMode)),
611
+ };
612
+
613
+ writeVersionAndDescriptor(writer, '', 'null', desc);
614
+ },
615
+ );
616
+
617
+ interface TimelineAudioClipDescriptor {
618
+ clipID: string;
619
+ timeScope: TimeScopeDescriptor;
620
+ frameReader: {
621
+ frameReaderType: number;
622
+ descVersion: 1;
623
+ 'Lnk ': {
624
+ descVersion: 1;
625
+ 'Nm ': string;
626
+ fullPath: string;
627
+ relPath: string;
628
+ },
629
+ mediaDescriptor: string;
630
+ },
631
+ muted: boolean;
632
+ audioLevel: number;
633
+ }
634
+
635
+ interface TimelineAudioClipGroupDescriptor {
636
+ groupID: string;
637
+ muted: boolean;
638
+ audioClipList: TimelineAudioClipDescriptor[];
639
+ }
640
+
641
+ interface TimelineInformationDescriptor {
642
+ Vrsn: 1;
643
+ enab: boolean;
644
+ frameStep: FractionDescriptor;
645
+ frameRate: number;
646
+ time: FractionDescriptor;
647
+ duration: FractionDescriptor;
648
+ workInTime: FractionDescriptor;
649
+ workOutTime: FractionDescriptor;
650
+ LCnt: number;
651
+ globalTrackList: TimelineTrackDescriptor[];
652
+ audioClipGroupList?: {
653
+ audioClipGroupList?: TimelineAudioClipGroupDescriptor[];
654
+ },
655
+ hasMotion: boolean;
656
+ }
657
+
658
+ addHandler(
659
+ 1075, // Timeline Information
660
+ target => target.timelineInformation !== undefined,
661
+ (reader, target, _, options) => {
662
+ const desc = readVersionAndDescriptor(reader) as TimelineInformationDescriptor;
663
+ // console.log('1075', require('util').inspect(desc, false, 99, true));
664
+
665
+ target.timelineInformation = {
666
+ enabled: desc.enab,
667
+ frameStep: desc.frameStep,
668
+ frameRate: desc.frameRate,
669
+ time: desc.time,
670
+ duration: desc.duration,
671
+ workInTime: desc.workInTime,
672
+ workOutTime: desc.workOutTime,
673
+ repeats: desc.LCnt,
674
+ hasMotion: desc.hasMotion,
675
+ globalTracks: parseTrackList(desc.globalTrackList, !!options.logMissingFeatures),
676
+ };
677
+
678
+ if (desc.audioClipGroupList?.audioClipGroupList?.length) {
679
+ target.timelineInformation.audioClipGroups = desc.audioClipGroupList.audioClipGroupList.map(g => ({
680
+ id: g.groupID,
681
+ muted: g.muted,
682
+ audioClips: g.audioClipList.map(({ clipID, timeScope, muted, audioLevel, frameReader }) => ({
683
+ id: clipID,
684
+ start: timeScope.Strt,
685
+ duration: timeScope.duration,
686
+ inTime: timeScope.inTime,
687
+ outTime: timeScope.outTime,
688
+ muted: muted,
689
+ audioLevel: audioLevel,
690
+ frameReader: {
691
+ type: frameReader.frameReaderType,
692
+ mediaDescriptor: frameReader.mediaDescriptor,
693
+ link: {
694
+ name: frameReader['Lnk ']['Nm '],
695
+ fullPath: frameReader['Lnk '].fullPath,
696
+ relativePath: frameReader['Lnk '].relPath,
697
+ },
698
+ },
699
+ })),
700
+ }));
701
+ }
702
+ },
703
+ (writer, target) => {
704
+ const timeline = target.timelineInformation!;
705
+ const desc: TimelineInformationDescriptor = {
706
+ Vrsn: 1,
707
+ enab: timeline.enabled,
708
+ frameStep: timeline.frameStep,
709
+ frameRate: timeline.frameRate,
710
+ time: timeline.time,
711
+ duration: timeline.duration,
712
+ workInTime: timeline.workInTime,
713
+ workOutTime: timeline.workOutTime,
714
+ LCnt: timeline.repeats,
715
+ globalTrackList: serializeTrackList(timeline.globalTracks),
716
+ audioClipGroupList: {
717
+ audioClipGroupList: timeline.audioClipGroups?.map(a => ({
718
+ groupID: a.id,
719
+ muted: a.muted,
720
+ audioClipList: a.audioClips.map<TimelineAudioClipDescriptor>(c => ({
721
+ clipID: c.id,
722
+ timeScope: {
723
+ Vrsn: 1,
724
+ Strt: c.start,
725
+ duration: c.duration,
726
+ inTime: c.inTime,
727
+ outTime: c.outTime,
728
+ },
729
+ frameReader: {
730
+ frameReaderType: c.frameReader.type,
731
+ descVersion: 1,
732
+ 'Lnk ': {
733
+ descVersion: 1,
734
+ 'Nm ': c.frameReader.link.name,
735
+ fullPath: c.frameReader.link.fullPath,
736
+ relPath: c.frameReader.link.relativePath,
737
+ },
738
+ mediaDescriptor: c.frameReader.mediaDescriptor,
739
+ },
740
+ muted: c.muted,
741
+ audioLevel: c.audioLevel,
742
+ })),
743
+ })),
744
+ },
745
+ hasMotion: timeline.hasMotion,
746
+ };
747
+
748
+ // console.log('WRITE:1075', require('util').inspect(desc, false, 99, true));
749
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'anim');
750
+ },
751
+ );
752
+
753
+ interface SheetDisclosureDescriptor {
754
+ Vrsn: 1;
755
+ sheetTimelineOptions?: {
756
+ Vrsn: 2;
757
+ sheetID: number;
758
+ sheetDisclosed: boolean;
759
+ lightsDisclosed: boolean;
760
+ meshesDisclosed: boolean;
761
+ materialsDisclosed: boolean;
762
+ }[];
763
+ }
764
+
765
+ addHandler(
766
+ 1076, // Sheet Disclosure
767
+ target => target.sheetDisclosure !== undefined,
768
+ (reader, target) => {
769
+ const desc = readVersionAndDescriptor(reader) as SheetDisclosureDescriptor;
770
+ // console.log('1076', require('util').inspect(desc, false, 99, true));
771
+
772
+ target.sheetDisclosure = {};
773
+
774
+ if (desc.sheetTimelineOptions) {
775
+ target.sheetDisclosure.sheetTimelineOptions = desc.sheetTimelineOptions.map(o => ({
776
+ sheetID: o.sheetID,
777
+ sheetDisclosed: o.sheetDisclosed,
778
+ lightsDisclosed: o.lightsDisclosed,
779
+ meshesDisclosed: o.meshesDisclosed,
780
+ materialsDisclosed: o.materialsDisclosed,
781
+ }));
782
+ }
783
+ },
784
+ (writer, target) => {
785
+ const disclosure = target.sheetDisclosure!;
786
+ const desc: SheetDisclosureDescriptor = { Vrsn: 1 };
787
+
788
+ if (disclosure.sheetTimelineOptions) {
789
+ desc.sheetTimelineOptions = disclosure.sheetTimelineOptions.map(d => ({
790
+ Vrsn: 2,
791
+ sheetID: d.sheetID,
792
+ sheetDisclosed: d.sheetDisclosed,
793
+ lightsDisclosed: d.lightsDisclosed,
794
+ meshesDisclosed: d.meshesDisclosed,
795
+ materialsDisclosed: d.materialsDisclosed,
796
+ }));
797
+ }
798
+
799
+ writeVersionAndDescriptor(writer, '', 'null', desc);
800
+ },
801
+ );
802
+
803
+ addHandler(
804
+ 1054, // URL List
805
+ target => target.urlsList !== undefined,
806
+ (reader, target, _, options) => {
807
+ const count = readUint32(reader);
808
+ target.urlsList = [];
809
+
810
+ for (let i = 0; i < count; i++) {
811
+ const long = readSignature(reader);
812
+ if (long !== 'slic' && options.throwForMissingFeatures) throw new Error('Unknown long');
813
+ const id = readUint32(reader);
814
+ const url = readUnicodeString(reader);
815
+ target.urlsList.push({ id, url, ref: 'slice' });
816
+ }
817
+ },
818
+ (writer, target) => {
819
+ const list = target.urlsList!;
820
+ writeUint32(writer, list.length);
821
+
822
+ for (let i = 0; i < list.length; i++) {
823
+ writeSignature(writer, 'slic');
824
+ writeUint32(writer, list[i].id);
825
+ writeUnicodeString(writer, list[i].url);
826
+ }
827
+ },
828
+ );
829
+
830
+ interface BoundsDesc {
831
+ 'Top ': number;
832
+ Left: number;
833
+ Btom: number;
834
+ Rght: number;
835
+ }
836
+
837
+ interface SlicesSliceDesc {
838
+ sliceID: number;
839
+ groupID: number;
840
+ origin: string;
841
+ 'Nm '?: string;
842
+ Type: string;
843
+ bounds: BoundsDesc;
844
+ url: string;
845
+ null: string;
846
+ Msge: string;
847
+ altTag: string;
848
+ cellTextIsHTML: boolean;
849
+ cellText: string;
850
+ horzAlign: string;
851
+ vertAlign: string;
852
+ bgColorType: string;
853
+ bgColor?: { 'Rd ': number; 'Grn ': number; 'Bl ': number; alpha: number; };
854
+ topOutset?: number;
855
+ leftOutset?: number;
856
+ bottomOutset?: number;
857
+ rightOutset?: number;
858
+ }
859
+
860
+ interface SlicesDesc {
861
+ bounds: BoundsDesc;
862
+ slices: SlicesSliceDesc[];
863
+ }
864
+
865
+ interface SlicesDesc7 extends SlicesDesc {
866
+ baseName: string;
867
+ }
868
+
869
+ function boundsToBounds(bounds: { left: number; top: number; right: number; bottom: number }): BoundsDesc {
870
+ return { 'Top ': bounds.top, Left: bounds.left, Btom: bounds.bottom, Rght: bounds.right };
871
+ }
872
+
873
+ function boundsFromBounds(bounds: BoundsDesc): { left: number; top: number; right: number; bottom: number } {
874
+ return { top: bounds['Top '], left: bounds.Left, bottom: bounds.Btom, right: bounds.Rght };
875
+ }
876
+
877
+ function clamped<T>(array: T[], index: number) {
878
+ return array[Math.max(0, Math.min(array.length - 1, index))];
879
+ }
880
+
881
+ const sliceOrigins: ('userGenerated' | 'autoGenerated' | 'layer')[] = ['autoGenerated', 'layer', 'userGenerated'];
882
+ const sliceTypes: ('image' | 'noImage')[] = ['noImage', 'image'];
883
+ const sliceAlignments: ('default')[] = ['default'];
884
+
885
+ addHandler(
886
+ 1050, // Slices
887
+ target => target.slices ? target.slices.length : 0,
888
+ (reader, target) => {
889
+ const version = readUint32(reader);
890
+
891
+ if (version == 6) {
892
+ if (!target.slices) target.slices = [];
893
+
894
+ const top = readInt32(reader);
895
+ const left = readInt32(reader);
896
+ const bottom = readInt32(reader);
897
+ const right = readInt32(reader);
898
+ const groupName = readUnicodeString(reader);
899
+ const count = readUint32(reader);
900
+ target.slices.push({ bounds: { top, left, bottom, right }, groupName, slices: [] });
901
+ const slices = target.slices[target.slices.length - 1].slices;
902
+
903
+ for (let i = 0; i < count; i++) {
904
+ const id = readUint32(reader);
905
+ const groupId = readUint32(reader);
906
+ const origin = clamped(sliceOrigins, readUint32(reader));
907
+ const associatedLayerId = origin == 'layer' ? readUint32(reader) : 0;
908
+ const name = readUnicodeString(reader);
909
+ const type = clamped(sliceTypes, readUint32(reader));
910
+ const top = readInt32(reader);
911
+ const left = readInt32(reader);
912
+ const bottom = readInt32(reader);
913
+ const right = readInt32(reader);
914
+ const url = readUnicodeString(reader);
915
+ const target = readUnicodeString(reader);
916
+ const message = readUnicodeString(reader);
917
+ const altTag = readUnicodeString(reader);
918
+ const cellTextIsHTML = !!readUint8(reader);
919
+ const cellText = readUnicodeString(reader);
920
+ const horizontalAlignment = clamped(sliceAlignments, readUint32(reader));
921
+ const verticalAlignment = clamped(sliceAlignments, readUint32(reader));
922
+ const a = readUint8(reader);
923
+ const r = readUint8(reader);
924
+ const g = readUint8(reader);
925
+ const b = readUint8(reader);
926
+ const backgroundColorType = ((a + r + g + b) === 0) ? 'none' : (a === 0 ? 'matte' : 'color');
927
+ slices.push({
928
+ id, groupId, origin, associatedLayerId, name, target, message, altTag, cellTextIsHTML, cellText,
929
+ horizontalAlignment, verticalAlignment, type, url,
930
+ bounds: { top, left, bottom, right },
931
+ backgroundColorType, backgroundColor: { r, g, b, a },
932
+ });
933
+ // console.log(require('util').inspect(slices[slices.length - 1], false, 99, true));
934
+ }
935
+ const desc = readVersionAndDescriptor(reader) as SlicesDesc;
936
+ desc.slices.forEach(d => {
937
+ const slice = slices.find(s => d.sliceID == s.id);
938
+ if (slice) {
939
+ slice.topOutset = d.topOutset;
940
+ slice.leftOutset = d.leftOutset;
941
+ slice.bottomOutset = d.bottomOutset;
942
+ slice.rightOutset = d.rightOutset;
943
+ }
944
+ });
945
+
946
+ // console.log(require('util').inspect(desc, false, 99, true));
947
+ // console.log(require('util').inspect(target.slices, false, 99, true));
948
+ } else if (version == 7 || version == 8) {
949
+ const desc = readVersionAndDescriptor(reader) as SlicesDesc7;
950
+ // console.log(require('util').inspect(desc, false, 99, true));
951
+
952
+ if (!target.slices) target.slices = [];
953
+ target.slices.push({
954
+ groupName: desc.baseName,
955
+ bounds: boundsFromBounds(desc.bounds),
956
+ slices: desc.slices.map(s => ({
957
+ name: '',
958
+ id: s.sliceID,
959
+ groupId: s.groupID,
960
+ associatedLayerId: 0,
961
+ origin: ESliceOrigin.decode(s.origin),
962
+ type: ESliceType.decode(s.Type),
963
+ bounds: boundsFromBounds(s.bounds),
964
+ url: s.url,
965
+ target: s.null,
966
+ message: s.Msge,
967
+ altTag: s.altTag,
968
+ cellTextIsHTML: s.cellTextIsHTML,
969
+ cellText: s.cellText,
970
+ horizontalAlignment: ESliceHorzAlign.decode(s.horzAlign),
971
+ verticalAlignment: ESliceVertAlign.decode(s.vertAlign),
972
+ backgroundColorType: ESliceBGColorType.decode(s.bgColorType),
973
+ 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 },
974
+ topOutset: s.topOutset || 0,
975
+ leftOutset: s.leftOutset || 0,
976
+ bottomOutset: s.bottomOutset || 0,
977
+ rightOutset: s.rightOutset || 0,
978
+ })),
979
+ });
980
+ } else {
981
+ throw new Error(`Invalid slices version (${version})`);
982
+ }
983
+ },
984
+ (writer, target, index) => {
985
+ const { bounds, groupName, slices } = target.slices![index];
986
+
987
+ writeUint32(writer, 6); // version
988
+ writeInt32(writer, bounds.top);
989
+ writeInt32(writer, bounds.left);
990
+ writeInt32(writer, bounds.bottom);
991
+ writeInt32(writer, bounds.right);
992
+ writeUnicodeString(writer, groupName);
993
+ writeUint32(writer, slices.length);
994
+
995
+ for (let i = 0; i < slices.length; i++) {
996
+ const slice = slices[i];
997
+ let { a, r, g, b } = slice.backgroundColor;
998
+
999
+ if (slice.backgroundColorType === 'none') {
1000
+ a = r = g = b = 0;
1001
+ } else if (slice.backgroundColorType === 'matte') {
1002
+ a = 0;
1003
+ r = g = b = 255;
1004
+ }
1005
+
1006
+ writeUint32(writer, slice.id);
1007
+ writeUint32(writer, slice.groupId);
1008
+ writeUint32(writer, sliceOrigins.indexOf(slice.origin));
1009
+ if (slice.origin === 'layer') writeUint32(writer, slice.associatedLayerId);
1010
+ writeUnicodeString(writer, slice.name);
1011
+ writeUint32(writer, sliceTypes.indexOf(slice.type));
1012
+ writeInt32(writer, slice.bounds.top);
1013
+ writeInt32(writer, slice.bounds.left);
1014
+ writeInt32(writer, slice.bounds.bottom);
1015
+ writeInt32(writer, slice.bounds.right);
1016
+ writeUnicodeString(writer, slice.url);
1017
+ writeUnicodeString(writer, slice.target);
1018
+ writeUnicodeString(writer, slice.message);
1019
+ writeUnicodeString(writer, slice.altTag);
1020
+ writeUint8(writer, slice.cellTextIsHTML ? 1 : 0);
1021
+ writeUnicodeString(writer, slice.cellText);
1022
+ writeUint32(writer, sliceAlignments.indexOf(slice.horizontalAlignment));
1023
+ writeUint32(writer, sliceAlignments.indexOf(slice.verticalAlignment));
1024
+ writeUint8(writer, a);
1025
+ writeUint8(writer, r);
1026
+ writeUint8(writer, g);
1027
+ writeUint8(writer, b);
1028
+ }
1029
+
1030
+ const desc: SlicesDesc = {
1031
+ bounds: boundsToBounds(bounds),
1032
+ slices: [],
1033
+ };
1034
+
1035
+ slices.forEach(s => {
1036
+ const slice: SlicesSliceDesc = {
1037
+ sliceID: s.id,
1038
+ groupID: s.groupId,
1039
+ origin: ESliceOrigin.encode(s.origin),
1040
+ Type: ESliceType.encode(s.type),
1041
+ bounds: boundsToBounds(s.bounds),
1042
+ ...(s.name ? { 'Nm ': s.name } : {}),
1043
+ url: s.url,
1044
+ null: s.target,
1045
+ Msge: s.message,
1046
+ altTag: s.altTag,
1047
+ cellTextIsHTML: s.cellTextIsHTML,
1048
+ cellText: s.cellText,
1049
+ horzAlign: ESliceHorzAlign.encode(s.horizontalAlignment),
1050
+ vertAlign: ESliceVertAlign.encode(s.verticalAlignment),
1051
+ bgColorType: ESliceBGColorType.encode(s.backgroundColorType),
1052
+ };
1053
+
1054
+ if (s.backgroundColorType === 'color') {
1055
+ const { r, g, b, a } = s.backgroundColor;
1056
+ slice.bgColor = { 'Rd ': r, 'Grn ': g, 'Bl ': b, alpha: a };
1057
+ }
1058
+
1059
+ slice.topOutset = s.topOutset || 0;
1060
+ slice.leftOutset = s.leftOutset || 0;
1061
+ slice.bottomOutset = s.bottomOutset || 0;
1062
+ slice.rightOutset = s.rightOutset || 0;
1063
+ desc.slices.push(slice);
1064
+ });
1065
+
1066
+ writeVersionAndDescriptor(writer, '', 'null', desc, 'slices');
1067
+ },
1068
+ );
1069
+
1070
+ addHandler(
1071
+ 1064,
1072
+ target => target.pixelAspectRatio !== undefined,
1073
+ (reader, target) => {
1074
+ if (readUint32(reader) > 2) throw new Error('Invalid pixelAspectRatio version');
1075
+ target.pixelAspectRatio = { aspect: readFloat64(reader) };
1076
+ },
1077
+ (writer, target) => {
1078
+ writeUint32(writer, 2); // version
1079
+ writeFloat64(writer, target.pixelAspectRatio!.aspect);
1080
+ },
1081
+ );
1082
+
1083
+ addHandler(
1084
+ 1041,
1085
+ target => target.iccUntaggedProfile !== undefined,
1086
+ (reader, target) => {
1087
+ target.iccUntaggedProfile = !!readUint8(reader);
1088
+ },
1089
+ (writer, target) => {
1090
+ writeUint8(writer, target.iccUntaggedProfile ? 1 : 0);
1091
+ },
1092
+ );
1093
+
1094
+ MOCK_HANDLERS && addHandler(
1095
+ 1039, // ICC Profile
1096
+ target => (target as any)._ir1039 !== undefined,
1097
+ (reader, target, left) => {
1098
+ // TODO: this is raw bytes, just return as a byte array
1099
+ LOG_MOCK_HANDLERS && console.log('image resource 1039', left());
1100
+ (target as any)._ir1039 = readBytes(reader, left());
1101
+ },
1102
+ (writer, target) => {
1103
+ writeBytes(writer, (target as any)._ir1039);
1104
+ },
1105
+ );
1106
+
1107
+ addHandler(
1108
+ 1044,
1109
+ target => target.idsSeedNumber !== undefined,
1110
+ (reader, target) => target.idsSeedNumber = readUint32(reader),
1111
+ (writer, target) => writeUint32(writer, target.idsSeedNumber!),
1112
+ );
1113
+
1114
+ addHandler(
1115
+ 1036,
1116
+ target => target.thumbnail !== undefined || target.thumbnailRaw !== undefined,
1117
+ (reader, target, left, options) => {
1118
+ const format = readUint32(reader); // 1 = kJpegRGB, 0 = kRawRGB
1119
+ const width = readUint32(reader);
1120
+ const height = readUint32(reader);
1121
+ readUint32(reader); // widthBytes = (width * bits_per_pixel + 31) / 32 * 4.
1122
+ readUint32(reader); // totalSize = widthBytes * height * planes
1123
+ readUint32(reader); // sizeAfterCompression
1124
+ const bitsPerPixel = readUint16(reader); // 24
1125
+ const planes = readUint16(reader); // 1
1126
+
1127
+ if (format !== 1 || bitsPerPixel !== 24 || planes !== 1) {
1128
+ options.logMissingFeatures && console.log(`Invalid thumbnail data (format: ${format}, bitsPerPixel: ${bitsPerPixel}, planes: ${planes})`);
1129
+ skipBytes(reader, left());
1130
+ return;
1131
+ }
1132
+
1133
+ const size = left();
1134
+ const data = readBytes(reader, size);
1135
+
1136
+ if (options.useRawThumbnail) {
1137
+ target.thumbnailRaw = { width, height, data };
1138
+ } else if (data.byteLength) {
1139
+ target.thumbnail = createCanvasFromData(data);
1140
+ }
1141
+ },
1142
+ (writer, target) => {
1143
+ let width = 0;
1144
+ let height = 0;
1145
+ let data: Uint8Array;
1146
+
1147
+ if (target.thumbnailRaw) {
1148
+ width = target.thumbnailRaw.width;
1149
+ height = target.thumbnailRaw.height;
1150
+ data = target.thumbnailRaw.data;
1151
+ } else {
1152
+ const dataUrl = target.thumbnail!.toDataURL('image/jpeg', 1)?.substring('data:image/jpeg;base64,'.length);
1153
+
1154
+ if (dataUrl) {
1155
+ width = target.thumbnail!.width;
1156
+ height = target.thumbnail!.height;
1157
+ data = toByteArray(dataUrl);
1158
+ } else {
1159
+ data = new Uint8Array(0);
1160
+ }
1161
+ }
1162
+
1163
+ const bitsPerPixel = 24;
1164
+ const widthBytes = Math.floor((width * bitsPerPixel + 31) / 32) * 4;
1165
+ const planes = 1;
1166
+ const totalSize = widthBytes * height * planes;
1167
+ const sizeAfterCompression = data.length;
1168
+
1169
+ writeUint32(writer, 1); // 1 = kJpegRGB
1170
+ writeUint32(writer, width);
1171
+ writeUint32(writer, height);
1172
+ writeUint32(writer, widthBytes);
1173
+ writeUint32(writer, totalSize);
1174
+ writeUint32(writer, sizeAfterCompression);
1175
+ writeUint16(writer, bitsPerPixel);
1176
+ writeUint16(writer, planes);
1177
+ writeBytes(writer, data);
1178
+ },
1179
+ );
1180
+
1181
+ addHandler(
1182
+ 1057,
1183
+ target => target.versionInfo !== undefined,
1184
+ (reader, target, left) => {
1185
+ const version = readUint32(reader);
1186
+ if (version !== 1) throw new Error('Invalid versionInfo version');
1187
+
1188
+ target.versionInfo = {
1189
+ hasRealMergedData: !!readUint8(reader),
1190
+ writerName: readUnicodeString(reader),
1191
+ readerName: readUnicodeString(reader),
1192
+ fileVersion: readUint32(reader),
1193
+ };
1194
+
1195
+ skipBytes(reader, left());
1196
+ },
1197
+ (writer, target) => {
1198
+ const versionInfo = target.versionInfo!;
1199
+ writeUint32(writer, 1); // version
1200
+ writeUint8(writer, versionInfo.hasRealMergedData ? 1 : 0);
1201
+ writeUnicodeString(writer, versionInfo.writerName);
1202
+ writeUnicodeString(writer, versionInfo.readerName);
1203
+ writeUint32(writer, versionInfo.fileVersion);
1204
+ },
1205
+ );
1206
+
1207
+ MOCK_HANDLERS && addHandler(
1208
+ 1058, // EXIF data 1.
1209
+ target => (target as any)._ir1058 !== undefined,
1210
+ (reader, target, left) => {
1211
+ LOG_MOCK_HANDLERS && console.log('image resource 1058', left());
1212
+ (target as any)._ir1058 = readBytes(reader, left());
1213
+ },
1214
+ (writer, target) => {
1215
+ writeBytes(writer, (target as any)._ir1058);
1216
+ },
1217
+ );
1218
+
1219
+ addHandler(
1220
+ 7000,
1221
+ target => target.imageReadyVariables !== undefined,
1222
+ (reader, target, left) => {
1223
+ target.imageReadyVariables = readUtf8String(reader, left());
1224
+ },
1225
+ (writer, target) => {
1226
+ writeUtf8String(writer, target.imageReadyVariables!);
1227
+ },
1228
+ );
1229
+
1230
+ addHandler(
1231
+ 7001,
1232
+ target => target.imageReadyDataSets !== undefined,
1233
+ (reader, target, left) => {
1234
+ target.imageReadyDataSets = readUtf8String(reader, left());
1235
+ },
1236
+ (writer, target) => {
1237
+ writeUtf8String(writer, target.imageReadyDataSets!);
1238
+ },
1239
+ );
1240
+
1241
+ interface Descriptor1088 {
1242
+ 'null': string[];
1243
+ }
1244
+
1245
+ addHandler(
1246
+ 1088,
1247
+ target => target.pathSelectionState !== undefined,
1248
+ (reader, target, _left) => {
1249
+ const desc: Descriptor1088 = readVersionAndDescriptor(reader);
1250
+ // console.log(require('util').inspect(desc, false, 99, true));
1251
+ target.pathSelectionState = desc['null'];
1252
+ },
1253
+ (writer, target) => {
1254
+ const desc: Descriptor1088 = { 'null': target.pathSelectionState! };
1255
+ writeVersionAndDescriptor(writer, '', 'null', desc);
1256
+ },
1257
+ );
1258
+
1259
+ MOCK_HANDLERS && addHandler(
1260
+ 1025,
1261
+ target => (target as any)._ir1025 !== undefined,
1262
+ (reader, target, left) => {
1263
+ LOG_MOCK_HANDLERS && console.log('image resource 1025', left());
1264
+ (target as any)._ir1025 = readBytes(reader, left());
1265
+ },
1266
+ (writer, target) => {
1267
+ writeBytes(writer, (target as any)._ir1025);
1268
+ },
1269
+ );
1270
+
1271
+ const FrmD = createEnum<'auto' | 'none' | 'dispose'>('FrmD', '', {
1272
+ auto: 'Auto',
1273
+ none: 'None',
1274
+ dispose: 'Disp',
1275
+ });
1276
+
1277
+ interface AnimationFrameDescriptor {
1278
+ FrID: number;
1279
+ FrDl?: number;
1280
+ FrDs: string;
1281
+ FrGA?: number;
1282
+ }
1283
+
1284
+ interface AnimationDescriptor {
1285
+ FsID: number;
1286
+ AFrm?: number;
1287
+ FsFr: number[];
1288
+ LCnt: number;
1289
+ }
1290
+
1291
+ interface AnimationsDescriptor {
1292
+ AFSt?: number;
1293
+ FrIn: AnimationFrameDescriptor[];
1294
+ FSts: AnimationDescriptor[];
1295
+ }
1296
+
1297
+ addHandler(
1298
+ 4000, // Plug-In resource(s)
1299
+ target => target.animations !== undefined,
1300
+ (reader, target, left, { logMissingFeatures, logDevFeatures }) => {
1301
+ const key = readSignature(reader);
1302
+
1303
+ if (key === 'mani') {
1304
+ checkSignature(reader, 'IRFR');
1305
+ readSection(reader, 1, left => {
1306
+ while (left()) {
1307
+ checkSignature(reader, '8BIM');
1308
+ const key = readSignature(reader);
1309
+
1310
+ readSection(reader, 1, left => {
1311
+ if (key === 'AnDs') {
1312
+ const desc = readVersionAndDescriptor(reader) as AnimationsDescriptor;
1313
+ target.animations = {
1314
+ // desc.AFSt ???
1315
+ frames: desc.FrIn.map(x => ({
1316
+ id: x.FrID,
1317
+ delay: (x.FrDl || 0) / 100,
1318
+ dispose: x.FrDs ? FrmD.decode(x.FrDs) : 'auto', // missing == auto
1319
+ // x.FrGA ???
1320
+ })),
1321
+ animations: desc.FSts.map(x => ({
1322
+ id: x.FsID,
1323
+ frames: x.FsFr,
1324
+ repeats: x.LCnt,
1325
+ activeFrame: x.AFrm || 0,
1326
+ })),
1327
+ };
1328
+
1329
+ // console.log('#4000 AnDs', require('util').inspect(desc, false, 99, true));
1330
+ // console.log('#4000 AnDs:result', require('util').inspect(target.animations, false, 99, true));
1331
+ } else if (key === 'Roll') {
1332
+ const bytes = readBytes(reader, left());
1333
+ logDevFeatures && console.log('#4000 Roll', bytes);
1334
+ } else {
1335
+ logMissingFeatures && console.log('Unhandled subsection in #4000', key);
1336
+ }
1337
+ });
1338
+ }
1339
+ });
1340
+ } else if (key === 'mopt') {
1341
+ const bytes = readBytes(reader, left());
1342
+ logDevFeatures && console.log('#4000 mopt', bytes);
1343
+ } else {
1344
+ logMissingFeatures && console.log('Unhandled key in #4000:', key);
1345
+ }
1346
+ },
1347
+ (writer, target) => {
1348
+ if (target.animations) {
1349
+ writeSignature(writer, 'mani');
1350
+ writeSignature(writer, 'IRFR');
1351
+ writeSection(writer, 1, () => {
1352
+ writeSignature(writer, '8BIM');
1353
+ writeSignature(writer, 'AnDs');
1354
+ writeSection(writer, 1, () => {
1355
+ const desc: AnimationsDescriptor = {
1356
+ // AFSt: 0, // ???
1357
+ FrIn: [],
1358
+ FSts: [],
1359
+ };
1360
+
1361
+ for (let i = 0; i < target.animations!.frames.length; i++) {
1362
+ const f = target.animations!.frames[i];
1363
+ const frame: AnimationFrameDescriptor = {
1364
+ FrID: f.id,
1365
+ } as any;
1366
+ if (f.delay) frame.FrDl = (f.delay * 100) | 0;
1367
+ frame.FrDs = FrmD.encode(f.dispose);
1368
+ // if (i === 0) frame.FrGA = 30; // ???
1369
+ desc.FrIn.push(frame);
1370
+ }
1371
+
1372
+ for (let i = 0; i < target.animations!.animations.length; i++) {
1373
+ const a = target.animations!.animations[i];
1374
+ const anim: AnimationDescriptor = {
1375
+ FsID: a.id,
1376
+ AFrm: a.activeFrame! | 0,
1377
+ FsFr: a.frames,
1378
+ LCnt: a.repeats! | 0,
1379
+ };
1380
+ desc.FSts.push(anim);
1381
+ }
1382
+
1383
+ writeVersionAndDescriptor(writer, '', 'null', desc);
1384
+ });
1385
+
1386
+ // writeSignature(writer, '8BIM');
1387
+ // writeSignature(writer, 'Roll');
1388
+ // writeSection(writer, 1, () => {
1389
+ // writeZeros(writer, 8);
1390
+ // });
1391
+ });
1392
+ }
1393
+ },
1394
+ );
1395
+
1396
+ // TODO: Unfinished
1397
+ MOCK_HANDLERS && addHandler(
1398
+ 4001, // Plug-In resource(s)
1399
+ target => (target as any)._ir4001 !== undefined,
1400
+ (reader, target, left, { logMissingFeatures, logDevFeatures }) => {
1401
+ if (MOCK_HANDLERS) {
1402
+ LOG_MOCK_HANDLERS && console.log('image resource 4001', left());
1403
+ (target as any)._ir4001 = readBytes(reader, left());
1404
+ return;
1405
+ }
1406
+
1407
+ const key = readSignature(reader);
1408
+
1409
+ if (key === 'mfri') {
1410
+ const version = readUint32(reader);
1411
+ if (version !== 2) throw new Error('Invalid mfri version');
1412
+
1413
+ const length = readUint32(reader);
1414
+ const bytes = readBytes(reader, length);
1415
+ logDevFeatures && console.log('mfri', bytes);
1416
+ } else if (key === 'mset') {
1417
+ const desc = readVersionAndDescriptor(reader);
1418
+ logDevFeatures && console.log('mset', desc);
1419
+ } else {
1420
+ logMissingFeatures && console.log('Unhandled key in #4001', key);
1421
+ }
1422
+ },
1423
+ (writer, target) => {
1424
+ writeBytes(writer, (target as any)._ir4001);
1425
+ },
1426
+ );
1427
+
1428
+ // TODO: Unfinished
1429
+ MOCK_HANDLERS && addHandler(
1430
+ 4002, // Plug-In resource(s)
1431
+ target => (target as any)._ir4002 !== undefined,
1432
+ (reader, target, left) => {
1433
+ LOG_MOCK_HANDLERS && console.log('image resource 4002', left());
1434
+ (target as any)._ir4002 = readBytes(reader, left());
1435
+ },
1436
+ (writer, target) => {
1437
+ writeBytes(writer, (target as any)._ir4002);
1438
+ },
1439
+ );