@pooder/kit 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -205,7 +205,7 @@ var BackgroundTool = class {
205
205
  import {
206
206
  ContributionPointIds as ContributionPointIds2
207
207
  } from "@pooder/core";
208
- import { Path, Pattern } from "fabric";
208
+ import { Canvas as FabricCanvas, Path, Pattern } from "fabric";
209
209
 
210
210
  // src/tracer.ts
211
211
  import paper from "paper";
@@ -1756,6 +1756,7 @@ function buildSceneGeometry(configService, layout) {
1756
1756
  }
1757
1757
 
1758
1758
  // src/dieline.ts
1759
+ var IMAGE_OBJECT_LAYER_ID = "image.user";
1759
1760
  var DielineTool = class {
1760
1761
  constructor(options) {
1761
1762
  this.id = "pooder.kit.dieline";
@@ -2082,8 +2083,8 @@ var DielineTool = class {
2082
2083
  {
2083
2084
  command: "exportCutImage",
2084
2085
  title: "Export Cut Image",
2085
- handler: () => {
2086
- return this.exportCutImage();
2086
+ handler: (options) => {
2087
+ return this.exportCutImage(options);
2087
2088
  }
2088
2089
  },
2089
2090
  {
@@ -2423,18 +2424,29 @@ var DielineTool = class {
2423
2424
  pathData: this.state.pathData
2424
2425
  };
2425
2426
  }
2426
- async exportCutImage() {
2427
- if (!this.canvasService) return null;
2427
+ async exportCutImage(options) {
2428
+ var _a, _b;
2429
+ const debug = (options == null ? void 0 : options.debug) === true;
2430
+ if (!this.canvasService) {
2431
+ console.warn("[DielineTool] exportCutImage returned null: canvas-not-ready");
2432
+ return null;
2433
+ }
2428
2434
  const configService = this.getConfigService();
2429
- if (!configService) return null;
2430
- const userLayer = this.canvasService.getLayer("user");
2431
- if (!userLayer) return null;
2435
+ if (!configService) {
2436
+ console.warn(
2437
+ "[DielineTool] exportCutImage returned null: config-service-not-ready"
2438
+ );
2439
+ return null;
2440
+ }
2432
2441
  this.syncSizeState(configService);
2433
2442
  const sceneLayout = computeSceneLayout(
2434
2443
  this.canvasService,
2435
2444
  readSizeState(configService)
2436
2445
  );
2437
- if (!sceneLayout) return null;
2446
+ if (!sceneLayout) {
2447
+ console.warn("[DielineTool] exportCutImage returned null: scene-layout-null");
2448
+ return null;
2449
+ }
2438
2450
  const { shape, radius, features, pathData } = this.state;
2439
2451
  const canvasW = sceneLayout.canvasWidth || this.canvasService.canvas.width || 800;
2440
2452
  const canvasH = sceneLayout.canvasHeight || this.canvasService.canvas.height || 600;
@@ -2467,24 +2479,104 @@ var DielineTool = class {
2467
2479
  canvasWidth: canvasW,
2468
2480
  canvasHeight: canvasH
2469
2481
  });
2470
- const clonedLayer = await userLayer.clone();
2471
2482
  const clipPath = new Path(generatedPathData, {
2472
- originX: "left",
2473
- originY: "top",
2474
- left: 0,
2475
- top: 0,
2483
+ originX: "center",
2484
+ originY: "center",
2485
+ left: cx,
2486
+ top: cy,
2476
2487
  absolutePositioned: true
2477
2488
  });
2478
- clonedLayer.clipPath = clipPath;
2479
- const bounds = clipPath.getBoundingRect();
2480
- return clonedLayer.toDataURL({
2481
- format: "png",
2482
- multiplier: 2,
2483
- left: bounds.left,
2484
- top: bounds.top,
2485
- width: bounds.width,
2486
- height: bounds.height
2489
+ const pathOffsetX = Number((_a = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _a.x);
2490
+ const pathOffsetY = Number((_b = clipPath == null ? void 0 : clipPath.pathOffset) == null ? void 0 : _b.y);
2491
+ const centerX = Number.isFinite(pathOffsetX) ? pathOffsetX : cx;
2492
+ const centerY = Number.isFinite(pathOffsetY) ? pathOffsetY : cy;
2493
+ clipPath.set({
2494
+ originX: "center",
2495
+ originY: "center",
2496
+ left: centerX,
2497
+ top: centerY,
2498
+ absolutePositioned: true
2499
+ });
2500
+ clipPath.setCoords();
2501
+ const pathBounds = clipPath.getBoundingRect();
2502
+ if (!Number.isFinite(pathBounds.left) || !Number.isFinite(pathBounds.top) || !Number.isFinite(pathBounds.width) || !Number.isFinite(pathBounds.height) || pathBounds.width <= 0 || pathBounds.height <= 0) {
2503
+ console.warn("[DielineTool] exportCutImage returned null: invalid-cut-bounds", {
2504
+ bounds: pathBounds
2505
+ });
2506
+ return null;
2507
+ }
2508
+ const exportBounds = pathBounds;
2509
+ const sourceImages = this.canvasService.canvas.getObjects().filter((obj) => {
2510
+ var _a2;
2511
+ return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID;
2512
+ });
2513
+ if (!sourceImages.length) {
2514
+ console.warn(
2515
+ "[DielineTool] exportCutImage returned null: no-image-objects-on-canvas"
2516
+ );
2517
+ return null;
2518
+ }
2519
+ const sourceCanvasWidth = Number(
2520
+ this.canvasService.canvas.width || sceneLayout.canvasWidth || canvasW
2521
+ );
2522
+ const sourceCanvasHeight = Number(
2523
+ this.canvasService.canvas.height || sceneLayout.canvasHeight || canvasH
2524
+ );
2525
+ const el = document.createElement("canvas");
2526
+ const exportCanvas = new FabricCanvas(el, {
2527
+ renderOnAddRemove: false,
2528
+ selection: false,
2529
+ enableRetinaScaling: false,
2530
+ preserveObjectStacking: true
2531
+ });
2532
+ exportCanvas.setDimensions({
2533
+ width: Math.max(1, sourceCanvasWidth),
2534
+ height: Math.max(1, sourceCanvasHeight)
2487
2535
  });
2536
+ try {
2537
+ for (const source of sourceImages) {
2538
+ const clone = await source.clone();
2539
+ clone.set({
2540
+ selectable: false,
2541
+ evented: false
2542
+ });
2543
+ clone.setCoords();
2544
+ exportCanvas.add(clone);
2545
+ }
2546
+ exportCanvas.clipPath = clipPath;
2547
+ exportCanvas.renderAll();
2548
+ const dataUrl = exportCanvas.toDataURL({
2549
+ format: "png",
2550
+ multiplier: 2,
2551
+ left: exportBounds.left,
2552
+ top: exportBounds.top,
2553
+ width: exportBounds.width,
2554
+ height: exportBounds.height
2555
+ });
2556
+ if (debug) {
2557
+ console.info("[DielineTool] exportCutImage success", {
2558
+ sourceCount: sourceImages.length,
2559
+ bounds: exportBounds,
2560
+ rawPathBounds: pathBounds,
2561
+ pathOffset: {
2562
+ x: Number.isFinite(pathOffsetX) ? pathOffsetX : null,
2563
+ y: Number.isFinite(pathOffsetY) ? pathOffsetY : null
2564
+ },
2565
+ clipPathCenter: {
2566
+ x: centerX,
2567
+ y: centerY
2568
+ },
2569
+ cutRect: sceneLayout.cutRect,
2570
+ canvasSize: {
2571
+ width: Math.max(1, sourceCanvasWidth),
2572
+ height: Math.max(1, sourceCanvasHeight)
2573
+ }
2574
+ });
2575
+ }
2576
+ return dataUrl;
2577
+ } finally {
2578
+ exportCanvas.dispose();
2579
+ }
2488
2580
  }
2489
2581
  };
2490
2582
 
@@ -3575,8 +3667,8 @@ var FeatureTool = class {
3575
3667
  import {
3576
3668
  ContributionPointIds as ContributionPointIds5
3577
3669
  } from "@pooder/core";
3578
- import { Canvas as FabricCanvas, Image as FabricImage, Point as Point2 } from "fabric";
3579
- var IMAGE_OBJECT_LAYER_ID = "image.user";
3670
+ import { Canvas as FabricCanvas2, Image as FabricImage, Point as Point2 } from "fabric";
3671
+ var IMAGE_OBJECT_LAYER_ID2 = "image.user";
3580
3672
  var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
3581
3673
  var IMAGE_REPLACE_GUARD_MS = 2500;
3582
3674
  var ImageTool = class {
@@ -3636,7 +3728,7 @@ var ImageTool = class {
3636
3728
  const selectedImage = list.find(
3637
3729
  (obj) => {
3638
3730
  var _a2;
3639
- return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID;
3731
+ return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID2;
3640
3732
  }
3641
3733
  );
3642
3734
  this.isImageSelectionActive = !!selectedImage;
@@ -3675,7 +3767,7 @@ var ImageTool = class {
3675
3767
  const target = e == null ? void 0 : e.target;
3676
3768
  const id = (_a = target == null ? void 0 : target.data) == null ? void 0 : _a.id;
3677
3769
  const layerId = (_b = target == null ? void 0 : target.data) == null ? void 0 : _b.layerId;
3678
- if (typeof id !== "string" || layerId !== IMAGE_OBJECT_LAYER_ID) return;
3770
+ if (typeof id !== "string" || layerId !== IMAGE_OBJECT_LAYER_ID2) return;
3679
3771
  const frame = this.getFrameRect();
3680
3772
  if (!frame.width || !frame.height) return;
3681
3773
  const center = target.getCenterPoint ? target.getCenterPoint() : new Point2((_c = target.left) != null ? _c : 0, (_d = target.top) != null ? _d : 0);
@@ -3706,10 +3798,7 @@ var ImageTool = class {
3706
3798
  context.eventBus.on("selection:created", this.onSelectionChanged);
3707
3799
  context.eventBus.on("selection:updated", this.onSelectionChanged);
3708
3800
  context.eventBus.on("selection:cleared", this.onSelectionCleared);
3709
- context.eventBus.on(
3710
- "scene:layout:change",
3711
- this.onSceneLayoutChanged
3712
- );
3801
+ context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
3713
3802
  const configService = context.services.get(
3714
3803
  "ConfigurationService"
3715
3804
  );
@@ -3749,10 +3838,7 @@ var ImageTool = class {
3749
3838
  context.eventBus.off("selection:created", this.onSelectionChanged);
3750
3839
  context.eventBus.off("selection:updated", this.onSelectionChanged);
3751
3840
  context.eventBus.off("selection:cleared", this.onSelectionCleared);
3752
- context.eventBus.off(
3753
- "scene:layout:change",
3754
- this.onSceneLayoutChanged
3755
- );
3841
+ context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
3756
3842
  (_a = this.dirtyTrackerDisposable) == null ? void 0 : _a.dispose();
3757
3843
  this.dirtyTrackerDisposable = void 0;
3758
3844
  this.clearRenderedImages();
@@ -3861,7 +3947,7 @@ var ImageTool = class {
3861
3947
  id: "image.frame.outerBackground",
3862
3948
  type: "color",
3863
3949
  label: "Image Frame Outer Background",
3864
- default: "rgba(0,0,0,0.18)"
3950
+ default: "#f5f5f5"
3865
3951
  }
3866
3952
  ],
3867
3953
  [ContributionPointIds5.COMMANDS]: [
@@ -3904,6 +3990,7 @@ var ImageTool = class {
3904
3990
  this.workingItems = this.cloneItems(this.items);
3905
3991
  this.hasWorkingChanges = false;
3906
3992
  this.updateImages();
3993
+ this.emitWorkingChange();
3907
3994
  }
3908
3995
  },
3909
3996
  {
@@ -4018,13 +4105,20 @@ var ImageTool = class {
4018
4105
  cloneItems(items) {
4019
4106
  return this.normalizeItems((items || []).map((i) => ({ ...i })));
4020
4107
  }
4108
+ emitWorkingChange(changedId = null) {
4109
+ var _a;
4110
+ (_a = this.context) == null ? void 0 : _a.eventBus.emit("image:working:change", {
4111
+ changedId,
4112
+ items: this.cloneItems(this.workingItems)
4113
+ });
4114
+ }
4021
4115
  generateId() {
4022
4116
  return Math.random().toString(36).substring(2, 9);
4023
4117
  }
4024
4118
  getImageIdFromActiveObject() {
4025
4119
  var _a, _b, _c;
4026
4120
  const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
4027
- if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
4121
+ if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
4028
4122
  return active.data.id;
4029
4123
  }
4030
4124
  return null;
@@ -4086,6 +4180,7 @@ var ImageTool = class {
4086
4180
  if (this.workingItems.some((existing) => existing.id === item.id)) return;
4087
4181
  this.workingItems = this.cloneItems([...this.workingItems, item]);
4088
4182
  this.updateImages();
4183
+ this.emitWorkingChange(item.id);
4089
4184
  }
4090
4185
  async updateImage(id, updates, options = {}) {
4091
4186
  this.syncToolActiveFromWorkbench();
@@ -4193,7 +4288,7 @@ var ImageTool = class {
4193
4288
  if (!this.canvasService) return [];
4194
4289
  return this.canvasService.canvas.getObjects().filter((obj) => {
4195
4290
  var _a;
4196
- return ((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) === IMAGE_OBJECT_LAYER_ID;
4291
+ return ((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) === IMAGE_OBJECT_LAYER_ID2;
4197
4292
  });
4198
4293
  }
4199
4294
  getOverlayObjects() {
@@ -4269,10 +4364,7 @@ var ImageTool = class {
4269
4364
  "image.frame.innerBackground",
4270
4365
  "rgba(0,0,0,0)"
4271
4366
  ) || "rgba(0,0,0,0)",
4272
- outerBackground: this.getConfig(
4273
- "image.frame.outerBackground",
4274
- "rgba(0,0,0,0.18)"
4275
- ) || "rgba(0,0,0,0.18)"
4367
+ outerBackground: "#f5f5f5"
4276
4368
  };
4277
4369
  }
4278
4370
  resolveRenderImageState(item) {
@@ -4349,7 +4441,7 @@ var ImageTool = class {
4349
4441
  created.set({
4350
4442
  data: {
4351
4443
  id: item.id,
4352
- layerId: IMAGE_OBJECT_LAYER_ID,
4444
+ layerId: IMAGE_OBJECT_LAYER_ID2,
4353
4445
  type: "image-item"
4354
4446
  }
4355
4447
  });
@@ -4364,7 +4456,7 @@ var ImageTool = class {
4364
4456
  data: {
4365
4457
  ...obj.data || {},
4366
4458
  id: item.id,
4367
- layerId: IMAGE_OBJECT_LAYER_ID,
4459
+ layerId: IMAGE_OBJECT_LAYER_ID2,
4368
4460
  type: "image-item"
4369
4461
  }
4370
4462
  });
@@ -4416,10 +4508,21 @@ var ImageTool = class {
4416
4508
  const canvasW = this.canvasService.canvas.width || 0;
4417
4509
  const canvasH = this.canvasService.canvas.height || 0;
4418
4510
  const visual = this.getFrameVisualConfig();
4419
- const topH = Math.max(0, frame.top);
4420
- const bottomH = Math.max(0, canvasH - (frame.top + frame.height));
4421
- const leftW = Math.max(0, frame.left);
4422
- const rightW = Math.max(0, canvasW - (frame.left + frame.width));
4511
+ const frameLeft = Math.max(0, Math.min(canvasW, frame.left));
4512
+ const frameTop = Math.max(0, Math.min(canvasH, frame.top));
4513
+ const frameRight = Math.max(
4514
+ frameLeft,
4515
+ Math.min(canvasW, frame.left + frame.width)
4516
+ );
4517
+ const frameBottom = Math.max(
4518
+ frameTop,
4519
+ Math.min(canvasH, frame.top + frame.height)
4520
+ );
4521
+ const visibleFrameH = Math.max(0, frameBottom - frameTop);
4522
+ const topH = frameTop;
4523
+ const bottomH = Math.max(0, canvasH - frameBottom);
4524
+ const leftW = frameLeft;
4525
+ const rightW = Math.max(0, canvasW - frameRight);
4423
4526
  const mask = [
4424
4527
  {
4425
4528
  id: "image.cropMask.top",
@@ -4443,7 +4546,7 @@ var ImageTool = class {
4443
4546
  data: { id: "image.cropMask.bottom", zIndex: 2 },
4444
4547
  props: {
4445
4548
  left: canvasW / 2,
4446
- top: frame.top + frame.height + bottomH / 2,
4549
+ top: frameBottom + bottomH / 2,
4447
4550
  width: canvasW,
4448
4551
  height: bottomH,
4449
4552
  originX: "center",
@@ -4459,9 +4562,9 @@ var ImageTool = class {
4459
4562
  data: { id: "image.cropMask.left", zIndex: 3 },
4460
4563
  props: {
4461
4564
  left: leftW / 2,
4462
- top: frame.top + frame.height / 2,
4565
+ top: frameTop + visibleFrameH / 2,
4463
4566
  width: leftW,
4464
- height: frame.height,
4567
+ height: visibleFrameH,
4465
4568
  originX: "center",
4466
4569
  originY: "center",
4467
4570
  fill: visual.outerBackground,
@@ -4474,10 +4577,10 @@ var ImageTool = class {
4474
4577
  type: "rect",
4475
4578
  data: { id: "image.cropMask.right", zIndex: 4 },
4476
4579
  props: {
4477
- left: frame.left + frame.width + rightW / 2,
4478
- top: frame.top + frame.height / 2,
4580
+ left: frameRight + rightW / 2,
4581
+ top: frameTop + visibleFrameH / 2,
4479
4582
  width: rightW,
4480
- height: frame.height,
4583
+ height: visibleFrameH,
4481
4584
  originX: "center",
4482
4585
  originY: "center",
4483
4586
  fill: visual.outerBackground,
@@ -4567,6 +4670,7 @@ var ImageTool = class {
4567
4670
  if (this.isToolActive) {
4568
4671
  this.updateImages();
4569
4672
  }
4673
+ this.emitWorkingChange(id);
4570
4674
  }
4571
4675
  async updateImageInConfig(id, updates) {
4572
4676
  var _a, _b, _c, _d;
@@ -4647,6 +4751,7 @@ var ImageTool = class {
4647
4751
  this.isImageSelectionActive = true;
4648
4752
  this.focusedImageId = id;
4649
4753
  this.updateImages();
4754
+ this.emitWorkingChange(id);
4650
4755
  }
4651
4756
  focusImageSelection(id) {
4652
4757
  if (!this.canvasService) return;
@@ -4739,6 +4844,7 @@ var ImageTool = class {
4739
4844
  this.hasWorkingChanges = false;
4740
4845
  this.workingItems = this.cloneItems(next);
4741
4846
  this.updateConfig(next);
4847
+ this.emitWorkingChange(focusId);
4742
4848
  if (focusId) {
4743
4849
  this.focusedImageId = focusId;
4744
4850
  this.isImageSelectionActive = true;
@@ -4758,7 +4864,7 @@ var ImageTool = class {
4758
4864
  const width = Math.max(1, Math.round(frame.width * multiplier));
4759
4865
  const height = Math.max(1, Math.round(frame.height * multiplier));
4760
4866
  const el = document.createElement("canvas");
4761
- const tempCanvas = new FabricCanvas(el, {
4867
+ const tempCanvas = new FabricCanvas2(el, {
4762
4868
  renderOnAddRemove: false,
4763
4869
  selection: false,
4764
4870
  enableRetinaScaling: false,
@@ -4768,7 +4874,7 @@ var ImageTool = class {
4768
4874
  const idSet = new Set(imageIds);
4769
4875
  const sourceObjects = this.canvasService.canvas.getObjects().filter((obj) => {
4770
4876
  var _a2, _b2;
4771
- return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID && typeof ((_b2 = obj == null ? void 0 : obj.data) == null ? void 0 : _b2.id) === "string" && idSet.has(obj.data.id);
4877
+ return ((_a2 = obj == null ? void 0 : obj.data) == null ? void 0 : _a2.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_b2 = obj == null ? void 0 : obj.data) == null ? void 0 : _b2.id) === "string" && idSet.has(obj.data.id);
4772
4878
  });
4773
4879
  for (const source of sourceObjects) {
4774
4880
  const clone = await source.clone();
@@ -4815,7 +4921,7 @@ import {
4815
4921
  } from "@pooder/core";
4816
4922
  import { Image as FabricImage2 } from "fabric";
4817
4923
  var WHITE_INK_OBJECT_LAYER_ID = "white-ink.user";
4818
- var IMAGE_OBJECT_LAYER_ID2 = "image.user";
4924
+ var IMAGE_OBJECT_LAYER_ID3 = "image.user";
4819
4925
  var WHITE_INK_DEBUG_KEY = "whiteInk.debug";
4820
4926
  var WHITE_INK_PREVIEW_IMAGE_VISIBLE_KEY = "whiteInk.previewImageVisible";
4821
4927
  var WHITE_INK_DEFAULT_OPACITY = 0.85;
@@ -5516,7 +5622,7 @@ var WhiteInkTool = class {
5516
5622
  const imageIndexes = objects.map(
5517
5623
  (obj, index) => {
5518
5624
  var _a;
5519
- return ((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) === IMAGE_OBJECT_LAYER_ID2 ? index : -1;
5625
+ return ((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) === IMAGE_OBJECT_LAYER_ID3 ? index : -1;
5520
5626
  }
5521
5627
  ).filter((index) => index >= 0);
5522
5628
  if (imageIndexes.length > 0) {
@@ -5549,7 +5655,7 @@ var WhiteInkTool = class {
5549
5655
  let changed = false;
5550
5656
  this.canvasService.canvas.getObjects().forEach((obj) => {
5551
5657
  var _a, _b;
5552
- if (((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) !== IMAGE_OBJECT_LAYER_ID2) return;
5658
+ if (((_a = obj == null ? void 0 : obj.data) == null ? void 0 : _a.layerId) !== IMAGE_OBJECT_LAYER_ID3) return;
5553
5659
  if (obj.visible === visible) return;
5554
5660
  obj.set({ visible });
5555
5661
  (_b = obj.setCoords) == null ? void 0 : _b.call(obj);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/dieline.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  ConfigurationContribution,
7
7
  ConfigurationService,
8
8
  } from "@pooder/core";
9
- import { Path, Pattern } from "fabric";
9
+ import { Canvas as FabricCanvas, Path, Pattern } from "fabric";
10
10
  import CanvasService from "./CanvasService";
11
11
  import { ImageTracer } from "./tracer";
12
12
  import { Unit } from "./coordinate";
@@ -63,6 +63,8 @@ export interface DielineState {
63
63
  pathData?: string;
64
64
  }
65
65
 
66
+ const IMAGE_OBJECT_LAYER_ID = "image.user";
67
+
66
68
  export class DielineTool implements Extension {
67
69
  id = "pooder.kit.dieline";
68
70
  public metadata = {
@@ -424,8 +426,8 @@ export class DielineTool implements Extension {
424
426
  {
425
427
  command: "exportCutImage",
426
428
  title: "Export Cut Image",
427
- handler: () => {
428
- return this.exportCutImage();
429
+ handler: (options?: { debug?: boolean }) => {
430
+ return this.exportCutImage(options);
429
431
  },
430
432
  },
431
433
  {
@@ -824,19 +826,30 @@ export class DielineTool implements Extension {
824
826
  } as DielineGeometry;
825
827
  }
826
828
 
827
- public async exportCutImage() {
828
- if (!this.canvasService) return null;
829
+ public async exportCutImage(options?: { debug?: boolean }) {
830
+ const debug = options?.debug === true;
831
+
832
+ if (!this.canvasService) {
833
+ console.warn("[DielineTool] exportCutImage returned null: canvas-not-ready");
834
+ return null;
835
+ }
829
836
  const configService = this.getConfigService();
830
- if (!configService) return null;
831
- const userLayer = this.canvasService.getLayer("user");
832
- if (!userLayer) return null;
837
+ if (!configService) {
838
+ console.warn(
839
+ "[DielineTool] exportCutImage returned null: config-service-not-ready",
840
+ );
841
+ return null;
842
+ }
833
843
 
834
844
  this.syncSizeState(configService);
835
845
  const sceneLayout = computeSceneLayout(
836
846
  this.canvasService,
837
847
  readSizeState(configService),
838
848
  );
839
- if (!sceneLayout) return null;
849
+ if (!sceneLayout) {
850
+ console.warn("[DielineTool] exportCutImage returned null: scene-layout-null");
851
+ return null;
852
+ }
840
853
 
841
854
  const { shape, radius, features, pathData } = this.state;
842
855
  const canvasW = sceneLayout.canvasWidth || this.canvasService.canvas.width || 800;
@@ -874,24 +887,119 @@ export class DielineTool implements Extension {
874
887
  canvasHeight: canvasH,
875
888
  });
876
889
 
877
- const clonedLayer = await userLayer.clone();
878
890
  const clipPath = new Path(generatedPathData, {
879
- originX: "left",
880
- originY: "top",
881
- left: 0,
882
- top: 0,
891
+ originX: "center",
892
+ originY: "center",
893
+ left: cx,
894
+ top: cy,
883
895
  absolutePositioned: true,
884
896
  });
885
- clonedLayer.clipPath = clipPath;
886
-
887
- const bounds = clipPath.getBoundingRect();
888
- return clonedLayer.toDataURL({
889
- format: "png",
890
- multiplier: 2,
891
- left: bounds.left,
892
- top: bounds.top,
893
- width: bounds.width,
894
- height: bounds.height,
897
+ const pathOffsetX = Number((clipPath as any)?.pathOffset?.x);
898
+ const pathOffsetY = Number((clipPath as any)?.pathOffset?.y);
899
+ const centerX = Number.isFinite(pathOffsetX) ? pathOffsetX : cx;
900
+ const centerY = Number.isFinite(pathOffsetY) ? pathOffsetY : cy;
901
+ clipPath.set({
902
+ originX: "center",
903
+ originY: "center",
904
+ left: centerX,
905
+ top: centerY,
906
+ absolutePositioned: true,
895
907
  });
908
+ clipPath.setCoords();
909
+
910
+ const pathBounds = clipPath.getBoundingRect();
911
+ if (
912
+ !Number.isFinite(pathBounds.left) ||
913
+ !Number.isFinite(pathBounds.top) ||
914
+ !Number.isFinite(pathBounds.width) ||
915
+ !Number.isFinite(pathBounds.height) ||
916
+ pathBounds.width <= 0 ||
917
+ pathBounds.height <= 0
918
+ ) {
919
+ console.warn("[DielineTool] exportCutImage returned null: invalid-cut-bounds", {
920
+ bounds: pathBounds,
921
+ });
922
+ return null;
923
+ }
924
+ const exportBounds = pathBounds;
925
+
926
+ const sourceImages = this.canvasService.canvas.getObjects().filter((obj: any) => {
927
+ return obj?.data?.layerId === IMAGE_OBJECT_LAYER_ID;
928
+ });
929
+ if (!sourceImages.length) {
930
+ console.warn(
931
+ "[DielineTool] exportCutImage returned null: no-image-objects-on-canvas",
932
+ );
933
+ return null;
934
+ }
935
+
936
+ const sourceCanvasWidth = Number(
937
+ this.canvasService.canvas.width || sceneLayout.canvasWidth || canvasW,
938
+ );
939
+ const sourceCanvasHeight = Number(
940
+ this.canvasService.canvas.height || sceneLayout.canvasHeight || canvasH,
941
+ );
942
+
943
+ const el = document.createElement("canvas");
944
+ const exportCanvas = new FabricCanvas(el, {
945
+ renderOnAddRemove: false,
946
+ selection: false,
947
+ enableRetinaScaling: false,
948
+ preserveObjectStacking: true,
949
+ } as any);
950
+ exportCanvas.setDimensions({
951
+ width: Math.max(1, sourceCanvasWidth),
952
+ height: Math.max(1, sourceCanvasHeight),
953
+ });
954
+
955
+ try {
956
+ for (const source of sourceImages as any[]) {
957
+ const clone = await source.clone();
958
+ clone.set({
959
+ selectable: false,
960
+ evented: false,
961
+ });
962
+ clone.setCoords();
963
+ exportCanvas.add(clone);
964
+ }
965
+
966
+ exportCanvas.clipPath = clipPath;
967
+ exportCanvas.renderAll();
968
+
969
+ const dataUrl = exportCanvas.toDataURL({
970
+ format: "png",
971
+ multiplier: 2,
972
+ left: exportBounds.left,
973
+ top: exportBounds.top,
974
+ width: exportBounds.width,
975
+ height: exportBounds.height,
976
+ });
977
+
978
+ if (debug) {
979
+ console.info("[DielineTool] exportCutImage success", {
980
+ sourceCount: sourceImages.length,
981
+ bounds: exportBounds,
982
+ rawPathBounds: pathBounds,
983
+ pathOffset: {
984
+ x: Number.isFinite(pathOffsetX) ? pathOffsetX : null,
985
+ y: Number.isFinite(pathOffsetY) ? pathOffsetY : null,
986
+ },
987
+ clipPathCenter: {
988
+ x: centerX,
989
+ y: centerY,
990
+ },
991
+ cutRect: sceneLayout.cutRect,
992
+ canvasSize: {
993
+ width: Math.max(1, sourceCanvasWidth),
994
+ height: Math.max(1, sourceCanvasHeight),
995
+ },
996
+ });
997
+ }
998
+
999
+ return dataUrl;
1000
+ } finally {
1001
+ exportCanvas.dispose();
1002
+ }
896
1003
  }
1004
+
897
1005
  }