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
@@ -2,12 +2,15 @@ var _a;
2
2
  import SerializableCommand from '../commands/SerializableCommand';
3
3
  import EditorImage from '../EditorImage';
4
4
  import Mat33 from '../math/Mat33';
5
+ /**
6
+ * A base class for everything that can be added to an {@link EditorImage}.
7
+ */
5
8
  export default class AbstractComponent {
6
9
  constructor(
7
10
  // A unique identifier for the type of component
8
11
  componentKind) {
9
12
  this.componentKind = componentKind;
10
- // Get and manage data attached by a loader.
13
+ // Stores data attached by a loader.
11
14
  this.loadSaveData = {};
12
15
  this.lastChangedTime = (new Date()).getTime();
13
16
  this.zIndex = AbstractComponent.zIndexCounter++;
@@ -18,7 +21,7 @@ export default class AbstractComponent {
18
21
  }
19
22
  }
20
23
  // Returns a unique ID for this element.
21
- // @see { @link EditorImage!default.lookupElement }
24
+ // @see { @link lib!EditorImage.lookupElement }
22
25
  getId() {
23
26
  return this.id;
24
27
  }
@@ -28,21 +31,47 @@ export default class AbstractComponent {
28
31
  static registerComponent(componentKind, deserialize) {
29
32
  this.deserializationCallbacks[componentKind] = deserialize !== null && deserialize !== void 0 ? deserialize : null;
30
33
  }
34
+ /**
35
+ * Attach data that can be used while exporting the component (e.g. to SVG).
36
+ *
37
+ * This is intended for use by a {@link ImageLoader}.
38
+ */
31
39
  attachLoadSaveData(key, data) {
32
40
  if (!this.loadSaveData[key]) {
33
41
  this.loadSaveData[key] = [];
34
42
  }
35
43
  this.loadSaveData[key].push(data);
36
44
  }
45
+ /** See {@link attachLoadSaveData} */
37
46
  getLoadSaveData() {
38
47
  return this.loadSaveData;
39
48
  }
40
49
  getZIndex() {
41
50
  return this.zIndex;
42
51
  }
52
+ /** @returns the bounding box of */
43
53
  getBBox() {
44
54
  return this.contentBBox;
45
55
  }
56
+ /**
57
+ * @returns true if this component intersects `rect` -- it is entirely contained
58
+ * within the rectangle or one of the rectangle's edges intersects this component.
59
+ */
60
+ intersectsRect(rect) {
61
+ // If this component intersects rect,
62
+ // it is either contained entirely within rect or intersects one of rect's edges.
63
+ // If contained within,
64
+ if (rect.containsRect(this.getBBox())) {
65
+ return true;
66
+ }
67
+ // Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
68
+ // As such, test with more lines than just the rect's edges.
69
+ const testLines = [];
70
+ for (const subregion of rect.divideIntoGrid(2, 2)) {
71
+ testLines.push(...subregion.getEdges());
72
+ }
73
+ return testLines.some(edge => this.intersects(edge));
74
+ }
46
75
  // Returns a command that, when applied, transforms this by [affineTransfm] and
47
76
  // updates the editor.
48
77
  transformBy(affineTransfm) {
@@ -183,6 +212,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
183
212
  hadParent = true;
184
213
  }
185
214
  this.component.applyTransformation(newTransfm);
215
+ this.component.lastChangedTime = (new Date()).getTime();
186
216
  // Add the element back to the document.
187
217
  if (hadParent) {
188
218
  EditorImage.addElement(this.component).apply(editor);
package/dist/src/lib.d.ts CHANGED
@@ -8,22 +8,25 @@
8
8
  * ```
9
9
  *
10
10
  * @see
11
- * {@link Editor!}
11
+ * {@link Editor}
12
12
  *
13
13
  * @packageDocumentation
14
14
  */
15
- import Editor from './Editor';
15
+ import Editor, { EditorSettings } from './Editor';
16
16
  export { default as EditorImage } from './EditorImage';
17
17
  export * from './types';
18
18
  export { default as getLocalizationTable } from './localizations/getLocalizationTable';
19
19
  export * from './localization';
20
20
  export { default as Color4 } from './Color4';
21
+ export { default as SVGLoader } from './SVGLoader';
22
+ export { default as Viewport } from './Viewport';
21
23
  export * from './math/lib';
22
24
  export * from './components/lib';
23
25
  export * from './commands/lib';
24
26
  export * from './tools/lib';
25
27
  export * from './toolbar/lib';
28
+ export * from './rendering/lib';
26
29
  export { default as Pointer, PointerDevice } from './Pointer';
27
30
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
28
- export { Editor };
31
+ export { Editor, EditorSettings };
29
32
  export default Editor;
package/dist/src/lib.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * ```
9
9
  *
10
10
  * @see
11
- * {@link Editor!}
11
+ * {@link Editor}
12
12
  *
13
13
  * @packageDocumentation
14
14
  */
@@ -18,11 +18,14 @@ export * from './types';
18
18
  export { default as getLocalizationTable } from './localizations/getLocalizationTable';
19
19
  export * from './localization';
20
20
  export { default as Color4 } from './Color4';
21
+ export { default as SVGLoader } from './SVGLoader';
22
+ export { default as Viewport } from './Viewport';
21
23
  export * from './math/lib';
22
24
  export * from './components/lib';
23
25
  export * from './commands/lib';
24
26
  export * from './tools/lib';
25
27
  export * from './toolbar/lib';
28
+ export * from './rendering/lib';
26
29
  export { default as Pointer, PointerDevice } from './Pointer';
27
30
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
28
31
  export { Editor };
@@ -102,7 +102,7 @@ export default class Mat33 {
102
102
  static translation(amount: Vec2): Mat33;
103
103
  static zRotation(radians: number, center?: Point2): Mat33;
104
104
  static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
105
- /** @see {@link !fromCSSMatrix} */
105
+ /** @see {@link fromCSSMatrix} */
106
106
  toCSSMatrix(): string;
107
107
  /**
108
108
  * Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
@@ -241,7 +241,7 @@ export default class Mat33 {
241
241
  // Translate such that [center] goes to (0, 0)
242
242
  return result.rightMul(Mat33.translation(center.times(-1)));
243
243
  }
244
- /** @see {@link !fromCSSMatrix} */
244
+ /** @see {@link fromCSSMatrix} */
245
245
  toCSSMatrix() {
246
246
  return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
247
247
  }
@@ -1,3 +1,12 @@
1
+ import AbstractRenderer from './renderers/AbstractRenderer';
2
+ import { Editor } from '../Editor';
3
+ import { Point2 } from '../math/Vec2';
4
+ import RenderingCache from './caching/RenderingCache';
5
+ import Color4 from '../Color4';
6
+ export declare enum RenderingMode {
7
+ DummyRenderer = 0,
8
+ CanvasRenderer = 1
9
+ }
1
10
  /**
2
11
  * Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
3
12
  *
@@ -9,18 +18,7 @@
9
18
  * const center = Vec2.of(w / 2, h / 2);
10
19
  * const colorAtCenter = editor.display.getColorAt(center);
11
20
  * ```
12
- *
13
- * @packageDocumentation
14
21
  */
15
- import AbstractRenderer from './renderers/AbstractRenderer';
16
- import { Editor } from '../Editor';
17
- import { Point2 } from '../math/Vec2';
18
- import RenderingCache from './caching/RenderingCache';
19
- import Color4 from '../Color4';
20
- export declare enum RenderingMode {
21
- DummyRenderer = 0,
22
- CanvasRenderer = 1
23
- }
24
22
  export default class Display {
25
23
  private editor;
26
24
  private parent;
@@ -1,17 +1,3 @@
1
- /**
2
- * Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
3
- *
4
- * @example
5
- * ```
6
- * const editor = new Editor(document.body);
7
- * const w = editor.display.width;
8
- * const h = editor.display.height;
9
- * const center = Vec2.of(w / 2, h / 2);
10
- * const colorAtCenter = editor.display.getColorAt(center);
11
- * ```
12
- *
13
- * @packageDocumentation
14
- */
15
1
  import CanvasRenderer from './renderers/CanvasRenderer';
16
2
  import { EditorEventType } from '../types';
17
3
  import DummyRenderer from './renderers/DummyRenderer';
@@ -25,6 +11,18 @@ export var RenderingMode;
25
11
  RenderingMode[RenderingMode["CanvasRenderer"] = 1] = "CanvasRenderer";
26
12
  // SVGRenderer is not supported by the main display
27
13
  })(RenderingMode || (RenderingMode = {}));
14
+ /**
15
+ * Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
16
+ *
17
+ * @example
18
+ * ```
19
+ * const editor = new Editor(document.body);
20
+ * const w = editor.display.width;
21
+ * const h = editor.display.height;
22
+ * const center = Vec2.of(w / 2, h / 2);
23
+ * const colorAtCenter = editor.display.getColorAt(center);
24
+ * ```
25
+ */
28
26
  export default class Display {
29
27
  /** @internal */
30
28
  constructor(editor, mode, parent) {
@@ -0,0 +1,3 @@
1
+ export { default as AbstractRenderer } from './renderers/AbstractRenderer';
2
+ export { default as DummyRenderer } from './renderers/DummyRenderer';
3
+ export { default as Display } from './Display';
@@ -0,0 +1,3 @@
1
+ export { default as AbstractRenderer } from './renderers/AbstractRenderer';
2
+ export { default as DummyRenderer } from './renderers/DummyRenderer';
3
+ export { default as Display } from './Display';
@@ -1,6 +1,6 @@
1
- // Renderer that outputs nothing. Useful for automated tests.
2
1
  import { Vec2 } from '../../math/Vec2';
3
2
  import AbstractRenderer from './AbstractRenderer';
3
+ // Renderer that outputs almost nothing. Useful for automated tests.
4
4
  export default class DummyRenderer extends AbstractRenderer {
5
5
  constructor(viewport) {
6
6
  super(viewport);
@@ -17,7 +17,7 @@ export default class DummyRenderer extends AbstractRenderer {
17
17
  }
18
18
  displaySize() {
19
19
  // Do we have a stored viewport size?
20
- const viewportSize = this.getViewport().getResolution();
20
+ const viewportSize = this.getViewport().getScreenRectSize();
21
21
  // Don't use a 0x0 viewport — DummyRenderer is often used
22
22
  // for tests that run without a display, so pretend we have a
23
23
  // reasonable-sized display.
@@ -31,6 +31,10 @@ export default class SVGRenderer extends AbstractRenderer {
31
31
  stroke-linecap: round;
32
32
  stroke-linejoin: round;
33
33
  }
34
+
35
+ text {
36
+ white-space: pre;
37
+ }
34
38
  `.replace(/\s+/g, '');
35
39
  styleSheet.setAttribute('id', renderedStylesheetId);
36
40
  this.elem.appendChild(styleSheet);
@@ -3,6 +3,11 @@ import { ToolbarLocalization } from './localization';
3
3
  import { ActionButtonIcon } from './types';
4
4
  import BaseWidget from './widgets/BaseWidget';
5
5
  export declare const toolbarCSSPrefix = "toolbar-";
6
+ interface SpacerOptions {
7
+ grow: number;
8
+ minSize: string;
9
+ maxSize: string;
10
+ }
6
11
  export default class HTMLToolbar {
7
12
  private editor;
8
13
  private localizationTable;
@@ -13,8 +18,45 @@ export default class HTMLToolbar {
13
18
  /** @internal */
14
19
  constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
15
20
  setupColorPickers(): void;
21
+ /**
22
+ * Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
23
+ * (i.e. its `addTo` method should not have been called).
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const toolbar = editor.addToolbar();
28
+ * const insertImageWidget = new InsertImageWidget(editor);
29
+ * toolbar.addWidget(insertImageWidget);
30
+ * ```
31
+ */
16
32
  addWidget(widget: BaseWidget): void;
33
+ /**
34
+ * Adds a spacer.
35
+ *
36
+ * @example
37
+ * Adding a save button that moves to the very right edge of the toolbar
38
+ * while keeping the other buttons centered:
39
+ * ```ts
40
+ * const toolbar = editor.addToolbar(false);
41
+ *
42
+ * toolbar.addSpacer({ grow: 1 });
43
+ * toolbar.addDefaults();
44
+ * toolbar.addSpacer({ grow: 1 });
45
+ *
46
+ * toolbar.addActionButton({
47
+ * label: 'Save',
48
+ * icon: editor.icons.makeSaveIcon(),
49
+ * }, () => {
50
+ * saveCallback();
51
+ * });
52
+ * ```
53
+ */
54
+ addSpacer(options?: Partial<SpacerOptions>): void;
17
55
  serializeState(): string;
56
+ /**
57
+ * Deserialize toolbar widgets from the given state.
58
+ * Assumes that toolbar widgets are in the same order as when state was serialized.
59
+ */
18
60
  deserializeState(state: string): void;
19
61
  /**
20
62
  * Adds an action button with `title` to this toolbar (or to the given `parent` element).
@@ -25,4 +67,13 @@ export default class HTMLToolbar {
25
67
  addUndoRedoButtons(): void;
26
68
  addDefaultToolWidgets(): void;
27
69
  addDefaultActionButtons(): void;
70
+ /**
71
+ * Adds both the default tool widgets and action buttons. Equivalent to
72
+ * ```ts
73
+ * toolbar.addDefaultToolWidgets();
74
+ * toolbar.addDefaultActionButtons();
75
+ * ```
76
+ */
77
+ addDefaults(): void;
28
78
  }
79
+ export {};
@@ -12,7 +12,8 @@ import EraserWidget from './widgets/EraserToolWidget';
12
12
  import SelectionToolWidget from './widgets/SelectionToolWidget';
13
13
  import TextToolWidget from './widgets/TextToolWidget';
14
14
  import HandToolWidget from './widgets/HandToolWidget';
15
- import { ActionButtonWidget, InsertImageWidget } from './lib';
15
+ import ActionButtonWidget from './widgets/ActionButtonWidget';
16
+ import InsertImageWidget from './widgets/InsertImageWidget';
16
17
  export const toolbarCSSPrefix = 'toolbar-';
17
18
  export default class HTMLToolbar {
18
19
  /** @internal */
@@ -94,8 +95,17 @@ export default class HTMLToolbar {
94
95
  }
95
96
  });
96
97
  }
97
- // Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
98
- // (i.e. its `addTo` method should not have been called).
98
+ /**
99
+ * Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
100
+ * (i.e. its `addTo` method should not have been called).
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * const toolbar = editor.addToolbar();
105
+ * const insertImageWidget = new InsertImageWidget(editor);
106
+ * toolbar.addWidget(insertImageWidget);
107
+ * ```
108
+ */
99
109
  addWidget(widget) {
100
110
  // Prevent name collisions
101
111
  const id = widget.getUniqueIdIn(this.widgets);
@@ -105,6 +115,41 @@ export default class HTMLToolbar {
105
115
  widget.addTo(this.container);
106
116
  this.setupColorPickers();
107
117
  }
118
+ /**
119
+ * Adds a spacer.
120
+ *
121
+ * @example
122
+ * Adding a save button that moves to the very right edge of the toolbar
123
+ * while keeping the other buttons centered:
124
+ * ```ts
125
+ * const toolbar = editor.addToolbar(false);
126
+ *
127
+ * toolbar.addSpacer({ grow: 1 });
128
+ * toolbar.addDefaults();
129
+ * toolbar.addSpacer({ grow: 1 });
130
+ *
131
+ * toolbar.addActionButton({
132
+ * label: 'Save',
133
+ * icon: editor.icons.makeSaveIcon(),
134
+ * }, () => {
135
+ * saveCallback();
136
+ * });
137
+ * ```
138
+ */
139
+ addSpacer(options = {}) {
140
+ const spacer = document.createElement('div');
141
+ spacer.classList.add(`${toolbarCSSPrefix}spacer`);
142
+ if (options.grow) {
143
+ spacer.style.flexGrow = `${options.grow}`;
144
+ }
145
+ if (options.minSize) {
146
+ spacer.style.minWidth = options.minSize;
147
+ }
148
+ if (options.maxSize) {
149
+ spacer.style.maxWidth = options.maxSize;
150
+ }
151
+ this.container.appendChild(spacer);
152
+ }
108
153
  serializeState() {
109
154
  const result = {};
110
155
  for (const widgetId in this.widgets) {
@@ -112,8 +157,10 @@ export default class HTMLToolbar {
112
157
  }
113
158
  return JSON.stringify(result);
114
159
  }
115
- // Deserialize toolbar widgets from the given state.
116
- // Assumes that toolbar widgets are in the same order as when state was serialized.
160
+ /**
161
+ * Deserialize toolbar widgets from the given state.
162
+ * Assumes that toolbar widgets are in the same order as when state was serialized.
163
+ */
117
164
  deserializeState(state) {
118
165
  const data = JSON.parse(state);
119
166
  for (const widgetId in data) {
@@ -188,5 +235,16 @@ export default class HTMLToolbar {
188
235
  addDefaultActionButtons() {
189
236
  this.addUndoRedoButtons();
190
237
  }
238
+ /**
239
+ * Adds both the default tool widgets and action buttons. Equivalent to
240
+ * ```ts
241
+ * toolbar.addDefaultToolWidgets();
242
+ * toolbar.addDefaultActionButtons();
243
+ * ```
244
+ */
245
+ addDefaults() {
246
+ this.addDefaultToolWidgets();
247
+ this.addDefaultActionButtons();
248
+ }
191
249
  }
192
250
  HTMLToolbar.colorisStarted = false;
@@ -7,7 +7,7 @@ export default class IconProvider {
7
7
  makeUndoIcon(): IconType;
8
8
  makeRedoIcon(mirror?: boolean): IconType;
9
9
  makeDropdownIcon(): IconType;
10
- makeEraserIcon(): IconType;
10
+ makeEraserIcon(eraserSize?: number): IconType;
11
11
  makeSelectionIcon(): IconType;
12
12
  /**
13
13
  * @param pathData - SVG path data (e.g. `m10,10l30,30z`)
@@ -21,7 +21,7 @@ export default class IconProvider {
21
21
  makeRotationLockIcon(): IconType;
22
22
  makeInsertImageIcon(): IconType;
23
23
  makeTextIcon(textStyle: TextStyle): IconType;
24
- makePenIcon(tipThickness: number, color: string | Color4, roundedTip?: boolean): IconType;
24
+ makePenIcon(strokeSize: number, color: string | Color4, rounded?: boolean): IconType;
25
25
  makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
26
26
  makePipetteIcon(color?: Color4): IconType;
27
27
  makeResizeViewportIcon(): IconType;
@@ -30,7 +30,7 @@ export default class IconProvider {
30
30
  makeUndoIcon() {
31
31
  return this.makeRedoIcon(true);
32
32
  }
33
- // @param mirror - reflect across the x-axis @internal
33
+ // @param mirror - reflect across the x-axis. This parameter is internal.
34
34
  // @returns a redo icon.
35
35
  makeRedoIcon(mirror = false) {
36
36
  const icon = document.createElementNS(svgNamespace, 'svg');
@@ -67,19 +67,46 @@ export default class IconProvider {
67
67
  icon.setAttribute('viewBox', '0 0 100 100');
68
68
  return icon;
69
69
  }
70
- makeEraserIcon() {
70
+ makeEraserIcon(eraserSize) {
71
71
  const icon = document.createElementNS(svgNamespace, 'svg');
72
- // Draw an eraser-like shape
72
+ eraserSize !== null && eraserSize !== void 0 ? eraserSize : (eraserSize = 10);
73
+ const scaledSize = eraserSize / 4;
74
+ const eraserColor = '#ff70af';
75
+ // Draw an eraser-like shape. Created with Inkscape
73
76
  icon.innerHTML = `
74
77
  <g>
75
- <rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
78
+ <path
79
+ style="fill:${eraserColor}"
80
+ stroke="black"
81
+ transform="rotate(41.35)"
82
+ d="M 52.5 27
83
+ C 50 28.9 48.9 31.7 48.9 34.8
84
+ L 48.9 39.8
85
+ C 48.9 45.3 53.4 49.8 58.9 49.8
86
+ L 103.9 49.8
87
+ C 105.8 49.8 107.6 49.2 109.1 48.3
88
+ L 110.2 ${scaledSize + 49.5} L 159.7 ${scaledSize + 5}
89
+ L 157.7 ${-scaledSize + 5.2} L 112.4 ${49.5 - scaledSize}
90
+ C 113.4 43.5 113.9 41.7 113.9 39.8
91
+ L 113.9 34.8
92
+ C 113.9 29.3 109.4 24.8 103.9 24.8
93
+ L 58.9 24.8
94
+ C 56.5 24.8 54.3 25.7 52.5 27
95
+ z "
96
+ id="path438" />
97
+
76
98
  <rect
77
- x=10 y=10 width=80 height=50
99
+ stroke="#cc8077"
78
100
  ${iconColorFill}
79
- />
101
+ id="rect218"
102
+ width="65"
103
+ height="75"
104
+ x="48.9"
105
+ y="-38.7"
106
+ transform="rotate(41.35)" />
80
107
  </g>
81
108
  `;
82
- icon.setAttribute('viewBox', '0 0 100 100');
109
+ icon.setAttribute('viewBox', '0 0 120 120');
83
110
  return icon;
84
111
  }
85
112
  makeSelectionIcon() {
@@ -309,45 +336,106 @@ export default class IconProvider {
309
336
  icon.appendChild(textNode);
310
337
  return icon;
311
338
  }
312
- makePenIcon(tipThickness, color, roundedTip) {
339
+ makePenIcon(strokeSize, color, rounded) {
313
340
  if (color instanceof Color4) {
314
341
  color = color.toHexString();
315
342
  }
316
343
  const icon = document.createElementNS(svgNamespace, 'svg');
317
344
  icon.setAttribute('viewBox', '0 0 100 100');
318
- const halfThickness = tipThickness / 2;
319
- // Draw a pen-like shape
320
- const penTipLeft = 50 - halfThickness;
321
- const penTipRight = 50 + halfThickness;
322
- let tipCenterPrimaryPath = `L${penTipLeft},95 L${penTipRight},90`;
323
- let tipCenterBackgroundPath = `L${penTipLeft},85 L${penTipRight},83`;
324
- if (roundedTip) {
325
- tipCenterPrimaryPath = `L${penTipLeft},95 q${halfThickness},10 ${2 * halfThickness},-5`;
326
- tipCenterBackgroundPath = `L${penTipLeft},87 q${halfThickness},10 ${2 * halfThickness},-3`;
345
+ const tipThickness = strokeSize / 2;
346
+ const inkTipPath = `
347
+ M ${15 - tipThickness},${80 - tipThickness}
348
+ ${15 - tipThickness},${80 + tipThickness}
349
+ 30,83
350
+ 15,65
351
+ Z
352
+ `;
353
+ const trailStartEndY = 80 + tipThickness;
354
+ const inkTrailPath = `
355
+ m ${15 - tipThickness * 1.1},${trailStartEndY}
356
+ c 35,10 55,15 60,30
357
+ l ${35 + tipThickness * 1.2},${-10 - tipThickness}
358
+ C 80.47,98.32 50.5,${90 + tipThickness} 20,${trailStartEndY} Z
359
+ `;
360
+ const colorBubblePath = `
361
+ M 72.45,35.67
362
+ A 10,15 41.8 0 1 55,40.2 10,15 41.8 0 1 57.55,22.3 10,15 41.8 0 1 75,17.8 10,15 41.8 0 1 72.5,35.67
363
+ Z
364
+ `;
365
+ let gripMainPath = 'M 85,-25 25,35 h 10 v 10 h 10 v 10 h 10 v 10 h 10 l -5,10 60,-60 z';
366
+ let gripShadow1Path = 'M 25,35 H 35 L 90,-15 85,-25 Z';
367
+ let gripShadow2Path = 'M 60,75 65,65 H 55 l 55,-55 10,5 z';
368
+ if (rounded) {
369
+ gripMainPath = 'M 85,-25 25,35 c 15,0 40,30 35,40 l 60,-60 z';
370
+ gripShadow1Path = 'm 25,35 c 3.92361,0.384473 7.644275,0.980572 10,3 l 55,-53 -5,-10 z';
371
+ gripShadow2Path = 'M 60,75 C 61,66 59,65 56,59 l 54,-54 10,10 z';
327
372
  }
328
- const primaryStrokeTipPath = `M14,63 ${tipCenterPrimaryPath} L88,60 Z`;
329
- const backgroundStrokeTipPath = `M14,63 ${tipCenterBackgroundPath} L88,60 Z`;
330
- icon.innerHTML = `
331
- <defs>
332
- ${checkerboardPatternDef}
333
- </defs>
334
- <g>
335
- <!-- Pen grip -->
373
+ const penTipPath = `M 25,35 ${10 - tipThickness / 4},${70 - tipThickness / 2} 20,75 25,85 60,75 70,55 45,25 Z`;
374
+ const pencilTipColor = Color4.fromHex('#f4d7d7');
375
+ const tipColor = pencilTipColor.mix(Color4.fromString(color), tipThickness / 40 - 0.1).toHexString();
376
+ const ink = `
377
+ <path
378
+ fill="${checkerboardPatternRef}"
379
+ d="${inkTipPath}"
380
+ />
381
+ <path
382
+ fill="${checkerboardPatternRef}"
383
+ d="${inkTrailPath}"
384
+ />
385
+ <path
386
+ fill="${color}"
387
+ d="${inkTipPath}"
388
+ />
389
+ <path
390
+ fill="${color}"
391
+ d="${inkTrailPath}"
392
+ />
393
+ `;
394
+ const penTip = `
395
+ <path
396
+ fill="${checkerboardPatternRef}"
397
+ d="${penTipPath}"
398
+ />
399
+ <path
400
+ fill="${tipColor}"
401
+ stroke="${color}"
402
+ d="${penTipPath}"
403
+ />
404
+ `;
405
+ const grip = `
336
406
  <path
337
- d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
338
407
  ${iconColorStrokeFill}
408
+ d="${gripMainPath}"
409
+ />
410
+
411
+ <!-- shadows -->
412
+ <path
413
+ fill="rgba(150, 150, 150, 0.3)"
414
+ d="${gripShadow1Path}"
339
415
  />
340
- </g>
341
- <g>
342
- <!-- Checkerboard background for slightly transparent pens -->
343
- <path d='${backgroundStrokeTipPath}' fill='${checkerboardPatternRef}'/>
344
-
345
- <!-- Actual pen tip -->
346
416
  <path
347
- d='${primaryStrokeTipPath}'
348
- fill='${color}'
349
- stroke='${color}'
417
+ fill="rgba(100, 100, 100, 0.2)"
418
+ d="${gripShadow2Path}"
350
419
  />
420
+
421
+ <!-- color bubble -->
422
+ <path
423
+ fill="${checkerboardPatternRef}"
424
+ d="${colorBubblePath}"
425
+ />
426
+ <path
427
+ fill="${color}"
428
+ d="${colorBubblePath}"
429
+ />
430
+ `;
431
+ icon.innerHTML = `
432
+ <defs>
433
+ ${checkerboardPatternDef}
434
+ </defs>
435
+ <g>
436
+ ${ink}
437
+ ${penTip}
438
+ ${grip}
351
439
  </g>
352
440
  `;
353
441
  return icon;
@@ -2,9 +2,16 @@ import Editor from '../../Editor';
2
2
  import Eraser from '../../tools/Eraser';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import BaseToolWidget from './BaseToolWidget';
5
+ import { SavedToolbuttonState } from './BaseWidget';
5
6
  export default class EraserToolWidget extends BaseToolWidget {
7
+ private tool;
8
+ private thicknessInput;
6
9
  constructor(editor: Editor, tool: Eraser, localizationTable?: ToolbarLocalization);
7
10
  protected getTitle(): string;
8
11
  protected createIcon(): Element;
9
- protected fillDropdown(_dropdown: HTMLElement): boolean;
12
+ private updateInputs;
13
+ private static nextThicknessInputId;
14
+ protected fillDropdown(dropdown: HTMLElement): boolean;
15
+ serializeState(): SavedToolbuttonState;
16
+ deserializeFrom(state: SavedToolbuttonState): void;
10
17
  }