@pooder/kit 3.2.0 → 3.4.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
@@ -255,7 +255,7 @@ var ImageTracer = class {
255
255
  * @param options Configuration options.
256
256
  */
257
257
  static async trace(imageUrl, options = {}) {
258
- var _a, _b;
258
+ var _a, _b, _c, _d, _e;
259
259
  const img = await this.loadImage(imageUrl);
260
260
  const width = img.width;
261
261
  const height = img.height;
@@ -266,21 +266,213 @@ var ImageTracer = class {
266
266
  if (!ctx) throw new Error("Could not get 2D context");
267
267
  ctx.drawImage(img, 0, 0);
268
268
  const imageData = ctx.getImageData(0, 0, width, height);
269
- const points = this.marchingSquares(imageData, (_a = options.threshold) != null ? _a : 10);
270
- let finalPoints = points;
271
- if (options.scaleToWidth && options.scaleToHeight && points.length > 0) {
269
+ const threshold = (_a = options.threshold) != null ? _a : 10;
270
+ const adaptiveRadius = Math.max(
271
+ 5,
272
+ Math.floor(Math.max(width, height) * 0.02)
273
+ );
274
+ const radius = (_b = options.morphologyRadius) != null ? _b : adaptiveRadius;
275
+ let mask = this.createMask(imageData, threshold);
276
+ if (radius > 0) {
277
+ mask = this.dilate(mask, width, height, radius);
278
+ mask = this.erode(mask, width, height, radius);
279
+ mask = this.fillHoles(mask, width, height);
280
+ }
281
+ const allContourPoints = this.traceAllContours(mask, width, height);
282
+ if (allContourPoints.length === 0) {
283
+ const w = (_c = options.scaleToWidth) != null ? _c : width;
284
+ const h = (_d = options.scaleToHeight) != null ? _d : height;
285
+ return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
286
+ }
287
+ const primaryContour = allContourPoints.sort(
288
+ (a, b) => b.length - a.length
289
+ )[0];
290
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
291
+ for (const p of primaryContour) {
292
+ if (p.x < minX) minX = p.x;
293
+ if (p.y < minY) minY = p.y;
294
+ if (p.x > maxX) maxX = p.x;
295
+ if (p.y > maxY) maxY = p.y;
296
+ }
297
+ const globalBounds = {
298
+ minX,
299
+ minY,
300
+ width: maxX - minX,
301
+ height: maxY - minY
302
+ };
303
+ let finalPoints = primaryContour;
304
+ if (options.scaleToWidth && options.scaleToHeight) {
272
305
  finalPoints = this.scalePoints(
273
- points,
306
+ primaryContour,
274
307
  options.scaleToWidth,
275
- options.scaleToHeight
308
+ options.scaleToHeight,
309
+ globalBounds
276
310
  );
277
311
  }
278
312
  const simplifiedPoints = this.douglasPeucker(
279
313
  finalPoints,
280
- (_b = options.simplifyTolerance) != null ? _b : 0.5
314
+ (_e = options.simplifyTolerance) != null ? _e : 2
281
315
  );
282
316
  return this.pointsToSVG(simplifiedPoints);
283
317
  }
318
+ static createMask(imageData, threshold) {
319
+ const { width, height, data } = imageData;
320
+ const mask = new Uint8Array(width * height);
321
+ for (let i = 0; i < width * height; i++) {
322
+ const idx = i * 4;
323
+ const r = data[idx];
324
+ const g = data[idx + 1];
325
+ const b = data[idx + 2];
326
+ const a = data[idx + 3];
327
+ if (a > threshold && !(r > 240 && g > 240 && b > 240)) {
328
+ mask[i] = 1;
329
+ } else {
330
+ mask[i] = 0;
331
+ }
332
+ }
333
+ return mask;
334
+ }
335
+ /**
336
+ * Fast 1D-separable Dilation
337
+ */
338
+ static dilate(mask, width, height, radius) {
339
+ const horizontal = new Uint8Array(width * height);
340
+ for (let y = 0; y < height; y++) {
341
+ let count = 0;
342
+ for (let x = -radius; x < width; x++) {
343
+ if (x + radius < width && mask[y * width + x + radius]) count++;
344
+ if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
345
+ if (x >= 0) horizontal[y * width + x] = count > 0 ? 1 : 0;
346
+ }
347
+ }
348
+ const vertical = new Uint8Array(width * height);
349
+ for (let x = 0; x < width; x++) {
350
+ let count = 0;
351
+ for (let y = -radius; y < height; y++) {
352
+ if (y + radius < height && horizontal[(y + radius) * width + x])
353
+ count++;
354
+ if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
355
+ count--;
356
+ if (y >= 0) vertical[y * width + x] = count > 0 ? 1 : 0;
357
+ }
358
+ }
359
+ return vertical;
360
+ }
361
+ /**
362
+ * Fast 1D-separable Erosion
363
+ */
364
+ static erode(mask, width, height, radius) {
365
+ const horizontal = new Uint8Array(width * height);
366
+ for (let y = 0; y < height; y++) {
367
+ let count = 0;
368
+ for (let x = -radius; x < width; x++) {
369
+ if (x + radius < width && mask[y * width + x + radius]) count++;
370
+ if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
371
+ if (x >= 0) {
372
+ const winWidth = Math.min(x + radius, width - 1) - Math.max(x - radius, 0) + 1;
373
+ horizontal[y * width + x] = count === winWidth ? 1 : 0;
374
+ }
375
+ }
376
+ }
377
+ const vertical = new Uint8Array(width * height);
378
+ for (let x = 0; x < width; x++) {
379
+ let count = 0;
380
+ for (let y = -radius; y < height; y++) {
381
+ if (y + radius < height && horizontal[(y + radius) * width + x])
382
+ count++;
383
+ if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
384
+ count--;
385
+ if (y >= 0) {
386
+ const winHeight = Math.min(y + radius, height - 1) - Math.max(y - radius, 0) + 1;
387
+ vertical[y * width + x] = count === winHeight ? 1 : 0;
388
+ }
389
+ }
390
+ }
391
+ return vertical;
392
+ }
393
+ /**
394
+ * Fills internal holes in the binary mask using flood fill from edges.
395
+ */
396
+ static fillHoles(mask, width, height) {
397
+ const background = new Uint8Array(width * height);
398
+ const queue = [];
399
+ for (let x = 0; x < width; x++) {
400
+ if (mask[x] === 0) {
401
+ background[x] = 1;
402
+ queue.push([x, 0]);
403
+ }
404
+ const lastRow = (height - 1) * width + x;
405
+ if (mask[lastRow] === 0) {
406
+ background[lastRow] = 1;
407
+ queue.push([x, height - 1]);
408
+ }
409
+ }
410
+ for (let y = 1; y < height - 1; y++) {
411
+ if (mask[y * width] === 0) {
412
+ background[y * width] = 1;
413
+ queue.push([0, y]);
414
+ }
415
+ if (mask[y * width + width - 1] === 0) {
416
+ background[y * width + width - 1] = 1;
417
+ queue.push([width - 1, y]);
418
+ }
419
+ }
420
+ const dirs = [
421
+ [0, 1],
422
+ [0, -1],
423
+ [1, 0],
424
+ [-1, 0]
425
+ ];
426
+ let head = 0;
427
+ while (head < queue.length) {
428
+ const [cx, cy] = queue[head++];
429
+ for (const [dx, dy] of dirs) {
430
+ const nx = cx + dx;
431
+ const ny = cy + dy;
432
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
433
+ const nidx = ny * width + nx;
434
+ if (mask[nidx] === 0 && background[nidx] === 0) {
435
+ background[nidx] = 1;
436
+ queue.push([nx, ny]);
437
+ }
438
+ }
439
+ }
440
+ }
441
+ const filledMask = new Uint8Array(width * height);
442
+ for (let i = 0; i < width * height; i++) {
443
+ filledMask[i] = background[i] === 0 ? 1 : 0;
444
+ }
445
+ return filledMask;
446
+ }
447
+ /**
448
+ * Traces all contours in the mask with optimized start-point detection
449
+ */
450
+ static traceAllContours(mask, width, height) {
451
+ const visited = new Uint8Array(width * height);
452
+ const allContours = [];
453
+ for (let y = 0; y < height; y++) {
454
+ for (let x = 0; x < width; x++) {
455
+ const idx = y * width + x;
456
+ if (mask[idx] && !visited[idx]) {
457
+ const isLeftEdge = x === 0 || mask[idx - 1] === 0;
458
+ if (isLeftEdge) {
459
+ const contour = this.marchingSquares(
460
+ mask,
461
+ visited,
462
+ x,
463
+ y,
464
+ width,
465
+ height
466
+ );
467
+ if (contour.length > 2) {
468
+ allContours.push(contour);
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ return allContours;
475
+ }
284
476
  static loadImage(url) {
285
477
  return new Promise((resolve, reject) => {
286
478
  const img = new Image();
@@ -294,33 +486,11 @@ var ImageTracer = class {
294
486
  * Moore-Neighbor Tracing Algorithm
295
487
  * More robust for irregular shapes than simple Marching Squares walker.
296
488
  */
297
- static marchingSquares(imageData, alphaThreshold) {
298
- const width = imageData.width;
299
- const height = imageData.height;
300
- const data = imageData.data;
489
+ static marchingSquares(mask, visited, startX, startY, width, height) {
301
490
  const isSolid = (x, y) => {
302
491
  if (x < 0 || x >= width || y < 0 || y >= height) return false;
303
- const index = (y * width + x) * 4;
304
- const r = data[index];
305
- const g = data[index + 1];
306
- const b = data[index + 2];
307
- const a = data[index + 3];
308
- if (a <= alphaThreshold) return false;
309
- if (r > 240 && g > 240 && b > 240) return false;
310
- return true;
492
+ return mask[y * width + x] === 1;
311
493
  };
312
- let startX = -1;
313
- let startY = -1;
314
- searchLoop: for (let y = 0; y < height; y++) {
315
- for (let x = 0; x < width; x++) {
316
- if (isSolid(x, y)) {
317
- startX = x;
318
- startY = y;
319
- break searchLoop;
320
- }
321
- }
322
- }
323
- if (startX === -1) return [];
324
494
  const points = [];
325
495
  let cx = startX;
326
496
  let cy = startY;
@@ -339,6 +509,7 @@ var ImageTracer = class {
339
509
  let steps = 0;
340
510
  do {
341
511
  points.push({ x: cx, y: cy });
512
+ visited[cy * width + cx] = 1;
342
513
  let found = false;
343
514
  for (let i = 0; i < 8; i++) {
344
515
  const idx = (backtrack + 1 + i) % 8;
@@ -347,16 +518,12 @@ var ImageTracer = class {
347
518
  if (isSolid(nx, ny)) {
348
519
  cx = nx;
349
520
  cy = ny;
350
- backtrack = (idx + 4) % 8;
351
- backtrack = (idx + 4 + 1) % 8;
352
521
  backtrack = (idx + 4 + 1) % 8;
353
522
  found = true;
354
523
  break;
355
524
  }
356
525
  }
357
- if (!found) {
358
- break;
359
- }
526
+ if (!found) break;
360
527
  steps++;
361
528
  } while ((cx !== startX || cy !== startY) && steps < maxSteps);
362
529
  return points;
@@ -405,23 +572,14 @@ var ImageTracer = class {
405
572
  dy = p.y - y;
406
573
  return dx * dx + dy * dy;
407
574
  }
408
- static scalePoints(points, targetWidth, targetHeight) {
575
+ static scalePoints(points, targetWidth, targetHeight, bounds) {
409
576
  if (points.length === 0) return points;
410
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
411
- for (const p of points) {
412
- if (p.x < minX) minX = p.x;
413
- if (p.y < minY) minY = p.y;
414
- if (p.x > maxX) maxX = p.x;
415
- if (p.y > maxY) maxY = p.y;
416
- }
417
- const srcW = maxX - minX;
418
- const srcH = maxY - minY;
419
- if (srcW === 0 || srcH === 0) return points;
420
- const scaleX = targetWidth / srcW;
421
- const scaleY = targetHeight / srcH;
577
+ if (bounds.width === 0 || bounds.height === 0) return points;
578
+ const scaleX = targetWidth / bounds.width;
579
+ const scaleY = targetHeight / bounds.height;
422
580
  return points.map((p) => ({
423
- x: (p.x - minX) * scaleX,
424
- y: (p.y - minY) * scaleY
581
+ x: (p.x - bounds.minX) * scaleX,
582
+ y: (p.y - bounds.minY) * scaleY
425
583
  }));
426
584
  }
427
585
  static pointsToSVG(points) {
@@ -614,12 +772,25 @@ function getDielineShape(options) {
614
772
  let lugsPath = null;
615
773
  let cutsPath = null;
616
774
  holes.forEach((hole) => {
617
- const lug = new import_paper.default.Path.Circle({
618
- center: [hole.x, hole.y],
775
+ const center = new import_paper.default.Point(hole.x, hole.y);
776
+ const lug = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
777
+ point: [
778
+ center.x - hole.outerRadius,
779
+ center.y - hole.outerRadius
780
+ ],
781
+ size: [hole.outerRadius * 2, hole.outerRadius * 2]
782
+ }) : new import_paper.default.Path.Circle({
783
+ center,
619
784
  radius: hole.outerRadius
620
785
  });
621
- const cut = new import_paper.default.Path.Circle({
622
- center: [hole.x, hole.y],
786
+ const cut = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
787
+ point: [
788
+ center.x - hole.innerRadius,
789
+ center.y - hole.innerRadius
790
+ ],
791
+ size: [hole.innerRadius * 2, hole.innerRadius * 2]
792
+ }) : new import_paper.default.Path.Circle({
793
+ center,
623
794
  radius: hole.innerRadius
624
795
  });
625
796
  if (!lugsPath) {
@@ -676,7 +847,9 @@ function getDielineShape(options) {
676
847
  return mainShape;
677
848
  }
678
849
  function generateDielinePath(options) {
679
- ensurePaper(options.width * 2, options.height * 2);
850
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
851
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
852
+ ensurePaper(paperWidth, paperHeight);
680
853
  import_paper.default.project.activeLayer.removeChildren();
681
854
  const mainShape = getDielineShape(options);
682
855
  const pathData = mainShape.pathData;
@@ -700,8 +873,9 @@ function generateMaskPath(options) {
700
873
  return pathData;
701
874
  }
702
875
  function generateBleedZonePath(options, offset) {
703
- const maxDim = Math.max(options.width, options.height) + Math.abs(offset) * 4;
704
- ensurePaper(maxDim, maxDim);
876
+ const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
877
+ const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
878
+ ensurePaper(paperWidth, paperHeight);
705
879
  import_paper.default.project.activeLayer.removeChildren();
706
880
  const shapeOriginal = getDielineShape(options);
707
881
  let shapeOffset;
@@ -1145,7 +1319,9 @@ var DielineTool = class {
1145
1319
  x: cx,
1146
1320
  y: cy,
1147
1321
  holes: absoluteHoles,
1148
- pathData: this.pathData
1322
+ pathData: this.pathData,
1323
+ canvasWidth: canvasW,
1324
+ canvasHeight: canvasH
1149
1325
  });
1150
1326
  const insideObj = new import_fabric2.Path(productPathData, {
1151
1327
  fill: insideColor,
@@ -1168,7 +1344,9 @@ var DielineTool = class {
1168
1344
  x: cx,
1169
1345
  y: cy,
1170
1346
  holes: absoluteHoles,
1171
- pathData: this.pathData
1347
+ pathData: this.pathData,
1348
+ canvasWidth: canvasW,
1349
+ canvasHeight: canvasH
1172
1350
  },
1173
1351
  visualOffset
1174
1352
  );
@@ -1195,7 +1373,9 @@ var DielineTool = class {
1195
1373
  x: cx,
1196
1374
  y: cy,
1197
1375
  holes: absoluteHoles,
1198
- pathData: this.pathData
1376
+ pathData: this.pathData,
1377
+ canvasWidth: canvasW,
1378
+ canvasHeight: canvasH
1199
1379
  });
1200
1380
  const offsetBorderObj = new import_fabric2.Path(offsetPathData, {
1201
1381
  fill: null,
@@ -1219,7 +1399,9 @@ var DielineTool = class {
1219
1399
  x: cx,
1220
1400
  y: cy,
1221
1401
  holes: absoluteHoles,
1222
- pathData: this.pathData
1402
+ pathData: this.pathData,
1403
+ canvasWidth: canvasW,
1404
+ canvasHeight: canvasH
1223
1405
  });
1224
1406
  const borderObj = new import_fabric2.Path(borderPathData, {
1225
1407
  fill: "transparent",
@@ -1334,7 +1516,9 @@ var DielineTool = class {
1334
1516
  x: cx,
1335
1517
  y: cy,
1336
1518
  holes: absoluteHoles,
1337
- pathData: this.pathData
1519
+ pathData: this.pathData,
1520
+ canvasWidth: canvasW,
1521
+ canvasHeight: canvasH
1338
1522
  });
1339
1523
  const clonedLayer = await userLayer.clone();
1340
1524
  const clipPath = new import_fabric2.Path(pathData, {
@@ -1627,7 +1811,7 @@ var HoleTool = class {
1627
1811
  command: "addHole",
1628
1812
  title: "Add Hole",
1629
1813
  handler: (x, y) => {
1630
- var _a, _b, _c;
1814
+ var _a, _b, _c, _d;
1631
1815
  if (!this.canvasService) return false;
1632
1816
  let normalizedX = 0.5;
1633
1817
  let normalizedY = 0.5;
@@ -1650,9 +1834,11 @@ var HoleTool = class {
1650
1834
  const lastHole = currentHoles[currentHoles.length - 1];
1651
1835
  const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
1652
1836
  const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
1837
+ const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
1653
1838
  const newHole = {
1654
1839
  x: normalizedX,
1655
1840
  y: normalizedY,
1841
+ shape,
1656
1842
  innerRadius,
1657
1843
  outerRadius
1658
1844
  };
@@ -1684,6 +1870,7 @@ var HoleTool = class {
1684
1870
  if (!this.handleDielineChange) {
1685
1871
  this.handleDielineChange = (geometry) => {
1686
1872
  this.currentGeometry = geometry;
1873
+ this.redraw();
1687
1874
  const changed = this.enforceConstraints();
1688
1875
  if (changed) {
1689
1876
  this.syncHolesToDieline();
@@ -1797,10 +1984,16 @@ var HoleTool = class {
1797
1984
  }
1798
1985
  syncHolesFromCanvas() {
1799
1986
  if (!this.canvasService) return;
1800
- const objects = this.canvasService.canvas.getObjects().filter((obj) => {
1801
- var _a;
1802
- return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1803
- });
1987
+ const objects = this.canvasService.canvas.getObjects().filter(
1988
+ (obj) => {
1989
+ var _a;
1990
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
1991
+ }
1992
+ );
1993
+ if (objects.length === 0 && this.holes.length > 0) {
1994
+ console.warn("HoleTool: No markers found on canvas to sync from");
1995
+ return;
1996
+ }
1804
1997
  objects.sort(
1805
1998
  (a, b) => {
1806
1999
  var _a, _b, _c, _d;
@@ -1812,6 +2005,13 @@ var HoleTool = class {
1812
2005
  const original = this.holes[i];
1813
2006
  const newAbsX = obj.left;
1814
2007
  const newAbsY = obj.top;
2008
+ if (isNaN(newAbsX) || isNaN(newAbsY)) {
2009
+ console.error("HoleTool: Invalid marker coordinates", {
2010
+ newAbsX,
2011
+ newAbsY
2012
+ });
2013
+ return original;
2014
+ }
1815
2015
  const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
1816
2016
  const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
1817
2017
  const unitScale = Coordinate.convertUnit(1, "mm", unit);
@@ -1868,7 +2068,11 @@ var HoleTool = class {
1868
2068
  offsetY: (newAbsY - by) / scale / unitScale,
1869
2069
  // Clear direct coordinates if we use anchor
1870
2070
  x: void 0,
1871
- y: void 0
2071
+ y: void 0,
2072
+ // Ensure other properties are preserved
2073
+ innerRadius: original.innerRadius,
2074
+ outerRadius: original.outerRadius,
2075
+ shape: original.shape || "circle"
1872
2076
  };
1873
2077
  }
1874
2078
  let normalizedX = 0.5;
@@ -1888,9 +2092,13 @@ var HoleTool = class {
1888
2092
  ...original,
1889
2093
  x: normalizedX,
1890
2094
  y: normalizedY,
1891
- // Ensure radii are preserved
2095
+ // Clear offsets if we are using direct normalized coordinates
2096
+ offsetX: void 0,
2097
+ offsetY: void 0,
2098
+ // Ensure other properties are preserved
1892
2099
  innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
1893
- outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25
2100
+ outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
2101
+ shape: (original == null ? void 0 : original.shape) || "circle"
1894
2102
  };
1895
2103
  });
1896
2104
  this.holes = newHoles;
@@ -1948,7 +2156,16 @@ var HoleTool = class {
1948
2156
  { width: geometry.width, height: geometry.height }
1949
2157
  // Use geometry dims instead of canvas
1950
2158
  );
1951
- const innerCircle = new import_fabric4.Circle({
2159
+ const isSquare = hole.shape === "square";
2160
+ const innerMarker = isSquare ? new import_fabric4.Rect({
2161
+ width: visualInnerRadius * 2,
2162
+ height: visualInnerRadius * 2,
2163
+ fill: "transparent",
2164
+ stroke: "red",
2165
+ strokeWidth: 2,
2166
+ originX: "center",
2167
+ originY: "center"
2168
+ }) : new import_fabric4.Circle({
1952
2169
  radius: visualInnerRadius,
1953
2170
  fill: "transparent",
1954
2171
  stroke: "red",
@@ -1956,7 +2173,16 @@ var HoleTool = class {
1956
2173
  originX: "center",
1957
2174
  originY: "center"
1958
2175
  });
1959
- const outerCircle = new import_fabric4.Circle({
2176
+ const outerMarker = isSquare ? new import_fabric4.Rect({
2177
+ width: visualOuterRadius * 2,
2178
+ height: visualOuterRadius * 2,
2179
+ fill: "transparent",
2180
+ stroke: "#666",
2181
+ strokeWidth: 1,
2182
+ strokeDashArray: [5, 5],
2183
+ originX: "center",
2184
+ originY: "center"
2185
+ }) : new import_fabric4.Circle({
1960
2186
  radius: visualOuterRadius,
1961
2187
  fill: "transparent",
1962
2188
  stroke: "#666",
@@ -1965,7 +2191,7 @@ var HoleTool = class {
1965
2191
  originX: "center",
1966
2192
  originY: "center"
1967
2193
  });
1968
- const holeGroup = new import_fabric4.Group([outerCircle, innerCircle], {
2194
+ const holeGroup = new import_fabric4.Group([outerMarker, innerMarker], {
1969
2195
  left: pos.x,
1970
2196
  top: pos.y,
1971
2197
  originX: "center",
@@ -2107,6 +2333,7 @@ var ImageTool = class {
2107
2333
  };
2108
2334
  this.items = [];
2109
2335
  this.objectMap = /* @__PURE__ */ new Map();
2336
+ this.loadResolvers = /* @__PURE__ */ new Map();
2110
2337
  this.isUpdatingConfig = false;
2111
2338
  }
2112
2339
  activate(context) {
@@ -2116,19 +2343,15 @@ var ImageTool = class {
2116
2343
  console.warn("CanvasService not found for ImageTool");
2117
2344
  return;
2118
2345
  }
2119
- const configService = context.services.get("ConfigurationService");
2346
+ const configService = context.services.get(
2347
+ "ConfigurationService"
2348
+ );
2120
2349
  if (configService) {
2121
2350
  this.items = configService.get("image.items", []) || [];
2122
2351
  configService.onAnyChange((e) => {
2123
2352
  if (this.isUpdatingConfig) return;
2124
- let shouldUpdate = false;
2125
2353
  if (e.key === "image.items") {
2126
2354
  this.items = e.value || [];
2127
- shouldUpdate = true;
2128
- } else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
2129
- shouldUpdate = true;
2130
- }
2131
- if (shouldUpdate) {
2132
2355
  this.updateImages();
2133
2356
  }
2134
2357
  });
@@ -2164,15 +2387,39 @@ var ImageTool = class {
2164
2387
  {
2165
2388
  command: "addImage",
2166
2389
  title: "Add Image",
2167
- handler: (url, options) => {
2390
+ handler: async (url, options) => {
2391
+ const id = this.generateId();
2168
2392
  const newItem = {
2169
- id: this.generateId(),
2393
+ id,
2170
2394
  url,
2171
2395
  opacity: 1,
2172
2396
  ...options
2173
2397
  };
2398
+ const promise = new Promise((resolve) => {
2399
+ this.loadResolvers.set(id, () => resolve(id));
2400
+ });
2174
2401
  this.updateConfig([...this.items, newItem]);
2175
- return newItem.id;
2402
+ return promise;
2403
+ }
2404
+ },
2405
+ {
2406
+ command: "fitImageToArea",
2407
+ title: "Fit Image to Area",
2408
+ handler: (id, area) => {
2409
+ var _a, _b;
2410
+ const item = this.items.find((i) => i.id === id);
2411
+ const obj = this.objectMap.get(id);
2412
+ if (item && obj && obj.width && obj.height) {
2413
+ const scale = Math.max(
2414
+ area.width / obj.width,
2415
+ area.height / obj.height
2416
+ );
2417
+ this.updateImageInConfig(id, {
2418
+ scale,
2419
+ left: (_a = area.left) != null ? _a : 0.5,
2420
+ top: (_b = area.top) != null ? _b : 0.5
2421
+ });
2422
+ }
2176
2423
  }
2177
2424
  },
2178
2425
  {
@@ -2240,7 +2487,9 @@ var ImageTool = class {
2240
2487
  if (!this.context) return;
2241
2488
  this.isUpdatingConfig = true;
2242
2489
  this.items = newItems;
2243
- const configService = this.context.services.get("ConfigurationService");
2490
+ const configService = this.context.services.get(
2491
+ "ConfigurationService"
2492
+ );
2244
2493
  if (configService) {
2245
2494
  configService.update("image.items", newItems);
2246
2495
  }
@@ -2286,39 +2535,12 @@ var ImageTool = class {
2286
2535
  var _a, _b;
2287
2536
  const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
2288
2537
  const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
2289
- let layoutScale = 1;
2290
- let layoutOffsetX = 0;
2291
- let layoutOffsetY = 0;
2292
- let visualWidth = canvasW;
2293
- let visualHeight = canvasH;
2294
- let dielinePhysicalWidth = 500;
2295
- let dielinePhysicalHeight = 500;
2296
- if (this.context) {
2297
- const configService = this.context.services.get("ConfigurationService");
2298
- if (configService) {
2299
- dielinePhysicalWidth = configService.get("dieline.width") || 500;
2300
- dielinePhysicalHeight = configService.get("dieline.height") || 500;
2301
- const padding = configService.get("dieline.padding") || 40;
2302
- const layout = Coordinate.calculateLayout(
2303
- { width: canvasW, height: canvasH },
2304
- { width: dielinePhysicalWidth, height: dielinePhysicalHeight },
2305
- padding
2306
- );
2307
- layoutScale = layout.scale;
2308
- layoutOffsetX = layout.offsetX;
2309
- layoutOffsetY = layout.offsetY;
2310
- visualWidth = layout.width;
2311
- visualHeight = layout.height;
2312
- }
2313
- }
2314
2538
  return {
2315
- layoutScale,
2316
- layoutOffsetX,
2317
- layoutOffsetY,
2318
- visualWidth,
2319
- visualHeight,
2320
- dielinePhysicalWidth,
2321
- dielinePhysicalHeight
2539
+ layoutScale: 1,
2540
+ layoutOffsetX: 0,
2541
+ layoutOffsetY: 0,
2542
+ visualWidth: canvasW,
2543
+ visualHeight: canvasH
2322
2544
  };
2323
2545
  }
2324
2546
  updateImages() {
@@ -2341,8 +2563,8 @@ var ImageTool = class {
2341
2563
  if (!obj) {
2342
2564
  this.loadImage(item, layer, layout);
2343
2565
  } else {
2344
- this.updateObjectProperties(obj, item, layout);
2345
2566
  layer.remove(obj);
2567
+ this.updateObjectProperties(obj, item, layout);
2346
2568
  layer.add(obj);
2347
2569
  }
2348
2570
  });
@@ -2350,10 +2572,17 @@ var ImageTool = class {
2350
2572
  this.canvasService.requestRenderAll();
2351
2573
  }
2352
2574
  updateObjectProperties(obj, item, layout) {
2353
- const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2575
+ const {
2576
+ layoutScale,
2577
+ layoutOffsetX,
2578
+ layoutOffsetY,
2579
+ visualWidth,
2580
+ visualHeight
2581
+ } = layout;
2354
2582
  const updates = {};
2355
2583
  if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
2356
- if (item.angle !== void 0 && obj.angle !== item.angle) updates.angle = item.angle;
2584
+ if (item.angle !== void 0 && obj.angle !== item.angle)
2585
+ updates.angle = item.angle;
2357
2586
  if (item.left !== void 0) {
2358
2587
  const globalLeft = layoutOffsetX + item.left * visualWidth;
2359
2588
  if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
@@ -2362,13 +2591,12 @@ var ImageTool = class {
2362
2591
  const globalTop = layoutOffsetY + item.top * visualHeight;
2363
2592
  if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
2364
2593
  }
2365
- if (item.width !== void 0 && obj.width) {
2366
- const targetScaleX = item.width * layoutScale / obj.width;
2367
- if (Math.abs(obj.scaleX - targetScaleX) > 1e-3) updates.scaleX = targetScaleX;
2368
- }
2369
- if (item.height !== void 0 && obj.height) {
2370
- const targetScaleY = item.height * layoutScale / obj.height;
2371
- if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
2594
+ if (item.scale !== void 0) {
2595
+ const targetScale = item.scale * layoutScale;
2596
+ if (Math.abs(obj.scaleX - targetScale) > 1e-3) {
2597
+ updates.scaleX = targetScale;
2598
+ updates.scaleY = targetScale;
2599
+ }
2372
2600
  }
2373
2601
  if (obj.originX !== "center") {
2374
2602
  updates.originX = "center";
@@ -2376,6 +2604,7 @@ var ImageTool = class {
2376
2604
  }
2377
2605
  if (Object.keys(updates).length > 0) {
2378
2606
  obj.set(updates);
2607
+ obj.setCoords();
2379
2608
  }
2380
2609
  }
2381
2610
  loadImage(item, layer, layout) {
@@ -2385,24 +2614,20 @@ var ImageTool = class {
2385
2614
  image.set({
2386
2615
  originX: "center",
2387
2616
  originY: "center",
2388
- data: { id: item.id }
2617
+ data: { id: item.id },
2618
+ uniformScaling: true,
2619
+ lockScalingFlip: true
2389
2620
  });
2390
- let { width, height, left, top } = item;
2391
- const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight, dielinePhysicalWidth, dielinePhysicalHeight } = layout;
2392
- if (width === void 0 && height === void 0) {
2393
- const imgAspect = (image.width || 1) / (image.height || 1);
2394
- const dielineAspect = dielinePhysicalWidth / dielinePhysicalHeight;
2395
- if (imgAspect > dielineAspect) {
2396
- const w = dielinePhysicalWidth;
2397
- width = w;
2398
- height = w / imgAspect;
2399
- } else {
2400
- const h = dielinePhysicalHeight;
2401
- height = h;
2402
- width = h * imgAspect;
2403
- }
2404
- item.width = width;
2405
- item.height = height;
2621
+ image.setControlsVisibility({
2622
+ mt: false,
2623
+ mb: false,
2624
+ ml: false,
2625
+ mr: false
2626
+ });
2627
+ let { scale, left, top } = item;
2628
+ if (scale === void 0) {
2629
+ scale = 1;
2630
+ item.scale = scale;
2406
2631
  }
2407
2632
  if (left === void 0 && top === void 0) {
2408
2633
  left = 0.5;
@@ -2413,13 +2638,18 @@ var ImageTool = class {
2413
2638
  this.updateObjectProperties(image, item, layout);
2414
2639
  layer.add(image);
2415
2640
  this.objectMap.set(item.id, image);
2641
+ const resolver = this.loadResolvers.get(item.id);
2642
+ if (resolver) {
2643
+ resolver();
2644
+ this.loadResolvers.delete(item.id);
2645
+ }
2416
2646
  image.on("modified", (e) => {
2417
2647
  this.handleObjectModified(item.id, image);
2418
2648
  });
2419
2649
  layer.dirty = true;
2420
2650
  (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2421
- if (item.width !== width || item.height !== height || item.left !== left || item.top !== top) {
2422
- this.updateImageInConfig(item.id, { width, height, left, top });
2651
+ if (item.scale !== scale || item.left !== left || item.top !== top) {
2652
+ this.updateImageInConfig(item.id, { scale, left, top }, true);
2423
2653
  }
2424
2654
  }).catch((err) => {
2425
2655
  console.error("Failed to load image", item.url, err);
@@ -2427,29 +2657,28 @@ var ImageTool = class {
2427
2657
  }
2428
2658
  handleObjectModified(id, image) {
2429
2659
  const layout = this.getLayoutInfo();
2430
- const { layoutScale, layoutOffsetX, layoutOffsetY, visualWidth, visualHeight } = layout;
2660
+ const {
2661
+ layoutScale,
2662
+ layoutOffsetX,
2663
+ layoutOffsetY,
2664
+ visualWidth,
2665
+ visualHeight
2666
+ } = layout;
2431
2667
  const matrix = image.calcTransformMatrix();
2432
2668
  const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2433
2669
  const updates = {};
2434
2670
  updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
2435
2671
  updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
2436
2672
  updates.angle = image.angle;
2437
- if (image.width) {
2438
- const pixelWidth = image.width * image.scaleX;
2439
- updates.width = pixelWidth / layoutScale;
2440
- }
2441
- if (image.height) {
2442
- const pixelHeight = image.height * image.scaleY;
2443
- updates.height = pixelHeight / layoutScale;
2444
- }
2445
- this.updateImageInConfig(id, updates);
2673
+ updates.scale = image.scaleX / layoutScale;
2674
+ this.updateImageInConfig(id, updates, true);
2446
2675
  }
2447
- updateImageInConfig(id, updates) {
2676
+ updateImageInConfig(id, updates, skipCanvasUpdate = false) {
2448
2677
  const index = this.items.findIndex((i) => i.id === id);
2449
2678
  if (index !== -1) {
2450
2679
  const newItems = [...this.items];
2451
2680
  newItems[index] = { ...newItems[index], ...updates };
2452
- this.updateConfig(newItems, true);
2681
+ this.updateConfig(newItems, skipCanvasUpdate);
2453
2682
  }
2454
2683
  }
2455
2684
  };