@js-draw/math 1.23.1 → 1.24.2

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/dist/cjs/Mat33.js CHANGED
@@ -430,6 +430,8 @@ class Mat33 {
430
430
  if (cssString === '' || cssString === 'none') {
431
431
  return Mat33.identity;
432
432
  }
433
+ // Normalize spacing
434
+ cssString = cssString.trim().replace(/\s+/g, ' ');
433
435
  const parseArguments = (argumentString) => {
434
436
  const parsed = argumentString.split(/[, \t\n]+/g).map((argString) => {
435
437
  // Handle trailing spaces/commands
@@ -505,8 +507,8 @@ class Mat33 {
505
507
  },
506
508
  };
507
509
  // A command (\w+)
508
- // followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
509
- const partRegex = /\s*(\w+)\s*\(([^)]*)\)/gi;
510
+ // followed by a set of arguments ([^)]*)
511
+ const partRegex = /(?:^|\W)(\w+)\s?\(([^)]*)\)/gi;
510
512
  let match;
511
513
  let matrix = null;
512
514
  while ((match = partRegex.exec(cssString)) !== null) {
@@ -68,8 +68,8 @@ export interface Vec3 {
68
68
  *
69
69
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
70
70
  *
71
- * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
72
- * the resultant angle is in the range $[-\pi, pi]$.
71
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)` $\approx \pi$
72
+ * the resultant angle is in the range $[-\pi, \pi]$.
73
73
  *
74
74
  * **Example**:
75
75
  * ```ts,runnable,console
@@ -14,7 +14,7 @@ const cleanUpNumber = (text) => {
14
14
  const lastChar = text.charAt(text.length - 1);
15
15
  if (lastChar === '0' || lastChar === '.') {
16
16
  // Remove trailing zeroes
17
- text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
17
+ text = text.replace(/([.]\d*[^0])0+$/, '$1');
18
18
  text = text.replace(/[.]0+$/, '.');
19
19
  // Remove trailing period
20
20
  text = text.replace(/[.]$/, '');
@@ -3,6 +3,12 @@ import { Point2, Vec2 } from '../Vec2';
3
3
  import LineSegment2 from './LineSegment2';
4
4
  import Rect2 from './Rect2';
5
5
  import Parameterized2DShape from './Parameterized2DShape';
6
+ interface CorrectedBezierType extends Bezier {
7
+ dderivative(t: number): {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ }
6
12
  /**
7
13
  * A lazy-initializing wrapper around Bezier-js.
8
14
  *
@@ -17,7 +23,7 @@ export declare abstract class BezierJSWrapper extends Parameterized2DShape {
17
23
  protected constructor(bezierJsBezier?: Bezier);
18
24
  /** Returns the start, control points, and end point of this Bézier. */
19
25
  abstract getPoints(): readonly Point2[];
20
- protected getBezier(): Bezier;
26
+ protected getBezier(): CorrectedBezierType;
21
27
  signedDistance(point: Point2): number;
22
28
  /**
23
29
  * @returns the (more) exact distance from `point` to this.
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Rect2 = void 0;
7
7
  const LineSegment2_1 = __importDefault(require("./LineSegment2"));
8
+ const Mat33_1 = __importDefault(require("../Mat33"));
8
9
  const Vec2_1 = require("../Vec2");
9
10
  const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
10
11
  /**
@@ -244,6 +245,10 @@ class Rect2 extends Abstract2DShape_1.default {
244
245
  // [affineTransform] is a transformation matrix that both scales and **translates**.
245
246
  // the bounding box of this' four corners after transformed by the given affine transformation.
246
247
  transformedBoundingBox(affineTransform) {
248
+ // Optimize transforming by the identity matrix (a common case).
249
+ if (affineTransform === Mat33_1.default.identity) {
250
+ return this;
251
+ }
247
252
  return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
248
253
  }
249
254
  /** @return true iff this is equal to `other ± tolerance` */
@@ -424,6 +424,8 @@ export class Mat33 {
424
424
  if (cssString === '' || cssString === 'none') {
425
425
  return Mat33.identity;
426
426
  }
427
+ // Normalize spacing
428
+ cssString = cssString.trim().replace(/\s+/g, ' ');
427
429
  const parseArguments = (argumentString) => {
428
430
  const parsed = argumentString.split(/[, \t\n]+/g).map((argString) => {
429
431
  // Handle trailing spaces/commands
@@ -499,8 +501,8 @@ export class Mat33 {
499
501
  },
500
502
  };
501
503
  // A command (\w+)
502
- // followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
503
- const partRegex = /\s*(\w+)\s*\(([^)]*)\)/gi;
504
+ // followed by a set of arguments ([^)]*)
505
+ const partRegex = /(?:^|\W)(\w+)\s?\(([^)]*)\)/gi;
504
506
  let match;
505
507
  let matrix = null;
506
508
  while ((match = partRegex.exec(cssString)) !== null) {
@@ -68,8 +68,8 @@ export interface Vec3 {
68
68
  *
69
69
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
70
70
  *
71
- * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
72
- * the resultant angle is in the range $[-\pi, pi]$.
71
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)` $\approx \pi$
72
+ * the resultant angle is in the range $[-\pi, \pi]$.
73
73
  *
74
74
  * **Example**:
75
75
  * ```ts,runnable,console
@@ -11,7 +11,7 @@ export const cleanUpNumber = (text) => {
11
11
  const lastChar = text.charAt(text.length - 1);
12
12
  if (lastChar === '0' || lastChar === '.') {
13
13
  // Remove trailing zeroes
14
- text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
14
+ text = text.replace(/([.]\d*[^0])0+$/, '$1');
15
15
  text = text.replace(/[.]0+$/, '.');
16
16
  // Remove trailing period
17
17
  text = text.replace(/[.]$/, '');
@@ -3,6 +3,12 @@ import { Point2, Vec2 } from '../Vec2';
3
3
  import LineSegment2 from './LineSegment2';
4
4
  import Rect2 from './Rect2';
5
5
  import Parameterized2DShape from './Parameterized2DShape';
6
+ interface CorrectedBezierType extends Bezier {
7
+ dderivative(t: number): {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ }
6
12
  /**
7
13
  * A lazy-initializing wrapper around Bezier-js.
8
14
  *
@@ -17,7 +23,7 @@ export declare abstract class BezierJSWrapper extends Parameterized2DShape {
17
23
  protected constructor(bezierJsBezier?: Bezier);
18
24
  /** Returns the start, control points, and end point of this Bézier. */
19
25
  abstract getPoints(): readonly Point2[];
20
- protected getBezier(): Bezier;
26
+ protected getBezier(): CorrectedBezierType;
21
27
  signedDistance(point: Point2): number;
22
28
  /**
23
29
  * @returns the (more) exact distance from `point` to this.
@@ -1,4 +1,5 @@
1
1
  import LineSegment2 from './LineSegment2.mjs';
2
+ import Mat33 from '../Mat33.mjs';
2
3
  import { Vec2 } from '../Vec2.mjs';
3
4
  import Abstract2DShape from './Abstract2DShape.mjs';
4
5
  /**
@@ -238,6 +239,10 @@ export class Rect2 extends Abstract2DShape {
238
239
  // [affineTransform] is a transformation matrix that both scales and **translates**.
239
240
  // the bounding box of this' four corners after transformed by the given affine transformation.
240
241
  transformedBoundingBox(affineTransform) {
242
+ // Optimize transforming by the identity matrix (a common case).
243
+ if (affineTransform === Mat33.identity) {
244
+ return this;
245
+ }
241
246
  return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
242
247
  }
243
248
  /** @return true iff this is equal to `other ± tolerance` */
@@ -1,5 +1,6 @@
1
1
  console.log('Testing require()...');
2
2
 
3
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- This is a .cjs file
3
4
  const { Vec2, Color4, Mat33 } = require('@js-draw/math');
4
5
 
5
6
  if (Vec2.of(1, 1).x !== 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-draw/math",
3
- "version": "1.23.1",
3
+ "version": "1.24.2",
4
4
  "description": "A math library for js-draw. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -27,7 +27,7 @@
27
27
  "bezier-js": "6.1.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@js-draw/build-tool": "^1.23.1",
30
+ "@js-draw/build-tool": "^1.24.2",
31
31
  "@types/bezier-js": "4.1.0",
32
32
  "@types/jest": "29.5.5",
33
33
  "@types/jsdom": "21.1.3"
@@ -44,5 +44,5 @@
44
44
  "svg",
45
45
  "math"
46
46
  ],
47
- "gitHead": "e0bb3336d5f3a94533c823906778d39a4880f4cf"
47
+ "gitHead": "32c8db56fc8996c8d485118d1ee37077428344a3"
48
48
  }
@@ -30,6 +30,9 @@ describe('Mat33.fromCSSMatrix', () => {
30
30
  expect(Mat33.fromCSSMatrix('matrix(-1, 2e6, 3E-2,-5.123, -6.5e-1, 0.01)')).objEq(
31
31
  new Mat33(-1, 3e-2, -6.5e-1, 2e6, -5.123, 0.01, 0, 0, 1),
32
32
  );
33
+ expect(Mat33.fromCSSMatrix('matrix(-1,\t2e6,3E-2,-5.123, -6.5e-1,\n0.01\n)')).objEq(
34
+ new Mat33(-1, 3e-2, -6.5e-1, 2e6, -5.123, 0.01, 0, 0, 1),
35
+ );
33
36
  });
34
37
 
35
38
  it('should convert multi-matrix arguments into a single CSS matrix', () => {
package/src/Mat33.ts CHANGED
@@ -516,6 +516,8 @@ export class Mat33 {
516
516
  if (cssString === '' || cssString === 'none') {
517
517
  return Mat33.identity;
518
518
  }
519
+ // Normalize spacing
520
+ cssString = cssString.trim().replace(/\s+/g, ' ');
519
521
 
520
522
  const parseArguments = (argumentString: string): number[] => {
521
523
  const parsed = argumentString.split(/[, \t\n]+/g).map((argString) => {
@@ -608,8 +610,8 @@ export class Mat33 {
608
610
  };
609
611
 
610
612
  // A command (\w+)
611
- // followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
612
- const partRegex = /\s*(\w+)\s*\(([^)]*)\)/gi;
613
+ // followed by a set of arguments ([^)]*)
614
+ const partRegex = /(?:^|\W)(\w+)\s?\(([^)]*)\)/gi;
613
615
  let match;
614
616
  let matrix: Mat33 | null = null;
615
617
 
package/src/Vec3.ts CHANGED
@@ -72,8 +72,8 @@ export interface Vec3 {
72
72
  *
73
73
  * This is equivalent to `Math.atan2(vec.y, vec.x)`.
74
74
  *
75
- * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
76
- * the resultant angle is in the range $[-\pi, pi]$.
75
+ * As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)` $\approx \pi$
76
+ * the resultant angle is in the range $[-\pi, \pi]$.
77
77
  *
78
78
  * **Example**:
79
79
  * ```ts,runnable,console
@@ -11,5 +11,7 @@ it('cleanUpNumber', () => {
11
11
  expect(cleanUpNumber('1234')).toBe('1234');
12
12
  expect(cleanUpNumber('1234.5')).toBe('1234.5');
13
13
  expect(cleanUpNumber('1234.500')).toBe('1234.5');
14
+ expect(cleanUpNumber('1234.00500')).toBe('1234.005');
15
+ expect(cleanUpNumber('1234.001234500')).toBe('1234.0012345');
14
16
  expect(cleanUpNumber('1.1368683772161603e-13')).toBe('0');
15
17
  });
@@ -13,7 +13,7 @@ export const cleanUpNumber = (text: string) => {
13
13
  const lastChar = text.charAt(text.length - 1);
14
14
  if (lastChar === '0' || lastChar === '.') {
15
15
  // Remove trailing zeroes
16
- text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
16
+ text = text.replace(/([.]\d*[^0])0+$/, '$1');
17
17
  text = text.replace(/[.]0+$/, '.');
18
18
 
19
19
  // Remove trailing period
@@ -4,6 +4,11 @@ import LineSegment2 from './LineSegment2';
4
4
  import Rect2 from './Rect2';
5
5
  import Parameterized2DShape from './Parameterized2DShape';
6
6
 
7
+ // The typings for Bezier are incorrect in some cases:
8
+ interface CorrectedBezierType extends Bezier {
9
+ dderivative(t: number): { x: number; y: number };
10
+ }
11
+
7
12
  /**
8
13
  * A lazy-initializing wrapper around Bezier-js.
9
14
  *
@@ -14,13 +19,13 @@ import Parameterized2DShape from './Parameterized2DShape';
14
19
  * @internal
15
20
  */
16
21
  export abstract class BezierJSWrapper extends Parameterized2DShape {
17
- #bezierJs: Bezier | null = null;
22
+ #bezierJs: CorrectedBezierType | null = null;
18
23
 
19
24
  protected constructor(bezierJsBezier?: Bezier) {
20
25
  super();
21
26
 
22
27
  if (bezierJsBezier) {
23
- this.#bezierJs = bezierJsBezier;
28
+ this.#bezierJs = bezierJsBezier as CorrectedBezierType;
24
29
  }
25
30
  }
26
31
 
@@ -29,7 +34,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
29
34
 
30
35
  protected getBezier() {
31
36
  if (!this.#bezierJs) {
32
- this.#bezierJs = new Bezier(this.getPoints().map((p) => p.xy));
37
+ this.#bezierJs = new Bezier(this.getPoints().map((p) => p.xy)) as CorrectedBezierType;
33
38
  }
34
39
  return this.#bezierJs;
35
40
  }
@@ -62,7 +67,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
62
67
  }
63
68
 
64
69
  public secondDerivativeAt(t: number): Point2 {
65
- return Vec2.ofXY((this.getBezier() as any).dderivative(t));
70
+ return Vec2.ofXY(this.getBezier().dderivative(t));
66
71
  }
67
72
 
68
73
  /** @returns the [normal vector](https://en.wikipedia.org/wiki/Normal_(geometry)) to this curve at `t`. */
@@ -114,6 +114,10 @@ describe('Rect2', () => {
114
114
  });
115
115
 
116
116
  it('A transformed bounding box', () => {
117
+ expect(Rect2.unitSquare.transformedBoundingBox(Mat33.scaling2D(2))).objEq(
118
+ new Rect2(0, 0, 2, 2),
119
+ );
120
+
117
121
  const rotationMat = Mat33.zRotation(Math.PI / 4);
118
122
  const rect = Rect2.unitSquare.translatedBy(Vec2.of(-0.5, -0.5));
119
123
  const transformedBBox = rect.transformedBoundingBox(rotationMat);
@@ -310,6 +310,11 @@ export class Rect2 extends Abstract2DShape {
310
310
  // [affineTransform] is a transformation matrix that both scales and **translates**.
311
311
  // the bounding box of this' four corners after transformed by the given affine transformation.
312
312
  public transformedBoundingBox(affineTransform: Mat33): Rect2 {
313
+ // Optimize transforming by the identity matrix (a common case).
314
+ if (affineTransform === Mat33.identity) {
315
+ return this;
316
+ }
317
+
313
318
  return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
314
319
  }
315
320