@pooder/kit 6.0.1 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.test-dist/src/extensions/background/BackgroundTool.js +524 -0
  2. package/.test-dist/src/extensions/background/index.js +17 -0
  3. package/.test-dist/src/extensions/dieline/DielineTool.js +748 -0
  4. package/.test-dist/src/extensions/dieline/commands.js +127 -0
  5. package/.test-dist/src/extensions/dieline/config.js +107 -0
  6. package/.test-dist/src/extensions/dieline/index.js +21 -0
  7. package/.test-dist/src/extensions/dieline/model.js +2 -0
  8. package/.test-dist/src/extensions/dieline/renderer.js +2 -0
  9. package/.test-dist/src/extensions/feature/FeatureTool.js +914 -0
  10. package/.test-dist/src/extensions/feature/index.js +17 -0
  11. package/.test-dist/src/extensions/film/FilmTool.js +207 -0
  12. package/.test-dist/src/extensions/film/index.js +17 -0
  13. package/.test-dist/src/extensions/image/ImageTool.js +1499 -0
  14. package/.test-dist/src/extensions/image/commands.js +162 -0
  15. package/.test-dist/src/extensions/image/config.js +129 -0
  16. package/.test-dist/src/extensions/image/index.js +21 -0
  17. package/.test-dist/src/extensions/image/model.js +2 -0
  18. package/.test-dist/src/extensions/image/renderer.js +5 -0
  19. package/.test-dist/src/extensions/mirror/MirrorTool.js +104 -0
  20. package/.test-dist/src/extensions/mirror/index.js +17 -0
  21. package/.test-dist/src/extensions/ruler/RulerTool.js +442 -0
  22. package/.test-dist/src/extensions/ruler/index.js +17 -0
  23. package/.test-dist/src/extensions/sceneLayout.js +2 -93
  24. package/.test-dist/src/extensions/sceneLayoutModel.js +15 -200
  25. package/.test-dist/src/extensions/size/SizeTool.js +332 -0
  26. package/.test-dist/src/extensions/size/index.js +17 -0
  27. package/.test-dist/src/extensions/white-ink/WhiteInkTool.js +1003 -0
  28. package/.test-dist/src/extensions/white-ink/commands.js +148 -0
  29. package/.test-dist/src/extensions/white-ink/config.js +31 -0
  30. package/.test-dist/src/extensions/white-ink/index.js +21 -0
  31. package/.test-dist/src/extensions/white-ink/model.js +2 -0
  32. package/.test-dist/src/extensions/white-ink/renderer.js +5 -0
  33. package/.test-dist/src/services/SceneLayoutService.js +96 -0
  34. package/.test-dist/src/services/index.js +1 -0
  35. package/.test-dist/src/shared/constants/layers.js +25 -0
  36. package/.test-dist/src/shared/imaging/sourceSizeCache.js +82 -0
  37. package/.test-dist/src/shared/index.js +22 -0
  38. package/.test-dist/src/shared/runtime/sessionState.js +74 -0
  39. package/.test-dist/src/shared/runtime/subscriptions.js +30 -0
  40. package/.test-dist/src/shared/scene/frame.js +34 -0
  41. package/.test-dist/src/shared/scene/sceneLayoutModel.js +202 -0
  42. package/.test-dist/tests/run.js +116 -0
  43. package/CHANGELOG.md +14 -0
  44. package/dist/index.d.mts +390 -367
  45. package/dist/index.d.ts +390 -367
  46. package/dist/index.js +5138 -4927
  47. package/dist/index.mjs +1149 -1977
  48. package/dist/tracer-PO7CRBYY.mjs +1016 -0
  49. package/package.json +2 -2
  50. package/src/extensions/{background.ts → background/BackgroundTool.ts} +33 -50
  51. package/src/extensions/background/index.ts +1 -0
  52. package/src/extensions/{dieline.ts → dieline/DielineTool.ts} +14 -218
  53. package/src/extensions/dieline/commands.ts +109 -0
  54. package/src/extensions/dieline/config.ts +106 -0
  55. package/src/extensions/dieline/index.ts +5 -0
  56. package/src/extensions/dieline/model.ts +1 -0
  57. package/src/extensions/dieline/renderer.ts +1 -0
  58. package/src/extensions/{feature.ts → feature/FeatureTool.ts} +27 -21
  59. package/src/extensions/feature/index.ts +1 -0
  60. package/src/extensions/{film.ts → film/FilmTool.ts} +36 -48
  61. package/src/extensions/film/index.ts +1 -0
  62. package/src/extensions/{image.ts → image/ImageTool.ts} +123 -402
  63. package/src/extensions/image/commands.ts +176 -0
  64. package/src/extensions/image/config.ts +128 -0
  65. package/src/extensions/image/index.ts +5 -0
  66. package/src/extensions/image/model.ts +1 -0
  67. package/src/extensions/image/renderer.ts +1 -0
  68. package/src/extensions/{mirror.ts → mirror/MirrorTool.ts} +1 -1
  69. package/src/extensions/mirror/index.ts +1 -0
  70. package/src/extensions/{ruler.ts → ruler/RulerTool.ts} +4 -5
  71. package/src/extensions/ruler/index.ts +1 -0
  72. package/src/extensions/sceneLayout.ts +1 -140
  73. package/src/extensions/sceneLayoutModel.ts +1 -364
  74. package/src/extensions/{size.ts → size/SizeTool.ts} +7 -6
  75. package/src/extensions/size/index.ts +1 -0
  76. package/src/extensions/{white-ink.ts → white-ink/WhiteInkTool.ts} +130 -317
  77. package/src/extensions/white-ink/commands.ts +157 -0
  78. package/src/extensions/white-ink/config.ts +30 -0
  79. package/src/extensions/white-ink/index.ts +5 -0
  80. package/src/extensions/white-ink/model.ts +1 -0
  81. package/src/extensions/white-ink/renderer.ts +1 -0
  82. package/src/services/SceneLayoutService.ts +139 -0
  83. package/src/services/index.ts +1 -0
  84. package/src/shared/constants/layers.ts +23 -0
  85. package/src/shared/imaging/sourceSizeCache.ts +103 -0
  86. package/src/shared/index.ts +6 -0
  87. package/src/shared/runtime/sessionState.ts +105 -0
  88. package/src/shared/runtime/subscriptions.ts +45 -0
  89. package/src/shared/scene/frame.ts +46 -0
  90. package/src/shared/scene/sceneLayoutModel.ts +367 -0
  91. package/tests/run.ts +146 -0
@@ -0,0 +1,748 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DielineTool = void 0;
4
+ const core_1 = require("@pooder/core");
5
+ const fabric_1 = require("fabric");
6
+ const units_1 = require("../../units");
7
+ const dielineShape_1 = require("../dielineShape");
8
+ const geometry_1 = require("../geometry");
9
+ const sceneLayoutModel_1 = require("../../shared/scene/sceneLayoutModel");
10
+ const layers_1 = require("../../shared/constants/layers");
11
+ const commands_1 = require("./commands");
12
+ const config_1 = require("./config");
13
+ class DielineTool {
14
+ constructor(options) {
15
+ this.id = "pooder.kit.dieline";
16
+ this.metadata = {
17
+ name: "DielineTool",
18
+ };
19
+ this.state = {
20
+ shape: dielineShape_1.DEFAULT_DIELINE_SHAPE,
21
+ shapeStyle: { ...dielineShape_1.DEFAULT_DIELINE_SHAPE_STYLE },
22
+ width: 500,
23
+ height: 500,
24
+ radius: 0,
25
+ offset: 0,
26
+ padding: 140,
27
+ mainLine: {
28
+ width: 2.7,
29
+ color: "#FF0000",
30
+ dashLength: 5,
31
+ style: "solid",
32
+ },
33
+ offsetLine: {
34
+ width: 2.7,
35
+ color: "#FF0000",
36
+ dashLength: 5,
37
+ style: "solid",
38
+ },
39
+ insideColor: "rgba(0,0,0,0)",
40
+ showBleedLines: true,
41
+ features: [],
42
+ };
43
+ this.specs = [];
44
+ this.effects = [];
45
+ this.renderSeq = 0;
46
+ this.onCanvasResized = () => {
47
+ this.updateDieline();
48
+ };
49
+ if (options) {
50
+ // Deep merge for styles to avoid overwriting defaults with partial objects
51
+ if (options.mainLine) {
52
+ Object.assign(this.state.mainLine, options.mainLine);
53
+ delete options.mainLine;
54
+ }
55
+ if (options.offsetLine) {
56
+ Object.assign(this.state.offsetLine, options.offsetLine);
57
+ delete options.offsetLine;
58
+ }
59
+ if (options.shapeStyle) {
60
+ this.state.shapeStyle = (0, dielineShape_1.normalizeShapeStyle)(options.shapeStyle, this.state.shapeStyle);
61
+ delete options.shapeStyle;
62
+ }
63
+ Object.assign(this.state, options);
64
+ this.state.shape = (0, dielineShape_1.normalizeDielineShape)(options.shape, this.state.shape);
65
+ }
66
+ }
67
+ activate(context) {
68
+ this.context = context;
69
+ this.canvasService = context.services.get("CanvasService");
70
+ if (!this.canvasService) {
71
+ console.warn("CanvasService not found for DielineTool");
72
+ return;
73
+ }
74
+ this.renderProducerDisposable?.dispose();
75
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
76
+ passes: [
77
+ {
78
+ id: layers_1.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 });
94
+ const configService = context.services.get("ConfigurationService");
95
+ if (configService) {
96
+ // Load initial config
97
+ const s = this.state;
98
+ const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
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);
101
+ s.width = sizeState.actualWidthMm;
102
+ s.height = sizeState.actualHeightMm;
103
+ s.radius = (0, units_1.parseLengthToMm)(configService.get("dieline.radius", s.radius), "mm");
104
+ s.padding = sizeState.viewPadding;
105
+ s.offset =
106
+ sizeState.cutMode === "outset"
107
+ ? sizeState.cutMarginMm
108
+ : sizeState.cutMode === "inset"
109
+ ? -sizeState.cutMarginMm
110
+ : 0;
111
+ // Main Line
112
+ s.mainLine.width = configService.get("dieline.strokeWidth", s.mainLine.width);
113
+ s.mainLine.color = configService.get("dieline.strokeColor", s.mainLine.color);
114
+ s.mainLine.dashLength = configService.get("dieline.dashLength", s.mainLine.dashLength);
115
+ s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
116
+ // Offset Line
117
+ s.offsetLine.width = configService.get("dieline.offsetStrokeWidth", s.offsetLine.width);
118
+ s.offsetLine.color = configService.get("dieline.offsetStrokeColor", s.offsetLine.color);
119
+ s.offsetLine.dashLength = configService.get("dieline.offsetDashLength", s.offsetLine.dashLength);
120
+ s.offsetLine.style = configService.get("dieline.offsetStyle", s.offsetLine.style);
121
+ s.insideColor = configService.get("dieline.insideColor", s.insideColor);
122
+ s.showBleedLines = configService.get("dieline.showBleedLines", s.showBleedLines);
123
+ s.features = configService.get("dieline.features", s.features);
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;
135
+ // Listen for changes
136
+ configService.onAnyChange((e) => {
137
+ if (e.key.startsWith("size.")) {
138
+ const nextSize = (0, sceneLayoutModel_1.readSizeState)(configService);
139
+ s.width = nextSize.actualWidthMm;
140
+ s.height = nextSize.actualHeightMm;
141
+ s.padding = nextSize.viewPadding;
142
+ s.offset =
143
+ nextSize.cutMode === "outset"
144
+ ? nextSize.cutMarginMm
145
+ : nextSize.cutMode === "inset"
146
+ ? -nextSize.cutMarginMm
147
+ : 0;
148
+ this.updateDieline();
149
+ return;
150
+ }
151
+ if (e.key.startsWith("dieline.")) {
152
+ switch (e.key) {
153
+ case "dieline.shape":
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);
158
+ break;
159
+ case "dieline.radius":
160
+ s.radius = (0, units_1.parseLengthToMm)(e.value, "mm");
161
+ break;
162
+ case "dieline.strokeWidth":
163
+ s.mainLine.width = e.value;
164
+ break;
165
+ case "dieline.strokeColor":
166
+ s.mainLine.color = e.value;
167
+ break;
168
+ case "dieline.dashLength":
169
+ s.mainLine.dashLength = e.value;
170
+ break;
171
+ case "dieline.style":
172
+ s.mainLine.style = e.value;
173
+ break;
174
+ case "dieline.offsetStrokeWidth":
175
+ s.offsetLine.width = e.value;
176
+ break;
177
+ case "dieline.offsetStrokeColor":
178
+ s.offsetLine.color = e.value;
179
+ break;
180
+ case "dieline.offsetDashLength":
181
+ s.offsetLine.dashLength = e.value;
182
+ break;
183
+ case "dieline.offsetStyle":
184
+ s.offsetLine.style = e.value;
185
+ break;
186
+ case "dieline.insideColor":
187
+ s.insideColor = e.value;
188
+ break;
189
+ case "dieline.showBleedLines":
190
+ s.showBleedLines = e.value;
191
+ break;
192
+ case "dieline.features":
193
+ s.features = e.value;
194
+ break;
195
+ case "dieline.pathData":
196
+ s.pathData = e.value;
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;
210
+ }
211
+ this.updateDieline();
212
+ }
213
+ });
214
+ }
215
+ context.eventBus.on("canvas:resized", this.onCanvasResized);
216
+ this.updateDieline();
217
+ }
218
+ deactivate(context) {
219
+ context.eventBus.off("canvas:resized", this.onCanvasResized);
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
+ }
228
+ this.canvasService = undefined;
229
+ this.context = undefined;
230
+ }
231
+ contribute() {
232
+ return {
233
+ [core_1.ContributionPointIds.TOOLS]: [
234
+ {
235
+ id: this.id,
236
+ name: "Dieline",
237
+ interaction: "session",
238
+ session: {
239
+ autoBegin: false,
240
+ leavePolicy: "block",
241
+ },
242
+ },
243
+ ],
244
+ [core_1.ContributionPointIds.CONFIGURATIONS]: (0, config_1.createDielineConfigurations)(this.state),
245
+ [core_1.ContributionPointIds.COMMANDS]: (0, commands_1.createDielineCommands)(this, this.state),
246
+ };
247
+ }
248
+ createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
249
+ if (typeof document === "undefined") {
250
+ return undefined;
251
+ }
252
+ const size = 20;
253
+ const canvas = document.createElement("canvas");
254
+ canvas.width = size;
255
+ canvas.height = size;
256
+ const ctx = canvas.getContext("2d");
257
+ if (ctx) {
258
+ // Transparent background
259
+ ctx.clearRect(0, 0, size, size);
260
+ // Draw diagonal /
261
+ ctx.strokeStyle = color;
262
+ ctx.lineWidth = 1;
263
+ ctx.beginPath();
264
+ ctx.moveTo(0, size);
265
+ ctx.lineTo(size, 0);
266
+ ctx.stroke();
267
+ }
268
+ // @ts-ignore
269
+ return new fabric_1.Pattern({ source: canvas, repetition: "repeat" });
270
+ }
271
+ getConfigService() {
272
+ return this.context?.services.get("ConfigurationService");
273
+ }
274
+ hasImageItems() {
275
+ const configService = this.getConfigService();
276
+ if (!configService)
277
+ return false;
278
+ const items = configService.get("image.items", []);
279
+ return Array.isArray(items) && items.length > 0;
280
+ }
281
+ syncSizeState(configService) {
282
+ const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
283
+ this.state.width = sizeState.actualWidthMm;
284
+ this.state.height = sizeState.actualHeightMm;
285
+ this.state.padding = sizeState.viewPadding;
286
+ this.state.offset =
287
+ sizeState.cutMode === "outset"
288
+ ? sizeState.cutMarginMm
289
+ : sizeState.cutMode === "inset"
290
+ ? -sizeState.cutMarginMm
291
+ : 0;
292
+ }
293
+ buildDielineSpecs(sceneLayout) {
294
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor, showBleedLines, features, } = this.state;
295
+ const hasImages = this.hasImageItems();
296
+ const canvasW = sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
297
+ const canvasH = sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
298
+ const scale = sceneLayout.scale;
299
+ const cx = sceneLayout.trimRect.centerX;
300
+ const cy = sceneLayout.trimRect.centerY;
301
+ const visualWidth = sceneLayout.trimRect.width;
302
+ const visualHeight = sceneLayout.trimRect.height;
303
+ const visualRadius = radius * scale;
304
+ const cutW = sceneLayout.cutRect.width;
305
+ const cutH = sceneLayout.cutRect.height;
306
+ const visualOffset = (cutW - visualWidth) / 2;
307
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
308
+ const absoluteFeatures = (features || []).map((f) => ({
309
+ ...f,
310
+ x: f.x,
311
+ y: f.y,
312
+ width: (f.width || 0) * scale,
313
+ height: (f.height || 0) * scale,
314
+ radius: (f.radius || 0) * scale,
315
+ }));
316
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
317
+ const specs = [];
318
+ if (insideColor &&
319
+ insideColor !== "transparent" &&
320
+ insideColor !== "rgba(0,0,0,0)" &&
321
+ !hasImages) {
322
+ const productPathData = (0, geometry_1.generateDielinePath)({
323
+ shape,
324
+ width: cutW,
325
+ height: cutH,
326
+ radius: cutR,
327
+ x: cx,
328
+ y: cy,
329
+ features: cutFeatures,
330
+ shapeStyle,
331
+ pathData: this.state.pathData,
332
+ customSourceWidthPx: this.state.customSourceWidthPx,
333
+ customSourceHeightPx: this.state.customSourceHeightPx,
334
+ canvasWidth: canvasW,
335
+ canvasHeight: canvasH,
336
+ });
337
+ specs.push({
338
+ id: "dieline.inside",
339
+ type: "path",
340
+ space: "screen",
341
+ data: { id: "dieline.inside", type: "dieline" },
342
+ props: {
343
+ pathData: productPathData,
344
+ fill: insideColor,
345
+ stroke: null,
346
+ selectable: false,
347
+ evented: false,
348
+ originX: "left",
349
+ originY: "top",
350
+ },
351
+ });
352
+ }
353
+ if (Math.abs(visualOffset) > 0.0001) {
354
+ const bleedPathData = (0, geometry_1.generateBleedZonePath)({
355
+ shape,
356
+ width: visualWidth,
357
+ height: visualHeight,
358
+ radius: visualRadius,
359
+ x: cx,
360
+ y: cy,
361
+ features: cutFeatures,
362
+ shapeStyle,
363
+ pathData: this.state.pathData,
364
+ customSourceWidthPx: this.state.customSourceWidthPx,
365
+ customSourceHeightPx: this.state.customSourceHeightPx,
366
+ canvasWidth: canvasW,
367
+ canvasHeight: canvasH,
368
+ }, {
369
+ shape,
370
+ width: cutW,
371
+ height: cutH,
372
+ radius: cutR,
373
+ x: cx,
374
+ y: cy,
375
+ features: cutFeatures,
376
+ shapeStyle,
377
+ pathData: this.state.pathData,
378
+ customSourceWidthPx: this.state.customSourceWidthPx,
379
+ customSourceHeightPx: this.state.customSourceHeightPx,
380
+ canvasWidth: canvasW,
381
+ canvasHeight: canvasH,
382
+ }, visualOffset);
383
+ if (showBleedLines !== false) {
384
+ const pattern = this.createHatchPattern(mainLine.color);
385
+ if (pattern) {
386
+ specs.push({
387
+ id: "dieline.bleed-zone",
388
+ type: "path",
389
+ space: "screen",
390
+ data: { id: "dieline.bleed-zone", type: "dieline" },
391
+ props: {
392
+ pathData: bleedPathData,
393
+ fill: pattern,
394
+ stroke: null,
395
+ selectable: false,
396
+ evented: false,
397
+ objectCaching: false,
398
+ originX: "left",
399
+ originY: "top",
400
+ },
401
+ });
402
+ }
403
+ }
404
+ const offsetPathData = (0, geometry_1.generateDielinePath)({
405
+ shape,
406
+ width: cutW,
407
+ height: cutH,
408
+ radius: cutR,
409
+ x: cx,
410
+ y: cy,
411
+ features: cutFeatures,
412
+ shapeStyle,
413
+ pathData: this.state.pathData,
414
+ customSourceWidthPx: this.state.customSourceWidthPx,
415
+ customSourceHeightPx: this.state.customSourceHeightPx,
416
+ canvasWidth: canvasW,
417
+ canvasHeight: canvasH,
418
+ });
419
+ specs.push({
420
+ id: "dieline.offset-border",
421
+ type: "path",
422
+ space: "screen",
423
+ data: { id: "dieline.offset-border", type: "dieline" },
424
+ props: {
425
+ pathData: offsetPathData,
426
+ fill: null,
427
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
428
+ strokeWidth: offsetLine.width,
429
+ strokeDashArray: offsetLine.style === "dashed"
430
+ ? [offsetLine.dashLength, offsetLine.dashLength]
431
+ : undefined,
432
+ selectable: false,
433
+ evented: false,
434
+ originX: "left",
435
+ originY: "top",
436
+ },
437
+ });
438
+ }
439
+ const borderPathData = (0, geometry_1.generateDielinePath)({
440
+ shape,
441
+ width: visualWidth,
442
+ height: visualHeight,
443
+ radius: visualRadius,
444
+ x: cx,
445
+ y: cy,
446
+ features: absoluteFeatures,
447
+ shapeStyle,
448
+ pathData: this.state.pathData,
449
+ customSourceWidthPx: this.state.customSourceWidthPx,
450
+ customSourceHeightPx: this.state.customSourceHeightPx,
451
+ canvasWidth: canvasW,
452
+ canvasHeight: canvasH,
453
+ });
454
+ specs.push({
455
+ id: "dieline.border",
456
+ type: "path",
457
+ space: "screen",
458
+ data: { id: "dieline.border", type: "dieline" },
459
+ props: {
460
+ pathData: borderPathData,
461
+ fill: "transparent",
462
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
463
+ strokeWidth: mainLine.width,
464
+ strokeDashArray: mainLine.style === "dashed"
465
+ ? [mainLine.dashLength, mainLine.dashLength]
466
+ : undefined,
467
+ selectable: false,
468
+ evented: false,
469
+ originX: "left",
470
+ originY: "top",
471
+ },
472
+ });
473
+ return specs;
474
+ }
475
+ buildImageClipEffects(sceneLayout) {
476
+ const { shape, shapeStyle, radius, features } = this.state;
477
+ const canvasW = sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
478
+ const canvasH = sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
479
+ const scale = sceneLayout.scale;
480
+ const cx = sceneLayout.trimRect.centerX;
481
+ const cy = sceneLayout.trimRect.centerY;
482
+ const visualWidth = sceneLayout.trimRect.width;
483
+ const visualRadius = radius * scale;
484
+ const cutW = sceneLayout.cutRect.width;
485
+ const cutH = sceneLayout.cutRect.height;
486
+ const visualOffset = (cutW - visualWidth) / 2;
487
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
488
+ const absoluteFeatures = (features || []).map((f) => ({
489
+ ...f,
490
+ x: f.x,
491
+ y: f.y,
492
+ width: (f.width || 0) * scale,
493
+ height: (f.height || 0) * scale,
494
+ radius: (f.radius || 0) * scale,
495
+ }));
496
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
497
+ const clipPathData = (0, geometry_1.generateDielinePath)({
498
+ shape,
499
+ width: cutW,
500
+ height: cutH,
501
+ radius: cutR,
502
+ x: cx,
503
+ y: cy,
504
+ features: cutFeatures,
505
+ shapeStyle,
506
+ pathData: this.state.pathData,
507
+ customSourceWidthPx: this.state.customSourceWidthPx,
508
+ customSourceHeightPx: this.state.customSourceHeightPx,
509
+ canvasWidth: canvasW,
510
+ canvasHeight: canvasH,
511
+ });
512
+ if (!clipPathData)
513
+ return [];
514
+ return [
515
+ {
516
+ type: "clipPath",
517
+ id: "dieline.clip.image",
518
+ visibility: {
519
+ op: "not",
520
+ expr: { op: "anySessionActive" },
521
+ },
522
+ targetPassIds: [layers_1.IMAGE_OBJECT_LAYER_ID],
523
+ source: {
524
+ id: "dieline.effect.clip-path",
525
+ type: "path",
526
+ space: "screen",
527
+ data: {
528
+ id: "dieline.effect.clip-path",
529
+ type: "dieline-effect",
530
+ effect: "clipPath",
531
+ },
532
+ props: {
533
+ pathData: clipPathData,
534
+ fill: "#000000",
535
+ stroke: null,
536
+ originX: "left",
537
+ originY: "top",
538
+ selectable: false,
539
+ evented: false,
540
+ excludeFromExport: true,
541
+ },
542
+ },
543
+ },
544
+ ];
545
+ }
546
+ updateDieline(_emitEvent = true) {
547
+ void this.updateDielineAsync();
548
+ }
549
+ async updateDielineAsync() {
550
+ if (!this.canvasService)
551
+ return;
552
+ const configService = this.getConfigService();
553
+ if (!configService)
554
+ return;
555
+ const seq = ++this.renderSeq;
556
+ this.syncSizeState(configService);
557
+ const sceneLayout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
558
+ if (!sceneLayout) {
559
+ if (seq !== this.renderSeq)
560
+ return;
561
+ this.specs = [];
562
+ this.effects = [];
563
+ await this.canvasService.flushRenderFromProducers();
564
+ return;
565
+ }
566
+ const nextSpecs = this.buildDielineSpecs(sceneLayout);
567
+ const nextEffects = this.buildImageClipEffects(sceneLayout);
568
+ if (seq !== this.renderSeq)
569
+ return;
570
+ this.specs = nextSpecs;
571
+ this.effects = nextEffects;
572
+ await this.canvasService.flushRenderFromProducers();
573
+ if (seq !== this.renderSeq)
574
+ return;
575
+ this.canvasService.requestRenderAll();
576
+ }
577
+ getGeometry() {
578
+ if (!this.canvasService)
579
+ return null;
580
+ const configService = this.getConfigService();
581
+ if (!configService)
582
+ return null;
583
+ const sceneLayout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
584
+ if (!sceneLayout)
585
+ return null;
586
+ const sceneGeometry = (0, sceneLayoutModel_1.buildSceneGeometry)(configService, sceneLayout);
587
+ return {
588
+ ...sceneGeometry,
589
+ strokeWidth: this.state.mainLine.width,
590
+ pathData: this.state.pathData,
591
+ customSourceWidthPx: this.state.customSourceWidthPx,
592
+ customSourceHeightPx: this.state.customSourceHeightPx,
593
+ };
594
+ }
595
+ async exportCutImage(options) {
596
+ const debug = options?.debug === true;
597
+ if (!this.canvasService) {
598
+ console.warn("[DielineTool] exportCutImage returned null: canvas-not-ready");
599
+ return null;
600
+ }
601
+ const configService = this.getConfigService();
602
+ if (!configService) {
603
+ console.warn("[DielineTool] exportCutImage returned null: config-service-not-ready");
604
+ return null;
605
+ }
606
+ this.syncSizeState(configService);
607
+ const sceneLayout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
608
+ if (!sceneLayout) {
609
+ console.warn("[DielineTool] exportCutImage returned null: scene-layout-null");
610
+ return null;
611
+ }
612
+ const { shape, shapeStyle, radius, features, pathData } = this.state;
613
+ const canvasW = sceneLayout.canvasWidth || this.canvasService.canvas.width || 800;
614
+ const canvasH = sceneLayout.canvasHeight || this.canvasService.canvas.height || 600;
615
+ const scale = sceneLayout.scale;
616
+ const cx = sceneLayout.trimRect.centerX;
617
+ const cy = sceneLayout.trimRect.centerY;
618
+ const cutW = sceneLayout.cutRect.width;
619
+ const cutH = sceneLayout.cutRect.height;
620
+ const visualRadius = radius * scale;
621
+ const visualOffset = (cutW - sceneLayout.trimRect.width) / 2;
622
+ const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
623
+ const absoluteFeatures = (features || []).map((f) => ({
624
+ ...f,
625
+ x: f.x,
626
+ y: f.y,
627
+ width: (f.width || 0) * scale,
628
+ height: (f.height || 0) * scale,
629
+ radius: (f.radius || 0) * scale,
630
+ }));
631
+ const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
632
+ const generatedPathData = (0, geometry_1.generateDielinePath)({
633
+ shape,
634
+ width: cutW,
635
+ height: cutH,
636
+ radius: cutR,
637
+ x: cx,
638
+ y: cy,
639
+ features: cutFeatures,
640
+ shapeStyle,
641
+ pathData,
642
+ customSourceWidthPx: this.state.customSourceWidthPx,
643
+ customSourceHeightPx: this.state.customSourceHeightPx,
644
+ canvasWidth: canvasW,
645
+ canvasHeight: canvasH,
646
+ });
647
+ const clipPath = new fabric_1.Path(generatedPathData, {
648
+ originX: "center",
649
+ originY: "center",
650
+ left: cx,
651
+ top: cy,
652
+ absolutePositioned: true,
653
+ });
654
+ const pathOffsetX = Number(clipPath?.pathOffset?.x);
655
+ const pathOffsetY = Number(clipPath?.pathOffset?.y);
656
+ const centerX = Number.isFinite(pathOffsetX) ? pathOffsetX : cx;
657
+ const centerY = Number.isFinite(pathOffsetY) ? pathOffsetY : cy;
658
+ clipPath.set({
659
+ originX: "center",
660
+ originY: "center",
661
+ left: centerX,
662
+ top: centerY,
663
+ absolutePositioned: true,
664
+ });
665
+ clipPath.setCoords();
666
+ const pathBounds = clipPath.getBoundingRect();
667
+ if (!Number.isFinite(pathBounds.left) ||
668
+ !Number.isFinite(pathBounds.top) ||
669
+ !Number.isFinite(pathBounds.width) ||
670
+ !Number.isFinite(pathBounds.height) ||
671
+ pathBounds.width <= 0 ||
672
+ pathBounds.height <= 0) {
673
+ console.warn("[DielineTool] exportCutImage returned null: invalid-cut-bounds", {
674
+ bounds: pathBounds,
675
+ });
676
+ return null;
677
+ }
678
+ const exportBounds = pathBounds;
679
+ const sourceImages = this.canvasService.canvas
680
+ .getObjects()
681
+ .filter((obj) => {
682
+ return obj?.data?.layerId === layers_1.IMAGE_OBJECT_LAYER_ID;
683
+ });
684
+ if (!sourceImages.length) {
685
+ console.warn("[DielineTool] exportCutImage returned null: no-image-objects-on-canvas");
686
+ return null;
687
+ }
688
+ const sourceCanvasWidth = Number(this.canvasService.canvas.width || sceneLayout.canvasWidth || canvasW);
689
+ const sourceCanvasHeight = Number(this.canvasService.canvas.height || sceneLayout.canvasHeight || canvasH);
690
+ const el = document.createElement("canvas");
691
+ const exportCanvas = new fabric_1.Canvas(el, {
692
+ renderOnAddRemove: false,
693
+ selection: false,
694
+ enableRetinaScaling: false,
695
+ preserveObjectStacking: true,
696
+ });
697
+ exportCanvas.setDimensions({
698
+ width: Math.max(1, sourceCanvasWidth),
699
+ height: Math.max(1, sourceCanvasHeight),
700
+ });
701
+ try {
702
+ for (const source of sourceImages) {
703
+ const clone = await source.clone();
704
+ clone.set({
705
+ selectable: false,
706
+ evented: false,
707
+ });
708
+ clone.setCoords();
709
+ exportCanvas.add(clone);
710
+ }
711
+ exportCanvas.clipPath = clipPath;
712
+ exportCanvas.renderAll();
713
+ const dataUrl = exportCanvas.toDataURL({
714
+ format: "png",
715
+ multiplier: 2,
716
+ left: exportBounds.left,
717
+ top: exportBounds.top,
718
+ width: exportBounds.width,
719
+ height: exportBounds.height,
720
+ });
721
+ if (debug) {
722
+ console.info("[DielineTool] exportCutImage success", {
723
+ sourceCount: sourceImages.length,
724
+ bounds: exportBounds,
725
+ rawPathBounds: pathBounds,
726
+ pathOffset: {
727
+ x: Number.isFinite(pathOffsetX) ? pathOffsetX : null,
728
+ y: Number.isFinite(pathOffsetY) ? pathOffsetY : null,
729
+ },
730
+ clipPathCenter: {
731
+ x: centerX,
732
+ y: centerY,
733
+ },
734
+ cutRect: sceneLayout.cutRect,
735
+ canvasSize: {
736
+ width: Math.max(1, sourceCanvasWidth),
737
+ height: Math.max(1, sourceCanvasHeight),
738
+ },
739
+ });
740
+ }
741
+ return dataUrl;
742
+ }
743
+ finally {
744
+ exportCanvas.dispose();
745
+ }
746
+ }
747
+ }
748
+ exports.DielineTool = DielineTool;