js-draw 0.1.2 → 0.1.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/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.js +2 -3
- package/dist/src/SVGLoader.d.ts +8 -0
- package/dist/src/SVGLoader.js +105 -6
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/Viewport.js +2 -2
- package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
- package/dist/src/components/Text.d.ts +30 -0
- package/dist/src/components/Text.js +109 -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 +8 -1
- 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 +5 -0
- package/dist/src/rendering/renderers/AbstractRenderer.js +12 -0
- 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 +3 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +30 -1
- package/dist/src/testing/loadExpectExtensions.js +1 -4
- package/dist/src/toolbar/HTMLToolbar.js +52 -1
- package/dist/src/toolbar/icons.d.ts +2 -0
- package/dist/src/toolbar/icons.js +17 -0
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +29 -0
- package/dist/src/tools/TextTool.js +154 -0
- package/dist/src/tools/ToolController.d.ts +2 -1
- package/dist/src/tools/ToolController.js +4 -1
- package/dist/src/tools/localization.d.ts +3 -1
- package/dist/src/tools/localization.js +3 -1
- package/package.json +1 -1
- package/src/Editor.ts +3 -3
- package/src/SVGLoader.ts +124 -6
- package/src/Viewport.ts +2 -2
- package/src/components/SVGGlobalAttributesObject.ts +0 -1
- package/src/components/Text.ts +136 -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.toString.test.ts +7 -3
- package/src/geometry/Path.ts +11 -1
- package/src/geometry/Rect2.ts +8 -0
- package/src/rendering/renderers/AbstractRenderer.ts +16 -0
- package/src/rendering/renderers/CanvasRenderer.ts +34 -10
- package/src/rendering/renderers/DummyRenderer.ts +8 -0
- package/src/rendering/renderers/SVGRenderer.ts +36 -1
- package/src/testing/loadExpectExtensions.ts +1 -4
- package/src/toolbar/HTMLToolbar.ts +64 -1
- package/src/toolbar/icons.ts +23 -0
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +206 -0
- package/src/tools/ToolController.ts +4 -0
- package/src/tools/localization.ts +7 -2
@@ -1,5 +1,6 @@
|
|
1
1
|
// Renderer that outputs nothing. Useful for automated tests.
|
2
2
|
|
3
|
+
import { TextStyle } from '../../components/Text';
|
3
4
|
import Mat33 from '../../geometry/Mat33';
|
4
5
|
import Rect2 from '../../geometry/Rect2';
|
5
6
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
@@ -14,6 +15,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
14
15
|
public lastFillStyle: RenderingStyle|null = null;
|
15
16
|
public lastPoint: Point2|null = null;
|
16
17
|
public objectNestingLevel: number = 0;
|
18
|
+
public lastText: string|null = null;
|
17
19
|
|
18
20
|
// List of points drawn since the last clear.
|
19
21
|
public pointBuffer: Point2[] = [];
|
@@ -40,6 +42,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
40
42
|
this.clearedCount ++;
|
41
43
|
this.renderedPathCount = 0;
|
42
44
|
this.pointBuffer = [];
|
45
|
+
this.lastText = null;
|
43
46
|
|
44
47
|
// Ensure all objects finished rendering
|
45
48
|
if (this.objectNestingLevel > 0) {
|
@@ -88,6 +91,11 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
88
91
|
// As such, it is unlikely to be the target of automated tests.
|
89
92
|
}
|
90
93
|
|
94
|
+
|
95
|
+
public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
|
96
|
+
this.lastText = text;
|
97
|
+
}
|
98
|
+
|
91
99
|
public startObject(boundingBox: Rect2, _clip: boolean) {
|
92
100
|
super.startObject(boundingBox);
|
93
101
|
|
@@ -1,9 +1,11 @@
|
|
1
1
|
|
2
2
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
|
+
import { TextStyle } from '../../components/Text';
|
4
|
+
import Mat33 from '../../geometry/Mat33';
|
3
5
|
import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
|
4
6
|
import Rect2 from '../../geometry/Rect2';
|
5
7
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
6
|
-
import { svgAttributesDataKey, SVGLoaderUnknownAttribute } from '../../SVGLoader';
|
8
|
+
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
|
7
9
|
import Viewport from '../../Viewport';
|
8
10
|
import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
|
9
11
|
|
@@ -107,6 +109,32 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
107
109
|
this.objectElems?.push(pathElem);
|
108
110
|
}
|
109
111
|
|
112
|
+
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
113
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
114
|
+
|
115
|
+
const textElem = document.createElementNS(svgNameSpace, 'text');
|
116
|
+
textElem.appendChild(document.createTextNode(text));
|
117
|
+
textElem.style.transform = `matrix(
|
118
|
+
${transform.a1}, ${transform.b1},
|
119
|
+
${transform.a2}, ${transform.b2},
|
120
|
+
${transform.a3}, ${transform.b3}
|
121
|
+
)`;
|
122
|
+
textElem.style.fontFamily = style.fontFamily;
|
123
|
+
textElem.style.fontVariant = style.fontVariant ?? '';
|
124
|
+
textElem.style.fontWeight = style.fontWeight ?? '';
|
125
|
+
textElem.style.fontSize = style.size + 'px';
|
126
|
+
textElem.style.fill = style.renderingStyle.fill.toHexString();
|
127
|
+
|
128
|
+
if (style.renderingStyle.stroke) {
|
129
|
+
const strokeStyle = style.renderingStyle.stroke;
|
130
|
+
textElem.style.stroke = strokeStyle.color.toHexString();
|
131
|
+
textElem.style.strokeWidth = strokeStyle.width + 'px';
|
132
|
+
}
|
133
|
+
|
134
|
+
this.elem.appendChild(textElem);
|
135
|
+
this.objectElems?.push(textElem);
|
136
|
+
}
|
137
|
+
|
110
138
|
public startObject(boundingBox: Rect2) {
|
111
139
|
super.startObject(boundingBox);
|
112
140
|
|
@@ -127,12 +155,19 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
127
155
|
// Restore any attributes unsupported by the app.
|
128
156
|
for (const elem of this.objectElems ?? []) {
|
129
157
|
const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
|
158
|
+
const styleAttrs = loaderData[svgStyleAttributesDataKey] as SVGLoaderUnknownStyleAttribute[]|undefined;
|
130
159
|
|
131
160
|
if (attrs) {
|
132
161
|
for (const [ attr, value ] of attrs) {
|
133
162
|
elem.setAttribute(attr, value);
|
134
163
|
}
|
135
164
|
}
|
165
|
+
|
166
|
+
if (styleAttrs) {
|
167
|
+
for (const attr of styleAttrs) {
|
168
|
+
elem.style.setProperty(attr.key, attr.value, attr.priority);
|
169
|
+
}
|
170
|
+
}
|
136
171
|
}
|
137
172
|
}
|
138
173
|
}
|
@@ -15,10 +15,7 @@ export const loadExpectExtensions = () => {
|
|
15
15
|
return {
|
16
16
|
pass,
|
17
17
|
message: () => {
|
18
|
-
|
19
|
-
return `Expected ${expected} not to .eq ${actual}. Options(${eqArgs})`;
|
20
|
-
}
|
21
|
-
return `Expected ${expected} to .eq ${actual}. Options(${eqArgs})`;
|
18
|
+
return `Expected ${pass ? '!' : ''}(${actual}).eq(${expected}). Options(${eqArgs})`;
|
22
19
|
},
|
23
20
|
};
|
24
21
|
},
|
@@ -15,10 +15,11 @@ import { makeLineBuilder } from '../components/builders/LineBuilder';
|
|
15
15
|
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
|
16
16
|
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
17
17
|
import { ActionButtonIcon } from './types';
|
18
|
-
import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon } from './icons';
|
18
|
+
import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon, makeTextIcon } from './icons';
|
19
19
|
import PanZoom, { PanZoomMode } from '../tools/PanZoom';
|
20
20
|
import Mat33 from '../geometry/Mat33';
|
21
21
|
import Viewport from '../Viewport';
|
22
|
+
import TextTool from '../tools/TextTool';
|
22
23
|
|
23
24
|
|
24
25
|
const toolbarCSSPrefix = 'toolbar-';
|
@@ -403,6 +404,60 @@ class HandToolWidget extends ToolbarWidget {
|
|
403
404
|
}
|
404
405
|
}
|
405
406
|
|
407
|
+
class TextToolWidget extends ToolbarWidget {
|
408
|
+
private updateDropdownInputs: (()=>void)|null = null;
|
409
|
+
public constructor(editor: Editor, private tool: TextTool, localization: ToolbarLocalization) {
|
410
|
+
super(editor, tool, localization);
|
411
|
+
|
412
|
+
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
413
|
+
if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
|
414
|
+
this.updateIcon();
|
415
|
+
this.updateDropdownInputs?.();
|
416
|
+
}
|
417
|
+
});
|
418
|
+
}
|
419
|
+
|
420
|
+
protected getTitle(): string {
|
421
|
+
return this.targetTool.description;
|
422
|
+
}
|
423
|
+
|
424
|
+
protected createIcon(): Element {
|
425
|
+
const textStyle = this.tool.getTextStyle();
|
426
|
+
return makeTextIcon(textStyle);
|
427
|
+
}
|
428
|
+
|
429
|
+
private static idCounter: number = 0;
|
430
|
+
protected fillDropdown(dropdown: HTMLElement): boolean {
|
431
|
+
const colorRow = document.createElement('div');
|
432
|
+
const colorInput = document.createElement('input');
|
433
|
+
const colorLabel = document.createElement('label');
|
434
|
+
|
435
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
436
|
+
|
437
|
+
colorInput.classList.add('coloris_input');
|
438
|
+
colorInput.type = 'button';
|
439
|
+
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
440
|
+
colorLabel.setAttribute('for', colorInput.id);
|
441
|
+
|
442
|
+
colorInput.oninput = () => {
|
443
|
+
this.tool.setColor(Color4.fromString(colorInput.value));
|
444
|
+
};
|
445
|
+
|
446
|
+
colorRow.appendChild(colorLabel);
|
447
|
+
colorRow.appendChild(colorInput);
|
448
|
+
|
449
|
+
this.updateDropdownInputs = () => {
|
450
|
+
const style = this.tool.getTextStyle();
|
451
|
+
colorInput.value = style.renderingStyle.fill.toHexString();
|
452
|
+
};
|
453
|
+
this.updateDropdownInputs();
|
454
|
+
|
455
|
+
dropdown.appendChild(colorRow);
|
456
|
+
|
457
|
+
return true;
|
458
|
+
}
|
459
|
+
}
|
460
|
+
|
406
461
|
class PenWidget extends ToolbarWidget {
|
407
462
|
private updateInputs: ()=> void = () => {};
|
408
463
|
|
@@ -702,6 +757,14 @@ export default class HTMLToolbar {
|
|
702
757
|
(new SelectionWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
703
758
|
}
|
704
759
|
|
760
|
+
for (const tool of toolController.getMatchingTools(ToolType.Text)) {
|
761
|
+
if (!(tool instanceof TextTool)) {
|
762
|
+
throw new Error('All text tools must have kind === ToolType.Text');
|
763
|
+
}
|
764
|
+
|
765
|
+
(new TextToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
766
|
+
}
|
767
|
+
|
705
768
|
for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
|
706
769
|
if (!(tool instanceof PanZoom)) {
|
707
770
|
throw new Error('All SelectionTools must have kind === ToolType.PanZoom');
|
package/src/toolbar/icons.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
2
|
+
import { TextStyle } from '../components/Text';
|
2
3
|
import EventDispatcher from '../EventDispatcher';
|
3
4
|
import { Vec2 } from '../geometry/Vec2';
|
4
5
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
@@ -126,6 +127,28 @@ export const makeHandToolIcon = () => {
|
|
126
127
|
return icon;
|
127
128
|
};
|
128
129
|
|
130
|
+
export const makeTextIcon = (textStyle: TextStyle) => {
|
131
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
132
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
133
|
+
|
134
|
+
const textNode = document.createElementNS(svgNamespace, 'text');
|
135
|
+
textNode.appendChild(document.createTextNode('T'));
|
136
|
+
|
137
|
+
textNode.style.fontFamily = textStyle.fontFamily;
|
138
|
+
textNode.style.fontWeight = textStyle.fontWeight ?? '';
|
139
|
+
textNode.style.fontVariant = textStyle.fontVariant ?? '';
|
140
|
+
textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
|
141
|
+
|
142
|
+
textNode.style.textAnchor = 'middle';
|
143
|
+
textNode.setAttribute('x', '50');
|
144
|
+
textNode.setAttribute('y', '75');
|
145
|
+
textNode.style.fontSize = '65px';
|
146
|
+
|
147
|
+
icon.appendChild(textNode);
|
148
|
+
|
149
|
+
return icon;
|
150
|
+
};
|
151
|
+
|
129
152
|
export const makePenIcon = (tipThickness: number, color: string) => {
|
130
153
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
131
154
|
icon.setAttribute('viewBox', '0 0 100 100');
|
@@ -494,7 +494,7 @@ export default class SelectionTool extends BaseTool {
|
|
494
494
|
);
|
495
495
|
|
496
496
|
const selectionRect = this.selectionBox.region;
|
497
|
-
this.editor.viewport.zoomTo(selectionRect).apply(this.editor);
|
497
|
+
this.editor.viewport.zoomTo(selectionRect, false).apply(this.editor);
|
498
498
|
}
|
499
499
|
}
|
500
500
|
|
@@ -0,0 +1,206 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import Text, { TextStyle } from '../components/Text';
|
3
|
+
import Editor from '../Editor';
|
4
|
+
import EditorImage from '../EditorImage';
|
5
|
+
import Mat33 from '../geometry/Mat33';
|
6
|
+
import { Vec2 } from '../geometry/Vec2';
|
7
|
+
import { PointerDevice } from '../Pointer';
|
8
|
+
import { EditorEventType, PointerEvt } from '../types';
|
9
|
+
import BaseTool from './BaseTool';
|
10
|
+
import { ToolLocalization } from './localization';
|
11
|
+
import { ToolType } from './ToolController';
|
12
|
+
|
13
|
+
const overlayCssClass = 'textEditorOverlay';
|
14
|
+
export default class TextTool extends BaseTool {
|
15
|
+
public kind: ToolType = ToolType.Text;
|
16
|
+
private textStyle: TextStyle;
|
17
|
+
|
18
|
+
private textEditOverlay: HTMLElement;
|
19
|
+
private textInputElem: HTMLInputElement|null = null;
|
20
|
+
private textTargetPosition: Vec2|null = null;
|
21
|
+
private textMeasuringCtx: CanvasRenderingContext2D|null = null;
|
22
|
+
|
23
|
+
public constructor(private editor: Editor, description: string, private localizationTable: ToolLocalization) {
|
24
|
+
super(editor.notifier, description);
|
25
|
+
this.textStyle = {
|
26
|
+
size: 32,
|
27
|
+
fontFamily: 'sans-serif',
|
28
|
+
renderingStyle: {
|
29
|
+
fill: Color4.purple,
|
30
|
+
},
|
31
|
+
};
|
32
|
+
|
33
|
+
this.textEditOverlay = document.createElement('div');
|
34
|
+
this.textEditOverlay.classList.add(overlayCssClass);
|
35
|
+
this.editor.addStyleSheet(`
|
36
|
+
.${overlayCssClass} {
|
37
|
+
height: 0;
|
38
|
+
overflow: visible;
|
39
|
+
}
|
40
|
+
|
41
|
+
.${overlayCssClass} input {
|
42
|
+
background-color: rgba(0, 0, 0, 0);
|
43
|
+
border: none;
|
44
|
+
padding: 0;
|
45
|
+
}
|
46
|
+
`);
|
47
|
+
this.editor.createHTMLOverlay(this.textEditOverlay);
|
48
|
+
this.editor.notifier.on(EditorEventType.ViewportChanged, () => this.updateTextInput());
|
49
|
+
}
|
50
|
+
|
51
|
+
private getTextAscent(text: string, style: TextStyle): number {
|
52
|
+
this.textMeasuringCtx ??= document.createElement('canvas').getContext('2d');
|
53
|
+
if (this.textMeasuringCtx) {
|
54
|
+
Text.applyTextStyles(this.textMeasuringCtx, style);
|
55
|
+
return this.textMeasuringCtx.measureText(text).actualBoundingBoxAscent;
|
56
|
+
}
|
57
|
+
|
58
|
+
// Estimate
|
59
|
+
return style.size * 2 / 3;
|
60
|
+
}
|
61
|
+
|
62
|
+
private flushInput() {
|
63
|
+
if (this.textInputElem && this.textTargetPosition) {
|
64
|
+
const content = this.textInputElem.value;
|
65
|
+
this.textInputElem.remove();
|
66
|
+
this.textInputElem = null;
|
67
|
+
|
68
|
+
if (content === '') {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
|
72
|
+
const textTransform = Mat33.translation(
|
73
|
+
this.textTargetPosition
|
74
|
+
).rightMul(
|
75
|
+
Mat33.scaling2D(this.editor.viewport.getSizeOfPixelOnCanvas())
|
76
|
+
);
|
77
|
+
|
78
|
+
const textComponent = new Text(
|
79
|
+
[ content ],
|
80
|
+
textTransform,
|
81
|
+
this.textStyle,
|
82
|
+
);
|
83
|
+
|
84
|
+
const action = new EditorImage.AddElementCommand(textComponent);
|
85
|
+
this.editor.dispatch(action);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
private updateTextInput() {
|
90
|
+
if (!this.textInputElem || !this.textTargetPosition) {
|
91
|
+
this.textInputElem?.remove();
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
|
95
|
+
const textScreenPos = this.editor.viewport.canvasToScreen(this.textTargetPosition);
|
96
|
+
this.textInputElem.type = 'text';
|
97
|
+
this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
|
98
|
+
this.textInputElem.style.fontFamily = this.textStyle.fontFamily;
|
99
|
+
this.textInputElem.style.fontVariant = this.textStyle.fontVariant ?? '';
|
100
|
+
this.textInputElem.style.fontWeight = this.textStyle.fontWeight ?? '';
|
101
|
+
this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
|
102
|
+
this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
|
103
|
+
|
104
|
+
this.textInputElem.style.position = 'relative';
|
105
|
+
this.textInputElem.style.left = `${textScreenPos.x}px`;
|
106
|
+
const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
|
107
|
+
this.textInputElem.style.top = `${textScreenPos.y - ascent}px`;
|
108
|
+
}
|
109
|
+
|
110
|
+
private startTextInput(textCanvasPos: Vec2, initialText: string) {
|
111
|
+
this.flushInput();
|
112
|
+
|
113
|
+
this.textInputElem = document.createElement('input');
|
114
|
+
this.textInputElem.value = initialText;
|
115
|
+
this.textTargetPosition = textCanvasPos;
|
116
|
+
this.updateTextInput();
|
117
|
+
|
118
|
+
this.textInputElem.oninput = () => {
|
119
|
+
if (this.textInputElem) {
|
120
|
+
this.textInputElem.size = this.textInputElem?.value.length || 10;
|
121
|
+
}
|
122
|
+
};
|
123
|
+
this.textInputElem.onblur = () => {
|
124
|
+
this.flushInput();
|
125
|
+
};
|
126
|
+
this.textInputElem.onkeyup = (evt) => {
|
127
|
+
if (evt.key === 'Enter') {
|
128
|
+
this.flushInput();
|
129
|
+
}
|
130
|
+
};
|
131
|
+
|
132
|
+
this.textEditOverlay.replaceChildren(this.textInputElem);
|
133
|
+
setTimeout(() => this.textInputElem!.focus(), 100);
|
134
|
+
}
|
135
|
+
|
136
|
+
public setEnabled(enabled: boolean) {
|
137
|
+
super.setEnabled(enabled);
|
138
|
+
|
139
|
+
if (!enabled) {
|
140
|
+
this.flushInput();
|
141
|
+
}
|
142
|
+
|
143
|
+
this.textEditOverlay.style.display = enabled ? 'block' : 'none';
|
144
|
+
}
|
145
|
+
|
146
|
+
public onPointerDown({ current, allPointers }: PointerEvt): boolean {
|
147
|
+
if (current.device === PointerDevice.Eraser) {
|
148
|
+
return false;
|
149
|
+
}
|
150
|
+
|
151
|
+
if (allPointers.length === 1) {
|
152
|
+
this.startTextInput(current.canvasPos, '');
|
153
|
+
return true;
|
154
|
+
}
|
155
|
+
|
156
|
+
return false;
|
157
|
+
}
|
158
|
+
|
159
|
+
private dispatchUpdateEvent() {
|
160
|
+
this.updateTextInput();
|
161
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
162
|
+
kind: EditorEventType.ToolUpdated,
|
163
|
+
tool: this,
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
public setFontFamily(fontFamily: string) {
|
168
|
+
if (fontFamily !== this.textStyle.fontFamily) {
|
169
|
+
this.textStyle = {
|
170
|
+
...this.textStyle,
|
171
|
+
fontFamily: fontFamily,
|
172
|
+
};
|
173
|
+
|
174
|
+
this.dispatchUpdateEvent();
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
public setColor(color: Color4) {
|
179
|
+
if (!color.eq(this.textStyle.renderingStyle.fill)) {
|
180
|
+
this.textStyle = {
|
181
|
+
...this.textStyle,
|
182
|
+
renderingStyle: {
|
183
|
+
...this.textStyle.renderingStyle,
|
184
|
+
fill: color,
|
185
|
+
},
|
186
|
+
};
|
187
|
+
|
188
|
+
this.dispatchUpdateEvent();
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
public setFontSize(size: number) {
|
193
|
+
if (size !== this.textStyle.size) {
|
194
|
+
this.textStyle = {
|
195
|
+
...this.textStyle,
|
196
|
+
size,
|
197
|
+
};
|
198
|
+
|
199
|
+
this.dispatchUpdateEvent();
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
public getTextStyle(): TextStyle {
|
204
|
+
return this.textStyle;
|
205
|
+
}
|
206
|
+
}
|
@@ -9,12 +9,14 @@ import SelectionTool from './SelectionTool';
|
|
9
9
|
import Color4 from '../Color4';
|
10
10
|
import { ToolLocalization } from './localization';
|
11
11
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
12
|
+
import TextTool from './TextTool';
|
12
13
|
|
13
14
|
export enum ToolType {
|
14
15
|
Pen,
|
15
16
|
Selection,
|
16
17
|
Eraser,
|
17
18
|
PanZoom,
|
19
|
+
Text,
|
18
20
|
UndoRedoShortcut,
|
19
21
|
}
|
20
22
|
|
@@ -36,6 +38,8 @@ export default class ToolController {
|
|
36
38
|
|
37
39
|
// Highlighter-like pen with width=64
|
38
40
|
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
41
|
+
|
42
|
+
new TextTool(editor, localization.textTool, localization),
|
39
43
|
];
|
40
44
|
this.tools = [
|
41
45
|
panZoomTool,
|
@@ -1,12 +1,14 @@
|
|
1
1
|
|
2
2
|
export interface ToolLocalization {
|
3
|
-
|
3
|
+
rightClickDragPanTool: string;
|
4
4
|
penTool: (penId: number)=>string;
|
5
5
|
selectionTool: string;
|
6
6
|
eraserTool: string;
|
7
7
|
touchPanTool: string;
|
8
8
|
twoFingerPanZoomTool: string;
|
9
9
|
undoRedoTool: string;
|
10
|
+
textTool: string;
|
11
|
+
enterTextToInsert: string;
|
10
12
|
|
11
13
|
toolEnabledAnnouncement: (toolName: string) => string;
|
12
14
|
toolDisabledAnnouncement: (toolName: string) => string;
|
@@ -19,7 +21,10 @@ export const defaultToolLocalization: ToolLocalization = {
|
|
19
21
|
touchPanTool: 'Touch Panning',
|
20
22
|
twoFingerPanZoomTool: 'Panning and Zooming',
|
21
23
|
undoRedoTool: 'Undo/Redo',
|
22
|
-
|
24
|
+
rightClickDragPanTool: 'Right-click drag',
|
25
|
+
|
26
|
+
textTool: 'Text',
|
27
|
+
enterTextToInsert: 'Text to insert',
|
23
28
|
|
24
29
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
25
30
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|