ag-psd 22.0.2 → 23.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ag-psd",
3
- "version": "22.0.2",
3
+ "version": "23.1.0",
4
4
  "description": "Library for reading and writing PSD files",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist-es/index.js",
@@ -1893,6 +1893,15 @@ type SoLdDescriptorFilterItem = {
1893
1893
  _classID: 'LqFy',
1894
1894
  LqMe: Uint8Array;
1895
1895
  };
1896
+ } | {
1897
+ filterID: 442;
1898
+ Fltr: {
1899
+ _name: 'Perspective Warp';
1900
+ _classID: 'perspectiveWarpTransform';
1901
+ quads: { indices: number[] }[];
1902
+ vertices: HrznVrtcDescriptor[];
1903
+ warpedVertices: HrznVrtcDescriptor[];
1904
+ }
1896
1905
  });
1897
1906
 
1898
1907
  interface SoLdDescriptorFilter {
@@ -2474,7 +2483,18 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
2474
2483
  liquifyMesh: f.Fltr.LqMe,
2475
2484
  },
2476
2485
  };
2477
- }
2486
+ };
2487
+ case 'perspectiveWarpTransform': {
2488
+ return {
2489
+ ...base,
2490
+ type: 'perspective warp',
2491
+ filter: {
2492
+ vertices: f.Fltr.vertices.map(hrznVrtcToPoint),
2493
+ warpedVertices: f.Fltr.warpedVertices.map(hrznVrtcToPoint),
2494
+ quads: f.Fltr.quads.map(q => q.indices),
2495
+ },
2496
+ };
2497
+ };
2478
2498
  default:
2479
2499
  if (options.throwForMissingFeatures) {
2480
2500
  throw new Error(`Unknown filter classId: ${(f as any).Fltr._classID}`);
@@ -3154,6 +3174,17 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
3154
3174
  },
3155
3175
  filterID: 1282492025,
3156
3176
  };
3177
+ case 'perspective warp': return {
3178
+ ...base,
3179
+ Fltr: {
3180
+ _name: 'Perspective Warp',
3181
+ _classID: 'perspectiveWarpTransform',
3182
+ vertices: f.filter.vertices.map(pointToHrznVrtc),
3183
+ warpedVertices: f.filter.warpedVertices.map(pointToHrznVrtc),
3184
+ quads: f.filter.quads.map(indices => ({ indices })),
3185
+ },
3186
+ filterID: 442,
3187
+ };
3157
3188
  default: throw new Error(`Unknow filter type: ${(f as any).type}`);
3158
3189
  }
3159
3190
  }
@@ -3603,134 +3634,187 @@ interface FileOpenDescriptor {
3603
3634
  compInfo: { compID: number; originalCompID: number; };
3604
3635
  }
3605
3636
 
3606
- addHandler(
3607
- 'lnk2',
3608
- (target: any) => !!(target as Psd).linkedFiles && (target as Psd).linkedFiles!.length > 0,
3609
- (reader, target, left) => {
3610
- const psd = target as Psd;
3611
- psd.linkedFiles = psd.linkedFiles || [];
3612
-
3613
- while (left() > 8) {
3614
- let size = readLength64(reader); // size
3615
- const startOffset = reader.offset;
3616
- const type = readSignature(reader) as 'liFD' | 'liFE' | 'liFA';
3617
- // liFD - linked file data
3618
- // liFE - linked file external
3619
- // liFA - linked file alias
3620
- const version = readInt32(reader);
3621
- const id = readPascalString(reader, 1);
3622
- const name = readUnicodeString(reader);
3623
-
3624
- const fileType = readSignature(reader).trim(); // ' ' if empty
3625
- const fileCreator = readSignature(reader).trim(); // ' ' or '\0\0\0\0' if empty
3626
- const dataSize = readLength64(reader);
3627
- const hasFileOpenDescriptor = readUint8(reader);
3628
- const fileOpenDescriptor = hasFileOpenDescriptor ? readVersionAndDescriptor(reader) as FileOpenDescriptor : undefined;
3629
- const linkedFileDescriptor = type === 'liFE' ? readVersionAndDescriptor(reader) : undefined;
3630
- const file: LinkedFile = { id, name };
3631
-
3632
- if (fileType) file.type = fileType;
3633
- if (fileCreator) file.creator = fileCreator;
3634
-
3635
- if (fileOpenDescriptor) {
3636
- file.descriptor = {
3637
- compInfo: {
3638
- compID: fileOpenDescriptor.compInfo.compID,
3639
- originalCompID: fileOpenDescriptor.compInfo.originalCompID,
3640
- }
3641
- };
3642
- }
3637
+ interface LinkedFileDescriptor {
3638
+ descVersion: 2;
3639
+ 'Nm ': string;
3640
+ fullPath: string;
3641
+ originalPath: string;
3642
+ relPath: string;
3643
+ }
3643
3644
 
3644
- if (type === 'liFE' && version > 3) {
3645
- const year = readInt32(reader);
3646
- const month = readUint8(reader);
3647
- const day = readUint8(reader);
3648
- const hour = readUint8(reader);
3649
- const minute = readUint8(reader);
3650
- const seconds = readFloat64(reader);
3651
- const wholeSeconds = Math.floor(seconds);
3652
- const ms = (seconds - wholeSeconds) * 1000;
3653
- file.time = (new Date(year, month, day, hour, minute, wholeSeconds, ms)).toISOString();
3645
+ function createLnkHandler(tag: string) {
3646
+ addHandler(
3647
+ tag,
3648
+ (target: any) => {
3649
+ const psd = target as Psd;
3650
+ if (!psd.linkedFiles || !psd.linkedFiles.length) return false;
3651
+ if (tag === 'lnkE' && !psd.linkedFiles.some(f => f.linkedFile)) return false;
3652
+ return true;
3653
+ },
3654
+ (reader, target, left, _psd) => {
3655
+ const psd = target as Psd;
3656
+ psd.linkedFiles = psd.linkedFiles || [];
3657
+
3658
+ while (left() > 8) {
3659
+ let size = readLength64(reader);
3660
+ const startOffset = reader.offset;
3661
+ const type = readSignature(reader) as 'liFD' | 'liFE' | 'liFA';
3662
+ // liFD - linked file data
3663
+ // liFE - linked file external
3664
+ // liFA - linked file alias
3665
+ const version = readInt32(reader);
3666
+ const id = readPascalString(reader, 1);
3667
+ const name = readUnicodeString(reader);
3668
+
3669
+ const fileType = readSignature(reader).trim(); // ' ' if empty
3670
+ const fileCreator = readSignature(reader).trim(); // ' ' or '\0\0\0\0' if empty
3671
+ const dataSize = readLength64(reader);
3672
+ const hasFileOpenDescriptor = readUint8(reader);
3673
+ const fileOpenDescriptor = hasFileOpenDescriptor ? readVersionAndDescriptor(reader) as FileOpenDescriptor : undefined;
3674
+ const linkedFileDescriptor = type === 'liFE' ? readVersionAndDescriptor(reader) as LinkedFileDescriptor : undefined;
3675
+ const file: LinkedFile = { id, name };
3676
+
3677
+ if (fileType) file.type = fileType;
3678
+ if (fileCreator) file.creator = fileCreator;
3679
+
3680
+ if (fileOpenDescriptor) {
3681
+ file.descriptor = {
3682
+ compInfo: {
3683
+ compID: fileOpenDescriptor.compInfo.compID,
3684
+ originalCompID: fileOpenDescriptor.compInfo.originalCompID,
3685
+ }
3686
+ };
3687
+ }
3688
+
3689
+ if (type === 'liFE' && version > 3) {
3690
+ const year = readInt32(reader);
3691
+ const month = readUint8(reader);
3692
+ const day = readUint8(reader);
3693
+ const hour = readUint8(reader);
3694
+ const minute = readUint8(reader);
3695
+ const seconds = readFloat64(reader);
3696
+ const wholeSeconds = Math.floor(seconds);
3697
+ const ms = (seconds - wholeSeconds) * 1000;
3698
+ file.time = (new Date(Date.UTC(year, month, day, hour, minute, wholeSeconds, ms))).toISOString();
3699
+ }
3700
+
3701
+ const fileSize = type === 'liFE' ? readLength64(reader) : 0;
3702
+
3703
+ if (type === 'liFA') skipBytes(reader, 8);
3704
+ if (type === 'liFD') file.data = readBytes(reader, dataSize); // seems to be a typo in docs
3705
+ if (version >= 5) file.childDocumentID = readUnicodeString(reader);
3706
+ if (version >= 6) file.assetModTime = readFloat64(reader);
3707
+ if (version >= 7) file.assetLockedState = readUint8(reader);
3708
+ if (type === 'liFE' && version === 2) file.data = readBytes(reader, fileSize);
3709
+
3710
+ if (reader.skipLinkedFilesData) file.data = undefined;
3711
+
3712
+ if (tag === 'lnkE') {
3713
+ file.linkedFile = {
3714
+ fileSize,
3715
+ name: linkedFileDescriptor?.['Nm '] || '',
3716
+ fullPath: linkedFileDescriptor?.fullPath || '',
3717
+ originalPath: linkedFileDescriptor?.originalPath || '',
3718
+ relativePath: linkedFileDescriptor?.relPath || '',
3719
+ };
3720
+ }
3721
+
3722
+ psd.linkedFiles.push(file);
3723
+
3724
+ while (size % 4) size++;
3725
+ reader.offset = startOffset + size;
3654
3726
  }
3655
3727
 
3656
- const fileSize = type === 'liFE' ? readLength64(reader) : 0;
3657
- if (type === 'liFA') skipBytes(reader, 8);
3658
- if (type === 'liFD') file.data = readBytes(reader, dataSize); // seems to be a typo in docs
3659
- if (version >= 5) file.childDocumentID = readUnicodeString(reader);
3660
- if (version >= 6) file.assetModTime = readFloat64(reader);
3661
- if (version >= 7) file.assetLockedState = readUint8(reader);
3662
- if (type === 'liFE' && version === 2) file.data = readBytes(reader, fileSize);
3728
+ skipBytes(reader, left()); // ?
3729
+ },
3730
+ (writer, target) => {
3731
+ const psd = target as Psd;
3663
3732
 
3664
- if (reader.skipLinkedFilesData) file.data = undefined;
3733
+ for (const file of psd.linkedFiles!) {
3734
+ if ((tag === 'lnkE') !== !!file.linkedFile) continue;
3665
3735
 
3666
- psd.linkedFiles.push(file);
3667
- linkedFileDescriptor;
3736
+ let version = 2;
3668
3737
 
3669
- while (size % 4) size++;
3670
- reader.offset = startOffset + size;
3671
- }
3738
+ if (file.assetLockedState != null) version = 7;
3739
+ else if (file.assetModTime != null) version = 6;
3740
+ else if (file.childDocumentID != null) version = 5;
3741
+ else if (tag == 'lnkE') version = 3;
3672
3742
 
3673
- skipBytes(reader, left()); // ?
3674
- },
3675
- (writer, target) => {
3676
- const psd = target as Psd;
3743
+ writeLength64(writer, 0);
3677
3744
 
3678
- for (const file of psd.linkedFiles!) {
3679
- let version = 2;
3745
+ const sizeOffset = writer.offset;
3746
+ writeSignature(writer, (tag === 'lnkE') ? 'liFE' : (file.data ? 'liFD' : 'liFA'));
3747
+ writeInt32(writer, version);
3748
+ if (!file.id || typeof file.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(file.id)) {
3749
+ throw new Error('Linked file ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
3750
+ }
3751
+ writePascalString(writer, file.id, 1);
3752
+ writeUnicodeStringWithPadding(writer, file.name || '');
3753
+ writeSignature(writer, file.type ? `${file.type} `.substring(0, 4) : ' ');
3754
+ writeSignature(writer, file.creator ? `${file.creator} `.substring(0, 4) : '\0\0\0\0');
3755
+ writeLength64(writer, file.data ? file.data.byteLength : 0);
3756
+
3757
+ if (file.descriptor && file.descriptor.compInfo) {
3758
+ const desc: FileOpenDescriptor = {
3759
+ compInfo: {
3760
+ compID: file.descriptor.compInfo.compID,
3761
+ originalCompID: file.descriptor.compInfo.originalCompID,
3762
+ },
3763
+ };
3680
3764
 
3681
- if (file.assetLockedState != null) version = 7;
3682
- else if (file.assetModTime != null) version = 6;
3683
- else if (file.childDocumentID != null) version = 5;
3684
- // TODO: else if (file.time != null) version = 3; (only for liFE)
3765
+ writeUint8(writer, 1);
3766
+ writeVersionAndDescriptor(writer, '', 'null', desc);
3767
+ } else {
3768
+ writeUint8(writer, 0);
3769
+ }
3685
3770
 
3686
- writeUint32(writer, 0);
3687
- writeUint32(writer, 0); // size
3688
- const sizeOffset = writer.offset;
3689
- writeSignature(writer, file.data ? 'liFD' : 'liFA');
3690
- writeInt32(writer, version);
3691
- if (!file.id || typeof file.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(file.id)) {
3692
- throw new Error('Linked file ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
3693
- }
3694
- writePascalString(writer, file.id, 1);
3695
- writeUnicodeStringWithPadding(writer, file.name || '');
3696
- writeSignature(writer, file.type ? `${file.type} `.substring(0, 4) : ' ');
3697
- writeSignature(writer, file.creator ? `${file.creator} `.substring(0, 4) : '\0\0\0\0');
3698
- writeLength64(writer, file.data ? file.data.byteLength : 0);
3699
-
3700
- if (file.descriptor && file.descriptor.compInfo) {
3701
- const desc: FileOpenDescriptor = {
3702
- compInfo: {
3703
- compID: file.descriptor.compInfo.compID,
3704
- originalCompID: file.descriptor.compInfo.originalCompID,
3705
- }
3706
- };
3771
+ if (tag === 'lnkE') {
3772
+ const desc: LinkedFileDescriptor = {
3773
+ descVersion: 2,
3774
+ 'Nm ': file.linkedFile?.name ?? '',
3775
+ fullPath: file.linkedFile?.fullPath ?? '',
3776
+ originalPath: file.linkedFile?.originalPath ?? '',
3777
+ relPath: file.linkedFile?.relativePath ?? '',
3778
+ };
3707
3779
 
3708
- writeUint8(writer, 1);
3709
- writeVersionAndDescriptor(writer, '', 'null', desc);
3710
- } else {
3711
- writeUint8(writer, 0);
3712
- }
3780
+ writeVersionAndDescriptor(writer, '', 'ExternalFileLink', desc);
3781
+
3782
+ const time = file.time ? new Date(file.time) : new Date();
3783
+ writeInt32(writer, time.getUTCFullYear());
3784
+ writeUint8(writer, time.getUTCMonth());
3785
+ writeUint8(writer, time.getUTCDate());
3786
+ writeUint8(writer, time.getUTCHours());
3787
+ writeUint8(writer, time.getUTCMinutes());
3788
+ writeFloat64(writer, time.getUTCSeconds() + time.getUTCMilliseconds() / 1000);
3789
+ }
3790
+
3791
+ if (file.data) {
3792
+ writeBytes(writer, file.data);
3793
+ } else {
3794
+ writeLength64(writer, file.linkedFile?.fileSize || 0);
3795
+ }
3713
3796
 
3714
- if (file.data) writeBytes(writer, file.data);
3715
- else writeLength64(writer, 0);
3716
- if (version >= 5) writeUnicodeStringWithPadding(writer, file.childDocumentID || '');
3717
- if (version >= 6) writeFloat64(writer, file.assetModTime || 0);
3718
- if (version >= 7) writeUint8(writer, file.assetLockedState || 0);
3797
+ if (version >= 5) writeUnicodeStringWithPadding(writer, file.childDocumentID || '');
3798
+ if (version >= 6) writeFloat64(writer, file.assetModTime || 0);
3799
+ if (version >= 7) writeUint8(writer, file.assetLockedState || 0);
3719
3800
 
3720
- let size = writer.offset - sizeOffset;
3721
- writer.view.setUint32(sizeOffset - 4, size, false); // write size
3801
+ let size = writer.offset - sizeOffset;
3802
+ writer.view.setUint32(sizeOffset - 4, size, false); // write size
3722
3803
 
3723
- while (size % 4) {
3724
- size++;
3725
- writeUint8(writer, 0);
3804
+ while (size % 4) {
3805
+ size++;
3806
+ writeUint8(writer, 0);
3807
+ }
3726
3808
  }
3727
- }
3728
- },
3729
- );
3809
+ },
3810
+ );
3811
+ }
3812
+
3813
+ createLnkHandler('lnk2');
3814
+ createLnkHandler('lnkE');
3730
3815
 
3731
3816
  addHandlerAlias('lnkD', 'lnk2');
3732
3817
  addHandlerAlias('lnk3', 'lnk2');
3733
- addHandlerAlias('lnkE', 'lnk2');
3734
3818
 
3735
3819
  interface PthsDescriptor {
3736
3820
  pathList: {
package/src/descriptor.ts CHANGED
@@ -178,7 +178,7 @@ const typeToField: { [key: string]: string[]; } = {
178
178
  'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName',
179
179
  'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
180
180
  'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
181
- 'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment',
181
+ 'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath',
182
182
  ],
183
183
  'tdta': [
184
184
  'EngineData', 'LUT3DFileData', 'indexArray', 'originalVertexArray', 'deformedVertexArray',
@@ -935,7 +935,13 @@ export interface WarpDescriptor {
935
935
  Left: DescriptorUnitsValue;
936
936
  Btom: DescriptorUnitsValue;
937
937
  Rght: DescriptorUnitsValue;
938
- };
938
+ } | {
939
+ _classID: 'classFloatRect',
940
+ 'Top ': number,
941
+ Left: number,
942
+ Btom: number,
943
+ Rght: number,
944
+ },
939
945
  uOrder: number;
940
946
  vOrder: number;
941
947
  customEnvelopeWarp?: {
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { Psd, ReadOptions, WriteOptions } from './psd';
2
2
  import { PsdWriter, writePsd as writePsdInternal, getWriterBuffer, createWriter, getWriterBufferNoCopy } from './psdWriter';
3
3
  import { PsdReader, readPsd as readPsdInternal, createReader } from './psdReader';
4
+ import { fromByteArray } from 'base64-js';
5
+
4
6
  export * from './abr';
5
7
  export * from './csh';
6
8
  export { initializeCanvas } from './helpers';
7
9
  export * from './psd';
8
- import { fromByteArray } from 'base64-js';
9
- export { PsdReader, PsdWriter };
10
+ export type { PsdReader, PsdWriter };
10
11
 
11
12
  interface BufferLike {
12
13
  buffer: ArrayBuffer;
package/src/psd.ts CHANGED
@@ -678,7 +678,7 @@ export interface SelectiveColorAdjustment {
678
678
  blacks?: CMYK;
679
679
  }
680
680
 
681
- export interface LinkedFile {
681
+ export type LinkedFile = {
682
682
  id: string; // must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)
683
683
  name: string;
684
684
  type?: string;
@@ -691,6 +691,15 @@ export interface LinkedFile {
691
691
  childDocumentID?: string;
692
692
  assetModTime?: number;
693
693
  assetLockedState?: number;
694
+
695
+ // external files
696
+ linkedFile?: {
697
+ fileSize: number;
698
+ name: string;
699
+ fullPath: string;
700
+ originalPath: string;
701
+ relativePath: string;
702
+ };
694
703
  }
695
704
 
696
705
  type FilterVariant = {
@@ -1110,6 +1119,13 @@ type FilterVariant = {
1110
1119
  filter: {
1111
1120
  liquifyMesh: Uint8Array;
1112
1121
  };
1122
+ } | {
1123
+ type: 'perspective warp';
1124
+ filter: {
1125
+ quads: number[][]; // quad indices
1126
+ vertices: { x: UnitsValue; y: UnitsValue; }[];
1127
+ warpedVertices: { x: UnitsValue; y: UnitsValue; }[];
1128
+ }
1113
1129
  };
1114
1130
 
1115
1131
  /*
package/src/psdWriter.ts CHANGED
@@ -476,13 +476,6 @@ function addChildren(layers: Layer[], children: Layer[] | undefined) {
476
476
  sectionDivider: {
477
477
  type: SectionDividerType.BoundingSectionDivider,
478
478
  },
479
- // TESTING
480
- // nameSource: 'lset',
481
- // id: [4, 0, 0, 8, 11, 0, 0, 0, 0, 14][layers.length] || 0,
482
- // layerColor: 'none',
483
- // timestamp: [1611346817.349021, 0, 0, 1611346817.349175, 1611346817.3491833, 0, 0, 0, 0, 1611346817.349832][layers.length] || 0,
484
- // protected: {},
485
- // referencePoint: { x: 0, y: 0 },
486
479
  });
487
480
  addChildren(layers, c.children);
488
481
  layers.push({