ag-psd 31.0.3 → 32.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
- package/CHANGELOG.md +29 -0
- package/dist/abr.js +12 -5
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +2 -0
- package/dist/additionalInfo.js +123 -68
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +1365 -632
- package/dist/descriptor.d.ts +15 -2
- package/dist/descriptor.js +329 -100
- package/dist/descriptor.js.map +1 -1
- package/dist/effectsHelpers.js +12 -12
- package/dist/effectsHelpers.js.map +1 -1
- package/dist/engineData.d.ts +3 -1
- package/dist/engineData.js +34 -13
- package/dist/engineData.js.map +1 -1
- package/dist/engineData2.js +50 -21
- package/dist/engineData2.js.map +1 -1
- package/dist/helpers.d.ts +5 -2
- package/dist/helpers.js +18 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +45 -21
- package/dist/imageResources.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +27 -7
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +6 -1
- package/dist/psdReader.d.ts +2 -2
- package/dist/psdReader.js +457 -270
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +98 -76
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.js +126 -23
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +36 -15
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.js +12 -5
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +2 -0
- package/dist-es/additionalInfo.js +123 -68
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.d.ts +15 -2
- package/dist-es/descriptor.js +329 -100
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/effectsHelpers.js +12 -12
- package/dist-es/effectsHelpers.js.map +1 -1
- package/dist-es/engineData.d.ts +3 -1
- package/dist-es/engineData.js +34 -13
- package/dist-es/engineData.js.map +1 -1
- package/dist-es/engineData2.js +50 -21
- package/dist-es/engineData2.js.map +1 -1
- package/dist-es/helpers.d.ts +5 -2
- package/dist-es/helpers.js +18 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +45 -21
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +27 -7
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +6 -1
- package/dist-es/psdReader.d.ts +2 -2
- package/dist-es/psdReader.js +457 -270
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +98 -76
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.js +126 -23
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +36 -15
- package/dist-es/utf8.js.map +1 -1
- package/package.json +1 -1
- package/src/abr.ts +14 -6
- package/src/additionalInfo.ts +127 -66
- package/src/descriptor.ts +347 -107
- package/src/effectsHelpers.ts +9 -9
- package/src/engineData.ts +36 -13
- package/src/engineData2.ts +468 -432
- package/src/helpers.ts +19 -4
- package/src/imageResources.ts +49 -22
- package/src/index.ts +1 -0
- package/src/jpeg.ts +24 -9
- package/src/psd.ts +3 -1
- package/src/psdReader.ts +477 -260
- package/src/psdWriter.ts +100 -83
- package/src/text.ts +122 -12
- package/src/utf8.ts +35 -17
package/src/descriptor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEnum } from './helpers';
|
|
1
|
+
import { createEnum, normalizeLabAB, denormalizeLabAB } from './helpers';
|
|
2
2
|
import {
|
|
3
3
|
AntiAlias, BevelDirection, BevelStyle, BevelTechnique, BlendMode, Color, EffectContour,
|
|
4
4
|
EffectNoiseGradient, EffectPattern, EffectSolidGradient, ExtraGradientInfo, ExtraPatternInfo,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from './psdReader';
|
|
16
16
|
import {
|
|
17
17
|
PsdWriter, writeSignature, writeBytes, writeUint32, writeFloat64, writeUint8,
|
|
18
|
-
writeUnicodeStringWithPadding, writeInt32, writeFloat32,
|
|
18
|
+
writeUnicodeStringWithPadding, writeInt32, writeFloat32, writeInt32LE,
|
|
19
19
|
writeUnicodeStringWithoutLengthLE
|
|
20
20
|
} from './psdWriter';
|
|
21
21
|
|
|
@@ -140,6 +140,8 @@ const fieldToExtType: ExtTypeDict = {
|
|
|
140
140
|
origFXRefPoint: nullType,
|
|
141
141
|
FXRefPoint: nullType,
|
|
142
142
|
ClMg: makeType('', 'ClMg'),
|
|
143
|
+
interpretation: makeType('', 'footageInterpretation'),
|
|
144
|
+
bgColor: makeType('', 'NativeQuad'),
|
|
143
145
|
};
|
|
144
146
|
|
|
145
147
|
const fieldToArrayExtType: ExtTypeDict = {
|
|
@@ -174,6 +176,9 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
174
176
|
layerSettings: nullType,
|
|
175
177
|
list: nullType,
|
|
176
178
|
Adjs: makeType('', 'CrvA'),
|
|
179
|
+
vertices: makeType('', 'Pnt '),
|
|
180
|
+
warpedVertices: makeType('', 'Pnt '),
|
|
181
|
+
quads: makeType('', 'quadIndices'),
|
|
177
182
|
};
|
|
178
183
|
|
|
179
184
|
const typeToField: { [key: string]: string[]; } = {
|
|
@@ -200,6 +205,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
200
205
|
'meshExpansion', 'meshRigidity', 'VrsM', 'VrsN', 'NmbG', 'WLMn', 'WLMx', 'AmMn', 'AmMx', 'SclH', 'SclV',
|
|
201
206
|
'Lvl ', 'TlNm', 'TlOf', 'FlRs', 'Thsh', 'ShrS', 'ShrE', 'FlRs', 'Vrnc', 'Strg', 'ExtS', 'ExtD',
|
|
202
207
|
'HrzS', 'VrtS', 'NmbR', 'EdgF', 'Ang1', 'Ang2', 'Ang3', 'Ang4', 'lastAppliedComp', 'capturedInfo',
|
|
208
|
+
'pixelSourceType',
|
|
203
209
|
],
|
|
204
210
|
'enum': [
|
|
205
211
|
'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS', 'BlrQ',
|
|
@@ -210,6 +216,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
210
216
|
'enableCompCoreThreads', 'gs99', 'FrDs', 'trackID', 'animInterpStyle', 'horzAlign',
|
|
211
217
|
'vertAlign', 'bgColorType', 'shapeOperation', 'UndA', 'Wvtp', 'Drct', 'WndM', 'Edg ', 'FlCl', 'IntE',
|
|
212
218
|
'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod',
|
|
219
|
+
'interpretAlpha',
|
|
213
220
|
],
|
|
214
221
|
'bool': [
|
|
215
222
|
'PstS', 'printSixteenBit', 'masterFXSwitch', 'enab', 'uglg', 'antialiasGloss',
|
|
@@ -222,14 +229,15 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
222
229
|
'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
|
|
223
230
|
'cellTextIsHTML', 'Smoo', 'Clsp', 'validAtPosition', 'rigidType', 'hasoptions', 'filterMaskEnable',
|
|
224
231
|
'filterMaskLinked', 'filterMaskExtendWithWhite', 'removeJPEGArtifact', 'Mnch', 'ExtF', 'ExtM',
|
|
225
|
-
'moreAccurate', 'GpuY', 'LIWy', 'Cnty',
|
|
232
|
+
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
|
|
226
233
|
],
|
|
227
234
|
'doub': [
|
|
228
235
|
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght',
|
|
229
236
|
'strokeStyleMiterLimit', 'strokeStyleResolution', 'layerTime', 'keyOriginResolution',
|
|
230
237
|
'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
|
|
231
238
|
'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
|
|
232
|
-
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3'
|
|
239
|
+
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3',
|
|
240
|
+
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity',
|
|
233
241
|
],
|
|
234
242
|
'UntF': [
|
|
235
243
|
'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
|
|
@@ -245,16 +253,60 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
245
253
|
'warpValues', 'selectedPin', 'Pts ', 'SbpL', 'pathComponents', 'pinOffsets', 'posFinalPins',
|
|
246
254
|
'pinVertexIndices', 'PinP', 'PnRt', 'PnOv', 'PnDp', 'filterFXList', 'puppetShapeList', 'ShrP',
|
|
247
255
|
'channelDenoise', 'Mtrx', 'layerSettings', 'list', 'compList', 'Adjs',
|
|
256
|
+
'vertices', 'warpedVertices', 'quads', 'indices', 'Mpng',
|
|
248
257
|
],
|
|
249
258
|
'ObAr': ['meshPoints', 'quiltSliceX', 'quiltSliceY'],
|
|
250
259
|
'obj ': ['null', 'Chnl'],
|
|
251
260
|
'Pth ': ['DspF'],
|
|
261
|
+
'alis': ['alis'],
|
|
252
262
|
};
|
|
253
263
|
|
|
254
264
|
const channels = [
|
|
255
265
|
'Rd ', 'Grn ', 'Bl ', 'Yllw', 'Ylw ', 'Cyn ', 'Mgnt', 'Blck', 'Gry ', 'Lmnc', 'A ', 'B ',
|
|
256
266
|
];
|
|
257
267
|
|
|
268
|
+
// color classes whose channel values are written as doubles ('GRYC' and 'LABC' are kept for backwards compatibility)
|
|
269
|
+
const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
|
|
270
|
+
|
|
271
|
+
// Hidden (non-enumerable) markers used to remember how values were stored in the file, so they can be
|
|
272
|
+
// written back the same way. They don't change the shape of the objects returned by the reader.
|
|
273
|
+
const osTypeKey = typeof Symbol !== 'undefined' ? Symbol('agPsdOSType') : '__agPsdOSType';
|
|
274
|
+
const obArInfoKey = typeof Symbol !== 'undefined' ? Symbol('agPsdObAr') : '__agPsdObAr';
|
|
275
|
+
const referenceInfoKey = typeof Symbol !== 'undefined' ? Symbol('agPsdReference') : '__agPsdReference';
|
|
276
|
+
|
|
277
|
+
function setHidden<T extends object>(target: T, key: symbol | string, value: any): T {
|
|
278
|
+
Object.defineProperty(target, key, { value, enumerable: false, configurable: true, writable: true });
|
|
279
|
+
return target;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getHidden(target: any, key: symbol | string): any {
|
|
283
|
+
return (target && typeof target === 'object') ? target[key] : undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const osTypeFamilies: { [key: string]: string; } = {
|
|
287
|
+
UntF: 'UntF', UnFl: 'UntF',
|
|
288
|
+
Objc: 'Objc', GlbO: 'Objc',
|
|
289
|
+
type: 'type', GlbC: 'type',
|
|
290
|
+
comp: 'comp',
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// returns the OS type the value was originally read with, if it's compatible with the type we'd write it as
|
|
294
|
+
// fallback for unknown keys, based on the type of the value
|
|
295
|
+
function guessOSType(value: any): string | undefined {
|
|
296
|
+
if (typeof value === 'boolean') return 'bool';
|
|
297
|
+
if (typeof value === 'number' && !Number.isInteger(value)) return 'doub';
|
|
298
|
+
if (Array.isArray(value)) return 'VlLs';
|
|
299
|
+
if (value && typeof value === 'object' && typeof value._classID === 'string') return 'Objc';
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getRecordedOSType(value: any, type: string | undefined): string | undefined {
|
|
304
|
+
const recorded: string | undefined = getHidden(value, osTypeKey);
|
|
305
|
+
if (!recorded) return type;
|
|
306
|
+
if (!type || recorded === 'comp' || recorded === 'type' || recorded === 'GlbC') return recorded;
|
|
307
|
+
return osTypeFamilies[recorded] === osTypeFamilies[type] ? recorded : type;
|
|
308
|
+
}
|
|
309
|
+
|
|
258
310
|
const fieldToArrayType: Dict = {
|
|
259
311
|
'Mnm ': 'long',
|
|
260
312
|
'Mxm ': 'long',
|
|
@@ -294,6 +346,8 @@ const fieldToArrayType: Dict = {
|
|
|
294
346
|
Mtrx: 'long',
|
|
295
347
|
compList: 'long',
|
|
296
348
|
Chnl: 'enum',
|
|
349
|
+
indices: 'long',
|
|
350
|
+
Mpng: 'long',
|
|
297
351
|
};
|
|
298
352
|
|
|
299
353
|
const fieldToType: Dict = {};
|
|
@@ -312,6 +366,8 @@ for (const field of Object.keys(fieldToArrayExtType)) {
|
|
|
312
366
|
fieldToArrayType[field] = 'Objc';
|
|
313
367
|
}
|
|
314
368
|
|
|
369
|
+
const pixelSourceOriginRoot = 'PixelSource.origin';
|
|
370
|
+
|
|
315
371
|
function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
316
372
|
if (key === 'presetKind') {
|
|
317
373
|
return typeof value === 'string' ? 'enum' : 'long';
|
|
@@ -325,7 +381,7 @@ function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
|
325
381
|
return typeof value === 'string' ? 'enum' : 'long';
|
|
326
382
|
} else if (key === 'AntA') {
|
|
327
383
|
return typeof value === 'string' ? 'enum' : 'bool';
|
|
328
|
-
} else if ((key === 'Hrzn' || key === 'Vrtc') && (parent.Type === 'keyType.Pstn' || parent._classID === 'Ofst')) {
|
|
384
|
+
} else if ((key === 'Hrzn' || key === 'Vrtc') && (parent.Type === 'keyType.Pstn' || parent._classID === 'Ofst' || root === pixelSourceOriginRoot)) {
|
|
329
385
|
return 'long';
|
|
330
386
|
} else if (key === 'Hrzn' || key === 'Vrtc' || key === 'Top ' || key === 'Left' || key === 'Btom' || key === 'Rght') {
|
|
331
387
|
if (root === 'slices') return 'long';
|
|
@@ -336,6 +392,12 @@ function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
|
336
392
|
return root === 'artd' ? 'long' : 'doub';
|
|
337
393
|
} else if (key === 'Trnf') {
|
|
338
394
|
return Array.isArray(value) ? 'VlLs' : 'Objc';
|
|
395
|
+
} else if (key === 'alpha' && root === 'slices') {
|
|
396
|
+
return 'long';
|
|
397
|
+
} else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'HsbP') {
|
|
398
|
+
return 'enum';
|
|
399
|
+
} else if (key === 'LghD' && parent._classID === 'oilPaint') {
|
|
400
|
+
return 'long';
|
|
339
401
|
} else {
|
|
340
402
|
return fieldToType[key];
|
|
341
403
|
}
|
|
@@ -346,7 +408,7 @@ export function readAsciiStringOrClassId(reader: PsdReader) {
|
|
|
346
408
|
return readAsciiString(reader, length || 4);
|
|
347
409
|
}
|
|
348
410
|
|
|
349
|
-
function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
|
|
411
|
+
export function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
|
|
350
412
|
if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list') {
|
|
351
413
|
// write classId
|
|
352
414
|
writeInt32(writer, 0);
|
|
@@ -386,23 +448,25 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
386
448
|
writeUnicodeStringWithPadding(writer, name);
|
|
387
449
|
writeAsciiStringOrClassId(writer, classId);
|
|
388
450
|
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
if ('_name' in value) keyCount--;
|
|
392
|
-
if ('_classID' in value) keyCount--;
|
|
451
|
+
const allKeys = Object.keys(value);
|
|
452
|
+
const keys = allKeys.filter(key => key !== '_name' && key !== '_classID' && value[key] !== undefined);
|
|
393
453
|
|
|
394
|
-
writeUint32(writer,
|
|
454
|
+
writeUint32(writer, keys.length);
|
|
395
455
|
|
|
396
456
|
for (const key of keys) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
let type = getTypeByKey(key, value[key], root, value);
|
|
457
|
+
let type: string | undefined = getTypeByKey(key, value[key], root, value);
|
|
458
|
+
let childRoot = root;
|
|
400
459
|
let extType = fieldToExtType[key];
|
|
401
460
|
|
|
402
461
|
if (key === 'bounds' && root === 'text') {
|
|
403
462
|
extType = makeType('', 'bounds');
|
|
404
463
|
} else if (key === 'origin') {
|
|
405
464
|
type = root === 'slices' ? 'enum' : 'Objc';
|
|
465
|
+
|
|
466
|
+
if (classId === 'PixelSource') {
|
|
467
|
+
extType = nullType; // stored as 'null' class with integer Hrzn/Vrtc
|
|
468
|
+
childRoot = pixelSourceOriginRoot;
|
|
469
|
+
}
|
|
406
470
|
} else if ((key === 'Cyn ' || key === 'Mgnt' || key === 'Ylw ' || key === 'Blck') && value._classID === 'CMYC') {
|
|
407
471
|
type = 'doub';
|
|
408
472
|
} else if (/^PN[a-z][a-z]$/.test(key)) {
|
|
@@ -449,9 +513,12 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
449
513
|
type = 'Objc';
|
|
450
514
|
extType = nullType;
|
|
451
515
|
} else if (channels.indexOf(key) !== -1) {
|
|
452
|
-
type = (classId === 'RGBC' && root !== 'artd') ? 'doub' : 'long';
|
|
516
|
+
type = ((classId === 'RGBC' && root !== 'artd') || doubleColorClasses.indexOf(classId) !== -1) ? 'doub' : 'long';
|
|
453
517
|
} else if (key === 'profile') {
|
|
454
|
-
type = classId === 'printOutput' ? 'TEXT' : 'tdta';
|
|
518
|
+
type = (classId === 'printOutput' || classId === 'proofSetup') ? 'TEXT' : 'tdta';
|
|
519
|
+
} else if (key === 'alis' && typeof value[key] === 'string') {
|
|
520
|
+
// 'alis' is read as a binary string (one char per byte), strings with wider chars can only be stored as TEXT
|
|
521
|
+
type = /[^\x00-\xff]/.test(value[key]) ? 'TEXT' : 'alis';
|
|
455
522
|
} else if (key === 'strokeStyleContent') {
|
|
456
523
|
if (value[key]['Clr ']) {
|
|
457
524
|
extType = makeType('', 'solidColorLayer');
|
|
@@ -471,9 +538,12 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
471
538
|
// TODO: other color spaces
|
|
472
539
|
}
|
|
473
540
|
|
|
541
|
+
type = getRecordedOSType(value[key], type);
|
|
542
|
+
if (!type) type = guessOSType(value[key]);
|
|
543
|
+
|
|
474
544
|
writeAsciiStringOrClassId(writer, key);
|
|
475
545
|
writeSignature(writer, type || 'long');
|
|
476
|
-
writeOSType(writer, type || 'long', value[key], key, extType,
|
|
546
|
+
writeOSType(writer, type || 'long', value[key], key, extType, childRoot);
|
|
477
547
|
if (logErrors && !type) console.log(`Missing descriptor field type for: '${key}' in`, value);
|
|
478
548
|
}
|
|
479
549
|
}
|
|
@@ -483,8 +553,9 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
483
553
|
case 'obj ': // Reference
|
|
484
554
|
return readReferenceStructure(reader);
|
|
485
555
|
case 'Objc': // Descriptor
|
|
486
|
-
case 'GlbO': // GlobalObject same as Descriptor
|
|
487
556
|
return readDescriptorStructure(reader, includeClass);
|
|
557
|
+
case 'GlbO': // GlobalObject same as Descriptor
|
|
558
|
+
return setHidden(readDescriptorStructure(reader, includeClass), osTypeKey, type);
|
|
488
559
|
case 'VlLs': { // List
|
|
489
560
|
const length = readInt32(reader);
|
|
490
561
|
const items: any[] = [];
|
|
@@ -509,7 +580,7 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
509
580
|
const units = readSignature(reader);
|
|
510
581
|
const value = readFloat32(reader);
|
|
511
582
|
if (!unitsMap[units]) throw new Error(`Invalid units: ${units}`);
|
|
512
|
-
return { units: unitsMap[units], value };
|
|
583
|
+
return setHidden({ units: unitsMap[units], value }, osTypeKey, type);
|
|
513
584
|
}
|
|
514
585
|
case 'TEXT': // String
|
|
515
586
|
return readUnicodeString(reader);
|
|
@@ -523,13 +594,13 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
523
594
|
case 'comp': { // Large Integer
|
|
524
595
|
const low = readUint32(reader);
|
|
525
596
|
const high = readUint32(reader);
|
|
526
|
-
return { low, high };
|
|
597
|
+
return setHidden({ low, high }, osTypeKey, type);
|
|
527
598
|
}
|
|
528
599
|
case 'bool': // Boolean
|
|
529
600
|
return !!readUint8(reader);
|
|
530
601
|
case 'type': // Class
|
|
531
602
|
case 'GlbC': // Class
|
|
532
|
-
return readClassStructure(reader);
|
|
603
|
+
return setHidden(readClassStructure(reader), osTypeKey, type);
|
|
533
604
|
case 'alis': { // Alias
|
|
534
605
|
const length = readInt32(reader);
|
|
535
606
|
return readAsciiString(reader, length);
|
|
@@ -541,25 +612,24 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
541
612
|
case 'ObAr': { // Object array
|
|
542
613
|
readInt32(reader); // version: 16
|
|
543
614
|
readUnicodeString(reader); // name: ''
|
|
544
|
-
readAsciiStringOrClassId(reader); // 'rationalPoint'
|
|
615
|
+
const classID = readAsciiStringOrClassId(reader); // 'rationalPoint'
|
|
545
616
|
const length = readInt32(reader);
|
|
546
617
|
const items: any[] = [];
|
|
547
618
|
|
|
548
619
|
for (let i = 0; i < length; i++) {
|
|
549
620
|
const type1 = readAsciiStringOrClassId(reader); // type Hrzn | Vrtc
|
|
550
|
-
readSignature(reader); // UnFl
|
|
551
|
-
|
|
552
|
-
readSignature(reader); // units ? '#Pxl'
|
|
621
|
+
const valueType = readSignature(reader); // UnFl
|
|
622
|
+
const units = readSignature(reader); // units ? '#Pxl'
|
|
553
623
|
const valuesCount = readInt32(reader);
|
|
554
624
|
const values: number[] = [];
|
|
555
625
|
for (let j = 0; j < valuesCount; j++) {
|
|
556
626
|
values.push(readFloat64(reader));
|
|
557
627
|
}
|
|
558
628
|
|
|
559
|
-
items.push({ type: type1, values });
|
|
629
|
+
items.push(setHidden({ type: type1, values }, obArInfoKey, { valueType, units }));
|
|
560
630
|
}
|
|
561
631
|
|
|
562
|
-
return items;
|
|
632
|
+
return setHidden(items, obArInfoKey, { classID });
|
|
563
633
|
}
|
|
564
634
|
case 'Pth ': { // File path
|
|
565
635
|
/*const length =*/ readInt32(reader); // total size of all fields below
|
|
@@ -588,9 +658,9 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
588
658
|
case 'Objc': // Descriptor
|
|
589
659
|
case 'GlbO': { // GlobalObject same as Descriptor
|
|
590
660
|
if (typeof value !== 'object') throw new Error(`Invalid struct value: ${JSON.stringify(value)}, key: ${key}`);
|
|
591
|
-
if (!extType) throw new Error(`Missing ext type for: '${key}' (${JSON.stringify(value)})`);
|
|
592
|
-
const name = value._name || extType
|
|
593
|
-
const classID = value._classID || extType
|
|
661
|
+
if (!extType && !value._classID) throw new Error(`Missing ext type for: '${key}' (${JSON.stringify(value)})`);
|
|
662
|
+
const name = value._name || extType?.name || '';
|
|
663
|
+
const classID = value._classID || extType!.classID;
|
|
594
664
|
writeDescriptorStructure(writer, name, classID, value, root);
|
|
595
665
|
break;
|
|
596
666
|
}
|
|
@@ -599,7 +669,7 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
599
669
|
writeInt32(writer, value.length);
|
|
600
670
|
|
|
601
671
|
for (let i = 0; i < value.length; i++) {
|
|
602
|
-
const type = fieldToArrayType[key];
|
|
672
|
+
const type = getRecordedOSType(value[i], fieldToArrayType[key]) ?? guessOSType(value[i]);
|
|
603
673
|
writeSignature(writer, type || 'long');
|
|
604
674
|
writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
|
|
605
675
|
if (logErrors && !type) console.log(`Missing descriptor array type for: '${key}' in`, value);
|
|
@@ -631,19 +701,31 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
631
701
|
}
|
|
632
702
|
case 'long': // Integer
|
|
633
703
|
if (typeof value !== 'number') throw new Error(`Invalid integer value: ${JSON.stringify(value)}, key: ${key}`);
|
|
634
|
-
writeInt32(writer, value);
|
|
704
|
+
writeInt32(writer, Math.round(value)); // round instead of truncating fractional values
|
|
705
|
+
break;
|
|
706
|
+
case 'comp': // Large Integer
|
|
707
|
+
if (!value || typeof value !== 'object') throw new Error(`Invalid large integer value: ${JSON.stringify(value)}, key: ${key}`);
|
|
708
|
+
writeUint32(writer, value.low >>> 0);
|
|
709
|
+
writeUint32(writer, value.high >>> 0);
|
|
635
710
|
break;
|
|
636
|
-
// case 'comp': // Large Integer
|
|
637
|
-
// writeLargeInteger(reader);
|
|
638
711
|
case 'bool': // Boolean
|
|
639
712
|
if (typeof value !== 'boolean') throw new Error(`Invalid boolean value: ${JSON.stringify(value)}, key: ${key}`);
|
|
640
713
|
writeUint8(writer, value ? 1 : 0);
|
|
641
714
|
break;
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
715
|
+
case 'type': // Class
|
|
716
|
+
case 'GlbC': // Class
|
|
717
|
+
if (!value || typeof value !== 'object') throw new Error(`Invalid class value: ${JSON.stringify(value)}, key: ${key}`);
|
|
718
|
+
writeUnicodeStringWithPadding(writer, value.name || '');
|
|
719
|
+
writeAsciiStringOrClassId(writer, value.classID);
|
|
720
|
+
break;
|
|
721
|
+
case 'alis': { // Alias (read as a binary string, one char per byte)
|
|
722
|
+
if (typeof value !== 'string') throw new Error(`Invalid alias value: ${JSON.stringify(value)}, key: ${key}`);
|
|
723
|
+
writeInt32(writer, value.length);
|
|
724
|
+
for (let i = 0; i < value.length; i++) {
|
|
725
|
+
writeUint8(writer, value.charCodeAt(i) & 0xff);
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
647
729
|
case 'tdta': // Raw Data
|
|
648
730
|
writeInt32(writer, value.byteLength);
|
|
649
731
|
writeBytes(writer, value);
|
|
@@ -651,15 +733,16 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
651
733
|
case 'ObAr': { // Object array
|
|
652
734
|
writeInt32(writer, 16); // version
|
|
653
735
|
writeUnicodeStringWithPadding(writer, ''); // name
|
|
654
|
-
const type = ObArTypes[key];
|
|
736
|
+
const type = getHidden(value, obArInfoKey)?.classID ?? ObArTypes[key];
|
|
655
737
|
if (!type) throw new Error(`Not implemented ObArType for: ${key}`);
|
|
656
738
|
writeAsciiStringOrClassId(writer, type);
|
|
657
739
|
writeInt32(writer, value.length);
|
|
658
740
|
|
|
659
741
|
for (let i = 0; i < value.length; i++) {
|
|
742
|
+
const info = getHidden(value[i], obArInfoKey);
|
|
660
743
|
writeAsciiStringOrClassId(writer, value[i].type); // Hrzn | Vrtc
|
|
661
|
-
writeSignature(writer, 'UnFl');
|
|
662
|
-
writeSignature(writer, '#Pxl');
|
|
744
|
+
writeSignature(writer, info?.valueType ?? 'UnFl');
|
|
745
|
+
writeSignature(writer, info?.units ?? '#Pxl');
|
|
663
746
|
writeInt32(writer, value[i].values.length);
|
|
664
747
|
|
|
665
748
|
for (let j = 0; j < value[i].values.length; j++) {
|
|
@@ -682,45 +765,78 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
682
765
|
}
|
|
683
766
|
}
|
|
684
767
|
|
|
768
|
+
export interface DescriptorReferenceItem {
|
|
769
|
+
type: 'prop' | 'Clss' | 'Enmr' | 'rele' | 'Idnt' | 'indx' | 'name';
|
|
770
|
+
name?: string; // class name
|
|
771
|
+
classID?: string;
|
|
772
|
+
keyID?: string; // prop
|
|
773
|
+
typeID?: string; // Enmr
|
|
774
|
+
enumValue?: string; // Enmr
|
|
775
|
+
value?: string | number; // rele, Idnt, indx, name
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function referenceItemToValue(item: DescriptorReferenceItem): any {
|
|
779
|
+
switch (item.type) {
|
|
780
|
+
case 'prop': return item.keyID;
|
|
781
|
+
case 'Clss': return { name: item.name || '', classID: item.classID };
|
|
782
|
+
case 'Enmr': return `${item.typeID}.${item.enumValue}`;
|
|
783
|
+
default: return item.value;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function referenceItemMatches(item: DescriptorReferenceItem | undefined, value: any) {
|
|
788
|
+
if (!item) return false;
|
|
789
|
+
const expected = referenceItemToValue(item);
|
|
790
|
+
if (expected && typeof expected === 'object') {
|
|
791
|
+
return !!value && typeof value === 'object' && value.name === expected.name && value.classID === expected.classID;
|
|
792
|
+
}
|
|
793
|
+
return expected === value;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Attaches information about how each item of reference should be written (class IDs, reference types),
|
|
797
|
+
// the public value of the array stays the same (array of strings/numbers/class objects)
|
|
798
|
+
export function createDescriptorReference(items: DescriptorReferenceItem[]): any[] {
|
|
799
|
+
return setHidden(items.map(referenceItemToValue), referenceInfoKey, items);
|
|
800
|
+
}
|
|
801
|
+
|
|
685
802
|
function readReferenceStructure(reader: PsdReader) {
|
|
686
803
|
const itemsCount = readInt32(reader);
|
|
687
|
-
const
|
|
804
|
+
const infos: DescriptorReferenceItem[] = [];
|
|
688
805
|
|
|
689
806
|
for (let i = 0; i < itemsCount; i++) {
|
|
690
807
|
const type = readSignature(reader);
|
|
691
808
|
|
|
692
809
|
switch (type) {
|
|
693
810
|
case 'prop': { // Property
|
|
694
|
-
readClassStructure(reader);
|
|
811
|
+
const { name, classID } = readClassStructure(reader);
|
|
695
812
|
const keyID = readAsciiStringOrClassId(reader);
|
|
696
|
-
|
|
813
|
+
infos.push({ type, name, classID, keyID });
|
|
697
814
|
break;
|
|
698
815
|
}
|
|
699
|
-
case 'Clss': // Class
|
|
700
|
-
|
|
816
|
+
case 'Clss': { // Class
|
|
817
|
+
const { name, classID } = readClassStructure(reader);
|
|
818
|
+
infos.push({ type, name, classID });
|
|
701
819
|
break;
|
|
820
|
+
}
|
|
702
821
|
case 'Enmr': { // Enumerated Reference
|
|
703
|
-
readClassStructure(reader);
|
|
822
|
+
const { name, classID } = readClassStructure(reader);
|
|
704
823
|
const typeID = readAsciiStringOrClassId(reader);
|
|
705
|
-
const
|
|
706
|
-
|
|
824
|
+
const enumValue = readAsciiStringOrClassId(reader);
|
|
825
|
+
infos.push({ type, name, classID, typeID, enumValue });
|
|
707
826
|
break;
|
|
708
827
|
}
|
|
709
828
|
case 'rele': { // Offset
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
items.push(readUint32(reader));
|
|
829
|
+
const { name, classID } = readClassStructure(reader);
|
|
830
|
+
infos.push({ type, name, classID, value: readUint32(reader) });
|
|
713
831
|
break;
|
|
714
832
|
}
|
|
715
833
|
case 'Idnt': // Identifier
|
|
716
|
-
items.push(readInt32(reader));
|
|
717
|
-
break;
|
|
718
834
|
case 'indx': // Index
|
|
719
|
-
|
|
835
|
+
infos.push({ type, value: readInt32(reader) });
|
|
720
836
|
break;
|
|
721
837
|
case 'name': { // Name
|
|
722
|
-
readClassStructure(reader);
|
|
723
|
-
|
|
838
|
+
const { name, classID } = readClassStructure(reader);
|
|
839
|
+
infos.push({ type, name, classID, value: readUnicodeString(reader) });
|
|
724
840
|
break;
|
|
725
841
|
}
|
|
726
842
|
default:
|
|
@@ -728,46 +844,83 @@ function readReferenceStructure(reader: PsdReader) {
|
|
|
728
844
|
}
|
|
729
845
|
}
|
|
730
846
|
|
|
731
|
-
return
|
|
847
|
+
return createDescriptorReference(infos);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function guessReferenceItem(value: any): DescriptorReferenceItem {
|
|
851
|
+
if (typeof value === 'string') {
|
|
852
|
+
// enumerated references are read as `${typeID}.${enumValue}` where typeID is a 4 character ID or a string ID
|
|
853
|
+
// (values like 'My.Path' or 'Logo.v2' can't be typeID/enum value, so they are written as name references)
|
|
854
|
+
const match = /^([a-z ]{4}|[a-z]{5,})\.([a-z ]+)$/i.exec(value);
|
|
855
|
+
|
|
856
|
+
if (match) {
|
|
857
|
+
return { type: 'Enmr', name: '', classID: match[1], typeID: match[1], enumValue: match[2] };
|
|
858
|
+
} else {
|
|
859
|
+
return { type: 'name', name: '', classID: 'Lyr ', value };
|
|
860
|
+
}
|
|
861
|
+
} else if (typeof value === 'number') {
|
|
862
|
+
return { type: 'Idnt', value };
|
|
863
|
+
} else if (value && typeof value === 'object' && typeof value.classID === 'string') {
|
|
864
|
+
return { type: 'Clss', name: value.name || '', classID: value.classID };
|
|
865
|
+
} else {
|
|
866
|
+
throw new Error(`Invalid descriptor reference value: ${JSON.stringify(value)}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// path selection state (image resource 1088) is a reference to paths, enumerated references there use 'Path' class
|
|
871
|
+
// (name references use 'Lyr ' class, they reference layer of the vector mask)
|
|
872
|
+
export function pathSelectionStateToReference(items: string[]): any[] {
|
|
873
|
+
const infos: DescriptorReferenceItem[] | undefined = getHidden(items, referenceInfoKey);
|
|
874
|
+
|
|
875
|
+
return createDescriptorReference(items.map((value, i) => {
|
|
876
|
+
if (referenceItemMatches(infos?.[i], value)) return infos![i]; // keep information read from the file
|
|
877
|
+
|
|
878
|
+
const item = guessReferenceItem(value);
|
|
879
|
+
if (item.type === 'Enmr') item.classID = 'Path';
|
|
880
|
+
return item;
|
|
881
|
+
}));
|
|
732
882
|
}
|
|
733
883
|
|
|
734
884
|
function writeReferenceStructure(writer: PsdWriter, _key: string, items: any[]) {
|
|
885
|
+
if (!Array.isArray(items)) throw new Error(`Invalid reference value: ${JSON.stringify(items)}, key: ${_key}`);
|
|
886
|
+
|
|
887
|
+
const infos: DescriptorReferenceItem[] | undefined = getHidden(items, referenceInfoKey);
|
|
735
888
|
writeInt32(writer, items.length);
|
|
736
889
|
|
|
737
890
|
for (let i = 0; i < items.length; i++) {
|
|
738
891
|
const value = items[i];
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
if (typeof value === 'string') {
|
|
742
|
-
if (/^[a-z ]+\.[a-z ]+$/i.test(value)) {
|
|
743
|
-
type = 'Enmr';
|
|
744
|
-
} else {
|
|
745
|
-
type = 'name';
|
|
746
|
-
}
|
|
747
|
-
}
|
|
892
|
+
const recorded = infos?.[i];
|
|
893
|
+
const item = referenceItemMatches(recorded, value) ? recorded! : guessReferenceItem(value);
|
|
748
894
|
|
|
749
|
-
writeSignature(writer, type);
|
|
895
|
+
writeSignature(writer, item.type);
|
|
750
896
|
|
|
751
|
-
switch (type) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const [typeID, enumValue] = value.split('.');
|
|
756
|
-
writeClassStructure(writer, '\0', typeID);
|
|
757
|
-
writeAsciiStringOrClassId(writer, typeID);
|
|
758
|
-
writeAsciiStringOrClassId(writer, enumValue);
|
|
897
|
+
switch (item.type) {
|
|
898
|
+
case 'prop': // Property
|
|
899
|
+
writeClassStructure(writer, item.name || '', item.classID!);
|
|
900
|
+
writeAsciiStringOrClassId(writer, item.keyID!);
|
|
759
901
|
break;
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
902
|
+
case 'Clss': // Class
|
|
903
|
+
writeClassStructure(writer, item.name || '', item.classID!);
|
|
904
|
+
break;
|
|
905
|
+
case 'Enmr': // Enumerated Reference
|
|
906
|
+
writeClassStructure(writer, item.name || '', item.classID!);
|
|
907
|
+
writeAsciiStringOrClassId(writer, item.typeID!);
|
|
908
|
+
writeAsciiStringOrClassId(writer, item.enumValue!);
|
|
909
|
+
break;
|
|
910
|
+
case 'rele': // Offset
|
|
911
|
+
writeClassStructure(writer, item.name || '', item.classID!);
|
|
912
|
+
writeUint32(writer, item.value as number);
|
|
913
|
+
break;
|
|
914
|
+
case 'Idnt': // Identifier
|
|
915
|
+
case 'indx': // Index
|
|
916
|
+
writeInt32(writer, item.value as number);
|
|
917
|
+
break;
|
|
918
|
+
case 'name': // Name
|
|
919
|
+
writeClassStructure(writer, item.name || '', item.classID!);
|
|
920
|
+
writeUnicodeStringWithPadding(writer, `${item.value}`);
|
|
767
921
|
break;
|
|
768
|
-
}
|
|
769
922
|
default:
|
|
770
|
-
throw new Error(`Invalid descriptor reference type: ${type}`);
|
|
923
|
+
throw new Error(`Invalid descriptor reference type: ${item.type}`);
|
|
771
924
|
}
|
|
772
925
|
}
|
|
773
926
|
|
|
@@ -780,8 +933,8 @@ function readClassStructure(reader: PsdReader) {
|
|
|
780
933
|
return { name, classID };
|
|
781
934
|
}
|
|
782
935
|
|
|
783
|
-
function writeClassStructure(writer: PsdWriter, name: string, classID: string) {
|
|
784
|
-
|
|
936
|
+
export function writeClassStructure(writer: PsdWriter, name: string, classID: string) {
|
|
937
|
+
writeUnicodeStringWithPadding(writer, name);
|
|
785
938
|
writeAsciiStringOrClassId(writer, classID);
|
|
786
939
|
}
|
|
787
940
|
|
|
@@ -827,11 +980,11 @@ export type DescriptorColor = {
|
|
|
827
980
|
Blck: number;
|
|
828
981
|
} | {
|
|
829
982
|
_name: '';
|
|
830
|
-
_classID: 'GRYC'; //
|
|
983
|
+
_classID: 'Grsc' | 'GRYC'; // 'GRYC' was written by older versions of ag-psd
|
|
831
984
|
'Gry ': number;
|
|
832
985
|
} | {
|
|
833
986
|
_name: '';
|
|
834
|
-
_classID: 'LABC'; //
|
|
987
|
+
_classID: 'LbCl' | 'LABC'; // 'LABC' was written by older versions of ag-psd
|
|
835
988
|
Lmnc: number;
|
|
836
989
|
'A ': number;
|
|
837
990
|
'B ': number;
|
|
@@ -1258,9 +1411,11 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1258
1411
|
|
|
1259
1412
|
for (const key of Object.keys(e)) {
|
|
1260
1413
|
const value = (e as any)[key];
|
|
1414
|
+
if (value == null || typeof value !== 'object') continue;
|
|
1415
|
+
|
|
1261
1416
|
if (Array.isArray(value)) {
|
|
1262
1417
|
for (const effect of value) {
|
|
1263
|
-
if (effect
|
|
1418
|
+
if (effect?.enabled) info.numModifyingFX++;
|
|
1264
1419
|
}
|
|
1265
1420
|
} else if (value.enabled) {
|
|
1266
1421
|
info.numModifyingFX++;
|
|
@@ -1410,7 +1565,7 @@ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescri
|
|
|
1410
1565
|
Effc: !!t.effectParams,
|
|
1411
1566
|
...(t.effectParams ? {
|
|
1412
1567
|
effectParams: {
|
|
1413
|
-
keyList: serializeKeyList(t.keys),
|
|
1568
|
+
keyList: serializeKeyList(t.effectParams.keys),
|
|
1414
1569
|
fillCanvas: t.effectParams.fillCanvas,
|
|
1415
1570
|
zoomOrigin: t.effectParams.zoomOrigin,
|
|
1416
1571
|
}
|
|
@@ -1541,7 +1696,7 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1541
1696
|
case 'highlightOpacity': result.hglO = unitsPercent(val); break;
|
|
1542
1697
|
case 'shadowOpacity': result.sdwO = unitsPercent(val); break;
|
|
1543
1698
|
case 'angle':
|
|
1544
|
-
if (objName === 'gradientOverlay' || objName === '
|
|
1699
|
+
if (objName === 'gradientOverlay' || objName === 'patternOverlay') {
|
|
1545
1700
|
result.Angl = unitsAngle(val);
|
|
1546
1701
|
} else {
|
|
1547
1702
|
result.lagl = unitsAngle(val);
|
|
@@ -1586,7 +1741,8 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1586
1741
|
|
|
1587
1742
|
function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoiseGradient {
|
|
1588
1743
|
if (grad.GrdF === 'GrdF.CstS') {
|
|
1589
|
-
|
|
1744
|
+
// stop locations are always in 0..4096 range, independent of smoothness (Intr)
|
|
1745
|
+
const samples = 4096;
|
|
1590
1746
|
|
|
1591
1747
|
return {
|
|
1592
1748
|
type: 'solid',
|
|
@@ -1620,11 +1776,12 @@ function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoi
|
|
|
1620
1776
|
|
|
1621
1777
|
function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): DesciptorGradient {
|
|
1622
1778
|
if (grad.type === 'solid') {
|
|
1623
|
-
|
|
1779
|
+
// stop locations are always in 0..4096 range, independent of smoothness (Intr)
|
|
1780
|
+
const samples = 4096;
|
|
1624
1781
|
return {
|
|
1625
1782
|
'Nm ': grad.name || '',
|
|
1626
1783
|
GrdF: 'GrdF.CstS',
|
|
1627
|
-
Intr:
|
|
1784
|
+
Intr: Math.round((grad.smoothness ?? 1) * 4096),
|
|
1628
1785
|
Clrs: grad.colorStops.map(s => ({
|
|
1629
1786
|
'Clr ': serializeColor(s.color),
|
|
1630
1787
|
Type: 'Clry.UsrS',
|
|
@@ -1646,8 +1803,8 @@ function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): Des
|
|
|
1646
1803
|
ClrS: ClrS.encode(grad.colorModel),
|
|
1647
1804
|
RndS: grad.randomSeed || 0,
|
|
1648
1805
|
Smth: Math.round((grad.roughness ?? 1) * 4096),
|
|
1649
|
-
'Mnm ': (grad.min || [0, 0, 0, 0]).map(x => x * 100),
|
|
1650
|
-
'Mxm ': (grad.max || [1, 1, 1, 1]).map(x => x * 100),
|
|
1806
|
+
'Mnm ': (grad.min || [0, 0, 0, 0]).map(x => Math.round(x * 100)),
|
|
1807
|
+
'Mxm ': (grad.max || [1, 1, 1, 1]).map(x => Math.round(x * 100)),
|
|
1651
1808
|
};
|
|
1652
1809
|
}
|
|
1653
1810
|
}
|
|
@@ -1734,25 +1891,76 @@ export function serializeVectorContent(content: VectorContent): { descriptor: De
|
|
|
1734
1891
|
}
|
|
1735
1892
|
}
|
|
1736
1893
|
|
|
1894
|
+
// Descriptor color values: HSB saturation/brightness 0..100, CMYK 0..100, Gray 0..100, Lab L 0..100, a/b -128..127
|
|
1895
|
+
// Color values (see psd.ts): HSB 0..1, CMYK 0..255, Gray 0..255, Lab L 0..1, a/b -1..1 (same as readColor/writeColor)
|
|
1896
|
+
|
|
1897
|
+
const colorSourceKey = typeof Symbol !== 'undefined' ? Symbol('agPsdColorSource') : '__agPsdColorSource';
|
|
1898
|
+
|
|
1899
|
+
const colorConversions: { [key: string]: ((x: number) => number)[] | undefined; } = {
|
|
1900
|
+
// key: [descriptor -> color, color -> descriptor]
|
|
1901
|
+
Strt: [x => x / 100, x => x * 100],
|
|
1902
|
+
Brgh: [x => x / 100, x => x * 100],
|
|
1903
|
+
'Cyn ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1904
|
+
Mgnt: [x => x * 255 / 100, x => x * 100 / 255],
|
|
1905
|
+
'Ylw ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1906
|
+
Blck: [x => x * 255 / 100, x => x * 100 / 255],
|
|
1907
|
+
'Gry ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1908
|
+
Lmnc: [x => x / 100, x => x * 100],
|
|
1909
|
+
'A ': [normalizeLabAB, denormalizeLabAB],
|
|
1910
|
+
'B ': [normalizeLabAB, denormalizeLabAB],
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1913
|
+
function colorToDescriptorValue(key: string, value: number) {
|
|
1914
|
+
const conversion = colorConversions[key];
|
|
1915
|
+
if (!conversion) return value;
|
|
1916
|
+
const [from, to] = conversion;
|
|
1917
|
+
const result = to(value);
|
|
1918
|
+
if (!isFinite(result)) return result;
|
|
1919
|
+
|
|
1920
|
+
// prefer shortest number that converts back to the same value (avoids values like 30.200000000000003)
|
|
1921
|
+
for (let precision = 1; precision < 17; precision++) {
|
|
1922
|
+
const candidate = +result.toPrecision(precision);
|
|
1923
|
+
if (from(candidate) === value) return candidate;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
return result;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function shallowEqual(a: any, b: any) {
|
|
1930
|
+
const keys = Object.keys(a);
|
|
1931
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
1932
|
+
return keys.every(key => a[key] === b[key]);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1737
1935
|
export function parseColor(color: DescriptorColor): Color {
|
|
1936
|
+
let result: Color;
|
|
1937
|
+
|
|
1738
1938
|
if ('H ' in color) {
|
|
1739
|
-
|
|
1939
|
+
result = { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
|
|
1740
1940
|
} else if ('Rd ' in color) {
|
|
1741
1941
|
return { r: color['Rd '], g: color['Grn '], b: color['Bl '] };
|
|
1742
1942
|
} else if ('Cyn ' in color) {
|
|
1743
|
-
|
|
1943
|
+
result = {
|
|
1944
|
+
c: color['Cyn '] * 255 / 100,
|
|
1945
|
+
m: color.Mgnt * 255 / 100,
|
|
1946
|
+
y: color['Ylw '] * 255 / 100,
|
|
1947
|
+
k: color.Blck * 255 / 100,
|
|
1948
|
+
};
|
|
1744
1949
|
} else if ('Gry ' in color) {
|
|
1745
|
-
|
|
1950
|
+
result = { k: color['Gry '] * 255 / 100 };
|
|
1746
1951
|
} else if ('Lmnc' in color) {
|
|
1747
|
-
|
|
1952
|
+
result = { l: color.Lmnc / 100, a: normalizeLabAB(color['A ']), b: normalizeLabAB(color['B ']) };
|
|
1748
1953
|
} else if ('redFloat' in color) {
|
|
1749
1954
|
return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
|
|
1750
1955
|
} else {
|
|
1751
1956
|
throw new Error('Unsupported color descriptor');
|
|
1752
1957
|
}
|
|
1958
|
+
|
|
1959
|
+
// remember original descriptor values, so unmodified colors can be written back without conversion errors
|
|
1960
|
+
return setHidden(result, colorSourceKey, { color: { ...result }, descriptor: color });
|
|
1753
1961
|
}
|
|
1754
1962
|
|
|
1755
|
-
|
|
1963
|
+
function serializeColorValues(color: Color | undefined): DescriptorColor {
|
|
1756
1964
|
if (!color) {
|
|
1757
1965
|
return { _name: '', _classID: 'RGBC', 'Rd ': 0, 'Grn ': 0, 'Bl ': 0 };
|
|
1758
1966
|
} else if ('r' in color) {
|
|
@@ -1760,18 +1968,50 @@ export function serializeColor(color: Color | undefined): DescriptorColor {
|
|
|
1760
1968
|
} else if ('fr' in color) {
|
|
1761
1969
|
return { _name: '', _classID: 'RGBC', redFloat: color.fr, greenFloat: color.fg, blueFloat: color.fb };
|
|
1762
1970
|
} else if ('h' in color) {
|
|
1763
|
-
return {
|
|
1971
|
+
return {
|
|
1972
|
+
_name: '', _classID: 'HSBC',
|
|
1973
|
+
'H ': unitsAngle((color.h || 0) * 360),
|
|
1974
|
+
Strt: colorToDescriptorValue('Strt', color.s || 0),
|
|
1975
|
+
Brgh: colorToDescriptorValue('Brgh', color.b || 0),
|
|
1976
|
+
};
|
|
1764
1977
|
} else if ('c' in color) {
|
|
1765
|
-
return {
|
|
1978
|
+
return {
|
|
1979
|
+
_name: '', _classID: 'CMYC',
|
|
1980
|
+
'Cyn ': colorToDescriptorValue('Cyn ', color.c || 0),
|
|
1981
|
+
Mgnt: colorToDescriptorValue('Mgnt', color.m || 0),
|
|
1982
|
+
'Ylw ': colorToDescriptorValue('Ylw ', color.y || 0),
|
|
1983
|
+
Blck: colorToDescriptorValue('Blck', color.k || 0),
|
|
1984
|
+
};
|
|
1766
1985
|
} else if ('l' in color) {
|
|
1767
|
-
return {
|
|
1986
|
+
return {
|
|
1987
|
+
_name: '', _classID: 'LbCl',
|
|
1988
|
+
Lmnc: colorToDescriptorValue('Lmnc', color.l || 0),
|
|
1989
|
+
'A ': colorToDescriptorValue('A ', color.a || 0),
|
|
1990
|
+
'B ': colorToDescriptorValue('B ', color.b || 0),
|
|
1991
|
+
};
|
|
1768
1992
|
} else if ('k' in color) {
|
|
1769
|
-
return { _name: '', _classID: '
|
|
1993
|
+
return { _name: '', _classID: 'Grsc', 'Gry ': colorToDescriptorValue('Gry ', color.k || 0) };
|
|
1770
1994
|
} else {
|
|
1771
1995
|
throw new Error('Invalid color value');
|
|
1772
1996
|
}
|
|
1773
1997
|
}
|
|
1774
1998
|
|
|
1999
|
+
export function serializeColor(color: Color | undefined): DescriptorColor {
|
|
2000
|
+
const result: any = serializeColorValues(color);
|
|
2001
|
+
const source = getHidden(color, colorSourceKey);
|
|
2002
|
+
|
|
2003
|
+
if (source && shallowEqual(source.color, color)) {
|
|
2004
|
+
// color was not modified since it was read, use exact original values
|
|
2005
|
+
for (const key of Object.keys(result)) {
|
|
2006
|
+
if (colorConversions[key] && typeof source.descriptor[key] === 'number') {
|
|
2007
|
+
result[key] = source.descriptor[key];
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
return result;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
1775
2015
|
export function parseAngle(x: DescriptorUnitsValue) {
|
|
1776
2016
|
if (x === undefined) return 0;
|
|
1777
2017
|
if (x.units !== 'Angle') throw new Error(`Invalid units: ${x.units}`);
|