js-draw 1.9.0 → 1.10.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 +33 -13
- package/dist/cjs/Pointer.js +1 -1
- package/dist/cjs/Viewport.d.ts +6 -0
- package/dist/cjs/Viewport.js +6 -1
- package/dist/cjs/commands/Erase.d.ts +22 -2
- package/dist/cjs/commands/Erase.js +22 -2
- package/dist/cjs/commands/uniteCommands.d.ts +36 -0
- package/dist/cjs/commands/uniteCommands.js +36 -0
- 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 +1 -0
- package/dist/cjs/image/EditorImage.d.ts +32 -1
- package/dist/cjs/image/EditorImage.js +32 -1
- package/dist/cjs/rendering/Display.js +8 -1
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
- 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/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/tools/Pen.d.ts +9 -0
- package/dist/cjs/tools/Pen.js +77 -3
- package/dist/cjs/tools/TextTool.js +5 -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 +33 -13
- package/dist/mjs/Pointer.mjs +1 -1
- package/dist/mjs/Viewport.d.ts +6 -0
- package/dist/mjs/Viewport.mjs +6 -1
- package/dist/mjs/commands/Erase.d.ts +22 -2
- package/dist/mjs/commands/Erase.mjs +22 -2
- package/dist/mjs/commands/uniteCommands.d.ts +36 -0
- package/dist/mjs/commands/uniteCommands.mjs +36 -0
- 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 +1 -0
- package/dist/mjs/image/EditorImage.d.ts +32 -1
- package/dist/mjs/image/EditorImage.mjs +32 -1
- package/dist/mjs/rendering/Display.mjs +8 -1
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
- 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/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/tools/Pen.d.ts +9 -0
- package/dist/mjs/tools/Pen.mjs +77 -3
- package/dist/mjs/tools/TextTool.mjs +5 -1
- 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
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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.
|
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,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,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,7 @@ export interface ComponentBuilder {
|
|
7
7
|
getBBox(): Rect2;
|
8
8
|
build(): AbstractComponent;
|
9
9
|
preview(renderer: AbstractRenderer): void;
|
10
|
+
autocorrectShape?: () => Promise<AbstractComponent | null>;
|
10
11
|
addPoint(point: StrokeDataPoint): void;
|
11
12
|
}
|
12
13
|
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 */
|
@@ -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
|
-
/**
|
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;
|
@@ -138,6 +138,9 @@ class Display {
|
|
138
138
|
// Ensure correct drawing operations on high-resolution screens.
|
139
139
|
// See
|
140
140
|
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#scaling_for_high_resolution_displays
|
141
|
+
//
|
142
|
+
// This scaling causes the rendering contexts to automatically convert
|
143
|
+
// between screen coordinates and pixel coordinates.
|
141
144
|
wetInkCtx.resetTransform();
|
142
145
|
dryInkCtx.resetTransform();
|
143
146
|
dryInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
@@ -156,7 +159,11 @@ class Display {
|
|
156
159
|
dryInkCtx.restore();
|
157
160
|
};
|
158
161
|
this.getColorAt = (screenPos) => {
|
159
|
-
|
162
|
+
// getImageData isn't affected by a transformation matrix -- we need to
|
163
|
+
// pre-transform screenPos to convert it from screen coordinates into pixel
|
164
|
+
// coordinates.
|
165
|
+
const adjustedScreenPos = screenPos.times(this.devicePixelRatio);
|
166
|
+
const pixel = dryInkCtx.getImageData(adjustedScreenPos.x, adjustedScreenPos.y, 1, 1);
|
160
167
|
const data = pixel?.data;
|
161
168
|
if (data) {
|
162
169
|
const color = math_1.Color4.ofRGBA(data[0] / 255, data[1] / 255, data[2] / 255, data[3] / 255);
|
@@ -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,
|
@@ -51,6 +51,8 @@ export default class IconProvider {
|
|
51
51
|
makePenIcon(penStyle: PenStyle): IconElemType;
|
52
52
|
makeIconFromFactory(penStyle: PenStyle): IconElemType;
|
53
53
|
makePipetteIcon(color?: Color4): IconElemType;
|
54
|
+
makeShapeAutocorrectIcon(): IconElemType;
|
55
|
+
makeStrokeSmoothingIcon(): IconElemType;
|
54
56
|
/** Unused. @deprecated */
|
55
57
|
makeFormatSelectionIcon(): IconElemType;
|
56
58
|
makeResizeImageToSelectionIcon(): IconElemType;
|
@@ -649,6 +649,23 @@ class IconProvider {
|
|
649
649
|
icon.setAttribute('viewBox', '5 -40 140 140');
|
650
650
|
return icon;
|
651
651
|
}
|
652
|
+
makeShapeAutocorrectIcon() {
|
653
|
+
const fill = 'none';
|
654
|
+
const strokeColor = 'var(--icon-color)';
|
655
|
+
return this.makeIconFromPath(`
|
656
|
+
m 79.129476,33.847107 9.967823,-0.03218 v 55 h -55 l 0.03218,-9.96782
|
657
|
+
M 71.1,40.8 a 30,30 0 0 1 -30,30 30,30 0 0 1 -30,-30 30,30 0 0 1 30,-30 30,30 0 0 1 30,30 L 71.1,40.8
|
658
|
+
M 34.1,58.8 v -25 h 25 v 0
|
659
|
+
`, fill, strokeColor, '7px');
|
660
|
+
}
|
661
|
+
makeStrokeSmoothingIcon() {
|
662
|
+
const fill = 'none';
|
663
|
+
const strokeColor = 'var(--icon-color)';
|
664
|
+
return this.makeIconFromPath(`
|
665
|
+
m 31,83.2 c -50,0 30,-65 -20,-65
|
666
|
+
M 75,17.3 40,59.7 38.2,77.6 55.5,72.4 90.5,30 Z
|
667
|
+
`, fill, strokeColor, '7px');
|
668
|
+
}
|
652
669
|
/** Unused. @deprecated */
|
653
670
|
makeFormatSelectionIcon() {
|
654
671
|
return this.makeIconFromPath(`
|
@@ -10,6 +10,8 @@ export interface ToolbarLocalization {
|
|
10
10
|
arrowPen: string;
|
11
11
|
image: string;
|
12
12
|
inputAltText: string;
|
13
|
+
decreaseImageSize: string;
|
14
|
+
resetImage: string;
|
13
15
|
chooseFile: string;
|
14
16
|
dragAndDropHereOrBrowse: string;
|
15
17
|
cancel: string;
|
@@ -48,6 +50,7 @@ export interface ToolbarLocalization {
|
|
48
50
|
toggleOverflow: string;
|
49
51
|
about: string;
|
50
52
|
inputStabilization: string;
|
53
|
+
strokeAutocorrect: string;
|
51
54
|
errorImageHasZeroSize: string;
|
52
55
|
closeSidebar: (toolName: string) => string;
|
53
56
|
dropdownShown: (toolName: string) => string;
|
@@ -10,6 +10,8 @@ exports.defaultToolbarLocalization = {
|
|
10
10
|
image: 'Image',
|
11
11
|
reformatSelection: 'Format selection',
|
12
12
|
inputAltText: 'Alt text',
|
13
|
+
decreaseImageSize: 'Decrease size',
|
14
|
+
resetImage: 'Reset',
|
13
15
|
chooseFile: 'Choose file',
|
14
16
|
dragAndDropHereOrBrowse: 'Drag and drop here\nor\n{{browse}}',
|
15
17
|
submit: 'Submit',
|
@@ -40,7 +42,8 @@ exports.defaultToolbarLocalization = {
|
|
40
42
|
enableAutoresizeOption: 'Auto-resize',
|
41
43
|
toggleOverflow: 'More',
|
42
44
|
about: 'About',
|
43
|
-
inputStabilization: '
|
45
|
+
inputStabilization: 'Stabilization',
|
46
|
+
strokeAutocorrect: 'Autocorrect',
|
44
47
|
touchPanning: 'Touchscreen panning',
|
45
48
|
roundedTipPen: 'Round',
|
46
49
|
flatTipPen: 'Flat',
|