js-draw 0.1.1 → 0.1.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/CHANGELOG.md +13 -0
- package/README.md +21 -12
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -1
- package/dist/src/Editor.js +24 -6
- package/dist/src/EditorImage.js +3 -0
- package/dist/src/Pointer.d.ts +3 -2
- package/dist/src/Pointer.js +12 -3
- package/dist/src/SVGLoader.d.ts +11 -0
- package/dist/src/SVGLoader.js +113 -4
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/Viewport.js +12 -2
- package/dist/src/components/AbstractComponent.d.ts +6 -0
- package/dist/src/components/AbstractComponent.js +11 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/Text.d.ts +30 -0
- package/dist/src/components/Text.js +111 -0
- package/dist/src/components/localization.d.ts +1 -0
- package/dist/src/components/localization.js +1 -0
- package/dist/src/geometry/Mat33.d.ts +1 -0
- package/dist/src/geometry/Mat33.js +30 -0
- package/dist/src/geometry/Path.js +105 -67
- package/dist/src/geometry/Rect2.d.ts +2 -0
- package/dist/src/geometry/Rect2.js +6 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +13 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +6 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +50 -7
- package/dist/src/testing/loadExpectExtensions.js +1 -4
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +242 -154
- package/dist/src/toolbar/icons.d.ts +12 -0
- package/dist/src/toolbar/icons.js +198 -0
- package/dist/src/toolbar/localization.d.ts +5 -1
- package/dist/src/toolbar/localization.js +5 -1
- package/dist/src/toolbar/types.d.ts +4 -0
- package/dist/src/tools/PanZoom.d.ts +9 -6
- package/dist/src/tools/PanZoom.js +30 -21
- package/dist/src/tools/Pen.js +8 -3
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +30 -0
- package/dist/src/tools/TextTool.js +173 -0
- package/dist/src/tools/ToolController.d.ts +5 -5
- package/dist/src/tools/ToolController.js +10 -9
- package/dist/src/tools/localization.d.ts +3 -0
- package/dist/src/tools/localization.js +3 -0
- package/dist-test/test-dist-bundle.html +8 -1
- package/package.json +1 -1
- package/src/Editor.css +2 -0
- package/src/Editor.ts +26 -7
- package/src/EditorImage.ts +4 -0
- package/src/Pointer.ts +13 -4
- package/src/SVGLoader.ts +146 -5
- package/src/Viewport.ts +15 -3
- package/src/components/AbstractComponent.ts +16 -1
- package/src/components/SVGGlobalAttributesObject.ts +0 -1
- package/src/components/Stroke.ts +1 -1
- package/src/components/Text.ts +140 -0
- package/src/components/localization.ts +2 -0
- package/src/geometry/Mat33.test.ts +44 -0
- package/src/geometry/Mat33.ts +41 -0
- package/src/geometry/Path.fromString.test.ts +94 -4
- package/src/geometry/Path.toString.test.ts +7 -3
- package/src/geometry/Path.ts +110 -68
- package/src/geometry/Rect2.ts +8 -0
- package/src/rendering/renderers/AbstractRenderer.ts +18 -1
- package/src/rendering/renderers/CanvasRenderer.ts +34 -10
- package/src/rendering/renderers/DummyRenderer.ts +8 -0
- package/src/rendering/renderers/SVGRenderer.ts +57 -10
- package/src/testing/loadExpectExtensions.ts +1 -4
- package/src/toolbar/HTMLToolbar.ts +294 -170
- package/src/toolbar/icons.ts +227 -0
- package/src/toolbar/localization.ts +11 -2
- package/src/toolbar/toolbar.css +27 -11
- package/src/toolbar/types.ts +5 -0
- package/src/tools/PanZoom.ts +37 -27
- package/src/tools/Pen.ts +7 -3
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +225 -0
- package/src/tools/ToolController.ts +7 -5
- package/src/tools/localization.ts +7 -0
@@ -0,0 +1,198 @@
|
|
1
|
+
import EventDispatcher from '../EventDispatcher';
|
2
|
+
import { Vec2 } from '../geometry/Vec2';
|
3
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
4
|
+
import Viewport from '../Viewport';
|
5
|
+
const svgNamespace = 'http://www.w3.org/2000/svg';
|
6
|
+
const primaryForegroundFill = `
|
7
|
+
style='fill: var(--primary-foreground-color);'
|
8
|
+
`;
|
9
|
+
const primaryForegroundStrokeFill = `
|
10
|
+
style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
|
11
|
+
`;
|
12
|
+
export const makeUndoIcon = () => {
|
13
|
+
return makeRedoIcon(true);
|
14
|
+
};
|
15
|
+
export const makeRedoIcon = (mirror = false) => {
|
16
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
17
|
+
icon.innerHTML = `
|
18
|
+
<style>
|
19
|
+
.toolbar-svg-undo-redo-icon {
|
20
|
+
stroke: var(--primary-foreground-color);
|
21
|
+
stroke-width: 12;
|
22
|
+
stroke-linejoin: round;
|
23
|
+
stroke-linecap: round;
|
24
|
+
fill: none;
|
25
|
+
|
26
|
+
transform-origin: center;
|
27
|
+
}
|
28
|
+
</style>
|
29
|
+
<path
|
30
|
+
d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
|
31
|
+
class='toolbar-svg-undo-redo-icon'
|
32
|
+
style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
|
33
|
+
`;
|
34
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
35
|
+
return icon;
|
36
|
+
};
|
37
|
+
export const makeDropdownIcon = () => {
|
38
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
39
|
+
icon.innerHTML = `
|
40
|
+
<g>
|
41
|
+
<path
|
42
|
+
d='M5,10 L50,90 L95,10 Z'
|
43
|
+
${primaryForegroundFill}
|
44
|
+
/>
|
45
|
+
</g>
|
46
|
+
`;
|
47
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
48
|
+
return icon;
|
49
|
+
};
|
50
|
+
export const makeEraserIcon = () => {
|
51
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
52
|
+
// Draw an eraser-like shape
|
53
|
+
icon.innerHTML = `
|
54
|
+
<g>
|
55
|
+
<rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
|
56
|
+
<rect
|
57
|
+
x=10 y=10 width=80 height=50
|
58
|
+
${primaryForegroundFill}
|
59
|
+
/>
|
60
|
+
</g>
|
61
|
+
`;
|
62
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
63
|
+
return icon;
|
64
|
+
};
|
65
|
+
export const makeSelectionIcon = () => {
|
66
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
67
|
+
// Draw a cursor-like shape
|
68
|
+
icon.innerHTML = `
|
69
|
+
<g>
|
70
|
+
<rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
|
71
|
+
<rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
|
72
|
+
</g>
|
73
|
+
`;
|
74
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
75
|
+
return icon;
|
76
|
+
};
|
77
|
+
export const makeHandToolIcon = () => {
|
78
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
79
|
+
// Draw a cursor-like shape
|
80
|
+
icon.innerHTML = `
|
81
|
+
<g>
|
82
|
+
<path d='
|
83
|
+
m 10,60
|
84
|
+
5,30
|
85
|
+
H 90
|
86
|
+
V 30
|
87
|
+
C 90,20 75,20 75,30
|
88
|
+
V 60
|
89
|
+
20
|
90
|
+
C 75,10 60,10 60,20
|
91
|
+
V 60
|
92
|
+
15
|
93
|
+
C 60,5 45,5 45,15
|
94
|
+
V 60
|
95
|
+
25
|
96
|
+
C 45,15 30,15 30,25
|
97
|
+
V 60
|
98
|
+
75
|
99
|
+
L 25,60
|
100
|
+
C 20,45 10,50 10,60
|
101
|
+
Z'
|
102
|
+
|
103
|
+
fill='none'
|
104
|
+
style='
|
105
|
+
stroke: var(--primary-foreground-color);
|
106
|
+
stroke-width: 2;
|
107
|
+
'
|
108
|
+
/>
|
109
|
+
</g>
|
110
|
+
`;
|
111
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
112
|
+
return icon;
|
113
|
+
};
|
114
|
+
export const makeTextIcon = (textStyle) => {
|
115
|
+
var _a, _b;
|
116
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
117
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
118
|
+
const textNode = document.createElementNS(svgNamespace, 'text');
|
119
|
+
textNode.appendChild(document.createTextNode('T'));
|
120
|
+
textNode.style.fontFamily = textStyle.fontFamily;
|
121
|
+
textNode.style.fontWeight = (_a = textStyle.fontWeight) !== null && _a !== void 0 ? _a : '';
|
122
|
+
textNode.style.fontVariant = (_b = textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
|
123
|
+
textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
|
124
|
+
textNode.style.textAnchor = 'middle';
|
125
|
+
textNode.setAttribute('x', '50');
|
126
|
+
textNode.setAttribute('y', '75');
|
127
|
+
textNode.style.fontSize = '65px';
|
128
|
+
textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
|
129
|
+
icon.appendChild(textNode);
|
130
|
+
return icon;
|
131
|
+
};
|
132
|
+
export const makePenIcon = (tipThickness, color) => {
|
133
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
134
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
135
|
+
const halfThickness = tipThickness / 2;
|
136
|
+
// Draw a pen-like shape
|
137
|
+
const primaryStrokeTipPath = `M14,63 L${50 - halfThickness},95 L${50 + halfThickness},90 L88,60 Z`;
|
138
|
+
const backgroundStrokeTipPath = `M14,63 L${50 - halfThickness},85 L${50 + halfThickness},83 L88,60 Z`;
|
139
|
+
icon.innerHTML = `
|
140
|
+
<defs>
|
141
|
+
<pattern
|
142
|
+
id='checkerboard'
|
143
|
+
viewBox='0,0,10,10'
|
144
|
+
width='20%'
|
145
|
+
height='20%'
|
146
|
+
patternUnits='userSpaceOnUse'
|
147
|
+
>
|
148
|
+
<rect x=0 y=0 width=10 height=10 fill='white'/>
|
149
|
+
<rect x=0 y=0 width=5 height=5 fill='gray'/>
|
150
|
+
<rect x=5 y=5 width=5 height=5 fill='gray'/>
|
151
|
+
</pattern>
|
152
|
+
</defs>
|
153
|
+
<g>
|
154
|
+
<!-- Pen grip -->
|
155
|
+
<path
|
156
|
+
d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
|
157
|
+
${primaryForegroundStrokeFill}
|
158
|
+
/>
|
159
|
+
</g>
|
160
|
+
<g>
|
161
|
+
<!-- Checkerboard background for slightly transparent pens -->
|
162
|
+
<path d='${backgroundStrokeTipPath}' fill='url(#checkerboard)'/>
|
163
|
+
|
164
|
+
<!-- Actual pen tip -->
|
165
|
+
<path
|
166
|
+
d='${primaryStrokeTipPath}'
|
167
|
+
fill='${color}'
|
168
|
+
stroke='${color}'
|
169
|
+
/>
|
170
|
+
</g>
|
171
|
+
`;
|
172
|
+
return icon;
|
173
|
+
};
|
174
|
+
export const makeIconFromFactory = (pen, factory) => {
|
175
|
+
const toolThickness = pen.getThickness();
|
176
|
+
const nowTime = (new Date()).getTime();
|
177
|
+
const startPoint = {
|
178
|
+
pos: Vec2.of(10, 10),
|
179
|
+
width: toolThickness / 5,
|
180
|
+
color: pen.getColor(),
|
181
|
+
time: nowTime - 100,
|
182
|
+
};
|
183
|
+
const endPoint = {
|
184
|
+
pos: Vec2.of(90, 90),
|
185
|
+
width: toolThickness / 5,
|
186
|
+
color: pen.getColor(),
|
187
|
+
time: nowTime,
|
188
|
+
};
|
189
|
+
const viewport = new Viewport(new EventDispatcher());
|
190
|
+
const builder = factory(startPoint, viewport);
|
191
|
+
builder.addPoint(endPoint);
|
192
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
193
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
194
|
+
viewport.updateScreenSize(Vec2.of(100, 100));
|
195
|
+
const renderer = new SVGRenderer(icon, viewport);
|
196
|
+
builder.preview(renderer);
|
197
|
+
return icon;
|
198
|
+
};
|
@@ -1,4 +1,7 @@
|
|
1
1
|
export interface ToolbarLocalization {
|
2
|
+
fontLabel: string;
|
3
|
+
anyDevicePanning: string;
|
4
|
+
touchPanning: string;
|
2
5
|
outlinedRectanglePen: string;
|
3
6
|
filledRectanglePen: string;
|
4
7
|
linePen: string;
|
@@ -9,7 +12,7 @@ export interface ToolbarLocalization {
|
|
9
12
|
pen: string;
|
10
13
|
eraser: string;
|
11
14
|
select: string;
|
12
|
-
|
15
|
+
handTool: string;
|
13
16
|
thicknessLabel: string;
|
14
17
|
resizeImageToSelection: string;
|
15
18
|
deleteSelection: string;
|
@@ -17,5 +20,6 @@ export interface ToolbarLocalization {
|
|
17
20
|
redo: string;
|
18
21
|
dropdownShown: (toolName: string) => string;
|
19
22
|
dropdownHidden: (toolName: string) => string;
|
23
|
+
zoomLevel: (zoomPercentage: number) => string;
|
20
24
|
}
|
21
25
|
export declare const defaultToolbarLocalization: ToolbarLocalization;
|
@@ -2,14 +2,17 @@ export const defaultToolbarLocalization = {
|
|
2
2
|
pen: 'Pen',
|
3
3
|
eraser: 'Eraser',
|
4
4
|
select: 'Select',
|
5
|
-
|
5
|
+
handTool: 'Pan',
|
6
6
|
thicknessLabel: 'Thickness: ',
|
7
7
|
colorLabel: 'Color: ',
|
8
|
+
fontLabel: 'Font: ',
|
8
9
|
resizeImageToSelection: 'Resize image to selection',
|
9
10
|
deleteSelection: 'Delete selection',
|
10
11
|
undo: 'Undo',
|
11
12
|
redo: 'Redo',
|
12
13
|
selectObjectType: 'Object type: ',
|
14
|
+
touchPanning: 'Touchscreen panning',
|
15
|
+
anyDevicePanning: 'Any device panning',
|
13
16
|
freehandPen: 'Freehand',
|
14
17
|
arrowPen: 'Arrow',
|
15
18
|
linePen: 'Line',
|
@@ -17,4 +20,5 @@ export const defaultToolbarLocalization = {
|
|
17
20
|
filledRectanglePen: 'Filled rectangle',
|
18
21
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
19
22
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
23
|
+
zoomLevel: (zoomPercent) => `Zoom: ${zoomPercent}%`,
|
20
24
|
};
|
@@ -11,22 +11,23 @@ interface PinchData {
|
|
11
11
|
dist: number;
|
12
12
|
}
|
13
13
|
export declare enum PanZoomMode {
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
OneFingerTouchGestures = 1,
|
15
|
+
TwoFingerTouchGestures = 2,
|
16
|
+
RightClickDrags = 4,
|
17
|
+
SinglePointerGestures = 8
|
17
18
|
}
|
18
19
|
export default class PanZoom extends BaseTool {
|
19
20
|
private editor;
|
20
21
|
private mode;
|
21
|
-
readonly kind: ToolType.PanZoom
|
22
|
+
readonly kind: ToolType.PanZoom;
|
22
23
|
private transform;
|
23
24
|
private lastAngle;
|
24
25
|
private lastDist;
|
25
26
|
private lastScreenCenter;
|
26
27
|
constructor(editor: Editor, mode: PanZoomMode, description: string);
|
27
28
|
computePinchData(p1: Pointer, p2: Pointer): PinchData;
|
28
|
-
private
|
29
|
-
onPointerDown({ allPointers }: PointerEvt): boolean;
|
29
|
+
private allPointersAreOfType;
|
30
|
+
onPointerDown({ allPointers: pointers }: PointerEvt): boolean;
|
30
31
|
private getCenterDelta;
|
31
32
|
private handleTwoFingerMove;
|
32
33
|
private handleOneFingerMove;
|
@@ -36,5 +37,7 @@ export default class PanZoom extends BaseTool {
|
|
36
37
|
private updateTransform;
|
37
38
|
onWheel({ delta, screenPos }: WheelEvt): boolean;
|
38
39
|
onKeyPress({ key }: KeyPressEvent): boolean;
|
40
|
+
setMode(mode: PanZoomMode): void;
|
41
|
+
getMode(): PanZoomMode;
|
39
42
|
}
|
40
43
|
export {};
|
@@ -2,17 +2,16 @@ import Mat33 from '../geometry/Mat33';
|
|
2
2
|
import { Vec2 } from '../geometry/Vec2';
|
3
3
|
import Vec3 from '../geometry/Vec3';
|
4
4
|
import { PointerDevice } from '../Pointer';
|
5
|
+
import { EditorEventType } from '../types';
|
5
6
|
import { Viewport } from '../Viewport';
|
6
7
|
import BaseTool from './BaseTool';
|
7
8
|
import { ToolType } from './ToolController';
|
8
9
|
export var PanZoomMode;
|
9
10
|
(function (PanZoomMode) {
|
10
|
-
|
11
|
-
PanZoomMode[PanZoomMode["
|
12
|
-
|
13
|
-
PanZoomMode[PanZoomMode["
|
14
|
-
// / Handle gestures from any device, rather than just touch
|
15
|
-
PanZoomMode[PanZoomMode["AnyDevice"] = 4] = "AnyDevice";
|
11
|
+
PanZoomMode[PanZoomMode["OneFingerTouchGestures"] = 1] = "OneFingerTouchGestures";
|
12
|
+
PanZoomMode[PanZoomMode["TwoFingerTouchGestures"] = 2] = "TwoFingerTouchGestures";
|
13
|
+
PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
|
14
|
+
PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
|
16
15
|
})(PanZoomMode || (PanZoomMode = {}));
|
17
16
|
export default class PanZoom extends BaseTool {
|
18
17
|
constructor(editor, mode, description) {
|
@@ -21,9 +20,6 @@ export default class PanZoom extends BaseTool {
|
|
21
20
|
this.mode = mode;
|
22
21
|
this.kind = ToolType.PanZoom;
|
23
22
|
this.transform = null;
|
24
|
-
if (mode === PanZoomMode.OneFingerGestures) {
|
25
|
-
this.kind = ToolType.TouchPanZoom;
|
26
|
-
}
|
27
23
|
}
|
28
24
|
// Returns information about the pointers in a gesture
|
29
25
|
computePinchData(p1, p2) {
|
@@ -34,24 +30,25 @@ export default class PanZoom extends BaseTool {
|
|
34
30
|
const screenCenter = p2.screenPos.plus(p1.screenPos).times(0.5);
|
35
31
|
return { canvasCenter, screenCenter, angle, dist };
|
36
32
|
}
|
37
|
-
|
38
|
-
return
|
33
|
+
allPointersAreOfType(pointers, kind) {
|
34
|
+
return pointers.every(pointer => pointer.device === kind);
|
39
35
|
}
|
40
|
-
onPointerDown({ allPointers }) {
|
36
|
+
onPointerDown({ allPointers: pointers }) {
|
41
37
|
var _a;
|
42
38
|
let handlingGesture = false;
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
const { screenCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
|
39
|
+
const allAreTouch = this.allPointersAreOfType(pointers, PointerDevice.Touch);
|
40
|
+
const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
|
41
|
+
if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
|
42
|
+
const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
|
48
43
|
this.lastAngle = angle;
|
49
44
|
this.lastDist = dist;
|
50
45
|
this.lastScreenCenter = screenCenter;
|
51
46
|
handlingGesture = true;
|
52
47
|
}
|
53
|
-
else if (
|
54
|
-
this.
|
48
|
+
else if (pointers.length === 1 && ((this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
|
49
|
+
|| (isRightClick && this.mode & PanZoomMode.RightClickDrags)
|
50
|
+
|| (this.mode & PanZoomMode.SinglePointerGestures))) {
|
51
|
+
this.lastScreenCenter = pointers[0].screenPos;
|
55
52
|
handlingGesture = true;
|
56
53
|
}
|
57
54
|
if (handlingGesture) {
|
@@ -87,10 +84,10 @@ export default class PanZoom extends BaseTool {
|
|
87
84
|
var _a;
|
88
85
|
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
|
89
86
|
const lastTransform = this.transform;
|
90
|
-
if (allPointers.length === 2
|
87
|
+
if (allPointers.length === 2) {
|
91
88
|
this.handleTwoFingerMove(allPointers);
|
92
89
|
}
|
93
|
-
else if (allPointers.length === 1
|
90
|
+
else if (allPointers.length === 1) {
|
94
91
|
this.handleOneFingerMove(allPointers[0]);
|
95
92
|
}
|
96
93
|
lastTransform.unapply(this.editor);
|
@@ -191,4 +188,16 @@ export default class PanZoom extends BaseTool {
|
|
191
188
|
this.updateTransform(transformUpdate);
|
192
189
|
return true;
|
193
190
|
}
|
191
|
+
setMode(mode) {
|
192
|
+
if (mode !== this.mode) {
|
193
|
+
this.mode = mode;
|
194
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
195
|
+
kind: EditorEventType.ToolUpdated,
|
196
|
+
tool: this,
|
197
|
+
});
|
198
|
+
}
|
199
|
+
}
|
200
|
+
getMode() {
|
201
|
+
return this.mode;
|
202
|
+
}
|
194
203
|
}
|
package/dist/src/tools/Pen.js
CHANGED
@@ -66,9 +66,14 @@ export default class Pen extends BaseTool {
|
|
66
66
|
if (this.builder && current.isPrimary) {
|
67
67
|
const stroke = this.builder.build();
|
68
68
|
this.previewStroke();
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
if (stroke.getBBox().area > 0) {
|
70
|
+
const canFlatten = true;
|
71
|
+
const action = new EditorImage.AddElementCommand(stroke, canFlatten);
|
72
|
+
this.editor.dispatch(action);
|
73
|
+
}
|
74
|
+
else {
|
75
|
+
console.warn('Pen: Not adding empty stroke', stroke, 'to the canvas.');
|
76
|
+
}
|
72
77
|
}
|
73
78
|
this.builder = null;
|
74
79
|
this.editor.clearWetInk();
|
@@ -396,7 +396,7 @@ export default class SelectionTool extends BaseTool {
|
|
396
396
|
if (hasSelection) {
|
397
397
|
this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
|
398
398
|
const selectionRect = this.selectionBox.region;
|
399
|
-
this.editor.viewport.zoomTo(selectionRect).apply(this.editor);
|
399
|
+
this.editor.viewport.zoomTo(selectionRect, false).apply(this.editor);
|
400
400
|
}
|
401
401
|
}
|
402
402
|
onPointerUp(event) {
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import { TextStyle } from '../components/Text';
|
3
|
+
import Editor from '../Editor';
|
4
|
+
import { PointerEvt } from '../types';
|
5
|
+
import BaseTool from './BaseTool';
|
6
|
+
import { ToolLocalization } from './localization';
|
7
|
+
import { ToolType } from './ToolController';
|
8
|
+
export default class TextTool extends BaseTool {
|
9
|
+
private editor;
|
10
|
+
private localizationTable;
|
11
|
+
kind: ToolType;
|
12
|
+
private textStyle;
|
13
|
+
private textEditOverlay;
|
14
|
+
private textInputElem;
|
15
|
+
private textTargetPosition;
|
16
|
+
private textMeasuringCtx;
|
17
|
+
constructor(editor: Editor, description: string, localizationTable: ToolLocalization);
|
18
|
+
private getTextAscent;
|
19
|
+
private flushInput;
|
20
|
+
private updateTextInput;
|
21
|
+
private startTextInput;
|
22
|
+
setEnabled(enabled: boolean): void;
|
23
|
+
onPointerDown({ current, allPointers }: PointerEvt): boolean;
|
24
|
+
onGestureCancel(): void;
|
25
|
+
private dispatchUpdateEvent;
|
26
|
+
setFontFamily(fontFamily: string): void;
|
27
|
+
setColor(color: Color4): void;
|
28
|
+
setFontSize(size: number): void;
|
29
|
+
getTextStyle(): TextStyle;
|
30
|
+
}
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import Text from '../components/Text';
|
3
|
+
import EditorImage from '../EditorImage';
|
4
|
+
import Mat33 from '../geometry/Mat33';
|
5
|
+
import { PointerDevice } from '../Pointer';
|
6
|
+
import { EditorEventType } from '../types';
|
7
|
+
import BaseTool from './BaseTool';
|
8
|
+
import { ToolType } from './ToolController';
|
9
|
+
const overlayCssClass = 'textEditorOverlay';
|
10
|
+
export default class TextTool extends BaseTool {
|
11
|
+
constructor(editor, description, localizationTable) {
|
12
|
+
super(editor.notifier, description);
|
13
|
+
this.editor = editor;
|
14
|
+
this.localizationTable = localizationTable;
|
15
|
+
this.kind = ToolType.Text;
|
16
|
+
this.textInputElem = null;
|
17
|
+
this.textTargetPosition = null;
|
18
|
+
this.textMeasuringCtx = null;
|
19
|
+
this.textStyle = {
|
20
|
+
size: 32,
|
21
|
+
fontFamily: 'sans-serif',
|
22
|
+
renderingStyle: {
|
23
|
+
fill: Color4.purple,
|
24
|
+
},
|
25
|
+
};
|
26
|
+
this.textEditOverlay = document.createElement('div');
|
27
|
+
this.textEditOverlay.classList.add(overlayCssClass);
|
28
|
+
this.editor.addStyleSheet(`
|
29
|
+
.${overlayCssClass} {
|
30
|
+
height: 0;
|
31
|
+
overflow: visible;
|
32
|
+
}
|
33
|
+
|
34
|
+
.${overlayCssClass} input {
|
35
|
+
background-color: rgba(0, 0, 0, 0);
|
36
|
+
border: none;
|
37
|
+
padding: 0;
|
38
|
+
}
|
39
|
+
`);
|
40
|
+
this.editor.createHTMLOverlay(this.textEditOverlay);
|
41
|
+
this.editor.notifier.on(EditorEventType.ViewportChanged, () => this.updateTextInput());
|
42
|
+
}
|
43
|
+
getTextAscent(text, style) {
|
44
|
+
var _a;
|
45
|
+
(_a = this.textMeasuringCtx) !== null && _a !== void 0 ? _a : (this.textMeasuringCtx = document.createElement('canvas').getContext('2d'));
|
46
|
+
if (this.textMeasuringCtx) {
|
47
|
+
Text.applyTextStyles(this.textMeasuringCtx, style);
|
48
|
+
return this.textMeasuringCtx.measureText(text).actualBoundingBoxAscent;
|
49
|
+
}
|
50
|
+
// Estimate
|
51
|
+
return style.size * 2 / 3;
|
52
|
+
}
|
53
|
+
flushInput() {
|
54
|
+
if (this.textInputElem && this.textTargetPosition) {
|
55
|
+
const content = this.textInputElem.value;
|
56
|
+
this.textInputElem.remove();
|
57
|
+
this.textInputElem = null;
|
58
|
+
if (content === '') {
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
const textTransform = Mat33.translation(this.textTargetPosition).rightMul(Mat33.scaling2D(this.editor.viewport.getSizeOfPixelOnCanvas()));
|
62
|
+
const textComponent = new Text([content], textTransform, this.textStyle);
|
63
|
+
const action = new EditorImage.AddElementCommand(textComponent);
|
64
|
+
this.editor.dispatch(action);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
updateTextInput() {
|
68
|
+
var _a, _b, _c;
|
69
|
+
if (!this.textInputElem || !this.textTargetPosition) {
|
70
|
+
(_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.remove();
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
const viewport = this.editor.viewport;
|
74
|
+
const textScreenPos = this.editor.viewport.canvasToScreen(this.textTargetPosition);
|
75
|
+
this.textInputElem.type = 'text';
|
76
|
+
this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
|
77
|
+
this.textInputElem.style.fontFamily = this.textStyle.fontFamily;
|
78
|
+
this.textInputElem.style.fontVariant = (_b = this.textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
|
79
|
+
this.textInputElem.style.fontWeight = (_c = this.textStyle.fontWeight) !== null && _c !== void 0 ? _c : '';
|
80
|
+
this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
|
81
|
+
this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
|
82
|
+
this.textInputElem.style.position = 'relative';
|
83
|
+
this.textInputElem.style.left = `${textScreenPos.x}px`;
|
84
|
+
this.textInputElem.style.top = `${textScreenPos.y}px`;
|
85
|
+
this.textInputElem.style.margin = '0';
|
86
|
+
const rotation = viewport.getRotationAngle();
|
87
|
+
const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
|
88
|
+
this.textInputElem.style.transform = `rotate(${rotation * 180 / Math.PI}deg) translate(0, ${-ascent}px)`;
|
89
|
+
this.textInputElem.style.transformOrigin = 'top left';
|
90
|
+
}
|
91
|
+
startTextInput(textCanvasPos, initialText) {
|
92
|
+
this.flushInput();
|
93
|
+
this.textInputElem = document.createElement('input');
|
94
|
+
this.textInputElem.value = initialText;
|
95
|
+
this.textTargetPosition = textCanvasPos;
|
96
|
+
this.updateTextInput();
|
97
|
+
this.textInputElem.oninput = () => {
|
98
|
+
var _a;
|
99
|
+
if (this.textInputElem) {
|
100
|
+
this.textInputElem.size = ((_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.value.length) || 10;
|
101
|
+
}
|
102
|
+
};
|
103
|
+
this.textInputElem.onblur = () => {
|
104
|
+
// Don't remove the input within the context of a blur event handler.
|
105
|
+
// Doing so causes errors.
|
106
|
+
setTimeout(() => this.flushInput(), 0);
|
107
|
+
};
|
108
|
+
this.textInputElem.onkeyup = (evt) => {
|
109
|
+
var _a;
|
110
|
+
if (evt.key === 'Enter') {
|
111
|
+
this.flushInput();
|
112
|
+
this.editor.focus();
|
113
|
+
}
|
114
|
+
else if (evt.key === 'Escape') {
|
115
|
+
// Cancel input.
|
116
|
+
(_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.remove();
|
117
|
+
this.textInputElem = null;
|
118
|
+
this.editor.focus();
|
119
|
+
}
|
120
|
+
};
|
121
|
+
this.textEditOverlay.replaceChildren(this.textInputElem);
|
122
|
+
setTimeout(() => { var _a; return (_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
|
123
|
+
}
|
124
|
+
setEnabled(enabled) {
|
125
|
+
super.setEnabled(enabled);
|
126
|
+
if (!enabled) {
|
127
|
+
this.flushInput();
|
128
|
+
}
|
129
|
+
this.textEditOverlay.style.display = enabled ? 'block' : 'none';
|
130
|
+
}
|
131
|
+
onPointerDown({ current, allPointers }) {
|
132
|
+
if (current.device === PointerDevice.Eraser) {
|
133
|
+
return false;
|
134
|
+
}
|
135
|
+
if (allPointers.length === 1) {
|
136
|
+
this.startTextInput(current.canvasPos, '');
|
137
|
+
return true;
|
138
|
+
}
|
139
|
+
return false;
|
140
|
+
}
|
141
|
+
onGestureCancel() {
|
142
|
+
this.flushInput();
|
143
|
+
this.editor.focus();
|
144
|
+
}
|
145
|
+
dispatchUpdateEvent() {
|
146
|
+
this.updateTextInput();
|
147
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
148
|
+
kind: EditorEventType.ToolUpdated,
|
149
|
+
tool: this,
|
150
|
+
});
|
151
|
+
}
|
152
|
+
setFontFamily(fontFamily) {
|
153
|
+
if (fontFamily !== this.textStyle.fontFamily) {
|
154
|
+
this.textStyle = Object.assign(Object.assign({}, this.textStyle), { fontFamily: fontFamily });
|
155
|
+
this.dispatchUpdateEvent();
|
156
|
+
}
|
157
|
+
}
|
158
|
+
setColor(color) {
|
159
|
+
if (!color.eq(this.textStyle.renderingStyle.fill)) {
|
160
|
+
this.textStyle = Object.assign(Object.assign({}, this.textStyle), { renderingStyle: Object.assign(Object.assign({}, this.textStyle.renderingStyle), { fill: color }) });
|
161
|
+
this.dispatchUpdateEvent();
|
162
|
+
}
|
163
|
+
}
|
164
|
+
setFontSize(size) {
|
165
|
+
if (size !== this.textStyle.size) {
|
166
|
+
this.textStyle = Object.assign(Object.assign({}, this.textStyle), { size });
|
167
|
+
this.dispatchUpdateEvent();
|
168
|
+
}
|
169
|
+
}
|
170
|
+
getTextStyle() {
|
171
|
+
return this.textStyle;
|
172
|
+
}
|
173
|
+
}
|
@@ -3,11 +3,11 @@ import Editor from '../Editor';
|
|
3
3
|
import BaseTool from './BaseTool';
|
4
4
|
import { ToolLocalization } from './localization';
|
5
5
|
export declare enum ToolType {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
Pen = 0,
|
7
|
+
Selection = 1,
|
8
|
+
Eraser = 2,
|
9
|
+
PanZoom = 3,
|
10
|
+
Text = 4,
|
11
11
|
UndoRedoShortcut = 5
|
12
12
|
}
|
13
13
|
export default class ToolController {
|