js-draw 0.11.3 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Color4.d.ts +13 -0
  4. package/dist/src/Color4.js +17 -0
  5. package/dist/src/Editor.d.ts +33 -18
  6. package/dist/src/Editor.js +26 -24
  7. package/dist/src/EditorImage.d.ts +12 -0
  8. package/dist/src/EditorImage.js +12 -0
  9. package/dist/src/Pointer.d.ts +1 -0
  10. package/dist/src/Pointer.js +8 -0
  11. package/dist/src/SVGLoader.d.ts +5 -0
  12. package/dist/src/SVGLoader.js +49 -36
  13. package/dist/src/Viewport.d.ts +30 -1
  14. package/dist/src/Viewport.js +39 -9
  15. package/dist/src/commands/invertCommand.js +1 -1
  16. package/dist/src/components/AbstractComponent.d.ts +20 -0
  17. package/dist/src/components/AbstractComponent.js +32 -2
  18. package/dist/src/lib.d.ts +6 -3
  19. package/dist/src/lib.js +4 -1
  20. package/dist/src/math/Mat33.d.ts +1 -1
  21. package/dist/src/math/Mat33.js +1 -1
  22. package/dist/src/rendering/Display.d.ts +9 -11
  23. package/dist/src/rendering/Display.js +12 -14
  24. package/dist/src/rendering/lib.d.ts +3 -0
  25. package/dist/src/rendering/lib.js +3 -0
  26. package/dist/src/rendering/renderers/DummyRenderer.js +2 -2
  27. package/dist/src/rendering/renderers/SVGRenderer.js +4 -0
  28. package/dist/src/toolbar/HTMLToolbar.d.ts +51 -0
  29. package/dist/src/toolbar/HTMLToolbar.js +63 -5
  30. package/dist/src/toolbar/IconProvider.d.ts +2 -2
  31. package/dist/src/toolbar/IconProvider.js +123 -35
  32. package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +8 -1
  33. package/dist/src/toolbar/widgets/EraserToolWidget.js +45 -4
  34. package/dist/src/toolbar/widgets/PenToolWidget.js +2 -2
  35. package/dist/src/toolbar/widgets/SelectionToolWidget.js +12 -3
  36. package/dist/src/tools/Eraser.d.ts +10 -1
  37. package/dist/src/tools/Eraser.js +65 -13
  38. package/dist/src/tools/PanZoom.js +1 -1
  39. package/dist/src/tools/PasteHandler.d.ts +11 -4
  40. package/dist/src/tools/PasteHandler.js +12 -5
  41. package/dist/src/tools/Pen.d.ts +7 -2
  42. package/dist/src/tools/Pen.js +39 -6
  43. package/dist/src/tools/SelectionTool/Selection.d.ts +4 -1
  44. package/dist/src/tools/SelectionTool/Selection.js +64 -27
  45. package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -0
  46. package/dist/src/tools/SelectionTool/SelectionHandle.js +6 -0
  47. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -1
  48. package/dist/src/tools/SelectionTool/SelectionTool.js +56 -16
  49. package/dist/src/tools/TextTool.js +10 -6
  50. package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
  51. package/dist/src/tools/ToolSwitcherShortcut.js +9 -3
  52. package/dist/src/tools/UndoRedoShortcut.js +2 -4
  53. package/dist/src/types.d.ts +2 -2
  54. package/package.json +2 -2
  55. package/src/Color4.test.ts +11 -0
  56. package/src/Color4.ts +23 -0
  57. package/src/Editor.ts +39 -26
  58. package/src/EditorImage.ts +12 -0
  59. package/src/Pointer.ts +19 -0
  60. package/src/SVGLoader.ts +20 -15
  61. package/src/Viewport.ts +50 -11
  62. package/src/commands/invertCommand.ts +1 -1
  63. package/src/components/AbstractComponent.ts +52 -2
  64. package/src/lib.ts +6 -3
  65. package/src/math/Mat33.ts +1 -1
  66. package/src/rendering/Display.ts +12 -15
  67. package/src/rendering/RenderingStyle.ts +1 -1
  68. package/src/rendering/lib.ts +4 -0
  69. package/src/rendering/renderers/DummyRenderer.ts +2 -3
  70. package/src/rendering/renderers/SVGRenderer.ts +4 -0
  71. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -1
  72. package/src/toolbar/HTMLToolbar.ts +81 -5
  73. package/src/toolbar/IconProvider.ts +132 -37
  74. package/src/toolbar/widgets/EraserToolWidget.ts +64 -5
  75. package/src/toolbar/widgets/PenToolWidget.ts +2 -2
  76. package/src/toolbar/widgets/SelectionToolWidget.ts +2 -2
  77. package/src/tools/Eraser.test.ts +79 -0
  78. package/src/tools/Eraser.ts +81 -17
  79. package/src/tools/PanZoom.ts +1 -1
  80. package/src/tools/PasteHandler.ts +12 -6
  81. package/src/tools/Pen.test.ts +44 -1
  82. package/src/tools/Pen.ts +53 -8
  83. package/src/tools/SelectionTool/Selection.ts +73 -23
  84. package/src/tools/SelectionTool/SelectionHandle.ts +9 -0
  85. package/src/tools/SelectionTool/SelectionTool.test.ts +138 -21
  86. package/src/tools/SelectionTool/SelectionTool.ts +70 -16
  87. package/src/tools/TextTool.ts +14 -8
  88. package/src/tools/ToolSwitcherShortcut.ts +10 -5
  89. package/src/tools/UndoRedoShortcut.ts +2 -5
  90. package/src/types.ts +2 -2
  91. package/typedoc.json +2 -2
@@ -1,6 +1,3 @@
1
- // Allows users to select/transform portions of the `EditorImage`.
2
- // With respect to `extend`ing, `SelectionTool` is not stable.
3
- // @packageDocumentation
4
1
  import Mat33 from '../../math/Mat33';
5
2
  import { Vec2 } from '../../math/Vec2';
6
3
  import { EditorEventType } from '../../types';
@@ -10,7 +7,8 @@ import SVGRenderer from '../../rendering/renderers/SVGRenderer';
10
7
  import Selection from './Selection';
11
8
  import TextComponent from '../../components/TextComponent';
12
9
  export const cssPrefix = 'selection-tool-';
13
- // {@inheritDoc SelectionTool!}
10
+ // Allows users to select/transform portions of the `EditorImage`.
11
+ // With respect to `extend`ing, `SelectionTool` is not stable.
14
12
  export default class SelectionTool extends BaseTool {
15
13
  constructor(editor, description) {
16
14
  super(editor.notifier, description);
@@ -18,6 +16,7 @@ export default class SelectionTool extends BaseTool {
18
16
  this.lastEvtTarget = null;
19
17
  this.expandingSelectionBox = false;
20
18
  this.shiftKeyPressed = false;
19
+ this.ctrlKeyPressed = false;
21
20
  this.selectionBoxHandlingEvt = false;
22
21
  this.handleOverlay = document.createElement('div');
23
22
  editor.createHTMLOverlay(this.handleOverlay);
@@ -45,17 +44,37 @@ export default class SelectionTool extends BaseTool {
45
44
  }
46
45
  this.selectionBox.addTo(this.handleOverlay);
47
46
  }
48
- onPointerDown(event) {
49
- var _a;
50
- if (event.allPointers.length === 1 && event.current.isPrimary) {
51
- if (this.lastEvtTarget && ((_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.onDragStart(event.current, this.lastEvtTarget))) {
52
- this.selectionBoxHandlingEvt = true;
53
- this.expandingSelectionBox = false;
47
+ snapSelectionToGrid() {
48
+ if (!this.selectionBox)
49
+ throw new Error('No selection to snap!');
50
+ const topLeftOfBBox = this.selectionBox.region.topLeft;
51
+ const snapDistance = this.editor.viewport.snapToGrid(topLeftOfBBox).minus(topLeftOfBBox);
52
+ const oldTransform = this.selectionBox.getTransform();
53
+ this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDistance)));
54
+ this.selectionBox.finalizeTransform();
55
+ }
56
+ onPointerDown({ allPointers, current }) {
57
+ const snapToGrid = this.ctrlKeyPressed;
58
+ if (snapToGrid) {
59
+ current = current.snappedToGrid(this.editor.viewport);
60
+ }
61
+ if (allPointers.length === 1 && current.isPrimary) {
62
+ let transforming = false;
63
+ if (this.lastEvtTarget && this.selectionBox) {
64
+ if (snapToGrid) {
65
+ this.snapSelectionToGrid();
66
+ }
67
+ const dragStartResult = this.selectionBox.onDragStart(current, this.lastEvtTarget);
68
+ if (dragStartResult) {
69
+ transforming = true;
70
+ this.selectionBoxHandlingEvt = true;
71
+ this.expandingSelectionBox = false;
72
+ }
54
73
  }
55
- else {
74
+ if (!transforming) {
56
75
  // Shift key: Combine the new and old selection boxes at the end of the gesture.
57
76
  this.expandingSelectionBox = this.shiftKeyPressed;
58
- this.makeSelectionBox(event.current.canvasPos);
77
+ this.makeSelectionBox(current.canvasPos);
59
78
  }
60
79
  return true;
61
80
  }
@@ -64,11 +83,15 @@ export default class SelectionTool extends BaseTool {
64
83
  onPointerMove(event) {
65
84
  if (!this.selectionBox)
66
85
  return;
86
+ let currentPointer = event.current;
87
+ if (this.ctrlKeyPressed) {
88
+ currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
89
+ }
67
90
  if (this.selectionBoxHandlingEvt) {
68
- this.selectionBox.onDragUpdate(event.current);
91
+ this.selectionBox.onDragUpdate(currentPointer);
69
92
  }
70
93
  else {
71
- this.selectionBox.setToPoint(event.current.canvasPos);
94
+ this.selectionBox.setToPoint(currentPointer.canvasPos);
72
95
  }
73
96
  }
74
97
  onSelectionUpdated() {
@@ -113,7 +136,11 @@ export default class SelectionTool extends BaseTool {
113
136
  onPointerUp(event) {
114
137
  if (!this.selectionBox)
115
138
  return;
116
- this.selectionBox.setToPoint(event.current.canvasPos);
139
+ let currentPointer = event.current;
140
+ if (this.ctrlKeyPressed) {
141
+ currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
142
+ }
143
+ this.selectionBox.setToPoint(currentPointer.canvasPos);
117
144
  // Were we expanding the previous selection?
118
145
  if (this.expandingSelectionBox && this.prevSelectionBox) {
119
146
  // If so, finish expanding.
@@ -142,6 +169,10 @@ export default class SelectionTool extends BaseTool {
142
169
  this.expandingSelectionBox = false;
143
170
  }
144
171
  onKeyPress(event) {
172
+ if (event.key === 'Control') {
173
+ this.ctrlKeyPressed = true;
174
+ return true;
175
+ }
145
176
  if (this.selectionBox && event.ctrlKey && event.key === 'd') {
146
177
  // Handle duplication on key up — we don't want to accidentally duplicate
147
178
  // many times.
@@ -235,13 +266,19 @@ export default class SelectionTool extends BaseTool {
235
266
  return handled;
236
267
  }
237
268
  onKeyUp(evt) {
269
+ if (evt.key === 'Control') {
270
+ this.ctrlKeyPressed = false;
271
+ return true;
272
+ }
238
273
  if (evt.key === 'Shift') {
239
274
  this.shiftKeyPressed = false;
240
275
  return true;
241
276
  }
242
277
  else if (evt.ctrlKey) {
243
278
  if (this.selectionBox && evt.key === 'd') {
244
- this.editor.dispatch(this.selectionBox.duplicateSelectedObjects());
279
+ this.selectionBox.duplicateSelectedObjects().then(command => {
280
+ this.editor.dispatch(command);
281
+ });
245
282
  return true;
246
283
  }
247
284
  else if (evt.key === 'a') {
@@ -289,6 +326,8 @@ export default class SelectionTool extends BaseTool {
289
326
  // Clear the selection
290
327
  this.handleOverlay.replaceChildren();
291
328
  this.selectionBox = null;
329
+ this.shiftKeyPressed = false;
330
+ this.ctrlKeyPressed = false;
292
331
  this.handleOverlay.style.display = enabled ? 'block' : 'none';
293
332
  if (enabled) {
294
333
  this.handleOverlay.tabIndex = 0;
@@ -353,4 +392,5 @@ SelectionTool.handleableKeys = [
353
392
  'e', 'j', 'ArrowDown',
354
393
  'r', 'R',
355
394
  'i', 'I', 'o', 'O',
395
+ 'Control',
356
396
  ];
@@ -69,12 +69,13 @@ export default class TextTool extends BaseTool {
69
69
  flushInput(removeInput = true) {
70
70
  if (this.textInputElem && this.textTargetPosition) {
71
71
  const content = this.textInputElem.value.trimEnd();
72
+ this.textInputElem.value = '';
72
73
  if (removeInput) {
73
- this.textInputElem.remove();
74
+ // In some browsers, .remove() triggers a .blur event (synchronously).
75
+ // Clear this.textInputElem before removal
76
+ const input = this.textInputElem;
74
77
  this.textInputElem = null;
75
- }
76
- else {
77
- this.textInputElem.value = '';
78
+ input.remove();
78
79
  }
79
80
  if (content === '') {
80
81
  return;
@@ -145,9 +146,12 @@ export default class TextTool extends BaseTool {
145
146
  // Delay removing the input -- flushInput may be called within a blur()
146
147
  // event handler
147
148
  const removeInput = false;
148
- this.flushInput(removeInput);
149
149
  const input = this.textInputElem;
150
- setTimeout(() => input === null || input === void 0 ? void 0 : input.remove(), 0);
150
+ this.flushInput(removeInput);
151
+ this.textInputElem = null;
152
+ setTimeout(() => {
153
+ input === null || input === void 0 ? void 0 : input.remove();
154
+ }, 0);
151
155
  };
152
156
  this.textInputElem.onkeyup = (evt) => {
153
157
  var _a, _b;
@@ -1,6 +1,14 @@
1
1
  import Editor from '../Editor';
2
2
  import { KeyPressEvent } from '../types';
3
3
  import BaseTool from './BaseTool';
4
+ /**
5
+ * Handles keyboard events used, by default, to select tools. By default,
6
+ * 1 maps to the first primary tool, 2 to the second primary tool, ... .
7
+ *
8
+ * This is in the default set of {@link ToolController} tools.
9
+ *
10
+ * @deprecated This may be replaced in the future.
11
+ */
4
12
  export default class ToolSwitcherShortcut extends BaseTool {
5
13
  private editor;
6
14
  constructor(editor: Editor);
@@ -1,12 +1,18 @@
1
- // Handles ctrl+1, ctrl+2, ctrl+3, ..., shortcuts for switching tools.
2
- // @packageDocumentation
3
1
  import BaseTool from './BaseTool';
4
- // {@inheritDoc ToolSwitcherShortcut!}
2
+ /**
3
+ * Handles keyboard events used, by default, to select tools. By default,
4
+ * 1 maps to the first primary tool, 2 to the second primary tool, ... .
5
+ *
6
+ * This is in the default set of {@link ToolController} tools.
7
+ *
8
+ * @deprecated This may be replaced in the future.
9
+ */
5
10
  export default class ToolSwitcherShortcut extends BaseTool {
6
11
  constructor(editor) {
7
12
  super(editor.notifier, editor.localization.changeTool);
8
13
  this.editor = editor;
9
14
  }
15
+ // @internal
10
16
  onKeyPress({ key }) {
11
17
  const toolController = this.editor.toolController;
12
18
  const primaryTools = toolController.getPrimaryTools();
@@ -1,13 +1,11 @@
1
- // Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
2
- // @packageDocumentation
3
1
  import BaseTool from './BaseTool';
4
- // {@inheritDoc UndoRedoShortcut!}
2
+ // Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
5
3
  export default class UndoRedoShortcut extends BaseTool {
6
4
  constructor(editor) {
7
5
  super(editor.notifier, editor.localization.undoRedoTool);
8
6
  this.editor = editor;
9
7
  }
10
- // Activate undo/redo
8
+ // @internal
11
9
  onKeyPress({ key, ctrlKey }) {
12
10
  if (ctrlKey) {
13
11
  if (key === 'z') {
@@ -129,8 +129,8 @@ export interface ToolbarDropdownShownEvent {
129
129
  readonly parentWidget: BaseWidget;
130
130
  }
131
131
  export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
132
- export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
133
- export type ComponentAddedListener = (component: AbstractComponent) => void;
132
+ export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null | void;
133
+ export type ComponentAddedListener = (component: AbstractComponent) => Promise<void> | void;
134
134
  export type OnDetermineExportRectListener = (exportRect: Rect2) => void;
135
135
  export interface ImageLoader {
136
136
  start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.11.3",
3
+ "version": "0.13.0",
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/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
@@ -101,7 +101,7 @@
101
101
  "ts-jest": "^29.0.3",
102
102
  "ts-loader": "^9.4.2",
103
103
  "ts-node": "^10.9.1",
104
- "typedoc": "^0.23.23",
104
+ "typedoc": "^0.23.24",
105
105
  "typescript": "^4.9.4",
106
106
  "webpack": "^5.75.0"
107
107
  },
@@ -16,4 +16,15 @@ describe('Color4', () => {
16
16
  expect(Color4.fromString('rgba ( 255, 0,\t 0, 0.5)')).objEq(Color4.ofRGBA(1, 0, 0, 0.5));
17
17
  expect(Color4.fromString('rgba( 0, 0, 128, 0)')).objEq(Color4.ofRGBA(0, 0, 128/255, 0));
18
18
  });
19
+
20
+ it('should mix blue and red to get dark purple', () => {
21
+ expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 0, 1), 0.5)).objEq(Color4.ofRGB(0.5, 0, 0.5));
22
+ expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 0, 1), 0.1)).objEq(Color4.ofRGB(0.9, 0, 0.1));
23
+ });
24
+
25
+ it('should mix red and green to get yellow', () => {
26
+ expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.3)).objEq(
27
+ Color4.ofRGB(0.7, 0.3, 0)
28
+ );
29
+ });
19
30
  });
package/src/Color4.ts CHANGED
@@ -129,6 +129,28 @@ export default class Color4 {
129
129
  return this.toHexString() === other.toHexString();
130
130
  }
131
131
 
132
+ /**
133
+ * If `fractionTo` is not in the range [0, 1], it will be clamped to the nearest number
134
+ * in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
135
+ *
136
+ * @returns a color `fractionTo` of the way from this color to `other`.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
141
+ * ```
142
+ */
143
+ public mix(other: Color4, fractionTo: number): Color4 {
144
+ fractionTo = Math.min(Math.max(fractionTo, 0), 1);
145
+ const fractionOfThis = 1 - fractionTo;
146
+ return new Color4(
147
+ this.r * fractionOfThis + other.r * fractionTo,
148
+ this.g * fractionOfThis + other.g * fractionTo,
149
+ this.b * fractionOfThis + other.b * fractionTo,
150
+ this.a * fractionOfThis + other.a * fractionTo,
151
+ );
152
+ }
153
+
132
154
  private hexString: string|null = null;
133
155
 
134
156
  /**
@@ -176,5 +198,6 @@ export default class Color4 {
176
198
  public static yellow = Color4.ofRGB(1, 1, 0.1);
177
199
  public static clay = Color4.ofRGB(0.8, 0.4, 0.2);
178
200
  public static black = Color4.ofRGB(0, 0, 0);
201
+ public static gray = Color4.ofRGB(0.5, 0.5, 0.5);
179
202
  public static white = Color4.ofRGB(1, 1, 1);
180
203
  }
package/src/Editor.ts CHANGED
@@ -1,22 +1,3 @@
1
- /**
2
- * The main entrypoint for the full editor.
3
- *
4
- * @example
5
- * To create an editor with a toolbar,
6
- * ```
7
- * const editor = new Editor(document.body);
8
- *
9
- * const toolbar = editor.addToolbar();
10
- * toolbar.addActionButton('Save', () => {
11
- * const saveData = editor.toSVG().outerHTML;
12
- * // Do something with saveData...
13
- * });
14
- * ```
15
- *
16
- * @packageDocumentation
17
- */
18
-
19
-
20
1
  import EditorImage from './EditorImage';
21
2
  import ToolController from './tools/ToolController';
22
3
  import { InputEvtType, PointerEvt, EditorNotifier, EditorEventType, ImageLoader } from './types';
@@ -70,12 +51,27 @@ export interface EditorSettings {
70
51
  iconProvider: IconProvider,
71
52
  }
72
53
 
73
- // { @inheritDoc Editor! }
54
+ /**
55
+ * The main entrypoint for the full editor.
56
+ *
57
+ * @example
58
+ * To create an editor with a toolbar,
59
+ * ```
60
+ * const editor = new Editor(document.body);
61
+ *
62
+ * const toolbar = editor.addToolbar();
63
+ * toolbar.addActionButton('Save', () => {
64
+ * const saveData = editor.toSVG().outerHTML;
65
+ * // Do something with saveData...
66
+ * });
67
+ * ```
68
+ */
74
69
  export class Editor {
75
70
  // Wrapper around the viewport and toolbar
76
71
  private container: HTMLElement;
77
72
  private renderingRegion: HTMLElement;
78
73
 
74
+ /** Manages drawing surfaces/{@link lib!AbstractRenderer}s. */
79
75
  public display: Display;
80
76
 
81
77
  /**
@@ -116,11 +112,23 @@ export class Editor {
116
112
  /** Viewport for the exported/imported image. */
117
113
  private importExportViewport: Viewport;
118
114
 
115
+ /**
116
+ * Allows transforming the view and querying information about
117
+ * what is currently visible.
118
+ */
119
+ public readonly viewport: Viewport;
120
+
119
121
  /** @internal */
120
122
  public readonly localization: EditorLocalization;
121
123
 
124
+ /** {@link lib!EditorSettings.iconProvider} */
122
125
  public readonly icons: IconProvider;
123
- public readonly viewport: Viewport;
126
+
127
+ /**
128
+ * Controls the list of tools. See
129
+ * [the custom tool example](https://github.com/personalizedrefrigerator/js-draw/tree/main/docs/example-custom-tools)
130
+ * for more.
131
+ */
124
132
  public readonly toolController: ToolController;
125
133
 
126
134
  /**
@@ -292,8 +300,7 @@ export class Editor {
292
300
  const toolbar = new HTMLToolbar(this, this.container, this.localization);
293
301
 
294
302
  if (defaultLayout) {
295
- toolbar.addDefaultToolWidgets();
296
- toolbar.addDefaultActionButtons();
303
+ toolbar.addDefaults();
297
304
  }
298
305
 
299
306
  return toolbar;
@@ -766,12 +773,18 @@ export class Editor {
766
773
  this.nextRerenderListeners = [];
767
774
  }
768
775
 
776
+ /**
777
+ * @see {@link Display.getWetInkRenderer} {@link Display.flatten}
778
+ */
769
779
  public drawWetInk(...path: RenderablePathSpec[]) {
770
780
  for (const part of path) {
771
781
  this.display.getWetInkRenderer().drawPath(part);
772
782
  }
773
783
  }
774
784
 
785
+ /**
786
+ * @see {@link Display.getWetInkRenderer}
787
+ */
775
788
  public clearWetInk() {
776
789
  this.display.getWetInkRenderer().clear();
777
790
  }
@@ -896,7 +909,7 @@ export class Editor {
896
909
  public toDataURL(format: 'image/png'|'image/jpeg'|'image/webp' = 'image/png'): string {
897
910
  const canvas = document.createElement('canvas');
898
911
 
899
- const resolution = this.importExportViewport.getResolution();
912
+ const resolution = this.importExportViewport.getScreenRectSize();
900
913
 
901
914
  canvas.width = resolution.x;
902
915
  canvas.height = resolution.y;
@@ -946,8 +959,8 @@ export class Editor {
946
959
  this.showLoadingWarning(0);
947
960
  this.display.setDraftMode(true);
948
961
 
949
- await loader.start((component) => {
950
- this.dispatchNoAnnounce(EditorImage.addElement(component));
962
+ await loader.start(async (component) => {
963
+ await this.dispatchNoAnnounce(EditorImage.addElement(component));
951
964
  }, (countProcessed: number, totalToProcess: number) => {
952
965
  if (countProcessed % 500 === 0) {
953
966
  this.showLoadingWarning(countProcessed / totalToProcess);
@@ -75,6 +75,11 @@ export default class EditorImage {
75
75
  delete this.componentsById[elem.getId()];
76
76
  }
77
77
 
78
+ /**
79
+ * @returns the AbstractComponent with `id`, if it exists.
80
+ *
81
+ * @see {@link AbstractComponent.getId}
82
+ */
78
83
  public lookupElement(id: string): AbstractComponent|null {
79
84
  return this.componentsById[id] ?? null;
80
85
  }
@@ -84,6 +89,13 @@ export default class EditorImage {
84
89
  return this.root.addLeaf(elem);
85
90
  }
86
91
 
92
+ /**
93
+ * Returns a command that adds the given element to the `EditorImage`.
94
+ * If `applyByFlattening` is true, the content of the wet ink renderer is
95
+ * rendered onto the main rendering canvas instead of doing a full re-render.
96
+ *
97
+ * @see {@link Display.flatten}
98
+ */
87
99
  public static addElement(elem: AbstractComponent, applyByFlattening: boolean = false): SerializableCommand {
88
100
  return new EditorImage.AddElementCommand(elem, applyByFlattening);
89
101
  }
package/src/Pointer.ts CHANGED
@@ -36,6 +36,25 @@ export default class Pointer {
36
36
  ) {
37
37
  }
38
38
 
39
+ // Snaps this pointer to the nearest grid point (rounds the coordinates of this
40
+ // pointer based on the current zoom). Returns a new Pointer and does not modify
41
+ // this.
42
+ public snappedToGrid(viewport: Viewport): Pointer {
43
+ const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
44
+ const snappedScreenPos = viewport.canvasToScreen(snappedCanvasPos);
45
+
46
+ return new Pointer(
47
+ snappedScreenPos,
48
+ snappedCanvasPos,
49
+ this.pressure,
50
+ this.isPrimary,
51
+ this.down,
52
+ this.device,
53
+ this.id,
54
+ this.timeStamp,
55
+ );
56
+ }
57
+
39
58
  // Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
40
59
  // considered the top left of `relativeTo`.
41
60
  public static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport, relativeTo?: HTMLElement): Pointer {
package/src/SVGLoader.ts CHANGED
@@ -31,6 +31,7 @@ export type SVGLoaderUnknownStyleAttribute = { key: string, value: string, prior
31
31
 
32
32
  const supportedStrokeFillStyleAttrs = [ 'stroke', 'fill', 'stroke-width' ];
33
33
 
34
+ // Handles loading images from SVG.
34
35
  export default class SVGLoader implements ImageLoader {
35
36
  private onAddComponent: ComponentAddedListener|null = null;
36
37
  private onProgress: OnProgressListener|null = null;
@@ -156,7 +157,7 @@ export default class SVGLoader implements ImageLoader {
156
157
  }
157
158
 
158
159
  // Adds a stroke with a single path
159
- private addPath(node: SVGPathElement) {
160
+ private async addPath(node: SVGPathElement) {
160
161
  let elem: AbstractComponent;
161
162
  try {
162
163
  const strokeData = this.strokeDataFromElem(node);
@@ -181,7 +182,7 @@ export default class SVGLoader implements ImageLoader {
181
182
  return;
182
183
  }
183
184
  }
184
- this.onAddComponent?.(elem);
185
+ await this.onAddComponent?.(elem);
185
186
  }
186
187
 
187
188
  // If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
@@ -274,10 +275,10 @@ export default class SVGLoader implements ImageLoader {
274
275
  return result;
275
276
  }
276
277
 
277
- private addText(elem: SVGTextElement|SVGTSpanElement) {
278
+ private async addText(elem: SVGTextElement|SVGTSpanElement) {
278
279
  try {
279
280
  const textElem = this.makeText(elem);
280
- this.onAddComponent?.(textElem);
281
+ await this.onAddComponent?.(textElem);
281
282
  } catch (e) {
282
283
  console.error('Invalid text object in node', elem, '. Continuing.... Error:', e);
283
284
  this.addUnknownNode(elem);
@@ -300,17 +301,17 @@ export default class SVGLoader implements ImageLoader {
300
301
  new Set([ 'transform' ])
301
302
  );
302
303
 
303
- this.onAddComponent?.(imageElem);
304
+ await this.onAddComponent?.(imageElem);
304
305
  } catch (e) {
305
306
  console.error('Error loading image:', e, '. Element: ', elem, '. Continuing...');
306
- this.addUnknownNode(elem);
307
+ await this.addUnknownNode(elem);
307
308
  }
308
309
  }
309
310
 
310
- private addUnknownNode(node: SVGElement) {
311
+ private async addUnknownNode(node: SVGElement) {
311
312
  if (this.storeUnknown) {
312
313
  const component = new UnknownSVGObject(node);
313
- this.onAddComponent?.(component);
314
+ await this.onAddComponent?.(component);
314
315
  }
315
316
  }
316
317
 
@@ -335,9 +336,9 @@ export default class SVGLoader implements ImageLoader {
335
336
  this.onDetermineExportRect?.(this.rootViewBox);
336
337
  }
337
338
 
338
- private updateSVGAttrs(node: SVGSVGElement) {
339
+ private async updateSVGAttrs(node: SVGSVGElement) {
339
340
  if (this.storeUnknown) {
340
- this.onAddComponent?.(new SVGGlobalAttributesObject(this.getSourceAttrs(node)));
341
+ await this.onAddComponent?.(new SVGGlobalAttributesObject(this.getSourceAttrs(node)));
341
342
  }
342
343
  }
343
344
 
@@ -350,10 +351,10 @@ export default class SVGLoader implements ImageLoader {
350
351
  // Continue -- visit the node's children.
351
352
  break;
352
353
  case 'path':
353
- this.addPath(node as SVGPathElement);
354
+ await this.addPath(node as SVGPathElement);
354
355
  break;
355
356
  case 'text':
356
- this.addText(node as SVGTextElement);
357
+ await this.addText(node as SVGTextElement);
357
358
  visitChildren = false;
358
359
  break;
359
360
  case 'image':
@@ -367,7 +368,7 @@ export default class SVGLoader implements ImageLoader {
367
368
  this.updateSVGAttrs(node as SVGSVGElement);
368
369
  break;
369
370
  case 'style':
370
- this.addUnknownNode(node as SVGStyleElement);
371
+ await this.addUnknownNode(node as SVGStyleElement);
371
372
  break;
372
373
  default:
373
374
  console.warn('Unknown SVG element,', node);
@@ -377,7 +378,7 @@ export default class SVGLoader implements ImageLoader {
377
378
  );
378
379
  }
379
380
 
380
- this.addUnknownNode(node as SVGElement);
381
+ await this.addUnknownNode(node as SVGElement);
381
382
  return;
382
383
  }
383
384
 
@@ -422,7 +423,11 @@ export default class SVGLoader implements ImageLoader {
422
423
  this.onFinish?.();
423
424
  }
424
425
 
425
- // @param sanitize - if `true`, don't store unknown attributes.
426
+ /**
427
+ * @see {@link Editor.loadFrom}
428
+ * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
429
+ * @param sanitize - if `true`, don't store unknown attributes.
430
+ */
426
431
  public static fromString(text: string, sanitize: boolean = false): SVGLoader {
427
432
  const sandbox = document.createElement('iframe');
428
433
  sandbox.src = 'about:blank';