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.
@@ -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 canvasTransform = viewport.screenToCanvasTransform;
12
- const maxSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 7;
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
- return Viewport.roundPoint(point, this.minFitAllowed);
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 upperBoundary = computeBoundaryCurve(1, halfVec);
231
- const lowerBoundary = computeBoundaryCurve(-1, halfVec);
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 (upperBoundary.intersects(lowerBoundary).length > 0) {
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) / 4;
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
- if (this.buffer.length > 3) {
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}toolButton`);
91
+ button.classList.add(`${toolbarCSSPrefix}button`);
92
92
  if (typeof title === 'string') {
93
93
  button.innerText = title;
94
94
  }
@@ -49,10 +49,15 @@ export default class Pen extends BaseTool {
49
49
  this.previewStroke();
50
50
  }
51
51
  onPointerDown({ current, allPointers }) {
52
- if (current.device === PointerDevice.Eraser) {
53
- return false;
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 || current.device === PointerDevice.Pen) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
@@ -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 canvasTransform = viewport.screenToCanvasTransform;
17
- const maxSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 7;
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: LinePathCommand,
28
- upperToLowerConnector: LinePathCommand,
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: LinePathCommand|null = null;
36
- private mostRecentConnector: LinePathCommand|null = null;
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: LinePathCommand;
100
- let pathStartConnector: LinePathCommand;
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
- return Viewport.roundPoint(point, this.minFitAllowed);
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 upperBoundary = computeBoundaryCurve(1, halfVec);
321
- const lowerBoundary = computeBoundaryCurve(-1, halfVec);
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 (upperBoundary.intersects(lowerBoundary).length > 0) {
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) / 4;
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
- if (this.buffer.length > 3) {
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}toolButton`);
115
+ button.classList.add(`${toolbarCSSPrefix}button`);
116
116
 
117
117
  if (typeof title === 'string') {
118
118
  button.innerText = title;
@@ -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
- if (current.device === PointerDevice.Eraser) {
71
- return false;
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 || current.device === PointerDevice.Pen) {
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
  }