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,3 +1,4 @@
1
+ import { TextStyle } from '../../components/Text';
1
2
  import Mat33 from '../../geometry/Mat33';
2
3
  import Rect2 from '../../geometry/Rect2';
3
4
  import { Point2, Vec2 } from '../../geometry/Vec2';
@@ -10,6 +11,7 @@ export default class DummyRenderer extends AbstractRenderer {
10
11
  lastFillStyle: RenderingStyle | null;
11
12
  lastPoint: Point2 | null;
12
13
  objectNestingLevel: number;
14
+ lastText: string | null;
13
15
  pointBuffer: Point2[];
14
16
  constructor(viewport: Viewport);
15
17
  displaySize(): Vec2;
@@ -21,6 +23,7 @@ export default class DummyRenderer extends AbstractRenderer {
21
23
  protected traceCubicBezierCurve(p1: Vec3, p2: Vec3, p3: Vec3): void;
22
24
  protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
23
25
  drawPoints(..._points: Vec3[]): void;
26
+ drawText(text: string, _transform: Mat33, _style: TextStyle): void;
24
27
  startObject(boundingBox: Rect2, _clip: boolean): void;
25
28
  endObject(): void;
26
29
  isTooSmallToRender(_rect: Rect2): boolean;
@@ -10,6 +10,7 @@ export default class DummyRenderer extends AbstractRenderer {
10
10
  this.lastFillStyle = null;
11
11
  this.lastPoint = null;
12
12
  this.objectNestingLevel = 0;
13
+ this.lastText = null;
13
14
  // List of points drawn since the last clear.
14
15
  this.pointBuffer = [];
15
16
  }
@@ -28,6 +29,7 @@ export default class DummyRenderer extends AbstractRenderer {
28
29
  this.clearedCount++;
29
30
  this.renderedPathCount = 0;
30
31
  this.pointBuffer = [];
32
+ this.lastText = null;
31
33
  // Ensure all objects finished rendering
32
34
  if (this.objectNestingLevel > 0) {
33
35
  throw new Error(`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`);
@@ -68,6 +70,9 @@ export default class DummyRenderer extends AbstractRenderer {
68
70
  // drawPoints is intended for debugging.
69
71
  // As such, it is unlikely to be the target of automated tests.
70
72
  }
73
+ drawText(text, _transform, _style) {
74
+ this.lastText = text;
75
+ }
71
76
  startObject(boundingBox, _clip) {
72
77
  super.startObject(boundingBox);
73
78
  this.objectNestingLevel += 1;
@@ -1,4 +1,6 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
+ import { TextStyle } from '../../components/Text';
3
+ import Mat33 from '../../geometry/Mat33';
2
4
  import Rect2 from '../../geometry/Rect2';
3
5
  import { Point2, Vec2 } from '../../geometry/Vec2';
4
6
  import Viewport from '../../Viewport';
@@ -19,6 +21,7 @@ export default class SVGRenderer extends AbstractRenderer {
19
21
  protected beginPath(startPoint: Point2): void;
20
22
  protected endPath(style: RenderingStyle): void;
21
23
  private addPathToSVG;
24
+ drawText(text: string, transform: Mat33, style: TextStyle): void;
22
25
  startObject(boundingBox: Rect2): void;
23
26
  endObject(loaderData?: LoadSaveDataTable): void;
24
27
  protected lineTo(point: Point2): void;
@@ -1,6 +1,6 @@
1
1
  import Path, { PathCommandType } from '../../geometry/Path';
2
2
  import { Vec2 } from '../../geometry/Vec2';
3
- import { svgAttributesDataKey } from '../../SVGLoader';
3
+ import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
4
4
  import AbstractRenderer from './AbstractRenderer';
5
5
  const svgNameSpace = 'http://www.w3.org/2000/svg';
6
6
  export default class SVGRenderer extends AbstractRenderer {
@@ -86,6 +86,29 @@ export default class SVGRenderer extends AbstractRenderer {
86
86
  this.elem.appendChild(pathElem);
87
87
  (_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
88
88
  }
89
+ drawText(text, transform, style) {
90
+ var _a, _b, _c;
91
+ transform = this.getCanvasToScreenTransform().rightMul(transform);
92
+ const textElem = document.createElementNS(svgNameSpace, 'text');
93
+ textElem.appendChild(document.createTextNode(text));
94
+ textElem.style.transform = `matrix(
95
+ ${transform.a1}, ${transform.b1},
96
+ ${transform.a2}, ${transform.b2},
97
+ ${transform.a3}, ${transform.b3}
98
+ )`;
99
+ textElem.style.fontFamily = style.fontFamily;
100
+ textElem.style.fontVariant = (_a = style.fontVariant) !== null && _a !== void 0 ? _a : '';
101
+ textElem.style.fontWeight = (_b = style.fontWeight) !== null && _b !== void 0 ? _b : '';
102
+ textElem.style.fontSize = style.size + 'px';
103
+ textElem.style.fill = style.renderingStyle.fill.toHexString();
104
+ if (style.renderingStyle.stroke) {
105
+ const strokeStyle = style.renderingStyle.stroke;
106
+ textElem.style.stroke = strokeStyle.color.toHexString();
107
+ textElem.style.strokeWidth = strokeStyle.width + 'px';
108
+ }
109
+ this.elem.appendChild(textElem);
110
+ (_c = this.objectElems) === null || _c === void 0 ? void 0 : _c.push(textElem);
111
+ }
89
112
  startObject(boundingBox) {
90
113
  super.startObject(boundingBox);
91
114
  // Only accumulate a path within an object
@@ -103,11 +126,17 @@ export default class SVGRenderer extends AbstractRenderer {
103
126
  // Restore any attributes unsupported by the app.
104
127
  for (const elem of (_a = this.objectElems) !== null && _a !== void 0 ? _a : []) {
105
128
  const attrs = loaderData[svgAttributesDataKey];
129
+ const styleAttrs = loaderData[svgStyleAttributesDataKey];
106
130
  if (attrs) {
107
131
  for (const [attr, value] of attrs) {
108
132
  elem.setAttribute(attr, value);
109
133
  }
110
134
  }
135
+ if (styleAttrs) {
136
+ for (const attr of styleAttrs) {
137
+ elem.style.setProperty(attr.key, attr.value, attr.priority);
138
+ }
139
+ }
111
140
  }
112
141
  }
113
142
  }
@@ -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
  },
@@ -10,10 +10,11 @@ import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
10
10
  import { makeLineBuilder } from '../components/builders/LineBuilder';
11
11
  import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
12
12
  import { defaultToolbarLocalization } from './localization';
13
- import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon } from './icons';
13
+ import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon, makeTextIcon } from './icons';
14
14
  import PanZoom, { PanZoomMode } from '../tools/PanZoom';
15
15
  import Mat33 from '../geometry/Mat33';
16
16
  import Viewport from '../Viewport';
17
+ import TextTool from '../tools/TextTool';
17
18
  const toolbarCSSPrefix = 'toolbar-';
18
19
  class ToolbarWidget {
19
20
  constructor(editor, targetTool, localizationTable) {
@@ -316,6 +317,50 @@ class HandToolWidget extends ToolbarWidget {
316
317
  this.setDropdownVisible(!this.isDropdownVisible());
317
318
  }
318
319
  }
320
+ class TextToolWidget extends ToolbarWidget {
321
+ constructor(editor, tool, localization) {
322
+ super(editor, tool, localization);
323
+ this.tool = tool;
324
+ this.updateDropdownInputs = null;
325
+ editor.notifier.on(EditorEventType.ToolUpdated, evt => {
326
+ var _a;
327
+ if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
328
+ this.updateIcon();
329
+ (_a = this.updateDropdownInputs) === null || _a === void 0 ? void 0 : _a.call(this);
330
+ }
331
+ });
332
+ }
333
+ getTitle() {
334
+ return this.targetTool.description;
335
+ }
336
+ createIcon() {
337
+ const textStyle = this.tool.getTextStyle();
338
+ return makeTextIcon(textStyle);
339
+ }
340
+ fillDropdown(dropdown) {
341
+ const colorRow = document.createElement('div');
342
+ const colorInput = document.createElement('input');
343
+ const colorLabel = document.createElement('label');
344
+ colorLabel.innerText = this.localizationTable.colorLabel;
345
+ colorInput.classList.add('coloris_input');
346
+ colorInput.type = 'button';
347
+ colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
348
+ colorLabel.setAttribute('for', colorInput.id);
349
+ colorInput.oninput = () => {
350
+ this.tool.setColor(Color4.fromString(colorInput.value));
351
+ };
352
+ colorRow.appendChild(colorLabel);
353
+ colorRow.appendChild(colorInput);
354
+ this.updateDropdownInputs = () => {
355
+ const style = this.tool.getTextStyle();
356
+ colorInput.value = style.renderingStyle.fill.toHexString();
357
+ };
358
+ this.updateDropdownInputs();
359
+ dropdown.appendChild(colorRow);
360
+ return true;
361
+ }
362
+ }
363
+ TextToolWidget.idCounter = 0;
319
364
  class PenWidget extends ToolbarWidget {
320
365
  constructor(editor, tool, localization, penTypes) {
321
366
  super(editor, tool, localization);
@@ -559,6 +604,12 @@ export default class HTMLToolbar {
559
604
  }
560
605
  (new SelectionWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
561
606
  }
607
+ for (const tool of toolController.getMatchingTools(ToolType.Text)) {
608
+ if (!(tool instanceof TextTool)) {
609
+ throw new Error('All text tools must have kind === ToolType.Text');
610
+ }
611
+ (new TextToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
612
+ }
562
613
  for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
563
614
  if (!(tool instanceof PanZoom)) {
564
615
  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 Pen from '../tools/Pen';
3
4
  export declare const makeUndoIcon: () => SVGSVGElement;
4
5
  export declare const makeRedoIcon: (mirror?: boolean) => SVGSVGElement;
@@ -6,5 +7,6 @@ export declare const makeDropdownIcon: () => SVGSVGElement;
6
7
  export declare const makeEraserIcon: () => SVGSVGElement;
7
8
  export declare const makeSelectionIcon: () => SVGSVGElement;
8
9
  export declare const makeHandToolIcon: () => SVGSVGElement;
10
+ export declare const makeTextIcon: (textStyle: TextStyle) => SVGSVGElement;
9
11
  export declare const makePenIcon: (tipThickness: number, color: string) => SVGSVGElement;
10
12
  export declare const makeIconFromFactory: (pen: Pen, factory: ComponentBuilderFactory) => SVGSVGElement;
@@ -111,6 +111,23 @@ export const makeHandToolIcon = () => {
111
111
  icon.setAttribute('viewBox', '0 0 100 100');
112
112
  return icon;
113
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
+ icon.appendChild(textNode);
129
+ return icon;
130
+ };
114
131
  export const makePenIcon = (tipThickness, color) => {
115
132
  const icon = document.createElementNS(svgNamespace, 'svg');
116
133
  icon.setAttribute('viewBox', '0 0 100 100');
@@ -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,29 @@
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
+ private dispatchUpdateEvent;
25
+ setFontFamily(fontFamily: string): void;
26
+ setColor(color: Color4): void;
27
+ setFontSize(size: number): void;
28
+ getTextStyle(): TextStyle;
29
+ }
@@ -0,0 +1,154 @@
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 textScreenPos = this.editor.viewport.canvasToScreen(this.textTargetPosition);
74
+ this.textInputElem.type = 'text';
75
+ this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
76
+ this.textInputElem.style.fontFamily = this.textStyle.fontFamily;
77
+ this.textInputElem.style.fontVariant = (_b = this.textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
78
+ this.textInputElem.style.fontWeight = (_c = this.textStyle.fontWeight) !== null && _c !== void 0 ? _c : '';
79
+ this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
80
+ this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
81
+ this.textInputElem.style.position = 'relative';
82
+ this.textInputElem.style.left = `${textScreenPos.x}px`;
83
+ const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
84
+ this.textInputElem.style.top = `${textScreenPos.y - ascent}px`;
85
+ }
86
+ startTextInput(textCanvasPos, initialText) {
87
+ this.flushInput();
88
+ this.textInputElem = document.createElement('input');
89
+ this.textInputElem.value = initialText;
90
+ this.textTargetPosition = textCanvasPos;
91
+ this.updateTextInput();
92
+ this.textInputElem.oninput = () => {
93
+ var _a;
94
+ if (this.textInputElem) {
95
+ this.textInputElem.size = ((_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.value.length) || 10;
96
+ }
97
+ };
98
+ this.textInputElem.onblur = () => {
99
+ this.flushInput();
100
+ };
101
+ this.textInputElem.onkeyup = (evt) => {
102
+ if (evt.key === 'Enter') {
103
+ this.flushInput();
104
+ }
105
+ };
106
+ this.textEditOverlay.replaceChildren(this.textInputElem);
107
+ setTimeout(() => this.textInputElem.focus(), 100);
108
+ }
109
+ setEnabled(enabled) {
110
+ super.setEnabled(enabled);
111
+ if (!enabled) {
112
+ this.flushInput();
113
+ }
114
+ this.textEditOverlay.style.display = enabled ? 'block' : 'none';
115
+ }
116
+ onPointerDown({ current, allPointers }) {
117
+ if (current.device === PointerDevice.Eraser) {
118
+ return false;
119
+ }
120
+ if (allPointers.length === 1) {
121
+ this.startTextInput(current.canvasPos, '');
122
+ return true;
123
+ }
124
+ return false;
125
+ }
126
+ dispatchUpdateEvent() {
127
+ this.updateTextInput();
128
+ this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
129
+ kind: EditorEventType.ToolUpdated,
130
+ tool: this,
131
+ });
132
+ }
133
+ setFontFamily(fontFamily) {
134
+ if (fontFamily !== this.textStyle.fontFamily) {
135
+ this.textStyle = Object.assign(Object.assign({}, this.textStyle), { fontFamily: fontFamily });
136
+ this.dispatchUpdateEvent();
137
+ }
138
+ }
139
+ setColor(color) {
140
+ if (!color.eq(this.textStyle.renderingStyle.fill)) {
141
+ this.textStyle = Object.assign(Object.assign({}, this.textStyle), { renderingStyle: Object.assign(Object.assign({}, this.textStyle.renderingStyle), { fill: color }) });
142
+ this.dispatchUpdateEvent();
143
+ }
144
+ }
145
+ setFontSize(size) {
146
+ if (size !== this.textStyle.size) {
147
+ this.textStyle = Object.assign(Object.assign({}, this.textStyle), { size });
148
+ this.dispatchUpdateEvent();
149
+ }
150
+ }
151
+ getTextStyle() {
152
+ return this.textStyle;
153
+ }
154
+ }
@@ -7,7 +7,8 @@ export declare enum ToolType {
7
7
  Selection = 1,
8
8
  Eraser = 2,
9
9
  PanZoom = 3,
10
- UndoRedoShortcut = 4
10
+ Text = 4,
11
+ UndoRedoShortcut = 5
11
12
  }
12
13
  export default class ToolController {
13
14
  private tools;
@@ -6,13 +6,15 @@ import Eraser from './Eraser';
6
6
  import SelectionTool from './SelectionTool';
7
7
  import Color4 from '../Color4';
8
8
  import UndoRedoShortcut from './UndoRedoShortcut';
9
+ import TextTool from './TextTool';
9
10
  export var ToolType;
10
11
  (function (ToolType) {
11
12
  ToolType[ToolType["Pen"] = 0] = "Pen";
12
13
  ToolType[ToolType["Selection"] = 1] = "Selection";
13
14
  ToolType[ToolType["Eraser"] = 2] = "Eraser";
14
15
  ToolType[ToolType["PanZoom"] = 3] = "PanZoom";
15
- ToolType[ToolType["UndoRedoShortcut"] = 4] = "UndoRedoShortcut";
16
+ ToolType[ToolType["Text"] = 4] = "Text";
17
+ ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
16
18
  })(ToolType || (ToolType = {}));
17
19
  export default class ToolController {
18
20
  constructor(editor, localization) {
@@ -27,6 +29,7 @@ export default class ToolController {
27
29
  new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 8 }),
28
30
  // Highlighter-like pen with width=64
29
31
  new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
32
+ new TextTool(editor, localization.textTool, localization),
30
33
  ];
31
34
  this.tools = [
32
35
  panZoomTool,
@@ -1,11 +1,13 @@
1
1
  export interface ToolLocalization {
2
- RightClickDragPanTool: string;
2
+ rightClickDragPanTool: string;
3
3
  penTool: (penId: number) => string;
4
4
  selectionTool: string;
5
5
  eraserTool: string;
6
6
  touchPanTool: string;
7
7
  twoFingerPanZoomTool: string;
8
8
  undoRedoTool: string;
9
+ textTool: string;
10
+ enterTextToInsert: string;
9
11
  toolEnabledAnnouncement: (toolName: string) => string;
10
12
  toolDisabledAnnouncement: (toolName: string) => string;
11
13
  }
@@ -5,7 +5,9 @@ export const defaultToolLocalization = {
5
5
  touchPanTool: 'Touch Panning',
6
6
  twoFingerPanZoomTool: 'Panning and Zooming',
7
7
  undoRedoTool: 'Undo/Redo',
8
- RightClickDragPanTool: 'Right-click drag',
8
+ rightClickDragPanTool: 'Right-click drag',
9
+ textTool: 'Text',
10
+ enterTextToInsert: 'Text to insert',
9
11
  toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
10
12
  toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
11
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "dist/src/Editor.js",
6
6
  "types": "dist/src/Editor.d.ts",
package/src/Editor.ts CHANGED
@@ -374,9 +374,11 @@ export class Editor {
374
374
  // Draw a rectangle around the region that will be visible on save
375
375
  const renderer = this.display.getDryInkRenderer();
376
376
 
377
+ this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
378
+
377
379
  if (showImageBounds) {
378
380
  const exportRectFill = { fill: Color4.fromHex('#44444455') };
379
- const exportRectStrokeWidth = 12;
381
+ const exportRectStrokeWidth = 5 * this.viewport.getSizeOfPixelOnCanvas();
380
382
  renderer.drawRect(
381
383
  this.importExportViewport.visibleRect,
382
384
  exportRectStrokeWidth,
@@ -384,8 +386,6 @@ export class Editor {
384
386
  );
385
387
  }
386
388
 
387
- //this.image.render(renderer, this.viewport);
388
- this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
389
389
  this.rerenderQueued = false;
390
390
  }
391
391