js-draw 0.0.1
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/.eslintrc.js +57 -0
- package/.husky/pre-commit +4 -0
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/__mocks__/coloris.ts +8 -0
- package/__mocks__/styleMock.js +1 -0
- package/dist/__mocks__/coloris.d.ts +2 -0
- package/dist/__mocks__/coloris.js +5 -0
- package/dist/build_tools/BundledFile.d.ts +12 -0
- package/dist/build_tools/BundledFile.js +153 -0
- package/dist/scripts/bundle.d.ts +1 -0
- package/dist/scripts/bundle.js +19 -0
- package/dist/scripts/watchBundle.d.ts +1 -0
- package/dist/scripts/watchBundle.js +9 -0
- package/dist/src/Color4.d.ts +23 -0
- package/dist/src/Color4.js +102 -0
- package/dist/src/Display.d.ts +22 -0
- package/dist/src/Display.js +93 -0
- package/dist/src/Editor.d.ts +55 -0
- package/dist/src/Editor.js +366 -0
- package/dist/src/EditorImage.d.ts +44 -0
- package/dist/src/EditorImage.js +243 -0
- package/dist/src/EventDispatcher.d.ts +11 -0
- package/dist/src/EventDispatcher.js +39 -0
- package/dist/src/Pointer.d.ts +22 -0
- package/dist/src/Pointer.js +57 -0
- package/dist/src/SVGLoader.d.ts +21 -0
- package/dist/src/SVGLoader.js +204 -0
- package/dist/src/StrokeBuilder.d.ts +35 -0
- package/dist/src/StrokeBuilder.js +275 -0
- package/dist/src/UndoRedoHistory.d.ts +17 -0
- package/dist/src/UndoRedoHistory.js +46 -0
- package/dist/src/Viewport.d.ts +39 -0
- package/dist/src/Viewport.js +134 -0
- package/dist/src/commands/Command.d.ts +15 -0
- package/dist/src/commands/Command.js +29 -0
- package/dist/src/commands/Erase.d.ts +11 -0
- package/dist/src/commands/Erase.js +37 -0
- package/dist/src/commands/localization.d.ts +19 -0
- package/dist/src/commands/localization.js +17 -0
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +46 -0
- package/dist/src/components/Stroke.d.ts +16 -0
- package/dist/src/components/Stroke.js +79 -0
- package/dist/src/components/UnknownSVGObject.d.ts +15 -0
- package/dist/src/components/UnknownSVGObject.js +25 -0
- package/dist/src/components/localization.d.ts +5 -0
- package/dist/src/components/localization.js +4 -0
- package/dist/src/geometry/LineSegment2.d.ts +19 -0
- package/dist/src/geometry/LineSegment2.js +100 -0
- package/dist/src/geometry/Mat33.d.ts +31 -0
- package/dist/src/geometry/Mat33.js +187 -0
- package/dist/src/geometry/Path.d.ts +55 -0
- package/dist/src/geometry/Path.js +364 -0
- package/dist/src/geometry/Rect2.d.ts +47 -0
- package/dist/src/geometry/Rect2.js +148 -0
- package/dist/src/geometry/Vec2.d.ts +13 -0
- package/dist/src/geometry/Vec2.js +13 -0
- package/dist/src/geometry/Vec3.d.ts +32 -0
- package/dist/src/geometry/Vec3.js +98 -0
- package/dist/src/localization.d.ts +12 -0
- package/dist/src/localization.js +5 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +4 -0
- package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
- package/dist/src/rendering/AbstractRenderer.js +108 -0
- package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
- package/dist/src/rendering/CanvasRenderer.js +108 -0
- package/dist/src/rendering/DummyRenderer.d.ts +25 -0
- package/dist/src/rendering/DummyRenderer.js +65 -0
- package/dist/src/rendering/SVGRenderer.d.ts +27 -0
- package/dist/src/rendering/SVGRenderer.js +122 -0
- package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
- package/dist/src/testing/loadExpectExtensions.js +27 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
- package/dist/src/toolbar/HTMLToolbar.js +444 -0
- package/dist/src/toolbar/types.d.ts +17 -0
- package/dist/src/toolbar/types.js +5 -0
- package/dist/src/tools/BaseTool.d.ts +20 -0
- package/dist/src/tools/BaseTool.js +44 -0
- package/dist/src/tools/Eraser.d.ts +16 -0
- package/dist/src/tools/Eraser.js +53 -0
- package/dist/src/tools/PanZoom.d.ts +40 -0
- package/dist/src/tools/PanZoom.js +191 -0
- package/dist/src/tools/Pen.d.ts +25 -0
- package/dist/src/tools/Pen.js +97 -0
- package/dist/src/tools/SelectionTool.d.ts +49 -0
- package/dist/src/tools/SelectionTool.js +437 -0
- package/dist/src/tools/ToolController.d.ts +18 -0
- package/dist/src/tools/ToolController.js +110 -0
- package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
- package/dist/src/tools/ToolEnabledGroup.js +11 -0
- package/dist/src/tools/localization.d.ts +10 -0
- package/dist/src/tools/localization.js +9 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.js +20 -0
- package/jest.config.js +22 -0
- package/lint-staged.config.js +6 -0
- package/package.json +82 -0
- package/src/Color4.test.ts +12 -0
- package/src/Color4.ts +122 -0
- package/src/Display.ts +118 -0
- package/src/Editor.css +58 -0
- package/src/Editor.ts +469 -0
- package/src/EditorImage.test.ts +90 -0
- package/src/EditorImage.ts +297 -0
- package/src/EventDispatcher.test.ts +123 -0
- package/src/EventDispatcher.ts +53 -0
- package/src/Pointer.ts +93 -0
- package/src/SVGLoader.ts +230 -0
- package/src/StrokeBuilder.ts +362 -0
- package/src/UndoRedoHistory.ts +61 -0
- package/src/Viewport.ts +168 -0
- package/src/commands/Command.ts +43 -0
- package/src/commands/Erase.ts +52 -0
- package/src/commands/localization.ts +38 -0
- package/src/components/AbstractComponent.ts +73 -0
- package/src/components/Stroke.test.ts +18 -0
- package/src/components/Stroke.ts +102 -0
- package/src/components/UnknownSVGObject.ts +36 -0
- package/src/components/localization.ts +9 -0
- package/src/editorStyles.js +3 -0
- package/src/geometry/LineSegment2.test.ts +77 -0
- package/src/geometry/LineSegment2.ts +127 -0
- package/src/geometry/Mat33.test.ts +144 -0
- package/src/geometry/Mat33.ts +268 -0
- package/src/geometry/Path.fromString.test.ts +146 -0
- package/src/geometry/Path.test.ts +96 -0
- package/src/geometry/Path.toString.test.ts +31 -0
- package/src/geometry/Path.ts +456 -0
- package/src/geometry/Rect2.test.ts +121 -0
- package/src/geometry/Rect2.ts +215 -0
- package/src/geometry/Vec2.test.ts +32 -0
- package/src/geometry/Vec2.ts +18 -0
- package/src/geometry/Vec3.test.ts +29 -0
- package/src/geometry/Vec3.ts +133 -0
- package/src/localization.ts +27 -0
- package/src/rendering/AbstractRenderer.ts +164 -0
- package/src/rendering/CanvasRenderer.ts +141 -0
- package/src/rendering/DummyRenderer.ts +80 -0
- package/src/rendering/SVGRenderer.ts +159 -0
- package/src/testing/loadExpectExtensions.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +551 -0
- package/src/toolbar/toolbar.css +110 -0
- package/src/toolbar/types.ts +20 -0
- package/src/tools/BaseTool.ts +58 -0
- package/src/tools/Eraser.ts +67 -0
- package/src/tools/PanZoom.ts +253 -0
- package/src/tools/Pen.ts +121 -0
- package/src/tools/SelectionTool.test.ts +85 -0
- package/src/tools/SelectionTool.ts +545 -0
- package/src/tools/ToolController.ts +126 -0
- package/src/tools/ToolEnabledGroup.ts +14 -0
- package/src/tools/localization.ts +22 -0
- package/src/types.ts +133 -0
- package/tsconfig.json +28 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import Rect2 from '../geometry/Rect2';
|
3
|
+
import { Point2, Vec2 } from '../geometry/Vec2';
|
4
|
+
import Vec3 from '../geometry/Vec3';
|
5
|
+
import Viewport from '../Viewport';
|
6
|
+
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from './AbstractRenderer';
|
7
|
+
|
8
|
+
const minSquareCurveApproxDist = 25;
|
9
|
+
export default class CanvasRenderer extends AbstractRenderer {
|
10
|
+
private ignoreObjectsAboveLevel: number|null = null;
|
11
|
+
private ignoringObject: boolean = false;
|
12
|
+
|
13
|
+
public constructor(private ctx: CanvasRenderingContext2D, viewport: Viewport) {
|
14
|
+
super(viewport);
|
15
|
+
}
|
16
|
+
|
17
|
+
public displaySize(): Vec2 {
|
18
|
+
return Vec2.of(
|
19
|
+
this.ctx.canvas.clientWidth,
|
20
|
+
this.ctx.canvas.clientHeight
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
public clear() {
|
25
|
+
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
26
|
+
}
|
27
|
+
|
28
|
+
protected beginPath(startPoint: Point2) {
|
29
|
+
startPoint = this.viewport.canvasToScreen(startPoint);
|
30
|
+
|
31
|
+
this.ctx.beginPath();
|
32
|
+
this.ctx.moveTo(startPoint.x, startPoint.y);
|
33
|
+
}
|
34
|
+
|
35
|
+
protected endPath(style: RenderingStyle) {
|
36
|
+
this.ctx.fillStyle = style.fill.toHexString();
|
37
|
+
this.ctx.fill();
|
38
|
+
|
39
|
+
if (style.stroke) {
|
40
|
+
this.ctx.strokeStyle = style.stroke.color.toHexString();
|
41
|
+
this.ctx.lineWidth = this.viewport.getScaleFactor() * style.stroke.width;
|
42
|
+
this.ctx.stroke();
|
43
|
+
}
|
44
|
+
|
45
|
+
this.ctx.closePath();
|
46
|
+
}
|
47
|
+
|
48
|
+
protected lineTo(point: Point2) {
|
49
|
+
point = this.viewport.canvasToScreen(point);
|
50
|
+
this.ctx.lineTo(point.x, point.y);
|
51
|
+
}
|
52
|
+
|
53
|
+
protected moveTo(point: Point2) {
|
54
|
+
point = this.viewport.canvasToScreen(point);
|
55
|
+
this.ctx.moveTo(point.x, point.y);
|
56
|
+
}
|
57
|
+
|
58
|
+
protected traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2) {
|
59
|
+
p1 = this.viewport.canvasToScreen(p1);
|
60
|
+
p2 = this.viewport.canvasToScreen(p2);
|
61
|
+
p3 = this.viewport.canvasToScreen(p3);
|
62
|
+
|
63
|
+
// Approximate the curve if small enough.
|
64
|
+
const delta1 = p2.minus(p1);
|
65
|
+
const delta2 = p3.minus(p2);
|
66
|
+
if (delta1.magnitudeSquared() < minSquareCurveApproxDist
|
67
|
+
&& delta2.magnitudeSquared() < minSquareCurveApproxDist) {
|
68
|
+
this.ctx.lineTo(p3.x, p3.y);
|
69
|
+
} else {
|
70
|
+
this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3) {
|
75
|
+
controlPoint = this.viewport.canvasToScreen(controlPoint);
|
76
|
+
endPoint = this.viewport.canvasToScreen(endPoint);
|
77
|
+
|
78
|
+
// Approximate the curve with a line if small enough
|
79
|
+
const delta = controlPoint.minus(endPoint);
|
80
|
+
if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
|
81
|
+
this.ctx.lineTo(endPoint.x, endPoint.y);
|
82
|
+
} else {
|
83
|
+
this.ctx.quadraticCurveTo(
|
84
|
+
controlPoint.x, controlPoint.y, endPoint.x, endPoint.y
|
85
|
+
);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
public drawPath(path: RenderablePathSpec) {
|
90
|
+
if (this.ignoringObject) {
|
91
|
+
return;
|
92
|
+
}
|
93
|
+
|
94
|
+
super.drawPath(path);
|
95
|
+
}
|
96
|
+
|
97
|
+
public startObject(boundingBox: Rect2) {
|
98
|
+
// Should we ignore all objects within this object's bbox?
|
99
|
+
const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
|
100
|
+
const minRenderSize = 4;
|
101
|
+
if (Math.abs(diagonal.x) < minRenderSize && Math.abs(diagonal.y) < minRenderSize) {
|
102
|
+
this.ignoreObjectsAboveLevel = this.getNestingLevel();
|
103
|
+
this.ignoringObject = true;
|
104
|
+
}
|
105
|
+
|
106
|
+
super.startObject(boundingBox);
|
107
|
+
}
|
108
|
+
public endObject() {
|
109
|
+
super.endObject();
|
110
|
+
|
111
|
+
// If exiting an object with a too-small-to-draw bounding box,
|
112
|
+
if (this.ignoreObjectsAboveLevel !== null && this.getNestingLevel() <= this.ignoreObjectsAboveLevel) {
|
113
|
+
this.ignoreObjectsAboveLevel = null;
|
114
|
+
this.ignoringObject = false;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
public drawPoints(...points: Point2[]) {
|
119
|
+
const pointRadius = 10;
|
120
|
+
|
121
|
+
for (let i = 0; i < points.length; i++) {
|
122
|
+
const point = this.viewport.canvasToScreen(points[i]);
|
123
|
+
|
124
|
+
this.ctx.beginPath();
|
125
|
+
this.ctx.arc(point.x, point.y, pointRadius, 0, Math.PI * 2);
|
126
|
+
this.ctx.fillStyle = Color4.ofRGBA(
|
127
|
+
0.5 + Math.sin(i) / 2,
|
128
|
+
1.0,
|
129
|
+
0.5 + Math.cos(i * 0.2) / 4, 0.5
|
130
|
+
).toHexString();
|
131
|
+
this.ctx.fill();
|
132
|
+
this.ctx.stroke();
|
133
|
+
this.ctx.closePath();
|
134
|
+
|
135
|
+
this.ctx.textAlign = 'center';
|
136
|
+
this.ctx.textBaseline = 'middle';
|
137
|
+
this.ctx.fillStyle = 'black';
|
138
|
+
this.ctx.fillText(`${i}`, point.x, point.y, pointRadius * 2);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
// Renderer that outputs nothing. Useful for automated tests.
|
2
|
+
|
3
|
+
import Rect2 from '../geometry/Rect2';
|
4
|
+
import { Point2, Vec2 } from '../geometry/Vec2';
|
5
|
+
import Vec3 from '../geometry/Vec3';
|
6
|
+
import Viewport from '../Viewport';
|
7
|
+
import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
|
8
|
+
|
9
|
+
export default class DummyRenderer extends AbstractRenderer {
|
10
|
+
// Variables that track the state of what's been rendered
|
11
|
+
public clearedCount: number = 0;
|
12
|
+
public renderedPathCount: number = 0;
|
13
|
+
public lastFillStyle: RenderingStyle|null = null;
|
14
|
+
public lastPoint: Point2|null = null;
|
15
|
+
public objectNestingLevel: number = 0;
|
16
|
+
|
17
|
+
// List of points drawn since the last clear.
|
18
|
+
public pointBuffer: Point2[] = [];
|
19
|
+
|
20
|
+
public constructor(viewport: Viewport) {
|
21
|
+
super(viewport);
|
22
|
+
}
|
23
|
+
|
24
|
+
public displaySize(): Vec2 {
|
25
|
+
// Return a dummy
|
26
|
+
return Vec2.of(640, 480);
|
27
|
+
}
|
28
|
+
|
29
|
+
public clear() {
|
30
|
+
this.clearedCount ++;
|
31
|
+
this.renderedPathCount = 0;
|
32
|
+
this.pointBuffer = [];
|
33
|
+
|
34
|
+
// Ensure all objects finished rendering
|
35
|
+
if (this.objectNestingLevel > 0) {
|
36
|
+
throw new Error(
|
37
|
+
`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`
|
38
|
+
);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
protected beginPath(startPoint: Vec3) {
|
42
|
+
this.lastPoint = startPoint;
|
43
|
+
this.pointBuffer.push(startPoint);
|
44
|
+
}
|
45
|
+
protected endPath(style: RenderingStyle) {
|
46
|
+
this.renderedPathCount++;
|
47
|
+
this.lastFillStyle = style;
|
48
|
+
}
|
49
|
+
protected lineTo(point: Vec3) {
|
50
|
+
this.lastPoint = point;
|
51
|
+
this.pointBuffer.push(point);
|
52
|
+
}
|
53
|
+
protected moveTo(point: Point2) {
|
54
|
+
this.lastPoint = point;
|
55
|
+
this.pointBuffer.push(point);
|
56
|
+
}
|
57
|
+
protected traceCubicBezierCurve(p1: Vec3, p2: Vec3, p3: Vec3) {
|
58
|
+
this.lastPoint = p3;
|
59
|
+
this.pointBuffer.push(p1, p2, p3);
|
60
|
+
}
|
61
|
+
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3) {
|
62
|
+
this.lastPoint = endPoint;
|
63
|
+
this.pointBuffer.push(controlPoint, endPoint);
|
64
|
+
}
|
65
|
+
public drawPoints(..._points: Vec3[]) {
|
66
|
+
// drawPoints is intended for debugging.
|
67
|
+
// As such, it is unlikely to be the target of automated tests.
|
68
|
+
}
|
69
|
+
|
70
|
+
public startObject(boundingBox: Rect2) {
|
71
|
+
super.startObject(boundingBox);
|
72
|
+
|
73
|
+
this.objectNestingLevel += 1;
|
74
|
+
}
|
75
|
+
public endObject() {
|
76
|
+
super.endObject();
|
77
|
+
|
78
|
+
this.objectNestingLevel -= 1;
|
79
|
+
}
|
80
|
+
}
|
@@ -0,0 +1,159 @@
|
|
1
|
+
|
2
|
+
import Path, { PathCommand, PathCommandType } from '../geometry/Path';
|
3
|
+
import Rect2 from '../geometry/Rect2';
|
4
|
+
import { Point2, Vec2 } from '../geometry/Vec2';
|
5
|
+
import Viewport from '../Viewport';
|
6
|
+
import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
|
7
|
+
|
8
|
+
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
9
|
+
export default class SVGRenderer extends AbstractRenderer {
|
10
|
+
private currentPath: PathCommand[]|null;
|
11
|
+
private pathStart: Point2|null;
|
12
|
+
|
13
|
+
private lastPathStyle: RenderingStyle|null;
|
14
|
+
private lastPath: PathCommand[]|null;
|
15
|
+
private lastPathStart: Point2|null;
|
16
|
+
|
17
|
+
private mainGroup: SVGGElement;
|
18
|
+
|
19
|
+
public constructor(private elem: SVGSVGElement, viewport: Viewport) {
|
20
|
+
super(viewport);
|
21
|
+
this.clear();
|
22
|
+
}
|
23
|
+
|
24
|
+
public displaySize(): Vec2 {
|
25
|
+
return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
|
26
|
+
}
|
27
|
+
|
28
|
+
public clear() {
|
29
|
+
this.mainGroup = document.createElementNS(svgNameSpace, 'g');
|
30
|
+
|
31
|
+
// Remove all children
|
32
|
+
this.elem.replaceChildren(this.mainGroup);
|
33
|
+
}
|
34
|
+
|
35
|
+
protected beginPath(startPoint: Point2) {
|
36
|
+
this.currentPath = [];
|
37
|
+
this.pathStart = this.viewport.canvasToScreen(startPoint);
|
38
|
+
this.lastPathStart ??= this.pathStart;
|
39
|
+
}
|
40
|
+
|
41
|
+
protected endPath(style: RenderingStyle) {
|
42
|
+
if (this.currentPath == null) {
|
43
|
+
throw new Error('No path exists to end! Make sure beginPath was called!');
|
44
|
+
}
|
45
|
+
|
46
|
+
// Try to extend the previous path, if possible
|
47
|
+
if (style.fill.eq(this.lastPathStyle?.fill) && this.lastPath != null) {
|
48
|
+
this.lastPath.push({
|
49
|
+
kind: PathCommandType.MoveTo,
|
50
|
+
point: this.pathStart!,
|
51
|
+
}, ...this.currentPath);
|
52
|
+
this.pathStart = null;
|
53
|
+
this.currentPath = null;
|
54
|
+
} else {
|
55
|
+
this.addPathToSVG();
|
56
|
+
this.lastPathStart = this.pathStart;
|
57
|
+
this.lastPathStyle = style;
|
58
|
+
this.lastPath = this.currentPath;
|
59
|
+
|
60
|
+
this.pathStart = null;
|
61
|
+
this.currentPath = null;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
// Push [this.fullPath] to the SVG
|
66
|
+
private addPathToSVG() {
|
67
|
+
if (!this.lastPathStyle || !this.lastPath) {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
const pathElem = document.createElementNS(svgNameSpace, 'path');
|
72
|
+
pathElem.setAttribute('d', Path.toString(this.lastPathStart!, this.lastPath));
|
73
|
+
|
74
|
+
const style = this.lastPathStyle;
|
75
|
+
pathElem.setAttribute('fill', style.fill.toHexString());
|
76
|
+
|
77
|
+
if (style.stroke) {
|
78
|
+
pathElem.setAttribute('stroke', style.stroke.color.toHexString());
|
79
|
+
pathElem.setAttribute('stroke-width', style.stroke.width.toString());
|
80
|
+
}
|
81
|
+
|
82
|
+
this.mainGroup.appendChild(pathElem);
|
83
|
+
}
|
84
|
+
|
85
|
+
public startObject(boundingBox: Rect2) {
|
86
|
+
super.startObject(boundingBox);
|
87
|
+
|
88
|
+
// Only accumulate a path within an object
|
89
|
+
this.lastPath = null;
|
90
|
+
this.lastPathStart = null;
|
91
|
+
this.lastPathStyle = null;
|
92
|
+
}
|
93
|
+
|
94
|
+
public endObject() {
|
95
|
+
super.endObject();
|
96
|
+
|
97
|
+
// Don't extend paths across objects
|
98
|
+
this.addPathToSVG();
|
99
|
+
}
|
100
|
+
|
101
|
+
protected lineTo(point: Point2) {
|
102
|
+
point = this.viewport.canvasToScreen(point);
|
103
|
+
|
104
|
+
this.currentPath!.push({
|
105
|
+
kind: PathCommandType.LineTo,
|
106
|
+
point,
|
107
|
+
});
|
108
|
+
}
|
109
|
+
|
110
|
+
protected moveTo(point: Point2) {
|
111
|
+
point = this.viewport.canvasToScreen(point);
|
112
|
+
|
113
|
+
this.currentPath!.push({
|
114
|
+
kind: PathCommandType.MoveTo,
|
115
|
+
point,
|
116
|
+
});
|
117
|
+
}
|
118
|
+
|
119
|
+
protected traceCubicBezierCurve(
|
120
|
+
controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2
|
121
|
+
) {
|
122
|
+
controlPoint1 = this.viewport.canvasToScreen(controlPoint1);
|
123
|
+
controlPoint2 = this.viewport.canvasToScreen(controlPoint2);
|
124
|
+
endPoint = this.viewport.canvasToScreen(endPoint);
|
125
|
+
|
126
|
+
this.currentPath!.push({
|
127
|
+
kind: PathCommandType.CubicBezierTo,
|
128
|
+
controlPoint1,
|
129
|
+
controlPoint2,
|
130
|
+
endPoint,
|
131
|
+
});
|
132
|
+
}
|
133
|
+
|
134
|
+
protected traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2) {
|
135
|
+
controlPoint = this.viewport.canvasToScreen(controlPoint);
|
136
|
+
endPoint = this.viewport.canvasToScreen(endPoint);
|
137
|
+
|
138
|
+
this.currentPath!.push({
|
139
|
+
kind: PathCommandType.QuadraticBezierTo,
|
140
|
+
controlPoint,
|
141
|
+
endPoint,
|
142
|
+
});
|
143
|
+
}
|
144
|
+
|
145
|
+
public drawPoints(...points: Point2[]) {
|
146
|
+
points.map(point => {
|
147
|
+
const elem = document.createElementNS(svgNameSpace, 'circle');
|
148
|
+
elem.setAttribute('cx', `${point.x}`);
|
149
|
+
elem.setAttribute('cy', `${point.y}`);
|
150
|
+
elem.setAttribute('r', '15');
|
151
|
+
this.mainGroup.appendChild(elem);
|
152
|
+
});
|
153
|
+
}
|
154
|
+
|
155
|
+
// Renders a copy of the given element.
|
156
|
+
public drawSVGElem(elem: SVGElement) {
|
157
|
+
this.elem.appendChild(elem.cloneNode(true));
|
158
|
+
}
|
159
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
export const loadExpectExtensions = () => {
|
2
|
+
// Custom matchers. See
|
3
|
+
// https://jestjs.io/docs/expect#expectextendmatchers
|
4
|
+
expect.extend({
|
5
|
+
// Determine whether expected = actual based on the objects'
|
6
|
+
// .eq methods
|
7
|
+
objEq(actual: any, expected: any, ...eqArgs: any) {
|
8
|
+
let pass = false;
|
9
|
+
if ((expected ?? null) === null) {
|
10
|
+
pass = actual.eq(expected, ...eqArgs);
|
11
|
+
} else {
|
12
|
+
pass = expected.eq(actual, ...eqArgs);
|
13
|
+
}
|
14
|
+
|
15
|
+
return {
|
16
|
+
pass,
|
17
|
+
message: () => {
|
18
|
+
if (pass) {
|
19
|
+
return `Expected ${expected} not to .eq ${actual}. Options(${eqArgs})`;
|
20
|
+
}
|
21
|
+
return `Expected ${expected} to .eq ${actual}. Options(${eqArgs})`;
|
22
|
+
},
|
23
|
+
};
|
24
|
+
},
|
25
|
+
});
|
26
|
+
};
|
27
|
+
|
28
|
+
// Type declarations for custom matchers
|
29
|
+
export interface CustomMatchers<R = unknown> {
|
30
|
+
objEq(expected: {
|
31
|
+
eq: (other: any, ...args: any)=> boolean;
|
32
|
+
}, ...opts: any): R;
|
33
|
+
}
|
34
|
+
|
35
|
+
declare global {
|
36
|
+
export namespace jest {
|
37
|
+
interface Expect extends CustomMatchers {}
|
38
|
+
interface Matchers<R> extends CustomMatchers<R> {}
|
39
|
+
interface AsyncAsymmetricMatchers extends CustomMatchers {}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
export default loadExpectExtensions;
|