@pooder/kit 3.1.0 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "paper": "^0.12.18",
21
21
  "fabric": "^7.0.0",
22
- "@pooder/core": "1.1.0"
22
+ "@pooder/core": "1.2.0"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup src/index.ts --format cjs,esm --dts",
package/src/coordinate.ts CHANGED
@@ -8,7 +8,45 @@ export interface Size {
8
8
  height: number;
9
9
  }
10
10
 
11
+ export type Unit = "px" | "mm" | "cm" | "in";
12
+
13
+ export interface Layout {
14
+ scale: number;
15
+ offsetX: number;
16
+ offsetY: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+
11
21
  export class Coordinate {
22
+ /**
23
+ * Calculate layout to fit content within container while preserving aspect ratio.
24
+ */
25
+ static calculateLayout(
26
+ container: Size,
27
+ content: Size,
28
+ padding: number = 0,
29
+ ): Layout {
30
+ const availableWidth = Math.max(0, container.width - padding * 2);
31
+ const availableHeight = Math.max(0, container.height - padding * 2);
32
+
33
+ if (content.width === 0 || content.height === 0) {
34
+ return { scale: 1, offsetX: 0, offsetY: 0, width: 0, height: 0 };
35
+ }
36
+
37
+ const scaleX = availableWidth / content.width;
38
+ const scaleY = availableHeight / content.height;
39
+ const scale = Math.min(scaleX, scaleY);
40
+
41
+ const width = content.width * scale;
42
+ const height = content.height * scale;
43
+
44
+ const offsetX = (container.width - width) / 2;
45
+ const offsetY = (container.height - height) / 2;
46
+
47
+ return { scale, offsetX, offsetY, width, height };
48
+ }
49
+
12
50
  /**
13
51
  * Convert an absolute value to a normalized value (0-1).
14
52
  * @param value Absolute value (e.g., pixels)
@@ -46,4 +84,23 @@ export class Coordinate {
46
84
  y: this.toAbsolute(point.y, size.height),
47
85
  };
48
86
  }
87
+
88
+ static convertUnit(value: number, from: Unit, to: Unit): number {
89
+ if (from === to) return value;
90
+
91
+ // Base unit: mm
92
+ const toMM: Record<Unit, number> = {
93
+ px: 0.264583, // 1px = 0.264583mm (96 DPI)
94
+ mm: 1,
95
+ cm: 10,
96
+ in: 25.4
97
+ };
98
+
99
+ const mmValue = value * (from === 'px' ? toMM.px : toMM[from] || 1);
100
+
101
+ if (to === 'px') {
102
+ return mmValue / toMM.px;
103
+ }
104
+ return mmValue / (toMM[to] || 1);
105
+ }
49
106
  }
package/src/dieline.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  import { Path, Pattern } from "fabric";
9
9
  import CanvasService from "./CanvasService";
10
10
  import { ImageTracer } from "./tracer";
11
- import { Coordinate } from "./coordinate";
11
+ import { Coordinate, Unit } from "./coordinate";
12
12
  import {
13
13
  generateDielinePath,
14
14
  generateMaskPath,
@@ -20,6 +20,7 @@ import {
20
20
 
21
21
  export interface DielineGeometry {
22
22
  shape: "rect" | "circle" | "ellipse" | "custom";
23
+ unit: Unit;
23
24
  x: number;
24
25
  y: number;
25
26
  width: number;
@@ -27,6 +28,7 @@ export interface DielineGeometry {
27
28
  radius: number;
28
29
  offset: number;
29
30
  borderLength?: number;
31
+ scale?: number;
30
32
  pathData?: string;
31
33
  }
32
34
 
@@ -36,6 +38,7 @@ export class DielineTool implements Extension {
36
38
  name: "DielineTool",
37
39
  };
38
40
 
41
+ private unit: Unit = "mm";
39
42
  private shape: "rect" | "circle" | "ellipse" | "custom" = "rect";
40
43
  private width: number = 500;
41
44
  private height: number = 500;
@@ -48,7 +51,7 @@ export class DielineTool implements Extension {
48
51
  private holes: HoleData[] = [];
49
52
  // Position is stored as normalized coordinates (0-1)
50
53
  private position?: { x: number; y: number };
51
- private borderLength?: number;
54
+ private padding: number | string = 140;
52
55
  private pathData?: string;
53
56
 
54
57
  private canvasService?: CanvasService;
@@ -56,13 +59,14 @@ export class DielineTool implements Extension {
56
59
 
57
60
  constructor(
58
61
  options?: Partial<{
62
+ unit: Unit;
59
63
  shape: "rect" | "circle" | "ellipse" | "custom";
60
64
  width: number;
61
65
  height: number;
62
66
  radius: number;
63
67
  // Position is normalized (0-1)
64
68
  position: { x: number; y: number };
65
- borderLength: number;
69
+ padding: number | string;
66
70
  offset: number;
67
71
  style: "solid" | "dashed";
68
72
  insideColor: string;
@@ -88,14 +92,12 @@ export class DielineTool implements Extension {
88
92
  const configService = context.services.get<any>("ConfigurationService");
89
93
  if (configService) {
90
94
  // Load initial config
95
+ this.unit = configService.get("dieline.unit", this.unit);
91
96
  this.shape = configService.get("dieline.shape", this.shape);
92
97
  this.width = configService.get("dieline.width", this.width);
93
98
  this.height = configService.get("dieline.height", this.height);
94
99
  this.radius = configService.get("dieline.radius", this.radius);
95
- this.borderLength = configService.get(
96
- "dieline.borderLength",
97
- this.borderLength,
98
- );
100
+ this.padding = configService.get("dieline.padding", this.padding);
99
101
  this.offset = configService.get("dieline.offset", this.offset);
100
102
  this.style = configService.get("dieline.style", this.style);
101
103
  this.insideColor = configService.get(
@@ -141,6 +143,13 @@ export class DielineTool implements Extension {
141
143
  contribute() {
142
144
  return {
143
145
  [ContributionPointIds.CONFIGURATIONS]: [
146
+ {
147
+ id: "dieline.unit",
148
+ type: "select",
149
+ label: "Unit",
150
+ options: ["px", "mm", "cm", "in"],
151
+ default: this.unit,
152
+ },
144
153
  {
145
154
  id: "dieline.shape",
146
155
  type: "select",
@@ -176,15 +185,14 @@ export class DielineTool implements Extension {
176
185
  id: "dieline.position",
177
186
  type: "json",
178
187
  label: "Position (Normalized)",
179
- default: this.position,
188
+ default: this.radius,
180
189
  },
181
190
  {
182
- id: "dieline.borderLength",
183
- type: "number",
184
- label: "Margin",
185
- min: 0,
186
- max: 500,
187
- default: this.borderLength,
191
+ id: "dieline.padding",
192
+ type: "select",
193
+ label: "View Padding",
194
+ options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
195
+ default: this.padding,
188
196
  },
189
197
  {
190
198
  id: "dieline.offset",
@@ -255,8 +263,9 @@ export class DielineTool implements Extension {
255
263
  const newWidth = bounds.width * scale;
256
264
  const newHeight = bounds.height * scale;
257
265
 
258
- const configService =
259
- this.context?.services.get<any>("ConfigurationService");
266
+ const configService = this.context?.services.get<any>(
267
+ "ConfigurationService",
268
+ );
260
269
  if (configService) {
261
270
  configService.update("dieline.width", newWidth);
262
271
  configService.update("dieline.height", newHeight);
@@ -336,12 +345,30 @@ export class DielineTool implements Extension {
336
345
  return new Pattern({ source: canvas, repetition: "repeat" });
337
346
  }
338
347
 
348
+ private resolvePadding(
349
+ containerWidth: number,
350
+ containerHeight: number,
351
+ ): number {
352
+ if (typeof this.padding === "number") {
353
+ return this.padding;
354
+ }
355
+ if (typeof this.padding === "string") {
356
+ if (this.padding.endsWith("%")) {
357
+ const percent = parseFloat(this.padding) / 100;
358
+ return Math.min(containerWidth, containerHeight) * percent;
359
+ }
360
+ return parseFloat(this.padding) || 0;
361
+ }
362
+ return 0;
363
+ }
364
+
339
365
  public updateDieline(emitEvent: boolean = true) {
340
366
  if (!this.canvasService) return;
341
367
  const layer = this.getLayer();
342
368
  if (!layer) return;
343
369
 
344
370
  const {
371
+ unit,
345
372
  shape,
346
373
  radius,
347
374
  offset,
@@ -349,7 +376,6 @@ export class DielineTool implements Extension {
349
376
  insideColor,
350
377
  outsideColor,
351
378
  position,
352
- borderLength,
353
379
  showBleedLines,
354
380
  holes,
355
381
  } = this;
@@ -358,45 +384,72 @@ export class DielineTool implements Extension {
358
384
  const canvasW = this.canvasService.canvas.width || 800;
359
385
  const canvasH = this.canvasService.canvas.height || 600;
360
386
 
361
- let visualWidth = width;
362
- let visualHeight = height;
363
-
364
- if (borderLength && borderLength > 0) {
365
- visualWidth = Math.max(0, canvasW - borderLength * 2);
366
- visualHeight = Math.max(0, canvasH - borderLength * 2);
367
- }
368
-
369
- const cx = Coordinate.toAbsolute(position?.x ?? 0.5, canvasW);
370
- const cy = Coordinate.toAbsolute(position?.y ?? 0.5, canvasH);
387
+ // Calculate Layout based on Physical Dimensions and Canvas Size
388
+ // Add padding to avoid edge hugging
389
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
390
+ const layout = Coordinate.calculateLayout(
391
+ { width: canvasW, height: canvasH },
392
+ { width, height },
393
+ paddingPx,
394
+ );
395
+
396
+ const scale = layout.scale;
397
+ const cx = layout.offsetX + layout.width / 2;
398
+ const cy = layout.offsetY + layout.height / 2;
399
+
400
+ // Scaled dimensions for rendering (Pixels)
401
+ const visualWidth = layout.width;
402
+ const visualHeight = layout.height;
403
+ const visualRadius = radius * scale;
404
+ const visualOffset = offset * scale;
371
405
 
372
406
  // Clear existing objects
373
407
  layer.remove(...layer.getObjects());
374
408
 
375
- // Resolve Holes for Geometry Generation
409
+ // Resolve Holes for Geometry Generation (using visual coordinates)
376
410
  const geometryForHoles = {
377
411
  x: cx,
378
412
  y: cy,
379
413
  width: visualWidth,
380
414
  height: visualHeight,
415
+ // Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
381
416
  };
382
417
 
383
418
  const absoluteHoles = (holes || []).map((h) => {
384
- const pos = resolveHolePosition(
385
- h,
386
- geometryForHoles,
387
- { width: canvasW, height: canvasH }
388
- );
419
+ // Scale hole radii and offsets: mm -> current unit -> pixels
420
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
421
+ const offsetScale = unitScale * scale;
422
+
423
+ // Apply scaling to offsets BEFORE resolving position
424
+ const hWithPixelOffsets = {
425
+ ...h,
426
+ offsetX: (h.offsetX || 0) * offsetScale,
427
+ offsetY: (h.offsetY || 0) * offsetScale,
428
+ };
429
+
430
+ const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
431
+ width: canvasW,
432
+ height: canvasH,
433
+ });
434
+
389
435
  return {
390
436
  ...h,
391
437
  x: pos.x,
392
438
  y: pos.y,
439
+ // Scale hole radii: mm -> current unit -> pixels
440
+ innerRadius: h.innerRadius * offsetScale,
441
+ outerRadius: h.outerRadius * offsetScale,
442
+ // Store scaled offsets in the result for consistency, though pos is already resolved
443
+ offsetX: hWithPixelOffsets.offsetX,
444
+ offsetY: hWithPixelOffsets.offsetY,
393
445
  };
394
446
  });
395
447
 
396
448
  // 1. Draw Mask (Outside)
397
- const cutW = Math.max(0, width + offset * 2);
398
- const cutH = Math.max(0, height + offset * 2);
399
- const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
449
+ const cutW = Math.max(0, visualWidth + visualOffset * 2);
450
+ const cutH = Math.max(0, visualHeight + visualOffset * 2);
451
+ const cutR =
452
+ visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
400
453
 
401
454
  // Use Paper.js to generate the complex mask path
402
455
  const maskPathData = generateMaskPath({
@@ -440,6 +493,8 @@ export class DielineTool implements Extension {
440
493
  y: cy,
441
494
  holes: absoluteHoles,
442
495
  pathData: this.pathData,
496
+ canvasWidth: canvasW,
497
+ canvasHeight: canvasH,
443
498
  });
444
499
 
445
500
  const insideObj = new Path(productPathData, {
@@ -458,15 +513,17 @@ export class DielineTool implements Extension {
458
513
  const bleedPathData = generateBleedZonePath(
459
514
  {
460
515
  shape,
461
- width,
462
- height,
463
- radius,
516
+ width: visualWidth,
517
+ height: visualHeight,
518
+ radius: visualRadius,
464
519
  x: cx,
465
520
  y: cy,
466
521
  holes: absoluteHoles,
467
522
  pathData: this.pathData,
523
+ canvasWidth: canvasW,
524
+ canvasHeight: canvasH,
468
525
  },
469
- offset,
526
+ visualOffset,
470
527
  );
471
528
 
472
529
  // Use solid red for hatch lines to match dieline, background is transparent
@@ -496,6 +553,8 @@ export class DielineTool implements Extension {
496
553
  y: cy,
497
554
  holes: absoluteHoles,
498
555
  pathData: this.pathData,
556
+ canvasWidth: canvasW,
557
+ canvasHeight: canvasH,
499
558
  });
500
559
 
501
560
  const offsetBorderObj = new Path(offsetPathData, {
@@ -517,13 +576,15 @@ export class DielineTool implements Extension {
517
576
  // generateDielinePath expects holes to be in absolute coordinates (matching width/height scale)
518
577
  const borderPathData = generateDielinePath({
519
578
  shape,
520
- width: width,
521
- height: height,
522
- radius: radius,
579
+ width: visualWidth,
580
+ height: visualHeight,
581
+ radius: visualRadius,
523
582
  x: cx,
524
583
  y: cy,
525
- holes: absoluteHoles, // FIX: Use absoluteHoles instead of holes
584
+ holes: absoluteHoles,
526
585
  pathData: this.pathData,
586
+ canvasWidth: canvasW,
587
+ canvasHeight: canvasH,
527
588
  });
528
589
 
529
590
  const borderObj = new Path(borderPathData, {
@@ -566,6 +627,13 @@ export class DielineTool implements Extension {
566
627
  // Emit change event so other tools (like HoleTool) can react
567
628
  // Only emit if requested (to avoid loops when updating non-geometry props like holes)
568
629
  if (emitEvent && this.context) {
630
+ // FIX: Ensure we use the exact same geometry values as used in rendering above.
631
+ // Although getGeometry() recalculates layout, it should be identical if props haven't changed.
632
+ // But to be absolutely safe and avoid micro-differences or race conditions, we can reuse calculated values.
633
+ // However, getGeometry is public API, so it's better if it's correct.
634
+ // Let's verify getGeometry logic matches updateDieline logic perfectly.
635
+ // Yes, both use Coordinate.calculateLayout with the same inputs.
636
+
569
637
  const geometry = this.getGeometry();
570
638
  if (geometry) {
571
639
  this.context.eventBus.emit("dieline:geometry:change", geometry);
@@ -575,141 +643,140 @@ export class DielineTool implements Extension {
575
643
 
576
644
  public getGeometry(): DielineGeometry | null {
577
645
  if (!this.canvasService) return null;
578
- const { shape, width, height, radius, position, borderLength, offset } =
579
- this;
646
+ const { unit, shape, width, height, radius, position, offset } = this;
580
647
  const canvasW = this.canvasService.canvas.width || 800;
581
648
  const canvasH = this.canvasService.canvas.height || 600;
582
649
 
583
- let visualWidth = width;
584
- let visualHeight = height;
650
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
651
+ const layout = Coordinate.calculateLayout(
652
+ { width: canvasW, height: canvasH },
653
+ { width, height },
654
+ paddingPx,
655
+ );
585
656
 
586
- if (borderLength && borderLength > 0) {
587
- visualWidth = Math.max(0, canvasW - borderLength * 2);
588
- visualHeight = Math.max(0, canvasH - borderLength * 2);
589
- }
657
+ const scale = layout.scale;
658
+ const cx = layout.offsetX + layout.width / 2;
659
+ const cy = layout.offsetY + layout.height / 2;
590
660
 
591
- const cx = Coordinate.toAbsolute(position?.x ?? 0.5, canvasW);
592
- const cy = Coordinate.toAbsolute(position?.y ?? 0.5, canvasH);
661
+ const visualWidth = layout.width;
662
+ const visualHeight = layout.height;
593
663
 
594
664
  return {
595
665
  shape,
666
+ unit,
596
667
  x: cx,
597
668
  y: cy,
598
669
  width: visualWidth,
599
670
  height: visualHeight,
600
- radius,
601
- offset,
602
- borderLength,
671
+ radius: radius * scale,
672
+ offset: offset * scale,
673
+ // Pass scale to help other tools (like HoleTool) convert units
674
+ scale,
603
675
  pathData: this.pathData,
604
- };
676
+ } as DielineGeometry;
605
677
  }
606
678
 
607
- public exportCutImage() {
679
+ public async exportCutImage() {
608
680
  if (!this.canvasService) return null;
609
- const canvas = this.canvasService.canvas;
681
+ const userLayer = this.canvasService.getLayer("user");
682
+
683
+ // Even if no user images, we might want to export the shape?
684
+ // But usually "Cut Image" implies the printed content.
685
+ // If empty, maybe just return null or empty string.
686
+ if (!userLayer) return null;
610
687
 
611
688
  // 1. Generate Path Data
612
689
  const { shape, width, height, radius, position, holes } = this;
613
- const canvasW = canvas.width || 800;
614
- const canvasH = canvas.height || 600;
615
- const cx = Coordinate.toAbsolute(position?.x ?? 0.5, canvasW);
616
- const cy = Coordinate.toAbsolute(position?.y ?? 0.5, canvasH);
690
+ const canvasW = this.canvasService.canvas.width || 800;
691
+ const canvasH = this.canvasService.canvas.height || 600;
692
+
693
+ const paddingPx = this.resolvePadding(canvasW, canvasH);
694
+ const layout = Coordinate.calculateLayout(
695
+ { width: canvasW, height: canvasH },
696
+ { width, height },
697
+ paddingPx,
698
+ );
699
+ const scale = layout.scale;
700
+ const cx = layout.offsetX + layout.width / 2;
701
+ const cy = layout.offsetY + layout.height / 2;
702
+ const visualWidth = layout.width;
703
+ const visualHeight = layout.height;
704
+ const visualRadius = radius * scale;
617
705
 
618
706
  // Denormalize Holes for Export
619
707
  const absoluteHoles = (holes || []).map((h) => {
708
+ const unit = this.unit || "mm";
709
+ const unitScale = Coordinate.convertUnit(1, "mm", unit);
710
+
620
711
  const pos = resolveHolePosition(
621
- h,
622
- { x: cx, y: cy, width, height },
623
- { width: canvasW, height: canvasH }
712
+ {
713
+ ...h,
714
+ offsetX: (h.offsetX || 0) * unitScale * scale,
715
+ offsetY: (h.offsetY || 0) * unitScale * scale,
716
+ },
717
+ { x: cx, y: cy, width: visualWidth, height: visualHeight },
718
+ { width: canvasW, height: canvasH },
624
719
  );
720
+
625
721
  return {
626
722
  ...h,
627
723
  x: pos.x,
628
724
  y: pos.y,
725
+ innerRadius: h.innerRadius * unitScale * scale,
726
+ outerRadius: h.outerRadius * unitScale * scale,
727
+ offsetX: (h.offsetX || 0) * unitScale * scale,
728
+ offsetY: (h.offsetY || 0) * unitScale * scale,
629
729
  };
630
730
  });
631
731
 
632
732
  const pathData = generateDielinePath({
633
733
  shape,
634
- width,
635
- height,
636
- radius,
734
+ width: visualWidth,
735
+ height: visualHeight,
736
+ radius: visualRadius,
637
737
  x: cx,
638
738
  y: cy,
639
739
  holes: absoluteHoles,
640
740
  pathData: this.pathData,
741
+ canvasWidth: canvasW,
742
+ canvasHeight: canvasH,
641
743
  });
642
744
 
643
- // 2. Create Clip Path
644
- // @ts-ignore
745
+ // 2. Prepare for Export
746
+ // Clone the layer to not affect the stage
747
+ const clonedLayer = await userLayer.clone();
748
+
749
+ // Create Clip Path
750
+ // Note: In Fabric, clipPath is relative to the object center usually,
751
+ // but for a Group that is full-canvas (left=0, top=0), absolute coordinates should work
752
+ // if we configure it correctly.
753
+ // However, Fabric's clipPath handling can be tricky with Groups.
754
+ // Safest bet: Position the clipPath absolutely and ensuring group is absolute.
755
+
645
756
  const clipPath = new Path(pathData, {
646
- left: 0,
647
- top: 0,
648
757
  originX: "left",
649
758
  originY: "top",
650
- absolutePositioned: true,
651
- });
652
-
653
- // 3. Hide UI Layers
654
- const layer = this.getLayer();
655
- const wasVisible = layer?.visible ?? true;
656
- if (layer) layer.visible = false;
657
-
658
- // Hide hole markers
659
- const holeMarkers = canvas
660
- .getObjects()
661
- .filter((o: any) => o.data?.type === "hole-marker");
662
- holeMarkers.forEach((o) => (o.visible = false));
663
-
664
- // Hide Ruler Overlay
665
- const rulerLayer = canvas
666
- .getObjects()
667
- .find((obj: any) => obj.data?.id === "ruler-overlay");
668
- const rulerWasVisible = rulerLayer?.visible ?? true;
669
- if (rulerLayer) rulerLayer.visible = false;
670
-
671
- // 4. Apply Clip & Export
672
- const originalClip = canvas.clipPath;
673
- canvas.clipPath = clipPath;
674
-
675
- const bbox = clipPath.getBoundingRect();
676
-
677
- const clipPathCorrected = new Path(pathData, {
678
- absolutePositioned: true,
679
759
  left: 0,
680
760
  top: 0,
761
+ absolutePositioned: true, // Important for groups
681
762
  });
682
763
 
683
- const tempPath = new Path(pathData);
684
- const tempBounds = tempPath.getBoundingRect();
685
-
686
- clipPathCorrected.set({
687
- left: tempBounds.left,
688
- top: tempBounds.top,
689
- originX: "left",
690
- originY: "top",
691
- });
764
+ clonedLayer.clipPath = clipPath;
692
765
 
693
- // 4. Apply Clip & Export
694
- canvas.clipPath = clipPathCorrected;
766
+ // 3. Calculate Crop Area (The Dieline Bounds)
767
+ // We want to export only the area covered by the dieline
768
+ const bounds = clipPath.getBoundingRect();
695
769
 
696
- const exportBbox = clipPathCorrected.getBoundingRect();
697
- const dataURL = canvas.toDataURL({
770
+ // 4. Export
771
+ const dataUrl = clonedLayer.toDataURL({
698
772
  format: "png",
699
- multiplier: 2,
700
- left: exportBbox.left,
701
- top: exportBbox.top,
702
- width: exportBbox.width,
703
- height: exportBbox.height,
773
+ multiplier: 2, // Better quality
774
+ left: bounds.left,
775
+ top: bounds.top,
776
+ width: bounds.width,
777
+ height: bounds.height,
704
778
  });
705
779
 
706
- // 5. Restore
707
- canvas.clipPath = originalClip;
708
- if (layer) layer.visible = wasVisible;
709
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
710
- holeMarkers.forEach((o) => (o.visible = true));
711
- canvas.requestRenderAll();
712
-
713
- return dataURL;
780
+ return dataUrl;
714
781
  }
715
782
  }