@pooder/kit 3.0.1 → 3.2.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
@@ -434,6 +434,24 @@ var ImageTracer = class {
434
434
 
435
435
  // src/coordinate.ts
436
436
  var Coordinate = class {
437
+ /**
438
+ * Calculate layout to fit content within container while preserving aspect ratio.
439
+ */
440
+ static calculateLayout(container, content, padding = 0) {
441
+ const availableWidth = Math.max(0, container.width - padding * 2);
442
+ const availableHeight = Math.max(0, container.height - padding * 2);
443
+ if (content.width === 0 || content.height === 0) {
444
+ return { scale: 1, offsetX: 0, offsetY: 0, width: 0, height: 0 };
445
+ }
446
+ const scaleX = availableWidth / content.width;
447
+ const scaleY = availableHeight / content.height;
448
+ const scale = Math.min(scaleX, scaleY);
449
+ const width = content.width * scale;
450
+ const height = content.height * scale;
451
+ const offsetX = (container.width - width) / 2;
452
+ const offsetY = (container.height - height) / 2;
453
+ return { scale, offsetX, offsetY, width, height };
454
+ }
437
455
  /**
438
456
  * Convert an absolute value to a normalized value (0-1).
439
457
  * @param value Absolute value (e.g., pixels)
@@ -468,10 +486,85 @@ var Coordinate = class {
468
486
  y: this.toAbsolute(point.y, size.height)
469
487
  };
470
488
  }
489
+ static convertUnit(value, from, to) {
490
+ if (from === to) return value;
491
+ const toMM = {
492
+ px: 0.264583,
493
+ // 1px = 0.264583mm (96 DPI)
494
+ mm: 1,
495
+ cm: 10,
496
+ in: 25.4
497
+ };
498
+ const mmValue = value * (from === "px" ? toMM.px : toMM[from] || 1);
499
+ if (to === "px") {
500
+ return mmValue / toMM.px;
501
+ }
502
+ return mmValue / (toMM[to] || 1);
503
+ }
471
504
  };
472
505
 
473
506
  // src/geometry.ts
474
507
  var import_paper = __toESM(require("paper"));
508
+ function resolveHolePosition(hole, geometry, canvasSize) {
509
+ if (hole.anchor) {
510
+ const { x, y, width, height } = geometry;
511
+ let bx = x;
512
+ let by = y;
513
+ const left = x - width / 2;
514
+ const right = x + width / 2;
515
+ const top = y - height / 2;
516
+ const bottom = y + height / 2;
517
+ switch (hole.anchor) {
518
+ case "top-left":
519
+ bx = left;
520
+ by = top;
521
+ break;
522
+ case "top-center":
523
+ bx = x;
524
+ by = top;
525
+ break;
526
+ case "top-right":
527
+ bx = right;
528
+ by = top;
529
+ break;
530
+ case "center-left":
531
+ bx = left;
532
+ by = y;
533
+ break;
534
+ case "center":
535
+ bx = x;
536
+ by = y;
537
+ break;
538
+ case "center-right":
539
+ bx = right;
540
+ by = y;
541
+ break;
542
+ case "bottom-left":
543
+ bx = left;
544
+ by = bottom;
545
+ break;
546
+ case "bottom-center":
547
+ bx = x;
548
+ by = bottom;
549
+ break;
550
+ case "bottom-right":
551
+ bx = right;
552
+ by = bottom;
553
+ break;
554
+ }
555
+ return {
556
+ x: bx + (hole.offsetX || 0),
557
+ y: by + (hole.offsetY || 0)
558
+ };
559
+ } else if (hole.x !== void 0 && hole.y !== void 0) {
560
+ const { x, width, y, height } = geometry;
561
+ return {
562
+ x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
563
+ y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
564
+ };
565
+ }
566
+ return { x: 0, y: 0 };
567
+ }
475
568
  function ensurePaper(width, height) {
476
569
  if (!import_paper.default.project) {
477
570
  import_paper.default.setup(new import_paper.default.Size(width, height));
@@ -573,7 +666,10 @@ function getDielineShape(options) {
573
666
  cutsPath.remove();
574
667
  mainShape = temp;
575
668
  } catch (e) {
576
- console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
669
+ console.error(
670
+ "Geometry: Failed to subtract cutsPath from mainShape",
671
+ e
672
+ );
577
673
  }
578
674
  }
579
675
  }
@@ -666,7 +762,12 @@ function getPathBounds(pathData) {
666
762
  path.pathData = pathData;
667
763
  const bounds = path.bounds;
668
764
  path.remove();
669
- return { width: bounds.width, height: bounds.height };
765
+ return {
766
+ x: bounds.x,
767
+ y: bounds.y,
768
+ width: bounds.width,
769
+ height: bounds.height
770
+ };
670
771
  }
671
772
 
672
773
  // src/dieline.ts
@@ -676,6 +777,7 @@ var DielineTool = class {
676
777
  this.metadata = {
677
778
  name: "DielineTool"
678
779
  };
780
+ this.unit = "mm";
679
781
  this.shape = "rect";
680
782
  this.width = 500;
681
783
  this.height = 500;
@@ -686,6 +788,7 @@ var DielineTool = class {
686
788
  this.outsideColor = "#ffffff";
687
789
  this.showBleedLines = true;
688
790
  this.holes = [];
791
+ this.padding = 140;
689
792
  if (options) {
690
793
  Object.assign(this, options);
691
794
  }
@@ -699,14 +802,12 @@ var DielineTool = class {
699
802
  }
700
803
  const configService = context.services.get("ConfigurationService");
701
804
  if (configService) {
805
+ this.unit = configService.get("dieline.unit", this.unit);
702
806
  this.shape = configService.get("dieline.shape", this.shape);
703
807
  this.width = configService.get("dieline.width", this.width);
704
808
  this.height = configService.get("dieline.height", this.height);
705
809
  this.radius = configService.get("dieline.radius", this.radius);
706
- this.borderLength = configService.get(
707
- "dieline.borderLength",
708
- this.borderLength
709
- );
810
+ this.padding = configService.get("dieline.padding", this.padding);
710
811
  this.offset = configService.get("dieline.offset", this.offset);
711
812
  this.style = configService.get("dieline.style", this.style);
712
813
  this.insideColor = configService.get(
@@ -747,6 +848,13 @@ var DielineTool = class {
747
848
  contribute() {
748
849
  return {
749
850
  [import_core2.ContributionPointIds.CONFIGURATIONS]: [
851
+ {
852
+ id: "dieline.unit",
853
+ type: "select",
854
+ label: "Unit",
855
+ options: ["px", "mm", "cm", "in"],
856
+ default: this.unit
857
+ },
750
858
  {
751
859
  id: "dieline.shape",
752
860
  type: "select",
@@ -782,15 +890,14 @@ var DielineTool = class {
782
890
  id: "dieline.position",
783
891
  type: "json",
784
892
  label: "Position (Normalized)",
785
- default: this.position
893
+ default: this.radius
786
894
  },
787
895
  {
788
- id: "dieline.borderLength",
789
- type: "number",
790
- label: "Margin",
791
- min: 0,
792
- max: 500,
793
- default: this.borderLength
896
+ id: "dieline.padding",
897
+ type: "select",
898
+ label: "View Padding",
899
+ options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
900
+ default: this.padding
794
901
  },
795
902
  {
796
903
  id: "dieline.offset",
@@ -833,65 +940,6 @@ var DielineTool = class {
833
940
  }
834
941
  ],
835
942
  [import_core2.ContributionPointIds.COMMANDS]: [
836
- {
837
- command: "reset",
838
- title: "Reset Dieline",
839
- handler: () => {
840
- this.shape = "rect";
841
- this.width = 300;
842
- this.height = 300;
843
- this.radius = 0;
844
- this.offset = 0;
845
- this.style = "solid";
846
- this.insideColor = "rgba(0,0,0,0)";
847
- this.outsideColor = "#ffffff";
848
- this.showBleedLines = true;
849
- this.holes = [];
850
- this.pathData = void 0;
851
- this.updateDieline();
852
- return true;
853
- }
854
- },
855
- {
856
- command: "setDimensions",
857
- title: "Set Dimensions",
858
- handler: (width, height) => {
859
- if (this.width === width && this.height === height) return true;
860
- this.width = width;
861
- this.height = height;
862
- this.updateDieline();
863
- return true;
864
- }
865
- },
866
- {
867
- command: "setShape",
868
- title: "Set Shape",
869
- handler: (shape) => {
870
- if (this.shape === shape) return true;
871
- this.shape = shape;
872
- this.updateDieline();
873
- return true;
874
- }
875
- },
876
- {
877
- command: "setBleed",
878
- title: "Set Bleed",
879
- handler: (bleed) => {
880
- if (this.offset === bleed) return true;
881
- this.offset = bleed;
882
- this.updateDieline();
883
- return true;
884
- }
885
- },
886
- {
887
- command: "setHoles",
888
- title: "Set Holes",
889
- handler: (holes) => {
890
- this.holes = holes;
891
- this.updateDieline(false);
892
- return true;
893
- }
894
- },
895
943
  {
896
944
  command: "getGeometry",
897
945
  title: "Get Geometry",
@@ -910,16 +958,23 @@ var DielineTool = class {
910
958
  command: "detectEdge",
911
959
  title: "Detect Edge from Image",
912
960
  handler: async (imageUrl, options) => {
961
+ var _a;
913
962
  try {
914
963
  const pathData = await ImageTracer.trace(imageUrl, options);
915
964
  const bounds = getPathBounds(pathData);
916
965
  const currentMax = Math.max(this.width, this.height);
917
966
  const scale = currentMax / Math.max(bounds.width, bounds.height);
918
- this.width = bounds.width * scale;
919
- this.height = bounds.height * scale;
920
- this.shape = "custom";
921
- this.pathData = pathData;
922
- this.updateDieline();
967
+ const newWidth = bounds.width * scale;
968
+ const newHeight = bounds.height * scale;
969
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
970
+ "ConfigurationService"
971
+ );
972
+ if (configService) {
973
+ configService.update("dieline.width", newWidth);
974
+ configService.update("dieline.height", newHeight);
975
+ configService.update("dieline.shape", "custom");
976
+ configService.update("dieline.pathData", pathData);
977
+ }
923
978
  return pathData;
924
979
  } catch (e) {
925
980
  console.error("Edge detection failed", e);
@@ -978,11 +1033,25 @@ var DielineTool = class {
978
1033
  }
979
1034
  return new import_fabric2.Pattern({ source: canvas, repetition: "repeat" });
980
1035
  }
1036
+ resolvePadding(containerWidth, containerHeight) {
1037
+ if (typeof this.padding === "number") {
1038
+ return this.padding;
1039
+ }
1040
+ if (typeof this.padding === "string") {
1041
+ if (this.padding.endsWith("%")) {
1042
+ const percent = parseFloat(this.padding) / 100;
1043
+ return Math.min(containerWidth, containerHeight) * percent;
1044
+ }
1045
+ return parseFloat(this.padding) || 0;
1046
+ }
1047
+ return 0;
1048
+ }
981
1049
  updateDieline(emitEvent = true) {
982
1050
  if (!this.canvasService) return;
983
1051
  const layer = this.getLayer();
984
1052
  if (!layer) return;
985
1053
  const {
1054
+ unit,
986
1055
  shape,
987
1056
  radius,
988
1057
  offset,
@@ -990,35 +1059,60 @@ var DielineTool = class {
990
1059
  insideColor,
991
1060
  outsideColor,
992
1061
  position,
993
- borderLength,
994
1062
  showBleedLines,
995
1063
  holes
996
1064
  } = this;
997
1065
  let { width, height } = this;
998
1066
  const canvasW = this.canvasService.canvas.width || 800;
999
1067
  const canvasH = this.canvasService.canvas.height || 600;
1000
- if (borderLength && borderLength > 0) {
1001
- width = Math.max(0, canvasW - borderLength * 2);
1002
- height = Math.max(0, canvasH - borderLength * 2);
1003
- }
1004
- const normalizedPos = position != null ? position : { x: 0.5, y: 0.5 };
1005
- const cx = Coordinate.toAbsolute(normalizedPos.x, canvasW);
1006
- const cy = Coordinate.toAbsolute(normalizedPos.y, canvasH);
1068
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1069
+ const layout = Coordinate.calculateLayout(
1070
+ { width: canvasW, height: canvasH },
1071
+ { width, height },
1072
+ paddingPx
1073
+ );
1074
+ const scale = layout.scale;
1075
+ const cx = layout.offsetX + layout.width / 2;
1076
+ const cy = layout.offsetY + layout.height / 2;
1077
+ const visualWidth = layout.width;
1078
+ const visualHeight = layout.height;
1079
+ const visualRadius = radius * scale;
1080
+ const visualOffset = offset * scale;
1007
1081
  layer.remove(...layer.getObjects());
1082
+ const geometryForHoles = {
1083
+ x: cx,
1084
+ y: cy,
1085
+ width: visualWidth,
1086
+ height: visualHeight
1087
+ // Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
1088
+ };
1008
1089
  const absoluteHoles = (holes || []).map((h) => {
1009
- const p = Coordinate.denormalizePoint(
1010
- { x: h.x, y: h.y },
1011
- { width: canvasW, height: canvasH }
1012
- );
1090
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1091
+ const offsetScale = unitScale * scale;
1092
+ const hWithPixelOffsets = {
1093
+ ...h,
1094
+ offsetX: (h.offsetX || 0) * offsetScale,
1095
+ offsetY: (h.offsetY || 0) * offsetScale
1096
+ };
1097
+ const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
1098
+ width: canvasW,
1099
+ height: canvasH
1100
+ });
1013
1101
  return {
1014
1102
  ...h,
1015
- x: p.x,
1016
- y: p.y
1103
+ x: pos.x,
1104
+ y: pos.y,
1105
+ // Scale hole radii: mm -> current unit -> pixels
1106
+ innerRadius: h.innerRadius * offsetScale,
1107
+ outerRadius: h.outerRadius * offsetScale,
1108
+ // Store scaled offsets in the result for consistency, though pos is already resolved
1109
+ offsetX: hWithPixelOffsets.offsetX,
1110
+ offsetY: hWithPixelOffsets.offsetY
1017
1111
  };
1018
1112
  });
1019
- const cutW = Math.max(0, width + offset * 2);
1020
- const cutH = Math.max(0, height + offset * 2);
1021
- const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
1113
+ const cutW = Math.max(0, visualWidth + visualOffset * 2);
1114
+ const cutH = Math.max(0, visualHeight + visualOffset * 2);
1115
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
1022
1116
  const maskPathData = generateMaskPath({
1023
1117
  canvasWidth: canvasW,
1024
1118
  canvasHeight: canvasH,
@@ -1068,15 +1162,15 @@ var DielineTool = class {
1068
1162
  const bleedPathData = generateBleedZonePath(
1069
1163
  {
1070
1164
  shape,
1071
- width,
1072
- height,
1073
- radius,
1165
+ width: visualWidth,
1166
+ height: visualHeight,
1167
+ radius: visualRadius,
1074
1168
  x: cx,
1075
1169
  y: cy,
1076
1170
  holes: absoluteHoles,
1077
1171
  pathData: this.pathData
1078
1172
  },
1079
- offset
1173
+ visualOffset
1080
1174
  );
1081
1175
  if (showBleedLines !== false) {
1082
1176
  const pattern = this.createHatchPattern("red");
@@ -1119,13 +1213,12 @@ var DielineTool = class {
1119
1213
  }
1120
1214
  const borderPathData = generateDielinePath({
1121
1215
  shape,
1122
- width,
1123
- height,
1124
- radius,
1216
+ width: visualWidth,
1217
+ height: visualHeight,
1218
+ radius: visualRadius,
1125
1219
  x: cx,
1126
1220
  y: cy,
1127
1221
  holes: absoluteHoles,
1128
- // FIX: Use absoluteHoles instead of holes
1129
1222
  pathData: this.pathData
1130
1223
  });
1131
1224
  const borderObj = new import_fabric2.Path(borderPathData, {
@@ -1163,114 +1256,107 @@ var DielineTool = class {
1163
1256
  }
1164
1257
  }
1165
1258
  getGeometry() {
1166
- var _a, _b;
1167
1259
  if (!this.canvasService) return null;
1168
- const { shape, width, height, radius, position, borderLength, offset } = this;
1260
+ const { unit, shape, width, height, radius, position, offset } = this;
1169
1261
  const canvasW = this.canvasService.canvas.width || 800;
1170
1262
  const canvasH = this.canvasService.canvas.height || 600;
1171
- let visualWidth = width;
1172
- let visualHeight = height;
1173
- if (borderLength && borderLength > 0) {
1174
- visualWidth = Math.max(0, canvasW - borderLength * 2);
1175
- visualHeight = Math.max(0, canvasH - borderLength * 2);
1176
- }
1177
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1178
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1263
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1264
+ const layout = Coordinate.calculateLayout(
1265
+ { width: canvasW, height: canvasH },
1266
+ { width, height },
1267
+ paddingPx
1268
+ );
1269
+ const scale = layout.scale;
1270
+ const cx = layout.offsetX + layout.width / 2;
1271
+ const cy = layout.offsetY + layout.height / 2;
1272
+ const visualWidth = layout.width;
1273
+ const visualHeight = layout.height;
1179
1274
  return {
1180
1275
  shape,
1276
+ unit,
1181
1277
  x: cx,
1182
1278
  y: cy,
1183
1279
  width: visualWidth,
1184
1280
  height: visualHeight,
1185
- radius,
1186
- offset,
1187
- borderLength,
1281
+ radius: radius * scale,
1282
+ offset: offset * scale,
1283
+ // Pass scale to help other tools (like HoleTool) convert units
1284
+ scale,
1188
1285
  pathData: this.pathData
1189
1286
  };
1190
1287
  }
1191
- exportCutImage() {
1192
- var _a, _b, _c, _d;
1288
+ async exportCutImage() {
1193
1289
  if (!this.canvasService) return null;
1194
- const canvas = this.canvasService.canvas;
1290
+ const userLayer = this.canvasService.getLayer("user");
1291
+ if (!userLayer) return null;
1195
1292
  const { shape, width, height, radius, position, holes } = this;
1196
- const canvasW = canvas.width || 800;
1197
- const canvasH = canvas.height || 600;
1198
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1199
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1293
+ const canvasW = this.canvasService.canvas.width || 800;
1294
+ const canvasH = this.canvasService.canvas.height || 600;
1295
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1296
+ const layout = Coordinate.calculateLayout(
1297
+ { width: canvasW, height: canvasH },
1298
+ { width, height },
1299
+ paddingPx
1300
+ );
1301
+ const scale = layout.scale;
1302
+ const cx = layout.offsetX + layout.width / 2;
1303
+ const cy = layout.offsetY + layout.height / 2;
1304
+ const visualWidth = layout.width;
1305
+ const visualHeight = layout.height;
1306
+ const visualRadius = radius * scale;
1200
1307
  const absoluteHoles = (holes || []).map((h) => {
1201
- const p = Coordinate.denormalizePoint(
1202
- { x: h.x, y: h.y },
1308
+ const unit = this.unit || "mm";
1309
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1310
+ const pos = resolveHolePosition(
1311
+ {
1312
+ ...h,
1313
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1314
+ offsetY: (h.offsetY || 0) * unitScale * scale
1315
+ },
1316
+ { x: cx, y: cy, width: visualWidth, height: visualHeight },
1203
1317
  { width: canvasW, height: canvasH }
1204
1318
  );
1205
1319
  return {
1206
1320
  ...h,
1207
- x: p.x,
1208
- y: p.y
1321
+ x: pos.x,
1322
+ y: pos.y,
1323
+ innerRadius: h.innerRadius * unitScale * scale,
1324
+ outerRadius: h.outerRadius * unitScale * scale,
1325
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1326
+ offsetY: (h.offsetY || 0) * unitScale * scale
1209
1327
  };
1210
1328
  });
1211
1329
  const pathData = generateDielinePath({
1212
1330
  shape,
1213
- width,
1214
- height,
1215
- radius,
1331
+ width: visualWidth,
1332
+ height: visualHeight,
1333
+ radius: visualRadius,
1216
1334
  x: cx,
1217
1335
  y: cy,
1218
1336
  holes: absoluteHoles,
1219
1337
  pathData: this.pathData
1220
1338
  });
1339
+ const clonedLayer = await userLayer.clone();
1221
1340
  const clipPath = new import_fabric2.Path(pathData, {
1222
- left: 0,
1223
- top: 0,
1224
1341
  originX: "left",
1225
1342
  originY: "top",
1226
- absolutePositioned: true
1227
- });
1228
- const layer = this.getLayer();
1229
- const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
1230
- if (layer) layer.visible = false;
1231
- const holeMarkers = canvas.getObjects().filter((o) => {
1232
- var _a2;
1233
- return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
1234
- });
1235
- holeMarkers.forEach((o) => o.visible = false);
1236
- const rulerLayer = canvas.getObjects().find((obj) => {
1237
- var _a2;
1238
- return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
1239
- });
1240
- const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
1241
- if (rulerLayer) rulerLayer.visible = false;
1242
- const originalClip = canvas.clipPath;
1243
- canvas.clipPath = clipPath;
1244
- const bbox = clipPath.getBoundingRect();
1245
- const clipPathCorrected = new import_fabric2.Path(pathData, {
1246
- absolutePositioned: true,
1247
1343
  left: 0,
1248
- top: 0
1249
- });
1250
- const tempPath = new import_fabric2.Path(pathData);
1251
- const tempBounds = tempPath.getBoundingRect();
1252
- clipPathCorrected.set({
1253
- left: tempBounds.left,
1254
- top: tempBounds.top,
1255
- originX: "left",
1256
- originY: "top"
1344
+ top: 0,
1345
+ absolutePositioned: true
1346
+ // Important for groups
1257
1347
  });
1258
- canvas.clipPath = clipPathCorrected;
1259
- const exportBbox = clipPathCorrected.getBoundingRect();
1260
- const dataURL = canvas.toDataURL({
1348
+ clonedLayer.clipPath = clipPath;
1349
+ const bounds = clipPath.getBoundingRect();
1350
+ const dataUrl = clonedLayer.toDataURL({
1261
1351
  format: "png",
1262
1352
  multiplier: 2,
1263
- left: exportBbox.left,
1264
- top: exportBbox.top,
1265
- width: exportBbox.width,
1266
- height: exportBbox.height
1353
+ // Better quality
1354
+ left: bounds.left,
1355
+ top: bounds.top,
1356
+ width: bounds.width,
1357
+ height: bounds.height
1267
1358
  });
1268
- canvas.clipPath = originalClip;
1269
- if (layer) layer.visible = wasVisible;
1270
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
1271
- holeMarkers.forEach((o) => o.visible = true);
1272
- canvas.requestRenderAll();
1273
- return dataURL;
1359
+ return dataUrl;
1274
1360
  }
1275
1361
  };
1276
1362
 
@@ -1442,9 +1528,6 @@ var HoleTool = class {
1442
1528
  this.metadata = {
1443
1529
  name: "HoleTool"
1444
1530
  };
1445
- this.innerRadius = 15;
1446
- this.outerRadius = 25;
1447
- this.style = "solid";
1448
1531
  this.holes = [];
1449
1532
  this.constraintTarget = "bleed";
1450
1533
  this.isUpdatingConfig = false;
@@ -1468,53 +1551,20 @@ var HoleTool = class {
1468
1551
  "ConfigurationService"
1469
1552
  );
1470
1553
  if (configService) {
1471
- this.innerRadius = configService.get(
1472
- "hole.innerRadius",
1473
- this.innerRadius
1474
- );
1475
- this.outerRadius = configService.get(
1476
- "hole.outerRadius",
1477
- this.outerRadius
1478
- );
1479
- this.style = configService.get("hole.style", this.style);
1480
1554
  this.constraintTarget = configService.get(
1481
1555
  "hole.constraintTarget",
1482
1556
  this.constraintTarget
1483
1557
  );
1484
- const dielineHoles = configService.get("dieline.holes", []);
1485
- if (this.canvasService) {
1486
- const { width, height } = this.canvasService.canvas;
1487
- this.holes = dielineHoles.map((h) => {
1488
- const p = Coordinate.denormalizePoint(h, {
1489
- width: width || 800,
1490
- height: height || 600
1491
- });
1492
- return { x: p.x, y: p.y };
1493
- });
1494
- }
1558
+ this.holes = configService.get("dieline.holes", []);
1495
1559
  configService.onAnyChange((e) => {
1496
1560
  if (this.isUpdatingConfig) return;
1497
- if (e.key.startsWith("hole.")) {
1498
- const prop = e.key.split(".")[1];
1499
- if (prop && prop in this) {
1500
- this[prop] = e.value;
1501
- this.redraw();
1502
- this.syncHolesToDieline();
1503
- }
1561
+ if (e.key === "hole.constraintTarget") {
1562
+ this.constraintTarget = e.value;
1563
+ this.enforceConstraints();
1504
1564
  }
1505
1565
  if (e.key === "dieline.holes") {
1506
- const holes = e.value || [];
1507
- if (this.canvasService) {
1508
- const { width, height } = this.canvasService.canvas;
1509
- this.holes = holes.map((h) => {
1510
- const p = Coordinate.denormalizePoint(h, {
1511
- width: width || 800,
1512
- height: height || 600
1513
- });
1514
- return { x: p.x, y: p.y };
1515
- });
1516
- this.redraw();
1517
- }
1566
+ this.holes = e.value || [];
1567
+ this.redraw();
1518
1568
  }
1519
1569
  });
1520
1570
  }
@@ -1528,29 +1578,6 @@ var HoleTool = class {
1528
1578
  contribute() {
1529
1579
  return {
1530
1580
  [import_core4.ContributionPointIds.CONFIGURATIONS]: [
1531
- {
1532
- id: "hole.innerRadius",
1533
- type: "number",
1534
- label: "Inner Radius",
1535
- min: 1,
1536
- max: 100,
1537
- default: 15
1538
- },
1539
- {
1540
- id: "hole.outerRadius",
1541
- type: "number",
1542
- label: "Outer Radius",
1543
- min: 1,
1544
- max: 100,
1545
- default: 25
1546
- },
1547
- {
1548
- id: "hole.style",
1549
- type: "select",
1550
- label: "Line Style",
1551
- options: ["solid", "dashed"],
1552
- default: "solid"
1553
- },
1554
1581
  {
1555
1582
  id: "hole.constraintTarget",
1556
1583
  type: "select",
@@ -1564,6 +1591,7 @@ var HoleTool = class {
1564
1591
  command: "resetHoles",
1565
1592
  title: "Reset Holes",
1566
1593
  handler: () => {
1594
+ var _a;
1567
1595
  if (!this.canvasService) return false;
1568
1596
  let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1569
1597
  if (this.currentGeometry) {
@@ -1574,12 +1602,24 @@ var HoleTool = class {
1574
1602
  holes: []
1575
1603
  });
1576
1604
  }
1577
- this.innerRadius = 15;
1578
- this.outerRadius = 25;
1579
- this.style = "solid";
1580
- this.holes = [defaultPos];
1581
- this.redraw();
1582
- this.syncHolesToDieline();
1605
+ const { width, height } = this.canvasService.canvas;
1606
+ const normalizedHole = Coordinate.normalizePoint(defaultPos, {
1607
+ width: width || 800,
1608
+ height: height || 600
1609
+ });
1610
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1611
+ "ConfigurationService"
1612
+ );
1613
+ if (configService) {
1614
+ configService.update("dieline.holes", [
1615
+ {
1616
+ x: normalizedHole.x,
1617
+ y: normalizedHole.y,
1618
+ innerRadius: 15,
1619
+ outerRadius: 25
1620
+ }
1621
+ ]);
1622
+ }
1583
1623
  return true;
1584
1624
  }
1585
1625
  },
@@ -1587,10 +1627,37 @@ var HoleTool = class {
1587
1627
  command: "addHole",
1588
1628
  title: "Add Hole",
1589
1629
  handler: (x, y) => {
1590
- if (!this.holes) this.holes = [];
1591
- this.holes.push({ x, y });
1592
- this.redraw();
1593
- this.syncHolesToDieline();
1630
+ var _a, _b, _c;
1631
+ if (!this.canvasService) return false;
1632
+ let normalizedX = 0.5;
1633
+ let normalizedY = 0.5;
1634
+ if (this.currentGeometry) {
1635
+ const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
1636
+ const left = gx - gw / 2;
1637
+ const top = gy - gh / 2;
1638
+ normalizedX = gw > 0 ? (x - left) / gw : 0.5;
1639
+ normalizedY = gh > 0 ? (y - top) / gh : 0.5;
1640
+ } else {
1641
+ const { width, height } = this.canvasService.canvas;
1642
+ normalizedX = Coordinate.toNormalized(x, width || 800);
1643
+ normalizedY = Coordinate.toNormalized(y, height || 600);
1644
+ }
1645
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1646
+ "ConfigurationService"
1647
+ );
1648
+ if (configService) {
1649
+ const currentHoles = configService.get("dieline.holes", []);
1650
+ const lastHole = currentHoles[currentHoles.length - 1];
1651
+ const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
1652
+ const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
1653
+ const newHole = {
1654
+ x: normalizedX,
1655
+ y: normalizedY,
1656
+ innerRadius,
1657
+ outerRadius
1658
+ };
1659
+ configService.update("dieline.holes", [...currentHoles, newHole]);
1660
+ }
1594
1661
  return true;
1595
1662
  }
1596
1663
  },
@@ -1598,9 +1665,13 @@ var HoleTool = class {
1598
1665
  command: "clearHoles",
1599
1666
  title: "Clear Holes",
1600
1667
  handler: () => {
1601
- this.holes = [];
1602
- this.redraw();
1603
- this.syncHolesToDieline();
1668
+ var _a;
1669
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1670
+ "ConfigurationService"
1671
+ );
1672
+ if (configService) {
1673
+ configService.update("dieline.holes", []);
1674
+ }
1604
1675
  return true;
1605
1676
  }
1606
1677
  }
@@ -1641,10 +1712,12 @@ var HoleTool = class {
1641
1712
  }
1642
1713
  if (!this.handleMoving) {
1643
1714
  this.handleMoving = (e) => {
1644
- var _a;
1715
+ var _a, _b, _c, _d, _e;
1645
1716
  const target = e.target;
1646
1717
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1647
1718
  if (!this.currentGeometry) return;
1719
+ const index = (_c = (_b = target.data) == null ? void 0 : _b.index) != null ? _c : -1;
1720
+ const holeData = this.holes[index];
1648
1721
  const effectiveOffset = this.constraintTarget === "original" ? 0 : this.currentGeometry.offset;
1649
1722
  const constraintGeometry = {
1650
1723
  ...this.currentGeometry,
@@ -1656,7 +1729,12 @@ var HoleTool = class {
1656
1729
  radius: Math.max(0, this.currentGeometry.radius + effectiveOffset)
1657
1730
  };
1658
1731
  const p = new import_fabric4.Point(target.left, target.top);
1659
- const newPos = this.calculateConstrainedPosition(p, constraintGeometry);
1732
+ const newPos = this.calculateConstrainedPosition(
1733
+ p,
1734
+ constraintGeometry,
1735
+ (_d = holeData == null ? void 0 : holeData.innerRadius) != null ? _d : 15,
1736
+ (_e = holeData == null ? void 0 : holeData.outerRadius) != null ? _e : 25
1737
+ );
1660
1738
  target.set({
1661
1739
  left: newPos.x,
1662
1740
  top: newPos.y
@@ -1669,7 +1747,10 @@ var HoleTool = class {
1669
1747
  var _a;
1670
1748
  const target = e.target;
1671
1749
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1672
- this.syncHolesFromCanvas();
1750
+ const changed = this.enforceConstraints();
1751
+ if (!changed) {
1752
+ this.syncHolesFromCanvas();
1753
+ }
1673
1754
  };
1674
1755
  canvas.on("object:modified", this.handleModified);
1675
1756
  }
@@ -1677,19 +1758,6 @@ var HoleTool = class {
1677
1758
  }
1678
1759
  initializeHoles() {
1679
1760
  if (!this.canvasService) return;
1680
- if (!this.holes || this.holes.length === 0) {
1681
- let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1682
- if (this.currentGeometry) {
1683
- const g = this.currentGeometry;
1684
- const topCenter = { x: g.x, y: g.y - g.height / 2 };
1685
- const snapped = getNearestPointOnDieline(topCenter, {
1686
- ...g,
1687
- holes: []
1688
- });
1689
- defaultPos = snapped;
1690
- }
1691
- this.holes = [defaultPos];
1692
- }
1693
1761
  this.redraw();
1694
1762
  this.syncHolesToDieline();
1695
1763
  }
@@ -1733,32 +1801,110 @@ var HoleTool = class {
1733
1801
  var _a;
1734
1802
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1735
1803
  });
1736
- const holes = objects.map((obj) => ({ x: obj.left, y: obj.top }));
1737
- this.holes = holes;
1804
+ objects.sort(
1805
+ (a, b) => {
1806
+ var _a, _b, _c, _d;
1807
+ 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);
1808
+ }
1809
+ );
1810
+ const newHoles = objects.map((obj, i) => {
1811
+ var _a, _b, _c, _d;
1812
+ const original = this.holes[i];
1813
+ const newAbsX = obj.left;
1814
+ const newAbsY = obj.top;
1815
+ const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
1816
+ const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
1817
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1818
+ if (original && original.anchor && this.currentGeometry) {
1819
+ const { x, y, width, height } = this.currentGeometry;
1820
+ let bx = x;
1821
+ let by = y;
1822
+ const left = x - width / 2;
1823
+ const right = x + width / 2;
1824
+ const top = y - height / 2;
1825
+ const bottom = y + height / 2;
1826
+ switch (original.anchor) {
1827
+ case "top-left":
1828
+ bx = left;
1829
+ by = top;
1830
+ break;
1831
+ case "top-center":
1832
+ bx = x;
1833
+ by = top;
1834
+ break;
1835
+ case "top-right":
1836
+ bx = right;
1837
+ by = top;
1838
+ break;
1839
+ case "center-left":
1840
+ bx = left;
1841
+ by = y;
1842
+ break;
1843
+ case "center":
1844
+ bx = x;
1845
+ by = y;
1846
+ break;
1847
+ case "center-right":
1848
+ bx = right;
1849
+ by = y;
1850
+ break;
1851
+ case "bottom-left":
1852
+ bx = left;
1853
+ by = bottom;
1854
+ break;
1855
+ case "bottom-center":
1856
+ bx = x;
1857
+ by = bottom;
1858
+ break;
1859
+ case "bottom-right":
1860
+ bx = right;
1861
+ by = bottom;
1862
+ break;
1863
+ }
1864
+ return {
1865
+ ...original,
1866
+ // Denormalize offset back to physical units (mm)
1867
+ offsetX: (newAbsX - bx) / scale / unitScale,
1868
+ offsetY: (newAbsY - by) / scale / unitScale,
1869
+ // Clear direct coordinates if we use anchor
1870
+ x: void 0,
1871
+ y: void 0
1872
+ };
1873
+ }
1874
+ let normalizedX = 0.5;
1875
+ let normalizedY = 0.5;
1876
+ if (this.currentGeometry) {
1877
+ const { x, y, width, height } = this.currentGeometry;
1878
+ const left = x - width / 2;
1879
+ const top = y - height / 2;
1880
+ normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
1881
+ normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
1882
+ } else {
1883
+ const { width, height } = this.canvasService.canvas;
1884
+ normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
1885
+ normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
1886
+ }
1887
+ return {
1888
+ ...original,
1889
+ x: normalizedX,
1890
+ y: normalizedY,
1891
+ // Ensure radii are preserved
1892
+ innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
1893
+ outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25
1894
+ };
1895
+ });
1896
+ this.holes = newHoles;
1738
1897
  this.syncHolesToDieline();
1739
1898
  }
1740
1899
  syncHolesToDieline() {
1741
1900
  if (!this.context || !this.canvasService) return;
1742
- const { holes, innerRadius, outerRadius } = this;
1743
- const currentHoles = holes || [];
1744
- const width = this.canvasService.canvas.width || 800;
1745
- const height = this.canvasService.canvas.height || 600;
1746
1901
  const configService = this.context.services.get(
1747
1902
  "ConfigurationService"
1748
1903
  );
1749
1904
  if (configService) {
1750
1905
  this.isUpdatingConfig = true;
1751
1906
  try {
1752
- const normalizedHoles = currentHoles.map((h) => {
1753
- const p = Coordinate.normalizePoint(h, { width, height });
1754
- return {
1755
- x: p.x,
1756
- y: p.y,
1757
- innerRadius,
1758
- outerRadius
1759
- };
1760
- });
1761
- configService.update("dieline.holes", normalizedHoles);
1907
+ configService.update("dieline.holes", this.holes);
1762
1908
  } finally {
1763
1909
  this.isUpdatingConfig = false;
1764
1910
  }
@@ -1767,19 +1913,43 @@ var HoleTool = class {
1767
1913
  redraw() {
1768
1914
  if (!this.canvasService) return;
1769
1915
  const canvas = this.canvasService.canvas;
1916
+ const { width, height } = canvas;
1770
1917
  const existing = canvas.getObjects().filter((obj) => {
1771
1918
  var _a;
1772
1919
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1773
1920
  });
1774
1921
  existing.forEach((obj) => canvas.remove(obj));
1775
- const { innerRadius, outerRadius, style, holes } = this;
1922
+ const holes = this.holes;
1776
1923
  if (!holes || holes.length === 0) {
1777
1924
  this.canvasService.requestRenderAll();
1778
1925
  return;
1779
1926
  }
1927
+ const geometry = this.currentGeometry || {
1928
+ x: (width || 800) / 2,
1929
+ y: (height || 600) / 2,
1930
+ width: width || 800,
1931
+ height: height || 600,
1932
+ scale: 1
1933
+ // Default scale if no geometry loaded
1934
+ };
1780
1935
  holes.forEach((hole, index) => {
1936
+ const scale = geometry.scale || 1;
1937
+ const unit = geometry.unit || "mm";
1938
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1939
+ const visualInnerRadius = hole.innerRadius * unitScale * scale;
1940
+ const visualOuterRadius = hole.outerRadius * unitScale * scale;
1941
+ const pos = resolveHolePosition(
1942
+ {
1943
+ ...hole,
1944
+ offsetX: (hole.offsetX || 0) * unitScale * scale,
1945
+ offsetY: (hole.offsetY || 0) * unitScale * scale
1946
+ },
1947
+ geometry,
1948
+ { width: geometry.width, height: geometry.height }
1949
+ // Use geometry dims instead of canvas
1950
+ );
1781
1951
  const innerCircle = new import_fabric4.Circle({
1782
- radius: innerRadius,
1952
+ radius: visualInnerRadius,
1783
1953
  fill: "transparent",
1784
1954
  stroke: "red",
1785
1955
  strokeWidth: 2,
@@ -1787,17 +1957,17 @@ var HoleTool = class {
1787
1957
  originY: "center"
1788
1958
  });
1789
1959
  const outerCircle = new import_fabric4.Circle({
1790
- radius: outerRadius,
1960
+ radius: visualOuterRadius,
1791
1961
  fill: "transparent",
1792
1962
  stroke: "#666",
1793
1963
  strokeWidth: 1,
1794
- strokeDashArray: style === "dashed" ? [5, 5] : void 0,
1964
+ strokeDashArray: [5, 5],
1795
1965
  originX: "center",
1796
1966
  originY: "center"
1797
1967
  });
1798
1968
  const holeGroup = new import_fabric4.Group([outerCircle, innerCircle], {
1799
- left: hole.x,
1800
- top: hole.y,
1969
+ left: pos.x,
1970
+ top: pos.y,
1801
1971
  originX: "center",
1802
1972
  originY: "center",
1803
1973
  selectable: true,
@@ -1842,9 +2012,6 @@ var HoleTool = class {
1842
2012
  enforceConstraints() {
1843
2013
  const geometry = this.currentGeometry;
1844
2014
  if (!geometry || !this.canvasService) {
1845
- console.log(
1846
- "[HoleTool] Skipping enforceConstraints: No geometry or canvas service"
1847
- );
1848
2015
  return false;
1849
2016
  }
1850
2017
  const effectiveOffset = this.constraintTarget === "original" ? 0 : geometry.offset;
@@ -1858,9 +2025,6 @@ var HoleTool = class {
1858
2025
  var _a;
1859
2026
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1860
2027
  });
1861
- console.log(
1862
- `[HoleTool] Enforcing constraints on ${objects.length} markers`
1863
- );
1864
2028
  let changed = false;
1865
2029
  objects.sort(
1866
2030
  (a, b) => {
@@ -1869,16 +2033,22 @@ var HoleTool = class {
1869
2033
  }
1870
2034
  );
1871
2035
  const newHoles = [];
1872
- objects.forEach((obj) => {
2036
+ objects.forEach((obj, i) => {
2037
+ var _a, _b;
1873
2038
  const currentPos = new import_fabric4.Point(obj.left, obj.top);
2039
+ const holeData = this.holes[i];
2040
+ const scale = geometry.scale || 1;
2041
+ const unit = geometry.unit || "mm";
2042
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
2043
+ const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
2044
+ const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
1874
2045
  const newPos = this.calculateConstrainedPosition(
1875
2046
  currentPos,
1876
- constraintGeometry
2047
+ constraintGeometry,
2048
+ innerR,
2049
+ outerR
1877
2050
  );
1878
2051
  if (currentPos.distanceFrom(newPos) > 0.1) {
1879
- console.log(
1880
- `[HoleTool] Moving hole from (${currentPos.x}, ${currentPos.y}) to (${newPos.x}, ${newPos.y})`
1881
- );
1882
2052
  obj.set({
1883
2053
  left: newPos.x,
1884
2054
  top: newPos.y
@@ -1886,16 +2056,14 @@ var HoleTool = class {
1886
2056
  obj.setCoords();
1887
2057
  changed = true;
1888
2058
  }
1889
- newHoles.push({ x: obj.left, y: obj.top });
1890
2059
  });
1891
2060
  if (changed) {
1892
- this.holes = newHoles;
1893
- this.canvasService.requestRenderAll();
2061
+ this.syncHolesFromCanvas();
1894
2062
  return true;
1895
2063
  }
1896
2064
  return false;
1897
2065
  }
1898
- calculateConstrainedPosition(p, g) {
2066
+ calculateConstrainedPosition(p, g, innerRadius, outerRadius) {
1899
2067
  const options = {
1900
2068
  ...g,
1901
2069
  holes: []
@@ -1909,7 +2077,6 @@ var HoleTool = class {
1909
2077
  const dist = p.distanceFrom(nearestP);
1910
2078
  const v = p.subtract(nearestP);
1911
2079
  const center = new import_fabric4.Point(g.x, g.y);
1912
- const centerToNearest = nearestP.subtract(center);
1913
2080
  const distToCenter = p.distanceFrom(center);
1914
2081
  const nearestDistToCenter = nearestP.distanceFrom(center);
1915
2082
  let signedDist = dist;
@@ -1918,9 +2085,9 @@ var HoleTool = class {
1918
2085
  }
1919
2086
  let clampedDist = signedDist;
1920
2087
  if (signedDist > 0) {
1921
- clampedDist = Math.min(signedDist, this.innerRadius);
2088
+ clampedDist = Math.min(signedDist, innerRadius);
1922
2089
  } else {
1923
- clampedDist = Math.max(signedDist, -this.outerRadius);
2090
+ clampedDist = Math.max(signedDist, -outerRadius);
1924
2091
  }
1925
2092
  if (dist < 1e-3) return nearestP;
1926
2093
  const scale = Math.abs(clampedDist) / (dist || 1);
@@ -1933,17 +2100,14 @@ var HoleTool = class {
1933
2100
  var import_core5 = require("@pooder/core");
1934
2101
  var import_fabric5 = require("fabric");
1935
2102
  var ImageTool = class {
1936
- constructor(options) {
2103
+ constructor() {
1937
2104
  this.id = "pooder.kit.image";
1938
2105
  this.metadata = {
1939
2106
  name: "ImageTool"
1940
2107
  };
1941
- this._loadingUrl = null;
1942
- this.url = "";
1943
- this.opacity = 1;
1944
- if (options) {
1945
- Object.assign(this, options);
1946
- }
2108
+ this.items = [];
2109
+ this.objectMap = /* @__PURE__ */ new Map();
2110
+ this.isUpdatingConfig = false;
1947
2111
  }
1948
2112
  activate(context) {
1949
2113
  this.context = context;
@@ -1954,38 +2118,33 @@ var ImageTool = class {
1954
2118
  }
1955
2119
  const configService = context.services.get("ConfigurationService");
1956
2120
  if (configService) {
1957
- this.url = configService.get("image.url", this.url);
1958
- this.opacity = configService.get("image.opacity", this.opacity);
1959
- this.width = configService.get("image.width", this.width);
1960
- this.height = configService.get("image.height", this.height);
1961
- this.angle = configService.get("image.angle", this.angle);
1962
- this.left = configService.get("image.left", this.left);
1963
- this.top = configService.get("image.top", this.top);
2121
+ this.items = configService.get("image.items", []) || [];
1964
2122
  configService.onAnyChange((e) => {
1965
- if (e.key.startsWith("image.")) {
1966
- const prop = e.key.split(".")[1];
1967
- console.log(
1968
- `[ImageTool] Config change detected: ${e.key} -> ${e.value}`
1969
- );
1970
- if (prop && prop in this) {
1971
- this[prop] = e.value;
1972
- this.updateImage();
1973
- }
2123
+ if (this.isUpdatingConfig) return;
2124
+ let shouldUpdate = false;
2125
+ if (e.key === "image.items") {
2126
+ this.items = e.value || [];
2127
+ shouldUpdate = true;
2128
+ } else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
2129
+ shouldUpdate = true;
2130
+ }
2131
+ if (shouldUpdate) {
2132
+ this.updateImages();
1974
2133
  }
1975
2134
  });
1976
2135
  }
1977
2136
  this.ensureLayer();
1978
- this.updateImage();
2137
+ this.updateImages();
1979
2138
  }
1980
2139
  deactivate(context) {
1981
2140
  if (this.canvasService) {
1982
2141
  const layer = this.canvasService.getLayer("user");
1983
2142
  if (layer) {
1984
- const userImage = this.canvasService.getObject("user-image", "user");
1985
- if (userImage) {
1986
- layer.remove(userImage);
1987
- this.canvasService.requestRenderAll();
1988
- }
2143
+ this.objectMap.forEach((obj) => {
2144
+ layer.remove(obj);
2145
+ });
2146
+ this.objectMap.clear();
2147
+ this.canvasService.requestRenderAll();
1989
2148
  }
1990
2149
  this.canvasService = void 0;
1991
2150
  this.context = void 0;
@@ -1995,82 +2154,103 @@ var ImageTool = class {
1995
2154
  return {
1996
2155
  [import_core5.ContributionPointIds.CONFIGURATIONS]: [
1997
2156
  {
1998
- id: "image.url",
1999
- type: "string",
2000
- label: "Image URL",
2001
- default: this.url
2002
- },
2157
+ id: "image.items",
2158
+ type: "array",
2159
+ label: "Images",
2160
+ default: []
2161
+ }
2162
+ ],
2163
+ [import_core5.ContributionPointIds.COMMANDS]: [
2003
2164
  {
2004
- id: "image.opacity",
2005
- type: "number",
2006
- label: "Opacity",
2007
- min: 0,
2008
- max: 1,
2009
- step: 0.1,
2010
- default: this.opacity
2165
+ command: "addImage",
2166
+ title: "Add Image",
2167
+ handler: (url, options) => {
2168
+ const newItem = {
2169
+ id: this.generateId(),
2170
+ url,
2171
+ opacity: 1,
2172
+ ...options
2173
+ };
2174
+ this.updateConfig([...this.items, newItem]);
2175
+ return newItem.id;
2176
+ }
2011
2177
  },
2012
2178
  {
2013
- id: "image.width",
2014
- type: "number",
2015
- label: "Width",
2016
- min: 0,
2017
- max: 5e3,
2018
- default: this.width
2179
+ command: "removeImage",
2180
+ title: "Remove Image",
2181
+ handler: (id) => {
2182
+ const newItems = this.items.filter((item) => item.id !== id);
2183
+ if (newItems.length !== this.items.length) {
2184
+ this.updateConfig(newItems);
2185
+ }
2186
+ }
2019
2187
  },
2020
2188
  {
2021
- id: "image.height",
2022
- type: "number",
2023
- label: "Height",
2024
- min: 0,
2025
- max: 5e3,
2026
- default: this.height
2189
+ command: "updateImage",
2190
+ title: "Update Image",
2191
+ handler: (id, updates) => {
2192
+ const index = this.items.findIndex((item) => item.id === id);
2193
+ if (index !== -1) {
2194
+ const newItems = [...this.items];
2195
+ newItems[index] = { ...newItems[index], ...updates };
2196
+ this.updateConfig(newItems);
2197
+ }
2198
+ }
2027
2199
  },
2028
2200
  {
2029
- id: "image.angle",
2030
- type: "number",
2031
- label: "Rotation",
2032
- min: 0,
2033
- max: 360,
2034
- default: this.angle
2201
+ command: "clearImages",
2202
+ title: "Clear Images",
2203
+ handler: () => {
2204
+ this.updateConfig([]);
2205
+ }
2035
2206
  },
2036
2207
  {
2037
- id: "image.left",
2038
- type: "number",
2039
- label: "Left (Normalized)",
2040
- min: 0,
2041
- max: 1,
2042
- default: this.left
2208
+ command: "bringToFront",
2209
+ title: "Bring Image to Front",
2210
+ handler: (id) => {
2211
+ const index = this.items.findIndex((item) => item.id === id);
2212
+ if (index !== -1 && index < this.items.length - 1) {
2213
+ const newItems = [...this.items];
2214
+ const [item] = newItems.splice(index, 1);
2215
+ newItems.push(item);
2216
+ this.updateConfig(newItems);
2217
+ }
2218
+ }
2043
2219
  },
2044
2220
  {
2045
- id: "image.top",
2046
- type: "number",
2047
- label: "Top (Normalized)",
2048
- min: 0,
2049
- max: 1,
2050
- default: this.top
2051
- }
2052
- ],
2053
- [import_core5.ContributionPointIds.COMMANDS]: [
2054
- {
2055
- command: "setUserImage",
2056
- title: "Set User Image",
2057
- handler: (url, opacity, width, height, angle, left, top) => {
2058
- if (this.url === url && this.opacity === opacity && this.width === width && this.height === height && this.angle === angle && this.left === left && this.top === top)
2059
- return true;
2060
- this.url = url;
2061
- this.opacity = opacity;
2062
- this.width = width;
2063
- this.height = height;
2064
- this.angle = angle;
2065
- this.left = left;
2066
- this.top = top;
2067
- this.updateImage();
2068
- return true;
2221
+ command: "sendToBack",
2222
+ title: "Send Image to Back",
2223
+ handler: (id) => {
2224
+ const index = this.items.findIndex((item) => item.id === id);
2225
+ if (index > 0) {
2226
+ const newItems = [...this.items];
2227
+ const [item] = newItems.splice(index, 1);
2228
+ newItems.unshift(item);
2229
+ this.updateConfig(newItems);
2230
+ }
2069
2231
  }
2070
2232
  }
2071
2233
  ]
2072
2234
  };
2073
2235
  }
2236
+ generateId() {
2237
+ return Math.random().toString(36).substring(2, 9);
2238
+ }
2239
+ updateConfig(newItems, skipCanvasUpdate = false) {
2240
+ if (!this.context) return;
2241
+ this.isUpdatingConfig = true;
2242
+ this.items = newItems;
2243
+ const configService = this.context.services.get("ConfigurationService");
2244
+ if (configService) {
2245
+ configService.update("image.items", newItems);
2246
+ }
2247
+ if (!skipCanvasUpdate) {
2248
+ this.updateImages();
2249
+ }
2250
+ setTimeout(() => {
2251
+ this.isUpdatingConfig = false;
2252
+ }, 50);
2253
+ }
2074
2254
  ensureLayer() {
2075
2255
  if (!this.canvasService) return;
2076
2256
  let userLayer = this.canvasService.getLayer("user");
@@ -2102,224 +2282,176 @@ var ImageTool = class {
2102
2282
  this.canvasService.requestRenderAll();
2103
2283
  }
2104
2284
  }
2105
- updateImage() {
2285
+ getLayoutInfo() {
2106
2286
  var _a, _b;
2287
+ const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
2288
+ const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
2289
+ let layoutScale = 1;
2290
+ let layoutOffsetX = 0;
2291
+ let layoutOffsetY = 0;
2292
+ let visualWidth = canvasW;
2293
+ let visualHeight = canvasH;
2294
+ let dielinePhysicalWidth = 500;
2295
+ let dielinePhysicalHeight = 500;
2296
+ if (this.context) {
2297
+ const configService = this.context.services.get("ConfigurationService");
2298
+ if (configService) {
2299
+ dielinePhysicalWidth = configService.get("dieline.width") || 500;
2300
+ dielinePhysicalHeight = configService.get("dieline.height") || 500;
2301
+ const padding = configService.get("dieline.padding") || 40;
2302
+ const layout = Coordinate.calculateLayout(
2303
+ { width: canvasW, height: canvasH },
2304
+ { width: dielinePhysicalWidth, height: dielinePhysicalHeight },
2305
+ padding
2306
+ );
2307
+ layoutScale = layout.scale;
2308
+ layoutOffsetX = layout.offsetX;
2309
+ layoutOffsetY = layout.offsetY;
2310
+ visualWidth = layout.width;
2311
+ visualHeight = layout.height;
2312
+ }
2313
+ }
2314
+ return {
2315
+ layoutScale,
2316
+ layoutOffsetX,
2317
+ layoutOffsetY,
2318
+ visualWidth,
2319
+ visualHeight,
2320
+ dielinePhysicalWidth,
2321
+ dielinePhysicalHeight
2322
+ };
2323
+ }
2324
+ updateImages() {
2107
2325
  if (!this.canvasService) return;
2108
- let { url, opacity, width, height, angle, left, top } = this;
2109
2326
  const layer = this.canvasService.getLayer("user");
2110
2327
  if (!layer) {
2111
2328
  console.warn("[ImageTool] User layer not found");
2112
2329
  return;
2113
2330
  }
2114
- const userImage = this.canvasService.getObject("user-image", "user");
2115
- if (this._loadingUrl === url) return;
2116
- if (userImage) {
2117
- const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
2118
- if (currentSrc !== url) {
2119
- this.loadImage(layer);
2331
+ const currentIds = new Set(this.items.map((i) => i.id));
2332
+ for (const [id, obj] of this.objectMap) {
2333
+ if (!currentIds.has(id)) {
2334
+ layer.remove(obj);
2335
+ this.objectMap.delete(id);
2336
+ }
2337
+ }
2338
+ const layout = this.getLayoutInfo();
2339
+ this.items.forEach((item, index) => {
2340
+ let obj = this.objectMap.get(item.id);
2341
+ if (!obj) {
2342
+ this.loadImage(item, layer, layout);
2120
2343
  } else {
2121
- const updates = {};
2122
- const canvasW = this.canvasService.canvas.width || 800;
2123
- const canvasH = this.canvasService.canvas.height || 600;
2124
- const centerX = canvasW / 2;
2125
- const centerY = canvasH / 2;
2126
- if (userImage.opacity !== opacity) updates.opacity = opacity;
2127
- if (angle !== void 0 && userImage.angle !== angle)
2128
- updates.angle = angle;
2129
- if (userImage.originX !== "center") {
2130
- userImage.set({
2131
- originX: "center",
2132
- originY: "center",
2133
- left: userImage.left + userImage.width * userImage.scaleX / 2,
2134
- top: userImage.top + userImage.height * userImage.scaleY / 2
2135
- });
2136
- }
2137
- if (left !== void 0) {
2138
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2139
- const localLeft = globalLeft - centerX;
2140
- if (Math.abs(userImage.left - localLeft) > 1)
2141
- updates.left = localLeft;
2142
- }
2143
- if (top !== void 0) {
2144
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2145
- const localTop = globalTop - centerY;
2146
- if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
2147
- }
2148
- if (width !== void 0 && userImage.width)
2149
- updates.scaleX = width / userImage.width;
2150
- if (height !== void 0 && userImage.height)
2151
- updates.scaleY = height / userImage.height;
2152
- if (Object.keys(updates).length > 0) {
2153
- userImage.set(updates);
2154
- layer.dirty = true;
2155
- this.canvasService.requestRenderAll();
2156
- }
2344
+ this.updateObjectProperties(obj, item, layout);
2345
+ layer.remove(obj);
2346
+ layer.add(obj);
2157
2347
  }
2158
- } else {
2159
- this.loadImage(layer);
2348
+ });
2349
+ layer.dirty = true;
2350
+ this.canvasService.requestRenderAll();
2351
+ }
2352
+ updateObjectProperties(obj, item, layout) {
2353
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2354
+ const updates = {};
2355
+ if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
2356
+ if (item.angle !== void 0 && obj.angle !== item.angle) updates.angle = item.angle;
2357
+ if (item.left !== void 0) {
2358
+ const globalLeft = layoutOffsetX + item.left * visualWidth;
2359
+ if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
2360
+ }
2361
+ if (item.top !== void 0) {
2362
+ const globalTop = layoutOffsetY + item.top * visualHeight;
2363
+ if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
2364
+ }
2365
+ if (item.width !== void 0 && obj.width) {
2366
+ const targetScaleX = item.width * layoutScale / obj.width;
2367
+ if (Math.abs(obj.scaleX - targetScaleX) > 1e-3) updates.scaleX = targetScaleX;
2368
+ }
2369
+ if (item.height !== void 0 && obj.height) {
2370
+ const targetScaleY = item.height * layoutScale / obj.height;
2371
+ if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
2372
+ }
2373
+ if (obj.originX !== "center") {
2374
+ updates.originX = "center";
2375
+ updates.originY = "center";
2376
+ }
2377
+ if (Object.keys(updates).length > 0) {
2378
+ obj.set(updates);
2160
2379
  }
2161
2380
  }
2162
- loadImage(layer) {
2163
- if (!this.canvasService) return;
2164
- const { url } = this;
2165
- if (!url) return;
2166
- this._loadingUrl = url;
2167
- import_fabric5.FabricImage.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2168
- var _a, _b, _c, _d, _e, _f;
2169
- if (this._loadingUrl !== url) return;
2170
- this._loadingUrl = null;
2171
- let { opacity, width, height, angle, left, top } = this;
2172
- if (this.context) {
2173
- const configService = this.context.services.get(
2174
- "ConfigurationService"
2175
- );
2176
- const dielineWidth = configService.get("dieline.width");
2177
- const dielineHeight = configService.get("dieline.height");
2178
- console.log(
2179
- "[ImageTool] Dieline config debug:",
2180
- {
2181
- widthVal: dielineWidth,
2182
- heightVal: dielineHeight,
2183
- // Debug: dump all keys to see what is available
2184
- allKeys: Array.from(
2185
- ((_a = configService.configValues) == null ? void 0 : _a.keys()) || []
2186
- )
2187
- },
2188
- configService
2189
- );
2190
- if (width === void 0 && height === void 0) {
2191
- const scale = Math.min(
2192
- dielineWidth / (image.width || 1),
2193
- dielineHeight / (image.height || 1)
2194
- );
2195
- width = (image.width || 1) * scale;
2196
- height = (image.height || 1) * scale;
2197
- this.width = width;
2198
- this.height = height;
2199
- }
2200
- if (left === void 0 && top === void 0) {
2201
- const dielinePos = configService == null ? void 0 : configService.get("dieline.position");
2202
- if (dielinePos) {
2203
- this.left = dielinePos.x;
2204
- this.top = dielinePos.y;
2205
- } else {
2206
- this.left = 0.5;
2207
- this.top = 0.5;
2208
- }
2209
- left = this.left;
2210
- top = this.top;
2211
- }
2212
- }
2213
- const existingImage = this.canvasService.getObject(
2214
- "user-image",
2215
- "user"
2216
- );
2217
- if (existingImage) {
2218
- const defaultLeft = existingImage.left;
2219
- const defaultTop = existingImage.top;
2220
- const defaultAngle = existingImage.angle;
2221
- const defaultScaleX = existingImage.scaleX;
2222
- const defaultScaleY = existingImage.scaleY;
2223
- const canvasW = ((_b = this.canvasService) == null ? void 0 : _b.canvas.width) || 800;
2224
- const canvasH = ((_c = this.canvasService) == null ? void 0 : _c.canvas.height) || 600;
2225
- const centerX = canvasW / 2;
2226
- const centerY = canvasH / 2;
2227
- let targetLeft = left !== void 0 ? left : defaultLeft;
2228
- let targetTop = top !== void 0 ? top : defaultTop;
2229
- const configService = (_d = this.context) == null ? void 0 : _d.services.get(
2230
- "ConfigurationService"
2231
- );
2232
- console.log("[ImageTool] Loading EXISTING image...", {
2233
- canvasW,
2234
- canvasH,
2235
- centerX,
2236
- centerY,
2237
- incomingLeft: left,
2238
- incomingTop: top,
2239
- dielinePos: configService == null ? void 0 : configService.get("dieline.position"),
2240
- existingImage: !!existingImage
2241
- });
2242
- if (left !== void 0) {
2243
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2244
- targetLeft = globalLeft;
2245
- console.log("[ImageTool] Calculated targetLeft", {
2246
- globalLeft,
2247
- targetLeft
2248
- });
2249
- }
2250
- if (top !== void 0) {
2251
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2252
- targetTop = globalTop;
2253
- console.log("[ImageTool] Calculated targetTop", {
2254
- globalTop,
2255
- targetTop
2256
- });
2257
- }
2258
- image.set({
2259
- originX: "center",
2260
- // Use center origin for easier positioning
2261
- originY: "center",
2262
- left: targetLeft,
2263
- top: targetTop,
2264
- angle: angle !== void 0 ? angle : defaultAngle,
2265
- scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
2266
- scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
2267
- });
2268
- layer.remove(existingImage);
2269
- } else {
2270
- image.set({
2271
- originX: "center",
2272
- originY: "center"
2273
- });
2274
- if (width !== void 0 && image.width)
2275
- image.scaleX = width / image.width;
2276
- if (height !== void 0 && image.height)
2277
- image.scaleY = height / image.height;
2278
- if (angle !== void 0) image.angle = angle;
2279
- const canvasW = ((_e = this.canvasService) == null ? void 0 : _e.canvas.width) || 800;
2280
- const canvasH = ((_f = this.canvasService) == null ? void 0 : _f.canvas.height) || 600;
2281
- const centerX = canvasW / 2;
2282
- const centerY = canvasH / 2;
2283
- if (left !== void 0) {
2284
- image.left = Coordinate.toAbsolute(left, canvasW);
2285
- } else {
2286
- image.left = centerX;
2287
- }
2288
- if (top !== void 0) {
2289
- image.top = Coordinate.toAbsolute(top, canvasH);
2381
+ loadImage(item, layer, layout) {
2382
+ import_fabric5.Image.fromURL(item.url, { crossOrigin: "anonymous" }).then((image) => {
2383
+ var _a;
2384
+ if (!this.items.find((i) => i.id === item.id)) return;
2385
+ image.set({
2386
+ originX: "center",
2387
+ originY: "center",
2388
+ data: { id: item.id }
2389
+ });
2390
+ let { width, height, left, top } = item;
2391
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, dielinePhysicalWidth, dielinePhysicalHeight } = layout;
2392
+ if (width === void 0 && height === void 0) {
2393
+ const imgAspect = (image.width || 1) / (image.height || 1);
2394
+ const dielineAspect = dielinePhysicalWidth / dielinePhysicalHeight;
2395
+ if (imgAspect > dielineAspect) {
2396
+ const w = dielinePhysicalWidth;
2397
+ width = w;
2398
+ height = w / imgAspect;
2290
2399
  } else {
2291
- image.top = centerY;
2400
+ const h = dielinePhysicalHeight;
2401
+ height = h;
2402
+ width = h * imgAspect;
2292
2403
  }
2404
+ item.width = width;
2405
+ item.height = height;
2293
2406
  }
2294
- image.set({
2295
- opacity: opacity !== void 0 ? opacity : 1,
2296
- data: {
2297
- id: "user-image"
2298
- }
2299
- });
2407
+ if (left === void 0 && top === void 0) {
2408
+ left = 0.5;
2409
+ top = 0.5;
2410
+ item.left = left;
2411
+ item.top = top;
2412
+ }
2413
+ this.updateObjectProperties(image, item, layout);
2300
2414
  layer.add(image);
2415
+ this.objectMap.set(item.id, image);
2301
2416
  image.on("modified", (e) => {
2302
- var _a2, _b2;
2303
- const matrix = image.calcTransformMatrix();
2304
- const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2305
- const canvasW = ((_a2 = this.canvasService) == null ? void 0 : _a2.canvas.width) || 800;
2306
- const canvasH = ((_b2 = this.canvasService) == null ? void 0 : _b2.canvas.height) || 600;
2307
- this.left = Coordinate.toNormalized(globalPoint.x, canvasW);
2308
- this.top = Coordinate.toNormalized(globalPoint.y, canvasH);
2309
- this.angle = e.target.angle;
2310
- if (image.width) this.width = e.target.width * e.target.scaleX;
2311
- if (image.height) this.height = e.target.height * e.target.scaleY;
2312
- if (this.context) {
2313
- this.context.eventBus.emit("update");
2314
- }
2417
+ this.handleObjectModified(item.id, image);
2315
2418
  });
2316
2419
  layer.dirty = true;
2317
- this.canvasService.requestRenderAll();
2420
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2421
+ if (item.width !== width || item.height !== height || item.left !== left || item.top !== top) {
2422
+ this.updateImageInConfig(item.id, { width, height, left, top });
2423
+ }
2318
2424
  }).catch((err) => {
2319
- if (this._loadingUrl === url) this._loadingUrl = null;
2320
- console.error("Failed to load image", url, err);
2425
+ console.error("Failed to load image", item.url, err);
2321
2426
  });
2322
2427
  }
2428
+ handleObjectModified(id, image) {
2429
+ const layout = this.getLayoutInfo();
2430
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2431
+ const matrix = image.calcTransformMatrix();
2432
+ const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2433
+ const updates = {};
2434
+ updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
2435
+ updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
2436
+ updates.angle = image.angle;
2437
+ if (image.width) {
2438
+ const pixelWidth = image.width * image.scaleX;
2439
+ updates.width = pixelWidth / layoutScale;
2440
+ }
2441
+ if (image.height) {
2442
+ const pixelHeight = image.height * image.scaleY;
2443
+ updates.height = pixelHeight / layoutScale;
2444
+ }
2445
+ this.updateImageInConfig(id, updates);
2446
+ }
2447
+ updateImageInConfig(id, updates) {
2448
+ const index = this.items.findIndex((i) => i.id === id);
2449
+ if (index !== -1) {
2450
+ const newItems = [...this.items];
2451
+ newItems[index] = { ...newItems[index], ...updates };
2452
+ this.updateConfig(newItems, true);
2453
+ }
2454
+ }
2323
2455
  };
2324
2456
 
2325
2457
  // src/white-ink.ts
@@ -2622,12 +2754,18 @@ var RulerTool = class {
2622
2754
  this.metadata = {
2623
2755
  name: "RulerTool"
2624
2756
  };
2625
- this.unit = "px";
2626
2757
  this.thickness = 20;
2758
+ this.gap = 15;
2627
2759
  this.backgroundColor = "#f0f0f0";
2628
2760
  this.textColor = "#333333";
2629
2761
  this.lineColor = "#999999";
2630
2762
  this.fontSize = 10;
2763
+ // Dieline context for sync
2764
+ this.dielineWidth = 500;
2765
+ this.dielineHeight = 500;
2766
+ this.dielineUnit = "mm";
2767
+ this.dielinePadding = 40;
2768
+ this.dielineOffset = 0;
2631
2769
  if (options) {
2632
2770
  Object.assign(this, options);
2633
2771
  }
@@ -2640,8 +2778,8 @@ var RulerTool = class {
2640
2778
  }
2641
2779
  const configService = context.services.get("ConfigurationService");
2642
2780
  if (configService) {
2643
- this.unit = configService.get("ruler.unit", this.unit);
2644
2781
  this.thickness = configService.get("ruler.thickness", this.thickness);
2782
+ this.gap = configService.get("ruler.gap", this.gap);
2645
2783
  this.backgroundColor = configService.get(
2646
2784
  "ruler.backgroundColor",
2647
2785
  this.backgroundColor
@@ -2649,13 +2787,38 @@ var RulerTool = class {
2649
2787
  this.textColor = configService.get("ruler.textColor", this.textColor);
2650
2788
  this.lineColor = configService.get("ruler.lineColor", this.lineColor);
2651
2789
  this.fontSize = configService.get("ruler.fontSize", this.fontSize);
2790
+ this.dielineUnit = configService.get("dieline.unit", this.dielineUnit);
2791
+ this.dielineWidth = configService.get("dieline.width", this.dielineWidth);
2792
+ this.dielineHeight = configService.get(
2793
+ "dieline.height",
2794
+ this.dielineHeight
2795
+ );
2796
+ this.dielinePadding = configService.get(
2797
+ "dieline.padding",
2798
+ this.dielinePadding
2799
+ );
2800
+ this.dielineOffset = configService.get(
2801
+ "dieline.offset",
2802
+ this.dielineOffset
2803
+ );
2652
2804
  configService.onAnyChange((e) => {
2805
+ let shouldUpdate = false;
2653
2806
  if (e.key.startsWith("ruler.")) {
2654
2807
  const prop = e.key.split(".")[1];
2655
2808
  if (prop && prop in this) {
2656
2809
  this[prop] = e.value;
2657
- this.updateRuler();
2810
+ shouldUpdate = true;
2658
2811
  }
2812
+ } else if (e.key.startsWith("dieline.")) {
2813
+ if (e.key === "dieline.unit") this.dielineUnit = e.value;
2814
+ if (e.key === "dieline.width") this.dielineWidth = e.value;
2815
+ if (e.key === "dieline.height") this.dielineHeight = e.value;
2816
+ if (e.key === "dieline.padding") this.dielinePadding = e.value;
2817
+ if (e.key === "dieline.offset") this.dielineOffset = e.value;
2818
+ shouldUpdate = true;
2819
+ }
2820
+ if (shouldUpdate) {
2821
+ this.updateRuler();
2659
2822
  }
2660
2823
  });
2661
2824
  }
@@ -2669,13 +2832,6 @@ var RulerTool = class {
2669
2832
  contribute() {
2670
2833
  return {
2671
2834
  [import_core7.ContributionPointIds.CONFIGURATIONS]: [
2672
- {
2673
- id: "ruler.unit",
2674
- type: "select",
2675
- label: "Unit",
2676
- options: ["px", "mm", "cm", "in"],
2677
- default: "px"
2678
- },
2679
2835
  {
2680
2836
  id: "ruler.thickness",
2681
2837
  type: "number",
@@ -2684,6 +2840,14 @@ var RulerTool = class {
2684
2840
  max: 100,
2685
2841
  default: 20
2686
2842
  },
2843
+ {
2844
+ id: "ruler.gap",
2845
+ type: "number",
2846
+ label: "Gap",
2847
+ min: 0,
2848
+ max: 100,
2849
+ default: 15
2850
+ },
2687
2851
  {
2688
2852
  id: "ruler.backgroundColor",
2689
2853
  type: "color",
@@ -2712,16 +2876,6 @@ var RulerTool = class {
2712
2876
  }
2713
2877
  ],
2714
2878
  [import_core7.ContributionPointIds.COMMANDS]: [
2715
- {
2716
- command: "setUnit",
2717
- title: "Set Ruler Unit",
2718
- handler: (unit) => {
2719
- if (this.unit === unit) return true;
2720
- this.unit = unit;
2721
- this.updateRuler();
2722
- return true;
2723
- }
2724
- },
2725
2879
  {
2726
2880
  command: "setTheme",
2727
2881
  title: "Set Ruler Theme",
@@ -2772,6 +2926,68 @@ var RulerTool = class {
2772
2926
  this.canvasService.canvas.remove(layer);
2773
2927
  }
2774
2928
  }
2929
+ createArrowLine(x1, y1, x2, y2, color) {
2930
+ const line = new import_fabric7.Line([x1, y1, x2, y2], {
2931
+ stroke: color,
2932
+ strokeWidth: this.thickness / 20,
2933
+ // Scale stroke width relative to thickness (default 1)
2934
+ selectable: false,
2935
+ evented: false
2936
+ });
2937
+ const arrowSize = Math.max(4, this.thickness * 0.3);
2938
+ const angle = Math.atan2(y2 - y1, x2 - x1);
2939
+ const endArrow = new import_fabric7.Polygon(
2940
+ [
2941
+ { x: 0, y: 0 },
2942
+ { x: -arrowSize, y: -arrowSize / 2 },
2943
+ { x: -arrowSize, y: arrowSize / 2 }
2944
+ ],
2945
+ {
2946
+ fill: color,
2947
+ left: x2,
2948
+ top: y2,
2949
+ originX: "right",
2950
+ originY: "center",
2951
+ angle: angle * 180 / Math.PI,
2952
+ selectable: false,
2953
+ evented: false
2954
+ }
2955
+ );
2956
+ const startArrow = new import_fabric7.Polygon(
2957
+ [
2958
+ { x: 0, y: 0 },
2959
+ { x: arrowSize, y: -arrowSize / 2 },
2960
+ { x: arrowSize, y: arrowSize / 2 }
2961
+ ],
2962
+ {
2963
+ fill: color,
2964
+ left: x1,
2965
+ top: y1,
2966
+ originX: "left",
2967
+ originY: "center",
2968
+ angle: angle * 180 / Math.PI,
2969
+ selectable: false,
2970
+ evented: false
2971
+ }
2972
+ );
2973
+ return new import_fabric7.Group([line, startArrow, endArrow], {
2974
+ selectable: false,
2975
+ evented: false
2976
+ });
2977
+ }
2978
+ resolvePadding(containerWidth, containerHeight) {
2979
+ if (typeof this.dielinePadding === "number") {
2980
+ return this.dielinePadding;
2981
+ }
2982
+ if (typeof this.dielinePadding === "string") {
2983
+ if (this.dielinePadding.endsWith("%")) {
2984
+ const percent = parseFloat(this.dielinePadding) / 100;
2985
+ return Math.min(containerWidth, containerHeight) * percent;
2986
+ }
2987
+ return parseFloat(this.dielinePadding) || 0;
2988
+ }
2989
+ return 0;
2990
+ }
2775
2991
  updateRuler() {
2776
2992
  if (!this.canvasService) return;
2777
2993
  const layer = this.getLayer();
@@ -2780,95 +2996,141 @@ var RulerTool = class {
2780
2996
  const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
2781
2997
  const width = this.canvasService.canvas.width || 800;
2782
2998
  const height = this.canvasService.canvas.height || 600;
2783
- const topBg = new import_fabric7.Rect({
2784
- left: 0,
2785
- top: 0,
2786
- width,
2787
- height: thickness,
2788
- fill: backgroundColor,
2789
- selectable: false,
2790
- evented: false
2791
- });
2792
- const leftBg = new import_fabric7.Rect({
2793
- left: 0,
2794
- top: 0,
2795
- width: thickness,
2796
- height,
2797
- fill: backgroundColor,
2798
- selectable: false,
2799
- evented: false
2800
- });
2801
- const cornerBg = new import_fabric7.Rect({
2802
- left: 0,
2803
- top: 0,
2804
- width: thickness,
2805
- height: thickness,
2806
- fill: backgroundColor,
2807
- stroke: lineColor,
2808
- strokeWidth: 1,
2999
+ const paddingPx = this.resolvePadding(width, height);
3000
+ const layout = Coordinate.calculateLayout(
3001
+ { width, height },
3002
+ { width: this.dielineWidth, height: this.dielineHeight },
3003
+ paddingPx
3004
+ );
3005
+ const scale = layout.scale;
3006
+ const offsetX = layout.offsetX;
3007
+ const offsetY = layout.offsetY;
3008
+ const visualWidth = layout.width;
3009
+ const visualHeight = layout.height;
3010
+ const rawOffset = this.dielineOffset || 0;
3011
+ const effectiveOffset = rawOffset > 0 ? rawOffset : 0;
3012
+ const expandPixels = effectiveOffset * scale;
3013
+ const gap = this.gap || 15;
3014
+ const rulerLeft = offsetX - expandPixels;
3015
+ const rulerTop = offsetY - expandPixels;
3016
+ const rulerRight = offsetX + visualWidth + expandPixels;
3017
+ const rulerBottom = offsetY + visualHeight + expandPixels;
3018
+ const displayWidth = this.dielineWidth + effectiveOffset * 2;
3019
+ const displayHeight = this.dielineHeight + effectiveOffset * 2;
3020
+ const topRulerY = rulerTop - gap;
3021
+ const topRulerXStart = rulerLeft;
3022
+ const topRulerXEnd = rulerRight;
3023
+ const leftRulerX = rulerLeft - gap;
3024
+ const leftRulerYStart = rulerTop;
3025
+ const leftRulerYEnd = rulerBottom;
3026
+ const topDimLine = this.createArrowLine(
3027
+ topRulerXStart,
3028
+ topRulerY,
3029
+ topRulerXEnd,
3030
+ topRulerY,
3031
+ lineColor
3032
+ );
3033
+ layer.add(topDimLine);
3034
+ const extLen = 5;
3035
+ layer.add(
3036
+ new import_fabric7.Line(
3037
+ [
3038
+ topRulerXStart,
3039
+ topRulerY - extLen,
3040
+ topRulerXStart,
3041
+ topRulerY + extLen
3042
+ ],
3043
+ {
3044
+ stroke: lineColor,
3045
+ strokeWidth: 1,
3046
+ selectable: false,
3047
+ evented: false
3048
+ }
3049
+ )
3050
+ );
3051
+ layer.add(
3052
+ new import_fabric7.Line(
3053
+ [topRulerXEnd, topRulerY - extLen, topRulerXEnd, topRulerY + extLen],
3054
+ {
3055
+ stroke: lineColor,
3056
+ strokeWidth: 1,
3057
+ selectable: false,
3058
+ evented: false
3059
+ }
3060
+ )
3061
+ );
3062
+ const widthStr = parseFloat(displayWidth.toFixed(2)).toString();
3063
+ const topTextContent = `${widthStr} ${this.dielineUnit}`;
3064
+ const topText = new import_fabric7.Text(topTextContent, {
3065
+ left: topRulerXStart + (rulerRight - rulerLeft) / 2,
3066
+ top: topRulerY,
3067
+ fontSize,
3068
+ fill: textColor,
3069
+ fontFamily: "Arial",
3070
+ originX: "center",
3071
+ originY: "center",
3072
+ backgroundColor,
3073
+ // Background mask for readability
2809
3074
  selectable: false,
2810
3075
  evented: false
2811
3076
  });
2812
- layer.add(topBg);
2813
- layer.add(leftBg);
2814
- layer.add(cornerBg);
2815
- const step = 100;
2816
- const subStep = 10;
2817
- const midStep = 50;
2818
- for (let x = 0; x <= width; x += subStep) {
2819
- if (x < thickness) continue;
2820
- let len = thickness * 0.25;
2821
- if (x % step === 0) len = thickness * 0.8;
2822
- else if (x % midStep === 0) len = thickness * 0.5;
2823
- const line = new import_fabric7.Line([x, thickness - len, x, thickness], {
2824
- stroke: lineColor,
2825
- strokeWidth: 1,
2826
- selectable: false,
2827
- evented: false
2828
- });
2829
- layer.add(line);
2830
- if (x % step === 0) {
2831
- const text = new import_fabric7.Text(x.toString(), {
2832
- left: x + 2,
2833
- top: 2,
2834
- fontSize,
2835
- fill: textColor,
2836
- fontFamily: "Arial",
3077
+ layer.add(topText);
3078
+ const leftDimLine = this.createArrowLine(
3079
+ leftRulerX,
3080
+ leftRulerYStart,
3081
+ leftRulerX,
3082
+ leftRulerYEnd,
3083
+ lineColor
3084
+ );
3085
+ layer.add(leftDimLine);
3086
+ layer.add(
3087
+ new import_fabric7.Line(
3088
+ [
3089
+ leftRulerX - extLen,
3090
+ leftRulerYStart,
3091
+ leftRulerX + extLen,
3092
+ leftRulerYStart
3093
+ ],
3094
+ {
3095
+ stroke: lineColor,
3096
+ strokeWidth: 1,
2837
3097
  selectable: false,
2838
3098
  evented: false
2839
- });
2840
- layer.add(text);
2841
- }
2842
- }
2843
- for (let y = 0; y <= height; y += subStep) {
2844
- if (y < thickness) continue;
2845
- let len = thickness * 0.25;
2846
- if (y % step === 0) len = thickness * 0.8;
2847
- else if (y % midStep === 0) len = thickness * 0.5;
2848
- const line = new import_fabric7.Line([thickness - len, y, thickness, y], {
2849
- stroke: lineColor,
2850
- strokeWidth: 1,
2851
- selectable: false,
2852
- evented: false
2853
- });
2854
- layer.add(line);
2855
- if (y % step === 0) {
2856
- const text = new import_fabric7.Text(y.toString(), {
2857
- angle: -90,
2858
- left: thickness / 2 - fontSize / 3,
2859
- // approximate centering
2860
- top: y + fontSize,
2861
- fontSize,
2862
- fill: textColor,
2863
- fontFamily: "Arial",
2864
- originX: "center",
2865
- originY: "center",
3099
+ }
3100
+ )
3101
+ );
3102
+ layer.add(
3103
+ new import_fabric7.Line(
3104
+ [
3105
+ leftRulerX - extLen,
3106
+ leftRulerYEnd,
3107
+ leftRulerX + extLen,
3108
+ leftRulerYEnd
3109
+ ],
3110
+ {
3111
+ stroke: lineColor,
3112
+ strokeWidth: 1,
2866
3113
  selectable: false,
2867
3114
  evented: false
2868
- });
2869
- layer.add(text);
2870
- }
2871
- }
3115
+ }
3116
+ )
3117
+ );
3118
+ const heightStr = parseFloat(displayHeight.toFixed(2)).toString();
3119
+ const leftTextContent = `${heightStr} ${this.dielineUnit}`;
3120
+ const leftText = new import_fabric7.Text(leftTextContent, {
3121
+ left: leftRulerX,
3122
+ top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
3123
+ angle: -90,
3124
+ fontSize,
3125
+ fill: textColor,
3126
+ fontFamily: "Arial",
3127
+ originX: "center",
3128
+ originY: "center",
3129
+ backgroundColor,
3130
+ selectable: false,
3131
+ evented: false
3132
+ });
3133
+ layer.add(leftText);
2872
3134
  this.canvasService.canvas.bringObjectToFront(layer);
2873
3135
  this.canvasService.canvas.requestRenderAll();
2874
3136
  }