@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.
@@ -6,19 +6,8 @@ import {
6
6
  } from "@pooder/core";
7
7
  import { Canvas as FabricCanvas, Path, Pattern } from "fabric";
8
8
  import { CanvasService, RenderEffectSpec, RenderObjectSpec } from "../../services";
9
- import { parseLengthToMm } from "../../units";
10
- import {
11
- DEFAULT_DIELINE_SHAPE,
12
- DEFAULT_DIELINE_SHAPE_STYLE,
13
- normalizeShapeStyle,
14
- normalizeDielineShape,
15
- } from "../dielineShape";
16
- import type { DielineShape, DielineShapeStyle } from "../dielineShape";
17
- import {
18
- generateDielinePath,
19
- generateBleedZonePath,
20
- DielineFeature,
21
- } from "../geometry";
9
+ import { generateDielinePath } from "../geometry";
10
+ import { normalizeShapeStyle, normalizeDielineShape } from "../dielineShape";
22
11
  import {
23
12
  buildSceneGeometry,
24
13
  computeSceneLayout,
@@ -30,49 +19,13 @@ import {
30
19
  } from "../../shared/constants/layers";
31
20
  import { createDielineCommands } from "./commands";
32
21
  import { createDielineConfigurations } from "./config";
33
-
34
- export interface DielineGeometry {
35
- shape: DielineShape;
36
- shapeStyle: DielineShapeStyle;
37
- unit: "px";
38
- x: number;
39
- y: number;
40
- width: number;
41
- height: number;
42
- radius: number;
43
- offset: number;
44
- borderLength?: number;
45
- scale?: number;
46
- strokeWidth?: number;
47
- pathData?: string;
48
- customSourceWidthPx?: number;
49
- customSourceHeightPx?: number;
50
- }
51
-
52
- export interface LineStyle {
53
- width: number;
54
- color: string;
55
- dashLength: number;
56
- style: "solid" | "dashed" | "hidden";
57
- }
58
-
59
- export interface DielineState {
60
- shape: DielineShape;
61
- shapeStyle: DielineShapeStyle;
62
- width: number;
63
- height: number;
64
- radius: number;
65
- offset: number;
66
- padding: number | string;
67
- mainLine: LineStyle;
68
- offsetLine: LineStyle;
69
- insideColor: string;
70
- showBleedLines: boolean;
71
- features: DielineFeature[];
72
- pathData?: string;
73
- customSourceWidthPx?: number;
74
- customSourceHeightPx?: number;
75
- }
22
+ import {
23
+ createDefaultDielineState,
24
+ DielineGeometry,
25
+ DielineState,
26
+ readDielineState,
27
+ } from "./model";
28
+ import { buildDielineRenderBundle } from "./renderBuilder";
76
29
 
77
30
  export class DielineTool implements Extension {
78
31
  id = "pooder.kit.dieline";
@@ -80,30 +33,7 @@ export class DielineTool implements Extension {
80
33
  name: "DielineTool",
81
34
  };
82
35
 
83
- private state: DielineState = {
84
- shape: DEFAULT_DIELINE_SHAPE,
85
- shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
86
- width: 500,
87
- height: 500,
88
- radius: 0,
89
- offset: 0,
90
- padding: 140,
91
- mainLine: {
92
- width: 2.7,
93
- color: "#FF0000",
94
- dashLength: 5,
95
- style: "solid",
96
- },
97
- offsetLine: {
98
- width: 2.7,
99
- color: "#FF0000",
100
- dashLength: 5,
101
- style: "solid",
102
- },
103
- insideColor: "rgba(0,0,0,0)",
104
- showBleedLines: true,
105
- features: [],
106
- };
36
+ private state: DielineState = createDefaultDielineState();
107
37
 
108
38
  private canvasService?: CanvasService;
109
39
  private context?: ExtensionContext;
@@ -174,166 +104,12 @@ export class DielineTool implements Extension {
174
104
  "ConfigurationService",
175
105
  );
176
106
  if (configService) {
177
- // Load initial config
178
- const s = this.state;
179
- const sizeState = readSizeState(configService);
180
- s.shape = normalizeDielineShape(
181
- configService.get("dieline.shape", s.shape),
182
- s.shape,
183
- );
184
- s.shapeStyle = normalizeShapeStyle(
185
- configService.get("dieline.shapeStyle", s.shapeStyle),
186
- s.shapeStyle,
187
- );
188
- s.width = sizeState.actualWidthMm;
189
- s.height = sizeState.actualHeightMm;
190
- s.radius = parseLengthToMm(
191
- configService.get("dieline.radius", s.radius),
192
- "mm",
193
- );
194
- s.padding = sizeState.viewPadding;
195
- s.offset =
196
- sizeState.cutMode === "outset"
197
- ? sizeState.cutMarginMm
198
- : sizeState.cutMode === "inset"
199
- ? -sizeState.cutMarginMm
200
- : 0;
201
-
202
- // Main Line
203
- s.mainLine.width = configService.get(
204
- "dieline.strokeWidth",
205
- s.mainLine.width,
206
- );
207
- s.mainLine.color = configService.get(
208
- "dieline.strokeColor",
209
- s.mainLine.color,
210
- );
211
- s.mainLine.dashLength = configService.get(
212
- "dieline.dashLength",
213
- s.mainLine.dashLength,
214
- );
215
- s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
216
-
217
- // Offset Line
218
- s.offsetLine.width = configService.get(
219
- "dieline.offsetStrokeWidth",
220
- s.offsetLine.width,
221
- );
222
- s.offsetLine.color = configService.get(
223
- "dieline.offsetStrokeColor",
224
- s.offsetLine.color,
225
- );
226
- s.offsetLine.dashLength = configService.get(
227
- "dieline.offsetDashLength",
228
- s.offsetLine.dashLength,
229
- );
230
- s.offsetLine.style = configService.get(
231
- "dieline.offsetStyle",
232
- s.offsetLine.style,
233
- );
234
-
235
- s.insideColor = configService.get("dieline.insideColor", s.insideColor);
236
- s.showBleedLines = configService.get(
237
- "dieline.showBleedLines",
238
- s.showBleedLines,
239
- );
240
- s.features = configService.get("dieline.features", s.features);
241
- s.pathData = configService.get("dieline.pathData", s.pathData);
242
- const sourceWidth = Number(
243
- configService.get("dieline.customSourceWidthPx", 0),
244
- );
245
- const sourceHeight = Number(
246
- configService.get("dieline.customSourceHeightPx", 0),
247
- );
248
- s.customSourceWidthPx =
249
- Number.isFinite(sourceWidth) && sourceWidth > 0
250
- ? sourceWidth
251
- : undefined;
252
- s.customSourceHeightPx =
253
- Number.isFinite(sourceHeight) && sourceHeight > 0
254
- ? sourceHeight
255
- : undefined;
107
+ Object.assign(this.state, readDielineState(configService, this.state));
256
108
 
257
109
  // Listen for changes
258
110
  configService.onAnyChange((e: { key: string; value: any }) => {
259
- if (e.key.startsWith("size.")) {
260
- const nextSize = readSizeState(configService);
261
- s.width = nextSize.actualWidthMm;
262
- s.height = nextSize.actualHeightMm;
263
- s.padding = nextSize.viewPadding;
264
- s.offset =
265
- nextSize.cutMode === "outset"
266
- ? nextSize.cutMarginMm
267
- : nextSize.cutMode === "inset"
268
- ? -nextSize.cutMarginMm
269
- : 0;
270
- this.updateDieline();
271
- return;
272
- }
273
-
274
- if (e.key.startsWith("dieline.")) {
275
- switch (e.key) {
276
- case "dieline.shape":
277
- s.shape = normalizeDielineShape(e.value, s.shape);
278
- break;
279
- case "dieline.shapeStyle":
280
- s.shapeStyle = normalizeShapeStyle(e.value, s.shapeStyle);
281
- break;
282
- case "dieline.radius":
283
- s.radius = parseLengthToMm(e.value, "mm");
284
- break;
285
-
286
- case "dieline.strokeWidth":
287
- s.mainLine.width = e.value;
288
- break;
289
- case "dieline.strokeColor":
290
- s.mainLine.color = e.value;
291
- break;
292
- case "dieline.dashLength":
293
- s.mainLine.dashLength = e.value;
294
- break;
295
- case "dieline.style":
296
- s.mainLine.style = e.value;
297
- break;
298
-
299
- case "dieline.offsetStrokeWidth":
300
- s.offsetLine.width = e.value;
301
- break;
302
- case "dieline.offsetStrokeColor":
303
- s.offsetLine.color = e.value;
304
- break;
305
- case "dieline.offsetDashLength":
306
- s.offsetLine.dashLength = e.value;
307
- break;
308
- case "dieline.offsetStyle":
309
- s.offsetLine.style = e.value;
310
- break;
311
-
312
- case "dieline.insideColor":
313
- s.insideColor = e.value;
314
- break;
315
- case "dieline.showBleedLines":
316
- s.showBleedLines = e.value;
317
- break;
318
- case "dieline.features":
319
- s.features = e.value;
320
- break;
321
- case "dieline.pathData":
322
- s.pathData = e.value;
323
- break;
324
- case "dieline.customSourceWidthPx":
325
- s.customSourceWidthPx =
326
- Number.isFinite(Number(e.value)) && Number(e.value) > 0
327
- ? Number(e.value)
328
- : undefined;
329
- break;
330
- case "dieline.customSourceHeightPx":
331
- s.customSourceHeightPx =
332
- Number.isFinite(Number(e.value)) && Number(e.value) > 0
333
- ? Number(e.value)
334
- : undefined;
335
- break;
336
- }
111
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
112
+ Object.assign(this.state, readDielineState(configService, this.state));
337
113
  this.updateDieline();
338
114
  }
339
115
  });
@@ -413,316 +189,39 @@ export class DielineTool implements Extension {
413
189
  return Array.isArray(items) && items.length > 0;
414
190
  }
415
191
 
416
- private syncSizeState(configService: ConfigurationService) {
417
- const sizeState = readSizeState(configService);
418
- this.state.width = sizeState.actualWidthMm;
419
- this.state.height = sizeState.actualHeightMm;
420
- this.state.padding = sizeState.viewPadding;
421
- this.state.offset =
422
- sizeState.cutMode === "outset"
423
- ? sizeState.cutMarginMm
424
- : sizeState.cutMode === "inset"
425
- ? -sizeState.cutMarginMm
426
- : 0;
427
- }
428
-
429
192
  private buildDielineSpecs(
430
193
  sceneLayout: NonNullable<ReturnType<typeof computeSceneLayout>>,
431
194
  ): RenderObjectSpec[] {
432
- const {
433
- shape,
434
- shapeStyle,
435
- radius,
436
- mainLine,
437
- offsetLine,
438
- insideColor,
439
- showBleedLines,
440
- features,
441
- } = this.state;
442
195
  const hasImages = this.hasImageItems();
443
-
444
- const canvasW =
445
- sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
446
- const canvasH =
447
- sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
448
- const scale = sceneLayout.scale;
449
- const cx = sceneLayout.trimRect.centerX;
450
- const cy = sceneLayout.trimRect.centerY;
451
-
452
- const visualWidth = sceneLayout.trimRect.width;
453
- const visualHeight = sceneLayout.trimRect.height;
454
- const visualRadius = radius * scale;
455
- const cutW = sceneLayout.cutRect.width;
456
- const cutH = sceneLayout.cutRect.height;
457
- const visualOffset = (cutW - visualWidth) / 2;
458
- const cutR =
459
- visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
460
-
461
- const absoluteFeatures = (features || []).map((f) => ({
462
- ...f,
463
- x: f.x,
464
- y: f.y,
465
- width: (f.width || 0) * scale,
466
- height: (f.height || 0) * scale,
467
- radius: (f.radius || 0) * scale,
468
- }));
469
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
470
-
471
- const specs: RenderObjectSpec[] = [];
472
-
473
- if (
474
- insideColor &&
475
- insideColor !== "transparent" &&
476
- insideColor !== "rgba(0,0,0,0)" &&
477
- !hasImages
478
- ) {
479
- const productPathData = generateDielinePath({
480
- shape,
481
- width: cutW,
482
- height: cutH,
483
- radius: cutR,
484
- x: cx,
485
- y: cy,
486
- features: cutFeatures,
487
- shapeStyle,
488
- pathData: this.state.pathData,
489
- customSourceWidthPx: this.state.customSourceWidthPx,
490
- customSourceHeightPx: this.state.customSourceHeightPx,
491
- canvasWidth: canvasW,
492
- canvasHeight: canvasH,
493
- });
494
-
495
- specs.push({
496
- id: "dieline.inside",
497
- type: "path",
498
- space: "screen",
499
- data: { id: "dieline.inside", type: "dieline" },
500
- props: {
501
- pathData: productPathData,
502
- fill: insideColor,
503
- stroke: null,
504
- selectable: false,
505
- evented: false,
506
- originX: "left",
507
- originY: "top",
508
- },
509
- });
510
- }
511
-
512
- if (Math.abs(visualOffset) > 0.0001) {
513
- const bleedPathData = generateBleedZonePath(
514
- {
515
- shape,
516
- width: visualWidth,
517
- height: visualHeight,
518
- radius: visualRadius,
519
- x: cx,
520
- y: cy,
521
- features: cutFeatures,
522
- shapeStyle,
523
- pathData: this.state.pathData,
524
- customSourceWidthPx: this.state.customSourceWidthPx,
525
- customSourceHeightPx: this.state.customSourceHeightPx,
526
- canvasWidth: canvasW,
527
- canvasHeight: canvasH,
528
- },
529
- {
530
- shape,
531
- width: cutW,
532
- height: cutH,
533
- radius: cutR,
534
- x: cx,
535
- y: cy,
536
- features: cutFeatures,
537
- shapeStyle,
538
- pathData: this.state.pathData,
539
- customSourceWidthPx: this.state.customSourceWidthPx,
540
- customSourceHeightPx: this.state.customSourceHeightPx,
541
- canvasWidth: canvasW,
542
- canvasHeight: canvasH,
543
- },
544
- visualOffset,
545
- );
546
-
547
- if (showBleedLines !== false) {
548
- const pattern = this.createHatchPattern(mainLine.color);
549
- if (pattern) {
550
- specs.push({
551
- id: "dieline.bleed-zone",
552
- type: "path",
553
- space: "screen",
554
- data: { id: "dieline.bleed-zone", type: "dieline" },
555
- props: {
556
- pathData: bleedPathData,
557
- fill: pattern,
558
- stroke: null,
559
- selectable: false,
560
- evented: false,
561
- objectCaching: false,
562
- originX: "left",
563
- originY: "top",
564
- },
565
- });
566
- }
567
- }
568
-
569
- const offsetPathData = generateDielinePath({
570
- shape,
571
- width: cutW,
572
- height: cutH,
573
- radius: cutR,
574
- x: cx,
575
- y: cy,
576
- features: cutFeatures,
577
- shapeStyle,
578
- pathData: this.state.pathData,
579
- customSourceWidthPx: this.state.customSourceWidthPx,
580
- customSourceHeightPx: this.state.customSourceHeightPx,
581
- canvasWidth: canvasW,
582
- canvasHeight: canvasH,
583
- });
584
-
585
- specs.push({
586
- id: "dieline.offset-border",
587
- type: "path",
588
- space: "screen",
589
- data: { id: "dieline.offset-border", type: "dieline" },
590
- props: {
591
- pathData: offsetPathData,
592
- fill: null,
593
- stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
594
- strokeWidth: offsetLine.width,
595
- strokeDashArray:
596
- offsetLine.style === "dashed"
597
- ? [offsetLine.dashLength, offsetLine.dashLength]
598
- : undefined,
599
- selectable: false,
600
- evented: false,
601
- originX: "left",
602
- originY: "top",
603
- },
604
- });
605
- }
606
-
607
- const borderPathData = generateDielinePath({
608
- shape,
609
- width: visualWidth,
610
- height: visualHeight,
611
- radius: visualRadius,
612
- x: cx,
613
- y: cy,
614
- features: absoluteFeatures,
615
- shapeStyle,
616
- pathData: this.state.pathData,
617
- customSourceWidthPx: this.state.customSourceWidthPx,
618
- customSourceHeightPx: this.state.customSourceHeightPx,
619
- canvasWidth: canvasW,
620
- canvasHeight: canvasH,
621
- });
622
-
623
- specs.push({
624
- id: "dieline.border",
625
- type: "path",
626
- space: "screen",
627
- data: { id: "dieline.border", type: "dieline" },
628
- props: {
629
- pathData: borderPathData,
630
- fill: "transparent",
631
- stroke: mainLine.style === "hidden" ? null : mainLine.color,
632
- strokeWidth: mainLine.width,
633
- strokeDashArray:
634
- mainLine.style === "dashed"
635
- ? [mainLine.dashLength, mainLine.dashLength]
636
- : undefined,
637
- selectable: false,
638
- evented: false,
639
- originX: "left",
640
- originY: "top",
641
- },
642
- });
643
-
644
- return specs;
196
+ return buildDielineRenderBundle({
197
+ state: this.state,
198
+ sceneLayout,
199
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800,
200
+ canvasHeight:
201
+ sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600,
202
+ hasImages,
203
+ createHatchPattern: (color) => this.createHatchPattern(color),
204
+ includeImageClipEffect: false,
205
+ }).specs;
645
206
  }
646
207
 
647
208
  private buildImageClipEffects(
648
209
  sceneLayout: NonNullable<ReturnType<typeof computeSceneLayout>>,
649
210
  ): RenderEffectSpec[] {
650
- const { shape, shapeStyle, radius, features } = this.state;
651
-
652
- const canvasW =
653
- sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800;
654
- const canvasH =
655
- sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600;
656
- const scale = sceneLayout.scale;
657
- const cx = sceneLayout.trimRect.centerX;
658
- const cy = sceneLayout.trimRect.centerY;
659
-
660
- const visualWidth = sceneLayout.trimRect.width;
661
- const visualRadius = radius * scale;
662
- const cutW = sceneLayout.cutRect.width;
663
- const cutH = sceneLayout.cutRect.height;
664
- const visualOffset = (cutW - visualWidth) / 2;
665
- const cutR =
666
- visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
667
-
668
- const absoluteFeatures = (features || []).map((f) => ({
669
- ...f,
670
- x: f.x,
671
- y: f.y,
672
- width: (f.width || 0) * scale,
673
- height: (f.height || 0) * scale,
674
- radius: (f.radius || 0) * scale,
675
- }));
676
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
677
-
678
- const clipPathData = generateDielinePath({
679
- shape,
680
- width: cutW,
681
- height: cutH,
682
- radius: cutR,
683
- x: cx,
684
- y: cy,
685
- features: cutFeatures,
686
- shapeStyle,
687
- pathData: this.state.pathData,
688
- customSourceWidthPx: this.state.customSourceWidthPx,
689
- customSourceHeightPx: this.state.customSourceHeightPx,
690
- canvasWidth: canvasW,
691
- canvasHeight: canvasH,
692
- });
693
- if (!clipPathData) return [];
694
-
695
- return [
696
- {
697
- type: "clipPath",
698
- id: "dieline.clip.image",
699
- visibility: {
700
- op: "not",
701
- expr: { op: "anySessionActive" },
702
- },
703
- targetPassIds: [IMAGE_OBJECT_LAYER_ID],
704
- source: {
705
- id: "dieline.effect.clip-path",
706
- type: "path",
707
- space: "screen",
708
- data: {
709
- id: "dieline.effect.clip-path",
710
- type: "dieline-effect",
711
- effect: "clipPath",
712
- },
713
- props: {
714
- pathData: clipPathData,
715
- fill: "#000000",
716
- stroke: null,
717
- originX: "left",
718
- originY: "top",
719
- selectable: false,
720
- evented: false,
721
- excludeFromExport: true,
722
- },
723
- },
211
+ return buildDielineRenderBundle({
212
+ state: this.state,
213
+ sceneLayout,
214
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService?.canvas.width || 800,
215
+ canvasHeight:
216
+ sceneLayout.canvasHeight || this.canvasService?.canvas.height || 600,
217
+ hasImages: this.hasImageItems(),
218
+ includeImageClipEffect: true,
219
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
220
+ clipVisibility: {
221
+ op: "not",
222
+ expr: { op: "anySessionActive" },
724
223
  },
725
- ];
224
+ }).effects;
726
225
  }
727
226
 
728
227
  public updateDieline(_emitEvent: boolean = true) {
@@ -735,7 +234,7 @@ export class DielineTool implements Extension {
735
234
  if (!configService) return;
736
235
  const seq = ++this.renderSeq;
737
236
 
738
- this.syncSizeState(configService);
237
+ Object.assign(this.state, readDielineState(configService, this.state));
739
238
  const sceneLayout = computeSceneLayout(
740
239
  this.canvasService,
741
240
  readSizeState(configService),
@@ -794,7 +293,7 @@ export class DielineTool implements Extension {
794
293
  return null;
795
294
  }
796
295
 
797
- this.syncSizeState(configService);
296
+ this.state = readDielineState(configService, this.state);
798
297
  const sceneLayout = computeSceneLayout(
799
298
  this.canvasService,
800
299
  readSizeState(configService),