@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.js CHANGED
@@ -434,6 +434,24 @@ var ImageTracer = class {
434
434
 
435
435
  // src/coordinate.ts
436
436
  var Coordinate = class {
437
+ /**
438
+ * Calculate layout to fit content within container while preserving aspect ratio.
439
+ */
440
+ static calculateLayout(container, content, padding = 0) {
441
+ const availableWidth = Math.max(0, container.width - padding * 2);
442
+ const availableHeight = Math.max(0, container.height - padding * 2);
443
+ if (content.width === 0 || content.height === 0) {
444
+ return { scale: 1, offsetX: 0, offsetY: 0, width: 0, height: 0 };
445
+ }
446
+ const scaleX = availableWidth / content.width;
447
+ const scaleY = availableHeight / content.height;
448
+ const scale = Math.min(scaleX, scaleY);
449
+ const width = content.width * scale;
450
+ const height = content.height * scale;
451
+ const offsetX = (container.width - width) / 2;
452
+ const offsetY = (container.height - height) / 2;
453
+ return { scale, offsetX, offsetY, width, height };
454
+ }
437
455
  /**
438
456
  * Convert an absolute value to a normalized value (0-1).
439
457
  * @param value Absolute value (e.g., pixels)
@@ -468,6 +486,21 @@ var Coordinate = class {
468
486
  y: this.toAbsolute(point.y, size.height)
469
487
  };
470
488
  }
489
+ static convertUnit(value, from, to) {
490
+ if (from === to) return value;
491
+ const toMM = {
492
+ px: 0.264583,
493
+ // 1px = 0.264583mm (96 DPI)
494
+ mm: 1,
495
+ cm: 10,
496
+ in: 25.4
497
+ };
498
+ const mmValue = value * (from === "px" ? toMM.px : toMM[from] || 1);
499
+ if (to === "px") {
500
+ return mmValue / toMM.px;
501
+ }
502
+ return mmValue / (toMM[to] || 1);
503
+ }
471
504
  };
472
505
 
473
506
  // src/geometry.ts
@@ -524,9 +557,10 @@ function resolveHolePosition(hole, geometry, canvasSize) {
524
557
  y: by + (hole.offsetY || 0)
525
558
  };
526
559
  } else if (hole.x !== void 0 && hole.y !== void 0) {
560
+ const { x, width, y, height } = geometry;
527
561
  return {
528
- x: hole.x * canvasSize.width,
529
- y: hole.y * canvasSize.height
562
+ x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
563
+ y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
530
564
  };
531
565
  }
532
566
  return { x: 0, y: 0 };
@@ -580,12 +614,25 @@ function getDielineShape(options) {
580
614
  let lugsPath = null;
581
615
  let cutsPath = null;
582
616
  holes.forEach((hole) => {
583
- const lug = new import_paper.default.Path.Circle({
584
- center: [hole.x, hole.y],
617
+ const center = new import_paper.default.Point(hole.x, hole.y);
618
+ const lug = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
619
+ point: [
620
+ center.x - hole.outerRadius,
621
+ center.y - hole.outerRadius
622
+ ],
623
+ size: [hole.outerRadius * 2, hole.outerRadius * 2]
624
+ }) : new import_paper.default.Path.Circle({
625
+ center,
585
626
  radius: hole.outerRadius
586
627
  });
587
- const cut = new import_paper.default.Path.Circle({
588
- center: [hole.x, hole.y],
628
+ const cut = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
629
+ point: [
630
+ center.x - hole.innerRadius,
631
+ center.y - hole.innerRadius
632
+ ],
633
+ size: [hole.innerRadius * 2, hole.innerRadius * 2]
634
+ }) : new import_paper.default.Path.Circle({
635
+ center,
589
636
  radius: hole.innerRadius
590
637
  });
591
638
  if (!lugsPath) {
@@ -632,14 +679,19 @@ function getDielineShape(options) {
632
679
  cutsPath.remove();
633
680
  mainShape = temp;
634
681
  } catch (e) {
635
- console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
682
+ console.error(
683
+ "Geometry: Failed to subtract cutsPath from mainShape",
684
+ e
685
+ );
636
686
  }
637
687
  }
638
688
  }
639
689
  return mainShape;
640
690
  }
641
691
  function generateDielinePath(options) {
642
- ensurePaper(options.width * 2, options.height * 2);
692
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
693
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
694
+ ensurePaper(paperWidth, paperHeight);
643
695
  import_paper.default.project.activeLayer.removeChildren();
644
696
  const mainShape = getDielineShape(options);
645
697
  const pathData = mainShape.pathData;
@@ -663,8 +715,9 @@ function generateMaskPath(options) {
663
715
  return pathData;
664
716
  }
665
717
  function generateBleedZonePath(options, offset) {
666
- const maxDim = Math.max(options.width, options.height) + Math.abs(offset) * 4;
667
- ensurePaper(maxDim, maxDim);
718
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
719
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
720
+ ensurePaper(paperWidth, paperHeight);
668
721
  import_paper.default.project.activeLayer.removeChildren();
669
722
  const shapeOriginal = getDielineShape(options);
670
723
  let shapeOffset;
@@ -725,7 +778,12 @@ function getPathBounds(pathData) {
725
778
  path.pathData = pathData;
726
779
  const bounds = path.bounds;
727
780
  path.remove();
728
- return { width: bounds.width, height: bounds.height };
781
+ return {
782
+ x: bounds.x,
783
+ y: bounds.y,
784
+ width: bounds.width,
785
+ height: bounds.height
786
+ };
729
787
  }
730
788
 
731
789
  // src/dieline.ts
@@ -735,6 +793,7 @@ var DielineTool = class {
735
793
  this.metadata = {
736
794
  name: "DielineTool"
737
795
  };
796
+ this.unit = "mm";
738
797
  this.shape = "rect";
739
798
  this.width = 500;
740
799
  this.height = 500;
@@ -745,6 +804,7 @@ var DielineTool = class {
745
804
  this.outsideColor = "#ffffff";
746
805
  this.showBleedLines = true;
747
806
  this.holes = [];
807
+ this.padding = 140;
748
808
  if (options) {
749
809
  Object.assign(this, options);
750
810
  }
@@ -758,14 +818,12 @@ var DielineTool = class {
758
818
  }
759
819
  const configService = context.services.get("ConfigurationService");
760
820
  if (configService) {
821
+ this.unit = configService.get("dieline.unit", this.unit);
761
822
  this.shape = configService.get("dieline.shape", this.shape);
762
823
  this.width = configService.get("dieline.width", this.width);
763
824
  this.height = configService.get("dieline.height", this.height);
764
825
  this.radius = configService.get("dieline.radius", this.radius);
765
- this.borderLength = configService.get(
766
- "dieline.borderLength",
767
- this.borderLength
768
- );
826
+ this.padding = configService.get("dieline.padding", this.padding);
769
827
  this.offset = configService.get("dieline.offset", this.offset);
770
828
  this.style = configService.get("dieline.style", this.style);
771
829
  this.insideColor = configService.get(
@@ -806,6 +864,13 @@ var DielineTool = class {
806
864
  contribute() {
807
865
  return {
808
866
  [import_core2.ContributionPointIds.CONFIGURATIONS]: [
867
+ {
868
+ id: "dieline.unit",
869
+ type: "select",
870
+ label: "Unit",
871
+ options: ["px", "mm", "cm", "in"],
872
+ default: this.unit
873
+ },
809
874
  {
810
875
  id: "dieline.shape",
811
876
  type: "select",
@@ -841,15 +906,14 @@ var DielineTool = class {
841
906
  id: "dieline.position",
842
907
  type: "json",
843
908
  label: "Position (Normalized)",
844
- default: this.position
909
+ default: this.radius
845
910
  },
846
911
  {
847
- id: "dieline.borderLength",
848
- type: "number",
849
- label: "Margin",
850
- min: 0,
851
- max: 500,
852
- default: this.borderLength
912
+ id: "dieline.padding",
913
+ type: "select",
914
+ label: "View Padding",
915
+ options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
916
+ default: this.padding
853
917
  },
854
918
  {
855
919
  id: "dieline.offset",
@@ -918,7 +982,9 @@ var DielineTool = class {
918
982
  const scale = currentMax / Math.max(bounds.width, bounds.height);
919
983
  const newWidth = bounds.width * scale;
920
984
  const newHeight = bounds.height * scale;
921
- const configService = (_a = this.context) == null ? void 0 : _a.services.get("ConfigurationService");
985
+ const configService = (_a = this.context) == null ? void 0 : _a.services.get(
986
+ "ConfigurationService"
987
+ );
922
988
  if (configService) {
923
989
  configService.update("dieline.width", newWidth);
924
990
  configService.update("dieline.height", newHeight);
@@ -983,12 +1049,25 @@ var DielineTool = class {
983
1049
  }
984
1050
  return new import_fabric2.Pattern({ source: canvas, repetition: "repeat" });
985
1051
  }
1052
+ resolvePadding(containerWidth, containerHeight) {
1053
+ if (typeof this.padding === "number") {
1054
+ return this.padding;
1055
+ }
1056
+ if (typeof this.padding === "string") {
1057
+ if (this.padding.endsWith("%")) {
1058
+ const percent = parseFloat(this.padding) / 100;
1059
+ return Math.min(containerWidth, containerHeight) * percent;
1060
+ }
1061
+ return parseFloat(this.padding) || 0;
1062
+ }
1063
+ return 0;
1064
+ }
986
1065
  updateDieline(emitEvent = true) {
987
- var _a, _b;
988
1066
  if (!this.canvasService) return;
989
1067
  const layer = this.getLayer();
990
1068
  if (!layer) return;
991
1069
  const {
1070
+ unit,
992
1071
  shape,
993
1072
  radius,
994
1073
  offset,
@@ -996,43 +1075,60 @@ var DielineTool = class {
996
1075
  insideColor,
997
1076
  outsideColor,
998
1077
  position,
999
- borderLength,
1000
1078
  showBleedLines,
1001
1079
  holes
1002
1080
  } = this;
1003
1081
  let { width, height } = this;
1004
1082
  const canvasW = this.canvasService.canvas.width || 800;
1005
1083
  const canvasH = this.canvasService.canvas.height || 600;
1006
- let visualWidth = width;
1007
- let visualHeight = height;
1008
- if (borderLength && borderLength > 0) {
1009
- visualWidth = Math.max(0, canvasW - borderLength * 2);
1010
- visualHeight = Math.max(0, canvasH - borderLength * 2);
1011
- }
1012
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1013
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1084
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1085
+ const layout = Coordinate.calculateLayout(
1086
+ { width: canvasW, height: canvasH },
1087
+ { width, height },
1088
+ paddingPx
1089
+ );
1090
+ const scale = layout.scale;
1091
+ const cx = layout.offsetX + layout.width / 2;
1092
+ const cy = layout.offsetY + layout.height / 2;
1093
+ const visualWidth = layout.width;
1094
+ const visualHeight = layout.height;
1095
+ const visualRadius = radius * scale;
1096
+ const visualOffset = offset * scale;
1014
1097
  layer.remove(...layer.getObjects());
1015
1098
  const geometryForHoles = {
1016
1099
  x: cx,
1017
1100
  y: cy,
1018
1101
  width: visualWidth,
1019
1102
  height: visualHeight
1103
+ // Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
1020
1104
  };
1021
1105
  const absoluteHoles = (holes || []).map((h) => {
1022
- const pos = resolveHolePosition(
1023
- h,
1024
- geometryForHoles,
1025
- { width: canvasW, height: canvasH }
1026
- );
1106
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1107
+ const offsetScale = unitScale * scale;
1108
+ const hWithPixelOffsets = {
1109
+ ...h,
1110
+ offsetX: (h.offsetX || 0) * offsetScale,
1111
+ offsetY: (h.offsetY || 0) * offsetScale
1112
+ };
1113
+ const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
1114
+ width: canvasW,
1115
+ height: canvasH
1116
+ });
1027
1117
  return {
1028
1118
  ...h,
1029
1119
  x: pos.x,
1030
- y: pos.y
1120
+ y: pos.y,
1121
+ // Scale hole radii: mm -> current unit -> pixels
1122
+ innerRadius: h.innerRadius * offsetScale,
1123
+ outerRadius: h.outerRadius * offsetScale,
1124
+ // Store scaled offsets in the result for consistency, though pos is already resolved
1125
+ offsetX: hWithPixelOffsets.offsetX,
1126
+ offsetY: hWithPixelOffsets.offsetY
1031
1127
  };
1032
1128
  });
1033
- const cutW = Math.max(0, width + offset * 2);
1034
- const cutH = Math.max(0, height + offset * 2);
1035
- const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
1129
+ const cutW = Math.max(0, visualWidth + visualOffset * 2);
1130
+ const cutH = Math.max(0, visualHeight + visualOffset * 2);
1131
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
1036
1132
  const maskPathData = generateMaskPath({
1037
1133
  canvasWidth: canvasW,
1038
1134
  canvasHeight: canvasH,
@@ -1065,7 +1161,9 @@ var DielineTool = class {
1065
1161
  x: cx,
1066
1162
  y: cy,
1067
1163
  holes: absoluteHoles,
1068
- pathData: this.pathData
1164
+ pathData: this.pathData,
1165
+ canvasWidth: canvasW,
1166
+ canvasHeight: canvasH
1069
1167
  });
1070
1168
  const insideObj = new import_fabric2.Path(productPathData, {
1071
1169
  fill: insideColor,
@@ -1082,15 +1180,17 @@ var DielineTool = class {
1082
1180
  const bleedPathData = generateBleedZonePath(
1083
1181
  {
1084
1182
  shape,
1085
- width,
1086
- height,
1087
- radius,
1183
+ width: visualWidth,
1184
+ height: visualHeight,
1185
+ radius: visualRadius,
1088
1186
  x: cx,
1089
1187
  y: cy,
1090
1188
  holes: absoluteHoles,
1091
- pathData: this.pathData
1189
+ pathData: this.pathData,
1190
+ canvasWidth: canvasW,
1191
+ canvasHeight: canvasH
1092
1192
  },
1093
- offset
1193
+ visualOffset
1094
1194
  );
1095
1195
  if (showBleedLines !== false) {
1096
1196
  const pattern = this.createHatchPattern("red");
@@ -1115,7 +1215,9 @@ var DielineTool = class {
1115
1215
  x: cx,
1116
1216
  y: cy,
1117
1217
  holes: absoluteHoles,
1118
- pathData: this.pathData
1218
+ pathData: this.pathData,
1219
+ canvasWidth: canvasW,
1220
+ canvasHeight: canvasH
1119
1221
  });
1120
1222
  const offsetBorderObj = new import_fabric2.Path(offsetPathData, {
1121
1223
  fill: null,
@@ -1133,14 +1235,15 @@ var DielineTool = class {
1133
1235
  }
1134
1236
  const borderPathData = generateDielinePath({
1135
1237
  shape,
1136
- width,
1137
- height,
1138
- radius,
1238
+ width: visualWidth,
1239
+ height: visualHeight,
1240
+ radius: visualRadius,
1139
1241
  x: cx,
1140
1242
  y: cy,
1141
1243
  holes: absoluteHoles,
1142
- // FIX: Use absoluteHoles instead of holes
1143
- pathData: this.pathData
1244
+ pathData: this.pathData,
1245
+ canvasWidth: canvasW,
1246
+ canvasHeight: canvasH
1144
1247
  });
1145
1248
  const borderObj = new import_fabric2.Path(borderPathData, {
1146
1249
  fill: "transparent",
@@ -1177,115 +1280,109 @@ var DielineTool = class {
1177
1280
  }
1178
1281
  }
1179
1282
  getGeometry() {
1180
- var _a, _b;
1181
1283
  if (!this.canvasService) return null;
1182
- const { shape, width, height, radius, position, borderLength, offset } = this;
1284
+ const { unit, shape, width, height, radius, position, offset } = this;
1183
1285
  const canvasW = this.canvasService.canvas.width || 800;
1184
1286
  const canvasH = this.canvasService.canvas.height || 600;
1185
- let visualWidth = width;
1186
- let visualHeight = height;
1187
- if (borderLength && borderLength > 0) {
1188
- visualWidth = Math.max(0, canvasW - borderLength * 2);
1189
- visualHeight = Math.max(0, canvasH - borderLength * 2);
1190
- }
1191
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1192
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1287
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1288
+ const layout = Coordinate.calculateLayout(
1289
+ { width: canvasW, height: canvasH },
1290
+ { width, height },
1291
+ paddingPx
1292
+ );
1293
+ const scale = layout.scale;
1294
+ const cx = layout.offsetX + layout.width / 2;
1295
+ const cy = layout.offsetY + layout.height / 2;
1296
+ const visualWidth = layout.width;
1297
+ const visualHeight = layout.height;
1193
1298
  return {
1194
1299
  shape,
1300
+ unit,
1195
1301
  x: cx,
1196
1302
  y: cy,
1197
1303
  width: visualWidth,
1198
1304
  height: visualHeight,
1199
- radius,
1200
- offset,
1201
- borderLength,
1305
+ radius: radius * scale,
1306
+ offset: offset * scale,
1307
+ // Pass scale to help other tools (like HoleTool) convert units
1308
+ scale,
1202
1309
  pathData: this.pathData
1203
1310
  };
1204
1311
  }
1205
- exportCutImage() {
1206
- var _a, _b, _c, _d;
1312
+ async exportCutImage() {
1207
1313
  if (!this.canvasService) return null;
1208
- const canvas = this.canvasService.canvas;
1314
+ const userLayer = this.canvasService.getLayer("user");
1315
+ if (!userLayer) return null;
1209
1316
  const { shape, width, height, radius, position, holes } = this;
1210
- const canvasW = canvas.width || 800;
1211
- const canvasH = canvas.height || 600;
1212
- const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1213
- const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1317
+ const canvasW = this.canvasService.canvas.width || 800;
1318
+ const canvasH = this.canvasService.canvas.height || 600;
1319
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
1320
+ const layout = Coordinate.calculateLayout(
1321
+ { width: canvasW, height: canvasH },
1322
+ { width, height },
1323
+ paddingPx
1324
+ );
1325
+ const scale = layout.scale;
1326
+ const cx = layout.offsetX + layout.width / 2;
1327
+ const cy = layout.offsetY + layout.height / 2;
1328
+ const visualWidth = layout.width;
1329
+ const visualHeight = layout.height;
1330
+ const visualRadius = radius * scale;
1214
1331
  const absoluteHoles = (holes || []).map((h) => {
1332
+ const unit = this.unit || "mm";
1333
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1215
1334
  const pos = resolveHolePosition(
1216
- h,
1217
- { x: cx, y: cy, width, height },
1335
+ {
1336
+ ...h,
1337
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1338
+ offsetY: (h.offsetY || 0) * unitScale * scale
1339
+ },
1340
+ { x: cx, y: cy, width: visualWidth, height: visualHeight },
1218
1341
  { width: canvasW, height: canvasH }
1219
1342
  );
1220
1343
  return {
1221
1344
  ...h,
1222
1345
  x: pos.x,
1223
- y: pos.y
1346
+ y: pos.y,
1347
+ innerRadius: h.innerRadius * unitScale * scale,
1348
+ outerRadius: h.outerRadius * unitScale * scale,
1349
+ offsetX: (h.offsetX || 0) * unitScale * scale,
1350
+ offsetY: (h.offsetY || 0) * unitScale * scale
1224
1351
  };
1225
1352
  });
1226
1353
  const pathData = generateDielinePath({
1227
1354
  shape,
1228
- width,
1229
- height,
1230
- radius,
1355
+ width: visualWidth,
1356
+ height: visualHeight,
1357
+ radius: visualRadius,
1231
1358
  x: cx,
1232
1359
  y: cy,
1233
1360
  holes: absoluteHoles,
1234
- pathData: this.pathData
1361
+ pathData: this.pathData,
1362
+ canvasWidth: canvasW,
1363
+ canvasHeight: canvasH
1235
1364
  });
1365
+ const clonedLayer = await userLayer.clone();
1236
1366
  const clipPath = new import_fabric2.Path(pathData, {
1237
- left: 0,
1238
- top: 0,
1239
1367
  originX: "left",
1240
1368
  originY: "top",
1241
- absolutePositioned: true
1242
- });
1243
- const layer = this.getLayer();
1244
- const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
1245
- if (layer) layer.visible = false;
1246
- const holeMarkers = canvas.getObjects().filter((o) => {
1247
- var _a2;
1248
- return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
1249
- });
1250
- holeMarkers.forEach((o) => o.visible = false);
1251
- const rulerLayer = canvas.getObjects().find((obj) => {
1252
- var _a2;
1253
- return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
1254
- });
1255
- const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
1256
- if (rulerLayer) rulerLayer.visible = false;
1257
- const originalClip = canvas.clipPath;
1258
- canvas.clipPath = clipPath;
1259
- const bbox = clipPath.getBoundingRect();
1260
- const clipPathCorrected = new import_fabric2.Path(pathData, {
1261
- absolutePositioned: true,
1262
1369
  left: 0,
1263
- top: 0
1264
- });
1265
- const tempPath = new import_fabric2.Path(pathData);
1266
- const tempBounds = tempPath.getBoundingRect();
1267
- clipPathCorrected.set({
1268
- left: tempBounds.left,
1269
- top: tempBounds.top,
1270
- originX: "left",
1271
- originY: "top"
1370
+ top: 0,
1371
+ absolutePositioned: true
1372
+ // Important for groups
1272
1373
  });
1273
- canvas.clipPath = clipPathCorrected;
1274
- const exportBbox = clipPathCorrected.getBoundingRect();
1275
- const dataURL = canvas.toDataURL({
1374
+ clonedLayer.clipPath = clipPath;
1375
+ const bounds = clipPath.getBoundingRect();
1376
+ const dataUrl = clonedLayer.toDataURL({
1276
1377
  format: "png",
1277
1378
  multiplier: 2,
1278
- left: exportBbox.left,
1279
- top: exportBbox.top,
1280
- width: exportBbox.width,
1281
- height: exportBbox.height
1379
+ // Better quality
1380
+ left: bounds.left,
1381
+ top: bounds.top,
1382
+ width: bounds.width,
1383
+ height: bounds.height
1282
1384
  });
1283
- canvas.clipPath = originalClip;
1284
- if (layer) layer.visible = wasVisible;
1285
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
1286
- holeMarkers.forEach((o) => o.visible = true);
1287
- canvas.requestRenderAll();
1288
- return dataURL;
1385
+ return dataUrl;
1289
1386
  }
1290
1387
  };
1291
1388
 
@@ -1556,13 +1653,21 @@ var HoleTool = class {
1556
1653
  command: "addHole",
1557
1654
  title: "Add Hole",
1558
1655
  handler: (x, y) => {
1559
- var _a, _b, _c;
1656
+ var _a, _b, _c, _d;
1560
1657
  if (!this.canvasService) return false;
1561
- const { width, height } = this.canvasService.canvas;
1562
- const normalizedHole = Coordinate.normalizePoint(
1563
- { x, y },
1564
- { width: width || 800, height: height || 600 }
1565
- );
1658
+ let normalizedX = 0.5;
1659
+ let normalizedY = 0.5;
1660
+ if (this.currentGeometry) {
1661
+ const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
1662
+ const left = gx - gw / 2;
1663
+ const top = gy - gh / 2;
1664
+ normalizedX = gw > 0 ? (x - left) / gw : 0.5;
1665
+ normalizedY = gh > 0 ? (y - top) / gh : 0.5;
1666
+ } else {
1667
+ const { width, height } = this.canvasService.canvas;
1668
+ normalizedX = Coordinate.toNormalized(x, width || 800);
1669
+ normalizedY = Coordinate.toNormalized(y, height || 600);
1670
+ }
1566
1671
  const configService = (_a = this.context) == null ? void 0 : _a.services.get(
1567
1672
  "ConfigurationService"
1568
1673
  );
@@ -1571,9 +1676,11 @@ var HoleTool = class {
1571
1676
  const lastHole = currentHoles[currentHoles.length - 1];
1572
1677
  const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
1573
1678
  const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
1679
+ const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
1574
1680
  const newHole = {
1575
- x: normalizedHole.x,
1576
- y: normalizedHole.y,
1681
+ x: normalizedX,
1682
+ y: normalizedY,
1683
+ shape,
1577
1684
  innerRadius,
1578
1685
  outerRadius
1579
1686
  };
@@ -1605,6 +1712,7 @@ var HoleTool = class {
1605
1712
  if (!this.handleDielineChange) {
1606
1713
  this.handleDielineChange = (geometry) => {
1607
1714
  this.currentGeometry = geometry;
1715
+ this.redraw();
1608
1716
  const changed = this.enforceConstraints();
1609
1717
  if (changed) {
1610
1718
  this.syncHolesToDieline();
@@ -1668,7 +1776,10 @@ var HoleTool = class {
1668
1776
  var _a;
1669
1777
  const target = e.target;
1670
1778
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1671
- this.syncHolesFromCanvas();
1779
+ const changed = this.enforceConstraints();
1780
+ if (!changed) {
1781
+ this.syncHolesFromCanvas();
1782
+ }
1672
1783
  };
1673
1784
  canvas.on("object:modified", this.handleModified);
1674
1785
  }
@@ -1715,10 +1826,16 @@ var HoleTool = class {
1715
1826
  }
1716
1827
  syncHolesFromCanvas() {
1717
1828
  if (!this.canvasService) return;
1718
- const objects = this.canvasService.canvas.getObjects().filter((obj) => {
1719
- var _a;
1720
- return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1721
- });
1829
+ const objects = this.canvasService.canvas.getObjects().filter(
1830
+ (obj) => {
1831
+ var _a;
1832
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
1833
+ }
1834
+ );
1835
+ if (objects.length === 0 && this.holes.length > 0) {
1836
+ console.warn("HoleTool: No markers found on canvas to sync from");
1837
+ return;
1838
+ }
1722
1839
  objects.sort(
1723
1840
  (a, b) => {
1724
1841
  var _a, _b, _c, _d;
@@ -1726,18 +1843,28 @@ var HoleTool = class {
1726
1843
  }
1727
1844
  );
1728
1845
  const newHoles = objects.map((obj, i) => {
1729
- var _a, _b;
1846
+ var _a, _b, _c, _d;
1730
1847
  const original = this.holes[i];
1731
1848
  const newAbsX = obj.left;
1732
1849
  const newAbsY = obj.top;
1850
+ if (isNaN(newAbsX) || isNaN(newAbsY)) {
1851
+ console.error("HoleTool: Invalid marker coordinates", {
1852
+ newAbsX,
1853
+ newAbsY
1854
+ });
1855
+ return original;
1856
+ }
1857
+ const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
1858
+ const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
1859
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1733
1860
  if (original && original.anchor && this.currentGeometry) {
1734
- const { x, y, width: width2, height: height2 } = this.currentGeometry;
1861
+ const { x, y, width, height } = this.currentGeometry;
1735
1862
  let bx = x;
1736
1863
  let by = y;
1737
- const left = x - width2 / 2;
1738
- const right = x + width2 / 2;
1739
- const top = y - height2 / 2;
1740
- const bottom = y + height2 / 2;
1864
+ const left = x - width / 2;
1865
+ const right = x + width / 2;
1866
+ const top = y - height / 2;
1867
+ const bottom = y + height / 2;
1741
1868
  switch (original.anchor) {
1742
1869
  case "top-left":
1743
1870
  bx = left;
@@ -1778,25 +1905,42 @@ var HoleTool = class {
1778
1905
  }
1779
1906
  return {
1780
1907
  ...original,
1781
- offsetX: newAbsX - bx,
1782
- offsetY: newAbsY - by,
1908
+ // Denormalize offset back to physical units (mm)
1909
+ offsetX: (newAbsX - bx) / scale / unitScale,
1910
+ offsetY: (newAbsY - by) / scale / unitScale,
1783
1911
  // Clear direct coordinates if we use anchor
1784
1912
  x: void 0,
1785
- y: void 0
1913
+ y: void 0,
1914
+ // Ensure other properties are preserved
1915
+ innerRadius: original.innerRadius,
1916
+ outerRadius: original.outerRadius,
1917
+ shape: original.shape || "circle"
1786
1918
  };
1787
1919
  }
1788
- const { width, height } = this.canvasService.canvas;
1789
- const p = Coordinate.normalizePoint(
1790
- { x: newAbsX, y: newAbsY },
1791
- { width: width || 800, height: height || 600 }
1792
- );
1920
+ let normalizedX = 0.5;
1921
+ let normalizedY = 0.5;
1922
+ if (this.currentGeometry) {
1923
+ const { x, y, width, height } = this.currentGeometry;
1924
+ const left = x - width / 2;
1925
+ const top = y - height / 2;
1926
+ normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
1927
+ normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
1928
+ } else {
1929
+ const { width, height } = this.canvasService.canvas;
1930
+ normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
1931
+ normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
1932
+ }
1793
1933
  return {
1794
1934
  ...original,
1795
- x: p.x,
1796
- y: p.y,
1797
- // Ensure radii are preserved
1798
- innerRadius: (_a = original == null ? void 0 : original.innerRadius) != null ? _a : 15,
1799
- outerRadius: (_b = original == null ? void 0 : original.outerRadius) != null ? _b : 25
1935
+ x: normalizedX,
1936
+ y: normalizedY,
1937
+ // Clear offsets if we are using direct normalized coordinates
1938
+ offsetX: void 0,
1939
+ offsetY: void 0,
1940
+ // Ensure other properties are preserved
1941
+ innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
1942
+ outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
1943
+ shape: (original == null ? void 0 : original.shape) || "circle"
1800
1944
  };
1801
1945
  });
1802
1946
  this.holes = newHoles;
@@ -1834,24 +1978,54 @@ var HoleTool = class {
1834
1978
  x: (width || 800) / 2,
1835
1979
  y: (height || 600) / 2,
1836
1980
  width: width || 800,
1837
- height: height || 600
1981
+ height: height || 600,
1982
+ scale: 1
1983
+ // Default scale if no geometry loaded
1838
1984
  };
1839
1985
  holes.forEach((hole, index) => {
1986
+ const scale = geometry.scale || 1;
1987
+ const unit = geometry.unit || "mm";
1988
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
1989
+ const visualInnerRadius = hole.innerRadius * unitScale * scale;
1990
+ const visualOuterRadius = hole.outerRadius * unitScale * scale;
1840
1991
  const pos = resolveHolePosition(
1841
- hole,
1992
+ {
1993
+ ...hole,
1994
+ offsetX: (hole.offsetX || 0) * unitScale * scale,
1995
+ offsetY: (hole.offsetY || 0) * unitScale * scale
1996
+ },
1842
1997
  geometry,
1843
- { width: width || 800, height: height || 600 }
1998
+ { width: geometry.width, height: geometry.height }
1999
+ // Use geometry dims instead of canvas
1844
2000
  );
1845
- const innerCircle = new import_fabric4.Circle({
1846
- radius: hole.innerRadius,
2001
+ const isSquare = hole.shape === "square";
2002
+ const innerMarker = isSquare ? new import_fabric4.Rect({
2003
+ width: visualInnerRadius * 2,
2004
+ height: visualInnerRadius * 2,
2005
+ fill: "transparent",
2006
+ stroke: "red",
2007
+ strokeWidth: 2,
2008
+ originX: "center",
2009
+ originY: "center"
2010
+ }) : new import_fabric4.Circle({
2011
+ radius: visualInnerRadius,
1847
2012
  fill: "transparent",
1848
2013
  stroke: "red",
1849
2014
  strokeWidth: 2,
1850
2015
  originX: "center",
1851
2016
  originY: "center"
1852
2017
  });
1853
- const outerCircle = new import_fabric4.Circle({
1854
- radius: hole.outerRadius,
2018
+ const outerMarker = isSquare ? new import_fabric4.Rect({
2019
+ width: visualOuterRadius * 2,
2020
+ height: visualOuterRadius * 2,
2021
+ fill: "transparent",
2022
+ stroke: "#666",
2023
+ strokeWidth: 1,
2024
+ strokeDashArray: [5, 5],
2025
+ originX: "center",
2026
+ originY: "center"
2027
+ }) : new import_fabric4.Circle({
2028
+ radius: visualOuterRadius,
1855
2029
  fill: "transparent",
1856
2030
  stroke: "#666",
1857
2031
  strokeWidth: 1,
@@ -1859,7 +2033,7 @@ var HoleTool = class {
1859
2033
  originX: "center",
1860
2034
  originY: "center"
1861
2035
  });
1862
- const holeGroup = new import_fabric4.Group([outerCircle, innerCircle], {
2036
+ const holeGroup = new import_fabric4.Group([outerMarker, innerMarker], {
1863
2037
  left: pos.x,
1864
2038
  top: pos.y,
1865
2039
  originX: "center",
@@ -1931,11 +2105,16 @@ var HoleTool = class {
1931
2105
  var _a, _b;
1932
2106
  const currentPos = new import_fabric4.Point(obj.left, obj.top);
1933
2107
  const holeData = this.holes[i];
2108
+ const scale = geometry.scale || 1;
2109
+ const unit = geometry.unit || "mm";
2110
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
2111
+ const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
2112
+ const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
1934
2113
  const newPos = this.calculateConstrainedPosition(
1935
2114
  currentPos,
1936
2115
  constraintGeometry,
1937
- (_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15,
1938
- (_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25
2116
+ innerR,
2117
+ outerR
1939
2118
  );
1940
2119
  if (currentPos.distanceFrom(newPos) > 0.1) {
1941
2120
  obj.set({
@@ -1989,17 +2168,14 @@ var HoleTool = class {
1989
2168
  var import_core5 = require("@pooder/core");
1990
2169
  var import_fabric5 = require("fabric");
1991
2170
  var ImageTool = class {
1992
- constructor(options) {
2171
+ constructor() {
1993
2172
  this.id = "pooder.kit.image";
1994
2173
  this.metadata = {
1995
2174
  name: "ImageTool"
1996
2175
  };
1997
- this._loadingUrl = null;
1998
- this.url = "";
1999
- this.opacity = 1;
2000
- if (options) {
2001
- Object.assign(this, options);
2002
- }
2176
+ this.items = [];
2177
+ this.objectMap = /* @__PURE__ */ new Map();
2178
+ this.isUpdatingConfig = false;
2003
2179
  }
2004
2180
  activate(context) {
2005
2181
  this.context = context;
@@ -2010,38 +2186,33 @@ var ImageTool = class {
2010
2186
  }
2011
2187
  const configService = context.services.get("ConfigurationService");
2012
2188
  if (configService) {
2013
- this.url = configService.get("image.url", this.url);
2014
- this.opacity = configService.get("image.opacity", this.opacity);
2015
- this.width = configService.get("image.width", this.width);
2016
- this.height = configService.get("image.height", this.height);
2017
- this.angle = configService.get("image.angle", this.angle);
2018
- this.left = configService.get("image.left", this.left);
2019
- this.top = configService.get("image.top", this.top);
2189
+ this.items = configService.get("image.items", []) || [];
2020
2190
  configService.onAnyChange((e) => {
2021
- if (e.key.startsWith("image.")) {
2022
- const prop = e.key.split(".")[1];
2023
- console.log(
2024
- `[ImageTool] Config change detected: ${e.key} -> ${e.value}`
2025
- );
2026
- if (prop && prop in this) {
2027
- this[prop] = e.value;
2028
- this.updateImage();
2029
- }
2191
+ if (this.isUpdatingConfig) return;
2192
+ let shouldUpdate = false;
2193
+ if (e.key === "image.items") {
2194
+ this.items = e.value || [];
2195
+ shouldUpdate = true;
2196
+ } else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
2197
+ shouldUpdate = true;
2198
+ }
2199
+ if (shouldUpdate) {
2200
+ this.updateImages();
2030
2201
  }
2031
2202
  });
2032
2203
  }
2033
2204
  this.ensureLayer();
2034
- this.updateImage();
2205
+ this.updateImages();
2035
2206
  }
2036
2207
  deactivate(context) {
2037
2208
  if (this.canvasService) {
2038
2209
  const layer = this.canvasService.getLayer("user");
2039
2210
  if (layer) {
2040
- const userImage = this.canvasService.getObject("user-image", "user");
2041
- if (userImage) {
2042
- layer.remove(userImage);
2043
- this.canvasService.requestRenderAll();
2044
- }
2211
+ this.objectMap.forEach((obj) => {
2212
+ layer.remove(obj);
2213
+ });
2214
+ this.objectMap.clear();
2215
+ this.canvasService.requestRenderAll();
2045
2216
  }
2046
2217
  this.canvasService = void 0;
2047
2218
  this.context = void 0;
@@ -2051,82 +2222,103 @@ var ImageTool = class {
2051
2222
  return {
2052
2223
  [import_core5.ContributionPointIds.CONFIGURATIONS]: [
2053
2224
  {
2054
- id: "image.url",
2055
- type: "string",
2056
- label: "Image URL",
2057
- default: this.url
2058
- },
2225
+ id: "image.items",
2226
+ type: "array",
2227
+ label: "Images",
2228
+ default: []
2229
+ }
2230
+ ],
2231
+ [import_core5.ContributionPointIds.COMMANDS]: [
2059
2232
  {
2060
- id: "image.opacity",
2061
- type: "number",
2062
- label: "Opacity",
2063
- min: 0,
2064
- max: 1,
2065
- step: 0.1,
2066
- default: this.opacity
2233
+ command: "addImage",
2234
+ title: "Add Image",
2235
+ handler: (url, options) => {
2236
+ const newItem = {
2237
+ id: this.generateId(),
2238
+ url,
2239
+ opacity: 1,
2240
+ ...options
2241
+ };
2242
+ this.updateConfig([...this.items, newItem]);
2243
+ return newItem.id;
2244
+ }
2067
2245
  },
2068
2246
  {
2069
- id: "image.width",
2070
- type: "number",
2071
- label: "Width",
2072
- min: 0,
2073
- max: 5e3,
2074
- default: this.width
2247
+ command: "removeImage",
2248
+ title: "Remove Image",
2249
+ handler: (id) => {
2250
+ const newItems = this.items.filter((item) => item.id !== id);
2251
+ if (newItems.length !== this.items.length) {
2252
+ this.updateConfig(newItems);
2253
+ }
2254
+ }
2075
2255
  },
2076
2256
  {
2077
- id: "image.height",
2078
- type: "number",
2079
- label: "Height",
2080
- min: 0,
2081
- max: 5e3,
2082
- default: this.height
2257
+ command: "updateImage",
2258
+ title: "Update Image",
2259
+ handler: (id, updates) => {
2260
+ const index = this.items.findIndex((item) => item.id === id);
2261
+ if (index !== -1) {
2262
+ const newItems = [...this.items];
2263
+ newItems[index] = { ...newItems[index], ...updates };
2264
+ this.updateConfig(newItems);
2265
+ }
2266
+ }
2083
2267
  },
2084
2268
  {
2085
- id: "image.angle",
2086
- type: "number",
2087
- label: "Rotation",
2088
- min: 0,
2089
- max: 360,
2090
- default: this.angle
2269
+ command: "clearImages",
2270
+ title: "Clear Images",
2271
+ handler: () => {
2272
+ this.updateConfig([]);
2273
+ }
2091
2274
  },
2092
2275
  {
2093
- id: "image.left",
2094
- type: "number",
2095
- label: "Left (Normalized)",
2096
- min: 0,
2097
- max: 1,
2098
- default: this.left
2276
+ command: "bringToFront",
2277
+ title: "Bring Image to Front",
2278
+ handler: (id) => {
2279
+ const index = this.items.findIndex((item) => item.id === id);
2280
+ if (index !== -1 && index < this.items.length - 1) {
2281
+ const newItems = [...this.items];
2282
+ const [item] = newItems.splice(index, 1);
2283
+ newItems.push(item);
2284
+ this.updateConfig(newItems);
2285
+ }
2286
+ }
2099
2287
  },
2100
2288
  {
2101
- id: "image.top",
2102
- type: "number",
2103
- label: "Top (Normalized)",
2104
- min: 0,
2105
- max: 1,
2106
- default: this.top
2107
- }
2108
- ],
2109
- [import_core5.ContributionPointIds.COMMANDS]: [
2110
- {
2111
- command: "setUserImage",
2112
- title: "Set User Image",
2113
- handler: (url, opacity, width, height, angle, left, top) => {
2114
- if (this.url === url && this.opacity === opacity && this.width === width && this.height === height && this.angle === angle && this.left === left && this.top === top)
2115
- return true;
2116
- this.url = url;
2117
- this.opacity = opacity;
2118
- this.width = width;
2119
- this.height = height;
2120
- this.angle = angle;
2121
- this.left = left;
2122
- this.top = top;
2123
- this.updateImage();
2124
- return true;
2289
+ command: "sendToBack",
2290
+ title: "Send Image to Back",
2291
+ handler: (id) => {
2292
+ const index = this.items.findIndex((item) => item.id === id);
2293
+ if (index > 0) {
2294
+ const newItems = [...this.items];
2295
+ const [item] = newItems.splice(index, 1);
2296
+ newItems.unshift(item);
2297
+ this.updateConfig(newItems);
2298
+ }
2125
2299
  }
2126
2300
  }
2127
2301
  ]
2128
2302
  };
2129
2303
  }
2304
+ generateId() {
2305
+ return Math.random().toString(36).substring(2, 9);
2306
+ }
2307
+ updateConfig(newItems, skipCanvasUpdate = false) {
2308
+ if (!this.context) return;
2309
+ this.isUpdatingConfig = true;
2310
+ this.items = newItems;
2311
+ const configService = this.context.services.get("ConfigurationService");
2312
+ if (configService) {
2313
+ configService.update("image.items", newItems);
2314
+ }
2315
+ if (!skipCanvasUpdate) {
2316
+ this.updateImages();
2317
+ }
2318
+ setTimeout(() => {
2319
+ this.isUpdatingConfig = false;
2320
+ }, 50);
2321
+ }
2130
2322
  ensureLayer() {
2131
2323
  if (!this.canvasService) return;
2132
2324
  let userLayer = this.canvasService.getLayer("user");
@@ -2158,224 +2350,194 @@ var ImageTool = class {
2158
2350
  this.canvasService.requestRenderAll();
2159
2351
  }
2160
2352
  }
2161
- updateImage() {
2353
+ getLayoutInfo() {
2162
2354
  var _a, _b;
2355
+ const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
2356
+ const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
2357
+ let layoutScale = 1;
2358
+ let layoutOffsetX = 0;
2359
+ let layoutOffsetY = 0;
2360
+ let visualWidth = canvasW;
2361
+ let visualHeight = canvasH;
2362
+ let dielinePhysicalWidth = 500;
2363
+ let dielinePhysicalHeight = 500;
2364
+ let bleedOffset = 0;
2365
+ if (this.context) {
2366
+ const configService = this.context.services.get("ConfigurationService");
2367
+ if (configService) {
2368
+ dielinePhysicalWidth = configService.get("dieline.width") || 500;
2369
+ dielinePhysicalHeight = configService.get("dieline.height") || 500;
2370
+ bleedOffset = configService.get("dieline.offset") || 0;
2371
+ const paddingValue = configService.get("dieline.padding") || 40;
2372
+ let padding = 0;
2373
+ if (typeof paddingValue === "number") {
2374
+ padding = paddingValue;
2375
+ } else if (typeof paddingValue === "string") {
2376
+ if (paddingValue.endsWith("%")) {
2377
+ const percent = parseFloat(paddingValue) / 100;
2378
+ padding = Math.min(canvasW, canvasH) * percent;
2379
+ } else {
2380
+ padding = parseFloat(paddingValue) || 0;
2381
+ }
2382
+ }
2383
+ const layout = Coordinate.calculateLayout(
2384
+ { width: canvasW, height: canvasH },
2385
+ { width: dielinePhysicalWidth, height: dielinePhysicalHeight },
2386
+ padding
2387
+ );
2388
+ layoutScale = layout.scale;
2389
+ layoutOffsetX = layout.offsetX;
2390
+ layoutOffsetY = layout.offsetY;
2391
+ visualWidth = layout.width;
2392
+ visualHeight = layout.height;
2393
+ }
2394
+ }
2395
+ return {
2396
+ layoutScale,
2397
+ layoutOffsetX,
2398
+ layoutOffsetY,
2399
+ visualWidth,
2400
+ visualHeight,
2401
+ dielinePhysicalWidth,
2402
+ dielinePhysicalHeight,
2403
+ bleedOffset
2404
+ };
2405
+ }
2406
+ updateImages() {
2163
2407
  if (!this.canvasService) return;
2164
- let { url, opacity, width, height, angle, left, top } = this;
2165
2408
  const layer = this.canvasService.getLayer("user");
2166
2409
  if (!layer) {
2167
2410
  console.warn("[ImageTool] User layer not found");
2168
2411
  return;
2169
2412
  }
2170
- const userImage = this.canvasService.getObject("user-image", "user");
2171
- if (this._loadingUrl === url) return;
2172
- if (userImage) {
2173
- const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
2174
- if (currentSrc !== url) {
2175
- this.loadImage(layer);
2176
- } else {
2177
- const updates = {};
2178
- const canvasW = this.canvasService.canvas.width || 800;
2179
- const canvasH = this.canvasService.canvas.height || 600;
2180
- const centerX = canvasW / 2;
2181
- const centerY = canvasH / 2;
2182
- if (userImage.opacity !== opacity) updates.opacity = opacity;
2183
- if (angle !== void 0 && userImage.angle !== angle)
2184
- updates.angle = angle;
2185
- if (userImage.originX !== "center") {
2186
- userImage.set({
2187
- originX: "center",
2188
- originY: "center",
2189
- left: userImage.left + userImage.width * userImage.scaleX / 2,
2190
- top: userImage.top + userImage.height * userImage.scaleY / 2
2191
- });
2192
- }
2193
- if (left !== void 0) {
2194
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2195
- const localLeft = globalLeft - centerX;
2196
- if (Math.abs(userImage.left - localLeft) > 1)
2197
- updates.left = localLeft;
2198
- }
2199
- if (top !== void 0) {
2200
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2201
- const localTop = globalTop - centerY;
2202
- if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
2203
- }
2204
- if (width !== void 0 && userImage.width)
2205
- updates.scaleX = width / userImage.width;
2206
- if (height !== void 0 && userImage.height)
2207
- updates.scaleY = height / userImage.height;
2208
- if (Object.keys(updates).length > 0) {
2209
- userImage.set(updates);
2210
- layer.dirty = true;
2211
- this.canvasService.requestRenderAll();
2212
- }
2413
+ const currentIds = new Set(this.items.map((i) => i.id));
2414
+ for (const [id, obj] of this.objectMap) {
2415
+ if (!currentIds.has(id)) {
2416
+ layer.remove(obj);
2417
+ this.objectMap.delete(id);
2213
2418
  }
2214
- } else {
2215
- this.loadImage(layer);
2216
2419
  }
2217
- }
2218
- loadImage(layer) {
2219
- if (!this.canvasService) return;
2220
- const { url } = this;
2221
- if (!url) return;
2222
- this._loadingUrl = url;
2223
- import_fabric5.FabricImage.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2224
- var _a, _b, _c, _d, _e, _f;
2225
- if (this._loadingUrl !== url) return;
2226
- this._loadingUrl = null;
2227
- let { opacity, width, height, angle, left, top } = this;
2228
- if (this.context) {
2229
- const configService = this.context.services.get(
2230
- "ConfigurationService"
2231
- );
2232
- const dielineWidth = configService.get("dieline.width");
2233
- const dielineHeight = configService.get("dieline.height");
2234
- console.log(
2235
- "[ImageTool] Dieline config debug:",
2236
- {
2237
- widthVal: dielineWidth,
2238
- heightVal: dielineHeight,
2239
- // Debug: dump all keys to see what is available
2240
- allKeys: Array.from(
2241
- ((_a = configService.configValues) == null ? void 0 : _a.keys()) || []
2242
- )
2243
- },
2244
- configService
2245
- );
2246
- if (width === void 0 && height === void 0) {
2247
- const scale = Math.min(
2248
- dielineWidth / (image.width || 1),
2249
- dielineHeight / (image.height || 1)
2250
- );
2251
- width = (image.width || 1) * scale;
2252
- height = (image.height || 1) * scale;
2253
- this.width = width;
2254
- this.height = height;
2255
- }
2256
- if (left === void 0 && top === void 0) {
2257
- const dielinePos = configService == null ? void 0 : configService.get("dieline.position");
2258
- if (dielinePos) {
2259
- this.left = dielinePos.x;
2260
- this.top = dielinePos.y;
2261
- } else {
2262
- this.left = 0.5;
2263
- this.top = 0.5;
2264
- }
2265
- left = this.left;
2266
- top = this.top;
2267
- }
2268
- }
2269
- const existingImage = this.canvasService.getObject(
2270
- "user-image",
2271
- "user"
2272
- );
2273
- if (existingImage) {
2274
- const defaultLeft = existingImage.left;
2275
- const defaultTop = existingImage.top;
2276
- const defaultAngle = existingImage.angle;
2277
- const defaultScaleX = existingImage.scaleX;
2278
- const defaultScaleY = existingImage.scaleY;
2279
- const canvasW = ((_b = this.canvasService) == null ? void 0 : _b.canvas.width) || 800;
2280
- const canvasH = ((_c = this.canvasService) == null ? void 0 : _c.canvas.height) || 600;
2281
- const centerX = canvasW / 2;
2282
- const centerY = canvasH / 2;
2283
- let targetLeft = left !== void 0 ? left : defaultLeft;
2284
- let targetTop = top !== void 0 ? top : defaultTop;
2285
- const configService = (_d = this.context) == null ? void 0 : _d.services.get(
2286
- "ConfigurationService"
2287
- );
2288
- console.log("[ImageTool] Loading EXISTING image...", {
2289
- canvasW,
2290
- canvasH,
2291
- centerX,
2292
- centerY,
2293
- incomingLeft: left,
2294
- incomingTop: top,
2295
- dielinePos: configService == null ? void 0 : configService.get("dieline.position"),
2296
- existingImage: !!existingImage
2297
- });
2298
- if (left !== void 0) {
2299
- const globalLeft = Coordinate.toAbsolute(left, canvasW);
2300
- targetLeft = globalLeft;
2301
- console.log("[ImageTool] Calculated targetLeft", {
2302
- globalLeft,
2303
- targetLeft
2304
- });
2305
- }
2306
- if (top !== void 0) {
2307
- const globalTop = Coordinate.toAbsolute(top, canvasH);
2308
- targetTop = globalTop;
2309
- console.log("[ImageTool] Calculated targetTop", {
2310
- globalTop,
2311
- targetTop
2312
- });
2313
- }
2314
- image.set({
2315
- originX: "center",
2316
- // Use center origin for easier positioning
2317
- originY: "center",
2318
- left: targetLeft,
2319
- top: targetTop,
2320
- angle: angle !== void 0 ? angle : defaultAngle,
2321
- scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
2322
- scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
2323
- });
2324
- layer.remove(existingImage);
2420
+ const layout = this.getLayoutInfo();
2421
+ this.items.forEach((item, index) => {
2422
+ let obj = this.objectMap.get(item.id);
2423
+ if (!obj) {
2424
+ this.loadImage(item, layer, layout);
2325
2425
  } else {
2326
- image.set({
2327
- originX: "center",
2328
- originY: "center"
2329
- });
2330
- if (width !== void 0 && image.width)
2331
- image.scaleX = width / image.width;
2332
- if (height !== void 0 && image.height)
2333
- image.scaleY = height / image.height;
2334
- if (angle !== void 0) image.angle = angle;
2335
- const canvasW = ((_e = this.canvasService) == null ? void 0 : _e.canvas.width) || 800;
2336
- const canvasH = ((_f = this.canvasService) == null ? void 0 : _f.canvas.height) || 600;
2337
- const centerX = canvasW / 2;
2338
- const centerY = canvasH / 2;
2339
- if (left !== void 0) {
2340
- image.left = Coordinate.toAbsolute(left, canvasW);
2341
- } else {
2342
- image.left = centerX;
2343
- }
2344
- if (top !== void 0) {
2345
- image.top = Coordinate.toAbsolute(top, canvasH);
2346
- } else {
2347
- image.top = centerY;
2348
- }
2426
+ this.updateObjectProperties(obj, item, layout);
2427
+ layer.remove(obj);
2428
+ layer.add(obj);
2349
2429
  }
2430
+ });
2431
+ layer.dirty = true;
2432
+ this.canvasService.requestRenderAll();
2433
+ }
2434
+ updateObjectProperties(obj, item, layout) {
2435
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2436
+ const updates = {};
2437
+ if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
2438
+ if (item.angle !== void 0 && obj.angle !== item.angle) updates.angle = item.angle;
2439
+ if (item.left !== void 0) {
2440
+ const globalLeft = layoutOffsetX + item.left * visualWidth;
2441
+ if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
2442
+ }
2443
+ if (item.top !== void 0) {
2444
+ const globalTop = layoutOffsetY + item.top * visualHeight;
2445
+ if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
2446
+ }
2447
+ if (item.width !== void 0 && obj.width) {
2448
+ const targetScaleX = item.width * layoutScale / obj.width;
2449
+ if (Math.abs(obj.scaleX - targetScaleX) > 1e-3) updates.scaleX = targetScaleX;
2450
+ }
2451
+ if (item.height !== void 0 && obj.height) {
2452
+ const targetScaleY = item.height * layoutScale / obj.height;
2453
+ if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
2454
+ }
2455
+ if (obj.originX !== "center") {
2456
+ updates.originX = "center";
2457
+ updates.originY = "center";
2458
+ }
2459
+ if (Object.keys(updates).length > 0) {
2460
+ obj.set(updates);
2461
+ }
2462
+ }
2463
+ loadImage(item, layer, layout) {
2464
+ import_fabric5.Image.fromURL(item.url, { crossOrigin: "anonymous" }).then((image) => {
2465
+ var _a;
2466
+ if (!this.items.find((i) => i.id === item.id)) return;
2350
2467
  image.set({
2351
- opacity: opacity !== void 0 ? opacity : 1,
2352
- data: {
2353
- id: "user-image"
2354
- }
2468
+ originX: "center",
2469
+ originY: "center",
2470
+ data: { id: item.id },
2471
+ uniformScaling: true,
2472
+ lockScalingFlip: true
2473
+ });
2474
+ image.setControlsVisibility({
2475
+ mt: false,
2476
+ mb: false,
2477
+ ml: false,
2478
+ mr: false
2355
2479
  });
2480
+ let { width, height, left, top } = item;
2481
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, dielinePhysicalWidth, dielinePhysicalHeight, bleedOffset } = layout;
2482
+ if (width === void 0 && height === void 0) {
2483
+ const targetWidth = dielinePhysicalWidth + 2 * bleedOffset;
2484
+ const targetHeight = dielinePhysicalHeight + 2 * bleedOffset;
2485
+ const targetMax = Math.max(targetWidth, targetHeight);
2486
+ const imageMax = Math.max(image.width || 1, image.height || 1);
2487
+ const scale = targetMax / imageMax;
2488
+ width = (image.width || 1) * scale;
2489
+ height = (image.height || 1) * scale;
2490
+ item.width = width;
2491
+ item.height = height;
2492
+ }
2493
+ if (left === void 0 && top === void 0) {
2494
+ left = 0.5;
2495
+ top = 0.5;
2496
+ item.left = left;
2497
+ item.top = top;
2498
+ }
2499
+ this.updateObjectProperties(image, item, layout);
2356
2500
  layer.add(image);
2501
+ this.objectMap.set(item.id, image);
2357
2502
  image.on("modified", (e) => {
2358
- var _a2, _b2;
2359
- const matrix = image.calcTransformMatrix();
2360
- const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2361
- const canvasW = ((_a2 = this.canvasService) == null ? void 0 : _a2.canvas.width) || 800;
2362
- const canvasH = ((_b2 = this.canvasService) == null ? void 0 : _b2.canvas.height) || 600;
2363
- this.left = Coordinate.toNormalized(globalPoint.x, canvasW);
2364
- this.top = Coordinate.toNormalized(globalPoint.y, canvasH);
2365
- this.angle = e.target.angle;
2366
- if (image.width) this.width = e.target.width * e.target.scaleX;
2367
- if (image.height) this.height = e.target.height * e.target.scaleY;
2368
- if (this.context) {
2369
- this.context.eventBus.emit("update");
2370
- }
2503
+ this.handleObjectModified(item.id, image);
2371
2504
  });
2372
2505
  layer.dirty = true;
2373
- this.canvasService.requestRenderAll();
2506
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2507
+ if (item.width !== width || item.height !== height || item.left !== left || item.top !== top) {
2508
+ this.updateImageInConfig(item.id, { width, height, left, top });
2509
+ }
2374
2510
  }).catch((err) => {
2375
- if (this._loadingUrl === url) this._loadingUrl = null;
2376
- console.error("Failed to load image", url, err);
2511
+ console.error("Failed to load image", item.url, err);
2377
2512
  });
2378
2513
  }
2514
+ handleObjectModified(id, image) {
2515
+ const layout = this.getLayoutInfo();
2516
+ const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2517
+ const matrix = image.calcTransformMatrix();
2518
+ const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2519
+ const updates = {};
2520
+ updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
2521
+ updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
2522
+ updates.angle = image.angle;
2523
+ if (image.width) {
2524
+ const pixelWidth = image.width * image.scaleX;
2525
+ updates.width = pixelWidth / layoutScale;
2526
+ }
2527
+ if (image.height) {
2528
+ const pixelHeight = image.height * image.scaleY;
2529
+ updates.height = pixelHeight / layoutScale;
2530
+ }
2531
+ this.updateImageInConfig(id, updates);
2532
+ }
2533
+ updateImageInConfig(id, updates) {
2534
+ const index = this.items.findIndex((i) => i.id === id);
2535
+ if (index !== -1) {
2536
+ const newItems = [...this.items];
2537
+ newItems[index] = { ...newItems[index], ...updates };
2538
+ this.updateConfig(newItems, true);
2539
+ }
2540
+ }
2379
2541
  };
2380
2542
 
2381
2543
  // src/white-ink.ts
@@ -2678,12 +2840,18 @@ var RulerTool = class {
2678
2840
  this.metadata = {
2679
2841
  name: "RulerTool"
2680
2842
  };
2681
- this.unit = "px";
2682
2843
  this.thickness = 20;
2844
+ this.gap = 15;
2683
2845
  this.backgroundColor = "#f0f0f0";
2684
2846
  this.textColor = "#333333";
2685
2847
  this.lineColor = "#999999";
2686
2848
  this.fontSize = 10;
2849
+ // Dieline context for sync
2850
+ this.dielineWidth = 500;
2851
+ this.dielineHeight = 500;
2852
+ this.dielineUnit = "mm";
2853
+ this.dielinePadding = 40;
2854
+ this.dielineOffset = 0;
2687
2855
  if (options) {
2688
2856
  Object.assign(this, options);
2689
2857
  }
@@ -2696,8 +2864,8 @@ var RulerTool = class {
2696
2864
  }
2697
2865
  const configService = context.services.get("ConfigurationService");
2698
2866
  if (configService) {
2699
- this.unit = configService.get("ruler.unit", this.unit);
2700
2867
  this.thickness = configService.get("ruler.thickness", this.thickness);
2868
+ this.gap = configService.get("ruler.gap", this.gap);
2701
2869
  this.backgroundColor = configService.get(
2702
2870
  "ruler.backgroundColor",
2703
2871
  this.backgroundColor
@@ -2705,13 +2873,38 @@ var RulerTool = class {
2705
2873
  this.textColor = configService.get("ruler.textColor", this.textColor);
2706
2874
  this.lineColor = configService.get("ruler.lineColor", this.lineColor);
2707
2875
  this.fontSize = configService.get("ruler.fontSize", this.fontSize);
2876
+ this.dielineUnit = configService.get("dieline.unit", this.dielineUnit);
2877
+ this.dielineWidth = configService.get("dieline.width", this.dielineWidth);
2878
+ this.dielineHeight = configService.get(
2879
+ "dieline.height",
2880
+ this.dielineHeight
2881
+ );
2882
+ this.dielinePadding = configService.get(
2883
+ "dieline.padding",
2884
+ this.dielinePadding
2885
+ );
2886
+ this.dielineOffset = configService.get(
2887
+ "dieline.offset",
2888
+ this.dielineOffset
2889
+ );
2708
2890
  configService.onAnyChange((e) => {
2891
+ let shouldUpdate = false;
2709
2892
  if (e.key.startsWith("ruler.")) {
2710
2893
  const prop = e.key.split(".")[1];
2711
2894
  if (prop && prop in this) {
2712
2895
  this[prop] = e.value;
2713
- this.updateRuler();
2896
+ shouldUpdate = true;
2714
2897
  }
2898
+ } else if (e.key.startsWith("dieline.")) {
2899
+ if (e.key === "dieline.unit") this.dielineUnit = e.value;
2900
+ if (e.key === "dieline.width") this.dielineWidth = e.value;
2901
+ if (e.key === "dieline.height") this.dielineHeight = e.value;
2902
+ if (e.key === "dieline.padding") this.dielinePadding = e.value;
2903
+ if (e.key === "dieline.offset") this.dielineOffset = e.value;
2904
+ shouldUpdate = true;
2905
+ }
2906
+ if (shouldUpdate) {
2907
+ this.updateRuler();
2715
2908
  }
2716
2909
  });
2717
2910
  }
@@ -2725,13 +2918,6 @@ var RulerTool = class {
2725
2918
  contribute() {
2726
2919
  return {
2727
2920
  [import_core7.ContributionPointIds.CONFIGURATIONS]: [
2728
- {
2729
- id: "ruler.unit",
2730
- type: "select",
2731
- label: "Unit",
2732
- options: ["px", "mm", "cm", "in"],
2733
- default: "px"
2734
- },
2735
2921
  {
2736
2922
  id: "ruler.thickness",
2737
2923
  type: "number",
@@ -2740,6 +2926,14 @@ var RulerTool = class {
2740
2926
  max: 100,
2741
2927
  default: 20
2742
2928
  },
2929
+ {
2930
+ id: "ruler.gap",
2931
+ type: "number",
2932
+ label: "Gap",
2933
+ min: 0,
2934
+ max: 100,
2935
+ default: 15
2936
+ },
2743
2937
  {
2744
2938
  id: "ruler.backgroundColor",
2745
2939
  type: "color",
@@ -2768,16 +2962,6 @@ var RulerTool = class {
2768
2962
  }
2769
2963
  ],
2770
2964
  [import_core7.ContributionPointIds.COMMANDS]: [
2771
- {
2772
- command: "setUnit",
2773
- title: "Set Ruler Unit",
2774
- handler: (unit) => {
2775
- if (this.unit === unit) return true;
2776
- this.unit = unit;
2777
- this.updateRuler();
2778
- return true;
2779
- }
2780
- },
2781
2965
  {
2782
2966
  command: "setTheme",
2783
2967
  title: "Set Ruler Theme",
@@ -2828,6 +3012,68 @@ var RulerTool = class {
2828
3012
  this.canvasService.canvas.remove(layer);
2829
3013
  }
2830
3014
  }
3015
+ createArrowLine(x1, y1, x2, y2, color) {
3016
+ const line = new import_fabric7.Line([x1, y1, x2, y2], {
3017
+ stroke: color,
3018
+ strokeWidth: this.thickness / 20,
3019
+ // Scale stroke width relative to thickness (default 1)
3020
+ selectable: false,
3021
+ evented: false
3022
+ });
3023
+ const arrowSize = Math.max(4, this.thickness * 0.3);
3024
+ const angle = Math.atan2(y2 - y1, x2 - x1);
3025
+ const endArrow = new import_fabric7.Polygon(
3026
+ [
3027
+ { x: 0, y: 0 },
3028
+ { x: -arrowSize, y: -arrowSize / 2 },
3029
+ { x: -arrowSize, y: arrowSize / 2 }
3030
+ ],
3031
+ {
3032
+ fill: color,
3033
+ left: x2,
3034
+ top: y2,
3035
+ originX: "right",
3036
+ originY: "center",
3037
+ angle: angle * 180 / Math.PI,
3038
+ selectable: false,
3039
+ evented: false
3040
+ }
3041
+ );
3042
+ const startArrow = new import_fabric7.Polygon(
3043
+ [
3044
+ { x: 0, y: 0 },
3045
+ { x: arrowSize, y: -arrowSize / 2 },
3046
+ { x: arrowSize, y: arrowSize / 2 }
3047
+ ],
3048
+ {
3049
+ fill: color,
3050
+ left: x1,
3051
+ top: y1,
3052
+ originX: "left",
3053
+ originY: "center",
3054
+ angle: angle * 180 / Math.PI,
3055
+ selectable: false,
3056
+ evented: false
3057
+ }
3058
+ );
3059
+ return new import_fabric7.Group([line, startArrow, endArrow], {
3060
+ selectable: false,
3061
+ evented: false
3062
+ });
3063
+ }
3064
+ resolvePadding(containerWidth, containerHeight) {
3065
+ if (typeof this.dielinePadding === "number") {
3066
+ return this.dielinePadding;
3067
+ }
3068
+ if (typeof this.dielinePadding === "string") {
3069
+ if (this.dielinePadding.endsWith("%")) {
3070
+ const percent = parseFloat(this.dielinePadding) / 100;
3071
+ return Math.min(containerWidth, containerHeight) * percent;
3072
+ }
3073
+ return parseFloat(this.dielinePadding) || 0;
3074
+ }
3075
+ return 0;
3076
+ }
2831
3077
  updateRuler() {
2832
3078
  if (!this.canvasService) return;
2833
3079
  const layer = this.getLayer();
@@ -2836,95 +3082,141 @@ var RulerTool = class {
2836
3082
  const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
2837
3083
  const width = this.canvasService.canvas.width || 800;
2838
3084
  const height = this.canvasService.canvas.height || 600;
2839
- const topBg = new import_fabric7.Rect({
2840
- left: 0,
2841
- top: 0,
2842
- width,
2843
- height: thickness,
2844
- fill: backgroundColor,
2845
- selectable: false,
2846
- evented: false
2847
- });
2848
- const leftBg = new import_fabric7.Rect({
2849
- left: 0,
2850
- top: 0,
2851
- width: thickness,
2852
- height,
2853
- fill: backgroundColor,
2854
- selectable: false,
2855
- evented: false
2856
- });
2857
- const cornerBg = new import_fabric7.Rect({
2858
- left: 0,
2859
- top: 0,
2860
- width: thickness,
2861
- height: thickness,
2862
- fill: backgroundColor,
2863
- stroke: lineColor,
2864
- strokeWidth: 1,
3085
+ const paddingPx = this.resolvePadding(width, height);
3086
+ const layout = Coordinate.calculateLayout(
3087
+ { width, height },
3088
+ { width: this.dielineWidth, height: this.dielineHeight },
3089
+ paddingPx
3090
+ );
3091
+ const scale = layout.scale;
3092
+ const offsetX = layout.offsetX;
3093
+ const offsetY = layout.offsetY;
3094
+ const visualWidth = layout.width;
3095
+ const visualHeight = layout.height;
3096
+ const rawOffset = this.dielineOffset || 0;
3097
+ const effectiveOffset = rawOffset > 0 ? rawOffset : 0;
3098
+ const expandPixels = effectiveOffset * scale;
3099
+ const gap = this.gap || 15;
3100
+ const rulerLeft = offsetX - expandPixels;
3101
+ const rulerTop = offsetY - expandPixels;
3102
+ const rulerRight = offsetX + visualWidth + expandPixels;
3103
+ const rulerBottom = offsetY + visualHeight + expandPixels;
3104
+ const displayWidth = this.dielineWidth + effectiveOffset * 2;
3105
+ const displayHeight = this.dielineHeight + effectiveOffset * 2;
3106
+ const topRulerY = rulerTop - gap;
3107
+ const topRulerXStart = rulerLeft;
3108
+ const topRulerXEnd = rulerRight;
3109
+ const leftRulerX = rulerLeft - gap;
3110
+ const leftRulerYStart = rulerTop;
3111
+ const leftRulerYEnd = rulerBottom;
3112
+ const topDimLine = this.createArrowLine(
3113
+ topRulerXStart,
3114
+ topRulerY,
3115
+ topRulerXEnd,
3116
+ topRulerY,
3117
+ lineColor
3118
+ );
3119
+ layer.add(topDimLine);
3120
+ const extLen = 5;
3121
+ layer.add(
3122
+ new import_fabric7.Line(
3123
+ [
3124
+ topRulerXStart,
3125
+ topRulerY - extLen,
3126
+ topRulerXStart,
3127
+ topRulerY + extLen
3128
+ ],
3129
+ {
3130
+ stroke: lineColor,
3131
+ strokeWidth: 1,
3132
+ selectable: false,
3133
+ evented: false
3134
+ }
3135
+ )
3136
+ );
3137
+ layer.add(
3138
+ new import_fabric7.Line(
3139
+ [topRulerXEnd, topRulerY - extLen, topRulerXEnd, topRulerY + extLen],
3140
+ {
3141
+ stroke: lineColor,
3142
+ strokeWidth: 1,
3143
+ selectable: false,
3144
+ evented: false
3145
+ }
3146
+ )
3147
+ );
3148
+ const widthStr = parseFloat(displayWidth.toFixed(2)).toString();
3149
+ const topTextContent = `${widthStr} ${this.dielineUnit}`;
3150
+ const topText = new import_fabric7.Text(topTextContent, {
3151
+ left: topRulerXStart + (rulerRight - rulerLeft) / 2,
3152
+ top: topRulerY,
3153
+ fontSize,
3154
+ fill: textColor,
3155
+ fontFamily: "Arial",
3156
+ originX: "center",
3157
+ originY: "center",
3158
+ backgroundColor,
3159
+ // Background mask for readability
2865
3160
  selectable: false,
2866
3161
  evented: false
2867
3162
  });
2868
- layer.add(topBg);
2869
- layer.add(leftBg);
2870
- layer.add(cornerBg);
2871
- const step = 100;
2872
- const subStep = 10;
2873
- const midStep = 50;
2874
- for (let x = 0; x <= width; x += subStep) {
2875
- if (x < thickness) continue;
2876
- let len = thickness * 0.25;
2877
- if (x % step === 0) len = thickness * 0.8;
2878
- else if (x % midStep === 0) len = thickness * 0.5;
2879
- const line = new import_fabric7.Line([x, thickness - len, x, thickness], {
2880
- stroke: lineColor,
2881
- strokeWidth: 1,
2882
- selectable: false,
2883
- evented: false
2884
- });
2885
- layer.add(line);
2886
- if (x % step === 0) {
2887
- const text = new import_fabric7.Text(x.toString(), {
2888
- left: x + 2,
2889
- top: 2,
2890
- fontSize,
2891
- fill: textColor,
2892
- fontFamily: "Arial",
3163
+ layer.add(topText);
3164
+ const leftDimLine = this.createArrowLine(
3165
+ leftRulerX,
3166
+ leftRulerYStart,
3167
+ leftRulerX,
3168
+ leftRulerYEnd,
3169
+ lineColor
3170
+ );
3171
+ layer.add(leftDimLine);
3172
+ layer.add(
3173
+ new import_fabric7.Line(
3174
+ [
3175
+ leftRulerX - extLen,
3176
+ leftRulerYStart,
3177
+ leftRulerX + extLen,
3178
+ leftRulerYStart
3179
+ ],
3180
+ {
3181
+ stroke: lineColor,
3182
+ strokeWidth: 1,
2893
3183
  selectable: false,
2894
3184
  evented: false
2895
- });
2896
- layer.add(text);
2897
- }
2898
- }
2899
- for (let y = 0; y <= height; y += subStep) {
2900
- if (y < thickness) continue;
2901
- let len = thickness * 0.25;
2902
- if (y % step === 0) len = thickness * 0.8;
2903
- else if (y % midStep === 0) len = thickness * 0.5;
2904
- const line = new import_fabric7.Line([thickness - len, y, thickness, y], {
2905
- stroke: lineColor,
2906
- strokeWidth: 1,
2907
- selectable: false,
2908
- evented: false
2909
- });
2910
- layer.add(line);
2911
- if (y % step === 0) {
2912
- const text = new import_fabric7.Text(y.toString(), {
2913
- angle: -90,
2914
- left: thickness / 2 - fontSize / 3,
2915
- // approximate centering
2916
- top: y + fontSize,
2917
- fontSize,
2918
- fill: textColor,
2919
- fontFamily: "Arial",
2920
- originX: "center",
2921
- originY: "center",
3185
+ }
3186
+ )
3187
+ );
3188
+ layer.add(
3189
+ new import_fabric7.Line(
3190
+ [
3191
+ leftRulerX - extLen,
3192
+ leftRulerYEnd,
3193
+ leftRulerX + extLen,
3194
+ leftRulerYEnd
3195
+ ],
3196
+ {
3197
+ stroke: lineColor,
3198
+ strokeWidth: 1,
2922
3199
  selectable: false,
2923
3200
  evented: false
2924
- });
2925
- layer.add(text);
2926
- }
2927
- }
3201
+ }
3202
+ )
3203
+ );
3204
+ const heightStr = parseFloat(displayHeight.toFixed(2)).toString();
3205
+ const leftTextContent = `${heightStr} ${this.dielineUnit}`;
3206
+ const leftText = new import_fabric7.Text(leftTextContent, {
3207
+ left: leftRulerX,
3208
+ top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
3209
+ angle: -90,
3210
+ fontSize,
3211
+ fill: textColor,
3212
+ fontFamily: "Arial",
3213
+ originX: "center",
3214
+ originY: "center",
3215
+ backgroundColor,
3216
+ selectable: false,
3217
+ evented: false
3218
+ });
3219
+ layer.add(leftText);
2928
3220
  this.canvasService.canvas.bringObjectToFront(layer);
2929
3221
  this.canvasService.canvas.requestRenderAll();
2930
3222
  }