@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.
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/{chunk-DMRSJBJF.js → chunk-CZOXTEMN.js} +2709 -599
- package/dist/chunk-CZOXTEMN.js.map +1 -0
- package/dist/{chunk-MGL4U5WA.cjs → chunk-YGIMK4NX.cjs} +2697 -587
- package/dist/chunk-YGIMK4NX.cjs.map +1 -0
- package/dist/{export-DRiLzP_j.d.cts → export-DBba-buV.d.cts} +556 -20
- package/dist/{export-CO2ryu3I.d.ts → export-DNu_Jr8p.d.ts} +556 -20
- package/dist/image-editor/index.cjs +1953 -702
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +253 -9
- package/dist/image-editor/index.d.ts +253 -9
- package/dist/image-editor/index.js +1297 -46
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.d.cts +2 -2
- package/dist/paint-editor/index.d.ts +2 -2
- package/dist/paint-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +27 -30
- package/skills/pixerate-image-editor/references/shapes-and-type.md +60 -0
- package/dist/chunk-DMRSJBJF.js.map +0 -1
- package/dist/chunk-MGL4U5WA.cjs.map +0 -1
|
@@ -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
|
|
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(
|
|
528
|
-
if (dist(
|
|
529
|
-
if (dist(
|
|
530
|
-
if (dist(
|
|
531
|
-
const nearX =
|
|
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(
|
|
536
|
-
if (nearY && Math.abs(
|
|
537
|
-
if (
|
|
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
|
|
2149
|
-
const idx = (y * w +
|
|
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
|
|
2892
|
-
m = new RegExp(`^rgba?\\(\\s*${
|
|
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,]+${
|
|
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
|
|
3132
|
+
const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
|
|
3131
3133
|
const filters = [...layer.filters];
|
|
3132
|
-
filters[i] =
|
|
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)
|
|
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
|
|
3524
|
-
if (!
|
|
3525
|
-
const tile = finishTile(
|
|
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
|
|
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
|
-
|
|
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")
|
|
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)
|
|
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))
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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))
|
|
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))
|
|
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))
|
|
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)
|
|
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
|
|
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)
|
|
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))
|
|
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)
|
|
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)
|
|
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))
|
|
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"))
|
|
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)
|
|
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")
|
|
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")
|
|
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))
|
|
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)
|
|
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)
|
|
4905
|
-
let
|
|
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)
|
|
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")
|
|
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))
|
|
4913
|
-
|
|
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
|
-
|
|
5602
|
+
merged2 = normalizeSmartFilter({ ...current, ...changes, kind: current.kind }, current.id);
|
|
4916
5603
|
}
|
|
4917
5604
|
const filters = [...layer.smartFilters.filters];
|
|
4918
|
-
filters[i] =
|
|
5605
|
+
filters[i] = merged2;
|
|
4919
5606
|
const next = withStack(layer, { ...layer.smartFilters, filters }, canvas);
|
|
4920
|
-
return { layers: mapLayer(layers, id, () => next), layer: next, filter:
|
|
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)
|
|
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)
|
|
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)
|
|
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 =
|
|
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
|
|
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
|
|
5282
|
-
return surfaceFromPixels(
|
|
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 =
|
|
5297
|
-
if (typeof r.feather === "number" && r.feather > 0) mask.feather =
|
|
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:
|
|
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:
|
|
5321
|
-
contrast:
|
|
5322
|
-
saturation:
|
|
5323
|
-
exposure:
|
|
5324
|
-
temperature:
|
|
5325
|
-
tint:
|
|
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:
|
|
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(
|
|
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 =
|
|
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,
|
|
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(
|
|
5418
|
-
const height = Math.round(
|
|
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:
|
|
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 =
|
|
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 =
|
|
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(
|
|
5603
|
-
return
|
|
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(
|
|
6481
|
+
beginLen(long5) {
|
|
5711
6482
|
const at = this.length;
|
|
5712
|
-
if (
|
|
6483
|
+
if (long5) this.u64(0);
|
|
5713
6484
|
else this.u32(0);
|
|
5714
|
-
return { at, long:
|
|
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/
|
|
5760
|
-
var
|
|
5761
|
-
|
|
5762
|
-
|
|
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
|
|
5765
|
-
|
|
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
|
|
5770
|
-
|
|
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
|
|
5773
|
-
const
|
|
5774
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
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
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
return
|
|
6422
|
-
case
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
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
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
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
|
-
|
|
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
|
|
6516
|
-
var
|
|
6517
|
-
var
|
|
6518
|
-
var
|
|
6519
|
-
var
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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(...
|
|
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:
|
|
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 ",
|
|
6803
|
-
["Left",
|
|
6804
|
-
["Btom",
|
|
6805
|
-
["Rght",
|
|
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",
|
|
6809
|
-
["warpValue",
|
|
6810
|
-
["warpPerspective",
|
|
6811
|
-
["warpPerspectiveOther",
|
|
6812
|
-
["warpRotate",
|
|
6813
|
-
["bounds",
|
|
6814
|
-
["uOrder",
|
|
6815
|
-
["vOrder",
|
|
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",
|
|
6824
|
-
const
|
|
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(...
|
|
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 ?
|
|
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
|
|
8882
|
+
return obj2(
|
|
6928
8883
|
descriptor("blendOptions", [
|
|
6929
|
-
["Opct",
|
|
6930
|
-
["Md ",
|
|
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",
|
|
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",
|
|
6964
|
-
list2.push(
|
|
8918
|
+
setItem(d, "enab", boolV2(f.enabled));
|
|
8919
|
+
list2.push(obj2(d));
|
|
6965
8920
|
} else if (f.kind === "blur") {
|
|
6966
|
-
const rgb = (v) =>
|
|
8921
|
+
const rgb = (v) => obj2(descriptor("RGBC", [["Rd ", dbl2(v)], ["Grn ", dbl2(v)], ["Bl ", dbl2(v)]]));
|
|
6967
8922
|
list2.push(
|
|
6968
|
-
|
|
8923
|
+
obj2(
|
|
6969
8924
|
descriptor("filterFX", [
|
|
6970
8925
|
["Nm ", textV(BOX_BLUR.name)],
|
|
6971
8926
|
["blendOptions", blendOptions(f)],
|
|
6972
|
-
["enab",
|
|
6973
|
-
["hasoptions",
|
|
8927
|
+
["enab", boolV2(f.enabled)],
|
|
8928
|
+
["hasoptions", boolV2(true)],
|
|
6974
8929
|
["FrgC", rgb(0)],
|
|
6975
8930
|
["BckC", rgb(255)],
|
|
6976
|
-
["Fltr",
|
|
6977
|
-
["filterID",
|
|
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",
|
|
6985
|
-
["validAtPosition",
|
|
6986
|
-
["filterMaskEnable",
|
|
6987
|
-
["filterMaskLinked",
|
|
6988
|
-
["filterMaskExtendWithWhite",
|
|
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(
|
|
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",
|
|
7012
|
-
setItem(d, "Sz ",
|
|
7013
|
-
if (s.filterFX) setItem(d, "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) =>
|
|
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",
|
|
7021
|
-
["totalPages",
|
|
7022
|
-
["Crop",
|
|
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",
|
|
7026
|
-
["Annt",
|
|
7027
|
-
["Type",
|
|
8980
|
+
["frameCount", long3(1)],
|
|
8981
|
+
["Annt", long3(16)],
|
|
8982
|
+
["Type", long3(2)],
|
|
7028
8983
|
["Trnf", quad],
|
|
7029
8984
|
["nonAffineTransform", quad],
|
|
7030
|
-
["warp",
|
|
7031
|
-
["Sz ",
|
|
7032
|
-
["Rslt",
|
|
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",
|
|
7035
|
-
items.push(["comp",
|
|
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
|
|
7256
|
-
for (let i = 0; i < n; i++)
|
|
7257
|
-
for (const { record, lengths } of
|
|
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:
|
|
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
|
|
7379
|
-
w.ascii(
|
|
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 (
|
|
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
|
|
8357
|
-
var pct = (d, key, fallback) =>
|
|
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
|
|
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
|
|
8374
|
-
var
|
|
8375
|
-
var
|
|
8376
|
-
var
|
|
8377
|
-
var
|
|
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
|
|
10416
|
+
var obj3 = (d) => ({ type: "Objc", value: d });
|
|
8380
10417
|
function blendOf(d, key, fallback, notes) {
|
|
8381
|
-
const id =
|
|
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 ",
|
|
8421
|
-
["Grn ",
|
|
8422
|
-
["Bl ",
|
|
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(
|
|
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",
|
|
8449
|
-
["Vrtc",
|
|
10485
|
+
["Hrzn", dbl3(p.input * 255)],
|
|
10486
|
+
["Vrtc", dbl3(p.output * 255)]
|
|
8450
10487
|
];
|
|
8451
|
-
if (p.corner) items.push(["Cnty",
|
|
8452
|
-
return
|
|
10488
|
+
if (p.corner) items.push(["Cnty", boolV3(false)]);
|
|
10489
|
+
return obj3(descriptor("CrPt", items));
|
|
8453
10490
|
})
|
|
8454
10491
|
};
|
|
8455
|
-
return
|
|
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 ===
|
|
8476
|
-
angle: clamp3(
|
|
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 (
|
|
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 =
|
|
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(
|
|
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(
|
|
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 (
|
|
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) =>
|
|
10556
|
+
(s) => obj3(
|
|
8520
10557
|
descriptor("Clrt", [
|
|
8521
|
-
["Clr ",
|
|
8522
|
-
["Type",
|
|
8523
|
-
["Lctn",
|
|
8524
|
-
["Mdpn",
|
|
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) =>
|
|
10570
|
+
(s) => obj3(
|
|
8534
10571
|
descriptor("TrnS", [
|
|
8535
|
-
["Opct",
|
|
8536
|
-
["Lctn",
|
|
8537
|
-
["Mdpn",
|
|
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",
|
|
8545
|
-
["Intr",
|
|
10581
|
+
["GrdF", enumV3("GrdF", "CstS")],
|
|
10582
|
+
["Intr", dbl3(0)],
|
|
8546
10583
|
["Clrs", clrs],
|
|
8547
10584
|
["Trns", trns]
|
|
8548
10585
|
]);
|
|
8549
10586
|
return [
|
|
8550
|
-
["Grad",
|
|
8551
|
-
["Angl",
|
|
8552
|
-
["Type",
|
|
8553
|
-
["Rvrs",
|
|
8554
|
-
["Algn",
|
|
8555
|
-
["Scl ",
|
|
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
|
-
|
|
10595
|
+
obj3(
|
|
8559
10596
|
descriptor("Pnt ", [
|
|
8560
|
-
["Hrzn",
|
|
8561
|
-
["Vrtc",
|
|
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
|
|
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(
|
|
10619
|
+
angle: clamp3(num3(d, "Angl", 0), -360, 360),
|
|
8583
10620
|
link: descBool(d, "Algn") !== false,
|
|
8584
|
-
phase: phase ? { x:
|
|
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",
|
|
8590
|
-
out.push(["Algn",
|
|
10626
|
+
if (angle) out.push(["Angl", unit3("#Ang", angle)]);
|
|
10627
|
+
out.push(["Algn", boolV3(link)]);
|
|
8591
10628
|
out.push([
|
|
8592
10629
|
"phase",
|
|
8593
|
-
|
|
10630
|
+
obj3(
|
|
8594
10631
|
descriptor("Pnt ", [
|
|
8595
|
-
["Hrzn",
|
|
8596
|
-
["Vrtc",
|
|
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(
|
|
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(
|
|
10663
|
+
angle: clamp3(num3(d, "lagl", 120), -360, 360),
|
|
8627
10664
|
useGlobalLight: descBool(d, "uglg") !== false,
|
|
8628
|
-
distance: clamp3(
|
|
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:
|
|
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:
|
|
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[
|
|
8663
|
-
technique:
|
|
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:
|
|
10702
|
+
direction: enumOf3(d, "bvlD") === "Out " ? "down" : "up",
|
|
8666
10703
|
size: size("blur", 5),
|
|
8667
|
-
soften: clamp3(
|
|
8668
|
-
angle: clamp3(
|
|
8669
|
-
altitude: clamp3(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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",
|
|
8744
|
-
["present",
|
|
8745
|
-
["showInDialog",
|
|
8746
|
-
["Md ",
|
|
8747
|
-
["Opct",
|
|
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 ",
|
|
8758
|
-
["uglg",
|
|
8759
|
-
["lagl",
|
|
8760
|
-
["Dstn",
|
|
8761
|
-
["Ckmt",
|
|
8762
|
-
["blur",
|
|
8763
|
-
["Nose",
|
|
8764
|
-
["AntA",
|
|
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",
|
|
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 ",
|
|
10812
|
+
else items.push(["Clr ", obj3(colorDesc(e.fill.color))]);
|
|
8776
10813
|
items.push(
|
|
8777
|
-
["GlwT",
|
|
8778
|
-
["Ckmt",
|
|
8779
|
-
["blur",
|
|
8780
|
-
["Nose",
|
|
8781
|
-
["ShdN",
|
|
8782
|
-
["AntA",
|
|
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",
|
|
10821
|
+
["Inpr", unit3("#Prc", e.range * 100)]
|
|
8785
10822
|
);
|
|
8786
|
-
if (e.kind === "inner-glow") items.push(["glwS",
|
|
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",
|
|
8794
|
-
["present",
|
|
8795
|
-
["showInDialog",
|
|
8796
|
-
["hglM",
|
|
8797
|
-
["hglC",
|
|
8798
|
-
["hglO",
|
|
8799
|
-
["sdwM",
|
|
8800
|
-
["sdwC",
|
|
8801
|
-
["sdwO",
|
|
8802
|
-
["bvlT",
|
|
8803
|
-
["bvlS",
|
|
8804
|
-
["uglg",
|
|
8805
|
-
["lagl",
|
|
8806
|
-
["Lald",
|
|
8807
|
-
["srgR",
|
|
8808
|
-
["blur",
|
|
8809
|
-
["bvlD",
|
|
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",
|
|
10848
|
+
["Sftn", unit3("#Pxl", e.soften)]
|
|
8812
10849
|
];
|
|
8813
10850
|
if (e.contourElement) {
|
|
8814
|
-
items.push(["useShape",
|
|
8815
|
-
} else items.push(["useShape",
|
|
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",
|
|
8820
|
-
["InvT",
|
|
8821
|
-
["Scl ",
|
|
8822
|
-
["textureDepth",
|
|
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",
|
|
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 ",
|
|
8833
|
-
["AntA",
|
|
8834
|
-
["Invr",
|
|
8835
|
-
["lagl",
|
|
8836
|
-
["Dstn",
|
|
8837
|
-
["blur",
|
|
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 ",
|
|
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",
|
|
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 ",
|
|
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",
|
|
8855
|
-
["Sz ",
|
|
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",
|
|
8859
|
-
else if (f.type === "gradient") items.push(["PntT",
|
|
8860
|
-
else items.push(["PntT",
|
|
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 ",
|
|
8868
|
-
["masterFXSwitch",
|
|
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,
|
|
8876
|
-
else if (multi) items.push([multi, { type: "VlLs", items: of.map(
|
|
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,
|
|
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
|
|
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 =
|
|
10952
|
+
const blur = fixed2(b.u32());
|
|
8916
10953
|
b.u32();
|
|
8917
|
-
const angle =
|
|
8918
|
-
const distance =
|
|
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 =
|
|
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
|
|
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
|
|
9262
|
-
planes.forEach((p, i) =>
|
|
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(
|
|
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-
|
|
11767
|
+
//# sourceMappingURL=chunk-CZOXTEMN.js.map
|