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,364 @@
1
+ import { Bezier } from 'bezier-js';
2
+ import LineSegment2 from './LineSegment2';
3
+ import Rect2 from './Rect2';
4
+ import { Vec2 } from './Vec2';
5
+ export var PathCommandType;
6
+ (function (PathCommandType) {
7
+ PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
8
+ PathCommandType[PathCommandType["MoveTo"] = 1] = "MoveTo";
9
+ PathCommandType[PathCommandType["CubicBezierTo"] = 2] = "CubicBezierTo";
10
+ PathCommandType[PathCommandType["QuadraticBezierTo"] = 3] = "QuadraticBezierTo";
11
+ })(PathCommandType || (PathCommandType = {}));
12
+ export default class Path {
13
+ constructor(startPoint, parts) {
14
+ this.startPoint = startPoint;
15
+ this.parts = parts;
16
+ this.cachedGeometry = null;
17
+ // Initial bounding box contains one point: the start point.
18
+ this.bbox = Rect2.bboxOf([startPoint]);
19
+ // Convert into a representation of the geometry (cache for faster intersection
20
+ // calculation)
21
+ for (const part of parts) {
22
+ this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
23
+ }
24
+ }
25
+ // Lazy-loads and returns this path's geometry
26
+ get geometry() {
27
+ if (this.cachedGeometry) {
28
+ return this.cachedGeometry;
29
+ }
30
+ let startPoint = this.startPoint;
31
+ const geometry = [];
32
+ for (const part of this.parts) {
33
+ switch (part.kind) {
34
+ case PathCommandType.CubicBezierTo:
35
+ geometry.push(new Bezier(startPoint.xy, part.controlPoint1.xy, part.controlPoint2.xy, part.endPoint.xy));
36
+ startPoint = part.endPoint;
37
+ break;
38
+ case PathCommandType.QuadraticBezierTo:
39
+ geometry.push(new Bezier(startPoint.xy, part.controlPoint.xy, part.endPoint.xy));
40
+ startPoint = part.endPoint;
41
+ break;
42
+ case PathCommandType.LineTo:
43
+ geometry.push(new LineSegment2(startPoint, part.point));
44
+ startPoint = part.point;
45
+ break;
46
+ case PathCommandType.MoveTo:
47
+ startPoint = part.point;
48
+ break;
49
+ }
50
+ }
51
+ this.cachedGeometry = geometry;
52
+ return this.cachedGeometry;
53
+ }
54
+ static computeBBoxForSegment(startPoint, part) {
55
+ const points = [startPoint];
56
+ let exhaustivenessCheck;
57
+ switch (part.kind) {
58
+ case PathCommandType.MoveTo:
59
+ case PathCommandType.LineTo:
60
+ points.push(part.point);
61
+ break;
62
+ case PathCommandType.CubicBezierTo:
63
+ points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
64
+ break;
65
+ case PathCommandType.QuadraticBezierTo:
66
+ points.push(part.controlPoint, part.endPoint);
67
+ break;
68
+ default:
69
+ exhaustivenessCheck = part;
70
+ return exhaustivenessCheck;
71
+ }
72
+ return Rect2.bboxOf(points);
73
+ }
74
+ intersection(line) {
75
+ const result = [];
76
+ for (const part of this.geometry) {
77
+ if (part instanceof LineSegment2) {
78
+ const intersection = part.intersection(line);
79
+ if (intersection) {
80
+ result.push({
81
+ curve: part,
82
+ parameterValue: intersection.t,
83
+ point: intersection.point,
84
+ });
85
+ }
86
+ }
87
+ else {
88
+ const intersectionPoints = part.intersects(line).map(t => {
89
+ // We're using the .intersects(line) function, which is documented
90
+ // to always return numbers. However, to satisfy the type checker (and
91
+ // possibly improperly-defined types),
92
+ if (typeof t === 'string') {
93
+ t = parseFloat(t);
94
+ }
95
+ const point = Vec2.ofXY(part.get(t));
96
+ // Ensure that the intersection is on the line
97
+ if (point.minus(line.p1).magnitude() > line.length
98
+ || point.minus(line.p2).magnitude() > line.length) {
99
+ return null;
100
+ }
101
+ return {
102
+ point,
103
+ parameterValue: t,
104
+ curve: part,
105
+ };
106
+ }).filter(entry => entry !== null);
107
+ result.push(...intersectionPoints);
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ transformedBy(affineTransfm) {
113
+ const startPoint = affineTransfm.transformVec2(this.startPoint);
114
+ const newParts = [];
115
+ let exhaustivenessCheck;
116
+ for (const part of this.parts) {
117
+ switch (part.kind) {
118
+ case PathCommandType.MoveTo:
119
+ case PathCommandType.LineTo:
120
+ newParts.push({
121
+ kind: part.kind,
122
+ point: affineTransfm.transformVec2(part.point),
123
+ });
124
+ break;
125
+ case PathCommandType.CubicBezierTo:
126
+ newParts.push({
127
+ kind: part.kind,
128
+ controlPoint1: affineTransfm.transformVec2(part.controlPoint1),
129
+ controlPoint2: affineTransfm.transformVec2(part.controlPoint2),
130
+ endPoint: affineTransfm.transformVec2(part.endPoint),
131
+ });
132
+ break;
133
+ case PathCommandType.QuadraticBezierTo:
134
+ newParts.push({
135
+ kind: part.kind,
136
+ controlPoint: affineTransfm.transformVec2(part.controlPoint),
137
+ endPoint: affineTransfm.transformVec2(part.endPoint),
138
+ });
139
+ break;
140
+ default:
141
+ exhaustivenessCheck = part;
142
+ return exhaustivenessCheck;
143
+ }
144
+ }
145
+ return new Path(startPoint, newParts);
146
+ }
147
+ // Creates a new path by joining [other] to the end of this path
148
+ union(other) {
149
+ if (!other) {
150
+ return this;
151
+ }
152
+ return new Path(this.startPoint, [
153
+ ...this.parts,
154
+ {
155
+ kind: PathCommandType.MoveTo,
156
+ point: other.startPoint,
157
+ },
158
+ ...other.parts,
159
+ ]);
160
+ }
161
+ static fromRenderable(renderable) {
162
+ return new Path(renderable.startPoint, renderable.commands);
163
+ }
164
+ toRenderable(fill) {
165
+ return {
166
+ startPoint: this.startPoint,
167
+ style: fill,
168
+ commands: this.parts,
169
+ };
170
+ }
171
+ toString() {
172
+ return Path.toString(this.startPoint, this.parts);
173
+ }
174
+ static toString(startPoint, parts) {
175
+ const result = [];
176
+ const toRoundedString = (num) => {
177
+ // Try to remove rounding errors. If the number ends in at least three/four zeroes
178
+ // (or nines) just one or two digits, it's probably a rounding error.
179
+ const fixRoundingUpExp = /^([-]?\d*\.?\d*[1-9.])0{4,}\d$/;
180
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d*9{4,}\d)$/;
181
+ let text = num.toString();
182
+ if (text.indexOf('.') === -1) {
183
+ return text;
184
+ }
185
+ const roundingDownMatch = hasRoundingDownExp.exec(text);
186
+ if (roundingDownMatch) {
187
+ const negativeSign = roundingDownMatch[1];
188
+ const lastDigit = parseInt(text.charAt(text.length - 1), 10);
189
+ const postDecimal = parseInt(roundingDownMatch[3], 10);
190
+ const preDecimal = parseInt(roundingDownMatch[2], 10);
191
+ let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
192
+ let carry = 0;
193
+ if (newPostDecimal.length > postDecimal.toString().length) {
194
+ // Left-shift
195
+ newPostDecimal = newPostDecimal.substring(1);
196
+ carry = 1;
197
+ }
198
+ text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
199
+ }
200
+ text = text.replace(fixRoundingUpExp, '$1');
201
+ // Remove trailing period (if it exists)
202
+ return text.replace(/[.]$/, '');
203
+ };
204
+ const addCommand = (command, ...points) => {
205
+ const parts = [];
206
+ for (const point of points) {
207
+ const xComponent = toRoundedString(point.x);
208
+ const yComponent = toRoundedString(point.y);
209
+ parts.push(`${xComponent},${yComponent}`);
210
+ }
211
+ result.push(`${command}${parts.join(' ')}`);
212
+ };
213
+ addCommand('M', startPoint);
214
+ let exhaustivenessCheck;
215
+ for (const part of parts) {
216
+ switch (part.kind) {
217
+ case PathCommandType.MoveTo:
218
+ addCommand('M', part.point);
219
+ break;
220
+ case PathCommandType.LineTo:
221
+ addCommand('L', part.point);
222
+ break;
223
+ case PathCommandType.CubicBezierTo:
224
+ addCommand('C', part.controlPoint1, part.controlPoint2, part.endPoint);
225
+ break;
226
+ case PathCommandType.QuadraticBezierTo:
227
+ addCommand('Q', part.controlPoint, part.endPoint);
228
+ break;
229
+ default:
230
+ exhaustivenessCheck = part;
231
+ return exhaustivenessCheck;
232
+ }
233
+ }
234
+ return result.join('');
235
+ }
236
+ // Create a Path from a SVG path specification.
237
+ // TODO: Support a larger subset of SVG paths.
238
+ // TODO: Support s,t shorthands.
239
+ static fromString(pathString) {
240
+ // See the MDN reference:
241
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
242
+ // and
243
+ // https://www.w3.org/TR/SVG2/paths.html
244
+ // Remove linebreaks
245
+ pathString = pathString.split('\n').join(' ');
246
+ let lastPos = Vec2.zero;
247
+ let firstPos = null;
248
+ const commands = [];
249
+ const moveTo = (point) => {
250
+ commands.push({
251
+ kind: PathCommandType.MoveTo,
252
+ point,
253
+ });
254
+ };
255
+ const lineTo = (point) => {
256
+ commands.push({
257
+ kind: PathCommandType.LineTo,
258
+ point,
259
+ });
260
+ };
261
+ const cubicBezierTo = (cp1, cp2, end) => {
262
+ commands.push({
263
+ kind: PathCommandType.CubicBezierTo,
264
+ controlPoint1: cp1,
265
+ controlPoint2: cp2,
266
+ endPoint: end,
267
+ });
268
+ };
269
+ const quadraticBeierTo = (controlPoint, endPoint) => {
270
+ commands.push({
271
+ kind: PathCommandType.QuadraticBezierTo,
272
+ controlPoint,
273
+ endPoint,
274
+ });
275
+ };
276
+ // Each command: Command character followed by anything that isn't a command character
277
+ const commandExp = /([MmZzLlHhVvCcSsQqTtAa])\s*([^a-zA-Z]*)/g;
278
+ let current;
279
+ while ((current = commandExp.exec(pathString)) !== null) {
280
+ const argParts = current[2].trim().split(/[^0-9.-]/).filter(part => part.length > 0);
281
+ const numericArgs = argParts.map(arg => parseFloat(arg));
282
+ const commandChar = current[1];
283
+ const uppercaseCommand = commandChar !== commandChar.toLowerCase();
284
+ const args = numericArgs.reduce((accumulator, current, index, parts) => {
285
+ if (index % 2 !== 0) {
286
+ const currentAsFloat = current;
287
+ const prevAsFloat = parts[index - 1];
288
+ return accumulator.concat(Vec2.of(prevAsFloat, currentAsFloat));
289
+ }
290
+ else {
291
+ return accumulator;
292
+ }
293
+ }, []).map((coordinate) => {
294
+ // Lowercase commands are relative, uppercase commands use absolute
295
+ // positioning
296
+ if (uppercaseCommand) {
297
+ lastPos = coordinate;
298
+ return coordinate;
299
+ }
300
+ else {
301
+ lastPos = lastPos.plus(coordinate);
302
+ return lastPos;
303
+ }
304
+ });
305
+ let expectedPointArgCount;
306
+ switch (commandChar.toLowerCase()) {
307
+ case 'm':
308
+ expectedPointArgCount = 1;
309
+ moveTo(args[0]);
310
+ break;
311
+ case 'l':
312
+ expectedPointArgCount = 1;
313
+ lineTo(args[0]);
314
+ break;
315
+ case 'z':
316
+ expectedPointArgCount = 0;
317
+ // firstPos can be null if the stroke data is just 'z'.
318
+ if (firstPos) {
319
+ lineTo(firstPos);
320
+ }
321
+ break;
322
+ case 'c':
323
+ expectedPointArgCount = 3;
324
+ cubicBezierTo(args[0], args[1], args[2]);
325
+ break;
326
+ case 'q':
327
+ expectedPointArgCount = 2;
328
+ quadraticBeierTo(args[0], args[1]);
329
+ break;
330
+ // Horizontal line
331
+ case 'h':
332
+ expectedPointArgCount = 0;
333
+ if (uppercaseCommand) {
334
+ lineTo(Vec2.of(numericArgs[0], lastPos.y));
335
+ }
336
+ else {
337
+ lineTo(lastPos.plus(Vec2.of(numericArgs[0], 0)));
338
+ }
339
+ break;
340
+ // Vertical line
341
+ case 'v':
342
+ expectedPointArgCount = 0;
343
+ if (uppercaseCommand) {
344
+ lineTo(Vec2.of(lastPos.x, numericArgs[1]));
345
+ }
346
+ else {
347
+ lineTo(lastPos.plus(Vec2.of(0, numericArgs[1])));
348
+ }
349
+ break;
350
+ default:
351
+ throw new Error(`Unknown path command ${commandChar}`);
352
+ }
353
+ if (args.length !== expectedPointArgCount) {
354
+ throw new Error(`
355
+ Incorrect number of arguments: got ${JSON.stringify(args)} with a length of ${args.length} ≠ ${expectedPointArgCount}.
356
+ `.trim());
357
+ }
358
+ if (args.length > 0) {
359
+ firstPos !== null && firstPos !== void 0 ? firstPos : (firstPos = args[0]);
360
+ }
361
+ }
362
+ return new Path(firstPos !== null && firstPos !== void 0 ? firstPos : Vec2.zero, commands);
363
+ }
364
+ }
@@ -0,0 +1,47 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import Mat33 from './Mat33';
3
+ import { Point2, Vec2 } from './Vec2';
4
+ interface RectTemplate {
5
+ x: number;
6
+ y: number;
7
+ w?: number;
8
+ h?: number;
9
+ width?: number;
10
+ height?: number;
11
+ }
12
+ export default class Rect2 {
13
+ readonly x: number;
14
+ readonly y: number;
15
+ readonly w: number;
16
+ readonly h: number;
17
+ readonly topLeft: Point2;
18
+ readonly size: Vec2;
19
+ readonly bottomRight: Point2;
20
+ readonly center: Point2;
21
+ readonly area: number;
22
+ constructor(x: number, y: number, w: number, h: number);
23
+ translatedBy(vec: Vec2): Rect2;
24
+ resizedTo(size: Vec2): Rect2;
25
+ containsPoint(other: Point2): boolean;
26
+ containsRect(other: Rect2): boolean;
27
+ intersects(other: Rect2): boolean;
28
+ intersection(other: Rect2): Rect2 | null;
29
+ union(other: Rect2): Rect2;
30
+ grownToPoint(point: Point2, margin?: number): Rect2;
31
+ grownBy(margin: number): Rect2;
32
+ get corners(): Point2[];
33
+ get maxDimension(): number;
34
+ get topRight(): import("./Vec3").default;
35
+ get bottomLeft(): import("./Vec3").default;
36
+ getEdges(): LineSegment2[];
37
+ transformedBoundingBox(affineTransform: Mat33): Rect2;
38
+ /** @return true iff this is equal to [other] ± fuzz */
39
+ eq(other: Rect2, fuzz?: number): boolean;
40
+ toString(): string;
41
+ static fromCorners(corner1: Point2, corner2: Point2): Rect2;
42
+ static bboxOf(points: Point2[], margin?: number): Rect2;
43
+ static of(template: RectTemplate): Rect2;
44
+ static empty: Rect2;
45
+ static unitSquare: Rect2;
46
+ }
47
+ export {};
@@ -0,0 +1,148 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import { Vec2 } from './Vec2';
3
+ // invariant: w > 0, h > 0.
4
+ export default class Rect2 {
5
+ constructor(x, y, w, h) {
6
+ this.x = x;
7
+ this.y = y;
8
+ this.w = w;
9
+ this.h = h;
10
+ if (w < 0) {
11
+ this.x += w;
12
+ this.w = Math.abs(w);
13
+ }
14
+ if (h < 0) {
15
+ this.y += h;
16
+ this.h = Math.abs(h);
17
+ }
18
+ // Precompute/store vector forms.
19
+ this.topLeft = Vec2.of(this.x, this.y);
20
+ this.size = Vec2.of(this.w, this.h);
21
+ this.bottomRight = this.topLeft.plus(this.size);
22
+ this.center = this.topLeft.plus(this.size.times(0.5));
23
+ this.area = this.w * this.h;
24
+ }
25
+ translatedBy(vec) {
26
+ return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
27
+ }
28
+ resizedTo(size) {
29
+ return new Rect2(this.x, this.y, size.x, size.y);
30
+ }
31
+ containsPoint(other) {
32
+ return this.x <= other.x && this.y <= other.y
33
+ && this.x + this.w >= other.x && this.y + this.h >= other.y;
34
+ }
35
+ containsRect(other) {
36
+ return this.x <= other.x && this.y <= other.y
37
+ && this.bottomRight.x >= other.bottomRight.x
38
+ && this.bottomRight.y >= other.bottomRight.y;
39
+ }
40
+ intersects(other) {
41
+ return this.intersection(other) !== null;
42
+ }
43
+ // Returns the overlap of this and [other], or null, if no such
44
+ // / overlap exists
45
+ intersection(other) {
46
+ const topLeft = this.topLeft.zip(other.topLeft, Math.max);
47
+ const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min);
48
+ // The intersection can't be outside of this rectangle
49
+ if (!this.containsPoint(topLeft) || !this.containsPoint(bottomRight)) {
50
+ return null;
51
+ }
52
+ else if (!other.containsPoint(topLeft) || !other.containsPoint(bottomRight)) {
53
+ return null;
54
+ }
55
+ return Rect2.fromCorners(topLeft, bottomRight);
56
+ }
57
+ // Returns a new rectangle containing both [this] and [other].
58
+ union(other) {
59
+ const topLeft = this.topLeft.zip(other.topLeft, Math.min);
60
+ const bottomRight = this.bottomRight.zip(other.bottomRight, Math.max);
61
+ return Rect2.fromCorners(topLeft, bottomRight);
62
+ }
63
+ // Returns a rectangle containing this and [point].
64
+ // [margin] is the minimum distance between the new point and the edge
65
+ // of the resultant rectangle.
66
+ grownToPoint(point, margin = 0) {
67
+ const otherRect = new Rect2(point.x - margin, point.y - margin, margin * 2, margin * 2);
68
+ return this.union(otherRect);
69
+ }
70
+ // Returns this grown by [margin] in both the x and y directions.
71
+ grownBy(margin) {
72
+ return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
73
+ }
74
+ get corners() {
75
+ return [
76
+ this.bottomRight,
77
+ this.topRight,
78
+ this.topLeft,
79
+ this.bottomLeft,
80
+ ];
81
+ }
82
+ get maxDimension() {
83
+ return Math.max(this.w, this.h);
84
+ }
85
+ get topRight() {
86
+ return this.bottomRight.plus(Vec2.of(0, -this.h));
87
+ }
88
+ get bottomLeft() {
89
+ return this.topLeft.plus(Vec2.of(0, this.h));
90
+ }
91
+ // Returns edges in the order
92
+ // [ rightEdge, topEdge, leftEdge, bottomEdge ]
93
+ getEdges() {
94
+ const corners = this.corners;
95
+ return [
96
+ new LineSegment2(corners[0], corners[1]),
97
+ new LineSegment2(corners[1], corners[2]),
98
+ new LineSegment2(corners[2], corners[3]),
99
+ new LineSegment2(corners[3], corners[0]),
100
+ ];
101
+ }
102
+ // [affineTransform] is a transformation matrix that both scales and **translates**.
103
+ // the bounding box of this' four corners after transformed by the given affine transformation.
104
+ transformedBoundingBox(affineTransform) {
105
+ return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
106
+ }
107
+ /** @return true iff this is equal to [other] ± fuzz */
108
+ eq(other, fuzz = 0) {
109
+ return this.topLeft.eq(other.topLeft, fuzz) && this.size.eq(other.size, fuzz);
110
+ }
111
+ toString() {
112
+ return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
113
+ }
114
+ static fromCorners(corner1, corner2) {
115
+ return new Rect2(Math.min(corner1.x, corner2.x), Math.min(corner1.y, corner2.y), Math.abs(corner1.x - corner2.x), Math.abs(corner1.y - corner2.y));
116
+ }
117
+ // Returns a box that contains all points in [points] with at least [margin]
118
+ // between each point and the edge of the box.
119
+ static bboxOf(points, margin = 0) {
120
+ let minX = 0;
121
+ let minY = 0;
122
+ let maxX = 0;
123
+ let maxY = 0;
124
+ let isFirst = true;
125
+ for (const point of points) {
126
+ if (isFirst) {
127
+ minX = point.x;
128
+ minY = point.y;
129
+ maxX = point.x;
130
+ maxY = point.y;
131
+ isFirst = false;
132
+ }
133
+ minX = Math.min(minX, point.x);
134
+ minY = Math.min(minY, point.y);
135
+ maxX = Math.max(maxX, point.x);
136
+ maxY = Math.max(maxY, point.y);
137
+ }
138
+ return Rect2.fromCorners(Vec2.of(minX - margin, minY - margin), Vec2.of(maxX + margin, maxY + margin));
139
+ }
140
+ static of(template) {
141
+ var _a, _b, _c, _d;
142
+ const width = (_b = (_a = template.width) !== null && _a !== void 0 ? _a : template.w) !== null && _b !== void 0 ? _b : 0;
143
+ const height = (_d = (_c = template.height) !== null && _c !== void 0 ? _c : template.h) !== null && _d !== void 0 ? _d : 0;
144
+ return new Rect2(template.x, template.y, width, height);
145
+ }
146
+ }
147
+ Rect2.empty = new Rect2(0, 0, 0, 0);
148
+ Rect2.unitSquare = new Rect2(0, 0, 1, 1);
@@ -0,0 +1,13 @@
1
+ import Vec3 from './Vec3';
2
+ export declare namespace Vec2 {
3
+ const of: (x: number, y: number) => Vec2;
4
+ const ofXY: ({ x, y }: {
5
+ x: number;
6
+ y: number;
7
+ }) => Vec2;
8
+ const unitX: Vec3;
9
+ const unitY: Vec3;
10
+ const zero: Vec3;
11
+ }
12
+ export declare type Point2 = Vec3;
13
+ export declare type Vec2 = Vec3;
@@ -0,0 +1,13 @@
1
+ import Vec3 from './Vec3';
2
+ export var Vec2;
3
+ (function (Vec2) {
4
+ Vec2.of = (x, y) => {
5
+ return Vec3.of(x, y, 0);
6
+ };
7
+ Vec2.ofXY = ({ x, y }) => {
8
+ return Vec3.of(x, y, 0);
9
+ };
10
+ Vec2.unitX = Vec2.of(1, 0);
11
+ Vec2.unitY = Vec2.of(0, 1);
12
+ Vec2.zero = Vec2.of(0, 0);
13
+ })(Vec2 || (Vec2 = {}));
@@ -0,0 +1,32 @@
1
+ export default class Vec3 {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ readonly z: number;
5
+ private constructor();
6
+ get xy(): {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ static of(x: number, y: number, z: number): Vec3;
11
+ at(idx: number): number;
12
+ magnitude(): number;
13
+ magnitudeSquared(): number;
14
+ angle(): number;
15
+ normalized(): Vec3;
16
+ times(c: number): Vec3;
17
+ plus(v: Vec3): Vec3;
18
+ minus(v: Vec3): Vec3;
19
+ dot(other: Vec3): number;
20
+ cross(other: Vec3): Vec3;
21
+ extend(distance: number, direction: Vec3): Vec3;
22
+ lerp(target: Vec3, fractionTo: number): Vec3;
23
+ zip(other: Vec3, zip: (componentInThis: number, componentInOther: number) => number): Vec3;
24
+ map(fn: (component: number) => number): Vec3;
25
+ asArray(): number[];
26
+ eq(other: Vec3, fuzz: number): boolean;
27
+ toString(): string;
28
+ static unitX: Vec3;
29
+ static unitY: Vec3;
30
+ static unitZ: Vec3;
31
+ static zero: Vec3;
32
+ }