js-draw 1.9.1 → 1.11.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 (145) hide show
  1. package/dist/Editor.css +48 -1
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +41 -0
  5. package/dist/cjs/Editor.js +9 -0
  6. package/dist/cjs/Pointer.js +1 -1
  7. package/dist/cjs/commands/Erase.d.ts +22 -2
  8. package/dist/cjs/commands/Erase.js +22 -2
  9. package/dist/cjs/commands/invertCommand.js +5 -0
  10. package/dist/cjs/commands/uniteCommands.d.ts +36 -0
  11. package/dist/cjs/commands/uniteCommands.js +36 -0
  12. package/dist/cjs/components/AbstractComponent.d.ts +8 -0
  13. package/dist/cjs/components/AbstractComponent.js +28 -8
  14. package/dist/cjs/components/ImageComponent.d.ts +12 -0
  15. package/dist/cjs/components/ImageComponent.js +16 -9
  16. package/dist/cjs/components/Stroke.d.ts +16 -2
  17. package/dist/cjs/components/Stroke.js +17 -1
  18. package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
  19. package/dist/cjs/components/builders/CircleBuilder.js +3 -3
  20. package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
  21. package/dist/cjs/components/builders/LineBuilder.js +3 -3
  22. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
  23. package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
  24. package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
  25. package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
  26. package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
  27. package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
  28. package/dist/cjs/components/builders/types.d.ts +12 -0
  29. package/dist/cjs/image/EditorImage.d.ts +32 -1
  30. package/dist/cjs/image/EditorImage.js +32 -1
  31. package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
  32. package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
  33. package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
  34. package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
  35. package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
  36. package/dist/cjs/toolbar/IconProvider.js +17 -0
  37. package/dist/cjs/toolbar/localization.d.ts +3 -0
  38. package/dist/cjs/toolbar/localization.js +4 -1
  39. package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
  40. package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
  41. package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
  42. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
  43. package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
  44. package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
  45. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
  46. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
  47. package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
  48. package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
  49. package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
  50. package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
  51. package/dist/cjs/tools/Pen.d.ts +9 -0
  52. package/dist/cjs/tools/Pen.js +82 -3
  53. package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
  54. package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
  55. package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
  56. package/dist/cjs/tools/SelectionTool/SelectionTool.js +19 -1
  57. package/dist/cjs/tools/TextTool.js +5 -1
  58. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
  59. package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
  60. package/dist/cjs/tools/keybindings.d.ts +1 -0
  61. package/dist/cjs/tools/keybindings.js +3 -1
  62. package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
  63. package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
  64. package/dist/cjs/util/ReactiveValue.d.ts +2 -0
  65. package/dist/cjs/util/ReactiveValue.js +2 -0
  66. package/dist/cjs/util/lib.d.ts +1 -0
  67. package/dist/cjs/util/lib.js +4 -1
  68. package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
  69. package/dist/cjs/util/waitForImageLoaded.js +12 -0
  70. package/dist/cjs/version.js +1 -1
  71. package/dist/mjs/Editor.d.ts +41 -0
  72. package/dist/mjs/Editor.mjs +9 -0
  73. package/dist/mjs/Pointer.mjs +1 -1
  74. package/dist/mjs/commands/Erase.d.ts +22 -2
  75. package/dist/mjs/commands/Erase.mjs +22 -2
  76. package/dist/mjs/commands/invertCommand.mjs +5 -0
  77. package/dist/mjs/commands/uniteCommands.d.ts +36 -0
  78. package/dist/mjs/commands/uniteCommands.mjs +36 -0
  79. package/dist/mjs/components/AbstractComponent.d.ts +8 -0
  80. package/dist/mjs/components/AbstractComponent.mjs +28 -8
  81. package/dist/mjs/components/ImageComponent.d.ts +12 -0
  82. package/dist/mjs/components/ImageComponent.mjs +16 -9
  83. package/dist/mjs/components/Stroke.d.ts +16 -2
  84. package/dist/mjs/components/Stroke.mjs +17 -1
  85. package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
  86. package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
  87. package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
  88. package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
  89. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
  90. package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
  91. package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
  92. package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
  93. package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
  94. package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
  95. package/dist/mjs/components/builders/types.d.ts +12 -0
  96. package/dist/mjs/image/EditorImage.d.ts +32 -1
  97. package/dist/mjs/image/EditorImage.mjs +32 -1
  98. package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
  99. package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
  100. package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
  101. package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
  102. package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
  103. package/dist/mjs/toolbar/IconProvider.mjs +17 -0
  104. package/dist/mjs/toolbar/localization.d.ts +3 -0
  105. package/dist/mjs/toolbar/localization.mjs +4 -1
  106. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
  107. package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
  108. package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
  109. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
  110. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
  111. package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
  112. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
  113. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
  114. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
  115. package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
  116. package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
  117. package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
  118. package/dist/mjs/tools/Pen.d.ts +9 -0
  119. package/dist/mjs/tools/Pen.mjs +82 -3
  120. package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
  121. package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
  122. package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
  123. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +20 -2
  124. package/dist/mjs/tools/TextTool.mjs +5 -1
  125. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
  126. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
  127. package/dist/mjs/tools/keybindings.d.ts +1 -0
  128. package/dist/mjs/tools/keybindings.mjs +2 -0
  129. package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
  130. package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
  131. package/dist/mjs/util/ReactiveValue.d.ts +2 -0
  132. package/dist/mjs/util/ReactiveValue.mjs +2 -0
  133. package/dist/mjs/util/lib.d.ts +1 -0
  134. package/dist/mjs/util/lib.mjs +1 -0
  135. package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
  136. package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
  137. package/dist/mjs/version.mjs +1 -1
  138. package/package.json +3 -3
  139. package/src/Editor.scss +7 -0
  140. package/src/toolbar/AbstractToolbar.scss +20 -0
  141. package/src/toolbar/toolbar.scss +1 -1
  142. package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
  143. package/src/toolbar/widgets/PenToolWidget.scss +33 -0
  144. package/src/tools/SelectionTool/SelectionTool.scss +6 -0
  145. package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -6,7 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const math_1 = require("@js-draw/math");
7
7
  const assertions_1 = require("../util/assertions");
8
8
  const AbstractComponent_1 = __importDefault(require("./AbstractComponent"));
9
- // Represents a raster image.
9
+ const waitForImageLoaded_1 = __importDefault(require("../util/waitForImageLoaded"));
10
+ /**
11
+ * Represents a raster image.
12
+ *
13
+ * **Example: Adding images**:
14
+ * [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
15
+ */
10
16
  class ImageComponent extends AbstractComponent_1.default {
11
17
  constructor(image) {
12
18
  super('image-component');
@@ -29,15 +35,14 @@ class ImageComponent extends AbstractComponent_1.default {
29
35
  this.contentBBox = this.getImageRect();
30
36
  this.contentBBox = this.contentBBox.transformedBoundingBox(this.image.transform);
31
37
  }
32
- // Load from an image. Waits for the image to load if incomplete.
38
+ /**
39
+ * Load from an image. Waits for the image to load if incomplete.
40
+ *
41
+ * The image, `elem`, must not [taint](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases)
42
+ * an HTMLCanvasElement when rendered.
43
+ */
33
44
  static async fromImage(elem, transform) {
34
- if (!elem.complete) {
35
- await new Promise((resolve, reject) => {
36
- elem.onload = resolve;
37
- elem.onerror = reject;
38
- elem.onabort = reject;
39
- });
40
- }
45
+ await (0, waitForImageLoaded_1.default)(elem);
41
46
  let width, height;
42
47
  if (typeof elem.width === 'number' && typeof elem.height === 'number'
43
48
  && elem.width !== 0 && elem.height !== 0) {
@@ -79,6 +84,7 @@ class ImageComponent extends AbstractComponent_1.default {
79
84
  canvas.drawImage(this.image);
80
85
  canvas.endObject(this.getLoadSaveData());
81
86
  }
87
+ // A *very* rough estimate of how long it takes to render this component
82
88
  getProportionalRenderingTime() {
83
89
  // Estimate: Equivalent to a stroke with 10 segments.
84
90
  return 10;
@@ -103,6 +109,7 @@ class ImageComponent extends AbstractComponent_1.default {
103
109
  getAltText() {
104
110
  return this.image.label;
105
111
  }
112
+ // The base64 image URL of this image.
106
113
  getURL() {
107
114
  return this.image.base64Url;
108
115
  }
@@ -5,7 +5,7 @@ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
5
  import AbstractComponent from './AbstractComponent';
6
6
  import { ImageComponentLocalization } from './localization';
7
7
  import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
8
- import RenderablePathSpec from '../rendering/RenderablePathSpec';
8
+ import RenderablePathSpec, { RenderablePathSpecWithPath } from '../rendering/RenderablePathSpec';
9
9
  /**
10
10
  * Represents an {@link AbstractComponent} made up of one or more {@link Path}s.
11
11
  *
@@ -21,6 +21,9 @@ import RenderablePathSpec from '../rendering/RenderablePathSpec';
21
21
  * ```ts
22
22
  * editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
23
23
  * ```
24
+ *
25
+ * **Adding**:
26
+ * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
24
27
  */
25
28
  export default class Stroke extends AbstractComponent implements RestyleableComponent {
26
29
  private parts;
@@ -39,7 +42,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
39
42
  *
40
43
  * const stroke = new Stroke([
41
44
  * // Fill with red
42
- * pathToRenderable({ fill: Color4.red })
45
+ * pathToRenderable(path, { fill: Color4.red })
43
46
  * ]);
44
47
  * ```
45
48
  */
@@ -57,6 +60,17 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
57
60
  private bboxForPart;
58
61
  getExactBBox(): Rect2;
59
62
  protected applyTransformation(affineTransfm: Mat33): void;
63
+ /**
64
+ * @returns A list of the parts that make up this path. Many paths only have one part.
65
+ *
66
+ * Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
67
+ * of that part of the stroke. Use the `.path` property to do collision detection and other
68
+ * operations involving the stroke's geometry.
69
+ *
70
+ * Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
71
+ * `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
72
+ */
73
+ getParts(): Readonly<RenderablePathSpecWithPath>[];
60
74
  /**
61
75
  * @returns the {@link Path.union} of all paths that make up this stroke.
62
76
  */
@@ -23,6 +23,9 @@ const RenderablePathSpec_1 = require("../rendering/RenderablePathSpec");
23
23
  * ```ts
24
24
  * editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
25
25
  * ```
26
+ *
27
+ * **Adding**:
28
+ * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
26
29
  */
27
30
  class Stroke extends AbstractComponent_1.default {
28
31
  /**
@@ -37,7 +40,7 @@ class Stroke extends AbstractComponent_1.default {
37
40
  *
38
41
  * const stroke = new Stroke([
39
42
  * // Fill with red
40
- * pathToRenderable({ fill: Color4.red })
43
+ * pathToRenderable(path, { fill: Color4.red })
41
44
  * ]);
42
45
  * ```
43
46
  */
@@ -295,6 +298,19 @@ class Stroke extends AbstractComponent_1.default {
295
298
  };
296
299
  });
297
300
  }
301
+ /**
302
+ * @returns A list of the parts that make up this path. Many paths only have one part.
303
+ *
304
+ * Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
305
+ * of that part of the stroke. Use the `.path` property to do collision detection and other
306
+ * operations involving the stroke's geometry.
307
+ *
308
+ * Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
309
+ * `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
310
+ */
311
+ getParts() {
312
+ return [...this.parts];
313
+ }
298
314
  /**
299
315
  * @returns the {@link Path.union} of all paths that make up this stroke.
300
316
  */
@@ -6,10 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.makeArrowBuilder = void 0;
7
7
  const math_1 = require("@js-draw/math");
8
8
  const Stroke_1 = __importDefault(require("../Stroke"));
9
- const makeArrowBuilder = (initialPoint, viewport) => {
9
+ const makeSnapToGridAutocorrect_1 = __importDefault(require("./autocorrect/makeSnapToGridAutocorrect"));
10
+ exports.makeArrowBuilder = (0, makeSnapToGridAutocorrect_1.default)((initialPoint, viewport) => {
10
11
  return new ArrowBuilder(initialPoint, viewport);
11
- };
12
- exports.makeArrowBuilder = makeArrowBuilder;
12
+ });
13
13
  class ArrowBuilder {
14
14
  constructor(startPoint, viewport) {
15
15
  this.startPoint = startPoint;
@@ -8,10 +8,10 @@ const math_1 = require("@js-draw/math");
8
8
  const RenderablePathSpec_1 = require("../../rendering/RenderablePathSpec");
9
9
  const Viewport_1 = __importDefault(require("../../Viewport"));
10
10
  const Stroke_1 = __importDefault(require("../Stroke"));
11
- const makeOutlinedCircleBuilder = (initialPoint, viewport) => {
11
+ const makeSnapToGridAutocorrect_1 = __importDefault(require("./autocorrect/makeSnapToGridAutocorrect"));
12
+ exports.makeOutlinedCircleBuilder = (0, makeSnapToGridAutocorrect_1.default)((initialPoint, viewport) => {
12
13
  return new CircleBuilder(initialPoint, viewport);
13
- };
14
- exports.makeOutlinedCircleBuilder = makeOutlinedCircleBuilder;
14
+ });
15
15
  class CircleBuilder {
16
16
  constructor(startPoint, viewport) {
17
17
  this.startPoint = startPoint;
@@ -8,14 +8,14 @@ const math_1 = require("@js-draw/math");
8
8
  const Stroke_1 = __importDefault(require("../Stroke"));
9
9
  const Viewport_1 = __importDefault(require("../../Viewport"));
10
10
  const StrokeSmoother_1 = require("../util/StrokeSmoother");
11
- const makeFreehandLineBuilder = (initialPoint, viewport) => {
11
+ const makeShapeFitAutocorrect_1 = __importDefault(require("./autocorrect/makeShapeFitAutocorrect"));
12
+ exports.makeFreehandLineBuilder = (0, makeShapeFitAutocorrect_1.default)((initialPoint, viewport) => {
12
13
  // Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
13
14
  // less than ±1 px from the curve.
14
15
  const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
15
16
  const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
16
17
  return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
17
- };
18
- exports.makeFreehandLineBuilder = makeFreehandLineBuilder;
18
+ });
19
19
  // Handles stroke smoothing and creates Strokes from user/stylus input.
20
20
  class FreehandLineBuilder {
21
21
  constructor(startPoint, minFitAllowed, maxFitAllowed, viewport) {
@@ -7,10 +7,10 @@ exports.makeLineBuilder = void 0;
7
7
  const math_1 = require("@js-draw/math");
8
8
  const RenderablePathSpec_1 = require("../../rendering/RenderablePathSpec");
9
9
  const Stroke_1 = __importDefault(require("../Stroke"));
10
- const makeLineBuilder = (initialPoint, viewport) => {
10
+ const makeSnapToGridAutocorrect_1 = __importDefault(require("./autocorrect/makeSnapToGridAutocorrect"));
11
+ exports.makeLineBuilder = (0, makeSnapToGridAutocorrect_1.default)((initialPoint, viewport) => {
11
12
  return new LineBuilder(initialPoint, viewport);
12
- };
13
- exports.makeLineBuilder = makeLineBuilder;
13
+ });
14
14
  class LineBuilder {
15
15
  constructor(startPoint, viewport) {
16
16
  this.startPoint = startPoint;
@@ -9,14 +9,14 @@ const math_1 = require("@js-draw/math");
9
9
  const Stroke_1 = __importDefault(require("../Stroke"));
10
10
  const Viewport_1 = __importDefault(require("../../Viewport"));
11
11
  const StrokeSmoother_1 = require("../util/StrokeSmoother");
12
- const makePressureSensitiveFreehandLineBuilder = (initialPoint, viewport) => {
12
+ const makeShapeFitAutocorrect_1 = __importDefault(require("./autocorrect/makeShapeFitAutocorrect"));
13
+ exports.makePressureSensitiveFreehandLineBuilder = (0, makeShapeFitAutocorrect_1.default)((initialPoint, viewport) => {
13
14
  // Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
14
15
  // less than ±1 px from the curve.
15
16
  const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
16
17
  const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
17
18
  return new PressureSensitiveFreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
18
- };
19
- exports.makePressureSensitiveFreehandLineBuilder = makePressureSensitiveFreehandLineBuilder;
19
+ });
20
20
  // Handles stroke smoothing and creates Strokes from user/stylus input.
21
21
  class PressureSensitiveFreehandLineBuilder {
22
22
  constructor(startPoint,
@@ -7,14 +7,13 @@ exports.makeOutlinedRectangleBuilder = exports.makeFilledRectangleBuilder = void
7
7
  const math_1 = require("@js-draw/math");
8
8
  const RenderablePathSpec_1 = require("../../rendering/RenderablePathSpec");
9
9
  const Stroke_1 = __importDefault(require("../Stroke"));
10
- const makeFilledRectangleBuilder = (initialPoint, viewport) => {
10
+ const makeSnapToGridAutocorrect_1 = __importDefault(require("./autocorrect/makeSnapToGridAutocorrect"));
11
+ exports.makeFilledRectangleBuilder = (0, makeSnapToGridAutocorrect_1.default)((initialPoint, viewport) => {
11
12
  return new RectangleBuilder(initialPoint, true, viewport);
12
- };
13
- exports.makeFilledRectangleBuilder = makeFilledRectangleBuilder;
14
- const makeOutlinedRectangleBuilder = (initialPoint, viewport) => {
13
+ });
14
+ exports.makeOutlinedRectangleBuilder = (0, makeSnapToGridAutocorrect_1.default)((initialPoint, viewport) => {
15
15
  return new RectangleBuilder(initialPoint, false, viewport);
16
- };
17
- exports.makeOutlinedRectangleBuilder = makeOutlinedRectangleBuilder;
16
+ });
18
17
  class RectangleBuilder {
19
18
  constructor(startPoint, filled, viewport) {
20
19
  this.startPoint = startPoint;
@@ -0,0 +1,3 @@
1
+ import { ComponentBuilderFactory } from '../types';
2
+ declare const makeShapeFitAutocorrect: (sourceFactory: ComponentBuilderFactory) => ComponentBuilderFactory;
3
+ export default makeShapeFitAutocorrect;
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const math_1 = require("@js-draw/math");
4
+ const makeShapeFitAutocorrect = (sourceFactory) => {
5
+ return (startPoint, viewport) => {
6
+ return new ShapeFitBuilder(sourceFactory, startPoint, viewport);
7
+ };
8
+ };
9
+ exports.default = makeShapeFitAutocorrect;
10
+ const makeLineTemplate = (startPoint, points, _bbox) => {
11
+ const templatePoints = [
12
+ startPoint,
13
+ points[points.length - 1],
14
+ ];
15
+ return { points: templatePoints, };
16
+ };
17
+ const makeRectangleTemplate = (_startPoint, _points, bbox) => {
18
+ return { points: [...bbox.corners, bbox.corners[0]], };
19
+ };
20
+ class ShapeFitBuilder {
21
+ constructor(sourceFactory, startPoint, viewport) {
22
+ this.sourceFactory = sourceFactory;
23
+ this.startPoint = startPoint;
24
+ this.viewport = viewport;
25
+ this.builder = sourceFactory(startPoint, viewport);
26
+ this.points = [startPoint];
27
+ }
28
+ getBBox() {
29
+ return this.builder.getBBox();
30
+ }
31
+ build() {
32
+ return this.builder.build();
33
+ }
34
+ preview(renderer) {
35
+ this.builder.preview(renderer);
36
+ }
37
+ addPoint(point) {
38
+ this.points.push(point);
39
+ this.builder.addPoint(point);
40
+ }
41
+ async autocorrectShape() {
42
+ // Use screen points so that autocorrected shapes rotate with the screen.
43
+ const startPoint = this.viewport.canvasToScreen(this.startPoint.pos);
44
+ const points = this.points.map(point => this.viewport.canvasToScreen(point.pos));
45
+ const bbox = math_1.Rect2.bboxOf(points);
46
+ const snappedStartPoint = this.viewport.canvasToScreen(this.viewport.snapToGrid(this.startPoint.pos));
47
+ const snappedPoints = this.points.map(point => this.viewport.canvasToScreen(this.viewport.snapToGrid(point.pos)));
48
+ const snappedBBox = math_1.Rect2.bboxOf(snappedPoints);
49
+ // Only fit larger shapes
50
+ if (bbox.maxDimension < 32) {
51
+ return null;
52
+ }
53
+ const maxError = Math.min(30, bbox.maxDimension / 4);
54
+ // Create templates
55
+ const templates = [
56
+ {
57
+ ...makeLineTemplate(snappedStartPoint, snappedPoints, snappedBBox),
58
+ toleranceMultiplier: 0.5,
59
+ },
60
+ makeLineTemplate(startPoint, points, bbox),
61
+ {
62
+ ...makeRectangleTemplate(snappedStartPoint, snappedPoints, snappedBBox),
63
+ toleranceMultiplier: 0.6,
64
+ },
65
+ makeRectangleTemplate(startPoint, points, bbox),
66
+ ];
67
+ // Find a good fit fit
68
+ const selectTemplate = (maximumAllowedError) => {
69
+ for (const template of templates) {
70
+ const templatePoints = template.points;
71
+ // Maximum square error to accept the template
72
+ const acceptMaximumSquareError = maximumAllowedError * maximumAllowedError * (template.toleranceMultiplier ?? 1);
73
+ // Gets the point at index, wrapping the the start of the template if
74
+ // outside the array of points.
75
+ const templateAt = (index) => {
76
+ while (index < 0) {
77
+ index += templatePoints.length;
78
+ }
79
+ index %= templatePoints.length;
80
+ return templatePoints[index];
81
+ };
82
+ let closestToFirst = null;
83
+ let closestToFirstSqrDist = Infinity;
84
+ let templateStartIndex = 0;
85
+ // Find the closest point to the startPoint
86
+ for (let i = 0; i < templatePoints.length; i++) {
87
+ const current = templatePoints[i];
88
+ const currentSqrDist = current.minus(startPoint).magnitudeSquared();
89
+ if (!closestToFirst || currentSqrDist < closestToFirstSqrDist) {
90
+ closestToFirstSqrDist = currentSqrDist;
91
+ closestToFirst = current;
92
+ templateStartIndex = i;
93
+ }
94
+ }
95
+ // Walk through the points and find the maximum error
96
+ let maximumSqrError = 0;
97
+ let templateIndex = templateStartIndex;
98
+ for (const point of points) {
99
+ let minimumCurrentSqrError = Infinity;
100
+ let minimumErrorAtIndex = templateIndex;
101
+ const windowRadius = 6;
102
+ for (let i = -windowRadius; i <= windowRadius; i++) {
103
+ const index = templateIndex + i;
104
+ const prevTemplatePoint = templateAt(index - 1);
105
+ const currentTemplatePoint = templateAt(index);
106
+ const nextTemplatePoint = templateAt(index + 1);
107
+ const prevToCurrent = new math_1.LineSegment2(prevTemplatePoint, currentTemplatePoint);
108
+ const currentToNext = new math_1.LineSegment2(currentTemplatePoint, nextTemplatePoint);
109
+ const prevToCurrentDist = prevToCurrent.distance(point);
110
+ const nextToCurrentDist = currentToNext.distance(point);
111
+ const error = Math.min(prevToCurrentDist, nextToCurrentDist);
112
+ const squareError = error * error;
113
+ if (squareError < minimumCurrentSqrError) {
114
+ minimumCurrentSqrError = squareError;
115
+ minimumErrorAtIndex = index;
116
+ }
117
+ }
118
+ templateIndex = minimumErrorAtIndex;
119
+ maximumSqrError = Math.max(minimumCurrentSqrError, maximumSqrError);
120
+ if (maximumSqrError > acceptMaximumSquareError) {
121
+ break;
122
+ }
123
+ }
124
+ if (maximumSqrError < acceptMaximumSquareError) {
125
+ return templatePoints;
126
+ }
127
+ }
128
+ return null;
129
+ };
130
+ const template = selectTemplate(maxError);
131
+ if (!template) {
132
+ return null;
133
+ }
134
+ const lastDataPoint = this.points[this.points.length - 1];
135
+ const startWidth = this.startPoint.width;
136
+ const endWidth = lastDataPoint.width;
137
+ const startColor = this.startPoint.color;
138
+ const endColor = lastDataPoint.color;
139
+ const startTime = this.startPoint.time;
140
+ const endTime = lastDataPoint.time;
141
+ const templateIndexToStrokeDataPoint = (index) => {
142
+ const prevPoint = template[Math.max(0, Math.floor(index))];
143
+ const nextPoint = template[Math.min(Math.ceil(index), template.length - 1)];
144
+ const point = prevPoint.lerp(nextPoint, index - Math.floor(index));
145
+ const fractionToEnd = index / template.length;
146
+ return {
147
+ pos: this.viewport.screenToCanvas(point),
148
+ width: startWidth * (1 - fractionToEnd) + endWidth * fractionToEnd,
149
+ color: startColor.mix(endColor, fractionToEnd),
150
+ time: startTime * (1 - fractionToEnd) + endTime * fractionToEnd,
151
+ };
152
+ };
153
+ const builder = this.sourceFactory(templateIndexToStrokeDataPoint(0), this.viewport);
154
+ // Prevent the original builder from doing stroke smoothing if the template is short
155
+ // enough to likely have sharp corners.
156
+ const preventSmoothing = template.length < 10;
157
+ for (let i = 0; i < template.length; i++) {
158
+ if (preventSmoothing) {
159
+ builder.addPoint(templateIndexToStrokeDataPoint(i - 0.001));
160
+ }
161
+ builder.addPoint(templateIndexToStrokeDataPoint(i));
162
+ if (preventSmoothing) {
163
+ builder.addPoint(templateIndexToStrokeDataPoint(i + 0.001));
164
+ }
165
+ }
166
+ return builder.build();
167
+ }
168
+ }
@@ -0,0 +1,3 @@
1
+ import { ComponentBuilderFactory } from '../types';
2
+ declare const makeSnapToGridAutocorrect: (sourceFactory: ComponentBuilderFactory) => ComponentBuilderFactory;
3
+ export default makeSnapToGridAutocorrect;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const makeSnapToGridAutocorrect = (sourceFactory) => {
4
+ return (startPoint, viewport) => {
5
+ return new SnapToGridAutocompleteBuilder(sourceFactory, startPoint, viewport);
6
+ };
7
+ };
8
+ exports.default = makeSnapToGridAutocorrect;
9
+ class SnapToGridAutocompleteBuilder {
10
+ constructor(sourceFactory, startPoint, viewport) {
11
+ this.sourceFactory = sourceFactory;
12
+ this.startPoint = startPoint;
13
+ this.viewport = viewport;
14
+ this.builder = sourceFactory(startPoint, viewport);
15
+ this.points = [startPoint];
16
+ }
17
+ getBBox() {
18
+ return this.builder.getBBox();
19
+ }
20
+ build() {
21
+ return this.builder.build();
22
+ }
23
+ preview(renderer) {
24
+ this.builder.preview(renderer);
25
+ }
26
+ addPoint(point) {
27
+ this.points.push(point);
28
+ this.builder.addPoint(point);
29
+ }
30
+ async autocorrectShape() {
31
+ const snapToGrid = (point) => {
32
+ return {
33
+ ...point,
34
+ pos: this.viewport.snapToGrid(point.pos),
35
+ };
36
+ };
37
+ // Use screen points so that snapped shapes rotate with the screen.
38
+ const startPoint = snapToGrid(this.startPoint);
39
+ const builder = this.sourceFactory(startPoint, this.viewport);
40
+ const points = this.points.map(point => snapToGrid(point));
41
+ for (const point of points) {
42
+ builder.addPoint(point);
43
+ }
44
+ return builder.build();
45
+ }
46
+ }
@@ -7,6 +7,18 @@ export interface ComponentBuilder {
7
7
  getBBox(): Rect2;
8
8
  build(): AbstractComponent;
9
9
  preview(renderer: AbstractRenderer): void;
10
+ /**
11
+ * Called when the pen is stationary (or the user otherwise
12
+ * activates autocomplete). This might attempt to fit the user's
13
+ * drawing to a particular shape.
14
+ *
15
+ * The shape returned by this function may be ignored if it has
16
+ * an empty bounding box.
17
+ *
18
+ * Although this returns a Promise, it should return *as fast as
19
+ * possible*.
20
+ */
21
+ autocorrectShape?: () => Promise<AbstractComponent | null>;
10
22
  addPoint(point: StrokeDataPoint): void;
11
23
  }
12
24
  export type ComponentBuilderFactory = (startPoint: StrokeDataPoint, viewport: Viewport) => ComponentBuilder;
@@ -87,6 +87,9 @@ export default class EditorImage {
87
87
  * rendered onto the main rendering canvas instead of doing a full re-render.
88
88
  *
89
89
  * @see {@link Display.flatten}
90
+ *
91
+ * @example
92
+ * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
90
93
  */
91
94
  static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
92
95
  /** @see EditorImage.addElement */
@@ -105,8 +108,36 @@ export default class EditorImage {
105
108
  * autoresize (if it was previously enabled).
106
109
  */
107
110
  setImportExportRect(imageRect: Rect2): SerializableCommand;
111
+ /** @see {@link setAutoresizeEnabled} */
108
112
  getAutoresizeEnabled(): boolean;
109
- /** Returns a `Command` that sets whether the image should autoresize. */
113
+ /**
114
+ * Returns a `Command` that sets whether the image should autoresize when
115
+ * {@link AbstractComponent}s are added/removed.
116
+ *
117
+ * @example
118
+ *
119
+ * ```ts,runnable
120
+ * import { Editor } from 'js-draw';
121
+ *
122
+ * const editor = new Editor(document.body);
123
+ * const toolbar = editor.addToolbar();
124
+ *
125
+ * // Add a save button to demonstrate what the output looks like
126
+ * // (it should change size to fit whatever was drawn)
127
+ * toolbar.addSaveButton(() => {
128
+ * document.body.replaceChildren(editor.toSVG({ sanitize: true }));
129
+ * });
130
+ *
131
+ * // Actually using setAutoresizeEnabled:
132
+ * //
133
+ * // To set autoresize without announcing for accessibility/making undoable
134
+ * const addToHistory = false;
135
+ * editor.dispatchNoAnnounce(editor.image.setAutoresizeEnabled(true), addToHistory);
136
+ *
137
+ * // Add to undo history **and** announce for accessibility
138
+ * //editor.dispatch(editor.image.setAutoresizeEnabled(true), true);
139
+ * ```
140
+ */
110
141
  setAutoresizeEnabled(autoresize: boolean): Command;
111
142
  private setAutoresizeEnabledDirectly;
112
143
  /** Updates the size/position of the viewport */
@@ -210,6 +210,9 @@ class EditorImage {
210
210
  * rendered onto the main rendering canvas instead of doing a full re-render.
211
211
  *
212
212
  * @see {@link Display.flatten}
213
+ *
214
+ * @example
215
+ * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
213
216
  */
214
217
  static addElement(elem, applyByFlattening = false) {
215
218
  return new _a.AddElementCommand(elem, applyByFlattening);
@@ -237,10 +240,38 @@ class EditorImage {
237
240
  setImportExportRect(imageRect) {
238
241
  return _a.SetImportExportRectCommand.of(this, imageRect, false);
239
242
  }
243
+ /** @see {@link setAutoresizeEnabled} */
240
244
  getAutoresizeEnabled() {
241
245
  return this.shouldAutoresizeExportViewport;
242
246
  }
243
- /** Returns a `Command` that sets whether the image should autoresize. */
247
+ /**
248
+ * Returns a `Command` that sets whether the image should autoresize when
249
+ * {@link AbstractComponent}s are added/removed.
250
+ *
251
+ * @example
252
+ *
253
+ * ```ts,runnable
254
+ * import { Editor } from 'js-draw';
255
+ *
256
+ * const editor = new Editor(document.body);
257
+ * const toolbar = editor.addToolbar();
258
+ *
259
+ * // Add a save button to demonstrate what the output looks like
260
+ * // (it should change size to fit whatever was drawn)
261
+ * toolbar.addSaveButton(() => {
262
+ * document.body.replaceChildren(editor.toSVG({ sanitize: true }));
263
+ * });
264
+ *
265
+ * // Actually using setAutoresizeEnabled:
266
+ * //
267
+ * // To set autoresize without announcing for accessibility/making undoable
268
+ * const addToHistory = false;
269
+ * editor.dispatchNoAnnounce(editor.image.setAutoresizeEnabled(true), addToHistory);
270
+ *
271
+ * // Add to undo history **and** announce for accessibility
272
+ * //editor.dispatch(editor.image.setAutoresizeEnabled(true), true);
273
+ * ```
274
+ */
244
275
  setAutoresizeEnabled(autoresize) {
245
276
  if (autoresize === this.shouldAutoresizeExportViewport) {
246
277
  return Command_1.default.empty;
@@ -6,11 +6,15 @@ interface RenderablePathSpec {
6
6
  style: RenderingStyle;
7
7
  path?: Path;
8
8
  }
9
- interface RenderablePathSpecWithPath extends RenderablePathSpec {
9
+ export interface RenderablePathSpecWithPath extends RenderablePathSpec {
10
10
  path: Path;
11
11
  }
12
12
  /** Converts a renderable path (a path with a `startPoint`, `commands`, and `style`). */
13
13
  export declare const pathFromRenderable: (renderable: RenderablePathSpec) => Path;
14
+ /**
15
+ * Converts `path` into a format that can be rendered (by passing to a {@link Stroke} constructor
16
+ * or directly to an {@link AbstractRenderer.drawPath}).
17
+ */
14
18
  export declare const pathToRenderable: (path: Path, style: RenderingStyle) => RenderablePathSpecWithPath;
15
19
  interface RectangleSimplificationResult {
16
20
  rectangle: Rect2;
@@ -10,6 +10,10 @@ const pathFromRenderable = (renderable) => {
10
10
  return new math_1.Path(renderable.startPoint, renderable.commands);
11
11
  };
12
12
  exports.pathFromRenderable = pathFromRenderable;
13
+ /**
14
+ * Converts `path` into a format that can be rendered (by passing to a {@link Stroke} constructor
15
+ * or directly to an {@link AbstractRenderer.drawPath}).
16
+ */
13
17
  const pathToRenderable = (path, style) => {
14
18
  return {
15
19
  startPoint: path.startPoint,
@@ -3,6 +3,7 @@ import { ToolbarLocalization } from './localization';
3
3
  import { ActionButtonIcon } from './types';
4
4
  import BaseWidget, { ToolbarWidgetTag } from './widgets/BaseWidget';
5
5
  import { DispatcherEventListener } from '../EventDispatcher';
6
+ import { BaseTool } from '../lib';
6
7
  export interface SpacerOptions {
7
8
  grow: number;
8
9
  minSize: string;
@@ -131,7 +132,7 @@ export default abstract class AbstractToolbar {
131
132
  /**
132
133
  * Adds an "Exit" button that, when clicked, calls `exitCallback`.
133
134
  *
134
- * **Note**: This is roughly equivalent to
135
+ * **Note**: This is *roughly* equivalent to
135
136
  * ```ts
136
137
  * toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
137
138
  * label: this.editor.localization.exit,
@@ -154,9 +155,24 @@ export default abstract class AbstractToolbar {
154
155
  */
155
156
  addUndoRedoButtons(undoFirst?: boolean): void;
156
157
  /**
157
- * Adds toolbar widgets based on the enabled tools.
158
+ * Adds widgets for pen/eraser/selection/text/pan-zoom primary tools.
159
+ *
160
+ * If `filter` returns `false` for a tool, no widget is added for that tool.
161
+ * See {@link addDefaultToolWidgets}
162
+ */
163
+ addWidgetsForPrimaryTools(filter?: (tool: BaseTool) => boolean): void;
164
+ /**
165
+ * Adds toolbar widgets based on the enabled tools, and additional tool-like
166
+ * buttons (e.g. {@link DocumentPropertiesWidget} and {@link InsertImageWidget}).
158
167
  */
159
168
  addDefaultToolWidgets(): void;
169
+ /**
170
+ * Adds widgets that don't correspond to tools, but do allow the user to control
171
+ * the editor in some way.
172
+ *
173
+ * By default, this includes {@link DocumentPropertiesWidget} and {@link InsertImageWidget}.
174
+ */
175
+ addDefaultEditorControlWidgets(): void;
160
176
  addDefaultActionButtons(): void;
161
177
  /**
162
178
  * Adds both the default tool widgets and action buttons.