@pooder/kit 0.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -9
- package/dist/index.d.mts +29 -6
- package/dist/index.d.ts +29 -6
- package/dist/index.js +405 -62
- package/dist/index.mjs +435 -68
- package/package.json +2 -2
- package/src/background.ts +183 -173
- package/src/dieline.ts +580 -424
- package/src/film.ts +163 -155
- package/src/geometry.ts +251 -244
- package/src/hole.ts +493 -413
- package/src/image.ts +304 -146
- package/src/index.ts +8 -7
- package/src/mirror.ts +91 -0
- package/src/ruler.ts +255 -238
- package/src/white-ink.ts +329 -302
- package/tsconfig.json +13 -13
package/src/dieline.ts
CHANGED
|
@@ -1,424 +1,580 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
Command,
|
|
3
|
+
Editor,
|
|
4
|
+
EditorState,
|
|
5
|
+
Extension,
|
|
6
|
+
OptionSchema,
|
|
7
|
+
Rect,
|
|
8
|
+
Circle,
|
|
9
|
+
Ellipse,
|
|
10
|
+
Path,
|
|
11
|
+
PooderLayer,
|
|
12
|
+
Pattern,
|
|
13
|
+
} from "@pooder/core";
|
|
14
|
+
import {
|
|
15
|
+
generateDielinePath,
|
|
16
|
+
generateMaskPath,
|
|
17
|
+
generateBleedZonePath,
|
|
18
|
+
HoleData,
|
|
19
|
+
} from "./geometry";
|
|
20
|
+
|
|
21
|
+
export interface DielineToolOptions {
|
|
22
|
+
shape: "rect" | "circle" | "ellipse";
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
radius: number; // corner radius for rect
|
|
26
|
+
position?: { x: number; y: number };
|
|
27
|
+
borderLength?: number;
|
|
28
|
+
offset: number;
|
|
29
|
+
style: "solid" | "dashed";
|
|
30
|
+
insideColor: string;
|
|
31
|
+
outsideColor: string;
|
|
32
|
+
showBleedLines?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Alias for compatibility if needed, or just use DielineToolOptions
|
|
36
|
+
export type DielineConfig = DielineToolOptions;
|
|
37
|
+
|
|
38
|
+
export interface DielineGeometry {
|
|
39
|
+
shape: "rect" | "circle" | "ellipse";
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
radius: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class DielineTool implements Extension<DielineToolOptions> {
|
|
48
|
+
public name = "DielineTool";
|
|
49
|
+
public options: DielineToolOptions = {
|
|
50
|
+
shape: "rect",
|
|
51
|
+
width: 300,
|
|
52
|
+
height: 300,
|
|
53
|
+
radius: 0,
|
|
54
|
+
offset: 0,
|
|
55
|
+
style: "solid",
|
|
56
|
+
insideColor: "rgba(0,0,0,0)",
|
|
57
|
+
outsideColor: "#ffffff",
|
|
58
|
+
showBleedLines: true,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
public schema: Record<keyof DielineToolOptions, OptionSchema> = {
|
|
62
|
+
shape: {
|
|
63
|
+
type: "select",
|
|
64
|
+
options: ["rect", "circle", "ellipse"],
|
|
65
|
+
label: "Shape",
|
|
66
|
+
},
|
|
67
|
+
width: { type: "number", min: 10, max: 2000, label: "Width" },
|
|
68
|
+
height: { type: "number", min: 10, max: 2000, label: "Height" },
|
|
69
|
+
radius: { type: "number", min: 0, max: 500, label: "Corner Radius" },
|
|
70
|
+
position: { type: "string", label: "Position" }, // Complex object, simplified for now or need custom handler
|
|
71
|
+
borderLength: { type: "number", min: 0, max: 500, label: "Margin" },
|
|
72
|
+
offset: { type: "number", min: -100, max: 100, label: "Bleed Offset" },
|
|
73
|
+
showBleedLines: { type: "boolean", label: "Show Bleed Lines" },
|
|
74
|
+
style: {
|
|
75
|
+
type: "select",
|
|
76
|
+
options: ["solid", "dashed"],
|
|
77
|
+
label: "Line Style",
|
|
78
|
+
},
|
|
79
|
+
insideColor: { type: "color", label: "Inside Color" },
|
|
80
|
+
outsideColor: { type: "color", label: "Outside Color" },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
onMount(editor: Editor) {
|
|
84
|
+
this.createLayer(editor);
|
|
85
|
+
this.updateDieline(editor);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onUnmount(editor: Editor) {
|
|
89
|
+
this.destroyLayer(editor);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onUpdate(editor: Editor, state: EditorState) {
|
|
93
|
+
this.updateDieline(editor);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onDestroy(editor: Editor) {
|
|
97
|
+
this.destroyLayer(editor);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private getLayer(editor: Editor, id: string) {
|
|
101
|
+
return editor.canvas
|
|
102
|
+
.getObjects()
|
|
103
|
+
.find((obj: any) => obj.data?.id === id) as PooderLayer | undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private createLayer(editor: Editor) {
|
|
107
|
+
let layer = this.getLayer(editor, "dieline-overlay");
|
|
108
|
+
|
|
109
|
+
if (!layer) {
|
|
110
|
+
const width = editor.canvas.width || 800;
|
|
111
|
+
const height = editor.canvas.height || 600;
|
|
112
|
+
|
|
113
|
+
layer = new PooderLayer([], {
|
|
114
|
+
width,
|
|
115
|
+
height,
|
|
116
|
+
selectable: false,
|
|
117
|
+
evented: false,
|
|
118
|
+
data: { id: "dieline-overlay" },
|
|
119
|
+
} as any);
|
|
120
|
+
|
|
121
|
+
editor.canvas.add(layer);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
editor.canvas.bringObjectToFront(layer);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private destroyLayer(editor: Editor) {
|
|
128
|
+
const layer = this.getLayer(editor, "dieline-overlay");
|
|
129
|
+
if (layer) {
|
|
130
|
+
editor.canvas.remove(layer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private createHatchPattern(color: string = "rgba(0, 0, 0, 0.3)") {
|
|
135
|
+
if (typeof document === "undefined") {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const size = 20;
|
|
139
|
+
const canvas = document.createElement("canvas");
|
|
140
|
+
canvas.width = size;
|
|
141
|
+
canvas.height = size;
|
|
142
|
+
const ctx = canvas.getContext("2d");
|
|
143
|
+
if (ctx) {
|
|
144
|
+
// Transparent background
|
|
145
|
+
ctx.clearRect(0, 0, size, size);
|
|
146
|
+
|
|
147
|
+
// Draw diagonal /
|
|
148
|
+
ctx.strokeStyle = color;
|
|
149
|
+
ctx.lineWidth = 1;
|
|
150
|
+
ctx.beginPath();
|
|
151
|
+
ctx.moveTo(0, size);
|
|
152
|
+
ctx.lineTo(size, 0);
|
|
153
|
+
ctx.stroke();
|
|
154
|
+
}
|
|
155
|
+
// @ts-ignore
|
|
156
|
+
return new Pattern({ source: canvas, repetition: "repeat" });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public updateDieline(editor: Editor) {
|
|
160
|
+
const {
|
|
161
|
+
shape,
|
|
162
|
+
radius,
|
|
163
|
+
offset,
|
|
164
|
+
style,
|
|
165
|
+
insideColor,
|
|
166
|
+
outsideColor,
|
|
167
|
+
position,
|
|
168
|
+
borderLength,
|
|
169
|
+
showBleedLines,
|
|
170
|
+
} = this.options;
|
|
171
|
+
let { width, height } = this.options;
|
|
172
|
+
|
|
173
|
+
const canvasW = editor.canvas.width || 800;
|
|
174
|
+
const canvasH = editor.canvas.height || 600;
|
|
175
|
+
|
|
176
|
+
// Handle borderLength (Margin)
|
|
177
|
+
if (borderLength && borderLength > 0) {
|
|
178
|
+
width = Math.max(0, canvasW - borderLength * 2);
|
|
179
|
+
height = Math.max(0, canvasH - borderLength * 2);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle Position
|
|
183
|
+
const cx = position?.x ?? canvasW / 2;
|
|
184
|
+
const cy = position?.y ?? canvasH / 2;
|
|
185
|
+
|
|
186
|
+
const layer = this.getLayer(editor, "dieline-overlay");
|
|
187
|
+
if (!layer) return;
|
|
188
|
+
|
|
189
|
+
// Clear existing objects
|
|
190
|
+
layer.remove(...layer.getObjects());
|
|
191
|
+
|
|
192
|
+
// Get Hole Tool and Enforce Constraints
|
|
193
|
+
const holeTool = editor.getExtension("HoleTool") as any;
|
|
194
|
+
if (holeTool && typeof holeTool.enforceConstraints === "function") {
|
|
195
|
+
holeTool.enforceConstraints(editor);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Get Hole Data
|
|
199
|
+
const holes = holeTool ? holeTool.options.holes || [] : [];
|
|
200
|
+
const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
|
|
201
|
+
const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
|
|
202
|
+
|
|
203
|
+
const holeData: HoleData[] = holes.map((h: any) => ({
|
|
204
|
+
x: h.x,
|
|
205
|
+
y: h.y,
|
|
206
|
+
innerRadius,
|
|
207
|
+
outerRadius,
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
// 1. Draw Mask (Outside)
|
|
211
|
+
const cutW = Math.max(0, width + offset * 2);
|
|
212
|
+
const cutH = Math.max(0, height + offset * 2);
|
|
213
|
+
const cutR = radius === 0 ? 0 : Math.max(0, radius + offset);
|
|
214
|
+
|
|
215
|
+
// Use Paper.js to generate the complex mask path
|
|
216
|
+
const maskPathData = generateMaskPath({
|
|
217
|
+
canvasWidth: canvasW,
|
|
218
|
+
canvasHeight: canvasH,
|
|
219
|
+
shape,
|
|
220
|
+
width: cutW,
|
|
221
|
+
height: cutH,
|
|
222
|
+
radius: cutR,
|
|
223
|
+
x: cx,
|
|
224
|
+
y: cy,
|
|
225
|
+
holes: holeData,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const mask = new Path(maskPathData, {
|
|
229
|
+
fill: outsideColor,
|
|
230
|
+
stroke: null,
|
|
231
|
+
selectable: false,
|
|
232
|
+
evented: false,
|
|
233
|
+
originX: "left" as const,
|
|
234
|
+
originY: "top" as const,
|
|
235
|
+
left: 0,
|
|
236
|
+
top: 0,
|
|
237
|
+
});
|
|
238
|
+
layer.add(mask);
|
|
239
|
+
|
|
240
|
+
// 2. Draw Inside Fill (Dieline Shape itself, merged with holes if needed, or just the shape?)
|
|
241
|
+
// The user wants "fusion effect" so holes should be part of the dieline visually.
|
|
242
|
+
// If insideColor is transparent, it doesn't matter much.
|
|
243
|
+
// If insideColor is opaque, we need to punch holes in it too.
|
|
244
|
+
// Let's use Paper.js for this too if insideColor is not transparent.
|
|
245
|
+
|
|
246
|
+
if (
|
|
247
|
+
insideColor &&
|
|
248
|
+
insideColor !== "transparent" &&
|
|
249
|
+
insideColor !== "rgba(0,0,0,0)"
|
|
250
|
+
) {
|
|
251
|
+
// Generate path for the product shape (Paper) = Dieline - Holes
|
|
252
|
+
const productPathData = generateDielinePath({
|
|
253
|
+
shape,
|
|
254
|
+
width: cutW,
|
|
255
|
+
height: cutH,
|
|
256
|
+
radius: cutR,
|
|
257
|
+
x: cx,
|
|
258
|
+
y: cy,
|
|
259
|
+
holes: holeData,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const insideObj = new Path(productPathData, {
|
|
263
|
+
fill: insideColor,
|
|
264
|
+
stroke: null,
|
|
265
|
+
selectable: false,
|
|
266
|
+
evented: false,
|
|
267
|
+
originX: "left", // paper.js paths are absolute
|
|
268
|
+
originY: "top",
|
|
269
|
+
});
|
|
270
|
+
layer.add(insideObj);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 3. Draw Bleed Zone (Hatch Fill) and Offset Border
|
|
274
|
+
if (offset !== 0) {
|
|
275
|
+
const bleedPathData = generateBleedZonePath(
|
|
276
|
+
{
|
|
277
|
+
shape,
|
|
278
|
+
width,
|
|
279
|
+
height,
|
|
280
|
+
radius,
|
|
281
|
+
x: cx,
|
|
282
|
+
y: cy,
|
|
283
|
+
holes: holeData,
|
|
284
|
+
},
|
|
285
|
+
offset,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Use solid red for hatch lines to match dieline, background is transparent
|
|
289
|
+
if (showBleedLines !== false) {
|
|
290
|
+
const pattern = this.createHatchPattern("red");
|
|
291
|
+
if (pattern) {
|
|
292
|
+
const bleedObj = new Path(bleedPathData, {
|
|
293
|
+
fill: pattern,
|
|
294
|
+
stroke: null,
|
|
295
|
+
selectable: false,
|
|
296
|
+
evented: false,
|
|
297
|
+
objectCaching: false,
|
|
298
|
+
originX: "left",
|
|
299
|
+
originY: "top",
|
|
300
|
+
});
|
|
301
|
+
layer.add(bleedObj);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Offset Dieline Border
|
|
306
|
+
const offsetPathData = generateDielinePath({
|
|
307
|
+
shape,
|
|
308
|
+
width: cutW,
|
|
309
|
+
height: cutH,
|
|
310
|
+
radius: cutR,
|
|
311
|
+
x: cx,
|
|
312
|
+
y: cy,
|
|
313
|
+
holes: holeData,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const offsetBorderObj = new Path(offsetPathData, {
|
|
317
|
+
fill: null,
|
|
318
|
+
stroke: "#666", // Grey
|
|
319
|
+
strokeWidth: 1,
|
|
320
|
+
strokeDashArray: [4, 4], // Dashed
|
|
321
|
+
selectable: false,
|
|
322
|
+
evented: false,
|
|
323
|
+
originX: "left",
|
|
324
|
+
originY: "top",
|
|
325
|
+
});
|
|
326
|
+
layer.add(offsetBorderObj);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 4. Draw Dieline (Visual Border)
|
|
330
|
+
// This should outline the product shape AND the holes.
|
|
331
|
+
// Paper.js `generateDielinePath` returns exactly this (Dieline - Holes).
|
|
332
|
+
|
|
333
|
+
const borderPathData = generateDielinePath({
|
|
334
|
+
shape,
|
|
335
|
+
width: width,
|
|
336
|
+
height: height,
|
|
337
|
+
radius: radius,
|
|
338
|
+
x: cx,
|
|
339
|
+
y: cy,
|
|
340
|
+
holes: holeData,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const borderObj = new Path(borderPathData, {
|
|
344
|
+
fill: "transparent",
|
|
345
|
+
stroke: "red",
|
|
346
|
+
strokeWidth: 1,
|
|
347
|
+
strokeDashArray: style === "dashed" ? [5, 5] : undefined,
|
|
348
|
+
selectable: false,
|
|
349
|
+
evented: false,
|
|
350
|
+
originX: "left",
|
|
351
|
+
originY: "top",
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
layer.add(borderObj);
|
|
355
|
+
|
|
356
|
+
editor.canvas.requestRenderAll();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
commands: Record<string, Command> = {
|
|
360
|
+
reset: {
|
|
361
|
+
execute: (editor: Editor) => {
|
|
362
|
+
this.options = {
|
|
363
|
+
shape: "rect",
|
|
364
|
+
width: 300,
|
|
365
|
+
height: 300,
|
|
366
|
+
radius: 0,
|
|
367
|
+
offset: 0,
|
|
368
|
+
style: "solid",
|
|
369
|
+
insideColor: "rgba(0,0,0,0)",
|
|
370
|
+
outsideColor: "#ffffff",
|
|
371
|
+
showBleedLines: true,
|
|
372
|
+
};
|
|
373
|
+
this.updateDieline(editor);
|
|
374
|
+
return true;
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
destroy: {
|
|
378
|
+
execute: (editor: Editor) => {
|
|
379
|
+
this.destroyLayer(editor);
|
|
380
|
+
return true;
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
setDimensions: {
|
|
384
|
+
execute: (editor: Editor, width: number, height: number) => {
|
|
385
|
+
if (this.options.width === width && this.options.height === height)
|
|
386
|
+
return true;
|
|
387
|
+
this.options.width = width;
|
|
388
|
+
this.options.height = height;
|
|
389
|
+
this.updateDieline(editor);
|
|
390
|
+
return true;
|
|
391
|
+
},
|
|
392
|
+
schema: {
|
|
393
|
+
width: {
|
|
394
|
+
type: "number",
|
|
395
|
+
label: "Width",
|
|
396
|
+
min: 10,
|
|
397
|
+
max: 2000,
|
|
398
|
+
required: true,
|
|
399
|
+
},
|
|
400
|
+
height: {
|
|
401
|
+
type: "number",
|
|
402
|
+
label: "Height",
|
|
403
|
+
min: 10,
|
|
404
|
+
max: 2000,
|
|
405
|
+
required: true,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
setShape: {
|
|
410
|
+
execute: (editor: Editor, shape: "rect" | "circle" | "ellipse") => {
|
|
411
|
+
if (this.options.shape === shape) return true;
|
|
412
|
+
this.options.shape = shape;
|
|
413
|
+
this.updateDieline(editor);
|
|
414
|
+
return true;
|
|
415
|
+
},
|
|
416
|
+
schema: {
|
|
417
|
+
shape: {
|
|
418
|
+
type: "string",
|
|
419
|
+
label: "Shape",
|
|
420
|
+
options: ["rect", "circle", "ellipse"],
|
|
421
|
+
required: true,
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
setBleed: {
|
|
426
|
+
execute: (editor: Editor, bleed: number) => {
|
|
427
|
+
if (this.options.offset === bleed) return true;
|
|
428
|
+
this.options.offset = bleed;
|
|
429
|
+
this.updateDieline(editor);
|
|
430
|
+
return true;
|
|
431
|
+
},
|
|
432
|
+
schema: {
|
|
433
|
+
bleed: {
|
|
434
|
+
type: "number",
|
|
435
|
+
label: "Bleed",
|
|
436
|
+
min: -100,
|
|
437
|
+
max: 100,
|
|
438
|
+
required: true,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
exportCutImage: {
|
|
443
|
+
execute: (editor: Editor) => {
|
|
444
|
+
// 1. Generate Path Data
|
|
445
|
+
const { shape, width, height, radius, position } = this.options;
|
|
446
|
+
const canvasW = editor.canvas.width || 800;
|
|
447
|
+
const canvasH = editor.canvas.height || 600;
|
|
448
|
+
const cx = position?.x ?? canvasW / 2;
|
|
449
|
+
const cy = position?.y ?? canvasH / 2;
|
|
450
|
+
|
|
451
|
+
const holeTool = editor.getExtension("HoleTool") as any;
|
|
452
|
+
const holes = holeTool ? holeTool.options.holes || [] : [];
|
|
453
|
+
const innerRadius = holeTool ? holeTool.options.innerRadius || 15 : 15;
|
|
454
|
+
const outerRadius = holeTool ? holeTool.options.outerRadius || 25 : 25;
|
|
455
|
+
const holeData = holes.map((h: any) => ({
|
|
456
|
+
x: h.x,
|
|
457
|
+
y: h.y,
|
|
458
|
+
innerRadius,
|
|
459
|
+
outerRadius,
|
|
460
|
+
}));
|
|
461
|
+
|
|
462
|
+
const pathData = generateDielinePath({
|
|
463
|
+
shape,
|
|
464
|
+
width,
|
|
465
|
+
height,
|
|
466
|
+
radius,
|
|
467
|
+
x: cx,
|
|
468
|
+
y: cy,
|
|
469
|
+
holes: holeData,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// 2. Create Clip Path
|
|
473
|
+
// @ts-ignore
|
|
474
|
+
const clipPath = new Path(pathData, {
|
|
475
|
+
left: 0,
|
|
476
|
+
top: 0,
|
|
477
|
+
originX: "left",
|
|
478
|
+
originY: "top",
|
|
479
|
+
absolutePositioned: true,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// 3. Hide UI Layers
|
|
483
|
+
const layer = this.getLayer(editor, "dieline-overlay");
|
|
484
|
+
const wasVisible = layer?.visible ?? true;
|
|
485
|
+
if (layer) layer.visible = false;
|
|
486
|
+
|
|
487
|
+
// Hide hole markers
|
|
488
|
+
const holeMarkers = editor.canvas
|
|
489
|
+
.getObjects()
|
|
490
|
+
.filter((o: any) => o.data?.type === "hole-marker");
|
|
491
|
+
holeMarkers.forEach((o) => (o.visible = false));
|
|
492
|
+
|
|
493
|
+
// Hide Ruler Overlay
|
|
494
|
+
const rulerLayer = editor.canvas
|
|
495
|
+
.getObjects()
|
|
496
|
+
.find((obj: any) => obj.data?.id === "ruler-overlay");
|
|
497
|
+
const rulerWasVisible = rulerLayer?.visible ?? true;
|
|
498
|
+
if (rulerLayer) rulerLayer.visible = false;
|
|
499
|
+
|
|
500
|
+
// 4. Apply Clip & Export
|
|
501
|
+
const originalClip = editor.canvas.clipPath;
|
|
502
|
+
editor.canvas.clipPath = clipPath;
|
|
503
|
+
|
|
504
|
+
const bbox = clipPath.getBoundingRect();
|
|
505
|
+
// Adjust hole coordinates to be relative to the bounding box
|
|
506
|
+
const holeDataRelative = holes.map((h: any) => ({
|
|
507
|
+
x: h.x - bbox.left,
|
|
508
|
+
y: h.y - bbox.top,
|
|
509
|
+
innerRadius,
|
|
510
|
+
outerRadius,
|
|
511
|
+
}));
|
|
512
|
+
|
|
513
|
+
const clipPathCorrected = new Path(pathData, {
|
|
514
|
+
absolutePositioned: true,
|
|
515
|
+
left: 0,
|
|
516
|
+
top: 0,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const tempPath = new Path(pathData);
|
|
520
|
+
const tempBounds = tempPath.getBoundingRect();
|
|
521
|
+
|
|
522
|
+
clipPathCorrected.set({
|
|
523
|
+
left: tempBounds.left,
|
|
524
|
+
top: tempBounds.top,
|
|
525
|
+
originX: "left",
|
|
526
|
+
originY: "top",
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// 4. Apply Clip & Export
|
|
530
|
+
editor.canvas.clipPath = clipPathCorrected;
|
|
531
|
+
|
|
532
|
+
const exportBbox = clipPathCorrected.getBoundingRect();
|
|
533
|
+
const dataURL = editor.canvas.toDataURL({
|
|
534
|
+
format: "png",
|
|
535
|
+
multiplier: 2,
|
|
536
|
+
left: exportBbox.left,
|
|
537
|
+
top: exportBbox.top,
|
|
538
|
+
width: exportBbox.width,
|
|
539
|
+
height: exportBbox.height,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// 5. Restore
|
|
543
|
+
editor.canvas.clipPath = originalClip;
|
|
544
|
+
if (layer) layer.visible = wasVisible;
|
|
545
|
+
if (rulerLayer) rulerLayer.visible = rulerWasVisible;
|
|
546
|
+
holeMarkers.forEach((o) => (o.visible = true));
|
|
547
|
+
editor.canvas.requestRenderAll();
|
|
548
|
+
|
|
549
|
+
return dataURL;
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
public getGeometry(editor: Editor): DielineGeometry | null {
|
|
555
|
+
const { shape, width, height, radius, position, borderLength } =
|
|
556
|
+
this.options;
|
|
557
|
+
const canvasW = editor.canvas.width || 800;
|
|
558
|
+
const canvasH = editor.canvas.height || 600;
|
|
559
|
+
|
|
560
|
+
let visualWidth = width;
|
|
561
|
+
let visualHeight = height;
|
|
562
|
+
|
|
563
|
+
if (borderLength && borderLength > 0) {
|
|
564
|
+
visualWidth = Math.max(0, canvasW - borderLength * 2);
|
|
565
|
+
visualHeight = Math.max(0, canvasH - borderLength * 2);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const cx = position?.x ?? canvasW / 2;
|
|
569
|
+
const cy = position?.y ?? canvasH / 2;
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
shape,
|
|
573
|
+
x: cx,
|
|
574
|
+
y: cy,
|
|
575
|
+
width: visualWidth,
|
|
576
|
+
height: visualHeight,
|
|
577
|
+
radius,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|