@pooder/kit 6.1.2 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,392 @@ 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/dieline/renderBuilder.ts
5660
+ var DEFAULT_IDS = {
5661
+ inside: "dieline.inside",
5662
+ bleedZone: "dieline.bleed-zone",
5663
+ offsetBorder: "dieline.offset-border",
5664
+ border: "dieline.border",
5665
+ clip: "dieline.clip.image",
5666
+ clipSource: "dieline.effect.clip-path"
5667
+ };
5668
+ function scaleFeatures(state, scale) {
5669
+ return (state.features || []).map((feature) => ({
5670
+ ...feature,
5671
+ x: feature.x,
5672
+ y: feature.y,
5673
+ width: (feature.width || 0) * scale,
5674
+ height: (feature.height || 0) * scale,
5675
+ radius: (feature.radius || 0) * scale
5676
+ }));
5677
+ }
5678
+ function buildDielineRenderBundle(options) {
5679
+ const ids = { ...DEFAULT_IDS, ...options.ids || {} };
5680
+ const {
5681
+ state,
5682
+ sceneLayout,
5683
+ canvasWidth,
5684
+ canvasHeight,
5685
+ hasImages,
5686
+ createHatchPattern,
5687
+ includeImageClipEffect = true,
5688
+ clipTargetPassIds = [IMAGE_OBJECT_LAYER_ID],
5689
+ clipVisibility
5690
+ } = options;
5691
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } = state;
5692
+ const scale = sceneLayout.scale;
5693
+ const cx = sceneLayout.trimRect.centerX;
5694
+ const cy = sceneLayout.trimRect.centerY;
5695
+ const visualWidth = sceneLayout.trimRect.width;
5696
+ const visualHeight = sceneLayout.trimRect.height;
5697
+ const visualRadius = radius * scale;
5698
+ const cutW = sceneLayout.cutRect.width;
5699
+ const cutH = sceneLayout.cutRect.height;
5700
+ const visualOffset = (cutW - visualWidth) / 2;
5701
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5702
+ const absoluteFeatures = scaleFeatures(state, scale);
5703
+ const cutFeatures = absoluteFeatures.filter((feature) => !feature.skipCut);
5704
+ const common = {
5705
+ shape,
5706
+ shapeStyle,
5707
+ pathData: state.pathData,
5708
+ customSourceWidthPx: state.customSourceWidthPx,
5709
+ customSourceHeightPx: state.customSourceHeightPx,
5710
+ canvasWidth,
5711
+ canvasHeight
5712
+ };
5713
+ const specs = [];
5714
+ if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
5715
+ specs.push({
5716
+ id: ids.inside,
5717
+ type: "path",
5718
+ space: "screen",
5719
+ data: { id: ids.inside, type: "dieline" },
5720
+ props: {
5721
+ pathData: generateDielinePath({
5722
+ ...common,
5723
+ width: cutW,
5724
+ height: cutH,
5725
+ radius: cutR,
5726
+ x: cx,
5727
+ y: cy,
5728
+ features: cutFeatures
5729
+ }),
5730
+ fill: insideColor,
5731
+ stroke: null,
5732
+ selectable: false,
5733
+ evented: false,
5734
+ originX: "left",
5735
+ originY: "top"
5736
+ }
5737
+ });
5738
+ }
5739
+ if (Math.abs(visualOffset) > 1e-4) {
5740
+ const trimPathInput = {
5741
+ ...common,
5742
+ width: visualWidth,
5743
+ height: visualHeight,
5744
+ radius: visualRadius,
5745
+ x: cx,
5746
+ y: cy,
5747
+ features: cutFeatures
5748
+ };
5749
+ const cutPathInput = {
5750
+ ...common,
5751
+ width: cutW,
5752
+ height: cutH,
5753
+ radius: cutR,
5754
+ x: cx,
5755
+ y: cy,
5756
+ features: cutFeatures
5757
+ };
5758
+ if (state.showBleedLines !== false) {
5759
+ const pattern = createHatchPattern == null ? void 0 : createHatchPattern(mainLine.color);
5760
+ if (pattern) {
5761
+ specs.push({
5762
+ id: ids.bleedZone,
5763
+ type: "path",
5764
+ space: "screen",
5765
+ data: { id: ids.bleedZone, type: "dieline" },
5766
+ props: {
5767
+ pathData: generateBleedZonePath(
5768
+ trimPathInput,
5769
+ cutPathInput,
5770
+ visualOffset
5771
+ ),
5772
+ fill: pattern,
5773
+ stroke: null,
5774
+ selectable: false,
5775
+ evented: false,
5776
+ objectCaching: false,
5777
+ originX: "left",
5778
+ originY: "top"
5779
+ }
5780
+ });
5781
+ }
5782
+ }
5783
+ specs.push({
5784
+ id: ids.offsetBorder,
5785
+ type: "path",
5786
+ space: "screen",
5787
+ data: { id: ids.offsetBorder, type: "dieline" },
5788
+ props: {
5789
+ pathData: generateDielinePath(cutPathInput),
5790
+ fill: null,
5791
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
5792
+ strokeWidth: offsetLine.width,
5793
+ strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
5794
+ selectable: false,
5795
+ evented: false,
5796
+ originX: "left",
5797
+ originY: "top"
5798
+ }
5799
+ });
5800
+ }
5801
+ specs.push({
5802
+ id: ids.border,
5803
+ type: "path",
5804
+ space: "screen",
5805
+ data: { id: ids.border, type: "dieline" },
5806
+ props: {
5807
+ pathData: generateDielinePath({
5808
+ ...common,
5809
+ width: visualWidth,
5810
+ height: visualHeight,
5811
+ radius: visualRadius,
5812
+ x: cx,
5813
+ y: cy,
5814
+ features: absoluteFeatures
5815
+ }),
5816
+ fill: "transparent",
5817
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
5818
+ strokeWidth: mainLine.width,
5819
+ strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
5820
+ selectable: false,
5821
+ evented: false,
5822
+ originX: "left",
5823
+ originY: "top"
5824
+ }
5825
+ });
5826
+ if (!includeImageClipEffect) {
5827
+ return { specs, effects: [] };
5828
+ }
5829
+ const clipPathData = generateDielinePath({
5830
+ ...common,
5831
+ width: cutW,
5832
+ height: cutH,
5833
+ radius: cutR,
5834
+ x: cx,
5835
+ y: cy,
5836
+ features: cutFeatures
5837
+ });
5838
+ if (!clipPathData) {
5839
+ return { specs, effects: [] };
5840
+ }
5841
+ return {
5842
+ specs,
5843
+ effects: [
5844
+ {
5845
+ type: "clipPath",
5846
+ id: ids.clip,
5847
+ visibility: clipVisibility,
5848
+ targetPassIds: clipTargetPassIds,
5849
+ source: {
5850
+ id: ids.clipSource,
5851
+ type: "path",
5852
+ space: "screen",
5853
+ data: {
5854
+ id: ids.clipSource,
5855
+ type: "dieline-effect",
5856
+ effect: "clipPath"
5857
+ },
5858
+ props: {
5859
+ pathData: clipPathData,
5860
+ fill: "#000000",
5861
+ stroke: null,
5862
+ originX: "left",
5863
+ originY: "top",
5864
+ selectable: false,
5865
+ evented: false,
5866
+ excludeFromExport: true
5867
+ }
5568
5868
  }
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;
5869
+ }
5870
+ ]
5871
+ };
5872
+ }
5873
+
5874
+ // src/extensions/dieline/DielineTool.ts
5875
+ var DielineTool = class {
5876
+ constructor(options) {
5877
+ this.id = "pooder.kit.dieline";
5878
+ this.metadata = {
5879
+ name: "DielineTool"
5880
+ };
5881
+ this.state = createDefaultDielineState();
5882
+ this.specs = [];
5883
+ this.effects = [];
5884
+ this.renderSeq = 0;
5885
+ this.onCanvasResized = () => {
5886
+ this.updateDieline();
5887
+ };
5888
+ if (options) {
5889
+ if (options.mainLine) {
5890
+ Object.assign(this.state.mainLine, options.mainLine);
5891
+ delete options.mainLine;
5892
+ }
5893
+ if (options.offsetLine) {
5894
+ Object.assign(this.state.offsetLine, options.offsetLine);
5895
+ delete options.offsetLine;
5896
+ }
5897
+ if (options.shapeStyle) {
5898
+ this.state.shapeStyle = normalizeShapeStyle(
5899
+ options.shapeStyle,
5900
+ this.state.shapeStyle
5901
+ );
5902
+ delete options.shapeStyle;
5903
+ }
5904
+ Object.assign(this.state, options);
5905
+ this.state.shape = normalizeDielineShape(options.shape, this.state.shape);
5906
+ }
5907
+ }
5908
+ activate(context) {
5909
+ var _a;
5910
+ this.context = context;
5911
+ this.canvasService = context.services.get("CanvasService");
5912
+ if (!this.canvasService) {
5913
+ console.warn("CanvasService not found for DielineTool");
5914
+ return;
5915
+ }
5916
+ (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
5917
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(
5918
+ this.id,
5919
+ () => ({
5920
+ passes: [
5921
+ {
5922
+ id: DIELINE_LAYER_ID,
5923
+ stack: 700,
5924
+ order: 0,
5925
+ replace: true,
5926
+ visibility: {
5927
+ op: "not",
5928
+ expr: {
5929
+ op: "activeToolIn",
5930
+ ids: ["pooder.kit.image", "pooder.kit.white-ink"]
5931
+ }
5932
+ },
5933
+ effects: this.effects,
5934
+ objects: this.specs
5622
5935
  }
5936
+ ]
5937
+ }),
5938
+ { priority: 250 }
5939
+ );
5940
+ const configService = context.services.get(
5941
+ "ConfigurationService"
5942
+ );
5943
+ if (configService) {
5944
+ Object.assign(this.state, readDielineState(configService, this.state));
5945
+ configService.onAnyChange((e) => {
5946
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
5947
+ Object.assign(this.state, readDielineState(configService, this.state));
5623
5948
  this.updateDieline();
5624
5949
  }
5625
5950
  });
@@ -5690,272 +6015,34 @@ var DielineTool = class {
5690
6015
  const items = configService.get("image.items", []);
5691
6016
  return Array.isArray(items) && items.length > 0;
5692
6017
  }
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
6018
  buildDielineSpecs(sceneLayout) {
5701
6019
  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
6020
  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;
6021
+ return buildDielineRenderBundle({
6022
+ state: this.state,
6023
+ sceneLayout,
6024
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
6025
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
6026
+ hasImages,
6027
+ createHatchPattern: (color) => this.createHatchPattern(color),
6028
+ includeImageClipEffect: false
6029
+ }).specs;
5888
6030
  }
5889
6031
  buildImageClipEffects(sceneLayout) {
5890
6032
  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
- ];
6033
+ return buildDielineRenderBundle({
6034
+ state: this.state,
6035
+ sceneLayout,
6036
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
6037
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
6038
+ hasImages: this.hasImageItems(),
6039
+ includeImageClipEffect: true,
6040
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
6041
+ clipVisibility: {
6042
+ op: "not",
6043
+ expr: { op: "anySessionActive" }
6044
+ }
6045
+ }).effects;
5959
6046
  }
5960
6047
  updateDieline(_emitEvent = true) {
5961
6048
  void this.updateDielineAsync();
@@ -5965,7 +6052,7 @@ var DielineTool = class {
5965
6052
  const configService = this.getConfigService();
5966
6053
  if (!configService) return;
5967
6054
  const seq = ++this.renderSeq;
5968
- this.syncSizeState(configService);
6055
+ Object.assign(this.state, readDielineState(configService, this.state));
5969
6056
  const sceneLayout = computeSceneLayout(
5970
6057
  this.canvasService,
5971
6058
  readSizeState(configService)
@@ -6020,7 +6107,7 @@ var DielineTool = class {
6020
6107
  );
6021
6108
  return null;
6022
6109
  }
6023
- this.syncSizeState(configService);
6110
+ this.state = readDielineState(configService, this.state);
6024
6111
  const sceneLayout = computeSceneLayout(
6025
6112
  this.canvasService,
6026
6113
  readSizeState(configService)
@@ -6172,6 +6259,7 @@ var DielineTool = class {
6172
6259
 
6173
6260
  // src/extensions/feature/FeatureTool.ts
6174
6261
  var import_core5 = require("@pooder/core");
6262
+ var import_fabric4 = require("fabric");
6175
6263
 
6176
6264
  // src/extensions/constraints.ts
6177
6265
  var ConstraintRegistry = class {
@@ -6386,7 +6474,9 @@ var FeatureTool = class {
6386
6474
  this.isFeatureSessionActive = false;
6387
6475
  this.sessionOriginalFeatures = null;
6388
6476
  this.hasWorkingChanges = false;
6389
- this.specs = [];
6477
+ this.markerSpecs = [];
6478
+ this.sessionDielineSpecs = [];
6479
+ this.sessionDielineEffects = [];
6390
6480
  this.renderSeq = 0;
6391
6481
  this.subscriptions = new SubscriptionBag();
6392
6482
  this.handleMoving = null;
@@ -6396,7 +6486,7 @@ var FeatureTool = class {
6396
6486
  this.onToolActivated = (event) => {
6397
6487
  this.isToolActive = event.id === this.id;
6398
6488
  if (!this.isToolActive) {
6399
- this.restoreSessionFeaturesToConfig();
6489
+ this.suspendFeatureSession();
6400
6490
  }
6401
6491
  this.updateVisibility();
6402
6492
  };
@@ -6416,16 +6506,38 @@ var FeatureTool = class {
6416
6506
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
6417
6507
  this.renderProducerDisposable = this.canvasService.registerRenderProducer(
6418
6508
  this.id,
6419
- () => ({
6420
- passes: [
6509
+ () => {
6510
+ const passes = [
6421
6511
  {
6422
6512
  id: FEATURE_OVERLAY_LAYER_ID,
6423
6513
  stack: 880,
6424
6514
  order: 0,
6425
- objects: this.specs
6515
+ replace: true,
6516
+ objects: this.markerSpecs
6426
6517
  }
6427
- ]
6428
- }),
6518
+ ];
6519
+ if (this.isSessionVisible()) {
6520
+ passes.push(
6521
+ {
6522
+ id: DIELINE_LAYER_ID,
6523
+ stack: 700,
6524
+ order: 0,
6525
+ replace: false,
6526
+ visibility: { op: "const", value: false },
6527
+ objects: []
6528
+ },
6529
+ {
6530
+ id: FEATURE_DIELINE_LAYER_ID,
6531
+ stack: 705,
6532
+ order: 0,
6533
+ replace: true,
6534
+ effects: this.sessionDielineEffects,
6535
+ objects: this.sessionDielineSpecs
6536
+ }
6537
+ );
6538
+ }
6539
+ return { passes };
6540
+ },
6429
6541
  { priority: 350 }
6430
6542
  );
6431
6543
  const configService = context.services.get(
@@ -6440,12 +6552,22 @@ var FeatureTool = class {
6440
6552
  (e) => {
6441
6553
  if (this.isUpdatingConfig) return;
6442
6554
  if (e.key === "dieline.features") {
6443
- if (this.isFeatureSessionActive) return;
6555
+ if (this.isFeatureSessionActive && this.hasFeatureSessionDraft()) {
6556
+ return;
6557
+ }
6558
+ if (this.hasFeatureSessionDraft()) {
6559
+ this.clearFeatureSessionState();
6560
+ }
6444
6561
  const next = e.value || [];
6445
6562
  this.workingFeatures = this.cloneFeatures(next);
6446
6563
  this.hasWorkingChanges = false;
6447
6564
  this.redraw();
6448
6565
  this.emitWorkingChange();
6566
+ return;
6567
+ }
6568
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
6569
+ void this.refreshGeometry();
6570
+ this.redraw({ enforceConstraints: true });
6449
6571
  }
6450
6572
  }
6451
6573
  );
@@ -6461,7 +6583,8 @@ var FeatureTool = class {
6461
6583
  deactivate(context) {
6462
6584
  var _a;
6463
6585
  this.subscriptions.disposeAll();
6464
- this.restoreSessionFeaturesToConfig();
6586
+ this.restoreCommittedFeaturesToConfig();
6587
+ this.clearFeatureSessionState();
6465
6588
  (_a = this.dirtyTrackerDisposable) == null ? void 0 : _a.dispose();
6466
6589
  this.dirtyTrackerDisposable = void 0;
6467
6590
  this.teardown();
@@ -6471,6 +6594,9 @@ var FeatureTool = class {
6471
6594
  updateVisibility() {
6472
6595
  this.redraw();
6473
6596
  }
6597
+ isSessionVisible() {
6598
+ return this.isToolActive && this.isFeatureSessionActive;
6599
+ }
6474
6600
  contribute() {
6475
6601
  return {
6476
6602
  [import_core5.ContributionPointIds.TOOLS]: [
@@ -6497,15 +6623,16 @@ var FeatureTool = class {
6497
6623
  if (this.isFeatureSessionActive) {
6498
6624
  return { ok: true };
6499
6625
  }
6500
- const original = this.getCommittedFeatures();
6501
- this.sessionOriginalFeatures = this.cloneFeatures(original);
6626
+ if (!this.hasFeatureSessionDraft()) {
6627
+ const original = this.getCommittedFeatures();
6628
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
6629
+ this.setWorkingFeatures(this.cloneFeatures(original));
6630
+ this.hasWorkingChanges = false;
6631
+ }
6502
6632
  this.isFeatureSessionActive = true;
6503
6633
  await this.refreshGeometry();
6504
- this.setWorkingFeatures(this.cloneFeatures(original));
6505
- this.hasWorkingChanges = false;
6506
6634
  this.redraw();
6507
6635
  this.emitWorkingChange();
6508
- this.updateCommittedFeatures([]);
6509
6636
  return { ok: true };
6510
6637
  }
6511
6638
  },
@@ -6541,25 +6668,6 @@ var FeatureTool = class {
6541
6668
  return true;
6542
6669
  }
6543
6670
  },
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
6671
  {
6564
6672
  command: "rollbackFeatureSession",
6565
6673
  title: "Rollback Feature Session",
@@ -6626,17 +6734,24 @@ var FeatureTool = class {
6626
6734
  this.isUpdatingConfig = false;
6627
6735
  }
6628
6736
  }
6737
+ hasFeatureSessionDraft() {
6738
+ return Array.isArray(this.sessionOriginalFeatures);
6739
+ }
6629
6740
  clearFeatureSessionState() {
6630
6741
  this.isFeatureSessionActive = false;
6631
6742
  this.sessionOriginalFeatures = null;
6632
6743
  }
6633
- restoreSessionFeaturesToConfig() {
6634
- if (!this.isFeatureSessionActive) return;
6744
+ restoreCommittedFeaturesToConfig() {
6745
+ if (!this.hasFeatureSessionDraft()) return;
6635
6746
  const original = this.cloneFeatures(
6636
6747
  this.sessionOriginalFeatures || this.getCommittedFeatures()
6637
6748
  );
6638
6749
  this.updateCommittedFeatures(original);
6639
- this.clearFeatureSessionState();
6750
+ }
6751
+ suspendFeatureSession() {
6752
+ if (!this.isFeatureSessionActive) return;
6753
+ this.restoreCommittedFeaturesToConfig();
6754
+ this.isFeatureSessionActive = false;
6640
6755
  }
6641
6756
  emitWorkingChange() {
6642
6757
  var _a;
@@ -6658,7 +6773,7 @@ var FeatureTool = class {
6658
6773
  }
6659
6774
  async resetWorkingFeaturesFromSource() {
6660
6775
  const next = this.cloneFeatures(
6661
- this.isFeatureSessionActive && this.sessionOriginalFeatures ? this.sessionOriginalFeatures : this.getCommittedFeatures()
6776
+ this.sessionOriginalFeatures || this.getCommittedFeatures()
6662
6777
  );
6663
6778
  await this.refreshGeometry();
6664
6779
  this.setWorkingFeatures(next);
@@ -6882,11 +6997,35 @@ var FeatureTool = class {
6882
6997
  this.handleSceneGeometryChange = null;
6883
6998
  }
6884
6999
  this.renderSeq += 1;
6885
- this.specs = [];
7000
+ this.markerSpecs = [];
7001
+ this.sessionDielineSpecs = [];
7002
+ this.sessionDielineEffects = [];
6886
7003
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
6887
7004
  this.renderProducerDisposable = void 0;
6888
7005
  void this.canvasService.flushRenderFromProducers();
6889
7006
  }
7007
+ createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
7008
+ if (typeof document === "undefined") {
7009
+ return void 0;
7010
+ }
7011
+ const size = 20;
7012
+ const canvas = document.createElement("canvas");
7013
+ canvas.width = size;
7014
+ canvas.height = size;
7015
+ const ctx = canvas.getContext("2d");
7016
+ if (ctx) {
7017
+ ctx.clearRect(0, 0, size, size);
7018
+ ctx.strokeStyle = color;
7019
+ ctx.lineWidth = 1;
7020
+ ctx.beginPath();
7021
+ ctx.moveTo(0, size);
7022
+ ctx.lineTo(size, 0);
7023
+ ctx.stroke();
7024
+ }
7025
+ return new import_fabric4.Pattern({
7026
+ source: canvas
7027
+ });
7028
+ }
6890
7029
  getDraggableMarkerTarget(target) {
6891
7030
  var _a, _b;
6892
7031
  if (!this.isFeatureSessionActive || !this.isToolActive) return null;
@@ -6962,6 +7101,7 @@ var FeatureTool = class {
6962
7101
  next[index] = updatedFeature;
6963
7102
  this.setWorkingFeatures(next);
6964
7103
  this.hasWorkingChanges = true;
7104
+ this.redraw();
6965
7105
  this.emitWorkingChange();
6966
7106
  }
6967
7107
  syncGroupFromCanvas(target) {
@@ -7000,6 +7140,7 @@ var FeatureTool = class {
7000
7140
  if (!changed) return;
7001
7141
  this.setWorkingFeatures(next);
7002
7142
  this.hasWorkingChanges = true;
7143
+ this.redraw();
7003
7144
  this.emitWorkingChange();
7004
7145
  }
7005
7146
  redraw(options = {}) {
@@ -7008,7 +7149,10 @@ var FeatureTool = class {
7008
7149
  async redrawAsync(options = {}) {
7009
7150
  if (!this.canvasService) return;
7010
7151
  const seq = ++this.renderSeq;
7011
- this.specs = this.buildFeatureSpecs();
7152
+ this.markerSpecs = this.buildMarkerSpecs();
7153
+ const sessionRender = this.buildSessionDielineRender();
7154
+ this.sessionDielineSpecs = sessionRender.specs;
7155
+ this.sessionDielineEffects = sessionRender.effects;
7012
7156
  if (seq !== this.renderSeq) return;
7013
7157
  await this.canvasService.flushRenderFromProducers();
7014
7158
  if (seq !== this.renderSeq) return;
@@ -7016,7 +7160,49 @@ var FeatureTool = class {
7016
7160
  this.enforceConstraints();
7017
7161
  }
7018
7162
  }
7019
- buildFeatureSpecs() {
7163
+ buildSessionDielineRender() {
7164
+ if (!this.isSessionVisible() || !this.canvasService) {
7165
+ return { specs: [], effects: [] };
7166
+ }
7167
+ const configService = this.getConfigService();
7168
+ if (!configService) {
7169
+ return { specs: [], effects: [] };
7170
+ }
7171
+ const sceneLayout = computeSceneLayout(
7172
+ this.canvasService,
7173
+ readSizeState(configService)
7174
+ );
7175
+ if (!sceneLayout) {
7176
+ return { specs: [], effects: [] };
7177
+ }
7178
+ const state = readDielineState(configService);
7179
+ state.features = this.cloneFeatures(this.workingFeatures);
7180
+ return buildDielineRenderBundle({
7181
+ state,
7182
+ sceneLayout,
7183
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService.canvas.width || 800,
7184
+ canvasHeight: sceneLayout.canvasHeight || this.canvasService.canvas.height || 600,
7185
+ hasImages: this.hasImageItems(),
7186
+ createHatchPattern: (color) => this.createHatchPattern(color),
7187
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
7188
+ clipVisibility: { op: "const", value: true },
7189
+ ids: {
7190
+ inside: "feature.session.dieline.inside",
7191
+ bleedZone: "feature.session.dieline.bleed-zone",
7192
+ offsetBorder: "feature.session.dieline.offset-border",
7193
+ border: "feature.session.dieline.border",
7194
+ clip: "feature.session.dieline.clip.image",
7195
+ clipSource: "feature.session.dieline.effect.clip-path"
7196
+ }
7197
+ });
7198
+ }
7199
+ hasImageItems() {
7200
+ const configService = this.getConfigService();
7201
+ if (!configService) return false;
7202
+ const items = configService.get("image.items", []);
7203
+ return Array.isArray(items) && items.length > 0;
7204
+ }
7205
+ buildMarkerSpecs() {
7020
7206
  if (!this.isFeatureSessionActive || !this.currentGeometry || this.workingFeatures.length === 0) {
7021
7207
  return [];
7022
7208
  }
@@ -7289,7 +7475,7 @@ var FeatureTool = class {
7289
7475
 
7290
7476
  // src/extensions/film/FilmTool.ts
7291
7477
  var import_core6 = require("@pooder/core");
7292
- var import_fabric4 = require("fabric");
7478
+ var import_fabric5 = require("fabric");
7293
7479
  var FILM_IMAGE_ID = "film-image";
7294
7480
  var DEFAULT_WIDTH2 = 800;
7295
7481
  var DEFAULT_HEIGHT2 = 600;
@@ -7463,7 +7649,7 @@ var FilmTool = class {
7463
7649
  }
7464
7650
  async loadImageSize(src) {
7465
7651
  try {
7466
- const image = await import_fabric4.FabricImage.fromURL(src, {
7652
+ const image = await import_fabric5.FabricImage.fromURL(src, {
7467
7653
  crossOrigin: "anonymous"
7468
7654
  });
7469
7655
  const width = Number((image == null ? void 0 : image.width) || 0);
@@ -9458,7 +9644,7 @@ var SceneLayoutService = class {
9458
9644
  };
9459
9645
 
9460
9646
  // src/services/CanvasService.ts
9461
- var import_fabric5 = require("fabric");
9647
+ var import_fabric6 = require("fabric");
9462
9648
  var import_core11 = require("@pooder/core");
9463
9649
 
9464
9650
  // src/services/ViewportSystem.ts
@@ -9634,10 +9820,10 @@ var CanvasService = class {
9634
9820
  if (this.producerApplyInProgress) return;
9635
9821
  this.scheduleManagedPassVisibilityRefresh();
9636
9822
  };
9637
- if (el instanceof import_fabric5.Canvas) {
9823
+ if (el instanceof import_fabric6.Canvas) {
9638
9824
  this.canvas = el;
9639
9825
  } else {
9640
- this.canvas = new import_fabric5.Canvas(el, {
9826
+ this.canvas = new import_fabric6.Canvas(el, {
9641
9827
  preserveObjectStacking: true,
9642
9828
  ...options
9643
9829
  });
@@ -10532,7 +10718,7 @@ var CanvasService = class {
10532
10718
  var _a, _b;
10533
10719
  if (spec.type === "rect") {
10534
10720
  const props = this.resolveFabricProps(spec, spec.props || {});
10535
- const rect = new import_fabric5.Rect({
10721
+ const rect = new import_fabric6.Rect({
10536
10722
  ...props,
10537
10723
  data: { ...spec.data || {}, id: spec.id }
10538
10724
  });
@@ -10543,7 +10729,7 @@ var CanvasService = class {
10543
10729
  const pathData = this.readPathDataFromSpec(spec);
10544
10730
  if (!pathData) return void 0;
10545
10731
  const props = this.resolveFabricProps(spec, spec.props || {});
10546
- const path = new import_fabric5.Path(pathData, {
10732
+ const path = new import_fabric6.Path(pathData, {
10547
10733
  ...props,
10548
10734
  data: { ...spec.data || {}, id: spec.id }
10549
10735
  });
@@ -10552,7 +10738,7 @@ var CanvasService = class {
10552
10738
  }
10553
10739
  if (spec.type === "image") {
10554
10740
  if (!spec.src) return void 0;
10555
- const image = await import_fabric5.Image.fromURL(spec.src, { crossOrigin: "anonymous" });
10741
+ const image = await import_fabric6.Image.fromURL(spec.src, { crossOrigin: "anonymous" });
10556
10742
  const props = this.resolveFabricProps(spec, spec.props || {});
10557
10743
  image.set({
10558
10744
  ...props,
@@ -10564,7 +10750,7 @@ var CanvasService = class {
10564
10750
  if (spec.type === "text") {
10565
10751
  const content = String((_b = (_a = spec.props) == null ? void 0 : _a.text) != null ? _b : "");
10566
10752
  const props = this.resolveFabricProps(spec, spec.props || {});
10567
- const text = new import_fabric5.Text(content, {
10753
+ const text = new import_fabric6.Text(content, {
10568
10754
  ...props,
10569
10755
  data: { ...spec.data || {}, id: spec.id }
10570
10756
  });
@@ -10590,11 +10776,13 @@ var CanvasService = class {
10590
10776
  WhiteInkTool,
10591
10777
  computeImageCoverScale,
10592
10778
  computeWhiteInkCoverScale,
10779
+ createDefaultDielineState,
10593
10780
  createDielineCommands,
10594
10781
  createDielineConfigurations,
10595
10782
  createImageCommands,
10596
10783
  createImageConfigurations,
10597
10784
  createWhiteInkCommands,
10598
10785
  createWhiteInkConfigurations,
10599
- evaluateVisibilityExpr
10786
+ evaluateVisibilityExpr,
10787
+ readDielineState
10600
10788
  });