@pixerate/editor 0.110.0 → 0.111.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-HHZ6ZJTW.js} +878 -84
- package/dist/chunk-HHZ6ZJTW.js.map +1 -0
- package/dist/{chunk-MGL4U5WA.cjs → chunk-PHUMLSCW.cjs} +951 -157
- package/dist/chunk-PHUMLSCW.cjs.map +1 -0
- package/dist/{export-CO2ryu3I.d.ts → export-DBNVwb6h.d.ts} +518 -9
- package/dist/{export-DRiLzP_j.d.cts → export-DoDhCszr.d.cts} +518 -9
- package/dist/image-editor/index.cjs +1381 -677
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +97 -9
- package/dist/image-editor/index.d.ts +97 -9
- package/dist/image-editor/index.js +746 -42
- 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
|
|
@@ -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;
|
|
@@ -3887,6 +3889,671 @@ function annotationOfShape(layer) {
|
|
|
3887
3889
|
return out;
|
|
3888
3890
|
}
|
|
3889
3891
|
|
|
3892
|
+
// src/image-editor/layers/text.ts
|
|
3893
|
+
var fail3 = (message) => {
|
|
3894
|
+
throw new LayerOperationError(message);
|
|
3895
|
+
};
|
|
3896
|
+
var DEFAULT_TEXT_STYLE = { fontFamily: "sans-serif", fontWeight: 400, fontSize: 24, color: "#000000" };
|
|
3897
|
+
var DEFAULT_AUTO_LEADING = 1.2;
|
|
3898
|
+
var sameStyle = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3899
|
+
function compact(runs) {
|
|
3900
|
+
const out = [];
|
|
3901
|
+
for (const r of runs) {
|
|
3902
|
+
if (r.length <= 0) continue;
|
|
3903
|
+
const last = out[out.length - 1];
|
|
3904
|
+
if (last && sameStyle(last.style, r.style)) out[out.length - 1] = { ...last, length: last.length + r.length };
|
|
3905
|
+
else out.push({ ...r });
|
|
3906
|
+
}
|
|
3907
|
+
return out;
|
|
3908
|
+
}
|
|
3909
|
+
function fitRuns(runs, length, fallback) {
|
|
3910
|
+
const out = [];
|
|
3911
|
+
let left = length;
|
|
3912
|
+
for (const r of runs) {
|
|
3913
|
+
if (left <= 0) break;
|
|
3914
|
+
const n = Math.min(Math.max(0, Math.floor(r.length)), left);
|
|
3915
|
+
if (n > 0) out.push({ ...r, length: n });
|
|
3916
|
+
left -= n;
|
|
3917
|
+
}
|
|
3918
|
+
if (left > 0) {
|
|
3919
|
+
if (out.length) out[out.length - 1] = { ...out[out.length - 1], length: out[out.length - 1].length + left };
|
|
3920
|
+
else out.push({ length: left, style: runs[runs.length - 1]?.style ?? fallback });
|
|
3921
|
+
}
|
|
3922
|
+
return compact(out);
|
|
3923
|
+
}
|
|
3924
|
+
function styleAt(runs, index, fallback) {
|
|
3925
|
+
let at = 0;
|
|
3926
|
+
for (const r of runs) {
|
|
3927
|
+
if (index < at + r.length) return r.style;
|
|
3928
|
+
at += r.length;
|
|
3929
|
+
}
|
|
3930
|
+
return runs[runs.length - 1]?.style ?? fallback;
|
|
3931
|
+
}
|
|
3932
|
+
function restyleRange(runs, start, end, patch) {
|
|
3933
|
+
const out = [];
|
|
3934
|
+
let at = 0;
|
|
3935
|
+
for (const r of runs) {
|
|
3936
|
+
const a = at;
|
|
3937
|
+
const b = at + r.length;
|
|
3938
|
+
at = b;
|
|
3939
|
+
const lo = Math.max(a, start);
|
|
3940
|
+
const hi = Math.min(b, end);
|
|
3941
|
+
if (hi <= lo) {
|
|
3942
|
+
out.push({ ...r });
|
|
3943
|
+
continue;
|
|
3944
|
+
}
|
|
3945
|
+
if (lo > a) out.push({ length: lo - a, style: r.style });
|
|
3946
|
+
out.push({ length: hi - lo, style: patch(r.style) });
|
|
3947
|
+
if (b > hi) out.push({ length: b - hi, style: r.style });
|
|
3948
|
+
}
|
|
3949
|
+
return compact(out);
|
|
3950
|
+
}
|
|
3951
|
+
function spliceRuns(runs, start, end, inserted, style) {
|
|
3952
|
+
const out = [];
|
|
3953
|
+
let at = 0;
|
|
3954
|
+
let placed = false;
|
|
3955
|
+
for (const r of runs) {
|
|
3956
|
+
const a = at;
|
|
3957
|
+
const b = at + r.length;
|
|
3958
|
+
at = b;
|
|
3959
|
+
if (b <= start) {
|
|
3960
|
+
out.push({ ...r });
|
|
3961
|
+
continue;
|
|
3962
|
+
}
|
|
3963
|
+
if (a < start) out.push({ length: start - a, style: r.style });
|
|
3964
|
+
if (!placed) {
|
|
3965
|
+
out.push({ length: inserted, style });
|
|
3966
|
+
placed = true;
|
|
3967
|
+
}
|
|
3968
|
+
if (b > end) out.push({ length: b - Math.max(end, a), style: r.style });
|
|
3969
|
+
}
|
|
3970
|
+
if (!placed) out.push({ length: inserted, style });
|
|
3971
|
+
return compact(out);
|
|
3972
|
+
}
|
|
3973
|
+
function paragraphRanges(text3) {
|
|
3974
|
+
const out = [];
|
|
3975
|
+
let start = 0;
|
|
3976
|
+
for (let i = 0; i < text3.length; i++) {
|
|
3977
|
+
if (text3[i] === "\n") {
|
|
3978
|
+
out.push({ start, end: i + 1, content: i });
|
|
3979
|
+
start = i + 1;
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
out.push({ start, end: text3.length, content: text3.length });
|
|
3983
|
+
return out;
|
|
3984
|
+
}
|
|
3985
|
+
function fitParagraphRuns(runs, text3) {
|
|
3986
|
+
const fallback = runs[0]?.style ?? {};
|
|
3987
|
+
const out = compact(paragraphRanges(text3).map((p) => ({ length: p.end - p.start, style: styleAt(runs, p.start, fallback) })));
|
|
3988
|
+
return out.length ? out : [{ length: 0, style: fallback }];
|
|
3989
|
+
}
|
|
3990
|
+
function fontOf(style) {
|
|
3991
|
+
const f = { family: style.fontFamily, weight: style.fontWeight, italic: Boolean(style.italic), size: style.fontSize };
|
|
3992
|
+
if (style.postScriptName) f.postScriptName = style.postScriptName;
|
|
3993
|
+
return f;
|
|
3994
|
+
}
|
|
3995
|
+
var isBreakAfter = (c) => c === " " || c === " " || c === "-" || c === "\u2010" || c === "\u200B";
|
|
3996
|
+
var isSpace = (c) => c === " " || c === " ";
|
|
3997
|
+
var isWide = (code) => code >= 11904 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519;
|
|
3998
|
+
var layoutCache = /* @__PURE__ */ new WeakMap();
|
|
3999
|
+
function layoutText(layer, shaper) {
|
|
4000
|
+
let byShaper = layoutCache.get(layer);
|
|
4001
|
+
const hit = byShaper?.get(shaper.id);
|
|
4002
|
+
if (hit) return hit;
|
|
4003
|
+
const out = computeLayout(layer, shaper);
|
|
4004
|
+
if (!byShaper) {
|
|
4005
|
+
byShaper = /* @__PURE__ */ new Map();
|
|
4006
|
+
layoutCache.set(layer, byShaper);
|
|
4007
|
+
}
|
|
4008
|
+
byShaper.set(shaper.id, out);
|
|
4009
|
+
return out;
|
|
4010
|
+
}
|
|
4011
|
+
function computeLayout(layer, shaper) {
|
|
4012
|
+
const { text: text3, frame } = layer;
|
|
4013
|
+
const runs = fitRuns(layer.runs, text3.length, DEFAULT_TEXT_STYLE);
|
|
4014
|
+
const missing = /* @__PURE__ */ new Set();
|
|
4015
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
4016
|
+
const familyOf = (font) => {
|
|
4017
|
+
const key = `${font.family}|${font.weight}|${font.italic}|${font.postScriptName ?? ""}`;
|
|
4018
|
+
let fam = resolved.get(key);
|
|
4019
|
+
if (fam === void 0) {
|
|
4020
|
+
const r = shaper.resolve(font);
|
|
4021
|
+
fam = r.family;
|
|
4022
|
+
if (!r.available) missing.add(font.family);
|
|
4023
|
+
resolved.set(key, fam);
|
|
4024
|
+
}
|
|
4025
|
+
return fam;
|
|
4026
|
+
};
|
|
4027
|
+
const resolvedFont = (style) => ({ ...fontOf(style), family: familyOf(fontOf(style)) });
|
|
4028
|
+
const box2 = frame.kind === "box" ? frame : null;
|
|
4029
|
+
const lines = [];
|
|
4030
|
+
let y = 0;
|
|
4031
|
+
let first = true;
|
|
4032
|
+
const paragraphs = paragraphRanges(text3);
|
|
4033
|
+
paragraphs.forEach((para, pi) => {
|
|
4034
|
+
const pstyle = styleAt(layer.paragraphs, para.start, {});
|
|
4035
|
+
const autoLeading = pstyle.autoLeading ?? DEFAULT_AUTO_LEADING;
|
|
4036
|
+
if (pi > 0) y += pstyle.spaceBefore ?? 0;
|
|
4037
|
+
const chars = [];
|
|
4038
|
+
let i = para.start;
|
|
4039
|
+
while (i < para.content) {
|
|
4040
|
+
const style = styleAt(runs, i, DEFAULT_TEXT_STYLE);
|
|
4041
|
+
let j = i + 1;
|
|
4042
|
+
while (j < para.content && styleAt(runs, j, DEFAULT_TEXT_STYLE) === style) j++;
|
|
4043
|
+
const seg = text3.slice(i, j);
|
|
4044
|
+
const carets = shaper.measure(seg, resolvedFont(style));
|
|
4045
|
+
const track = (style.tracking ?? 0) / 1e3 * style.fontSize;
|
|
4046
|
+
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 });
|
|
4047
|
+
i = j;
|
|
4048
|
+
}
|
|
4049
|
+
const startIndent = pstyle.startIndent ?? 0;
|
|
4050
|
+
const endIndent = pstyle.endIndent ?? 0;
|
|
4051
|
+
const firstIndent = pstyle.firstLineIndent ?? 0;
|
|
4052
|
+
const breaks = [];
|
|
4053
|
+
if (!box2 || !chars.length) breaks.push([0, chars.length]);
|
|
4054
|
+
else {
|
|
4055
|
+
let s = 0;
|
|
4056
|
+
while (s < chars.length) {
|
|
4057
|
+
const avail = box2.width - startIndent - endIndent - (breaks.length === 0 ? firstIndent : 0);
|
|
4058
|
+
let w = 0;
|
|
4059
|
+
let e = s;
|
|
4060
|
+
let lastBreak = -1;
|
|
4061
|
+
while (e < chars.length) {
|
|
4062
|
+
const c = chars[e];
|
|
4063
|
+
if (!isSpace(c.ch) && w + c.advance > avail && e > s) break;
|
|
4064
|
+
w += c.advance;
|
|
4065
|
+
if (isBreakAfter(c.ch) || isWide(c.ch.charCodeAt(0)) || e + 1 < chars.length && isWide(chars[e + 1].ch.charCodeAt(0))) lastBreak = e + 1;
|
|
4066
|
+
e++;
|
|
4067
|
+
}
|
|
4068
|
+
if (e < chars.length && lastBreak > s) e = lastBreak;
|
|
4069
|
+
while (e < chars.length && isSpace(chars[e].ch)) e++;
|
|
4070
|
+
breaks.push([s, e]);
|
|
4071
|
+
s = e;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
const align = pstyle.align ?? "left";
|
|
4075
|
+
breaks.forEach(([s, e], li) => {
|
|
4076
|
+
const lineChars = chars.slice(s, e);
|
|
4077
|
+
let visibleEnd = lineChars.length;
|
|
4078
|
+
while (visibleEnd > 0 && isSpace(lineChars[visibleEnd - 1].ch)) visibleEnd--;
|
|
4079
|
+
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)];
|
|
4080
|
+
let ascent = 0;
|
|
4081
|
+
let descent = 0;
|
|
4082
|
+
let leading = 0;
|
|
4083
|
+
for (const st of styles) {
|
|
4084
|
+
const m = shaper.metrics(resolvedFont(st));
|
|
4085
|
+
ascent = Math.max(ascent, m.ascent);
|
|
4086
|
+
descent = Math.max(descent, m.descent);
|
|
4087
|
+
leading = Math.max(leading, st.leading ?? autoLeading * st.fontSize);
|
|
4088
|
+
}
|
|
4089
|
+
if (first) {
|
|
4090
|
+
y = frame.kind === "box" || frame.kind === "point" && frame.origin === "top" ? ascent : 0;
|
|
4091
|
+
first = false;
|
|
4092
|
+
} else y += leading;
|
|
4093
|
+
const indent = startIndent + (li === 0 ? firstIndent : 0);
|
|
4094
|
+
let width = 0;
|
|
4095
|
+
for (let k2 = 0; k2 < visibleEnd; k2++) width += lineChars[k2].advance;
|
|
4096
|
+
const lastLine = li === breaks.length - 1;
|
|
4097
|
+
let extraPerSpace = 0;
|
|
4098
|
+
if (box2 && align === "justify" && !lastLine) {
|
|
4099
|
+
const spaces = lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
|
|
4100
|
+
const avail = box2.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
|
|
4101
|
+
if (spaces > 0 && avail > width) extraPerSpace = (avail - width) / spaces;
|
|
4102
|
+
}
|
|
4103
|
+
let x0;
|
|
4104
|
+
const fullWidth = width + extraPerSpace * lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
|
|
4105
|
+
if (box2) {
|
|
4106
|
+
const avail = box2.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
|
|
4107
|
+
x0 = indent + (align === "center" ? (avail - fullWidth) / 2 : align === "right" ? avail - fullWidth : 0);
|
|
4108
|
+
} else x0 = indent + (align === "center" ? -fullWidth / 2 : align === "right" ? -fullWidth : 0);
|
|
4109
|
+
const laid = [];
|
|
4110
|
+
let x = x0;
|
|
4111
|
+
for (const c of lineChars) {
|
|
4112
|
+
const adv = c.advance + (isSpace(c.ch) ? extraPerSpace : 0);
|
|
4113
|
+
laid.push({ index: c.index, x, advance: adv });
|
|
4114
|
+
x += adv;
|
|
4115
|
+
}
|
|
4116
|
+
const fragments = [];
|
|
4117
|
+
let k = 0;
|
|
4118
|
+
while (k < lineChars.length) {
|
|
4119
|
+
const st = lineChars[k].style;
|
|
4120
|
+
let m = k + 1;
|
|
4121
|
+
while (m < lineChars.length && lineChars[m].style === st) m++;
|
|
4122
|
+
const font = resolvedFont(st);
|
|
4123
|
+
const fx = laid[k].x;
|
|
4124
|
+
const carets = [0];
|
|
4125
|
+
for (let q2 = k; q2 < m; q2++) carets.push(laid[q2].x + laid[q2].advance - fx);
|
|
4126
|
+
fragments.push({
|
|
4127
|
+
start: lineChars[k].index,
|
|
4128
|
+
end: lineChars[m - 1].index + 1,
|
|
4129
|
+
style: st,
|
|
4130
|
+
font,
|
|
4131
|
+
family: font.family,
|
|
4132
|
+
x: fx,
|
|
4133
|
+
y: y - (st.baselineShift ?? 0),
|
|
4134
|
+
carets,
|
|
4135
|
+
spaced: Boolean(st.tracking) || extraPerSpace > 0
|
|
4136
|
+
});
|
|
4137
|
+
k = m;
|
|
4138
|
+
}
|
|
4139
|
+
const lineStart = lineChars.length ? lineChars[0].index : para.start;
|
|
4140
|
+
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 });
|
|
4141
|
+
});
|
|
4142
|
+
y += pstyle.spaceAfter ?? 0;
|
|
4143
|
+
});
|
|
4144
|
+
let bounds;
|
|
4145
|
+
if (box2) bounds = { x: 0, y: 0, width: box2.width, height: box2.height };
|
|
4146
|
+
else {
|
|
4147
|
+
let x0 = Infinity;
|
|
4148
|
+
let x1 = -Infinity;
|
|
4149
|
+
for (const l of lines) {
|
|
4150
|
+
x0 = Math.min(x0, l.x);
|
|
4151
|
+
x1 = Math.max(x1, l.x + l.width);
|
|
4152
|
+
}
|
|
4153
|
+
if (!Number.isFinite(x0)) x0 = x1 = 0;
|
|
4154
|
+
const top = lines.length ? lines[0].baseline - lines[0].ascent : 0;
|
|
4155
|
+
const last = lines[lines.length - 1];
|
|
4156
|
+
const bottom = last ? last.baseline + last.descent : 0;
|
|
4157
|
+
bounds = { x: x0, y: top, width: Math.max(0, x1 - x0), height: Math.max(0, bottom - top) };
|
|
4158
|
+
}
|
|
4159
|
+
return { lines, bounds, missingFonts: [...missing] };
|
|
4160
|
+
}
|
|
4161
|
+
function textIndexAt(layout, p) {
|
|
4162
|
+
if (!layout.lines.length) return 0;
|
|
4163
|
+
let line = layout.lines[layout.lines.length - 1];
|
|
4164
|
+
for (const l of layout.lines) {
|
|
4165
|
+
if (p.y <= l.baseline + l.descent) {
|
|
4166
|
+
line = l;
|
|
4167
|
+
break;
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
if (!line.chars.length) return line.start;
|
|
4171
|
+
for (const c of line.chars) if (p.x < c.x + c.advance / 2) return c.index;
|
|
4172
|
+
const lastChar = line.chars[line.chars.length - 1];
|
|
4173
|
+
return lastChar.index + 1;
|
|
4174
|
+
}
|
|
4175
|
+
function caretAt(layout, index) {
|
|
4176
|
+
const lines = layout.lines;
|
|
4177
|
+
if (!lines.length) return { x: 0, top: 0, bottom: 0, line: 0 };
|
|
4178
|
+
for (let li = 0; li < lines.length; li++) {
|
|
4179
|
+
const l2 = lines[li];
|
|
4180
|
+
const next = lines[li + 1];
|
|
4181
|
+
if (next && index >= next.start && (next.chars.length || index > l2.end || next.start > l2.end)) continue;
|
|
4182
|
+
const c = l2.chars.find((ch) => ch.index === index);
|
|
4183
|
+
const prev = l2.chars.find((ch) => ch.index === index - 1);
|
|
4184
|
+
const x = c ? c.x : prev ? prev.x + prev.advance : l2.x;
|
|
4185
|
+
return { x, top: l2.baseline - l2.ascent, bottom: l2.baseline + l2.descent, line: li };
|
|
4186
|
+
}
|
|
4187
|
+
const l = lines[lines.length - 1];
|
|
4188
|
+
return { x: l.x + l.width, top: l.baseline - l.ascent, bottom: l.baseline + l.descent, line: lines.length - 1 };
|
|
4189
|
+
}
|
|
4190
|
+
var TEXT_RENDER_MARGIN = MAX_EFFECT_REACH;
|
|
4191
|
+
function textMatrix(layer) {
|
|
4192
|
+
return layer.transform ?? IDENTITY_MATRIX;
|
|
4193
|
+
}
|
|
4194
|
+
function rectCorners(r) {
|
|
4195
|
+
return [
|
|
4196
|
+
{ x: r.x, y: r.y },
|
|
4197
|
+
{ x: r.x + r.width, y: r.y },
|
|
4198
|
+
{ x: r.x + r.width, y: r.y + r.height },
|
|
4199
|
+
{ x: r.x, y: r.y + r.height }
|
|
4200
|
+
];
|
|
4201
|
+
}
|
|
4202
|
+
function docRect(r, m) {
|
|
4203
|
+
const pts = rectCorners(r).map((p) => applyMatrix(m, p));
|
|
4204
|
+
const x0 = Math.floor(Math.min(...pts.map((p) => p.x)));
|
|
4205
|
+
const y0 = Math.floor(Math.min(...pts.map((p) => p.y)));
|
|
4206
|
+
const x1 = Math.ceil(Math.max(...pts.map((p) => p.x)));
|
|
4207
|
+
const y1 = Math.ceil(Math.max(...pts.map((p) => p.y)));
|
|
4208
|
+
return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
4209
|
+
}
|
|
4210
|
+
function intersect2(a, b) {
|
|
4211
|
+
const x0 = Math.max(a.x, b.x);
|
|
4212
|
+
const y0 = Math.max(a.y, b.y);
|
|
4213
|
+
const x1 = Math.min(a.x + a.width, b.x + b.width);
|
|
4214
|
+
const y1 = Math.min(a.y + a.height, b.y + b.height);
|
|
4215
|
+
return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
|
|
4216
|
+
}
|
|
4217
|
+
function inkBounds(layout) {
|
|
4218
|
+
let b = null;
|
|
4219
|
+
for (const l of layout.lines) {
|
|
4220
|
+
for (const f of l.fragments) {
|
|
4221
|
+
const pad = f.style.fontSize * 0.5;
|
|
4222
|
+
const w = f.carets[f.carets.length - 1];
|
|
4223
|
+
const r = { x: f.x - pad, y: f.y - l.ascent - pad, width: w + 2 * pad, height: l.ascent + l.descent + 2 * pad };
|
|
4224
|
+
if (!b) b = r;
|
|
4225
|
+
else {
|
|
4226
|
+
const x0 = Math.min(b.x, r.x);
|
|
4227
|
+
const y0 = Math.min(b.y, r.y);
|
|
4228
|
+
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 };
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
return b;
|
|
4233
|
+
}
|
|
4234
|
+
function decorations(f, l) {
|
|
4235
|
+
const out = [];
|
|
4236
|
+
const t = Math.max(1, f.style.fontSize / 15);
|
|
4237
|
+
const w = f.carets[f.carets.length - 1];
|
|
4238
|
+
if (f.style.underline) out.push({ x: f.x, y: f.y + Math.max(t, l.descent * 0.35), width: w, height: t });
|
|
4239
|
+
if (f.style.strikethrough) out.push({ x: f.x, y: f.y - l.ascent * 0.3 - t / 2, width: w, height: t });
|
|
4240
|
+
return out;
|
|
4241
|
+
}
|
|
4242
|
+
var rectPath = (r) => [
|
|
4243
|
+
{ type: "M", x: r.x, y: r.y },
|
|
4244
|
+
{ type: "L", x: r.x + r.width, y: r.y },
|
|
4245
|
+
{ type: "L", x: r.x + r.width, y: r.y + r.height },
|
|
4246
|
+
{ type: "L", x: r.x, y: r.y + r.height },
|
|
4247
|
+
{ type: "Z" }
|
|
4248
|
+
];
|
|
4249
|
+
function fragmentOutline(shaper, text3, f) {
|
|
4250
|
+
const s = text3.slice(f.start, f.end);
|
|
4251
|
+
if (!f.spaced) return shaper.outline(s, f.font, f.x, f.y);
|
|
4252
|
+
const out = [];
|
|
4253
|
+
for (let k = 0; k < s.length; k++) {
|
|
4254
|
+
if (isSpace(s[k])) continue;
|
|
4255
|
+
out.push(...shaper.outline(s[k], f.font, f.x + f.carets[k], f.y));
|
|
4256
|
+
}
|
|
4257
|
+
return out;
|
|
4258
|
+
}
|
|
4259
|
+
function textOutlines(layer, shaper) {
|
|
4260
|
+
if (!shaper.outline) fail3(`The text shaper "${shaper.id}" cannot give glyph outlines; plug in an outline shaper (createOutlineTextShaper) to convert text to shapes.`);
|
|
4261
|
+
const layout = layoutText(layer, shaper);
|
|
4262
|
+
const m = textMatrix(layer);
|
|
4263
|
+
const out = [];
|
|
4264
|
+
for (const l of layout.lines) {
|
|
4265
|
+
for (const f of l.fragments) {
|
|
4266
|
+
let path = fragmentOutline(shaper, layer.text, f);
|
|
4267
|
+
for (const r of decorations(f, l)) path = [...path, ...rectPath(r)];
|
|
4268
|
+
out.push({ fragment: f, path: isIdentityMatrix(m) ? path : transformBezierPath(path, m) });
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
return out;
|
|
4272
|
+
}
|
|
4273
|
+
var rgbOf = (hex) => {
|
|
4274
|
+
const c = parseCssColor(hex);
|
|
4275
|
+
const h = (c?.hex ?? "#000000").slice(1);
|
|
4276
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
4277
|
+
};
|
|
4278
|
+
function renderOutlines(layer, shaper, region) {
|
|
4279
|
+
const parts = textOutlines(layer, shaper).map(({ fragment, path }) => ({ style: fragment.style, flat: flattenPath(path) }));
|
|
4280
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
4281
|
+
for (const { tx, ty } of tilesInRect(region)) {
|
|
4282
|
+
const rect = { x: tx * TILE, y: ty * TILE, width: TILE, height: TILE };
|
|
4283
|
+
const n = TILE * TILE;
|
|
4284
|
+
const acc = new Float32Array(n * 4);
|
|
4285
|
+
let any = false;
|
|
4286
|
+
for (const p of parts) {
|
|
4287
|
+
const cov = rasterizeCoverageSampled(p.flat, rect, "nonzero");
|
|
4288
|
+
if (!cov) continue;
|
|
4289
|
+
const [r, g, b] = rgbOf(p.style.color);
|
|
4290
|
+
const op = p.style.opacity ?? 1;
|
|
4291
|
+
for (let i = 0; i < n; i++) {
|
|
4292
|
+
const sa = layer.antiAlias === false ? cov[i] >= 0.5 ? op : 0 : cov[i] * op;
|
|
4293
|
+
if (!(sa > 0)) continue;
|
|
4294
|
+
const k = i * 4;
|
|
4295
|
+
const da = acc[k + 3];
|
|
4296
|
+
const ao = sa + da * (1 - sa);
|
|
4297
|
+
acc[k] = (r * sa + acc[k] * da * (1 - sa)) / ao;
|
|
4298
|
+
acc[k + 1] = (g * sa + acc[k + 1] * da * (1 - sa)) / ao;
|
|
4299
|
+
acc[k + 2] = (b * sa + acc[k + 2] * da * (1 - sa)) / ao;
|
|
4300
|
+
acc[k + 3] = ao;
|
|
4301
|
+
any = true;
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
if (!any) continue;
|
|
4305
|
+
const px = new Uint8ClampedArray(n * 4);
|
|
4306
|
+
for (let i = 0; i < n * 4; i += 4) {
|
|
4307
|
+
const a = Math.round(acc[i + 3] * 255);
|
|
4308
|
+
if (!a) continue;
|
|
4309
|
+
px[i] = Math.round(acc[i]);
|
|
4310
|
+
px[i + 1] = Math.round(acc[i + 1]);
|
|
4311
|
+
px[i + 2] = Math.round(acc[i + 2]);
|
|
4312
|
+
px[i + 3] = a;
|
|
4313
|
+
}
|
|
4314
|
+
const tile = finishTile(px, 4, 0);
|
|
4315
|
+
if (tile) tiles.set(tileKey(tx, ty), tile);
|
|
4316
|
+
}
|
|
4317
|
+
return new TiledSurface(4, tiles, 0);
|
|
4318
|
+
}
|
|
4319
|
+
function renderOnCanvas(layer, shaper, region, env) {
|
|
4320
|
+
if (!env.createCanvas) return null;
|
|
4321
|
+
const canvas = env.createCanvas(region.width, region.height);
|
|
4322
|
+
const ctx = canvas.getContext("2d");
|
|
4323
|
+
if (!ctx || typeof ctx.getImageData !== "function") return null;
|
|
4324
|
+
const layout = layoutText(layer, shaper);
|
|
4325
|
+
const m = textMatrix(layer);
|
|
4326
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4327
|
+
ctx.clearRect(0, 0, region.width, region.height);
|
|
4328
|
+
ctx.setTransform(m[0], m[1], m[2], m[3], m[4] - region.x, m[5] - region.y);
|
|
4329
|
+
for (const l of layout.lines) {
|
|
4330
|
+
for (const f of l.fragments) {
|
|
4331
|
+
const [r, g, b] = rgbOf(f.style.color);
|
|
4332
|
+
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
|
4333
|
+
ctx.globalAlpha = f.style.opacity ?? 1;
|
|
4334
|
+
const s = layer.text.slice(f.start, f.end);
|
|
4335
|
+
if (!f.spaced) shaper.draw(ctx, s, f.font, f.x, f.y);
|
|
4336
|
+
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);
|
|
4337
|
+
for (const d of decorations(f, l)) ctx.fillRect(d.x, d.y, d.width, d.height);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
ctx.globalAlpha = 1;
|
|
4341
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4342
|
+
const data = ctx.getImageData(0, 0, region.width, region.height).data;
|
|
4343
|
+
return surfaceFromPixels({ width: region.width, height: region.height, data: new Uint8ClampedArray(data) }, 4, region.x, region.y);
|
|
4344
|
+
}
|
|
4345
|
+
function renderText(layer, env) {
|
|
4346
|
+
const shaper = env.shaper;
|
|
4347
|
+
if (!shaper || !shaper.outline && !shaper.draw) return null;
|
|
4348
|
+
const layout = layoutText(layer, shaper);
|
|
4349
|
+
const ink = inkBounds(layout);
|
|
4350
|
+
const clip = { x: -TEXT_RENDER_MARGIN, y: -TEXT_RENDER_MARGIN, width: env.width + 2 * TEXT_RENDER_MARGIN, height: env.height + 2 * TEXT_RENDER_MARGIN };
|
|
4351
|
+
const region = ink ? intersect2(docRect(ink, textMatrix(layer)), clip) : null;
|
|
4352
|
+
if (!region) return { surface: emptySurface(4), missingFonts: layout.missingFonts };
|
|
4353
|
+
if (shaper.outline) return { surface: renderOutlines(layer, shaper, region), missingFonts: layout.missingFonts };
|
|
4354
|
+
const surface = renderOnCanvas(layer, shaper, region, env);
|
|
4355
|
+
return surface ? { surface, missingFonts: layout.missingFonts } : null;
|
|
4356
|
+
}
|
|
4357
|
+
function rerenderText(layer, env) {
|
|
4358
|
+
const r = renderText(layer, env);
|
|
4359
|
+
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.");
|
|
4360
|
+
const next = { ...layer, surface: r.surface };
|
|
4361
|
+
delete next.rendering;
|
|
4362
|
+
if (r.missingFonts.length) next.missingFonts = r.missingFonts;
|
|
4363
|
+
else delete next.missingFonts;
|
|
4364
|
+
return next;
|
|
4365
|
+
}
|
|
4366
|
+
var isNum2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
4367
|
+
function num(v, path, min, max) {
|
|
4368
|
+
if (!isNum2(v) || v < min || v > max) fail3(`${path} must be a number from ${min} to ${max}, got ${JSON.stringify(v)}.`);
|
|
4369
|
+
return v;
|
|
4370
|
+
}
|
|
4371
|
+
var TEXT_STYLE_FIELDS = ["fontFamily", "fontWeight", "italic", "fontSize", "color", "opacity", "tracking", "leading", "baselineShift", "underline", "strikethrough"];
|
|
4372
|
+
function patchTextStyle(base, patch, path = "style") {
|
|
4373
|
+
const out = { ...base };
|
|
4374
|
+
const o = out;
|
|
4375
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
4376
|
+
if (v === void 0) continue;
|
|
4377
|
+
switch (k) {
|
|
4378
|
+
case "fontFamily":
|
|
4379
|
+
if (typeof v !== "string" || !v.trim()) fail3(`${path}.fontFamily must be a font family name.`);
|
|
4380
|
+
out.fontFamily = v.trim();
|
|
4381
|
+
if (out.postScriptName) delete out.postScriptName;
|
|
4382
|
+
break;
|
|
4383
|
+
case "fontWeight": {
|
|
4384
|
+
const w = v === "normal" ? 400 : v === "bold" ? 700 : v;
|
|
4385
|
+
out.fontWeight = Math.round(num(w, `${path}.fontWeight`, 1, 1e3));
|
|
4386
|
+
if (out.postScriptName) delete out.postScriptName;
|
|
4387
|
+
break;
|
|
4388
|
+
}
|
|
4389
|
+
case "italic":
|
|
4390
|
+
case "underline":
|
|
4391
|
+
case "strikethrough":
|
|
4392
|
+
if (typeof v !== "boolean" && v !== null) fail3(`${path}.${k} must be true or false.`);
|
|
4393
|
+
if (v) o[k] = true;
|
|
4394
|
+
else delete o[k];
|
|
4395
|
+
if (k === "italic" && out.postScriptName) delete out.postScriptName;
|
|
4396
|
+
break;
|
|
4397
|
+
case "fontSize":
|
|
4398
|
+
out.fontSize = num(v, `${path}.fontSize`, 0.5, 5e3);
|
|
4399
|
+
break;
|
|
4400
|
+
case "color": {
|
|
4401
|
+
const c = parseCssColor(v);
|
|
4402
|
+
if (!c) fail3(`${path}.color must be a colour such as #rrggbb.`);
|
|
4403
|
+
out.color = c.hex;
|
|
4404
|
+
if (c.alpha < 1) out.opacity = c.alpha;
|
|
4405
|
+
break;
|
|
4406
|
+
}
|
|
4407
|
+
case "opacity":
|
|
4408
|
+
case "tracking":
|
|
4409
|
+
case "leading":
|
|
4410
|
+
case "baselineShift": {
|
|
4411
|
+
if (v === null) {
|
|
4412
|
+
delete o[k];
|
|
4413
|
+
break;
|
|
4414
|
+
}
|
|
4415
|
+
const [min, max] = k === "opacity" ? [0, 1] : k === "tracking" ? [-1e3, 1e4] : k === "leading" ? [0, 1e4] : [-5e3, 5e3];
|
|
4416
|
+
const value = num(v, `${path}.${k}`, min, max);
|
|
4417
|
+
if (k === "opacity" && value === 1 || (k === "tracking" || k === "baselineShift") && value === 0) delete o[k];
|
|
4418
|
+
else o[k] = value;
|
|
4419
|
+
break;
|
|
4420
|
+
}
|
|
4421
|
+
default:
|
|
4422
|
+
fail3(`${path}.${k} is not a character style field; use ${TEXT_STYLE_FIELDS.join(", ")}.`);
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
return out;
|
|
4426
|
+
}
|
|
4427
|
+
var PARAGRAPH_STYLE_FIELDS = ["align", "autoLeading", "firstLineIndent", "startIndent", "endIndent", "spaceBefore", "spaceAfter"];
|
|
4428
|
+
function patchParagraphStyle(base, patch, path = "paragraph") {
|
|
4429
|
+
const out = { ...base };
|
|
4430
|
+
const o = out;
|
|
4431
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
4432
|
+
if (v === void 0) continue;
|
|
4433
|
+
if (k === "align") {
|
|
4434
|
+
if (!TEXT_ALIGNS.includes(v)) fail3(`${path}.align must be one of ${TEXT_ALIGNS.join(", ")}.`);
|
|
4435
|
+
if (v === "left") delete out.align;
|
|
4436
|
+
else out.align = v;
|
|
4437
|
+
} else if (PARAGRAPH_STYLE_FIELDS.includes(k)) {
|
|
4438
|
+
if (v === null) {
|
|
4439
|
+
delete o[k];
|
|
4440
|
+
continue;
|
|
4441
|
+
}
|
|
4442
|
+
const value = k === "autoLeading" ? num(v, `${path}.${k}`, 0.1, 10) : num(v, `${path}.${k}`, -1e4, 1e4);
|
|
4443
|
+
if (k === "autoLeading" && value === DEFAULT_AUTO_LEADING || k !== "autoLeading" && value === 0) delete o[k];
|
|
4444
|
+
else o[k] = value;
|
|
4445
|
+
} else fail3(`${path}.${k} is not a paragraph style field; use ${PARAGRAPH_STYLE_FIELDS.join(", ")}.`);
|
|
4446
|
+
}
|
|
4447
|
+
return out;
|
|
4448
|
+
}
|
|
4449
|
+
function textRange(text3, start, end) {
|
|
4450
|
+
const s = start === void 0 ? 0 : start;
|
|
4451
|
+
const e = end === void 0 ? text3.length : end;
|
|
4452
|
+
if (!Number.isInteger(s) || !Number.isInteger(e) || s < 0 || e > text3.length || s > e) {
|
|
4453
|
+
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)}.`);
|
|
4454
|
+
}
|
|
4455
|
+
return [s, e];
|
|
4456
|
+
}
|
|
4457
|
+
function requireTypeLayer(layers, id) {
|
|
4458
|
+
const layer = requireLayer(layers, id).layer;
|
|
4459
|
+
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.`);
|
|
4460
|
+
return layer;
|
|
4461
|
+
}
|
|
4462
|
+
function normalizeText(text3, path = "text") {
|
|
4463
|
+
if (typeof text3 !== "string") return fail3(`${path} must be a string.`);
|
|
4464
|
+
return text3.replace(/\r\n?/g, "\n");
|
|
4465
|
+
}
|
|
4466
|
+
function typeLayerName(text3) {
|
|
4467
|
+
const line = text3.split("\n").find((l) => l.trim()) ?? "";
|
|
4468
|
+
return line.trim().slice(0, 32) || "Text";
|
|
4469
|
+
}
|
|
4470
|
+
function createTypeLayer(init, env, id = newLayerId(), lenient = false) {
|
|
4471
|
+
const text3 = normalizeText(init.text);
|
|
4472
|
+
const layer = {
|
|
4473
|
+
type: "type",
|
|
4474
|
+
id,
|
|
4475
|
+
name: init.name ?? typeLayerName(text3),
|
|
4476
|
+
visible: true,
|
|
4477
|
+
opacity: 1,
|
|
4478
|
+
blend: "normal",
|
|
4479
|
+
text: text3,
|
|
4480
|
+
runs: fitRuns(init.runs, text3.length, DEFAULT_TEXT_STYLE),
|
|
4481
|
+
paragraphs: fitParagraphRuns(init.paragraphs ?? [{ length: text3.length, style: {} }], text3),
|
|
4482
|
+
frame: init.frame,
|
|
4483
|
+
surface: emptySurface(4),
|
|
4484
|
+
offset: { x: 0, y: 0 }
|
|
4485
|
+
};
|
|
4486
|
+
if (!layer.runs.length) layer.runs = [{ length: 0, style: init.runs[0]?.style ?? DEFAULT_TEXT_STYLE }];
|
|
4487
|
+
if (init.transform && !isIdentityMatrix(init.transform)) layer.transform = init.transform;
|
|
4488
|
+
if (lenient && !renderText(layer, env)) return { ...layer, rendering: "stored" };
|
|
4489
|
+
return rerenderText(layer, env);
|
|
4490
|
+
}
|
|
4491
|
+
function updateTypeLayer(layers, id, change, env, action = "edit it") {
|
|
4492
|
+
const current = requireTypeLayer(layers, id);
|
|
4493
|
+
requireUnlocked(current, "pixels", action);
|
|
4494
|
+
const patch = change(current);
|
|
4495
|
+
const next = { ...current, ...patch };
|
|
4496
|
+
if (!("annotation" in patch)) delete next.annotation;
|
|
4497
|
+
if ("text" in patch || "runs" in patch) next.runs = fitRuns(next.runs, next.text.length, current.runs[0]?.style ?? DEFAULT_TEXT_STYLE);
|
|
4498
|
+
if (!next.runs.length) next.runs = [{ length: 0, style: styleAt(current.runs, 0, DEFAULT_TEXT_STYLE) }];
|
|
4499
|
+
if ("text" in patch || "paragraphs" in patch) next.paragraphs = fitParagraphRuns(next.paragraphs, next.text);
|
|
4500
|
+
const rendered = rerenderText(next, env);
|
|
4501
|
+
return { layers: mapLayer(layers, id, () => rendered), layer: rendered };
|
|
4502
|
+
}
|
|
4503
|
+
function replaceTextRange(layer, start, end, insert) {
|
|
4504
|
+
const text3 = layer.text.slice(0, start) + insert + layer.text.slice(end);
|
|
4505
|
+
const style = styleAt(layer.runs, start > 0 && start === layer.text.length ? start - 1 : start, DEFAULT_TEXT_STYLE);
|
|
4506
|
+
const runs = spliceRuns(layer.runs, start, end, insert.length, style);
|
|
4507
|
+
const pstyle = styleAt(layer.paragraphs, start, {});
|
|
4508
|
+
const paragraphs = spliceRuns(layer.paragraphs, start, end, insert.length, pstyle);
|
|
4509
|
+
return { text: text3, runs, paragraphs: fitParagraphRuns(paragraphs, text3) };
|
|
4510
|
+
}
|
|
4511
|
+
function transformTypeLayer(layer, m) {
|
|
4512
|
+
const t = multiplyMatrices(textMatrix(layer), m);
|
|
4513
|
+
const next = { ...layer };
|
|
4514
|
+
if (isIdentityMatrix(t)) delete next.transform;
|
|
4515
|
+
else next.transform = t;
|
|
4516
|
+
delete next.annotation;
|
|
4517
|
+
return next;
|
|
4518
|
+
}
|
|
4519
|
+
function typeFrame(layer, shaper) {
|
|
4520
|
+
if (layer.frame.kind === "box") return { x: 0, y: 0, width: layer.frame.width, height: layer.frame.height };
|
|
4521
|
+
if (!shaper) return fail3("Type layers need a text shaper to be measured; none is set here.");
|
|
4522
|
+
const b = layoutText(layer, shaper).bounds;
|
|
4523
|
+
return { x: b.x, y: b.y, width: Math.max(b.width, 1), height: Math.max(b.height, 1) };
|
|
4524
|
+
}
|
|
4525
|
+
function typeAsPixelLayer(layer) {
|
|
4526
|
+
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;
|
|
4527
|
+
return { ...rest, type: "pixel", surface: layer.surface, offset: { x: 0, y: 0 } };
|
|
4528
|
+
}
|
|
4529
|
+
function toTextSpace(layer, p) {
|
|
4530
|
+
return applyMatrix(invertMatrix(textMatrix(layer)), p);
|
|
4531
|
+
}
|
|
4532
|
+
function typeFromAnnotation(a) {
|
|
4533
|
+
if (a.type !== "text") return null;
|
|
4534
|
+
const t = a;
|
|
4535
|
+
const c = parseCssColor(t.color);
|
|
4536
|
+
if (!c) return null;
|
|
4537
|
+
const weight = t.fontWeight === void 0 || t.fontWeight === "normal" ? 400 : t.fontWeight === "bold" ? 700 : Number(t.fontWeight);
|
|
4538
|
+
if (!Number.isFinite(weight)) return null;
|
|
4539
|
+
const style = { fontFamily: t.fontFamily || "sans-serif", fontWeight: weight, fontSize: t.fontSize, color: c.hex };
|
|
4540
|
+
const op = c.alpha * (t.opacity ?? 1);
|
|
4541
|
+
if (op !== 1) style.opacity = op;
|
|
4542
|
+
const text3 = t.text.replace(/\r?\n|\r/g, " ");
|
|
4543
|
+
const translate = [1, 0, 0, 1, t.x, t.y];
|
|
4544
|
+
const transform = t.rotation ? multiplyMatrices(translate, rotateMatrix(t.rotation, { x: t.x, y: t.y })) : translate;
|
|
4545
|
+
return { text: text3, runs: [{ length: text3.length, style }], frame: { kind: "point", origin: "top" }, transform };
|
|
4546
|
+
}
|
|
4547
|
+
function annotationOfType(layer) {
|
|
4548
|
+
const a = layer.annotation;
|
|
4549
|
+
if (!a) return null;
|
|
4550
|
+
if ((a.blend ?? "normal") === layer.blend) return a;
|
|
4551
|
+
const out = { ...a };
|
|
4552
|
+
if (layer.blend === "normal") delete out.blend;
|
|
4553
|
+
else out.blend = layer.blend;
|
|
4554
|
+
return out;
|
|
4555
|
+
}
|
|
4556
|
+
|
|
3890
4557
|
// src/image-editor/layers/defaults.ts
|
|
3891
4558
|
var DEFAULT_ADJUSTMENTS_VALUES = {
|
|
3892
4559
|
brightness: 0,
|
|
@@ -3988,6 +4655,12 @@ function annotationLayerName(annotation) {
|
|
|
3988
4655
|
}
|
|
3989
4656
|
}
|
|
3990
4657
|
function annotationLayerFor(annotation, canvas) {
|
|
4658
|
+
const blend = annotation.blend && annotation.blend !== "normal" ? annotation.blend : "normal";
|
|
4659
|
+
const type = canvas ? typeFromAnnotation(annotation) : null;
|
|
4660
|
+
if (type) {
|
|
4661
|
+
const layer = createTypeLayer({ ...type, name: annotationLayerName(annotation) }, { shaper: null, ...canvas }, annotation.id, true);
|
|
4662
|
+
return { ...layer, blend, annotation };
|
|
4663
|
+
}
|
|
3991
4664
|
const shape = canvas ? shapeFromAnnotation(annotation) : null;
|
|
3992
4665
|
if (shape) {
|
|
3993
4666
|
const layer = createShapeLayer(
|
|
@@ -4010,6 +4683,7 @@ function annotationLayerFor(annotation, canvas) {
|
|
|
4010
4683
|
function annotationOfLayer(layer) {
|
|
4011
4684
|
if (layer.type === "annotation") return layer.annotation;
|
|
4012
4685
|
if (layer.type === "shape") return annotationOfShape(layer);
|
|
4686
|
+
if (layer.type === "type") return annotationOfType(layer);
|
|
4013
4687
|
return null;
|
|
4014
4688
|
}
|
|
4015
4689
|
function adjustmentNodeFromLegacy(layer, width, height) {
|
|
@@ -4166,7 +4840,7 @@ function isLegacyShape(stack) {
|
|
|
4166
4840
|
}
|
|
4167
4841
|
for (; i < layers.length; i++) {
|
|
4168
4842
|
const l = layers[i];
|
|
4169
|
-
if (l.type === "shape") {
|
|
4843
|
+
if (l.type === "shape" || l.type === "type") {
|
|
4170
4844
|
const a = l.annotation;
|
|
4171
4845
|
if (!a || (a.blend ?? "normal") !== l.blend || !isPlain({ ...l, blend: "normal" }) || l.fillOpacity !== void 0) return false;
|
|
4172
4846
|
if (l.id !== a.id || l.name !== annotationLayerName(a)) return false;
|
|
@@ -4338,7 +5012,7 @@ function geometryHomography(geometry) {
|
|
|
4338
5012
|
}
|
|
4339
5013
|
|
|
4340
5014
|
// src/image-editor/layers/smart.ts
|
|
4341
|
-
var
|
|
5015
|
+
var fail4 = (message) => {
|
|
4342
5016
|
throw new LayerOperationError(message);
|
|
4343
5017
|
};
|
|
4344
5018
|
var SMART_RENDER_MARGIN = MAX_EFFECT_REACH;
|
|
@@ -4360,13 +5034,13 @@ function isContentsDecoded(contents) {
|
|
|
4360
5034
|
function requireDecoded(layer, action) {
|
|
4361
5035
|
if (isContentsDecoded(layer.contents)) return;
|
|
4362
5036
|
const what = layer.contents.file?.type ? `a ${layer.contents.file.type.toUpperCase()} file` : "a file";
|
|
4363
|
-
|
|
5037
|
+
fail4(
|
|
4364
5038
|
`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
5039
|
);
|
|
4366
5040
|
}
|
|
4367
5041
|
function requireSmartObject(layers, id) {
|
|
4368
5042
|
const layer = requireLayer(layers, id).layer;
|
|
4369
|
-
if (layer.type !== "smart")
|
|
5043
|
+
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
5044
|
return layer;
|
|
4371
5045
|
}
|
|
4372
5046
|
var contentsPixels = /* @__PURE__ */ new WeakMap();
|
|
@@ -4392,11 +5066,11 @@ function contentsSurface(contents) {
|
|
|
4392
5066
|
return s;
|
|
4393
5067
|
}
|
|
4394
5068
|
function rasterContents(pixels, name, id = newContentsId()) {
|
|
4395
|
-
if (!(pixels.width > 0 && pixels.height > 0) || pixels.data.length < pixels.width * pixels.height * 4)
|
|
5069
|
+
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
5070
|
return { id, name, width: pixels.width, height: pixels.height, kind: "raster", raster: surfaceFromPixels(pixels, 4, 0, 0) };
|
|
4397
5071
|
}
|
|
4398
5072
|
function documentContents(document, width, height, name, id = newContentsId()) {
|
|
4399
|
-
if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0))
|
|
5073
|
+
if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0)) fail4("Contents width and height must be positive whole numbers.");
|
|
4400
5074
|
return { id, name, width, height, kind: "document", document };
|
|
4401
5075
|
}
|
|
4402
5076
|
var placedCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4457,16 +5131,16 @@ function placeContents(layer, canvas, options = {}) {
|
|
|
4457
5131
|
try {
|
|
4458
5132
|
if (hasWarp(g)) {
|
|
4459
5133
|
const f = transformMap(g);
|
|
4460
|
-
if (!f)
|
|
5134
|
+
if (!f) fail4("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
|
|
4461
5135
|
out = warpSurface(source, (p) => f({ x: p.x * k, y: p.y * k }), { interpolation, source: box2, clip, step: step > 1 ? step : void 0 });
|
|
4462
5136
|
} else {
|
|
4463
5137
|
const h = transformHomography(g);
|
|
4464
|
-
if (!h)
|
|
5138
|
+
if (!h) fail4("The placement folds the contents over itself or through the horizon; keep the corners a convex quad.");
|
|
4465
5139
|
const hk = multiplyHomographies([k, 0, 0, 0, k, 0, 0, 0, 1], h);
|
|
4466
5140
|
out = transformSurface(source, hk, { interpolation, source: box2, clip, step: step > 1 ? step : void 0 });
|
|
4467
5141
|
}
|
|
4468
5142
|
} catch (e) {
|
|
4469
|
-
if (e instanceof TransformTooLargeError)
|
|
5143
|
+
if (e instanceof TransformTooLargeError) fail4(e.message);
|
|
4470
5144
|
throw e;
|
|
4471
5145
|
}
|
|
4472
5146
|
}
|
|
@@ -4559,19 +5233,19 @@ function rerender(layer, canvas, action = "change") {
|
|
|
4559
5233
|
function normalizePlacement(raw, contents) {
|
|
4560
5234
|
const c = raw?.corners;
|
|
4561
5235
|
if (!Array.isArray(c) || c.length !== 4 || !c.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) {
|
|
4562
|
-
|
|
5236
|
+
fail4("corners must be four finite points [top-left, top-right, bottom-right, bottom-left] in document pixels.");
|
|
4563
5237
|
}
|
|
4564
5238
|
const out = { corners: c.map((p) => ({ x: p.x, y: p.y })) };
|
|
4565
5239
|
if (raw.warp) {
|
|
4566
|
-
if (!isValidWarpMesh(raw.warp))
|
|
5240
|
+
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
5241
|
out.warp = { us: [...raw.warp.us], vs: [...raw.warp.vs], points: raw.warp.points.map((p) => ({ x: p.x, y: p.y })) };
|
|
4568
5242
|
}
|
|
4569
5243
|
if (raw.interpolation !== void 0) {
|
|
4570
|
-
if (!isInterpolation(raw.interpolation))
|
|
5244
|
+
if (!isInterpolation(raw.interpolation)) fail4(`Unknown interpolation "${String(raw.interpolation)}"; use nearest, bilinear or bicubic.`);
|
|
4571
5245
|
if (raw.interpolation !== "bicubic") out.interpolation = raw.interpolation;
|
|
4572
5246
|
}
|
|
4573
5247
|
const g = { box: { x: 0, y: 0, width: contents.width, height: contents.height }, corners: out.corners, warp: out.warp ?? null };
|
|
4574
|
-
if (!transformHomography(g))
|
|
5248
|
+
if (!transformHomography(g)) fail4("These corners fold the contents over themselves or through the horizon; keep the quad convex.");
|
|
4575
5249
|
if (out.warp && !hasWarp(g)) delete out.warp;
|
|
4576
5250
|
return out;
|
|
4577
5251
|
}
|
|
@@ -4636,7 +5310,7 @@ function translateLayer(layer, dx, dy) {
|
|
|
4636
5310
|
if (!dx && !dy) return layer;
|
|
4637
5311
|
const moveMask = (m) => {
|
|
4638
5312
|
if (!m) return m;
|
|
4639
|
-
if (m.source)
|
|
5313
|
+
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
5314
|
return { ...m, surface: translateSurface(m.surface, dx, dy) };
|
|
4641
5315
|
};
|
|
4642
5316
|
switch (layer.type) {
|
|
@@ -4674,14 +5348,19 @@ function translateLayer(layer, dx, dy) {
|
|
|
4674
5348
|
delete next.annotation;
|
|
4675
5349
|
return next;
|
|
4676
5350
|
}
|
|
5351
|
+
case "type": {
|
|
5352
|
+
const next = { ...transformTypeLayer(layer, translateMatrix(dx, dy)), surface: translateSurface(layer.surface, dx, dy), mask: moveMask(layer.mask) };
|
|
5353
|
+
if (!next.mask) delete next.mask;
|
|
5354
|
+
return next;
|
|
5355
|
+
}
|
|
4677
5356
|
case "annotation":
|
|
4678
|
-
return
|
|
5357
|
+
return fail4(`Annotation layer "${layer.name || layer.id}" cannot go into a smart object; rasterize it first.`);
|
|
4679
5358
|
}
|
|
4680
5359
|
}
|
|
4681
5360
|
function layerExtent(layer, surfaceOf, canvas) {
|
|
4682
5361
|
const full = { x: 0, y: 0, width: canvas.width, height: canvas.height };
|
|
4683
5362
|
let b = null;
|
|
4684
|
-
if (layer.type === "pixel" || layer.type === "smart" || layer.type === "shape") {
|
|
5363
|
+
if (layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type") {
|
|
4685
5364
|
const s = layer.type === "pixel" ? surfaceOf(layer) : layer.surface;
|
|
4686
5365
|
if (s.defaultValue !== 0) b = full;
|
|
4687
5366
|
else {
|
|
@@ -4709,10 +5388,10 @@ function asSmartFilters(filters) {
|
|
|
4709
5388
|
return { enabled: true, filters: filters.map((f) => ({ ...f, blend: "normal", opacity: 1 })) };
|
|
4710
5389
|
}
|
|
4711
5390
|
function convertToSmartObject(layers, ids, canvas, options) {
|
|
4712
|
-
if (!ids.length)
|
|
5391
|
+
if (!ids.length) fail4("Give at least one layer id to convert.");
|
|
4713
5392
|
const locs = [...new Set(ids)].map((id) => requireLayer(layers, id));
|
|
4714
5393
|
const parent = locs[0].parent?.id ?? null;
|
|
4715
|
-
if (locs.some((l) => (l.parent?.id ?? null) !== parent))
|
|
5394
|
+
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
5395
|
for (const l of locs) requireUnlocked(l.layer, "all", "convert it");
|
|
4717
5396
|
const ordered = locs.sort((a, b) => a.index - b.index);
|
|
4718
5397
|
const top = ordered[ordered.length - 1];
|
|
@@ -4720,9 +5399,9 @@ function convertToSmartObject(layers, ids, canvas, options) {
|
|
|
4720
5399
|
const single = ordered.length === 1 ? ordered[0].layer : null;
|
|
4721
5400
|
if (single && single.type === "pixel") {
|
|
4722
5401
|
const surface = options.surfaceOf(single);
|
|
4723
|
-
if (surface.defaultValue !== 0)
|
|
5402
|
+
if (surface.defaultValue !== 0) fail4(`Layer "${single.name || single.id}" has no transparent area to crop to; it cannot be converted.`);
|
|
4724
5403
|
const b = contentBounds(surface);
|
|
4725
|
-
if (!b)
|
|
5404
|
+
if (!b) fail4(`Layer "${single.name || single.id}" has no pixels to convert; paint something first.`);
|
|
4726
5405
|
const contents = {
|
|
4727
5406
|
id: newContentsId(),
|
|
4728
5407
|
name: `${single.name || "Layer"}.png`,
|
|
@@ -4754,7 +5433,7 @@ function convertToSmartObject(layers, ids, canvas, options) {
|
|
|
4754
5433
|
} else {
|
|
4755
5434
|
let b = null;
|
|
4756
5435
|
for (const l of ordered) b = union(b, layerExtent(l.layer, options.surfaceOf, canvas));
|
|
4757
|
-
if (!b || !(b.width > 0 && b.height > 0))
|
|
5436
|
+
if (!b || !(b.width > 0 && b.height > 0)) fail4("The layers have no pixels to convert; paint something first.");
|
|
4758
5437
|
const nested = ordered.map((l) => {
|
|
4759
5438
|
let layer = l.layer;
|
|
4760
5439
|
if (layer.type === "pixel" && layer.source === "document") {
|
|
@@ -4762,7 +5441,7 @@ function convertToSmartObject(layers, ids, canvas, options) {
|
|
|
4762
5441
|
delete resolved.source;
|
|
4763
5442
|
layer = resolved;
|
|
4764
5443
|
}
|
|
4765
|
-
if (flattenLayers([layer]).some((x) => x.type === "pixel" && x.source === "document"))
|
|
5444
|
+
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
5445
|
return translateLayer(layer, -b.x, -b.y);
|
|
4767
5446
|
});
|
|
4768
5447
|
if (nested[0].clipped) {
|
|
@@ -4827,7 +5506,7 @@ function instancesOf(layers, contentsId) {
|
|
|
4827
5506
|
}
|
|
4828
5507
|
function replaceContents(layers, contentsId, contents, canvas) {
|
|
4829
5508
|
const ids = instancesOf(layers, contentsId);
|
|
4830
|
-
if (!ids.length)
|
|
5509
|
+
if (!ids.length) fail4(`No smart object uses contents "${contentsId}".`);
|
|
4831
5510
|
for (const id of ids) requireUnlocked(findLayer(layers, id).layer, "all", "replace its contents");
|
|
4832
5511
|
const next = { ...contents, id: contentsId };
|
|
4833
5512
|
let out = layers;
|
|
@@ -4860,12 +5539,12 @@ function refitPlacement(layer, contents) {
|
|
|
4860
5539
|
return out;
|
|
4861
5540
|
}
|
|
4862
5541
|
function normalizeSmartFilter(raw, id = newLayerId("flt")) {
|
|
4863
|
-
if (raw.kind === "psd")
|
|
5542
|
+
if (raw.kind === "psd") fail4("Photoshop filters can only come from a PSD; add develop or blur.");
|
|
4864
5543
|
const base = normalizeFilter(raw, id);
|
|
4865
5544
|
const blend = raw.blend ?? "normal";
|
|
4866
|
-
if (!isBlendMode(blend) || blend === "pigment")
|
|
5545
|
+
if (!isBlendMode(blend) || blend === "pigment") fail4(`Unknown blend mode "${String(blend)}" for a smart filter.`);
|
|
4867
5546
|
const opacity = raw.opacity ?? 1;
|
|
4868
|
-
if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1))
|
|
5547
|
+
if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail4("opacity must be a number from 0 to 1.");
|
|
4869
5548
|
return { ...base, blend, opacity };
|
|
4870
5549
|
}
|
|
4871
5550
|
function withStack(layer, stack, canvas) {
|
|
@@ -4880,7 +5559,7 @@ function stackOf(layer) {
|
|
|
4880
5559
|
function requireFilter(layer, filterId) {
|
|
4881
5560
|
const list2 = layer.smartFilters?.filters ?? [];
|
|
4882
5561
|
const i = list2.findIndex((f) => f.id === filterId);
|
|
4883
|
-
if (i === -1)
|
|
5562
|
+
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
5563
|
return i;
|
|
4885
5564
|
}
|
|
4886
5565
|
function editableSmart(layers, id) {
|
|
@@ -4901,15 +5580,15 @@ function updateSmartFilter(layers, id, filterId, changes, canvas) {
|
|
|
4901
5580
|
const layer = editableSmart(layers, id);
|
|
4902
5581
|
const i = requireFilter(layer, filterId);
|
|
4903
5582
|
const current = layer.smartFilters.filters[i];
|
|
4904
|
-
if ("kind" in changes && changes.kind !== current.kind)
|
|
5583
|
+
if ("kind" in changes && changes.kind !== current.kind) fail4("A smart filter kind cannot change; remove it and add another.");
|
|
4905
5584
|
let merged;
|
|
4906
5585
|
if (current.kind === "psd") {
|
|
4907
5586
|
const extra = Object.keys(changes).filter((k) => !["enabled", "blend", "opacity", "kind"].includes(k));
|
|
4908
|
-
if (extra.length)
|
|
5587
|
+
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
5588
|
const blend = changes.blend ?? current.blend;
|
|
4910
|
-
if (!isBlendMode(blend) || blend === "pigment")
|
|
5589
|
+
if (!isBlendMode(blend) || blend === "pigment") fail4(`Unknown blend mode "${String(blend)}" for a smart filter.`);
|
|
4911
5590
|
const opacity = changes.opacity ?? current.opacity;
|
|
4912
|
-
if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1))
|
|
5591
|
+
if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail4("opacity must be a number from 0 to 1.");
|
|
4913
5592
|
merged = { ...current, enabled: changes.enabled === void 0 ? current.enabled : Boolean(changes.enabled), blend, opacity };
|
|
4914
5593
|
} else {
|
|
4915
5594
|
merged = normalizeSmartFilter({ ...current, ...changes, kind: current.kind }, current.id);
|
|
@@ -4931,7 +5610,7 @@ function moveSmartFilter(layers, id, filterId, index, canvas) {
|
|
|
4931
5610
|
const layer = editableSmart(layers, id);
|
|
4932
5611
|
const i = requireFilter(layer, filterId);
|
|
4933
5612
|
const list2 = layer.smartFilters.filters;
|
|
4934
|
-
if (!Number.isInteger(index) || index < 0 || index >= list2.length)
|
|
5613
|
+
if (!Number.isInteger(index) || index < 0 || index >= list2.length) fail4(`index must be a whole number from 0 to ${list2.length - 1}.`);
|
|
4935
5614
|
const filters = [...list2];
|
|
4936
5615
|
const [f] = filters.splice(i, 1);
|
|
4937
5616
|
filters.splice(index, 0, f);
|
|
@@ -4945,7 +5624,7 @@ function setSmartFiltersEnabled(layers, id, enabled, canvas) {
|
|
|
4945
5624
|
}
|
|
4946
5625
|
function setSmartFilterMask(layers, id, mask, canvas) {
|
|
4947
5626
|
const layer = editableSmart(layers, id);
|
|
4948
|
-
if (mask?.source)
|
|
5627
|
+
if (mask?.source) fail4("A smart filter mask is painted, not generated; use a raster mask.");
|
|
4949
5628
|
const stack = { ...stackOf(layer) };
|
|
4950
5629
|
if (mask) stack.mask = mask;
|
|
4951
5630
|
else delete stack.mask;
|
|
@@ -4954,7 +5633,7 @@ function setSmartFilterMask(layers, id, mask, canvas) {
|
|
|
4954
5633
|
}
|
|
4955
5634
|
function withFilterMaskSurface(layer, surface, canvas) {
|
|
4956
5635
|
const stack = stackOf(layer);
|
|
4957
|
-
if (!stack.mask)
|
|
5636
|
+
if (!stack.mask) fail4(`Smart object "${layer.name || layer.id}" has no filter mask; add one with set_smart_filter_mask.`);
|
|
4958
5637
|
return withStack(layer, { ...stack, mask: { ...stack.mask, surface } }, canvas);
|
|
4959
5638
|
}
|
|
4960
5639
|
function placementBounds(layer) {
|
|
@@ -5075,6 +5754,84 @@ function serializeLayer(layer, ctx) {
|
|
|
5075
5754
|
return serializeSmartObject(layer, base, ctx);
|
|
5076
5755
|
case "shape":
|
|
5077
5756
|
return serializeShape(layer, base);
|
|
5757
|
+
case "type":
|
|
5758
|
+
return serializeType(layer, base, ctx);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
function serializeType(layer, base, ctx) {
|
|
5762
|
+
const out = {
|
|
5763
|
+
...base,
|
|
5764
|
+
type: "type",
|
|
5765
|
+
text: layer.text,
|
|
5766
|
+
runs: JSON.parse(JSON.stringify(layer.runs)),
|
|
5767
|
+
paragraphs: JSON.parse(JSON.stringify(layer.paragraphs)),
|
|
5768
|
+
frame: { ...layer.frame }
|
|
5769
|
+
};
|
|
5770
|
+
if (layer.transform) out.transform = [...layer.transform];
|
|
5771
|
+
if (layer.antiAlias === false) out.antiAlias = false;
|
|
5772
|
+
if (layer.fillOpacity !== void 0 && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;
|
|
5773
|
+
const enc = encodeSurface(layer.surface);
|
|
5774
|
+
if (enc) {
|
|
5775
|
+
out.asset = ctx.addAsset(enc.url, layer.id);
|
|
5776
|
+
out.x = enc.x;
|
|
5777
|
+
out.y = enc.y;
|
|
5778
|
+
}
|
|
5779
|
+
if (layer.missingFonts?.length) out.missingFonts = [...layer.missingFonts];
|
|
5780
|
+
if (layer.annotation) out.annotation = JSON.parse(JSON.stringify(layer.annotation));
|
|
5781
|
+
if (layer.psd?.tySh || layer.psd?.blocks?.length) {
|
|
5782
|
+
out.psd = {};
|
|
5783
|
+
if (layer.psd.tySh) out.psd.tySh = bytesToBase64(layer.psd.tySh);
|
|
5784
|
+
if (layer.psd.blocks?.length) out.psd.blocks = layer.psd.blocks.map((b) => ({ key: b.key, data: bytesToBase64(b.data) }));
|
|
5785
|
+
}
|
|
5786
|
+
return out;
|
|
5787
|
+
}
|
|
5788
|
+
function readType(r, base, ctx) {
|
|
5789
|
+
const label = `Layer "${base.name || base.id}"`;
|
|
5790
|
+
try {
|
|
5791
|
+
const text3 = normalizeText(r.text ?? "");
|
|
5792
|
+
const runs = (Array.isArray(r.runs) ? r.runs : []).map((run, i) => ({
|
|
5793
|
+
length: Math.max(0, Math.floor(num2(run?.length, 0, 1e9, 0))),
|
|
5794
|
+
style: patchTextStyle(DEFAULT_TEXT_STYLE, run?.style && typeof run.style === "object" ? run.style : {}, `runs[${i}].style`)
|
|
5795
|
+
}));
|
|
5796
|
+
const paragraphs = (Array.isArray(r.paragraphs) ? r.paragraphs : []).map((p, i) => ({
|
|
5797
|
+
length: Math.max(0, Math.floor(num2(p?.length, 0, 1e9, 0))),
|
|
5798
|
+
style: patchParagraphStyle({}, p?.style && typeof p.style === "object" ? p.style : {}, `paragraphs[${i}].style`)
|
|
5799
|
+
}));
|
|
5800
|
+
const f = r.frame;
|
|
5801
|
+
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" };
|
|
5802
|
+
const m = Array.isArray(r.transform) && r.transform.length === 6 && r.transform.every((v) => typeof v === "number" && Number.isFinite(v)) ? r.transform : void 0;
|
|
5803
|
+
const layer = {
|
|
5804
|
+
...base,
|
|
5805
|
+
type: "type",
|
|
5806
|
+
text: text3,
|
|
5807
|
+
runs: fitRuns(runs, text3.length, DEFAULT_TEXT_STYLE),
|
|
5808
|
+
paragraphs: fitParagraphRuns(paragraphs.length ? paragraphs : [{ length: text3.length, style: {} }], text3),
|
|
5809
|
+
frame,
|
|
5810
|
+
surface: emptySurface(4),
|
|
5811
|
+
offset: { x: 0, y: 0 }
|
|
5812
|
+
};
|
|
5813
|
+
if (!layer.runs.length) layer.runs = [{ length: 0, style: runs[0]?.style ?? DEFAULT_TEXT_STYLE }];
|
|
5814
|
+
if (m) layer.transform = m;
|
|
5815
|
+
if (r.antiAlias === false) layer.antiAlias = false;
|
|
5816
|
+
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
|
|
5817
|
+
if (Array.isArray(r.missingFonts)) layer.missingFonts = r.missingFonts.filter((x) => typeof x === "string");
|
|
5818
|
+
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 }));
|
|
5819
|
+
if (r.psd && typeof r.psd === "object") {
|
|
5820
|
+
const psd = {};
|
|
5821
|
+
if (typeof r.psd.tySh === "string") psd.tySh = base64ToBytes(r.psd.tySh);
|
|
5822
|
+
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) }));
|
|
5823
|
+
if (psd.tySh || psd.blocks?.length) layer.psd = psd;
|
|
5824
|
+
}
|
|
5825
|
+
if (typeof r.asset === "string") return { ...layer, surface: readSurface(r.asset, r.x, r.y, 4, 0, ctx, label), rendering: "stored" };
|
|
5826
|
+
const env = { ...ctx.canvas ?? { width: 1, height: 1 }, shaper: ctx.text?.shaper ?? null, createCanvas: ctx.text?.createCanvas };
|
|
5827
|
+
try {
|
|
5828
|
+
return rerenderText(layer, env);
|
|
5829
|
+
} catch {
|
|
5830
|
+
return { ...layer, rendering: "stored" };
|
|
5831
|
+
}
|
|
5832
|
+
} catch (e) {
|
|
5833
|
+
ctx.problems.push(`${label}: the text could not be read (${e instanceof Error ? e.message : String(e)})`);
|
|
5834
|
+
return null;
|
|
5078
5835
|
}
|
|
5079
5836
|
}
|
|
5080
5837
|
function serializeShape(layer, base) {
|
|
@@ -5117,7 +5874,7 @@ function readShape(r, base, ctx) {
|
|
|
5117
5874
|
}
|
|
5118
5875
|
const layer = createShapeLayer({ ...data, fillRule: r.fillRule === "evenodd" ? "evenodd" : "nonzero" }, env, base.id);
|
|
5119
5876
|
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 =
|
|
5877
|
+
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) out.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
|
|
5121
5878
|
if (a && shapeFromAnnotation(a)) out.annotation = a;
|
|
5122
5879
|
if (r.psd && Array.isArray(r.psd.blocks)) {
|
|
5123
5880
|
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) }));
|
|
@@ -5269,7 +6026,7 @@ function decodePngDataUrl(url, channels) {
|
|
|
5269
6026
|
}
|
|
5270
6027
|
return { width: png.width, height: png.height, data: out };
|
|
5271
6028
|
}
|
|
5272
|
-
var
|
|
6029
|
+
var num2 = (v, lo, hi, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : fallback;
|
|
5273
6030
|
function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
|
|
5274
6031
|
if (typeof ref !== "string" || !ref) return emptySurface(channels, defaultValue);
|
|
5275
6032
|
const url = ctx.assets[ref];
|
|
@@ -5279,7 +6036,7 @@ function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
|
|
|
5279
6036
|
}
|
|
5280
6037
|
try {
|
|
5281
6038
|
const px = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
|
|
5282
|
-
return surfaceFromPixels(px, channels, Math.round(
|
|
6039
|
+
return surfaceFromPixels(px, channels, Math.round(num2(x, -1e7, 1e7, 0)), Math.round(num2(y, -1e7, 1e7, 0)), defaultValue);
|
|
5283
6040
|
} catch (err) {
|
|
5284
6041
|
ctx.problems.push(`${what}: asset "${ref}" could not be decoded (${err.message})`);
|
|
5285
6042
|
return emptySurface(channels, defaultValue);
|
|
@@ -5293,8 +6050,8 @@ function readMask(raw, ctx, what) {
|
|
|
5293
6050
|
surface: r.source ? emptySurface(1, 255) : readSurface(r.asset, r.x, r.y, 1, defaultValue, ctx, `${what} mask`),
|
|
5294
6051
|
defaultValue: r.source ? 255 : defaultValue
|
|
5295
6052
|
};
|
|
5296
|
-
if (typeof r.density === "number" && r.density !== 1) mask.density =
|
|
5297
|
-
if (typeof r.feather === "number" && r.feather > 0) mask.feather =
|
|
6053
|
+
if (typeof r.density === "number" && r.density !== 1) mask.density = num2(r.density, 0, 1, 1);
|
|
6054
|
+
if (typeof r.feather === "number" && r.feather > 0) mask.feather = num2(r.feather, 0, 1e3, 0);
|
|
5298
6055
|
if (r.enabled === false) mask.enabled = false;
|
|
5299
6056
|
if (r.linked === false) mask.linked = false;
|
|
5300
6057
|
if (r.source && typeof r.source === "object") {
|
|
@@ -5311,18 +6068,18 @@ function readFilters(raw) {
|
|
|
5311
6068
|
const r = f;
|
|
5312
6069
|
const id = typeof r.id === "string" && r.id ? r.id : newLayerId("flt");
|
|
5313
6070
|
const enabled = r.enabled !== false;
|
|
5314
|
-
if (r.kind === "blur") out.push({ id, kind: "blur", enabled, radius:
|
|
6071
|
+
if (r.kind === "blur") out.push({ id, kind: "blur", enabled, radius: num2(r.radius, 0, 50, 0) });
|
|
5315
6072
|
else if (r.kind === "develop") {
|
|
5316
6073
|
out.push({
|
|
5317
6074
|
id,
|
|
5318
6075
|
kind: "develop",
|
|
5319
6076
|
enabled,
|
|
5320
|
-
brightness:
|
|
5321
|
-
contrast:
|
|
5322
|
-
saturation:
|
|
5323
|
-
exposure:
|
|
5324
|
-
temperature:
|
|
5325
|
-
tint:
|
|
6077
|
+
brightness: num2(r.brightness, -100, 100, 0),
|
|
6078
|
+
contrast: num2(r.contrast, -100, 100, 0),
|
|
6079
|
+
saturation: num2(r.saturation, -100, 100, 0),
|
|
6080
|
+
exposure: num2(r.exposure, -100, 100, 0),
|
|
6081
|
+
temperature: num2(r.temperature, -100, 100, 0),
|
|
6082
|
+
tint: num2(r.tint, -100, 100, 0),
|
|
5326
6083
|
process: normalizeProcessVersion(r.process)
|
|
5327
6084
|
});
|
|
5328
6085
|
}
|
|
@@ -5337,7 +6094,7 @@ function deserializeLayer(raw, ctx) {
|
|
|
5337
6094
|
id,
|
|
5338
6095
|
name: typeof r.name === "string" ? r.name : "",
|
|
5339
6096
|
visible: r.visible !== false,
|
|
5340
|
-
opacity:
|
|
6097
|
+
opacity: num2(r.opacity, 0, 1, 1),
|
|
5341
6098
|
blend: normalizeBlendMode(r.blend)
|
|
5342
6099
|
};
|
|
5343
6100
|
if (r.clipped === true) base.clipped = true;
|
|
@@ -5356,10 +6113,10 @@ function deserializeLayer(raw, ctx) {
|
|
|
5356
6113
|
...base,
|
|
5357
6114
|
type: "pixel",
|
|
5358
6115
|
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(
|
|
6116
|
+
offset: { x: Math.round(num2(r.offset?.x, -1e7, 1e7, 0)), y: Math.round(num2(r.offset?.y, -1e7, 1e7, 0)) }
|
|
5360
6117
|
};
|
|
5361
6118
|
if (r.source === "document") layer.source = "document";
|
|
5362
|
-
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity =
|
|
6119
|
+
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
|
|
5363
6120
|
const filters = readFilters(r.filters);
|
|
5364
6121
|
if (filters) layer.filters = filters;
|
|
5365
6122
|
return layer;
|
|
@@ -5386,15 +6143,17 @@ function deserializeLayer(raw, ctx) {
|
|
|
5386
6143
|
return null;
|
|
5387
6144
|
}
|
|
5388
6145
|
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") {
|
|
6146
|
+
const migrated = annotationLayerFor(annotation, { ...ctx.canvas ?? { width: 1, height: 1 }, shaper: ctx.text?.shaper ?? null, createCanvas: ctx.text?.createCanvas });
|
|
6147
|
+
if (migrated.type === "shape" || migrated.type === "type") {
|
|
5391
6148
|
const blend = base.blend === "normal" ? migrated.blend : base.blend;
|
|
5392
|
-
return { ...migrated, ...base, blend,
|
|
6149
|
+
return { ...migrated, ...base, blend, id, name: base.name };
|
|
5393
6150
|
}
|
|
5394
6151
|
return { ...base, type: "annotation", annotation };
|
|
5395
6152
|
}
|
|
5396
6153
|
case "shape":
|
|
5397
6154
|
return readShape(r, base, ctx);
|
|
6155
|
+
case "type":
|
|
6156
|
+
return readType(r, base, ctx);
|
|
5398
6157
|
case "smart":
|
|
5399
6158
|
return readSmartObject(r, base, ctx);
|
|
5400
6159
|
default:
|
|
@@ -5414,8 +6173,8 @@ function readContents(id, ctx) {
|
|
|
5414
6173
|
ctx.problems.push(`Smart object contents "${id}" are missing`);
|
|
5415
6174
|
return null;
|
|
5416
6175
|
}
|
|
5417
|
-
const width = Math.round(
|
|
5418
|
-
const height = Math.round(
|
|
6176
|
+
const width = Math.round(num2(raw.width, 0, 3e5, 0));
|
|
6177
|
+
const height = Math.round(num2(raw.height, 0, 3e5, 0));
|
|
5419
6178
|
if (!(width > 0 && height > 0)) {
|
|
5420
6179
|
ctx.problems.push(`Smart object contents "${id}" have no size`);
|
|
5421
6180
|
return null;
|
|
@@ -5439,7 +6198,7 @@ function readContents(id, ctx) {
|
|
|
5439
6198
|
const layers = deserializeLayers(raw.layers, ctx);
|
|
5440
6199
|
ctx.canvas = outer;
|
|
5441
6200
|
c.document = { layers };
|
|
5442
|
-
if (raw.globalLight && typeof raw.globalLight === "object") c.document.globalLight = { angle:
|
|
6201
|
+
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
6202
|
const patterns = deserializePatterns(raw.patterns, ctx.problems);
|
|
5444
6203
|
if (patterns.length) c.document.patterns = patterns;
|
|
5445
6204
|
}
|
|
@@ -5456,7 +6215,7 @@ function readSmartFilters(raw, ctx, what) {
|
|
|
5456
6215
|
for (const f of list2) {
|
|
5457
6216
|
if (!f || typeof f !== "object") continue;
|
|
5458
6217
|
const blend = isBlendMode(f.blend) && f.blend !== "pigment" ? f.blend : "normal";
|
|
5459
|
-
const opacity =
|
|
6218
|
+
const opacity = num2(f.opacity, 0, 1, 1);
|
|
5460
6219
|
if (f.kind === "psd") {
|
|
5461
6220
|
try {
|
|
5462
6221
|
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 +6253,7 @@ function readSmartObject(r, base, ctx) {
|
|
|
5494
6253
|
const layer = { ...base, type: "smart", contents, placement, surface: emptySurface(4), offset: { x: 0, y: 0 } };
|
|
5495
6254
|
const filters = readSmartFilters(r.smartFilters, ctx, what);
|
|
5496
6255
|
if (filters) layer.smartFilters = filters;
|
|
5497
|
-
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity =
|
|
6256
|
+
if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
|
|
5498
6257
|
if (r.psd && typeof r.psd.key === "string" && typeof r.psd.data === "string") {
|
|
5499
6258
|
try {
|
|
5500
6259
|
layer.psd = { key: r.psd.key, data: base64ToBytes(r.psd.data) };
|
|
@@ -8353,8 +9112,8 @@ function blendFromPsdId(id) {
|
|
|
8353
9112
|
function psdBlendId(mode) {
|
|
8354
9113
|
return BLEND_ID.get(mode) ?? "Nrml";
|
|
8355
9114
|
}
|
|
8356
|
-
var
|
|
8357
|
-
var pct = (d, key, fallback) =>
|
|
9115
|
+
var num3 = (d, key, fallback) => descNumber(d, key) ?? fallback;
|
|
9116
|
+
var pct = (d, key, fallback) => num3(d, key, fallback * 100) / 100;
|
|
8358
9117
|
var bool = (d, key) => descBool(d, key) === true;
|
|
8359
9118
|
var enumOf2 = (d, key) => {
|
|
8360
9119
|
const v = descGet(d, key);
|
|
@@ -8427,7 +9186,7 @@ function contourOf(d, key) {
|
|
|
8427
9186
|
if (!c) return void 0;
|
|
8428
9187
|
const name = text2(c, "Nm ");
|
|
8429
9188
|
const points = list(c, "Crv ").map((p) => {
|
|
8430
|
-
const out = { input: clamp3(
|
|
9189
|
+
const out = { input: clamp3(num3(p, "Hrzn", 0) / 255, 0, 1), output: clamp3(num3(p, "Vrtc", 0) / 255, 0, 1) };
|
|
8431
9190
|
if (descBool(p, "Cnty") === false) out.corner = true;
|
|
8432
9191
|
return out;
|
|
8433
9192
|
});
|
|
@@ -8473,7 +9232,7 @@ function gradientOf(d, notes) {
|
|
|
8473
9232
|
{ position: 1, color: "#ffffff" }
|
|
8474
9233
|
],
|
|
8475
9234
|
style: GRADIENT_STYLES.find(([, id]) => id === enumOf2(d, "Type"))?.[0] ?? "linear",
|
|
8476
|
-
angle: clamp3(
|
|
9235
|
+
angle: clamp3(num3(d, "Angl", 90), -360, 360),
|
|
8477
9236
|
scale: clamp3(pct(d, "Scl ", 1), 0.1, 1.5),
|
|
8478
9237
|
reverse: bool(d, "Rvrs"),
|
|
8479
9238
|
align: descBool(d, "Algn") !== false,
|
|
@@ -8491,7 +9250,7 @@ function gradientOf(d, notes) {
|
|
|
8491
9250
|
const type = enumOf2(s, "Type");
|
|
8492
9251
|
if (type === "FrgC" || type === "BckC") notes.add("foreground / background gradient stops became black / white");
|
|
8493
9252
|
const color = type === "BckC" ? "#ffffff" : colorOf(descObject(s, "Clr "));
|
|
8494
|
-
return { position: clamp3(
|
|
9253
|
+
return { position: clamp3(num3(s, "Lctn", 0) / 4096, 0, 1), color, mid: clamp3(num3(s, "Mdpn", 50) / 100, 0.05, 0.95) };
|
|
8495
9254
|
}).sort((a, b) => a.position - b.position);
|
|
8496
9255
|
if (raw.length) {
|
|
8497
9256
|
const stops = [];
|
|
@@ -8507,9 +9266,9 @@ function gradientOf(d, notes) {
|
|
|
8507
9266
|
});
|
|
8508
9267
|
g.stops = stops;
|
|
8509
9268
|
}
|
|
8510
|
-
const opacity = list(grad, "Trns").map((s) => ({ position: clamp3(
|
|
9269
|
+
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
9270
|
if (opacity.some((o) => o.opacity < 1)) g.opacityStops = opacity;
|
|
8512
|
-
if (
|
|
9271
|
+
if (num3(grad, "Intr", 0) > 0) notes.add("gradient smoothness is drawn as straight segments");
|
|
8513
9272
|
return g;
|
|
8514
9273
|
}
|
|
8515
9274
|
function gradientItems(g) {
|
|
@@ -8579,9 +9338,9 @@ function patternRefDesc(patternId, name) {
|
|
|
8579
9338
|
function placementOf(d) {
|
|
8580
9339
|
const phase = descObject(d, "phase");
|
|
8581
9340
|
return {
|
|
8582
|
-
angle: clamp3(
|
|
9341
|
+
angle: clamp3(num3(d, "Angl", 0), -360, 360),
|
|
8583
9342
|
link: descBool(d, "Algn") !== false,
|
|
8584
|
-
phase: phase ? { x:
|
|
9343
|
+
phase: phase ? { x: num3(phase, "Hrzn", 0), y: num3(phase, "Vrtc", 0) } : { x: 0, y: 0 }
|
|
8585
9344
|
};
|
|
8586
9345
|
}
|
|
8587
9346
|
function placementItems(angle, link, phase) {
|
|
@@ -8616,16 +9375,16 @@ function common(d, kind, notes) {
|
|
|
8616
9375
|
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
9376
|
}
|
|
8618
9377
|
function readOne(kind, d, notes) {
|
|
8619
|
-
const size = (key, fallback) => clamp3(
|
|
9378
|
+
const size = (key, fallback) => clamp3(num3(d, key, fallback), 0, 250);
|
|
8620
9379
|
switch (kind) {
|
|
8621
9380
|
case "drop-shadow":
|
|
8622
9381
|
case "inner-shadow": {
|
|
8623
9382
|
const base = {
|
|
8624
9383
|
...common(d, kind, notes),
|
|
8625
9384
|
color: colorOf(descObject(d, "Clr ")),
|
|
8626
|
-
angle: clamp3(
|
|
9385
|
+
angle: clamp3(num3(d, "lagl", 120), -360, 360),
|
|
8627
9386
|
useGlobalLight: descBool(d, "uglg") !== false,
|
|
8628
|
-
distance: clamp3(
|
|
9387
|
+
distance: clamp3(num3(d, "Dstn", 5), 0, 3e4),
|
|
8629
9388
|
size: size("blur", 5),
|
|
8630
9389
|
contour: contourOf(d, "TrnS"),
|
|
8631
9390
|
antiAlias: bool(d, "AntA"),
|
|
@@ -8664,9 +9423,9 @@ function readOne(kind, d, notes) {
|
|
|
8664
9423
|
depth: clamp3(pct(d, "srgR", 1), 0, 10),
|
|
8665
9424
|
direction: enumOf2(d, "bvlD") === "Out " ? "down" : "up",
|
|
8666
9425
|
size: size("blur", 5),
|
|
8667
|
-
soften: clamp3(
|
|
8668
|
-
angle: clamp3(
|
|
8669
|
-
altitude: clamp3(
|
|
9426
|
+
soften: clamp3(num3(d, "Sftn", 0), 0, 16),
|
|
9427
|
+
angle: clamp3(num3(d, "lagl", 120), -360, 360),
|
|
9428
|
+
altitude: clamp3(num3(d, "Lald", 30), 0, 90),
|
|
8670
9429
|
useGlobalLight: descBool(d, "uglg") !== false,
|
|
8671
9430
|
glossContour: contourOf(d, "TrnS"),
|
|
8672
9431
|
highlightColor: colorOf(descObject(d, "hglC"), "#ffffff"),
|
|
@@ -8689,7 +9448,7 @@ function readOne(kind, d, notes) {
|
|
|
8689
9448
|
...common(d, kind, notes),
|
|
8690
9449
|
kind,
|
|
8691
9450
|
color: colorOf(descObject(d, "Clr ")),
|
|
8692
|
-
angle: clamp3(
|
|
9451
|
+
angle: clamp3(num3(d, "lagl", 19), -360, 360),
|
|
8693
9452
|
distance: size("Dstn", 11),
|
|
8694
9453
|
size: size("blur", 14),
|
|
8695
9454
|
contour: contourOf(d, "MpgS"),
|
|
@@ -9461,6 +10220,7 @@ export {
|
|
|
9461
10220
|
removeAlpha,
|
|
9462
10221
|
SHAPE_OPERATIONS,
|
|
9463
10222
|
LIVE_SHAPE_KINDS,
|
|
10223
|
+
TEXT_ALIGNS,
|
|
9464
10224
|
newLayerId,
|
|
9465
10225
|
pixelLayers,
|
|
9466
10226
|
developFilter,
|
|
@@ -9536,6 +10296,40 @@ export {
|
|
|
9536
10296
|
shapeFromAnnotation,
|
|
9537
10297
|
annotationBlend,
|
|
9538
10298
|
annotationOfShape,
|
|
10299
|
+
DEFAULT_TEXT_STYLE,
|
|
10300
|
+
DEFAULT_AUTO_LEADING,
|
|
10301
|
+
fitRuns,
|
|
10302
|
+
styleAt,
|
|
10303
|
+
restyleRange,
|
|
10304
|
+
spliceRuns,
|
|
10305
|
+
paragraphRanges,
|
|
10306
|
+
fitParagraphRuns,
|
|
10307
|
+
fontOf,
|
|
10308
|
+
layoutText,
|
|
10309
|
+
textIndexAt,
|
|
10310
|
+
caretAt,
|
|
10311
|
+
TEXT_RENDER_MARGIN,
|
|
10312
|
+
textMatrix,
|
|
10313
|
+
textOutlines,
|
|
10314
|
+
renderText,
|
|
10315
|
+
rerenderText,
|
|
10316
|
+
TEXT_STYLE_FIELDS,
|
|
10317
|
+
patchTextStyle,
|
|
10318
|
+
PARAGRAPH_STYLE_FIELDS,
|
|
10319
|
+
patchParagraphStyle,
|
|
10320
|
+
textRange,
|
|
10321
|
+
requireTypeLayer,
|
|
10322
|
+
normalizeText,
|
|
10323
|
+
typeLayerName,
|
|
10324
|
+
createTypeLayer,
|
|
10325
|
+
updateTypeLayer,
|
|
10326
|
+
replaceTextRange,
|
|
10327
|
+
transformTypeLayer,
|
|
10328
|
+
typeFrame,
|
|
10329
|
+
typeAsPixelLayer,
|
|
10330
|
+
toTextSpace,
|
|
10331
|
+
typeFromAnnotation,
|
|
10332
|
+
annotationOfType,
|
|
9539
10333
|
BACKGROUND_LAYER_ID,
|
|
9540
10334
|
BACKGROUND_LAYER_NAME,
|
|
9541
10335
|
DEVELOP_FILTER_ID,
|
|
@@ -9654,4 +10448,4 @@ export {
|
|
|
9654
10448
|
smartContentsFile,
|
|
9655
10449
|
exportPsd
|
|
9656
10450
|
};
|
|
9657
|
-
//# sourceMappingURL=chunk-
|
|
10451
|
+
//# sourceMappingURL=chunk-HHZ6ZJTW.js.map
|