@pooder/kit 3.1.0 → 3.3.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,6 +446,21 @@ 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
@@ -484,9 +517,10 @@ function resolveHolePosition(hole, geometry, canvasSize) {
484
517
  y: by + (hole.offsetY || 0)
485
518
  };
486
519
  } else if (hole.x !== void 0 && hole.y !== void 0) {
520
+ const { x, width, y, height } = geometry;
487
521
  return {
488
- x: hole.x * canvasSize.width,
489
- y: hole.y * canvasSize.height
522
+ x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
523
+ y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
490
524
  };
491
525
  }
492
526
  return { x: 0, y: 0 };
@@ -540,12 +574,25 @@ function getDielineShape(options) {
540
574
  let lugsPath = null;
541
575
  let cutsPath = null;
542
576
  holes.forEach((hole) => {
543
- const lug = new paper.Path.Circle({
544
- center: [hole.x, hole.y],
577
+ const center = new paper.Point(hole.x, hole.y);
578
+ const lug = hole.shape === "square" ? new paper.Path.Rectangle({
579
+ point: [
580
+ center.x - hole.outerRadius,
581
+ center.y - hole.outerRadius
582
+ ],
583
+ size: [hole.outerRadius * 2, hole.outerRadius * 2]
584
+ }) : new paper.Path.Circle({
585
+ center,
545
586
  radius: hole.outerRadius
546
587
  });
547
- const cut = new paper.Path.Circle({
548
- center: [hole.x, hole.y],
588
+ const cut = hole.shape === "square" ? new paper.Path.Rectangle({
589
+ point: [
590
+ center.x - hole.innerRadius,
591
+ center.y - hole.innerRadius
592
+ ],
593
+ size: [hole.innerRadius * 2, hole.innerRadius * 2]
594
+ }) : new paper.Path.Circle({
595
+ center,
549
596
  radius: hole.innerRadius
550
597
  });
551
598
  if (!lugsPath) {
@@ -592,14 +639,19 @@ function getDielineShape(options) {
592
639
  cutsPath.remove();
593
640
  mainShape = temp;
594
641
  } catch (e) {
595
- console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
642
+ console.error(
643
+ "Geometry: Failed to subtract cutsPath from mainShape",
644
+ e
645
+ );
596
646
  }
597
647
  }
598
648
  }
599
649
  return mainShape;
600
650
  }
601
651
  function generateDielinePath(options) {
602
- ensurePaper(options.width * 2, options.height * 2);
652
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
653
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
654
+ ensurePaper(paperWidth, paperHeight);
603
655
  paper.project.activeLayer.removeChildren();
604
656
  const mainShape = getDielineShape(options);
605
657
  const pathData = mainShape.pathData;
@@ -623,8 +675,9 @@ function generateMaskPath(options) {
623
675
  return pathData;
624
676
  }
625
677
  function generateBleedZonePath(options, offset) {
626
- const maxDim = Math.max(options.width, options.height) + Math.abs(offset) * 4;
627
- ensurePaper(maxDim, maxDim);
678
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
679
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
680
+ ensurePaper(paperWidth, paperHeight);
628
681
  paper.project.activeLayer.removeChildren();
629
682
  const shapeOriginal = getDielineShape(options);
630
683
  let shapeOffset;
@@ -685,7 +738,12 @@ function getPathBounds(pathData) {
685
738
  path.pathData = pathData;
686
739
  const bounds = path.bounds;
687
740
  path.remove();
688
- return { width: bounds.width, height: bounds.height };
741
+ return {
742
+ x: bounds.x,
743
+ y: bounds.y,
744
+ width: bounds.width,
745
+ height: bounds.height
746
+ };
689
747
  }
690
748
 
691
749
  // src/dieline.ts
@@ -695,6 +753,7 @@ var DielineTool = class {
695
753
  this.metadata = {
696
754
  name: "DielineTool"
697
755
  };
756
+ this.unit = "mm";
698
757
  this.shape = "rect";
699
758
  this.width = 500;
700
759
  this.height = 500;
@@ -705,6 +764,7 @@ var DielineTool = class {
705
764
  this.outsideColor = "#ffffff";
706
765
  this.showBleedLines = true;
707
766
  this.holes = [];
767
+ this.padding = 140;
708
768
  if (options) {
709
769
  Object.assign(this, options);
710
770
  }
@@ -718,14 +778,12 @@ var DielineTool = class {
718
778
  }
719
779
  const configService = context.services.get("ConfigurationService");
720
780
  if (configService) {
781
+ this.unit = configService.get("dieline.unit", this.unit);
721
782
  this.shape = configService.get("dieline.shape", this.shape);
722
783
  this.width = configService.get("dieline.width", this.width);
723
784
  this.height = configService.get("dieline.height", this.height);
724
785
  this.radius = configService.get("dieline.radius", this.radius);
725
- this.borderLength = configService.get(
726
- "dieline.borderLength",
727
- this.borderLength
728
- );
786
+ this.padding = configService.get("dieline.padding", this.padding);
729
787
  this.offset = configService.get("dieline.offset", this.offset);
730
788
  this.style = configService.get("dieline.style", this.style);
731
789
  this.insideColor = configService.get(
@@ -766,6 +824,13 @@ var DielineTool = class {
766
824
  contribute() {
767
825
  return {
768
826
  [ContributionPointIds2.CONFIGURATIONS]: [
827
+ {
828
+ id: "dieline.unit",
829
+ type: "select",
830
+ label: "Unit",
831
+ options: ["px", "mm", "cm", "in"],
832
+ default: this.unit
833
+ },
769
834
  {
770
835
  id: "dieline.shape",
771
836
  type: "select",
@@ -801,15 +866,14 @@ var DielineTool = class {
801
866
  id: "dieline.position",
802
867
  type: "json",
803
868
  label: "Position (Normalized)",
804
- default: this.position
869
+ default: this.radius
805
870
  },
806
871
  {
807
- id: "dieline.borderLength",
808
- type: "number",
809
- label: "Margin",
810
- min: 0,
811
- max: 500,
812
- default: this.borderLength
872
+ id: "dieline.padding",
873
+ type: "select",
874
+ label: "View Padding",
875
+ options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
876
+ default: this.padding
813
877
  },
814
878
  {
815
879
  id: "dieline.offset",
@@ -878,7 +942,9 @@ var DielineTool = class {
878
942
  const scale = currentMax / Math.max(bounds.width, bounds.height);
879
943
  const newWidth = bounds.width * scale;
880
944
  const newHeight = bounds.height * scale;
881
- const configService = (_a = this.context) == null ? void 0 : _a.services.get("ConfigurationService");
945
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
946
+ "ConfigurationService"
947
+ );
882
948
  if (configService) {
883
949
  configService.update("dieline.width", newWidth);
884
950
  configService.update("dieline.height", newHeight);
@@ -943,12 +1009,25 @@ var DielineTool = class {
943
1009
  }
944
1010
  return new Pattern({ source: canvas, repetition: "repeat" });
945
1011
  }
1012
+ resolvePadding(containerWidth, containerHeight) {
1013
+ if (typeof this.padding === "number") {
1014
+ return this.padding;
1015
+ }
1016
+ if (typeof this.padding === "string") {
1017
+ if (this.padding.endsWith("%")) {
1018
+ const percent = parseFloat(this.padding) / 100;
1019
+ return Math.min(containerWidth, containerHeight) * percent;
1020
+ }
1021
+ return parseFloat(this.padding) || 0;
1022
+ }
1023
+ return 0;
1024
+ }
946
1025
  updateDieline(emitEvent = true) {
947
- var _a, _b;
948
1026
  if (!this.canvasService) return;
949
1027
  const layer = this.getLayer();
950
1028
  if (!layer) return;
951
1029
  const {
1030
+ unit,
952
1031
  shape,
953
1032
  radius,
954
1033
  offset,
@@ -956,43 +1035,60 @@ var DielineTool = class {
956
1035
  insideColor,
957
1036
  outsideColor,
958
1037
  position,
959
- borderLength,
960
1038
  showBleedLines,
961
1039
  holes
962
1040
  } = this;
963
1041
  let { width, height } = this;
964
1042
  const canvasW = this.canvasService.canvas.width || 800;
965
1043
  const canvasH = this.canvasService.canvas.height || 600;
966
- let visualWidth = width;
967
- let visualHeight = height;
968
- if (borderLength && borderLength > 0) {
969
- visualWidth = Math.max(0, canvasW - borderLength * 2);
970
- visualHeight = Math.max(0, canvasH - borderLength * 2);
971
- }
972
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
973
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1044
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1045
+ const layout = Coordinate.calculateLayout(
1046
+ { width: canvasW, height: canvasH },
1047
+ { width, height },
1048
+ paddingPx
1049
+ );
1050
+ const scale = layout.scale;
1051
+ const cx = layout.offsetX + layout.width / 2;
1052
+ const cy = layout.offsetY + layout.height / 2;
1053
+ const visualWidth = layout.width;
1054
+ const visualHeight = layout.height;
1055
+ const visualRadius = radius * scale;
1056
+ const visualOffset = offset * scale;
974
1057
  layer.remove(...layer.getObjects());
975
1058
  const geometryForHoles = {
976
1059
  x: cx,
977
1060
  y: cy,
978
1061
  width: visualWidth,
979
1062
  height: visualHeight
1063
+ // Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
980
1064
  };
981
1065
  const absoluteHoles = (holes || []).map((h) => {
982
- const pos = resolveHolePosition(
983
- h,
984
- geometryForHoles,
985
- { width: canvasW, height: canvasH }
986
- );
1066
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1067
+ const offsetScale = unitScale * scale;
1068
+ const hWithPixelOffsets = {
1069
+ ...h,
1070
+ offsetX: (h.offsetX || 0) * offsetScale,
1071
+ offsetY: (h.offsetY || 0) * offsetScale
1072
+ };
1073
+ const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
1074
+ width: canvasW,
1075
+ height: canvasH
1076
+ });
987
1077
  return {
988
1078
  ...h,
989
1079
  x: pos.x,
990
- y: pos.y
1080
+ y: pos.y,
1081
+ // Scale hole radii: mm -> current unit -> pixels
1082
+ innerRadius: h.innerRadius * offsetScale,
1083
+ outerRadius: h.outerRadius * offsetScale,
1084
+ // Store scaled offsets in the result for consistency, though pos is already resolved
1085
+ offsetX: hWithPixelOffsets.offsetX,
1086
+ offsetY: hWithPixelOffsets.offsetY
991
1087
  };
992
1088
  });
993
- const cutW = Math.max(0, width + offset * 2);
994
- const cutH = Math.max(0, height + offset * 2);
995
- const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
1089
+ const cutW = Math.max(0, visualWidth + visualOffset * 2);
1090
+ const cutH = Math.max(0, visualHeight + visualOffset * 2);
1091
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
996
1092
  const maskPathData = generateMaskPath({
997
1093
  canvasWidth: canvasW,
998
1094
  canvasHeight: canvasH,
@@ -1025,7 +1121,9 @@ var DielineTool = class {
1025
1121
  x: cx,
1026
1122
  y: cy,
1027
1123
  holes: absoluteHoles,
1028
- pathData: this.pathData
1124
+ pathData: this.pathData,
1125
+ canvasWidth: canvasW,
1126
+ canvasHeight: canvasH
1029
1127
  });
1030
1128
  const insideObj = new Path(productPathData, {
1031
1129
  fill: insideColor,
@@ -1042,15 +1140,17 @@ var DielineTool = class {
1042
1140
  const bleedPathData = generateBleedZonePath(
1043
1141
  {
1044
1142
  shape,
1045
- width,
1046
- height,
1047
- radius,
1143
+ width: visualWidth,
1144
+ height: visualHeight,
1145
+ radius: visualRadius,
1048
1146
  x: cx,
1049
1147
  y: cy,
1050
1148
  holes: absoluteHoles,
1051
- pathData: this.pathData
1149
+ pathData: this.pathData,
1150
+ canvasWidth: canvasW,
1151
+ canvasHeight: canvasH
1052
1152
  },
1053
- offset
1153
+ visualOffset
1054
1154
  );
1055
1155
  if (showBleedLines !== false) {
1056
1156
  const pattern = this.createHatchPattern("red");
@@ -1075,7 +1175,9 @@ var DielineTool = class {
1075
1175
  x: cx,
1076
1176
  y: cy,
1077
1177
  holes: absoluteHoles,
1078
- pathData: this.pathData
1178
+ pathData: this.pathData,
1179
+ canvasWidth: canvasW,
1180
+ canvasHeight: canvasH
1079
1181
  });
1080
1182
  const offsetBorderObj = new Path(offsetPathData, {
1081
1183
  fill: null,
@@ -1093,14 +1195,15 @@ var DielineTool = class {
1093
1195
  }
1094
1196
  const borderPathData = generateDielinePath({
1095
1197
  shape,
1096
- width,
1097
- height,
1098
- radius,
1198
+ width: visualWidth,
1199
+ height: visualHeight,
1200
+ radius: visualRadius,
1099
1201
  x: cx,
1100
1202
  y: cy,
1101
1203
  holes: absoluteHoles,
1102
- // FIX: Use absoluteHoles instead of holes
1103
- pathData: this.pathData
1204
+ pathData: this.pathData,
1205
+ canvasWidth: canvasW,
1206
+ canvasHeight: canvasH
1104
1207
  });
1105
1208
  const borderObj = new Path(borderPathData, {
1106
1209
  fill: "transparent",
@@ -1137,115 +1240,109 @@ var DielineTool = class {
1137
1240
  }
1138
1241
  }
1139
1242
  getGeometry() {
1140
- var _a, _b;
1141
1243
  if (!this.canvasService) return null;
1142
- const { shape, width, height, radius, position, borderLength, offset } = this;
1244
+ const { unit, shape, width, height, radius, position, offset } = this;
1143
1245
  const canvasW = this.canvasService.canvas.width || 800;
1144
1246
  const canvasH = this.canvasService.canvas.height || 600;
1145
- let visualWidth = width;
1146
- let visualHeight = height;
1147
- if (borderLength && borderLength > 0) {
1148
- visualWidth = Math.max(0, canvasW - borderLength * 2);
1149
- visualHeight = Math.max(0, canvasH - borderLength * 2);
1150
- }
1151
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1152
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1247
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1248
+ const layout = Coordinate.calculateLayout(
1249
+ { width: canvasW, height: canvasH },
1250
+ { width, height },
1251
+ paddingPx
1252
+ );
1253
+ const scale = layout.scale;
1254
+ const cx = layout.offsetX + layout.width / 2;
1255
+ const cy = layout.offsetY + layout.height / 2;
1256
+ const visualWidth = layout.width;
1257
+ const visualHeight = layout.height;
1153
1258
  return {
1154
1259
  shape,
1260
+ unit,
1155
1261
  x: cx,
1156
1262
  y: cy,
1157
1263
  width: visualWidth,
1158
1264
  height: visualHeight,
1159
- radius,
1160
- offset,
1161
- borderLength,
1265
+ radius: radius * scale,
1266
+ offset: offset * scale,
1267
+ // Pass scale to help other tools (like HoleTool) convert units
1268
+ scale,
1162
1269
  pathData: this.pathData
1163
1270
  };
1164
1271
  }
1165
- exportCutImage() {
1166
- var _a, _b, _c, _d;
1272
+ async exportCutImage() {
1167
1273
  if (!this.canvasService) return null;
1168
- const canvas = this.canvasService.canvas;
1274
+ const userLayer = this.canvasService.getLayer("user");
1275
+ if (!userLayer) return null;
1169
1276
  const { shape, width, height, radius, position, holes } = this;
1170
- const canvasW = canvas.width || 800;
1171
- const canvasH = canvas.height || 600;
1172
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1173
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1277
+ const canvasW = this.canvasService.canvas.width || 800;
1278
+ const canvasH = this.canvasService.canvas.height || 600;
1279
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1280
+ const layout = Coordinate.calculateLayout(
1281
+ { width: canvasW, height: canvasH },
1282
+ { width, height },
1283
+ paddingPx
1284
+ );
1285
+ const scale = layout.scale;
1286
+ const cx = layout.offsetX + layout.width / 2;
1287
+ const cy = layout.offsetY + layout.height / 2;
1288
+ const visualWidth = layout.width;
1289
+ const visualHeight = layout.height;
1290
+ const visualRadius = radius * scale;
1174
1291
  const absoluteHoles = (holes || []).map((h) => {
1292
+ const unit = this.unit || "mm";
1293
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1175
1294
  const pos = resolveHolePosition(
1176
- h,
1177
- { x: cx, y: cy, width, height },
1295
+ {
1296
+ ...h,
1297
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1298
+ offsetY: (h.offsetY || 0) * unitScale * scale
1299
+ },
1300
+ { x: cx, y: cy, width: visualWidth, height: visualHeight },
1178
1301
  { width: canvasW, height: canvasH }
1179
1302
  );
1180
1303
  return {
1181
1304
  ...h,
1182
1305
  x: pos.x,
1183
- y: pos.y
1306
+ y: pos.y,
1307
+ innerRadius: h.innerRadius * unitScale * scale,
1308
+ outerRadius: h.outerRadius * unitScale * scale,
1309
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1310
+ offsetY: (h.offsetY || 0) * unitScale * scale
1184
1311
  };
1185
1312
  });
1186
1313
  const pathData = generateDielinePath({
1187
1314
  shape,
1188
- width,
1189
- height,
1190
- radius,
1315
+ width: visualWidth,
1316
+ height: visualHeight,
1317
+ radius: visualRadius,
1191
1318
  x: cx,
1192
1319
  y: cy,
1193
1320
  holes: absoluteHoles,
1194
- pathData: this.pathData
1321
+ pathData: this.pathData,
1322
+ canvasWidth: canvasW,
1323
+ canvasHeight: canvasH
1195
1324
  });
1325
+ const clonedLayer = await userLayer.clone();
1196
1326
  const clipPath = new Path(pathData, {
1197
- left: 0,
1198
- top: 0,
1199
1327
  originX: "left",
1200
1328
  originY: "top",
1201
- absolutePositioned: true
1202
- });
1203
- const layer = this.getLayer();
1204
- const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
1205
- if (layer) layer.visible = false;
1206
- const holeMarkers = canvas.getObjects().filter((o) => {
1207
- var _a2;
1208
- return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
1209
- });
1210
- holeMarkers.forEach((o) => o.visible = false);
1211
- const rulerLayer = canvas.getObjects().find((obj) => {
1212
- var _a2;
1213
- return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
1214
- });
1215
- const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
1216
- if (rulerLayer) rulerLayer.visible = false;
1217
- const originalClip = canvas.clipPath;
1218
- canvas.clipPath = clipPath;
1219
- const bbox = clipPath.getBoundingRect();
1220
- const clipPathCorrected = new Path(pathData, {
1221
- absolutePositioned: true,
1222
1329
  left: 0,
1223
- top: 0
1224
- });
1225
- const tempPath = new Path(pathData);
1226
- const tempBounds = tempPath.getBoundingRect();
1227
- clipPathCorrected.set({
1228
- left: tempBounds.left,
1229
- top: tempBounds.top,
1230
- originX: "left",
1231
- originY: "top"
1330
+ top: 0,
1331
+ absolutePositioned: true
1332
+ // Important for groups
1232
1333
  });
1233
- canvas.clipPath = clipPathCorrected;
1234
- const exportBbox = clipPathCorrected.getBoundingRect();
1235
- const dataURL = canvas.toDataURL({
1334
+ clonedLayer.clipPath = clipPath;
1335
+ const bounds = clipPath.getBoundingRect();
1336
+ const dataUrl = clonedLayer.toDataURL({
1236
1337
  format: "png",
1237
1338
  multiplier: 2,
1238
- left: exportBbox.left,
1239
- top: exportBbox.top,
1240
- width: exportBbox.width,
1241
- height: exportBbox.height
1339
+ // Better quality
1340
+ left: bounds.left,
1341
+ top: bounds.top,
1342
+ width: bounds.width,
1343
+ height: bounds.height
1242
1344
  });
1243
- canvas.clipPath = originalClip;
1244
- if (layer) layer.visible = wasVisible;
1245
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
1246
- holeMarkers.forEach((o) => o.visible = true);
1247
- canvas.requestRenderAll();
1248
- return dataURL;
1345
+ return dataUrl;
1249
1346
  }
1250
1347
  };
1251
1348
 
@@ -1414,7 +1511,7 @@ var FilmTool = class {
1414
1511
  import {
1415
1512
  ContributionPointIds as ContributionPointIds4
1416
1513
  } from "@pooder/core";
1417
- import { Circle, Group, Point } from "fabric";
1514
+ import { Circle, Group, Point, Rect as Rect2 } from "fabric";
1418
1515
  var HoleTool = class {
1419
1516
  constructor(options) {
1420
1517
  this.id = "pooder.kit.hole";
@@ -1520,13 +1617,21 @@ var HoleTool = class {
1520
1617
  command: "addHole",
1521
1618
  title: "Add Hole",
1522
1619
  handler: (x, y) => {
1523
- var _a, _b, _c;
1620
+ var _a, _b, _c, _d;
1524
1621
  if (!this.canvasService) return false;
1525
- const { width, height } = this.canvasService.canvas;
1526
- const normalizedHole = Coordinate.normalizePoint(
1527
- { x, y },
1528
- { width: width || 800, height: height || 600 }
1529
- );
1622
+ let normalizedX = 0.5;
1623
+ let normalizedY = 0.5;
1624
+ if (this.currentGeometry) {
1625
+ const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
1626
+ const left = gx - gw / 2;
1627
+ const top = gy - gh / 2;
1628
+ normalizedX = gw > 0 ? (x - left) / gw : 0.5;
1629
+ normalizedY = gh > 0 ? (y - top) / gh : 0.5;
1630
+ } else {
1631
+ const { width, height } = this.canvasService.canvas;
1632
+ normalizedX = Coordinate.toNormalized(x, width || 800);
1633
+ normalizedY = Coordinate.toNormalized(y, height || 600);
1634
+ }
1530
1635
  const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1531
1636
  "ConfigurationService"
1532
1637
  );
@@ -1535,9 +1640,11 @@ var HoleTool = class {
1535
1640
  const lastHole = currentHoles[currentHoles.length - 1];
1536
1641
  const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
1537
1642
  const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
1643
+ const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
1538
1644
  const newHole = {
1539
- x: normalizedHole.x,
1540
- y: normalizedHole.y,
1645
+ x: normalizedX,
1646
+ y: normalizedY,
1647
+ shape,
1541
1648
  innerRadius,
1542
1649
  outerRadius
1543
1650
  };
@@ -1569,6 +1676,7 @@ var HoleTool = class {
1569
1676
  if (!this.handleDielineChange) {
1570
1677
  this.handleDielineChange = (geometry) => {
1571
1678
  this.currentGeometry = geometry;
1679
+ this.redraw();
1572
1680
  const changed = this.enforceConstraints();
1573
1681
  if (changed) {
1574
1682
  this.syncHolesToDieline();
@@ -1632,7 +1740,10 @@ var HoleTool = class {
1632
1740
  var _a;
1633
1741
  const target = e.target;
1634
1742
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1635
- this.syncHolesFromCanvas();
1743
+ const changed = this.enforceConstraints();
1744
+ if (!changed) {
1745
+ this.syncHolesFromCanvas();
1746
+ }
1636
1747
  };
1637
1748
  canvas.on("object:modified", this.handleModified);
1638
1749
  }
@@ -1679,10 +1790,16 @@ var HoleTool = class {
1679
1790
  }
1680
1791
  syncHolesFromCanvas() {
1681
1792
  if (!this.canvasService) return;
1682
- const objects = this.canvasService.canvas.getObjects().filter((obj) => {
1683
- var _a;
1684
- return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1685
- });
1793
+ const objects = this.canvasService.canvas.getObjects().filter(
1794
+ (obj) => {
1795
+ var _a;
1796
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
1797
+ }
1798
+ );
1799
+ if (objects.length === 0 && this.holes.length > 0) {
1800
+ console.warn("HoleTool: No markers found on canvas to sync from");
1801
+ return;
1802
+ }
1686
1803
  objects.sort(
1687
1804
  (a, b) => {
1688
1805
  var _a, _b, _c, _d;
@@ -1690,18 +1807,28 @@ var HoleTool = class {
1690
1807
  }
1691
1808
  );
1692
1809
  const newHoles = objects.map((obj, i) => {
1693
- var _a, _b;
1810
+ var _a, _b, _c, _d;
1694
1811
  const original = this.holes[i];
1695
1812
  const newAbsX = obj.left;
1696
1813
  const newAbsY = obj.top;
1814
+ if (isNaN(newAbsX) || isNaN(newAbsY)) {
1815
+ console.error("HoleTool: Invalid marker coordinates", {
1816
+ newAbsX,
1817
+ newAbsY
1818
+ });
1819
+ return original;
1820
+ }
1821
+ const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
1822
+ const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
1823
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1697
1824
  if (original && original.anchor && this.currentGeometry) {
1698
- const { x, y, width: width2, height: height2 } = this.currentGeometry;
1825
+ const { x, y, width, height } = this.currentGeometry;
1699
1826
  let bx = x;
1700
1827
  let by = y;
1701
- const left = x - width2 / 2;
1702
- const right = x + width2 / 2;
1703
- const top = y - height2 / 2;
1704
- const bottom = y + height2 / 2;
1828
+ const left = x - width / 2;
1829
+ const right = x + width / 2;
1830
+ const top = y - height / 2;
1831
+ const bottom = y + height / 2;
1705
1832
  switch (original.anchor) {
1706
1833
  case "top-left":
1707
1834
  bx = left;
@@ -1742,25 +1869,42 @@ var HoleTool = class {
1742
1869
  }
1743
1870
  return {
1744
1871
  ...original,
1745
- offsetX: newAbsX - bx,
1746
- offsetY: newAbsY - by,
1872
+ // Denormalize offset back to physical units (mm)
1873
+ offsetX: (newAbsX - bx) / scale / unitScale,
1874
+ offsetY: (newAbsY - by) / scale / unitScale,
1747
1875
  // Clear direct coordinates if we use anchor
1748
1876
  x: void 0,
1749
- y: void 0
1877
+ y: void 0,
1878
+ // Ensure other properties are preserved
1879
+ innerRadius: original.innerRadius,
1880
+ outerRadius: original.outerRadius,
1881
+ shape: original.shape || "circle"
1750
1882
  };
1751
1883
  }
1752
- const { width, height } = this.canvasService.canvas;
1753
- const p = Coordinate.normalizePoint(
1754
- { x: newAbsX, y: newAbsY },
1755
- { width: width || 800, height: height || 600 }
1756
- );
1884
+ let normalizedX = 0.5;
1885
+ let normalizedY = 0.5;
1886
+ if (this.currentGeometry) {
1887
+ const { x, y, width, height } = this.currentGeometry;
1888
+ const left = x - width / 2;
1889
+ const top = y - height / 2;
1890
+ normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
1891
+ normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
1892
+ } else {
1893
+ const { width, height } = this.canvasService.canvas;
1894
+ normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
1895
+ normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
1896
+ }
1757
1897
  return {
1758
1898
  ...original,
1759
- x: p.x,
1760
- y: p.y,
1761
- // Ensure radii are preserved
1762
- innerRadius: (_a = original == null ? void 0 : original.innerRadius) != null ? _a : 15,
1763
- outerRadius: (_b = original == null ? void 0 : original.outerRadius) != null ? _b : 25
1899
+ x: normalizedX,
1900
+ y: normalizedY,
1901
+ // Clear offsets if we are using direct normalized coordinates
1902
+ offsetX: void 0,
1903
+ offsetY: void 0,
1904
+ // Ensure other properties are preserved
1905
+ innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
1906
+ outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
1907
+ shape: (original == null ? void 0 : original.shape) || "circle"
1764
1908
  };
1765
1909
  });
1766
1910
  this.holes = newHoles;
@@ -1798,24 +1942,54 @@ var HoleTool = class {
1798
1942
  x: (width || 800) / 2,
1799
1943
  y: (height || 600) / 2,
1800
1944
  width: width || 800,
1801
- height: height || 600
1945
+ height: height || 600,
1946
+ scale: 1
1947
+ // Default scale if no geometry loaded
1802
1948
  };
1803
1949
  holes.forEach((hole, index) => {
1950
+ const scale = geometry.scale || 1;
1951
+ const unit = geometry.unit || "mm";
1952
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1953
+ const visualInnerRadius = hole.innerRadius * unitScale * scale;
1954
+ const visualOuterRadius = hole.outerRadius * unitScale * scale;
1804
1955
  const pos = resolveHolePosition(
1805
- hole,
1956
+ {
1957
+ ...hole,
1958
+ offsetX: (hole.offsetX || 0) * unitScale * scale,
1959
+ offsetY: (hole.offsetY || 0) * unitScale * scale
1960
+ },
1806
1961
  geometry,
1807
- { width: width || 800, height: height || 600 }
1962
+ { width: geometry.width, height: geometry.height }
1963
+ // Use geometry dims instead of canvas
1808
1964
  );
1809
- const innerCircle = new Circle({
1810
- radius: hole.innerRadius,
1965
+ const isSquare = hole.shape === "square";
1966
+ const innerMarker = isSquare ? new Rect2({
1967
+ width: visualInnerRadius * 2,
1968
+ height: visualInnerRadius * 2,
1969
+ fill: "transparent",
1970
+ stroke: "red",
1971
+ strokeWidth: 2,
1972
+ originX: "center",
1973
+ originY: "center"
1974
+ }) : new Circle({
1975
+ radius: visualInnerRadius,
1811
1976
  fill: "transparent",
1812
1977
  stroke: "red",
1813
1978
  strokeWidth: 2,
1814
1979
  originX: "center",
1815
1980
  originY: "center"
1816
1981
  });
1817
- const outerCircle = new Circle({
1818
- radius: hole.outerRadius,
1982
+ const outerMarker = isSquare ? new Rect2({
1983
+ width: visualOuterRadius * 2,
1984
+ height: visualOuterRadius * 2,
1985
+ fill: "transparent",
1986
+ stroke: "#666",
1987
+ strokeWidth: 1,
1988
+ strokeDashArray: [5, 5],
1989
+ originX: "center",
1990
+ originY: "center"
1991
+ }) : new Circle({
1992
+ radius: visualOuterRadius,
1819
1993
  fill: "transparent",
1820
1994
  stroke: "#666",
1821
1995
  strokeWidth: 1,
@@ -1823,7 +1997,7 @@ var HoleTool = class {
1823
1997
  originX: "center",
1824
1998
  originY: "center"
1825
1999
  });
1826
- const holeGroup = new Group([outerCircle, innerCircle], {
2000
+ const holeGroup = new Group([outerMarker, innerMarker], {
1827
2001
  left: pos.x,
1828
2002
  top: pos.y,
1829
2003
  originX: "center",
@@ -1895,11 +2069,16 @@ var HoleTool = class {
1895
2069
  var _a, _b;
1896
2070
  const currentPos = new Point(obj.left, obj.top);
1897
2071
  const holeData = this.holes[i];
2072
+ const scale = geometry.scale || 1;
2073
+ const unit = geometry.unit || "mm";
2074
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
2075
+ const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
2076
+ const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
1898
2077
  const newPos = this.calculateConstrainedPosition(
1899
2078
  currentPos,
1900
2079
  constraintGeometry,
1901
- (_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15,
1902
- (_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25
2080
+ innerR,
2081
+ outerR
1903
2082
  );
1904
2083
  if (currentPos.distanceFrom(newPos) > 0.1) {
1905
2084
  obj.set({
@@ -1953,19 +2132,16 @@ var HoleTool = class {
1953
2132
  import {
1954
2133
  ContributionPointIds as ContributionPointIds5
1955
2134
  } from "@pooder/core";
1956
- import { FabricImage as Image4, Point as Point2, util } from "fabric";
2135
+ import { Image as Image4, Point as Point2, util } from "fabric";
1957
2136
  var ImageTool = class {
1958
- constructor(options) {
2137
+ constructor() {
1959
2138
  this.id = "pooder.kit.image";
1960
2139
  this.metadata = {
1961
2140
  name: "ImageTool"
1962
2141
  };
1963
- this._loadingUrl = null;
1964
- this.url = "";
1965
- this.opacity = 1;
1966
- if (options) {
1967
- Object.assign(this, options);
1968
- }
2142
+ this.items = [];
2143
+ this.objectMap = /* @__PURE__ */ new Map();
2144
+ this.isUpdatingConfig = false;
1969
2145
  }
1970
2146
  activate(context) {
1971
2147
  this.context = context;
@@ -1976,38 +2152,33 @@ var ImageTool = class {
1976
2152
  }
1977
2153
  const configService = context.services.get("ConfigurationService");
1978
2154
  if (configService) {
1979
- this.url = configService.get("image.url", this.url);
1980
- this.opacity = configService.get("image.opacity", this.opacity);
1981
- this.width = configService.get("image.width", this.width);
1982
- this.height = configService.get("image.height", this.height);
1983
- this.angle = configService.get("image.angle", this.angle);
1984
- this.left = configService.get("image.left", this.left);
1985
- this.top = configService.get("image.top", this.top);
2155
+ this.items = configService.get("image.items", []) || [];
1986
2156
  configService.onAnyChange((e) => {
1987
- if (e.key.startsWith("image.")) {
1988
- const prop = e.key.split(".")[1];
1989
- console.log(
1990
- `[ImageTool] Config change detected: ${e.key} -> ${e.value}`
1991
- );
1992
- if (prop && prop in this) {
1993
- this[prop] = e.value;
1994
- this.updateImage();
1995
- }
2157
+ if (this.isUpdatingConfig) return;
2158
+ let shouldUpdate = false;
2159
+ if (e.key === "image.items") {
2160
+ this.items = e.value || [];
2161
+ shouldUpdate = true;
2162
+ } else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
2163
+ shouldUpdate = true;
2164
+ }
2165
+ if (shouldUpdate) {
2166
+ this.updateImages();
1996
2167
  }
1997
2168
  });
1998
2169
  }
1999
2170
  this.ensureLayer();
2000
- this.updateImage();
2171
+ this.updateImages();
2001
2172
  }
2002
2173
  deactivate(context) {
2003
2174
  if (this.canvasService) {
2004
2175
  const layer = this.canvasService.getLayer("user");
2005
2176
  if (layer) {
2006
- const userImage = this.canvasService.getObject("user-image", "user");
2007
- if (userImage) {
2008
- layer.remove(userImage);
2009
- this.canvasService.requestRenderAll();
2010
- }
2177
+ this.objectMap.forEach((obj) => {
2178
+ layer.remove(obj);
2179
+ });
2180
+ this.objectMap.clear();
2181
+ this.canvasService.requestRenderAll();
2011
2182
  }
2012
2183
  this.canvasService = void 0;
2013
2184
  this.context = void 0;
@@ -2017,82 +2188,103 @@ var ImageTool = class {
2017
2188
  return {
2018
2189
  [ContributionPointIds5.CONFIGURATIONS]: [
2019
2190
  {
2020
- id: "image.url",
2021
- type: "string",
2022
- label: "Image URL",
2023
- default: this.url
2024
- },
2191
+ id: "image.items",
2192
+ type: "array",
2193
+ label: "Images",
2194
+ default: []
2195
+ }
2196
+ ],
2197
+ [ContributionPointIds5.COMMANDS]: [
2025
2198
  {
2026
- id: "image.opacity",
2027
- type: "number",
2028
- label: "Opacity",
2029
- min: 0,
2030
- max: 1,
2031
- step: 0.1,
2032
- default: this.opacity
2199
+ command: "addImage",
2200
+ title: "Add Image",
2201
+ handler: (url, options) => {
2202
+ const newItem = {
2203
+ id: this.generateId(),
2204
+ url,
2205
+ opacity: 1,
2206
+ ...options
2207
+ };
2208
+ this.updateConfig([...this.items, newItem]);
2209
+ return newItem.id;
2210
+ }
2033
2211
  },
2034
2212
  {
2035
- id: "image.width",
2036
- type: "number",
2037
- label: "Width",
2038
- min: 0,
2039
- max: 5e3,
2040
- default: this.width
2213
+ command: "removeImage",
2214
+ title: "Remove Image",
2215
+ handler: (id) => {
2216
+ const newItems = this.items.filter((item) => item.id !== id);
2217
+ if (newItems.length !== this.items.length) {
2218
+ this.updateConfig(newItems);
2219
+ }
2220
+ }
2041
2221
  },
2042
2222
  {
2043
- id: "image.height",
2044
- type: "number",
2045
- label: "Height",
2046
- min: 0,
2047
- max: 5e3,
2048
- default: this.height
2223
+ command: "updateImage",
2224
+ title: "Update Image",
2225
+ handler: (id, updates) => {
2226
+ const index = this.items.findIndex((item) => item.id === id);
2227
+ if (index !== -1) {
2228
+ const newItems = [...this.items];
2229
+ newItems[index] = { ...newItems[index], ...updates };
2230
+ this.updateConfig(newItems);
2231
+ }
2232
+ }
2049
2233
  },
2050
2234
  {
2051
- id: "image.angle",
2052
- type: "number",
2053
- label: "Rotation",
2054
- min: 0,
2055
- max: 360,
2056
- default: this.angle
2235
+ command: "clearImages",
2236
+ title: "Clear Images",
2237
+ handler: () => {
2238
+ this.updateConfig([]);
2239
+ }
2057
2240
  },
2058
2241
  {
2059
- id: "image.left",
2060
- type: "number",
2061
- label: "Left (Normalized)",
2062
- min: 0,
2063
- max: 1,
2064
- default: this.left
2242
+ command: "bringToFront",
2243
+ title: "Bring Image to Front",
2244
+ handler: (id) => {
2245
+ const index = this.items.findIndex((item) => item.id === id);
2246
+ if (index !== -1 && index < this.items.length - 1) {
2247
+ const newItems = [...this.items];
2248
+ const [item] = newItems.splice(index, 1);
2249
+ newItems.push(item);
2250
+ this.updateConfig(newItems);
2251
+ }
2252
+ }
2065
2253
  },
2066
2254
  {
2067
- id: "image.top",
2068
- type: "number",
2069
- label: "Top (Normalized)",
2070
- min: 0,
2071
- max: 1,
2072
- default: this.top
2073
- }
2074
- ],
2075
- [ContributionPointIds5.COMMANDS]: [
2076
- {
2077
- command: "setUserImage",
2078
- title: "Set User Image",
2079
- handler: (url, opacity, width, height, angle, left, top) => {
2080
- if (this.url === url && this.opacity === opacity && this.width === width && this.height === height && this.angle === angle && this.left === left && this.top === top)
2081
- return true;
2082
- this.url = url;
2083
- this.opacity = opacity;
2084
- this.width = width;
2085
- this.height = height;
2086
- this.angle = angle;
2087
- this.left = left;
2088
- this.top = top;
2089
- this.updateImage();
2090
- return true;
2255
+ command: "sendToBack",
2256
+ title: "Send Image to Back",
2257
+ handler: (id) => {
2258
+ const index = this.items.findIndex((item) => item.id === id);
2259
+ if (index > 0) {
2260
+ const newItems = [...this.items];
2261
+ const [item] = newItems.splice(index, 1);
2262
+ newItems.unshift(item);
2263
+ this.updateConfig(newItems);
2264
+ }
2091
2265
  }
2092
2266
  }
2093
2267
  ]
2094
2268
  };
2095
2269
  }
2270
+ generateId() {
2271
+ return Math.random().toString(36).substring(2, 9);
2272
+ }
2273
+ updateConfig(newItems, skipCanvasUpdate = false) {
2274
+ if (!this.context) return;
2275
+ this.isUpdatingConfig = true;
2276
+ this.items = newItems;
2277
+ const configService = this.context.services.get("ConfigurationService");
2278
+ if (configService) {
2279
+ configService.update("image.items", newItems);
2280
+ }
2281
+ if (!skipCanvasUpdate) {
2282
+ this.updateImages();
2283
+ }
2284
+ setTimeout(() => {
2285
+ this.isUpdatingConfig = false;
2286
+ }, 50);
2287
+ }
2096
2288
  ensureLayer() {
2097
2289
  if (!this.canvasService) return;
2098
2290
  let userLayer = this.canvasService.getLayer("user");
@@ -2124,224 +2316,194 @@ var ImageTool = class {
2124
2316
  this.canvasService.requestRenderAll();
2125
2317
  }
2126
2318
  }
2127
- updateImage() {
2319
+ getLayoutInfo() {
2128
2320
  var _a, _b;
2321
+ const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
2322
+ const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
2323
+ let layoutScale = 1;
2324
+ let layoutOffsetX = 0;
2325
+ let layoutOffsetY = 0;
2326
+ let visualWidth = canvasW;
2327
+ let visualHeight = canvasH;
2328
+ let dielinePhysicalWidth = 500;
2329
+ let dielinePhysicalHeight = 500;
2330
+ let bleedOffset = 0;
2331
+ if (this.context) {
2332
+ const configService = this.context.services.get("ConfigurationService");
2333
+ if (configService) {
2334
+ dielinePhysicalWidth = configService.get("dieline.width") || 500;
2335
+ dielinePhysicalHeight = configService.get("dieline.height") || 500;
2336
+ bleedOffset = configService.get("dieline.offset") || 0;
2337
+ const paddingValue = configService.get("dieline.padding") || 40;
2338
+ let padding = 0;
2339
+ if (typeof paddingValue === "number") {
2340
+ padding = paddingValue;
2341
+ } else if (typeof paddingValue === "string") {
2342
+ if (paddingValue.endsWith("%")) {
2343
+ const percent = parseFloat(paddingValue) / 100;
2344
+ padding = Math.min(canvasW, canvasH) * percent;
2345
+ } else {
2346
+ padding = parseFloat(paddingValue) || 0;
2347
+ }
2348
+ }
2349
+ const layout = Coordinate.calculateLayout(
2350
+ { width: canvasW, height: canvasH },
2351
+ { width: dielinePhysicalWidth, height: dielinePhysicalHeight },
2352
+ padding
2353
+ );
2354
+ layoutScale = layout.scale;
2355
+ layoutOffsetX = layout.offsetX;
2356
+ layoutOffsetY = layout.offsetY;
2357
+ visualWidth = layout.width;
2358
+ visualHeight = layout.height;
2359
+ }
2360
+ }
2361
+ return {
2362
+ layoutScale,
2363
+ layoutOffsetX,
2364
+ layoutOffsetY,
2365
+ visualWidth,
2366
+ visualHeight,
2367
+ dielinePhysicalWidth,
2368
+ dielinePhysicalHeight,
2369
+ bleedOffset
2370
+ };
2371
+ }
2372
+ updateImages() {
2129
2373
  if (!this.canvasService) return;
2130
- let { url, opacity, width, height, angle, left, top } = this;
2131
2374
  const layer = this.canvasService.getLayer("user");
2132
2375
  if (!layer) {
2133
2376
  console.warn("[ImageTool] User layer not found");
2134
2377
  return;
2135
2378
  }
2136
- const userImage = this.canvasService.getObject("user-image", "user");
2137
- if (this._loadingUrl === url) return;
2138
- if (userImage) {
2139
- const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
2140
- if (currentSrc !== url) {
2141
- this.loadImage(layer);
2142
- } else {
2143
- const updates = {};
2144
- const canvasW = this.canvasService.canvas.width || 800;
2145
- const canvasH = this.canvasService.canvas.height || 600;
2146
- const centerX = canvasW / 2;
2147
- const centerY = canvasH / 2;
2148
- if (userImage.opacity !== opacity) updates.opacity = opacity;
2149
- if (angle !== void 0 && userImage.angle !== angle)
2150
- updates.angle = angle;
2151
- if (userImage.originX !== "center") {
2152
- userImage.set({
2153
- originX: "center",
2154
- originY: "center",
2155
- left: userImage.left + userImage.width * userImage.scaleX / 2,
2156
- top: userImage.top + userImage.height * userImage.scaleY / 2
2157
- });
2158
- }
2159
- if (left !== void 0) {
2160
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2161
- const localLeft = globalLeft - centerX;
2162
- if (Math.abs(userImage.left - localLeft) > 1)
2163
- updates.left = localLeft;
2164
- }
2165
- if (top !== void 0) {
2166
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2167
- const localTop = globalTop - centerY;
2168
- if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
2169
- }
2170
- if (width !== void 0 && userImage.width)
2171
- updates.scaleX = width / userImage.width;
2172
- if (height !== void 0 && userImage.height)
2173
- updates.scaleY = height / userImage.height;
2174
- if (Object.keys(updates).length > 0) {
2175
- userImage.set(updates);
2176
- layer.dirty = true;
2177
- this.canvasService.requestRenderAll();
2178
- }
2379
+ const currentIds = new Set(this.items.map((i) => i.id));
2380
+ for (const [id, obj] of this.objectMap) {
2381
+ if (!currentIds.has(id)) {
2382
+ layer.remove(obj);
2383
+ this.objectMap.delete(id);
2179
2384
  }
2180
- } else {
2181
- this.loadImage(layer);
2182
2385
  }
2183
- }
2184
- loadImage(layer) {
2185
- if (!this.canvasService) return;
2186
- const { url } = this;
2187
- if (!url) return;
2188
- this._loadingUrl = url;
2189
- Image4.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2190
- var _a, _b, _c, _d, _e, _f;
2191
- if (this._loadingUrl !== url) return;
2192
- this._loadingUrl = null;
2193
- let { opacity, width, height, angle, left, top } = this;
2194
- if (this.context) {
2195
- const configService = this.context.services.get(
2196
- "ConfigurationService"
2197
- );
2198
- const dielineWidth = configService.get("dieline.width");
2199
- const dielineHeight = configService.get("dieline.height");
2200
- console.log(
2201
- "[ImageTool] Dieline config debug:",
2202
- {
2203
- widthVal: dielineWidth,
2204
- heightVal: dielineHeight,
2205
- // Debug: dump all keys to see what is available
2206
- allKeys: Array.from(
2207
- ((_a = configService.configValues) == null ? void 0 : _a.keys()) || []
2208
- )
2209
- },
2210
- configService
2211
- );
2212
- if (width === void 0 && height === void 0) {
2213
- const scale = Math.min(
2214
- dielineWidth / (image.width || 1),
2215
- dielineHeight / (image.height || 1)
2216
- );
2217
- width = (image.width || 1) * scale;
2218
- height = (image.height || 1) * scale;
2219
- this.width = width;
2220
- this.height = height;
2221
- }
2222
- if (left === void 0 && top === void 0) {
2223
- const dielinePos = configService == null ? void 0 : configService.get("dieline.position");
2224
- if (dielinePos) {
2225
- this.left = dielinePos.x;
2226
- this.top = dielinePos.y;
2227
- } else {
2228
- this.left = 0.5;
2229
- this.top = 0.5;
2230
- }
2231
- left = this.left;
2232
- top = this.top;
2233
- }
2234
- }
2235
- const existingImage = this.canvasService.getObject(
2236
- "user-image",
2237
- "user"
2238
- );
2239
- if (existingImage) {
2240
- const defaultLeft = existingImage.left;
2241
- const defaultTop = existingImage.top;
2242
- const defaultAngle = existingImage.angle;
2243
- const defaultScaleX = existingImage.scaleX;
2244
- const defaultScaleY = existingImage.scaleY;
2245
- const canvasW = ((_b = this.canvasService) == null ? void 0 : _b.canvas.width) || 800;
2246
- const canvasH = ((_c = this.canvasService) == null ? void 0 : _c.canvas.height) || 600;
2247
- const centerX = canvasW / 2;
2248
- const centerY = canvasH / 2;
2249
- let targetLeft = left !== void 0 ? left : defaultLeft;
2250
- let targetTop = top !== void 0 ? top : defaultTop;
2251
- const configService = (_d = this.context) == null ? void 0 : _d.services.get(
2252
- "ConfigurationService"
2253
- );
2254
- console.log("[ImageTool] Loading EXISTING image...", {
2255
- canvasW,
2256
- canvasH,
2257
- centerX,
2258
- centerY,
2259
- incomingLeft: left,
2260
- incomingTop: top,
2261
- dielinePos: configService == null ? void 0 : configService.get("dieline.position"),
2262
- existingImage: !!existingImage
2263
- });
2264
- if (left !== void 0) {
2265
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2266
- targetLeft = globalLeft;
2267
- console.log("[ImageTool] Calculated targetLeft", {
2268
- globalLeft,
2269
- targetLeft
2270
- });
2271
- }
2272
- if (top !== void 0) {
2273
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2274
- targetTop = globalTop;
2275
- console.log("[ImageTool] Calculated targetTop", {
2276
- globalTop,
2277
- targetTop
2278
- });
2279
- }
2280
- image.set({
2281
- originX: "center",
2282
- // Use center origin for easier positioning
2283
- originY: "center",
2284
- left: targetLeft,
2285
- top: targetTop,
2286
- angle: angle !== void 0 ? angle : defaultAngle,
2287
- scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
2288
- scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
2289
- });
2290
- layer.remove(existingImage);
2386
+ const layout = this.getLayoutInfo();
2387
+ this.items.forEach((item, index) => {
2388
+ let obj = this.objectMap.get(item.id);
2389
+ if (!obj) {
2390
+ this.loadImage(item, layer, layout);
2291
2391
  } else {
2292
- image.set({
2293
- originX: "center",
2294
- originY: "center"
2295
- });
2296
- if (width !== void 0 && image.width)
2297
- image.scaleX = width / image.width;
2298
- if (height !== void 0 && image.height)
2299
- image.scaleY = height / image.height;
2300
- if (angle !== void 0) image.angle = angle;
2301
- const canvasW = ((_e = this.canvasService) == null ? void 0 : _e.canvas.width) || 800;
2302
- const canvasH = ((_f = this.canvasService) == null ? void 0 : _f.canvas.height) || 600;
2303
- const centerX = canvasW / 2;
2304
- const centerY = canvasH / 2;
2305
- if (left !== void 0) {
2306
- image.left = Coordinate.toAbsolute(left, canvasW);
2307
- } else {
2308
- image.left = centerX;
2309
- }
2310
- if (top !== void 0) {
2311
- image.top = Coordinate.toAbsolute(top, canvasH);
2312
- } else {
2313
- image.top = centerY;
2314
- }
2392
+ this.updateObjectProperties(obj, item, layout);
2393
+ layer.remove(obj);
2394
+ layer.add(obj);
2315
2395
  }
2396
+ });
2397
+ layer.dirty = true;
2398
+ this.canvasService.requestRenderAll();
2399
+ }
2400
+ updateObjectProperties(obj, item, layout) {
2401
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2402
+ const updates = {};
2403
+ if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
2404
+ if (item.angle !== void 0 && obj.angle !== item.angle) updates.angle = item.angle;
2405
+ if (item.left !== void 0) {
2406
+ const globalLeft = layoutOffsetX + item.left * visualWidth;
2407
+ if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
2408
+ }
2409
+ if (item.top !== void 0) {
2410
+ const globalTop = layoutOffsetY + item.top * visualHeight;
2411
+ if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
2412
+ }
2413
+ if (item.width !== void 0 && obj.width) {
2414
+ const targetScaleX = item.width * layoutScale / obj.width;
2415
+ if (Math.abs(obj.scaleX - targetScaleX) > 1e-3) updates.scaleX = targetScaleX;
2416
+ }
2417
+ if (item.height !== void 0 && obj.height) {
2418
+ const targetScaleY = item.height * layoutScale / obj.height;
2419
+ if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
2420
+ }
2421
+ if (obj.originX !== "center") {
2422
+ updates.originX = "center";
2423
+ updates.originY = "center";
2424
+ }
2425
+ if (Object.keys(updates).length > 0) {
2426
+ obj.set(updates);
2427
+ }
2428
+ }
2429
+ loadImage(item, layer, layout) {
2430
+ Image4.fromURL(item.url, { crossOrigin: "anonymous" }).then((image) => {
2431
+ var _a;
2432
+ if (!this.items.find((i) => i.id === item.id)) return;
2316
2433
  image.set({
2317
- opacity: opacity !== void 0 ? opacity : 1,
2318
- data: {
2319
- id: "user-image"
2320
- }
2434
+ originX: "center",
2435
+ originY: "center",
2436
+ data: { id: item.id },
2437
+ uniformScaling: true,
2438
+ lockScalingFlip: true
2439
+ });
2440
+ image.setControlsVisibility({
2441
+ mt: false,
2442
+ mb: false,
2443
+ ml: false,
2444
+ mr: false
2321
2445
  });
2446
+ let { width, height, left, top } = item;
2447
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, dielinePhysicalWidth, dielinePhysicalHeight, bleedOffset } = layout;
2448
+ if (width === void 0 && height === void 0) {
2449
+ const targetWidth = dielinePhysicalWidth + 2 * bleedOffset;
2450
+ const targetHeight = dielinePhysicalHeight + 2 * bleedOffset;
2451
+ const targetMax = Math.max(targetWidth, targetHeight);
2452
+ const imageMax = Math.max(image.width || 1, image.height || 1);
2453
+ const scale = targetMax / imageMax;
2454
+ width = (image.width || 1) * scale;
2455
+ height = (image.height || 1) * scale;
2456
+ item.width = width;
2457
+ item.height = height;
2458
+ }
2459
+ if (left === void 0 && top === void 0) {
2460
+ left = 0.5;
2461
+ top = 0.5;
2462
+ item.left = left;
2463
+ item.top = top;
2464
+ }
2465
+ this.updateObjectProperties(image, item, layout);
2322
2466
  layer.add(image);
2467
+ this.objectMap.set(item.id, image);
2323
2468
  image.on("modified", (e) => {
2324
- var _a2, _b2;
2325
- const matrix = image.calcTransformMatrix();
2326
- const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
2327
- const canvasW = ((_a2 = this.canvasService) == null ? void 0 : _a2.canvas.width) || 800;
2328
- const canvasH = ((_b2 = this.canvasService) == null ? void 0 : _b2.canvas.height) || 600;
2329
- this.left = Coordinate.toNormalized(globalPoint.x, canvasW);
2330
- this.top = Coordinate.toNormalized(globalPoint.y, canvasH);
2331
- this.angle = e.target.angle;
2332
- if (image.width) this.width = e.target.width * e.target.scaleX;
2333
- if (image.height) this.height = e.target.height * e.target.scaleY;
2334
- if (this.context) {
2335
- this.context.eventBus.emit("update");
2336
- }
2469
+ this.handleObjectModified(item.id, image);
2337
2470
  });
2338
2471
  layer.dirty = true;
2339
- this.canvasService.requestRenderAll();
2472
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2473
+ if (item.width !== width || item.height !== height || item.left !== left || item.top !== top) {
2474
+ this.updateImageInConfig(item.id, { width, height, left, top });
2475
+ }
2340
2476
  }).catch((err) => {
2341
- if (this._loadingUrl === url) this._loadingUrl = null;
2342
- console.error("Failed to load image", url, err);
2477
+ console.error("Failed to load image", item.url, err);
2343
2478
  });
2344
2479
  }
2480
+ handleObjectModified(id, image) {
2481
+ const layout = this.getLayoutInfo();
2482
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2483
+ const matrix = image.calcTransformMatrix();
2484
+ const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
2485
+ const updates = {};
2486
+ updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
2487
+ updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
2488
+ updates.angle = image.angle;
2489
+ if (image.width) {
2490
+ const pixelWidth = image.width * image.scaleX;
2491
+ updates.width = pixelWidth / layoutScale;
2492
+ }
2493
+ if (image.height) {
2494
+ const pixelHeight = image.height * image.scaleY;
2495
+ updates.height = pixelHeight / layoutScale;
2496
+ }
2497
+ this.updateImageInConfig(id, updates);
2498
+ }
2499
+ updateImageInConfig(id, updates) {
2500
+ const index = this.items.findIndex((i) => i.id === id);
2501
+ if (index !== -1) {
2502
+ const newItems = [...this.items];
2503
+ newItems[index] = { ...newItems[index], ...updates };
2504
+ this.updateConfig(newItems, true);
2505
+ }
2506
+ }
2345
2507
  };
2346
2508
 
2347
2509
  // src/white-ink.ts
@@ -2641,19 +2803,25 @@ var WhiteInkTool = class {
2641
2803
  import {
2642
2804
  ContributionPointIds as ContributionPointIds7
2643
2805
  } from "@pooder/core";
2644
- import { Rect as Rect2, Line, Text } from "fabric";
2806
+ import { Line, Text, Group as Group2, Polygon } from "fabric";
2645
2807
  var RulerTool = class {
2646
2808
  constructor(options) {
2647
2809
  this.id = "pooder.kit.ruler";
2648
2810
  this.metadata = {
2649
2811
  name: "RulerTool"
2650
2812
  };
2651
- this.unit = "px";
2652
2813
  this.thickness = 20;
2814
+ this.gap = 15;
2653
2815
  this.backgroundColor = "#f0f0f0";
2654
2816
  this.textColor = "#333333";
2655
2817
  this.lineColor = "#999999";
2656
2818
  this.fontSize = 10;
2819
+ // Dieline context for sync
2820
+ this.dielineWidth = 500;
2821
+ this.dielineHeight = 500;
2822
+ this.dielineUnit = "mm";
2823
+ this.dielinePadding = 40;
2824
+ this.dielineOffset = 0;
2657
2825
  if (options) {
2658
2826
  Object.assign(this, options);
2659
2827
  }
@@ -2666,8 +2834,8 @@ var RulerTool = class {
2666
2834
  }
2667
2835
  const configService = context.services.get("ConfigurationService");
2668
2836
  if (configService) {
2669
- this.unit = configService.get("ruler.unit", this.unit);
2670
2837
  this.thickness = configService.get("ruler.thickness", this.thickness);
2838
+ this.gap = configService.get("ruler.gap", this.gap);
2671
2839
  this.backgroundColor = configService.get(
2672
2840
  "ruler.backgroundColor",
2673
2841
  this.backgroundColor
@@ -2675,13 +2843,38 @@ var RulerTool = class {
2675
2843
  this.textColor = configService.get("ruler.textColor", this.textColor);
2676
2844
  this.lineColor = configService.get("ruler.lineColor", this.lineColor);
2677
2845
  this.fontSize = configService.get("ruler.fontSize", this.fontSize);
2846
+ this.dielineUnit = configService.get("dieline.unit", this.dielineUnit);
2847
+ this.dielineWidth = configService.get("dieline.width", this.dielineWidth);
2848
+ this.dielineHeight = configService.get(
2849
+ "dieline.height",
2850
+ this.dielineHeight
2851
+ );
2852
+ this.dielinePadding = configService.get(
2853
+ "dieline.padding",
2854
+ this.dielinePadding
2855
+ );
2856
+ this.dielineOffset = configService.get(
2857
+ "dieline.offset",
2858
+ this.dielineOffset
2859
+ );
2678
2860
  configService.onAnyChange((e) => {
2861
+ let shouldUpdate = false;
2679
2862
  if (e.key.startsWith("ruler.")) {
2680
2863
  const prop = e.key.split(".")[1];
2681
2864
  if (prop && prop in this) {
2682
2865
  this[prop] = e.value;
2683
- this.updateRuler();
2866
+ shouldUpdate = true;
2684
2867
  }
2868
+ } else if (e.key.startsWith("dieline.")) {
2869
+ if (e.key === "dieline.unit") this.dielineUnit = e.value;
2870
+ if (e.key === "dieline.width") this.dielineWidth = e.value;
2871
+ if (e.key === "dieline.height") this.dielineHeight = e.value;
2872
+ if (e.key === "dieline.padding") this.dielinePadding = e.value;
2873
+ if (e.key === "dieline.offset") this.dielineOffset = e.value;
2874
+ shouldUpdate = true;
2875
+ }
2876
+ if (shouldUpdate) {
2877
+ this.updateRuler();
2685
2878
  }
2686
2879
  });
2687
2880
  }
@@ -2695,13 +2888,6 @@ var RulerTool = class {
2695
2888
  contribute() {
2696
2889
  return {
2697
2890
  [ContributionPointIds7.CONFIGURATIONS]: [
2698
- {
2699
- id: "ruler.unit",
2700
- type: "select",
2701
- label: "Unit",
2702
- options: ["px", "mm", "cm", "in"],
2703
- default: "px"
2704
- },
2705
2891
  {
2706
2892
  id: "ruler.thickness",
2707
2893
  type: "number",
@@ -2710,6 +2896,14 @@ var RulerTool = class {
2710
2896
  max: 100,
2711
2897
  default: 20
2712
2898
  },
2899
+ {
2900
+ id: "ruler.gap",
2901
+ type: "number",
2902
+ label: "Gap",
2903
+ min: 0,
2904
+ max: 100,
2905
+ default: 15
2906
+ },
2713
2907
  {
2714
2908
  id: "ruler.backgroundColor",
2715
2909
  type: "color",
@@ -2738,16 +2932,6 @@ var RulerTool = class {
2738
2932
  }
2739
2933
  ],
2740
2934
  [ContributionPointIds7.COMMANDS]: [
2741
- {
2742
- command: "setUnit",
2743
- title: "Set Ruler Unit",
2744
- handler: (unit) => {
2745
- if (this.unit === unit) return true;
2746
- this.unit = unit;
2747
- this.updateRuler();
2748
- return true;
2749
- }
2750
- },
2751
2935
  {
2752
2936
  command: "setTheme",
2753
2937
  title: "Set Ruler Theme",
@@ -2798,6 +2982,68 @@ var RulerTool = class {
2798
2982
  this.canvasService.canvas.remove(layer);
2799
2983
  }
2800
2984
  }
2985
+ createArrowLine(x1, y1, x2, y2, color) {
2986
+ const line = new Line([x1, y1, x2, y2], {
2987
+ stroke: color,
2988
+ strokeWidth: this.thickness / 20,
2989
+ // Scale stroke width relative to thickness (default 1)
2990
+ selectable: false,
2991
+ evented: false
2992
+ });
2993
+ const arrowSize = Math.max(4, this.thickness * 0.3);
2994
+ const angle = Math.atan2(y2 - y1, x2 - x1);
2995
+ const endArrow = new Polygon(
2996
+ [
2997
+ { x: 0, y: 0 },
2998
+ { x: -arrowSize, y: -arrowSize / 2 },
2999
+ { x: -arrowSize, y: arrowSize / 2 }
3000
+ ],
3001
+ {
3002
+ fill: color,
3003
+ left: x2,
3004
+ top: y2,
3005
+ originX: "right",
3006
+ originY: "center",
3007
+ angle: angle * 180 / Math.PI,
3008
+ selectable: false,
3009
+ evented: false
3010
+ }
3011
+ );
3012
+ const startArrow = new Polygon(
3013
+ [
3014
+ { x: 0, y: 0 },
3015
+ { x: arrowSize, y: -arrowSize / 2 },
3016
+ { x: arrowSize, y: arrowSize / 2 }
3017
+ ],
3018
+ {
3019
+ fill: color,
3020
+ left: x1,
3021
+ top: y1,
3022
+ originX: "left",
3023
+ originY: "center",
3024
+ angle: angle * 180 / Math.PI,
3025
+ selectable: false,
3026
+ evented: false
3027
+ }
3028
+ );
3029
+ return new Group2([line, startArrow, endArrow], {
3030
+ selectable: false,
3031
+ evented: false
3032
+ });
3033
+ }
3034
+ resolvePadding(containerWidth, containerHeight) {
3035
+ if (typeof this.dielinePadding === "number") {
3036
+ return this.dielinePadding;
3037
+ }
3038
+ if (typeof this.dielinePadding === "string") {
3039
+ if (this.dielinePadding.endsWith("%")) {
3040
+ const percent = parseFloat(this.dielinePadding) / 100;
3041
+ return Math.min(containerWidth, containerHeight) * percent;
3042
+ }
3043
+ return parseFloat(this.dielinePadding) || 0;
3044
+ }
3045
+ return 0;
3046
+ }
2801
3047
  updateRuler() {
2802
3048
  if (!this.canvasService) return;
2803
3049
  const layer = this.getLayer();
@@ -2806,95 +3052,141 @@ var RulerTool = class {
2806
3052
  const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
2807
3053
  const width = this.canvasService.canvas.width || 800;
2808
3054
  const height = this.canvasService.canvas.height || 600;
2809
- const topBg = new Rect2({
2810
- left: 0,
2811
- top: 0,
2812
- width,
2813
- height: thickness,
2814
- fill: backgroundColor,
2815
- selectable: false,
2816
- evented: false
2817
- });
2818
- const leftBg = new Rect2({
2819
- left: 0,
2820
- top: 0,
2821
- width: thickness,
2822
- height,
2823
- fill: backgroundColor,
2824
- selectable: false,
2825
- evented: false
2826
- });
2827
- const cornerBg = new Rect2({
2828
- left: 0,
2829
- top: 0,
2830
- width: thickness,
2831
- height: thickness,
2832
- fill: backgroundColor,
2833
- stroke: lineColor,
2834
- strokeWidth: 1,
3055
+ const paddingPx = this.resolvePadding(width, height);
3056
+ const layout = Coordinate.calculateLayout(
3057
+ { width, height },
3058
+ { width: this.dielineWidth, height: this.dielineHeight },
3059
+ paddingPx
3060
+ );
3061
+ const scale = layout.scale;
3062
+ const offsetX = layout.offsetX;
3063
+ const offsetY = layout.offsetY;
3064
+ const visualWidth = layout.width;
3065
+ const visualHeight = layout.height;
3066
+ const rawOffset = this.dielineOffset || 0;
3067
+ const effectiveOffset = rawOffset > 0 ? rawOffset : 0;
3068
+ const expandPixels = effectiveOffset * scale;
3069
+ const gap = this.gap || 15;
3070
+ const rulerLeft = offsetX - expandPixels;
3071
+ const rulerTop = offsetY - expandPixels;
3072
+ const rulerRight = offsetX + visualWidth + expandPixels;
3073
+ const rulerBottom = offsetY + visualHeight + expandPixels;
3074
+ const displayWidth = this.dielineWidth + effectiveOffset * 2;
3075
+ const displayHeight = this.dielineHeight + effectiveOffset * 2;
3076
+ const topRulerY = rulerTop - gap;
3077
+ const topRulerXStart = rulerLeft;
3078
+ const topRulerXEnd = rulerRight;
3079
+ const leftRulerX = rulerLeft - gap;
3080
+ const leftRulerYStart = rulerTop;
3081
+ const leftRulerYEnd = rulerBottom;
3082
+ const topDimLine = this.createArrowLine(
3083
+ topRulerXStart,
3084
+ topRulerY,
3085
+ topRulerXEnd,
3086
+ topRulerY,
3087
+ lineColor
3088
+ );
3089
+ layer.add(topDimLine);
3090
+ const extLen = 5;
3091
+ layer.add(
3092
+ new Line(
3093
+ [
3094
+ topRulerXStart,
3095
+ topRulerY - extLen,
3096
+ topRulerXStart,
3097
+ topRulerY + extLen
3098
+ ],
3099
+ {
3100
+ stroke: lineColor,
3101
+ strokeWidth: 1,
3102
+ selectable: false,
3103
+ evented: false
3104
+ }
3105
+ )
3106
+ );
3107
+ layer.add(
3108
+ new Line(
3109
+ [topRulerXEnd, topRulerY - extLen, topRulerXEnd, topRulerY + extLen],
3110
+ {
3111
+ stroke: lineColor,
3112
+ strokeWidth: 1,
3113
+ selectable: false,
3114
+ evented: false
3115
+ }
3116
+ )
3117
+ );
3118
+ const widthStr = parseFloat(displayWidth.toFixed(2)).toString();
3119
+ const topTextContent = `${widthStr} ${this.dielineUnit}`;
3120
+ const topText = new Text(topTextContent, {
3121
+ left: topRulerXStart + (rulerRight - rulerLeft) / 2,
3122
+ top: topRulerY,
3123
+ fontSize,
3124
+ fill: textColor,
3125
+ fontFamily: "Arial",
3126
+ originX: "center",
3127
+ originY: "center",
3128
+ backgroundColor,
3129
+ // Background mask for readability
2835
3130
  selectable: false,
2836
3131
  evented: false
2837
3132
  });
2838
- layer.add(topBg);
2839
- layer.add(leftBg);
2840
- layer.add(cornerBg);
2841
- const step = 100;
2842
- const subStep = 10;
2843
- const midStep = 50;
2844
- for (let x = 0; x <= width; x += subStep) {
2845
- if (x < thickness) continue;
2846
- let len = thickness * 0.25;
2847
- if (x % step === 0) len = thickness * 0.8;
2848
- else if (x % midStep === 0) len = thickness * 0.5;
2849
- const line = new Line([x, thickness - len, x, thickness], {
2850
- stroke: lineColor,
2851
- strokeWidth: 1,
2852
- selectable: false,
2853
- evented: false
2854
- });
2855
- layer.add(line);
2856
- if (x % step === 0) {
2857
- const text = new Text(x.toString(), {
2858
- left: x + 2,
2859
- top: 2,
2860
- fontSize,
2861
- fill: textColor,
2862
- fontFamily: "Arial",
3133
+ layer.add(topText);
3134
+ const leftDimLine = this.createArrowLine(
3135
+ leftRulerX,
3136
+ leftRulerYStart,
3137
+ leftRulerX,
3138
+ leftRulerYEnd,
3139
+ lineColor
3140
+ );
3141
+ layer.add(leftDimLine);
3142
+ layer.add(
3143
+ new Line(
3144
+ [
3145
+ leftRulerX - extLen,
3146
+ leftRulerYStart,
3147
+ leftRulerX + extLen,
3148
+ leftRulerYStart
3149
+ ],
3150
+ {
3151
+ stroke: lineColor,
3152
+ strokeWidth: 1,
2863
3153
  selectable: false,
2864
3154
  evented: false
2865
- });
2866
- layer.add(text);
2867
- }
2868
- }
2869
- for (let y = 0; y <= height; y += subStep) {
2870
- if (y < thickness) continue;
2871
- let len = thickness * 0.25;
2872
- if (y % step === 0) len = thickness * 0.8;
2873
- else if (y % midStep === 0) len = thickness * 0.5;
2874
- const line = new Line([thickness - len, y, thickness, y], {
2875
- stroke: lineColor,
2876
- strokeWidth: 1,
2877
- selectable: false,
2878
- evented: false
2879
- });
2880
- layer.add(line);
2881
- if (y % step === 0) {
2882
- const text = new Text(y.toString(), {
2883
- angle: -90,
2884
- left: thickness / 2 - fontSize / 3,
2885
- // approximate centering
2886
- top: y + fontSize,
2887
- fontSize,
2888
- fill: textColor,
2889
- fontFamily: "Arial",
2890
- originX: "center",
2891
- originY: "center",
3155
+ }
3156
+ )
3157
+ );
3158
+ layer.add(
3159
+ new Line(
3160
+ [
3161
+ leftRulerX - extLen,
3162
+ leftRulerYEnd,
3163
+ leftRulerX + extLen,
3164
+ leftRulerYEnd
3165
+ ],
3166
+ {
3167
+ stroke: lineColor,
3168
+ strokeWidth: 1,
2892
3169
  selectable: false,
2893
3170
  evented: false
2894
- });
2895
- layer.add(text);
2896
- }
2897
- }
3171
+ }
3172
+ )
3173
+ );
3174
+ const heightStr = parseFloat(displayHeight.toFixed(2)).toString();
3175
+ const leftTextContent = `${heightStr} ${this.dielineUnit}`;
3176
+ const leftText = new Text(leftTextContent, {
3177
+ left: leftRulerX,
3178
+ top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
3179
+ angle: -90,
3180
+ fontSize,
3181
+ fill: textColor,
3182
+ fontFamily: "Arial",
3183
+ originX: "center",
3184
+ originY: "center",
3185
+ backgroundColor,
3186
+ selectable: false,
3187
+ evented: false
3188
+ });
3189
+ layer.add(leftText);
2898
3190
  this.canvasService.canvas.bringObjectToFront(layer);
2899
3191
  this.canvasService.canvas.requestRenderAll();
2900
3192
  }
@@ -2993,7 +3285,7 @@ var MirrorTool = class {
2993
3285
  };
2994
3286
 
2995
3287
  // src/CanvasService.ts
2996
- import { Canvas, Group as Group2 } from "fabric";
3288
+ import { Canvas, Group as Group3 } from "fabric";
2997
3289
  var CanvasService = class {
2998
3290
  constructor(el, options) {
2999
3291
  if (el instanceof Canvas) {
@@ -3030,7 +3322,7 @@ var CanvasService = class {
3030
3322
  ...options,
3031
3323
  data: { ...options.data, id }
3032
3324
  };
3033
- layer = new Group2([], defaultOptions);
3325
+ layer = new Group3([], defaultOptions);
3034
3326
  this.canvas.add(layer);
3035
3327
  }
3036
3328
  return layer;