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