@pixerate/editor 0.109.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/blending-DXl06NSx.d.ts +486 -0
- package/dist/blending-aOE6Y3UE.d.cts +486 -0
- package/dist/chunk-7VOHOPLW.cjs +1136 -0
- package/dist/chunk-7VOHOPLW.cjs.map +1 -0
- package/dist/chunk-FTBZPI77.cjs +1 -0
- package/dist/chunk-FTBZPI77.cjs.map +1 -0
- package/dist/{chunk-RE77QYUF.js → chunk-HHZ6ZJTW.js} +880 -86
- package/dist/chunk-HHZ6ZJTW.js.map +1 -0
- package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
- package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
- package/dist/{chunk-CFMWALBM.js → chunk-LQEEDHOP.js} +2 -2
- package/dist/{chunk-D37BX2ZA.cjs → chunk-PHUMLSCW.cjs} +1141 -347
- package/dist/chunk-PHUMLSCW.cjs.map +1 -0
- package/dist/{chunk-AZTZRLDI.cjs → chunk-POGBUYWE.cjs} +92 -92
- package/dist/{chunk-AZTZRLDI.cjs.map → chunk-POGBUYWE.cjs.map} +1 -1
- package/dist/chunk-VKK5VS2T.js +1 -0
- package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
- package/dist/chunk-XIOMMGPD.cjs.map +1 -0
- package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
- package/dist/chunk-Z4BC6M74.js.map +1 -0
- package/dist/{export-BLPLaE6Z.d.ts → export-DBNVwb6h.d.ts} +521 -12
- package/dist/{export-DSUc4fQD.d.cts → export-DoDhCszr.d.cts} +521 -12
- package/dist/image-editor/index.cjs +1630 -900
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +104 -16
- package/dist/image-editor/index.d.ts +104 -16
- package/dist/image-editor/index.js +776 -46
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +28 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -89
- package/dist/paint/index.d.ts +3 -89
- package/dist/paint/index.js +27 -3
- package/dist/paint-editor/index.cjs +1052 -286
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +302 -23
- package/dist/paint-editor/index.d.ts +302 -23
- package/dist/paint-editor/index.js +848 -82
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +5 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +2 -2
- package/dist/raster/index.d.ts +2 -2
- package/dist/raster/index.js +4 -2
- package/dist/{render-C-fO_mes.d.cts → render-BEriFuqK.d.cts} +1 -1
- package/dist/{render-OLIMk_v0.d.ts → render-tjzKaik-.d.ts} +1 -1
- package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
- package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -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/skills/pixerate-paint-editor/SKILL.md +49 -13
- package/dist/chunk-3PHA3UKI.js.map +0 -1
- package/dist/chunk-6BU5WU4R.cjs +0 -1
- package/dist/chunk-6BU5WU4R.cjs.map +0 -1
- package/dist/chunk-D37BX2ZA.cjs.map +0 -1
- package/dist/chunk-HUFQD4CB.cjs +0 -734
- package/dist/chunk-HUFQD4CB.cjs.map +0 -1
- package/dist/chunk-RE77QYUF.js.map +0 -1
- package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
- package/dist/chunk-XYBJKUAU.js +0 -1
- package/dist/mixer-5ziHU8OS.d.cts +0 -282
- package/dist/mixer-DNqiYXYQ.d.ts +0 -282
- /package/dist/{chunk-CFMWALBM.js.map → chunk-LQEEDHOP.js.map} +0 -0
- /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createCaptureTool
|
|
6
6
|
} from "../chunk-REOJJIY3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-VKK5VS2T.js";
|
|
8
8
|
import {
|
|
9
9
|
ADJUSTMENT_KEYS,
|
|
10
10
|
ADJUSTMENT_KINDS,
|
|
@@ -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,
|
|
@@ -398,7 +433,7 @@ import {
|
|
|
398
433
|
validateLayerEffect,
|
|
399
434
|
validateLayerEffects,
|
|
400
435
|
warpSurface
|
|
401
|
-
} from "../chunk-
|
|
436
|
+
} from "../chunk-LQEEDHOP.js";
|
|
402
437
|
import {
|
|
403
438
|
applyHomography,
|
|
404
439
|
boxQuad,
|
|
@@ -410,8 +445,10 @@ import {
|
|
|
410
445
|
} from "../chunk-GX332W2C.js";
|
|
411
446
|
import {
|
|
412
447
|
CoverageMap,
|
|
448
|
+
DEFAULT_BLENDER,
|
|
413
449
|
DEFAULT_BRUSH,
|
|
414
450
|
DEFAULT_MIXER,
|
|
451
|
+
DEFAULT_SMUDGE,
|
|
415
452
|
DabWalker,
|
|
416
453
|
EMPTY_MIXER_LOAD,
|
|
417
454
|
MAX_BRUSH_SIZE,
|
|
@@ -419,23 +456,33 @@ import {
|
|
|
419
456
|
MIXER_PICKUP_RATE,
|
|
420
457
|
MIXING_MODELS,
|
|
421
458
|
MixerStroke,
|
|
459
|
+
PaintExchange,
|
|
460
|
+
SmudgeTip,
|
|
422
461
|
StrokeCoverage,
|
|
462
|
+
TileMedium,
|
|
423
463
|
applyStrokeToTile,
|
|
464
|
+
blendRadius,
|
|
465
|
+
blenderDab,
|
|
424
466
|
cleanMixer,
|
|
425
467
|
coverageTileCoord,
|
|
468
|
+
createBlenderScratch,
|
|
426
469
|
createPigmentTools,
|
|
427
470
|
dabCoverage,
|
|
428
471
|
dabRect,
|
|
472
|
+
exchangeRates,
|
|
429
473
|
loadMixer,
|
|
474
|
+
mixerRates,
|
|
475
|
+
normalizeBlender,
|
|
430
476
|
normalizeBrush,
|
|
431
477
|
normalizeMixer,
|
|
478
|
+
normalizeSmudge,
|
|
432
479
|
rasterizeDab,
|
|
433
480
|
smoothPoints,
|
|
434
481
|
tipFalloff,
|
|
435
482
|
tipReach,
|
|
436
483
|
tipShape,
|
|
437
484
|
walkDabs
|
|
438
|
-
} from "../chunk-
|
|
485
|
+
} from "../chunk-Z4BC6M74.js";
|
|
439
486
|
import {
|
|
440
487
|
BLEND_MODES,
|
|
441
488
|
PAINTERLY_BLEND_MODES,
|
|
@@ -470,6 +517,7 @@ import {
|
|
|
470
517
|
mixPigments,
|
|
471
518
|
normalizeBlendMode,
|
|
472
519
|
packRgba,
|
|
520
|
+
pigment8,
|
|
473
521
|
readPixel,
|
|
474
522
|
readRegion,
|
|
475
523
|
reflectanceToRgb,
|
|
@@ -488,17 +536,20 @@ import {
|
|
|
488
536
|
translateSurface,
|
|
489
537
|
uniformTile,
|
|
490
538
|
writeRegion
|
|
491
|
-
} from "../chunk-
|
|
539
|
+
} from "../chunk-HNS7UHCM.js";
|
|
492
540
|
import {
|
|
493
541
|
WARP_STYLES,
|
|
494
542
|
WARP_STYLE_LABELS,
|
|
495
543
|
applyMatrix,
|
|
544
|
+
bezierToSubpaths,
|
|
496
545
|
dragWarpMeshPoint,
|
|
497
546
|
evalWarpMesh,
|
|
498
547
|
identityWarpMesh,
|
|
548
|
+
invertMatrix,
|
|
499
549
|
multiplyMatrices,
|
|
500
550
|
pullWarpMesh,
|
|
501
551
|
resizeWarpMesh,
|
|
552
|
+
rotateMatrix,
|
|
502
553
|
roundSubpaths,
|
|
503
554
|
translateMatrix,
|
|
504
555
|
warpMeshColumns,
|
|
@@ -687,7 +738,7 @@ function needsLegacyBase(input) {
|
|
|
687
738
|
if (hasResampledLegacyMask(stack.layers, canvas.width, canvas.height)) return true;
|
|
688
739
|
return false;
|
|
689
740
|
}
|
|
690
|
-
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;
|
|
691
742
|
var overlayViews = /* @__PURE__ */ new WeakMap();
|
|
692
743
|
function overlayView(l) {
|
|
693
744
|
if (l.type === "annotation") return l;
|
|
@@ -703,7 +754,7 @@ function splitOverlayAnnotations(layers) {
|
|
|
703
754
|
let i = layers.length;
|
|
704
755
|
while (i > 0) {
|
|
705
756
|
const l = layers[i - 1];
|
|
706
|
-
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--;
|
|
707
758
|
else break;
|
|
708
759
|
}
|
|
709
760
|
return {
|
|
@@ -2428,6 +2479,159 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
2428
2479
|
return createSelectionMask(mask.width, mask.height, out);
|
|
2429
2480
|
}
|
|
2430
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
|
+
|
|
2431
2635
|
// src/image-editor/free-transform.ts
|
|
2432
2636
|
var FREE_TRANSFORM_CONTENTS = ["auto", "layer", "selection", "selection-copy", "selection-outline"];
|
|
2433
2637
|
var fail = (message) => {
|
|
@@ -2499,7 +2703,7 @@ var FreeTransformSession = class {
|
|
|
2499
2703
|
}
|
|
2500
2704
|
const targetId = init.layerId ?? host.activeLayerId();
|
|
2501
2705
|
const target = targetId && content !== "selection-outline" ? findLayer(host.layers(), targetId)?.layer : void 0;
|
|
2502
|
-
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";
|
|
2503
2707
|
if (content === "auto") content = selected && !smartTarget ? "selection" : "layer";
|
|
2504
2708
|
this.content = content;
|
|
2505
2709
|
if (content !== "layer" && !selected) fail(`Nothing is selected; ${content} needs a selection. Select first, or use content layer.`);
|
|
@@ -2526,7 +2730,7 @@ var FreeTransformSession = class {
|
|
|
2526
2730
|
const linked = mask?.linked !== false;
|
|
2527
2731
|
const maskAlone = channel === "mask" && (!linked || layer.type !== "pixel");
|
|
2528
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.`);
|
|
2529
|
-
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.`);
|
|
2530
2734
|
if (!layer.visible && content !== "selection-copy") fail(`Layer "${layer.name || id}" is hidden; show it before you transform it.`);
|
|
2531
2735
|
if (content !== "selection-copy") {
|
|
2532
2736
|
requireUnlocked(layer, "position", "transform it");
|
|
@@ -2548,6 +2752,26 @@ var FreeTransformSession = class {
|
|
|
2548
2752
|
this.shown = this.startLayers;
|
|
2549
2753
|
return;
|
|
2550
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
|
+
}
|
|
2551
2775
|
if (layer.type === "shape" && !maskAlone) {
|
|
2552
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).`);
|
|
2553
2777
|
const g = shapeGeometryBounds(layer);
|
|
@@ -2650,6 +2874,9 @@ var FreeTransformSession = class {
|
|
|
2650
2874
|
/** True when the layer is a shape layer (the transform maps its paths and renders them again). */
|
|
2651
2875
|
shapeLayer = false;
|
|
2652
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;
|
|
2653
2880
|
geometryValue;
|
|
2654
2881
|
smart = null;
|
|
2655
2882
|
smartReduced = /* @__PURE__ */ new Map();
|
|
@@ -2698,6 +2925,9 @@ var FreeTransformSession = class {
|
|
|
2698
2925
|
};
|
|
2699
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.");
|
|
2700
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
|
+
}
|
|
2701
2931
|
this.geometryValue = next;
|
|
2702
2932
|
if (preview !== false) this.render(preview);
|
|
2703
2933
|
return next;
|
|
@@ -2705,6 +2935,7 @@ var FreeTransformSession = class {
|
|
|
2705
2935
|
/** Resamples every part for the current geometry and shows it (or returns it, for commit). */
|
|
2706
2936
|
resample(final, preview = {}) {
|
|
2707
2937
|
if (this.smart) return { layers: this.resampleSmart(final, preview), selection: this.startSelection };
|
|
2938
|
+
if (this.typeTarget) return { layers: this.resampleType(), selection: this.startSelection };
|
|
2708
2939
|
const g = this.geometryValue;
|
|
2709
2940
|
const identity = isIdentityTransform(g);
|
|
2710
2941
|
const warp = hasWarp(g);
|
|
@@ -2785,6 +3016,27 @@ var FreeTransformSession = class {
|
|
|
2785
3016
|
const surface = renderShape(moved, this.host.shapeEnv(), final ? {} : { clip: preview.clip });
|
|
2786
3017
|
return mapLayer(this.startLayers, layer.id, () => ({ ...moved, surface }));
|
|
2787
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
|
+
}
|
|
2788
3040
|
/** A smart object re-rendered from its contents for the current geometry (previews leave the smart filters out). */
|
|
2789
3041
|
resampleSmart(final, preview) {
|
|
2790
3042
|
const layer = this.smart;
|
|
@@ -2808,7 +3060,7 @@ var FreeTransformSession = class {
|
|
|
2808
3060
|
/** Whether the geometry (or the interpolation of a smart object) differs from where it started. */
|
|
2809
3061
|
isChanged() {
|
|
2810
3062
|
const g = this.geometryValue;
|
|
2811
|
-
if (!this.smart) return !isIdentityTransform(g);
|
|
3063
|
+
if (!this.smart && !this.typeTarget) return !isIdentityTransform(g);
|
|
2812
3064
|
const a = this.startGeometry;
|
|
2813
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);
|
|
2814
3066
|
const warp = (w, box) => w && hasWarp({ box, corners: a.corners, warp: w }) ? JSON.stringify(w) : "";
|
|
@@ -2845,7 +3097,7 @@ var FreeTransformSession = class {
|
|
|
2845
3097
|
const id = this.copyId ?? this.layerId;
|
|
2846
3098
|
const layer = id ? findLayer(layers, id)?.layer : void 0;
|
|
2847
3099
|
if (this.moves.includes("pixels") && layer?.type === "pixel") bounds = toDoc(contentBounds(this.host.pixelsOf(layer)), layer.offset);
|
|
2848
|
-
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);
|
|
2849
3101
|
else if (this.moves.includes("mask") && layer?.mask) bounds = toDoc(contentBounds(layer.mask.surface), layer.type === "pixel" ? layer.offset : { x: 0, y: 0 });
|
|
2850
3102
|
else if (this.moves.includes("selection")) bounds = selection?.bounds ? { ...selection.bounds } : null;
|
|
2851
3103
|
return {
|
|
@@ -3099,6 +3351,7 @@ var ImageEditorController = class {
|
|
|
3099
3351
|
compositor = new LayerCompositor();
|
|
3100
3352
|
constructor(options = {}) {
|
|
3101
3353
|
this.maxHistoryDepth = options.maxHistoryDepth ?? 50;
|
|
3354
|
+
if (options.textShaper !== void 0) this.textShaperValue = options.textShaper;
|
|
3102
3355
|
this.maxHistoryBytes = options.maxHistoryBytes ?? 512 * 1024 * 1024;
|
|
3103
3356
|
this.onStateChange = options.onStateChange;
|
|
3104
3357
|
this.renderer = new ImageEditorRenderer();
|
|
@@ -3805,6 +4058,21 @@ var ImageEditorController = class {
|
|
|
3805
4058
|
const b = shapeGeometryBounds(moved) ?? { x: 0, y: 0 };
|
|
3806
4059
|
return { offset: { x: Math.round(b.x), y: Math.round(b.y) }, bounds: this.getLayerBounds(id) };
|
|
3807
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
|
+
}
|
|
3808
4076
|
const current = layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
|
|
3809
4077
|
const x = move.x ?? current.x + (move.dx ?? 0);
|
|
3810
4078
|
const y = move.y ?? current.y + (move.dy ?? 0);
|
|
@@ -3953,6 +4221,7 @@ var ImageEditorController = class {
|
|
|
3953
4221
|
canvas: () => this.naturalSize,
|
|
3954
4222
|
pixelsOf: (layer) => this.surfaceOf(layer),
|
|
3955
4223
|
shapeEnv: () => this.shapeEnv(),
|
|
4224
|
+
textEnv: () => this.textEnv(),
|
|
3956
4225
|
capture: () => this.captureHistoryEntry(),
|
|
3957
4226
|
show: (layers, selection, active) => {
|
|
3958
4227
|
this.state.layers = layers;
|
|
@@ -4053,7 +4322,7 @@ var ImageEditorController = class {
|
|
|
4053
4322
|
if (turn !== 90 && turn !== -90 && turn !== 180) throw new LayerOperationError(`turn must be 90, -90 or 180, got ${String(turn)}.`);
|
|
4054
4323
|
const session = this.beginFreeTransform({ interpolation: "nearest", ...init });
|
|
4055
4324
|
try {
|
|
4056
|
-
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;
|
|
4057
4326
|
const cx = b.x + b.width / 2;
|
|
4058
4327
|
const cy = b.y + b.height / 2;
|
|
4059
4328
|
const ox = turn === 180 ? 0 : Math.round(cx - b.height / 2) - (cx - b.height / 2);
|
|
@@ -4773,6 +5042,7 @@ var ImageEditorController = class {
|
|
|
4773
5042
|
} else {
|
|
4774
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.`);
|
|
4775
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).`);
|
|
4776
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).`);
|
|
4777
5047
|
requireUnlocked(layer, "pixels", action);
|
|
4778
5048
|
}
|
|
@@ -5478,7 +5748,11 @@ var ImageEditorController = class {
|
|
|
5478
5748
|
// --- Annotations & Layers ---
|
|
5479
5749
|
/** The layer showing annotation `id`: an annotation layer, or a shape layer migrated from it. */
|
|
5480
5750
|
annotationLayer(id) {
|
|
5481
|
-
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() };
|
|
5482
5756
|
}
|
|
5483
5757
|
/**
|
|
5484
5758
|
* Adds an annotation as its own layer (top of the stack unless `aboveId` is given). Pen,
|
|
@@ -5490,7 +5764,7 @@ var ImageEditorController = class {
|
|
|
5490
5764
|
this.recordHistory();
|
|
5491
5765
|
const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
5492
5766
|
const fullAnnotation = { ...annotation, id };
|
|
5493
|
-
const layer = annotationLayerFor(fullAnnotation, this.
|
|
5767
|
+
const layer = annotationLayerFor(fullAnnotation, this.annotationEnv());
|
|
5494
5768
|
const loc = placement.aboveId ? findLayer(this.state.layers, placement.aboveId) : null;
|
|
5495
5769
|
this.state.layers = loc ? insertLayer(this.state.layers, layer, loc.parent?.id ?? null, loc.index + 1) : insertLayer(this.state.layers, layer, null);
|
|
5496
5770
|
this.state.selectedAnnotationId = id;
|
|
@@ -5510,24 +5784,14 @@ var ImageEditorController = class {
|
|
|
5510
5784
|
}
|
|
5511
5785
|
this.notify();
|
|
5512
5786
|
}
|
|
5513
|
-
/** `layer` (an annotation or
|
|
5787
|
+
/** `layer` (an annotation, or a shape or type layer migrated from one) showing `annotation` instead, keeping its other properties. */
|
|
5514
5788
|
layerForAnnotation(layer, annotation, name) {
|
|
5515
|
-
const
|
|
5516
|
-
const {
|
|
5517
|
-
const
|
|
5518
|
-
if (
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
const fresh = rerenderShape({ ...base, components: shape.components, fill: shape.fill, stroke: shape.stroke }, this.shapeEnv());
|
|
5522
|
-
delete fresh.fillRule;
|
|
5523
|
-
return { ...fresh, annotation };
|
|
5524
|
-
}
|
|
5525
|
-
const created = createShapeLayer({ ...shape, name, blend: base.blend }, this.shapeEnv(), layer.id);
|
|
5526
|
-
const { type: _t2, ...rest2 } = base;
|
|
5527
|
-
return { ...rest2, ...created, annotation };
|
|
5528
|
-
}
|
|
5529
|
-
const { type: _t, components: _c, fill: _f, stroke: _s, surface: _sf, offset: _o, fillRule: _r, fillOpacity: _fo, psd: _p, ...rest } = props;
|
|
5530
|
-
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 };
|
|
5531
5795
|
}
|
|
5532
5796
|
removeAnnotation(id) {
|
|
5533
5797
|
this.recordHistory();
|
|
@@ -5542,7 +5806,7 @@ var ImageEditorController = class {
|
|
|
5542
5806
|
clearAnnotations() {
|
|
5543
5807
|
this.recordHistory();
|
|
5544
5808
|
let layers = this.state.layers;
|
|
5545
|
-
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);
|
|
5546
5810
|
this.state.layers = layers;
|
|
5547
5811
|
this.state.selectedAnnotationId = null;
|
|
5548
5812
|
this.notify();
|
|
@@ -5781,6 +6045,233 @@ var ImageEditorController = class {
|
|
|
5781
6045
|
this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
|
|
5782
6046
|
return target.id;
|
|
5783
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
|
+
}
|
|
5784
6275
|
// --- Shape layers ---
|
|
5785
6276
|
/** What shape rendering needs: the canvas size and the document's patterns. */
|
|
5786
6277
|
shapeEnv() {
|
|
@@ -5957,11 +6448,11 @@ var ImageEditorController = class {
|
|
|
5957
6448
|
}
|
|
5958
6449
|
} else if (layer.type === "smart") {
|
|
5959
6450
|
return this.rasterizeSmartObject(id);
|
|
5960
|
-
} else if (layer.type === "shape") {
|
|
5961
|
-
next = shapeAsPixelLayer(layer);
|
|
6451
|
+
} else if (layer.type === "shape" || layer.type === "type") {
|
|
6452
|
+
next = layer.type === "shape" ? shapeAsPixelLayer(layer) : typeAsPixelLayer(layer);
|
|
5962
6453
|
if (this.state.selectedAnnotationId && this.state.selectedAnnotationId === layer.annotation?.id) this.state.selectedAnnotationId = null;
|
|
5963
6454
|
} else {
|
|
5964
|
-
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.`);
|
|
5965
6456
|
}
|
|
5966
6457
|
this.commitLayers(mapLayer(this.state.layers, id, () => next));
|
|
5967
6458
|
return id;
|
|
@@ -6086,9 +6577,9 @@ var ImageEditorController = class {
|
|
|
6086
6577
|
if (mask2) out2.mask = mask2;
|
|
6087
6578
|
return out2;
|
|
6088
6579
|
}
|
|
6089
|
-
if (layer.type === "shape") {
|
|
6090
|
-
add2("Shape layers were written as pixel layers", layer.name);
|
|
6091
|
-
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);
|
|
6092
6583
|
const mask2 = bakeMask(layer);
|
|
6093
6584
|
if (mask2) out2.mask = mask2;
|
|
6094
6585
|
else delete out2.mask;
|
|
@@ -6190,7 +6681,7 @@ var ImageEditorController = class {
|
|
|
6190
6681
|
const bg = developTarget(layers, null);
|
|
6191
6682
|
if (bg && bg.source !== "document") layers = mapLayer(layers, bg.id, () => ({ ...bg, source: "document" }));
|
|
6192
6683
|
if (clearAnnotations) {
|
|
6193
|
-
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);
|
|
6194
6685
|
this.state.selectedAnnotationId = null;
|
|
6195
6686
|
}
|
|
6196
6687
|
this.state.layers = layers;
|
|
@@ -6266,7 +6757,8 @@ var ImageEditorController = class {
|
|
|
6266
6757
|
problems,
|
|
6267
6758
|
smartContents: Array.isArray(data.smartContents) ? data.smartContents : [],
|
|
6268
6759
|
canvas,
|
|
6269
|
-
pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null
|
|
6760
|
+
pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null,
|
|
6761
|
+
text: this.textEnv()
|
|
6270
6762
|
});
|
|
6271
6763
|
this.lastLoadProblems = problems;
|
|
6272
6764
|
s.crop = data.crop ? { ...data.crop } : null;
|
|
@@ -6469,7 +6961,7 @@ function collectStateTiles(layers, into) {
|
|
|
6469
6961
|
for (const l of flattenLayers(layers)) {
|
|
6470
6962
|
if (l.type === "pixel" && l.source !== "document") collectTiles(l.surface, into);
|
|
6471
6963
|
if (l.mask && !l.mask.source) collectTiles(l.mask.surface, into);
|
|
6472
|
-
if (l.type === "shape") collectTiles(l.surface, into);
|
|
6964
|
+
if (l.type === "shape" || l.type === "type") collectTiles(l.surface, into);
|
|
6473
6965
|
if (l.type === "smart") {
|
|
6474
6966
|
collectTiles(l.surface, into);
|
|
6475
6967
|
if (l.smartFilters?.mask) collectTiles(l.smartFilters.mask.surface, into);
|
|
@@ -6518,7 +7010,7 @@ function resizePixels(px2, width, height) {
|
|
|
6518
7010
|
function withoutAnnotations(layers) {
|
|
6519
7011
|
const out = [];
|
|
6520
7012
|
for (const l of layers) {
|
|
6521
|
-
if (l.type === "annotation" || l.type === "shape" && l.annotation) continue;
|
|
7013
|
+
if (l.type === "annotation" || (l.type === "shape" || l.type === "type") && l.annotation) continue;
|
|
6522
7014
|
out.push(l.type === "group" ? { ...l, children: withoutAnnotations(l.children) } : l);
|
|
6523
7015
|
}
|
|
6524
7016
|
return out;
|
|
@@ -6708,7 +7200,7 @@ function loadImageEditorDocument(input) {
|
|
|
6708
7200
|
if (state.selection === void 0) delete state.selection;
|
|
6709
7201
|
return { state, report: result.report, unknown: result.unknown };
|
|
6710
7202
|
}
|
|
6711
|
-
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"]);
|
|
6712
7204
|
function cleanLayers(value, dropped) {
|
|
6713
7205
|
const out = [];
|
|
6714
7206
|
for (const raw of value) {
|
|
@@ -7768,6 +8260,18 @@ function describeLayer(layer, activeId, boundsOf) {
|
|
|
7768
8260
|
};
|
|
7769
8261
|
if (layer.annotation) out.annotation = { id: layer.annotation.id, type: layer.annotation.type };
|
|
7770
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;
|
|
7771
8275
|
case "smart":
|
|
7772
8276
|
out.bounds = boundsOf(layer);
|
|
7773
8277
|
out.fillOpacity = round3(layer.fillOpacity ?? 1);
|
|
@@ -7813,7 +8317,7 @@ function createImageLayerTools(controller) {
|
|
|
7813
8317
|
const listLayers = {
|
|
7814
8318
|
name: "list_layers",
|
|
7815
8319
|
title: "List image layers",
|
|
7816
|
-
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.",
|
|
7817
8321
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
7818
8322
|
annotations: { title: "List image layers", ...READ2 },
|
|
7819
8323
|
handler: () => {
|
|
@@ -8157,7 +8661,7 @@ function createImageLayerTools(controller) {
|
|
|
8157
8661
|
const rasterize = {
|
|
8158
8662
|
name: "rasterize_layer",
|
|
8159
8663
|
title: "Rasterize a layer",
|
|
8160
|
-
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.",
|
|
8161
8665
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
|
|
8162
8666
|
annotations: { title: "Rasterize a layer", ...DESTRUCTIVE },
|
|
8163
8667
|
handler: async ({ layerId }) => ({ layer: describe(await controller.rasterizeLayer(exists(layerId))) })
|
|
@@ -10006,6 +10510,176 @@ function createImageShapeTools(controller) {
|
|
|
10006
10510
|
return [get, add2, update, setComponent, addComponent, removeComponent, merge];
|
|
10007
10511
|
}
|
|
10008
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
|
+
|
|
10009
10683
|
// src/image-editor/tools.ts
|
|
10010
10684
|
var LAYER_PATH = /^layers\.([^.]+)\.(.+)$/;
|
|
10011
10685
|
function adjustmentsSummary(adjustments) {
|
|
@@ -10246,6 +10920,7 @@ function createImageEditorToolProvider(controller, options = {}) {
|
|
|
10246
10920
|
...createImageTransformTools(controller),
|
|
10247
10921
|
...createImageSmartObjectTools(controller),
|
|
10248
10922
|
...createImageShapeTools(controller),
|
|
10923
|
+
...createImageTypeTools(controller),
|
|
10249
10924
|
...createImageEffectTools(controller),
|
|
10250
10925
|
...createImagePaintTools(controller),
|
|
10251
10926
|
...createPigmentTools(),
|
|
@@ -10588,6 +11263,8 @@ function layerBadge(layer) {
|
|
|
10588
11263
|
return "so";
|
|
10589
11264
|
case "shape":
|
|
10590
11265
|
return "shp";
|
|
11266
|
+
case "type":
|
|
11267
|
+
return "T";
|
|
10591
11268
|
}
|
|
10592
11269
|
}
|
|
10593
11270
|
function dropZone(fraction, isGroup) {
|
|
@@ -10627,7 +11304,7 @@ function layerMenu(controller) {
|
|
|
10627
11304
|
{ action: "delete-mask", label: "Delete Layer Mask", enabled: Boolean(active?.mask) },
|
|
10628
11305
|
{ action: "toggle-mask", label: active?.mask?.enabled === false ? "Enable Layer Mask" : "Disable Layer Mask", enabled: Boolean(active?.mask) },
|
|
10629
11306
|
{ action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
|
|
10630
|
-
{ 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) },
|
|
10631
11308
|
{ action: "convert-smart", label: "Convert to Smart Object", enabled: Boolean(active && active.type !== "annotation") },
|
|
10632
11309
|
{ action: "edit-contents", label: "Edit Contents", enabled: active?.type === "smart" },
|
|
10633
11310
|
{ action: "smart-via-copy", label: "New Smart Object via Copy", enabled: active?.type === "smart" },
|
|
@@ -11560,6 +12237,8 @@ export {
|
|
|
11560
12237
|
D50,
|
|
11561
12238
|
D65,
|
|
11562
12239
|
DEFAULT_ADJUSTMENTS,
|
|
12240
|
+
DEFAULT_AUTO_LEADING,
|
|
12241
|
+
DEFAULT_BLENDER,
|
|
11563
12242
|
DEFAULT_BRUSH,
|
|
11564
12243
|
DEFAULT_DEPTH_MASK,
|
|
11565
12244
|
DEFAULT_GLOBAL_LIGHT,
|
|
@@ -11567,6 +12246,8 @@ export {
|
|
|
11567
12246
|
DEFAULT_MIXER,
|
|
11568
12247
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
11569
12248
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
12249
|
+
DEFAULT_SMUDGE,
|
|
12250
|
+
DEFAULT_TEXT_STYLE,
|
|
11570
12251
|
DEFAULT_TRANSFORM,
|
|
11571
12252
|
DEPTH_MASK_CONTROLS,
|
|
11572
12253
|
DEVELOP_FILTER_ID,
|
|
@@ -11616,6 +12297,7 @@ export {
|
|
|
11616
12297
|
MixerStroke,
|
|
11617
12298
|
PAINTERLY_BLEND_MODES,
|
|
11618
12299
|
PAINT_TOOLS,
|
|
12300
|
+
PARAGRAPH_STYLE_FIELDS,
|
|
11619
12301
|
PATTERN_BLOCK_KEYS,
|
|
11620
12302
|
PIGMENT_BANDS,
|
|
11621
12303
|
PIGMENT_LAMBDA_MIN,
|
|
@@ -11623,6 +12305,7 @@ export {
|
|
|
11623
12305
|
PIGMENT_WAVELENGTHS,
|
|
11624
12306
|
PSD_BLEND_KEYS,
|
|
11625
12307
|
PSD_FILE_ACCEPT,
|
|
12308
|
+
PaintExchange,
|
|
11626
12309
|
PaintGesture,
|
|
11627
12310
|
PaintStrokeSession,
|
|
11628
12311
|
PatternPlacement,
|
|
@@ -11639,14 +12322,19 @@ export {
|
|
|
11639
12322
|
SMART_RENDER_MARGIN,
|
|
11640
12323
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
11641
12324
|
SelectionGesture,
|
|
12325
|
+
SmudgeTip,
|
|
11642
12326
|
StrokeCoverage,
|
|
11643
12327
|
SurfaceDraft,
|
|
12328
|
+
TEXT_ALIGNS,
|
|
12329
|
+
TEXT_RENDER_MARGIN,
|
|
12330
|
+
TEXT_STYLE_FIELDS,
|
|
11644
12331
|
TILE,
|
|
11645
12332
|
TILE_SHIFT,
|
|
11646
12333
|
TINT_SCALE,
|
|
11647
12334
|
TONE_LUT_SIZE,
|
|
11648
12335
|
TONE_RANGE_PRESETS,
|
|
11649
12336
|
TRANSFORM_ANCHORS,
|
|
12337
|
+
TileMedium,
|
|
11650
12338
|
TiledSurface,
|
|
11651
12339
|
TransformTooLargeError,
|
|
11652
12340
|
VIBRANCE_GREY,
|
|
@@ -11673,6 +12361,7 @@ export {
|
|
|
11673
12361
|
annotationLayerName,
|
|
11674
12362
|
annotationOfLayer,
|
|
11675
12363
|
annotationOfShape,
|
|
12364
|
+
annotationOfType,
|
|
11676
12365
|
applyAdjustmentLayers,
|
|
11677
12366
|
applyAdjustments,
|
|
11678
12367
|
applyBoxBlur,
|
|
@@ -11696,8 +12385,10 @@ export {
|
|
|
11696
12385
|
blendImageData,
|
|
11697
12386
|
blendLum,
|
|
11698
12387
|
blendPixel,
|
|
12388
|
+
blendRadius,
|
|
11699
12389
|
blendRgb,
|
|
11700
12390
|
blendTile,
|
|
12391
|
+
blenderDab,
|
|
11701
12392
|
blurFilter,
|
|
11702
12393
|
borderCoverage,
|
|
11703
12394
|
boxMean,
|
|
@@ -11713,6 +12404,7 @@ export {
|
|
|
11713
12404
|
calculateCropDrag,
|
|
11714
12405
|
canvasCompositeOperation,
|
|
11715
12406
|
canvasToImagePoint,
|
|
12407
|
+
caretAt,
|
|
11716
12408
|
changedTiles,
|
|
11717
12409
|
clampControlValue,
|
|
11718
12410
|
clampCropToImage,
|
|
@@ -11739,6 +12431,8 @@ export {
|
|
|
11739
12431
|
coverageToByte,
|
|
11740
12432
|
coveringRect,
|
|
11741
12433
|
createAdjustmentLayer,
|
|
12434
|
+
createBlenderScratch,
|
|
12435
|
+
createCanvasTextShaper,
|
|
11742
12436
|
createImageCaptureSurface,
|
|
11743
12437
|
createImageEditTools,
|
|
11744
12438
|
createImageEditorToolProvider,
|
|
@@ -11750,10 +12444,15 @@ export {
|
|
|
11750
12444
|
createImageShapeTools,
|
|
11751
12445
|
createImageSmartObjectTools,
|
|
11752
12446
|
createImageTransformTools,
|
|
12447
|
+
createImageTypeTools,
|
|
12448
|
+
createOutlineTextShaper,
|
|
11753
12449
|
createPigment,
|
|
11754
12450
|
createPigmentTools,
|
|
11755
12451
|
createSelectionMask,
|
|
11756
12452
|
createShapeLayer,
|
|
12453
|
+
createTypeLayer,
|
|
12454
|
+
cssFont,
|
|
12455
|
+
cssFontFamily,
|
|
11757
12456
|
curveLut,
|
|
11758
12457
|
curveLutFromLevels,
|
|
11759
12458
|
curvePointToBox,
|
|
@@ -11811,6 +12510,7 @@ export {
|
|
|
11811
12510
|
encodeSurface,
|
|
11812
12511
|
evalWarpMesh,
|
|
11813
12512
|
evaluateAdjustmentLayers,
|
|
12513
|
+
exchangeRates,
|
|
11814
12514
|
expandCoverage,
|
|
11815
12515
|
exportPsd,
|
|
11816
12516
|
extractNormalizedLayerItems,
|
|
@@ -11821,9 +12521,12 @@ export {
|
|
|
11821
12521
|
findLayer,
|
|
11822
12522
|
findLayerControl,
|
|
11823
12523
|
finishTile,
|
|
12524
|
+
fitParagraphRuns,
|
|
12525
|
+
fitRuns,
|
|
11824
12526
|
flattenLayers,
|
|
11825
12527
|
flattenPath,
|
|
11826
12528
|
flipCorners,
|
|
12529
|
+
fontOf,
|
|
11827
12530
|
formatControlValue,
|
|
11828
12531
|
fullSelection,
|
|
11829
12532
|
gaussianBlur,
|
|
@@ -11914,6 +12617,7 @@ export {
|
|
|
11914
12617
|
layerTree,
|
|
11915
12618
|
layerTypeLabel,
|
|
11916
12619
|
layersFromLegacy,
|
|
12620
|
+
layoutText,
|
|
11917
12621
|
legacyAdjustmentLayer,
|
|
11918
12622
|
legacyMaskOf,
|
|
11919
12623
|
legacyView,
|
|
@@ -11941,6 +12645,7 @@ export {
|
|
|
11941
12645
|
mixPigmentRgb,
|
|
11942
12646
|
mixPigments,
|
|
11943
12647
|
mixerLoadResult,
|
|
12648
|
+
mixerRates,
|
|
11944
12649
|
modifySelection,
|
|
11945
12650
|
moveCurvePoint,
|
|
11946
12651
|
moveLayer,
|
|
@@ -11955,6 +12660,7 @@ export {
|
|
|
11955
12660
|
normalizeAdjustmentLayers,
|
|
11956
12661
|
normalizeAngle,
|
|
11957
12662
|
normalizeBlendMode,
|
|
12663
|
+
normalizeBlender,
|
|
11958
12664
|
normalizeBrush,
|
|
11959
12665
|
normalizeFilter,
|
|
11960
12666
|
normalizeLayerEffects,
|
|
@@ -11968,13 +12674,16 @@ export {
|
|
|
11968
12674
|
normalizeShapePaint,
|
|
11969
12675
|
normalizeShapeStroke,
|
|
11970
12676
|
normalizeSmartFilter,
|
|
12677
|
+
normalizeSmudge,
|
|
11971
12678
|
normalizeStoredAdjustments,
|
|
11972
12679
|
normalizeSubpaths,
|
|
12680
|
+
normalizeText,
|
|
11973
12681
|
offsetLayer,
|
|
11974
12682
|
offsetShape,
|
|
11975
12683
|
offsetSmartObject,
|
|
11976
12684
|
openPsdFile,
|
|
11977
12685
|
packRgba,
|
|
12686
|
+
paragraphRanges,
|
|
11978
12687
|
parseAdjustmentBlock,
|
|
11979
12688
|
parseBlend,
|
|
11980
12689
|
parseColor,
|
|
@@ -11986,7 +12695,10 @@ export {
|
|
|
11986
12695
|
parsePatternBlock,
|
|
11987
12696
|
parseSelectionMode,
|
|
11988
12697
|
parseVersionedDescriptor,
|
|
12698
|
+
patchParagraphStyle,
|
|
12699
|
+
patchTextStyle,
|
|
11989
12700
|
photoFilterMatrix,
|
|
12701
|
+
pigment8,
|
|
11990
12702
|
pixelLayerBounds,
|
|
11991
12703
|
pixelLayers,
|
|
11992
12704
|
placeContents,
|
|
@@ -12040,19 +12752,24 @@ export {
|
|
|
12040
12752
|
renderShape,
|
|
12041
12753
|
renderShapeRect,
|
|
12042
12754
|
renderSmartObject,
|
|
12755
|
+
renderText,
|
|
12043
12756
|
replaceContents,
|
|
12757
|
+
replaceTextRange,
|
|
12044
12758
|
requireLayer,
|
|
12045
12759
|
requireOperation,
|
|
12046
12760
|
requireShapeLayer,
|
|
12047
12761
|
requireSmartObject,
|
|
12762
|
+
requireTypeLayer,
|
|
12048
12763
|
requireUnlocked,
|
|
12049
12764
|
rerender,
|
|
12050
12765
|
rerenderShape,
|
|
12766
|
+
rerenderText,
|
|
12051
12767
|
resizePixels,
|
|
12052
12768
|
resizeSelection,
|
|
12053
12769
|
resizeWarpMesh,
|
|
12054
12770
|
resolveExternalAssets,
|
|
12055
12771
|
resolveMosaicRegionBounds,
|
|
12772
|
+
restyleRange,
|
|
12056
12773
|
rgbToGray,
|
|
12057
12774
|
rgbToHsl,
|
|
12058
12775
|
rgbToReflectance,
|
|
@@ -12099,16 +12816,22 @@ export {
|
|
|
12099
12816
|
smoothCoverage,
|
|
12100
12817
|
smoothPoints,
|
|
12101
12818
|
solidSurface,
|
|
12819
|
+
spliceRuns,
|
|
12102
12820
|
splitOverlayAnnotations,
|
|
12103
12821
|
srgb8ToLab,
|
|
12104
12822
|
srgbToLinear,
|
|
12105
12823
|
standardBlendMode,
|
|
12824
|
+
styleAt,
|
|
12106
12825
|
sumHistograms,
|
|
12107
12826
|
surfaceBytes,
|
|
12108
12827
|
surfaceFromPixels,
|
|
12109
12828
|
surfacesEqual,
|
|
12110
12829
|
tempTintToXy,
|
|
12111
12830
|
temperatureToKelvin,
|
|
12831
|
+
textIndexAt,
|
|
12832
|
+
textMatrix,
|
|
12833
|
+
textOutlines,
|
|
12834
|
+
textRange,
|
|
12112
12835
|
tileCoord,
|
|
12113
12836
|
tileKey,
|
|
12114
12837
|
tileRect,
|
|
@@ -12117,6 +12840,7 @@ export {
|
|
|
12117
12840
|
tipReach,
|
|
12118
12841
|
tipShape,
|
|
12119
12842
|
toHex,
|
|
12843
|
+
toTextSpace,
|
|
12120
12844
|
toneCurve,
|
|
12121
12845
|
toneLuts,
|
|
12122
12846
|
toneRange,
|
|
@@ -12124,10 +12848,15 @@ export {
|
|
|
12124
12848
|
transformMap,
|
|
12125
12849
|
transformShapeComponents,
|
|
12126
12850
|
transformSurface,
|
|
12851
|
+
transformTypeLayer,
|
|
12127
12852
|
transformedBounds,
|
|
12128
12853
|
transformedBox,
|
|
12129
12854
|
transformedPoint,
|
|
12130
12855
|
translateSurface,
|
|
12856
|
+
typeAsPixelLayer,
|
|
12857
|
+
typeFrame,
|
|
12858
|
+
typeFromAnnotation,
|
|
12859
|
+
typeLayerName,
|
|
12131
12860
|
ungroupLayer,
|
|
12132
12861
|
uniformTile,
|
|
12133
12862
|
unionRect,
|
|
@@ -12137,6 +12866,7 @@ export {
|
|
|
12137
12866
|
updateLayerMaskProps,
|
|
12138
12867
|
updateShapeLayer,
|
|
12139
12868
|
updateSmartFilter,
|
|
12869
|
+
updateTypeLayer,
|
|
12140
12870
|
uvToXy,
|
|
12141
12871
|
validateEffectGradient,
|
|
12142
12872
|
validateLayerEffect,
|