@overtone-art/canvas-editor-core 0.6.4 → 0.8.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/dist/chunk-XNGPX7FG.mjs +641 -0
- package/dist/chunk-XNGPX7FG.mjs.map +1 -0
- package/dist/index.d.mts +182 -4
- package/dist/index.d.ts +182 -4
- package/dist/index.global.js +55 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +668 -129
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +320 -141
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +146 -0
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +3 -1
- package/dist/node.mjs.map +1 -1
- package/dist/{types-DSu2jbiV.d.mts → types-DmintAiu.d.mts} +56 -2
- package/dist/{types-DSu2jbiV.d.ts → types-DmintAiu.d.ts} +56 -2
- package/package.json +1 -1
- package/dist/chunk-MCBRZQ4M.mjs +0 -261
- package/dist/chunk-MCBRZQ4M.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
TEXT_FIT_SLACK,
|
|
3
|
+
TextWrapManager,
|
|
4
|
+
applyMatrix,
|
|
5
|
+
applyRelativeMatrix,
|
|
6
|
+
applyTextWrapToObject,
|
|
7
|
+
asObject,
|
|
8
|
+
asText,
|
|
2
9
|
computeCoverPlacement,
|
|
3
10
|
computePrintAreaClip,
|
|
4
11
|
displaceRgba,
|
|
@@ -7,11 +14,22 @@ import {
|
|
|
7
14
|
exportMockup,
|
|
8
15
|
exportPNG,
|
|
9
16
|
exportPrintArea,
|
|
10
|
-
exportSVG
|
|
11
|
-
|
|
17
|
+
exportSVG,
|
|
18
|
+
fitTextWidth,
|
|
19
|
+
fitToBox,
|
|
20
|
+
matrixOf,
|
|
21
|
+
preWrapWordSplit,
|
|
22
|
+
readTextWrap,
|
|
23
|
+
relativeMatrix,
|
|
24
|
+
textInk,
|
|
25
|
+
toCanvasSpace,
|
|
26
|
+
toHostSpace,
|
|
27
|
+
toMatrix,
|
|
28
|
+
unwrapGroup
|
|
29
|
+
} from "./chunk-XNGPX7FG.mjs";
|
|
12
30
|
|
|
13
31
|
// src/editor.ts
|
|
14
|
-
import { Canvas, FabricImage as FabricImage3, Group as Group7, Textbox as Textbox2, filters } from "fabric";
|
|
32
|
+
import { Canvas, FabricImage as FabricImage3, Group as Group7, Rect as Rect4, Textbox as Textbox2, filters } from "fabric";
|
|
15
33
|
|
|
16
34
|
// src/events.ts
|
|
17
35
|
var EventEmitter = class {
|
|
@@ -55,6 +73,53 @@ import { ActiveSelection } from "fabric";
|
|
|
55
73
|
import { nanoid } from "nanoid";
|
|
56
74
|
var generateId = () => nanoid(12);
|
|
57
75
|
|
|
76
|
+
// src/blend.ts
|
|
77
|
+
var BLEND_MODES = [
|
|
78
|
+
"normal",
|
|
79
|
+
"darken",
|
|
80
|
+
"multiply",
|
|
81
|
+
"color-burn",
|
|
82
|
+
"lighten",
|
|
83
|
+
"screen",
|
|
84
|
+
"color-dodge",
|
|
85
|
+
"linear-dodge",
|
|
86
|
+
"overlay",
|
|
87
|
+
"soft-light",
|
|
88
|
+
"hard-light",
|
|
89
|
+
"difference",
|
|
90
|
+
"exclusion",
|
|
91
|
+
"hue",
|
|
92
|
+
"saturation",
|
|
93
|
+
"color",
|
|
94
|
+
"luminosity"
|
|
95
|
+
];
|
|
96
|
+
var OPERATION_EXCEPTIONS = {
|
|
97
|
+
normal: "source-over",
|
|
98
|
+
"linear-dodge": "lighter"
|
|
99
|
+
};
|
|
100
|
+
function blendOperation(mode) {
|
|
101
|
+
return OPERATION_EXCEPTIONS[mode] ?? mode;
|
|
102
|
+
}
|
|
103
|
+
function blendModeOf(operation) {
|
|
104
|
+
if (!operation) return "normal";
|
|
105
|
+
for (const mode of BLEND_MODES) {
|
|
106
|
+
if (blendOperation(mode) === operation) return mode;
|
|
107
|
+
}
|
|
108
|
+
return "normal";
|
|
109
|
+
}
|
|
110
|
+
var BLEND_GROUPS = [
|
|
111
|
+
{ label: "Normal", modes: ["normal"] },
|
|
112
|
+
{ label: "Darken", modes: ["darken", "multiply", "color-burn"] },
|
|
113
|
+
{ label: "Lighten", modes: ["lighten", "screen", "color-dodge", "linear-dodge"] },
|
|
114
|
+
{ label: "Contrast", modes: ["overlay", "soft-light", "hard-light"] },
|
|
115
|
+
{ label: "Comparative", modes: ["difference", "exclusion"] },
|
|
116
|
+
{ label: "Composite", modes: ["hue", "saturation", "color", "luminosity"] }
|
|
117
|
+
];
|
|
118
|
+
function blendModeLabel(mode) {
|
|
119
|
+
if (mode === "linear-dodge") return "Linear Dodge (Add)";
|
|
120
|
+
return mode.split("-").map((word) => (word[0] ?? "").toUpperCase() + word.slice(1)).join(" ");
|
|
121
|
+
}
|
|
122
|
+
|
|
58
123
|
// src/layer.ts
|
|
59
124
|
var Layer = class {
|
|
60
125
|
id;
|
|
@@ -98,6 +163,7 @@ var Layer = class {
|
|
|
98
163
|
return Object.keys(this.meta).length > 0;
|
|
99
164
|
}
|
|
100
165
|
toData() {
|
|
166
|
+
const blend = blendModeOf(this.fabricObject.globalCompositeOperation);
|
|
101
167
|
return {
|
|
102
168
|
id: this.id,
|
|
103
169
|
type: this.type,
|
|
@@ -105,6 +171,7 @@ var Layer = class {
|
|
|
105
171
|
visible: this.visible,
|
|
106
172
|
locked: this.locked,
|
|
107
173
|
opacity: this.opacity,
|
|
174
|
+
...blend !== "normal" ? { blend } : {},
|
|
108
175
|
...this.hasMeta() ? { meta: this.meta } : {},
|
|
109
176
|
...this.children.length > 0 ? { children: this.children.map((c) => c.toData()) } : {}
|
|
110
177
|
};
|
|
@@ -266,7 +333,11 @@ var LayerManager = class {
|
|
|
266
333
|
visible: layer.visible,
|
|
267
334
|
opacity: layer.opacity,
|
|
268
335
|
selectable: !layer.locked,
|
|
269
|
-
evented: !layer.locked
|
|
336
|
+
evented: !layer.locked,
|
|
337
|
+
// Compositing lives only on the fabric object (see `blendModeOf`); carry
|
|
338
|
+
// it from the object being replaced or a blend set before this call
|
|
339
|
+
// silently reverts to normal.
|
|
340
|
+
globalCompositeOperation: previous.globalCompositeOperation
|
|
270
341
|
});
|
|
271
342
|
this.canvas.insertAt(Math.max(0, stackIndex), fabricObject);
|
|
272
343
|
if (layer.renderProxy) {
|
|
@@ -296,7 +367,12 @@ var LayerManager = class {
|
|
|
296
367
|
visible: layer.visible,
|
|
297
368
|
opacity: layer.opacity,
|
|
298
369
|
selectable: !layer.locked,
|
|
299
|
-
evented: !layer.locked
|
|
370
|
+
evented: !layer.locked,
|
|
371
|
+
// The proxy is what actually draws once a layer has one (see
|
|
372
|
+
// `LayerManager`'s class doc); a blend set before the proxy existed
|
|
373
|
+
// must not silently stop applying. Read off the layer's own object —
|
|
374
|
+
// that stays the single source of truth (see `blendModeOf`).
|
|
375
|
+
globalCompositeOperation: layer.fabricObject.globalCompositeOperation
|
|
300
376
|
});
|
|
301
377
|
this.canvas.add(proxy);
|
|
302
378
|
this.syncZOrder();
|
|
@@ -1143,60 +1219,7 @@ var STROKE2 = "#22c55e";
|
|
|
1143
1219
|
import { util } from "fabric";
|
|
1144
1220
|
|
|
1145
1221
|
// src/pattern/tiled-pattern-object.ts
|
|
1146
|
-
import { FabricObject, Point
|
|
1147
|
-
|
|
1148
|
-
// src/text-fit.ts
|
|
1149
|
-
import { Point } from "fabric";
|
|
1150
|
-
var TEXT_FIT_SLACK = 0.5;
|
|
1151
|
-
var MEASURE_WIDTH = 1e5;
|
|
1152
|
-
function unwrappedWidth(text) {
|
|
1153
|
-
const authored = text.width;
|
|
1154
|
-
try {
|
|
1155
|
-
text.set({ width: MEASURE_WIDTH });
|
|
1156
|
-
text.initDimensions?.();
|
|
1157
|
-
const measured = text.calcTextWidth?.() ?? authored;
|
|
1158
|
-
return Number.isFinite(measured) && measured > 0 ? measured : authored;
|
|
1159
|
-
} finally {
|
|
1160
|
-
text.set({ width: authored });
|
|
1161
|
-
text.initDimensions?.();
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
function asText(object) {
|
|
1165
|
-
if (!object) return null;
|
|
1166
|
-
const text = object;
|
|
1167
|
-
return typeof text.text === "string" && typeof text.calcTextWidth === "function" ? text : null;
|
|
1168
|
-
}
|
|
1169
|
-
function textInk(text) {
|
|
1170
|
-
const boxWidth = Math.max(0, text.width ?? 0);
|
|
1171
|
-
const measured = text.calcTextWidth?.() ?? boxWidth;
|
|
1172
|
-
const width = Math.max(1, Math.min(boxWidth, Number.isFinite(measured) ? measured : boxWidth));
|
|
1173
|
-
const slack = (boxWidth - width) / 2;
|
|
1174
|
-
const align = text.textAlign ?? "left";
|
|
1175
|
-
if (align.includes("center")) return { width, dx: 0 };
|
|
1176
|
-
const flip = text.flipX ? -1 : 1;
|
|
1177
|
-
return { width, dx: flip * (align.includes("right") ? slack : -slack) };
|
|
1178
|
-
}
|
|
1179
|
-
function fitTextWidth(object) {
|
|
1180
|
-
const text = asText(object);
|
|
1181
|
-
if (!text || text.path) return false;
|
|
1182
|
-
const before = textInk(text);
|
|
1183
|
-
const fitted = unwrappedWidth(text) + TEXT_FIT_SLACK;
|
|
1184
|
-
if (Math.abs(fitted - text.width) < TEXT_FIT_SLACK) return false;
|
|
1185
|
-
const centre = text.getCenterPoint();
|
|
1186
|
-
text.set({ width: fitted });
|
|
1187
|
-
text.initDimensions?.();
|
|
1188
|
-
const after = textInk(text);
|
|
1189
|
-
const shift = (before.dx - after.dx) * (text.scaleX ?? 1);
|
|
1190
|
-
const radians = (text.angle ?? 0) * Math.PI / 180;
|
|
1191
|
-
const moved = new Point(
|
|
1192
|
-
centre.x + shift * Math.cos(radians),
|
|
1193
|
-
centre.y + shift * Math.sin(radians)
|
|
1194
|
-
);
|
|
1195
|
-
text.setPositionByOrigin(moved, "center", "center");
|
|
1196
|
-
text.setCoords();
|
|
1197
|
-
text.dirty = true;
|
|
1198
|
-
return true;
|
|
1199
|
-
}
|
|
1222
|
+
import { FabricObject, Point } from "fabric";
|
|
1200
1223
|
|
|
1201
1224
|
// src/pattern/tile-geometry.ts
|
|
1202
1225
|
var MAX_TILES_PER_AXIS = 200;
|
|
@@ -1353,7 +1376,7 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
|
|
|
1353
1376
|
const centre = this.getCenterPoint();
|
|
1354
1377
|
const shift = this.shiftVector(tileW, tileH, this.liveAngle());
|
|
1355
1378
|
const ink = this.inkOffset();
|
|
1356
|
-
return new
|
|
1379
|
+
return new Point(centre.x - shift.x - ink.x, centre.y - shift.y - ink.y);
|
|
1357
1380
|
}
|
|
1358
1381
|
_render(ctx) {
|
|
1359
1382
|
const { width, height } = this.area;
|
|
@@ -1485,12 +1508,12 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
|
|
|
1485
1508
|
*/
|
|
1486
1509
|
inkOffset() {
|
|
1487
1510
|
const text = asText(this.source);
|
|
1488
|
-
if (!text) return new
|
|
1511
|
+
if (!text) return new Point(0, 0);
|
|
1489
1512
|
const { dx } = textInk(text);
|
|
1490
|
-
if (!dx) return new
|
|
1513
|
+
if (!dx) return new Point(0, 0);
|
|
1491
1514
|
const scaled = dx * (this.source.scaleX ?? 1);
|
|
1492
1515
|
const radians = (this.source.angle ?? 0) * Math.PI / 180;
|
|
1493
|
-
return new
|
|
1516
|
+
return new Point(scaled * Math.cos(radians), scaled * Math.sin(radians));
|
|
1494
1517
|
}
|
|
1495
1518
|
/** The source's on-canvas width, before the tile scale. */
|
|
1496
1519
|
baseW() {
|
|
@@ -1551,11 +1574,11 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
|
|
|
1551
1574
|
const radians = angle * Math.PI / 180;
|
|
1552
1575
|
const cos = Math.cos(radians);
|
|
1553
1576
|
const sin = Math.sin(radians);
|
|
1554
|
-
return new
|
|
1577
|
+
return new Point(dx * cos - dy * sin, dx * sin + dy * cos);
|
|
1555
1578
|
}
|
|
1556
1579
|
anchorFromOrigin(origin, tileW, tileH, angle) {
|
|
1557
1580
|
const shift = this.shiftVector(tileW, tileH, angle);
|
|
1558
|
-
return new
|
|
1581
|
+
return new Point(origin.x + shift.x, origin.y + shift.y);
|
|
1559
1582
|
}
|
|
1560
1583
|
/**
|
|
1561
1584
|
* Snapshot the source at (at least) `scale`, reusing the cached one while it
|
|
@@ -1969,7 +1992,7 @@ function buildCurvePathData(config, width, fontSize) {
|
|
|
1969
1992
|
}
|
|
1970
1993
|
|
|
1971
1994
|
// src/text-curve.ts
|
|
1972
|
-
var
|
|
1995
|
+
var MEASURE_WIDTH = 1e5;
|
|
1973
1996
|
var PATH_SLACK = 0.06;
|
|
1974
1997
|
var FALLBACK_LINE_HEIGHT = 1.16;
|
|
1975
1998
|
var patches = /* @__PURE__ */ new WeakMap();
|
|
@@ -2017,7 +2040,7 @@ function isCurvable(object) {
|
|
|
2017
2040
|
function measureText(text) {
|
|
2018
2041
|
const authored = text.width;
|
|
2019
2042
|
try {
|
|
2020
|
-
text.set({ width:
|
|
2043
|
+
text.set({ width: MEASURE_WIDTH });
|
|
2021
2044
|
text.initDimensions?.();
|
|
2022
2045
|
return Math.max(1, text.calcTextWidth?.() ?? text.width ?? 1);
|
|
2023
2046
|
} finally {
|
|
@@ -2487,6 +2510,7 @@ var MaskPresetManager = class {
|
|
|
2487
2510
|
object.dirty = true;
|
|
2488
2511
|
object.setCoords();
|
|
2489
2512
|
this.canvas.requestRenderAll();
|
|
2513
|
+
this.events.emit("masks:changed", { target: layerId });
|
|
2490
2514
|
this.events.emit("layer:modified", { layerId });
|
|
2491
2515
|
if (save) this.history.save();
|
|
2492
2516
|
return true;
|
|
@@ -2761,6 +2785,7 @@ async function deserializeEditor(editor, state) {
|
|
|
2761
2785
|
for (const item of staged) {
|
|
2762
2786
|
restoreLayer(editor, item.serialized, item.fabricObject);
|
|
2763
2787
|
}
|
|
2788
|
+
editor.wrap.refreshAll();
|
|
2764
2789
|
editor.canvas.requestRenderAll();
|
|
2765
2790
|
}
|
|
2766
2791
|
function restoreLayer(editor, serialized, fabricObject) {
|
|
@@ -3412,76 +3437,7 @@ function needsAbsoluteSpace(entries) {
|
|
|
3412
3437
|
}
|
|
3413
3438
|
|
|
3414
3439
|
// src/masks/edit.ts
|
|
3415
|
-
import { util as util4 } from "fabric";
|
|
3416
|
-
|
|
3417
|
-
// src/masks/space.ts
|
|
3418
3440
|
import { util as util3 } from "fabric";
|
|
3419
|
-
function matrixOf(object) {
|
|
3420
|
-
return object.calcTransformMatrix();
|
|
3421
|
-
}
|
|
3422
|
-
function applyMatrix(object, matrix) {
|
|
3423
|
-
const decomposed = util3.qrDecompose(matrix);
|
|
3424
|
-
object.set({
|
|
3425
|
-
flipX: false,
|
|
3426
|
-
flipY: false,
|
|
3427
|
-
originX: "center",
|
|
3428
|
-
originY: "center",
|
|
3429
|
-
left: decomposed.translateX,
|
|
3430
|
-
top: decomposed.translateY,
|
|
3431
|
-
scaleX: decomposed.scaleX,
|
|
3432
|
-
scaleY: decomposed.scaleY,
|
|
3433
|
-
angle: decomposed.angle,
|
|
3434
|
-
skewX: decomposed.skewX,
|
|
3435
|
-
skewY: 0
|
|
3436
|
-
});
|
|
3437
|
-
object.setCoords();
|
|
3438
|
-
}
|
|
3439
|
-
function toCanvasSpace(object, host) {
|
|
3440
|
-
applyMatrix(object, util3.multiplyTransformMatrices(matrixOf(host), matrixOf(object)));
|
|
3441
|
-
}
|
|
3442
|
-
function toHostSpace(object, host) {
|
|
3443
|
-
applyMatrix(
|
|
3444
|
-
object,
|
|
3445
|
-
util3.multiplyTransformMatrices(util3.invertTransform(matrixOf(host)), matrixOf(object))
|
|
3446
|
-
);
|
|
3447
|
-
}
|
|
3448
|
-
function relativeMatrix(object, host) {
|
|
3449
|
-
return util3.multiplyTransformMatrices(util3.invertTransform(matrixOf(host)), matrixOf(object));
|
|
3450
|
-
}
|
|
3451
|
-
function applyRelativeMatrix(object, host, rel) {
|
|
3452
|
-
applyMatrix(object, util3.multiplyTransformMatrices(matrixOf(host), rel));
|
|
3453
|
-
}
|
|
3454
|
-
function asObject(clip) {
|
|
3455
|
-
return clip;
|
|
3456
|
-
}
|
|
3457
|
-
function toMatrix(values) {
|
|
3458
|
-
if (!values || values.length !== 6 || values.some((value) => !Number.isFinite(value)))
|
|
3459
|
-
return null;
|
|
3460
|
-
return [values[0], values[1], values[2], values[3], values[4], values[5]];
|
|
3461
|
-
}
|
|
3462
|
-
function fitToBox(object, box, zoom = 1) {
|
|
3463
|
-
const width = Math.max(1, box.width) * zoom;
|
|
3464
|
-
const height = Math.max(1, box.height) * zoom;
|
|
3465
|
-
object.set({
|
|
3466
|
-
originX: "center",
|
|
3467
|
-
originY: "center",
|
|
3468
|
-
angle: 0,
|
|
3469
|
-
skewX: 0,
|
|
3470
|
-
skewY: 0,
|
|
3471
|
-
left: box.left + box.width / 2,
|
|
3472
|
-
top: box.top + box.height / 2,
|
|
3473
|
-
scaleX: width / Math.max(1, object.width ?? 1),
|
|
3474
|
-
scaleY: height / Math.max(1, object.height ?? 1)
|
|
3475
|
-
});
|
|
3476
|
-
object.setCoords();
|
|
3477
|
-
}
|
|
3478
|
-
function unwrapGroup(group) {
|
|
3479
|
-
const children = group.removeAll();
|
|
3480
|
-
for (const child of children) child.setCoords();
|
|
3481
|
-
return children;
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
|
-
// src/masks/edit.ts
|
|
3485
3441
|
var MaskEditController = class {
|
|
3486
3442
|
constructor(canvas, onCommit) {
|
|
3487
3443
|
this.canvas = canvas;
|
|
@@ -3562,8 +3518,8 @@ var MaskEditController = class {
|
|
|
3562
3518
|
if (!this.handle || !this.child || !this.group) return;
|
|
3563
3519
|
applyMatrix(
|
|
3564
3520
|
this.child,
|
|
3565
|
-
|
|
3566
|
-
|
|
3521
|
+
util3.multiplyTransformMatrices(
|
|
3522
|
+
util3.invertTransform(matrixOf(this.group)),
|
|
3567
3523
|
matrixOf(this.handle)
|
|
3568
3524
|
)
|
|
3569
3525
|
);
|
|
@@ -3713,6 +3669,24 @@ var MaskStackStore = class {
|
|
|
3713
3669
|
this.pinning = false;
|
|
3714
3670
|
}
|
|
3715
3671
|
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Is the host's own `clipPath` the wrap manager's box clip rather than a mask?
|
|
3674
|
+
*
|
|
3675
|
+
* Adoption rescues a clip whose provenance is UNKNOWN — a mask preset, or a
|
|
3676
|
+
* hand-set `clipPath` from before stacks existed — so the first add() cannot
|
|
3677
|
+
* silently throw it away. A text layer's box clip is the opposite case: its
|
|
3678
|
+
* provenance is known exactly (the wrap manager derives it from
|
|
3679
|
+
* `meta.overflow` on every pass and re-installs it under whatever owns the
|
|
3680
|
+
* property next), and it is not a mask. Adopted, it becomes a stack row the
|
|
3681
|
+
* user never made whose 'add' union covers the whole box — swallowing the mask
|
|
3682
|
+
* they actually added.
|
|
3683
|
+
*
|
|
3684
|
+
* Only meaningful with an empty stack: once there are entries the top-level
|
|
3685
|
+
* clip is the composed group and the box clip is nested underneath it.
|
|
3686
|
+
*/
|
|
3687
|
+
holdsBoxClip(meta) {
|
|
3688
|
+
return meta?.overflow === "hidden" && !meta.maskPreset;
|
|
3689
|
+
}
|
|
3716
3690
|
/**
|
|
3717
3691
|
* Take the current geometry back out of the composed clip, entry-aligned.
|
|
3718
3692
|
*
|
|
@@ -3725,7 +3699,13 @@ var MaskStackStore = class {
|
|
|
3725
3699
|
const clip = host.clipPath;
|
|
3726
3700
|
if (!clip) return [];
|
|
3727
3701
|
const entries = this.list(target);
|
|
3728
|
-
|
|
3702
|
+
const meta = this.hostLayer(target)?.meta;
|
|
3703
|
+
if (entries.length === 0 && this.holdsBoxClip(meta)) return [];
|
|
3704
|
+
if (entries.length === 0 || !(clip instanceof Group5)) {
|
|
3705
|
+
const source = asObject(clip);
|
|
3706
|
+
if (meta?.overflow === "hidden") source.clipPath = void 0;
|
|
3707
|
+
return [source];
|
|
3708
|
+
}
|
|
3729
3709
|
const children = unwrapGroup(clip);
|
|
3730
3710
|
const extra = children.length - entries.length;
|
|
3731
3711
|
return extra > 0 ? children.slice(extra) : children;
|
|
@@ -4056,6 +4036,7 @@ var LayerMaskManager = class extends MaskStackStore {
|
|
|
4056
4036
|
const clip = layer?.fabricObject.clipPath;
|
|
4057
4037
|
if (!layer || !clip || layer.meta.pattern) return null;
|
|
4058
4038
|
if ((layer.meta.maskStack ?? []).length > 0) return null;
|
|
4039
|
+
if (this.holdsBoxClip(layer.meta)) return null;
|
|
4059
4040
|
const entry = {
|
|
4060
4041
|
...DEFAULT_ENTRY,
|
|
4061
4042
|
id: generateId(),
|
|
@@ -4092,6 +4073,109 @@ var LayerMaskManager = class extends MaskStackStore {
|
|
|
4092
4073
|
}
|
|
4093
4074
|
};
|
|
4094
4075
|
|
|
4076
|
+
// src/gradient.ts
|
|
4077
|
+
import { Color, Gradient } from "fabric";
|
|
4078
|
+
var DEFAULT_GRADIENT_CONFIG = {
|
|
4079
|
+
kind: "linear",
|
|
4080
|
+
angle: 90,
|
|
4081
|
+
center: { x: 0.5, y: 0.5 },
|
|
4082
|
+
radius: 0.5,
|
|
4083
|
+
stops: [
|
|
4084
|
+
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
4085
|
+
{ color: "#000000", opacity: 1, position: 1 }
|
|
4086
|
+
]
|
|
4087
|
+
};
|
|
4088
|
+
var MIN_RADIUS = 1e-3;
|
|
4089
|
+
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4090
|
+
function linearCoords(angle, box) {
|
|
4091
|
+
const radians = angle * Math.PI / 180;
|
|
4092
|
+
const sin = Math.sin(radians);
|
|
4093
|
+
const cos = Math.cos(radians);
|
|
4094
|
+
const vx = box.width * sin;
|
|
4095
|
+
const vy = -box.height * cos;
|
|
4096
|
+
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4097
|
+
const scale = length / (vx * vx + vy * vy || 1);
|
|
4098
|
+
return {
|
|
4099
|
+
x1: 0.5 - scale * vx / 2,
|
|
4100
|
+
y1: 0.5 - scale * vy / 2,
|
|
4101
|
+
x2: 0.5 + scale * vx / 2,
|
|
4102
|
+
y2: 0.5 + scale * vy / 2
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
function angleFromCoords(coords, box) {
|
|
4106
|
+
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4107
|
+
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4108
|
+
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4109
|
+
return (degrees + 360) % 360;
|
|
4110
|
+
}
|
|
4111
|
+
function stopColor(stop) {
|
|
4112
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4113
|
+
const color = new Color(source);
|
|
4114
|
+
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4115
|
+
color.setAlpha(clamp(opacity, 0, 1));
|
|
4116
|
+
return color.toRgba();
|
|
4117
|
+
}
|
|
4118
|
+
function toFabricGradient(config, box) {
|
|
4119
|
+
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).map((stop) => {
|
|
4120
|
+
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4121
|
+
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4122
|
+
});
|
|
4123
|
+
if (config.kind === "radial") {
|
|
4124
|
+
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4125
|
+
return new Gradient({
|
|
4126
|
+
type: "radial",
|
|
4127
|
+
gradientUnits: "percentage",
|
|
4128
|
+
coords: {
|
|
4129
|
+
x1: config.center.x,
|
|
4130
|
+
y1: config.center.y,
|
|
4131
|
+
r1: 0,
|
|
4132
|
+
x2: config.center.x,
|
|
4133
|
+
y2: config.center.y,
|
|
4134
|
+
r2: radius
|
|
4135
|
+
},
|
|
4136
|
+
colorStops
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
return new Gradient({
|
|
4140
|
+
type: "linear",
|
|
4141
|
+
gradientUnits: "percentage",
|
|
4142
|
+
coords: linearCoords(config.angle, box),
|
|
4143
|
+
colorStops
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
function readGradientConfig(object) {
|
|
4147
|
+
const fill = object.fill;
|
|
4148
|
+
if (!fill || typeof fill === "string" || !(fill instanceof Gradient)) return null;
|
|
4149
|
+
const stops = fill.colorStops.map((stop) => {
|
|
4150
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4151
|
+
const color = new Color(source);
|
|
4152
|
+
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4153
|
+
return {
|
|
4154
|
+
color: `#${color.toHex().toLowerCase()}`,
|
|
4155
|
+
opacity: color.getAlpha(),
|
|
4156
|
+
position: clamp(offset, 0, 1)
|
|
4157
|
+
};
|
|
4158
|
+
});
|
|
4159
|
+
if (fill.type === "radial") {
|
|
4160
|
+
const { x1, y1, r2 } = fill.coords;
|
|
4161
|
+
return {
|
|
4162
|
+
kind: "radial",
|
|
4163
|
+
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4164
|
+
center: { x: x1, y: y1 },
|
|
4165
|
+
radius: r2,
|
|
4166
|
+
stops
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4170
|
+
return {
|
|
4171
|
+
kind: "linear",
|
|
4172
|
+
angle: angleFromCoords(fill.coords, box),
|
|
4173
|
+
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4174
|
+
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4175
|
+
stops
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4095
4179
|
// src/editor.ts
|
|
4096
4180
|
var MIN_ZOOM = 0.1;
|
|
4097
4181
|
var MAX_ZOOM = 8;
|
|
@@ -4108,6 +4192,7 @@ var CanvasEditor = class {
|
|
|
4108
4192
|
crop;
|
|
4109
4193
|
patterns;
|
|
4110
4194
|
curves;
|
|
4195
|
+
wrap;
|
|
4111
4196
|
maskPresets;
|
|
4112
4197
|
/** Stacked boolean masks, per layer and for the design as a whole. */
|
|
4113
4198
|
layerMasks;
|
|
@@ -4158,6 +4243,7 @@ var CanvasEditor = class {
|
|
|
4158
4243
|
this.crop = new CropController(this.canvas, this.history, this.events);
|
|
4159
4244
|
this.patterns = new PatternManager(this.canvas, this.layers, this.history, this.events);
|
|
4160
4245
|
this.curves = new TextCurveManager(this.canvas, this.layers, this.history, this.events);
|
|
4246
|
+
this.wrap = new TextWrapManager(this.canvas, this.layers, this.history, this.events);
|
|
4161
4247
|
this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
|
|
4162
4248
|
this.layerMasks = new LayerMaskManager(this.canvas, this.layers, this.history, this.events);
|
|
4163
4249
|
this.setupCanvasEvents();
|
|
@@ -4246,6 +4332,65 @@ var CanvasEditor = class {
|
|
|
4246
4332
|
this.history.save();
|
|
4247
4333
|
return layer;
|
|
4248
4334
|
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Insert a gradient layer. It is an ordinary `Rect` with a gradient fill —
|
|
4337
|
+
* which is exactly why the Node print compositor needs no code for it.
|
|
4338
|
+
*/
|
|
4339
|
+
addGradient(config = DEFAULT_GRADIENT_CONFIG, box) {
|
|
4340
|
+
const width = box?.width ?? this.canvas.getWidth();
|
|
4341
|
+
const height = box?.height ?? this.canvas.getHeight();
|
|
4342
|
+
const object = new Rect4({
|
|
4343
|
+
left: box?.left ?? 0,
|
|
4344
|
+
top: box?.top ?? 0,
|
|
4345
|
+
originX: "left",
|
|
4346
|
+
originY: "top",
|
|
4347
|
+
width,
|
|
4348
|
+
height,
|
|
4349
|
+
// A gradient fill IS the artwork; an outline is not part of this
|
|
4350
|
+
// layer's design, only its own gradient (unlike a plain shape, where a
|
|
4351
|
+
// stroke is a legitimate look).
|
|
4352
|
+
strokeWidth: 0,
|
|
4353
|
+
fill: toFabricGradient(config, { width, height })
|
|
4354
|
+
});
|
|
4355
|
+
const layer = this.layers.add("gradient", object, "Gradient");
|
|
4356
|
+
this.layers.select(layer.id);
|
|
4357
|
+
this.history.save();
|
|
4358
|
+
return layer;
|
|
4359
|
+
}
|
|
4360
|
+
/**
|
|
4361
|
+
* Repaint a layer's gradient. `save: false` while a slider is being dragged —
|
|
4362
|
+
* the settled value is the one worth an undo step.
|
|
4363
|
+
*/
|
|
4364
|
+
setGradient(layerId, config, save = true) {
|
|
4365
|
+
const layer = this.layers.get(layerId);
|
|
4366
|
+
if (!layer) return;
|
|
4367
|
+
const object = layer.fabricObject;
|
|
4368
|
+
object.set({
|
|
4369
|
+
fill: toFabricGradient(config, {
|
|
4370
|
+
width: object.width || 1,
|
|
4371
|
+
height: object.height || 1
|
|
4372
|
+
})
|
|
4373
|
+
});
|
|
4374
|
+
object.dirty = true;
|
|
4375
|
+
this.canvas.requestRenderAll();
|
|
4376
|
+
this.events.emit("layer:modified", { layerId });
|
|
4377
|
+
if (save) this.history.save();
|
|
4378
|
+
}
|
|
4379
|
+
/** Set a layer's blend mode. Applied to the render proxy too, like opacity. */
|
|
4380
|
+
setBlend(layerId, mode) {
|
|
4381
|
+
const layer = this.layers.get(layerId);
|
|
4382
|
+
if (!layer) return;
|
|
4383
|
+
const operation = blendOperation(mode);
|
|
4384
|
+
layer.fabricObject.set({ globalCompositeOperation: operation });
|
|
4385
|
+
layer.fabricObject.dirty = true;
|
|
4386
|
+
if (layer.renderProxy) {
|
|
4387
|
+
layer.renderProxy.set({ globalCompositeOperation: operation });
|
|
4388
|
+
layer.renderProxy.dirty = true;
|
|
4389
|
+
}
|
|
4390
|
+
this.canvas.requestRenderAll();
|
|
4391
|
+
this.events.emit("layer:modified", { layerId });
|
|
4392
|
+
this.history.save();
|
|
4393
|
+
}
|
|
4249
4394
|
async addTemplate(template, params) {
|
|
4250
4395
|
const values = {};
|
|
4251
4396
|
for (const parameter of template.parameters) {
|
|
@@ -4914,15 +5059,34 @@ var CanvasEditor = class {
|
|
|
4914
5059
|
}
|
|
4915
5060
|
// ─── Text curve ─────────────────────────────────────
|
|
4916
5061
|
applyTextCurve(layerId, config) {
|
|
4917
|
-
|
|
5062
|
+
const applied = this.curves.apply(layerId, config);
|
|
5063
|
+
if (applied) this.wrap.refresh(layerId);
|
|
5064
|
+
return applied;
|
|
4918
5065
|
}
|
|
4919
5066
|
clearTextCurve(layerId) {
|
|
4920
|
-
|
|
5067
|
+
const cleared = this.curves.clear(layerId);
|
|
5068
|
+
if (cleared) this.wrap.refresh(layerId);
|
|
5069
|
+
return cleared;
|
|
4921
5070
|
}
|
|
4922
5071
|
getTextCurve(layerId) {
|
|
4923
5072
|
return this.curves.get(layerId);
|
|
4924
5073
|
}
|
|
5074
|
+
// ─── Text wrap ──────────────────────────────────────
|
|
5075
|
+
applyTextWrap(layerId, wrap) {
|
|
5076
|
+
return this.wrap.apply(layerId, wrap);
|
|
5077
|
+
}
|
|
5078
|
+
setTextOverflow(layerId, overflow) {
|
|
5079
|
+
return this.wrap.setOverflow(layerId, overflow);
|
|
5080
|
+
}
|
|
5081
|
+
getTextWrap(layerId) {
|
|
5082
|
+
return this.wrap.get(layerId);
|
|
5083
|
+
}
|
|
4925
5084
|
// ─── Mask presets ───────────────────────────────────
|
|
5085
|
+
// A preset assigns `clipPath` wholesale, so a text layer clipping to its box
|
|
5086
|
+
// loses that clip the moment a mask lands on it. Putting it back is NOT wired
|
|
5087
|
+
// here: the preset manager announces `masks:changed` and the wrap manager
|
|
5088
|
+
// re-derives from that, which covers this facade, the manager called directly,
|
|
5089
|
+
// and `refreshAll()` on a restore — all with one mechanism.
|
|
4926
5090
|
applyMaskPreset(layerId, id) {
|
|
4927
5091
|
return this.maskPresets.apply(layerId, id);
|
|
4928
5092
|
}
|
|
@@ -5011,6 +5175,7 @@ var CanvasEditor = class {
|
|
|
5011
5175
|
this.history.dispose();
|
|
5012
5176
|
this.patterns.dispose();
|
|
5013
5177
|
this.curves.dispose();
|
|
5178
|
+
this.wrap.dispose();
|
|
5014
5179
|
this.events.removeAllListeners();
|
|
5015
5180
|
this.canvas.dispose();
|
|
5016
5181
|
}
|
|
@@ -5133,10 +5298,13 @@ var AnnotationOverlay = class {
|
|
|
5133
5298
|
};
|
|
5134
5299
|
export {
|
|
5135
5300
|
AnnotationOverlay,
|
|
5301
|
+
BLEND_GROUPS,
|
|
5302
|
+
BLEND_MODES,
|
|
5136
5303
|
CANVAS_MASK_TARGET,
|
|
5137
5304
|
CANVAS_SIZE_PRESETS,
|
|
5138
5305
|
CanvasEditor,
|
|
5139
5306
|
CropController,
|
|
5307
|
+
DEFAULT_GRADIENT_CONFIG,
|
|
5140
5308
|
DEFAULT_LAYER_SHADOW,
|
|
5141
5309
|
DEFAULT_PATTERN_CONFIG,
|
|
5142
5310
|
DEFAULT_SELECTION_STYLE,
|
|
@@ -5159,12 +5327,18 @@ export {
|
|
|
5159
5327
|
TEXTURE_MASK_IDS,
|
|
5160
5328
|
TEXTURE_MASK_SIZE,
|
|
5161
5329
|
TextCurveManager,
|
|
5330
|
+
TextWrapManager,
|
|
5162
5331
|
TiledPatternObject,
|
|
5163
5332
|
UnitConverter,
|
|
5333
|
+
angleFromCoords,
|
|
5164
5334
|
applyAspectLock,
|
|
5165
5335
|
applyLayerShadow,
|
|
5166
5336
|
applyObjectSelectionStyle,
|
|
5167
5337
|
applySelectionStyle,
|
|
5338
|
+
applyTextWrapToObject,
|
|
5339
|
+
blendModeLabel,
|
|
5340
|
+
blendModeOf,
|
|
5341
|
+
blendOperation,
|
|
5168
5342
|
buildCurveLinePaths,
|
|
5169
5343
|
buildCurvePathData,
|
|
5170
5344
|
clamp,
|
|
@@ -5190,9 +5364,13 @@ export {
|
|
|
5190
5364
|
isMaskPresetId,
|
|
5191
5365
|
isShapeMaskId,
|
|
5192
5366
|
isTextureMaskId,
|
|
5367
|
+
linearCoords,
|
|
5193
5368
|
needsAbsoluteSpace,
|
|
5194
5369
|
normalizeTextCurve,
|
|
5370
|
+
preWrapWordSplit,
|
|
5371
|
+
readGradientConfig,
|
|
5195
5372
|
readLayerShadow,
|
|
5373
|
+
readTextWrap,
|
|
5196
5374
|
renderTextureMask,
|
|
5197
5375
|
resetTransform,
|
|
5198
5376
|
restoreLocks,
|
|
@@ -5203,6 +5381,7 @@ export {
|
|
|
5203
5381
|
textPathCurve,
|
|
5204
5382
|
textPathSpec,
|
|
5205
5383
|
toCanvasSpace,
|
|
5384
|
+
toFabricGradient,
|
|
5206
5385
|
toHostSpace,
|
|
5207
5386
|
unwrapGroup
|
|
5208
5387
|
};
|