@hprint/core 0.0.11-alpha.1 → 0.0.11-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hprint/core",
3
- "version": "0.0.11-alpha.1",
3
+ "version": "0.0.11-alpha.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -33,7 +33,7 @@
33
33
  "hotkeys-js": "~3.8.9",
34
34
  "tapable": "~2.3.0",
35
35
  "uuid": "~8.3.2",
36
- "@hprint/shared": "0.0.11-alpha.1"
36
+ "@hprint/shared": "0.0.11-alpha.3"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "vite build",
@@ -4,6 +4,24 @@ import { fabric, StaticCanvas } from 'fabric';
4
4
  import type { IEditor, IPluginTempl } from '@hprint/core';
5
5
  import { SelectEvent, SelectMode } from '../../plugins/src/types/eventType';
6
6
 
7
+ export type PrintRotation = 0 | 90 | 180 | 270;
8
+
9
+ export interface PrintExportOptions {
10
+ rotation?: PrintRotation | number;
11
+ }
12
+
13
+ export interface PrintSVGExportOptions extends PrintExportOptions {
14
+ width?: string;
15
+ height?: string;
16
+ }
17
+
18
+ export interface PrintExportResult {
19
+ content: string;
20
+ width: number;
21
+ height: number;
22
+ rotation: PrintRotation;
23
+ }
24
+
7
25
  type IPlugin = Pick<
8
26
  ServersPlugin,
9
27
  | 'insert'
@@ -15,7 +33,9 @@ type IPlugin = Pick<
15
33
  | 'saveJson'
16
34
  | 'saveSvg'
17
35
  | 'getBase64'
36
+ | 'getBase64Result'
18
37
  | 'getSVG'
38
+ | 'getSVGResult'
19
39
  | 'saveImg'
20
40
  | 'clear'
21
41
  | 'preview'
@@ -54,7 +74,9 @@ class ServersPlugin implements IPluginTempl {
54
74
  'saveSvg',
55
75
  'saveImg',
56
76
  'getBase64',
77
+ 'getBase64Result',
57
78
  'getSVG',
79
+ 'getSVGResult',
58
80
  'clear',
59
81
  'preview',
60
82
  'staticPreview',
@@ -312,20 +334,42 @@ class ServersPlugin implements IPluginTempl {
312
334
  });
313
335
  }
314
336
 
315
- getBase64() {
337
+ getBase64(options?: PrintExportOptions) {
316
338
  return new Promise<string>((resolve) => {
317
339
  this.editor.hooksEntity.hookSaveBefore.callAsync('', () => {
318
340
  const option = this._getSaveOption();
319
341
  this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
320
342
  const dataUrl = this.canvas.toDataURL(option);
321
- this.editor.hooksEntity.hookSaveAfter.callAsync(dataUrl, () =>
322
- resolve(dataUrl)
343
+ void this.rotateBase64(dataUrl, options?.rotation).then(
344
+ (content) => {
345
+ this.editor.hooksEntity.hookSaveAfter.callAsync(
346
+ content,
347
+ () => resolve(content)
348
+ );
349
+ },
350
+ () => {
351
+ this.editor.hooksEntity.hookSaveAfter.callAsync(
352
+ dataUrl,
353
+ () => resolve(dataUrl)
354
+ );
355
+ }
323
356
  );
324
357
  });
325
358
  });
326
359
  }
327
360
 
328
- getSVG(options?: { width?: string; height?: string }) {
361
+ async getBase64Result(
362
+ options?: PrintExportOptions
363
+ ): Promise<PrintExportResult> {
364
+ const rotation = this.normalizePrintRotation(options?.rotation);
365
+ return {
366
+ content: await this.getBase64({ rotation }),
367
+ ...this.getPrintExportSize(rotation),
368
+ rotation,
369
+ };
370
+ }
371
+
372
+ getSVG(options?: PrintSVGExportOptions) {
329
373
  return new Promise<string>((resolve) => {
330
374
  this.editor.hooksEntity.hookSaveBefore.callAsync('', () => {
331
375
  this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
@@ -337,7 +381,10 @@ class ServersPlugin implements IPluginTempl {
337
381
  fabric.fontPaths = {
338
382
  ...fontOption,
339
383
  };
340
- const svg = this.canvas.toSVG(svgOption);
384
+ const svg = this.rotateSVG(
385
+ this.canvas.toSVG(svgOption),
386
+ options?.rotation
387
+ );
341
388
  // this._printSvgString(svg);
342
389
  this.editor.hooksEntity.hookSaveAfter.callAsync(svg, () =>
343
390
  resolve(svg)
@@ -346,6 +393,17 @@ class ServersPlugin implements IPluginTempl {
346
393
  });
347
394
  }
348
395
 
396
+ async getSVGResult(
397
+ options?: PrintSVGExportOptions
398
+ ): Promise<PrintExportResult> {
399
+ const rotation = this.normalizePrintRotation(options?.rotation);
400
+ return {
401
+ content: await this.getSVG({ ...options, rotation }),
402
+ ...this.getPrintExportSize(rotation),
403
+ rotation,
404
+ };
405
+ }
406
+
349
407
  preview() {
350
408
  return new Promise<string>((resolve) => {
351
409
  this.editor.hooksEntity.hookSaveBefore.callAsync('', () => {
@@ -423,6 +481,124 @@ class ServersPlugin implements IPluginTempl {
423
481
  return option;
424
482
  }
425
483
 
484
+ private normalizePrintRotation(rotation?: number): PrintRotation {
485
+ return [0, 90, 180, 270].includes(Number(rotation))
486
+ ? (Number(rotation) as PrintRotation)
487
+ : 0;
488
+ }
489
+
490
+ private getPrintExportSize(rotation: PrintRotation) {
491
+ const workspace = this.canvas
492
+ .getObjects()
493
+ .find((item: fabric.Object) => item.id === 'workspace') as
494
+ | fabric.Object
495
+ | undefined;
496
+ const getSizeByUnit = (this.editor as any).getSizeByUnit;
497
+ const width = workspace
498
+ ? getSizeByUnit
499
+ ? getSizeByUnit.call(this.editor, workspace.width || 0)
500
+ : workspace.width || 0
501
+ : 0;
502
+ const height = workspace
503
+ ? getSizeByUnit
504
+ ? getSizeByUnit.call(this.editor, workspace.height || 0)
505
+ : workspace.height || 0
506
+ : 0;
507
+ return rotation === 90 || rotation === 270
508
+ ? { width: height, height: width }
509
+ : { width, height };
510
+ }
511
+
512
+ private rotateBase64(dataUrl: string, rotation?: number) {
513
+ const normalizedRotation = this.normalizePrintRotation(rotation);
514
+ if (!normalizedRotation || typeof Image === 'undefined') {
515
+ return Promise.resolve(dataUrl);
516
+ }
517
+ return new Promise<string>((resolve, reject) => {
518
+ const image = new Image();
519
+ image.onload = () => {
520
+ const canvas = document.createElement('canvas');
521
+ const swapSize =
522
+ normalizedRotation === 90 || normalizedRotation === 270;
523
+ canvas.width = swapSize ? image.height : image.width;
524
+ canvas.height = swapSize ? image.width : image.height;
525
+ const context = canvas.getContext('2d');
526
+ if (!context) {
527
+ reject(new Error('无法创建旋转画布'));
528
+ return;
529
+ }
530
+ if (normalizedRotation === 90) {
531
+ context.translate(canvas.width, 0);
532
+ } else if (normalizedRotation === 180) {
533
+ context.translate(canvas.width, canvas.height);
534
+ } else if (normalizedRotation === 270) {
535
+ context.translate(0, canvas.height);
536
+ }
537
+ context.rotate((normalizedRotation * Math.PI) / 180);
538
+ context.drawImage(image, 0, 0);
539
+ const mimeType = /^data:([^;,]+)/i.exec(dataUrl)?.[1];
540
+ resolve(canvas.toDataURL(mimeType || 'image/jpeg', 1));
541
+ };
542
+ image.onerror = () => reject(new Error('无法读取导出图片'));
543
+ image.src = dataUrl;
544
+ });
545
+ }
546
+
547
+ private rotateSVG(svg: string, rotation?: number) {
548
+ const normalizedRotation = this.normalizePrintRotation(rotation);
549
+ if (!normalizedRotation || typeof DOMParser === 'undefined') return svg;
550
+ const document = new DOMParser().parseFromString(svg, 'image/svg+xml');
551
+ const root = document.documentElement;
552
+ if (!root || root.nodeName === 'parsererror') return svg;
553
+ const values = String(root.getAttribute('viewBox') || '')
554
+ .trim()
555
+ .split(/[\s,]+/)
556
+ .map(Number);
557
+ if (values.length !== 4 || values.some((value) => !Number.isFinite(value))) {
558
+ return svg;
559
+ }
560
+ const [x, y, width, height] = values;
561
+ const originalWidth = root.getAttribute('width');
562
+ const originalHeight = root.getAttribute('height');
563
+ const transforms: Record<Exclude<PrintRotation, 0>, string> = {
564
+ 90: `translate(${height} 0) rotate(90) translate(${-x} ${-y})`,
565
+ 180: `translate(${width} ${height}) rotate(180) translate(${-x} ${-y})`,
566
+ 270: `translate(0 ${width}) rotate(270) translate(${-x} ${-y})`,
567
+ };
568
+ const transform = transforms[
569
+ normalizedRotation as Exclude<PrintRotation, 0>
570
+ ];
571
+ const content = document.createElementNS(
572
+ 'http://www.w3.org/2000/svg',
573
+ 'g'
574
+ );
575
+ content.setAttribute(
576
+ 'transform',
577
+ transform
578
+ );
579
+
580
+ Array.from(root.childNodes).forEach((node) => {
581
+ if (
582
+ node.nodeType === 1 &&
583
+ ['defs', 'style', 'desc', 'title', 'metadata'].includes(
584
+ (node as Element).localName
585
+ )
586
+ ) {
587
+ return;
588
+ }
589
+ content.appendChild(node);
590
+ });
591
+ root.appendChild(content);
592
+ if (normalizedRotation === 90 || normalizedRotation === 270) {
593
+ root.setAttribute('viewBox', `0 0 ${height} ${width}`);
594
+ if (originalHeight) root.setAttribute('width', originalHeight);
595
+ if (originalWidth) root.setAttribute('height', originalWidth);
596
+ } else {
597
+ root.setAttribute('viewBox', `0 0 ${width} ${height}`);
598
+ }
599
+ return new XMLSerializer().serializeToString(root);
600
+ }
601
+
426
602
  clear() {
427
603
  this.canvas.getObjects().forEach((obj) => {
428
604
  if (obj.id !== 'workspace') {
package/src/index.ts CHANGED
@@ -5,7 +5,13 @@ import CustomTextbox from './objects/CustomTextbox';
5
5
  import { fabric } from 'fabric';
6
6
  import type { Canvas, Point, IEvent } from 'fabric/fabric-impl';
7
7
 
8
- export { Utils, CustomRect, CustomTextbox, fabric, Canvas, Point, IEvent };
9
- export default Editor;
10
-
11
- export * from './interface/Editor';
8
+ export { Utils, CustomRect, CustomTextbox, fabric, Canvas, Point, IEvent };
9
+ export default Editor;
10
+
11
+ export * from './interface/Editor';
12
+ export type {
13
+ PrintExportOptions,
14
+ PrintExportResult,
15
+ PrintRotation,
16
+ PrintSVGExportOptions,
17
+ } from './ServersPlugin';
@@ -3,10 +3,75 @@
3
3
  */
4
4
  import { fabric } from 'fabric';
5
5
 
6
- fabric.Textbox = fabric.util.createClass(fabric.Textbox, {
7
- type: 'textbox',
8
-
9
- _renderChars: function (method, ctx, line, left, top, lineIndex) {
6
+ fabric.Textbox = fabric.util.createClass(fabric.Textbox, {
7
+ type: 'textbox',
8
+
9
+ _getLineHeightPadding: function () {
10
+ if (!this.lineHeightPadding || !this._textLines.length) return 0;
11
+ var lineHeight = this.getHeightOfLine(0);
12
+ return (lineHeight - lineHeight / this.lineHeight) / 2;
13
+ },
14
+
15
+ calcTextHeight: function () {
16
+ if (!this.lineHeightPadding) {
17
+ return this.callSuper('calcTextHeight');
18
+ }
19
+ var height = 0;
20
+ for (var i = 0, len = this._textLines.length; i < len; i++) {
21
+ height += this.getHeightOfLine(i);
22
+ }
23
+ return height;
24
+ },
25
+
26
+ _renderTextCommon: function (ctx, method) {
27
+ if (!this.lineHeightPadding) {
28
+ return this.callSuper('_renderTextCommon', ctx, method);
29
+ }
30
+ ctx.save();
31
+ var lineHeights = 0,
32
+ left = this._getLeftOffset(),
33
+ top = this._getTopOffset() + this._getLineHeightPadding();
34
+ for (var i = 0, len = this._textLines.length; i < len; i++) {
35
+ var heightOfLine = this.getHeightOfLine(i),
36
+ maxHeight = heightOfLine / this.lineHeight,
37
+ leftOffset = this._getLineLeftOffset(i);
38
+ this._renderTextLine(
39
+ method,
40
+ ctx,
41
+ this._textLines[i],
42
+ left + leftOffset,
43
+ top + lineHeights + maxHeight,
44
+ i
45
+ );
46
+ lineHeights += heightOfLine;
47
+ }
48
+ ctx.restore();
49
+ },
50
+
51
+ _getSVGLeftTopOffsets: function () {
52
+ var offsets = this.callSuper('_getSVGLeftTopOffsets');
53
+ if (this.lineHeightPadding) {
54
+ offsets.textTop += this._getLineHeightPadding();
55
+ }
56
+ return offsets;
57
+ },
58
+
59
+ _getCursorBoundaries: function (position) {
60
+ var boundaries = this.callSuper('_getCursorBoundaries', position);
61
+ if (this.lineHeightPadding) {
62
+ boundaries.top += this._getLineHeightPadding();
63
+ }
64
+ return boundaries;
65
+ },
66
+
67
+ toObject: function (propertiesToInclude) {
68
+ return this.callSuper('toObject', [
69
+ ...(propertiesToInclude || []),
70
+ 'lineHeightPadding',
71
+ ]);
72
+ },
73
+
74
+ _renderChars: function (method, ctx, line, left, top, lineIndex) {
10
75
  // set proper line offset
11
76
  var lineHeight = this.getHeightOfLine(lineIndex),
12
77
  isJustify = this.textAlign.indexOf('justify') !== -1,