@hprint/plugins 0.0.1-alpha.0 → 0.0.1-alpha.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.
@@ -1,10 +1,10 @@
1
1
  import { fabric } from '@hprint/core';
2
- import QRCodeStyling from 'qr-code-styling';
2
+ import bwipjs from 'bwip-js';
3
3
  import { utils } from '@hprint/shared';
4
4
  import { getUnit, processOptions, formatOriginValues } from '../utils/units';
5
5
  import type { IEditor, IPluginTempl } from '@hprint/core';
6
6
 
7
- type IPlugin = Pick<QrCodePlugin, 'addQrCode' | 'setQrCode' | 'getQrCodeTypes'>;
7
+ type IPlugin = Pick<QrCodePlugin, 'addQrCode' | 'setQrCode'>;
8
8
 
9
9
  declare module '@hprint/core' {
10
10
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -45,30 +45,12 @@ class QrParamsDefaults {
45
45
  height = 300;
46
46
  type = 'canvas' as const;
47
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
- };
48
+ ecLevel = 'M' as const;
67
49
  }
68
50
 
69
51
  class QrCodePlugin implements IPluginTempl {
70
52
  static pluginName = 'QrCodePlugin';
71
- static apis = ['addQrCode', 'setQrCode', 'getQrCodeTypes'];
53
+ static apis = ['addQrCode', 'setQrCode'];
72
54
  constructor(
73
55
  public canvas: fabric.Canvas,
74
56
  public editor: IEditor
@@ -82,40 +64,50 @@ class QrCodePlugin implements IPluginTempl {
82
64
  }
83
65
  }
84
66
 
67
+ async hookTransformObjectEnd({ originObject, fabricObject }: { originObject: any, fabricObject: any }) {
68
+ if (originObject.extensionType === 'qrcode') {
69
+ this._bindQrCodeEvents(fabricObject);
70
+ }
71
+ }
72
+
85
73
  async _getBase64Str(options: any): Promise<string> {
86
74
  const zoom = this.canvas.getZoom() || 1;
87
75
  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
76
 
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 || '';
77
+ const targetWidth = (options.width || 300) * zoom * dpr;
78
+ // 估算 module 数量,QR Code 通常在 21-177 之间,取一个中间值作为估算基础
79
+ const estimatedModules = 35;
80
+ let bwipScale = Math.ceil(targetWidth / estimatedModules);
81
+ if (bwipScale < 2) bwipScale = 2;
82
+
83
+ const canvas = document.createElement('canvas');
84
+
85
+ const barColor = options.color?.replace('#', '') || '000000';
86
+ const bgColor = options.bgColor?.replace('#', '') || 'ffffff';
87
+ const ecLevel = options.ecLevel || 'M';
88
+
89
+ try {
90
+ bwipjs.toCanvas(canvas, {
91
+ bcid: 'qrcode',
92
+ text: options.data || ' ',
93
+ scale: bwipScale,
94
+ eclevel: ecLevel,
95
+ barcolor: barColor,
96
+ backgroundcolor: bgColor,
97
+ } as any);
98
+ return canvas.toDataURL('image/png');
99
+ } catch (error) {
100
+ console.error('QR Code generation failed:', error);
101
+ return '';
102
+ }
104
103
  }
105
104
 
106
105
  _defaultBarcodeOption() {
107
106
  return {
108
- value: 'https://kuaitu.cc',
107
+ value: '@hprint/print',
109
108
  width: 300,
110
109
  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',
110
+ ecLevel: 'M',
119
111
  };
120
112
  }
121
113
 
@@ -128,43 +120,15 @@ class QrCodePlugin implements IPluginTempl {
128
120
  const size = hasW && hasH
129
121
  ? Math.max(option.width, option.height)
130
122
  : (hasW ? option.width : (hasH ? option.height : undefined));
131
- const base = {
123
+ const options = {
124
+ ...option,
132
125
  width: size,
133
126
  height: size ?? option.width,
134
127
  type: 'canvas',
135
128
  data: option.value != null ? String(option.value) : undefined,
136
129
  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
130
  };
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;
131
+ return options;
168
132
  }
169
133
 
170
134
  private async _updateQrCodeImage(imgEl: fabric.Image, immediate = false) {
@@ -221,6 +185,44 @@ class QrCodePlugin implements IPluginTempl {
221
185
  }
222
186
  }
223
187
 
188
+ /**
189
+ * 绑定二维码相关事件与方法
190
+ */
191
+ private _bindQrCodeEvents(imgEl: fabric.Image) {
192
+ (imgEl as any).setExtension = async (fields: Record<string, any>) => {
193
+ const currentExt = (imgEl.get('extension') as any) || {};
194
+ const merged = { ...currentExt, ...(fields || {}) };
195
+ imgEl.set('extension', merged);
196
+ await this._updateQrCodeImage(imgEl, true);
197
+ };
198
+ (imgEl as any).setExtensionByUnit = async (
199
+ fields: Record<string, any>,
200
+ dpi?: number
201
+ ) => {
202
+ const curUnit = getUnit(this.editor);
203
+ const { processed, originByUnit } = processOptions(fields || {}, curUnit, dpi);
204
+ const precision = (this.editor as any).getPrecision?.();
205
+ const formattedOrigin = formatOriginValues(originByUnit[curUnit] || {}, precision);
206
+ const originSize = (imgEl as any)._originSize || {};
207
+ const unitOrigin = originSize[curUnit] || {};
208
+ unitOrigin.extension = { ...(unitOrigin.extension || {}), ...formattedOrigin };
209
+ (imgEl as any)._originSize = { ...originSize, [curUnit]: unitOrigin };
210
+ const currentExt = (imgEl.get('extension') as any) || {};
211
+ const merged = { ...currentExt, ...processed };
212
+ imgEl.set('extension', merged);
213
+ await this._updateQrCodeImage(imgEl, true);
214
+ };
215
+ (imgEl as any).off?.('modified');
216
+ (imgEl as any).off?.('scaled');
217
+ imgEl.on('modified', async (event: any) => {
218
+ const target = (event?.target as fabric.Image) || imgEl;
219
+ await this._updateQrCodeImage(target, true);
220
+ });
221
+ imgEl.on('scaled', async () => {
222
+ await this._updateQrCodeImage(imgEl, true);
223
+ });
224
+ }
225
+
224
226
  /**
225
227
  * 创建二维码,支持传入内容与样式,进行单位转换并存储原始尺寸
226
228
  */
@@ -231,15 +233,9 @@ class QrCodePlugin implements IPluginTempl {
231
233
  top?: number;
232
234
  width?: number;
233
235
  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;
236
+ ecLevel?: 'L' | 'M' | 'Q' | 'H';
237
+ color: string;
238
+ bgColor: string;
243
239
  },
244
240
  dpi?: number
245
241
  ): Promise<fabric.Image> {
@@ -250,19 +246,7 @@ class QrCodePlugin implements IPluginTempl {
250
246
  };
251
247
  const unit = getUnit(this.editor);
252
248
  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
- };
249
+ const finalOption = { ...option, ...processed };
266
250
  const paramsOption = this._paramsToOption(finalOption);
267
251
  const url = await this._getBase64Str(paramsOption);
268
252
  return new Promise<fabric.Image>((resolve) => {
@@ -302,38 +286,7 @@ class QrCodePlugin implements IPluginTempl {
302
286
  originMapped.height = originMapped.width;
303
287
  }
304
288
  (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
- });
289
+ this._bindQrCodeEvents(imgEl);
337
290
  resolve(imgEl);
338
291
  },
339
292
  { crossOrigin: 'anonymous' }
@@ -356,15 +309,7 @@ class QrCodePlugin implements IPluginTempl {
356
309
  extension: { ...option },
357
310
  });
358
311
  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
- });
312
+ this._bindQrCodeEvents(imgEl);
368
313
  this.editor.del();
369
314
  this.canvas.add(imgEl);
370
315
  this.canvas.setActiveObject(imgEl);
@@ -376,15 +321,6 @@ class QrCodePlugin implements IPluginTempl {
376
321
  }
377
322
  }
378
323
 
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
324
  destroy() {
389
325
  console.log('pluginDestroy');
390
326
  }
@@ -244,7 +244,11 @@ class ResizePlugin implements IPluginTempl {
244
244
  if (this.editor.getUnit() !== 'px') {
245
245
  ({ width: curUnitWidth, height: curUnitHeight } = this.editor.getOriginSize());
246
246
  }
247
- this.editor.emit('sizeChange', { width: curUnitWidth, height: curUnitHeight });
247
+ this.editor.emit('sizeChange', {
248
+ width: this.editor.getSizeByUnit(curUnitWidth),
249
+ height: this.editor.getSizeByUnit(curUnitHeight),
250
+ unit: this.editor.getUnit()
251
+ });
248
252
  }
249
253
  }
250
254
 
@@ -218,13 +218,13 @@ class UnitPlugin implements IPluginTempl {
218
218
  setSizeByUnit(width: number, height: number, options: { dpi: number, slient?: boolean }) {
219
219
  const unit = (this.editor as any).getUnit?.() || 'px';
220
220
  if (unit === 'mm') {
221
- return this.setSizeMm(width, height, options.dpi);
221
+ return this.setSizeMm(width, height, options?.dpi);
222
222
  }
223
223
  if (unit === 'inch') {
224
224
  this._syncOriginSize(width, height);
225
225
  const wmm = width * LengthConvert.CONSTANTS.INCH_TO_MM;
226
226
  const hmm = height * LengthConvert.CONSTANTS.INCH_TO_MM;
227
- return this.setSizeMm(wmm, hmm, options.dpi);
227
+ return this.setSizeMm(wmm, hmm, options?.dpi);
228
228
  }
229
229
  this.editor.setSize(width, height, { slient: options.slient });
230
230
  }
@@ -240,7 +240,6 @@ class UnitPlugin implements IPluginTempl {
240
240
  height: this.canvas.getHeight(),
241
241
  };
242
242
  }
243
-
244
243
  const ensureMmWidth = originMm.width !== undefined
245
244
  ? originMm.width
246
245
  : LengthConvert.pxToMm(this.canvas.getWidth(), dpi, { direct: true });
@@ -91,9 +91,11 @@ class WorkspacePlugin implements IPluginTempl {
91
91
  if (workspace.width && workspace.height) {
92
92
  this.setSize(workspace.width, workspace.height);
93
93
  this.editor.emit(
94
- 'sizeChange',
95
- workspace.width,
96
- workspace.height
94
+ 'sizeChange', {
95
+ width: this.editor.getSizeByUnit(workspace.width),
96
+ height: this.editor.getSizeByUnit(workspace.height),
97
+ unit: this.editor.getUnit(),
98
+ }
97
99
  );
98
100
  }
99
101
  }
@@ -194,9 +196,11 @@ class WorkspacePlugin implements IPluginTempl {
194
196
  this.workspace.set('width', width);
195
197
  this.workspace.set('height', height);
196
198
  options?.slient !== true && this.editor.emit(
197
- 'sizeChange',
198
- this.workspace.width,
199
- this.workspace.height
199
+ 'sizeChange', {
200
+ width: this.editor.getSizeByUnit(width),
201
+ height: this.editor.getSizeByUnit(height),
202
+ unit: this.editor.getUnit(),
203
+ }
200
204
  );
201
205
  this.auto();
202
206
  }
@@ -45,12 +45,14 @@ export function applyMmToObject(
45
45
  }
46
46
 
47
47
  export function syncMmFromObject(obj: fabric.Object, dpi?: number, precision?: number) {
48
+ const isImage = obj.type === 'image';
48
49
  const toMm = (v: number | undefined) =>
49
50
  v === undefined ? undefined : applyPrecision(LengthConvert.pxToMm(v, dpi), precision);
50
51
  const left = obj.left as number | undefined;
51
52
  const top = obj.top as number | undefined;
52
- const width = obj.width as number | undefined;
53
- const height = obj.height as number | undefined;
53
+ // 图片的width是图片的实际宽度,应取在画布中绘制用的宽度obj.getScaledWidth()
54
+ const width = isImage ? obj.getScaledWidth() : obj.width as number | undefined;
55
+ const height = isImage ? obj.getScaledHeight() : obj.height as number | undefined;
54
56
  const strokeWidth = obj.strokeWidth as number | undefined;
55
57
  const fontSize = (obj as any).fontSize as number | undefined;
56
58