@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.mjs CHANGED
@@ -394,6 +394,24 @@ var ImageTracer = class {
394
394
 
395
395
  // src/coordinate.ts
396
396
  var Coordinate = class {
397
+ /**
398
+ * Calculate layout to fit content within container while preserving aspect ratio.
399
+ */
400
+ static calculateLayout(container, content, padding = 0) {
401
+ const availableWidth = Math.max(0, container.width - padding * 2);
402
+ const availableHeight = Math.max(0, container.height - padding * 2);
403
+ if (content.width === 0 || content.height === 0) {
404
+ return { scale: 1, offsetX: 0, offsetY: 0, width: 0, height: 0 };
405
+ }
406
+ const scaleX = availableWidth / content.width;
407
+ const scaleY = availableHeight / content.height;
408
+ const scale = Math.min(scaleX, scaleY);
409
+ const width = content.width * scale;
410
+ const height = content.height * scale;
411
+ const offsetX = (container.width - width) / 2;
412
+ const offsetY = (container.height - height) / 2;
413
+ return { scale, offsetX, offsetY, width, height };
414
+ }
397
415
  /**
398
416
  * Convert an absolute value to a normalized value (0-1).
399
417
  * @param value Absolute value (e.g., pixels)
@@ -428,10 +446,85 @@ var Coordinate = class {
428
446
  y: this.toAbsolute(point.y, size.height)
429
447
  };
430
448
  }
449
+ static convertUnit(value, from, to) {
450
+ if (from === to) return value;
451
+ const toMM = {
452
+ px: 0.264583,
453
+ // 1px = 0.264583mm (96 DPI)
454
+ mm: 1,
455
+ cm: 10,
456
+ in: 25.4
457
+ };
458
+ const mmValue = value * (from === "px" ? toMM.px : toMM[from] || 1);
459
+ if (to === "px") {
460
+ return mmValue / toMM.px;
461
+ }
462
+ return mmValue / (toMM[to] || 1);
463
+ }
431
464
  };
432
465
 
433
466
  // src/geometry.ts
434
467
  import paper from "paper";
468
+ function resolveHolePosition(hole, geometry, canvasSize) {
469
+ if (hole.anchor) {
470
+ const { x, y, width, height } = geometry;
471
+ let bx = x;
472
+ let by = y;
473
+ const left = x - width / 2;
474
+ const right = x + width / 2;
475
+ const top = y - height / 2;
476
+ const bottom = y + height / 2;
477
+ switch (hole.anchor) {
478
+ case "top-left":
479
+ bx = left;
480
+ by = top;
481
+ break;
482
+ case "top-center":
483
+ bx = x;
484
+ by = top;
485
+ break;
486
+ case "top-right":
487
+ bx = right;
488
+ by = top;
489
+ break;
490
+ case "center-left":
491
+ bx = left;
492
+ by = y;
493
+ break;
494
+ case "center":
495
+ bx = x;
496
+ by = y;
497
+ break;
498
+ case "center-right":
499
+ bx = right;
500
+ by = y;
501
+ break;
502
+ case "bottom-left":
503
+ bx = left;
504
+ by = bottom;
505
+ break;
506
+ case "bottom-center":
507
+ bx = x;
508
+ by = bottom;
509
+ break;
510
+ case "bottom-right":
511
+ bx = right;
512
+ by = bottom;
513
+ break;
514
+ }
515
+ return {
516
+ x: bx + (hole.offsetX || 0),
517
+ y: by + (hole.offsetY || 0)
518
+ };
519
+ } else if (hole.x !== void 0 && hole.y !== void 0) {
520
+ const { x, width, y, height } = geometry;
521
+ return {
522
+ x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
523
+ y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
524
+ };
525
+ }
526
+ return { x: 0, y: 0 };
527
+ }
435
528
  function ensurePaper(width, height) {
436
529
  if (!paper.project) {
437
530
  paper.setup(new paper.Size(width, height));
@@ -533,7 +626,10 @@ function getDielineShape(options) {
533
626
  cutsPath.remove();
534
627
  mainShape = temp;
535
628
  } catch (e) {
536
- console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
629
+ console.error(
630
+ "Geometry: Failed to subtract cutsPath from mainShape",
631
+ e
632
+ );
537
633
  }
538
634
  }
539
635
  }
@@ -626,7 +722,12 @@ function getPathBounds(pathData) {
626
722
  path.pathData = pathData;
627
723
  const bounds = path.bounds;
628
724
  path.remove();
629
- return { width: bounds.width, height: bounds.height };
725
+ return {
726
+ x: bounds.x,
727
+ y: bounds.y,
728
+ width: bounds.width,
729
+ height: bounds.height
730
+ };
630
731
  }
631
732
 
632
733
  // src/dieline.ts
@@ -636,6 +737,7 @@ var DielineTool = class {
636
737
  this.metadata = {
637
738
  name: "DielineTool"
638
739
  };
740
+ this.unit = "mm";
639
741
  this.shape = "rect";
640
742
  this.width = 500;
641
743
  this.height = 500;
@@ -646,6 +748,7 @@ var DielineTool = class {
646
748
  this.outsideColor = "#ffffff";
647
749
  this.showBleedLines = true;
648
750
  this.holes = [];
751
+ this.padding = 140;
649
752
  if (options) {
650
753
  Object.assign(this, options);
651
754
  }
@@ -659,14 +762,12 @@ var DielineTool = class {
659
762
  }
660
763
  const configService = context.services.get("ConfigurationService");
661
764
  if (configService) {
765
+ this.unit = configService.get("dieline.unit", this.unit);
662
766
  this.shape = configService.get("dieline.shape", this.shape);
663
767
  this.width = configService.get("dieline.width", this.width);
664
768
  this.height = configService.get("dieline.height", this.height);
665
769
  this.radius = configService.get("dieline.radius", this.radius);
666
- this.borderLength = configService.get(
667
- "dieline.borderLength",
668
- this.borderLength
669
- );
770
+ this.padding = configService.get("dieline.padding", this.padding);
670
771
  this.offset = configService.get("dieline.offset", this.offset);
671
772
  this.style = configService.get("dieline.style", this.style);
672
773
  this.insideColor = configService.get(
@@ -707,6 +808,13 @@ var DielineTool = class {
707
808
  contribute() {
708
809
  return {
709
810
  [ContributionPointIds2.CONFIGURATIONS]: [
811
+ {
812
+ id: "dieline.unit",
813
+ type: "select",
814
+ label: "Unit",
815
+ options: ["px", "mm", "cm", "in"],
816
+ default: this.unit
817
+ },
710
818
  {
711
819
  id: "dieline.shape",
712
820
  type: "select",
@@ -742,15 +850,14 @@ var DielineTool = class {
742
850
  id: "dieline.position",
743
851
  type: "json",
744
852
  label: "Position (Normalized)",
745
- default: this.position
853
+ default: this.radius
746
854
  },
747
855
  {
748
- id: "dieline.borderLength",
749
- type: "number",
750
- label: "Margin",
751
- min: 0,
752
- max: 500,
753
- default: this.borderLength
856
+ id: "dieline.padding",
857
+ type: "select",
858
+ label: "View Padding",
859
+ options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
860
+ default: this.padding
754
861
  },
755
862
  {
756
863
  id: "dieline.offset",
@@ -793,65 +900,6 @@ var DielineTool = class {
793
900
  }
794
901
  ],
795
902
  [ContributionPointIds2.COMMANDS]: [
796
- {
797
- command: "reset",
798
- title: "Reset Dieline",
799
- handler: () => {
800
- this.shape = "rect";
801
- this.width = 300;
802
- this.height = 300;
803
- this.radius = 0;
804
- this.offset = 0;
805
- this.style = "solid";
806
- this.insideColor = "rgba(0,0,0,0)";
807
- this.outsideColor = "#ffffff";
808
- this.showBleedLines = true;
809
- this.holes = [];
810
- this.pathData = void 0;
811
- this.updateDieline();
812
- return true;
813
- }
814
- },
815
- {
816
- command: "setDimensions",
817
- title: "Set Dimensions",
818
- handler: (width, height) => {
819
- if (this.width === width && this.height === height) return true;
820
- this.width = width;
821
- this.height = height;
822
- this.updateDieline();
823
- return true;
824
- }
825
- },
826
- {
827
- command: "setShape",
828
- title: "Set Shape",
829
- handler: (shape) => {
830
- if (this.shape === shape) return true;
831
- this.shape = shape;
832
- this.updateDieline();
833
- return true;
834
- }
835
- },
836
- {
837
- command: "setBleed",
838
- title: "Set Bleed",
839
- handler: (bleed) => {
840
- if (this.offset === bleed) return true;
841
- this.offset = bleed;
842
- this.updateDieline();
843
- return true;
844
- }
845
- },
846
- {
847
- command: "setHoles",
848
- title: "Set Holes",
849
- handler: (holes) => {
850
- this.holes = holes;
851
- this.updateDieline(false);
852
- return true;
853
- }
854
- },
855
903
  {
856
904
  command: "getGeometry",
857
905
  title: "Get Geometry",
@@ -870,16 +918,23 @@ var DielineTool = class {
870
918
  command: "detectEdge",
871
919
  title: "Detect Edge from Image",
872
920
  handler: async (imageUrl, options) => {
921
+ var _a;
873
922
  try {
874
923
  const pathData = await ImageTracer.trace(imageUrl, options);
875
924
  const bounds = getPathBounds(pathData);
876
925
  const currentMax = Math.max(this.width, this.height);
877
926
  const scale = currentMax / Math.max(bounds.width, bounds.height);
878
- this.width = bounds.width * scale;
879
- this.height = bounds.height * scale;
880
- this.shape = "custom";
881
- this.pathData = pathData;
882
- this.updateDieline();
927
+ const newWidth = bounds.width * scale;
928
+ const newHeight = bounds.height * scale;
929
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
930
+ "ConfigurationService"
931
+ );
932
+ if (configService) {
933
+ configService.update("dieline.width", newWidth);
934
+ configService.update("dieline.height", newHeight);
935
+ configService.update("dieline.shape", "custom");
936
+ configService.update("dieline.pathData", pathData);
937
+ }
883
938
  return pathData;
884
939
  } catch (e) {
885
940
  console.error("Edge detection failed", e);
@@ -938,11 +993,25 @@ var DielineTool = class {
938
993
  }
939
994
  return new Pattern({ source: canvas, repetition: "repeat" });
940
995
  }
996
+ resolvePadding(containerWidth, containerHeight) {
997
+ if (typeof this.padding === "number") {
998
+ return this.padding;
999
+ }
1000
+ if (typeof this.padding === "string") {
1001
+ if (this.padding.endsWith("%")) {
1002
+ const percent = parseFloat(this.padding) / 100;
1003
+ return Math.min(containerWidth, containerHeight) * percent;
1004
+ }
1005
+ return parseFloat(this.padding) || 0;
1006
+ }
1007
+ return 0;
1008
+ }
941
1009
  updateDieline(emitEvent = true) {
942
1010
  if (!this.canvasService) return;
943
1011
  const layer = this.getLayer();
944
1012
  if (!layer) return;
945
1013
  const {
1014
+ unit,
946
1015
  shape,
947
1016
  radius,
948
1017
  offset,
@@ -950,35 +1019,60 @@ var DielineTool = class {
950
1019
  insideColor,
951
1020
  outsideColor,
952
1021
  position,
953
- borderLength,
954
1022
  showBleedLines,
955
1023
  holes
956
1024
  } = this;
957
1025
  let { width, height } = this;
958
1026
  const canvasW = this.canvasService.canvas.width || 800;
959
1027
  const canvasH = this.canvasService.canvas.height || 600;
960
- if (borderLength && borderLength > 0) {
961
- width = Math.max(0, canvasW - borderLength * 2);
962
- height = Math.max(0, canvasH - borderLength * 2);
963
- }
964
- const normalizedPos = position != null ? position : { x: 0.5, y: 0.5 };
965
- const cx = Coordinate.toAbsolute(normalizedPos.x, canvasW);
966
- const cy = Coordinate.toAbsolute(normalizedPos.y, canvasH);
1028
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1029
+ const layout = Coordinate.calculateLayout(
1030
+ { width: canvasW, height: canvasH },
1031
+ { width, height },
1032
+ paddingPx
1033
+ );
1034
+ const scale = layout.scale;
1035
+ const cx = layout.offsetX + layout.width / 2;
1036
+ const cy = layout.offsetY + layout.height / 2;
1037
+ const visualWidth = layout.width;
1038
+ const visualHeight = layout.height;
1039
+ const visualRadius = radius * scale;
1040
+ const visualOffset = offset * scale;
967
1041
  layer.remove(...layer.getObjects());
1042
+ const geometryForHoles = {
1043
+ x: cx,
1044
+ y: cy,
1045
+ width: visualWidth,
1046
+ height: visualHeight
1047
+ // Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
1048
+ };
968
1049
  const absoluteHoles = (holes || []).map((h) => {
969
- const p = Coordinate.denormalizePoint(
970
- { x: h.x, y: h.y },
971
- { width: canvasW, height: canvasH }
972
- );
1050
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1051
+ const offsetScale = unitScale * scale;
1052
+ const hWithPixelOffsets = {
1053
+ ...h,
1054
+ offsetX: (h.offsetX || 0) * offsetScale,
1055
+ offsetY: (h.offsetY || 0) * offsetScale
1056
+ };
1057
+ const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
1058
+ width: canvasW,
1059
+ height: canvasH
1060
+ });
973
1061
  return {
974
1062
  ...h,
975
- x: p.x,
976
- y: p.y
1063
+ x: pos.x,
1064
+ y: pos.y,
1065
+ // Scale hole radii: mm -> current unit -> pixels
1066
+ innerRadius: h.innerRadius * offsetScale,
1067
+ outerRadius: h.outerRadius * offsetScale,
1068
+ // Store scaled offsets in the result for consistency, though pos is already resolved
1069
+ offsetX: hWithPixelOffsets.offsetX,
1070
+ offsetY: hWithPixelOffsets.offsetY
977
1071
  };
978
1072
  });
979
- const cutW = Math.max(0, width + offset * 2);
980
- const cutH = Math.max(0, height + offset * 2);
981
- const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
1073
+ const cutW = Math.max(0, visualWidth + visualOffset * 2);
1074
+ const cutH = Math.max(0, visualHeight + visualOffset * 2);
1075
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
982
1076
  const maskPathData = generateMaskPath({
983
1077
  canvasWidth: canvasW,
984
1078
  canvasHeight: canvasH,
@@ -1028,15 +1122,15 @@ var DielineTool = class {
1028
1122
  const bleedPathData = generateBleedZonePath(
1029
1123
  {
1030
1124
  shape,
1031
- width,
1032
- height,
1033
- radius,
1125
+ width: visualWidth,
1126
+ height: visualHeight,
1127
+ radius: visualRadius,
1034
1128
  x: cx,
1035
1129
  y: cy,
1036
1130
  holes: absoluteHoles,
1037
1131
  pathData: this.pathData
1038
1132
  },
1039
- offset
1133
+ visualOffset
1040
1134
  );
1041
1135
  if (showBleedLines !== false) {
1042
1136
  const pattern = this.createHatchPattern("red");
@@ -1079,13 +1173,12 @@ var DielineTool = class {
1079
1173
  }
1080
1174
  const borderPathData = generateDielinePath({
1081
1175
  shape,
1082
- width,
1083
- height,
1084
- radius,
1176
+ width: visualWidth,
1177
+ height: visualHeight,
1178
+ radius: visualRadius,
1085
1179
  x: cx,
1086
1180
  y: cy,
1087
1181
  holes: absoluteHoles,
1088
- // FIX: Use absoluteHoles instead of holes
1089
1182
  pathData: this.pathData
1090
1183
  });
1091
1184
  const borderObj = new Path(borderPathData, {
@@ -1123,114 +1216,107 @@ var DielineTool = class {
1123
1216
  }
1124
1217
  }
1125
1218
  getGeometry() {
1126
- var _a, _b;
1127
1219
  if (!this.canvasService) return null;
1128
- const { shape, width, height, radius, position, borderLength, offset } = this;
1220
+ const { unit, shape, width, height, radius, position, offset } = this;
1129
1221
  const canvasW = this.canvasService.canvas.width || 800;
1130
1222
  const canvasH = this.canvasService.canvas.height || 600;
1131
- let visualWidth = width;
1132
- let visualHeight = height;
1133
- if (borderLength && borderLength > 0) {
1134
- visualWidth = Math.max(0, canvasW - borderLength * 2);
1135
- visualHeight = Math.max(0, canvasH - borderLength * 2);
1136
- }
1137
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1138
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1223
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1224
+ const layout = Coordinate.calculateLayout(
1225
+ { width: canvasW, height: canvasH },
1226
+ { width, height },
1227
+ paddingPx
1228
+ );
1229
+ const scale = layout.scale;
1230
+ const cx = layout.offsetX + layout.width / 2;
1231
+ const cy = layout.offsetY + layout.height / 2;
1232
+ const visualWidth = layout.width;
1233
+ const visualHeight = layout.height;
1139
1234
  return {
1140
1235
  shape,
1236
+ unit,
1141
1237
  x: cx,
1142
1238
  y: cy,
1143
1239
  width: visualWidth,
1144
1240
  height: visualHeight,
1145
- radius,
1146
- offset,
1147
- borderLength,
1241
+ radius: radius * scale,
1242
+ offset: offset * scale,
1243
+ // Pass scale to help other tools (like HoleTool) convert units
1244
+ scale,
1148
1245
  pathData: this.pathData
1149
1246
  };
1150
1247
  }
1151
- exportCutImage() {
1152
- var _a, _b, _c, _d;
1248
+ async exportCutImage() {
1153
1249
  if (!this.canvasService) return null;
1154
- const canvas = this.canvasService.canvas;
1250
+ const userLayer = this.canvasService.getLayer("user");
1251
+ if (!userLayer) return null;
1155
1252
  const { shape, width, height, radius, position, holes } = this;
1156
- const canvasW = canvas.width || 800;
1157
- const canvasH = canvas.height || 600;
1158
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1159
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1253
+ const canvasW = this.canvasService.canvas.width || 800;
1254
+ const canvasH = this.canvasService.canvas.height || 600;
1255
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1256
+ const layout = Coordinate.calculateLayout(
1257
+ { width: canvasW, height: canvasH },
1258
+ { width, height },
1259
+ paddingPx
1260
+ );
1261
+ const scale = layout.scale;
1262
+ const cx = layout.offsetX + layout.width / 2;
1263
+ const cy = layout.offsetY + layout.height / 2;
1264
+ const visualWidth = layout.width;
1265
+ const visualHeight = layout.height;
1266
+ const visualRadius = radius * scale;
1160
1267
  const absoluteHoles = (holes || []).map((h) => {
1161
- const p = Coordinate.denormalizePoint(
1162
- { x: h.x, y: h.y },
1268
+ const unit = this.unit || "mm";
1269
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1270
+ const pos = resolveHolePosition(
1271
+ {
1272
+ ...h,
1273
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1274
+ offsetY: (h.offsetY || 0) * unitScale * scale
1275
+ },
1276
+ { x: cx, y: cy, width: visualWidth, height: visualHeight },
1163
1277
  { width: canvasW, height: canvasH }
1164
1278
  );
1165
1279
  return {
1166
1280
  ...h,
1167
- x: p.x,
1168
- y: p.y
1281
+ x: pos.x,
1282
+ y: pos.y,
1283
+ innerRadius: h.innerRadius * unitScale * scale,
1284
+ outerRadius: h.outerRadius * unitScale * scale,
1285
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1286
+ offsetY: (h.offsetY || 0) * unitScale * scale
1169
1287
  };
1170
1288
  });
1171
1289
  const pathData = generateDielinePath({
1172
1290
  shape,
1173
- width,
1174
- height,
1175
- radius,
1291
+ width: visualWidth,
1292
+ height: visualHeight,
1293
+ radius: visualRadius,
1176
1294
  x: cx,
1177
1295
  y: cy,
1178
1296
  holes: absoluteHoles,
1179
1297
  pathData: this.pathData
1180
1298
  });
1299
+ const clonedLayer = await userLayer.clone();
1181
1300
  const clipPath = new Path(pathData, {
1182
- left: 0,
1183
- top: 0,
1184
1301
  originX: "left",
1185
1302
  originY: "top",
1186
- absolutePositioned: true
1187
- });
1188
- const layer = this.getLayer();
1189
- const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
1190
- if (layer) layer.visible = false;
1191
- const holeMarkers = canvas.getObjects().filter((o) => {
1192
- var _a2;
1193
- return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
1194
- });
1195
- holeMarkers.forEach((o) => o.visible = false);
1196
- const rulerLayer = canvas.getObjects().find((obj) => {
1197
- var _a2;
1198
- return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
1199
- });
1200
- const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
1201
- if (rulerLayer) rulerLayer.visible = false;
1202
- const originalClip = canvas.clipPath;
1203
- canvas.clipPath = clipPath;
1204
- const bbox = clipPath.getBoundingRect();
1205
- const clipPathCorrected = new Path(pathData, {
1206
- absolutePositioned: true,
1207
1303
  left: 0,
1208
- top: 0
1209
- });
1210
- const tempPath = new Path(pathData);
1211
- const tempBounds = tempPath.getBoundingRect();
1212
- clipPathCorrected.set({
1213
- left: tempBounds.left,
1214
- top: tempBounds.top,
1215
- originX: "left",
1216
- originY: "top"
1304
+ top: 0,
1305
+ absolutePositioned: true
1306
+ // Important for groups
1217
1307
  });
1218
- canvas.clipPath = clipPathCorrected;
1219
- const exportBbox = clipPathCorrected.getBoundingRect();
1220
- const dataURL = canvas.toDataURL({
1308
+ clonedLayer.clipPath = clipPath;
1309
+ const bounds = clipPath.getBoundingRect();
1310
+ const dataUrl = clonedLayer.toDataURL({
1221
1311
  format: "png",
1222
1312
  multiplier: 2,
1223
- left: exportBbox.left,
1224
- top: exportBbox.top,
1225
- width: exportBbox.width,
1226
- height: exportBbox.height
1313
+ // Better quality
1314
+ left: bounds.left,
1315
+ top: bounds.top,
1316
+ width: bounds.width,
1317
+ height: bounds.height
1227
1318
  });
1228
- canvas.clipPath = originalClip;
1229
- if (layer) layer.visible = wasVisible;
1230
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
1231
- holeMarkers.forEach((o) => o.visible = true);
1232
- canvas.requestRenderAll();
1233
- return dataURL;
1319
+ return dataUrl;
1234
1320
  }
1235
1321
  };
1236
1322
 
@@ -1406,9 +1492,6 @@ var HoleTool = class {
1406
1492
  this.metadata = {
1407
1493
  name: "HoleTool"
1408
1494
  };
1409
- this.innerRadius = 15;
1410
- this.outerRadius = 25;
1411
- this.style = "solid";
1412
1495
  this.holes = [];
1413
1496
  this.constraintTarget = "bleed";
1414
1497
  this.isUpdatingConfig = false;
@@ -1432,53 +1515,20 @@ var HoleTool = class {
1432
1515
  "ConfigurationService"
1433
1516
  );
1434
1517
  if (configService) {
1435
- this.innerRadius = configService.get(
1436
- "hole.innerRadius",
1437
- this.innerRadius
1438
- );
1439
- this.outerRadius = configService.get(
1440
- "hole.outerRadius",
1441
- this.outerRadius
1442
- );
1443
- this.style = configService.get("hole.style", this.style);
1444
1518
  this.constraintTarget = configService.get(
1445
1519
  "hole.constraintTarget",
1446
1520
  this.constraintTarget
1447
1521
  );
1448
- const dielineHoles = configService.get("dieline.holes", []);
1449
- if (this.canvasService) {
1450
- const { width, height } = this.canvasService.canvas;
1451
- this.holes = dielineHoles.map((h) => {
1452
- const p = Coordinate.denormalizePoint(h, {
1453
- width: width || 800,
1454
- height: height || 600
1455
- });
1456
- return { x: p.x, y: p.y };
1457
- });
1458
- }
1522
+ this.holes = configService.get("dieline.holes", []);
1459
1523
  configService.onAnyChange((e) => {
1460
1524
  if (this.isUpdatingConfig) return;
1461
- if (e.key.startsWith("hole.")) {
1462
- const prop = e.key.split(".")[1];
1463
- if (prop && prop in this) {
1464
- this[prop] = e.value;
1465
- this.redraw();
1466
- this.syncHolesToDieline();
1467
- }
1525
+ if (e.key === "hole.constraintTarget") {
1526
+ this.constraintTarget = e.value;
1527
+ this.enforceConstraints();
1468
1528
  }
1469
1529
  if (e.key === "dieline.holes") {
1470
- const holes = e.value || [];
1471
- if (this.canvasService) {
1472
- const { width, height } = this.canvasService.canvas;
1473
- this.holes = holes.map((h) => {
1474
- const p = Coordinate.denormalizePoint(h, {
1475
- width: width || 800,
1476
- height: height || 600
1477
- });
1478
- return { x: p.x, y: p.y };
1479
- });
1480
- this.redraw();
1481
- }
1530
+ this.holes = e.value || [];
1531
+ this.redraw();
1482
1532
  }
1483
1533
  });
1484
1534
  }
@@ -1492,29 +1542,6 @@ var HoleTool = class {
1492
1542
  contribute() {
1493
1543
  return {
1494
1544
  [ContributionPointIds4.CONFIGURATIONS]: [
1495
- {
1496
- id: "hole.innerRadius",
1497
- type: "number",
1498
- label: "Inner Radius",
1499
- min: 1,
1500
- max: 100,
1501
- default: 15
1502
- },
1503
- {
1504
- id: "hole.outerRadius",
1505
- type: "number",
1506
- label: "Outer Radius",
1507
- min: 1,
1508
- max: 100,
1509
- default: 25
1510
- },
1511
- {
1512
- id: "hole.style",
1513
- type: "select",
1514
- label: "Line Style",
1515
- options: ["solid", "dashed"],
1516
- default: "solid"
1517
- },
1518
1545
  {
1519
1546
  id: "hole.constraintTarget",
1520
1547
  type: "select",
@@ -1528,6 +1555,7 @@ var HoleTool = class {
1528
1555
  command: "resetHoles",
1529
1556
  title: "Reset Holes",
1530
1557
  handler: () => {
1558
+ var _a;
1531
1559
  if (!this.canvasService) return false;
1532
1560
  let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1533
1561
  if (this.currentGeometry) {
@@ -1538,12 +1566,24 @@ var HoleTool = class {
1538
1566
  holes: []
1539
1567
  });
1540
1568
  }
1541
- this.innerRadius = 15;
1542
- this.outerRadius = 25;
1543
- this.style = "solid";
1544
- this.holes = [defaultPos];
1545
- this.redraw();
1546
- this.syncHolesToDieline();
1569
+ const { width, height } = this.canvasService.canvas;
1570
+ const normalizedHole = Coordinate.normalizePoint(defaultPos, {
1571
+ width: width || 800,
1572
+ height: height || 600
1573
+ });
1574
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1575
+ "ConfigurationService"
1576
+ );
1577
+ if (configService) {
1578
+ configService.update("dieline.holes", [
1579
+ {
1580
+ x: normalizedHole.x,
1581
+ y: normalizedHole.y,
1582
+ innerRadius: 15,
1583
+ outerRadius: 25
1584
+ }
1585
+ ]);
1586
+ }
1547
1587
  return true;
1548
1588
  }
1549
1589
  },
@@ -1551,10 +1591,37 @@ var HoleTool = class {
1551
1591
  command: "addHole",
1552
1592
  title: "Add Hole",
1553
1593
  handler: (x, y) => {
1554
- if (!this.holes) this.holes = [];
1555
- this.holes.push({ x, y });
1556
- this.redraw();
1557
- this.syncHolesToDieline();
1594
+ var _a, _b, _c;
1595
+ if (!this.canvasService) return false;
1596
+ let normalizedX = 0.5;
1597
+ let normalizedY = 0.5;
1598
+ if (this.currentGeometry) {
1599
+ const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
1600
+ const left = gx - gw / 2;
1601
+ const top = gy - gh / 2;
1602
+ normalizedX = gw > 0 ? (x - left) / gw : 0.5;
1603
+ normalizedY = gh > 0 ? (y - top) / gh : 0.5;
1604
+ } else {
1605
+ const { width, height } = this.canvasService.canvas;
1606
+ normalizedX = Coordinate.toNormalized(x, width || 800);
1607
+ normalizedY = Coordinate.toNormalized(y, height || 600);
1608
+ }
1609
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1610
+ "ConfigurationService"
1611
+ );
1612
+ if (configService) {
1613
+ const currentHoles = configService.get("dieline.holes", []);
1614
+ const lastHole = currentHoles[currentHoles.length - 1];
1615
+ const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
1616
+ const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
1617
+ const newHole = {
1618
+ x: normalizedX,
1619
+ y: normalizedY,
1620
+ innerRadius,
1621
+ outerRadius
1622
+ };
1623
+ configService.update("dieline.holes", [...currentHoles, newHole]);
1624
+ }
1558
1625
  return true;
1559
1626
  }
1560
1627
  },
@@ -1562,9 +1629,13 @@ var HoleTool = class {
1562
1629
  command: "clearHoles",
1563
1630
  title: "Clear Holes",
1564
1631
  handler: () => {
1565
- this.holes = [];
1566
- this.redraw();
1567
- this.syncHolesToDieline();
1632
+ var _a;
1633
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1634
+ "ConfigurationService"
1635
+ );
1636
+ if (configService) {
1637
+ configService.update("dieline.holes", []);
1638
+ }
1568
1639
  return true;
1569
1640
  }
1570
1641
  }
@@ -1605,10 +1676,12 @@ var HoleTool = class {
1605
1676
  }
1606
1677
  if (!this.handleMoving) {
1607
1678
  this.handleMoving = (e) => {
1608
- var _a;
1679
+ var _a, _b, _c, _d, _e;
1609
1680
  const target = e.target;
1610
1681
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1611
1682
  if (!this.currentGeometry) return;
1683
+ const index = (_c = (_b = target.data) == null ? void 0 : _b.index) != null ? _c : -1;
1684
+ const holeData = this.holes[index];
1612
1685
  const effectiveOffset = this.constraintTarget === "original" ? 0 : this.currentGeometry.offset;
1613
1686
  const constraintGeometry = {
1614
1687
  ...this.currentGeometry,
@@ -1620,7 +1693,12 @@ var HoleTool = class {
1620
1693
  radius: Math.max(0, this.currentGeometry.radius + effectiveOffset)
1621
1694
  };
1622
1695
  const p = new Point(target.left, target.top);
1623
- const newPos = this.calculateConstrainedPosition(p, constraintGeometry);
1696
+ const newPos = this.calculateConstrainedPosition(
1697
+ p,
1698
+ constraintGeometry,
1699
+ (_d = holeData == null ? void 0 : holeData.innerRadius) != null ? _d : 15,
1700
+ (_e = holeData == null ? void 0 : holeData.outerRadius) != null ? _e : 25
1701
+ );
1624
1702
  target.set({
1625
1703
  left: newPos.x,
1626
1704
  top: newPos.y
@@ -1633,7 +1711,10 @@ var HoleTool = class {
1633
1711
  var _a;
1634
1712
  const target = e.target;
1635
1713
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1636
- this.syncHolesFromCanvas();
1714
+ const changed = this.enforceConstraints();
1715
+ if (!changed) {
1716
+ this.syncHolesFromCanvas();
1717
+ }
1637
1718
  };
1638
1719
  canvas.on("object:modified", this.handleModified);
1639
1720
  }
@@ -1641,19 +1722,6 @@ var HoleTool = class {
1641
1722
  }
1642
1723
  initializeHoles() {
1643
1724
  if (!this.canvasService) return;
1644
- if (!this.holes || this.holes.length === 0) {
1645
- let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1646
- if (this.currentGeometry) {
1647
- const g = this.currentGeometry;
1648
- const topCenter = { x: g.x, y: g.y - g.height / 2 };
1649
- const snapped = getNearestPointOnDieline(topCenter, {
1650
- ...g,
1651
- holes: []
1652
- });
1653
- defaultPos = snapped;
1654
- }
1655
- this.holes = [defaultPos];
1656
- }
1657
1725
  this.redraw();
1658
1726
  this.syncHolesToDieline();
1659
1727
  }
@@ -1697,32 +1765,110 @@ var HoleTool = class {
1697
1765
  var _a;
1698
1766
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1699
1767
  });
1700
- const holes = objects.map((obj) => ({ x: obj.left, y: obj.top }));
1701
- this.holes = holes;
1768
+ objects.sort(
1769
+ (a, b) => {
1770
+ var _a, _b, _c, _d;
1771
+ 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);
1772
+ }
1773
+ );
1774
+ const newHoles = objects.map((obj, i) => {
1775
+ var _a, _b, _c, _d;
1776
+ const original = this.holes[i];
1777
+ const newAbsX = obj.left;
1778
+ const newAbsY = obj.top;
1779
+ const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
1780
+ const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
1781
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1782
+ if (original && original.anchor && this.currentGeometry) {
1783
+ const { x, y, width, height } = this.currentGeometry;
1784
+ let bx = x;
1785
+ let by = y;
1786
+ const left = x - width / 2;
1787
+ const right = x + width / 2;
1788
+ const top = y - height / 2;
1789
+ const bottom = y + height / 2;
1790
+ switch (original.anchor) {
1791
+ case "top-left":
1792
+ bx = left;
1793
+ by = top;
1794
+ break;
1795
+ case "top-center":
1796
+ bx = x;
1797
+ by = top;
1798
+ break;
1799
+ case "top-right":
1800
+ bx = right;
1801
+ by = top;
1802
+ break;
1803
+ case "center-left":
1804
+ bx = left;
1805
+ by = y;
1806
+ break;
1807
+ case "center":
1808
+ bx = x;
1809
+ by = y;
1810
+ break;
1811
+ case "center-right":
1812
+ bx = right;
1813
+ by = y;
1814
+ break;
1815
+ case "bottom-left":
1816
+ bx = left;
1817
+ by = bottom;
1818
+ break;
1819
+ case "bottom-center":
1820
+ bx = x;
1821
+ by = bottom;
1822
+ break;
1823
+ case "bottom-right":
1824
+ bx = right;
1825
+ by = bottom;
1826
+ break;
1827
+ }
1828
+ return {
1829
+ ...original,
1830
+ // Denormalize offset back to physical units (mm)
1831
+ offsetX: (newAbsX - bx) / scale / unitScale,
1832
+ offsetY: (newAbsY - by) / scale / unitScale,
1833
+ // Clear direct coordinates if we use anchor
1834
+ x: void 0,
1835
+ y: void 0
1836
+ };
1837
+ }
1838
+ let normalizedX = 0.5;
1839
+ let normalizedY = 0.5;
1840
+ if (this.currentGeometry) {
1841
+ const { x, y, width, height } = this.currentGeometry;
1842
+ const left = x - width / 2;
1843
+ const top = y - height / 2;
1844
+ normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
1845
+ normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
1846
+ } else {
1847
+ const { width, height } = this.canvasService.canvas;
1848
+ normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
1849
+ normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
1850
+ }
1851
+ return {
1852
+ ...original,
1853
+ x: normalizedX,
1854
+ y: normalizedY,
1855
+ // Ensure radii are preserved
1856
+ innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
1857
+ outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25
1858
+ };
1859
+ });
1860
+ this.holes = newHoles;
1702
1861
  this.syncHolesToDieline();
1703
1862
  }
1704
1863
  syncHolesToDieline() {
1705
1864
  if (!this.context || !this.canvasService) return;
1706
- const { holes, innerRadius, outerRadius } = this;
1707
- const currentHoles = holes || [];
1708
- const width = this.canvasService.canvas.width || 800;
1709
- const height = this.canvasService.canvas.height || 600;
1710
1865
  const configService = this.context.services.get(
1711
1866
  "ConfigurationService"
1712
1867
  );
1713
1868
  if (configService) {
1714
1869
  this.isUpdatingConfig = true;
1715
1870
  try {
1716
- const normalizedHoles = currentHoles.map((h) => {
1717
- const p = Coordinate.normalizePoint(h, { width, height });
1718
- return {
1719
- x: p.x,
1720
- y: p.y,
1721
- innerRadius,
1722
- outerRadius
1723
- };
1724
- });
1725
- configService.update("dieline.holes", normalizedHoles);
1871
+ configService.update("dieline.holes", this.holes);
1726
1872
  } finally {
1727
1873
  this.isUpdatingConfig = false;
1728
1874
  }
@@ -1731,19 +1877,43 @@ var HoleTool = class {
1731
1877
  redraw() {
1732
1878
  if (!this.canvasService) return;
1733
1879
  const canvas = this.canvasService.canvas;
1880
+ const { width, height } = canvas;
1734
1881
  const existing = canvas.getObjects().filter((obj) => {
1735
1882
  var _a;
1736
1883
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1737
1884
  });
1738
1885
  existing.forEach((obj) => canvas.remove(obj));
1739
- const { innerRadius, outerRadius, style, holes } = this;
1886
+ const holes = this.holes;
1740
1887
  if (!holes || holes.length === 0) {
1741
1888
  this.canvasService.requestRenderAll();
1742
1889
  return;
1743
1890
  }
1891
+ const geometry = this.currentGeometry || {
1892
+ x: (width || 800) / 2,
1893
+ y: (height || 600) / 2,
1894
+ width: width || 800,
1895
+ height: height || 600,
1896
+ scale: 1
1897
+ // Default scale if no geometry loaded
1898
+ };
1744
1899
  holes.forEach((hole, index) => {
1900
+ const scale = geometry.scale || 1;
1901
+ const unit = geometry.unit || "mm";
1902
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1903
+ const visualInnerRadius = hole.innerRadius * unitScale * scale;
1904
+ const visualOuterRadius = hole.outerRadius * unitScale * scale;
1905
+ const pos = resolveHolePosition(
1906
+ {
1907
+ ...hole,
1908
+ offsetX: (hole.offsetX || 0) * unitScale * scale,
1909
+ offsetY: (hole.offsetY || 0) * unitScale * scale
1910
+ },
1911
+ geometry,
1912
+ { width: geometry.width, height: geometry.height }
1913
+ // Use geometry dims instead of canvas
1914
+ );
1745
1915
  const innerCircle = new Circle({
1746
- radius: innerRadius,
1916
+ radius: visualInnerRadius,
1747
1917
  fill: "transparent",
1748
1918
  stroke: "red",
1749
1919
  strokeWidth: 2,
@@ -1751,17 +1921,17 @@ var HoleTool = class {
1751
1921
  originY: "center"
1752
1922
  });
1753
1923
  const outerCircle = new Circle({
1754
- radius: outerRadius,
1924
+ radius: visualOuterRadius,
1755
1925
  fill: "transparent",
1756
1926
  stroke: "#666",
1757
1927
  strokeWidth: 1,
1758
- strokeDashArray: style === "dashed" ? [5, 5] : void 0,
1928
+ strokeDashArray: [5, 5],
1759
1929
  originX: "center",
1760
1930
  originY: "center"
1761
1931
  });
1762
1932
  const holeGroup = new Group([outerCircle, innerCircle], {
1763
- left: hole.x,
1764
- top: hole.y,
1933
+ left: pos.x,
1934
+ top: pos.y,
1765
1935
  originX: "center",
1766
1936
  originY: "center",
1767
1937
  selectable: true,
@@ -1806,9 +1976,6 @@ var HoleTool = class {
1806
1976
  enforceConstraints() {
1807
1977
  const geometry = this.currentGeometry;
1808
1978
  if (!geometry || !this.canvasService) {
1809
- console.log(
1810
- "[HoleTool] Skipping enforceConstraints: No geometry or canvas service"
1811
- );
1812
1979
  return false;
1813
1980
  }
1814
1981
  const effectiveOffset = this.constraintTarget === "original" ? 0 : geometry.offset;
@@ -1822,9 +1989,6 @@ var HoleTool = class {
1822
1989
  var _a;
1823
1990
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1824
1991
  });
1825
- console.log(
1826
- `[HoleTool] Enforcing constraints on ${objects.length} markers`
1827
- );
1828
1992
  let changed = false;
1829
1993
  objects.sort(
1830
1994
  (a, b) => {
@@ -1833,16 +1997,22 @@ var HoleTool = class {
1833
1997
  }
1834
1998
  );
1835
1999
  const newHoles = [];
1836
- objects.forEach((obj) => {
2000
+ objects.forEach((obj, i) => {
2001
+ var _a, _b;
1837
2002
  const currentPos = new Point(obj.left, obj.top);
2003
+ const holeData = this.holes[i];
2004
+ const scale = geometry.scale || 1;
2005
+ const unit = geometry.unit || "mm";
2006
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
2007
+ const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
2008
+ const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
1838
2009
  const newPos = this.calculateConstrainedPosition(
1839
2010
  currentPos,
1840
- constraintGeometry
2011
+ constraintGeometry,
2012
+ innerR,
2013
+ outerR
1841
2014
  );
1842
2015
  if (currentPos.distanceFrom(newPos) > 0.1) {
1843
- console.log(
1844
- `[HoleTool] Moving hole from (${currentPos.x}, ${currentPos.y}) to (${newPos.x}, ${newPos.y})`
1845
- );
1846
2016
  obj.set({
1847
2017
  left: newPos.x,
1848
2018
  top: newPos.y
@@ -1850,16 +2020,14 @@ var HoleTool = class {
1850
2020
  obj.setCoords();
1851
2021
  changed = true;
1852
2022
  }
1853
- newHoles.push({ x: obj.left, y: obj.top });
1854
2023
  });
1855
2024
  if (changed) {
1856
- this.holes = newHoles;
1857
- this.canvasService.requestRenderAll();
2025
+ this.syncHolesFromCanvas();
1858
2026
  return true;
1859
2027
  }
1860
2028
  return false;
1861
2029
  }
1862
- calculateConstrainedPosition(p, g) {
2030
+ calculateConstrainedPosition(p, g, innerRadius, outerRadius) {
1863
2031
  const options = {
1864
2032
  ...g,
1865
2033
  holes: []
@@ -1873,7 +2041,6 @@ var HoleTool = class {
1873
2041
  const dist = p.distanceFrom(nearestP);
1874
2042
  const v = p.subtract(nearestP);
1875
2043
  const center = new Point(g.x, g.y);
1876
- const centerToNearest = nearestP.subtract(center);
1877
2044
  const distToCenter = p.distanceFrom(center);
1878
2045
  const nearestDistToCenter = nearestP.distanceFrom(center);
1879
2046
  let signedDist = dist;
@@ -1882,9 +2049,9 @@ var HoleTool = class {
1882
2049
  }
1883
2050
  let clampedDist = signedDist;
1884
2051
  if (signedDist > 0) {
1885
- clampedDist = Math.min(signedDist, this.innerRadius);
2052
+ clampedDist = Math.min(signedDist, innerRadius);
1886
2053
  } else {
1887
- clampedDist = Math.max(signedDist, -this.outerRadius);
2054
+ clampedDist = Math.max(signedDist, -outerRadius);
1888
2055
  }
1889
2056
  if (dist < 1e-3) return nearestP;
1890
2057
  const scale = Math.abs(clampedDist) / (dist || 1);
@@ -1897,19 +2064,16 @@ var HoleTool = class {
1897
2064
  import {
1898
2065
  ContributionPointIds as ContributionPointIds5
1899
2066
  } from "@pooder/core";
1900
- import { FabricImage as Image4, Point as Point2, util } from "fabric";
2067
+ import { Image as Image4, Point as Point2, util } from "fabric";
1901
2068
  var ImageTool = class {
1902
- constructor(options) {
2069
+ constructor() {
1903
2070
  this.id = "pooder.kit.image";
1904
2071
  this.metadata = {
1905
2072
  name: "ImageTool"
1906
2073
  };
1907
- this._loadingUrl = null;
1908
- this.url = "";
1909
- this.opacity = 1;
1910
- if (options) {
1911
- Object.assign(this, options);
1912
- }
2074
+ this.items = [];
2075
+ this.objectMap = /* @__PURE__ */ new Map();
2076
+ this.isUpdatingConfig = false;
1913
2077
  }
1914
2078
  activate(context) {
1915
2079
  this.context = context;
@@ -1920,38 +2084,33 @@ var ImageTool = class {
1920
2084
  }
1921
2085
  const configService = context.services.get("ConfigurationService");
1922
2086
  if (configService) {
1923
- this.url = configService.get("image.url", this.url);
1924
- this.opacity = configService.get("image.opacity", this.opacity);
1925
- this.width = configService.get("image.width", this.width);
1926
- this.height = configService.get("image.height", this.height);
1927
- this.angle = configService.get("image.angle", this.angle);
1928
- this.left = configService.get("image.left", this.left);
1929
- this.top = configService.get("image.top", this.top);
2087
+ this.items = configService.get("image.items", []) || [];
1930
2088
  configService.onAnyChange((e) => {
1931
- if (e.key.startsWith("image.")) {
1932
- const prop = e.key.split(".")[1];
1933
- console.log(
1934
- `[ImageTool] Config change detected: ${e.key} -> ${e.value}`
1935
- );
1936
- if (prop && prop in this) {
1937
- this[prop] = e.value;
1938
- this.updateImage();
1939
- }
2089
+ if (this.isUpdatingConfig) return;
2090
+ let shouldUpdate = false;
2091
+ if (e.key === "image.items") {
2092
+ this.items = e.value || [];
2093
+ shouldUpdate = true;
2094
+ } else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
2095
+ shouldUpdate = true;
2096
+ }
2097
+ if (shouldUpdate) {
2098
+ this.updateImages();
1940
2099
  }
1941
2100
  });
1942
2101
  }
1943
2102
  this.ensureLayer();
1944
- this.updateImage();
2103
+ this.updateImages();
1945
2104
  }
1946
2105
  deactivate(context) {
1947
2106
  if (this.canvasService) {
1948
2107
  const layer = this.canvasService.getLayer("user");
1949
2108
  if (layer) {
1950
- const userImage = this.canvasService.getObject("user-image", "user");
1951
- if (userImage) {
1952
- layer.remove(userImage);
1953
- this.canvasService.requestRenderAll();
1954
- }
2109
+ this.objectMap.forEach((obj) => {
2110
+ layer.remove(obj);
2111
+ });
2112
+ this.objectMap.clear();
2113
+ this.canvasService.requestRenderAll();
1955
2114
  }
1956
2115
  this.canvasService = void 0;
1957
2116
  this.context = void 0;
@@ -1961,82 +2120,103 @@ var ImageTool = class {
1961
2120
  return {
1962
2121
  [ContributionPointIds5.CONFIGURATIONS]: [
1963
2122
  {
1964
- id: "image.url",
1965
- type: "string",
1966
- label: "Image URL",
1967
- default: this.url
1968
- },
2123
+ id: "image.items",
2124
+ type: "array",
2125
+ label: "Images",
2126
+ default: []
2127
+ }
2128
+ ],
2129
+ [ContributionPointIds5.COMMANDS]: [
1969
2130
  {
1970
- id: "image.opacity",
1971
- type: "number",
1972
- label: "Opacity",
1973
- min: 0,
1974
- max: 1,
1975
- step: 0.1,
1976
- default: this.opacity
2131
+ command: "addImage",
2132
+ title: "Add Image",
2133
+ handler: (url, options) => {
2134
+ const newItem = {
2135
+ id: this.generateId(),
2136
+ url,
2137
+ opacity: 1,
2138
+ ...options
2139
+ };
2140
+ this.updateConfig([...this.items, newItem]);
2141
+ return newItem.id;
2142
+ }
1977
2143
  },
1978
2144
  {
1979
- id: "image.width",
1980
- type: "number",
1981
- label: "Width",
1982
- min: 0,
1983
- max: 5e3,
1984
- default: this.width
2145
+ command: "removeImage",
2146
+ title: "Remove Image",
2147
+ handler: (id) => {
2148
+ const newItems = this.items.filter((item) => item.id !== id);
2149
+ if (newItems.length !== this.items.length) {
2150
+ this.updateConfig(newItems);
2151
+ }
2152
+ }
1985
2153
  },
1986
2154
  {
1987
- id: "image.height",
1988
- type: "number",
1989
- label: "Height",
1990
- min: 0,
1991
- max: 5e3,
1992
- default: this.height
2155
+ command: "updateImage",
2156
+ title: "Update Image",
2157
+ handler: (id, updates) => {
2158
+ const index = this.items.findIndex((item) => item.id === id);
2159
+ if (index !== -1) {
2160
+ const newItems = [...this.items];
2161
+ newItems[index] = { ...newItems[index], ...updates };
2162
+ this.updateConfig(newItems);
2163
+ }
2164
+ }
1993
2165
  },
1994
2166
  {
1995
- id: "image.angle",
1996
- type: "number",
1997
- label: "Rotation",
1998
- min: 0,
1999
- max: 360,
2000
- default: this.angle
2167
+ command: "clearImages",
2168
+ title: "Clear Images",
2169
+ handler: () => {
2170
+ this.updateConfig([]);
2171
+ }
2001
2172
  },
2002
2173
  {
2003
- id: "image.left",
2004
- type: "number",
2005
- label: "Left (Normalized)",
2006
- min: 0,
2007
- max: 1,
2008
- default: this.left
2174
+ command: "bringToFront",
2175
+ title: "Bring Image to Front",
2176
+ handler: (id) => {
2177
+ const index = this.items.findIndex((item) => item.id === id);
2178
+ if (index !== -1 && index < this.items.length - 1) {
2179
+ const newItems = [...this.items];
2180
+ const [item] = newItems.splice(index, 1);
2181
+ newItems.push(item);
2182
+ this.updateConfig(newItems);
2183
+ }
2184
+ }
2009
2185
  },
2010
2186
  {
2011
- id: "image.top",
2012
- type: "number",
2013
- label: "Top (Normalized)",
2014
- min: 0,
2015
- max: 1,
2016
- default: this.top
2017
- }
2018
- ],
2019
- [ContributionPointIds5.COMMANDS]: [
2020
- {
2021
- command: "setUserImage",
2022
- title: "Set User Image",
2023
- handler: (url, opacity, width, height, angle, left, top) => {
2024
- if (this.url === url && this.opacity === opacity && this.width === width && this.height === height && this.angle === angle && this.left === left && this.top === top)
2025
- return true;
2026
- this.url = url;
2027
- this.opacity = opacity;
2028
- this.width = width;
2029
- this.height = height;
2030
- this.angle = angle;
2031
- this.left = left;
2032
- this.top = top;
2033
- this.updateImage();
2034
- return true;
2187
+ command: "sendToBack",
2188
+ title: "Send Image to Back",
2189
+ handler: (id) => {
2190
+ const index = this.items.findIndex((item) => item.id === id);
2191
+ if (index > 0) {
2192
+ const newItems = [...this.items];
2193
+ const [item] = newItems.splice(index, 1);
2194
+ newItems.unshift(item);
2195
+ this.updateConfig(newItems);
2196
+ }
2035
2197
  }
2036
2198
  }
2037
2199
  ]
2038
2200
  };
2039
2201
  }
2202
+ generateId() {
2203
+ return Math.random().toString(36).substring(2, 9);
2204
+ }
2205
+ updateConfig(newItems, skipCanvasUpdate = false) {
2206
+ if (!this.context) return;
2207
+ this.isUpdatingConfig = true;
2208
+ this.items = newItems;
2209
+ const configService = this.context.services.get("ConfigurationService");
2210
+ if (configService) {
2211
+ configService.update("image.items", newItems);
2212
+ }
2213
+ if (!skipCanvasUpdate) {
2214
+ this.updateImages();
2215
+ }
2216
+ setTimeout(() => {
2217
+ this.isUpdatingConfig = false;
2218
+ }, 50);
2219
+ }
2040
2220
  ensureLayer() {
2041
2221
  if (!this.canvasService) return;
2042
2222
  let userLayer = this.canvasService.getLayer("user");
@@ -2068,224 +2248,176 @@ var ImageTool = class {
2068
2248
  this.canvasService.requestRenderAll();
2069
2249
  }
2070
2250
  }
2071
- updateImage() {
2251
+ getLayoutInfo() {
2072
2252
  var _a, _b;
2253
+ const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
2254
+ const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
2255
+ let layoutScale = 1;
2256
+ let layoutOffsetX = 0;
2257
+ let layoutOffsetY = 0;
2258
+ let visualWidth = canvasW;
2259
+ let visualHeight = canvasH;
2260
+ let dielinePhysicalWidth = 500;
2261
+ let dielinePhysicalHeight = 500;
2262
+ if (this.context) {
2263
+ const configService = this.context.services.get("ConfigurationService");
2264
+ if (configService) {
2265
+ dielinePhysicalWidth = configService.get("dieline.width") || 500;
2266
+ dielinePhysicalHeight = configService.get("dieline.height") || 500;
2267
+ const padding = configService.get("dieline.padding") || 40;
2268
+ const layout = Coordinate.calculateLayout(
2269
+ { width: canvasW, height: canvasH },
2270
+ { width: dielinePhysicalWidth, height: dielinePhysicalHeight },
2271
+ padding
2272
+ );
2273
+ layoutScale = layout.scale;
2274
+ layoutOffsetX = layout.offsetX;
2275
+ layoutOffsetY = layout.offsetY;
2276
+ visualWidth = layout.width;
2277
+ visualHeight = layout.height;
2278
+ }
2279
+ }
2280
+ return {
2281
+ layoutScale,
2282
+ layoutOffsetX,
2283
+ layoutOffsetY,
2284
+ visualWidth,
2285
+ visualHeight,
2286
+ dielinePhysicalWidth,
2287
+ dielinePhysicalHeight
2288
+ };
2289
+ }
2290
+ updateImages() {
2073
2291
  if (!this.canvasService) return;
2074
- let { url, opacity, width, height, angle, left, top } = this;
2075
2292
  const layer = this.canvasService.getLayer("user");
2076
2293
  if (!layer) {
2077
2294
  console.warn("[ImageTool] User layer not found");
2078
2295
  return;
2079
2296
  }
2080
- const userImage = this.canvasService.getObject("user-image", "user");
2081
- if (this._loadingUrl === url) return;
2082
- if (userImage) {
2083
- const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
2084
- if (currentSrc !== url) {
2085
- this.loadImage(layer);
2297
+ const currentIds = new Set(this.items.map((i) => i.id));
2298
+ for (const [id, obj] of this.objectMap) {
2299
+ if (!currentIds.has(id)) {
2300
+ layer.remove(obj);
2301
+ this.objectMap.delete(id);
2302
+ }
2303
+ }
2304
+ const layout = this.getLayoutInfo();
2305
+ this.items.forEach((item, index) => {
2306
+ let obj = this.objectMap.get(item.id);
2307
+ if (!obj) {
2308
+ this.loadImage(item, layer, layout);
2086
2309
  } else {
2087
- const updates = {};
2088
- const canvasW = this.canvasService.canvas.width || 800;
2089
- const canvasH = this.canvasService.canvas.height || 600;
2090
- const centerX = canvasW / 2;
2091
- const centerY = canvasH / 2;
2092
- if (userImage.opacity !== opacity) updates.opacity = opacity;
2093
- if (angle !== void 0 && userImage.angle !== angle)
2094
- updates.angle = angle;
2095
- if (userImage.originX !== "center") {
2096
- userImage.set({
2097
- originX: "center",
2098
- originY: "center",
2099
- left: userImage.left + userImage.width * userImage.scaleX / 2,
2100
- top: userImage.top + userImage.height * userImage.scaleY / 2
2101
- });
2102
- }
2103
- if (left !== void 0) {
2104
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2105
- const localLeft = globalLeft - centerX;
2106
- if (Math.abs(userImage.left - localLeft) > 1)
2107
- updates.left = localLeft;
2108
- }
2109
- if (top !== void 0) {
2110
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2111
- const localTop = globalTop - centerY;
2112
- if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
2113
- }
2114
- if (width !== void 0 && userImage.width)
2115
- updates.scaleX = width / userImage.width;
2116
- if (height !== void 0 && userImage.height)
2117
- updates.scaleY = height / userImage.height;
2118
- if (Object.keys(updates).length > 0) {
2119
- userImage.set(updates);
2120
- layer.dirty = true;
2121
- this.canvasService.requestRenderAll();
2122
- }
2310
+ this.updateObjectProperties(obj, item, layout);
2311
+ layer.remove(obj);
2312
+ layer.add(obj);
2123
2313
  }
2124
- } else {
2125
- this.loadImage(layer);
2314
+ });
2315
+ layer.dirty = true;
2316
+ this.canvasService.requestRenderAll();
2317
+ }
2318
+ updateObjectProperties(obj, item, layout) {
2319
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2320
+ const updates = {};
2321
+ if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
2322
+ if (item.angle !== void 0 && obj.angle !== item.angle) updates.angle = item.angle;
2323
+ if (item.left !== void 0) {
2324
+ const globalLeft = layoutOffsetX + item.left * visualWidth;
2325
+ if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
2326
+ }
2327
+ if (item.top !== void 0) {
2328
+ const globalTop = layoutOffsetY + item.top * visualHeight;
2329
+ if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
2330
+ }
2331
+ if (item.width !== void 0 && obj.width) {
2332
+ const targetScaleX = item.width * layoutScale / obj.width;
2333
+ if (Math.abs(obj.scaleX - targetScaleX) > 1e-3) updates.scaleX = targetScaleX;
2334
+ }
2335
+ if (item.height !== void 0 && obj.height) {
2336
+ const targetScaleY = item.height * layoutScale / obj.height;
2337
+ if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
2338
+ }
2339
+ if (obj.originX !== "center") {
2340
+ updates.originX = "center";
2341
+ updates.originY = "center";
2342
+ }
2343
+ if (Object.keys(updates).length > 0) {
2344
+ obj.set(updates);
2126
2345
  }
2127
2346
  }
2128
- loadImage(layer) {
2129
- if (!this.canvasService) return;
2130
- const { url } = this;
2131
- if (!url) return;
2132
- this._loadingUrl = url;
2133
- Image4.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2134
- var _a, _b, _c, _d, _e, _f;
2135
- if (this._loadingUrl !== url) return;
2136
- this._loadingUrl = null;
2137
- let { opacity, width, height, angle, left, top } = this;
2138
- if (this.context) {
2139
- const configService = this.context.services.get(
2140
- "ConfigurationService"
2141
- );
2142
- const dielineWidth = configService.get("dieline.width");
2143
- const dielineHeight = configService.get("dieline.height");
2144
- console.log(
2145
- "[ImageTool] Dieline config debug:",
2146
- {
2147
- widthVal: dielineWidth,
2148
- heightVal: dielineHeight,
2149
- // Debug: dump all keys to see what is available
2150
- allKeys: Array.from(
2151
- ((_a = configService.configValues) == null ? void 0 : _a.keys()) || []
2152
- )
2153
- },
2154
- configService
2155
- );
2156
- if (width === void 0 && height === void 0) {
2157
- const scale = Math.min(
2158
- dielineWidth / (image.width || 1),
2159
- dielineHeight / (image.height || 1)
2160
- );
2161
- width = (image.width || 1) * scale;
2162
- height = (image.height || 1) * scale;
2163
- this.width = width;
2164
- this.height = height;
2165
- }
2166
- if (left === void 0 && top === void 0) {
2167
- const dielinePos = configService == null ? void 0 : configService.get("dieline.position");
2168
- if (dielinePos) {
2169
- this.left = dielinePos.x;
2170
- this.top = dielinePos.y;
2171
- } else {
2172
- this.left = 0.5;
2173
- this.top = 0.5;
2174
- }
2175
- left = this.left;
2176
- top = this.top;
2177
- }
2178
- }
2179
- const existingImage = this.canvasService.getObject(
2180
- "user-image",
2181
- "user"
2182
- );
2183
- if (existingImage) {
2184
- const defaultLeft = existingImage.left;
2185
- const defaultTop = existingImage.top;
2186
- const defaultAngle = existingImage.angle;
2187
- const defaultScaleX = existingImage.scaleX;
2188
- const defaultScaleY = existingImage.scaleY;
2189
- const canvasW = ((_b = this.canvasService) == null ? void 0 : _b.canvas.width) || 800;
2190
- const canvasH = ((_c = this.canvasService) == null ? void 0 : _c.canvas.height) || 600;
2191
- const centerX = canvasW / 2;
2192
- const centerY = canvasH / 2;
2193
- let targetLeft = left !== void 0 ? left : defaultLeft;
2194
- let targetTop = top !== void 0 ? top : defaultTop;
2195
- const configService = (_d = this.context) == null ? void 0 : _d.services.get(
2196
- "ConfigurationService"
2197
- );
2198
- console.log("[ImageTool] Loading EXISTING image...", {
2199
- canvasW,
2200
- canvasH,
2201
- centerX,
2202
- centerY,
2203
- incomingLeft: left,
2204
- incomingTop: top,
2205
- dielinePos: configService == null ? void 0 : configService.get("dieline.position"),
2206
- existingImage: !!existingImage
2207
- });
2208
- if (left !== void 0) {
2209
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2210
- targetLeft = globalLeft;
2211
- console.log("[ImageTool] Calculated targetLeft", {
2212
- globalLeft,
2213
- targetLeft
2214
- });
2215
- }
2216
- if (top !== void 0) {
2217
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2218
- targetTop = globalTop;
2219
- console.log("[ImageTool] Calculated targetTop", {
2220
- globalTop,
2221
- targetTop
2222
- });
2223
- }
2224
- image.set({
2225
- originX: "center",
2226
- // Use center origin for easier positioning
2227
- originY: "center",
2228
- left: targetLeft,
2229
- top: targetTop,
2230
- angle: angle !== void 0 ? angle : defaultAngle,
2231
- scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
2232
- scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
2233
- });
2234
- layer.remove(existingImage);
2235
- } else {
2236
- image.set({
2237
- originX: "center",
2238
- originY: "center"
2239
- });
2240
- if (width !== void 0 && image.width)
2241
- image.scaleX = width / image.width;
2242
- if (height !== void 0 && image.height)
2243
- image.scaleY = height / image.height;
2244
- if (angle !== void 0) image.angle = angle;
2245
- const canvasW = ((_e = this.canvasService) == null ? void 0 : _e.canvas.width) || 800;
2246
- const canvasH = ((_f = this.canvasService) == null ? void 0 : _f.canvas.height) || 600;
2247
- const centerX = canvasW / 2;
2248
- const centerY = canvasH / 2;
2249
- if (left !== void 0) {
2250
- image.left = Coordinate.toAbsolute(left, canvasW);
2251
- } else {
2252
- image.left = centerX;
2253
- }
2254
- if (top !== void 0) {
2255
- image.top = Coordinate.toAbsolute(top, canvasH);
2347
+ loadImage(item, layer, layout) {
2348
+ Image4.fromURL(item.url, { crossOrigin: "anonymous" }).then((image) => {
2349
+ var _a;
2350
+ if (!this.items.find((i) => i.id === item.id)) return;
2351
+ image.set({
2352
+ originX: "center",
2353
+ originY: "center",
2354
+ data: { id: item.id }
2355
+ });
2356
+ let { width, height, left, top } = item;
2357
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, dielinePhysicalWidth, dielinePhysicalHeight } = layout;
2358
+ if (width === void 0 && height === void 0) {
2359
+ const imgAspect = (image.width || 1) / (image.height || 1);
2360
+ const dielineAspect = dielinePhysicalWidth / dielinePhysicalHeight;
2361
+ if (imgAspect > dielineAspect) {
2362
+ const w = dielinePhysicalWidth;
2363
+ width = w;
2364
+ height = w / imgAspect;
2256
2365
  } else {
2257
- image.top = centerY;
2366
+ const h = dielinePhysicalHeight;
2367
+ height = h;
2368
+ width = h * imgAspect;
2258
2369
  }
2370
+ item.width = width;
2371
+ item.height = height;
2259
2372
  }
2260
- image.set({
2261
- opacity: opacity !== void 0 ? opacity : 1,
2262
- data: {
2263
- id: "user-image"
2264
- }
2265
- });
2373
+ if (left === void 0 && top === void 0) {
2374
+ left = 0.5;
2375
+ top = 0.5;
2376
+ item.left = left;
2377
+ item.top = top;
2378
+ }
2379
+ this.updateObjectProperties(image, item, layout);
2266
2380
  layer.add(image);
2381
+ this.objectMap.set(item.id, image);
2267
2382
  image.on("modified", (e) => {
2268
- var _a2, _b2;
2269
- const matrix = image.calcTransformMatrix();
2270
- const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
2271
- const canvasW = ((_a2 = this.canvasService) == null ? void 0 : _a2.canvas.width) || 800;
2272
- const canvasH = ((_b2 = this.canvasService) == null ? void 0 : _b2.canvas.height) || 600;
2273
- this.left = Coordinate.toNormalized(globalPoint.x, canvasW);
2274
- this.top = Coordinate.toNormalized(globalPoint.y, canvasH);
2275
- this.angle = e.target.angle;
2276
- if (image.width) this.width = e.target.width * e.target.scaleX;
2277
- if (image.height) this.height = e.target.height * e.target.scaleY;
2278
- if (this.context) {
2279
- this.context.eventBus.emit("update");
2280
- }
2383
+ this.handleObjectModified(item.id, image);
2281
2384
  });
2282
2385
  layer.dirty = true;
2283
- this.canvasService.requestRenderAll();
2386
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2387
+ if (item.width !== width || item.height !== height || item.left !== left || item.top !== top) {
2388
+ this.updateImageInConfig(item.id, { width, height, left, top });
2389
+ }
2284
2390
  }).catch((err) => {
2285
- if (this._loadingUrl === url) this._loadingUrl = null;
2286
- console.error("Failed to load image", url, err);
2391
+ console.error("Failed to load image", item.url, err);
2287
2392
  });
2288
2393
  }
2394
+ handleObjectModified(id, image) {
2395
+ const layout = this.getLayoutInfo();
2396
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2397
+ const matrix = image.calcTransformMatrix();
2398
+ const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
2399
+ const updates = {};
2400
+ updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
2401
+ updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
2402
+ updates.angle = image.angle;
2403
+ if (image.width) {
2404
+ const pixelWidth = image.width * image.scaleX;
2405
+ updates.width = pixelWidth / layoutScale;
2406
+ }
2407
+ if (image.height) {
2408
+ const pixelHeight = image.height * image.scaleY;
2409
+ updates.height = pixelHeight / layoutScale;
2410
+ }
2411
+ this.updateImageInConfig(id, updates);
2412
+ }
2413
+ updateImageInConfig(id, updates) {
2414
+ const index = this.items.findIndex((i) => i.id === id);
2415
+ if (index !== -1) {
2416
+ const newItems = [...this.items];
2417
+ newItems[index] = { ...newItems[index], ...updates };
2418
+ this.updateConfig(newItems, true);
2419
+ }
2420
+ }
2289
2421
  };
2290
2422
 
2291
2423
  // src/white-ink.ts
@@ -2585,19 +2717,25 @@ var WhiteInkTool = class {
2585
2717
  import {
2586
2718
  ContributionPointIds as ContributionPointIds7
2587
2719
  } from "@pooder/core";
2588
- import { Rect as Rect2, Line, Text } from "fabric";
2720
+ import { Line, Text, Group as Group2, Polygon } from "fabric";
2589
2721
  var RulerTool = class {
2590
2722
  constructor(options) {
2591
2723
  this.id = "pooder.kit.ruler";
2592
2724
  this.metadata = {
2593
2725
  name: "RulerTool"
2594
2726
  };
2595
- this.unit = "px";
2596
2727
  this.thickness = 20;
2728
+ this.gap = 15;
2597
2729
  this.backgroundColor = "#f0f0f0";
2598
2730
  this.textColor = "#333333";
2599
2731
  this.lineColor = "#999999";
2600
2732
  this.fontSize = 10;
2733
+ // Dieline context for sync
2734
+ this.dielineWidth = 500;
2735
+ this.dielineHeight = 500;
2736
+ this.dielineUnit = "mm";
2737
+ this.dielinePadding = 40;
2738
+ this.dielineOffset = 0;
2601
2739
  if (options) {
2602
2740
  Object.assign(this, options);
2603
2741
  }
@@ -2610,8 +2748,8 @@ var RulerTool = class {
2610
2748
  }
2611
2749
  const configService = context.services.get("ConfigurationService");
2612
2750
  if (configService) {
2613
- this.unit = configService.get("ruler.unit", this.unit);
2614
2751
  this.thickness = configService.get("ruler.thickness", this.thickness);
2752
+ this.gap = configService.get("ruler.gap", this.gap);
2615
2753
  this.backgroundColor = configService.get(
2616
2754
  "ruler.backgroundColor",
2617
2755
  this.backgroundColor
@@ -2619,13 +2757,38 @@ var RulerTool = class {
2619
2757
  this.textColor = configService.get("ruler.textColor", this.textColor);
2620
2758
  this.lineColor = configService.get("ruler.lineColor", this.lineColor);
2621
2759
  this.fontSize = configService.get("ruler.fontSize", this.fontSize);
2760
+ this.dielineUnit = configService.get("dieline.unit", this.dielineUnit);
2761
+ this.dielineWidth = configService.get("dieline.width", this.dielineWidth);
2762
+ this.dielineHeight = configService.get(
2763
+ "dieline.height",
2764
+ this.dielineHeight
2765
+ );
2766
+ this.dielinePadding = configService.get(
2767
+ "dieline.padding",
2768
+ this.dielinePadding
2769
+ );
2770
+ this.dielineOffset = configService.get(
2771
+ "dieline.offset",
2772
+ this.dielineOffset
2773
+ );
2622
2774
  configService.onAnyChange((e) => {
2775
+ let shouldUpdate = false;
2623
2776
  if (e.key.startsWith("ruler.")) {
2624
2777
  const prop = e.key.split(".")[1];
2625
2778
  if (prop && prop in this) {
2626
2779
  this[prop] = e.value;
2627
- this.updateRuler();
2780
+ shouldUpdate = true;
2628
2781
  }
2782
+ } else if (e.key.startsWith("dieline.")) {
2783
+ if (e.key === "dieline.unit") this.dielineUnit = e.value;
2784
+ if (e.key === "dieline.width") this.dielineWidth = e.value;
2785
+ if (e.key === "dieline.height") this.dielineHeight = e.value;
2786
+ if (e.key === "dieline.padding") this.dielinePadding = e.value;
2787
+ if (e.key === "dieline.offset") this.dielineOffset = e.value;
2788
+ shouldUpdate = true;
2789
+ }
2790
+ if (shouldUpdate) {
2791
+ this.updateRuler();
2629
2792
  }
2630
2793
  });
2631
2794
  }
@@ -2639,13 +2802,6 @@ var RulerTool = class {
2639
2802
  contribute() {
2640
2803
  return {
2641
2804
  [ContributionPointIds7.CONFIGURATIONS]: [
2642
- {
2643
- id: "ruler.unit",
2644
- type: "select",
2645
- label: "Unit",
2646
- options: ["px", "mm", "cm", "in"],
2647
- default: "px"
2648
- },
2649
2805
  {
2650
2806
  id: "ruler.thickness",
2651
2807
  type: "number",
@@ -2654,6 +2810,14 @@ var RulerTool = class {
2654
2810
  max: 100,
2655
2811
  default: 20
2656
2812
  },
2813
+ {
2814
+ id: "ruler.gap",
2815
+ type: "number",
2816
+ label: "Gap",
2817
+ min: 0,
2818
+ max: 100,
2819
+ default: 15
2820
+ },
2657
2821
  {
2658
2822
  id: "ruler.backgroundColor",
2659
2823
  type: "color",
@@ -2682,16 +2846,6 @@ var RulerTool = class {
2682
2846
  }
2683
2847
  ],
2684
2848
  [ContributionPointIds7.COMMANDS]: [
2685
- {
2686
- command: "setUnit",
2687
- title: "Set Ruler Unit",
2688
- handler: (unit) => {
2689
- if (this.unit === unit) return true;
2690
- this.unit = unit;
2691
- this.updateRuler();
2692
- return true;
2693
- }
2694
- },
2695
2849
  {
2696
2850
  command: "setTheme",
2697
2851
  title: "Set Ruler Theme",
@@ -2742,6 +2896,68 @@ var RulerTool = class {
2742
2896
  this.canvasService.canvas.remove(layer);
2743
2897
  }
2744
2898
  }
2899
+ createArrowLine(x1, y1, x2, y2, color) {
2900
+ const line = new Line([x1, y1, x2, y2], {
2901
+ stroke: color,
2902
+ strokeWidth: this.thickness / 20,
2903
+ // Scale stroke width relative to thickness (default 1)
2904
+ selectable: false,
2905
+ evented: false
2906
+ });
2907
+ const arrowSize = Math.max(4, this.thickness * 0.3);
2908
+ const angle = Math.atan2(y2 - y1, x2 - x1);
2909
+ const endArrow = new Polygon(
2910
+ [
2911
+ { x: 0, y: 0 },
2912
+ { x: -arrowSize, y: -arrowSize / 2 },
2913
+ { x: -arrowSize, y: arrowSize / 2 }
2914
+ ],
2915
+ {
2916
+ fill: color,
2917
+ left: x2,
2918
+ top: y2,
2919
+ originX: "right",
2920
+ originY: "center",
2921
+ angle: angle * 180 / Math.PI,
2922
+ selectable: false,
2923
+ evented: false
2924
+ }
2925
+ );
2926
+ const startArrow = new Polygon(
2927
+ [
2928
+ { x: 0, y: 0 },
2929
+ { x: arrowSize, y: -arrowSize / 2 },
2930
+ { x: arrowSize, y: arrowSize / 2 }
2931
+ ],
2932
+ {
2933
+ fill: color,
2934
+ left: x1,
2935
+ top: y1,
2936
+ originX: "left",
2937
+ originY: "center",
2938
+ angle: angle * 180 / Math.PI,
2939
+ selectable: false,
2940
+ evented: false
2941
+ }
2942
+ );
2943
+ return new Group2([line, startArrow, endArrow], {
2944
+ selectable: false,
2945
+ evented: false
2946
+ });
2947
+ }
2948
+ resolvePadding(containerWidth, containerHeight) {
2949
+ if (typeof this.dielinePadding === "number") {
2950
+ return this.dielinePadding;
2951
+ }
2952
+ if (typeof this.dielinePadding === "string") {
2953
+ if (this.dielinePadding.endsWith("%")) {
2954
+ const percent = parseFloat(this.dielinePadding) / 100;
2955
+ return Math.min(containerWidth, containerHeight) * percent;
2956
+ }
2957
+ return parseFloat(this.dielinePadding) || 0;
2958
+ }
2959
+ return 0;
2960
+ }
2745
2961
  updateRuler() {
2746
2962
  if (!this.canvasService) return;
2747
2963
  const layer = this.getLayer();
@@ -2750,95 +2966,141 @@ var RulerTool = class {
2750
2966
  const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
2751
2967
  const width = this.canvasService.canvas.width || 800;
2752
2968
  const height = this.canvasService.canvas.height || 600;
2753
- const topBg = new Rect2({
2754
- left: 0,
2755
- top: 0,
2756
- width,
2757
- height: thickness,
2758
- fill: backgroundColor,
2759
- selectable: false,
2760
- evented: false
2761
- });
2762
- const leftBg = new Rect2({
2763
- left: 0,
2764
- top: 0,
2765
- width: thickness,
2766
- height,
2767
- fill: backgroundColor,
2768
- selectable: false,
2769
- evented: false
2770
- });
2771
- const cornerBg = new Rect2({
2772
- left: 0,
2773
- top: 0,
2774
- width: thickness,
2775
- height: thickness,
2776
- fill: backgroundColor,
2777
- stroke: lineColor,
2778
- strokeWidth: 1,
2969
+ const paddingPx = this.resolvePadding(width, height);
2970
+ const layout = Coordinate.calculateLayout(
2971
+ { width, height },
2972
+ { width: this.dielineWidth, height: this.dielineHeight },
2973
+ paddingPx
2974
+ );
2975
+ const scale = layout.scale;
2976
+ const offsetX = layout.offsetX;
2977
+ const offsetY = layout.offsetY;
2978
+ const visualWidth = layout.width;
2979
+ const visualHeight = layout.height;
2980
+ const rawOffset = this.dielineOffset || 0;
2981
+ const effectiveOffset = rawOffset > 0 ? rawOffset : 0;
2982
+ const expandPixels = effectiveOffset * scale;
2983
+ const gap = this.gap || 15;
2984
+ const rulerLeft = offsetX - expandPixels;
2985
+ const rulerTop = offsetY - expandPixels;
2986
+ const rulerRight = offsetX + visualWidth + expandPixels;
2987
+ const rulerBottom = offsetY + visualHeight + expandPixels;
2988
+ const displayWidth = this.dielineWidth + effectiveOffset * 2;
2989
+ const displayHeight = this.dielineHeight + effectiveOffset * 2;
2990
+ const topRulerY = rulerTop - gap;
2991
+ const topRulerXStart = rulerLeft;
2992
+ const topRulerXEnd = rulerRight;
2993
+ const leftRulerX = rulerLeft - gap;
2994
+ const leftRulerYStart = rulerTop;
2995
+ const leftRulerYEnd = rulerBottom;
2996
+ const topDimLine = this.createArrowLine(
2997
+ topRulerXStart,
2998
+ topRulerY,
2999
+ topRulerXEnd,
3000
+ topRulerY,
3001
+ lineColor
3002
+ );
3003
+ layer.add(topDimLine);
3004
+ const extLen = 5;
3005
+ layer.add(
3006
+ new Line(
3007
+ [
3008
+ topRulerXStart,
3009
+ topRulerY - extLen,
3010
+ topRulerXStart,
3011
+ topRulerY + extLen
3012
+ ],
3013
+ {
3014
+ stroke: lineColor,
3015
+ strokeWidth: 1,
3016
+ selectable: false,
3017
+ evented: false
3018
+ }
3019
+ )
3020
+ );
3021
+ layer.add(
3022
+ new Line(
3023
+ [topRulerXEnd, topRulerY - extLen, topRulerXEnd, topRulerY + extLen],
3024
+ {
3025
+ stroke: lineColor,
3026
+ strokeWidth: 1,
3027
+ selectable: false,
3028
+ evented: false
3029
+ }
3030
+ )
3031
+ );
3032
+ const widthStr = parseFloat(displayWidth.toFixed(2)).toString();
3033
+ const topTextContent = `${widthStr} ${this.dielineUnit}`;
3034
+ const topText = new Text(topTextContent, {
3035
+ left: topRulerXStart + (rulerRight - rulerLeft) / 2,
3036
+ top: topRulerY,
3037
+ fontSize,
3038
+ fill: textColor,
3039
+ fontFamily: "Arial",
3040
+ originX: "center",
3041
+ originY: "center",
3042
+ backgroundColor,
3043
+ // Background mask for readability
2779
3044
  selectable: false,
2780
3045
  evented: false
2781
3046
  });
2782
- layer.add(topBg);
2783
- layer.add(leftBg);
2784
- layer.add(cornerBg);
2785
- const step = 100;
2786
- const subStep = 10;
2787
- const midStep = 50;
2788
- for (let x = 0; x <= width; x += subStep) {
2789
- if (x < thickness) continue;
2790
- let len = thickness * 0.25;
2791
- if (x % step === 0) len = thickness * 0.8;
2792
- else if (x % midStep === 0) len = thickness * 0.5;
2793
- const line = new Line([x, thickness - len, x, thickness], {
2794
- stroke: lineColor,
2795
- strokeWidth: 1,
2796
- selectable: false,
2797
- evented: false
2798
- });
2799
- layer.add(line);
2800
- if (x % step === 0) {
2801
- const text = new Text(x.toString(), {
2802
- left: x + 2,
2803
- top: 2,
2804
- fontSize,
2805
- fill: textColor,
2806
- fontFamily: "Arial",
3047
+ layer.add(topText);
3048
+ const leftDimLine = this.createArrowLine(
3049
+ leftRulerX,
3050
+ leftRulerYStart,
3051
+ leftRulerX,
3052
+ leftRulerYEnd,
3053
+ lineColor
3054
+ );
3055
+ layer.add(leftDimLine);
3056
+ layer.add(
3057
+ new Line(
3058
+ [
3059
+ leftRulerX - extLen,
3060
+ leftRulerYStart,
3061
+ leftRulerX + extLen,
3062
+ leftRulerYStart
3063
+ ],
3064
+ {
3065
+ stroke: lineColor,
3066
+ strokeWidth: 1,
2807
3067
  selectable: false,
2808
3068
  evented: false
2809
- });
2810
- layer.add(text);
2811
- }
2812
- }
2813
- for (let y = 0; y <= height; y += subStep) {
2814
- if (y < thickness) continue;
2815
- let len = thickness * 0.25;
2816
- if (y % step === 0) len = thickness * 0.8;
2817
- else if (y % midStep === 0) len = thickness * 0.5;
2818
- const line = new Line([thickness - len, y, thickness, y], {
2819
- stroke: lineColor,
2820
- strokeWidth: 1,
2821
- selectable: false,
2822
- evented: false
2823
- });
2824
- layer.add(line);
2825
- if (y % step === 0) {
2826
- const text = new Text(y.toString(), {
2827
- angle: -90,
2828
- left: thickness / 2 - fontSize / 3,
2829
- // approximate centering
2830
- top: y + fontSize,
2831
- fontSize,
2832
- fill: textColor,
2833
- fontFamily: "Arial",
2834
- originX: "center",
2835
- originY: "center",
3069
+ }
3070
+ )
3071
+ );
3072
+ layer.add(
3073
+ new Line(
3074
+ [
3075
+ leftRulerX - extLen,
3076
+ leftRulerYEnd,
3077
+ leftRulerX + extLen,
3078
+ leftRulerYEnd
3079
+ ],
3080
+ {
3081
+ stroke: lineColor,
3082
+ strokeWidth: 1,
2836
3083
  selectable: false,
2837
3084
  evented: false
2838
- });
2839
- layer.add(text);
2840
- }
2841
- }
3085
+ }
3086
+ )
3087
+ );
3088
+ const heightStr = parseFloat(displayHeight.toFixed(2)).toString();
3089
+ const leftTextContent = `${heightStr} ${this.dielineUnit}`;
3090
+ const leftText = new Text(leftTextContent, {
3091
+ left: leftRulerX,
3092
+ top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
3093
+ angle: -90,
3094
+ fontSize,
3095
+ fill: textColor,
3096
+ fontFamily: "Arial",
3097
+ originX: "center",
3098
+ originY: "center",
3099
+ backgroundColor,
3100
+ selectable: false,
3101
+ evented: false
3102
+ });
3103
+ layer.add(leftText);
2842
3104
  this.canvasService.canvas.bringObjectToFront(layer);
2843
3105
  this.canvasService.canvas.requestRenderAll();
2844
3106
  }
@@ -2937,7 +3199,7 @@ var MirrorTool = class {
2937
3199
  };
2938
3200
 
2939
3201
  // src/CanvasService.ts
2940
- import { Canvas, Group as Group2 } from "fabric";
3202
+ import { Canvas, Group as Group3 } from "fabric";
2941
3203
  var CanvasService = class {
2942
3204
  constructor(el, options) {
2943
3205
  if (el instanceof Canvas) {
@@ -2974,7 +3236,7 @@ var CanvasService = class {
2974
3236
  ...options,
2975
3237
  data: { ...options.data, id }
2976
3238
  };
2977
- layer = new Group2([], defaultOptions);
3239
+ layer = new Group3([], defaultOptions);
2978
3240
  this.canvas.add(layer);
2979
3241
  }
2980
3242
  return layer;