js-draw 0.3.1 → 0.4.0
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/.github/ISSUE_TEMPLATE/translation.md +4 -1
- package/CHANGELOG.md +16 -0
- package/README.md +1 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +15 -1
- package/dist/src/Editor.js +221 -78
- package/dist/src/EditorImage.js +4 -1
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +8 -3
- package/dist/src/SVGLoader.d.ts +4 -1
- package/dist/src/SVGLoader.js +78 -33
- package/dist/src/UndoRedoHistory.d.ts +1 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/Viewport.d.ts +2 -0
- package/dist/src/Viewport.js +26 -5
- package/dist/src/commands/lib.d.ts +2 -1
- package/dist/src/commands/lib.js +2 -1
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/commands/uniteCommands.d.ts +4 -0
- package/dist/src/commands/uniteCommands.js +105 -0
- package/dist/src/components/AbstractComponent.d.ts +2 -0
- package/dist/src/components/AbstractComponent.js +41 -5
- package/dist/src/components/ImageComponent.d.ts +27 -0
- package/dist/src/components/ImageComponent.js +129 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
- package/dist/src/components/lib.d.ts +4 -2
- package/dist/src/components/lib.js +4 -2
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/language/assertions.d.ts +1 -0
- package/dist/src/language/assertions.js +5 -0
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +3 -0
- package/dist/src/math/Mat33.d.ts +38 -2
- package/dist/src/math/Mat33.js +30 -1
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Path.js +10 -8
- package/dist/src/math/Vec3.d.ts +11 -1
- package/dist/src/math/Vec3.js +15 -0
- package/dist/src/math/rounding.d.ts +1 -0
- package/dist/src/math/rounding.js +13 -6
- package/dist/src/rendering/localization.d.ts +3 -0
- package/dist/src/rendering/localization.js +3 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
- package/dist/src/toolbar/HTMLToolbar.js +5 -4
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/tools/BaseTool.d.ts +3 -1
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/PasteHandler.d.ts +16 -0
- package/dist/src/tools/PasteHandler.js +144 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
- package/dist/src/tools/SelectionTool/Selection.js +337 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +276 -0
- package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
- package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
- package/dist/src/tools/SelectionTool/types.d.ts +9 -0
- package/dist/src/tools/SelectionTool/types.js +11 -0
- package/dist/src/tools/ToolController.js +37 -28
- package/dist/src/tools/lib.d.ts +2 -1
- package/dist/src/tools/lib.js +2 -1
- package/dist/src/tools/localization.d.ts +3 -0
- package/dist/src/tools/localization.js +3 -0
- package/dist/src/types.d.ts +14 -3
- package/dist/src/types.js +2 -0
- package/package.json +1 -1
- package/src/Editor.css +1 -0
- package/src/Editor.ts +275 -109
- package/src/EditorImage.ts +7 -1
- package/src/Pointer.ts +8 -3
- package/src/SVGLoader.ts +90 -36
- package/src/UndoRedoHistory.test.ts +33 -0
- package/src/UndoRedoHistory.ts +8 -0
- package/src/Viewport.ts +30 -6
- package/src/commands/lib.ts +2 -0
- package/src/commands/localization.ts +2 -0
- package/src/commands/uniteCommands.test.ts +23 -0
- package/src/commands/uniteCommands.ts +121 -0
- package/src/components/AbstractComponent.ts +53 -11
- package/src/components/ImageComponent.ts +149 -0
- package/src/components/Text.ts +2 -6
- package/src/components/builders/FreehandLineBuilder.ts +2 -2
- package/src/components/lib.ts +7 -2
- package/src/components/localization.ts +4 -0
- package/src/language/assertions.ts +6 -0
- package/src/math/LineSegment2.test.ts +9 -0
- package/src/math/LineSegment2.ts +5 -0
- package/src/math/Mat33.test.ts +14 -0
- package/src/math/Mat33.ts +43 -2
- package/src/math/Path.toString.test.ts +12 -1
- package/src/math/Path.ts +11 -9
- package/src/math/Vec3.ts +22 -1
- package/src/math/rounding.test.ts +30 -5
- package/src/math/rounding.ts +16 -7
- package/src/rendering/localization.ts +6 -0
- package/src/rendering/renderers/AbstractRenderer.ts +19 -2
- package/src/rendering/renderers/CanvasRenderer.ts +10 -1
- package/src/rendering/renderers/DummyRenderer.ts +6 -1
- package/src/rendering/renderers/SVGRenderer.ts +50 -21
- package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
- package/src/toolbar/HTMLToolbar.ts +5 -4
- package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
- package/src/tools/BaseTool.ts +9 -1
- package/src/tools/PasteHandler.ts +159 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +455 -0
- package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
- package/src/tools/SelectionTool/SelectionTool.css +22 -0
- package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
- package/src/tools/SelectionTool/SelectionTool.ts +335 -0
- package/src/tools/SelectionTool/TransformMode.ts +114 -0
- package/src/tools/SelectionTool/types.ts +11 -0
- package/src/tools/ToolController.ts +52 -45
- package/src/tools/lib.ts +2 -1
- package/src/tools/localization.ts +8 -0
- package/src/types.ts +17 -3
- package/dist/src/tools/SelectionTool.d.ts +0 -59
- package/dist/src/tools/SelectionTool.js +0 -589
- package/src/tools/SelectionTool.ts +0 -725
@@ -7,7 +7,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
7
7
|
import Vec3 from '../../math/Vec3';
|
8
8
|
import Viewport from '../../Viewport';
|
9
9
|
import RenderingStyle from '../RenderingStyle';
|
10
|
-
import AbstractRenderer from './AbstractRenderer';
|
10
|
+
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
11
11
|
|
12
12
|
export default class DummyRenderer extends AbstractRenderer {
|
13
13
|
// Variables that track the state of what's been rendered
|
@@ -17,6 +17,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
17
17
|
public lastPoint: Point2|null = null;
|
18
18
|
public objectNestingLevel: number = 0;
|
19
19
|
public lastText: string|null = null;
|
20
|
+
public lastImage: RenderableImage|null = null;
|
20
21
|
|
21
22
|
// List of points drawn since the last clear.
|
22
23
|
public pointBuffer: Point2[] = [];
|
@@ -44,6 +45,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
44
45
|
this.renderedPathCount = 0;
|
45
46
|
this.pointBuffer = [];
|
46
47
|
this.lastText = null;
|
48
|
+
this.lastImage = null;
|
47
49
|
|
48
50
|
// Ensure all objects finished rendering
|
49
51
|
if (this.objectNestingLevel > 0) {
|
@@ -96,6 +98,9 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
96
98
|
public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
|
97
99
|
this.lastText = text;
|
98
100
|
}
|
101
|
+
public drawImage(image: RenderableImage) {
|
102
|
+
this.lastImage = image;
|
103
|
+
}
|
99
104
|
|
100
105
|
public startObject(boundingBox: Rect2, _clip: boolean) {
|
101
106
|
super.startObject(boundingBox);
|
@@ -9,7 +9,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
9
9
|
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
|
10
10
|
import Viewport from '../../Viewport';
|
11
11
|
import RenderingStyle from '../RenderingStyle';
|
12
|
-
import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
|
12
|
+
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
13
13
|
|
14
14
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
15
15
|
export default class SVGRenderer extends AbstractRenderer {
|
@@ -19,13 +19,18 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
19
19
|
|
20
20
|
private overwrittenAttrs: Record<string, string|null> = {};
|
21
21
|
|
22
|
-
|
22
|
+
// Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
|
23
|
+
public constructor(private elem: SVGSVGElement, viewport: Viewport, private sanitize: boolean = false) {
|
23
24
|
super(viewport);
|
24
25
|
this.clear();
|
25
26
|
}
|
26
27
|
|
27
28
|
// Sets an attribute on the root SVG element.
|
28
29
|
public setRootSVGAttribute(name: string, value: string|null) {
|
30
|
+
if (this.sanitize) {
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
|
29
34
|
// Make the original value of the attribute restorable on clear
|
30
35
|
if (!(name in this.overwrittenAttrs)) {
|
31
36
|
this.overwrittenAttrs[name] = this.elem.getAttribute(name);
|
@@ -43,18 +48,21 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
43
48
|
}
|
44
49
|
|
45
50
|
public clear() {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
this.
|
52
|
-
|
53
|
-
|
51
|
+
this.lastPathString = [];
|
52
|
+
|
53
|
+
if (!this.sanitize) {
|
54
|
+
// Restore all all attributes
|
55
|
+
for (const attrName in this.overwrittenAttrs) {
|
56
|
+
const value = this.overwrittenAttrs[attrName];
|
57
|
+
|
58
|
+
if (value) {
|
59
|
+
this.elem.setAttribute(attrName, value);
|
60
|
+
} else {
|
61
|
+
this.elem.removeAttribute(attrName);
|
62
|
+
}
|
54
63
|
}
|
64
|
+
this.overwrittenAttrs = {};
|
55
65
|
}
|
56
|
-
this.overwrittenAttrs = {};
|
57
|
-
this.lastPathString = [];
|
58
66
|
}
|
59
67
|
|
60
68
|
// Push [this.fullPath] to the SVG
|
@@ -91,26 +99,31 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
91
99
|
this.lastPathString.push(path.toString());
|
92
100
|
}
|
93
101
|
|
94
|
-
|
95
|
-
|
96
|
-
|
102
|
+
// Apply [elemTransform] to [elem].
|
103
|
+
private transformFrom(elemTransform: Mat33, elem: SVGElement) {
|
104
|
+
let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
|
97
105
|
const translation = transform.transformVec2(Vec2.zero);
|
98
106
|
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
99
107
|
|
100
|
-
|
101
|
-
textElem.appendChild(document.createTextNode(text));
|
102
|
-
textElem.style.transform = `matrix(
|
108
|
+
elem.style.transform = `matrix(
|
103
109
|
${transform.a1}, ${transform.b1},
|
104
110
|
${transform.a2}, ${transform.b2},
|
105
111
|
${transform.a3}, ${transform.b3}
|
106
112
|
)`;
|
113
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
114
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
115
|
+
}
|
116
|
+
|
117
|
+
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
118
|
+
const textElem = document.createElementNS(svgNameSpace, 'text');
|
119
|
+
textElem.appendChild(document.createTextNode(text));
|
120
|
+
this.transformFrom(transform, textElem);
|
121
|
+
|
107
122
|
textElem.style.fontFamily = style.fontFamily;
|
108
123
|
textElem.style.fontVariant = style.fontVariant ?? '';
|
109
124
|
textElem.style.fontWeight = style.fontWeight ?? '';
|
110
125
|
textElem.style.fontSize = style.size + 'px';
|
111
126
|
textElem.style.fill = style.renderingStyle.fill.toHexString();
|
112
|
-
textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
113
|
-
textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
114
127
|
|
115
128
|
if (style.renderingStyle.stroke) {
|
116
129
|
const strokeStyle = style.renderingStyle.stroke;
|
@@ -122,6 +135,18 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
122
135
|
this.objectElems?.push(textElem);
|
123
136
|
}
|
124
137
|
|
138
|
+
public drawImage(image: RenderableImage) {
|
139
|
+
const svgImgElem = document.createElementNS(svgNameSpace, 'image');
|
140
|
+
svgImgElem.setAttribute('href', image.base64Url);
|
141
|
+
svgImgElem.setAttribute('width', image.image.getAttribute('width') ?? '');
|
142
|
+
svgImgElem.setAttribute('height', image.image.getAttribute('height') ?? '');
|
143
|
+
svgImgElem.setAttribute('aria-label', image.image.getAttribute('aria-label') ?? image.image.getAttribute('alt') ?? '');
|
144
|
+
this.transformFrom(image.transform, svgImgElem);
|
145
|
+
|
146
|
+
this.elem.appendChild(svgImgElem);
|
147
|
+
this.objectElems?.push(svgImgElem);
|
148
|
+
}
|
149
|
+
|
125
150
|
public startObject(boundingBox: Rect2) {
|
126
151
|
super.startObject(boundingBox);
|
127
152
|
|
@@ -137,7 +162,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
137
162
|
// Don't extend paths across objects
|
138
163
|
this.addPathToSVG();
|
139
164
|
|
140
|
-
if (loaderData) {
|
165
|
+
if (loaderData && !this.sanitize) {
|
141
166
|
// Restore any attributes unsupported by the app.
|
142
167
|
for (const elem of this.objectElems ?? []) {
|
143
168
|
const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
|
@@ -181,6 +206,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
181
206
|
|
182
207
|
// Renders a **copy** of the given element.
|
183
208
|
public drawSVGElem(elem: SVGElement) {
|
209
|
+
if (this.sanitize) {
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
|
184
213
|
this.elem.appendChild(elem.cloneNode(true));
|
185
214
|
}
|
186
215
|
|
@@ -6,7 +6,7 @@ import Vec3 from '../../math/Vec3';
|
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import { TextRendererLocalization } from '../localization';
|
8
8
|
import RenderingStyle from '../RenderingStyle';
|
9
|
-
import AbstractRenderer from './AbstractRenderer';
|
9
|
+
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
10
10
|
|
11
11
|
// Outputs a description of what was rendered.
|
12
12
|
|
@@ -14,6 +14,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
14
14
|
private descriptionBuilder: string[] = [];
|
15
15
|
private pathCount: number = 0;
|
16
16
|
private textNodeCount: number = 0;
|
17
|
+
private imageNodeCount: number = 0;
|
17
18
|
|
18
19
|
public constructor(viewport: Viewport, private localizationTable: TextRendererLocalization) {
|
19
20
|
super(viewport);
|
@@ -33,7 +34,8 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
33
34
|
public getDescription(): string {
|
34
35
|
return [
|
35
36
|
this.localizationTable.pathNodeCount(this.pathCount),
|
36
|
-
this.localizationTable.textNodeCount(this.textNodeCount),
|
37
|
+
...(this.textNodeCount > 0 ? this.localizationTable.textNodeCount(this.textNodeCount) : []),
|
38
|
+
...(this.imageNodeCount > 0 ? this.localizationTable.imageNodeCount(this.imageNodeCount) : []),
|
37
39
|
...this.descriptionBuilder
|
38
40
|
].join('\n');
|
39
41
|
}
|
@@ -55,6 +57,12 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
55
57
|
this.descriptionBuilder.push(this.localizationTable.textNode(text));
|
56
58
|
this.textNodeCount ++;
|
57
59
|
}
|
60
|
+
public drawImage(image: RenderableImage) {
|
61
|
+
const label = image.label ? this.localizationTable.imageNode(image.label) : this.localizationTable.unlabeledImageNode;
|
62
|
+
|
63
|
+
this.descriptionBuilder.push(label);
|
64
|
+
this.imageNodeCount ++;
|
65
|
+
}
|
58
66
|
public isTooSmallToRender(rect: Rect2): boolean {
|
59
67
|
return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
|
60
68
|
}
|
@@ -3,19 +3,20 @@ import { EditorEventType } from '../types';
|
|
3
3
|
|
4
4
|
import { coloris, init as colorisInit } from '@melloware/coloris';
|
5
5
|
import Color4 from '../Color4';
|
6
|
-
import SelectionTool from '../tools/SelectionTool';
|
7
6
|
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
8
7
|
import { ActionButtonIcon } from './types';
|
9
8
|
import { makeRedoIcon, makeUndoIcon } from './icons';
|
10
|
-
import
|
9
|
+
import SelectionTool from '../tools/SelectionTool/SelectionTool';
|
10
|
+
import PanZoomTool from '../tools/PanZoom';
|
11
11
|
import TextTool from '../tools/TextTool';
|
12
|
+
import EraserTool from '../tools/Eraser';
|
13
|
+
import PenTool from '../tools/Pen';
|
12
14
|
import PenToolWidget from './widgets/PenToolWidget';
|
13
15
|
import EraserWidget from './widgets/EraserToolWidget';
|
14
16
|
import SelectionToolWidget from './widgets/SelectionToolWidget';
|
15
17
|
import TextToolWidget from './widgets/TextToolWidget';
|
16
18
|
import HandToolWidget from './widgets/HandToolWidget';
|
17
19
|
import BaseWidget from './widgets/BaseWidget';
|
18
|
-
import { EraserTool, PenTool } from '../tools/lib';
|
19
20
|
|
20
21
|
export const toolbarCSSPrefix = 'toolbar-';
|
21
22
|
|
@@ -200,7 +201,7 @@ export default class HTMLToolbar {
|
|
200
201
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
201
202
|
}
|
202
203
|
|
203
|
-
const panZoomTool = toolController.getMatchingTools(
|
204
|
+
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
204
205
|
if (panZoomTool) {
|
205
206
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
206
207
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
-
import SelectionTool from '../../tools/SelectionTool';
|
2
|
+
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
|
3
3
|
import { EditorEventType } from '../../types';
|
4
4
|
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
package/src/tools/BaseTool.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent } from '../types';
|
1
|
+
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent, PasteEvent, CopyEvent } from '../types';
|
2
2
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
3
3
|
|
4
4
|
export default abstract class BaseTool implements PointerEvtListener {
|
@@ -17,6 +17,14 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
17
17
|
return false;
|
18
18
|
}
|
19
19
|
|
20
|
+
public onCopy(_event: CopyEvent): boolean {
|
21
|
+
return false;
|
22
|
+
}
|
23
|
+
|
24
|
+
public onPaste(_event: PasteEvent): boolean {
|
25
|
+
return false;
|
26
|
+
}
|
27
|
+
|
20
28
|
public onKeyPress(_event: KeyPressEvent): boolean {
|
21
29
|
return false;
|
22
30
|
}
|
@@ -0,0 +1,159 @@
|
|
1
|
+
/**
|
2
|
+
* A tool that handles paste events.
|
3
|
+
* @packageDocumentation
|
4
|
+
*/
|
5
|
+
|
6
|
+
import Editor from '../Editor';
|
7
|
+
import { AbstractComponent, TextComponent } from '../components/lib';
|
8
|
+
import { Command, uniteCommands } from '../commands/lib';
|
9
|
+
import SVGLoader from '../SVGLoader';
|
10
|
+
import { PasteEvent } from '../types';
|
11
|
+
import { Mat33, Rect2, Vec2 } from '../math/lib';
|
12
|
+
import BaseTool from './BaseTool';
|
13
|
+
import EditorImage from '../EditorImage';
|
14
|
+
import SelectionTool from './SelectionTool/SelectionTool';
|
15
|
+
import TextTool from './TextTool';
|
16
|
+
import Color4 from '../Color4';
|
17
|
+
import { TextStyle } from '../components/Text';
|
18
|
+
import ImageComponent from '../components/ImageComponent';
|
19
|
+
import Viewport from '../Viewport';
|
20
|
+
|
21
|
+
// { @inheritDoc PasteHandler! }
|
22
|
+
export default class PasteHandler extends BaseTool {
|
23
|
+
public constructor(private editor: Editor) {
|
24
|
+
super(editor.notifier, editor.localization.pasteHandler);
|
25
|
+
}
|
26
|
+
|
27
|
+
public onPaste(event: PasteEvent): boolean {
|
28
|
+
const mime = event.mime.toLowerCase();
|
29
|
+
|
30
|
+
if (mime === 'image/svg+xml') {
|
31
|
+
void this.doSVGPaste(event.data);
|
32
|
+
return true;
|
33
|
+
}
|
34
|
+
else if (mime === 'text/plain') {
|
35
|
+
void this.doTextPaste(event.data);
|
36
|
+
return true;
|
37
|
+
}
|
38
|
+
else if (mime === 'image/png' || mime === 'image/jpeg') {
|
39
|
+
void this.doImagePaste(event.data);
|
40
|
+
return true;
|
41
|
+
}
|
42
|
+
|
43
|
+
return false;
|
44
|
+
}
|
45
|
+
|
46
|
+
private async addComponentsFromPaste(components: AbstractComponent[]) {
|
47
|
+
let bbox: Rect2|null = null;
|
48
|
+
for (const component of components) {
|
49
|
+
if (bbox) {
|
50
|
+
bbox = bbox.union(component.getBBox());
|
51
|
+
} else {
|
52
|
+
bbox = component.getBBox();
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
if (!bbox) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
// Find a transform that scales/moves bbox onto the screen.
|
61
|
+
const visibleRect = this.editor.viewport.visibleRect;
|
62
|
+
const scaleRatioX = visibleRect.width / bbox.width;
|
63
|
+
const scaleRatioY = visibleRect.height / bbox.height;
|
64
|
+
|
65
|
+
let scaleRatio = scaleRatioX;
|
66
|
+
if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
|
67
|
+
scaleRatio = scaleRatioY;
|
68
|
+
}
|
69
|
+
scaleRatio *= 2 / 3;
|
70
|
+
|
71
|
+
scaleRatio = Viewport.roundScaleRatio(scaleRatio);
|
72
|
+
|
73
|
+
const transfm = Mat33.translation(
|
74
|
+
visibleRect.center.minus(bbox.center)
|
75
|
+
).rightMul(
|
76
|
+
Mat33.scaling2D(scaleRatio, bbox.center)
|
77
|
+
);
|
78
|
+
|
79
|
+
const commands: Command[] = [];
|
80
|
+
for (const component of components) {
|
81
|
+
// To allow deserialization, we need to add first, then transform.
|
82
|
+
commands.push(EditorImage.addElement(component));
|
83
|
+
commands.push(component.transformBy(transfm));
|
84
|
+
}
|
85
|
+
|
86
|
+
const applyChunkSize = 100;
|
87
|
+
this.editor.dispatch(uniteCommands(commands, applyChunkSize), true);
|
88
|
+
|
89
|
+
for (const selectionTool of this.editor.toolController.getMatchingTools(SelectionTool)) {
|
90
|
+
selectionTool.setEnabled(true);
|
91
|
+
selectionTool.setSelection(components);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
private async doSVGPaste(data: string) {
|
96
|
+
const sanitize = true;
|
97
|
+
const loader = SVGLoader.fromString(data, sanitize);
|
98
|
+
|
99
|
+
const components: AbstractComponent[] = [];
|
100
|
+
|
101
|
+
await loader.start((component) => {
|
102
|
+
components.push(component);
|
103
|
+
},
|
104
|
+
(_countProcessed: number, _totalToProcess: number) => null);
|
105
|
+
|
106
|
+
await this.addComponentsFromPaste(components);
|
107
|
+
}
|
108
|
+
|
109
|
+
private async doTextPaste(text: string) {
|
110
|
+
const textTools = this.editor.toolController.getMatchingTools(TextTool);
|
111
|
+
|
112
|
+
textTools.sort((a, b) => {
|
113
|
+
if (!a.isEnabled() && b.isEnabled()) {
|
114
|
+
return -1;
|
115
|
+
}
|
116
|
+
|
117
|
+
if (!b.isEnabled() && a.isEnabled()) {
|
118
|
+
return 1;
|
119
|
+
}
|
120
|
+
|
121
|
+
return 0;
|
122
|
+
});
|
123
|
+
|
124
|
+
const defaultTextStyle: TextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
|
125
|
+
const pastedTextStyle: TextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
|
126
|
+
|
127
|
+
const lines = text.split('\n');
|
128
|
+
let lastComponent: TextComponent|null = null;
|
129
|
+
const components: TextComponent[] = [];
|
130
|
+
|
131
|
+
for (const line of lines) {
|
132
|
+
let position = Vec2.zero;
|
133
|
+
if (lastComponent) {
|
134
|
+
const lineMargin = Math.floor(pastedTextStyle.size);
|
135
|
+
position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
|
136
|
+
}
|
137
|
+
|
138
|
+
const component = new TextComponent([ line ], Mat33.translation(position), pastedTextStyle);
|
139
|
+
components.push(component);
|
140
|
+
lastComponent = component;
|
141
|
+
}
|
142
|
+
|
143
|
+
if (components.length === 1) {
|
144
|
+
await this.addComponentsFromPaste([ components[0] ]);
|
145
|
+
} else {
|
146
|
+
// Wrap the existing `TextComponent`s --- dragging one component should drag all.
|
147
|
+
await this.addComponentsFromPaste([
|
148
|
+
new TextComponent(components, Mat33.identity, pastedTextStyle)
|
149
|
+
]);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
private async doImagePaste(dataURL: string) {
|
154
|
+
const image = new Image();
|
155
|
+
image.src = dataURL;
|
156
|
+
const component = await ImageComponent.fromImage(image, Mat33.identity);
|
157
|
+
await this.addComponentsFromPaste([ component ]);
|
158
|
+
}
|
159
|
+
}
|
package/src/tools/Pen.ts
CHANGED
@@ -171,7 +171,7 @@ export default class Pen extends BaseTool {
|
|
171
171
|
}
|
172
172
|
|
173
173
|
if (newThickness !== undefined) {
|
174
|
-
newThickness = Math.min(Math.max(1, newThickness),
|
174
|
+
newThickness = Math.min(Math.max(1, newThickness), 256);
|
175
175
|
this.setThickness(newThickness);
|
176
176
|
return true;
|
177
177
|
}
|