@pooder/kit 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BackgroundTool: () => BackgroundTool,
34
+ CanvasService: () => CanvasService,
34
35
  DielineTool: () => DielineTool,
35
36
  FilmTool: () => FilmTool,
36
37
  HoleTool: () => HoleTool,
@@ -43,118 +44,152 @@ module.exports = __toCommonJS(index_exports);
43
44
 
44
45
  // src/background.ts
45
46
  var import_core = require("@pooder/core");
47
+ var import_fabric = require("fabric");
46
48
  var BackgroundTool = class {
47
- constructor() {
48
- this.name = "BackgroundTool";
49
- this.options = {
50
- color: "",
51
- url: ""
49
+ constructor(options) {
50
+ this.id = "pooder.kit.background";
51
+ this.metadata = {
52
+ name: "BackgroundTool"
52
53
  };
53
- this.schema = {
54
- color: {
55
- type: "color",
56
- label: "Background Color"
57
- },
58
- url: {
59
- type: "string",
60
- label: "Image URL"
61
- }
62
- };
63
- this.commands = {
64
- reset: {
65
- execute: (editor) => {
66
- this.updateBackground(editor, this.options);
67
- return true;
54
+ this.color = "";
55
+ this.url = "";
56
+ if (options) {
57
+ Object.assign(this, options);
58
+ }
59
+ }
60
+ activate(context) {
61
+ this.canvasService = context.services.get("CanvasService");
62
+ if (!this.canvasService) {
63
+ console.warn("CanvasService not found for BackgroundTool");
64
+ return;
65
+ }
66
+ const configService = context.services.get("ConfigurationService");
67
+ if (configService) {
68
+ this.color = configService.get("background.color", this.color);
69
+ this.url = configService.get("background.url", this.url);
70
+ configService.onAnyChange((e) => {
71
+ if (e.key.startsWith("background.")) {
72
+ const prop = e.key.split(".")[1];
73
+ console.log(
74
+ `[BackgroundTool] Config change detected: ${e.key} -> ${e.value}, prop: ${prop}`
75
+ );
76
+ if (prop && prop in this) {
77
+ console.log(
78
+ `[BackgroundTool] Updating option ${prop} to ${e.value}`
79
+ );
80
+ this[prop] = e.value;
81
+ this.updateBackground();
82
+ } else {
83
+ console.warn(
84
+ `[BackgroundTool] Property ${prop} not found in options`
85
+ );
86
+ }
68
87
  }
69
- },
70
- clear: {
71
- execute: (editor) => {
72
- this.options = {
73
- color: "transparent",
74
- url: ""
75
- };
76
- this.updateBackground(editor, this.options);
77
- return true;
88
+ });
89
+ }
90
+ this.initLayer();
91
+ this.updateBackground();
92
+ }
93
+ deactivate(context) {
94
+ if (this.canvasService) {
95
+ const layer = this.canvasService.getLayer("background");
96
+ if (layer) {
97
+ this.canvasService.canvas.remove(layer);
98
+ }
99
+ this.canvasService = void 0;
100
+ }
101
+ }
102
+ contribute() {
103
+ return {
104
+ [import_core.ContributionPointIds.CONFIGURATIONS]: [
105
+ {
106
+ id: "background.color",
107
+ type: "color",
108
+ label: "Background Color",
109
+ default: ""
110
+ },
111
+ {
112
+ id: "background.url",
113
+ type: "string",
114
+ label: "Image URL",
115
+ default: ""
78
116
  }
79
- },
80
- setBackgroundColor: {
81
- execute: (editor, color) => {
82
- if (this.options.color === color) return true;
83
- this.options.color = color;
84
- this.updateBackground(editor, this.options);
85
- return true;
117
+ ],
118
+ [import_core.ContributionPointIds.COMMANDS]: [
119
+ {
120
+ command: "reset",
121
+ title: "Reset Background",
122
+ handler: () => {
123
+ this.updateBackground();
124
+ return true;
125
+ }
86
126
  },
87
- schema: {
88
- color: {
89
- type: "string",
90
- // Should be 'color' if supported by CommandArgSchema, but using 'string' for now as per previous plan
91
- label: "Background Color",
92
- required: true
127
+ {
128
+ command: "clear",
129
+ title: "Clear Background",
130
+ handler: () => {
131
+ this.color = "transparent";
132
+ this.url = "";
133
+ this.updateBackground();
134
+ return true;
93
135
  }
94
- }
95
- },
96
- setBackgroundImage: {
97
- execute: (editor, url) => {
98
- if (this.options.url === url) return true;
99
- this.options.url = url;
100
- this.updateBackground(editor, this.options);
101
- return true;
102
136
  },
103
- schema: {
104
- url: {
105
- type: "string",
106
- label: "Image URL",
107
- required: true
137
+ {
138
+ command: "setBackgroundColor",
139
+ title: "Set Background Color",
140
+ handler: (color) => {
141
+ if (this.color === color) return true;
142
+ this.color = color;
143
+ this.updateBackground();
144
+ return true;
145
+ }
146
+ },
147
+ {
148
+ command: "setBackgroundImage",
149
+ title: "Set Background Image",
150
+ handler: (url) => {
151
+ if (this.url === url) return true;
152
+ this.url = url;
153
+ this.updateBackground();
154
+ return true;
108
155
  }
109
156
  }
110
- }
157
+ ]
111
158
  };
112
159
  }
113
- initLayer(editor) {
114
- let backgroundLayer = editor.getLayer("background");
160
+ initLayer() {
161
+ if (!this.canvasService) return;
162
+ let backgroundLayer = this.canvasService.getLayer("background");
115
163
  if (!backgroundLayer) {
116
- backgroundLayer = new import_core.PooderLayer([], {
117
- width: editor.canvas.width,
118
- height: editor.canvas.height,
164
+ backgroundLayer = this.canvasService.createLayer("background", {
165
+ width: this.canvasService.canvas.width,
166
+ height: this.canvasService.canvas.height,
119
167
  selectable: false,
120
- evented: false,
121
- data: {
122
- id: "background"
123
- }
168
+ evented: false
124
169
  });
125
- editor.canvas.add(backgroundLayer);
126
- editor.canvas.sendObjectToBack(backgroundLayer);
127
- }
128
- }
129
- onMount(editor) {
130
- this.initLayer(editor);
131
- this.updateBackground(editor, this.options);
132
- }
133
- onUnmount(editor) {
134
- const layer = editor.getLayer("background");
135
- if (layer) {
136
- editor.canvas.remove(layer);
170
+ this.canvasService.canvas.sendObjectToBack(backgroundLayer);
137
171
  }
138
172
  }
139
- onUpdate(editor, state) {
140
- this.updateBackground(editor, this.options);
141
- }
142
- async updateBackground(editor, options) {
143
- const layer = editor.getLayer("background");
173
+ async updateBackground() {
174
+ if (!this.canvasService) return;
175
+ const layer = this.canvasService.getLayer("background");
144
176
  if (!layer) {
145
177
  console.warn("[BackgroundTool] Background layer not found");
146
178
  return;
147
179
  }
148
- const { color, url } = options;
149
- const width = editor.state.width;
150
- const height = editor.state.height;
151
- let rect = editor.getObject("background-color-rect", "background");
180
+ const { color, url } = this;
181
+ const width = this.canvasService.canvas.width || 800;
182
+ const height = this.canvasService.canvas.height || 600;
183
+ let rect = this.canvasService.getObject(
184
+ "background-color-rect",
185
+ "background"
186
+ );
152
187
  if (rect) {
153
188
  rect.set({
154
189
  fill: color
155
190
  });
156
191
  } else {
157
- rect = new import_core.Rect({
192
+ rect = new import_fabric.Rect({
158
193
  width,
159
194
  height,
160
195
  fill: color,
@@ -167,7 +202,10 @@ var BackgroundTool = class {
167
202
  layer.add(rect);
168
203
  layer.sendObjectToBack(rect);
169
204
  }
170
- let img = editor.getObject("background-image", "background");
205
+ let img = this.canvasService.getObject(
206
+ "background-image",
207
+ "background"
208
+ );
171
209
  try {
172
210
  if (img) {
173
211
  if (img.getSrc() !== url) {
@@ -179,7 +217,7 @@ var BackgroundTool = class {
179
217
  }
180
218
  } else {
181
219
  if (url) {
182
- img = await import_core.Image.fromURL(url, { crossOrigin: "anonymous" });
220
+ img = await import_fabric.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
183
221
  img.set({
184
222
  originX: "left",
185
223
  originY: "top",
@@ -196,25 +234,253 @@ var BackgroundTool = class {
196
234
  layer.add(img);
197
235
  }
198
236
  }
199
- editor.canvas.requestRenderAll();
237
+ this.canvasService.requestRenderAll();
200
238
  } catch (e) {
201
239
  console.error("[BackgroundTool] Failed to load image", e);
202
240
  }
241
+ layer.dirty = true;
242
+ this.canvasService.requestRenderAll();
203
243
  }
204
244
  };
205
245
 
206
246
  // src/dieline.ts
207
247
  var import_core2 = require("@pooder/core");
248
+ var import_fabric2 = require("fabric");
249
+
250
+ // src/tracer.ts
251
+ var ImageTracer = class {
252
+ /**
253
+ * Main entry point: Traces an image URL to an SVG path string.
254
+ * @param imageUrl The URL or Base64 string of the image.
255
+ * @param options Configuration options.
256
+ */
257
+ static async trace(imageUrl, options = {}) {
258
+ var _a, _b;
259
+ const img = await this.loadImage(imageUrl);
260
+ const width = img.width;
261
+ const height = img.height;
262
+ const canvas = document.createElement("canvas");
263
+ canvas.width = width;
264
+ canvas.height = height;
265
+ const ctx = canvas.getContext("2d");
266
+ if (!ctx) throw new Error("Could not get 2D context");
267
+ ctx.drawImage(img, 0, 0);
268
+ const imageData = ctx.getImageData(0, 0, width, height);
269
+ const points = this.marchingSquares(imageData, (_a = options.threshold) != null ? _a : 10);
270
+ let finalPoints = points;
271
+ if (options.scaleToWidth && options.scaleToHeight && points.length > 0) {
272
+ finalPoints = this.scalePoints(
273
+ points,
274
+ options.scaleToWidth,
275
+ options.scaleToHeight
276
+ );
277
+ }
278
+ const simplifiedPoints = this.douglasPeucker(
279
+ finalPoints,
280
+ (_b = options.simplifyTolerance) != null ? _b : 0.5
281
+ );
282
+ return this.pointsToSVG(simplifiedPoints);
283
+ }
284
+ static loadImage(url) {
285
+ return new Promise((resolve, reject) => {
286
+ const img = new Image();
287
+ img.crossOrigin = "Anonymous";
288
+ img.onload = () => resolve(img);
289
+ img.onerror = (e) => reject(e);
290
+ img.src = url;
291
+ });
292
+ }
293
+ /**
294
+ * Moore-Neighbor Tracing Algorithm
295
+ * More robust for irregular shapes than simple Marching Squares walker.
296
+ */
297
+ static marchingSquares(imageData, alphaThreshold) {
298
+ const width = imageData.width;
299
+ const height = imageData.height;
300
+ const data = imageData.data;
301
+ const isSolid = (x, y) => {
302
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
303
+ const index = (y * width + x) * 4;
304
+ const r = data[index];
305
+ const g = data[index + 1];
306
+ const b = data[index + 2];
307
+ const a = data[index + 3];
308
+ if (a <= alphaThreshold) return false;
309
+ if (r > 240 && g > 240 && b > 240) return false;
310
+ return true;
311
+ };
312
+ let startX = -1;
313
+ let startY = -1;
314
+ searchLoop: for (let y = 0; y < height; y++) {
315
+ for (let x = 0; x < width; x++) {
316
+ if (isSolid(x, y)) {
317
+ startX = x;
318
+ startY = y;
319
+ break searchLoop;
320
+ }
321
+ }
322
+ }
323
+ if (startX === -1) return [];
324
+ const points = [];
325
+ let cx = startX;
326
+ let cy = startY;
327
+ const neighbors = [
328
+ { x: 0, y: -1 },
329
+ { x: 1, y: -1 },
330
+ { x: 1, y: 0 },
331
+ { x: 1, y: 1 },
332
+ { x: 0, y: 1 },
333
+ { x: -1, y: 1 },
334
+ { x: -1, y: 0 },
335
+ { x: -1, y: -1 }
336
+ ];
337
+ let backtrack = 6;
338
+ const maxSteps = width * height * 3;
339
+ let steps = 0;
340
+ do {
341
+ points.push({ x: cx, y: cy });
342
+ let found = false;
343
+ for (let i = 0; i < 8; i++) {
344
+ const idx = (backtrack + 1 + i) % 8;
345
+ const nx = cx + neighbors[idx].x;
346
+ const ny = cy + neighbors[idx].y;
347
+ if (isSolid(nx, ny)) {
348
+ cx = nx;
349
+ cy = ny;
350
+ backtrack = (idx + 4) % 8;
351
+ backtrack = (idx + 4 + 1) % 8;
352
+ backtrack = (idx + 4 + 1) % 8;
353
+ found = true;
354
+ break;
355
+ }
356
+ }
357
+ if (!found) {
358
+ break;
359
+ }
360
+ steps++;
361
+ } while ((cx !== startX || cy !== startY) && steps < maxSteps);
362
+ return points;
363
+ }
364
+ /**
365
+ * Douglas-Peucker Line Simplification
366
+ */
367
+ static douglasPeucker(points, tolerance) {
368
+ if (points.length <= 2) return points;
369
+ const sqTolerance = tolerance * tolerance;
370
+ let maxSqDist = 0;
371
+ let index = 0;
372
+ const first = points[0];
373
+ const last = points[points.length - 1];
374
+ for (let i = 1; i < points.length - 1; i++) {
375
+ const sqDist = this.getSqSegDist(points[i], first, last);
376
+ if (sqDist > maxSqDist) {
377
+ index = i;
378
+ maxSqDist = sqDist;
379
+ }
380
+ }
381
+ if (maxSqDist > sqTolerance) {
382
+ const left = this.douglasPeucker(points.slice(0, index + 1), tolerance);
383
+ const right = this.douglasPeucker(points.slice(index), tolerance);
384
+ return left.slice(0, left.length - 1).concat(right);
385
+ } else {
386
+ return [first, last];
387
+ }
388
+ }
389
+ static getSqSegDist(p, p1, p2) {
390
+ let x = p1.x;
391
+ let y = p1.y;
392
+ let dx = p2.x - x;
393
+ let dy = p2.y - y;
394
+ if (dx !== 0 || dy !== 0) {
395
+ const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
396
+ if (t > 1) {
397
+ x = p2.x;
398
+ y = p2.y;
399
+ } else if (t > 0) {
400
+ x += dx * t;
401
+ y += dy * t;
402
+ }
403
+ }
404
+ dx = p.x - x;
405
+ dy = p.y - y;
406
+ return dx * dx + dy * dy;
407
+ }
408
+ static scalePoints(points, targetWidth, targetHeight) {
409
+ if (points.length === 0) return points;
410
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
411
+ for (const p of points) {
412
+ if (p.x < minX) minX = p.x;
413
+ if (p.y < minY) minY = p.y;
414
+ if (p.x > maxX) maxX = p.x;
415
+ if (p.y > maxY) maxY = p.y;
416
+ }
417
+ const srcW = maxX - minX;
418
+ const srcH = maxY - minY;
419
+ if (srcW === 0 || srcH === 0) return points;
420
+ const scaleX = targetWidth / srcW;
421
+ const scaleY = targetHeight / srcH;
422
+ return points.map((p) => ({
423
+ x: (p.x - minX) * scaleX,
424
+ y: (p.y - minY) * scaleY
425
+ }));
426
+ }
427
+ static pointsToSVG(points) {
428
+ if (points.length === 0) return "";
429
+ const head = points[0];
430
+ const tail = points.slice(1);
431
+ return `M ${head.x} ${head.y} ` + tail.map((p) => `L ${p.x} ${p.y}`).join(" ") + " Z";
432
+ }
433
+ };
434
+
435
+ // src/coordinate.ts
436
+ var Coordinate = class {
437
+ /**
438
+ * Convert an absolute value to a normalized value (0-1).
439
+ * @param value Absolute value (e.g., pixels)
440
+ * @param total Total dimension size (e.g., canvas width)
441
+ */
442
+ static toNormalized(value, total) {
443
+ return total === 0 ? 0 : value / total;
444
+ }
445
+ /**
446
+ * Convert a normalized value (0-1) to an absolute value.
447
+ * @param normalized Normalized value (0-1)
448
+ * @param total Total dimension size (e.g., canvas width)
449
+ */
450
+ static toAbsolute(normalized, total) {
451
+ return normalized * total;
452
+ }
453
+ /**
454
+ * Normalize a point's coordinates.
455
+ */
456
+ static normalizePoint(point, size) {
457
+ return {
458
+ x: this.toNormalized(point.x, size.width),
459
+ y: this.toNormalized(point.y, size.height)
460
+ };
461
+ }
462
+ /**
463
+ * Denormalize a point's coordinates to absolute pixels.
464
+ */
465
+ static denormalizePoint(point, size) {
466
+ return {
467
+ x: this.toAbsolute(point.x, size.width),
468
+ y: this.toAbsolute(point.y, size.height)
469
+ };
470
+ }
471
+ };
208
472
 
209
473
  // src/geometry.ts
210
474
  var import_paper = __toESM(require("paper"));
211
475
  function ensurePaper(width, height) {
212
476
  if (!import_paper.default.project) {
213
477
  import_paper.default.setup(new import_paper.default.Size(width, height));
478
+ } else {
479
+ import_paper.default.view.viewSize = new import_paper.default.Size(width, height);
214
480
  }
215
481
  }
216
482
  function createBaseShape(options) {
217
- const { shape, width, height, radius, x, y } = options;
483
+ const { shape, width, height, radius, x, y, pathData } = options;
218
484
  const center = new import_paper.default.Point(x, y);
219
485
  if (shape === "rect") {
220
486
  return new import_paper.default.Path.Rectangle({
@@ -228,11 +494,24 @@ function createBaseShape(options) {
228
494
  center,
229
495
  radius: Math.max(0, r)
230
496
  });
231
- } else {
497
+ } else if (shape === "ellipse") {
232
498
  return new import_paper.default.Path.Ellipse({
233
499
  center,
234
500
  radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
235
501
  });
502
+ } else if (shape === "custom" && pathData) {
503
+ const path = new import_paper.default.Path();
504
+ path.pathData = pathData;
505
+ path.position = center;
506
+ if (width > 0 && height > 0 && path.bounds.width > 0 && path.bounds.height > 0) {
507
+ path.scale(width / path.bounds.width, height / path.bounds.height);
508
+ }
509
+ return path;
510
+ } else {
511
+ return new import_paper.default.Path.Rectangle({
512
+ point: [x - width / 2, y - height / 2],
513
+ size: [Math.max(0, width), Math.max(0, height)]
514
+ });
236
515
  }
237
516
  }
238
517
  function getDielineShape(options) {
@@ -246,10 +525,6 @@ function getDielineShape(options) {
246
525
  center: [hole.x, hole.y],
247
526
  radius: hole.outerRadius
248
527
  });
249
- if (!mainShape.intersects(lug) && !mainShape.contains(lug.position)) {
250
- lug.remove();
251
- return;
252
- }
253
528
  const cut = new import_paper.default.Path.Circle({
254
529
  center: [hole.x, hole.y],
255
530
  radius: hole.innerRadius
@@ -257,31 +532,49 @@ function getDielineShape(options) {
257
532
  if (!lugsPath) {
258
533
  lugsPath = lug;
259
534
  } else {
260
- const temp = lugsPath.unite(lug);
261
- lugsPath.remove();
262
- lug.remove();
263
- lugsPath = temp;
535
+ try {
536
+ const temp = lugsPath.unite(lug);
537
+ lugsPath.remove();
538
+ lug.remove();
539
+ lugsPath = temp;
540
+ } catch (e) {
541
+ console.error("Geometry: Failed to unite lug", e);
542
+ lug.remove();
543
+ }
264
544
  }
265
545
  if (!cutsPath) {
266
546
  cutsPath = cut;
267
547
  } else {
268
- const temp = cutsPath.unite(cut);
269
- cutsPath.remove();
270
- cut.remove();
271
- cutsPath = temp;
548
+ try {
549
+ const temp = cutsPath.unite(cut);
550
+ cutsPath.remove();
551
+ cut.remove();
552
+ cutsPath = temp;
553
+ } catch (e) {
554
+ console.error("Geometry: Failed to unite cut", e);
555
+ cut.remove();
556
+ }
272
557
  }
273
558
  });
274
559
  if (lugsPath) {
275
- const temp = mainShape.unite(lugsPath);
276
- mainShape.remove();
277
- lugsPath.remove();
278
- mainShape = temp;
560
+ try {
561
+ const temp = mainShape.unite(lugsPath);
562
+ mainShape.remove();
563
+ lugsPath.remove();
564
+ mainShape = temp;
565
+ } catch (e) {
566
+ console.error("Geometry: Failed to unite lugsPath to mainShape", e);
567
+ }
279
568
  }
280
569
  if (cutsPath) {
281
- const temp = mainShape.subtract(cutsPath);
282
- mainShape.remove();
283
- cutsPath.remove();
284
- mainShape = temp;
570
+ try {
571
+ const temp = mainShape.subtract(cutsPath);
572
+ mainShape.remove();
573
+ cutsPath.remove();
574
+ mainShape = temp;
575
+ } catch (e) {
576
+ console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
577
+ }
285
578
  }
286
579
  }
287
580
  return mainShape;
@@ -315,13 +608,37 @@ function generateBleedZonePath(options, offset) {
315
608
  ensurePaper(maxDim, maxDim);
316
609
  import_paper.default.project.activeLayer.removeChildren();
317
610
  const shapeOriginal = getDielineShape(options);
318
- const offsetOptions = {
319
- ...options,
320
- width: Math.max(0, options.width + offset * 2),
321
- height: Math.max(0, options.height + offset * 2),
322
- radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset)
323
- };
324
- const shapeOffset = getDielineShape(offsetOptions);
611
+ let shapeOffset;
612
+ if (options.shape === "custom") {
613
+ const stroker = shapeOriginal.clone();
614
+ stroker.strokeColor = new import_paper.default.Color("black");
615
+ stroker.strokeWidth = Math.abs(offset) * 2;
616
+ stroker.strokeJoin = "round";
617
+ stroker.strokeCap = "round";
618
+ let expanded;
619
+ try {
620
+ expanded = stroker.expand({ stroke: true, fill: false, insert: false });
621
+ } catch (e) {
622
+ stroker.remove();
623
+ shapeOffset = shapeOriginal.clone();
624
+ return shapeOffset.pathData;
625
+ }
626
+ stroker.remove();
627
+ if (offset > 0) {
628
+ shapeOffset = shapeOriginal.unite(expanded);
629
+ } else {
630
+ shapeOffset = shapeOriginal.subtract(expanded);
631
+ }
632
+ expanded.remove();
633
+ } else {
634
+ const offsetOptions = {
635
+ ...options,
636
+ width: Math.max(0, options.width + offset * 2),
637
+ height: Math.max(0, options.height + offset * 2),
638
+ radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset)
639
+ };
640
+ shapeOffset = getDielineShape(offsetOptions);
641
+ }
325
642
  let bleedZone;
326
643
  if (offset > 0) {
327
644
  bleedZone = shapeOffset.subtract(shapeOriginal);
@@ -344,256 +661,301 @@ function getNearestPointOnDieline(point, options) {
344
661
  shape.remove();
345
662
  return result;
346
663
  }
664
+ function getPathBounds(pathData) {
665
+ const path = new import_paper.default.Path();
666
+ path.pathData = pathData;
667
+ const bounds = path.bounds;
668
+ path.remove();
669
+ return { width: bounds.width, height: bounds.height };
670
+ }
347
671
 
348
672
  // src/dieline.ts
349
673
  var DielineTool = class {
350
- constructor() {
351
- this.name = "DielineTool";
352
- this.options = {
353
- shape: "rect",
354
- width: 300,
355
- height: 300,
356
- radius: 0,
357
- offset: 0,
358
- style: "solid",
359
- insideColor: "rgba(0,0,0,0)",
360
- outsideColor: "#ffffff",
361
- showBleedLines: true
362
- };
363
- this.schema = {
364
- shape: {
365
- type: "select",
366
- options: ["rect", "circle", "ellipse"],
367
- label: "Shape"
368
- },
369
- width: { type: "number", min: 10, max: 2e3, label: "Width" },
370
- height: { type: "number", min: 10, max: 2e3, label: "Height" },
371
- radius: { type: "number", min: 0, max: 500, label: "Corner Radius" },
372
- position: { type: "string", label: "Position" },
373
- // Complex object, simplified for now or need custom handler
374
- borderLength: { type: "number", min: 0, max: 500, label: "Margin" },
375
- offset: { type: "number", min: -100, max: 100, label: "Bleed Offset" },
376
- showBleedLines: { type: "boolean", label: "Show Bleed Lines" },
377
- style: {
378
- type: "select",
379
- options: ["solid", "dashed"],
380
- label: "Line Style"
381
- },
382
- insideColor: { type: "color", label: "Inside Color" },
383
- outsideColor: { type: "color", label: "Outside Color" }
674
+ constructor(options) {
675
+ this.id = "pooder.kit.dieline";
676
+ this.metadata = {
677
+ name: "DielineTool"
384
678
  };
385
- this.commands = {
386
- reset: {
387
- execute: (editor) => {
388
- this.options = {
389
- shape: "rect",
390
- width: 300,
391
- height: 300,
392
- radius: 0,
393
- offset: 0,
394
- style: "solid",
395
- insideColor: "rgba(0,0,0,0)",
396
- outsideColor: "#ffffff",
397
- showBleedLines: true
398
- };
399
- this.updateDieline(editor);
400
- return true;
679
+ this.shape = "rect";
680
+ this.width = 500;
681
+ this.height = 500;
682
+ this.radius = 0;
683
+ this.offset = 0;
684
+ this.style = "solid";
685
+ this.insideColor = "rgba(0,0,0,0)";
686
+ this.outsideColor = "#ffffff";
687
+ this.showBleedLines = true;
688
+ this.holes = [];
689
+ if (options) {
690
+ Object.assign(this, options);
691
+ }
692
+ }
693
+ activate(context) {
694
+ this.context = context;
695
+ this.canvasService = context.services.get("CanvasService");
696
+ if (!this.canvasService) {
697
+ console.warn("CanvasService not found for DielineTool");
698
+ return;
699
+ }
700
+ const configService = context.services.get("ConfigurationService");
701
+ if (configService) {
702
+ this.shape = configService.get("dieline.shape", this.shape);
703
+ this.width = configService.get("dieline.width", this.width);
704
+ this.height = configService.get("dieline.height", this.height);
705
+ this.radius = configService.get("dieline.radius", this.radius);
706
+ this.borderLength = configService.get(
707
+ "dieline.borderLength",
708
+ this.borderLength
709
+ );
710
+ this.offset = configService.get("dieline.offset", this.offset);
711
+ this.style = configService.get("dieline.style", this.style);
712
+ this.insideColor = configService.get(
713
+ "dieline.insideColor",
714
+ this.insideColor
715
+ );
716
+ this.outsideColor = configService.get(
717
+ "dieline.outsideColor",
718
+ this.outsideColor
719
+ );
720
+ this.showBleedLines = configService.get(
721
+ "dieline.showBleedLines",
722
+ this.showBleedLines
723
+ );
724
+ this.holes = configService.get("dieline.holes", this.holes);
725
+ this.pathData = configService.get("dieline.pathData", this.pathData);
726
+ configService.onAnyChange((e) => {
727
+ if (e.key.startsWith("dieline.")) {
728
+ const prop = e.key.split(".")[1];
729
+ console.log(
730
+ `[DielineTool] Config change detected: ${e.key} -> ${e.value}`
731
+ );
732
+ if (prop && prop in this) {
733
+ this[prop] = e.value;
734
+ this.updateDieline();
735
+ }
401
736
  }
402
- },
403
- destroy: {
404
- execute: (editor) => {
405
- this.destroyLayer(editor);
406
- return true;
737
+ });
738
+ }
739
+ this.createLayer();
740
+ this.updateDieline();
741
+ }
742
+ deactivate(context) {
743
+ this.destroyLayer();
744
+ this.canvasService = void 0;
745
+ this.context = void 0;
746
+ }
747
+ contribute() {
748
+ return {
749
+ [import_core2.ContributionPointIds.CONFIGURATIONS]: [
750
+ {
751
+ id: "dieline.shape",
752
+ type: "select",
753
+ label: "Shape",
754
+ options: ["rect", "circle", "ellipse", "custom"],
755
+ default: this.shape
756
+ },
757
+ {
758
+ id: "dieline.width",
759
+ type: "number",
760
+ label: "Width",
761
+ min: 10,
762
+ max: 2e3,
763
+ default: this.width
764
+ },
765
+ {
766
+ id: "dieline.height",
767
+ type: "number",
768
+ label: "Height",
769
+ min: 10,
770
+ max: 2e3,
771
+ default: this.height
772
+ },
773
+ {
774
+ id: "dieline.radius",
775
+ type: "number",
776
+ label: "Corner Radius",
777
+ min: 0,
778
+ max: 500,
779
+ default: this.radius
780
+ },
781
+ {
782
+ id: "dieline.position",
783
+ type: "json",
784
+ label: "Position (Normalized)",
785
+ default: this.position
786
+ },
787
+ {
788
+ id: "dieline.borderLength",
789
+ type: "number",
790
+ label: "Margin",
791
+ min: 0,
792
+ max: 500,
793
+ default: this.borderLength
794
+ },
795
+ {
796
+ id: "dieline.offset",
797
+ type: "number",
798
+ label: "Bleed Offset",
799
+ min: -100,
800
+ max: 100,
801
+ default: this.offset
802
+ },
803
+ {
804
+ id: "dieline.showBleedLines",
805
+ type: "boolean",
806
+ label: "Show Bleed Lines",
807
+ default: this.showBleedLines
808
+ },
809
+ {
810
+ id: "dieline.style",
811
+ type: "select",
812
+ label: "Line Style",
813
+ options: ["solid", "dashed"],
814
+ default: this.style
815
+ },
816
+ {
817
+ id: "dieline.insideColor",
818
+ type: "color",
819
+ label: "Inside Color",
820
+ default: this.insideColor
821
+ },
822
+ {
823
+ id: "dieline.outsideColor",
824
+ type: "color",
825
+ label: "Outside Color",
826
+ default: this.outsideColor
827
+ },
828
+ {
829
+ id: "dieline.holes",
830
+ type: "json",
831
+ label: "Holes",
832
+ default: this.holes
407
833
  }
408
- },
409
- setDimensions: {
410
- execute: (editor, width, height) => {
411
- if (this.options.width === width && this.options.height === height)
834
+ ],
835
+ [import_core2.ContributionPointIds.COMMANDS]: [
836
+ {
837
+ command: "reset",
838
+ title: "Reset Dieline",
839
+ handler: () => {
840
+ this.shape = "rect";
841
+ this.width = 300;
842
+ this.height = 300;
843
+ this.radius = 0;
844
+ this.offset = 0;
845
+ this.style = "solid";
846
+ this.insideColor = "rgba(0,0,0,0)";
847
+ this.outsideColor = "#ffffff";
848
+ this.showBleedLines = true;
849
+ this.holes = [];
850
+ this.pathData = void 0;
851
+ this.updateDieline();
412
852
  return true;
413
- this.options.width = width;
414
- this.options.height = height;
415
- this.updateDieline(editor);
416
- return true;
853
+ }
417
854
  },
418
- schema: {
419
- width: {
420
- type: "number",
421
- label: "Width",
422
- min: 10,
423
- max: 2e3,
424
- required: true
425
- },
426
- height: {
427
- type: "number",
428
- label: "Height",
429
- min: 10,
430
- max: 2e3,
431
- required: true
855
+ {
856
+ command: "setDimensions",
857
+ title: "Set Dimensions",
858
+ handler: (width, height) => {
859
+ if (this.width === width && this.height === height) return true;
860
+ this.width = width;
861
+ this.height = height;
862
+ this.updateDieline();
863
+ return true;
432
864
  }
433
- }
434
- },
435
- setShape: {
436
- execute: (editor, shape) => {
437
- if (this.options.shape === shape) return true;
438
- this.options.shape = shape;
439
- this.updateDieline(editor);
440
- return true;
441
865
  },
442
- schema: {
443
- shape: {
444
- type: "string",
445
- label: "Shape",
446
- options: ["rect", "circle", "ellipse"],
447
- required: true
866
+ {
867
+ command: "setShape",
868
+ title: "Set Shape",
869
+ handler: (shape) => {
870
+ if (this.shape === shape) return true;
871
+ this.shape = shape;
872
+ this.updateDieline();
873
+ return true;
448
874
  }
449
- }
450
- },
451
- setBleed: {
452
- execute: (editor, bleed) => {
453
- if (this.options.offset === bleed) return true;
454
- this.options.offset = bleed;
455
- this.updateDieline(editor);
456
- return true;
457
875
  },
458
- schema: {
459
- bleed: {
460
- type: "number",
461
- label: "Bleed",
462
- min: -100,
463
- max: 100,
464
- required: true
876
+ {
877
+ command: "setBleed",
878
+ title: "Set Bleed",
879
+ handler: (bleed) => {
880
+ if (this.offset === bleed) return true;
881
+ this.offset = bleed;
882
+ this.updateDieline();
883
+ return true;
884
+ }
885
+ },
886
+ {
887
+ command: "setHoles",
888
+ title: "Set Holes",
889
+ handler: (holes) => {
890
+ this.holes = holes;
891
+ this.updateDieline(false);
892
+ return true;
893
+ }
894
+ },
895
+ {
896
+ command: "getGeometry",
897
+ title: "Get Geometry",
898
+ handler: () => {
899
+ return this.getGeometry();
900
+ }
901
+ },
902
+ {
903
+ command: "exportCutImage",
904
+ title: "Export Cut Image",
905
+ handler: () => {
906
+ return this.exportCutImage();
907
+ }
908
+ },
909
+ {
910
+ command: "detectEdge",
911
+ title: "Detect Edge from Image",
912
+ handler: async (imageUrl, options) => {
913
+ try {
914
+ const pathData = await ImageTracer.trace(imageUrl, options);
915
+ const bounds = getPathBounds(pathData);
916
+ const currentMax = Math.max(this.width, this.height);
917
+ const scale = currentMax / Math.max(bounds.width, bounds.height);
918
+ this.width = bounds.width * scale;
919
+ this.height = bounds.height * scale;
920
+ this.shape = "custom";
921
+ this.pathData = pathData;
922
+ this.updateDieline();
923
+ return pathData;
924
+ } catch (e) {
925
+ console.error("Edge detection failed", e);
926
+ throw e;
927
+ }
465
928
  }
466
929
  }
467
- },
468
- exportCutImage: {
469
- execute: (editor) => {
470
- var _a, _b, _c, _d;
471
- const { shape, width, height, radius, position } = this.options;
472
- const canvasW = editor.canvas.width || 800;
473
- const canvasH = editor.canvas.height || 600;
474
- const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
475
- const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
476
- const holeTool = editor.getExtension("HoleTool");
477
- const holes = holeTool ? holeTool.options.holes || [] : [];
478
- const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
479
- const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
480
- const holeData = holes.map((h) => ({
481
- x: h.x,
482
- y: h.y,
483
- innerRadius,
484
- outerRadius
485
- }));
486
- const pathData = generateDielinePath({
487
- shape,
488
- width,
489
- height,
490
- radius,
491
- x: cx,
492
- y: cy,
493
- holes: holeData
494
- });
495
- const clipPath = new import_core2.Path(pathData, {
496
- left: 0,
497
- top: 0,
498
- originX: "left",
499
- originY: "top",
500
- absolutePositioned: true
501
- });
502
- const layer = this.getLayer(editor, "dieline-overlay");
503
- const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
504
- if (layer) layer.visible = false;
505
- const holeMarkers = editor.canvas.getObjects().filter((o) => {
506
- var _a2;
507
- return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
508
- });
509
- holeMarkers.forEach((o) => o.visible = false);
510
- const rulerLayer = editor.canvas.getObjects().find((obj) => {
511
- var _a2;
512
- return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
513
- });
514
- const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
515
- if (rulerLayer) rulerLayer.visible = false;
516
- const originalClip = editor.canvas.clipPath;
517
- editor.canvas.clipPath = clipPath;
518
- const bbox = clipPath.getBoundingRect();
519
- const holeDataRelative = holes.map((h) => ({
520
- x: h.x - bbox.left,
521
- y: h.y - bbox.top,
522
- innerRadius,
523
- outerRadius
524
- }));
525
- const clipPathCorrected = new import_core2.Path(pathData, {
526
- absolutePositioned: true,
527
- left: 0,
528
- top: 0
529
- });
530
- const tempPath = new import_core2.Path(pathData);
531
- const tempBounds = tempPath.getBoundingRect();
532
- clipPathCorrected.set({
533
- left: tempBounds.left,
534
- top: tempBounds.top,
535
- originX: "left",
536
- originY: "top"
537
- });
538
- editor.canvas.clipPath = clipPathCorrected;
539
- const exportBbox = clipPathCorrected.getBoundingRect();
540
- const dataURL = editor.canvas.toDataURL({
541
- format: "png",
542
- multiplier: 2,
543
- left: exportBbox.left,
544
- top: exportBbox.top,
545
- width: exportBbox.width,
546
- height: exportBbox.height
547
- });
548
- editor.canvas.clipPath = originalClip;
549
- if (layer) layer.visible = wasVisible;
550
- if (rulerLayer) rulerLayer.visible = rulerWasVisible;
551
- holeMarkers.forEach((o) => o.visible = true);
552
- editor.canvas.requestRenderAll();
553
- return dataURL;
554
- }
555
- }
930
+ ]
556
931
  };
557
932
  }
558
- onMount(editor) {
559
- this.createLayer(editor);
560
- this.updateDieline(editor);
561
- }
562
- onUnmount(editor) {
563
- this.destroyLayer(editor);
564
- }
565
- onUpdate(editor, state) {
566
- this.updateDieline(editor);
933
+ getLayer() {
934
+ var _a;
935
+ return (_a = this.canvasService) == null ? void 0 : _a.getLayer("dieline-overlay");
567
936
  }
568
- onDestroy(editor) {
569
- this.destroyLayer(editor);
570
- }
571
- getLayer(editor, id) {
572
- return editor.canvas.getObjects().find((obj) => {
573
- var _a;
574
- return ((_a = obj.data) == null ? void 0 : _a.id) === id;
937
+ createLayer() {
938
+ if (!this.canvasService) return;
939
+ const width = this.canvasService.canvas.width || 800;
940
+ const height = this.canvasService.canvas.height || 600;
941
+ const layer = this.canvasService.createLayer("dieline-overlay", {
942
+ width,
943
+ height,
944
+ selectable: false,
945
+ evented: false
575
946
  });
576
- }
577
- createLayer(editor) {
578
- let layer = this.getLayer(editor, "dieline-overlay");
579
- if (!layer) {
580
- const width = editor.canvas.width || 800;
581
- const height = editor.canvas.height || 600;
582
- layer = new import_core2.PooderLayer([], {
583
- width,
584
- height,
585
- selectable: false,
586
- evented: false,
587
- data: { id: "dieline-overlay" }
588
- });
589
- editor.canvas.add(layer);
947
+ this.canvasService.canvas.bringObjectToFront(layer);
948
+ const userLayer = this.canvasService.getLayer("user");
949
+ if (userLayer) {
950
+ const userIndex = this.canvasService.canvas.getObjects().indexOf(userLayer);
951
+ this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
590
952
  }
591
- editor.canvas.bringObjectToFront(layer);
592
953
  }
593
- destroyLayer(editor) {
594
- const layer = this.getLayer(editor, "dieline-overlay");
954
+ destroyLayer() {
955
+ if (!this.canvasService) return;
956
+ const layer = this.getLayer();
595
957
  if (layer) {
596
- editor.canvas.remove(layer);
958
+ this.canvasService.canvas.remove(layer);
597
959
  }
598
960
  }
599
961
  createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
@@ -614,10 +976,12 @@ var DielineTool = class {
614
976
  ctx.lineTo(size, 0);
615
977
  ctx.stroke();
616
978
  }
617
- return new import_core2.Pattern({ source: canvas, repetition: "repeat" });
979
+ return new import_fabric2.Pattern({ source: canvas, repetition: "repeat" });
618
980
  }
619
- updateDieline(editor) {
620
- var _a, _b;
981
+ updateDieline(emitEvent = true) {
982
+ if (!this.canvasService) return;
983
+ const layer = this.getLayer();
984
+ if (!layer) return;
621
985
  const {
622
986
  shape,
623
987
  radius,
@@ -627,33 +991,31 @@ var DielineTool = class {
627
991
  outsideColor,
628
992
  position,
629
993
  borderLength,
630
- showBleedLines
631
- } = this.options;
632
- let { width, height } = this.options;
633
- const canvasW = editor.canvas.width || 800;
634
- const canvasH = editor.canvas.height || 600;
994
+ showBleedLines,
995
+ holes
996
+ } = this;
997
+ let { width, height } = this;
998
+ const canvasW = this.canvasService.canvas.width || 800;
999
+ const canvasH = this.canvasService.canvas.height || 600;
635
1000
  if (borderLength && borderLength > 0) {
636
1001
  width = Math.max(0, canvasW - borderLength * 2);
637
1002
  height = Math.max(0, canvasH - borderLength * 2);
638
1003
  }
639
- const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
640
- const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
641
- const layer = this.getLayer(editor, "dieline-overlay");
642
- if (!layer) return;
1004
+ const normalizedPos = position != null ? position : { x: 0.5, y: 0.5 };
1005
+ const cx = Coordinate.toAbsolute(normalizedPos.x, canvasW);
1006
+ const cy = Coordinate.toAbsolute(normalizedPos.y, canvasH);
643
1007
  layer.remove(...layer.getObjects());
644
- const holeTool = editor.getExtension("HoleTool");
645
- if (holeTool && typeof holeTool.enforceConstraints === "function") {
646
- holeTool.enforceConstraints(editor);
647
- }
648
- const holes = holeTool ? holeTool.options.holes || [] : [];
649
- const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
650
- const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
651
- const holeData = holes.map((h) => ({
652
- x: h.x,
653
- y: h.y,
654
- innerRadius,
655
- outerRadius
656
- }));
1008
+ const absoluteHoles = (holes || []).map((h) => {
1009
+ const p = Coordinate.denormalizePoint(
1010
+ { x: h.x, y: h.y },
1011
+ { width: canvasW, height: canvasH }
1012
+ );
1013
+ return {
1014
+ ...h,
1015
+ x: p.x,
1016
+ y: p.y
1017
+ };
1018
+ });
657
1019
  const cutW = Math.max(0, width + offset * 2);
658
1020
  const cutH = Math.max(0, height + offset * 2);
659
1021
  const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
@@ -666,9 +1028,10 @@ var DielineTool = class {
666
1028
  radius: cutR,
667
1029
  x: cx,
668
1030
  y: cy,
669
- holes: holeData
1031
+ holes: absoluteHoles,
1032
+ pathData: this.pathData
670
1033
  });
671
- const mask = new import_core2.Path(maskPathData, {
1034
+ const mask = new import_fabric2.Path(maskPathData, {
672
1035
  fill: outsideColor,
673
1036
  stroke: null,
674
1037
  selectable: false,
@@ -687,9 +1050,10 @@ var DielineTool = class {
687
1050
  radius: cutR,
688
1051
  x: cx,
689
1052
  y: cy,
690
- holes: holeData
1053
+ holes: absoluteHoles,
1054
+ pathData: this.pathData
691
1055
  });
692
- const insideObj = new import_core2.Path(productPathData, {
1056
+ const insideObj = new import_fabric2.Path(productPathData, {
693
1057
  fill: insideColor,
694
1058
  stroke: null,
695
1059
  selectable: false,
@@ -709,14 +1073,15 @@ var DielineTool = class {
709
1073
  radius,
710
1074
  x: cx,
711
1075
  y: cy,
712
- holes: holeData
1076
+ holes: absoluteHoles,
1077
+ pathData: this.pathData
713
1078
  },
714
1079
  offset
715
1080
  );
716
1081
  if (showBleedLines !== false) {
717
1082
  const pattern = this.createHatchPattern("red");
718
1083
  if (pattern) {
719
- const bleedObj = new import_core2.Path(bleedPathData, {
1084
+ const bleedObj = new import_fabric2.Path(bleedPathData, {
720
1085
  fill: pattern,
721
1086
  stroke: null,
722
1087
  selectable: false,
@@ -735,9 +1100,10 @@ var DielineTool = class {
735
1100
  radius: cutR,
736
1101
  x: cx,
737
1102
  y: cy,
738
- holes: holeData
1103
+ holes: absoluteHoles,
1104
+ pathData: this.pathData
739
1105
  });
740
- const offsetBorderObj = new import_core2.Path(offsetPathData, {
1106
+ const offsetBorderObj = new import_fabric2.Path(offsetPathData, {
741
1107
  fill: null,
742
1108
  stroke: "#666",
743
1109
  // Grey
@@ -758,9 +1124,11 @@ var DielineTool = class {
758
1124
  radius,
759
1125
  x: cx,
760
1126
  y: cy,
761
- holes: holeData
1127
+ holes: absoluteHoles,
1128
+ // FIX: Use absoluteHoles instead of holes
1129
+ pathData: this.pathData
762
1130
  });
763
- const borderObj = new import_core2.Path(borderPathData, {
1131
+ const borderObj = new import_fabric2.Path(borderPathData, {
764
1132
  fill: "transparent",
765
1133
  stroke: "red",
766
1134
  strokeWidth: 1,
@@ -771,104 +1139,236 @@ var DielineTool = class {
771
1139
  originY: "top"
772
1140
  });
773
1141
  layer.add(borderObj);
774
- editor.canvas.requestRenderAll();
1142
+ const userLayer = this.canvasService.getLayer("user");
1143
+ if (layer && userLayer) {
1144
+ const layerIndex = this.canvasService.canvas.getObjects().indexOf(layer);
1145
+ const userIndex = this.canvasService.canvas.getObjects().indexOf(userLayer);
1146
+ if (layerIndex < userIndex) {
1147
+ this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
1148
+ }
1149
+ } else {
1150
+ this.canvasService.canvas.bringObjectToFront(layer);
1151
+ }
1152
+ const rulerLayer = this.canvasService.getLayer("ruler-overlay");
1153
+ if (rulerLayer) {
1154
+ this.canvasService.canvas.bringObjectToFront(rulerLayer);
1155
+ }
1156
+ layer.dirty = true;
1157
+ this.canvasService.requestRenderAll();
1158
+ if (emitEvent && this.context) {
1159
+ const geometry = this.getGeometry();
1160
+ if (geometry) {
1161
+ this.context.eventBus.emit("dieline:geometry:change", geometry);
1162
+ }
1163
+ }
775
1164
  }
776
- getGeometry(editor) {
1165
+ getGeometry() {
777
1166
  var _a, _b;
778
- const { shape, width, height, radius, position, borderLength } = this.options;
779
- const canvasW = editor.canvas.width || 800;
780
- const canvasH = editor.canvas.height || 600;
1167
+ if (!this.canvasService) return null;
1168
+ const { shape, width, height, radius, position, borderLength, offset } = this;
1169
+ const canvasW = this.canvasService.canvas.width || 800;
1170
+ const canvasH = this.canvasService.canvas.height || 600;
781
1171
  let visualWidth = width;
782
1172
  let visualHeight = height;
783
1173
  if (borderLength && borderLength > 0) {
784
1174
  visualWidth = Math.max(0, canvasW - borderLength * 2);
785
1175
  visualHeight = Math.max(0, canvasH - borderLength * 2);
786
1176
  }
787
- const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
788
- const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
1177
+ const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1178
+ const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
789
1179
  return {
790
1180
  shape,
791
1181
  x: cx,
792
1182
  y: cy,
793
1183
  width: visualWidth,
794
1184
  height: visualHeight,
795
- radius
1185
+ radius,
1186
+ offset,
1187
+ borderLength,
1188
+ pathData: this.pathData
796
1189
  };
797
1190
  }
1191
+ exportCutImage() {
1192
+ var _a, _b, _c, _d;
1193
+ if (!this.canvasService) return null;
1194
+ const canvas = this.canvasService.canvas;
1195
+ const { shape, width, height, radius, position, holes } = this;
1196
+ const canvasW = canvas.width || 800;
1197
+ const canvasH = canvas.height || 600;
1198
+ const cx = Coordinate.toAbsolute((_a = position == null ? void 0 : position.x) != null ? _a : 0.5, canvasW);
1199
+ const cy = Coordinate.toAbsolute((_b = position == null ? void 0 : position.y) != null ? _b : 0.5, canvasH);
1200
+ const absoluteHoles = (holes || []).map((h) => {
1201
+ const p = Coordinate.denormalizePoint(
1202
+ { x: h.x, y: h.y },
1203
+ { width: canvasW, height: canvasH }
1204
+ );
1205
+ return {
1206
+ ...h,
1207
+ x: p.x,
1208
+ y: p.y
1209
+ };
1210
+ });
1211
+ const pathData = generateDielinePath({
1212
+ shape,
1213
+ width,
1214
+ height,
1215
+ radius,
1216
+ x: cx,
1217
+ y: cy,
1218
+ holes: absoluteHoles,
1219
+ pathData: this.pathData
1220
+ });
1221
+ const clipPath = new import_fabric2.Path(pathData, {
1222
+ left: 0,
1223
+ top: 0,
1224
+ originX: "left",
1225
+ originY: "top",
1226
+ absolutePositioned: true
1227
+ });
1228
+ const layer = this.getLayer();
1229
+ const wasVisible = (_c = layer == null ? void 0 : layer.visible) != null ? _c : true;
1230
+ if (layer) layer.visible = false;
1231
+ const holeMarkers = canvas.getObjects().filter((o) => {
1232
+ var _a2;
1233
+ return ((_a2 = o.data) == null ? void 0 : _a2.type) === "hole-marker";
1234
+ });
1235
+ holeMarkers.forEach((o) => o.visible = false);
1236
+ const rulerLayer = canvas.getObjects().find((obj) => {
1237
+ var _a2;
1238
+ return ((_a2 = obj.data) == null ? void 0 : _a2.id) === "ruler-overlay";
1239
+ });
1240
+ const rulerWasVisible = (_d = rulerLayer == null ? void 0 : rulerLayer.visible) != null ? _d : true;
1241
+ if (rulerLayer) rulerLayer.visible = false;
1242
+ const originalClip = canvas.clipPath;
1243
+ canvas.clipPath = clipPath;
1244
+ const bbox = clipPath.getBoundingRect();
1245
+ const clipPathCorrected = new import_fabric2.Path(pathData, {
1246
+ absolutePositioned: true,
1247
+ left: 0,
1248
+ top: 0
1249
+ });
1250
+ const tempPath = new import_fabric2.Path(pathData);
1251
+ const tempBounds = tempPath.getBoundingRect();
1252
+ clipPathCorrected.set({
1253
+ left: tempBounds.left,
1254
+ top: tempBounds.top,
1255
+ originX: "left",
1256
+ originY: "top"
1257
+ });
1258
+ canvas.clipPath = clipPathCorrected;
1259
+ const exportBbox = clipPathCorrected.getBoundingRect();
1260
+ const dataURL = canvas.toDataURL({
1261
+ format: "png",
1262
+ multiplier: 2,
1263
+ left: exportBbox.left,
1264
+ top: exportBbox.top,
1265
+ width: exportBbox.width,
1266
+ height: exportBbox.height
1267
+ });
1268
+ canvas.clipPath = originalClip;
1269
+ if (layer) layer.visible = wasVisible;
1270
+ if (rulerLayer) rulerLayer.visible = rulerWasVisible;
1271
+ holeMarkers.forEach((o) => o.visible = true);
1272
+ canvas.requestRenderAll();
1273
+ return dataURL;
1274
+ }
798
1275
  };
799
1276
 
800
1277
  // src/film.ts
801
1278
  var import_core3 = require("@pooder/core");
1279
+ var import_fabric3 = require("fabric");
802
1280
  var FilmTool = class {
803
- constructor() {
804
- this.name = "FilmTool";
805
- this.options = {
806
- url: "",
807
- opacity: 0.5
1281
+ constructor(options) {
1282
+ this.id = "pooder.kit.film";
1283
+ this.metadata = {
1284
+ name: "FilmTool"
808
1285
  };
809
- this.schema = {
810
- url: {
811
- type: "string",
812
- label: "Film Image URL"
813
- },
814
- opacity: {
815
- type: "number",
816
- min: 0,
817
- max: 1,
818
- step: 0.1,
819
- label: "Opacity"
820
- }
821
- };
822
- this.commands = {
823
- setFilmImage: {
824
- execute: (editor, url, opacity) => {
825
- if (this.options.url === url && this.options.opacity === opacity)
826
- return true;
827
- this.options.url = url;
828
- this.options.opacity = opacity;
829
- this.updateFilm(editor, this.options);
830
- return true;
831
- },
832
- schema: {
833
- url: {
834
- type: "string",
835
- label: "Image URL",
836
- required: true
837
- },
838
- opacity: {
839
- type: "number",
840
- label: "Opacity",
841
- min: 0,
842
- max: 1,
843
- required: true
1286
+ this.url = "";
1287
+ this.opacity = 0.5;
1288
+ if (options) {
1289
+ Object.assign(this, options);
1290
+ }
1291
+ }
1292
+ activate(context) {
1293
+ this.canvasService = context.services.get("CanvasService");
1294
+ if (!this.canvasService) {
1295
+ console.warn("CanvasService not found for FilmTool");
1296
+ return;
1297
+ }
1298
+ const configService = context.services.get("ConfigurationService");
1299
+ if (configService) {
1300
+ this.url = configService.get("film.url", this.url);
1301
+ this.opacity = configService.get("film.opacity", this.opacity);
1302
+ configService.onAnyChange((e) => {
1303
+ if (e.key.startsWith("film.")) {
1304
+ const prop = e.key.split(".")[1];
1305
+ console.log(
1306
+ `[FilmTool] Config change detected: ${e.key} -> ${e.value}`
1307
+ );
1308
+ if (prop && prop in this) {
1309
+ this[prop] = e.value;
1310
+ this.updateFilm();
844
1311
  }
845
1312
  }
1313
+ });
1314
+ }
1315
+ this.initLayer();
1316
+ this.updateFilm();
1317
+ }
1318
+ deactivate(context) {
1319
+ if (this.canvasService) {
1320
+ const layer = this.canvasService.getLayer("overlay");
1321
+ if (layer) {
1322
+ const img = this.canvasService.getObject("film-image", "overlay");
1323
+ if (img) {
1324
+ layer.remove(img);
1325
+ this.canvasService.requestRenderAll();
1326
+ }
846
1327
  }
847
- };
848
- }
849
- onMount(editor) {
850
- this.initLayer(editor);
851
- this.updateFilm(editor, this.options);
852
- }
853
- onUnmount(editor) {
854
- const layer = editor.getLayer("overlay");
855
- if (layer) {
856
- const img = editor.getObject("film-image", "overlay");
857
- if (img) {
858
- layer.remove(img);
859
- editor.canvas.requestRenderAll();
860
- }
1328
+ this.canvasService = void 0;
861
1329
  }
862
1330
  }
863
- onUpdate(editor, state) {
864
- this.updateFilm(editor, this.options);
1331
+ contribute() {
1332
+ return {
1333
+ [import_core3.ContributionPointIds.CONFIGURATIONS]: [
1334
+ {
1335
+ id: "film.url",
1336
+ type: "string",
1337
+ label: "Film Image URL",
1338
+ default: ""
1339
+ },
1340
+ {
1341
+ id: "film.opacity",
1342
+ type: "number",
1343
+ label: "Opacity",
1344
+ min: 0,
1345
+ max: 1,
1346
+ step: 0.1,
1347
+ default: 0.5
1348
+ }
1349
+ ],
1350
+ [import_core3.ContributionPointIds.COMMANDS]: [
1351
+ {
1352
+ command: "setFilmImage",
1353
+ title: "Set Film Image",
1354
+ handler: (url, opacity) => {
1355
+ if (this.url === url && this.opacity === opacity) return true;
1356
+ this.url = url;
1357
+ this.opacity = opacity;
1358
+ this.updateFilm();
1359
+ return true;
1360
+ }
1361
+ }
1362
+ ]
1363
+ };
865
1364
  }
866
- initLayer(editor) {
867
- let overlayLayer = editor.getLayer("overlay");
1365
+ initLayer() {
1366
+ if (!this.canvasService) return;
1367
+ let overlayLayer = this.canvasService.getLayer("overlay");
868
1368
  if (!overlayLayer) {
869
- const width = editor.state.width;
870
- const height = editor.state.height;
871
- const layer = new import_core3.PooderLayer([], {
1369
+ const width = this.canvasService.canvas.width || 800;
1370
+ const height = this.canvasService.canvas.height || 600;
1371
+ const layer = this.canvasService.createLayer("overlay", {
872
1372
  width,
873
1373
  height,
874
1374
  left: 0,
@@ -878,33 +1378,30 @@ var FilmTool = class {
878
1378
  selectable: false,
879
1379
  evented: false,
880
1380
  subTargetCheck: false,
881
- interactive: false,
882
- data: {
883
- id: "overlay"
884
- }
1381
+ interactive: false
885
1382
  });
886
- editor.canvas.add(layer);
887
- editor.canvas.bringObjectToFront(layer);
1383
+ this.canvasService.canvas.bringObjectToFront(layer);
888
1384
  }
889
1385
  }
890
- async updateFilm(editor, options) {
891
- const layer = editor.getLayer("overlay");
1386
+ async updateFilm() {
1387
+ if (!this.canvasService) return;
1388
+ const layer = this.canvasService.getLayer("overlay");
892
1389
  if (!layer) {
893
1390
  console.warn("[FilmTool] Overlay layer not found");
894
1391
  return;
895
1392
  }
896
- const { url, opacity } = options;
1393
+ const { url, opacity } = this;
897
1394
  if (!url) {
898
- const img2 = editor.getObject("film-image", "overlay");
1395
+ const img2 = this.canvasService.getObject("film-image", "overlay");
899
1396
  if (img2) {
900
1397
  layer.remove(img2);
901
- editor.canvas.requestRenderAll();
1398
+ this.canvasService.requestRenderAll();
902
1399
  }
903
1400
  return;
904
1401
  }
905
- const width = editor.state.width;
906
- const height = editor.state.height;
907
- let img = editor.getObject("film-image", "overlay");
1402
+ const width = this.canvasService.canvas.width || 800;
1403
+ const height = this.canvasService.canvas.height || 600;
1404
+ let img = this.canvasService.getObject("film-image", "overlay");
908
1405
  try {
909
1406
  if (img) {
910
1407
  if (img.getSrc() !== url) {
@@ -912,7 +1409,7 @@ var FilmTool = class {
912
1409
  }
913
1410
  img.set({ opacity });
914
1411
  } else {
915
- img = await import_core3.Image.fromURL(url, { crossOrigin: "anonymous" });
1412
+ img = await import_fabric3.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
916
1413
  img.scaleToWidth(width);
917
1414
  if (img.getScaledHeight() < height) img.scaleToHeight(height);
918
1415
  img.set({
@@ -927,204 +1424,263 @@ var FilmTool = class {
927
1424
  });
928
1425
  layer.add(img);
929
1426
  }
930
- editor.canvas.requestRenderAll();
1427
+ this.canvasService.requestRenderAll();
931
1428
  } catch (error) {
932
1429
  console.error("[FilmTool] Failed to load film image", url, error);
933
1430
  }
1431
+ layer.dirty = true;
1432
+ this.canvasService.requestRenderAll();
934
1433
  }
935
1434
  };
936
1435
 
937
1436
  // src/hole.ts
938
1437
  var import_core4 = require("@pooder/core");
1438
+ var import_fabric4 = require("fabric");
939
1439
  var HoleTool = class {
940
- constructor() {
941
- this.name = "HoleTool";
942
- this.options = {
943
- innerRadius: 15,
944
- outerRadius: 25,
945
- style: "solid",
946
- holes: [],
947
- constraintTarget: "bleed"
948
- };
949
- this.schema = {
950
- innerRadius: {
951
- type: "number",
952
- min: 1,
953
- max: 100,
954
- label: "Inner Radius"
955
- },
956
- outerRadius: {
957
- type: "number",
958
- min: 1,
959
- max: 100,
960
- label: "Outer Radius"
961
- },
962
- style: {
963
- type: "select",
964
- options: ["solid", "dashed"],
965
- label: "Line Style"
966
- },
967
- constraintTarget: {
968
- type: "select",
969
- options: ["original", "bleed"],
970
- label: "Constraint Target"
971
- },
972
- holes: {
973
- type: "json",
974
- label: "Holes"
975
- }
1440
+ constructor(options) {
1441
+ this.id = "pooder.kit.hole";
1442
+ this.metadata = {
1443
+ name: "HoleTool"
976
1444
  };
1445
+ this.innerRadius = 15;
1446
+ this.outerRadius = 25;
1447
+ this.style = "solid";
1448
+ this.holes = [];
1449
+ this.constraintTarget = "bleed";
1450
+ this.isUpdatingConfig = false;
977
1451
  this.handleMoving = null;
978
1452
  this.handleModified = null;
979
- this.commands = {
980
- reset: {
981
- execute: (editor) => {
982
- let defaultPos = { x: editor.canvas.width / 2, y: 50 };
983
- const g = this.getDielineGeometry(editor);
984
- if (g) {
985
- const topCenter = { x: g.x, y: g.y - g.height / 2 };
986
- defaultPos = getNearestPointOnDieline(topCenter, {
987
- ...g,
988
- holes: []
989
- });
1453
+ this.handleDielineChange = null;
1454
+ // Cache geometry to enforce constraints during drag
1455
+ this.currentGeometry = null;
1456
+ if (options) {
1457
+ Object.assign(this, options);
1458
+ }
1459
+ }
1460
+ activate(context) {
1461
+ this.context = context;
1462
+ this.canvasService = context.services.get("CanvasService");
1463
+ if (!this.canvasService) {
1464
+ console.warn("CanvasService not found for HoleTool");
1465
+ return;
1466
+ }
1467
+ const configService = context.services.get(
1468
+ "ConfigurationService"
1469
+ );
1470
+ if (configService) {
1471
+ this.innerRadius = configService.get(
1472
+ "hole.innerRadius",
1473
+ this.innerRadius
1474
+ );
1475
+ this.outerRadius = configService.get(
1476
+ "hole.outerRadius",
1477
+ this.outerRadius
1478
+ );
1479
+ this.style = configService.get("hole.style", this.style);
1480
+ this.constraintTarget = configService.get(
1481
+ "hole.constraintTarget",
1482
+ this.constraintTarget
1483
+ );
1484
+ const dielineHoles = configService.get("dieline.holes", []);
1485
+ if (this.canvasService) {
1486
+ const { width, height } = this.canvasService.canvas;
1487
+ this.holes = dielineHoles.map((h) => {
1488
+ const p = Coordinate.denormalizePoint(h, {
1489
+ width: width || 800,
1490
+ height: height || 600
1491
+ });
1492
+ return { x: p.x, y: p.y };
1493
+ });
1494
+ }
1495
+ configService.onAnyChange((e) => {
1496
+ if (this.isUpdatingConfig) return;
1497
+ if (e.key.startsWith("hole.")) {
1498
+ const prop = e.key.split(".")[1];
1499
+ if (prop && prop in this) {
1500
+ this[prop] = e.value;
1501
+ this.redraw();
1502
+ this.syncHolesToDieline();
990
1503
  }
991
- this.options = {
992
- innerRadius: 15,
993
- outerRadius: 25,
994
- style: "solid",
995
- holes: [defaultPos]
996
- };
997
- this.redraw(editor);
998
- const dielineTool = editor.getExtension("DielineTool");
999
- if (dielineTool && dielineTool.updateDieline) {
1000
- dielineTool.updateDieline(editor);
1504
+ }
1505
+ if (e.key === "dieline.holes") {
1506
+ const holes = e.value || [];
1507
+ if (this.canvasService) {
1508
+ const { width, height } = this.canvasService.canvas;
1509
+ this.holes = holes.map((h) => {
1510
+ const p = Coordinate.denormalizePoint(h, {
1511
+ width: width || 800,
1512
+ height: height || 600
1513
+ });
1514
+ return { x: p.x, y: p.y };
1515
+ });
1516
+ this.redraw();
1001
1517
  }
1002
- return true;
1003
1518
  }
1004
- },
1005
- addHole: {
1006
- execute: (editor, x, y) => {
1007
- if (!this.options.holes) this.options.holes = [];
1008
- this.options.holes.push({ x, y });
1009
- this.redraw(editor);
1010
- const dielineTool = editor.getExtension("DielineTool");
1011
- if (dielineTool && dielineTool.updateDieline) {
1012
- dielineTool.updateDieline(editor);
1519
+ });
1520
+ }
1521
+ this.setup();
1522
+ }
1523
+ deactivate(context) {
1524
+ this.teardown();
1525
+ this.canvasService = void 0;
1526
+ this.context = void 0;
1527
+ }
1528
+ contribute() {
1529
+ return {
1530
+ [import_core4.ContributionPointIds.CONFIGURATIONS]: [
1531
+ {
1532
+ id: "hole.innerRadius",
1533
+ type: "number",
1534
+ label: "Inner Radius",
1535
+ min: 1,
1536
+ max: 100,
1537
+ default: 15
1538
+ },
1539
+ {
1540
+ id: "hole.outerRadius",
1541
+ type: "number",
1542
+ label: "Outer Radius",
1543
+ min: 1,
1544
+ max: 100,
1545
+ default: 25
1546
+ },
1547
+ {
1548
+ id: "hole.style",
1549
+ type: "select",
1550
+ label: "Line Style",
1551
+ options: ["solid", "dashed"],
1552
+ default: "solid"
1553
+ },
1554
+ {
1555
+ id: "hole.constraintTarget",
1556
+ type: "select",
1557
+ label: "Constraint Target",
1558
+ options: ["original", "bleed"],
1559
+ default: "bleed"
1560
+ }
1561
+ ],
1562
+ [import_core4.ContributionPointIds.COMMANDS]: [
1563
+ {
1564
+ command: "resetHoles",
1565
+ title: "Reset Holes",
1566
+ handler: () => {
1567
+ if (!this.canvasService) return false;
1568
+ let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1569
+ if (this.currentGeometry) {
1570
+ const g = this.currentGeometry;
1571
+ const topCenter = { x: g.x, y: g.y - g.height / 2 };
1572
+ defaultPos = getNearestPointOnDieline(topCenter, {
1573
+ ...g,
1574
+ holes: []
1575
+ });
1576
+ }
1577
+ this.innerRadius = 15;
1578
+ this.outerRadius = 25;
1579
+ this.style = "solid";
1580
+ this.holes = [defaultPos];
1581
+ this.redraw();
1582
+ this.syncHolesToDieline();
1583
+ return true;
1013
1584
  }
1014
- return true;
1015
1585
  },
1016
- schema: {
1017
- x: {
1018
- type: "number",
1019
- label: "X Position",
1020
- required: true
1021
- },
1022
- y: {
1023
- type: "number",
1024
- label: "Y Position",
1025
- required: true
1586
+ {
1587
+ command: "addHole",
1588
+ title: "Add Hole",
1589
+ handler: (x, y) => {
1590
+ if (!this.holes) this.holes = [];
1591
+ this.holes.push({ x, y });
1592
+ this.redraw();
1593
+ this.syncHolesToDieline();
1594
+ return true;
1026
1595
  }
1027
- }
1028
- },
1029
- clearHoles: {
1030
- execute: (editor) => {
1031
- this.options.holes = [];
1032
- this.redraw(editor);
1033
- const dielineTool = editor.getExtension("DielineTool");
1034
- if (dielineTool && dielineTool.updateDieline) {
1035
- dielineTool.updateDieline(editor);
1596
+ },
1597
+ {
1598
+ command: "clearHoles",
1599
+ title: "Clear Holes",
1600
+ handler: () => {
1601
+ this.holes = [];
1602
+ this.redraw();
1603
+ this.syncHolesToDieline();
1604
+ return true;
1036
1605
  }
1037
- return true;
1038
1606
  }
1039
- }
1607
+ ]
1040
1608
  };
1041
1609
  }
1042
- onMount(editor) {
1043
- this.setup(editor);
1044
- }
1045
- onUnmount(editor) {
1046
- this.teardown(editor);
1047
- }
1048
- onDestroy(editor) {
1049
- this.teardown(editor);
1050
- }
1051
- getDielineGeometry(editor) {
1052
- const dielineTool = editor.getExtension("DielineTool");
1053
- if (!dielineTool) return null;
1054
- const geometry = dielineTool.getGeometry(editor);
1055
- if (!geometry) return null;
1056
- const offset = this.options.constraintTarget === "original" ? 0 : dielineTool.options.offset || 0;
1057
- return {
1058
- ...geometry,
1059
- width: Math.max(0, geometry.width + offset * 2),
1060
- height: Math.max(0, geometry.height + offset * 2),
1061
- radius: Math.max(0, geometry.radius + offset)
1062
- };
1063
- }
1064
- enforceConstraints(editor) {
1065
- const geometry = this.getDielineGeometry(editor);
1066
- if (!geometry) return;
1067
- const objects = editor.canvas.getObjects().filter((obj) => {
1068
- var _a;
1069
- return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1070
- });
1071
- let changed = false;
1072
- objects.sort(
1073
- (a, b) => {
1074
- var _a, _b, _c, _d;
1075
- return ((_b = (_a = a.data) == null ? void 0 : _a.index) != null ? _b : 0) - ((_d = (_c = b.data) == null ? void 0 : _c.index) != null ? _d : 0);
1076
- }
1077
- );
1078
- const newHoles = [];
1079
- objects.forEach((obj) => {
1080
- const currentPos = new import_core4.Point(obj.left, obj.top);
1081
- const newPos = this.calculateConstrainedPosition(currentPos, geometry);
1082
- if (currentPos.distanceFrom(newPos) > 0.1) {
1083
- obj.set({
1084
- left: newPos.x,
1085
- top: newPos.y
1086
- });
1087
- obj.setCoords();
1088
- changed = true;
1610
+ setup() {
1611
+ if (!this.canvasService || !this.context) return;
1612
+ const canvas = this.canvasService.canvas;
1613
+ if (!this.handleDielineChange) {
1614
+ this.handleDielineChange = (geometry) => {
1615
+ this.currentGeometry = geometry;
1616
+ const changed = this.enforceConstraints();
1617
+ if (changed) {
1618
+ this.syncHolesToDieline();
1619
+ }
1620
+ };
1621
+ this.context.eventBus.on(
1622
+ "dieline:geometry:change",
1623
+ this.handleDielineChange
1624
+ );
1625
+ }
1626
+ const commandService = this.context.services.get("CommandService");
1627
+ if (commandService) {
1628
+ try {
1629
+ const geometry = commandService.executeCommand("getGeometry");
1630
+ if (geometry) {
1631
+ Promise.resolve(geometry).then((g) => {
1632
+ if (g) {
1633
+ this.currentGeometry = g;
1634
+ this.enforceConstraints();
1635
+ this.initializeHoles();
1636
+ }
1637
+ });
1638
+ }
1639
+ } catch (e) {
1089
1640
  }
1090
- newHoles.push({ x: obj.left, y: obj.top });
1091
- });
1092
- if (changed) {
1093
- this.options.holes = newHoles;
1094
- editor.canvas.requestRenderAll();
1095
1641
  }
1096
- }
1097
- setup(editor) {
1098
1642
  if (!this.handleMoving) {
1099
1643
  this.handleMoving = (e) => {
1100
1644
  var _a;
1101
1645
  const target = e.target;
1102
1646
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1103
- const geometry = this.getDielineGeometry(editor);
1104
- if (!geometry) return;
1105
- const p = new import_core4.Point(target.left, target.top);
1106
- const newPos = this.calculateConstrainedPosition(p, geometry);
1647
+ if (!this.currentGeometry) return;
1648
+ const effectiveOffset = this.constraintTarget === "original" ? 0 : this.currentGeometry.offset;
1649
+ const constraintGeometry = {
1650
+ ...this.currentGeometry,
1651
+ width: Math.max(0, this.currentGeometry.width + effectiveOffset * 2),
1652
+ height: Math.max(
1653
+ 0,
1654
+ this.currentGeometry.height + effectiveOffset * 2
1655
+ ),
1656
+ radius: Math.max(0, this.currentGeometry.radius + effectiveOffset)
1657
+ };
1658
+ const p = new import_fabric4.Point(target.left, target.top);
1659
+ const newPos = this.calculateConstrainedPosition(p, constraintGeometry);
1107
1660
  target.set({
1108
1661
  left: newPos.x,
1109
1662
  top: newPos.y
1110
1663
  });
1111
1664
  };
1112
- editor.canvas.on("object:moving", this.handleMoving);
1665
+ canvas.on("object:moving", this.handleMoving);
1113
1666
  }
1114
1667
  if (!this.handleModified) {
1115
1668
  this.handleModified = (e) => {
1116
1669
  var _a;
1117
1670
  const target = e.target;
1118
1671
  if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
1119
- this.syncHolesFromCanvas(editor);
1672
+ this.syncHolesFromCanvas();
1120
1673
  };
1121
- editor.canvas.on("object:modified", this.handleModified);
1674
+ canvas.on("object:modified", this.handleModified);
1122
1675
  }
1123
- const opts = this.options;
1124
- if (!opts.holes || opts.holes.length === 0) {
1125
- let defaultPos = { x: editor.canvas.width / 2, y: 50 };
1126
- const g = this.getDielineGeometry(editor);
1127
- if (g) {
1676
+ this.initializeHoles();
1677
+ }
1678
+ initializeHoles() {
1679
+ if (!this.canvasService) return;
1680
+ if (!this.holes || this.holes.length === 0) {
1681
+ let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
1682
+ if (this.currentGeometry) {
1683
+ const g = this.currentGeometry;
1128
1684
  const topCenter = { x: g.x, y: g.y - g.height / 2 };
1129
1685
  const snapped = getNearestPointOnDieline(topCenter, {
1130
1686
  ...g,
@@ -1132,65 +1688,97 @@ var HoleTool = class {
1132
1688
  });
1133
1689
  defaultPos = snapped;
1134
1690
  }
1135
- opts.holes = [defaultPos];
1136
- }
1137
- this.options = { ...opts };
1138
- this.redraw(editor);
1139
- const dielineTool = editor.getExtension("DielineTool");
1140
- if (dielineTool && dielineTool.updateDieline) {
1141
- dielineTool.updateDieline(editor);
1691
+ this.holes = [defaultPos];
1142
1692
  }
1693
+ this.redraw();
1694
+ this.syncHolesToDieline();
1143
1695
  }
1144
- teardown(editor) {
1696
+ teardown() {
1697
+ if (!this.canvasService) return;
1698
+ const canvas = this.canvasService.canvas;
1145
1699
  if (this.handleMoving) {
1146
- editor.canvas.off("object:moving", this.handleMoving);
1700
+ canvas.off("object:moving", this.handleMoving);
1147
1701
  this.handleMoving = null;
1148
1702
  }
1149
1703
  if (this.handleModified) {
1150
- editor.canvas.off("object:modified", this.handleModified);
1704
+ canvas.off("object:modified", this.handleModified);
1151
1705
  this.handleModified = null;
1152
1706
  }
1153
- const objects = editor.canvas.getObjects().filter((obj) => {
1707
+ if (this.handleDielineChange && this.context) {
1708
+ this.context.eventBus.off(
1709
+ "dieline:geometry:change",
1710
+ this.handleDielineChange
1711
+ );
1712
+ this.handleDielineChange = null;
1713
+ }
1714
+ const objects = canvas.getObjects().filter((obj) => {
1154
1715
  var _a;
1155
1716
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1156
1717
  });
1157
- objects.forEach((obj) => editor.canvas.remove(obj));
1158
- editor.canvas.requestRenderAll();
1159
- }
1160
- onUpdate(editor, state) {
1161
- this.enforceConstraints(editor);
1162
- this.redraw(editor);
1163
- const dielineTool = editor.getExtension("DielineTool");
1164
- if (dielineTool && dielineTool.updateDieline) {
1165
- dielineTool.updateDieline(editor);
1718
+ objects.forEach((obj) => canvas.remove(obj));
1719
+ if (this.context) {
1720
+ const commandService = this.context.services.get("CommandService");
1721
+ if (commandService) {
1722
+ try {
1723
+ commandService.executeCommand("setHoles", []);
1724
+ } catch (e) {
1725
+ }
1726
+ }
1166
1727
  }
1728
+ this.canvasService.requestRenderAll();
1167
1729
  }
1168
- syncHolesFromCanvas(editor) {
1169
- const objects = editor.canvas.getObjects().filter((obj) => {
1730
+ syncHolesFromCanvas() {
1731
+ if (!this.canvasService) return;
1732
+ const objects = this.canvasService.canvas.getObjects().filter((obj) => {
1170
1733
  var _a;
1171
1734
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1172
1735
  });
1173
1736
  const holes = objects.map((obj) => ({ x: obj.left, y: obj.top }));
1174
- this.options.holes = holes;
1175
- const dielineTool = editor.getExtension("DielineTool");
1176
- if (dielineTool && dielineTool.updateDieline) {
1177
- dielineTool.updateDieline(editor);
1737
+ this.holes = holes;
1738
+ this.syncHolesToDieline();
1739
+ }
1740
+ syncHolesToDieline() {
1741
+ if (!this.context || !this.canvasService) return;
1742
+ const { holes, innerRadius, outerRadius } = this;
1743
+ const currentHoles = holes || [];
1744
+ const width = this.canvasService.canvas.width || 800;
1745
+ const height = this.canvasService.canvas.height || 600;
1746
+ const configService = this.context.services.get(
1747
+ "ConfigurationService"
1748
+ );
1749
+ if (configService) {
1750
+ this.isUpdatingConfig = true;
1751
+ try {
1752
+ const normalizedHoles = currentHoles.map((h) => {
1753
+ const p = Coordinate.normalizePoint(h, { width, height });
1754
+ return {
1755
+ x: p.x,
1756
+ y: p.y,
1757
+ innerRadius,
1758
+ outerRadius
1759
+ };
1760
+ });
1761
+ configService.update("dieline.holes", normalizedHoles);
1762
+ } finally {
1763
+ this.isUpdatingConfig = false;
1764
+ }
1178
1765
  }
1179
1766
  }
1180
- redraw(editor) {
1181
- const canvas = editor.canvas;
1767
+ redraw() {
1768
+ if (!this.canvasService) return;
1769
+ const canvas = this.canvasService.canvas;
1182
1770
  const existing = canvas.getObjects().filter((obj) => {
1183
1771
  var _a;
1184
1772
  return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1185
1773
  });
1186
1774
  existing.forEach((obj) => canvas.remove(obj));
1187
- const { innerRadius, outerRadius, style, holes } = this.options;
1775
+ const { innerRadius, outerRadius, style, holes } = this;
1188
1776
  if (!holes || holes.length === 0) {
1189
- canvas.requestRenderAll();
1777
+ this.canvasService.requestRenderAll();
1190
1778
  return;
1191
1779
  }
1192
1780
  holes.forEach((hole, index) => {
1193
- const innerCircle = new import_core4.Circle({
1781
+ const innerCircle = new import_fabric4.Circle({
1194
1782
  radius: innerRadius,
1195
1783
  fill: "transparent",
1196
1784
  stroke: "red",
@@ -1198,7 +1786,7 @@ var HoleTool = class {
1198
1786
  originX: "center",
1199
1787
  originY: "center"
1200
1788
  });
1201
- const outerCircle = new import_core4.Circle({
1789
+ const outerCircle = new import_fabric4.Circle({
1202
1790
  radius: outerRadius,
1203
1791
  fill: "transparent",
1204
1792
  stroke: "#666",
@@ -1207,7 +1795,7 @@ var HoleTool = class {
1207
1795
  originX: "center",
1208
1796
  originY: "center"
1209
1797
  });
1210
- const holeGroup = new import_core4.Group([outerCircle, innerCircle], {
1798
+ const holeGroup = new import_fabric4.Group([outerCircle, innerCircle], {
1211
1799
  left: hole.x,
1212
1800
  top: hole.y,
1213
1801
  originX: "center",
@@ -1244,7 +1832,68 @@ var HoleTool = class {
1244
1832
  canvas.add(holeGroup);
1245
1833
  canvas.bringObjectToFront(holeGroup);
1246
1834
  });
1247
- canvas.requestRenderAll();
1835
+ const markers = canvas.getObjects().filter((o) => {
1836
+ var _a;
1837
+ return ((_a = o.data) == null ? void 0 : _a.type) === "hole-marker";
1838
+ });
1839
+ markers.forEach((m) => canvas.bringObjectToFront(m));
1840
+ this.canvasService.requestRenderAll();
1841
+ }
1842
+ enforceConstraints() {
1843
+ const geometry = this.currentGeometry;
1844
+ if (!geometry || !this.canvasService) {
1845
+ console.log(
1846
+ "[HoleTool] Skipping enforceConstraints: No geometry or canvas service"
1847
+ );
1848
+ return false;
1849
+ }
1850
+ const effectiveOffset = this.constraintTarget === "original" ? 0 : geometry.offset;
1851
+ const constraintGeometry = {
1852
+ ...geometry,
1853
+ width: Math.max(0, geometry.width + effectiveOffset * 2),
1854
+ height: Math.max(0, geometry.height + effectiveOffset * 2),
1855
+ radius: Math.max(0, geometry.radius + effectiveOffset)
1856
+ };
1857
+ const objects = this.canvasService.canvas.getObjects().filter((obj) => {
1858
+ var _a;
1859
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
1860
+ });
1861
+ console.log(
1862
+ `[HoleTool] Enforcing constraints on ${objects.length} markers`
1863
+ );
1864
+ let changed = false;
1865
+ objects.sort(
1866
+ (a, b) => {
1867
+ var _a, _b, _c, _d;
1868
+ return ((_b = (_a = a.data) == null ? void 0 : _a.index) != null ? _b : 0) - ((_d = (_c = b.data) == null ? void 0 : _c.index) != null ? _d : 0);
1869
+ }
1870
+ );
1871
+ const newHoles = [];
1872
+ objects.forEach((obj) => {
1873
+ const currentPos = new import_fabric4.Point(obj.left, obj.top);
1874
+ const newPos = this.calculateConstrainedPosition(
1875
+ currentPos,
1876
+ constraintGeometry
1877
+ );
1878
+ if (currentPos.distanceFrom(newPos) > 0.1) {
1879
+ console.log(
1880
+ `[HoleTool] Moving hole from (${currentPos.x}, ${currentPos.y}) to (${newPos.x}, ${newPos.y})`
1881
+ );
1882
+ obj.set({
1883
+ left: newPos.x,
1884
+ top: newPos.y
1885
+ });
1886
+ obj.setCoords();
1887
+ changed = true;
1888
+ }
1889
+ newHoles.push({ x: obj.left, y: obj.top });
1890
+ });
1891
+ if (changed) {
1892
+ this.holes = newHoles;
1893
+ this.canvasService.requestRenderAll();
1894
+ return true;
1895
+ }
1896
+ return false;
1248
1897
  }
1249
1898
  calculateConstrainedPosition(p, g) {
1250
1899
  const options = {
@@ -1256,10 +1905,10 @@ var HoleTool = class {
1256
1905
  { x: p.x, y: p.y },
1257
1906
  options
1258
1907
  );
1259
- const nearestP = new import_core4.Point(nearest.x, nearest.y);
1908
+ const nearestP = new import_fabric4.Point(nearest.x, nearest.y);
1260
1909
  const dist = p.distanceFrom(nearestP);
1261
1910
  const v = p.subtract(nearestP);
1262
- const center = new import_core4.Point(g.x, g.y);
1911
+ const center = new import_fabric4.Point(g.x, g.y);
1263
1912
  const centerToNearest = nearestP.subtract(center);
1264
1913
  const distToCenter = p.distanceFrom(center);
1265
1914
  const nearestDistToCenter = nearestP.distanceFrom(center);
@@ -1269,12 +1918,11 @@ var HoleTool = class {
1269
1918
  }
1270
1919
  let clampedDist = signedDist;
1271
1920
  if (signedDist > 0) {
1272
- clampedDist = Math.min(signedDist, this.options.innerRadius);
1921
+ clampedDist = Math.min(signedDist, this.innerRadius);
1273
1922
  } else {
1274
- clampedDist = Math.max(signedDist, -this.options.outerRadius);
1923
+ clampedDist = Math.max(signedDist, -this.outerRadius);
1275
1924
  }
1276
1925
  if (dist < 1e-3) return nearestP;
1277
- const dir = v.scalarDivide(dist);
1278
1926
  const scale = Math.abs(clampedDist) / (dist || 1);
1279
1927
  const offset = v.scalarMultiply(scale);
1280
1928
  return nearestP.add(offset);
@@ -1283,111 +1931,153 @@ var HoleTool = class {
1283
1931
 
1284
1932
  // src/image.ts
1285
1933
  var import_core5 = require("@pooder/core");
1934
+ var import_fabric5 = require("fabric");
1286
1935
  var ImageTool = class {
1287
- constructor() {
1288
- this.name = "ImageTool";
1289
- this._loadingUrl = null;
1290
- this.options = {
1291
- url: "",
1292
- opacity: 1
1293
- };
1294
- this.schema = {
1295
- url: {
1296
- type: "string",
1297
- label: "Image URL"
1298
- },
1299
- opacity: {
1300
- type: "number",
1301
- min: 0,
1302
- max: 1,
1303
- step: 0.1,
1304
- label: "Opacity"
1305
- },
1306
- width: {
1307
- type: "number",
1308
- label: "Width",
1309
- min: 0,
1310
- max: 5e3
1311
- },
1312
- height: {
1313
- type: "number",
1314
- label: "Height",
1315
- min: 0,
1316
- max: 5e3
1317
- },
1318
- angle: {
1319
- type: "number",
1320
- label: "Rotation",
1321
- min: 0,
1322
- max: 360
1323
- },
1324
- left: {
1325
- type: "number",
1326
- label: "Left",
1327
- min: 0,
1328
- max: 1e3
1329
- },
1330
- top: {
1331
- type: "number",
1332
- label: "Top",
1333
- min: 0,
1334
- max: 1e3
1335
- }
1336
- };
1337
- this.commands = {
1338
- setUserImage: {
1339
- execute: (editor, url, opacity, width, height, angle, left, top) => {
1340
- if (this.options.url === url && this.options.opacity === opacity && this.options.width === width && this.options.height === height && this.options.angle === angle && this.options.left === left && this.options.top === top)
1341
- return true;
1342
- this.options = { url, opacity, width, height, angle, left, top };
1343
- this.updateImage(editor, this.options);
1344
- return true;
1345
- },
1346
- schema: {
1347
- url: {
1348
- type: "string",
1349
- label: "Image URL",
1350
- required: true
1351
- },
1352
- opacity: {
1353
- type: "number",
1354
- label: "Opacity",
1355
- min: 0,
1356
- max: 1,
1357
- required: true
1358
- },
1359
- width: { type: "number", label: "Width" },
1360
- height: { type: "number", label: "Height" },
1361
- angle: { type: "number", label: "Angle" },
1362
- left: { type: "number", label: "Left" },
1363
- top: { type: "number", label: "Top" }
1364
- }
1365
- }
1936
+ constructor(options) {
1937
+ this.id = "pooder.kit.image";
1938
+ this.metadata = {
1939
+ name: "ImageTool"
1366
1940
  };
1941
+ this._loadingUrl = null;
1942
+ this.url = "";
1943
+ this.opacity = 1;
1944
+ if (options) {
1945
+ Object.assign(this, options);
1946
+ }
1367
1947
  }
1368
- onMount(editor) {
1369
- this.ensureLayer(editor);
1370
- this.updateImage(editor, this.options);
1371
- }
1372
- onUnmount(editor) {
1373
- const layer = editor.getLayer("user");
1374
- if (layer) {
1375
- const userImage = editor.getObject("user-image", "user");
1376
- if (userImage) {
1377
- layer.remove(userImage);
1378
- editor.canvas.requestRenderAll();
1948
+ activate(context) {
1949
+ this.context = context;
1950
+ this.canvasService = context.services.get("CanvasService");
1951
+ if (!this.canvasService) {
1952
+ console.warn("CanvasService not found for ImageTool");
1953
+ return;
1954
+ }
1955
+ const configService = context.services.get("ConfigurationService");
1956
+ if (configService) {
1957
+ this.url = configService.get("image.url", this.url);
1958
+ this.opacity = configService.get("image.opacity", this.opacity);
1959
+ this.width = configService.get("image.width", this.width);
1960
+ this.height = configService.get("image.height", this.height);
1961
+ this.angle = configService.get("image.angle", this.angle);
1962
+ this.left = configService.get("image.left", this.left);
1963
+ this.top = configService.get("image.top", this.top);
1964
+ configService.onAnyChange((e) => {
1965
+ if (e.key.startsWith("image.")) {
1966
+ const prop = e.key.split(".")[1];
1967
+ console.log(
1968
+ `[ImageTool] Config change detected: ${e.key} -> ${e.value}`
1969
+ );
1970
+ if (prop && prop in this) {
1971
+ this[prop] = e.value;
1972
+ this.updateImage();
1973
+ }
1974
+ }
1975
+ });
1976
+ }
1977
+ this.ensureLayer();
1978
+ this.updateImage();
1979
+ }
1980
+ deactivate(context) {
1981
+ if (this.canvasService) {
1982
+ const layer = this.canvasService.getLayer("user");
1983
+ if (layer) {
1984
+ const userImage = this.canvasService.getObject("user-image", "user");
1985
+ if (userImage) {
1986
+ layer.remove(userImage);
1987
+ this.canvasService.requestRenderAll();
1988
+ }
1379
1989
  }
1990
+ this.canvasService = void 0;
1991
+ this.context = void 0;
1380
1992
  }
1381
1993
  }
1382
- onUpdate(editor, state) {
1383
- this.updateImage(editor, this.options);
1994
+ contribute() {
1995
+ return {
1996
+ [import_core5.ContributionPointIds.CONFIGURATIONS]: [
1997
+ {
1998
+ id: "image.url",
1999
+ type: "string",
2000
+ label: "Image URL",
2001
+ default: this.url
2002
+ },
2003
+ {
2004
+ id: "image.opacity",
2005
+ type: "number",
2006
+ label: "Opacity",
2007
+ min: 0,
2008
+ max: 1,
2009
+ step: 0.1,
2010
+ default: this.opacity
2011
+ },
2012
+ {
2013
+ id: "image.width",
2014
+ type: "number",
2015
+ label: "Width",
2016
+ min: 0,
2017
+ max: 5e3,
2018
+ default: this.width
2019
+ },
2020
+ {
2021
+ id: "image.height",
2022
+ type: "number",
2023
+ label: "Height",
2024
+ min: 0,
2025
+ max: 5e3,
2026
+ default: this.height
2027
+ },
2028
+ {
2029
+ id: "image.angle",
2030
+ type: "number",
2031
+ label: "Rotation",
2032
+ min: 0,
2033
+ max: 360,
2034
+ default: this.angle
2035
+ },
2036
+ {
2037
+ id: "image.left",
2038
+ type: "number",
2039
+ label: "Left (Normalized)",
2040
+ min: 0,
2041
+ max: 1,
2042
+ default: this.left
2043
+ },
2044
+ {
2045
+ id: "image.top",
2046
+ type: "number",
2047
+ label: "Top (Normalized)",
2048
+ min: 0,
2049
+ max: 1,
2050
+ default: this.top
2051
+ }
2052
+ ],
2053
+ [import_core5.ContributionPointIds.COMMANDS]: [
2054
+ {
2055
+ command: "setUserImage",
2056
+ title: "Set User Image",
2057
+ handler: (url, opacity, width, height, angle, left, top) => {
2058
+ if (this.url === url && this.opacity === opacity && this.width === width && this.height === height && this.angle === angle && this.left === left && this.top === top)
2059
+ return true;
2060
+ this.url = url;
2061
+ this.opacity = opacity;
2062
+ this.width = width;
2063
+ this.height = height;
2064
+ this.angle = angle;
2065
+ this.left = left;
2066
+ this.top = top;
2067
+ this.updateImage();
2068
+ return true;
2069
+ }
2070
+ }
2071
+ ]
2072
+ };
1384
2073
  }
1385
- ensureLayer(editor) {
1386
- let userLayer = editor.getLayer("user");
2074
+ ensureLayer() {
2075
+ if (!this.canvasService) return;
2076
+ let userLayer = this.canvasService.getLayer("user");
1387
2077
  if (!userLayer) {
1388
- userLayer = new import_core5.PooderLayer([], {
1389
- width: editor.state.width,
1390
- height: editor.state.height,
2078
+ userLayer = this.canvasService.createLayer("user", {
2079
+ width: this.canvasService.canvas.width,
2080
+ height: this.canvasService.canvas.height,
1391
2081
  left: 0,
1392
2082
  top: 0,
1393
2083
  originX: "left",
@@ -1395,42 +2085,64 @@ var ImageTool = class {
1395
2085
  selectable: false,
1396
2086
  evented: true,
1397
2087
  subTargetCheck: true,
1398
- interactive: true,
1399
- data: {
1400
- id: "user"
1401
- }
2088
+ interactive: true
1402
2089
  });
1403
- editor.canvas.add(userLayer);
2090
+ const dielineLayer = this.canvasService.getLayer("dieline-overlay");
2091
+ if (dielineLayer) {
2092
+ const index = this.canvasService.canvas.getObjects().indexOf(dielineLayer);
2093
+ if (index >= 0) {
2094
+ this.canvasService.canvas.moveObjectTo(userLayer, index);
2095
+ }
2096
+ } else {
2097
+ const bgLayer = this.canvasService.getLayer("background");
2098
+ if (bgLayer) {
2099
+ this.canvasService.canvas.sendObjectToBack(bgLayer);
2100
+ }
2101
+ }
2102
+ this.canvasService.requestRenderAll();
1404
2103
  }
1405
2104
  }
1406
- updateImage(editor, opts) {
2105
+ updateImage() {
1407
2106
  var _a, _b;
1408
- let { url, opacity, width, height, angle, left, top } = opts;
1409
- const layer = editor.getLayer("user");
2107
+ if (!this.canvasService) return;
2108
+ let { url, opacity, width, height, angle, left, top } = this;
2109
+ const layer = this.canvasService.getLayer("user");
1410
2110
  if (!layer) {
1411
2111
  console.warn("[ImageTool] User layer not found");
1412
2112
  return;
1413
2113
  }
1414
- const userImage = editor.getObject("user-image", "user");
2114
+ const userImage = this.canvasService.getObject("user-image", "user");
1415
2115
  if (this._loadingUrl === url) return;
1416
2116
  if (userImage) {
1417
2117
  const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1418
2118
  if (currentSrc !== url) {
1419
- this.loadImage(editor, layer, opts);
2119
+ this.loadImage(layer);
1420
2120
  } else {
1421
2121
  const updates = {};
1422
- const centerX = editor.state.width / 2;
1423
- const centerY = editor.state.height / 2;
2122
+ const canvasW = this.canvasService.canvas.width || 800;
2123
+ const canvasH = this.canvasService.canvas.height || 600;
2124
+ const centerX = canvasW / 2;
2125
+ const centerY = canvasH / 2;
1424
2126
  if (userImage.opacity !== opacity) updates.opacity = opacity;
1425
2127
  if (angle !== void 0 && userImage.angle !== angle)
1426
2128
  updates.angle = angle;
2129
+ if (userImage.originX !== "center") {
2130
+ userImage.set({
2131
+ originX: "center",
2132
+ originY: "center",
2133
+ left: userImage.left + userImage.width * userImage.scaleX / 2,
2134
+ top: userImage.top + userImage.height * userImage.scaleY / 2
2135
+ });
2136
+ }
1427
2137
  if (left !== void 0) {
1428
- const localLeft = left - centerX;
2138
+ const globalLeft = Coordinate.toAbsolute(left, canvasW);
2139
+ const localLeft = globalLeft - centerX;
1429
2140
  if (Math.abs(userImage.left - localLeft) > 1)
1430
2141
  updates.left = localLeft;
1431
2142
  }
1432
2143
  if (top !== void 0) {
1433
- const localTop = top - centerY;
2144
+ const globalTop = Coordinate.toAbsolute(top, canvasH);
2145
+ const localTop = globalTop - centerY;
1434
2146
  if (Math.abs(userImage.top - localTop) > 1) updates.top = localTop;
1435
2147
  }
1436
2148
  if (width !== void 0 && userImage.width)
@@ -1439,44 +2151,145 @@ var ImageTool = class {
1439
2151
  updates.scaleY = height / userImage.height;
1440
2152
  if (Object.keys(updates).length > 0) {
1441
2153
  userImage.set(updates);
1442
- editor.canvas.requestRenderAll();
2154
+ layer.dirty = true;
2155
+ this.canvasService.requestRenderAll();
1443
2156
  }
1444
2157
  }
1445
2158
  } else {
1446
- this.loadImage(editor, layer, opts);
2159
+ this.loadImage(layer);
1447
2160
  }
1448
2161
  }
1449
- loadImage(editor, layer, opts) {
1450
- const { url } = opts;
2162
+ loadImage(layer) {
2163
+ if (!this.canvasService) return;
2164
+ const { url } = this;
2165
+ if (!url) return;
1451
2166
  this._loadingUrl = url;
1452
- import_core5.Image.fromURL(url).then((image) => {
2167
+ import_fabric5.FabricImage.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2168
+ var _a, _b, _c, _d, _e, _f;
1453
2169
  if (this._loadingUrl !== url) return;
1454
2170
  this._loadingUrl = null;
1455
- const currentOpts = this.options;
1456
- const { opacity, width, height, angle, left, top } = currentOpts;
1457
- const existingImage = editor.getObject("user-image", "user");
2171
+ let { opacity, width, height, angle, left, top } = this;
2172
+ if (this.context) {
2173
+ const configService = this.context.services.get(
2174
+ "ConfigurationService"
2175
+ );
2176
+ const dielineWidth = configService.get("dieline.width");
2177
+ const dielineHeight = configService.get("dieline.height");
2178
+ console.log(
2179
+ "[ImageTool] Dieline config debug:",
2180
+ {
2181
+ widthVal: dielineWidth,
2182
+ heightVal: dielineHeight,
2183
+ // Debug: dump all keys to see what is available
2184
+ allKeys: Array.from(
2185
+ ((_a = configService.configValues) == null ? void 0 : _a.keys()) || []
2186
+ )
2187
+ },
2188
+ configService
2189
+ );
2190
+ if (width === void 0 && height === void 0) {
2191
+ const scale = Math.min(
2192
+ dielineWidth / (image.width || 1),
2193
+ dielineHeight / (image.height || 1)
2194
+ );
2195
+ width = (image.width || 1) * scale;
2196
+ height = (image.height || 1) * scale;
2197
+ this.width = width;
2198
+ this.height = height;
2199
+ }
2200
+ if (left === void 0 && top === void 0) {
2201
+ const dielinePos = configService == null ? void 0 : configService.get("dieline.position");
2202
+ if (dielinePos) {
2203
+ this.left = dielinePos.x;
2204
+ this.top = dielinePos.y;
2205
+ } else {
2206
+ this.left = 0.5;
2207
+ this.top = 0.5;
2208
+ }
2209
+ left = this.left;
2210
+ top = this.top;
2211
+ }
2212
+ }
2213
+ const existingImage = this.canvasService.getObject(
2214
+ "user-image",
2215
+ "user"
2216
+ );
1458
2217
  if (existingImage) {
1459
2218
  const defaultLeft = existingImage.left;
1460
2219
  const defaultTop = existingImage.top;
1461
2220
  const defaultAngle = existingImage.angle;
1462
2221
  const defaultScaleX = existingImage.scaleX;
1463
2222
  const defaultScaleY = existingImage.scaleY;
2223
+ const canvasW = ((_b = this.canvasService) == null ? void 0 : _b.canvas.width) || 800;
2224
+ const canvasH = ((_c = this.canvasService) == null ? void 0 : _c.canvas.height) || 600;
2225
+ const centerX = canvasW / 2;
2226
+ const centerY = canvasH / 2;
2227
+ let targetLeft = left !== void 0 ? left : defaultLeft;
2228
+ let targetTop = top !== void 0 ? top : defaultTop;
2229
+ const configService = (_d = this.context) == null ? void 0 : _d.services.get(
2230
+ "ConfigurationService"
2231
+ );
2232
+ console.log("[ImageTool] Loading EXISTING image...", {
2233
+ canvasW,
2234
+ canvasH,
2235
+ centerX,
2236
+ centerY,
2237
+ incomingLeft: left,
2238
+ incomingTop: top,
2239
+ dielinePos: configService == null ? void 0 : configService.get("dieline.position"),
2240
+ existingImage: !!existingImage
2241
+ });
2242
+ if (left !== void 0) {
2243
+ const globalLeft = Coordinate.toAbsolute(left, canvasW);
2244
+ targetLeft = globalLeft;
2245
+ console.log("[ImageTool] Calculated targetLeft", {
2246
+ globalLeft,
2247
+ targetLeft
2248
+ });
2249
+ }
2250
+ if (top !== void 0) {
2251
+ const globalTop = Coordinate.toAbsolute(top, canvasH);
2252
+ targetTop = globalTop;
2253
+ console.log("[ImageTool] Calculated targetTop", {
2254
+ globalTop,
2255
+ targetTop
2256
+ });
2257
+ }
1464
2258
  image.set({
1465
- left: left !== void 0 ? left : defaultLeft,
1466
- top: top !== void 0 ? top : defaultTop,
2259
+ originX: "center",
2260
+ // Use center origin for easier positioning
2261
+ originY: "center",
2262
+ left: targetLeft,
2263
+ top: targetTop,
1467
2264
  angle: angle !== void 0 ? angle : defaultAngle,
1468
2265
  scaleX: width !== void 0 && image.width ? width / image.width : defaultScaleX,
1469
2266
  scaleY: height !== void 0 && image.height ? height / image.height : defaultScaleY
1470
2267
  });
1471
2268
  layer.remove(existingImage);
1472
2269
  } else {
2270
+ image.set({
2271
+ originX: "center",
2272
+ originY: "center"
2273
+ });
1473
2274
  if (width !== void 0 && image.width)
1474
2275
  image.scaleX = width / image.width;
1475
2276
  if (height !== void 0 && image.height)
1476
2277
  image.scaleY = height / image.height;
1477
2278
  if (angle !== void 0) image.angle = angle;
1478
- if (left !== void 0) image.left = left;
1479
- if (top !== void 0) image.top = top;
2279
+ const canvasW = ((_e = this.canvasService) == null ? void 0 : _e.canvas.width) || 800;
2280
+ const canvasH = ((_f = this.canvasService) == null ? void 0 : _f.canvas.height) || 600;
2281
+ const centerX = canvasW / 2;
2282
+ const centerY = canvasH / 2;
2283
+ if (left !== void 0) {
2284
+ image.left = Coordinate.toAbsolute(left, canvasW);
2285
+ } else {
2286
+ image.left = centerX;
2287
+ }
2288
+ if (top !== void 0) {
2289
+ image.top = Coordinate.toAbsolute(top, canvasH);
2290
+ } else {
2291
+ image.top = centerY;
2292
+ }
1480
2293
  }
1481
2294
  image.set({
1482
2295
  opacity: opacity !== void 0 ? opacity : 1,
@@ -1486,18 +2299,22 @@ var ImageTool = class {
1486
2299
  });
1487
2300
  layer.add(image);
1488
2301
  image.on("modified", (e) => {
2302
+ var _a2, _b2;
1489
2303
  const matrix = image.calcTransformMatrix();
1490
- const globalPoint = import_core5.util.transformPoint(new import_core5.Point(0, 0), matrix);
1491
- this.options.left = globalPoint.x;
1492
- this.options.top = globalPoint.y;
1493
- this.options.angle = e.target.angle;
1494
- if (image.width)
1495
- this.options.width = e.target.width * e.target.scaleX;
1496
- if (image.height)
1497
- this.options.height = e.target.height * e.target.scaleY;
1498
- editor.emit("update");
2304
+ const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
2305
+ const canvasW = ((_a2 = this.canvasService) == null ? void 0 : _a2.canvas.width) || 800;
2306
+ const canvasH = ((_b2 = this.canvasService) == null ? void 0 : _b2.canvas.height) || 600;
2307
+ this.left = Coordinate.toNormalized(globalPoint.x, canvasW);
2308
+ this.top = Coordinate.toNormalized(globalPoint.y, canvasH);
2309
+ this.angle = e.target.angle;
2310
+ if (image.width) this.width = e.target.width * e.target.scaleX;
2311
+ if (image.height) this.height = e.target.height * e.target.scaleY;
2312
+ if (this.context) {
2313
+ this.context.eventBus.emit("update");
2314
+ }
1499
2315
  });
1500
- editor.canvas.requestRenderAll();
2316
+ layer.dirty = true;
2317
+ this.canvasService.requestRenderAll();
1501
2318
  }).catch((err) => {
1502
2319
  if (this._loadingUrl === url) this._loadingUrl = null;
1503
2320
  console.error("Failed to load image", url, err);
@@ -1507,69 +2324,108 @@ var ImageTool = class {
1507
2324
 
1508
2325
  // src/white-ink.ts
1509
2326
  var import_core6 = require("@pooder/core");
2327
+ var import_fabric6 = require("fabric");
1510
2328
  var WhiteInkTool = class {
1511
- constructor() {
1512
- this.name = "WhiteInkTool";
1513
- this.options = {
1514
- customMask: "",
1515
- opacity: 1,
1516
- enableClip: false
1517
- };
1518
- this.schema = {
1519
- customMask: { type: "string", label: "Custom Mask URL" },
1520
- opacity: { type: "number", min: 0, max: 1, step: 0.01, label: "Opacity" },
1521
- enableClip: { type: "boolean", label: "Enable Clip" }
2329
+ constructor(options) {
2330
+ this.id = "pooder.kit.white-ink";
2331
+ this.metadata = {
2332
+ name: "WhiteInkTool"
1522
2333
  };
1523
- this.commands = {
1524
- setWhiteInkImage: {
1525
- execute: (editor, customMask, opacity, enableClip = true) => {
1526
- if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip)
1527
- return true;
1528
- this.options.customMask = customMask;
1529
- this.options.opacity = opacity;
1530
- this.options.enableClip = enableClip;
1531
- this.updateWhiteInk(editor, this.options);
1532
- return true;
1533
- },
1534
- schema: {
1535
- customMask: {
1536
- type: "string",
1537
- label: "Custom Mask URL",
1538
- required: true
1539
- },
1540
- opacity: {
1541
- type: "number",
1542
- label: "Opacity",
1543
- min: 0,
1544
- max: 1,
1545
- required: true
1546
- },
1547
- enableClip: {
1548
- type: "boolean",
1549
- label: "Enable Clip",
1550
- default: true,
1551
- required: false
2334
+ this.customMask = "";
2335
+ this.opacity = 1;
2336
+ this.enableClip = false;
2337
+ this._loadingUrl = null;
2338
+ if (options) {
2339
+ Object.assign(this, options);
2340
+ }
2341
+ }
2342
+ activate(context) {
2343
+ this.canvasService = context.services.get("CanvasService");
2344
+ if (!this.canvasService) {
2345
+ console.warn("CanvasService not found for WhiteInkTool");
2346
+ return;
2347
+ }
2348
+ const configService = context.services.get("ConfigurationService");
2349
+ if (configService) {
2350
+ this.customMask = configService.get(
2351
+ "whiteInk.customMask",
2352
+ this.customMask
2353
+ );
2354
+ this.opacity = configService.get("whiteInk.opacity", this.opacity);
2355
+ this.enableClip = configService.get(
2356
+ "whiteInk.enableClip",
2357
+ this.enableClip
2358
+ );
2359
+ configService.onAnyChange((e) => {
2360
+ if (e.key.startsWith("whiteInk.")) {
2361
+ const prop = e.key.split(".")[1];
2362
+ console.log(
2363
+ `[WhiteInkTool] Config change detected: ${e.key} -> ${e.value}`
2364
+ );
2365
+ if (prop && prop in this) {
2366
+ this[prop] = e.value;
2367
+ this.updateWhiteInk();
1552
2368
  }
1553
2369
  }
1554
- }
1555
- };
1556
- }
1557
- onMount(editor) {
1558
- this.setup(editor);
1559
- this.updateWhiteInk(editor, this.options);
2370
+ });
2371
+ }
2372
+ this.setup();
2373
+ this.updateWhiteInk();
1560
2374
  }
1561
- onUnmount(editor) {
1562
- this.teardown(editor);
2375
+ deactivate(context) {
2376
+ this.teardown();
2377
+ this.canvasService = void 0;
1563
2378
  }
1564
- onDestroy(editor) {
1565
- this.teardown(editor);
2379
+ contribute() {
2380
+ return {
2381
+ [import_core6.ContributionPointIds.CONFIGURATIONS]: [
2382
+ {
2383
+ id: "whiteInk.customMask",
2384
+ type: "string",
2385
+ label: "Custom Mask URL",
2386
+ default: ""
2387
+ },
2388
+ {
2389
+ id: "whiteInk.opacity",
2390
+ type: "number",
2391
+ label: "Opacity",
2392
+ min: 0,
2393
+ max: 1,
2394
+ step: 0.01,
2395
+ default: 1
2396
+ },
2397
+ {
2398
+ id: "whiteInk.enableClip",
2399
+ type: "boolean",
2400
+ label: "Enable Clip",
2401
+ default: false
2402
+ }
2403
+ ],
2404
+ [import_core6.ContributionPointIds.COMMANDS]: [
2405
+ {
2406
+ command: "setWhiteInkImage",
2407
+ title: "Set White Ink Image",
2408
+ handler: (customMask, opacity, enableClip = true) => {
2409
+ if (this.customMask === customMask && this.opacity === opacity && this.enableClip === enableClip)
2410
+ return true;
2411
+ this.customMask = customMask;
2412
+ this.opacity = opacity;
2413
+ this.enableClip = enableClip;
2414
+ this.updateWhiteInk();
2415
+ return true;
2416
+ }
2417
+ }
2418
+ ]
2419
+ };
1566
2420
  }
1567
- setup(editor) {
1568
- let userLayer = editor.getLayer("user");
2421
+ setup() {
2422
+ if (!this.canvasService) return;
2423
+ const canvas = this.canvasService.canvas;
2424
+ let userLayer = this.canvasService.getLayer("user");
1569
2425
  if (!userLayer) {
1570
- userLayer = new import_core6.PooderLayer([], {
1571
- width: editor.state.width,
1572
- height: editor.state.height,
2426
+ userLayer = this.canvasService.createLayer("user", {
2427
+ width: canvas.width,
2428
+ height: canvas.height,
1573
2429
  left: 0,
1574
2430
  top: 0,
1575
2431
  originX: "left",
@@ -1577,61 +2433,58 @@ var WhiteInkTool = class {
1577
2433
  selectable: false,
1578
2434
  evented: true,
1579
2435
  subTargetCheck: true,
1580
- interactive: true,
1581
- data: {
1582
- id: "user"
1583
- }
2436
+ interactive: true
1584
2437
  });
1585
- editor.canvas.add(userLayer);
2438
+ canvas.add(userLayer);
1586
2439
  }
1587
2440
  if (!this.syncHandler) {
1588
2441
  this.syncHandler = (e) => {
1589
2442
  var _a;
1590
2443
  const target = e.target;
1591
2444
  if (target && ((_a = target.data) == null ? void 0 : _a.id) === "user-image") {
1592
- this.syncWithUserImage(editor);
2445
+ this.syncWithUserImage();
1593
2446
  }
1594
2447
  };
1595
- editor.canvas.on("object:moving", this.syncHandler);
1596
- editor.canvas.on("object:scaling", this.syncHandler);
1597
- editor.canvas.on("object:rotating", this.syncHandler);
1598
- editor.canvas.on("object:modified", this.syncHandler);
2448
+ canvas.on("object:moving", this.syncHandler);
2449
+ canvas.on("object:scaling", this.syncHandler);
2450
+ canvas.on("object:rotating", this.syncHandler);
2451
+ canvas.on("object:modified", this.syncHandler);
1599
2452
  }
1600
2453
  }
1601
- teardown(editor) {
2454
+ teardown() {
2455
+ if (!this.canvasService) return;
2456
+ const canvas = this.canvasService.canvas;
1602
2457
  if (this.syncHandler) {
1603
- editor.canvas.off("object:moving", this.syncHandler);
1604
- editor.canvas.off("object:scaling", this.syncHandler);
1605
- editor.canvas.off("object:rotating", this.syncHandler);
1606
- editor.canvas.off("object:modified", this.syncHandler);
2458
+ canvas.off("object:moving", this.syncHandler);
2459
+ canvas.off("object:scaling", this.syncHandler);
2460
+ canvas.off("object:rotating", this.syncHandler);
2461
+ canvas.off("object:modified", this.syncHandler);
1607
2462
  this.syncHandler = void 0;
1608
2463
  }
1609
- const layer = editor.getLayer("user");
2464
+ const layer = this.canvasService.getLayer("user");
1610
2465
  if (layer) {
1611
- const whiteInk = editor.getObject("white-ink", "user");
2466
+ const whiteInk = this.canvasService.getObject("white-ink", "user");
1612
2467
  if (whiteInk) {
1613
2468
  layer.remove(whiteInk);
1614
2469
  }
1615
2470
  }
1616
- const userImage = editor.getObject("user-image", "user");
2471
+ const userImage = this.canvasService.getObject("user-image", "user");
1617
2472
  if (userImage && userImage.clipPath) {
1618
2473
  userImage.set({ clipPath: void 0 });
1619
2474
  }
1620
- editor.canvas.requestRenderAll();
2475
+ this.canvasService.requestRenderAll();
1621
2476
  }
1622
- onUpdate(editor, state) {
1623
- this.updateWhiteInk(editor, this.options);
1624
- }
1625
- updateWhiteInk(editor, opts) {
2477
+ updateWhiteInk() {
1626
2478
  var _a, _b;
1627
- const { customMask, opacity, enableClip } = opts;
1628
- const layer = editor.getLayer("user");
2479
+ if (!this.canvasService) return;
2480
+ const { customMask, opacity, enableClip } = this;
2481
+ const layer = this.canvasService.getLayer("user");
1629
2482
  if (!layer) {
1630
2483
  console.warn("[WhiteInkTool] User layer not found");
1631
2484
  return;
1632
2485
  }
1633
- const whiteInk = editor.getObject("white-ink", "user");
1634
- const userImage = editor.getObject("user-image", "user");
2486
+ const whiteInk = this.canvasService.getObject("white-ink", "user");
2487
+ const userImage = this.canvasService.getObject("user-image", "user");
1635
2488
  if (!customMask) {
1636
2489
  if (whiteInk) {
1637
2490
  layer.remove(whiteInk);
@@ -1639,50 +2492,51 @@ var WhiteInkTool = class {
1639
2492
  if (userImage && userImage.clipPath) {
1640
2493
  userImage.set({ clipPath: void 0 });
1641
2494
  }
1642
- editor.canvas.requestRenderAll();
2495
+ layer.dirty = true;
2496
+ this.canvasService.requestRenderAll();
1643
2497
  return;
1644
2498
  }
1645
2499
  if (whiteInk) {
1646
2500
  const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1647
2501
  if (currentSrc !== customMask) {
1648
- this.loadWhiteInk(
1649
- editor,
1650
- layer,
1651
- customMask,
1652
- opacity,
1653
- enableClip,
1654
- whiteInk
1655
- );
2502
+ this.loadWhiteInk(layer, customMask, opacity, enableClip, whiteInk);
1656
2503
  } else {
1657
2504
  if (whiteInk.opacity !== opacity) {
1658
2505
  whiteInk.set({ opacity });
1659
- editor.canvas.requestRenderAll();
2506
+ layer.dirty = true;
2507
+ this.canvasService.requestRenderAll();
1660
2508
  }
1661
2509
  }
1662
2510
  } else {
1663
- this.loadWhiteInk(editor, layer, customMask, opacity, enableClip);
2511
+ this.loadWhiteInk(layer, customMask, opacity, enableClip);
1664
2512
  }
1665
2513
  if (userImage) {
1666
2514
  if (enableClip) {
1667
2515
  if (!userImage.clipPath) {
1668
- this.applyClipPath(editor, customMask);
2516
+ this.applyClipPath(customMask);
1669
2517
  }
1670
2518
  } else {
1671
2519
  if (userImage.clipPath) {
1672
2520
  userImage.set({ clipPath: void 0 });
1673
- editor.canvas.requestRenderAll();
2521
+ layer.dirty = true;
2522
+ this.canvasService.requestRenderAll();
1674
2523
  }
1675
2524
  }
1676
2525
  }
1677
2526
  }
1678
- loadWhiteInk(editor, layer, url, opacity, enableClip, oldImage) {
1679
- import_core6.Image.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
2527
+ loadWhiteInk(layer, url, opacity, enableClip, oldImage) {
2528
+ if (!this.canvasService) return;
2529
+ if (this._loadingUrl === url) return;
2530
+ this._loadingUrl = url;
2531
+ import_fabric6.FabricImage.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
1680
2532
  var _a;
2533
+ if (this._loadingUrl !== url) return;
2534
+ this._loadingUrl = null;
1681
2535
  if (oldImage) {
1682
2536
  layer.remove(oldImage);
1683
2537
  }
1684
2538
  (_a = image.filters) == null ? void 0 : _a.push(
1685
- new import_core6.filters.BlendColor({
2539
+ new import_fabric6.filters.BlendColor({
1686
2540
  color: "#FFFFFF",
1687
2541
  mode: "add"
1688
2542
  })
@@ -1697,26 +2551,29 @@ var WhiteInkTool = class {
1697
2551
  }
1698
2552
  });
1699
2553
  layer.add(image);
1700
- const userImage = editor.getObject("user-image", "user");
2554
+ const userImage = this.canvasService.getObject("user-image", "user");
1701
2555
  if (userImage) {
1702
2556
  layer.remove(userImage);
1703
2557
  layer.add(userImage);
1704
2558
  }
1705
2559
  if (enableClip) {
1706
- this.applyClipPath(editor, url);
2560
+ this.applyClipPath(url);
1707
2561
  } else if (userImage) {
1708
2562
  userImage.set({ clipPath: void 0 });
1709
2563
  }
1710
- this.syncWithUserImage(editor);
1711
- editor.canvas.requestRenderAll();
2564
+ this.syncWithUserImage();
2565
+ layer.dirty = true;
2566
+ this.canvasService.requestRenderAll();
1712
2567
  }).catch((err) => {
1713
2568
  console.error("Failed to load white ink mask", url, err);
2569
+ this._loadingUrl = null;
1714
2570
  });
1715
2571
  }
1716
- applyClipPath(editor, url) {
1717
- const userImage = editor.getObject("user-image", "user");
2572
+ applyClipPath(url) {
2573
+ if (!this.canvasService) return;
2574
+ const userImage = this.canvasService.getObject("user-image", "user");
1718
2575
  if (!userImage) return;
1719
- import_core6.Image.fromURL(url, { crossOrigin: "anonymous" }).then((maskImage) => {
2576
+ import_fabric6.FabricImage.fromURL(url, { crossOrigin: "anonymous" }).then((maskImage) => {
1720
2577
  maskImage.set({
1721
2578
  originX: "center",
1722
2579
  originY: "center",
@@ -1727,14 +2584,17 @@ var WhiteInkTool = class {
1727
2584
  scaleY: userImage.height / maskImage.height
1728
2585
  });
1729
2586
  userImage.set({ clipPath: maskImage });
1730
- editor.canvas.requestRenderAll();
2587
+ const layer = this.canvasService.getLayer("user");
2588
+ if (layer) layer.dirty = true;
2589
+ this.canvasService.requestRenderAll();
1731
2590
  }).catch((err) => {
1732
2591
  console.error("Failed to load clip path", url, err);
1733
2592
  });
1734
2593
  }
1735
- syncWithUserImage(editor) {
1736
- const userImage = editor.getObject("user-image", "user");
1737
- const whiteInk = editor.getObject("white-ink", "user");
2594
+ syncWithUserImage() {
2595
+ if (!this.canvasService) return;
2596
+ const userImage = this.canvasService.getObject("user-image", "user");
2597
+ const whiteInk = this.canvasService.getObject("white-ink", "user");
1738
2598
  if (userImage && whiteInk) {
1739
2599
  whiteInk.set({
1740
2600
  left: userImage.left,
@@ -1755,114 +2615,172 @@ var WhiteInkTool = class {
1755
2615
 
1756
2616
  // src/ruler.ts
1757
2617
  var import_core7 = require("@pooder/core");
2618
+ var import_fabric7 = require("fabric");
1758
2619
  var RulerTool = class {
1759
- constructor() {
1760
- this.name = "RulerTool";
1761
- this.options = {
1762
- unit: "px",
1763
- thickness: 20,
1764
- backgroundColor: "#f0f0f0",
1765
- textColor: "#333333",
1766
- lineColor: "#999999",
1767
- fontSize: 10
2620
+ constructor(options) {
2621
+ this.id = "pooder.kit.ruler";
2622
+ this.metadata = {
2623
+ name: "RulerTool"
1768
2624
  };
1769
- this.schema = {
1770
- unit: {
1771
- type: "select",
1772
- options: ["px", "mm", "cm", "in"],
1773
- label: "Unit"
1774
- },
1775
- thickness: { type: "number", min: 10, max: 100, label: "Thickness" },
1776
- backgroundColor: { type: "color", label: "Background Color" },
1777
- textColor: { type: "color", label: "Text Color" },
1778
- lineColor: { type: "color", label: "Line Color" },
1779
- fontSize: { type: "number", min: 8, max: 24, label: "Font Size" }
1780
- };
1781
- this.commands = {
1782
- setUnit: {
1783
- execute: (editor, unit) => {
1784
- if (this.options.unit === unit) return true;
1785
- this.options.unit = unit;
1786
- this.updateRuler(editor);
1787
- return true;
1788
- },
1789
- schema: {
1790
- unit: {
1791
- type: "string",
1792
- label: "Unit",
1793
- options: ["px", "mm", "cm", "in"],
1794
- required: true
2625
+ this.unit = "px";
2626
+ this.thickness = 20;
2627
+ this.backgroundColor = "#f0f0f0";
2628
+ this.textColor = "#333333";
2629
+ this.lineColor = "#999999";
2630
+ this.fontSize = 10;
2631
+ if (options) {
2632
+ Object.assign(this, options);
2633
+ }
2634
+ }
2635
+ activate(context) {
2636
+ this.canvasService = context.services.get("CanvasService");
2637
+ if (!this.canvasService) {
2638
+ console.warn("CanvasService not found for RulerTool");
2639
+ return;
2640
+ }
2641
+ const configService = context.services.get("ConfigurationService");
2642
+ if (configService) {
2643
+ this.unit = configService.get("ruler.unit", this.unit);
2644
+ this.thickness = configService.get("ruler.thickness", this.thickness);
2645
+ this.backgroundColor = configService.get(
2646
+ "ruler.backgroundColor",
2647
+ this.backgroundColor
2648
+ );
2649
+ this.textColor = configService.get("ruler.textColor", this.textColor);
2650
+ this.lineColor = configService.get("ruler.lineColor", this.lineColor);
2651
+ this.fontSize = configService.get("ruler.fontSize", this.fontSize);
2652
+ configService.onAnyChange((e) => {
2653
+ if (e.key.startsWith("ruler.")) {
2654
+ const prop = e.key.split(".")[1];
2655
+ if (prop && prop in this) {
2656
+ this[prop] = e.value;
2657
+ this.updateRuler();
1795
2658
  }
1796
2659
  }
1797
- },
1798
- setTheme: {
1799
- execute: (editor, theme) => {
1800
- const newOptions = { ...this.options, ...theme };
1801
- if (JSON.stringify(newOptions) === JSON.stringify(this.options))
2660
+ });
2661
+ }
2662
+ this.createLayer();
2663
+ this.updateRuler();
2664
+ }
2665
+ deactivate(context) {
2666
+ this.destroyLayer();
2667
+ this.canvasService = void 0;
2668
+ }
2669
+ contribute() {
2670
+ return {
2671
+ [import_core7.ContributionPointIds.CONFIGURATIONS]: [
2672
+ {
2673
+ id: "ruler.unit",
2674
+ type: "select",
2675
+ label: "Unit",
2676
+ options: ["px", "mm", "cm", "in"],
2677
+ default: "px"
2678
+ },
2679
+ {
2680
+ id: "ruler.thickness",
2681
+ type: "number",
2682
+ label: "Thickness",
2683
+ min: 10,
2684
+ max: 100,
2685
+ default: 20
2686
+ },
2687
+ {
2688
+ id: "ruler.backgroundColor",
2689
+ type: "color",
2690
+ label: "Background Color",
2691
+ default: "#f0f0f0"
2692
+ },
2693
+ {
2694
+ id: "ruler.textColor",
2695
+ type: "color",
2696
+ label: "Text Color",
2697
+ default: "#333333"
2698
+ },
2699
+ {
2700
+ id: "ruler.lineColor",
2701
+ type: "color",
2702
+ label: "Line Color",
2703
+ default: "#999999"
2704
+ },
2705
+ {
2706
+ id: "ruler.fontSize",
2707
+ type: "number",
2708
+ label: "Font Size",
2709
+ min: 8,
2710
+ max: 24,
2711
+ default: 10
2712
+ }
2713
+ ],
2714
+ [import_core7.ContributionPointIds.COMMANDS]: [
2715
+ {
2716
+ command: "setUnit",
2717
+ title: "Set Ruler Unit",
2718
+ handler: (unit) => {
2719
+ if (this.unit === unit) return true;
2720
+ this.unit = unit;
2721
+ this.updateRuler();
1802
2722
  return true;
1803
- this.options = newOptions;
1804
- this.updateRuler(editor);
1805
- return true;
2723
+ }
1806
2724
  },
1807
- schema: {
1808
- theme: {
1809
- type: "object",
1810
- label: "Theme",
1811
- required: true
2725
+ {
2726
+ command: "setTheme",
2727
+ title: "Set Ruler Theme",
2728
+ handler: (theme) => {
2729
+ const oldState = {
2730
+ backgroundColor: this.backgroundColor,
2731
+ textColor: this.textColor,
2732
+ lineColor: this.lineColor,
2733
+ fontSize: this.fontSize,
2734
+ thickness: this.thickness
2735
+ };
2736
+ const newState = { ...oldState, ...theme };
2737
+ if (JSON.stringify(newState) === JSON.stringify(oldState))
2738
+ return true;
2739
+ Object.assign(this, newState);
2740
+ this.updateRuler();
2741
+ return true;
1812
2742
  }
1813
2743
  }
1814
- }
2744
+ ]
1815
2745
  };
1816
2746
  }
1817
- onMount(editor) {
1818
- this.createLayer(editor);
1819
- this.updateRuler(editor);
1820
- }
1821
- onUnmount(editor) {
1822
- this.destroyLayer(editor);
2747
+ getLayer() {
2748
+ var _a;
2749
+ return (_a = this.canvasService) == null ? void 0 : _a.getLayer("ruler-overlay");
1823
2750
  }
1824
- onUpdate(editor, state) {
1825
- this.updateRuler(editor);
1826
- }
1827
- onDestroy(editor) {
1828
- this.destroyLayer(editor);
1829
- }
1830
- getLayer(editor) {
1831
- return editor.canvas.getObjects().find((obj) => {
1832
- var _a;
1833
- return ((_a = obj.data) == null ? void 0 : _a.id) === "ruler-overlay";
2751
+ createLayer() {
2752
+ if (!this.canvasService) return;
2753
+ const canvas = this.canvasService.canvas;
2754
+ const width = canvas.width || 800;
2755
+ const height = canvas.height || 600;
2756
+ const layer = this.canvasService.createLayer("ruler-overlay", {
2757
+ width,
2758
+ height,
2759
+ selectable: false,
2760
+ evented: false,
2761
+ left: 0,
2762
+ top: 0,
2763
+ originX: "left",
2764
+ originY: "top"
1834
2765
  });
2766
+ canvas.bringObjectToFront(layer);
1835
2767
  }
1836
- createLayer(editor) {
1837
- let layer = this.getLayer(editor);
1838
- if (!layer) {
1839
- const width = editor.canvas.width || 800;
1840
- const height = editor.canvas.height || 600;
1841
- layer = new import_core7.PooderLayer([], {
1842
- width,
1843
- height,
1844
- selectable: false,
1845
- evented: false,
1846
- data: { id: "ruler-overlay" }
1847
- });
1848
- editor.canvas.add(layer);
1849
- }
1850
- editor.canvas.bringObjectToFront(layer);
1851
- }
1852
- destroyLayer(editor) {
1853
- const layer = this.getLayer(editor);
2768
+ destroyLayer() {
2769
+ if (!this.canvasService) return;
2770
+ const layer = this.getLayer();
1854
2771
  if (layer) {
1855
- editor.canvas.remove(layer);
2772
+ this.canvasService.canvas.remove(layer);
1856
2773
  }
1857
2774
  }
1858
- updateRuler(editor) {
1859
- const layer = this.getLayer(editor);
2775
+ updateRuler() {
2776
+ if (!this.canvasService) return;
2777
+ const layer = this.getLayer();
1860
2778
  if (!layer) return;
1861
2779
  layer.remove(...layer.getObjects());
1862
- const { thickness, backgroundColor, lineColor, textColor, fontSize } = this.options;
1863
- const width = editor.canvas.width || 800;
1864
- const height = editor.canvas.height || 600;
1865
- const topBg = new import_core7.Rect({
2780
+ const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
2781
+ const width = this.canvasService.canvas.width || 800;
2782
+ const height = this.canvasService.canvas.height || 600;
2783
+ const topBg = new import_fabric7.Rect({
1866
2784
  left: 0,
1867
2785
  top: 0,
1868
2786
  width,
@@ -1871,7 +2789,7 @@ var RulerTool = class {
1871
2789
  selectable: false,
1872
2790
  evented: false
1873
2791
  });
1874
- const leftBg = new import_core7.Rect({
2792
+ const leftBg = new import_fabric7.Rect({
1875
2793
  left: 0,
1876
2794
  top: 0,
1877
2795
  width: thickness,
@@ -1880,7 +2798,7 @@ var RulerTool = class {
1880
2798
  selectable: false,
1881
2799
  evented: false
1882
2800
  });
1883
- const cornerBg = new import_core7.Rect({
2801
+ const cornerBg = new import_fabric7.Rect({
1884
2802
  left: 0,
1885
2803
  top: 0,
1886
2804
  width: thickness,
@@ -1891,7 +2809,9 @@ var RulerTool = class {
1891
2809
  selectable: false,
1892
2810
  evented: false
1893
2811
  });
1894
- layer.add(topBg, leftBg, cornerBg);
2812
+ layer.add(topBg);
2813
+ layer.add(leftBg);
2814
+ layer.add(cornerBg);
1895
2815
  const step = 100;
1896
2816
  const subStep = 10;
1897
2817
  const midStep = 50;
@@ -1900,7 +2820,7 @@ var RulerTool = class {
1900
2820
  let len = thickness * 0.25;
1901
2821
  if (x % step === 0) len = thickness * 0.8;
1902
2822
  else if (x % midStep === 0) len = thickness * 0.5;
1903
- const line = new import_core7.Line([x, thickness - len, x, thickness], {
2823
+ const line = new import_fabric7.Line([x, thickness - len, x, thickness], {
1904
2824
  stroke: lineColor,
1905
2825
  strokeWidth: 1,
1906
2826
  selectable: false,
@@ -1908,7 +2828,7 @@ var RulerTool = class {
1908
2828
  });
1909
2829
  layer.add(line);
1910
2830
  if (x % step === 0) {
1911
- const text = new import_core7.Text(x.toString(), {
2831
+ const text = new import_fabric7.Text(x.toString(), {
1912
2832
  left: x + 2,
1913
2833
  top: 2,
1914
2834
  fontSize,
@@ -1925,7 +2845,7 @@ var RulerTool = class {
1925
2845
  let len = thickness * 0.25;
1926
2846
  if (y % step === 0) len = thickness * 0.8;
1927
2847
  else if (y % midStep === 0) len = thickness * 0.5;
1928
- const line = new import_core7.Line([thickness - len, y, thickness, y], {
2848
+ const line = new import_fabric7.Line([thickness - len, y, thickness, y], {
1929
2849
  stroke: lineColor,
1930
2850
  strokeWidth: 1,
1931
2851
  selectable: false,
@@ -1933,7 +2853,7 @@ var RulerTool = class {
1933
2853
  });
1934
2854
  layer.add(line);
1935
2855
  if (y % step === 0) {
1936
- const text = new import_core7.Text(y.toString(), {
2856
+ const text = new import_fabric7.Text(y.toString(), {
1937
2857
  angle: -90,
1938
2858
  left: thickness / 2 - fontSize / 3,
1939
2859
  // approximate centering
@@ -1949,62 +2869,80 @@ var RulerTool = class {
1949
2869
  layer.add(text);
1950
2870
  }
1951
2871
  }
1952
- editor.canvas.bringObjectToFront(layer);
1953
- editor.canvas.requestRenderAll();
2872
+ this.canvasService.canvas.bringObjectToFront(layer);
2873
+ this.canvasService.canvas.requestRenderAll();
1954
2874
  }
1955
2875
  };
1956
2876
 
1957
2877
  // src/mirror.ts
2878
+ var import_core8 = require("@pooder/core");
1958
2879
  var MirrorTool = class {
1959
- constructor() {
1960
- this.name = "MirrorTool";
1961
- this.options = {
1962
- enabled: false
2880
+ constructor(options) {
2881
+ this.id = "pooder.kit.mirror";
2882
+ this.metadata = {
2883
+ name: "MirrorTool"
1963
2884
  };
1964
- this.schema = {
1965
- enabled: {
1966
- type: "boolean",
1967
- label: "Mirror View"
1968
- }
1969
- };
1970
- this.commands = {
1971
- toggleMirror: {
1972
- execute: (editor) => {
1973
- this.options.enabled = !this.options.enabled;
1974
- this.applyMirror(editor, this.options.enabled);
1975
- return true;
1976
- }
1977
- },
1978
- setMirror: {
1979
- execute: (editor, enabled) => {
1980
- if (this.options.enabled === enabled) return true;
1981
- this.options.enabled = enabled;
1982
- this.applyMirror(editor, enabled);
1983
- return true;
1984
- },
1985
- schema: {
1986
- enabled: {
1987
- type: "boolean",
1988
- label: "Enabled",
1989
- required: true
1990
- }
1991
- }
1992
- }
2885
+ this.enabled = false;
2886
+ if (options) {
2887
+ Object.assign(this, options);
2888
+ }
2889
+ }
2890
+ toJSON() {
2891
+ return {
2892
+ enabled: this.enabled
1993
2893
  };
1994
2894
  }
1995
- onMount(editor) {
1996
- if (this.options.enabled) {
1997
- this.applyMirror(editor, true);
2895
+ loadFromJSON(json) {
2896
+ this.enabled = json.enabled;
2897
+ }
2898
+ activate(context) {
2899
+ this.canvasService = context.services.get("CanvasService");
2900
+ if (!this.canvasService) {
2901
+ console.warn("CanvasService not found for MirrorTool");
2902
+ return;
2903
+ }
2904
+ const configService = context.services.get("ConfigurationService");
2905
+ if (configService) {
2906
+ this.enabled = configService.get("mirror.enabled", this.enabled);
2907
+ configService.onAnyChange((e) => {
2908
+ if (e.key === "mirror.enabled") {
2909
+ this.applyMirror(e.value);
2910
+ }
2911
+ });
2912
+ }
2913
+ if (this.enabled) {
2914
+ this.applyMirror(true);
1998
2915
  }
1999
2916
  }
2000
- onUpdate(editor) {
2001
- this.applyMirror(editor, this.options.enabled);
2917
+ deactivate(context) {
2918
+ this.applyMirror(false);
2919
+ this.canvasService = void 0;
2002
2920
  }
2003
- onUnmount(editor) {
2004
- this.applyMirror(editor, false);
2921
+ contribute() {
2922
+ return {
2923
+ [import_core8.ContributionPointIds.CONFIGURATIONS]: [
2924
+ {
2925
+ id: "mirror.enabled",
2926
+ type: "boolean",
2927
+ label: "Enable Mirror",
2928
+ default: false
2929
+ }
2930
+ ],
2931
+ [import_core8.ContributionPointIds.COMMANDS]: [
2932
+ {
2933
+ command: "setMirror",
2934
+ title: "Set Mirror",
2935
+ handler: (enabled) => {
2936
+ this.applyMirror(enabled);
2937
+ return true;
2938
+ }
2939
+ }
2940
+ ]
2941
+ };
2005
2942
  }
2006
- applyMirror(editor, enabled) {
2007
- const canvas = editor.canvas;
2943
+ applyMirror(enabled) {
2944
+ if (!this.canvasService) return;
2945
+ const canvas = this.canvasService.canvas;
2008
2946
  if (!canvas) return;
2009
2947
  const width = canvas.width || 800;
2010
2948
  let vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
@@ -2015,17 +2953,85 @@ var MirrorTool = class {
2015
2953
  vpt[4] = width - vpt[4];
2016
2954
  canvas.setViewportTransform(vpt);
2017
2955
  canvas.requestRenderAll();
2956
+ this.enabled = true;
2018
2957
  } else if (!enabled && isFlipped) {
2019
2958
  vpt[0] = -vpt[0];
2020
2959
  vpt[4] = width - vpt[4];
2021
2960
  canvas.setViewportTransform(vpt);
2022
2961
  canvas.requestRenderAll();
2962
+ this.enabled = false;
2963
+ }
2964
+ }
2965
+ };
2966
+
2967
+ // src/CanvasService.ts
2968
+ var import_fabric8 = require("fabric");
2969
+ var CanvasService = class {
2970
+ constructor(el, options) {
2971
+ if (el instanceof import_fabric8.Canvas) {
2972
+ this.canvas = el;
2973
+ } else {
2974
+ this.canvas = new import_fabric8.Canvas(el, {
2975
+ preserveObjectStacking: true,
2976
+ ...options
2977
+ });
2978
+ }
2979
+ }
2980
+ dispose() {
2981
+ this.canvas.dispose();
2982
+ }
2983
+ /**
2984
+ * Get a layer (Group) by its ID.
2985
+ * We assume layers are Groups directly on the canvas with a data.id property.
2986
+ */
2987
+ getLayer(id) {
2988
+ return this.canvas.getObjects().find((obj) => {
2989
+ var _a;
2990
+ return ((_a = obj.data) == null ? void 0 : _a.id) === id;
2991
+ });
2992
+ }
2993
+ /**
2994
+ * Create a layer (Group) with the given ID if it doesn't exist.
2995
+ */
2996
+ createLayer(id, options = {}) {
2997
+ let layer = this.getLayer(id);
2998
+ if (!layer) {
2999
+ const defaultOptions = {
3000
+ selectable: false,
3001
+ evented: false,
3002
+ ...options,
3003
+ data: { ...options.data, id }
3004
+ };
3005
+ layer = new import_fabric8.Group([], defaultOptions);
3006
+ this.canvas.add(layer);
3007
+ }
3008
+ return layer;
3009
+ }
3010
+ /**
3011
+ * Find an object by ID, optionally within a specific layer.
3012
+ */
3013
+ getObject(id, layerId) {
3014
+ if (layerId) {
3015
+ const layer = this.getLayer(layerId);
3016
+ if (!layer) return void 0;
3017
+ return layer.getObjects().find((obj) => {
3018
+ var _a;
3019
+ return ((_a = obj.data) == null ? void 0 : _a.id) === id;
3020
+ });
2023
3021
  }
3022
+ return this.canvas.getObjects().find((obj) => {
3023
+ var _a;
3024
+ return ((_a = obj.data) == null ? void 0 : _a.id) === id;
3025
+ });
3026
+ }
3027
+ requestRenderAll() {
3028
+ this.canvas.requestRenderAll();
2024
3029
  }
2025
3030
  };
2026
3031
  // Annotate the CommonJS export names for ESM import in node:
2027
3032
  0 && (module.exports = {
2028
3033
  BackgroundTool,
3034
+ CanvasService,
2029
3035
  DielineTool,
2030
3036
  FilmTool,
2031
3037
  HoleTool,