Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into apply-transform-shape
Browse files Browse the repository at this point in the history
  • Loading branch information
KTibow committed Dec 28, 2023
2 parents f4a638e + f238d6a commit 99cc6ad
Show file tree
Hide file tree
Showing 61 changed files with 1,472 additions and 1,373 deletions.
3 changes: 0 additions & 3 deletions .github/FUNDING.yml

This file was deleted.

2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
node-version: ${{ env.NODE }}
cache: yarn
- run: yarn install
- run: yarn playwright install --with-deps chromium
- run: yarn test-regression
test:
name: ${{ matrix.os }} Node.js ${{ matrix.node-version }}
Expand All @@ -58,5 +59,6 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: yarn
- run: yarn install
- run: yarn playwright install --with-deps chromium
- run: yarn test
- run: yarn test-browser
5 changes: 0 additions & 5 deletions SECURITY.md

This file was deleted.

3 changes: 3 additions & 0 deletions docs/03-plugins/convert-path-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ svgo:
transformPrecision:
description: Number of decimal places to round to, using conventional rounding rules.
default: 5
smartArcRounding:
description: Round the radius of circular arcs when the effective change is under the error. The effective change is determined using the <a href="https://wikipedia.org/wiki/Sagitta_(geometry)" target="_blank">sagitta</a> of the arc.
default: true
removeUseless:
description: Remove redundant path commands that don't draw anything.
default: true
Expand Down
4 changes: 2 additions & 2 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const parseSvg = (data, from) => {
*/
let current = root;
/**
* @type {Array<XastParent>}
* @type {XastParent[]}
*/
const stack = [root];

Expand Down Expand Up @@ -213,7 +213,7 @@ const parseSvg = (data, from) => {
sax.ontext = (text) => {
if (current.type === 'element') {
// prevent trimming of meaningful whitespace inside textual tags
if (textElems.includes(current.name)) {
if (textElems.has(current.name)) {
/**
* @type {XastText}
*/
Expand Down
136 changes: 84 additions & 52 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ const readNumber = (string, cursor) => {
};

/**
* @type {(string: string) => Array<PathDataItem>}
* @type {(string: string) => PathDataItem[]}
*/
const parsePathData = (string) => {
/**
* @type {Array<PathDataItem>}
* @type {PathDataItem[]}
*/
const pathData = [];
/**
Expand Down Expand Up @@ -243,15 +243,21 @@ const parsePathData = (string) => {
exports.parsePathData = parsePathData;

/**
* @type {(number: number, precision?: number) => string}
* @type {(number: number, precision?: number) => {
* roundedStr: string,
* rounded: number
* }}
*/
const stringifyNumber = (number, precision) => {
const roundAndStringify = (number, precision) => {
if (precision != null) {
const ratio = 10 ** precision;
number = Math.round(number * ratio) / ratio;
}
// remove zero whole from decimal number
return removeLeadingZero(number);

return {
roundedStr: removeLeadingZero(number),
rounded: number,
};
};

/**
Expand All @@ -267,83 +273,109 @@ const stringifyNumber = (number, precision) => {
*/
const stringifyArgs = (command, args, precision, disableSpaceAfterFlags) => {
let result = '';
let prev = '';
for (let i = 0; i < args.length; i += 1) {
const number = args[i];
const numberString = stringifyNumber(number, precision);
let previous;

for (let i = 0; i < args.length; i++) {
const { roundedStr, rounded } = roundAndStringify(args[i], precision);
if (
disableSpaceAfterFlags &&
(command === 'A' || command === 'a') &&
// consider combined arcs
(i % 7 === 4 || i % 7 === 5)
) {
result += numberString;
} else if (i === 0 || numberString.startsWith('-')) {
result += roundedStr;
} else if (i === 0 || rounded < 0) {
// avoid space before first and negative numbers
result += numberString;
} else if (prev.includes('.') && numberString.startsWith('.')) {
result += roundedStr;
} else if (
!Number.isInteger(previous) &&
rounded != 0 &&
rounded < 1 &&
rounded > -1
) {
// remove space before decimal with zero whole
// only when previous number is also decimal
result += numberString;
result += roundedStr;
} else {
result += ` ${numberString}`;
result += ` ${roundedStr}`;
}
prev = numberString;
previous = rounded;
}

return result;
};

/**
* @typedef {{
* pathData: Array<PathDataItem>;
* pathData: PathDataItem[];
* precision?: number;
* disableSpaceAfterFlags?: boolean;
* }} StringifyPathDataOptions
*/

/**
* @type {(options: StringifyPathDataOptions) => string}
* @param {StringifyPathDataOptions} options
* @returns {string}
*/
const stringifyPathData = ({ pathData, precision, disableSpaceAfterFlags }) => {
// combine sequence of the same commands
let combined = [];
for (let i = 0; i < pathData.length; i += 1) {
if (pathData.length === 1) {
const { command, args } = pathData[0];
return (
command + stringifyArgs(command, args, precision, disableSpaceAfterFlags)
);
}

let result = '';
let prev = { ...pathData[0] };

// match leading moveto with following lineto
if (pathData[1].command === 'L') {
prev.command = 'M';
} else if (pathData[1].command === 'l') {
prev.command = 'm';
}

for (let i = 1; i < pathData.length; i++) {
const { command, args } = pathData[i];
if (i === 0) {
combined.push({ command, args });
} else {
/**
* @type {PathDataItem}
*/
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 (
(prev.command === command &&
prev.command !== 'M' &&
prev.command !== 'm') ||
// combine matching moveto and lineto sequences
(prev.command === 'M' && command === 'L') ||
(prev.command === 'm' && command === 'l')
) {
prev.args = [...prev.args, ...args];
if (i === pathData.length - 1) {
result +=
prev.command +
stringifyArgs(
prev.command,
prev.args,
precision,
disableSpaceAfterFlags,
);
}
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 {
result +=
prev.command +
stringifyArgs(
prev.command,
prev.args,
precision,
disableSpaceAfterFlags,
);

if (i === pathData.length - 1) {
result +=
command +
stringifyArgs(command, args, precision, disableSpaceAfterFlags);
} else {
combined.push({ command, args });
prev = { command, args };
}
}
}
let result = '';
for (const { command, args } of combined) {
result +=
command + stringifyArgs(command, args, precision, disableSpaceAfterFlags);
}

return result;
};
exports.stringifyPathData = stringifyPathData;
4 changes: 2 additions & 2 deletions lib/path.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('stringify path data', () => {
});
it('should configure precision', () => {
/**
* @type {Array<PathDataItem>}
* @type {PathDataItem[]}
*/
const pathData = [
{ command: 'M', args: [0, -1.9876] },
Expand All @@ -169,7 +169,7 @@ describe('stringify path data', () => {
});
it('allows to avoid spaces after arc flags', () => {
/**
* @type {Array<PathDataItem>}
* @type {PathDataItem[]}
*/
const pathData = [
{ command: 'M', args: [0, 0] },
Expand Down
2 changes: 1 addition & 1 deletion lib/stringifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ const stringifyElement = (node, config, state) => {
tagCloseStart = defaults.tagCloseStart;
tagCloseEnd = defaults.tagCloseEnd;
openIndent = '';
} else if (textElems.includes(node.name)) {
} else if (textElems.has(node.name)) {
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
closeIndent = '';
Expand Down
Loading

0 comments on commit 99cc6ad

Please sign in to comment.