Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(): rm runtime RegExp usages #9802

Merged
merged 5 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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]

- perf(): rm runtime RegExp usages [#9802](https://github.com/fabricjs/fabric.js/pull/9802)
- fix(Canvas): Fix searchPossibleTargets for non-interactive nested targets [#9762](https://github.com/fabricjs/fabric.js/pull/9762)
- test(): Rename svg tests [#9775](https://github.com/fabricjs/fabric.js/pull/9775)
- refactor(): `_findTargetCorner` is now called `findControl` and returns the key and the control and the coordinates [#9668](https://github.com/fabricjs/fabric.js/pull/9668)
Expand Down
22 changes: 10 additions & 12 deletions src/Shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ import { rotateVector } from './util/misc/vectors';

const shadowOffsetRegex = '(-?\\d+(?:\\.\\d*)?(?:px)?(?:\\s?|$))?';

const reOffsetsAndBlur = () =>
new RegExp(
'(?:\\s|^)' +
shadowOffsetRegex +
shadowOffsetRegex +
'(' +
reNum +
'?(?:px)?)?(?:\\s?|$)(?:$|\\s)'
);
const reOffsetsAndBlur = new RegExp(
'(?:\\s|^)' +
shadowOffsetRegex +
shadowOffsetRegex +
'(' +
reNum +
'?(?:px)?)?(?:\\s?|$)(?:$|\\s)'
);

export const shadowDefaultValues: Partial<TClassProperties<Shadow>> = {
color: 'rgb(0,0,0)',
Expand Down Expand Up @@ -142,11 +141,10 @@ export class Shadow {
*/
static parseShadow(value: string) {
const shadowStr = value.trim(),
regex = reOffsetsAndBlur(),
[, offsetX = 0, offsetY = 0, blur = 0] = (
regex.exec(shadowStr) || []
reOffsetsAndBlur.exec(shadowStr) || []
).map((value) => parseFloat(value) || 0),
color = (shadowStr.replace(regex, '') || 'rgb(0,0,0)').trim();
color = (shadowStr.replace(reOffsetsAndBlur, '') || 'rgb(0,0,0)').trim();

return {
color,
Expand Down
4 changes: 3 additions & 1 deletion src/filters/BaseFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
import type { Abortable } from '../typedefs';
import { FabricError } from '../util/internals/console';

const regex = new RegExp(highPsourceCode, 'g');

export class BaseFilter {
/**
* Filter type
Expand Down Expand Up @@ -84,7 +86,7 @@ export class BaseFilter {
} = getEnv();
if (GLPrecision !== 'highp') {
fragmentSource = fragmentSource.replace(
new RegExp(highPsourceCode, 'g'),
regex,
highPsourceCode.replace('highp', GLPrecision)
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/parser/getSvgRegex.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Do not use in runtime
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is already used at runtime during svg parsing.
Which is the concept you wanted to add here? to do not add it in loops?

export function getSvgRegex(arr: string[]) {
return new RegExp('^(' + arr.join('|') + ')\\b', 'i');
}
7 changes: 4 additions & 3 deletions src/parser/parseTransformAttribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ const transforms = `(?:${transform}*)`;
const transformList = String.raw`^\s*(?:${transforms}?)\s*$`;
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
const reTransformList = new RegExp(transformList);
const reTransform = new RegExp(transform);
const reTransformAll = new RegExp(transform, 'g');
// == end transform regexp
const reTransform = new RegExp(transform, 'g');

/**
* Parses "transform" attribute, returning an array of values
Expand Down Expand Up @@ -53,8 +54,8 @@ export function parseTransformAttribute(attributeValue: string): TMat2D {
return [...iMatrix];
}

for (const match of attributeValue.matchAll(reTransform)) {
const transformMatch = new RegExp(transform).exec(match[0]);
for (const match of attributeValue.matchAll(reTransformAll)) {
const transformMatch = reTransform.exec(match[0]);
if (!transformMatch) {
continue;
}
Expand Down
4 changes: 3 additions & 1 deletion src/util/internals/cleanupSvgAttribute.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { reNum } from '../../parser/constants';

const regex = new RegExp(`(${reNum})`, 'gi');

export const cleanupSvgAttribute = (attributeValue: string) =>
attributeValue
.replace(new RegExp(`(${reNum})`, 'gi'), ' $1 ')
.replace(regex, ' $1 ')
// replace annoying commas and arbitrary whitespace with single spaces
.replace(/,/gi, ' ')
.replace(/\s+/gi, ' ');
33 changes: 13 additions & 20 deletions src/util/path/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,9 @@ export const getPointOnPath = (
}
};

const rePathCmdAll = new RegExp(rePathCommand, 'gi');
const rePathCmd = new RegExp(rePathCommand, 'i');

/**
*
* @param {string} pathString
Expand All @@ -847,41 +850,31 @@ export const parsePath = (pathString: string): TComplexPathData => {
pathString = cleanupSvgAttribute(pathString);

const res: TComplexPathData = [];
for (const match of pathString.matchAll(new RegExp(rePathCommand, 'gi'))) {
let matchStr = match[0];
for (let [match] of pathString.matchAll(rePathCmdAll)) {
const chain: TComplexPathData = [];
let paramArr: RegExpExecArray | null;
do {
paramArr = new RegExp(rePathCommand, 'i').exec(matchStr);
paramArr = rePathCmd.exec(match);
if (!paramArr) {
break;
}
// ignore undefined match groups
const filteredGroups = paramArr.filter((g) => g);
const filteredGroups = paramArr.filter(Boolean);
Copy link
Member

@asturur asturur Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equal to call Boolean as Boolean(g, index, paramArr) this is usually suggested to avoid, i would avoid

Copy link
Contributor

@jiayihu jiayihu May 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid filter(Boolean) also because from TS 5.5 it will be able to automatically type narrow .filter(g => !!g) to defined values, but .filter(Boolean) won't work

microsoft/TypeScript#50387 (comment)

// remove the first element from the match array since it's just the whole command
filteredGroups.shift();
const value = filteredGroups.shift();
// if we can't parse the number, just interpret it as a string
// (since it's probably the path command)
const command = filteredGroups.map((g) => {
const command: (string | number)[] = [];
for (const g of filteredGroups) {
const numParse = Number.parseFloat(g);
if (Number.isNaN(numParse)) {
return g;
} else {
return numParse;
}
});
chain.push(command as any);
command.push(Number.isNaN(numParse) ? g : numParse);
}
chain.push(command as TComplexParsedCommand);
// stop now if it's a z command
if (filteredGroups.length <= 1) {
break;
}
// remove the last part of the chained command
filteredGroups.shift();
// ` ?` is to support commands with optional spaces between flags
matchStr = matchStr.replace(
new RegExp(`${filteredGroups.join(' ?')} ?$`),
''
);
match = match.slice(value ? value.length : 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the changes to the path parsing code please either add a very detailed explanation or let's not do them.

} while (paramArr);
// add the chain, convert multiple m's to l's in the process
chain.reverse().forEach((c, idx) => {
Expand Down