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/additionalInfo.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fromByteArray, toByteArray } from 'base64-js';
|
|
2
2
|
import { readEffects, writeEffects } from './effectsHelpers';
|
|
3
|
-
import { clamp, createEnum, layerColors, MOCK_HANDLERS } from './helpers';
|
|
4
|
-
import { LayerAdditionalInfo, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation,
|
|
5
|
-
import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString,
|
|
6
|
-
import { PsdWriter, writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32, writePattern } from './psdWriter';
|
|
3
|
+
import { clamp, createEnum, layerColors, MOCK_HANDLERS, normalizeLabAB } from './helpers';
|
|
4
|
+
import { LayerAdditionalInfo, LevelsFilterAdjustment, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation, VectorPathData, AnimationFrame, Timeline, PlacedLayerFilter, UnitsValue, Filter, PlacedLayer, Layer } from './psd';
|
|
5
|
+
import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString, readPatternSafe, readLayerInfo } from './psdReader';
|
|
6
|
+
import { PsdWriter, writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32, writePattern, writeUint64 } from './psdWriter';
|
|
7
7
|
import { Annt, BlnM, DescriptorColor, DescriptorUnitsValue, parsePercent, parseUnits, parseUnitsOrNumber, QuiltWarpDescriptor, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, TextDescriptor, textGridding, unitsPercent, unitsValue, WarpDescriptor, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, StrokeDescriptor, Ornt, horzVrtcToXY, LmfxDescriptor, Lfx2Descriptor, FrameListDescriptor, TimelineDescriptor, FrameDescriptor, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, FractionDescriptor, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType, PxScDescriptor } from './descriptor';
|
|
8
8
|
import { serializeEngineData, parseEngineData } from './engineData';
|
|
9
9
|
import { encodeEngineData, decodeEngineData } from './text';
|
|
@@ -13,6 +13,8 @@ import type { InternalImageResources } from './imageResources';
|
|
|
13
13
|
export interface ExtendedWriteOptions extends WriteOptions {
|
|
14
14
|
layerIds: Set<number>;
|
|
15
15
|
layerToId: Map<any, number>;
|
|
16
|
+
explicitLayerIds?: Set<number>; // ids set on layers in the document (generated ids can't use them)
|
|
17
|
+
nextGeneratedLayerId?: number; // all ids below this one are already taken
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
type HasMethod = (target: LayerAdditionalInfo) => boolean;
|
|
@@ -49,11 +51,6 @@ function readLength64(reader: PsdReader) {
|
|
|
49
51
|
return readUint32(reader);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
function writeLength64(writer: PsdWriter, length: number) {
|
|
53
|
-
writeUint32(writer, 0);
|
|
54
|
-
writeUint32(writer, length);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
54
|
addHandler(
|
|
58
55
|
'TySh',
|
|
59
56
|
hasKey('text'),
|
|
@@ -95,7 +92,7 @@ addHandler(
|
|
|
95
92
|
if (text.boundingBox) target.text.boundingBox = descBoundsToBounds(text.boundingBox);
|
|
96
93
|
|
|
97
94
|
if (text.EngineData) {
|
|
98
|
-
const engineData = parseEngineData(text.EngineData);
|
|
95
|
+
const engineData = parseEngineData(text.EngineData, reader);
|
|
99
96
|
const textData = decodeEngineData(engineData);
|
|
100
97
|
// console.log(require('util').inspect(engineData, false, 99, false), 'utf8');
|
|
101
98
|
|
|
@@ -106,7 +103,8 @@ addHandler(
|
|
|
106
103
|
// require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
|
|
107
104
|
|
|
108
105
|
// console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
|
|
109
|
-
|
|
106
|
+
// descriptor anti-alias value is more reliable than the one from engine data
|
|
107
|
+
target.text = { ...target.text, ...textData, antiAlias: target.text!.antiAlias ?? textData.antiAlias };
|
|
110
108
|
// console.log(require('util').inspect(target.text, false, 99, true));
|
|
111
109
|
}
|
|
112
110
|
|
|
@@ -140,10 +138,10 @@ addHandler(
|
|
|
140
138
|
writeInt16(writer, 1); // warp version
|
|
141
139
|
writeVersionAndDescriptor(writer, '', 'warp', encodeWarp(warp));
|
|
142
140
|
|
|
143
|
-
writeFloat32(writer, text.left
|
|
144
|
-
writeFloat32(writer, text.top
|
|
145
|
-
writeFloat32(writer, text.right
|
|
146
|
-
writeFloat32(writer, text.bottom
|
|
141
|
+
writeFloat32(writer, text.left ?? 0);
|
|
142
|
+
writeFloat32(writer, text.top ?? 0);
|
|
143
|
+
writeFloat32(writer, text.right ?? 0);
|
|
144
|
+
writeFloat32(writer, text.bottom ?? 0);
|
|
147
145
|
|
|
148
146
|
// writeZeros(writer, 2);
|
|
149
147
|
},
|
|
@@ -231,7 +229,7 @@ function writeBezierKnot(writer: PsdWriter, points: number[], width: number, hei
|
|
|
231
229
|
|
|
232
230
|
export const booleanOperations: BooleanOperation[] = ['exclude', 'combine', 'subtract', 'intersect'];
|
|
233
231
|
|
|
234
|
-
export function readVectorMask(reader: PsdReader, vectorMask:
|
|
232
|
+
export function readVectorMask(reader: PsdReader, vectorMask: VectorPathData, width: number, height: number, size: number) {
|
|
235
233
|
const end = reader.offset + size;
|
|
236
234
|
const paths = vectorMask.paths;
|
|
237
235
|
let path: BezierPath | undefined = undefined;
|
|
@@ -286,6 +284,51 @@ export function readVectorMask(reader: PsdReader, vectorMask: LayerVectorMask, w
|
|
|
286
284
|
return paths;
|
|
287
285
|
}
|
|
288
286
|
|
|
287
|
+
// componentIndices - write index of path component for each subpath (Photoshop writes them for layer vector masks)
|
|
288
|
+
export function writeVectorMask(writer: PsdWriter, vectorMask: VectorPathData, width: number, height: number, componentIndices = false) {
|
|
289
|
+
// initial entry
|
|
290
|
+
writeUint16(writer, 6);
|
|
291
|
+
writeZeros(writer, 24);
|
|
292
|
+
|
|
293
|
+
const clipboard = vectorMask.clipboard;
|
|
294
|
+
if (clipboard) {
|
|
295
|
+
writeUint16(writer, 7);
|
|
296
|
+
writeFixedPointPath32(writer, clipboard.top);
|
|
297
|
+
writeFixedPointPath32(writer, clipboard.left);
|
|
298
|
+
writeFixedPointPath32(writer, clipboard.bottom);
|
|
299
|
+
writeFixedPointPath32(writer, clipboard.right);
|
|
300
|
+
writeFixedPointPath32(writer, clipboard.resolution);
|
|
301
|
+
writeZeros(writer, 4);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
writeUint16(writer, 8);
|
|
305
|
+
writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
|
|
306
|
+
writeZeros(writer, 22);
|
|
307
|
+
|
|
308
|
+
let componentIndex = -1;
|
|
309
|
+
|
|
310
|
+
for (const path of vectorMask.paths) {
|
|
311
|
+
// subpaths without operation are part of the same path component as previous subpath
|
|
312
|
+
if (path.operation || componentIndex < 0) componentIndex++;
|
|
313
|
+
|
|
314
|
+
writeUint16(writer, path.open ? 3 : 0);
|
|
315
|
+
writeUint16(writer, path.knots.length);
|
|
316
|
+
writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
|
|
317
|
+
writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
|
|
318
|
+
writeZeros(writer, 4);
|
|
319
|
+
writeUint32(writer, componentIndices ? componentIndex : 0);
|
|
320
|
+
writeZeros(writer, 10);
|
|
321
|
+
|
|
322
|
+
const linkedKnot = path.open ? 4 : 1;
|
|
323
|
+
const unlinkedKnot = path.open ? 5 : 2;
|
|
324
|
+
|
|
325
|
+
for (const { linked, points } of path.knots) {
|
|
326
|
+
writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
|
|
327
|
+
writeBezierKnot(writer, points, width, height);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
289
332
|
addHandler(
|
|
290
333
|
'vmsk',
|
|
291
334
|
hasKey('vectorMask'),
|
|
@@ -315,45 +358,11 @@ addHandler(
|
|
|
315
358
|
|
|
316
359
|
writeUint32(writer, 3); // version
|
|
317
360
|
writeUint32(writer, flags);
|
|
318
|
-
|
|
319
|
-
// initial entry
|
|
320
|
-
writeUint16(writer, 6);
|
|
321
|
-
writeZeros(writer, 24);
|
|
322
|
-
|
|
323
|
-
const clipboard = vectorMask.clipboard;
|
|
324
|
-
if (clipboard) {
|
|
325
|
-
writeUint16(writer, 7);
|
|
326
|
-
writeFixedPointPath32(writer, clipboard.top);
|
|
327
|
-
writeFixedPointPath32(writer, clipboard.left);
|
|
328
|
-
writeFixedPointPath32(writer, clipboard.bottom);
|
|
329
|
-
writeFixedPointPath32(writer, clipboard.right);
|
|
330
|
-
writeFixedPointPath32(writer, clipboard.resolution);
|
|
331
|
-
writeZeros(writer, 4);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
writeUint16(writer, 8);
|
|
335
|
-
writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
|
|
336
|
-
writeZeros(writer, 22);
|
|
337
|
-
|
|
338
|
-
for (const path of vectorMask.paths) {
|
|
339
|
-
writeUint16(writer, path.open ? 3 : 0);
|
|
340
|
-
writeUint16(writer, path.knots.length);
|
|
341
|
-
writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
|
|
342
|
-
writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
|
|
343
|
-
writeZeros(writer, 18); // TODO: these are sometimes non-zero
|
|
344
|
-
|
|
345
|
-
const linkedKnot = path.open ? 4 : 1;
|
|
346
|
-
const unlinkedKnot = path.open ? 5 : 2;
|
|
347
|
-
|
|
348
|
-
for (const { linked, points } of path.knots) {
|
|
349
|
-
writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
|
|
350
|
-
writeBezierKnot(writer, points, width, height);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
361
|
+
writeVectorMask(writer, vectorMask, width, height, true);
|
|
353
362
|
},
|
|
354
363
|
);
|
|
355
364
|
|
|
356
|
-
//
|
|
365
|
+
// written instead of 'vmsk' for layers with vector stroke ('vscg' section)
|
|
357
366
|
addHandlerAlias('vsms', 'vmsk');
|
|
358
367
|
// addHandlerAlias('vmsk', 'vsms');
|
|
359
368
|
|
|
@@ -368,7 +377,7 @@ addHandler(
|
|
|
368
377
|
},
|
|
369
378
|
);
|
|
370
379
|
|
|
371
|
-
interface
|
|
380
|
+
export interface KeyDescriptorListDescriptor {
|
|
372
381
|
keyDescriptorList: {
|
|
373
382
|
keyShapeInvalidated?: boolean;
|
|
374
383
|
keyOriginType?: number;
|
|
@@ -398,121 +407,128 @@ interface VogkDescriptor {
|
|
|
398
407
|
}[];
|
|
399
408
|
}
|
|
400
409
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
+
export function parseKeyDescriptorList(desc: KeyDescriptorListDescriptor) {
|
|
411
|
+
const keyDescriptorList: KeyDescriptorItem[] = [];
|
|
412
|
+
|
|
413
|
+
for (const i of desc.keyDescriptorList) {
|
|
414
|
+
const item: KeyDescriptorItem = {};
|
|
415
|
+
|
|
416
|
+
if (i.keyShapeInvalidated != null) item.keyShapeInvalidated = i.keyShapeInvalidated;
|
|
417
|
+
if (i.keyOriginType != null) item.keyOriginType = i.keyOriginType;
|
|
418
|
+
if (i.keyOriginResolution != null) item.keyOriginResolution = i.keyOriginResolution;
|
|
419
|
+
if (i.keyOriginShapeBBox) {
|
|
420
|
+
item.keyOriginShapeBoundingBox = {
|
|
421
|
+
top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
|
|
422
|
+
left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
|
|
423
|
+
bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
|
|
424
|
+
right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const rectRadii = i.keyOriginRRectRadii;
|
|
428
|
+
if (rectRadii) {
|
|
429
|
+
item.keyOriginRRectRadii = {
|
|
430
|
+
topRight: parseUnits(rectRadii.topRight),
|
|
431
|
+
topLeft: parseUnits(rectRadii.topLeft),
|
|
432
|
+
bottomLeft: parseUnits(rectRadii.bottomLeft),
|
|
433
|
+
bottomRight: parseUnits(rectRadii.bottomRight),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const corners = i.keyOriginBoxCorners;
|
|
437
|
+
if (corners) {
|
|
438
|
+
item.keyOriginBoxCorners = [
|
|
439
|
+
{ x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
|
|
440
|
+
{ x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
|
|
441
|
+
{ x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
|
|
442
|
+
{ x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
|
|
443
|
+
];
|
|
444
|
+
}
|
|
445
|
+
const trnf = i.Trnf;
|
|
446
|
+
if (trnf) {
|
|
447
|
+
item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
|
|
448
|
+
}
|
|
410
449
|
|
|
411
|
-
|
|
412
|
-
|
|
450
|
+
keyDescriptorList.push(item);
|
|
451
|
+
}
|
|
413
452
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (i.keyOriginResolution != null) item.keyOriginResolution = i.keyOriginResolution;
|
|
417
|
-
if (i.keyOriginShapeBBox) {
|
|
418
|
-
item.keyOriginShapeBoundingBox = {
|
|
419
|
-
top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
|
|
420
|
-
left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
|
|
421
|
-
bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
|
|
422
|
-
right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
const rectRadii = i.keyOriginRRectRadii;
|
|
426
|
-
if (rectRadii) {
|
|
427
|
-
item.keyOriginRRectRadii = {
|
|
428
|
-
topRight: parseUnits(rectRadii.topRight),
|
|
429
|
-
topLeft: parseUnits(rectRadii.topLeft),
|
|
430
|
-
bottomLeft: parseUnits(rectRadii.bottomLeft),
|
|
431
|
-
bottomRight: parseUnits(rectRadii.bottomRight),
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
const corners = i.keyOriginBoxCorners;
|
|
435
|
-
if (corners) {
|
|
436
|
-
item.keyOriginBoxCorners = [
|
|
437
|
-
{ x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
|
|
438
|
-
{ x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
|
|
439
|
-
{ x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
|
|
440
|
-
{ x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
|
|
441
|
-
];
|
|
442
|
-
}
|
|
443
|
-
const trnf = i.Trnf;
|
|
444
|
-
if (trnf) {
|
|
445
|
-
item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
|
|
446
|
-
}
|
|
453
|
+
return keyDescriptorList;
|
|
454
|
+
}
|
|
447
455
|
|
|
448
|
-
|
|
449
|
-
|
|
456
|
+
export function serializeKeyDescriptorList(keyDescriptorList: KeyDescriptorItem[]) {
|
|
457
|
+
const desc: KeyDescriptorListDescriptor = { keyDescriptorList: [] };
|
|
450
458
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
(writer, target) => {
|
|
454
|
-
target;
|
|
455
|
-
const orig = target.vectorOrigination!;
|
|
456
|
-
const desc: VogkDescriptor = { keyDescriptorList: [] };
|
|
459
|
+
for (let i = 0; i < keyDescriptorList.length; i++) {
|
|
460
|
+
const item = keyDescriptorList[i];
|
|
457
461
|
|
|
458
|
-
|
|
459
|
-
const item = orig.keyDescriptorList[i];
|
|
462
|
+
desc.keyDescriptorList.push({} as any); // we're adding keyOriginIndex at the end
|
|
460
463
|
|
|
461
|
-
|
|
464
|
+
const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
|
|
462
465
|
|
|
463
|
-
|
|
466
|
+
if (item.keyOriginType != null) out.keyOriginType = item.keyOriginType;
|
|
467
|
+
if (item.keyOriginResolution != null) out.keyOriginResolution = item.keyOriginResolution;
|
|
464
468
|
|
|
465
|
-
|
|
466
|
-
|
|
469
|
+
const radii = item.keyOriginRRectRadii;
|
|
470
|
+
if (radii) {
|
|
471
|
+
out.keyOriginRRectRadii = {
|
|
472
|
+
unitValueQuadVersion: 1,
|
|
473
|
+
topRight: unitsValue(radii.topRight, 'topRight'),
|
|
474
|
+
topLeft: unitsValue(radii.topLeft, 'topLeft'),
|
|
475
|
+
bottomLeft: unitsValue(radii.bottomLeft, 'bottomLeft'),
|
|
476
|
+
bottomRight: unitsValue(radii.bottomRight, 'bottomRight'),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
467
479
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
480
|
+
const box = item.keyOriginShapeBoundingBox;
|
|
481
|
+
if (box) {
|
|
482
|
+
out.keyOriginShapeBBox = {
|
|
483
|
+
unitValueQuadVersion: 1,
|
|
484
|
+
'Top ': unitsValue(box.top, 'top'),
|
|
485
|
+
Left: unitsValue(box.left, 'left'),
|
|
486
|
+
Btom: unitsValue(box.bottom, 'bottom'),
|
|
487
|
+
Rght: unitsValue(box.right, 'right'),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
478
490
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
491
|
+
const corners = item.keyOriginBoxCorners;
|
|
492
|
+
if (corners && corners.length === 4) {
|
|
493
|
+
out.keyOriginBoxCorners = {
|
|
494
|
+
rectangleCornerA: { Hrzn: corners[0].x, Vrtc: corners[0].y },
|
|
495
|
+
rectangleCornerB: { Hrzn: corners[1].x, Vrtc: corners[1].y },
|
|
496
|
+
rectangleCornerC: { Hrzn: corners[2].x, Vrtc: corners[2].y },
|
|
497
|
+
rectangleCornerD: { Hrzn: corners[3].x, Vrtc: corners[3].y },
|
|
498
|
+
};
|
|
499
|
+
}
|
|
489
500
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
501
|
+
const transform = item.transform;
|
|
502
|
+
if (transform && transform.length === 6) {
|
|
503
|
+
out.Trnf = {
|
|
504
|
+
xx: transform[0],
|
|
505
|
+
xy: transform[1],
|
|
506
|
+
yx: transform[2],
|
|
507
|
+
yy: transform[3],
|
|
508
|
+
tx: transform[4],
|
|
509
|
+
ty: transform[5],
|
|
510
|
+
};
|
|
511
|
+
}
|
|
499
512
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
xx: transform[0],
|
|
504
|
-
xy: transform[1],
|
|
505
|
-
yx: transform[2],
|
|
506
|
-
yy: transform[3],
|
|
507
|
-
tx: transform[4],
|
|
508
|
-
ty: transform[5],
|
|
509
|
-
};
|
|
510
|
-
}
|
|
513
|
+
if (item.keyShapeInvalidated != null) out.keyShapeInvalidated = item.keyShapeInvalidated;
|
|
514
|
+
out.keyOriginIndex = i;
|
|
515
|
+
}
|
|
511
516
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
517
|
+
return desc;
|
|
518
|
+
}
|
|
515
519
|
|
|
520
|
+
addHandler(
|
|
521
|
+
'vogk',
|
|
522
|
+
hasKey('vectorOrigination'),
|
|
523
|
+
(reader, target, left) => {
|
|
524
|
+
if (readInt32(reader) !== 1) throw new Error(`Invalid vogk version`);
|
|
525
|
+
const desc = readVersionAndDescriptor(reader) as KeyDescriptorListDescriptor;
|
|
526
|
+
// console.log(require('util').inspect(desc, false, 99, true));
|
|
527
|
+
target.vectorOrigination = { keyDescriptorList: parseKeyDescriptorList(desc) };
|
|
528
|
+
skipBytes(reader, left());
|
|
529
|
+
},
|
|
530
|
+
(writer, target) => {
|
|
531
|
+
const desc = serializeKeyDescriptorList(target.vectorOrigination!.keyDescriptorList);
|
|
516
532
|
writeInt32(writer, 1); // version
|
|
517
533
|
writeVersionAndDescriptor(writer, '', 'null', desc);
|
|
518
534
|
}
|
|
@@ -520,7 +536,7 @@ addHandler(
|
|
|
520
536
|
|
|
521
537
|
addHandler(
|
|
522
538
|
'lmfx',
|
|
523
|
-
target => target.effects !== undefined && hasMultiEffects(target.effects),
|
|
539
|
+
target => target.effects !== undefined && !isGroupLayer(target) && hasMultiEffects(target.effects),
|
|
524
540
|
(reader, target, left) => {
|
|
525
541
|
const version = readUint32(reader);
|
|
526
542
|
if (version !== 0) throw new Error('Invalid lmfx version');
|
|
@@ -586,15 +602,58 @@ addHandler(
|
|
|
586
602
|
(writer, target) => writeSignature(writer, target.nameSource!),
|
|
587
603
|
);
|
|
588
604
|
|
|
605
|
+
// these sections reference layer id
|
|
606
|
+
function layerNeedsId(target: LayerAdditionalInfo) {
|
|
607
|
+
return !!(target.animationFrames || target.timeline || target.comps);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function getExplicitLayerIds(psd: Psd, options: ExtendedWriteOptions) {
|
|
611
|
+
if (!options.explicitLayerIds) {
|
|
612
|
+
const ids = new Set<number>();
|
|
613
|
+
const addIds = (layers: Layer[] | undefined) => {
|
|
614
|
+
for (const layer of layers ?? []) {
|
|
615
|
+
if (layer.id !== undefined) ids.add(layer.id);
|
|
616
|
+
addIds(layer.children);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
addIds(psd.children);
|
|
620
|
+
options.explicitLayerIds = ids;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return options.explicitLayerIds;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function getLayerId(target: LayerAdditionalInfo, options: ExtendedWriteOptions) {
|
|
627
|
+
const id = options.layerToId.get(target) ?? target.id;
|
|
628
|
+
if (id === undefined) throw new Error('Missing layer id');
|
|
629
|
+
return id;
|
|
630
|
+
}
|
|
631
|
+
|
|
589
632
|
addHandler(
|
|
590
633
|
'lyid',
|
|
591
|
-
|
|
634
|
+
target => target.id !== undefined || layerNeedsId(target),
|
|
592
635
|
(reader, target) => {
|
|
593
636
|
target.id = readUint32(reader);
|
|
594
637
|
},
|
|
595
|
-
(writer, target,
|
|
596
|
-
let id
|
|
597
|
-
|
|
638
|
+
(writer, target, psd, options) => {
|
|
639
|
+
let id: number;
|
|
640
|
+
|
|
641
|
+
if (target.id !== undefined) {
|
|
642
|
+
id = target.id;
|
|
643
|
+
|
|
644
|
+
if (options.layerIds.has(id)) {
|
|
645
|
+
// make sure we don't have duplicate layer ids (and don't take id explicitly set on another layer)
|
|
646
|
+
const explicitIds = getExplicitLayerIds(psd, options);
|
|
647
|
+
while (options.layerIds.has(id) || explicitIds.has(id)) id += 100;
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
// generate id for layers that don't have it, if it's referenced by other sections
|
|
651
|
+
const explicitIds = getExplicitLayerIds(psd, options);
|
|
652
|
+
id = options.nextGeneratedLayerId ?? 1;
|
|
653
|
+
while (options.layerIds.has(id) || explicitIds.has(id)) id++;
|
|
654
|
+
options.nextGeneratedLayerId = id + 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
598
657
|
writeUint32(writer, id);
|
|
599
658
|
options.layerIds.add(id);
|
|
600
659
|
options.layerToId.set(target, id);
|
|
@@ -698,15 +757,17 @@ addHandler(
|
|
|
698
757
|
};
|
|
699
758
|
|
|
700
759
|
if (flags & 0x08) target.protected.artboards = true;
|
|
760
|
+
if (flags & 0x80000000) target.protected.all = true;
|
|
701
761
|
},
|
|
702
762
|
(writer, target) => {
|
|
703
763
|
const flags =
|
|
704
764
|
(target.protected!.transparency ? 0x01 : 0) |
|
|
705
765
|
(target.protected!.composite ? 0x02 : 0) |
|
|
706
766
|
(target.protected!.position ? 0x04 : 0) |
|
|
707
|
-
(target.protected!.artboards ? 0x08 : 0)
|
|
767
|
+
(target.protected!.artboards ? 0x08 : 0) |
|
|
768
|
+
(target.protected!.all ? 0x80000000 : 0);
|
|
708
769
|
|
|
709
|
-
writeUint32(writer, flags);
|
|
770
|
+
writeUint32(writer, flags >>> 0);
|
|
710
771
|
},
|
|
711
772
|
);
|
|
712
773
|
|
|
@@ -729,6 +790,62 @@ interface CustomDescriptor {
|
|
|
729
790
|
layerTime?: number;
|
|
730
791
|
}
|
|
731
792
|
|
|
793
|
+
interface GeneratorSettingsDescriptor {
|
|
794
|
+
generatorSettings?: {
|
|
795
|
+
_name: '';
|
|
796
|
+
_classID: string; // 'null' or 'generatorSettings'
|
|
797
|
+
exportAs?: {
|
|
798
|
+
_name: '';
|
|
799
|
+
_classID: 'null';
|
|
800
|
+
exportOption?: string; // JSON
|
|
801
|
+
scaleSettings?: string; // JSON
|
|
802
|
+
};
|
|
803
|
+
generator_45_assets?: { _name: ''; _classID: string; json: string; };
|
|
804
|
+
layerTime?: number;
|
|
805
|
+
crema?: { _name: ''; _classID: string; json: string; };
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function parseGeneratorSettings(settings: NonNullable<GeneratorSettingsDescriptor['generatorSettings']>) {
|
|
810
|
+
const result: NonNullable<LayerAdditionalInfo['generatorSettings']> = {};
|
|
811
|
+
|
|
812
|
+
if (settings.exportAs) {
|
|
813
|
+
result.exportAs = {};
|
|
814
|
+
if (settings.exportAs.exportOption !== undefined) result.exportAs.exportOption = settings.exportAs.exportOption;
|
|
815
|
+
if (settings.exportAs.scaleSettings !== undefined) result.exportAs.scaleSettings = settings.exportAs.scaleSettings;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (settings.generator_45_assets) result.assets = settings.generator_45_assets.json;
|
|
819
|
+
if (settings.layerTime !== undefined) result.layerTime = settings.layerTime;
|
|
820
|
+
if (settings.crema) result.crema = settings.crema.json;
|
|
821
|
+
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function serializeGeneratorSettings(generatorSettings: NonNullable<LayerAdditionalInfo['generatorSettings']>): GeneratorSettingsDescriptor {
|
|
826
|
+
const settings: GeneratorSettingsDescriptor['generatorSettings'] = {
|
|
827
|
+
_name: '',
|
|
828
|
+
_classID: generatorSettings.assets !== undefined ? 'generatorSettings' : 'null',
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
if (generatorSettings.exportAs) {
|
|
832
|
+
settings.exportAs = { _name: '', _classID: 'null' };
|
|
833
|
+
if (generatorSettings.exportAs.exportOption !== undefined) settings.exportAs.exportOption = generatorSettings.exportAs.exportOption;
|
|
834
|
+
if (generatorSettings.exportAs.scaleSettings !== undefined) settings.exportAs.scaleSettings = generatorSettings.exportAs.scaleSettings;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (generatorSettings.assets !== undefined) settings.generator_45_assets = { _name: '', _classID: 'generatorSettings', json: generatorSettings.assets };
|
|
838
|
+
if (generatorSettings.layerTime !== undefined) settings.layerTime = generatorSettings.layerTime;
|
|
839
|
+
if (generatorSettings.crema !== undefined) settings.crema = { _name: '', _classID: 'null', json: generatorSettings.crema };
|
|
840
|
+
|
|
841
|
+
return { generatorSettings: settings };
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// document (Psd object) stores generator settings in 'extn' section instead of 'shmd' section
|
|
845
|
+
function isDocument(target: LayerAdditionalInfo) {
|
|
846
|
+
return (target as Psd).width !== undefined;
|
|
847
|
+
}
|
|
848
|
+
|
|
732
849
|
interface CmlsDescriptor {
|
|
733
850
|
origFXRefPoint?: { Hrzn: number; Vrtc: number; };
|
|
734
851
|
LyrI: number;
|
|
@@ -742,112 +859,113 @@ interface CmlsDescriptor {
|
|
|
742
859
|
|
|
743
860
|
addHandler(
|
|
744
861
|
'shmd', // Metadata setting
|
|
745
|
-
target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined
|
|
862
|
+
target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined ||
|
|
863
|
+
(target.generatorSettings !== undefined && !isDocument(target)),
|
|
746
864
|
(reader, target, left) => {
|
|
747
865
|
const count = readUint32(reader);
|
|
748
866
|
|
|
749
867
|
for (let i = 0; i < count; i++) {
|
|
750
|
-
|
|
868
|
+
const signature = readSignature(reader);
|
|
869
|
+
if (signature !== '8BIM' && signature !== '8ELE') throw new Error(`Invalid shmd signature: '${signature}'`); // 8ELE in Photoshop Elements
|
|
751
870
|
const key = readSignature(reader);
|
|
752
871
|
readUint8(reader); // copy
|
|
753
872
|
skipBytes(reader, 3);
|
|
754
873
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
874
|
+
const end = reader.offset + 4 + reader.view.getUint32(reader.offset);
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
readSection(reader, 1, left => {
|
|
878
|
+
if (key === 'cust') {
|
|
879
|
+
const desc = readVersionAndDescriptor(reader) as CustomDescriptor;
|
|
880
|
+
// console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
|
|
881
|
+
if (desc.layerTime !== undefined) target.timestamp = desc.layerTime;
|
|
882
|
+
} else if (key === 'mlst') {
|
|
883
|
+
const desc = readVersionAndDescriptor(reader) as FrameListDescriptor;
|
|
884
|
+
// console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
|
|
885
|
+
|
|
886
|
+
target.animationFrames = [];
|
|
887
|
+
|
|
888
|
+
for (let i = 0; i < desc.LaSt.length; i++) {
|
|
889
|
+
const f = desc.LaSt[i];
|
|
890
|
+
const frame: AnimationFrame = { frames: f.FrLs };
|
|
891
|
+
if (f.enab !== undefined) frame.enable = f.enab;
|
|
892
|
+
if (f.Ofst) frame.offset = horzVrtcToXY(f.Ofst);
|
|
893
|
+
if (f.FXRf) frame.referencePoint = horzVrtcToXY(f.FXRf);
|
|
894
|
+
if (f.Lefx) frame.effects = parseEffects(f.Lefx, !!reader.logMissingFeatures);
|
|
895
|
+
if (f.blendOptions && f.blendOptions.Opct) frame.opacity = parsePercent(f.blendOptions.Opct);
|
|
896
|
+
target.animationFrames.push(frame);
|
|
897
|
+
}
|
|
898
|
+
} else if (key === 'mdyn') {
|
|
899
|
+
// frame flags
|
|
900
|
+
readUint16(reader); // unknown
|
|
901
|
+
const propagate = readUint8(reader);
|
|
902
|
+
const flags = readUint8(reader);
|
|
903
|
+
|
|
904
|
+
target.animationFrameFlags = {
|
|
905
|
+
propagateFrameOne: !propagate,
|
|
906
|
+
unifyLayerPosition: (flags & 1) !== 0,
|
|
907
|
+
unifyLayerStyle: (flags & 2) !== 0,
|
|
908
|
+
unifyLayerVisibility: (flags & 4) !== 0,
|
|
909
|
+
};
|
|
910
|
+
} else if (key === 'tmln') {
|
|
911
|
+
const desc = readVersionAndDescriptor(reader) as TimelineDescriptor;
|
|
912
|
+
const timeScope = desc.timeScope;
|
|
913
|
+
// console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
|
|
914
|
+
|
|
915
|
+
const timeline: Timeline = {
|
|
916
|
+
start: frac(timeScope.Strt),
|
|
917
|
+
duration: frac(timeScope.duration),
|
|
918
|
+
inTime: frac(timeScope.inTime),
|
|
919
|
+
outTime: frac(timeScope.outTime),
|
|
920
|
+
autoScope: desc.autoScope,
|
|
921
|
+
audioLevel: desc.audioLevel,
|
|
922
|
+
};
|
|
801
923
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
924
|
+
if (desc.trackList) {
|
|
925
|
+
timeline.tracks = parseTrackList(desc.trackList, !!reader.logMissingFeatures);
|
|
926
|
+
}
|
|
805
927
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
928
|
+
target.timeline = timeline;
|
|
929
|
+
// console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
|
|
930
|
+
} else if (key === 'cmls') {
|
|
931
|
+
const desc = readVersionAndDescriptor(reader) as CmlsDescriptor;
|
|
932
|
+
// console.log('cmls', require('util').inspect(desc, false, 99, true));
|
|
811
933
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
934
|
+
target.comps = {
|
|
935
|
+
settings: [],
|
|
936
|
+
};
|
|
815
937
|
|
|
816
|
-
|
|
938
|
+
if (desc.origFXRefPoint) target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
|
|
817
939
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
generatorSettings
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
};
|
|
831
|
-
layerTime: number;
|
|
832
|
-
};
|
|
940
|
+
for (const item of desc.layerSettings) {
|
|
941
|
+
target.comps.settings.push({ compList: item.compList });
|
|
942
|
+
const t = target.comps.settings[target.comps.settings.length - 1];
|
|
943
|
+
if ('enab' in item) t.enabled = item.enab;
|
|
944
|
+
if (item.Ofst) t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
|
|
945
|
+
if (item.FXRefPoint) t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
|
|
946
|
+
}
|
|
947
|
+
} else if (key === 'extn') {
|
|
948
|
+
const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
|
|
949
|
+
if (desc.generatorSettings) target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
|
|
950
|
+
} else {
|
|
951
|
+
reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
|
|
833
952
|
}
|
|
834
953
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
skipBytes(reader, left());
|
|
844
|
-
});
|
|
954
|
+
skipBytes(reader, left());
|
|
955
|
+
});
|
|
956
|
+
} catch (e) {
|
|
957
|
+
// failure to read one item shouldn't drop all other items
|
|
958
|
+
if (reader.throwForMissingFeatures) throw e;
|
|
959
|
+
reader.logMissingFeatures && reader.log(`Failed to read "shmd" section key ${key}: ${(e as Error).message}`);
|
|
960
|
+
reader.offset = end;
|
|
961
|
+
}
|
|
845
962
|
}
|
|
846
963
|
|
|
847
964
|
skipBytes(reader, left());
|
|
848
965
|
},
|
|
849
966
|
(writer, target, _, options) => {
|
|
850
967
|
const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
|
|
968
|
+
const generatorSettings = isDocument(target) ? undefined : target.generatorSettings;
|
|
851
969
|
|
|
852
970
|
let count = 0;
|
|
853
971
|
if (animationFrames) count++;
|
|
@@ -855,6 +973,7 @@ addHandler(
|
|
|
855
973
|
if (timeline) count++;
|
|
856
974
|
if (timestamp !== undefined) count++;
|
|
857
975
|
if (comps) count++;
|
|
976
|
+
if (generatorSettings) count++;
|
|
858
977
|
writeUint32(writer, count);
|
|
859
978
|
|
|
860
979
|
if (animationFrames) {
|
|
@@ -864,7 +983,7 @@ addHandler(
|
|
|
864
983
|
writeZeros(writer, 3);
|
|
865
984
|
writeSection(writer, 2, () => {
|
|
866
985
|
const desc: FrameListDescriptor = {
|
|
867
|
-
LaID: target
|
|
986
|
+
LaID: getLayerId(target, options),
|
|
868
987
|
LaSt: [],
|
|
869
988
|
};
|
|
870
989
|
|
|
@@ -875,8 +994,8 @@ addHandler(
|
|
|
875
994
|
frame.FrLs = f.frames;
|
|
876
995
|
if (f.offset) frame.Ofst = xyToHorzVrtc(f.offset);
|
|
877
996
|
if (f.referencePoint) frame.FXRf = xyToHorzVrtc(f.referencePoint);
|
|
878
|
-
if (f.effects) frame.Lefx = serializeEffects(f.effects, false,
|
|
879
|
-
if (f.opacity !== undefined) frame.blendOptions = { Opct:
|
|
997
|
+
if (f.effects) frame.Lefx = serializeEffects(f.effects, false, hasMultiEffects(f.effects));
|
|
998
|
+
if (f.opacity !== undefined) frame.blendOptions = { Opct: unitsPercentF(f.opacity) };
|
|
880
999
|
desc.LaSt.push(frame);
|
|
881
1000
|
}
|
|
882
1001
|
|
|
@@ -922,9 +1041,7 @@ addHandler(
|
|
|
922
1041
|
desc.trackList = serializeTrackList(timeline.tracks);
|
|
923
1042
|
}
|
|
924
1043
|
|
|
925
|
-
|
|
926
|
-
if (!id) throw new Error('You need to provide layer.id value whan writing document with animations');
|
|
927
|
-
desc.LyrI = id;
|
|
1044
|
+
desc.LyrI = getLayerId(target, options);
|
|
928
1045
|
|
|
929
1046
|
// console.log('WRITE:tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
|
|
930
1047
|
writeVersionAndDescriptor(writer, '', 'null', desc, 'anim');
|
|
@@ -950,16 +1067,13 @@ addHandler(
|
|
|
950
1067
|
writeUint8(writer, 0); // copy (always false)
|
|
951
1068
|
writeZeros(writer, 3);
|
|
952
1069
|
writeSection(writer, 2, () => {
|
|
953
|
-
const id = options.layerToId.get(target) || target.id;
|
|
954
|
-
if (!id) throw new Error('You need to provide layer.id value whan writing document with layer comps');
|
|
955
|
-
|
|
956
1070
|
const desc: CmlsDescriptor = {} as any;
|
|
957
1071
|
|
|
958
1072
|
if (comps.originalEffectsReferencePoint) {
|
|
959
1073
|
desc.origFXRefPoint = { Hrzn: comps.originalEffectsReferencePoint.x, Vrtc: comps.originalEffectsReferencePoint.y };
|
|
960
1074
|
}
|
|
961
1075
|
|
|
962
|
-
desc.LyrI =
|
|
1076
|
+
desc.LyrI = getLayerId(target, options);
|
|
963
1077
|
desc.layerSettings = [];
|
|
964
1078
|
|
|
965
1079
|
for (const item of comps.settings) {
|
|
@@ -975,12 +1089,22 @@ addHandler(
|
|
|
975
1089
|
writeVersionAndDescriptor(writer, '', 'null', desc);
|
|
976
1090
|
}, true);
|
|
977
1091
|
}
|
|
1092
|
+
|
|
1093
|
+
if (generatorSettings) {
|
|
1094
|
+
writeSignature(writer, '8BIM');
|
|
1095
|
+
writeSignature(writer, 'extn');
|
|
1096
|
+
writeUint8(writer, 0); // copy (always false)
|
|
1097
|
+
writeZeros(writer, 3);
|
|
1098
|
+
writeSection(writer, 2, () => {
|
|
1099
|
+
writeVersionAndDescriptor(writer, '', 'null', serializeGeneratorSettings(generatorSettings));
|
|
1100
|
+
}, true);
|
|
1101
|
+
}
|
|
978
1102
|
},
|
|
979
1103
|
);
|
|
980
1104
|
|
|
981
1105
|
addHandler(
|
|
982
1106
|
'PxSc',
|
|
983
|
-
()
|
|
1107
|
+
hasKey('pixelSource'),
|
|
984
1108
|
(reader, target) => {
|
|
985
1109
|
const desc = readVersionAndDescriptor(reader, true) as PxScDescriptor;
|
|
986
1110
|
// console.log('PxSc', require('util').inspect(desc, false, 99, true));
|
|
@@ -1017,7 +1141,7 @@ addHandler(
|
|
|
1017
1141
|
_classID: 'PixelSource',
|
|
1018
1142
|
pixelSourceType: 1986285651, // vdP
|
|
1019
1143
|
descVersion: 1,
|
|
1020
|
-
origin: { Hrzn: source.origin.x, Vrtc: source.origin.y },
|
|
1144
|
+
origin: { Hrzn: source.origin.x, Vrtc: source.origin.y }, // written as 'null' class with 'long' values (see writeDescriptorStructure)
|
|
1021
1145
|
interpretation: {
|
|
1022
1146
|
_name: '',
|
|
1023
1147
|
_classID: 'footageInterpretation',
|
|
@@ -1066,7 +1190,7 @@ addHandler(
|
|
|
1066
1190
|
lineAlignment: strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
|
|
1067
1191
|
scaleLock: desc.strokeStyleScaleLock,
|
|
1068
1192
|
strokeAdjust: desc.strokeStyleStrokeAdjust,
|
|
1069
|
-
lineDashSet: desc.strokeStyleLineDashSet.map(parseUnits),
|
|
1193
|
+
lineDashSet: (desc.strokeStyleLineDashSet || []).map(parseUnits),
|
|
1070
1194
|
blendMode: BlnM.decode(desc.strokeStyleBlendMode),
|
|
1071
1195
|
opacity: parsePercent(desc.strokeStyleOpacity),
|
|
1072
1196
|
content: parseVectorContent(desc.strokeStyleContent),
|
|
@@ -1105,8 +1229,8 @@ interface ArtbDescriptor {
|
|
|
1105
1229
|
artboardRect: { 'Top ': number; Left: number; Btom: number; Rght: number; };
|
|
1106
1230
|
guideIndeces: any[];
|
|
1107
1231
|
artboardPresetName: string;
|
|
1108
|
-
'Clr '
|
|
1109
|
-
artboardBackgroundType
|
|
1232
|
+
'Clr '?: DescriptorColor;
|
|
1233
|
+
artboardBackgroundType?: number;
|
|
1110
1234
|
}
|
|
1111
1235
|
|
|
1112
1236
|
addHandler(
|
|
@@ -1119,10 +1243,11 @@ addHandler(
|
|
|
1119
1243
|
rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
|
|
1120
1244
|
guideIndices: desc.guideIndeces,
|
|
1121
1245
|
presetName: desc.artboardPresetName,
|
|
1122
|
-
color: parseColor(desc['Clr ']),
|
|
1123
|
-
backgroundType: desc.artboardBackgroundType,
|
|
1124
1246
|
};
|
|
1125
1247
|
|
|
1248
|
+
if (desc['Clr ']) target.artboard.color = parseColor(desc['Clr ']);
|
|
1249
|
+
if (desc.artboardBackgroundType !== undefined) target.artboard.backgroundType = desc.artboardBackgroundType;
|
|
1250
|
+
|
|
1126
1251
|
skipBytes(reader, left());
|
|
1127
1252
|
},
|
|
1128
1253
|
(writer, target) => {
|
|
@@ -1269,13 +1394,13 @@ function encodeWarp(warp: Warp): WarpDescriptor {
|
|
|
1269
1394
|
|
|
1270
1395
|
addHandler(
|
|
1271
1396
|
'PlLd',
|
|
1272
|
-
|
|
1397
|
+
target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
|
|
1273
1398
|
(reader, target, left) => {
|
|
1274
1399
|
if (readSignature(reader) !== 'plcL') throw new Error(`Invalid PlLd signature`);
|
|
1275
1400
|
if (readInt32(reader) !== 3) throw new Error(`Invalid PlLd version`);
|
|
1276
1401
|
const id = readPascalString(reader, 1);
|
|
1277
1402
|
const pageNumber = readInt32(reader);
|
|
1278
|
-
const totalPages = readInt32(reader); //
|
|
1403
|
+
const totalPages = readInt32(reader); // for multi-page documents (PDF)
|
|
1279
1404
|
readInt32(reader); // anitAliasPolicy 16
|
|
1280
1405
|
const placedLayerType = readInt32(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
|
|
1281
1406
|
if (!placedLayerTypes[placedLayerType]) throw new Error('Invalid PlLd type');
|
|
@@ -1827,7 +1952,7 @@ type SoLdDescriptorFilterItem = {
|
|
|
1827
1952
|
'Fl ': string;
|
|
1828
1953
|
};
|
|
1829
1954
|
} | {
|
|
1830
|
-
filterID: 991 | 943; //
|
|
1955
|
+
filterID: 991 | 943; // 943 in older Photoshop versions (2021), 991 in newer ones, settings are the same
|
|
1831
1956
|
Fltr: {
|
|
1832
1957
|
_name: 'Rigid Transform';
|
|
1833
1958
|
_classID: 'rigidTransform';
|
|
@@ -1997,7 +2122,7 @@ type SoLdDescriptorFilterItem = {
|
|
|
1997
2122
|
Fltr: {
|
|
1998
2123
|
_name: 'Perspective Warp';
|
|
1999
2124
|
_classID: 'perspectiveWarpTransform';
|
|
2000
|
-
quads: { indices: number[]; }[];
|
|
2125
|
+
quads: { _name?: ''; _classID?: string; indices: number[]; }[];
|
|
2001
2126
|
vertices: HrznVrtcDescriptor[];
|
|
2002
2127
|
warpedVertices: HrznVrtcDescriptor[];
|
|
2003
2128
|
};
|
|
@@ -2006,7 +2131,7 @@ type SoLdDescriptorFilterItem = {
|
|
|
2006
2131
|
Fltr: {
|
|
2007
2132
|
_name: 'Curves';
|
|
2008
2133
|
_classID: 'Crvs';
|
|
2009
|
-
presetKind
|
|
2134
|
+
presetKind?: string; // 'presetKindType.presetKindCustom';
|
|
2010
2135
|
Adjs?: {
|
|
2011
2136
|
_name: '';
|
|
2012
2137
|
_classID: 'CrvA';
|
|
@@ -2015,6 +2140,14 @@ type SoLdDescriptorFilterItem = {
|
|
|
2015
2140
|
Mpng?: number[];
|
|
2016
2141
|
}[];
|
|
2017
2142
|
};
|
|
2143
|
+
} | {
|
|
2144
|
+
filterID: 1282829427;
|
|
2145
|
+
Fltr: {
|
|
2146
|
+
_name: 'Levels';
|
|
2147
|
+
_classID: 'Lvls';
|
|
2148
|
+
presetKind?: string; // 'presetKindType.presetKindCustom';
|
|
2149
|
+
Adjs?: FilterLevelsAdjustment[];
|
|
2150
|
+
};
|
|
2018
2151
|
} | {
|
|
2019
2152
|
filterID: 1231976050;
|
|
2020
2153
|
} | {
|
|
@@ -2036,6 +2169,18 @@ interface FilterCurvesCurvePoint {
|
|
|
2036
2169
|
Cnty?: boolean;
|
|
2037
2170
|
}
|
|
2038
2171
|
|
|
2172
|
+
interface FilterLevelsAdjustment {
|
|
2173
|
+
_name: '';
|
|
2174
|
+
_classID: 'LvlA';
|
|
2175
|
+
Chnl: string[]; // 'Chnl.Cmps' | 'Chnl.Rd ' | 'Chnl.Grn ' | 'Chnl.Bl '
|
|
2176
|
+
Inpt?: number[]; // shadow, highlight
|
|
2177
|
+
Otpt?: number[]; // shadow, highlight
|
|
2178
|
+
'Gmm '?: number;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
const levelsFilterKeys = ['_name', '_classID', 'presetKind', 'Adjs'];
|
|
2182
|
+
const levelsFilterAdjustmentKeys = ['_name', '_classID', 'Chnl', 'Inpt', 'Otpt', 'Gmm '];
|
|
2183
|
+
|
|
2039
2184
|
interface SoLdDescriptorFilter {
|
|
2040
2185
|
_name: '',
|
|
2041
2186
|
_classID: 'filterFXStyle',
|
|
@@ -2048,11 +2193,13 @@ interface SoLdDescriptorFilter {
|
|
|
2048
2193
|
}
|
|
2049
2194
|
|
|
2050
2195
|
function uint8ToFloat32(array: Uint8Array) {
|
|
2051
|
-
|
|
2196
|
+
const length = Math.floor(array.byteLength / 4);
|
|
2197
|
+
return new Float32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
|
|
2052
2198
|
}
|
|
2053
2199
|
|
|
2054
2200
|
function uint8ToUint32(array: Uint8Array) {
|
|
2055
|
-
|
|
2201
|
+
const length = Math.floor(array.byteLength / 4);
|
|
2202
|
+
return new Uint32Array(array.buffer.slice(array.byteOffset, array.byteOffset + length * 4), 0, length);
|
|
2056
2203
|
}
|
|
2057
2204
|
|
|
2058
2205
|
function toUint8(array: Uint32Array | Float32Array) {
|
|
@@ -2097,7 +2244,7 @@ function pointToHrznVrtc(point: { x: UnitsValue; y: UnitsValue; }): HrznVrtcDesc
|
|
|
2097
2244
|
};
|
|
2098
2245
|
}
|
|
2099
2246
|
|
|
2100
|
-
function parseFilterFXItem(f: SoLdDescriptorFilterItem,
|
|
2247
|
+
function parseFilterFXItem(f: SoLdDescriptorFilterItem, reader: PsdReader): Filter | undefined {
|
|
2101
2248
|
const base: Omit<Filter, 'type' | 'filter'> = {
|
|
2102
2249
|
name: f['Nm '],
|
|
2103
2250
|
opacity: parsePercent(f.blendOptions.Opct),
|
|
@@ -2177,7 +2324,7 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2177
2324
|
undefinedAreas: UndA.decode(f.Fltr.UndA),
|
|
2178
2325
|
displacementFile: {
|
|
2179
2326
|
signature: f.Fltr.DspF.sig,
|
|
2180
|
-
path: f.Fltr.DspF.path,
|
|
2327
|
+
path: f.Fltr.DspF.path,
|
|
2181
2328
|
},
|
|
2182
2329
|
},
|
|
2183
2330
|
};
|
|
@@ -2510,10 +2657,9 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2510
2657
|
{ x: f.Fltr.PuX2, y: f.Fltr.PuY2, },
|
|
2511
2658
|
{ x: f.Fltr.PuX3, y: f.Fltr.PuY3, },
|
|
2512
2659
|
],
|
|
2513
|
-
puppetShapeList: f.Fltr.puppetShapeList
|
|
2660
|
+
puppetShapeList: (f.Fltr.puppetShapeList || []).map(p => ({
|
|
2514
2661
|
rigidType: p.rigidType,
|
|
2515
|
-
//
|
|
2516
|
-
// TODO: VrsN
|
|
2662
|
+
// VrsM / VrsN - version (always 1.0)
|
|
2517
2663
|
originalVertexArray: uint8ToPoints(p.originalVertexArray),
|
|
2518
2664
|
deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
|
|
2519
2665
|
indexArray: Array.from(uint8ToUint32(p.indexArray)),
|
|
@@ -2632,7 +2778,8 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2632
2778
|
...base,
|
|
2633
2779
|
type: 'curves',
|
|
2634
2780
|
filter: {
|
|
2635
|
-
|
|
2781
|
+
// Photoshop doesn't write preset kind for filters with explicit values
|
|
2782
|
+
...(f.Fltr.presetKind !== undefined ? { presetKind: presetKindType.decode(f.Fltr.presetKind) } : {}),
|
|
2636
2783
|
...(f.Fltr.Adjs ? {
|
|
2637
2784
|
adjustments: f.Fltr.Adjs.map(a => {
|
|
2638
2785
|
const channels = a.Chnl.map(Chnl.decode);
|
|
@@ -2655,6 +2802,40 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2655
2802
|
},
|
|
2656
2803
|
};
|
|
2657
2804
|
};
|
|
2805
|
+
case 'Lvls': {
|
|
2806
|
+
const adjustments = f.Fltr.Adjs;
|
|
2807
|
+
const isKnown = (obj: object, keys: string[]) => Object.keys(obj).every(k => keys.indexOf(k) !== -1);
|
|
2808
|
+
|
|
2809
|
+
// skip filter with settings we don't know about
|
|
2810
|
+
if (!isKnown(f.Fltr, levelsFilterKeys) || (adjustments && !adjustments.every(a => isKnown(a, levelsFilterAdjustmentKeys)))) {
|
|
2811
|
+
if (reader.throwForMissingFeatures) throw new Error('Unknown levels filter settings');
|
|
2812
|
+
reader.logMissingFeatures && reader.log(`Skipped levels smart filter with unknown settings`);
|
|
2813
|
+
return undefined;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
return {
|
|
2817
|
+
...base,
|
|
2818
|
+
type: 'levels',
|
|
2819
|
+
filter: {
|
|
2820
|
+
...(f.Fltr.presetKind !== undefined ? { presetKind: presetKindType.decode(f.Fltr.presetKind) } : {}),
|
|
2821
|
+
...(adjustments ? {
|
|
2822
|
+
adjustments: adjustments.map(a => {
|
|
2823
|
+
const result: LevelsFilterAdjustment = { channels: a.Chnl.map(Chnl.decode) };
|
|
2824
|
+
if (a.Inpt) {
|
|
2825
|
+
result.shadowInput = a.Inpt[0];
|
|
2826
|
+
result.highlightInput = a.Inpt[1];
|
|
2827
|
+
}
|
|
2828
|
+
if (a.Otpt) {
|
|
2829
|
+
result.shadowOutput = a.Otpt[0];
|
|
2830
|
+
result.highlightOutput = a.Otpt[1];
|
|
2831
|
+
}
|
|
2832
|
+
if (a['Gmm '] !== undefined) result.midtoneInput = a['Gmm '];
|
|
2833
|
+
return result;
|
|
2834
|
+
}),
|
|
2835
|
+
} : {}),
|
|
2836
|
+
},
|
|
2837
|
+
};
|
|
2838
|
+
};
|
|
2658
2839
|
case 'BrgC': {
|
|
2659
2840
|
return {
|
|
2660
2841
|
...base,
|
|
@@ -2667,10 +2848,11 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2667
2848
|
};
|
|
2668
2849
|
};
|
|
2669
2850
|
default:
|
|
2670
|
-
if (
|
|
2851
|
+
if (reader.throwForMissingFeatures) {
|
|
2671
2852
|
// console.log('FILTER', require('util').inspect(f, false, 99, true));
|
|
2672
2853
|
throw new Error(`Unknown filter classId: ${(f as any).Fltr._classID}`);
|
|
2673
2854
|
}
|
|
2855
|
+
reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${(f as any).Fltr._classID} (${f['Nm ']})`);
|
|
2674
2856
|
return undefined;
|
|
2675
2857
|
}
|
|
2676
2858
|
} else {
|
|
@@ -2689,22 +2871,24 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2689
2871
|
case 1314149187: return { ...base, type: 'ntsc colors' };
|
|
2690
2872
|
case 1231976050: return { ...base, type: 'invert' }
|
|
2691
2873
|
default:
|
|
2692
|
-
if (
|
|
2874
|
+
if (reader.throwForMissingFeatures) {
|
|
2693
2875
|
// console.log('FILTER', require('util').inspect(f, false, 99, true));
|
|
2694
2876
|
throw new Error(`Unknown filterID: ${(f as any).filterID}`);
|
|
2695
2877
|
}
|
|
2878
|
+
reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${(f as any).filterID} (${f['Nm ']})`);
|
|
2879
|
+
return undefined;
|
|
2696
2880
|
}
|
|
2697
2881
|
}
|
|
2698
2882
|
}
|
|
2699
2883
|
|
|
2700
|
-
function parseFilterFX(desc: SoLdDescriptorFilter,
|
|
2884
|
+
function parseFilterFX(desc: SoLdDescriptorFilter, reader: PsdReader): PlacedLayerFilter {
|
|
2701
2885
|
return {
|
|
2702
2886
|
enabled: desc.enab,
|
|
2703
2887
|
validAtPosition: desc.validAtPosition,
|
|
2704
2888
|
maskEnabled: desc.filterMaskEnable,
|
|
2705
2889
|
maskLinked: desc.filterMaskLinked,
|
|
2706
2890
|
maskExtendWithWhite: desc.filterMaskExtendWithWhite,
|
|
2707
|
-
list: desc.filterFXList.map(x => parseFilterFXItem(x,
|
|
2891
|
+
list: desc.filterFXList.map(x => parseFilterFXItem(x, reader)).filter((x): x is Filter => !!x),
|
|
2708
2892
|
};
|
|
2709
2893
|
}
|
|
2710
2894
|
|
|
@@ -3243,14 +3427,14 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3243
3427
|
Fltr: {
|
|
3244
3428
|
_name: 'Rigid Transform',
|
|
3245
3429
|
_classID: 'rigidTransform',
|
|
3246
|
-
'null': ['Ordn.Trgt'], //
|
|
3430
|
+
'null': ['Ordn.Trgt'], // target layer reference
|
|
3247
3431
|
rigidType: f.filter.rigidType,
|
|
3248
|
-
puppetShapeList: f.filter.puppetShapeList.map(p => ({
|
|
3432
|
+
puppetShapeList: (f.filter.puppetShapeList || []).map(p => ({
|
|
3249
3433
|
_name: '',
|
|
3250
3434
|
_classID: 'puppetShape',
|
|
3251
3435
|
rigidType: p.rigidType,
|
|
3252
|
-
VrsM: 1, //
|
|
3253
|
-
VrsN: 0, //
|
|
3436
|
+
VrsM: 1, // version major
|
|
3437
|
+
VrsN: 0, // version minor
|
|
3254
3438
|
originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
|
|
3255
3439
|
deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
|
|
3256
3440
|
indexArray: toUint8(new Uint32Array(p.indexArray)),
|
|
@@ -3356,7 +3540,7 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3356
3540
|
_classID: 'perspectiveWarpTransform',
|
|
3357
3541
|
vertices: f.filter.vertices.map(pointToHrznVrtc),
|
|
3358
3542
|
warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
|
|
3359
|
-
quads: f.filter.quads.map(indices => ({ indices })),
|
|
3543
|
+
quads: f.filter.quads.map(indices => ({ _name: '', _classID: 'quadIndices', indices })),
|
|
3360
3544
|
},
|
|
3361
3545
|
filterID: 442,
|
|
3362
3546
|
};
|
|
@@ -3365,7 +3549,7 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3365
3549
|
Fltr: {
|
|
3366
3550
|
_name: 'Curves',
|
|
3367
3551
|
_classID: 'Crvs',
|
|
3368
|
-
presetKind: presetKindType.encode(f.filter.presetKind),
|
|
3552
|
+
...(f.filter.presetKind !== undefined ? { presetKind: presetKindType.encode(f.filter.presetKind) } : {}),
|
|
3369
3553
|
...(f.filter.adjustments ? {
|
|
3370
3554
|
Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
|
|
3371
3555
|
_name: '',
|
|
@@ -3388,6 +3572,28 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3388
3572
|
},
|
|
3389
3573
|
filterID: 1131574899,
|
|
3390
3574
|
};
|
|
3575
|
+
case 'levels': return {
|
|
3576
|
+
...base,
|
|
3577
|
+
Fltr: {
|
|
3578
|
+
_name: 'Levels',
|
|
3579
|
+
_classID: 'Lvls',
|
|
3580
|
+
...(f.filter.presetKind !== undefined ? { presetKind: presetKindType.encode(f.filter.presetKind) } : {}),
|
|
3581
|
+
...(f.filter.adjustments ? {
|
|
3582
|
+
Adjs: f.filter.adjustments.map(a => {
|
|
3583
|
+
const adjustment: FilterLevelsAdjustment = { _name: '', _classID: 'LvlA', Chnl: a.channels.map(Chnl.encode) };
|
|
3584
|
+
if (a.shadowInput !== undefined || a.highlightInput !== undefined) {
|
|
3585
|
+
adjustment.Inpt = [Math.round(a.shadowInput ?? 0), Math.round(a.highlightInput ?? 255)];
|
|
3586
|
+
}
|
|
3587
|
+
if (a.shadowOutput !== undefined || a.highlightOutput !== undefined) {
|
|
3588
|
+
adjustment.Otpt = [Math.round(a.shadowOutput ?? 0), Math.round(a.highlightOutput ?? 255)];
|
|
3589
|
+
}
|
|
3590
|
+
if (a.midtoneInput !== undefined) adjustment['Gmm '] = a.midtoneInput;
|
|
3591
|
+
return adjustment;
|
|
3592
|
+
}),
|
|
3593
|
+
} : {}),
|
|
3594
|
+
},
|
|
3595
|
+
filterID: 1282829427,
|
|
3596
|
+
};
|
|
3391
3597
|
case 'brightness/contrast': return {
|
|
3392
3598
|
...base,
|
|
3393
3599
|
Fltr: {
|
|
@@ -3399,10 +3605,17 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3399
3605
|
},
|
|
3400
3606
|
filterID: 1114793795,
|
|
3401
3607
|
};
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3608
|
+
case 'hsb/hsl': return {
|
|
3609
|
+
...base,
|
|
3610
|
+
Fltr: {
|
|
3611
|
+
_name: 'HSB/HSL',
|
|
3612
|
+
_classID: 'HsbP',
|
|
3613
|
+
Inpt: ClrS.encode(f.filter.inputMode),
|
|
3614
|
+
Otpt: ClrS.encode(f.filter.rowOrder),
|
|
3615
|
+
},
|
|
3616
|
+
filterID: 1215521360,
|
|
3617
|
+
};
|
|
3618
|
+
default: throw new Error(`Unknown filter type: ${(f as any).type}`);
|
|
3406
3619
|
}
|
|
3407
3620
|
}
|
|
3408
3621
|
|
|
@@ -3424,8 +3637,8 @@ interface SoLdDescriptor {
|
|
|
3424
3637
|
'Sz ': { _name: '', _classID: 'Pnt ', Wdth: number; Hght: number; };
|
|
3425
3638
|
Rslt: DescriptorUnitsValue;
|
|
3426
3639
|
filterFX?: SoLdDescriptorFilter;
|
|
3427
|
-
comp?: number;
|
|
3428
|
-
compInfo?: { compID: number; originalCompID: number; };
|
|
3640
|
+
comp?: number;
|
|
3641
|
+
compInfo?: { compID: number; originalCompID: number; };
|
|
3429
3642
|
Impr?: {}; // ???
|
|
3430
3643
|
ClMg?: { // TODO: support this ?
|
|
3431
3644
|
_name: '';
|
|
@@ -3473,7 +3686,7 @@ function getWarpFromPlacedLayer(placed: PlacedLayer): Warp {
|
|
|
3473
3686
|
|
|
3474
3687
|
addHandler(
|
|
3475
3688
|
'SoLd',
|
|
3476
|
-
|
|
3689
|
+
target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
|
|
3477
3690
|
(reader, target, left) => {
|
|
3478
3691
|
if (readSignature(reader) !== 'soLD') throw new Error(`Invalid SoLd type`);
|
|
3479
3692
|
const version = readInt32(reader);
|
|
@@ -3531,6 +3744,15 @@ addHandler(
|
|
|
3531
3744
|
throw new Error('Placed layer ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
|
|
3532
3745
|
}
|
|
3533
3746
|
|
|
3747
|
+
const placedType = placedLayerTypes.indexOf(placed.type);
|
|
3748
|
+
if (placedType === -1) throw new Error('Invalid placedLayer type');
|
|
3749
|
+
|
|
3750
|
+
const warp = getWarpFromPlacedLayer(placed);
|
|
3751
|
+
// size of linked image matches warp bounds if it's not provided
|
|
3752
|
+
const bounds = warp.bounds;
|
|
3753
|
+
const width = placed.width || (bounds ? bounds.right.value - bounds.left.value : 0);
|
|
3754
|
+
const height = placed.height || (bounds ? bounds.bottom.value - bounds.top.value : 0);
|
|
3755
|
+
|
|
3534
3756
|
const desc: SoLdDescriptor = {
|
|
3535
3757
|
Idnt: placed.id,
|
|
3536
3758
|
placed: placed.placed ?? placed.id,
|
|
@@ -3541,16 +3763,16 @@ addHandler(
|
|
|
3541
3763
|
duration: placed.duration || { numerator: 0, denominator: 600 },
|
|
3542
3764
|
frameCount: placed.frameCount || 0,
|
|
3543
3765
|
Annt: 16,
|
|
3544
|
-
Type:
|
|
3766
|
+
Type: placedType,
|
|
3545
3767
|
Trnf: placed.transform,
|
|
3546
3768
|
nonAffineTransform: placed.nonAffineTransform ?? placed.transform,
|
|
3547
3769
|
// quiltWarp: {} as any,
|
|
3548
|
-
warp: encodeWarp(
|
|
3770
|
+
warp: encodeWarp(warp),
|
|
3549
3771
|
'Sz ': {
|
|
3550
3772
|
_name: '',
|
|
3551
3773
|
_classID: 'Pnt ',
|
|
3552
|
-
Wdth:
|
|
3553
|
-
Hght:
|
|
3774
|
+
Wdth: width,
|
|
3775
|
+
Hght: height,
|
|
3554
3776
|
},
|
|
3555
3777
|
Rslt: placed.resolution ? unitsValue(placed.resolution, 'resolution') : { units: 'Density', value: 72 },
|
|
3556
3778
|
};
|
|
@@ -3568,15 +3790,6 @@ addHandler(
|
|
|
3568
3790
|
};
|
|
3569
3791
|
}
|
|
3570
3792
|
|
|
3571
|
-
// TODO:
|
|
3572
|
-
// desc.comp = -1;
|
|
3573
|
-
// desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
|
|
3574
|
-
// desc.ClMg = {
|
|
3575
|
-
// _name: '',
|
|
3576
|
-
// _classID: 'ClMg',
|
|
3577
|
-
// placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
|
|
3578
|
-
// } as any;
|
|
3579
|
-
|
|
3580
3793
|
// if (JSON.stringify(t) !== JSON.stringify(desc)) {
|
|
3581
3794
|
// console.log('read', require('util').inspect(t, false, 99, true));
|
|
3582
3795
|
// console.log('write', require('util').inspect(desc, false, 99, true));
|
|
@@ -3608,7 +3821,17 @@ addHandler(
|
|
|
3608
3821
|
},
|
|
3609
3822
|
);
|
|
3610
3823
|
|
|
3611
|
-
|
|
3824
|
+
const soLdHandler = infoHandlersMap['SoLd'];
|
|
3825
|
+
|
|
3826
|
+
addHandler(
|
|
3827
|
+
'SoLE', // linked smart object (Photoshop doesn't write PlLd for these)
|
|
3828
|
+
target => !!target.placedLayer?.external,
|
|
3829
|
+
(reader, target, left, psd, imageResources) => {
|
|
3830
|
+
soLdHandler.read(reader, target, left, psd, imageResources);
|
|
3831
|
+
target.placedLayer!.external = true;
|
|
3832
|
+
},
|
|
3833
|
+
soLdHandler.write,
|
|
3834
|
+
);
|
|
3612
3835
|
|
|
3613
3836
|
addHandler(
|
|
3614
3837
|
'fxrp',
|
|
@@ -3625,25 +3848,50 @@ addHandler(
|
|
|
3625
3848
|
},
|
|
3626
3849
|
);
|
|
3627
3850
|
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
)
|
|
3851
|
+
function createGlobalLayerInfoHandler(key: string, bitsPerChannel: number | undefined) {
|
|
3852
|
+
addHandler(
|
|
3853
|
+
key,
|
|
3854
|
+
() => false,
|
|
3855
|
+
(reader, _target, left, psd, imageResources) => {
|
|
3856
|
+
// Photoshop writes empty (or 8bit compatibility) layer info section alongside Lr16/Lr32 in 16/32bit documents,
|
|
3857
|
+
// layers from global info with matching bit depth replace layers from that section, other ones are only used
|
|
3858
|
+
// if the section is empty
|
|
3859
|
+
if (!reader.layersInGlobalInfo && (psd.bitsPerChannel === bitsPerChannel || !psd.children?.length)) {
|
|
3860
|
+
const sectionLayers = psd.children;
|
|
3861
|
+
psd.children = [];
|
|
3862
|
+
|
|
3863
|
+
try {
|
|
3864
|
+
readLayerInfo(reader, psd, imageResources);
|
|
3865
|
+
} catch (e) {
|
|
3866
|
+
psd.children = sectionLayers; // keep layers from layer info section if global info is broken
|
|
3867
|
+
throw e;
|
|
3868
|
+
}
|
|
3637
3869
|
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3870
|
+
reader.layersInGlobalInfo = true;
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
skipBytes(reader, left());
|
|
3874
|
+
},
|
|
3875
|
+
() => { },
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
createGlobalLayerInfoHandler('Lr16', 16);
|
|
3880
|
+
createGlobalLayerInfoHandler('Lr32', 32);
|
|
3881
|
+
createGlobalLayerInfoHandler('Layr', undefined);
|
|
3882
|
+
|
|
3883
|
+
// merged transparency markers, first extra channel of composite image is transparency
|
|
3884
|
+
for (const key of ['Mtrn', 'Mt16', 'Mt32']) {
|
|
3885
|
+
addHandler(
|
|
3886
|
+
key,
|
|
3887
|
+
() => false,
|
|
3888
|
+
(reader, _target, left) => {
|
|
3889
|
+
reader.globalAlpha = true;
|
|
3890
|
+
skipBytes(reader, left());
|
|
3891
|
+
},
|
|
3892
|
+
() => { },
|
|
3893
|
+
);
|
|
3894
|
+
}
|
|
3647
3895
|
|
|
3648
3896
|
addHandler(
|
|
3649
3897
|
'LMsk',
|
|
@@ -3651,7 +3899,7 @@ addHandler(
|
|
|
3651
3899
|
(reader, target) => {
|
|
3652
3900
|
target.userMask = {
|
|
3653
3901
|
colorSpace: readColor(reader),
|
|
3654
|
-
opacity: readUint16(reader) /
|
|
3902
|
+
opacity: readUint16(reader) / 100,
|
|
3655
3903
|
};
|
|
3656
3904
|
const flag = readUint8(reader);
|
|
3657
3905
|
if (flag !== 128) throw new Error('Invalid flag value');
|
|
@@ -3660,7 +3908,7 @@ addHandler(
|
|
|
3660
3908
|
(writer, target) => {
|
|
3661
3909
|
const userMask = target.userMask!;
|
|
3662
3910
|
writeColor(writer, userMask.colorSpace);
|
|
3663
|
-
writeUint16(writer, clamp(userMask.opacity, 0, 1) *
|
|
3911
|
+
writeUint16(writer, Math.round(clamp(userMask.opacity, 0, 1) * 100));
|
|
3664
3912
|
writeUint8(writer, 128);
|
|
3665
3913
|
writeZeros(writer, 1);
|
|
3666
3914
|
},
|
|
@@ -3678,11 +3926,12 @@ if (MOCK_HANDLERS) {
|
|
|
3678
3926
|
);
|
|
3679
3927
|
} else {
|
|
3680
3928
|
addHandler(
|
|
3681
|
-
'Patt', //
|
|
3929
|
+
'Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
|
|
3682
3930
|
target => !!(target.patterns && target.patterns.length > 0),
|
|
3683
3931
|
(reader, target, left) => {
|
|
3684
3932
|
while (left() > 0) {
|
|
3685
|
-
const pattern =
|
|
3933
|
+
const pattern = readPatternSafe(reader);
|
|
3934
|
+
if (!pattern) continue;
|
|
3686
3935
|
if (target.patterns === undefined) target.patterns = [];
|
|
3687
3936
|
target.patterns.push(pattern);
|
|
3688
3937
|
}
|
|
@@ -3810,7 +4059,8 @@ addHandler(
|
|
|
3810
4059
|
const annotations: Annotation[] = [];
|
|
3811
4060
|
|
|
3812
4061
|
for (let i = 0; i < count; i++) {
|
|
3813
|
-
|
|
4062
|
+
const recordStart = reader.offset;
|
|
4063
|
+
const length = readUint32(reader); // includes the length field itself
|
|
3814
4064
|
const type = readSignature(reader);
|
|
3815
4065
|
const open = !!readUint8(reader);
|
|
3816
4066
|
/*const flags =*/ readUint8(reader); // always 28
|
|
@@ -3827,10 +4077,12 @@ addHandler(
|
|
|
3827
4077
|
let data: string | Uint8Array;
|
|
3828
4078
|
|
|
3829
4079
|
if (type === 'txtA') {
|
|
4080
|
+
const dataStart = reader.offset;
|
|
4081
|
+
|
|
3830
4082
|
if (dataLength >= 2 && readUint16(reader) === 0xfeff) {
|
|
3831
|
-
data = readUnicodeStringWithLength(reader, (dataLength - 2) / 2);
|
|
4083
|
+
data = readUnicodeStringWithLength(reader, Math.floor((dataLength - 2) / 2));
|
|
3832
4084
|
} else {
|
|
3833
|
-
reader.offset
|
|
4085
|
+
reader.offset = dataStart;
|
|
3834
4086
|
data = readAsciiString(reader, dataLength);
|
|
3835
4087
|
}
|
|
3836
4088
|
|
|
@@ -3844,6 +4096,8 @@ addHandler(
|
|
|
3844
4096
|
annotations.push({
|
|
3845
4097
|
type: type === 'txtA' ? 'text' : 'sound', open, iconLocation, popupLocation, color, author, name, date, data,
|
|
3846
4098
|
});
|
|
4099
|
+
|
|
4100
|
+
if (length >= (reader.offset - recordStart)) reader.offset = recordStart + length;
|
|
3847
4101
|
}
|
|
3848
4102
|
|
|
3849
4103
|
(target as Psd).annotations = annotations;
|
|
@@ -3907,6 +4161,11 @@ interface LinkedFileDescriptor {
|
|
|
3907
4161
|
relPath: string;
|
|
3908
4162
|
}
|
|
3909
4163
|
|
|
4164
|
+
function readFileTypeSignature(reader: PsdReader) {
|
|
4165
|
+
const value = readSignature(reader);
|
|
4166
|
+
return /^[\0 ]*$/.test(value) ? '' : value.trim(); // empty value is stored as spaces or zeros
|
|
4167
|
+
}
|
|
4168
|
+
|
|
3910
4169
|
function createLnkHandler(tag: string) {
|
|
3911
4170
|
addHandler(
|
|
3912
4171
|
tag,
|
|
@@ -3931,8 +4190,8 @@ function createLnkHandler(tag: string) {
|
|
|
3931
4190
|
const id = readPascalString(reader, 1);
|
|
3932
4191
|
const name = readUnicodeString(reader);
|
|
3933
4192
|
|
|
3934
|
-
const fileType =
|
|
3935
|
-
const fileCreator =
|
|
4193
|
+
const fileType = readFileTypeSignature(reader); // ' ' if empty
|
|
4194
|
+
const fileCreator = readFileTypeSignature(reader); // ' ' or '\0\0\0\0' if empty
|
|
3936
4195
|
const dataSize = readLength64(reader);
|
|
3937
4196
|
const hasFileOpenDescriptor = readUint8(reader);
|
|
3938
4197
|
const fileOpenDescriptor = hasFileOpenDescriptor ? readVersionAndDescriptor(reader) as FileOpenDescriptor : undefined;
|
|
@@ -3998,16 +4257,19 @@ function createLnkHandler(tag: string) {
|
|
|
3998
4257
|
for (const file of psd.linkedFiles!) {
|
|
3999
4258
|
if ((tag === 'lnkE') !== !!file.linkedFile) continue;
|
|
4000
4259
|
|
|
4260
|
+
const external = tag === 'lnkE';
|
|
4001
4261
|
let version = 2;
|
|
4002
4262
|
if (file.assetLockedState != null) version = 7;
|
|
4003
4263
|
else if (file.assetModTime != null) version = 6;
|
|
4004
4264
|
else if (file.childDocumentID != null) version = 5;
|
|
4005
|
-
|
|
4265
|
+
// liFE version 2 is the only one with embedded data, it has no date and its file size is the size of the data,
|
|
4266
|
+
// versions 5+ can't store the data, so it's not written in that case
|
|
4267
|
+
else if (external && !file.data) version = 4;
|
|
4006
4268
|
|
|
4007
|
-
|
|
4269
|
+
writeUint64(writer, 0);
|
|
4008
4270
|
|
|
4009
4271
|
const sizeOffset = writer.offset;
|
|
4010
|
-
writeSignature(writer,
|
|
4272
|
+
writeSignature(writer, external ? 'liFE' : (file.data ? 'liFD' : 'liFA'));
|
|
4011
4273
|
writeInt32(writer, version);
|
|
4012
4274
|
if (!file.id || typeof file.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(file.id)) {
|
|
4013
4275
|
throw new Error('Linked file ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
|
|
@@ -4016,7 +4278,7 @@ function createLnkHandler(tag: string) {
|
|
|
4016
4278
|
writeUnicodeStringWithPadding(writer, file.name || '');
|
|
4017
4279
|
writeSignature(writer, file.type ? `${file.type} `.substring(0, 4) : ' ');
|
|
4018
4280
|
writeSignature(writer, file.creator ? `${file.creator} `.substring(0, 4) : '\0\0\0\0');
|
|
4019
|
-
|
|
4281
|
+
writeUint64(writer, (!external && file.data) ? file.data.byteLength : 0);
|
|
4020
4282
|
|
|
4021
4283
|
if (file.descriptor && file.descriptor.compInfo) {
|
|
4022
4284
|
const desc: FileOpenDescriptor = {
|
|
@@ -4032,7 +4294,7 @@ function createLnkHandler(tag: string) {
|
|
|
4032
4294
|
writeUint8(writer, 0);
|
|
4033
4295
|
}
|
|
4034
4296
|
|
|
4035
|
-
if (
|
|
4297
|
+
if (external) {
|
|
4036
4298
|
const desc: LinkedFileDescriptor = {
|
|
4037
4299
|
descVersion: 2,
|
|
4038
4300
|
'Nm ': file.linkedFile?.name ?? '',
|
|
@@ -4042,7 +4304,9 @@ function createLnkHandler(tag: string) {
|
|
|
4042
4304
|
};
|
|
4043
4305
|
|
|
4044
4306
|
writeVersionAndDescriptor(writer, '', 'ExternalFileLink', desc);
|
|
4307
|
+
}
|
|
4045
4308
|
|
|
4309
|
+
if (external && version > 3) {
|
|
4046
4310
|
const time = file.time ? new Date(file.time) : new Date();
|
|
4047
4311
|
writeInt32(writer, time.getUTCFullYear());
|
|
4048
4312
|
writeUint8(writer, time.getUTCMonth());
|
|
@@ -4052,15 +4316,18 @@ function createLnkHandler(tag: string) {
|
|
|
4052
4316
|
writeFloat64(writer, time.getUTCSeconds() + time.getUTCMilliseconds() / 1000);
|
|
4053
4317
|
}
|
|
4054
4318
|
|
|
4055
|
-
if (
|
|
4056
|
-
|
|
4319
|
+
if (external) {
|
|
4320
|
+
writeUint64(writer, (version === 2 ? file.data?.byteLength : file.linkedFile?.fileSize) || 0); // file size
|
|
4321
|
+
} else if (file.data) {
|
|
4322
|
+
writeBytes(writer, file.data); // liFD
|
|
4057
4323
|
} else {
|
|
4058
|
-
|
|
4324
|
+
writeZeros(writer, 8); // liFA
|
|
4059
4325
|
}
|
|
4060
4326
|
|
|
4061
4327
|
if (version >= 5) writeUnicodeStringWithPadding(writer, file.childDocumentID || '');
|
|
4062
4328
|
if (version >= 6) writeFloat64(writer, file.assetModTime || 0);
|
|
4063
4329
|
if (version >= 7) writeUint8(writer, file.assetLockedState || 0);
|
|
4330
|
+
if (external && version === 2 && file.data) writeBytes(writer, file.data);
|
|
4064
4331
|
|
|
4065
4332
|
let size = writer.offset - sizeOffset;
|
|
4066
4333
|
writer.view.setUint32(sizeOffset - 4, size, false); // write size
|
|
@@ -4082,28 +4349,55 @@ addHandlerAlias('lnk3', 'lnk2');
|
|
|
4082
4349
|
|
|
4083
4350
|
interface PthsDescriptor {
|
|
4084
4351
|
pathList: {
|
|
4352
|
+
_name: '';
|
|
4085
4353
|
_classID: 'pathInfoClass';
|
|
4086
4354
|
pathUnicodeName: string;
|
|
4087
|
-
pathSymmetryClass
|
|
4355
|
+
pathSymmetryClass?: {
|
|
4356
|
+
_name: '';
|
|
4088
4357
|
_classID: 'pathSymmetryClass';
|
|
4089
4358
|
pathSymmetryMode: string; // 'pathSymmetryModeEnum.pathSymmetryModeBasicPath'
|
|
4090
4359
|
};
|
|
4091
4360
|
}[];
|
|
4092
4361
|
}
|
|
4093
4362
|
|
|
4363
|
+
// unicode names of saved paths (path data is stored in image resources 2000-2998)
|
|
4094
4364
|
addHandler(
|
|
4095
4365
|
'pths',
|
|
4096
|
-
|
|
4097
|
-
(reader,
|
|
4366
|
+
target => (target as Psd).imageResources?.savedPaths !== undefined,
|
|
4367
|
+
(reader, _target, left, psd) => {
|
|
4098
4368
|
const desc = readVersionAndDescriptor(reader, true) as PthsDescriptor;
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4369
|
+
const imageResources = psd.imageResources ??= {};
|
|
4370
|
+
const savedPaths = imageResources.savedPaths ??= [];
|
|
4371
|
+
|
|
4372
|
+
for (let i = 0; i < desc.pathList.length && i < savedPaths.length; i++) {
|
|
4373
|
+
const { pathUnicodeName, pathSymmetryClass } = desc.pathList[i];
|
|
4374
|
+
const path = savedPaths[i];
|
|
4375
|
+
|
|
4376
|
+
// clipping path is referenced by (non-unicode) name
|
|
4377
|
+
if (imageResources.clippingPath && imageResources.clippingPath.name === path.name) {
|
|
4378
|
+
imageResources.clippingPath.name = pathUnicodeName;
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
path.name = pathUnicodeName;
|
|
4382
|
+
if (pathSymmetryClass) path.symmetryMode = pathSymmetryClass.pathSymmetryMode.split('.')[1];
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
skipBytes(reader, left());
|
|
4103
4386
|
},
|
|
4104
|
-
(writer, _target) => {
|
|
4387
|
+
(writer, _target, psd) => {
|
|
4105
4388
|
const desc: PthsDescriptor = {
|
|
4106
|
-
pathList:
|
|
4389
|
+
pathList: psd.imageResources!.savedPaths!.map(path => ({
|
|
4390
|
+
_name: '',
|
|
4391
|
+
_classID: 'pathInfoClass',
|
|
4392
|
+
pathUnicodeName: path.name,
|
|
4393
|
+
...(path.symmetryMode ? {
|
|
4394
|
+
pathSymmetryClass: {
|
|
4395
|
+
_name: '',
|
|
4396
|
+
_classID: 'pathSymmetryClass',
|
|
4397
|
+
pathSymmetryMode: `pathSymmetryModeEnum.${path.symmetryMode}`,
|
|
4398
|
+
},
|
|
4399
|
+
} : {}),
|
|
4400
|
+
})),
|
|
4107
4401
|
};
|
|
4108
4402
|
|
|
4109
4403
|
writeVersionAndDescriptor(writer, '', 'pathsDataClass', desc);
|
|
@@ -4118,15 +4412,8 @@ addHandler(
|
|
|
4118
4412
|
);
|
|
4119
4413
|
|
|
4120
4414
|
addHandler(
|
|
4121
|
-
'lfxs',
|
|
4122
|
-
|
|
4123
|
-
// NOTE: this might be insufficient
|
|
4124
|
-
// target => target.effects !== undefined && (
|
|
4125
|
-
// !!target.effects.dropShadow?.some(e => e.choke) ||
|
|
4126
|
-
// !!target.effects.innerShadow?.some(e => e.choke) ||
|
|
4127
|
-
// !!target.effects.outerGlow?.choke ||
|
|
4128
|
-
// !!target.effects.innerGlow?.choke
|
|
4129
|
-
// ),
|
|
4415
|
+
'lfxs', // effects of group layers (instead of 'lfx2' / 'lmfx')
|
|
4416
|
+
target => target.effects !== undefined && isGroupLayer(target),
|
|
4130
4417
|
(reader, target, left) => {
|
|
4131
4418
|
const version = readUint32(reader);
|
|
4132
4419
|
if (version !== 0) throw new Error(`Invalid lfxs version`);
|
|
@@ -4183,6 +4470,12 @@ function readLevelsChannel(reader: PsdReader): LevelsAdjustmentChannel {
|
|
|
4183
4470
|
return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
|
|
4184
4471
|
}
|
|
4185
4472
|
|
|
4473
|
+
const defaultLevelsChannel: LevelsAdjustmentChannel = { shadowInput: 0, highlightInput: 255, shadowOutput: 0, highlightOutput: 255, midtoneInput: 1 };
|
|
4474
|
+
|
|
4475
|
+
function isDefaultLevelsChannel(c: LevelsAdjustmentChannel) {
|
|
4476
|
+
return c.shadowInput === 0 && c.highlightInput === 255 && c.shadowOutput === 0 && c.highlightOutput === 255 && c.midtoneInput === 1;
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4186
4479
|
function writeLevelsChannel(writer: PsdWriter, channel: LevelsAdjustmentChannel) {
|
|
4187
4480
|
writeInt16(writer, channel.shadowInput);
|
|
4188
4481
|
writeInt16(writer, channel.highlightInput);
|
|
@@ -4197,62 +4490,49 @@ addHandler(
|
|
|
4197
4490
|
(reader, target, left) => {
|
|
4198
4491
|
if (readUint16(reader) !== 2) throw new Error('Invalid levl version');
|
|
4199
4492
|
|
|
4200
|
-
const
|
|
4493
|
+
const channels: LevelsAdjustmentChannel[] = [];
|
|
4201
4494
|
|
|
4202
|
-
for (let i = 0; i < 29 && left(); i++) {
|
|
4203
|
-
|
|
4495
|
+
for (let i = 0; i < 29 && left() >= 10; i++) {
|
|
4496
|
+
channels.push(readLevelsChannel(reader));
|
|
4204
4497
|
}
|
|
4205
4498
|
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
target.adjustment = {
|
|
4209
|
-
...target.adjustment as PresetInfo,
|
|
4210
|
-
type: 'levels',
|
|
4211
|
-
rgb,
|
|
4212
|
-
red,
|
|
4213
|
-
green,
|
|
4214
|
-
blue,
|
|
4215
|
-
};
|
|
4216
|
-
|
|
4217
|
-
if (left()) {
|
|
4499
|
+
if (left() >= 8) {
|
|
4218
4500
|
if (readSignature(reader) !== 'Lvls') throw new Error(`Invalid levl signature`);
|
|
4219
4501
|
if (readUint16(reader) !== 3) throw new Error(`Invalid levl version`);
|
|
4220
4502
|
|
|
4221
4503
|
const totalChannelRecords = readUint16(reader);
|
|
4222
4504
|
|
|
4223
|
-
|
|
4224
|
-
|
|
4505
|
+
while (channels.length < totalChannelRecords && left() >= 10) {
|
|
4506
|
+
channels.push(readLevelsChannel(reader));
|
|
4225
4507
|
}
|
|
4226
4508
|
}
|
|
4227
4509
|
|
|
4510
|
+
// records for all possible channels are stored, skip unused ones
|
|
4511
|
+
while (channels.length && isDefaultLevelsChannel(channels[channels.length - 1])) channels.pop();
|
|
4512
|
+
|
|
4513
|
+
target.adjustment = {
|
|
4514
|
+
...target.adjustment as PresetInfo,
|
|
4515
|
+
type: 'levels',
|
|
4516
|
+
channels,
|
|
4517
|
+
};
|
|
4518
|
+
|
|
4228
4519
|
skipBytes(reader, left());
|
|
4229
4520
|
},
|
|
4230
4521
|
(writer, target) => {
|
|
4231
|
-
const
|
|
4232
|
-
const defaultChannel = {
|
|
4233
|
-
shadowInput: 0,
|
|
4234
|
-
highlightInput: 255,
|
|
4235
|
-
shadowOutput: 0,
|
|
4236
|
-
highlightOutput: 255,
|
|
4237
|
-
midtoneInput: 1,
|
|
4238
|
-
};
|
|
4522
|
+
const channels = (target.adjustment as LevelsAdjustment).channels || [];
|
|
4239
4523
|
|
|
4240
4524
|
writeUint16(writer, 2); // version
|
|
4241
|
-
writeLevelsChannel(writer, info.rgb || defaultChannel);
|
|
4242
|
-
writeLevelsChannel(writer, info.red || defaultChannel);
|
|
4243
|
-
writeLevelsChannel(writer, info.green || defaultChannel);
|
|
4244
|
-
writeLevelsChannel(writer, info.blue || defaultChannel);
|
|
4245
4525
|
|
|
4246
|
-
for (let i = 0; i <
|
|
4247
|
-
writeLevelsChannel(writer,
|
|
4526
|
+
for (let i = 0; i < 29; i++) {
|
|
4527
|
+
writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
|
|
4248
4528
|
}
|
|
4249
4529
|
|
|
4250
4530
|
writeSignature(writer, 'Lvls');
|
|
4251
4531
|
writeUint16(writer, 3); // version
|
|
4252
4532
|
writeUint16(writer, 62); // total count
|
|
4253
4533
|
|
|
4254
|
-
for (let i =
|
|
4255
|
-
writeLevelsChannel(writer,
|
|
4534
|
+
for (let i = 29; i < 62; i++) {
|
|
4535
|
+
writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
|
|
4256
4536
|
}
|
|
4257
4537
|
},
|
|
4258
4538
|
);
|
|
@@ -4286,17 +4566,20 @@ addHandler(
|
|
|
4286
4566
|
readUint8(reader);
|
|
4287
4567
|
if (readUint16(reader) !== 1) throw new Error('Invalid curv version');
|
|
4288
4568
|
readUint16(reader);
|
|
4289
|
-
const
|
|
4290
|
-
const
|
|
4569
|
+
const channelsMask = readUint16(reader);
|
|
4570
|
+
const channels: CurvesAdjustmentChannel[] = [];
|
|
4291
4571
|
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4572
|
+
for (let i = 0; i < 16; i++) {
|
|
4573
|
+
if (channelsMask & (1 << i)) {
|
|
4574
|
+
while (channels.length < i) channels.push([]);
|
|
4575
|
+
channels.push(readCurveChannel(reader));
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4296
4578
|
|
|
4297
4579
|
target.adjustment = {
|
|
4298
4580
|
...target.adjustment as PresetInfo,
|
|
4299
|
-
|
|
4581
|
+
type: 'curves',
|
|
4582
|
+
channels,
|
|
4300
4583
|
};
|
|
4301
4584
|
|
|
4302
4585
|
// ignoring, duplicate information
|
|
@@ -4319,35 +4602,33 @@ addHandler(
|
|
|
4319
4602
|
skipBytes(reader, left());
|
|
4320
4603
|
},
|
|
4321
4604
|
(writer, target) => {
|
|
4322
|
-
const
|
|
4323
|
-
const
|
|
4324
|
-
let
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4605
|
+
const channels = ((target.adjustment as CurvesAdjustment).channels || []).slice(0, 16);
|
|
4606
|
+
const used: number[] = [];
|
|
4607
|
+
let channelsMask = 0;
|
|
4608
|
+
|
|
4609
|
+
for (let i = 0; i < channels.length; i++) {
|
|
4610
|
+
if (channels[i]?.length) {
|
|
4611
|
+
channelsMask |= 1 << i;
|
|
4612
|
+
used.push(i);
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4331
4615
|
|
|
4332
4616
|
writeUint8(writer, 0);
|
|
4333
4617
|
writeUint16(writer, 1); // version
|
|
4334
4618
|
writeUint16(writer, 0);
|
|
4335
|
-
writeUint16(writer,
|
|
4619
|
+
writeUint16(writer, channelsMask);
|
|
4336
4620
|
|
|
4337
|
-
|
|
4338
|
-
if (red && red.length) writeCurveChannel(writer, red);
|
|
4339
|
-
if (green && green.length) writeCurveChannel(writer, green);
|
|
4340
|
-
if (blue && blue.length) writeCurveChannel(writer, blue);
|
|
4621
|
+
for (const i of used) writeCurveChannel(writer, channels[i]);
|
|
4341
4622
|
|
|
4342
4623
|
writeSignature(writer, 'Crv ');
|
|
4343
4624
|
writeUint16(writer, 4); // version
|
|
4344
4625
|
writeUint16(writer, 0);
|
|
4345
|
-
writeUint16(writer,
|
|
4626
|
+
writeUint16(writer, used.length);
|
|
4346
4627
|
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4628
|
+
for (const i of used) {
|
|
4629
|
+
writeUint16(writer, i);
|
|
4630
|
+
writeCurveChannel(writer, channels[i]);
|
|
4631
|
+
}
|
|
4351
4632
|
},
|
|
4352
4633
|
);
|
|
4353
4634
|
|
|
@@ -4432,10 +4713,26 @@ addHandler(
|
|
|
4432
4713
|
(reader, target, left) => {
|
|
4433
4714
|
if (readUint16(reader) !== 2) throw new Error('Invalid hue2 version');
|
|
4434
4715
|
|
|
4716
|
+
const colorize = !!readUint8(reader);
|
|
4717
|
+
skipBytes(reader, 1);
|
|
4718
|
+
const colorizeHue = readInt16(reader); // stored as -180..180
|
|
4719
|
+
const colorizeSaturation = readInt16(reader);
|
|
4720
|
+
const colorizeLightness = readInt16(reader);
|
|
4721
|
+
|
|
4435
4722
|
target.adjustment = {
|
|
4436
4723
|
...target.adjustment as PresetInfo,
|
|
4437
4724
|
type: 'hue/saturation',
|
|
4438
|
-
|
|
4725
|
+
colorize,
|
|
4726
|
+
colorization: {
|
|
4727
|
+
hue: colorizeHue < 0 ? colorizeHue + 360 : colorizeHue,
|
|
4728
|
+
saturation: colorizeSaturation,
|
|
4729
|
+
lightness: colorizeLightness,
|
|
4730
|
+
},
|
|
4731
|
+
master: {
|
|
4732
|
+
hue: readInt16(reader),
|
|
4733
|
+
saturation: readInt16(reader),
|
|
4734
|
+
lightness: readInt16(reader),
|
|
4735
|
+
},
|
|
4439
4736
|
reds: readHueChannel(reader),
|
|
4440
4737
|
yellows: readHueChannel(reader),
|
|
4441
4738
|
greens: readHueChannel(reader),
|
|
@@ -4450,7 +4747,15 @@ addHandler(
|
|
|
4450
4747
|
const info = target.adjustment as HueSaturationAdjustment;
|
|
4451
4748
|
|
|
4452
4749
|
writeUint16(writer, 2); // version
|
|
4453
|
-
|
|
4750
|
+
writeUint8(writer, info.colorize ? 1 : 0);
|
|
4751
|
+
writeUint8(writer, 0);
|
|
4752
|
+
const colorizeHue = Math.round(info.colorization?.hue ?? 0);
|
|
4753
|
+
writeInt16(writer, colorizeHue > 180 ? colorizeHue - 360 : colorizeHue);
|
|
4754
|
+
writeInt16(writer, Math.round(info.colorization?.saturation ?? 0));
|
|
4755
|
+
writeInt16(writer, Math.round(info.colorization?.lightness ?? 0));
|
|
4756
|
+
writeInt16(writer, Math.round(info.master?.hue ?? 0));
|
|
4757
|
+
writeInt16(writer, Math.round(info.master?.saturation ?? 0));
|
|
4758
|
+
writeInt16(writer, Math.round(info.master?.lightness ?? 0));
|
|
4454
4759
|
writeHueChannel(writer, info.reds);
|
|
4455
4760
|
writeHueChannel(writer, info.yellows);
|
|
4456
4761
|
writeHueChannel(writer, info.greens);
|
|
@@ -4564,11 +4869,17 @@ addHandler(
|
|
|
4564
4869
|
if (version === 2) {
|
|
4565
4870
|
color = readColor(reader);
|
|
4566
4871
|
} else { // version 3
|
|
4567
|
-
//
|
|
4872
|
+
// XYZ color relative to D50 white point, 32768 = 1.0 (not documented, matches colors of Photoshop presets),
|
|
4873
|
+
// converted to Lab (same scale as readColor()) so it can be written back as version 2
|
|
4874
|
+
const x = readInt32(reader) / 32768;
|
|
4875
|
+
const y = readInt32(reader) / 32768;
|
|
4876
|
+
const z = readInt32(reader) / 32768;
|
|
4877
|
+
if (!(x >= 0 && y >= 0 && z >= 0 && x <= 2 && y <= 2 && z <= 2)) throw new Error('Invalid phfl XYZ color');
|
|
4878
|
+
const f = (t: number) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
4568
4879
|
color = {
|
|
4569
|
-
l:
|
|
4570
|
-
a:
|
|
4571
|
-
b:
|
|
4880
|
+
l: Math.max(0, 116 * f(y) - 16) / 100,
|
|
4881
|
+
a: normalizeLabAB(500 * (f(x) - f(y))),
|
|
4882
|
+
b: normalizeLabAB(200 * (f(y) - f(z))),
|
|
4572
4883
|
};
|
|
4573
4884
|
}
|
|
4574
4885
|
|
|
@@ -4585,7 +4896,7 @@ addHandler(
|
|
|
4585
4896
|
const info = target.adjustment as PhotoFilterAdjustment;
|
|
4586
4897
|
writeUint16(writer, 2); // version
|
|
4587
4898
|
writeColor(writer, info.color || { l: 0, a: 0, b: 0 });
|
|
4588
|
-
writeUint32(writer, (info.density || 0) * 100);
|
|
4899
|
+
writeUint32(writer, Math.round(Math.max(0, info.density || 0) * 100));
|
|
4589
4900
|
writeUint8(writer, info.preserveLuminosity ? 1 : 0);
|
|
4590
4901
|
writeZeros(writer, 3);
|
|
4591
4902
|
},
|
|
@@ -4784,7 +5095,7 @@ addHandler(
|
|
|
4784
5095
|
const hasMethod = !!readUint8(reader); reader.offset--;
|
|
4785
5096
|
if (hasMethod) {
|
|
4786
5097
|
const method = readSignature(reader);
|
|
4787
|
-
info.method = gradientInterpolationMethodType.decode(method);
|
|
5098
|
+
info.method = gradientInterpolationMethodType.decode(`gradientInterpolationMethodType.${method}`);
|
|
4788
5099
|
}
|
|
4789
5100
|
|
|
4790
5101
|
info.name = readUnicodeString(reader);
|
|
@@ -4844,8 +5155,9 @@ addHandler(
|
|
|
4844
5155
|
|
|
4845
5156
|
skipBytes(reader, left());
|
|
4846
5157
|
|
|
4847
|
-
|
|
4848
|
-
for (const s of info.
|
|
5158
|
+
// stop locations are always in 0..4096 range, independent of smoothness
|
|
5159
|
+
for (const s of info.colorStops) s.location /= 4096;
|
|
5160
|
+
for (const s of info.opacityStops) s.location /= 4096;
|
|
4849
5161
|
|
|
4850
5162
|
target.adjustment = info;
|
|
4851
5163
|
},
|
|
@@ -4856,16 +5168,16 @@ addHandler(
|
|
|
4856
5168
|
writeUint8(writer, info.dither ? 1 : 0);
|
|
4857
5169
|
|
|
4858
5170
|
if (info.method !== undefined) {
|
|
4859
|
-
writeSignature(writer, gradientInterpolationMethodType.encode(info.method));
|
|
5171
|
+
writeSignature(writer, gradientInterpolationMethodType.encode(info.method).split('.')[1]);
|
|
4860
5172
|
}
|
|
4861
5173
|
|
|
4862
5174
|
writeUnicodeStringWithPadding(writer, info.name || '');
|
|
4863
5175
|
writeUint16(writer, info.colorStops && info.colorStops.length || 0);
|
|
4864
5176
|
|
|
4865
|
-
const interpolation = Math.round((info.smoothness ?? 1) * 4096);
|
|
5177
|
+
const interpolation = Math.round(clamp(info.smoothness ?? 1, 0, 1) * 4096);
|
|
4866
5178
|
|
|
4867
5179
|
for (const s of info.colorStops || []) {
|
|
4868
|
-
writeUint32(writer, Math.round(s.location *
|
|
5180
|
+
writeUint32(writer, Math.round(clamp(s.location, 0, 1) * 4096));
|
|
4869
5181
|
writeUint32(writer, Math.round(s.midpoint * 100));
|
|
4870
5182
|
writeColor(writer, s.color);
|
|
4871
5183
|
writeZeros(writer, 2);
|
|
@@ -4874,9 +5186,9 @@ addHandler(
|
|
|
4874
5186
|
writeUint16(writer, info.opacityStops && info.opacityStops.length || 0);
|
|
4875
5187
|
|
|
4876
5188
|
for (const s of info.opacityStops || []) {
|
|
4877
|
-
writeUint32(writer, Math.round(s.location *
|
|
5189
|
+
writeUint32(writer, Math.round(clamp(s.location, 0, 1) * 4096));
|
|
4878
5190
|
writeUint32(writer, Math.round(s.midpoint * 100));
|
|
4879
|
-
writeUint16(writer, Math.round(s.opacity * 0xff));
|
|
5191
|
+
writeUint16(writer, Math.round(clamp(s.opacity, 0, 1) * 0xff));
|
|
4880
5192
|
}
|
|
4881
5193
|
|
|
4882
5194
|
writeUint16(writer, 2); // expansion count
|
|
@@ -5102,7 +5414,7 @@ addHandler(
|
|
|
5102
5414
|
target.engineData = fromByteArray(data);
|
|
5103
5415
|
|
|
5104
5416
|
const layersByIndex = getTextLayersSortedByIndex(psd);
|
|
5105
|
-
const engineData = parseEngineData(data);
|
|
5417
|
+
const engineData = parseEngineData(data, reader);
|
|
5106
5418
|
const engineData2 = decodeEngineData2(engineData);
|
|
5107
5419
|
const TextFrameSet = engineData2.ResourceDict.TextFrameSet;
|
|
5108
5420
|
|
|
@@ -5230,7 +5542,7 @@ addHandler(
|
|
|
5230
5542
|
|
|
5231
5543
|
writer.view.setUint32(length2Offset - 4, writer.offset - length2Offset, false);
|
|
5232
5544
|
|
|
5233
|
-
const extra =
|
|
5545
|
+
const extra = mask.extra;
|
|
5234
5546
|
if (extra) {
|
|
5235
5547
|
writeUint8(writer, 1);
|
|
5236
5548
|
writeInt32(writer, extra.top);
|
|
@@ -5262,25 +5574,25 @@ addHandler(
|
|
|
5262
5574
|
(reader, target) => {
|
|
5263
5575
|
target.filterMask = {
|
|
5264
5576
|
colorSpace: readColor(reader),
|
|
5265
|
-
opacity: readUint16(reader) /
|
|
5577
|
+
opacity: readUint16(reader) / 100,
|
|
5266
5578
|
};
|
|
5267
5579
|
},
|
|
5268
5580
|
(writer, target) => {
|
|
5269
5581
|
writeColor(writer, target.filterMask!.colorSpace);
|
|
5270
|
-
writeUint16(writer, clamp(target.filterMask!.opacity ?? 1, 0, 1) *
|
|
5582
|
+
writeUint16(writer, Math.round(clamp(target.filterMask!.opacity ?? 1, 0, 1) * 100));
|
|
5271
5583
|
},
|
|
5272
5584
|
);
|
|
5273
5585
|
|
|
5274
5586
|
interface ArtdDescriptor {
|
|
5275
5587
|
'Cnt ': number;
|
|
5276
|
-
autoExpandOffset
|
|
5277
|
-
origin
|
|
5278
|
-
autoExpandEnabled
|
|
5279
|
-
autoNestEnabled
|
|
5280
|
-
autoPositionEnabled
|
|
5588
|
+
autoExpandOffset?: { Hrzn: number; Vrtc: number; };
|
|
5589
|
+
origin?: { Hrzn: number; Vrtc: number; };
|
|
5590
|
+
autoExpandEnabled?: boolean;
|
|
5591
|
+
autoNestEnabled?: boolean;
|
|
5592
|
+
autoPositionEnabled?: boolean;
|
|
5281
5593
|
shrinkwrapOnSaveEnabled?: boolean;
|
|
5282
|
-
docDefaultNewArtboardBackgroundColor
|
|
5283
|
-
docDefaultNewArtboardBackgroundType
|
|
5594
|
+
docDefaultNewArtboardBackgroundColor?: DescriptorColor;
|
|
5595
|
+
docDefaultNewArtboardBackgroundType?: number;
|
|
5284
5596
|
}
|
|
5285
5597
|
|
|
5286
5598
|
addHandler(
|
|
@@ -5288,17 +5600,15 @@ addHandler(
|
|
|
5288
5600
|
target => (target as Psd).artboards !== undefined,
|
|
5289
5601
|
(reader, target, left) => {
|
|
5290
5602
|
const desc = readVersionAndDescriptor(reader) as ArtdDescriptor;
|
|
5291
|
-
(target as Psd).artboards = {
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
|
|
5301
|
-
};
|
|
5603
|
+
const artboards: Psd['artboards'] = (target as Psd).artboards = { count: desc['Cnt '] };
|
|
5604
|
+
if (desc.autoExpandOffset) artboards.autoExpandOffset = { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc };
|
|
5605
|
+
if (desc.origin) artboards.origin = { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc };
|
|
5606
|
+
if (desc.autoExpandEnabled !== undefined) artboards.autoExpandEnabled = desc.autoExpandEnabled;
|
|
5607
|
+
if (desc.autoNestEnabled !== undefined) artboards.autoNestEnabled = desc.autoNestEnabled;
|
|
5608
|
+
if (desc.autoPositionEnabled !== undefined) artboards.autoPositionEnabled = desc.autoPositionEnabled;
|
|
5609
|
+
artboards.shrinkwrapOnSaveEnabled = !!desc.shrinkwrapOnSaveEnabled;
|
|
5610
|
+
if (desc.docDefaultNewArtboardBackgroundColor) artboards.docDefaultNewArtboardBackgroundColor = parseColor(desc.docDefaultNewArtboardBackgroundColor);
|
|
5611
|
+
if (desc.docDefaultNewArtboardBackgroundType !== undefined) artboards.docDefaultNewArtboardBackgroundType = desc.docDefaultNewArtboardBackgroundType;
|
|
5302
5612
|
|
|
5303
5613
|
skipBytes(reader, left());
|
|
5304
5614
|
},
|
|
@@ -5319,13 +5629,17 @@ addHandler(
|
|
|
5319
5629
|
},
|
|
5320
5630
|
);
|
|
5321
5631
|
|
|
5632
|
+
function isGroupLayer(target: LayerAdditionalInfo) {
|
|
5633
|
+
return !!(target as Layer).children;
|
|
5634
|
+
}
|
|
5635
|
+
|
|
5322
5636
|
export function hasMultiEffects(effects: LayerEffectsInfo) {
|
|
5323
5637
|
return Object.keys(effects).map(key => (effects as any)[key]).some(v => Array.isArray(v) && v.length > 1);
|
|
5324
5638
|
}
|
|
5325
5639
|
|
|
5326
5640
|
addHandler(
|
|
5327
5641
|
'lfx2',
|
|
5328
|
-
target => target.effects !== undefined && !hasMultiEffects(target.effects),
|
|
5642
|
+
target => target.effects !== undefined && !isGroupLayer(target) && !hasMultiEffects(target.effects),
|
|
5329
5643
|
(reader, target, left) => {
|
|
5330
5644
|
const version = readUint32(reader);
|
|
5331
5645
|
if (version !== 0) throw new Error(`Invalid lfx2 version`);
|
|
@@ -5333,8 +5647,7 @@ addHandler(
|
|
|
5333
5647
|
const desc: Lfx2Descriptor & LmfxDescriptor = readVersionAndDescriptor(reader);
|
|
5334
5648
|
// console.log('READ', require('util').inspect(desc, false, 99, true));
|
|
5335
5649
|
|
|
5336
|
-
//
|
|
5337
|
-
// discard if read in 'lrFX' section
|
|
5650
|
+
// discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
|
|
5338
5651
|
target.effects = parseEffects(desc, !!reader.logMissingFeatures);
|
|
5339
5652
|
|
|
5340
5653
|
skipBytes(reader, left());
|
|
@@ -5408,26 +5721,17 @@ addHandler(
|
|
|
5408
5721
|
},
|
|
5409
5722
|
);
|
|
5410
5723
|
|
|
5411
|
-
|
|
5412
|
-
generatorSettings: {
|
|
5413
|
-
generator_45_assets: { json: string; };
|
|
5414
|
-
layerTime: number;
|
|
5415
|
-
};
|
|
5416
|
-
}
|
|
5417
|
-
|
|
5418
|
-
// extension settings ?, ignore it
|
|
5724
|
+
// generator settings of the document (layers store them in 'shmd' section)
|
|
5419
5725
|
addHandler(
|
|
5420
5726
|
'extn',
|
|
5421
|
-
target =>
|
|
5422
|
-
(reader, target) => {
|
|
5423
|
-
const desc
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
if (MOCK_HANDLERS) (target as any)._extn = desc;
|
|
5727
|
+
target => target.generatorSettings !== undefined && isDocument(target),
|
|
5728
|
+
(reader, target, left) => {
|
|
5729
|
+
const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
|
|
5730
|
+
if (desc.generatorSettings) target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
|
|
5731
|
+
skipBytes(reader, left());
|
|
5427
5732
|
},
|
|
5428
5733
|
(writer, target) => {
|
|
5429
|
-
|
|
5430
|
-
if (MOCK_HANDLERS) writeVersionAndDescriptor(writer, '', 'null', (target as any)._extn);
|
|
5734
|
+
writeVersionAndDescriptor(writer, '', 'null', serializeGeneratorSettings(target.generatorSettings!));
|
|
5431
5735
|
},
|
|
5432
5736
|
);
|
|
5433
5737
|
|
|
@@ -5439,7 +5743,7 @@ addHandler(
|
|
|
5439
5743
|
skipBytes(reader, 3);
|
|
5440
5744
|
},
|
|
5441
5745
|
(writer, target) => {
|
|
5442
|
-
writeUint8(writer, target.fillOpacity
|
|
5746
|
+
writeUint8(writer, Math.round(clamp(target.fillOpacity!, 0, 1) * 0xff));
|
|
5443
5747
|
writeZeros(writer, 3);
|
|
5444
5748
|
},
|
|
5445
5749
|
);
|
|
@@ -5450,7 +5754,7 @@ addHandler(
|
|
|
5450
5754
|
(reader, target, left) => {
|
|
5451
5755
|
target.channelBlendingRestrictions = [];
|
|
5452
5756
|
|
|
5453
|
-
while (left()
|
|
5757
|
+
while (left() >= 4) {
|
|
5454
5758
|
target.channelBlendingRestrictions.push(readInt32(reader));
|
|
5455
5759
|
}
|
|
5456
5760
|
},
|