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.
Files changed (156) hide show
  1. package/.eslintrc.js +57 -0
  2. package/.husky/pre-commit +4 -0
  3. package/LICENSE +21 -0
  4. package/README.md +74 -0
  5. package/__mocks__/coloris.ts +8 -0
  6. package/__mocks__/styleMock.js +1 -0
  7. package/dist/__mocks__/coloris.d.ts +2 -0
  8. package/dist/__mocks__/coloris.js +5 -0
  9. package/dist/build_tools/BundledFile.d.ts +12 -0
  10. package/dist/build_tools/BundledFile.js +153 -0
  11. package/dist/scripts/bundle.d.ts +1 -0
  12. package/dist/scripts/bundle.js +19 -0
  13. package/dist/scripts/watchBundle.d.ts +1 -0
  14. package/dist/scripts/watchBundle.js +9 -0
  15. package/dist/src/Color4.d.ts +23 -0
  16. package/dist/src/Color4.js +102 -0
  17. package/dist/src/Display.d.ts +22 -0
  18. package/dist/src/Display.js +93 -0
  19. package/dist/src/Editor.d.ts +55 -0
  20. package/dist/src/Editor.js +366 -0
  21. package/dist/src/EditorImage.d.ts +44 -0
  22. package/dist/src/EditorImage.js +243 -0
  23. package/dist/src/EventDispatcher.d.ts +11 -0
  24. package/dist/src/EventDispatcher.js +39 -0
  25. package/dist/src/Pointer.d.ts +22 -0
  26. package/dist/src/Pointer.js +57 -0
  27. package/dist/src/SVGLoader.d.ts +21 -0
  28. package/dist/src/SVGLoader.js +204 -0
  29. package/dist/src/StrokeBuilder.d.ts +35 -0
  30. package/dist/src/StrokeBuilder.js +275 -0
  31. package/dist/src/UndoRedoHistory.d.ts +17 -0
  32. package/dist/src/UndoRedoHistory.js +46 -0
  33. package/dist/src/Viewport.d.ts +39 -0
  34. package/dist/src/Viewport.js +134 -0
  35. package/dist/src/commands/Command.d.ts +15 -0
  36. package/dist/src/commands/Command.js +29 -0
  37. package/dist/src/commands/Erase.d.ts +11 -0
  38. package/dist/src/commands/Erase.js +37 -0
  39. package/dist/src/commands/localization.d.ts +19 -0
  40. package/dist/src/commands/localization.js +17 -0
  41. package/dist/src/components/AbstractComponent.d.ts +19 -0
  42. package/dist/src/components/AbstractComponent.js +46 -0
  43. package/dist/src/components/Stroke.d.ts +16 -0
  44. package/dist/src/components/Stroke.js +79 -0
  45. package/dist/src/components/UnknownSVGObject.d.ts +15 -0
  46. package/dist/src/components/UnknownSVGObject.js +25 -0
  47. package/dist/src/components/localization.d.ts +5 -0
  48. package/dist/src/components/localization.js +4 -0
  49. package/dist/src/geometry/LineSegment2.d.ts +19 -0
  50. package/dist/src/geometry/LineSegment2.js +100 -0
  51. package/dist/src/geometry/Mat33.d.ts +31 -0
  52. package/dist/src/geometry/Mat33.js +187 -0
  53. package/dist/src/geometry/Path.d.ts +55 -0
  54. package/dist/src/geometry/Path.js +364 -0
  55. package/dist/src/geometry/Rect2.d.ts +47 -0
  56. package/dist/src/geometry/Rect2.js +148 -0
  57. package/dist/src/geometry/Vec2.d.ts +13 -0
  58. package/dist/src/geometry/Vec2.js +13 -0
  59. package/dist/src/geometry/Vec3.d.ts +32 -0
  60. package/dist/src/geometry/Vec3.js +98 -0
  61. package/dist/src/localization.d.ts +12 -0
  62. package/dist/src/localization.js +5 -0
  63. package/dist/src/main.d.ts +3 -0
  64. package/dist/src/main.js +4 -0
  65. package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
  66. package/dist/src/rendering/AbstractRenderer.js +108 -0
  67. package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
  68. package/dist/src/rendering/CanvasRenderer.js +108 -0
  69. package/dist/src/rendering/DummyRenderer.d.ts +25 -0
  70. package/dist/src/rendering/DummyRenderer.js +65 -0
  71. package/dist/src/rendering/SVGRenderer.d.ts +27 -0
  72. package/dist/src/rendering/SVGRenderer.js +122 -0
  73. package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
  74. package/dist/src/testing/loadExpectExtensions.js +27 -0
  75. package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
  76. package/dist/src/toolbar/HTMLToolbar.js +444 -0
  77. package/dist/src/toolbar/types.d.ts +17 -0
  78. package/dist/src/toolbar/types.js +5 -0
  79. package/dist/src/tools/BaseTool.d.ts +20 -0
  80. package/dist/src/tools/BaseTool.js +44 -0
  81. package/dist/src/tools/Eraser.d.ts +16 -0
  82. package/dist/src/tools/Eraser.js +53 -0
  83. package/dist/src/tools/PanZoom.d.ts +40 -0
  84. package/dist/src/tools/PanZoom.js +191 -0
  85. package/dist/src/tools/Pen.d.ts +25 -0
  86. package/dist/src/tools/Pen.js +97 -0
  87. package/dist/src/tools/SelectionTool.d.ts +49 -0
  88. package/dist/src/tools/SelectionTool.js +437 -0
  89. package/dist/src/tools/ToolController.d.ts +18 -0
  90. package/dist/src/tools/ToolController.js +110 -0
  91. package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
  92. package/dist/src/tools/ToolEnabledGroup.js +11 -0
  93. package/dist/src/tools/localization.d.ts +10 -0
  94. package/dist/src/tools/localization.js +9 -0
  95. package/dist/src/types.d.ts +88 -0
  96. package/dist/src/types.js +20 -0
  97. package/jest.config.js +22 -0
  98. package/lint-staged.config.js +6 -0
  99. package/package.json +82 -0
  100. package/src/Color4.test.ts +12 -0
  101. package/src/Color4.ts +122 -0
  102. package/src/Display.ts +118 -0
  103. package/src/Editor.css +58 -0
  104. package/src/Editor.ts +469 -0
  105. package/src/EditorImage.test.ts +90 -0
  106. package/src/EditorImage.ts +297 -0
  107. package/src/EventDispatcher.test.ts +123 -0
  108. package/src/EventDispatcher.ts +53 -0
  109. package/src/Pointer.ts +93 -0
  110. package/src/SVGLoader.ts +230 -0
  111. package/src/StrokeBuilder.ts +362 -0
  112. package/src/UndoRedoHistory.ts +61 -0
  113. package/src/Viewport.ts +168 -0
  114. package/src/commands/Command.ts +43 -0
  115. package/src/commands/Erase.ts +52 -0
  116. package/src/commands/localization.ts +38 -0
  117. package/src/components/AbstractComponent.ts +73 -0
  118. package/src/components/Stroke.test.ts +18 -0
  119. package/src/components/Stroke.ts +102 -0
  120. package/src/components/UnknownSVGObject.ts +36 -0
  121. package/src/components/localization.ts +9 -0
  122. package/src/editorStyles.js +3 -0
  123. package/src/geometry/LineSegment2.test.ts +77 -0
  124. package/src/geometry/LineSegment2.ts +127 -0
  125. package/src/geometry/Mat33.test.ts +144 -0
  126. package/src/geometry/Mat33.ts +268 -0
  127. package/src/geometry/Path.fromString.test.ts +146 -0
  128. package/src/geometry/Path.test.ts +96 -0
  129. package/src/geometry/Path.toString.test.ts +31 -0
  130. package/src/geometry/Path.ts +456 -0
  131. package/src/geometry/Rect2.test.ts +121 -0
  132. package/src/geometry/Rect2.ts +215 -0
  133. package/src/geometry/Vec2.test.ts +32 -0
  134. package/src/geometry/Vec2.ts +18 -0
  135. package/src/geometry/Vec3.test.ts +29 -0
  136. package/src/geometry/Vec3.ts +133 -0
  137. package/src/localization.ts +27 -0
  138. package/src/rendering/AbstractRenderer.ts +164 -0
  139. package/src/rendering/CanvasRenderer.ts +141 -0
  140. package/src/rendering/DummyRenderer.ts +80 -0
  141. package/src/rendering/SVGRenderer.ts +159 -0
  142. package/src/testing/loadExpectExtensions.ts +43 -0
  143. package/src/toolbar/HTMLToolbar.ts +551 -0
  144. package/src/toolbar/toolbar.css +110 -0
  145. package/src/toolbar/types.ts +20 -0
  146. package/src/tools/BaseTool.ts +58 -0
  147. package/src/tools/Eraser.ts +67 -0
  148. package/src/tools/PanZoom.ts +253 -0
  149. package/src/tools/Pen.ts +121 -0
  150. package/src/tools/SelectionTool.test.ts +85 -0
  151. package/src/tools/SelectionTool.ts +545 -0
  152. package/src/tools/ToolController.ts +126 -0
  153. package/src/tools/ToolEnabledGroup.ts +14 -0
  154. package/src/tools/localization.ts +22 -0
  155. package/src/types.ts +133 -0
  156. package/tsconfig.json +28 -0
@@ -0,0 +1,98 @@
1
+ // A vector with three components. Can also be used to represent a two-component vector
2
+ export default class Vec3 {
3
+ constructor(x, y, z) {
4
+ this.x = x;
5
+ this.y = y;
6
+ this.z = z;
7
+ }
8
+ // Returns the x, y components of this
9
+ get xy() {
10
+ // Useful for APIs that behave differently if .z is present.
11
+ return {
12
+ x: this.x,
13
+ y: this.y,
14
+ };
15
+ }
16
+ static of(x, y, z) {
17
+ return new Vec3(x, y, z);
18
+ }
19
+ // Returns this' [idx]th component
20
+ at(idx) {
21
+ if (idx === 0)
22
+ return this.x;
23
+ if (idx === 1)
24
+ return this.y;
25
+ if (idx === 2)
26
+ return this.z;
27
+ throw new Error(`${idx} out of bounds!`);
28
+ }
29
+ magnitude() {
30
+ return Math.sqrt(this.dot(this));
31
+ }
32
+ magnitudeSquared() {
33
+ return this.dot(this);
34
+ }
35
+ // Return this' angle in the XY plane (treats this as a Vec2)
36
+ angle() {
37
+ return Math.atan2(this.y, this.x);
38
+ }
39
+ normalized() {
40
+ const norm = this.magnitude();
41
+ return Vec3.of(this.x / norm, this.y / norm, this.z / norm);
42
+ }
43
+ times(c) {
44
+ return Vec3.of(this.x * c, this.y * c, this.z * c);
45
+ }
46
+ plus(v) {
47
+ return Vec3.of(this.x + v.x, this.y + v.y, this.z + v.z);
48
+ }
49
+ minus(v) {
50
+ return this.plus(v.times(-1));
51
+ }
52
+ dot(other) {
53
+ return this.x * other.x + this.y * other.y + this.z * other.z;
54
+ }
55
+ cross(other) {
56
+ // | i j k |
57
+ // | x1 y1 z1| = (i)(y1z2 - y2z1) - (j)(x1z2 - x2z1) + (k)(x1y2 - x2y1)
58
+ // | x2 y2 z2|
59
+ 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
+ }
61
+ // Returns this plus a vector of length [distance] in [direction]
62
+ extend(distance, direction) {
63
+ return this.plus(direction.normalized().times(distance));
64
+ }
65
+ // Returns a vector [fractionTo] of the way to target from this.
66
+ lerp(target, fractionTo) {
67
+ return this.times(1 - fractionTo).plus(target.times(fractionTo));
68
+ }
69
+ // [zip] Maps a component of this and a corresponding component of
70
+ // [other] to a component of the output vector.
71
+ zip(other, zip) {
72
+ return Vec3.of(zip(other.x, this.x), zip(other.y, this.y), zip(other.z, this.z));
73
+ }
74
+ // Returns a vector with each component acted on by [fn]
75
+ map(fn) {
76
+ return Vec3.of(fn(this.x), fn(this.y), fn(this.z));
77
+ }
78
+ asArray() {
79
+ return [this.x, this.y, this.z];
80
+ }
81
+ // [fuzz] The maximum difference between two components for this and [other]
82
+ // to be considered equal.
83
+ eq(other, fuzz) {
84
+ for (let i = 0; i < 3; i++) {
85
+ if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
86
+ return false;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+ toString() {
92
+ return `Vec(${this.x}, ${this.y}, ${this.z})`;
93
+ }
94
+ }
95
+ Vec3.unitX = Vec3.of(1, 0, 0);
96
+ Vec3.unitY = Vec3.of(0, 1, 0);
97
+ Vec3.unitZ = Vec3.of(0, 0, 1);
98
+ Vec3.zero = Vec3.of(0, 0, 0);
@@ -0,0 +1,12 @@
1
+ import { CommandLocalization } from './commands/localization';
2
+ import { ImageComponentLocalization } from './components/localization';
3
+ import { ToolbarLocalization } from './toolbar/types';
4
+ import { ToolLocalization } from './tools/localization';
5
+ export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization {
6
+ undoAnnouncement: (actionDescription: string) => string;
7
+ redoAnnouncement: (actionDescription: string) => string;
8
+ doneLoading: string;
9
+ loading: (percentage: number) => string;
10
+ imageEditor: string;
11
+ }
12
+ export declare const defaultEditorLocalization: EditorLocalization;
@@ -0,0 +1,5 @@
1
+ import { defaultCommandLocalization } from './commands/localization';
2
+ import { defaultComponentLocalization } from './components/localization';
3
+ import HTMLToolbar from './toolbar/HTMLToolbar';
4
+ import { defaultToolLocalization } from './tools/localization';
5
+ export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, HTMLToolbar.defaultLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), { loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });
@@ -0,0 +1,3 @@
1
+ import './editorStyles';
2
+ import Editor from './Editor';
3
+ export { Editor };
@@ -0,0 +1,4 @@
1
+ // Main entrypoint for bundled content
2
+ import './editorStyles';
3
+ import Editor from './Editor';
4
+ export { Editor };
@@ -0,0 +1,38 @@
1
+ import Color4 from '../Color4';
2
+ import { PathCommand } from '../geometry/Path';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import { Point2, Vec2 } from '../geometry/Vec2';
5
+ import Viewport from '../Viewport';
6
+ export interface RenderingStyle {
7
+ fill: Color4;
8
+ stroke?: {
9
+ color: Color4;
10
+ width: number;
11
+ };
12
+ }
13
+ export interface RenderablePathSpec {
14
+ startPoint: Point2;
15
+ commands: PathCommand[];
16
+ style: RenderingStyle;
17
+ }
18
+ export default abstract class AbstractRenderer {
19
+ protected viewport: Viewport;
20
+ protected constructor(viewport: Viewport);
21
+ abstract displaySize(): Vec2;
22
+ abstract clear(): void;
23
+ protected abstract beginPath(startPoint: Point2): void;
24
+ protected abstract endPath(style: RenderingStyle): void;
25
+ protected abstract lineTo(point: Point2): void;
26
+ protected abstract moveTo(point: Point2): void;
27
+ protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
28
+ protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
29
+ private objectLevel;
30
+ private currentPaths;
31
+ private flushPath;
32
+ drawPath(path: RenderablePathSpec): void;
33
+ drawRect(rect: Rect2, lineWidth: number, lineFill: RenderingStyle): void;
34
+ startObject(_boundingBox: Rect2): void;
35
+ endObject(): void;
36
+ protected getNestingLevel(): number;
37
+ abstract drawPoints(...points: Point2[]): void;
38
+ }
@@ -0,0 +1,108 @@
1
+ import { PathCommandType } from '../geometry/Path';
2
+ import Rect2 from '../geometry/Rect2';
3
+ import { Vec2 } from '../geometry/Vec2';
4
+ const stylesEqual = (a, b) => {
5
+ var _a, _b, _c, _d, _e;
6
+ return a === b || (a.fill.eq(b.fill)
7
+ && ((_b = (_a = a.stroke) === null || _a === void 0 ? void 0 : _a.color) === null || _b === void 0 ? void 0 : _b.eq((_c = b.stroke) === null || _c === void 0 ? void 0 : _c.color))
8
+ && ((_d = a.stroke) === null || _d === void 0 ? void 0 : _d.width) === ((_e = b.stroke) === null || _e === void 0 ? void 0 : _e.width));
9
+ };
10
+ export default class AbstractRenderer {
11
+ constructor(viewport) {
12
+ this.viewport = viewport;
13
+ this.objectLevel = 0;
14
+ this.currentPaths = null;
15
+ }
16
+ flushPath() {
17
+ if (!this.currentPaths) {
18
+ return;
19
+ }
20
+ let lastStyle = null;
21
+ for (const path of this.currentPaths) {
22
+ const { startPoint, commands, style } = path;
23
+ if (!lastStyle || !stylesEqual(lastStyle, style)) {
24
+ if (lastStyle) {
25
+ this.endPath(lastStyle);
26
+ }
27
+ this.beginPath(startPoint);
28
+ lastStyle = style;
29
+ }
30
+ else {
31
+ this.moveTo(startPoint);
32
+ }
33
+ for (const command of commands) {
34
+ if (command.kind === PathCommandType.LineTo) {
35
+ this.lineTo(command.point);
36
+ }
37
+ else if (command.kind === PathCommandType.MoveTo) {
38
+ this.moveTo(command.point);
39
+ }
40
+ else if (command.kind === PathCommandType.CubicBezierTo) {
41
+ this.traceCubicBezierCurve(command.controlPoint1, command.controlPoint2, command.endPoint);
42
+ }
43
+ else if (command.kind === PathCommandType.QuadraticBezierTo) {
44
+ this.traceQuadraticBezierCurve(command.controlPoint, command.endPoint);
45
+ }
46
+ }
47
+ }
48
+ if (lastStyle) {
49
+ this.endPath(lastStyle);
50
+ }
51
+ }
52
+ drawPath(path) {
53
+ // If we're being called outside of an object,
54
+ // we can't delay rendering
55
+ if (this.objectLevel === 0) {
56
+ this.currentPaths = [path];
57
+ this.flushPath();
58
+ this.currentPaths = null;
59
+ }
60
+ else {
61
+ // Otherwise, don't render paths all at once. This prevents faint lines between
62
+ // segments of the same stroke from being visible.
63
+ this.currentPaths.push(path);
64
+ }
65
+ }
66
+ // Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill]
67
+ drawRect(rect, lineWidth, lineFill) {
68
+ const commands = [];
69
+ // Vector from the top left corner or bottom right corner to the edge of the
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
+ });
90
+ }
91
+ // Note the start/end of an object with the given bounding box.
92
+ startObject(_boundingBox) {
93
+ this.currentPaths = [];
94
+ this.objectLevel++;
95
+ }
96
+ endObject() {
97
+ // Render the paths all at once
98
+ this.flushPath();
99
+ this.currentPaths = null;
100
+ this.objectLevel--;
101
+ if (this.objectLevel < 0) {
102
+ throw new Error('More objects have ended than have been started (negative object nesting level)!');
103
+ }
104
+ }
105
+ getNestingLevel() {
106
+ return this.objectLevel;
107
+ }
108
+ }
@@ -0,0 +1,23 @@
1
+ import Rect2 from '../geometry/Rect2';
2
+ import { Point2, Vec2 } from '../geometry/Vec2';
3
+ import Vec3 from '../geometry/Vec3';
4
+ import Viewport from '../Viewport';
5
+ import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from './AbstractRenderer';
6
+ export default class CanvasRenderer extends AbstractRenderer {
7
+ private ctx;
8
+ private ignoreObjectsAboveLevel;
9
+ private ignoringObject;
10
+ constructor(ctx: CanvasRenderingContext2D, viewport: Viewport);
11
+ displaySize(): Vec2;
12
+ clear(): void;
13
+ protected beginPath(startPoint: Point2): void;
14
+ protected endPath(style: RenderingStyle): void;
15
+ protected lineTo(point: Point2): void;
16
+ protected moveTo(point: Point2): void;
17
+ protected traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
18
+ protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
19
+ drawPath(path: RenderablePathSpec): void;
20
+ startObject(boundingBox: Rect2): void;
21
+ endObject(): void;
22
+ drawPoints(...points: Point2[]): void;
23
+ }
@@ -0,0 +1,108 @@
1
+ import Color4 from '../Color4';
2
+ import { Vec2 } from '../geometry/Vec2';
3
+ import AbstractRenderer from './AbstractRenderer';
4
+ const minSquareCurveApproxDist = 25;
5
+ export default class CanvasRenderer extends AbstractRenderer {
6
+ constructor(ctx, viewport) {
7
+ super(viewport);
8
+ this.ctx = ctx;
9
+ this.ignoreObjectsAboveLevel = null;
10
+ this.ignoringObject = false;
11
+ }
12
+ displaySize() {
13
+ return Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
14
+ }
15
+ clear() {
16
+ this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
17
+ }
18
+ beginPath(startPoint) {
19
+ startPoint = this.viewport.canvasToScreen(startPoint);
20
+ this.ctx.beginPath();
21
+ this.ctx.moveTo(startPoint.x, startPoint.y);
22
+ }
23
+ endPath(style) {
24
+ this.ctx.fillStyle = style.fill.toHexString();
25
+ this.ctx.fill();
26
+ if (style.stroke) {
27
+ this.ctx.strokeStyle = style.stroke.color.toHexString();
28
+ this.ctx.lineWidth = this.viewport.getScaleFactor() * style.stroke.width;
29
+ this.ctx.stroke();
30
+ }
31
+ this.ctx.closePath();
32
+ }
33
+ lineTo(point) {
34
+ point = this.viewport.canvasToScreen(point);
35
+ this.ctx.lineTo(point.x, point.y);
36
+ }
37
+ moveTo(point) {
38
+ point = this.viewport.canvasToScreen(point);
39
+ this.ctx.moveTo(point.x, point.y);
40
+ }
41
+ traceCubicBezierCurve(p1, p2, p3) {
42
+ p1 = this.viewport.canvasToScreen(p1);
43
+ p2 = this.viewport.canvasToScreen(p2);
44
+ p3 = this.viewport.canvasToScreen(p3);
45
+ // Approximate the curve if small enough.
46
+ const delta1 = p2.minus(p1);
47
+ const delta2 = p3.minus(p2);
48
+ if (delta1.magnitudeSquared() < minSquareCurveApproxDist
49
+ && delta2.magnitudeSquared() < minSquareCurveApproxDist) {
50
+ this.ctx.lineTo(p3.x, p3.y);
51
+ }
52
+ else {
53
+ this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
54
+ }
55
+ }
56
+ traceQuadraticBezierCurve(controlPoint, endPoint) {
57
+ controlPoint = this.viewport.canvasToScreen(controlPoint);
58
+ endPoint = this.viewport.canvasToScreen(endPoint);
59
+ // Approximate the curve with a line if small enough
60
+ const delta = controlPoint.minus(endPoint);
61
+ if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
62
+ this.ctx.lineTo(endPoint.x, endPoint.y);
63
+ }
64
+ else {
65
+ this.ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
66
+ }
67
+ }
68
+ drawPath(path) {
69
+ if (this.ignoringObject) {
70
+ return;
71
+ }
72
+ super.drawPath(path);
73
+ }
74
+ startObject(boundingBox) {
75
+ // Should we ignore all objects within this object's bbox?
76
+ const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
77
+ const minRenderSize = 4;
78
+ if (Math.abs(diagonal.x) < minRenderSize && Math.abs(diagonal.y) < minRenderSize) {
79
+ this.ignoreObjectsAboveLevel = this.getNestingLevel();
80
+ this.ignoringObject = true;
81
+ }
82
+ super.startObject(boundingBox);
83
+ }
84
+ endObject() {
85
+ super.endObject();
86
+ // If exiting an object with a too-small-to-draw bounding box,
87
+ if (this.ignoreObjectsAboveLevel !== null && this.getNestingLevel() <= this.ignoreObjectsAboveLevel) {
88
+ this.ignoreObjectsAboveLevel = null;
89
+ this.ignoringObject = false;
90
+ }
91
+ }
92
+ drawPoints(...points) {
93
+ const pointRadius = 10;
94
+ for (let i = 0; i < points.length; i++) {
95
+ const point = this.viewport.canvasToScreen(points[i]);
96
+ this.ctx.beginPath();
97
+ this.ctx.arc(point.x, point.y, pointRadius, 0, Math.PI * 2);
98
+ this.ctx.fillStyle = Color4.ofRGBA(0.5 + Math.sin(i) / 2, 1.0, 0.5 + Math.cos(i * 0.2) / 4, 0.5).toHexString();
99
+ this.ctx.fill();
100
+ this.ctx.stroke();
101
+ this.ctx.closePath();
102
+ this.ctx.textAlign = 'center';
103
+ this.ctx.textBaseline = 'middle';
104
+ this.ctx.fillStyle = 'black';
105
+ this.ctx.fillText(`${i}`, point.x, point.y, pointRadius * 2);
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,25 @@
1
+ import Rect2 from '../geometry/Rect2';
2
+ import { Point2, Vec2 } from '../geometry/Vec2';
3
+ import Vec3 from '../geometry/Vec3';
4
+ import Viewport from '../Viewport';
5
+ import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
6
+ export default class DummyRenderer extends AbstractRenderer {
7
+ clearedCount: number;
8
+ renderedPathCount: number;
9
+ lastFillStyle: RenderingStyle | null;
10
+ lastPoint: Point2 | null;
11
+ objectNestingLevel: number;
12
+ pointBuffer: Point2[];
13
+ constructor(viewport: Viewport);
14
+ displaySize(): Vec2;
15
+ clear(): void;
16
+ protected beginPath(startPoint: Vec3): void;
17
+ protected endPath(style: RenderingStyle): void;
18
+ protected lineTo(point: Vec3): void;
19
+ protected moveTo(point: Point2): void;
20
+ protected traceCubicBezierCurve(p1: Vec3, p2: Vec3, p3: Vec3): void;
21
+ protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
22
+ drawPoints(..._points: Vec3[]): void;
23
+ startObject(boundingBox: Rect2): void;
24
+ endObject(): void;
25
+ }
@@ -0,0 +1,65 @@
1
+ // Renderer that outputs nothing. Useful for automated tests.
2
+ import { Vec2 } from '../geometry/Vec2';
3
+ import AbstractRenderer from './AbstractRenderer';
4
+ export default class DummyRenderer extends AbstractRenderer {
5
+ constructor(viewport) {
6
+ super(viewport);
7
+ // Variables that track the state of what's been rendered
8
+ this.clearedCount = 0;
9
+ this.renderedPathCount = 0;
10
+ this.lastFillStyle = null;
11
+ this.lastPoint = null;
12
+ this.objectNestingLevel = 0;
13
+ // List of points drawn since the last clear.
14
+ this.pointBuffer = [];
15
+ }
16
+ displaySize() {
17
+ // Return a dummy
18
+ return Vec2.of(640, 480);
19
+ }
20
+ clear() {
21
+ this.clearedCount++;
22
+ this.renderedPathCount = 0;
23
+ this.pointBuffer = [];
24
+ // Ensure all objects finished rendering
25
+ if (this.objectNestingLevel > 0) {
26
+ throw new Error(`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`);
27
+ }
28
+ }
29
+ beginPath(startPoint) {
30
+ this.lastPoint = startPoint;
31
+ this.pointBuffer.push(startPoint);
32
+ }
33
+ endPath(style) {
34
+ this.renderedPathCount++;
35
+ this.lastFillStyle = style;
36
+ }
37
+ lineTo(point) {
38
+ this.lastPoint = point;
39
+ this.pointBuffer.push(point);
40
+ }
41
+ moveTo(point) {
42
+ this.lastPoint = point;
43
+ this.pointBuffer.push(point);
44
+ }
45
+ traceCubicBezierCurve(p1, p2, p3) {
46
+ this.lastPoint = p3;
47
+ this.pointBuffer.push(p1, p2, p3);
48
+ }
49
+ traceQuadraticBezierCurve(controlPoint, endPoint) {
50
+ this.lastPoint = endPoint;
51
+ this.pointBuffer.push(controlPoint, endPoint);
52
+ }
53
+ drawPoints(..._points) {
54
+ // drawPoints is intended for debugging.
55
+ // As such, it is unlikely to be the target of automated tests.
56
+ }
57
+ startObject(boundingBox) {
58
+ super.startObject(boundingBox);
59
+ this.objectNestingLevel += 1;
60
+ }
61
+ endObject() {
62
+ super.endObject();
63
+ this.objectNestingLevel -= 1;
64
+ }
65
+ }
@@ -0,0 +1,27 @@
1
+ import Rect2 from '../geometry/Rect2';
2
+ import { Point2, Vec2 } from '../geometry/Vec2';
3
+ import Viewport from '../Viewport';
4
+ import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
5
+ export default class SVGRenderer extends AbstractRenderer {
6
+ private elem;
7
+ private currentPath;
8
+ private pathStart;
9
+ private lastPathStyle;
10
+ private lastPath;
11
+ private lastPathStart;
12
+ private mainGroup;
13
+ constructor(elem: SVGSVGElement, viewport: Viewport);
14
+ displaySize(): Vec2;
15
+ clear(): void;
16
+ protected beginPath(startPoint: Point2): void;
17
+ protected endPath(style: RenderingStyle): void;
18
+ private addPathToSVG;
19
+ startObject(boundingBox: Rect2): void;
20
+ endObject(): void;
21
+ protected lineTo(point: Point2): void;
22
+ protected moveTo(point: Point2): void;
23
+ protected traceCubicBezierCurve(controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2): void;
24
+ protected traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
25
+ drawPoints(...points: Point2[]): void;
26
+ drawSVGElem(elem: SVGElement): void;
27
+ }
@@ -0,0 +1,122 @@
1
+ import Path, { PathCommandType } from '../geometry/Path';
2
+ import { Vec2 } from '../geometry/Vec2';
3
+ import AbstractRenderer from './AbstractRenderer';
4
+ const svgNameSpace = 'http://www.w3.org/2000/svg';
5
+ export default class SVGRenderer extends AbstractRenderer {
6
+ constructor(elem, viewport) {
7
+ super(viewport);
8
+ this.elem = elem;
9
+ this.clear();
10
+ }
11
+ displaySize() {
12
+ return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
13
+ }
14
+ clear() {
15
+ this.mainGroup = document.createElementNS(svgNameSpace, 'g');
16
+ // Remove all children
17
+ this.elem.replaceChildren(this.mainGroup);
18
+ }
19
+ beginPath(startPoint) {
20
+ var _a;
21
+ this.currentPath = [];
22
+ this.pathStart = this.viewport.canvasToScreen(startPoint);
23
+ (_a = this.lastPathStart) !== null && _a !== void 0 ? _a : (this.lastPathStart = this.pathStart);
24
+ }
25
+ endPath(style) {
26
+ var _a;
27
+ if (this.currentPath == null) {
28
+ throw new Error('No path exists to end! Make sure beginPath was called!');
29
+ }
30
+ // Try to extend the previous path, if possible
31
+ if (style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) && this.lastPath != null) {
32
+ this.lastPath.push({
33
+ kind: PathCommandType.MoveTo,
34
+ point: this.pathStart,
35
+ }, ...this.currentPath);
36
+ this.pathStart = null;
37
+ this.currentPath = null;
38
+ }
39
+ else {
40
+ this.addPathToSVG();
41
+ this.lastPathStart = this.pathStart;
42
+ this.lastPathStyle = style;
43
+ this.lastPath = this.currentPath;
44
+ this.pathStart = null;
45
+ this.currentPath = null;
46
+ }
47
+ }
48
+ // Push [this.fullPath] to the SVG
49
+ addPathToSVG() {
50
+ if (!this.lastPathStyle || !this.lastPath) {
51
+ return;
52
+ }
53
+ const pathElem = document.createElementNS(svgNameSpace, 'path');
54
+ pathElem.setAttribute('d', Path.toString(this.lastPathStart, this.lastPath));
55
+ const style = this.lastPathStyle;
56
+ pathElem.setAttribute('fill', style.fill.toHexString());
57
+ if (style.stroke) {
58
+ pathElem.setAttribute('stroke', style.stroke.color.toHexString());
59
+ pathElem.setAttribute('stroke-width', style.stroke.width.toString());
60
+ }
61
+ this.mainGroup.appendChild(pathElem);
62
+ }
63
+ startObject(boundingBox) {
64
+ super.startObject(boundingBox);
65
+ // Only accumulate a path within an object
66
+ this.lastPath = null;
67
+ this.lastPathStart = null;
68
+ this.lastPathStyle = null;
69
+ }
70
+ endObject() {
71
+ super.endObject();
72
+ // Don't extend paths across objects
73
+ this.addPathToSVG();
74
+ }
75
+ lineTo(point) {
76
+ point = this.viewport.canvasToScreen(point);
77
+ this.currentPath.push({
78
+ kind: PathCommandType.LineTo,
79
+ point,
80
+ });
81
+ }
82
+ moveTo(point) {
83
+ point = this.viewport.canvasToScreen(point);
84
+ this.currentPath.push({
85
+ kind: PathCommandType.MoveTo,
86
+ point,
87
+ });
88
+ }
89
+ traceCubicBezierCurve(controlPoint1, controlPoint2, endPoint) {
90
+ controlPoint1 = this.viewport.canvasToScreen(controlPoint1);
91
+ controlPoint2 = this.viewport.canvasToScreen(controlPoint2);
92
+ endPoint = this.viewport.canvasToScreen(endPoint);
93
+ this.currentPath.push({
94
+ kind: PathCommandType.CubicBezierTo,
95
+ controlPoint1,
96
+ controlPoint2,
97
+ endPoint,
98
+ });
99
+ }
100
+ traceQuadraticBezierCurve(controlPoint, endPoint) {
101
+ controlPoint = this.viewport.canvasToScreen(controlPoint);
102
+ endPoint = this.viewport.canvasToScreen(endPoint);
103
+ this.currentPath.push({
104
+ kind: PathCommandType.QuadraticBezierTo,
105
+ controlPoint,
106
+ endPoint,
107
+ });
108
+ }
109
+ drawPoints(...points) {
110
+ points.map(point => {
111
+ const elem = document.createElementNS(svgNameSpace, 'circle');
112
+ elem.setAttribute('cx', `${point.x}`);
113
+ elem.setAttribute('cy', `${point.y}`);
114
+ elem.setAttribute('r', '15');
115
+ this.mainGroup.appendChild(elem);
116
+ });
117
+ }
118
+ // Renders a copy of the given element.
119
+ drawSVGElem(elem) {
120
+ this.elem.appendChild(elem.cloneNode(true));
121
+ }
122
+ }
@@ -0,0 +1,17 @@
1
+ export declare const loadExpectExtensions: () => void;
2
+ export interface CustomMatchers<R = unknown> {
3
+ objEq(expected: {
4
+ eq: (other: any, ...args: any) => boolean;
5
+ }, ...opts: any): R;
6
+ }
7
+ declare global {
8
+ export namespace jest {
9
+ interface Expect extends CustomMatchers {
10
+ }
11
+ interface Matchers<R> extends CustomMatchers<R> {
12
+ }
13
+ interface AsyncAsymmetricMatchers extends CustomMatchers {
14
+ }
15
+ }
16
+ }
17
+ export default loadExpectExtensions;