Skip to content

Commit 6996fca

Browse files
authored
refactor: improve performance of stringifyPathData (#1900)
1 parent a3da8b3 commit 6996fca

File tree

2 files changed

+93
-60
lines changed

2 files changed

+93
-60
lines changed

lib/path.js

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,21 @@ const parsePathData = (string) => {
243243
exports.parsePathData = parsePathData;
244244

245245
/**
246-
* @type {(number: number, precision?: number) => string}
246+
* @type {(number: number, precision?: number) => {
247+
* roundedStr: string,
248+
* rounded: number
249+
* }}
247250
*/
248-
const stringifyNumber = (number, precision) => {
251+
const roundAndStringify = (number, precision) => {
249252
if (precision != null) {
250253
const ratio = 10 ** precision;
251254
number = Math.round(number * ratio) / ratio;
252255
}
253-
// remove zero whole from decimal number
254-
return removeLeadingZero(number);
256+
257+
return {
258+
roundedStr: removeLeadingZero(number),
259+
rounded: number,
260+
};
255261
};
256262

257263
/**
@@ -267,29 +273,35 @@ const stringifyNumber = (number, precision) => {
267273
*/
268274
const stringifyArgs = (command, args, precision, disableSpaceAfterFlags) => {
269275
let result = '';
270-
let prev = '';
271-
for (let i = 0; i < args.length; i += 1) {
272-
const number = args[i];
273-
const numberString = stringifyNumber(number, precision);
276+
let previous;
277+
278+
for (let i = 0; i < args.length; i++) {
279+
const { roundedStr, rounded } = roundAndStringify(args[i], precision);
274280
if (
275281
disableSpaceAfterFlags &&
276282
(command === 'A' || command === 'a') &&
277283
// consider combined arcs
278284
(i % 7 === 4 || i % 7 === 5)
279285
) {
280-
result += numberString;
281-
} else if (i === 0 || numberString.startsWith('-')) {
286+
result += roundedStr;
287+
} else if (i === 0 || rounded < 0) {
282288
// avoid space before first and negative numbers
283-
result += numberString;
284-
} else if (prev.includes('.') && numberString.startsWith('.')) {
289+
result += roundedStr;
290+
} else if (
291+
!Number.isInteger(previous) &&
292+
rounded != 0 &&
293+
rounded < 1 &&
294+
rounded > -1
295+
) {
285296
// remove space before decimal with zero whole
286297
// only when previous number is also decimal
287-
result += numberString;
298+
result += roundedStr;
288299
} else {
289-
result += ` ${numberString}`;
300+
result += ` ${roundedStr}`;
290301
}
291-
prev = numberString;
302+
previous = rounded;
292303
}
304+
293305
return result;
294306
};
295307

@@ -302,48 +314,68 @@ const stringifyArgs = (command, args, precision, disableSpaceAfterFlags) => {
302314
*/
303315

304316
/**
305-
* @type {(options: StringifyPathDataOptions) => string}
317+
* @param {StringifyPathDataOptions} options
318+
* @returns {string}
306319
*/
307320
const stringifyPathData = ({ pathData, precision, disableSpaceAfterFlags }) => {
308-
// combine sequence of the same commands
309-
let combined = [];
310-
for (let i = 0; i < pathData.length; i += 1) {
321+
if (pathData.length === 1) {
322+
const { command, args } = pathData[0];
323+
return (
324+
command + stringifyArgs(command, args, precision, disableSpaceAfterFlags)
325+
);
326+
}
327+
328+
let result = '';
329+
let prev = { ...pathData[0] };
330+
331+
// match leading moveto with following lineto
332+
if (pathData[1].command === 'L') {
333+
prev.command = 'M';
334+
} else if (pathData[1].command === 'l') {
335+
prev.command = 'm';
336+
}
337+
338+
for (let i = 1; i < pathData.length; i++) {
311339
const { command, args } = pathData[i];
312-
if (i === 0) {
313-
combined.push({ command, args });
314-
} else {
315-
/**
316-
* @type {PathDataItem}
317-
*/
318-
const last = combined[combined.length - 1];
319-
// match leading moveto with following lineto
320-
if (i === 1) {
321-
if (command === 'L') {
322-
last.command = 'M';
323-
}
324-
if (command === 'l') {
325-
last.command = 'm';
326-
}
340+
if (
341+
(prev.command === command &&
342+
prev.command !== 'M' &&
343+
prev.command !== 'm') ||
344+
// combine matching moveto and lineto sequences
345+
(prev.command === 'M' && command === 'L') ||
346+
(prev.command === 'm' && command === 'l')
347+
) {
348+
prev.args = [...prev.args, ...args];
349+
if (i === pathData.length - 1) {
350+
result +=
351+
prev.command +
352+
stringifyArgs(
353+
prev.command,
354+
prev.args,
355+
precision,
356+
disableSpaceAfterFlags,
357+
);
327358
}
328-
if (
329-
(last.command === command &&
330-
last.command !== 'M' &&
331-
last.command !== 'm') ||
332-
// combine matching moveto and lineto sequences
333-
(last.command === 'M' && command === 'L') ||
334-
(last.command === 'm' && command === 'l')
335-
) {
336-
last.args = [...last.args, ...args];
359+
} else {
360+
result +=
361+
prev.command +
362+
stringifyArgs(
363+
prev.command,
364+
prev.args,
365+
precision,
366+
disableSpaceAfterFlags,
367+
);
368+
369+
if (i === pathData.length - 1) {
370+
result +=
371+
command +
372+
stringifyArgs(command, args, precision, disableSpaceAfterFlags);
337373
} else {
338-
combined.push({ command, args });
374+
prev = { command, args };
339375
}
340376
}
341377
}
342-
let result = '';
343-
for (const { command, args } of combined) {
344-
result +=
345-
command + stringifyArgs(command, args, precision, disableSpaceAfterFlags);
346-
}
378+
347379
return result;
348380
};
349381
exports.stringifyPathData = stringifyPathData;

lib/svgo/tools.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,24 @@ exports.cleanupOutData = (data, params, command) => {
124124
/**
125125
* Remove floating-point numbers leading zero.
126126
*
127+
* @param {number} value
128+
* @returns {string}
127129
* @example
128130
* 0.5 → .5
129-
*
130-
* @example
131131
* -0.5 → -.5
132-
*
133-
* @type {(num: number) => string}
134132
*/
135-
const removeLeadingZero = (num) => {
136-
var strNum = num.toString();
133+
const removeLeadingZero = (value) => {
134+
const strValue = value.toString();
137135

138-
if (0 < num && num < 1 && strNum.charAt(0) === '0') {
139-
strNum = strNum.slice(1);
140-
} else if (-1 < num && num < 0 && strNum.charAt(1) === '0') {
141-
strNum = strNum.charAt(0) + strNum.slice(2);
136+
if (0 < value && value < 1 && strValue.startsWith('0')) {
137+
return strValue.slice(1);
142138
}
143-
return strNum;
139+
140+
if (-1 < value && value < 0 && strValue[1] === '0') {
141+
return strValue[0] + strValue.slice(2);
142+
}
143+
144+
return strValue;
144145
};
145146
exports.removeLeadingZero = removeLeadingZero;
146147

0 commit comments

Comments
 (0)