@overtone-art/canvas-editor-core 0.8.6 → 0.9.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-TP527WVQ.mjs → chunk-QZUDGRRS.mjs} +253 -1
- package/dist/chunk-QZUDGRRS.mjs.map +1 -0
- package/dist/index.d.mts +169 -7
- package/dist/index.d.ts +169 -7
- package/dist/index.global.js +81 -57
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1226 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +946 -125
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +2 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.js +260 -1
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +9 -2
- package/dist/node.mjs.map +1 -1
- package/dist/{types-F2GH3dcU.d.mts → specs-B_jOmY6u.d.mts} +111 -13
- package/dist/{types-F2GH3dcU.d.ts → specs-B_jOmY6u.d.ts} +111 -13
- package/package.json +4 -2
- package/dist/chunk-TP527WVQ.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,11 +17,21 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
ADJUST_KEYS: () => ADJUST_KEYS,
|
|
34
|
+
ADJUST_RANGES: () => ADJUST_RANGES,
|
|
23
35
|
AnnotationOverlay: () => AnnotationOverlay,
|
|
24
36
|
BLEND_GROUPS: () => BLEND_GROUPS,
|
|
25
37
|
BLEND_MODES: () => BLEND_MODES,
|
|
@@ -35,7 +47,12 @@ __export(index_exports, {
|
|
|
35
47
|
EventEmitter: () => EventEmitter,
|
|
36
48
|
FontRegistry: () => FontRegistry,
|
|
37
49
|
HistoryManager: () => HistoryManager,
|
|
50
|
+
IMAGE_FILTER_PRESETS: () => IMAGE_FILTER_PRESETS,
|
|
51
|
+
IMAGE_PROXY_MAX_EDGE: () => IMAGE_PROXY_MAX_EDGE,
|
|
52
|
+
ImageEffectsManager: () => ImageEffectsManager,
|
|
53
|
+
LAYER_EFFECT_DEFAULTS: () => LAYER_EFFECT_DEFAULTS,
|
|
38
54
|
Layer: () => Layer,
|
|
55
|
+
LayerEffectsManager: () => LayerEffectsManager,
|
|
39
56
|
LayerManager: () => LayerManager,
|
|
40
57
|
LayerMaskManager: () => LayerMaskManager,
|
|
41
58
|
LicenseManager: () => LicenseManager,
|
|
@@ -43,10 +60,12 @@ __export(index_exports, {
|
|
|
43
60
|
MaskController: () => MaskController,
|
|
44
61
|
MaskPresetManager: () => MaskPresetManager,
|
|
45
62
|
MaskRefinementError: () => MaskRefinementError,
|
|
63
|
+
NEUTRAL_ADJUST: () => NEUTRAL_ADJUST,
|
|
46
64
|
PatternManager: () => PatternManager,
|
|
47
65
|
ProjectManager: () => ProjectManager,
|
|
48
66
|
SHAPE_MASK_BOX: () => SHAPE_MASK_BOX,
|
|
49
67
|
SHAPE_MASK_IDS: () => SHAPE_MASK_IDS,
|
|
68
|
+
ShapeManager: () => ShapeManager,
|
|
50
69
|
SnapManager: () => SnapManager,
|
|
51
70
|
TEXTURE_MASK_IDS: () => TEXTURE_MASK_IDS,
|
|
52
71
|
TEXTURE_MASK_SIZE: () => TEXTURE_MASK_SIZE,
|
|
@@ -54,6 +73,8 @@ __export(index_exports, {
|
|
|
54
73
|
TextWrapManager: () => TextWrapManager,
|
|
55
74
|
TiledPatternObject: () => TiledPatternObject,
|
|
56
75
|
UnitConverter: () => UnitConverter,
|
|
76
|
+
alignObjects: () => alignObjects,
|
|
77
|
+
alphaClipMatrix: () => alphaClipMatrix,
|
|
57
78
|
angleFromCoords: () => angleFromCoords,
|
|
58
79
|
applyAspectLock: () => applyAspectLock,
|
|
59
80
|
applyLayerShadow: () => applyLayerShadow,
|
|
@@ -66,6 +87,8 @@ __export(index_exports, {
|
|
|
66
87
|
buildCurveLinePaths: () => buildCurveLinePaths,
|
|
67
88
|
buildCurvePathData: () => buildCurvePathData,
|
|
68
89
|
clamp: () => clamp,
|
|
90
|
+
clampAdjust: () => clampAdjust,
|
|
91
|
+
clampAlphaClip: () => clampAlphaClip,
|
|
69
92
|
clearTextureMaskCache: () => clearTextureMaskCache,
|
|
70
93
|
composeMaskGroup: () => composeMaskGroup,
|
|
71
94
|
computeCoverPlacement: () => computeCoverPlacement,
|
|
@@ -73,6 +96,7 @@ __export(index_exports, {
|
|
|
73
96
|
computeTilePositions: () => computeTilePositions,
|
|
74
97
|
deserializeEditor: () => deserializeEditor,
|
|
75
98
|
displaceRgba: () => displaceRgba,
|
|
99
|
+
distributeObjects: () => distributeObjects,
|
|
76
100
|
drawTiles: () => drawTiles,
|
|
77
101
|
escapeXml: () => escapeXml,
|
|
78
102
|
explodeTemplateSvg: () => explodeTemplateSvg,
|
|
@@ -84,12 +108,15 @@ __export(index_exports, {
|
|
|
84
108
|
fitTextWidth: () => fitTextWidth,
|
|
85
109
|
fitToBox: () => fitToBox,
|
|
86
110
|
generateId: () => generateId,
|
|
111
|
+
imageEffectsFilterSpecs: () => imageEffectsFilterSpecs,
|
|
112
|
+
imageFilterPreset: () => imageFilterPreset,
|
|
87
113
|
isCssColor: () => isCssColor,
|
|
88
114
|
isMaskPresetId: () => isMaskPresetId,
|
|
89
115
|
isShapeMaskId: () => isShapeMaskId,
|
|
90
116
|
isTextureMaskId: () => isTextureMaskId,
|
|
91
117
|
linearCoords: () => linearCoords,
|
|
92
118
|
needsAbsoluteSpace: () => needsAbsoluteSpace,
|
|
119
|
+
normalizeImageEffects: () => normalizeImageEffects,
|
|
93
120
|
normalizeTextCurve: () => normalizeTextCurve,
|
|
94
121
|
preWrapWordSplit: () => preWrapWordSplit,
|
|
95
122
|
readGradientConfig: () => readGradientConfig,
|
|
@@ -97,6 +124,7 @@ __export(index_exports, {
|
|
|
97
124
|
readTextWrap: () => readTextWrap,
|
|
98
125
|
renderTextureMask: () => renderTextureMask,
|
|
99
126
|
resetTransform: () => resetTransform,
|
|
127
|
+
resolveShapeParams: () => resolveShapeParams,
|
|
100
128
|
restoreLocks: () => restoreLocks,
|
|
101
129
|
round2: () => round2,
|
|
102
130
|
sanitizeSvg: () => sanitizeSvg,
|
|
@@ -107,12 +135,15 @@ __export(index_exports, {
|
|
|
107
135
|
toCanvasSpace: () => toCanvasSpace,
|
|
108
136
|
toFabricGradient: () => toFabricGradient,
|
|
109
137
|
toHostSpace: () => toHostSpace,
|
|
138
|
+
unionBox: () => unionBox,
|
|
110
139
|
unwrapGroup: () => unwrapGroup
|
|
111
140
|
});
|
|
112
141
|
module.exports = __toCommonJS(index_exports);
|
|
113
142
|
|
|
114
143
|
// src/editor.ts
|
|
115
|
-
var
|
|
144
|
+
var import_fabric25 = require("fabric");
|
|
145
|
+
var fabricModule = __toESM(require("fabric"));
|
|
146
|
+
var import_canvas_editor_effects2 = require("@overtone-art/canvas-editor-effects");
|
|
116
147
|
|
|
117
148
|
// src/events.ts
|
|
118
149
|
var EventEmitter = class {
|
|
@@ -264,9 +295,31 @@ var Layer = class {
|
|
|
264
295
|
...this.toData(),
|
|
265
296
|
// A group's own object already carries its children's geometry, so the
|
|
266
297
|
// child records in `toData` stay identity-only — nothing is duplicated.
|
|
267
|
-
fabricObject: this.
|
|
298
|
+
fabricObject: this.serializeObject()
|
|
268
299
|
};
|
|
269
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* A member of an ActiveSelection holds selection-relative coordinates and
|
|
303
|
+
* `toObject()` writes them verbatim. A snapshot taken mid-selection — which
|
|
304
|
+
* `HistoryManager.undo()` takes — then held positions no restore could use,
|
|
305
|
+
* and the redo scattered the layers. Realise the selection's transform for
|
|
306
|
+
* the write and put the object's own transform back.
|
|
307
|
+
*/
|
|
308
|
+
serializeObject() {
|
|
309
|
+
const object = this.fabricObject;
|
|
310
|
+
const selection = object.group;
|
|
311
|
+
if (!(selection instanceof import_fabric.ActiveSelection)) {
|
|
312
|
+
return object.toObject();
|
|
313
|
+
}
|
|
314
|
+
const saved = import_fabric.util.saveObjectTransform(object);
|
|
315
|
+
import_fabric.util.addTransformToObject(object, selection.calcOwnMatrix());
|
|
316
|
+
try {
|
|
317
|
+
return object.toObject();
|
|
318
|
+
} finally {
|
|
319
|
+
object.set(saved);
|
|
320
|
+
object.setCoords();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
270
323
|
};
|
|
271
324
|
var LayerManager = class {
|
|
272
325
|
constructor(canvas, events) {
|
|
@@ -1198,7 +1251,7 @@ var CropController = class {
|
|
|
1198
1251
|
activeLayerId() {
|
|
1199
1252
|
return this.session?.layerId ?? null;
|
|
1200
1253
|
}
|
|
1201
|
-
start(layer) {
|
|
1254
|
+
start(layer, options = {}) {
|
|
1202
1255
|
if (layer.type !== "image") return;
|
|
1203
1256
|
if (this.session) this.cancel();
|
|
1204
1257
|
const image = layer.fabricObject;
|
|
@@ -1206,6 +1259,12 @@ var CropController = class {
|
|
|
1206
1259
|
if (prevAngle) image.rotate(0);
|
|
1207
1260
|
image.setCoords();
|
|
1208
1261
|
const b = image.getBoundingRect();
|
|
1262
|
+
const prevTransform = {
|
|
1263
|
+
scaleX: image.scaleX,
|
|
1264
|
+
scaleY: image.scaleY,
|
|
1265
|
+
left: image.left,
|
|
1266
|
+
top: image.top
|
|
1267
|
+
};
|
|
1209
1268
|
const rect = new import_fabric3.Rect({
|
|
1210
1269
|
left: b.left,
|
|
1211
1270
|
top: b.top,
|
|
@@ -1230,8 +1289,95 @@ var CropController = class {
|
|
|
1230
1289
|
this.canvas.add(rect);
|
|
1231
1290
|
this.canvas.setActiveObject(rect);
|
|
1232
1291
|
this.canvas.requestRenderAll();
|
|
1233
|
-
this.session = {
|
|
1292
|
+
this.session = {
|
|
1293
|
+
layerId: layer.id,
|
|
1294
|
+
image,
|
|
1295
|
+
rect,
|
|
1296
|
+
prevSelectable,
|
|
1297
|
+
prevEvented,
|
|
1298
|
+
prevAngle,
|
|
1299
|
+
prevTransform,
|
|
1300
|
+
startRect: { left: b.left, top: b.top, width: b.width, height: b.height },
|
|
1301
|
+
aspect: null
|
|
1302
|
+
};
|
|
1234
1303
|
this.events.emit("crop:changed", { active: true, layerId: layer.id });
|
|
1304
|
+
if (options.aspect) this.setAspect(options.aspect);
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* The frame's own geometry, without its stroke.
|
|
1308
|
+
*
|
|
1309
|
+
* The rect draws a 1px dashed outline, and `getBoundingRect` /
|
|
1310
|
+
* `getScaledWidth` both count it — so reading the frame through them made the
|
|
1311
|
+
* aspect box, the zoom centre and the applied crop each about a pixel larger
|
|
1312
|
+
* than the box the user drew. The rect is axis-aligned (rotation is locked),
|
|
1313
|
+
* so its own left/top and scaled dimensions are exact.
|
|
1314
|
+
*/
|
|
1315
|
+
frameBox(rect) {
|
|
1316
|
+
return {
|
|
1317
|
+
left: rect.left,
|
|
1318
|
+
top: rect.top,
|
|
1319
|
+
width: rect.width * rect.scaleX,
|
|
1320
|
+
height: rect.height * rect.scaleY
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Lock the frame to a ratio: it becomes the largest box of that ratio inside
|
|
1325
|
+
* the current frame, centred, and only the corner handles stay — fabric's
|
|
1326
|
+
* default `uniformScaling` then keeps the ratio through a drag. `null` frees it.
|
|
1327
|
+
*/
|
|
1328
|
+
setAspect(ratio) {
|
|
1329
|
+
const s = this.session;
|
|
1330
|
+
if (!s) return;
|
|
1331
|
+
s.aspect = ratio && ratio > 0 ? ratio : null;
|
|
1332
|
+
const { rect } = s;
|
|
1333
|
+
const sides = s.aspect === null;
|
|
1334
|
+
rect.setControlsVisibility({ ml: sides, mr: sides, mt: sides, mb: sides, mtr: false });
|
|
1335
|
+
if (s.aspect !== null) {
|
|
1336
|
+
rect.setCoords();
|
|
1337
|
+
const current = this.frameBox(rect);
|
|
1338
|
+
const width = Math.min(current.width, current.height * s.aspect);
|
|
1339
|
+
const height = width / s.aspect;
|
|
1340
|
+
rect.set({
|
|
1341
|
+
left: current.left + (current.width - width) / 2,
|
|
1342
|
+
top: current.top + (current.height - height) / 2,
|
|
1343
|
+
width,
|
|
1344
|
+
height,
|
|
1345
|
+
scaleX: 1,
|
|
1346
|
+
scaleY: 1
|
|
1347
|
+
});
|
|
1348
|
+
rect.setCoords();
|
|
1349
|
+
}
|
|
1350
|
+
this.canvas.requestRenderAll();
|
|
1351
|
+
}
|
|
1352
|
+
/** Zoom the image under a fixed frame, about the frame's centre. */
|
|
1353
|
+
setScale(factor) {
|
|
1354
|
+
const s = this.session;
|
|
1355
|
+
if (!s || !Number.isFinite(factor) || factor <= 0) return;
|
|
1356
|
+
const { image, rect } = s;
|
|
1357
|
+
rect.setCoords();
|
|
1358
|
+
const frame = this.frameBox(rect);
|
|
1359
|
+
const centre = { x: frame.left + frame.width / 2, y: frame.top + frame.height / 2 };
|
|
1360
|
+
const scaleX = Math.max(0.01, image.scaleX * factor);
|
|
1361
|
+
const scaleY = Math.max(0.01, image.scaleY * factor);
|
|
1362
|
+
const applied = scaleX / image.scaleX;
|
|
1363
|
+
image.set({
|
|
1364
|
+
scaleX,
|
|
1365
|
+
scaleY,
|
|
1366
|
+
left: centre.x - (centre.x - image.left) * applied,
|
|
1367
|
+
top: centre.y - (centre.y - image.top) * applied
|
|
1368
|
+
});
|
|
1369
|
+
image.setCoords();
|
|
1370
|
+
this.canvas.requestRenderAll();
|
|
1371
|
+
}
|
|
1372
|
+
/** Back to the frame and image transform the session started with; aspect freed. */
|
|
1373
|
+
reset() {
|
|
1374
|
+
const s = this.session;
|
|
1375
|
+
if (!s) return;
|
|
1376
|
+
s.image.set(s.prevTransform);
|
|
1377
|
+
s.image.setCoords();
|
|
1378
|
+
s.rect.set({ ...s.startRect, scaleX: 1, scaleY: 1 });
|
|
1379
|
+
s.rect.setCoords();
|
|
1380
|
+
this.setAspect(null);
|
|
1235
1381
|
}
|
|
1236
1382
|
apply() {
|
|
1237
1383
|
const s = this.session;
|
|
@@ -1246,10 +1392,11 @@ var CropController = class {
|
|
|
1246
1392
|
const el = image.getElement();
|
|
1247
1393
|
const naturalW = el.naturalWidth || image.width || 0;
|
|
1248
1394
|
const naturalH = el.naturalHeight || image.height || 0;
|
|
1249
|
-
|
|
1250
|
-
let
|
|
1251
|
-
let
|
|
1252
|
-
let
|
|
1395
|
+
const frame = this.frameBox(rect);
|
|
1396
|
+
let cropX = (image.cropX ?? 0) + (frame.left - imgLeft) / scaleX;
|
|
1397
|
+
let cropY = (image.cropY ?? 0) + (frame.top - imgTop) / scaleY;
|
|
1398
|
+
let cropW = frame.width / scaleX;
|
|
1399
|
+
let cropH = frame.height / scaleY;
|
|
1253
1400
|
cropX = clamp(cropX, 0, Math.max(0, naturalW - 1));
|
|
1254
1401
|
cropY = clamp(cropY, 0, Math.max(0, naturalH - 1));
|
|
1255
1402
|
cropW = clamp(cropW, 1, naturalW - cropX);
|
|
@@ -1274,6 +1421,8 @@ var CropController = class {
|
|
|
1274
1421
|
cancel() {
|
|
1275
1422
|
const s = this.session;
|
|
1276
1423
|
if (!s) return;
|
|
1424
|
+
s.image.set(s.prevTransform);
|
|
1425
|
+
s.image.setCoords();
|
|
1277
1426
|
if (s.prevAngle) {
|
|
1278
1427
|
s.image.rotate(s.prevAngle);
|
|
1279
1428
|
s.image.setCoords();
|
|
@@ -1298,14 +1447,70 @@ var CropController = class {
|
|
|
1298
1447
|
};
|
|
1299
1448
|
var STROKE2 = "#22c55e";
|
|
1300
1449
|
|
|
1450
|
+
// src/align.ts
|
|
1451
|
+
var import_fabric4 = require("fabric");
|
|
1452
|
+
function unionBox(boxes) {
|
|
1453
|
+
const left = Math.min(...boxes.map((b) => b.left));
|
|
1454
|
+
const top = Math.min(...boxes.map((b) => b.top));
|
|
1455
|
+
const right = Math.max(...boxes.map((b) => b.left + b.width));
|
|
1456
|
+
const bottom = Math.max(...boxes.map((b) => b.top + b.height));
|
|
1457
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
1458
|
+
}
|
|
1459
|
+
function boxOf(object) {
|
|
1460
|
+
object.setCoords();
|
|
1461
|
+
return object.getBoundingRect();
|
|
1462
|
+
}
|
|
1463
|
+
function moveBy(object, dx, dy) {
|
|
1464
|
+
const parent = object.group;
|
|
1465
|
+
const vector = parent ? import_fabric4.util.sendVectorToPlane(new import_fabric4.Point(dx, dy), void 0, parent.calcTransformMatrix()) : new import_fabric4.Point(dx, dy);
|
|
1466
|
+
object.set({ left: (object.left ?? 0) + vector.x, top: (object.top ?? 0) + vector.y });
|
|
1467
|
+
object.setCoords();
|
|
1468
|
+
}
|
|
1469
|
+
function alignObjects(objects, target, frame) {
|
|
1470
|
+
for (const object of objects) {
|
|
1471
|
+
const b = boxOf(object);
|
|
1472
|
+
if (target === "left") moveBy(object, frame.left - b.left, 0);
|
|
1473
|
+
if (target === "center-h") moveBy(object, frame.left + (frame.width - b.width) / 2 - b.left, 0);
|
|
1474
|
+
if (target === "right") moveBy(object, frame.left + frame.width - b.width - b.left, 0);
|
|
1475
|
+
if (target === "top") moveBy(object, 0, frame.top - b.top);
|
|
1476
|
+
if (target === "middle") moveBy(object, 0, frame.top + (frame.height - b.height) / 2 - b.top);
|
|
1477
|
+
if (target === "bottom") moveBy(object, 0, frame.top + frame.height - b.height - b.top);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
function distributeObjects(objects, axis) {
|
|
1481
|
+
if (objects.length < 3) return false;
|
|
1482
|
+
const measured = objects.map((object) => ({ object, box: boxOf(object) }));
|
|
1483
|
+
const pos = (b) => axis === "h" ? b.left : b.top;
|
|
1484
|
+
const size = (b) => axis === "h" ? b.width : b.height;
|
|
1485
|
+
const ordered = [...measured].sort((a, b) => pos(a.box) - pos(b.box));
|
|
1486
|
+
const span = unionBox(measured.map((m) => m.box));
|
|
1487
|
+
const total = ordered.reduce((sum, m) => sum + size(m.box), 0);
|
|
1488
|
+
const gap = (size(span) - total) / (ordered.length - 1);
|
|
1489
|
+
let cursor = pos(span);
|
|
1490
|
+
for (const { object, box } of ordered) {
|
|
1491
|
+
const delta = cursor - pos(box);
|
|
1492
|
+
moveBy(object, axis === "h" ? delta : 0, axis === "h" ? 0 : delta);
|
|
1493
|
+
cursor += size(box) + gap;
|
|
1494
|
+
}
|
|
1495
|
+
return true;
|
|
1496
|
+
}
|
|
1497
|
+
function relayoutSelection(active) {
|
|
1498
|
+
if (!(active instanceof import_fabric4.ActiveSelection)) return;
|
|
1499
|
+
active.triggerLayout();
|
|
1500
|
+
active.setCoords();
|
|
1501
|
+
}
|
|
1502
|
+
function canvasBox(width, height) {
|
|
1503
|
+
return { left: 0, top: 0, width, height };
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1301
1506
|
// src/pattern/pattern-manager.ts
|
|
1302
|
-
var
|
|
1507
|
+
var import_fabric7 = require("fabric");
|
|
1303
1508
|
|
|
1304
1509
|
// src/pattern/tiled-pattern-object.ts
|
|
1305
|
-
var
|
|
1510
|
+
var import_fabric6 = require("fabric");
|
|
1306
1511
|
|
|
1307
1512
|
// src/text-fit.ts
|
|
1308
|
-
var
|
|
1513
|
+
var import_fabric5 = require("fabric");
|
|
1309
1514
|
var TEXT_FIT_SLACK = 0.5;
|
|
1310
1515
|
var MEASURE_WIDTH = 1e5;
|
|
1311
1516
|
var GROWTH_FACTOR = 1.1;
|
|
@@ -1394,7 +1599,7 @@ function fitTextWidth(object) {
|
|
|
1394
1599
|
const after = textInk(text);
|
|
1395
1600
|
const shift = (before.dx - after.dx) * (text.scaleX ?? 1);
|
|
1396
1601
|
const radians = (text.angle ?? 0) * Math.PI / 180;
|
|
1397
|
-
const moved = new
|
|
1602
|
+
const moved = new import_fabric5.Point(
|
|
1398
1603
|
centre.x + shift * Math.cos(radians),
|
|
1399
1604
|
centre.y + shift * Math.sin(radians)
|
|
1400
1605
|
);
|
|
@@ -1418,9 +1623,9 @@ function computeTilePositions(config, targetW, targetH, baseW, baseH, origin) {
|
|
|
1418
1623
|
const tileW = Math.max(minTile, baseW * (1 + config.horizontalSpacing / 100));
|
|
1419
1624
|
const tileH = Math.max(minTile, baseH * (1 + config.verticalSpacing / 100));
|
|
1420
1625
|
const cols = Math.ceil(span / tileW) + 2;
|
|
1421
|
-
const
|
|
1626
|
+
const rows2 = Math.ceil(span / tileH) + 2;
|
|
1422
1627
|
const [colFrom, colTo] = axisRange(config.horizontalLimit, Math.ceil(cols / 2));
|
|
1423
|
-
const [rowFrom, rowTo] = axisRange(config.verticalLimit, Math.ceil(
|
|
1628
|
+
const [rowFrom, rowTo] = axisRange(config.verticalLimit, Math.ceil(rows2 / 2));
|
|
1424
1629
|
const shiftX = tileW * (clampOffset(config.offsetX) / 100);
|
|
1425
1630
|
const shiftY = tileH * (clampOffset(config.offsetY) / 100);
|
|
1426
1631
|
const placements = [];
|
|
@@ -1479,7 +1684,7 @@ var MAX_SNAPSHOT_PIXELS = 16e6;
|
|
|
1479
1684
|
var MAX_SNAPSHOT_SCALE = 8;
|
|
1480
1685
|
var SNAPSHOT_SHRINK_FACTOR = 2;
|
|
1481
1686
|
var TILE_BLEED_DEVICE_PX = 2;
|
|
1482
|
-
var TiledPatternObject = class _TiledPatternObject extends
|
|
1687
|
+
var TiledPatternObject = class _TiledPatternObject extends import_fabric6.FabricObject {
|
|
1483
1688
|
static type = "TiledPattern";
|
|
1484
1689
|
/** The layer's real object. Never rendered directly (it is kept at opacity 0). */
|
|
1485
1690
|
source;
|
|
@@ -1563,7 +1768,7 @@ var TiledPatternObject = class _TiledPatternObject extends import_fabric5.Fabric
|
|
|
1563
1768
|
const centre = this.getCenterPoint();
|
|
1564
1769
|
const shift = this.shiftVector(tileW, tileH, this.liveAngle());
|
|
1565
1770
|
const ink = this.inkOffset();
|
|
1566
|
-
return new
|
|
1771
|
+
return new import_fabric6.Point(centre.x - shift.x - ink.x, centre.y - shift.y - ink.y);
|
|
1567
1772
|
}
|
|
1568
1773
|
_render(ctx) {
|
|
1569
1774
|
const { width, height } = this.area;
|
|
@@ -1650,6 +1855,10 @@ var TiledPatternObject = class _TiledPatternObject extends import_fabric5.Fabric
|
|
|
1650
1855
|
angle: this.angle,
|
|
1651
1856
|
scaleX: this.scaleX,
|
|
1652
1857
|
scaleY: this.scaleY,
|
|
1858
|
+
skewX: this.skewX,
|
|
1859
|
+
skewY: this.skewY,
|
|
1860
|
+
flipX: this.flipX,
|
|
1861
|
+
flipY: this.flipY,
|
|
1653
1862
|
width: this.width,
|
|
1654
1863
|
height: this.height,
|
|
1655
1864
|
visible: this.visible,
|
|
@@ -1695,12 +1904,12 @@ var TiledPatternObject = class _TiledPatternObject extends import_fabric5.Fabric
|
|
|
1695
1904
|
*/
|
|
1696
1905
|
inkOffset() {
|
|
1697
1906
|
const text = asText(this.source);
|
|
1698
|
-
if (!text) return new
|
|
1907
|
+
if (!text) return new import_fabric6.Point(0, 0);
|
|
1699
1908
|
const { dx } = textInk(text);
|
|
1700
|
-
if (!dx) return new
|
|
1909
|
+
if (!dx) return new import_fabric6.Point(0, 0);
|
|
1701
1910
|
const scaled = dx * (this.source.scaleX ?? 1);
|
|
1702
1911
|
const radians = (this.source.angle ?? 0) * Math.PI / 180;
|
|
1703
|
-
return new
|
|
1912
|
+
return new import_fabric6.Point(scaled * Math.cos(radians), scaled * Math.sin(radians));
|
|
1704
1913
|
}
|
|
1705
1914
|
/** The source's on-canvas width, before the tile scale. */
|
|
1706
1915
|
baseW() {
|
|
@@ -1761,11 +1970,11 @@ var TiledPatternObject = class _TiledPatternObject extends import_fabric5.Fabric
|
|
|
1761
1970
|
const radians = angle * Math.PI / 180;
|
|
1762
1971
|
const cos = Math.cos(radians);
|
|
1763
1972
|
const sin = Math.sin(radians);
|
|
1764
|
-
return new
|
|
1973
|
+
return new import_fabric6.Point(dx * cos - dy * sin, dx * sin + dy * cos);
|
|
1765
1974
|
}
|
|
1766
1975
|
anchorFromOrigin(origin, tileW, tileH, angle) {
|
|
1767
1976
|
const shift = this.shiftVector(tileW, tileH, angle);
|
|
1768
|
-
return new
|
|
1977
|
+
return new import_fabric6.Point(origin.x + shift.x, origin.y + shift.y);
|
|
1769
1978
|
}
|
|
1770
1979
|
/**
|
|
1771
1980
|
* Snapshot the source at (at least) `scale`, reusing the cached one while it
|
|
@@ -1970,6 +2179,11 @@ var PatternManager = class {
|
|
|
1970
2179
|
if (wasActive) this.canvas.setActiveObject(proxy);
|
|
1971
2180
|
this.canvas.requestRenderAll();
|
|
1972
2181
|
}
|
|
2182
|
+
/** Fold a programmatic move of the proxy into the config and the source, as a drop does. */
|
|
2183
|
+
commitProxy(layerId) {
|
|
2184
|
+
const proxy = this.proxies.get(layerId);
|
|
2185
|
+
if (proxy) this.commitGesture(proxy);
|
|
2186
|
+
}
|
|
1973
2187
|
/**
|
|
1974
2188
|
* Fold a finished drag / scale / rotate on the proxy back into the pattern:
|
|
1975
2189
|
* position becomes the grid origin (carried by the source), scale becomes the
|
|
@@ -2037,7 +2251,7 @@ async function unbakeLegacyLayer(layer, state) {
|
|
|
2037
2251
|
cropY: state.original.cropY,
|
|
2038
2252
|
angle: state.original.angle
|
|
2039
2253
|
});
|
|
2040
|
-
image.clipPath = state.originalClip ? (await
|
|
2254
|
+
image.clipPath = state.originalClip ? (await import_fabric7.util.enlivenObjects([state.originalClip]))[0] : void 0;
|
|
2041
2255
|
restoreLocks(image, state.originalLocks);
|
|
2042
2256
|
image.setCoords();
|
|
2043
2257
|
}
|
|
@@ -2054,8 +2268,763 @@ function restoreLocks(obj, locks) {
|
|
|
2054
2268
|
);
|
|
2055
2269
|
}
|
|
2056
2270
|
|
|
2271
|
+
// src/shapes.ts
|
|
2272
|
+
function resolveShapeParams(plugin, overrides = {}) {
|
|
2273
|
+
const params = {};
|
|
2274
|
+
for (const def of plugin.params ?? []) {
|
|
2275
|
+
const value = overrides[def.key];
|
|
2276
|
+
const wanted = typeof value === "number" && Number.isFinite(value) ? value : def.default;
|
|
2277
|
+
params[def.key] = clamp(wanted, def.min, def.max);
|
|
2278
|
+
}
|
|
2279
|
+
return params;
|
|
2280
|
+
}
|
|
2281
|
+
var ShapeManager = class {
|
|
2282
|
+
constructor(canvas, layers, history, events) {
|
|
2283
|
+
this.canvas = canvas;
|
|
2284
|
+
this.layers = layers;
|
|
2285
|
+
this.history = history;
|
|
2286
|
+
this.events = events;
|
|
2287
|
+
this.canvas.on("object:modified", this.onObjectModified);
|
|
2288
|
+
}
|
|
2289
|
+
canvas;
|
|
2290
|
+
layers;
|
|
2291
|
+
history;
|
|
2292
|
+
events;
|
|
2293
|
+
plugins = /* @__PURE__ */ new Map();
|
|
2294
|
+
/**
|
|
2295
|
+
* An arrow scaled 2×1 must not stretch its head: the new box is baked into
|
|
2296
|
+
* the points and the scale returns to 1. Runs before the editor's own
|
|
2297
|
+
* `object:modified` handler (constructed earlier), so history records the
|
|
2298
|
+
* baked state.
|
|
2299
|
+
*/
|
|
2300
|
+
onObjectModified = (event) => {
|
|
2301
|
+
const target = event.target;
|
|
2302
|
+
if (!target) return;
|
|
2303
|
+
const layer = this.layers.findByObject(target);
|
|
2304
|
+
const state = layer?.meta.shape;
|
|
2305
|
+
if (!layer || !state || target !== layer.fabricObject) return;
|
|
2306
|
+
const plugin = this.plugins.get(state.id);
|
|
2307
|
+
if (!plugin?.bakesScale || !plugin.regenerate) return;
|
|
2308
|
+
const { scaleX = 1, scaleY = 1 } = target;
|
|
2309
|
+
if (Math.abs(scaleX - scaleY) < 1e-6) return;
|
|
2310
|
+
target.set({
|
|
2311
|
+
width: target.width * scaleX,
|
|
2312
|
+
height: target.height * scaleY,
|
|
2313
|
+
scaleX: 1,
|
|
2314
|
+
scaleY: 1
|
|
2315
|
+
});
|
|
2316
|
+
plugin.regenerate(target, state.params);
|
|
2317
|
+
target.setCoords();
|
|
2318
|
+
target.dirty = true;
|
|
2319
|
+
this.canvas.requestRenderAll();
|
|
2320
|
+
};
|
|
2321
|
+
dispose() {
|
|
2322
|
+
this.canvas.off("object:modified", this.onObjectModified);
|
|
2323
|
+
}
|
|
2324
|
+
/** A restored layer names its plugin by id; only a registered one can regenerate. */
|
|
2325
|
+
register(plugin) {
|
|
2326
|
+
this.plugins.set(plugin.name, plugin);
|
|
2327
|
+
}
|
|
2328
|
+
read(layerId) {
|
|
2329
|
+
return this.layers.get(layerId)?.meta.shape ?? null;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* `commit: false` is a slider drag: geometry and `meta.shape` are written and
|
|
2333
|
+
* `layer:modified` is emitted so the Shape panel re-reads, but nothing reaches
|
|
2334
|
+
* history. The default commits one entry.
|
|
2335
|
+
*/
|
|
2336
|
+
setParams(layerId, patch, commit = true) {
|
|
2337
|
+
const layer = this.layers.get(layerId);
|
|
2338
|
+
const state = layer?.meta.shape;
|
|
2339
|
+
if (!layer || !state) return;
|
|
2340
|
+
const plugin = this.plugins.get(state.id);
|
|
2341
|
+
if (!plugin?.regenerate) return;
|
|
2342
|
+
const params = resolveShapeParams(plugin, { ...state.params, ...patch });
|
|
2343
|
+
plugin.regenerate(layer.fabricObject, params);
|
|
2344
|
+
layer.fabricObject.setCoords();
|
|
2345
|
+
layer.fabricObject.dirty = true;
|
|
2346
|
+
const next = { ...state, params };
|
|
2347
|
+
if (commit) this.layers.setMeta(layerId, { shape: next });
|
|
2348
|
+
else layer.meta.shape = next;
|
|
2349
|
+
this.canvas.requestRenderAll();
|
|
2350
|
+
this.events.emit("layer:modified", { layerId });
|
|
2351
|
+
if (commit) this.history.save();
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
// src/image-effects/manager.ts
|
|
2356
|
+
var import_fabric9 = require("fabric");
|
|
2357
|
+
|
|
2358
|
+
// src/image-effects/presets.ts
|
|
2359
|
+
var LUMA = [0.299, 0.587, 0.114];
|
|
2360
|
+
var rows = (r, g, b) => [...r, ...g, ...b, 0, 0, 0, 1, 0];
|
|
2361
|
+
var IMAGE_FILTER_PRESETS = [
|
|
2362
|
+
{ id: "original", name: "Original", version: 1, adjust: {} },
|
|
2363
|
+
{
|
|
2364
|
+
id: "black-white",
|
|
2365
|
+
name: "Black & White",
|
|
2366
|
+
version: 1,
|
|
2367
|
+
adjust: {},
|
|
2368
|
+
matrix: rows([...LUMA, 0, 0], [...LUMA, 0, 0], [...LUMA, 0, 0])
|
|
2369
|
+
},
|
|
2370
|
+
{
|
|
2371
|
+
id: "sepia",
|
|
2372
|
+
name: "Sepia",
|
|
2373
|
+
version: 1,
|
|
2374
|
+
adjust: {},
|
|
2375
|
+
matrix: rows(
|
|
2376
|
+
[0.393, 0.769, 0.189, 0, 0],
|
|
2377
|
+
[0.349, 0.686, 0.168, 0, 0],
|
|
2378
|
+
[0.272, 0.534, 0.131, 0, 0]
|
|
2379
|
+
)
|
|
2380
|
+
},
|
|
2381
|
+
{
|
|
2382
|
+
id: "fade",
|
|
2383
|
+
name: "Fade",
|
|
2384
|
+
version: 1,
|
|
2385
|
+
adjust: { contrast: -0.2, saturation: -0.25, brightness: 0.08 }
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
id: "vivid",
|
|
2389
|
+
name: "Vivid",
|
|
2390
|
+
version: 1,
|
|
2391
|
+
adjust: { vibrance: 0.5, contrast: 0.15, saturation: 0.15 }
|
|
2392
|
+
},
|
|
2393
|
+
{ id: "fuchsia", name: "Fuchsia", version: 1, adjust: { tint: 0.5, saturation: 0.2 } },
|
|
2394
|
+
{ id: "cool", name: "Cool", version: 1, adjust: { temperature: -0.45 } },
|
|
2395
|
+
{ id: "warm", name: "Warm", version: 1, adjust: { temperature: 0.45 } },
|
|
2396
|
+
{
|
|
2397
|
+
id: "duotone",
|
|
2398
|
+
name: "Duotone",
|
|
2399
|
+
version: 1,
|
|
2400
|
+
adjust: {},
|
|
2401
|
+
matrix: rows(
|
|
2402
|
+
[0.2544, 0.4995, 0.097, 0, 0.1059],
|
|
2403
|
+
[0.2216, 0.435, 0.0845, 0, 0.1647],
|
|
2404
|
+
[0.1453, 0.2853, 0.0554, 0, 0.2863]
|
|
2405
|
+
)
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
id: "nashville",
|
|
2409
|
+
name: "Nashville",
|
|
2410
|
+
version: 1,
|
|
2411
|
+
adjust: { temperature: 0.25, tint: -0.1, contrast: 0.2, saturation: 0.15, brightness: 0.05 }
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
id: "valencia",
|
|
2415
|
+
name: "Valencia",
|
|
2416
|
+
version: 1,
|
|
2417
|
+
adjust: { temperature: 0.2, contrast: 0.08, brightness: 0.08, saturation: -0.05 }
|
|
2418
|
+
},
|
|
2419
|
+
{
|
|
2420
|
+
id: "clarendon",
|
|
2421
|
+
name: "Clarendon",
|
|
2422
|
+
version: 1,
|
|
2423
|
+
adjust: { contrast: 0.2, saturation: 0.35, temperature: -0.1 }
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
id: "reyes",
|
|
2427
|
+
name: "Reyes",
|
|
2428
|
+
version: 1,
|
|
2429
|
+
adjust: { brightness: 0.1, contrast: -0.15, saturation: -0.35, temperature: 0.15 }
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
id: "lark",
|
|
2433
|
+
name: "Lark",
|
|
2434
|
+
version: 1,
|
|
2435
|
+
adjust: { brightness: 0.08, contrast: -0.05, saturation: 0.1, temperature: -0.15 }
|
|
2436
|
+
},
|
|
2437
|
+
{
|
|
2438
|
+
id: "juno",
|
|
2439
|
+
name: "Juno",
|
|
2440
|
+
version: 1,
|
|
2441
|
+
adjust: { saturation: 0.3, contrast: 0.1, temperature: 0.15, tint: 0.1 }
|
|
2442
|
+
}
|
|
2443
|
+
];
|
|
2444
|
+
function imageFilterPreset(id) {
|
|
2445
|
+
return IMAGE_FILTER_PRESETS.find((preset) => preset.id === id);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// src/image-effects/proxy.ts
|
|
2449
|
+
var import_fabric8 = require("fabric");
|
|
2450
|
+
function sourceSize(source) {
|
|
2451
|
+
const el = source;
|
|
2452
|
+
return { width: el.naturalWidth || el.width, height: el.naturalHeight || el.height };
|
|
2453
|
+
}
|
|
2454
|
+
function proxyScale(width, height, maxEdge) {
|
|
2455
|
+
const edge = Math.max(width, height);
|
|
2456
|
+
return edge > maxEdge ? maxEdge / edge : 1;
|
|
2457
|
+
}
|
|
2458
|
+
function buildProxySource(source, maxEdge) {
|
|
2459
|
+
const { width, height } = sourceSize(source);
|
|
2460
|
+
const scale = proxyScale(width, height, maxEdge);
|
|
2461
|
+
if (scale === 1) return null;
|
|
2462
|
+
const element = import_fabric8.util.createCanvasElement();
|
|
2463
|
+
element.width = Math.max(1, Math.round(width * scale));
|
|
2464
|
+
element.height = Math.max(1, Math.round(height * scale));
|
|
2465
|
+
const context = element.getContext("2d");
|
|
2466
|
+
if (!context) throw new Error("2D canvas context is unavailable");
|
|
2467
|
+
context.drawImage(source, 0, 0, element.width, element.height);
|
|
2468
|
+
return { element, width: element.width, height: element.height };
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// src/image-effects/types.ts
|
|
2472
|
+
var ADJUST_KEYS = [
|
|
2473
|
+
"temperature",
|
|
2474
|
+
"tint",
|
|
2475
|
+
"brightness",
|
|
2476
|
+
"contrast",
|
|
2477
|
+
"highlights",
|
|
2478
|
+
"shadows",
|
|
2479
|
+
"whites",
|
|
2480
|
+
"blacks",
|
|
2481
|
+
"invert",
|
|
2482
|
+
"vibrance",
|
|
2483
|
+
"saturation",
|
|
2484
|
+
"hue",
|
|
2485
|
+
"sharpness",
|
|
2486
|
+
"clarity",
|
|
2487
|
+
"vignette",
|
|
2488
|
+
"blur",
|
|
2489
|
+
"noise",
|
|
2490
|
+
"pixelate"
|
|
2491
|
+
];
|
|
2492
|
+
var NEUTRAL_ADJUST = Object.fromEntries(
|
|
2493
|
+
ADJUST_KEYS.map((k) => [k, 0])
|
|
2494
|
+
);
|
|
2495
|
+
var ADJUST_RANGES = {
|
|
2496
|
+
invert: [0, 1],
|
|
2497
|
+
vignette: [0, 1],
|
|
2498
|
+
blur: [0, 1],
|
|
2499
|
+
noise: [0, 1],
|
|
2500
|
+
pixelate: [0, 1]
|
|
2501
|
+
};
|
|
2502
|
+
var IMAGE_PROXY_MAX_EDGE = 2048;
|
|
2503
|
+
|
|
2504
|
+
// src/image-effects/specs.ts
|
|
2505
|
+
var ORDER = [
|
|
2506
|
+
"highlights",
|
|
2507
|
+
"shadows",
|
|
2508
|
+
"whites",
|
|
2509
|
+
"blacks",
|
|
2510
|
+
"brightness",
|
|
2511
|
+
"contrast",
|
|
2512
|
+
"temperature",
|
|
2513
|
+
"tint",
|
|
2514
|
+
"vibrance",
|
|
2515
|
+
"saturation",
|
|
2516
|
+
"hue",
|
|
2517
|
+
"invert",
|
|
2518
|
+
"clarity",
|
|
2519
|
+
"sharpness",
|
|
2520
|
+
"noise",
|
|
2521
|
+
"pixelate",
|
|
2522
|
+
"blur",
|
|
2523
|
+
"vignette"
|
|
2524
|
+
];
|
|
2525
|
+
function clampAdjust(adjust) {
|
|
2526
|
+
const out = {};
|
|
2527
|
+
for (const key of ADJUST_KEYS) {
|
|
2528
|
+
const value = adjust[key];
|
|
2529
|
+
if (typeof value !== "number" || !Number.isFinite(value)) continue;
|
|
2530
|
+
const [min, max] = ADJUST_RANGES[key] ?? [-1, 1];
|
|
2531
|
+
out[key] = key === "invert" ? value >= 0.5 ? 1 : 0 : clamp(value, min, max);
|
|
2532
|
+
}
|
|
2533
|
+
return out;
|
|
2534
|
+
}
|
|
2535
|
+
function clampAlphaClip(clip) {
|
|
2536
|
+
if (!clip || typeof clip !== "object") return void 0;
|
|
2537
|
+
const high = clamp(Number(clip.high) || 0, 1, 255);
|
|
2538
|
+
const low = clamp(Math.min(Number(clip.low) || 0, high - 1), 0, 254);
|
|
2539
|
+
return low === 0 && high === 255 ? void 0 : { low, high };
|
|
2540
|
+
}
|
|
2541
|
+
function alphaClipMatrix({ low, high }) {
|
|
2542
|
+
const span = Math.max(1, high - low);
|
|
2543
|
+
return [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 255 / span, -low / span];
|
|
2544
|
+
}
|
|
2545
|
+
function specFor(key, v) {
|
|
2546
|
+
if (v === 0) return null;
|
|
2547
|
+
switch (key) {
|
|
2548
|
+
case "highlights":
|
|
2549
|
+
return { type: "Highlights", amount: v };
|
|
2550
|
+
case "shadows":
|
|
2551
|
+
return { type: "Shadows", amount: v };
|
|
2552
|
+
case "whites":
|
|
2553
|
+
return { type: "Whites", amount: v };
|
|
2554
|
+
case "blacks":
|
|
2555
|
+
return { type: "Blacks", amount: v };
|
|
2556
|
+
case "brightness":
|
|
2557
|
+
return { type: "Brightness", brightness: v };
|
|
2558
|
+
case "contrast":
|
|
2559
|
+
return { type: "Contrast", contrast: v };
|
|
2560
|
+
case "temperature":
|
|
2561
|
+
return { type: "Temperature", amount: v };
|
|
2562
|
+
case "tint":
|
|
2563
|
+
return { type: "Tint", amount: v };
|
|
2564
|
+
case "vibrance":
|
|
2565
|
+
return { type: "Vibrance", vibrance: v };
|
|
2566
|
+
case "saturation":
|
|
2567
|
+
return { type: "Saturation", saturation: v };
|
|
2568
|
+
case "hue":
|
|
2569
|
+
return { type: "HueRotation", rotation: v };
|
|
2570
|
+
case "invert":
|
|
2571
|
+
return { type: "Invert", alpha: false, invert: true };
|
|
2572
|
+
case "clarity":
|
|
2573
|
+
return { type: "Clarity", amount: v };
|
|
2574
|
+
case "sharpness":
|
|
2575
|
+
return { type: "Convolute", opaque: false, matrix: [0, -v, 0, -v, 1 + 4 * v, -v, 0, -v, 0] };
|
|
2576
|
+
case "noise":
|
|
2577
|
+
return { type: "Noise", noise: Math.round(v * 300) };
|
|
2578
|
+
case "pixelate":
|
|
2579
|
+
return { type: "Pixelate", blocksize: Math.max(1, Math.round(1 + v * 40)) };
|
|
2580
|
+
case "blur":
|
|
2581
|
+
return { type: "Blur", blur: v };
|
|
2582
|
+
case "vignette":
|
|
2583
|
+
return { type: "Vignette", amount: v };
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
function imageEffectsFilterSpecs(state) {
|
|
2587
|
+
const specs = [];
|
|
2588
|
+
const preset = state.preset ? imageFilterPreset(state.preset.id) : void 0;
|
|
2589
|
+
if (preset?.matrix)
|
|
2590
|
+
specs.push({ type: "ColorMatrix", matrix: [...preset.matrix], colorsOnly: true });
|
|
2591
|
+
const adjust = clampAdjust(state.adjust);
|
|
2592
|
+
for (const key of ORDER) {
|
|
2593
|
+
const spec = specFor(key, adjust[key] ?? 0);
|
|
2594
|
+
if (spec) specs.push(spec);
|
|
2595
|
+
}
|
|
2596
|
+
const clip = clampAlphaClip(state.alphaClip);
|
|
2597
|
+
if (clip) specs.push({ type: "ColorMatrix", matrix: alphaClipMatrix(clip), colorsOnly: false });
|
|
2598
|
+
return specs;
|
|
2599
|
+
}
|
|
2600
|
+
function normalizeImageEffects(meta) {
|
|
2601
|
+
const alphaClip = clampAlphaClip(meta.imageAlphaClip);
|
|
2602
|
+
const withClip = (state) => alphaClip ? { ...state, alphaClip } : state;
|
|
2603
|
+
const current = meta.imageEffects;
|
|
2604
|
+
if (current && typeof current === "object") {
|
|
2605
|
+
const preset = current.preset && imageFilterPreset(current.preset.id) ? { id: current.preset.id, version: current.preset.version } : void 0;
|
|
2606
|
+
return withClip({ ...preset ? { preset } : {}, adjust: clampAdjust(current.adjust ?? {}) });
|
|
2607
|
+
}
|
|
2608
|
+
const legacy = meta.imageAdjustments;
|
|
2609
|
+
return withClip({ adjust: clampAdjust(legacy ?? {}) });
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// src/image-effects/manager.ts
|
|
2613
|
+
var stable = (value) => JSON.stringify(
|
|
2614
|
+
value,
|
|
2615
|
+
(_k, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort()) : v
|
|
2616
|
+
);
|
|
2617
|
+
var ImageEffectsManager = class {
|
|
2618
|
+
constructor(canvas, layers, history, events) {
|
|
2619
|
+
this.canvas = canvas;
|
|
2620
|
+
this.layers = layers;
|
|
2621
|
+
this.history = history;
|
|
2622
|
+
this.events = events;
|
|
2623
|
+
this.events.on("layer:removed", this.onRemoved);
|
|
2624
|
+
}
|
|
2625
|
+
canvas;
|
|
2626
|
+
layers;
|
|
2627
|
+
history;
|
|
2628
|
+
events;
|
|
2629
|
+
proxies = /* @__PURE__ */ new Map();
|
|
2630
|
+
lastSpecs = /* @__PURE__ */ new Map();
|
|
2631
|
+
onRemoved = ({ layerId }) => this.drop(layerId);
|
|
2632
|
+
read(layerId) {
|
|
2633
|
+
const layer = this.imageLayer(layerId);
|
|
2634
|
+
return layer ? normalizeImageEffects(layer.meta) : { adjust: {} };
|
|
2635
|
+
}
|
|
2636
|
+
/** The filter instances a state derives to, in the D5 order, with no layer involved. */
|
|
2637
|
+
filtersFor(state) {
|
|
2638
|
+
return imageEffectsFilterSpecs(state).map(
|
|
2639
|
+
(spec) => new (import_fabric9.classRegistry.getClass(spec.type))(spec)
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
preview(layerId, patch) {
|
|
2643
|
+
this.write(layerId, patch);
|
|
2644
|
+
}
|
|
2645
|
+
commit(layerId, patch) {
|
|
2646
|
+
if (this.write(layerId, patch)) this.history.save();
|
|
2647
|
+
}
|
|
2648
|
+
reset(layerId) {
|
|
2649
|
+
if (this.write(layerId, { preset: void 0, adjust: {} }, true)) this.history.save();
|
|
2650
|
+
}
|
|
2651
|
+
/** Re-derive after a source swap or a load. */
|
|
2652
|
+
refresh(layerId) {
|
|
2653
|
+
const layer = this.imageLayer(layerId);
|
|
2654
|
+
if (layer) this.apply(layer, this.read(layerId));
|
|
2655
|
+
}
|
|
2656
|
+
refreshAll() {
|
|
2657
|
+
for (const id of [...this.proxies.keys()]) if (!this.layers.get(id)) this.drop(id);
|
|
2658
|
+
for (const layer of this.layers.getAll()) {
|
|
2659
|
+
if (layer.type !== "image") continue;
|
|
2660
|
+
if ("imageAdjustments" in layer.meta) {
|
|
2661
|
+
const migrated = normalizeImageEffects(layer.meta);
|
|
2662
|
+
layer.meta.imageEffects = {
|
|
2663
|
+
...migrated.preset ? { preset: migrated.preset } : {},
|
|
2664
|
+
adjust: migrated.adjust
|
|
2665
|
+
};
|
|
2666
|
+
delete layer.meta.imageAdjustments;
|
|
2667
|
+
}
|
|
2668
|
+
this.apply(layer, this.read(layer.id));
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
hasProxy(layerId) {
|
|
2672
|
+
return this.proxies.has(layerId);
|
|
2673
|
+
}
|
|
2674
|
+
/** Run `fn` with every proxied image filtered at source resolution (export), then restore the proxies. */
|
|
2675
|
+
withSourceResolution(fn) {
|
|
2676
|
+
const swapped = [];
|
|
2677
|
+
for (const id of this.proxies.keys()) {
|
|
2678
|
+
const layer = this.imageLayer(id);
|
|
2679
|
+
if (!layer) continue;
|
|
2680
|
+
const image = layer.fabricObject;
|
|
2681
|
+
image._element = image._originalElement;
|
|
2682
|
+
image._filteredEl = void 0;
|
|
2683
|
+
image._filterScalingX = 1;
|
|
2684
|
+
image._filterScalingY = 1;
|
|
2685
|
+
image.applyFilters();
|
|
2686
|
+
swapped.push(layer);
|
|
2687
|
+
}
|
|
2688
|
+
try {
|
|
2689
|
+
return fn();
|
|
2690
|
+
} finally {
|
|
2691
|
+
for (const layer of swapped) {
|
|
2692
|
+
const image = layer.fabricObject;
|
|
2693
|
+
if (image._filteredEl) image._filteredEl.width = 0;
|
|
2694
|
+
image._filteredEl = void 0;
|
|
2695
|
+
image._element = image._originalElement;
|
|
2696
|
+
this.render(layer);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
dispose() {
|
|
2701
|
+
this.events.off("layer:removed", this.onRemoved);
|
|
2702
|
+
for (const id of [...this.proxies.keys()]) this.drop(id);
|
|
2703
|
+
this.lastSpecs.clear();
|
|
2704
|
+
}
|
|
2705
|
+
imageLayer(layerId) {
|
|
2706
|
+
const layer = this.layers.get(layerId);
|
|
2707
|
+
return layer && layer.type === "image" ? layer : void 0;
|
|
2708
|
+
}
|
|
2709
|
+
write(layerId, patch, replace = false) {
|
|
2710
|
+
const layer = this.imageLayer(layerId);
|
|
2711
|
+
if (!layer) return false;
|
|
2712
|
+
const current = replace ? { adjust: {} } : this.read(layerId);
|
|
2713
|
+
const presetChanged = "preset" in patch && stable(patch.preset) !== stable(current.preset);
|
|
2714
|
+
const seed = presetChanged && patch.preset ? imageFilterPreset(patch.preset.id)?.adjust ?? {} : current.adjust;
|
|
2715
|
+
const preset = "preset" in patch ? patch.preset : current.preset;
|
|
2716
|
+
const alphaClip = clampAlphaClip("alphaClip" in patch ? patch.alphaClip : current.alphaClip);
|
|
2717
|
+
const next = {
|
|
2718
|
+
...preset ? { preset } : {},
|
|
2719
|
+
adjust: { ...seed, ...patch.adjust ?? {} },
|
|
2720
|
+
...alphaClip ? { alphaClip } : {}
|
|
2721
|
+
};
|
|
2722
|
+
layer.meta.imageEffects = { ...preset ? { preset } : {}, adjust: next.adjust };
|
|
2723
|
+
if (alphaClip) layer.meta.imageAlphaClip = alphaClip;
|
|
2724
|
+
else delete layer.meta.imageAlphaClip;
|
|
2725
|
+
delete layer.meta.imageAdjustments;
|
|
2726
|
+
this.apply(layer, next);
|
|
2727
|
+
this.events.emit("layer:modified", { layerId });
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
apply(layer, state) {
|
|
2731
|
+
const image = layer.fabricObject;
|
|
2732
|
+
const specs = imageEffectsFilterSpecs(state);
|
|
2733
|
+
const previous = this.lastSpecs.get(layer.id) ?? specs;
|
|
2734
|
+
const clip = clampAlphaClip(state.alphaClip);
|
|
2735
|
+
const legacyClip = clip ? stable(alphaClipMatrix(clip)) : null;
|
|
2736
|
+
const foreign = image.filters.filter((f) => {
|
|
2737
|
+
if (f.isNeutralState()) return false;
|
|
2738
|
+
const plain = f.toObject();
|
|
2739
|
+
if (legacyClip && plain.type === "ColorMatrix" && stable(plain.matrix) === legacyClip)
|
|
2740
|
+
return false;
|
|
2741
|
+
return !previous.some((s) => stable(s) === stable(plain));
|
|
2742
|
+
});
|
|
2743
|
+
const managed = specs.map((spec) => new (import_fabric9.classRegistry.getClass(spec.type))(spec));
|
|
2744
|
+
image.filters = [...managed, ...foreign];
|
|
2745
|
+
this.lastSpecs.set(layer.id, specs);
|
|
2746
|
+
this.render(layer);
|
|
2747
|
+
}
|
|
2748
|
+
render(layer) {
|
|
2749
|
+
const image = layer.fabricObject;
|
|
2750
|
+
const record = this.proxyFor(layer.id, image);
|
|
2751
|
+
if (!record) {
|
|
2752
|
+
image.applyFilters();
|
|
2753
|
+
} else {
|
|
2754
|
+
const active = image.filters.filter((f) => !f.isNeutralState());
|
|
2755
|
+
if (active.length === 0) {
|
|
2756
|
+
image._element = image._originalElement;
|
|
2757
|
+
image._filterScalingX = 1;
|
|
2758
|
+
image._filterScalingY = 1;
|
|
2759
|
+
} else {
|
|
2760
|
+
(0, import_fabric9.getFilterBackend)().applyFilters(
|
|
2761
|
+
active,
|
|
2762
|
+
record.source.element,
|
|
2763
|
+
record.source.width,
|
|
2764
|
+
record.source.height,
|
|
2765
|
+
record.target,
|
|
2766
|
+
`${image.cacheKey}_proxy`
|
|
2767
|
+
);
|
|
2768
|
+
const { width, height } = sourceSize(image._originalElement);
|
|
2769
|
+
image._element = record.target;
|
|
2770
|
+
image._filterScalingX = record.source.width / width;
|
|
2771
|
+
image._filterScalingY = record.source.height / height;
|
|
2772
|
+
}
|
|
2773
|
+
image.set("dirty", true);
|
|
2774
|
+
}
|
|
2775
|
+
this.canvas.requestRenderAll();
|
|
2776
|
+
}
|
|
2777
|
+
proxyFor(layerId, image) {
|
|
2778
|
+
const existing = this.proxies.get(layerId);
|
|
2779
|
+
if (existing && existing.original === image._originalElement) return existing;
|
|
2780
|
+
if (existing) this.drop(layerId);
|
|
2781
|
+
const source = buildProxySource(image._originalElement, IMAGE_PROXY_MAX_EDGE);
|
|
2782
|
+
if (!source) return null;
|
|
2783
|
+
const target = source.element.ownerDocument.createElement("canvas");
|
|
2784
|
+
target.width = source.width;
|
|
2785
|
+
target.height = source.height;
|
|
2786
|
+
const record = { original: image._originalElement, source, target };
|
|
2787
|
+
this.proxies.set(layerId, record);
|
|
2788
|
+
return record;
|
|
2789
|
+
}
|
|
2790
|
+
drop(layerId) {
|
|
2791
|
+
const record = this.proxies.get(layerId);
|
|
2792
|
+
if (!record) return;
|
|
2793
|
+
const layer = this.layers.get(layerId);
|
|
2794
|
+
if (layer)
|
|
2795
|
+
layer.fabricObject.removeTexture(
|
|
2796
|
+
`${layer.fabricObject.cacheKey}_proxy`
|
|
2797
|
+
);
|
|
2798
|
+
record.source.element.width = 0;
|
|
2799
|
+
record.target.width = 0;
|
|
2800
|
+
this.proxies.delete(layerId);
|
|
2801
|
+
this.lastSpecs.delete(layerId);
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
|
|
2805
|
+
// src/layer-effects.ts
|
|
2806
|
+
var import_canvas_editor_effects = require("@overtone-art/canvas-editor-effects");
|
|
2807
|
+
|
|
2808
|
+
// src/shadow.ts
|
|
2809
|
+
var import_fabric10 = require("fabric");
|
|
2810
|
+
|
|
2811
|
+
// src/utils/color.ts
|
|
2812
|
+
var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
|
|
2813
|
+
function isCssColor(value, allowEmpty = false) {
|
|
2814
|
+
if (typeof value !== "string") return false;
|
|
2815
|
+
const trimmed = value.trim();
|
|
2816
|
+
if (!trimmed) return allowEmpty;
|
|
2817
|
+
return CSS_COLOR.test(trimmed);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
// src/shadow.ts
|
|
2821
|
+
var DEFAULT_LAYER_SHADOW = {
|
|
2822
|
+
enabled: false,
|
|
2823
|
+
color: "#000000",
|
|
2824
|
+
blur: 12,
|
|
2825
|
+
offsetX: 6,
|
|
2826
|
+
offsetY: 6
|
|
2827
|
+
};
|
|
2828
|
+
function readLayerShadow(object) {
|
|
2829
|
+
const shadow = object.shadow;
|
|
2830
|
+
if (!shadow || typeof shadow === "string") return { ...DEFAULT_LAYER_SHADOW };
|
|
2831
|
+
return {
|
|
2832
|
+
enabled: true,
|
|
2833
|
+
color: typeof shadow.color === "string" ? shadow.color : DEFAULT_LAYER_SHADOW.color,
|
|
2834
|
+
blur: shadow.blur ?? DEFAULT_LAYER_SHADOW.blur,
|
|
2835
|
+
offsetX: shadow.offsetX ?? DEFAULT_LAYER_SHADOW.offsetX,
|
|
2836
|
+
offsetY: shadow.offsetY ?? DEFAULT_LAYER_SHADOW.offsetY
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
function applyLayerShadow(object, config) {
|
|
2840
|
+
const next = { ...readLayerShadow(object), ...config };
|
|
2841
|
+
if (!next.enabled) {
|
|
2842
|
+
object.set({ shadow: null });
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
const color2 = isCssColor(next.color) ? next.color : DEFAULT_LAYER_SHADOW.color;
|
|
2846
|
+
object.set({
|
|
2847
|
+
shadow: new import_fabric10.Shadow({
|
|
2848
|
+
color: color2,
|
|
2849
|
+
blur: Math.max(0, next.blur),
|
|
2850
|
+
offsetX: next.offsetX,
|
|
2851
|
+
offsetY: next.offsetY
|
|
2852
|
+
})
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/layer-effects.ts
|
|
2857
|
+
var LAYER_EFFECT_DEFAULTS = {
|
|
2858
|
+
outline: { color: "#000000", width: 4 },
|
|
2859
|
+
hollow: { color: "#000000", width: 4 },
|
|
2860
|
+
drop: { color: "#000000", blur: 0, offsetX: 6, offsetY: 6 },
|
|
2861
|
+
splice: { color: "#ff4d6d", offsetX: 8, offsetY: 8 },
|
|
2862
|
+
echo: { color: "#000000", count: 3, stepX: 6, stepY: 6, fade: 0.5 },
|
|
2863
|
+
background: { color: "#ffe600", padding: 12, radius: 8 }
|
|
2864
|
+
};
|
|
2865
|
+
var num = (p, key, fallback) => {
|
|
2866
|
+
const value = p[key];
|
|
2867
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2868
|
+
};
|
|
2869
|
+
var color = (p, key, fallback) => {
|
|
2870
|
+
const value = p[key];
|
|
2871
|
+
return typeof value === "string" && isCssColor(value) ? value : fallback;
|
|
2872
|
+
};
|
|
2873
|
+
var outlineProps = (p) => ({
|
|
2874
|
+
stroke: color(p, "color", "#000000"),
|
|
2875
|
+
strokeWidth: clamp(num(p, "width", 4), 0, 200),
|
|
2876
|
+
paintFirst: "stroke",
|
|
2877
|
+
strokeUniform: true
|
|
2878
|
+
});
|
|
2879
|
+
var LayerEffectsManager = class {
|
|
2880
|
+
constructor(canvas, layers, history, events) {
|
|
2881
|
+
this.canvas = canvas;
|
|
2882
|
+
this.layers = layers;
|
|
2883
|
+
this.history = history;
|
|
2884
|
+
this.events = events;
|
|
2885
|
+
}
|
|
2886
|
+
canvas;
|
|
2887
|
+
layers;
|
|
2888
|
+
history;
|
|
2889
|
+
events;
|
|
2890
|
+
read(layerId) {
|
|
2891
|
+
return this.layers.get(layerId)?.meta.effect ?? null;
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* `commit: false` is a slider drag: props are written and `layer:modified`
|
|
2895
|
+
* is emitted so the panels re-read, but nothing reaches history. The default
|
|
2896
|
+
* commits ONE entry, setMeta's own save folded into the same transaction.
|
|
2897
|
+
*/
|
|
2898
|
+
apply(layerId, kind, params = {}, commit = true) {
|
|
2899
|
+
const layer = this.layers.get(layerId);
|
|
2900
|
+
if (!layer || layer.type === "group") return;
|
|
2901
|
+
const object = layer.fabricObject;
|
|
2902
|
+
const write = () => {
|
|
2903
|
+
this.clear(object, layer.meta.effect);
|
|
2904
|
+
const next = kind === "none" ? void 0 : {
|
|
2905
|
+
kind,
|
|
2906
|
+
params: this.install(object, kind, { ...LAYER_EFFECT_DEFAULTS[kind], ...params }),
|
|
2907
|
+
version: 1
|
|
2908
|
+
};
|
|
2909
|
+
if (commit) this.layers.setMeta(layerId, { effect: next });
|
|
2910
|
+
else if (next) layer.meta.effect = next;
|
|
2911
|
+
else delete layer.meta.effect;
|
|
2912
|
+
object.dirty = true;
|
|
2913
|
+
object.setCoords();
|
|
2914
|
+
this.canvas.requestRenderAll();
|
|
2915
|
+
this.events.emit("layer:modified", { layerId });
|
|
2916
|
+
};
|
|
2917
|
+
if (!commit) {
|
|
2918
|
+
write();
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
this.history.beginTransaction();
|
|
2922
|
+
try {
|
|
2923
|
+
write();
|
|
2924
|
+
this.history.save();
|
|
2925
|
+
} finally {
|
|
2926
|
+
this.history.endTransaction();
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
/** Undo what the previous kind wrote, so kinds never stack. */
|
|
2930
|
+
clear(object, previous) {
|
|
2931
|
+
if (!previous) return;
|
|
2932
|
+
switch (previous.kind) {
|
|
2933
|
+
case "outline":
|
|
2934
|
+
object.set({ strokeWidth: 0, paintFirst: "fill", strokeUniform: false });
|
|
2935
|
+
return;
|
|
2936
|
+
case "hollow": {
|
|
2937
|
+
const fill = previous.params.previousFill;
|
|
2938
|
+
object.set({
|
|
2939
|
+
strokeWidth: 0,
|
|
2940
|
+
paintFirst: "fill",
|
|
2941
|
+
strokeUniform: false,
|
|
2942
|
+
fill: typeof fill === "string" ? fill : "#000000"
|
|
2943
|
+
});
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
case "drop":
|
|
2947
|
+
applyLayerShadow(object, { enabled: false });
|
|
2948
|
+
return;
|
|
2949
|
+
case "splice":
|
|
2950
|
+
case "echo":
|
|
2951
|
+
case "background":
|
|
2952
|
+
object.set({ effect: void 0 });
|
|
2953
|
+
return;
|
|
2954
|
+
case "none":
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
install(object, kind, p) {
|
|
2959
|
+
switch (kind) {
|
|
2960
|
+
case "outline": {
|
|
2961
|
+
const props = outlineProps(p);
|
|
2962
|
+
object.set(props);
|
|
2963
|
+
return { color: props.stroke, width: props.strokeWidth };
|
|
2964
|
+
}
|
|
2965
|
+
case "hollow": {
|
|
2966
|
+
const previousFill = typeof object.fill === "string" ? object.fill : "#000000";
|
|
2967
|
+
const props = outlineProps(p);
|
|
2968
|
+
object.set({ ...props, fill: "transparent" });
|
|
2969
|
+
return { color: props.stroke, width: props.strokeWidth, previousFill };
|
|
2970
|
+
}
|
|
2971
|
+
case "drop": {
|
|
2972
|
+
const shadow = {
|
|
2973
|
+
color: color(p, "color", "#000000"),
|
|
2974
|
+
blur: clamp(num(p, "blur", 0), 0, 200),
|
|
2975
|
+
offsetX: num(p, "offsetX", 6),
|
|
2976
|
+
offsetY: num(p, "offsetY", 6)
|
|
2977
|
+
};
|
|
2978
|
+
applyLayerShadow(object, { enabled: true, ...shadow });
|
|
2979
|
+
return shadow;
|
|
2980
|
+
}
|
|
2981
|
+
case "splice": {
|
|
2982
|
+
const effect = {
|
|
2983
|
+
kind: "splice",
|
|
2984
|
+
offsetX: num(p, "offsetX", 8),
|
|
2985
|
+
offsetY: num(p, "offsetY", 8),
|
|
2986
|
+
color: color(p, "color", "#ff4d6d"),
|
|
2987
|
+
version: 1
|
|
2988
|
+
};
|
|
2989
|
+
object.set({ effect });
|
|
2990
|
+
return { offsetX: effect.offsetX, offsetY: effect.offsetY, color: effect.color };
|
|
2991
|
+
}
|
|
2992
|
+
case "echo": {
|
|
2993
|
+
const effect = {
|
|
2994
|
+
kind: "echo",
|
|
2995
|
+
count: clamp(Math.round(num(p, "count", 3)), 1, import_canvas_editor_effects.ECHO_MAX_COUNT),
|
|
2996
|
+
stepX: num(p, "stepX", 6),
|
|
2997
|
+
stepY: num(p, "stepY", 6),
|
|
2998
|
+
color: color(p, "color", "#000000"),
|
|
2999
|
+
fade: clamp(num(p, "fade", 0.5), 0, 1),
|
|
3000
|
+
version: 1
|
|
3001
|
+
};
|
|
3002
|
+
object.set({ effect });
|
|
3003
|
+
return {
|
|
3004
|
+
count: effect.count,
|
|
3005
|
+
stepX: effect.stepX,
|
|
3006
|
+
stepY: effect.stepY,
|
|
3007
|
+
color: effect.color,
|
|
3008
|
+
fade: effect.fade
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
case "background": {
|
|
3012
|
+
const effect = {
|
|
3013
|
+
kind: "background",
|
|
3014
|
+
padding: clamp(num(p, "padding", 12), 0, 400),
|
|
3015
|
+
radius: clamp(num(p, "radius", 8), 0, 400),
|
|
3016
|
+
color: color(p, "color", "#ffe600"),
|
|
3017
|
+
version: 1
|
|
3018
|
+
};
|
|
3019
|
+
object.set({ effect });
|
|
3020
|
+
return { padding: effect.padding, radius: effect.radius, color: effect.color };
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
|
|
2057
3026
|
// src/text-curve.ts
|
|
2058
|
-
var
|
|
3027
|
+
var import_fabric11 = require("fabric");
|
|
2059
3028
|
|
|
2060
3029
|
// src/text-curve-geometry.ts
|
|
2061
3030
|
var DEFAULT_TEXT_CURVE = {
|
|
@@ -2064,9 +3033,12 @@ var DEFAULT_TEXT_CURVE = {
|
|
|
2064
3033
|
wave: 0,
|
|
2065
3034
|
waveLength: 4.1,
|
|
2066
3035
|
offset: 0,
|
|
2067
|
-
centerOffset: 0
|
|
3036
|
+
centerOffset: 0,
|
|
3037
|
+
angle: 0
|
|
2068
3038
|
};
|
|
2069
3039
|
var MIN_ARC = 0.5;
|
|
3040
|
+
var MIN_ANGLE = 0.5;
|
|
3041
|
+
var MAX_ANGLE = 80;
|
|
2070
3042
|
var FULL_CIRCLE_ARC = 99.5;
|
|
2071
3043
|
var MIN_SWEEP = 0.12;
|
|
2072
3044
|
var WAVE_AMPLITUDE_EM = 0.9;
|
|
@@ -2091,10 +3063,12 @@ function normalizeTextCurve(config) {
|
|
|
2091
3063
|
MAX_WAVE_LENGTH
|
|
2092
3064
|
),
|
|
2093
3065
|
offset: clamp(config.offset ?? 0, -100, 100),
|
|
2094
|
-
centerOffset: clamp(config.centerOffset ?? 0, -100, 100)
|
|
3066
|
+
centerOffset: clamp(config.centerOffset ?? 0, -100, 100),
|
|
3067
|
+
angle: clamp(config.angle ?? 0, -MAX_ANGLE, MAX_ANGLE)
|
|
2095
3068
|
};
|
|
2096
3069
|
}
|
|
2097
3070
|
function bendsBaseline(config) {
|
|
3071
|
+
if (config.shape === "angle") return Math.abs(config.angle) >= MIN_ANGLE;
|
|
2098
3072
|
return config.shape === "arc" ? Math.abs(config.arc) >= MIN_ARC : config.wave > 0;
|
|
2099
3073
|
}
|
|
2100
3074
|
function arcGeometry(width, config) {
|
|
@@ -2155,6 +3129,17 @@ function waveLinePath(width, fontSize, config, dy) {
|
|
|
2155
3129
|
}
|
|
2156
3130
|
return { data: commands.join(" "), length };
|
|
2157
3131
|
}
|
|
3132
|
+
function angleLinePath(width, config, dy) {
|
|
3133
|
+
const radians = config.angle * Math.PI / 180;
|
|
3134
|
+
const half = width / 2;
|
|
3135
|
+
const dx = half * Math.cos(radians);
|
|
3136
|
+
const rise = half * Math.sin(radians);
|
|
3137
|
+
const format = ([x, y]) => `${round(x)} ${round(y)}`;
|
|
3138
|
+
return {
|
|
3139
|
+
data: `M ${format([half - dx, dy + rise])} L ${format([half + dx, dy - rise])}`,
|
|
3140
|
+
length: width
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
2158
3143
|
function buildCurveLinePaths(config, width, fontSize, lineCount = 1, lineHeight = 0) {
|
|
2159
3144
|
const curve = normalizeTextCurve(config);
|
|
2160
3145
|
if (!bendsBaseline(curve)) return null;
|
|
@@ -2166,6 +3151,12 @@ function buildCurveLinePaths(config, width, fontSize, lineCount = 1, lineHeight
|
|
|
2166
3151
|
}
|
|
2167
3152
|
return paths;
|
|
2168
3153
|
}
|
|
3154
|
+
if (curve.shape === "angle") {
|
|
3155
|
+
for (let index = 0; index < lines; index++) {
|
|
3156
|
+
paths.push(angleLinePath(width, curve, (index - (lines - 1) / 2) * lineHeight));
|
|
3157
|
+
}
|
|
3158
|
+
return paths;
|
|
3159
|
+
}
|
|
2169
3160
|
const geometry = arcGeometry(width, curve);
|
|
2170
3161
|
const rotation = curve.offset / 100 * geometry.sweep;
|
|
2171
3162
|
for (let index = 0; index < lines; index++) {
|
|
@@ -2244,7 +3235,7 @@ function lineHeightOf(text) {
|
|
|
2244
3235
|
return text.fontSize * (text.lineHeight ?? 1) * FALLBACK_LINE_HEIGHT;
|
|
2245
3236
|
}
|
|
2246
3237
|
function toFabricPath(curve) {
|
|
2247
|
-
return new
|
|
3238
|
+
return new import_fabric11.Path(curve.data, { visible: false, objectCaching: false });
|
|
2248
3239
|
}
|
|
2249
3240
|
var TextCurveManager = class {
|
|
2250
3241
|
constructor(canvas, layers, history, events) {
|
|
@@ -2464,7 +3455,7 @@ var TextCurveManager = class {
|
|
|
2464
3455
|
};
|
|
2465
3456
|
|
|
2466
3457
|
// src/text-wrap.ts
|
|
2467
|
-
var
|
|
3458
|
+
var import_fabric13 = require("fabric");
|
|
2468
3459
|
|
|
2469
3460
|
// src/text-wrap-split.ts
|
|
2470
3461
|
var WHITESPACE = /[ \t\r]/;
|
|
@@ -2490,12 +3481,12 @@ function preWrapWordSplit(value) {
|
|
|
2490
3481
|
}
|
|
2491
3482
|
|
|
2492
3483
|
// src/masks/space.ts
|
|
2493
|
-
var
|
|
3484
|
+
var import_fabric12 = require("fabric");
|
|
2494
3485
|
function matrixOf(object) {
|
|
2495
3486
|
return object.calcTransformMatrix();
|
|
2496
3487
|
}
|
|
2497
3488
|
function applyMatrix(object, matrix) {
|
|
2498
|
-
const decomposed =
|
|
3489
|
+
const decomposed = import_fabric12.util.qrDecompose(matrix);
|
|
2499
3490
|
object.set({
|
|
2500
3491
|
flipX: false,
|
|
2501
3492
|
flipY: false,
|
|
@@ -2512,19 +3503,19 @@ function applyMatrix(object, matrix) {
|
|
|
2512
3503
|
object.setCoords();
|
|
2513
3504
|
}
|
|
2514
3505
|
function toCanvasSpace(object, host) {
|
|
2515
|
-
applyMatrix(object,
|
|
3506
|
+
applyMatrix(object, import_fabric12.util.multiplyTransformMatrices(matrixOf(host), matrixOf(object)));
|
|
2516
3507
|
}
|
|
2517
3508
|
function toHostSpace(object, host) {
|
|
2518
3509
|
applyMatrix(
|
|
2519
3510
|
object,
|
|
2520
|
-
|
|
3511
|
+
import_fabric12.util.multiplyTransformMatrices(import_fabric12.util.invertTransform(matrixOf(host)), matrixOf(object))
|
|
2521
3512
|
);
|
|
2522
3513
|
}
|
|
2523
3514
|
function relativeMatrix(object, host) {
|
|
2524
|
-
return
|
|
3515
|
+
return import_fabric12.util.multiplyTransformMatrices(import_fabric12.util.invertTransform(matrixOf(host)), matrixOf(object));
|
|
2525
3516
|
}
|
|
2526
3517
|
function applyRelativeMatrix(object, host, rel) {
|
|
2527
|
-
applyMatrix(object,
|
|
3518
|
+
applyMatrix(object, import_fabric12.util.multiplyTransformMatrices(matrixOf(host), rel));
|
|
2528
3519
|
}
|
|
2529
3520
|
function asObject(clip) {
|
|
2530
3521
|
return clip;
|
|
@@ -2587,7 +3578,7 @@ function boxClip(text, makeRect) {
|
|
|
2587
3578
|
function hasMask(meta) {
|
|
2588
3579
|
return !!meta.maskPreset || (meta.maskStack?.length ?? 0) > 0;
|
|
2589
3580
|
}
|
|
2590
|
-
function applyTextWrapToObject(object, meta, makeRect = (options) => new
|
|
3581
|
+
function applyTextWrapToObject(object, meta, makeRect = (options) => new import_fabric13.Rect(options)) {
|
|
2591
3582
|
const text = asText(object);
|
|
2592
3583
|
if (!text) return;
|
|
2593
3584
|
const state = readTextWrap(meta);
|
|
@@ -2726,7 +3717,7 @@ var TextWrapManager = class {
|
|
|
2726
3717
|
};
|
|
2727
3718
|
|
|
2728
3719
|
// src/mask-presets/manager.ts
|
|
2729
|
-
var
|
|
3720
|
+
var import_fabric14 = require("fabric");
|
|
2730
3721
|
|
|
2731
3722
|
// src/mask-presets/shapes.ts
|
|
2732
3723
|
var SHAPE_MASK_IDS = [
|
|
@@ -2994,13 +3985,13 @@ var MaskPresetManager = class {
|
|
|
2994
3985
|
objectCaching: false
|
|
2995
3986
|
};
|
|
2996
3987
|
if (isShapeMaskId(id)) {
|
|
2997
|
-
return new
|
|
3988
|
+
return new import_fabric14.Path(shapeMaskPathData(id), {
|
|
2998
3989
|
...shared,
|
|
2999
3990
|
scaleX: width / SHAPE_MASK_BOX,
|
|
3000
3991
|
scaleY: height / SHAPE_MASK_BOX
|
|
3001
3992
|
});
|
|
3002
3993
|
}
|
|
3003
|
-
return new
|
|
3994
|
+
return new import_fabric14.FabricImage(renderTextureMask(id), {
|
|
3004
3995
|
...shared,
|
|
3005
3996
|
scaleX: width / TEXTURE_MASK_SIZE,
|
|
3006
3997
|
scaleY: height / TEXTURE_MASK_SIZE
|
|
@@ -3008,54 +3999,6 @@ var MaskPresetManager = class {
|
|
|
3008
3999
|
}
|
|
3009
4000
|
};
|
|
3010
4001
|
|
|
3011
|
-
// src/shadow.ts
|
|
3012
|
-
var import_fabric11 = require("fabric");
|
|
3013
|
-
|
|
3014
|
-
// src/utils/color.ts
|
|
3015
|
-
var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
|
|
3016
|
-
function isCssColor(value, allowEmpty = false) {
|
|
3017
|
-
if (typeof value !== "string") return false;
|
|
3018
|
-
const trimmed = value.trim();
|
|
3019
|
-
if (!trimmed) return allowEmpty;
|
|
3020
|
-
return CSS_COLOR.test(trimmed);
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
// src/shadow.ts
|
|
3024
|
-
var DEFAULT_LAYER_SHADOW = {
|
|
3025
|
-
enabled: false,
|
|
3026
|
-
color: "#000000",
|
|
3027
|
-
blur: 12,
|
|
3028
|
-
offsetX: 6,
|
|
3029
|
-
offsetY: 6
|
|
3030
|
-
};
|
|
3031
|
-
function readLayerShadow(object) {
|
|
3032
|
-
const shadow = object.shadow;
|
|
3033
|
-
if (!shadow || typeof shadow === "string") return { ...DEFAULT_LAYER_SHADOW };
|
|
3034
|
-
return {
|
|
3035
|
-
enabled: true,
|
|
3036
|
-
color: typeof shadow.color === "string" ? shadow.color : DEFAULT_LAYER_SHADOW.color,
|
|
3037
|
-
blur: shadow.blur ?? DEFAULT_LAYER_SHADOW.blur,
|
|
3038
|
-
offsetX: shadow.offsetX ?? DEFAULT_LAYER_SHADOW.offsetX,
|
|
3039
|
-
offsetY: shadow.offsetY ?? DEFAULT_LAYER_SHADOW.offsetY
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
function applyLayerShadow(object, config) {
|
|
3043
|
-
const next = { ...readLayerShadow(object), ...config };
|
|
3044
|
-
if (!next.enabled) {
|
|
3045
|
-
object.set({ shadow: null });
|
|
3046
|
-
return;
|
|
3047
|
-
}
|
|
3048
|
-
const color = isCssColor(next.color) ? next.color : DEFAULT_LAYER_SHADOW.color;
|
|
3049
|
-
object.set({
|
|
3050
|
-
shadow: new import_fabric11.Shadow({
|
|
3051
|
-
color,
|
|
3052
|
-
blur: Math.max(0, next.blur),
|
|
3053
|
-
offsetX: next.offsetX,
|
|
3054
|
-
offsetY: next.offsetY
|
|
3055
|
-
})
|
|
3056
|
-
});
|
|
3057
|
-
}
|
|
3058
|
-
|
|
3059
4002
|
// src/selection-style.ts
|
|
3060
4003
|
var DEFAULT_SELECTION_STYLE = {
|
|
3061
4004
|
borderColor: "#c9a96e",
|
|
@@ -3161,7 +4104,7 @@ var UnitConverter = class {
|
|
|
3161
4104
|
};
|
|
3162
4105
|
|
|
3163
4106
|
// src/serialization.ts
|
|
3164
|
-
var
|
|
4107
|
+
var import_fabric15 = require("fabric");
|
|
3165
4108
|
var VERSION = "2.0.0";
|
|
3166
4109
|
function serializeEditor(editor) {
|
|
3167
4110
|
return {
|
|
@@ -3197,7 +4140,7 @@ async function deserializeEditor(editor, state) {
|
|
|
3197
4140
|
}
|
|
3198
4141
|
const staged = await Promise.all(
|
|
3199
4142
|
state.layers.map(async (serialized) => {
|
|
3200
|
-
const fabricObject = (await
|
|
4143
|
+
const fabricObject = (await import_fabric15.util.enlivenObjects([serialized.fabricObject]))[0];
|
|
3201
4144
|
if (!fabricObject) {
|
|
3202
4145
|
const source = serialized.fabricObject.src;
|
|
3203
4146
|
if (typeof source === "string" && source.startsWith("blob:")) {
|
|
@@ -3208,7 +4151,7 @@ async function deserializeEditor(editor, state) {
|
|
|
3208
4151
|
return { serialized, fabricObject };
|
|
3209
4152
|
})
|
|
3210
4153
|
);
|
|
3211
|
-
const stagedBackground = state.backgroundImage ? (await
|
|
4154
|
+
const stagedBackground = state.backgroundImage ? (await import_fabric15.util.enlivenObjects([state.backgroundImage]))[0] : null;
|
|
3212
4155
|
if (state.backgroundImage && !stagedBackground) {
|
|
3213
4156
|
const source = state.backgroundImage.src;
|
|
3214
4157
|
if (typeof source === "string" && source.startsWith("blob:")) {
|
|
@@ -3261,7 +4204,7 @@ function restoreLayer(editor, serialized, fabricObject) {
|
|
|
3261
4204
|
}
|
|
3262
4205
|
|
|
3263
4206
|
// src/export.ts
|
|
3264
|
-
var
|
|
4207
|
+
var import_fabric16 = require("fabric");
|
|
3265
4208
|
|
|
3266
4209
|
// src/displacement.ts
|
|
3267
4210
|
var CHANNEL_INDEX = {
|
|
@@ -3355,7 +4298,7 @@ async function exportPNG(canvas, options = {}) {
|
|
|
3355
4298
|
}
|
|
3356
4299
|
async function exportIsolatedPNG(source, objects, options = {}) {
|
|
3357
4300
|
const element = source.lowerCanvasEl.ownerDocument.createElement("canvas");
|
|
3358
|
-
const canvas = new
|
|
4301
|
+
const canvas = new import_fabric16.StaticCanvas(element, {
|
|
3359
4302
|
width: options.width ?? source.getWidth(),
|
|
3360
4303
|
height: options.height ?? source.getHeight(),
|
|
3361
4304
|
backgroundColor: options.backgroundColor || void 0
|
|
@@ -3385,7 +4328,7 @@ async function exportPrintArea(source, area, options = {}) {
|
|
|
3385
4328
|
throw new Error("Print area does not overlap the canvas");
|
|
3386
4329
|
}
|
|
3387
4330
|
const element = source.lowerCanvasEl.ownerDocument.createElement("canvas");
|
|
3388
|
-
const canvas = new
|
|
4331
|
+
const canvas = new import_fabric16.StaticCanvas(element, { width, height });
|
|
3389
4332
|
try {
|
|
3390
4333
|
const clones = await Promise.all(source.getObjects().map((object) => object.clone()));
|
|
3391
4334
|
if (clones.length) canvas.add(...clones);
|
|
@@ -3825,7 +4768,7 @@ var ProjectManager = class {
|
|
|
3825
4768
|
};
|
|
3826
4769
|
|
|
3827
4770
|
// src/mask.ts
|
|
3828
|
-
var
|
|
4771
|
+
var import_fabric17 = require("fabric");
|
|
3829
4772
|
var MaskRefinementError = class extends Error {
|
|
3830
4773
|
constructor(code, message, cause) {
|
|
3831
4774
|
super(message);
|
|
@@ -3857,7 +4800,7 @@ var MaskController = class {
|
|
|
3857
4800
|
throw new Error("Mask dimensions must be positive integers");
|
|
3858
4801
|
}
|
|
3859
4802
|
const backing = this.makeCanvas(width, height);
|
|
3860
|
-
const image = new
|
|
4803
|
+
const image = new import_fabric17.FabricImage(backing, {
|
|
3861
4804
|
left: 0,
|
|
3862
4805
|
top: 0,
|
|
3863
4806
|
originX: "left",
|
|
@@ -4050,8 +4993,8 @@ var MaskController = class {
|
|
|
4050
4993
|
point.y,
|
|
4051
4994
|
radius
|
|
4052
4995
|
);
|
|
4053
|
-
const
|
|
4054
|
-
gradient.addColorStop(0,
|
|
4996
|
+
const color2 = brush.mode === "subtract" ? "rgba(0,0,0,1)" : "rgba(255,255,255,1)";
|
|
4997
|
+
gradient.addColorStop(0, color2);
|
|
4055
4998
|
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
4056
4999
|
context.fillStyle = gradient;
|
|
4057
5000
|
context.beginPath();
|
|
@@ -4083,13 +5026,13 @@ var MaskController = class {
|
|
|
4083
5026
|
};
|
|
4084
5027
|
|
|
4085
5028
|
// src/masks/manager.ts
|
|
4086
|
-
var
|
|
5029
|
+
var import_fabric24 = require("fabric");
|
|
4087
5030
|
|
|
4088
5031
|
// src/masks/compose.ts
|
|
4089
|
-
var
|
|
5032
|
+
var import_fabric19 = require("fabric");
|
|
4090
5033
|
|
|
4091
5034
|
// src/gradient.ts
|
|
4092
|
-
var
|
|
5035
|
+
var import_fabric18 = require("fabric");
|
|
4093
5036
|
var DEFAULT_GRADIENT_CONFIG = {
|
|
4094
5037
|
kind: "linear",
|
|
4095
5038
|
angle: 90,
|
|
@@ -4126,10 +5069,10 @@ function angleFromCoords(coords, box) {
|
|
|
4126
5069
|
}
|
|
4127
5070
|
function stopColor(stop) {
|
|
4128
5071
|
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4129
|
-
const
|
|
5072
|
+
const color2 = new import_fabric18.Color(source);
|
|
4130
5073
|
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4131
|
-
|
|
4132
|
-
return
|
|
5074
|
+
color2.setAlpha(clamp(opacity, 0, 1));
|
|
5075
|
+
return color2.toRgba();
|
|
4133
5076
|
}
|
|
4134
5077
|
function toFabricGradient(config, box) {
|
|
4135
5078
|
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
@@ -4138,7 +5081,7 @@ function toFabricGradient(config, box) {
|
|
|
4138
5081
|
});
|
|
4139
5082
|
if (config.kind === "radial") {
|
|
4140
5083
|
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4141
|
-
return new
|
|
5084
|
+
return new import_fabric18.Gradient({
|
|
4142
5085
|
type: "radial",
|
|
4143
5086
|
gradientUnits: "percentage",
|
|
4144
5087
|
coords: {
|
|
@@ -4152,7 +5095,7 @@ function toFabricGradient(config, box) {
|
|
|
4152
5095
|
colorStops
|
|
4153
5096
|
});
|
|
4154
5097
|
}
|
|
4155
|
-
return new
|
|
5098
|
+
return new import_fabric18.Gradient({
|
|
4156
5099
|
type: "linear",
|
|
4157
5100
|
gradientUnits: "percentage",
|
|
4158
5101
|
coords: linearCoords(config.angle, box),
|
|
@@ -4162,24 +5105,24 @@ function toFabricGradient(config, box) {
|
|
|
4162
5105
|
function toFabricMaskGradient(config, box) {
|
|
4163
5106
|
const gradient = toFabricGradient(config, box);
|
|
4164
5107
|
gradient.colorStops = gradient.colorStops.map((stop) => {
|
|
4165
|
-
const
|
|
4166
|
-
const [red, green, blue] =
|
|
5108
|
+
const color2 = new import_fabric18.Color(stop.color);
|
|
5109
|
+
const [red, green, blue] = color2.getSource();
|
|
4167
5110
|
const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
|
|
4168
|
-
|
|
4169
|
-
return { offset: stop.offset, color:
|
|
5111
|
+
color2.setSource([255, 255, 255, clamp(luminance * color2.getAlpha(), 0, 1)]);
|
|
5112
|
+
return { offset: stop.offset, color: color2.toRgba() };
|
|
4170
5113
|
});
|
|
4171
5114
|
return gradient;
|
|
4172
5115
|
}
|
|
4173
5116
|
function readGradientConfig(object) {
|
|
4174
5117
|
const fill = object.fill;
|
|
4175
|
-
if (!fill || typeof fill === "string" || !(fill instanceof
|
|
5118
|
+
if (!fill || typeof fill === "string" || !(fill instanceof import_fabric18.Gradient)) return null;
|
|
4176
5119
|
const stops = fill.colorStops.map((stop) => {
|
|
4177
5120
|
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4178
|
-
const
|
|
5121
|
+
const color2 = new import_fabric18.Color(source);
|
|
4179
5122
|
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4180
5123
|
return {
|
|
4181
|
-
color: `#${
|
|
4182
|
-
opacity:
|
|
5124
|
+
color: `#${color2.toHex().toLowerCase()}`,
|
|
5125
|
+
opacity: color2.getAlpha(),
|
|
4183
5126
|
position: clamp(offset, 0, 1)
|
|
4184
5127
|
};
|
|
4185
5128
|
});
|
|
@@ -4213,7 +5156,7 @@ function neutralize(child) {
|
|
|
4213
5156
|
child.set({ opacity: 0, globalCompositeOperation: "source-over" });
|
|
4214
5157
|
}
|
|
4215
5158
|
function baseRect(box) {
|
|
4216
|
-
return new
|
|
5159
|
+
return new import_fabric19.Rect({
|
|
4217
5160
|
left: box.left,
|
|
4218
5161
|
top: box.top,
|
|
4219
5162
|
width: Math.max(1, box.width),
|
|
@@ -4248,7 +5191,7 @@ function composeMaskGroup(children, entries, options) {
|
|
|
4248
5191
|
});
|
|
4249
5192
|
const first = entries.find((entry) => entry.visible);
|
|
4250
5193
|
const withBase = !first || first.mode !== "add" ? [baseRect(options.box), ...children] : [...children];
|
|
4251
|
-
return new
|
|
5194
|
+
return new import_fabric19.Group(withBase, {
|
|
4252
5195
|
absolutePositioned: options.absolute,
|
|
4253
5196
|
// Cached, so the children's compositing operations resolve against each
|
|
4254
5197
|
// other instead of against the page underneath the mask.
|
|
@@ -4262,7 +5205,7 @@ function needsAbsoluteSpace(entries) {
|
|
|
4262
5205
|
}
|
|
4263
5206
|
|
|
4264
5207
|
// src/masks/edit.ts
|
|
4265
|
-
var
|
|
5208
|
+
var import_fabric20 = require("fabric");
|
|
4266
5209
|
|
|
4267
5210
|
// src/masks/edit-viewport.ts
|
|
4268
5211
|
var MIN_PADDING = 48;
|
|
@@ -4440,8 +5383,8 @@ var MaskEditController = class {
|
|
|
4440
5383
|
if (!this.handle || !this.child || !this.group) return;
|
|
4441
5384
|
applyMatrix(
|
|
4442
5385
|
this.child,
|
|
4443
|
-
|
|
4444
|
-
|
|
5386
|
+
import_fabric20.util.multiplyTransformMatrices(
|
|
5387
|
+
import_fabric20.util.invertTransform(matrixOf(this.group)),
|
|
4445
5388
|
matrixOf(this.handle)
|
|
4446
5389
|
)
|
|
4447
5390
|
);
|
|
@@ -4452,15 +5395,15 @@ var MaskEditController = class {
|
|
|
4452
5395
|
};
|
|
4453
5396
|
|
|
4454
5397
|
// src/masks/store.ts
|
|
4455
|
-
var
|
|
5398
|
+
var import_fabric23 = require("fabric");
|
|
4456
5399
|
|
|
4457
5400
|
// src/masks/host.ts
|
|
4458
|
-
var
|
|
5401
|
+
var import_fabric21 = require("fabric");
|
|
4459
5402
|
function findCanvasHost(layers) {
|
|
4460
5403
|
return layers.getAll().find((layer) => layer.meta.canvasMask);
|
|
4461
5404
|
}
|
|
4462
5405
|
function createCanvasHost(canvas, layers) {
|
|
4463
|
-
const rect = new
|
|
5406
|
+
const rect = new import_fabric21.Rect({
|
|
4464
5407
|
left: 0,
|
|
4465
5408
|
top: 0,
|
|
4466
5409
|
width: canvas.getWidth(),
|
|
@@ -4509,9 +5452,9 @@ function hostBoxOf(canvas, host, absolute) {
|
|
|
4509
5452
|
}
|
|
4510
5453
|
|
|
4511
5454
|
// src/masks/install.ts
|
|
4512
|
-
var
|
|
5455
|
+
var import_fabric22 = require("fabric");
|
|
4513
5456
|
function convertSpace(host, sources, absolute) {
|
|
4514
|
-
const wasAbsolute = host.clipPath instanceof
|
|
5457
|
+
const wasAbsolute = host.clipPath instanceof import_fabric22.Group ? host.clipPath.absolutePositioned : absolute;
|
|
4515
5458
|
if (absolute === wasAbsolute) return;
|
|
4516
5459
|
for (const source of sources) {
|
|
4517
5460
|
if (absolute) toCanvasSpace(source, host);
|
|
@@ -4647,7 +5590,7 @@ var MaskStackStore = class {
|
|
|
4647
5590
|
const entries = this.list(target);
|
|
4648
5591
|
const meta = this.hostLayer(target)?.meta;
|
|
4649
5592
|
if (entries.length === 0 && this.holdsBoxClip(meta)) return [];
|
|
4650
|
-
if (entries.length === 0 || !(clip instanceof
|
|
5593
|
+
if (entries.length === 0 || !(clip instanceof import_fabric23.Group)) {
|
|
4651
5594
|
const source = asObject(clip);
|
|
4652
5595
|
if (meta?.overflow === "hidden") source.clipPath = void 0;
|
|
4653
5596
|
return restoreGradientPaint(entries, [source]);
|
|
@@ -4712,7 +5655,7 @@ function restoreGradientPaint(entries, sources) {
|
|
|
4712
5655
|
};
|
|
4713
5656
|
const fill = source.fill;
|
|
4714
5657
|
const rendered = toFabricMaskGradient(entry.gradient, box);
|
|
4715
|
-
if (fill instanceof
|
|
5658
|
+
if (fill instanceof import_fabric23.Gradient && JSON.stringify(fill.toObject()) !== JSON.stringify(rendered.toObject())) {
|
|
4716
5659
|
return;
|
|
4717
5660
|
}
|
|
4718
5661
|
source.set({
|
|
@@ -4781,7 +5724,7 @@ var LayerMaskManager = class extends MaskStackStore {
|
|
|
4781
5724
|
if (index === -1) return false;
|
|
4782
5725
|
this.endEdit(false);
|
|
4783
5726
|
this.commit(target, host, [...entries], this.unwrap(target, host), false, true);
|
|
4784
|
-
const group = host.clipPath instanceof
|
|
5727
|
+
const group = host.clipPath instanceof import_fabric24.Group ? host.clipPath : null;
|
|
4785
5728
|
if (!group) return false;
|
|
4786
5729
|
const children = group.getObjects();
|
|
4787
5730
|
const child = children[children.length - entries.length + index];
|
|
@@ -5138,11 +6081,17 @@ var CanvasEditor = class {
|
|
|
5138
6081
|
snapping;
|
|
5139
6082
|
crop;
|
|
5140
6083
|
patterns;
|
|
6084
|
+
/** Parametric shape layers: `meta.shape` and in-place regeneration. */
|
|
6085
|
+
shapes;
|
|
5141
6086
|
curves;
|
|
5142
6087
|
wrap;
|
|
5143
6088
|
maskPresets;
|
|
6089
|
+
/** Owner of every image layer's filter array (D5). */
|
|
6090
|
+
imageEffects;
|
|
5144
6091
|
/** Stacked boolean masks, per layer and for the design as a whole. */
|
|
5145
6092
|
layerMasks;
|
|
6093
|
+
/** One effect per text or shape layer (D6). */
|
|
6094
|
+
effects;
|
|
5146
6095
|
fonts;
|
|
5147
6096
|
licensing;
|
|
5148
6097
|
pages;
|
|
@@ -5152,6 +6101,8 @@ var CanvasEditor = class {
|
|
|
5152
6101
|
zoomLevel = 1;
|
|
5153
6102
|
selectionStyle = { ...DEFAULT_SELECTION_STYLE };
|
|
5154
6103
|
mockup = null;
|
|
6104
|
+
/** Alt was held when a drag began on this layer; the drop leaves a copy behind. */
|
|
6105
|
+
altDrag = null;
|
|
5155
6106
|
// The design's configured background. The live canvas background is forced
|
|
5156
6107
|
// transparent while a mockup preview is shown, so this is the source of truth
|
|
5157
6108
|
// for serialization and export — not the (possibly transient) canvas value.
|
|
@@ -5159,6 +6110,7 @@ var CanvasEditor = class {
|
|
|
5159
6110
|
designBackgroundImage = null;
|
|
5160
6111
|
backgroundImageOptions = null;
|
|
5161
6112
|
constructor(canvasElement, config) {
|
|
6113
|
+
(0, import_canvas_editor_effects2.registerEffects)(fabricModule);
|
|
5162
6114
|
this.events = new EventEmitter();
|
|
5163
6115
|
this.fonts = new FontRegistry();
|
|
5164
6116
|
config.fonts?.forEach((font) => this.fonts.register(font));
|
|
@@ -5167,7 +6119,7 @@ var CanvasEditor = class {
|
|
|
5167
6119
|
const widthPx = this.units.toPixels(config.width);
|
|
5168
6120
|
const heightPx = this.units.toPixels(config.height);
|
|
5169
6121
|
this.designBackground = config.backgroundColor ?? "#ffffff";
|
|
5170
|
-
this.canvas = new
|
|
6122
|
+
this.canvas = new import_fabric25.Canvas(canvasElement, {
|
|
5171
6123
|
width: widthPx,
|
|
5172
6124
|
height: heightPx,
|
|
5173
6125
|
backgroundColor: this.designBackground,
|
|
@@ -5193,6 +6145,15 @@ var CanvasEditor = class {
|
|
|
5193
6145
|
this.wrap = new TextWrapManager(this.canvas, this.layers, this.history, this.events);
|
|
5194
6146
|
this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
|
|
5195
6147
|
this.layerMasks = new LayerMaskManager(this.canvas, this.layers, this.history, this.events);
|
|
6148
|
+
this.effects = new LayerEffectsManager(this.canvas, this.layers, this.history, this.events);
|
|
6149
|
+
this.imageEffects = new ImageEffectsManager(
|
|
6150
|
+
this.canvas,
|
|
6151
|
+
this.layers,
|
|
6152
|
+
this.history,
|
|
6153
|
+
this.events
|
|
6154
|
+
);
|
|
6155
|
+
this.shapes = new ShapeManager(this.canvas, this.layers, this.history, this.events);
|
|
6156
|
+
config.shapes?.forEach((plugin) => this.shapes.register(plugin));
|
|
5196
6157
|
this.setupCanvasEvents();
|
|
5197
6158
|
this.refreshSelectionStyle();
|
|
5198
6159
|
this.history.saveImmediate();
|
|
@@ -5202,7 +6163,7 @@ var CanvasEditor = class {
|
|
|
5202
6163
|
// ─── Layer Operations ────────────────────────────────
|
|
5203
6164
|
async addImage(url, options) {
|
|
5204
6165
|
try {
|
|
5205
|
-
const img = await
|
|
6166
|
+
const img = await import_fabric25.FabricImage.fromURL(
|
|
5206
6167
|
url,
|
|
5207
6168
|
{},
|
|
5208
6169
|
{ originX: "left", originY: "top", ...options }
|
|
@@ -5217,7 +6178,7 @@ var CanvasEditor = class {
|
|
|
5217
6178
|
}
|
|
5218
6179
|
}
|
|
5219
6180
|
/** Replace an image source without changing its layer identity or visual transform. */
|
|
5220
|
-
async replaceImageSource(layerId, url) {
|
|
6181
|
+
async replaceImageSource(layerId, url, options = {}) {
|
|
5221
6182
|
const layer = this.layers.get(layerId);
|
|
5222
6183
|
if (!layer || layer.type !== "image" && layer.type !== "mask") {
|
|
5223
6184
|
throw new Error(`Image or mask layer not found: ${layerId}`);
|
|
@@ -5228,7 +6189,11 @@ var CanvasEditor = class {
|
|
|
5228
6189
|
if (this.crop.activeLayerId() === layerId) this.crop.cancel();
|
|
5229
6190
|
const previous = layer.fabricObject;
|
|
5230
6191
|
try {
|
|
5231
|
-
const replacement = await
|
|
6192
|
+
const replacement = await import_fabric25.FabricImage.fromURL(
|
|
6193
|
+
url,
|
|
6194
|
+
{ crossOrigin: options.crossOrigin },
|
|
6195
|
+
{ originX: "left", originY: "top" }
|
|
6196
|
+
);
|
|
5232
6197
|
replacement.set({
|
|
5233
6198
|
left: previous.left,
|
|
5234
6199
|
top: previous.top,
|
|
@@ -5250,14 +6215,28 @@ var CanvasEditor = class {
|
|
|
5250
6215
|
replacement.applyFilters();
|
|
5251
6216
|
replacement.setCoords();
|
|
5252
6217
|
this.layers.replaceObject(layerId, replacement);
|
|
6218
|
+
this.imageEffects.refresh(layerId);
|
|
5253
6219
|
return layer;
|
|
5254
6220
|
} catch (error) {
|
|
5255
6221
|
this.events.emit("error", { message: "Failed to replace image source", error });
|
|
5256
6222
|
throw error;
|
|
5257
6223
|
}
|
|
5258
6224
|
}
|
|
6225
|
+
/**
|
|
6226
|
+
* The class the registry holds for a stock name.
|
|
6227
|
+
*
|
|
6228
|
+
* `registerEffects` (run in the constructor) overrides the stock names, but
|
|
6229
|
+
* registration only decides what an ENLIVEN produces — a `new Textbox(...)`
|
|
6230
|
+
* here would still be the stock class, which carries no `effect` in its
|
|
6231
|
+
* `customProperties` and so could never serialize one. Authoring and
|
|
6232
|
+
* restoring have to yield the same class.
|
|
6233
|
+
*/
|
|
6234
|
+
registered(name, fallback) {
|
|
6235
|
+
return import_fabric25.classRegistry.getClass(name) ?? fallback;
|
|
6236
|
+
}
|
|
5259
6237
|
addText(text, options) {
|
|
5260
|
-
const
|
|
6238
|
+
const TextboxClass = this.registered("Textbox", import_fabric25.Textbox);
|
|
6239
|
+
const textbox = new TextboxClass(text, {
|
|
5261
6240
|
fontSize: 32,
|
|
5262
6241
|
fontFamily: "Arial",
|
|
5263
6242
|
fill: "#000000",
|
|
@@ -5275,6 +6254,15 @@ var CanvasEditor = class {
|
|
|
5275
6254
|
addShape(plugin, options) {
|
|
5276
6255
|
const obj = plugin.create(options);
|
|
5277
6256
|
const layer = this.layers.add("shape", obj, plugin.name);
|
|
6257
|
+
if (plugin.params) {
|
|
6258
|
+
this.shapes.register(plugin);
|
|
6259
|
+
const overrides = options?.params ?? {};
|
|
6260
|
+
layer.meta.shape = {
|
|
6261
|
+
id: plugin.name,
|
|
6262
|
+
params: resolveShapeParams(plugin, overrides),
|
|
6263
|
+
version: 1
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
5278
6266
|
this.layers.select(layer.id);
|
|
5279
6267
|
this.history.save();
|
|
5280
6268
|
return layer;
|
|
@@ -5286,7 +6274,8 @@ var CanvasEditor = class {
|
|
|
5286
6274
|
addGradient(config = DEFAULT_GRADIENT_CONFIG, box) {
|
|
5287
6275
|
const width = box?.width ?? this.canvas.getWidth();
|
|
5288
6276
|
const height = box?.height ?? this.canvas.getHeight();
|
|
5289
|
-
const
|
|
6277
|
+
const RectClass = this.registered("Rect", import_fabric25.Rect);
|
|
6278
|
+
const object = new RectClass({
|
|
5290
6279
|
left: box?.left ?? 0,
|
|
5291
6280
|
top: box?.top ?? 0,
|
|
5292
6281
|
originX: "left",
|
|
@@ -5415,6 +6404,49 @@ var CanvasEditor = class {
|
|
|
5415
6404
|
this.canvas.requestRenderAll();
|
|
5416
6405
|
this.history.save();
|
|
5417
6406
|
}
|
|
6407
|
+
// ─── Align ──────────────────────────────────────────
|
|
6408
|
+
/** What the user is acting on: the proxy where a layer has one. */
|
|
6409
|
+
selectedTargets() {
|
|
6410
|
+
return this.canvas.getActiveObjects().filter((object) => this.layers.findByObject(object) !== void 0);
|
|
6411
|
+
}
|
|
6412
|
+
afterMove(objects) {
|
|
6413
|
+
relayoutSelection(this.canvas.getActiveObject());
|
|
6414
|
+
for (const object of objects) {
|
|
6415
|
+
const layer = this.layers.findByObject(object);
|
|
6416
|
+
if (!layer) continue;
|
|
6417
|
+
if (layer.renderProxy === object) this.patterns.commitProxy(layer.id);
|
|
6418
|
+
this.events.emit("layer:modified", { layerId: layer.id });
|
|
6419
|
+
}
|
|
6420
|
+
this.canvas.requestRenderAll();
|
|
6421
|
+
this.history.save();
|
|
6422
|
+
}
|
|
6423
|
+
/** One layer aligns to the canvas; several align to their common box. One history entry. */
|
|
6424
|
+
alignLayers(target) {
|
|
6425
|
+
const objects = this.selectedTargets();
|
|
6426
|
+
if (objects.length === 0) return false;
|
|
6427
|
+
const frame = objects.length === 1 ? canvasBox(this.canvas.getWidth(), this.canvas.getHeight()) : unionBox(objects.map((o) => (o.setCoords(), o.getBoundingRect())));
|
|
6428
|
+
this.history.beginTransaction();
|
|
6429
|
+
try {
|
|
6430
|
+
alignObjects(objects, target, frame);
|
|
6431
|
+
this.afterMove(objects);
|
|
6432
|
+
} finally {
|
|
6433
|
+
this.history.endTransaction();
|
|
6434
|
+
}
|
|
6435
|
+
return true;
|
|
6436
|
+
}
|
|
6437
|
+
/** Equal gaps along an axis. Needs three selected; returns false otherwise. */
|
|
6438
|
+
distributeLayers(axis) {
|
|
6439
|
+
const objects = this.selectedTargets();
|
|
6440
|
+
if (objects.length < 3) return false;
|
|
6441
|
+
this.history.beginTransaction();
|
|
6442
|
+
try {
|
|
6443
|
+
distributeObjects(objects, axis);
|
|
6444
|
+
this.afterMove(objects);
|
|
6445
|
+
} finally {
|
|
6446
|
+
this.history.endTransaction();
|
|
6447
|
+
}
|
|
6448
|
+
return true;
|
|
6449
|
+
}
|
|
5418
6450
|
/** Clone a layer (offset slightly) and select the copy. */
|
|
5419
6451
|
async duplicateLayer(id) {
|
|
5420
6452
|
const layer = this.layers.get(id);
|
|
@@ -5439,24 +6471,19 @@ var CanvasEditor = class {
|
|
|
5439
6471
|
this.history.save();
|
|
5440
6472
|
return copy;
|
|
5441
6473
|
}
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
layer.meta.imageAdjustments = next;
|
|
5456
|
-
image.applyFilters();
|
|
5457
|
-
this.canvas.requestRenderAll();
|
|
5458
|
-
this.history.save();
|
|
5459
|
-
return true;
|
|
6474
|
+
/** The drag moved the original; a copy takes the start position, just beneath it. */
|
|
6475
|
+
async leaveCopyBehind(layer, transform) {
|
|
6476
|
+
const now = import_fabric25.util.saveObjectTransform(layer.fabricObject);
|
|
6477
|
+
if (now.left === transform.left && now.top === transform.top) return;
|
|
6478
|
+
await this.history.transaction(async () => {
|
|
6479
|
+
const copy = await this.duplicateLayer(layer.id);
|
|
6480
|
+
if (!copy) return;
|
|
6481
|
+
copy.fabricObject.set(transform);
|
|
6482
|
+
copy.fabricObject.setCoords();
|
|
6483
|
+
this.layers.reorder(copy.id, this.layers.getAll().indexOf(layer));
|
|
6484
|
+
this.layers.select(layer.id);
|
|
6485
|
+
this.canvas.requestRenderAll();
|
|
6486
|
+
});
|
|
5460
6487
|
}
|
|
5461
6488
|
/**
|
|
5462
6489
|
* Combine two or more layers into one group layer. Picking a group among them
|
|
@@ -5491,7 +6518,7 @@ var CanvasEditor = class {
|
|
|
5491
6518
|
this.layers.detach(layer.id);
|
|
5492
6519
|
}
|
|
5493
6520
|
}
|
|
5494
|
-
const group = new
|
|
6521
|
+
const group = new import_fabric25.Group(objects);
|
|
5495
6522
|
const grouped = this.layers.add("group", group, name);
|
|
5496
6523
|
this.layers.adoptChildren(grouped, children);
|
|
5497
6524
|
this.layers.reorder(grouped.id, insertIndex);
|
|
@@ -5525,6 +6552,7 @@ var CanvasEditor = class {
|
|
|
5525
6552
|
await deserializeEditor(this, state);
|
|
5526
6553
|
await this.patterns.rehydrateAll();
|
|
5527
6554
|
this.layerMasks.refreshAll();
|
|
6555
|
+
this.imageEffects.refreshAll();
|
|
5528
6556
|
} catch (error) {
|
|
5529
6557
|
if (!managedByHistory) {
|
|
5530
6558
|
this.events.emit("error", { message: "Failed to load editor state", error });
|
|
@@ -5553,7 +6581,7 @@ var CanvasEditor = class {
|
|
|
5553
6581
|
const layer = this.layers.get(id);
|
|
5554
6582
|
if (!layer) throw new Error(`Layer not found: ${id}`);
|
|
5555
6583
|
try {
|
|
5556
|
-
if (options.resolution === "source" && layer.fabricObject instanceof
|
|
6584
|
+
if (options.resolution === "source" && layer.fabricObject instanceof import_fabric25.FabricImage) {
|
|
5557
6585
|
const image = await layer.fabricObject.clone();
|
|
5558
6586
|
image.set({
|
|
5559
6587
|
left: 0,
|
|
@@ -5601,7 +6629,9 @@ var CanvasEditor = class {
|
|
|
5601
6629
|
try {
|
|
5602
6630
|
await this.fonts.ready();
|
|
5603
6631
|
const blob = await this.withDesignBackground(
|
|
5604
|
-
() =>
|
|
6632
|
+
() => this.imageEffects.withSourceResolution(
|
|
6633
|
+
() => exportPNG(this.canvas, { ...options, format })
|
|
6634
|
+
)
|
|
5605
6635
|
);
|
|
5606
6636
|
this.events.emit("export:complete", { format });
|
|
5607
6637
|
this.licensing.track(`export:${format}`);
|
|
@@ -5639,7 +6669,9 @@ var CanvasEditor = class {
|
|
|
5639
6669
|
}
|
|
5640
6670
|
}
|
|
5641
6671
|
toDataURL(format = "png", multiplier = 1) {
|
|
5642
|
-
return this.withDesignBackground(
|
|
6672
|
+
return this.withDesignBackground(
|
|
6673
|
+
() => this.imageEffects.withSourceResolution(() => exportDataURL(this.canvas, format, multiplier))
|
|
6674
|
+
);
|
|
5643
6675
|
}
|
|
5644
6676
|
/**
|
|
5645
6677
|
* Export the print file: the design cropped to the mockup's print area, on
|
|
@@ -5663,7 +6695,9 @@ var CanvasEditor = class {
|
|
|
5663
6695
|
if (!this.mockup) throw new Error("No mockup is configured");
|
|
5664
6696
|
await this.fonts.ready();
|
|
5665
6697
|
try {
|
|
5666
|
-
const blob = await
|
|
6698
|
+
const blob = await this.imageEffects.withSourceResolution(
|
|
6699
|
+
() => exportMockup(this.canvas, this.mockup, options)
|
|
6700
|
+
);
|
|
5667
6701
|
this.licensing.track(`export:mockup:${options.format ?? "png"}`);
|
|
5668
6702
|
return blob;
|
|
5669
6703
|
} catch (error) {
|
|
@@ -5805,10 +6839,10 @@ var CanvasEditor = class {
|
|
|
5805
6839
|
this.imageProvider = provider;
|
|
5806
6840
|
}
|
|
5807
6841
|
// ─── Canvas Operations ──────────────────────────────
|
|
5808
|
-
setBackground(
|
|
5809
|
-
this.designBackground =
|
|
6842
|
+
setBackground(color2) {
|
|
6843
|
+
this.designBackground = color2;
|
|
5810
6844
|
if (!this.mockup) {
|
|
5811
|
-
this.canvas.backgroundColor =
|
|
6845
|
+
this.canvas.backgroundColor = color2;
|
|
5812
6846
|
this.canvas.requestRenderAll();
|
|
5813
6847
|
}
|
|
5814
6848
|
this.history.save();
|
|
@@ -5829,7 +6863,7 @@ var CanvasEditor = class {
|
|
|
5829
6863
|
return;
|
|
5830
6864
|
}
|
|
5831
6865
|
try {
|
|
5832
|
-
const image = await
|
|
6866
|
+
const image = await import_fabric25.FabricImage.fromURL(
|
|
5833
6867
|
url,
|
|
5834
6868
|
{ crossOrigin: options.crossOrigin ?? null, signal: options.signal },
|
|
5835
6869
|
{ originX: "left", originY: "top" }
|
|
@@ -6115,9 +7149,11 @@ var CanvasEditor = class {
|
|
|
6115
7149
|
}
|
|
6116
7150
|
// ─── Cleanup ────────────────────────────────────────
|
|
6117
7151
|
dispose() {
|
|
7152
|
+
this.imageEffects.dispose();
|
|
6118
7153
|
this.masks.dispose();
|
|
6119
7154
|
this.layerMasks.dispose();
|
|
6120
7155
|
this.snapping.dispose();
|
|
7156
|
+
this.shapes.dispose();
|
|
6121
7157
|
this.crop.dispose();
|
|
6122
7158
|
this.history.dispose();
|
|
6123
7159
|
this.patterns.dispose();
|
|
@@ -6151,12 +7187,26 @@ var CanvasEditor = class {
|
|
|
6151
7187
|
const active = this.canvas.getActiveObject();
|
|
6152
7188
|
if (active) applyObjectSelectionStyle(active, this.selectionStyle, this.zoomLevel);
|
|
6153
7189
|
};
|
|
7190
|
+
this.canvas.on("mouse:down", (e) => {
|
|
7191
|
+
this.altDrag = null;
|
|
7192
|
+
const target = e.target;
|
|
7193
|
+
const pointer = e.e;
|
|
7194
|
+
if (!target || !pointer?.altKey || target instanceof import_fabric25.ActiveSelection) return;
|
|
7195
|
+
const layer = this.layers.findByObject(target);
|
|
7196
|
+
if (!layer || layer.locked || layer.renderProxy) return;
|
|
7197
|
+
this.altDrag = { layerId: layer.id, transform: import_fabric25.util.saveObjectTransform(target) };
|
|
7198
|
+
});
|
|
6154
7199
|
this.canvas.on("object:modified", (e) => {
|
|
6155
7200
|
if (!e.target) return;
|
|
6156
7201
|
const layer = this.layers.findByObject(e.target);
|
|
6157
7202
|
if (layer) {
|
|
6158
7203
|
this.events.emit("layer:modified", { layerId: layer.id });
|
|
6159
7204
|
this.history.save();
|
|
7205
|
+
const pending = this.altDrag;
|
|
7206
|
+
this.altDrag = null;
|
|
7207
|
+
if (pending && pending.layerId === layer.id) {
|
|
7208
|
+
void this.leaveCopyBehind(layer, pending.transform);
|
|
7209
|
+
}
|
|
6160
7210
|
}
|
|
6161
7211
|
});
|
|
6162
7212
|
this.canvas.on("selection:created", (e) => {
|
|
@@ -6245,6 +7295,8 @@ var AnnotationOverlay = class {
|
|
|
6245
7295
|
};
|
|
6246
7296
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6247
7297
|
0 && (module.exports = {
|
|
7298
|
+
ADJUST_KEYS,
|
|
7299
|
+
ADJUST_RANGES,
|
|
6248
7300
|
AnnotationOverlay,
|
|
6249
7301
|
BLEND_GROUPS,
|
|
6250
7302
|
BLEND_MODES,
|
|
@@ -6260,7 +7312,12 @@ var AnnotationOverlay = class {
|
|
|
6260
7312
|
EventEmitter,
|
|
6261
7313
|
FontRegistry,
|
|
6262
7314
|
HistoryManager,
|
|
7315
|
+
IMAGE_FILTER_PRESETS,
|
|
7316
|
+
IMAGE_PROXY_MAX_EDGE,
|
|
7317
|
+
ImageEffectsManager,
|
|
7318
|
+
LAYER_EFFECT_DEFAULTS,
|
|
6263
7319
|
Layer,
|
|
7320
|
+
LayerEffectsManager,
|
|
6264
7321
|
LayerManager,
|
|
6265
7322
|
LayerMaskManager,
|
|
6266
7323
|
LicenseManager,
|
|
@@ -6268,10 +7325,12 @@ var AnnotationOverlay = class {
|
|
|
6268
7325
|
MaskController,
|
|
6269
7326
|
MaskPresetManager,
|
|
6270
7327
|
MaskRefinementError,
|
|
7328
|
+
NEUTRAL_ADJUST,
|
|
6271
7329
|
PatternManager,
|
|
6272
7330
|
ProjectManager,
|
|
6273
7331
|
SHAPE_MASK_BOX,
|
|
6274
7332
|
SHAPE_MASK_IDS,
|
|
7333
|
+
ShapeManager,
|
|
6275
7334
|
SnapManager,
|
|
6276
7335
|
TEXTURE_MASK_IDS,
|
|
6277
7336
|
TEXTURE_MASK_SIZE,
|
|
@@ -6279,6 +7338,8 @@ var AnnotationOverlay = class {
|
|
|
6279
7338
|
TextWrapManager,
|
|
6280
7339
|
TiledPatternObject,
|
|
6281
7340
|
UnitConverter,
|
|
7341
|
+
alignObjects,
|
|
7342
|
+
alphaClipMatrix,
|
|
6282
7343
|
angleFromCoords,
|
|
6283
7344
|
applyAspectLock,
|
|
6284
7345
|
applyLayerShadow,
|
|
@@ -6291,6 +7352,8 @@ var AnnotationOverlay = class {
|
|
|
6291
7352
|
buildCurveLinePaths,
|
|
6292
7353
|
buildCurvePathData,
|
|
6293
7354
|
clamp,
|
|
7355
|
+
clampAdjust,
|
|
7356
|
+
clampAlphaClip,
|
|
6294
7357
|
clearTextureMaskCache,
|
|
6295
7358
|
composeMaskGroup,
|
|
6296
7359
|
computeCoverPlacement,
|
|
@@ -6298,6 +7361,7 @@ var AnnotationOverlay = class {
|
|
|
6298
7361
|
computeTilePositions,
|
|
6299
7362
|
deserializeEditor,
|
|
6300
7363
|
displaceRgba,
|
|
7364
|
+
distributeObjects,
|
|
6301
7365
|
drawTiles,
|
|
6302
7366
|
escapeXml,
|
|
6303
7367
|
explodeTemplateSvg,
|
|
@@ -6309,12 +7373,15 @@ var AnnotationOverlay = class {
|
|
|
6309
7373
|
fitTextWidth,
|
|
6310
7374
|
fitToBox,
|
|
6311
7375
|
generateId,
|
|
7376
|
+
imageEffectsFilterSpecs,
|
|
7377
|
+
imageFilterPreset,
|
|
6312
7378
|
isCssColor,
|
|
6313
7379
|
isMaskPresetId,
|
|
6314
7380
|
isShapeMaskId,
|
|
6315
7381
|
isTextureMaskId,
|
|
6316
7382
|
linearCoords,
|
|
6317
7383
|
needsAbsoluteSpace,
|
|
7384
|
+
normalizeImageEffects,
|
|
6318
7385
|
normalizeTextCurve,
|
|
6319
7386
|
preWrapWordSplit,
|
|
6320
7387
|
readGradientConfig,
|
|
@@ -6322,6 +7389,7 @@ var AnnotationOverlay = class {
|
|
|
6322
7389
|
readTextWrap,
|
|
6323
7390
|
renderTextureMask,
|
|
6324
7391
|
resetTransform,
|
|
7392
|
+
resolveShapeParams,
|
|
6325
7393
|
restoreLocks,
|
|
6326
7394
|
round2,
|
|
6327
7395
|
sanitizeSvg,
|
|
@@ -6332,6 +7400,7 @@ var AnnotationOverlay = class {
|
|
|
6332
7400
|
toCanvasSpace,
|
|
6333
7401
|
toFabricGradient,
|
|
6334
7402
|
toHostSpace,
|
|
7403
|
+
unionBox,
|
|
6335
7404
|
unwrapGroup
|
|
6336
7405
|
});
|
|
6337
7406
|
//# sourceMappingURL=index.js.map
|