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,215 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import Mat33 from './Mat33';
3
+ import { Point2, Vec2 } from './Vec2';
4
+
5
+ // An object that can be converted to a Rect2.
6
+ interface RectTemplate {
7
+ x: number;
8
+ y: number;
9
+ w?: number;
10
+ h?: number;
11
+ width?: number;
12
+ height?: number;
13
+ }
14
+
15
+ // invariant: w > 0, h > 0.
16
+ export default class Rect2 {
17
+ // Derived state:
18
+
19
+ // topLeft assumes up is -y
20
+ public readonly topLeft: Point2;
21
+ public readonly size: Vec2;
22
+ public readonly bottomRight: Point2;
23
+ public readonly center: Point2;
24
+ public readonly area: number;
25
+
26
+ public constructor(
27
+ public readonly x: number,
28
+ public readonly y: number,
29
+ public readonly w: number,
30
+ public readonly h: number
31
+ ) {
32
+ if (w < 0) {
33
+ this.x += w;
34
+ this.w = Math.abs(w);
35
+ }
36
+
37
+ if (h < 0) {
38
+ this.y += h;
39
+ this.h = Math.abs(h);
40
+ }
41
+
42
+ // Precompute/store vector forms.
43
+ this.topLeft = Vec2.of(this.x, this.y);
44
+ this.size = Vec2.of(this.w, this.h);
45
+ this.bottomRight = this.topLeft.plus(this.size);
46
+ this.center = this.topLeft.plus(this.size.times(0.5));
47
+ this.area = this.w * this.h;
48
+ }
49
+
50
+ public translatedBy(vec: Vec2): Rect2 {
51
+ return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
52
+ }
53
+
54
+ public resizedTo(size: Vec2): Rect2 {
55
+ return new Rect2(this.x, this.y, size.x, size.y);
56
+ }
57
+
58
+ public containsPoint(other: Point2): boolean {
59
+ return this.x <= other.x && this.y <= other.y
60
+ && this.x + this.w >= other.x && this.y + this.h >= other.y;
61
+ }
62
+
63
+ public containsRect(other: Rect2): boolean {
64
+ return this.x <= other.x && this.y <= other.y
65
+ && this.bottomRight.x >= other.bottomRight.x
66
+ && this.bottomRight.y >= other.bottomRight.y;
67
+ }
68
+
69
+ public intersects(other: Rect2): boolean {
70
+ return this.intersection(other) !== null;
71
+ }
72
+
73
+ // Returns the overlap of this and [other], or null, if no such
74
+ // / overlap exists
75
+ public intersection(other: Rect2): Rect2|null {
76
+ const topLeft = this.topLeft.zip(other.topLeft, Math.max);
77
+ const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min);
78
+
79
+ // The intersection can't be outside of this rectangle
80
+ if (!this.containsPoint(topLeft) || !this.containsPoint(bottomRight)) {
81
+ return null;
82
+ } else if (!other.containsPoint(topLeft) || !other.containsPoint(bottomRight)) {
83
+ return null;
84
+ }
85
+
86
+ return Rect2.fromCorners(topLeft, bottomRight);
87
+ }
88
+
89
+ // Returns a new rectangle containing both [this] and [other].
90
+ public union(other: Rect2): Rect2 {
91
+ const topLeft = this.topLeft.zip(other.topLeft, Math.min);
92
+ const bottomRight = this.bottomRight.zip(other.bottomRight, Math.max);
93
+
94
+ return Rect2.fromCorners(
95
+ topLeft,
96
+ bottomRight
97
+ );
98
+ }
99
+
100
+ // Returns a rectangle containing this and [point].
101
+ // [margin] is the minimum distance between the new point and the edge
102
+ // of the resultant rectangle.
103
+ public grownToPoint(point: Point2, margin: number = 0): Rect2 {
104
+ const otherRect = new Rect2(
105
+ point.x - margin, point.y - margin,
106
+ margin * 2, margin * 2
107
+ );
108
+ return this.union(otherRect);
109
+ }
110
+
111
+ // Returns this grown by [margin] in both the x and y directions.
112
+ public grownBy(margin: number): Rect2 {
113
+ return new Rect2(
114
+ this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2
115
+ );
116
+ }
117
+
118
+ public get corners(): Point2[] {
119
+ return [
120
+ this.bottomRight,
121
+ this.topRight,
122
+ this.topLeft,
123
+ this.bottomLeft,
124
+ ];
125
+ }
126
+
127
+ public get maxDimension(): number {
128
+ return Math.max(this.w, this.h);
129
+ }
130
+
131
+ public get topRight() {
132
+ return this.bottomRight.plus(Vec2.of(0, -this.h));
133
+ }
134
+
135
+ public get bottomLeft() {
136
+ return this.topLeft.plus(Vec2.of(0, this.h));
137
+ }
138
+
139
+ // Returns edges in the order
140
+ // [ rightEdge, topEdge, leftEdge, bottomEdge ]
141
+ public getEdges(): LineSegment2[] {
142
+ const corners = this.corners;
143
+ return [
144
+ new LineSegment2(corners[0], corners[1]),
145
+ new LineSegment2(corners[1], corners[2]),
146
+ new LineSegment2(corners[2], corners[3]),
147
+ new LineSegment2(corners[3], corners[0]),
148
+ ];
149
+ }
150
+
151
+ // [affineTransform] is a transformation matrix that both scales and **translates**.
152
+ // the bounding box of this' four corners after transformed by the given affine transformation.
153
+ public transformedBoundingBox(affineTransform: Mat33): Rect2 {
154
+ return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
155
+ }
156
+
157
+ /** @return true iff this is equal to [other] ± fuzz */
158
+ public eq(other: Rect2, fuzz: number = 0): boolean {
159
+ return this.topLeft.eq(other.topLeft, fuzz) && this.size.eq(other.size, fuzz);
160
+ }
161
+
162
+ public toString(): string {
163
+ return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
164
+ }
165
+
166
+
167
+ public static fromCorners(corner1: Point2, corner2: Point2) {
168
+ return new Rect2(
169
+ Math.min(corner1.x, corner2.x),
170
+ Math.min(corner1.y, corner2.y),
171
+ Math.abs(corner1.x - corner2.x),
172
+ Math.abs(corner1.y - corner2.y)
173
+ );
174
+ }
175
+
176
+ // Returns a box that contains all points in [points] with at least [margin]
177
+ // between each point and the edge of the box.
178
+ public static bboxOf(points: Point2[], margin: number = 0) {
179
+ let minX = 0;
180
+ let minY = 0;
181
+ let maxX = 0;
182
+ let maxY = 0;
183
+ let isFirst = true;
184
+
185
+ for (const point of points) {
186
+ if (isFirst) {
187
+ minX = point.x;
188
+ minY = point.y;
189
+ maxX = point.x;
190
+ maxY = point.y;
191
+
192
+ isFirst = false;
193
+ }
194
+
195
+ minX = Math.min(minX, point.x);
196
+ minY = Math.min(minY, point.y);
197
+ maxX = Math.max(maxX, point.x);
198
+ maxY = Math.max(maxY, point.y);
199
+ }
200
+
201
+ return Rect2.fromCorners(
202
+ Vec2.of(minX - margin, minY - margin),
203
+ Vec2.of(maxX + margin, maxY + margin)
204
+ );
205
+ }
206
+
207
+ public static of(template: RectTemplate) {
208
+ const width = template.width ?? template.w ?? 0;
209
+ const height = template.height ?? template.h ?? 0;
210
+ return new Rect2(template.x, template.y, width, height);
211
+ }
212
+
213
+ public static empty = new Rect2(0, 0, 0, 0);
214
+ public static unitSquare = new Rect2(0, 0, 1, 1);
215
+ }
@@ -0,0 +1,32 @@
1
+ import { Vec2 } from './Vec2';
2
+ import Vec3 from './Vec3';
3
+ import { loadExpectExtensions } from '../testing/loadExpectExtensions';
4
+
5
+ loadExpectExtensions();
6
+
7
+ describe('Vec2', () => {
8
+ it('Magnitude', () => {
9
+ expect(Vec2.of(3, 4).magnitude()).toBe(5);
10
+ });
11
+
12
+ it('Addition', () => {
13
+ expect(Vec2.of(1, 2).plus(Vec2.of(3, 4))).objEq(Vec2.of(4, 6));
14
+ });
15
+
16
+ it('Multiplication', () => {
17
+ expect(Vec2.of(1, -1).times(22)).objEq(Vec2.of(22, -22));
18
+ });
19
+
20
+ it('More complicated expressions', () => {
21
+ expect((Vec2.of(1, 2).plus(Vec2.of(3, 4))).times(2)).objEq(Vec2.of(8, 12));
22
+ });
23
+
24
+ it('Angle', () => {
25
+ expect(Vec2.of(-1, 1).angle()).toBeCloseTo(3 * Math.PI / 4);
26
+ });
27
+
28
+ it('Perpindicular', () => {
29
+ const fuzz = 0.001;
30
+ expect(Vec2.unitX.cross(Vec3.unitZ)).objEq(Vec2.unitY.times(-1), fuzz);
31
+ });
32
+ });
@@ -0,0 +1,18 @@
1
+ import Vec3 from './Vec3';
2
+
3
+ export namespace Vec2 {
4
+ export const of = (x: number, y: number): Vec2 => {
5
+ return Vec3.of(x, y, 0);
6
+ };
7
+
8
+ export const ofXY = ({x, y}: { x: number, y: number }): Vec2 => {
9
+ return Vec3.of(x, y, 0);
10
+ };
11
+
12
+ export const unitX = Vec2.of(1, 0);
13
+ export const unitY = Vec2.of(0, 1);
14
+ export const zero = Vec2.of(0, 0);
15
+ }
16
+
17
+ export type Point2 = Vec3;
18
+ export type Vec2 = Vec3; // eslint-disable-line
@@ -0,0 +1,29 @@
1
+
2
+ import { loadExpectExtensions } from '../testing/loadExpectExtensions';
3
+ import Vec3 from './Vec3';
4
+
5
+ loadExpectExtensions();
6
+
7
+ describe('Vec3', () => {
8
+ it('.xy should contain the x and y components', () => {
9
+ const vec = Vec3.of(1, 2, 3);
10
+ expect(vec.xy).toMatchObject({
11
+ x: 1,
12
+ y: 2,
13
+ });
14
+ });
15
+
16
+ it('should be combinable with other vectors via .zip', () => {
17
+ const vec1 = Vec3.unitX;
18
+ const vec2 = Vec3.unitY;
19
+ expect(vec1.zip(vec2, Math.min)).objEq(Vec3.zero);
20
+ expect(vec1.zip(vec2, Math.max)).objEq(Vec3.of(1, 1, 0));
21
+ });
22
+
23
+ it('.cross should obey the right hand rule', () => {
24
+ const vec1 = Vec3.unitX;
25
+ const vec2 = Vec3.unitY;
26
+ expect(vec1.cross(vec2)).objEq(Vec3.unitZ);
27
+ expect(vec2.cross(vec1)).objEq(Vec3.unitZ.times(-1));
28
+ });
29
+ });
@@ -0,0 +1,133 @@
1
+
2
+
3
+ // A vector with three components. Can also be used to represent a two-component vector
4
+ export default class Vec3 {
5
+ private constructor(
6
+ public readonly x: number,
7
+ public readonly y: number,
8
+ public readonly z: number
9
+ ) {
10
+ }
11
+
12
+ // Returns the x, y components of this
13
+ public get xy(): { x: number; y: number } {
14
+ // Useful for APIs that behave differently if .z is present.
15
+ return {
16
+ x: this.x,
17
+ y: this.y,
18
+ };
19
+ }
20
+
21
+ public static of(x: number, y: number, z: number): Vec3 {
22
+ return new Vec3(x, y, z);
23
+ }
24
+
25
+ // Returns this' [idx]th component
26
+ public at(idx: number): number {
27
+ if (idx === 0) return this.x;
28
+ if (idx === 1) return this.y;
29
+ if (idx === 2) return this.z;
30
+
31
+ throw new Error(`${idx} out of bounds!`);
32
+ }
33
+
34
+ public magnitude(): number {
35
+ return Math.sqrt(this.dot(this));
36
+ }
37
+
38
+ public magnitudeSquared(): number {
39
+ return this.dot(this);
40
+ }
41
+
42
+ // Return this' angle in the XY plane (treats this as a Vec2)
43
+ public angle(): number {
44
+ return Math.atan2(this.y, this.x);
45
+ }
46
+
47
+ public normalized(): Vec3 {
48
+ const norm = this.magnitude();
49
+ return Vec3.of(this.x / norm, this.y / norm, this.z / norm);
50
+ }
51
+
52
+ public times(c: number): Vec3 {
53
+ return Vec3.of(this.x * c, this.y * c, this.z * c);
54
+ }
55
+
56
+ public plus(v: Vec3): Vec3 {
57
+ return Vec3.of(this.x + v.x, this.y + v.y, this.z + v.z);
58
+ }
59
+
60
+ public minus(v: Vec3): Vec3 {
61
+ return this.plus(v.times(-1));
62
+ }
63
+
64
+ public dot(other: Vec3): number {
65
+ return this.x * other.x + this.y * other.y + this.z * other.z;
66
+ }
67
+
68
+ public cross(other: Vec3): Vec3 {
69
+ // | i j k |
70
+ // | x1 y1 z1| = (i)(y1z2 - y2z1) - (j)(x1z2 - x2z1) + (k)(x1y2 - x2y1)
71
+ // | x2 y2 z2|
72
+ return Vec3.of(
73
+ this.y * other.z - other.y * this.z,
74
+ other.x * this.z - this.x * other.z,
75
+ this.x * other.y - other.x * this.y
76
+ );
77
+ }
78
+
79
+ // Returns this plus a vector of length [distance] in [direction]
80
+ public extend(distance: number, direction: Vec3): Vec3 {
81
+ return this.plus(direction.normalized().times(distance));
82
+ }
83
+
84
+ // Returns a vector [fractionTo] of the way to target from this.
85
+ public lerp(target: Vec3, fractionTo: number): Vec3 {
86
+ return this.times(1 - fractionTo).plus(target.times(fractionTo));
87
+ }
88
+
89
+ // [zip] Maps a component of this and a corresponding component of
90
+ // [other] to a component of the output vector.
91
+ public zip(
92
+ other: Vec3, zip: (componentInThis: number, componentInOther: number)=> number
93
+ ): Vec3 {
94
+ return Vec3.of(
95
+ zip(other.x, this.x),
96
+ zip(other.y, this.y),
97
+ zip(other.z, this.z)
98
+ );
99
+ }
100
+
101
+ // Returns a vector with each component acted on by [fn]
102
+ public map(fn: (component: number)=> number): Vec3 {
103
+ return Vec3.of(
104
+ fn(this.x), fn(this.y), fn(this.z)
105
+ );
106
+ }
107
+
108
+ public asArray(): number[] {
109
+ return [this.x, this.y, this.z];
110
+ }
111
+
112
+ // [fuzz] The maximum difference between two components for this and [other]
113
+ // to be considered equal.
114
+ public eq(other: Vec3, fuzz: number): boolean {
115
+ for (let i = 0; i < 3; i++) {
116
+ if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ public toString(): string {
125
+ return `Vec(${this.x}, ${this.y}, ${this.z})`;
126
+ }
127
+
128
+
129
+ public static unitX = Vec3.of(1, 0, 0);
130
+ public static unitY = Vec3.of(0, 1, 0);
131
+ public static unitZ = Vec3.of(0, 0, 1);
132
+ public static zero = Vec3.of(0, 0, 0);
133
+ }
@@ -0,0 +1,27 @@
1
+ import { CommandLocalization, defaultCommandLocalization } from './commands/localization';
2
+ import { defaultComponentLocalization, ImageComponentLocalization } from './components/localization';
3
+ import HTMLToolbar from './toolbar/HTMLToolbar';
4
+ import { ToolbarLocalization } from './toolbar/types';
5
+ import { defaultToolLocalization, ToolLocalization } from './tools/localization';
6
+
7
+
8
+ export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization {
9
+ undoAnnouncement: (actionDescription: string)=> string;
10
+ redoAnnouncement: (actionDescription: string)=> string;
11
+ doneLoading: string;
12
+ loading: (percentage: number)=>string;
13
+ imageEditor: string;
14
+ }
15
+
16
+ export const defaultEditorLocalization: EditorLocalization = {
17
+ ...HTMLToolbar.defaultLocalization,
18
+ ...defaultToolLocalization,
19
+ ...defaultCommandLocalization,
20
+ ...defaultComponentLocalization,
21
+ loading: (percentage: number) => `Loading ${percentage}%...`,
22
+ imageEditor: 'Image Editor',
23
+ doneLoading: 'Done loading',
24
+
25
+ undoAnnouncement: (commandDescription: string) => `Undid ${commandDescription}`,
26
+ redoAnnouncement: (commandDescription: string) => `Redid ${commandDescription}`,
27
+ };
@@ -0,0 +1,164 @@
1
+ import Color4 from '../Color4';
2
+ import { PathCommand, PathCommandType } from '../geometry/Path';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import { Point2, Vec2 } from '../geometry/Vec2';
5
+ import Viewport from '../Viewport';
6
+
7
+ export interface RenderingStyle {
8
+ fill: Color4;
9
+ stroke?: {
10
+ color: Color4;
11
+ width: number;
12
+ };
13
+ }
14
+
15
+ export interface RenderablePathSpec {
16
+ startPoint: Point2;
17
+ commands: PathCommand[];
18
+ style: RenderingStyle;
19
+ }
20
+
21
+ const stylesEqual = (a: RenderingStyle, b: RenderingStyle) => {
22
+ return a === b || (a.fill.eq(b.fill)
23
+ && a.stroke?.color?.eq(b.stroke?.color)
24
+ && a.stroke?.width === b.stroke?.width);
25
+ };
26
+
27
+ export default abstract class AbstractRenderer {
28
+ protected constructor(protected viewport: Viewport) { }
29
+
30
+ // Returns the size of the rendered region of this on
31
+ // the display (in pixels).
32
+ public abstract displaySize(): Vec2;
33
+
34
+ public abstract clear(): void;
35
+ protected abstract beginPath(startPoint: Point2): void;
36
+ protected abstract endPath(style: RenderingStyle): void;
37
+ protected abstract lineTo(point: Point2): void;
38
+ protected abstract moveTo(point: Point2): void;
39
+ protected abstract traceCubicBezierCurve(
40
+ p1: Point2, p2: Point2, p3: Point2,
41
+ ): void;
42
+ protected abstract traceQuadraticBezierCurve(
43
+ controlPoint: Point2, endPoint: Point2,
44
+ ): void;
45
+
46
+ private objectLevel: number = 0;
47
+ private currentPaths: RenderablePathSpec[]|null = null;
48
+ private flushPath() {
49
+ if (!this.currentPaths) {
50
+ return;
51
+ }
52
+
53
+ let lastStyle: RenderingStyle|null = null;
54
+ for (const path of this.currentPaths) {
55
+ const { startPoint, commands, style } = path;
56
+
57
+ if (!lastStyle || !stylesEqual(lastStyle, style)) {
58
+ if (lastStyle) {
59
+ this.endPath(lastStyle);
60
+ }
61
+
62
+ this.beginPath(startPoint);
63
+ lastStyle = style;
64
+ } else {
65
+ this.moveTo(startPoint);
66
+ }
67
+
68
+ for (const command of commands) {
69
+ if (command.kind === PathCommandType.LineTo) {
70
+ this.lineTo(command.point);
71
+ } else if (command.kind === PathCommandType.MoveTo) {
72
+ this.moveTo(command.point);
73
+ } else if (command.kind === PathCommandType.CubicBezierTo) {
74
+ this.traceCubicBezierCurve(
75
+ command.controlPoint1, command.controlPoint2, command.endPoint
76
+ );
77
+ } else if (command.kind === PathCommandType.QuadraticBezierTo) {
78
+ this.traceQuadraticBezierCurve(
79
+ command.controlPoint, command.endPoint
80
+ );
81
+ }
82
+ }
83
+ }
84
+
85
+ if (lastStyle) {
86
+ this.endPath(lastStyle);
87
+ }
88
+ }
89
+
90
+ public drawPath(path: RenderablePathSpec) {
91
+ // If we're being called outside of an object,
92
+ // we can't delay rendering
93
+ if (this.objectLevel === 0) {
94
+ this.currentPaths = [path];
95
+ this.flushPath();
96
+ this.currentPaths = null;
97
+ } else {
98
+ // Otherwise, don't render paths all at once. This prevents faint lines between
99
+ // segments of the same stroke from being visible.
100
+ this.currentPaths!.push(path);
101
+ }
102
+ }
103
+
104
+ // Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill]
105
+ public drawRect(rect: Rect2, lineWidth: number, lineFill: RenderingStyle): void {
106
+ const commands: PathCommand[] = [];
107
+
108
+ // Vector from the top left corner or bottom right corner to the edge of the
109
+ // stroked region.
110
+ const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
111
+ const innerRect = Rect2.fromCorners(
112
+ rect.topLeft.plus(cornerToEdge),
113
+ rect.bottomRight.minus(cornerToEdge)
114
+ );
115
+ const outerRect = Rect2.fromCorners(
116
+ rect.topLeft.minus(cornerToEdge),
117
+ rect.bottomRight.plus(cornerToEdge)
118
+ );
119
+
120
+ const corners = [
121
+ innerRect.corners[3],
122
+ ...innerRect.corners,
123
+ ...outerRect.corners.reverse(),
124
+ ];
125
+ for (const corner of corners) {
126
+ commands.push({
127
+ kind: PathCommandType.LineTo,
128
+ point: corner,
129
+ });
130
+ }
131
+
132
+ this.drawPath({
133
+ startPoint: outerRect.corners[3],
134
+ commands,
135
+ style: lineFill,
136
+ });
137
+ }
138
+
139
+ // Note the start/end of an object with the given bounding box.
140
+ public startObject(_boundingBox: Rect2) {
141
+ this.currentPaths = [];
142
+ this.objectLevel ++;
143
+ }
144
+
145
+ public endObject() {
146
+ // Render the paths all at once
147
+ this.flushPath();
148
+ this.currentPaths = null;
149
+ this.objectLevel --;
150
+
151
+ if (this.objectLevel < 0) {
152
+ throw new Error(
153
+ 'More objects have ended than have been started (negative object nesting level)!'
154
+ );
155
+ }
156
+ }
157
+
158
+ protected getNestingLevel() {
159
+ return this.objectLevel;
160
+ }
161
+
162
+ // Draw a representation of [points]. Intended for debugging.
163
+ public abstract drawPoints(...points: Point2[]): void;
164
+ }