@pixerate/editor 0.103.0 → 0.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/THIRD_PARTY_NOTICES.md +2 -0
  2. package/dist/{chunk-NQLQ2JOI.js → chunk-4X5MIVCE.js} +1155 -295
  3. package/dist/chunk-4X5MIVCE.js.map +1 -0
  4. package/dist/{chunk-VSMM7B3P.cjs → chunk-ZID226ZG.cjs} +1169 -309
  5. package/dist/chunk-ZID226ZG.cjs.map +1 -0
  6. package/dist/{compositor-CTMU-B1t.d.cts → compositor-Bc3PNP3C.d.cts} +1 -1
  7. package/dist/{compositor-B1ZrKR89.d.ts → compositor-BsYYllvR.d.ts} +1 -1
  8. package/dist/{export-X9rY79p0.d.cts → export-_m3Es4ru.d.cts} +19 -4
  9. package/dist/{export-BLpMyCYg.d.ts → export-iMrop3Jm.d.ts} +19 -4
  10. package/dist/image-editor/index.cjs +523 -539
  11. package/dist/image-editor/index.cjs.map +1 -1
  12. package/dist/image-editor/index.d.cts +16 -8
  13. package/dist/image-editor/index.d.ts +16 -8
  14. package/dist/image-editor/index.js +37 -53
  15. package/dist/image-editor/index.js.map +1 -1
  16. package/dist/{mixer-DGcH6HLu.d.cts → mixer-C1M1Nua_.d.cts} +1 -1
  17. package/dist/{mixer-DG_9Yu2W.d.ts → mixer-Cu5a9Bzd.d.ts} +1 -1
  18. package/dist/paint/index.d.cts +2 -2
  19. package/dist/paint/index.d.ts +2 -2
  20. package/dist/paint-editor/index.cjs +1531 -128
  21. package/dist/paint-editor/index.cjs.map +1 -1
  22. package/dist/paint-editor/index.d.cts +461 -14
  23. package/dist/paint-editor/index.d.ts +461 -14
  24. package/dist/paint-editor/index.js +1479 -76
  25. package/dist/paint-editor/index.js.map +1 -1
  26. package/dist/raster/index.d.cts +4 -4
  27. package/dist/raster/index.d.ts +4 -4
  28. package/dist/{surface-AfHCH7BR.d.cts → surface-BBJN9wxg.d.cts} +1 -1
  29. package/dist/{surface-AfHCH7BR.d.ts → surface-BBJN9wxg.d.ts} +1 -1
  30. package/package.json +1 -1
  31. package/skills/pixerate-image-editor/SKILL.md +14 -5
  32. package/skills/pixerate-paint-editor/SKILL.md +49 -8
  33. package/dist/chunk-NQLQ2JOI.js.map +0 -1
  34. package/dist/chunk-VSMM7B3P.cjs.map +0 -1
@@ -16,6 +16,7 @@ import {
16
16
  effectsFingerprint,
17
17
  effectsMargin,
18
18
  effectsScale,
19
+ encodePng,
19
20
  findLayer,
20
21
  flattenLayers,
21
22
  gaussianBlur,
@@ -60,8 +61,10 @@ import {
60
61
  translateSurface
61
62
  } from "./chunk-HEW6KDNI.js";
62
63
  import {
64
+ WARP_STYLES,
63
65
  applyMatrix,
64
66
  evalWarpMesh,
67
+ fitWarpMesh,
65
68
  identityWarpMesh,
66
69
  invertMatrix,
67
70
  isValidWarpMesh,
@@ -855,21 +858,21 @@ function toBase64(bytes, length) {
855
858
  parts.push(chunk);
856
859
  return parts.join("");
857
860
  }
858
- function fromBase64(text2) {
859
- if (text2.length % 4 !== 0) throw new Error("Invalid base64 length");
861
+ function fromBase64(text3) {
862
+ if (text3.length % 4 !== 0) throw new Error("Invalid base64 length");
860
863
  let pad = 0;
861
- if (text2.endsWith("==")) pad = 2;
862
- else if (text2.endsWith("=")) pad = 1;
863
- const out = new Uint8Array(text2.length / 4 * 3 - pad);
864
+ if (text3.endsWith("==")) pad = 2;
865
+ else if (text3.endsWith("=")) pad = 1;
866
+ const out = new Uint8Array(text3.length / 4 * 3 - pad);
864
867
  let o = 0;
865
868
  const digit = (i, k) => {
866
- const ch = text2.charCodeAt(i + k);
867
- if (ch === 61 && i + 4 === text2.length && k >= 4 - pad) return 0;
869
+ const ch = text3.charCodeAt(i + k);
870
+ if (ch === 61 && i + 4 === text3.length && k >= 4 - pad) return 0;
868
871
  const idx = ch < 128 ? B64_INDEX[ch] : -1;
869
872
  if (idx < 0) throw new Error("Invalid base64 character");
870
873
  return idx;
871
874
  };
872
- for (let i = 0; i < text2.length; i += 4) {
875
+ for (let i = 0; i < text3.length; i += 4) {
873
876
  const n = digit(i, 0) << 18 | digit(i, 1) << 12 | digit(i, 2) << 6 | digit(i, 3);
874
877
  if (o < out.length) out[o++] = n >> 16 & 255;
875
878
  if (o < out.length) out[o++] = n >> 8 & 255;
@@ -4030,8 +4033,8 @@ var PsdReader = class _PsdReader {
4030
4033
  return v;
4031
4034
  }
4032
4035
  /** A length field: 8 bytes in PSB where the format says so, else 4. */
4033
- len(long3) {
4034
- return long3 ? this.u64() : this.u32();
4036
+ len(long4) {
4037
+ return long4 ? this.u64() : this.u32();
4035
4038
  }
4036
4039
  bytes(n) {
4037
4040
  this.need(n, `${n} bytes`);
@@ -4138,11 +4141,11 @@ var PsdWriter = class {
4138
4141
  this.length += n;
4139
4142
  }
4140
4143
  /** Reserves a length field; `endLen` fills it with the bytes written since. */
4141
- beginLen(long3) {
4144
+ beginLen(long4) {
4142
4145
  const at = this.length;
4143
- if (long3) this.u64(0);
4146
+ if (long4) this.u64(0);
4144
4147
  else this.u32(0);
4145
- return { at, long: long3 };
4148
+ return { at, long: long4 };
4146
4149
  }
4147
4150
  endLen(mark) {
4148
4151
  const n = this.length - mark.at - (mark.long ? 8 : 4);
@@ -4187,6 +4190,162 @@ function encodeLegacyName(s) {
4187
4190
  return Uint8Array.from(out);
4188
4191
  }
4189
4192
 
4193
+ // src/image-editor/psd/compression.ts
4194
+ var MAX_DECODED_BYTES = 2 ** 31;
4195
+ function rowBytes(width, depth) {
4196
+ return depth === 1 ? Math.ceil(width / 8) : width * Math.max(1, depth >> 3);
4197
+ }
4198
+ function decodedLength(l) {
4199
+ const total = l.planes * l.height * rowBytes(l.width, l.depth);
4200
+ if (total > MAX_DECODED_BYTES) throw new PsdError(`Decoded channel data of ${total} bytes is larger than the ${MAX_DECODED_BYTES}-byte limit.`);
4201
+ return total;
4202
+ }
4203
+ function packbitsBound(n) {
4204
+ return n + Math.ceil(n / 128);
4205
+ }
4206
+ function packbitsEncode(src, out, at) {
4207
+ const n = src.length;
4208
+ let i = 0;
4209
+ while (i < n) {
4210
+ let run = 1;
4211
+ while (i + run < n && run < 128 && src[i + run] === src[i]) run++;
4212
+ if (run >= 2) {
4213
+ out[at++] = 257 - run & 255;
4214
+ out[at++] = src[i];
4215
+ i += run;
4216
+ continue;
4217
+ }
4218
+ const start = i;
4219
+ let len = 0;
4220
+ while (i < n && len < 128) {
4221
+ if (i + 2 < n && src[i] === src[i + 1] && src[i] === src[i + 2]) break;
4222
+ i++;
4223
+ len++;
4224
+ }
4225
+ out[at++] = len - 1;
4226
+ out.set(src.subarray(start, start + len), at);
4227
+ at += len;
4228
+ }
4229
+ return at;
4230
+ }
4231
+ function packbitsDecodeInto(src, expected, out, at) {
4232
+ const target = at + expected;
4233
+ let i = 0;
4234
+ while (at < target) {
4235
+ if (i >= src.length) throw new PsdError("PackBits row ended early.");
4236
+ const h = src[i++];
4237
+ if (h < 128) {
4238
+ const len = h + 1;
4239
+ if (i + len > src.length) throw new PsdError("PackBits literal is truncated.");
4240
+ if (at + len > target) throw new PsdError("PackBits literal overflows its row.");
4241
+ out.set(src.subarray(i, i + len), at);
4242
+ at += len;
4243
+ i += len;
4244
+ } else if (h !== 128) {
4245
+ const len = 257 - h;
4246
+ if (i >= src.length) throw new PsdError("PackBits run is truncated.");
4247
+ const b = src[i++];
4248
+ if (at + len > target) throw new PsdError("PackBits run overflows its row.");
4249
+ out.fill(b, at, at + len);
4250
+ at += len;
4251
+ }
4252
+ }
4253
+ return at;
4254
+ }
4255
+ function inflateChecked(data, expected) {
4256
+ let out;
4257
+ try {
4258
+ out = inflate(data, { maxOutput: expected + (1 << 20), sizeHint: expected, verify: false });
4259
+ } catch (e) {
4260
+ throw new PsdError(`ZIP channel data: ${e instanceof ZlibError || e instanceof Error ? e.message : String(e)}`);
4261
+ }
4262
+ if (out.length < expected) throw new PsdError(`ZIP stream produced ${out.length} of ${expected} bytes.`);
4263
+ return out.length === expected ? out : out.subarray(0, expected);
4264
+ }
4265
+ function unpredict(buf, l) {
4266
+ const rb = rowBytes(l.width, l.depth);
4267
+ if (rb === 0) return;
4268
+ const w = l.width;
4269
+ const scratch = l.depth === 32 ? new Uint8Array(rb) : null;
4270
+ for (let o = 0; o + rb <= buf.length; o += rb) {
4271
+ if (l.depth === 1 || l.depth === 8) {
4272
+ for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
4273
+ } else if (l.depth === 16) {
4274
+ for (let i = 1; i < w; i++) {
4275
+ const a = buf[o + 2 * i] << 8 | buf[o + 2 * i + 1];
4276
+ const b = buf[o + 2 * i - 2] << 8 | buf[o + 2 * i - 1];
4277
+ const v = a + b & 65535;
4278
+ buf[o + 2 * i] = v >> 8;
4279
+ buf[o + 2 * i + 1] = v & 255;
4280
+ }
4281
+ } else if (l.depth === 32) {
4282
+ for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
4283
+ scratch.set(buf.subarray(o, o + rb));
4284
+ for (let i = 0; i < w; i++) for (let k = 0; k < 4; k++) buf[o + 4 * i + k] = scratch[k * w + i];
4285
+ } else {
4286
+ throw new PsdError(`ZIP prediction is not defined for depth ${l.depth}.`);
4287
+ }
4288
+ }
4289
+ }
4290
+ function decodeRle(r, l, total) {
4291
+ const rows = l.planes * l.height;
4292
+ const rb = rowBytes(l.width, l.depth);
4293
+ r.checkCount(rows, l.psb ? 4 : 2, "RLE row counts");
4294
+ const counts = new Array(rows);
4295
+ for (let i = 0; i < rows; i++) counts[i] = l.psb ? r.u32() : r.u16();
4296
+ if (total > r.remaining * 130) throw new PsdError("RLE data is too short for its declared size.");
4297
+ const out = new Uint8Array(total);
4298
+ let at = 0;
4299
+ for (const c of counts) at = packbitsDecodeInto(r.bytes(c), rb, out, at);
4300
+ return out;
4301
+ }
4302
+ function decodePlanes(compression, data, l) {
4303
+ const total = decodedLength(l);
4304
+ switch (compression) {
4305
+ case 0:
4306
+ if (data.length < total) throw new PsdError(`Raw channel data has ${data.length} of ${total} bytes.`);
4307
+ return data.slice(0, total);
4308
+ case 1:
4309
+ return decodeRle(new PsdReader(data), l, total);
4310
+ case 2:
4311
+ return inflateChecked(data, total).slice();
4312
+ case 3: {
4313
+ const v = inflateChecked(data, total).slice();
4314
+ unpredict(v, l);
4315
+ return v;
4316
+ }
4317
+ default:
4318
+ throw new PsdError(`Unsupported compression method ${compression}.`);
4319
+ }
4320
+ }
4321
+ function encodeRle(decoded2, l) {
4322
+ const rows = l.planes * l.height;
4323
+ const rb = rowBytes(l.width, l.depth);
4324
+ const cs = l.psb ? 4 : 2;
4325
+ const packed = new Uint8Array(rows * packbitsBound(rb));
4326
+ let at = 0;
4327
+ const counts = new Uint8Array(rows * cs);
4328
+ for (let row = 0; row < rows; row++) {
4329
+ const start = at;
4330
+ at = packbitsEncode(decoded2.subarray(row * rb, (row + 1) * rb), packed, at);
4331
+ const n = at - start;
4332
+ if (cs === 2) {
4333
+ if (n > 65535) throw new PsdError("An RLE row is longer than 65535 bytes; write a PSB instead.");
4334
+ counts[row * 2] = n >> 8;
4335
+ counts[row * 2 + 1] = n & 255;
4336
+ } else {
4337
+ counts[row * 4] = n >>> 24;
4338
+ counts[row * 4 + 1] = n >>> 16 & 255;
4339
+ counts[row * 4 + 2] = n >>> 8 & 255;
4340
+ counts[row * 4 + 3] = n & 255;
4341
+ }
4342
+ }
4343
+ const out = new Uint8Array(counts.length + at);
4344
+ out.set(counts);
4345
+ out.set(packed.subarray(0, at), counts.length);
4346
+ return out;
4347
+ }
4348
+
4190
4349
  // src/image-editor/psd/descriptor.ts
4191
4350
  var MAX_DEPTH = 64;
4192
4351
  var STRING_IDS = /* @__PURE__ */ new Set(["warp", "list", "time"]);
@@ -4302,12 +4461,12 @@ function readValue(r, depth) {
4302
4461
  case "UntF":
4303
4462
  return { type, unit: r.ascii(4), value: r.f64() };
4304
4463
  case "UnFl": {
4305
- const unit2 = r.ascii(4);
4464
+ const unit3 = r.ascii(4);
4306
4465
  const n = r.u32();
4307
4466
  r.checkCount(n, 8, "Unit float list");
4308
4467
  const values = [];
4309
4468
  for (let i = 0; i < n; i++) values.push(r.f64());
4310
- return { type, unit: unit2, values };
4469
+ return { type, unit: unit3, values };
4311
4470
  }
4312
4471
  case "TEXT":
4313
4472
  return { type, value: r.unicode() };
@@ -4760,11 +4919,11 @@ function writeAdjustmentBlock(kind, params) {
4760
4919
  case "blackWhite": {
4761
4920
  const p = params;
4762
4921
  const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;
4763
- const dbl2 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
4922
+ const dbl3 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
4764
4923
  const items = BW_KEYS.map(([field, key]) => [key, long(clamp2(p[field], -200, 300))]);
4765
4924
  items.push(
4766
4925
  ["useTint", { type: "bool", value: p.tint !== null }],
4767
- ["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ", dbl2(tint[0])], ["Grn ", dbl2(tint[1])], ["Bl ", dbl2(tint[2])]]) }],
4926
+ ["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ", dbl3(tint[0])], ["Grn ", dbl3(tint[1])], ["Bl ", dbl3(tint[2])]]) }],
4768
4927
  ["bwPresetKind", { type: "long", value: 1 }],
4769
4928
  ["blackAndWhitePresetFileName", { type: "TEXT", value: "" }]
4770
4929
  );
@@ -4786,161 +4945,562 @@ function writeAdjustmentBlock(kind, params) {
4786
4945
  throw new Error(`Unknown adjustment kind "${kind}".`);
4787
4946
  }
4788
4947
 
4789
- // src/image-editor/psd/compression.ts
4790
- var MAX_DECODED_BYTES = 2 ** 31;
4791
- function rowBytes(width, depth) {
4792
- return depth === 1 ? Math.ceil(width / 8) : width * Math.max(1, depth >> 3);
4793
- }
4794
- function decodedLength(l) {
4795
- const total = l.planes * l.height * rowBytes(l.width, l.depth);
4796
- if (total > MAX_DECODED_BYTES) throw new PsdError(`Decoded channel data of ${total} bytes is larger than the ${MAX_DECODED_BYTES}-byte limit.`);
4797
- return total;
4798
- }
4799
- function packbitsBound(n) {
4800
- return n + Math.ceil(n / 128);
4801
- }
4802
- function packbitsEncode(src, out, at) {
4803
- const n = src.length;
4804
- let i = 0;
4805
- while (i < n) {
4806
- let run = 1;
4807
- while (i + run < n && run < 128 && src[i + run] === src[i]) run++;
4808
- if (run >= 2) {
4809
- out[at++] = 257 - run & 255;
4810
- out[at++] = src[i];
4811
- i += run;
4812
- continue;
4813
- }
4814
- const start = i;
4815
- let len = 0;
4816
- while (i < n && len < 128) {
4817
- if (i + 2 < n && src[i] === src[i + 1] && src[i] === src[i + 2]) break;
4818
- i++;
4819
- len++;
4948
+ // src/image-editor/psd/smart.ts
4949
+ var dbl = (value) => ({ type: "doub", value });
4950
+ var unit = (u, value) => ({ type: "UntF", unit: u, value });
4951
+ var long2 = (value) => ({ type: "long", value: Math.round(value) });
4952
+ var boolV = (value) => ({ type: "bool", value });
4953
+ var enumV = (typeId, value) => ({ type: "enum", typeId, value });
4954
+ var textV = (value) => ({ type: "TEXT", value });
4955
+ var obj = (d) => ({ type: "Objc", value: d });
4956
+ var text = (d, key) => {
4957
+ const v = descGet(d, key);
4958
+ return v?.type === "TEXT" ? v.value : void 0;
4959
+ };
4960
+ var enumOf = (d, key) => {
4961
+ const v = descGet(d, key);
4962
+ return v?.type === "enum" ? v.value : void 0;
4963
+ };
4964
+ var numbers = (d, key) => {
4965
+ const v = descGet(d, key);
4966
+ if (v?.type !== "VlLs") return null;
4967
+ const out = [];
4968
+ for (const it of v.items) {
4969
+ if (it.type === "doub" || it.type === "UntF" || it.type === "long") out.push(it.value);
4970
+ else return null;
4971
+ }
4972
+ return out;
4973
+ };
4974
+ function setItem(d, key, v) {
4975
+ const i = d.items.findIndex(([k]) => k === key);
4976
+ if (i >= 0) d.items[i] = [key, v];
4977
+ else d.items.push([key, v]);
4978
+ }
4979
+ function cloneDescriptor(d) {
4980
+ return parseVersionedDescriptor(versionedDescriptorBytes(d));
4981
+ }
4982
+ function psdUuid(seed) {
4983
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seed)) return seed;
4984
+ let hex = "";
4985
+ for (let k = 0; k < 4; k++) {
4986
+ let h = (2166136261 ^ k * 2654435769) >>> 0;
4987
+ for (let i = 0; i < seed.length; i++) h = Math.imul(h ^ seed.charCodeAt(i), 16777619) >>> 0;
4988
+ hex += h.toString(16).padStart(8, "0");
4989
+ }
4990
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
4991
+ }
4992
+ var LINKED_FILE_KEYS = ["lnk2", "lnk3", "lnkD", "lnkE"];
4993
+ function readLinkedItem(item) {
4994
+ const r = new PsdReader(item);
4995
+ const kind = r.ascii(4);
4996
+ const version = r.u32();
4997
+ const n = r.u8();
4998
+ const uuid = new TextDecoder().decode(r.bytes(n));
4999
+ const name = r.unicode();
5000
+ const fileType = r.ascii(4);
5001
+ r.skip(4);
5002
+ const len = r.u64();
5003
+ if (r.u8() !== 0) readVersionedDescriptor(r);
5004
+ if (kind === "liFD") return { kind, uuid, name, fileType, bytes: r.bytes(Math.min(len, r.remaining)) };
5005
+ let path;
5006
+ if (kind === "liFE") {
5007
+ try {
5008
+ const d = readVersionedDescriptor(r);
5009
+ path = text(d, "fullPath") ?? text(d, "originalPath") ?? text(d, "relPath");
5010
+ } catch {
5011
+ path = void 0;
4820
5012
  }
4821
- out[at++] = len - 1;
4822
- out.set(src.subarray(start, start + len), at);
4823
- at += len;
5013
+ void version;
4824
5014
  }
4825
- return at;
5015
+ return { kind, uuid, name, fileType, bytes: new Uint8Array(0), path: path ?? name };
4826
5016
  }
4827
- function packbitsDecodeInto(src, expected, out, at) {
4828
- const target = at + expected;
4829
- let i = 0;
4830
- while (at < target) {
4831
- if (i >= src.length) throw new PsdError("PackBits row ended early.");
4832
- const h = src[i++];
4833
- if (h < 128) {
4834
- const len = h + 1;
4835
- if (i + len > src.length) throw new PsdError("PackBits literal is truncated.");
4836
- if (at + len > target) throw new PsdError("PackBits literal overflows its row.");
4837
- out.set(src.subarray(i, i + len), at);
4838
- at += len;
4839
- i += len;
4840
- } else if (h !== 128) {
4841
- const len = 257 - h;
4842
- if (i >= src.length) throw new PsdError("PackBits run is truncated.");
4843
- const b = src[i++];
4844
- if (at + len > target) throw new PsdError("PackBits run overflows its row.");
4845
- out.fill(b, at, at + len);
4846
- at += len;
5017
+ function parseLinkedFiles(data) {
5018
+ const out = [];
5019
+ let at = 0;
5020
+ while (at + 8 <= data.length) {
5021
+ const r = new PsdReader(data, at);
5022
+ const len = r.u64();
5023
+ if (!(len > 0) || at + 8 + len > data.length) break;
5024
+ try {
5025
+ const item = readLinkedItem(data.subarray(at + 8, at + 8 + len));
5026
+ if (item) out.push(item);
5027
+ } catch {
4847
5028
  }
5029
+ at = at + 8 + len + 3 & ~3;
4848
5030
  }
4849
- return at;
5031
+ return out;
4850
5032
  }
4851
- function inflateChecked(data, expected) {
4852
- let out;
4853
- try {
4854
- out = inflate(data, { maxOutput: expected + (1 << 20), sizeHint: expected, verify: false });
4855
- } catch (e) {
4856
- throw new PsdError(`ZIP channel data: ${e instanceof ZlibError || e instanceof Error ? e.message : String(e)}`);
4857
- }
4858
- if (out.length < expected) throw new PsdError(`ZIP stream produced ${out.length} of ${expected} bytes.`);
4859
- return out.length === expected ? out : out.subarray(0, expected);
5033
+ function fileTypeOf(bytes) {
5034
+ const b = bytes;
5035
+ if (b[0] === 56 && b[1] === 66 && b[2] === 80 && b[3] === 83) return b[5] === 2 ? { code: "8BPB", ext: "psb" } : { code: "8BPS", ext: "psd" };
5036
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return { code: "PNGf", ext: "png" };
5037
+ if (b[0] === 255 && b[1] === 216) return { code: "JPEG", ext: "jpg" };
5038
+ if (b[0] === 73 && b[1] === 73 || b[0] === 77 && b[1] === 77) return { code: "TIFF", ext: "tif" };
5039
+ if (b[0] === 71 && b[1] === 73 && b[2] === 70) return { code: "GIFf", ext: "gif" };
5040
+ if (b[0] === 37 && b[1] === 80 && b[2] === 68 && b[3] === 70) return { code: "PDF ", ext: "pdf" };
5041
+ const head = new TextDecoder().decode(b.subarray(0, 256)).trimStart();
5042
+ if (head.startsWith("<?xml") || head.startsWith("<svg")) return { code: "SVG ", ext: "svg" };
5043
+ return { code: " ", ext: "bin" };
5044
+ }
5045
+ function encodeLinkedFile(f) {
5046
+ const w = new PsdWriter();
5047
+ w.ascii("liFD");
5048
+ w.u32(7);
5049
+ const id = new TextEncoder().encode(f.uuid).subarray(0, 255);
5050
+ w.u8(id.length);
5051
+ w.bytes(id);
5052
+ w.unicode(f.name, true);
5053
+ w.ascii(fileTypeOf(f.bytes).code);
5054
+ w.ascii("8BIM");
5055
+ w.u64(f.bytes.length);
5056
+ w.u8(0);
5057
+ w.bytes(f.bytes);
5058
+ w.u32(0);
5059
+ w.f64(0);
5060
+ w.u8(0);
5061
+ const item = w.result();
5062
+ const out = new PsdWriter();
5063
+ out.u64(item.length);
5064
+ out.bytes(item);
5065
+ out.zeros((4 - out.length % 4) % 4);
5066
+ return out.result();
4860
5067
  }
4861
- function unpredict(buf, l) {
4862
- const rb = rowBytes(l.width, l.depth);
4863
- if (rb === 0) return;
4864
- const w = l.width;
4865
- const scratch = l.depth === 32 ? new Uint8Array(rb) : null;
4866
- for (let o = 0; o + rb <= buf.length; o += rb) {
4867
- if (l.depth === 1 || l.depth === 8) {
4868
- for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
4869
- } else if (l.depth === 16) {
4870
- for (let i = 1; i < w; i++) {
4871
- const a = buf[o + 2 * i] << 8 | buf[o + 2 * i + 1];
4872
- const b = buf[o + 2 * i - 2] << 8 | buf[o + 2 * i - 1];
4873
- const v = a + b & 65535;
4874
- buf[o + 2 * i] = v >> 8;
4875
- buf[o + 2 * i + 1] = v & 255;
5068
+ var FILTER_EFFECTS_KEYS = ["FEid", "FXid"];
5069
+ var readRect = (r) => ({ top: r.i32(), left: r.i32(), bottom: r.i32(), right: r.i32() });
5070
+ function readPlane(r) {
5071
+ const len = r.u64();
5072
+ if (len < 2) throw new PsdError("Filter effects: a plane is shorter than its compression field.");
5073
+ const s = r.sub(len);
5074
+ return { compression: s.u16(), data: s.rest() };
5075
+ }
5076
+ function parseFilterEffects(data) {
5077
+ const r = new PsdReader(data);
5078
+ const version = r.u32();
5079
+ if (version < 1 || version > 3) throw new PsdError(`Filter effects version ${version}.`);
5080
+ const items = [];
5081
+ while (r.remaining >= 8) {
5082
+ const len = r.u64();
5083
+ const body = r.sub(len);
5084
+ while (body.remaining > 0) {
5085
+ const id = new TextDecoder().decode(body.pascal(1));
5086
+ const itemVersion = body.u32();
5087
+ const it = body.sub(body.u64());
5088
+ const rect = readRect(it);
5089
+ const depth = it.u32();
5090
+ const maxChannels = it.u32();
5091
+ if (maxChannels > 64) throw new PsdError("Filter effects: too many channels.");
5092
+ const slots = [];
5093
+ for (let i = 0; i < maxChannels + 2; i++) slots.push(it.u32() !== 0 ? readPlane(it) : null);
5094
+ let mask = null;
5095
+ if (body.remaining > 0 && body.u8() !== 0) mask = { rect: readRect(body), plane: readPlane(body) };
5096
+ items.push({ id, version: itemVersion, rect, depth, maxChannels, slots, mask });
5097
+ }
5098
+ const pad = (4 - len % 4) % 4;
5099
+ r.skip(Math.min(pad, r.remaining));
5100
+ if (r.remaining < 8) break;
5101
+ }
5102
+ return { version, items };
5103
+ }
5104
+ function writeFilterEffects(items, version = 3) {
5105
+ const w = new PsdWriter();
5106
+ w.u32(version);
5107
+ const plane = (b, p) => {
5108
+ b.u64(p.data.length + 2);
5109
+ b.u16(p.compression);
5110
+ b.bytes(p.data);
5111
+ };
5112
+ const rect = (b, r) => {
5113
+ b.i32(r.top);
5114
+ b.i32(r.left);
5115
+ b.i32(r.bottom);
5116
+ b.i32(r.right);
5117
+ };
5118
+ for (const it of items) {
5119
+ const b = new PsdWriter();
5120
+ b.pascal(new TextEncoder().encode(it.id), 1);
5121
+ b.u32(it.version);
5122
+ const mark = b.beginLen(true);
5123
+ rect(b, it.rect);
5124
+ b.u32(it.depth);
5125
+ b.u32(it.maxChannels);
5126
+ for (let i = 0; i < it.maxChannels + 2; i++) {
5127
+ const p = it.slots[i];
5128
+ if (p) {
5129
+ b.u32(1);
5130
+ plane(b, p);
5131
+ } else b.u32(0);
5132
+ }
5133
+ b.endLen(mark);
5134
+ if (it.mask) {
5135
+ b.u8(1);
5136
+ rect(b, it.mask.rect);
5137
+ plane(b, it.mask.plane);
5138
+ } else b.u8(0);
5139
+ const body = b.result();
5140
+ w.u64(body.length);
5141
+ w.bytes(body);
5142
+ w.zeros((4 - w.length % 4) % 4);
5143
+ }
5144
+ if (!items.length) w.u64(0);
5145
+ return w.result();
5146
+ }
5147
+ function filterMaskFromItem(item) {
5148
+ if (!item.mask || item.depth !== 8) return null;
5149
+ const { rect, plane } = item.mask;
5150
+ const width = rect.right - rect.left;
5151
+ const height = rect.bottom - rect.top;
5152
+ if (!(width > 0 && height > 0) || width * height > 1 << 28) return null;
5153
+ const bytes = decodePlanes(plane.compression, plane.data, { planes: 1, width, height, depth: 8, psb: true });
5154
+ const surface = surfaceFromPixels({ width, height, data: bytes }, 1, rect.left, rect.top, 255);
5155
+ return { surface, defaultValue: 255 };
5156
+ }
5157
+ function filterMaskItem(placed, mask, area) {
5158
+ const painted = contentBounds(mask.surface);
5159
+ let b = painted ?? { x: 0, y: 0, width: 1, height: 1 };
5160
+ if (mask.defaultValue !== 255 && area) {
5161
+ const x0 = Math.min(area.x, painted?.x ?? area.x);
5162
+ const y0 = Math.min(area.y, painted?.y ?? area.y);
5163
+ const x1 = Math.max(area.x + area.width, painted ? painted.x + painted.width : 0);
5164
+ const y1 = Math.max(area.y + area.height, painted ? painted.y + painted.height : 0);
5165
+ b = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
5166
+ }
5167
+ const rect = { top: b.y, left: b.x, bottom: b.y + b.height, right: b.x + b.width };
5168
+ const values = readRegion(mask.surface, b);
5169
+ const plane = new Uint8Array(values.buffer, values.byteOffset, values.length);
5170
+ return {
5171
+ id: placed,
5172
+ version: 1,
5173
+ rect,
5174
+ depth: 8,
5175
+ maxChannels: 24,
5176
+ slots: Array.from({ length: 26 }, () => null),
5177
+ mask: { rect, plane: { compression: 1, data: encodeRle(plane, { planes: 1, width: b.width, height: b.height, depth: 8, psb: true }) } }
5178
+ };
5179
+ }
5180
+ var warpStyleOf = (name) => {
5181
+ if (!name.startsWith("warp")) return null;
5182
+ const s = name[4]?.toLowerCase() + name.slice(5);
5183
+ return WARP_STYLES.includes(s) ? s : null;
5184
+ };
5185
+ function warpFromDescriptor(w, contents) {
5186
+ const full = { x: 0, y: 0, width: contents.width, height: contents.height };
5187
+ const styleName = enumOf(w, "warpStyle");
5188
+ if (!styleName) return void 0;
5189
+ if (styleName === "warpNone") return { mesh: null, frame: full };
5190
+ const bd = descObject(w, "bounds");
5191
+ if (!bd) return void 0;
5192
+ const left = descNumber(bd, "Left") ?? 0;
5193
+ const top = descNumber(bd, "Top ") ?? 0;
5194
+ const bounds = { x: left, y: top, width: (descNumber(bd, "Rght") ?? 0) - left, height: (descNumber(bd, "Btom") ?? 0) - top };
5195
+ if (!(bounds.width > 0 && bounds.height > 0)) return void 0;
5196
+ let mesh;
5197
+ let frame = full;
5198
+ if (styleName === "warpCustom") {
5199
+ const env = descObject(w, "customEnvelopeWarp");
5200
+ const arr = env ? descGet(env, "meshPoints") : void 0;
5201
+ if (!arr || arr.type !== "ObAr") return void 0;
5202
+ const xs = descGet(arr.body, "Hrzn");
5203
+ const ys = descGet(arr.body, "Vrtc");
5204
+ if (xs?.type !== "UnFl" || ys?.type !== "UnFl" || xs.values.length !== 16 || ys.values.length !== 16) return void 0;
5205
+ mesh = { us: [0, 1], vs: [0, 1], points: xs.values.map((x, i) => ({ x, y: ys.values[i] })) };
5206
+ const px = mesh.points.map((p) => p.x);
5207
+ const py = mesh.points.map((p) => p.y);
5208
+ const f = { x: Math.min(...px), y: Math.min(...py), width: Math.max(...px) - Math.min(...px), height: Math.max(...py) - Math.min(...py) };
5209
+ if (f.width > 1e-9 && f.height > 1e-9 && [f.x, f.y, f.width, f.height].every(Number.isFinite)) frame = f;
5210
+ } else {
5211
+ const style = warpStyleOf(styleName);
5212
+ if (!style) return void 0;
5213
+ mesh = warpPresetMesh(
5214
+ {
5215
+ style,
5216
+ bend: descNumber(w, "warpValue") ?? 0,
5217
+ horizontal: descNumber(w, "warpPerspective") ?? 0,
5218
+ vertical: descNumber(w, "warpPerspectiveOther") ?? 0,
5219
+ orientation: enumOf(w, "warpRotate") === "Vrtc" ? "vertical" : "horizontal"
5220
+ },
5221
+ bounds
5222
+ );
5223
+ if (!mesh) return { mesh: null, frame: full };
5224
+ }
5225
+ const same = Math.abs(bounds.x) < 1e-6 && Math.abs(bounds.y) < 1e-6 && Math.abs(bounds.width - contents.width) < 1e-6 && Math.abs(bounds.height - contents.height) < 1e-6;
5226
+ if (!same) {
5227
+ const m = mesh;
5228
+ mesh = fitWarpMesh((s, t) => evalWarpMesh(m, (s * contents.width - bounds.x) / bounds.width, (t * contents.height - bounds.y) / bounds.height), [0, 1], [0, 1]);
5229
+ }
5230
+ return { mesh, frame };
5231
+ }
5232
+ function warpDescriptor(mesh, contents) {
5233
+ const full = { x: 0, y: 0, width: contents.width, height: contents.height };
5234
+ const single = mesh && mesh.us.length === 2 && mesh.vs.length === 2 && mesh.points.length === 16;
5235
+ const bounds = descriptor("classFloatRect", [
5236
+ ["Top ", dbl(0)],
5237
+ ["Left", dbl(0)],
5238
+ ["Btom", dbl(contents.height)],
5239
+ ["Rght", dbl(contents.width)]
5240
+ ]);
5241
+ const items = [
5242
+ ["warpStyle", enumV("warpStyle", single ? "warpCustom" : "warpNone")],
5243
+ ["warpValue", dbl(0)],
5244
+ ["warpPerspective", dbl(0)],
5245
+ ["warpPerspectiveOther", dbl(0)],
5246
+ ["warpRotate", enumV("Ornt", "Hrzn")],
5247
+ ["bounds", obj(bounds)],
5248
+ ["uOrder", long2(4)],
5249
+ ["vOrder", long2(4)]
5250
+ ];
5251
+ let frame = full;
5252
+ if (single) {
5253
+ const body = descriptor("rationalPoint", [
5254
+ ["Hrzn", { type: "UnFl", unit: "#Pxl", values: mesh.points.map((p) => p.x) }],
5255
+ ["Vrtc", { type: "UnFl", unit: "#Pxl", values: mesh.points.map((p) => p.y) }]
5256
+ ]);
5257
+ items.push(["customEnvelopeWarp", obj(descriptor("customEnvelopeWarp", [["meshPoints", { type: "ObAr", prefix: 16, body }]]))]);
5258
+ const px = mesh.points.map((p) => p.x);
5259
+ const py = mesh.points.map((p) => p.y);
5260
+ frame = { x: Math.min(...px), y: Math.min(...py), width: Math.max(...px) - Math.min(...px), height: Math.max(...py) - Math.min(...py) };
5261
+ }
5262
+ return { warp: descriptor("warp", items), frame, dropped: Boolean(mesh && !single) };
5263
+ }
5264
+ var isParallelogram = (q2) => {
5265
+ const tol = 1e-6 * (1 + Math.max(...q2.flatMap((p) => [Math.abs(p.x), Math.abs(p.y)])));
5266
+ return Math.abs(q2[0].x + q2[2].x - q2[1].x - q2[3].x) <= tol && Math.abs(q2[0].y + q2[2].y - q2[1].y - q2[3].y) <= tol;
5267
+ };
5268
+ var toQuad = (p) => [
5269
+ { x: p[0], y: p[1] },
5270
+ { x: p[2], y: p[3] },
5271
+ { x: p[4], y: p[5] },
5272
+ { x: p[6], y: p[7] }
5273
+ ];
5274
+ function cornersFromQuad(quad, frame, contents) {
5275
+ if (quad.length !== 8 || !quad.every(Number.isFinite)) return null;
5276
+ const q2 = toQuad(quad);
5277
+ const h = rectToQuad(frame, q2);
5278
+ if (!h) return null;
5279
+ const out = boxQuad({ x: 0, y: 0, width: contents.width, height: contents.height }).map((p) => applyHomography(h, p));
5280
+ return out.every(Boolean) ? out : null;
5281
+ }
5282
+ function quadFromCorners(corners, frame, contents) {
5283
+ const h = rectToQuad({ x: 0, y: 0, width: contents.width, height: contents.height }, corners);
5284
+ const pts = boxQuad(frame).map((p) => (h ? applyHomography(h, p) : p) ?? p);
5285
+ return pts.flatMap((p) => [p.x, p.y]);
5286
+ }
5287
+ var PLACED_KEYS = ["SoLd", "SoLE", "PlLd"];
5288
+ function parsePlaced(key, data) {
5289
+ try {
5290
+ if (key === "SoLd" || key === "SoLE") {
5291
+ const r = new PsdReader(data);
5292
+ if (r.ascii(4) !== "soLD") return null;
5293
+ r.u32();
5294
+ const d = readVersionedDescriptor(r);
5295
+ const quad = numbers(d, "nonAffineTransform") ?? numbers(d, "Trnf") ?? [];
5296
+ const affine = numbers(d, "Trnf");
5297
+ const sz = descObject(d, "Sz ");
5298
+ const size = sz ? { width: descNumber(sz, "Wdth") ?? 0, height: descNumber(sz, "Hght") ?? 0 } : void 0;
5299
+ const use = affine && affine.length === 8 && isParallelogram(toQuad(affine)) && quad.length === 8 && !isParallelogram(toQuad(quad)) ? quad : affine ?? quad;
5300
+ return {
5301
+ key,
5302
+ descriptor: d,
5303
+ idnt: text(d, "Idnt") ?? "",
5304
+ placed: text(d, "placed") ?? "",
5305
+ quad: use,
5306
+ size: size && size.width > 0 && size.height > 0 ? size : void 0,
5307
+ warp: descObject(d, "warp"),
5308
+ filterFX: descObject(d, "filterFX")
5309
+ };
5310
+ }
5311
+ if (key === "PlLd") {
5312
+ const r = new PsdReader(data);
5313
+ if (r.ascii(4) !== "plcL") return null;
5314
+ r.u32();
5315
+ const n = r.u8();
5316
+ const idnt = new TextDecoder().decode(r.bytes(n));
5317
+ r.skip(16);
5318
+ const quad = [];
5319
+ for (let i = 0; i < 8; i++) quad.push(r.f64());
5320
+ let warp;
5321
+ if (r.remaining > 8 && r.peek(r.remaining).some((b) => b !== 0)) {
5322
+ r.u32();
5323
+ try {
5324
+ warp = readVersionedDescriptor(r);
5325
+ } catch {
5326
+ warp = void 0;
5327
+ }
4876
5328
  }
4877
- } else if (l.depth === 32) {
4878
- for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
4879
- scratch.set(buf.subarray(o, o + rb));
4880
- for (let i = 0; i < w; i++) for (let k = 0; k < 4; k++) buf[o + 4 * i + k] = scratch[k * w + i];
4881
- } else {
4882
- throw new PsdError(`ZIP prediction is not defined for depth ${l.depth}.`);
5329
+ return { key, idnt, placed: idnt, quad, warp };
4883
5330
  }
5331
+ } catch {
5332
+ return null;
4884
5333
  }
5334
+ return null;
4885
5335
  }
4886
- function decodeRle(r, l, total) {
4887
- const rows = l.planes * l.height;
4888
- const rb = rowBytes(l.width, l.depth);
4889
- r.checkCount(rows, l.psb ? 4 : 2, "RLE row counts");
4890
- const counts = new Array(rows);
4891
- for (let i = 0; i < rows; i++) counts[i] = l.psb ? r.u32() : r.u16();
4892
- if (total > r.remaining * 130) throw new PsdError("RLE data is too short for its declared size.");
4893
- const out = new Uint8Array(total);
4894
- let at = 0;
4895
- for (const c of counts) at = packbitsDecodeInto(r.bytes(c), rb, out, at);
5336
+ function placementFromPlaced(p, size, notes) {
5337
+ let mesh = null;
5338
+ let frame = { x: 0, y: 0, width: size.width, height: size.height };
5339
+ if (p.warp) {
5340
+ const w = warpFromDescriptor(p.warp, size);
5341
+ if (!w) notes.push("its warp could not be read and was left out");
5342
+ else {
5343
+ mesh = w.mesh;
5344
+ if (p.quad.length === 8 && isParallelogram(toQuad(p.quad))) frame = w.frame;
5345
+ }
5346
+ }
5347
+ const corners = cornersFromQuad(p.quad, frame, size);
5348
+ if (!corners) return null;
5349
+ const out = { corners };
5350
+ if (mesh) out.warp = mesh;
4896
5351
  return out;
4897
5352
  }
4898
- function decodePlanes(compression, data, l) {
4899
- const total = decodedLength(l);
4900
- switch (compression) {
4901
- case 0:
4902
- if (data.length < total) throw new PsdError(`Raw channel data has ${data.length} of ${total} bytes.`);
4903
- return data.slice(0, total);
4904
- case 1:
4905
- return decodeRle(new PsdReader(data), l, total);
4906
- case 2:
4907
- return inflateChecked(data, total).slice();
4908
- case 3: {
4909
- const v = inflateChecked(data, total).slice();
4910
- unpredict(v, l);
4911
- return v;
5353
+ var BOX_BLUR = { classId: "boxblur", id: 840, name: "Box Blur..." };
5354
+ function blendOptionsOf(item) {
5355
+ const o = descObject(item, "blendOptions");
5356
+ const opacity = o ? Math.min(1, Math.max(0, (descNumber(o, "Opct") ?? 100) / 100)) : 1;
5357
+ const id = o ? enumOf(o, "Md ") : void 0;
5358
+ return { blend: id && blendFromPsdId(id) || "normal", opacity };
5359
+ }
5360
+ function blendOptions(f) {
5361
+ return obj(
5362
+ descriptor("blendOptions", [
5363
+ ["Opct", unit("#Prc", Math.min(1, Math.max(0, f.opacity)) * 100)],
5364
+ ["Md ", enumV("BlnM", psdBlendId(f.blend))]
5365
+ ])
5366
+ );
5367
+ }
5368
+ function smartFiltersFromPsd(fx, newId, notes) {
5369
+ const filters = [];
5370
+ const list2 = descGet(fx, "filterFXList");
5371
+ for (const v of list2?.type === "VlLs" ? list2.items : []) {
5372
+ if (v.type !== "Objc" && v.type !== "GlbO") continue;
5373
+ const item = v.value;
5374
+ const { blend, opacity } = blendOptionsOf(item);
5375
+ const enabled = descBool(item, "enab") !== false;
5376
+ const fltr = descObject(item, "Fltr");
5377
+ const radius = fltr?.classId === BOX_BLUR.classId ? descNumber(fltr, "Rds ") : void 0;
5378
+ if (radius !== void 0 && radius >= 0 && radius <= 50) {
5379
+ filters.push({ id: newId(), kind: "blur", enabled, radius, blend, opacity });
5380
+ continue;
4912
5381
  }
4913
- default:
4914
- throw new PsdError(`Unsupported compression method ${compression}.`);
5382
+ const name = (text(item, "Nm ") ?? "Filter").replace(/&/g, "");
5383
+ const neutral = cloneDescriptor(item);
5384
+ setItem(neutral, "blendOptions", blendOptions({ blend: "normal", opacity: 1 }));
5385
+ setItem(neutral, "enab", boolV(true));
5386
+ filters.push({ id: newId(), kind: "psd", enabled, name, data: versionedDescriptorBytes(neutral), blend, opacity });
5387
+ notes.push(`the ${name.replace(/\.\.\.$/, "")} smart filter is kept but not drawn`);
4915
5388
  }
5389
+ return { enabled: descBool(fx, "enab") !== false, filters };
4916
5390
  }
4917
- function encodeRle(decoded2, l) {
4918
- const rows = l.planes * l.height;
4919
- const rb = rowBytes(l.width, l.depth);
4920
- const cs = l.psb ? 4 : 2;
4921
- const packed = new Uint8Array(rows * packbitsBound(rb));
4922
- let at = 0;
4923
- const counts = new Uint8Array(rows * cs);
4924
- for (let row = 0; row < rows; row++) {
4925
- const start = at;
4926
- at = packbitsEncode(decoded2.subarray(row * rb, (row + 1) * rb), packed, at);
4927
- const n = at - start;
4928
- if (cs === 2) {
4929
- if (n > 65535) throw new PsdError("An RLE row is longer than 65535 bytes; write a PSB instead.");
4930
- counts[row * 2] = n >> 8;
4931
- counts[row * 2 + 1] = n & 255;
4932
- } else {
4933
- counts[row * 4] = n >>> 24;
4934
- counts[row * 4 + 1] = n >>> 16 & 255;
4935
- counts[row * 4 + 2] = n >>> 8 & 255;
4936
- counts[row * 4 + 3] = n & 255;
4937
- }
5391
+ function filterFXDescriptor(stack, dropped) {
5392
+ const list2 = [];
5393
+ for (const f of stack.filters) {
5394
+ if (f.kind === "psd") {
5395
+ const d = parseVersionedDescriptor(f.data);
5396
+ setItem(d, "blendOptions", blendOptions(f));
5397
+ setItem(d, "enab", boolV(f.enabled));
5398
+ list2.push(obj(d));
5399
+ } else if (f.kind === "blur") {
5400
+ const rgb = (v) => obj(descriptor("RGBC", [["Rd ", dbl(v)], ["Grn ", dbl(v)], ["Bl ", dbl(v)]]));
5401
+ list2.push(
5402
+ obj(
5403
+ descriptor("filterFX", [
5404
+ ["Nm ", textV(BOX_BLUR.name)],
5405
+ ["blendOptions", blendOptions(f)],
5406
+ ["enab", boolV(f.enabled)],
5407
+ ["hasoptions", boolV(true)],
5408
+ ["FrgC", rgb(0)],
5409
+ ["BckC", rgb(255)],
5410
+ ["Fltr", obj(descriptor(BOX_BLUR.classId, [["Rds ", unit("#Pxl", f.radius)]]))],
5411
+ ["filterID", long2(BOX_BLUR.id)]
5412
+ ])
5413
+ )
5414
+ );
5415
+ } else dropped.push(f.kind);
5416
+ }
5417
+ return descriptor("filterFXStyle", [
5418
+ ["enab", boolV(stack.enabled)],
5419
+ ["validAtPosition", boolV(true)],
5420
+ ["filterMaskEnable", boolV(stack.mask ? stack.mask.enabled !== false : true)],
5421
+ ["filterMaskLinked", boolV(stack.mask ? stack.mask.linked !== false : false)],
5422
+ ["filterMaskExtendWithWhite", boolV(true)],
5423
+ ["filterFXList", { type: "VlLs", items: list2 }]
5424
+ ]);
5425
+ }
5426
+ function filterStackKey(stack) {
5427
+ if (!stack) return "";
5428
+ return JSON.stringify([
5429
+ stack.enabled,
5430
+ stack.filters.map((f) => f.kind === "psd" ? ["psd", f.enabled, f.blend, f.opacity, Array.from(f.data)] : { ...f, id: void 0 }),
5431
+ stack.mask ? [stack.mask.enabled !== false, stack.mask.linked !== false] : null
5432
+ ]);
5433
+ }
5434
+ function soldBytes(s, notes) {
5435
+ const { warp, frame, dropped } = warpDescriptor(s.placement.warp, s.size);
5436
+ if (dropped) notes.push("a warp of several patches was written without its warp (the layer\u2019s pixels still show it)");
5437
+ const quad = { type: "VlLs", items: quadFromCorners(s.placement.corners, frame, s.size).map(dbl) };
5438
+ let d;
5439
+ if (s.template) {
5440
+ d = cloneDescriptor(s.template);
5441
+ setItem(d, "Idnt", textV(s.idnt));
5442
+ setItem(d, "placed", textV(s.placed));
5443
+ setItem(d, "Trnf", quad);
5444
+ setItem(d, "nonAffineTransform", quad);
5445
+ setItem(d, "warp", obj(warp));
5446
+ setItem(d, "Sz ", obj(descriptor("Pnt ", [["Wdth", dbl(s.size.width)], ["Hght", dbl(s.size.height)]])));
5447
+ if (s.filterFX) setItem(d, "filterFX", obj(s.filterFX));
5448
+ else d.items = d.items.filter(([k]) => k !== "filterFX");
5449
+ } else {
5450
+ const ratio = (n, den) => obj(descriptor("null", [["numerator", long2(n)], ["denominator", long2(den)]]));
5451
+ const items = [
5452
+ ["Idnt", textV(s.idnt)],
5453
+ ["placed", textV(s.placed)],
5454
+ ["PgNm", long2(1)],
5455
+ ["totalPages", long2(1)],
5456
+ ["Crop", long2(1)],
5457
+ ["frameStep", ratio(0, 600)],
5458
+ ["duration", ratio(0, 600)],
5459
+ ["frameCount", long2(1)],
5460
+ ["Annt", long2(16)],
5461
+ ["Type", long2(2)],
5462
+ ["Trnf", quad],
5463
+ ["nonAffineTransform", quad],
5464
+ ["warp", obj(warp)],
5465
+ ["Sz ", obj(descriptor("Pnt ", [["Wdth", dbl(s.size.width)], ["Hght", dbl(s.size.height)]]))],
5466
+ ["Rslt", unit("#Rsl", 72)]
5467
+ ];
5468
+ if (s.filterFX) items.push(["filterFX", obj(s.filterFX)]);
5469
+ items.push(["comp", long2(-1)], ["compInfo", obj(descriptor("null", [["compID", long2(-1)], ["originalCompID", long2(-1)]]))]);
5470
+ d = descriptor("null", items);
4938
5471
  }
4939
- const out = new Uint8Array(counts.length + at);
4940
- out.set(counts);
4941
- out.set(packed.subarray(0, at), counts.length);
4942
- return out;
5472
+ const w = new PsdWriter();
5473
+ w.ascii("soLD");
5474
+ w.u32(4);
5475
+ writeVersionedDescriptor(w, d);
5476
+ w.zeros((4 - w.length % 4) % 4);
5477
+ return w.result();
5478
+ }
5479
+ function plldBytes(s) {
5480
+ const { warp, frame } = warpDescriptor(s.placement.warp, s.size);
5481
+ const w = new PsdWriter();
5482
+ w.ascii("plcL");
5483
+ w.u32(3);
5484
+ const id = new TextEncoder().encode(s.idnt).subarray(0, 255);
5485
+ w.u8(id.length);
5486
+ w.bytes(id);
5487
+ for (const v of [1, 1, 16, 2]) w.u32(v);
5488
+ for (const v of quadFromCorners(s.placement.corners, frame, s.size)) w.f64(v);
5489
+ w.u32(0);
5490
+ writeVersionedDescriptor(w, warp);
5491
+ w.zeros((4 - w.length % 4) % 4);
5492
+ return w.result();
4943
5493
  }
5494
+ var BROWSER_IMAGE_TYPES = {
5495
+ jpg: "image/jpeg",
5496
+ jpeg: "image/jpeg",
5497
+ gif: "image/gif",
5498
+ tif: "image/tiff",
5499
+ tiff: "image/tiff",
5500
+ svg: "image/svg+xml",
5501
+ png: "image/png",
5502
+ webp: "image/webp"
5503
+ };
4944
5504
 
4945
5505
  // src/image-editor/psd/file.ts
4946
5506
  var PSD_MAX_DIMENSION = 3e4;
@@ -4992,7 +5552,7 @@ var SECTION_OTHER = 0;
4992
5552
  var SECTION_OPEN = 1;
4993
5553
  var SECTION_CLOSED = 2;
4994
5554
  var SECTION_DIVIDER = 3;
4995
- function readRect(r) {
5555
+ function readRect2(r) {
4996
5556
  return { top: r.i32(), left: r.i32(), bottom: r.i32(), right: r.i32() };
4997
5557
  }
4998
5558
  function rectSize(rect) {
@@ -5079,8 +5639,8 @@ function realBeforeParameters(d) {
5079
5639
  function parseMask(d) {
5080
5640
  try {
5081
5641
  const r = new PsdReader(d);
5082
- const m = { rect: readRect(r), defaultColor: r.u8(), flags: r.u8() };
5083
- const readReal = () => r.remaining >= 18 ? { flags: r.u8(), background: r.u8(), rect: readRect(r) } : void 0;
5642
+ const m = { rect: readRect2(r), defaultColor: r.u8(), flags: r.u8() };
5643
+ const readReal = () => r.remaining >= 18 ? { flags: r.u8(), background: r.u8(), rect: readRect2(r) } : void 0;
5084
5644
  const params = (m.flags & MASK_FLAG_PARAMETERS) !== 0;
5085
5645
  const realFirst = params && realBeforeParameters(d);
5086
5646
  if (realFirst) m.real = readReal();
@@ -5098,7 +5658,7 @@ function parseMask(d) {
5098
5658
  }
5099
5659
  }
5100
5660
  function readLayerRecord(r, psb) {
5101
- const rect = readRect(r);
5661
+ const rect = readRect2(r);
5102
5662
  const n = r.u16();
5103
5663
  if (n > MAX_LAYER_CHANNELS) throw new PsdError(`A layer has ${n} channels (at most ${MAX_LAYER_CHANNELS}).`);
5104
5664
  const channels = [];
@@ -5249,11 +5809,11 @@ function writeRect(w, r) {
5249
5809
  }
5250
5810
  function writeBlocks(w, blocks, psb, alignment) {
5251
5811
  for (const b of blocks) {
5252
- const long3 = psb && PSB_LONG_KEYS.has(b.key);
5253
- w.ascii(long3 ? "8B64" : "8BIM");
5812
+ const long4 = psb && PSB_LONG_KEYS.has(b.key);
5813
+ w.ascii(long4 ? "8B64" : "8BIM");
5254
5814
  w.ascii(b.key);
5255
5815
  const pad = (alignment - b.data.length % alignment) % alignment;
5256
- if (long3) w.u64(b.data.length + pad);
5816
+ if (long4) w.u64(b.data.length + pad);
5257
5817
  else w.u32(b.data.length + pad);
5258
5818
  w.bytes(b.data);
5259
5819
  w.zeros(pad);
@@ -5678,6 +6238,7 @@ var FILL_KEYS = ["SoCo", "GdFl", "PtFl"];
5678
6238
  var TYPE_KEYS = ["TySh", "tySh"];
5679
6239
  var SMART_KEYS = ["SoLd", "SoLE", "PlLd"];
5680
6240
  var VECTOR_KEYS = ["vmsk", "vsms"];
6241
+ var MAX_CONTENTS_DEPTH = 6;
5681
6242
  var percent = (v) => Math.round(v / 255 * 100) / 100;
5682
6243
  var Warnings = class {
5683
6244
  groups = /* @__PURE__ */ new Map();
@@ -5896,6 +6457,26 @@ function importNode(ctx, node, depth) {
5896
6457
  return layer2;
5897
6458
  }
5898
6459
  const props = baseProps(ctx, rec, name, notes);
6460
+ if (kind === "smart-object" && ctx.skipPixels) {
6461
+ const block = PLACED_KEYS.map((k) => rec.blocks.find((b) => b.key === k)).find(Boolean);
6462
+ const placed = block ? parsePlaced(block.key, block.data) : null;
6463
+ if (placed) {
6464
+ const file = ctx.linked.get(placed.idnt);
6465
+ if (!file || file.kind !== "liFD") notes.push("linked contents come in as the stored pixels");
6466
+ entry.type = "smart";
6467
+ entry.status = notes.length ? "approximated" : "mapped";
6468
+ return { type: "pixel", id: ctx.newId(name), ...props, surface: emptySurface(4), offset: { x: 0, y: 0 } };
6469
+ }
6470
+ }
6471
+ if (kind === "smart-object" && !ctx.skipPixels) {
6472
+ const smart = importSmart(ctx, rec, name, notes, props);
6473
+ if (smart) {
6474
+ entry.type = "smart";
6475
+ entry.id = smart.id;
6476
+ entry.status = notes.length ? "approximated" : "mapped";
6477
+ return smart;
6478
+ }
6479
+ }
5899
6480
  if (kind !== "pixel") {
5900
6481
  const what = kind === "smart-object" ? "Smart objects" : kind === "type" ? "Type layers" : kind === "shape" ? "Shape layers" : "Fill layers";
5901
6482
  ctx.warn.add(`${what} came in as pixel layers (their stored pixels)`, name);
@@ -5913,6 +6494,126 @@ function importNode(ctx, node, depth) {
5913
6494
  entry.status = notes.length ? "approximated" : "mapped";
5914
6495
  return layer;
5915
6496
  }
6497
+ function contentsFor(ctx, placed, name, notes) {
6498
+ const memo = ctx.contents.get(placed.idnt);
6499
+ if (memo !== void 0) return memo;
6500
+ const file = ctx.linked.get(placed.idnt);
6501
+ let out = null;
6502
+ if (file && file.kind === "liFD" && file.bytes.length) {
6503
+ const t = fileTypeOf(file.bytes);
6504
+ const base = { id: placed.idnt, name: file.name || `${name}.${t.ext}`, file: { bytes: file.bytes.slice(), type: t.ext } };
6505
+ try {
6506
+ if (t.ext === "psd" || t.ext === "psb") {
6507
+ if (ctx.depth + 1 > MAX_CONTENTS_DEPTH) throw new PsdError("the contents nest too deep");
6508
+ const nested = psdToLayers(readPsd(file.bytes), { newId: ctx.newId, depth: ctx.depth + 1 });
6509
+ for (const w of nested.warnings) ctx.warn.add(`In its contents: ${w.replace(/\.$/, "")}`, name);
6510
+ out = {
6511
+ ...base,
6512
+ kind: "document",
6513
+ width: nested.width,
6514
+ height: nested.height,
6515
+ document: { layers: nested.layers, globalLight: nested.globalLight, patterns: nested.patterns.length ? nested.patterns : void 0 }
6516
+ };
6517
+ } else if (t.ext === "png") {
6518
+ const png = decodePng(file.bytes);
6519
+ out = { ...base, kind: "raster", width: png.width, height: png.height, raster: surfaceFromPixels(png, 4, 0, 0) };
6520
+ } else {
6521
+ const size = placed.size ?? { width: 0, height: 0 };
6522
+ if (!(size.width > 0 && size.height > 0)) throw new PsdError("the contents have no stored size");
6523
+ out = { ...base, kind: "raster", width: Math.round(size.width), height: Math.round(size.height) };
6524
+ if (BROWSER_IMAGE_TYPES[t.ext]) {
6525
+ ctx.warn.add(`Smart object contents in ${t.ext.toUpperCase()} format are decoded only in a browser (prepareSmartObjects); until then the layer shows its stored pixels and cannot be re-rendered`, name);
6526
+ } else {
6527
+ ctx.warn.add(`Smart object contents of an unsupported type (${t.ext === "bin" ? "unknown" : t.ext.toUpperCase()}) are kept for export, but the layer shows its stored pixels and cannot be re-rendered`, name);
6528
+ }
6529
+ notes.push(`${t.ext} contents not decoded here`);
6530
+ }
6531
+ } catch (e) {
6532
+ ctx.warn.add(`Smart object contents could not be read (${e instanceof Error ? e.message : String(e)}); the layer came in as its stored pixels`, name);
6533
+ notes.push("contents could not be read");
6534
+ out = null;
6535
+ }
6536
+ }
6537
+ ctx.contents.set(placed.idnt, out);
6538
+ return out;
6539
+ }
6540
+ function importSmart(ctx, rec, name, notes, props) {
6541
+ const block = PLACED_KEYS.map((k) => rec.blocks.find((b) => b.key === k)).find(Boolean);
6542
+ if (!block) return null;
6543
+ const placed = parsePlaced(block.key, block.data);
6544
+ if (!placed) return null;
6545
+ const surface = recordPixels(ctx, rec, name);
6546
+ let contents = placed.idnt ? contentsFor(ctx, placed, name, notes) : null;
6547
+ let placement = null;
6548
+ let preview = false;
6549
+ if (contents) {
6550
+ const size = placed.size ?? { width: contents.width, height: contents.height };
6551
+ const pnotes = [];
6552
+ placement = placementFromPlaced(placed, size, pnotes);
6553
+ for (const n of pnotes) {
6554
+ ctx.warn.add(`A smart object's placement was approximated: ${n}`, name);
6555
+ notes.push(n);
6556
+ }
6557
+ if (placement && (size.width !== contents.width || size.height !== contents.height)) {
6558
+ const kx = contents.width / size.width;
6559
+ const ky = contents.height / size.height;
6560
+ if (placement.warp) placement = { ...placement, warp: mapWarpMeshPoints(placement.warp, (p) => ({ x: p.x * kx, y: p.y * ky })) };
6561
+ }
6562
+ }
6563
+ if (!contents || !placement) {
6564
+ const b = surface.bounds;
6565
+ const { width, height } = rectSize(rec.rect);
6566
+ if (!b || !(width > 0 && height > 0)) return null;
6567
+ const file = ctx.linked.get(placed.idnt);
6568
+ contents = {
6569
+ id: placed.idnt || ctx.newId(`${name}-contents`),
6570
+ name: file?.name || `${name}.png`,
6571
+ width,
6572
+ height,
6573
+ kind: "raster",
6574
+ raster: translateSurface(surface, -rec.rect.left, -rec.rect.top)
6575
+ };
6576
+ if (file && file.kind !== "liFD") {
6577
+ contents.linked = { path: file.path ?? file.name };
6578
+ ctx.warn.add(`Linked smart objects came in as their embedded preview; the linked file is not read`, name);
6579
+ notes.push(`linked to "${file.path ?? file.name}"`);
6580
+ } else if (!notes.includes("contents could not be read")) {
6581
+ ctx.warn.add("A smart object without readable contents came in with its stored pixels as contents", name);
6582
+ notes.push("contents missing");
6583
+ }
6584
+ placement = { corners: boxQuad({ x: rec.rect.left, y: rec.rect.top, width, height }) };
6585
+ preview = true;
6586
+ }
6587
+ const layer = { type: "smart", id: ctx.newId(name), ...props, contents, placement, surface, offset: { x: 0, y: 0 } };
6588
+ const fill = u8Block(rec, "iOpa");
6589
+ if (fill !== void 0 && fill < 255) layer.fillOpacity = percent(fill);
6590
+ const effects = recordEffects(ctx, rec, name, notes, false);
6591
+ if (effects) layer.effects = effects;
6592
+ const mask = recordMask(ctx, rec, name);
6593
+ if (mask) layer.mask = mask;
6594
+ if (placed.filterFX) {
6595
+ const fnotes = [];
6596
+ let n = 0;
6597
+ const stack = smartFiltersFromPsd(placed.filterFX, () => `${layer.id}-flt-${++n}`, fnotes);
6598
+ if (preview) {
6599
+ stack.filters = stack.filters.map((f) => f.kind === "psd" ? f : { ...f, enabled: false });
6600
+ }
6601
+ for (const note of fnotes) {
6602
+ ctx.warn.add(`Smart filters were approximated: ${note}`, name);
6603
+ notes.push(note);
6604
+ }
6605
+ const item = ctx.filterItems.get(placed.placed);
6606
+ const fm = item ? filterMaskFromItem(item) : null;
6607
+ if (fm) {
6608
+ if (descBool(placed.filterFX, "filterMaskEnable") === false) fm.enabled = false;
6609
+ if (descBool(placed.filterFX, "filterMaskLinked") === false) fm.linked = false;
6610
+ stack.mask = fm;
6611
+ }
6612
+ if (stack.filters.length || stack.mask || !stack.enabled) layer.smartFilters = stack;
6613
+ }
6614
+ layer.psd = { key: block.key, data: block.data.slice(), placed: placed.placed || void 0 };
6615
+ return layer;
6616
+ }
5916
6617
  function mergedLayer(ctx) {
5917
6618
  const { header } = ctx.file;
5918
6619
  const { width, height } = header;
@@ -5960,8 +6661,24 @@ function psdToLayers(file, options = {}) {
5960
6661
  summary: [],
5961
6662
  colorCount: colorChannelCount(header.colorMode, header.channels),
5962
6663
  palette: header.colorMode === MODE_INDEXED ? file.colorModeData : void 0,
5963
- patterns: /* @__PURE__ */ new Map()
6664
+ patterns: /* @__PURE__ */ new Map(),
6665
+ linked: /* @__PURE__ */ new Map(),
6666
+ filterItems: /* @__PURE__ */ new Map(),
6667
+ contents: /* @__PURE__ */ new Map(),
6668
+ depth: options.depth ?? 0
5964
6669
  };
6670
+ for (const block of file.globalBlocks) {
6671
+ if (LINKED_FILE_KEYS.includes(block.key)) {
6672
+ for (const f of parseLinkedFiles(block.data)) if (!ctx.linked.has(f.uuid)) ctx.linked.set(f.uuid, f);
6673
+ }
6674
+ if (FILTER_EFFECTS_KEYS.includes(block.key)) {
6675
+ try {
6676
+ for (const it of parseFilterEffects(block.data).items) ctx.filterItems.set(it.id, it);
6677
+ } catch (e) {
6678
+ warn.extra.push(`The smart filter masks could not be read (${e instanceof Error ? e.message : String(e)}); they reveal everything.`);
6679
+ }
6680
+ }
6681
+ }
5965
6682
  for (const block of file.globalBlocks) {
5966
6683
  if (!PATTERN_BLOCK_KEYS.includes(block.key)) continue;
5967
6684
  try {
@@ -6064,14 +6781,20 @@ var BLEND_LONG = [
6064
6781
  ];
6065
6782
  var BLEND_OF = new Map([...BLEND_CODES, ...BLEND_LONG].map(([m, id]) => [id, m]));
6066
6783
  var BLEND_ID = new Map(BLEND_CODES);
6784
+ function blendFromPsdId(id) {
6785
+ return BLEND_OF.get(id);
6786
+ }
6787
+ function psdBlendId(mode) {
6788
+ return BLEND_ID.get(mode) ?? "Nrml";
6789
+ }
6067
6790
  var num2 = (d, key, fallback) => descNumber(d, key) ?? fallback;
6068
6791
  var pct = (d, key, fallback) => num2(d, key, fallback * 100) / 100;
6069
6792
  var bool = (d, key) => descBool(d, key) === true;
6070
- var enumOf = (d, key) => {
6793
+ var enumOf2 = (d, key) => {
6071
6794
  const v = descGet(d, key);
6072
6795
  return v?.type === "enum" ? v.value : void 0;
6073
6796
  };
6074
- var text = (d, key) => {
6797
+ var text2 = (d, key) => {
6075
6798
  const v = d ? descGet(d, key) : void 0;
6076
6799
  return v?.type === "TEXT" ? v.value : "";
6077
6800
  };
@@ -6081,15 +6804,15 @@ var list = (d, key) => {
6081
6804
  return v.items.flatMap((it) => it.type === "Objc" || it.type === "GlbO" ? [it.value] : []);
6082
6805
  };
6083
6806
  var clamp3 = (v, lo, hi) => Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : lo;
6084
- var unit = (u, value) => ({ type: "UntF", unit: u, value });
6085
- var dbl = (value) => ({ type: "doub", value });
6086
- var long2 = (value) => ({ type: "long", value: Math.round(value) });
6087
- var boolV = (value) => ({ type: "bool", value });
6088
- var enumV = (typeId, value) => ({ type: "enum", typeId, value });
6089
- var textV = (value) => ({ type: "TEXT", value });
6090
- var obj = (d) => ({ type: "Objc", value: d });
6807
+ var unit2 = (u, value) => ({ type: "UntF", unit: u, value });
6808
+ var dbl2 = (value) => ({ type: "doub", value });
6809
+ var long3 = (value) => ({ type: "long", value: Math.round(value) });
6810
+ var boolV2 = (value) => ({ type: "bool", value });
6811
+ var enumV2 = (typeId, value) => ({ type: "enum", typeId, value });
6812
+ var textV2 = (value) => ({ type: "TEXT", value });
6813
+ var obj2 = (d) => ({ type: "Objc", value: d });
6091
6814
  function blendOf(d, key, fallback, notes) {
6092
- const id = enumOf(d, key);
6815
+ const id = enumOf2(d, key);
6093
6816
  if (id === void 0) return fallback;
6094
6817
  const m = BLEND_OF.get(id);
6095
6818
  if (m) return m;
@@ -6128,15 +6851,15 @@ function colorOf(d, fallback = "#000000") {
6128
6851
  function colorDesc(hex) {
6129
6852
  const [r, g, b] = parseEffectColor(hex) ?? [0, 0, 0];
6130
6853
  return descriptor("RGBC", [
6131
- ["Rd ", dbl(r * 255)],
6132
- ["Grn ", dbl(g * 255)],
6133
- ["Bl ", dbl(b * 255)]
6854
+ ["Rd ", dbl2(r * 255)],
6855
+ ["Grn ", dbl2(g * 255)],
6856
+ ["Bl ", dbl2(b * 255)]
6134
6857
  ]);
6135
6858
  }
6136
6859
  function contourOf(d, key) {
6137
6860
  const c = descObject(d, key);
6138
6861
  if (!c) return void 0;
6139
- const name = text(c, "Nm ");
6862
+ const name = text2(c, "Nm ");
6140
6863
  const points = list(c, "Crv ").map((p) => {
6141
6864
  const out = { input: clamp3(num2(p, "Hrzn", 0) / 255, 0, 1), output: clamp3(num2(p, "Vrtc", 0) / 255, 0, 1) };
6142
6865
  if (descBool(p, "Cnty") === false) out.corner = true;
@@ -6156,16 +6879,16 @@ function contourDesc(c) {
6156
6879
  type: "VlLs",
6157
6880
  items: points.map((p) => {
6158
6881
  const items = [
6159
- ["Hrzn", dbl(p.input * 255)],
6160
- ["Vrtc", dbl(p.output * 255)]
6882
+ ["Hrzn", dbl2(p.input * 255)],
6883
+ ["Vrtc", dbl2(p.output * 255)]
6161
6884
  ];
6162
- if (p.corner) items.push(["Cnty", boolV(false)]);
6163
- return obj(descriptor("CrPt", items));
6885
+ if (p.corner) items.push(["Cnty", boolV2(false)]);
6886
+ return obj2(descriptor("CrPt", items));
6164
6887
  })
6165
6888
  };
6166
- return obj(
6889
+ return obj2(
6167
6890
  descriptor("ShpC", [
6168
- ["Nm ", textV(name)],
6891
+ ["Nm ", textV2(name)],
6169
6892
  ["Crv ", pts]
6170
6893
  ])
6171
6894
  );
@@ -6183,7 +6906,7 @@ function gradientOf(d, notes) {
6183
6906
  { position: 0, color: "#000000" },
6184
6907
  { position: 1, color: "#ffffff" }
6185
6908
  ],
6186
- style: GRADIENT_STYLES.find(([, id]) => id === enumOf(d, "Type"))?.[0] ?? "linear",
6909
+ style: GRADIENT_STYLES.find(([, id]) => id === enumOf2(d, "Type"))?.[0] ?? "linear",
6187
6910
  angle: clamp3(num2(d, "Angl", 90), -360, 360),
6188
6911
  scale: clamp3(pct(d, "Scl ", 1), 0.1, 1.5),
6189
6912
  reverse: bool(d, "Rvrs"),
@@ -6194,12 +6917,12 @@ function gradientOf(d, notes) {
6194
6917
  if (off) g.offset = { x: clamp3(pct(off, "Hrzn", 0), -1, 1), y: clamp3(pct(off, "Vrtc", 0), -1, 1) };
6195
6918
  const grad = descObject(d, "Grad");
6196
6919
  if (!grad) return g;
6197
- if (enumOf(grad, "GrdF") === "ClNs") {
6920
+ if (enumOf2(grad, "GrdF") === "ClNs") {
6198
6921
  notes.add("noise gradients became black to white");
6199
6922
  return g;
6200
6923
  }
6201
6924
  const raw = list(grad, "Clrs").map((s) => {
6202
- const type = enumOf(s, "Type");
6925
+ const type = enumOf2(s, "Type");
6203
6926
  if (type === "FrgC" || type === "BckC") notes.add("foreground / background gradient stops became black / white");
6204
6927
  const color = type === "BckC" ? "#ffffff" : colorOf(descObject(s, "Clr "));
6205
6928
  return { position: clamp3(num2(s, "Lctn", 0) / 4096, 0, 1), color, mid: clamp3(num2(s, "Mdpn", 50) / 100, 0.05, 0.95) };
@@ -6227,12 +6950,12 @@ function gradientItems(g) {
6227
6950
  const clrs = {
6228
6951
  type: "VlLs",
6229
6952
  items: g.stops.map(
6230
- (s) => obj(
6953
+ (s) => obj2(
6231
6954
  descriptor("Clrt", [
6232
- ["Clr ", obj(colorDesc(s.color))],
6233
- ["Type", enumV("Clry", "UsrS")],
6234
- ["Lctn", long2(s.position * 4096)],
6235
- ["Mdpn", long2(50)]
6955
+ ["Clr ", obj2(colorDesc(s.color))],
6956
+ ["Type", enumV2("Clry", "UsrS")],
6957
+ ["Lctn", long3(s.position * 4096)],
6958
+ ["Mdpn", long3(50)]
6236
6959
  ])
6237
6960
  )
6238
6961
  )
@@ -6241,35 +6964,35 @@ function gradientItems(g) {
6241
6964
  const trns = {
6242
6965
  type: "VlLs",
6243
6966
  items: op.map(
6244
- (s) => obj(
6967
+ (s) => obj2(
6245
6968
  descriptor("TrnS", [
6246
- ["Opct", unit("#Prc", s.opacity * 100)],
6247
- ["Lctn", long2(s.position * 4096)],
6248
- ["Mdpn", long2(50)]
6969
+ ["Opct", unit2("#Prc", s.opacity * 100)],
6970
+ ["Lctn", long3(s.position * 4096)],
6971
+ ["Mdpn", long3(50)]
6249
6972
  ])
6250
6973
  )
6251
6974
  )
6252
6975
  };
6253
6976
  const grad = descriptor("Grdn", [
6254
- ["Nm ", textV("Custom")],
6255
- ["GrdF", enumV("GrdF", "CstS")],
6256
- ["Intr", dbl(0)],
6977
+ ["Nm ", textV2("Custom")],
6978
+ ["GrdF", enumV2("GrdF", "CstS")],
6979
+ ["Intr", dbl2(0)],
6257
6980
  ["Clrs", clrs],
6258
6981
  ["Trns", trns]
6259
6982
  ]);
6260
6983
  return [
6261
- ["Grad", obj(grad)],
6262
- ["Angl", unit("#Ang", g.angle)],
6263
- ["Type", enumV("GrdT", GRADIENT_STYLES.find(([s]) => s === g.style)[1])],
6264
- ["Rvrs", boolV(g.reverse)],
6265
- ["Algn", boolV(g.align)],
6266
- ["Scl ", unit("#Prc", g.scale * 100)],
6984
+ ["Grad", obj2(grad)],
6985
+ ["Angl", unit2("#Ang", g.angle)],
6986
+ ["Type", enumV2("GrdT", GRADIENT_STYLES.find(([s]) => s === g.style)[1])],
6987
+ ["Rvrs", boolV2(g.reverse)],
6988
+ ["Algn", boolV2(g.align)],
6989
+ ["Scl ", unit2("#Prc", g.scale * 100)],
6267
6990
  [
6268
6991
  "Ofst",
6269
- obj(
6992
+ obj2(
6270
6993
  descriptor("Pnt ", [
6271
- ["Hrzn", unit("#Prc", g.offset.x * 100)],
6272
- ["Vrtc", unit("#Prc", g.offset.y * 100)]
6994
+ ["Hrzn", unit2("#Prc", g.offset.x * 100)],
6995
+ ["Vrtc", unit2("#Prc", g.offset.y * 100)]
6273
6996
  ])
6274
6997
  )
6275
6998
  ]
@@ -6277,13 +7000,13 @@ function gradientItems(g) {
6277
7000
  }
6278
7001
  function patternRefOf(d) {
6279
7002
  const p = descObject(d, "Ptrn");
6280
- return { patternId: text(p, "Idnt") || text(p, "Nm "), name: text(p, "Nm ") };
7003
+ return { patternId: text2(p, "Idnt") || text2(p, "Nm "), name: text2(p, "Nm ") };
6281
7004
  }
6282
7005
  function patternRefDesc(patternId, name) {
6283
- return obj(
7006
+ return obj2(
6284
7007
  descriptor("Ptrn", [
6285
- ["Nm ", textV(name ?? patternId)],
6286
- ["Idnt", textV(patternId)]
7008
+ ["Nm ", textV2(name ?? patternId)],
7009
+ ["Idnt", textV2(patternId)]
6287
7010
  ])
6288
7011
  );
6289
7012
  }
@@ -6297,14 +7020,14 @@ function placementOf(d) {
6297
7020
  }
6298
7021
  function placementItems(angle, link, phase) {
6299
7022
  const out = [];
6300
- if (angle) out.push(["Angl", unit("#Ang", angle)]);
6301
- out.push(["Algn", boolV(link)]);
7023
+ if (angle) out.push(["Angl", unit2("#Ang", angle)]);
7024
+ out.push(["Algn", boolV2(link)]);
6302
7025
  out.push([
6303
7026
  "phase",
6304
- obj(
7027
+ obj2(
6305
7028
  descriptor("Pnt ", [
6306
- ["Hrzn", dbl(phase.x)],
6307
- ["Vrtc", dbl(phase.y)]
7029
+ ["Hrzn", dbl2(phase.x)],
7030
+ ["Vrtc", dbl2(phase.y)]
6308
7031
  ])
6309
7032
  )
6310
7033
  ]);
@@ -6353,7 +7076,7 @@ function readOne(kind, d, notes) {
6353
7076
  const base = {
6354
7077
  ...common(d, kind, notes),
6355
7078
  fill,
6356
- technique: enumOf(d, "GlwT") === "PrBL" ? "precise" : "softer",
7079
+ technique: enumOf2(d, "GlwT") === "PrBL" ? "precise" : "softer",
6357
7080
  size: size("blur", 5),
6358
7081
  contour: contourOf(d, "TrnS"),
6359
7082
  antiAlias: bool(d, "AntA"),
@@ -6362,7 +7085,7 @@ function readOne(kind, d, notes) {
6362
7085
  noise: clamp3(pct(d, "Nose", 0), 0, 1)
6363
7086
  };
6364
7087
  const spread = clamp3(pct(d, "Ckmt", 0), 0, 1);
6365
- return kind === "outer-glow" ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf(d, "glwS") === "SrcC" ? "center" : "edge" };
7088
+ return kind === "outer-glow" ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf2(d, "glwS") === "SrcC" ? "center" : "edge" };
6366
7089
  }
6367
7090
  case "bevel-emboss": {
6368
7091
  const styles = { OtrB: "outer-bevel", InrB: "inner-bevel", Embs: "emboss", PlEb: "pillow-emboss", strokeEmboss: "stroke-emboss" };
@@ -6370,10 +7093,10 @@ function readOne(kind, d, notes) {
6370
7093
  id: newEffectId(),
6371
7094
  enabled: descBool(d, "enab") !== false,
6372
7095
  kind,
6373
- style: styles[enumOf(d, "bvlS") ?? ""] ?? "inner-bevel",
6374
- technique: enumOf(d, "bvlT") === "PrBL" ? "chisel-hard" : enumOf(d, "bvlT") === "Slmt" ? "chisel-soft" : "smooth",
7096
+ style: styles[enumOf2(d, "bvlS") ?? ""] ?? "inner-bevel",
7097
+ technique: enumOf2(d, "bvlT") === "PrBL" ? "chisel-hard" : enumOf2(d, "bvlT") === "Slmt" ? "chisel-soft" : "smooth",
6375
7098
  depth: clamp3(pct(d, "srgR", 1), 0, 10),
6376
- direction: enumOf(d, "bvlD") === "Out " ? "down" : "up",
7099
+ direction: enumOf2(d, "bvlD") === "Out " ? "down" : "up",
6377
7100
  size: size("blur", 5),
6378
7101
  soften: clamp3(num2(d, "Sftn", 0), 0, 16),
6379
7102
  angle: clamp3(num2(d, "lagl", 120), -360, 360),
@@ -6417,14 +7140,14 @@ function readOne(kind, d, notes) {
6417
7140
  return { ...common(d, kind, notes), kind, patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10), angle: place.angle, link: place.link, phase: place.phase };
6418
7141
  }
6419
7142
  case "stroke": {
6420
- const paint = enumOf(d, "PntT");
7143
+ const paint = enumOf2(d, "PntT");
6421
7144
  let fill;
6422
7145
  if (paint === "GrFl") fill = { type: "gradient", gradient: gradientOf(d, notes) };
6423
7146
  else if (paint === "Ptrn") {
6424
7147
  const ref = patternRefOf(d);
6425
7148
  fill = { type: "pattern", patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10) };
6426
7149
  } else fill = { type: "color", color: colorOf(descObject(d, "Clr ")) };
6427
- const pos = enumOf(d, "Styl");
7150
+ const pos = enumOf2(d, "Styl");
6428
7151
  return { ...common(d, kind, notes), kind, size: size("Sz ", 3), position: pos === "InsF" ? "inside" : pos === "CtrF" ? "center" : "outside", fill };
6429
7152
  }
6430
7153
  }
@@ -6451,11 +7174,11 @@ function parseLfx2(data) {
6451
7174
  }
6452
7175
  function commonItems(e) {
6453
7176
  return [
6454
- ["enab", boolV(e.enabled)],
6455
- ["present", boolV(true)],
6456
- ["showInDialog", boolV(true)],
6457
- ["Md ", enumV("BlnM", BLEND_ID.get(e.blend) ?? "Nrml")],
6458
- ["Opct", unit("#Prc", e.opacity * 100)]
7177
+ ["enab", boolV2(e.enabled)],
7178
+ ["present", boolV2(true)],
7179
+ ["showInDialog", boolV2(true)],
7180
+ ["Md ", enumV2("BlnM", BLEND_ID.get(e.blend) ?? "Nrml")],
7181
+ ["Opct", unit2("#Prc", e.opacity * 100)]
6459
7182
  ];
6460
7183
  }
6461
7184
  function writeOne(e) {
@@ -6465,17 +7188,17 @@ function writeOne(e) {
6465
7188
  const inner = e.kind === "inner-shadow";
6466
7189
  const items = [
6467
7190
  ...commonItems(e),
6468
- ["Clr ", obj(colorDesc(e.color))],
6469
- ["uglg", boolV(e.useGlobalLight)],
6470
- ["lagl", unit("#Ang", e.angle)],
6471
- ["Dstn", unit("#Pxl", e.distance)],
6472
- ["Ckmt", unit("#Pxl", (inner ? e.choke : e.spread) * 100)],
6473
- ["blur", unit("#Pxl", e.size)],
6474
- ["Nose", unit("#Prc", e.noise * 100)],
6475
- ["AntA", boolV(e.antiAlias)],
7191
+ ["Clr ", obj2(colorDesc(e.color))],
7192
+ ["uglg", boolV2(e.useGlobalLight)],
7193
+ ["lagl", unit2("#Ang", e.angle)],
7194
+ ["Dstn", unit2("#Pxl", e.distance)],
7195
+ ["Ckmt", unit2("#Pxl", (inner ? e.choke : e.spread) * 100)],
7196
+ ["blur", unit2("#Pxl", e.size)],
7197
+ ["Nose", unit2("#Prc", e.noise * 100)],
7198
+ ["AntA", boolV2(e.antiAlias)],
6476
7199
  ["TrnS", contourDesc(e.contour)]
6477
7200
  ];
6478
- if (e.kind === "drop-shadow") items.push(["layerConceals", boolV(e.knocksOut)]);
7201
+ if (e.kind === "drop-shadow") items.push(["layerConceals", boolV2(e.knocksOut)]);
6479
7202
  return descriptor(inner ? "IrSh" : "DrSh", items);
6480
7203
  }
6481
7204
  case "outer-glow":
@@ -6483,110 +7206,110 @@ function writeOne(e) {
6483
7206
  const inner = e.kind === "inner-glow";
6484
7207
  const items = [...commonItems(e)];
6485
7208
  if (e.fill.type === "gradient") items.push(...gradientItems(e.fill.gradient));
6486
- else items.push(["Clr ", obj(colorDesc(e.fill.color))]);
7209
+ else items.push(["Clr ", obj2(colorDesc(e.fill.color))]);
6487
7210
  items.push(
6488
- ["GlwT", enumV("BETE", e.technique === "precise" ? "PrBL" : "SfBL")],
6489
- ["Ckmt", unit("#Pxl", (inner ? e.choke : e.spread) * 100)],
6490
- ["blur", unit("#Pxl", e.size)],
6491
- ["Nose", unit("#Prc", e.noise * 100)],
6492
- ["ShdN", unit("#Prc", e.jitter * 100)],
6493
- ["AntA", boolV(e.antiAlias)],
7211
+ ["GlwT", enumV2("BETE", e.technique === "precise" ? "PrBL" : "SfBL")],
7212
+ ["Ckmt", unit2("#Pxl", (inner ? e.choke : e.spread) * 100)],
7213
+ ["blur", unit2("#Pxl", e.size)],
7214
+ ["Nose", unit2("#Prc", e.noise * 100)],
7215
+ ["ShdN", unit2("#Prc", e.jitter * 100)],
7216
+ ["AntA", boolV2(e.antiAlias)],
6494
7217
  ["TrnS", contourDesc(e.contour)],
6495
- ["Inpr", unit("#Prc", e.range * 100)]
7218
+ ["Inpr", unit2("#Prc", e.range * 100)]
6496
7219
  );
6497
- if (e.kind === "inner-glow") items.push(["glwS", enumV("IGSr", e.source === "center" ? "SrcC" : "SrcE")]);
7220
+ if (e.kind === "inner-glow") items.push(["glwS", enumV2("IGSr", e.source === "center" ? "SrcC" : "SrcE")]);
6498
7221
  return descriptor(inner ? "IrGl" : "OrGl", items);
6499
7222
  }
6500
7223
  case "bevel-emboss": {
6501
7224
  const style = { "outer-bevel": "OtrB", "inner-bevel": "InrB", emboss: "Embs", "pillow-emboss": "PlEb", "stroke-emboss": "strokeEmboss" }[e.style];
6502
7225
  const tech = { smooth: "SfBL", "chisel-hard": "PrBL", "chisel-soft": "Slmt" }[e.technique];
6503
7226
  const items = [
6504
- ["enab", boolV(e.enabled)],
6505
- ["present", boolV(true)],
6506
- ["showInDialog", boolV(true)],
6507
- ["hglM", enumV("BlnM", BLEND_ID.get(e.highlightBlend) ?? "Scrn")],
6508
- ["hglC", obj(colorDesc(e.highlightColor))],
6509
- ["hglO", unit("#Prc", e.highlightOpacity * 100)],
6510
- ["sdwM", enumV("BlnM", BLEND_ID.get(e.shadowBlend) ?? "Mltp")],
6511
- ["sdwC", obj(colorDesc(e.shadowColor))],
6512
- ["sdwO", unit("#Prc", e.shadowOpacity * 100)],
6513
- ["bvlT", enumV("bvlT", tech)],
6514
- ["bvlS", enumV("BESl", style)],
6515
- ["uglg", boolV(e.useGlobalLight)],
6516
- ["lagl", unit("#Ang", e.angle)],
6517
- ["Lald", unit("#Ang", e.altitude)],
6518
- ["srgR", unit("#Prc", e.depth * 100)],
6519
- ["blur", unit("#Pxl", e.size)],
6520
- ["bvlD", enumV("BESs", e.direction === "up" ? "In " : "Out ")],
7227
+ ["enab", boolV2(e.enabled)],
7228
+ ["present", boolV2(true)],
7229
+ ["showInDialog", boolV2(true)],
7230
+ ["hglM", enumV2("BlnM", BLEND_ID.get(e.highlightBlend) ?? "Scrn")],
7231
+ ["hglC", obj2(colorDesc(e.highlightColor))],
7232
+ ["hglO", unit2("#Prc", e.highlightOpacity * 100)],
7233
+ ["sdwM", enumV2("BlnM", BLEND_ID.get(e.shadowBlend) ?? "Mltp")],
7234
+ ["sdwC", obj2(colorDesc(e.shadowColor))],
7235
+ ["sdwO", unit2("#Prc", e.shadowOpacity * 100)],
7236
+ ["bvlT", enumV2("bvlT", tech)],
7237
+ ["bvlS", enumV2("BESl", style)],
7238
+ ["uglg", boolV2(e.useGlobalLight)],
7239
+ ["lagl", unit2("#Ang", e.angle)],
7240
+ ["Lald", unit2("#Ang", e.altitude)],
7241
+ ["srgR", unit2("#Prc", e.depth * 100)],
7242
+ ["blur", unit2("#Pxl", e.size)],
7243
+ ["bvlD", enumV2("BESs", e.direction === "up" ? "In " : "Out ")],
6521
7244
  ["TrnS", contourDesc(e.glossContour)],
6522
- ["Sftn", unit("#Pxl", e.soften)]
7245
+ ["Sftn", unit2("#Pxl", e.soften)]
6523
7246
  ];
6524
7247
  if (e.contourElement) {
6525
- items.push(["useShape", boolV(true)], ["MpgS", contourDesc(e.contourElement.contour)], ["AntA", boolV(e.contourElement.antiAlias)], ["Inpr", unit("#Prc", e.contourElement.range * 100)]);
6526
- } else items.push(["useShape", boolV(false)]);
7248
+ items.push(["useShape", boolV2(true)], ["MpgS", contourDesc(e.contourElement.contour)], ["AntA", boolV2(e.contourElement.antiAlias)], ["Inpr", unit2("#Prc", e.contourElement.range * 100)]);
7249
+ } else items.push(["useShape", boolV2(false)]);
6527
7250
  if (e.texture) {
6528
7251
  const t = e.texture;
6529
7252
  items.push(
6530
- ["useTexture", boolV(true)],
6531
- ["InvT", boolV(t.invert)],
6532
- ["Scl ", unit("#Prc", t.scale * 100)],
6533
- ["textureDepth", unit("#Prc", t.depth * 100)],
7253
+ ["useTexture", boolV2(true)],
7254
+ ["InvT", boolV2(t.invert)],
7255
+ ["Scl ", unit2("#Prc", t.scale * 100)],
7256
+ ["textureDepth", unit2("#Prc", t.depth * 100)],
6534
7257
  ["Ptrn", patternRefDesc(t.patternId, t.name)],
6535
7258
  ...placementItems(0, t.link ?? true, t.phase ?? { x: 0, y: 0 })
6536
7259
  );
6537
- } else items.push(["useTexture", boolV(false)]);
7260
+ } else items.push(["useTexture", boolV2(false)]);
6538
7261
  return descriptor("ebbl", items);
6539
7262
  }
6540
7263
  case "satin":
6541
7264
  return descriptor("ChFX", [
6542
7265
  ...commonItems(e),
6543
- ["Clr ", obj(colorDesc(e.color))],
6544
- ["AntA", boolV(e.antiAlias)],
6545
- ["Invr", boolV(e.invert)],
6546
- ["lagl", unit("#Ang", e.angle)],
6547
- ["Dstn", unit("#Pxl", e.distance)],
6548
- ["blur", unit("#Pxl", e.size)],
7266
+ ["Clr ", obj2(colorDesc(e.color))],
7267
+ ["AntA", boolV2(e.antiAlias)],
7268
+ ["Invr", boolV2(e.invert)],
7269
+ ["lagl", unit2("#Ang", e.angle)],
7270
+ ["Dstn", unit2("#Pxl", e.distance)],
7271
+ ["blur", unit2("#Pxl", e.size)],
6549
7272
  ["MpgS", contourDesc(e.contour)]
6550
7273
  ]);
6551
7274
  case "color-overlay":
6552
- return descriptor("SoFi", [...commonItems(e), ["Clr ", obj(colorDesc(e.color))]]);
7275
+ return descriptor("SoFi", [...commonItems(e), ["Clr ", obj2(colorDesc(e.color))]]);
6553
7276
  case "gradient-overlay":
6554
- return descriptor("GrFl", [...commonItems(e), ...gradientItems(e.gradient), ["Dthr", boolV(e.dither)]]);
7277
+ return descriptor("GrFl", [...commonItems(e), ...gradientItems(e.gradient), ["Dthr", boolV2(e.dither)]]);
6555
7278
  case "pattern-overlay":
6556
7279
  return descriptor("patternFill", [
6557
7280
  ...commonItems(e),
6558
7281
  ["Ptrn", patternRefDesc(e.patternId, e.name)],
6559
- ["Scl ", unit("#Prc", e.scale * 100)],
7282
+ ["Scl ", unit2("#Prc", e.scale * 100)],
6560
7283
  ...placementItems(e.angle ?? 0, e.link ?? true, e.phase ?? { x: 0, y: 0 })
6561
7284
  ]);
6562
7285
  case "stroke": {
6563
7286
  const items = [
6564
7287
  ...commonItems(e),
6565
- ["Styl", enumV("FStl", e.position === "inside" ? "InsF" : e.position === "center" ? "CtrF" : "OutF")],
6566
- ["Sz ", unit("#Pxl", e.size)]
7288
+ ["Styl", enumV2("FStl", e.position === "inside" ? "InsF" : e.position === "center" ? "CtrF" : "OutF")],
7289
+ ["Sz ", unit2("#Pxl", e.size)]
6567
7290
  ];
6568
7291
  const f = e.fill;
6569
- if (f.type === "color") items.push(["PntT", enumV("FrFl", "SClr")], ["Clr ", obj(colorDesc(f.color))]);
6570
- else if (f.type === "gradient") items.push(["PntT", enumV("FrFl", "GrFl")], ...gradientItems(f.gradient));
6571
- else items.push(["PntT", enumV("FrFl", "Ptrn")], ["Ptrn", patternRefDesc(f.patternId, f.name)], ["Scl ", unit("#Prc", f.scale * 100)]);
7292
+ if (f.type === "color") items.push(["PntT", enumV2("FrFl", "SClr")], ["Clr ", obj2(colorDesc(f.color))]);
7293
+ else if (f.type === "gradient") items.push(["PntT", enumV2("FrFl", "GrFl")], ...gradientItems(f.gradient));
7294
+ else items.push(["PntT", enumV2("FrFl", "Ptrn")], ["Ptrn", patternRefDesc(f.patternId, f.name)], ["Scl ", unit2("#Prc", f.scale * 100)]);
6572
7295
  return descriptor("FrFX", items);
6573
7296
  }
6574
7297
  }
6575
7298
  }
6576
7299
  function writeLfx2(effects) {
6577
7300
  const items = [
6578
- ["Scl ", unit("#Prc", 100)],
6579
- ["masterFXSwitch", boolV(effects.enabled)]
7301
+ ["Scl ", unit2("#Prc", 100)],
7302
+ ["masterFXSwitch", boolV2(effects.enabled)]
6580
7303
  ];
6581
7304
  const scale = effectsScale(effects);
6582
7305
  let dropped = 0;
6583
7306
  for (const [single, multi, kind] of KINDS) {
6584
7307
  const of = effects.items.filter((e) => e.kind === kind).map((e) => writeOne(scaleLayerEffect(e, scale)));
6585
7308
  if (!of.length) continue;
6586
- if (of.length === 1) items.push([single, obj(of[0])]);
6587
- else if (multi) items.push([multi, { type: "VlLs", items: of.map(obj) }]);
7309
+ if (of.length === 1) items.push([single, obj2(of[0])]);
7310
+ else if (multi) items.push([multi, { type: "VlLs", items: of.map(obj2) }]);
6588
7311
  else {
6589
- items.push([single, obj(of[0])]);
7312
+ items.push([single, obj2(of[0])]);
6590
7313
  dropped += of.length - 1;
6591
7314
  }
6592
7315
  }
@@ -6716,6 +7439,76 @@ function lockBits(layer) {
6716
7439
  if (!l) return 0;
6717
7440
  return (l.all ? 2147483648 : 0) | (l.transparency ? 1 : 0) | (l.pixels ? 2 : 0) | (l.position ? 4 : 0);
6718
7441
  }
7442
+ function prepareNestedLayers(layers) {
7443
+ const out = [];
7444
+ for (const l of layers) {
7445
+ if (l.type === "annotation") continue;
7446
+ let next = l;
7447
+ if (l.type === "group") next = { ...l, children: prepareNestedLayers(l.children) };
7448
+ else if (l.type === "pixel" && (l.filters?.length || l.source === "document")) {
7449
+ const p = { ...l };
7450
+ if (l.filters?.some(isActiveFilter)) {
7451
+ const b = l.surface.bounds;
7452
+ if (b) {
7453
+ const r = { x: b.x + l.offset.x - 64, y: b.y + l.offset.y - 64, width: b.width + 128, height: b.height + 128 };
7454
+ const plain = { ...l, visible: true, opacity: 1, blend: "normal" };
7455
+ delete plain.mask;
7456
+ delete plain.clipped;
7457
+ delete plain.effects;
7458
+ delete plain.fillOpacity;
7459
+ const data = new LayerCompositor().composite([plain], r, { width: r.x + r.width, height: r.y + r.height });
7460
+ p.surface = surfaceFromPixels({ width: r.width, height: r.height, data }, 4, r.x - l.offset.x, r.y - l.offset.y);
7461
+ }
7462
+ }
7463
+ delete p.filters;
7464
+ delete p.source;
7465
+ next = p;
7466
+ }
7467
+ if (next.mask?.source) {
7468
+ next = { ...next };
7469
+ delete next.mask;
7470
+ }
7471
+ out.push(next);
7472
+ }
7473
+ return out;
7474
+ }
7475
+ function smartContentsFile(c) {
7476
+ const base = c.name.replace(/\.\w+$/, "") || "Contents";
7477
+ if (c.file) return { name: c.name, bytes: c.file.bytes };
7478
+ if (c.kind === "raster") {
7479
+ const data = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
7480
+ return { name: `${base}.png`, bytes: encodePng(c.width, c.height, data) };
7481
+ }
7482
+ const doc = c.document;
7483
+ const merged = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
7484
+ const res = exportPsd({ width: c.width, height: c.height, layers: prepareNestedLayers(doc.layers), merged, patterns: doc.patterns, globalLight: doc.globalLight });
7485
+ return { name: `${base}.${res.psb ? "psb" : "psd"}`, bytes: res.bytes };
7486
+ }
7487
+ var near = (a, b) => Math.abs(a - b) <= 1e-6 * (1 + Math.abs(a));
7488
+ function placedUnchanged(layer, idnt, placedId) {
7489
+ const raw = layer.psd;
7490
+ if (!raw || raw.key === "PlLd") return false;
7491
+ const p = parsePlaced(raw.key, raw.data);
7492
+ if (!p || p.idnt !== idnt || p.placed !== placedId) return false;
7493
+ const size = p.size ?? { width: layer.contents.width, height: layer.contents.height };
7494
+ if (size.width !== layer.contents.width || size.height !== layer.contents.height) return false;
7495
+ const was = placementFromPlaced(p, size, []);
7496
+ if (!was) return false;
7497
+ const now = placementGeometry(layer);
7498
+ if (!was.corners.every((c, i) => near(c.x, now.corners[i].x) && near(c.y, now.corners[i].y))) return false;
7499
+ const meshKey = (m) => m ? m.points.map((q2) => `${q2.x.toFixed(5)},${q2.y.toFixed(5)}`).join(";") + `|${m.us.length}x${m.vs.length}` : "";
7500
+ if (meshKey(was.warp) !== meshKey(now.warp)) return false;
7501
+ if ((layer.placement.interpolation ?? "bicubic") !== "bicubic") return false;
7502
+ const stored = p.filterFX ? smartFiltersFromPsd(p.filterFX, () => "", []) : void 0;
7503
+ if (stored && p.filterFX) {
7504
+ if (descBool(p.filterFX, "filterMaskEnable") === false || descBool(p.filterFX, "filterMaskLinked") === false) {
7505
+ stored.mask = { surface: layer.surface, defaultValue: 255, enabled: descBool(p.filterFX, "filterMaskEnable") !== false, linked: descBool(p.filterFX, "filterMaskLinked") !== false };
7506
+ }
7507
+ }
7508
+ const current = layer.smartFilters;
7509
+ const norm = (st) => st && (st.filters.length || !st.enabled) ? filterStackKey({ ...st, mask: st.mask && (st.mask.enabled === false || st.mask.linked === false) ? st.mask : void 0 }) : "";
7510
+ return norm(stored) === norm(current);
7511
+ }
6719
7512
  function exportPsd(input) {
6720
7513
  const { width, height } = input;
6721
7514
  if (width < 1 || height < 1) throw new PsdError("The document is empty.");
@@ -6725,6 +7518,9 @@ function exportPsd(input) {
6725
7518
  const notes = /* @__PURE__ */ new Map();
6726
7519
  const note = (msg, name) => notes.set(msg, [...notes.get(msg) ?? [], name]);
6727
7520
  const patternIds2 = /* @__PURE__ */ new Set();
7521
+ const linkedFiles = /* @__PURE__ */ new Map();
7522
+ const filterItems = [];
7523
+ const placedIds = /* @__PURE__ */ new Set();
6728
7524
  const effectsBlock = (layer) => {
6729
7525
  const fx = layer.effects;
6730
7526
  if (!fx) return null;
@@ -6834,6 +7630,57 @@ function exportPsd(input) {
6834
7630
  records.push(rec);
6835
7631
  return;
6836
7632
  }
7633
+ case "smart": {
7634
+ const extra = [];
7635
+ if (layer.fillOpacity !== void 0 && layer.fillOpacity < 1) {
7636
+ extra.push({ key: "iOpa", data: Uint8Array.from([Math.round(Math.max(0, layer.fillOpacity) * 255), 0, 0, 0]) });
7637
+ }
7638
+ const c = layer.contents;
7639
+ const idnt = psdUuid(c.id);
7640
+ if (!linkedFiles.has(idnt)) {
7641
+ if (c.linked) note("Linked smart objects were embedded (their preview is written as the contents)", layer.name);
7642
+ linkedFiles.set(idnt, smartContentsFile(c));
7643
+ }
7644
+ let placedId = layer.psd?.placed ?? psdUuid(`${layer.id}:placed`);
7645
+ if (placedIds.has(placedId)) placedId = psdUuid(`${layer.id}:placed:${placedIds.size}`);
7646
+ placedIds.add(placedId);
7647
+ if (placedUnchanged(layer, idnt, placedId)) {
7648
+ extra.push({ key: layer.psd.key, data: padEven(layer.psd.data) });
7649
+ } else {
7650
+ const dropped = [];
7651
+ const filterFX = layer.smartFilters ? filterFXDescriptor(layer.smartFilters, dropped) : void 0;
7652
+ if (dropped.length) note("Develop smart filters have no Photoshop filter and were left out of the filter list (the layer\u2019s pixels still show them)", layer.name);
7653
+ const raw = layer.psd && layer.psd.key !== "PlLd" ? parsePlaced(layer.psd.key, layer.psd.data) : null;
7654
+ const spec = { idnt, placed: placedId, placement: layer.placement, size: { width: c.width, height: c.height }, filterFX, template: raw?.descriptor };
7655
+ const wnotes = [];
7656
+ extra.push({ key: "PlLd", data: plldBytes(spec) }, { key: "SoLd", data: soldBytes(spec, wnotes) });
7657
+ for (const n2 of wnotes) note(`A smart object: ${n2}`, layer.name);
7658
+ }
7659
+ const fm = layer.smartFilters?.mask;
7660
+ if (fm && !fm.source) filterItems.push(filterMaskItem(placedId, fm, { x: 0, y: 0, width, height }));
7661
+ const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
7662
+ const bounds = contentBounds(layer.surface);
7663
+ if (bounds) {
7664
+ const n2 = bounds.width * bounds.height;
7665
+ const [r2, g2, b2, a2] = rgbaToPlanes(readRegion(layer.surface, bounds), n2);
7666
+ rec.rect = toRect(bounds);
7667
+ rec.channels = [
7668
+ rleChannel(CHANNEL_ALPHA, a2, bounds.width, bounds.height, psb),
7669
+ rleChannel(0, r2, bounds.width, bounds.height, psb),
7670
+ rleChannel(1, g2, bounds.width, bounds.height, psb),
7671
+ rleChannel(2, b2, bounds.width, bounds.height, psb)
7672
+ ];
7673
+ } else {
7674
+ rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);
7675
+ }
7676
+ if (layer.mask) {
7677
+ const m = maskChannel(layer.mask, 0, 0, psb);
7678
+ rec.channels.push(m.channel);
7679
+ rec.mask = m.mask;
7680
+ }
7681
+ records.push(rec);
7682
+ return;
7683
+ }
6837
7684
  case "annotation":
6838
7685
  throw new PsdError(`Annotation layer "${layer.name}" must be rasterized before export.`);
6839
7686
  }
@@ -6883,6 +7730,17 @@ function exportPsd(input) {
6883
7730
  }
6884
7731
  if (embed.length) globalBlocks.push({ key: "Patt", data: writePatternBlock(embed) });
6885
7732
  }
7733
+ if (linkedFiles.size) {
7734
+ const parts = [...linkedFiles].map(([uuid, f]) => encodeLinkedFile({ uuid, name: f.name, bytes: f.bytes }));
7735
+ const data = new Uint8Array(parts.reduce((n2, p) => n2 + p.length, 0));
7736
+ let at = 0;
7737
+ for (const p of parts) {
7738
+ data.set(p, at);
7739
+ at += p.length;
7740
+ }
7741
+ globalBlocks.push({ key: "lnk2", data });
7742
+ }
7743
+ if (filterItems.length) globalBlocks.push({ key: "FEid", data: writeFilterEffects(filterItems) });
6886
7744
  const bytes = writePsd(
6887
7745
  {
6888
7746
  header: { psb, channels: planes.length, width, height, depth: 8, colorMode: 3 },
@@ -7147,23 +8005,24 @@ export {
7147
8005
  deserializeLayer,
7148
8006
  deserializeLayers,
7149
8007
  PsdError,
8008
+ decodePlanes,
8009
+ encodeRle,
7150
8010
  parseVersionedDescriptor,
7151
8011
  versionedDescriptorBytes,
7152
8012
  ADJUSTMENT_KEYS,
7153
8013
  SUPPORTED_ADJUSTMENT_KEYS,
7154
8014
  parseAdjustmentBlock,
7155
8015
  writeAdjustmentBlock,
7156
- decodePlanes,
7157
- encodeRle,
8016
+ parseLfx2,
8017
+ writeLfx2,
8018
+ parseLrfx,
8019
+ BROWSER_IMAGE_TYPES,
7158
8020
  readPsd,
7159
8021
  layerName,
7160
8022
  layerTree,
7161
8023
  decodeLayerChannel,
7162
8024
  decodeMergedImage,
7163
8025
  writePsd,
7164
- parseLfx2,
7165
- writeLfx2,
7166
- parseLrfx,
7167
8026
  PATTERN_BLOCK_KEYS,
7168
8027
  parsePatternBlock,
7169
8028
  psdPatternToEffectPattern,
@@ -7171,6 +8030,7 @@ export {
7171
8030
  PSD_BLEND_KEYS,
7172
8031
  psdToLayers,
7173
8032
  importPsd,
8033
+ smartContentsFile,
7174
8034
  exportPsd
7175
8035
  };
7176
- //# sourceMappingURL=chunk-NQLQ2JOI.js.map
8036
+ //# sourceMappingURL=chunk-4X5MIVCE.js.map