1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-31 07:44:22 +03:00

Implement path stringify (#1387)

Ref https://github.com/svg/svgo/issues/32

Added `stringifyPathData` utility to produce small as possible path by
- matching leading moveto and line to commands (m -> l, M -> L)
- combining moveto and lineto commands
- avoiding space before decimal numbers if possible
This commit is contained in:
Bogdan Chadkin
2021-03-02 01:07:26 +03:00
committed by GitHub
parent cf807fbe34
commit 04b2ae9a37
15 changed files with 227 additions and 97 deletions

View File

@ -208,3 +208,81 @@ const parsePathData = (string) => {
return pathData;
};
exports.parsePathData = parsePathData;
const stringifyNumber = ({ number, precision }) => {
let result;
if (precision == null) {
result = number.toString();
} else {
result = number.toFixed(precision);
if (result.includes('.')) {
result = result.replace(/\.?0+$/, '')
}
}
// remove zero whole from decimal number
if (result !== '0') {
result = result.replace(/^0/, '').replace(/^-0/, '-');
}
return result;
};
// elliptical arc large-arc and sweep flags are rendered with spaces
// because many non-browser environments are not able to parse such paths
const stringifyArgs = ({ args, precision }) => {
let result = '';
let prev;
for (let i = 0; i < args.length; i += 1) {
const number = args[i];
const numberString = stringifyNumber({ number, precision });
// avoid space before first and negative numbers
if (i === 0 || numberString.startsWith('-')) {
result += numberString;
} else if (prev.includes('.') && numberString.startsWith('.')) {
result += numberString;
} else {
result += ` ${numberString}`;
}
prev = numberString;
}
return result;
};
const stringifyPathData = ({ pathData, precision }) => {
// combine sequence of the same commands
let combined = [];
for (let i = 0; i < pathData.length; i += 1) {
const { command, args } = pathData[i];
if (i === 0) {
combined.push({ command, args });
} else {
const last = combined[combined.length - 1];
// match leading moveto with following lineto
if (i === 1) {
if (command === 'L') {
last.command = 'M';
}
if (command === 'l') {
last.command = 'm';
}
}
if (
(last.command === command &&
last.command !== 'M' &&
last.command !== 'm') ||
// combine matching moveto and lineto sequences
(last.command === 'M' && command === 'L') ||
(last.command === 'm' && command === 'l')
) {
last.args = [...last.args, ...args];
} else {
combined.push({ command, args });
}
}
}
let result = '';
for (const { command, args } of combined) {
result += command + stringifyArgs({ args, precision });
}
return result;
};
exports.stringifyPathData = stringifyPathData;