@pooder/kit 1.0.0 → 2.0.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/index.js CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  FilmTool: () => FilmTool,
36
36
  HoleTool: () => HoleTool,
37
37
  ImageTool: () => ImageTool,
38
+ MirrorTool: () => MirrorTool,
38
39
  RulerTool: () => RulerTool,
39
40
  WhiteInkTool: () => WhiteInkTool
40
41
  });
@@ -191,8 +192,7 @@ var BackgroundTool = class {
191
192
  }
192
193
  });
193
194
  img.scaleToWidth(width);
194
- if (img.getScaledHeight() < height)
195
- img.scaleToHeight(height);
195
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
196
196
  layer.add(img);
197
197
  }
198
198
  }
@@ -357,7 +357,8 @@ var DielineTool = class {
357
357
  offset: 0,
358
358
  style: "solid",
359
359
  insideColor: "rgba(0,0,0,0)",
360
- outsideColor: "#ffffff"
360
+ outsideColor: "#ffffff",
361
+ showBleedLines: true
361
362
  };
362
363
  this.schema = {
363
364
  shape: {
@@ -372,6 +373,7 @@ var DielineTool = class {
372
373
  // Complex object, simplified for now or need custom handler
373
374
  borderLength: { type: "number", min: 0, max: 500, label: "Margin" },
374
375
  offset: { type: "number", min: -100, max: 100, label: "Bleed Offset" },
376
+ showBleedLines: { type: "boolean", label: "Show Bleed Lines" },
375
377
  style: {
376
378
  type: "select",
377
379
  options: ["solid", "dashed"],
@@ -391,7 +393,8 @@ var DielineTool = class {
391
393
  offset: 0,
392
394
  style: "solid",
393
395
  insideColor: "rgba(0,0,0,0)",
394
- outsideColor: "#ffffff"
396
+ outsideColor: "#ffffff",
397
+ showBleedLines: true
395
398
  };
396
399
  this.updateDieline(editor);
397
400
  return true;
@@ -405,7 +408,8 @@ var DielineTool = class {
405
408
  },
406
409
  setDimensions: {
407
410
  execute: (editor, width, height) => {
408
- if (this.options.width === width && this.options.height === height) return true;
411
+ if (this.options.width === width && this.options.height === height)
412
+ return true;
409
413
  this.options.width = width;
410
414
  this.options.height = height;
411
415
  this.updateDieline(editor);
@@ -460,6 +464,94 @@ var DielineTool = class {
460
464
  required: true
461
465
  }
462
466
  }
467
+ },
468
+ exportCutImage: {
469
+ execute: (editor) => {
470
+ var _a, _b, _c, _d;
471
+ const { shape, width, height, radius, position } = this.options;
472
+ const canvasW = editor.canvas.width || 800;
473
+ const canvasH = editor.canvas.height || 600;
474
+ const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
475
+ const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
476
+ const holeTool = editor.getExtension("HoleTool");
477
+ const holes = holeTool ? holeTool.options.holes || [] : [];
478
+ const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
479
+ const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
480
+ const holeData = holes.map((h) => ({
481
+ x: h.x,
482
+ y: h.y,
483
+ innerRadius,
484
+ outerRadius
485
+ }));
486
+ const pathData = generateDielinePath({
487
+ shape,
488
+ width,
489
+ height,
490
+ radius,
491
+ x: cx,
492
+ y: cy,
493
+ holes: holeData
494
+ });
495
+ const clipPath = new import_core2.Path(pathData, {
496
+ left: 0,
497
+ top: 0,
498
+ originX: "left",
499
+ originY: "top",
500
+ absolutePositioned: true
501
+ });
502
+ const layer = this.getLayer(editor, "dieline-overlay");
503
+ const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
504
+ if (layer) layer.visible = false;
505
+ const holeMarkers = editor.canvas.getObjects().filter((o) => {
506
+ var _a2;
507
+ return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
508
+ });
509
+ holeMarkers.forEach((o) => o.visible = false);
510
+ const rulerLayer = editor.canvas.getObjects().find((obj) => {
511
+ var _a2;
512
+ return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
513
+ });
514
+ const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
515
+ if (rulerLayer) rulerLayer.visible = false;
516
+ const originalClip = editor.canvas.clipPath;
517
+ editor.canvas.clipPath = clipPath;
518
+ const bbox = clipPath.getBoundingRect();
519
+ const holeDataRelative = holes.map((h) => ({
520
+ x: h.x - bbox.left,
521
+ y: h.y - bbox.top,
522
+ innerRadius,
523
+ outerRadius
524
+ }));
525
+ const clipPathCorrected = new import_core2.Path(pathData, {
526
+ absolutePositioned: true,
527
+ left: 0,
528
+ top: 0
529
+ });
530
+ const tempPath = new import_core2.Path(pathData);
531
+ const tempBounds = tempPath.getBoundingRect();
532
+ clipPathCorrected.set({
533
+ left: tempBounds.left,
534
+ top: tempBounds.top,
535
+ originX: "left",
536
+ originY: "top"
537
+ });
538
+ editor.canvas.clipPath = clipPathCorrected;
539
+ const exportBbox = clipPathCorrected.getBoundingRect();
540
+ const dataURL = editor.canvas.toDataURL({
541
+ format: "png",
542
+ multiplier: 2,
543
+ left: exportBbox.left,
544
+ top: exportBbox.top,
545
+ width: exportBbox.width,
546
+ height: exportBbox.height
547
+ });
548
+ editor.canvas.clipPath = originalClip;
549
+ if (layer) layer.visible = wasVisible;
550
+ if (rulerLayer) rulerLayer.visible = rulerWasVisible;
551
+ holeMarkers.forEach((o) => o.visible = true);
552
+ editor.canvas.requestRenderAll();
553
+ return dataURL;
554
+ }
463
555
  }
464
556
  };
465
557
  }
@@ -526,7 +618,17 @@ var DielineTool = class {
526
618
  }
527
619
  updateDieline(editor) {
528
620
  var _a, _b;
529
- const { shape, radius, offset, style, insideColor, outsideColor, position, borderLength } = this.options;
621
+ const {
622
+ shape,
623
+ radius,
624
+ offset,
625
+ style,
626
+ insideColor,
627
+ outsideColor,
628
+ position,
629
+ borderLength,
630
+ showBleedLines
631
+ } = this.options;
530
632
  let { width, height } = this.options;
531
633
  const canvasW = editor.canvas.width || 800;
532
634
  const canvasH = editor.canvas.height || 600;
@@ -540,6 +642,9 @@ var DielineTool = class {
540
642
  if (!layer) return;
541
643
  layer.remove(...layer.getObjects());
542
644
  const holeTool = editor.getExtension("HoleTool");
645
+ if (holeTool && typeof holeTool.enforceConstraints === "function") {
646
+ holeTool.enforceConstraints(editor);
647
+ }
543
648
  const holes = holeTool ? holeTool.options.holes || [] : [];
544
649
  const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
545
650
  const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
@@ -596,27 +701,32 @@ var DielineTool = class {
596
701
  layer.add(insideObj);
597
702
  }
598
703
  if (offset !== 0) {
599
- const bleedPathData = generateBleedZonePath({
600
- shape,
601
- width,
602
- height,
603
- radius,
604
- x: cx,
605
- y: cy,
606
- holes: holeData
607
- }, offset);
608
- const pattern = this.createHatchPattern("red");
609
- if (pattern) {
610
- const bleedObj = new import_core2.Path(bleedPathData, {
611
- fill: pattern,
612
- stroke: null,
613
- selectable: false,
614
- evented: false,
615
- objectCaching: false,
616
- originX: "left",
617
- originY: "top"
618
- });
619
- layer.add(bleedObj);
704
+ const bleedPathData = generateBleedZonePath(
705
+ {
706
+ shape,
707
+ width,
708
+ height,
709
+ radius,
710
+ x: cx,
711
+ y: cy,
712
+ holes: holeData
713
+ },
714
+ offset
715
+ );
716
+ if (showBleedLines !== false) {
717
+ const pattern = this.createHatchPattern("red");
718
+ if (pattern) {
719
+ const bleedObj = new import_core2.Path(bleedPathData, {
720
+ fill: pattern,
721
+ stroke: null,
722
+ selectable: false,
723
+ evented: false,
724
+ objectCaching: false,
725
+ originX: "left",
726
+ originY: "top"
727
+ });
728
+ layer.add(bleedObj);
729
+ }
620
730
  }
621
731
  const offsetPathData = generateDielinePath({
622
732
  shape,
@@ -712,7 +822,8 @@ var FilmTool = class {
712
822
  this.commands = {
713
823
  setFilmImage: {
714
824
  execute: (editor, url, opacity) => {
715
- if (this.options.url === url && this.options.opacity === opacity) return true;
825
+ if (this.options.url === url && this.options.opacity === opacity)
826
+ return true;
716
827
  this.options.url = url;
717
828
  this.options.opacity = opacity;
718
829
  this.updateFilm(editor, this.options);
@@ -803,8 +914,7 @@ var FilmTool = class {
803
914
  } else {
804
915
  img = await import_core3.Image.fromURL(url, { crossOrigin: "anonymous" });
805
916
  img.scaleToWidth(width);
806
- if (img.getScaledHeight() < height)
807
- img.scaleToHeight(height);
917
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
808
918
  img.set({
809
919
  originX: "left",
810
920
  originY: "top",
@@ -833,7 +943,8 @@ var HoleTool = class {
833
943
  innerRadius: 15,
834
944
  outerRadius: 25,
835
945
  style: "solid",
836
- holes: []
946
+ holes: [],
947
+ constraintTarget: "bleed"
837
948
  };
838
949
  this.schema = {
839
950
  innerRadius: {
@@ -853,6 +964,11 @@ var HoleTool = class {
853
964
  options: ["solid", "dashed"],
854
965
  label: "Line Style"
855
966
  },
967
+ constraintTarget: {
968
+ type: "select",
969
+ options: ["original", "bleed"],
970
+ label: "Constraint Target"
971
+ },
856
972
  holes: {
857
973
  type: "json",
858
974
  label: "Holes"
@@ -867,7 +983,10 @@ var HoleTool = class {
867
983
  const g = this.getDielineGeometry(editor);
868
984
  if (g) {
869
985
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
870
- defaultPos = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
986
+ defaultPos = getNearestPointOnDieline(topCenter, {
987
+ ...g,
988
+ holes: []
989
+ });
871
990
  }
872
991
  this.options = {
873
992
  innerRadius: 15,
@@ -934,7 +1053,7 @@ var HoleTool = class {
934
1053
  if (!dielineTool) return null;
935
1054
  const geometry = dielineTool.getGeometry(editor);
936
1055
  if (!geometry) return null;
937
- const offset = dielineTool.options.offset || 0;
1056
+ const offset = this.options.constraintTarget === "original" ? 0 : dielineTool.options.offset || 0;
938
1057
  return {
939
1058
  ...geometry,
940
1059
  width: Math.max(0, geometry.width + offset * 2),
@@ -942,6 +1061,39 @@ var HoleTool = class {
942
1061
  radius: Math.max(0, geometry.radius + offset)
943
1062
  };
944
1063
  }
1064
+ enforceConstraints(editor) {
1065
+ const geometry = this.getDielineGeometry(editor);
1066
+ if (!geometry) return;
1067
+ const objects = editor.canvas.getObjects().filter((obj) => {
1068
+ var _a;
1069
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1070
+ });
1071
+ let changed = false;
1072
+ objects.sort(
1073
+ (a, b) => {
1074
+ var _a, _b, _c, _d;
1075
+ return ((_b = (_a = a.data) == null ? void 0 : _a.index) != null ? _b : 0) - ((_d = (_c = b.data) == null ? void 0 : _c.index) != null ? _d : 0);
1076
+ }
1077
+ );
1078
+ const newHoles = [];
1079
+ objects.forEach((obj) => {
1080
+ const currentPos = new import_core4.Point(obj.left, obj.top);
1081
+ const newPos = this.calculateConstrainedPosition(currentPos, geometry);
1082
+ if (currentPos.distanceFrom(newPos) > 0.1) {
1083
+ obj.set({
1084
+ left: newPos.x,
1085
+ top: newPos.y
1086
+ });
1087
+ obj.setCoords();
1088
+ changed = true;
1089
+ }
1090
+ newHoles.push({ x: obj.left, y: obj.top });
1091
+ });
1092
+ if (changed) {
1093
+ this.options.holes = newHoles;
1094
+ editor.canvas.requestRenderAll();
1095
+ }
1096
+ }
945
1097
  setup(editor) {
946
1098
  if (!this.handleMoving) {
947
1099
  this.handleMoving = (e) => {
@@ -974,7 +1126,10 @@ var HoleTool = class {
974
1126
  const g = this.getDielineGeometry(editor);
975
1127
  if (g) {
976
1128
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
977
- const snapped = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
1129
+ const snapped = getNearestPointOnDieline(topCenter, {
1130
+ ...g,
1131
+ holes: []
1132
+ });
978
1133
  defaultPos = snapped;
979
1134
  }
980
1135
  opts.holes = [defaultPos];
@@ -1003,6 +1158,12 @@ var HoleTool = class {
1003
1158
  editor.canvas.requestRenderAll();
1004
1159
  }
1005
1160
  onUpdate(editor, state) {
1161
+ this.enforceConstraints(editor);
1162
+ this.redraw(editor);
1163
+ const dielineTool = editor.getExtension("DielineTool");
1164
+ if (dielineTool && dielineTool.updateDieline) {
1165
+ dielineTool.updateDieline(editor);
1166
+ }
1006
1167
  }
1007
1168
  syncHolesFromCanvas(editor) {
1008
1169
  const objects = editor.canvas.getObjects().filter((obj) => {
@@ -1091,7 +1252,10 @@ var HoleTool = class {
1091
1252
  holes: []
1092
1253
  // We don't need holes for boundary calculation
1093
1254
  };
1094
- const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, options);
1255
+ const nearest = getNearestPointOnDieline(
1256
+ { x: p.x, y: p.y },
1257
+ options
1258
+ );
1095
1259
  const nearestP = new import_core4.Point(nearest.x, nearest.y);
1096
1260
  const dist = p.distanceFrom(nearestP);
1097
1261
  const v = p.subtract(nearestP);
@@ -1122,6 +1286,7 @@ var import_core5 = require("@pooder/core");
1122
1286
  var ImageTool = class {
1123
1287
  constructor() {
1124
1288
  this.name = "ImageTool";
1289
+ this._loadingUrl = null;
1125
1290
  this.options = {
1126
1291
  url: "",
1127
1292
  opacity: 1
@@ -1137,14 +1302,44 @@ var ImageTool = class {
1137
1302
  max: 1,
1138
1303
  step: 0.1,
1139
1304
  label: "Opacity"
1305
+ },
1306
+ width: {
1307
+ type: "number",
1308
+ label: "Width",
1309
+ min: 0,
1310
+ max: 5e3
1311
+ },
1312
+ height: {
1313
+ type: "number",
1314
+ label: "Height",
1315
+ min: 0,
1316
+ max: 5e3
1317
+ },
1318
+ angle: {
1319
+ type: "number",
1320
+ label: "Rotation",
1321
+ min: 0,
1322
+ max: 360
1323
+ },
1324
+ left: {
1325
+ type: "number",
1326
+ label: "Left",
1327
+ min: 0,
1328
+ max: 1e3
1329
+ },
1330
+ top: {
1331
+ type: "number",
1332
+ label: "Top",
1333
+ min: 0,
1334
+ max: 1e3
1140
1335
  }
1141
1336
  };
1142
1337
  this.commands = {
1143
1338
  setUserImage: {
1144
- execute: (editor, url, opacity) => {
1145
- if (this.options.url === url && this.options.opacity === opacity) return true;
1146
- this.options.url = url;
1147
- this.options.opacity = opacity;
1339
+ execute: (editor, url, opacity, width, height, angle, left, top) => {
1340
+ if (this.options.url === url && this.options.opacity === opacity && this.options.width === width && this.options.height === height && this.options.angle === angle && this.options.left === left && this.options.top === top)
1341
+ return true;
1342
+ this.options = { url, opacity, width, height, angle, left, top };
1148
1343
  this.updateImage(editor, this.options);
1149
1344
  return true;
1150
1345
  },
@@ -1160,7 +1355,12 @@ var ImageTool = class {
1160
1355
  min: 0,
1161
1356
  max: 1,
1162
1357
  required: true
1163
- }
1358
+ },
1359
+ width: { type: "number", label: "Width" },
1360
+ height: { type: "number", label: "Height" },
1361
+ angle: { type: "number", label: "Angle" },
1362
+ left: { type: "number", label: "Left" },
1363
+ top: { type: "number", label: "Top" }
1164
1364
  }
1165
1365
  }
1166
1366
  };
@@ -1205,43 +1405,101 @@ var ImageTool = class {
1205
1405
  }
1206
1406
  updateImage(editor, opts) {
1207
1407
  var _a, _b;
1208
- let { url, opacity } = opts;
1408
+ let { url, opacity, width, height, angle, left, top } = opts;
1209
1409
  const layer = editor.getLayer("user");
1210
1410
  if (!layer) {
1211
1411
  console.warn("[ImageTool] User layer not found");
1212
1412
  return;
1213
1413
  }
1214
1414
  const userImage = editor.getObject("user-image", "user");
1415
+ if (this._loadingUrl === url) return;
1215
1416
  if (userImage) {
1216
1417
  const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1217
1418
  if (currentSrc !== url) {
1218
- this.loadImage(editor, layer, url, opacity, userImage);
1419
+ this.loadImage(editor, layer, opts);
1219
1420
  } else {
1220
- if (userImage.opacity !== opacity) {
1221
- userImage.set({ opacity });
1421
+ const updates = {};
1422
+ const centerX = editor.state.width / 2;
1423
+ const centerY = editor.state.height / 2;
1424
+ if (userImage.opacity !== opacity) updates.opacity = opacity;
1425
+ if (angle !== void 0 && userImage.angle !== angle)
1426
+ updates.angle = angle;
1427
+ if (left !== void 0) {
1428
+ const localLeft = left - centerX;
1429
+ if (Math.abs(userImage.left - localLeft) > 1)
1430
+ updates.left = localLeft;
1431
+ }
1432
+ if (top !== void 0) {
1433
+ const localTop = top - centerY;
1434
+ if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
1435
+ }
1436
+ if (width !== void 0 && userImage.width)
1437
+ updates.scaleX = width / userImage.width;
1438
+ if (height !== void 0 && userImage.height)
1439
+ updates.scaleY = height / userImage.height;
1440
+ if (Object.keys(updates).length > 0) {
1441
+ userImage.set(updates);
1222
1442
  editor.canvas.requestRenderAll();
1223
1443
  }
1224
1444
  }
1225
1445
  } else {
1226
- this.loadImage(editor, layer, url, opacity);
1446
+ this.loadImage(editor, layer, opts);
1227
1447
  }
1228
1448
  }
1229
- loadImage(editor, layer, url, opacity, oldImage) {
1449
+ loadImage(editor, layer, opts) {
1450
+ const { url } = opts;
1451
+ this._loadingUrl = url;
1230
1452
  import_core5.Image.fromURL(url).then((image) => {
1231
- if (oldImage) {
1232
- const { left, top, scaleX, scaleY, angle } = oldImage;
1233
- image.set({ left, top, scaleX, scaleY, angle });
1234
- layer.remove(oldImage);
1453
+ if (this._loadingUrl !== url) return;
1454
+ this._loadingUrl = null;
1455
+ const currentOpts = this.options;
1456
+ const { opacity, width, height, angle, left, top } = currentOpts;
1457
+ const existingImage = editor.getObject("user-image", "user");
1458
+ if (existingImage) {
1459
+ const defaultLeft = existingImage.left;
1460
+ const defaultTop = existingImage.top;
1461
+ const defaultAngle = existingImage.angle;
1462
+ const defaultScaleX = existingImage.scaleX;
1463
+ const defaultScaleY = existingImage.scaleY;
1464
+ image.set({
1465
+ left: left !== void 0 ? left : defaultLeft,
1466
+ top: top !== void 0 ? top : defaultTop,
1467
+ angle: angle !== void 0 ? angle : defaultAngle,
1468
+ scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
1469
+ scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
1470
+ });
1471
+ layer.remove(existingImage);
1472
+ } else {
1473
+ if (width !== void 0 && image.width)
1474
+ image.scaleX = width / image.width;
1475
+ if (height !== void 0 && image.height)
1476
+ image.scaleY = height / image.height;
1477
+ if (angle !== void 0) image.angle = angle;
1478
+ if (left !== void 0) image.left = left;
1479
+ if (top !== void 0) image.top = top;
1235
1480
  }
1236
1481
  image.set({
1237
- opacity,
1482
+ opacity: opacity !== void 0 ? opacity : 1,
1238
1483
  data: {
1239
1484
  id: "user-image"
1240
1485
  }
1241
1486
  });
1242
1487
  layer.add(image);
1488
+ image.on("modified", (e) => {
1489
+ const matrix = image.calcTransformMatrix();
1490
+ const globalPoint = import_core5.util.transformPoint(new import_core5.Point(0, 0), matrix);
1491
+ this.options.left = globalPoint.x;
1492
+ this.options.top = globalPoint.y;
1493
+ this.options.angle = e.target.angle;
1494
+ if (image.width)
1495
+ this.options.width = e.target.width * e.target.scaleX;
1496
+ if (image.height)
1497
+ this.options.height = e.target.height * e.target.scaleY;
1498
+ editor.emit("update");
1499
+ });
1243
1500
  editor.canvas.requestRenderAll();
1244
1501
  }).catch((err) => {
1502
+ if (this._loadingUrl === url) this._loadingUrl = null;
1245
1503
  console.error("Failed to load image", url, err);
1246
1504
  });
1247
1505
  }
@@ -1265,7 +1523,8 @@ var WhiteInkTool = class {
1265
1523
  this.commands = {
1266
1524
  setWhiteInkImage: {
1267
1525
  execute: (editor, customMask, opacity, enableClip = true) => {
1268
- if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip) return true;
1526
+ if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip)
1527
+ return true;
1269
1528
  this.options.customMask = customMask;
1270
1529
  this.options.opacity = opacity;
1271
1530
  this.options.enableClip = enableClip;
@@ -1386,7 +1645,14 @@ var WhiteInkTool = class {
1386
1645
  if (whiteInk) {
1387
1646
  const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1388
1647
  if (currentSrc !== customMask) {
1389
- this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
1648
+ this.loadWhiteInk(
1649
+ editor,
1650
+ layer,
1651
+ customMask,
1652
+ opacity,
1653
+ enableClip,
1654
+ whiteInk
1655
+ );
1390
1656
  } else {
1391
1657
  if (whiteInk.opacity !== opacity) {
1392
1658
  whiteInk.set({ opacity });
@@ -1415,10 +1681,12 @@ var WhiteInkTool = class {
1415
1681
  if (oldImage) {
1416
1682
  layer.remove(oldImage);
1417
1683
  }
1418
- (_a = image.filters) == null ? void 0 : _a.push(new import_core6.filters.BlendColor({
1419
- color: "#FFFFFF",
1420
- mode: "add"
1421
- }));
1684
+ (_a = image.filters) == null ? void 0 : _a.push(
1685
+ new import_core6.filters.BlendColor({
1686
+ color: "#FFFFFF",
1687
+ mode: "add"
1688
+ })
1689
+ );
1422
1690
  image.applyFilters();
1423
1691
  image.set({
1424
1692
  opacity,
@@ -1530,7 +1798,8 @@ var RulerTool = class {
1530
1798
  setTheme: {
1531
1799
  execute: (editor, theme) => {
1532
1800
  const newOptions = { ...this.options, ...theme };
1533
- if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
1801
+ if (JSON.stringify(newOptions) === JSON.stringify(this.options))
1802
+ return true;
1534
1803
  this.options = newOptions;
1535
1804
  this.updateRuler(editor);
1536
1805
  return true;
@@ -1684,6 +1953,76 @@ var RulerTool = class {
1684
1953
  editor.canvas.requestRenderAll();
1685
1954
  }
1686
1955
  };
1956
+
1957
+ // src/mirror.ts
1958
+ var MirrorTool = class {
1959
+ constructor() {
1960
+ this.name = "MirrorTool";
1961
+ this.options = {
1962
+ enabled: false
1963
+ };
1964
+ this.schema = {
1965
+ enabled: {
1966
+ type: "boolean",
1967
+ label: "Mirror View"
1968
+ }
1969
+ };
1970
+ this.commands = {
1971
+ toggleMirror: {
1972
+ execute: (editor) => {
1973
+ this.options.enabled = !this.options.enabled;
1974
+ this.applyMirror(editor, this.options.enabled);
1975
+ return true;
1976
+ }
1977
+ },
1978
+ setMirror: {
1979
+ execute: (editor, enabled) => {
1980
+ if (this.options.enabled === enabled) return true;
1981
+ this.options.enabled = enabled;
1982
+ this.applyMirror(editor, enabled);
1983
+ return true;
1984
+ },
1985
+ schema: {
1986
+ enabled: {
1987
+ type: "boolean",
1988
+ label: "Enabled",
1989
+ required: true
1990
+ }
1991
+ }
1992
+ }
1993
+ };
1994
+ }
1995
+ onMount(editor) {
1996
+ if (this.options.enabled) {
1997
+ this.applyMirror(editor, true);
1998
+ }
1999
+ }
2000
+ onUpdate(editor) {
2001
+ this.applyMirror(editor, this.options.enabled);
2002
+ }
2003
+ onUnmount(editor) {
2004
+ this.applyMirror(editor, false);
2005
+ }
2006
+ applyMirror(editor, enabled) {
2007
+ const canvas = editor.canvas;
2008
+ if (!canvas) return;
2009
+ const width = canvas.width || 800;
2010
+ let vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
2011
+ vpt = [...vpt];
2012
+ const isFlipped = vpt[0] < 0;
2013
+ if (enabled && !isFlipped) {
2014
+ vpt[0] = -vpt[0];
2015
+ vpt[4] = width - vpt[4];
2016
+ canvas.setViewportTransform(vpt);
2017
+ canvas.requestRenderAll();
2018
+ } else if (!enabled && isFlipped) {
2019
+ vpt[0] = -vpt[0];
2020
+ vpt[4] = width - vpt[4];
2021
+ canvas.setViewportTransform(vpt);
2022
+ canvas.requestRenderAll();
2023
+ }
2024
+ }
2025
+ };
1687
2026
  // Annotate the CommonJS export names for ESM import in node:
1688
2027
  0 && (module.exports = {
1689
2028
  BackgroundTool,
@@ -1691,6 +2030,7 @@ var RulerTool = class {
1691
2030
  FilmTool,
1692
2031
  HoleTool,
1693
2032
  ImageTool,
2033
+ MirrorTool,
1694
2034
  RulerTool,
1695
2035
  WhiteInkTool
1696
2036
  });