ag-psd 15.0.3 → 15.0.5

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,1804 @@
1
+ import { createEnum } from './helpers';
2
+ import {
3
+ AntiAlias, BevelDirection, BevelStyle, BevelTechnique, BlendMode, Color, EffectContour,
4
+ EffectNoiseGradient, EffectPattern, EffectSolidGradient, ExtraGradientInfo, ExtraPatternInfo,
5
+ GlowSource, GlowTechnique, GradientStyle, InterpolationMethod, LayerEffectBevel,
6
+ LayerEffectGradientOverlay, LayerEffectInnerGlow, LayerEffectPatternOverlay,
7
+ LayerEffectSatin, LayerEffectShadow, LayerEffectsInfo, LayerEffectSolidFill,
8
+ LayerEffectsOuterGlow, LayerEffectStroke, LineAlignment, LineCapType, LineJoinType,
9
+ Orientation, TextGridding, TimelineKey, TimelineKeyInterpolation, TimelineTrack, TimelineTrackType,
10
+ Units, UnitsValue, VectorContent, WarpStyle
11
+ } from './psd';
12
+ import {
13
+ PsdReader, readSignature, readUnicodeString, readUint32, readUint8, readFloat64,
14
+ readBytes, readAsciiString, readInt32, readFloat32, readInt32LE, readUnicodeStringWithLength
15
+ } from './psdReader';
16
+ import {
17
+ PsdWriter, writeSignature, writeBytes, writeUint32, writeFloat64, writeUint8,
18
+ writeUnicodeStringWithPadding, writeInt32, writeFloat32, writeUnicodeString
19
+ } from './psdWriter';
20
+
21
+ interface Dict { [key: string]: string; }
22
+ interface NameClassID { name: string; classID: string; }
23
+ interface ExtTypeDict { [key: string]: NameClassID; }
24
+
25
+ function revMap(map: Dict) {
26
+ const result: Dict = {};
27
+ Object.keys(map).forEach(key => result[map[key]] = key);
28
+ return result;
29
+ }
30
+
31
+ const unitsMap: Dict = {
32
+ '#Ang': 'Angle',
33
+ '#Rsl': 'Density',
34
+ '#Rlt': 'Distance',
35
+ '#Nne': 'None',
36
+ '#Prc': 'Percent',
37
+ '#Pxl': 'Pixels',
38
+ '#Mlm': 'Millimeters',
39
+ '#Pnt': 'Points',
40
+ 'RrPi': 'Picas',
41
+ 'RrIn': 'Inches',
42
+ 'RrCm': 'Centimeters',
43
+ };
44
+
45
+ const unitsMapRev = revMap(unitsMap);
46
+ let logErrors = false;
47
+
48
+ export function setLogErrors(value: boolean) {
49
+ logErrors = value;
50
+ }
51
+
52
+ function makeType(name: string, classID: string) {
53
+ return { name, classID };
54
+ }
55
+
56
+ const nullType = makeType('', 'null');
57
+
58
+ const fieldToExtType: ExtTypeDict = {
59
+ strokeStyleContent: makeType('', 'solidColorLayer'),
60
+ // printProofSetup: makeType('校样设置', 'proofSetup'), // TESTING
61
+ printProofSetup: makeType('Proof Setup', 'proofSetup'),
62
+ patternFill: makeType('', 'patternFill'),
63
+ Grad: makeType('Gradient', 'Grdn'),
64
+ ebbl: makeType('', 'ebbl'),
65
+ SoFi: makeType('', 'SoFi'),
66
+ GrFl: makeType('', 'GrFl'),
67
+ sdwC: makeType('', 'RGBC'),
68
+ hglC: makeType('', 'RGBC'),
69
+ 'Clr ': makeType('', 'RGBC'),
70
+ 'tintColor': makeType('', 'RGBC'),
71
+ Ofst: makeType('', 'Pnt '),
72
+ ChFX: makeType('', 'ChFX'),
73
+ MpgS: makeType('', 'ShpC'),
74
+ DrSh: makeType('', 'DrSh'),
75
+ IrSh: makeType('', 'IrSh'),
76
+ OrGl: makeType('', 'OrGl'),
77
+ IrGl: makeType('', 'IrGl'),
78
+ TrnS: makeType('', 'ShpC'),
79
+ Ptrn: makeType('', 'Ptrn'),
80
+ FrFX: makeType('', 'FrFX'),
81
+ phase: makeType('', 'Pnt '),
82
+ frameStep: nullType,
83
+ duration: nullType,
84
+ workInTime: nullType,
85
+ workOutTime: nullType,
86
+ audioClipGroupList: nullType,
87
+ bounds: makeType('', 'Rctn'),
88
+ customEnvelopeWarp: makeType('', 'customEnvelopeWarp'),
89
+ warp: makeType('', 'warp'),
90
+ 'Sz ': makeType('', 'Pnt '),
91
+ origin: makeType('', 'Pnt '),
92
+ autoExpandOffset: makeType('', 'Pnt '),
93
+ keyOriginShapeBBox: makeType('', 'unitRect'),
94
+ Vrsn: nullType,
95
+ psVersion: nullType,
96
+ docDefaultNewArtboardBackgroundColor: makeType('', 'RGBC'),
97
+ artboardRect: makeType('', 'classFloatRect'),
98
+ keyOriginRRectRadii: makeType('', 'radii'),
99
+ keyOriginBoxCorners: nullType,
100
+ rectangleCornerA: makeType('', 'Pnt '),
101
+ rectangleCornerB: makeType('', 'Pnt '),
102
+ rectangleCornerC: makeType('', 'Pnt '),
103
+ rectangleCornerD: makeType('', 'Pnt '),
104
+ compInfo: nullType,
105
+ Trnf: makeType('Transform', 'Trnf'),
106
+ quiltWarp: makeType('', 'quiltWarp'),
107
+ generatorSettings: nullType,
108
+ crema: nullType,
109
+ FrIn: nullType,
110
+ blendOptions: nullType,
111
+ FXRf: nullType,
112
+ Lefx: nullType,
113
+ time: nullType,
114
+ animKey: nullType,
115
+ timeScope: nullType,
116
+ inTime: nullType,
117
+ outTime: nullType,
118
+ sheetStyle: nullType,
119
+ translation: nullType,
120
+ Skew: nullType,
121
+ 'Lnk ': makeType('', 'ExternalFileLink'),
122
+ frameReader: makeType('', 'FrameReader'),
123
+ effectParams: makeType('', 'motionTrackEffectParams'),
124
+ };
125
+
126
+ const fieldToArrayExtType: ExtTypeDict = {
127
+ 'Crv ': makeType('', 'CrPt'),
128
+ Clrs: makeType('', 'Clrt'),
129
+ Trns: makeType('', 'TrnS'),
130
+ keyDescriptorList: nullType,
131
+ solidFillMulti: makeType('', 'SoFi'),
132
+ gradientFillMulti: makeType('', 'GrFl'),
133
+ dropShadowMulti: makeType('', 'DrSh'),
134
+ innerShadowMulti: makeType('', 'IrSh'),
135
+ frameFXMulti: makeType('', 'FrFX'),
136
+ FrIn: nullType,
137
+ FSts: nullType,
138
+ LaSt: nullType,
139
+ sheetTimelineOptions: nullType,
140
+ trackList: makeType('', 'animationTrack'),
141
+ globalTrackList: makeType('', 'animationTrack'),
142
+ keyList: nullType,
143
+ audioClipGroupList: nullType,
144
+ audioClipList: nullType,
145
+ countObjectList: makeType('', 'countObject'),
146
+ countGroupList: makeType('', 'countGroup'),
147
+ slices: makeType('', 'slice'),
148
+ };
149
+
150
+ const typeToField: { [key: string]: string[]; } = {
151
+ 'TEXT': [
152
+ 'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName',
153
+ 'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
154
+ 'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
155
+ 'altTag', 'url', 'cellText',
156
+ ],
157
+ 'tdta': ['EngineData', 'LUT3DFileData'],
158
+ 'long': [
159
+ 'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ',
160
+ 'Brgh', 'Cntr', 'means', 'vibrance', 'Strt', 'bwPresetKind', 'presetKind', 'comp', 'compID', 'originalCompID',
161
+ 'curvesPresetKind', 'mixerPresetKind', 'uOrder', 'vOrder', 'PgNm', 'totalPages', 'Crop',
162
+ 'numerator', 'denominator', 'frameCount', 'Annt', 'keyOriginType', 'unitValueQuadVersion',
163
+ 'keyOriginIndex', 'major', 'minor', 'fix', 'docDefaultNewArtboardBackgroundType', 'artboardBackgroundType',
164
+ 'numModifyingFX', 'deformNumRows', 'deformNumCols', 'FrID', 'FrDl', 'FsID', 'LCnt', 'AFrm', 'AFSt',
165
+ 'numBefore', 'numAfter', 'Spcn', 'minOpacity', 'maxOpacity', 'BlnM', 'sheetID', 'gblA', 'globalAltitude',
166
+ 'descVersion', 'frameReaderType', 'LyrI', 'zoomOrigin', 'fontSize', 'Rds ', 'sliceID',
167
+ 'topOutset', 'leftOutset', 'bottomOutset', 'rightOutset',
168
+ ],
169
+ 'enum': [
170
+ 'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS',
171
+ 'sdwM', 'hglM', 'bvlT', 'bvlS', 'bvlD', 'Md ', 'glwS', 'GrdF', 'GlwT',
172
+ 'strokeStyleLineCapType', 'strokeStyleLineJoinType', 'strokeStyleLineAlignment',
173
+ 'strokeStyleBlendMode', 'PntT', 'Styl', 'lookupType', 'LUTFormat', 'dataOrder',
174
+ 'tableOrder', 'enableCompCore', 'enableCompCoreGPU', 'compCoreSupport', 'compCoreGPUSupport', 'Engn',
175
+ 'enableCompCoreThreads', 'gs99', 'FrDs', 'trackID', 'animInterpStyle', 'horzAlign',
176
+ 'vertAlign', 'bgColorType',
177
+ ],
178
+ 'bool': [
179
+ 'PstS', 'printSixteenBit', 'masterFXSwitch', 'enab', 'uglg', 'antialiasGloss',
180
+ 'useShape', 'useTexture', 'uglg', 'antialiasGloss', 'useShape', 'Vsbl',
181
+ 'useTexture', 'Algn', 'Rvrs', 'Dthr', 'Invr', 'VctC', 'ShTr', 'layerConceals',
182
+ 'strokeEnabled', 'fillEnabled', 'strokeStyleScaleLock', 'strokeStyleStrokeAdjust',
183
+ 'hardProof', 'MpBl', 'paperWhite', 'useLegacy', 'Auto', 'Lab ', 'useTint', 'keyShapeInvalidated',
184
+ 'autoExpandEnabled', 'autoNestEnabled', 'autoPositionEnabled', 'shrinkwrapOnSaveEnabled',
185
+ 'present', 'showInDialog', 'overprint', 'sheetDisclosed', 'lightsDisclosed', 'meshesDisclosed',
186
+ 'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
187
+ 'cellTextIsHTML',
188
+ ],
189
+ 'doub': [
190
+ 'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght',
191
+ 'strokeStyleMiterLimit', 'strokeStyleResolution', 'layerTime', 'keyOriginResolution',
192
+ 'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
193
+ 'X ', 'Y ',
194
+ ],
195
+ 'UntF': [
196
+ 'Scl ', 'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
197
+ 'Ckmt', 'Nose', 'Inpr', 'ShdN', 'strokeStyleLineWidth', 'strokeStyleLineDashOffset',
198
+ 'strokeStyleOpacity', 'H ', 'Top ', 'Left', 'Btom', 'Rght', 'Rslt',
199
+ 'topRight', 'topLeft', 'bottomLeft', 'bottomRight',
200
+ ],
201
+ 'VlLs': [
202
+ 'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
203
+ 'LaSt', 'Trnf', 'nonAffineTransform', 'keyDescriptorList', 'guideIndeces', 'gradientFillMulti',
204
+ 'solidFillMulti', 'frameFXMulti', 'innerShadowMulti', 'dropShadowMulti', 'FrIn', 'FSts', 'FsFr',
205
+ 'sheetTimelineOptions', 'audioClipList', 'trackList', 'globalTrackList', 'keyList', 'audioClipList',
206
+ 'warpValues',
207
+ ],
208
+ 'ObAr': ['meshPoints', 'quiltSliceX', 'quiltSliceY'],
209
+ 'obj ': ['null'],
210
+ };
211
+
212
+ const channels = [
213
+ 'Rd ', 'Grn ', 'Bl ', 'Yllw', 'Ylw ', 'Cyn ', 'Mgnt', 'Blck', 'Gry ', 'Lmnc', 'A ', 'B ',
214
+ ];
215
+
216
+ const fieldToArrayType: Dict = {
217
+ 'Mnm ': 'long',
218
+ 'Mxm ': 'long',
219
+ 'FrLs': 'long',
220
+ 'strokeStyleLineDashSet': 'UntF',
221
+ 'Trnf': 'doub',
222
+ 'nonAffineTransform': 'doub',
223
+ 'keyDescriptorList': 'Objc',
224
+ 'gradientFillMulti': 'Objc',
225
+ 'solidFillMulti': 'Objc',
226
+ 'frameFXMulti': 'Objc',
227
+ 'innerShadowMulti': 'Objc',
228
+ 'dropShadowMulti': 'Objc',
229
+ 'LaSt': 'Objc',
230
+ 'FrIn': 'Objc',
231
+ 'FSts': 'Objc',
232
+ 'FsFr': 'long',
233
+ 'blendOptions': 'Objc',
234
+ 'sheetTimelineOptions': 'Objc',
235
+ 'keyList': 'Objc',
236
+ 'warpValues': 'doub',
237
+ };
238
+
239
+ const fieldToType: Dict = {};
240
+
241
+ for (const type of Object.keys(typeToField)) {
242
+ for (const field of typeToField[type]) {
243
+ fieldToType[field] = type;
244
+ }
245
+ }
246
+
247
+ for (const field of Object.keys(fieldToExtType)) {
248
+ if (!fieldToType[field]) fieldToType[field] = 'Objc';
249
+ }
250
+
251
+ for (const field of Object.keys(fieldToArrayExtType)) {
252
+ fieldToArrayType[field] = 'Objc';
253
+ }
254
+
255
+ function getTypeByKey(key: string, value: any, root: string, parent: any) {
256
+ if (key === 'null' && root === 'slices') {
257
+ return 'TEXT';
258
+ } else if (key === 'groupID') {
259
+ return root === 'slices' ? 'long' : 'TEXT';
260
+ } else if (key === 'Sz ') {
261
+ return ('Wdth' in value) ? 'Objc' : (('units' in value) ? 'UntF' : 'doub');
262
+ } else if (key === 'Type') {
263
+ return typeof value === 'string' ? 'enum' : 'long';
264
+ } else if (key === 'AntA') {
265
+ return typeof value === 'string' ? 'enum' : 'bool';
266
+ } else if ((key === 'Hrzn' || key === 'Vrtc') && parent.Type === 'keyType.Pstn') {
267
+ return 'long';
268
+ } else if (key === 'Hrzn' || key === 'Vrtc' || key === 'Top ' || key === 'Left' || key === 'Btom' || key === 'Rght') {
269
+ if (root === 'slices') return 'long';
270
+ return typeof value === 'number' ? 'doub' : 'UntF';
271
+ } else if (key === 'Vrsn') {
272
+ return typeof value === 'number' ? 'long' : 'Objc';
273
+ } else if (key === 'Rd ' || key === 'Grn ' || key === 'Bl ') {
274
+ return root === 'artd' ? 'long' : 'doub';
275
+ } else if (key === 'Trnf') {
276
+ return Array.isArray(value) ? 'VlLs' : 'Objc';
277
+ } else {
278
+ return fieldToType[key];
279
+ }
280
+ }
281
+
282
+ export function readAsciiStringOrClassId(reader: PsdReader) {
283
+ const length = readInt32(reader);
284
+ return readAsciiString(reader, length || 4);
285
+ }
286
+
287
+ function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
288
+ if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold') {
289
+ // write classId
290
+ writeInt32(writer, 0);
291
+ writeSignature(writer, value);
292
+ } else {
293
+ // write ascii string
294
+ writeInt32(writer, value.length);
295
+
296
+ for (let i = 0; i < value.length; i++) {
297
+ writeUint8(writer, value.charCodeAt(i));
298
+ }
299
+ }
300
+ }
301
+
302
+ export function readDescriptorStructure(reader: PsdReader) {
303
+ const object: any = {};
304
+ // object.__struct =
305
+ readClassStructure(reader);
306
+ const itemsCount = readUint32(reader);
307
+ // console.log('//', object.__struct);
308
+ for (let i = 0; i < itemsCount; i++) {
309
+ const key = readAsciiStringOrClassId(reader);
310
+ const type = readSignature(reader);
311
+ // console.log(`> '${key}' '${type}'`);
312
+ const data = readOSType(reader, type);
313
+ // if (!getTypeByKey(key, data)) console.log(`> '${key}' '${type}'`, data);
314
+ object[key] = data;
315
+ }
316
+
317
+ return object;
318
+ }
319
+
320
+ export function writeDescriptorStructure(writer: PsdWriter, name: string, classId: string, value: any, root: string) {
321
+ if (logErrors && !classId) console.log('Missing classId for: ', name, classId, value);
322
+
323
+ // write class structure
324
+ writeUnicodeStringWithPadding(writer, name);
325
+ writeAsciiStringOrClassId(writer, classId);
326
+
327
+ const keys = Object.keys(value);
328
+ writeUint32(writer, keys.length);
329
+
330
+ for (const key of keys) {
331
+ let type = getTypeByKey(key, value[key], root, value);
332
+ let extType = fieldToExtType[key];
333
+
334
+ if (key === 'origin') {
335
+ type = root === 'slices' ? 'enum' : 'Objc';
336
+ } else if (key === 'bounds' && root === 'slices') {
337
+ type = 'Objc';
338
+ extType = makeType('', 'Rct1');
339
+ } else if (key === 'Scl ' && 'Hrzn' in value[key]) {
340
+ type = 'Objc';
341
+ extType = nullType;
342
+ } else if (key === 'audioClipGroupList' && keys.length === 1) {
343
+ type = 'VlLs';
344
+ } else if ((key === 'Strt' || key === 'Brgh') && 'H ' in value) {
345
+ type = 'doub';
346
+ } else if (key === 'Strt') {
347
+ type = 'Objc';
348
+ extType = nullType;
349
+ } else if (channels.indexOf(key) !== -1) {
350
+ type = (classId === 'RGBC' && root !== 'artd') ? 'doub' : 'long';
351
+ } else if (key === 'profile') {
352
+ type = classId === 'printOutput' ? 'TEXT' : 'tdta';
353
+ } else if (key === 'strokeStyleContent') {
354
+ if (value[key]['Clr ']) {
355
+ extType = makeType('', 'solidColorLayer');
356
+ } else if (value[key].Grad) {
357
+ extType = makeType('', 'gradientLayer');
358
+ } else if (value[key].Ptrn) {
359
+ extType = makeType('', 'patternLayer');
360
+ } else {
361
+ logErrors && console.log('Invalid strokeStyleContent value', value[key]);
362
+ }
363
+ } else if (key === 'bounds' && root === 'quiltWarp') {
364
+ extType = makeType('', 'classFloatRect');
365
+ }
366
+
367
+ if (extType && extType.classID === 'RGBC') {
368
+ if ('H ' in value[key]) extType = { classID: 'HSBC', name: '' };
369
+ // TODO: other color spaces
370
+ }
371
+
372
+ writeAsciiStringOrClassId(writer, key);
373
+ writeSignature(writer, type || 'long');
374
+ writeOSType(writer, type || 'long', value[key], key, extType, root);
375
+ if (logErrors && !type) console.log(`Missing descriptor field type for: '${key}' in`, value);
376
+ }
377
+ }
378
+
379
+ function readOSType(reader: PsdReader, type: string) {
380
+ switch (type) {
381
+ case 'obj ': // Reference
382
+ return readReferenceStructure(reader);
383
+ case 'Objc': // Descriptor
384
+ case 'GlbO': // GlobalObject same as Descriptor
385
+ return readDescriptorStructure(reader);
386
+ case 'VlLs': { // List
387
+ const length = readInt32(reader);
388
+ const items: any[] = [];
389
+
390
+ for (let i = 0; i < length; i++) {
391
+ const type = readSignature(reader);
392
+ // console.log(' >', type);
393
+ items.push(readOSType(reader, type));
394
+ }
395
+
396
+ return items;
397
+ }
398
+ case 'doub': // Double
399
+ return readFloat64(reader);
400
+ case 'UntF': { // Unit double
401
+ const units = readSignature(reader);
402
+ const value = readFloat64(reader);
403
+ if (!unitsMap[units]) throw new Error(`Invalid units: ${units}`);
404
+ return { units: unitsMap[units], value };
405
+ }
406
+ case 'UnFl': { // Unit float
407
+ const units = readSignature(reader);
408
+ const value = readFloat32(reader);
409
+ if (!unitsMap[units]) throw new Error(`Invalid units: ${units}`);
410
+ return { units: unitsMap[units], value };
411
+ }
412
+ case 'TEXT': // String
413
+ return readUnicodeString(reader);
414
+ case 'enum': { // Enumerated
415
+ const type = readAsciiStringOrClassId(reader);
416
+ const value = readAsciiStringOrClassId(reader);
417
+ return `${type}.${value}`;
418
+ }
419
+ case 'long': // Integer
420
+ return readInt32(reader);
421
+ case 'comp': { // Large Integer
422
+ const low = readUint32(reader);
423
+ const high = readUint32(reader);
424
+ return { low, high };
425
+ }
426
+ case 'bool': // Boolean
427
+ return !!readUint8(reader);
428
+ case 'type': // Class
429
+ case 'GlbC': // Class
430
+ return readClassStructure(reader);
431
+ case 'alis': { // Alias
432
+ const length = readInt32(reader);
433
+ return readAsciiString(reader, length);
434
+ }
435
+ case 'tdta': { // Raw Data
436
+ const length = readInt32(reader);
437
+ return readBytes(reader, length);
438
+ }
439
+ case 'ObAr': { // Object array
440
+ readInt32(reader); // version: 16
441
+ readUnicodeString(reader); // name: ''
442
+ readAsciiStringOrClassId(reader); // 'rationalPoint'
443
+ const length = readInt32(reader);
444
+ const items: any[] = [];
445
+
446
+ for (let i = 0; i < length; i++) {
447
+ const type1 = readAsciiStringOrClassId(reader); // type Hrzn | Vrtc
448
+ readSignature(reader); // UnFl
449
+
450
+ readSignature(reader); // units ? '#Pxl'
451
+ const valuesCount = readInt32(reader);
452
+ const values: number[] = [];
453
+ for (let j = 0; j < valuesCount; j++) {
454
+ values.push(readFloat64(reader));
455
+ }
456
+
457
+ items.push({ type: type1, values });
458
+ }
459
+
460
+ return items;
461
+ }
462
+ case 'Pth ': { // File path
463
+ /*const length =*/ readInt32(reader);
464
+ const sig = readSignature(reader);
465
+ /*const pathSize =*/ readInt32LE(reader);
466
+ const charsCount = readInt32LE(reader);
467
+ const path = readUnicodeStringWithLength(reader, charsCount);
468
+ return { sig, path };
469
+ }
470
+ default:
471
+ throw new Error(`Invalid TySh descriptor OSType: ${type} at ${reader.offset.toString(16)}`);
472
+ }
473
+ }
474
+
475
+ const ObArTypes: { [key: string]: string | undefined; } = {
476
+ meshPoints: 'rationalPoint',
477
+ quiltSliceX: 'UntF',
478
+ quiltSliceY: 'UntF',
479
+ };
480
+
481
+ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, extType: NameClassID | undefined, root: string) {
482
+ switch (type) {
483
+ case 'obj ': // Reference
484
+ writeReferenceStructure(writer, key, value);
485
+ break;
486
+ case 'Objc': // Descriptor
487
+ case 'GlbO': // GlobalObject same as Descriptor
488
+ if (!extType) throw new Error(`Missing ext type for: '${key}' (${JSON.stringify(value)})`);
489
+ writeDescriptorStructure(writer, extType.name, extType.classID, value, root);
490
+ break;
491
+ case 'VlLs': // List
492
+ writeInt32(writer, value.length);
493
+
494
+ for (let i = 0; i < value.length; i++) {
495
+ const type = fieldToArrayType[key];
496
+ writeSignature(writer, type || 'long');
497
+ writeOSType(writer, type || 'long', value[i], '', fieldToArrayExtType[key], root);
498
+ if (logErrors && !type) console.log(`Missing descriptor array type for: '${key}' in`, value);
499
+ }
500
+ break;
501
+ case 'doub': // Double
502
+ writeFloat64(writer, value);
503
+ break;
504
+ case 'UntF': // Unit double
505
+ if (!unitsMapRev[value.units]) throw new Error(`Invalid units: ${value.units} in ${key}`);
506
+ writeSignature(writer, unitsMapRev[value.units]);
507
+ writeFloat64(writer, value.value);
508
+ break;
509
+ case 'UnFl': // Unit float
510
+ if (!unitsMapRev[value.units]) throw new Error(`Invalid units: ${value.units} in ${key}`);
511
+ writeSignature(writer, unitsMapRev[value.units]);
512
+ writeFloat32(writer, value.value);
513
+ break;
514
+ case 'TEXT': // String
515
+ writeUnicodeStringWithPadding(writer, value);
516
+ break;
517
+ case 'enum': { // Enumerated
518
+ const [_type, val] = value.split('.');
519
+ writeAsciiStringOrClassId(writer, _type);
520
+ writeAsciiStringOrClassId(writer, val);
521
+ break;
522
+ }
523
+ case 'long': // Integer
524
+ writeInt32(writer, value);
525
+ break;
526
+ // case 'comp': // Large Integer
527
+ // writeLargeInteger(reader);
528
+ case 'bool': // Boolean
529
+ writeUint8(writer, value ? 1 : 0);
530
+ break;
531
+ // case 'type': // Class
532
+ // case 'GlbC': // Class
533
+ // writeClassStructure(reader);
534
+ // case 'alis': // Alias
535
+ // writeAliasStructure(reader);
536
+ case 'tdta': // Raw Data
537
+ writeInt32(writer, value.byteLength);
538
+ writeBytes(writer, value);
539
+ break;
540
+ case 'ObAr': { // Object array
541
+ writeInt32(writer, 16); // version
542
+ writeUnicodeStringWithPadding(writer, ''); // name
543
+ const type = ObArTypes[key];
544
+ if (!type) throw new Error(`Not implemented ObArType for: ${key}`);
545
+ writeAsciiStringOrClassId(writer, type);
546
+ writeInt32(writer, value.length);
547
+
548
+ for (let i = 0; i < value.length; i++) {
549
+ writeAsciiStringOrClassId(writer, value[i].type); // Hrzn | Vrtc
550
+ writeSignature(writer, 'UnFl');
551
+ writeSignature(writer, '#Pxl');
552
+ writeInt32(writer, value[i].values.length);
553
+
554
+ for (let j = 0; j < value[i].values.length; j++) {
555
+ writeFloat64(writer, value[i].values[j]);
556
+ }
557
+ }
558
+ break;
559
+ }
560
+ // case 'Pth ': // File path
561
+ // writeFilePath(reader);
562
+ default:
563
+ throw new Error(`Not implemented descriptor OSType: ${type}`);
564
+ }
565
+ }
566
+
567
+ function readReferenceStructure(reader: PsdReader) {
568
+ const itemsCount = readInt32(reader);
569
+ const items: any[] = [];
570
+
571
+ for (let i = 0; i < itemsCount; i++) {
572
+ const type = readSignature(reader);
573
+
574
+ switch (type) {
575
+ case 'prop': { // Property
576
+ readClassStructure(reader);
577
+ const keyID = readAsciiStringOrClassId(reader);
578
+ items.push(keyID);
579
+ break;
580
+ }
581
+ case 'Clss': // Class
582
+ items.push(readClassStructure(reader));
583
+ break;
584
+ case 'Enmr': { // Enumerated Reference
585
+ readClassStructure(reader);
586
+ const typeID = readAsciiStringOrClassId(reader);
587
+ const value = readAsciiStringOrClassId(reader);
588
+ items.push(`${typeID}.${value}`);
589
+ break;
590
+ }
591
+ case 'rele': { // Offset
592
+ // const { name, classID } =
593
+ readClassStructure(reader);
594
+ items.push(readUint32(reader));
595
+ break;
596
+ }
597
+ case 'Idnt': // Identifier
598
+ items.push(readInt32(reader));
599
+ break;
600
+ case 'indx': // Index
601
+ items.push(readInt32(reader));
602
+ break;
603
+ case 'name': { // Name
604
+ readClassStructure(reader);
605
+ items.push(readUnicodeString(reader));
606
+ break;
607
+ }
608
+ default:
609
+ throw new Error(`Invalid descriptor reference type: ${type}`);
610
+ }
611
+ }
612
+
613
+ return items;
614
+ }
615
+
616
+ function writeReferenceStructure(writer: PsdWriter, _key: string, items: any[]) {
617
+ writeInt32(writer, items.length);
618
+
619
+ for (let i = 0; i < items.length; i++) {
620
+ const value = items[i];
621
+ let type = 'unknown';
622
+
623
+ if (typeof value === 'string') {
624
+ if (/^[a-z]+\.[a-z]+$/i.test(value)) {
625
+ type = 'Enmr';
626
+ } else {
627
+ type = 'name';
628
+ }
629
+ }
630
+
631
+ writeSignature(writer, type);
632
+
633
+ switch (type) {
634
+ // case 'prop': // Property
635
+ // case 'Clss': // Class
636
+ case 'Enmr': { // Enumerated Reference
637
+ const [typeID, enumValue] = value.split('.');
638
+ writeClassStructure(writer, '\0', typeID);
639
+ writeAsciiStringOrClassId(writer, typeID);
640
+ writeAsciiStringOrClassId(writer, enumValue);
641
+ break;
642
+ }
643
+ // case 'rele': // Offset
644
+ // case 'Idnt': // Identifier
645
+ // case 'indx': // Index
646
+ case 'name': { // Name
647
+ writeClassStructure(writer, '\0', 'Lyr ');
648
+ writeUnicodeString(writer, value + '\0');
649
+ break;
650
+ }
651
+ default:
652
+ throw new Error(`Invalid descriptor reference type: ${type}`);
653
+ }
654
+ }
655
+
656
+ return items;
657
+ }
658
+
659
+ function readClassStructure(reader: PsdReader) {
660
+ const name = readUnicodeString(reader);
661
+ const classID = readAsciiStringOrClassId(reader);
662
+ // console.log({ name, classID });
663
+ return { name, classID };
664
+ }
665
+
666
+ function writeClassStructure(writer: PsdWriter, name: string, classID: string) {
667
+ writeUnicodeString(writer, name);
668
+ writeAsciiStringOrClassId(writer, classID);
669
+ }
670
+
671
+ export function readVersionAndDescriptor(reader: PsdReader) {
672
+ const version = readUint32(reader);
673
+ if (version !== 16) throw new Error(`Invalid descriptor version: ${version}`);
674
+ const desc = readDescriptorStructure(reader);
675
+ // console.log(require('util').inspect(desc, false, 99, true));
676
+ return desc;
677
+ }
678
+
679
+ export function writeVersionAndDescriptor(writer: PsdWriter, name: string, classID: string, descriptor: any, root = '') {
680
+ writeUint32(writer, 16); // version
681
+ writeDescriptorStructure(writer, name, classID, descriptor, root);
682
+ }
683
+
684
+ export type DescriptorUnits = 'Angle' | 'Density' | 'Distance' | 'None' | 'Percent' | 'Pixels' |
685
+ 'Millimeters' | 'Points' | 'Picas' | 'Inches' | 'Centimeters';
686
+
687
+ export interface DescriptorUnitsValue {
688
+ units: DescriptorUnits;
689
+ value: number;
690
+ }
691
+
692
+ export type DescriptorColor = {
693
+ 'Rd ': number;
694
+ 'Grn ': number;
695
+ 'Bl ': number;
696
+ } | {
697
+ 'H ': DescriptorUnitsValue;
698
+ Strt: number;
699
+ Brgh: number;
700
+ } | {
701
+ 'Cyn ': number;
702
+ Mgnt: number;
703
+ 'Ylw ': number;
704
+ Blck: number;
705
+ } | {
706
+ 'Gry ': number;
707
+ } | {
708
+ Lmnc: number;
709
+ 'A ': number;
710
+ 'B ': number;
711
+ };
712
+
713
+ export interface DesciptorPattern {
714
+ 'Nm ': string;
715
+ Idnt: string;
716
+ }
717
+
718
+ export type DesciptorGradient = {
719
+ 'Nm ': string;
720
+ GrdF: 'GrdF.CstS';
721
+ Intr: number;
722
+ Clrs: {
723
+ 'Clr ': DescriptorColor;
724
+ Type: 'Clry.UsrS';
725
+ Lctn: number;
726
+ Mdpn: number;
727
+ }[];
728
+ Trns: {
729
+ Opct: DescriptorUnitsValue;
730
+ Lctn: number;
731
+ Mdpn: number;
732
+ }[];
733
+ } | {
734
+ GrdF: 'GrdF.ClNs';
735
+ Smth: number;
736
+ 'Nm ': string;
737
+ ClrS: string;
738
+ RndS: number;
739
+ VctC?: boolean;
740
+ ShTr?: boolean;
741
+ 'Mnm ': number[];
742
+ 'Mxm ': number[];
743
+ };
744
+
745
+ export interface DescriptorColorContent {
746
+ 'Clr ': DescriptorColor;
747
+ }
748
+
749
+ export interface DescriptorGradientContent {
750
+ Grad: DesciptorGradient;
751
+ Type: string;
752
+ Dthr?: boolean;
753
+ Rvrs?: boolean;
754
+ Angl?: DescriptorUnitsValue;
755
+ 'Scl '?: DescriptorUnitsValue;
756
+ Algn?: boolean;
757
+ Ofst?: { Hrzn: DescriptorUnitsValue; Vrtc: DescriptorUnitsValue; };
758
+ }
759
+
760
+ export interface DescriptorPatternContent {
761
+ Ptrn: DesciptorPattern;
762
+ Lnkd?: boolean;
763
+ phase?: { Hrzn: number; Vrtc: number; };
764
+ }
765
+
766
+ export type DescriptorVectorContent = DescriptorColorContent | DescriptorGradientContent | DescriptorPatternContent;
767
+
768
+ export interface StrokeDescriptor {
769
+ strokeStyleVersion: number;
770
+ strokeEnabled: boolean;
771
+ fillEnabled: boolean;
772
+ strokeStyleLineWidth: DescriptorUnitsValue;
773
+ strokeStyleLineDashOffset: DescriptorUnitsValue;
774
+ strokeStyleMiterLimit: number;
775
+ strokeStyleLineCapType: string;
776
+ strokeStyleLineJoinType: string;
777
+ strokeStyleLineAlignment: string;
778
+ strokeStyleScaleLock: boolean;
779
+ strokeStyleStrokeAdjust: boolean;
780
+ strokeStyleLineDashSet: DescriptorUnitsValue[];
781
+ strokeStyleBlendMode: string;
782
+ strokeStyleOpacity: DescriptorUnitsValue;
783
+ strokeStyleContent: DescriptorVectorContent;
784
+ strokeStyleResolution: number;
785
+ }
786
+
787
+ export interface TextDescriptor {
788
+ 'Txt ': string;
789
+ textGridding: string;
790
+ Ornt: string;
791
+ AntA: string;
792
+ TextIndex: number;
793
+ EngineData?: Uint8Array;
794
+ }
795
+
796
+ export interface WarpDescriptor {
797
+ warpStyle: string;
798
+ warpValue?: number;
799
+ warpValues?: number[]
800
+ warpPerspective: number;
801
+ warpPerspectiveOther: number;
802
+ warpRotate: string;
803
+ bounds?: {
804
+ 'Top ': DescriptorUnitsValue;
805
+ Left: DescriptorUnitsValue;
806
+ Btom: DescriptorUnitsValue;
807
+ Rght: DescriptorUnitsValue;
808
+ };
809
+ uOrder: number;
810
+ vOrder: number;
811
+ customEnvelopeWarp?: {
812
+ meshPoints: {
813
+ type: 'Hrzn' | 'Vrtc';
814
+ values: number[];
815
+ }[];
816
+ };
817
+ }
818
+
819
+ export interface QuiltWarpDescriptor extends WarpDescriptor {
820
+ deformNumRows: number;
821
+ deformNumCols: number;
822
+ customEnvelopeWarp: {
823
+ quiltSliceX: {
824
+ type: 'quiltSliceX';
825
+ values: number[];
826
+ }[];
827
+ quiltSliceY: {
828
+ type: 'quiltSliceY';
829
+ values: number[];
830
+ }[];
831
+ meshPoints: {
832
+ type: 'Hrzn' | 'Vrtc';
833
+ values: number[];
834
+ }[];
835
+ };
836
+ }
837
+
838
+ export interface FractionDescriptor {
839
+ numerator: number;
840
+ denominator: number;
841
+ }
842
+
843
+ export interface HrznVrtcDescriptor {
844
+ Hrzn: number;
845
+ Vrtc: number;
846
+ }
847
+
848
+ export interface FrameDescriptor {
849
+ FrLs: number[];
850
+ enab?: boolean;
851
+ IMsk?: { Ofst: HrznVrtcDescriptor };
852
+ VMsk?: { Ofst: HrznVrtcDescriptor };
853
+ Ofst?: HrznVrtcDescriptor;
854
+ FXRf?: HrznVrtcDescriptor;
855
+ Lefx?: Lfx2Descriptor;
856
+ blendOptions?: { Opct: DescriptorUnitsValue; };
857
+ }
858
+
859
+ export interface FrameListDescriptor {
860
+ LaID: number; // layer ID
861
+ LaSt: FrameDescriptor[];
862
+ }
863
+
864
+ export function horzVrtcToXY(hv: HrznVrtcDescriptor): { x: number; y: number; } {
865
+ return { x: hv.Hrzn, y: hv.Vrtc };
866
+ }
867
+
868
+ export function xyToHorzVrtc(xy: { x: number; y: number; }): HrznVrtcDescriptor {
869
+ return { Hrzn: xy.x, Vrtc: xy.y };
870
+ }
871
+
872
+ export type TimelineAnimKeyDescriptor = {
873
+ Type: 'keyType.Opct';
874
+ Opct: DescriptorUnitsValue;
875
+ } | {
876
+ Type: 'keyType.Trnf';
877
+ 'Scl ': HrznVrtcDescriptor;
878
+ Skew: HrznVrtcDescriptor;
879
+ rotation: number;
880
+ translation: HrznVrtcDescriptor;
881
+ } | {
882
+ Type: 'keyType.Pstn';
883
+ Hrzn: number;
884
+ Vrtc: number;
885
+ } | {
886
+ Type: 'keyType.sheetStyle';
887
+ sheetStyle: {
888
+ Vrsn: number;
889
+ Lefx?: Lfx2Descriptor;
890
+ blendOptions: {};
891
+ };
892
+ } | {
893
+ Type: 'keyType.globalLighting';
894
+ gblA: number;
895
+ globalAltitude: number;
896
+ };
897
+
898
+ export interface TimelineKeyDescriptor {
899
+ Vrsn: 1;
900
+ animInterpStyle: 'animInterpStyle.Lnr ' | 'animInterpStyle.hold';
901
+ time: FractionDescriptor;
902
+ animKey: TimelineAnimKeyDescriptor;
903
+ selected: boolean;
904
+ }
905
+
906
+ export interface TimelineTrackDescriptor {
907
+ trackID: 'stdTrackID.globalLightingTrack' | 'stdTrackID.opacityTrack' | 'stdTrackID.styleTrack' | 'stdTrackID.sheetTransformTrack' | 'stdTrackID.sheetPositionTrack';
908
+ Vrsn: 1;
909
+ enab: boolean;
910
+ Effc: boolean;
911
+ effectParams?: {
912
+ keyList: TimelineKeyDescriptor[];
913
+ fillCanvas: boolean;
914
+ zoomOrigin: number;
915
+ };
916
+ keyList: TimelineKeyDescriptor[];
917
+ }
918
+
919
+ export interface TimeScopeDescriptor {
920
+ Vrsn: 1;
921
+ Strt: FractionDescriptor;
922
+ duration: FractionDescriptor;
923
+ inTime: FractionDescriptor;
924
+ outTime: FractionDescriptor;
925
+ }
926
+
927
+ export interface TimelineDescriptor {
928
+ Vrsn: 1;
929
+ timeScope: TimeScopeDescriptor;
930
+ autoScope: boolean;
931
+ audioLevel: number;
932
+ LyrI: number;
933
+ trackList?: TimelineTrackDescriptor[];
934
+ }
935
+
936
+ export interface EffectDescriptor extends Partial<DescriptorGradientContent>, Partial<DescriptorPatternContent> {
937
+ enab?: boolean;
938
+ Styl: string;
939
+ PntT?: string;
940
+ 'Md '?: string;
941
+ Opct?: DescriptorUnitsValue;
942
+ 'Sz '?: DescriptorUnitsValue;
943
+ 'Clr '?: DescriptorColor;
944
+ present?: boolean;
945
+ showInDialog?: boolean;
946
+ overprint?: boolean;
947
+ }
948
+
949
+ export interface Lfx2Descriptor {
950
+ 'Scl '?: DescriptorUnitsValue;
951
+ masterFXSwitch?: boolean;
952
+ DrSh?: EffectDescriptor;
953
+ IrSh?: EffectDescriptor;
954
+ OrGl?: EffectDescriptor;
955
+ IrGl?: EffectDescriptor;
956
+ ebbl?: EffectDescriptor;
957
+ SoFi?: EffectDescriptor;
958
+ patternFill?: EffectDescriptor;
959
+ GrFl?: EffectDescriptor;
960
+ ChFX?: EffectDescriptor;
961
+ FrFX?: EffectDescriptor;
962
+ }
963
+
964
+ export interface LmfxDescriptor {
965
+ 'Scl '?: DescriptorUnitsValue;
966
+ masterFXSwitch?: boolean;
967
+ numModifyingFX?: number;
968
+ OrGl?: EffectDescriptor;
969
+ IrGl?: EffectDescriptor;
970
+ ebbl?: EffectDescriptor;
971
+ ChFX?: EffectDescriptor;
972
+ dropShadowMulti?: EffectDescriptor[];
973
+ innerShadowMulti?: EffectDescriptor[];
974
+ solidFillMulti?: EffectDescriptor[];
975
+ gradientFillMulti?: EffectDescriptor[];
976
+ frameFXMulti?: EffectDescriptor[];
977
+ patternFill?: EffectDescriptor; // ???
978
+ }
979
+
980
+ function parseFxObject(fx: EffectDescriptor) {
981
+ const stroke: LayerEffectStroke = {
982
+ enabled: !!fx.enab,
983
+ position: FStl.decode(fx.Styl),
984
+ fillType: FrFl.decode(fx.PntT!),
985
+ blendMode: BlnM.decode(fx['Md ']!),
986
+ opacity: parsePercent(fx.Opct),
987
+ size: parseUnits(fx['Sz ']!),
988
+ };
989
+
990
+ if (fx.present !== undefined) stroke.present = fx.present;
991
+ if (fx.showInDialog !== undefined) stroke.showInDialog = fx.showInDialog;
992
+ if (fx.overprint !== undefined) stroke.overprint = fx.overprint;
993
+ if (fx['Clr ']) stroke.color = parseColor(fx['Clr ']);
994
+ if (fx.Grad) stroke.gradient = parseGradientContent(fx as any);
995
+ if (fx.Ptrn) stroke.pattern = parsePatternContent(fx as any);
996
+
997
+ return stroke;
998
+ }
999
+
1000
+ function serializeFxObject(stroke: LayerEffectStroke) {
1001
+ let FrFX: EffectDescriptor = {} as any;
1002
+ FrFX.enab = !!stroke.enabled;
1003
+ if (stroke.present !== undefined) FrFX.present = !!stroke.present;
1004
+ if (stroke.showInDialog !== undefined) FrFX.showInDialog = !!stroke.showInDialog;
1005
+ FrFX.Styl = FStl.encode(stroke.position);
1006
+ FrFX.PntT = FrFl.encode(stroke.fillType);
1007
+ FrFX['Md '] = BlnM.encode(stroke.blendMode);
1008
+ FrFX.Opct = unitsPercent(stroke.opacity);
1009
+ FrFX['Sz '] = unitsValue(stroke.size, 'size');
1010
+ if (stroke.color) FrFX['Clr '] = serializeColor(stroke.color);
1011
+ if (stroke.gradient) FrFX = { ...FrFX, ...serializeGradientContent(stroke.gradient) };
1012
+ if (stroke.pattern) FrFX = { ...FrFX, ...serializePatternContent(stroke.pattern) };
1013
+ if (stroke.overprint !== undefined) FrFX.overprint = !!stroke.overprint;
1014
+ return FrFX;
1015
+ }
1016
+
1017
+ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boolean) {
1018
+ const info: Lfx2Descriptor & LmfxDescriptor = multi ? {
1019
+ 'Scl ': unitsPercent(e.scale ?? 1),
1020
+ masterFXSwitch: !e.disabled,
1021
+ } : {
1022
+ masterFXSwitch: !e.disabled,
1023
+ 'Scl ': unitsPercent(e.scale ?? 1),
1024
+ };
1025
+
1026
+ const arrayKeys: (keyof LayerEffectsInfo)[] = ['dropShadow', 'innerShadow', 'solidFill', 'gradientOverlay', 'stroke'];
1027
+ for (const key of arrayKeys) {
1028
+ if (e[key] && !Array.isArray(e[key])) throw new Error(`${key} should be an array`);
1029
+ }
1030
+
1031
+ if (e.dropShadow?.[0] && !multi) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow', log);
1032
+ if (e.dropShadow?.[0] && multi) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow', log));
1033
+ if (e.innerShadow?.[0] && !multi) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow', log);
1034
+ if (e.innerShadow?.[0] && multi) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow', log));
1035
+ if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow', log);
1036
+ if (e.solidFill?.[0] && multi) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill', log));
1037
+ if (e.gradientOverlay?.[0] && multi) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay', log));
1038
+ if (e.stroke?.[0] && multi) info.frameFXMulti = e.stroke.map(i => serializeFxObject(i));
1039
+ if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow', log);
1040
+ if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel', log);
1041
+ if (e.solidFill?.[0] && !multi) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill', log);
1042
+ if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay', log);
1043
+ if (e.gradientOverlay?.[0] && !multi) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay', log);
1044
+ if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin', log);
1045
+ if (e.stroke?.[0] && !multi) info.FrFX = serializeFxObject(e.stroke?.[0]);
1046
+
1047
+ if (multi) {
1048
+ info.numModifyingFX = 0;
1049
+
1050
+ for (const key of Object.keys(e)) {
1051
+ const value = (e as any)[key];
1052
+ if (Array.isArray(value)) {
1053
+ for (const effect of value) {
1054
+ if (effect.enabled) info.numModifyingFX++;
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+
1060
+ return info;
1061
+ }
1062
+
1063
+ export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, log: boolean) {
1064
+ const effects: LayerEffectsInfo = {};
1065
+ if (!info.masterFXSwitch) effects.disabled = true;
1066
+ if (info['Scl ']) effects.scale = parsePercent(info['Scl ']);
1067
+ if (info.DrSh) effects.dropShadow = [parseEffectObject(info.DrSh, log)];
1068
+ if (info.dropShadowMulti) effects.dropShadow = info.dropShadowMulti.map(i => parseEffectObject(i, log));
1069
+ if (info.IrSh) effects.innerShadow = [parseEffectObject(info.IrSh, log)];
1070
+ if (info.innerShadowMulti) effects.innerShadow = info.innerShadowMulti.map(i => parseEffectObject(i, log));
1071
+ if (info.OrGl) effects.outerGlow = parseEffectObject(info.OrGl, log);
1072
+ if (info.IrGl) effects.innerGlow = parseEffectObject(info.IrGl, log);
1073
+ if (info.ebbl) effects.bevel = parseEffectObject(info.ebbl, log);
1074
+ if (info.SoFi) effects.solidFill = [parseEffectObject(info.SoFi, log)];
1075
+ if (info.solidFillMulti) effects.solidFill = info.solidFillMulti.map(i => parseEffectObject(i, log));
1076
+ if (info.patternFill) effects.patternOverlay = parseEffectObject(info.patternFill, log);
1077
+ if (info.GrFl) effects.gradientOverlay = [parseEffectObject(info.GrFl, log)];
1078
+ if (info.gradientFillMulti) effects.gradientOverlay = info.gradientFillMulti.map(i => parseEffectObject(i, log));
1079
+ if (info.ChFX) effects.satin = parseEffectObject(info.ChFX, log);
1080
+ if (info.FrFX) effects.stroke = [parseFxObject(info.FrFX)];
1081
+ if (info.frameFXMulti) effects.stroke = info.frameFXMulti.map(i => parseFxObject(i));
1082
+ return effects;
1083
+ }
1084
+
1085
+ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: boolean) {
1086
+ const keys: TimelineKey[] = [];
1087
+
1088
+ for (let j = 0; j < keyList.length; j++) {
1089
+ const key = keyList[j];
1090
+ const { time, selected, animKey } = key;
1091
+ const interpolation = animInterpStyleEnum.decode(key.animInterpStyle);
1092
+
1093
+ switch (animKey.Type) {
1094
+ case 'keyType.Opct':
1095
+ keys.push({ interpolation, time, selected, type: 'opacity', value: parsePercent(animKey.Opct) });
1096
+ break;
1097
+ case 'keyType.Pstn':
1098
+ keys.push({ interpolation, time, selected, type: 'position', x: animKey.Hrzn, y: animKey.Vrtc });
1099
+ break;
1100
+ case 'keyType.Trnf':
1101
+ keys.push({
1102
+ interpolation, time, selected, type: 'transform',
1103
+ scale: horzVrtcToXY(animKey['Scl ']), skew: horzVrtcToXY(animKey.Skew), rotation: animKey.rotation, translation: horzVrtcToXY(animKey.translation)
1104
+ });
1105
+ break;
1106
+ case 'keyType.sheetStyle': {
1107
+ const key: TimelineKey = { interpolation, time, selected, type: 'style' };
1108
+ if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx, logMissingFeatures);
1109
+ keys.push(key);
1110
+ break;
1111
+ }
1112
+ case 'keyType.globalLighting': {
1113
+ keys.push({
1114
+ interpolation, time, selected, type: 'globalLighting',
1115
+ globalAngle: animKey.gblA, globalAltitude: animKey.globalAltitude
1116
+ });
1117
+ break;
1118
+ }
1119
+ default: throw new Error(`Unsupported keyType value`);
1120
+ }
1121
+ }
1122
+
1123
+ return keys;
1124
+ }
1125
+
1126
+ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
1127
+ const keyList: TimelineKeyDescriptor[] = [];
1128
+
1129
+ for (let j = 0; j < keys.length; j++) {
1130
+ const key = keys[j];
1131
+ const { time, selected = false, interpolation } = key;
1132
+ const animInterpStyle = animInterpStyleEnum.encode(interpolation) as 'animInterpStyle.Lnr ' | 'animInterpStyle.hold';
1133
+ let animKey: TimelineAnimKeyDescriptor;
1134
+
1135
+ switch (key.type) {
1136
+ case 'opacity':
1137
+ animKey = { Type: 'keyType.Opct', Opct: unitsPercent(key.value) };
1138
+ break;
1139
+ case 'position':
1140
+ animKey = { Type: 'keyType.Pstn', Hrzn: key.x, Vrtc: key.y };
1141
+ break;
1142
+ case 'transform':
1143
+ animKey = { Type: 'keyType.Trnf', 'Scl ': xyToHorzVrtc(key.scale), Skew: xyToHorzVrtc(key.skew), rotation: key.rotation, translation: xyToHorzVrtc(key.translation) };
1144
+ break;
1145
+ case 'style':
1146
+ animKey = { Type: 'keyType.sheetStyle', sheetStyle: { Vrsn: 1, blendOptions: {} } };
1147
+ if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style, false, false), blendOptions: {} };
1148
+ break;
1149
+ case 'globalLighting': {
1150
+ animKey = { Type: 'keyType.globalLighting', gblA: key.globalAngle, globalAltitude: key.globalAltitude };
1151
+ break;
1152
+ }
1153
+ default: throw new Error(`Unsupported keyType value`);
1154
+ }
1155
+
1156
+ keyList.push({ Vrsn: 1, animInterpStyle, time, animKey, selected });
1157
+ }
1158
+
1159
+ return keyList;
1160
+ }
1161
+
1162
+ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingFeatures: boolean) {
1163
+ const tracks: TimelineTrack[] = [];
1164
+
1165
+ for (let i = 0; i < trackList.length; i++) {
1166
+ const tr = trackList[i];
1167
+ const track: TimelineTrack = {
1168
+ type: stdTrackID.decode(tr.trackID),
1169
+ enabled: tr.enab,
1170
+ keys: parseKeyList(tr.keyList, logMissingFeatures),
1171
+ };
1172
+
1173
+ if (tr.effectParams) {
1174
+ track.effectParams = {
1175
+ fillCanvas: tr.effectParams.fillCanvas,
1176
+ zoomOrigin: tr.effectParams.zoomOrigin,
1177
+ keys: parseKeyList(tr.effectParams.keyList, logMissingFeatures),
1178
+ };
1179
+ }
1180
+
1181
+ tracks.push(track);
1182
+ }
1183
+
1184
+ return tracks;
1185
+ }
1186
+
1187
+ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescriptor[] {
1188
+ const trackList: TimelineTrackDescriptor[] = [];
1189
+
1190
+ for (let i = 0; i < tracks.length; i++) {
1191
+ const t = tracks[i];
1192
+ trackList.push({
1193
+ trackID: stdTrackID.encode(t.type) as any,
1194
+ Vrsn: 1,
1195
+ enab: !!t.enabled,
1196
+ Effc: !!t.effectParams,
1197
+ ...(t.effectParams ? {
1198
+ effectParams: {
1199
+ keyList: serializeKeyList(t.keys),
1200
+ fillCanvas: t.effectParams.fillCanvas,
1201
+ zoomOrigin: t.effectParams.zoomOrigin,
1202
+ }
1203
+ } : {}),
1204
+ keyList: serializeKeyList(t.keys),
1205
+ });
1206
+ }
1207
+
1208
+ return trackList;
1209
+ }
1210
+
1211
+ type AllEffects = LayerEffectShadow & LayerEffectsOuterGlow & LayerEffectStroke &
1212
+ LayerEffectInnerGlow & LayerEffectBevel & LayerEffectSolidFill &
1213
+ LayerEffectPatternOverlay & LayerEffectSatin & LayerEffectGradientOverlay;
1214
+
1215
+ function parseEffectObject(obj: any, reportErrors: boolean) {
1216
+ const result: AllEffects = {} as any;
1217
+
1218
+ for (const key of Object.keys(obj)) {
1219
+ const val = obj[key];
1220
+
1221
+ switch (key) {
1222
+ case 'enab': result.enabled = !!val; break;
1223
+ case 'uglg': result.useGlobalLight = !!val; break;
1224
+ case 'AntA': result.antialiased = !!val; break;
1225
+ case 'Algn': result.align = !!val; break;
1226
+ case 'Dthr': result.dither = !!val; break;
1227
+ case 'Invr': result.invert = !!val; break;
1228
+ case 'Rvrs': result.reverse = !!val; break;
1229
+ case 'Clr ': result.color = parseColor(val); break;
1230
+ case 'hglC': result.highlightColor = parseColor(val); break;
1231
+ case 'sdwC': result.shadowColor = parseColor(val); break;
1232
+ case 'Styl': result.position = FStl.decode(val); break;
1233
+ case 'Md ': result.blendMode = BlnM.decode(val); break;
1234
+ case 'hglM': result.highlightBlendMode = BlnM.decode(val); break;
1235
+ case 'sdwM': result.shadowBlendMode = BlnM.decode(val); break;
1236
+ case 'bvlS': result.style = BESl.decode(val); break;
1237
+ case 'bvlD': result.direction = BESs.decode(val); break;
1238
+ case 'bvlT': result.technique = bvlT.decode(val) as any; break;
1239
+ case 'GlwT': result.technique = BETE.decode(val) as any; break;
1240
+ case 'glwS': result.source = IGSr.decode(val); break;
1241
+ case 'Type': result.type = GrdT.decode(val); break;
1242
+ case 'gs99': result.interpolationMethod = gradientInterpolationMethodType.decode(val); break;
1243
+ case 'Opct': result.opacity = parsePercent(val); break;
1244
+ case 'hglO': result.highlightOpacity = parsePercent(val); break;
1245
+ case 'sdwO': result.shadowOpacity = parsePercent(val); break;
1246
+ case 'lagl': result.angle = parseAngle(val); break;
1247
+ case 'Angl': result.angle = parseAngle(val); break;
1248
+ case 'Lald': result.altitude = parseAngle(val); break;
1249
+ case 'Sftn': result.soften = parseUnits(val); break;
1250
+ case 'srgR': result.strength = parsePercent(val); break;
1251
+ case 'blur': result.size = parseUnits(val); break;
1252
+ case 'Nose': result.noise = parsePercent(val); break;
1253
+ case 'Inpr': result.range = parsePercent(val); break;
1254
+ case 'Ckmt': result.choke = parseUnits(val); break;
1255
+ case 'ShdN': result.jitter = parsePercent(val); break;
1256
+ case 'Dstn': result.distance = parseUnits(val); break;
1257
+ case 'Scl ': result.scale = parsePercent(val); break;
1258
+ case 'Ptrn': result.pattern = { name: val['Nm '], id: val.Idnt }; break;
1259
+ case 'phase': result.phase = { x: val.Hrzn, y: val.Vrtc }; break;
1260
+ case 'Ofst': result.offset = { x: parsePercent(val.Hrzn), y: parsePercent(val.Vrtc) }; break;
1261
+ case 'MpgS':
1262
+ case 'TrnS':
1263
+ result.contour = {
1264
+ name: val['Nm '],
1265
+ curve: (val['Crv '] as any[]).map(p => ({ x: p.Hrzn, y: p.Vrtc })),
1266
+ };
1267
+ break;
1268
+ case 'Grad': result.gradient = parseGradient(val); break;
1269
+ case 'useTexture':
1270
+ case 'useShape':
1271
+ case 'layerConceals':
1272
+ case 'present':
1273
+ case 'showInDialog':
1274
+ case 'antialiasGloss': result[key] = val; break;
1275
+ default:
1276
+ reportErrors && console.log(`Invalid effect key: '${key}', value:`, val);
1277
+ }
1278
+ }
1279
+
1280
+ return result;
1281
+ }
1282
+
1283
+ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean) {
1284
+ const result: any = {};
1285
+
1286
+ for (const objKey of Object.keys(obj)) {
1287
+ const key: keyof AllEffects = objKey as any;
1288
+ const val = obj[key];
1289
+
1290
+ switch (key) {
1291
+ case 'enabled': result.enab = !!val; break;
1292
+ case 'useGlobalLight': result.uglg = !!val; break;
1293
+ case 'antialiased': result.AntA = !!val; break;
1294
+ case 'align': result.Algn = !!val; break;
1295
+ case 'dither': result.Dthr = !!val; break;
1296
+ case 'invert': result.Invr = !!val; break;
1297
+ case 'reverse': result.Rvrs = !!val; break;
1298
+ case 'color': result['Clr '] = serializeColor(val); break;
1299
+ case 'highlightColor': result.hglC = serializeColor(val); break;
1300
+ case 'shadowColor': result.sdwC = serializeColor(val); break;
1301
+ case 'position': result.Styl = FStl.encode(val); break;
1302
+ case 'blendMode': result['Md '] = BlnM.encode(val); break;
1303
+ case 'highlightBlendMode': result.hglM = BlnM.encode(val); break;
1304
+ case 'shadowBlendMode': result.sdwM = BlnM.encode(val); break;
1305
+ case 'style': result.bvlS = BESl.encode(val); break;
1306
+ case 'direction': result.bvlD = BESs.encode(val); break;
1307
+ case 'technique':
1308
+ if (objName === 'bevel') {
1309
+ result.bvlT = bvlT.encode(val);
1310
+ } else {
1311
+ result.GlwT = BETE.encode(val);
1312
+ }
1313
+ break;
1314
+ case 'source': result.glwS = IGSr.encode(val); break;
1315
+ case 'type': result.Type = GrdT.encode(val); break;
1316
+ case 'interpolationMethod': result.gs99 = gradientInterpolationMethodType.encode(val); break;
1317
+ case 'opacity': result.Opct = unitsPercent(val); break;
1318
+ case 'highlightOpacity': result.hglO = unitsPercent(val); break;
1319
+ case 'shadowOpacity': result.sdwO = unitsPercent(val); break;
1320
+ case 'angle':
1321
+ if (objName === 'gradientOverlay') {
1322
+ result.Angl = unitsAngle(val);
1323
+ } else {
1324
+ result.lagl = unitsAngle(val);
1325
+ }
1326
+ break;
1327
+ case 'altitude': result.Lald = unitsAngle(val); break;
1328
+ case 'soften': result.Sftn = unitsValue(val, key); break;
1329
+ case 'strength': result.srgR = unitsPercent(val); break;
1330
+ case 'size': result.blur = unitsValue(val, key); break;
1331
+ case 'noise': result.Nose = unitsPercent(val); break;
1332
+ case 'range': result.Inpr = unitsPercent(val); break;
1333
+ case 'choke': result.Ckmt = unitsValue(val, key); break;
1334
+ case 'jitter': result.ShdN = unitsPercent(val); break;
1335
+ case 'distance': result.Dstn = unitsValue(val, key); break;
1336
+ case 'scale': result['Scl '] = unitsPercent(val); break;
1337
+ case 'pattern': result.Ptrn = { 'Nm ': val.name, Idnt: val.id }; break;
1338
+ case 'phase': result.phase = { Hrzn: val.x, Vrtc: val.y }; break;
1339
+ case 'offset': result.Ofst = { Hrzn: unitsPercent(val.x), Vrtc: unitsPercent(val.y) }; break;
1340
+ case 'contour': {
1341
+ result[objName === 'satin' ? 'MpgS' : 'TrnS'] = {
1342
+ 'Nm ': (val as EffectContour).name,
1343
+ 'Crv ': (val as EffectContour).curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
1344
+ };
1345
+ break;
1346
+ }
1347
+ case 'gradient': result.Grad = serializeGradient(val); break;
1348
+ case 'useTexture':
1349
+ case 'useShape':
1350
+ case 'layerConceals':
1351
+ case 'present':
1352
+ case 'showInDialog':
1353
+ case 'antialiasGloss':
1354
+ result[key] = val;
1355
+ break;
1356
+ default:
1357
+ reportErrors && console.log(`Invalid effect key: '${key}', value:`, val);
1358
+ }
1359
+ }
1360
+
1361
+ return result;
1362
+ }
1363
+
1364
+ function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoiseGradient {
1365
+ if (grad.GrdF === 'GrdF.CstS') {
1366
+ const samples: number = grad.Intr || 4096;
1367
+
1368
+ return {
1369
+ type: 'solid',
1370
+ name: grad['Nm '],
1371
+ smoothness: grad.Intr / 4096,
1372
+ colorStops: grad.Clrs.map(s => ({
1373
+ color: parseColor(s['Clr ']),
1374
+ location: s.Lctn / samples,
1375
+ midpoint: s.Mdpn / 100,
1376
+ })),
1377
+ opacityStops: grad.Trns.map(s => ({
1378
+ opacity: parsePercent(s.Opct),
1379
+ location: s.Lctn / samples,
1380
+ midpoint: s.Mdpn / 100,
1381
+ })),
1382
+ };
1383
+ } else {
1384
+ return {
1385
+ type: 'noise',
1386
+ name: grad['Nm '],
1387
+ roughness: grad.Smth / 4096,
1388
+ colorModel: ClrS.decode(grad.ClrS),
1389
+ randomSeed: grad.RndS,
1390
+ restrictColors: !!grad.VctC,
1391
+ addTransparency: !!grad.ShTr,
1392
+ min: grad['Mnm '].map(x => x / 100),
1393
+ max: grad['Mxm '].map(x => x / 100),
1394
+ };
1395
+ }
1396
+ }
1397
+
1398
+ function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): DesciptorGradient {
1399
+ if (grad.type === 'solid') {
1400
+ const samples = Math.round((grad.smoothness ?? 1) * 4096);
1401
+ return {
1402
+ 'Nm ': grad.name || '',
1403
+ GrdF: 'GrdF.CstS',
1404
+ Intr: samples,
1405
+ Clrs: grad.colorStops.map(s => ({
1406
+ 'Clr ': serializeColor(s.color),
1407
+ Type: 'Clry.UsrS',
1408
+ Lctn: Math.round(s.location * samples),
1409
+ Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
1410
+ })),
1411
+ Trns: grad.opacityStops.map(s => ({
1412
+ Opct: unitsPercent(s.opacity),
1413
+ Lctn: Math.round(s.location * samples),
1414
+ Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
1415
+ })),
1416
+ };
1417
+ } else {
1418
+ return {
1419
+ GrdF: 'GrdF.ClNs',
1420
+ 'Nm ': grad.name || '',
1421
+ ShTr: !!grad.addTransparency,
1422
+ VctC: !!grad.restrictColors,
1423
+ ClrS: ClrS.encode(grad.colorModel),
1424
+ RndS: grad.randomSeed || 0,
1425
+ Smth: Math.round((grad.roughness ?? 1) * 4096),
1426
+ 'Mnm ': (grad.min || [0, 0, 0, 0]).map(x => x * 100),
1427
+ 'Mxm ': (grad.max || [1, 1, 1, 1]).map(x => x * 100),
1428
+ };
1429
+ }
1430
+ }
1431
+
1432
+ function parseGradientContent(descriptor: DescriptorGradientContent) {
1433
+ const result = parseGradient(descriptor.Grad) as (EffectSolidGradient | EffectNoiseGradient) & ExtraGradientInfo;
1434
+ result.style = GrdT.decode(descriptor.Type);
1435
+ if (descriptor.Dthr !== undefined) result.dither = descriptor.Dthr;
1436
+ if (descriptor.Rvrs !== undefined) result.reverse = descriptor.Rvrs;
1437
+ if (descriptor.Angl !== undefined) result.angle = parseAngle(descriptor.Angl);
1438
+ if (descriptor['Scl '] !== undefined) result.scale = parsePercent(descriptor['Scl ']);
1439
+ if (descriptor.Algn !== undefined) result.align = descriptor.Algn;
1440
+ if (descriptor.Ofst !== undefined) {
1441
+ result.offset = {
1442
+ x: parsePercent(descriptor.Ofst.Hrzn),
1443
+ y: parsePercent(descriptor.Ofst.Vrtc)
1444
+ };
1445
+ }
1446
+ return result;
1447
+ }
1448
+
1449
+ function parsePatternContent(descriptor: DescriptorPatternContent) {
1450
+ const result: EffectPattern & ExtraPatternInfo = {
1451
+ name: descriptor.Ptrn['Nm '],
1452
+ id: descriptor.Ptrn.Idnt,
1453
+ };
1454
+ if (descriptor.Lnkd !== undefined) result.linked = descriptor.Lnkd;
1455
+ if (descriptor.phase !== undefined) result.phase = { x: descriptor.phase.Hrzn, y: descriptor.phase.Vrtc };
1456
+ return result;
1457
+ }
1458
+
1459
+
1460
+ export function parseVectorContent(descriptor: DescriptorVectorContent): VectorContent {
1461
+ if ('Grad' in descriptor) {
1462
+ return parseGradientContent(descriptor);
1463
+ } else if ('Ptrn' in descriptor) {
1464
+ return { type: 'pattern', ...parsePatternContent(descriptor) };
1465
+ } else if ('Clr ' in descriptor) {
1466
+ return { type: 'color', color: parseColor(descriptor['Clr ']) };
1467
+ } else {
1468
+ throw new Error('Invalid vector content');
1469
+ }
1470
+ }
1471
+
1472
+ function serializeGradientContent(content: (EffectSolidGradient | EffectNoiseGradient) & ExtraGradientInfo) {
1473
+ const result: DescriptorGradientContent = {} as any;
1474
+ if (content.dither !== undefined) result.Dthr = content.dither;
1475
+ if (content.reverse !== undefined) result.Rvrs = content.reverse;
1476
+ if (content.angle !== undefined) result.Angl = unitsAngle(content.angle);
1477
+ result.Type = GrdT.encode(content.style);
1478
+ if (content.align !== undefined) result.Algn = content.align;
1479
+ if (content.scale !== undefined) result['Scl '] = unitsPercent(content.scale);
1480
+ if (content.offset) {
1481
+ result.Ofst = {
1482
+ Hrzn: unitsPercent(content.offset.x),
1483
+ Vrtc: unitsPercent(content.offset.y),
1484
+ };
1485
+ }
1486
+ result.Grad = serializeGradient(content);
1487
+ return result;
1488
+ }
1489
+
1490
+ function serializePatternContent(content: EffectPattern & ExtraPatternInfo) {
1491
+ const result: DescriptorPatternContent = {
1492
+ Ptrn: {
1493
+ 'Nm ': content.name || '',
1494
+ Idnt: content.id || '',
1495
+ }
1496
+ };
1497
+ if (content.linked !== undefined) result.Lnkd = !!content.linked;
1498
+ if (content.phase !== undefined) result.phase = { Hrzn: content.phase.x, Vrtc: content.phase.y };
1499
+ return result;
1500
+ }
1501
+
1502
+ export function serializeVectorContent(content: VectorContent): { descriptor: DescriptorVectorContent; key: string; } {
1503
+ if (content.type === 'color') {
1504
+ return { key: 'SoCo', descriptor: { 'Clr ': serializeColor(content.color) } };
1505
+ } else if (content.type === 'pattern') {
1506
+ return { key: 'PtFl', descriptor: serializePatternContent(content) };
1507
+ } else {
1508
+ return { key: 'GdFl', descriptor: serializeGradientContent(content) };
1509
+ }
1510
+ }
1511
+
1512
+ export function parseColor(color: DescriptorColor): Color {
1513
+ if ('H ' in color) {
1514
+ return { h: parsePercentOrAngle(color['H ']), s: color.Strt, b: color.Brgh };
1515
+ } else if ('Rd ' in color) {
1516
+ return { r: color['Rd '], g: color['Grn '], b: color['Bl '] };
1517
+ } else if ('Cyn ' in color) {
1518
+ return { c: color['Cyn '], m: color.Mgnt, y: color['Ylw '], k: color.Blck };
1519
+ } else if ('Gry ' in color) {
1520
+ return { k: color['Gry '] };
1521
+ } else if ('Lmnc' in color) {
1522
+ return { l: color.Lmnc, a: color['A '], b: color['B '] };
1523
+ } else {
1524
+ throw new Error('Unsupported color descriptor');
1525
+ }
1526
+ }
1527
+
1528
+ export function serializeColor(color: Color | undefined): DescriptorColor {
1529
+ if (!color) {
1530
+ return { 'Rd ': 0, 'Grn ': 0, 'Bl ': 0 };
1531
+ } else if ('r' in color) {
1532
+ return { 'Rd ': color.r || 0, 'Grn ': color.g || 0, 'Bl ': color.b || 0 };
1533
+ } else if ('h' in color) {
1534
+ return { 'H ': unitsAngle(color.h * 360), Strt: color.s || 0, Brgh: color.b || 0 };
1535
+ } else if ('c' in color) {
1536
+ return { 'Cyn ': color.c || 0, Mgnt: color.m || 0, 'Ylw ': color.y || 0, Blck: color.k || 0 };
1537
+ } else if ('l' in color) {
1538
+ return { Lmnc: color.l || 0, 'A ': color.a || 0, 'B ': color.b || 0 };
1539
+ } else if ('k' in color) {
1540
+ return { 'Gry ': color.k };
1541
+ } else {
1542
+ throw new Error('Invalid color value');
1543
+ }
1544
+ }
1545
+
1546
+ export function parseAngle(x: DescriptorUnitsValue) {
1547
+ if (x === undefined) return 0;
1548
+ if (x.units !== 'Angle') throw new Error(`Invalid units: ${x.units}`);
1549
+ return x.value;
1550
+ }
1551
+
1552
+ export function parsePercent(x: DescriptorUnitsValue | undefined) {
1553
+ if (x === undefined) return 1;
1554
+ if (x.units !== 'Percent') throw new Error(`Invalid units: ${x.units}`);
1555
+ return x.value / 100;
1556
+ }
1557
+
1558
+ export function parsePercentOrAngle(x: DescriptorUnitsValue | undefined) {
1559
+ if (x === undefined) return 1;
1560
+ if (x.units === 'Percent') return x.value / 100;
1561
+ if (x.units === 'Angle') return x.value / 360;
1562
+ throw new Error(`Invalid units: ${x.units}`);
1563
+ }
1564
+
1565
+ export function parseUnits({ units, value }: DescriptorUnitsValue): UnitsValue {
1566
+ if (
1567
+ units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
1568
+ units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
1569
+ ) {
1570
+ throw new Error(`Invalid units: ${JSON.stringify({ units, value })}`);
1571
+ }
1572
+ return { value, units };
1573
+ }
1574
+
1575
+ export function parseUnitsOrNumber(value: DescriptorUnitsValue | number, units: Units = 'Pixels'): UnitsValue {
1576
+ if (typeof value === 'number') return { value, units };
1577
+ return parseUnits(value);
1578
+ }
1579
+
1580
+ export function parseUnitsToNumber({ units, value }: DescriptorUnitsValue, expectedUnits: string): number {
1581
+ if (units !== expectedUnits) throw new Error(`Invalid units: ${JSON.stringify({ units, value })}`);
1582
+ return value;
1583
+ }
1584
+
1585
+ export function unitsAngle(value: number | undefined): DescriptorUnitsValue {
1586
+ return { units: 'Angle', value: value || 0 };
1587
+ }
1588
+
1589
+ export function unitsPercent(value: number | undefined): DescriptorUnitsValue {
1590
+ return { units: 'Percent', value: Math.round((value || 0) * 100) };
1591
+ }
1592
+
1593
+ export function unitsValue(x: UnitsValue | undefined, key: string): DescriptorUnitsValue {
1594
+ if (x == null) return { units: 'Pixels', value: 0 };
1595
+
1596
+ if (typeof x !== 'object')
1597
+ throw new Error(`Invalid value: ${JSON.stringify(x)} (key: ${key}) (should have value and units)`);
1598
+
1599
+ const { units, value } = x;
1600
+
1601
+ if (typeof value !== 'number')
1602
+ throw new Error(`Invalid value in ${JSON.stringify(x)} (key: ${key})`);
1603
+
1604
+ if (
1605
+ units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
1606
+ units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
1607
+ ) {
1608
+ throw new Error(`Invalid units in ${JSON.stringify(x)} (key: ${key})`);
1609
+ }
1610
+
1611
+ return { units, value };
1612
+ }
1613
+
1614
+ export const textGridding = createEnum<TextGridding>('textGridding', 'none', {
1615
+ none: 'None',
1616
+ round: 'Rnd ',
1617
+ });
1618
+
1619
+ export const Ornt = createEnum<Orientation>('Ornt', 'horizontal', {
1620
+ horizontal: 'Hrzn',
1621
+ vertical: 'Vrtc',
1622
+ });
1623
+
1624
+ export const Annt = createEnum<AntiAlias>('Annt', 'sharp', {
1625
+ none: 'Anno',
1626
+ sharp: 'antiAliasSharp',
1627
+ crisp: 'AnCr',
1628
+ strong: 'AnSt',
1629
+ smooth: 'AnSm',
1630
+ platform: 'antiAliasPlatformGray',
1631
+ platformLCD: 'antiAliasPlatformLCD',
1632
+ });
1633
+
1634
+ export const warpStyle = createEnum<WarpStyle>('warpStyle', 'none', {
1635
+ none: 'warpNone',
1636
+ arc: 'warpArc',
1637
+ arcLower: 'warpArcLower',
1638
+ arcUpper: 'warpArcUpper',
1639
+ arch: 'warpArch',
1640
+ bulge: 'warpBulge',
1641
+ shellLower: 'warpShellLower',
1642
+ shellUpper: 'warpShellUpper',
1643
+ flag: 'warpFlag',
1644
+ wave: 'warpWave',
1645
+ fish: 'warpFish',
1646
+ rise: 'warpRise',
1647
+ fisheye: 'warpFisheye',
1648
+ inflate: 'warpInflate',
1649
+ squeeze: 'warpSqueeze',
1650
+ twist: 'warpTwist',
1651
+ cylinder: 'warpCylinder',
1652
+ custom: 'warpCustom',
1653
+ });
1654
+
1655
+ export const BlnM = createEnum<BlendMode>('BlnM', 'normal', {
1656
+ 'normal': 'Nrml',
1657
+ 'dissolve': 'Dslv',
1658
+ 'darken': 'Drkn',
1659
+ 'multiply': 'Mltp',
1660
+ 'color burn': 'CBrn',
1661
+ 'linear burn': 'linearBurn',
1662
+ 'darker color': 'darkerColor',
1663
+ 'lighten': 'Lghn',
1664
+ 'screen': 'Scrn',
1665
+ 'color dodge': 'CDdg',
1666
+ 'linear dodge': 'linearDodge',
1667
+ 'lighter color': 'lighterColor',
1668
+ 'overlay': 'Ovrl',
1669
+ 'soft light': 'SftL',
1670
+ 'hard light': 'HrdL',
1671
+ 'vivid light': 'vividLight',
1672
+ 'linear light': 'linearLight',
1673
+ 'pin light': 'pinLight',
1674
+ 'hard mix': 'hardMix',
1675
+ 'difference': 'Dfrn',
1676
+ 'exclusion': 'Xclu',
1677
+ 'subtract': 'blendSubtraction',
1678
+ 'divide': 'blendDivide',
1679
+ 'hue': 'H ',
1680
+ 'saturation': 'Strt',
1681
+ 'color': 'Clr ',
1682
+ 'luminosity': 'Lmns',
1683
+ // used in ABR
1684
+ 'linear height': 'linearHeight',
1685
+ 'height': 'Hght',
1686
+ 'subtraction': 'Sbtr', // 2nd version of subtract ?
1687
+ });
1688
+
1689
+ export const BESl = createEnum<BevelStyle>('BESl', 'inner bevel', {
1690
+ 'inner bevel': 'InrB',
1691
+ 'outer bevel': 'OtrB',
1692
+ 'emboss': 'Embs',
1693
+ 'pillow emboss': 'PlEb',
1694
+ 'stroke emboss': 'strokeEmboss',
1695
+ });
1696
+
1697
+ export const bvlT = createEnum<BevelTechnique>('bvlT', 'smooth', {
1698
+ 'smooth': 'SfBL',
1699
+ 'chisel hard': 'PrBL',
1700
+ 'chisel soft': 'Slmt',
1701
+ });
1702
+
1703
+ export const BESs = createEnum<BevelDirection>('BESs', 'up', {
1704
+ up: 'In ',
1705
+ down: 'Out ',
1706
+ });
1707
+
1708
+ export const BETE = createEnum<GlowTechnique>('BETE', 'softer', {
1709
+ softer: 'SfBL',
1710
+ precise: 'PrBL',
1711
+ });
1712
+
1713
+ export const IGSr = createEnum<GlowSource>('IGSr', 'edge', {
1714
+ edge: 'SrcE',
1715
+ center: 'SrcC',
1716
+ });
1717
+
1718
+ export const GrdT = createEnum<GradientStyle>('GrdT', 'linear', {
1719
+ linear: 'Lnr ',
1720
+ radial: 'Rdl ',
1721
+ angle: 'Angl',
1722
+ reflected: 'Rflc',
1723
+ diamond: 'Dmnd',
1724
+ });
1725
+
1726
+ export const animInterpStyleEnum = createEnum<TimelineKeyInterpolation>('animInterpStyle', 'linear', {
1727
+ linear: 'Lnr ',
1728
+ hold: 'hold',
1729
+ });
1730
+
1731
+ export const stdTrackID = createEnum<TimelineTrackType>('stdTrackID', 'opacity', {
1732
+ opacity: 'opacityTrack',
1733
+ style: 'styleTrack',
1734
+ sheetTransform: 'sheetTransformTrack',
1735
+ sheetPosition: 'sheetPositionTrack',
1736
+ globalLighting: 'globalLightingTrack',
1737
+ });
1738
+
1739
+ export const gradientInterpolationMethodType = createEnum<InterpolationMethod>('gradientInterpolationMethodType', 'perceptual', {
1740
+ perceptual: 'Perc',
1741
+ linear: 'Lnr',
1742
+ classic: 'Gcls',
1743
+ });
1744
+
1745
+ export const ClrS = createEnum<'rgb' | 'hsb' | 'lab'>('ClrS', 'rgb', {
1746
+ rgb: 'RGBC',
1747
+ hsb: 'HSBl',
1748
+ lab: 'LbCl',
1749
+ });
1750
+
1751
+ export const FStl = createEnum<'inside' | 'center' | 'outside'>('FStl', 'outside', {
1752
+ outside: 'OutF',
1753
+ center: 'CtrF',
1754
+ inside: 'InsF'
1755
+ });
1756
+
1757
+ export const FrFl = createEnum<'color' | 'gradient' | 'pattern'>('FrFl', 'color', {
1758
+ color: 'SClr',
1759
+ gradient: 'GrFl',
1760
+ pattern: 'Ptrn',
1761
+ });
1762
+
1763
+ export const ESliceType = createEnum<'image' | 'noImage'>('ESliceType', 'image', {
1764
+ image: 'Img ',
1765
+ noImage: 'noImage',
1766
+ });
1767
+
1768
+ export const ESliceHorzAlign = createEnum<'default'>('ESliceHorzAlign', 'default', {
1769
+ default: 'default',
1770
+ });
1771
+
1772
+ export const ESliceVertAlign = createEnum<'default'>('ESliceVertAlign', 'default', {
1773
+ default: 'default',
1774
+ });
1775
+
1776
+ export const ESliceOrigin = createEnum<'userGenerated' | 'autoGenerated' | 'layer'>('ESliceOrigin', 'userGenerated', {
1777
+ userGenerated: 'userGenerated',
1778
+ autoGenerated: 'autoGenerated',
1779
+ layer: 'layer',
1780
+ });
1781
+
1782
+ export const ESliceBGColorType = createEnum<'none' | 'matte' | 'color'>('ESliceBGColorType', 'none', {
1783
+ none: 'None',
1784
+ matte: 'matte',
1785
+ color: 'Clr ',
1786
+ });
1787
+
1788
+ export const strokeStyleLineCapType = createEnum<LineCapType>('strokeStyleLineCapType', 'butt', {
1789
+ butt: 'strokeStyleButtCap',
1790
+ round: 'strokeStyleRoundCap',
1791
+ square: 'strokeStyleSquareCap',
1792
+ });
1793
+
1794
+ export const strokeStyleLineJoinType = createEnum<LineJoinType>('strokeStyleLineJoinType', 'miter', {
1795
+ miter: 'strokeStyleMiterJoin',
1796
+ round: 'strokeStyleRoundJoin',
1797
+ bevel: 'strokeStyleBevelJoin',
1798
+ });
1799
+
1800
+ export const strokeStyleLineAlignment = createEnum<LineAlignment>('strokeStyleLineAlignment', 'inside', {
1801
+ inside: 'strokeStyleAlignInside',
1802
+ center: 'strokeStyleAlignCenter',
1803
+ outside: 'strokeStyleAlignOutside',
1804
+ });