@overtone-art/canvas-editor-core 0.2.8 → 0.3.1

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/index.js CHANGED
@@ -24,7 +24,9 @@ __export(index_exports, {
24
24
  CANVAS_SIZE_PRESETS: () => CANVAS_SIZE_PRESETS,
25
25
  CanvasEditor: () => CanvasEditor,
26
26
  CropController: () => CropController,
27
+ DEFAULT_LAYER_SHADOW: () => DEFAULT_LAYER_SHADOW,
27
28
  DEFAULT_PATTERN_CONFIG: () => DEFAULT_PATTERN_CONFIG,
29
+ DEFAULT_TEXT_CURVE: () => DEFAULT_TEXT_CURVE,
28
30
  EventEmitter: () => EventEmitter,
29
31
  FontRegistry: () => FontRegistry,
30
32
  HistoryManager: () => HistoryManager,
@@ -32,16 +34,26 @@ __export(index_exports, {
32
34
  LayerManager: () => LayerManager,
33
35
  LicenseManager: () => LicenseManager,
34
36
  MaskController: () => MaskController,
37
+ MaskPresetManager: () => MaskPresetManager,
35
38
  MaskRefinementError: () => MaskRefinementError,
36
39
  PatternManager: () => PatternManager,
37
40
  ProjectManager: () => ProjectManager,
41
+ SHAPE_MASK_BOX: () => SHAPE_MASK_BOX,
42
+ SHAPE_MASK_IDS: () => SHAPE_MASK_IDS,
38
43
  SnapManager: () => SnapManager,
44
+ TEXTURE_MASK_IDS: () => TEXTURE_MASK_IDS,
45
+ TEXTURE_MASK_SIZE: () => TEXTURE_MASK_SIZE,
46
+ TextCurveManager: () => TextCurveManager,
39
47
  UnitConverter: () => UnitConverter,
48
+ applyAspectLock: () => applyAspectLock,
49
+ applyLayerShadow: () => applyLayerShadow,
40
50
  applyPatternLocks: () => applyPatternLocks,
51
+ buildCurvePathData: () => buildCurvePathData,
41
52
  buildPatternDataURL: () => buildPatternDataURL,
42
53
  captureLocks: () => captureLocks,
43
54
  clamp: () => clamp,
44
55
  clearPatternImageCache: () => clearPatternImageCache,
56
+ clearTextureMaskCache: () => clearTextureMaskCache,
45
57
  computeCoverPlacement: () => computeCoverPlacement,
46
58
  computePrintAreaClip: () => computePrintAreaClip,
47
59
  computeTilePositions: () => computeTilePositions,
@@ -52,19 +64,27 @@ __export(index_exports, {
52
64
  exportDataURL: () => exportDataURL,
53
65
  exportMockup: () => exportMockup,
54
66
  exportPNG: () => exportPNG,
67
+ exportPrintArea: () => exportPrintArea,
55
68
  exportSVG: () => exportSVG,
56
69
  generateId: () => generateId,
57
70
  isCssColor: () => isCssColor,
71
+ isMaskPresetId: () => isMaskPresetId,
72
+ isShapeMaskId: () => isShapeMaskId,
73
+ isTextureMaskId: () => isTextureMaskId,
58
74
  loadPatternImage: () => loadPatternImage,
75
+ readLayerShadow: () => readLayerShadow,
76
+ renderTextureMask: () => renderTextureMask,
77
+ resetTransform: () => resetTransform,
59
78
  restoreLocks: () => restoreLocks,
60
79
  round2: () => round2,
61
80
  sanitizeSvg: () => sanitizeSvg,
62
- serializeEditor: () => serializeEditor
81
+ serializeEditor: () => serializeEditor,
82
+ shapeMaskPathData: () => shapeMaskPathData
63
83
  });
64
84
  module.exports = __toCommonJS(index_exports);
65
85
 
66
86
  // src/editor.ts
67
- var import_fabric6 = require("fabric");
87
+ var import_fabric9 = require("fabric");
68
88
 
69
89
  // src/events.ts
70
90
  var EventEmitter = class {
@@ -284,6 +304,38 @@ var LayerManager = class {
284
304
  this.emitChanged();
285
305
  this.onPropertyChanged?.();
286
306
  }
307
+ /**
308
+ * Patch a layer's host metadata. A key whose value is `undefined` is removed.
309
+ *
310
+ * `meta` is part of `LayerData` and is serialized, so a host that assigns
311
+ * `layer.meta.x` directly gets neither of the two things every other setter
312
+ * here provides: `layers:changed` (so `useLayers()` keeps returning the old
313
+ * meta) and the history checkpoint (so an undo silently reverts the write —
314
+ * the state IS versioned, it was just never committed at the moment it
315
+ * changed). Going through this method is what makes metadata behave like
316
+ * every other layer property.
317
+ */
318
+ setMeta(id, patch) {
319
+ const layer = this.get(id);
320
+ if (!layer) return;
321
+ const next = { ...layer.meta };
322
+ let changed = false;
323
+ for (const [key, value] of Object.entries(patch)) {
324
+ if (value === void 0) {
325
+ if (key in next) {
326
+ delete next[key];
327
+ changed = true;
328
+ }
329
+ continue;
330
+ }
331
+ if (next[key] !== value) changed = true;
332
+ next[key] = value;
333
+ }
334
+ if (!changed) return;
335
+ layer.meta = next;
336
+ this.emitChanged();
337
+ this.onPropertyChanged?.();
338
+ }
287
339
  clear() {
288
340
  for (const layer of this.layers) {
289
341
  this.canvas.remove(layer.fabricObject);
@@ -1142,6 +1194,521 @@ function mod2(n) {
1142
1194
  return (n % 2 + 2) % 2;
1143
1195
  }
1144
1196
 
1197
+ // src/text-curve.ts
1198
+ var import_fabric3 = require("fabric");
1199
+ var DEFAULT_TEXT_CURVE = { arc: 0, wave: 0 };
1200
+ var MIN_ARC = 0.5;
1201
+ var FULL_CIRCLE_ARC = 99.5;
1202
+ var MIN_SWEEP = 0.12;
1203
+ var WAVE_PERIOD_EM = 4.1;
1204
+ var WAVE_AMPLITUDE_EM = 0.9;
1205
+ var WAVE_STEP = 6;
1206
+ var MEASURE_WIDTH = 1e5;
1207
+ var PATH_SLACK = 0.06;
1208
+ function isCurvable(object) {
1209
+ return !!object && typeof object.text === "string";
1210
+ }
1211
+ function measureText(text) {
1212
+ const authored = text.width;
1213
+ try {
1214
+ text.set({ width: MEASURE_WIDTH });
1215
+ text.initDimensions?.();
1216
+ return Math.max(1, text.calcTextWidth?.() ?? text.width ?? 1);
1217
+ } finally {
1218
+ if (authored !== void 0) text.set({ width: authored });
1219
+ text.initDimensions?.();
1220
+ }
1221
+ }
1222
+ function arcPathData(width, arc) {
1223
+ const magnitude = Math.min(100, Math.abs(arc));
1224
+ const direction = arc < 0 ? -1 : 1;
1225
+ const full = magnitude >= FULL_CIRCLE_ARC;
1226
+ const sweep = full ? Math.PI * 2 : Math.max(MIN_SWEEP, magnitude / 100 * Math.PI);
1227
+ const radius = width / sweep;
1228
+ const centerX = width / 2;
1229
+ const point = (angle) => [
1230
+ centerX + radius * Math.sin(angle),
1231
+ direction * radius * (1 - Math.cos(angle))
1232
+ ];
1233
+ const sweepFlag = direction > 0 ? 1 : 0;
1234
+ const format = ([x, y]) => `${round(x)} ${round(y)}`;
1235
+ if (full) {
1236
+ const start2 = point(-Math.PI);
1237
+ const top = point(0);
1238
+ return {
1239
+ data: [
1240
+ `M ${format(start2)}`,
1241
+ `A ${round(radius)} ${round(radius)} 0 0 ${sweepFlag} ${format(top)}`,
1242
+ `A ${round(radius)} ${round(radius)} 0 0 ${sweepFlag} ${format(start2)}`
1243
+ ].join(" "),
1244
+ length: width
1245
+ };
1246
+ }
1247
+ const start = point(-sweep / 2);
1248
+ const end = point(sweep / 2);
1249
+ const largeArc = sweep > Math.PI ? 1 : 0;
1250
+ return {
1251
+ data: `M ${format(start)} A ${round(radius)} ${round(radius)} 0 ${largeArc} ${sweepFlag} ${format(end)}`,
1252
+ // radius = width / sweep, so the arc is exactly `width` long.
1253
+ length: width
1254
+ };
1255
+ }
1256
+ function wavePathData(width, fontSize, wave) {
1257
+ const amplitude = clamp(wave, 0, 100) / 100 * fontSize * WAVE_AMPLITUDE_EM;
1258
+ const period = Math.max(1, fontSize * WAVE_PERIOD_EM);
1259
+ const steps = Math.max(2, Math.ceil(width / WAVE_STEP));
1260
+ const commands = [];
1261
+ let length = 0;
1262
+ let previous = null;
1263
+ for (let index = 0; index <= steps; index++) {
1264
+ const x = width * index / steps;
1265
+ const y = amplitude * Math.sin(x / period * Math.PI * 2);
1266
+ if (previous) length += Math.hypot(x - previous[0], y - previous[1]);
1267
+ previous = [x, y];
1268
+ commands.push(`${index === 0 ? "M" : "L"} ${round(x)} ${round(y)}`);
1269
+ }
1270
+ return { data: commands.join(" "), length };
1271
+ }
1272
+ function round(value) {
1273
+ return Math.round(value * 100) / 100;
1274
+ }
1275
+ function buildCurvePathData(config, width, fontSize) {
1276
+ if (Math.abs(config.arc) >= MIN_ARC) return arcPathData(width, config.arc);
1277
+ if (config.wave > 0) return wavePathData(width, fontSize, config.wave);
1278
+ return null;
1279
+ }
1280
+ function normalize(config) {
1281
+ const arc = clamp(config.arc ?? 0, -100, 100);
1282
+ return { arc, wave: Math.abs(arc) >= MIN_ARC ? 0 : clamp(config.wave ?? 0, 0, 100) };
1283
+ }
1284
+ var TextCurveManager = class {
1285
+ constructor(canvas, layers, history, events) {
1286
+ this.canvas = canvas;
1287
+ this.layers = layers;
1288
+ this.history = history;
1289
+ this.events = events;
1290
+ }
1291
+ canvas;
1292
+ layers;
1293
+ history;
1294
+ events;
1295
+ /** Curve parameters for a layer, or null when it is not curved text. */
1296
+ get(layerId) {
1297
+ const layer = this.layers.get(layerId);
1298
+ if (!layer || !isCurvable(layer.fabricObject)) return null;
1299
+ return layer.meta.curve ?? { ...DEFAULT_TEXT_CURVE };
1300
+ }
1301
+ isCurved(layerId) {
1302
+ const curve = this.get(layerId);
1303
+ return !!curve && (Math.abs(curve.arc) >= MIN_ARC || curve.wave > 0);
1304
+ }
1305
+ /** Apply (or update) the curve on a text layer. Zeroed config clears it. */
1306
+ apply(layerId, config, save = true) {
1307
+ const layer = this.layers.get(layerId);
1308
+ if (!layer || !isCurvable(layer.fabricObject)) return false;
1309
+ const next = normalize(config);
1310
+ const text = layer.fabricObject;
1311
+ const run = measureText(text);
1312
+ const curve = buildCurvePathData(next, run * (1 + PATH_SLACK), text.fontSize);
1313
+ if (!curve) {
1314
+ this.detach(text, layer.meta.curveWidth);
1315
+ delete layer.meta.curve;
1316
+ delete layer.meta.curveWidth;
1317
+ } else {
1318
+ if (layer.meta.curveWidth === void 0) layer.meta.curveWidth = text.width ?? 0;
1319
+ text.set({ width: Math.max(layer.meta.curveWidth, run + 2) });
1320
+ text.set({
1321
+ path: new import_fabric3.Path(curve.data, { visible: false, objectCaching: false }),
1322
+ pathAlign: "center",
1323
+ pathSide: "left",
1324
+ pathStartOffset: Math.max(0, (curve.length - run) / 2)
1325
+ });
1326
+ layer.meta.curve = next;
1327
+ }
1328
+ text.initDimensions?.();
1329
+ text.setCoords();
1330
+ text.dirty = true;
1331
+ this.canvas.requestRenderAll();
1332
+ this.events.emit("layer:modified", { layerId });
1333
+ if (save) this.history.save();
1334
+ return true;
1335
+ }
1336
+ /** Remove the curve, restoring the authored text box width. */
1337
+ clear(layerId, save = true) {
1338
+ return this.apply(layerId, DEFAULT_TEXT_CURVE, save);
1339
+ }
1340
+ /**
1341
+ * Rebuild the path from the stored parameters. Text content, font family and
1342
+ * font size all change the run's width, and the path is sized to that width —
1343
+ * without this the curve keeps the geometry of the text it was created from.
1344
+ */
1345
+ refresh(layerId, save = false) {
1346
+ const curve = this.layers.get(layerId)?.meta.curve;
1347
+ if (!curve) return false;
1348
+ return this.apply(layerId, curve, save);
1349
+ }
1350
+ /** Rebuild every curved layer — used after a state restore. */
1351
+ refreshAll() {
1352
+ for (const layer of this.layers.getAll()) {
1353
+ if (layer.meta.curve) this.refresh(layer.id);
1354
+ }
1355
+ }
1356
+ detach(text, authoredWidth) {
1357
+ text.set({ path: null, pathStartOffset: 0 });
1358
+ if (authoredWidth !== void 0 && authoredWidth > 0) text.set({ width: authoredWidth });
1359
+ }
1360
+ };
1361
+
1362
+ // src/mask-presets/manager.ts
1363
+ var import_fabric4 = require("fabric");
1364
+
1365
+ // src/mask-presets/shapes.ts
1366
+ var SHAPE_MASK_IDS = [
1367
+ "circle",
1368
+ "square",
1369
+ "triangle",
1370
+ "star",
1371
+ "heart",
1372
+ "octagram",
1373
+ "arch",
1374
+ "zigzag"
1375
+ ];
1376
+ function isShapeMaskId(value) {
1377
+ return typeof value === "string" && SHAPE_MASK_IDS.includes(value);
1378
+ }
1379
+ function starPoints(points, outer, inner, start = -Math.PI / 2) {
1380
+ const step = Math.PI / points;
1381
+ const coordinates = [];
1382
+ for (let index = 0; index < points * 2; index++) {
1383
+ const radius = index % 2 === 0 ? outer : inner;
1384
+ const angle = start + index * step;
1385
+ const x = 50 + Math.cos(angle) * radius;
1386
+ const y = 50 + Math.sin(angle) * radius;
1387
+ coordinates.push(`${index === 0 ? "M" : "L"} ${round3(x)} ${round3(y)}`);
1388
+ }
1389
+ return `${coordinates.join(" ")} Z`;
1390
+ }
1391
+ function round3(value) {
1392
+ return Math.round(value * 100) / 100;
1393
+ }
1394
+ var SHAPE_PATHS = {
1395
+ circle: "M 96 50 A 46 46 0 1 1 4 50 A 46 46 0 1 1 96 50 Z",
1396
+ square: "M 4 4 H 96 V 96 H 4 Z",
1397
+ triangle: "M 50 4 L 96 96 L 4 96 Z",
1398
+ star: starPoints(5, 46, 22),
1399
+ heart: "M 50 96 C -8 55 12 4 50 28 C 88 4 108 55 50 96 Z",
1400
+ octagram: starPoints(8, 46, 27),
1401
+ arch: "M 4 96 V 50 A 46 46 0 0 1 96 50 V 96 Z",
1402
+ zigzag: starPoints(16, 46, 39)
1403
+ };
1404
+ function shapeMaskPathData(id) {
1405
+ return SHAPE_PATHS[id];
1406
+ }
1407
+ var SHAPE_MASK_BOX = 100;
1408
+
1409
+ // src/mask-presets/textures.ts
1410
+ var TEXTURE_MASK_IDS = [
1411
+ "vignette",
1412
+ "halftone",
1413
+ "spray",
1414
+ "grunge",
1415
+ "torn",
1416
+ "band"
1417
+ ];
1418
+ function isTextureMaskId(value) {
1419
+ return typeof value === "string" && TEXTURE_MASK_IDS.includes(value);
1420
+ }
1421
+ var TEXTURE_MASK_SIZE = 320;
1422
+ var cache = /* @__PURE__ */ new Map();
1423
+ function seeded(seed) {
1424
+ let state = seed;
1425
+ return () => {
1426
+ state = state * 16807 % 2147483647;
1427
+ return (state - 1) / 2147483646;
1428
+ };
1429
+ }
1430
+ function traceRoughEdge(context, random, jitter, inset) {
1431
+ const size = TEXTURE_MASK_SIZE;
1432
+ const corners = [
1433
+ [inset, inset],
1434
+ [size - inset, inset],
1435
+ [size - inset, size - inset],
1436
+ [inset, size - inset]
1437
+ ];
1438
+ context.beginPath();
1439
+ for (let corner = 0; corner < corners.length; corner++) {
1440
+ const [fromX, fromY] = corners[corner];
1441
+ const [toX, toY] = corners[(corner + 1) % corners.length];
1442
+ for (let step = 0; step <= 26; step++) {
1443
+ const ratio = step / 26;
1444
+ const x = fromX + (toX - fromX) * ratio + (random() - 0.5) * jitter;
1445
+ const y = fromY + (toY - fromY) * ratio + (random() - 0.5) * jitter;
1446
+ if (corner === 0 && step === 0) context.moveTo(x, y);
1447
+ else context.lineTo(x, y);
1448
+ }
1449
+ }
1450
+ context.closePath();
1451
+ context.fill();
1452
+ }
1453
+ function paint(id, context) {
1454
+ const size = TEXTURE_MASK_SIZE;
1455
+ const center = size / 2;
1456
+ context.fillStyle = "#ffffff";
1457
+ switch (id) {
1458
+ case "vignette": {
1459
+ const gradient = context.createRadialGradient(
1460
+ center,
1461
+ center,
1462
+ size * 0.18,
1463
+ center,
1464
+ center,
1465
+ size * 0.52
1466
+ );
1467
+ gradient.addColorStop(0, "rgba(255,255,255,1)");
1468
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
1469
+ context.fillStyle = gradient;
1470
+ context.fillRect(0, 0, size, size);
1471
+ return;
1472
+ }
1473
+ case "band": {
1474
+ const gradient = context.createLinearGradient(0, 0, 0, size);
1475
+ gradient.addColorStop(0, "rgba(255,255,255,0)");
1476
+ gradient.addColorStop(0.25, "rgba(255,255,255,1)");
1477
+ gradient.addColorStop(0.75, "rgba(255,255,255,1)");
1478
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
1479
+ context.fillStyle = gradient;
1480
+ context.fillRect(0, 0, size, size);
1481
+ return;
1482
+ }
1483
+ case "halftone": {
1484
+ const pitch = 16;
1485
+ for (let y = pitch / 2; y < size; y += pitch) {
1486
+ for (let x = pitch / 2; x < size; x += pitch) {
1487
+ const distance = Math.hypot(x - center, y - center) / (size * 0.52);
1488
+ const radius = Math.max(0, pitch / 2 * (1 - distance) * 1.15);
1489
+ if (radius < 0.4) continue;
1490
+ context.beginPath();
1491
+ context.arc(x, y, radius, 0, Math.PI * 2);
1492
+ context.fill();
1493
+ }
1494
+ }
1495
+ return;
1496
+ }
1497
+ case "spray": {
1498
+ const random = seeded(42);
1499
+ const core = context.createRadialGradient(center, center, 10, center, center, size * 0.42);
1500
+ core.addColorStop(0, "rgba(255,255,255,1)");
1501
+ core.addColorStop(1, "rgba(255,255,255,0.85)");
1502
+ context.fillStyle = core;
1503
+ context.beginPath();
1504
+ context.arc(center, center, size * 0.42, 0, Math.PI * 2);
1505
+ context.fill();
1506
+ context.fillStyle = "rgba(255,255,255,0.9)";
1507
+ for (let dot = 0; dot < 900; dot++) {
1508
+ const angle = random() * Math.PI * 2;
1509
+ const distance = size * (0.3 + random() * 0.24);
1510
+ const x = center + Math.cos(angle) * distance;
1511
+ const y = center + Math.sin(angle) * distance;
1512
+ context.globalAlpha = 1 - (distance / size - 0.3) / 0.24;
1513
+ context.beginPath();
1514
+ context.arc(x, y, random() * 2.4, 0, Math.PI * 2);
1515
+ context.fill();
1516
+ }
1517
+ context.globalAlpha = 1;
1518
+ return;
1519
+ }
1520
+ case "grunge": {
1521
+ const random = seeded(7);
1522
+ traceRoughEdge(context, random, 14, size * 0.06);
1523
+ context.globalCompositeOperation = "destination-out";
1524
+ for (let speckle = 0; speckle < 240; speckle++) {
1525
+ const x = random() * size;
1526
+ const y = random() * size;
1527
+ const nearEdge = Math.min(x, y, size - x, size - y) < size * 0.12;
1528
+ if (!nearEdge && random() >= 0.12) continue;
1529
+ context.beginPath();
1530
+ context.arc(x, y, random() * 6 + 1, 0, Math.PI * 2);
1531
+ context.fill();
1532
+ }
1533
+ context.globalCompositeOperation = "source-over";
1534
+ return;
1535
+ }
1536
+ case "torn": {
1537
+ traceRoughEdge(context, seeded(99), 10, size * 0.08);
1538
+ return;
1539
+ }
1540
+ }
1541
+ }
1542
+ function renderTextureMask(id) {
1543
+ const cached = cache.get(id);
1544
+ if (cached) return cached;
1545
+ if (typeof document === "undefined") {
1546
+ throw new Error("Texture masks require a DOM canvas");
1547
+ }
1548
+ const canvas = document.createElement("canvas");
1549
+ canvas.width = TEXTURE_MASK_SIZE;
1550
+ canvas.height = TEXTURE_MASK_SIZE;
1551
+ const context = canvas.getContext("2d");
1552
+ if (!context) throw new Error("Texture masks require a 2D canvas context");
1553
+ paint(id, context);
1554
+ cache.set(id, canvas);
1555
+ return canvas;
1556
+ }
1557
+ function clearTextureMaskCache() {
1558
+ cache.clear();
1559
+ }
1560
+
1561
+ // src/mask-presets/manager.ts
1562
+ function isMaskPresetId(value) {
1563
+ return isShapeMaskId(value) || isTextureMaskId(value);
1564
+ }
1565
+ var MaskPresetManager = class {
1566
+ constructor(canvas, layers, history, events) {
1567
+ this.canvas = canvas;
1568
+ this.layers = layers;
1569
+ this.history = history;
1570
+ this.events = events;
1571
+ }
1572
+ canvas;
1573
+ layers;
1574
+ history;
1575
+ events;
1576
+ get(layerId) {
1577
+ return this.layers.get(layerId)?.meta.maskPreset ?? null;
1578
+ }
1579
+ /** Clip the layer to `id`. Passing null (or an unknown id) clears the clip. */
1580
+ apply(layerId, id, save = true) {
1581
+ const layer = this.layers.get(layerId);
1582
+ if (!layer) return false;
1583
+ const object = layer.fabricObject;
1584
+ if (id === null) {
1585
+ if (!layer.meta.maskPreset) return false;
1586
+ object.clipPath = void 0;
1587
+ delete layer.meta.maskPreset;
1588
+ } else {
1589
+ if (!isMaskPresetId(id)) return false;
1590
+ object.clipPath = this.buildClip(id, object);
1591
+ layer.meta.maskPreset = id;
1592
+ }
1593
+ object.dirty = true;
1594
+ object.setCoords();
1595
+ this.canvas.requestRenderAll();
1596
+ this.events.emit("layer:modified", { layerId });
1597
+ if (save) this.history.save();
1598
+ return true;
1599
+ }
1600
+ clear(layerId, save = true) {
1601
+ return this.apply(layerId, null, save);
1602
+ }
1603
+ /**
1604
+ * Re-fit the clip to the layer's current size. The clip is built for the
1605
+ * object's dimensions at the time it was applied; editing text or replacing an
1606
+ * image changes them, and a stale clip would crop the wrong region.
1607
+ */
1608
+ refresh(layerId, save = false) {
1609
+ const id = this.get(layerId);
1610
+ if (!id) return false;
1611
+ return this.apply(layerId, id, save);
1612
+ }
1613
+ /** Re-fit every masked layer — used after a state restore. */
1614
+ refreshAll() {
1615
+ for (const layer of this.layers.getAll()) {
1616
+ if (layer.meta.maskPreset) this.refresh(layer.id);
1617
+ }
1618
+ }
1619
+ buildClip(id, object) {
1620
+ const width = Math.max(1, object.width ?? 1);
1621
+ const height = Math.max(1, object.height ?? 1);
1622
+ const shared = {
1623
+ originX: "center",
1624
+ originY: "center",
1625
+ left: 0,
1626
+ top: 0,
1627
+ objectCaching: false
1628
+ };
1629
+ if (isShapeMaskId(id)) {
1630
+ return new import_fabric4.Path(shapeMaskPathData(id), {
1631
+ ...shared,
1632
+ scaleX: width / SHAPE_MASK_BOX,
1633
+ scaleY: height / SHAPE_MASK_BOX
1634
+ });
1635
+ }
1636
+ return new import_fabric4.FabricImage(renderTextureMask(id), {
1637
+ ...shared,
1638
+ scaleX: width / TEXTURE_MASK_SIZE,
1639
+ scaleY: height / TEXTURE_MASK_SIZE
1640
+ });
1641
+ }
1642
+ };
1643
+
1644
+ // src/shadow.ts
1645
+ var import_fabric5 = require("fabric");
1646
+
1647
+ // src/utils/color.ts
1648
+ var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
1649
+ function isCssColor(value, allowEmpty = false) {
1650
+ if (typeof value !== "string") return false;
1651
+ const trimmed = value.trim();
1652
+ if (!trimmed) return allowEmpty;
1653
+ return CSS_COLOR.test(trimmed);
1654
+ }
1655
+
1656
+ // src/shadow.ts
1657
+ var DEFAULT_LAYER_SHADOW = {
1658
+ enabled: false,
1659
+ color: "#000000",
1660
+ blur: 12,
1661
+ offsetX: 6,
1662
+ offsetY: 6
1663
+ };
1664
+ function readLayerShadow(object) {
1665
+ const shadow = object.shadow;
1666
+ if (!shadow || typeof shadow === "string") return { ...DEFAULT_LAYER_SHADOW };
1667
+ return {
1668
+ enabled: true,
1669
+ color: typeof shadow.color === "string" ? shadow.color : DEFAULT_LAYER_SHADOW.color,
1670
+ blur: shadow.blur ?? DEFAULT_LAYER_SHADOW.blur,
1671
+ offsetX: shadow.offsetX ?? DEFAULT_LAYER_SHADOW.offsetX,
1672
+ offsetY: shadow.offsetY ?? DEFAULT_LAYER_SHADOW.offsetY
1673
+ };
1674
+ }
1675
+ function applyLayerShadow(object, config) {
1676
+ const next = { ...readLayerShadow(object), ...config };
1677
+ if (!next.enabled) {
1678
+ object.set({ shadow: null });
1679
+ return;
1680
+ }
1681
+ const color = isCssColor(next.color) ? next.color : DEFAULT_LAYER_SHADOW.color;
1682
+ object.set({
1683
+ shadow: new import_fabric5.Shadow({
1684
+ color,
1685
+ blur: Math.max(0, next.blur),
1686
+ offsetX: next.offsetX,
1687
+ offsetY: next.offsetY
1688
+ })
1689
+ });
1690
+ }
1691
+
1692
+ // src/transform.ts
1693
+ var SIDE_CONTROLS = ["ml", "mr", "mt", "mb"];
1694
+ function applyAspectLock(object, locked) {
1695
+ for (const control of SIDE_CONTROLS) {
1696
+ object.setControlVisible(control, !locked);
1697
+ }
1698
+ }
1699
+ function resetTransform(object) {
1700
+ object.set({
1701
+ scaleX: 1,
1702
+ scaleY: 1,
1703
+ angle: 0,
1704
+ skewX: 0,
1705
+ skewY: 0,
1706
+ flipX: false,
1707
+ flipY: false
1708
+ });
1709
+ object.setCoords();
1710
+ }
1711
+
1145
1712
  // src/utils/units.ts
1146
1713
  var MM_PER_INCH = 25.4;
1147
1714
  var UnitConverter = class {
@@ -1190,18 +1757,7 @@ var UnitConverter = class {
1190
1757
  };
1191
1758
 
1192
1759
  // src/serialization.ts
1193
- var import_fabric3 = require("fabric");
1194
-
1195
- // src/utils/color.ts
1196
- var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
1197
- function isCssColor(value, allowEmpty = false) {
1198
- if (typeof value !== "string") return false;
1199
- const trimmed = value.trim();
1200
- if (!trimmed) return allowEmpty;
1201
- return CSS_COLOR.test(trimmed);
1202
- }
1203
-
1204
- // src/serialization.ts
1760
+ var import_fabric6 = require("fabric");
1205
1761
  var VERSION = "2.0.0";
1206
1762
  function serializeEditor(editor) {
1207
1763
  return {
@@ -1237,7 +1793,7 @@ async function deserializeEditor(editor, state) {
1237
1793
  }
1238
1794
  const staged = await Promise.all(
1239
1795
  state.layers.map(async (serialized) => {
1240
- const fabricObject = (await import_fabric3.util.enlivenObjects([serialized.fabricObject]))[0];
1796
+ const fabricObject = (await import_fabric6.util.enlivenObjects([serialized.fabricObject]))[0];
1241
1797
  if (!fabricObject) {
1242
1798
  const source = serialized.fabricObject.src;
1243
1799
  if (typeof source === "string" && source.startsWith("blob:")) {
@@ -1248,7 +1804,7 @@ async function deserializeEditor(editor, state) {
1248
1804
  return { serialized, fabricObject };
1249
1805
  })
1250
1806
  );
1251
- const stagedBackground = state.backgroundImage ? (await import_fabric3.util.enlivenObjects([state.backgroundImage]))[0] : null;
1807
+ const stagedBackground = state.backgroundImage ? (await import_fabric6.util.enlivenObjects([state.backgroundImage]))[0] : null;
1252
1808
  if (state.backgroundImage && !stagedBackground) {
1253
1809
  const source = state.backgroundImage.src;
1254
1810
  if (typeof source === "string" && source.startsWith("blob:")) {
@@ -1280,6 +1836,7 @@ function restoreLayer(editor, serialized, fabricObject) {
1280
1836
  const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);
1281
1837
  if (serialized.meta) {
1282
1838
  layer.meta = serialized.meta;
1839
+ if (layer.meta.lockAspect) applyAspectLock(fabricObject, true);
1283
1840
  }
1284
1841
  if (!serialized.visible) {
1285
1842
  editor.layers.setVisibility(layer.id, false);
@@ -1294,7 +1851,7 @@ function restoreLayer(editor, serialized, fabricObject) {
1294
1851
  }
1295
1852
 
1296
1853
  // src/export.ts
1297
- var import_fabric4 = require("fabric");
1854
+ var import_fabric7 = require("fabric");
1298
1855
 
1299
1856
  // src/displacement.ts
1300
1857
  var CHANNEL_INDEX = {
@@ -1388,7 +1945,7 @@ async function exportPNG(canvas, options = {}) {
1388
1945
  }
1389
1946
  async function exportIsolatedPNG(source, objects, options = {}) {
1390
1947
  const element = source.lowerCanvasEl.ownerDocument.createElement("canvas");
1391
- const canvas = new import_fabric4.StaticCanvas(element, {
1948
+ const canvas = new import_fabric7.StaticCanvas(element, {
1392
1949
  width: options.width ?? source.getWidth(),
1393
1950
  height: options.height ?? source.getHeight(),
1394
1951
  backgroundColor: options.backgroundColor || void 0
@@ -1403,6 +1960,38 @@ async function exportIsolatedPNG(source, objects, options = {}) {
1403
1960
  canvas.dispose();
1404
1961
  }
1405
1962
  }
1963
+ async function exportPrintArea(source, area, options = {}) {
1964
+ const { multiplier = 1, format = "png", quality = 1 } = options;
1965
+ const width = source.getWidth();
1966
+ const height = source.getHeight();
1967
+ const clip = computePrintAreaClip(
1968
+ area,
1969
+ multiplier,
1970
+ multiplier,
1971
+ width * multiplier,
1972
+ height * multiplier
1973
+ );
1974
+ if (clip.width <= 0 || clip.height <= 0) {
1975
+ throw new Error("Print area does not overlap the canvas");
1976
+ }
1977
+ const element = source.lowerCanvasEl.ownerDocument.createElement("canvas");
1978
+ const canvas = new import_fabric7.StaticCanvas(element, { width, height });
1979
+ try {
1980
+ const clones = await Promise.all(source.getObjects().map((object) => object.clone()));
1981
+ if (clones.length) canvas.add(...clones);
1982
+ canvas.requestRenderAll();
1983
+ const rendered = canvas.toCanvasElement(multiplier);
1984
+ const output = rendered.ownerDocument.createElement("canvas");
1985
+ output.width = Math.max(1, Math.round(clip.width));
1986
+ output.height = Math.max(1, Math.round(clip.height));
1987
+ const context = output.getContext("2d");
1988
+ if (!context) throw new Error("2D canvas context is unavailable");
1989
+ context.drawImage(rendered, -clip.left, -clip.top);
1990
+ return await canvasElementToBlob(output, format, quality);
1991
+ } finally {
1992
+ canvas.dispose();
1993
+ }
1994
+ }
1406
1995
  async function exportMockup(canvas, mockup, options = {}) {
1407
1996
  const { multiplier = 1, format = "png", quality = 1 } = options;
1408
1997
  const design = canvas.toCanvasElement(multiplier);
@@ -1826,7 +2415,7 @@ var ProjectManager = class {
1826
2415
  };
1827
2416
 
1828
2417
  // src/mask.ts
1829
- var import_fabric5 = require("fabric");
2418
+ var import_fabric8 = require("fabric");
1830
2419
  var MaskRefinementError = class extends Error {
1831
2420
  constructor(code, message, cause) {
1832
2421
  super(message);
@@ -1858,7 +2447,7 @@ var MaskController = class {
1858
2447
  throw new Error("Mask dimensions must be positive integers");
1859
2448
  }
1860
2449
  const backing = this.makeCanvas(width, height);
1861
- const image = new import_fabric5.FabricImage(backing, {
2450
+ const image = new import_fabric8.FabricImage(backing, {
1862
2451
  left: 0,
1863
2452
  top: 0,
1864
2453
  originX: "left",
@@ -2098,6 +2687,8 @@ var CanvasEditor = class {
2098
2687
  snapping;
2099
2688
  crop;
2100
2689
  patterns;
2690
+ curves;
2691
+ maskPresets;
2101
2692
  fonts;
2102
2693
  licensing;
2103
2694
  pages;
@@ -2121,7 +2712,7 @@ var CanvasEditor = class {
2121
2712
  const widthPx = this.units.toPixels(config.width);
2122
2713
  const heightPx = this.units.toPixels(config.height);
2123
2714
  this.designBackground = config.backgroundColor ?? "#ffffff";
2124
- this.canvas = new import_fabric6.Canvas(canvasElement, {
2715
+ this.canvas = new import_fabric9.Canvas(canvasElement, {
2125
2716
  width: widthPx,
2126
2717
  height: heightPx,
2127
2718
  backgroundColor: this.designBackground,
@@ -2149,6 +2740,8 @@ var CanvasEditor = class {
2149
2740
  this.events,
2150
2741
  config.patternSourceResolver
2151
2742
  );
2743
+ this.curves = new TextCurveManager(this.canvas, this.layers, this.history, this.events);
2744
+ this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
2152
2745
  this.setupCanvasEvents();
2153
2746
  this.history.saveImmediate();
2154
2747
  this.pages = new ProjectManager(this);
@@ -2157,7 +2750,7 @@ var CanvasEditor = class {
2157
2750
  // ─── Layer Operations ────────────────────────────────
2158
2751
  async addImage(url, options) {
2159
2752
  try {
2160
- const img = await import_fabric6.FabricImage.fromURL(
2753
+ const img = await import_fabric9.FabricImage.fromURL(
2161
2754
  url,
2162
2755
  {},
2163
2756
  { originX: "left", originY: "top", ...options }
@@ -2182,7 +2775,7 @@ var CanvasEditor = class {
2182
2775
  if (this.crop.activeLayerId() === layerId) this.crop.cancel();
2183
2776
  const previous = layer.fabricObject;
2184
2777
  try {
2185
- const replacement = await import_fabric6.FabricImage.fromURL(url, {}, { originX: "left", originY: "top" });
2778
+ const replacement = await import_fabric9.FabricImage.fromURL(url, {}, { originX: "left", originY: "top" });
2186
2779
  replacement.set({
2187
2780
  left: previous.left,
2188
2781
  top: previous.top,
@@ -2211,7 +2804,7 @@ var CanvasEditor = class {
2211
2804
  }
2212
2805
  }
2213
2806
  addText(text, options) {
2214
- const textbox = new import_fabric6.Textbox(text, {
2807
+ const textbox = new import_fabric9.Textbox(text, {
2215
2808
  fontSize: 32,
2216
2809
  fontFamily: "Arial",
2217
2810
  fill: "#000000",
@@ -2250,10 +2843,10 @@ var CanvasEditor = class {
2250
2843
  return value === void 0 ? token : escapeXml(value);
2251
2844
  })
2252
2845
  );
2253
- const { objects, options } = await (0, import_fabric6.loadSVGFromString)(resolved);
2846
+ const { objects, options } = await (0, import_fabric9.loadSVGFromString)(resolved);
2254
2847
  const validObjects = objects.filter((object) => object !== null);
2255
2848
  if (validObjects.length === 0) throw new Error("Template SVG contains no renderable objects");
2256
- const group = import_fabric6.util.groupSVGElements(validObjects, options);
2849
+ const group = import_fabric9.util.groupSVGElements(validObjects, options);
2257
2850
  group.set({
2258
2851
  left: this.canvas.getWidth() / 2,
2259
2852
  top: this.canvas.getHeight() / 2,
@@ -2326,10 +2919,10 @@ var CanvasEditor = class {
2326
2919
  const next = { ...previous, ...adjustments };
2327
2920
  const clampAdjustment = (value, min = -1) => clamp(value ?? 0, min, 1);
2328
2921
  image.filters = [
2329
- new import_fabric6.filters.Brightness({ brightness: clampAdjustment(next.brightness) }),
2330
- new import_fabric6.filters.Contrast({ contrast: clampAdjustment(next.contrast) }),
2331
- new import_fabric6.filters.Saturation({ saturation: clampAdjustment(next.saturation) }),
2332
- new import_fabric6.filters.Blur({ blur: clampAdjustment(next.blur, 0) })
2922
+ new import_fabric9.filters.Brightness({ brightness: clampAdjustment(next.brightness) }),
2923
+ new import_fabric9.filters.Contrast({ contrast: clampAdjustment(next.contrast) }),
2924
+ new import_fabric9.filters.Saturation({ saturation: clampAdjustment(next.saturation) }),
2925
+ new import_fabric9.filters.Blur({ blur: clampAdjustment(next.blur, 0) })
2333
2926
  ];
2334
2927
  layer.meta.imageAdjustments = next;
2335
2928
  image.applyFilters();
@@ -2346,7 +2939,7 @@ var CanvasEditor = class {
2346
2939
  const childData = children.map((layer) => structuredClone(layer.toData()));
2347
2940
  const objects = children.map((layer) => layer.fabricObject);
2348
2941
  for (const layer of children) this.layers.remove(layer.id);
2349
- const group = new import_fabric6.Group(objects);
2942
+ const group = new import_fabric9.Group(objects);
2350
2943
  const grouped = this.layers.add("group", group, name);
2351
2944
  grouped.meta.groupChildren = childData;
2352
2945
  this.layers.select(grouped.id);
@@ -2365,7 +2958,7 @@ var CanvasEditor = class {
2365
2958
  const objects = group.removeAll();
2366
2959
  this.layers.remove(id);
2367
2960
  const restored = objects.map((object, index) => {
2368
- import_fabric6.util.addTransformToObject(object, transform);
2961
+ import_fabric9.util.addTransformToObject(object, transform);
2369
2962
  object.setCoords();
2370
2963
  const data = childData[index];
2371
2964
  const layer = this.layers.add(data?.type ?? "group", object, data?.name, data?.id);
@@ -2421,7 +3014,7 @@ var CanvasEditor = class {
2421
3014
  const layer = this.layers.get(id);
2422
3015
  if (!layer) throw new Error(`Layer not found: ${id}`);
2423
3016
  try {
2424
- if (options.resolution === "source" && layer.fabricObject instanceof import_fabric6.FabricImage) {
3017
+ if (options.resolution === "source" && layer.fabricObject instanceof import_fabric9.FabricImage) {
2425
3018
  const image = await layer.fabricObject.clone();
2426
3019
  image.set({
2427
3020
  left: 0,
@@ -2505,6 +3098,23 @@ var CanvasEditor = class {
2505
3098
  toDataURL(format = "png", multiplier = 1) {
2506
3099
  return this.withDesignBackground(() => exportDataURL(this.canvas, format, multiplier));
2507
3100
  }
3101
+ /**
3102
+ * Export the print file: the design cropped to the mockup's print area, on
3103
+ * transparency. Without a print area this is the whole canvas, still
3104
+ * transparent — a print file never carries the design background.
3105
+ */
3106
+ async toPrintFile(options = {}) {
3107
+ await this.fonts.ready();
3108
+ try {
3109
+ const area = this.mockup?.printArea;
3110
+ const blob = area ? await exportPrintArea(this.canvas, area, options) : await exportIsolatedPNG(this.canvas, this.canvas.getObjects(), options);
3111
+ this.licensing.track(`export:print:${options.format ?? "png"}`);
3112
+ return blob;
3113
+ } catch (error) {
3114
+ this.events.emit("error", { message: "Failed to export print file", error });
3115
+ throw error;
3116
+ }
3117
+ }
2508
3118
  /** Export the current product-preview composite. Advanced warping is host-defined. */
2509
3119
  async toMockupImage(options = {}) {
2510
3120
  if (!this.mockup) throw new Error("No mockup is configured");
@@ -2676,7 +3286,7 @@ var CanvasEditor = class {
2676
3286
  return;
2677
3287
  }
2678
3288
  try {
2679
- const image = await import_fabric6.FabricImage.fromURL(
3289
+ const image = await import_fabric9.FabricImage.fromURL(
2680
3290
  url,
2681
3291
  { crossOrigin: options.crossOrigin ?? null, signal: options.signal },
2682
3292
  { originX: "left", originY: "top" }
@@ -2851,14 +3461,89 @@ var CanvasEditor = class {
2851
3461
  clearPattern(layerId) {
2852
3462
  return this.patterns.disable(layerId);
2853
3463
  }
3464
+ // ─── Text curve ─────────────────────────────────────
3465
+ applyTextCurve(layerId, config) {
3466
+ return this.curves.apply(layerId, config);
3467
+ }
3468
+ clearTextCurve(layerId) {
3469
+ return this.curves.clear(layerId);
3470
+ }
3471
+ getTextCurve(layerId) {
3472
+ return this.curves.get(layerId);
3473
+ }
3474
+ // ─── Mask presets ───────────────────────────────────
3475
+ applyMaskPreset(layerId, id) {
3476
+ return this.maskPresets.apply(layerId, id);
3477
+ }
3478
+ clearMaskPreset(layerId) {
3479
+ return this.maskPresets.clear(layerId);
3480
+ }
3481
+ getMaskPreset(layerId) {
3482
+ return this.maskPresets.get(layerId);
3483
+ }
3484
+ // ─── Layer transform ────────────────────────────────
3485
+ /**
3486
+ * Constrain a layer to its current proportions. Persisted on the layer so a
3487
+ * reopened design still resizes the way it was set up to.
3488
+ */
3489
+ setLayerAspectLock(layerId, locked) {
3490
+ const layer = this.layers.get(layerId);
3491
+ if (!layer) return false;
3492
+ applyAspectLock(layer.fabricObject, locked);
3493
+ if (locked) layer.meta.lockAspect = true;
3494
+ else delete layer.meta.lockAspect;
3495
+ this.canvas.requestRenderAll();
3496
+ this.events.emit("layer:modified", { layerId });
3497
+ this.history.save();
3498
+ return true;
3499
+ }
3500
+ getLayerAspectLock(layerId) {
3501
+ return this.layers.get(layerId)?.meta.lockAspect === true;
3502
+ }
3503
+ /** Re-apply every stored aspect lock — control visibility is not serialized. */
3504
+ restoreAspectLocks() {
3505
+ for (const layer of this.layers.getAll()) {
3506
+ if (layer.meta.lockAspect) applyAspectLock(layer.fabricObject, true);
3507
+ }
3508
+ }
3509
+ /** Drop scale, rotation, skew and flips; the layer stays where it is. */
3510
+ resetLayerTransform(layerId) {
3511
+ const layer = this.layers.get(layerId);
3512
+ if (!layer) return false;
3513
+ resetTransform(layer.fabricObject);
3514
+ this.canvas.requestRenderAll();
3515
+ this.events.emit("layer:modified", { layerId });
3516
+ this.history.save();
3517
+ return true;
3518
+ }
3519
+ // ─── Shadow ─────────────────────────────────────────
3520
+ setLayerShadow(layerId, config) {
3521
+ const layer = this.layers.get(layerId);
3522
+ if (!layer) return false;
3523
+ applyLayerShadow(layer.fabricObject, config);
3524
+ layer.fabricObject.dirty = true;
3525
+ this.canvas.requestRenderAll();
3526
+ this.events.emit("layer:modified", { layerId });
3527
+ this.history.save();
3528
+ return true;
3529
+ }
3530
+ getLayerShadow(layerId) {
3531
+ const layer = this.layers.get(layerId);
3532
+ return layer ? readLayerShadow(layer.fabricObject) : null;
3533
+ }
2854
3534
  // ─── Mockup (preview-only) ──────────────────────────
2855
- setMockup(mockup) {
3535
+ /**
3536
+ * Show (or clear) the product preview. Pass `history: false` for preview-only
3537
+ * changes such as swapping a colourway — those are not design edits and
3538
+ * should not fill the undo stack.
3539
+ */
3540
+ setMockup(mockup, options = {}) {
2856
3541
  this.mockup = mockup;
2857
3542
  this.canvas.backgroundColor = mockup ? "" : this.designBackground;
2858
3543
  this.canvas.backgroundImage = mockup ? void 0 : this.designBackgroundImage ?? void 0;
2859
3544
  this.canvas.requestRenderAll();
2860
3545
  this.events.emit("mockup:changed", { mockup });
2861
- this.history.save();
3546
+ if (options.history !== false) this.history.save();
2862
3547
  }
2863
3548
  clearMockup() {
2864
3549
  this.setMockup(null);
@@ -2970,7 +3655,9 @@ var AnnotationOverlay = class {
2970
3655
  CANVAS_SIZE_PRESETS,
2971
3656
  CanvasEditor,
2972
3657
  CropController,
3658
+ DEFAULT_LAYER_SHADOW,
2973
3659
  DEFAULT_PATTERN_CONFIG,
3660
+ DEFAULT_TEXT_CURVE,
2974
3661
  EventEmitter,
2975
3662
  FontRegistry,
2976
3663
  HistoryManager,
@@ -2978,16 +3665,26 @@ var AnnotationOverlay = class {
2978
3665
  LayerManager,
2979
3666
  LicenseManager,
2980
3667
  MaskController,
3668
+ MaskPresetManager,
2981
3669
  MaskRefinementError,
2982
3670
  PatternManager,
2983
3671
  ProjectManager,
3672
+ SHAPE_MASK_BOX,
3673
+ SHAPE_MASK_IDS,
2984
3674
  SnapManager,
3675
+ TEXTURE_MASK_IDS,
3676
+ TEXTURE_MASK_SIZE,
3677
+ TextCurveManager,
2985
3678
  UnitConverter,
3679
+ applyAspectLock,
3680
+ applyLayerShadow,
2986
3681
  applyPatternLocks,
3682
+ buildCurvePathData,
2987
3683
  buildPatternDataURL,
2988
3684
  captureLocks,
2989
3685
  clamp,
2990
3686
  clearPatternImageCache,
3687
+ clearTextureMaskCache,
2991
3688
  computeCoverPlacement,
2992
3689
  computePrintAreaClip,
2993
3690
  computeTilePositions,
@@ -2998,13 +3695,21 @@ var AnnotationOverlay = class {
2998
3695
  exportDataURL,
2999
3696
  exportMockup,
3000
3697
  exportPNG,
3698
+ exportPrintArea,
3001
3699
  exportSVG,
3002
3700
  generateId,
3003
3701
  isCssColor,
3702
+ isMaskPresetId,
3703
+ isShapeMaskId,
3704
+ isTextureMaskId,
3004
3705
  loadPatternImage,
3706
+ readLayerShadow,
3707
+ renderTextureMask,
3708
+ resetTransform,
3005
3709
  restoreLocks,
3006
3710
  round2,
3007
3711
  sanitizeSvg,
3008
- serializeEditor
3712
+ serializeEditor,
3713
+ shapeMaskPathData
3009
3714
  });
3010
3715
  //# sourceMappingURL=index.js.map