@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
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
D50,
|
|
18
18
|
D65,
|
|
19
19
|
DEFAULT_ADJUSTMENTS_VALUES,
|
|
20
|
+
DEFAULT_AUTO_LEADING,
|
|
20
21
|
DEFAULT_DEPTH_MASK_VALUES,
|
|
21
22
|
DEFAULT_MITER_LIMIT,
|
|
23
|
+
DEFAULT_TEXT_STYLE,
|
|
22
24
|
DEFAULT_TRANSFORM_VALUES,
|
|
23
25
|
DEVELOP_FILTER_ID,
|
|
24
26
|
EMPTY_DISTANCE,
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
MAX_KELVIN,
|
|
36
38
|
MIN_KELVIN,
|
|
37
39
|
MIRED_PER_TEMPERATURE_UNIT,
|
|
40
|
+
PARAGRAPH_STYLE_FIELDS,
|
|
38
41
|
PATTERN_BLOCK_KEYS,
|
|
39
42
|
PSD_BLEND_KEYS,
|
|
40
43
|
PsdError,
|
|
@@ -44,6 +47,9 @@ import {
|
|
|
44
47
|
SHAPE_RENDER_MARGIN,
|
|
45
48
|
SMART_RENDER_MARGIN,
|
|
46
49
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
50
|
+
TEXT_ALIGNS,
|
|
51
|
+
TEXT_RENDER_MARGIN,
|
|
52
|
+
TEXT_STYLE_FIELDS,
|
|
47
53
|
TINT_SCALE,
|
|
48
54
|
TONE_LUT_SIZE,
|
|
49
55
|
TRANSFORM_ANCHORS,
|
|
@@ -64,6 +70,7 @@ import {
|
|
|
64
70
|
annotationLayerName,
|
|
65
71
|
annotationOfLayer,
|
|
66
72
|
annotationOfShape,
|
|
73
|
+
annotationOfType,
|
|
67
74
|
applyAdjustmentLayers,
|
|
68
75
|
applyAdjustments,
|
|
69
76
|
applyBoxBlur,
|
|
@@ -84,6 +91,7 @@ import {
|
|
|
84
91
|
buildToneLut,
|
|
85
92
|
calculateCropDrag,
|
|
86
93
|
canvasToImagePoint,
|
|
94
|
+
caretAt,
|
|
87
95
|
clampCropToImage,
|
|
88
96
|
colorBalanceChannel,
|
|
89
97
|
combineSelections,
|
|
@@ -100,6 +108,7 @@ import {
|
|
|
100
108
|
createAdjustmentLayer,
|
|
101
109
|
createSelectionMask,
|
|
102
110
|
createShapeLayer,
|
|
111
|
+
createTypeLayer,
|
|
103
112
|
curveLutFromLevels,
|
|
104
113
|
decodeLayerChannel,
|
|
105
114
|
decodeLayerMask,
|
|
@@ -131,8 +140,11 @@ import {
|
|
|
131
140
|
exportPsd,
|
|
132
141
|
featherCoverage,
|
|
133
142
|
filterMaskCoverage,
|
|
143
|
+
fitParagraphRuns,
|
|
144
|
+
fitRuns,
|
|
134
145
|
flattenPath,
|
|
135
146
|
flipCorners,
|
|
147
|
+
fontOf,
|
|
136
148
|
fullSelection,
|
|
137
149
|
geometryHomography,
|
|
138
150
|
getAnnotationBounds,
|
|
@@ -183,6 +195,7 @@ import {
|
|
|
183
195
|
layerTree,
|
|
184
196
|
layerTypeLabel,
|
|
185
197
|
layersFromLegacy,
|
|
198
|
+
layoutText,
|
|
186
199
|
legacyAdjustmentLayer,
|
|
187
200
|
legacyMaskOf,
|
|
188
201
|
legacyView,
|
|
@@ -215,9 +228,11 @@ import {
|
|
|
215
228
|
normalizeShapeStroke,
|
|
216
229
|
normalizeSmartFilter,
|
|
217
230
|
normalizeSubpaths,
|
|
231
|
+
normalizeText,
|
|
218
232
|
offsetLayer,
|
|
219
233
|
offsetShape,
|
|
220
234
|
offsetSmartObject,
|
|
235
|
+
paragraphRanges,
|
|
221
236
|
parseAdjustmentBlock,
|
|
222
237
|
parseBlend,
|
|
223
238
|
parseCssColor,
|
|
@@ -226,6 +241,8 @@ import {
|
|
|
226
241
|
parsePatternBlock,
|
|
227
242
|
parseSelectionMode,
|
|
228
243
|
parseVersionedDescriptor,
|
|
244
|
+
patchParagraphStyle,
|
|
245
|
+
patchTextStyle,
|
|
229
246
|
photoFilterMatrix,
|
|
230
247
|
pixelLayerBounds,
|
|
231
248
|
pixelLayers,
|
|
@@ -256,16 +273,21 @@ import {
|
|
|
256
273
|
renderShape,
|
|
257
274
|
renderShapeRect,
|
|
258
275
|
renderSmartObject,
|
|
276
|
+
renderText,
|
|
259
277
|
replaceContents,
|
|
278
|
+
replaceTextRange,
|
|
260
279
|
requireLayer,
|
|
261
280
|
requireOperation,
|
|
262
281
|
requireShapeLayer,
|
|
263
282
|
requireSmartObject,
|
|
283
|
+
requireTypeLayer,
|
|
264
284
|
requireUnlocked,
|
|
265
285
|
rerender,
|
|
266
286
|
rerenderShape,
|
|
287
|
+
rerenderText,
|
|
267
288
|
resizeSelection,
|
|
268
289
|
resolveExternalAssets,
|
|
290
|
+
restyleRange,
|
|
269
291
|
rgbToGray,
|
|
270
292
|
rgbToHsl,
|
|
271
293
|
runLayerFilters,
|
|
@@ -301,16 +323,28 @@ import {
|
|
|
301
323
|
smartContentsFile,
|
|
302
324
|
smartObjectLayers,
|
|
303
325
|
smoothCoverage,
|
|
326
|
+
spliceRuns,
|
|
304
327
|
srgbToLinear,
|
|
328
|
+
styleAt,
|
|
305
329
|
tempTintToXy,
|
|
306
330
|
temperatureToKelvin,
|
|
331
|
+
textIndexAt,
|
|
332
|
+
textMatrix,
|
|
333
|
+
textOutlines,
|
|
334
|
+
textRange,
|
|
335
|
+
toTextSpace,
|
|
307
336
|
toneCurve,
|
|
308
337
|
toneLuts,
|
|
309
338
|
transformHomography,
|
|
310
339
|
transformMap,
|
|
311
340
|
transformShapeComponents,
|
|
341
|
+
transformTypeLayer,
|
|
312
342
|
transformedBox,
|
|
313
343
|
transformedPoint,
|
|
344
|
+
typeAsPixelLayer,
|
|
345
|
+
typeFrame,
|
|
346
|
+
typeFromAnnotation,
|
|
347
|
+
typeLayerName,
|
|
314
348
|
ungroupLayer,
|
|
315
349
|
unionRect,
|
|
316
350
|
unprojectCanvasPoint,
|
|
@@ -319,6 +353,7 @@ import {
|
|
|
319
353
|
updateLayerMaskProps,
|
|
320
354
|
updateShapeLayer,
|
|
321
355
|
updateSmartFilter,
|
|
356
|
+
updateTypeLayer,
|
|
322
357
|
uvToXy,
|
|
323
358
|
versionedDescriptorBytes,
|
|
324
359
|
vibranceLinear,
|
|
@@ -334,7 +369,7 @@ import {
|
|
|
334
369
|
writePsd,
|
|
335
370
|
xyToTempTint,
|
|
336
371
|
xyToUv
|
|
337
|
-
} from "../chunk-
|
|
372
|
+
} from "../chunk-HHZ6ZJTW.js";
|
|
338
373
|
import {
|
|
339
374
|
DEFAULT_GLOBAL_LIGHT,
|
|
340
375
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
@@ -506,12 +541,15 @@ import {
|
|
|
506
541
|
WARP_STYLES,
|
|
507
542
|
WARP_STYLE_LABELS,
|
|
508
543
|
applyMatrix,
|
|
544
|
+
bezierToSubpaths,
|
|
509
545
|
dragWarpMeshPoint,
|
|
510
546
|
evalWarpMesh,
|
|
511
547
|
identityWarpMesh,
|
|
548
|
+
invertMatrix,
|
|
512
549
|
multiplyMatrices,
|
|
513
550
|
pullWarpMesh,
|
|
514
551
|
resizeWarpMesh,
|
|
552
|
+
rotateMatrix,
|
|
515
553
|
roundSubpaths,
|
|
516
554
|
translateMatrix,
|
|
517
555
|
warpMeshColumns,
|
|
@@ -700,7 +738,7 @@ function needsLegacyBase(input) {
|
|
|
700
738
|
if (hasResampledLegacyMask(stack.layers, canvas.width, canvas.height)) return true;
|
|
701
739
|
return false;
|
|
702
740
|
}
|
|
703
|
-
var isPlainAnnotation = (l) => l.type === "annotation" ? l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask : l.type === "shape" && Boolean(l.annotation) && l.opacity === 1 && l.blend === (l.annotation.blend ?? "normal") && !l.clipped && !l.mask && !l.effects && (l.fillOpacity ?? 1) === 1;
|
|
741
|
+
var isPlainAnnotation = (l) => l.type === "annotation" ? l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask : (l.type === "shape" || l.type === "type") && Boolean(l.annotation) && l.opacity === 1 && l.blend === (l.annotation.blend ?? "normal") && !l.clipped && !l.mask && !l.effects && (l.fillOpacity ?? 1) === 1;
|
|
704
742
|
var overlayViews = /* @__PURE__ */ new WeakMap();
|
|
705
743
|
function overlayView(l) {
|
|
706
744
|
if (l.type === "annotation") return l;
|
|
@@ -716,7 +754,7 @@ function splitOverlayAnnotations(layers) {
|
|
|
716
754
|
let i = layers.length;
|
|
717
755
|
while (i > 0) {
|
|
718
756
|
const l = layers[i - 1];
|
|
719
|
-
if ((l.type === "annotation" || l.type === "shape" && l.annotation) && (!l.visible || isPlainAnnotation(l))) i--;
|
|
757
|
+
if ((l.type === "annotation" || (l.type === "shape" || l.type === "type") && l.annotation) && (!l.visible || isPlainAnnotation(l))) i--;
|
|
720
758
|
else break;
|
|
721
759
|
}
|
|
722
760
|
return {
|
|
@@ -2441,6 +2479,159 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
2441
2479
|
return createSelectionMask(mask.width, mask.height, out);
|
|
2442
2480
|
}
|
|
2443
2481
|
|
|
2482
|
+
// src/image-editor/layers/text-shapers.ts
|
|
2483
|
+
var GENERIC = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "math", "emoji", "fangsong"]);
|
|
2484
|
+
function cssFontFamily(family) {
|
|
2485
|
+
const f = family.trim();
|
|
2486
|
+
if (GENERIC.has(f.toLowerCase()) || f.includes(",") || /^["']/.test(f)) return f;
|
|
2487
|
+
return /^[A-Za-z_-][\w -]*$/.test(f) && !/\s{2}/.test(f) ? f : `"${f.replace(/"/g, '\\"')}"`;
|
|
2488
|
+
}
|
|
2489
|
+
function cssFont(font, family = font.family) {
|
|
2490
|
+
return `${font.italic ? "italic " : ""}${font.weight} ${font.size}px ${cssFontFamily(family)}`;
|
|
2491
|
+
}
|
|
2492
|
+
function createCanvasTextShaper(options = {}) {
|
|
2493
|
+
const make = options.createCanvas ?? ((w, h) => {
|
|
2494
|
+
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(w, h);
|
|
2495
|
+
if (typeof document !== "undefined") {
|
|
2496
|
+
const c = document.createElement("canvas");
|
|
2497
|
+
c.width = w;
|
|
2498
|
+
c.height = h;
|
|
2499
|
+
return c;
|
|
2500
|
+
}
|
|
2501
|
+
return null;
|
|
2502
|
+
});
|
|
2503
|
+
const canvas = make(1, 1);
|
|
2504
|
+
const ctx = canvas?.getContext("2d");
|
|
2505
|
+
if (!ctx || typeof ctx.measureText !== "function") return null;
|
|
2506
|
+
const fallback = options.fallbackFamily ?? "sans-serif";
|
|
2507
|
+
const available = /* @__PURE__ */ new Map();
|
|
2508
|
+
const widths = /* @__PURE__ */ new Map();
|
|
2509
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
2510
|
+
const probe = "mmmmmmmmmmlli1WQ@#";
|
|
2511
|
+
const width = (font, text) => {
|
|
2512
|
+
ctx.font = font;
|
|
2513
|
+
return ctx.measureText(text).width;
|
|
2514
|
+
};
|
|
2515
|
+
const isAvailable = (family) => {
|
|
2516
|
+
const f = family.trim();
|
|
2517
|
+
if (GENERIC.has(f.toLowerCase()) || f.includes(",")) return true;
|
|
2518
|
+
let hit = available.get(f);
|
|
2519
|
+
if (hit === void 0) {
|
|
2520
|
+
const q = cssFontFamily(f);
|
|
2521
|
+
hit = width(`72px ${q}, monospace`, probe) !== width("72px monospace", probe) || width(`72px ${q}, serif`, probe) !== width("72px serif", probe);
|
|
2522
|
+
available.set(f, hit);
|
|
2523
|
+
}
|
|
2524
|
+
return hit;
|
|
2525
|
+
};
|
|
2526
|
+
return {
|
|
2527
|
+
id: "canvas",
|
|
2528
|
+
resolve(font) {
|
|
2529
|
+
const ok = isAvailable(font.family);
|
|
2530
|
+
return { family: ok ? font.family : fallback, available: ok };
|
|
2531
|
+
},
|
|
2532
|
+
metrics(font) {
|
|
2533
|
+
const key = cssFont(font);
|
|
2534
|
+
let m = metrics.get(key);
|
|
2535
|
+
if (!m) {
|
|
2536
|
+
ctx.font = key;
|
|
2537
|
+
ctx.textBaseline = "alphabetic";
|
|
2538
|
+
const t = ctx.measureText("Hg");
|
|
2539
|
+
const ascent = t.emHeightAscent ?? t.fontBoundingBoxAscent ?? font.size * 0.8;
|
|
2540
|
+
const descent = t.emHeightDescent ?? t.fontBoundingBoxDescent ?? font.size * 0.2;
|
|
2541
|
+
m = { ascent: Number.isFinite(ascent) ? ascent : font.size * 0.8, descent: Number.isFinite(descent) ? descent : font.size * 0.2 };
|
|
2542
|
+
metrics.set(key, m);
|
|
2543
|
+
}
|
|
2544
|
+
return m;
|
|
2545
|
+
},
|
|
2546
|
+
measure(text, font) {
|
|
2547
|
+
const key = `${cssFont(font)}|${text}`;
|
|
2548
|
+
let w = widths.get(key);
|
|
2549
|
+
if (!w) {
|
|
2550
|
+
ctx.font = cssFont(font);
|
|
2551
|
+
w = [0];
|
|
2552
|
+
for (let i = 1; i <= text.length; i++) w.push(ctx.measureText(text.slice(0, i)).width);
|
|
2553
|
+
if (widths.size > 2e3) widths.clear();
|
|
2554
|
+
widths.set(key, w);
|
|
2555
|
+
}
|
|
2556
|
+
return w;
|
|
2557
|
+
},
|
|
2558
|
+
draw(target, text, font, x, y) {
|
|
2559
|
+
target.font = cssFont(font);
|
|
2560
|
+
target.textBaseline = "alphabetic";
|
|
2561
|
+
target.textAlign = "left";
|
|
2562
|
+
target.fillText(text, x, y);
|
|
2563
|
+
},
|
|
2564
|
+
families() {
|
|
2565
|
+
const out = /* @__PURE__ */ new Set(["sans-serif", "serif", "monospace"]);
|
|
2566
|
+
const fonts = typeof document !== "undefined" ? document.fonts : void 0;
|
|
2567
|
+
if (fonts && typeof fonts.forEach === "function") fonts.forEach((f) => out.add(f.family.replace(/^["']|["']$/g, "")));
|
|
2568
|
+
return [...out];
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function createOutlineTextShaper(fonts, options = {}) {
|
|
2573
|
+
if (!fonts.length) throw new Error("createOutlineTextShaper needs at least one font.");
|
|
2574
|
+
const entries = fonts.map((f) => {
|
|
2575
|
+
const e = "face" in f ? f : { face: f };
|
|
2576
|
+
const names = e.face.names;
|
|
2577
|
+
const family = e.family ?? names?.fontFamily?.en ?? Object.values(names?.fontFamily ?? {})[0] ?? "Font";
|
|
2578
|
+
const weight = e.weight ?? e.face.tables?.os2?.usWeightClass ?? 400;
|
|
2579
|
+
const italic = e.italic ?? Boolean((e.face.tables?.os2?.fsSelection ?? 0) & 1);
|
|
2580
|
+
const postScript = names?.postScriptName?.en ?? Object.values(names?.postScriptName ?? {})[0];
|
|
2581
|
+
return { face: e.face, family, key: family.toLowerCase(), weight, italic, postScript };
|
|
2582
|
+
});
|
|
2583
|
+
const fallbackKey = (options.fallback ?? entries[0].family).toLowerCase();
|
|
2584
|
+
const pick2 = (font) => {
|
|
2585
|
+
if (font.postScriptName) {
|
|
2586
|
+
const exact = entries.find((e) => e.postScript === font.postScriptName);
|
|
2587
|
+
if (exact) return { entry: exact, available: true };
|
|
2588
|
+
}
|
|
2589
|
+
const key = font.family.trim().toLowerCase();
|
|
2590
|
+
let pool = entries.filter((e) => e.key === key);
|
|
2591
|
+
const available = pool.length > 0;
|
|
2592
|
+
if (!available) pool = entries.filter((e) => e.key === fallbackKey);
|
|
2593
|
+
if (!pool.length) pool = entries;
|
|
2594
|
+
const styled = pool.filter((e) => e.italic === font.italic);
|
|
2595
|
+
const list = styled.length ? styled : pool;
|
|
2596
|
+
const entry = list.reduce((best, e) => Math.abs(e.weight - font.weight) < Math.abs(best.weight - font.weight) ? e : best);
|
|
2597
|
+
return { entry, available };
|
|
2598
|
+
};
|
|
2599
|
+
const toPath = (cmds) => {
|
|
2600
|
+
const out = [];
|
|
2601
|
+
for (const c of cmds) {
|
|
2602
|
+
if (c.type === "M" || c.type === "L") out.push({ type: c.type, x: c.x, y: c.y });
|
|
2603
|
+
else if (c.type === "C") out.push({ type: "C", x1: c.x1, y1: c.y1, x2: c.x2, y2: c.y2, x: c.x, y: c.y });
|
|
2604
|
+
else if (c.type === "Q") out.push({ type: "Q", x1: c.x1, y1: c.y1, x: c.x, y: c.y });
|
|
2605
|
+
else out.push({ type: "Z" });
|
|
2606
|
+
}
|
|
2607
|
+
return out;
|
|
2608
|
+
};
|
|
2609
|
+
return {
|
|
2610
|
+
id: options.id ?? `outline:${entries.map((e) => `${e.family}/${e.weight}${e.italic ? "i" : ""}`).join(",")}`,
|
|
2611
|
+
resolve(font) {
|
|
2612
|
+
const { entry, available } = pick2(font);
|
|
2613
|
+
return { family: entry.family, available };
|
|
2614
|
+
},
|
|
2615
|
+
metrics(font) {
|
|
2616
|
+
const { face } = pick2(font).entry;
|
|
2617
|
+
const k = font.size / face.unitsPerEm;
|
|
2618
|
+
return { ascent: face.ascender * k, descent: -face.descender * k };
|
|
2619
|
+
},
|
|
2620
|
+
measure(text, font) {
|
|
2621
|
+
const { face } = pick2(font).entry;
|
|
2622
|
+
const out = [0];
|
|
2623
|
+
for (let i = 1; i <= text.length; i++) out.push(face.getAdvanceWidth(text.slice(0, i), font.size, { kerning: true }));
|
|
2624
|
+
return out;
|
|
2625
|
+
},
|
|
2626
|
+
outline(text, font, x, y) {
|
|
2627
|
+
return toPath(pick2(font).entry.face.getPath(text, x, y, font.size, { kerning: true }).commands);
|
|
2628
|
+
},
|
|
2629
|
+
families() {
|
|
2630
|
+
return [...new Set(entries.map((e) => e.family))];
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2444
2635
|
// src/image-editor/free-transform.ts
|
|
2445
2636
|
var FREE_TRANSFORM_CONTENTS = ["auto", "layer", "selection", "selection-copy", "selection-outline"];
|
|
2446
2637
|
var fail = (message) => {
|
|
@@ -2512,7 +2703,7 @@ var FreeTransformSession = class {
|
|
|
2512
2703
|
}
|
|
2513
2704
|
const targetId = init.layerId ?? host.activeLayerId();
|
|
2514
2705
|
const target = targetId && content !== "selection-outline" ? findLayer(host.layers(), targetId)?.layer : void 0;
|
|
2515
|
-
const smartTarget = (target?.type === "smart" || target?.type === "shape") && (init.channel ?? (target.id === host.activeLayerId() ? host.activeTarget() : "pixels")) !== "mask";
|
|
2706
|
+
const smartTarget = (target?.type === "smart" || target?.type === "shape" || target?.type === "type") && (init.channel ?? (target.id === host.activeLayerId() ? host.activeTarget() : "pixels")) !== "mask";
|
|
2516
2707
|
if (content === "auto") content = selected && !smartTarget ? "selection" : "layer";
|
|
2517
2708
|
this.content = content;
|
|
2518
2709
|
if (content !== "layer" && !selected) fail(`Nothing is selected; ${content} needs a selection. Select first, or use content layer.`);
|
|
@@ -2539,7 +2730,7 @@ var FreeTransformSession = class {
|
|
|
2539
2730
|
const linked = mask?.linked !== false;
|
|
2540
2731
|
const maskAlone = channel === "mask" && (!linked || layer.type !== "pixel");
|
|
2541
2732
|
if (channel === "mask" && !mask) fail(layer.mask?.source ? `Layer "${layer.name || id}" has a depth mask, which is generated, not transformed.` : `Layer "${layer.name || id}" has no mask to transform; add one with add_layer_mask, or transform its pixels.`);
|
|
2542
|
-
if (!maskAlone && layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape") fail(`Layer "${layer.name || id}" is a ${layer.type} layer; Free Transform works on pixel, shape and smart object layers, layer masks (channel mask) and selections.`);
|
|
2733
|
+
if (!maskAlone && layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape" && layer.type !== "type") fail(`Layer "${layer.name || id}" is a ${layer.type} layer; Free Transform works on pixel, shape, type and smart object layers, layer masks (channel mask) and selections.`);
|
|
2543
2734
|
if (!layer.visible && content !== "selection-copy") fail(`Layer "${layer.name || id}" is hidden; show it before you transform it.`);
|
|
2544
2735
|
if (content !== "selection-copy") {
|
|
2545
2736
|
requireUnlocked(layer, "position", "transform it");
|
|
@@ -2561,6 +2752,26 @@ var FreeTransformSession = class {
|
|
|
2561
2752
|
this.shown = this.startLayers;
|
|
2562
2753
|
return;
|
|
2563
2754
|
}
|
|
2755
|
+
if (layer.type === "type" && !maskAlone) {
|
|
2756
|
+
if (content !== "layer") fail(`Layer "${layer.name || id}" is a type layer, which transforms as a whole; use content layer (or rasterize it to transform part of it).`);
|
|
2757
|
+
const frame = typeFrame(layer, host.textEnv().shaper);
|
|
2758
|
+
const m = textMatrix(layer);
|
|
2759
|
+
const at = (x, y) => ({ x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] });
|
|
2760
|
+
this.typeTarget = layer;
|
|
2761
|
+
this.typeLayer = true;
|
|
2762
|
+
this.box = frame;
|
|
2763
|
+
this.startGeometry = {
|
|
2764
|
+
box: frame,
|
|
2765
|
+
corners: [at(frame.x, frame.y), at(frame.x + frame.width, frame.y), at(frame.x + frame.width, frame.y + frame.height), at(frame.x, frame.y + frame.height)],
|
|
2766
|
+
warp: null
|
|
2767
|
+
};
|
|
2768
|
+
this.geometryValue = this.startGeometry;
|
|
2769
|
+
this.startInterp = this.interp;
|
|
2770
|
+
this.moves = mask && linked ? ["pixels", "mask"] : ["pixels"];
|
|
2771
|
+
if (mask && linked) this.parts.push({ what: "mask", offset: o, moving: mask.surface, base: null, reduced: /* @__PURE__ */ new Map() });
|
|
2772
|
+
this.shown = this.startLayers;
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2564
2775
|
if (layer.type === "shape" && !maskAlone) {
|
|
2565
2776
|
if (content !== "layer") fail(`Layer "${layer.name || id}" is a shape layer, which transforms as a whole; use content layer (or rasterize it to transform part of it).`);
|
|
2566
2777
|
const g = shapeGeometryBounds(layer);
|
|
@@ -2663,6 +2874,9 @@ var FreeTransformSession = class {
|
|
|
2663
2874
|
/** True when the layer is a shape layer (the transform maps its paths and renders them again). */
|
|
2664
2875
|
shapeLayer = false;
|
|
2665
2876
|
shape = null;
|
|
2877
|
+
/** True when the layer is a type layer (the transform sets its text matrix; affine only). */
|
|
2878
|
+
typeLayer = false;
|
|
2879
|
+
typeTarget = null;
|
|
2666
2880
|
geometryValue;
|
|
2667
2881
|
smart = null;
|
|
2668
2882
|
smartReduced = /* @__PURE__ */ new Map();
|
|
@@ -2711,6 +2925,9 @@ var FreeTransformSession = class {
|
|
|
2711
2925
|
};
|
|
2712
2926
|
if (next.corners.length !== 4 || !next.corners.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) fail("corners must be four finite points: top-left, top-right, bottom-right, bottom-left.");
|
|
2713
2927
|
if (!transformMap(next)) fail("These corners fold the frame over itself or through the horizon; keep the quad convex.");
|
|
2928
|
+
if (this.typeTarget && (hasWarp(next) || !matrixFromCorners(next.box, next.corners))) {
|
|
2929
|
+
fail("Type layers take scale, rotate, skew and flips only (the corners must stay a parallelogram, no warp); rasterize the layer (rasterize_layer) for distort, perspective or warp.");
|
|
2930
|
+
}
|
|
2714
2931
|
this.geometryValue = next;
|
|
2715
2932
|
if (preview !== false) this.render(preview);
|
|
2716
2933
|
return next;
|
|
@@ -2718,6 +2935,7 @@ var FreeTransformSession = class {
|
|
|
2718
2935
|
/** Resamples every part for the current geometry and shows it (or returns it, for commit). */
|
|
2719
2936
|
resample(final, preview = {}) {
|
|
2720
2937
|
if (this.smart) return { layers: this.resampleSmart(final, preview), selection: this.startSelection };
|
|
2938
|
+
if (this.typeTarget) return { layers: this.resampleType(), selection: this.startSelection };
|
|
2721
2939
|
const g = this.geometryValue;
|
|
2722
2940
|
const identity = isIdentityTransform(g);
|
|
2723
2941
|
const warp = hasWarp(g);
|
|
@@ -2798,6 +3016,27 @@ var FreeTransformSession = class {
|
|
|
2798
3016
|
const surface = renderShape(moved, this.host.shapeEnv(), final ? {} : { clip: preview.clip });
|
|
2799
3017
|
return mapLayer(this.startLayers, layer.id, () => ({ ...moved, surface }));
|
|
2800
3018
|
}
|
|
3019
|
+
/**
|
|
3020
|
+
* A type layer with its text matrix set from the corners (affine only: scale, rotate, skew and
|
|
3021
|
+
* flips), drawn again; a linked mask follows by the change's homography.
|
|
3022
|
+
*/
|
|
3023
|
+
resampleType() {
|
|
3024
|
+
const layer = this.typeTarget;
|
|
3025
|
+
const g = this.geometryValue;
|
|
3026
|
+
if (hasWarp(g)) fail("Type layers cannot be warped here; rasterize the layer first (rasterize_layer), or transform it with scale, rotate and skew.");
|
|
3027
|
+
const m = matrixFromCorners(g.box, g.corners);
|
|
3028
|
+
if (!m) fail("Type layers take scale, rotate, skew and flips only (the corners must stay a parallelogram); rasterize the layer for distort or perspective.");
|
|
3029
|
+
const next = { ...layer, transform: m };
|
|
3030
|
+
delete next.annotation;
|
|
3031
|
+
let layers = mapLayer(this.startLayers, layer.id, () => rerenderText(next, this.host.textEnv()));
|
|
3032
|
+
const change = multiplyHomographies(homographyFromAffine(invertMatrix(textMatrix(layer))), homographyFromAffine(m));
|
|
3033
|
+
for (const part of this.parts) {
|
|
3034
|
+
if (part.what !== "mask") continue;
|
|
3035
|
+
const surface = transformSurface(part.moving, change, { interpolation: this.interp === "nearest" ? "nearest" : "bilinear" });
|
|
3036
|
+
layers = mapLayer(layers, layer.id, (l) => ({ ...l, mask: { ...l.mask, surface } }));
|
|
3037
|
+
}
|
|
3038
|
+
return layers;
|
|
3039
|
+
}
|
|
2801
3040
|
/** A smart object re-rendered from its contents for the current geometry (previews leave the smart filters out). */
|
|
2802
3041
|
resampleSmart(final, preview) {
|
|
2803
3042
|
const layer = this.smart;
|
|
@@ -2821,7 +3060,7 @@ var FreeTransformSession = class {
|
|
|
2821
3060
|
/** Whether the geometry (or the interpolation of a smart object) differs from where it started. */
|
|
2822
3061
|
isChanged() {
|
|
2823
3062
|
const g = this.geometryValue;
|
|
2824
|
-
if (!this.smart) return !isIdentityTransform(g);
|
|
3063
|
+
if (!this.smart && !this.typeTarget) return !isIdentityTransform(g);
|
|
2825
3064
|
const a = this.startGeometry;
|
|
2826
3065
|
const same = g.corners.every((c, i) => Math.abs(c.x - a.corners[i].x) < 1e-9 && Math.abs(c.y - a.corners[i].y) < 1e-9);
|
|
2827
3066
|
const warp = (w, box) => w && hasWarp({ box, corners: a.corners, warp: w }) ? JSON.stringify(w) : "";
|
|
@@ -2858,7 +3097,7 @@ var FreeTransformSession = class {
|
|
|
2858
3097
|
const id = this.copyId ?? this.layerId;
|
|
2859
3098
|
const layer = id ? findLayer(layers, id)?.layer : void 0;
|
|
2860
3099
|
if (this.moves.includes("pixels") && layer?.type === "pixel") bounds = toDoc(contentBounds(this.host.pixelsOf(layer)), layer.offset);
|
|
2861
|
-
else if (this.moves.includes("pixels") && (layer?.type === "smart" || layer?.type === "shape")) bounds = contentBounds(layer.surface);
|
|
3100
|
+
else if (this.moves.includes("pixels") && (layer?.type === "smart" || layer?.type === "shape" || layer?.type === "type")) bounds = contentBounds(layer.surface);
|
|
2862
3101
|
else if (this.moves.includes("mask") && layer?.mask) bounds = toDoc(contentBounds(layer.mask.surface), layer.type === "pixel" ? layer.offset : { x: 0, y: 0 });
|
|
2863
3102
|
else if (this.moves.includes("selection")) bounds = selection?.bounds ? { ...selection.bounds } : null;
|
|
2864
3103
|
return {
|
|
@@ -3112,6 +3351,7 @@ var ImageEditorController = class {
|
|
|
3112
3351
|
compositor = new LayerCompositor();
|
|
3113
3352
|
constructor(options = {}) {
|
|
3114
3353
|
this.maxHistoryDepth = options.maxHistoryDepth ?? 50;
|
|
3354
|
+
if (options.textShaper !== void 0) this.textShaperValue = options.textShaper;
|
|
3115
3355
|
this.maxHistoryBytes = options.maxHistoryBytes ?? 512 * 1024 * 1024;
|
|
3116
3356
|
this.onStateChange = options.onStateChange;
|
|
3117
3357
|
this.renderer = new ImageEditorRenderer();
|
|
@@ -3818,6 +4058,21 @@ var ImageEditorController = class {
|
|
|
3818
4058
|
const b = shapeGeometryBounds(moved) ?? { x: 0, y: 0 };
|
|
3819
4059
|
return { offset: { x: Math.round(b.x), y: Math.round(b.y) }, bounds: this.getLayerBounds(id) };
|
|
3820
4060
|
}
|
|
4061
|
+
if (layer.type === "type") {
|
|
4062
|
+
const m = layer.transform ?? [1, 0, 0, 1, 0, 0];
|
|
4063
|
+
const dx = move.x !== void 0 ? move.x - m[4] : move.dx ?? 0;
|
|
4064
|
+
const dy = move.y !== void 0 ? move.y - m[5] : move.dy ?? 0;
|
|
4065
|
+
if (!Number.isFinite(dx) || !Number.isFinite(dy)) throw new LayerOperationError("x and y must be numbers.");
|
|
4066
|
+
requireUnlocked(layer, "position", "move it");
|
|
4067
|
+
const mx = Math.round(dx);
|
|
4068
|
+
const my = Math.round(dy);
|
|
4069
|
+
let moved = { ...transformTypeLayer(layer, [1, 0, 0, 1, mx, my]), surface: translateSurface(layer.surface, mx, my) };
|
|
4070
|
+
const mask = layer.mask;
|
|
4071
|
+
if (mask && mask.linked !== false && !mask.source && (mx || my)) moved = { ...moved, mask: { ...mask, surface: translateSurface(mask.surface, mx, my) } };
|
|
4072
|
+
this.commitLayers(mapLayer(this.state.layers, id, () => moved), void 0, commitToHistory);
|
|
4073
|
+
const t = moved.transform ?? [1, 0, 0, 1, 0, 0];
|
|
4074
|
+
return { offset: { x: Math.round(t[4]), y: Math.round(t[5]) }, bounds: this.getLayerBounds(id) };
|
|
4075
|
+
}
|
|
3821
4076
|
const current = layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
|
|
3822
4077
|
const x = move.x ?? current.x + (move.dx ?? 0);
|
|
3823
4078
|
const y = move.y ?? current.y + (move.dy ?? 0);
|
|
@@ -3966,6 +4221,7 @@ var ImageEditorController = class {
|
|
|
3966
4221
|
canvas: () => this.naturalSize,
|
|
3967
4222
|
pixelsOf: (layer) => this.surfaceOf(layer),
|
|
3968
4223
|
shapeEnv: () => this.shapeEnv(),
|
|
4224
|
+
textEnv: () => this.textEnv(),
|
|
3969
4225
|
capture: () => this.captureHistoryEntry(),
|
|
3970
4226
|
show: (layers, selection, active) => {
|
|
3971
4227
|
this.state.layers = layers;
|
|
@@ -4066,7 +4322,7 @@ var ImageEditorController = class {
|
|
|
4066
4322
|
if (turn !== 90 && turn !== -90 && turn !== 180) throw new LayerOperationError(`turn must be 90, -90 or 180, got ${String(turn)}.`);
|
|
4067
4323
|
const session = this.beginFreeTransform({ interpolation: "nearest", ...init });
|
|
4068
4324
|
try {
|
|
4069
|
-
const b = session.smartObject ? transformedBox(session.startGeometry) ?? session.box : session.box;
|
|
4325
|
+
const b = session.smartObject || session.typeLayer ? transformedBox(session.startGeometry) ?? session.box : session.box;
|
|
4070
4326
|
const cx = b.x + b.width / 2;
|
|
4071
4327
|
const cy = b.y + b.height / 2;
|
|
4072
4328
|
const ox = turn === 180 ? 0 : Math.round(cx - b.height / 2) - (cx - b.height / 2);
|
|
@@ -4786,6 +5042,7 @@ var ImageEditorController = class {
|
|
|
4786
5042
|
} else {
|
|
4787
5043
|
if (layer.type === "smart") throw new LayerOperationError(`Layer "${layer.name || id}" is a smart object; its pixels come from its contents. Rasterize it (rasterize_smart_object), edit its contents (edit_smart_object_contents), or ${action} on its mask or filter mask.`);
|
|
4788
5044
|
if (layer.type === "shape") throw new LayerOperationError(`Layer "${layer.name || id}" is a shape layer; its pixels are drawn from its paths. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`);
|
|
5045
|
+
if (layer.type === "type") throw new LayerOperationError(`Layer "${layer.name || id}" is a type layer; its pixels are drawn from its text. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`);
|
|
4789
5046
|
if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is a ${layer.type} layer; ${action} on a pixel layer, or on this layer's mask (channel mask).`);
|
|
4790
5047
|
requireUnlocked(layer, "pixels", action);
|
|
4791
5048
|
}
|
|
@@ -5491,7 +5748,11 @@ var ImageEditorController = class {
|
|
|
5491
5748
|
// --- Annotations & Layers ---
|
|
5492
5749
|
/** The layer showing annotation `id`: an annotation layer, or a shape layer migrated from it. */
|
|
5493
5750
|
annotationLayer(id) {
|
|
5494
|
-
return flattenLayers(this.state.layers).find((l) => (l.type === "annotation" || l.type === "shape") && annotationOfLayer(l)?.id === id);
|
|
5751
|
+
return flattenLayers(this.state.layers).find((l) => (l.type === "annotation" || l.type === "shape" || l.type === "type") && annotationOfLayer(l)?.id === id);
|
|
5752
|
+
}
|
|
5753
|
+
/** What migrating an annotation needs: the canvas size, patterns and the text shaper. */
|
|
5754
|
+
annotationEnv() {
|
|
5755
|
+
return { ...this.shapeEnv(), ...this.textEnv() };
|
|
5495
5756
|
}
|
|
5496
5757
|
/**
|
|
5497
5758
|
* Adds an annotation as its own layer (top of the stack unless `aboveId` is given). Pen,
|
|
@@ -5503,7 +5764,7 @@ var ImageEditorController = class {
|
|
|
5503
5764
|
this.recordHistory();
|
|
5504
5765
|
const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
5505
5766
|
const fullAnnotation = { ...annotation, id };
|
|
5506
|
-
const layer = annotationLayerFor(fullAnnotation, this.
|
|
5767
|
+
const layer = annotationLayerFor(fullAnnotation, this.annotationEnv());
|
|
5507
5768
|
const loc = placement.aboveId ? findLayer(this.state.layers, placement.aboveId) : null;
|
|
5508
5769
|
this.state.layers = loc ? insertLayer(this.state.layers, layer, loc.parent?.id ?? null, loc.index + 1) : insertLayer(this.state.layers, layer, null);
|
|
5509
5770
|
this.state.selectedAnnotationId = id;
|
|
@@ -5523,24 +5784,14 @@ var ImageEditorController = class {
|
|
|
5523
5784
|
}
|
|
5524
5785
|
this.notify();
|
|
5525
5786
|
}
|
|
5526
|
-
/** `layer` (an annotation or
|
|
5787
|
+
/** `layer` (an annotation, or a shape or type layer migrated from one) showing `annotation` instead, keeping its other properties. */
|
|
5527
5788
|
layerForAnnotation(layer, annotation, name) {
|
|
5528
|
-
const
|
|
5529
|
-
const {
|
|
5530
|
-
const
|
|
5531
|
-
if (
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
const fresh = rerenderShape({ ...base, components: shape.components, fill: shape.fill, stroke: shape.stroke }, this.shapeEnv());
|
|
5535
|
-
delete fresh.fillRule;
|
|
5536
|
-
return { ...fresh, annotation };
|
|
5537
|
-
}
|
|
5538
|
-
const created = createShapeLayer({ ...shape, name, blend: base.blend }, this.shapeEnv(), layer.id);
|
|
5539
|
-
const { type: _t2, ...rest2 } = base;
|
|
5540
|
-
return { ...rest2, ...created, annotation };
|
|
5541
|
-
}
|
|
5542
|
-
const { type: _t, components: _c, fill: _f, stroke: _s, surface: _sf, offset: _o, fillRule: _r, fillOpacity: _fo, psd: _p, ...rest } = props;
|
|
5543
|
-
return { ...rest, type: "annotation", name, blend: layer.type === "shape" ? "normal" : layer.blend, annotation };
|
|
5789
|
+
const fresh = annotationLayerFor(annotation, this.annotationEnv());
|
|
5790
|
+
const keep = { id: layer.id, visible: layer.visible, opacity: layer.opacity };
|
|
5791
|
+
for (const k of ["clipped", "mask", "locks", "effects"]) if (layer[k] !== void 0) keep[k] = layer[k];
|
|
5792
|
+
if (fresh.type !== "annotation" && layer.type !== "annotation" && layer.fillOpacity !== void 0) keep.fillOpacity = layer.fillOpacity;
|
|
5793
|
+
const blend = fresh.type === "annotation" ? layer.type === "annotation" ? layer.blend : "normal" : layer.type === "annotation" && layer.blend !== "normal" ? layer.blend : fresh.blend;
|
|
5794
|
+
return { ...fresh, ...keep, name, blend };
|
|
5544
5795
|
}
|
|
5545
5796
|
removeAnnotation(id) {
|
|
5546
5797
|
this.recordHistory();
|
|
@@ -5555,7 +5806,7 @@ var ImageEditorController = class {
|
|
|
5555
5806
|
clearAnnotations() {
|
|
5556
5807
|
this.recordHistory();
|
|
5557
5808
|
let layers = this.state.layers;
|
|
5558
|
-
for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
5809
|
+
for (const l of flattenLayers(layers)) if (l.type === "annotation" || (l.type === "shape" || l.type === "type") && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
5559
5810
|
this.state.layers = layers;
|
|
5560
5811
|
this.state.selectedAnnotationId = null;
|
|
5561
5812
|
this.notify();
|
|
@@ -5794,6 +6045,233 @@ var ImageEditorController = class {
|
|
|
5794
6045
|
this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
|
|
5795
6046
|
return target.id;
|
|
5796
6047
|
}
|
|
6048
|
+
// --- Type layers ---
|
|
6049
|
+
textShaperValue;
|
|
6050
|
+
/** The shaper that lays out and draws type layers (`null` when there is none here). */
|
|
6051
|
+
getTextShaper() {
|
|
6052
|
+
if (this.textShaperValue === void 0) this.textShaperValue = createCanvasTextShaper({ createCanvas: (w, h) => this.renderer.createCanvas(w, h) });
|
|
6053
|
+
return this.textShaperValue;
|
|
6054
|
+
}
|
|
6055
|
+
/** Sets the text shaper (fonts) type layers lay out and draw with; existing layers redraw on their next edit. */
|
|
6056
|
+
setTextShaper(shaper) {
|
|
6057
|
+
this.textShaperValue = shaper;
|
|
6058
|
+
this.notify();
|
|
6059
|
+
}
|
|
6060
|
+
/** What type layers lay out and draw with. */
|
|
6061
|
+
textEnv() {
|
|
6062
|
+
return { ...this.naturalSize, shaper: this.getTextShaper(), createCanvas: (w, h) => this.renderer.createCanvas(w, h) };
|
|
6063
|
+
}
|
|
6064
|
+
/** Font families the shaper offers (generic families at least). */
|
|
6065
|
+
listFonts() {
|
|
6066
|
+
return this.getTextShaper()?.families?.() ?? ["sans-serif", "serif", "monospace"];
|
|
6067
|
+
}
|
|
6068
|
+
typeInfo(id) {
|
|
6069
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6070
|
+
const ranges = (runs) => {
|
|
6071
|
+
let at = 0;
|
|
6072
|
+
return runs.map((r) => {
|
|
6073
|
+
const out = { start: at, end: at + r.length, style: JSON.parse(JSON.stringify(r.style)) };
|
|
6074
|
+
at += r.length;
|
|
6075
|
+
return out;
|
|
6076
|
+
});
|
|
6077
|
+
};
|
|
6078
|
+
const shaper = this.getTextShaper();
|
|
6079
|
+
let lines = [];
|
|
6080
|
+
let missing = layer.missingFonts ?? [];
|
|
6081
|
+
if (shaper) {
|
|
6082
|
+
const layout = layoutText(layer, shaper);
|
|
6083
|
+
const r = (n) => Math.round(n * 1e3) / 1e3;
|
|
6084
|
+
lines = layout.lines.map((l) => ({ start: l.start, end: l.end, baseline: r(l.baseline), width: r(l.width) }));
|
|
6085
|
+
if (layer.rendering === "stored") missing = layout.missingFonts;
|
|
6086
|
+
}
|
|
6087
|
+
const info = {
|
|
6088
|
+
id: layer.id,
|
|
6089
|
+
name: layer.name,
|
|
6090
|
+
text: layer.text,
|
|
6091
|
+
runs: ranges(layer.runs),
|
|
6092
|
+
paragraphs: ranges(layer.paragraphs),
|
|
6093
|
+
frame: { ...layer.frame },
|
|
6094
|
+
transform: [...layer.transform ?? [1, 0, 0, 1, 0, 0]],
|
|
6095
|
+
lines,
|
|
6096
|
+
bounds: this.getLayerBounds(id),
|
|
6097
|
+
missingFonts: [...missing],
|
|
6098
|
+
rendering: layer.rendering === "stored" ? "stored" : "live"
|
|
6099
|
+
};
|
|
6100
|
+
if (layer.annotation) info.annotationId = layer.annotation.id;
|
|
6101
|
+
return info;
|
|
6102
|
+
}
|
|
6103
|
+
/** A type layer's text, runs, paragraphs, frame, lines and bounds. */
|
|
6104
|
+
getTextLayer(id) {
|
|
6105
|
+
return this.typeInfo(id);
|
|
6106
|
+
}
|
|
6107
|
+
/**
|
|
6108
|
+
* Adds a type layer (one undo step) and makes it active: point text at `x`, `y`, or paragraph
|
|
6109
|
+
* text wrapped in `box`. Refused without a text shaper. Returns its id.
|
|
6110
|
+
*/
|
|
6111
|
+
addTextLayer(init) {
|
|
6112
|
+
const text = normalizeText(init.text);
|
|
6113
|
+
if (!Number.isFinite(init.x) || !Number.isFinite(init.y)) throw new LayerOperationError("x and y must be numbers (document pixels).");
|
|
6114
|
+
const style = patchTextStyle({ ...DEFAULT_TEXT_STYLE, color: this.state.paint.foreground }, init.style ?? {});
|
|
6115
|
+
const paragraph = patchParagraphStyle({}, init.paragraph ?? {});
|
|
6116
|
+
let frame = { kind: "point" };
|
|
6117
|
+
if (init.box) {
|
|
6118
|
+
const { width, height } = init.box;
|
|
6119
|
+
if (!(Number.isFinite(width) && width >= 1 && Number.isFinite(height) && height >= 1)) throw new LayerOperationError("box.width and box.height must be at least 1 pixel.");
|
|
6120
|
+
frame = { kind: "box", width, height };
|
|
6121
|
+
}
|
|
6122
|
+
if (init.rotation !== void 0 && !Number.isFinite(init.rotation)) throw new LayerOperationError("rotation must be a number of degrees.");
|
|
6123
|
+
if (init.opacity !== void 0 && !(Number.isFinite(init.opacity) && init.opacity >= 0 && init.opacity <= 1)) throw new LayerOperationError("opacity must be between 0 and 1.");
|
|
6124
|
+
if (init.blend !== void 0 && !isBlendMode(init.blend)) throw new LayerOperationError(`Unknown blend mode "${String(init.blend)}".`);
|
|
6125
|
+
const t = [1, 0, 0, 1, init.x, init.y];
|
|
6126
|
+
const transform = init.rotation ? multiplyMatrices(t, rotateMatrix(init.rotation, { x: init.x, y: init.y })) : t;
|
|
6127
|
+
const layer = createTypeLayer({ text, runs: [{ length: text.length, style }], paragraphs: [{ length: text.length, style: paragraph }], frame, transform, name: init.name ?? typeLayerName(text) }, this.textEnv());
|
|
6128
|
+
if (init.opacity !== void 0) layer.opacity = init.opacity;
|
|
6129
|
+
if (init.blend !== void 0) layer.blend = init.blend;
|
|
6130
|
+
const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(init));
|
|
6131
|
+
this.commitLayers(next, () => {
|
|
6132
|
+
this.state.activeLayerId = layer.id;
|
|
6133
|
+
this.state.activeTarget = "pixels";
|
|
6134
|
+
});
|
|
6135
|
+
return layer.id;
|
|
6136
|
+
}
|
|
6137
|
+
/**
|
|
6138
|
+
* Replaces text [start, end) (default all of it) with `text`; inserted text takes the style at
|
|
6139
|
+
* `start`. One undo step unless `commitToHistory` is false (typing). Returns the layer.
|
|
6140
|
+
*/
|
|
6141
|
+
setText(id, text, range = {}, commitToHistory = true) {
|
|
6142
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6143
|
+
const insert = normalizeText(text);
|
|
6144
|
+
const [start, end] = textRange(layer.text, range.start, range.end);
|
|
6145
|
+
const { layers } = updateTypeLayer(this.state.layers, id, (l) => replaceTextRange(l, start, end, insert), this.textEnv(), "edit its text");
|
|
6146
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
6147
|
+
return this.typeInfo(id);
|
|
6148
|
+
}
|
|
6149
|
+
/** Sets character style fields over [start, end) (default all), one undo step. Returns the layer. */
|
|
6150
|
+
setTextStyle(id, style, range = {}, commitToHistory = true) {
|
|
6151
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6152
|
+
const [start, end] = textRange(layer.text, range.start, range.end);
|
|
6153
|
+
if (!Object.keys(style).length) throw new LayerOperationError("Give at least one style field to set.");
|
|
6154
|
+
patchTextStyle(DEFAULT_TEXT_STYLE, style);
|
|
6155
|
+
const { layers } = updateTypeLayer(
|
|
6156
|
+
this.state.layers,
|
|
6157
|
+
id,
|
|
6158
|
+
(l) => {
|
|
6159
|
+
if (start === end) {
|
|
6160
|
+
if (l.text.length) throw new LayerOperationError("The range is empty; give start < end (or leave both out for the whole text).");
|
|
6161
|
+
return { runs: [{ length: 0, style: patchTextStyle(styleAt(l.runs, 0, DEFAULT_TEXT_STYLE), style) }] };
|
|
6162
|
+
}
|
|
6163
|
+
return { runs: restyleRange(l.runs, start, end, (s) => patchTextStyle(s, style)) };
|
|
6164
|
+
},
|
|
6165
|
+
this.textEnv(),
|
|
6166
|
+
"restyle its text"
|
|
6167
|
+
);
|
|
6168
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
6169
|
+
return this.typeInfo(id);
|
|
6170
|
+
}
|
|
6171
|
+
/** Sets paragraph style fields on every paragraph that [start, end) touches (default all), one undo step. */
|
|
6172
|
+
setParagraphStyle(id, style, range = {}, commitToHistory = true) {
|
|
6173
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6174
|
+
const [start, end] = textRange(layer.text, range.start, range.end);
|
|
6175
|
+
if (!Object.keys(style).length) throw new LayerOperationError("Give at least one paragraph field to set.");
|
|
6176
|
+
patchParagraphStyle({}, style);
|
|
6177
|
+
const { layers } = updateTypeLayer(
|
|
6178
|
+
this.state.layers,
|
|
6179
|
+
id,
|
|
6180
|
+
(l) => {
|
|
6181
|
+
let at = 0;
|
|
6182
|
+
const paragraphs = l.paragraphs.map((p) => {
|
|
6183
|
+
const a = at;
|
|
6184
|
+
at += p.length;
|
|
6185
|
+
const touches = a < end && at > start || start === end && start >= a && (start < at || at === l.text.length);
|
|
6186
|
+
return touches || !l.text.length ? { length: p.length, style: patchParagraphStyle(p.style, style) } : p;
|
|
6187
|
+
});
|
|
6188
|
+
return { paragraphs };
|
|
6189
|
+
},
|
|
6190
|
+
this.textEnv(),
|
|
6191
|
+
"restyle its paragraphs"
|
|
6192
|
+
);
|
|
6193
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
6194
|
+
return this.typeInfo(id);
|
|
6195
|
+
}
|
|
6196
|
+
/**
|
|
6197
|
+
* Point text ↔ paragraph text: `{ kind: 'box', width, height }` wraps the text in a box (its
|
|
6198
|
+
* top-left where the first line's top-left is now), `{ kind: 'point' }` sets it on one line per
|
|
6199
|
+
* paragraph. Also resizes a box. One undo step.
|
|
6200
|
+
*/
|
|
6201
|
+
setTextFrame(id, frame, commitToHistory = true) {
|
|
6202
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6203
|
+
if (frame.kind === "box" && !(Number.isFinite(frame.width) && frame.width >= 1 && Number.isFinite(frame.height) && frame.height >= 1)) throw new LayerOperationError("width and height must be at least 1 pixel.");
|
|
6204
|
+
if (frame.kind !== "box" && frame.kind !== "point") throw new LayerOperationError("kind must be point or box.");
|
|
6205
|
+
const shaper = this.getTextShaper();
|
|
6206
|
+
const { layers } = updateTypeLayer(
|
|
6207
|
+
this.state.layers,
|
|
6208
|
+
id,
|
|
6209
|
+
(l) => {
|
|
6210
|
+
if (frame.kind === "box" && l.frame.kind === "box") return { frame: { kind: "box", width: frame.width, height: frame.height } };
|
|
6211
|
+
if (!shaper) throw new LayerOperationError("Changing between point and paragraph text needs a text shaper to measure the text.");
|
|
6212
|
+
const lines = layoutText(l, shaper).lines;
|
|
6213
|
+
const first = lines[0];
|
|
6214
|
+
const dx = frame.kind === "box" ? first ? first.x : 0 : 0;
|
|
6215
|
+
const dy = frame.kind === "box" ? first ? first.baseline - first.ascent : 0 : first ? first.baseline : 0;
|
|
6216
|
+
const m = textMatrix(l);
|
|
6217
|
+
const shifted = [m[0], m[1], m[2], m[3], m[0] * dx + m[2] * dy + m[4], m[1] * dx + m[3] * dy + m[5]];
|
|
6218
|
+
const nextFrame = frame.kind === "box" ? { kind: "box", width: frame.width, height: frame.height } : { kind: "point" };
|
|
6219
|
+
return { frame: nextFrame, transform: shifted };
|
|
6220
|
+
},
|
|
6221
|
+
this.textEnv(),
|
|
6222
|
+
"change its frame"
|
|
6223
|
+
);
|
|
6224
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
6225
|
+
return this.typeInfo(id);
|
|
6226
|
+
}
|
|
6227
|
+
/**
|
|
6228
|
+
* Convert to Shape: the text's glyph outlines become a shape layer with the same id, name and
|
|
6229
|
+
* layer properties (needs a shaper that gives outlines). The fill is the first run's colour;
|
|
6230
|
+
* `warnings` says when other colours were dropped. One undo step.
|
|
6231
|
+
*/
|
|
6232
|
+
convertTextToShape(id) {
|
|
6233
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6234
|
+
requireUnlocked(layer, "all", "convert it");
|
|
6235
|
+
const shaper = this.getTextShaper();
|
|
6236
|
+
if (!shaper) throw new LayerOperationError("Convert to Shape needs a text shaper that gives glyph outlines; none is set here.");
|
|
6237
|
+
const parts = textOutlines(layer, shaper);
|
|
6238
|
+
const subpaths = parts.flatMap((p) => bezierToSubpaths(p.path));
|
|
6239
|
+
if (!subpaths.length) throw new LayerOperationError("The text draws nothing (it is empty or only spaces), so there is no shape to make.");
|
|
6240
|
+
const warnings = [];
|
|
6241
|
+
const colors = new Set(layer.runs.filter((r) => r.length > 0).map((r) => `${r.style.color}/${r.style.opacity ?? 1}`));
|
|
6242
|
+
if (colors.size > 1) warnings.push("The text had several colours; the shape takes the first one.");
|
|
6243
|
+
const missing = layoutText(layer, shaper).missingFonts;
|
|
6244
|
+
if (missing.length) warnings.push(`Missing fonts were drawn with a fallback: ${missing.join(", ")}.`);
|
|
6245
|
+
const first = layer.runs.find((r) => r.length > 0)?.style ?? DEFAULT_TEXT_STYLE;
|
|
6246
|
+
const fill = first.opacity !== void 0 && first.opacity !== 1 ? { type: "color", color: first.color, opacity: first.opacity } : { type: "color", color: first.color };
|
|
6247
|
+
const shape = createShapeLayer({ components: [{ op: "combine", subpaths }], fill, stroke: null, name: layer.name }, this.shapeEnv(), layer.id);
|
|
6248
|
+
const keep = { visible: layer.visible, opacity: layer.opacity, blend: layer.blend };
|
|
6249
|
+
for (const k of ["clipped", "mask", "locks", "effects", "fillOpacity"]) if (layer[k] !== void 0) keep[k] = layer[k];
|
|
6250
|
+
this.commitLayers(mapLayer(this.state.layers, id, () => ({ ...shape, ...keep })));
|
|
6251
|
+
return { layerId: id, warnings };
|
|
6252
|
+
}
|
|
6253
|
+
/** The layout of a type layer (lines, characters, bounds in text space), or `null` without a shaper. */
|
|
6254
|
+
getTextLayout(id) {
|
|
6255
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6256
|
+
const shaper = this.getTextShaper();
|
|
6257
|
+
return shaper ? layoutText(layer, shaper) : null;
|
|
6258
|
+
}
|
|
6259
|
+
/** The text index nearest a document point (for clicks on a type layer). */
|
|
6260
|
+
textIndexAt(id, point) {
|
|
6261
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6262
|
+
const layout = this.getTextLayout(id);
|
|
6263
|
+
return layout ? textIndexAt(layout, toTextSpace(layer, point)) : layer.text.length;
|
|
6264
|
+
}
|
|
6265
|
+
/** The caret at text index `index` as a document-space segment (top to bottom), for drawing it. */
|
|
6266
|
+
textCaret(id, index) {
|
|
6267
|
+
const layer = requireTypeLayer(this.state.layers, id);
|
|
6268
|
+
const layout = this.getTextLayout(id);
|
|
6269
|
+
if (!layout) return null;
|
|
6270
|
+
const c = caretAt(layout, Math.max(0, Math.min(layer.text.length, index)));
|
|
6271
|
+
const m = textMatrix(layer);
|
|
6272
|
+
const at = (x, y) => ({ x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] });
|
|
6273
|
+
return { top: at(c.x, c.top), bottom: at(c.x, c.bottom), line: c.line };
|
|
6274
|
+
}
|
|
5797
6275
|
// --- Shape layers ---
|
|
5798
6276
|
/** What shape rendering needs: the canvas size and the document's patterns. */
|
|
5799
6277
|
shapeEnv() {
|
|
@@ -5970,11 +6448,11 @@ var ImageEditorController = class {
|
|
|
5970
6448
|
}
|
|
5971
6449
|
} else if (layer.type === "smart") {
|
|
5972
6450
|
return this.rasterizeSmartObject(id);
|
|
5973
|
-
} else if (layer.type === "shape") {
|
|
5974
|
-
next = shapeAsPixelLayer(layer);
|
|
6451
|
+
} else if (layer.type === "shape" || layer.type === "type") {
|
|
6452
|
+
next = layer.type === "shape" ? shapeAsPixelLayer(layer) : typeAsPixelLayer(layer);
|
|
5975
6453
|
if (this.state.selectedAnnotationId && this.state.selectedAnnotationId === layer.annotation?.id) this.state.selectedAnnotationId = null;
|
|
5976
6454
|
} else {
|
|
5977
|
-
throw new LayerOperationError(`Only shape and annotation layers, smart objects and pixel layers with filters rasterize; "${layer.name || id}" is a ${layer.type} layer. Merge it down or flatten instead.`);
|
|
6455
|
+
throw new LayerOperationError(`Only shape, type and annotation layers, smart objects and pixel layers with filters rasterize; "${layer.name || id}" is a ${layer.type} layer. Merge it down or flatten instead.`);
|
|
5978
6456
|
}
|
|
5979
6457
|
this.commitLayers(mapLayer(this.state.layers, id, () => next));
|
|
5980
6458
|
return id;
|
|
@@ -6099,9 +6577,9 @@ var ImageEditorController = class {
|
|
|
6099
6577
|
if (mask2) out2.mask = mask2;
|
|
6100
6578
|
return out2;
|
|
6101
6579
|
}
|
|
6102
|
-
if (layer.type === "shape") {
|
|
6103
|
-
add2("Shape layers were written as pixel layers", layer.name);
|
|
6104
|
-
const out2 = shapeAsPixelLayer(layer);
|
|
6580
|
+
if (layer.type === "shape" || layer.type === "type") {
|
|
6581
|
+
add2(layer.type === "shape" ? "Shape layers were written as pixel layers" : "Type layers were written as pixel layers", layer.name);
|
|
6582
|
+
const out2 = layer.type === "shape" ? shapeAsPixelLayer(layer) : typeAsPixelLayer(layer);
|
|
6105
6583
|
const mask2 = bakeMask(layer);
|
|
6106
6584
|
if (mask2) out2.mask = mask2;
|
|
6107
6585
|
else delete out2.mask;
|
|
@@ -6203,7 +6681,7 @@ var ImageEditorController = class {
|
|
|
6203
6681
|
const bg = developTarget(layers, null);
|
|
6204
6682
|
if (bg && bg.source !== "document") layers = mapLayer(layers, bg.id, () => ({ ...bg, source: "document" }));
|
|
6205
6683
|
if (clearAnnotations) {
|
|
6206
|
-
for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
6684
|
+
for (const l of flattenLayers(layers)) if (l.type === "annotation" || (l.type === "shape" || l.type === "type") && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
6207
6685
|
this.state.selectedAnnotationId = null;
|
|
6208
6686
|
}
|
|
6209
6687
|
this.state.layers = layers;
|
|
@@ -6279,7 +6757,8 @@ var ImageEditorController = class {
|
|
|
6279
6757
|
problems,
|
|
6280
6758
|
smartContents: Array.isArray(data.smartContents) ? data.smartContents : [],
|
|
6281
6759
|
canvas,
|
|
6282
|
-
pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null
|
|
6760
|
+
pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null,
|
|
6761
|
+
text: this.textEnv()
|
|
6283
6762
|
});
|
|
6284
6763
|
this.lastLoadProblems = problems;
|
|
6285
6764
|
s.crop = data.crop ? { ...data.crop } : null;
|
|
@@ -6482,7 +6961,7 @@ function collectStateTiles(layers, into) {
|
|
|
6482
6961
|
for (const l of flattenLayers(layers)) {
|
|
6483
6962
|
if (l.type === "pixel" && l.source !== "document") collectTiles(l.surface, into);
|
|
6484
6963
|
if (l.mask && !l.mask.source) collectTiles(l.mask.surface, into);
|
|
6485
|
-
if (l.type === "shape") collectTiles(l.surface, into);
|
|
6964
|
+
if (l.type === "shape" || l.type === "type") collectTiles(l.surface, into);
|
|
6486
6965
|
if (l.type === "smart") {
|
|
6487
6966
|
collectTiles(l.surface, into);
|
|
6488
6967
|
if (l.smartFilters?.mask) collectTiles(l.smartFilters.mask.surface, into);
|
|
@@ -6531,7 +7010,7 @@ function resizePixels(px2, width, height) {
|
|
|
6531
7010
|
function withoutAnnotations(layers) {
|
|
6532
7011
|
const out = [];
|
|
6533
7012
|
for (const l of layers) {
|
|
6534
|
-
if (l.type === "annotation" || l.type === "shape" && l.annotation) continue;
|
|
7013
|
+
if (l.type === "annotation" || (l.type === "shape" || l.type === "type") && l.annotation) continue;
|
|
6535
7014
|
out.push(l.type === "group" ? { ...l, children: withoutAnnotations(l.children) } : l);
|
|
6536
7015
|
}
|
|
6537
7016
|
return out;
|
|
@@ -6721,7 +7200,7 @@ function loadImageEditorDocument(input) {
|
|
|
6721
7200
|
if (state.selection === void 0) delete state.selection;
|
|
6722
7201
|
return { state, report: result.report, unknown: result.unknown };
|
|
6723
7202
|
}
|
|
6724
|
-
var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart", "shape"]);
|
|
7203
|
+
var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart", "shape", "type"]);
|
|
6725
7204
|
function cleanLayers(value, dropped) {
|
|
6726
7205
|
const out = [];
|
|
6727
7206
|
for (const raw of value) {
|
|
@@ -7781,6 +8260,18 @@ function describeLayer(layer, activeId, boundsOf) {
|
|
|
7781
8260
|
};
|
|
7782
8261
|
if (layer.annotation) out.annotation = { id: layer.annotation.id, type: layer.annotation.type };
|
|
7783
8262
|
break;
|
|
8263
|
+
case "type":
|
|
8264
|
+
out.bounds = boundsOf(layer);
|
|
8265
|
+
out.fillOpacity = round3(layer.fillOpacity ?? 1);
|
|
8266
|
+
out.text = {
|
|
8267
|
+
text: layer.text.length > 80 ? `${layer.text.slice(0, 80)}\u2026` : layer.text,
|
|
8268
|
+
frame: layer.frame.kind,
|
|
8269
|
+
runs: layer.runs.length,
|
|
8270
|
+
rendering: layer.rendering === "stored" ? "stored" : "live",
|
|
8271
|
+
missingFonts: layer.missingFonts ?? []
|
|
8272
|
+
};
|
|
8273
|
+
if (layer.annotation) out.annotation = { id: layer.annotation.id, type: layer.annotation.type };
|
|
8274
|
+
break;
|
|
7784
8275
|
case "smart":
|
|
7785
8276
|
out.bounds = boundsOf(layer);
|
|
7786
8277
|
out.fillOpacity = round3(layer.fillOpacity ?? 1);
|
|
@@ -7826,7 +8317,7 @@ function createImageLayerTools(controller) {
|
|
|
7826
8317
|
const listLayers = {
|
|
7827
8318
|
name: "list_layers",
|
|
7828
8319
|
title: "List image layers",
|
|
7829
|
-
description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, shape, smart, annotation), name, document bounds (pixel, shape and smart layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, an effects summary (master switch, scale, effect ids and kinds; details from list_layer_effects), filters (pixel layers), kind and params (adjustment layers), a shape summary (component operations and kinds, fill and stroke paint types; details from get_shape_layer), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
|
|
8320
|
+
description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, shape, type, smart, annotation), name, document bounds (pixel, shape, type and smart layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, an effects summary (master switch, scale, effect ids and kinds; details from list_layer_effects), filters (pixel layers), kind and params (adjustment layers), a shape summary (component operations and kinds, fill and stroke paint types; details from get_shape_layer), a text summary (the text, point or box frame, run count, live or stored pixels, missing fonts; details from get_text_layer), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
|
|
7830
8321
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
7831
8322
|
annotations: { title: "List image layers", ...READ2 },
|
|
7832
8323
|
handler: () => {
|
|
@@ -8170,7 +8661,7 @@ function createImageLayerTools(controller) {
|
|
|
8170
8661
|
const rasterize = {
|
|
8171
8662
|
name: "rasterize_layer",
|
|
8172
8663
|
title: "Rasterize a layer",
|
|
8173
|
-
description: "Rasterize Layer: turns a shape layer into a pixel layer holding what it shows now (same id, name, opacity, blend, fill, mask and effects; its paths are gone, so later transforms resample pixels), turns an annotation layer into a pixel layer (needs a browser), rasterizes a smart object, or bakes a pixel layer's filters (develop, blur) into its pixels. One undo step. Returns the layer.",
|
|
8664
|
+
description: "Rasterize Layer: turns a shape or type layer into a pixel layer holding what it shows now (same id, name, opacity, blend, fill, mask and effects; its paths are gone, so later transforms resample pixels), turns an annotation layer into a pixel layer (needs a browser), rasterizes a smart object, or bakes a pixel layer's filters (develop, blur) into its pixels. One undo step. Returns the layer.",
|
|
8174
8665
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
|
|
8175
8666
|
annotations: { title: "Rasterize a layer", ...DESTRUCTIVE },
|
|
8176
8667
|
handler: async ({ layerId }) => ({ layer: describe(await controller.rasterizeLayer(exists(layerId))) })
|
|
@@ -10019,6 +10510,176 @@ function createImageShapeTools(controller) {
|
|
|
10019
10510
|
return [get, add2, update, setComponent, addComponent, removeComponent, merge];
|
|
10020
10511
|
}
|
|
10021
10512
|
|
|
10513
|
+
// src/image-editor/type-tools.ts
|
|
10514
|
+
var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10515
|
+
var WRITE8 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
10516
|
+
var WRITE_IDEMPOTENT6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10517
|
+
var DESTRUCTIVE7 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
10518
|
+
var LAYER_ID6 = { type: "string", description: "Type layer id from list_layers (type type) or add_text_layer." };
|
|
10519
|
+
var START = { type: "integer", minimum: 0, description: "Start of the range in UTF-16 code units, as get_text_layer counts them (default 0)." };
|
|
10520
|
+
var END = { type: "integer", minimum: 0, description: "End of the range, exclusive (default the end of the text)." };
|
|
10521
|
+
var STYLE = {
|
|
10522
|
+
fontFamily: { type: "string", description: "Font family name (list_fonts shows what is available; a missing family falls back, with a warning)." },
|
|
10523
|
+
fontWeight: { type: "integer", minimum: 1, maximum: 1e3, description: "Weight 100..900 (400 regular, 700 bold)." },
|
|
10524
|
+
italic: { type: "boolean", description: "Italic." },
|
|
10525
|
+
fontSize: { type: "number", minimum: 0.5, maximum: 5e3, description: "Size in document pixels." },
|
|
10526
|
+
color: { type: "string", description: "Colour (#rrggbb, or a CSS colour; its alpha becomes opacity)." },
|
|
10527
|
+
opacity: { type: ["number", "null"], minimum: 0, maximum: 1, description: "Text opacity 0..1 (null resets to 1)." },
|
|
10528
|
+
tracking: { type: ["number", "null"], minimum: -1e3, maximum: 1e4, description: "Letter spacing in 1/1000 em (null resets to 0)." },
|
|
10529
|
+
leading: { type: ["number", "null"], minimum: 0, maximum: 1e4, description: "Baseline-to-baseline distance in pixels (null = auto, the paragraph autoLeading \xD7 size)." },
|
|
10530
|
+
baselineShift: { type: ["number", "null"], minimum: -5e3, maximum: 5e3, description: "Pixels; positive raises the text (null resets to 0)." },
|
|
10531
|
+
underline: { type: "boolean", description: "Underline." },
|
|
10532
|
+
strikethrough: { type: "boolean", description: "Strike through." }
|
|
10533
|
+
};
|
|
10534
|
+
var PARAGRAPH = {
|
|
10535
|
+
align: { type: "string", enum: ["left", "center", "right", "justify"], description: "Alignment (justify stretches paragraph-box lines but the last)." },
|
|
10536
|
+
autoLeading: { type: ["number", "null"], minimum: 0.1, maximum: 10, description: "Auto leading as a multiple of the font size (default 1.2)." },
|
|
10537
|
+
firstLineIndent: { type: ["number", "null"], description: "First-line indent in pixels." },
|
|
10538
|
+
startIndent: { type: ["number", "null"], description: "Left indent in pixels." },
|
|
10539
|
+
endIndent: { type: ["number", "null"], description: "Right indent in pixels." },
|
|
10540
|
+
spaceBefore: { type: ["number", "null"], description: "Space above the paragraph in pixels (not before the first)." },
|
|
10541
|
+
spaceAfter: { type: ["number", "null"], description: "Space below the paragraph in pixels." }
|
|
10542
|
+
};
|
|
10543
|
+
function pick(args, keys) {
|
|
10544
|
+
const out = {};
|
|
10545
|
+
for (const k of keys) if (args[k] !== void 0) out[k] = args[k];
|
|
10546
|
+
return out;
|
|
10547
|
+
}
|
|
10548
|
+
function createImageTypeTools(controller) {
|
|
10549
|
+
const typeId = (id) => {
|
|
10550
|
+
if (typeof id !== "string" || !id) throw new LayerOperationError("layerId is required (a type layer id from list_layers).");
|
|
10551
|
+
requireTypeLayer(controller.getLayers(), id);
|
|
10552
|
+
return id;
|
|
10553
|
+
};
|
|
10554
|
+
const layerSummary = (id) => {
|
|
10555
|
+
const s = controller.getState();
|
|
10556
|
+
const loc = findLayer(s.layers, id);
|
|
10557
|
+
return loc ? { ...describeLayer(loc.layer, s.activeLayerId, (x) => controller.getLayerBounds(x.id)), parentId: loc.parent?.id ?? null, index: loc.index } : null;
|
|
10558
|
+
};
|
|
10559
|
+
const range = (args) => ({ start: args.start, end: args.end });
|
|
10560
|
+
const listFonts = {
|
|
10561
|
+
name: "list_fonts",
|
|
10562
|
+
title: "List fonts",
|
|
10563
|
+
description: "Returns the font families type layers can use here (generic families such as sans-serif always work). Changes nothing.",
|
|
10564
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
10565
|
+
annotations: { title: "List fonts", ...READ9 },
|
|
10566
|
+
handler: () => ({ families: controller.listFonts(), shaper: controller.getTextShaper()?.id ?? null })
|
|
10567
|
+
};
|
|
10568
|
+
const get = {
|
|
10569
|
+
name: "get_text_layer",
|
|
10570
|
+
title: "Read a type layer",
|
|
10571
|
+
description: "Returns a type layer\u2019s text, its character runs and paragraphs as [start, end) ranges in UTF-16 code units with their styles, the frame (point text, or a paragraph box with its size), the text-to-document transform, the laid-out lines (range, baseline, width), the rendered bounds, missing fonts and whether the pixels are live or stored from a file. Call before editing text you did not write, to get the ranges. Changes nothing.",
|
|
10572
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID6 }, required: ["layerId"], additionalProperties: false },
|
|
10573
|
+
annotations: { title: "Read a type layer", ...READ9 },
|
|
10574
|
+
handler: ({ layerId }) => controller.getTextLayer(typeId(layerId))
|
|
10575
|
+
};
|
|
10576
|
+
const add2 = {
|
|
10577
|
+
name: "add_text_layer",
|
|
10578
|
+
title: "Add a type layer",
|
|
10579
|
+
description: "Adds a live type layer and makes it active: point text whose origin (x, y) is the first line\u2019s baseline at its alignment edge, or paragraph text wrapped in box {width, height} with its top-left at (x, y). Line breaks in text start paragraphs. Style and paragraph settings apply to the whole text (change ranges later with set_text_style). Default style: 24 px sans-serif in the foreground colour. One undo step. Returns the layer and the text layer, including missingFonts.",
|
|
10580
|
+
inputSchema: {
|
|
10581
|
+
type: "object",
|
|
10582
|
+
properties: {
|
|
10583
|
+
text: { type: "string", description: "The text; \\n starts a new paragraph." },
|
|
10584
|
+
x: { type: "number", description: "Document x of the origin, pixels." },
|
|
10585
|
+
y: { type: "number", description: "Document y of the origin (point text: the first baseline), pixels." },
|
|
10586
|
+
box: {
|
|
10587
|
+
type: "object",
|
|
10588
|
+
description: "Make it paragraph (area) text wrapped in this box, pixels.",
|
|
10589
|
+
properties: { width: { type: "number", minimum: 1, description: "Box width, pixels." }, height: { type: "number", minimum: 1, description: "Box height, pixels." } },
|
|
10590
|
+
required: ["width", "height"],
|
|
10591
|
+
additionalProperties: false
|
|
10592
|
+
},
|
|
10593
|
+
style: { type: "object", description: "Character style.", properties: STYLE, additionalProperties: false },
|
|
10594
|
+
paragraph: { type: "object", description: "Paragraph style.", properties: PARAGRAPH, additionalProperties: false },
|
|
10595
|
+
rotation: { type: "number", description: "Degrees clockwise about the origin (default 0)." },
|
|
10596
|
+
name: { type: "string", description: "Layer name (default the start of the text)." },
|
|
10597
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1 (default 1)." },
|
|
10598
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal)." },
|
|
10599
|
+
aboveId: { type: "string", description: "Put the layer directly above this layer (default above the active layer)." },
|
|
10600
|
+
belowId: { type: "string", description: "Put the layer directly below this layer." },
|
|
10601
|
+
parentId: { type: "string", description: "Put the layer inside this group." },
|
|
10602
|
+
index: { type: "integer", minimum: 0, description: "Position among the siblings, 0 = bottom." }
|
|
10603
|
+
},
|
|
10604
|
+
required: ["text", "x", "y"],
|
|
10605
|
+
additionalProperties: false
|
|
10606
|
+
},
|
|
10607
|
+
annotations: { title: "Add a type layer", ...WRITE8 },
|
|
10608
|
+
handler: (args) => {
|
|
10609
|
+
const init = pick(args, ["text", "x", "y", "box", "style", "paragraph", "rotation", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]);
|
|
10610
|
+
if (init.blend !== void 0) init.blend = init.blend;
|
|
10611
|
+
const id = controller.addTextLayer(init);
|
|
10612
|
+
return { layer: layerSummary(id), text: controller.getTextLayer(id) };
|
|
10613
|
+
}
|
|
10614
|
+
};
|
|
10615
|
+
const setText = {
|
|
10616
|
+
name: "set_text",
|
|
10617
|
+
title: "Edit text",
|
|
10618
|
+
description: "Replaces the text in [start, end) (default all of it) with text; inserted text takes the style of the character at start. Use start = end to insert. Returns the text layer with its new runs.",
|
|
10619
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, text: { type: "string", description: "Replacement text (\\n starts a paragraph; empty deletes the range)." }, start: START, end: END }, required: ["layerId", "text"], additionalProperties: false },
|
|
10620
|
+
annotations: { title: "Edit text", ...WRITE8 },
|
|
10621
|
+
handler: (args) => controller.setText(typeId(args.layerId), args.text, range(args))
|
|
10622
|
+
};
|
|
10623
|
+
const setStyle = {
|
|
10624
|
+
name: "set_text_style",
|
|
10625
|
+
title: "Style a range of text",
|
|
10626
|
+
description: "Sets character style fields (font, weight, italic, size, colour, tracking, leading, baseline shift, underline, strike-through) over [start, end) of a type layer (default all of it); fields not given keep their values per run. Returns the text layer with its runs and missingFonts.",
|
|
10627
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...STYLE }, required: ["layerId"], additionalProperties: false },
|
|
10628
|
+
annotations: { title: "Style a range of text", ...WRITE_IDEMPOTENT6 },
|
|
10629
|
+
handler: (args) => {
|
|
10630
|
+
const style = pick(args, Object.keys(STYLE));
|
|
10631
|
+
if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one style field: ${Object.keys(STYLE).join(", ")}.`);
|
|
10632
|
+
return controller.setTextStyle(typeId(args.layerId), style, range(args));
|
|
10633
|
+
}
|
|
10634
|
+
};
|
|
10635
|
+
const setParagraph = {
|
|
10636
|
+
name: "set_paragraph_style",
|
|
10637
|
+
title: "Style paragraphs",
|
|
10638
|
+
description: "Sets paragraph fields (alignment, auto leading, indents, spacing) on every paragraph that [start, end) touches (default all). Returns the text layer.",
|
|
10639
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...PARAGRAPH }, required: ["layerId"], additionalProperties: false },
|
|
10640
|
+
annotations: { title: "Style paragraphs", ...WRITE_IDEMPOTENT6 },
|
|
10641
|
+
handler: (args) => {
|
|
10642
|
+
const style = pick(args, Object.keys(PARAGRAPH));
|
|
10643
|
+
if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one paragraph field: ${Object.keys(PARAGRAPH).join(", ")}.`);
|
|
10644
|
+
return controller.setParagraphStyle(typeId(args.layerId), style, range(args));
|
|
10645
|
+
}
|
|
10646
|
+
};
|
|
10647
|
+
const setFrame = {
|
|
10648
|
+
name: "set_text_frame",
|
|
10649
|
+
title: "Point or paragraph text",
|
|
10650
|
+
description: "Converts between point text (kind point: one line per paragraph) and paragraph text (kind box: wrapped in width \xD7 height pixels, its top-left where the first line starts), or resizes a paragraph box. The first line stays in place. Returns the text layer with its new lines.",
|
|
10651
|
+
inputSchema: {
|
|
10652
|
+
type: "object",
|
|
10653
|
+
properties: {
|
|
10654
|
+
layerId: LAYER_ID6,
|
|
10655
|
+
kind: { type: "string", enum: ["point", "box"], description: "point or box." },
|
|
10656
|
+
width: { type: "number", minimum: 1, description: "Box width in pixels (kind box)." },
|
|
10657
|
+
height: { type: "number", minimum: 1, description: "Box height in pixels (kind box)." }
|
|
10658
|
+
},
|
|
10659
|
+
required: ["layerId", "kind"],
|
|
10660
|
+
additionalProperties: false
|
|
10661
|
+
},
|
|
10662
|
+
annotations: { title: "Point or paragraph text", ...WRITE_IDEMPOTENT6 },
|
|
10663
|
+
handler: ({ layerId, kind, width, height }) => {
|
|
10664
|
+
const id = typeId(layerId);
|
|
10665
|
+
if (kind === "box" && (width === void 0 || height === void 0)) throw new LayerOperationError("kind box needs width and height in pixels.");
|
|
10666
|
+
return controller.setTextFrame(id, kind === "box" ? { kind: "box", width, height } : { kind: "point" });
|
|
10667
|
+
}
|
|
10668
|
+
};
|
|
10669
|
+
const toShape = {
|
|
10670
|
+
name: "convert_text_to_shape",
|
|
10671
|
+
title: "Convert text to a shape",
|
|
10672
|
+
description: "Type \u203A Convert to Shape: the text\u2019s glyph outlines become a shape layer (same id, name, mask, effects, opacity, blend) that can be edited with the shape tools; the text is no longer editable. Needs a font shaper that gives outlines (the browser\u2019s canvas cannot). The fill is the first run\u2019s colour. One undo step. Returns the layer and warnings.",
|
|
10673
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID6 }, required: ["layerId"], additionalProperties: false },
|
|
10674
|
+
annotations: { title: "Convert text to a shape", ...DESTRUCTIVE7 },
|
|
10675
|
+
handler: ({ layerId }) => {
|
|
10676
|
+
const { layerId: id, warnings } = controller.convertTextToShape(typeId(layerId));
|
|
10677
|
+
return { layer: layerSummary(id), shape: controller.getShapeLayer(id), warnings };
|
|
10678
|
+
}
|
|
10679
|
+
};
|
|
10680
|
+
return [listFonts, get, add2, setText, setStyle, setParagraph, setFrame, toShape];
|
|
10681
|
+
}
|
|
10682
|
+
|
|
10022
10683
|
// src/image-editor/tools.ts
|
|
10023
10684
|
var LAYER_PATH = /^layers\.([^.]+)\.(.+)$/;
|
|
10024
10685
|
function adjustmentsSummary(adjustments) {
|
|
@@ -10259,6 +10920,7 @@ function createImageEditorToolProvider(controller, options = {}) {
|
|
|
10259
10920
|
...createImageTransformTools(controller),
|
|
10260
10921
|
...createImageSmartObjectTools(controller),
|
|
10261
10922
|
...createImageShapeTools(controller),
|
|
10923
|
+
...createImageTypeTools(controller),
|
|
10262
10924
|
...createImageEffectTools(controller),
|
|
10263
10925
|
...createImagePaintTools(controller),
|
|
10264
10926
|
...createPigmentTools(),
|
|
@@ -10601,6 +11263,8 @@ function layerBadge(layer) {
|
|
|
10601
11263
|
return "so";
|
|
10602
11264
|
case "shape":
|
|
10603
11265
|
return "shp";
|
|
11266
|
+
case "type":
|
|
11267
|
+
return "T";
|
|
10604
11268
|
}
|
|
10605
11269
|
}
|
|
10606
11270
|
function dropZone(fraction, isGroup) {
|
|
@@ -10640,7 +11304,7 @@ function layerMenu(controller) {
|
|
|
10640
11304
|
{ action: "delete-mask", label: "Delete Layer Mask", enabled: Boolean(active?.mask) },
|
|
10641
11305
|
{ action: "toggle-mask", label: active?.mask?.enabled === false ? "Enable Layer Mask" : "Disable Layer Mask", enabled: Boolean(active?.mask) },
|
|
10642
11306
|
{ action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
|
|
10643
|
-
{ action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || active?.type === "shape" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
|
|
11307
|
+
{ action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || active?.type === "shape" || active?.type === "type" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
|
|
10644
11308
|
{ action: "convert-smart", label: "Convert to Smart Object", enabled: Boolean(active && active.type !== "annotation") },
|
|
10645
11309
|
{ action: "edit-contents", label: "Edit Contents", enabled: active?.type === "smart" },
|
|
10646
11310
|
{ action: "smart-via-copy", label: "New Smart Object via Copy", enabled: active?.type === "smart" },
|
|
@@ -11573,6 +12237,7 @@ export {
|
|
|
11573
12237
|
D50,
|
|
11574
12238
|
D65,
|
|
11575
12239
|
DEFAULT_ADJUSTMENTS,
|
|
12240
|
+
DEFAULT_AUTO_LEADING,
|
|
11576
12241
|
DEFAULT_BLENDER,
|
|
11577
12242
|
DEFAULT_BRUSH,
|
|
11578
12243
|
DEFAULT_DEPTH_MASK,
|
|
@@ -11582,6 +12247,7 @@ export {
|
|
|
11582
12247
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
11583
12248
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
11584
12249
|
DEFAULT_SMUDGE,
|
|
12250
|
+
DEFAULT_TEXT_STYLE,
|
|
11585
12251
|
DEFAULT_TRANSFORM,
|
|
11586
12252
|
DEPTH_MASK_CONTROLS,
|
|
11587
12253
|
DEVELOP_FILTER_ID,
|
|
@@ -11631,6 +12297,7 @@ export {
|
|
|
11631
12297
|
MixerStroke,
|
|
11632
12298
|
PAINTERLY_BLEND_MODES,
|
|
11633
12299
|
PAINT_TOOLS,
|
|
12300
|
+
PARAGRAPH_STYLE_FIELDS,
|
|
11634
12301
|
PATTERN_BLOCK_KEYS,
|
|
11635
12302
|
PIGMENT_BANDS,
|
|
11636
12303
|
PIGMENT_LAMBDA_MIN,
|
|
@@ -11658,6 +12325,9 @@ export {
|
|
|
11658
12325
|
SmudgeTip,
|
|
11659
12326
|
StrokeCoverage,
|
|
11660
12327
|
SurfaceDraft,
|
|
12328
|
+
TEXT_ALIGNS,
|
|
12329
|
+
TEXT_RENDER_MARGIN,
|
|
12330
|
+
TEXT_STYLE_FIELDS,
|
|
11661
12331
|
TILE,
|
|
11662
12332
|
TILE_SHIFT,
|
|
11663
12333
|
TINT_SCALE,
|
|
@@ -11691,6 +12361,7 @@ export {
|
|
|
11691
12361
|
annotationLayerName,
|
|
11692
12362
|
annotationOfLayer,
|
|
11693
12363
|
annotationOfShape,
|
|
12364
|
+
annotationOfType,
|
|
11694
12365
|
applyAdjustmentLayers,
|
|
11695
12366
|
applyAdjustments,
|
|
11696
12367
|
applyBoxBlur,
|
|
@@ -11733,6 +12404,7 @@ export {
|
|
|
11733
12404
|
calculateCropDrag,
|
|
11734
12405
|
canvasCompositeOperation,
|
|
11735
12406
|
canvasToImagePoint,
|
|
12407
|
+
caretAt,
|
|
11736
12408
|
changedTiles,
|
|
11737
12409
|
clampControlValue,
|
|
11738
12410
|
clampCropToImage,
|
|
@@ -11760,6 +12432,7 @@ export {
|
|
|
11760
12432
|
coveringRect,
|
|
11761
12433
|
createAdjustmentLayer,
|
|
11762
12434
|
createBlenderScratch,
|
|
12435
|
+
createCanvasTextShaper,
|
|
11763
12436
|
createImageCaptureSurface,
|
|
11764
12437
|
createImageEditTools,
|
|
11765
12438
|
createImageEditorToolProvider,
|
|
@@ -11771,10 +12444,15 @@ export {
|
|
|
11771
12444
|
createImageShapeTools,
|
|
11772
12445
|
createImageSmartObjectTools,
|
|
11773
12446
|
createImageTransformTools,
|
|
12447
|
+
createImageTypeTools,
|
|
12448
|
+
createOutlineTextShaper,
|
|
11774
12449
|
createPigment,
|
|
11775
12450
|
createPigmentTools,
|
|
11776
12451
|
createSelectionMask,
|
|
11777
12452
|
createShapeLayer,
|
|
12453
|
+
createTypeLayer,
|
|
12454
|
+
cssFont,
|
|
12455
|
+
cssFontFamily,
|
|
11778
12456
|
curveLut,
|
|
11779
12457
|
curveLutFromLevels,
|
|
11780
12458
|
curvePointToBox,
|
|
@@ -11843,9 +12521,12 @@ export {
|
|
|
11843
12521
|
findLayer,
|
|
11844
12522
|
findLayerControl,
|
|
11845
12523
|
finishTile,
|
|
12524
|
+
fitParagraphRuns,
|
|
12525
|
+
fitRuns,
|
|
11846
12526
|
flattenLayers,
|
|
11847
12527
|
flattenPath,
|
|
11848
12528
|
flipCorners,
|
|
12529
|
+
fontOf,
|
|
11849
12530
|
formatControlValue,
|
|
11850
12531
|
fullSelection,
|
|
11851
12532
|
gaussianBlur,
|
|
@@ -11936,6 +12617,7 @@ export {
|
|
|
11936
12617
|
layerTree,
|
|
11937
12618
|
layerTypeLabel,
|
|
11938
12619
|
layersFromLegacy,
|
|
12620
|
+
layoutText,
|
|
11939
12621
|
legacyAdjustmentLayer,
|
|
11940
12622
|
legacyMaskOf,
|
|
11941
12623
|
legacyView,
|
|
@@ -11995,11 +12677,13 @@ export {
|
|
|
11995
12677
|
normalizeSmudge,
|
|
11996
12678
|
normalizeStoredAdjustments,
|
|
11997
12679
|
normalizeSubpaths,
|
|
12680
|
+
normalizeText,
|
|
11998
12681
|
offsetLayer,
|
|
11999
12682
|
offsetShape,
|
|
12000
12683
|
offsetSmartObject,
|
|
12001
12684
|
openPsdFile,
|
|
12002
12685
|
packRgba,
|
|
12686
|
+
paragraphRanges,
|
|
12003
12687
|
parseAdjustmentBlock,
|
|
12004
12688
|
parseBlend,
|
|
12005
12689
|
parseColor,
|
|
@@ -12011,6 +12695,8 @@ export {
|
|
|
12011
12695
|
parsePatternBlock,
|
|
12012
12696
|
parseSelectionMode,
|
|
12013
12697
|
parseVersionedDescriptor,
|
|
12698
|
+
patchParagraphStyle,
|
|
12699
|
+
patchTextStyle,
|
|
12014
12700
|
photoFilterMatrix,
|
|
12015
12701
|
pigment8,
|
|
12016
12702
|
pixelLayerBounds,
|
|
@@ -12066,19 +12752,24 @@ export {
|
|
|
12066
12752
|
renderShape,
|
|
12067
12753
|
renderShapeRect,
|
|
12068
12754
|
renderSmartObject,
|
|
12755
|
+
renderText,
|
|
12069
12756
|
replaceContents,
|
|
12757
|
+
replaceTextRange,
|
|
12070
12758
|
requireLayer,
|
|
12071
12759
|
requireOperation,
|
|
12072
12760
|
requireShapeLayer,
|
|
12073
12761
|
requireSmartObject,
|
|
12762
|
+
requireTypeLayer,
|
|
12074
12763
|
requireUnlocked,
|
|
12075
12764
|
rerender,
|
|
12076
12765
|
rerenderShape,
|
|
12766
|
+
rerenderText,
|
|
12077
12767
|
resizePixels,
|
|
12078
12768
|
resizeSelection,
|
|
12079
12769
|
resizeWarpMesh,
|
|
12080
12770
|
resolveExternalAssets,
|
|
12081
12771
|
resolveMosaicRegionBounds,
|
|
12772
|
+
restyleRange,
|
|
12082
12773
|
rgbToGray,
|
|
12083
12774
|
rgbToHsl,
|
|
12084
12775
|
rgbToReflectance,
|
|
@@ -12125,16 +12816,22 @@ export {
|
|
|
12125
12816
|
smoothCoverage,
|
|
12126
12817
|
smoothPoints,
|
|
12127
12818
|
solidSurface,
|
|
12819
|
+
spliceRuns,
|
|
12128
12820
|
splitOverlayAnnotations,
|
|
12129
12821
|
srgb8ToLab,
|
|
12130
12822
|
srgbToLinear,
|
|
12131
12823
|
standardBlendMode,
|
|
12824
|
+
styleAt,
|
|
12132
12825
|
sumHistograms,
|
|
12133
12826
|
surfaceBytes,
|
|
12134
12827
|
surfaceFromPixels,
|
|
12135
12828
|
surfacesEqual,
|
|
12136
12829
|
tempTintToXy,
|
|
12137
12830
|
temperatureToKelvin,
|
|
12831
|
+
textIndexAt,
|
|
12832
|
+
textMatrix,
|
|
12833
|
+
textOutlines,
|
|
12834
|
+
textRange,
|
|
12138
12835
|
tileCoord,
|
|
12139
12836
|
tileKey,
|
|
12140
12837
|
tileRect,
|
|
@@ -12143,6 +12840,7 @@ export {
|
|
|
12143
12840
|
tipReach,
|
|
12144
12841
|
tipShape,
|
|
12145
12842
|
toHex,
|
|
12843
|
+
toTextSpace,
|
|
12146
12844
|
toneCurve,
|
|
12147
12845
|
toneLuts,
|
|
12148
12846
|
toneRange,
|
|
@@ -12150,10 +12848,15 @@ export {
|
|
|
12150
12848
|
transformMap,
|
|
12151
12849
|
transformShapeComponents,
|
|
12152
12850
|
transformSurface,
|
|
12851
|
+
transformTypeLayer,
|
|
12153
12852
|
transformedBounds,
|
|
12154
12853
|
transformedBox,
|
|
12155
12854
|
transformedPoint,
|
|
12156
12855
|
translateSurface,
|
|
12856
|
+
typeAsPixelLayer,
|
|
12857
|
+
typeFrame,
|
|
12858
|
+
typeFromAnnotation,
|
|
12859
|
+
typeLayerName,
|
|
12157
12860
|
ungroupLayer,
|
|
12158
12861
|
uniformTile,
|
|
12159
12862
|
unionRect,
|
|
@@ -12163,6 +12866,7 @@ export {
|
|
|
12163
12866
|
updateLayerMaskProps,
|
|
12164
12867
|
updateShapeLayer,
|
|
12165
12868
|
updateSmartFilter,
|
|
12869
|
+
updateTypeLayer,
|
|
12166
12870
|
uvToXy,
|
|
12167
12871
|
validateEffectGradient,
|
|
12168
12872
|
validateLayerEffect,
|