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