js-draw 0.0.3 → 0.0.6
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 +17 -0
- package/README.md +108 -0
- package/build_tools/BundledFile.ts +167 -0
- package/build_tools/bundle.ts +11 -0
- package/dist/build_tools/BundledFile.d.ts +13 -0
- package/dist/build_tools/BundledFile.js +157 -0
- package/dist/build_tools/bundle.d.ts +1 -0
- package/dist/build_tools/bundle.js +5 -0
- package/dist/bundle.js +1 -0
- package/dist/src/Display.js +4 -1
- package/dist/src/Editor.d.ts +9 -2
- package/dist/src/Editor.js +36 -7
- package/dist/src/EditorImage.d.ts +4 -2
- package/dist/src/EditorImage.js +29 -5
- package/dist/src/Pointer.js +1 -1
- package/dist/src/SVGLoader.js +3 -1
- package/dist/src/Viewport.d.ts +2 -2
- package/dist/src/bundle/bundled.d.ts +4 -0
- package/dist/src/bundle/bundled.js +5 -0
- 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} +42 -12
- 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/src/components/builders/types.js +1 -0
- package/dist/src/geometry/Path.d.ts +1 -0
- package/dist/src/geometry/Path.js +43 -0
- package/dist/src/geometry/Vec3.d.ts +2 -0
- package/dist/src/geometry/Vec3.js +13 -0
- package/dist/src/localization.d.ts +1 -1
- package/dist/src/localization.js +2 -2
- package/dist/src/rendering/AbstractRenderer.js +3 -25
- package/dist/src/toolbar/HTMLToolbar.d.ts +5 -3
- package/dist/src/toolbar/HTMLToolbar.js +139 -30
- package/dist/src/toolbar/localization.d.ts +20 -0
- package/dist/src/toolbar/localization.js +19 -0
- package/dist/src/toolbar/types.d.ts +0 -13
- package/dist/src/tools/Pen.d.ts +13 -3
- package/dist/src/tools/Pen.js +37 -28
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/ToolController.js +3 -3
- package/dist/src/types.d.ts +14 -2
- package/dist/src/types.js +1 -0
- package/dist-test/test-dist-bundle.html +35 -0
- package/package.json +15 -5
- package/src/Display.ts +3 -1
- package/src/Editor.css +0 -1
- package/src/Editor.ts +62 -13
- package/src/EditorImage.test.ts +5 -3
- package/src/EditorImage.ts +31 -3
- package/src/Pointer.ts +1 -1
- package/src/SVGLoader.ts +5 -1
- package/src/Viewport.ts +1 -1
- package/src/bundle/bundled.ts +7 -0
- 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} +59 -22
- 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.fromString.test.ts +11 -24
- package/src/geometry/Path.ts +56 -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/localization.ts +2 -3
- package/src/rendering/AbstractRenderer.ts +3 -32
- package/src/{editorStyles.js → styles.js} +0 -0
- package/src/toolbar/HTMLToolbar.ts +167 -39
- package/src/toolbar/localization.ts +44 -0
- package/src/toolbar/toolbar.css +12 -0
- package/src/toolbar/types.ts +0 -16
- package/src/tools/Pen.ts +56 -34
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/ToolController.ts +3 -3
- package/src/types.ts +16 -1
@@ -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();
|
@@ -135,9 +148,21 @@ export default class StrokeBuilder {
|
|
135
148
|
projectionT = 0.9;
|
136
149
|
}
|
137
150
|
}
|
138
|
-
const
|
139
|
-
|
140
|
-
|
151
|
+
const halfVecT = projectionT;
|
152
|
+
let halfVec = Vec2.ofXY(this.currentCurve.normal(halfVecT))
|
153
|
+
.normalized().times(this.curveStartWidth / 2 * halfVecT
|
154
|
+
+ this.curveEndWidth / 2 * (1 - halfVecT));
|
155
|
+
// Computes a boundary curve. [direction] should be either +1 or -1 (determines the side
|
156
|
+
// of the center curve to place the boundary).
|
157
|
+
const computeBoundaryCurve = (direction, halfVec) => {
|
158
|
+
return new Bezier(startPt.plus(startVec.times(direction)), controlPoint.plus(halfVec.times(direction)), endPt.plus(endVec.times(direction)));
|
159
|
+
};
|
160
|
+
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
161
|
+
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
162
|
+
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
163
|
+
if (upperBoundary.intersects(lowerBoundary).length === 2) {
|
164
|
+
halfVec = halfVec.times(2);
|
165
|
+
}
|
141
166
|
const pathCommands = [
|
142
167
|
{
|
143
168
|
kind: PathCommandType.QuadraticBezierTo,
|
@@ -179,7 +204,12 @@ export default class StrokeBuilder {
|
|
179
204
|
return;
|
180
205
|
}
|
181
206
|
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 4;
|
182
|
-
|
207
|
+
const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
|
208
|
+
&& this.segments.length === 0;
|
209
|
+
// Snap to the starting point if the stroke is contained within a small ball centered
|
210
|
+
// at the starting point.
|
211
|
+
// This allows us to create a circle/dot at the start of the stroke.
|
212
|
+
if (shouldSnapToInitial) {
|
183
213
|
return;
|
184
214
|
}
|
185
215
|
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;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -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
|
}
|
@@ -245,14 +277,24 @@ export default class Path {
|
|
245
277
|
pathString = pathString.split('\n').join(' ');
|
246
278
|
let lastPos = Vec2.zero;
|
247
279
|
let firstPos = null;
|
280
|
+
let isFirstCommand = true;
|
248
281
|
const commands = [];
|
249
282
|
const moveTo = (point) => {
|
283
|
+
// The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
|
284
|
+
if (isFirstCommand) {
|
285
|
+
isFirstCommand = false;
|
286
|
+
return;
|
287
|
+
}
|
250
288
|
commands.push({
|
251
289
|
kind: PathCommandType.MoveTo,
|
252
290
|
point,
|
253
291
|
});
|
254
292
|
};
|
255
293
|
const lineTo = (point) => {
|
294
|
+
if (isFirstCommand) {
|
295
|
+
isFirstCommand = false;
|
296
|
+
return;
|
297
|
+
}
|
256
298
|
commands.push({
|
257
299
|
kind: PathCommandType.LineTo,
|
258
300
|
point,
|
@@ -358,6 +400,7 @@ export default class Path {
|
|
358
400
|
if (args.length > 0) {
|
359
401
|
firstPos !== null && firstPos !== void 0 ? firstPos : (firstPos = args[0]);
|
360
402
|
}
|
403
|
+
isFirstCommand = false;
|
361
404
|
}
|
362
405
|
return new Path(firstPos !== null && firstPos !== void 0 ? firstPos : Vec2.zero, commands);
|
363
406
|
}
|
@@ -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));
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { CommandLocalization } from './commands/localization';
|
2
2
|
import { ImageComponentLocalization } from './components/localization';
|
3
|
-
import { ToolbarLocalization } from './toolbar/
|
3
|
+
import { ToolbarLocalization } from './toolbar/localization';
|
4
4
|
import { ToolLocalization } from './tools/localization';
|
5
5
|
export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization {
|
6
6
|
undoAnnouncement: (actionDescription: string) => string;
|
package/dist/src/localization.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { defaultCommandLocalization } from './commands/localization';
|
2
2
|
import { defaultComponentLocalization } from './components/localization';
|
3
|
-
import
|
3
|
+
import { defaultToolbarLocalization } from './toolbar/localization';
|
4
4
|
import { defaultToolLocalization } from './tools/localization';
|
5
|
-
export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},
|
5
|
+
export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultToolbarLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), { loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });
|
@@ -1,6 +1,4 @@
|
|
1
|
-
import { PathCommandType } from '../geometry/Path';
|
2
|
-
import Rect2 from '../geometry/Rect2';
|
3
|
-
import { Vec2 } from '../geometry/Vec2';
|
1
|
+
import Path, { PathCommandType } from '../geometry/Path';
|
4
2
|
const stylesEqual = (a, b) => {
|
5
3
|
var _a, _b, _c, _d, _e;
|
6
4
|
return a === b || (a.fill.eq(b.fill)
|
@@ -65,28 +63,8 @@ export default class AbstractRenderer {
|
|
65
63
|
}
|
66
64
|
// Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill]
|
67
65
|
drawRect(rect, lineWidth, lineFill) {
|
68
|
-
const
|
69
|
-
|
70
|
-
// stroked region.
|
71
|
-
const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
|
72
|
-
const innerRect = Rect2.fromCorners(rect.topLeft.plus(cornerToEdge), rect.bottomRight.minus(cornerToEdge));
|
73
|
-
const outerRect = Rect2.fromCorners(rect.topLeft.minus(cornerToEdge), rect.bottomRight.plus(cornerToEdge));
|
74
|
-
const corners = [
|
75
|
-
innerRect.corners[3],
|
76
|
-
...innerRect.corners,
|
77
|
-
...outerRect.corners.reverse(),
|
78
|
-
];
|
79
|
-
for (const corner of corners) {
|
80
|
-
commands.push({
|
81
|
-
kind: PathCommandType.LineTo,
|
82
|
-
point: corner,
|
83
|
-
});
|
84
|
-
}
|
85
|
-
this.drawPath({
|
86
|
-
startPoint: outerRect.corners[3],
|
87
|
-
commands,
|
88
|
-
style: lineFill,
|
89
|
-
});
|
66
|
+
const path = Path.fromRect(rect, lineWidth);
|
67
|
+
this.drawPath(path.toRenderable(lineFill));
|
90
68
|
}
|
91
69
|
// Note the start/end of an object with the given bounding box.
|
92
70
|
startObject(_boundingBox) {
|
@@ -1,12 +1,14 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
|
-
import { ToolbarLocalization } from './
|
2
|
+
import { ToolbarLocalization } from './localization';
|
3
3
|
export default class HTMLToolbar {
|
4
4
|
private editor;
|
5
5
|
private localizationTable;
|
6
6
|
private container;
|
7
|
-
|
7
|
+
private penTypes;
|
8
8
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
9
|
+
setupColorPickers(): void;
|
9
10
|
addActionButton(text: string, command: () => void, parent?: Element): HTMLButtonElement;
|
10
11
|
private addUndoRedoButtons;
|
11
|
-
|
12
|
+
addDefaultToolWidgets(): void;
|
13
|
+
addDefaultActionButtons(): void;
|
12
14
|
}
|