@pooder/kit 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1654 @@
1
+ // src/background.ts
2
+ import { Image, PooderLayer, Rect } from "@pooder/core";
3
+ var BackgroundTool = class {
4
+ constructor() {
5
+ this.name = "BackgroundTool";
6
+ this.options = {
7
+ color: "",
8
+ url: ""
9
+ };
10
+ this.schema = {
11
+ color: {
12
+ type: "color",
13
+ label: "Background Color"
14
+ },
15
+ url: {
16
+ type: "string",
17
+ label: "Image URL"
18
+ }
19
+ };
20
+ this.commands = {
21
+ reset: {
22
+ execute: (editor) => {
23
+ this.updateBackground(editor, this.options);
24
+ return true;
25
+ }
26
+ },
27
+ clear: {
28
+ execute: (editor) => {
29
+ this.options = {
30
+ color: "transparent",
31
+ url: ""
32
+ };
33
+ this.updateBackground(editor, this.options);
34
+ return true;
35
+ }
36
+ },
37
+ setBackgroundColor: {
38
+ execute: (editor, color) => {
39
+ if (this.options.color === color) return true;
40
+ this.options.color = color;
41
+ this.updateBackground(editor, this.options);
42
+ return true;
43
+ },
44
+ schema: {
45
+ color: {
46
+ type: "string",
47
+ // Should be 'color' if supported by CommandArgSchema, but using 'string' for now as per previous plan
48
+ label: "Background Color",
49
+ required: true
50
+ }
51
+ }
52
+ },
53
+ setBackgroundImage: {
54
+ execute: (editor, url) => {
55
+ if (this.options.url === url) return true;
56
+ this.options.url = url;
57
+ this.updateBackground(editor, this.options);
58
+ return true;
59
+ },
60
+ schema: {
61
+ url: {
62
+ type: "string",
63
+ label: "Image URL",
64
+ required: true
65
+ }
66
+ }
67
+ }
68
+ };
69
+ }
70
+ initLayer(editor) {
71
+ let backgroundLayer = editor.getLayer("background");
72
+ if (!backgroundLayer) {
73
+ backgroundLayer = new PooderLayer([], {
74
+ width: editor.canvas.width,
75
+ height: editor.canvas.height,
76
+ selectable: false,
77
+ evented: false,
78
+ data: {
79
+ id: "background"
80
+ }
81
+ });
82
+ editor.canvas.add(backgroundLayer);
83
+ editor.canvas.sendObjectToBack(backgroundLayer);
84
+ }
85
+ this.updateBackground(editor, this.options);
86
+ }
87
+ onMount(editor) {
88
+ this.initLayer(editor);
89
+ }
90
+ onUnmount(editor) {
91
+ const layer = editor.getLayer("background");
92
+ if (layer) {
93
+ editor.canvas.remove(layer);
94
+ }
95
+ }
96
+ onUpdate(editor, state) {
97
+ this.updateBackground(editor, this.options);
98
+ }
99
+ async updateBackground(editor, options) {
100
+ const layer = editor.getLayer("background");
101
+ if (!layer) {
102
+ console.warn("[BackgroundTool] Background layer not found");
103
+ return;
104
+ }
105
+ const { color, url } = options;
106
+ const width = editor.state.width;
107
+ const height = editor.state.height;
108
+ let rect = editor.getObject("background-color-rect", "background");
109
+ if (rect) {
110
+ rect.set({
111
+ fill: color
112
+ });
113
+ } else {
114
+ rect = new Rect({
115
+ width,
116
+ height,
117
+ fill: color,
118
+ selectable: false,
119
+ evented: false,
120
+ data: {
121
+ id: "background-color-rect"
122
+ }
123
+ });
124
+ layer.add(rect);
125
+ layer.sendObjectToBack(rect);
126
+ }
127
+ let img = editor.getObject("background-image", "background");
128
+ try {
129
+ if (img) {
130
+ if (img.getSrc() !== url) {
131
+ if (url) {
132
+ await img.setSrc(url);
133
+ } else {
134
+ layer.remove(img);
135
+ }
136
+ }
137
+ } else {
138
+ if (url) {
139
+ img = await Image.fromURL(url, { crossOrigin: "anonymous" });
140
+ img.set({
141
+ originX: "left",
142
+ originY: "top",
143
+ left: 0,
144
+ top: 0,
145
+ selectable: false,
146
+ evented: false,
147
+ data: {
148
+ id: "background-image"
149
+ }
150
+ });
151
+ img.scaleToWidth(width);
152
+ if (img.getScaledHeight() < height)
153
+ img.scaleToHeight(height);
154
+ layer.add(img);
155
+ }
156
+ }
157
+ editor.canvas.requestRenderAll();
158
+ } catch (e) {
159
+ console.error("[BackgroundTool] Failed to load image", e);
160
+ }
161
+ }
162
+ };
163
+
164
+ // src/dieline.ts
165
+ import { Path, PooderLayer as PooderLayer2, Pattern } from "@pooder/core";
166
+
167
+ // src/geometry.ts
168
+ import paper from "paper";
169
+ function ensurePaper(width, height) {
170
+ if (!paper.project) {
171
+ paper.setup(new paper.Size(width, height));
172
+ }
173
+ }
174
+ function createBaseShape(options) {
175
+ const { shape, width, height, radius, x, y } = options;
176
+ const center = new paper.Point(x, y);
177
+ if (shape === "rect") {
178
+ return new paper.Path.Rectangle({
179
+ point: [x - width / 2, y - height / 2],
180
+ size: [Math.max(0, width), Math.max(0, height)],
181
+ radius: Math.max(0, radius)
182
+ });
183
+ } else if (shape === "circle") {
184
+ const r = Math.min(width, height) / 2;
185
+ return new paper.Path.Circle({
186
+ center,
187
+ radius: Math.max(0, r)
188
+ });
189
+ } else {
190
+ return new paper.Path.Ellipse({
191
+ center,
192
+ radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
193
+ });
194
+ }
195
+ }
196
+ function getDielineShape(options) {
197
+ let mainShape = createBaseShape(options);
198
+ const { holes } = options;
199
+ if (holes && holes.length > 0) {
200
+ let lugsPath = null;
201
+ let cutsPath = null;
202
+ holes.forEach((hole) => {
203
+ const lug = new paper.Path.Circle({
204
+ center: [hole.x, hole.y],
205
+ radius: hole.outerRadius
206
+ });
207
+ if (!mainShape.intersects(lug) && !mainShape.contains(lug.position)) {
208
+ lug.remove();
209
+ return;
210
+ }
211
+ const cut = new paper.Path.Circle({
212
+ center: [hole.x, hole.y],
213
+ radius: hole.innerRadius
214
+ });
215
+ if (!lugsPath) {
216
+ lugsPath = lug;
217
+ } else {
218
+ const temp = lugsPath.unite(lug);
219
+ lugsPath.remove();
220
+ lug.remove();
221
+ lugsPath = temp;
222
+ }
223
+ if (!cutsPath) {
224
+ cutsPath = cut;
225
+ } else {
226
+ const temp = cutsPath.unite(cut);
227
+ cutsPath.remove();
228
+ cut.remove();
229
+ cutsPath = temp;
230
+ }
231
+ });
232
+ if (lugsPath) {
233
+ const temp = mainShape.unite(lugsPath);
234
+ mainShape.remove();
235
+ lugsPath.remove();
236
+ mainShape = temp;
237
+ }
238
+ if (cutsPath) {
239
+ const temp = mainShape.subtract(cutsPath);
240
+ mainShape.remove();
241
+ cutsPath.remove();
242
+ mainShape = temp;
243
+ }
244
+ }
245
+ return mainShape;
246
+ }
247
+ function generateDielinePath(options) {
248
+ ensurePaper(options.width * 2, options.height * 2);
249
+ paper.project.activeLayer.removeChildren();
250
+ const mainShape = getDielineShape(options);
251
+ const pathData = mainShape.pathData;
252
+ mainShape.remove();
253
+ return pathData;
254
+ }
255
+ function generateMaskPath(options) {
256
+ ensurePaper(options.canvasWidth, options.canvasHeight);
257
+ paper.project.activeLayer.removeChildren();
258
+ const { canvasWidth, canvasHeight } = options;
259
+ const maskRect = new paper.Path.Rectangle({
260
+ point: [0, 0],
261
+ size: [canvasWidth, canvasHeight]
262
+ });
263
+ const mainShape = getDielineShape(options);
264
+ const finalMask = maskRect.subtract(mainShape);
265
+ maskRect.remove();
266
+ mainShape.remove();
267
+ const pathData = finalMask.pathData;
268
+ finalMask.remove();
269
+ return pathData;
270
+ }
271
+ function generateBleedZonePath(options, offset) {
272
+ const maxDim = Math.max(options.width, options.height) + Math.abs(offset) * 4;
273
+ ensurePaper(maxDim, maxDim);
274
+ paper.project.activeLayer.removeChildren();
275
+ const shapeOriginal = getDielineShape(options);
276
+ const offsetOptions = {
277
+ ...options,
278
+ width: Math.max(0, options.width + offset * 2),
279
+ height: Math.max(0, options.height + offset * 2),
280
+ radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset)
281
+ };
282
+ const shapeOffset = getDielineShape(offsetOptions);
283
+ let bleedZone;
284
+ if (offset > 0) {
285
+ bleedZone = shapeOffset.subtract(shapeOriginal);
286
+ } else {
287
+ bleedZone = shapeOriginal.subtract(shapeOffset);
288
+ }
289
+ const pathData = bleedZone.pathData;
290
+ shapeOriginal.remove();
291
+ shapeOffset.remove();
292
+ bleedZone.remove();
293
+ return pathData;
294
+ }
295
+ function getNearestPointOnDieline(point, options) {
296
+ ensurePaper(options.width * 2, options.height * 2);
297
+ paper.project.activeLayer.removeChildren();
298
+ const shape = createBaseShape(options);
299
+ const p = new paper.Point(point.x, point.y);
300
+ const nearest = shape.getNearestPoint(p);
301
+ const result = { x: nearest.x, y: nearest.y };
302
+ shape.remove();
303
+ return result;
304
+ }
305
+
306
+ // src/dieline.ts
307
+ var DielineTool = class {
308
+ constructor() {
309
+ this.name = "DielineTool";
310
+ this.options = {
311
+ shape: "rect",
312
+ width: 300,
313
+ height: 300,
314
+ radius: 0,
315
+ offset: 0,
316
+ style: "solid",
317
+ insideColor: "rgba(0,0,0,0)",
318
+ outsideColor: "#ffffff"
319
+ };
320
+ this.schema = {
321
+ shape: {
322
+ type: "select",
323
+ options: ["rect", "circle", "ellipse"],
324
+ label: "Shape"
325
+ },
326
+ width: { type: "number", min: 10, max: 2e3, label: "Width" },
327
+ height: { type: "number", min: 10, max: 2e3, label: "Height" },
328
+ radius: { type: "number", min: 0, max: 500, label: "Corner Radius" },
329
+ position: { type: "string", label: "Position" },
330
+ // Complex object, simplified for now or need custom handler
331
+ borderLength: { type: "number", min: 0, max: 500, label: "Margin" },
332
+ offset: { type: "number", min: -100, max: 100, label: "Bleed Offset" },
333
+ style: {
334
+ type: "select",
335
+ options: ["solid", "dashed"],
336
+ label: "Line Style"
337
+ },
338
+ insideColor: { type: "color", label: "Inside Color" },
339
+ outsideColor: { type: "color", label: "Outside Color" }
340
+ };
341
+ this.commands = {
342
+ reset: {
343
+ execute: (editor) => {
344
+ this.options = {
345
+ shape: "rect",
346
+ width: 300,
347
+ height: 300,
348
+ radius: 0,
349
+ offset: 0,
350
+ style: "solid",
351
+ insideColor: "rgba(0,0,0,0)",
352
+ outsideColor: "#ffffff"
353
+ };
354
+ this.updateDieline(editor);
355
+ return true;
356
+ }
357
+ },
358
+ destroy: {
359
+ execute: (editor) => {
360
+ this.destroyLayer(editor);
361
+ return true;
362
+ }
363
+ },
364
+ setDimensions: {
365
+ execute: (editor, width, height) => {
366
+ if (this.options.width === width && this.options.height === height) return true;
367
+ this.options.width = width;
368
+ this.options.height = height;
369
+ this.updateDieline(editor);
370
+ return true;
371
+ },
372
+ schema: {
373
+ width: {
374
+ type: "number",
375
+ label: "Width",
376
+ min: 10,
377
+ max: 2e3,
378
+ required: true
379
+ },
380
+ height: {
381
+ type: "number",
382
+ label: "Height",
383
+ min: 10,
384
+ max: 2e3,
385
+ required: true
386
+ }
387
+ }
388
+ },
389
+ setShape: {
390
+ execute: (editor, shape) => {
391
+ if (this.options.shape === shape) return true;
392
+ this.options.shape = shape;
393
+ this.updateDieline(editor);
394
+ return true;
395
+ },
396
+ schema: {
397
+ shape: {
398
+ type: "string",
399
+ label: "Shape",
400
+ options: ["rect", "circle", "ellipse"],
401
+ required: true
402
+ }
403
+ }
404
+ },
405
+ setBleed: {
406
+ execute: (editor, bleed) => {
407
+ if (this.options.offset === bleed) return true;
408
+ this.options.offset = bleed;
409
+ this.updateDieline(editor);
410
+ return true;
411
+ },
412
+ schema: {
413
+ bleed: {
414
+ type: "number",
415
+ label: "Bleed",
416
+ min: -100,
417
+ max: 100,
418
+ required: true
419
+ }
420
+ }
421
+ }
422
+ };
423
+ }
424
+ onMount(editor) {
425
+ this.createLayer(editor);
426
+ }
427
+ onUnmount(editor) {
428
+ this.destroyLayer(editor);
429
+ }
430
+ onUpdate(editor, state) {
431
+ this.updateDieline(editor);
432
+ }
433
+ onDestroy(editor) {
434
+ this.destroyLayer(editor);
435
+ }
436
+ getLayer(editor, id) {
437
+ return editor.canvas.getObjects().find((obj) => {
438
+ var _a;
439
+ return ((_a = obj.data) == null ? void 0 : _a.id) === id;
440
+ });
441
+ }
442
+ createLayer(editor) {
443
+ let layer = this.getLayer(editor, "dieline-overlay");
444
+ if (!layer) {
445
+ const width = editor.canvas.width || 800;
446
+ const height = editor.canvas.height || 600;
447
+ layer = new PooderLayer2([], {
448
+ width,
449
+ height,
450
+ selectable: false,
451
+ evented: false,
452
+ data: { id: "dieline-overlay" }
453
+ });
454
+ editor.canvas.add(layer);
455
+ }
456
+ editor.canvas.bringObjectToFront(layer);
457
+ this.updateDieline(editor);
458
+ }
459
+ destroyLayer(editor) {
460
+ const layer = this.getLayer(editor, "dieline-overlay");
461
+ if (layer) {
462
+ editor.canvas.remove(layer);
463
+ }
464
+ }
465
+ createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
466
+ if (typeof document === "undefined") {
467
+ return void 0;
468
+ }
469
+ const size = 20;
470
+ const canvas = document.createElement("canvas");
471
+ canvas.width = size;
472
+ canvas.height = size;
473
+ const ctx = canvas.getContext("2d");
474
+ if (ctx) {
475
+ ctx.clearRect(0, 0, size, size);
476
+ ctx.strokeStyle = color;
477
+ ctx.lineWidth = 1;
478
+ ctx.beginPath();
479
+ ctx.moveTo(0, size);
480
+ ctx.lineTo(size, 0);
481
+ ctx.stroke();
482
+ }
483
+ return new Pattern({ source: canvas, repetition: "repeat" });
484
+ }
485
+ updateDieline(editor) {
486
+ var _a, _b;
487
+ const { shape, radius, offset, style, insideColor, outsideColor, position, borderLength } = this.options;
488
+ let { width, height } = this.options;
489
+ const canvasW = editor.canvas.width || 800;
490
+ const canvasH = editor.canvas.height || 600;
491
+ if (borderLength && borderLength > 0) {
492
+ width = Math.max(0, canvasW - borderLength * 2);
493
+ height = Math.max(0, canvasH - borderLength * 2);
494
+ }
495
+ const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
496
+ const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
497
+ const layer = this.getLayer(editor, "dieline-overlay");
498
+ if (!layer) return;
499
+ layer.remove(...layer.getObjects());
500
+ const holeTool = editor.getExtension("HoleTool");
501
+ const holes = holeTool ? holeTool.options.holes || [] : [];
502
+ const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
503
+ const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
504
+ const holeData = holes.map((h) => ({
505
+ x: h.x,
506
+ y: h.y,
507
+ innerRadius,
508
+ outerRadius
509
+ }));
510
+ const cutW = Math.max(0, width + offset * 2);
511
+ const cutH = Math.max(0, height + offset * 2);
512
+ const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
513
+ const maskPathData = generateMaskPath({
514
+ canvasWidth: canvasW,
515
+ canvasHeight: canvasH,
516
+ shape,
517
+ width: cutW,
518
+ height: cutH,
519
+ radius: cutR,
520
+ x: cx,
521
+ y: cy,
522
+ holes: holeData
523
+ });
524
+ const mask = new Path(maskPathData, {
525
+ fill: outsideColor,
526
+ stroke: null,
527
+ selectable: false,
528
+ evented: false,
529
+ originX: "left",
530
+ originY: "top",
531
+ left: 0,
532
+ top: 0
533
+ });
534
+ layer.add(mask);
535
+ if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)") {
536
+ const productPathData = generateDielinePath({
537
+ shape,
538
+ width: cutW,
539
+ height: cutH,
540
+ radius: cutR,
541
+ x: cx,
542
+ y: cy,
543
+ holes: holeData
544
+ });
545
+ const insideObj = new Path(productPathData, {
546
+ fill: insideColor,
547
+ stroke: null,
548
+ selectable: false,
549
+ evented: false,
550
+ originX: "left",
551
+ // paper.js paths are absolute
552
+ originY: "top"
553
+ });
554
+ layer.add(insideObj);
555
+ }
556
+ if (offset !== 0) {
557
+ const bleedPathData = generateBleedZonePath({
558
+ shape,
559
+ width,
560
+ height,
561
+ radius,
562
+ x: cx,
563
+ y: cy,
564
+ holes: holeData
565
+ }, offset);
566
+ const pattern = this.createHatchPattern("red");
567
+ if (pattern) {
568
+ const bleedObj = new Path(bleedPathData, {
569
+ fill: pattern,
570
+ stroke: null,
571
+ selectable: false,
572
+ evented: false,
573
+ objectCaching: false,
574
+ originX: "left",
575
+ originY: "top"
576
+ });
577
+ layer.add(bleedObj);
578
+ }
579
+ const offsetPathData = generateDielinePath({
580
+ shape,
581
+ width: cutW,
582
+ height: cutH,
583
+ radius: cutR,
584
+ x: cx,
585
+ y: cy,
586
+ holes: holeData
587
+ });
588
+ const offsetBorderObj = new Path(offsetPathData, {
589
+ fill: null,
590
+ stroke: "#666",
591
+ // Grey
592
+ strokeWidth: 1,
593
+ strokeDashArray: [4, 4],
594
+ // Dashed
595
+ selectable: false,
596
+ evented: false,
597
+ originX: "left",
598
+ originY: "top"
599
+ });
600
+ layer.add(offsetBorderObj);
601
+ }
602
+ const borderPathData = generateDielinePath({
603
+ shape,
604
+ width,
605
+ height,
606
+ radius,
607
+ x: cx,
608
+ y: cy,
609
+ holes: holeData
610
+ });
611
+ const borderObj = new Path(borderPathData, {
612
+ fill: "transparent",
613
+ stroke: "red",
614
+ strokeWidth: 1,
615
+ strokeDashArray: style === "dashed" ? [5, 5] : void 0,
616
+ selectable: false,
617
+ evented: false,
618
+ originX: "left",
619
+ originY: "top"
620
+ });
621
+ layer.add(borderObj);
622
+ editor.canvas.requestRenderAll();
623
+ }
624
+ getGeometry(editor) {
625
+ var _a, _b;
626
+ const { shape, width, height, radius, position, borderLength } = this.options;
627
+ const canvasW = editor.canvas.width || 800;
628
+ const canvasH = editor.canvas.height || 600;
629
+ let visualWidth = width;
630
+ let visualHeight = height;
631
+ if (borderLength && borderLength > 0) {
632
+ visualWidth = Math.max(0, canvasW - borderLength * 2);
633
+ visualHeight = Math.max(0, canvasH - borderLength * 2);
634
+ }
635
+ const cx = (_a = position == null ? void 0 : position.x) != null ? _a : canvasW / 2;
636
+ const cy = (_b = position == null ? void 0 : position.y) != null ? _b : canvasH / 2;
637
+ return {
638
+ shape,
639
+ x: cx,
640
+ y: cy,
641
+ width: visualWidth,
642
+ height: visualHeight,
643
+ radius
644
+ };
645
+ }
646
+ };
647
+
648
+ // src/film.ts
649
+ import { Image as Image2, PooderLayer as PooderLayer3 } from "@pooder/core";
650
+ var FilmTool = class {
651
+ constructor() {
652
+ this.name = "FilmTool";
653
+ this.options = {
654
+ url: "",
655
+ opacity: 0.5
656
+ };
657
+ this.schema = {
658
+ url: {
659
+ type: "string",
660
+ label: "Film Image URL"
661
+ },
662
+ opacity: {
663
+ type: "number",
664
+ min: 0,
665
+ max: 1,
666
+ step: 0.1,
667
+ label: "Opacity"
668
+ }
669
+ };
670
+ this.commands = {
671
+ setFilmImage: {
672
+ execute: (editor, url, opacity) => {
673
+ if (this.options.url === url && this.options.opacity === opacity) return true;
674
+ this.options.url = url;
675
+ this.options.opacity = opacity;
676
+ this.updateFilm(editor, this.options);
677
+ return true;
678
+ },
679
+ schema: {
680
+ url: {
681
+ type: "string",
682
+ label: "Image URL",
683
+ required: true
684
+ },
685
+ opacity: {
686
+ type: "number",
687
+ label: "Opacity",
688
+ min: 0,
689
+ max: 1,
690
+ required: true
691
+ }
692
+ }
693
+ }
694
+ };
695
+ }
696
+ onMount(editor) {
697
+ this.initLayer(editor);
698
+ this.updateFilm(editor, this.options);
699
+ }
700
+ onUnmount(editor) {
701
+ const layer = editor.getLayer("overlay");
702
+ if (layer) {
703
+ const img = editor.getObject("film-image", "overlay");
704
+ if (img) {
705
+ layer.remove(img);
706
+ editor.canvas.requestRenderAll();
707
+ }
708
+ }
709
+ }
710
+ onUpdate(editor, state) {
711
+ }
712
+ initLayer(editor) {
713
+ let overlayLayer = editor.getLayer("overlay");
714
+ if (!overlayLayer) {
715
+ const width = editor.state.width;
716
+ const height = editor.state.height;
717
+ const layer = new PooderLayer3([], {
718
+ width,
719
+ height,
720
+ left: 0,
721
+ top: 0,
722
+ originX: "left",
723
+ originY: "top",
724
+ selectable: false,
725
+ evented: false,
726
+ subTargetCheck: false,
727
+ interactive: false,
728
+ data: {
729
+ id: "overlay"
730
+ }
731
+ });
732
+ editor.canvas.add(layer);
733
+ editor.canvas.bringObjectToFront(layer);
734
+ }
735
+ }
736
+ async updateFilm(editor, options) {
737
+ const layer = editor.getLayer("overlay");
738
+ if (!layer) {
739
+ console.warn("[FilmTool] Overlay layer not found");
740
+ return;
741
+ }
742
+ const { url, opacity } = options;
743
+ if (!url) {
744
+ const img2 = editor.getObject("film-image", "overlay");
745
+ if (img2) {
746
+ layer.remove(img2);
747
+ editor.canvas.requestRenderAll();
748
+ }
749
+ return;
750
+ }
751
+ const width = editor.state.width;
752
+ const height = editor.state.height;
753
+ let img = editor.getObject("film-image", "overlay");
754
+ try {
755
+ if (img) {
756
+ if (img.getSrc() !== url) {
757
+ await img.setSrc(url);
758
+ }
759
+ img.set({ opacity });
760
+ } else {
761
+ img = await Image2.fromURL(url, { crossOrigin: "anonymous" });
762
+ img.scaleToWidth(width);
763
+ if (img.getScaledHeight() < height)
764
+ img.scaleToHeight(height);
765
+ img.set({
766
+ originX: "left",
767
+ originY: "top",
768
+ left: 0,
769
+ top: 0,
770
+ opacity,
771
+ selectable: false,
772
+ evented: false,
773
+ data: { id: "film-image" }
774
+ });
775
+ layer.add(img);
776
+ }
777
+ editor.canvas.requestRenderAll();
778
+ } catch (error) {
779
+ console.error("[FilmTool] Failed to load film image", url, error);
780
+ }
781
+ }
782
+ };
783
+
784
+ // src/hole.ts
785
+ import { Circle as Circle2, Group, Point } from "@pooder/core";
786
+ var HoleTool = class {
787
+ constructor() {
788
+ this.name = "HoleTool";
789
+ this.options = {
790
+ innerRadius: 15,
791
+ outerRadius: 25,
792
+ style: "solid",
793
+ holes: []
794
+ };
795
+ this.schema = {
796
+ innerRadius: {
797
+ type: "number",
798
+ min: 1,
799
+ max: 100,
800
+ label: "Inner Radius"
801
+ },
802
+ outerRadius: {
803
+ type: "number",
804
+ min: 1,
805
+ max: 100,
806
+ label: "Outer Radius"
807
+ },
808
+ style: {
809
+ type: "select",
810
+ options: ["solid", "dashed"],
811
+ label: "Line Style"
812
+ },
813
+ holes: {
814
+ type: "json",
815
+ label: "Holes"
816
+ }
817
+ };
818
+ this.handleMoving = null;
819
+ this.handleModified = null;
820
+ this.commands = {
821
+ reset: {
822
+ execute: (editor) => {
823
+ let defaultPos = { x: editor.canvas.width / 2, y: 50 };
824
+ const g = this.getDielineGeometry(editor);
825
+ if (g) {
826
+ const topCenter = { x: g.x, y: g.y - g.height / 2 };
827
+ defaultPos = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
828
+ }
829
+ this.options = {
830
+ innerRadius: 15,
831
+ outerRadius: 25,
832
+ style: "solid",
833
+ holes: [defaultPos]
834
+ };
835
+ this.redraw(editor);
836
+ const dielineTool = editor.getExtension("DielineTool");
837
+ if (dielineTool && dielineTool.updateDieline) {
838
+ dielineTool.updateDieline(editor);
839
+ }
840
+ return true;
841
+ }
842
+ },
843
+ addHole: {
844
+ execute: (editor, x, y) => {
845
+ if (!this.options.holes) this.options.holes = [];
846
+ this.options.holes.push({ x, y });
847
+ this.redraw(editor);
848
+ const dielineTool = editor.getExtension("DielineTool");
849
+ if (dielineTool && dielineTool.updateDieline) {
850
+ dielineTool.updateDieline(editor);
851
+ }
852
+ return true;
853
+ },
854
+ schema: {
855
+ x: {
856
+ type: "number",
857
+ label: "X Position",
858
+ required: true
859
+ },
860
+ y: {
861
+ type: "number",
862
+ label: "Y Position",
863
+ required: true
864
+ }
865
+ }
866
+ },
867
+ clearHoles: {
868
+ execute: (editor) => {
869
+ this.options.holes = [];
870
+ this.redraw(editor);
871
+ const dielineTool = editor.getExtension("DielineTool");
872
+ if (dielineTool && dielineTool.updateDieline) {
873
+ dielineTool.updateDieline(editor);
874
+ }
875
+ return true;
876
+ }
877
+ }
878
+ };
879
+ }
880
+ onMount(editor) {
881
+ this.setup(editor);
882
+ }
883
+ onUnmount(editor) {
884
+ this.teardown(editor);
885
+ }
886
+ onDestroy(editor) {
887
+ this.teardown(editor);
888
+ }
889
+ getDielineGeometry(editor) {
890
+ const dielineTool = editor.getExtension("DielineTool");
891
+ if (!dielineTool) return null;
892
+ const geometry = dielineTool.getGeometry(editor);
893
+ if (!geometry) return null;
894
+ const offset = dielineTool.options.offset || 0;
895
+ return {
896
+ ...geometry,
897
+ width: Math.max(0, geometry.width + offset * 2),
898
+ height: Math.max(0, geometry.height + offset * 2),
899
+ radius: Math.max(0, geometry.radius + offset)
900
+ };
901
+ }
902
+ setup(editor) {
903
+ if (!this.handleMoving) {
904
+ this.handleMoving = (e) => {
905
+ var _a;
906
+ const target = e.target;
907
+ if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
908
+ const geometry = this.getDielineGeometry(editor);
909
+ if (!geometry) return;
910
+ const p = new Point(target.left, target.top);
911
+ const newPos = this.calculateConstrainedPosition(p, geometry);
912
+ target.set({
913
+ left: newPos.x,
914
+ top: newPos.y
915
+ });
916
+ };
917
+ editor.canvas.on("object:moving", this.handleMoving);
918
+ }
919
+ if (!this.handleModified) {
920
+ this.handleModified = (e) => {
921
+ var _a;
922
+ const target = e.target;
923
+ if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "hole-marker") return;
924
+ this.syncHolesFromCanvas(editor);
925
+ };
926
+ editor.canvas.on("object:modified", this.handleModified);
927
+ }
928
+ const opts = this.options;
929
+ if (!opts.holes || opts.holes.length === 0) {
930
+ let defaultPos = { x: editor.canvas.width / 2, y: 50 };
931
+ const g = this.getDielineGeometry(editor);
932
+ if (g) {
933
+ const topCenter = { x: g.x, y: g.y - g.height / 2 };
934
+ const snapped = getNearestPointOnDieline(topCenter, { ...g, holes: [] });
935
+ defaultPos = snapped;
936
+ }
937
+ opts.holes = [defaultPos];
938
+ }
939
+ this.options = { ...opts };
940
+ this.redraw(editor);
941
+ const dielineTool = editor.getExtension("DielineTool");
942
+ if (dielineTool && dielineTool.updateDieline) {
943
+ dielineTool.updateDieline(editor);
944
+ }
945
+ }
946
+ teardown(editor) {
947
+ if (this.handleMoving) {
948
+ editor.canvas.off("object:moving", this.handleMoving);
949
+ this.handleMoving = null;
950
+ }
951
+ if (this.handleModified) {
952
+ editor.canvas.off("object:modified", this.handleModified);
953
+ this.handleModified = null;
954
+ }
955
+ const objects = editor.canvas.getObjects().filter((obj) => {
956
+ var _a;
957
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
958
+ });
959
+ objects.forEach((obj) => editor.canvas.remove(obj));
960
+ editor.canvas.requestRenderAll();
961
+ }
962
+ onUpdate(editor, state) {
963
+ }
964
+ syncHolesFromCanvas(editor) {
965
+ const objects = editor.canvas.getObjects().filter((obj) => {
966
+ var _a;
967
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
968
+ });
969
+ const holes = objects.map((obj) => ({ x: obj.left, y: obj.top }));
970
+ this.options.holes = holes;
971
+ const dielineTool = editor.getExtension("DielineTool");
972
+ if (dielineTool && dielineTool.updateDieline) {
973
+ dielineTool.updateDieline(editor);
974
+ }
975
+ }
976
+ redraw(editor) {
977
+ const canvas = editor.canvas;
978
+ const existing = canvas.getObjects().filter((obj) => {
979
+ var _a;
980
+ return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker";
981
+ });
982
+ existing.forEach((obj) => canvas.remove(obj));
983
+ const { innerRadius, outerRadius, style, holes } = this.options;
984
+ if (!holes || holes.length === 0) {
985
+ canvas.requestRenderAll();
986
+ return;
987
+ }
988
+ holes.forEach((hole, index) => {
989
+ const innerCircle = new Circle2({
990
+ radius: innerRadius,
991
+ fill: "transparent",
992
+ stroke: "red",
993
+ strokeWidth: 2,
994
+ originX: "center",
995
+ originY: "center"
996
+ });
997
+ const outerCircle = new Circle2({
998
+ radius: outerRadius,
999
+ fill: "transparent",
1000
+ stroke: "#666",
1001
+ strokeWidth: 1,
1002
+ strokeDashArray: style === "dashed" ? [5, 5] : void 0,
1003
+ originX: "center",
1004
+ originY: "center"
1005
+ });
1006
+ const holeGroup = new Group([outerCircle, innerCircle], {
1007
+ left: hole.x,
1008
+ top: hole.y,
1009
+ originX: "center",
1010
+ originY: "center",
1011
+ selectable: true,
1012
+ hasControls: false,
1013
+ // Don't allow resizing/rotating
1014
+ hasBorders: false,
1015
+ subTargetCheck: false,
1016
+ opacity: 0,
1017
+ // Default hidden
1018
+ hoverCursor: "move",
1019
+ data: { type: "hole-marker", index }
1020
+ });
1021
+ holeGroup.name = "hole-marker";
1022
+ holeGroup.on("mouseover", () => {
1023
+ holeGroup.set("opacity", 1);
1024
+ canvas.requestRenderAll();
1025
+ });
1026
+ holeGroup.on("mouseout", () => {
1027
+ if (canvas.getActiveObject() !== holeGroup) {
1028
+ holeGroup.set("opacity", 0);
1029
+ canvas.requestRenderAll();
1030
+ }
1031
+ });
1032
+ holeGroup.on("selected", () => {
1033
+ holeGroup.set("opacity", 1);
1034
+ canvas.requestRenderAll();
1035
+ });
1036
+ holeGroup.on("deselected", () => {
1037
+ holeGroup.set("opacity", 0);
1038
+ canvas.requestRenderAll();
1039
+ });
1040
+ canvas.add(holeGroup);
1041
+ canvas.bringObjectToFront(holeGroup);
1042
+ });
1043
+ canvas.requestRenderAll();
1044
+ }
1045
+ calculateConstrainedPosition(p, g) {
1046
+ const options = {
1047
+ ...g,
1048
+ holes: []
1049
+ // We don't need holes for boundary calculation
1050
+ };
1051
+ const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, options);
1052
+ const nearestP = new Point(nearest.x, nearest.y);
1053
+ const dist = p.distanceFrom(nearestP);
1054
+ const v = p.subtract(nearestP);
1055
+ const center = new Point(g.x, g.y);
1056
+ const centerToNearest = nearestP.subtract(center);
1057
+ const distToCenter = p.distanceFrom(center);
1058
+ const nearestDistToCenter = nearestP.distanceFrom(center);
1059
+ let signedDist = dist;
1060
+ if (distToCenter < nearestDistToCenter) {
1061
+ signedDist = -dist;
1062
+ }
1063
+ let clampedDist = signedDist;
1064
+ if (signedDist > 0) {
1065
+ clampedDist = Math.min(signedDist, this.options.innerRadius);
1066
+ } else {
1067
+ clampedDist = Math.max(signedDist, -this.options.outerRadius);
1068
+ }
1069
+ if (dist < 1e-3) return nearestP;
1070
+ const dir = v.scalarDivide(dist);
1071
+ const scale = Math.abs(clampedDist) / (dist || 1);
1072
+ const offset = v.scalarMultiply(scale);
1073
+ return nearestP.add(offset);
1074
+ }
1075
+ };
1076
+
1077
+ // src/image.ts
1078
+ import { Image as Image3, PooderLayer as PooderLayer4 } from "@pooder/core";
1079
+ var ImageTool = class {
1080
+ constructor() {
1081
+ this.name = "ImageTool";
1082
+ this.options = {
1083
+ url: "",
1084
+ opacity: 1
1085
+ };
1086
+ this.schema = {
1087
+ url: {
1088
+ type: "string",
1089
+ label: "Image URL"
1090
+ },
1091
+ opacity: {
1092
+ type: "number",
1093
+ min: 0,
1094
+ max: 1,
1095
+ step: 0.1,
1096
+ label: "Opacity"
1097
+ }
1098
+ };
1099
+ this.commands = {
1100
+ setUserImage: {
1101
+ execute: (editor, url, opacity) => {
1102
+ if (this.options.url === url && this.options.opacity === opacity) return true;
1103
+ this.options.url = url;
1104
+ this.options.opacity = opacity;
1105
+ this.updateImage(editor, this.options);
1106
+ return true;
1107
+ },
1108
+ schema: {
1109
+ url: {
1110
+ type: "string",
1111
+ label: "Image URL",
1112
+ required: true
1113
+ },
1114
+ opacity: {
1115
+ type: "number",
1116
+ label: "Opacity",
1117
+ min: 0,
1118
+ max: 1,
1119
+ required: true
1120
+ }
1121
+ }
1122
+ }
1123
+ };
1124
+ }
1125
+ onMount(editor) {
1126
+ this.ensureLayer(editor);
1127
+ }
1128
+ onUnmount(editor) {
1129
+ const layer = editor.getLayer("user");
1130
+ if (layer) {
1131
+ const userImage = editor.getObject("user-image", "user");
1132
+ if (userImage) {
1133
+ layer.remove(userImage);
1134
+ editor.canvas.requestRenderAll();
1135
+ }
1136
+ }
1137
+ }
1138
+ onUpdate(editor, state) {
1139
+ this.updateImage(editor, this.options);
1140
+ }
1141
+ ensureLayer(editor) {
1142
+ let userLayer = editor.getLayer("user");
1143
+ if (!userLayer) {
1144
+ userLayer = new PooderLayer4([], {
1145
+ width: editor.state.width,
1146
+ height: editor.state.height,
1147
+ left: 0,
1148
+ top: 0,
1149
+ originX: "left",
1150
+ originY: "top",
1151
+ selectable: false,
1152
+ evented: true,
1153
+ subTargetCheck: true,
1154
+ interactive: true,
1155
+ data: {
1156
+ id: "user"
1157
+ }
1158
+ });
1159
+ editor.canvas.add(userLayer);
1160
+ }
1161
+ }
1162
+ updateImage(editor, opts) {
1163
+ var _a, _b;
1164
+ let { url, opacity } = opts;
1165
+ const layer = editor.getLayer("user");
1166
+ if (!layer) {
1167
+ console.warn("[ImageTool] User layer not found");
1168
+ return;
1169
+ }
1170
+ const userImage = editor.getObject("user-image", "user");
1171
+ if (userImage) {
1172
+ const currentSrc = ((_a = userImage.getSrc) == null ? void 0 : _a.call(userImage)) || ((_b = userImage._element) == null ? void 0 : _b.src);
1173
+ if (currentSrc !== url) {
1174
+ this.loadImage(editor, layer, url, opacity, userImage);
1175
+ } else {
1176
+ if (userImage.opacity !== opacity) {
1177
+ userImage.set({ opacity });
1178
+ editor.canvas.requestRenderAll();
1179
+ }
1180
+ }
1181
+ } else {
1182
+ this.loadImage(editor, layer, url, opacity);
1183
+ }
1184
+ }
1185
+ loadImage(editor, layer, url, opacity, oldImage) {
1186
+ Image3.fromURL(url).then((image) => {
1187
+ if (oldImage) {
1188
+ const { left, top, scaleX, scaleY, angle } = oldImage;
1189
+ image.set({ left, top, scaleX, scaleY, angle });
1190
+ layer.remove(oldImage);
1191
+ }
1192
+ image.set({
1193
+ opacity,
1194
+ data: {
1195
+ id: "user-image"
1196
+ }
1197
+ });
1198
+ layer.add(image);
1199
+ editor.canvas.requestRenderAll();
1200
+ }).catch((err) => {
1201
+ console.error("Failed to load image", url, err);
1202
+ });
1203
+ }
1204
+ };
1205
+
1206
+ // src/white-ink.ts
1207
+ import {
1208
+ Image as Image4,
1209
+ filters,
1210
+ PooderLayer as PooderLayer5
1211
+ } from "@pooder/core";
1212
+ var WhiteInkTool = class {
1213
+ constructor() {
1214
+ this.name = "WhiteInkTool";
1215
+ this.options = {
1216
+ customMask: "",
1217
+ opacity: 1,
1218
+ enableClip: false
1219
+ };
1220
+ this.schema = {
1221
+ customMask: { type: "string", label: "Custom Mask URL" },
1222
+ opacity: { type: "number", min: 0, max: 1, step: 0.01, label: "Opacity" },
1223
+ enableClip: { type: "boolean", label: "Enable Clip" }
1224
+ };
1225
+ this.commands = {
1226
+ setWhiteInkImage: {
1227
+ execute: (editor, customMask, opacity, enableClip = true) => {
1228
+ if (this.options.customMask === customMask && this.options.opacity === opacity && this.options.enableClip === enableClip) return true;
1229
+ this.options.customMask = customMask;
1230
+ this.options.opacity = opacity;
1231
+ this.options.enableClip = enableClip;
1232
+ this.updateWhiteInk(editor, this.options);
1233
+ return true;
1234
+ },
1235
+ schema: {
1236
+ customMask: {
1237
+ type: "string",
1238
+ label: "Custom Mask URL",
1239
+ required: true
1240
+ },
1241
+ opacity: {
1242
+ type: "number",
1243
+ label: "Opacity",
1244
+ min: 0,
1245
+ max: 1,
1246
+ required: true
1247
+ },
1248
+ enableClip: {
1249
+ type: "boolean",
1250
+ label: "Enable Clip",
1251
+ default: true,
1252
+ required: false
1253
+ }
1254
+ }
1255
+ }
1256
+ };
1257
+ }
1258
+ onMount(editor) {
1259
+ this.setup(editor);
1260
+ }
1261
+ onUnmount(editor) {
1262
+ this.teardown(editor);
1263
+ }
1264
+ onDestroy(editor) {
1265
+ this.teardown(editor);
1266
+ }
1267
+ setup(editor) {
1268
+ let userLayer = editor.getLayer("user");
1269
+ if (!userLayer) {
1270
+ userLayer = new PooderLayer5([], {
1271
+ width: editor.state.width,
1272
+ height: editor.state.height,
1273
+ left: 0,
1274
+ top: 0,
1275
+ originX: "left",
1276
+ originY: "top",
1277
+ selectable: false,
1278
+ evented: true,
1279
+ subTargetCheck: true,
1280
+ interactive: true,
1281
+ data: {
1282
+ id: "user"
1283
+ }
1284
+ });
1285
+ editor.canvas.add(userLayer);
1286
+ }
1287
+ if (!this.syncHandler) {
1288
+ this.syncHandler = (e) => {
1289
+ var _a;
1290
+ const target = e.target;
1291
+ if (target && ((_a = target.data) == null ? void 0 : _a.id) === "user-image") {
1292
+ this.syncWithUserImage(editor);
1293
+ }
1294
+ };
1295
+ editor.canvas.on("object:moving", this.syncHandler);
1296
+ editor.canvas.on("object:scaling", this.syncHandler);
1297
+ editor.canvas.on("object:rotating", this.syncHandler);
1298
+ editor.canvas.on("object:modified", this.syncHandler);
1299
+ }
1300
+ this.updateWhiteInk(editor, this.options);
1301
+ }
1302
+ teardown(editor) {
1303
+ if (this.syncHandler) {
1304
+ editor.canvas.off("object:moving", this.syncHandler);
1305
+ editor.canvas.off("object:scaling", this.syncHandler);
1306
+ editor.canvas.off("object:rotating", this.syncHandler);
1307
+ editor.canvas.off("object:modified", this.syncHandler);
1308
+ this.syncHandler = void 0;
1309
+ }
1310
+ const layer = editor.getLayer("user");
1311
+ if (layer) {
1312
+ const whiteInk = editor.getObject("white-ink", "user");
1313
+ if (whiteInk) {
1314
+ layer.remove(whiteInk);
1315
+ }
1316
+ }
1317
+ const userImage = editor.getObject("user-image", "user");
1318
+ if (userImage && userImage.clipPath) {
1319
+ userImage.set({ clipPath: void 0 });
1320
+ }
1321
+ editor.canvas.requestRenderAll();
1322
+ }
1323
+ onUpdate(editor, state) {
1324
+ this.updateWhiteInk(editor, this.options);
1325
+ }
1326
+ updateWhiteInk(editor, opts) {
1327
+ var _a, _b;
1328
+ const { customMask, opacity, enableClip } = opts;
1329
+ const layer = editor.getLayer("user");
1330
+ if (!layer) {
1331
+ console.warn("[WhiteInkTool] User layer not found");
1332
+ return;
1333
+ }
1334
+ const whiteInk = editor.getObject("white-ink", "user");
1335
+ const userImage = editor.getObject("user-image", "user");
1336
+ if (!customMask) {
1337
+ if (whiteInk) {
1338
+ layer.remove(whiteInk);
1339
+ }
1340
+ if (userImage && userImage.clipPath) {
1341
+ userImage.set({ clipPath: void 0 });
1342
+ }
1343
+ editor.canvas.requestRenderAll();
1344
+ return;
1345
+ }
1346
+ if (whiteInk) {
1347
+ const currentSrc = ((_a = whiteInk.getSrc) == null ? void 0 : _a.call(whiteInk)) || ((_b = whiteInk._element) == null ? void 0 : _b.src);
1348
+ if (currentSrc !== customMask) {
1349
+ this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
1350
+ } else {
1351
+ if (whiteInk.opacity !== opacity) {
1352
+ whiteInk.set({ opacity });
1353
+ editor.canvas.requestRenderAll();
1354
+ }
1355
+ }
1356
+ } else {
1357
+ this.loadWhiteInk(editor, layer, customMask, opacity, enableClip);
1358
+ }
1359
+ if (userImage) {
1360
+ if (enableClip) {
1361
+ if (!userImage.clipPath) {
1362
+ this.applyClipPath(editor, customMask);
1363
+ }
1364
+ } else {
1365
+ if (userImage.clipPath) {
1366
+ userImage.set({ clipPath: void 0 });
1367
+ editor.canvas.requestRenderAll();
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ loadWhiteInk(editor, layer, url, opacity, enableClip, oldImage) {
1373
+ Image4.fromURL(url, { crossOrigin: "anonymous" }).then((image) => {
1374
+ var _a;
1375
+ if (oldImage) {
1376
+ layer.remove(oldImage);
1377
+ }
1378
+ (_a = image.filters) == null ? void 0 : _a.push(new filters.BlendColor({
1379
+ color: "#FFFFFF",
1380
+ mode: "add"
1381
+ }));
1382
+ image.applyFilters();
1383
+ image.set({
1384
+ opacity,
1385
+ selectable: false,
1386
+ evented: false,
1387
+ data: {
1388
+ id: "white-ink"
1389
+ }
1390
+ });
1391
+ layer.add(image);
1392
+ const userImage = editor.getObject("user-image", "user");
1393
+ if (userImage) {
1394
+ layer.remove(userImage);
1395
+ layer.add(userImage);
1396
+ }
1397
+ if (enableClip) {
1398
+ this.applyClipPath(editor, url);
1399
+ } else if (userImage) {
1400
+ userImage.set({ clipPath: void 0 });
1401
+ }
1402
+ this.syncWithUserImage(editor);
1403
+ editor.canvas.requestRenderAll();
1404
+ }).catch((err) => {
1405
+ console.error("Failed to load white ink mask", url, err);
1406
+ });
1407
+ }
1408
+ applyClipPath(editor, url) {
1409
+ const userImage = editor.getObject("user-image", "user");
1410
+ if (!userImage) return;
1411
+ Image4.fromURL(url, { crossOrigin: "anonymous" }).then((maskImage) => {
1412
+ maskImage.set({
1413
+ originX: "center",
1414
+ originY: "center",
1415
+ left: 0,
1416
+ top: 0,
1417
+ // Scale to fit userImage if dimensions differ
1418
+ scaleX: userImage.width / maskImage.width,
1419
+ scaleY: userImage.height / maskImage.height
1420
+ });
1421
+ userImage.set({ clipPath: maskImage });
1422
+ editor.canvas.requestRenderAll();
1423
+ }).catch((err) => {
1424
+ console.error("Failed to load clip path", url, err);
1425
+ });
1426
+ }
1427
+ syncWithUserImage(editor) {
1428
+ const userImage = editor.getObject("user-image", "user");
1429
+ const whiteInk = editor.getObject("white-ink", "user");
1430
+ if (userImage && whiteInk) {
1431
+ whiteInk.set({
1432
+ left: userImage.left,
1433
+ top: userImage.top,
1434
+ scaleX: userImage.scaleX,
1435
+ scaleY: userImage.scaleY,
1436
+ angle: userImage.angle,
1437
+ skewX: userImage.skewX,
1438
+ skewY: userImage.skewY,
1439
+ flipX: userImage.flipX,
1440
+ flipY: userImage.flipY,
1441
+ originX: userImage.originX,
1442
+ originY: userImage.originY
1443
+ });
1444
+ }
1445
+ }
1446
+ };
1447
+
1448
+ // src/ruler.ts
1449
+ import { PooderLayer as PooderLayer6, Rect as Rect3, Line, Text } from "@pooder/core";
1450
+ var RulerTool = class {
1451
+ constructor() {
1452
+ this.name = "RulerTool";
1453
+ this.options = {
1454
+ unit: "px",
1455
+ thickness: 20,
1456
+ backgroundColor: "#f0f0f0",
1457
+ textColor: "#333333",
1458
+ lineColor: "#999999",
1459
+ fontSize: 10
1460
+ };
1461
+ this.schema = {
1462
+ unit: {
1463
+ type: "select",
1464
+ options: ["px", "mm", "cm", "in"],
1465
+ label: "Unit"
1466
+ },
1467
+ thickness: { type: "number", min: 10, max: 100, label: "Thickness" },
1468
+ backgroundColor: { type: "color", label: "Background Color" },
1469
+ textColor: { type: "color", label: "Text Color" },
1470
+ lineColor: { type: "color", label: "Line Color" },
1471
+ fontSize: { type: "number", min: 8, max: 24, label: "Font Size" }
1472
+ };
1473
+ this.commands = {
1474
+ setUnit: {
1475
+ execute: (editor, unit) => {
1476
+ if (this.options.unit === unit) return true;
1477
+ this.options.unit = unit;
1478
+ this.updateRuler(editor);
1479
+ return true;
1480
+ },
1481
+ schema: {
1482
+ unit: {
1483
+ type: "string",
1484
+ label: "Unit",
1485
+ options: ["px", "mm", "cm", "in"],
1486
+ required: true
1487
+ }
1488
+ }
1489
+ },
1490
+ setTheme: {
1491
+ execute: (editor, theme) => {
1492
+ const newOptions = { ...this.options, ...theme };
1493
+ if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
1494
+ this.options = newOptions;
1495
+ this.updateRuler(editor);
1496
+ return true;
1497
+ },
1498
+ schema: {
1499
+ theme: {
1500
+ type: "object",
1501
+ label: "Theme",
1502
+ required: true
1503
+ }
1504
+ }
1505
+ }
1506
+ };
1507
+ }
1508
+ onMount(editor) {
1509
+ this.createLayer(editor);
1510
+ }
1511
+ onUnmount(editor) {
1512
+ this.destroyLayer(editor);
1513
+ }
1514
+ onUpdate(editor, state) {
1515
+ }
1516
+ onDestroy(editor) {
1517
+ this.destroyLayer(editor);
1518
+ }
1519
+ getLayer(editor) {
1520
+ return editor.canvas.getObjects().find((obj) => {
1521
+ var _a;
1522
+ return ((_a = obj.data) == null ? void 0 : _a.id) === "ruler-overlay";
1523
+ });
1524
+ }
1525
+ createLayer(editor) {
1526
+ let layer = this.getLayer(editor);
1527
+ if (!layer) {
1528
+ const width = editor.canvas.width || 800;
1529
+ const height = editor.canvas.height || 600;
1530
+ layer = new PooderLayer6([], {
1531
+ width,
1532
+ height,
1533
+ selectable: false,
1534
+ evented: false,
1535
+ data: { id: "ruler-overlay" }
1536
+ });
1537
+ editor.canvas.add(layer);
1538
+ }
1539
+ editor.canvas.bringObjectToFront(layer);
1540
+ this.updateRuler(editor);
1541
+ }
1542
+ destroyLayer(editor) {
1543
+ const layer = this.getLayer(editor);
1544
+ if (layer) {
1545
+ editor.canvas.remove(layer);
1546
+ }
1547
+ }
1548
+ updateRuler(editor) {
1549
+ const layer = this.getLayer(editor);
1550
+ if (!layer) return;
1551
+ layer.remove(...layer.getObjects());
1552
+ const { thickness, backgroundColor, lineColor, textColor, fontSize } = this.options;
1553
+ const width = editor.canvas.width || 800;
1554
+ const height = editor.canvas.height || 600;
1555
+ const topBg = new Rect3({
1556
+ left: 0,
1557
+ top: 0,
1558
+ width,
1559
+ height: thickness,
1560
+ fill: backgroundColor,
1561
+ selectable: false,
1562
+ evented: false
1563
+ });
1564
+ const leftBg = new Rect3({
1565
+ left: 0,
1566
+ top: 0,
1567
+ width: thickness,
1568
+ height,
1569
+ fill: backgroundColor,
1570
+ selectable: false,
1571
+ evented: false
1572
+ });
1573
+ const cornerBg = new Rect3({
1574
+ left: 0,
1575
+ top: 0,
1576
+ width: thickness,
1577
+ height: thickness,
1578
+ fill: backgroundColor,
1579
+ stroke: lineColor,
1580
+ strokeWidth: 1,
1581
+ selectable: false,
1582
+ evented: false
1583
+ });
1584
+ layer.add(topBg, leftBg, cornerBg);
1585
+ const step = 100;
1586
+ const subStep = 10;
1587
+ const midStep = 50;
1588
+ for (let x = 0; x <= width; x += subStep) {
1589
+ if (x < thickness) continue;
1590
+ let len = thickness * 0.25;
1591
+ if (x % step === 0) len = thickness * 0.8;
1592
+ else if (x % midStep === 0) len = thickness * 0.5;
1593
+ const line = new Line([x, thickness - len, x, thickness], {
1594
+ stroke: lineColor,
1595
+ strokeWidth: 1,
1596
+ selectable: false,
1597
+ evented: false
1598
+ });
1599
+ layer.add(line);
1600
+ if (x % step === 0) {
1601
+ const text = new Text(x.toString(), {
1602
+ left: x + 2,
1603
+ top: 2,
1604
+ fontSize,
1605
+ fill: textColor,
1606
+ fontFamily: "Arial",
1607
+ selectable: false,
1608
+ evented: false
1609
+ });
1610
+ layer.add(text);
1611
+ }
1612
+ }
1613
+ for (let y = 0; y <= height; y += subStep) {
1614
+ if (y < thickness) continue;
1615
+ let len = thickness * 0.25;
1616
+ if (y % step === 0) len = thickness * 0.8;
1617
+ else if (y % midStep === 0) len = thickness * 0.5;
1618
+ const line = new Line([thickness - len, y, thickness, y], {
1619
+ stroke: lineColor,
1620
+ strokeWidth: 1,
1621
+ selectable: false,
1622
+ evented: false
1623
+ });
1624
+ layer.add(line);
1625
+ if (y % step === 0) {
1626
+ const text = new Text(y.toString(), {
1627
+ angle: -90,
1628
+ left: thickness / 2 - fontSize / 3,
1629
+ // approximate centering
1630
+ top: y + fontSize,
1631
+ fontSize,
1632
+ fill: textColor,
1633
+ fontFamily: "Arial",
1634
+ originX: "center",
1635
+ originY: "center",
1636
+ selectable: false,
1637
+ evented: false
1638
+ });
1639
+ layer.add(text);
1640
+ }
1641
+ }
1642
+ editor.canvas.bringObjectToFront(layer);
1643
+ editor.canvas.requestRenderAll();
1644
+ }
1645
+ };
1646
+ export {
1647
+ BackgroundTool,
1648
+ DielineTool,
1649
+ FilmTool,
1650
+ HoleTool,
1651
+ ImageTool,
1652
+ RulerTool,
1653
+ WhiteInkTool
1654
+ };