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.
Files changed (159) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/00d0e0b4 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/75efccd4 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  25. package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
  26. package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
  27. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  28. package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
  29. package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
  30. package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +0 -0
  31. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  32. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  33. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8569d6b +0 -0
  34. package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
  35. package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
  36. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  37. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  38. package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
  39. package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
  40. package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +0 -0
  41. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  42. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  43. package/.v8-cache/v24.7.0-x64-ef5a0af0/d02dd6f7 +0 -0
  44. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  45. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  46. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6735171 +0 -0
  47. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  48. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  49. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  50. package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
  51. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  52. package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
  53. package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
  54. package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
  55. package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
  56. package/CHANGELOG.md +107 -0
  57. package/LICENSE +5 -0
  58. package/README.md +3 -6
  59. package/README_PSD.md +242 -81
  60. package/dist/abr.d.ts +11 -4
  61. package/dist/abr.js +363 -229
  62. package/dist/abr.js.map +1 -1
  63. package/dist/additionalInfo.d.ts +56 -2
  64. package/dist/additionalInfo.js +600 -364
  65. package/dist/additionalInfo.js.map +1 -1
  66. package/dist/ase.d.ts +6 -1
  67. package/dist/ase.js +61 -61
  68. package/dist/ase.js.map +1 -1
  69. package/dist/bundle.js +3096 -1541
  70. package/dist/descriptor.d.ts +25 -6
  71. package/dist/descriptor.js +380 -135
  72. package/dist/descriptor.js.map +1 -1
  73. package/dist/effectsHelpers.js +12 -12
  74. package/dist/effectsHelpers.js.map +1 -1
  75. package/dist/engineData.d.ts +3 -1
  76. package/dist/engineData.js +34 -13
  77. package/dist/engineData.js.map +1 -1
  78. package/dist/engineData2.js +50 -21
  79. package/dist/engineData2.js.map +1 -1
  80. package/dist/helpers.d.ts +6 -3
  81. package/dist/helpers.js +22 -6
  82. package/dist/helpers.js.map +1 -1
  83. package/dist/imageResources.d.ts +15 -3
  84. package/dist/imageResources.js +529 -144
  85. package/dist/imageResources.js.map +1 -1
  86. package/dist/index.js +1 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/jpeg.js +30 -10
  89. package/dist/jpeg.js.map +1 -1
  90. package/dist/psd.d.ts +154 -18
  91. package/dist/psd.js.map +1 -1
  92. package/dist/psdReader.d.ts +12 -2
  93. package/dist/psdReader.js +720 -421
  94. package/dist/psdReader.js.map +1 -1
  95. package/dist/psdWriter.d.ts +3 -0
  96. package/dist/psdWriter.js +167 -130
  97. package/dist/psdWriter.js.map +1 -1
  98. package/dist/text.js +143 -29
  99. package/dist/text.js.map +1 -1
  100. package/dist/utf8.js +36 -18
  101. package/dist/utf8.js.map +1 -1
  102. package/dist-es/abr.d.ts +11 -4
  103. package/dist-es/abr.js +363 -229
  104. package/dist-es/abr.js.map +1 -1
  105. package/dist-es/additionalInfo.d.ts +56 -2
  106. package/dist-es/additionalInfo.js +600 -364
  107. package/dist-es/additionalInfo.js.map +1 -1
  108. package/dist-es/ase.d.ts +6 -1
  109. package/dist-es/ase.js +61 -61
  110. package/dist-es/ase.js.map +1 -1
  111. package/dist-es/descriptor.d.ts +25 -6
  112. package/dist-es/descriptor.js +380 -135
  113. package/dist-es/descriptor.js.map +1 -1
  114. package/dist-es/effectsHelpers.js +12 -12
  115. package/dist-es/effectsHelpers.js.map +1 -1
  116. package/dist-es/engineData.d.ts +3 -1
  117. package/dist-es/engineData.js +34 -13
  118. package/dist-es/engineData.js.map +1 -1
  119. package/dist-es/engineData2.js +50 -21
  120. package/dist-es/engineData2.js.map +1 -1
  121. package/dist-es/helpers.d.ts +6 -3
  122. package/dist-es/helpers.js +22 -6
  123. package/dist-es/helpers.js.map +1 -1
  124. package/dist-es/imageResources.d.ts +15 -3
  125. package/dist-es/imageResources.js +529 -144
  126. package/dist-es/imageResources.js.map +1 -1
  127. package/dist-es/index.js +1 -0
  128. package/dist-es/index.js.map +1 -1
  129. package/dist-es/jpeg.js +30 -10
  130. package/dist-es/jpeg.js.map +1 -1
  131. package/dist-es/psd.d.ts +154 -18
  132. package/dist-es/psd.js.map +1 -1
  133. package/dist-es/psdReader.d.ts +12 -2
  134. package/dist-es/psdReader.js +720 -421
  135. package/dist-es/psdReader.js.map +1 -1
  136. package/dist-es/psdWriter.d.ts +3 -0
  137. package/dist-es/psdWriter.js +167 -130
  138. package/dist-es/psdWriter.js.map +1 -1
  139. package/dist-es/text.js +143 -29
  140. package/dist-es/text.js.map +1 -1
  141. package/dist-es/utf8.js +36 -18
  142. package/dist-es/utf8.js.map +1 -1
  143. package/package.json +2 -2
  144. package/src/abr.ts +380 -232
  145. package/src/additionalInfo.ts +781 -477
  146. package/src/ase.ts +66 -61
  147. package/src/descriptor.ts +386 -142
  148. package/src/effectsHelpers.ts +9 -9
  149. package/src/engineData.ts +36 -13
  150. package/src/engineData2.ts +468 -432
  151. package/src/helpers.ts +23 -5
  152. package/src/imageResources.ts +658 -167
  153. package/src/index.ts +1 -0
  154. package/src/jpeg.ts +27 -12
  155. package/src/psd.ts +158 -30
  156. package/src/psdReader.ts +732 -414
  157. package/src/psdWriter.ts +186 -132
  158. package/src/text.ts +139 -18
  159. 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, writeUnicodeString, writeInt32LE,
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
- const keys = Object.keys(value);
390
- let keyCount = keys.length;
391
- if ('_name' in value) keyCount--;
392
- if ('_classID' in value) keyCount--;
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
- writeUint32(writer, keyCount);
457
+ const allKeys = Object.keys(value);
458
+ const keys = allKeys.filter(key => key !== '_name' && key !== '_classID' && value[key] !== undefined);
395
459
 
396
- for (const key of keys) {
397
- if (key === '_name' || key === '_classID') continue;
460
+ writeUint32(writer, keys.length);
398
461
 
399
- let type = getTypeByKey(key, value[key], root, value);
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
- // TODO: other color spaces
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, root);
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.name;
593
- const classID = value._classID || extType.classID;
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
- // case 'type': // Class
643
- // case 'GlbC': // Class
644
- // writeClassStructure(reader);
645
- // case 'alis': // Alias
646
- // writeAliasStructure(reader);
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 length = 4 + 4 + 4 + value.path.length * 2;
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, value.path.length);
677
- writeUnicodeStringWithoutLengthLE(writer, value.path);
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': { // Property
801
+ case 'prop': // Property
694
802
  readClassStructure(reader);
695
- const keyID = readAsciiStringOrClassId(reader);
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 value = readAsciiStringOrClassId(reader);
706
- items.push(`${typeID}.${value}`);
811
+ const enumValue = readAsciiStringOrClassId(reader);
812
+ items.push(`${typeID}.${enumValue}`);
707
813
  break;
708
814
  }
709
- case 'rele': { // Offset
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': { // 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
- writeInt32(writer, items.length);
866
+ if (!Array.isArray(items)) throw new Error(`Invalid reference value: ${JSON.stringify(items)}, key: ${_key}`);
736
867
 
737
- for (let i = 0; i < items.length; i++) {
738
- const value = items[i];
739
- let type = 'unknown';
868
+ writeInt32(writer, items.length);
740
869
 
741
- if (typeof value === 'string') {
742
- if (/^[a-z ]+\.[a-z ]+$/i.test(value)) {
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
- // case 'prop': // Property
753
- // case 'Clss': // Class
754
- case 'Enmr': { // Enumerated Reference
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
- // case 'rele': // Offset
762
- // case 'Idnt': // Identifier
763
- // case 'indx': // Index
764
- case 'name': { // Name
765
- writeClassStructure(writer, '\0', 'Lyr ');
766
- writeUnicodeString(writer, value + '\0');
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
- writeUnicodeString(writer, name);
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 ': DescriptorColor;
857
- Type: 'Clry.UsrS';
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: parseUnits(fx['Sz ']!),
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.enabled) info.numModifyingFX++;
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 (info['Scl ']) effects.scale = parsePercent(info['Scl ']);
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 > 1) console.log('Unhandled effect keys:', rest);
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': result.interpolationMethod = gradientInterpolationMethodType.decode(val); break;
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 = { x: val.Hrzn, y: val.Vrtc }; break;
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
- result.contour = {
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': result.gs99 = gradientInterpolationMethodType.encode(val); break;
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 === 'patternFill') {
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: unitsPercent(val.x), Vrtc: unitsPercent(val.y) }; break;
1563
- case 'contour': {
1564
- result[objName === 'satin' ? 'MpgS' : 'TrnS'] = {
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
- const samples: number = grad.Intr || 4096;
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
- color: parseColor(s['Clr ']),
1597
- location: s.Lctn / samples,
1598
- midpoint: s.Mdpn / 100,
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
- const samples = Math.round((grad.smoothness ?? 1) * 4096);
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: samples,
1790
+ Intr: Math.round((grad.smoothness ?? 1) * 4096),
1628
1791
  Clrs: grad.colorStops.map(s => ({
1629
1792
  'Clr ': serializeColor(s.color),
1630
- Type: 'Clry.UsrS',
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: unitsPercent(s.opacity),
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 = { x: descriptor.phase.Hrzn, y: descriptor.phase.Vrtc };
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 '] = unitsPercent(content.scale);
1877
+ if (content.scale !== undefined) result['Scl '] = unitsPercentF(content.scale);
1705
1878
  if (content.offset) {
1706
1879
  result.Ofst = {
1707
- Hrzn: unitsPercent(content.offset.x),
1708
- Vrtc: unitsPercent(content.offset.y),
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 { c: color['Cyn '], m: color.Mgnt, y: color['Ylw '], k: color.Blck };
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 { _name: '', _classID: 'HSBC', 'H ': unitsAngle(color.h * 360), Strt: color.s || 0, Brgh: color.b || 0 };
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 { _name: '', _classID: 'CMYC', 'Cyn ': color.c || 0, Mgnt: color.m || 0, 'Ylw ': color.y || 0, Blck: color.k || 0 };
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 { _name: '', _classID: 'LABC', Lmnc: color.l || 0, 'A ': color.a || 0, 'B ': color.b || 0 };
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: 'GRYC', 'Gry ': color.k };
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',