@pooder/kit 6.1.2 → 6.2.1

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.
Files changed (30) hide show
  1. package/.test-dist/src/extensions/background/BackgroundTool.js +177 -5
  2. package/.test-dist/src/extensions/constraintUtils.js +44 -0
  3. package/.test-dist/src/extensions/dieline/DielineTool.js +52 -409
  4. package/.test-dist/src/extensions/dieline/featureResolution.js +29 -0
  5. package/.test-dist/src/extensions/dieline/model.js +83 -0
  6. package/.test-dist/src/extensions/dieline/renderBuilder.js +227 -0
  7. package/.test-dist/src/extensions/feature/FeatureTool.js +156 -45
  8. package/.test-dist/src/extensions/featureCoordinates.js +21 -0
  9. package/.test-dist/src/extensions/featurePlacement.js +46 -0
  10. package/.test-dist/src/extensions/image/ImageTool.js +281 -25
  11. package/.test-dist/src/extensions/ruler/RulerTool.js +24 -1
  12. package/.test-dist/src/shared/constants/layers.js +3 -1
  13. package/.test-dist/tests/run.js +25 -0
  14. package/CHANGELOG.md +12 -0
  15. package/dist/index.d.mts +47 -13
  16. package/dist/index.d.ts +47 -13
  17. package/dist/index.js +1325 -977
  18. package/dist/index.mjs +1311 -966
  19. package/package.json +1 -1
  20. package/src/extensions/background/BackgroundTool.ts +264 -4
  21. package/src/extensions/dieline/DielineTool.ts +67 -548
  22. package/src/extensions/dieline/model.ts +165 -1
  23. package/src/extensions/dieline/renderBuilder.ts +301 -0
  24. package/src/extensions/feature/FeatureTool.ts +190 -47
  25. package/src/extensions/featureCoordinates.ts +35 -0
  26. package/src/extensions/featurePlacement.ts +118 -0
  27. package/src/extensions/image/ImageTool.ts +139 -157
  28. package/src/extensions/ruler/RulerTool.ts +24 -2
  29. package/src/shared/constants/layers.ts +2 -0
  30. package/tests/run.ts +37 -0
package/dist/index.js CHANGED
@@ -1075,13 +1075,15 @@ __export(index_exports, {
1075
1075
  WhiteInkTool: () => WhiteInkTool,
1076
1076
  computeImageCoverScale: () => getCoverScale,
1077
1077
  computeWhiteInkCoverScale: () => getCoverScale,
1078
+ createDefaultDielineState: () => createDefaultDielineState,
1078
1079
  createDielineCommands: () => createDielineCommands,
1079
1080
  createDielineConfigurations: () => createDielineConfigurations,
1080
1081
  createImageCommands: () => createImageCommands,
1081
1082
  createImageConfigurations: () => createImageConfigurations,
1082
1083
  createWhiteInkCommands: () => createWhiteInkCommands,
1083
1084
  createWhiteInkConfigurations: () => createWhiteInkConfigurations,
1084
- evaluateVisibilityExpr: () => evaluateVisibilityExpr
1085
+ evaluateVisibilityExpr: () => evaluateVisibilityExpr,
1086
+ readDielineState: () => readDielineState
1085
1087
  });
1086
1088
  module.exports = __toCommonJS(index_exports);
1087
1089
 
@@ -1485,6 +1487,7 @@ var WHITE_INK_OBJECT_LAYER_ID = "white-ink.user";
1485
1487
  var WHITE_INK_COVER_LAYER_ID = "white-ink.cover";
1486
1488
  var WHITE_INK_OVERLAY_LAYER_ID = "white-ink.overlay";
1487
1489
  var DIELINE_LAYER_ID = "dieline-overlay";
1490
+ var FEATURE_DIELINE_LAYER_ID = "feature-dieline-overlay";
1488
1491
  var FEATURE_OVERLAY_LAYER_ID = "feature-overlay";
1489
1492
  var RULER_LAYER_ID = "ruler-overlay";
1490
1493
  var FILM_LAYER_ID = "overlay";
@@ -1626,6 +1629,18 @@ function normalizeFitMode2(value, fallback) {
1626
1629
  }
1627
1630
  return fallback;
1628
1631
  }
1632
+ function normalizeRegionUnit(value, fallback) {
1633
+ if (value === "px" || value === "normalized") {
1634
+ return value;
1635
+ }
1636
+ return fallback;
1637
+ }
1638
+ function normalizeRegistrationFrame(value, fallback) {
1639
+ if (value === "trim" || value === "cut" || value === "bleed" || value === "focus" || value === "viewport") {
1640
+ return value;
1641
+ }
1642
+ return fallback;
1643
+ }
1629
1644
  function normalizeAnchor(value, fallback) {
1630
1645
  if (typeof value !== "string") return fallback;
1631
1646
  const trimmed = value.trim();
@@ -1636,6 +1651,63 @@ function normalizeOrder(value, fallback) {
1636
1651
  if (!Number.isFinite(numeric)) return fallback;
1637
1652
  return numeric;
1638
1653
  }
1654
+ function normalizeRegionValue(value, fallback) {
1655
+ const numeric = Number(value);
1656
+ return Number.isFinite(numeric) ? numeric : fallback;
1657
+ }
1658
+ function normalizeRegistrationRegion(raw, fallback) {
1659
+ if (!raw || typeof raw !== "object") {
1660
+ return fallback ? { ...fallback } : void 0;
1661
+ }
1662
+ const input = raw;
1663
+ const base = fallback || {
1664
+ left: 0,
1665
+ top: 0,
1666
+ width: 1,
1667
+ height: 1,
1668
+ unit: "normalized"
1669
+ };
1670
+ return {
1671
+ left: normalizeRegionValue(input.left, base.left),
1672
+ top: normalizeRegionValue(input.top, base.top),
1673
+ width: normalizeRegionValue(input.width, base.width),
1674
+ height: normalizeRegionValue(input.height, base.height),
1675
+ unit: normalizeRegionUnit(input.unit, base.unit)
1676
+ };
1677
+ }
1678
+ function normalizeRegistration(raw, fallback) {
1679
+ if (!raw || typeof raw !== "object") {
1680
+ return fallback ? {
1681
+ sourceRegion: fallback.sourceRegion ? { ...fallback.sourceRegion } : void 0,
1682
+ targetFrame: fallback.targetFrame,
1683
+ fit: fallback.fit
1684
+ } : void 0;
1685
+ }
1686
+ const input = raw;
1687
+ const normalized = {
1688
+ sourceRegion: normalizeRegistrationRegion(
1689
+ input.sourceRegion,
1690
+ fallback == null ? void 0 : fallback.sourceRegion
1691
+ ),
1692
+ targetFrame: normalizeRegistrationFrame(
1693
+ input.targetFrame,
1694
+ (fallback == null ? void 0 : fallback.targetFrame) || "trim"
1695
+ ),
1696
+ fit: normalizeFitMode2(input.fit, (fallback == null ? void 0 : fallback.fit) || "stretch")
1697
+ };
1698
+ if (!normalized.sourceRegion) {
1699
+ return void 0;
1700
+ }
1701
+ return normalized;
1702
+ }
1703
+ function cloneRegistration(registration) {
1704
+ if (!registration) return void 0;
1705
+ return {
1706
+ sourceRegion: registration.sourceRegion ? { ...registration.sourceRegion } : void 0,
1707
+ targetFrame: registration.targetFrame,
1708
+ fit: registration.fit
1709
+ };
1710
+ }
1639
1711
  function normalizeLayer(raw, index, fallback) {
1640
1712
  const fallbackLayer = fallback || {
1641
1713
  id: `layer-${index + 1}`,
@@ -1649,7 +1721,10 @@ function normalizeLayer(raw, index, fallback) {
1649
1721
  src: ""
1650
1722
  };
1651
1723
  if (!raw || typeof raw !== "object") {
1652
- return { ...fallbackLayer };
1724
+ return {
1725
+ ...fallbackLayer,
1726
+ registration: cloneRegistration(fallbackLayer.registration)
1727
+ };
1653
1728
  }
1654
1729
  const input = raw;
1655
1730
  const kind = normalizeLayerKind(input.kind, fallbackLayer.kind);
@@ -1663,7 +1738,8 @@ function normalizeLayer(raw, index, fallback) {
1663
1738
  enabled: typeof input.enabled === "boolean" ? input.enabled : fallbackLayer.enabled,
1664
1739
  exportable: typeof input.exportable === "boolean" ? input.exportable : fallbackLayer.exportable,
1665
1740
  color: kind === "color" ? typeof input.color === "string" ? input.color : typeof fallbackLayer.color === "string" ? fallbackLayer.color : "#ffffff" : void 0,
1666
- src: kind === "image" ? typeof input.src === "string" ? input.src.trim() : typeof fallbackLayer.src === "string" ? fallbackLayer.src : "" : void 0
1741
+ src: kind === "image" ? typeof input.src === "string" ? input.src.trim() : typeof fallbackLayer.src === "string" ? fallbackLayer.src : "" : void 0,
1742
+ registration: kind === "image" ? normalizeRegistration(input.registration, fallbackLayer.registration) : void 0
1667
1743
  };
1668
1744
  }
1669
1745
  function normalizeConfig(raw) {
@@ -1693,7 +1769,10 @@ function normalizeConfig(raw) {
1693
1769
  function cloneConfig(config) {
1694
1770
  return {
1695
1771
  version: config.version,
1696
- layers: (config.layers || []).map((layer) => ({ ...layer }))
1772
+ layers: (config.layers || []).map((layer) => ({
1773
+ ...layer,
1774
+ registration: cloneRegistration(layer.registration)
1775
+ }))
1697
1776
  };
1698
1777
  }
1699
1778
  function mergeConfig(base, patch) {
@@ -1944,6 +2023,41 @@ var BackgroundTool = class {
1944
2023
  height: layout.trimRect.height
1945
2024
  };
1946
2025
  }
2026
+ resolveTargetFrameRect(frame) {
2027
+ if (frame === "viewport") {
2028
+ return this.getViewportRect();
2029
+ }
2030
+ const layout = this.resolveSceneLayout();
2031
+ if (!layout) {
2032
+ return frame === "focus" ? this.getViewportRect() : null;
2033
+ }
2034
+ switch (frame) {
2035
+ case "trim":
2036
+ case "focus":
2037
+ return {
2038
+ left: layout.trimRect.left,
2039
+ top: layout.trimRect.top,
2040
+ width: layout.trimRect.width,
2041
+ height: layout.trimRect.height
2042
+ };
2043
+ case "cut":
2044
+ return {
2045
+ left: layout.cutRect.left,
2046
+ top: layout.cutRect.top,
2047
+ width: layout.cutRect.width,
2048
+ height: layout.cutRect.height
2049
+ };
2050
+ case "bleed":
2051
+ return {
2052
+ left: layout.bleedRect.left,
2053
+ top: layout.bleedRect.top,
2054
+ width: layout.bleedRect.width,
2055
+ height: layout.bleedRect.height
2056
+ };
2057
+ default:
2058
+ return null;
2059
+ }
2060
+ }
1947
2061
  resolveAnchorRect(anchor) {
1948
2062
  if (anchor === "focus") {
1949
2063
  return this.resolveFocusRect() || this.getViewportRect();
@@ -1976,6 +2090,53 @@ var BackgroundTool = class {
1976
2090
  scaleY: scale
1977
2091
  };
1978
2092
  }
2093
+ resolveRegistrationRegion(region, sourceSize) {
2094
+ const sourceWidth = Math.max(1, Number(sourceSize.width || 0));
2095
+ const sourceHeight = Math.max(1, Number(sourceSize.height || 0));
2096
+ const width = region.unit === "normalized" ? region.width * sourceWidth : region.width;
2097
+ const height = region.unit === "normalized" ? region.height * sourceHeight : region.height;
2098
+ const left = region.unit === "normalized" ? region.left * sourceWidth : region.left;
2099
+ const top = region.unit === "normalized" ? region.top * sourceHeight : region.top;
2100
+ if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
2101
+ return null;
2102
+ }
2103
+ return { left, top, width, height };
2104
+ }
2105
+ resolveRegistrationPlacement(layer, sourceSize) {
2106
+ const registration = layer.registration;
2107
+ if (!(registration == null ? void 0 : registration.sourceRegion)) return null;
2108
+ const targetRect = this.resolveTargetFrameRect(
2109
+ registration.targetFrame || "trim"
2110
+ );
2111
+ if (!targetRect) return null;
2112
+ const sourceRegion = this.resolveRegistrationRegion(
2113
+ registration.sourceRegion,
2114
+ sourceSize
2115
+ );
2116
+ if (!sourceRegion) return null;
2117
+ const fit = registration.fit || "stretch";
2118
+ const baseScaleX = targetRect.width / sourceRegion.width;
2119
+ const baseScaleY = targetRect.height / sourceRegion.height;
2120
+ if (fit === "stretch") {
2121
+ return {
2122
+ left: targetRect.left - sourceRegion.left * baseScaleX,
2123
+ top: targetRect.top - sourceRegion.top * baseScaleY,
2124
+ scaleX: baseScaleX,
2125
+ scaleY: baseScaleY
2126
+ };
2127
+ }
2128
+ const uniformScale = fit === "contain" ? Math.min(baseScaleX, baseScaleY) : Math.max(baseScaleX, baseScaleY);
2129
+ const alignedWidth = sourceRegion.width * uniformScale;
2130
+ const alignedHeight = sourceRegion.height * uniformScale;
2131
+ const offsetLeft = targetRect.left + (targetRect.width - alignedWidth) / 2;
2132
+ const offsetTop = targetRect.top + (targetRect.height - alignedHeight) / 2;
2133
+ return {
2134
+ left: offsetLeft - sourceRegion.left * uniformScale,
2135
+ top: offsetTop - sourceRegion.top * uniformScale,
2136
+ scaleX: uniformScale,
2137
+ scaleY: uniformScale
2138
+ };
2139
+ }
1979
2140
  buildColorLayerSpec(layer) {
1980
2141
  const rect = this.resolveAnchorRect(layer.anchor);
1981
2142
  return {
@@ -2009,8 +2170,11 @@ var BackgroundTool = class {
2009
2170
  if (!src) return [];
2010
2171
  const sourceSize = this.sourceSizeCache.getSourceSize(src);
2011
2172
  if (!sourceSize) return [];
2012
- const rect = this.resolveAnchorRect(layer.anchor);
2013
- const placement = this.resolveImagePlacement(rect, sourceSize, layer.fit);
2173
+ const placement = this.resolveRegistrationPlacement(layer, sourceSize) || this.resolveImagePlacement(
2174
+ this.resolveAnchorRect(layer.anchor),
2175
+ sourceSize,
2176
+ layer.fit
2177
+ );
2014
2178
  return [
2015
2179
  {
2016
2180
  id: `background.layer.${layer.id}.image`,
@@ -3075,8 +3239,6 @@ var IMAGE_DEFAULT_CONTROL_CAPABILITIES = [
3075
3239
  "scale"
3076
3240
  ];
3077
3241
  var IMAGE_MOVE_SNAP_THRESHOLD_PX = 6;
3078
- var IMAGE_MOVE_SNAP_RELEASE_THRESHOLD_PX = 10;
3079
- var IMAGE_SNAP_GUIDE_LAYER_ID = "image.snapGuide";
3080
3242
  var IMAGE_CONTROL_DESCRIPTORS = [
3081
3243
  {
3082
3244
  key: "tl",
@@ -3123,13 +3285,15 @@ var ImageTool = class {
3123
3285
  this.overlaySpecs = [];
3124
3286
  this.activeSnapX = null;
3125
3287
  this.activeSnapY = null;
3288
+ this.movingImageId = null;
3289
+ this.hasRenderedSnapGuides = false;
3126
3290
  this.subscriptions = new SubscriptionBag();
3127
3291
  this.imageControlsByCapabilityKey = /* @__PURE__ */ new Map();
3128
3292
  this.onToolActivated = (event) => {
3129
3293
  const before = this.isToolActive;
3130
3294
  this.syncToolActiveFromWorkbench(event.id);
3131
3295
  if (!this.isToolActive) {
3132
- this.clearSnapGuides();
3296
+ this.endMoveSnapInteraction();
3133
3297
  this.setImageFocus(null, {
3134
3298
  syncCanvasSelection: true,
3135
3299
  skipRender: true
@@ -3180,7 +3344,7 @@ var ImageTool = class {
3180
3344
  this.updateImages();
3181
3345
  };
3182
3346
  this.onSelectionCleared = () => {
3183
- this.clearSnapGuides();
3347
+ this.endMoveSnapInteraction();
3184
3348
  this.setImageFocus(null, {
3185
3349
  syncCanvasSelection: false,
3186
3350
  skipRender: true
@@ -3189,7 +3353,8 @@ var ImageTool = class {
3189
3353
  this.updateImages();
3190
3354
  };
3191
3355
  this.onSceneLayoutChanged = () => {
3192
- this.updateSnapGuideVisuals();
3356
+ var _a;
3357
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
3193
3358
  this.updateImages();
3194
3359
  };
3195
3360
  this.onSceneGeometryChanged = () => {
@@ -3202,11 +3367,12 @@ var ImageTool = class {
3202
3367
  const id = (_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id;
3203
3368
  const layerId = (_b = target == null ? void 0 : target.data) == null ? void 0 : _b.layerId;
3204
3369
  if (typeof id !== "string" || layerId !== IMAGE_OBJECT_LAYER_ID) return;
3370
+ if (this.movingImageId === id) {
3371
+ this.applyMoveSnapToTarget(target);
3372
+ }
3205
3373
  const frame = this.getFrameRect();
3374
+ this.endMoveSnapInteraction();
3206
3375
  if (!frame.width || !frame.height) return;
3207
- const matches = this.computeMoveSnapMatches(target, frame);
3208
- this.applySnapMatchesToTarget(target, matches);
3209
- this.clearSnapGuides();
3210
3376
  const center = target.getCenterPoint ? target.getCenterPoint() : new import_fabric2.Point((_c = target.left) != null ? _c : 0, (_d = target.top) != null ? _d : 0);
3211
3377
  const centerScene = this.canvasService ? this.canvasService.toScenePoint({ x: center.x, y: center.y }) : { x: center.x, y: center.y };
3212
3378
  const objectScale = Number.isFinite(target == null ? void 0 : target.scaleX) ? target.scaleX : 1;
@@ -3347,7 +3513,7 @@ var ImageTool = class {
3347
3513
  this.imageSpecs = [];
3348
3514
  this.overlaySpecs = [];
3349
3515
  this.imageControlsByCapabilityKey.clear();
3350
- this.clearSnapGuides();
3516
+ this.endMoveSnapInteraction();
3351
3517
  this.unbindCanvasInteractionHandlers();
3352
3518
  this.clearRenderedImages();
3353
3519
  (_b = this.renderProducerDisposable) == null ? void 0 : _b.dispose();
@@ -3360,21 +3526,61 @@ var ImageTool = class {
3360
3526
  }
3361
3527
  bindCanvasInteractionHandlers() {
3362
3528
  if (!this.canvasService || this.canvasObjectMovingHandler) return;
3529
+ this.canvasMouseUpHandler = (e) => {
3530
+ var _a;
3531
+ const target = this.getActiveImageTarget(e == null ? void 0 : e.target);
3532
+ if (target && typeof ((_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id) === "string" && target.data.id === this.movingImageId) {
3533
+ this.applyMoveSnapToTarget(target);
3534
+ }
3535
+ this.endMoveSnapInteraction();
3536
+ };
3363
3537
  this.canvasObjectMovingHandler = (e) => {
3364
3538
  this.handleCanvasObjectMoving(e);
3365
3539
  };
3540
+ this.canvasBeforeRenderHandler = () => {
3541
+ this.handleCanvasBeforeRender();
3542
+ };
3543
+ this.canvasAfterRenderHandler = () => {
3544
+ this.handleCanvasAfterRender();
3545
+ };
3546
+ this.canvasService.canvas.on("mouse:up", this.canvasMouseUpHandler);
3366
3547
  this.canvasService.canvas.on(
3367
3548
  "object:moving",
3368
3549
  this.canvasObjectMovingHandler
3369
3550
  );
3551
+ this.canvasService.canvas.on(
3552
+ "before:render",
3553
+ this.canvasBeforeRenderHandler
3554
+ );
3555
+ this.canvasService.canvas.on("after:render", this.canvasAfterRenderHandler);
3370
3556
  }
3371
3557
  unbindCanvasInteractionHandlers() {
3372
- if (!this.canvasService || !this.canvasObjectMovingHandler) return;
3373
- this.canvasService.canvas.off(
3374
- "object:moving",
3375
- this.canvasObjectMovingHandler
3376
- );
3558
+ if (!this.canvasService) return;
3559
+ if (this.canvasMouseUpHandler) {
3560
+ this.canvasService.canvas.off("mouse:up", this.canvasMouseUpHandler);
3561
+ }
3562
+ if (this.canvasObjectMovingHandler) {
3563
+ this.canvasService.canvas.off(
3564
+ "object:moving",
3565
+ this.canvasObjectMovingHandler
3566
+ );
3567
+ }
3568
+ if (this.canvasBeforeRenderHandler) {
3569
+ this.canvasService.canvas.off(
3570
+ "before:render",
3571
+ this.canvasBeforeRenderHandler
3572
+ );
3573
+ }
3574
+ if (this.canvasAfterRenderHandler) {
3575
+ this.canvasService.canvas.off(
3576
+ "after:render",
3577
+ this.canvasAfterRenderHandler
3578
+ );
3579
+ }
3580
+ this.canvasMouseUpHandler = void 0;
3377
3581
  this.canvasObjectMovingHandler = void 0;
3582
+ this.canvasBeforeRenderHandler = void 0;
3583
+ this.canvasAfterRenderHandler = void 0;
3378
3584
  }
3379
3585
  getActiveImageTarget(target) {
3380
3586
  var _a, _b;
@@ -3403,20 +3609,11 @@ var ImageTool = class {
3403
3609
  if (!this.canvasService) return px;
3404
3610
  return this.canvasService.toSceneLength(px);
3405
3611
  }
3406
- pickSnapMatch(candidates, previous) {
3612
+ pickSnapMatch(candidates) {
3407
3613
  if (!candidates.length) return null;
3408
3614
  const snapThreshold = this.getSnapThresholdScene(
3409
3615
  IMAGE_MOVE_SNAP_THRESHOLD_PX
3410
3616
  );
3411
- const releaseThreshold = this.getSnapThresholdScene(
3412
- IMAGE_MOVE_SNAP_RELEASE_THRESHOLD_PX
3413
- );
3414
- if (previous) {
3415
- const sticky = candidates.find((candidate) => {
3416
- return candidate.lineId === previous.lineId && Math.abs(candidate.deltaScene) <= releaseThreshold;
3417
- });
3418
- if (sticky) return sticky;
3419
- }
3420
3617
  let best = null;
3421
3618
  candidates.forEach((candidate) => {
3422
3619
  if (Math.abs(candidate.deltaScene) > snapThreshold) return;
@@ -3426,8 +3623,7 @@ var ImageTool = class {
3426
3623
  });
3427
3624
  return best;
3428
3625
  }
3429
- computeMoveSnapMatches(target, frame) {
3430
- const bounds = this.getTargetBoundsScene(target);
3626
+ computeMoveSnapMatches(bounds, frame) {
3431
3627
  if (!bounds || frame.width <= 0 || frame.height <= 0) {
3432
3628
  return { x: null, y: null };
3433
3629
  }
@@ -3478,8 +3674,8 @@ var ImageTool = class {
3478
3674
  }
3479
3675
  ];
3480
3676
  return {
3481
- x: this.pickSnapMatch(xCandidates, this.activeSnapX),
3482
- y: this.pickSnapMatch(yCandidates, this.activeSnapY)
3677
+ x: this.pickSnapMatch(xCandidates),
3678
+ y: this.pickSnapMatch(yCandidates)
3483
3679
  };
3484
3680
  }
3485
3681
  areSnapMatchesEqual(a, b) {
@@ -3488,136 +3684,123 @@ var ImageTool = class {
3488
3684
  return a.lineId === b.lineId && a.axis === b.axis && a.kind === b.kind;
3489
3685
  }
3490
3686
  updateSnapMatchState(nextX, nextY) {
3687
+ var _a;
3491
3688
  const changed = !this.areSnapMatchesEqual(this.activeSnapX, nextX) || !this.areSnapMatchesEqual(this.activeSnapY, nextY);
3492
3689
  this.activeSnapX = nextX;
3493
3690
  this.activeSnapY = nextY;
3494
3691
  if (changed) {
3495
- this.updateSnapGuideVisuals();
3692
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
3496
3693
  }
3497
3694
  }
3498
- clearSnapGuides() {
3695
+ clearSnapPreview() {
3499
3696
  var _a;
3500
3697
  this.activeSnapX = null;
3501
3698
  this.activeSnapY = null;
3502
- this.removeSnapGuideObject("x");
3503
- this.removeSnapGuideObject("y");
3699
+ this.hasRenderedSnapGuides = false;
3504
3700
  (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
3505
3701
  }
3506
- removeSnapGuideObject(axis) {
3702
+ endMoveSnapInteraction() {
3703
+ this.movingImageId = null;
3704
+ this.clearSnapPreview();
3705
+ }
3706
+ applyMoveSnapToTarget(target) {
3707
+ var _a, _b, _c, _d;
3708
+ if (!this.canvasService) {
3709
+ return { x: null, y: null };
3710
+ }
3711
+ const frame = this.getFrameRect();
3712
+ if (frame.width <= 0 || frame.height <= 0) {
3713
+ return { x: null, y: null };
3714
+ }
3715
+ const bounds = this.getTargetBoundsScene(target);
3716
+ const matches = this.computeMoveSnapMatches(bounds, frame);
3717
+ const deltaScreenX = this.canvasService.toScreenLength(
3718
+ (_b = (_a = matches.x) == null ? void 0 : _a.deltaScene) != null ? _b : 0
3719
+ );
3720
+ const deltaScreenY = this.canvasService.toScreenLength(
3721
+ (_d = (_c = matches.y) == null ? void 0 : _c.deltaScene) != null ? _d : 0
3722
+ );
3723
+ if (deltaScreenX || deltaScreenY) {
3724
+ target.set({
3725
+ left: Number(target.left || 0) + deltaScreenX,
3726
+ top: Number(target.top || 0) + deltaScreenY
3727
+ });
3728
+ target.setCoords();
3729
+ }
3730
+ return matches;
3731
+ }
3732
+ handleCanvasBeforeRender() {
3507
3733
  if (!this.canvasService) return;
3508
- const canvas = this.canvasService.canvas;
3509
- const current = axis === "x" ? this.snapGuideXObject : this.snapGuideYObject;
3510
- if (!current) return;
3511
- canvas.remove(current);
3512
- if (axis === "x") {
3513
- this.snapGuideXObject = void 0;
3734
+ if (!this.hasRenderedSnapGuides && !this.activeSnapX && !this.activeSnapY) {
3514
3735
  return;
3515
3736
  }
3516
- this.snapGuideYObject = void 0;
3737
+ this.canvasService.canvas.clearContext(
3738
+ this.canvasService.canvas.contextTop
3739
+ );
3740
+ this.hasRenderedSnapGuides = false;
3517
3741
  }
3518
- createOrUpdateSnapGuideObject(axis, pathData) {
3742
+ drawSnapGuideLine(from, to) {
3519
3743
  if (!this.canvasService) return;
3520
- const canvas = this.canvasService.canvas;
3744
+ const ctx = this.canvasService.canvas.contextTop;
3745
+ if (!ctx) return;
3521
3746
  const color = this.getConfig("image.control.borderColor", "#1677ff") || "#1677ff";
3522
- const strokeWidth = 1;
3523
- this.removeSnapGuideObject(axis);
3524
- const created = new import_fabric2.Path(pathData, {
3525
- originX: "left",
3526
- originY: "top",
3527
- fill: "rgba(0,0,0,0)",
3528
- stroke: color,
3529
- strokeWidth,
3530
- selectable: false,
3531
- evented: false,
3532
- excludeFromExport: true,
3533
- objectCaching: false,
3534
- data: {
3535
- id: `${IMAGE_SNAP_GUIDE_LAYER_ID}.${axis}`,
3536
- layerId: IMAGE_SNAP_GUIDE_LAYER_ID,
3537
- type: "image-snap-guide"
3538
- }
3539
- });
3540
- created.setCoords();
3541
- canvas.add(created);
3542
- canvas.bringObjectToFront(created);
3543
- if (axis === "x") {
3544
- this.snapGuideXObject = created;
3545
- return;
3546
- }
3547
- this.snapGuideYObject = created;
3747
+ ctx.save();
3748
+ ctx.strokeStyle = color;
3749
+ ctx.lineWidth = 1;
3750
+ ctx.beginPath();
3751
+ ctx.moveTo(from.x, from.y);
3752
+ ctx.lineTo(to.x, to.y);
3753
+ ctx.stroke();
3754
+ ctx.restore();
3548
3755
  }
3549
- updateSnapGuideVisuals() {
3756
+ handleCanvasAfterRender() {
3550
3757
  if (!this.canvasService || !this.isImageEditingVisible()) {
3551
- this.removeSnapGuideObject("x");
3552
- this.removeSnapGuideObject("y");
3553
3758
  return;
3554
3759
  }
3555
3760
  const frame = this.getFrameRect();
3556
3761
  if (frame.width <= 0 || frame.height <= 0) {
3557
- this.removeSnapGuideObject("x");
3558
- this.removeSnapGuideObject("y");
3559
3762
  return;
3560
3763
  }
3561
3764
  const frameScreen = this.getFrameRectScreen(frame);
3765
+ let drew = false;
3562
3766
  if (this.activeSnapX) {
3563
3767
  const x = this.canvasService.toScreenPoint({
3564
3768
  x: this.activeSnapX.lineScene,
3565
3769
  y: frame.top
3566
3770
  }).x;
3567
- this.createOrUpdateSnapGuideObject(
3568
- "x",
3569
- `M ${x} ${frameScreen.top} L ${x} ${frameScreen.top + frameScreen.height}`
3771
+ this.drawSnapGuideLine(
3772
+ { x, y: frameScreen.top },
3773
+ { x, y: frameScreen.top + frameScreen.height }
3570
3774
  );
3571
- } else {
3572
- this.removeSnapGuideObject("x");
3775
+ drew = true;
3573
3776
  }
3574
3777
  if (this.activeSnapY) {
3575
3778
  const y = this.canvasService.toScreenPoint({
3576
3779
  x: frame.left,
3577
3780
  y: this.activeSnapY.lineScene
3578
3781
  }).y;
3579
- this.createOrUpdateSnapGuideObject(
3580
- "y",
3581
- `M ${frameScreen.left} ${y} L ${frameScreen.left + frameScreen.width} ${y}`
3782
+ this.drawSnapGuideLine(
3783
+ { x: frameScreen.left, y },
3784
+ { x: frameScreen.left + frameScreen.width, y }
3582
3785
  );
3583
- } else {
3584
- this.removeSnapGuideObject("y");
3786
+ drew = true;
3585
3787
  }
3586
- this.canvasService.requestRenderAll();
3788
+ this.hasRenderedSnapGuides = drew;
3587
3789
  }
3588
3790
  handleCanvasObjectMoving(e) {
3589
- var _a, _b, _c, _d;
3791
+ var _a;
3590
3792
  const target = this.getActiveImageTarget(e == null ? void 0 : e.target);
3591
3793
  if (!target || !this.canvasService) return;
3794
+ this.movingImageId = typeof ((_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id) === "string" ? target.data.id : null;
3592
3795
  const frame = this.getFrameRect();
3593
3796
  if (frame.width <= 0 || frame.height <= 0) {
3594
- this.clearSnapGuides();
3797
+ this.endMoveSnapInteraction();
3595
3798
  return;
3596
3799
  }
3597
- const matches = this.computeMoveSnapMatches(target, frame);
3598
- const deltaX = (_b = (_a = matches.x) == null ? void 0 : _a.deltaScene) != null ? _b : 0;
3599
- const deltaY = (_d = (_c = matches.y) == null ? void 0 : _c.deltaScene) != null ? _d : 0;
3600
- if (deltaX || deltaY) {
3601
- target.set({
3602
- left: Number(target.left || 0) + this.canvasService.toScreenLength(deltaX),
3603
- top: Number(target.top || 0) + this.canvasService.toScreenLength(deltaY)
3604
- });
3605
- target.setCoords();
3606
- }
3800
+ const rawBounds = this.getTargetBoundsScene(target);
3801
+ const matches = this.computeMoveSnapMatches(rawBounds, frame);
3607
3802
  this.updateSnapMatchState(matches.x, matches.y);
3608
3803
  }
3609
- applySnapMatchesToTarget(target, matches) {
3610
- var _a, _b, _c, _d;
3611
- if (!this.canvasService || !target) return;
3612
- const deltaX = (_b = (_a = matches.x) == null ? void 0 : _a.deltaScene) != null ? _b : 0;
3613
- const deltaY = (_d = (_c = matches.y) == null ? void 0 : _c.deltaScene) != null ? _d : 0;
3614
- if (!deltaX && !deltaY) return;
3615
- target.set({
3616
- left: Number(target.left || 0) + this.canvasService.toScreenLength(deltaX),
3617
- top: Number(target.top || 0) + this.canvasService.toScreenLength(deltaY)
3618
- });
3619
- target.setCoords();
3620
- }
3621
3804
  syncToolActiveFromWorkbench(fallbackId) {
3622
3805
  var _a;
3623
3806
  const wb = (_a = this.context) == null ? void 0 : _a.services.get("WorkbenchService");
@@ -4241,33 +4424,9 @@ var ImageTool = class {
4241
4424
  originY: "top",
4242
4425
  fill: hatchFill,
4243
4426
  opacity: patternFill ? 1 : 0.8,
4244
- stroke: null,
4245
- fillRule: "evenodd",
4246
- selectable: false,
4247
- evented: false,
4248
- excludeFromExport: true,
4249
- objectCaching: false
4250
- }
4251
- },
4252
- {
4253
- id: "image.cropShapePath",
4254
- type: "path",
4255
- data: { id: "image.cropShapePath", zIndex: 6 },
4256
- layout: {
4257
- reference: "custom",
4258
- referenceRect: frameRect,
4259
- alignX: "start",
4260
- alignY: "start",
4261
- offsetX: shapeBounds.x,
4262
- offsetY: shapeBounds.y
4263
- },
4264
- props: {
4265
- pathData: shapePathData,
4266
- originX: "left",
4267
- originY: "top",
4268
- fill: "rgba(0,0,0,0)",
4269
4427
  stroke: "rgba(255, 0, 0, 0.9)",
4270
4428
  strokeWidth: (_b = (_a = this.canvasService) == null ? void 0 : _a.toSceneLength(1)) != null ? _b : 1,
4429
+ fillRule: "evenodd",
4271
4430
  selectable: false,
4272
4431
  evented: false,
4273
4432
  excludeFromExport: true,
@@ -4604,7 +4763,6 @@ var ImageTool = class {
4604
4763
  isImageSelectionActive: this.isImageSelectionActive,
4605
4764
  focusedImageId: this.focusedImageId
4606
4765
  });
4607
- this.updateSnapGuideVisuals();
4608
4766
  this.canvasService.requestRenderAll();
4609
4767
  }
4610
4768
  clampNormalized(value) {
@@ -5401,225 +5559,668 @@ function createDielineConfigurations(state) {
5401
5559
  ];
5402
5560
  }
5403
5561
 
5404
- // src/extensions/dieline/DielineTool.ts
5405
- var DielineTool = class {
5406
- constructor(options) {
5407
- this.id = "pooder.kit.dieline";
5408
- this.metadata = {
5409
- name: "DielineTool"
5410
- };
5411
- this.state = {
5412
- shape: DEFAULT_DIELINE_SHAPE,
5413
- shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
5414
- width: 500,
5415
- height: 500,
5416
- radius: 0,
5417
- offset: 0,
5418
- padding: 140,
5419
- mainLine: {
5420
- width: 2.7,
5421
- color: "#FF0000",
5422
- dashLength: 5,
5423
- style: "solid"
5424
- },
5425
- offsetLine: {
5426
- width: 2.7,
5427
- color: "#FF0000",
5428
- dashLength: 5,
5429
- style: "solid"
5430
- },
5431
- insideColor: "rgba(0,0,0,0)",
5432
- showBleedLines: true,
5433
- features: []
5434
- };
5435
- this.specs = [];
5436
- this.effects = [];
5437
- this.renderSeq = 0;
5438
- this.onCanvasResized = () => {
5439
- this.updateDieline();
5440
- };
5441
- if (options) {
5442
- if (options.mainLine) {
5443
- Object.assign(this.state.mainLine, options.mainLine);
5444
- delete options.mainLine;
5445
- }
5446
- if (options.offsetLine) {
5447
- Object.assign(this.state.offsetLine, options.offsetLine);
5448
- delete options.offsetLine;
5449
- }
5450
- if (options.shapeStyle) {
5451
- this.state.shapeStyle = normalizeShapeStyle(
5452
- options.shapeStyle,
5453
- this.state.shapeStyle
5454
- );
5455
- delete options.shapeStyle;
5456
- }
5457
- Object.assign(this.state, options);
5458
- this.state.shape = normalizeDielineShape(options.shape, this.state.shape);
5562
+ // src/extensions/dieline/model.ts
5563
+ function createDefaultDielineState() {
5564
+ return {
5565
+ shape: DEFAULT_DIELINE_SHAPE,
5566
+ shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
5567
+ width: 500,
5568
+ height: 500,
5569
+ radius: 0,
5570
+ offset: 0,
5571
+ padding: 140,
5572
+ mainLine: {
5573
+ width: 2.7,
5574
+ color: "#FF0000",
5575
+ dashLength: 5,
5576
+ style: "solid"
5577
+ },
5578
+ offsetLine: {
5579
+ width: 2.7,
5580
+ color: "#FF0000",
5581
+ dashLength: 5,
5582
+ style: "solid"
5583
+ },
5584
+ insideColor: "rgba(0,0,0,0)",
5585
+ showBleedLines: true,
5586
+ features: []
5587
+ };
5588
+ }
5589
+ function readDielineState(configService, fallback) {
5590
+ const base = createDefaultDielineState();
5591
+ if (fallback) {
5592
+ Object.assign(base, fallback);
5593
+ if (fallback.mainLine) {
5594
+ base.mainLine = { ...base.mainLine, ...fallback.mainLine };
5459
5595
  }
5460
- }
5461
- activate(context) {
5462
- var _a;
5463
- this.context = context;
5464
- this.canvasService = context.services.get("CanvasService");
5465
- if (!this.canvasService) {
5466
- console.warn("CanvasService not found for DielineTool");
5467
- return;
5596
+ if (fallback.offsetLine) {
5597
+ base.offsetLine = { ...base.offsetLine, ...fallback.offsetLine };
5468
5598
  }
5469
- (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
5470
- this.renderProducerDisposable = this.canvasService.registerRenderProducer(
5471
- this.id,
5472
- () => ({
5473
- passes: [
5474
- {
5475
- id: DIELINE_LAYER_ID,
5476
- stack: 700,
5477
- order: 0,
5478
- replace: true,
5479
- visibility: {
5480
- op: "not",
5481
- expr: {
5482
- op: "activeToolIn",
5483
- ids: ["pooder.kit.image", "pooder.kit.white-ink"]
5484
- }
5485
- },
5486
- effects: this.effects,
5487
- objects: this.specs
5488
- }
5489
- ]
5490
- }),
5491
- { priority: 250 }
5492
- );
5493
- const configService = context.services.get(
5494
- "ConfigurationService"
5495
- );
5496
- if (configService) {
5497
- const s = this.state;
5498
- const sizeState = readSizeState(configService);
5499
- s.shape = normalizeDielineShape(
5500
- configService.get("dieline.shape", s.shape),
5501
- s.shape
5502
- );
5503
- s.shapeStyle = normalizeShapeStyle(
5504
- configService.get("dieline.shapeStyle", s.shapeStyle),
5505
- s.shapeStyle
5506
- );
5507
- s.width = sizeState.actualWidthMm;
5508
- s.height = sizeState.actualHeightMm;
5509
- s.radius = parseLengthToMm(
5510
- configService.get("dieline.radius", s.radius),
5511
- "mm"
5512
- );
5513
- s.padding = sizeState.viewPadding;
5514
- s.offset = sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0;
5515
- s.mainLine.width = configService.get(
5516
- "dieline.strokeWidth",
5517
- s.mainLine.width
5518
- );
5519
- s.mainLine.color = configService.get(
5520
- "dieline.strokeColor",
5521
- s.mainLine.color
5522
- );
5523
- s.mainLine.dashLength = configService.get(
5599
+ if (fallback.shapeStyle) {
5600
+ base.shapeStyle = normalizeShapeStyle(fallback.shapeStyle, base.shapeStyle);
5601
+ }
5602
+ }
5603
+ const sizeState = readSizeState(configService);
5604
+ const sourceWidth = Number(configService.get("dieline.customSourceWidthPx", 0));
5605
+ const sourceHeight = Number(
5606
+ configService.get("dieline.customSourceHeightPx", 0)
5607
+ );
5608
+ return {
5609
+ ...base,
5610
+ shape: normalizeDielineShape(
5611
+ configService.get("dieline.shape", base.shape),
5612
+ base.shape
5613
+ ),
5614
+ shapeStyle: normalizeShapeStyle(
5615
+ configService.get("dieline.shapeStyle", base.shapeStyle),
5616
+ base.shapeStyle
5617
+ ),
5618
+ width: sizeState.actualWidthMm,
5619
+ height: sizeState.actualHeightMm,
5620
+ radius: parseLengthToMm(configService.get("dieline.radius", base.radius), "mm"),
5621
+ padding: sizeState.viewPadding,
5622
+ offset: sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0,
5623
+ mainLine: {
5624
+ width: configService.get("dieline.strokeWidth", base.mainLine.width),
5625
+ color: configService.get("dieline.strokeColor", base.mainLine.color),
5626
+ dashLength: configService.get(
5524
5627
  "dieline.dashLength",
5525
- s.mainLine.dashLength
5526
- );
5527
- s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
5528
- s.offsetLine.width = configService.get(
5628
+ base.mainLine.dashLength
5629
+ ),
5630
+ style: configService.get("dieline.style", base.mainLine.style)
5631
+ },
5632
+ offsetLine: {
5633
+ width: configService.get(
5529
5634
  "dieline.offsetStrokeWidth",
5530
- s.offsetLine.width
5531
- );
5532
- s.offsetLine.color = configService.get(
5635
+ base.offsetLine.width
5636
+ ),
5637
+ color: configService.get(
5533
5638
  "dieline.offsetStrokeColor",
5534
- s.offsetLine.color
5535
- );
5536
- s.offsetLine.dashLength = configService.get(
5639
+ base.offsetLine.color
5640
+ ),
5641
+ dashLength: configService.get(
5537
5642
  "dieline.offsetDashLength",
5538
- s.offsetLine.dashLength
5539
- );
5540
- s.offsetLine.style = configService.get(
5541
- "dieline.offsetStyle",
5542
- s.offsetLine.style
5543
- );
5544
- s.insideColor = configService.get("dieline.insideColor", s.insideColor);
5545
- s.showBleedLines = configService.get(
5546
- "dieline.showBleedLines",
5547
- s.showBleedLines
5548
- );
5549
- s.features = configService.get("dieline.features", s.features);
5550
- s.pathData = configService.get("dieline.pathData", s.pathData);
5551
- const sourceWidth = Number(
5552
- configService.get("dieline.customSourceWidthPx", 0)
5553
- );
5554
- const sourceHeight = Number(
5555
- configService.get("dieline.customSourceHeightPx", 0)
5556
- );
5557
- s.customSourceWidthPx = Number.isFinite(sourceWidth) && sourceWidth > 0 ? sourceWidth : void 0;
5558
- s.customSourceHeightPx = Number.isFinite(sourceHeight) && sourceHeight > 0 ? sourceHeight : void 0;
5559
- configService.onAnyChange((e) => {
5560
- if (e.key.startsWith("size.")) {
5561
- const nextSize = readSizeState(configService);
5562
- s.width = nextSize.actualWidthMm;
5563
- s.height = nextSize.actualHeightMm;
5564
- s.padding = nextSize.viewPadding;
5565
- s.offset = nextSize.cutMode === "outset" ? nextSize.cutMarginMm : nextSize.cutMode === "inset" ? -nextSize.cutMarginMm : 0;
5566
- this.updateDieline();
5567
- return;
5643
+ base.offsetLine.dashLength
5644
+ ),
5645
+ style: configService.get("dieline.offsetStyle", base.offsetLine.style)
5646
+ },
5647
+ insideColor: configService.get("dieline.insideColor", base.insideColor),
5648
+ showBleedLines: configService.get(
5649
+ "dieline.showBleedLines",
5650
+ base.showBleedLines
5651
+ ),
5652
+ features: configService.get("dieline.features", base.features),
5653
+ pathData: configService.get("dieline.pathData", base.pathData),
5654
+ customSourceWidthPx: Number.isFinite(sourceWidth) && sourceWidth > 0 ? sourceWidth : void 0,
5655
+ customSourceHeightPx: Number.isFinite(sourceHeight) && sourceHeight > 0 ? sourceHeight : void 0
5656
+ };
5657
+ }
5658
+
5659
+ // src/extensions/constraints.ts
5660
+ var ConstraintRegistry = class {
5661
+ static register(type, handler) {
5662
+ this.handlers.set(type, handler);
5663
+ }
5664
+ static apply(x, y, feature, context, constraints) {
5665
+ const list = constraints || feature.constraints;
5666
+ if (!list || list.length === 0) {
5667
+ return { x, y };
5668
+ }
5669
+ let currentX = x;
5670
+ let currentY = y;
5671
+ for (const constraint of list) {
5672
+ const handler = this.handlers.get(constraint.type);
5673
+ if (handler) {
5674
+ const result = handler(currentX, currentY, feature, context, constraint.params || {});
5675
+ currentX = result.x;
5676
+ currentY = result.y;
5677
+ }
5678
+ }
5679
+ return { x: currentX, y: currentY };
5680
+ }
5681
+ };
5682
+ ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
5683
+ var pathConstraint = (x, y, feature, context, params) => {
5684
+ const { dielineWidth, dielineHeight, geometry } = context;
5685
+ if (!geometry) return { x, y };
5686
+ const minX = geometry.x - geometry.width / 2;
5687
+ const minY = geometry.y - geometry.height / 2;
5688
+ const absX = minX + x * geometry.width;
5689
+ const absY = minY + y * geometry.height;
5690
+ const nearest = getNearestPointOnDieline(
5691
+ { x: absX, y: absY },
5692
+ geometry
5693
+ );
5694
+ let finalX = nearest.x;
5695
+ let finalY = nearest.y;
5696
+ const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
5697
+ if (hasOffsetParams && nearest.normal) {
5698
+ const dx = absX - nearest.x;
5699
+ const dy = absY - nearest.y;
5700
+ const nx2 = nearest.normal.x;
5701
+ const ny2 = nearest.normal.y;
5702
+ const dist = dx * nx2 + dy * ny2;
5703
+ const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
5704
+ const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
5705
+ const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
5706
+ const minOffset = rawMin * scale;
5707
+ const maxOffset = rawMax * scale;
5708
+ const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
5709
+ finalX = nearest.x + nx2 * clampedDist;
5710
+ finalY = nearest.y + ny2 * clampedDist;
5711
+ }
5712
+ const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
5713
+ const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
5714
+ return { x: nx, y: ny };
5715
+ };
5716
+ var edgeConstraint = (x, y, feature, context, params) => {
5717
+ const { dielineWidth, dielineHeight } = context;
5718
+ const allowedEdges = params.allowedEdges || [
5719
+ "top",
5720
+ "bottom",
5721
+ "left",
5722
+ "right"
5723
+ ];
5724
+ const confine = params.confine || false;
5725
+ const offset = params.offset || 0;
5726
+ const distances = [];
5727
+ if (allowedEdges.includes("top"))
5728
+ distances.push({ edge: "top", dist: y * dielineHeight });
5729
+ if (allowedEdges.includes("bottom"))
5730
+ distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
5731
+ if (allowedEdges.includes("left"))
5732
+ distances.push({ edge: "left", dist: x * dielineWidth });
5733
+ if (allowedEdges.includes("right"))
5734
+ distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
5735
+ if (distances.length === 0) return { x, y };
5736
+ distances.sort((a, b) => a.dist - b.dist);
5737
+ const nearest = distances[0].edge;
5738
+ let newX = x;
5739
+ let newY = y;
5740
+ const fw = feature.width || 0;
5741
+ const fh = feature.height || 0;
5742
+ switch (nearest) {
5743
+ case "top":
5744
+ newY = 0 + offset / dielineHeight;
5745
+ if (confine) {
5746
+ const minX = fw / 2 / dielineWidth;
5747
+ const maxX = 1 - minX;
5748
+ newX = Math.max(minX, Math.min(newX, maxX));
5749
+ }
5750
+ break;
5751
+ case "bottom":
5752
+ newY = 1 - offset / dielineHeight;
5753
+ if (confine) {
5754
+ const minX = fw / 2 / dielineWidth;
5755
+ const maxX = 1 - minX;
5756
+ newX = Math.max(minX, Math.min(newX, maxX));
5757
+ }
5758
+ break;
5759
+ case "left":
5760
+ newX = 0 + offset / dielineWidth;
5761
+ if (confine) {
5762
+ const minY = fh / 2 / dielineHeight;
5763
+ const maxY = 1 - minY;
5764
+ newY = Math.max(minY, Math.min(newY, maxY));
5765
+ }
5766
+ break;
5767
+ case "right":
5768
+ newX = 1 - offset / dielineWidth;
5769
+ if (confine) {
5770
+ const minY = fh / 2 / dielineHeight;
5771
+ const maxY = 1 - minY;
5772
+ newY = Math.max(minY, Math.min(newY, maxY));
5773
+ }
5774
+ break;
5775
+ }
5776
+ return { x: newX, y: newY };
5777
+ };
5778
+ var internalConstraint = (x, y, feature, context, params) => {
5779
+ const { dielineWidth, dielineHeight } = context;
5780
+ const margin = params.margin || 0;
5781
+ const fw = feature.width || 0;
5782
+ const fh = feature.height || 0;
5783
+ const minX = (margin + fw / 2) / dielineWidth;
5784
+ const maxX = 1 - (margin + fw / 2) / dielineWidth;
5785
+ const minY = (margin + fh / 2) / dielineHeight;
5786
+ const maxY = 1 - (margin + fh / 2) / dielineHeight;
5787
+ const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
5788
+ const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
5789
+ return { x: clampedX, y: clampedY };
5790
+ };
5791
+ var tangentBottomConstraint = (x, y, feature, context, params) => {
5792
+ const { dielineWidth, dielineHeight } = context;
5793
+ const gap = params.gap || 0;
5794
+ const confineX = params.confineX !== false;
5795
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5796
+ const newY = 1 + (extentY + gap) / dielineHeight;
5797
+ let newX = x;
5798
+ if (confineX) {
5799
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5800
+ const minX = extentX / dielineWidth;
5801
+ const maxX = 1 - extentX / dielineWidth;
5802
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5803
+ }
5804
+ return { x: newX, y: newY };
5805
+ };
5806
+ var lowestTangentConstraint = (x, y, feature, context, params) => {
5807
+ const { dielineWidth, dielineHeight, geometry } = context;
5808
+ if (!geometry) return { x, y };
5809
+ const lowest = getLowestPointOnDieline(geometry);
5810
+ const minY = geometry.y - geometry.height / 2;
5811
+ const normY = (lowest.y - minY) / geometry.height;
5812
+ const gap = params.gap || 0;
5813
+ const confineX = params.confineX !== false;
5814
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5815
+ const newY = normY + (extentY + gap) / dielineHeight;
5816
+ let newX = x;
5817
+ if (confineX) {
5818
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5819
+ const minX = extentX / dielineWidth;
5820
+ const maxX = 1 - extentX / dielineWidth;
5821
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5822
+ }
5823
+ return { x: newX, y: newY };
5824
+ };
5825
+ ConstraintRegistry.register("path", pathConstraint);
5826
+ ConstraintRegistry.register("edge", edgeConstraint);
5827
+ ConstraintRegistry.register("internal", internalConstraint);
5828
+ ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
5829
+ ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
5830
+
5831
+ // src/extensions/featureCoordinates.ts
5832
+ function resolveFeaturePosition2(feature, geometry) {
5833
+ const { x, y, width, height } = geometry;
5834
+ const left = x - width / 2;
5835
+ const top = y - height / 2;
5836
+ return {
5837
+ x: left + feature.x * width,
5838
+ y: top + feature.y * height
5839
+ };
5840
+ }
5841
+ function normalizePointInGeometry(point, geometry) {
5842
+ const left = geometry.x - geometry.width / 2;
5843
+ const top = geometry.y - geometry.height / 2;
5844
+ return {
5845
+ x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
5846
+ y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5
5847
+ };
5848
+ }
5849
+
5850
+ // src/extensions/featurePlacement.ts
5851
+ function scaleFeatureForRender(feature, scale, x, y) {
5852
+ return {
5853
+ ...feature,
5854
+ x,
5855
+ y,
5856
+ width: feature.width !== void 0 ? feature.width * scale : void 0,
5857
+ height: feature.height !== void 0 ? feature.height * scale : void 0,
5858
+ radius: feature.radius !== void 0 ? feature.radius * scale : void 0
5859
+ };
5860
+ }
5861
+ function resolveFeaturePlacements(features, geometry) {
5862
+ const dielineWidth = geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
5863
+ const dielineHeight = geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
5864
+ return (features || []).map((feature) => {
5865
+ var _a;
5866
+ const activeConstraints = (_a = feature.constraints) == null ? void 0 : _a.filter(
5867
+ (constraint) => !constraint.validateOnly
5868
+ );
5869
+ const constrained = ConstraintRegistry.apply(
5870
+ feature.x,
5871
+ feature.y,
5872
+ feature,
5873
+ {
5874
+ dielineWidth,
5875
+ dielineHeight,
5876
+ geometry
5877
+ },
5878
+ activeConstraints
5879
+ );
5880
+ const center = resolveFeaturePosition2(
5881
+ {
5882
+ ...feature,
5883
+ x: constrained.x,
5884
+ y: constrained.y
5885
+ },
5886
+ geometry
5887
+ );
5888
+ return {
5889
+ feature,
5890
+ normalizedX: constrained.x,
5891
+ normalizedY: constrained.y,
5892
+ centerX: center.x,
5893
+ centerY: center.y
5894
+ };
5895
+ });
5896
+ }
5897
+ function projectPlacedFeatures(placements, geometry, scale) {
5898
+ return placements.map((placement) => {
5899
+ const normalized = normalizePointInGeometry(
5900
+ { x: placement.centerX, y: placement.centerY },
5901
+ geometry
5902
+ );
5903
+ return scaleFeatureForRender(
5904
+ placement.feature,
5905
+ scale,
5906
+ normalized.x,
5907
+ normalized.y
5908
+ );
5909
+ });
5910
+ }
5911
+
5912
+ // src/extensions/dieline/renderBuilder.ts
5913
+ var DEFAULT_IDS = {
5914
+ inside: "dieline.inside",
5915
+ bleedZone: "dieline.bleed-zone",
5916
+ offsetBorder: "dieline.offset-border",
5917
+ border: "dieline.border",
5918
+ clip: "dieline.clip.image",
5919
+ clipSource: "dieline.effect.clip-path"
5920
+ };
5921
+ function buildDielineRenderBundle(options) {
5922
+ const ids = { ...DEFAULT_IDS, ...options.ids || {} };
5923
+ const {
5924
+ state,
5925
+ sceneLayout,
5926
+ canvasWidth,
5927
+ canvasHeight,
5928
+ hasImages,
5929
+ createHatchPattern,
5930
+ includeImageClipEffect = true,
5931
+ clipTargetPassIds = [IMAGE_OBJECT_LAYER_ID],
5932
+ clipVisibility
5933
+ } = options;
5934
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } = state;
5935
+ const scale = sceneLayout.scale;
5936
+ const cx = sceneLayout.trimRect.centerX;
5937
+ const cy = sceneLayout.trimRect.centerY;
5938
+ const visualWidth = sceneLayout.trimRect.width;
5939
+ const visualHeight = sceneLayout.trimRect.height;
5940
+ const visualRadius = radius * scale;
5941
+ const cutW = sceneLayout.cutRect.width;
5942
+ const cutH = sceneLayout.cutRect.height;
5943
+ const visualOffset = (cutW - visualWidth) / 2;
5944
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5945
+ const placements = resolveFeaturePlacements(state.features || [], {
5946
+ shape,
5947
+ shapeStyle,
5948
+ pathData: state.pathData,
5949
+ customSourceWidthPx: state.customSourceWidthPx,
5950
+ customSourceHeightPx: state.customSourceHeightPx,
5951
+ canvasWidth,
5952
+ canvasHeight,
5953
+ x: cx,
5954
+ y: cy,
5955
+ width: visualWidth,
5956
+ height: visualHeight,
5957
+ radius: visualRadius,
5958
+ scale
5959
+ });
5960
+ const absoluteFeatures = projectPlacedFeatures(
5961
+ placements,
5962
+ {
5963
+ x: cx,
5964
+ y: cy,
5965
+ width: visualWidth,
5966
+ height: visualHeight
5967
+ },
5968
+ scale
5969
+ );
5970
+ const cutFeatures = projectPlacedFeatures(
5971
+ placements.filter((placement) => !placement.feature.skipCut),
5972
+ {
5973
+ x: cx,
5974
+ y: cy,
5975
+ width: cutW,
5976
+ height: cutH
5977
+ },
5978
+ scale
5979
+ );
5980
+ const common = {
5981
+ shape,
5982
+ shapeStyle,
5983
+ pathData: state.pathData,
5984
+ customSourceWidthPx: state.customSourceWidthPx,
5985
+ customSourceHeightPx: state.customSourceHeightPx,
5986
+ canvasWidth,
5987
+ canvasHeight
5988
+ };
5989
+ const specs = [];
5990
+ if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
5991
+ specs.push({
5992
+ id: ids.inside,
5993
+ type: "path",
5994
+ space: "screen",
5995
+ data: { id: ids.inside, type: "dieline" },
5996
+ props: {
5997
+ pathData: generateDielinePath({
5998
+ ...common,
5999
+ width: cutW,
6000
+ height: cutH,
6001
+ radius: cutR,
6002
+ x: cx,
6003
+ y: cy,
6004
+ features: cutFeatures
6005
+ }),
6006
+ fill: insideColor,
6007
+ stroke: null,
6008
+ selectable: false,
6009
+ evented: false,
6010
+ originX: "left",
6011
+ originY: "top"
6012
+ }
6013
+ });
6014
+ }
6015
+ if (Math.abs(visualOffset) > 1e-4) {
6016
+ const trimPathInput = {
6017
+ ...common,
6018
+ width: visualWidth,
6019
+ height: visualHeight,
6020
+ radius: visualRadius,
6021
+ x: cx,
6022
+ y: cy,
6023
+ features: cutFeatures
6024
+ };
6025
+ const cutPathInput = {
6026
+ ...common,
6027
+ width: cutW,
6028
+ height: cutH,
6029
+ radius: cutR,
6030
+ x: cx,
6031
+ y: cy,
6032
+ features: cutFeatures
6033
+ };
6034
+ if (state.showBleedLines !== false) {
6035
+ const pattern = createHatchPattern == null ? void 0 : createHatchPattern(mainLine.color);
6036
+ if (pattern) {
6037
+ specs.push({
6038
+ id: ids.bleedZone,
6039
+ type: "path",
6040
+ space: "screen",
6041
+ data: { id: ids.bleedZone, type: "dieline" },
6042
+ props: {
6043
+ pathData: generateBleedZonePath(
6044
+ trimPathInput,
6045
+ cutPathInput,
6046
+ visualOffset
6047
+ ),
6048
+ fill: pattern,
6049
+ stroke: null,
6050
+ selectable: false,
6051
+ evented: false,
6052
+ objectCaching: false,
6053
+ originX: "left",
6054
+ originY: "top"
6055
+ }
6056
+ });
6057
+ }
6058
+ }
6059
+ specs.push({
6060
+ id: ids.offsetBorder,
6061
+ type: "path",
6062
+ space: "screen",
6063
+ data: { id: ids.offsetBorder, type: "dieline" },
6064
+ props: {
6065
+ pathData: generateDielinePath(cutPathInput),
6066
+ fill: null,
6067
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
6068
+ strokeWidth: offsetLine.width,
6069
+ strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
6070
+ selectable: false,
6071
+ evented: false,
6072
+ originX: "left",
6073
+ originY: "top"
6074
+ }
6075
+ });
6076
+ }
6077
+ specs.push({
6078
+ id: ids.border,
6079
+ type: "path",
6080
+ space: "screen",
6081
+ data: { id: ids.border, type: "dieline" },
6082
+ props: {
6083
+ pathData: generateDielinePath({
6084
+ ...common,
6085
+ width: visualWidth,
6086
+ height: visualHeight,
6087
+ radius: visualRadius,
6088
+ x: cx,
6089
+ y: cy,
6090
+ features: absoluteFeatures
6091
+ }),
6092
+ fill: "transparent",
6093
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
6094
+ strokeWidth: mainLine.width,
6095
+ strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
6096
+ selectable: false,
6097
+ evented: false,
6098
+ originX: "left",
6099
+ originY: "top"
6100
+ }
6101
+ });
6102
+ if (!includeImageClipEffect) {
6103
+ return { specs, effects: [] };
6104
+ }
6105
+ const clipPathData = generateDielinePath({
6106
+ ...common,
6107
+ width: cutW,
6108
+ height: cutH,
6109
+ radius: cutR,
6110
+ x: cx,
6111
+ y: cy,
6112
+ features: cutFeatures
6113
+ });
6114
+ if (!clipPathData) {
6115
+ return { specs, effects: [] };
6116
+ }
6117
+ return {
6118
+ specs,
6119
+ effects: [
6120
+ {
6121
+ type: "clipPath",
6122
+ id: ids.clip,
6123
+ visibility: clipVisibility,
6124
+ targetPassIds: clipTargetPassIds,
6125
+ source: {
6126
+ id: ids.clipSource,
6127
+ type: "path",
6128
+ space: "screen",
6129
+ data: {
6130
+ id: ids.clipSource,
6131
+ type: "dieline-effect",
6132
+ effect: "clipPath"
6133
+ },
6134
+ props: {
6135
+ pathData: clipPathData,
6136
+ fill: "#000000",
6137
+ stroke: null,
6138
+ originX: "left",
6139
+ originY: "top",
6140
+ selectable: false,
6141
+ evented: false,
6142
+ excludeFromExport: true
6143
+ }
5568
6144
  }
5569
- if (e.key.startsWith("dieline.")) {
5570
- switch (e.key) {
5571
- case "dieline.shape":
5572
- s.shape = normalizeDielineShape(e.value, s.shape);
5573
- break;
5574
- case "dieline.shapeStyle":
5575
- s.shapeStyle = normalizeShapeStyle(e.value, s.shapeStyle);
5576
- break;
5577
- case "dieline.radius":
5578
- s.radius = parseLengthToMm(e.value, "mm");
5579
- break;
5580
- case "dieline.strokeWidth":
5581
- s.mainLine.width = e.value;
5582
- break;
5583
- case "dieline.strokeColor":
5584
- s.mainLine.color = e.value;
5585
- break;
5586
- case "dieline.dashLength":
5587
- s.mainLine.dashLength = e.value;
5588
- break;
5589
- case "dieline.style":
5590
- s.mainLine.style = e.value;
5591
- break;
5592
- case "dieline.offsetStrokeWidth":
5593
- s.offsetLine.width = e.value;
5594
- break;
5595
- case "dieline.offsetStrokeColor":
5596
- s.offsetLine.color = e.value;
5597
- break;
5598
- case "dieline.offsetDashLength":
5599
- s.offsetLine.dashLength = e.value;
5600
- break;
5601
- case "dieline.offsetStyle":
5602
- s.offsetLine.style = e.value;
5603
- break;
5604
- case "dieline.insideColor":
5605
- s.insideColor = e.value;
5606
- break;
5607
- case "dieline.showBleedLines":
5608
- s.showBleedLines = e.value;
5609
- break;
5610
- case "dieline.features":
5611
- s.features = e.value;
5612
- break;
5613
- case "dieline.pathData":
5614
- s.pathData = e.value;
5615
- break;
5616
- case "dieline.customSourceWidthPx":
5617
- s.customSourceWidthPx = Number.isFinite(Number(e.value)) && Number(e.value) > 0 ? Number(e.value) : void 0;
5618
- break;
5619
- case "dieline.customSourceHeightPx":
5620
- s.customSourceHeightPx = Number.isFinite(Number(e.value)) && Number(e.value) > 0 ? Number(e.value) : void 0;
5621
- break;
6145
+ }
6146
+ ]
6147
+ };
6148
+ }
6149
+
6150
+ // src/extensions/dieline/DielineTool.ts
6151
+ var DielineTool = class {
6152
+ constructor(options) {
6153
+ this.id = "pooder.kit.dieline";
6154
+ this.metadata = {
6155
+ name: "DielineTool"
6156
+ };
6157
+ this.state = createDefaultDielineState();
6158
+ this.specs = [];
6159
+ this.effects = [];
6160
+ this.renderSeq = 0;
6161
+ this.onCanvasResized = () => {
6162
+ this.updateDieline();
6163
+ };
6164
+ if (options) {
6165
+ if (options.mainLine) {
6166
+ Object.assign(this.state.mainLine, options.mainLine);
6167
+ delete options.mainLine;
6168
+ }
6169
+ if (options.offsetLine) {
6170
+ Object.assign(this.state.offsetLine, options.offsetLine);
6171
+ delete options.offsetLine;
6172
+ }
6173
+ if (options.shapeStyle) {
6174
+ this.state.shapeStyle = normalizeShapeStyle(
6175
+ options.shapeStyle,
6176
+ this.state.shapeStyle
6177
+ );
6178
+ delete options.shapeStyle;
6179
+ }
6180
+ Object.assign(this.state, options);
6181
+ this.state.shape = normalizeDielineShape(options.shape, this.state.shape);
6182
+ }
6183
+ }
6184
+ activate(context) {
6185
+ var _a;
6186
+ this.context = context;
6187
+ this.canvasService = context.services.get("CanvasService");
6188
+ if (!this.canvasService) {
6189
+ console.warn("CanvasService not found for DielineTool");
6190
+ return;
6191
+ }
6192
+ (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
6193
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(
6194
+ this.id,
6195
+ () => ({
6196
+ passes: [
6197
+ {
6198
+ id: DIELINE_LAYER_ID,
6199
+ stack: 700,
6200
+ order: 0,
6201
+ replace: true,
6202
+ visibility: {
6203
+ op: "not",
6204
+ expr: {
6205
+ op: "activeToolIn",
6206
+ ids: ["pooder.kit.image", "pooder.kit.white-ink"]
6207
+ }
6208
+ },
6209
+ effects: this.effects,
6210
+ objects: this.specs
5622
6211
  }
6212
+ ]
6213
+ }),
6214
+ { priority: 250 }
6215
+ );
6216
+ const configService = context.services.get(
6217
+ "ConfigurationService"
6218
+ );
6219
+ if (configService) {
6220
+ Object.assign(this.state, readDielineState(configService, this.state));
6221
+ configService.onAnyChange((e) => {
6222
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
6223
+ Object.assign(this.state, readDielineState(configService, this.state));
5623
6224
  this.updateDieline();
5624
6225
  }
5625
6226
  });
@@ -5663,299 +6264,61 @@ var DielineTool = class {
5663
6264
  return void 0;
5664
6265
  }
5665
6266
  const size = 20;
5666
- const canvas = document.createElement("canvas");
5667
- canvas.width = size;
5668
- canvas.height = size;
5669
- const ctx = canvas.getContext("2d");
5670
- if (ctx) {
5671
- ctx.clearRect(0, 0, size, size);
5672
- ctx.strokeStyle = color;
5673
- ctx.lineWidth = 1;
5674
- ctx.beginPath();
5675
- ctx.moveTo(0, size);
5676
- ctx.lineTo(size, 0);
5677
- ctx.stroke();
5678
- }
5679
- return new import_fabric3.Pattern({ source: canvas, repetition: "repeat" });
5680
- }
5681
- getConfigService() {
5682
- var _a;
5683
- return (_a = this.context) == null ? void 0 : _a.services.get(
5684
- "ConfigurationService"
5685
- );
5686
- }
5687
- hasImageItems() {
5688
- const configService = this.getConfigService();
5689
- if (!configService) return false;
5690
- const items = configService.get("image.items", []);
5691
- return Array.isArray(items) && items.length > 0;
5692
- }
5693
- syncSizeState(configService) {
5694
- const sizeState = readSizeState(configService);
5695
- this.state.width = sizeState.actualWidthMm;
5696
- this.state.height = sizeState.actualHeightMm;
5697
- this.state.padding = sizeState.viewPadding;
5698
- this.state.offset = sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0;
5699
- }
5700
- buildDielineSpecs(sceneLayout) {
5701
- var _a, _b;
5702
- const {
5703
- shape,
5704
- shapeStyle,
5705
- radius,
5706
- mainLine,
5707
- offsetLine,
5708
- insideColor,
5709
- showBleedLines,
5710
- features
5711
- } = this.state;
5712
- const hasImages = this.hasImageItems();
5713
- const canvasW = sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
5714
- const canvasH = sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
5715
- const scale = sceneLayout.scale;
5716
- const cx = sceneLayout.trimRect.centerX;
5717
- const cy = sceneLayout.trimRect.centerY;
5718
- const visualWidth = sceneLayout.trimRect.width;
5719
- const visualHeight = sceneLayout.trimRect.height;
5720
- const visualRadius = radius * scale;
5721
- const cutW = sceneLayout.cutRect.width;
5722
- const cutH = sceneLayout.cutRect.height;
5723
- const visualOffset = (cutW - visualWidth) / 2;
5724
- const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5725
- const absoluteFeatures = (features || []).map((f) => ({
5726
- ...f,
5727
- x: f.x,
5728
- y: f.y,
5729
- width: (f.width || 0) * scale,
5730
- height: (f.height || 0) * scale,
5731
- radius: (f.radius || 0) * scale
5732
- }));
5733
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
5734
- const specs = [];
5735
- if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
5736
- const productPathData = generateDielinePath({
5737
- shape,
5738
- width: cutW,
5739
- height: cutH,
5740
- radius: cutR,
5741
- x: cx,
5742
- y: cy,
5743
- features: cutFeatures,
5744
- shapeStyle,
5745
- pathData: this.state.pathData,
5746
- customSourceWidthPx: this.state.customSourceWidthPx,
5747
- customSourceHeightPx: this.state.customSourceHeightPx,
5748
- canvasWidth: canvasW,
5749
- canvasHeight: canvasH
5750
- });
5751
- specs.push({
5752
- id: "dieline.inside",
5753
- type: "path",
5754
- space: "screen",
5755
- data: { id: "dieline.inside", type: "dieline" },
5756
- props: {
5757
- pathData: productPathData,
5758
- fill: insideColor,
5759
- stroke: null,
5760
- selectable: false,
5761
- evented: false,
5762
- originX: "left",
5763
- originY: "top"
5764
- }
5765
- });
5766
- }
5767
- if (Math.abs(visualOffset) > 1e-4) {
5768
- const bleedPathData = generateBleedZonePath(
5769
- {
5770
- shape,
5771
- width: visualWidth,
5772
- height: visualHeight,
5773
- radius: visualRadius,
5774
- x: cx,
5775
- y: cy,
5776
- features: cutFeatures,
5777
- shapeStyle,
5778
- pathData: this.state.pathData,
5779
- customSourceWidthPx: this.state.customSourceWidthPx,
5780
- customSourceHeightPx: this.state.customSourceHeightPx,
5781
- canvasWidth: canvasW,
5782
- canvasHeight: canvasH
5783
- },
5784
- {
5785
- shape,
5786
- width: cutW,
5787
- height: cutH,
5788
- radius: cutR,
5789
- x: cx,
5790
- y: cy,
5791
- features: cutFeatures,
5792
- shapeStyle,
5793
- pathData: this.state.pathData,
5794
- customSourceWidthPx: this.state.customSourceWidthPx,
5795
- customSourceHeightPx: this.state.customSourceHeightPx,
5796
- canvasWidth: canvasW,
5797
- canvasHeight: canvasH
5798
- },
5799
- visualOffset
5800
- );
5801
- if (showBleedLines !== false) {
5802
- const pattern = this.createHatchPattern(mainLine.color);
5803
- if (pattern) {
5804
- specs.push({
5805
- id: "dieline.bleed-zone",
5806
- type: "path",
5807
- space: "screen",
5808
- data: { id: "dieline.bleed-zone", type: "dieline" },
5809
- props: {
5810
- pathData: bleedPathData,
5811
- fill: pattern,
5812
- stroke: null,
5813
- selectable: false,
5814
- evented: false,
5815
- objectCaching: false,
5816
- originX: "left",
5817
- originY: "top"
5818
- }
5819
- });
5820
- }
5821
- }
5822
- const offsetPathData = generateDielinePath({
5823
- shape,
5824
- width: cutW,
5825
- height: cutH,
5826
- radius: cutR,
5827
- x: cx,
5828
- y: cy,
5829
- features: cutFeatures,
5830
- shapeStyle,
5831
- pathData: this.state.pathData,
5832
- customSourceWidthPx: this.state.customSourceWidthPx,
5833
- customSourceHeightPx: this.state.customSourceHeightPx,
5834
- canvasWidth: canvasW,
5835
- canvasHeight: canvasH
5836
- });
5837
- specs.push({
5838
- id: "dieline.offset-border",
5839
- type: "path",
5840
- space: "screen",
5841
- data: { id: "dieline.offset-border", type: "dieline" },
5842
- props: {
5843
- pathData: offsetPathData,
5844
- fill: null,
5845
- stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
5846
- strokeWidth: offsetLine.width,
5847
- strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
5848
- selectable: false,
5849
- evented: false,
5850
- originX: "left",
5851
- originY: "top"
5852
- }
5853
- });
5854
- }
5855
- const borderPathData = generateDielinePath({
5856
- shape,
5857
- width: visualWidth,
5858
- height: visualHeight,
5859
- radius: visualRadius,
5860
- x: cx,
5861
- y: cy,
5862
- features: absoluteFeatures,
5863
- shapeStyle,
5864
- pathData: this.state.pathData,
5865
- customSourceWidthPx: this.state.customSourceWidthPx,
5866
- customSourceHeightPx: this.state.customSourceHeightPx,
5867
- canvasWidth: canvasW,
5868
- canvasHeight: canvasH
5869
- });
5870
- specs.push({
5871
- id: "dieline.border",
5872
- type: "path",
5873
- space: "screen",
5874
- data: { id: "dieline.border", type: "dieline" },
5875
- props: {
5876
- pathData: borderPathData,
5877
- fill: "transparent",
5878
- stroke: mainLine.style === "hidden" ? null : mainLine.color,
5879
- strokeWidth: mainLine.width,
5880
- strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
5881
- selectable: false,
5882
- evented: false,
5883
- originX: "left",
5884
- originY: "top"
5885
- }
5886
- });
5887
- return specs;
5888
- }
5889
- buildImageClipEffects(sceneLayout) {
5890
- var _a, _b;
5891
- const { shape, shapeStyle, radius, features } = this.state;
5892
- const canvasW = sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
5893
- const canvasH = sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
5894
- const scale = sceneLayout.scale;
5895
- const cx = sceneLayout.trimRect.centerX;
5896
- const cy = sceneLayout.trimRect.centerY;
5897
- const visualWidth = sceneLayout.trimRect.width;
5898
- const visualRadius = radius * scale;
5899
- const cutW = sceneLayout.cutRect.width;
5900
- const cutH = sceneLayout.cutRect.height;
5901
- const visualOffset = (cutW - visualWidth) / 2;
5902
- const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5903
- const absoluteFeatures = (features || []).map((f) => ({
5904
- ...f,
5905
- x: f.x,
5906
- y: f.y,
5907
- width: (f.width || 0) * scale,
5908
- height: (f.height || 0) * scale,
5909
- radius: (f.radius || 0) * scale
5910
- }));
5911
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
5912
- const clipPathData = generateDielinePath({
5913
- shape,
5914
- width: cutW,
5915
- height: cutH,
5916
- radius: cutR,
5917
- x: cx,
5918
- y: cy,
5919
- features: cutFeatures,
5920
- shapeStyle,
5921
- pathData: this.state.pathData,
5922
- customSourceWidthPx: this.state.customSourceWidthPx,
5923
- customSourceHeightPx: this.state.customSourceHeightPx,
5924
- canvasWidth: canvasW,
5925
- canvasHeight: canvasH
5926
- });
5927
- if (!clipPathData) return [];
5928
- return [
5929
- {
5930
- type: "clipPath",
5931
- id: "dieline.clip.image",
5932
- visibility: {
5933
- op: "not",
5934
- expr: { op: "anySessionActive" }
5935
- },
5936
- targetPassIds: [IMAGE_OBJECT_LAYER_ID],
5937
- source: {
5938
- id: "dieline.effect.clip-path",
5939
- type: "path",
5940
- space: "screen",
5941
- data: {
5942
- id: "dieline.effect.clip-path",
5943
- type: "dieline-effect",
5944
- effect: "clipPath"
5945
- },
5946
- props: {
5947
- pathData: clipPathData,
5948
- fill: "#000000",
5949
- stroke: null,
5950
- originX: "left",
5951
- originY: "top",
5952
- selectable: false,
5953
- evented: false,
5954
- excludeFromExport: true
5955
- }
5956
- }
5957
- }
5958
- ];
6267
+ const canvas = document.createElement("canvas");
6268
+ canvas.width = size;
6269
+ canvas.height = size;
6270
+ const ctx = canvas.getContext("2d");
6271
+ if (ctx) {
6272
+ ctx.clearRect(0, 0, size, size);
6273
+ ctx.strokeStyle = color;
6274
+ ctx.lineWidth = 1;
6275
+ ctx.beginPath();
6276
+ ctx.moveTo(0, size);
6277
+ ctx.lineTo(size, 0);
6278
+ ctx.stroke();
6279
+ }
6280
+ return new import_fabric3.Pattern({ source: canvas, repetition: "repeat" });
6281
+ }
6282
+ getConfigService() {
6283
+ var _a;
6284
+ return (_a = this.context) == null ? void 0 : _a.services.get(
6285
+ "ConfigurationService"
6286
+ );
6287
+ }
6288
+ hasImageItems() {
6289
+ const configService = this.getConfigService();
6290
+ if (!configService) return false;
6291
+ const items = configService.get("image.items", []);
6292
+ return Array.isArray(items) && items.length > 0;
6293
+ }
6294
+ buildDielineSpecs(sceneLayout) {
6295
+ var _a, _b;
6296
+ const hasImages = this.hasImageItems();
6297
+ return buildDielineRenderBundle({
6298
+ state: this.state,
6299
+ sceneLayout,
6300
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
6301
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
6302
+ hasImages,
6303
+ createHatchPattern: (color) => this.createHatchPattern(color),
6304
+ includeImageClipEffect: false
6305
+ }).specs;
6306
+ }
6307
+ buildImageClipEffects(sceneLayout) {
6308
+ var _a, _b;
6309
+ return buildDielineRenderBundle({
6310
+ state: this.state,
6311
+ sceneLayout,
6312
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
6313
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
6314
+ hasImages: this.hasImageItems(),
6315
+ includeImageClipEffect: true,
6316
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
6317
+ clipVisibility: {
6318
+ op: "not",
6319
+ expr: { op: "anySessionActive" }
6320
+ }
6321
+ }).effects;
5959
6322
  }
5960
6323
  updateDieline(_emitEvent = true) {
5961
6324
  void this.updateDielineAsync();
@@ -5965,7 +6328,7 @@ var DielineTool = class {
5965
6328
  const configService = this.getConfigService();
5966
6329
  if (!configService) return;
5967
6330
  const seq = ++this.renderSeq;
5968
- this.syncSizeState(configService);
6331
+ Object.assign(this.state, readDielineState(configService, this.state));
5969
6332
  const sceneLayout = computeSceneLayout(
5970
6333
  this.canvasService,
5971
6334
  readSizeState(configService)
@@ -6020,7 +6383,7 @@ var DielineTool = class {
6020
6383
  );
6021
6384
  return null;
6022
6385
  }
6023
- this.syncSizeState(configService);
6386
+ this.state = readDielineState(configService, this.state);
6024
6387
  const sceneLayout = computeSceneLayout(
6025
6388
  this.canvasService,
6026
6389
  readSizeState(configService)
@@ -6042,15 +6405,31 @@ var DielineTool = class {
6042
6405
  const visualRadius = radius * scale;
6043
6406
  const visualOffset = (cutW - sceneLayout.trimRect.width) / 2;
6044
6407
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
6045
- const absoluteFeatures = (features || []).map((f) => ({
6046
- ...f,
6047
- x: f.x,
6048
- y: f.y,
6049
- width: (f.width || 0) * scale,
6050
- height: (f.height || 0) * scale,
6051
- radius: (f.radius || 0) * scale
6052
- }));
6053
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
6408
+ const placements = resolveFeaturePlacements(features || [], {
6409
+ shape,
6410
+ shapeStyle,
6411
+ pathData,
6412
+ customSourceWidthPx: this.state.customSourceWidthPx,
6413
+ customSourceHeightPx: this.state.customSourceHeightPx,
6414
+ canvasWidth: canvasW,
6415
+ canvasHeight: canvasH,
6416
+ x: cx,
6417
+ y: cy,
6418
+ width: sceneLayout.trimRect.width,
6419
+ height: sceneLayout.trimRect.height,
6420
+ radius: visualRadius,
6421
+ scale
6422
+ });
6423
+ const cutFeatures = projectPlacedFeatures(
6424
+ placements.filter((placement) => !placement.feature.skipCut),
6425
+ {
6426
+ x: cx,
6427
+ y: cy,
6428
+ width: cutW,
6429
+ height: cutH
6430
+ },
6431
+ scale
6432
+ );
6054
6433
  const generatedPathData = generateDielinePath({
6055
6434
  shape,
6056
6435
  width: cutW,
@@ -6072,278 +6451,107 @@ var DielineTool = class {
6072
6451
  left: cx,
6073
6452
  top: cy,
6074
6453
  absolutePositioned: true
6075
- });
6076
- const pathOffsetX = Number((_a = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _a.x);
6077
- const pathOffsetY = Number((_b = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _b.y);
6078
- const centerX = Number.isFinite(pathOffsetX) ? pathOffsetX : cx;
6079
- const centerY = Number.isFinite(pathOffsetY) ? pathOffsetY : cy;
6080
- clipPath.set({
6081
- originX: "center",
6082
- originY: "center",
6083
- left: centerX,
6084
- top: centerY,
6085
- absolutePositioned: true
6086
- });
6087
- clipPath.setCoords();
6088
- const pathBounds = clipPath.getBoundingRect();
6089
- if (!Number.isFinite(pathBounds.left) || !Number.isFinite(pathBounds.top) || !Number.isFinite(pathBounds.width) || !Number.isFinite(pathBounds.height) || pathBounds.width <= 0 || pathBounds.height <= 0) {
6090
- console.warn(
6091
- "[DielineTool] exportCutImage returned null: invalid-cut-bounds",
6092
- {
6093
- bounds: pathBounds
6094
- }
6095
- );
6096
- return null;
6097
- }
6098
- const exportBounds = pathBounds;
6099
- const sourceImages = this.canvasService.canvas.getObjects().filter((obj) => {
6100
- var _a2;
6101
- return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID;
6102
- });
6103
- if (!sourceImages.length) {
6104
- console.warn(
6105
- "[DielineTool] exportCutImage returned null: no-image-objects-on-canvas"
6106
- );
6107
- return null;
6108
- }
6109
- const sourceCanvasWidth = Number(
6110
- this.canvasService.canvas.width || sceneLayout.canvasWidth || canvasW
6111
- );
6112
- const sourceCanvasHeight = Number(
6113
- this.canvasService.canvas.height || sceneLayout.canvasHeight || canvasH
6114
- );
6115
- const el = document.createElement("canvas");
6116
- const exportCanvas = new import_fabric3.Canvas(el, {
6117
- renderOnAddRemove: false,
6118
- selection: false,
6119
- enableRetinaScaling: false,
6120
- preserveObjectStacking: true
6121
- });
6122
- exportCanvas.setDimensions({
6123
- width: Math.max(1, sourceCanvasWidth),
6124
- height: Math.max(1, sourceCanvasHeight)
6125
- });
6126
- try {
6127
- for (const source of sourceImages) {
6128
- const clone = await source.clone();
6129
- clone.set({
6130
- selectable: false,
6131
- evented: false
6132
- });
6133
- clone.setCoords();
6134
- exportCanvas.add(clone);
6135
- }
6136
- exportCanvas.clipPath = clipPath;
6137
- exportCanvas.renderAll();
6138
- const dataUrl = exportCanvas.toDataURL({
6139
- format: "png",
6140
- multiplier: 2,
6141
- left: exportBounds.left,
6142
- top: exportBounds.top,
6143
- width: exportBounds.width,
6144
- height: exportBounds.height
6145
- });
6146
- if (debug) {
6147
- console.info("[DielineTool] exportCutImage success", {
6148
- sourceCount: sourceImages.length,
6149
- bounds: exportBounds,
6150
- rawPathBounds: pathBounds,
6151
- pathOffset: {
6152
- x: Number.isFinite(pathOffsetX) ? pathOffsetX : null,
6153
- y: Number.isFinite(pathOffsetY) ? pathOffsetY : null
6154
- },
6155
- clipPathCenter: {
6156
- x: centerX,
6157
- y: centerY
6158
- },
6159
- cutRect: sceneLayout.cutRect,
6160
- canvasSize: {
6161
- width: Math.max(1, sourceCanvasWidth),
6162
- height: Math.max(1, sourceCanvasHeight)
6163
- }
6164
- });
6165
- }
6166
- return dataUrl;
6167
- } finally {
6168
- exportCanvas.dispose();
6169
- }
6170
- }
6171
- };
6172
-
6173
- // src/extensions/feature/FeatureTool.ts
6174
- var import_core5 = require("@pooder/core");
6175
-
6176
- // src/extensions/constraints.ts
6177
- var ConstraintRegistry = class {
6178
- static register(type, handler) {
6179
- this.handlers.set(type, handler);
6180
- }
6181
- static apply(x, y, feature, context, constraints) {
6182
- const list = constraints || feature.constraints;
6183
- if (!list || list.length === 0) {
6184
- return { x, y };
6185
- }
6186
- let currentX = x;
6187
- let currentY = y;
6188
- for (const constraint of list) {
6189
- const handler = this.handlers.get(constraint.type);
6190
- if (handler) {
6191
- const result = handler(currentX, currentY, feature, context, constraint.params || {});
6192
- currentX = result.x;
6193
- currentY = result.y;
6194
- }
6195
- }
6196
- return { x: currentX, y: currentY };
6197
- }
6198
- };
6199
- ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
6200
- var pathConstraint = (x, y, feature, context, params) => {
6201
- const { dielineWidth, dielineHeight, geometry } = context;
6202
- if (!geometry) return { x, y };
6203
- const minX = geometry.x - geometry.width / 2;
6204
- const minY = geometry.y - geometry.height / 2;
6205
- const absX = minX + x * geometry.width;
6206
- const absY = minY + y * geometry.height;
6207
- const nearest = getNearestPointOnDieline(
6208
- { x: absX, y: absY },
6209
- geometry
6210
- );
6211
- let finalX = nearest.x;
6212
- let finalY = nearest.y;
6213
- const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
6214
- if (hasOffsetParams && nearest.normal) {
6215
- const dx = absX - nearest.x;
6216
- const dy = absY - nearest.y;
6217
- const nx2 = nearest.normal.x;
6218
- const ny2 = nearest.normal.y;
6219
- const dist = dx * nx2 + dy * ny2;
6220
- const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
6221
- const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
6222
- const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
6223
- const minOffset = rawMin * scale;
6224
- const maxOffset = rawMax * scale;
6225
- const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
6226
- finalX = nearest.x + nx2 * clampedDist;
6227
- finalY = nearest.y + ny2 * clampedDist;
6228
- }
6229
- const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
6230
- const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
6231
- return { x: nx, y: ny };
6232
- };
6233
- var edgeConstraint = (x, y, feature, context, params) => {
6234
- const { dielineWidth, dielineHeight } = context;
6235
- const allowedEdges = params.allowedEdges || [
6236
- "top",
6237
- "bottom",
6238
- "left",
6239
- "right"
6240
- ];
6241
- const confine = params.confine || false;
6242
- const offset = params.offset || 0;
6243
- const distances = [];
6244
- if (allowedEdges.includes("top"))
6245
- distances.push({ edge: "top", dist: y * dielineHeight });
6246
- if (allowedEdges.includes("bottom"))
6247
- distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
6248
- if (allowedEdges.includes("left"))
6249
- distances.push({ edge: "left", dist: x * dielineWidth });
6250
- if (allowedEdges.includes("right"))
6251
- distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
6252
- if (distances.length === 0) return { x, y };
6253
- distances.sort((a, b) => a.dist - b.dist);
6254
- const nearest = distances[0].edge;
6255
- let newX = x;
6256
- let newY = y;
6257
- const fw = feature.width || 0;
6258
- const fh = feature.height || 0;
6259
- switch (nearest) {
6260
- case "top":
6261
- newY = 0 + offset / dielineHeight;
6262
- if (confine) {
6263
- const minX = fw / 2 / dielineWidth;
6264
- const maxX = 1 - minX;
6265
- newX = Math.max(minX, Math.min(newX, maxX));
6266
- }
6267
- break;
6268
- case "bottom":
6269
- newY = 1 - offset / dielineHeight;
6270
- if (confine) {
6271
- const minX = fw / 2 / dielineWidth;
6272
- const maxX = 1 - minX;
6273
- newX = Math.max(minX, Math.min(newX, maxX));
6274
- }
6275
- break;
6276
- case "left":
6277
- newX = 0 + offset / dielineWidth;
6278
- if (confine) {
6279
- const minY = fh / 2 / dielineHeight;
6280
- const maxY = 1 - minY;
6281
- newY = Math.max(minY, Math.min(newY, maxY));
6454
+ });
6455
+ const pathOffsetX = Number((_a = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _a.x);
6456
+ const pathOffsetY = Number((_b = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _b.y);
6457
+ const centerX = Number.isFinite(pathOffsetX) ? pathOffsetX : cx;
6458
+ const centerY = Number.isFinite(pathOffsetY) ? pathOffsetY : cy;
6459
+ clipPath.set({
6460
+ originX: "center",
6461
+ originY: "center",
6462
+ left: centerX,
6463
+ top: centerY,
6464
+ absolutePositioned: true
6465
+ });
6466
+ clipPath.setCoords();
6467
+ const pathBounds = clipPath.getBoundingRect();
6468
+ if (!Number.isFinite(pathBounds.left) || !Number.isFinite(pathBounds.top) || !Number.isFinite(pathBounds.width) || !Number.isFinite(pathBounds.height) || pathBounds.width <= 0 || pathBounds.height <= 0) {
6469
+ console.warn(
6470
+ "[DielineTool] exportCutImage returned null: invalid-cut-bounds",
6471
+ {
6472
+ bounds: pathBounds
6473
+ }
6474
+ );
6475
+ return null;
6476
+ }
6477
+ const exportBounds = pathBounds;
6478
+ const sourceImages = this.canvasService.canvas.getObjects().filter((obj) => {
6479
+ var _a2;
6480
+ return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID;
6481
+ });
6482
+ if (!sourceImages.length) {
6483
+ console.warn(
6484
+ "[DielineTool] exportCutImage returned null: no-image-objects-on-canvas"
6485
+ );
6486
+ return null;
6487
+ }
6488
+ const sourceCanvasWidth = Number(
6489
+ this.canvasService.canvas.width || sceneLayout.canvasWidth || canvasW
6490
+ );
6491
+ const sourceCanvasHeight = Number(
6492
+ this.canvasService.canvas.height || sceneLayout.canvasHeight || canvasH
6493
+ );
6494
+ const el = document.createElement("canvas");
6495
+ const exportCanvas = new import_fabric3.Canvas(el, {
6496
+ renderOnAddRemove: false,
6497
+ selection: false,
6498
+ enableRetinaScaling: false,
6499
+ preserveObjectStacking: true
6500
+ });
6501
+ exportCanvas.setDimensions({
6502
+ width: Math.max(1, sourceCanvasWidth),
6503
+ height: Math.max(1, sourceCanvasHeight)
6504
+ });
6505
+ try {
6506
+ for (const source of sourceImages) {
6507
+ const clone = await source.clone();
6508
+ clone.set({
6509
+ selectable: false,
6510
+ evented: false
6511
+ });
6512
+ clone.setCoords();
6513
+ exportCanvas.add(clone);
6282
6514
  }
6283
- break;
6284
- case "right":
6285
- newX = 1 - offset / dielineWidth;
6286
- if (confine) {
6287
- const minY = fh / 2 / dielineHeight;
6288
- const maxY = 1 - minY;
6289
- newY = Math.max(minY, Math.min(newY, maxY));
6515
+ exportCanvas.clipPath = clipPath;
6516
+ exportCanvas.renderAll();
6517
+ const dataUrl = exportCanvas.toDataURL({
6518
+ format: "png",
6519
+ multiplier: 2,
6520
+ left: exportBounds.left,
6521
+ top: exportBounds.top,
6522
+ width: exportBounds.width,
6523
+ height: exportBounds.height
6524
+ });
6525
+ if (debug) {
6526
+ console.info("[DielineTool] exportCutImage success", {
6527
+ sourceCount: sourceImages.length,
6528
+ bounds: exportBounds,
6529
+ rawPathBounds: pathBounds,
6530
+ pathOffset: {
6531
+ x: Number.isFinite(pathOffsetX) ? pathOffsetX : null,
6532
+ y: Number.isFinite(pathOffsetY) ? pathOffsetY : null
6533
+ },
6534
+ clipPathCenter: {
6535
+ x: centerX,
6536
+ y: centerY
6537
+ },
6538
+ cutRect: sceneLayout.cutRect,
6539
+ canvasSize: {
6540
+ width: Math.max(1, sourceCanvasWidth),
6541
+ height: Math.max(1, sourceCanvasHeight)
6542
+ }
6543
+ });
6290
6544
  }
6291
- break;
6292
- }
6293
- return { x: newX, y: newY };
6294
- };
6295
- var internalConstraint = (x, y, feature, context, params) => {
6296
- const { dielineWidth, dielineHeight } = context;
6297
- const margin = params.margin || 0;
6298
- const fw = feature.width || 0;
6299
- const fh = feature.height || 0;
6300
- const minX = (margin + fw / 2) / dielineWidth;
6301
- const maxX = 1 - (margin + fw / 2) / dielineWidth;
6302
- const minY = (margin + fh / 2) / dielineHeight;
6303
- const maxY = 1 - (margin + fh / 2) / dielineHeight;
6304
- const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
6305
- const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
6306
- return { x: clampedX, y: clampedY };
6307
- };
6308
- var tangentBottomConstraint = (x, y, feature, context, params) => {
6309
- const { dielineWidth, dielineHeight } = context;
6310
- const gap = params.gap || 0;
6311
- const confineX = params.confineX !== false;
6312
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
6313
- const newY = 1 + (extentY + gap) / dielineHeight;
6314
- let newX = x;
6315
- if (confineX) {
6316
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
6317
- const minX = extentX / dielineWidth;
6318
- const maxX = 1 - extentX / dielineWidth;
6319
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
6320
- }
6321
- return { x: newX, y: newY };
6322
- };
6323
- var lowestTangentConstraint = (x, y, feature, context, params) => {
6324
- const { dielineWidth, dielineHeight, geometry } = context;
6325
- if (!geometry) return { x, y };
6326
- const lowest = getLowestPointOnDieline(geometry);
6327
- const minY = geometry.y - geometry.height / 2;
6328
- const normY = (lowest.y - minY) / geometry.height;
6329
- const gap = params.gap || 0;
6330
- const confineX = params.confineX !== false;
6331
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
6332
- const newY = normY + (extentY + gap) / dielineHeight;
6333
- let newX = x;
6334
- if (confineX) {
6335
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
6336
- const minX = extentX / dielineWidth;
6337
- const maxX = 1 - extentX / dielineWidth;
6338
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
6545
+ return dataUrl;
6546
+ } finally {
6547
+ exportCanvas.dispose();
6548
+ }
6339
6549
  }
6340
- return { x: newX, y: newY };
6341
6550
  };
6342
- ConstraintRegistry.register("path", pathConstraint);
6343
- ConstraintRegistry.register("edge", edgeConstraint);
6344
- ConstraintRegistry.register("internal", internalConstraint);
6345
- ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
6346
- ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
6551
+
6552
+ // src/extensions/feature/FeatureTool.ts
6553
+ var import_core5 = require("@pooder/core");
6554
+ var import_fabric4 = require("fabric");
6347
6555
 
6348
6556
  // src/extensions/featureComplete.ts
6349
6557
  function validateFeaturesStrict(features, context) {
@@ -6386,7 +6594,9 @@ var FeatureTool = class {
6386
6594
  this.isFeatureSessionActive = false;
6387
6595
  this.sessionOriginalFeatures = null;
6388
6596
  this.hasWorkingChanges = false;
6389
- this.specs = [];
6597
+ this.markerSpecs = [];
6598
+ this.sessionDielineSpecs = [];
6599
+ this.sessionDielineEffects = [];
6390
6600
  this.renderSeq = 0;
6391
6601
  this.subscriptions = new SubscriptionBag();
6392
6602
  this.handleMoving = null;
@@ -6396,7 +6606,7 @@ var FeatureTool = class {
6396
6606
  this.onToolActivated = (event) => {
6397
6607
  this.isToolActive = event.id === this.id;
6398
6608
  if (!this.isToolActive) {
6399
- this.restoreSessionFeaturesToConfig();
6609
+ this.suspendFeatureSession();
6400
6610
  }
6401
6611
  this.updateVisibility();
6402
6612
  };
@@ -6416,16 +6626,38 @@ var FeatureTool = class {
6416
6626
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
6417
6627
  this.renderProducerDisposable = this.canvasService.registerRenderProducer(
6418
6628
  this.id,
6419
- () => ({
6420
- passes: [
6629
+ () => {
6630
+ const passes = [
6421
6631
  {
6422
6632
  id: FEATURE_OVERLAY_LAYER_ID,
6423
6633
  stack: 880,
6424
6634
  order: 0,
6425
- objects: this.specs
6635
+ replace: true,
6636
+ objects: this.markerSpecs
6426
6637
  }
6427
- ]
6428
- }),
6638
+ ];
6639
+ if (this.isSessionVisible()) {
6640
+ passes.push(
6641
+ {
6642
+ id: DIELINE_LAYER_ID,
6643
+ stack: 700,
6644
+ order: 0,
6645
+ replace: false,
6646
+ visibility: { op: "const", value: false },
6647
+ objects: []
6648
+ },
6649
+ {
6650
+ id: FEATURE_DIELINE_LAYER_ID,
6651
+ stack: 705,
6652
+ order: 0,
6653
+ replace: true,
6654
+ effects: this.sessionDielineEffects,
6655
+ objects: this.sessionDielineSpecs
6656
+ }
6657
+ );
6658
+ }
6659
+ return { passes };
6660
+ },
6429
6661
  { priority: 350 }
6430
6662
  );
6431
6663
  const configService = context.services.get(
@@ -6440,12 +6672,22 @@ var FeatureTool = class {
6440
6672
  (e) => {
6441
6673
  if (this.isUpdatingConfig) return;
6442
6674
  if (e.key === "dieline.features") {
6443
- if (this.isFeatureSessionActive) return;
6675
+ if (this.isFeatureSessionActive && this.hasFeatureSessionDraft()) {
6676
+ return;
6677
+ }
6678
+ if (this.hasFeatureSessionDraft()) {
6679
+ this.clearFeatureSessionState();
6680
+ }
6444
6681
  const next = e.value || [];
6445
6682
  this.workingFeatures = this.cloneFeatures(next);
6446
6683
  this.hasWorkingChanges = false;
6447
6684
  this.redraw();
6448
6685
  this.emitWorkingChange();
6686
+ return;
6687
+ }
6688
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
6689
+ void this.refreshGeometry();
6690
+ this.redraw({ enforceConstraints: true });
6449
6691
  }
6450
6692
  }
6451
6693
  );
@@ -6461,7 +6703,8 @@ var FeatureTool = class {
6461
6703
  deactivate(context) {
6462
6704
  var _a;
6463
6705
  this.subscriptions.disposeAll();
6464
- this.restoreSessionFeaturesToConfig();
6706
+ this.restoreCommittedFeaturesToConfig();
6707
+ this.clearFeatureSessionState();
6465
6708
  (_a = this.dirtyTrackerDisposable) == null ? void 0 : _a.dispose();
6466
6709
  this.dirtyTrackerDisposable = void 0;
6467
6710
  this.teardown();
@@ -6471,6 +6714,9 @@ var FeatureTool = class {
6471
6714
  updateVisibility() {
6472
6715
  this.redraw();
6473
6716
  }
6717
+ isSessionVisible() {
6718
+ return this.isToolActive && this.isFeatureSessionActive;
6719
+ }
6474
6720
  contribute() {
6475
6721
  return {
6476
6722
  [import_core5.ContributionPointIds.TOOLS]: [
@@ -6497,15 +6743,16 @@ var FeatureTool = class {
6497
6743
  if (this.isFeatureSessionActive) {
6498
6744
  return { ok: true };
6499
6745
  }
6500
- const original = this.getCommittedFeatures();
6501
- this.sessionOriginalFeatures = this.cloneFeatures(original);
6746
+ if (!this.hasFeatureSessionDraft()) {
6747
+ const original = this.getCommittedFeatures();
6748
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
6749
+ this.setWorkingFeatures(this.cloneFeatures(original));
6750
+ this.hasWorkingChanges = false;
6751
+ }
6502
6752
  this.isFeatureSessionActive = true;
6503
6753
  await this.refreshGeometry();
6504
- this.setWorkingFeatures(this.cloneFeatures(original));
6505
- this.hasWorkingChanges = false;
6506
6754
  this.redraw();
6507
6755
  this.emitWorkingChange();
6508
- this.updateCommittedFeatures([]);
6509
6756
  return { ok: true };
6510
6757
  }
6511
6758
  },
@@ -6541,25 +6788,6 @@ var FeatureTool = class {
6541
6788
  return true;
6542
6789
  }
6543
6790
  },
6544
- {
6545
- command: "getWorkingFeatures",
6546
- title: "Get Working Features",
6547
- handler: () => {
6548
- return this.cloneFeatures(this.workingFeatures);
6549
- }
6550
- },
6551
- {
6552
- command: "setWorkingFeatures",
6553
- title: "Set Working Features",
6554
- handler: async (features) => {
6555
- await this.refreshGeometry();
6556
- this.setWorkingFeatures(this.cloneFeatures(features || []));
6557
- this.hasWorkingChanges = true;
6558
- this.redraw();
6559
- this.emitWorkingChange();
6560
- return { ok: true };
6561
- }
6562
- },
6563
6791
  {
6564
6792
  command: "rollbackFeatureSession",
6565
6793
  title: "Rollback Feature Session",
@@ -6626,17 +6854,24 @@ var FeatureTool = class {
6626
6854
  this.isUpdatingConfig = false;
6627
6855
  }
6628
6856
  }
6857
+ hasFeatureSessionDraft() {
6858
+ return Array.isArray(this.sessionOriginalFeatures);
6859
+ }
6629
6860
  clearFeatureSessionState() {
6630
6861
  this.isFeatureSessionActive = false;
6631
6862
  this.sessionOriginalFeatures = null;
6632
6863
  }
6633
- restoreSessionFeaturesToConfig() {
6634
- if (!this.isFeatureSessionActive) return;
6864
+ restoreCommittedFeaturesToConfig() {
6865
+ if (!this.hasFeatureSessionDraft()) return;
6635
6866
  const original = this.cloneFeatures(
6636
6867
  this.sessionOriginalFeatures || this.getCommittedFeatures()
6637
6868
  );
6638
6869
  this.updateCommittedFeatures(original);
6639
- this.clearFeatureSessionState();
6870
+ }
6871
+ suspendFeatureSession() {
6872
+ if (!this.isFeatureSessionActive) return;
6873
+ this.restoreCommittedFeaturesToConfig();
6874
+ this.isFeatureSessionActive = false;
6640
6875
  }
6641
6876
  emitWorkingChange() {
6642
6877
  var _a;
@@ -6658,7 +6893,7 @@ var FeatureTool = class {
6658
6893
  }
6659
6894
  async resetWorkingFeaturesFromSource() {
6660
6895
  const next = this.cloneFeatures(
6661
- this.isFeatureSessionActive && this.sessionOriginalFeatures ? this.sessionOriginalFeatures : this.getCommittedFeatures()
6896
+ this.sessionOriginalFeatures || this.getCommittedFeatures()
6662
6897
  );
6663
6898
  await this.refreshGeometry();
6664
6899
  this.setWorkingFeatures(next);
@@ -6882,11 +7117,35 @@ var FeatureTool = class {
6882
7117
  this.handleSceneGeometryChange = null;
6883
7118
  }
6884
7119
  this.renderSeq += 1;
6885
- this.specs = [];
7120
+ this.markerSpecs = [];
7121
+ this.sessionDielineSpecs = [];
7122
+ this.sessionDielineEffects = [];
6886
7123
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
6887
7124
  this.renderProducerDisposable = void 0;
6888
7125
  void this.canvasService.flushRenderFromProducers();
6889
7126
  }
7127
+ createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
7128
+ if (typeof document === "undefined") {
7129
+ return void 0;
7130
+ }
7131
+ const size = 20;
7132
+ const canvas = document.createElement("canvas");
7133
+ canvas.width = size;
7134
+ canvas.height = size;
7135
+ const ctx = canvas.getContext("2d");
7136
+ if (ctx) {
7137
+ ctx.clearRect(0, 0, size, size);
7138
+ ctx.strokeStyle = color;
7139
+ ctx.lineWidth = 1;
7140
+ ctx.beginPath();
7141
+ ctx.moveTo(0, size);
7142
+ ctx.lineTo(size, 0);
7143
+ ctx.stroke();
7144
+ }
7145
+ return new import_fabric4.Pattern({
7146
+ source: canvas
7147
+ });
7148
+ }
6890
7149
  getDraggableMarkerTarget(target) {
6891
7150
  var _a, _b;
6892
7151
  if (!this.isFeatureSessionActive || !this.isToolActive) return null;
@@ -6962,6 +7221,7 @@ var FeatureTool = class {
6962
7221
  next[index] = updatedFeature;
6963
7222
  this.setWorkingFeatures(next);
6964
7223
  this.hasWorkingChanges = true;
7224
+ this.redraw();
6965
7225
  this.emitWorkingChange();
6966
7226
  }
6967
7227
  syncGroupFromCanvas(target) {
@@ -7000,6 +7260,7 @@ var FeatureTool = class {
7000
7260
  if (!changed) return;
7001
7261
  this.setWorkingFeatures(next);
7002
7262
  this.hasWorkingChanges = true;
7263
+ this.redraw();
7003
7264
  this.emitWorkingChange();
7004
7265
  }
7005
7266
  redraw(options = {}) {
@@ -7008,7 +7269,10 @@ var FeatureTool = class {
7008
7269
  async redrawAsync(options = {}) {
7009
7270
  if (!this.canvasService) return;
7010
7271
  const seq = ++this.renderSeq;
7011
- this.specs = this.buildFeatureSpecs();
7272
+ this.markerSpecs = this.buildMarkerSpecs();
7273
+ const sessionRender = this.buildSessionDielineRender();
7274
+ this.sessionDielineSpecs = sessionRender.specs;
7275
+ this.sessionDielineEffects = sessionRender.effects;
7012
7276
  if (seq !== this.renderSeq) return;
7013
7277
  await this.canvasService.flushRenderFromProducers();
7014
7278
  if (seq !== this.renderSeq) return;
@@ -7016,15 +7280,77 @@ var FeatureTool = class {
7016
7280
  this.enforceConstraints();
7017
7281
  }
7018
7282
  }
7019
- buildFeatureSpecs() {
7283
+ buildSessionDielineRender() {
7284
+ if (!this.isSessionVisible() || !this.canvasService) {
7285
+ return { specs: [], effects: [] };
7286
+ }
7287
+ const configService = this.getConfigService();
7288
+ if (!configService) {
7289
+ return { specs: [], effects: [] };
7290
+ }
7291
+ const sceneLayout = computeSceneLayout(
7292
+ this.canvasService,
7293
+ readSizeState(configService)
7294
+ );
7295
+ if (!sceneLayout) {
7296
+ return { specs: [], effects: [] };
7297
+ }
7298
+ const state = readDielineState(configService);
7299
+ state.features = this.cloneFeatures(this.workingFeatures);
7300
+ return buildDielineRenderBundle({
7301
+ state,
7302
+ sceneLayout,
7303
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService.canvas.width || 800,
7304
+ canvasHeight: sceneLayout.canvasHeight || this.canvasService.canvas.height || 600,
7305
+ hasImages: this.hasImageItems(),
7306
+ createHatchPattern: (color) => this.createHatchPattern(color),
7307
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
7308
+ clipVisibility: { op: "const", value: true },
7309
+ ids: {
7310
+ inside: "feature.session.dieline.inside",
7311
+ bleedZone: "feature.session.dieline.bleed-zone",
7312
+ offsetBorder: "feature.session.dieline.offset-border",
7313
+ border: "feature.session.dieline.border",
7314
+ clip: "feature.session.dieline.clip.image",
7315
+ clipSource: "feature.session.dieline.effect.clip-path"
7316
+ }
7317
+ });
7318
+ }
7319
+ hasImageItems() {
7320
+ const configService = this.getConfigService();
7321
+ if (!configService) return false;
7322
+ const items = configService.get("image.items", []);
7323
+ return Array.isArray(items) && items.length > 0;
7324
+ }
7325
+ buildMarkerSpecs() {
7020
7326
  if (!this.isFeatureSessionActive || !this.currentGeometry || this.workingFeatures.length === 0) {
7021
7327
  return [];
7022
7328
  }
7023
7329
  const groups = /* @__PURE__ */ new Map();
7024
7330
  const singles = [];
7025
- this.workingFeatures.forEach((feature, index) => {
7331
+ const placements = resolveFeaturePlacements(
7332
+ this.workingFeatures,
7333
+ {
7334
+ shape: this.currentGeometry.shape,
7335
+ shapeStyle: this.currentGeometry.shapeStyle,
7336
+ pathData: this.currentGeometry.pathData,
7337
+ customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
7338
+ customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
7339
+ x: this.currentGeometry.x,
7340
+ y: this.currentGeometry.y,
7341
+ width: this.currentGeometry.width,
7342
+ height: this.currentGeometry.height,
7343
+ radius: this.currentGeometry.radius,
7344
+ scale: this.currentGeometry.scale || 1
7345
+ }
7346
+ );
7347
+ placements.forEach((placement, index) => {
7348
+ const feature = placement.feature;
7026
7349
  const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
7027
- const position = resolveFeaturePosition(feature, geometry);
7350
+ const position = {
7351
+ x: placement.centerX,
7352
+ y: placement.centerY
7353
+ };
7028
7354
  const scale = geometry.scale || 1;
7029
7355
  const marker = {
7030
7356
  feature,
@@ -7289,7 +7615,7 @@ var FeatureTool = class {
7289
7615
 
7290
7616
  // src/extensions/film/FilmTool.ts
7291
7617
  var import_core6 = require("@pooder/core");
7292
- var import_fabric4 = require("fabric");
7618
+ var import_fabric5 = require("fabric");
7293
7619
  var FILM_IMAGE_ID = "film-image";
7294
7620
  var DEFAULT_WIDTH2 = 800;
7295
7621
  var DEFAULT_HEIGHT2 = 600;
@@ -7463,7 +7789,7 @@ var FilmTool = class {
7463
7789
  }
7464
7790
  async loadImageSize(src) {
7465
7791
  try {
7466
- const image = await import_fabric4.FabricImage.fromURL(src, {
7792
+ const image = await import_fabric5.FabricImage.fromURL(src, {
7467
7793
  crossOrigin: "anonymous"
7468
7794
  });
7469
7795
  const width = Number((image == null ? void 0 : image.width) || 0);
@@ -7595,11 +7921,12 @@ var EXTENSION_LINE_LENGTH = 5;
7595
7921
  var MIN_ARROW_SIZE = 4;
7596
7922
  var THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
7597
7923
  var DEFAULT_THICKNESS = 20;
7598
- var DEFAULT_GAP = 45;
7924
+ var DEFAULT_GAP = 65;
7599
7925
  var DEFAULT_FONT_SIZE = 10;
7600
7926
  var DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
7601
7927
  var DEFAULT_TEXT_COLOR = "#333333";
7602
7928
  var DEFAULT_LINE_COLOR = "#999999";
7929
+ var RULER_DEBUG_KEY = "ruler.debug";
7603
7930
  var RULER_THICKNESS_MIN = 10;
7604
7931
  var RULER_THICKNESS_MAX = 100;
7605
7932
  var RULER_GAP_MIN = 0;
@@ -7618,6 +7945,7 @@ var RulerTool = class {
7618
7945
  this.textColor = DEFAULT_TEXT_COLOR;
7619
7946
  this.lineColor = DEFAULT_LINE_COLOR;
7620
7947
  this.fontSize = DEFAULT_FONT_SIZE;
7948
+ this.debugEnabled = false;
7621
7949
  this.renderSeq = 0;
7622
7950
  this.numericProps = /* @__PURE__ */ new Set(["thickness", "gap", "fontSize"]);
7623
7951
  this.specs = [];
@@ -7666,7 +7994,14 @@ var RulerTool = class {
7666
7994
  this.syncConfig(configService);
7667
7995
  configService.onAnyChange((e) => {
7668
7996
  let shouldUpdate = false;
7669
- if (e.key.startsWith("ruler.")) {
7997
+ if (e.key === RULER_DEBUG_KEY) {
7998
+ this.debugEnabled = e.value === true;
7999
+ this.log("config:update", {
8000
+ key: e.key,
8001
+ raw: e.value,
8002
+ normalized: this.debugEnabled
8003
+ });
8004
+ } else if (e.key.startsWith("ruler.")) {
7670
8005
  const prop = e.key.split(".")[1];
7671
8006
  if (prop && prop in this) {
7672
8007
  if (this.numericProps.has(prop)) {
@@ -7753,6 +8088,12 @@ var RulerTool = class {
7753
8088
  min: RULER_FONT_SIZE_MIN,
7754
8089
  max: RULER_FONT_SIZE_MAX,
7755
8090
  default: DEFAULT_FONT_SIZE
8091
+ },
8092
+ {
8093
+ id: RULER_DEBUG_KEY,
8094
+ type: "boolean",
8095
+ label: "Ruler Debug Log",
8096
+ default: false
7756
8097
  }
7757
8098
  ],
7758
8099
  [import_core8.ContributionPointIds.COMMANDS]: [
@@ -7789,7 +8130,11 @@ var RulerTool = class {
7789
8130
  ]
7790
8131
  };
7791
8132
  }
8133
+ isDebugEnabled() {
8134
+ return this.debugEnabled;
8135
+ }
7792
8136
  log(step, payload) {
8137
+ if (!this.isDebugEnabled()) return;
7793
8138
  if (payload) {
7794
8139
  console.debug(`[RulerTool] ${step}`, payload);
7795
8140
  return;
@@ -7818,6 +8163,7 @@ var RulerTool = class {
7818
8163
  configService.get("ruler.fontSize", this.fontSize),
7819
8164
  DEFAULT_FONT_SIZE
7820
8165
  );
8166
+ this.debugEnabled = configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
7821
8167
  this.log("config:loaded", {
7822
8168
  thickness: this.thickness,
7823
8169
  gap: this.gap,
@@ -9458,7 +9804,7 @@ var SceneLayoutService = class {
9458
9804
  };
9459
9805
 
9460
9806
  // src/services/CanvasService.ts
9461
- var import_fabric5 = require("fabric");
9807
+ var import_fabric6 = require("fabric");
9462
9808
  var import_core11 = require("@pooder/core");
9463
9809
 
9464
9810
  // src/services/ViewportSystem.ts
@@ -9634,10 +9980,10 @@ var CanvasService = class {
9634
9980
  if (this.producerApplyInProgress) return;
9635
9981
  this.scheduleManagedPassVisibilityRefresh();
9636
9982
  };
9637
- if (el instanceof import_fabric5.Canvas) {
9983
+ if (el instanceof import_fabric6.Canvas) {
9638
9984
  this.canvas = el;
9639
9985
  } else {
9640
- this.canvas = new import_fabric5.Canvas(el, {
9986
+ this.canvas = new import_fabric6.Canvas(el, {
9641
9987
  preserveObjectStacking: true,
9642
9988
  ...options
9643
9989
  });
@@ -10532,7 +10878,7 @@ var CanvasService = class {
10532
10878
  var _a, _b;
10533
10879
  if (spec.type === "rect") {
10534
10880
  const props = this.resolveFabricProps(spec, spec.props || {});
10535
- const rect = new import_fabric5.Rect({
10881
+ const rect = new import_fabric6.Rect({
10536
10882
  ...props,
10537
10883
  data: { ...spec.data || {}, id: spec.id }
10538
10884
  });
@@ -10543,7 +10889,7 @@ var CanvasService = class {
10543
10889
  const pathData = this.readPathDataFromSpec(spec);
10544
10890
  if (!pathData) return void 0;
10545
10891
  const props = this.resolveFabricProps(spec, spec.props || {});
10546
- const path = new import_fabric5.Path(pathData, {
10892
+ const path = new import_fabric6.Path(pathData, {
10547
10893
  ...props,
10548
10894
  data: { ...spec.data || {}, id: spec.id }
10549
10895
  });
@@ -10552,7 +10898,7 @@ var CanvasService = class {
10552
10898
  }
10553
10899
  if (spec.type === "image") {
10554
10900
  if (!spec.src) return void 0;
10555
- const image = await import_fabric5.Image.fromURL(spec.src, { crossOrigin: "anonymous" });
10901
+ const image = await import_fabric6.Image.fromURL(spec.src, { crossOrigin: "anonymous" });
10556
10902
  const props = this.resolveFabricProps(spec, spec.props || {});
10557
10903
  image.set({
10558
10904
  ...props,
@@ -10564,7 +10910,7 @@ var CanvasService = class {
10564
10910
  if (spec.type === "text") {
10565
10911
  const content = String((_b = (_a = spec.props) == null ? void 0 : _a.text) != null ? _b : "");
10566
10912
  const props = this.resolveFabricProps(spec, spec.props || {});
10567
- const text = new import_fabric5.Text(content, {
10913
+ const text = new import_fabric6.Text(content, {
10568
10914
  ...props,
10569
10915
  data: { ...spec.data || {}, id: spec.id }
10570
10916
  });
@@ -10590,11 +10936,13 @@ var CanvasService = class {
10590
10936
  WhiteInkTool,
10591
10937
  computeImageCoverScale,
10592
10938
  computeWhiteInkCoverScale,
10939
+ createDefaultDielineState,
10593
10940
  createDielineCommands,
10594
10941
  createDielineConfigurations,
10595
10942
  createImageCommands,
10596
10943
  createImageConfigurations,
10597
10944
  createWhiteInkCommands,
10598
10945
  createWhiteInkConfigurations,
10599
- evaluateVisibilityExpr
10946
+ evaluateVisibilityExpr,
10947
+ readDielineState
10600
10948
  });