@pooder/kit 6.1.1 → 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.mjs CHANGED
@@ -400,6 +400,7 @@ var WHITE_INK_OBJECT_LAYER_ID = "white-ink.user";
400
400
  var WHITE_INK_COVER_LAYER_ID = "white-ink.cover";
401
401
  var WHITE_INK_OVERLAY_LAYER_ID = "white-ink.overlay";
402
402
  var DIELINE_LAYER_ID = "dieline-overlay";
403
+ var FEATURE_DIELINE_LAYER_ID = "feature-dieline-overlay";
403
404
  var FEATURE_OVERLAY_LAYER_ID = "feature-overlay";
404
405
  var RULER_LAYER_ID = "ruler-overlay";
405
406
  var FILM_LAYER_ID = "overlay";
@@ -541,6 +542,18 @@ function normalizeFitMode2(value, fallback) {
541
542
  }
542
543
  return fallback;
543
544
  }
545
+ function normalizeRegionUnit(value, fallback) {
546
+ if (value === "px" || value === "normalized") {
547
+ return value;
548
+ }
549
+ return fallback;
550
+ }
551
+ function normalizeRegistrationFrame(value, fallback) {
552
+ if (value === "trim" || value === "cut" || value === "bleed" || value === "focus" || value === "viewport") {
553
+ return value;
554
+ }
555
+ return fallback;
556
+ }
544
557
  function normalizeAnchor(value, fallback) {
545
558
  if (typeof value !== "string") return fallback;
546
559
  const trimmed = value.trim();
@@ -551,6 +564,63 @@ function normalizeOrder(value, fallback) {
551
564
  if (!Number.isFinite(numeric)) return fallback;
552
565
  return numeric;
553
566
  }
567
+ function normalizeRegionValue(value, fallback) {
568
+ const numeric = Number(value);
569
+ return Number.isFinite(numeric) ? numeric : fallback;
570
+ }
571
+ function normalizeRegistrationRegion(raw, fallback) {
572
+ if (!raw || typeof raw !== "object") {
573
+ return fallback ? { ...fallback } : void 0;
574
+ }
575
+ const input = raw;
576
+ const base = fallback || {
577
+ left: 0,
578
+ top: 0,
579
+ width: 1,
580
+ height: 1,
581
+ unit: "normalized"
582
+ };
583
+ return {
584
+ left: normalizeRegionValue(input.left, base.left),
585
+ top: normalizeRegionValue(input.top, base.top),
586
+ width: normalizeRegionValue(input.width, base.width),
587
+ height: normalizeRegionValue(input.height, base.height),
588
+ unit: normalizeRegionUnit(input.unit, base.unit)
589
+ };
590
+ }
591
+ function normalizeRegistration(raw, fallback) {
592
+ if (!raw || typeof raw !== "object") {
593
+ return fallback ? {
594
+ sourceRegion: fallback.sourceRegion ? { ...fallback.sourceRegion } : void 0,
595
+ targetFrame: fallback.targetFrame,
596
+ fit: fallback.fit
597
+ } : void 0;
598
+ }
599
+ const input = raw;
600
+ const normalized = {
601
+ sourceRegion: normalizeRegistrationRegion(
602
+ input.sourceRegion,
603
+ fallback == null ? void 0 : fallback.sourceRegion
604
+ ),
605
+ targetFrame: normalizeRegistrationFrame(
606
+ input.targetFrame,
607
+ (fallback == null ? void 0 : fallback.targetFrame) || "trim"
608
+ ),
609
+ fit: normalizeFitMode2(input.fit, (fallback == null ? void 0 : fallback.fit) || "stretch")
610
+ };
611
+ if (!normalized.sourceRegion) {
612
+ return void 0;
613
+ }
614
+ return normalized;
615
+ }
616
+ function cloneRegistration(registration) {
617
+ if (!registration) return void 0;
618
+ return {
619
+ sourceRegion: registration.sourceRegion ? { ...registration.sourceRegion } : void 0,
620
+ targetFrame: registration.targetFrame,
621
+ fit: registration.fit
622
+ };
623
+ }
554
624
  function normalizeLayer(raw, index, fallback) {
555
625
  const fallbackLayer = fallback || {
556
626
  id: `layer-${index + 1}`,
@@ -564,7 +634,10 @@ function normalizeLayer(raw, index, fallback) {
564
634
  src: ""
565
635
  };
566
636
  if (!raw || typeof raw !== "object") {
567
- return { ...fallbackLayer };
637
+ return {
638
+ ...fallbackLayer,
639
+ registration: cloneRegistration(fallbackLayer.registration)
640
+ };
568
641
  }
569
642
  const input = raw;
570
643
  const kind = normalizeLayerKind(input.kind, fallbackLayer.kind);
@@ -578,7 +651,8 @@ function normalizeLayer(raw, index, fallback) {
578
651
  enabled: typeof input.enabled === "boolean" ? input.enabled : fallbackLayer.enabled,
579
652
  exportable: typeof input.exportable === "boolean" ? input.exportable : fallbackLayer.exportable,
580
653
  color: kind === "color" ? typeof input.color === "string" ? input.color : typeof fallbackLayer.color === "string" ? fallbackLayer.color : "#ffffff" : void 0,
581
- src: kind === "image" ? typeof input.src === "string" ? input.src.trim() : typeof fallbackLayer.src === "string" ? fallbackLayer.src : "" : void 0
654
+ src: kind === "image" ? typeof input.src === "string" ? input.src.trim() : typeof fallbackLayer.src === "string" ? fallbackLayer.src : "" : void 0,
655
+ registration: kind === "image" ? normalizeRegistration(input.registration, fallbackLayer.registration) : void 0
582
656
  };
583
657
  }
584
658
  function normalizeConfig(raw) {
@@ -608,7 +682,10 @@ function normalizeConfig(raw) {
608
682
  function cloneConfig(config) {
609
683
  return {
610
684
  version: config.version,
611
- layers: (config.layers || []).map((layer) => ({ ...layer }))
685
+ layers: (config.layers || []).map((layer) => ({
686
+ ...layer,
687
+ registration: cloneRegistration(layer.registration)
688
+ }))
612
689
  };
613
690
  }
614
691
  function mergeConfig(base, patch) {
@@ -859,6 +936,41 @@ var BackgroundTool = class {
859
936
  height: layout.trimRect.height
860
937
  };
861
938
  }
939
+ resolveTargetFrameRect(frame) {
940
+ if (frame === "viewport") {
941
+ return this.getViewportRect();
942
+ }
943
+ const layout = this.resolveSceneLayout();
944
+ if (!layout) {
945
+ return frame === "focus" ? this.getViewportRect() : null;
946
+ }
947
+ switch (frame) {
948
+ case "trim":
949
+ case "focus":
950
+ return {
951
+ left: layout.trimRect.left,
952
+ top: layout.trimRect.top,
953
+ width: layout.trimRect.width,
954
+ height: layout.trimRect.height
955
+ };
956
+ case "cut":
957
+ return {
958
+ left: layout.cutRect.left,
959
+ top: layout.cutRect.top,
960
+ width: layout.cutRect.width,
961
+ height: layout.cutRect.height
962
+ };
963
+ case "bleed":
964
+ return {
965
+ left: layout.bleedRect.left,
966
+ top: layout.bleedRect.top,
967
+ width: layout.bleedRect.width,
968
+ height: layout.bleedRect.height
969
+ };
970
+ default:
971
+ return null;
972
+ }
973
+ }
862
974
  resolveAnchorRect(anchor) {
863
975
  if (anchor === "focus") {
864
976
  return this.resolveFocusRect() || this.getViewportRect();
@@ -891,6 +1003,53 @@ var BackgroundTool = class {
891
1003
  scaleY: scale
892
1004
  };
893
1005
  }
1006
+ resolveRegistrationRegion(region, sourceSize) {
1007
+ const sourceWidth = Math.max(1, Number(sourceSize.width || 0));
1008
+ const sourceHeight = Math.max(1, Number(sourceSize.height || 0));
1009
+ const width = region.unit === "normalized" ? region.width * sourceWidth : region.width;
1010
+ const height = region.unit === "normalized" ? region.height * sourceHeight : region.height;
1011
+ const left = region.unit === "normalized" ? region.left * sourceWidth : region.left;
1012
+ const top = region.unit === "normalized" ? region.top * sourceHeight : region.top;
1013
+ if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
1014
+ return null;
1015
+ }
1016
+ return { left, top, width, height };
1017
+ }
1018
+ resolveRegistrationPlacement(layer, sourceSize) {
1019
+ const registration = layer.registration;
1020
+ if (!(registration == null ? void 0 : registration.sourceRegion)) return null;
1021
+ const targetRect = this.resolveTargetFrameRect(
1022
+ registration.targetFrame || "trim"
1023
+ );
1024
+ if (!targetRect) return null;
1025
+ const sourceRegion = this.resolveRegistrationRegion(
1026
+ registration.sourceRegion,
1027
+ sourceSize
1028
+ );
1029
+ if (!sourceRegion) return null;
1030
+ const fit = registration.fit || "stretch";
1031
+ const baseScaleX = targetRect.width / sourceRegion.width;
1032
+ const baseScaleY = targetRect.height / sourceRegion.height;
1033
+ if (fit === "stretch") {
1034
+ return {
1035
+ left: targetRect.left - sourceRegion.left * baseScaleX,
1036
+ top: targetRect.top - sourceRegion.top * baseScaleY,
1037
+ scaleX: baseScaleX,
1038
+ scaleY: baseScaleY
1039
+ };
1040
+ }
1041
+ const uniformScale = fit === "contain" ? Math.min(baseScaleX, baseScaleY) : Math.max(baseScaleX, baseScaleY);
1042
+ const alignedWidth = sourceRegion.width * uniformScale;
1043
+ const alignedHeight = sourceRegion.height * uniformScale;
1044
+ const offsetLeft = targetRect.left + (targetRect.width - alignedWidth) / 2;
1045
+ const offsetTop = targetRect.top + (targetRect.height - alignedHeight) / 2;
1046
+ return {
1047
+ left: offsetLeft - sourceRegion.left * uniformScale,
1048
+ top: offsetTop - sourceRegion.top * uniformScale,
1049
+ scaleX: uniformScale,
1050
+ scaleY: uniformScale
1051
+ };
1052
+ }
894
1053
  buildColorLayerSpec(layer) {
895
1054
  const rect = this.resolveAnchorRect(layer.anchor);
896
1055
  return {
@@ -924,8 +1083,11 @@ var BackgroundTool = class {
924
1083
  if (!src) return [];
925
1084
  const sourceSize = this.sourceSizeCache.getSourceSize(src);
926
1085
  if (!sourceSize) return [];
927
- const rect = this.resolveAnchorRect(layer.anchor);
928
- const placement = this.resolveImagePlacement(rect, sourceSize, layer.fit);
1086
+ const placement = this.resolveRegistrationPlacement(layer, sourceSize) || this.resolveImagePlacement(
1087
+ this.resolveAnchorRect(layer.anchor),
1088
+ sourceSize,
1089
+ layer.fit
1090
+ );
929
1091
  return [
930
1092
  {
931
1093
  id: `background.layer.${layer.id}.image`,
@@ -1998,6 +2160,7 @@ var IMAGE_DEFAULT_CONTROL_CAPABILITIES = [
1998
2160
  "rotate",
1999
2161
  "scale"
2000
2162
  ];
2163
+ var IMAGE_MOVE_SNAP_THRESHOLD_PX = 6;
2001
2164
  var IMAGE_CONTROL_DESCRIPTORS = [
2002
2165
  {
2003
2166
  key: "tl",
@@ -2042,12 +2205,17 @@ var ImageTool = class {
2042
2205
  this.renderSeq = 0;
2043
2206
  this.imageSpecs = [];
2044
2207
  this.overlaySpecs = [];
2208
+ this.activeSnapX = null;
2209
+ this.activeSnapY = null;
2210
+ this.movingImageId = null;
2211
+ this.hasRenderedSnapGuides = false;
2045
2212
  this.subscriptions = new SubscriptionBag();
2046
2213
  this.imageControlsByCapabilityKey = /* @__PURE__ */ new Map();
2047
2214
  this.onToolActivated = (event) => {
2048
2215
  const before = this.isToolActive;
2049
2216
  this.syncToolActiveFromWorkbench(event.id);
2050
2217
  if (!this.isToolActive) {
2218
+ this.endMoveSnapInteraction();
2051
2219
  this.setImageFocus(null, {
2052
2220
  syncCanvasSelection: true,
2053
2221
  skipRender: true
@@ -2098,6 +2266,7 @@ var ImageTool = class {
2098
2266
  this.updateImages();
2099
2267
  };
2100
2268
  this.onSelectionCleared = () => {
2269
+ this.endMoveSnapInteraction();
2101
2270
  this.setImageFocus(null, {
2102
2271
  syncCanvasSelection: false,
2103
2272
  skipRender: true
@@ -2106,6 +2275,8 @@ var ImageTool = class {
2106
2275
  this.updateImages();
2107
2276
  };
2108
2277
  this.onSceneLayoutChanged = () => {
2278
+ var _a;
2279
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2109
2280
  this.updateImages();
2110
2281
  };
2111
2282
  this.onSceneGeometryChanged = () => {
@@ -2118,7 +2289,11 @@ var ImageTool = class {
2118
2289
  const id = (_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id;
2119
2290
  const layerId = (_b = target == null ? void 0 : target.data) == null ? void 0 : _b.layerId;
2120
2291
  if (typeof id !== "string" || layerId !== IMAGE_OBJECT_LAYER_ID) return;
2292
+ if (this.movingImageId === id) {
2293
+ this.applyMoveSnapToTarget(target);
2294
+ }
2121
2295
  const frame = this.getFrameRect();
2296
+ this.endMoveSnapInteraction();
2122
2297
  if (!frame.width || !frame.height) return;
2123
2298
  const center = target.getCenterPoint ? target.getCenterPoint() : new Point((_c = target.left) != null ? _c : 0, (_d = target.top) != null ? _d : 0);
2124
2299
  const centerScene = this.canvasService ? this.canvasService.toScenePoint({ x: center.x, y: center.y }) : { x: center.x, y: center.y };
@@ -2182,8 +2357,17 @@ var ImageTool = class {
2182
2357
  }),
2183
2358
  { priority: 300 }
2184
2359
  );
2185
- this.subscriptions.on(context.eventBus, "tool:activated", this.onToolActivated);
2186
- this.subscriptions.on(context.eventBus, "object:modified", this.onObjectModified);
2360
+ this.bindCanvasInteractionHandlers();
2361
+ this.subscriptions.on(
2362
+ context.eventBus,
2363
+ "tool:activated",
2364
+ this.onToolActivated
2365
+ );
2366
+ this.subscriptions.on(
2367
+ context.eventBus,
2368
+ "object:modified",
2369
+ this.onObjectModified
2370
+ );
2187
2371
  this.subscriptions.on(
2188
2372
  context.eventBus,
2189
2373
  "selection:created",
@@ -2251,6 +2435,8 @@ var ImageTool = class {
2251
2435
  this.imageSpecs = [];
2252
2436
  this.overlaySpecs = [];
2253
2437
  this.imageControlsByCapabilityKey.clear();
2438
+ this.endMoveSnapInteraction();
2439
+ this.unbindCanvasInteractionHandlers();
2254
2440
  this.clearRenderedImages();
2255
2441
  (_b = this.renderProducerDisposable) == null ? void 0 : _b.dispose();
2256
2442
  this.renderProducerDisposable = void 0;
@@ -2260,6 +2446,283 @@ var ImageTool = class {
2260
2446
  }
2261
2447
  this.context = void 0;
2262
2448
  }
2449
+ bindCanvasInteractionHandlers() {
2450
+ if (!this.canvasService || this.canvasObjectMovingHandler) return;
2451
+ this.canvasMouseUpHandler = (e) => {
2452
+ var _a;
2453
+ const target = this.getActiveImageTarget(e == null ? void 0 : e.target);
2454
+ if (target && typeof ((_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id) === "string" && target.data.id === this.movingImageId) {
2455
+ this.applyMoveSnapToTarget(target);
2456
+ }
2457
+ this.endMoveSnapInteraction();
2458
+ };
2459
+ this.canvasObjectMovingHandler = (e) => {
2460
+ this.handleCanvasObjectMoving(e);
2461
+ };
2462
+ this.canvasBeforeRenderHandler = () => {
2463
+ this.handleCanvasBeforeRender();
2464
+ };
2465
+ this.canvasAfterRenderHandler = () => {
2466
+ this.handleCanvasAfterRender();
2467
+ };
2468
+ this.canvasService.canvas.on("mouse:up", this.canvasMouseUpHandler);
2469
+ this.canvasService.canvas.on(
2470
+ "object:moving",
2471
+ this.canvasObjectMovingHandler
2472
+ );
2473
+ this.canvasService.canvas.on(
2474
+ "before:render",
2475
+ this.canvasBeforeRenderHandler
2476
+ );
2477
+ this.canvasService.canvas.on("after:render", this.canvasAfterRenderHandler);
2478
+ }
2479
+ unbindCanvasInteractionHandlers() {
2480
+ if (!this.canvasService) return;
2481
+ if (this.canvasMouseUpHandler) {
2482
+ this.canvasService.canvas.off("mouse:up", this.canvasMouseUpHandler);
2483
+ }
2484
+ if (this.canvasObjectMovingHandler) {
2485
+ this.canvasService.canvas.off(
2486
+ "object:moving",
2487
+ this.canvasObjectMovingHandler
2488
+ );
2489
+ }
2490
+ if (this.canvasBeforeRenderHandler) {
2491
+ this.canvasService.canvas.off(
2492
+ "before:render",
2493
+ this.canvasBeforeRenderHandler
2494
+ );
2495
+ }
2496
+ if (this.canvasAfterRenderHandler) {
2497
+ this.canvasService.canvas.off(
2498
+ "after:render",
2499
+ this.canvasAfterRenderHandler
2500
+ );
2501
+ }
2502
+ this.canvasMouseUpHandler = void 0;
2503
+ this.canvasObjectMovingHandler = void 0;
2504
+ this.canvasBeforeRenderHandler = void 0;
2505
+ this.canvasAfterRenderHandler = void 0;
2506
+ }
2507
+ getActiveImageTarget(target) {
2508
+ var _a, _b;
2509
+ if (!this.isToolActive) return null;
2510
+ if (!target) return null;
2511
+ if (((_a = target == null ? void 0 : target.data) == null ? void 0 : _a.layerId) !== IMAGE_OBJECT_LAYER_ID) return null;
2512
+ if (typeof ((_b = target == null ? void 0 : target.data) == null ? void 0 : _b.id) !== "string") return null;
2513
+ return target;
2514
+ }
2515
+ getTargetBoundsScene(target) {
2516
+ if (!this.canvasService || !target) return null;
2517
+ const rawBounds = typeof target.getBoundingRect === "function" ? target.getBoundingRect() : {
2518
+ left: Number(target.left || 0),
2519
+ top: Number(target.top || 0),
2520
+ width: Number(target.width || 0),
2521
+ height: Number(target.height || 0)
2522
+ };
2523
+ return this.canvasService.toSceneRect({
2524
+ left: Number(rawBounds.left || 0),
2525
+ top: Number(rawBounds.top || 0),
2526
+ width: Number(rawBounds.width || 0),
2527
+ height: Number(rawBounds.height || 0)
2528
+ });
2529
+ }
2530
+ getSnapThresholdScene(px) {
2531
+ if (!this.canvasService) return px;
2532
+ return this.canvasService.toSceneLength(px);
2533
+ }
2534
+ pickSnapMatch(candidates) {
2535
+ if (!candidates.length) return null;
2536
+ const snapThreshold = this.getSnapThresholdScene(
2537
+ IMAGE_MOVE_SNAP_THRESHOLD_PX
2538
+ );
2539
+ let best = null;
2540
+ candidates.forEach((candidate) => {
2541
+ if (Math.abs(candidate.deltaScene) > snapThreshold) return;
2542
+ if (!best || Math.abs(candidate.deltaScene) < Math.abs(best.deltaScene)) {
2543
+ best = candidate;
2544
+ }
2545
+ });
2546
+ return best;
2547
+ }
2548
+ computeMoveSnapMatches(bounds, frame) {
2549
+ if (!bounds || frame.width <= 0 || frame.height <= 0) {
2550
+ return { x: null, y: null };
2551
+ }
2552
+ const xCandidates = [
2553
+ {
2554
+ axis: "x",
2555
+ lineId: "frame-left",
2556
+ kind: "edge",
2557
+ lineScene: frame.left,
2558
+ deltaScene: frame.left - bounds.left
2559
+ },
2560
+ {
2561
+ axis: "x",
2562
+ lineId: "frame-center-x",
2563
+ kind: "center",
2564
+ lineScene: frame.left + frame.width / 2,
2565
+ deltaScene: frame.left + frame.width / 2 - (bounds.left + bounds.width / 2)
2566
+ },
2567
+ {
2568
+ axis: "x",
2569
+ lineId: "frame-right",
2570
+ kind: "edge",
2571
+ lineScene: frame.left + frame.width,
2572
+ deltaScene: frame.left + frame.width - (bounds.left + bounds.width)
2573
+ }
2574
+ ];
2575
+ const yCandidates = [
2576
+ {
2577
+ axis: "y",
2578
+ lineId: "frame-top",
2579
+ kind: "edge",
2580
+ lineScene: frame.top,
2581
+ deltaScene: frame.top - bounds.top
2582
+ },
2583
+ {
2584
+ axis: "y",
2585
+ lineId: "frame-center-y",
2586
+ kind: "center",
2587
+ lineScene: frame.top + frame.height / 2,
2588
+ deltaScene: frame.top + frame.height / 2 - (bounds.top + bounds.height / 2)
2589
+ },
2590
+ {
2591
+ axis: "y",
2592
+ lineId: "frame-bottom",
2593
+ kind: "edge",
2594
+ lineScene: frame.top + frame.height,
2595
+ deltaScene: frame.top + frame.height - (bounds.top + bounds.height)
2596
+ }
2597
+ ];
2598
+ return {
2599
+ x: this.pickSnapMatch(xCandidates),
2600
+ y: this.pickSnapMatch(yCandidates)
2601
+ };
2602
+ }
2603
+ areSnapMatchesEqual(a, b) {
2604
+ if (!a && !b) return true;
2605
+ if (!a || !b) return false;
2606
+ return a.lineId === b.lineId && a.axis === b.axis && a.kind === b.kind;
2607
+ }
2608
+ updateSnapMatchState(nextX, nextY) {
2609
+ var _a;
2610
+ const changed = !this.areSnapMatchesEqual(this.activeSnapX, nextX) || !this.areSnapMatchesEqual(this.activeSnapY, nextY);
2611
+ this.activeSnapX = nextX;
2612
+ this.activeSnapY = nextY;
2613
+ if (changed) {
2614
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2615
+ }
2616
+ }
2617
+ clearSnapPreview() {
2618
+ var _a;
2619
+ this.activeSnapX = null;
2620
+ this.activeSnapY = null;
2621
+ this.hasRenderedSnapGuides = false;
2622
+ (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
2623
+ }
2624
+ endMoveSnapInteraction() {
2625
+ this.movingImageId = null;
2626
+ this.clearSnapPreview();
2627
+ }
2628
+ applyMoveSnapToTarget(target) {
2629
+ var _a, _b, _c, _d;
2630
+ if (!this.canvasService) {
2631
+ return { x: null, y: null };
2632
+ }
2633
+ const frame = this.getFrameRect();
2634
+ if (frame.width <= 0 || frame.height <= 0) {
2635
+ return { x: null, y: null };
2636
+ }
2637
+ const bounds = this.getTargetBoundsScene(target);
2638
+ const matches = this.computeMoveSnapMatches(bounds, frame);
2639
+ const deltaScreenX = this.canvasService.toScreenLength(
2640
+ (_b = (_a = matches.x) == null ? void 0 : _a.deltaScene) != null ? _b : 0
2641
+ );
2642
+ const deltaScreenY = this.canvasService.toScreenLength(
2643
+ (_d = (_c = matches.y) == null ? void 0 : _c.deltaScene) != null ? _d : 0
2644
+ );
2645
+ if (deltaScreenX || deltaScreenY) {
2646
+ target.set({
2647
+ left: Number(target.left || 0) + deltaScreenX,
2648
+ top: Number(target.top || 0) + deltaScreenY
2649
+ });
2650
+ target.setCoords();
2651
+ }
2652
+ return matches;
2653
+ }
2654
+ handleCanvasBeforeRender() {
2655
+ if (!this.canvasService) return;
2656
+ if (!this.hasRenderedSnapGuides && !this.activeSnapX && !this.activeSnapY) {
2657
+ return;
2658
+ }
2659
+ this.canvasService.canvas.clearContext(
2660
+ this.canvasService.canvas.contextTop
2661
+ );
2662
+ this.hasRenderedSnapGuides = false;
2663
+ }
2664
+ drawSnapGuideLine(from, to) {
2665
+ if (!this.canvasService) return;
2666
+ const ctx = this.canvasService.canvas.contextTop;
2667
+ if (!ctx) return;
2668
+ const color = this.getConfig("image.control.borderColor", "#1677ff") || "#1677ff";
2669
+ ctx.save();
2670
+ ctx.strokeStyle = color;
2671
+ ctx.lineWidth = 1;
2672
+ ctx.beginPath();
2673
+ ctx.moveTo(from.x, from.y);
2674
+ ctx.lineTo(to.x, to.y);
2675
+ ctx.stroke();
2676
+ ctx.restore();
2677
+ }
2678
+ handleCanvasAfterRender() {
2679
+ if (!this.canvasService || !this.isImageEditingVisible()) {
2680
+ return;
2681
+ }
2682
+ const frame = this.getFrameRect();
2683
+ if (frame.width <= 0 || frame.height <= 0) {
2684
+ return;
2685
+ }
2686
+ const frameScreen = this.getFrameRectScreen(frame);
2687
+ let drew = false;
2688
+ if (this.activeSnapX) {
2689
+ const x = this.canvasService.toScreenPoint({
2690
+ x: this.activeSnapX.lineScene,
2691
+ y: frame.top
2692
+ }).x;
2693
+ this.drawSnapGuideLine(
2694
+ { x, y: frameScreen.top },
2695
+ { x, y: frameScreen.top + frameScreen.height }
2696
+ );
2697
+ drew = true;
2698
+ }
2699
+ if (this.activeSnapY) {
2700
+ const y = this.canvasService.toScreenPoint({
2701
+ x: frame.left,
2702
+ y: this.activeSnapY.lineScene
2703
+ }).y;
2704
+ this.drawSnapGuideLine(
2705
+ { x: frameScreen.left, y },
2706
+ { x: frameScreen.left + frameScreen.width, y }
2707
+ );
2708
+ drew = true;
2709
+ }
2710
+ this.hasRenderedSnapGuides = drew;
2711
+ }
2712
+ handleCanvasObjectMoving(e) {
2713
+ var _a;
2714
+ const target = this.getActiveImageTarget(e == null ? void 0 : e.target);
2715
+ if (!target || !this.canvasService) return;
2716
+ this.movingImageId = typeof ((_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id) === "string" ? target.data.id : null;
2717
+ const frame = this.getFrameRect();
2718
+ if (frame.width <= 0 || frame.height <= 0) {
2719
+ this.endMoveSnapInteraction();
2720
+ return;
2721
+ }
2722
+ const rawBounds = this.getTargetBoundsScene(target);
2723
+ const matches = this.computeMoveSnapMatches(rawBounds, frame);
2724
+ this.updateSnapMatchState(matches.x, matches.y);
2725
+ }
2263
2726
  syncToolActiveFromWorkbench(fallbackId) {
2264
2727
  var _a;
2265
2728
  const wb = (_a = this.context) == null ? void 0 : _a.services.get("WorkbenchService");
@@ -2883,33 +3346,9 @@ var ImageTool = class {
2883
3346
  originY: "top",
2884
3347
  fill: hatchFill,
2885
3348
  opacity: patternFill ? 1 : 0.8,
2886
- stroke: null,
2887
- fillRule: "evenodd",
2888
- selectable: false,
2889
- evented: false,
2890
- excludeFromExport: true,
2891
- objectCaching: false
2892
- }
2893
- },
2894
- {
2895
- id: "image.cropShapePath",
2896
- type: "path",
2897
- data: { id: "image.cropShapePath", zIndex: 6 },
2898
- layout: {
2899
- reference: "custom",
2900
- referenceRect: frameRect,
2901
- alignX: "start",
2902
- alignY: "start",
2903
- offsetX: shapeBounds.x,
2904
- offsetY: shapeBounds.y
2905
- },
2906
- props: {
2907
- pathData: shapePathData,
2908
- originX: "left",
2909
- originY: "top",
2910
- fill: "rgba(0,0,0,0)",
2911
3349
  stroke: "rgba(255, 0, 0, 0.9)",
2912
3350
  strokeWidth: (_b = (_a = this.canvasService) == null ? void 0 : _a.toSceneLength(1)) != null ? _b : 1,
3351
+ fillRule: "evenodd",
2913
3352
  selectable: false,
2914
3353
  evented: false,
2915
3354
  excludeFromExport: true,
@@ -4046,225 +4485,392 @@ function createDielineConfigurations(state) {
4046
4485
  ];
4047
4486
  }
4048
4487
 
4049
- // src/extensions/dieline/DielineTool.ts
4050
- var DielineTool = class {
4051
- constructor(options) {
4052
- this.id = "pooder.kit.dieline";
4053
- this.metadata = {
4054
- name: "DielineTool"
4055
- };
4056
- this.state = {
4057
- shape: DEFAULT_DIELINE_SHAPE,
4058
- shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
4059
- width: 500,
4060
- height: 500,
4061
- radius: 0,
4062
- offset: 0,
4063
- padding: 140,
4064
- mainLine: {
4065
- width: 2.7,
4066
- color: "#FF0000",
4067
- dashLength: 5,
4068
- style: "solid"
4069
- },
4070
- offsetLine: {
4071
- width: 2.7,
4072
- color: "#FF0000",
4073
- dashLength: 5,
4074
- style: "solid"
4075
- },
4076
- insideColor: "rgba(0,0,0,0)",
4077
- showBleedLines: true,
4078
- features: []
4079
- };
4080
- this.specs = [];
4081
- this.effects = [];
4082
- this.renderSeq = 0;
4083
- this.onCanvasResized = () => {
4084
- this.updateDieline();
4085
- };
4086
- if (options) {
4087
- if (options.mainLine) {
4088
- Object.assign(this.state.mainLine, options.mainLine);
4089
- delete options.mainLine;
4090
- }
4091
- if (options.offsetLine) {
4092
- Object.assign(this.state.offsetLine, options.offsetLine);
4093
- delete options.offsetLine;
4094
- }
4095
- if (options.shapeStyle) {
4096
- this.state.shapeStyle = normalizeShapeStyle(
4097
- options.shapeStyle,
4098
- this.state.shapeStyle
4099
- );
4100
- delete options.shapeStyle;
4101
- }
4102
- Object.assign(this.state, options);
4103
- this.state.shape = normalizeDielineShape(options.shape, this.state.shape);
4488
+ // src/extensions/dieline/model.ts
4489
+ function createDefaultDielineState() {
4490
+ return {
4491
+ shape: DEFAULT_DIELINE_SHAPE,
4492
+ shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
4493
+ width: 500,
4494
+ height: 500,
4495
+ radius: 0,
4496
+ offset: 0,
4497
+ padding: 140,
4498
+ mainLine: {
4499
+ width: 2.7,
4500
+ color: "#FF0000",
4501
+ dashLength: 5,
4502
+ style: "solid"
4503
+ },
4504
+ offsetLine: {
4505
+ width: 2.7,
4506
+ color: "#FF0000",
4507
+ dashLength: 5,
4508
+ style: "solid"
4509
+ },
4510
+ insideColor: "rgba(0,0,0,0)",
4511
+ showBleedLines: true,
4512
+ features: []
4513
+ };
4514
+ }
4515
+ function readDielineState(configService, fallback) {
4516
+ const base = createDefaultDielineState();
4517
+ if (fallback) {
4518
+ Object.assign(base, fallback);
4519
+ if (fallback.mainLine) {
4520
+ base.mainLine = { ...base.mainLine, ...fallback.mainLine };
4104
4521
  }
4105
- }
4106
- activate(context) {
4107
- var _a;
4108
- this.context = context;
4109
- this.canvasService = context.services.get("CanvasService");
4110
- if (!this.canvasService) {
4111
- console.warn("CanvasService not found for DielineTool");
4112
- return;
4522
+ if (fallback.offsetLine) {
4523
+ base.offsetLine = { ...base.offsetLine, ...fallback.offsetLine };
4113
4524
  }
4114
- (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
4115
- this.renderProducerDisposable = this.canvasService.registerRenderProducer(
4116
- this.id,
4117
- () => ({
4118
- passes: [
4119
- {
4120
- id: DIELINE_LAYER_ID,
4121
- stack: 700,
4122
- order: 0,
4123
- replace: true,
4124
- visibility: {
4125
- op: "not",
4126
- expr: {
4127
- op: "activeToolIn",
4128
- ids: ["pooder.kit.image", "pooder.kit.white-ink"]
4129
- }
4130
- },
4131
- effects: this.effects,
4132
- objects: this.specs
4133
- }
4134
- ]
4135
- }),
4136
- { priority: 250 }
4137
- );
4138
- const configService = context.services.get(
4139
- "ConfigurationService"
4140
- );
4141
- if (configService) {
4142
- const s = this.state;
4143
- const sizeState = readSizeState(configService);
4144
- s.shape = normalizeDielineShape(
4145
- configService.get("dieline.shape", s.shape),
4146
- s.shape
4147
- );
4148
- s.shapeStyle = normalizeShapeStyle(
4149
- configService.get("dieline.shapeStyle", s.shapeStyle),
4150
- s.shapeStyle
4151
- );
4152
- s.width = sizeState.actualWidthMm;
4153
- s.height = sizeState.actualHeightMm;
4154
- s.radius = parseLengthToMm(
4155
- configService.get("dieline.radius", s.radius),
4156
- "mm"
4157
- );
4158
- s.padding = sizeState.viewPadding;
4159
- s.offset = sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0;
4160
- s.mainLine.width = configService.get(
4161
- "dieline.strokeWidth",
4162
- s.mainLine.width
4163
- );
4164
- s.mainLine.color = configService.get(
4165
- "dieline.strokeColor",
4166
- s.mainLine.color
4167
- );
4168
- s.mainLine.dashLength = configService.get(
4525
+ if (fallback.shapeStyle) {
4526
+ base.shapeStyle = normalizeShapeStyle(fallback.shapeStyle, base.shapeStyle);
4527
+ }
4528
+ }
4529
+ const sizeState = readSizeState(configService);
4530
+ const sourceWidth = Number(configService.get("dieline.customSourceWidthPx", 0));
4531
+ const sourceHeight = Number(
4532
+ configService.get("dieline.customSourceHeightPx", 0)
4533
+ );
4534
+ return {
4535
+ ...base,
4536
+ shape: normalizeDielineShape(
4537
+ configService.get("dieline.shape", base.shape),
4538
+ base.shape
4539
+ ),
4540
+ shapeStyle: normalizeShapeStyle(
4541
+ configService.get("dieline.shapeStyle", base.shapeStyle),
4542
+ base.shapeStyle
4543
+ ),
4544
+ width: sizeState.actualWidthMm,
4545
+ height: sizeState.actualHeightMm,
4546
+ radius: parseLengthToMm(configService.get("dieline.radius", base.radius), "mm"),
4547
+ padding: sizeState.viewPadding,
4548
+ offset: sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0,
4549
+ mainLine: {
4550
+ width: configService.get("dieline.strokeWidth", base.mainLine.width),
4551
+ color: configService.get("dieline.strokeColor", base.mainLine.color),
4552
+ dashLength: configService.get(
4169
4553
  "dieline.dashLength",
4170
- s.mainLine.dashLength
4171
- );
4172
- s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
4173
- s.offsetLine.width = configService.get(
4554
+ base.mainLine.dashLength
4555
+ ),
4556
+ style: configService.get("dieline.style", base.mainLine.style)
4557
+ },
4558
+ offsetLine: {
4559
+ width: configService.get(
4174
4560
  "dieline.offsetStrokeWidth",
4175
- s.offsetLine.width
4176
- );
4177
- s.offsetLine.color = configService.get(
4561
+ base.offsetLine.width
4562
+ ),
4563
+ color: configService.get(
4178
4564
  "dieline.offsetStrokeColor",
4179
- s.offsetLine.color
4180
- );
4181
- s.offsetLine.dashLength = configService.get(
4565
+ base.offsetLine.color
4566
+ ),
4567
+ dashLength: configService.get(
4182
4568
  "dieline.offsetDashLength",
4183
- s.offsetLine.dashLength
4184
- );
4185
- s.offsetLine.style = configService.get(
4186
- "dieline.offsetStyle",
4187
- s.offsetLine.style
4188
- );
4189
- s.insideColor = configService.get("dieline.insideColor", s.insideColor);
4190
- s.showBleedLines = configService.get(
4191
- "dieline.showBleedLines",
4192
- s.showBleedLines
4193
- );
4194
- s.features = configService.get("dieline.features", s.features);
4195
- s.pathData = configService.get("dieline.pathData", s.pathData);
4196
- const sourceWidth = Number(
4197
- configService.get("dieline.customSourceWidthPx", 0)
4198
- );
4199
- const sourceHeight = Number(
4200
- configService.get("dieline.customSourceHeightPx", 0)
4201
- );
4202
- s.customSourceWidthPx = Number.isFinite(sourceWidth) && sourceWidth > 0 ? sourceWidth : void 0;
4203
- s.customSourceHeightPx = Number.isFinite(sourceHeight) && sourceHeight > 0 ? sourceHeight : void 0;
4204
- configService.onAnyChange((e) => {
4205
- if (e.key.startsWith("size.")) {
4206
- const nextSize = readSizeState(configService);
4207
- s.width = nextSize.actualWidthMm;
4208
- s.height = nextSize.actualHeightMm;
4209
- s.padding = nextSize.viewPadding;
4210
- s.offset = nextSize.cutMode === "outset" ? nextSize.cutMarginMm : nextSize.cutMode === "inset" ? -nextSize.cutMarginMm : 0;
4211
- this.updateDieline();
4212
- return;
4569
+ base.offsetLine.dashLength
4570
+ ),
4571
+ style: configService.get("dieline.offsetStyle", base.offsetLine.style)
4572
+ },
4573
+ insideColor: configService.get("dieline.insideColor", base.insideColor),
4574
+ showBleedLines: configService.get(
4575
+ "dieline.showBleedLines",
4576
+ base.showBleedLines
4577
+ ),
4578
+ features: configService.get("dieline.features", base.features),
4579
+ pathData: configService.get("dieline.pathData", base.pathData),
4580
+ customSourceWidthPx: Number.isFinite(sourceWidth) && sourceWidth > 0 ? sourceWidth : void 0,
4581
+ customSourceHeightPx: Number.isFinite(sourceHeight) && sourceHeight > 0 ? sourceHeight : void 0
4582
+ };
4583
+ }
4584
+
4585
+ // src/extensions/dieline/renderBuilder.ts
4586
+ var DEFAULT_IDS = {
4587
+ inside: "dieline.inside",
4588
+ bleedZone: "dieline.bleed-zone",
4589
+ offsetBorder: "dieline.offset-border",
4590
+ border: "dieline.border",
4591
+ clip: "dieline.clip.image",
4592
+ clipSource: "dieline.effect.clip-path"
4593
+ };
4594
+ function scaleFeatures(state, scale) {
4595
+ return (state.features || []).map((feature) => ({
4596
+ ...feature,
4597
+ x: feature.x,
4598
+ y: feature.y,
4599
+ width: (feature.width || 0) * scale,
4600
+ height: (feature.height || 0) * scale,
4601
+ radius: (feature.radius || 0) * scale
4602
+ }));
4603
+ }
4604
+ function buildDielineRenderBundle(options) {
4605
+ const ids = { ...DEFAULT_IDS, ...options.ids || {} };
4606
+ const {
4607
+ state,
4608
+ sceneLayout,
4609
+ canvasWidth,
4610
+ canvasHeight,
4611
+ hasImages,
4612
+ createHatchPattern,
4613
+ includeImageClipEffect = true,
4614
+ clipTargetPassIds = [IMAGE_OBJECT_LAYER_ID],
4615
+ clipVisibility
4616
+ } = options;
4617
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } = state;
4618
+ const scale = sceneLayout.scale;
4619
+ const cx = sceneLayout.trimRect.centerX;
4620
+ const cy = sceneLayout.trimRect.centerY;
4621
+ const visualWidth = sceneLayout.trimRect.width;
4622
+ const visualHeight = sceneLayout.trimRect.height;
4623
+ const visualRadius = radius * scale;
4624
+ const cutW = sceneLayout.cutRect.width;
4625
+ const cutH = sceneLayout.cutRect.height;
4626
+ const visualOffset = (cutW - visualWidth) / 2;
4627
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
4628
+ const absoluteFeatures = scaleFeatures(state, scale);
4629
+ const cutFeatures = absoluteFeatures.filter((feature) => !feature.skipCut);
4630
+ const common = {
4631
+ shape,
4632
+ shapeStyle,
4633
+ pathData: state.pathData,
4634
+ customSourceWidthPx: state.customSourceWidthPx,
4635
+ customSourceHeightPx: state.customSourceHeightPx,
4636
+ canvasWidth,
4637
+ canvasHeight
4638
+ };
4639
+ const specs = [];
4640
+ if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
4641
+ specs.push({
4642
+ id: ids.inside,
4643
+ type: "path",
4644
+ space: "screen",
4645
+ data: { id: ids.inside, type: "dieline" },
4646
+ props: {
4647
+ pathData: generateDielinePath({
4648
+ ...common,
4649
+ width: cutW,
4650
+ height: cutH,
4651
+ radius: cutR,
4652
+ x: cx,
4653
+ y: cy,
4654
+ features: cutFeatures
4655
+ }),
4656
+ fill: insideColor,
4657
+ stroke: null,
4658
+ selectable: false,
4659
+ evented: false,
4660
+ originX: "left",
4661
+ originY: "top"
4662
+ }
4663
+ });
4664
+ }
4665
+ if (Math.abs(visualOffset) > 1e-4) {
4666
+ const trimPathInput = {
4667
+ ...common,
4668
+ width: visualWidth,
4669
+ height: visualHeight,
4670
+ radius: visualRadius,
4671
+ x: cx,
4672
+ y: cy,
4673
+ features: cutFeatures
4674
+ };
4675
+ const cutPathInput = {
4676
+ ...common,
4677
+ width: cutW,
4678
+ height: cutH,
4679
+ radius: cutR,
4680
+ x: cx,
4681
+ y: cy,
4682
+ features: cutFeatures
4683
+ };
4684
+ if (state.showBleedLines !== false) {
4685
+ const pattern = createHatchPattern == null ? void 0 : createHatchPattern(mainLine.color);
4686
+ if (pattern) {
4687
+ specs.push({
4688
+ id: ids.bleedZone,
4689
+ type: "path",
4690
+ space: "screen",
4691
+ data: { id: ids.bleedZone, type: "dieline" },
4692
+ props: {
4693
+ pathData: generateBleedZonePath(
4694
+ trimPathInput,
4695
+ cutPathInput,
4696
+ visualOffset
4697
+ ),
4698
+ fill: pattern,
4699
+ stroke: null,
4700
+ selectable: false,
4701
+ evented: false,
4702
+ objectCaching: false,
4703
+ originX: "left",
4704
+ originY: "top"
4705
+ }
4706
+ });
4707
+ }
4708
+ }
4709
+ specs.push({
4710
+ id: ids.offsetBorder,
4711
+ type: "path",
4712
+ space: "screen",
4713
+ data: { id: ids.offsetBorder, type: "dieline" },
4714
+ props: {
4715
+ pathData: generateDielinePath(cutPathInput),
4716
+ fill: null,
4717
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
4718
+ strokeWidth: offsetLine.width,
4719
+ strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
4720
+ selectable: false,
4721
+ evented: false,
4722
+ originX: "left",
4723
+ originY: "top"
4724
+ }
4725
+ });
4726
+ }
4727
+ specs.push({
4728
+ id: ids.border,
4729
+ type: "path",
4730
+ space: "screen",
4731
+ data: { id: ids.border, type: "dieline" },
4732
+ props: {
4733
+ pathData: generateDielinePath({
4734
+ ...common,
4735
+ width: visualWidth,
4736
+ height: visualHeight,
4737
+ radius: visualRadius,
4738
+ x: cx,
4739
+ y: cy,
4740
+ features: absoluteFeatures
4741
+ }),
4742
+ fill: "transparent",
4743
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
4744
+ strokeWidth: mainLine.width,
4745
+ strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
4746
+ selectable: false,
4747
+ evented: false,
4748
+ originX: "left",
4749
+ originY: "top"
4750
+ }
4751
+ });
4752
+ if (!includeImageClipEffect) {
4753
+ return { specs, effects: [] };
4754
+ }
4755
+ const clipPathData = generateDielinePath({
4756
+ ...common,
4757
+ width: cutW,
4758
+ height: cutH,
4759
+ radius: cutR,
4760
+ x: cx,
4761
+ y: cy,
4762
+ features: cutFeatures
4763
+ });
4764
+ if (!clipPathData) {
4765
+ return { specs, effects: [] };
4766
+ }
4767
+ return {
4768
+ specs,
4769
+ effects: [
4770
+ {
4771
+ type: "clipPath",
4772
+ id: ids.clip,
4773
+ visibility: clipVisibility,
4774
+ targetPassIds: clipTargetPassIds,
4775
+ source: {
4776
+ id: ids.clipSource,
4777
+ type: "path",
4778
+ space: "screen",
4779
+ data: {
4780
+ id: ids.clipSource,
4781
+ type: "dieline-effect",
4782
+ effect: "clipPath"
4783
+ },
4784
+ props: {
4785
+ pathData: clipPathData,
4786
+ fill: "#000000",
4787
+ stroke: null,
4788
+ originX: "left",
4789
+ originY: "top",
4790
+ selectable: false,
4791
+ evented: false,
4792
+ excludeFromExport: true
4793
+ }
4213
4794
  }
4214
- if (e.key.startsWith("dieline.")) {
4215
- switch (e.key) {
4216
- case "dieline.shape":
4217
- s.shape = normalizeDielineShape(e.value, s.shape);
4218
- break;
4219
- case "dieline.shapeStyle":
4220
- s.shapeStyle = normalizeShapeStyle(e.value, s.shapeStyle);
4221
- break;
4222
- case "dieline.radius":
4223
- s.radius = parseLengthToMm(e.value, "mm");
4224
- break;
4225
- case "dieline.strokeWidth":
4226
- s.mainLine.width = e.value;
4227
- break;
4228
- case "dieline.strokeColor":
4229
- s.mainLine.color = e.value;
4230
- break;
4231
- case "dieline.dashLength":
4232
- s.mainLine.dashLength = e.value;
4233
- break;
4234
- case "dieline.style":
4235
- s.mainLine.style = e.value;
4236
- break;
4237
- case "dieline.offsetStrokeWidth":
4238
- s.offsetLine.width = e.value;
4239
- break;
4240
- case "dieline.offsetStrokeColor":
4241
- s.offsetLine.color = e.value;
4242
- break;
4243
- case "dieline.offsetDashLength":
4244
- s.offsetLine.dashLength = e.value;
4245
- break;
4246
- case "dieline.offsetStyle":
4247
- s.offsetLine.style = e.value;
4248
- break;
4249
- case "dieline.insideColor":
4250
- s.insideColor = e.value;
4251
- break;
4252
- case "dieline.showBleedLines":
4253
- s.showBleedLines = e.value;
4254
- break;
4255
- case "dieline.features":
4256
- s.features = e.value;
4257
- break;
4258
- case "dieline.pathData":
4259
- s.pathData = e.value;
4260
- break;
4261
- case "dieline.customSourceWidthPx":
4262
- s.customSourceWidthPx = Number.isFinite(Number(e.value)) && Number(e.value) > 0 ? Number(e.value) : void 0;
4263
- break;
4264
- case "dieline.customSourceHeightPx":
4265
- s.customSourceHeightPx = Number.isFinite(Number(e.value)) && Number(e.value) > 0 ? Number(e.value) : void 0;
4266
- break;
4795
+ }
4796
+ ]
4797
+ };
4798
+ }
4799
+
4800
+ // src/extensions/dieline/DielineTool.ts
4801
+ var DielineTool = class {
4802
+ constructor(options) {
4803
+ this.id = "pooder.kit.dieline";
4804
+ this.metadata = {
4805
+ name: "DielineTool"
4806
+ };
4807
+ this.state = createDefaultDielineState();
4808
+ this.specs = [];
4809
+ this.effects = [];
4810
+ this.renderSeq = 0;
4811
+ this.onCanvasResized = () => {
4812
+ this.updateDieline();
4813
+ };
4814
+ if (options) {
4815
+ if (options.mainLine) {
4816
+ Object.assign(this.state.mainLine, options.mainLine);
4817
+ delete options.mainLine;
4818
+ }
4819
+ if (options.offsetLine) {
4820
+ Object.assign(this.state.offsetLine, options.offsetLine);
4821
+ delete options.offsetLine;
4822
+ }
4823
+ if (options.shapeStyle) {
4824
+ this.state.shapeStyle = normalizeShapeStyle(
4825
+ options.shapeStyle,
4826
+ this.state.shapeStyle
4827
+ );
4828
+ delete options.shapeStyle;
4829
+ }
4830
+ Object.assign(this.state, options);
4831
+ this.state.shape = normalizeDielineShape(options.shape, this.state.shape);
4832
+ }
4833
+ }
4834
+ activate(context) {
4835
+ var _a;
4836
+ this.context = context;
4837
+ this.canvasService = context.services.get("CanvasService");
4838
+ if (!this.canvasService) {
4839
+ console.warn("CanvasService not found for DielineTool");
4840
+ return;
4841
+ }
4842
+ (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
4843
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(
4844
+ this.id,
4845
+ () => ({
4846
+ passes: [
4847
+ {
4848
+ id: DIELINE_LAYER_ID,
4849
+ stack: 700,
4850
+ order: 0,
4851
+ replace: true,
4852
+ visibility: {
4853
+ op: "not",
4854
+ expr: {
4855
+ op: "activeToolIn",
4856
+ ids: ["pooder.kit.image", "pooder.kit.white-ink"]
4857
+ }
4858
+ },
4859
+ effects: this.effects,
4860
+ objects: this.specs
4267
4861
  }
4862
+ ]
4863
+ }),
4864
+ { priority: 250 }
4865
+ );
4866
+ const configService = context.services.get(
4867
+ "ConfigurationService"
4868
+ );
4869
+ if (configService) {
4870
+ Object.assign(this.state, readDielineState(configService, this.state));
4871
+ configService.onAnyChange((e) => {
4872
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
4873
+ Object.assign(this.state, readDielineState(configService, this.state));
4268
4874
  this.updateDieline();
4269
4875
  }
4270
4876
  });
@@ -4335,272 +4941,34 @@ var DielineTool = class {
4335
4941
  const items = configService.get("image.items", []);
4336
4942
  return Array.isArray(items) && items.length > 0;
4337
4943
  }
4338
- syncSizeState(configService) {
4339
- const sizeState = readSizeState(configService);
4340
- this.state.width = sizeState.actualWidthMm;
4341
- this.state.height = sizeState.actualHeightMm;
4342
- this.state.padding = sizeState.viewPadding;
4343
- this.state.offset = sizeState.cutMode === "outset" ? sizeState.cutMarginMm : sizeState.cutMode === "inset" ? -sizeState.cutMarginMm : 0;
4344
- }
4345
4944
  buildDielineSpecs(sceneLayout) {
4346
4945
  var _a, _b;
4347
- const {
4348
- shape,
4349
- shapeStyle,
4350
- radius,
4351
- mainLine,
4352
- offsetLine,
4353
- insideColor,
4354
- showBleedLines,
4355
- features
4356
- } = this.state;
4357
4946
  const hasImages = this.hasImageItems();
4358
- const canvasW = sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
4359
- const canvasH = sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
4360
- const scale = sceneLayout.scale;
4361
- const cx = sceneLayout.trimRect.centerX;
4362
- const cy = sceneLayout.trimRect.centerY;
4363
- const visualWidth = sceneLayout.trimRect.width;
4364
- const visualHeight = sceneLayout.trimRect.height;
4365
- const visualRadius = radius * scale;
4366
- const cutW = sceneLayout.cutRect.width;
4367
- const cutH = sceneLayout.cutRect.height;
4368
- const visualOffset = (cutW - visualWidth) / 2;
4369
- const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
4370
- const absoluteFeatures = (features || []).map((f) => ({
4371
- ...f,
4372
- x: f.x,
4373
- y: f.y,
4374
- width: (f.width || 0) * scale,
4375
- height: (f.height || 0) * scale,
4376
- radius: (f.radius || 0) * scale
4377
- }));
4378
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
4379
- const specs = [];
4380
- if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
4381
- const productPathData = generateDielinePath({
4382
- shape,
4383
- width: cutW,
4384
- height: cutH,
4385
- radius: cutR,
4386
- x: cx,
4387
- y: cy,
4388
- features: cutFeatures,
4389
- shapeStyle,
4390
- pathData: this.state.pathData,
4391
- customSourceWidthPx: this.state.customSourceWidthPx,
4392
- customSourceHeightPx: this.state.customSourceHeightPx,
4393
- canvasWidth: canvasW,
4394
- canvasHeight: canvasH
4395
- });
4396
- specs.push({
4397
- id: "dieline.inside",
4398
- type: "path",
4399
- space: "screen",
4400
- data: { id: "dieline.inside", type: "dieline" },
4401
- props: {
4402
- pathData: productPathData,
4403
- fill: insideColor,
4404
- stroke: null,
4405
- selectable: false,
4406
- evented: false,
4407
- originX: "left",
4408
- originY: "top"
4409
- }
4410
- });
4411
- }
4412
- if (Math.abs(visualOffset) > 1e-4) {
4413
- const bleedPathData = generateBleedZonePath(
4414
- {
4415
- shape,
4416
- width: visualWidth,
4417
- height: visualHeight,
4418
- radius: visualRadius,
4419
- x: cx,
4420
- y: cy,
4421
- features: cutFeatures,
4422
- shapeStyle,
4423
- pathData: this.state.pathData,
4424
- customSourceWidthPx: this.state.customSourceWidthPx,
4425
- customSourceHeightPx: this.state.customSourceHeightPx,
4426
- canvasWidth: canvasW,
4427
- canvasHeight: canvasH
4428
- },
4429
- {
4430
- shape,
4431
- width: cutW,
4432
- height: cutH,
4433
- radius: cutR,
4434
- x: cx,
4435
- y: cy,
4436
- features: cutFeatures,
4437
- shapeStyle,
4438
- pathData: this.state.pathData,
4439
- customSourceWidthPx: this.state.customSourceWidthPx,
4440
- customSourceHeightPx: this.state.customSourceHeightPx,
4441
- canvasWidth: canvasW,
4442
- canvasHeight: canvasH
4443
- },
4444
- visualOffset
4445
- );
4446
- if (showBleedLines !== false) {
4447
- const pattern = this.createHatchPattern(mainLine.color);
4448
- if (pattern) {
4449
- specs.push({
4450
- id: "dieline.bleed-zone",
4451
- type: "path",
4452
- space: "screen",
4453
- data: { id: "dieline.bleed-zone", type: "dieline" },
4454
- props: {
4455
- pathData: bleedPathData,
4456
- fill: pattern,
4457
- stroke: null,
4458
- selectable: false,
4459
- evented: false,
4460
- objectCaching: false,
4461
- originX: "left",
4462
- originY: "top"
4463
- }
4464
- });
4465
- }
4466
- }
4467
- const offsetPathData = generateDielinePath({
4468
- shape,
4469
- width: cutW,
4470
- height: cutH,
4471
- radius: cutR,
4472
- x: cx,
4473
- y: cy,
4474
- features: cutFeatures,
4475
- shapeStyle,
4476
- pathData: this.state.pathData,
4477
- customSourceWidthPx: this.state.customSourceWidthPx,
4478
- customSourceHeightPx: this.state.customSourceHeightPx,
4479
- canvasWidth: canvasW,
4480
- canvasHeight: canvasH
4481
- });
4482
- specs.push({
4483
- id: "dieline.offset-border",
4484
- type: "path",
4485
- space: "screen",
4486
- data: { id: "dieline.offset-border", type: "dieline" },
4487
- props: {
4488
- pathData: offsetPathData,
4489
- fill: null,
4490
- stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
4491
- strokeWidth: offsetLine.width,
4492
- strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
4493
- selectable: false,
4494
- evented: false,
4495
- originX: "left",
4496
- originY: "top"
4497
- }
4498
- });
4499
- }
4500
- const borderPathData = generateDielinePath({
4501
- shape,
4502
- width: visualWidth,
4503
- height: visualHeight,
4504
- radius: visualRadius,
4505
- x: cx,
4506
- y: cy,
4507
- features: absoluteFeatures,
4508
- shapeStyle,
4509
- pathData: this.state.pathData,
4510
- customSourceWidthPx: this.state.customSourceWidthPx,
4511
- customSourceHeightPx: this.state.customSourceHeightPx,
4512
- canvasWidth: canvasW,
4513
- canvasHeight: canvasH
4514
- });
4515
- specs.push({
4516
- id: "dieline.border",
4517
- type: "path",
4518
- space: "screen",
4519
- data: { id: "dieline.border", type: "dieline" },
4520
- props: {
4521
- pathData: borderPathData,
4522
- fill: "transparent",
4523
- stroke: mainLine.style === "hidden" ? null : mainLine.color,
4524
- strokeWidth: mainLine.width,
4525
- strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
4526
- selectable: false,
4527
- evented: false,
4528
- originX: "left",
4529
- originY: "top"
4530
- }
4531
- });
4532
- return specs;
4947
+ return buildDielineRenderBundle({
4948
+ state: this.state,
4949
+ sceneLayout,
4950
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
4951
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
4952
+ hasImages,
4953
+ createHatchPattern: (color) => this.createHatchPattern(color),
4954
+ includeImageClipEffect: false
4955
+ }).specs;
4533
4956
  }
4534
4957
  buildImageClipEffects(sceneLayout) {
4535
4958
  var _a, _b;
4536
- const { shape, shapeStyle, radius, features } = this.state;
4537
- const canvasW = sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
4538
- const canvasH = sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
4539
- const scale = sceneLayout.scale;
4540
- const cx = sceneLayout.trimRect.centerX;
4541
- const cy = sceneLayout.trimRect.centerY;
4542
- const visualWidth = sceneLayout.trimRect.width;
4543
- const visualRadius = radius * scale;
4544
- const cutW = sceneLayout.cutRect.width;
4545
- const cutH = sceneLayout.cutRect.height;
4546
- const visualOffset = (cutW - visualWidth) / 2;
4547
- const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
4548
- const absoluteFeatures = (features || []).map((f) => ({
4549
- ...f,
4550
- x: f.x,
4551
- y: f.y,
4552
- width: (f.width || 0) * scale,
4553
- height: (f.height || 0) * scale,
4554
- radius: (f.radius || 0) * scale
4555
- }));
4556
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
4557
- const clipPathData = generateDielinePath({
4558
- shape,
4559
- width: cutW,
4560
- height: cutH,
4561
- radius: cutR,
4562
- x: cx,
4563
- y: cy,
4564
- features: cutFeatures,
4565
- shapeStyle,
4566
- pathData: this.state.pathData,
4567
- customSourceWidthPx: this.state.customSourceWidthPx,
4568
- customSourceHeightPx: this.state.customSourceHeightPx,
4569
- canvasWidth: canvasW,
4570
- canvasHeight: canvasH
4571
- });
4572
- if (!clipPathData) return [];
4573
- return [
4574
- {
4575
- type: "clipPath",
4576
- id: "dieline.clip.image",
4577
- visibility: {
4578
- op: "not",
4579
- expr: { op: "anySessionActive" }
4580
- },
4581
- targetPassIds: [IMAGE_OBJECT_LAYER_ID],
4582
- source: {
4583
- id: "dieline.effect.clip-path",
4584
- type: "path",
4585
- space: "screen",
4586
- data: {
4587
- id: "dieline.effect.clip-path",
4588
- type: "dieline-effect",
4589
- effect: "clipPath"
4590
- },
4591
- props: {
4592
- pathData: clipPathData,
4593
- fill: "#000000",
4594
- stroke: null,
4595
- originX: "left",
4596
- originY: "top",
4597
- selectable: false,
4598
- evented: false,
4599
- excludeFromExport: true
4600
- }
4601
- }
4959
+ return buildDielineRenderBundle({
4960
+ state: this.state,
4961
+ sceneLayout,
4962
+ canvasWidth: sceneLayout.canvasWidth || ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800,
4963
+ canvasHeight: sceneLayout.canvasHeight || ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600,
4964
+ hasImages: this.hasImageItems(),
4965
+ includeImageClipEffect: true,
4966
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
4967
+ clipVisibility: {
4968
+ op: "not",
4969
+ expr: { op: "anySessionActive" }
4602
4970
  }
4603
- ];
4971
+ }).effects;
4604
4972
  }
4605
4973
  updateDieline(_emitEvent = true) {
4606
4974
  void this.updateDielineAsync();
@@ -4610,7 +4978,7 @@ var DielineTool = class {
4610
4978
  const configService = this.getConfigService();
4611
4979
  if (!configService) return;
4612
4980
  const seq = ++this.renderSeq;
4613
- this.syncSizeState(configService);
4981
+ Object.assign(this.state, readDielineState(configService, this.state));
4614
4982
  const sceneLayout = computeSceneLayout(
4615
4983
  this.canvasService,
4616
4984
  readSizeState(configService)
@@ -4665,7 +5033,7 @@ var DielineTool = class {
4665
5033
  );
4666
5034
  return null;
4667
5035
  }
4668
- this.syncSizeState(configService);
5036
+ this.state = readDielineState(configService, this.state);
4669
5037
  const sceneLayout = computeSceneLayout(
4670
5038
  this.canvasService,
4671
5039
  readSizeState(configService)
@@ -4819,6 +5187,7 @@ var DielineTool = class {
4819
5187
  import {
4820
5188
  ContributionPointIds as ContributionPointIds5
4821
5189
  } from "@pooder/core";
5190
+ import { Pattern as Pattern3 } from "fabric";
4822
5191
 
4823
5192
  // src/extensions/constraints.ts
4824
5193
  var ConstraintRegistry = class {
@@ -5033,7 +5402,9 @@ var FeatureTool = class {
5033
5402
  this.isFeatureSessionActive = false;
5034
5403
  this.sessionOriginalFeatures = null;
5035
5404
  this.hasWorkingChanges = false;
5036
- this.specs = [];
5405
+ this.markerSpecs = [];
5406
+ this.sessionDielineSpecs = [];
5407
+ this.sessionDielineEffects = [];
5037
5408
  this.renderSeq = 0;
5038
5409
  this.subscriptions = new SubscriptionBag();
5039
5410
  this.handleMoving = null;
@@ -5043,7 +5414,7 @@ var FeatureTool = class {
5043
5414
  this.onToolActivated = (event) => {
5044
5415
  this.isToolActive = event.id === this.id;
5045
5416
  if (!this.isToolActive) {
5046
- this.restoreSessionFeaturesToConfig();
5417
+ this.suspendFeatureSession();
5047
5418
  }
5048
5419
  this.updateVisibility();
5049
5420
  };
@@ -5063,16 +5434,38 @@ var FeatureTool = class {
5063
5434
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
5064
5435
  this.renderProducerDisposable = this.canvasService.registerRenderProducer(
5065
5436
  this.id,
5066
- () => ({
5067
- passes: [
5437
+ () => {
5438
+ const passes = [
5068
5439
  {
5069
5440
  id: FEATURE_OVERLAY_LAYER_ID,
5070
5441
  stack: 880,
5071
5442
  order: 0,
5072
- objects: this.specs
5443
+ replace: true,
5444
+ objects: this.markerSpecs
5073
5445
  }
5074
- ]
5075
- }),
5446
+ ];
5447
+ if (this.isSessionVisible()) {
5448
+ passes.push(
5449
+ {
5450
+ id: DIELINE_LAYER_ID,
5451
+ stack: 700,
5452
+ order: 0,
5453
+ replace: false,
5454
+ visibility: { op: "const", value: false },
5455
+ objects: []
5456
+ },
5457
+ {
5458
+ id: FEATURE_DIELINE_LAYER_ID,
5459
+ stack: 705,
5460
+ order: 0,
5461
+ replace: true,
5462
+ effects: this.sessionDielineEffects,
5463
+ objects: this.sessionDielineSpecs
5464
+ }
5465
+ );
5466
+ }
5467
+ return { passes };
5468
+ },
5076
5469
  { priority: 350 }
5077
5470
  );
5078
5471
  const configService = context.services.get(
@@ -5087,12 +5480,22 @@ var FeatureTool = class {
5087
5480
  (e) => {
5088
5481
  if (this.isUpdatingConfig) return;
5089
5482
  if (e.key === "dieline.features") {
5090
- if (this.isFeatureSessionActive) return;
5483
+ if (this.isFeatureSessionActive && this.hasFeatureSessionDraft()) {
5484
+ return;
5485
+ }
5486
+ if (this.hasFeatureSessionDraft()) {
5487
+ this.clearFeatureSessionState();
5488
+ }
5091
5489
  const next = e.value || [];
5092
5490
  this.workingFeatures = this.cloneFeatures(next);
5093
5491
  this.hasWorkingChanges = false;
5094
5492
  this.redraw();
5095
5493
  this.emitWorkingChange();
5494
+ return;
5495
+ }
5496
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
5497
+ void this.refreshGeometry();
5498
+ this.redraw({ enforceConstraints: true });
5096
5499
  }
5097
5500
  }
5098
5501
  );
@@ -5108,7 +5511,8 @@ var FeatureTool = class {
5108
5511
  deactivate(context) {
5109
5512
  var _a;
5110
5513
  this.subscriptions.disposeAll();
5111
- this.restoreSessionFeaturesToConfig();
5514
+ this.restoreCommittedFeaturesToConfig();
5515
+ this.clearFeatureSessionState();
5112
5516
  (_a = this.dirtyTrackerDisposable) == null ? void 0 : _a.dispose();
5113
5517
  this.dirtyTrackerDisposable = void 0;
5114
5518
  this.teardown();
@@ -5118,6 +5522,9 @@ var FeatureTool = class {
5118
5522
  updateVisibility() {
5119
5523
  this.redraw();
5120
5524
  }
5525
+ isSessionVisible() {
5526
+ return this.isToolActive && this.isFeatureSessionActive;
5527
+ }
5121
5528
  contribute() {
5122
5529
  return {
5123
5530
  [ContributionPointIds5.TOOLS]: [
@@ -5144,15 +5551,16 @@ var FeatureTool = class {
5144
5551
  if (this.isFeatureSessionActive) {
5145
5552
  return { ok: true };
5146
5553
  }
5147
- const original = this.getCommittedFeatures();
5148
- this.sessionOriginalFeatures = this.cloneFeatures(original);
5554
+ if (!this.hasFeatureSessionDraft()) {
5555
+ const original = this.getCommittedFeatures();
5556
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
5557
+ this.setWorkingFeatures(this.cloneFeatures(original));
5558
+ this.hasWorkingChanges = false;
5559
+ }
5149
5560
  this.isFeatureSessionActive = true;
5150
5561
  await this.refreshGeometry();
5151
- this.setWorkingFeatures(this.cloneFeatures(original));
5152
- this.hasWorkingChanges = false;
5153
5562
  this.redraw();
5154
5563
  this.emitWorkingChange();
5155
- this.updateCommittedFeatures([]);
5156
5564
  return { ok: true };
5157
5565
  }
5158
5566
  },
@@ -5188,25 +5596,6 @@ var FeatureTool = class {
5188
5596
  return true;
5189
5597
  }
5190
5598
  },
5191
- {
5192
- command: "getWorkingFeatures",
5193
- title: "Get Working Features",
5194
- handler: () => {
5195
- return this.cloneFeatures(this.workingFeatures);
5196
- }
5197
- },
5198
- {
5199
- command: "setWorkingFeatures",
5200
- title: "Set Working Features",
5201
- handler: async (features) => {
5202
- await this.refreshGeometry();
5203
- this.setWorkingFeatures(this.cloneFeatures(features || []));
5204
- this.hasWorkingChanges = true;
5205
- this.redraw();
5206
- this.emitWorkingChange();
5207
- return { ok: true };
5208
- }
5209
- },
5210
5599
  {
5211
5600
  command: "rollbackFeatureSession",
5212
5601
  title: "Rollback Feature Session",
@@ -5273,17 +5662,24 @@ var FeatureTool = class {
5273
5662
  this.isUpdatingConfig = false;
5274
5663
  }
5275
5664
  }
5665
+ hasFeatureSessionDraft() {
5666
+ return Array.isArray(this.sessionOriginalFeatures);
5667
+ }
5276
5668
  clearFeatureSessionState() {
5277
5669
  this.isFeatureSessionActive = false;
5278
5670
  this.sessionOriginalFeatures = null;
5279
5671
  }
5280
- restoreSessionFeaturesToConfig() {
5281
- if (!this.isFeatureSessionActive) return;
5672
+ restoreCommittedFeaturesToConfig() {
5673
+ if (!this.hasFeatureSessionDraft()) return;
5282
5674
  const original = this.cloneFeatures(
5283
5675
  this.sessionOriginalFeatures || this.getCommittedFeatures()
5284
5676
  );
5285
5677
  this.updateCommittedFeatures(original);
5286
- this.clearFeatureSessionState();
5678
+ }
5679
+ suspendFeatureSession() {
5680
+ if (!this.isFeatureSessionActive) return;
5681
+ this.restoreCommittedFeaturesToConfig();
5682
+ this.isFeatureSessionActive = false;
5287
5683
  }
5288
5684
  emitWorkingChange() {
5289
5685
  var _a;
@@ -5305,7 +5701,7 @@ var FeatureTool = class {
5305
5701
  }
5306
5702
  async resetWorkingFeaturesFromSource() {
5307
5703
  const next = this.cloneFeatures(
5308
- this.isFeatureSessionActive && this.sessionOriginalFeatures ? this.sessionOriginalFeatures : this.getCommittedFeatures()
5704
+ this.sessionOriginalFeatures || this.getCommittedFeatures()
5309
5705
  );
5310
5706
  await this.refreshGeometry();
5311
5707
  this.setWorkingFeatures(next);
@@ -5529,11 +5925,35 @@ var FeatureTool = class {
5529
5925
  this.handleSceneGeometryChange = null;
5530
5926
  }
5531
5927
  this.renderSeq += 1;
5532
- this.specs = [];
5928
+ this.markerSpecs = [];
5929
+ this.sessionDielineSpecs = [];
5930
+ this.sessionDielineEffects = [];
5533
5931
  (_a = this.renderProducerDisposable) == null ? void 0 : _a.dispose();
5534
5932
  this.renderProducerDisposable = void 0;
5535
5933
  void this.canvasService.flushRenderFromProducers();
5536
5934
  }
5935
+ createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
5936
+ if (typeof document === "undefined") {
5937
+ return void 0;
5938
+ }
5939
+ const size = 20;
5940
+ const canvas = document.createElement("canvas");
5941
+ canvas.width = size;
5942
+ canvas.height = size;
5943
+ const ctx = canvas.getContext("2d");
5944
+ if (ctx) {
5945
+ ctx.clearRect(0, 0, size, size);
5946
+ ctx.strokeStyle = color;
5947
+ ctx.lineWidth = 1;
5948
+ ctx.beginPath();
5949
+ ctx.moveTo(0, size);
5950
+ ctx.lineTo(size, 0);
5951
+ ctx.stroke();
5952
+ }
5953
+ return new Pattern3({
5954
+ source: canvas
5955
+ });
5956
+ }
5537
5957
  getDraggableMarkerTarget(target) {
5538
5958
  var _a, _b;
5539
5959
  if (!this.isFeatureSessionActive || !this.isToolActive) return null;
@@ -5609,6 +6029,7 @@ var FeatureTool = class {
5609
6029
  next[index] = updatedFeature;
5610
6030
  this.setWorkingFeatures(next);
5611
6031
  this.hasWorkingChanges = true;
6032
+ this.redraw();
5612
6033
  this.emitWorkingChange();
5613
6034
  }
5614
6035
  syncGroupFromCanvas(target) {
@@ -5647,6 +6068,7 @@ var FeatureTool = class {
5647
6068
  if (!changed) return;
5648
6069
  this.setWorkingFeatures(next);
5649
6070
  this.hasWorkingChanges = true;
6071
+ this.redraw();
5650
6072
  this.emitWorkingChange();
5651
6073
  }
5652
6074
  redraw(options = {}) {
@@ -5655,7 +6077,10 @@ var FeatureTool = class {
5655
6077
  async redrawAsync(options = {}) {
5656
6078
  if (!this.canvasService) return;
5657
6079
  const seq = ++this.renderSeq;
5658
- this.specs = this.buildFeatureSpecs();
6080
+ this.markerSpecs = this.buildMarkerSpecs();
6081
+ const sessionRender = this.buildSessionDielineRender();
6082
+ this.sessionDielineSpecs = sessionRender.specs;
6083
+ this.sessionDielineEffects = sessionRender.effects;
5659
6084
  if (seq !== this.renderSeq) return;
5660
6085
  await this.canvasService.flushRenderFromProducers();
5661
6086
  if (seq !== this.renderSeq) return;
@@ -5663,7 +6088,49 @@ var FeatureTool = class {
5663
6088
  this.enforceConstraints();
5664
6089
  }
5665
6090
  }
5666
- buildFeatureSpecs() {
6091
+ buildSessionDielineRender() {
6092
+ if (!this.isSessionVisible() || !this.canvasService) {
6093
+ return { specs: [], effects: [] };
6094
+ }
6095
+ const configService = this.getConfigService();
6096
+ if (!configService) {
6097
+ return { specs: [], effects: [] };
6098
+ }
6099
+ const sceneLayout = computeSceneLayout(
6100
+ this.canvasService,
6101
+ readSizeState(configService)
6102
+ );
6103
+ if (!sceneLayout) {
6104
+ return { specs: [], effects: [] };
6105
+ }
6106
+ const state = readDielineState(configService);
6107
+ state.features = this.cloneFeatures(this.workingFeatures);
6108
+ return buildDielineRenderBundle({
6109
+ state,
6110
+ sceneLayout,
6111
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService.canvas.width || 800,
6112
+ canvasHeight: sceneLayout.canvasHeight || this.canvasService.canvas.height || 600,
6113
+ hasImages: this.hasImageItems(),
6114
+ createHatchPattern: (color) => this.createHatchPattern(color),
6115
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
6116
+ clipVisibility: { op: "const", value: true },
6117
+ ids: {
6118
+ inside: "feature.session.dieline.inside",
6119
+ bleedZone: "feature.session.dieline.bleed-zone",
6120
+ offsetBorder: "feature.session.dieline.offset-border",
6121
+ border: "feature.session.dieline.border",
6122
+ clip: "feature.session.dieline.clip.image",
6123
+ clipSource: "feature.session.dieline.effect.clip-path"
6124
+ }
6125
+ });
6126
+ }
6127
+ hasImageItems() {
6128
+ const configService = this.getConfigService();
6129
+ if (!configService) return false;
6130
+ const items = configService.get("image.items", []);
6131
+ return Array.isArray(items) && items.length > 0;
6132
+ }
6133
+ buildMarkerSpecs() {
5667
6134
  if (!this.isFeatureSessionActive || !this.currentGeometry || this.workingFeatures.length === 0) {
5668
6135
  return [];
5669
6136
  }
@@ -9250,11 +9717,13 @@ export {
9250
9717
  WhiteInkTool,
9251
9718
  getCoverScale as computeImageCoverScale,
9252
9719
  getCoverScale as computeWhiteInkCoverScale,
9720
+ createDefaultDielineState,
9253
9721
  createDielineCommands,
9254
9722
  createDielineConfigurations,
9255
9723
  createImageCommands,
9256
9724
  createImageConfigurations,
9257
9725
  createWhiteInkCommands,
9258
9726
  createWhiteInkConfigurations,
9259
- evaluateVisibilityExpr
9727
+ evaluateVisibilityExpr,
9728
+ readDielineState
9260
9729
  };