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

Support fill rule #139

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
10,066 changes: 9,043 additions & 1,023 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"dependencies": {
"commander": "^9.3.0",
"glob": "^8.0.3",
"paper-jsdom": "^0.12.15",
"sax": "^1.2.4",
"svg-pathdata": "^6.0.3"
},
Expand Down
116 changes: 85 additions & 31 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const Sax = require('sax');
const { SVGPathData } = require('svg-pathdata');
const svgShapesToPath = require('./svgshapes2svgpath');
const { Matrix } = require('./Matrix');
const paper = require('paper');

// Transform fill-rule from evenodd to nonzero
function reorientPath(pathData, width, height) {
paper.setup(new paper.Size(width, height));
var path = new paper.CompoundPath(pathData);
path.reorient();
return path.pathData;
}

// Transform helpers (will move elsewhere later)
function parseTransforms(value) {
Expand Down Expand Up @@ -42,6 +51,24 @@ function matrixFromTransformAttribute(transformAttributeString) {
return result;
}

function hasParent(parentTag, parents) {
return parents.some((tag) => parentTag === tag.name);
}

function findDefs(defs, name) {
return defs.find((tag) => tag.attributes.id === name.replace('#', ''));
}

function hasFillRule(tag) {
return (
'fill-rule' in tag.attributes && 'evenodd' === tag.attributes['fill-rule']
);
}

function parentHasFillRule(parents) {
return parents.some((tag) => hasFillRule(tag));
}

// Rendering
function tagShouldRender(curTag, parents) {
let values;
Expand Down Expand Up @@ -71,6 +98,9 @@ function tagShouldRender(curTag, parents) {
return true;
}
}
if ('clipPath' === tag.name) {
return true;
}
return false;
});
}
Expand Down Expand Up @@ -130,10 +160,46 @@ class SVGIcons2SVGFontStream extends Transform {
this.log = this._options.log || console.log.bind(console); // eslint-disable-line
}

_processElement(tag, glyph, parents) {
if ('rect' === tag.name && 'none' !== tag.attributes.fill) {
return svgShapesToPath.rectToPath(tag.attributes);
} else if ('line' === tag.name && 'none' !== tag.attributes.fill) {
this.log(
`Found a line element in the icon "${glyph.name}" the result could be different than expected.`
);
return svgShapesToPath.lineToPath(tag.attributes);
} else if ('polyline' === tag.name && 'none' !== tag.attributes.fill) {
this.log(
`Found a polyline element in the icon "${glyph.name}" the result could be different than expected.`
);
return svgShapesToPath.polylineToPath(tag.attributes);
} else if ('polygon' === tag.name && 'none' !== tag.attributes.fill) {
return svgShapesToPath.polygonToPath(tag.attributes);
} else if (
['circle', 'ellipse'].includes(tag.name) &&
'none' !== tag.attributes.fill
) {
return svgShapesToPath.circleToPath(tag.attributes);
} else if (
'path' === tag.name &&
tag.attributes.d &&
'none' !== tag.attributes.fill
) {
let pathData = tag.attributes.d;
//Found fill rule "evenodd" support
if (hasFillRule(tag) || parentHasFillRule(parents)) {
pathData = reorientPath(tag.attributes.d, glyph.width, glyph.height);
}
return pathData;
}
return null;
}

_transform(svgIconStream, _unused, svgIconStreamCallback) {
// Parsing each icons asynchronously
const saxStream = Sax.createStream(true);
const parents = [];
const defs = [];
const transformStack = [new Matrix()];
function applyTransform(d) {
return new SVGPathData(d).matrix(
Expand Down Expand Up @@ -229,6 +295,11 @@ class SVGIcons2SVGFontStream extends Transform {
return;
}

if (hasParent('defs', parents)) {
defs.push(tag);
return;
}

// Save the view size
if ('svg' === tag.name) {
if ('viewBox' in tag.attributes) {
Expand Down Expand Up @@ -274,41 +345,24 @@ class SVGIcons2SVGFontStream extends Transform {
this.log(
`Found a clipPath element in the icon "${glyph.name}" the result may be different than expected.`
);
} else if ('rect' === tag.name && 'none' !== tag.attributes.fill) {
glyph.paths.push(
applyTransform(svgShapesToPath.rectToPath(tag.attributes))
);
} else if ('line' === tag.name && 'none' !== tag.attributes.fill) {
this.log(
`Found a line element in the icon "${glyph.name}" the result could be different than expected.`
);
glyph.paths.push(
applyTransform(svgShapesToPath.lineToPath(tag.attributes))
);
} else if ('polyline' === tag.name && 'none' !== tag.attributes.fill) {
this.log(
`Found a polyline element in the icon "${glyph.name}" the result could be different than expected.`
);
glyph.paths.push(
applyTransform(svgShapesToPath.polylineToPath(tag.attributes))
);
} else if ('polygon' === tag.name && 'none' !== tag.attributes.fill) {
glyph.paths.push(
applyTransform(svgShapesToPath.polygonToPath(tag.attributes))
);
} else if (
['circle', 'ellipse'].includes(tag.name) &&
'use' === tag.name &&
tag.attributes['xlink:href'] &&
'none' !== tag.attributes.fill
) {
glyph.paths.push(
applyTransform(svgShapesToPath.circleToPath(tag.attributes))
const pathData = this._processElement(
findDefs(defs, tag.attributes['xlink:href']),
glyph,
parents
);
} else if (
'path' === tag.name &&
tag.attributes.d &&
'none' !== tag.attributes.fill
) {
glyph.paths.push(applyTransform(tag.attributes.d));
if (pathData) {
glyph.paths.push(applyTransform(pathData));
}
} else {
const pathData = this._processElement(tag, glyph, parents);
if (pathData) {
glyph.paths.push(applyTransform(pathData));
}
}

// According to http://www.w3.org/TR/SVG/painting.html#SpecifyingPaint
Expand Down
6 changes: 3 additions & 3 deletions tests/cli.mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('Testing CLI', () => {
);
done();
});
});
}).timeout(5000);

it('should work for more than 32 SVG icons', (done) => {
const command =
Expand Down Expand Up @@ -80,7 +80,7 @@ describe('Testing CLI', () => {
);
done();
});
});
}).timeout(5000);

describe('with nested icons', () => {
it('should work', (done) => {
Expand Down Expand Up @@ -111,6 +111,6 @@ describe('Testing CLI', () => {
);
done();
});
});
}).timeout(5000);
});
});
2 changes: 1 addition & 1 deletion tests/expected/hiddenpathesicons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/expected/lotoficons-cli.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/expected/lotoficons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading