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.
- package/dist/Editor.css +48 -1
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +41 -0
- package/dist/cjs/Editor.js +9 -0
- package/dist/cjs/Pointer.js +1 -1
- package/dist/cjs/commands/Erase.d.ts +22 -2
- package/dist/cjs/commands/Erase.js +22 -2
- package/dist/cjs/commands/invertCommand.js +5 -0
- package/dist/cjs/commands/uniteCommands.d.ts +36 -0
- package/dist/cjs/commands/uniteCommands.js +36 -0
- package/dist/cjs/components/AbstractComponent.d.ts +8 -0
- package/dist/cjs/components/AbstractComponent.js +28 -8
- package/dist/cjs/components/ImageComponent.d.ts +12 -0
- package/dist/cjs/components/ImageComponent.js +16 -9
- package/dist/cjs/components/Stroke.d.ts +16 -2
- package/dist/cjs/components/Stroke.js +17 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
- package/dist/cjs/components/builders/CircleBuilder.js +3 -3
- package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/LineBuilder.js +3 -3
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
- package/dist/cjs/components/builders/types.d.ts +12 -0
- package/dist/cjs/image/EditorImage.d.ts +32 -1
- package/dist/cjs/image/EditorImage.js +32 -1
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
- package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/cjs/toolbar/IconProvider.js +17 -0
- package/dist/cjs/toolbar/localization.d.ts +3 -0
- package/dist/cjs/toolbar/localization.js +4 -1
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
- package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
- package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
- package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
- package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/cjs/tools/Pen.d.ts +9 -0
- package/dist/cjs/tools/Pen.js +82 -3
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +19 -1
- package/dist/cjs/tools/TextTool.js +5 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
- package/dist/cjs/tools/keybindings.d.ts +1 -0
- package/dist/cjs/tools/keybindings.js +3 -1
- package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
- package/dist/cjs/util/ReactiveValue.d.ts +2 -0
- package/dist/cjs/util/ReactiveValue.js +2 -0
- package/dist/cjs/util/lib.d.ts +1 -0
- package/dist/cjs/util/lib.js +4 -1
- package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/cjs/util/waitForImageLoaded.js +12 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +41 -0
- package/dist/mjs/Editor.mjs +9 -0
- package/dist/mjs/Pointer.mjs +1 -1
- package/dist/mjs/commands/Erase.d.ts +22 -2
- package/dist/mjs/commands/Erase.mjs +22 -2
- package/dist/mjs/commands/invertCommand.mjs +5 -0
- package/dist/mjs/commands/uniteCommands.d.ts +36 -0
- package/dist/mjs/commands/uniteCommands.mjs +36 -0
- package/dist/mjs/components/AbstractComponent.d.ts +8 -0
- package/dist/mjs/components/AbstractComponent.mjs +28 -8
- package/dist/mjs/components/ImageComponent.d.ts +12 -0
- package/dist/mjs/components/ImageComponent.mjs +16 -9
- package/dist/mjs/components/Stroke.d.ts +16 -2
- package/dist/mjs/components/Stroke.mjs +17 -1
- package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
- package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
- package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
- package/dist/mjs/components/builders/types.d.ts +12 -0
- package/dist/mjs/image/EditorImage.d.ts +32 -1
- package/dist/mjs/image/EditorImage.mjs +32 -1
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
- package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/mjs/toolbar/IconProvider.mjs +17 -0
- package/dist/mjs/toolbar/localization.d.ts +3 -0
- package/dist/mjs/toolbar/localization.mjs +4 -1
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
- package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
- package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
- package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
- package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/mjs/tools/Pen.d.ts +9 -0
- package/dist/mjs/tools/Pen.mjs +82 -3
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +20 -2
- package/dist/mjs/tools/TextTool.mjs +5 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
- package/dist/mjs/tools/keybindings.d.ts +1 -0
- package/dist/mjs/tools/keybindings.mjs +2 -0
- package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
- package/dist/mjs/util/ReactiveValue.d.ts +2 -0
- package/dist/mjs/util/ReactiveValue.mjs +2 -0
- package/dist/mjs/util/lib.d.ts +1 -0
- package/dist/mjs/util/lib.mjs +1 -0
- package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -3
- package/src/Editor.scss +7 -0
- package/src/toolbar/AbstractToolbar.scss +20 -0
- package/src/toolbar/toolbar.scss +1 -1
- package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
- package/src/toolbar/widgets/PenToolWidget.scss +33 -0
- package/src/tools/SelectionTool/SelectionTool.scss +6 -0
- package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -18,6 +18,9 @@ import { pathFromRenderable, pathToRenderable, simplifyPathToFullScreenOrEmpty
|
|
18
18
|
* ```ts
|
19
19
|
* editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
|
20
20
|
* ```
|
21
|
+
*
|
22
|
+
* **Adding**:
|
23
|
+
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
|
21
24
|
*/
|
22
25
|
export default class Stroke extends AbstractComponent {
|
23
26
|
/**
|
@@ -32,7 +35,7 @@ export default class Stroke extends AbstractComponent {
|
|
32
35
|
*
|
33
36
|
* const stroke = new Stroke([
|
34
37
|
* // Fill with red
|
35
|
-
* pathToRenderable({ fill: Color4.red })
|
38
|
+
* pathToRenderable(path, { fill: Color4.red })
|
36
39
|
* ]);
|
37
40
|
* ```
|
38
41
|
*/
|
@@ -290,6 +293,19 @@ export default class Stroke extends AbstractComponent {
|
|
290
293
|
};
|
291
294
|
});
|
292
295
|
}
|
296
|
+
/**
|
297
|
+
* @returns A list of the parts that make up this path. Many paths only have one part.
|
298
|
+
*
|
299
|
+
* Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
|
300
|
+
* of that part of the stroke. Use the `.path` property to do collision detection and other
|
301
|
+
* operations involving the stroke's geometry.
|
302
|
+
*
|
303
|
+
* Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
|
304
|
+
* `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
|
305
|
+
*/
|
306
|
+
getParts() {
|
307
|
+
return [...this.parts];
|
308
|
+
}
|
293
309
|
/**
|
294
310
|
* @returns the {@link Path.union} of all paths that make up this stroke.
|
295
311
|
*/
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import { Path, PathCommandType } from '@js-draw/math';
|
2
2
|
import Stroke from '../Stroke.mjs';
|
3
|
-
|
3
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
4
|
+
export const makeArrowBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
4
5
|
return new ArrowBuilder(initialPoint, viewport);
|
5
|
-
};
|
6
|
+
});
|
6
7
|
export default class ArrowBuilder {
|
7
8
|
constructor(startPoint, viewport) {
|
8
9
|
this.startPoint = startPoint;
|
@@ -2,9 +2,10 @@ import { Vec2, Path, PathCommandType, Color4 } from '@js-draw/math';
|
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Viewport from '../../Viewport.mjs';
|
4
4
|
import Stroke from '../Stroke.mjs';
|
5
|
-
|
5
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
6
|
+
export const makeOutlinedCircleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
6
7
|
return new CircleBuilder(initialPoint, viewport);
|
7
|
-
};
|
8
|
+
});
|
8
9
|
class CircleBuilder {
|
9
10
|
constructor(startPoint, viewport) {
|
10
11
|
this.startPoint = startPoint;
|
@@ -2,13 +2,14 @@ import { Vec2, Rect2, Color4, PathCommandType } from '@js-draw/math';
|
|
2
2
|
import Stroke from '../Stroke.mjs';
|
3
3
|
import Viewport from '../../Viewport.mjs';
|
4
4
|
import { StrokeSmoother } from '../util/StrokeSmoother.mjs';
|
5
|
-
|
5
|
+
import makeShapeFitAutocorrect from './autocorrect/makeShapeFitAutocorrect.mjs';
|
6
|
+
export const makeFreehandLineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
|
6
7
|
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
7
8
|
// less than ±1 px from the curve.
|
8
9
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
9
10
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
10
11
|
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
|
11
|
-
};
|
12
|
+
});
|
12
13
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
13
14
|
export default class FreehandLineBuilder {
|
14
15
|
constructor(startPoint, minFitAllowed, maxFitAllowed, viewport) {
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { Path, PathCommandType } from '@js-draw/math';
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
|
-
|
4
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
5
|
+
export const makeLineBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
5
6
|
return new LineBuilder(initialPoint, viewport);
|
6
|
-
};
|
7
|
+
});
|
7
8
|
export default class LineBuilder {
|
8
9
|
constructor(startPoint, viewport) {
|
9
10
|
this.startPoint = startPoint;
|
@@ -3,13 +3,14 @@ import { Vec2, Rect2, PathCommandType } from '@js-draw/math';
|
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
4
|
import Viewport from '../../Viewport.mjs';
|
5
5
|
import { StrokeSmoother } from '../util/StrokeSmoother.mjs';
|
6
|
-
|
6
|
+
import makeShapeFitAutocorrect from './autocorrect/makeShapeFitAutocorrect.mjs';
|
7
|
+
export const makePressureSensitiveFreehandLineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
|
7
8
|
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
8
9
|
// less than ±1 px from the curve.
|
9
10
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
10
11
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
11
12
|
return new PressureSensitiveFreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
|
12
|
-
};
|
13
|
+
});
|
13
14
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
14
15
|
export default class PressureSensitiveFreehandLineBuilder {
|
15
16
|
constructor(startPoint,
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { Mat33, Rect2, Path } from '@js-draw/math';
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
|
-
|
4
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
5
|
+
export const makeFilledRectangleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
5
6
|
return new RectangleBuilder(initialPoint, true, viewport);
|
6
|
-
};
|
7
|
-
export const makeOutlinedRectangleBuilder = (initialPoint, viewport) => {
|
7
|
+
});
|
8
|
+
export const makeOutlinedRectangleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
8
9
|
return new RectangleBuilder(initialPoint, false, viewport);
|
9
|
-
};
|
10
|
+
});
|
10
11
|
export default class RectangleBuilder {
|
11
12
|
constructor(startPoint, filled, viewport) {
|
12
13
|
this.startPoint = startPoint;
|
@@ -0,0 +1,166 @@
|
|
1
|
+
import { Rect2, LineSegment2 } from '@js-draw/math';
|
2
|
+
const makeShapeFitAutocorrect = (sourceFactory) => {
|
3
|
+
return (startPoint, viewport) => {
|
4
|
+
return new ShapeFitBuilder(sourceFactory, startPoint, viewport);
|
5
|
+
};
|
6
|
+
};
|
7
|
+
export default makeShapeFitAutocorrect;
|
8
|
+
const makeLineTemplate = (startPoint, points, _bbox) => {
|
9
|
+
const templatePoints = [
|
10
|
+
startPoint,
|
11
|
+
points[points.length - 1],
|
12
|
+
];
|
13
|
+
return { points: templatePoints, };
|
14
|
+
};
|
15
|
+
const makeRectangleTemplate = (_startPoint, _points, bbox) => {
|
16
|
+
return { points: [...bbox.corners, bbox.corners[0]], };
|
17
|
+
};
|
18
|
+
class ShapeFitBuilder {
|
19
|
+
constructor(sourceFactory, startPoint, viewport) {
|
20
|
+
this.sourceFactory = sourceFactory;
|
21
|
+
this.startPoint = startPoint;
|
22
|
+
this.viewport = viewport;
|
23
|
+
this.builder = sourceFactory(startPoint, viewport);
|
24
|
+
this.points = [startPoint];
|
25
|
+
}
|
26
|
+
getBBox() {
|
27
|
+
return this.builder.getBBox();
|
28
|
+
}
|
29
|
+
build() {
|
30
|
+
return this.builder.build();
|
31
|
+
}
|
32
|
+
preview(renderer) {
|
33
|
+
this.builder.preview(renderer);
|
34
|
+
}
|
35
|
+
addPoint(point) {
|
36
|
+
this.points.push(point);
|
37
|
+
this.builder.addPoint(point);
|
38
|
+
}
|
39
|
+
async autocorrectShape() {
|
40
|
+
// Use screen points so that autocorrected shapes rotate with the screen.
|
41
|
+
const startPoint = this.viewport.canvasToScreen(this.startPoint.pos);
|
42
|
+
const points = this.points.map(point => this.viewport.canvasToScreen(point.pos));
|
43
|
+
const bbox = Rect2.bboxOf(points);
|
44
|
+
const snappedStartPoint = this.viewport.canvasToScreen(this.viewport.snapToGrid(this.startPoint.pos));
|
45
|
+
const snappedPoints = this.points.map(point => this.viewport.canvasToScreen(this.viewport.snapToGrid(point.pos)));
|
46
|
+
const snappedBBox = Rect2.bboxOf(snappedPoints);
|
47
|
+
// Only fit larger shapes
|
48
|
+
if (bbox.maxDimension < 32) {
|
49
|
+
return null;
|
50
|
+
}
|
51
|
+
const maxError = Math.min(30, bbox.maxDimension / 4);
|
52
|
+
// Create templates
|
53
|
+
const templates = [
|
54
|
+
{
|
55
|
+
...makeLineTemplate(snappedStartPoint, snappedPoints, snappedBBox),
|
56
|
+
toleranceMultiplier: 0.5,
|
57
|
+
},
|
58
|
+
makeLineTemplate(startPoint, points, bbox),
|
59
|
+
{
|
60
|
+
...makeRectangleTemplate(snappedStartPoint, snappedPoints, snappedBBox),
|
61
|
+
toleranceMultiplier: 0.6,
|
62
|
+
},
|
63
|
+
makeRectangleTemplate(startPoint, points, bbox),
|
64
|
+
];
|
65
|
+
// Find a good fit fit
|
66
|
+
const selectTemplate = (maximumAllowedError) => {
|
67
|
+
for (const template of templates) {
|
68
|
+
const templatePoints = template.points;
|
69
|
+
// Maximum square error to accept the template
|
70
|
+
const acceptMaximumSquareError = maximumAllowedError * maximumAllowedError * (template.toleranceMultiplier ?? 1);
|
71
|
+
// Gets the point at index, wrapping the the start of the template if
|
72
|
+
// outside the array of points.
|
73
|
+
const templateAt = (index) => {
|
74
|
+
while (index < 0) {
|
75
|
+
index += templatePoints.length;
|
76
|
+
}
|
77
|
+
index %= templatePoints.length;
|
78
|
+
return templatePoints[index];
|
79
|
+
};
|
80
|
+
let closestToFirst = null;
|
81
|
+
let closestToFirstSqrDist = Infinity;
|
82
|
+
let templateStartIndex = 0;
|
83
|
+
// Find the closest point to the startPoint
|
84
|
+
for (let i = 0; i < templatePoints.length; i++) {
|
85
|
+
const current = templatePoints[i];
|
86
|
+
const currentSqrDist = current.minus(startPoint).magnitudeSquared();
|
87
|
+
if (!closestToFirst || currentSqrDist < closestToFirstSqrDist) {
|
88
|
+
closestToFirstSqrDist = currentSqrDist;
|
89
|
+
closestToFirst = current;
|
90
|
+
templateStartIndex = i;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
// Walk through the points and find the maximum error
|
94
|
+
let maximumSqrError = 0;
|
95
|
+
let templateIndex = templateStartIndex;
|
96
|
+
for (const point of points) {
|
97
|
+
let minimumCurrentSqrError = Infinity;
|
98
|
+
let minimumErrorAtIndex = templateIndex;
|
99
|
+
const windowRadius = 6;
|
100
|
+
for (let i = -windowRadius; i <= windowRadius; i++) {
|
101
|
+
const index = templateIndex + i;
|
102
|
+
const prevTemplatePoint = templateAt(index - 1);
|
103
|
+
const currentTemplatePoint = templateAt(index);
|
104
|
+
const nextTemplatePoint = templateAt(index + 1);
|
105
|
+
const prevToCurrent = new LineSegment2(prevTemplatePoint, currentTemplatePoint);
|
106
|
+
const currentToNext = new LineSegment2(currentTemplatePoint, nextTemplatePoint);
|
107
|
+
const prevToCurrentDist = prevToCurrent.distance(point);
|
108
|
+
const nextToCurrentDist = currentToNext.distance(point);
|
109
|
+
const error = Math.min(prevToCurrentDist, nextToCurrentDist);
|
110
|
+
const squareError = error * error;
|
111
|
+
if (squareError < minimumCurrentSqrError) {
|
112
|
+
minimumCurrentSqrError = squareError;
|
113
|
+
minimumErrorAtIndex = index;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
templateIndex = minimumErrorAtIndex;
|
117
|
+
maximumSqrError = Math.max(minimumCurrentSqrError, maximumSqrError);
|
118
|
+
if (maximumSqrError > acceptMaximumSquareError) {
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
if (maximumSqrError < acceptMaximumSquareError) {
|
123
|
+
return templatePoints;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
return null;
|
127
|
+
};
|
128
|
+
const template = selectTemplate(maxError);
|
129
|
+
if (!template) {
|
130
|
+
return null;
|
131
|
+
}
|
132
|
+
const lastDataPoint = this.points[this.points.length - 1];
|
133
|
+
const startWidth = this.startPoint.width;
|
134
|
+
const endWidth = lastDataPoint.width;
|
135
|
+
const startColor = this.startPoint.color;
|
136
|
+
const endColor = lastDataPoint.color;
|
137
|
+
const startTime = this.startPoint.time;
|
138
|
+
const endTime = lastDataPoint.time;
|
139
|
+
const templateIndexToStrokeDataPoint = (index) => {
|
140
|
+
const prevPoint = template[Math.max(0, Math.floor(index))];
|
141
|
+
const nextPoint = template[Math.min(Math.ceil(index), template.length - 1)];
|
142
|
+
const point = prevPoint.lerp(nextPoint, index - Math.floor(index));
|
143
|
+
const fractionToEnd = index / template.length;
|
144
|
+
return {
|
145
|
+
pos: this.viewport.screenToCanvas(point),
|
146
|
+
width: startWidth * (1 - fractionToEnd) + endWidth * fractionToEnd,
|
147
|
+
color: startColor.mix(endColor, fractionToEnd),
|
148
|
+
time: startTime * (1 - fractionToEnd) + endTime * fractionToEnd,
|
149
|
+
};
|
150
|
+
};
|
151
|
+
const builder = this.sourceFactory(templateIndexToStrokeDataPoint(0), this.viewport);
|
152
|
+
// Prevent the original builder from doing stroke smoothing if the template is short
|
153
|
+
// enough to likely have sharp corners.
|
154
|
+
const preventSmoothing = template.length < 10;
|
155
|
+
for (let i = 0; i < template.length; i++) {
|
156
|
+
if (preventSmoothing) {
|
157
|
+
builder.addPoint(templateIndexToStrokeDataPoint(i - 0.001));
|
158
|
+
}
|
159
|
+
builder.addPoint(templateIndexToStrokeDataPoint(i));
|
160
|
+
if (preventSmoothing) {
|
161
|
+
builder.addPoint(templateIndexToStrokeDataPoint(i + 0.001));
|
162
|
+
}
|
163
|
+
}
|
164
|
+
return builder.build();
|
165
|
+
}
|
166
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
const makeSnapToGridAutocorrect = (sourceFactory) => {
|
2
|
+
return (startPoint, viewport) => {
|
3
|
+
return new SnapToGridAutocompleteBuilder(sourceFactory, startPoint, viewport);
|
4
|
+
};
|
5
|
+
};
|
6
|
+
export default makeSnapToGridAutocorrect;
|
7
|
+
class SnapToGridAutocompleteBuilder {
|
8
|
+
constructor(sourceFactory, startPoint, viewport) {
|
9
|
+
this.sourceFactory = sourceFactory;
|
10
|
+
this.startPoint = startPoint;
|
11
|
+
this.viewport = viewport;
|
12
|
+
this.builder = sourceFactory(startPoint, viewport);
|
13
|
+
this.points = [startPoint];
|
14
|
+
}
|
15
|
+
getBBox() {
|
16
|
+
return this.builder.getBBox();
|
17
|
+
}
|
18
|
+
build() {
|
19
|
+
return this.builder.build();
|
20
|
+
}
|
21
|
+
preview(renderer) {
|
22
|
+
this.builder.preview(renderer);
|
23
|
+
}
|
24
|
+
addPoint(point) {
|
25
|
+
this.points.push(point);
|
26
|
+
this.builder.addPoint(point);
|
27
|
+
}
|
28
|
+
async autocorrectShape() {
|
29
|
+
const snapToGrid = (point) => {
|
30
|
+
return {
|
31
|
+
...point,
|
32
|
+
pos: this.viewport.snapToGrid(point.pos),
|
33
|
+
};
|
34
|
+
};
|
35
|
+
// Use screen points so that snapped shapes rotate with the screen.
|
36
|
+
const startPoint = snapToGrid(this.startPoint);
|
37
|
+
const builder = this.sourceFactory(startPoint, this.viewport);
|
38
|
+
const points = this.points.map(point => snapToGrid(point));
|
39
|
+
for (const point of points) {
|
40
|
+
builder.addPoint(point);
|
41
|
+
}
|
42
|
+
return builder.build();
|
43
|
+
}
|
44
|
+
}
|
@@ -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
|
-
/**
|
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 */
|
@@ -180,6 +180,9 @@ class EditorImage {
|
|
180
180
|
* rendered onto the main rendering canvas instead of doing a full re-render.
|
181
181
|
*
|
182
182
|
* @see {@link Display.flatten}
|
183
|
+
*
|
184
|
+
* @example
|
185
|
+
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
|
183
186
|
*/
|
184
187
|
static addElement(elem, applyByFlattening = false) {
|
185
188
|
return new _a.AddElementCommand(elem, applyByFlattening);
|
@@ -207,10 +210,38 @@ class EditorImage {
|
|
207
210
|
setImportExportRect(imageRect) {
|
208
211
|
return _a.SetImportExportRectCommand.of(this, imageRect, false);
|
209
212
|
}
|
213
|
+
/** @see {@link setAutoresizeEnabled} */
|
210
214
|
getAutoresizeEnabled() {
|
211
215
|
return this.shouldAutoresizeExportViewport;
|
212
216
|
}
|
213
|
-
/**
|
217
|
+
/**
|
218
|
+
* Returns a `Command` that sets whether the image should autoresize when
|
219
|
+
* {@link AbstractComponent}s are added/removed.
|
220
|
+
*
|
221
|
+
* @example
|
222
|
+
*
|
223
|
+
* ```ts,runnable
|
224
|
+
* import { Editor } from 'js-draw';
|
225
|
+
*
|
226
|
+
* const editor = new Editor(document.body);
|
227
|
+
* const toolbar = editor.addToolbar();
|
228
|
+
*
|
229
|
+
* // Add a save button to demonstrate what the output looks like
|
230
|
+
* // (it should change size to fit whatever was drawn)
|
231
|
+
* toolbar.addSaveButton(() => {
|
232
|
+
* document.body.replaceChildren(editor.toSVG({ sanitize: true }));
|
233
|
+
* });
|
234
|
+
*
|
235
|
+
* // Actually using setAutoresizeEnabled:
|
236
|
+
* //
|
237
|
+
* // To set autoresize without announcing for accessibility/making undoable
|
238
|
+
* const addToHistory = false;
|
239
|
+
* editor.dispatchNoAnnounce(editor.image.setAutoresizeEnabled(true), addToHistory);
|
240
|
+
*
|
241
|
+
* // Add to undo history **and** announce for accessibility
|
242
|
+
* //editor.dispatch(editor.image.setAutoresizeEnabled(true), true);
|
243
|
+
* ```
|
244
|
+
*/
|
214
245
|
setAutoresizeEnabled(autoresize) {
|
215
246
|
if (autoresize === this.shouldAutoresizeExportViewport) {
|
216
247
|
return Command.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;
|
@@ -6,6 +6,10 @@ export const pathFromRenderable = (renderable) => {
|
|
6
6
|
}
|
7
7
|
return new Path(renderable.startPoint, renderable.commands);
|
8
8
|
};
|
9
|
+
/**
|
10
|
+
* Converts `path` into a format that can be rendered (by passing to a {@link Stroke} constructor
|
11
|
+
* or directly to an {@link AbstractRenderer.drawPath}).
|
12
|
+
*/
|
9
13
|
export const pathToRenderable = (path, style) => {
|
10
14
|
return {
|
11
15
|
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
|
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.
|
@@ -30,6 +30,7 @@ import DocumentPropertiesWidget from './widgets/DocumentPropertiesWidget.mjs';
|
|
30
30
|
import { Color4 } from '@js-draw/math';
|
31
31
|
import { toolbarCSSPrefix } from './constants.mjs';
|
32
32
|
import SaveActionWidget from './widgets/SaveActionWidget.mjs';
|
33
|
+
import ExitActionWidget from './widgets/ExitActionWidget.mjs';
|
33
34
|
class AbstractToolbar {
|
34
35
|
/** @internal */
|
35
36
|
constructor(editor, localizationTable = defaultToolbarLocalization) {
|
@@ -292,14 +293,13 @@ class AbstractToolbar {
|
|
292
293
|
*/
|
293
294
|
addSaveButton(saveCallback, labelOverride = {}) {
|
294
295
|
const widget = new SaveActionWidget(this.editor, this.localizationTable, saveCallback, labelOverride);
|
295
|
-
widget.setTags([ToolbarWidgetTag.Save]);
|
296
296
|
this.addWidget(widget);
|
297
297
|
return widget;
|
298
298
|
}
|
299
299
|
/**
|
300
300
|
* Adds an "Exit" button that, when clicked, calls `exitCallback`.
|
301
301
|
*
|
302
|
-
* **Note**: This is roughly equivalent to
|
302
|
+
* **Note**: This is *roughly* equivalent to
|
303
303
|
* ```ts
|
304
304
|
* toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
|
305
305
|
* label: this.editor.localization.exit,
|
@@ -316,15 +316,9 @@ class AbstractToolbar {
|
|
316
316
|
* @final
|
317
317
|
*/
|
318
318
|
addExitButton(exitCallback, labelOverride = {}) {
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
...labelOverride,
|
323
|
-
}, () => {
|
324
|
-
exitCallback();
|
325
|
-
}, {
|
326
|
-
autoDisableInReadOnlyEditors: false,
|
327
|
-
});
|
319
|
+
const widget = new ExitActionWidget(this.editor, this.localizationTable, exitCallback, labelOverride);
|
320
|
+
this.addWidget(widget);
|
321
|
+
return widget;
|
328
322
|
}
|
329
323
|
/**
|
330
324
|
* Adds undo and redo buttons that trigger the editor's built-in undo and redo
|
@@ -372,27 +366,49 @@ class AbstractToolbar {
|
|
372
366
|
});
|
373
367
|
}
|
374
368
|
/**
|
375
|
-
* Adds
|
369
|
+
* Adds widgets for pen/eraser/selection/text/pan-zoom primary tools.
|
370
|
+
*
|
371
|
+
* If `filter` returns `false` for a tool, no widget is added for that tool.
|
372
|
+
* See {@link addDefaultToolWidgets}
|
376
373
|
*/
|
377
|
-
|
378
|
-
const
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
374
|
+
addWidgetsForPrimaryTools(filter) {
|
375
|
+
for (const tool of this.editor.toolController.getPrimaryTools()) {
|
376
|
+
if (filter && !filter?.(tool)) {
|
377
|
+
continue;
|
378
|
+
}
|
379
|
+
if (tool instanceof PenTool) {
|
380
|
+
const widget = new PenToolWidget(this.editor, tool, this.localizationTable);
|
381
|
+
this.addWidget(widget);
|
382
|
+
}
|
383
|
+
else if (tool instanceof EraserTool) {
|
384
|
+
this.addWidget(new EraserWidget(this.editor, tool, this.localizationTable));
|
385
|
+
}
|
386
|
+
else if (tool instanceof SelectionTool) {
|
387
|
+
this.addWidget(new SelectionToolWidget(this.editor, tool, this.localizationTable));
|
388
|
+
}
|
389
|
+
else if (tool instanceof TextTool) {
|
390
|
+
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
391
|
+
}
|
392
|
+
else if (tool instanceof PanZoomTool) {
|
393
|
+
this.addWidget(new HandToolWidget(this.editor, tool, this.localizationTable));
|
394
|
+
}
|
395
395
|
}
|
396
|
+
}
|
397
|
+
/**
|
398
|
+
* Adds toolbar widgets based on the enabled tools, and additional tool-like
|
399
|
+
* buttons (e.g. {@link DocumentPropertiesWidget} and {@link InsertImageWidget}).
|
400
|
+
*/
|
401
|
+
addDefaultToolWidgets() {
|
402
|
+
this.addWidgetsForPrimaryTools();
|
403
|
+
this.addDefaultEditorControlWidgets();
|
404
|
+
}
|
405
|
+
/**
|
406
|
+
* Adds widgets that don't correspond to tools, but do allow the user to control
|
407
|
+
* the editor in some way.
|
408
|
+
*
|
409
|
+
* By default, this includes {@link DocumentPropertiesWidget} and {@link InsertImageWidget}.
|
410
|
+
*/
|
411
|
+
addDefaultEditorControlWidgets() {
|
396
412
|
this.addWidget(new DocumentPropertiesWidget(this.editor, this.localizationTable));
|
397
413
|
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
398
414
|
}
|