js-draw 0.1.11 → 0.2.0
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 +1 -0
- package/.firebaserc +5 -0
- package/.github/workflows/firebase-hosting-merge.yml +25 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
- package/.github/workflows/github-pages.yml +52 -0
- package/CHANGELOG.md +13 -0
- package/README.md +11 -6
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +19 -0
- package/dist/src/Color4.js +24 -3
- package/dist/src/Editor.d.ts +133 -4
- package/dist/src/Editor.js +124 -27
- package/dist/src/EditorImage.d.ts +8 -3
- package/dist/src/EditorImage.js +42 -26
- package/dist/src/EventDispatcher.d.ts +18 -0
- package/dist/src/EventDispatcher.js +19 -4
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +4 -3
- package/dist/src/SVGLoader.d.ts +1 -1
- package/dist/src/SVGLoader.js +14 -6
- package/dist/src/UndoRedoHistory.js +15 -2
- package/dist/src/Viewport.d.ts +8 -25
- package/dist/src/Viewport.js +18 -10
- package/dist/src/bundle/bundled.d.ts +1 -2
- package/dist/src/bundle/bundled.js +1 -2
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/Command.js +4 -4
- package/dist/src/commands/Duplicate.d.ts +2 -2
- package/dist/src/commands/Duplicate.js +4 -5
- package/dist/src/commands/Erase.d.ts +2 -2
- package/dist/src/commands/Erase.js +7 -6
- package/dist/src/commands/SerializableCommand.d.ts +4 -5
- package/dist/src/commands/SerializableCommand.js +12 -4
- package/dist/src/commands/invertCommand.d.ts +4 -0
- package/dist/src/commands/invertCommand.js +44 -0
- package/dist/src/commands/lib.d.ts +6 -0
- package/dist/src/commands/lib.js +6 -0
- package/dist/src/commands/localization.d.ts +2 -1
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/components/AbstractComponent.d.ts +16 -11
- package/dist/src/components/AbstractComponent.js +28 -17
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +4 -4
- package/dist/src/components/SVGGlobalAttributesObject.js +8 -2
- package/dist/src/components/Stroke.d.ts +16 -6
- package/dist/src/components/Stroke.js +12 -9
- package/dist/src/components/Text.d.ts +5 -5
- package/dist/src/components/Text.js +9 -9
- package/dist/src/components/UnknownSVGObject.d.ts +4 -4
- package/dist/src/components/UnknownSVGObject.js +7 -2
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
- package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/LineBuilder.js +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.js +3 -3
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/components/lib.d.ts +4 -0
- package/dist/src/components/lib.js +4 -0
- package/dist/src/lib.d.ts +25 -0
- package/dist/src/lib.js +25 -0
- package/dist/src/localization.d.ts +1 -0
- package/dist/src/localization.js +5 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
- package/dist/src/{geometry → math}/LineSegment2.js +0 -0
- package/dist/src/math/Mat33.d.ts +78 -0
- package/dist/src/{geometry → math}/Mat33.js +48 -20
- package/dist/src/{geometry → math}/Path.d.ts +2 -1
- package/dist/src/{geometry → math}/Path.js +59 -52
- package/dist/src/{geometry → math}/Rect2.d.ts +2 -2
- package/dist/src/{geometry → math}/Rect2.js +0 -0
- package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
- package/dist/src/{geometry → math}/Vec2.js +0 -0
- package/dist/src/math/Vec3.d.ts +96 -0
- package/dist/src/{geometry → math}/Vec3.js +63 -15
- package/dist/src/math/lib.d.ts +7 -0
- package/dist/src/math/lib.js +7 -0
- package/dist/src/math/rounding.d.ts +3 -0
- package/dist/src/math/rounding.js +121 -0
- package/dist/src/rendering/Display.d.ts +47 -1
- package/dist/src/rendering/Display.js +60 -15
- package/dist/src/rendering/caching/CacheRecord.d.ts +3 -2
- package/dist/src/rendering/caching/CacheRecord.js +4 -1
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +5 -4
- package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
- package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
- package/dist/src/rendering/caching/RenderingCache.js +10 -11
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
- package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +2 -4
- package/dist/src/rendering/localization.d.ts +2 -0
- package/dist/src/rendering/localization.js +2 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/icons.d.ts +3 -0
- package/dist/src/toolbar/icons.js +142 -132
- package/dist/src/toolbar/localization.d.ts +2 -1
- package/dist/src/toolbar/localization.js +2 -1
- package/dist/src/toolbar/makeColorInput.js +3 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
- package/dist/src/toolbar/widgets/PenWidget.js +1 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
- package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
- package/dist/src/tools/Eraser.js +1 -1
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +24 -14
- package/dist/src/tools/Pen.d.ts +1 -2
- package/dist/src/tools/Pen.js +8 -1
- package/dist/src/tools/PipetteTool.js +1 -0
- package/dist/src/tools/SelectionTool.d.ts +3 -3
- package/dist/src/tools/SelectionTool.js +51 -28
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/types.d.ts +21 -10
- package/dist/src/types.js +7 -5
- package/firebase.json +16 -0
- package/package.json +118 -101
- package/src/Color4.ts +23 -2
- package/src/Editor.ts +181 -37
- package/src/EditorImage.test.ts +2 -4
- package/src/EditorImage.ts +46 -28
- package/src/EventDispatcher.ts +21 -6
- package/src/Pointer.ts +4 -3
- package/src/SVGLoader.ts +14 -6
- package/src/UndoRedoHistory.ts +18 -2
- package/src/Viewport.ts +23 -18
- package/src/bundle/bundled.ts +1 -2
- package/src/commands/Command.ts +5 -5
- package/src/commands/Duplicate.ts +4 -5
- package/src/commands/Erase.ts +7 -6
- package/src/commands/SerializableCommand.ts +17 -9
- package/src/commands/invertCommand.ts +51 -0
- package/src/commands/lib.ts +14 -0
- package/src/commands/localization.ts +3 -1
- package/src/components/AbstractComponent.ts +35 -24
- package/src/components/SVGGlobalAttributesObject.ts +11 -4
- package/src/components/Stroke.test.ts +4 -6
- package/src/components/Stroke.ts +15 -11
- package/src/components/Text.test.ts +2 -2
- package/src/components/Text.ts +9 -10
- package/src/components/UnknownSVGObject.ts +10 -4
- package/src/components/builders/ArrowBuilder.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +190 -80
- package/src/components/builders/LineBuilder.ts +2 -2
- package/src/components/builders/RectangleBuilder.ts +3 -3
- package/src/components/builders/types.ts +1 -1
- package/src/components/lib.ts +9 -0
- package/src/lib.ts +28 -0
- package/src/localization.ts +6 -0
- package/src/localizations/es.ts +2 -1
- package/src/{geometry → math}/LineSegment2.test.ts +0 -0
- package/src/{geometry → math}/LineSegment2.ts +0 -0
- package/src/{geometry → math}/Mat33.test.ts +0 -0
- package/src/{geometry → math}/Mat33.ts +48 -20
- package/src/{geometry → math}/Path.fromString.test.ts +0 -0
- package/src/{geometry → math}/Path.test.ts +0 -0
- package/src/{geometry → math}/Path.toString.test.ts +11 -2
- package/src/{geometry → math}/Path.ts +61 -58
- package/src/{geometry → math}/Rect2.test.ts +0 -0
- package/src/{geometry → math}/Rect2.ts +2 -2
- package/src/{geometry → math}/Vec2.test.ts +0 -0
- package/src/{geometry → math}/Vec2.ts +0 -0
- package/src/{geometry → math}/Vec3.test.ts +0 -0
- package/src/{geometry → math}/Vec3.ts +64 -16
- package/src/math/lib.ts +15 -0
- package/src/math/rounding.test.ts +40 -0
- package/src/math/rounding.ts +147 -0
- package/src/rendering/Display.ts +63 -15
- package/src/rendering/caching/CacheRecord.test.ts +3 -3
- package/src/rendering/caching/CacheRecord.ts +6 -2
- package/src/rendering/caching/CacheRecordManager.ts +34 -8
- package/src/rendering/caching/RenderingCache.test.ts +3 -3
- package/src/rendering/caching/RenderingCache.ts +11 -16
- package/src/rendering/caching/RenderingCacheNode.ts +23 -7
- package/src/rendering/caching/testUtils.ts +1 -1
- package/src/rendering/caching/types.ts +2 -7
- package/src/rendering/localization.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +4 -4
- package/src/rendering/renderers/CanvasRenderer.ts +5 -5
- package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
- package/src/rendering/renderers/DummyRenderer.ts +4 -4
- package/src/rendering/renderers/SVGRenderer.ts +10 -4
- package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
- package/src/toolbar/HTMLToolbar.ts +1 -0
- package/src/toolbar/icons.ts +157 -137
- package/src/toolbar/localization.ts +4 -2
- package/src/toolbar/makeColorInput.ts +3 -2
- package/src/toolbar/toolbar.css +1 -1
- package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
- package/src/toolbar/widgets/BaseWidget.ts +2 -0
- package/src/toolbar/widgets/HandToolWidget.ts +3 -3
- package/src/toolbar/widgets/PenWidget.ts +2 -0
- package/src/toolbar/widgets/SelectionWidget.ts +46 -41
- package/src/tools/Eraser.ts +2 -2
- package/src/tools/PanZoom.ts +28 -17
- package/src/tools/Pen.ts +11 -2
- package/src/tools/PipetteTool.ts +2 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +52 -24
- package/src/tools/TextTool.ts +2 -2
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/types.ts +23 -7
- package/tsconfig.json +4 -1
- package/typedoc.json +20 -0
- package/dist/src/geometry/Mat33.d.ts +0 -32
- package/dist/src/geometry/Vec3.d.ts +0 -34
@@ -1,15 +1,22 @@
|
|
1
1
|
import { Point2, Vec2 } from './Vec2';
|
2
2
|
import Vec3 from './Vec3';
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
/**
|
5
|
+
* Represents a three dimensional linear transformation or
|
6
|
+
* a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
7
|
+
* **and** translates while a linear transformation just scales/rotates/shears).
|
8
|
+
*/
|
7
9
|
export default class Mat33 {
|
8
10
|
private readonly rows: Vec3[];
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
/**
|
13
|
+
* Creates a matrix from inputs in the form,
|
14
|
+
* ```
|
15
|
+
* ⎡ a1 a2 a3 ⎤
|
16
|
+
* ⎢ b1 b2 b3 ⎥
|
17
|
+
* ⎣ c1 c2 c3 ⎦
|
18
|
+
* ```
|
19
|
+
*/
|
13
20
|
public constructor(
|
14
21
|
public readonly a1: number,
|
15
22
|
public readonly a2: number,
|
@@ -30,6 +37,14 @@ export default class Mat33 {
|
|
30
37
|
];
|
31
38
|
}
|
32
39
|
|
40
|
+
/**
|
41
|
+
* Creates a matrix from the given rows:
|
42
|
+
* ```
|
43
|
+
* ⎡ r1.x r1.y r1.z ⎤
|
44
|
+
* ⎢ r2.x r2.y r2.z ⎥
|
45
|
+
* ⎣ r3.x r3.y r3.z ⎦
|
46
|
+
* ```
|
47
|
+
*/
|
33
48
|
public static ofRows(r1: Vec3, r2: Vec3, r3: Vec3): Mat33 {
|
34
49
|
return new Mat33(
|
35
50
|
r1.x, r1.y, r1.z,
|
@@ -44,8 +59,13 @@ export default class Mat33 {
|
|
44
59
|
0, 0, 1
|
45
60
|
);
|
46
61
|
|
47
|
-
|
48
|
-
|
62
|
+
/**
|
63
|
+
* Either returns the inverse of this, or, if this matrix is singular/uninvertable,
|
64
|
+
* returns Mat33.identity.
|
65
|
+
*
|
66
|
+
* This may cache the computed inverse and return the cached version instead of recomputing
|
67
|
+
* it.
|
68
|
+
*/
|
49
69
|
public inverse(): Mat33 {
|
50
70
|
return this.computeInverse() ?? Mat33.identity;
|
51
71
|
}
|
@@ -162,9 +182,11 @@ export default class Mat33 {
|
|
162
182
|
);
|
163
183
|
}
|
164
184
|
|
165
|
-
|
166
|
-
|
167
|
-
|
185
|
+
/**
|
186
|
+
* Applies this as an affine transformation to the given vector.
|
187
|
+
* Returns a transformed version of `other`.
|
188
|
+
*/
|
189
|
+
public transformVec2(other: Vec2): Vec2 {
|
168
190
|
// When transforming a Vec2, we want to use the z transformation
|
169
191
|
// components of this for translation:
|
170
192
|
// ⎡ . . tX ⎤
|
@@ -179,8 +201,10 @@ export default class Mat33 {
|
|
179
201
|
return Vec2.of(intermediate.x, intermediate.y);
|
180
202
|
}
|
181
203
|
|
182
|
-
|
183
|
-
|
204
|
+
/**
|
205
|
+
* Applies this as a linear transformation to the given vector (doesn't translate).
|
206
|
+
* This is the standard way of transforming vectors in ℝ³.
|
207
|
+
*/
|
184
208
|
public transformVec3(other: Vec3): Vec3 {
|
185
209
|
return Vec3.of(
|
186
210
|
this.rows[0].dot(other),
|
@@ -189,7 +213,7 @@ export default class Mat33 {
|
|
189
213
|
);
|
190
214
|
}
|
191
215
|
|
192
|
-
|
216
|
+
/** Returns true iff this = other ± fuzz */
|
193
217
|
public eq(other: Mat33, fuzz: number = 0): boolean {
|
194
218
|
for (let i = 0; i < 3; i++) {
|
195
219
|
if (!this.rows[i].eq(other.rows[i], fuzz)) {
|
@@ -205,12 +229,16 @@ export default class Mat33 {
|
|
205
229
|
⎡ ${this.a1},\t ${this.a2},\t ${this.a3}\t ⎤
|
206
230
|
⎢ ${this.b1},\t ${this.b2},\t ${this.b3}\t ⎥
|
207
231
|
⎣ ${this.c1},\t ${this.c2},\t ${this.c3}\t ⎦
|
208
|
-
`.
|
232
|
+
`.trimEnd().trimStart();
|
209
233
|
}
|
210
234
|
|
211
|
-
|
212
|
-
|
213
|
-
|
235
|
+
/**
|
236
|
+
* ```
|
237
|
+
* result[0] = top left element
|
238
|
+
* result[1] = element at row zero, column 1
|
239
|
+
* ...
|
240
|
+
* ```
|
241
|
+
*/
|
214
242
|
public toArray(): number[] {
|
215
243
|
return [
|
216
244
|
this.a1, this.a2, this.a3,
|
@@ -219,7 +247,7 @@ export default class Mat33 {
|
|
219
247
|
];
|
220
248
|
}
|
221
249
|
|
222
|
-
|
250
|
+
/** Constructs a 3x3 translation matrix (for translating `Vec2`s) */
|
223
251
|
public static translation(amount: Vec2): Mat33 {
|
224
252
|
// When transforming Vec2s by a 3x3 matrix, we give the input
|
225
253
|
// Vec2s z = 1. As such,
|
@@ -269,7 +297,7 @@ export default class Mat33 {
|
|
269
297
|
return result.rightMul(Mat33.translation(center.times(-1)));
|
270
298
|
}
|
271
299
|
|
272
|
-
|
300
|
+
/** Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33. */
|
273
301
|
public static fromCSSMatrix(cssString: string): Mat33 {
|
274
302
|
if (cssString === '' || cssString === 'none') {
|
275
303
|
return Mat33.identity;
|
File without changes
|
File without changes
|
@@ -15,7 +15,7 @@ describe('Path.toString', () => {
|
|
15
15
|
point: Vec2.of(0.3, 0.4),
|
16
16
|
},
|
17
17
|
]);
|
18
|
-
expect(path.toString()).toBe('
|
18
|
+
expect(path.toString()).toBe('M.1,.2L.3,.4');
|
19
19
|
});
|
20
20
|
|
21
21
|
it('should fix rounding errors', () => {
|
@@ -30,7 +30,8 @@ describe('Path.toString', () => {
|
|
30
30
|
point: Vec2.of(184.00482359999998, 1)
|
31
31
|
}
|
32
32
|
]);
|
33
|
-
|
33
|
+
|
34
|
+
expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
|
34
35
|
});
|
35
36
|
|
36
37
|
it('should not remove trailing zeroes before decimal points', () => {
|
@@ -40,6 +41,14 @@ describe('Path.toString', () => {
|
|
40
41
|
point: Vec2.of(30.0001, 40.000000001),
|
41
42
|
},
|
42
43
|
]);
|
44
|
+
|
43
45
|
expect(path.toString()).toBe('M1000,2000000L30.0001,40');
|
44
46
|
});
|
47
|
+
|
48
|
+
it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
|
49
|
+
const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
|
50
|
+
expect(path1.toString()).toBe([
|
51
|
+
'M100,100', 'L101,101', 'Q102,102 90,90', 'L100,100'
|
52
|
+
].join(''));
|
53
|
+
});
|
45
54
|
});
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
|
3
3
|
import RenderingStyle from '../rendering/RenderingStyle';
|
4
|
+
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
4
5
|
import LineSegment2 from './LineSegment2';
|
5
6
|
import Mat33 from './Mat33';
|
6
7
|
import Rect2 from './Rect2';
|
@@ -170,8 +171,8 @@ export default class Path {
|
|
170
171
|
return result;
|
171
172
|
}
|
172
173
|
|
173
|
-
public
|
174
|
-
const startPoint =
|
174
|
+
public mapPoints(mapping: (point: Point2)=>Point2): Path {
|
175
|
+
const startPoint = mapping(this.startPoint);
|
175
176
|
const newParts: PathCommand[] = [];
|
176
177
|
|
177
178
|
let exhaustivenessCheck: never;
|
@@ -181,22 +182,22 @@ export default class Path {
|
|
181
182
|
case PathCommandType.LineTo:
|
182
183
|
newParts.push({
|
183
184
|
kind: part.kind,
|
184
|
-
point:
|
185
|
+
point: mapping(part.point),
|
185
186
|
});
|
186
187
|
break;
|
187
188
|
case PathCommandType.CubicBezierTo:
|
188
189
|
newParts.push({
|
189
190
|
kind: part.kind,
|
190
|
-
controlPoint1:
|
191
|
-
controlPoint2:
|
192
|
-
endPoint:
|
191
|
+
controlPoint1: mapping(part.controlPoint1),
|
192
|
+
controlPoint2: mapping(part.controlPoint2),
|
193
|
+
endPoint: mapping(part.endPoint),
|
193
194
|
});
|
194
195
|
break;
|
195
196
|
case PathCommandType.QuadraticBezierTo:
|
196
197
|
newParts.push({
|
197
198
|
kind: part.kind,
|
198
|
-
controlPoint:
|
199
|
-
endPoint:
|
199
|
+
controlPoint: mapping(part.controlPoint),
|
200
|
+
endPoint: mapping(part.endPoint),
|
200
201
|
});
|
201
202
|
break;
|
202
203
|
default:
|
@@ -208,6 +209,10 @@ export default class Path {
|
|
208
209
|
return new Path(startPoint, newParts);
|
209
210
|
}
|
210
211
|
|
212
|
+
public transformedBy(affineTransfm: Mat33): Path {
|
213
|
+
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
214
|
+
}
|
215
|
+
|
211
216
|
// Creates a new path by joining [other] to the end of this path
|
212
217
|
public union(other: Path|null): Path {
|
213
218
|
if (!other) {
|
@@ -280,71 +285,69 @@ export default class Path {
|
|
280
285
|
}
|
281
286
|
|
282
287
|
public toString(): string {
|
283
|
-
|
288
|
+
// Hueristic: Try to determine whether converting absolute to relative commands is worth it.
|
289
|
+
// If we're near (0, 0), it probably isn't worth it and if bounding boxes are large,
|
290
|
+
// it also probably isn't worth it.
|
291
|
+
const makeRelativeCommands =
|
292
|
+
Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.size.x) < 2
|
293
|
+
&& Math.abs(this.bbox.topLeft.y) > 10 && Math.abs(this.bbox.size.y) < 2;
|
294
|
+
|
295
|
+
return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
|
284
296
|
}
|
285
297
|
|
286
298
|
public serialize(): string {
|
287
299
|
return this.toString();
|
288
300
|
}
|
289
301
|
|
290
|
-
|
302
|
+
// @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
|
303
|
+
// conversions can lead to smaller output strings, but also take time.
|
304
|
+
public static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands: boolean = true): string {
|
291
305
|
const result: string[] = [];
|
292
306
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
const
|
297
|
-
const
|
298
|
-
|
299
|
-
|
300
|
-
if (text.indexOf('.') === -1) {
|
301
|
-
return text;
|
302
|
-
}
|
307
|
+
let prevPoint: Point2|undefined;
|
308
|
+
const addCommand = (command: string, ...points: Point2[]) => {
|
309
|
+
const absoluteCommandParts: string[] = [];
|
310
|
+
const relativeCommandParts: string[] = [];
|
311
|
+
const makeAbsCommand = !prevPoint || onlyAbsCommands;
|
312
|
+
const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
|
313
|
+
const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
|
303
314
|
|
304
|
-
const
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
}
|
315
|
+
for (const point of points) {
|
316
|
+
// Relative commands are often shorter as strings than absolute commands.
|
317
|
+
if (!makeAbsCommand) {
|
318
|
+
const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint!.x, roundedPrevX, roundedPrevY);
|
319
|
+
const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint!.y, roundedPrevX, roundedPrevY);
|
320
|
+
|
321
|
+
// No need for an additional separator if it starts with a '-'
|
322
|
+
if (yComponentRelative.charAt(0) === '-') {
|
323
|
+
relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
|
324
|
+
} else {
|
325
|
+
relativeCommandParts.push(`${xComponentRelative},${yComponentRelative}`);
|
326
|
+
}
|
327
|
+
} else {
|
328
|
+
const xComponent = toRoundedString(point.x);
|
329
|
+
const yComponent = toRoundedString(point.y);
|
320
330
|
|
321
|
-
|
322
|
-
while (newPostDecimal.length < origPostDecimalString.length) {
|
323
|
-
newPostDecimal = carry.toString(10) + newPostDecimal;
|
324
|
-
carry = 0;
|
331
|
+
absoluteCommandParts.push(`${xComponent},${yComponent}`);
|
325
332
|
}
|
326
|
-
|
327
|
-
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
328
333
|
}
|
329
334
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
+
let commandString;
|
336
|
+
if (makeAbsCommand) {
|
337
|
+
commandString = `${command}${absoluteCommandParts.join(' ')}`;
|
338
|
+
} else {
|
339
|
+
commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
|
340
|
+
}
|
335
341
|
|
336
|
-
//
|
337
|
-
|
338
|
-
|
342
|
+
// Don't add no-ops.
|
343
|
+
if (commandString === 'l0,0') {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
result.push(commandString);
|
339
347
|
|
340
|
-
|
341
|
-
|
342
|
-
for (const point of points) {
|
343
|
-
const xComponent = toRoundedString(point.x);
|
344
|
-
const yComponent = toRoundedString(point.y);
|
345
|
-
parts.push(`${xComponent},${yComponent}`);
|
348
|
+
if (points.length > 0) {
|
349
|
+
prevPoint = points[points.length - 1];
|
346
350
|
}
|
347
|
-
result.push(`${command}${parts.join(' ')}`);
|
348
351
|
};
|
349
352
|
|
350
353
|
addCommand('M', startPoint);
|
@@ -374,7 +377,7 @@ export default class Path {
|
|
374
377
|
|
375
378
|
// Create a Path from a SVG path specification.
|
376
379
|
// TODO: Support a larger subset of SVG paths.
|
377
|
-
// TODO: Support s
|
380
|
+
// TODO: Support `s`,`t` commands shorthands.
|
378
381
|
public static fromString(pathString: string): Path {
|
379
382
|
// See the MDN reference:
|
380
383
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
File without changes
|
@@ -2,8 +2,8 @@ import LineSegment2 from './LineSegment2';
|
|
2
2
|
import Mat33 from './Mat33';
|
3
3
|
import { Point2, Vec2 } from './Vec2';
|
4
4
|
|
5
|
-
|
6
|
-
interface RectTemplate {
|
5
|
+
/** An object that can be converted to a Rect2. */
|
6
|
+
export interface RectTemplate {
|
7
7
|
x: number;
|
8
8
|
y: number;
|
9
9
|
w?: number;
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,6 +1,10 @@
|
|
1
1
|
|
2
2
|
|
3
|
-
|
3
|
+
/**
|
4
|
+
* A vector with three components. Can also be used to represent a two-component vector.
|
5
|
+
*
|
6
|
+
* A `Vec3` is immutable.
|
7
|
+
*/
|
4
8
|
export default class Vec3 {
|
5
9
|
private constructor(
|
6
10
|
public readonly x: number,
|
@@ -9,7 +13,7 @@ export default class Vec3 {
|
|
9
13
|
) {
|
10
14
|
}
|
11
15
|
|
12
|
-
|
16
|
+
/** Returns the x, y components of this. */
|
13
17
|
public get xy(): { x: number; y: number } {
|
14
18
|
// Useful for APIs that behave differently if .z is present.
|
15
19
|
return {
|
@@ -22,7 +26,7 @@ export default class Vec3 {
|
|
22
26
|
return new Vec3(x, y, z);
|
23
27
|
}
|
24
28
|
|
25
|
-
|
29
|
+
/** Returns this' `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
|
26
30
|
public at(idx: number): number {
|
27
31
|
if (idx === 0) return this.x;
|
28
32
|
if (idx === 1) return this.y;
|
@@ -31,7 +35,7 @@ export default class Vec3 {
|
|
31
35
|
throw new Error(`${idx} out of bounds!`);
|
32
36
|
}
|
33
37
|
|
34
|
-
|
38
|
+
/** Alias for this.magnitude. */
|
35
39
|
public length(): number {
|
36
40
|
return this.magnitude();
|
37
41
|
}
|
@@ -44,16 +48,26 @@ export default class Vec3 {
|
|
44
48
|
return this.dot(this);
|
45
49
|
}
|
46
50
|
|
47
|
-
|
51
|
+
/**
|
52
|
+
* Return this' angle in the XY plane (treats this as a Vec2).
|
53
|
+
*
|
54
|
+
* This is equivalent to `Math.atan2(vec.y, vec.x)`.
|
55
|
+
*/
|
48
56
|
public angle(): number {
|
49
57
|
return Math.atan2(this.y, this.x);
|
50
58
|
}
|
51
59
|
|
60
|
+
/**
|
61
|
+
* Returns a unit vector in the same direction as this.
|
62
|
+
*
|
63
|
+
* If `this` has zero length, the resultant vector has `NaN` components.
|
64
|
+
*/
|
52
65
|
public normalized(): Vec3 {
|
53
66
|
const norm = this.magnitude();
|
54
67
|
return Vec3.of(this.x / norm, this.y / norm, this.z / norm);
|
55
68
|
}
|
56
69
|
|
70
|
+
/** @returns A copy of `this` multiplied by a scalar. */
|
57
71
|
public times(c: number): Vec3 {
|
58
72
|
return Vec3.of(this.x * c, this.y * c, this.z * c);
|
59
73
|
}
|
@@ -81,8 +95,10 @@ export default class Vec3 {
|
|
81
95
|
);
|
82
96
|
}
|
83
97
|
|
84
|
-
|
85
|
-
|
98
|
+
/**
|
99
|
+
* Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
|
100
|
+
* 90 degrees counter-clockwise.
|
101
|
+
*/
|
86
102
|
public orthog(): Vec3 {
|
87
103
|
// If parallel to the z-axis
|
88
104
|
if (this.dot(Vec3.unitX) === 0 && this.dot(Vec3.unitY) === 0) {
|
@@ -92,18 +108,32 @@ export default class Vec3 {
|
|
92
108
|
return this.cross(Vec3.unitZ.times(-1)).normalized();
|
93
109
|
}
|
94
110
|
|
95
|
-
|
111
|
+
/** Returns this plus a vector of length `distance` in `direction`. */
|
96
112
|
public extend(distance: number, direction: Vec3): Vec3 {
|
97
113
|
return this.plus(direction.normalized().times(distance));
|
98
114
|
}
|
99
115
|
|
100
|
-
|
116
|
+
/** Returns a vector `fractionTo` of the way to target from this. */
|
101
117
|
public lerp(target: Vec3, fractionTo: number): Vec3 {
|
102
118
|
return this.times(1 - fractionTo).plus(target.times(fractionTo));
|
103
119
|
}
|
104
120
|
|
105
|
-
|
106
|
-
|
121
|
+
/**
|
122
|
+
* `zip` Maps a component of this and a corresponding component of
|
123
|
+
* `other` to a component of the output vector.
|
124
|
+
*
|
125
|
+
* @example
|
126
|
+
* ```
|
127
|
+
* const a = Vec3.of(1, 2, 3);
|
128
|
+
* const b = Vec3.of(0.5, 2.1, 2.9);
|
129
|
+
*
|
130
|
+
* const zipped = a.zip(b, (aComponent, bComponent) => {
|
131
|
+
* return Math.min(aComponent, bComponent);
|
132
|
+
* });
|
133
|
+
*
|
134
|
+
* console.log(zipped.toString()); // → Vec(0.5, 2, 2.9)
|
135
|
+
* ```
|
136
|
+
*/
|
107
137
|
public zip(
|
108
138
|
other: Vec3, zip: (componentInThis: number, componentInOther: number)=> number
|
109
139
|
): Vec3 {
|
@@ -114,10 +144,17 @@ export default class Vec3 {
|
|
114
144
|
);
|
115
145
|
}
|
116
146
|
|
117
|
-
|
118
|
-
|
147
|
+
/**
|
148
|
+
* Returns a vector with each component acted on by `fn`.
|
149
|
+
*
|
150
|
+
* @example
|
151
|
+
* ```
|
152
|
+
* console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
|
153
|
+
* ```
|
154
|
+
*/
|
155
|
+
public map(fn: (component: number, index: number)=> number): Vec3 {
|
119
156
|
return Vec3.of(
|
120
|
-
fn(this.x), fn(this.y), fn(this.z)
|
157
|
+
fn(this.x, 0), fn(this.y, 1), fn(this.z, 2)
|
121
158
|
);
|
122
159
|
}
|
123
160
|
|
@@ -125,8 +162,19 @@ export default class Vec3 {
|
|
125
162
|
return [this.x, this.y, this.z];
|
126
163
|
}
|
127
164
|
|
128
|
-
|
129
|
-
|
165
|
+
/**
|
166
|
+
* [fuzz] The maximum difference between two components for this and [other]
|
167
|
+
* to be considered equal.
|
168
|
+
*
|
169
|
+
* @example
|
170
|
+
* ```
|
171
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 100); // → true
|
172
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 0.1); // → false
|
173
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3); // → true
|
174
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3.01); // → true
|
175
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
176
|
+
* ```
|
177
|
+
*/
|
130
178
|
public eq(other: Vec3, fuzz: number): boolean {
|
131
179
|
for (let i = 0; i < 3; i++) {
|
132
180
|
if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
|
package/src/math/lib.ts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
import LineSegment2 from './LineSegment2';
|
2
|
+
import Mat33 from './Mat33';
|
3
|
+
import Path from './Path';
|
4
|
+
import Rect2 from './Rect2';
|
5
|
+
import { Vec2 } from './Vec2';
|
6
|
+
import Vec3 from './Vec3';
|
7
|
+
|
8
|
+
export {
|
9
|
+
LineSegment2,
|
10
|
+
Mat33,
|
11
|
+
Path,
|
12
|
+
Rect2,
|
13
|
+
Vec3,
|
14
|
+
Vec2,
|
15
|
+
};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
2
|
+
|
3
|
+
describe('toRoundedString', () => {
|
4
|
+
it('should round up numbers endings similar to .999999999999999', () => {
|
5
|
+
expect(toRoundedString(0.999999999)).toBe('1');
|
6
|
+
expect(toRoundedString(0.899999999)).toBe('.9');
|
7
|
+
expect(toRoundedString(9.999999999)).toBe('10');
|
8
|
+
expect(toRoundedString(-10.999999999)).toBe('-11');
|
9
|
+
});
|
10
|
+
|
11
|
+
it('should round up numbers similar to 10.999999998', () => {
|
12
|
+
expect(toRoundedString(10.999999998)).toBe('11');
|
13
|
+
});
|
14
|
+
|
15
|
+
// Handling this creates situations with potential error:
|
16
|
+
//it('should round strings with multiple digits after the ending decimal points', () => {
|
17
|
+
// expect(toRoundedString(292.2 - 292.8)).toBe('-0.6');
|
18
|
+
//});
|
19
|
+
|
20
|
+
it('should round down strings ending endings similar to .00000001', () => {
|
21
|
+
expect(toRoundedString(10.00000001)).toBe('10');
|
22
|
+
});
|
23
|
+
});
|
24
|
+
|
25
|
+
it('toStringOfSamePrecision', () => {
|
26
|
+
expect(toStringOfSamePrecision(1.23456, '1.12')).toBe('1.23');
|
27
|
+
expect(toStringOfSamePrecision(1.23456, '1.1')).toBe('1.2');
|
28
|
+
expect(toStringOfSamePrecision(1.23456, '1.1', '5.32')).toBe('1.23');
|
29
|
+
expect(toStringOfSamePrecision(-1.23456, '1.1', '5.32')).toBe('-1.23');
|
30
|
+
expect(toStringOfSamePrecision(-1.99999, '1.1', '5.32')).toBe('-2');
|
31
|
+
expect(toStringOfSamePrecision(1.99999, '1.1', '5.32')).toBe('2');
|
32
|
+
expect(toStringOfSamePrecision(1.89999, '1.1', '5.32')).toBe('1.9');
|
33
|
+
expect(toStringOfSamePrecision(9.99999999, '-1.1234')).toBe('10');
|
34
|
+
expect(toStringOfSamePrecision(9.999999998999996, '100')).toBe('10');
|
35
|
+
expect(toStringOfSamePrecision(0.000012345, '0.000012')).toBe('.000012');
|
36
|
+
expect(toStringOfSamePrecision(0.000012645, '.000012')).toBe('.000013');
|
37
|
+
expect(toStringOfSamePrecision(-0.09999999999999432, '291.3')).toBe('-.1');
|
38
|
+
expect(toStringOfSamePrecision(-0.9999999999999432, '291.3')).toBe('-1');
|
39
|
+
expect(toStringOfSamePrecision(9998.9, '.1', '-11')).toBe('9998.9');
|
40
|
+
});
|