@pooder/kit 0.0.2 → 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
  });
@@ -124,10 +125,10 @@ var BackgroundTool = class {
124
125
  editor.canvas.add(backgroundLayer);
125
126
  editor.canvas.sendObjectToBack(backgroundLayer);
126
127
  }
127
- this.updateBackground(editor, this.options);
128
128
  }
129
129
  onMount(editor) {
130
130
  this.initLayer(editor);
131
+ this.updateBackground(editor, this.options);
131
132
  }
132
133
  onUnmount(editor) {
133
134
  const layer = editor.getLayer("background");
@@ -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,11 +464,100 @@ 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
  }
466
558
  onMount(editor) {
467
559
  this.createLayer(editor);
560
+ this.updateDieline(editor);
468
561
  }
469
562
  onUnmount(editor) {
470
563
  this.destroyLayer(editor);
@@ -496,7 +589,6 @@ var DielineTool = class {
496
589
  editor.canvas.add(layer);
497
590
  }
498
591
  editor.canvas.bringObjectToFront(layer);
499
- this.updateDieline(editor);
500
592
  }
501
593
  destroyLayer(editor) {
502
594
  const layer = this.getLayer(editor, "dieline-overlay");
@@ -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);
@@ -750,6 +861,7 @@ var FilmTool = class {
750
861
  }
751
862
  }
752
863
  onUpdate(editor, state) {
864
+ this.updateFilm(editor, this.options);
753
865
  }
754
866
  initLayer(editor) {
755
867
  let overlayLayer = editor.getLayer("overlay");
@@ -802,8 +914,7 @@ var FilmTool = class {
802
914
  } else {
803
915
  img = await import_core3.Image.fromURL(url, { crossOrigin: "anonymous" });
804
916
  img.scaleToWidth(width);
805
- if (img.getScaledHeight() < height)
806
- img.scaleToHeight(height);
917
+ if (img.getScaledHeight() < height) img.scaleToHeight(height);
807
918
  img.set({
808
919
  originX: "left",
809
920
  originY: "top",
@@ -832,7 +943,8 @@ var HoleTool = class {
832
943
  innerRadius: 15,
833
944
  outerRadius: 25,
834
945
  style: "solid",
835
- holes: []
946
+ holes: [],
947
+ constraintTarget: "bleed"
836
948
  };
837
949
  this.schema = {
838
950
  innerRadius: {
@@ -852,6 +964,11 @@ var HoleTool = class {
852
964
  options: ["solid", "dashed"],
853
965
  label: "Line Style"
854
966
  },
967
+ constraintTarget: {
968
+ type: "select",
969
+ options: ["original", "bleed"],
970
+ label: "Constraint Target"
971
+ },
855
972
  holes: {
856
973
  type: "json",
857
974
  label: "Holes"
@@ -866,7 +983,10 @@ var HoleTool = class {
866
983
  const g = this.getDielineGeometry(editor);
867
984
  if (g) {
868
985
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
869
- defaultPos = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
986
+ defaultPos = getNearestPointOnDieline(topCenter, {
987
+ ...g,
988
+ holes: []
989
+ });
870
990
  }
871
991
  this.options = {
872
992
  innerRadius: 15,
@@ -933,7 +1053,7 @@ var HoleTool = class {
933
1053
  if (!dielineTool) return null;
934
1054
  const geometry = dielineTool.getGeometry(editor);
935
1055
  if (!geometry) return null;
936
- const offset = dielineTool.options.offset || 0;
1056
+ const offset = this.options.constraintTarget === "original" ? 0 : dielineTool.options.offset || 0;
937
1057
  return {
938
1058
  ...geometry,
939
1059
  width: Math.max(0, geometry.width + offset * 2),
@@ -941,6 +1061,39 @@ var HoleTool = class {
941
1061
  radius: Math.max(0, geometry.radius + offset)
942
1062
  };
943
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
+ }
944
1097
  setup(editor) {
945
1098
  if (!this.handleMoving) {
946
1099
  this.handleMoving = (e) => {
@@ -973,7 +1126,10 @@ var HoleTool = class {
973
1126
  const g = this.getDielineGeometry(editor);
974
1127
  if (g) {
975
1128
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
976
- const snapped = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
1129
+ const snapped = getNearestPointOnDieline(topCenter, {
1130
+ ...g,
1131
+ holes: []
1132
+ });
977
1133
  defaultPos = snapped;
978
1134
  }
979
1135
  opts.holes = [defaultPos];
@@ -1002,6 +1158,12 @@ var HoleTool = class {
1002
1158
  editor.canvas.requestRenderAll();
1003
1159
  }
1004
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
+ }
1005
1167
  }
1006
1168
  syncHolesFromCanvas(editor) {
1007
1169
  const objects = editor.canvas.getObjects().filter((obj) => {
@@ -1090,7 +1252,10 @@ var HoleTool = class {
1090
1252
  holes: []
1091
1253
  // We don't need holes for boundary calculation
1092
1254
  };
1093
- const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, options);
1255
+ const nearest = getNearestPointOnDieline(
1256
+ { x: p.x, y: p.y },
1257
+ options
1258
+ );
1094
1259
  const nearestP = new import_core4.Point(nearest.x, nearest.y);
1095
1260
  const dist = p.distanceFrom(nearestP);
1096
1261
  const v = p.subtract(nearestP);
@@ -1121,6 +1286,7 @@ var import_core5 = require("@pooder/core");
1121
1286
  var ImageTool = class {
1122
1287
  constructor() {
1123
1288
  this.name = "ImageTool";
1289
+ this._loadingUrl = null;
1124
1290
  this.options = {
1125
1291
  url: "",
1126
1292
  opacity: 1
@@ -1136,14 +1302,44 @@ var ImageTool = class {
1136
1302
  max: 1,
1137
1303
  step: 0.1,
1138
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
1139
1335
  }
1140
1336
  };
1141
1337
  this.commands = {
1142
1338
  setUserImage: {
1143
- execute: (editor, url, opacity) => {
1144
- if (this.options.url === url && this.options.opacity === opacity) return true;
1145
- this.options.url = url;
1146
- 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 };
1147
1343
  this.updateImage(editor, this.options);
1148
1344
  return true;
1149
1345
  },
@@ -1159,13 +1355,19 @@ var ImageTool = class {
1159
1355
  min: 0,
1160
1356
  max: 1,
1161
1357
  required: true
1162
- }
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" }
1163
1364
  }
1164
1365
  }
1165
1366
  };
1166
1367
  }
1167
1368
  onMount(editor) {
1168
1369
  this.ensureLayer(editor);
1370
+ this.updateImage(editor, this.options);
1169
1371
  }
1170
1372
  onUnmount(editor) {
1171
1373
  const layer = editor.getLayer("user");
@@ -1203,43 +1405,101 @@ var ImageTool = class {
1203
1405
  }
1204
1406
  updateImage(editor, opts) {
1205
1407
  var _a, _b;
1206
- let { url, opacity } = opts;
1408
+ let { url, opacity, width, height, angle, left, top } = opts;
1207
1409
  const layer = editor.getLayer("user");
1208
1410
  if (!layer) {
1209
1411
  console.warn("[ImageTool] User layer not found");
1210
1412
  return;
1211
1413
  }
1212
1414
  const userImage = editor.getObject("user-image", "user");
1415
+ if (this._loadingUrl === url) return;
1213
1416
  if (userImage) {
1214
1417
  const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1215
1418
  if (currentSrc !== url) {
1216
- this.loadImage(editor, layer, url, opacity, userImage);
1419
+ this.loadImage(editor, layer, opts);
1217
1420
  } else {
1218
- if (userImage.opacity !== opacity) {
1219
- 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);
1220
1442
  editor.canvas.requestRenderAll();
1221
1443
  }
1222
1444
  }
1223
1445
  } else {
1224
- this.loadImage(editor, layer, url, opacity);
1446
+ this.loadImage(editor, layer, opts);
1225
1447
  }
1226
1448
  }
1227
- loadImage(editor, layer, url, opacity, oldImage) {
1449
+ loadImage(editor, layer, opts) {
1450
+ const { url } = opts;
1451
+ this._loadingUrl = url;
1228
1452
  import_core5.Image.fromURL(url).then((image) => {
1229
- if (oldImage) {
1230
- const { left, top, scaleX, scaleY, angle } = oldImage;
1231
- image.set({ left, top, scaleX, scaleY, angle });
1232
- 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;
1233
1480
  }
1234
1481
  image.set({
1235
- opacity,
1482
+ opacity: opacity !== void 0 ? opacity : 1,
1236
1483
  data: {
1237
1484
  id: "user-image"
1238
1485
  }
1239
1486
  });
1240
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
+ });
1241
1500
  editor.canvas.requestRenderAll();
1242
1501
  }).catch((err) => {
1502
+ if (this._loadingUrl === url) this._loadingUrl = null;
1243
1503
  console.error("Failed to load image", url, err);
1244
1504
  });
1245
1505
  }
@@ -1263,7 +1523,8 @@ var WhiteInkTool = class {
1263
1523
  this.commands = {
1264
1524
  setWhiteInkImage: {
1265
1525
  execute: (editor, customMask, opacity, enableClip = true) => {
1266
- 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;
1267
1528
  this.options.customMask = customMask;
1268
1529
  this.options.opacity = opacity;
1269
1530
  this.options.enableClip = enableClip;
@@ -1295,6 +1556,7 @@ var WhiteInkTool = class {
1295
1556
  }
1296
1557
  onMount(editor) {
1297
1558
  this.setup(editor);
1559
+ this.updateWhiteInk(editor, this.options);
1298
1560
  }
1299
1561
  onUnmount(editor) {
1300
1562
  this.teardown(editor);
@@ -1335,7 +1597,6 @@ var WhiteInkTool = class {
1335
1597
  editor.canvas.on("object:rotating", this.syncHandler);
1336
1598
  editor.canvas.on("object:modified", this.syncHandler);
1337
1599
  }
1338
- this.updateWhiteInk(editor, this.options);
1339
1600
  }
1340
1601
  teardown(editor) {
1341
1602
  if (this.syncHandler) {
@@ -1384,7 +1645,14 @@ var WhiteInkTool = class {
1384
1645
  if (whiteInk) {
1385
1646
  const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1386
1647
  if (currentSrc !== customMask) {
1387
- this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
1648
+ this.loadWhiteInk(
1649
+ editor,
1650
+ layer,
1651
+ customMask,
1652
+ opacity,
1653
+ enableClip,
1654
+ whiteInk
1655
+ );
1388
1656
  } else {
1389
1657
  if (whiteInk.opacity !== opacity) {
1390
1658
  whiteInk.set({ opacity });
@@ -1413,10 +1681,12 @@ var WhiteInkTool = class {
1413
1681
  if (oldImage) {
1414
1682
  layer.remove(oldImage);
1415
1683
  }
1416
- (_a = image.filters) == null ? void 0 : _a.push(new import_core6.filters.BlendColor({
1417
- color: "#FFFFFF",
1418
- mode: "add"
1419
- }));
1684
+ (_a = image.filters) == null ? void 0 : _a.push(
1685
+ new import_core6.filters.BlendColor({
1686
+ color: "#FFFFFF",
1687
+ mode: "add"
1688
+ })
1689
+ );
1420
1690
  image.applyFilters();
1421
1691
  image.set({
1422
1692
  opacity,
@@ -1528,7 +1798,8 @@ var RulerTool = class {
1528
1798
  setTheme: {
1529
1799
  execute: (editor, theme) => {
1530
1800
  const newOptions = { ...this.options, ...theme };
1531
- if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
1801
+ if (JSON.stringify(newOptions) === JSON.stringify(this.options))
1802
+ return true;
1532
1803
  this.options = newOptions;
1533
1804
  this.updateRuler(editor);
1534
1805
  return true;
@@ -1545,11 +1816,13 @@ var RulerTool = class {
1545
1816
  }
1546
1817
  onMount(editor) {
1547
1818
  this.createLayer(editor);
1819
+ this.updateRuler(editor);
1548
1820
  }
1549
1821
  onUnmount(editor) {
1550
1822
  this.destroyLayer(editor);
1551
1823
  }
1552
1824
  onUpdate(editor, state) {
1825
+ this.updateRuler(editor);
1553
1826
  }
1554
1827
  onDestroy(editor) {
1555
1828
  this.destroyLayer(editor);
@@ -1575,7 +1848,6 @@ var RulerTool = class {
1575
1848
  editor.canvas.add(layer);
1576
1849
  }
1577
1850
  editor.canvas.bringObjectToFront(layer);
1578
- this.updateRuler(editor);
1579
1851
  }
1580
1852
  destroyLayer(editor) {
1581
1853
  const layer = this.getLayer(editor);
@@ -1681,6 +1953,76 @@ var RulerTool = class {
1681
1953
  editor.canvas.requestRenderAll();
1682
1954
  }
1683
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
+ };
1684
2026
  // Annotate the CommonJS export names for ESM import in node:
1685
2027
  0 && (module.exports = {
1686
2028
  BackgroundTool,
@@ -1688,6 +2030,7 @@ var RulerTool = class {
1688
2030
  FilmTool,
1689
2031
  HoleTool,
1690
2032
  ImageTool,
2033
+ MirrorTool,
1691
2034
  RulerTool,
1692
2035
  WhiteInkTool
1693
2036
  });