Skip to content

Commit

Permalink
chore(TS): Parse transform attribute typing + regex refactoring (#8878)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lazauya authored May 3, 2023
1 parent 577085a commit dc644fb
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 111 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- chore(TS): Parse transform attribute typing [#8878](https://github.com/fabricjs/fabric.js/pull/8878)
- chore(TS): Fix typing for DOMParser [#8871](https://github.com/fabricjs/fabric.js/pull/8871)
- fix(Path, polyline): fix for SVG import [#8879](https://github.com/fabricjs/fabric.js/pull/8879)
- chore(TS) add types for loadSVGFromURl, parseSVGDocument, loadSVGFromString [#8869](https://github.com/fabricjs/fabric.js/pull/8869)
Expand Down
2 changes: 1 addition & 1 deletion src/parser/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const storage = {
clipPaths,
};

export const reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)';
export const reNum = String.raw`(?:[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)`;

export const svgNS = 'http://www.w3.org/2000/svg';

Expand Down
136 changes: 41 additions & 95 deletions src/parser/parseTransformAttribute.ts
Original file line number Diff line number Diff line change
@@ -1,105 +1,50 @@
//@ts-nocheck
import { iMatrix } from '../constants';
import { commaWsp, reNum } from './constants';
import { reNum } from './constants';
import { multiplyTransformMatrices } from '../util/misc/matrix';
import { degreesToRadians } from '../util/misc/radiansDegreesConversion';
import { rotateMatrix } from './rotateMatrix';
import { scaleMatrix } from './scaleMatrix';
import { skewMatrix } from './skewMatrix';
import { translateMatrix } from './translateMatrix';
import { TMat2D } from '../typedefs';

// == begin transform regexp
const number = reNum,
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
rotate =
'(?:(rotate)\\s*\\(\\s*(' +
number +
')(?:' +
commaWsp +
'(' +
number +
')' +
commaWsp +
'(' +
number +
'))?\\s*\\))',
scale =
'(?:(scale)\\s*\\(\\s*(' +
number +
')(?:' +
commaWsp +
'(' +
number +
'))?\\s*\\))',
translate =
'(?:(translate)\\s*\\(\\s*(' +
number +
')(?:' +
commaWsp +
'(' +
number +
'))?\\s*\\))',
matrix =
'(?:(matrix)\\s*\\(\\s*' +
'(' +
number +
')' +
commaWsp +
'(' +
number +
')' +
commaWsp +
'(' +
number +
')' +
commaWsp +
'(' +
number +
')' +
commaWsp +
'(' +
number +
')' +
commaWsp +
'(' +
number +
')' +
'\\s*\\))',
transform =
'(?:' +
matrix +
'|' +
translate +
'|' +
scale +
'|' +
rotate +
'|' +
skewX +
'|' +
skewY +
')',
transforms =
'(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
transformList = '^\\s*(?:' + transforms + '?)\\s*$',
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
reTransformList = new RegExp(transformList),
// == end transform regexp
reTransform = new RegExp(transform, 'g');
const p = `(${reNum})`;
const skewX = String.raw`(skewX)\(${p}\)`;
const skewY = String.raw`(skewY)\(${p}\)`;
const rotate = String.raw`(rotate)\(${p}(?: ${p} ${p})?\)`;
const scale = String.raw`(scale)\(${p}(?: ${p})?\)`;
const translate = String.raw`(translate)\(${p}(?: ${p})?\)`;
const matrix = String.raw`(matrix)\(${p} ${p} ${p} ${p} ${p} ${p}\)`;
const transform = `(?:${matrix}|${translate}|${rotate}|${scale}|${skewX}|${skewY})`;
const transforms = `(?:${transform}*)`;
const transformList = String.raw`^\s*(?:${transforms}?)\s*$`;
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
const reTransformList = new RegExp(transformList);
// == end transform regexp
const reTransform = new RegExp(transform, 'g');

/**
* Parses "transform" attribute, returning an array of values
* @static
* @function
* @memberOf fabric
* @param {String} attributeValue String containing attribute value
* @return {Array} Array of 6 elements representing transformation matrix
* @return {TTransformMatrix} Array of 6 elements representing transformation matrix
*/
export function parseTransformAttribute(attributeValue) {
export function parseTransformAttribute(attributeValue: string): TMat2D {
// first we clean the string
attributeValue = attributeValue
.replace(new RegExp(`(${reNum})`, 'gi'), ' $1 ')
// replace annoying commas and arbitrary whitespace with single spaces
.replace(/,/gi, ' ')
.replace(/\s+/gi, ' ')
// remove spaces around front parentheses
.replace(/\s*([()])\s*/gi, '$1');

// start with identity matrix
let matrix = iMatrix.concat();
const matrices = [];
let matrix: TMat2D = [...iMatrix];
const matrices: TMat2D[] = [];

// return if no argument was given or
// an argument does not match transform attribute regexp
Expand All @@ -110,13 +55,14 @@ export function parseTransformAttribute(attributeValue) {
return matrix;
}

attributeValue.replace(reTransform, function (match) {
const m = new RegExp(transform).exec(match).filter(function (match) {
// match !== '' && match != null
return !!match;
}),
operation = m[1],
args = m.slice(2).map(parseFloat);
for (const match of attributeValue.matchAll(reTransform)) {
const transformMatch = new RegExp(transform).exec(match[0]);
if (!transformMatch) {
continue;
}
const matchedParams = transformMatch.filter((m) => !!m);
const operation = matchedParams[1];
const args = matchedParams.slice(2).map(parseFloat);

switch (operation) {
case 'translate':
Expand All @@ -136,15 +82,15 @@ export function parseTransformAttribute(attributeValue) {
skewMatrix(matrix, args, 1);
break;
case 'matrix':
matrix = args;
matrix = args as TMat2D;
break;
}

// snapshot current matrix into matrices array
matrices.push(matrix.concat());
matrices.push([...matrix]);
// reset
matrix = iMatrix.concat();
});
matrix = [...iMatrix];
}

let combinedMatrix = matrices[0];
while (matrices.length > 1) {
Expand Down
16 changes: 14 additions & 2 deletions src/parser/rotateMatrix.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
//@ts-nocheck
import { cos } from '../util/misc/cos';
import { sin } from '../util/misc/sin';
import { TMat2D } from '../typedefs';

export function rotateMatrix(matrix, args) {
/**
* A rotation matrix
* In the form of
* [cos(a) -sin(a) -xcos(a)+ysin(a)+x]
* [sin(a) cos(a) -xsin(a)-ycos(a)+y]
* [0 0 1 ]
*/
type TMatRotate = [a: number] | [a: number, x: number, y: number];

export function rotateMatrix(
matrix: TMat2D,
args: TMatRotate | number[]
): void {
const cosValue = cos(args[0]),
sinValue = sin(args[0]);
let x = 0,
Expand Down
15 changes: 13 additions & 2 deletions src/parser/scaleMatrix.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
//@ts-nocheck
import { TMat2D } from '../typedefs';

export function scaleMatrix(matrix, args) {
/**
* A scale matrix
* Takes form
* [x 0 0]
* [0 y 0]
* [0 0 1]
* For more info, see
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#scale
*/
type TMatScale = [x: number] | [x: number, y: number];

export function scaleMatrix(matrix: TMat2D, args: TMatScale | number[]) {
const multiplierX = args[0],
multiplierY = args.length === 2 ? args[1] : args[0];

Expand Down
18 changes: 16 additions & 2 deletions src/parser/skewMatrix.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
//@ts-nocheck
import { degreesToRadians } from '../util/misc/radiansDegreesConversion';
import { TMat2D } from '../typedefs';

export function skewMatrix(matrix, args, pos) {
/**
* A matrix in the form
* [0 x 0]
* [y 0 0]
* [0 0 1]
* For more info, see
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#skewx
*/
type TMatSkew = [xy: number];

export function skewMatrix(
matrix: TMat2D,
args: TMatSkew | number[],
pos: 2 | 1
): void {
matrix[pos] = Math.tan(degreesToRadians(args[0]));
}
21 changes: 19 additions & 2 deletions src/parser/translateMatrix.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
//@ts-nocheck
import { TMat2D } from '../typedefs';

export function translateMatrix(matrix, args) {
/**
* A translation matrix in the form of
* [ 1 0 x ]
* [ 0 1 y ]
* [ 0 0 1 ]
* See @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#translate for more details
*/
type TMatTranslate = [x: number] | [x: number, y: number];

/**
* Force the translation to be this
* @param matrix
* @param args
*/
export function translateMatrix(
matrix: TMat2D,
args: TMatTranslate | number[]
): void {
matrix[4] = args[0];
if (args.length === 2) {
matrix[5] = args[1];
Expand Down
17 changes: 16 additions & 1 deletion src/typedefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,22 @@ export const enum SupportedSVGUnit {
em = 'em',
}

export type TMat2D = [number, number, number, number, number, number];
/**
* A transform matrix.
* Basically a matrix in the form
* [ a c e ]
* [ b d f ]
* [ 0 0 1 ]
* For more details, see @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#matrix
*/
export type TMat2D = [
a: number,
b: number,
c: number,
d: number,
e: number,
f: number
];

/**
* An invalid keyword and an empty string will be handled as the `anonymous` keyword.
Expand Down
5 changes: 3 additions & 2 deletions src/util/path/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
TParsedArcCommand,
} from './typedefs';
import { XY, Point } from '../../Point';
import { numberRegExStr, rePathCommand } from './regex';
import { rePathCommand } from './regex';
import { reNum } from '../../parser/constants';

/**
* Commands that may be repeated
Expand Down Expand Up @@ -843,7 +844,7 @@ export const parsePath = (pathString: string): TComplexPathData => {
// clean the string
// add spaces around the numbers
pathString = pathString
.replace(new RegExp(`(${numberRegExStr})`, 'gi'), ' $1 ')
.replace(new RegExp(`(${reNum})`, 'gi'), ' $1 ')
// replace annoying commas and arbitrary whitespace with single spaces
.replace(/,/gi, ' ')
.replace(/\s+/gi, ' ');
Expand Down
6 changes: 2 additions & 4 deletions src/util/path/regex.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// absolute value number
const absNumberRegExStr = String.raw`(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?`;
export const numberRegExStr = `[-+]?${absNumberRegExStr}`;
import { reNum } from '../../parser/constants';

/**
* p for param
* using "bad naming" here because it makes the regex much easier to read
*/
const p = `(${numberRegExStr})`;
const p = `(${reNum})`;

const reMoveToCommand = `(M) (?:${p} ${p} ?)+`;

Expand Down

0 comments on commit dc644fb

Please sign in to comment.