@pooder/kit 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/index.d.mts +167 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +1693 -0
- package/dist/index.mjs +1654 -0
- package/package.json +27 -0
- package/src/background.ts +173 -0
- package/src/dieline.ts +424 -0
- package/src/film.ts +155 -0
- package/src/geometry.ts +244 -0
- package/src/hole.ts +413 -0
- package/src/image.ts +146 -0
- package/src/index.ts +7 -0
- package/src/ruler.ts +238 -0
- package/src/white-ink.ts +302 -0
- package/tsconfig.json +13 -0
package/src/ruler.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Command, Editor, EditorState, Extension, OptionSchema, PooderLayer, Rect, Line, Text } from '@pooder/core';
|
|
2
|
+
|
|
3
|
+
export interface RulerToolOptions {
|
|
4
|
+
unit: 'px' | 'mm' | 'cm' | 'in';
|
|
5
|
+
thickness: number;
|
|
6
|
+
backgroundColor: string;
|
|
7
|
+
textColor: string;
|
|
8
|
+
lineColor: string;
|
|
9
|
+
fontSize: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class RulerTool implements Extension<RulerToolOptions> {
|
|
13
|
+
public name = 'RulerTool';
|
|
14
|
+
public options: RulerToolOptions = {
|
|
15
|
+
unit: 'px',
|
|
16
|
+
thickness: 20,
|
|
17
|
+
backgroundColor: '#f0f0f0',
|
|
18
|
+
textColor: '#333333',
|
|
19
|
+
lineColor: '#999999',
|
|
20
|
+
fontSize: 10
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
public schema: Record<keyof RulerToolOptions, OptionSchema> = {
|
|
24
|
+
unit: {
|
|
25
|
+
type: 'select',
|
|
26
|
+
options: ['px', 'mm', 'cm', 'in'],
|
|
27
|
+
label: 'Unit'
|
|
28
|
+
},
|
|
29
|
+
thickness: { type: 'number', min: 10, max: 100, label: 'Thickness' },
|
|
30
|
+
backgroundColor: { type: 'color', label: 'Background Color' },
|
|
31
|
+
textColor: { type: 'color', label: 'Text Color' },
|
|
32
|
+
lineColor: { type: 'color', label: 'Line Color' },
|
|
33
|
+
fontSize: { type: 'number', min: 8, max: 24, label: 'Font Size' }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
onMount(editor: Editor) {
|
|
37
|
+
this.createLayer(editor);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onUnmount(editor: Editor) {
|
|
41
|
+
this.destroyLayer(editor);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onUpdate(editor: Editor, state: EditorState) {
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onDestroy(editor: Editor) {
|
|
48
|
+
this.destroyLayer(editor);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private getLayer(editor: Editor) {
|
|
52
|
+
return editor.canvas.getObjects().find((obj: any) => obj.data?.id === 'ruler-overlay') as PooderLayer | undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private createLayer(editor: Editor) {
|
|
56
|
+
let layer = this.getLayer(editor);
|
|
57
|
+
|
|
58
|
+
if (!layer) {
|
|
59
|
+
const width = editor.canvas.width || 800;
|
|
60
|
+
const height = editor.canvas.height || 600;
|
|
61
|
+
|
|
62
|
+
layer = new PooderLayer([], {
|
|
63
|
+
width,
|
|
64
|
+
height,
|
|
65
|
+
selectable: false,
|
|
66
|
+
evented: false,
|
|
67
|
+
data: { id: 'ruler-overlay' }
|
|
68
|
+
} as any);
|
|
69
|
+
|
|
70
|
+
editor.canvas.add(layer);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
editor.canvas.bringObjectToFront(layer);
|
|
74
|
+
this.updateRuler(editor);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private destroyLayer(editor: Editor) {
|
|
78
|
+
const layer = this.getLayer(editor);
|
|
79
|
+
if (layer) {
|
|
80
|
+
editor.canvas.remove(layer);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private updateRuler(editor: Editor) {
|
|
85
|
+
const layer = this.getLayer(editor);
|
|
86
|
+
if (!layer) return;
|
|
87
|
+
|
|
88
|
+
layer.remove(...layer.getObjects());
|
|
89
|
+
|
|
90
|
+
const { thickness, backgroundColor, lineColor, textColor, fontSize } = this.options;
|
|
91
|
+
const width = editor.canvas.width || 800;
|
|
92
|
+
const height = editor.canvas.height || 600;
|
|
93
|
+
|
|
94
|
+
// Backgrounds
|
|
95
|
+
const topBg = new Rect({
|
|
96
|
+
left: 0,
|
|
97
|
+
top: 0,
|
|
98
|
+
width: width,
|
|
99
|
+
height: thickness,
|
|
100
|
+
fill: backgroundColor,
|
|
101
|
+
selectable: false,
|
|
102
|
+
evented: false
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const leftBg = new Rect({
|
|
106
|
+
left: 0,
|
|
107
|
+
top: 0,
|
|
108
|
+
width: thickness,
|
|
109
|
+
height: height,
|
|
110
|
+
fill: backgroundColor,
|
|
111
|
+
selectable: false,
|
|
112
|
+
evented: false
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const cornerBg = new Rect({
|
|
116
|
+
left: 0,
|
|
117
|
+
top: 0,
|
|
118
|
+
width: thickness,
|
|
119
|
+
height: thickness,
|
|
120
|
+
fill: backgroundColor,
|
|
121
|
+
stroke: lineColor,
|
|
122
|
+
strokeWidth: 1,
|
|
123
|
+
selectable: false,
|
|
124
|
+
evented: false
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
layer.add(topBg, leftBg, cornerBg);
|
|
128
|
+
|
|
129
|
+
// Drawing Constants (Pixel based for now)
|
|
130
|
+
const step = 100; // Major tick
|
|
131
|
+
const subStep = 10; // Minor tick
|
|
132
|
+
const midStep = 50; // Medium tick
|
|
133
|
+
|
|
134
|
+
// Top Ruler
|
|
135
|
+
for (let x = 0; x <= width; x += subStep) {
|
|
136
|
+
if (x < thickness) continue; // Skip corner
|
|
137
|
+
|
|
138
|
+
let len = thickness * 0.25;
|
|
139
|
+
if (x % step === 0) len = thickness * 0.8;
|
|
140
|
+
else if (x % midStep === 0) len = thickness * 0.5;
|
|
141
|
+
|
|
142
|
+
const line = new Line([x, thickness - len, x, thickness], {
|
|
143
|
+
stroke: lineColor,
|
|
144
|
+
strokeWidth: 1,
|
|
145
|
+
selectable: false,
|
|
146
|
+
evented: false
|
|
147
|
+
});
|
|
148
|
+
layer.add(line);
|
|
149
|
+
|
|
150
|
+
if (x % step === 0) {
|
|
151
|
+
const text = new Text(x.toString(), {
|
|
152
|
+
left: x + 2,
|
|
153
|
+
top: 2,
|
|
154
|
+
fontSize: fontSize,
|
|
155
|
+
fill: textColor,
|
|
156
|
+
fontFamily: 'Arial',
|
|
157
|
+
selectable: false,
|
|
158
|
+
evented: false
|
|
159
|
+
});
|
|
160
|
+
layer.add(text);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Left Ruler
|
|
165
|
+
for (let y = 0; y <= height; y += subStep) {
|
|
166
|
+
if (y < thickness) continue; // Skip corner
|
|
167
|
+
|
|
168
|
+
let len = thickness * 0.25;
|
|
169
|
+
if (y % step === 0) len = thickness * 0.8;
|
|
170
|
+
else if (y % midStep === 0) len = thickness * 0.5;
|
|
171
|
+
|
|
172
|
+
const line = new Line([thickness - len, y, thickness, y], {
|
|
173
|
+
stroke: lineColor,
|
|
174
|
+
strokeWidth: 1,
|
|
175
|
+
selectable: false,
|
|
176
|
+
evented: false
|
|
177
|
+
});
|
|
178
|
+
layer.add(line);
|
|
179
|
+
|
|
180
|
+
if (y % step === 0) {
|
|
181
|
+
const text = new Text(y.toString(), {
|
|
182
|
+
angle: -90,
|
|
183
|
+
left: thickness / 2 - fontSize / 3, // approximate centering
|
|
184
|
+
top: y + fontSize,
|
|
185
|
+
fontSize: fontSize,
|
|
186
|
+
fill: textColor,
|
|
187
|
+
fontFamily: 'Arial',
|
|
188
|
+
originX: 'center',
|
|
189
|
+
originY: 'center',
|
|
190
|
+
selectable: false,
|
|
191
|
+
evented: false
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
layer.add(text);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Always bring ruler to front
|
|
199
|
+
editor.canvas.bringObjectToFront(layer);
|
|
200
|
+
editor.canvas.requestRenderAll();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
commands: Record<string, Command> = {
|
|
204
|
+
setUnit: {
|
|
205
|
+
execute: (editor: Editor, unit: 'px' | 'mm' | 'cm' | 'in') => {
|
|
206
|
+
if (this.options.unit === unit) return true;
|
|
207
|
+
this.options.unit = unit;
|
|
208
|
+
this.updateRuler(editor);
|
|
209
|
+
return true;
|
|
210
|
+
},
|
|
211
|
+
schema: {
|
|
212
|
+
unit: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
label: 'Unit',
|
|
215
|
+
options: ['px', 'mm', 'cm', 'in'],
|
|
216
|
+
required: true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
setTheme: {
|
|
221
|
+
execute: (editor: Editor, theme: Partial<RulerToolOptions>) => {
|
|
222
|
+
const newOptions = { ...this.options, ...theme };
|
|
223
|
+
if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
|
|
224
|
+
|
|
225
|
+
this.options = newOptions;
|
|
226
|
+
this.updateRuler(editor);
|
|
227
|
+
return true;
|
|
228
|
+
},
|
|
229
|
+
schema: {
|
|
230
|
+
theme: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
label: 'Theme',
|
|
233
|
+
required: true
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
package/src/white-ink.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Command,
|
|
3
|
+
Editor,
|
|
4
|
+
EditorState,
|
|
5
|
+
EventHandler,
|
|
6
|
+
Extension,
|
|
7
|
+
OptionSchema,
|
|
8
|
+
Image,
|
|
9
|
+
filters,
|
|
10
|
+
PooderObject,
|
|
11
|
+
PooderLayer
|
|
12
|
+
} from '@pooder/core';
|
|
13
|
+
|
|
14
|
+
interface WhiteInkToolOptions {
|
|
15
|
+
customMask: string;
|
|
16
|
+
opacity: number;
|
|
17
|
+
enableClip: boolean;
|
|
18
|
+
}
|
|
19
|
+
export class WhiteInkTool implements Extension<WhiteInkToolOptions> {
|
|
20
|
+
public name = 'WhiteInkTool';
|
|
21
|
+
public options: WhiteInkToolOptions = {
|
|
22
|
+
customMask: '',
|
|
23
|
+
opacity: 1,
|
|
24
|
+
enableClip: false
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
public schema: Record<keyof WhiteInkToolOptions, OptionSchema> = {
|
|
28
|
+
customMask: { type: 'string', label: 'Custom Mask URL' },
|
|
29
|
+
opacity: { type: 'number', min: 0, max: 1, step: 0.01, label: 'Opacity' },
|
|
30
|
+
enableClip: { type: 'boolean', label: 'Enable Clip' }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private syncHandler: EventHandler | undefined;
|
|
34
|
+
|
|
35
|
+
onMount(editor: Editor) {
|
|
36
|
+
this.setup(editor);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onUnmount(editor: Editor) {
|
|
40
|
+
this.teardown(editor);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onDestroy(editor: Editor) {
|
|
44
|
+
this.teardown(editor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private setup(editor: Editor) {
|
|
48
|
+
let userLayer = editor.getLayer("user");
|
|
49
|
+
if (!userLayer) {
|
|
50
|
+
userLayer = new PooderLayer([], {
|
|
51
|
+
width: editor.state.width,
|
|
52
|
+
height: editor.state.height,
|
|
53
|
+
left: 0,
|
|
54
|
+
top: 0,
|
|
55
|
+
originX: 'left',
|
|
56
|
+
originY: 'top',
|
|
57
|
+
selectable: false,
|
|
58
|
+
evented: true,
|
|
59
|
+
subTargetCheck: true,
|
|
60
|
+
interactive: true,
|
|
61
|
+
data: {
|
|
62
|
+
id: 'user'
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
editor.canvas.add(userLayer);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!this.syncHandler) {
|
|
69
|
+
this.syncHandler = (e: any) => {
|
|
70
|
+
const target = e.target;
|
|
71
|
+
if (target && target.data?.id === 'user-image') {
|
|
72
|
+
this.syncWithUserImage(editor);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
editor.canvas.on('object:moving', this.syncHandler);
|
|
77
|
+
editor.canvas.on('object:scaling', this.syncHandler);
|
|
78
|
+
editor.canvas.on('object:rotating', this.syncHandler);
|
|
79
|
+
editor.canvas.on('object:modified', this.syncHandler);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.updateWhiteInk(editor, this.options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private teardown(editor: Editor) {
|
|
86
|
+
if (this.syncHandler) {
|
|
87
|
+
editor.canvas.off('object:moving', this.syncHandler);
|
|
88
|
+
editor.canvas.off('object:scaling', this.syncHandler);
|
|
89
|
+
editor.canvas.off('object:rotating', this.syncHandler);
|
|
90
|
+
editor.canvas.off('object:modified', this.syncHandler);
|
|
91
|
+
this.syncHandler = undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const layer = editor.getLayer("user");
|
|
95
|
+
if (layer) {
|
|
96
|
+
const whiteInk = editor.getObject("white-ink", "user");
|
|
97
|
+
if (whiteInk) {
|
|
98
|
+
layer.remove(whiteInk);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const userImage = editor.getObject("user-image", "user") as any;
|
|
103
|
+
if (userImage && userImage.clipPath) {
|
|
104
|
+
userImage.set({ clipPath: undefined });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
editor.canvas.requestRenderAll();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onUpdate(editor: Editor, state: EditorState) {
|
|
111
|
+
this.updateWhiteInk(editor, this.options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
commands: Record<string, Command> = {
|
|
115
|
+
setWhiteInkImage: {
|
|
116
|
+
execute: (editor: Editor, customMask: string, opacity: number, enableClip: boolean = true) => {
|
|
117
|
+
if (this.options.customMask === customMask &&
|
|
118
|
+
this.options.opacity === opacity &&
|
|
119
|
+
this.options.enableClip === enableClip) return true;
|
|
120
|
+
|
|
121
|
+
this.options.customMask = customMask;
|
|
122
|
+
this.options.opacity = opacity;
|
|
123
|
+
this.options.enableClip = enableClip;
|
|
124
|
+
|
|
125
|
+
this.updateWhiteInk(editor, this.options);
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
},
|
|
129
|
+
schema: {
|
|
130
|
+
customMask: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
label: 'Custom Mask URL',
|
|
133
|
+
required: true
|
|
134
|
+
},
|
|
135
|
+
opacity: {
|
|
136
|
+
type: 'number',
|
|
137
|
+
label: 'Opacity',
|
|
138
|
+
min: 0,
|
|
139
|
+
max: 1,
|
|
140
|
+
required: true
|
|
141
|
+
},
|
|
142
|
+
enableClip: {
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
label: 'Enable Clip',
|
|
145
|
+
default: true,
|
|
146
|
+
required: false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
private updateWhiteInk(editor: Editor, opts: WhiteInkToolOptions) {
|
|
153
|
+
const { customMask, opacity, enableClip } = opts;
|
|
154
|
+
|
|
155
|
+
const layer = editor.getLayer("user");
|
|
156
|
+
if (!layer) {
|
|
157
|
+
console.warn('[WhiteInkTool] User layer not found');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const whiteInk = editor.getObject("white-ink", "user") as any;
|
|
162
|
+
const userImage = editor.getObject("user-image", "user") as any;
|
|
163
|
+
|
|
164
|
+
if (!customMask) {
|
|
165
|
+
if (whiteInk) {
|
|
166
|
+
layer.remove(whiteInk);
|
|
167
|
+
}
|
|
168
|
+
if (userImage && userImage.clipPath) {
|
|
169
|
+
userImage.set({ clipPath: undefined });
|
|
170
|
+
}
|
|
171
|
+
editor.canvas.requestRenderAll();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if we need to load/reload white ink backing
|
|
176
|
+
if (whiteInk) {
|
|
177
|
+
const currentSrc = whiteInk.getSrc?.() || whiteInk._element?.src;
|
|
178
|
+
if (currentSrc !== customMask) {
|
|
179
|
+
this.loadWhiteInk(editor, layer, customMask, opacity, enableClip, whiteInk);
|
|
180
|
+
} else {
|
|
181
|
+
if (whiteInk.opacity !== opacity) {
|
|
182
|
+
whiteInk.set({ opacity });
|
|
183
|
+
editor.canvas.requestRenderAll();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
this.loadWhiteInk(editor, layer, customMask, opacity, enableClip);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Handle Clip Path Toggle
|
|
191
|
+
if (userImage) {
|
|
192
|
+
if (enableClip) {
|
|
193
|
+
// If enabled but missing, or mask changed (handled by re-load above, but good to ensure), apply it
|
|
194
|
+
// We check if clipPath is present. Ideally we should check if it matches current mask,
|
|
195
|
+
// but re-applying is safe.
|
|
196
|
+
if (!userImage.clipPath) {
|
|
197
|
+
this.applyClipPath(editor, customMask);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
// If disabled but present, remove it
|
|
201
|
+
if (userImage.clipPath) {
|
|
202
|
+
userImage.set({ clipPath: undefined });
|
|
203
|
+
editor.canvas.requestRenderAll();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private loadWhiteInk(editor: Editor, layer: PooderLayer, url: string, opacity: number, enableClip: boolean, oldImage?: any) {
|
|
210
|
+
Image.fromURL(url, { crossOrigin: 'anonymous' }).then(image => {
|
|
211
|
+
if (oldImage) {
|
|
212
|
+
// Remove old image but don't copy properties yet, we'll sync with user-image
|
|
213
|
+
layer.remove(oldImage);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
image.filters?.push(new filters.BlendColor({
|
|
217
|
+
color: '#FFFFFF',
|
|
218
|
+
mode: 'add'
|
|
219
|
+
}));
|
|
220
|
+
image.applyFilters();
|
|
221
|
+
|
|
222
|
+
image.set({
|
|
223
|
+
opacity,
|
|
224
|
+
selectable: false,
|
|
225
|
+
evented: false,
|
|
226
|
+
data: {
|
|
227
|
+
id: 'white-ink'
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Add to layer
|
|
232
|
+
layer.add(image);
|
|
233
|
+
|
|
234
|
+
// Ensure white-ink is behind user-image
|
|
235
|
+
const userImage = editor.getObject("user-image", "user");
|
|
236
|
+
if (userImage) {
|
|
237
|
+
// Re-adding moves it to the top of the stack
|
|
238
|
+
layer.remove(userImage);
|
|
239
|
+
layer.add(userImage);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Apply clip path to user-image if enabled
|
|
243
|
+
if (enableClip) {
|
|
244
|
+
this.applyClipPath(editor, url);
|
|
245
|
+
} else if (userImage) {
|
|
246
|
+
userImage.set({ clipPath: undefined });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Sync position immediately
|
|
250
|
+
this.syncWithUserImage(editor);
|
|
251
|
+
|
|
252
|
+
editor.canvas.requestRenderAll();
|
|
253
|
+
}).catch(err => {
|
|
254
|
+
console.error("Failed to load white ink mask", url, err);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private applyClipPath(editor: Editor, url: string) {
|
|
259
|
+
const userImage = editor.getObject("user-image", "user") as any;
|
|
260
|
+
if (!userImage) return;
|
|
261
|
+
|
|
262
|
+
Image.fromURL(url, { crossOrigin: 'anonymous' }).then(maskImage => {
|
|
263
|
+
// Configure clipPath
|
|
264
|
+
// It needs to be relative to the object center
|
|
265
|
+
maskImage.set({
|
|
266
|
+
originX: 'center',
|
|
267
|
+
originY: 'center',
|
|
268
|
+
left: 0,
|
|
269
|
+
top: 0,
|
|
270
|
+
// Scale to fit userImage if dimensions differ
|
|
271
|
+
scaleX: userImage.width / maskImage.width,
|
|
272
|
+
scaleY: userImage.height / maskImage.height
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
userImage.set({ clipPath: maskImage });
|
|
276
|
+
editor.canvas.requestRenderAll();
|
|
277
|
+
}).catch(err => {
|
|
278
|
+
console.error("Failed to load clip path", url, err);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private syncWithUserImage(editor: Editor) {
|
|
283
|
+
const userImage = editor.getObject("user-image", "user");
|
|
284
|
+
const whiteInk = editor.getObject("white-ink", "user");
|
|
285
|
+
|
|
286
|
+
if (userImage && whiteInk) {
|
|
287
|
+
whiteInk.set({
|
|
288
|
+
left: userImage.left,
|
|
289
|
+
top: userImage.top,
|
|
290
|
+
scaleX: userImage.scaleX,
|
|
291
|
+
scaleY: userImage.scaleY,
|
|
292
|
+
angle: userImage.angle,
|
|
293
|
+
skewX: userImage.skewX,
|
|
294
|
+
skewY: userImage.skewY,
|
|
295
|
+
flipX: userImage.flipX,
|
|
296
|
+
flipY: userImage.flipY,
|
|
297
|
+
originX: userImage.originX,
|
|
298
|
+
originY: userImage.originY
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
package/tsconfig.json
ADDED