@pixerate/editor 0.110.0 → 0.112.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.
@@ -98,7 +98,8 @@ import {
98
98
  bezierPathBounds,
99
99
  outlineStroke,
100
100
  pathBoolean,
101
- strokeOutlineRings
101
+ strokeOutlineRings,
102
+ transformBezierPath
102
103
  } from "./chunk-YCSFS3GK.js";
103
104
 
104
105
  // src/image-editor/color.ts
@@ -521,20 +522,20 @@ function hitTestCrop(canvasPt, state, vpWidth, vpHeight, handleThreshold = 14, c
521
522
  }
522
523
  function hitTestCropRect(canvasPt, rect, handleThreshold) {
523
524
  const { x, y, width: w, height: h } = rect;
524
- const px = canvasPt.x;
525
+ const px2 = canvasPt.x;
525
526
  const py = canvasPt.y;
526
527
  const dist = (x1, y1, x2, y2) => Math.hypot(x1 - x2, y1 - y2);
527
- if (dist(px, py, x, y) <= handleThreshold) return "nw";
528
- if (dist(px, py, x + w, y) <= handleThreshold) return "ne";
529
- if (dist(px, py, x + w, y + h) <= handleThreshold) return "se";
530
- if (dist(px, py, x, y + h) <= handleThreshold) return "sw";
531
- const nearX = px >= x - handleThreshold && px <= x + w + handleThreshold;
528
+ if (dist(px2, py, x, y) <= handleThreshold) return "nw";
529
+ if (dist(px2, py, x + w, y) <= handleThreshold) return "ne";
530
+ if (dist(px2, py, x + w, y + h) <= handleThreshold) return "se";
531
+ if (dist(px2, py, x, y + h) <= handleThreshold) return "sw";
532
+ const nearX = px2 >= x - handleThreshold && px2 <= x + w + handleThreshold;
532
533
  const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;
533
534
  if (nearX && Math.abs(py - y) <= handleThreshold) return "n";
534
535
  if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return "s";
535
- if (nearY && Math.abs(px - x) <= handleThreshold) return "w";
536
- if (nearY && Math.abs(px - (x + w)) <= handleThreshold) return "e";
537
- if (px >= x && px <= x + w && py >= y && py <= y + h) {
536
+ if (nearY && Math.abs(px2 - x) <= handleThreshold) return "w";
537
+ if (nearY && Math.abs(px2 - (x + w)) <= handleThreshold) return "e";
538
+ if (px2 >= x && px2 <= x + w && py >= y && py <= y + h) {
538
539
  return "inside";
539
540
  }
540
541
  return null;
@@ -2145,8 +2146,8 @@ function applyFastBoxBlur(imageData, radius) {
2145
2146
  let r = 0, g = 0, b = 0, a = 0;
2146
2147
  let count = 0;
2147
2148
  for (let k = -radius; k <= radius; k++) {
2148
- const px = Math.min(w - 1, Math.max(0, x + k));
2149
- const idx = (y * w + px) * 4;
2149
+ const px2 = Math.min(w - 1, Math.max(0, x + k));
2150
+ const idx = (y * w + px2) * 4;
2150
2151
  r += src[idx];
2151
2152
  g += src[idx + 1];
2152
2153
  b += src[idx + 2];
@@ -2224,6 +2225,7 @@ function removeAlpha(imageData) {
2224
2225
  // src/image-editor/layers/model.ts
2225
2226
  var SHAPE_OPERATIONS = ["combine", "subtract", "intersect", "exclude"];
2226
2227
  var LIVE_SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "line"];
2228
+ var TEXT_ALIGNS = ["left", "center", "right", "justify"];
2227
2229
  var idCounter = 0;
2228
2230
  function newLayerId(prefix = "layer") {
2229
2231
  return `${prefix}_${Date.now().toString(36)}_${(++idCounter).toString(36)}`;
@@ -2251,13 +2253,13 @@ function pixelLayerBounds(layer, surface = layer.surface) {
2251
2253
  return { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height };
2252
2254
  }
2253
2255
  function layerTypeLabel(type) {
2254
- return type === "pixel" ? "Pixel layer" : type === "adjustment" ? "Adjustment layer" : type === "group" ? "Group" : type === "smart" ? "Smart object" : type === "shape" ? "Shape layer" : "Annotation";
2256
+ return type === "pixel" ? "Pixel layer" : type === "adjustment" ? "Adjustment layer" : type === "group" ? "Group" : type === "smart" ? "Smart object" : type === "shape" ? "Shape layer" : type === "type" ? "Type layer" : "Annotation";
2255
2257
  }
2256
2258
  function hasSurface(layer) {
2257
- return layer.type === "pixel" || layer.type === "smart" || layer.type === "shape";
2259
+ return layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2258
2260
  }
2259
2261
  function isRenderedLayer(layer) {
2260
- return layer.type === "smart" || layer.type === "shape";
2262
+ return layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2261
2263
  }
2262
2264
  function shapeLayers(layers) {
2263
2265
  return flattenLayers(layers).filter((l) => l.type === "shape");
@@ -2359,7 +2361,7 @@ var PHOTO_PROFILE = {
2359
2361
  * A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
2360
2362
  * layer, and a shape layer the pixels rendered from its paths.
2361
2363
  */
2362
- isPixelLike: (l) => l.type === "smart" || l.type === "shape",
2364
+ isPixelLike: (l) => l.type === "smart" || l.type === "shape" || l.type === "type",
2363
2365
  maskSourcePlane: (source, W, H, sources) => {
2364
2366
  const opts = source;
2365
2367
  const depth = sources.depthMap?.(opts) ?? null;
@@ -2888,14 +2890,14 @@ function parseCssColor(value) {
2888
2890
  if (h.length <= 4) h = h.replace(/./g, (c) => c + c);
2889
2891
  return { hex: `#${h.slice(0, 6)}`, alpha: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1 };
2890
2892
  }
2891
- const num3 = "([+-]?[\\d.]+%?)";
2892
- m = new RegExp(`^rgba?\\(\\s*${num3}[\\s,]+${num3}[\\s,]+${num3}(?:\\s*[,/]\\s*${num3})?\\s*\\)$`).exec(text3);
2893
+ const num4 = "([+-]?[\\d.]+%?)";
2894
+ m = new RegExp(`^rgba?\\(\\s*${num4}[\\s,]+${num4}[\\s,]+${num4}(?:\\s*[,/]\\s*${num4})?\\s*\\)$`).exec(text3);
2893
2895
  if (m) {
2894
2896
  const rgb = [m[1], m[2], m[3]].map(channel);
2895
2897
  if (!rgb.every(Number.isFinite)) return null;
2896
2898
  return { hex: `#${rgb.map(hex2).join("")}`, alpha: alphaOf(m[4]) };
2897
2899
  }
2898
- m = new RegExp(`^hsla?\\(\\s*([+-]?[\\d.]+)(?:deg)?[\\s,]+${num3}[\\s,]+${num3}(?:\\s*[,/]\\s*${num3})?\\s*\\)$`).exec(text3);
2900
+ m = new RegExp(`^hsla?\\(\\s*([+-]?[\\d.]+)(?:deg)?[\\s,]+${num4}[\\s,]+${num4}(?:\\s*[,/]\\s*${num4})?\\s*\\)$`).exec(text3);
2899
2901
  if (m) {
2900
2902
  const rgb = hslToRgb2(parseFloat(m[1]), parseFloat(m[2]) / 100, parseFloat(m[3]) / 100);
2901
2903
  if (!rgb.every(Number.isFinite)) return null;
@@ -2962,7 +2964,7 @@ function withNewIds(layer) {
2962
2964
  const next = { ...layer, id: newLayerId(layer.type === "adjustment" ? "adj" : layer.type) };
2963
2965
  if (next.type === "group") next.children = next.children.map(withNewIds);
2964
2966
  if (next.type === "annotation") next.annotation = { ...next.annotation, id: next.id };
2965
- if (next.type === "shape") delete next.annotation;
2967
+ if (next.type === "shape" || next.type === "type") delete next.annotation;
2966
2968
  if (next.type === "smart" && next.psd?.placed) {
2967
2969
  next.psd = { ...next.psd };
2968
2970
  delete next.psd.placed;
@@ -2992,7 +2994,7 @@ function setLayerProperties(layers, id, props) {
2992
2994
  next.opacity = props.opacity;
2993
2995
  }
2994
2996
  if (props.fillOpacity !== void 0) {
2995
- if (layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape") fail(`fillOpacity applies to pixel, shape and smart object layers; "${id}" is a ${layer.type} layer.`);
2997
+ if (layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape" && layer.type !== "type") fail(`fillOpacity applies to pixel, shape, type and smart object layers; "${id}" is a ${layer.type} layer.`);
2996
2998
  if (!(typeof props.fillOpacity === "number" && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail("fillOpacity must be a number from 0 to 1.");
2997
2999
  const p = next;
2998
3000
  if (props.fillOpacity === 1) delete p.fillOpacity;
@@ -3127,9 +3129,9 @@ function updateLayerFilter(layers, id, filterId, changes) {
3127
3129
  if (i === -1) fail(`Layer "${id}" has no filter "${filterId}". Filters: ${(layer.filters ?? []).map((f) => f.id).join(", ") || "none"}.`);
3128
3130
  const current = layer.filters[i];
3129
3131
  if ("kind" in changes && changes.kind !== current.kind) fail("A filter kind cannot change; remove it and add another.");
3130
- const merged = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
3132
+ const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
3131
3133
  const filters = [...layer.filters];
3132
- filters[i] = merged;
3134
+ filters[i] = merged2;
3133
3135
  return mapLayer(layers, id, () => ({ ...layer, filters }));
3134
3136
  }
3135
3137
  function removeLayerFilter(layers, id, filterId) {
@@ -3416,7 +3418,8 @@ function shapeGeometry(layer) {
3416
3418
  bounds = { x: x0, y: y0, width: Math.max(bounds.x + bounds.width, b.x + b.width) - x0, height: Math.max(bounds.y + bounds.height, b.y + b.height) - y0 };
3417
3419
  }
3418
3420
  };
3419
- if (layer.fill) for (const f of fills) add(flatBounds(f.flat));
3421
+ if (layer.fill && layer.inverted) add({ x: -1e7, y: -1e7, width: 2e7, height: 2e7 });
3422
+ else if (layer.fill) for (const f of fills) add(flatBounds(f.flat));
3420
3423
  if (stroke) add(flatBounds(stroke));
3421
3424
  const g = shapeGeometryBounds(layer) ?? { x: 0, y: 0, width: 0, height: 0 };
3422
3425
  const frame = { x: Math.floor(g.x), y: Math.floor(g.y), width: Math.max(1, Math.ceil(g.x + g.width) - Math.floor(g.x)), height: Math.max(1, Math.ceil(g.y + g.height) - Math.floor(g.y)) };
@@ -3424,8 +3427,14 @@ function shapeGeometry(layer) {
3424
3427
  geometryCache.set(layer, geo);
3425
3428
  return geo;
3426
3429
  }
3427
- function fillCoverage(geo, rect, rule) {
3430
+ function fillCoverage(geo, rect, rule, inverted = false) {
3428
3431
  const n = rect.width * rect.height;
3432
+ if (inverted) {
3433
+ const area = fillCoverage(geo, rect, rule);
3434
+ const out = new Float32Array(n);
3435
+ for (let i = 0; i < n; i++) out[i] = 1 - (area ? area[i] : 0);
3436
+ return out;
3437
+ }
3429
3438
  let acc = null;
3430
3439
  geo.fills.forEach((f, i) => {
3431
3440
  const cov = f.flat.length > 1 ? rasterizeCoverageSampled(f.flat, rect, rule) : rasterizeCoverage(f.flat, rect, rule);
@@ -3463,7 +3472,7 @@ function renderShapeRect(layer, rect, env, ctx = paintContext(env)) {
3463
3472
  const n = rect.width * rect.height;
3464
3473
  const rule = layer.fillRule ?? "nonzero";
3465
3474
  const needArea = Boolean(layer.fill) || geo.stroke !== null && geo.align !== "center";
3466
- const area = needArea ? fillCoverage(geo, rect, rule) : null;
3475
+ const area = needArea ? fillCoverage(geo, rect, rule, Boolean(layer.inverted && layer.fill)) : null;
3467
3476
  let sCov = geo.stroke ? rasterizeCoverageSampled(geo.stroke, rect, "nonzero") : null;
3468
3477
  if (sCov && geo.align === "inside") sCov = area ? combineCoverage(sCov, area, "intersect", n) : null;
3469
3478
  else if (sCov && geo.align === "outside" && area) sCov = combineCoverage(sCov, area, "subtract", n);
@@ -3520,9 +3529,9 @@ function renderShape(layer, env, options = {}) {
3520
3529
  const tiles = /* @__PURE__ */ new Map();
3521
3530
  for (const { tx, ty } of tilesInRect(region)) {
3522
3531
  const rect = { x: tx * TILE, y: ty * TILE, width: TILE, height: TILE };
3523
- const px = renderShapeRect(layer, rect, env, ctx);
3524
- if (!px) continue;
3525
- const tile = finishTile(px, 4, 0);
3532
+ const px2 = renderShapeRect(layer, rect, env, ctx);
3533
+ if (!px2) continue;
3534
+ const tile = finishTile(px2, 4, 0);
3526
3535
  if (tile) tiles.set(tileKey(tx, ty), tile);
3527
3536
  }
3528
3537
  return new TiledSurface(4, tiles, 0);
@@ -3753,6 +3762,7 @@ function createShapeLayer(init, env, id = newLayerId()) {
3753
3762
  offset: { x: 0, y: 0 }
3754
3763
  };
3755
3764
  if (init.fillRule && init.fillRule !== "nonzero") layer.fillRule = init.fillRule;
3765
+ if (init.inverted) layer.inverted = true;
3756
3766
  return rerenderShape(layer, env);
3757
3767
  }
3758
3768
  function defaultShapeName(c) {
@@ -3805,7 +3815,7 @@ function mergeShapeComponents(components, fillRule = "nonzero") {
3805
3815
  return [{ op: "combine", subpaths }];
3806
3816
  }
3807
3817
  function shapeAsPixelLayer(layer) {
3808
- const { components: _c, fillRule: _r, fill: _f, stroke: _s, annotation: _a, psd: _p, type: _t, ...rest } = layer;
3818
+ const { components: _c, fillRule: _r, inverted: _i, fill: _f, stroke: _s, annotation: _a, psd: _p, type: _t, ...rest } = layer;
3809
3819
  return { ...rest, type: "pixel", surface: layer.surface, offset: { x: 0, y: 0 } };
3810
3820
  }
3811
3821
  function annotationPaint(color, opacity) {
@@ -3887,6 +3897,671 @@ function annotationOfShape(layer) {
3887
3897
  return out;
3888
3898
  }
3889
3899
 
3900
+ // src/image-editor/layers/text.ts
3901
+ var fail3 = (message) => {
3902
+ throw new LayerOperationError(message);
3903
+ };
3904
+ var DEFAULT_TEXT_STYLE = { fontFamily: "sans-serif", fontWeight: 400, fontSize: 24, color: "#000000" };
3905
+ var DEFAULT_AUTO_LEADING = 1.2;
3906
+ var sameStyle = (a, b) => JSON.stringify(a) === JSON.stringify(b);
3907
+ function compact(runs) {
3908
+ const out = [];
3909
+ for (const r of runs) {
3910
+ if (r.length <= 0) continue;
3911
+ const last = out[out.length - 1];
3912
+ if (last && sameStyle(last.style, r.style)) out[out.length - 1] = { ...last, length: last.length + r.length };
3913
+ else out.push({ ...r });
3914
+ }
3915
+ return out;
3916
+ }
3917
+ function fitRuns(runs, length, fallback) {
3918
+ const out = [];
3919
+ let left = length;
3920
+ for (const r of runs) {
3921
+ if (left <= 0) break;
3922
+ const n = Math.min(Math.max(0, Math.floor(r.length)), left);
3923
+ if (n > 0) out.push({ ...r, length: n });
3924
+ left -= n;
3925
+ }
3926
+ if (left > 0) {
3927
+ if (out.length) out[out.length - 1] = { ...out[out.length - 1], length: out[out.length - 1].length + left };
3928
+ else out.push({ length: left, style: runs[runs.length - 1]?.style ?? fallback });
3929
+ }
3930
+ return compact(out);
3931
+ }
3932
+ function styleAt(runs, index, fallback) {
3933
+ let at = 0;
3934
+ for (const r of runs) {
3935
+ if (index < at + r.length) return r.style;
3936
+ at += r.length;
3937
+ }
3938
+ return runs[runs.length - 1]?.style ?? fallback;
3939
+ }
3940
+ function restyleRange(runs, start, end, patch) {
3941
+ const out = [];
3942
+ let at = 0;
3943
+ for (const r of runs) {
3944
+ const a = at;
3945
+ const b = at + r.length;
3946
+ at = b;
3947
+ const lo = Math.max(a, start);
3948
+ const hi = Math.min(b, end);
3949
+ if (hi <= lo) {
3950
+ out.push({ ...r });
3951
+ continue;
3952
+ }
3953
+ if (lo > a) out.push({ length: lo - a, style: r.style });
3954
+ out.push({ length: hi - lo, style: patch(r.style) });
3955
+ if (b > hi) out.push({ length: b - hi, style: r.style });
3956
+ }
3957
+ return compact(out);
3958
+ }
3959
+ function spliceRuns(runs, start, end, inserted, style) {
3960
+ const out = [];
3961
+ let at = 0;
3962
+ let placed = false;
3963
+ for (const r of runs) {
3964
+ const a = at;
3965
+ const b = at + r.length;
3966
+ at = b;
3967
+ if (b <= start) {
3968
+ out.push({ ...r });
3969
+ continue;
3970
+ }
3971
+ if (a < start) out.push({ length: start - a, style: r.style });
3972
+ if (!placed) {
3973
+ out.push({ length: inserted, style });
3974
+ placed = true;
3975
+ }
3976
+ if (b > end) out.push({ length: b - Math.max(end, a), style: r.style });
3977
+ }
3978
+ if (!placed) out.push({ length: inserted, style });
3979
+ return compact(out);
3980
+ }
3981
+ function paragraphRanges(text3) {
3982
+ const out = [];
3983
+ let start = 0;
3984
+ for (let i = 0; i < text3.length; i++) {
3985
+ if (text3[i] === "\n") {
3986
+ out.push({ start, end: i + 1, content: i });
3987
+ start = i + 1;
3988
+ }
3989
+ }
3990
+ out.push({ start, end: text3.length, content: text3.length });
3991
+ return out;
3992
+ }
3993
+ function fitParagraphRuns(runs, text3) {
3994
+ const fallback = runs[0]?.style ?? {};
3995
+ const out = compact(paragraphRanges(text3).map((p) => ({ length: p.end - p.start, style: styleAt(runs, p.start, fallback) })));
3996
+ return out.length ? out : [{ length: 0, style: fallback }];
3997
+ }
3998
+ function fontOf(style) {
3999
+ const f = { family: style.fontFamily, weight: style.fontWeight, italic: Boolean(style.italic), size: style.fontSize };
4000
+ if (style.postScriptName) f.postScriptName = style.postScriptName;
4001
+ return f;
4002
+ }
4003
+ var isBreakAfter = (c) => c === " " || c === " " || c === "-" || c === "\u2010" || c === "\u200B";
4004
+ var isSpace = (c) => c === " " || c === " ";
4005
+ var isWide = (code) => code >= 11904 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519;
4006
+ var layoutCache = /* @__PURE__ */ new WeakMap();
4007
+ function layoutText(layer, shaper) {
4008
+ let byShaper = layoutCache.get(layer);
4009
+ const hit = byShaper?.get(shaper.id);
4010
+ if (hit) return hit;
4011
+ const out = computeLayout(layer, shaper);
4012
+ if (!byShaper) {
4013
+ byShaper = /* @__PURE__ */ new Map();
4014
+ layoutCache.set(layer, byShaper);
4015
+ }
4016
+ byShaper.set(shaper.id, out);
4017
+ return out;
4018
+ }
4019
+ function computeLayout(layer, shaper) {
4020
+ const { text: text3, frame } = layer;
4021
+ const runs = fitRuns(layer.runs, text3.length, DEFAULT_TEXT_STYLE);
4022
+ const missing = /* @__PURE__ */ new Set();
4023
+ const resolved = /* @__PURE__ */ new Map();
4024
+ const familyOf = (font) => {
4025
+ const key = `${font.family}|${font.weight}|${font.italic}|${font.postScriptName ?? ""}`;
4026
+ let fam = resolved.get(key);
4027
+ if (fam === void 0) {
4028
+ const r = shaper.resolve(font);
4029
+ fam = r.family;
4030
+ if (!r.available) missing.add(font.family);
4031
+ resolved.set(key, fam);
4032
+ }
4033
+ return fam;
4034
+ };
4035
+ const resolvedFont = (style) => ({ ...fontOf(style), family: familyOf(fontOf(style)) });
4036
+ const box2 = frame.kind === "box" ? frame : null;
4037
+ const lines = [];
4038
+ let y = 0;
4039
+ let first = true;
4040
+ const paragraphs = paragraphRanges(text3);
4041
+ paragraphs.forEach((para, pi) => {
4042
+ const pstyle = styleAt(layer.paragraphs, para.start, {});
4043
+ const autoLeading = pstyle.autoLeading ?? DEFAULT_AUTO_LEADING;
4044
+ if (pi > 0) y += pstyle.spaceBefore ?? 0;
4045
+ const chars = [];
4046
+ let i = para.start;
4047
+ while (i < para.content) {
4048
+ const style = styleAt(runs, i, DEFAULT_TEXT_STYLE);
4049
+ let j = i + 1;
4050
+ while (j < para.content && styleAt(runs, j, DEFAULT_TEXT_STYLE) === style) j++;
4051
+ const seg = text3.slice(i, j);
4052
+ const carets = shaper.measure(seg, resolvedFont(style));
4053
+ const track = (style.tracking ?? 0) / 1e3 * style.fontSize;
4054
+ for (let k = 0; k < seg.length; k++) chars.push({ index: i + k, ch: seg[k], advance: (carets[k + 1] ?? 0) - (carets[k] ?? 0) + track, style });
4055
+ i = j;
4056
+ }
4057
+ const startIndent = pstyle.startIndent ?? 0;
4058
+ const endIndent = pstyle.endIndent ?? 0;
4059
+ const firstIndent = pstyle.firstLineIndent ?? 0;
4060
+ const breaks = [];
4061
+ if (!box2 || !chars.length) breaks.push([0, chars.length]);
4062
+ else {
4063
+ let s = 0;
4064
+ while (s < chars.length) {
4065
+ const avail = box2.width - startIndent - endIndent - (breaks.length === 0 ? firstIndent : 0);
4066
+ let w = 0;
4067
+ let e = s;
4068
+ let lastBreak = -1;
4069
+ while (e < chars.length) {
4070
+ const c = chars[e];
4071
+ if (!isSpace(c.ch) && w + c.advance > avail && e > s) break;
4072
+ w += c.advance;
4073
+ if (isBreakAfter(c.ch) || isWide(c.ch.charCodeAt(0)) || e + 1 < chars.length && isWide(chars[e + 1].ch.charCodeAt(0))) lastBreak = e + 1;
4074
+ e++;
4075
+ }
4076
+ if (e < chars.length && lastBreak > s) e = lastBreak;
4077
+ while (e < chars.length && isSpace(chars[e].ch)) e++;
4078
+ breaks.push([s, e]);
4079
+ s = e;
4080
+ }
4081
+ }
4082
+ const align = pstyle.align ?? "left";
4083
+ breaks.forEach(([s, e], li) => {
4084
+ const lineChars = chars.slice(s, e);
4085
+ let visibleEnd = lineChars.length;
4086
+ while (visibleEnd > 0 && isSpace(lineChars[visibleEnd - 1].ch)) visibleEnd--;
4087
+ const styles = lineChars.length ? [...new Set(lineChars.map((c) => c.style))] : [styleAt(runs, Math.min(para.start, Math.max(0, text3.length - 1)), DEFAULT_TEXT_STYLE)];
4088
+ let ascent = 0;
4089
+ let descent = 0;
4090
+ let leading = 0;
4091
+ for (const st of styles) {
4092
+ const m = shaper.metrics(resolvedFont(st));
4093
+ ascent = Math.max(ascent, m.ascent);
4094
+ descent = Math.max(descent, m.descent);
4095
+ leading = Math.max(leading, st.leading ?? autoLeading * st.fontSize);
4096
+ }
4097
+ if (first) {
4098
+ y = frame.kind === "box" || frame.kind === "point" && frame.origin === "top" ? ascent : 0;
4099
+ first = false;
4100
+ } else y += leading;
4101
+ const indent = startIndent + (li === 0 ? firstIndent : 0);
4102
+ let width = 0;
4103
+ for (let k2 = 0; k2 < visibleEnd; k2++) width += lineChars[k2].advance;
4104
+ const lastLine = li === breaks.length - 1;
4105
+ let extraPerSpace = 0;
4106
+ if (box2 && align === "justify" && !lastLine) {
4107
+ const spaces = lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
4108
+ const avail = box2.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4109
+ if (spaces > 0 && avail > width) extraPerSpace = (avail - width) / spaces;
4110
+ }
4111
+ let x0;
4112
+ const fullWidth = width + extraPerSpace * lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
4113
+ if (box2) {
4114
+ const avail = box2.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4115
+ x0 = indent + (align === "center" ? (avail - fullWidth) / 2 : align === "right" ? avail - fullWidth : 0);
4116
+ } else x0 = indent + (align === "center" ? -fullWidth / 2 : align === "right" ? -fullWidth : 0);
4117
+ const laid = [];
4118
+ let x = x0;
4119
+ for (const c of lineChars) {
4120
+ const adv = c.advance + (isSpace(c.ch) ? extraPerSpace : 0);
4121
+ laid.push({ index: c.index, x, advance: adv });
4122
+ x += adv;
4123
+ }
4124
+ const fragments = [];
4125
+ let k = 0;
4126
+ while (k < lineChars.length) {
4127
+ const st = lineChars[k].style;
4128
+ let m = k + 1;
4129
+ while (m < lineChars.length && lineChars[m].style === st) m++;
4130
+ const font = resolvedFont(st);
4131
+ const fx = laid[k].x;
4132
+ const carets = [0];
4133
+ for (let q2 = k; q2 < m; q2++) carets.push(laid[q2].x + laid[q2].advance - fx);
4134
+ fragments.push({
4135
+ start: lineChars[k].index,
4136
+ end: lineChars[m - 1].index + 1,
4137
+ style: st,
4138
+ font,
4139
+ family: font.family,
4140
+ x: fx,
4141
+ y: y - (st.baselineShift ?? 0),
4142
+ carets,
4143
+ spaced: Boolean(st.tracking) || extraPerSpace > 0
4144
+ });
4145
+ k = m;
4146
+ }
4147
+ const lineStart = lineChars.length ? lineChars[0].index : para.start;
4148
+ lines.push({ start: lineStart, end: lineChars.length ? lineChars[lineChars.length - 1].index + 1 : para.start, baseline: y, ascent, descent, x: x0, width, chars: laid, fragments });
4149
+ });
4150
+ y += pstyle.spaceAfter ?? 0;
4151
+ });
4152
+ let bounds;
4153
+ if (box2) bounds = { x: 0, y: 0, width: box2.width, height: box2.height };
4154
+ else {
4155
+ let x0 = Infinity;
4156
+ let x1 = -Infinity;
4157
+ for (const l of lines) {
4158
+ x0 = Math.min(x0, l.x);
4159
+ x1 = Math.max(x1, l.x + l.width);
4160
+ }
4161
+ if (!Number.isFinite(x0)) x0 = x1 = 0;
4162
+ const top = lines.length ? lines[0].baseline - lines[0].ascent : 0;
4163
+ const last = lines[lines.length - 1];
4164
+ const bottom = last ? last.baseline + last.descent : 0;
4165
+ bounds = { x: x0, y: top, width: Math.max(0, x1 - x0), height: Math.max(0, bottom - top) };
4166
+ }
4167
+ return { lines, bounds, missingFonts: [...missing] };
4168
+ }
4169
+ function textIndexAt(layout, p) {
4170
+ if (!layout.lines.length) return 0;
4171
+ let line = layout.lines[layout.lines.length - 1];
4172
+ for (const l of layout.lines) {
4173
+ if (p.y <= l.baseline + l.descent) {
4174
+ line = l;
4175
+ break;
4176
+ }
4177
+ }
4178
+ if (!line.chars.length) return line.start;
4179
+ for (const c of line.chars) if (p.x < c.x + c.advance / 2) return c.index;
4180
+ const lastChar = line.chars[line.chars.length - 1];
4181
+ return lastChar.index + 1;
4182
+ }
4183
+ function caretAt(layout, index) {
4184
+ const lines = layout.lines;
4185
+ if (!lines.length) return { x: 0, top: 0, bottom: 0, line: 0 };
4186
+ for (let li = 0; li < lines.length; li++) {
4187
+ const l2 = lines[li];
4188
+ const next = lines[li + 1];
4189
+ if (next && index >= next.start && (next.chars.length || index > l2.end || next.start > l2.end)) continue;
4190
+ const c = l2.chars.find((ch) => ch.index === index);
4191
+ const prev = l2.chars.find((ch) => ch.index === index - 1);
4192
+ const x = c ? c.x : prev ? prev.x + prev.advance : l2.x;
4193
+ return { x, top: l2.baseline - l2.ascent, bottom: l2.baseline + l2.descent, line: li };
4194
+ }
4195
+ const l = lines[lines.length - 1];
4196
+ return { x: l.x + l.width, top: l.baseline - l.ascent, bottom: l.baseline + l.descent, line: lines.length - 1 };
4197
+ }
4198
+ var TEXT_RENDER_MARGIN = MAX_EFFECT_REACH;
4199
+ function textMatrix(layer) {
4200
+ return layer.transform ?? IDENTITY_MATRIX;
4201
+ }
4202
+ function rectCorners(r) {
4203
+ return [
4204
+ { x: r.x, y: r.y },
4205
+ { x: r.x + r.width, y: r.y },
4206
+ { x: r.x + r.width, y: r.y + r.height },
4207
+ { x: r.x, y: r.y + r.height }
4208
+ ];
4209
+ }
4210
+ function docRect(r, m) {
4211
+ const pts = rectCorners(r).map((p) => applyMatrix(m, p));
4212
+ const x0 = Math.floor(Math.min(...pts.map((p) => p.x)));
4213
+ const y0 = Math.floor(Math.min(...pts.map((p) => p.y)));
4214
+ const x1 = Math.ceil(Math.max(...pts.map((p) => p.x)));
4215
+ const y1 = Math.ceil(Math.max(...pts.map((p) => p.y)));
4216
+ return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
4217
+ }
4218
+ function intersect2(a, b) {
4219
+ const x0 = Math.max(a.x, b.x);
4220
+ const y0 = Math.max(a.y, b.y);
4221
+ const x1 = Math.min(a.x + a.width, b.x + b.width);
4222
+ const y1 = Math.min(a.y + a.height, b.y + b.height);
4223
+ return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
4224
+ }
4225
+ function inkBounds(layout) {
4226
+ let b = null;
4227
+ for (const l of layout.lines) {
4228
+ for (const f of l.fragments) {
4229
+ const pad = f.style.fontSize * 0.5;
4230
+ const w = f.carets[f.carets.length - 1];
4231
+ const r = { x: f.x - pad, y: f.y - l.ascent - pad, width: w + 2 * pad, height: l.ascent + l.descent + 2 * pad };
4232
+ if (!b) b = r;
4233
+ else {
4234
+ const x0 = Math.min(b.x, r.x);
4235
+ const y0 = Math.min(b.y, r.y);
4236
+ b = { x: x0, y: y0, width: Math.max(b.x + b.width, r.x + r.width) - x0, height: Math.max(b.y + b.height, r.y + r.height) - y0 };
4237
+ }
4238
+ }
4239
+ }
4240
+ return b;
4241
+ }
4242
+ function decorations(f, l) {
4243
+ const out = [];
4244
+ const t = Math.max(1, f.style.fontSize / 15);
4245
+ const w = f.carets[f.carets.length - 1];
4246
+ if (f.style.underline) out.push({ x: f.x, y: f.y + Math.max(t, l.descent * 0.35), width: w, height: t });
4247
+ if (f.style.strikethrough) out.push({ x: f.x, y: f.y - l.ascent * 0.3 - t / 2, width: w, height: t });
4248
+ return out;
4249
+ }
4250
+ var rectPath = (r) => [
4251
+ { type: "M", x: r.x, y: r.y },
4252
+ { type: "L", x: r.x + r.width, y: r.y },
4253
+ { type: "L", x: r.x + r.width, y: r.y + r.height },
4254
+ { type: "L", x: r.x, y: r.y + r.height },
4255
+ { type: "Z" }
4256
+ ];
4257
+ function fragmentOutline(shaper, text3, f) {
4258
+ const s = text3.slice(f.start, f.end);
4259
+ if (!f.spaced) return shaper.outline(s, f.font, f.x, f.y);
4260
+ const out = [];
4261
+ for (let k = 0; k < s.length; k++) {
4262
+ if (isSpace(s[k])) continue;
4263
+ out.push(...shaper.outline(s[k], f.font, f.x + f.carets[k], f.y));
4264
+ }
4265
+ return out;
4266
+ }
4267
+ function textOutlines(layer, shaper) {
4268
+ if (!shaper.outline) fail3(`The text shaper "${shaper.id}" cannot give glyph outlines; plug in an outline shaper (createOutlineTextShaper) to convert text to shapes.`);
4269
+ const layout = layoutText(layer, shaper);
4270
+ const m = textMatrix(layer);
4271
+ const out = [];
4272
+ for (const l of layout.lines) {
4273
+ for (const f of l.fragments) {
4274
+ let path = fragmentOutline(shaper, layer.text, f);
4275
+ for (const r of decorations(f, l)) path = [...path, ...rectPath(r)];
4276
+ out.push({ fragment: f, path: isIdentityMatrix(m) ? path : transformBezierPath(path, m) });
4277
+ }
4278
+ }
4279
+ return out;
4280
+ }
4281
+ var rgbOf = (hex) => {
4282
+ const c = parseCssColor(hex);
4283
+ const h = (c?.hex ?? "#000000").slice(1);
4284
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
4285
+ };
4286
+ function renderOutlines(layer, shaper, region) {
4287
+ const parts = textOutlines(layer, shaper).map(({ fragment, path }) => ({ style: fragment.style, flat: flattenPath(path) }));
4288
+ const tiles = /* @__PURE__ */ new Map();
4289
+ for (const { tx, ty } of tilesInRect(region)) {
4290
+ const rect = { x: tx * TILE, y: ty * TILE, width: TILE, height: TILE };
4291
+ const n = TILE * TILE;
4292
+ const acc = new Float32Array(n * 4);
4293
+ let any = false;
4294
+ for (const p of parts) {
4295
+ const cov = rasterizeCoverageSampled(p.flat, rect, "nonzero");
4296
+ if (!cov) continue;
4297
+ const [r, g, b] = rgbOf(p.style.color);
4298
+ const op = p.style.opacity ?? 1;
4299
+ for (let i = 0; i < n; i++) {
4300
+ const sa = layer.antiAlias === false ? cov[i] >= 0.5 ? op : 0 : cov[i] * op;
4301
+ if (!(sa > 0)) continue;
4302
+ const k = i * 4;
4303
+ const da = acc[k + 3];
4304
+ const ao = sa + da * (1 - sa);
4305
+ acc[k] = (r * sa + acc[k] * da * (1 - sa)) / ao;
4306
+ acc[k + 1] = (g * sa + acc[k + 1] * da * (1 - sa)) / ao;
4307
+ acc[k + 2] = (b * sa + acc[k + 2] * da * (1 - sa)) / ao;
4308
+ acc[k + 3] = ao;
4309
+ any = true;
4310
+ }
4311
+ }
4312
+ if (!any) continue;
4313
+ const px2 = new Uint8ClampedArray(n * 4);
4314
+ for (let i = 0; i < n * 4; i += 4) {
4315
+ const a = Math.round(acc[i + 3] * 255);
4316
+ if (!a) continue;
4317
+ px2[i] = Math.round(acc[i]);
4318
+ px2[i + 1] = Math.round(acc[i + 1]);
4319
+ px2[i + 2] = Math.round(acc[i + 2]);
4320
+ px2[i + 3] = a;
4321
+ }
4322
+ const tile = finishTile(px2, 4, 0);
4323
+ if (tile) tiles.set(tileKey(tx, ty), tile);
4324
+ }
4325
+ return new TiledSurface(4, tiles, 0);
4326
+ }
4327
+ function renderOnCanvas(layer, shaper, region, env) {
4328
+ if (!env.createCanvas) return null;
4329
+ const canvas = env.createCanvas(region.width, region.height);
4330
+ const ctx = canvas.getContext("2d");
4331
+ if (!ctx || typeof ctx.getImageData !== "function") return null;
4332
+ const layout = layoutText(layer, shaper);
4333
+ const m = textMatrix(layer);
4334
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
4335
+ ctx.clearRect(0, 0, region.width, region.height);
4336
+ ctx.setTransform(m[0], m[1], m[2], m[3], m[4] - region.x, m[5] - region.y);
4337
+ for (const l of layout.lines) {
4338
+ for (const f of l.fragments) {
4339
+ const [r, g, b] = rgbOf(f.style.color);
4340
+ ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
4341
+ ctx.globalAlpha = f.style.opacity ?? 1;
4342
+ const s = layer.text.slice(f.start, f.end);
4343
+ if (!f.spaced) shaper.draw(ctx, s, f.font, f.x, f.y);
4344
+ else for (let k = 0; k < s.length; k++) if (!isSpace(s[k])) shaper.draw(ctx, s[k], f.font, f.x + f.carets[k], f.y);
4345
+ for (const d of decorations(f, l)) ctx.fillRect(d.x, d.y, d.width, d.height);
4346
+ }
4347
+ }
4348
+ ctx.globalAlpha = 1;
4349
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
4350
+ const data = ctx.getImageData(0, 0, region.width, region.height).data;
4351
+ return surfaceFromPixels({ width: region.width, height: region.height, data: new Uint8ClampedArray(data) }, 4, region.x, region.y);
4352
+ }
4353
+ function renderText(layer, env) {
4354
+ const shaper = env.shaper;
4355
+ if (!shaper || !shaper.outline && !shaper.draw) return null;
4356
+ const layout = layoutText(layer, shaper);
4357
+ const ink = inkBounds(layout);
4358
+ const clip = { x: -TEXT_RENDER_MARGIN, y: -TEXT_RENDER_MARGIN, width: env.width + 2 * TEXT_RENDER_MARGIN, height: env.height + 2 * TEXT_RENDER_MARGIN };
4359
+ const region = ink ? intersect2(docRect(ink, textMatrix(layer)), clip) : null;
4360
+ if (!region) return { surface: emptySurface(4), missingFonts: layout.missingFonts };
4361
+ if (shaper.outline) return { surface: renderOutlines(layer, shaper, region), missingFonts: layout.missingFonts };
4362
+ const surface = renderOnCanvas(layer, shaper, region, env);
4363
+ return surface ? { surface, missingFonts: layout.missingFonts } : null;
4364
+ }
4365
+ function rerenderText(layer, env) {
4366
+ const r = renderText(layer, env);
4367
+ if (!r) return fail3("Type layers need a text shaper to draw them, and none is set here (no 2D canvas). Pass textShaper in the editor options (for example createOutlineTextShaper with your fonts), or edit in a browser.");
4368
+ const next = { ...layer, surface: r.surface };
4369
+ delete next.rendering;
4370
+ if (r.missingFonts.length) next.missingFonts = r.missingFonts;
4371
+ else delete next.missingFonts;
4372
+ return next;
4373
+ }
4374
+ var isNum2 = (v) => typeof v === "number" && Number.isFinite(v);
4375
+ function num(v, path, min, max) {
4376
+ if (!isNum2(v) || v < min || v > max) fail3(`${path} must be a number from ${min} to ${max}, got ${JSON.stringify(v)}.`);
4377
+ return v;
4378
+ }
4379
+ var TEXT_STYLE_FIELDS = ["fontFamily", "fontWeight", "italic", "fontSize", "color", "opacity", "tracking", "leading", "baselineShift", "underline", "strikethrough"];
4380
+ function patchTextStyle(base, patch, path = "style") {
4381
+ const out = { ...base };
4382
+ const o = out;
4383
+ for (const [k, v] of Object.entries(patch)) {
4384
+ if (v === void 0) continue;
4385
+ switch (k) {
4386
+ case "fontFamily":
4387
+ if (typeof v !== "string" || !v.trim()) fail3(`${path}.fontFamily must be a font family name.`);
4388
+ out.fontFamily = v.trim();
4389
+ if (out.postScriptName) delete out.postScriptName;
4390
+ break;
4391
+ case "fontWeight": {
4392
+ const w = v === "normal" ? 400 : v === "bold" ? 700 : v;
4393
+ out.fontWeight = Math.round(num(w, `${path}.fontWeight`, 1, 1e3));
4394
+ if (out.postScriptName) delete out.postScriptName;
4395
+ break;
4396
+ }
4397
+ case "italic":
4398
+ case "underline":
4399
+ case "strikethrough":
4400
+ if (typeof v !== "boolean" && v !== null) fail3(`${path}.${k} must be true or false.`);
4401
+ if (v) o[k] = true;
4402
+ else delete o[k];
4403
+ if (k === "italic" && out.postScriptName) delete out.postScriptName;
4404
+ break;
4405
+ case "fontSize":
4406
+ out.fontSize = num(v, `${path}.fontSize`, 0.5, 5e3);
4407
+ break;
4408
+ case "color": {
4409
+ const c = parseCssColor(v);
4410
+ if (!c) fail3(`${path}.color must be a colour such as #rrggbb.`);
4411
+ out.color = c.hex;
4412
+ if (c.alpha < 1) out.opacity = c.alpha;
4413
+ break;
4414
+ }
4415
+ case "opacity":
4416
+ case "tracking":
4417
+ case "leading":
4418
+ case "baselineShift": {
4419
+ if (v === null) {
4420
+ delete o[k];
4421
+ break;
4422
+ }
4423
+ const [min, max] = k === "opacity" ? [0, 1] : k === "tracking" ? [-1e3, 1e4] : k === "leading" ? [0, 1e4] : [-5e3, 5e3];
4424
+ const value = num(v, `${path}.${k}`, min, max);
4425
+ if (k === "opacity" && value === 1 || (k === "tracking" || k === "baselineShift") && value === 0) delete o[k];
4426
+ else o[k] = value;
4427
+ break;
4428
+ }
4429
+ default:
4430
+ fail3(`${path}.${k} is not a character style field; use ${TEXT_STYLE_FIELDS.join(", ")}.`);
4431
+ }
4432
+ }
4433
+ return out;
4434
+ }
4435
+ var PARAGRAPH_STYLE_FIELDS = ["align", "autoLeading", "firstLineIndent", "startIndent", "endIndent", "spaceBefore", "spaceAfter"];
4436
+ function patchParagraphStyle(base, patch, path = "paragraph") {
4437
+ const out = { ...base };
4438
+ const o = out;
4439
+ for (const [k, v] of Object.entries(patch)) {
4440
+ if (v === void 0) continue;
4441
+ if (k === "align") {
4442
+ if (!TEXT_ALIGNS.includes(v)) fail3(`${path}.align must be one of ${TEXT_ALIGNS.join(", ")}.`);
4443
+ if (v === "left") delete out.align;
4444
+ else out.align = v;
4445
+ } else if (PARAGRAPH_STYLE_FIELDS.includes(k)) {
4446
+ if (v === null) {
4447
+ delete o[k];
4448
+ continue;
4449
+ }
4450
+ const value = k === "autoLeading" ? num(v, `${path}.${k}`, 0.1, 10) : num(v, `${path}.${k}`, -1e4, 1e4);
4451
+ if (k === "autoLeading" && value === DEFAULT_AUTO_LEADING || k !== "autoLeading" && value === 0) delete o[k];
4452
+ else o[k] = value;
4453
+ } else fail3(`${path}.${k} is not a paragraph style field; use ${PARAGRAPH_STYLE_FIELDS.join(", ")}.`);
4454
+ }
4455
+ return out;
4456
+ }
4457
+ function textRange(text3, start, end) {
4458
+ const s = start === void 0 ? 0 : start;
4459
+ const e = end === void 0 ? text3.length : end;
4460
+ if (!Number.isInteger(s) || !Number.isInteger(e) || s < 0 || e > text3.length || s > e) {
4461
+ fail3(`start and end must be whole numbers with 0 \u2264 start \u2264 end \u2264 ${text3.length} (the text's length in UTF-16 code units), got ${String(start)} and ${String(end)}.`);
4462
+ }
4463
+ return [s, e];
4464
+ }
4465
+ function requireTypeLayer(layers, id) {
4466
+ const layer = requireLayer(layers, id).layer;
4467
+ if (layer.type !== "type") fail3(`Layer "${layer.name || id}" is a ${layer.type} layer, not a type layer; list_layers shows each layer's type.`);
4468
+ return layer;
4469
+ }
4470
+ function normalizeText(text3, path = "text") {
4471
+ if (typeof text3 !== "string") return fail3(`${path} must be a string.`);
4472
+ return text3.replace(/\r\n?/g, "\n");
4473
+ }
4474
+ function typeLayerName(text3) {
4475
+ const line = text3.split("\n").find((l) => l.trim()) ?? "";
4476
+ return line.trim().slice(0, 32) || "Text";
4477
+ }
4478
+ function createTypeLayer(init, env, id = newLayerId(), lenient = false) {
4479
+ const text3 = normalizeText(init.text);
4480
+ const layer = {
4481
+ type: "type",
4482
+ id,
4483
+ name: init.name ?? typeLayerName(text3),
4484
+ visible: true,
4485
+ opacity: 1,
4486
+ blend: "normal",
4487
+ text: text3,
4488
+ runs: fitRuns(init.runs, text3.length, DEFAULT_TEXT_STYLE),
4489
+ paragraphs: fitParagraphRuns(init.paragraphs ?? [{ length: text3.length, style: {} }], text3),
4490
+ frame: init.frame,
4491
+ surface: emptySurface(4),
4492
+ offset: { x: 0, y: 0 }
4493
+ };
4494
+ if (!layer.runs.length) layer.runs = [{ length: 0, style: init.runs[0]?.style ?? DEFAULT_TEXT_STYLE }];
4495
+ if (init.transform && !isIdentityMatrix(init.transform)) layer.transform = init.transform;
4496
+ if (lenient && !renderText(layer, env)) return { ...layer, rendering: "stored" };
4497
+ return rerenderText(layer, env);
4498
+ }
4499
+ function updateTypeLayer(layers, id, change, env, action = "edit it") {
4500
+ const current = requireTypeLayer(layers, id);
4501
+ requireUnlocked(current, "pixels", action);
4502
+ const patch = change(current);
4503
+ const next = { ...current, ...patch };
4504
+ if (!("annotation" in patch)) delete next.annotation;
4505
+ if ("text" in patch || "runs" in patch) next.runs = fitRuns(next.runs, next.text.length, current.runs[0]?.style ?? DEFAULT_TEXT_STYLE);
4506
+ if (!next.runs.length) next.runs = [{ length: 0, style: styleAt(current.runs, 0, DEFAULT_TEXT_STYLE) }];
4507
+ if ("text" in patch || "paragraphs" in patch) next.paragraphs = fitParagraphRuns(next.paragraphs, next.text);
4508
+ const rendered = rerenderText(next, env);
4509
+ return { layers: mapLayer(layers, id, () => rendered), layer: rendered };
4510
+ }
4511
+ function replaceTextRange(layer, start, end, insert) {
4512
+ const text3 = layer.text.slice(0, start) + insert + layer.text.slice(end);
4513
+ const style = styleAt(layer.runs, start > 0 && start === layer.text.length ? start - 1 : start, DEFAULT_TEXT_STYLE);
4514
+ const runs = spliceRuns(layer.runs, start, end, insert.length, style);
4515
+ const pstyle = styleAt(layer.paragraphs, start, {});
4516
+ const paragraphs = spliceRuns(layer.paragraphs, start, end, insert.length, pstyle);
4517
+ return { text: text3, runs, paragraphs: fitParagraphRuns(paragraphs, text3) };
4518
+ }
4519
+ function transformTypeLayer(layer, m) {
4520
+ const t = multiplyMatrices(textMatrix(layer), m);
4521
+ const next = { ...layer };
4522
+ if (isIdentityMatrix(t)) delete next.transform;
4523
+ else next.transform = t;
4524
+ delete next.annotation;
4525
+ return next;
4526
+ }
4527
+ function typeFrame(layer, shaper) {
4528
+ if (layer.frame.kind === "box") return { x: 0, y: 0, width: layer.frame.width, height: layer.frame.height };
4529
+ if (!shaper) return fail3("Type layers need a text shaper to be measured; none is set here.");
4530
+ const b = layoutText(layer, shaper).bounds;
4531
+ return { x: b.x, y: b.y, width: Math.max(b.width, 1), height: Math.max(b.height, 1) };
4532
+ }
4533
+ function typeAsPixelLayer(layer) {
4534
+ const { text: _t, runs: _r, paragraphs: _p, frame: _f, transform: _m, antiAlias: _a, rendering: _g, missingFonts: _mf, annotation: _an, psd: _psd, type: _ty, ...rest } = layer;
4535
+ return { ...rest, type: "pixel", surface: layer.surface, offset: { x: 0, y: 0 } };
4536
+ }
4537
+ function toTextSpace(layer, p) {
4538
+ return applyMatrix(invertMatrix(textMatrix(layer)), p);
4539
+ }
4540
+ function typeFromAnnotation(a) {
4541
+ if (a.type !== "text") return null;
4542
+ const t = a;
4543
+ const c = parseCssColor(t.color);
4544
+ if (!c) return null;
4545
+ const weight = t.fontWeight === void 0 || t.fontWeight === "normal" ? 400 : t.fontWeight === "bold" ? 700 : Number(t.fontWeight);
4546
+ if (!Number.isFinite(weight)) return null;
4547
+ const style = { fontFamily: t.fontFamily || "sans-serif", fontWeight: weight, fontSize: t.fontSize, color: c.hex };
4548
+ const op = c.alpha * (t.opacity ?? 1);
4549
+ if (op !== 1) style.opacity = op;
4550
+ const text3 = t.text.replace(/\r?\n|\r/g, " ");
4551
+ const translate = [1, 0, 0, 1, t.x, t.y];
4552
+ const transform = t.rotation ? multiplyMatrices(translate, rotateMatrix(t.rotation, { x: t.x, y: t.y })) : translate;
4553
+ return { text: text3, runs: [{ length: text3.length, style }], frame: { kind: "point", origin: "top" }, transform };
4554
+ }
4555
+ function annotationOfType(layer) {
4556
+ const a = layer.annotation;
4557
+ if (!a) return null;
4558
+ if ((a.blend ?? "normal") === layer.blend) return a;
4559
+ const out = { ...a };
4560
+ if (layer.blend === "normal") delete out.blend;
4561
+ else out.blend = layer.blend;
4562
+ return out;
4563
+ }
4564
+
3890
4565
  // src/image-editor/layers/defaults.ts
3891
4566
  var DEFAULT_ADJUSTMENTS_VALUES = {
3892
4567
  brightness: 0,
@@ -3988,6 +4663,12 @@ function annotationLayerName(annotation) {
3988
4663
  }
3989
4664
  }
3990
4665
  function annotationLayerFor(annotation, canvas) {
4666
+ const blend = annotation.blend && annotation.blend !== "normal" ? annotation.blend : "normal";
4667
+ const type = canvas ? typeFromAnnotation(annotation) : null;
4668
+ if (type) {
4669
+ const layer = createTypeLayer({ ...type, name: annotationLayerName(annotation) }, { shaper: null, ...canvas }, annotation.id, true);
4670
+ return { ...layer, blend, annotation };
4671
+ }
3991
4672
  const shape = canvas ? shapeFromAnnotation(annotation) : null;
3992
4673
  if (shape) {
3993
4674
  const layer = createShapeLayer(
@@ -4010,6 +4691,7 @@ function annotationLayerFor(annotation, canvas) {
4010
4691
  function annotationOfLayer(layer) {
4011
4692
  if (layer.type === "annotation") return layer.annotation;
4012
4693
  if (layer.type === "shape") return annotationOfShape(layer);
4694
+ if (layer.type === "type") return annotationOfType(layer);
4013
4695
  return null;
4014
4696
  }
4015
4697
  function adjustmentNodeFromLegacy(layer, width, height) {
@@ -4166,7 +4848,7 @@ function isLegacyShape(stack) {
4166
4848
  }
4167
4849
  for (; i < layers.length; i++) {
4168
4850
  const l = layers[i];
4169
- if (l.type === "shape") {
4851
+ if (l.type === "shape" || l.type === "type") {
4170
4852
  const a = l.annotation;
4171
4853
  if (!a || (a.blend ?? "normal") !== l.blend || !isPlain({ ...l, blend: "normal" }) || l.fillOpacity !== void 0) return false;
4172
4854
  if (l.id !== a.id || l.name !== annotationLayerName(a)) return false;
@@ -4338,7 +5020,7 @@ function geometryHomography(geometry) {
4338
5020
  }
4339
5021
 
4340
5022
  // src/image-editor/layers/smart.ts
4341
- var fail3 = (message) => {
5023
+ var fail4 = (message) => {
4342
5024
  throw new LayerOperationError(message);
4343
5025
  };
4344
5026
  var SMART_RENDER_MARGIN = MAX_EFFECT_REACH;
@@ -4360,13 +5042,13 @@ function isContentsDecoded(contents) {
4360
5042
  function requireDecoded(layer, action) {
4361
5043
  if (isContentsDecoded(layer.contents)) return;
4362
5044
  const what = layer.contents.file?.type ? `a ${layer.contents.file.type.toUpperCase()} file` : "a file";
4363
- fail3(
5045
+ fail4(
4364
5046
  `The contents of smart object "${layer.name || layer.id}" are ${what} that could not be decoded here, so it cannot ${action}. Run prepareSmartObjects() in a browser first, or rasterize it.`
4365
5047
  );
4366
5048
  }
4367
5049
  function requireSmartObject(layers, id) {
4368
5050
  const layer = requireLayer(layers, id).layer;
4369
- if (layer.type !== "smart") fail3(`Layer "${layer.name || id}" is a ${layer.type} layer, not a smart object; convert it with convert_to_smart_object.`);
5051
+ if (layer.type !== "smart") fail4(`Layer "${layer.name || id}" is a ${layer.type} layer, not a smart object; convert it with convert_to_smart_object.`);
4370
5052
  return layer;
4371
5053
  }
4372
5054
  var contentsPixels = /* @__PURE__ */ new WeakMap();
@@ -4392,11 +5074,11 @@ function contentsSurface(contents) {
4392
5074
  return s;
4393
5075
  }
4394
5076
  function rasterContents(pixels, name, id = newContentsId()) {
4395
- if (!(pixels.width > 0 && pixels.height > 0) || pixels.data.length < pixels.width * pixels.height * 4) fail3("Contents pixels must be RGBA with width \xD7 height \xD7 4 bytes.");
5077
+ if (!(pixels.width > 0 && pixels.height > 0) || pixels.data.length < pixels.width * pixels.height * 4) fail4("Contents pixels must be RGBA with width \xD7 height \xD7 4 bytes.");
4396
5078
  return { id, name, width: pixels.width, height: pixels.height, kind: "raster", raster: surfaceFromPixels(pixels, 4, 0, 0) };
4397
5079
  }
4398
5080
  function documentContents(document, width, height, name, id = newContentsId()) {
4399
- if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0)) fail3("Contents width and height must be positive whole numbers.");
5081
+ if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0)) fail4("Contents width and height must be positive whole numbers.");
4400
5082
  return { id, name, width, height, kind: "document", document };
4401
5083
  }
4402
5084
  var placedCache = /* @__PURE__ */ new WeakMap();
@@ -4457,16 +5139,16 @@ function placeContents(layer, canvas, options = {}) {
4457
5139
  try {
4458
5140
  if (hasWarp(g)) {
4459
5141
  const f = transformMap(g);
4460
- if (!f) fail3("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
5142
+ if (!f) fail4("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
4461
5143
  out = warpSurface(source, (p) => f({ x: p.x * k, y: p.y * k }), { interpolation, source: box2, clip, step: step > 1 ? step : void 0 });
4462
5144
  } else {
4463
5145
  const h = transformHomography(g);
4464
- if (!h) fail3("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
5146
+ if (!h) fail4("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
4465
5147
  const hk = multiplyHomographies([k, 0, 0, 0, k, 0, 0, 0, 1], h);
4466
5148
  out = transformSurface(source, hk, { interpolation, source: box2, clip, step: step > 1 ? step : void 0 });
4467
5149
  }
4468
5150
  } catch (e) {
4469
- if (e instanceof TransformTooLargeError) fail3(e.message);
5151
+ if (e instanceof TransformTooLargeError) fail4(e.message);
4470
5152
  throw e;
4471
5153
  }
4472
5154
  }
@@ -4559,19 +5241,19 @@ function rerender(layer, canvas, action = "change") {
4559
5241
  function normalizePlacement(raw, contents) {
4560
5242
  const c = raw?.corners;
4561
5243
  if (!Array.isArray(c) || c.length !== 4 || !c.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) {
4562
- fail3("corners must be four finite points [top-left, top-right, bottom-right, bottom-left] in document pixels.");
5244
+ fail4("corners must be four finite points [top-left, top-right, bottom-right, bottom-left] in document pixels.");
4563
5245
  }
4564
5246
  const out = { corners: c.map((p) => ({ x: p.x, y: p.y })) };
4565
5247
  if (raw.warp) {
4566
- if (!isValidWarpMesh(raw.warp)) fail3("warp must be a mesh with us and vs (knots from 0 to 1, increasing) and (3\xB7columns + 1) \xD7 (3\xB7rows + 1) finite points in contents pixels.");
5248
+ if (!isValidWarpMesh(raw.warp)) fail4("warp must be a mesh with us and vs (knots from 0 to 1, increasing) and (3\xB7columns + 1) \xD7 (3\xB7rows + 1) finite points in contents pixels.");
4567
5249
  out.warp = { us: [...raw.warp.us], vs: [...raw.warp.vs], points: raw.warp.points.map((p) => ({ x: p.x, y: p.y })) };
4568
5250
  }
4569
5251
  if (raw.interpolation !== void 0) {
4570
- if (!isInterpolation(raw.interpolation)) fail3(`Unknown interpolation "${String(raw.interpolation)}"; use nearest, bilinear or bicubic.`);
5252
+ if (!isInterpolation(raw.interpolation)) fail4(`Unknown interpolation "${String(raw.interpolation)}"; use nearest, bilinear or bicubic.`);
4571
5253
  if (raw.interpolation !== "bicubic") out.interpolation = raw.interpolation;
4572
5254
  }
4573
5255
  const g = { box: { x: 0, y: 0, width: contents.width, height: contents.height }, corners: out.corners, warp: out.warp ?? null };
4574
- if (!transformHomography(g)) fail3("These corners fold the contents over themselves or through the horizon; keep the quad convex.");
5256
+ if (!transformHomography(g)) fail4("These corners fold the contents over themselves or through the horizon; keep the quad convex.");
4575
5257
  if (out.warp && !hasWarp(g)) delete out.warp;
4576
5258
  return out;
4577
5259
  }
@@ -4636,7 +5318,7 @@ function translateLayer(layer, dx, dy) {
4636
5318
  if (!dx && !dy) return layer;
4637
5319
  const moveMask = (m) => {
4638
5320
  if (!m) return m;
4639
- if (m.source) fail3(`Layer "${layer.name || layer.id}" has a depth mask, which cannot move into a smart object; bake or remove it first.`);
5321
+ if (m.source) fail4(`Layer "${layer.name || layer.id}" has a depth mask, which cannot move into a smart object; bake or remove it first.`);
4640
5322
  return { ...m, surface: translateSurface(m.surface, dx, dy) };
4641
5323
  };
4642
5324
  switch (layer.type) {
@@ -4674,14 +5356,19 @@ function translateLayer(layer, dx, dy) {
4674
5356
  delete next.annotation;
4675
5357
  return next;
4676
5358
  }
5359
+ case "type": {
5360
+ const next = { ...transformTypeLayer(layer, translateMatrix(dx, dy)), surface: translateSurface(layer.surface, dx, dy), mask: moveMask(layer.mask) };
5361
+ if (!next.mask) delete next.mask;
5362
+ return next;
5363
+ }
4677
5364
  case "annotation":
4678
- return fail3(`Annotation layer "${layer.name || layer.id}" cannot go into a smart object; rasterize it first.`);
5365
+ return fail4(`Annotation layer "${layer.name || layer.id}" cannot go into a smart object; rasterize it first.`);
4679
5366
  }
4680
5367
  }
4681
5368
  function layerExtent(layer, surfaceOf, canvas) {
4682
5369
  const full = { x: 0, y: 0, width: canvas.width, height: canvas.height };
4683
5370
  let b = null;
4684
- if (layer.type === "pixel" || layer.type === "smart" || layer.type === "shape") {
5371
+ if (layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type") {
4685
5372
  const s = layer.type === "pixel" ? surfaceOf(layer) : layer.surface;
4686
5373
  if (s.defaultValue !== 0) b = full;
4687
5374
  else {
@@ -4709,10 +5396,10 @@ function asSmartFilters(filters) {
4709
5396
  return { enabled: true, filters: filters.map((f) => ({ ...f, blend: "normal", opacity: 1 })) };
4710
5397
  }
4711
5398
  function convertToSmartObject(layers, ids, canvas, options) {
4712
- if (!ids.length) fail3("Give at least one layer id to convert.");
5399
+ if (!ids.length) fail4("Give at least one layer id to convert.");
4713
5400
  const locs = [...new Set(ids)].map((id) => requireLayer(layers, id));
4714
5401
  const parent = locs[0].parent?.id ?? null;
4715
- if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail3("Layers to convert must share one parent; move them next to each other first.");
5402
+ if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail4("Layers to convert must share one parent; move them next to each other first.");
4716
5403
  for (const l of locs) requireUnlocked(l.layer, "all", "convert it");
4717
5404
  const ordered = locs.sort((a, b) => a.index - b.index);
4718
5405
  const top = ordered[ordered.length - 1];
@@ -4720,9 +5407,9 @@ function convertToSmartObject(layers, ids, canvas, options) {
4720
5407
  const single = ordered.length === 1 ? ordered[0].layer : null;
4721
5408
  if (single && single.type === "pixel") {
4722
5409
  const surface = options.surfaceOf(single);
4723
- if (surface.defaultValue !== 0) fail3(`Layer "${single.name || single.id}" has no transparent area to crop to; it cannot be converted.`);
5410
+ if (surface.defaultValue !== 0) fail4(`Layer "${single.name || single.id}" has no transparent area to crop to; it cannot be converted.`);
4724
5411
  const b = contentBounds(surface);
4725
- if (!b) fail3(`Layer "${single.name || single.id}" has no pixels to convert; paint something first.`);
5412
+ if (!b) fail4(`Layer "${single.name || single.id}" has no pixels to convert; paint something first.`);
4726
5413
  const contents = {
4727
5414
  id: newContentsId(),
4728
5415
  name: `${single.name || "Layer"}.png`,
@@ -4754,7 +5441,7 @@ function convertToSmartObject(layers, ids, canvas, options) {
4754
5441
  } else {
4755
5442
  let b = null;
4756
5443
  for (const l of ordered) b = union(b, layerExtent(l.layer, options.surfaceOf, canvas));
4757
- if (!b || !(b.width > 0 && b.height > 0)) fail3("The layers have no pixels to convert; paint something first.");
5444
+ if (!b || !(b.width > 0 && b.height > 0)) fail4("The layers have no pixels to convert; paint something first.");
4758
5445
  const nested = ordered.map((l) => {
4759
5446
  let layer = l.layer;
4760
5447
  if (layer.type === "pixel" && layer.source === "document") {
@@ -4762,7 +5449,7 @@ function convertToSmartObject(layers, ids, canvas, options) {
4762
5449
  delete resolved.source;
4763
5450
  layer = resolved;
4764
5451
  }
4765
- if (flattenLayers([layer]).some((x) => x.type === "pixel" && x.source === "document")) fail3(`Layer "${layer.name || layer.id}" holds the document image inside a group; convert the Background on its own.`);
5452
+ if (flattenLayers([layer]).some((x) => x.type === "pixel" && x.source === "document")) fail4(`Layer "${layer.name || layer.id}" holds the document image inside a group; convert the Background on its own.`);
4766
5453
  return translateLayer(layer, -b.x, -b.y);
4767
5454
  });
4768
5455
  if (nested[0].clipped) {
@@ -4827,7 +5514,7 @@ function instancesOf(layers, contentsId) {
4827
5514
  }
4828
5515
  function replaceContents(layers, contentsId, contents, canvas) {
4829
5516
  const ids = instancesOf(layers, contentsId);
4830
- if (!ids.length) fail3(`No smart object uses contents "${contentsId}".`);
5517
+ if (!ids.length) fail4(`No smart object uses contents "${contentsId}".`);
4831
5518
  for (const id of ids) requireUnlocked(findLayer(layers, id).layer, "all", "replace its contents");
4832
5519
  const next = { ...contents, id: contentsId };
4833
5520
  let out = layers;
@@ -4860,12 +5547,12 @@ function refitPlacement(layer, contents) {
4860
5547
  return out;
4861
5548
  }
4862
5549
  function normalizeSmartFilter(raw, id = newLayerId("flt")) {
4863
- if (raw.kind === "psd") fail3("Photoshop filters can only come from a PSD; add develop or blur.");
5550
+ if (raw.kind === "psd") fail4("Photoshop filters can only come from a PSD; add develop or blur.");
4864
5551
  const base = normalizeFilter(raw, id);
4865
5552
  const blend = raw.blend ?? "normal";
4866
- if (!isBlendMode(blend) || blend === "pigment") fail3(`Unknown blend mode "${String(blend)}" for a smart filter.`);
5553
+ if (!isBlendMode(blend) || blend === "pigment") fail4(`Unknown blend mode "${String(blend)}" for a smart filter.`);
4867
5554
  const opacity = raw.opacity ?? 1;
4868
- if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail3("opacity must be a number from 0 to 1.");
5555
+ if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail4("opacity must be a number from 0 to 1.");
4869
5556
  return { ...base, blend, opacity };
4870
5557
  }
4871
5558
  function withStack(layer, stack, canvas) {
@@ -4880,7 +5567,7 @@ function stackOf(layer) {
4880
5567
  function requireFilter(layer, filterId) {
4881
5568
  const list2 = layer.smartFilters?.filters ?? [];
4882
5569
  const i = list2.findIndex((f) => f.id === filterId);
4883
- if (i === -1) fail3(`Smart object "${layer.name || layer.id}" has no smart filter "${filterId}". Filters: ${list2.map((f) => `${f.id} (${f.kind})`).join(", ") || "none"}; call get_smart_object.`);
5570
+ if (i === -1) fail4(`Smart object "${layer.name || layer.id}" has no smart filter "${filterId}". Filters: ${list2.map((f) => `${f.id} (${f.kind})`).join(", ") || "none"}; call get_smart_object.`);
4884
5571
  return i;
4885
5572
  }
4886
5573
  function editableSmart(layers, id) {
@@ -4901,23 +5588,23 @@ function updateSmartFilter(layers, id, filterId, changes, canvas) {
4901
5588
  const layer = editableSmart(layers, id);
4902
5589
  const i = requireFilter(layer, filterId);
4903
5590
  const current = layer.smartFilters.filters[i];
4904
- if ("kind" in changes && changes.kind !== current.kind) fail3("A smart filter kind cannot change; remove it and add another.");
4905
- let merged;
5591
+ if ("kind" in changes && changes.kind !== current.kind) fail4("A smart filter kind cannot change; remove it and add another.");
5592
+ let merged2;
4906
5593
  if (current.kind === "psd") {
4907
5594
  const extra = Object.keys(changes).filter((k) => !["enabled", "blend", "opacity", "kind"].includes(k));
4908
- if (extra.length) fail3(`"${current.name}" is a Photoshop filter we keep but do not model; only enabled, blend and opacity change. Unknown: ${extra.join(", ")}.`);
5595
+ if (extra.length) fail4(`"${current.name}" is a Photoshop filter we keep but do not model; only enabled, blend and opacity change. Unknown: ${extra.join(", ")}.`);
4909
5596
  const blend = changes.blend ?? current.blend;
4910
- if (!isBlendMode(blend) || blend === "pigment") fail3(`Unknown blend mode "${String(blend)}" for a smart filter.`);
5597
+ if (!isBlendMode(blend) || blend === "pigment") fail4(`Unknown blend mode "${String(blend)}" for a smart filter.`);
4911
5598
  const opacity = changes.opacity ?? current.opacity;
4912
- if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail3("opacity must be a number from 0 to 1.");
4913
- merged = { ...current, enabled: changes.enabled === void 0 ? current.enabled : Boolean(changes.enabled), blend, opacity };
5599
+ if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail4("opacity must be a number from 0 to 1.");
5600
+ merged2 = { ...current, enabled: changes.enabled === void 0 ? current.enabled : Boolean(changes.enabled), blend, opacity };
4914
5601
  } else {
4915
- merged = normalizeSmartFilter({ ...current, ...changes, kind: current.kind }, current.id);
5602
+ merged2 = normalizeSmartFilter({ ...current, ...changes, kind: current.kind }, current.id);
4916
5603
  }
4917
5604
  const filters = [...layer.smartFilters.filters];
4918
- filters[i] = merged;
5605
+ filters[i] = merged2;
4919
5606
  const next = withStack(layer, { ...layer.smartFilters, filters }, canvas);
4920
- return { layers: mapLayer(layers, id, () => next), layer: next, filter: merged };
5607
+ return { layers: mapLayer(layers, id, () => next), layer: next, filter: merged2 };
4921
5608
  }
4922
5609
  function removeSmartFilter(layers, id, filterId, canvas) {
4923
5610
  const layer = editableSmart(layers, id);
@@ -4931,7 +5618,7 @@ function moveSmartFilter(layers, id, filterId, index, canvas) {
4931
5618
  const layer = editableSmart(layers, id);
4932
5619
  const i = requireFilter(layer, filterId);
4933
5620
  const list2 = layer.smartFilters.filters;
4934
- if (!Number.isInteger(index) || index < 0 || index >= list2.length) fail3(`index must be a whole number from 0 to ${list2.length - 1}.`);
5621
+ if (!Number.isInteger(index) || index < 0 || index >= list2.length) fail4(`index must be a whole number from 0 to ${list2.length - 1}.`);
4935
5622
  const filters = [...list2];
4936
5623
  const [f] = filters.splice(i, 1);
4937
5624
  filters.splice(index, 0, f);
@@ -4945,7 +5632,7 @@ function setSmartFiltersEnabled(layers, id, enabled, canvas) {
4945
5632
  }
4946
5633
  function setSmartFilterMask(layers, id, mask, canvas) {
4947
5634
  const layer = editableSmart(layers, id);
4948
- if (mask?.source) fail3("A smart filter mask is painted, not generated; use a raster mask.");
5635
+ if (mask?.source) fail4("A smart filter mask is painted, not generated; use a raster mask.");
4949
5636
  const stack = { ...stackOf(layer) };
4950
5637
  if (mask) stack.mask = mask;
4951
5638
  else delete stack.mask;
@@ -4954,7 +5641,7 @@ function setSmartFilterMask(layers, id, mask, canvas) {
4954
5641
  }
4955
5642
  function withFilterMaskSurface(layer, surface, canvas) {
4956
5643
  const stack = stackOf(layer);
4957
- if (!stack.mask) fail3(`Smart object "${layer.name || layer.id}" has no filter mask; add one with set_smart_filter_mask.`);
5644
+ if (!stack.mask) fail4(`Smart object "${layer.name || layer.id}" has no filter mask; add one with set_smart_filter_mask.`);
4958
5645
  return withStack(layer, { ...stack, mask: { ...stack.mask, surface } }, canvas);
4959
5646
  }
4960
5647
  function placementBounds(layer) {
@@ -5075,6 +5762,86 @@ function serializeLayer(layer, ctx) {
5075
5762
  return serializeSmartObject(layer, base, ctx);
5076
5763
  case "shape":
5077
5764
  return serializeShape(layer, base);
5765
+ case "type":
5766
+ return serializeType(layer, base, ctx);
5767
+ }
5768
+ }
5769
+ function serializeType(layer, base, ctx) {
5770
+ const out = {
5771
+ ...base,
5772
+ type: "type",
5773
+ text: layer.text,
5774
+ runs: JSON.parse(JSON.stringify(layer.runs)),
5775
+ paragraphs: JSON.parse(JSON.stringify(layer.paragraphs)),
5776
+ frame: { ...layer.frame }
5777
+ };
5778
+ if (layer.transform) out.transform = [...layer.transform];
5779
+ if (layer.antiAlias === false) out.antiAlias = false;
5780
+ if (layer.fillOpacity !== void 0 && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;
5781
+ const enc = encodeSurface(layer.surface);
5782
+ if (enc) {
5783
+ out.asset = ctx.addAsset(enc.url, layer.id);
5784
+ out.x = enc.x;
5785
+ out.y = enc.y;
5786
+ }
5787
+ if (layer.missingFonts?.length) out.missingFonts = [...layer.missingFonts];
5788
+ if (layer.annotation) out.annotation = JSON.parse(JSON.stringify(layer.annotation));
5789
+ if (layer.psd?.tySh || layer.psd?.blocks?.length) {
5790
+ out.psd = {};
5791
+ if (layer.psd.tySh) out.psd.tySh = bytesToBase64(layer.psd.tySh);
5792
+ if (layer.psd.blocks?.length) out.psd.blocks = layer.psd.blocks.map((b) => ({ key: b.key, data: bytesToBase64(b.data) }));
5793
+ if (layer.psd.signature) out.psd.signature = layer.psd.signature;
5794
+ }
5795
+ return out;
5796
+ }
5797
+ function readType(r, base, ctx) {
5798
+ const label = `Layer "${base.name || base.id}"`;
5799
+ try {
5800
+ const text3 = normalizeText(r.text ?? "");
5801
+ const runs = (Array.isArray(r.runs) ? r.runs : []).map((run, i) => ({
5802
+ length: Math.max(0, Math.floor(num2(run?.length, 0, 1e9, 0))),
5803
+ style: patchTextStyle(DEFAULT_TEXT_STYLE, run?.style && typeof run.style === "object" ? run.style : {}, `runs[${i}].style`)
5804
+ }));
5805
+ const paragraphs = (Array.isArray(r.paragraphs) ? r.paragraphs : []).map((p, i) => ({
5806
+ length: Math.max(0, Math.floor(num2(p?.length, 0, 1e9, 0))),
5807
+ style: patchParagraphStyle({}, p?.style && typeof p.style === "object" ? p.style : {}, `paragraphs[${i}].style`)
5808
+ }));
5809
+ const f = r.frame;
5810
+ const frame = f?.kind === "box" ? { kind: "box", width: num2(f.width, 1, 1e6, 100), height: num2(f.height, 1, 1e6, 100) } : f?.origin === "top" ? { kind: "point", origin: "top" } : { kind: "point" };
5811
+ const m = Array.isArray(r.transform) && r.transform.length === 6 && r.transform.every((v) => typeof v === "number" && Number.isFinite(v)) ? r.transform : void 0;
5812
+ const layer = {
5813
+ ...base,
5814
+ type: "type",
5815
+ text: text3,
5816
+ runs: fitRuns(runs, text3.length, DEFAULT_TEXT_STYLE),
5817
+ paragraphs: fitParagraphRuns(paragraphs.length ? paragraphs : [{ length: text3.length, style: {} }], text3),
5818
+ frame,
5819
+ surface: emptySurface(4),
5820
+ offset: { x: 0, y: 0 }
5821
+ };
5822
+ if (!layer.runs.length) layer.runs = [{ length: 0, style: runs[0]?.style ?? DEFAULT_TEXT_STYLE }];
5823
+ if (m) layer.transform = m;
5824
+ if (r.antiAlias === false) layer.antiAlias = false;
5825
+ if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
5826
+ if (Array.isArray(r.missingFonts)) layer.missingFonts = r.missingFonts.filter((x) => typeof x === "string");
5827
+ if (r.annotation && typeof r.annotation === "object" && r.annotation.type === "text") layer.annotation = JSON.parse(JSON.stringify({ ...r.annotation, id: r.annotation.id ?? base.id }));
5828
+ if (r.psd && typeof r.psd === "object") {
5829
+ const psd = {};
5830
+ if (typeof r.psd.tySh === "string") psd.tySh = base64ToBytes(r.psd.tySh);
5831
+ if (Array.isArray(r.psd.blocks)) psd.blocks = r.psd.blocks.filter((b) => b && typeof b.key === "string" && typeof b.data === "string").map((b) => ({ key: b.key, data: base64ToBytes(b.data) }));
5832
+ if (typeof r.psd.signature === "string") psd.signature = r.psd.signature;
5833
+ if (psd.tySh || psd.blocks?.length) layer.psd = psd;
5834
+ }
5835
+ if (typeof r.asset === "string") return { ...layer, surface: readSurface(r.asset, r.x, r.y, 4, 0, ctx, label), rendering: "stored" };
5836
+ const env = { ...ctx.canvas ?? { width: 1, height: 1 }, shaper: ctx.text?.shaper ?? null, createCanvas: ctx.text?.createCanvas };
5837
+ try {
5838
+ return rerenderText(layer, env);
5839
+ } catch {
5840
+ return { ...layer, rendering: "stored" };
5841
+ }
5842
+ } catch (e) {
5843
+ ctx.problems.push(`${label}: the text could not be read (${e instanceof Error ? e.message : String(e)})`);
5844
+ return null;
5078
5845
  }
5079
5846
  }
5080
5847
  function serializeShape(layer, base) {
@@ -5086,9 +5853,10 @@ function serializeShape(layer, base) {
5086
5853
  stroke: layer.stroke ? JSON.parse(JSON.stringify(layer.stroke)) : null
5087
5854
  };
5088
5855
  if (layer.fillRule === "evenodd") out.fillRule = "evenodd";
5856
+ if (layer.inverted) out.inverted = true;
5089
5857
  if (layer.fillOpacity !== void 0 && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;
5090
5858
  if (layer.annotation) out.annotation = JSON.parse(JSON.stringify(layer.annotation));
5091
- if (layer.psd?.blocks?.length) out.psd = { blocks: layer.psd.blocks.map((b) => ({ key: b.key, data: bytesToBase64(b.data) })) };
5859
+ if (layer.psd?.blocks?.length) out.psd = { blocks: layer.psd.blocks.map((b) => ({ key: b.key, data: bytesToBase64(b.data) })), ...layer.psd.signature ? { signature: layer.psd.signature } : {} };
5092
5860
  return out;
5093
5861
  }
5094
5862
  function readShape(r, base, ctx) {
@@ -5115,13 +5883,14 @@ function readShape(r, base, ctx) {
5115
5883
  stroke: r.stroke === null || r.stroke === void 0 ? null : normalizeShapeStroke(r.stroke, "stroke")
5116
5884
  };
5117
5885
  }
5118
- const layer = createShapeLayer({ ...data, fillRule: r.fillRule === "evenodd" ? "evenodd" : "nonzero" }, env, base.id);
5886
+ const layer = createShapeLayer({ ...data, fillRule: r.fillRule === "evenodd" ? "evenodd" : "nonzero", inverted: r.inverted === true && !a }, env, base.id);
5119
5887
  const out = { ...layer, ...base, type: "shape", components: layer.components, fill: layer.fill, stroke: layer.stroke, surface: layer.surface, offset: { x: 0, y: 0 } };
5120
- if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) out.fillOpacity = num(r.fillOpacity, 0, 1, 1);
5888
+ if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) out.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
5121
5889
  if (a && shapeFromAnnotation(a)) out.annotation = a;
5122
5890
  if (r.psd && Array.isArray(r.psd.blocks)) {
5123
5891
  const blocks = r.psd.blocks.filter((b) => b && typeof b.key === "string" && typeof b.data === "string").map((b) => ({ key: b.key, data: base64ToBytes(b.data) }));
5124
5892
  if (blocks.length) out.psd = { blocks };
5893
+ if (out.psd && typeof r.psd.signature === "string") out.psd.signature = r.psd.signature;
5125
5894
  }
5126
5895
  return out;
5127
5896
  } catch (e) {
@@ -5269,7 +6038,7 @@ function decodePngDataUrl(url, channels) {
5269
6038
  }
5270
6039
  return { width: png.width, height: png.height, data: out };
5271
6040
  }
5272
- var num = (v, lo, hi, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : fallback;
6041
+ var num2 = (v, lo, hi, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : fallback;
5273
6042
  function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
5274
6043
  if (typeof ref !== "string" || !ref) return emptySurface(channels, defaultValue);
5275
6044
  const url = ctx.assets[ref];
@@ -5278,8 +6047,8 @@ function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
5278
6047
  return emptySurface(channels, defaultValue);
5279
6048
  }
5280
6049
  try {
5281
- const px = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
5282
- return surfaceFromPixels(px, channels, Math.round(num(x, -1e7, 1e7, 0)), Math.round(num(y, -1e7, 1e7, 0)), defaultValue);
6050
+ const px2 = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
6051
+ return surfaceFromPixels(px2, channels, Math.round(num2(x, -1e7, 1e7, 0)), Math.round(num2(y, -1e7, 1e7, 0)), defaultValue);
5283
6052
  } catch (err) {
5284
6053
  ctx.problems.push(`${what}: asset "${ref}" could not be decoded (${err.message})`);
5285
6054
  return emptySurface(channels, defaultValue);
@@ -5293,8 +6062,8 @@ function readMask(raw, ctx, what) {
5293
6062
  surface: r.source ? emptySurface(1, 255) : readSurface(r.asset, r.x, r.y, 1, defaultValue, ctx, `${what} mask`),
5294
6063
  defaultValue: r.source ? 255 : defaultValue
5295
6064
  };
5296
- if (typeof r.density === "number" && r.density !== 1) mask.density = num(r.density, 0, 1, 1);
5297
- if (typeof r.feather === "number" && r.feather > 0) mask.feather = num(r.feather, 0, 1e3, 0);
6065
+ if (typeof r.density === "number" && r.density !== 1) mask.density = num2(r.density, 0, 1, 1);
6066
+ if (typeof r.feather === "number" && r.feather > 0) mask.feather = num2(r.feather, 0, 1e3, 0);
5298
6067
  if (r.enabled === false) mask.enabled = false;
5299
6068
  if (r.linked === false) mask.linked = false;
5300
6069
  if (r.source && typeof r.source === "object") {
@@ -5311,18 +6080,18 @@ function readFilters(raw) {
5311
6080
  const r = f;
5312
6081
  const id = typeof r.id === "string" && r.id ? r.id : newLayerId("flt");
5313
6082
  const enabled = r.enabled !== false;
5314
- if (r.kind === "blur") out.push({ id, kind: "blur", enabled, radius: num(r.radius, 0, 50, 0) });
6083
+ if (r.kind === "blur") out.push({ id, kind: "blur", enabled, radius: num2(r.radius, 0, 50, 0) });
5315
6084
  else if (r.kind === "develop") {
5316
6085
  out.push({
5317
6086
  id,
5318
6087
  kind: "develop",
5319
6088
  enabled,
5320
- brightness: num(r.brightness, -100, 100, 0),
5321
- contrast: num(r.contrast, -100, 100, 0),
5322
- saturation: num(r.saturation, -100, 100, 0),
5323
- exposure: num(r.exposure, -100, 100, 0),
5324
- temperature: num(r.temperature, -100, 100, 0),
5325
- tint: num(r.tint, -100, 100, 0),
6089
+ brightness: num2(r.brightness, -100, 100, 0),
6090
+ contrast: num2(r.contrast, -100, 100, 0),
6091
+ saturation: num2(r.saturation, -100, 100, 0),
6092
+ exposure: num2(r.exposure, -100, 100, 0),
6093
+ temperature: num2(r.temperature, -100, 100, 0),
6094
+ tint: num2(r.tint, -100, 100, 0),
5326
6095
  process: normalizeProcessVersion(r.process)
5327
6096
  });
5328
6097
  }
@@ -5337,7 +6106,7 @@ function deserializeLayer(raw, ctx) {
5337
6106
  id,
5338
6107
  name: typeof r.name === "string" ? r.name : "",
5339
6108
  visible: r.visible !== false,
5340
- opacity: num(r.opacity, 0, 1, 1),
6109
+ opacity: num2(r.opacity, 0, 1, 1),
5341
6110
  blend: normalizeBlendMode(r.blend)
5342
6111
  };
5343
6112
  if (r.clipped === true) base.clipped = true;
@@ -5356,10 +6125,10 @@ function deserializeLayer(raw, ctx) {
5356
6125
  ...base,
5357
6126
  type: "pixel",
5358
6127
  surface: r.source === "document" ? emptySurface(4) : readSurface(r.asset, r.x, r.y, 4, 0, ctx, `Layer "${base.name || id}"`),
5359
- offset: { x: Math.round(num(r.offset?.x, -1e7, 1e7, 0)), y: Math.round(num(r.offset?.y, -1e7, 1e7, 0)) }
6128
+ offset: { x: Math.round(num2(r.offset?.x, -1e7, 1e7, 0)), y: Math.round(num2(r.offset?.y, -1e7, 1e7, 0)) }
5360
6129
  };
5361
6130
  if (r.source === "document") layer.source = "document";
5362
- if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num(r.fillOpacity, 0, 1, 1);
6131
+ if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
5363
6132
  const filters = readFilters(r.filters);
5364
6133
  if (filters) layer.filters = filters;
5365
6134
  return layer;
@@ -5386,15 +6155,17 @@ function deserializeLayer(raw, ctx) {
5386
6155
  return null;
5387
6156
  }
5388
6157
  const annotation = JSON.parse(JSON.stringify({ ...a, id: typeof a.id === "string" ? a.id : id }));
5389
- const migrated = annotationLayerFor(annotation, ctx.canvas ?? { width: 1, height: 1 });
5390
- if (migrated.type === "shape") {
6158
+ const migrated = annotationLayerFor(annotation, { ...ctx.canvas ?? { width: 1, height: 1 }, shaper: ctx.text?.shaper ?? null, createCanvas: ctx.text?.createCanvas });
6159
+ if (migrated.type === "shape" || migrated.type === "type") {
5391
6160
  const blend = base.blend === "normal" ? migrated.blend : base.blend;
5392
- return { ...migrated, ...base, blend, type: "shape", id, name: base.name };
6161
+ return { ...migrated, ...base, blend, id, name: base.name };
5393
6162
  }
5394
6163
  return { ...base, type: "annotation", annotation };
5395
6164
  }
5396
6165
  case "shape":
5397
6166
  return readShape(r, base, ctx);
6167
+ case "type":
6168
+ return readType(r, base, ctx);
5398
6169
  case "smart":
5399
6170
  return readSmartObject(r, base, ctx);
5400
6171
  default:
@@ -5414,8 +6185,8 @@ function readContents(id, ctx) {
5414
6185
  ctx.problems.push(`Smart object contents "${id}" are missing`);
5415
6186
  return null;
5416
6187
  }
5417
- const width = Math.round(num(raw.width, 0, 3e5, 0));
5418
- const height = Math.round(num(raw.height, 0, 3e5, 0));
6188
+ const width = Math.round(num2(raw.width, 0, 3e5, 0));
6189
+ const height = Math.round(num2(raw.height, 0, 3e5, 0));
5419
6190
  if (!(width > 0 && height > 0)) {
5420
6191
  ctx.problems.push(`Smart object contents "${id}" have no size`);
5421
6192
  return null;
@@ -5439,7 +6210,7 @@ function readContents(id, ctx) {
5439
6210
  const layers = deserializeLayers(raw.layers, ctx);
5440
6211
  ctx.canvas = outer;
5441
6212
  c.document = { layers };
5442
- if (raw.globalLight && typeof raw.globalLight === "object") c.document.globalLight = { angle: num(raw.globalLight.angle, -360, 360, 120), altitude: num(raw.globalLight.altitude, 0, 90, 30) };
6213
+ if (raw.globalLight && typeof raw.globalLight === "object") c.document.globalLight = { angle: num2(raw.globalLight.angle, -360, 360, 120), altitude: num2(raw.globalLight.altitude, 0, 90, 30) };
5443
6214
  const patterns = deserializePatterns(raw.patterns, ctx.problems);
5444
6215
  if (patterns.length) c.document.patterns = patterns;
5445
6216
  }
@@ -5456,7 +6227,7 @@ function readSmartFilters(raw, ctx, what) {
5456
6227
  for (const f of list2) {
5457
6228
  if (!f || typeof f !== "object") continue;
5458
6229
  const blend = isBlendMode(f.blend) && f.blend !== "pigment" ? f.blend : "normal";
5459
- const opacity = num(f.opacity, 0, 1, 1);
6230
+ const opacity = num2(f.opacity, 0, 1, 1);
5460
6231
  if (f.kind === "psd") {
5461
6232
  try {
5462
6233
  filters.push({ id: typeof f.id === "string" ? f.id : newLayerId("flt"), kind: "psd", enabled: f.enabled !== false, name: typeof f.name === "string" ? f.name : "Filter", data: base64ToBytes(String(f.data ?? "")), blend, opacity });
@@ -5494,7 +6265,7 @@ function readSmartObject(r, base, ctx) {
5494
6265
  const layer = { ...base, type: "smart", contents, placement, surface: emptySurface(4), offset: { x: 0, y: 0 } };
5495
6266
  const filters = readSmartFilters(r.smartFilters, ctx, what);
5496
6267
  if (filters) layer.smartFilters = filters;
5497
- if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num(r.fillOpacity, 0, 1, 1);
6268
+ if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
5498
6269
  if (r.psd && typeof r.psd.key === "string" && typeof r.psd.data === "string") {
5499
6270
  try {
5500
6271
  layer.psd = { key: r.psd.key, data: base64ToBytes(r.psd.data) };
@@ -5599,8 +6370,8 @@ var PsdReader = class _PsdReader {
5599
6370
  return v;
5600
6371
  }
5601
6372
  /** A length field: 8 bytes in PSB where the format says so, else 4. */
5602
- len(long4) {
5603
- return long4 ? this.u64() : this.u32();
6373
+ len(long5) {
6374
+ return long5 ? this.u64() : this.u32();
5604
6375
  }
5605
6376
  bytes(n) {
5606
6377
  this.need(n, `${n} bytes`);
@@ -5707,11 +6478,11 @@ var PsdWriter = class {
5707
6478
  this.length += n;
5708
6479
  }
5709
6480
  /** Reserves a length field; `endLen` fills it with the bytes written since. */
5710
- beginLen(long4) {
6481
+ beginLen(long5) {
5711
6482
  const at = this.length;
5712
- if (long4) this.u64(0);
6483
+ if (long5) this.u64(0);
5713
6484
  else this.u32(0);
5714
- return { at, long: long4 };
6485
+ return { at, long: long5 };
5715
6486
  }
5716
6487
  endLen(mark) {
5717
6488
  const n = this.length - mark.at - (mark.long ? 8 : 4);
@@ -5756,180 +6527,24 @@ function encodeLegacyName(s) {
5756
6527
  return Uint8Array.from(out);
5757
6528
  }
5758
6529
 
5759
- // src/image-editor/psd/compression.ts
5760
- var MAX_DECODED_BYTES = 2 ** 31;
5761
- function rowBytes(width, depth) {
5762
- return depth === 1 ? Math.ceil(width / 8) : width * Math.max(1, depth >> 3);
6530
+ // src/image-editor/psd/descriptor.ts
6531
+ var MAX_DEPTH = 64;
6532
+ var STRING_IDS = /* @__PURE__ */ new Set(["warp", "list", "time"]);
6533
+ function descriptor(classId, items = []) {
6534
+ return { name: "", classId, items };
5763
6535
  }
5764
- function decodedLength(l) {
5765
- const total = l.planes * l.height * rowBytes(l.width, l.depth);
5766
- if (total > MAX_DECODED_BYTES) throw new PsdError(`Decoded channel data of ${total} bytes is larger than the ${MAX_DECODED_BYTES}-byte limit.`);
5767
- return total;
6536
+ function descGet(d, key) {
6537
+ return d.items.find(([k]) => k === key)?.[1];
5768
6538
  }
5769
- function packbitsBound(n) {
5770
- return n + Math.ceil(n / 128);
6539
+ function descNumber(d, key) {
6540
+ const v = descGet(d, key);
6541
+ if (!v) return void 0;
6542
+ if (v.type === "long" || v.type === "doub" || v.type === "UntF" || v.type === "comp") return v.value;
6543
+ return void 0;
5771
6544
  }
5772
- function packbitsEncode(src, out, at) {
5773
- const n = src.length;
5774
- let i = 0;
5775
- while (i < n) {
5776
- let run = 1;
5777
- while (i + run < n && run < 128 && src[i + run] === src[i]) run++;
5778
- if (run >= 2) {
5779
- out[at++] = 257 - run & 255;
5780
- out[at++] = src[i];
5781
- i += run;
5782
- continue;
5783
- }
5784
- const start = i;
5785
- let len = 0;
5786
- while (i < n && len < 128) {
5787
- if (i + 2 < n && src[i] === src[i + 1] && src[i] === src[i + 2]) break;
5788
- i++;
5789
- len++;
5790
- }
5791
- out[at++] = len - 1;
5792
- out.set(src.subarray(start, start + len), at);
5793
- at += len;
5794
- }
5795
- return at;
5796
- }
5797
- function packbitsDecodeInto(src, expected, out, at) {
5798
- const target = at + expected;
5799
- let i = 0;
5800
- while (at < target) {
5801
- if (i >= src.length) throw new PsdError("PackBits row ended early.");
5802
- const h = src[i++];
5803
- if (h < 128) {
5804
- const len = h + 1;
5805
- if (i + len > src.length) throw new PsdError("PackBits literal is truncated.");
5806
- if (at + len > target) throw new PsdError("PackBits literal overflows its row.");
5807
- out.set(src.subarray(i, i + len), at);
5808
- at += len;
5809
- i += len;
5810
- } else if (h !== 128) {
5811
- const len = 257 - h;
5812
- if (i >= src.length) throw new PsdError("PackBits run is truncated.");
5813
- const b = src[i++];
5814
- if (at + len > target) throw new PsdError("PackBits run overflows its row.");
5815
- out.fill(b, at, at + len);
5816
- at += len;
5817
- }
5818
- }
5819
- return at;
5820
- }
5821
- function inflateChecked(data, expected) {
5822
- let out;
5823
- try {
5824
- out = inflate(data, { maxOutput: expected + (1 << 20), sizeHint: expected, verify: false });
5825
- } catch (e) {
5826
- throw new PsdError(`ZIP channel data: ${e instanceof ZlibError || e instanceof Error ? e.message : String(e)}`);
5827
- }
5828
- if (out.length < expected) throw new PsdError(`ZIP stream produced ${out.length} of ${expected} bytes.`);
5829
- return out.length === expected ? out : out.subarray(0, expected);
5830
- }
5831
- function unpredict(buf, l) {
5832
- const rb = rowBytes(l.width, l.depth);
5833
- if (rb === 0) return;
5834
- const w = l.width;
5835
- const scratch = l.depth === 32 ? new Uint8Array(rb) : null;
5836
- for (let o = 0; o + rb <= buf.length; o += rb) {
5837
- if (l.depth === 1 || l.depth === 8) {
5838
- for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
5839
- } else if (l.depth === 16) {
5840
- for (let i = 1; i < w; i++) {
5841
- const a = buf[o + 2 * i] << 8 | buf[o + 2 * i + 1];
5842
- const b = buf[o + 2 * i - 2] << 8 | buf[o + 2 * i - 1];
5843
- const v = a + b & 65535;
5844
- buf[o + 2 * i] = v >> 8;
5845
- buf[o + 2 * i + 1] = v & 255;
5846
- }
5847
- } else if (l.depth === 32) {
5848
- for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
5849
- scratch.set(buf.subarray(o, o + rb));
5850
- for (let i = 0; i < w; i++) for (let k = 0; k < 4; k++) buf[o + 4 * i + k] = scratch[k * w + i];
5851
- } else {
5852
- throw new PsdError(`ZIP prediction is not defined for depth ${l.depth}.`);
5853
- }
5854
- }
5855
- }
5856
- function decodeRle(r, l, total) {
5857
- const rows = l.planes * l.height;
5858
- const rb = rowBytes(l.width, l.depth);
5859
- r.checkCount(rows, l.psb ? 4 : 2, "RLE row counts");
5860
- const counts = new Array(rows);
5861
- for (let i = 0; i < rows; i++) counts[i] = l.psb ? r.u32() : r.u16();
5862
- if (total > r.remaining * 130) throw new PsdError("RLE data is too short for its declared size.");
5863
- const out = new Uint8Array(total);
5864
- let at = 0;
5865
- for (const c of counts) at = packbitsDecodeInto(r.bytes(c), rb, out, at);
5866
- return out;
5867
- }
5868
- function decodePlanes(compression, data, l) {
5869
- const total = decodedLength(l);
5870
- switch (compression) {
5871
- case 0:
5872
- if (data.length < total) throw new PsdError(`Raw channel data has ${data.length} of ${total} bytes.`);
5873
- return data.slice(0, total);
5874
- case 1:
5875
- return decodeRle(new PsdReader(data), l, total);
5876
- case 2:
5877
- return inflateChecked(data, total).slice();
5878
- case 3: {
5879
- const v = inflateChecked(data, total).slice();
5880
- unpredict(v, l);
5881
- return v;
5882
- }
5883
- default:
5884
- throw new PsdError(`Unsupported compression method ${compression}.`);
5885
- }
5886
- }
5887
- function encodeRle(decoded2, l) {
5888
- const rows = l.planes * l.height;
5889
- const rb = rowBytes(l.width, l.depth);
5890
- const cs = l.psb ? 4 : 2;
5891
- const packed = new Uint8Array(rows * packbitsBound(rb));
5892
- let at = 0;
5893
- const counts = new Uint8Array(rows * cs);
5894
- for (let row = 0; row < rows; row++) {
5895
- const start = at;
5896
- at = packbitsEncode(decoded2.subarray(row * rb, (row + 1) * rb), packed, at);
5897
- const n = at - start;
5898
- if (cs === 2) {
5899
- if (n > 65535) throw new PsdError("An RLE row is longer than 65535 bytes; write a PSB instead.");
5900
- counts[row * 2] = n >> 8;
5901
- counts[row * 2 + 1] = n & 255;
5902
- } else {
5903
- counts[row * 4] = n >>> 24;
5904
- counts[row * 4 + 1] = n >>> 16 & 255;
5905
- counts[row * 4 + 2] = n >>> 8 & 255;
5906
- counts[row * 4 + 3] = n & 255;
5907
- }
5908
- }
5909
- const out = new Uint8Array(counts.length + at);
5910
- out.set(counts);
5911
- out.set(packed.subarray(0, at), counts.length);
5912
- return out;
5913
- }
5914
-
5915
- // src/image-editor/psd/descriptor.ts
5916
- var MAX_DEPTH = 64;
5917
- var STRING_IDS = /* @__PURE__ */ new Set(["warp", "list", "time"]);
5918
- function descriptor(classId, items = []) {
5919
- return { name: "", classId, items };
5920
- }
5921
- function descGet(d, key) {
5922
- return d.items.find(([k]) => k === key)?.[1];
5923
- }
5924
- function descNumber(d, key) {
5925
- const v = descGet(d, key);
5926
- if (!v) return void 0;
5927
- if (v.type === "long" || v.type === "doub" || v.type === "UntF" || v.type === "comp") return v.value;
5928
- return void 0;
5929
- }
5930
- function descBool(d, key) {
5931
- const v = descGet(d, key);
5932
- return v?.type === "bool" ? v.value : void 0;
6545
+ function descBool(d, key) {
6546
+ const v = descGet(d, key);
6547
+ return v?.type === "bool" ? v.value : void 0;
5933
6548
  }
5934
6549
  function descObject(d, key) {
5935
6550
  const v = descGet(d, key);
@@ -6027,12 +6642,12 @@ function readValue(r, depth) {
6027
6642
  case "UntF":
6028
6643
  return { type, unit: r.ascii(4), value: r.f64() };
6029
6644
  case "UnFl": {
6030
- const unit3 = r.ascii(4);
6645
+ const unit4 = r.ascii(4);
6031
6646
  const n = r.u32();
6032
6647
  r.checkCount(n, 8, "Unit float list");
6033
6648
  const values = [];
6034
6649
  for (let i = 0; i < n; i++) values.push(r.f64());
6035
- return { type, unit: unit3, values };
6650
+ return { type, unit: unit4, values };
6036
6651
  }
6037
6652
  case "TEXT":
6038
6653
  return { type, value: r.unicode() };
@@ -6393,137 +7008,1477 @@ function parseAdjustmentBlock(key, data) {
6393
7008
  const highlights = tone();
6394
7009
  return { kind: "colorBalance", params: { shadows, midtones, highlights, preserveLuminosity: r.u8() !== 0 } };
6395
7010
  }
6396
- case "blwh":
6397
- return parseBlackWhite(data);
6398
- case "phfl":
6399
- return parsePhotoFilter(data);
6400
- default:
6401
- return null;
7011
+ case "blwh":
7012
+ return parseBlackWhite(data);
7013
+ case "phfl":
7014
+ return parsePhotoFilter(data);
7015
+ default:
7016
+ return null;
7017
+ }
7018
+ } catch {
7019
+ return null;
7020
+ }
7021
+ }
7022
+ var q = (v) => Math.round(clamp2(v, 0, 255));
7023
+ var i16 = (v) => Math.round(clamp2(v, -32768, 32767));
7024
+ function writeLevels(w, c) {
7025
+ w.u16(q(c.inBlack));
7026
+ w.u16(q(c.inWhite));
7027
+ w.u16(q(c.outBlack));
7028
+ w.u16(q(c.outWhite));
7029
+ w.u16(Math.round(clamp2(c.gamma * 100, 1, 999)));
7030
+ }
7031
+ var long = (value) => ({ type: "long", value: i16(value) });
7032
+ function writeAdjustmentBlock(kind, params) {
7033
+ const w = new PsdWriter();
7034
+ switch (kind) {
7035
+ case "invert":
7036
+ return { key: "nvrt", data: new Uint8Array(0) };
7037
+ case "threshold": {
7038
+ const p = params;
7039
+ w.u16(Math.max(1, q(p.level)));
7040
+ w.u16(0);
7041
+ return { key: "thrs", data: w.result() };
7042
+ }
7043
+ case "posterize": {
7044
+ const p = params;
7045
+ w.u16(Math.round(clamp2(p.levels, 2, 255)));
7046
+ w.u16(0);
7047
+ return { key: "post", data: w.result() };
7048
+ }
7049
+ case "levels": {
7050
+ const p = params;
7051
+ w.u16(2);
7052
+ for (const c of [p.master, p.red, p.green, p.blue]) writeLevels(w, c);
7053
+ const ident = { inBlack: 0, inWhite: 255, gamma: 1, outBlack: 0, outWhite: 255 };
7054
+ for (let i = 4; i < 29; i++) writeLevels(w, ident);
7055
+ return { key: "levl", data: w.result() };
7056
+ }
7057
+ case "curves": {
7058
+ const p = params;
7059
+ w.u8(0);
7060
+ w.u16(1);
7061
+ w.u32(15);
7062
+ for (const c of [p.master, p.red, p.green, p.blue]) {
7063
+ const pts = c.slice(0, 19);
7064
+ w.u16(pts.length);
7065
+ for (const pt of pts) {
7066
+ w.u16(q(pt.output));
7067
+ w.u16(q(pt.input));
7068
+ }
7069
+ }
7070
+ return { key: "curv", data: w.result() };
7071
+ }
7072
+ case "hueSaturation": {
7073
+ const p = params;
7074
+ w.u16(2);
7075
+ w.u8(p.colorize ? 1 : 0);
7076
+ w.u8(0);
7077
+ for (let k = 0; k < 2; k++) for (const x of [p.hue, p.saturation, p.lightness]) w.i16(i16(x));
7078
+ for (let i = 0; i < 6; i++) {
7079
+ const r = p.ranges[i] ?? neutralHueRange(i);
7080
+ for (const b of r.bounds) w.u16(Math.round((b % 360 + 360) % 360) % 360);
7081
+ for (const x of [r.hue, r.saturation, r.lightness]) w.i16(i16(x));
7082
+ }
7083
+ return { key: "hue2", data: w.result() };
7084
+ }
7085
+ case "vibrance": {
7086
+ const p = params;
7087
+ const d = descriptor("null", [
7088
+ ["vibrance", long(clamp2(p.vibrance, -100, 100))],
7089
+ ["Strt", long(clamp2(p.saturation, -100, 100))]
7090
+ ]);
7091
+ return { key: "vibA", data: versionedDescriptorBytes(d) };
7092
+ }
7093
+ case "colorBalance": {
7094
+ const p = params;
7095
+ for (const x of [...p.shadows, ...p.midtones, ...p.highlights]) w.i16(i16(clamp2(x, -100, 100)));
7096
+ w.u8(p.preserveLuminosity ? 1 : 0);
7097
+ w.u8(0);
7098
+ return { key: "blnc", data: w.result() };
7099
+ }
7100
+ case "blackWhite": {
7101
+ const p = params;
7102
+ const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;
7103
+ const dbl4 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
7104
+ const items = BW_KEYS.map(([field, key]) => [key, long(clamp2(p[field], -200, 300))]);
7105
+ items.push(
7106
+ ["useTint", { type: "bool", value: p.tint !== null }],
7107
+ ["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ", dbl4(tint[0])], ["Grn ", dbl4(tint[1])], ["Bl ", dbl4(tint[2])]]) }],
7108
+ ["bwPresetKind", { type: "long", value: 1 }],
7109
+ ["blackAndWhitePresetFileName", { type: "TEXT", value: "" }]
7110
+ );
7111
+ return { key: "blwh", data: versionedDescriptorBytes(descriptor("null", items)) };
7112
+ }
7113
+ case "photoFilter": {
7114
+ const p = params;
7115
+ const rgb = hexToRgb(p.color);
7116
+ w.u16(2);
7117
+ w.u16(0);
7118
+ for (const x of rgb) w.u16(Math.round(clamp2(x, 0, 1) * 65535));
7119
+ w.u16(0);
7120
+ w.u32(Math.round(clamp2(p.density, 0, 100)));
7121
+ w.u8(p.preserveLuminosity ? 1 : 0);
7122
+ w.zeros(3);
7123
+ return { key: "phfl", data: w.result() };
7124
+ }
7125
+ }
7126
+ throw new Error(`Unknown adjustment kind "${kind}".`);
7127
+ }
7128
+
7129
+ // src/image-editor/psd/paths.ts
7130
+ var RECORD = 26;
7131
+ var fixed = (v) => v / 16777216;
7132
+ var toFixed = (v) => Math.max(-2147483648, Math.min(2147483647, Math.round(v * 16777216)));
7133
+ function readPathRecords(data) {
7134
+ const r = new PsdReader(data);
7135
+ const subpaths = [];
7136
+ let initialFill = false;
7137
+ let remaining = 0;
7138
+ while (r.remaining >= RECORD) {
7139
+ const selector = r.u16();
7140
+ const body = r.sub(24);
7141
+ if (selector === 0 || selector === 3) {
7142
+ remaining = body.u16();
7143
+ subpaths.push({ closed: selector === 0, operation: body.i16(), knots: [] });
7144
+ } else if (selector === 1 || selector === 2 || selector === 4 || selector === 5) {
7145
+ const closed = selector <= 2;
7146
+ const last = subpaths[subpaths.length - 1];
7147
+ if (remaining === 0 && (!last || last.closed !== closed)) subpaths.push({ closed, operation: -1, knots: [] });
7148
+ remaining = Math.max(0, remaining - 1);
7149
+ const pt = () => {
7150
+ const y = fixed(body.i32());
7151
+ const x = fixed(body.i32());
7152
+ return { x, y };
7153
+ };
7154
+ const pre = pt();
7155
+ const anchor = pt();
7156
+ const post = pt();
7157
+ subpaths[subpaths.length - 1].knots.push({ linked: selector === 1 || selector === 4, pre, anchor, post });
7158
+ } else if (selector === 8) {
7159
+ initialFill = body.u16() !== 0;
7160
+ }
7161
+ }
7162
+ return { subpaths, initialFill };
7163
+ }
7164
+ function writePathRecords(path) {
7165
+ const w = new PsdWriter();
7166
+ w.u16(6);
7167
+ w.zeros(24);
7168
+ w.u16(8);
7169
+ w.u16(path.initialFill ? 1 : 0);
7170
+ w.zeros(22);
7171
+ for (const s of path.subpaths) {
7172
+ w.u16(s.closed ? 0 : 3);
7173
+ w.u16(Math.min(s.knots.length, 65535));
7174
+ w.i16(s.operation);
7175
+ w.u16(1);
7176
+ w.zeros(18);
7177
+ for (const k of s.knots.slice(0, 65535)) {
7178
+ w.u16(s.closed ? k.linked ? 1 : 2 : k.linked ? 4 : 5);
7179
+ for (const p of [k.pre, k.anchor, k.post]) {
7180
+ w.i32(toFixed(p.y));
7181
+ w.i32(toFixed(p.x));
7182
+ }
7183
+ }
7184
+ }
7185
+ return w.result();
7186
+ }
7187
+ var VMSK_INVERT = 1;
7188
+ function readVectorBlock(data) {
7189
+ if (data.length < 8) return null;
7190
+ const r = new PsdReader(data);
7191
+ const version = r.u32();
7192
+ const flags = r.u32();
7193
+ return { version, flags, path: readPathRecords(data.subarray(8)) };
7194
+ }
7195
+ function writeVectorBlock(path, flags = 0) {
7196
+ const body = writePathRecords(path);
7197
+ const w = new PsdWriter();
7198
+ w.u32(3);
7199
+ w.u32(flags);
7200
+ w.bytes(body);
7201
+ return w.result();
7202
+ }
7203
+ var OP_TO_PSD = { combine: 1, subtract: 2, intersect: 3, exclude: 0 };
7204
+ var OP_FROM_PSD = (v) => v === 0 ? "exclude" : v === 2 ? "subtract" : v === 3 ? "intersect" : "combine";
7205
+ function componentsFromVectorBlock(data, width, height) {
7206
+ const block = readVectorBlock(data);
7207
+ if (!block) return null;
7208
+ const pt = (p) => ({ x: p.x * width, y: p.y * height });
7209
+ const comps = [];
7210
+ for (const s of block.path.subpaths) {
7211
+ if (!s.knots.length) continue;
7212
+ const anchors = s.knots.map((k) => {
7213
+ const a = pt(k.anchor);
7214
+ const pre = pt(k.pre);
7215
+ const post = pt(k.post);
7216
+ const out = { x: a.x, y: a.y };
7217
+ if (pre.x !== a.x || pre.y !== a.y) out.in = pre;
7218
+ if (post.x !== a.x || post.y !== a.y) out.out = post;
7219
+ if (k.linked && (out.in || out.out)) out.type = "smooth";
7220
+ return out;
7221
+ });
7222
+ const sp = { closed: s.closed, anchors };
7223
+ if (s.operation === -1 && comps.length) comps[comps.length - 1].subpaths.push(sp);
7224
+ else comps.push({ op: s.operation, subpaths: [sp] });
7225
+ }
7226
+ let inverted = comps.length ? comps[0].op === 2 : block.path.initialFill;
7227
+ const components = comps.map((c, i) => {
7228
+ let op = i === 0 ? "combine" : OP_FROM_PSD(c.op);
7229
+ if (inverted && i > 0) op = op === "combine" ? "subtract" : op === "subtract" ? "combine" : op;
7230
+ return { op, subpaths: c.subpaths };
7231
+ });
7232
+ if (block.flags & VMSK_INVERT) inverted = !inverted;
7233
+ return { components, inverted, flags: block.flags };
7234
+ }
7235
+ function vectorBlockForComponents(components, inverted, width, height, flags = 0) {
7236
+ const w = Math.max(1, width);
7237
+ const h = Math.max(1, height);
7238
+ const fr = (p) => ({ x: p.x / w, y: p.y / h });
7239
+ const subpaths = [];
7240
+ components.forEach((c, ci) => {
7241
+ expandVectorCorners(c.subpaths).forEach((sp, si) => {
7242
+ if (!sp.anchors.length) return;
7243
+ subpaths.push({
7244
+ closed: sp.closed,
7245
+ operation: si > 0 ? -1 : ci === 0 ? 1 : OP_TO_PSD[c.op],
7246
+ knots: sp.anchors.map((a) => ({
7247
+ linked: a.type === "smooth" || a.type === "symmetric",
7248
+ pre: fr(a.in ?? a),
7249
+ anchor: fr(a),
7250
+ post: fr(a.out ?? a)
7251
+ }))
7252
+ });
7253
+ });
7254
+ });
7255
+ return writeVectorBlock({ subpaths, initialFill: subpaths.length === 0 && inverted }, flags & ~VMSK_INVERT | (inverted && subpaths.length ? VMSK_INVERT : 0));
7256
+ }
7257
+
7258
+ // src/image-editor/psd/shapes.ts
7259
+ var SHAPE_PSD_KEYS = ["vsms", "vmsk", "vscg", "vstk", "vogk", "SoCo", "GdFl", "PtFl"];
7260
+ var unit = (u, value) => ({ type: "UntF", unit: u, value });
7261
+ var dbl = (value) => ({ type: "doub", value });
7262
+ var long2 = (value) => ({ type: "long", value: Math.round(value) });
7263
+ var boolV = (value) => ({ type: "bool", value });
7264
+ var enumV = (typeId, value) => ({ type: "enum", typeId, value });
7265
+ var obj = (d) => ({ type: "Objc", value: d });
7266
+ var enumOf = (d, key) => {
7267
+ const v = descGet(d, key);
7268
+ return v?.type === "enum" ? v.value : void 0;
7269
+ };
7270
+ function read(data) {
7271
+ try {
7272
+ return parseVersionedDescriptor(data);
7273
+ } catch {
7274
+ return null;
7275
+ }
7276
+ }
7277
+ function px(v, dpi) {
7278
+ if (!v) return void 0;
7279
+ if (v.type === "UntF") {
7280
+ if (v.unit === "#Pnt") return v.value * dpi / 72;
7281
+ if (v.unit === "#Mlm") return v.value * dpi / 25.4;
7282
+ return v.value;
7283
+ }
7284
+ if (v.type === "doub" || v.type === "long") return v.value;
7285
+ return void 0;
7286
+ }
7287
+ function paintFromContent(key, d, notes) {
7288
+ if (key === "SoCo") return { type: "color", color: colorOf(descObject(d, "Clr ")) };
7289
+ if (key === "GdFl") return { type: "gradient", gradient: gradientOf(d, notes) };
7290
+ if (key === "PtFl") {
7291
+ const ref = patternRefOf(d);
7292
+ const place = placementOf(d);
7293
+ const scale = (descNumber(d, "Scl ") ?? 100) / 100;
7294
+ const p = { type: "pattern", patternId: ref.patternId, name: ref.name, scale: Math.max(0.01, Math.min(10, scale)) };
7295
+ if (place.angle) p.angle = place.angle;
7296
+ if (!place.link) p.link = false;
7297
+ if (place.phase.x || place.phase.y) p.phase = place.phase;
7298
+ return p;
7299
+ }
7300
+ return null;
7301
+ }
7302
+ function contentOfPaint(p) {
7303
+ if (p.type === "color") return { key: "SoCo", desc: descriptor("null", [["Clr ", obj(colorDesc(p.color))]]) };
7304
+ if (p.type === "gradient") return { key: "GdFl", desc: descriptor("null", gradientItems(p.gradient)) };
7305
+ return {
7306
+ key: "PtFl",
7307
+ desc: descriptor("null", [["Ptrn", patternRefDesc(p.patternId, p.name)], ["Scl ", unit("#Prc", p.scale * 100)], ...placementItems(p.angle ?? 0, p.link ?? true, p.phase ?? { x: 0, y: 0 })])
7308
+ };
7309
+ }
7310
+ function paintFromVscg(data, notes) {
7311
+ if (data.length < 8) return null;
7312
+ const key = String.fromCharCode(...data.subarray(0, 4));
7313
+ const d = read(data.subarray(4));
7314
+ return d ? paintFromContent(key, d, notes) : null;
7315
+ }
7316
+ function vscgBytes(p) {
7317
+ const { key, desc } = contentOfPaint(p);
7318
+ const body = versionedDescriptorBytes(desc);
7319
+ const out = new Uint8Array(4 + body.length);
7320
+ for (let i = 0; i < 4; i++) out[i] = key.charCodeAt(i);
7321
+ out.set(body, 4);
7322
+ return out;
7323
+ }
7324
+ function classOfContent(d) {
7325
+ if (d.classId === "solidColorLayer" || descGet(d, "Clr ")) return "SoCo";
7326
+ if (d.classId === "gradientLayer" || descGet(d, "Grad")) return "GdFl";
7327
+ return "PtFl";
7328
+ }
7329
+ function strokeFromVstk(data, dpi, notes) {
7330
+ const d = read(data);
7331
+ if (!d) return null;
7332
+ const fillEnabled = descBool(d, "fillEnabled") !== false;
7333
+ if (descBool(d, "strokeEnabled") !== true) return { stroke: null, fillEnabled };
7334
+ const content = descObject(d, "strokeStyleContent");
7335
+ const paint = (content ? paintFromContent(classOfContent(content), content, notes) : null) ?? { type: "color", color: "#000000" };
7336
+ const opacity = (descNumber(d, "strokeStyleOpacity") ?? 100) / 100;
7337
+ const stroke = { width: px(descGet(d, "strokeStyleLineWidth"), dpi) ?? 1, paint: opacity < 1 ? { ...paint, opacity } : paint };
7338
+ const cap = enumOf(d, "strokeStyleLineCapType");
7339
+ if (cap === "strokeStyleRoundCap") stroke.cap = "round";
7340
+ else if (cap === "strokeStyleSquareCap") stroke.cap = "square";
7341
+ const join = enumOf(d, "strokeStyleLineJoinType");
7342
+ if (join === "strokeStyleRoundJoin") stroke.join = "round";
7343
+ else if (join === "strokeStyleBevelJoin") stroke.join = "bevel";
7344
+ const align = enumOf(d, "strokeStyleLineAlignment");
7345
+ if (align === "strokeStyleAlignInside") stroke.align = "inside";
7346
+ else if (align === "strokeStyleAlignOutside") stroke.align = "outside";
7347
+ const miter = descNumber(d, "strokeStyleMiterLimit");
7348
+ if (miter !== void 0 && miter !== 100) stroke.miterLimit = Math.max(1, Math.min(500, miter));
7349
+ const dashes = descGet(d, "strokeStyleLineDashSet");
7350
+ if (dashes?.type === "VlLs") {
7351
+ const list2 = dashes.items.map((v) => v.type === "UntF" || v.type === "doub" || v.type === "long" ? v.value : NaN).filter((v) => Number.isFinite(v) && v >= 0);
7352
+ if (list2.length && list2.some((v) => v > 0)) stroke.dashes = list2;
7353
+ }
7354
+ const offset = descNumber(d, "strokeStyleLineDashOffset");
7355
+ if (offset) stroke.dashOffset = offset;
7356
+ if (enumOf(d, "strokeStyleBlendMode") && enumOf(d, "strokeStyleBlendMode") !== "Nrml" && enumOf(d, "strokeStyleBlendMode") !== "normal") notes.add("a shape stroke blend mode other than Normal became Normal");
7357
+ return { stroke, fillEnabled };
7358
+ }
7359
+ function vstkBytes(stroke, fillEnabled, dpi) {
7360
+ const s = stroke ?? { width: 3, paint: { type: "color", color: "#000000" } };
7361
+ const { key, desc } = contentOfPaint(s.paint);
7362
+ const content = { ...desc, classId: key === "SoCo" ? "solidColorLayer" : key === "GdFl" ? "gradientLayer" : "patternLayer" };
7363
+ const d = descriptor("strokeStyle", [
7364
+ ["strokeStyleVersion", long2(2)],
7365
+ ["strokeEnabled", boolV(Boolean(stroke))],
7366
+ ["fillEnabled", boolV(fillEnabled)],
7367
+ ["strokeStyleLineWidth", unit("#Pxl", s.width)],
7368
+ ["strokeStyleLineDashOffset", unit("#Pnt", s.dashOffset ?? 0)],
7369
+ ["strokeStyleMiterLimit", dbl(s.miterLimit ?? 100)],
7370
+ ["strokeStyleLineCapType", enumV("strokeStyleLineCapType", s.cap === "round" ? "strokeStyleRoundCap" : s.cap === "square" ? "strokeStyleSquareCap" : "strokeStyleButtCap")],
7371
+ ["strokeStyleLineJoinType", enumV("strokeStyleLineJoinType", s.join === "round" ? "strokeStyleRoundJoin" : s.join === "bevel" ? "strokeStyleBevelJoin" : "strokeStyleMiterJoin")],
7372
+ ["strokeStyleLineAlignment", enumV("strokeStyleLineAlignment", s.align === "inside" ? "strokeStyleAlignInside" : s.align === "outside" ? "strokeStyleAlignOutside" : "strokeStyleAlignCenter")],
7373
+ ["strokeStyleScaleLock", boolV(false)],
7374
+ ["strokeStyleStrokeAdjust", boolV(false)],
7375
+ ["strokeStyleLineDashSet", { type: "VlLs", items: (s.dashes ?? []).map((v) => unit("#Nne", v)) }],
7376
+ ["strokeStyleBlendMode", enumV("BlnM", "Nrml")],
7377
+ ["strokeStyleOpacity", unit("#Prc", (s.paint.opacity ?? 1) * 100)],
7378
+ ["strokeStyleContent", obj(content)],
7379
+ ["strokeStyleResolution", dbl(dpi)]
7380
+ ]);
7381
+ return versionedDescriptorBytes(d);
7382
+ }
7383
+ function rectOf(o) {
7384
+ const b = descObject(o, "keyOriginShapeBBox");
7385
+ if (!b) return null;
7386
+ const t = descNumber(b, "Top ");
7387
+ const l = descNumber(b, "Left");
7388
+ const bt = descNumber(b, "Btom");
7389
+ const r = descNumber(b, "Rght");
7390
+ if ([t, l, bt, r].some((v) => v === void 0 || !Number.isFinite(v))) return null;
7391
+ return { x: l, y: t, width: r - l, height: bt - t };
7392
+ }
7393
+ function pointOf(o, key) {
7394
+ const p = descObject(o, key);
7395
+ const x = p && descNumber(p, "Hrzn");
7396
+ const y = p && descNumber(p, "Vrtc");
7397
+ return x !== void 0 && y !== void 0 && Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
7398
+ }
7399
+ function liveShapesFromVogk(data) {
7400
+ const out = /* @__PURE__ */ new Map();
7401
+ if (data.length < 8) return out;
7402
+ const d = read(data.subarray(4));
7403
+ const list2 = d && descGet(d, "keyDescriptorList");
7404
+ if (!list2 || list2.type !== "VlLs") return out;
7405
+ list2.items.forEach((item, i) => {
7406
+ if (item.type !== "Objc") return;
7407
+ const o = item.value;
7408
+ if (descBool(o, "keyShapeInvalidated") === true) return;
7409
+ const index = descNumber(o, "keyOriginIndex") ?? i;
7410
+ let transform;
7411
+ const t = descObject(o, "Trnf");
7412
+ if (t) {
7413
+ const g = (k, dv) => descNumber(t, k) ?? dv;
7414
+ const m = [g("xx", 1), g("xy", 0), g("yx", 0), g("yy", 1), g("tx", 0), g("ty", 0)];
7415
+ if (!(m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0)) transform = m;
7416
+ }
7417
+ const tt = transform ? { transform } : {};
7418
+ const type = descNumber(o, "keyOriginType");
7419
+ let live = null;
7420
+ const r = rectOf(o);
7421
+ if ((type === 1 || type === 2) && r) {
7422
+ const rr = type === 2 ? descObject(o, "keyOriginRRectRadii") : void 0;
7423
+ const g = (k) => rr ? descNumber(rr, k) ?? 0 : 0;
7424
+ const radii = [g("topLeft"), g("topRight"), g("bottomRight"), g("bottomLeft")];
7425
+ live = { kind: "rect", ...r, ...radii.some((v) => v > 0) ? { radii } : {}, ...tt };
7426
+ } else if (type === 5 && r) live = { kind: "ellipse", ...r, ...tt };
7427
+ else if (type === 4) {
7428
+ const a = pointOf(o, "keyOriginLineStart");
7429
+ const b = pointOf(o, "keyOriginLineEnd");
7430
+ if (a && b) {
7431
+ const weight = descNumber(o, "keyOriginLineWeight") ?? 1;
7432
+ const line = { kind: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, weight, ...tt };
7433
+ const head = () => {
7434
+ const w = (descNumber(o, "keyOriginLineArrWdth") ?? 500) / 100 * weight;
7435
+ const l = (descNumber(o, "keyOriginLineArrLngth") ?? 1e3) / 100 * weight;
7436
+ const c = (descNumber(o, "keyOriginLineArrConc") ?? 0) / 100;
7437
+ return c ? { width: w, length: l, concavity: Math.max(-0.5, Math.min(0.5, c)) } : { width: w, length: l };
7438
+ };
7439
+ if (descBool(o, "keyOriginLineArrowSt") === true) line.startArrow = head();
7440
+ if (descBool(o, "keyOriginLineArrowEnd") === true) line.endArrow = head();
7441
+ live = line;
7442
+ }
7443
+ }
7444
+ if (live) out.set(index, live);
7445
+ });
7446
+ return out;
7447
+ }
7448
+ function vogkBytes(components, dpi) {
7449
+ const bbox = (r) => obj(
7450
+ descriptor("unitRect", [
7451
+ ["unitValueQuadVersion", long2(1)],
7452
+ ["Top ", unit("#Pxl", r.y)],
7453
+ ["Left", unit("#Pxl", r.x)],
7454
+ ["Btom", unit("#Pxl", r.y + r.height)],
7455
+ ["Rght", unit("#Pxl", r.x + r.width)]
7456
+ ])
7457
+ );
7458
+ const items = [];
7459
+ components.forEach((c, index) => {
7460
+ const live = c.live;
7461
+ if (!live || live.kind === "polygon" || live.kind === "star") return;
7462
+ const base = (type) => [
7463
+ ["keyOriginType", long2(type)],
7464
+ ["keyOriginResolution", dbl(dpi)]
7465
+ ];
7466
+ let o;
7467
+ if (live.kind === "rect") {
7468
+ const radii = live.radii ?? [0, 0, 0, 0];
7469
+ if (radii.every((v) => v === 0)) o = [...base(1), ["keyOriginShapeBBox", bbox(live)]];
7470
+ else
7471
+ o = [
7472
+ ...base(2),
7473
+ [
7474
+ "keyOriginRRectRadii",
7475
+ obj(
7476
+ descriptor("radii", [
7477
+ ["unitValueQuadVersion", long2(1)],
7478
+ ["topRight", unit("#Pxl", radii[1])],
7479
+ ["topLeft", unit("#Pxl", radii[0])],
7480
+ ["bottomLeft", unit("#Pxl", radii[3])],
7481
+ ["bottomRight", unit("#Pxl", radii[2])]
7482
+ ])
7483
+ )
7484
+ ],
7485
+ ["keyOriginShapeBBox", bbox(live)]
7486
+ ];
7487
+ } else if (live.kind === "ellipse") o = [...base(5), ["keyOriginShapeBBox", bbox(live)]];
7488
+ else {
7489
+ const pnt = (x, y) => obj(descriptor("Pnt ", [["Hrzn", dbl(x)], ["Vrtc", dbl(y)]]));
7490
+ const head = live.endArrow ?? live.startArrow;
7491
+ const w = live.weight || 1;
7492
+ o = [
7493
+ ...base(4),
7494
+ ["keyOriginShapeBBox", bbox({ x: Math.min(live.x1, live.x2), y: Math.min(live.y1, live.y2), width: Math.abs(live.x2 - live.x1), height: Math.abs(live.y2 - live.y1) })],
7495
+ ["keyOriginLineEnd", pnt(live.x2, live.y2)],
7496
+ ["keyOriginLineStart", pnt(live.x1, live.y1)],
7497
+ ["keyOriginLineWeight", dbl(live.weight)],
7498
+ ["keyOriginLineArrowSt", boolV(Boolean(live.startArrow))],
7499
+ ["keyOriginLineArrowEnd", boolV(Boolean(live.endArrow))],
7500
+ ["keyOriginLineArrWdth", dbl(head ? head.width / w * 100 : 0)],
7501
+ ["keyOriginLineArrLngth", dbl(head ? head.length / w * 100 : 0)],
7502
+ ["keyOriginLineArrConc", long2(head?.concavity ? head.concavity * 100 : 0)]
7503
+ ];
7504
+ }
7505
+ const m = live.transform;
7506
+ if (m) {
7507
+ o.push([
7508
+ "Trnf",
7509
+ obj(
7510
+ descriptor("Trnf", [
7511
+ ["xx", dbl(m[0])],
7512
+ ["xy", dbl(m[1])],
7513
+ ["yx", dbl(m[2])],
7514
+ ["yy", dbl(m[3])],
7515
+ ["tx", dbl(m[4])],
7516
+ ["ty", dbl(m[5])]
7517
+ ])
7518
+ )
7519
+ ]);
7520
+ }
7521
+ o.push(["keyOriginIndex", long2(index)]);
7522
+ items.push(obj(descriptor("null", o)));
7523
+ });
7524
+ if (!items.length) return null;
7525
+ const body = versionedDescriptorBytes(descriptor("null", [["keyDescriptorList", { type: "VlLs", items }]]));
7526
+ const out = new Uint8Array(4 + body.length);
7527
+ new DataView(out.buffer).setUint32(0, 1);
7528
+ out.set(body, 4);
7529
+ return out;
7530
+ }
7531
+ function shapeSignature(layer) {
7532
+ return JSON.stringify([layer.components, layer.fill, layer.stroke, layer.fillRule ?? "nonzero", Boolean(layer.inverted)]);
7533
+ }
7534
+ function shapeFromBlocks(blocks, width, height, dpi, notes) {
7535
+ const block = (k) => blocks.find((b) => b.key === k)?.data;
7536
+ const vector = block("vsms") ?? block("vmsk");
7537
+ const path = vector ? componentsFromVectorBlock(vector, width, height) : null;
7538
+ if (!path) return null;
7539
+ let fill = null;
7540
+ const vscg = block("vscg");
7541
+ if (vscg) fill = paintFromVscg(vscg, notes);
7542
+ for (const key of ["SoCo", "GdFl", "PtFl"]) {
7543
+ if (fill) break;
7544
+ const data = block(key);
7545
+ const d = data && (() => {
7546
+ try {
7547
+ return parseVersionedDescriptor(data);
7548
+ } catch {
7549
+ return null;
7550
+ }
7551
+ })();
7552
+ if (d) fill = paintFromContent(key, d, notes);
7553
+ }
7554
+ let stroke = null;
7555
+ const vstk = block("vstk");
7556
+ if (vstk) {
7557
+ const s = strokeFromVstk(vstk, dpi, notes);
7558
+ if (s) {
7559
+ stroke = s.stroke;
7560
+ if (!s.fillEnabled) fill = null;
7561
+ }
7562
+ }
7563
+ const vogk = block("vogk");
7564
+ const live = vogk ? liveShapesFromVogk(vogk) : /* @__PURE__ */ new Map();
7565
+ const components = path.components.map((c, i) => {
7566
+ const l = live.get(i);
7567
+ return l && sameExtent(liveShapeSubpaths(l), c) ? { ...c, live: l } : c;
7568
+ });
7569
+ return { components, inverted: path.inverted, fill, stroke };
7570
+ }
7571
+ function sameExtent(sub, c) {
7572
+ const ext = (list2) => {
7573
+ let x0 = Infinity;
7574
+ let y0 = Infinity;
7575
+ let x1 = -Infinity;
7576
+ let y1 = -Infinity;
7577
+ for (const sp of list2) for (const a2 of sp.anchors) {
7578
+ x0 = Math.min(x0, a2.x);
7579
+ y0 = Math.min(y0, a2.y);
7580
+ x1 = Math.max(x1, a2.x);
7581
+ y1 = Math.max(y1, a2.y);
7582
+ }
7583
+ return [x0, y0, x1, y1];
7584
+ };
7585
+ const a = ext(sub);
7586
+ const b = ext(c.subpaths);
7587
+ return a.every((v, i) => Math.abs(v - b[i]) < 1);
7588
+ }
7589
+ function shapeBlocks(layer, width, height, dpi, notes) {
7590
+ const kept = layer.psd?.blocks;
7591
+ if (kept?.length && layer.psd?.signature === shapeSignature(layer)) return kept.map((b) => ({ key: b.key, data: b.data }));
7592
+ const out = [];
7593
+ out.push({ key: "vsms", data: vectorBlockForComponents(layer.components, Boolean(layer.inverted), width, height) });
7594
+ if (layer.fillRule === "evenodd" && layer.components.some((c) => c.subpaths.length > 1)) notes.add("Even-odd fill rules were written as nonzero (Photoshop fills shapes nonzero)");
7595
+ const fill = layer.fill ?? { type: "color", color: "#000000" };
7596
+ if (layer.fill?.opacity !== void 0 && layer.fill.opacity < 1) notes.add("Shape fill opacity has no PSD equivalent and was written as 100 % (use the layer fill opacity)");
7597
+ out.push({ key: "vscg", data: vscgBytes(fill) });
7598
+ if (layer.stroke || !layer.fill) out.push({ key: "vstk", data: vstkBytes(layer.stroke, Boolean(layer.fill), dpi) });
7599
+ const vogk = vogkBytes(layer.components, dpi);
7600
+ if (vogk) out.push({ key: "vogk", data: vogk });
7601
+ if (layer.components.some((c) => c.live?.kind === "line" && (c.live.startArrow && c.live.endArrow && JSON.stringify(c.live.startArrow) !== JSON.stringify(c.live.endArrow) || false))) {
7602
+ notes.add("Lines with different start and end arrowheads were written with one arrowhead size (Photoshop has one)");
7603
+ }
7604
+ return out;
7605
+ }
7606
+
7607
+ // src/image-editor/psd/engine-data.ts
7608
+ var EngineDataError = class extends Error {
7609
+ constructor(message) {
7610
+ super(`EngineData: ${message}`);
7611
+ this.name = "EngineDataError";
7612
+ }
7613
+ };
7614
+ var MAX_DEPTH2 = 64;
7615
+ var isWs = (c) => c === 32 || c === 9 || c === 13 || c === 10 || c === 0;
7616
+ var isDelim = (c) => isWs(c) || c === 91 || c === 93 || c === 60 || c === 62 || c === 40 || c === 47;
7617
+ function parseEngineData(b) {
7618
+ let i = 0;
7619
+ const ws = () => {
7620
+ while (i < b.length && isWs(b[i])) i++;
7621
+ };
7622
+ const name = () => {
7623
+ i++;
7624
+ const start = i;
7625
+ while (i < b.length && !isDelim(b[i])) i++;
7626
+ let s = "";
7627
+ for (let k = start; k < i; k++) s += String.fromCharCode(b[k]);
7628
+ return s;
7629
+ };
7630
+ const value = (depth) => {
7631
+ if (depth > MAX_DEPTH2) throw new EngineDataError("nesting too deep");
7632
+ ws();
7633
+ if (i >= b.length) throw new EngineDataError("unexpected end");
7634
+ const c = b[i];
7635
+ if (c === 60 && b[i + 1] === 60) {
7636
+ i += 2;
7637
+ const items = [];
7638
+ for (; ; ) {
7639
+ ws();
7640
+ if (b[i] === 62 && b[i + 1] === 62) {
7641
+ i += 2;
7642
+ return { t: "dict", items };
7643
+ }
7644
+ if (b[i] !== 47) throw new EngineDataError(i >= b.length ? "unterminated dictionary" : `expected a key at byte ${i}`);
7645
+ const k = name();
7646
+ items.push([k, value(depth + 1)]);
7647
+ }
7648
+ }
7649
+ if (c === 91) {
7650
+ i++;
7651
+ const items = [];
7652
+ for (; ; ) {
7653
+ ws();
7654
+ if (i >= b.length) throw new EngineDataError("unterminated array");
7655
+ if (b[i] === 93) {
7656
+ i++;
7657
+ return { t: "array", items };
7658
+ }
7659
+ items.push(value(depth + 1));
7660
+ }
7661
+ }
7662
+ if (c === 40) {
7663
+ i++;
7664
+ const raw = [];
7665
+ for (; ; ) {
7666
+ if (i >= b.length) throw new EngineDataError("unterminated string");
7667
+ const x = b[i];
7668
+ if (x === 92) {
7669
+ if (i + 1 < b.length) raw.push(b[i + 1]);
7670
+ i += 2;
7671
+ } else if (x === 41) {
7672
+ i++;
7673
+ break;
7674
+ } else {
7675
+ raw.push(x);
7676
+ i++;
7677
+ }
7678
+ }
7679
+ return { t: "string", value: decodeString(raw) };
7680
+ }
7681
+ if (c === 47) return { t: "name", value: name() };
7682
+ const start = i;
7683
+ while (i < b.length && !isDelim(b[i])) i++;
7684
+ let tok = "";
7685
+ for (let k = start; k < i; k++) tok += String.fromCharCode(b[k]);
7686
+ if (tok === "true" || tok === "false") return { t: "bool", value: tok === "true" };
7687
+ if (!tok) throw new EngineDataError(`unexpected byte at ${i}`);
7688
+ const n = Number(tok);
7689
+ if (!Number.isFinite(n)) throw new EngineDataError(`bad number "${tok}"`);
7690
+ return /[.eE]/.test(tok) ? { t: "real", value: n } : { t: "int", value: n };
7691
+ };
7692
+ ws();
7693
+ if (b[i] === 47) {
7694
+ const items = [];
7695
+ while (i < b.length) {
7696
+ ws();
7697
+ if (i >= b.length) break;
7698
+ if (b[i] !== 47) throw new EngineDataError(`expected a key at byte ${i}`);
7699
+ const k = name();
7700
+ items.push([k, value(1)]);
7701
+ ws();
7702
+ }
7703
+ return { t: "dict", items };
7704
+ }
7705
+ return value(0);
7706
+ }
7707
+ function decodeString(raw) {
7708
+ if (raw.length >= 2 && raw[0] === 254 && raw[1] === 255) {
7709
+ let s = "";
7710
+ for (let k = 2; k + 1 < raw.length; k += 2) s += String.fromCharCode(raw[k] << 8 | raw[k + 1]);
7711
+ return s;
7712
+ }
7713
+ return String.fromCharCode(...raw);
7714
+ }
7715
+ var isContainer = (v) => v.t === "dict" || v.t === "array" && v.items.some((x) => x.t === "dict" || x.t === "array");
7716
+ function formatReal(r) {
7717
+ if (!Number.isFinite(r)) return "0.0";
7718
+ if (Number.isInteger(r) && Math.abs(r) < 1e15) return r.toFixed(1);
7719
+ let s = r.toFixed(5);
7720
+ while (s.endsWith("0")) s = s.slice(0, -1);
7721
+ if (s === "0." || s === "-0.") return "0.0";
7722
+ if (s.endsWith(".")) s += "0";
7723
+ return s.replace(/^(-?)0\./, "$1.");
7724
+ }
7725
+ function writeEngineData(v) {
7726
+ const out = [10, 10];
7727
+ const push = (s) => {
7728
+ for (let k = 0; k < s.length; k++) out.push(s.charCodeAt(k) & 255);
7729
+ };
7730
+ const indent = (n) => {
7731
+ for (let k = 0; k < n; k++) out.push(9);
7732
+ };
7733
+ const write = (x, depth) => {
7734
+ switch (x.t) {
7735
+ case "dict":
7736
+ push("<<\n");
7737
+ for (const [k, val] of x.items) {
7738
+ indent(depth + 1);
7739
+ push(`/${k}`);
7740
+ if (isContainer(val)) {
7741
+ push("\n");
7742
+ indent(depth + 1);
7743
+ } else push(" ");
7744
+ write(val, depth + 1);
7745
+ push("\n");
7746
+ }
7747
+ indent(depth);
7748
+ push(">>");
7749
+ break;
7750
+ case "array":
7751
+ if (isContainer(x)) {
7752
+ push("[\n");
7753
+ for (const item of x.items) {
7754
+ indent(depth);
7755
+ write(item, depth);
7756
+ push("\n");
7757
+ }
7758
+ indent(depth);
7759
+ push("]");
7760
+ } else {
7761
+ push("[");
7762
+ for (const item of x.items) {
7763
+ push(" ");
7764
+ write(item, depth);
7765
+ }
7766
+ push(" ]");
7767
+ }
7768
+ break;
7769
+ case "string": {
7770
+ out.push(40);
7771
+ const raw = [254, 255];
7772
+ for (let k = 0; k < x.value.length; k++) {
7773
+ const u = x.value.charCodeAt(k);
7774
+ raw.push(u >> 8, u & 255);
7775
+ }
7776
+ for (const c of raw) {
7777
+ if (c === 40 || c === 41 || c === 92) out.push(92);
7778
+ out.push(c);
7779
+ }
7780
+ out.push(41);
7781
+ break;
7782
+ }
7783
+ case "int":
7784
+ push(String(Math.round(x.value)));
7785
+ break;
7786
+ case "real":
7787
+ push(formatReal(x.value));
7788
+ break;
7789
+ case "bool":
7790
+ push(x.value ? "true" : "false");
7791
+ break;
7792
+ case "name":
7793
+ push(`/${x.value}`);
7794
+ break;
7795
+ }
7796
+ };
7797
+ write(v, 0);
7798
+ out.push(10);
7799
+ return Uint8Array.from(out);
7800
+ }
7801
+ var E = {
7802
+ dict: (items = []) => ({ t: "dict", items }),
7803
+ array: (items) => ({ t: "array", items }),
7804
+ str: (value) => ({ t: "string", value }),
7805
+ int: (value) => ({ t: "int", value }),
7806
+ real: (value) => ({ t: "real", value }),
7807
+ bool: (value) => ({ t: "bool", value })
7808
+ };
7809
+ function eGet(v, key) {
7810
+ return v?.t === "dict" ? v.items.find(([k]) => k === key)?.[1] : void 0;
7811
+ }
7812
+ function ePath(v, keys) {
7813
+ return keys.reduce((at, k) => eGet(at, k), v);
7814
+ }
7815
+ function eNum(v) {
7816
+ return v && (v.t === "int" || v.t === "real") ? v.value : void 0;
7817
+ }
7818
+ function eBool(v) {
7819
+ if (!v) return void 0;
7820
+ if (v.t === "bool") return v.value;
7821
+ if (v.t === "int") return v.value !== 0;
7822
+ return void 0;
7823
+ }
7824
+ function eStr(v) {
7825
+ return v && (v.t === "string" || v.t === "name") ? v.value : void 0;
7826
+ }
7827
+ function eArray(v) {
7828
+ return v?.t === "array" ? v.items : [];
7829
+ }
7830
+ function eSet(d, key, value) {
7831
+ if (d.t !== "dict") return;
7832
+ const at = d.items.findIndex(([k]) => k === key);
7833
+ if (at >= 0) d.items[at] = [key, value];
7834
+ else d.items.push([key, value]);
7835
+ }
7836
+ function eClone(v) {
7837
+ return JSON.parse(JSON.stringify(v));
7838
+ }
7839
+
7840
+ // src/image-editor/psd/type.ts
7841
+ function parseTySh(data) {
7842
+ try {
7843
+ const r = new PsdReader(data);
7844
+ r.u16();
7845
+ const m = [];
7846
+ for (let k = 0; k < 6; k++) m.push(r.f64());
7847
+ r.u16();
7848
+ const text3 = readVersionedDescriptor(r);
7849
+ let warp = null;
7850
+ if (r.remaining >= 6) {
7851
+ const at = r.pos;
7852
+ try {
7853
+ r.u16();
7854
+ warp = readVersionedDescriptor(r);
7855
+ } catch {
7856
+ r.pos = at;
7857
+ }
7858
+ }
7859
+ return { transform: m, text: text3, warp, tail: r.rest() };
7860
+ } catch {
7861
+ return null;
7862
+ }
7863
+ }
7864
+ function defaultWarp() {
7865
+ return descriptor("warp", [
7866
+ ["warpStyle", { type: "enum", typeId: "warpStyle", value: "warpNone" }],
7867
+ ["warpValue", { type: "doub", value: 0 }],
7868
+ ["warpPerspective", { type: "doub", value: 0 }],
7869
+ ["warpPerspectiveOther", { type: "doub", value: 0 }],
7870
+ ["warpRotate", { type: "enum", typeId: "Ornt", value: "Hrzn" }]
7871
+ ]);
7872
+ }
7873
+ function writeTySh(t) {
7874
+ const w = new PsdWriter();
7875
+ w.u16(1);
7876
+ for (const v of t.transform) w.f64(v);
7877
+ w.u16(50);
7878
+ writeVersionedDescriptor(w, t.text);
7879
+ w.u16(1);
7880
+ writeVersionedDescriptor(w, t.warp ?? defaultWarp());
7881
+ if (t.tail.length >= 16) w.bytes(t.tail);
7882
+ else w.zeros(16);
7883
+ return w.result();
7884
+ }
7885
+ var WEIGHTS = [
7886
+ [/thin|hairline/, 100],
7887
+ [/extralight|ultralight/, 200],
7888
+ [/semibold|demibold|demi/, 600],
7889
+ [/extrabold|ultrabold/, 800],
7890
+ [/black|heavy/, 900],
7891
+ [/bold/, 700],
7892
+ [/medium/, 500],
7893
+ [/light/, 300]
7894
+ ];
7895
+ function fontFromPostScript(ps) {
7896
+ let pro = ps;
7897
+ let style = "";
7898
+ const dash = ps.lastIndexOf("-");
7899
+ if (dash > 0) {
7900
+ pro = ps.slice(0, dash);
7901
+ style = ps.slice(dash + 1);
7902
+ const prev = pro.lastIndexOf("-");
7903
+ if (/^(it|italic|oblique)$/i.test(style) && prev > 0 && /^(thin|hairline|extralight|ultralight|light|regular|roman|book|medium|semibold|demibold|demi|bold|extrabold|ultrabold|heavy|black)$/i.test(pro.slice(prev + 1))) {
7904
+ style = ps.slice(prev + 1);
7905
+ pro = ps.slice(0, prev);
7906
+ }
7907
+ } else {
7908
+ const m = /^(.*?)(Bold|Italic|Light|Medium|Black|Regular|Oblique)+$/.exec(ps);
7909
+ if (m && m[1]) {
7910
+ pro = m[1];
7911
+ style = ps.slice(m[1].length);
7912
+ }
7913
+ }
7914
+ let suffix = "";
7915
+ if (pro.endsWith("Pro")) suffix = " Pro";
7916
+ else if (pro.endsWith("Std")) suffix = " Std";
7917
+ const fam = pro.replace(/(MT|PS|Std|Pro)+$/, "");
7918
+ let family = "";
7919
+ for (let i = 0; i < fam.length; i++) {
7920
+ const c = fam[i];
7921
+ const upper = c >= "A" && c <= "Z";
7922
+ const prevLower = i > 0 && fam[i - 1] >= "a" && fam[i - 1] <= "z";
7923
+ const prevUpper = i > 0 && fam[i - 1] >= "A" && fam[i - 1] <= "Z";
7924
+ const nextLower = i + 1 < fam.length && fam[i + 1] >= "a" && fam[i + 1] <= "z";
7925
+ if (i > 0 && upper && (prevLower || prevUpper && nextLower)) family += " ";
7926
+ family += c;
7927
+ }
7928
+ family += suffix;
7929
+ const s = style.toLowerCase();
7930
+ const weight = WEIGHTS.find(([re]) => re.test(s))?.[1] ?? 400;
7931
+ return { family: family || ps, weight, italic: /italic|oblique|it$/.test(s) };
7932
+ }
7933
+ var STYLE_NAMES = [
7934
+ [100, "Thin"],
7935
+ [200, "ExtraLight"],
7936
+ [300, "Light"],
7937
+ [400, "Regular"],
7938
+ [500, "Medium"],
7939
+ [600, "SemiBold"],
7940
+ [700, "Bold"],
7941
+ [800, "ExtraBold"],
7942
+ [900, "Black"]
7943
+ ];
7944
+ function postScriptFor(s) {
7945
+ if (s.postScriptName) return s.postScriptName;
7946
+ const base = s.fontFamily.replace(/["',]/g, "").replace(/\s+/g, "");
7947
+ const weight = STYLE_NAMES.reduce((best, w) => Math.abs(w[0] - s.fontWeight) < Math.abs(best[0] - s.fontWeight) ? w : best)[1];
7948
+ if (weight === "Regular" && !s.italic) return base;
7949
+ return `${base}-${weight === "Regular" ? "" : weight}${s.italic ? "Italic" : ""}`;
7950
+ }
7951
+ var hex22 = (v) => Math.round(Math.max(0, Math.min(1, v)) * 255).toString(16).padStart(2, "0");
7952
+ function merged(levels) {
7953
+ const items = [];
7954
+ for (const l of levels) {
7955
+ if (l?.t !== "dict") continue;
7956
+ for (const [k, v] of l.items) if (!items.some(([o]) => o === k)) items.push([k, v]);
7957
+ }
7958
+ return E.dict(items);
7959
+ }
7960
+ function charStyle(d, fonts, notes) {
7961
+ const n = (k) => eNum(eGet(d, k));
7962
+ const flag = (k) => eBool(eGet(d, k));
7963
+ const s = { ...DEFAULT_TEXT_STYLE };
7964
+ const fi = n("Font");
7965
+ const ps = fi !== void 0 ? fonts[fi] : void 0;
7966
+ if (ps) {
7967
+ const f = fontFromPostScript(ps);
7968
+ s.fontFamily = f.family;
7969
+ s.fontWeight = f.weight;
7970
+ if (f.italic) s.italic = true;
7971
+ s.postScriptName = ps;
7972
+ }
7973
+ const size = n("FontSize");
7974
+ if (size !== void 0 && size > 0) s.fontSize = size;
7975
+ if (flag("AutoLeading") === false) {
7976
+ const lead = n("Leading");
7977
+ if (lead !== void 0 && lead > 0) s.leading = lead;
7978
+ }
7979
+ const tracking = n("Tracking");
7980
+ if (tracking) s.tracking = tracking;
7981
+ const shift = n("BaselineShift");
7982
+ if (shift) s.baselineShift = shift;
7983
+ if (flag("Underline")) s.underline = true;
7984
+ if (flag("Strikethrough")) s.strikethrough = true;
7985
+ if (flag("FauxBold")) notes.add("faux bold is drawn as the regular weight");
7986
+ if (flag("FauxItalic")) notes.add("faux italic is drawn upright");
7987
+ if ((n("HorizontalScale") ?? 1) !== 1 || (n("VerticalScale") ?? 1) !== 1) notes.add("horizontal and vertical character scaling is drawn at 100 %");
7988
+ if ((n("FontCaps") ?? 0) !== 0) notes.add("small caps and all caps are drawn as typed");
7989
+ const color = eGet(d, "FillColor");
7990
+ const values = eArray(eGet(color, "Values")).map((v) => eNum(v) ?? 0);
7991
+ if (values.length === 4) {
7992
+ s.color = `#${hex22(values[1])}${hex22(values[2])}${hex22(values[3])}`;
7993
+ if (values[0] < 1) s.opacity = Math.max(0, values[0]);
7994
+ } else if (values.length === 2) {
7995
+ const g = hex22(values[1]);
7996
+ s.color = `#${g}${g}${g}`;
7997
+ } else if (values.length === 5) {
7998
+ const [c, m, y, k] = values.slice(1);
7999
+ s.color = `#${hex22((1 - c) * (1 - k))}${hex22((1 - m) * (1 - k))}${hex22((1 - y) * (1 - k))}`;
8000
+ notes.add("CMYK text colours were converted to RGB");
8001
+ }
8002
+ return s;
8003
+ }
8004
+ function paraStyle(d) {
8005
+ const n = (k) => eNum(eGet(d, k));
8006
+ const out = {};
8007
+ const j = n("Justification");
8008
+ const align = j === 1 ? "right" : j === 2 ? "center" : j !== void 0 && j >= 3 ? "justify" : "left";
8009
+ if (align !== "left") out.align = align;
8010
+ const set = (k, v) => {
8011
+ if (v) out[k] = v;
8012
+ };
8013
+ set("firstLineIndent", n("FirstLineIndent"));
8014
+ set("startIndent", n("StartIndent"));
8015
+ set("endIndent", n("EndIndent"));
8016
+ set("spaceBefore", n("SpaceBefore"));
8017
+ set("spaceAfter", n("SpaceAfter"));
8018
+ const auto = n("AutoLeading");
8019
+ if (auto !== void 0 && auto > 0 && Math.abs(auto - 1.2) > 1e-6) out.autoLeading = auto;
8020
+ return out;
8021
+ }
8022
+ function typeFromTySh(data, notes) {
8023
+ const t = parseTySh(data);
8024
+ if (!t) return null;
8025
+ const txtV = descGet(t.text, "Txt ");
8026
+ const txt = txtV?.type === "TEXT" ? txtV.value.replace(/\0+$/, "") : void 0;
8027
+ const ed = descGet(t.text, "EngineData");
8028
+ let engine = null;
8029
+ if (ed?.type === "tdta") {
8030
+ try {
8031
+ engine = parseEngineData(ed.data);
8032
+ } catch {
8033
+ notes.add("the text engine data could not be read; the text came in in one style");
8034
+ }
8035
+ }
8036
+ const anti = descGet(t.text, "AntA");
8037
+ const antiAlias = !(anti?.type === "enum" && (anti.value === "Anno" || anti.value === "antiAliasNone"));
8038
+ const ornt = descGet(t.text, "Ornt");
8039
+ if (ornt?.type === "enum" && ornt.value === "Vrtc") notes.add("vertical type is laid out horizontally");
8040
+ if (t.warp) {
8041
+ const style = descGet(t.warp, "warpStyle");
8042
+ if (style?.type === "enum" && style.value !== "warpNone") notes.add("warped text is drawn without its warp when edited");
8043
+ }
8044
+ const edText = eStr(ePath(engine ?? void 0, ["EngineDict", "Editor", "Text"])) ?? "";
8045
+ const text3 = (txt ?? edText.replace(/\r$/, "")).replace(/\r\n?/g, "\n");
8046
+ let runs = [{ length: text3.length, style: { ...DEFAULT_TEXT_STYLE } }];
8047
+ let paragraphs = [{ length: text3.length, style: {} }];
8048
+ let frame = { kind: "point" };
8049
+ let transform = t.transform;
8050
+ const fonts = [];
8051
+ if (engine) {
8052
+ const res = eGet(engine, "ResourceDict") ?? eGet(engine, "DocumentResources");
8053
+ for (const f of eArray(eGet(res, "FontSet"))) fonts.push(eStr(eGet(f, "Name")) ?? "");
8054
+ const styleSet = eArray(eGet(res, "StyleSheetSet"));
8055
+ const paraSet = eArray(eGet(res, "ParagraphSheetSet"));
8056
+ const normalStyle = styleSet[eNum(eGet(res, "TheNormalStyleSheet")) ?? 0] ?? styleSet[0];
8057
+ const normalPara = paraSet[eNum(eGet(res, "TheNormalParagraphSheet")) ?? 0] ?? paraSet[0];
8058
+ const named = (set, sheet) => {
8059
+ const parent = eNum(eGet(sheet, "Parent"));
8060
+ if (parent !== void 0 && set[parent]) return set[parent];
8061
+ const name = eStr(eGet(sheet, "Name"));
8062
+ return name ? set.find((s) => eStr(eGet(s, "Name")) === name) : void 0;
8063
+ };
8064
+ const prun = ePath(engine, ["EngineDict", "ParagraphRun"]);
8065
+ const psheets = eArray(eGet(prun, "RunArray")).map((x) => eGet(x, "ParagraphSheet"));
8066
+ const plens = eArray(eGet(prun, "RunLengthArray")).map((v) => eNum(v) ?? 0);
8067
+ const paraBases = psheets.map((sheet) => {
8068
+ const own = named(paraSet, sheet);
8069
+ const base = merged([eGet(own, "Properties"), eGet(normalPara, "Properties")]);
8070
+ const di = eNum(eGet(sheet, "DefaultStyleSheet")) ?? eNum(eGet(own, "DefaultStyleSheet"));
8071
+ return { base, charDefault: di !== void 0 ? styleSet[di] : void 0 };
8072
+ });
8073
+ if (psheets.length) {
8074
+ paragraphs = psheets.map((sheet, i) => ({ length: plens[i] ?? 0, style: paraStyle(merged([eGet(sheet, "Properties"), paraBases[i].base])) }));
8075
+ }
8076
+ const srun = ePath(engine, ["EngineDict", "StyleRun"]);
8077
+ const ssheets = eArray(eGet(srun, "RunArray")).map((x) => eGet(x, "StyleSheet"));
8078
+ const slens = eArray(eGet(srun, "RunLengthArray")).map((v) => eNum(v) ?? 0);
8079
+ const paraAt = (at) => {
8080
+ let end = 0;
8081
+ for (let i = 0; i < plens.length; i++) {
8082
+ end += plens[i];
8083
+ if (at < end) return i;
8084
+ }
8085
+ return plens.length - 1;
8086
+ };
8087
+ if (ssheets.length) {
8088
+ let at = 0;
8089
+ runs = ssheets.map((sheet, i) => {
8090
+ const own = named(styleSet, sheet);
8091
+ const paraDefault = paraBases[paraAt(at)]?.charDefault;
8092
+ at += slens[i] ?? 0;
8093
+ const d = merged([eGet(sheet, "StyleSheetData"), eGet(own, "StyleSheetData"), eGet(paraDefault, "StyleSheetData"), eGet(normalStyle, "StyleSheetData")]);
8094
+ return { length: slens[i] ?? 0, style: charStyle(d, fonts, notes) };
8095
+ });
8096
+ }
8097
+ const shape = eArray(ePath(engine, ["EngineDict", "Rendered", "Shapes", "Children"]))[0];
8098
+ if (eNum(eGet(shape, "ShapeType")) === 1) {
8099
+ const b = eArray(ePath(shape, ["Cookie", "Photoshop", "BoxBounds"])).map((v) => eNum(v) ?? 0);
8100
+ if (b.length === 4 && b[2] > b[0] && b[3] > b[1]) {
8101
+ frame = { kind: "box", width: b[2] - b[0], height: b[3] - b[1] };
8102
+ transform = multiplyMatrices([1, 0, 0, 1, b[0], b[1]], t.transform);
8103
+ }
8104
+ }
8105
+ }
8106
+ return {
8107
+ text: text3,
8108
+ runs: fitRuns(runs, text3.length, DEFAULT_TEXT_STYLE),
8109
+ paragraphs: fitParagraphRuns(paragraphs, text3),
8110
+ frame,
8111
+ transform,
8112
+ antiAlias,
8113
+ fonts: [...new Set(fonts.filter(Boolean))]
8114
+ };
8115
+ }
8116
+ function typeSignature(layer) {
8117
+ return JSON.stringify([layer.text, layer.runs, layer.paragraphs, layer.frame, layer.transform ?? null, layer.antiAlias ?? true]);
8118
+ }
8119
+ function styleSheetData(s, font) {
8120
+ const [r, g, b] = [1, 3, 5].map((i) => parseInt(s.color.slice(i, i + 2), 16) / 255);
8121
+ return E.dict([
8122
+ ["Font", E.int(font)],
8123
+ ["FontSize", E.real(s.fontSize)],
8124
+ ["FauxBold", E.bool(false)],
8125
+ ["FauxItalic", E.bool(false)],
8126
+ ["AutoLeading", E.bool(s.leading === void 0)],
8127
+ ["Leading", E.real(s.leading ?? 0)],
8128
+ ["HorizontalScale", E.real(1)],
8129
+ ["VerticalScale", E.real(1)],
8130
+ ["Tracking", E.int(Math.round(s.tracking ?? 0))],
8131
+ ["AutoKerning", E.bool(true)],
8132
+ ["Kerning", E.int(0)],
8133
+ ["BaselineShift", E.real(s.baselineShift ?? 0)],
8134
+ ["FontCaps", E.int(0)],
8135
+ ["Underline", E.bool(Boolean(s.underline))],
8136
+ ["Strikethrough", E.bool(Boolean(s.strikethrough))],
8137
+ ["Ligatures", E.bool(true)],
8138
+ ["DLigatures", E.bool(false)],
8139
+ ["FillFlag", E.bool(true)],
8140
+ [
8141
+ "FillColor",
8142
+ E.dict([
8143
+ ["Type", E.int(1)],
8144
+ ["Values", E.array([E.real(s.opacity ?? 1), E.real(r), E.real(g), E.real(b)])]
8145
+ ])
8146
+ ]
8147
+ ]);
8148
+ }
8149
+ function paragraphProperties(p) {
8150
+ const j = p.align === "right" ? 1 : p.align === "center" ? 2 : p.align === "justify" ? 3 : 0;
8151
+ return E.dict([
8152
+ ["Justification", E.int(j)],
8153
+ ["FirstLineIndent", E.real(p.firstLineIndent ?? 0)],
8154
+ ["StartIndent", E.real(p.startIndent ?? 0)],
8155
+ ["EndIndent", E.real(p.endIndent ?? 0)],
8156
+ ["SpaceBefore", E.real(p.spaceBefore ?? 0)],
8157
+ ["SpaceAfter", E.real(p.spaceAfter ?? 0)],
8158
+ ["AutoHyphenate", E.bool(false)],
8159
+ ["AutoLeading", E.real(p.autoLeading ?? 1.2)],
8160
+ ["LeadingType", E.int(0)]
8161
+ ]);
8162
+ }
8163
+ var fontEntry = (name) => E.dict([
8164
+ ["Name", E.str(name)],
8165
+ ["Script", E.int(0)],
8166
+ ["FontType", E.int(1)],
8167
+ ["Synthetic", E.int(0)]
8168
+ ]);
8169
+ function buildEngineData(layer, template) {
8170
+ const e = template?.t === "dict" ? eClone(template) : E.dict([["EngineDict", E.dict()], ["ResourceDict", E.dict()]]);
8171
+ if (eGet(e, "EngineDict")?.t !== "dict") eSet(e, "EngineDict", E.dict());
8172
+ const dict = eGet(e, "EngineDict");
8173
+ const text3 = layer.text.replace(/\n/g, "\r");
8174
+ const edText = `${text3}\r`;
8175
+ const res0 = eGet(e, "ResourceDict") ?? eGet(e, "DocumentResources");
8176
+ const fonts = eArray(eGet(res0, "FontSet")).map((f) => eStr(eGet(f, "Name")) ?? "");
8177
+ const fontIndex = (ps) => {
8178
+ const at = fonts.indexOf(ps);
8179
+ if (at >= 0) return at;
8180
+ fonts.push(ps);
8181
+ return fonts.length - 1;
8182
+ };
8183
+ const runs = fitRuns(layer.runs, layer.text.length, DEFAULT_TEXT_STYLE);
8184
+ const sruns = [];
8185
+ const slens = [];
8186
+ runs.forEach((r, i) => {
8187
+ sruns.push(E.dict([["StyleSheet", E.dict([["StyleSheetData", styleSheetData(r.style, fontIndex(postScriptFor(r.style)))]])]]));
8188
+ slens.push(E.int(r.length + (i === runs.length - 1 ? 1 : 0)));
8189
+ });
8190
+ if (!runs.length) {
8191
+ sruns.push(E.dict([["StyleSheet", E.dict([["StyleSheetData", styleSheetData(DEFAULT_TEXT_STYLE, fontIndex(postScriptFor(DEFAULT_TEXT_STYLE)))]])]]));
8192
+ slens.push(E.int(1));
8193
+ }
8194
+ const paras = fitParagraphRuns(layer.paragraphs, layer.text);
8195
+ const pruns = [];
8196
+ const plens = [];
8197
+ paras.forEach((p, i) => {
8198
+ pruns.push(E.dict([["ParagraphSheet", E.dict([["DefaultStyleSheet", E.int(0)], ["Properties", paragraphProperties(p.style)]])]]));
8199
+ plens.push(E.int(p.length + (i === paras.length - 1 ? 1 : 0)));
8200
+ });
8201
+ const editor = eGet(dict, "Editor") ?? E.dict();
8202
+ eSet(editor, "Text", E.str(edText));
8203
+ eSet(dict, "Editor", editor);
8204
+ const pr = eGet(dict, "ParagraphRun") ?? E.dict();
8205
+ eSet(pr, "RunArray", E.array(pruns));
8206
+ eSet(pr, "RunLengthArray", E.array(plens));
8207
+ if (!eGet(pr, "IsJoinable")) eSet(pr, "IsJoinable", E.int(1));
8208
+ eSet(dict, "ParagraphRun", pr);
8209
+ const sr = eGet(dict, "StyleRun") ?? E.dict();
8210
+ eSet(sr, "RunArray", E.array(sruns));
8211
+ eSet(sr, "RunLengthArray", E.array(slens));
8212
+ if (!eGet(sr, "IsJoinable")) eSet(sr, "IsJoinable", E.int(2));
8213
+ eSet(dict, "StyleRun", sr);
8214
+ eSet(dict, "AntiAlias", E.int(layer.antiAlias === false ? 0 : 3));
8215
+ if (!eGet(dict, "UseFractionalGlyphWidths")) eSet(dict, "UseFractionalGlyphWidths", E.bool(true));
8216
+ const box2 = layer.frame.kind === "box" ? layer.frame : null;
8217
+ const shapeType = box2 ? 1 : 0;
8218
+ const photoshop = E.dict([["ShapeType", E.int(shapeType)]]);
8219
+ if (box2) eSet(photoshop, "BoxBounds", E.array([E.real(0), E.real(0), E.real(box2.width), E.real(box2.height)]));
8220
+ else eSet(photoshop, "PointBase", E.array([E.real(0), E.real(0)]));
8221
+ eSet(
8222
+ photoshop,
8223
+ "Base",
8224
+ E.dict([
8225
+ ["ShapeType", E.int(shapeType)],
8226
+ ["TransformPoint0", E.array([E.real(1), E.real(0)])],
8227
+ ["TransformPoint1", E.array([E.real(0), E.real(1)])],
8228
+ ["TransformPoint2", E.array([E.real(0), E.real(0)])]
8229
+ ])
8230
+ );
8231
+ const child = E.dict([
8232
+ ["ShapeType", E.int(shapeType)],
8233
+ ["Procession", E.int(0)],
8234
+ ["Lines", E.dict([["WritingDirection", E.int(0)], ["Children", E.array([])]])],
8235
+ ["Cookie", E.dict([["Photoshop", photoshop]])]
8236
+ ]);
8237
+ eSet(dict, "Rendered", E.dict([["Version", E.int(1)], ["Shapes", E.dict([["WritingDirection", E.int(0)], ["Children", E.array([child])]])]]));
8238
+ const fontSet = E.array(fonts.map(fontEntry));
8239
+ for (const key of ["ResourceDict", "DocumentResources"]) {
8240
+ let rd = eGet(e, key);
8241
+ if (!rd) {
8242
+ if (key === "DocumentResources") {
8243
+ eSet(e, key, eClone(eGet(e, "ResourceDict") ?? E.dict()));
8244
+ continue;
8245
+ }
8246
+ rd = E.dict();
8247
+ eSet(e, key, rd);
8248
+ }
8249
+ eSet(rd, "FontSet", fontSet);
8250
+ if (!eGet(rd, "StyleSheetSet")) eSet(rd, "StyleSheetSet", E.array([E.dict([["Name", E.str("Normal RGB")], ["StyleSheetData", styleSheetData(runs[0]?.style ?? DEFAULT_TEXT_STYLE, 0)]])]));
8251
+ if (!eGet(rd, "ParagraphSheetSet")) eSet(rd, "ParagraphSheetSet", E.array([E.dict([["Name", E.str("Normal RGB")], ["DefaultStyleSheet", E.int(0)], ["Properties", paragraphProperties({})]])]));
8252
+ if (!eGet(rd, "TheNormalStyleSheet")) eSet(rd, "TheNormalStyleSheet", E.int(0));
8253
+ if (!eGet(rd, "TheNormalParagraphSheet")) eSet(rd, "TheNormalParagraphSheet", E.int(0));
8254
+ }
8255
+ return e;
8256
+ }
8257
+ function inkBounds2(layer) {
8258
+ if (layer.frame.kind === "box") return [0, 0, layer.frame.width, layer.frame.height];
8259
+ const b = contentBounds(layer.surface);
8260
+ if (!b) return [0, 0, 0, 0];
8261
+ const inv = invertMatrix(textMatrix(layer));
8262
+ const pts = [
8263
+ { x: b.x, y: b.y },
8264
+ { x: b.x + b.width, y: b.y },
8265
+ { x: b.x + b.width, y: b.y + b.height },
8266
+ { x: b.x, y: b.y + b.height }
8267
+ ].map((p) => applyMatrix(inv, p));
8268
+ return [Math.min(...pts.map((p) => p.x)), Math.min(...pts.map((p) => p.y)), Math.max(...pts.map((p) => p.x)), Math.max(...pts.map((p) => p.y))];
8269
+ }
8270
+ function tyShForLayer(layer) {
8271
+ const raw = layer.psd?.tySh;
8272
+ if (raw && layer.psd?.signature === typeSignature(layer)) return raw;
8273
+ const old = raw ? parseTySh(raw) : null;
8274
+ const oldEd = old ? descGet(old.text, "EngineData") : void 0;
8275
+ let template = null;
8276
+ if (oldEd?.type === "tdta") {
8277
+ try {
8278
+ template = parseEngineData(oldEd.data);
8279
+ } catch {
8280
+ template = null;
8281
+ }
8282
+ }
8283
+ const engine = buildEngineData(layer, template);
8284
+ const text3 = old ? { ...old.text, items: [...old.text.items] } : descriptor("TxLr");
8285
+ const set = (key, v) => {
8286
+ const at = text3.items.findIndex(([k]) => k === key);
8287
+ if (at >= 0) text3.items[at] = [key, v];
8288
+ else text3.items.push([key, v]);
8289
+ };
8290
+ set("Txt ", { type: "TEXT", value: layer.text.replace(/\n/g, "\r") });
8291
+ if (!descGet(text3, "textGridding")) set("textGridding", { type: "enum", typeId: "textGridding", value: "None" });
8292
+ set("Ornt", { type: "enum", typeId: "Ornt", value: "Hrzn" });
8293
+ set("AntA", { type: "enum", typeId: "Annt", value: layer.antiAlias === false ? "Anno" : "AnSm" });
8294
+ const [l, t, r, b] = inkBounds2(layer);
8295
+ const rect = (cls) => ({
8296
+ type: "Objc",
8297
+ value: descriptor(cls, [
8298
+ ["Left", { type: "UntF", unit: "#Pnt", value: l }],
8299
+ ["Top ", { type: "UntF", unit: "#Pnt", value: t }],
8300
+ ["Rght", { type: "UntF", unit: "#Pnt", value: r }],
8301
+ ["Btom", { type: "UntF", unit: "#Pnt", value: b }]
8302
+ ])
8303
+ });
8304
+ set("bounds", rect("bounds"));
8305
+ set("boundingBox", rect("boundingBox"));
8306
+ if (!descGet(text3, "TextIndex")) set("TextIndex", { type: "long", value: 0 });
8307
+ set("EngineData", { type: "tdta", data: writeEngineData(engine) });
8308
+ const warpStyle = old?.warp ? descGet(old.warp, "warpStyle") : void 0;
8309
+ const warp = old?.warp && warpStyle?.type === "enum" && warpStyle.value === "warpNone" ? old.warp : null;
8310
+ return writeTySh({ transform: textMatrix(layer), text: text3, warp, tail: new Uint8Array(16) });
8311
+ }
8312
+
8313
+ // src/image-editor/psd/compression.ts
8314
+ var MAX_DECODED_BYTES = 2 ** 31;
8315
+ function rowBytes(width, depth) {
8316
+ return depth === 1 ? Math.ceil(width / 8) : width * Math.max(1, depth >> 3);
8317
+ }
8318
+ function decodedLength(l) {
8319
+ const total = l.planes * l.height * rowBytes(l.width, l.depth);
8320
+ if (total > MAX_DECODED_BYTES) throw new PsdError(`Decoded channel data of ${total} bytes is larger than the ${MAX_DECODED_BYTES}-byte limit.`);
8321
+ return total;
8322
+ }
8323
+ function packbitsBound(n) {
8324
+ return n + Math.ceil(n / 128);
8325
+ }
8326
+ function packbitsEncode(src, out, at) {
8327
+ const n = src.length;
8328
+ let i = 0;
8329
+ while (i < n) {
8330
+ let run = 1;
8331
+ while (i + run < n && run < 128 && src[i + run] === src[i]) run++;
8332
+ if (run >= 2) {
8333
+ out[at++] = 257 - run & 255;
8334
+ out[at++] = src[i];
8335
+ i += run;
8336
+ continue;
8337
+ }
8338
+ const start = i;
8339
+ let len = 0;
8340
+ while (i < n && len < 128) {
8341
+ if (i + 2 < n && src[i] === src[i + 1] && src[i] === src[i + 2]) break;
8342
+ i++;
8343
+ len++;
8344
+ }
8345
+ out[at++] = len - 1;
8346
+ out.set(src.subarray(start, start + len), at);
8347
+ at += len;
8348
+ }
8349
+ return at;
8350
+ }
8351
+ function packbitsDecodeInto(src, expected, out, at) {
8352
+ const target = at + expected;
8353
+ let i = 0;
8354
+ while (at < target) {
8355
+ if (i >= src.length) throw new PsdError("PackBits row ended early.");
8356
+ const h = src[i++];
8357
+ if (h < 128) {
8358
+ const len = h + 1;
8359
+ if (i + len > src.length) throw new PsdError("PackBits literal is truncated.");
8360
+ if (at + len > target) throw new PsdError("PackBits literal overflows its row.");
8361
+ out.set(src.subarray(i, i + len), at);
8362
+ at += len;
8363
+ i += len;
8364
+ } else if (h !== 128) {
8365
+ const len = 257 - h;
8366
+ if (i >= src.length) throw new PsdError("PackBits run is truncated.");
8367
+ const b = src[i++];
8368
+ if (at + len > target) throw new PsdError("PackBits run overflows its row.");
8369
+ out.fill(b, at, at + len);
8370
+ at += len;
8371
+ }
8372
+ }
8373
+ return at;
8374
+ }
8375
+ function inflateChecked(data, expected) {
8376
+ let out;
8377
+ try {
8378
+ out = inflate(data, { maxOutput: expected + (1 << 20), sizeHint: expected, verify: false });
8379
+ } catch (e) {
8380
+ throw new PsdError(`ZIP channel data: ${e instanceof ZlibError || e instanceof Error ? e.message : String(e)}`);
8381
+ }
8382
+ if (out.length < expected) throw new PsdError(`ZIP stream produced ${out.length} of ${expected} bytes.`);
8383
+ return out.length === expected ? out : out.subarray(0, expected);
8384
+ }
8385
+ function unpredict(buf, l) {
8386
+ const rb = rowBytes(l.width, l.depth);
8387
+ if (rb === 0) return;
8388
+ const w = l.width;
8389
+ const scratch = l.depth === 32 ? new Uint8Array(rb) : null;
8390
+ for (let o = 0; o + rb <= buf.length; o += rb) {
8391
+ if (l.depth === 1 || l.depth === 8) {
8392
+ for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
8393
+ } else if (l.depth === 16) {
8394
+ for (let i = 1; i < w; i++) {
8395
+ const a = buf[o + 2 * i] << 8 | buf[o + 2 * i + 1];
8396
+ const b = buf[o + 2 * i - 2] << 8 | buf[o + 2 * i - 1];
8397
+ const v = a + b & 65535;
8398
+ buf[o + 2 * i] = v >> 8;
8399
+ buf[o + 2 * i + 1] = v & 255;
8400
+ }
8401
+ } else if (l.depth === 32) {
8402
+ for (let i = 1; i < rb; i++) buf[o + i] = buf[o + i] + buf[o + i - 1] & 255;
8403
+ scratch.set(buf.subarray(o, o + rb));
8404
+ for (let i = 0; i < w; i++) for (let k = 0; k < 4; k++) buf[o + 4 * i + k] = scratch[k * w + i];
8405
+ } else {
8406
+ throw new PsdError(`ZIP prediction is not defined for depth ${l.depth}.`);
6402
8407
  }
6403
- } catch {
6404
- return null;
6405
8408
  }
6406
8409
  }
6407
- var q = (v) => Math.round(clamp2(v, 0, 255));
6408
- var i16 = (v) => Math.round(clamp2(v, -32768, 32767));
6409
- function writeLevels(w, c) {
6410
- w.u16(q(c.inBlack));
6411
- w.u16(q(c.inWhite));
6412
- w.u16(q(c.outBlack));
6413
- w.u16(q(c.outWhite));
6414
- w.u16(Math.round(clamp2(c.gamma * 100, 1, 999)));
8410
+ function decodeRle(r, l, total) {
8411
+ const rows = l.planes * l.height;
8412
+ const rb = rowBytes(l.width, l.depth);
8413
+ r.checkCount(rows, l.psb ? 4 : 2, "RLE row counts");
8414
+ const counts = new Array(rows);
8415
+ for (let i = 0; i < rows; i++) counts[i] = l.psb ? r.u32() : r.u16();
8416
+ if (total > r.remaining * 130) throw new PsdError("RLE data is too short for its declared size.");
8417
+ const out = new Uint8Array(total);
8418
+ let at = 0;
8419
+ for (const c of counts) at = packbitsDecodeInto(r.bytes(c), rb, out, at);
8420
+ return out;
6415
8421
  }
6416
- var long = (value) => ({ type: "long", value: i16(value) });
6417
- function writeAdjustmentBlock(kind, params) {
6418
- const w = new PsdWriter();
6419
- switch (kind) {
6420
- case "invert":
6421
- return { key: "nvrt", data: new Uint8Array(0) };
6422
- case "threshold": {
6423
- const p = params;
6424
- w.u16(Math.max(1, q(p.level)));
6425
- w.u16(0);
6426
- return { key: "thrs", data: w.result() };
6427
- }
6428
- case "posterize": {
6429
- const p = params;
6430
- w.u16(Math.round(clamp2(p.levels, 2, 255)));
6431
- w.u16(0);
6432
- return { key: "post", data: w.result() };
6433
- }
6434
- case "levels": {
6435
- const p = params;
6436
- w.u16(2);
6437
- for (const c of [p.master, p.red, p.green, p.blue]) writeLevels(w, c);
6438
- const ident = { inBlack: 0, inWhite: 255, gamma: 1, outBlack: 0, outWhite: 255 };
6439
- for (let i = 4; i < 29; i++) writeLevels(w, ident);
6440
- return { key: "levl", data: w.result() };
6441
- }
6442
- case "curves": {
6443
- const p = params;
6444
- w.u8(0);
6445
- w.u16(1);
6446
- w.u32(15);
6447
- for (const c of [p.master, p.red, p.green, p.blue]) {
6448
- const pts = c.slice(0, 19);
6449
- w.u16(pts.length);
6450
- for (const pt of pts) {
6451
- w.u16(q(pt.output));
6452
- w.u16(q(pt.input));
6453
- }
6454
- }
6455
- return { key: "curv", data: w.result() };
6456
- }
6457
- case "hueSaturation": {
6458
- const p = params;
6459
- w.u16(2);
6460
- w.u8(p.colorize ? 1 : 0);
6461
- w.u8(0);
6462
- for (let k = 0; k < 2; k++) for (const x of [p.hue, p.saturation, p.lightness]) w.i16(i16(x));
6463
- for (let i = 0; i < 6; i++) {
6464
- const r = p.ranges[i] ?? neutralHueRange(i);
6465
- for (const b of r.bounds) w.u16(Math.round((b % 360 + 360) % 360) % 360);
6466
- for (const x of [r.hue, r.saturation, r.lightness]) w.i16(i16(x));
6467
- }
6468
- return { key: "hue2", data: w.result() };
6469
- }
6470
- case "vibrance": {
6471
- const p = params;
6472
- const d = descriptor("null", [
6473
- ["vibrance", long(clamp2(p.vibrance, -100, 100))],
6474
- ["Strt", long(clamp2(p.saturation, -100, 100))]
6475
- ]);
6476
- return { key: "vibA", data: versionedDescriptorBytes(d) };
6477
- }
6478
- case "colorBalance": {
6479
- const p = params;
6480
- for (const x of [...p.shadows, ...p.midtones, ...p.highlights]) w.i16(i16(clamp2(x, -100, 100)));
6481
- w.u8(p.preserveLuminosity ? 1 : 0);
6482
- w.u8(0);
6483
- return { key: "blnc", data: w.result() };
6484
- }
6485
- case "blackWhite": {
6486
- const p = params;
6487
- const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;
6488
- const dbl3 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
6489
- const items = BW_KEYS.map(([field, key]) => [key, long(clamp2(p[field], -200, 300))]);
6490
- items.push(
6491
- ["useTint", { type: "bool", value: p.tint !== null }],
6492
- ["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ", dbl3(tint[0])], ["Grn ", dbl3(tint[1])], ["Bl ", dbl3(tint[2])]]) }],
6493
- ["bwPresetKind", { type: "long", value: 1 }],
6494
- ["blackAndWhitePresetFileName", { type: "TEXT", value: "" }]
6495
- );
6496
- return { key: "blwh", data: versionedDescriptorBytes(descriptor("null", items)) };
8422
+ function decodePlanes(compression, data, l) {
8423
+ const total = decodedLength(l);
8424
+ switch (compression) {
8425
+ case 0:
8426
+ if (data.length < total) throw new PsdError(`Raw channel data has ${data.length} of ${total} bytes.`);
8427
+ return data.slice(0, total);
8428
+ case 1:
8429
+ return decodeRle(new PsdReader(data), l, total);
8430
+ case 2:
8431
+ return inflateChecked(data, total).slice();
8432
+ case 3: {
8433
+ const v = inflateChecked(data, total).slice();
8434
+ unpredict(v, l);
8435
+ return v;
6497
8436
  }
6498
- case "photoFilter": {
6499
- const p = params;
6500
- const rgb = hexToRgb(p.color);
6501
- w.u16(2);
6502
- w.u16(0);
6503
- for (const x of rgb) w.u16(Math.round(clamp2(x, 0, 1) * 65535));
6504
- w.u16(0);
6505
- w.u32(Math.round(clamp2(p.density, 0, 100)));
6506
- w.u8(p.preserveLuminosity ? 1 : 0);
6507
- w.zeros(3);
6508
- return { key: "phfl", data: w.result() };
8437
+ default:
8438
+ throw new PsdError(`Unsupported compression method ${compression}.`);
8439
+ }
8440
+ }
8441
+ function encodeRle(decoded2, l) {
8442
+ const rows = l.planes * l.height;
8443
+ const rb = rowBytes(l.width, l.depth);
8444
+ const cs = l.psb ? 4 : 2;
8445
+ const packed = new Uint8Array(rows * packbitsBound(rb));
8446
+ let at = 0;
8447
+ const counts = new Uint8Array(rows * cs);
8448
+ for (let row = 0; row < rows; row++) {
8449
+ const start = at;
8450
+ at = packbitsEncode(decoded2.subarray(row * rb, (row + 1) * rb), packed, at);
8451
+ const n = at - start;
8452
+ if (cs === 2) {
8453
+ if (n > 65535) throw new PsdError("An RLE row is longer than 65535 bytes; write a PSB instead.");
8454
+ counts[row * 2] = n >> 8;
8455
+ counts[row * 2 + 1] = n & 255;
8456
+ } else {
8457
+ counts[row * 4] = n >>> 24;
8458
+ counts[row * 4 + 1] = n >>> 16 & 255;
8459
+ counts[row * 4 + 2] = n >>> 8 & 255;
8460
+ counts[row * 4 + 3] = n & 255;
6509
8461
  }
6510
8462
  }
6511
- throw new Error(`Unknown adjustment kind "${kind}".`);
8463
+ const out = new Uint8Array(counts.length + at);
8464
+ out.set(counts);
8465
+ out.set(packed.subarray(0, at), counts.length);
8466
+ return out;
6512
8467
  }
6513
8468
 
6514
8469
  // src/image-editor/psd/smart.ts
6515
- var dbl = (value) => ({ type: "doub", value });
6516
- var unit = (u, value) => ({ type: "UntF", unit: u, value });
6517
- var long2 = (value) => ({ type: "long", value: Math.round(value) });
6518
- var boolV = (value) => ({ type: "bool", value });
6519
- var enumV = (typeId, value) => ({ type: "enum", typeId, value });
8470
+ var dbl2 = (value) => ({ type: "doub", value });
8471
+ var unit2 = (u, value) => ({ type: "UntF", unit: u, value });
8472
+ var long3 = (value) => ({ type: "long", value: Math.round(value) });
8473
+ var boolV2 = (value) => ({ type: "bool", value });
8474
+ var enumV2 = (typeId, value) => ({ type: "enum", typeId, value });
6520
8475
  var textV = (value) => ({ type: "TEXT", value });
6521
- var obj = (d) => ({ type: "Objc", value: d });
8476
+ var obj2 = (d) => ({ type: "Objc", value: d });
6522
8477
  var text = (d, key) => {
6523
8478
  const v = descGet(d, key);
6524
8479
  return v?.type === "TEXT" ? v.value : void 0;
6525
8480
  };
6526
- var enumOf = (d, key) => {
8481
+ var enumOf2 = (d, key) => {
6527
8482
  const v = descGet(d, key);
6528
8483
  return v?.type === "enum" ? v.value : void 0;
6529
8484
  };
@@ -6750,7 +8705,7 @@ var warpStyleOf = (name) => {
6750
8705
  };
6751
8706
  function warpFromDescriptor(w, contents) {
6752
8707
  const full = { x: 0, y: 0, width: contents.width, height: contents.height };
6753
- const styleName = enumOf(w, "warpStyle");
8708
+ const styleName = enumOf2(w, "warpStyle");
6754
8709
  if (!styleName) return void 0;
6755
8710
  if (styleName === "warpNone") return { mesh: null, frame: full };
6756
8711
  const bd = descObject(w, "bounds");
@@ -6769,9 +8724,9 @@ function warpFromDescriptor(w, contents) {
6769
8724
  const ys = descGet(arr.body, "Vrtc");
6770
8725
  if (xs?.type !== "UnFl" || ys?.type !== "UnFl" || xs.values.length !== 16 || ys.values.length !== 16) return void 0;
6771
8726
  mesh = { us: [0, 1], vs: [0, 1], points: xs.values.map((x, i) => ({ x, y: ys.values[i] })) };
6772
- const px = mesh.points.map((p) => p.x);
8727
+ const px2 = mesh.points.map((p) => p.x);
6773
8728
  const py = mesh.points.map((p) => p.y);
6774
- const f = { x: Math.min(...px), y: Math.min(...py), width: Math.max(...px) - Math.min(...px), height: Math.max(...py) - Math.min(...py) };
8729
+ const f = { x: Math.min(...px2), y: Math.min(...py), width: Math.max(...px2) - Math.min(...px2), height: Math.max(...py) - Math.min(...py) };
6775
8730
  if (f.width > 1e-9 && f.height > 1e-9 && [f.x, f.y, f.width, f.height].every(Number.isFinite)) frame = f;
6776
8731
  } else {
6777
8732
  const style = warpStyleOf(styleName);
@@ -6782,7 +8737,7 @@ function warpFromDescriptor(w, contents) {
6782
8737
  bend: descNumber(w, "warpValue") ?? 0,
6783
8738
  horizontal: descNumber(w, "warpPerspective") ?? 0,
6784
8739
  vertical: descNumber(w, "warpPerspectiveOther") ?? 0,
6785
- orientation: enumOf(w, "warpRotate") === "Vrtc" ? "vertical" : "horizontal"
8740
+ orientation: enumOf2(w, "warpRotate") === "Vrtc" ? "vertical" : "horizontal"
6786
8741
  },
6787
8742
  bounds
6788
8743
  );
@@ -6799,20 +8754,20 @@ function warpDescriptor(mesh, contents) {
6799
8754
  const full = { x: 0, y: 0, width: contents.width, height: contents.height };
6800
8755
  const single = mesh && mesh.us.length === 2 && mesh.vs.length === 2 && mesh.points.length === 16;
6801
8756
  const bounds = descriptor("classFloatRect", [
6802
- ["Top ", dbl(0)],
6803
- ["Left", dbl(0)],
6804
- ["Btom", dbl(contents.height)],
6805
- ["Rght", dbl(contents.width)]
8757
+ ["Top ", dbl2(0)],
8758
+ ["Left", dbl2(0)],
8759
+ ["Btom", dbl2(contents.height)],
8760
+ ["Rght", dbl2(contents.width)]
6806
8761
  ]);
6807
8762
  const items = [
6808
- ["warpStyle", enumV("warpStyle", single ? "warpCustom" : "warpNone")],
6809
- ["warpValue", dbl(0)],
6810
- ["warpPerspective", dbl(0)],
6811
- ["warpPerspectiveOther", dbl(0)],
6812
- ["warpRotate", enumV("Ornt", "Hrzn")],
6813
- ["bounds", obj(bounds)],
6814
- ["uOrder", long2(4)],
6815
- ["vOrder", long2(4)]
8763
+ ["warpStyle", enumV2("warpStyle", single ? "warpCustom" : "warpNone")],
8764
+ ["warpValue", dbl2(0)],
8765
+ ["warpPerspective", dbl2(0)],
8766
+ ["warpPerspectiveOther", dbl2(0)],
8767
+ ["warpRotate", enumV2("Ornt", "Hrzn")],
8768
+ ["bounds", obj2(bounds)],
8769
+ ["uOrder", long3(4)],
8770
+ ["vOrder", long3(4)]
6816
8771
  ];
6817
8772
  let frame = full;
6818
8773
  if (single) {
@@ -6820,10 +8775,10 @@ function warpDescriptor(mesh, contents) {
6820
8775
  ["Hrzn", { type: "UnFl", unit: "#Pxl", values: mesh.points.map((p) => p.x) }],
6821
8776
  ["Vrtc", { type: "UnFl", unit: "#Pxl", values: mesh.points.map((p) => p.y) }]
6822
8777
  ]);
6823
- items.push(["customEnvelopeWarp", obj(descriptor("customEnvelopeWarp", [["meshPoints", { type: "ObAr", prefix: 16, body }]]))]);
6824
- const px = mesh.points.map((p) => p.x);
8778
+ items.push(["customEnvelopeWarp", obj2(descriptor("customEnvelopeWarp", [["meshPoints", { type: "ObAr", prefix: 16, body }]]))]);
8779
+ const px2 = mesh.points.map((p) => p.x);
6825
8780
  const py = mesh.points.map((p) => p.y);
6826
- frame = { x: Math.min(...px), y: Math.min(...py), width: Math.max(...px) - Math.min(...px), height: Math.max(...py) - Math.min(...py) };
8781
+ frame = { x: Math.min(...px2), y: Math.min(...py), width: Math.max(...px2) - Math.min(...px2), height: Math.max(...py) - Math.min(...py) };
6827
8782
  }
6828
8783
  return { warp: descriptor("warp", items), frame, dropped: Boolean(mesh && !single) };
6829
8784
  }
@@ -6920,14 +8875,14 @@ var BOX_BLUR = { classId: "boxblur", id: 840, name: "Box Blur..." };
6920
8875
  function blendOptionsOf(item) {
6921
8876
  const o = descObject(item, "blendOptions");
6922
8877
  const opacity = o ? Math.min(1, Math.max(0, (descNumber(o, "Opct") ?? 100) / 100)) : 1;
6923
- const id = o ? enumOf(o, "Md ") : void 0;
8878
+ const id = o ? enumOf2(o, "Md ") : void 0;
6924
8879
  return { blend: id && blendFromPsdId(id) || "normal", opacity };
6925
8880
  }
6926
8881
  function blendOptions(f) {
6927
- return obj(
8882
+ return obj2(
6928
8883
  descriptor("blendOptions", [
6929
- ["Opct", unit("#Prc", Math.min(1, Math.max(0, f.opacity)) * 100)],
6930
- ["Md ", enumV("BlnM", psdBlendId(f.blend))]
8884
+ ["Opct", unit2("#Prc", Math.min(1, Math.max(0, f.opacity)) * 100)],
8885
+ ["Md ", enumV2("BlnM", psdBlendId(f.blend))]
6931
8886
  ])
6932
8887
  );
6933
8888
  }
@@ -6948,7 +8903,7 @@ function smartFiltersFromPsd(fx, newId, notes) {
6948
8903
  const name = (text(item, "Nm ") ?? "Filter").replace(/&/g, "");
6949
8904
  const neutral = cloneDescriptor(item);
6950
8905
  setItem(neutral, "blendOptions", blendOptions({ blend: "normal", opacity: 1 }));
6951
- setItem(neutral, "enab", boolV(true));
8906
+ setItem(neutral, "enab", boolV2(true));
6952
8907
  filters.push({ id: newId(), kind: "psd", enabled, name, data: versionedDescriptorBytes(neutral), blend, opacity });
6953
8908
  notes.push(`the ${name.replace(/\.\.\.$/, "")} smart filter is kept but not drawn`);
6954
8909
  }
@@ -6960,32 +8915,32 @@ function filterFXDescriptor(stack, dropped) {
6960
8915
  if (f.kind === "psd") {
6961
8916
  const d = parseVersionedDescriptor(f.data);
6962
8917
  setItem(d, "blendOptions", blendOptions(f));
6963
- setItem(d, "enab", boolV(f.enabled));
6964
- list2.push(obj(d));
8918
+ setItem(d, "enab", boolV2(f.enabled));
8919
+ list2.push(obj2(d));
6965
8920
  } else if (f.kind === "blur") {
6966
- const rgb = (v) => obj(descriptor("RGBC", [["Rd ", dbl(v)], ["Grn ", dbl(v)], ["Bl ", dbl(v)]]));
8921
+ const rgb = (v) => obj2(descriptor("RGBC", [["Rd ", dbl2(v)], ["Grn ", dbl2(v)], ["Bl ", dbl2(v)]]));
6967
8922
  list2.push(
6968
- obj(
8923
+ obj2(
6969
8924
  descriptor("filterFX", [
6970
8925
  ["Nm ", textV(BOX_BLUR.name)],
6971
8926
  ["blendOptions", blendOptions(f)],
6972
- ["enab", boolV(f.enabled)],
6973
- ["hasoptions", boolV(true)],
8927
+ ["enab", boolV2(f.enabled)],
8928
+ ["hasoptions", boolV2(true)],
6974
8929
  ["FrgC", rgb(0)],
6975
8930
  ["BckC", rgb(255)],
6976
- ["Fltr", obj(descriptor(BOX_BLUR.classId, [["Rds ", unit("#Pxl", f.radius)]]))],
6977
- ["filterID", long2(BOX_BLUR.id)]
8931
+ ["Fltr", obj2(descriptor(BOX_BLUR.classId, [["Rds ", unit2("#Pxl", f.radius)]]))],
8932
+ ["filterID", long3(BOX_BLUR.id)]
6978
8933
  ])
6979
8934
  )
6980
8935
  );
6981
8936
  } else dropped.push(f.kind);
6982
8937
  }
6983
8938
  return descriptor("filterFXStyle", [
6984
- ["enab", boolV(stack.enabled)],
6985
- ["validAtPosition", boolV(true)],
6986
- ["filterMaskEnable", boolV(stack.mask ? stack.mask.enabled !== false : true)],
6987
- ["filterMaskLinked", boolV(stack.mask ? stack.mask.linked !== false : false)],
6988
- ["filterMaskExtendWithWhite", boolV(true)],
8939
+ ["enab", boolV2(stack.enabled)],
8940
+ ["validAtPosition", boolV2(true)],
8941
+ ["filterMaskEnable", boolV2(stack.mask ? stack.mask.enabled !== false : true)],
8942
+ ["filterMaskLinked", boolV2(stack.mask ? stack.mask.linked !== false : false)],
8943
+ ["filterMaskExtendWithWhite", boolV2(true)],
6989
8944
  ["filterFXList", { type: "VlLs", items: list2 }]
6990
8945
  ]);
6991
8946
  }
@@ -7000,7 +8955,7 @@ function filterStackKey(stack) {
7000
8955
  function soldBytes(s, notes) {
7001
8956
  const { warp, frame, dropped } = warpDescriptor(s.placement.warp, s.size);
7002
8957
  if (dropped) notes.push("a warp of several patches was written without its warp (the layer\u2019s pixels still show it)");
7003
- const quad = { type: "VlLs", items: quadFromCorners(s.placement.corners, frame, s.size).map(dbl) };
8958
+ const quad = { type: "VlLs", items: quadFromCorners(s.placement.corners, frame, s.size).map(dbl2) };
7004
8959
  let d;
7005
8960
  if (s.template) {
7006
8961
  d = cloneDescriptor(s.template);
@@ -7008,31 +8963,31 @@ function soldBytes(s, notes) {
7008
8963
  setItem(d, "placed", textV(s.placed));
7009
8964
  setItem(d, "Trnf", quad);
7010
8965
  setItem(d, "nonAffineTransform", quad);
7011
- setItem(d, "warp", obj(warp));
7012
- setItem(d, "Sz ", obj(descriptor("Pnt ", [["Wdth", dbl(s.size.width)], ["Hght", dbl(s.size.height)]])));
7013
- if (s.filterFX) setItem(d, "filterFX", obj(s.filterFX));
8966
+ setItem(d, "warp", obj2(warp));
8967
+ setItem(d, "Sz ", obj2(descriptor("Pnt ", [["Wdth", dbl2(s.size.width)], ["Hght", dbl2(s.size.height)]])));
8968
+ if (s.filterFX) setItem(d, "filterFX", obj2(s.filterFX));
7014
8969
  else d.items = d.items.filter(([k]) => k !== "filterFX");
7015
8970
  } else {
7016
- const ratio = (n, den) => obj(descriptor("null", [["numerator", long2(n)], ["denominator", long2(den)]]));
8971
+ const ratio = (n, den) => obj2(descriptor("null", [["numerator", long3(n)], ["denominator", long3(den)]]));
7017
8972
  const items = [
7018
8973
  ["Idnt", textV(s.idnt)],
7019
8974
  ["placed", textV(s.placed)],
7020
- ["PgNm", long2(1)],
7021
- ["totalPages", long2(1)],
7022
- ["Crop", long2(1)],
8975
+ ["PgNm", long3(1)],
8976
+ ["totalPages", long3(1)],
8977
+ ["Crop", long3(1)],
7023
8978
  ["frameStep", ratio(0, 600)],
7024
8979
  ["duration", ratio(0, 600)],
7025
- ["frameCount", long2(1)],
7026
- ["Annt", long2(16)],
7027
- ["Type", long2(2)],
8980
+ ["frameCount", long3(1)],
8981
+ ["Annt", long3(16)],
8982
+ ["Type", long3(2)],
7028
8983
  ["Trnf", quad],
7029
8984
  ["nonAffineTransform", quad],
7030
- ["warp", obj(warp)],
7031
- ["Sz ", obj(descriptor("Pnt ", [["Wdth", dbl(s.size.width)], ["Hght", dbl(s.size.height)]]))],
7032
- ["Rslt", unit("#Rsl", 72)]
8985
+ ["warp", obj2(warp)],
8986
+ ["Sz ", obj2(descriptor("Pnt ", [["Wdth", dbl2(s.size.width)], ["Hght", dbl2(s.size.height)]]))],
8987
+ ["Rslt", unit2("#Rsl", 72)]
7033
8988
  ];
7034
- if (s.filterFX) items.push(["filterFX", obj(s.filterFX)]);
7035
- items.push(["comp", long2(-1)], ["compInfo", obj(descriptor("null", [["compID", long2(-1)], ["originalCompID", long2(-1)]]))]);
8989
+ if (s.filterFX) items.push(["filterFX", obj2(s.filterFX)]);
8990
+ items.push(["comp", long3(-1)], ["compInfo", obj2(descriptor("null", [["compID", long3(-1)], ["originalCompID", long3(-1)]]))]);
7036
8991
  d = descriptor("null", items);
7037
8992
  }
7038
8993
  const w = new PsdWriter();
@@ -7252,9 +9207,9 @@ function readLayerInfo(data, psb) {
7252
9207
  const count = r.i16();
7253
9208
  const n = Math.abs(count);
7254
9209
  r.checkCount(n, 34, "Layer records");
7255
- const read = [];
7256
- for (let i = 0; i < n; i++) read.push(readLayerRecord(r, psb));
7257
- for (const { record, lengths } of read) {
9210
+ const read2 = [];
9211
+ for (let i = 0; i < n; i++) read2.push(readLayerRecord(r, psb));
9212
+ for (const { record, lengths } of read2) {
7258
9213
  record.channels.forEach((ch, i) => {
7259
9214
  const len = lengths[i];
7260
9215
  if (len === 0) return;
@@ -7263,7 +9218,7 @@ function readLayerInfo(data, psb) {
7263
9218
  ch.data = r.bytes(len - 2);
7264
9219
  });
7265
9220
  }
7266
- return { layers: read.map((x) => x.record), mergedAlpha: count < 0 };
9221
+ return { layers: read2.map((x) => x.record), mergedAlpha: count < 0 };
7267
9222
  }
7268
9223
  function readPsd(bytes) {
7269
9224
  const r = new PsdReader(bytes);
@@ -7375,11 +9330,11 @@ function writeRect(w, r) {
7375
9330
  }
7376
9331
  function writeBlocks(w, blocks, psb, alignment) {
7377
9332
  for (const b of blocks) {
7378
- const long4 = psb && PSB_LONG_KEYS.has(b.key);
7379
- w.ascii(long4 ? "8B64" : "8BIM");
9333
+ const long5 = psb && PSB_LONG_KEYS.has(b.key);
9334
+ w.ascii(long5 ? "8B64" : "8BIM");
7380
9335
  w.ascii(b.key);
7381
9336
  const pad = (alignment - b.data.length % alignment) % alignment;
7382
- if (long4) w.u64(b.data.length + pad);
9337
+ if (long5) w.u64(b.data.length + pad);
7383
9338
  else w.u32(b.data.length + pad);
7384
9339
  w.bytes(b.data);
7385
9340
  w.zeros(pad);
@@ -7800,7 +9755,7 @@ var PSD_BLEND_KEYS = {
7800
9755
  var BLEND_TO_PSD = Object.fromEntries(
7801
9756
  Object.entries(PSD_BLEND_KEYS).map(([k, v]) => [v, k])
7802
9757
  );
7803
- var FILL_KEYS = ["SoCo", "GdFl", "PtFl"];
9758
+ var FILL_KEYS = ["SoCo", "GdFl", "PtFl", "vscg"];
7804
9759
  var TYPE_KEYS = ["TySh", "tySh"];
7805
9760
  var SMART_KEYS = ["SoLd", "SoLE", "PlLd"];
7806
9761
  var VECTOR_KEYS = ["vmsk", "vsms"];
@@ -8043,6 +9998,24 @@ function importNode(ctx, node, depth) {
8043
9998
  return smart;
8044
9999
  }
8045
10000
  }
10001
+ if (kind === "shape") {
10002
+ const shape = importShape(ctx, rec, name, notes, props);
10003
+ if (shape) {
10004
+ entry.type = "shape";
10005
+ entry.id = shape.id;
10006
+ entry.status = notes.length ? "approximated" : "mapped";
10007
+ return shape;
10008
+ }
10009
+ }
10010
+ if (kind === "type") {
10011
+ const type = importType(ctx, rec, name, notes, props);
10012
+ if (type) {
10013
+ entry.type = "type";
10014
+ entry.id = type.id;
10015
+ entry.status = notes.length ? "approximated" : "mapped";
10016
+ return type;
10017
+ }
10018
+ }
8046
10019
  if (kind !== "pixel") {
8047
10020
  const what = kind === "smart-object" ? "Smart objects" : kind === "type" ? "Type layers" : kind === "shape" ? "Shape layers" : "Fill layers";
8048
10021
  ctx.warn.add(`${what} came in as pixel layers (their stored pixels)`, name);
@@ -8060,6 +10033,69 @@ function importNode(ctx, node, depth) {
8060
10033
  entry.status = notes.length ? "approximated" : "mapped";
8061
10034
  return layer;
8062
10035
  }
10036
+ function importShape(ctx, rec, name, notes, props) {
10037
+ const approx = /* @__PURE__ */ new Set();
10038
+ const { width, height } = ctx.file.header;
10039
+ const data = shapeFromBlocks(rec.blocks, width, height, ctx.dpi, approx);
10040
+ if (!data) return null;
10041
+ for (const n of approx) {
10042
+ ctx.warn.add(`A shape layer was approximated: ${n}`, name);
10043
+ notes.push(n);
10044
+ }
10045
+ const layer = {
10046
+ type: "shape",
10047
+ id: ctx.newId(name),
10048
+ ...props,
10049
+ components: data.components,
10050
+ fill: data.fill,
10051
+ stroke: data.stroke,
10052
+ surface: emptySurface(4),
10053
+ offset: { x: 0, y: 0 }
10054
+ };
10055
+ if (data.inverted) layer.inverted = true;
10056
+ if (!ctx.skipPixels) layer.surface = renderShape(layer, { width, height, pattern: (id) => ctx.patterns.get(id) ?? null });
10057
+ layer.psd = { blocks: rec.blocks.filter((b) => SHAPE_PSD_KEYS.includes(b.key)).map((b) => ({ key: b.key, data: b.data.slice() })), signature: shapeSignature(layer) };
10058
+ const fill = u8Block(rec, "iOpa");
10059
+ if (fill !== void 0 && fill < 255) layer.fillOpacity = percent(fill);
10060
+ const effects = recordEffects(ctx, rec, name, notes, false);
10061
+ if (effects) layer.effects = effects;
10062
+ const mask = recordMask(ctx, rec, name);
10063
+ if (mask) layer.mask = mask;
10064
+ return layer;
10065
+ }
10066
+ function importType(ctx, rec, name, notes, props) {
10067
+ const block = rec.blocks.find((b) => b.key === "TySh" || b.key === "tySh");
10068
+ if (!block || block.key !== "TySh") return null;
10069
+ const approx = /* @__PURE__ */ new Set();
10070
+ const data = typeFromTySh(block.data, approx);
10071
+ if (!data) return null;
10072
+ for (const n of approx) {
10073
+ ctx.warn.add(`A type layer was approximated: ${n}`, name);
10074
+ notes.push(n);
10075
+ }
10076
+ const layer = {
10077
+ type: "type",
10078
+ id: ctx.newId(name),
10079
+ ...props,
10080
+ text: data.text,
10081
+ runs: data.runs,
10082
+ paragraphs: data.paragraphs,
10083
+ frame: data.frame,
10084
+ transform: data.transform,
10085
+ surface: recordPixels(ctx, rec, name),
10086
+ offset: { x: 0, y: 0 },
10087
+ rendering: "stored"
10088
+ };
10089
+ if (!data.antiAlias) layer.antiAlias = false;
10090
+ layer.psd = { tySh: block.data.slice(), signature: typeSignature(layer) };
10091
+ const fill = u8Block(rec, "iOpa");
10092
+ if (fill !== void 0 && fill < 255) layer.fillOpacity = percent(fill);
10093
+ const effects = recordEffects(ctx, rec, name, notes, false);
10094
+ if (effects) layer.effects = effects;
10095
+ const mask = recordMask(ctx, rec, name);
10096
+ if (mask) layer.mask = mask;
10097
+ return layer;
10098
+ }
8063
10099
  function contentsFor(ctx, placed, name, notes) {
8064
10100
  const memo = ctx.contents.get(placed.idnt);
8065
10101
  if (memo !== void 0) return memo;
@@ -8231,7 +10267,8 @@ function psdToLayers(file, options = {}) {
8231
10267
  linked: /* @__PURE__ */ new Map(),
8232
10268
  filterItems: /* @__PURE__ */ new Map(),
8233
10269
  contents: /* @__PURE__ */ new Map(),
8234
- depth: options.depth ?? 0
10270
+ depth: options.depth ?? 0,
10271
+ dpi: resolution(file) ?? 72
8235
10272
  };
8236
10273
  for (const block of file.globalBlocks) {
8237
10274
  if (LINKED_FILE_KEYS.includes(block.key)) {
@@ -8353,10 +10390,10 @@ function blendFromPsdId(id) {
8353
10390
  function psdBlendId(mode) {
8354
10391
  return BLEND_ID.get(mode) ?? "Nrml";
8355
10392
  }
8356
- var num2 = (d, key, fallback) => descNumber(d, key) ?? fallback;
8357
- var pct = (d, key, fallback) => num2(d, key, fallback * 100) / 100;
10393
+ var num3 = (d, key, fallback) => descNumber(d, key) ?? fallback;
10394
+ var pct = (d, key, fallback) => num3(d, key, fallback * 100) / 100;
8358
10395
  var bool = (d, key) => descBool(d, key) === true;
8359
- var enumOf2 = (d, key) => {
10396
+ var enumOf3 = (d, key) => {
8360
10397
  const v = descGet(d, key);
8361
10398
  return v?.type === "enum" ? v.value : void 0;
8362
10399
  };
@@ -8370,15 +10407,15 @@ var list = (d, key) => {
8370
10407
  return v.items.flatMap((it) => it.type === "Objc" || it.type === "GlbO" ? [it.value] : []);
8371
10408
  };
8372
10409
  var clamp3 = (v, lo, hi) => Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : lo;
8373
- var unit2 = (u, value) => ({ type: "UntF", unit: u, value });
8374
- var dbl2 = (value) => ({ type: "doub", value });
8375
- var long3 = (value) => ({ type: "long", value: Math.round(value) });
8376
- var boolV2 = (value) => ({ type: "bool", value });
8377
- var enumV2 = (typeId, value) => ({ type: "enum", typeId, value });
10410
+ var unit3 = (u, value) => ({ type: "UntF", unit: u, value });
10411
+ var dbl3 = (value) => ({ type: "doub", value });
10412
+ var long4 = (value) => ({ type: "long", value: Math.round(value) });
10413
+ var boolV3 = (value) => ({ type: "bool", value });
10414
+ var enumV3 = (typeId, value) => ({ type: "enum", typeId, value });
8378
10415
  var textV2 = (value) => ({ type: "TEXT", value });
8379
- var obj2 = (d) => ({ type: "Objc", value: d });
10416
+ var obj3 = (d) => ({ type: "Objc", value: d });
8380
10417
  function blendOf(d, key, fallback, notes) {
8381
- const id = enumOf2(d, key);
10418
+ const id = enumOf3(d, key);
8382
10419
  if (id === void 0) return fallback;
8383
10420
  const m = BLEND_OF.get(id);
8384
10421
  if (m) return m;
@@ -8417,9 +10454,9 @@ function colorOf(d, fallback = "#000000") {
8417
10454
  function colorDesc(hex) {
8418
10455
  const [r, g, b] = parseEffectColor(hex) ?? [0, 0, 0];
8419
10456
  return descriptor("RGBC", [
8420
- ["Rd ", dbl2(r * 255)],
8421
- ["Grn ", dbl2(g * 255)],
8422
- ["Bl ", dbl2(b * 255)]
10457
+ ["Rd ", dbl3(r * 255)],
10458
+ ["Grn ", dbl3(g * 255)],
10459
+ ["Bl ", dbl3(b * 255)]
8423
10460
  ]);
8424
10461
  }
8425
10462
  function contourOf(d, key) {
@@ -8427,7 +10464,7 @@ function contourOf(d, key) {
8427
10464
  if (!c) return void 0;
8428
10465
  const name = text2(c, "Nm ");
8429
10466
  const points = list(c, "Crv ").map((p) => {
8430
- const out = { input: clamp3(num2(p, "Hrzn", 0) / 255, 0, 1), output: clamp3(num2(p, "Vrtc", 0) / 255, 0, 1) };
10467
+ const out = { input: clamp3(num3(p, "Hrzn", 0) / 255, 0, 1), output: clamp3(num3(p, "Vrtc", 0) / 255, 0, 1) };
8431
10468
  if (descBool(p, "Cnty") === false) out.corner = true;
8432
10469
  return out;
8433
10470
  });
@@ -8445,14 +10482,14 @@ function contourDesc(c) {
8445
10482
  type: "VlLs",
8446
10483
  items: points.map((p) => {
8447
10484
  const items = [
8448
- ["Hrzn", dbl2(p.input * 255)],
8449
- ["Vrtc", dbl2(p.output * 255)]
10485
+ ["Hrzn", dbl3(p.input * 255)],
10486
+ ["Vrtc", dbl3(p.output * 255)]
8450
10487
  ];
8451
- if (p.corner) items.push(["Cnty", boolV2(false)]);
8452
- return obj2(descriptor("CrPt", items));
10488
+ if (p.corner) items.push(["Cnty", boolV3(false)]);
10489
+ return obj3(descriptor("CrPt", items));
8453
10490
  })
8454
10491
  };
8455
- return obj2(
10492
+ return obj3(
8456
10493
  descriptor("ShpC", [
8457
10494
  ["Nm ", textV2(name)],
8458
10495
  ["Crv ", pts]
@@ -8472,8 +10509,8 @@ function gradientOf(d, notes) {
8472
10509
  { position: 0, color: "#000000" },
8473
10510
  { position: 1, color: "#ffffff" }
8474
10511
  ],
8475
- style: GRADIENT_STYLES.find(([, id]) => id === enumOf2(d, "Type"))?.[0] ?? "linear",
8476
- angle: clamp3(num2(d, "Angl", 90), -360, 360),
10512
+ style: GRADIENT_STYLES.find(([, id]) => id === enumOf3(d, "Type"))?.[0] ?? "linear",
10513
+ angle: clamp3(num3(d, "Angl", 90), -360, 360),
8477
10514
  scale: clamp3(pct(d, "Scl ", 1), 0.1, 1.5),
8478
10515
  reverse: bool(d, "Rvrs"),
8479
10516
  align: descBool(d, "Algn") !== false,
@@ -8483,15 +10520,15 @@ function gradientOf(d, notes) {
8483
10520
  if (off) g.offset = { x: clamp3(pct(off, "Hrzn", 0), -1, 1), y: clamp3(pct(off, "Vrtc", 0), -1, 1) };
8484
10521
  const grad = descObject(d, "Grad");
8485
10522
  if (!grad) return g;
8486
- if (enumOf2(grad, "GrdF") === "ClNs") {
10523
+ if (enumOf3(grad, "GrdF") === "ClNs") {
8487
10524
  notes.add("noise gradients became black to white");
8488
10525
  return g;
8489
10526
  }
8490
10527
  const raw = list(grad, "Clrs").map((s) => {
8491
- const type = enumOf2(s, "Type");
10528
+ const type = enumOf3(s, "Type");
8492
10529
  if (type === "FrgC" || type === "BckC") notes.add("foreground / background gradient stops became black / white");
8493
10530
  const color = type === "BckC" ? "#ffffff" : colorOf(descObject(s, "Clr "));
8494
- return { position: clamp3(num2(s, "Lctn", 0) / 4096, 0, 1), color, mid: clamp3(num2(s, "Mdpn", 50) / 100, 0.05, 0.95) };
10531
+ return { position: clamp3(num3(s, "Lctn", 0) / 4096, 0, 1), color, mid: clamp3(num3(s, "Mdpn", 50) / 100, 0.05, 0.95) };
8495
10532
  }).sort((a, b) => a.position - b.position);
8496
10533
  if (raw.length) {
8497
10534
  const stops = [];
@@ -8507,21 +10544,21 @@ function gradientOf(d, notes) {
8507
10544
  });
8508
10545
  g.stops = stops;
8509
10546
  }
8510
- const opacity = list(grad, "Trns").map((s) => ({ position: clamp3(num2(s, "Lctn", 0) / 4096, 0, 1), opacity: clamp3(num2(s, "Opct", 100) / 100, 0, 1) })).sort((a, b) => a.position - b.position);
10547
+ const opacity = list(grad, "Trns").map((s) => ({ position: clamp3(num3(s, "Lctn", 0) / 4096, 0, 1), opacity: clamp3(num3(s, "Opct", 100) / 100, 0, 1) })).sort((a, b) => a.position - b.position);
8511
10548
  if (opacity.some((o) => o.opacity < 1)) g.opacityStops = opacity;
8512
- if (num2(grad, "Intr", 0) > 0) notes.add("gradient smoothness is drawn as straight segments");
10549
+ if (num3(grad, "Intr", 0) > 0) notes.add("gradient smoothness is drawn as straight segments");
8513
10550
  return g;
8514
10551
  }
8515
10552
  function gradientItems(g) {
8516
10553
  const clrs = {
8517
10554
  type: "VlLs",
8518
10555
  items: g.stops.map(
8519
- (s) => obj2(
10556
+ (s) => obj3(
8520
10557
  descriptor("Clrt", [
8521
- ["Clr ", obj2(colorDesc(s.color))],
8522
- ["Type", enumV2("Clry", "UsrS")],
8523
- ["Lctn", long3(s.position * 4096)],
8524
- ["Mdpn", long3(50)]
10558
+ ["Clr ", obj3(colorDesc(s.color))],
10559
+ ["Type", enumV3("Clry", "UsrS")],
10560
+ ["Lctn", long4(s.position * 4096)],
10561
+ ["Mdpn", long4(50)]
8525
10562
  ])
8526
10563
  )
8527
10564
  )
@@ -8530,35 +10567,35 @@ function gradientItems(g) {
8530
10567
  const trns = {
8531
10568
  type: "VlLs",
8532
10569
  items: op.map(
8533
- (s) => obj2(
10570
+ (s) => obj3(
8534
10571
  descriptor("TrnS", [
8535
- ["Opct", unit2("#Prc", s.opacity * 100)],
8536
- ["Lctn", long3(s.position * 4096)],
8537
- ["Mdpn", long3(50)]
10572
+ ["Opct", unit3("#Prc", s.opacity * 100)],
10573
+ ["Lctn", long4(s.position * 4096)],
10574
+ ["Mdpn", long4(50)]
8538
10575
  ])
8539
10576
  )
8540
10577
  )
8541
10578
  };
8542
10579
  const grad = descriptor("Grdn", [
8543
10580
  ["Nm ", textV2("Custom")],
8544
- ["GrdF", enumV2("GrdF", "CstS")],
8545
- ["Intr", dbl2(0)],
10581
+ ["GrdF", enumV3("GrdF", "CstS")],
10582
+ ["Intr", dbl3(0)],
8546
10583
  ["Clrs", clrs],
8547
10584
  ["Trns", trns]
8548
10585
  ]);
8549
10586
  return [
8550
- ["Grad", obj2(grad)],
8551
- ["Angl", unit2("#Ang", g.angle)],
8552
- ["Type", enumV2("GrdT", GRADIENT_STYLES.find(([s]) => s === g.style)[1])],
8553
- ["Rvrs", boolV2(g.reverse)],
8554
- ["Algn", boolV2(g.align)],
8555
- ["Scl ", unit2("#Prc", g.scale * 100)],
10587
+ ["Grad", obj3(grad)],
10588
+ ["Angl", unit3("#Ang", g.angle)],
10589
+ ["Type", enumV3("GrdT", GRADIENT_STYLES.find(([s]) => s === g.style)[1])],
10590
+ ["Rvrs", boolV3(g.reverse)],
10591
+ ["Algn", boolV3(g.align)],
10592
+ ["Scl ", unit3("#Prc", g.scale * 100)],
8556
10593
  [
8557
10594
  "Ofst",
8558
- obj2(
10595
+ obj3(
8559
10596
  descriptor("Pnt ", [
8560
- ["Hrzn", unit2("#Prc", g.offset.x * 100)],
8561
- ["Vrtc", unit2("#Prc", g.offset.y * 100)]
10597
+ ["Hrzn", unit3("#Prc", g.offset.x * 100)],
10598
+ ["Vrtc", unit3("#Prc", g.offset.y * 100)]
8562
10599
  ])
8563
10600
  )
8564
10601
  ]
@@ -8569,7 +10606,7 @@ function patternRefOf(d) {
8569
10606
  return { patternId: text2(p, "Idnt") || text2(p, "Nm "), name: text2(p, "Nm ") };
8570
10607
  }
8571
10608
  function patternRefDesc(patternId, name) {
8572
- return obj2(
10609
+ return obj3(
8573
10610
  descriptor("Ptrn", [
8574
10611
  ["Nm ", textV2(name ?? patternId)],
8575
10612
  ["Idnt", textV2(patternId)]
@@ -8579,21 +10616,21 @@ function patternRefDesc(patternId, name) {
8579
10616
  function placementOf(d) {
8580
10617
  const phase = descObject(d, "phase");
8581
10618
  return {
8582
- angle: clamp3(num2(d, "Angl", 0), -360, 360),
10619
+ angle: clamp3(num3(d, "Angl", 0), -360, 360),
8583
10620
  link: descBool(d, "Algn") !== false,
8584
- phase: phase ? { x: num2(phase, "Hrzn", 0), y: num2(phase, "Vrtc", 0) } : { x: 0, y: 0 }
10621
+ phase: phase ? { x: num3(phase, "Hrzn", 0), y: num3(phase, "Vrtc", 0) } : { x: 0, y: 0 }
8585
10622
  };
8586
10623
  }
8587
10624
  function placementItems(angle, link, phase) {
8588
10625
  const out = [];
8589
- if (angle) out.push(["Angl", unit2("#Ang", angle)]);
8590
- out.push(["Algn", boolV2(link)]);
10626
+ if (angle) out.push(["Angl", unit3("#Ang", angle)]);
10627
+ out.push(["Algn", boolV3(link)]);
8591
10628
  out.push([
8592
10629
  "phase",
8593
- obj2(
10630
+ obj3(
8594
10631
  descriptor("Pnt ", [
8595
- ["Hrzn", dbl2(phase.x)],
8596
- ["Vrtc", dbl2(phase.y)]
10632
+ ["Hrzn", dbl3(phase.x)],
10633
+ ["Vrtc", dbl3(phase.y)]
8597
10634
  ])
8598
10635
  )
8599
10636
  ]);
@@ -8616,16 +10653,16 @@ function common(d, kind, notes) {
8616
10653
  return { id: newEffectId(), enabled: descBool(d, "enab") !== false, blend: blendOf(d, "Md ", def.blend, notes), opacity: clamp3(pct(d, "Opct", def.opacity), 0, 1) };
8617
10654
  }
8618
10655
  function readOne(kind, d, notes) {
8619
- const size = (key, fallback) => clamp3(num2(d, key, fallback), 0, 250);
10656
+ const size = (key, fallback) => clamp3(num3(d, key, fallback), 0, 250);
8620
10657
  switch (kind) {
8621
10658
  case "drop-shadow":
8622
10659
  case "inner-shadow": {
8623
10660
  const base = {
8624
10661
  ...common(d, kind, notes),
8625
10662
  color: colorOf(descObject(d, "Clr ")),
8626
- angle: clamp3(num2(d, "lagl", 120), -360, 360),
10663
+ angle: clamp3(num3(d, "lagl", 120), -360, 360),
8627
10664
  useGlobalLight: descBool(d, "uglg") !== false,
8628
- distance: clamp3(num2(d, "Dstn", 5), 0, 3e4),
10665
+ distance: clamp3(num3(d, "Dstn", 5), 0, 3e4),
8629
10666
  size: size("blur", 5),
8630
10667
  contour: contourOf(d, "TrnS"),
8631
10668
  antiAlias: bool(d, "AntA"),
@@ -8642,7 +10679,7 @@ function readOne(kind, d, notes) {
8642
10679
  const base = {
8643
10680
  ...common(d, kind, notes),
8644
10681
  fill,
8645
- technique: enumOf2(d, "GlwT") === "PrBL" ? "precise" : "softer",
10682
+ technique: enumOf3(d, "GlwT") === "PrBL" ? "precise" : "softer",
8646
10683
  size: size("blur", 5),
8647
10684
  contour: contourOf(d, "TrnS"),
8648
10685
  antiAlias: bool(d, "AntA"),
@@ -8651,7 +10688,7 @@ function readOne(kind, d, notes) {
8651
10688
  noise: clamp3(pct(d, "Nose", 0), 0, 1)
8652
10689
  };
8653
10690
  const spread = clamp3(pct(d, "Ckmt", 0), 0, 1);
8654
- return kind === "outer-glow" ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf2(d, "glwS") === "SrcC" ? "center" : "edge" };
10691
+ return kind === "outer-glow" ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf3(d, "glwS") === "SrcC" ? "center" : "edge" };
8655
10692
  }
8656
10693
  case "bevel-emboss": {
8657
10694
  const styles = { OtrB: "outer-bevel", InrB: "inner-bevel", Embs: "emboss", PlEb: "pillow-emboss", strokeEmboss: "stroke-emboss" };
@@ -8659,14 +10696,14 @@ function readOne(kind, d, notes) {
8659
10696
  id: newEffectId(),
8660
10697
  enabled: descBool(d, "enab") !== false,
8661
10698
  kind,
8662
- style: styles[enumOf2(d, "bvlS") ?? ""] ?? "inner-bevel",
8663
- technique: enumOf2(d, "bvlT") === "PrBL" ? "chisel-hard" : enumOf2(d, "bvlT") === "Slmt" ? "chisel-soft" : "smooth",
10699
+ style: styles[enumOf3(d, "bvlS") ?? ""] ?? "inner-bevel",
10700
+ technique: enumOf3(d, "bvlT") === "PrBL" ? "chisel-hard" : enumOf3(d, "bvlT") === "Slmt" ? "chisel-soft" : "smooth",
8664
10701
  depth: clamp3(pct(d, "srgR", 1), 0, 10),
8665
- direction: enumOf2(d, "bvlD") === "Out " ? "down" : "up",
10702
+ direction: enumOf3(d, "bvlD") === "Out " ? "down" : "up",
8666
10703
  size: size("blur", 5),
8667
- soften: clamp3(num2(d, "Sftn", 0), 0, 16),
8668
- angle: clamp3(num2(d, "lagl", 120), -360, 360),
8669
- altitude: clamp3(num2(d, "Lald", 30), 0, 90),
10704
+ soften: clamp3(num3(d, "Sftn", 0), 0, 16),
10705
+ angle: clamp3(num3(d, "lagl", 120), -360, 360),
10706
+ altitude: clamp3(num3(d, "Lald", 30), 0, 90),
8670
10707
  useGlobalLight: descBool(d, "uglg") !== false,
8671
10708
  glossContour: contourOf(d, "TrnS"),
8672
10709
  highlightColor: colorOf(descObject(d, "hglC"), "#ffffff"),
@@ -8689,7 +10726,7 @@ function readOne(kind, d, notes) {
8689
10726
  ...common(d, kind, notes),
8690
10727
  kind,
8691
10728
  color: colorOf(descObject(d, "Clr ")),
8692
- angle: clamp3(num2(d, "lagl", 19), -360, 360),
10729
+ angle: clamp3(num3(d, "lagl", 19), -360, 360),
8693
10730
  distance: size("Dstn", 11),
8694
10731
  size: size("blur", 14),
8695
10732
  contour: contourOf(d, "MpgS"),
@@ -8706,14 +10743,14 @@ function readOne(kind, d, notes) {
8706
10743
  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 };
8707
10744
  }
8708
10745
  case "stroke": {
8709
- const paint = enumOf2(d, "PntT");
10746
+ const paint = enumOf3(d, "PntT");
8710
10747
  let fill;
8711
10748
  if (paint === "GrFl") fill = { type: "gradient", gradient: gradientOf(d, notes) };
8712
10749
  else if (paint === "Ptrn") {
8713
10750
  const ref = patternRefOf(d);
8714
10751
  fill = { type: "pattern", patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10) };
8715
10752
  } else fill = { type: "color", color: colorOf(descObject(d, "Clr ")) };
8716
- const pos = enumOf2(d, "Styl");
10753
+ const pos = enumOf3(d, "Styl");
8717
10754
  return { ...common(d, kind, notes), kind, size: size("Sz ", 3), position: pos === "InsF" ? "inside" : pos === "CtrF" ? "center" : "outside", fill };
8718
10755
  }
8719
10756
  }
@@ -8740,11 +10777,11 @@ function parseLfx2(data) {
8740
10777
  }
8741
10778
  function commonItems(e) {
8742
10779
  return [
8743
- ["enab", boolV2(e.enabled)],
8744
- ["present", boolV2(true)],
8745
- ["showInDialog", boolV2(true)],
8746
- ["Md ", enumV2("BlnM", BLEND_ID.get(e.blend) ?? "Nrml")],
8747
- ["Opct", unit2("#Prc", e.opacity * 100)]
10780
+ ["enab", boolV3(e.enabled)],
10781
+ ["present", boolV3(true)],
10782
+ ["showInDialog", boolV3(true)],
10783
+ ["Md ", enumV3("BlnM", BLEND_ID.get(e.blend) ?? "Nrml")],
10784
+ ["Opct", unit3("#Prc", e.opacity * 100)]
8748
10785
  ];
8749
10786
  }
8750
10787
  function writeOne(e) {
@@ -8754,17 +10791,17 @@ function writeOne(e) {
8754
10791
  const inner = e.kind === "inner-shadow";
8755
10792
  const items = [
8756
10793
  ...commonItems(e),
8757
- ["Clr ", obj2(colorDesc(e.color))],
8758
- ["uglg", boolV2(e.useGlobalLight)],
8759
- ["lagl", unit2("#Ang", e.angle)],
8760
- ["Dstn", unit2("#Pxl", e.distance)],
8761
- ["Ckmt", unit2("#Pxl", (inner ? e.choke : e.spread) * 100)],
8762
- ["blur", unit2("#Pxl", e.size)],
8763
- ["Nose", unit2("#Prc", e.noise * 100)],
8764
- ["AntA", boolV2(e.antiAlias)],
10794
+ ["Clr ", obj3(colorDesc(e.color))],
10795
+ ["uglg", boolV3(e.useGlobalLight)],
10796
+ ["lagl", unit3("#Ang", e.angle)],
10797
+ ["Dstn", unit3("#Pxl", e.distance)],
10798
+ ["Ckmt", unit3("#Pxl", (inner ? e.choke : e.spread) * 100)],
10799
+ ["blur", unit3("#Pxl", e.size)],
10800
+ ["Nose", unit3("#Prc", e.noise * 100)],
10801
+ ["AntA", boolV3(e.antiAlias)],
8765
10802
  ["TrnS", contourDesc(e.contour)]
8766
10803
  ];
8767
- if (e.kind === "drop-shadow") items.push(["layerConceals", boolV2(e.knocksOut)]);
10804
+ if (e.kind === "drop-shadow") items.push(["layerConceals", boolV3(e.knocksOut)]);
8768
10805
  return descriptor(inner ? "IrSh" : "DrSh", items);
8769
10806
  }
8770
10807
  case "outer-glow":
@@ -8772,110 +10809,110 @@ function writeOne(e) {
8772
10809
  const inner = e.kind === "inner-glow";
8773
10810
  const items = [...commonItems(e)];
8774
10811
  if (e.fill.type === "gradient") items.push(...gradientItems(e.fill.gradient));
8775
- else items.push(["Clr ", obj2(colorDesc(e.fill.color))]);
10812
+ else items.push(["Clr ", obj3(colorDesc(e.fill.color))]);
8776
10813
  items.push(
8777
- ["GlwT", enumV2("BETE", e.technique === "precise" ? "PrBL" : "SfBL")],
8778
- ["Ckmt", unit2("#Pxl", (inner ? e.choke : e.spread) * 100)],
8779
- ["blur", unit2("#Pxl", e.size)],
8780
- ["Nose", unit2("#Prc", e.noise * 100)],
8781
- ["ShdN", unit2("#Prc", e.jitter * 100)],
8782
- ["AntA", boolV2(e.antiAlias)],
10814
+ ["GlwT", enumV3("BETE", e.technique === "precise" ? "PrBL" : "SfBL")],
10815
+ ["Ckmt", unit3("#Pxl", (inner ? e.choke : e.spread) * 100)],
10816
+ ["blur", unit3("#Pxl", e.size)],
10817
+ ["Nose", unit3("#Prc", e.noise * 100)],
10818
+ ["ShdN", unit3("#Prc", e.jitter * 100)],
10819
+ ["AntA", boolV3(e.antiAlias)],
8783
10820
  ["TrnS", contourDesc(e.contour)],
8784
- ["Inpr", unit2("#Prc", e.range * 100)]
10821
+ ["Inpr", unit3("#Prc", e.range * 100)]
8785
10822
  );
8786
- if (e.kind === "inner-glow") items.push(["glwS", enumV2("IGSr", e.source === "center" ? "SrcC" : "SrcE")]);
10823
+ if (e.kind === "inner-glow") items.push(["glwS", enumV3("IGSr", e.source === "center" ? "SrcC" : "SrcE")]);
8787
10824
  return descriptor(inner ? "IrGl" : "OrGl", items);
8788
10825
  }
8789
10826
  case "bevel-emboss": {
8790
10827
  const style = { "outer-bevel": "OtrB", "inner-bevel": "InrB", emboss: "Embs", "pillow-emboss": "PlEb", "stroke-emboss": "strokeEmboss" }[e.style];
8791
10828
  const tech = { smooth: "SfBL", "chisel-hard": "PrBL", "chisel-soft": "Slmt" }[e.technique];
8792
10829
  const items = [
8793
- ["enab", boolV2(e.enabled)],
8794
- ["present", boolV2(true)],
8795
- ["showInDialog", boolV2(true)],
8796
- ["hglM", enumV2("BlnM", BLEND_ID.get(e.highlightBlend) ?? "Scrn")],
8797
- ["hglC", obj2(colorDesc(e.highlightColor))],
8798
- ["hglO", unit2("#Prc", e.highlightOpacity * 100)],
8799
- ["sdwM", enumV2("BlnM", BLEND_ID.get(e.shadowBlend) ?? "Mltp")],
8800
- ["sdwC", obj2(colorDesc(e.shadowColor))],
8801
- ["sdwO", unit2("#Prc", e.shadowOpacity * 100)],
8802
- ["bvlT", enumV2("bvlT", tech)],
8803
- ["bvlS", enumV2("BESl", style)],
8804
- ["uglg", boolV2(e.useGlobalLight)],
8805
- ["lagl", unit2("#Ang", e.angle)],
8806
- ["Lald", unit2("#Ang", e.altitude)],
8807
- ["srgR", unit2("#Prc", e.depth * 100)],
8808
- ["blur", unit2("#Pxl", e.size)],
8809
- ["bvlD", enumV2("BESs", e.direction === "up" ? "In " : "Out ")],
10830
+ ["enab", boolV3(e.enabled)],
10831
+ ["present", boolV3(true)],
10832
+ ["showInDialog", boolV3(true)],
10833
+ ["hglM", enumV3("BlnM", BLEND_ID.get(e.highlightBlend) ?? "Scrn")],
10834
+ ["hglC", obj3(colorDesc(e.highlightColor))],
10835
+ ["hglO", unit3("#Prc", e.highlightOpacity * 100)],
10836
+ ["sdwM", enumV3("BlnM", BLEND_ID.get(e.shadowBlend) ?? "Mltp")],
10837
+ ["sdwC", obj3(colorDesc(e.shadowColor))],
10838
+ ["sdwO", unit3("#Prc", e.shadowOpacity * 100)],
10839
+ ["bvlT", enumV3("bvlT", tech)],
10840
+ ["bvlS", enumV3("BESl", style)],
10841
+ ["uglg", boolV3(e.useGlobalLight)],
10842
+ ["lagl", unit3("#Ang", e.angle)],
10843
+ ["Lald", unit3("#Ang", e.altitude)],
10844
+ ["srgR", unit3("#Prc", e.depth * 100)],
10845
+ ["blur", unit3("#Pxl", e.size)],
10846
+ ["bvlD", enumV3("BESs", e.direction === "up" ? "In " : "Out ")],
8810
10847
  ["TrnS", contourDesc(e.glossContour)],
8811
- ["Sftn", unit2("#Pxl", e.soften)]
10848
+ ["Sftn", unit3("#Pxl", e.soften)]
8812
10849
  ];
8813
10850
  if (e.contourElement) {
8814
- items.push(["useShape", boolV2(true)], ["MpgS", contourDesc(e.contourElement.contour)], ["AntA", boolV2(e.contourElement.antiAlias)], ["Inpr", unit2("#Prc", e.contourElement.range * 100)]);
8815
- } else items.push(["useShape", boolV2(false)]);
10851
+ items.push(["useShape", boolV3(true)], ["MpgS", contourDesc(e.contourElement.contour)], ["AntA", boolV3(e.contourElement.antiAlias)], ["Inpr", unit3("#Prc", e.contourElement.range * 100)]);
10852
+ } else items.push(["useShape", boolV3(false)]);
8816
10853
  if (e.texture) {
8817
10854
  const t = e.texture;
8818
10855
  items.push(
8819
- ["useTexture", boolV2(true)],
8820
- ["InvT", boolV2(t.invert)],
8821
- ["Scl ", unit2("#Prc", t.scale * 100)],
8822
- ["textureDepth", unit2("#Prc", t.depth * 100)],
10856
+ ["useTexture", boolV3(true)],
10857
+ ["InvT", boolV3(t.invert)],
10858
+ ["Scl ", unit3("#Prc", t.scale * 100)],
10859
+ ["textureDepth", unit3("#Prc", t.depth * 100)],
8823
10860
  ["Ptrn", patternRefDesc(t.patternId, t.name)],
8824
10861
  ...placementItems(0, t.link ?? true, t.phase ?? { x: 0, y: 0 })
8825
10862
  );
8826
- } else items.push(["useTexture", boolV2(false)]);
10863
+ } else items.push(["useTexture", boolV3(false)]);
8827
10864
  return descriptor("ebbl", items);
8828
10865
  }
8829
10866
  case "satin":
8830
10867
  return descriptor("ChFX", [
8831
10868
  ...commonItems(e),
8832
- ["Clr ", obj2(colorDesc(e.color))],
8833
- ["AntA", boolV2(e.antiAlias)],
8834
- ["Invr", boolV2(e.invert)],
8835
- ["lagl", unit2("#Ang", e.angle)],
8836
- ["Dstn", unit2("#Pxl", e.distance)],
8837
- ["blur", unit2("#Pxl", e.size)],
10869
+ ["Clr ", obj3(colorDesc(e.color))],
10870
+ ["AntA", boolV3(e.antiAlias)],
10871
+ ["Invr", boolV3(e.invert)],
10872
+ ["lagl", unit3("#Ang", e.angle)],
10873
+ ["Dstn", unit3("#Pxl", e.distance)],
10874
+ ["blur", unit3("#Pxl", e.size)],
8838
10875
  ["MpgS", contourDesc(e.contour)]
8839
10876
  ]);
8840
10877
  case "color-overlay":
8841
- return descriptor("SoFi", [...commonItems(e), ["Clr ", obj2(colorDesc(e.color))]]);
10878
+ return descriptor("SoFi", [...commonItems(e), ["Clr ", obj3(colorDesc(e.color))]]);
8842
10879
  case "gradient-overlay":
8843
- return descriptor("GrFl", [...commonItems(e), ...gradientItems(e.gradient), ["Dthr", boolV2(e.dither)]]);
10880
+ return descriptor("GrFl", [...commonItems(e), ...gradientItems(e.gradient), ["Dthr", boolV3(e.dither)]]);
8844
10881
  case "pattern-overlay":
8845
10882
  return descriptor("patternFill", [
8846
10883
  ...commonItems(e),
8847
10884
  ["Ptrn", patternRefDesc(e.patternId, e.name)],
8848
- ["Scl ", unit2("#Prc", e.scale * 100)],
10885
+ ["Scl ", unit3("#Prc", e.scale * 100)],
8849
10886
  ...placementItems(e.angle ?? 0, e.link ?? true, e.phase ?? { x: 0, y: 0 })
8850
10887
  ]);
8851
10888
  case "stroke": {
8852
10889
  const items = [
8853
10890
  ...commonItems(e),
8854
- ["Styl", enumV2("FStl", e.position === "inside" ? "InsF" : e.position === "center" ? "CtrF" : "OutF")],
8855
- ["Sz ", unit2("#Pxl", e.size)]
10891
+ ["Styl", enumV3("FStl", e.position === "inside" ? "InsF" : e.position === "center" ? "CtrF" : "OutF")],
10892
+ ["Sz ", unit3("#Pxl", e.size)]
8856
10893
  ];
8857
10894
  const f = e.fill;
8858
- if (f.type === "color") items.push(["PntT", enumV2("FrFl", "SClr")], ["Clr ", obj2(colorDesc(f.color))]);
8859
- else if (f.type === "gradient") items.push(["PntT", enumV2("FrFl", "GrFl")], ...gradientItems(f.gradient));
8860
- else items.push(["PntT", enumV2("FrFl", "Ptrn")], ["Ptrn", patternRefDesc(f.patternId, f.name)], ["Scl ", unit2("#Prc", f.scale * 100)]);
10895
+ if (f.type === "color") items.push(["PntT", enumV3("FrFl", "SClr")], ["Clr ", obj3(colorDesc(f.color))]);
10896
+ else if (f.type === "gradient") items.push(["PntT", enumV3("FrFl", "GrFl")], ...gradientItems(f.gradient));
10897
+ else items.push(["PntT", enumV3("FrFl", "Ptrn")], ["Ptrn", patternRefDesc(f.patternId, f.name)], ["Scl ", unit3("#Prc", f.scale * 100)]);
8861
10898
  return descriptor("FrFX", items);
8862
10899
  }
8863
10900
  }
8864
10901
  }
8865
10902
  function writeLfx2(effects) {
8866
10903
  const items = [
8867
- ["Scl ", unit2("#Prc", 100)],
8868
- ["masterFXSwitch", boolV2(effects.enabled)]
10904
+ ["Scl ", unit3("#Prc", 100)],
10905
+ ["masterFXSwitch", boolV3(effects.enabled)]
8869
10906
  ];
8870
10907
  const scale = effectsScale(effects);
8871
10908
  let dropped = 0;
8872
10909
  for (const [single, multi, kind] of KINDS) {
8873
10910
  const of = effects.items.filter((e) => e.kind === kind).map((e) => writeOne(scaleLayerEffect(e, scale)));
8874
10911
  if (!of.length) continue;
8875
- if (of.length === 1) items.push([single, obj2(of[0])]);
8876
- else if (multi) items.push([multi, { type: "VlLs", items: of.map(obj2) }]);
10912
+ if (of.length === 1) items.push([single, obj3(of[0])]);
10913
+ else if (multi) items.push([multi, { type: "VlLs", items: of.map(obj3) }]);
8877
10914
  else {
8878
- items.push([single, obj2(of[0])]);
10915
+ items.push([single, obj3(of[0])]);
8879
10916
  dropped += of.length - 1;
8880
10917
  }
8881
10918
  }
@@ -8893,7 +10930,7 @@ function parseLrfx(data) {
8893
10930
  const count = r.u16();
8894
10931
  const items = [];
8895
10932
  const notes = /* @__PURE__ */ new Set();
8896
- const fixed = (v) => (v | 0) / 65536;
10933
+ const fixed2 = (v) => (v | 0) / 65536;
8897
10934
  for (let i = 0; i < count; i++) {
8898
10935
  if (new TextDecoder().decode(r.bytes(4)) !== "8BIM") return null;
8899
10936
  const key = new TextDecoder().decode(r.bytes(4));
@@ -8912,10 +10949,10 @@ function parseLrfx(data) {
8912
10949
  };
8913
10950
  if (key === "dsdw" || key === "isdw") {
8914
10951
  b.u32();
8915
- const blur = fixed(b.u32());
10952
+ const blur = fixed2(b.u32());
8916
10953
  b.u32();
8917
- const angle = fixed(b.u32());
8918
- const distance = fixed(b.u32());
10954
+ const angle = fixed2(b.u32());
10955
+ const distance = fixed2(b.u32());
8919
10956
  const color = colorAt(b);
8920
10957
  const blend = blendAt(b);
8921
10958
  const enabled = b.u8() !== 0;
@@ -8925,7 +10962,7 @@ function parseLrfx(data) {
8925
10962
  items.push(key === "dsdw" ? { ...base, kind: "drop-shadow", spread: 0, knocksOut: true } : { ...base, kind: "inner-shadow", choke: 0 });
8926
10963
  } else if (key === "oglw" || key === "iglw") {
8927
10964
  b.u32();
8928
- const blur = fixed(b.u32());
10965
+ const blur = fixed2(b.u32());
8929
10966
  b.u32();
8930
10967
  const color = colorAt(b);
8931
10968
  const blend = blendAt(b);
@@ -9046,8 +11083,8 @@ function smartContentsFile(c) {
9046
11083
  return { name: `${base}.png`, bytes: encodePng(c.width, c.height, data) };
9047
11084
  }
9048
11085
  const doc = c.document;
9049
- const merged = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
9050
- const res = exportPsd({ width: c.width, height: c.height, layers: prepareNestedLayers(doc.layers), merged, patterns: doc.patterns, globalLight: doc.globalLight });
11086
+ const merged2 = readRegion(contentsSurface(c), { x: 0, y: 0, width: c.width, height: c.height });
11087
+ const res = exportPsd({ width: c.width, height: c.height, layers: prepareNestedLayers(doc.layers), merged: merged2, patterns: doc.patterns, globalLight: doc.globalLight });
9051
11088
  return { name: `${base}.${res.psb ? "psb" : "psd"}`, bytes: res.bytes };
9052
11089
  }
9053
11090
  var near = (a, b) => Math.abs(a - b) <= 1e-6 * (1 + Math.abs(a));
@@ -9247,6 +11284,44 @@ function exportPsd(input) {
9247
11284
  records.push(rec);
9248
11285
  return;
9249
11286
  }
11287
+ case "shape":
11288
+ case "type": {
11289
+ const extra = [];
11290
+ if (layer.fillOpacity !== void 0 && layer.fillOpacity < 1) {
11291
+ extra.push({ key: "iOpa", data: Uint8Array.from([Math.round(Math.max(0, layer.fillOpacity) * 255), 0, 0, 0]) });
11292
+ }
11293
+ if (layer.type === "shape") {
11294
+ const losses = /* @__PURE__ */ new Set();
11295
+ for (const b2 of shapeBlocks(layer, width, height, input.resolution ?? 72, losses)) extra.push({ key: b2.key, data: padEven(b2.data) });
11296
+ for (const n2 of losses) note(n2, layer.name);
11297
+ if (layer.fill?.type === "pattern") patternIds2.add(layer.fill.patternId);
11298
+ if (layer.stroke?.paint.type === "pattern") patternIds2.add(layer.stroke.paint.patternId);
11299
+ } else {
11300
+ extra.push({ key: "TySh", data: padEven(tyShForLayer(layer)) });
11301
+ }
11302
+ const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
11303
+ const bounds = contentBounds(layer.surface);
11304
+ if (bounds) {
11305
+ const n2 = bounds.width * bounds.height;
11306
+ const [r2, g2, b2, a2] = rgbaToPlanes(readRegion(layer.surface, bounds), n2);
11307
+ rec.rect = toRect(bounds);
11308
+ rec.channels = [
11309
+ rleChannel(CHANNEL_ALPHA, a2, bounds.width, bounds.height, psb),
11310
+ rleChannel(0, r2, bounds.width, bounds.height, psb),
11311
+ rleChannel(1, g2, bounds.width, bounds.height, psb),
11312
+ rleChannel(2, b2, bounds.width, bounds.height, psb)
11313
+ ];
11314
+ } else {
11315
+ rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);
11316
+ }
11317
+ if (layer.mask) {
11318
+ const m = maskChannel(layer.mask, 0, 0, psb);
11319
+ rec.channels.push(m.channel);
11320
+ rec.mask = m.mask;
11321
+ }
11322
+ records.push(rec);
11323
+ return;
11324
+ }
9250
11325
  case "annotation":
9251
11326
  throw new PsdError(`Annotation layer "${layer.name}" must be rasterized before export.`);
9252
11327
  }
@@ -9258,8 +11333,8 @@ function exportPsd(input) {
9258
11333
  const [r, g, b, a] = rgbaToPlanes(matteWhite(input.merged), n);
9259
11334
  const transparent = a.some((v) => v !== 255);
9260
11335
  const planes = transparent ? [r, g, b, a] : [r, g, b];
9261
- const merged = new Uint8Array(n * planes.length);
9262
- planes.forEach((p, i) => merged.set(p, i * n));
11336
+ const merged2 = new Uint8Array(n * planes.length);
11337
+ planes.forEach((p, i) => merged2.set(p, i * n));
9263
11338
  const res = Math.max(1, input.resolution ?? 72);
9264
11339
  const rw = new PsdWriter();
9265
11340
  for (let k = 0; k < 2; k++) {
@@ -9316,7 +11391,7 @@ function exportPsd(input) {
9316
11391
  mergedAlpha: transparent && records.length > 0,
9317
11392
  globalBlocks,
9318
11393
  imageCompression: 1,
9319
- imageData: encodeRle(merged, { planes: planes.length, width, height, depth: 8, psb })
11394
+ imageData: encodeRle(merged2, { planes: planes.length, width, height, depth: 8, psb })
9320
11395
  },
9321
11396
  encodeLegacyName
9322
11397
  );
@@ -9461,6 +11536,7 @@ export {
9461
11536
  removeAlpha,
9462
11537
  SHAPE_OPERATIONS,
9463
11538
  LIVE_SHAPE_KINDS,
11539
+ TEXT_ALIGNS,
9464
11540
  newLayerId,
9465
11541
  pixelLayers,
9466
11542
  developFilter,
@@ -9536,6 +11612,40 @@ export {
9536
11612
  shapeFromAnnotation,
9537
11613
  annotationBlend,
9538
11614
  annotationOfShape,
11615
+ DEFAULT_TEXT_STYLE,
11616
+ DEFAULT_AUTO_LEADING,
11617
+ fitRuns,
11618
+ styleAt,
11619
+ restyleRange,
11620
+ spliceRuns,
11621
+ paragraphRanges,
11622
+ fitParagraphRuns,
11623
+ fontOf,
11624
+ layoutText,
11625
+ textIndexAt,
11626
+ caretAt,
11627
+ TEXT_RENDER_MARGIN,
11628
+ textMatrix,
11629
+ textOutlines,
11630
+ renderText,
11631
+ rerenderText,
11632
+ TEXT_STYLE_FIELDS,
11633
+ patchTextStyle,
11634
+ PARAGRAPH_STYLE_FIELDS,
11635
+ patchParagraphStyle,
11636
+ textRange,
11637
+ requireTypeLayer,
11638
+ normalizeText,
11639
+ typeLayerName,
11640
+ createTypeLayer,
11641
+ updateTypeLayer,
11642
+ replaceTextRange,
11643
+ transformTypeLayer,
11644
+ typeFrame,
11645
+ typeAsPixelLayer,
11646
+ toTextSpace,
11647
+ typeFromAnnotation,
11648
+ annotationOfType,
9539
11649
  BACKGROUND_LAYER_ID,
9540
11650
  BACKGROUND_LAYER_NAME,
9541
11651
  DEVELOP_FILTER_ID,
@@ -9626,8 +11736,6 @@ export {
9626
11736
  deserializeLayer,
9627
11737
  deserializeLayers,
9628
11738
  PsdError,
9629
- decodePlanes,
9630
- encodeRle,
9631
11739
  parseVersionedDescriptor,
9632
11740
  versionedDescriptorBytes,
9633
11741
  ADJUSTMENT_KEYS,
@@ -9637,6 +11745,8 @@ export {
9637
11745
  parseLfx2,
9638
11746
  writeLfx2,
9639
11747
  parseLrfx,
11748
+ decodePlanes,
11749
+ encodeRle,
9640
11750
  BROWSER_IMAGE_TYPES,
9641
11751
  readPsd,
9642
11752
  layerName,
@@ -9654,4 +11764,4 @@ export {
9654
11764
  smartContentsFile,
9655
11765
  exportPsd
9656
11766
  };
9657
- //# sourceMappingURL=chunk-DMRSJBJF.js.map
11767
+ //# sourceMappingURL=chunk-CZOXTEMN.js.map