@hprint/plugins 0.0.1-alpha.1 → 0.0.1-alpha.3

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.
Files changed (64) hide show
  1. package/dist/index.js +122 -117
  2. package/dist/index.mjs +41257 -21115
  3. package/dist/src/plugins/AlignGuidLinePlugin.d.ts +7 -2
  4. package/dist/src/plugins/AlignGuidLinePlugin.d.ts.map +1 -1
  5. package/dist/src/plugins/BarCodePlugin.d.ts +4 -0
  6. package/dist/src/plugins/BarCodePlugin.d.ts.map +1 -1
  7. package/dist/src/plugins/CreateElementPlugin.d.ts +3 -9
  8. package/dist/src/plugins/CreateElementPlugin.d.ts.map +1 -1
  9. package/dist/src/plugins/GroupAlignPlugin.d.ts.map +1 -1
  10. package/dist/src/plugins/LockPlugin.d.ts.map +1 -1
  11. package/dist/src/plugins/QrCodePlugin.d.ts +19 -97
  12. package/dist/src/plugins/QrCodePlugin.d.ts.map +1 -1
  13. package/dist/src/plugins/ResizePlugin.d.ts.map +1 -1
  14. package/dist/src/plugins/UnitPlugin.d.ts.map +1 -1
  15. package/dist/src/plugins/WorkspacePlugin.d.ts.map +1 -1
  16. package/dist/src/utils/units.d.ts.map +1 -1
  17. package/package.json +5 -4
  18. package/src/assets/style/resizePlugin.css +27 -27
  19. package/src/objects/Arrow.js +47 -47
  20. package/src/objects/ThinTailArrow.js +50 -50
  21. package/src/plugins/AlignGuidLinePlugin.ts +1152 -1141
  22. package/src/plugins/BarCodePlugin.ts +33 -27
  23. package/src/plugins/ControlsPlugin.ts +251 -251
  24. package/src/plugins/ControlsRotatePlugin.ts +111 -111
  25. package/src/plugins/CopyPlugin.ts +255 -255
  26. package/src/plugins/CreateElementPlugin.ts +14 -10
  27. package/src/plugins/DeleteHotKeyPlugin.ts +57 -57
  28. package/src/plugins/DrawLinePlugin.ts +162 -162
  29. package/src/plugins/DrawPolygonPlugin.ts +205 -205
  30. package/src/plugins/DringPlugin.ts +125 -125
  31. package/src/plugins/FlipPlugin.ts +59 -59
  32. package/src/plugins/FontPlugin.ts +165 -165
  33. package/src/plugins/FreeDrawPlugin.ts +49 -49
  34. package/src/plugins/GroupAlignPlugin.ts +365 -365
  35. package/src/plugins/GroupPlugin.ts +82 -82
  36. package/src/plugins/GroupTextEditorPlugin.ts +198 -198
  37. package/src/plugins/HistoryPlugin.ts +181 -181
  38. package/src/plugins/ImageStroke.ts +121 -121
  39. package/src/plugins/LayerPlugin.ts +108 -108
  40. package/src/plugins/LockPlugin.ts +242 -240
  41. package/src/plugins/MaskPlugin.ts +155 -155
  42. package/src/plugins/MaterialPlugin.ts +224 -224
  43. package/src/plugins/MiddleMousePlugin.ts +45 -45
  44. package/src/plugins/MoveHotKeyPlugin.ts +46 -46
  45. package/src/plugins/PathTextPlugin.ts +89 -89
  46. package/src/plugins/PolygonModifyPlugin.ts +224 -224
  47. package/src/plugins/PrintPlugin.ts +81 -81
  48. package/src/plugins/PsdPlugin.ts +52 -52
  49. package/src/plugins/QrCodePlugin.ts +322 -393
  50. package/src/plugins/ResizePlugin.ts +278 -274
  51. package/src/plugins/RulerPlugin.ts +78 -78
  52. package/src/plugins/SimpleClipImagePlugin.ts +244 -244
  53. package/src/plugins/UnitPlugin.ts +326 -327
  54. package/src/plugins/WaterMarkPlugin.ts +257 -257
  55. package/src/plugins/WorkspacePlugin.ts +10 -6
  56. package/src/types/eventType.ts +11 -11
  57. package/src/utils/psd.js +432 -432
  58. package/src/utils/ruler/guideline.ts +145 -145
  59. package/src/utils/ruler/index.ts +91 -91
  60. package/src/utils/ruler/ruler.ts +924 -924
  61. package/src/utils/ruler/utils.ts +162 -162
  62. package/src/utils/units.ts +4 -2
  63. package/tsconfig.json +10 -10
  64. package/vite.config.ts +29 -29
@@ -1,393 +1,322 @@
1
- import { fabric } from '@hprint/core';
2
- import QRCodeStyling from 'qr-code-styling';
3
- import { utils } from '@hprint/shared';
4
- import { getUnit, processOptions, formatOriginValues } from '../utils/units';
5
- import type { IEditor, IPluginTempl } from '@hprint/core';
6
-
7
- type IPlugin = Pick<QrCodePlugin, 'addQrCode' | 'setQrCode' | 'getQrCodeTypes'>;
8
-
9
- declare module '@hprint/core' {
10
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
11
- interface IEditor extends IPlugin { }
12
- }
13
-
14
- // 二维码生成参数
15
-
16
- enum DotsType {
17
- rounded = 'rounded',
18
- dots = 'dots',
19
- classy = 'classy',
20
- classy_rounded = 'classy-rounded',
21
- square = 'square',
22
- extra_rounded = 'extra-rounded',
23
- }
24
-
25
- enum CornersType {
26
- dot = 'dot',
27
- square = 'square',
28
- extra_rounded = 'extra-rounded',
29
- }
30
-
31
- enum cornersDotType {
32
- dot = 'dot',
33
- square = 'square',
34
- }
35
-
36
- enum errorCorrectionLevelType {
37
- L = 'L',
38
- M = 'M',
39
- Q = 'Q',
40
- H = 'H',
41
- }
42
-
43
- class QrParamsDefaults {
44
- width = 300;
45
- height = 300;
46
- type = 'canvas' as const;
47
- data = ' ';
48
- margin = 10;
49
- qrOptions = {
50
- errorCorrectionLevel: 'M' as const,
51
- };
52
- dotsOptions = {
53
- color: '#000000',
54
- type: 'square' as const,
55
- };
56
- cornersSquareOptions = {
57
- color: '#000000',
58
- type: 'square' as const,
59
- };
60
- cornersDotOptions = {
61
- color: '#000000',
62
- type: 'square' as const,
63
- };
64
- backgroundOptions = {
65
- color: '#ffffff',
66
- };
67
- }
68
-
69
- class QrCodePlugin implements IPluginTempl {
70
- static pluginName = 'QrCodePlugin';
71
- static apis = ['addQrCode', 'setQrCode', 'getQrCodeTypes'];
72
- constructor(
73
- public canvas: fabric.Canvas,
74
- public editor: IEditor
75
- ) { }
76
-
77
- async hookTransform(object: any) {
78
- if (object.extensionType === 'qrcode') {
79
- const paramsOption = this._paramsToOption(object.extension);
80
- const url = await this._getBase64Str(paramsOption);
81
- object.src = url;
82
- }
83
- }
84
-
85
- async _getBase64Str(options: any): Promise<string> {
86
- const zoom = this.canvas.getZoom() || 1;
87
- const dpr = (window && (window as any).devicePixelRatio) || 1;
88
- let scale = zoom * dpr;
89
- const maxScale = 4;
90
- if (!Number.isFinite(scale) || scale <= 0) scale = 1;
91
- scale = Math.min(scale, maxScale);
92
-
93
- const scaledOptions = {
94
- ...options,
95
- width: Math.round((options.width || 300) * scale),
96
- height: Math.round((options.height || options.width || 300) * scale),
97
- margin: Math.round((options.margin || 0) * scale),
98
- };
99
- const qrCode = new QRCodeStyling(scaledOptions);
100
- const blob = await qrCode.getRawData('png');
101
- if (!blob) return '';
102
- const base64Str = (await utils.blobToBase64(blob)) as string;
103
- return base64Str || '';
104
- }
105
-
106
- _defaultBarcodeOption() {
107
- return {
108
- value: 'https://kuaitu.cc',
109
- width: 300,
110
- margin: 10,
111
- errorCorrectionLevel: 'M',
112
- dotsColor: '#000000',
113
- dotsType: 'square',
114
- cornersSquareColor: '#000000',
115
- cornersSquareType: 'square',
116
- cornersDotColor: '#000000',
117
- cornersDotType: 'square',
118
- background: '#ffffff',
119
- };
120
- }
121
-
122
- /**
123
- * 将内部参数转换为二维码库需要的参数
124
- */
125
- _paramsToOption(option: any) {
126
- const hasW = Number.isFinite(option.width);
127
- const hasH = Number.isFinite(option.height);
128
- const size = hasW && hasH
129
- ? Math.max(option.width, option.height)
130
- : (hasW ? option.width : (hasH ? option.height : undefined));
131
- const base = {
132
- width: size,
133
- height: size ?? option.width,
134
- type: 'canvas',
135
- data: option.value != null ? String(option.value) : undefined,
136
- margin: option.margin,
137
- qrOptions: {
138
- errorCorrectionLevel: option.errorCorrectionLevel,
139
- },
140
- dotsOptions: {
141
- color: option.dotsColor,
142
- type: option.dotsType,
143
- },
144
- cornersSquareOptions: {
145
- color: option.cornersSquareColor,
146
- type: option.cornersSquareType,
147
- },
148
- cornersDotOptions: {
149
- color: option.cornersDotColor,
150
- type: option.cornersDotType,
151
- },
152
- backgroundOptions: {
153
- color: option.background,
154
- },
155
- };
156
- const defaultParams = new QrParamsDefaults();
157
- const merged = Object.assign({}, defaultParams, base);
158
- merged.qrOptions = Object.assign({}, defaultParams.qrOptions, base.qrOptions);
159
- merged.dotsOptions = Object.assign({}, defaultParams.dotsOptions, base.dotsOptions);
160
- merged.cornersSquareOptions = Object.assign({}, defaultParams.cornersSquareOptions, base.cornersSquareOptions);
161
- merged.cornersDotOptions = Object.assign({}, defaultParams.cornersDotOptions, base.cornersDotOptions);
162
- merged.backgroundOptions = Object.assign({}, defaultParams.backgroundOptions, base.backgroundOptions);
163
-
164
- if (!merged.data || (typeof merged.data === 'string' && merged.data.trim() === '')) {
165
- merged.data = defaultParams.data;
166
- }
167
- return merged;
168
- }
169
-
170
- private async _updateQrCodeImage(imgEl: fabric.Image, immediate = false) {
171
- const extension = imgEl.get('extension');
172
- if (!extension) return;
173
- const updateFn = async () => {
174
- const currentWidth = imgEl.getScaledWidth();
175
- const currentHeight = imgEl.getScaledHeight();
176
- const size = Math.max(currentWidth, currentHeight);
177
- const options = {
178
- ...extension,
179
- width: size,
180
- height: size,
181
- };
182
- const paramsOption = this._paramsToOption(options);
183
- try {
184
- const url = await this._getBase64Str(paramsOption);
185
- await new Promise<void>((resolve) => {
186
- imgEl.setSrc(url, () => {
187
- this._setImageScale(imgEl, currentWidth, currentHeight);
188
- imgEl.set('extension', options);
189
- this.canvas.renderAll();
190
- resolve();
191
- });
192
- });
193
- } catch (error) {
194
- console.error(error);
195
- }
196
- };
197
- if (immediate) {
198
- await updateFn();
199
- } else {
200
- setTimeout(updateFn, 300);
201
- }
202
- }
203
-
204
- /**
205
- * 设置图片缩放到目标宽高
206
- */
207
- private _setImageScale(
208
- imgEl: fabric.Image,
209
- targetWidth: number,
210
- targetHeight: number
211
- ) {
212
- const imgWidth = imgEl.width || 0;
213
- const imgHeight = imgEl.height || 0;
214
- if (imgWidth > 0 && imgHeight > 0) {
215
- const scaleX = targetWidth / imgWidth;
216
- const scaleY = targetHeight / imgHeight;
217
- imgEl.set({
218
- scaleX,
219
- scaleY,
220
- });
221
- }
222
- }
223
-
224
- /**
225
- * 创建二维码,支持传入内容与样式,进行单位转换并存储原始尺寸
226
- */
227
- async addQrCode(
228
- data?: string,
229
- opts?: {
230
- left?: number;
231
- top?: number;
232
- width?: number;
233
- height?: number;
234
- margin?: number;
235
- errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
236
- dotsColor?: string;
237
- dotsType?: string;
238
- cornersSquareColor?: string;
239
- cornersSquareType?: string;
240
- cornersDotColor?: string;
241
- cornersDotType?: string;
242
- background?: string;
243
- },
244
- dpi?: number
245
- ): Promise<fabric.Image> {
246
- const option = {
247
- ...this._defaultBarcodeOption(),
248
- ...(opts || {}),
249
- ...(data ? { value: data } : {}),
250
- };
251
- const unit = getUnit(this.editor);
252
- const { processed, originByUnit } = processOptions(option, unit, dpi, ['left', 'top', 'width', 'height', 'margin']);
253
- const finalOptionBase = { ...option, ...processed };
254
- const finalOption = {
255
- ...finalOptionBase,
256
- width: Number.isFinite(finalOptionBase.width) && finalOptionBase.width > 0
257
- ? finalOptionBase.width
258
- : this._defaultBarcodeOption().width,
259
- height: Number.isFinite(finalOptionBase.height) && finalOptionBase.height > 0
260
- ? finalOptionBase.height
261
- : finalOptionBase.width,
262
- margin: Number.isFinite(finalOptionBase.margin) && finalOptionBase.margin >= 0
263
- ? finalOptionBase.margin
264
- : this._defaultBarcodeOption().margin,
265
- };
266
- const paramsOption = this._paramsToOption(finalOption);
267
- const url = await this._getBase64Str(paramsOption);
268
- return new Promise<fabric.Image>((resolve) => {
269
- fabric.Image.fromURL(
270
- url,
271
- (imgEl) => {
272
- const safeLeft = Number.isFinite(processed.left)
273
- ? processed.left
274
- : 0;
275
- const safeTop = Number.isFinite(processed.top)
276
- ? processed.top
277
- : 0;
278
- imgEl.set({
279
- left: safeLeft,
280
- top: safeTop,
281
- extensionType: 'qrcode',
282
- extension: finalOption,
283
- imageSmoothing: false,
284
- });
285
-
286
- const targetWidth =
287
- typeof finalOption.width === 'number'
288
- ? finalOption.width
289
- : imgEl.width ?? 0;
290
- const targetHeight =
291
- typeof finalOption.height === 'number'
292
- ? finalOption.height
293
- : targetWidth;
294
- this._setImageScale(imgEl, targetWidth, targetHeight);
295
-
296
- const origin = originByUnit[unit] || {};
297
- const originMapped: Record<string, any> = { ...origin };
298
- if (
299
- originMapped.height === undefined &&
300
- originMapped.width !== undefined
301
- ) {
302
- originMapped.height = originMapped.width;
303
- }
304
- (imgEl as any)._originSize = { [unit]: originMapped };
305
- (imgEl as any).setExtension = async (fields: Record<string, any>) => {
306
- const currentExt = (imgEl.get('extension') as any) || {};
307
- const merged = { ...currentExt, ...(fields || {}) };
308
- imgEl.set('extension', merged);
309
- await this._updateQrCodeImage(imgEl, true);
310
- };
311
- (imgEl as any).setExtensionByUnit = async (
312
- fields: Record<string, any>,
313
- dpi?: number
314
- ) => {
315
- const curUnit = getUnit(this.editor);
316
- const { processed, originByUnit } = processOptions(fields || {}, curUnit, dpi);
317
- const precision = (this.editor as any).getPrecision?.();
318
- const formattedOrigin = formatOriginValues(originByUnit[curUnit] || {}, precision);
319
- const originSize = (imgEl as any)._originSize || {};
320
- const unitOrigin = originSize[curUnit] || {};
321
- unitOrigin.extension = { ...(unitOrigin.extension || {}), ...formattedOrigin };
322
- (imgEl as any)._originSize = { ...originSize, [curUnit]: unitOrigin };
323
- const currentExt = (imgEl.get('extension') as any) || {};
324
- const merged = { ...currentExt, ...processed };
325
- imgEl.set('extension', merged);
326
- await this._updateQrCodeImage(imgEl, true);
327
- };
328
- (imgEl as any).off?.('modified');
329
- (imgEl as any).off?.('scaled');
330
- imgEl.on('modified', async (event: any) => {
331
- const target = (event?.target as fabric.Image) || imgEl;
332
- await this._updateQrCodeImage(target, true);
333
- });
334
- imgEl.on('scaled', async () => {
335
- await this._updateQrCodeImage(imgEl, true);
336
- });
337
- resolve(imgEl);
338
- },
339
- { crossOrigin: 'anonymous' }
340
- );
341
- });
342
- }
343
-
344
- async setQrCode(option: any) {
345
- try {
346
- const paramsOption = this._paramsToOption(option);
347
- const url = await this._getBase64Str(paramsOption);
348
- const activeObject = this.canvas.getActiveObjects()[0];
349
- fabric.Image.fromURL(
350
- url,
351
- (imgEl) => {
352
- imgEl.set({
353
- left: activeObject.left,
354
- top: activeObject.top,
355
- extensionType: 'qrcode',
356
- extension: { ...option },
357
- });
358
- imgEl.scaleToWidth(activeObject.getScaledWidth());
359
- imgEl.off('modified');
360
- imgEl.off('scaled');
361
- imgEl.on('modified', async (event: any) => {
362
- const target = (event?.target as fabric.Image) || imgEl;
363
- await this._updateQrCodeImage(target, true);
364
- });
365
- imgEl.on('scaled', async () => {
366
- await this._updateQrCodeImage(imgEl, true);
367
- });
368
- this.editor.del();
369
- this.canvas.add(imgEl);
370
- this.canvas.setActiveObject(imgEl);
371
- },
372
- { crossOrigin: 'anonymous' }
373
- );
374
- } catch (error) {
375
- console.log(error);
376
- }
377
- }
378
-
379
- getQrCodeTypes() {
380
- return {
381
- DotsType: Object.values(DotsType),
382
- CornersType: Object.values(CornersType),
383
- cornersDotType: Object.values(cornersDotType),
384
- errorCorrectionLevelType: Object.values(errorCorrectionLevelType),
385
- };
386
- }
387
-
388
- destroy() {
389
- console.log('pluginDestroy');
390
- }
391
- }
392
-
393
- export default QrCodePlugin;
1
+ import { fabric } from '@hprint/core';
2
+ import bwipjs from 'bwip-js';
3
+ import { utils } from '@hprint/shared';
4
+ import { getUnit, processOptions, formatOriginValues } from '../utils/units';
5
+ import type { IEditor, IPluginTempl } from '@hprint/core';
6
+
7
+ type IPlugin = Pick<QrCodePlugin, 'addQrCode' | 'setQrCode'>;
8
+
9
+ declare module '@hprint/core' {
10
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
11
+ interface IEditor extends IPlugin { }
12
+ }
13
+
14
+ class QrCodePlugin implements IPluginTempl {
15
+ static pluginName = 'QrCodePlugin';
16
+ static apis = ['addQrCode', 'setQrCode'];
17
+ constructor(
18
+ public canvas: fabric.Canvas,
19
+ public editor: IEditor
20
+ ) { }
21
+
22
+ async hookTransform(object: any) {
23
+ if (object.extensionType === 'qrcode') {
24
+ const paramsOption = this._paramsToOption(object.extension);
25
+ const { url, width, height } = await this._getQrCodeResult(paramsOption);
26
+ object.src = url;
27
+
28
+ // 修复 base64 生成后,没有拉伸到容器大小的问题
29
+ if (width > 0 && height > 0) {
30
+ const oldWidth = object.width || 0;
31
+ const oldHeight = object.height || 0;
32
+ const oldScaleX = object.scaleX || 1;
33
+ const oldScaleY = object.scaleY || 1;
34
+
35
+ const displayWidth = oldWidth * oldScaleX;
36
+ const displayHeight = oldHeight * oldScaleY;
37
+
38
+ if (displayWidth > 0 && displayHeight > 0) {
39
+ object.width = width;
40
+ object.height = height;
41
+ object.scaleX = displayWidth / width;
42
+ object.scaleY = displayHeight / height;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ async hookTransformObjectEnd({ originObject, fabricObject }: { originObject: any, fabricObject: any }) {
49
+ if (originObject.extensionType === 'qrcode') {
50
+ this._bindQrCodeEvents(fabricObject);
51
+ }
52
+ }
53
+
54
+ async _getQrCodeResult(options: any): Promise<{ url: string; width: number; height: number }> {
55
+ const zoom = this.canvas.getZoom() || 1;
56
+ const dpr = (window && (window as any).devicePixelRatio) || 1;
57
+
58
+ const targetWidth = (options.width || 300) * zoom * dpr;
59
+ // 估算 module 数量,QR Code 通常在 21-177 之间,取一个中间值作为估算基础
60
+ const estimatedModules = 35;
61
+ // 计算需要的缩放比例,确保生成的图片足够大
62
+ let bwipScale = Math.ceil(targetWidth / estimatedModules);
63
+ // 保证最小缩放比例,避免过小
64
+ if (bwipScale < 2) bwipScale = 2;
65
+
66
+ const canvas = document.createElement('canvas');
67
+
68
+ const barColor = options.color?.replace('#', '') || '000000';
69
+ const bgColor = options.bgColor?.replace('#', '') || 'ffffff';
70
+ const ecLevel = options.ecLevel || 'M';
71
+
72
+ try {
73
+ bwipjs.toCanvas(canvas, {
74
+ bcid: 'qrcode',
75
+ text: options.data || ' ',
76
+ scale: bwipScale,
77
+ eclevel: ecLevel,
78
+ barcolor: barColor,
79
+ backgroundcolor: bgColor,
80
+ } as any);
81
+ return {
82
+ url: canvas.toDataURL('image/png'),
83
+ width: canvas.width,
84
+ height: canvas.height
85
+ };
86
+ } catch (error) {
87
+ console.error('QR Code generation failed:', error);
88
+ return { url: '', width: 0, height: 0 };
89
+ }
90
+ }
91
+
92
+ async _getBase64Str(options: any): Promise<string> {
93
+ const { url } = await this._getQrCodeResult(options);
94
+ return url;
95
+ }
96
+
97
+ _defaultBarcodeOption() {
98
+ return {
99
+ value: '@hprint/print',
100
+ width: 300,
101
+ margin: 10,
102
+ ecLevel: 'M',
103
+ };
104
+ }
105
+
106
+ /**
107
+ * 将内部参数转换为二维码库需要的参数
108
+ */
109
+ _paramsToOption(option: any) {
110
+ const hasW = Number.isFinite(option.width);
111
+ const hasH = Number.isFinite(option.height);
112
+ const size = hasW && hasH
113
+ ? Math.max(option.width, option.height)
114
+ : (hasW ? option.width : (hasH ? option.height : undefined));
115
+ const options = {
116
+ ...option,
117
+ width: size,
118
+ height: size ?? option.width,
119
+ type: 'canvas',
120
+ data: option.value != null ? String(option.value) : undefined,
121
+ margin: option.margin,
122
+ };
123
+ return options;
124
+ }
125
+
126
+ private async _updateQrCodeImage(imgEl: fabric.Image, immediate = false) {
127
+ const extension = imgEl.get('extension');
128
+ if (!extension) return;
129
+ const updateFn = async () => {
130
+ const currentWidth = imgEl.getScaledWidth();
131
+ const currentHeight = imgEl.getScaledHeight();
132
+ const size = Math.max(currentWidth, currentHeight);
133
+ const options = {
134
+ ...extension,
135
+ width: size,
136
+ height: size,
137
+ };
138
+ const paramsOption = this._paramsToOption(options);
139
+ try {
140
+ const url = await this._getBase64Str(paramsOption);
141
+ await new Promise<void>((resolve) => {
142
+ imgEl.setSrc(url, () => {
143
+ this._setImageScale(imgEl, currentWidth, currentHeight);
144
+ imgEl.set('extension', options);
145
+ this.canvas.renderAll();
146
+ resolve();
147
+ });
148
+ });
149
+ } catch (error) {
150
+ console.error(error);
151
+ }
152
+ };
153
+ if (immediate) {
154
+ await updateFn();
155
+ } else {
156
+ setTimeout(updateFn, 300);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * 设置图片缩放到目标宽高
162
+ */
163
+ private _setImageScale(
164
+ imgEl: fabric.Image,
165
+ targetWidth: number,
166
+ targetHeight: number
167
+ ) {
168
+ const imgWidth = imgEl.width || 0;
169
+ const imgHeight = imgEl.height || 0;
170
+ if (imgWidth > 0 && imgHeight > 0) {
171
+ const scaleX = targetWidth / imgWidth;
172
+ const scaleY = targetHeight / imgHeight;
173
+ imgEl.set({
174
+ scaleX,
175
+ scaleY,
176
+ });
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 绑定二维码相关事件与方法
182
+ */
183
+ private _bindQrCodeEvents(imgEl: fabric.Image) {
184
+ (imgEl as any).setExtension = async (fields: Record<string, any>) => {
185
+ const currentExt = (imgEl.get('extension') as any) || {};
186
+ const merged = { ...currentExt, ...(fields || {}) };
187
+ imgEl.set('extension', merged);
188
+ await this._updateQrCodeImage(imgEl, true);
189
+ };
190
+ (imgEl as any).setExtensionByUnit = async (
191
+ fields: Record<string, any>,
192
+ dpi?: number
193
+ ) => {
194
+ const curUnit = getUnit(this.editor);
195
+ const { processed, originByUnit } = processOptions(fields || {}, curUnit, dpi);
196
+ const precision = (this.editor as any).getPrecision?.();
197
+ const formattedOrigin = formatOriginValues(originByUnit[curUnit] || {}, precision);
198
+ const originSize = (imgEl as any)._originSize || {};
199
+ const unitOrigin = originSize[curUnit] || {};
200
+ unitOrigin.extension = { ...(unitOrigin.extension || {}), ...formattedOrigin };
201
+ (imgEl as any)._originSize = { ...originSize, [curUnit]: unitOrigin };
202
+ const currentExt = (imgEl.get('extension') as any) || {};
203
+ const merged = { ...currentExt, ...processed };
204
+ imgEl.set('extension', merged);
205
+ await this._updateQrCodeImage(imgEl, true);
206
+ };
207
+ (imgEl as any).off?.('modified');
208
+ (imgEl as any).off?.('scaled');
209
+ imgEl.on('modified', async (event: any) => {
210
+ const target = (event?.target as fabric.Image) || imgEl;
211
+ await this._updateQrCodeImage(target, true);
212
+ });
213
+ imgEl.on('scaled', async () => {
214
+ await this._updateQrCodeImage(imgEl, true);
215
+ });
216
+
217
+ }
218
+
219
+ /**
220
+ * 创建二维码,支持传入内容与样式,进行单位转换并存储原始尺寸
221
+ */
222
+ async addQrCode(
223
+ data?: string,
224
+ opts?: {
225
+ left?: number;
226
+ top?: number;
227
+ width?: number;
228
+ height?: number;
229
+ ecLevel?: 'L' | 'M' | 'Q' | 'H';
230
+ color: string;
231
+ bgColor: string;
232
+ },
233
+ dpi?: number
234
+ ): Promise<fabric.Image> {
235
+ const option = {
236
+ ...this._defaultBarcodeOption(),
237
+ ...(opts || {}),
238
+ ...(data ? { value: data } : {}),
239
+ };
240
+ const unit = getUnit(this.editor);
241
+ const { processed, originByUnit } = processOptions(option, unit, dpi, ['left', 'top', 'width', 'height', 'margin']);
242
+ const finalOption = { ...option, ...processed };
243
+ const paramsOption = this._paramsToOption(finalOption);
244
+ const url = await this._getBase64Str(paramsOption);
245
+ return new Promise<fabric.Image>((resolve) => {
246
+ fabric.Image.fromURL(
247
+ url,
248
+ (imgEl) => {
249
+ const safeLeft = Number.isFinite(processed.left)
250
+ ? processed.left
251
+ : 0;
252
+ const safeTop = Number.isFinite(processed.top)
253
+ ? processed.top
254
+ : 0;
255
+ imgEl.set({
256
+ left: safeLeft,
257
+ top: safeTop,
258
+ extensionType: 'qrcode',
259
+ extension: finalOption,
260
+ imageSmoothing: false,
261
+ });
262
+
263
+ const targetWidth =
264
+ typeof finalOption.width === 'number'
265
+ ? finalOption.width
266
+ : imgEl.width ?? 0;
267
+ const targetHeight =
268
+ typeof finalOption.height === 'number'
269
+ ? finalOption.height
270
+ : targetWidth;
271
+ this._setImageScale(imgEl, targetWidth, targetHeight);
272
+
273
+ const origin = originByUnit[unit] || {};
274
+ const originMapped: Record<string, any> = { ...origin };
275
+ if (
276
+ originMapped.height === undefined &&
277
+ originMapped.width !== undefined
278
+ ) {
279
+ originMapped.height = originMapped.width;
280
+ }
281
+ (imgEl as any)._originSize = { [unit]: originMapped };
282
+ this._bindQrCodeEvents(imgEl);
283
+ resolve(imgEl);
284
+ },
285
+ { crossOrigin: 'anonymous' }
286
+ );
287
+ });
288
+ }
289
+
290
+ async setQrCode(option: any) {
291
+ try {
292
+ const paramsOption = this._paramsToOption(option);
293
+ const url = await this._getBase64Str(paramsOption);
294
+ const activeObject = this.canvas.getActiveObjects()[0];
295
+ fabric.Image.fromURL(
296
+ url,
297
+ (imgEl) => {
298
+ imgEl.set({
299
+ left: activeObject.left,
300
+ top: activeObject.top,
301
+ extensionType: 'qrcode',
302
+ extension: { ...option },
303
+ });
304
+ imgEl.scaleToWidth(activeObject.getScaledWidth());
305
+ this._bindQrCodeEvents(imgEl);
306
+ this.editor.del();
307
+ this.canvas.add(imgEl);
308
+ this.canvas.setActiveObject(imgEl);
309
+ },
310
+ { crossOrigin: 'anonymous' }
311
+ );
312
+ } catch (error) {
313
+ console.log(error);
314
+ }
315
+ }
316
+
317
+ destroy() {
318
+ console.log('pluginDestroy');
319
+ }
320
+ }
321
+
322
+ export default QrCodePlugin;