ag-psd 32.0.0 → 34.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/03e19709 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +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/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +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/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +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/d13d7610 +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/e6745c2b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +5 -0
- package/README.md +4 -7
- package/README_PSD.md +261 -84
- package/TODO.md +13 -84
- package/dist/abr.d.ts +11 -4
- package/dist/abr.js +362 -230
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +828 -361
- package/dist/additionalInfo.js.map +1 -1
- package/dist/ase.d.ts +6 -1
- package/dist/ase.js +61 -61
- package/dist/ase.js.map +1 -1
- package/dist/bundle.js +3321 -1364
- package/dist/descriptor.d.ts +28 -13
- package/dist/descriptor.js +295 -241
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +7 -1
- package/dist/helpers.js +246 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +586 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +8 -8
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +292 -19
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +10 -0
- package/dist/psdReader.js +424 -216
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +373 -113
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +75 -6
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +0 -3
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +362 -230
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +828 -361
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/ase.d.ts +6 -1
- package/dist-es/ase.js +61 -61
- package/dist-es/ase.js.map +1 -1
- package/dist-es/descriptor.d.ts +28 -13
- package/dist-es/descriptor.js +295 -241
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +7 -1
- package/dist-es/helpers.js +246 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +586 -144
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.d.ts +1 -0
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +8 -8
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +292 -19
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +10 -0
- package/dist-es/psdReader.js +424 -216
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +373 -113
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +75 -6
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +0 -3
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/perf.js +312 -9
- package/src/abr.ts +382 -235
- package/src/additionalInfo.ts +1071 -482
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +291 -242
- package/src/helpers.ts +266 -3
- package/src/imageResources.ts +736 -169
- package/src/index.ts +1 -0
- package/src/jpeg.ts +8 -8
- package/src/psd.ts +271 -32
- package/src/psdReader.ts +440 -221
- package/src/psdWriter.ts +402 -110
- package/src/text.ts +93 -7
- package/src/utf8.ts +0 -4
package/src/descriptor.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createEnum, normalizeLabAB, denormalizeLabAB } from './helpers';
|
|
2
2
|
import {
|
|
3
|
-
AntiAlias, BevelDirection, BevelStyle, BevelTechnique, BlendMode, Color, EffectContour,
|
|
3
|
+
AntiAlias, BevelDirection, BevelStyle, BevelTechnique, BlendMode, Color, ColorStop, EffectContour,
|
|
4
4
|
EffectNoiseGradient, EffectPattern, EffectSolidGradient, ExtraGradientInfo, ExtraPatternInfo,
|
|
5
5
|
GlowSource, GlowTechnique, GradientStyle, InterpolationMethod, LayerEffectBevel,
|
|
6
6
|
LayerEffectGradientOverlay, LayerEffectInnerGlow, LayerEffectPatternOverlay,
|
|
@@ -44,11 +44,6 @@ const unitsMap: Dict = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const unitsMapRev = revMap(unitsMap);
|
|
47
|
-
let logErrors = false;
|
|
48
|
-
|
|
49
|
-
export function setLogErrors(value: boolean) {
|
|
50
|
-
logErrors = value;
|
|
51
|
-
}
|
|
52
47
|
|
|
53
48
|
function makeType(name: string, classID: string) {
|
|
54
49
|
return { name, classID };
|
|
@@ -98,6 +93,7 @@ const fieldToExtType: ExtTypeDict = {
|
|
|
98
93
|
psVersion: nullType,
|
|
99
94
|
docDefaultNewArtboardBackgroundColor: makeType('', 'RGBC'),
|
|
100
95
|
artboardRect: makeType('', 'classFloatRect'),
|
|
96
|
+
framedGroupRect: makeType('', 'classFloatRect'),
|
|
101
97
|
keyOriginRRectRadii: makeType('', 'radii'),
|
|
102
98
|
keyOriginBoxCorners: nullType,
|
|
103
99
|
rectangleCornerA: makeType('', 'Pnt '),
|
|
@@ -149,6 +145,7 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
149
145
|
Clrs: makeType('', 'Clrt'),
|
|
150
146
|
Trns: makeType('', 'TrnS'),
|
|
151
147
|
keyDescriptorList: nullType,
|
|
148
|
+
keyRootDescriptorList: nullType,
|
|
152
149
|
solidFillMulti: makeType('', 'SoFi'),
|
|
153
150
|
gradientFillMulti: makeType('', 'GrFl'),
|
|
154
151
|
dropShadowMulti: makeType('', 'DrSh'),
|
|
@@ -183,17 +180,23 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
183
180
|
|
|
184
181
|
const typeToField: { [key: string]: string[]; } = {
|
|
185
182
|
'TEXT': [
|
|
186
|
-
'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName',
|
|
183
|
+
'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName', 'exportOption', 'scaleSettings',
|
|
184
|
+
'BackgroundImagePath', 'ImageSubfolderName', 'SavedIteratedName',
|
|
187
185
|
'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
|
|
188
186
|
'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
|
|
189
|
-
'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath',
|
|
187
|
+
'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath', 'Bk ', 'pathUnicodeName',
|
|
190
188
|
],
|
|
191
189
|
'tdta': [
|
|
192
190
|
'EngineData', 'LUT3DFileData', 'indexArray', 'originalVertexArray', 'deformedVertexArray',
|
|
193
|
-
'LqMe',
|
|
191
|
+
'LqMe', 'bookKey',
|
|
194
192
|
],
|
|
195
193
|
'long': [
|
|
196
|
-
'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ',
|
|
194
|
+
'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ', 'bookID',
|
|
195
|
+
'cropRectBottom', 'cropRectLeft', 'cropRectRight', 'cropRectTop',
|
|
196
|
+
// save for web output settings
|
|
197
|
+
'Version', 'BackgroundColorBlue', 'BackgroundColorGreen', 'BackgroundColorRed', 'BackgroundColorState', 'AttributeCase', 'Encoding',
|
|
198
|
+
'DuplicateFileNameBehavior', 'ImageMapLocation', 'ImageMapType', 'Indent', 'LineEndings', 'SpacersEmptyCells',
|
|
199
|
+
'SpacersHorizontal', 'SpacersVertical', 'StylesFormat', 'TDWidthHeight', 'TagCase', 'ColorPolicy', 'MetadataPolicy',
|
|
197
200
|
'Brgh', 'Cntr', 'means', 'vibrance', 'Strt', 'bwPresetKind', 'comp', 'compID', 'originalCompID',
|
|
198
201
|
'curvesPresetKind', 'mixerPresetKind', 'uOrder', 'vOrder', 'PgNm', 'totalPages', 'Crop',
|
|
199
202
|
'numerator', 'denominator', 'frameCount', 'Annt', 'keyOriginType', 'unitValueQuadVersion',
|
|
@@ -205,7 +208,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
205
208
|
'meshExpansion', 'meshRigidity', 'VrsM', 'VrsN', 'NmbG', 'WLMn', 'WLMx', 'AmMn', 'AmMx', 'SclH', 'SclV',
|
|
206
209
|
'Lvl ', 'TlNm', 'TlOf', 'FlRs', 'Thsh', 'ShrS', 'ShrE', 'FlRs', 'Vrnc', 'Strg', 'ExtS', 'ExtD',
|
|
207
210
|
'HrzS', 'VrtS', 'NmbR', 'EdgF', 'Ang1', 'Ang2', 'Ang3', 'Ang4', 'lastAppliedComp', 'capturedInfo',
|
|
208
|
-
'pixelSourceType',
|
|
211
|
+
'pixelSourceType', 'framedGroupType',
|
|
209
212
|
],
|
|
210
213
|
'enum': [
|
|
211
214
|
'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS', 'BlrQ',
|
|
@@ -215,7 +218,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
215
218
|
'tableOrder', 'enableCompCore', 'enableCompCoreGPU', 'compCoreSupport', 'compCoreGPUSupport', 'Engn',
|
|
216
219
|
'enableCompCoreThreads', 'gs99', 'FrDs', 'trackID', 'animInterpStyle', 'horzAlign',
|
|
217
220
|
'vertAlign', 'bgColorType', 'shapeOperation', 'UndA', 'Wvtp', 'Drct', 'WndM', 'Edg ', 'FlCl', 'IntE',
|
|
218
|
-
'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod',
|
|
221
|
+
'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod', 'pathSymmetryMode',
|
|
219
222
|
'interpretAlpha',
|
|
220
223
|
],
|
|
221
224
|
'bool': [
|
|
@@ -229,15 +232,15 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
229
232
|
'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
|
|
230
233
|
'cellTextIsHTML', 'Smoo', 'Clsp', 'validAtPosition', 'rigidType', 'hasoptions', 'filterMaskEnable',
|
|
231
234
|
'filterMaskLinked', 'filterMaskExtendWithWhite', 'removeJPEGArtifact', 'Mnch', 'ExtF', 'ExtM',
|
|
232
|
-
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
|
|
235
|
+
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn', 'LockPPI',
|
|
233
236
|
],
|
|
234
237
|
'doub': [
|
|
235
|
-
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght',
|
|
238
|
+
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght', 'Gmm ',
|
|
236
239
|
'strokeStyleMiterLimit', 'strokeStyleResolution', 'layerTime', 'keyOriginResolution',
|
|
237
240
|
'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
|
|
238
241
|
'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
|
|
239
242
|
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3',
|
|
240
|
-
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity',
|
|
243
|
+
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity', 'PreferredResolution',
|
|
241
244
|
],
|
|
242
245
|
'UntF': [
|
|
243
246
|
'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
|
|
@@ -246,7 +249,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
246
249
|
'topRight', 'topLeft', 'bottomLeft', 'bottomRight', 'ClNs', 'Shrp',
|
|
247
250
|
],
|
|
248
251
|
'VlLs': [
|
|
249
|
-
'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
|
|
252
|
+
'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'keyRootDescriptorList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
|
|
250
253
|
'LaSt', 'Trnf', 'nonAffineTransform', 'keyDescriptorList', 'guideIndeces', 'gradientFillMulti',
|
|
251
254
|
'solidFillMulti', 'frameFXMulti', 'innerShadowMulti', 'dropShadowMulti', 'FrIn', 'FSts', 'FsFr',
|
|
252
255
|
'sheetTimelineOptions', 'audioClipList', 'trackList', 'globalTrackList', 'keyList', 'audioClipList',
|
|
@@ -268,53 +271,45 @@ const channels = [
|
|
|
268
271
|
// color classes whose channel values are written as doubles ('GRYC' and 'LABC' are kept for backwards compatibility)
|
|
269
272
|
const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
|
|
270
273
|
|
|
271
|
-
//
|
|
272
|
-
|
|
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
|
-
}
|
|
274
|
+
// fallback for unknown keys, based on the type of the value
|
|
275
|
+
export const cameraRawFilterClassID = 'Adobe Camera Raw Filter';
|
|
285
276
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
comp: 'comp',
|
|
277
|
+
// types of Camera Raw Filter settings (keys clash with other descriptors so they're typed by class)
|
|
278
|
+
export const cameraRawFilterKeyTypes: Dict = {
|
|
279
|
+
CrVe: 'TEXT', PrVN: 'long', PrVe: 'long', WBal: 'enum', Ex12: 'doub', ShpR: 'doub',
|
|
280
|
+
'Crv ': 'VlLs', CrvR: 'VlLs', CrvG: 'VlLs', CrvB: 'VlLs',
|
|
291
281
|
};
|
|
292
282
|
|
|
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
283
|
function guessOSType(value: any): string | undefined {
|
|
296
284
|
if (typeof value === 'boolean') return 'bool';
|
|
297
285
|
if (typeof value === 'number' && !Number.isInteger(value)) return 'doub';
|
|
298
286
|
if (Array.isArray(value)) return 'VlLs';
|
|
299
|
-
if (value && typeof value === 'object' && typeof value._classID === 'string') return 'Objc';
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
287
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
288
|
+
if (value && typeof value === 'object') {
|
|
289
|
+
if (typeof value._classID === 'string') return 'Objc';
|
|
290
|
+
if (typeof value.classID === 'string') return 'type'; // class read from 'type' or 'GlbC'
|
|
291
|
+
if (typeof value.low === 'number' && typeof value.high === 'number') return 'comp';
|
|
292
|
+
if (typeof value.units === 'string' && typeof value.value === 'number') return 'UntF';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return undefined;
|
|
308
296
|
}
|
|
309
297
|
|
|
310
298
|
const fieldToArrayType: Dict = {
|
|
311
299
|
'Mnm ': 'long',
|
|
312
300
|
'Mxm ': 'long',
|
|
301
|
+
HtmlFileNameComponents: 'long', // save for web output settings
|
|
302
|
+
SavingFileNameComponents: 'long',
|
|
303
|
+
SliceFileNameComponents: 'long',
|
|
304
|
+
Inpt: 'long', // levels filter
|
|
305
|
+
Otpt: 'long', // levels filter
|
|
313
306
|
FrLs: 'long',
|
|
314
307
|
strokeStyleLineDashSet: 'UntF',
|
|
315
308
|
Trnf: 'doub',
|
|
316
309
|
nonAffineTransform: 'doub',
|
|
317
310
|
keyDescriptorList: 'Objc',
|
|
311
|
+
keyRootDescriptorList: 'Objc',
|
|
312
|
+
pathList: 'Objc',
|
|
318
313
|
gradientFillMulti: 'Objc',
|
|
319
314
|
solidFillMulti: 'Objc',
|
|
320
315
|
frameFXMulti: 'Objc',
|
|
@@ -396,6 +391,12 @@ function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
|
396
391
|
return 'long';
|
|
397
392
|
} else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'HsbP') {
|
|
398
393
|
return 'enum';
|
|
394
|
+
} else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'LvlA') {
|
|
395
|
+
return 'VlLs';
|
|
396
|
+
} else if (key === 'Intr' && typeof value === 'boolean') {
|
|
397
|
+
return 'bool'; // interpolation in print style
|
|
398
|
+
} else if (key === 'PgPs') {
|
|
399
|
+
return 'enum';
|
|
399
400
|
} else if (key === 'LghD' && parent._classID === 'oilPaint') {
|
|
400
401
|
return 'long';
|
|
401
402
|
} else {
|
|
@@ -409,7 +410,7 @@ export function readAsciiStringOrClassId(reader: PsdReader) {
|
|
|
409
410
|
}
|
|
410
411
|
|
|
411
412
|
export function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
|
|
412
|
-
if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list') {
|
|
413
|
+
if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list' && value !== 'json') {
|
|
413
414
|
// write classId
|
|
414
415
|
writeInt32(writer, 0);
|
|
415
416
|
writeSignature(writer, value);
|
|
@@ -441,12 +442,21 @@ export function readDescriptorStructure(reader: PsdReader, includeClass: boolean
|
|
|
441
442
|
return object;
|
|
442
443
|
}
|
|
443
444
|
|
|
445
|
+
export const outputSettingsRoot = 'outputSettings';
|
|
446
|
+
|
|
444
447
|
export function writeDescriptorStructure(writer: PsdWriter, name: string, classId: string, value: any, root: string) {
|
|
445
|
-
if (
|
|
448
|
+
if (writer.logMissingFeatures && !classId) writer.log('Missing classId for: ', name, classId, value);
|
|
446
449
|
|
|
447
450
|
// write class structure
|
|
448
451
|
writeUnicodeStringWithPadding(writer, name);
|
|
449
|
-
|
|
452
|
+
|
|
453
|
+
if (root === outputSettingsRoot && classId === 'null') {
|
|
454
|
+
// Photoshop writes 'null' class id as 4 character string in save for web output settings
|
|
455
|
+
writeInt32(writer, 4);
|
|
456
|
+
writeSignature(writer, classId);
|
|
457
|
+
} else {
|
|
458
|
+
writeAsciiStringOrClassId(writer, classId);
|
|
459
|
+
}
|
|
450
460
|
|
|
451
461
|
const allKeys = Object.keys(value);
|
|
452
462
|
const keys = allKeys.filter(key => key !== '_name' && key !== '_classID' && value[key] !== undefined);
|
|
@@ -458,6 +468,11 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
458
468
|
let childRoot = root;
|
|
459
469
|
let extType = fieldToExtType[key];
|
|
460
470
|
|
|
471
|
+
if (classId === cameraRawFilterClassID) {
|
|
472
|
+
type = cameraRawFilterKeyTypes[key] ?? 'long'; // other settings are integer levels
|
|
473
|
+
childRoot = cameraRawFilterClassID; // lists (curves) contain integer levels
|
|
474
|
+
}
|
|
475
|
+
|
|
461
476
|
if (key === 'bounds' && root === 'text') {
|
|
462
477
|
extType = makeType('', 'bounds');
|
|
463
478
|
} else if (key === 'origin') {
|
|
@@ -527,24 +542,26 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
527
542
|
} else if (value[key].Ptrn) {
|
|
528
543
|
extType = makeType('', 'patternLayer');
|
|
529
544
|
} else {
|
|
530
|
-
|
|
545
|
+
writer.logMissingFeatures && writer.log('Invalid strokeStyleContent value', value[key]);
|
|
531
546
|
}
|
|
532
547
|
} else if (key === 'bounds' && root === 'quiltWarp') {
|
|
533
548
|
extType = makeType('', 'classFloatRect');
|
|
534
549
|
}
|
|
535
550
|
|
|
551
|
+
// guess color class from its fields (colors created by serializeColor always have _classID set)
|
|
536
552
|
if (extType && extType.classID === 'RGBC') {
|
|
537
553
|
if ('H ' in value[key]) extType = { classID: 'HSBC', name: '' };
|
|
538
|
-
|
|
554
|
+
else if ('Cyn ' in value[key]) extType = { classID: 'CMYC', name: '' };
|
|
555
|
+
else if ('Lmnc' in value[key]) extType = { classID: 'LbCl', name: '' };
|
|
556
|
+
else if ('Gry ' in value[key]) extType = { classID: 'Grsc', name: '' };
|
|
539
557
|
}
|
|
540
558
|
|
|
541
|
-
type = getRecordedOSType(value[key], type);
|
|
542
559
|
if (!type) type = guessOSType(value[key]);
|
|
543
560
|
|
|
544
561
|
writeAsciiStringOrClassId(writer, key);
|
|
545
562
|
writeSignature(writer, type || 'long');
|
|
546
563
|
writeOSType(writer, type || 'long', value[key], key, extType, childRoot);
|
|
547
|
-
if (
|
|
564
|
+
if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor field type for: '${key}' in`, value);
|
|
548
565
|
}
|
|
549
566
|
}
|
|
550
567
|
|
|
@@ -553,9 +570,8 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
553
570
|
case 'obj ': // Reference
|
|
554
571
|
return readReferenceStructure(reader);
|
|
555
572
|
case 'Objc': // Descriptor
|
|
556
|
-
return readDescriptorStructure(reader, includeClass);
|
|
557
573
|
case 'GlbO': // GlobalObject same as Descriptor
|
|
558
|
-
return
|
|
574
|
+
return readDescriptorStructure(reader, includeClass);
|
|
559
575
|
case 'VlLs': { // List
|
|
560
576
|
const length = readInt32(reader);
|
|
561
577
|
const items: any[] = [];
|
|
@@ -580,7 +596,7 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
580
596
|
const units = readSignature(reader);
|
|
581
597
|
const value = readFloat32(reader);
|
|
582
598
|
if (!unitsMap[units]) throw new Error(`Invalid units: ${units}`);
|
|
583
|
-
return
|
|
599
|
+
return { units: unitsMap[units], value };
|
|
584
600
|
}
|
|
585
601
|
case 'TEXT': // String
|
|
586
602
|
return readUnicodeString(reader);
|
|
@@ -594,13 +610,13 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
594
610
|
case 'comp': { // Large Integer
|
|
595
611
|
const low = readUint32(reader);
|
|
596
612
|
const high = readUint32(reader);
|
|
597
|
-
return
|
|
613
|
+
return { low, high };
|
|
598
614
|
}
|
|
599
615
|
case 'bool': // Boolean
|
|
600
616
|
return !!readUint8(reader);
|
|
601
617
|
case 'type': // Class
|
|
602
618
|
case 'GlbC': // Class
|
|
603
|
-
return
|
|
619
|
+
return readClassStructure(reader);
|
|
604
620
|
case 'alis': { // Alias
|
|
605
621
|
const length = readInt32(reader);
|
|
606
622
|
return readAsciiString(reader, length);
|
|
@@ -612,24 +628,24 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
612
628
|
case 'ObAr': { // Object array
|
|
613
629
|
readInt32(reader); // version: 16
|
|
614
630
|
readUnicodeString(reader); // name: ''
|
|
615
|
-
|
|
631
|
+
readAsciiStringOrClassId(reader); // classID: 'rationalPoint'
|
|
616
632
|
const length = readInt32(reader);
|
|
617
633
|
const items: any[] = [];
|
|
618
634
|
|
|
619
635
|
for (let i = 0; i < length; i++) {
|
|
620
636
|
const type1 = readAsciiStringOrClassId(reader); // type Hrzn | Vrtc
|
|
621
|
-
|
|
622
|
-
|
|
637
|
+
readSignature(reader); // UnFl
|
|
638
|
+
readSignature(reader); // units ? '#Pxl'
|
|
623
639
|
const valuesCount = readInt32(reader);
|
|
624
640
|
const values: number[] = [];
|
|
625
641
|
for (let j = 0; j < valuesCount; j++) {
|
|
626
642
|
values.push(readFloat64(reader));
|
|
627
643
|
}
|
|
628
644
|
|
|
629
|
-
items.push(
|
|
645
|
+
items.push({ type: type1, values });
|
|
630
646
|
}
|
|
631
647
|
|
|
632
|
-
return
|
|
648
|
+
return items;
|
|
633
649
|
}
|
|
634
650
|
case 'Pth ': { // File path
|
|
635
651
|
/*const length =*/ readInt32(reader); // total size of all fields below
|
|
@@ -669,10 +685,10 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
669
685
|
writeInt32(writer, value.length);
|
|
670
686
|
|
|
671
687
|
for (let i = 0; i < value.length; i++) {
|
|
672
|
-
const type =
|
|
688
|
+
const type = root === cameraRawFilterClassID ? 'long' : (fieldToArrayType[key] ?? guessOSType(value[i]));
|
|
673
689
|
writeSignature(writer, type || 'long');
|
|
674
690
|
writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
|
|
675
|
-
if (
|
|
691
|
+
if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor array type for: '${key}' in`, value);
|
|
676
692
|
}
|
|
677
693
|
break;
|
|
678
694
|
case 'doub': // Double
|
|
@@ -733,16 +749,15 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
733
749
|
case 'ObAr': { // Object array
|
|
734
750
|
writeInt32(writer, 16); // version
|
|
735
751
|
writeUnicodeStringWithPadding(writer, ''); // name
|
|
736
|
-
const type =
|
|
752
|
+
const type = ObArTypes[key];
|
|
737
753
|
if (!type) throw new Error(`Not implemented ObArType for: ${key}`);
|
|
738
754
|
writeAsciiStringOrClassId(writer, type);
|
|
739
755
|
writeInt32(writer, value.length);
|
|
740
756
|
|
|
741
757
|
for (let i = 0; i < value.length; i++) {
|
|
742
|
-
const info = getHidden(value[i], obArInfoKey);
|
|
743
758
|
writeAsciiStringOrClassId(writer, value[i].type); // Hrzn | Vrtc
|
|
744
|
-
writeSignature(writer,
|
|
745
|
-
writeSignature(writer,
|
|
759
|
+
writeSignature(writer, 'UnFl');
|
|
760
|
+
writeSignature(writer, '#Pxl');
|
|
746
761
|
writeInt32(writer, value[i].values.length);
|
|
747
762
|
|
|
748
763
|
for (let j = 0; j < value[i].values.length; j++) {
|
|
@@ -752,12 +767,13 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
752
767
|
break;
|
|
753
768
|
}
|
|
754
769
|
case 'Pth ': { // File path
|
|
755
|
-
const
|
|
770
|
+
const path = value.path + '\0'; // Photoshop stores trailing null character (stripped when reading)
|
|
771
|
+
const length = 4 + 4 + 4 + path.length * 2;
|
|
756
772
|
writeInt32(writer, length);
|
|
757
773
|
writeSignature(writer, value.sig);
|
|
758
774
|
writeInt32LE(writer, length);
|
|
759
|
-
writeInt32LE(writer,
|
|
760
|
-
writeUnicodeStringWithoutLengthLE(writer,
|
|
775
|
+
writeInt32LE(writer, path.length);
|
|
776
|
+
writeUnicodeStringWithoutLengthLE(writer, path);
|
|
761
777
|
break;
|
|
762
778
|
}
|
|
763
779
|
default:
|
|
@@ -765,86 +781,64 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
765
781
|
}
|
|
766
782
|
}
|
|
767
783
|
|
|
784
|
+
// Reference item with explicit reference type and class, can be used in place of a value in reference arrays
|
|
785
|
+
// passed to writeVersionAndDescriptor (references are read as values only: strings, numbers and class objects)
|
|
768
786
|
export interface DescriptorReferenceItem {
|
|
769
787
|
type: 'prop' | 'Clss' | 'Enmr' | 'rele' | 'Idnt' | 'indx' | 'name';
|
|
770
|
-
|
|
771
|
-
|
|
788
|
+
_name?: string; // class name
|
|
789
|
+
_classID?: string;
|
|
772
790
|
keyID?: string; // prop
|
|
773
791
|
typeID?: string; // Enmr
|
|
774
792
|
enumValue?: string; // Enmr
|
|
775
793
|
value?: string | number; // rele, Idnt, indx, name
|
|
776
794
|
}
|
|
777
795
|
|
|
778
|
-
|
|
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
|
-
}
|
|
796
|
+
const referenceTypes = ['prop', 'Clss', 'Enmr', 'rele', 'Idnt', 'indx', 'name'];
|
|
795
797
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
export function createDescriptorReference(items: DescriptorReferenceItem[]): any[] {
|
|
799
|
-
return setHidden(items.map(referenceItemToValue), referenceInfoKey, items);
|
|
798
|
+
function isReferenceItem(value: any): value is DescriptorReferenceItem {
|
|
799
|
+
return !!value && typeof value === 'object' && referenceTypes.indexOf(value.type) !== -1;
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
function readReferenceStructure(reader: PsdReader) {
|
|
803
803
|
const itemsCount = readInt32(reader);
|
|
804
|
-
const
|
|
804
|
+
const items: any[] = [];
|
|
805
805
|
|
|
806
806
|
for (let i = 0; i < itemsCount; i++) {
|
|
807
807
|
const type = readSignature(reader);
|
|
808
808
|
|
|
809
809
|
switch (type) {
|
|
810
|
-
case 'prop':
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
infos.push({ type, name, classID, keyID });
|
|
810
|
+
case 'prop': // Property
|
|
811
|
+
readClassStructure(reader);
|
|
812
|
+
items.push(readAsciiStringOrClassId(reader));
|
|
814
813
|
break;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const { name, classID } = readClassStructure(reader);
|
|
818
|
-
infos.push({ type, name, classID });
|
|
814
|
+
case 'Clss': // Class
|
|
815
|
+
items.push(readClassStructure(reader));
|
|
819
816
|
break;
|
|
820
|
-
}
|
|
821
817
|
case 'Enmr': { // Enumerated Reference
|
|
822
|
-
|
|
818
|
+
readClassStructure(reader);
|
|
823
819
|
const typeID = readAsciiStringOrClassId(reader);
|
|
824
820
|
const enumValue = readAsciiStringOrClassId(reader);
|
|
825
|
-
|
|
821
|
+
items.push(`${typeID}.${enumValue}`);
|
|
826
822
|
break;
|
|
827
823
|
}
|
|
828
|
-
case 'rele':
|
|
829
|
-
|
|
830
|
-
|
|
824
|
+
case 'rele': // Offset
|
|
825
|
+
readClassStructure(reader);
|
|
826
|
+
items.push(readUint32(reader));
|
|
831
827
|
break;
|
|
832
|
-
}
|
|
833
828
|
case 'Idnt': // Identifier
|
|
834
829
|
case 'indx': // Index
|
|
835
|
-
|
|
830
|
+
items.push(readInt32(reader));
|
|
836
831
|
break;
|
|
837
|
-
case 'name':
|
|
838
|
-
|
|
839
|
-
|
|
832
|
+
case 'name': // Name
|
|
833
|
+
readClassStructure(reader);
|
|
834
|
+
items.push(readUnicodeString(reader));
|
|
840
835
|
break;
|
|
841
|
-
}
|
|
842
836
|
default:
|
|
843
837
|
throw new Error(`Invalid descriptor reference type: ${type}`);
|
|
844
838
|
}
|
|
845
839
|
}
|
|
846
840
|
|
|
847
|
-
return
|
|
841
|
+
return items;
|
|
848
842
|
}
|
|
849
843
|
|
|
850
844
|
function guessReferenceItem(value: any): DescriptorReferenceItem {
|
|
@@ -854,14 +848,14 @@ function guessReferenceItem(value: any): DescriptorReferenceItem {
|
|
|
854
848
|
const match = /^([a-z ]{4}|[a-z]{5,})\.([a-z ]+)$/i.exec(value);
|
|
855
849
|
|
|
856
850
|
if (match) {
|
|
857
|
-
return { type: 'Enmr',
|
|
851
|
+
return { type: 'Enmr', _name: '', _classID: match[1], typeID: match[1], enumValue: match[2] };
|
|
858
852
|
} else {
|
|
859
|
-
return { type: 'name',
|
|
853
|
+
return { type: 'name', _name: '', _classID: 'Lyr ', value };
|
|
860
854
|
}
|
|
861
855
|
} else if (typeof value === 'number') {
|
|
862
856
|
return { type: 'Idnt', value };
|
|
863
857
|
} else if (value && typeof value === 'object' && typeof value.classID === 'string') {
|
|
864
|
-
return { type: 'Clss',
|
|
858
|
+
return { type: 'Clss', _name: value.name || '', _classID: value.classID };
|
|
865
859
|
} else {
|
|
866
860
|
throw new Error(`Invalid descriptor reference value: ${JSON.stringify(value)}`);
|
|
867
861
|
}
|
|
@@ -869,46 +863,39 @@ function guessReferenceItem(value: any): DescriptorReferenceItem {
|
|
|
869
863
|
|
|
870
864
|
// path selection state (image resource 1088) is a reference to paths, enumerated references there use 'Path' class
|
|
871
865
|
// (name references use 'Lyr ' class, they reference layer of the vector mask)
|
|
872
|
-
export function pathSelectionStateToReference(items: string[]):
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
return createDescriptorReference(items.map((value, i) => {
|
|
876
|
-
if (referenceItemMatches(infos?.[i], value)) return infos![i]; // keep information read from the file
|
|
877
|
-
|
|
866
|
+
export function pathSelectionStateToReference(items: string[]): DescriptorReferenceItem[] {
|
|
867
|
+
return items.map(value => {
|
|
878
868
|
const item = guessReferenceItem(value);
|
|
879
|
-
if (item.type === 'Enmr') item.
|
|
869
|
+
if (item.type === 'Enmr') item._classID = 'Path';
|
|
880
870
|
return item;
|
|
881
|
-
})
|
|
871
|
+
});
|
|
882
872
|
}
|
|
883
873
|
|
|
884
874
|
function writeReferenceStructure(writer: PsdWriter, _key: string, items: any[]) {
|
|
885
875
|
if (!Array.isArray(items)) throw new Error(`Invalid reference value: ${JSON.stringify(items)}, key: ${_key}`);
|
|
886
876
|
|
|
887
|
-
const infos: DescriptorReferenceItem[] | undefined = getHidden(items, referenceInfoKey);
|
|
888
877
|
writeInt32(writer, items.length);
|
|
889
878
|
|
|
890
|
-
for (
|
|
891
|
-
const
|
|
892
|
-
const recorded = infos?.[i];
|
|
893
|
-
const item = referenceItemMatches(recorded, value) ? recorded! : guessReferenceItem(value);
|
|
879
|
+
for (const value of items) {
|
|
880
|
+
const item = isReferenceItem(value) ? value : guessReferenceItem(value);
|
|
894
881
|
|
|
895
882
|
writeSignature(writer, item.type);
|
|
896
883
|
|
|
897
884
|
switch (item.type) {
|
|
898
885
|
case 'prop': // Property
|
|
899
|
-
writeClassStructure(writer, item.
|
|
886
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
900
887
|
writeAsciiStringOrClassId(writer, item.keyID!);
|
|
901
888
|
break;
|
|
902
889
|
case 'Clss': // Class
|
|
903
|
-
writeClassStructure(writer, item.
|
|
890
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
904
891
|
break;
|
|
905
892
|
case 'Enmr': // Enumerated Reference
|
|
906
|
-
writeClassStructure(writer, item.
|
|
893
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
907
894
|
writeAsciiStringOrClassId(writer, item.typeID!);
|
|
908
895
|
writeAsciiStringOrClassId(writer, item.enumValue!);
|
|
909
896
|
break;
|
|
910
897
|
case 'rele': // Offset
|
|
911
|
-
writeClassStructure(writer, item.
|
|
898
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
912
899
|
writeUint32(writer, item.value as number);
|
|
913
900
|
break;
|
|
914
901
|
case 'Idnt': // Identifier
|
|
@@ -916,7 +903,7 @@ function writeReferenceStructure(writer: PsdWriter, _key: string, items: any[])
|
|
|
916
903
|
writeInt32(writer, item.value as number);
|
|
917
904
|
break;
|
|
918
905
|
case 'name': // Name
|
|
919
|
-
writeClassStructure(writer, item.
|
|
906
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
920
907
|
writeUnicodeStringWithPadding(writer, `${item.value}`);
|
|
921
908
|
break;
|
|
922
909
|
default:
|
|
@@ -1006,8 +993,8 @@ export type DesciptorGradient = {
|
|
|
1006
993
|
GrdF: 'GrdF.CstS';
|
|
1007
994
|
Intr: number;
|
|
1008
995
|
Clrs: {
|
|
1009
|
-
'Clr '
|
|
1010
|
-
Type
|
|
996
|
+
'Clr '?: DescriptorColor;
|
|
997
|
+
Type?: string; // 'Clry.UsrS' | 'Clry.FrgC' | 'Clry.BckC'
|
|
1011
998
|
Lctn: number;
|
|
1012
999
|
Mdpn: number;
|
|
1013
1000
|
}[];
|
|
@@ -1047,7 +1034,9 @@ export interface DescriptorGradientContent {
|
|
|
1047
1034
|
export interface DescriptorPatternContent {
|
|
1048
1035
|
Ptrn: DesciptorPattern;
|
|
1049
1036
|
Lnkd?: boolean;
|
|
1050
|
-
phase?: { Hrzn: number; Vrtc: number; };
|
|
1037
|
+
phase?: { Hrzn: number | DescriptorUnitsValue; Vrtc: number | DescriptorUnitsValue; };
|
|
1038
|
+
'Scl '?: DescriptorUnitsValue;
|
|
1039
|
+
Angl?: DescriptorUnitsValue;
|
|
1051
1040
|
}
|
|
1052
1041
|
|
|
1053
1042
|
export type DescriptorVectorContent = DescriptorColorContent | DescriptorGradientContent | DescriptorPatternContent;
|
|
@@ -1343,7 +1332,7 @@ function parseFxObject(fx: EffectDescriptor) {
|
|
|
1343
1332
|
fillType: FrFl.decode(fx.PntT!),
|
|
1344
1333
|
blendMode: BlnM.decode(fx['Md ']!),
|
|
1345
1334
|
opacity: parsePercent(fx.Opct),
|
|
1346
|
-
size:
|
|
1335
|
+
size: parseUnitsOrNumber(fx['Sz '] as DescriptorUnitsValue | number), // stored as number (pixels) in old versions
|
|
1347
1336
|
};
|
|
1348
1337
|
|
|
1349
1338
|
if (fx.present !== undefined) stroke.present = fx.present;
|
|
@@ -1373,7 +1362,7 @@ function serializeFxObject(stroke: LayerEffectStroke) {
|
|
|
1373
1362
|
return FrFX;
|
|
1374
1363
|
}
|
|
1375
1364
|
|
|
1376
|
-
export function serializeEffects(e: LayerEffectsInfo,
|
|
1365
|
+
export function serializeEffects(e: LayerEffectsInfo, writer: PsdWriter, multi: boolean) {
|
|
1377
1366
|
const info: Lfx2Descriptor & LmfxDescriptor = multi ? {
|
|
1378
1367
|
'Scl ': unitsPercentF(e.scale ?? 1),
|
|
1379
1368
|
masterFXSwitch: !e.disabled,
|
|
@@ -1390,20 +1379,20 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1390
1379
|
const useMulti = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length > 1 && multi;
|
|
1391
1380
|
const useSingle = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length >= 1 && (!multi || arr.length === 1);
|
|
1392
1381
|
|
|
1393
|
-
if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow',
|
|
1394
|
-
if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow',
|
|
1395
|
-
if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow',
|
|
1396
|
-
if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow',
|
|
1397
|
-
if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow',
|
|
1398
|
-
if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill',
|
|
1399
|
-
if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay',
|
|
1382
|
+
if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow', writer);
|
|
1383
|
+
if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow', writer));
|
|
1384
|
+
if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow', writer);
|
|
1385
|
+
if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow', writer));
|
|
1386
|
+
if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow', writer);
|
|
1387
|
+
if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill', writer));
|
|
1388
|
+
if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay', writer));
|
|
1400
1389
|
if (useMulti(e.stroke)) info.frameFXMulti = e.stroke.map(i => serializeFxObject(i));
|
|
1401
|
-
if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow',
|
|
1402
|
-
if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel',
|
|
1403
|
-
if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill',
|
|
1404
|
-
if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay',
|
|
1405
|
-
if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay',
|
|
1406
|
-
if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin',
|
|
1390
|
+
if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow', writer);
|
|
1391
|
+
if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel', writer);
|
|
1392
|
+
if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill', writer);
|
|
1393
|
+
if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay', writer);
|
|
1394
|
+
if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay', writer);
|
|
1395
|
+
if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin', writer);
|
|
1407
1396
|
if (useSingle(e.stroke)) info.FrFX = serializeFxObject(e.stroke?.[0]);
|
|
1408
1397
|
|
|
1409
1398
|
if (multi) {
|
|
@@ -1426,31 +1415,31 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1426
1415
|
return info;
|
|
1427
1416
|
}
|
|
1428
1417
|
|
|
1429
|
-
export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor,
|
|
1418
|
+
export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, reader: PsdReader) {
|
|
1430
1419
|
const effects: LayerEffectsInfo = {};
|
|
1431
|
-
const { masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX, ...rest } = info;
|
|
1420
|
+
const { _name, _classID, 'Scl ': scale, masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX, ...rest } = info as typeof info & { _name?: string; _classID?: string; };
|
|
1432
1421
|
if (!masterFXSwitch) effects.disabled = true;
|
|
1433
|
-
if (
|
|
1434
|
-
if (DrSh) effects.dropShadow = [parseEffectObject(DrSh,
|
|
1435
|
-
if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i,
|
|
1436
|
-
if (IrSh) effects.innerShadow = [parseEffectObject(IrSh,
|
|
1437
|
-
if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i,
|
|
1438
|
-
if (OrGl) effects.outerGlow = parseEffectObject(OrGl,
|
|
1439
|
-
if (IrGl) effects.innerGlow = parseEffectObject(IrGl,
|
|
1440
|
-
if (ebbl) effects.bevel = parseEffectObject(ebbl,
|
|
1441
|
-
if (SoFi) effects.solidFill = [parseEffectObject(SoFi,
|
|
1442
|
-
if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i,
|
|
1443
|
-
if (patternFill) effects.patternOverlay = parseEffectObject(patternFill,
|
|
1444
|
-
if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl,
|
|
1445
|
-
if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i,
|
|
1446
|
-
if (ChFX) effects.satin = parseEffectObject(ChFX,
|
|
1422
|
+
if (scale) effects.scale = parsePercent(scale);
|
|
1423
|
+
if (DrSh) effects.dropShadow = [parseEffectObject(DrSh, reader)];
|
|
1424
|
+
if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i, reader));
|
|
1425
|
+
if (IrSh) effects.innerShadow = [parseEffectObject(IrSh, reader)];
|
|
1426
|
+
if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i, reader));
|
|
1427
|
+
if (OrGl) effects.outerGlow = parseEffectObject(OrGl, reader);
|
|
1428
|
+
if (IrGl) effects.innerGlow = parseEffectObject(IrGl, reader);
|
|
1429
|
+
if (ebbl) effects.bevel = parseEffectObject(ebbl, reader, 'bevel');
|
|
1430
|
+
if (SoFi) effects.solidFill = [parseEffectObject(SoFi, reader)];
|
|
1431
|
+
if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i, reader));
|
|
1432
|
+
if (patternFill) effects.patternOverlay = parseEffectObject(patternFill, reader);
|
|
1433
|
+
if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl, reader)];
|
|
1434
|
+
if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i, reader));
|
|
1435
|
+
if (ChFX) effects.satin = parseEffectObject(ChFX, reader);
|
|
1447
1436
|
if (FrFX) effects.stroke = [parseFxObject(FrFX)];
|
|
1448
1437
|
if (frameFXMulti) effects.stroke = frameFXMulti.map(i => parseFxObject(i));
|
|
1449
|
-
if (
|
|
1438
|
+
if (reader.logMissingFeatures && Object.keys(rest).length) reader.log('Unhandled effect keys:', rest);
|
|
1450
1439
|
return effects;
|
|
1451
1440
|
}
|
|
1452
1441
|
|
|
1453
|
-
function parseKeyList(keyList: TimelineKeyDescriptor[],
|
|
1442
|
+
function parseKeyList(keyList: TimelineKeyDescriptor[], reader: PsdReader) {
|
|
1454
1443
|
const keys: TimelineKey[] = [];
|
|
1455
1444
|
|
|
1456
1445
|
for (let j = 0; j < keyList.length; j++) {
|
|
@@ -1474,7 +1463,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
|
|
|
1474
1463
|
break;
|
|
1475
1464
|
case 'keyType.sheetStyle': {
|
|
1476
1465
|
const key: TimelineKey = { interpolation, time, selected, type: 'style' };
|
|
1477
|
-
if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx,
|
|
1466
|
+
if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx, reader);
|
|
1478
1467
|
keys.push(key);
|
|
1479
1468
|
break;
|
|
1480
1469
|
}
|
|
@@ -1492,7 +1481,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
|
|
|
1492
1481
|
return keys;
|
|
1493
1482
|
}
|
|
1494
1483
|
|
|
1495
|
-
function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
1484
|
+
function serializeKeyList(keys: TimelineKey[], writer: PsdWriter): TimelineKeyDescriptor[] {
|
|
1496
1485
|
const keyList: TimelineKeyDescriptor[] = [];
|
|
1497
1486
|
|
|
1498
1487
|
for (let j = 0; j < keys.length; j++) {
|
|
@@ -1513,7 +1502,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
|
1513
1502
|
break;
|
|
1514
1503
|
case 'style':
|
|
1515
1504
|
animKey = { Type: 'keyType.sheetStyle', sheetStyle: { Vrsn: 1, blendOptions: {} } };
|
|
1516
|
-
if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style,
|
|
1505
|
+
if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style, writer, false), blendOptions: {} };
|
|
1517
1506
|
break;
|
|
1518
1507
|
case 'globalLighting': {
|
|
1519
1508
|
animKey = { Type: 'keyType.globalLighting', gblA: key.globalAngle, globalAltitude: key.globalAltitude };
|
|
@@ -1528,7 +1517,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
|
|
|
1528
1517
|
return keyList;
|
|
1529
1518
|
}
|
|
1530
1519
|
|
|
1531
|
-
export function parseTrackList(trackList: TimelineTrackDescriptor[],
|
|
1520
|
+
export function parseTrackList(trackList: TimelineTrackDescriptor[], reader: PsdReader) {
|
|
1532
1521
|
const tracks: TimelineTrack[] = [];
|
|
1533
1522
|
|
|
1534
1523
|
for (let i = 0; i < trackList.length; i++) {
|
|
@@ -1536,14 +1525,14 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
|
|
|
1536
1525
|
const track: TimelineTrack = {
|
|
1537
1526
|
type: stdTrackID.decode(tr.trackID),
|
|
1538
1527
|
enabled: tr.enab,
|
|
1539
|
-
keys: parseKeyList(tr.keyList,
|
|
1528
|
+
keys: parseKeyList(tr.keyList, reader),
|
|
1540
1529
|
};
|
|
1541
1530
|
|
|
1542
1531
|
if (tr.effectParams) {
|
|
1543
1532
|
track.effectParams = {
|
|
1544
1533
|
fillCanvas: tr.effectParams.fillCanvas,
|
|
1545
1534
|
zoomOrigin: tr.effectParams.zoomOrigin,
|
|
1546
|
-
keys: parseKeyList(tr.effectParams.keyList,
|
|
1535
|
+
keys: parseKeyList(tr.effectParams.keyList, reader),
|
|
1547
1536
|
};
|
|
1548
1537
|
}
|
|
1549
1538
|
|
|
@@ -1553,7 +1542,7 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
|
|
|
1553
1542
|
return tracks;
|
|
1554
1543
|
}
|
|
1555
1544
|
|
|
1556
|
-
export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescriptor[] {
|
|
1545
|
+
export function serializeTrackList(tracks: TimelineTrack[], writer: PsdWriter): TimelineTrackDescriptor[] {
|
|
1557
1546
|
const trackList: TimelineTrackDescriptor[] = [];
|
|
1558
1547
|
|
|
1559
1548
|
for (let i = 0; i < tracks.length; i++) {
|
|
@@ -1565,12 +1554,12 @@ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescri
|
|
|
1565
1554
|
Effc: !!t.effectParams,
|
|
1566
1555
|
...(t.effectParams ? {
|
|
1567
1556
|
effectParams: {
|
|
1568
|
-
keyList: serializeKeyList(t.effectParams.keys),
|
|
1557
|
+
keyList: serializeKeyList(t.effectParams.keys, writer),
|
|
1569
1558
|
fillCanvas: t.effectParams.fillCanvas,
|
|
1570
1559
|
zoomOrigin: t.effectParams.zoomOrigin,
|
|
1571
1560
|
}
|
|
1572
1561
|
} : {}),
|
|
1573
|
-
keyList: serializeKeyList(t.keys),
|
|
1562
|
+
keyList: serializeKeyList(t.keys, writer),
|
|
1574
1563
|
});
|
|
1575
1564
|
}
|
|
1576
1565
|
|
|
@@ -1581,7 +1570,7 @@ type AllEffects = LayerEffectShadow & LayerEffectsOuterGlow & LayerEffectStroke
|
|
|
1581
1570
|
LayerEffectInnerGlow & LayerEffectBevel & LayerEffectSolidFill &
|
|
1582
1571
|
LayerEffectPatternOverlay & LayerEffectSatin & LayerEffectGradientOverlay;
|
|
1583
1572
|
|
|
1584
|
-
function parseEffectObject(obj: any,
|
|
1573
|
+
function parseEffectObject(obj: any, reader: PsdReader, objName?: string) {
|
|
1585
1574
|
const result: AllEffects = {} as any;
|
|
1586
1575
|
|
|
1587
1576
|
for (const key of Object.keys(obj)) {
|
|
@@ -1608,7 +1597,10 @@ function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
|
1608
1597
|
case 'GlwT': result.technique = BETE.decode(val) as any; break;
|
|
1609
1598
|
case 'glwS': result.source = IGSr.decode(val); break;
|
|
1610
1599
|
case 'Type': result.type = GrdT.decode(val); break;
|
|
1611
|
-
case 'gs99':
|
|
1600
|
+
case 'gs99': // gradient overlay
|
|
1601
|
+
case 'gradientsInterpolationMethod': // glows
|
|
1602
|
+
result.interpolationMethod = gradientInterpolationMethodType.decode(val);
|
|
1603
|
+
break;
|
|
1612
1604
|
case 'Opct': result.opacity = parsePercent(val); break;
|
|
1613
1605
|
case 'hglO': result.highlightOpacity = parsePercent(val); break;
|
|
1614
1606
|
case 'sdwO': result.shadowOpacity = parsePercent(val); break;
|
|
@@ -1625,15 +1617,23 @@ function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
|
1625
1617
|
case 'Dstn': result.distance = parseUnits(val); break;
|
|
1626
1618
|
case 'Scl ': result.scale = parsePercent(val); break;
|
|
1627
1619
|
case 'Ptrn': result.pattern = { name: val['Nm '], id: val.Idnt }; break;
|
|
1628
|
-
case 'phase': result.phase =
|
|
1620
|
+
case 'phase': result.phase = parsePhase(val); break;
|
|
1629
1621
|
case 'Ofst': result.offset = { x: parsePercent(val.Hrzn), y: parsePercent(val.Vrtc) }; break;
|
|
1630
1622
|
case 'MpgS':
|
|
1631
|
-
case 'TrnS':
|
|
1632
|
-
|
|
1623
|
+
case 'TrnS': {
|
|
1624
|
+
const contour: EffectContour = {
|
|
1633
1625
|
name: val['Nm '],
|
|
1634
1626
|
curve: (val['Crv '] as any[]).map(p => ({ x: p.Hrzn, y: p.Vrtc })),
|
|
1635
1627
|
};
|
|
1628
|
+
|
|
1629
|
+
// bevel can have both: gloss contour (TrnS) and contour of the shape (MpgS)
|
|
1630
|
+
if (objName === 'bevel' && key === 'MpgS') {
|
|
1631
|
+
result.shapeContour = contour;
|
|
1632
|
+
} else {
|
|
1633
|
+
result.contour = contour;
|
|
1634
|
+
}
|
|
1636
1635
|
break;
|
|
1636
|
+
}
|
|
1637
1637
|
case 'Grad': result.gradient = parseGradient(val); break;
|
|
1638
1638
|
case 'useTexture':
|
|
1639
1639
|
case 'useShape':
|
|
@@ -1645,14 +1645,14 @@ function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
|
1645
1645
|
case '_classID':
|
|
1646
1646
|
break;
|
|
1647
1647
|
default:
|
|
1648
|
-
|
|
1648
|
+
reader.logMissingFeatures && reader.log(`Invalid effect key: '${key}', value:`, val);
|
|
1649
1649
|
}
|
|
1650
1650
|
}
|
|
1651
1651
|
|
|
1652
1652
|
return result;
|
|
1653
1653
|
}
|
|
1654
1654
|
|
|
1655
|
-
function serializeEffectObject(obj: any, objName: string,
|
|
1655
|
+
function serializeEffectObject(obj: any, objName: string, writer: PsdWriter) {
|
|
1656
1656
|
const result: any = {
|
|
1657
1657
|
enab: false,
|
|
1658
1658
|
};
|
|
@@ -1691,7 +1691,9 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1691
1691
|
break;
|
|
1692
1692
|
case 'source': result.glwS = IGSr.encode(val); break;
|
|
1693
1693
|
case 'type': result.Type = GrdT.encode(val); break;
|
|
1694
|
-
case 'interpolationMethod':
|
|
1694
|
+
case 'interpolationMethod':
|
|
1695
|
+
result[(objName === 'outerGlow' || objName === 'innerGlow') ? 'gradientsInterpolationMethod' : 'gs99'] = gradientInterpolationMethodType.encode(val);
|
|
1696
|
+
break;
|
|
1695
1697
|
case 'opacity': result.Opct = unitsPercent(val); break;
|
|
1696
1698
|
case 'highlightOpacity': result.hglO = unitsPercent(val); break;
|
|
1697
1699
|
case 'shadowOpacity': result.sdwO = unitsPercent(val); break;
|
|
@@ -1714,14 +1716,9 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1714
1716
|
case 'scale': result['Scl '] = unitsPercent(val); break;
|
|
1715
1717
|
case 'pattern': result.Ptrn = { 'Nm ': val.name, Idnt: val.id }; break;
|
|
1716
1718
|
case 'phase': result.phase = { Hrzn: val.x, Vrtc: val.y }; break;
|
|
1717
|
-
case 'offset': result.Ofst = { Hrzn:
|
|
1718
|
-
case 'contour':
|
|
1719
|
-
|
|
1720
|
-
'Nm ': (val as EffectContour).name,
|
|
1721
|
-
'Crv ': (val as EffectContour).curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
|
|
1722
|
-
};
|
|
1723
|
-
break;
|
|
1724
|
-
}
|
|
1719
|
+
case 'offset': result.Ofst = { Hrzn: unitsPercentF(val.x), Vrtc: unitsPercentF(val.y) }; break;
|
|
1720
|
+
case 'contour': result[objName === 'satin' ? 'MpgS' : 'TrnS'] = serializeContour(val); break;
|
|
1721
|
+
case 'shapeContour': result.MpgS = serializeContour(val); break;
|
|
1725
1722
|
case 'gradient': result.Grad = serializeGradient(val); break;
|
|
1726
1723
|
case 'useTexture':
|
|
1727
1724
|
case 'useShape':
|
|
@@ -1732,13 +1729,20 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1732
1729
|
result[key] = val;
|
|
1733
1730
|
break;
|
|
1734
1731
|
default:
|
|
1735
|
-
|
|
1732
|
+
writer.logMissingFeatures && writer.log(`Invalid effect key: '${key}', value:`, val);
|
|
1736
1733
|
}
|
|
1737
1734
|
}
|
|
1738
1735
|
|
|
1739
1736
|
return result;
|
|
1740
1737
|
}
|
|
1741
1738
|
|
|
1739
|
+
function serializeContour(contour: EffectContour) {
|
|
1740
|
+
return {
|
|
1741
|
+
'Nm ': contour.name,
|
|
1742
|
+
'Crv ': contour.curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1742
1746
|
function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoiseGradient {
|
|
1743
1747
|
if (grad.GrdF === 'GrdF.CstS') {
|
|
1744
1748
|
// stop locations are always in 0..4096 range, independent of smoothness (Intr)
|
|
@@ -1748,11 +1752,22 @@ function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoi
|
|
|
1748
1752
|
type: 'solid',
|
|
1749
1753
|
name: grad['Nm '],
|
|
1750
1754
|
smoothness: grad.Intr / 4096,
|
|
1751
|
-
colorStops: grad.Clrs.map(s =>
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1755
|
+
colorStops: grad.Clrs.map(s => {
|
|
1756
|
+
const stop: ColorStop = {
|
|
1757
|
+
color: s['Clr '] ? parseColor(s['Clr ']) : { r: 0, g: 0, b: 0 },
|
|
1758
|
+
location: s.Lctn / samples,
|
|
1759
|
+
midpoint: s.Mdpn / 100,
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
if (s.Type) {
|
|
1763
|
+
try {
|
|
1764
|
+
const type = Clry.decode(s.Type);
|
|
1765
|
+
if (type !== 'user') stop.type = type;
|
|
1766
|
+
} catch { } // unknown stop type, read as user color
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
return stop;
|
|
1770
|
+
}),
|
|
1756
1771
|
opacityStops: grad.Trns.map(s => ({
|
|
1757
1772
|
opacity: parsePercent(s.Opct),
|
|
1758
1773
|
location: s.Lctn / samples,
|
|
@@ -1784,12 +1799,12 @@ function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): Des
|
|
|
1784
1799
|
Intr: Math.round((grad.smoothness ?? 1) * 4096),
|
|
1785
1800
|
Clrs: grad.colorStops.map(s => ({
|
|
1786
1801
|
'Clr ': serializeColor(s.color),
|
|
1787
|
-
Type:
|
|
1802
|
+
Type: Clry.encode(s.type ?? 'user'),
|
|
1788
1803
|
Lctn: Math.round(s.location * samples),
|
|
1789
1804
|
Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
|
|
1790
1805
|
})),
|
|
1791
1806
|
Trns: grad.opacityStops.map(s => ({
|
|
1792
|
-
Opct:
|
|
1807
|
+
Opct: unitsPercentF(s.opacity),
|
|
1793
1808
|
Lctn: Math.round(s.location * samples),
|
|
1794
1809
|
Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
|
|
1795
1810
|
})),
|
|
@@ -1827,13 +1842,23 @@ function parseGradientContent(descriptor: DescriptorGradientContent) {
|
|
|
1827
1842
|
return result;
|
|
1828
1843
|
}
|
|
1829
1844
|
|
|
1845
|
+
// phase is usually stored as plain numbers, but some files store it as unit values
|
|
1846
|
+
function parsePhase({ Hrzn, Vrtc }: { Hrzn: number | DescriptorUnitsValue; Vrtc: number | DescriptorUnitsValue; }) {
|
|
1847
|
+
return {
|
|
1848
|
+
x: typeof Hrzn === 'number' ? Hrzn : Hrzn.value,
|
|
1849
|
+
y: typeof Vrtc === 'number' ? Vrtc : Vrtc.value,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1830
1853
|
function parsePatternContent(descriptor: DescriptorPatternContent) {
|
|
1831
1854
|
const result: EffectPattern & ExtraPatternInfo = {
|
|
1832
1855
|
name: descriptor.Ptrn['Nm '],
|
|
1833
1856
|
id: descriptor.Ptrn.Idnt,
|
|
1834
1857
|
};
|
|
1835
1858
|
if (descriptor.Lnkd !== undefined) result.linked = descriptor.Lnkd;
|
|
1836
|
-
if (descriptor.phase !== undefined) result.phase =
|
|
1859
|
+
if (descriptor.phase !== undefined) result.phase = parsePhase(descriptor.phase);
|
|
1860
|
+
if (descriptor['Scl '] !== undefined) result.scale = parsePercent(descriptor['Scl ']);
|
|
1861
|
+
if (descriptor.Angl !== undefined) result.angle = parseAngle(descriptor.Angl);
|
|
1837
1862
|
return result;
|
|
1838
1863
|
}
|
|
1839
1864
|
|
|
@@ -1858,11 +1883,11 @@ function serializeGradientContent(content: (EffectSolidGradient | EffectNoiseGra
|
|
|
1858
1883
|
if (content.angle !== undefined) result.Angl = unitsAngle(content.angle);
|
|
1859
1884
|
result.Type = GrdT.encode(content.style);
|
|
1860
1885
|
if (content.align !== undefined) result.Algn = content.align;
|
|
1861
|
-
if (content.scale !== undefined) result['Scl '] =
|
|
1886
|
+
if (content.scale !== undefined) result['Scl '] = unitsPercentF(content.scale);
|
|
1862
1887
|
if (content.offset) {
|
|
1863
1888
|
result.Ofst = {
|
|
1864
|
-
Hrzn:
|
|
1865
|
-
Vrtc:
|
|
1889
|
+
Hrzn: unitsPercentF(content.offset.x),
|
|
1890
|
+
Vrtc: unitsPercentF(content.offset.y),
|
|
1866
1891
|
};
|
|
1867
1892
|
}
|
|
1868
1893
|
result.Grad = serializeGradient(content);
|
|
@@ -1878,6 +1903,8 @@ function serializePatternContent(content: EffectPattern & ExtraPatternInfo) {
|
|
|
1878
1903
|
};
|
|
1879
1904
|
if (content.linked !== undefined) result.Lnkd = !!content.linked;
|
|
1880
1905
|
if (content.phase !== undefined) result.phase = { Hrzn: content.phase.x, Vrtc: content.phase.y };
|
|
1906
|
+
if (content.scale !== undefined) result['Scl '] = unitsPercentF(content.scale);
|
|
1907
|
+
if (content.angle !== undefined) result.Angl = unitsAngle(content.angle);
|
|
1881
1908
|
return result;
|
|
1882
1909
|
}
|
|
1883
1910
|
|
|
@@ -1894,10 +1921,9 @@ export function serializeVectorContent(content: VectorContent): { descriptor: De
|
|
|
1894
1921
|
// Descriptor color values: HSB saturation/brightness 0..100, CMYK 0..100, Gray 0..100, Lab L 0..100, a/b -128..127
|
|
1895
1922
|
// 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
1923
|
|
|
1897
|
-
const colorSourceKey = typeof Symbol !== 'undefined' ? Symbol('agPsdColorSource') : '__agPsdColorSource';
|
|
1898
|
-
|
|
1899
1924
|
const colorConversions: { [key: string]: ((x: number) => number)[] | undefined; } = {
|
|
1900
1925
|
// key: [descriptor -> color, color -> descriptor]
|
|
1926
|
+
'H ': [x => x / 360, x => x * 360], // angle
|
|
1901
1927
|
Strt: [x => x / 100, x => x * 100],
|
|
1902
1928
|
Brgh: [x => x / 100, x => x * 100],
|
|
1903
1929
|
'Cyn ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
@@ -1926,38 +1952,63 @@ function colorToDescriptorValue(key: string, value: number) {
|
|
|
1926
1952
|
return result;
|
|
1927
1953
|
}
|
|
1928
1954
|
|
|
1929
|
-
function
|
|
1930
|
-
|
|
1931
|
-
if (keys.length !== Object.keys(b).length) return false;
|
|
1932
|
-
return keys.every(key => a[key] === b[key]);
|
|
1955
|
+
function isBookColor(color: any) {
|
|
1956
|
+
return color._classID === 'BkCl' || 'Bk ' in color || 'bookID' in color;
|
|
1933
1957
|
}
|
|
1934
1958
|
|
|
1935
1959
|
export function parseColor(color: DescriptorColor): Color {
|
|
1936
|
-
|
|
1960
|
+
const result = parseColorValues(color);
|
|
1961
|
+
|
|
1962
|
+
if (isBookColor(color)) {
|
|
1963
|
+
const book = color as any as { 'Bk '?: string; 'Nm '?: string; bookID?: number; bookKey?: Uint8Array | string; };
|
|
1964
|
+
const key = book.bookKey;
|
|
1965
|
+
result.book = {
|
|
1966
|
+
book: book['Bk '] ?? '',
|
|
1967
|
+
name: book['Nm '] ?? '',
|
|
1968
|
+
bookID: book.bookID ?? 0,
|
|
1969
|
+
bookKey: typeof key === 'string' ? key : (key ? String.fromCharCode(...Array.from(key)) : ''),
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
return result;
|
|
1974
|
+
}
|
|
1937
1975
|
|
|
1976
|
+
function parseColorValues(color: DescriptorColor): Color {
|
|
1938
1977
|
if ('H ' in color) {
|
|
1939
|
-
|
|
1978
|
+
return { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
|
|
1940
1979
|
} else if ('Rd ' in color) {
|
|
1941
1980
|
return { r: color['Rd '], g: color['Grn '], b: color['Bl '] };
|
|
1942
1981
|
} else if ('Cyn ' in color) {
|
|
1943
|
-
|
|
1982
|
+
return {
|
|
1944
1983
|
c: color['Cyn '] * 255 / 100,
|
|
1945
1984
|
m: color.Mgnt * 255 / 100,
|
|
1946
1985
|
y: color['Ylw '] * 255 / 100,
|
|
1947
1986
|
k: color.Blck * 255 / 100,
|
|
1948
1987
|
};
|
|
1949
1988
|
} else if ('Gry ' in color) {
|
|
1950
|
-
|
|
1989
|
+
return { k: color['Gry '] * 255 / 100 };
|
|
1951
1990
|
} else if ('Lmnc' in color) {
|
|
1952
|
-
|
|
1991
|
+
return { l: color.Lmnc / 100, a: normalizeLabAB(color['A ']), b: normalizeLabAB(color['B ']) };
|
|
1953
1992
|
} else if ('redFloat' in color) {
|
|
1954
1993
|
return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
|
|
1994
|
+
} else if (isBookColor(color)) {
|
|
1995
|
+
return { r: 0, g: 0, b: 0 }; // book (Pantone) color without color values
|
|
1955
1996
|
} else {
|
|
1956
1997
|
throw new Error('Unsupported color descriptor');
|
|
1957
1998
|
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
export function serializeColor(color: Color | undefined): DescriptorColor {
|
|
2002
|
+
const result = serializeColorValues(color);
|
|
2003
|
+
|
|
2004
|
+
if (color?.book) {
|
|
2005
|
+
const { book, name, bookID, bookKey } = color.book;
|
|
2006
|
+
const key = new Uint8Array(bookKey.length);
|
|
2007
|
+
for (let i = 0; i < key.length; i++) key[i] = bookKey.charCodeAt(i) & 0xff;
|
|
2008
|
+
Object.assign(result, { 'Bk ': book, 'Nm ': name, bookID, bookKey: key });
|
|
2009
|
+
}
|
|
1958
2010
|
|
|
1959
|
-
|
|
1960
|
-
return setHidden(result, colorSourceKey, { color: { ...result }, descriptor: color });
|
|
2011
|
+
return result;
|
|
1961
2012
|
}
|
|
1962
2013
|
|
|
1963
2014
|
function serializeColorValues(color: Color | undefined): DescriptorColor {
|
|
@@ -1970,7 +2021,7 @@ function serializeColorValues(color: Color | undefined): DescriptorColor {
|
|
|
1970
2021
|
} else if ('h' in color) {
|
|
1971
2022
|
return {
|
|
1972
2023
|
_name: '', _classID: 'HSBC',
|
|
1973
|
-
'H ': unitsAngle((color.h || 0)
|
|
2024
|
+
'H ': unitsAngle(colorToDescriptorValue('H ', color.h || 0)),
|
|
1974
2025
|
Strt: colorToDescriptorValue('Strt', color.s || 0),
|
|
1975
2026
|
Brgh: colorToDescriptorValue('Brgh', color.b || 0),
|
|
1976
2027
|
};
|
|
@@ -1996,22 +2047,6 @@ function serializeColorValues(color: Color | undefined): DescriptorColor {
|
|
|
1996
2047
|
}
|
|
1997
2048
|
}
|
|
1998
2049
|
|
|
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
|
-
|
|
2015
2050
|
export function parseAngle(x: DescriptorUnitsValue) {
|
|
2016
2051
|
if (x === undefined) return 0;
|
|
2017
2052
|
if (x.units !== 'Angle') throw new Error(`Invalid units: ${x.units}`);
|
|
@@ -2034,7 +2069,8 @@ export function parsePercentOrAngle(x: DescriptorUnitsValue | undefined) {
|
|
|
2034
2069
|
export function parseUnits({ units, value }: DescriptorUnitsValue): UnitsValue {
|
|
2035
2070
|
if (
|
|
2036
2071
|
units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
|
|
2037
|
-
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
|
|
2072
|
+
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
|
|
2073
|
+
units !== 'Distance'
|
|
2038
2074
|
) {
|
|
2039
2075
|
throw new Error(`Invalid units: ${JSON.stringify({ units, value })}`);
|
|
2040
2076
|
}
|
|
@@ -2076,7 +2112,8 @@ export function unitsValue(x: UnitsValue | undefined, key: string): DescriptorUn
|
|
|
2076
2112
|
|
|
2077
2113
|
if (
|
|
2078
2114
|
units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
|
|
2079
|
-
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
|
|
2115
|
+
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
|
|
2116
|
+
units !== 'Distance'
|
|
2080
2117
|
) {
|
|
2081
2118
|
throw new Error(`Invalid units in ${JSON.stringify(x)} (key: ${key})`);
|
|
2082
2119
|
}
|
|
@@ -2222,6 +2259,12 @@ export const gradientInterpolationMethodType = createEnum<InterpolationMethod>('
|
|
|
2222
2259
|
// TODO: stripes
|
|
2223
2260
|
});
|
|
2224
2261
|
|
|
2262
|
+
export const Clry = createEnum<'user' | 'foreground' | 'background'>('Clry', 'user', {
|
|
2263
|
+
user: 'UsrS',
|
|
2264
|
+
foreground: 'FrgC',
|
|
2265
|
+
background: 'BckC',
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2225
2268
|
export const ClrS = createEnum<'rgb' | 'hsb' | 'lab' | 'hsl'>('ClrS', 'rgb', {
|
|
2226
2269
|
rgb: 'RGBC',
|
|
2227
2270
|
hsb: 'HSBl',
|
|
@@ -2451,3 +2494,9 @@ export const presetKindType = createEnum<'custom' | 'default'>('presetKindType',
|
|
|
2451
2494
|
custom: 'presetKindCustom',
|
|
2452
2495
|
default: 'presetKindDefault',
|
|
2453
2496
|
});
|
|
2497
|
+
|
|
2498
|
+
export const patternColorHandling = createEnum<'convert' | 'avoid conversion' | 'luminance only'>('', 'convert', {
|
|
2499
|
+
'convert': 'conv',
|
|
2500
|
+
'avoid conversion': 'avod',
|
|
2501
|
+
'luminance only': 'lumi',
|
|
2502
|
+
});
|