ag-psd 31.0.3 → 33.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/03e19709 +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/2a19133e +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/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/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 +107 -0
- package/LICENSE +5 -0
- package/README.md +3 -6
- package/README_PSD.md +242 -81
- package/dist/abr.d.ts +11 -4
- package/dist/abr.js +363 -229
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +600 -364
- 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 +3096 -1541
- package/dist/descriptor.d.ts +25 -6
- package/dist/descriptor.js +380 -135
- 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 +6 -3
- package/dist/helpers.js +22 -6
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +529 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +30 -10
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +154 -18
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +12 -2
- package/dist/psdReader.js +720 -421
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +3 -0
- package/dist/psdWriter.js +167 -130
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.js +143 -29
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +36 -18
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +363 -229
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +600 -364
- 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 +25 -6
- package/dist-es/descriptor.js +380 -135
- 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 +6 -3
- package/dist-es/helpers.js +22 -6
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +529 -144
- 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 +30 -10
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +154 -18
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +12 -2
- package/dist-es/psdReader.js +720 -421
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +3 -0
- package/dist-es/psdWriter.js +167 -130
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.js +143 -29
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +36 -18
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/src/abr.ts +380 -232
- package/src/additionalInfo.ts +781 -477
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +386 -142
- package/src/effectsHelpers.ts +9 -9
- package/src/engineData.ts +36 -13
- package/src/engineData2.ts +468 -432
- package/src/helpers.ts +23 -5
- package/src/imageResources.ts +658 -167
- package/src/index.ts +1 -0
- package/src/jpeg.ts +27 -12
- package/src/psd.ts +158 -30
- package/src/psdReader.ts +732 -414
- package/src/psdWriter.ts +186 -132
- package/src/text.ts +139 -18
- package/src/utf8.ts +35 -21
package/src/descriptor.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createEnum } from './helpers';
|
|
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,
|
|
@@ -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 = {
|
|
@@ -147,6 +149,7 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
147
149
|
Clrs: makeType('', 'Clrt'),
|
|
148
150
|
Trns: makeType('', 'TrnS'),
|
|
149
151
|
keyDescriptorList: nullType,
|
|
152
|
+
keyRootDescriptorList: nullType,
|
|
150
153
|
solidFillMulti: makeType('', 'SoFi'),
|
|
151
154
|
gradientFillMulti: makeType('', 'GrFl'),
|
|
152
155
|
dropShadowMulti: makeType('', 'DrSh'),
|
|
@@ -174,21 +177,30 @@ const fieldToArrayExtType: ExtTypeDict = {
|
|
|
174
177
|
layerSettings: nullType,
|
|
175
178
|
list: nullType,
|
|
176
179
|
Adjs: makeType('', 'CrvA'),
|
|
180
|
+
vertices: makeType('', 'Pnt '),
|
|
181
|
+
warpedVertices: makeType('', 'Pnt '),
|
|
182
|
+
quads: makeType('', 'quadIndices'),
|
|
177
183
|
};
|
|
178
184
|
|
|
179
185
|
const typeToField: { [key: string]: string[]; } = {
|
|
180
186
|
'TEXT': [
|
|
181
|
-
'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName',
|
|
187
|
+
'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName', 'exportOption', 'scaleSettings',
|
|
188
|
+
'BackgroundImagePath', 'ImageSubfolderName',
|
|
182
189
|
'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
|
|
183
190
|
'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
|
|
184
|
-
'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath',
|
|
191
|
+
'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath', 'Bk ', 'pathUnicodeName',
|
|
185
192
|
],
|
|
186
193
|
'tdta': [
|
|
187
194
|
'EngineData', 'LUT3DFileData', 'indexArray', 'originalVertexArray', 'deformedVertexArray',
|
|
188
|
-
'LqMe',
|
|
195
|
+
'LqMe', 'bookKey',
|
|
189
196
|
],
|
|
190
197
|
'long': [
|
|
191
|
-
'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ',
|
|
198
|
+
'TextIndex', 'RndS', 'Mdpn', 'Smth', 'Lctn', 'strokeStyleVersion', 'LaID', 'Vrsn', 'Cnt ', 'bookID',
|
|
199
|
+
'cropRectBottom', 'cropRectLeft', 'cropRectRight', 'cropRectTop',
|
|
200
|
+
// save for web output settings
|
|
201
|
+
'Version', 'BackgroundColorBlue', 'BackgroundColorGreen', 'BackgroundColorRed', 'BackgroundColorState', 'AttributeCase', 'Encoding',
|
|
202
|
+
'DuplicateFileNameBehavior', 'ImageMapLocation', 'ImageMapType', 'Indent', 'LineEndings', 'SpacersEmptyCells',
|
|
203
|
+
'SpacersHorizontal', 'SpacersVertical', 'StylesFormat', 'TDWidthHeight', 'TagCase', 'ColorPolicy', 'MetadataPolicy',
|
|
192
204
|
'Brgh', 'Cntr', 'means', 'vibrance', 'Strt', 'bwPresetKind', 'comp', 'compID', 'originalCompID',
|
|
193
205
|
'curvesPresetKind', 'mixerPresetKind', 'uOrder', 'vOrder', 'PgNm', 'totalPages', 'Crop',
|
|
194
206
|
'numerator', 'denominator', 'frameCount', 'Annt', 'keyOriginType', 'unitValueQuadVersion',
|
|
@@ -200,6 +212,7 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
200
212
|
'meshExpansion', 'meshRigidity', 'VrsM', 'VrsN', 'NmbG', 'WLMn', 'WLMx', 'AmMn', 'AmMx', 'SclH', 'SclV',
|
|
201
213
|
'Lvl ', 'TlNm', 'TlOf', 'FlRs', 'Thsh', 'ShrS', 'ShrE', 'FlRs', 'Vrnc', 'Strg', 'ExtS', 'ExtD',
|
|
202
214
|
'HrzS', 'VrtS', 'NmbR', 'EdgF', 'Ang1', 'Ang2', 'Ang3', 'Ang4', 'lastAppliedComp', 'capturedInfo',
|
|
215
|
+
'pixelSourceType',
|
|
203
216
|
],
|
|
204
217
|
'enum': [
|
|
205
218
|
'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS', 'BlrQ',
|
|
@@ -209,7 +222,8 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
209
222
|
'tableOrder', 'enableCompCore', 'enableCompCoreGPU', 'compCoreSupport', 'compCoreGPUSupport', 'Engn',
|
|
210
223
|
'enableCompCoreThreads', 'gs99', 'FrDs', 'trackID', 'animInterpStyle', 'horzAlign',
|
|
211
224
|
'vertAlign', 'bgColorType', 'shapeOperation', 'UndA', 'Wvtp', 'Drct', 'WndM', 'Edg ', 'FlCl', 'IntE',
|
|
212
|
-
'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod',
|
|
225
|
+
'IntC', 'Cnvr', 'Fl ', 'Dstr', 'MztT', 'Lns ', 'ExtT', 'DspM', 'ExtR', 'ZZTy', 'SphM', 'SmBQ', 'placedLayerOCIOConversion', 'gradientsInterpolationMethod', 'pathSymmetryMode',
|
|
226
|
+
'interpretAlpha',
|
|
213
227
|
],
|
|
214
228
|
'bool': [
|
|
215
229
|
'PstS', 'printSixteenBit', 'masterFXSwitch', 'enab', 'uglg', 'antialiasGloss',
|
|
@@ -222,14 +236,15 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
222
236
|
'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
|
|
223
237
|
'cellTextIsHTML', 'Smoo', 'Clsp', 'validAtPosition', 'rigidType', 'hasoptions', 'filterMaskEnable',
|
|
224
238
|
'filterMaskLinked', 'filterMaskExtendWithWhite', 'removeJPEGArtifact', 'Mnch', 'ExtF', 'ExtM',
|
|
225
|
-
'moreAccurate', 'GpuY', 'LIWy', 'Cnty',
|
|
239
|
+
'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
|
|
226
240
|
],
|
|
227
241
|
'doub': [
|
|
228
|
-
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght',
|
|
242
|
+
'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght', 'Gmm ',
|
|
229
243
|
'strokeStyleMiterLimit', 'strokeStyleResolution', 'layerTime', 'keyOriginResolution',
|
|
230
244
|
'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
|
|
231
245
|
'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
|
|
232
|
-
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3'
|
|
246
|
+
'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3',
|
|
247
|
+
'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity',
|
|
233
248
|
],
|
|
234
249
|
'UntF': [
|
|
235
250
|
'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
|
|
@@ -238,31 +253,59 @@ const typeToField: { [key: string]: string[]; } = {
|
|
|
238
253
|
'topRight', 'topLeft', 'bottomLeft', 'bottomRight', 'ClNs', 'Shrp',
|
|
239
254
|
],
|
|
240
255
|
'VlLs': [
|
|
241
|
-
'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
|
|
256
|
+
'Crv ', 'Clrs', 'Mnm ', 'Mxm ', 'Trns', 'pathList', 'keyRootDescriptorList', 'strokeStyleLineDashSet', 'FrLs', 'slices',
|
|
242
257
|
'LaSt', 'Trnf', 'nonAffineTransform', 'keyDescriptorList', 'guideIndeces', 'gradientFillMulti',
|
|
243
258
|
'solidFillMulti', 'frameFXMulti', 'innerShadowMulti', 'dropShadowMulti', 'FrIn', 'FSts', 'FsFr',
|
|
244
259
|
'sheetTimelineOptions', 'audioClipList', 'trackList', 'globalTrackList', 'keyList', 'audioClipList',
|
|
245
260
|
'warpValues', 'selectedPin', 'Pts ', 'SbpL', 'pathComponents', 'pinOffsets', 'posFinalPins',
|
|
246
261
|
'pinVertexIndices', 'PinP', 'PnRt', 'PnOv', 'PnDp', 'filterFXList', 'puppetShapeList', 'ShrP',
|
|
247
262
|
'channelDenoise', 'Mtrx', 'layerSettings', 'list', 'compList', 'Adjs',
|
|
263
|
+
'vertices', 'warpedVertices', 'quads', 'indices', 'Mpng',
|
|
248
264
|
],
|
|
249
265
|
'ObAr': ['meshPoints', 'quiltSliceX', 'quiltSliceY'],
|
|
250
266
|
'obj ': ['null', 'Chnl'],
|
|
251
267
|
'Pth ': ['DspF'],
|
|
268
|
+
'alis': ['alis'],
|
|
252
269
|
};
|
|
253
270
|
|
|
254
271
|
const channels = [
|
|
255
272
|
'Rd ', 'Grn ', 'Bl ', 'Yllw', 'Ylw ', 'Cyn ', 'Mgnt', 'Blck', 'Gry ', 'Lmnc', 'A ', 'B ',
|
|
256
273
|
];
|
|
257
274
|
|
|
275
|
+
// color classes whose channel values are written as doubles ('GRYC' and 'LABC' are kept for backwards compatibility)
|
|
276
|
+
const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
|
|
277
|
+
|
|
278
|
+
// fallback for unknown keys, based on the type of the value
|
|
279
|
+
function guessOSType(value: any): string | undefined {
|
|
280
|
+
if (typeof value === 'boolean') return 'bool';
|
|
281
|
+
if (typeof value === 'number' && !Number.isInteger(value)) return 'doub';
|
|
282
|
+
if (Array.isArray(value)) return 'VlLs';
|
|
283
|
+
|
|
284
|
+
if (value && typeof value === 'object') {
|
|
285
|
+
if (typeof value._classID === 'string') return 'Objc';
|
|
286
|
+
if (typeof value.classID === 'string') return 'type'; // class read from 'type' or 'GlbC'
|
|
287
|
+
if (typeof value.low === 'number' && typeof value.high === 'number') return 'comp';
|
|
288
|
+
if (typeof value.units === 'string' && typeof value.value === 'number') return 'UntF';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
258
294
|
const fieldToArrayType: Dict = {
|
|
259
295
|
'Mnm ': 'long',
|
|
260
296
|
'Mxm ': 'long',
|
|
297
|
+
HtmlFileNameComponents: 'long', // save for web output settings
|
|
298
|
+
SavingFileNameComponents: 'long',
|
|
299
|
+
SliceFileNameComponents: 'long',
|
|
300
|
+
Inpt: 'long', // levels filter
|
|
301
|
+
Otpt: 'long', // levels filter
|
|
261
302
|
FrLs: 'long',
|
|
262
303
|
strokeStyleLineDashSet: 'UntF',
|
|
263
304
|
Trnf: 'doub',
|
|
264
305
|
nonAffineTransform: 'doub',
|
|
265
306
|
keyDescriptorList: 'Objc',
|
|
307
|
+
keyRootDescriptorList: 'Objc',
|
|
308
|
+
pathList: 'Objc',
|
|
266
309
|
gradientFillMulti: 'Objc',
|
|
267
310
|
solidFillMulti: 'Objc',
|
|
268
311
|
frameFXMulti: 'Objc',
|
|
@@ -294,6 +337,8 @@ const fieldToArrayType: Dict = {
|
|
|
294
337
|
Mtrx: 'long',
|
|
295
338
|
compList: 'long',
|
|
296
339
|
Chnl: 'enum',
|
|
340
|
+
indices: 'long',
|
|
341
|
+
Mpng: 'long',
|
|
297
342
|
};
|
|
298
343
|
|
|
299
344
|
const fieldToType: Dict = {};
|
|
@@ -312,6 +357,8 @@ for (const field of Object.keys(fieldToArrayExtType)) {
|
|
|
312
357
|
fieldToArrayType[field] = 'Objc';
|
|
313
358
|
}
|
|
314
359
|
|
|
360
|
+
const pixelSourceOriginRoot = 'PixelSource.origin';
|
|
361
|
+
|
|
315
362
|
function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
316
363
|
if (key === 'presetKind') {
|
|
317
364
|
return typeof value === 'string' ? 'enum' : 'long';
|
|
@@ -325,7 +372,7 @@ function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
|
325
372
|
return typeof value === 'string' ? 'enum' : 'long';
|
|
326
373
|
} else if (key === 'AntA') {
|
|
327
374
|
return typeof value === 'string' ? 'enum' : 'bool';
|
|
328
|
-
} else if ((key === 'Hrzn' || key === 'Vrtc') && (parent.Type === 'keyType.Pstn' || parent._classID === 'Ofst')) {
|
|
375
|
+
} else if ((key === 'Hrzn' || key === 'Vrtc') && (parent.Type === 'keyType.Pstn' || parent._classID === 'Ofst' || root === pixelSourceOriginRoot)) {
|
|
329
376
|
return 'long';
|
|
330
377
|
} else if (key === 'Hrzn' || key === 'Vrtc' || key === 'Top ' || key === 'Left' || key === 'Btom' || key === 'Rght') {
|
|
331
378
|
if (root === 'slices') return 'long';
|
|
@@ -336,6 +383,18 @@ function getTypeByKey(key: string, value: any, root: string, parent: any) {
|
|
|
336
383
|
return root === 'artd' ? 'long' : 'doub';
|
|
337
384
|
} else if (key === 'Trnf') {
|
|
338
385
|
return Array.isArray(value) ? 'VlLs' : 'Objc';
|
|
386
|
+
} else if (key === 'alpha' && root === 'slices') {
|
|
387
|
+
return 'long';
|
|
388
|
+
} else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'HsbP') {
|
|
389
|
+
return 'enum';
|
|
390
|
+
} else if ((key === 'Inpt' || key === 'Otpt') && parent._classID === 'LvlA') {
|
|
391
|
+
return 'VlLs';
|
|
392
|
+
} else if (key === 'Intr' && typeof value === 'boolean') {
|
|
393
|
+
return 'bool'; // interpolation in print style
|
|
394
|
+
} else if (key === 'PgPs') {
|
|
395
|
+
return 'enum';
|
|
396
|
+
} else if (key === 'LghD' && parent._classID === 'oilPaint') {
|
|
397
|
+
return 'long';
|
|
339
398
|
} else {
|
|
340
399
|
return fieldToType[key];
|
|
341
400
|
}
|
|
@@ -346,8 +405,8 @@ export function readAsciiStringOrClassId(reader: PsdReader) {
|
|
|
346
405
|
return readAsciiString(reader, length || 4);
|
|
347
406
|
}
|
|
348
407
|
|
|
349
|
-
function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
|
|
350
|
-
if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list') {
|
|
408
|
+
export function writeAsciiStringOrClassId(writer: PsdWriter, value: string) {
|
|
409
|
+
if (value.length === 4 && value !== 'warp' && value !== 'time' && value !== 'hold' && value !== 'list' && value !== 'json') {
|
|
351
410
|
// write classId
|
|
352
411
|
writeInt32(writer, 0);
|
|
353
412
|
writeSignature(writer, value);
|
|
@@ -379,30 +438,41 @@ export function readDescriptorStructure(reader: PsdReader, includeClass: boolean
|
|
|
379
438
|
return object;
|
|
380
439
|
}
|
|
381
440
|
|
|
441
|
+
export const outputSettingsRoot = 'outputSettings';
|
|
442
|
+
|
|
382
443
|
export function writeDescriptorStructure(writer: PsdWriter, name: string, classId: string, value: any, root: string) {
|
|
383
444
|
if (logErrors && !classId) console.log('Missing classId for: ', name, classId, value);
|
|
384
445
|
|
|
385
446
|
// write class structure
|
|
386
447
|
writeUnicodeStringWithPadding(writer, name);
|
|
387
|
-
writeAsciiStringOrClassId(writer, classId);
|
|
388
448
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
449
|
+
if (root === outputSettingsRoot && classId === 'null') {
|
|
450
|
+
// Photoshop writes 'null' class id as 4 character string in save for web output settings
|
|
451
|
+
writeInt32(writer, 4);
|
|
452
|
+
writeSignature(writer, classId);
|
|
453
|
+
} else {
|
|
454
|
+
writeAsciiStringOrClassId(writer, classId);
|
|
455
|
+
}
|
|
393
456
|
|
|
394
|
-
|
|
457
|
+
const allKeys = Object.keys(value);
|
|
458
|
+
const keys = allKeys.filter(key => key !== '_name' && key !== '_classID' && value[key] !== undefined);
|
|
395
459
|
|
|
396
|
-
|
|
397
|
-
if (key === '_name' || key === '_classID') continue;
|
|
460
|
+
writeUint32(writer, keys.length);
|
|
398
461
|
|
|
399
|
-
|
|
462
|
+
for (const key of keys) {
|
|
463
|
+
let type: string | undefined = getTypeByKey(key, value[key], root, value);
|
|
464
|
+
let childRoot = root;
|
|
400
465
|
let extType = fieldToExtType[key];
|
|
401
466
|
|
|
402
467
|
if (key === 'bounds' && root === 'text') {
|
|
403
468
|
extType = makeType('', 'bounds');
|
|
404
469
|
} else if (key === 'origin') {
|
|
405
470
|
type = root === 'slices' ? 'enum' : 'Objc';
|
|
471
|
+
|
|
472
|
+
if (classId === 'PixelSource') {
|
|
473
|
+
extType = nullType; // stored as 'null' class with integer Hrzn/Vrtc
|
|
474
|
+
childRoot = pixelSourceOriginRoot;
|
|
475
|
+
}
|
|
406
476
|
} else if ((key === 'Cyn ' || key === 'Mgnt' || key === 'Ylw ' || key === 'Blck') && value._classID === 'CMYC') {
|
|
407
477
|
type = 'doub';
|
|
408
478
|
} else if (/^PN[a-z][a-z]$/.test(key)) {
|
|
@@ -449,9 +519,12 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
449
519
|
type = 'Objc';
|
|
450
520
|
extType = nullType;
|
|
451
521
|
} else if (channels.indexOf(key) !== -1) {
|
|
452
|
-
type = (classId === 'RGBC' && root !== 'artd') ? 'doub' : 'long';
|
|
522
|
+
type = ((classId === 'RGBC' && root !== 'artd') || doubleColorClasses.indexOf(classId) !== -1) ? 'doub' : 'long';
|
|
453
523
|
} else if (key === 'profile') {
|
|
454
|
-
type = classId === 'printOutput' ? 'TEXT' : 'tdta';
|
|
524
|
+
type = (classId === 'printOutput' || classId === 'proofSetup') ? 'TEXT' : 'tdta';
|
|
525
|
+
} else if (key === 'alis' && typeof value[key] === 'string') {
|
|
526
|
+
// 'alis' is read as a binary string (one char per byte), strings with wider chars can only be stored as TEXT
|
|
527
|
+
type = /[^\x00-\xff]/.test(value[key]) ? 'TEXT' : 'alis';
|
|
455
528
|
} else if (key === 'strokeStyleContent') {
|
|
456
529
|
if (value[key]['Clr ']) {
|
|
457
530
|
extType = makeType('', 'solidColorLayer');
|
|
@@ -466,14 +539,19 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
|
|
|
466
539
|
extType = makeType('', 'classFloatRect');
|
|
467
540
|
}
|
|
468
541
|
|
|
542
|
+
// guess color class from its fields (colors created by serializeColor always have _classID set)
|
|
469
543
|
if (extType && extType.classID === 'RGBC') {
|
|
470
544
|
if ('H ' in value[key]) extType = { classID: 'HSBC', name: '' };
|
|
471
|
-
|
|
545
|
+
else if ('Cyn ' in value[key]) extType = { classID: 'CMYC', name: '' };
|
|
546
|
+
else if ('Lmnc' in value[key]) extType = { classID: 'LbCl', name: '' };
|
|
547
|
+
else if ('Gry ' in value[key]) extType = { classID: 'Grsc', name: '' };
|
|
472
548
|
}
|
|
473
549
|
|
|
550
|
+
if (!type) type = guessOSType(value[key]);
|
|
551
|
+
|
|
474
552
|
writeAsciiStringOrClassId(writer, key);
|
|
475
553
|
writeSignature(writer, type || 'long');
|
|
476
|
-
writeOSType(writer, type || 'long', value[key], key, extType,
|
|
554
|
+
writeOSType(writer, type || 'long', value[key], key, extType, childRoot);
|
|
477
555
|
if (logErrors && !type) console.log(`Missing descriptor field type for: '${key}' in`, value);
|
|
478
556
|
}
|
|
479
557
|
}
|
|
@@ -541,14 +619,13 @@ function readOSType(reader: PsdReader, type: string, includeClass: boolean) {
|
|
|
541
619
|
case 'ObAr': { // Object array
|
|
542
620
|
readInt32(reader); // version: 16
|
|
543
621
|
readUnicodeString(reader); // name: ''
|
|
544
|
-
readAsciiStringOrClassId(reader); // 'rationalPoint'
|
|
622
|
+
readAsciiStringOrClassId(reader); // classID: 'rationalPoint'
|
|
545
623
|
const length = readInt32(reader);
|
|
546
624
|
const items: any[] = [];
|
|
547
625
|
|
|
548
626
|
for (let i = 0; i < length; i++) {
|
|
549
627
|
const type1 = readAsciiStringOrClassId(reader); // type Hrzn | Vrtc
|
|
550
628
|
readSignature(reader); // UnFl
|
|
551
|
-
|
|
552
629
|
readSignature(reader); // units ? '#Pxl'
|
|
553
630
|
const valuesCount = readInt32(reader);
|
|
554
631
|
const values: number[] = [];
|
|
@@ -588,9 +665,9 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
588
665
|
case 'Objc': // Descriptor
|
|
589
666
|
case 'GlbO': { // GlobalObject same as Descriptor
|
|
590
667
|
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
|
|
668
|
+
if (!extType && !value._classID) throw new Error(`Missing ext type for: '${key}' (${JSON.stringify(value)})`);
|
|
669
|
+
const name = value._name || extType?.name || '';
|
|
670
|
+
const classID = value._classID || extType!.classID;
|
|
594
671
|
writeDescriptorStructure(writer, name, classID, value, root);
|
|
595
672
|
break;
|
|
596
673
|
}
|
|
@@ -599,7 +676,7 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
599
676
|
writeInt32(writer, value.length);
|
|
600
677
|
|
|
601
678
|
for (let i = 0; i < value.length; i++) {
|
|
602
|
-
const type = fieldToArrayType[key];
|
|
679
|
+
const type = fieldToArrayType[key] ?? guessOSType(value[i]);
|
|
603
680
|
writeSignature(writer, type || 'long');
|
|
604
681
|
writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
|
|
605
682
|
if (logErrors && !type) console.log(`Missing descriptor array type for: '${key}' in`, value);
|
|
@@ -631,19 +708,31 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
631
708
|
}
|
|
632
709
|
case 'long': // Integer
|
|
633
710
|
if (typeof value !== 'number') throw new Error(`Invalid integer value: ${JSON.stringify(value)}, key: ${key}`);
|
|
634
|
-
writeInt32(writer, value);
|
|
711
|
+
writeInt32(writer, Math.round(value)); // round instead of truncating fractional values
|
|
712
|
+
break;
|
|
713
|
+
case 'comp': // Large Integer
|
|
714
|
+
if (!value || typeof value !== 'object') throw new Error(`Invalid large integer value: ${JSON.stringify(value)}, key: ${key}`);
|
|
715
|
+
writeUint32(writer, value.low >>> 0);
|
|
716
|
+
writeUint32(writer, value.high >>> 0);
|
|
635
717
|
break;
|
|
636
|
-
// case 'comp': // Large Integer
|
|
637
|
-
// writeLargeInteger(reader);
|
|
638
718
|
case 'bool': // Boolean
|
|
639
719
|
if (typeof value !== 'boolean') throw new Error(`Invalid boolean value: ${JSON.stringify(value)}, key: ${key}`);
|
|
640
720
|
writeUint8(writer, value ? 1 : 0);
|
|
641
721
|
break;
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
722
|
+
case 'type': // Class
|
|
723
|
+
case 'GlbC': // Class
|
|
724
|
+
if (!value || typeof value !== 'object') throw new Error(`Invalid class value: ${JSON.stringify(value)}, key: ${key}`);
|
|
725
|
+
writeUnicodeStringWithPadding(writer, value.name || '');
|
|
726
|
+
writeAsciiStringOrClassId(writer, value.classID);
|
|
727
|
+
break;
|
|
728
|
+
case 'alis': { // Alias (read as a binary string, one char per byte)
|
|
729
|
+
if (typeof value !== 'string') throw new Error(`Invalid alias value: ${JSON.stringify(value)}, key: ${key}`);
|
|
730
|
+
writeInt32(writer, value.length);
|
|
731
|
+
for (let i = 0; i < value.length; i++) {
|
|
732
|
+
writeUint8(writer, value.charCodeAt(i) & 0xff);
|
|
733
|
+
}
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
647
736
|
case 'tdta': // Raw Data
|
|
648
737
|
writeInt32(writer, value.byteLength);
|
|
649
738
|
writeBytes(writer, value);
|
|
@@ -669,12 +758,13 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
669
758
|
break;
|
|
670
759
|
}
|
|
671
760
|
case 'Pth ': { // File path
|
|
672
|
-
const
|
|
761
|
+
const path = value.path + '\0'; // Photoshop stores trailing null character (stripped when reading)
|
|
762
|
+
const length = 4 + 4 + 4 + path.length * 2;
|
|
673
763
|
writeInt32(writer, length);
|
|
674
764
|
writeSignature(writer, value.sig);
|
|
675
765
|
writeInt32LE(writer, length);
|
|
676
|
-
writeInt32LE(writer,
|
|
677
|
-
writeUnicodeStringWithoutLengthLE(writer,
|
|
766
|
+
writeInt32LE(writer, path.length);
|
|
767
|
+
writeUnicodeStringWithoutLengthLE(writer, path);
|
|
678
768
|
break;
|
|
679
769
|
}
|
|
680
770
|
default:
|
|
@@ -682,6 +772,24 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
|
|
|
682
772
|
}
|
|
683
773
|
}
|
|
684
774
|
|
|
775
|
+
// Reference item with explicit reference type and class, can be used in place of a value in reference arrays
|
|
776
|
+
// passed to writeVersionAndDescriptor (references are read as values only: strings, numbers and class objects)
|
|
777
|
+
export interface DescriptorReferenceItem {
|
|
778
|
+
type: 'prop' | 'Clss' | 'Enmr' | 'rele' | 'Idnt' | 'indx' | 'name';
|
|
779
|
+
_name?: string; // class name
|
|
780
|
+
_classID?: string;
|
|
781
|
+
keyID?: string; // prop
|
|
782
|
+
typeID?: string; // Enmr
|
|
783
|
+
enumValue?: string; // Enmr
|
|
784
|
+
value?: string | number; // rele, Idnt, indx, name
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const referenceTypes = ['prop', 'Clss', 'Enmr', 'rele', 'Idnt', 'indx', 'name'];
|
|
788
|
+
|
|
789
|
+
function isReferenceItem(value: any): value is DescriptorReferenceItem {
|
|
790
|
+
return !!value && typeof value === 'object' && referenceTypes.indexOf(value.type) !== -1;
|
|
791
|
+
}
|
|
792
|
+
|
|
685
793
|
function readReferenceStructure(reader: PsdReader) {
|
|
686
794
|
const itemsCount = readInt32(reader);
|
|
687
795
|
const items: any[] = [];
|
|
@@ -690,39 +798,32 @@ function readReferenceStructure(reader: PsdReader) {
|
|
|
690
798
|
const type = readSignature(reader);
|
|
691
799
|
|
|
692
800
|
switch (type) {
|
|
693
|
-
case 'prop':
|
|
801
|
+
case 'prop': // Property
|
|
694
802
|
readClassStructure(reader);
|
|
695
|
-
|
|
696
|
-
items.push(keyID);
|
|
803
|
+
items.push(readAsciiStringOrClassId(reader));
|
|
697
804
|
break;
|
|
698
|
-
}
|
|
699
805
|
case 'Clss': // Class
|
|
700
806
|
items.push(readClassStructure(reader));
|
|
701
807
|
break;
|
|
702
808
|
case 'Enmr': { // Enumerated Reference
|
|
703
809
|
readClassStructure(reader);
|
|
704
810
|
const typeID = readAsciiStringOrClassId(reader);
|
|
705
|
-
const
|
|
706
|
-
items.push(`${typeID}.${
|
|
811
|
+
const enumValue = readAsciiStringOrClassId(reader);
|
|
812
|
+
items.push(`${typeID}.${enumValue}`);
|
|
707
813
|
break;
|
|
708
814
|
}
|
|
709
|
-
case 'rele':
|
|
710
|
-
// const { name, classID } =
|
|
815
|
+
case 'rele': // Offset
|
|
711
816
|
readClassStructure(reader);
|
|
712
817
|
items.push(readUint32(reader));
|
|
713
818
|
break;
|
|
714
|
-
}
|
|
715
819
|
case 'Idnt': // Identifier
|
|
716
|
-
items.push(readInt32(reader));
|
|
717
|
-
break;
|
|
718
820
|
case 'indx': // Index
|
|
719
821
|
items.push(readInt32(reader));
|
|
720
822
|
break;
|
|
721
|
-
case 'name':
|
|
823
|
+
case 'name': // Name
|
|
722
824
|
readClassStructure(reader);
|
|
723
825
|
items.push(readUnicodeString(reader));
|
|
724
826
|
break;
|
|
725
|
-
}
|
|
726
827
|
default:
|
|
727
828
|
throw new Error(`Invalid descriptor reference type: ${type}`);
|
|
728
829
|
}
|
|
@@ -731,43 +832,73 @@ function readReferenceStructure(reader: PsdReader) {
|
|
|
731
832
|
return items;
|
|
732
833
|
}
|
|
733
834
|
|
|
835
|
+
function guessReferenceItem(value: any): DescriptorReferenceItem {
|
|
836
|
+
if (typeof value === 'string') {
|
|
837
|
+
// enumerated references are read as `${typeID}.${enumValue}` where typeID is a 4 character ID or a string ID
|
|
838
|
+
// (values like 'My.Path' or 'Logo.v2' can't be typeID/enum value, so they are written as name references)
|
|
839
|
+
const match = /^([a-z ]{4}|[a-z]{5,})\.([a-z ]+)$/i.exec(value);
|
|
840
|
+
|
|
841
|
+
if (match) {
|
|
842
|
+
return { type: 'Enmr', _name: '', _classID: match[1], typeID: match[1], enumValue: match[2] };
|
|
843
|
+
} else {
|
|
844
|
+
return { type: 'name', _name: '', _classID: 'Lyr ', value };
|
|
845
|
+
}
|
|
846
|
+
} else if (typeof value === 'number') {
|
|
847
|
+
return { type: 'Idnt', value };
|
|
848
|
+
} else if (value && typeof value === 'object' && typeof value.classID === 'string') {
|
|
849
|
+
return { type: 'Clss', _name: value.name || '', _classID: value.classID };
|
|
850
|
+
} else {
|
|
851
|
+
throw new Error(`Invalid descriptor reference value: ${JSON.stringify(value)}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// path selection state (image resource 1088) is a reference to paths, enumerated references there use 'Path' class
|
|
856
|
+
// (name references use 'Lyr ' class, they reference layer of the vector mask)
|
|
857
|
+
export function pathSelectionStateToReference(items: string[]): DescriptorReferenceItem[] {
|
|
858
|
+
return items.map(value => {
|
|
859
|
+
const item = guessReferenceItem(value);
|
|
860
|
+
if (item.type === 'Enmr') item._classID = 'Path';
|
|
861
|
+
return item;
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
734
865
|
function writeReferenceStructure(writer: PsdWriter, _key: string, items: any[]) {
|
|
735
|
-
|
|
866
|
+
if (!Array.isArray(items)) throw new Error(`Invalid reference value: ${JSON.stringify(items)}, key: ${_key}`);
|
|
736
867
|
|
|
737
|
-
|
|
738
|
-
const value = items[i];
|
|
739
|
-
let type = 'unknown';
|
|
868
|
+
writeInt32(writer, items.length);
|
|
740
869
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
type = 'Enmr';
|
|
744
|
-
} else {
|
|
745
|
-
type = 'name';
|
|
746
|
-
}
|
|
747
|
-
}
|
|
870
|
+
for (const value of items) {
|
|
871
|
+
const item = isReferenceItem(value) ? value : guessReferenceItem(value);
|
|
748
872
|
|
|
749
|
-
writeSignature(writer, type);
|
|
873
|
+
writeSignature(writer, item.type);
|
|
750
874
|
|
|
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);
|
|
875
|
+
switch (item.type) {
|
|
876
|
+
case 'prop': // Property
|
|
877
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
878
|
+
writeAsciiStringOrClassId(writer, item.keyID!);
|
|
759
879
|
break;
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
880
|
+
case 'Clss': // Class
|
|
881
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
882
|
+
break;
|
|
883
|
+
case 'Enmr': // Enumerated Reference
|
|
884
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
885
|
+
writeAsciiStringOrClassId(writer, item.typeID!);
|
|
886
|
+
writeAsciiStringOrClassId(writer, item.enumValue!);
|
|
887
|
+
break;
|
|
888
|
+
case 'rele': // Offset
|
|
889
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
890
|
+
writeUint32(writer, item.value as number);
|
|
891
|
+
break;
|
|
892
|
+
case 'Idnt': // Identifier
|
|
893
|
+
case 'indx': // Index
|
|
894
|
+
writeInt32(writer, item.value as number);
|
|
895
|
+
break;
|
|
896
|
+
case 'name': // Name
|
|
897
|
+
writeClassStructure(writer, item._name || '', item._classID!);
|
|
898
|
+
writeUnicodeStringWithPadding(writer, `${item.value}`);
|
|
767
899
|
break;
|
|
768
|
-
}
|
|
769
900
|
default:
|
|
770
|
-
throw new Error(`Invalid descriptor reference type: ${type}`);
|
|
901
|
+
throw new Error(`Invalid descriptor reference type: ${item.type}`);
|
|
771
902
|
}
|
|
772
903
|
}
|
|
773
904
|
|
|
@@ -780,8 +911,8 @@ function readClassStructure(reader: PsdReader) {
|
|
|
780
911
|
return { name, classID };
|
|
781
912
|
}
|
|
782
913
|
|
|
783
|
-
function writeClassStructure(writer: PsdWriter, name: string, classID: string) {
|
|
784
|
-
|
|
914
|
+
export function writeClassStructure(writer: PsdWriter, name: string, classID: string) {
|
|
915
|
+
writeUnicodeStringWithPadding(writer, name);
|
|
785
916
|
writeAsciiStringOrClassId(writer, classID);
|
|
786
917
|
}
|
|
787
918
|
|
|
@@ -827,11 +958,11 @@ export type DescriptorColor = {
|
|
|
827
958
|
Blck: number;
|
|
828
959
|
} | {
|
|
829
960
|
_name: '';
|
|
830
|
-
_classID: 'GRYC'; //
|
|
961
|
+
_classID: 'Grsc' | 'GRYC'; // 'GRYC' was written by older versions of ag-psd
|
|
831
962
|
'Gry ': number;
|
|
832
963
|
} | {
|
|
833
964
|
_name: '';
|
|
834
|
-
_classID: 'LABC'; //
|
|
965
|
+
_classID: 'LbCl' | 'LABC'; // 'LABC' was written by older versions of ag-psd
|
|
835
966
|
Lmnc: number;
|
|
836
967
|
'A ': number;
|
|
837
968
|
'B ': number;
|
|
@@ -853,8 +984,8 @@ export type DesciptorGradient = {
|
|
|
853
984
|
GrdF: 'GrdF.CstS';
|
|
854
985
|
Intr: number;
|
|
855
986
|
Clrs: {
|
|
856
|
-
'Clr '
|
|
857
|
-
Type
|
|
987
|
+
'Clr '?: DescriptorColor;
|
|
988
|
+
Type?: string; // 'Clry.UsrS' | 'Clry.FrgC' | 'Clry.BckC'
|
|
858
989
|
Lctn: number;
|
|
859
990
|
Mdpn: number;
|
|
860
991
|
}[];
|
|
@@ -894,7 +1025,9 @@ export interface DescriptorGradientContent {
|
|
|
894
1025
|
export interface DescriptorPatternContent {
|
|
895
1026
|
Ptrn: DesciptorPattern;
|
|
896
1027
|
Lnkd?: boolean;
|
|
897
|
-
phase?: { Hrzn: number; Vrtc: number; };
|
|
1028
|
+
phase?: { Hrzn: number | DescriptorUnitsValue; Vrtc: number | DescriptorUnitsValue; };
|
|
1029
|
+
'Scl '?: DescriptorUnitsValue;
|
|
1030
|
+
Angl?: DescriptorUnitsValue;
|
|
898
1031
|
}
|
|
899
1032
|
|
|
900
1033
|
export type DescriptorVectorContent = DescriptorColorContent | DescriptorGradientContent | DescriptorPatternContent;
|
|
@@ -1190,7 +1323,7 @@ function parseFxObject(fx: EffectDescriptor) {
|
|
|
1190
1323
|
fillType: FrFl.decode(fx.PntT!),
|
|
1191
1324
|
blendMode: BlnM.decode(fx['Md ']!),
|
|
1192
1325
|
opacity: parsePercent(fx.Opct),
|
|
1193
|
-
size:
|
|
1326
|
+
size: parseUnitsOrNumber(fx['Sz '] as DescriptorUnitsValue | number), // stored as number (pixels) in old versions
|
|
1194
1327
|
};
|
|
1195
1328
|
|
|
1196
1329
|
if (fx.present !== undefined) stroke.present = fx.present;
|
|
@@ -1258,9 +1391,11 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1258
1391
|
|
|
1259
1392
|
for (const key of Object.keys(e)) {
|
|
1260
1393
|
const value = (e as any)[key];
|
|
1394
|
+
if (value == null || typeof value !== 'object') continue;
|
|
1395
|
+
|
|
1261
1396
|
if (Array.isArray(value)) {
|
|
1262
1397
|
for (const effect of value) {
|
|
1263
|
-
if (effect
|
|
1398
|
+
if (effect?.enabled) info.numModifyingFX++;
|
|
1264
1399
|
}
|
|
1265
1400
|
} else if (value.enabled) {
|
|
1266
1401
|
info.numModifyingFX++;
|
|
@@ -1273,16 +1408,16 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
|
|
|
1273
1408
|
|
|
1274
1409
|
export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, log: boolean) {
|
|
1275
1410
|
const effects: LayerEffectsInfo = {};
|
|
1276
|
-
const { masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX, ...rest } = info;
|
|
1411
|
+
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; };
|
|
1277
1412
|
if (!masterFXSwitch) effects.disabled = true;
|
|
1278
|
-
if (
|
|
1413
|
+
if (scale) effects.scale = parsePercent(scale);
|
|
1279
1414
|
if (DrSh) effects.dropShadow = [parseEffectObject(DrSh, log)];
|
|
1280
1415
|
if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i, log));
|
|
1281
1416
|
if (IrSh) effects.innerShadow = [parseEffectObject(IrSh, log)];
|
|
1282
1417
|
if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i, log));
|
|
1283
1418
|
if (OrGl) effects.outerGlow = parseEffectObject(OrGl, log);
|
|
1284
1419
|
if (IrGl) effects.innerGlow = parseEffectObject(IrGl, log);
|
|
1285
|
-
if (ebbl) effects.bevel = parseEffectObject(ebbl, log);
|
|
1420
|
+
if (ebbl) effects.bevel = parseEffectObject(ebbl, log, 'bevel');
|
|
1286
1421
|
if (SoFi) effects.solidFill = [parseEffectObject(SoFi, log)];
|
|
1287
1422
|
if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i, log));
|
|
1288
1423
|
if (patternFill) effects.patternOverlay = parseEffectObject(patternFill, log);
|
|
@@ -1291,7 +1426,7 @@ export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, log: boolean
|
|
|
1291
1426
|
if (ChFX) effects.satin = parseEffectObject(ChFX, log);
|
|
1292
1427
|
if (FrFX) effects.stroke = [parseFxObject(FrFX)];
|
|
1293
1428
|
if (frameFXMulti) effects.stroke = frameFXMulti.map(i => parseFxObject(i));
|
|
1294
|
-
if (log && Object.keys(rest).length
|
|
1429
|
+
if (log && Object.keys(rest).length) console.log('Unhandled effect keys:', rest);
|
|
1295
1430
|
return effects;
|
|
1296
1431
|
}
|
|
1297
1432
|
|
|
@@ -1410,7 +1545,7 @@ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescri
|
|
|
1410
1545
|
Effc: !!t.effectParams,
|
|
1411
1546
|
...(t.effectParams ? {
|
|
1412
1547
|
effectParams: {
|
|
1413
|
-
keyList: serializeKeyList(t.keys),
|
|
1548
|
+
keyList: serializeKeyList(t.effectParams.keys),
|
|
1414
1549
|
fillCanvas: t.effectParams.fillCanvas,
|
|
1415
1550
|
zoomOrigin: t.effectParams.zoomOrigin,
|
|
1416
1551
|
}
|
|
@@ -1426,7 +1561,7 @@ type AllEffects = LayerEffectShadow & LayerEffectsOuterGlow & LayerEffectStroke
|
|
|
1426
1561
|
LayerEffectInnerGlow & LayerEffectBevel & LayerEffectSolidFill &
|
|
1427
1562
|
LayerEffectPatternOverlay & LayerEffectSatin & LayerEffectGradientOverlay;
|
|
1428
1563
|
|
|
1429
|
-
function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
1564
|
+
function parseEffectObject(obj: any, reportErrors: boolean, objName?: string) {
|
|
1430
1565
|
const result: AllEffects = {} as any;
|
|
1431
1566
|
|
|
1432
1567
|
for (const key of Object.keys(obj)) {
|
|
@@ -1453,7 +1588,10 @@ function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
|
1453
1588
|
case 'GlwT': result.technique = BETE.decode(val) as any; break;
|
|
1454
1589
|
case 'glwS': result.source = IGSr.decode(val); break;
|
|
1455
1590
|
case 'Type': result.type = GrdT.decode(val); break;
|
|
1456
|
-
case 'gs99':
|
|
1591
|
+
case 'gs99': // gradient overlay
|
|
1592
|
+
case 'gradientsInterpolationMethod': // glows
|
|
1593
|
+
result.interpolationMethod = gradientInterpolationMethodType.decode(val);
|
|
1594
|
+
break;
|
|
1457
1595
|
case 'Opct': result.opacity = parsePercent(val); break;
|
|
1458
1596
|
case 'hglO': result.highlightOpacity = parsePercent(val); break;
|
|
1459
1597
|
case 'sdwO': result.shadowOpacity = parsePercent(val); break;
|
|
@@ -1470,15 +1608,23 @@ function parseEffectObject(obj: any, reportErrors: boolean) {
|
|
|
1470
1608
|
case 'Dstn': result.distance = parseUnits(val); break;
|
|
1471
1609
|
case 'Scl ': result.scale = parsePercent(val); break;
|
|
1472
1610
|
case 'Ptrn': result.pattern = { name: val['Nm '], id: val.Idnt }; break;
|
|
1473
|
-
case 'phase': result.phase =
|
|
1611
|
+
case 'phase': result.phase = parsePhase(val); break;
|
|
1474
1612
|
case 'Ofst': result.offset = { x: parsePercent(val.Hrzn), y: parsePercent(val.Vrtc) }; break;
|
|
1475
1613
|
case 'MpgS':
|
|
1476
|
-
case 'TrnS':
|
|
1477
|
-
|
|
1614
|
+
case 'TrnS': {
|
|
1615
|
+
const contour: EffectContour = {
|
|
1478
1616
|
name: val['Nm '],
|
|
1479
1617
|
curve: (val['Crv '] as any[]).map(p => ({ x: p.Hrzn, y: p.Vrtc })),
|
|
1480
1618
|
};
|
|
1619
|
+
|
|
1620
|
+
// bevel can have both: gloss contour (TrnS) and contour of the shape (MpgS)
|
|
1621
|
+
if (objName === 'bevel' && key === 'MpgS') {
|
|
1622
|
+
result.shapeContour = contour;
|
|
1623
|
+
} else {
|
|
1624
|
+
result.contour = contour;
|
|
1625
|
+
}
|
|
1481
1626
|
break;
|
|
1627
|
+
}
|
|
1482
1628
|
case 'Grad': result.gradient = parseGradient(val); break;
|
|
1483
1629
|
case 'useTexture':
|
|
1484
1630
|
case 'useShape':
|
|
@@ -1536,12 +1682,14 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1536
1682
|
break;
|
|
1537
1683
|
case 'source': result.glwS = IGSr.encode(val); break;
|
|
1538
1684
|
case 'type': result.Type = GrdT.encode(val); break;
|
|
1539
|
-
case 'interpolationMethod':
|
|
1685
|
+
case 'interpolationMethod':
|
|
1686
|
+
result[(objName === 'outerGlow' || objName === 'innerGlow') ? 'gradientsInterpolationMethod' : 'gs99'] = gradientInterpolationMethodType.encode(val);
|
|
1687
|
+
break;
|
|
1540
1688
|
case 'opacity': result.Opct = unitsPercent(val); break;
|
|
1541
1689
|
case 'highlightOpacity': result.hglO = unitsPercent(val); break;
|
|
1542
1690
|
case 'shadowOpacity': result.sdwO = unitsPercent(val); break;
|
|
1543
1691
|
case 'angle':
|
|
1544
|
-
if (objName === 'gradientOverlay' || objName === '
|
|
1692
|
+
if (objName === 'gradientOverlay' || objName === 'patternOverlay') {
|
|
1545
1693
|
result.Angl = unitsAngle(val);
|
|
1546
1694
|
} else {
|
|
1547
1695
|
result.lagl = unitsAngle(val);
|
|
@@ -1559,14 +1707,9 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1559
1707
|
case 'scale': result['Scl '] = unitsPercent(val); break;
|
|
1560
1708
|
case 'pattern': result.Ptrn = { 'Nm ': val.name, Idnt: val.id }; break;
|
|
1561
1709
|
case 'phase': result.phase = { Hrzn: val.x, Vrtc: val.y }; break;
|
|
1562
|
-
case 'offset': result.Ofst = { Hrzn:
|
|
1563
|
-
case 'contour':
|
|
1564
|
-
|
|
1565
|
-
'Nm ': (val as EffectContour).name,
|
|
1566
|
-
'Crv ': (val as EffectContour).curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
|
|
1567
|
-
};
|
|
1568
|
-
break;
|
|
1569
|
-
}
|
|
1710
|
+
case 'offset': result.Ofst = { Hrzn: unitsPercentF(val.x), Vrtc: unitsPercentF(val.y) }; break;
|
|
1711
|
+
case 'contour': result[objName === 'satin' ? 'MpgS' : 'TrnS'] = serializeContour(val); break;
|
|
1712
|
+
case 'shapeContour': result.MpgS = serializeContour(val); break;
|
|
1570
1713
|
case 'gradient': result.Grad = serializeGradient(val); break;
|
|
1571
1714
|
case 'useTexture':
|
|
1572
1715
|
case 'useShape':
|
|
@@ -1584,19 +1727,38 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
|
|
|
1584
1727
|
return result;
|
|
1585
1728
|
}
|
|
1586
1729
|
|
|
1730
|
+
function serializeContour(contour: EffectContour) {
|
|
1731
|
+
return {
|
|
1732
|
+
'Nm ': contour.name,
|
|
1733
|
+
'Crv ': contour.curve.map(p => ({ Hrzn: p.x, Vrtc: p.y })),
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1587
1737
|
function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoiseGradient {
|
|
1588
1738
|
if (grad.GrdF === 'GrdF.CstS') {
|
|
1589
|
-
|
|
1739
|
+
// stop locations are always in 0..4096 range, independent of smoothness (Intr)
|
|
1740
|
+
const samples = 4096;
|
|
1590
1741
|
|
|
1591
1742
|
return {
|
|
1592
1743
|
type: 'solid',
|
|
1593
1744
|
name: grad['Nm '],
|
|
1594
1745
|
smoothness: grad.Intr / 4096,
|
|
1595
|
-
colorStops: grad.Clrs.map(s =>
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1746
|
+
colorStops: grad.Clrs.map(s => {
|
|
1747
|
+
const stop: ColorStop = {
|
|
1748
|
+
color: s['Clr '] ? parseColor(s['Clr ']) : { r: 0, g: 0, b: 0 },
|
|
1749
|
+
location: s.Lctn / samples,
|
|
1750
|
+
midpoint: s.Mdpn / 100,
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
if (s.Type) {
|
|
1754
|
+
try {
|
|
1755
|
+
const type = Clry.decode(s.Type);
|
|
1756
|
+
if (type !== 'user') stop.type = type;
|
|
1757
|
+
} catch { } // unknown stop type, read as user color
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
return stop;
|
|
1761
|
+
}),
|
|
1600
1762
|
opacityStops: grad.Trns.map(s => ({
|
|
1601
1763
|
opacity: parsePercent(s.Opct),
|
|
1602
1764
|
location: s.Lctn / samples,
|
|
@@ -1620,19 +1782,20 @@ function parseGradient(grad: DesciptorGradient): EffectSolidGradient | EffectNoi
|
|
|
1620
1782
|
|
|
1621
1783
|
function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): DesciptorGradient {
|
|
1622
1784
|
if (grad.type === 'solid') {
|
|
1623
|
-
|
|
1785
|
+
// stop locations are always in 0..4096 range, independent of smoothness (Intr)
|
|
1786
|
+
const samples = 4096;
|
|
1624
1787
|
return {
|
|
1625
1788
|
'Nm ': grad.name || '',
|
|
1626
1789
|
GrdF: 'GrdF.CstS',
|
|
1627
|
-
Intr:
|
|
1790
|
+
Intr: Math.round((grad.smoothness ?? 1) * 4096),
|
|
1628
1791
|
Clrs: grad.colorStops.map(s => ({
|
|
1629
1792
|
'Clr ': serializeColor(s.color),
|
|
1630
|
-
Type:
|
|
1793
|
+
Type: Clry.encode(s.type ?? 'user'),
|
|
1631
1794
|
Lctn: Math.round(s.location * samples),
|
|
1632
1795
|
Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
|
|
1633
1796
|
})),
|
|
1634
1797
|
Trns: grad.opacityStops.map(s => ({
|
|
1635
|
-
Opct:
|
|
1798
|
+
Opct: unitsPercentF(s.opacity),
|
|
1636
1799
|
Lctn: Math.round(s.location * samples),
|
|
1637
1800
|
Mdpn: Math.round((s.midpoint ?? 0.5) * 100),
|
|
1638
1801
|
})),
|
|
@@ -1646,8 +1809,8 @@ function serializeGradient(grad: EffectSolidGradient | EffectNoiseGradient): Des
|
|
|
1646
1809
|
ClrS: ClrS.encode(grad.colorModel),
|
|
1647
1810
|
RndS: grad.randomSeed || 0,
|
|
1648
1811
|
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),
|
|
1812
|
+
'Mnm ': (grad.min || [0, 0, 0, 0]).map(x => Math.round(x * 100)),
|
|
1813
|
+
'Mxm ': (grad.max || [1, 1, 1, 1]).map(x => Math.round(x * 100)),
|
|
1651
1814
|
};
|
|
1652
1815
|
}
|
|
1653
1816
|
}
|
|
@@ -1670,13 +1833,23 @@ function parseGradientContent(descriptor: DescriptorGradientContent) {
|
|
|
1670
1833
|
return result;
|
|
1671
1834
|
}
|
|
1672
1835
|
|
|
1836
|
+
// phase is usually stored as plain numbers, but some files store it as unit values
|
|
1837
|
+
function parsePhase({ Hrzn, Vrtc }: { Hrzn: number | DescriptorUnitsValue; Vrtc: number | DescriptorUnitsValue; }) {
|
|
1838
|
+
return {
|
|
1839
|
+
x: typeof Hrzn === 'number' ? Hrzn : Hrzn.value,
|
|
1840
|
+
y: typeof Vrtc === 'number' ? Vrtc : Vrtc.value,
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1673
1844
|
function parsePatternContent(descriptor: DescriptorPatternContent) {
|
|
1674
1845
|
const result: EffectPattern & ExtraPatternInfo = {
|
|
1675
1846
|
name: descriptor.Ptrn['Nm '],
|
|
1676
1847
|
id: descriptor.Ptrn.Idnt,
|
|
1677
1848
|
};
|
|
1678
1849
|
if (descriptor.Lnkd !== undefined) result.linked = descriptor.Lnkd;
|
|
1679
|
-
if (descriptor.phase !== undefined) result.phase =
|
|
1850
|
+
if (descriptor.phase !== undefined) result.phase = parsePhase(descriptor.phase);
|
|
1851
|
+
if (descriptor['Scl '] !== undefined) result.scale = parsePercent(descriptor['Scl ']);
|
|
1852
|
+
if (descriptor.Angl !== undefined) result.angle = parseAngle(descriptor.Angl);
|
|
1680
1853
|
return result;
|
|
1681
1854
|
}
|
|
1682
1855
|
|
|
@@ -1701,11 +1874,11 @@ function serializeGradientContent(content: (EffectSolidGradient | EffectNoiseGra
|
|
|
1701
1874
|
if (content.angle !== undefined) result.Angl = unitsAngle(content.angle);
|
|
1702
1875
|
result.Type = GrdT.encode(content.style);
|
|
1703
1876
|
if (content.align !== undefined) result.Algn = content.align;
|
|
1704
|
-
if (content.scale !== undefined) result['Scl '] =
|
|
1877
|
+
if (content.scale !== undefined) result['Scl '] = unitsPercentF(content.scale);
|
|
1705
1878
|
if (content.offset) {
|
|
1706
1879
|
result.Ofst = {
|
|
1707
|
-
Hrzn:
|
|
1708
|
-
Vrtc:
|
|
1880
|
+
Hrzn: unitsPercentF(content.offset.x),
|
|
1881
|
+
Vrtc: unitsPercentF(content.offset.y),
|
|
1709
1882
|
};
|
|
1710
1883
|
}
|
|
1711
1884
|
result.Grad = serializeGradient(content);
|
|
@@ -1721,6 +1894,8 @@ function serializePatternContent(content: EffectPattern & ExtraPatternInfo) {
|
|
|
1721
1894
|
};
|
|
1722
1895
|
if (content.linked !== undefined) result.Lnkd = !!content.linked;
|
|
1723
1896
|
if (content.phase !== undefined) result.phase = { Hrzn: content.phase.x, Vrtc: content.phase.y };
|
|
1897
|
+
if (content.scale !== undefined) result['Scl '] = unitsPercentF(content.scale);
|
|
1898
|
+
if (content.angle !== undefined) result.Angl = unitsAngle(content.angle);
|
|
1724
1899
|
return result;
|
|
1725
1900
|
}
|
|
1726
1901
|
|
|
@@ -1734,19 +1909,64 @@ export function serializeVectorContent(content: VectorContent): { descriptor: De
|
|
|
1734
1909
|
}
|
|
1735
1910
|
}
|
|
1736
1911
|
|
|
1912
|
+
// Descriptor color values: HSB saturation/brightness 0..100, CMYK 0..100, Gray 0..100, Lab L 0..100, a/b -128..127
|
|
1913
|
+
// 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)
|
|
1914
|
+
|
|
1915
|
+
const colorConversions: { [key: string]: ((x: number) => number)[] | undefined; } = {
|
|
1916
|
+
// key: [descriptor -> color, color -> descriptor]
|
|
1917
|
+
'H ': [x => x / 360, x => x * 360], // angle
|
|
1918
|
+
Strt: [x => x / 100, x => x * 100],
|
|
1919
|
+
Brgh: [x => x / 100, x => x * 100],
|
|
1920
|
+
'Cyn ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1921
|
+
Mgnt: [x => x * 255 / 100, x => x * 100 / 255],
|
|
1922
|
+
'Ylw ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1923
|
+
Blck: [x => x * 255 / 100, x => x * 100 / 255],
|
|
1924
|
+
'Gry ': [x => x * 255 / 100, x => x * 100 / 255],
|
|
1925
|
+
Lmnc: [x => x / 100, x => x * 100],
|
|
1926
|
+
'A ': [normalizeLabAB, denormalizeLabAB],
|
|
1927
|
+
'B ': [normalizeLabAB, denormalizeLabAB],
|
|
1928
|
+
};
|
|
1929
|
+
|
|
1930
|
+
function colorToDescriptorValue(key: string, value: number) {
|
|
1931
|
+
const conversion = colorConversions[key];
|
|
1932
|
+
if (!conversion) return value;
|
|
1933
|
+
const [from, to] = conversion;
|
|
1934
|
+
const result = to(value);
|
|
1935
|
+
if (!isFinite(result)) return result;
|
|
1936
|
+
|
|
1937
|
+
// prefer shortest number that converts back to the same value (avoids values like 30.200000000000003)
|
|
1938
|
+
for (let precision = 1; precision < 17; precision++) {
|
|
1939
|
+
const candidate = +result.toPrecision(precision);
|
|
1940
|
+
if (from(candidate) === value) return candidate;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
return result;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function isBookColor(color: any) {
|
|
1947
|
+
return color._classID === 'BkCl' || 'Bk ' in color || 'bookID' in color;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1737
1950
|
export function parseColor(color: DescriptorColor): Color {
|
|
1738
1951
|
if ('H ' in color) {
|
|
1739
|
-
return { h: parsePercentOrAngle(color['H ']), s: color.Strt, b: color.Brgh };
|
|
1952
|
+
return { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
|
|
1740
1953
|
} else if ('Rd ' in color) {
|
|
1741
1954
|
return { r: color['Rd '], g: color['Grn '], b: color['Bl '] };
|
|
1742
1955
|
} else if ('Cyn ' in color) {
|
|
1743
|
-
return {
|
|
1956
|
+
return {
|
|
1957
|
+
c: color['Cyn '] * 255 / 100,
|
|
1958
|
+
m: color.Mgnt * 255 / 100,
|
|
1959
|
+
y: color['Ylw '] * 255 / 100,
|
|
1960
|
+
k: color.Blck * 255 / 100,
|
|
1961
|
+
};
|
|
1744
1962
|
} else if ('Gry ' in color) {
|
|
1745
|
-
return { k: color['Gry '] };
|
|
1963
|
+
return { k: color['Gry '] * 255 / 100 };
|
|
1746
1964
|
} else if ('Lmnc' in color) {
|
|
1747
|
-
return { l: color.Lmnc, a: color['A '], b: color['B '] };
|
|
1965
|
+
return { l: color.Lmnc / 100, a: normalizeLabAB(color['A ']), b: normalizeLabAB(color['B ']) };
|
|
1748
1966
|
} else if ('redFloat' in color) {
|
|
1749
1967
|
return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
|
|
1968
|
+
} else if (isBookColor(color)) {
|
|
1969
|
+
return { r: 0, g: 0, b: 0 }; // book (Pantone) colors are not supported
|
|
1750
1970
|
} else {
|
|
1751
1971
|
throw new Error('Unsupported color descriptor');
|
|
1752
1972
|
}
|
|
@@ -1760,13 +1980,29 @@ export function serializeColor(color: Color | undefined): DescriptorColor {
|
|
|
1760
1980
|
} else if ('fr' in color) {
|
|
1761
1981
|
return { _name: '', _classID: 'RGBC', redFloat: color.fr, greenFloat: color.fg, blueFloat: color.fb };
|
|
1762
1982
|
} else if ('h' in color) {
|
|
1763
|
-
return {
|
|
1983
|
+
return {
|
|
1984
|
+
_name: '', _classID: 'HSBC',
|
|
1985
|
+
'H ': unitsAngle(colorToDescriptorValue('H ', color.h || 0)),
|
|
1986
|
+
Strt: colorToDescriptorValue('Strt', color.s || 0),
|
|
1987
|
+
Brgh: colorToDescriptorValue('Brgh', color.b || 0),
|
|
1988
|
+
};
|
|
1764
1989
|
} else if ('c' in color) {
|
|
1765
|
-
return {
|
|
1990
|
+
return {
|
|
1991
|
+
_name: '', _classID: 'CMYC',
|
|
1992
|
+
'Cyn ': colorToDescriptorValue('Cyn ', color.c || 0),
|
|
1993
|
+
Mgnt: colorToDescriptorValue('Mgnt', color.m || 0),
|
|
1994
|
+
'Ylw ': colorToDescriptorValue('Ylw ', color.y || 0),
|
|
1995
|
+
Blck: colorToDescriptorValue('Blck', color.k || 0),
|
|
1996
|
+
};
|
|
1766
1997
|
} else if ('l' in color) {
|
|
1767
|
-
return {
|
|
1998
|
+
return {
|
|
1999
|
+
_name: '', _classID: 'LbCl',
|
|
2000
|
+
Lmnc: colorToDescriptorValue('Lmnc', color.l || 0),
|
|
2001
|
+
'A ': colorToDescriptorValue('A ', color.a || 0),
|
|
2002
|
+
'B ': colorToDescriptorValue('B ', color.b || 0),
|
|
2003
|
+
};
|
|
1768
2004
|
} else if ('k' in color) {
|
|
1769
|
-
return { _name: '', _classID: '
|
|
2005
|
+
return { _name: '', _classID: 'Grsc', 'Gry ': colorToDescriptorValue('Gry ', color.k || 0) };
|
|
1770
2006
|
} else {
|
|
1771
2007
|
throw new Error('Invalid color value');
|
|
1772
2008
|
}
|
|
@@ -1794,7 +2030,8 @@ export function parsePercentOrAngle(x: DescriptorUnitsValue | undefined) {
|
|
|
1794
2030
|
export function parseUnits({ units, value }: DescriptorUnitsValue): UnitsValue {
|
|
1795
2031
|
if (
|
|
1796
2032
|
units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
|
|
1797
|
-
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
|
|
2033
|
+
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
|
|
2034
|
+
units !== 'Distance'
|
|
1798
2035
|
) {
|
|
1799
2036
|
throw new Error(`Invalid units: ${JSON.stringify({ units, value })}`);
|
|
1800
2037
|
}
|
|
@@ -1836,7 +2073,8 @@ export function unitsValue(x: UnitsValue | undefined, key: string): DescriptorUn
|
|
|
1836
2073
|
|
|
1837
2074
|
if (
|
|
1838
2075
|
units !== 'Pixels' && units !== 'Millimeters' && units !== 'Points' && units !== 'None' &&
|
|
1839
|
-
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density'
|
|
2076
|
+
units !== 'Picas' && units !== 'Inches' && units !== 'Centimeters' && units !== 'Density' &&
|
|
2077
|
+
units !== 'Distance'
|
|
1840
2078
|
) {
|
|
1841
2079
|
throw new Error(`Invalid units in ${JSON.stringify(x)} (key: ${key})`);
|
|
1842
2080
|
}
|
|
@@ -1982,6 +2220,12 @@ export const gradientInterpolationMethodType = createEnum<InterpolationMethod>('
|
|
|
1982
2220
|
// TODO: stripes
|
|
1983
2221
|
});
|
|
1984
2222
|
|
|
2223
|
+
export const Clry = createEnum<'user' | 'foreground' | 'background'>('Clry', 'user', {
|
|
2224
|
+
user: 'UsrS',
|
|
2225
|
+
foreground: 'FrgC',
|
|
2226
|
+
background: 'BckC',
|
|
2227
|
+
});
|
|
2228
|
+
|
|
1985
2229
|
export const ClrS = createEnum<'rgb' | 'hsb' | 'lab' | 'hsl'>('ClrS', 'rgb', {
|
|
1986
2230
|
rgb: 'RGBC',
|
|
1987
2231
|
hsb: 'HSBl',
|