svgo
Advanced tools
Comparing version 0.5.6 to 0.6.0
@@ -0,1 +1,13 @@ | ||
### [ [>](https://github.com/svg/svgo/tree/v0.6.0) ] 0.6.0 / 08.11.2015 | ||
* New optimization: circular curves now being converted to arcs. A notable improvement for circles within paths. | ||
* New plugin “[minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js)” which minifies `<style>` elments content with CSSO by @strarsis (svgo still doesn't understand its content) | ||
* New plugin “[removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js)” (disabled by default) by @betsydupuis. | ||
* Fixed issues wuth parsing numbers with exponent fraction (could happen with high precision >= 7). | ||
* Fixed rounding error due to incorrect preserving of precision in transformations. | ||
* Fixed shortand curve distortion due to converted previous curve to not a curve. | ||
* Fixed interoperability issue with `precision` cli-option and `full` config. | ||
* Fixed an error produced by “[removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js)” by @thiakil | ||
* Another Inkscape prefix namespace is being removed. | ||
* Fixed an issue in [moveElemsAttrsToGroup“](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup“.js)” with transforms moved around `clip-path`. | ||
### [ [>](https://github.com/svg/svgo/tree/v0.5.6) ] 0.5.6 / 13.08.2015 | ||
@@ -2,0 +14,0 @@ * Fixed paths removing. |
@@ -180,3 +180,3 @@ /* jshint quotmark: false */ | ||
if (opts.disable) { | ||
config = changePluginsState(opts.disable, false, config); | ||
changePluginsState(opts.disable, false, config); | ||
} | ||
@@ -186,3 +186,3 @@ | ||
if (opts.enable) { | ||
config = changePluginsState(opts.enable, true, config); | ||
changePluginsState(opts.enable, true, config); | ||
} | ||
@@ -354,3 +354,3 @@ | ||
// extend config | ||
if (config && config.plugins) { | ||
if (config.plugins) { | ||
@@ -371,5 +371,5 @@ names.forEach(function(name) { | ||
// if there are such plugin name | ||
// if there is such a plugin name | ||
if (key === name) { | ||
// do not replace plugin's params with true | ||
// don't replace plugin's params with true | ||
if (typeof plugin[key] !== 'object' || !state) { | ||
@@ -404,3 +404,3 @@ plugin[key] = state; | ||
config = { plugins: [] }; | ||
config.plugins = []; | ||
@@ -407,0 +407,0 @@ names.forEach(function(name) { |
@@ -18,35 +18,27 @@ 'use strict'; | ||
var defaults; | ||
config = config || {}; | ||
if (config && config.full) { | ||
if (config.full) { | ||
defaults = config; | ||
if (defaults.plugins) { | ||
if (Array.isArray(defaults.plugins)) { | ||
defaults.plugins = preparePluginsArray(defaults.plugins); | ||
defaults.plugins = optimizePluginsArray(defaults.plugins); | ||
} | ||
defaults.multipass = config.multipass; | ||
} else { | ||
defaults = EXTEND({}, yaml.safeLoad(FS.readFileSync(__dirname + '/../../.svgo.yml', 'utf8'))); | ||
defaults.plugins = preparePluginsArray(defaults.plugins); | ||
defaults = extendConfig(defaults, config); | ||
} | ||
if (config) { | ||
defaults = extendConfig(defaults, config); | ||
defaults.multipass = config.multipass; | ||
if ('floatPrecision' in config) { | ||
defaults.plugins.forEach(function(plugin) { | ||
if (plugin.params && ('floatPrecision' in plugin.params)) { | ||
plugin.params.floatPrecision = config.floatPrecision; | ||
} | ||
}); | ||
if ('floatPrecision' in config && Array.isArray(defaults.plugins)) { | ||
defaults.plugins.forEach(function(plugin) { | ||
if (plugin.params && ('floatPrecision' in plugin.params)) { | ||
// Don't touch default plugin params | ||
plugin.params = EXTEND({}, plugin.params, { floatPrecision: config.floatPrecision }); | ||
} | ||
} | ||
}); | ||
} | ||
if (Array.isArray(defaults.plugins)) { | ||
defaults.plugins = optimizePluginsArray(defaults.plugins); | ||
} | ||
@@ -167,2 +159,4 @@ | ||
defaults.multipass = config.multipass; | ||
// svg2js | ||
@@ -207,21 +201,11 @@ if (config.svg2js) { | ||
plugins = plugins.map(function(item) { | ||
return [item]; | ||
}); | ||
plugins = plugins.filter(function(item) { | ||
if (prev && item[0].type === prev[0].type) { | ||
prev.push(item[0]); | ||
return false; | ||
return plugins.reduce(function(plugins, item) { | ||
if (prev && item.type == prev[0].type) { | ||
prev.push(item); | ||
} else { | ||
plugins.push(prev = [item]); | ||
} | ||
return plugins; | ||
}, []); | ||
prev = item; | ||
return true; | ||
}); | ||
return plugins; | ||
} |
@@ -90,2 +90,9 @@ 'use strict'; | ||
// remove floating-point numbers leading zeros | ||
// 0.5 → .5 | ||
// -0.5 → -.5 | ||
if (params.leadingZero) { | ||
item = removeLeadingZero(item); | ||
} | ||
// no extra space in front of negative number or | ||
@@ -96,3 +103,3 @@ // in front of a floating number if a previous number is floating too | ||
(item < 0 || | ||
(item > 0 && item < 1 && prev % 1 !== 0) | ||
(/^\./.test(item) && prev % 1 !== 0) | ||
) | ||
@@ -106,9 +113,2 @@ ) { | ||
// remove floating-point numbers leading zeros | ||
// 0.5 → .5 | ||
// -0.5 → -.5 | ||
if (params.leadingZero) { | ||
item = removeLeadingZero(item); | ||
} | ||
str += delimiter + item; | ||
@@ -115,0 +115,0 @@ |
{ | ||
"name": "svgo", | ||
"version": "0.5.6", | ||
"version": "0.6.0", | ||
"description": "Nodejs-based tool for optimizing SVG vector graphics files", | ||
@@ -42,13 +42,14 @@ "keywords": [ "svgo", "svg", "optimize", "minify" ], | ||
"dependencies": { | ||
"sax": "~1.1.1", | ||
"sax": "~1.1.4", | ||
"coa": "~1.0.1", | ||
"js-yaml": "~3.3.1", | ||
"js-yaml": "~3.4.3", | ||
"colors": "~1.1.2", | ||
"whet.extend": "~0.9.9", | ||
"mkdirp": "~0.5.1" | ||
"mkdirp": "~0.5.1", | ||
"csso": "~1.4.1" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~2.2.5", | ||
"should": "7.0.3", | ||
"istanbul": "~0.3.17", | ||
"mocha": "~2.3.3", | ||
"should": "7.1.1", | ||
"istanbul": "~0.4.0", | ||
"mocha-istanbul": "~0.2.0", | ||
@@ -55,0 +56,0 @@ "coveralls": "~2.11.4" |
@@ -2270,2 +2270,3 @@ 'use strict'; | ||
'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', | ||
'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd', | ||
'http://www.inkscape.org/namespaces/inkscape', | ||
@@ -2272,0 +2273,0 @@ 'http://www.bohemiancoding.com/sketch/ns', |
@@ -39,4 +39,4 @@ 'use strict'; | ||
currentIDstring, | ||
IDs = {}, | ||
referencesIDs = {}, | ||
IDs = Object.create(null), | ||
referencesIDs = Object.create(null), | ||
idPrefix = 'id-', // prefix IDs so that values like '__proto__' don't break the work | ||
@@ -53,3 +53,3 @@ hasStyleOrScript = false; | ||
for (var i = 0; i < items.content.length; i++) { | ||
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) { | ||
@@ -62,54 +62,54 @@ var item = items.content[i], | ||
hasStyleOrScript = true; | ||
continue; | ||
} | ||
// …and don't remove any ID if yes | ||
if (!hasStyleOrScript) { | ||
if (item.isElem()) { | ||
if (item.isElem()) { | ||
item.eachAttr(function(attr) { | ||
// save IDs | ||
if (attr.name === 'id') { | ||
if (idPrefix + attr.value in IDs) { | ||
item.removeAttr('id'); | ||
} else { | ||
IDs[idPrefix + attr.value] = item; | ||
} | ||
item.eachAttr(function(attr) { | ||
var key; | ||
// save IDs | ||
if (attr.name === 'id') { | ||
key = idPrefix + attr.value; | ||
if (key in IDs) { | ||
item.removeAttr('id'); | ||
} else { | ||
IDs[key] = item; | ||
} | ||
} | ||
// save IDs url() references | ||
else if (referencesProps.indexOf(attr.name) > -1) { | ||
match = attr.value.match(regReferencesUrl); | ||
// save IDs url() references | ||
else if (referencesProps.indexOf(attr.name) > -1) { | ||
match = attr.value.match(regReferencesUrl); | ||
if (match) { | ||
if (referencesIDs[idPrefix + match[2]]) { | ||
referencesIDs[idPrefix + match[2]].push(attr); | ||
} else { | ||
referencesIDs[idPrefix + match[2]] = [attr]; | ||
} | ||
if (match) { | ||
key = idPrefix + match[2]; | ||
if (referencesIDs[key]) { | ||
referencesIDs[key].push(attr); | ||
} else { | ||
referencesIDs[key] = [attr]; | ||
} | ||
} | ||
} | ||
// save IDs href references | ||
else if ( | ||
attr.name === 'xlink:href' && (match = attr.value.match(regReferencesHref)) || | ||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin)) | ||
) { | ||
if (referencesIDs[idPrefix + match[1]]) { | ||
referencesIDs[idPrefix + match[1]].push(attr); | ||
} else { | ||
referencesIDs[idPrefix + match[1]] = [attr]; | ||
} | ||
// save IDs href references | ||
else if ( | ||
attr.name === 'xlink:href' && (match = attr.value.match(regReferencesHref)) || | ||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin)) | ||
) { | ||
key = idPrefix + match[1]; | ||
if (referencesIDs[key]) { | ||
referencesIDs[key].push(attr); | ||
} else { | ||
referencesIDs[key] = [attr]; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
// go deeper | ||
if (item.content) { | ||
monkeys(item); | ||
} | ||
// go deeper | ||
if (item.content) { | ||
monkeys(item); | ||
} | ||
} | ||
@@ -123,36 +123,35 @@ | ||
if (!hasStyleOrScript) { | ||
if (hasStyleOrScript) { | ||
return data; | ||
} | ||
for (var k in referencesIDs) { | ||
if (IDs[k]) { | ||
for (var k in referencesIDs) { | ||
if (IDs[k]) { | ||
// replace referenced IDs with the minified ones | ||
if (params.minify) { | ||
// replace referenced IDs with the minified ones | ||
if (params.minify) { | ||
currentIDstring = getIDstring(currentID = generateID(currentID), params); | ||
IDs[k].attr('id').value = currentIDstring; | ||
currentIDstring = getIDstring(currentID = generateID(currentID), params); | ||
IDs[k].attr('id').value = currentIDstring; | ||
referencesIDs[k].forEach(function(attr) { | ||
k = k.replace(idPrefix, ''); | ||
attr.value = attr.value | ||
.replace('#' + k, '#' + currentIDstring) | ||
.replace(k + '.', currentIDstring + '.'); | ||
}); | ||
referencesIDs[k].forEach(function(attr) { | ||
k = k.replace(idPrefix, ''); | ||
attr.value = attr.value | ||
.replace('#' + k, '#' + currentIDstring) | ||
.replace(k + '.', currentIDstring + '.'); | ||
}); | ||
} | ||
} | ||
// don't remove referenced IDs | ||
delete IDs[idPrefix + k]; | ||
// don't remove referenced IDs | ||
delete IDs[idPrefix + k]; | ||
} | ||
} | ||
} | ||
// remove non-referenced IDs attributes from elements | ||
if (params.remove) { | ||
// remove non-referenced IDs attributes from elements | ||
if (params.remove) { | ||
for(var ID in IDs) { | ||
IDs[ID].removeAttr('id'); | ||
} | ||
for(var ID in IDs) { | ||
IDs[ID].removeAttr('id'); | ||
} | ||
@@ -159,0 +158,0 @@ |
@@ -21,3 +21,3 @@ 'use strict'; | ||
cm: 96/2.54, | ||
mm: 9600/2.54, | ||
mm: 96/25.4, | ||
in: 96, | ||
@@ -24,0 +24,0 @@ pt: 4/3, |
@@ -20,3 +20,3 @@ 'use strict'; | ||
cm: 96/2.54, | ||
mm: 9600/2.54, | ||
mm: 96/25.4, | ||
in: 96, | ||
@@ -23,0 +23,0 @@ pt: 4/3, |
@@ -12,2 +12,6 @@ 'use strict'; | ||
applyTransformsStroked: true, | ||
makeArcs: { | ||
threshold: 2.5, // coefficient of rounding error | ||
tolerance: 0.5 // percentage of radius | ||
}, | ||
straightCurves: true, | ||
@@ -32,2 +36,4 @@ lineShorthands: true, | ||
error, | ||
arcThreshold, | ||
arcTolerance, | ||
hasMarkerMid; | ||
@@ -57,2 +63,6 @@ | ||
error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2; | ||
if (params.makeArcs) { | ||
arcThreshold = params.makeArcs.threshold; | ||
arcTolerance = params.makeArcs.tolerance; | ||
} | ||
hasMarkerMid = item.hasAttr('marker-mid'); | ||
@@ -258,14 +268,17 @@ | ||
var relSubpoint = [0, 0], | ||
var stringify = data2Path.bind(null, params), | ||
relSubpoint = [0, 0], | ||
pathBase = [0, 0], | ||
prev = {}; | ||
path = path.filter(function(item, index) { | ||
path = path.filter(function(item, index, path) { | ||
var instruction = item.instruction, | ||
data = item.data; | ||
data = item.data, | ||
next = path[index + 1]; | ||
if (data) { | ||
var sdata; | ||
var sdata = data, | ||
circle; | ||
@@ -286,2 +299,107 @@ if (instruction === 's') { | ||
// convert curves to arcs if possible | ||
if ( | ||
params.makeArcs && | ||
(instruction == 'c' || instruction == 's') && | ||
isConvex(sdata) && | ||
(circle = findCircle(sdata)) | ||
) { | ||
var r = roundData([circle.radius])[0], | ||
angle = findArcAngle(sdata), | ||
sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0, | ||
arc = { | ||
instruction: 'a', | ||
data: [r, r, 0, 0, sweep, sdata[4], sdata[5]], | ||
coords: item.coords.slice(), | ||
base: item.base | ||
}, | ||
output = [arc], | ||
// relative coordinates to adjust the found circle | ||
relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]], | ||
relCircle = { center: relCenter, radius: circle.radius }, | ||
arcCurves = [item], | ||
hasPrev = 0, | ||
suffix = '', | ||
nextLonghand; | ||
if ( | ||
prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) || | ||
prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle) | ||
) { | ||
arcCurves.unshift(prev); | ||
arc.base = prev.base; | ||
arc.data[5] = arc.coords[0] - arc.base[0]; | ||
arc.data[6] = arc.coords[1] - arc.base[1]; | ||
angle += findArcAngle(prev.instruction == 'a' ? prev.sdata : prev.data); | ||
if (angle > Math.PI) arc.data[3] = 1; | ||
hasPrev = 1; | ||
} | ||
// check if next curves are fitting the arc | ||
for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) { | ||
var nextData = next.data; | ||
if (next.instruction == 's') { | ||
nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() }, | ||
path[j - 1].data); | ||
nextData = nextLonghand.data; | ||
nextLonghand.data = nextData.slice(0, 2); | ||
suffix = stringify([nextLonghand]); | ||
} | ||
if (isConvex(nextData) && isArc(nextData, relCircle)) { | ||
angle += findArcAngle(nextData); | ||
if (angle - 2 * Math.PI > 1e-3) break; // more than 360° | ||
if (angle > Math.PI) arc.data[3] = 1; | ||
arcCurves.push(next); | ||
if (2 * Math.PI - angle > 1e-3) { // less than 360° | ||
arc.coords = next.coords; | ||
arc.data[5] = arc.coords[0] - arc.base[0]; | ||
arc.data[6] = arc.coords[1] - arc.base[1]; | ||
} else { | ||
// full circle, make a half-circle arc and add a second one | ||
arc.data[5] = 2 * (relCircle.center[0] - nextData[4]); | ||
arc.data[6] = 2 * (relCircle.center[1] - nextData[5]); | ||
arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]]; | ||
arc = { | ||
instruction: 'a', | ||
data: [r, r, 0, 0, sweep, | ||
next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]], | ||
coords: next.coords, | ||
base: arc.coords | ||
}; | ||
output.push(arc); | ||
j++; | ||
break; | ||
} | ||
relCenter[0] -= nextData[4]; | ||
relCenter[1] -= nextData[5]; | ||
} else break; | ||
} | ||
if ((stringify(output) + suffix).length < stringify(arcCurves).length) { | ||
if (path[j] && path[j].instruction == 's') { | ||
makeLonghand(path[j], path[j - 1].data); | ||
} | ||
if (hasPrev) { | ||
var prevArc = output.shift(); | ||
roundData(prevArc.data); | ||
relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2]; | ||
relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1]; | ||
prev.instruction = 'a'; | ||
prev.data = prevArc.data; | ||
item.base = prev.coords = prevArc.coords; | ||
} | ||
arc = output.shift(); | ||
if (arcCurves.length == 1) { | ||
item.sdata = sdata.slice(); // preserve curve data for future checks | ||
} else if (arcCurves.length - 1 - hasPrev > 0) { | ||
// filter out consumed next items | ||
path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output)); | ||
} | ||
if (!arc) return false; | ||
instruction = 'a'; | ||
data = arc.data; | ||
item.coords = arc.coords; | ||
} | ||
} | ||
// Rounding relative coordinates, taking in account accummulating error | ||
@@ -322,10 +440,10 @@ // to get closer to absolute coordinates. Sum of rounded value remains same: | ||
// c | ||
if ( | ||
instruction === 'c' && | ||
isCurveStraightLine( | ||
[ 0, data[0], data[2], data[4] ], | ||
[ 0, data[1], data[3], data[5] ] | ||
) | ||
isCurveStraightLine(data) || | ||
instruction === 's' && | ||
isCurveStraightLine(sdata) | ||
) { | ||
if (next && next.instruction == 's') | ||
makeLonghand(next, data); // fix up next curve | ||
instruction = 'l'; | ||
@@ -335,10 +453,8 @@ data = data.slice(-2); | ||
// s | ||
else if ( | ||
instruction === 's' && | ||
isCurveStraightLine( | ||
[ 0, sdata[0], sdata[2], sdata[4] ], | ||
[ 0, sdata[1], sdata[3], sdata[5] ] | ||
) | ||
instruction === 'q' && | ||
isCurveStraightLine(data) | ||
) { | ||
if (next && next.instruction == 't') | ||
makeLonghand(next, data); // fix up next curve | ||
instruction = 'l'; | ||
@@ -348,16 +464,7 @@ data = data.slice(-2); | ||
// q | ||
else if ( | ||
instruction === 'q' && | ||
isCurveStraightLine( | ||
[ 0, data[0], data[2] ], | ||
[ 0, data[1], data[3] ] | ||
) | ||
instruction === 't' && | ||
prev.instruction !== 'q' && | ||
prev.instruction !== 't' | ||
) { | ||
// save the original one for the future potential q + t conversion | ||
item.original = { | ||
instruction: instruction, | ||
data: data | ||
}; | ||
instruction = 'l'; | ||
@@ -367,30 +474,2 @@ data = data.slice(-2); | ||
else if (instruction === 't') { | ||
// q (original) + t | ||
if ( | ||
prev.original && | ||
prev.original.instruction === 'q' | ||
) { | ||
if (isCurveStraightLine( | ||
[ prev.original.data[0], prev.original.data[2], data[0] ], | ||
[ prev.original.data[1], prev.original.data[3], data[1] ] | ||
)) { | ||
instruction = 'l'; | ||
data = data.slice(-2); | ||
} else { | ||
prev.instruction = 'q'; | ||
prev.data = prev.original.data; | ||
} | ||
} | ||
// [^qt] + t | ||
else if ('qt'.indexOf(prev.instruction) < 0) { | ||
instruction = 'l'; | ||
data = data.slice(-2); | ||
} | ||
} | ||
// a | ||
else if ( | ||
@@ -438,3 +517,2 @@ instruction === 'a' && | ||
prev.coords = item.coords; | ||
if (prev.original) prev.original = null; | ||
path[index] = prev; | ||
@@ -609,3 +687,3 @@ return false; | ||
absoluteDataStr.length == relativeDataStr.length - 1 && | ||
(data[0] < 0 || 0 < data[0] && data[0] < 1 && prev.data[prev.data.length - 1] % 1) | ||
(data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1) | ||
) | ||
@@ -628,2 +706,55 @@ ) { | ||
/** | ||
* Checks if curve is convex. Control points of such a curve must form | ||
* a convex quadrilateral with diagonals crosspoint inside of it. | ||
* | ||
* @param {Array} data input path data | ||
* @return {Boolean} output | ||
*/ | ||
function isConvex(data) { | ||
var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]); | ||
return center && | ||
(data[2] < center[0] == center[0] < 0) && | ||
(data[3] < center[1] == center[1] < 0) && | ||
(data[4] < center[0] == center[0] < data[0]) && | ||
(data[5] < center[1] == center[1] < data[1]); | ||
} | ||
/** | ||
* Computes lines equations by two points and returns their intersection point. | ||
* | ||
* @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y) | ||
* @return {Array|undefined} output coordinate of lines' crosspoint | ||
*/ | ||
function getIntersection(coords) { | ||
// Prev line equation parameters. | ||
var a1 = coords[1] - coords[3], // y1 - y2 | ||
b1 = coords[2] - coords[0], // x2 - x1 | ||
c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1 | ||
// Next line equation parameters | ||
a2 = coords[5] - coords[7], // y1 - y2 | ||
b2 = coords[6] - coords[4], // x2 - x1 | ||
c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1 | ||
denom = (a1 * b2 - a2 * b1); | ||
if (!denom) return; // parallel lines havn't an intersection | ||
var cross = [ | ||
(b1 * c2 - b2 * c1) / denom, | ||
(a1 * c2 - a2 * c1) / -denom | ||
]; | ||
if ( | ||
!isNaN(cross[0]) && !isNaN(cross[1]) && | ||
isFinite(cross[0]) && isFinite(cross[1]) | ||
) { | ||
return cross; | ||
} | ||
} | ||
/** | ||
* Decrease accuracy of floating-point numbers | ||
@@ -671,16 +802,15 @@ * in path data keeping a specified number of decimals. | ||
function isCurveStraightLine(xs, ys) { | ||
function isCurveStraightLine(data) { | ||
// Get line equation a·x + b·y + c = 0 coefficients a, b, c by start and end points. | ||
var i = xs.length - 1, | ||
a = ys[0] - ys[i], // y1 − y2 | ||
b = xs[i] - xs[0], // x2 − x1 | ||
c = xs[0] * ys[i] - xs[i] * ys[0], // x1·y2 − x2·y1 | ||
// Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points. | ||
var i = data.length - 2, | ||
a = -data[i + 1], // y1 − y2 (y1 = 0) | ||
b = data[i], // x2 − x1 (x1 = 0) | ||
d = 1 / (a * a + b * b); // same part for all points | ||
if (!isFinite(d)) return false; // curve that ends at start point isn't the case | ||
if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case | ||
// Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²)) | ||
while (--i) { | ||
if (Math.sqrt(Math.pow(c - a * xs[i] - b * ys[i], 2) * d) > error) | ||
while ((i -= 2) >= 0) { | ||
if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error) | ||
return false; | ||
@@ -692,1 +822,141 @@ } | ||
} | ||
/** | ||
* Converts next curve from shorthand to full form using the current curve data. | ||
* | ||
* @param {Object} item curve to convert | ||
* @param {Array} data current curve data | ||
*/ | ||
function makeLonghand(item, data) { | ||
switch (item.instruction) { | ||
case 's': item.instruction = 'c'; break; | ||
case 't': item.instruction = 'q'; break; | ||
} | ||
item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]); | ||
return item; | ||
} | ||
/** | ||
* Returns distance between two points | ||
* | ||
* @param {Array} point1 first point coordinates | ||
* @param {Array} point2 second point coordinates | ||
* @return {Number} distance | ||
*/ | ||
function getDistance(point1, point2) { | ||
return Math.sqrt(Math.pow(point1[0] - point2[0], 2) + Math.pow(point1[1] - point2[1], 2)); | ||
} | ||
/** | ||
* Returns coordinates of the curve point corresponding to the certain t | ||
* a·(1 - t)³·p1 + b·(1 - t)²·t·p2 + c·(1 - t)·t²·p3 + d·t³·p4, | ||
* where pN are control points and p1 is zero due to relative coordinates. | ||
* | ||
* @param {Array} curve array of curve points coordinates | ||
* @param {Number} t parametric position from 0 to 1 | ||
* @return {Array} Point coordinates | ||
*/ | ||
function getCubicBezierPoint(curve, t) { | ||
var sqrT = t * t, | ||
cubT = sqrT * t, | ||
mt = 1 - t, | ||
sqrMt = mt * mt; | ||
return [ | ||
3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4], | ||
3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5] | ||
]; | ||
} | ||
/** | ||
* Finds circle by 3 points of the curve and checks if the curve fits the found circle. | ||
* | ||
* @param {Array} curve | ||
* @return {Object|undefined} circle | ||
*/ | ||
function findCircle(curve) { | ||
var midPoint = getCubicBezierPoint(curve, 1/2), | ||
m1 = [midPoint[0] / 2, midPoint[1] / 2], | ||
m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2], | ||
center = getIntersection([ | ||
m1[0], m1[1], | ||
m1[0] + m1[1], m1[1] - m1[0], | ||
m2[0], m2[1], | ||
m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0]) | ||
]), | ||
radius = center && getDistance([0, 0], center), | ||
tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100); | ||
if (center && [1/4, 3/4].every(function(point) { | ||
return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance; | ||
})) | ||
return { center: center, radius: radius}; | ||
} | ||
/** | ||
* Checks if a curve fits the given circe. | ||
* | ||
* @param {Object} circle | ||
* @param {Array} curve | ||
* @return {Boolean} | ||
*/ | ||
function isArc(curve, circle) { | ||
var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100); | ||
return [0, 1/4, 1/2, 3/4, 1].every(function(point) { | ||
return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance; | ||
}); | ||
} | ||
/** | ||
* Checks if a previos curve fits the given circe. | ||
* | ||
* @param {Object} circle | ||
* @param {Array} curve | ||
* @return {Boolean} | ||
*/ | ||
function isArcPrev(curve, circle) { | ||
return isArc(curve, { | ||
center: [circle.center[0] + curve[4], circle.center[1] + curve[5]], | ||
radius: circle.radius | ||
}); | ||
} | ||
/** | ||
* Finds angle of an arc formed by a curve. | ||
* | ||
* @param {Array} curve | ||
* @return {Number} angle | ||
*/ | ||
function findArcAngle(curve) { | ||
var x1 = curve[0], | ||
y1 = curve[1], | ||
x2 = curve[2] - curve[4], | ||
y2 = curve[3] - curve[5]; | ||
return Math.PI - Math.acos( | ||
(x1 * x2 + y1 * y2 ) / | ||
Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) | ||
); | ||
} | ||
/** | ||
* Converts given path data to string. | ||
* | ||
* @param {Object} params | ||
* @param {Array} pathData | ||
* @return {String} | ||
*/ | ||
function data2Path(params, pathData) { | ||
return pathData.reduce(function(pathString, item) { | ||
return pathString += item.instruction + (item.data ? cleanupOutData(roundData(item.data.slice()), params) : ''); | ||
}, ''); | ||
} |
@@ -90,5 +90,2 @@ 'use strict'; | ||
var closePath = item.isElem('polygon') && | ||
(item.computedAttr('id') || (item.computedAttr('stroke') || 'none') != 'none'); | ||
item.addAttr({ | ||
@@ -98,3 +95,3 @@ name: 'd', | ||
'L' + coords.slice(2).join(' ') + | ||
(closePath ? 'z' : ''), | ||
(item.isElem('polygon') ? 'z' : ''), | ||
prefix: '', | ||
@@ -101,0 +98,0 @@ local: 'd' |
@@ -25,2 +25,3 @@ 'use strict'; | ||
var cleanupOutData = require('../lib/svgo/tools').cleanupOutData, | ||
EXTEND = require('whet.extend'), | ||
transform2js = require('./_transforms.js').transform2js, | ||
@@ -79,3 +80,3 @@ transformsMultiply = require('./_transforms.js').transformsMultiply, | ||
var data = transform2js(item.attr(attrName).value); | ||
definePrecision(data, params); | ||
params = definePrecision(data, params); | ||
@@ -89,5 +90,3 @@ if (params.collapseIntoOne && data.length > 1) { | ||
} else { | ||
data.forEach(function(transform) { | ||
transform = roundTransform(transform, params); | ||
}); | ||
data.forEach(roundTransform); | ||
} | ||
@@ -122,2 +121,5 @@ | ||
// Clone params so it don't affect other elements transformations. | ||
params = EXTEND({}, params); | ||
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value. | ||
@@ -137,5 +139,7 @@ if (matrixData.length) { | ||
floatRound = params.floatPrecision >= 1 ? smartRound.bind(this, params.floatPrecision) : round; | ||
degRound = params.degPrecision >= 1 ? smartRound.bind(this, params.degPrecision) : round; | ||
floatRound = params.floatPrecision >= 1 ? smartRound.bind(this, params.floatPrecision) : round; | ||
transformRound = params.transformPrecision >= 1 ? smartRound.bind(this, params.transformPrecision) : round; | ||
return params; | ||
} | ||
@@ -188,11 +192,5 @@ | ||
transform = roundTransform(transform, params); | ||
// fixed-point numbers | ||
// 12.754997 → 12.755 | ||
if (params.transformPrecision !== false) { | ||
transform.data = transform.data.map(function(num) { | ||
return +num.toFixed(params.transformPrecision); | ||
}); | ||
} | ||
roundTransform(transform); | ||
@@ -307,3 +305,3 @@ // convert long translate transform notation to the shorts one | ||
transformJS.forEach(function(transform) { | ||
transform = roundTransform(transform, params); | ||
roundTransform(transform); | ||
transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')'; | ||
@@ -310,0 +308,0 @@ }); |
@@ -42,2 +42,3 @@ 'use strict'; | ||
hasTransform = false, | ||
hasClip = item.hasAttr('clip-path'), | ||
intersected = item.content.every(function(inner) { | ||
@@ -66,3 +67,3 @@ if (inner.isElem() && inner.hasAttr()) { | ||
if (!allPath || name !== 'transform') { | ||
if (!allPath && !hasClip || name !== 'transform') { | ||
@@ -69,0 +70,0 @@ g.removeAttr(name); |
@@ -77,3 +77,4 @@ 'use strict'; | ||
!item.isEmpty() && | ||
elems[elem].content | ||
elems[elem] && //make sure we know of this element before checking its children | ||
elem !== 'foreignObject'//Don't check foreignObject | ||
) { | ||
@@ -83,4 +84,13 @@ item.content.forEach(function(content, i) { | ||
content.isElem() && | ||
!content.prefix && | ||
elems[elem].content.indexOf(content.elem) === -1 | ||
!content.prefix && | ||
( | ||
( | ||
elems[elem].content && // Do we have a record of its permitted content? | ||
elems[elem].content.indexOf(content.elem) === -1 | ||
) || | ||
( | ||
!elems[elem].content && // we dont know about its permitted content | ||
!elems[content.elem] // check that we know about the element at all | ||
) | ||
) | ||
) { | ||
@@ -93,3 +103,3 @@ item.content.splice(i, 1); | ||
// remove element's unknown attrs and attrs with default values | ||
if (elems[elem].attrs) { | ||
if (elems[elem] && elems[elem].attrs) { | ||
@@ -96,0 +106,0 @@ item.eachAttr(function(attr) { |
@@ -36,2 +36,3 @@ **english** | [русский](https://github.com/svg/svgo/blob/master/README.ru.md) | ||
* [ [ cleanUpEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] remove or cleanup `enable-background` attribute when possible | ||
* [ [ minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js) ] minify `<style>` elements content with [CSSO](https://github.com/css/csso) | ||
* [ [ convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] convert styles into attributes | ||
@@ -58,2 +59,3 @@ * [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | ||
* [ [ addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) ] add classnames to an outer `<svg>` element (disabled by default) | ||
* [ [ removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) ] remove `<style>` elements (disabled by default) | ||
@@ -60,0 +62,0 @@ Want to know how it works and how to write your own plugin? [Of course you want to](https://github.com/svg/svgo/blob/master/docs/how-it-works/en.md). |
@@ -36,2 +36,3 @@ [english](https://github.com/svg/svgo/blob/master/README.md) | **русский** | ||
* [ [ cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] удаление или оптимизация атрибута `enable-background`, когда это возможно | ||
* [ [ minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js) ] уменьшает содержимое элементов `<style>` с помощью [CSSO](https://github.com/css/csso). | ||
* [ [ convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] конвертирование стилей в атрибуте `style` в отдельные svg-атрибуты | ||
@@ -58,2 +59,3 @@ * [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] конвертирование цветовых значений: из `rgb()` в `#rrggbb`, из `#rrggbb` в `#rgb` | ||
* [ [ addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) ] добавляет имена классов корневому элементу `<svg>` (выключено по умолчанию) | ||
* [ [ removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) ] удаляет элементы `<style>` (выключено по умолчанию) | ||
@@ -86,3 +88,3 @@ Хотите узнать, как это работает и как написать свой плагин? [Конечно же, да!](https://github.com/svg/svgo/blob/master/docs/how-it-works/ru.md). | ||
--pretty : Удобочитаемое форматирование SVG | ||
--show-plugins : доступные плагины | ||
--show-plugins : Доступные плагины | ||
@@ -89,0 +91,0 @@ Аргументы: |
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
507214
67
10787
149
7
8
6
+ Addedcsso@~1.4.1
+ Addedansi-regex@2.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedchalk@1.1.3(transitive)
+ Addedclap@1.2.3(transitive)
+ Addedcsso@1.4.4(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedesprima@2.7.3(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedinherit@2.2.7(transitive)
+ Addedjs-yaml@3.4.6(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedsupports-color@2.0.0(transitive)
- Removedesprima@2.2.0(transitive)
- Removedjs-yaml@3.3.1(transitive)
Updatedjs-yaml@~3.4.3
Updatedsax@~1.1.4