@pooder/kit 5.3.1 → 6.0.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.
Files changed (65) hide show
  1. package/.test-dist/src/extensions/background.js +475 -131
  2. package/.test-dist/src/extensions/dieline.js +283 -180
  3. package/.test-dist/src/extensions/dielineShape.js +66 -0
  4. package/.test-dist/src/extensions/feature.js +388 -303
  5. package/.test-dist/src/extensions/film.js +133 -74
  6. package/.test-dist/src/extensions/geometry.js +120 -56
  7. package/.test-dist/src/extensions/image.js +296 -212
  8. package/.test-dist/src/extensions/index.js +1 -3
  9. package/.test-dist/src/extensions/maskOps.js +75 -20
  10. package/.test-dist/src/extensions/ruler.js +312 -215
  11. package/.test-dist/src/extensions/sceneLayoutModel.js +9 -3
  12. package/.test-dist/src/extensions/sceneVisibility.js +3 -10
  13. package/.test-dist/src/extensions/tracer.js +229 -58
  14. package/.test-dist/src/extensions/white-ink.js +139 -129
  15. package/.test-dist/src/services/CanvasService.js +888 -126
  16. package/.test-dist/src/services/index.js +1 -0
  17. package/.test-dist/src/services/visibility.js +54 -0
  18. package/.test-dist/tests/run.js +58 -4
  19. package/CHANGELOG.md +12 -0
  20. package/dist/index.d.mts +377 -82
  21. package/dist/index.d.ts +377 -82
  22. package/dist/index.js +3920 -2178
  23. package/dist/index.mjs +3992 -2247
  24. package/package.json +1 -1
  25. package/src/extensions/background.ts +631 -145
  26. package/src/extensions/dieline.ts +280 -187
  27. package/src/extensions/dielineShape.ts +109 -0
  28. package/src/extensions/feature.ts +485 -366
  29. package/src/extensions/film.ts +152 -76
  30. package/src/extensions/geometry.ts +203 -104
  31. package/src/extensions/image.ts +319 -238
  32. package/src/extensions/index.ts +0 -1
  33. package/src/extensions/ruler.ts +481 -268
  34. package/src/extensions/sceneLayoutModel.ts +18 -6
  35. package/src/extensions/white-ink.ts +157 -171
  36. package/src/services/CanvasService.ts +1126 -140
  37. package/src/services/index.ts +1 -0
  38. package/src/services/renderSpec.ts +69 -4
  39. package/src/services/visibility.ts +78 -0
  40. package/tests/run.ts +139 -4
  41. package/.test-dist/src/CanvasService.js +0 -249
  42. package/.test-dist/src/ViewportSystem.js +0 -75
  43. package/.test-dist/src/background.js +0 -203
  44. package/.test-dist/src/bridgeSelection.js +0 -20
  45. package/.test-dist/src/constraints.js +0 -237
  46. package/.test-dist/src/dieline.js +0 -818
  47. package/.test-dist/src/edgeScale.js +0 -12
  48. package/.test-dist/src/feature.js +0 -826
  49. package/.test-dist/src/featureComplete.js +0 -32
  50. package/.test-dist/src/film.js +0 -167
  51. package/.test-dist/src/geometry.js +0 -506
  52. package/.test-dist/src/image.js +0 -1250
  53. package/.test-dist/src/maskOps.js +0 -270
  54. package/.test-dist/src/mirror.js +0 -104
  55. package/.test-dist/src/renderSpec.js +0 -2
  56. package/.test-dist/src/ruler.js +0 -343
  57. package/.test-dist/src/sceneLayout.js +0 -99
  58. package/.test-dist/src/sceneLayoutModel.js +0 -196
  59. package/.test-dist/src/sceneView.js +0 -40
  60. package/.test-dist/src/sceneVisibility.js +0 -42
  61. package/.test-dist/src/size.js +0 -332
  62. package/.test-dist/src/tracer.js +0 -544
  63. package/.test-dist/src/white-ink.js +0 -829
  64. package/.test-dist/src/wrappedOffsets.js +0 -33
  65. package/src/extensions/sceneVisibility.ts +0 -71
@@ -5,9 +5,11 @@ const core_1 = require("@pooder/core");
5
5
  const fabric_1 = require("fabric");
6
6
  const tracer_1 = require("./tracer");
7
7
  const units_1 = require("../units");
8
+ const dielineShape_1 = require("./dielineShape");
8
9
  const geometry_1 = require("./geometry");
9
10
  const sceneLayoutModel_1 = require("./sceneLayoutModel");
10
11
  const IMAGE_OBJECT_LAYER_ID = "image.user";
12
+ const DIELINE_LAYER_ID = "dieline-overlay";
11
13
  class DielineTool {
12
14
  constructor(options) {
13
15
  this.id = "pooder.kit.dieline";
@@ -15,8 +17,8 @@ class DielineTool {
15
17
  name: "DielineTool",
16
18
  };
17
19
  this.state = {
18
- displayUnit: "mm",
19
- shape: "rect",
20
+ shape: dielineShape_1.DEFAULT_DIELINE_SHAPE,
21
+ shapeStyle: { ...dielineShape_1.DEFAULT_DIELINE_SHAPE_STYLE },
20
22
  width: 500,
21
23
  height: 500,
22
24
  radius: 0,
@@ -35,10 +37,12 @@ class DielineTool {
35
37
  style: "solid",
36
38
  },
37
39
  insideColor: "rgba(0,0,0,0)",
38
- outsideColor: "#ffffff",
39
40
  showBleedLines: true,
40
41
  features: [],
41
42
  };
43
+ this.specs = [];
44
+ this.effects = [];
45
+ this.renderSeq = 0;
42
46
  this.onCanvasResized = () => {
43
47
  this.updateDieline();
44
48
  };
@@ -52,7 +56,12 @@ class DielineTool {
52
56
  Object.assign(this.state.offsetLine, options.offsetLine);
53
57
  delete options.offsetLine;
54
58
  }
59
+ if (options.shapeStyle) {
60
+ this.state.shapeStyle = (0, dielineShape_1.normalizeShapeStyle)(options.shapeStyle, this.state.shapeStyle);
61
+ delete options.shapeStyle;
62
+ }
55
63
  Object.assign(this.state, options);
64
+ this.state.shape = (0, dielineShape_1.normalizeDielineShape)(options.shape, this.state.shape);
56
65
  }
57
66
  }
58
67
  activate(context) {
@@ -62,13 +71,33 @@ class DielineTool {
62
71
  console.warn("CanvasService not found for DielineTool");
63
72
  return;
64
73
  }
74
+ this.renderProducerDisposable?.dispose();
75
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
76
+ passes: [
77
+ {
78
+ id: DIELINE_LAYER_ID,
79
+ stack: 700,
80
+ order: 0,
81
+ replace: true,
82
+ visibility: {
83
+ op: "not",
84
+ expr: {
85
+ op: "activeToolIn",
86
+ ids: ["pooder.kit.image", "pooder.kit.white-ink"],
87
+ },
88
+ },
89
+ effects: this.effects,
90
+ objects: this.specs,
91
+ },
92
+ ],
93
+ }), { priority: 250 });
65
94
  const configService = context.services.get("ConfigurationService");
66
95
  if (configService) {
67
96
  // Load initial config
68
97
  const s = this.state;
69
98
  const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
70
- s.displayUnit = sizeState.unit;
71
- s.shape = configService.get("dieline.shape", s.shape);
99
+ s.shape = (0, dielineShape_1.normalizeDielineShape)(configService.get("dieline.shape", s.shape), s.shape);
100
+ s.shapeStyle = (0, dielineShape_1.normalizeShapeStyle)(configService.get("dieline.shapeStyle", s.shapeStyle), s.shapeStyle);
72
101
  s.width = sizeState.actualWidthMm;
73
102
  s.height = sizeState.actualHeightMm;
74
103
  s.radius = (0, units_1.parseLengthToMm)(configService.get("dieline.radius", s.radius), "mm");
@@ -90,15 +119,23 @@ class DielineTool {
90
119
  s.offsetLine.dashLength = configService.get("dieline.offsetDashLength", s.offsetLine.dashLength);
91
120
  s.offsetLine.style = configService.get("dieline.offsetStyle", s.offsetLine.style);
92
121
  s.insideColor = configService.get("dieline.insideColor", s.insideColor);
93
- s.outsideColor = configService.get("dieline.outsideColor", s.outsideColor);
94
122
  s.showBleedLines = configService.get("dieline.showBleedLines", s.showBleedLines);
95
123
  s.features = configService.get("dieline.features", s.features);
96
124
  s.pathData = configService.get("dieline.pathData", s.pathData);
125
+ const sourceWidth = Number(configService.get("dieline.customSourceWidthPx", 0));
126
+ const sourceHeight = Number(configService.get("dieline.customSourceHeightPx", 0));
127
+ s.customSourceWidthPx =
128
+ Number.isFinite(sourceWidth) && sourceWidth > 0
129
+ ? sourceWidth
130
+ : undefined;
131
+ s.customSourceHeightPx =
132
+ Number.isFinite(sourceHeight) && sourceHeight > 0
133
+ ? sourceHeight
134
+ : undefined;
97
135
  // Listen for changes
98
136
  configService.onAnyChange((e) => {
99
137
  if (e.key.startsWith("size.")) {
100
138
  const nextSize = (0, sceneLayoutModel_1.readSizeState)(configService);
101
- s.displayUnit = nextSize.unit;
102
139
  s.width = nextSize.actualWidthMm;
103
140
  s.height = nextSize.actualHeightMm;
104
141
  s.padding = nextSize.viewPadding;
@@ -114,7 +151,10 @@ class DielineTool {
114
151
  if (e.key.startsWith("dieline.")) {
115
152
  switch (e.key) {
116
153
  case "dieline.shape":
117
- s.shape = e.value;
154
+ s.shape = (0, dielineShape_1.normalizeDielineShape)(e.value, s.shape);
155
+ break;
156
+ case "dieline.shapeStyle":
157
+ s.shapeStyle = (0, dielineShape_1.normalizeShapeStyle)(e.value, s.shapeStyle);
118
158
  break;
119
159
  case "dieline.radius":
120
160
  s.radius = (0, units_1.parseLengthToMm)(e.value, "mm");
@@ -146,9 +186,6 @@ class DielineTool {
146
186
  case "dieline.insideColor":
147
187
  s.insideColor = e.value;
148
188
  break;
149
- case "dieline.outsideColor":
150
- s.outsideColor = e.value;
151
- break;
152
189
  case "dieline.showBleedLines":
153
190
  s.showBleedLines = e.value;
154
191
  break;
@@ -158,18 +195,36 @@ class DielineTool {
158
195
  case "dieline.pathData":
159
196
  s.pathData = e.value;
160
197
  break;
198
+ case "dieline.customSourceWidthPx":
199
+ s.customSourceWidthPx =
200
+ Number.isFinite(Number(e.value)) && Number(e.value) > 0
201
+ ? Number(e.value)
202
+ : undefined;
203
+ break;
204
+ case "dieline.customSourceHeightPx":
205
+ s.customSourceHeightPx =
206
+ Number.isFinite(Number(e.value)) && Number(e.value) > 0
207
+ ? Number(e.value)
208
+ : undefined;
209
+ break;
161
210
  }
162
211
  this.updateDieline();
163
212
  }
164
213
  });
165
214
  }
166
215
  context.eventBus.on("canvas:resized", this.onCanvasResized);
167
- this.createLayer();
168
216
  this.updateDieline();
169
217
  }
170
218
  deactivate(context) {
171
219
  context.eventBus.off("canvas:resized", this.onCanvasResized);
172
- this.destroyLayer();
220
+ this.renderSeq += 1;
221
+ this.specs = [];
222
+ this.effects = [];
223
+ this.renderProducerDisposable?.dispose();
224
+ this.renderProducerDisposable = undefined;
225
+ if (this.canvasService) {
226
+ void this.canvasService.flushRenderFromProducers();
227
+ }
173
228
  this.canvasService = undefined;
174
229
  this.context = undefined;
175
230
  }
@@ -192,7 +247,7 @@ class DielineTool {
192
247
  id: "dieline.shape",
193
248
  type: "select",
194
249
  label: "Shape",
195
- options: ["rect", "circle", "ellipse", "custom"],
250
+ options: Array.from(dielineShape_1.DIELINE_SHAPES),
196
251
  default: s.shape,
197
252
  },
198
253
  {
@@ -203,6 +258,12 @@ class DielineTool {
203
258
  max: 500,
204
259
  default: s.radius,
205
260
  },
261
+ {
262
+ id: "dieline.shapeStyle",
263
+ type: "json",
264
+ label: "Shape Style",
265
+ default: s.shapeStyle,
266
+ },
206
267
  {
207
268
  id: "dieline.showBleedLines",
208
269
  type: "boolean",
@@ -275,12 +336,6 @@ class DielineTool {
275
336
  label: "Inside Color",
276
337
  default: s.insideColor,
277
338
  },
278
- {
279
- id: "dieline.outsideColor",
280
- type: "color",
281
- label: "Outside Color",
282
- default: s.outsideColor,
283
- },
284
339
  {
285
340
  id: "dieline.features",
286
341
  type: "json",
@@ -326,6 +381,13 @@ class DielineTool {
326
381
  try {
327
382
  const detectOptions = options || {};
328
383
  const debug = detectOptions.debug === true;
384
+ const tracerOptions = {
385
+ expand: detectOptions.expand ?? 0,
386
+ smoothing: detectOptions.smoothing ?? true,
387
+ simplifyTolerance: detectOptions.simplifyTolerance ?? 2,
388
+ threshold: detectOptions.threshold,
389
+ debug,
390
+ };
329
391
  // Helper to get image dimensions
330
392
  const loadImage = (url) => {
331
393
  return new Promise((resolve, reject) => {
@@ -338,7 +400,7 @@ class DielineTool {
338
400
  };
339
401
  const [img, traced] = await Promise.all([
340
402
  loadImage(imageUrl),
341
- tracer_1.ImageTracer.traceWithBounds(imageUrl, detectOptions),
403
+ tracer_1.ImageTracer.traceWithBounds(imageUrl, tracerOptions),
342
404
  ]);
343
405
  const { pathData, baseBounds, bounds } = traced;
344
406
  if (debug) {
@@ -349,21 +411,8 @@ class DielineTool {
349
411
  expandedBounds: bounds,
350
412
  currentDielineWidth: s.width,
351
413
  currentDielineHeight: s.height,
352
- options: {
353
- expand: detectOptions.expand ?? 0,
354
- morphologyRadius: detectOptions.morphologyRadius,
355
- connectRadiusMax: detectOptions.connectRadiusMax,
356
- smoothing: detectOptions.smoothing,
357
- simplifyTolerance: detectOptions.simplifyTolerance,
358
- threshold: detectOptions.threshold,
359
- maskMode: detectOptions.maskMode,
360
- whiteThreshold: detectOptions.whiteThreshold,
361
- alphaOpaqueCutoff: detectOptions.alphaOpaqueCutoff,
362
- noChannels: detectOptions.noChannels,
363
- componentMode: detectOptions.componentMode,
364
- minComponentArea: detectOptions.minComponentArea,
365
- forceConnected: detectOptions.forceConnected,
366
- },
414
+ options: tracerOptions,
415
+ strategy: "single-connected-silhouette",
367
416
  });
368
417
  }
369
418
  return {
@@ -383,38 +432,6 @@ class DielineTool {
383
432
  ],
384
433
  };
385
434
  }
386
- getLayer() {
387
- return this.canvasService?.getLayer("dieline-overlay");
388
- }
389
- createLayer() {
390
- if (!this.canvasService)
391
- return;
392
- const width = this.canvasService.canvas.width || 800;
393
- const height = this.canvasService.canvas.height || 600;
394
- const layer = this.canvasService.createLayer("dieline-overlay", {
395
- width,
396
- height,
397
- selectable: false,
398
- evented: false,
399
- });
400
- this.canvasService.canvas.bringObjectToFront(layer);
401
- // Ensure above user layer
402
- const userLayer = this.canvasService.getLayer("user");
403
- if (userLayer) {
404
- const userIndex = this.canvasService.canvas
405
- .getObjects()
406
- .indexOf(userLayer);
407
- this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
408
- }
409
- }
410
- destroyLayer() {
411
- if (!this.canvasService)
412
- return;
413
- const layer = this.getLayer();
414
- if (layer) {
415
- this.canvasService.canvas.remove(layer);
416
- }
417
- }
418
435
  createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
419
436
  if (typeof document === "undefined") {
420
437
  return undefined;
@@ -441,9 +458,15 @@ class DielineTool {
441
458
  getConfigService() {
442
459
  return this.context?.services.get("ConfigurationService");
443
460
  }
461
+ hasImageItems() {
462
+ const configService = this.getConfigService();
463
+ if (!configService)
464
+ return false;
465
+ const items = configService.get("image.items", []);
466
+ return Array.isArray(items) && items.length > 0;
467
+ }
444
468
  syncSizeState(configService) {
445
469
  const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
446
- this.state.displayUnit = sizeState.unit;
447
470
  this.state.width = sizeState.actualWidthMm;
448
471
  this.state.height = sizeState.actualHeightMm;
449
472
  this.state.padding = sizeState.viewPadding;
@@ -454,31 +477,11 @@ class DielineTool {
454
477
  ? -sizeState.cutMarginMm
455
478
  : 0;
456
479
  }
457
- bringFeatureMarkersToFront() {
458
- if (!this.canvasService)
459
- return;
460
- const canvas = this.canvasService.canvas;
461
- canvas
462
- .getObjects()
463
- .filter((obj) => obj?.data?.type === "feature-marker")
464
- .forEach((obj) => canvas.bringObjectToFront(obj));
465
- }
466
- updateDieline(_emitEvent = true) {
467
- if (!this.canvasService)
468
- return;
469
- const layer = this.getLayer();
470
- if (!layer)
471
- return;
472
- const configService = this.getConfigService();
473
- if (!configService)
474
- return;
475
- this.syncSizeState(configService);
476
- const sceneLayout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
477
- if (!sceneLayout)
478
- return;
479
- const { shape, radius, mainLine, offsetLine, insideColor, outsideColor, showBleedLines, features, } = this.state;
480
- const canvasW = sceneLayout.canvasWidth || this.canvasService.canvas.width || 800;
481
- const canvasH = sceneLayout.canvasHeight || this.canvasService.canvas.height || 600;
480
+ buildDielineSpecs(sceneLayout) {
481
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor, showBleedLines, features, } = this.state;
482
+ const hasImages = this.hasImageItems();
483
+ const canvasW = sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
484
+ const canvasH = sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
482
485
  const scale = sceneLayout.scale;
483
486
  const cx = sceneLayout.trimRect.centerX;
484
487
  const cy = sceneLayout.trimRect.centerY;
@@ -489,7 +492,6 @@ class DielineTool {
489
492
  const cutH = sceneLayout.cutRect.height;
490
493
  const visualOffset = (cutW - visualWidth) / 2;
491
494
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
492
- layer.remove(...layer.getObjects());
493
495
  const absoluteFeatures = (features || []).map((f) => ({
494
496
  ...f,
495
497
  x: f.x,
@@ -499,32 +501,11 @@ class DielineTool {
499
501
  radius: (f.radius || 0) * scale,
500
502
  }));
501
503
  const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
502
- const maskPathData = (0, geometry_1.generateMaskPath)({
503
- canvasWidth: canvasW,
504
- canvasHeight: canvasH,
505
- shape,
506
- width: cutW,
507
- height: cutH,
508
- radius: cutR,
509
- x: cx,
510
- y: cy,
511
- features: cutFeatures,
512
- pathData: this.state.pathData,
513
- });
514
- const mask = new fabric_1.Path(maskPathData, {
515
- fill: outsideColor,
516
- stroke: null,
517
- selectable: false,
518
- evented: false,
519
- originX: "left",
520
- originY: "top",
521
- left: 0,
522
- top: 0,
523
- });
524
- layer.add(mask);
504
+ const specs = [];
525
505
  if (insideColor &&
526
506
  insideColor !== "transparent" &&
527
- insideColor !== "rgba(0,0,0,0)") {
507
+ insideColor !== "rgba(0,0,0,0)" &&
508
+ !hasImages) {
528
509
  const productPathData = (0, geometry_1.generateDielinePath)({
529
510
  shape,
530
511
  width: cutW,
@@ -533,19 +514,28 @@ class DielineTool {
533
514
  x: cx,
534
515
  y: cy,
535
516
  features: cutFeatures,
517
+ shapeStyle,
536
518
  pathData: this.state.pathData,
519
+ customSourceWidthPx: this.state.customSourceWidthPx,
520
+ customSourceHeightPx: this.state.customSourceHeightPx,
537
521
  canvasWidth: canvasW,
538
522
  canvasHeight: canvasH,
539
523
  });
540
- const insideObj = new fabric_1.Path(productPathData, {
541
- fill: insideColor,
542
- stroke: null,
543
- selectable: false,
544
- evented: false,
545
- originX: "left",
546
- originY: "top",
524
+ specs.push({
525
+ id: "dieline.inside",
526
+ type: "path",
527
+ space: "screen",
528
+ data: { id: "dieline.inside", type: "dieline" },
529
+ props: {
530
+ pathData: productPathData,
531
+ fill: insideColor,
532
+ stroke: null,
533
+ selectable: false,
534
+ evented: false,
535
+ originX: "left",
536
+ originY: "top",
537
+ },
547
538
  });
548
- layer.add(insideObj);
549
539
  }
550
540
  if (Math.abs(visualOffset) > 0.0001) {
551
541
  const bleedPathData = (0, geometry_1.generateBleedZonePath)({
@@ -556,7 +546,10 @@ class DielineTool {
556
546
  x: cx,
557
547
  y: cy,
558
548
  features: cutFeatures,
549
+ shapeStyle,
559
550
  pathData: this.state.pathData,
551
+ customSourceWidthPx: this.state.customSourceWidthPx,
552
+ customSourceHeightPx: this.state.customSourceHeightPx,
560
553
  canvasWidth: canvasW,
561
554
  canvasHeight: canvasH,
562
555
  }, {
@@ -567,23 +560,32 @@ class DielineTool {
567
560
  x: cx,
568
561
  y: cy,
569
562
  features: cutFeatures,
563
+ shapeStyle,
570
564
  pathData: this.state.pathData,
565
+ customSourceWidthPx: this.state.customSourceWidthPx,
566
+ customSourceHeightPx: this.state.customSourceHeightPx,
571
567
  canvasWidth: canvasW,
572
568
  canvasHeight: canvasH,
573
569
  }, visualOffset);
574
570
  if (showBleedLines !== false) {
575
571
  const pattern = this.createHatchPattern(mainLine.color);
576
572
  if (pattern) {
577
- const bleedObj = new fabric_1.Path(bleedPathData, {
578
- fill: pattern,
579
- stroke: null,
580
- selectable: false,
581
- evented: false,
582
- objectCaching: false,
583
- originX: "left",
584
- originY: "top",
573
+ specs.push({
574
+ id: "dieline.bleed-zone",
575
+ type: "path",
576
+ space: "screen",
577
+ data: { id: "dieline.bleed-zone", type: "dieline" },
578
+ props: {
579
+ pathData: bleedPathData,
580
+ fill: pattern,
581
+ stroke: null,
582
+ selectable: false,
583
+ evented: false,
584
+ objectCaching: false,
585
+ originX: "left",
586
+ originY: "top",
587
+ },
585
588
  });
586
- layer.add(bleedObj);
587
589
  }
588
590
  }
589
591
  const offsetPathData = (0, geometry_1.generateDielinePath)({
@@ -594,23 +596,32 @@ class DielineTool {
594
596
  x: cx,
595
597
  y: cy,
596
598
  features: cutFeatures,
599
+ shapeStyle,
597
600
  pathData: this.state.pathData,
601
+ customSourceWidthPx: this.state.customSourceWidthPx,
602
+ customSourceHeightPx: this.state.customSourceHeightPx,
598
603
  canvasWidth: canvasW,
599
604
  canvasHeight: canvasH,
600
605
  });
601
- const offsetBorderObj = new fabric_1.Path(offsetPathData, {
602
- fill: null,
603
- stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
604
- strokeWidth: offsetLine.width,
605
- strokeDashArray: offsetLine.style === "dashed"
606
- ? [offsetLine.dashLength, offsetLine.dashLength]
607
- : undefined,
608
- selectable: false,
609
- evented: false,
610
- originX: "left",
611
- originY: "top",
606
+ specs.push({
607
+ id: "dieline.offset-border",
608
+ type: "path",
609
+ space: "screen",
610
+ data: { id: "dieline.offset-border", type: "dieline" },
611
+ props: {
612
+ pathData: offsetPathData,
613
+ fill: null,
614
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
615
+ strokeWidth: offsetLine.width,
616
+ strokeDashArray: offsetLine.style === "dashed"
617
+ ? [offsetLine.dashLength, offsetLine.dashLength]
618
+ : undefined,
619
+ selectable: false,
620
+ evented: false,
621
+ originX: "left",
622
+ originY: "top",
623
+ },
612
624
  });
613
- layer.add(offsetBorderObj);
614
625
  }
615
626
  const borderPathData = (0, geometry_1.generateDielinePath)({
616
627
  shape,
@@ -620,43 +631,130 @@ class DielineTool {
620
631
  x: cx,
621
632
  y: cy,
622
633
  features: absoluteFeatures,
634
+ shapeStyle,
623
635
  pathData: this.state.pathData,
636
+ customSourceWidthPx: this.state.customSourceWidthPx,
637
+ customSourceHeightPx: this.state.customSourceHeightPx,
624
638
  canvasWidth: canvasW,
625
639
  canvasHeight: canvasH,
626
640
  });
627
- const borderObj = new fabric_1.Path(borderPathData, {
628
- fill: "transparent",
629
- stroke: mainLine.style === "hidden" ? null : mainLine.color,
630
- strokeWidth: mainLine.width,
631
- strokeDashArray: mainLine.style === "dashed"
632
- ? [mainLine.dashLength, mainLine.dashLength]
633
- : undefined,
634
- selectable: false,
635
- evented: false,
636
- originX: "left",
637
- originY: "top",
641
+ specs.push({
642
+ id: "dieline.border",
643
+ type: "path",
644
+ space: "screen",
645
+ data: { id: "dieline.border", type: "dieline" },
646
+ props: {
647
+ pathData: borderPathData,
648
+ fill: "transparent",
649
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
650
+ strokeWidth: mainLine.width,
651
+ strokeDashArray: mainLine.style === "dashed"
652
+ ? [mainLine.dashLength, mainLine.dashLength]
653
+ : undefined,
654
+ selectable: false,
655
+ evented: false,
656
+ originX: "left",
657
+ originY: "top",
658
+ },
638
659
  });
639
- layer.add(borderObj);
640
- const userLayer = this.canvasService.getLayer("user");
641
- if (layer && userLayer) {
642
- const layerIndex = this.canvasService.canvas.getObjects().indexOf(layer);
643
- const userIndex = this.canvasService.canvas
644
- .getObjects()
645
- .indexOf(userLayer);
646
- if (layerIndex < userIndex) {
647
- this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
648
- }
649
- }
650
- else {
651
- this.canvasService.canvas.bringObjectToFront(layer);
652
- }
653
- // Feature tool markers can extend outside trim. Keep them above dieline mask.
654
- this.bringFeatureMarkersToFront();
655
- const rulerLayer = this.canvasService.getLayer("ruler-overlay");
656
- if (rulerLayer) {
657
- this.canvasService.canvas.bringObjectToFront(rulerLayer);
660
+ return specs;
661
+ }
662
+ buildImageClipEffects(sceneLayout) {
663
+ const { shape, shapeStyle, radius, features } = this.state;
664
+ const canvasW = sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
665
+ const canvasH = sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
666
+ const scale = sceneLayout.scale;
667
+ const cx = sceneLayout.trimRect.centerX;
668
+ const cy = sceneLayout.trimRect.centerY;
669
+ const visualWidth = sceneLayout.trimRect.width;
670
+ const visualRadius = radius * scale;
671
+ const cutW = sceneLayout.cutRect.width;
672
+ const cutH = sceneLayout.cutRect.height;
673
+ const visualOffset = (cutW - visualWidth) / 2;
674
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
675
+ const absoluteFeatures = (features || []).map((f) => ({
676
+ ...f,
677
+ x: f.x,
678
+ y: f.y,
679
+ width: (f.width || 0) * scale,
680
+ height: (f.height || 0) * scale,
681
+ radius: (f.radius || 0) * scale,
682
+ }));
683
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
684
+ const clipPathData = (0, geometry_1.generateDielinePath)({
685
+ shape,
686
+ width: cutW,
687
+ height: cutH,
688
+ radius: cutR,
689
+ x: cx,
690
+ y: cy,
691
+ features: cutFeatures,
692
+ shapeStyle,
693
+ pathData: this.state.pathData,
694
+ customSourceWidthPx: this.state.customSourceWidthPx,
695
+ customSourceHeightPx: this.state.customSourceHeightPx,
696
+ canvasWidth: canvasW,
697
+ canvasHeight: canvasH,
698
+ });
699
+ if (!clipPathData)
700
+ return [];
701
+ return [
702
+ {
703
+ type: "clipPath",
704
+ id: "dieline.clip.image",
705
+ targetPassIds: [IMAGE_OBJECT_LAYER_ID],
706
+ source: {
707
+ id: "dieline.effect.clip-path",
708
+ type: "path",
709
+ space: "screen",
710
+ data: {
711
+ id: "dieline.effect.clip-path",
712
+ type: "dieline-effect",
713
+ effect: "clipPath",
714
+ },
715
+ props: {
716
+ pathData: clipPathData,
717
+ fill: "#000000",
718
+ stroke: null,
719
+ originX: "left",
720
+ originY: "top",
721
+ selectable: false,
722
+ evented: false,
723
+ excludeFromExport: true,
724
+ },
725
+ },
726
+ },
727
+ ];
728
+ }
729
+ updateDieline(_emitEvent = true) {
730
+ void this.updateDielineAsync();
731
+ }
732
+ async updateDielineAsync() {
733
+ if (!this.canvasService)
734
+ return;
735
+ const configService = this.getConfigService();
736
+ if (!configService)
737
+ return;
738
+ const seq = ++this.renderSeq;
739
+ this.syncSizeState(configService);
740
+ const sceneLayout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
741
+ if (!sceneLayout) {
742
+ if (seq !== this.renderSeq)
743
+ return;
744
+ this.specs = [];
745
+ this.effects = [];
746
+ await this.canvasService.flushRenderFromProducers();
747
+ return;
658
748
  }
659
- layer.dirty = true;
749
+ const nextSpecs = this.buildDielineSpecs(sceneLayout);
750
+ const nextEffects = this.buildImageClipEffects(sceneLayout);
751
+ if (seq !== this.renderSeq)
752
+ return;
753
+ this.specs = nextSpecs;
754
+ this.effects = nextEffects;
755
+ await this.canvasService.flushRenderFromProducers();
756
+ if (seq !== this.renderSeq)
757
+ return;
660
758
  this.canvasService.requestRenderAll();
661
759
  }
662
760
  getGeometry() {
@@ -673,6 +771,8 @@ class DielineTool {
673
771
  ...sceneGeometry,
674
772
  strokeWidth: this.state.mainLine.width,
675
773
  pathData: this.state.pathData,
774
+ customSourceWidthPx: this.state.customSourceWidthPx,
775
+ customSourceHeightPx: this.state.customSourceHeightPx,
676
776
  };
677
777
  }
678
778
  async exportCutImage(options) {
@@ -692,7 +792,7 @@ class DielineTool {
692
792
  console.warn("[DielineTool] exportCutImage returned null: scene-layout-null");
693
793
  return null;
694
794
  }
695
- const { shape, radius, features, pathData } = this.state;
795
+ const { shape, shapeStyle, radius, features, pathData } = this.state;
696
796
  const canvasW = sceneLayout.canvasWidth || this.canvasService.canvas.width || 800;
697
797
  const canvasH = sceneLayout.canvasHeight || this.canvasService.canvas.height || 600;
698
798
  const scale = sceneLayout.scale;
@@ -720,7 +820,10 @@ class DielineTool {
720
820
  x: cx,
721
821
  y: cy,
722
822
  features: cutFeatures,
823
+ shapeStyle,
723
824
  pathData,
825
+ customSourceWidthPx: this.state.customSourceWidthPx,
826
+ customSourceHeightPx: this.state.customSourceHeightPx,
724
827
  canvasWidth: canvasW,
725
828
  canvasHeight: canvasH,
726
829
  });