js-draw 0.0.1 → 0.0.4
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 +10 -0
- package/README.md +5 -3
- package/dist/src/Editor.d.ts +1 -1
- package/dist/src/Editor.js +7 -2
- package/dist/src/EditorImage.d.ts +2 -0
- package/dist/src/EditorImage.js +29 -5
- package/dist/src/Pointer.js +1 -1
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +1 -1
- package/dist/src/components/Stroke.d.ts +1 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/UnknownSVGObject.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +17 -0
- package/dist/src/components/builders/ArrowBuilder.js +83 -0
- package/dist/src/{StrokeBuilder.d.ts → components/builders/FreehandLineBuilder.d.ts} +9 -13
- package/dist/src/{StrokeBuilder.js → components/builders/FreehandLineBuilder.js} +27 -9
- package/dist/src/components/builders/LineBuilder.d.ts +16 -0
- package/dist/src/components/builders/LineBuilder.js +57 -0
- package/dist/src/components/builders/RectangleBuilder.d.ts +18 -0
- package/dist/src/components/builders/RectangleBuilder.js +41 -0
- package/dist/src/components/builders/types.d.ts +12 -0
- package/dist/{scripts/bundle.d.ts → src/components/builders/types.js} +0 -0
- package/dist/src/geometry/Path.d.ts +1 -0
- package/dist/src/geometry/Path.js +32 -0
- package/dist/src/geometry/Vec3.d.ts +2 -0
- package/dist/src/geometry/Vec3.js +13 -0
- package/dist/src/rendering/AbstractRenderer.js +3 -25
- package/dist/src/toolbar/HTMLToolbar.d.ts +4 -1
- package/dist/src/toolbar/HTMLToolbar.js +143 -16
- package/dist/src/toolbar/types.d.ts +6 -0
- package/dist/src/tools/Pen.d.ts +13 -3
- package/dist/src/tools/Pen.js +37 -28
- package/dist/src/tools/ToolController.js +3 -3
- package/dist/src/types.d.ts +14 -2
- package/dist/src/types.js +1 -0
- package/package.json +8 -4
- package/src/Editor.css +11 -0
- package/src/Editor.ts +9 -2
- package/src/EditorImage.ts +31 -3
- package/src/Pointer.ts +1 -1
- package/src/Viewport.ts +1 -1
- package/src/components/AbstractComponent.ts +1 -1
- package/src/components/Stroke.ts +2 -2
- package/src/components/UnknownSVGObject.ts +1 -1
- package/src/components/builders/ArrowBuilder.ts +104 -0
- package/src/{StrokeBuilder.ts → components/builders/FreehandLineBuilder.ts} +36 -18
- package/src/components/builders/LineBuilder.ts +75 -0
- package/src/components/builders/RectangleBuilder.ts +59 -0
- package/src/components/builders/types.ts +15 -0
- package/src/geometry/Path.ts +43 -0
- package/src/geometry/Vec2.test.ts +1 -0
- package/src/geometry/Vec3.test.ts +14 -0
- package/src/geometry/Vec3.ts +16 -0
- package/src/rendering/AbstractRenderer.ts +3 -32
- package/src/{editorStyles.js → styles.js} +0 -0
- package/src/toolbar/HTMLToolbar.ts +172 -22
- package/src/toolbar/toolbar.css +12 -0
- package/src/toolbar/types.ts +6 -0
- package/src/tools/Pen.ts +56 -34
- package/src/tools/ToolController.ts +3 -3
- package/src/types.ts +16 -1
- package/dist/build_tools/BundledFile.d.ts +0 -12
- package/dist/build_tools/BundledFile.js +0 -153
- package/dist/scripts/bundle.js +0 -19
- package/dist/scripts/watchBundle.d.ts +0 -1
- package/dist/scripts/watchBundle.js +0 -9
- package/dist/src/main.d.ts +0 -3
- package/dist/src/main.js +0 -4
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# js-draw
|
2
2
|
|
3
|
-
|
3
|
+
[NPM package](https://www.npmjs.com/package/js-draw) | [GitHub](https://github.com/personalizedrefrigerator/js-draw) | [Try it!](https://personalizedrefrigerator.github.io/js-draw/example/example.html)
|
4
4
|
|
5
|
-
|
5
|
+

|
6
|
+
|
7
|
+
For example usage, see [docs/example/example.ts](docs/example/example.ts).
|
6
8
|
|
7
9
|
# API
|
8
10
|
|
@@ -63,7 +65,7 @@ For example, although `js-draw` doesn't support `<circle/>` elements,
|
|
63
65
|
```
|
64
66
|
renders as
|
65
67
|
|
66
|
-

|
67
69
|
|
68
70
|
but exports to
|
69
71
|
```svg
|
package/dist/src/Editor.d.ts
CHANGED
@@ -28,7 +28,7 @@ export declare class Editor {
|
|
28
28
|
showLoadingWarning(fractionLoaded: number): void;
|
29
29
|
hideLoadingWarning(): void;
|
30
30
|
announceForAccessibility(message: string): void;
|
31
|
-
addToolbar(): HTMLToolbar;
|
31
|
+
addToolbar(defaultLayout?: boolean): HTMLToolbar;
|
32
32
|
private registerListeners;
|
33
33
|
dispatch(command: Command, addToHistory?: boolean): void;
|
34
34
|
private asyncApplyOrUnapplyCommands;
|
package/dist/src/Editor.js
CHANGED
@@ -80,8 +80,13 @@ export class Editor {
|
|
80
80
|
announceForAccessibility(message) {
|
81
81
|
this.accessibilityAnnounceArea.innerText = message;
|
82
82
|
}
|
83
|
-
addToolbar() {
|
84
|
-
|
83
|
+
addToolbar(defaultLayout = true) {
|
84
|
+
const toolbar = new HTMLToolbar(this, this.container, this.localization);
|
85
|
+
if (defaultLayout) {
|
86
|
+
toolbar.addDefaultToolWidgets();
|
87
|
+
toolbar.addDefaultActionButtons();
|
88
|
+
}
|
89
|
+
return toolbar;
|
85
90
|
}
|
86
91
|
registerListeners() {
|
87
92
|
const pointers = {};
|
@@ -30,6 +30,8 @@ export declare class ImageNode {
|
|
30
30
|
private bbox;
|
31
31
|
private children;
|
32
32
|
private targetChildCount;
|
33
|
+
private minZIndex;
|
34
|
+
private maxZIndex;
|
33
35
|
constructor(parent?: ImageNode | null);
|
34
36
|
getContent(): AbstractComponent | null;
|
35
37
|
getParent(): ImageNode | null;
|
package/dist/src/EditorImage.js
CHANGED
@@ -95,6 +95,8 @@ export class ImageNode {
|
|
95
95
|
this.children = [];
|
96
96
|
this.bbox = Rect2.empty;
|
97
97
|
this.content = null;
|
98
|
+
this.minZIndex = null;
|
99
|
+
this.maxZIndex = null;
|
98
100
|
}
|
99
101
|
getContent() {
|
100
102
|
return this.content;
|
@@ -107,7 +109,7 @@ export class ImageNode {
|
|
107
109
|
return child.getBBox().intersects(region);
|
108
110
|
});
|
109
111
|
}
|
110
|
-
//
|
112
|
+
// Returns a list of `ImageNode`s with content (and thus no children).
|
111
113
|
getLeavesInRegion(region, minFractionOfRegion = 0) {
|
112
114
|
const result = [];
|
113
115
|
// Don't render if too small
|
@@ -181,21 +183,41 @@ export class ImageNode {
|
|
181
183
|
return this.bbox;
|
182
184
|
}
|
183
185
|
// Recomputes this' bounding box. If [bubbleUp], also recompute
|
184
|
-
// this' ancestors bounding boxes
|
186
|
+
// this' ancestors bounding boxes. This also re-computes this' bounding box
|
187
|
+
// in the z-direction (z-indicies).
|
185
188
|
recomputeBBox(bubbleUp) {
|
186
|
-
var _a;
|
189
|
+
var _a, _b, _c;
|
187
190
|
const oldBBox = this.bbox;
|
188
191
|
if (this.content !== null) {
|
189
192
|
this.bbox = this.content.getBBox();
|
193
|
+
this.minZIndex = this.content.zIndex;
|
194
|
+
this.maxZIndex = this.content.zIndex;
|
190
195
|
}
|
191
196
|
else {
|
192
197
|
this.bbox = Rect2.empty;
|
198
|
+
this.minZIndex = null;
|
199
|
+
this.maxZIndex = null;
|
200
|
+
let isFirst = true;
|
193
201
|
for (const child of this.children) {
|
194
|
-
|
202
|
+
if (isFirst) {
|
203
|
+
this.bbox = child.getBBox();
|
204
|
+
isFirst = false;
|
205
|
+
}
|
206
|
+
else {
|
207
|
+
this.bbox = this.bbox.union(child.getBBox());
|
208
|
+
}
|
209
|
+
(_a = this.minZIndex) !== null && _a !== void 0 ? _a : (this.minZIndex = child.minZIndex);
|
210
|
+
(_b = this.maxZIndex) !== null && _b !== void 0 ? _b : (this.maxZIndex = child.maxZIndex);
|
211
|
+
if (child.minZIndex !== null && this.minZIndex !== null) {
|
212
|
+
this.minZIndex = Math.min(child.minZIndex, this.minZIndex);
|
213
|
+
}
|
214
|
+
if (child.maxZIndex !== null && this.maxZIndex !== null) {
|
215
|
+
this.maxZIndex = Math.max(child.maxZIndex, this.maxZIndex);
|
216
|
+
}
|
195
217
|
}
|
196
218
|
}
|
197
219
|
if (bubbleUp && !oldBBox.eq(this.bbox)) {
|
198
|
-
(
|
220
|
+
(_c = this.parent) === null || _c === void 0 ? void 0 : _c.recomputeBBox(true);
|
199
221
|
}
|
200
222
|
}
|
201
223
|
rebalance() {
|
@@ -221,6 +243,8 @@ export class ImageNode {
|
|
221
243
|
}
|
222
244
|
// Remove this node and all of its children
|
223
245
|
remove() {
|
246
|
+
this.minZIndex = null;
|
247
|
+
this.maxZIndex = null;
|
224
248
|
if (!this.parent) {
|
225
249
|
this.content = null;
|
226
250
|
this.children = [];
|
package/dist/src/Pointer.js
CHANGED
@@ -44,7 +44,7 @@ export default class Pointer {
|
|
44
44
|
device = PointerDevice.Eraser;
|
45
45
|
}
|
46
46
|
const timeStamp = (new Date()).getTime();
|
47
|
-
const canvasPos = viewport.screenToCanvas(screenPos);
|
47
|
+
const canvasPos = viewport.roundPoint(viewport.screenToCanvas(screenPos));
|
48
48
|
return new Pointer(screenPos, canvasPos, (_b = evt.pressure) !== null && _b !== void 0 ? _b : null, evt.isPrimary, isDown, device, evt.pointerId, timeStamp);
|
49
49
|
}
|
50
50
|
// Create a new Pointer from a point on the canvas.
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -3,7 +3,7 @@ import Editor from './Editor';
|
|
3
3
|
import Mat33 from './geometry/Mat33';
|
4
4
|
import Rect2 from './geometry/Rect2';
|
5
5
|
import { Point2, Vec2 } from './geometry/Vec2';
|
6
|
-
import { StrokeDataPoint } from './
|
6
|
+
import { StrokeDataPoint } from './types';
|
7
7
|
import { EditorNotifier } from './types';
|
8
8
|
declare type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
|
9
9
|
export declare class Viewport {
|
@@ -11,7 +11,7 @@ export default abstract class AbstractComponent {
|
|
11
11
|
private static zIndexCounter;
|
12
12
|
protected constructor();
|
13
13
|
getBBox(): Rect2;
|
14
|
-
abstract render(canvas: AbstractRenderer, visibleRect
|
14
|
+
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
15
15
|
abstract intersects(lineSegment: LineSegment2): boolean;
|
16
16
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
17
17
|
transformBy(affineTransfm: Mat33): Command;
|
@@ -9,7 +9,7 @@ export default class Stroke extends AbstractComponent {
|
|
9
9
|
protected contentBBox: Rect2;
|
10
10
|
constructor(parts: RenderablePathSpec[]);
|
11
11
|
intersects(line: LineSegment2): boolean;
|
12
|
-
render(canvas: AbstractRenderer, visibleRect
|
12
|
+
render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
13
13
|
private bboxForPart;
|
14
14
|
protected applyTransformation(affineTransfm: Mat33): void;
|
15
15
|
description(localization: ImageComponentLocalization): string;
|
@@ -37,7 +37,7 @@ export default class Stroke extends AbstractComponent {
|
|
37
37
|
canvas.startObject(this.getBBox());
|
38
38
|
for (const part of this.parts) {
|
39
39
|
const bbox = part.bbox;
|
40
|
-
if (bbox.intersects(visibleRect)) {
|
40
|
+
if (!visibleRect || bbox.intersects(visibleRect)) {
|
41
41
|
canvas.drawPath(part);
|
42
42
|
}
|
43
43
|
}
|
@@ -8,7 +8,7 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
8
8
|
private svgObject;
|
9
9
|
protected contentBBox: Rect2;
|
10
10
|
constructor(svgObject: SVGElement);
|
11
|
-
render(canvas: AbstractRenderer, _visibleRect
|
11
|
+
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
12
12
|
intersects(lineSegment: LineSegment2): boolean;
|
13
13
|
protected applyTransformation(_affineTransfm: Mat33): void;
|
14
14
|
description(localization: ImageComponentLocalization): string;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import Rect2 from '../../geometry/Rect2';
|
2
|
+
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import { StrokeDataPoint } from '../../types';
|
4
|
+
import AbstractComponent from '../AbstractComponent';
|
5
|
+
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
6
|
+
export declare const makeArrowBuilder: ComponentBuilderFactory;
|
7
|
+
export default class ArrowBuilder implements ComponentBuilder {
|
8
|
+
private readonly startPoint;
|
9
|
+
private endPoint;
|
10
|
+
constructor(startPoint: StrokeDataPoint);
|
11
|
+
private getLineWidth;
|
12
|
+
getBBox(): Rect2;
|
13
|
+
private buildPreview;
|
14
|
+
build(): AbstractComponent;
|
15
|
+
preview(renderer: AbstractRenderer): void;
|
16
|
+
addPoint(point: StrokeDataPoint): void;
|
17
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import { PathCommandType } from '../../geometry/Path';
|
2
|
+
import Stroke from '../Stroke';
|
3
|
+
export const makeArrowBuilder = (initialPoint, _viewport) => {
|
4
|
+
return new ArrowBuilder(initialPoint);
|
5
|
+
};
|
6
|
+
export default class ArrowBuilder {
|
7
|
+
constructor(startPoint) {
|
8
|
+
this.startPoint = startPoint;
|
9
|
+
this.endPoint = startPoint;
|
10
|
+
}
|
11
|
+
getLineWidth() {
|
12
|
+
return Math.max(this.endPoint.width, this.startPoint.width);
|
13
|
+
}
|
14
|
+
getBBox() {
|
15
|
+
const preview = this.buildPreview();
|
16
|
+
return preview.getBBox();
|
17
|
+
}
|
18
|
+
buildPreview() {
|
19
|
+
const startPoint = this.startPoint.pos;
|
20
|
+
const endPoint = this.endPoint.pos;
|
21
|
+
const toEnd = endPoint.minus(startPoint).normalized();
|
22
|
+
const arrowLength = endPoint.minus(startPoint).length();
|
23
|
+
// Ensure that the arrow tip is smaller than the arrow.
|
24
|
+
const arrowTipSize = Math.min(this.getLineWidth(), arrowLength / 2);
|
25
|
+
const startSize = this.startPoint.width / 2;
|
26
|
+
const endSize = this.endPoint.width / 2;
|
27
|
+
const arrowTipBase = endPoint.minus(toEnd.times(arrowTipSize));
|
28
|
+
// Scaled normal vectors.
|
29
|
+
const lineNormal = toEnd.orthog();
|
30
|
+
const scaledStartNormal = lineNormal.times(startSize);
|
31
|
+
const scaledBaseNormal = lineNormal.times(endSize);
|
32
|
+
const preview = new Stroke([
|
33
|
+
{
|
34
|
+
startPoint: arrowTipBase.minus(scaledBaseNormal),
|
35
|
+
commands: [
|
36
|
+
// Stem
|
37
|
+
{
|
38
|
+
kind: PathCommandType.LineTo,
|
39
|
+
point: startPoint.minus(scaledStartNormal),
|
40
|
+
},
|
41
|
+
{
|
42
|
+
kind: PathCommandType.LineTo,
|
43
|
+
point: startPoint.plus(scaledStartNormal),
|
44
|
+
},
|
45
|
+
{
|
46
|
+
kind: PathCommandType.LineTo,
|
47
|
+
point: arrowTipBase.plus(scaledBaseNormal),
|
48
|
+
},
|
49
|
+
// Head
|
50
|
+
{
|
51
|
+
kind: PathCommandType.LineTo,
|
52
|
+
point: arrowTipBase.plus(lineNormal.times(arrowTipSize).plus(scaledBaseNormal))
|
53
|
+
},
|
54
|
+
{
|
55
|
+
kind: PathCommandType.LineTo,
|
56
|
+
point: endPoint.plus(toEnd.times(endSize)),
|
57
|
+
},
|
58
|
+
{
|
59
|
+
kind: PathCommandType.LineTo,
|
60
|
+
point: arrowTipBase.plus(lineNormal.times(-arrowTipSize).minus(scaledBaseNormal)),
|
61
|
+
},
|
62
|
+
{
|
63
|
+
kind: PathCommandType.LineTo,
|
64
|
+
point: arrowTipBase.minus(scaledBaseNormal),
|
65
|
+
},
|
66
|
+
],
|
67
|
+
style: {
|
68
|
+
fill: this.startPoint.color,
|
69
|
+
}
|
70
|
+
}
|
71
|
+
]);
|
72
|
+
return preview;
|
73
|
+
}
|
74
|
+
build() {
|
75
|
+
return this.buildPreview();
|
76
|
+
}
|
77
|
+
preview(renderer) {
|
78
|
+
this.buildPreview().render(renderer);
|
79
|
+
}
|
80
|
+
addPoint(point) {
|
81
|
+
this.endPoint = point;
|
82
|
+
}
|
83
|
+
}
|
@@ -1,15 +1,10 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
export
|
7
|
-
|
8
|
-
width: number;
|
9
|
-
time: number;
|
10
|
-
color: Color4;
|
11
|
-
}
|
12
|
-
export default class StrokeBuilder {
|
1
|
+
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
2
|
+
import Rect2 from '../../geometry/Rect2';
|
3
|
+
import Stroke from '../Stroke';
|
4
|
+
import { StrokeDataPoint } from '../../types';
|
5
|
+
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
6
|
+
export declare const makeFreehandLineBuilder: ComponentBuilderFactory;
|
7
|
+
export default class FreehandLineBuilder implements ComponentBuilder {
|
13
8
|
private startPoint;
|
14
9
|
private minFitAllowed;
|
15
10
|
private maxFitAllowed;
|
@@ -25,7 +20,8 @@ export default class StrokeBuilder {
|
|
25
20
|
constructor(startPoint: StrokeDataPoint, minFitAllowed: number, maxFitAllowed: number);
|
26
21
|
getBBox(): Rect2;
|
27
22
|
private getRenderingStyle;
|
28
|
-
|
23
|
+
private getPreview;
|
24
|
+
preview(renderer: AbstractRenderer): void;
|
29
25
|
build(): Stroke;
|
30
26
|
private roundPoint;
|
31
27
|
private finalizeCurrentCurve;
|
@@ -1,12 +1,20 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
|
-
import { Vec2 } from '
|
3
|
-
import Rect2 from '
|
4
|
-
import { PathCommandType } from '
|
5
|
-
import LineSegment2 from '
|
6
|
-
import Stroke from '
|
7
|
-
import Viewport from '
|
2
|
+
import { Vec2 } from '../../geometry/Vec2';
|
3
|
+
import Rect2 from '../../geometry/Rect2';
|
4
|
+
import { PathCommandType } from '../../geometry/Path';
|
5
|
+
import LineSegment2 from '../../geometry/LineSegment2';
|
6
|
+
import Stroke from '../Stroke';
|
7
|
+
import Viewport from '../../Viewport';
|
8
|
+
export const makeFreehandLineBuilder = (initialPoint, viewport) => {
|
9
|
+
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
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;
|
14
|
+
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist);
|
15
|
+
};
|
8
16
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
9
|
-
export default class
|
17
|
+
export default class FreehandLineBuilder {
|
10
18
|
constructor(startPoint,
|
11
19
|
// Maximum distance from the actual curve (irrespective of stroke width)
|
12
20
|
// for which a point is considered 'part of the curve'.
|
@@ -34,13 +42,18 @@ export default class StrokeBuilder {
|
|
34
42
|
};
|
35
43
|
}
|
36
44
|
// Get the segments that make up this' path. Can be called after calling build()
|
37
|
-
|
45
|
+
getPreview() {
|
38
46
|
if (this.currentCurve && this.lastPoint) {
|
39
47
|
const currentPath = this.currentSegmentToPath();
|
40
48
|
return this.segments.concat(currentPath);
|
41
49
|
}
|
42
50
|
return this.segments;
|
43
51
|
}
|
52
|
+
preview(renderer) {
|
53
|
+
for (const part of this.getPreview()) {
|
54
|
+
renderer.drawPath(part);
|
55
|
+
}
|
56
|
+
}
|
44
57
|
build() {
|
45
58
|
if (this.lastPoint) {
|
46
59
|
this.finalizeCurrentCurve();
|
@@ -179,7 +192,12 @@ export default class StrokeBuilder {
|
|
179
192
|
return;
|
180
193
|
}
|
181
194
|
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 4;
|
182
|
-
|
195
|
+
const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
|
196
|
+
&& this.segments.length === 0;
|
197
|
+
// Snap to the starting point if the stroke is contained within a small ball centered
|
198
|
+
// at the starting point.
|
199
|
+
// This allows us to create a circle/dot at the start of the stroke.
|
200
|
+
if (shouldSnapToInitial) {
|
183
201
|
return;
|
184
202
|
}
|
185
203
|
const velocity = newPoint.pos.minus(this.lastPoint.pos).times(1 / (deltaTime) * 1000);
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import Rect2 from '../../geometry/Rect2';
|
2
|
+
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import { StrokeDataPoint } from '../../types';
|
4
|
+
import AbstractComponent from '../AbstractComponent';
|
5
|
+
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
6
|
+
export declare const makeLineBuilder: ComponentBuilderFactory;
|
7
|
+
export default class LineBuilder implements ComponentBuilder {
|
8
|
+
private readonly startPoint;
|
9
|
+
private endPoint;
|
10
|
+
constructor(startPoint: StrokeDataPoint);
|
11
|
+
getBBox(): Rect2;
|
12
|
+
private buildPreview;
|
13
|
+
build(): AbstractComponent;
|
14
|
+
preview(renderer: AbstractRenderer): void;
|
15
|
+
addPoint(point: StrokeDataPoint): void;
|
16
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { PathCommandType } from '../../geometry/Path';
|
2
|
+
import Stroke from '../Stroke';
|
3
|
+
export const makeLineBuilder = (initialPoint, _viewport) => {
|
4
|
+
return new LineBuilder(initialPoint);
|
5
|
+
};
|
6
|
+
export default class LineBuilder {
|
7
|
+
constructor(startPoint) {
|
8
|
+
this.startPoint = startPoint;
|
9
|
+
this.endPoint = startPoint;
|
10
|
+
}
|
11
|
+
getBBox() {
|
12
|
+
const preview = this.buildPreview();
|
13
|
+
return preview.getBBox();
|
14
|
+
}
|
15
|
+
buildPreview() {
|
16
|
+
const startPoint = this.startPoint.pos;
|
17
|
+
const endPoint = this.endPoint.pos;
|
18
|
+
const toEnd = endPoint.minus(startPoint).normalized();
|
19
|
+
const startSize = this.startPoint.width / 2;
|
20
|
+
const endSize = this.endPoint.width / 2;
|
21
|
+
const lineNormal = toEnd.orthog();
|
22
|
+
const scaledStartNormal = lineNormal.times(startSize);
|
23
|
+
const scaledEndNormal = lineNormal.times(endSize);
|
24
|
+
const preview = new Stroke([
|
25
|
+
{
|
26
|
+
startPoint: startPoint.minus(scaledStartNormal),
|
27
|
+
commands: [
|
28
|
+
{
|
29
|
+
kind: PathCommandType.LineTo,
|
30
|
+
point: startPoint.plus(scaledStartNormal),
|
31
|
+
},
|
32
|
+
{
|
33
|
+
kind: PathCommandType.LineTo,
|
34
|
+
point: endPoint.plus(scaledEndNormal),
|
35
|
+
},
|
36
|
+
{
|
37
|
+
kind: PathCommandType.LineTo,
|
38
|
+
point: endPoint.minus(scaledEndNormal),
|
39
|
+
},
|
40
|
+
],
|
41
|
+
style: {
|
42
|
+
fill: this.startPoint.color,
|
43
|
+
}
|
44
|
+
}
|
45
|
+
]);
|
46
|
+
return preview;
|
47
|
+
}
|
48
|
+
build() {
|
49
|
+
return this.buildPreview();
|
50
|
+
}
|
51
|
+
preview(renderer) {
|
52
|
+
this.buildPreview().render(renderer);
|
53
|
+
}
|
54
|
+
addPoint(point) {
|
55
|
+
this.endPoint = point;
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import Rect2 from '../../geometry/Rect2';
|
2
|
+
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import { StrokeDataPoint } from '../../types';
|
4
|
+
import AbstractComponent from '../AbstractComponent';
|
5
|
+
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
6
|
+
export declare const makeFilledRectangleBuilder: ComponentBuilderFactory;
|
7
|
+
export declare const makeOutlinedRectangleBuilder: ComponentBuilderFactory;
|
8
|
+
export default class RectangleBuilder implements ComponentBuilder {
|
9
|
+
private readonly startPoint;
|
10
|
+
private filled;
|
11
|
+
private endPoint;
|
12
|
+
constructor(startPoint: StrokeDataPoint, filled: boolean);
|
13
|
+
getBBox(): Rect2;
|
14
|
+
private buildPreview;
|
15
|
+
build(): AbstractComponent;
|
16
|
+
preview(renderer: AbstractRenderer): void;
|
17
|
+
addPoint(point: StrokeDataPoint): void;
|
18
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import Path from '../../geometry/Path';
|
2
|
+
import Rect2 from '../../geometry/Rect2';
|
3
|
+
import Stroke from '../Stroke';
|
4
|
+
export const makeFilledRectangleBuilder = (initialPoint, _viewport) => {
|
5
|
+
return new RectangleBuilder(initialPoint, true);
|
6
|
+
};
|
7
|
+
export const makeOutlinedRectangleBuilder = (initialPoint, _viewport) => {
|
8
|
+
return new RectangleBuilder(initialPoint, false);
|
9
|
+
};
|
10
|
+
export default class RectangleBuilder {
|
11
|
+
constructor(startPoint, filled) {
|
12
|
+
this.startPoint = startPoint;
|
13
|
+
this.filled = filled;
|
14
|
+
// Initially, the start and end points are the same.
|
15
|
+
this.endPoint = startPoint;
|
16
|
+
}
|
17
|
+
getBBox() {
|
18
|
+
const preview = this.buildPreview();
|
19
|
+
return preview.getBBox();
|
20
|
+
}
|
21
|
+
buildPreview() {
|
22
|
+
const startPoint = this.startPoint.pos;
|
23
|
+
const endPoint = this.endPoint.pos;
|
24
|
+
const path = Path.fromRect(Rect2.fromCorners(startPoint, endPoint), this.filled ? null : this.endPoint.width);
|
25
|
+
const preview = new Stroke([
|
26
|
+
path.toRenderable({
|
27
|
+
fill: this.endPoint.color
|
28
|
+
}),
|
29
|
+
]);
|
30
|
+
return preview;
|
31
|
+
}
|
32
|
+
build() {
|
33
|
+
return this.buildPreview();
|
34
|
+
}
|
35
|
+
preview(renderer) {
|
36
|
+
this.buildPreview().render(renderer);
|
37
|
+
}
|
38
|
+
addPoint(point) {
|
39
|
+
this.endPoint = point;
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import Rect2 from '../../geometry/Rect2';
|
2
|
+
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import { StrokeDataPoint } from '../../types';
|
4
|
+
import Viewport from '../../Viewport';
|
5
|
+
import AbstractComponent from '../AbstractComponent';
|
6
|
+
export interface ComponentBuilder {
|
7
|
+
getBBox(): Rect2;
|
8
|
+
build(): AbstractComponent;
|
9
|
+
preview(renderer: AbstractRenderer): void;
|
10
|
+
addPoint(point: StrokeDataPoint): void;
|
11
|
+
}
|
12
|
+
export declare type ComponentBuilderFactory = (startPoint: StrokeDataPoint, viewport: Viewport) => ComponentBuilder;
|
File without changes
|
@@ -46,6 +46,7 @@ export default class Path {
|
|
46
46
|
intersection(line: LineSegment2): IntersectionResult[];
|
47
47
|
transformedBy(affineTransfm: Mat33): Path;
|
48
48
|
union(other: Path | null): Path;
|
49
|
+
static fromRect(rect: Rect2, lineWidth?: number | null): Path;
|
49
50
|
static fromRenderable(renderable: RenderablePathSpec): Path;
|
50
51
|
toRenderable(fill: RenderingStyle): RenderablePathSpec;
|
51
52
|
toString(): string;
|
@@ -158,6 +158,38 @@ export default class Path {
|
|
158
158
|
...other.parts,
|
159
159
|
]);
|
160
160
|
}
|
161
|
+
// Returns a path that outlines [rect]. If [lineWidth] is not given, the resultant path is
|
162
|
+
// the outline of [rect]. Otherwise, the resultant path represents a line of width [lineWidth]
|
163
|
+
// that traces [rect].
|
164
|
+
static fromRect(rect, lineWidth = null) {
|
165
|
+
const commands = [];
|
166
|
+
let corners;
|
167
|
+
let startPoint;
|
168
|
+
if (lineWidth !== null) {
|
169
|
+
// Vector from the top left corner or bottom right corner to the edge of the
|
170
|
+
// stroked region.
|
171
|
+
const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
|
172
|
+
const innerRect = Rect2.fromCorners(rect.topLeft.plus(cornerToEdge), rect.bottomRight.minus(cornerToEdge));
|
173
|
+
const outerRect = Rect2.fromCorners(rect.topLeft.minus(cornerToEdge), rect.bottomRight.plus(cornerToEdge));
|
174
|
+
corners = [
|
175
|
+
innerRect.corners[3],
|
176
|
+
...innerRect.corners,
|
177
|
+
...outerRect.corners.reverse(),
|
178
|
+
];
|
179
|
+
startPoint = outerRect.corners[3];
|
180
|
+
}
|
181
|
+
else {
|
182
|
+
corners = rect.corners.slice(1);
|
183
|
+
startPoint = rect.corners[0];
|
184
|
+
}
|
185
|
+
for (const corner of corners) {
|
186
|
+
commands.push({
|
187
|
+
kind: PathCommandType.LineTo,
|
188
|
+
point: corner,
|
189
|
+
});
|
190
|
+
}
|
191
|
+
return new Path(startPoint, commands);
|
192
|
+
}
|
161
193
|
static fromRenderable(renderable) {
|
162
194
|
return new Path(renderable.startPoint, renderable.commands);
|
163
195
|
}
|
@@ -9,6 +9,7 @@ export default class Vec3 {
|
|
9
9
|
};
|
10
10
|
static of(x: number, y: number, z: number): Vec3;
|
11
11
|
at(idx: number): number;
|
12
|
+
length(): number;
|
12
13
|
magnitude(): number;
|
13
14
|
magnitudeSquared(): number;
|
14
15
|
angle(): number;
|
@@ -18,6 +19,7 @@ export default class Vec3 {
|
|
18
19
|
minus(v: Vec3): Vec3;
|
19
20
|
dot(other: Vec3): number;
|
20
21
|
cross(other: Vec3): Vec3;
|
22
|
+
orthog(): Vec3;
|
21
23
|
extend(distance: number, direction: Vec3): Vec3;
|
22
24
|
lerp(target: Vec3, fractionTo: number): Vec3;
|
23
25
|
zip(other: Vec3, zip: (componentInThis: number, componentInOther: number) => number): Vec3;
|
@@ -26,6 +26,10 @@ export default class Vec3 {
|
|
26
26
|
return this.z;
|
27
27
|
throw new Error(`${idx} out of bounds!`);
|
28
28
|
}
|
29
|
+
// Alias for this.magnitude
|
30
|
+
length() {
|
31
|
+
return this.magnitude();
|
32
|
+
}
|
29
33
|
magnitude() {
|
30
34
|
return Math.sqrt(this.dot(this));
|
31
35
|
}
|
@@ -58,6 +62,15 @@ export default class Vec3 {
|
|
58
62
|
// | x2 y2 z2|
|
59
63
|
return Vec3.of(this.y * other.z - other.y * this.z, other.x * this.z - this.x * other.z, this.x * other.y - other.x * this.y);
|
60
64
|
}
|
65
|
+
// Returns a vector orthogonal to this. If this is a Vec2, returns [this] rotated
|
66
|
+
// 90 degrees counter-clockwise.
|
67
|
+
orthog() {
|
68
|
+
// If parallel to the z-axis
|
69
|
+
if (this.dot(Vec3.unitX) === 0 && this.dot(Vec3.unitY) === 0) {
|
70
|
+
return this.dot(Vec3.unitX) === 0 ? Vec3.unitX : this.cross(Vec3.unitX).normalized();
|
71
|
+
}
|
72
|
+
return this.cross(Vec3.unitZ.times(-1)).normalized();
|
73
|
+
}
|
61
74
|
// Returns this plus a vector of length [distance] in [direction]
|
62
75
|
extend(distance, direction) {
|
63
76
|
return this.plus(direction.normalized().times(distance));
|