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