@pooder/kit 3.3.0 → 3.4.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 +6 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +287 -144
- package/dist/index.mjs +287 -144
- package/package.json +1 -1
- package/src/image.ts +113 -138
- package/src/tracer.ts +278 -164
package/src/image.ts
CHANGED
|
@@ -14,8 +14,7 @@ export interface ImageItem {
|
|
|
14
14
|
id: string;
|
|
15
15
|
url: string;
|
|
16
16
|
opacity: number;
|
|
17
|
-
|
|
18
|
-
height?: number;
|
|
17
|
+
scale?: number;
|
|
19
18
|
angle?: number;
|
|
20
19
|
left?: number;
|
|
21
20
|
top?: number;
|
|
@@ -30,6 +29,7 @@ export class ImageTool implements Extension {
|
|
|
30
29
|
|
|
31
30
|
private items: ImageItem[] = [];
|
|
32
31
|
private objectMap: Map<string, FabricObject> = new Map();
|
|
32
|
+
private loadResolvers: Map<string, () => void> = new Map();
|
|
33
33
|
private canvasService?: CanvasService;
|
|
34
34
|
private context?: ExtensionContext;
|
|
35
35
|
private isUpdatingConfig = false;
|
|
@@ -42,7 +42,9 @@ export class ImageTool implements Extension {
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const configService = context.services.get<ConfigurationService>(
|
|
45
|
+
const configService = context.services.get<ConfigurationService>(
|
|
46
|
+
"ConfigurationService",
|
|
47
|
+
);
|
|
46
48
|
if (configService) {
|
|
47
49
|
// Load initial config
|
|
48
50
|
this.items = configService.get("image.items", []) || [];
|
|
@@ -51,17 +53,8 @@ export class ImageTool implements Extension {
|
|
|
51
53
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
52
54
|
if (this.isUpdatingConfig) return;
|
|
53
55
|
|
|
54
|
-
let shouldUpdate = false;
|
|
55
56
|
if (e.key === "image.items") {
|
|
56
57
|
this.items = e.value || [];
|
|
57
|
-
shouldUpdate = true;
|
|
58
|
-
} else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
|
|
59
|
-
// Dieline changes affect image layout/scale
|
|
60
|
-
// Ignore dieline.holes as they don't affect layout and can cause jitter
|
|
61
|
-
shouldUpdate = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (shouldUpdate) {
|
|
65
58
|
this.updateImages();
|
|
66
59
|
}
|
|
67
60
|
});
|
|
@@ -100,15 +93,43 @@ export class ImageTool implements Extension {
|
|
|
100
93
|
{
|
|
101
94
|
command: "addImage",
|
|
102
95
|
title: "Add Image",
|
|
103
|
-
handler: (url: string, options?: Partial<ImageItem>) => {
|
|
96
|
+
handler: async (url: string, options?: Partial<ImageItem>) => {
|
|
97
|
+
const id = this.generateId();
|
|
104
98
|
const newItem: ImageItem = {
|
|
105
|
-
id
|
|
99
|
+
id,
|
|
106
100
|
url,
|
|
107
101
|
opacity: 1,
|
|
108
102
|
...options,
|
|
109
103
|
};
|
|
104
|
+
|
|
105
|
+
const promise = new Promise<string>((resolve) => {
|
|
106
|
+
this.loadResolvers.set(id, () => resolve(id));
|
|
107
|
+
});
|
|
108
|
+
|
|
110
109
|
this.updateConfig([...this.items, newItem]);
|
|
111
|
-
return
|
|
110
|
+
return promise;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
command: "fitImageToArea",
|
|
115
|
+
title: "Fit Image to Area",
|
|
116
|
+
handler: (
|
|
117
|
+
id: string,
|
|
118
|
+
area: { width: number; height: number; left?: number; top?: number },
|
|
119
|
+
) => {
|
|
120
|
+
const item = this.items.find((i) => i.id === id);
|
|
121
|
+
const obj = this.objectMap.get(id);
|
|
122
|
+
if (item && obj && obj.width && obj.height) {
|
|
123
|
+
const scale = Math.max(
|
|
124
|
+
area.width / obj.width,
|
|
125
|
+
area.height / obj.height,
|
|
126
|
+
);
|
|
127
|
+
this.updateImageInConfig(id, {
|
|
128
|
+
scale,
|
|
129
|
+
left: area.left ?? 0.5,
|
|
130
|
+
top: area.top ?? 0.5,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
112
133
|
},
|
|
113
134
|
},
|
|
114
135
|
{
|
|
@@ -178,7 +199,9 @@ export class ImageTool implements Extension {
|
|
|
178
199
|
if (!this.context) return;
|
|
179
200
|
this.isUpdatingConfig = true;
|
|
180
201
|
this.items = newItems;
|
|
181
|
-
const configService = this.context.services.get<ConfigurationService>(
|
|
202
|
+
const configService = this.context.services.get<ConfigurationService>(
|
|
203
|
+
"ConfigurationService",
|
|
204
|
+
);
|
|
182
205
|
if (configService) {
|
|
183
206
|
configService.update("image.items", newItems);
|
|
184
207
|
}
|
|
@@ -187,7 +210,7 @@ export class ImageTool implements Extension {
|
|
|
187
210
|
if (!skipCanvasUpdate) {
|
|
188
211
|
this.updateImages();
|
|
189
212
|
}
|
|
190
|
-
|
|
213
|
+
|
|
191
214
|
// Reset flag after a short delay to allow config propagation
|
|
192
215
|
setTimeout(() => {
|
|
193
216
|
this.isUpdatingConfig = false;
|
|
@@ -214,7 +237,9 @@ export class ImageTool implements Extension {
|
|
|
214
237
|
// Try to insert below dieline-overlay
|
|
215
238
|
const dielineLayer = this.canvasService.getLayer("dieline-overlay");
|
|
216
239
|
if (dielineLayer) {
|
|
217
|
-
const index = this.canvasService.canvas
|
|
240
|
+
const index = this.canvasService.canvas
|
|
241
|
+
.getObjects()
|
|
242
|
+
.indexOf(dielineLayer);
|
|
218
243
|
// If dieline is at 0, move user to 0 (dieline shifts to 1)
|
|
219
244
|
if (index >= 0) {
|
|
220
245
|
this.canvasService.canvas.moveObjectTo(userLayer, index);
|
|
@@ -233,58 +258,13 @@ export class ImageTool implements Extension {
|
|
|
233
258
|
private getLayoutInfo() {
|
|
234
259
|
const canvasW = this.canvasService?.canvas.width || 800;
|
|
235
260
|
const canvasH = this.canvasService?.canvas.height || 600;
|
|
236
|
-
|
|
237
|
-
let layoutScale = 1;
|
|
238
|
-
let layoutOffsetX = 0;
|
|
239
|
-
let layoutOffsetY = 0;
|
|
240
|
-
let visualWidth = canvasW;
|
|
241
|
-
let visualHeight = canvasH;
|
|
242
|
-
let dielinePhysicalWidth = 500;
|
|
243
|
-
let dielinePhysicalHeight = 500;
|
|
244
|
-
let bleedOffset = 0;
|
|
245
|
-
|
|
246
|
-
if (this.context) {
|
|
247
|
-
const configService = this.context.services.get<ConfigurationService>("ConfigurationService");
|
|
248
|
-
if (configService) {
|
|
249
|
-
dielinePhysicalWidth = configService.get("dieline.width") || 500;
|
|
250
|
-
dielinePhysicalHeight = configService.get("dieline.height") || 500;
|
|
251
|
-
bleedOffset = configService.get("dieline.offset") || 0;
|
|
252
|
-
|
|
253
|
-
const paddingValue = configService.get("dieline.padding") || 40;
|
|
254
|
-
let padding = 0;
|
|
255
|
-
if (typeof paddingValue === "number") {
|
|
256
|
-
padding = paddingValue;
|
|
257
|
-
} else if (typeof paddingValue === "string") {
|
|
258
|
-
if (paddingValue.endsWith("%")) {
|
|
259
|
-
const percent = parseFloat(paddingValue) / 100;
|
|
260
|
-
padding = Math.min(canvasW, canvasH) * percent;
|
|
261
|
-
} else {
|
|
262
|
-
padding = parseFloat(paddingValue) || 0;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const layout = Coordinate.calculateLayout(
|
|
267
|
-
{ width: canvasW, height: canvasH },
|
|
268
|
-
{ width: dielinePhysicalWidth, height: dielinePhysicalHeight },
|
|
269
|
-
padding
|
|
270
|
-
);
|
|
271
|
-
layoutScale = layout.scale;
|
|
272
|
-
layoutOffsetX = layout.offsetX;
|
|
273
|
-
layoutOffsetY = layout.offsetY;
|
|
274
|
-
visualWidth = layout.width;
|
|
275
|
-
visualHeight = layout.height;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
261
|
|
|
279
262
|
return {
|
|
280
|
-
layoutScale,
|
|
281
|
-
layoutOffsetX,
|
|
282
|
-
layoutOffsetY,
|
|
283
|
-
visualWidth,
|
|
284
|
-
visualHeight,
|
|
285
|
-
dielinePhysicalWidth,
|
|
286
|
-
dielinePhysicalHeight,
|
|
287
|
-
bleedOffset
|
|
263
|
+
layoutScale: 1,
|
|
264
|
+
layoutOffsetX: 0,
|
|
265
|
+
layoutOffsetY: 0,
|
|
266
|
+
visualWidth: canvasW,
|
|
267
|
+
visualHeight: canvasH,
|
|
288
268
|
};
|
|
289
269
|
}
|
|
290
270
|
|
|
@@ -297,7 +277,7 @@ export class ImageTool implements Extension {
|
|
|
297
277
|
}
|
|
298
278
|
|
|
299
279
|
// 1. Remove objects that are no longer in items
|
|
300
|
-
const currentIds = new Set(this.items.map(i => i.id));
|
|
280
|
+
const currentIds = new Set(this.items.map((i) => i.id));
|
|
301
281
|
for (const [id, obj] of this.objectMap) {
|
|
302
282
|
if (!currentIds.has(id)) {
|
|
303
283
|
layer.remove(obj);
|
|
@@ -316,38 +296,38 @@ export class ImageTool implements Extension {
|
|
|
316
296
|
this.loadImage(item, layer, layout);
|
|
317
297
|
} else {
|
|
318
298
|
// Existing object, update properties
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// Ensure Z-Index order
|
|
322
|
-
// Note: layer.add() appends to end, so if we process in order, they should be roughly correct.
|
|
323
|
-
// However, if we need strict ordering, we might need to verify index.
|
|
324
|
-
// For simplicity, we rely on the fact that if it exists, it's already on canvas.
|
|
325
|
-
// To enforce strict Z-order matching array order:
|
|
326
|
-
// We can check if the object at layer._objects[index] is this object.
|
|
327
|
-
// But Fabric's Group/Layer handling might be complex.
|
|
328
|
-
// A simple way is: remove and re-add if order is wrong, or use moveObjectTo.
|
|
329
|
-
|
|
330
|
-
// Since we are iterating items in order, we can check if the object is at the expected visual index relative to other user images.
|
|
331
|
-
// But for now, let's assume update logic is sufficient.
|
|
332
|
-
// If we want to support reordering, we should probably just `moveTo`
|
|
299
|
+
// We remove and re-add to ensure coordinates are correctly converted
|
|
300
|
+
// from absolute (updateObjectProperties) to relative (layer.add)
|
|
333
301
|
layer.remove(obj);
|
|
334
|
-
|
|
302
|
+
this.updateObjectProperties(obj, item, layout);
|
|
303
|
+
layer.add(obj);
|
|
335
304
|
}
|
|
336
305
|
});
|
|
337
|
-
|
|
306
|
+
|
|
338
307
|
layer.dirty = true;
|
|
339
308
|
this.canvasService.requestRenderAll();
|
|
340
309
|
}
|
|
341
310
|
|
|
342
|
-
private updateObjectProperties(
|
|
343
|
-
|
|
311
|
+
private updateObjectProperties(
|
|
312
|
+
obj: FabricObject,
|
|
313
|
+
item: ImageItem,
|
|
314
|
+
layout: any,
|
|
315
|
+
) {
|
|
316
|
+
const {
|
|
317
|
+
layoutScale,
|
|
318
|
+
layoutOffsetX,
|
|
319
|
+
layoutOffsetY,
|
|
320
|
+
visualWidth,
|
|
321
|
+
visualHeight,
|
|
322
|
+
} = layout;
|
|
344
323
|
const updates: any = {};
|
|
345
324
|
|
|
346
325
|
// Opacity
|
|
347
326
|
if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
|
|
348
|
-
|
|
327
|
+
|
|
349
328
|
// Angle
|
|
350
|
-
if (item.angle !== undefined && obj.angle !== item.angle)
|
|
329
|
+
if (item.angle !== undefined && obj.angle !== item.angle)
|
|
330
|
+
updates.angle = item.angle;
|
|
351
331
|
|
|
352
332
|
// Position (Normalized -> Absolute)
|
|
353
333
|
if (item.left !== undefined) {
|
|
@@ -359,27 +339,27 @@ export class ImageTool implements Extension {
|
|
|
359
339
|
if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
|
|
360
340
|
}
|
|
361
341
|
|
|
362
|
-
// Scale
|
|
363
|
-
if (item.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (Math.abs(obj.scaleY - targetScaleY) > 0.001) updates.scaleY = targetScaleY;
|
|
342
|
+
// Scale
|
|
343
|
+
if (item.scale !== undefined) {
|
|
344
|
+
const targetScale = item.scale * layoutScale;
|
|
345
|
+
if (Math.abs(obj.scaleX - targetScale) > 0.001) {
|
|
346
|
+
updates.scaleX = targetScale;
|
|
347
|
+
updates.scaleY = targetScale;
|
|
348
|
+
}
|
|
370
349
|
}
|
|
371
|
-
|
|
350
|
+
|
|
372
351
|
// Center origin if not set
|
|
373
352
|
if (obj.originX !== "center") {
|
|
374
353
|
updates.originX = "center";
|
|
375
354
|
updates.originY = "center";
|
|
376
355
|
// Adjust position because origin changed (Fabric logic)
|
|
377
|
-
// For simplicity, we just set it, next cycle will fix pos if needed,
|
|
356
|
+
// For simplicity, we just set it, next cycle will fix pos if needed,
|
|
378
357
|
// or we can calculate the shift. Ideally we set origin on creation.
|
|
379
358
|
}
|
|
380
359
|
|
|
381
360
|
if (Object.keys(updates).length > 0) {
|
|
382
361
|
obj.set(updates);
|
|
362
|
+
obj.setCoords();
|
|
383
363
|
}
|
|
384
364
|
}
|
|
385
365
|
|
|
@@ -387,7 +367,7 @@ export class ImageTool implements Extension {
|
|
|
387
367
|
Image.fromURL(item.url, { crossOrigin: "anonymous" })
|
|
388
368
|
.then((image) => {
|
|
389
369
|
// Double check if item still exists
|
|
390
|
-
if (!this.items.find(i => i.id === item.id)) return;
|
|
370
|
+
if (!this.items.find((i) => i.id === item.id)) return;
|
|
391
371
|
|
|
392
372
|
image.set({
|
|
393
373
|
originX: "center",
|
|
@@ -405,26 +385,11 @@ export class ImageTool implements Extension {
|
|
|
405
385
|
});
|
|
406
386
|
|
|
407
387
|
// Initial Layout
|
|
408
|
-
let {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
// Calculate target dimensions including bleed
|
|
414
|
-
const targetWidth = dielinePhysicalWidth + 2 * bleedOffset;
|
|
415
|
-
const targetHeight = dielinePhysicalHeight + 2 * bleedOffset;
|
|
416
|
-
|
|
417
|
-
// "适应最长边" (Fit to longest side) logic
|
|
418
|
-
const targetMax = Math.max(targetWidth, targetHeight);
|
|
419
|
-
const imageMax = Math.max(image.width || 1, image.height || 1);
|
|
420
|
-
const scale = targetMax / imageMax;
|
|
421
|
-
|
|
422
|
-
width = (image.width || 1) * scale;
|
|
423
|
-
height = (image.height || 1) * scale;
|
|
424
|
-
|
|
425
|
-
// Update item with defaults
|
|
426
|
-
item.width = width;
|
|
427
|
-
item.height = height;
|
|
388
|
+
let { scale, left, top } = item;
|
|
389
|
+
|
|
390
|
+
if (scale === undefined) {
|
|
391
|
+
scale = 1; // Default scale if not provided and not fitted yet
|
|
392
|
+
item.scale = scale;
|
|
428
393
|
}
|
|
429
394
|
|
|
430
395
|
if (left === undefined && top === undefined) {
|
|
@@ -440,6 +405,13 @@ export class ImageTool implements Extension {
|
|
|
440
405
|
layer.add(image);
|
|
441
406
|
this.objectMap.set(item.id, image);
|
|
442
407
|
|
|
408
|
+
// Notify addImage that load is complete
|
|
409
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
410
|
+
if (resolver) {
|
|
411
|
+
resolver();
|
|
412
|
+
this.loadResolvers.delete(item.id);
|
|
413
|
+
}
|
|
414
|
+
|
|
443
415
|
// Bind Events
|
|
444
416
|
image.on("modified", (e: any) => {
|
|
445
417
|
this.handleObjectModified(item.id, image);
|
|
@@ -447,10 +419,10 @@ export class ImageTool implements Extension {
|
|
|
447
419
|
|
|
448
420
|
layer.dirty = true;
|
|
449
421
|
this.canvasService?.requestRenderAll();
|
|
450
|
-
|
|
451
|
-
// Save defaults if we
|
|
452
|
-
if (item.
|
|
453
|
-
|
|
422
|
+
|
|
423
|
+
// Save defaults if we set them
|
|
424
|
+
if (item.scale !== scale || item.left !== left || item.top !== top) {
|
|
425
|
+
this.updateImageInConfig(item.id, { scale, left, top }, true);
|
|
454
426
|
}
|
|
455
427
|
})
|
|
456
428
|
.catch((err) => {
|
|
@@ -460,7 +432,13 @@ export class ImageTool implements Extension {
|
|
|
460
432
|
|
|
461
433
|
private handleObjectModified(id: string, image: FabricObject) {
|
|
462
434
|
const layout = this.getLayoutInfo();
|
|
463
|
-
const {
|
|
435
|
+
const {
|
|
436
|
+
layoutScale,
|
|
437
|
+
layoutOffsetX,
|
|
438
|
+
layoutOffsetY,
|
|
439
|
+
visualWidth,
|
|
440
|
+
visualHeight,
|
|
441
|
+
} = layout;
|
|
464
442
|
|
|
465
443
|
const matrix = image.calcTransformMatrix();
|
|
466
444
|
const globalPoint = util.transformPoint(new Point(0, 0), matrix);
|
|
@@ -472,25 +450,22 @@ export class ImageTool implements Extension {
|
|
|
472
450
|
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
473
451
|
updates.angle = image.angle;
|
|
474
452
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
const pixelWidth = image.width * image.scaleX;
|
|
478
|
-
updates.width = pixelWidth / layoutScale;
|
|
479
|
-
}
|
|
480
|
-
if (image.height) {
|
|
481
|
-
const pixelHeight = image.height * image.scaleY;
|
|
482
|
-
updates.height = pixelHeight / layoutScale;
|
|
483
|
-
}
|
|
453
|
+
// Scale
|
|
454
|
+
updates.scale = image.scaleX / layoutScale;
|
|
484
455
|
|
|
485
|
-
this.updateImageInConfig(id, updates);
|
|
456
|
+
this.updateImageInConfig(id, updates, true);
|
|
486
457
|
}
|
|
487
458
|
|
|
488
|
-
private updateImageInConfig(
|
|
489
|
-
|
|
459
|
+
private updateImageInConfig(
|
|
460
|
+
id: string,
|
|
461
|
+
updates: Partial<ImageItem>,
|
|
462
|
+
skipCanvasUpdate = false,
|
|
463
|
+
) {
|
|
464
|
+
const index = this.items.findIndex((i) => i.id === id);
|
|
490
465
|
if (index !== -1) {
|
|
491
466
|
const newItems = [...this.items];
|
|
492
467
|
newItems[index] = { ...newItems[index], ...updates };
|
|
493
|
-
this.updateConfig(newItems,
|
|
468
|
+
this.updateConfig(newItems, skipCanvasUpdate);
|
|
494
469
|
}
|
|
495
470
|
}
|
|
496
471
|
}
|