@hprint/core 0.0.11-alpha.1 → 0.0.11-alpha.4
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/dist/Editor.d.ts +2 -2
- package/dist/Editor.d.ts.map +1 -1
- package/dist/ServersPlugin.d.ts +23 -6
- package/dist/ServersPlugin.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -146
- package/dist/index.mjs +3799 -3675
- package/package.json +2 -2
- package/src/Editor.ts +1 -1
- package/src/ServersPlugin.ts +182 -6
- package/src/index.ts +11 -5
- package/src/objects/CustomRect.js +1 -1
- package/src/objects/CustomTextbox.js +70 -5
- package/src/utils/fabric-history.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hprint/core",
|
|
3
|
-
"version": "0.0.11-alpha.
|
|
3
|
+
"version": "0.0.11-alpha.4",
|
|
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.
|
|
36
|
+
"@hprint/shared": "0.0.11-alpha.4"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "vite build",
|
package/src/Editor.ts
CHANGED
|
@@ -3,7 +3,7 @@ import hotkeys from 'hotkeys-js';
|
|
|
3
3
|
import ContextMenu from './ContextMenu.js';
|
|
4
4
|
import ServersPlugin from './ServersPlugin';
|
|
5
5
|
import { AsyncSeriesHook } from 'tapable';
|
|
6
|
-
import { fabric } from '
|
|
6
|
+
import { fabric } from 'fabric';
|
|
7
7
|
import type {
|
|
8
8
|
IPluginMenu,
|
|
9
9
|
IPluginClass,
|
package/src/ServersPlugin.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { v4 as uuid } from 'uuid';
|
|
2
2
|
import { selectFiles, clipboardText, downFile } from './utils/utils';
|
|
3
|
-
import { fabric
|
|
3
|
+
import { fabric } 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.
|
|
322
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
@@ -2,10 +2,16 @@ import Editor from './Editor';
|
|
|
2
2
|
import Utils from './utils/utils';
|
|
3
3
|
import CustomRect from './objects/CustomRect';
|
|
4
4
|
import CustomTextbox from './objects/CustomTextbox';
|
|
5
|
-
import { fabric } from 'fabric';
|
|
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';
|
|
@@ -1,12 +1,77 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 文本框,修改两端对齐逻辑
|
|
3
3
|
*/
|
|
4
|
-
import { fabric } from 'fabric';
|
|
4
|
+
import { fabric } from 'fabric';
|
|
5
5
|
|
|
6
|
-
fabric.Textbox = fabric.util.createClass(fabric.Textbox, {
|
|
7
|
-
type: 'textbox',
|
|
8
|
-
|
|
9
|
-
|
|
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,
|