js-draw 0.2.1 → 0.2.3
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/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.js +18 -9
- package/dist/src/toolbar/HTMLToolbar.js +1 -1
- package/dist/src/tools/Pen.js +8 -3
- package/package.json +1 -1
- package/src/components/builders/FreehandLineBuilder.ts +27 -17
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/toolbar.css +2 -1
- package/src/tools/Pen.ts +9 -3
@@ -8,9 +8,8 @@ import Viewport from '../../Viewport';
|
|
8
8
|
export const makeFreehandLineBuilder = (initialPoint, viewport) => {
|
9
9
|
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
10
10
|
// less than ± 2 px from the curve.
|
11
|
-
const
|
12
|
-
const
|
13
|
-
const minSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 2;
|
11
|
+
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
|
12
|
+
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 2;
|
14
13
|
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist);
|
15
14
|
};
|
16
15
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
@@ -27,6 +26,7 @@ export default class FreehandLineBuilder {
|
|
27
26
|
this.isFirstSegment = true;
|
28
27
|
this.pathStartConnector = null;
|
29
28
|
this.mostRecentConnector = null;
|
29
|
+
this.lastExitingVec = null;
|
30
30
|
this.currentCurve = null;
|
31
31
|
this.lastPoint = this.startPoint;
|
32
32
|
this.upperSegments = [];
|
@@ -34,6 +34,7 @@ export default class FreehandLineBuilder {
|
|
34
34
|
this.buffer = [this.startPoint.pos];
|
35
35
|
this.momentum = Vec2.zero;
|
36
36
|
this.currentCurve = null;
|
37
|
+
this.curveStartWidth = startPoint.width;
|
37
38
|
this.bbox = new Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0);
|
38
39
|
}
|
39
40
|
getBBox() {
|
@@ -122,7 +123,11 @@ export default class FreehandLineBuilder {
|
|
122
123
|
return this.previewStroke();
|
123
124
|
}
|
124
125
|
roundPoint(point) {
|
125
|
-
|
126
|
+
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth);
|
127
|
+
if (minFit < 1e-10) {
|
128
|
+
minFit = this.minFitAllowed;
|
129
|
+
}
|
130
|
+
return Viewport.roundPoint(point, minFit);
|
126
131
|
}
|
127
132
|
finalizeCurrentCurve() {
|
128
133
|
// Case where no points have been added
|
@@ -227,10 +232,13 @@ export default class FreehandLineBuilder {
|
|
227
232
|
const computeBoundaryCurve = (direction, halfVec) => {
|
228
233
|
return new Bezier(startPt.plus(startVec.times(direction)), controlPoint.plus(halfVec.times(direction)), endPt.plus(endVec.times(direction)));
|
229
234
|
};
|
230
|
-
const
|
231
|
-
|
235
|
+
const boundariesIntersect = () => {
|
236
|
+
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
237
|
+
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
238
|
+
return upperBoundary.intersects(lowerBoundary).length > 0;
|
239
|
+
};
|
232
240
|
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
233
|
-
if (
|
241
|
+
if (boundariesIntersect()) {
|
234
242
|
halfVec = halfVec.times(2);
|
235
243
|
}
|
236
244
|
// Each starts at startPt ± startVec
|
@@ -274,7 +282,7 @@ export default class FreehandLineBuilder {
|
|
274
282
|
console.warn('Discarding NaN point.', newPoint);
|
275
283
|
return;
|
276
284
|
}
|
277
|
-
const threshold = Math.min(this.lastPoint.width, newPoint.width) /
|
285
|
+
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 3;
|
278
286
|
const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
|
279
287
|
&& this.isFirstSegment;
|
280
288
|
// Snap to the starting point if the stroke is contained within a small ball centered
|
@@ -361,7 +369,8 @@ export default class FreehandLineBuilder {
|
|
361
369
|
}
|
362
370
|
return true;
|
363
371
|
};
|
364
|
-
|
372
|
+
const approxCurveLen = controlPoint.minus(segmentStart).magnitude() + segmentEnd.minus(controlPoint).magnitude();
|
373
|
+
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 2) {
|
365
374
|
if (!curveMatchesPoints(this.currentCurve)) {
|
366
375
|
// Use a curve that better fits the points
|
367
376
|
this.currentCurve = prevCurve;
|
@@ -88,7 +88,7 @@ export default class HTMLToolbar {
|
|
88
88
|
}
|
89
89
|
addActionButton(title, command, parent) {
|
90
90
|
const button = document.createElement('button');
|
91
|
-
button.classList.add(`${toolbarCSSPrefix}
|
91
|
+
button.classList.add(`${toolbarCSSPrefix}button`);
|
92
92
|
if (typeof title === 'string') {
|
93
93
|
button.innerText = title;
|
94
94
|
}
|
package/dist/src/tools/Pen.js
CHANGED
@@ -49,10 +49,15 @@ export default class Pen extends BaseTool {
|
|
49
49
|
this.previewStroke();
|
50
50
|
}
|
51
51
|
onPointerDown({ current, allPointers }) {
|
52
|
-
|
53
|
-
|
52
|
+
const isEraser = current.device === PointerDevice.Eraser;
|
53
|
+
let anyDeviceIsStylus = false;
|
54
|
+
for (const pointer of allPointers) {
|
55
|
+
if (pointer.device === PointerDevice.Pen) {
|
56
|
+
anyDeviceIsStylus = true;
|
57
|
+
break;
|
58
|
+
}
|
54
59
|
}
|
55
|
-
if (allPointers.length === 1
|
60
|
+
if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
|
56
61
|
this.builder = this.builderFactory(this.getStrokePoint(current), this.editor.viewport);
|
57
62
|
return true;
|
58
63
|
}
|
package/package.json
CHANGED
@@ -2,7 +2,7 @@ import { Bezier } from 'bezier-js';
|
|
2
2
|
import AbstractRenderer, { RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
|
3
3
|
import { Point2, Vec2 } from '../../math/Vec2';
|
4
4
|
import Rect2 from '../../math/Rect2';
|
5
|
-
import { LinePathCommand, PathCommandType, QuadraticBezierPathCommand } from '../../math/Path';
|
5
|
+
import { LinePathCommand, PathCommand, PathCommandType, QuadraticBezierPathCommand } from '../../math/Path';
|
6
6
|
import LineSegment2 from '../../math/LineSegment2';
|
7
7
|
import Stroke from '../Stroke';
|
8
8
|
import Viewport from '../../Viewport';
|
@@ -13,9 +13,8 @@ import RenderingStyle from '../../rendering/RenderingStyle';
|
|
13
13
|
export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
14
14
|
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
15
15
|
// less than ± 2 px from the curve.
|
16
|
-
const
|
17
|
-
const
|
18
|
-
const minSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 2;
|
16
|
+
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
|
17
|
+
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 2;
|
19
18
|
|
20
19
|
return new FreehandLineBuilder(
|
21
20
|
initialPoint, minSmoothingDist, maxSmoothingDist
|
@@ -24,16 +23,16 @@ export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: S
|
|
24
23
|
|
25
24
|
type CurrentSegmentToPathResult = {
|
26
25
|
upperCurve: QuadraticBezierPathCommand,
|
27
|
-
lowerToUpperConnector:
|
28
|
-
upperToLowerConnector:
|
26
|
+
lowerToUpperConnector: PathCommand,
|
27
|
+
upperToLowerConnector: PathCommand,
|
29
28
|
lowerCurve: QuadraticBezierPathCommand,
|
30
29
|
};
|
31
30
|
|
32
31
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
33
32
|
export default class FreehandLineBuilder implements ComponentBuilder {
|
34
33
|
private isFirstSegment: boolean = true;
|
35
|
-
private pathStartConnector:
|
36
|
-
private mostRecentConnector:
|
34
|
+
private pathStartConnector: PathCommand|null = null;
|
35
|
+
private mostRecentConnector: PathCommand|null = null;
|
37
36
|
|
38
37
|
// Beginning of the list of lower parts
|
39
38
|
// ↓
|
@@ -53,7 +52,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
53
52
|
|
54
53
|
private buffer: Point2[];
|
55
54
|
private lastPoint: StrokeDataPoint;
|
56
|
-
private lastExitingVec: Vec2;
|
55
|
+
private lastExitingVec: Vec2|null = null;
|
57
56
|
private currentCurve: Bezier|null = null;
|
58
57
|
private curveStartWidth: number;
|
59
58
|
private curveEndWidth: number;
|
@@ -79,6 +78,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
79
78
|
this.buffer = [this.startPoint.pos];
|
80
79
|
this.momentum = Vec2.zero;
|
81
80
|
this.currentCurve = null;
|
81
|
+
this.curveStartWidth = startPoint.width;
|
82
82
|
|
83
83
|
this.bbox = new Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0);
|
84
84
|
}
|
@@ -96,8 +96,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
96
96
|
private previewPath(): RenderablePathSpec|null {
|
97
97
|
let upperPath: QuadraticBezierPathCommand[];
|
98
98
|
let lowerPath: QuadraticBezierPathCommand[];
|
99
|
-
let lowerToUpperCap:
|
100
|
-
let pathStartConnector:
|
99
|
+
let lowerToUpperCap: PathCommand;
|
100
|
+
let pathStartConnector: PathCommand;
|
101
101
|
if (this.currentCurve) {
|
102
102
|
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
|
103
103
|
upperPath = this.upperSegments.concat(upperCurve);
|
@@ -180,7 +180,13 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
180
180
|
}
|
181
181
|
|
182
182
|
private roundPoint(point: Point2): Point2 {
|
183
|
-
|
183
|
+
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth);
|
184
|
+
|
185
|
+
if (minFit < 1e-10) {
|
186
|
+
minFit = this.minFitAllowed;
|
187
|
+
}
|
188
|
+
|
189
|
+
return Viewport.roundPoint(point, minFit);
|
184
190
|
}
|
185
191
|
|
186
192
|
private finalizeCurrentCurve() {
|
@@ -317,11 +323,14 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
317
323
|
);
|
318
324
|
};
|
319
325
|
|
320
|
-
const
|
321
|
-
|
326
|
+
const boundariesIntersect = () => {
|
327
|
+
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
328
|
+
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
329
|
+
return upperBoundary.intersects(lowerBoundary).length > 0;
|
330
|
+
};
|
322
331
|
|
323
332
|
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
324
|
-
if (
|
333
|
+
if (boundariesIntersect()) {
|
325
334
|
halfVec = halfVec.times(2);
|
326
335
|
}
|
327
336
|
|
@@ -372,7 +381,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
372
381
|
return;
|
373
382
|
}
|
374
383
|
|
375
|
-
const threshold = Math.min(this.lastPoint.width, newPoint.width) /
|
384
|
+
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 3;
|
376
385
|
const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
|
377
386
|
&& this.isFirstSegment;
|
378
387
|
|
@@ -493,7 +502,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
493
502
|
return true;
|
494
503
|
};
|
495
504
|
|
496
|
-
|
505
|
+
const approxCurveLen = controlPoint.minus(segmentStart).magnitude() + segmentEnd.minus(controlPoint).magnitude();
|
506
|
+
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 2) {
|
497
507
|
if (!curveMatchesPoints(this.currentCurve)) {
|
498
508
|
// Use a curve that better fits the points
|
499
509
|
this.currentCurve = prevCurve;
|
@@ -112,7 +112,7 @@ export default class HTMLToolbar {
|
|
112
112
|
|
113
113
|
public addActionButton(title: string|ActionButtonIcon, command: ()=> void, parent?: Element) {
|
114
114
|
const button = document.createElement('button');
|
115
|
-
button.classList.add(`${toolbarCSSPrefix}
|
115
|
+
button.classList.add(`${toolbarCSSPrefix}button`);
|
116
116
|
|
117
117
|
if (typeof title === 'string') {
|
118
118
|
button.innerText = title;
|
package/src/toolbar/toolbar.css
CHANGED
@@ -22,7 +22,8 @@
|
|
22
22
|
|
23
23
|
.toolbar-root > .toolbar-toolContainer > .toolbar-button,
|
24
24
|
.toolbar-root > .toolbar-toolContainer > * > button,
|
25
|
-
.toolbar-root > .toolbar-buttonGroup > button
|
25
|
+
.toolbar-root > .toolbar-buttonGroup > button,
|
26
|
+
.toolbar-root > .toolbar-button {
|
26
27
|
width: min-content;
|
27
28
|
white-space: pre;
|
28
29
|
height: min(20vh, 60px);
|
package/src/tools/Pen.ts
CHANGED
@@ -67,11 +67,17 @@ export default class Pen extends BaseTool {
|
|
67
67
|
}
|
68
68
|
|
69
69
|
public onPointerDown({ current, allPointers }: PointerEvt): boolean {
|
70
|
-
|
71
|
-
|
70
|
+
const isEraser = current.device === PointerDevice.Eraser;
|
71
|
+
|
72
|
+
let anyDeviceIsStylus = false;
|
73
|
+
for (const pointer of allPointers) {
|
74
|
+
if (pointer.device === PointerDevice.Pen) {
|
75
|
+
anyDeviceIsStylus = true;
|
76
|
+
break;
|
77
|
+
}
|
72
78
|
}
|
73
79
|
|
74
|
-
if (allPointers.length === 1
|
80
|
+
if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
|
75
81
|
this.builder = this.builderFactory(this.getStrokePoint(current), this.editor.viewport);
|
76
82
|
return true;
|
77
83
|
}
|