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.
Files changed (60) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +2 -2
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.js +2 -3
  5. package/dist/src/SVGLoader.d.ts +8 -0
  6. package/dist/src/SVGLoader.js +105 -6
  7. package/dist/src/Viewport.d.ts +1 -1
  8. package/dist/src/Viewport.js +2 -2
  9. package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
  10. package/dist/src/components/Text.d.ts +30 -0
  11. package/dist/src/components/Text.js +109 -0
  12. package/dist/src/components/localization.d.ts +1 -0
  13. package/dist/src/components/localization.js +1 -0
  14. package/dist/src/geometry/Mat33.d.ts +1 -0
  15. package/dist/src/geometry/Mat33.js +30 -0
  16. package/dist/src/geometry/Path.js +8 -1
  17. package/dist/src/geometry/Rect2.d.ts +2 -0
  18. package/dist/src/geometry/Rect2.js +6 -0
  19. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +5 -0
  20. package/dist/src/rendering/renderers/AbstractRenderer.js +12 -0
  21. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
  22. package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
  23. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
  24. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  25. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -0
  26. package/dist/src/rendering/renderers/SVGRenderer.js +30 -1
  27. package/dist/src/testing/loadExpectExtensions.js +1 -4
  28. package/dist/src/toolbar/HTMLToolbar.js +52 -1
  29. package/dist/src/toolbar/icons.d.ts +2 -0
  30. package/dist/src/toolbar/icons.js +17 -0
  31. package/dist/src/tools/SelectionTool.js +1 -1
  32. package/dist/src/tools/TextTool.d.ts +29 -0
  33. package/dist/src/tools/TextTool.js +154 -0
  34. package/dist/src/tools/ToolController.d.ts +2 -1
  35. package/dist/src/tools/ToolController.js +4 -1
  36. package/dist/src/tools/localization.d.ts +3 -1
  37. package/dist/src/tools/localization.js +3 -1
  38. package/package.json +1 -1
  39. package/src/Editor.ts +3 -3
  40. package/src/SVGLoader.ts +124 -6
  41. package/src/Viewport.ts +2 -2
  42. package/src/components/SVGGlobalAttributesObject.ts +0 -1
  43. package/src/components/Text.ts +136 -0
  44. package/src/components/localization.ts +2 -0
  45. package/src/geometry/Mat33.test.ts +44 -0
  46. package/src/geometry/Mat33.ts +41 -0
  47. package/src/geometry/Path.toString.test.ts +7 -3
  48. package/src/geometry/Path.ts +11 -1
  49. package/src/geometry/Rect2.ts +8 -0
  50. package/src/rendering/renderers/AbstractRenderer.ts +16 -0
  51. package/src/rendering/renderers/CanvasRenderer.ts +34 -10
  52. package/src/rendering/renderers/DummyRenderer.ts +8 -0
  53. package/src/rendering/renderers/SVGRenderer.ts +36 -1
  54. package/src/testing/loadExpectExtensions.ts +1 -4
  55. package/src/toolbar/HTMLToolbar.ts +64 -1
  56. package/src/toolbar/icons.ts +23 -0
  57. package/src/tools/SelectionTool.ts +1 -1
  58. package/src/tools/TextTool.ts +206 -0
  59. package/src/tools/ToolController.ts +4 -0
  60. 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
- if (pass) {
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');
@@ -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
- RightClickDragPanTool: string;
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
- RightClickDragPanTool: 'Right-click drag',
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`,