Skip to content

Commit

Permalink
Refactors fromString implementation (#107)
Browse files Browse the repository at this point in the history
* prepare unit test for new implementation

* adds new fromString implementation

* fromString: handles Infinity

* lint cleanup

* improves doc

* improves documentation
  • Loading branch information
chrvadala authored Feb 10, 2025
1 parent a716492 commit 1179dd7
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 15 deletions.
24 changes: 24 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Any value could be a float or a string that contains a float</p>
<dt><a href="#fromString">fromString(string)</a> ⇒ <code>Matrix</code></dt>
<dd><p>Parse a string formatted as matrix(a,b,c,d,e,f)</p>
</dd>
<dt><del><a href="#fromStringLegacy">fromStringLegacy(string)</a> ⇒ <code>Matrix</code></del></dt>
<dd><p>Parse a string formatted as matrix(a,b,c,d,e,f) - Legacy implementation of <code>fromString(matrix)</code>;
Read this PR for details <a href="https://github.com/chrvadala/transformation-matrix/pull/107">https://github.com/chrvadala/transformation-matrix/pull/107</a></p>
</dd>
<dt><a href="#fromTransformAttribute">fromTransformAttribute(transformString)</a> ⇒ <code>Array.&lt;MatrixDescriptor&gt;</code></dt>
<dd><p>Parser for SVG Trasform Attribute <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">http://www.w3.org/TR/SVG/coords.html#TransformAttribute</a></p>
</dd>
Expand Down Expand Up @@ -287,6 +291,26 @@ Parse a string formatted as matrix(a,b,c,d,e,f)
> fromString('matrix(1,2,3,4,5,6)')
{a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
```
<a name="fromStringLegacy"></a>

## ~~fromStringLegacy(string) ⇒ <code>Matrix</code>~~
***Deprecated***

Parse a string formatted as matrix(a,b,c,d,e,f) - Legacy implementation of `fromString(matrix)`;
Read this PR for details [https://github.com/chrvadala/transformation-matrix/pull/107](https://github.com/chrvadala/transformation-matrix/pull/107)

**Kind**: global function
**Returns**: <code>Matrix</code> - Affine Matrix

| Param | Type | Description |
| --- | --- | --- |
| string | <code>string</code> | String with an affine matrix |

**Example**
```js
> fromStringLegacy('matrix(1,2,3,4,5,6)')
{a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
```
<a name="fromTransformAttribute"></a>

## fromTransformAttribute(transformString) ⇒ <code>Array.&lt;MatrixDescriptor&gt;</code>
Expand Down
41 changes: 41 additions & 0 deletions src/fromString.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ const matrixRegex = /^matrix\(\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
export function fromString (string) {
const parseFloatOrThrow = number => {
const n = parseFloat(number)
if (Number.isFinite(n)) return n // excludes NaN, +Infinite, -Infinite
throw new Error(`'${string}' is not a matrix`)
}

const prefix = string.substring(0, 7).toLowerCase()
const suffix = string.substring(string.length - 1)
const body = string.substring(7, string.length - 1)
const elements = body.split(',')

if (
prefix === 'matrix(' &&
suffix === ')' &&
elements.length === 6
) {
return {
a: parseFloatOrThrow(elements[0]),
b: parseFloatOrThrow(elements[1]),
c: parseFloatOrThrow(elements[2]),
d: parseFloatOrThrow(elements[3]),
e: parseFloatOrThrow(elements[4]),
f: parseFloatOrThrow(elements[5])
}
}

throw new Error(`'${string}' is not a matrix`)
}

/**
* Parse a string formatted as matrix(a,b,c,d,e,f) - Legacy implementation of `fromString(matrix)`;
* Read this PR for details {@link https://github.com/chrvadala/transformation-matrix/pull/107}
* @param string {string} String with an affine matrix
* @deprecated
* @returns {Matrix} Affine Matrix
*
* @example
* > fromStringLegacy('matrix(1,2,3,4,5,6)')
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
export function fromStringLegacy (string) {
const parsed = string.match(matrixRegex)
if (parsed === null || parsed.length < 7) throw new Error(`'${string}' is not a matrix`)
return {
Expand Down
60 changes: 45 additions & 15 deletions test/fromString.spec.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
/* global describe, it, expect */
import { fromString } from '../src/fromString'
import { fromString, fromStringLegacy } from '../src/fromString'

describe('fromString', () => {
describe.each([
['fromString', fromString],
['fromStringLegacy', fromStringLegacy]
])('fromString (implementation: %s)', (fnName, fn) => {
it('should parse a matrix from string', () => {
expect(fromString('matrix(1,2,3,4,5,6)')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fromString('matrix(1 , 2 , 3 , 4 , 5 , 6 )')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fromString('MaTrIx(1,2,3,4,5,6)')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fromString('matrix(1.1,2.2,3.3,4.4,5.5,6.6)')).toEqual({ a: 1.1, b: 2.2, c: 3.3, d: 4.4, e: 5.5, f: 6.6 })
expect(fromString('matrix(1.1 ,2.2 ,3.3 , 4.4, 5.5, 6.6 )')).toEqual({ a: 1.1, b: 2.2, c: 3.3, d: 4.4, e: 5.5, f: 6.6 })
expect(fromString('matrix(1,2.2 ,3.3,4.4,5, 6 )')).toEqual({ a: 1, b: 2.2, c: 3.3, d: 4.4, e: 5, f: 6 })
expect(fn('matrix(1,2,3,4,5,6)')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fn('matrix(1 , 2 , 3 , 4 , 5 , 6 )')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fn('MaTrIx(1,2,3,4,5,6)')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
expect(fn('matrix(1.1,2.2,3.3,4.4,5.5,6.6)')).toEqual({ a: 1.1, b: 2.2, c: 3.3, d: 4.4, e: 5.5, f: 6.6 })
expect(fn('matrix(1.1 ,2.2 ,3.3 , 4.4, 5.5, 6.6 )')).toEqual({ a: 1.1, b: 2.2, c: 3.3, d: 4.4, e: 5.5, f: 6.6 })
expect(fn('matrix(1,2.2 ,3.3,4.4,5, 6 )')).toEqual({ a: 1, b: 2.2, c: 3.3, d: 4.4, e: 5, f: 6 })

expect(fromString('matrix(-1.1,-2.2,-3.3,-4.4,-5.5,-6.6)')).toEqual({ a: -1.1, b: -2.2, c: -3.3, d: -4.4, e: -5.5, f: -6.6 })
expect(fromString('matrix(-1,-2,-3,-4,-5,-6)')).toEqual({ a: -1, b: -2, c: -3, d: -4, e: -5, f: -6 })
expect(fn('matrix(-1.1,-2.2,-3.3,-4.4,-5.5,-6.6)')).toEqual({ a: -1.1, b: -2.2, c: -3.3, d: -4.4, e: -5.5, f: -6.6 })
expect(fn('matrix(-1,-2,-3,-4,-5,-6)')).toEqual({ a: -1, b: -2, c: -3, d: -4, e: -5, f: -6 })

expect(fromString('matrix(+43e+21, -43e+21, +43e-21, -43e-21, 43e0, 0e0)')).toEqual({ a: +43e+21, b: -43e+21, c: +43e-21, d: -43e-21, e: 43, f: 0 })
expect(fn('matrix(+43e+21, -43e+21, +43e-21, -43e-21, 43e0, 0e0)')).toEqual({ a: +43e+21, b: -43e+21, c: +43e-21, d: -43e-21, e: 43, f: 0 })

expect(fromString.bind(this, 'matrix()')).toThrow()
expect(fromString.bind(this, 'matrix(1,2,3,4,5)')).toThrow()
expect(fromString.bind(this, 'matrix(a,b,c,d,e,f)')).toThrow()
expect(fn.bind(this, 'matrix()')).toThrow(new Error("'matrix()' is not a matrix"))
expect(fn.bind(this, 'matrix(1,2,3,4,5)')).toThrow(new Error("'matrix(1,2,3,4,5)' is not a matrix"))
expect(fn.bind(this, 'matrix(a,b,c,d,e,f)')).toThrow(new Error("'matrix(a,b,c,d,e,f)' is not a matrix"))

expect(fromString('matrix(6.123233995736766e-17,1,-1,6.123233995736766e-17,440,-350)'))
expect(fn.bind(this, 'matrix(Infinity,Infinity,Infinity,Infinity,Infinity,Infinity)'))
.toThrow(new Error("'matrix(Infinity,Infinity,Infinity,Infinity,Infinity,Infinity)' is not a matrix"))

expect(fn.bind(this, 'matrix(-Infinity,-Infinity,-Infinity,-Infinity,-Infinity,-Infinity)'))
.toThrow(new Error("'matrix(-Infinity,-Infinity,-Infinity,-Infinity,-Infinity,-Infinity)' is not a matrix"))

expect(fn('matrix(6.123233995736766e-17,1,-1,6.123233995736766e-17,440,-350)'))
.toEqual({ a: 6.123233995736766e-17, b: 1, c: -1, d: 6.123233995736766e-17, e: 440, f: -350 })

if (fnName === 'fromString') {
// current version throws an exception in a case that number is NaN
expect(fn.bind(this, 'matrix(ee,ee,ee,ee,ee,ee)')).toThrow(new Error("'matrix(ee,ee,ee,ee,ee,ee)' is not a matrix"))
// current version ignores measures, supporting the ability to parse values like this 10px
expect(fn('matrix(1px,2px,3px,4px,5px,6px)')).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 })
}

if (fnName === 'fromStringLegacy') {
expect(fn('matrix(ee,ee,ee,ee,ee,ee)'))
.toEqual({
a: Number.NaN,
b: Number.NaN,
c: Number.NaN,
d: Number.NaN,
e: Number.NaN,
f: Number.NaN
})

expect(fn.bind(this, 'matrix(1px,2px,3px,4px,5px,6px)')).toThrow(new Error("'matrix(1px,2px,3px,4px,5px,6px)' is not a matrix"))
}
})
})

0 comments on commit 1179dd7

Please sign in to comment.