svgo
Advanced tools
Comparing version 0.5.1 to 0.5.2
@@ -0,1 +1,13 @@ | ||
### [ [>](https://github.com/svg/svgo/tree/v0.5.2) ] 0.5.2 / 24.05.2015 | ||
* Introduced new `transformPrecision` option for better image quality (defaults to 5) in “[convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js)” and “[convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js)” (for the purpose of applying transformations) plugins. | ||
* Matrix transformations now can be decomposed into a combination of few simple transforms like `translate`, `rotate`, `scale`. | ||
* Arcs (paths `arcto` command) are now correctly being transformed into another arcs without being converting to Bezier curves. | ||
* Fixed an issue with “[mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js)” failing to detect paths intersection in some cases. | ||
* Fixed a bug with “[removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js)” removing some paths, which was introduced in [v0.5.1](https://github.com/svg/svgo/tree/v0.5.1). | ||
* Fixed a bug with transformation having `rotate()` with optional parameters. | ||
* Patterns with inherited attributes are no longer being removed. | ||
* Styles are no longer being removed from `<desc>` (by @dennari). | ||
* SVGO no longer breaks during parsing. | ||
* Added `clone()` method to JSAPI (by @jakearchibald) | ||
### [ [>](https://github.com/svg/svgo/tree/v0.5.1) ] 0.5.1 / 30.03.2015 | ||
@@ -2,0 +14,0 @@ * added new command-line option to set precision in floating point numbers. |
@@ -6,3 +6,2 @@ 'use strict'; | ||
var JSAPI = module.exports = function(data, parentNode) { | ||
EXTEND(this, data); | ||
@@ -15,3 +14,37 @@ if (parentNode) { | ||
} | ||
}; | ||
/** | ||
* Perform a deep clone of this node. | ||
* | ||
* @return {Object} element | ||
*/ | ||
JSAPI.prototype.clone = function() { | ||
var node = this; | ||
var nodeData = {}; | ||
Object.keys(node).forEach(function(key) { | ||
if (key != 'content') { | ||
nodeData[key] = node[key]; | ||
} | ||
}); | ||
// Deep-clone node data | ||
// This is still faster than using EXTEND(true…) | ||
nodeData = JSON.parse(JSON.stringify(nodeData)); | ||
// parentNode gets set to a proper object by the parent clone, | ||
// but it needs to be true/false now to do the right thing | ||
// in the constructor. | ||
var clonedNode = new JSAPI(nodeData, !!node.parentNode); | ||
if (node.content) { | ||
clonedNode.content = node.content.map(function(childNode) { | ||
var clonedChild = childNode.clone(); | ||
clonedChild.parentNode = clonedNode; | ||
return clonedChild; | ||
}); | ||
} | ||
return clonedNode; | ||
}; | ||
@@ -18,0 +51,0 @@ |
@@ -140,3 +140,3 @@ 'use strict'; | ||
callback(root); | ||
if (!this.error) callback(root); | ||
@@ -153,7 +153,7 @@ }; | ||
while (start && !start.text) start = start.content[0]; | ||
if (start) start.text = start.text.replace(/^\s+/, ''); | ||
while (start && start.content && !start.text) start = start.content[0]; | ||
if (start && start.text) start.text = start.text.replace(/^\s+/, ''); | ||
while (end && !end.text) end = end.content[end.content.length - 1]; | ||
if (end) end.text = end.text.replace(/\s+$/, ''); | ||
while (end && end.content && !end.text) end = end.content[end.content.length - 1]; | ||
if (end && end.text) end.text = end.text.replace(/\s+$/, ''); | ||
@@ -160,0 +160,0 @@ return elem; |
{ | ||
"name": "svgo", | ||
"version": "0.5.1", | ||
"version": "0.5.2", | ||
"description": "Nodejs-based tool for optimizing SVG vector graphics files", | ||
@@ -42,13 +42,13 @@ "keywords": [ "svgo", "svg", "optimize", "minify" ], | ||
"dependencies": { | ||
"sax": "~0.6.1", | ||
"sax": "~1.1.1", | ||
"coa": "~1.0.1", | ||
"js-yaml": "~3.2.3", | ||
"colors": "~1.0.3", | ||
"js-yaml": "~3.3.1", | ||
"colors": "~1.1.0", | ||
"whet.extend": "~0.9.9", | ||
"mkdirp": "~0.5.0" | ||
"mkdirp": "~0.5.1" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~2.2.1", | ||
"should": "~5.2.0", | ||
"istanbul": "~0.3.11", | ||
"mocha": "~2.2.5", | ||
"should": "6.0.3", | ||
"istanbul": "~0.3.14", | ||
"mocha-istanbul": "~0.2.0", | ||
@@ -60,6 +60,3 @@ "coveralls": "~2.11.2" | ||
}, | ||
"licenses": [{ | ||
"type": "MIT", | ||
"url": "https://raw.github.com/svg/svgo/master/LICENSE" | ||
}] | ||
"license": "MIT" | ||
} |
@@ -8,2 +8,3 @@ 'use strict'; | ||
transformsMultiply = require('./_transforms').transformsMultiply, | ||
transformArc = require('./_transforms').transformArc, | ||
collections = require('./_collections.js'), | ||
@@ -171,7 +172,6 @@ referencesProps = collections.referencesProps, | ||
* @param {Array} path input path data | ||
* @param {Boolean} applyTransformsStroked whether to apply transforms to stroked lines. | ||
* @param {Number} floatPrecision precision (used for stroke width) | ||
* @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width) | ||
* @return {Array} output path data | ||
*/ | ||
exports.applyTransforms = function(elem, path, applyTransformsStroked, floatPrecision) { | ||
exports.applyTransforms = function(elem, path, params) { | ||
// if there are no 'stroke' attr and references to other objects such as | ||
@@ -186,64 +186,29 @@ // gradiends or clip-path which are also subjects to transform. | ||
var matrix = transformsMultiply(transform2js(elem.attr('transform').value)), | ||
splittedMatrix = matrix.splitted || splitMatrix(matrix.data), | ||
stroke = elem.computedAttr('stroke'), | ||
newPoint, sx, sy; | ||
transformPrecision = params.transformPrecision, | ||
newPoint, scale; | ||
if (stroke && stroke.value != 'none'){ | ||
if (!applyTransformsStroked){ | ||
return path; | ||
} | ||
if (matrix.name == 'matrix'){ | ||
sx = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(floatPrecision); | ||
sy = +Math.sqrt(matrix.data[2] * matrix.data[2] + matrix.data[3] * matrix.data[3]).toFixed(floatPrecision); | ||
} else if (matrix.name == 'scale'){ | ||
sx = +matrix.data[0].toFixed(floatPrecision); | ||
sy = +matrix.data[1].toFixed(floatPrecision); | ||
} else { | ||
sx = 1; | ||
sy = 1; | ||
} | ||
if (stroke && stroke.value != 'none') { | ||
if (!params.applyTransformsStroked || | ||
(matrix.data[0] != matrix.data[3] || matrix.data[1] != -matrix.data[2]) && | ||
(matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2])) | ||
return path; | ||
if (sx !== sy){ | ||
return path; | ||
} | ||
if (sx !== 1){ | ||
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth; | ||
scale = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(transformPrecision); | ||
if (elem.hasAttr('stroke-width')){ | ||
elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim() | ||
.replace(regNumericValues, function(num) { return removeLeadingZero(num * sx) }); | ||
} else { | ||
elem.addAttr({ | ||
name: 'stroke-width', | ||
prefix: '', | ||
local: 'stroke-width', | ||
value: strokeWidth.replace(regNumericValues, function(num) { return removeLeadingZero(num * sx) }) | ||
}); | ||
} | ||
} | ||
} | ||
if (scale !== 1) { | ||
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth; | ||
// If an 'a' command can't be transformed directly, convert path to curves. | ||
if (!splittedMatrix.isSimple && path.some(function(i) { return i.instruction == 'a' })) { | ||
var prev; | ||
path.forEach(function(item, index, path){ | ||
if (item.instruction == 'a') { | ||
var curves = a2c.apply(0, [0, 0].concat(item.data)), | ||
items = [], | ||
curveData; | ||
while ((curveData = curves.splice(0,6)).length) { | ||
var base = prev.coords; | ||
items.push(prev = { | ||
instruction: 'c', | ||
data: curveData, | ||
coords: [base[0] + item.data[4], base[1] + item.data[5]], | ||
base: prev.coords | ||
}); | ||
} | ||
path.splice.apply(path, [index, 1].concat(items)); | ||
if (elem.hasAttr('stroke-width')) { | ||
elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim() | ||
.replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) }); | ||
} else { | ||
if (prev) item.base = prev.coords; | ||
prev = item; | ||
elem.addAttr({ | ||
name: 'stroke-width', | ||
prefix: '', | ||
local: 'stroke-width', | ||
value: strokeWidth.replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) }) | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -289,5 +254,13 @@ | ||
pathItem.data[0] *= splittedMatrix.scalex; | ||
pathItem.data[1] *= splittedMatrix.scaley; | ||
pathItem.data[2] += splittedMatrix.rotate; | ||
transformArc(pathItem.data, matrix.data); | ||
// reduce number of digits in rotation angle | ||
if (Math.abs(pathItem.data[2]) > 80) { | ||
var a = pathItem.data[0], | ||
rotation = pathItem.data[2]; | ||
pathItem.data[0] = pathItem.data[1]; | ||
pathItem.data[1] = a; | ||
pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90); | ||
} | ||
newPoint = transformPoint(matrix.data, pathItem.data[5], pathItem.data[6]); | ||
@@ -572,7 +545,12 @@ pathItem.data[5] = newPoint[0]; | ||
// concat previous data with current | ||
prev = newPath[prevIndex] = { | ||
instruction: prev.instruction, | ||
data: prev.data.concat(item.data), | ||
coords: item.coords, | ||
base: prev.base | ||
if (item.instruction != 'M') { | ||
prev = newPath[prevIndex] = { | ||
instruction: prev.instruction, | ||
data: prev.data.concat(item.data), | ||
coords: item.coords, | ||
base: prev.base | ||
} | ||
} else { | ||
prev.data = item.data; | ||
prev.coords = item.coords; | ||
} | ||
@@ -607,3 +585,3 @@ } else { | ||
*/ | ||
exports.interesects = function(path1, path2) { | ||
exports.intersects = function(path1, path2) { | ||
if (path1.length < 3 || path2.length < 3) return false; // nothing to fill | ||
@@ -662,18 +640,16 @@ | ||
// Compute farthest polygon point in particular direction. | ||
// Computes farthest polygon point in particular direction. | ||
// Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in. | ||
// Since we're working on convex hull, the dot product is increasing until we find the farthest point. | ||
function supportPoint(polygon, direction) { | ||
// Choose a quadrant to search in. In the worst case only one quadrant would be iterated. | ||
var index = direction[1] >= 0 ? | ||
direction[0] < 0 ? polygon.maxY : polygon.maxX : // [1, 0] lands right on maxX | ||
direction[0] < 0 ? polygon.maxY : polygon.maxX : | ||
direction[0] < 0 ? polygon.minX : polygon.minY, | ||
max = dot(polygon[index], direction); | ||
for (var i = index; i < polygon.length; i++) { | ||
var value = dot(polygon[i], direction); | ||
if (value >= max) { | ||
index = i; | ||
max = value; | ||
} else break; // surely we've found the maximum since we've choosen a quadrant | ||
max = -Infinity, | ||
value; | ||
while ((value = dot(polygon[index], direction)) > max) { | ||
max = value; | ||
index = ++index % polygon.length; | ||
} | ||
return polygon[index]; | ||
return polygon[(index || polygon.length) - 1]; | ||
} | ||
@@ -843,3 +819,4 @@ }; | ||
var lower = [], | ||
minY = 0; | ||
minY = 0, | ||
bottom = 0; | ||
for (var i = 0; i < points.length; i++) { | ||
@@ -850,3 +827,4 @@ while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) { | ||
if (points[i][1] < points[minY][1]) { | ||
minY = lower.length; | ||
minY = i; | ||
bottom = lower.length; | ||
} | ||
@@ -857,4 +835,5 @@ lower.push(points[i]); | ||
var upper = [], | ||
maxY = points.length - 1; | ||
for (var i = points.length ; i--;) { | ||
maxY = points.length - 1, | ||
top = 0; | ||
for (var i = points.length; i--;) { | ||
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) { | ||
@@ -864,3 +843,4 @@ upper.pop(); | ||
if (points[i][1] > points[maxY][1]) { | ||
maxY = upper.length; | ||
maxY = i; | ||
top = upper.length; | ||
} | ||
@@ -878,4 +858,4 @@ upper.push(points[i]); | ||
hull.maxX = lower.length; | ||
hull.minY = minY; | ||
hull.maxY = (lower.length + maxY) % hull.length; | ||
hull.minY = bottom; | ||
hull.maxY = (lower.length + top) % hull.length; | ||
@@ -893,63 +873,2 @@ return hull; | ||
function norm(a) { | ||
return a[0] * a[0] + a[1] * a[1]; | ||
} | ||
function normalize(a) { | ||
var mag = Math.sqrt(norm(a)); | ||
if (a[0]) a[0] /= mag; | ||
if (a[1]) a[1] /= mag; | ||
} | ||
function deg(rad) { | ||
return rad * 180 / Math.PI % 360; | ||
} | ||
function determinant(matrix) { | ||
return matrix[0] * matrix[3] - matrix[1] * matrix[2]; | ||
} | ||
/* Splits matrix into primitive transformations | ||
= (object) in format: | ||
o dx (number) translation by x | ||
o dy (number) translation by y | ||
o scalex (number) scale by x | ||
o scaley (number) scale by y | ||
o shear (number) shear | ||
o rotate (number) rotation in deg | ||
o isSimple (boolean) could it be represented via simple transformations | ||
*/ | ||
function splitMatrix(matrix) { | ||
var out = {}; | ||
// translation | ||
out.dx = matrix[4]; | ||
out.dy = matrix[5]; | ||
// scale and shear | ||
var row = [[matrix[0] , matrix[2] ], [matrix[1] , matrix[3]]]; | ||
out.scalex = Math.sqrt(norm(row[0])); | ||
normalize(row[0]); | ||
out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; | ||
row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; | ||
out.scaley = Math.sqrt(norm(row[1])); | ||
normalize(row[1]); | ||
out.shear /= out.scaley; | ||
if (determinant(matrix) < 0) { | ||
out.scalex = -out.scalex; | ||
} | ||
// rotation | ||
var sin = -row[0][1], | ||
cos = row[1][1]; | ||
if (cos < 0) { | ||
out.rotate = deg(Math.acos(cos)); | ||
if (sin < 0) { | ||
out.rotate = 360 - out.rotate; | ||
} | ||
} else { | ||
out.rotate = deg(Math.asin(sin)); | ||
} | ||
out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); | ||
return out; | ||
} | ||
function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { | ||
@@ -956,0 +875,0 @@ // for more information of where this Math came from visit: |
@@ -28,5 +28,3 @@ 'use strict'; | ||
// then collect it and change current context | ||
transforms.push(current = { | ||
name: item | ||
}); | ||
transforms.push(current = { name: item }); | ||
// else if item is data | ||
@@ -36,15 +34,2 @@ } else { | ||
current.data = item.split(regTransformDataSplit).map(Number); | ||
// 'rotate' has optional parameters <cx> <cy> which specify the point to rotate about. | ||
// equivalent to 'translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>)' | ||
if (current.name == 'rotate' && current.data.length == 3) { | ||
transforms.push({ | ||
name: 'translate', | ||
data: [-current.data[1], -current.data[2]] | ||
}); | ||
transforms.splice(transforms.length - 2, 0, { | ||
name: 'translate', | ||
data: current.data.splice(1) | ||
}); | ||
} | ||
} | ||
@@ -67,25 +52,6 @@ } | ||
var simple = true, | ||
scalex = 1, | ||
scaley = 1, | ||
rotate = 0; | ||
// convert transforms objects to the matrices | ||
transforms = transforms.map(function(transform) { | ||
if (transform.name === 'matrix') { | ||
simple = false; | ||
return transform.data; | ||
} else if (simple) { | ||
if (transform.name == 'scale') { | ||
scalex *= transform.data[0]; | ||
scaley *= transform.data[1]; | ||
} else if (transform.name == 'rotate') { | ||
if (scalex.toFixed(9) == scaley.toFixed(9)) { | ||
rotate += transform.data[0]; | ||
} else { | ||
simple = false; | ||
} | ||
} else if (transform.name != 'translate') { | ||
simple = false; | ||
} | ||
} | ||
@@ -103,11 +69,2 @@ return transformToMatrix(transform); | ||
if (simple) { | ||
transforms.splitted = { | ||
scalex: scalex, | ||
scaley: scaley, | ||
rotate: rotate, | ||
isSimple: true | ||
} | ||
} | ||
return transforms; | ||
@@ -159,73 +116,75 @@ | ||
/** | ||
* Convert matrix data to the transform alias. | ||
* Decompose matrix into simple transforms. See | ||
* http://www.maths-informatique-jeux.com/blog/frederic/?post/2013/12/01/Decomposition-of-2D-transform-matrices | ||
* | ||
* @param {Object} data matrix transform object | ||
* @return {Object} transform object | ||
* @return {Object|Array} transforms array or original transform object | ||
*/ | ||
exports.matrixToTransform = function(transform, params) { | ||
var floatPrecision = params.floatPrecision, | ||
data = transform.data, | ||
transforms = [], | ||
sx = +Math.sqrt(data[0] * data[0] + data[1] * data[1]).toFixed(params.transformPrecision), | ||
sy = +((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(params.transformPrecision), | ||
colsSum = data[0] * data[2] + data[1] * data[3], | ||
rowsSum = data[0] * data[1] + data[2] * data[3], | ||
scaleBefore = rowsSum || +(sx == sy); | ||
var data = transform.data; | ||
// [..., ..., ..., ..., tx, ty] → translate(tx, ty) | ||
if (data[4] || data[5]) { | ||
transforms.push({ name: 'translate', data: data.slice(4, data[5] ? 6 : 5) }); | ||
} | ||
// [1, 0, 0, 1, tx, ty] → translate(tx, ty) | ||
if ( | ||
data[0] === 1 && | ||
data[1] === 0 && | ||
data[2] === 0 && | ||
data[3] === 1 | ||
) { | ||
transform.name = 'translate'; | ||
transform.data = [data[4], data[5]]; | ||
// [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy) | ||
if (!data[1] && data[2]) { | ||
transforms.push({ name: 'skewX', data: [mth.atan(data[2] / sy, floatPrecision)] }); | ||
// [sx, 0, 0, sy, 0, 0] → scale(sx, sy) | ||
} else if ( | ||
data[1] === 0 && | ||
data[2] === 0 && | ||
data[4] === 0 && | ||
data[5] === 0 | ||
) { | ||
transform.name = 'scale'; | ||
transform.data = [data[0], data[3]]; | ||
// [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy) | ||
} else if (data[1] && !data[2]) { | ||
transforms.push({ name: 'skewY', data: [mth.atan(data[1] / data[0], floatPrecision)] }); | ||
sx = data[0]; | ||
sy = data[3]; | ||
// [cos(a), sin(a), -sin(a), cos(a), 0 0] → rotate(a) | ||
} else if ( | ||
data[0] === data[3] && | ||
data[1] === -data[2] && | ||
data[4] === 0 && | ||
data[5] === 0 | ||
) { | ||
var a1 = mth.acos(data[0], params.floatPrecision), | ||
a2 = mth.asin(data[1], params.floatPrecision); | ||
// [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or | ||
// [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore) | ||
} else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) { | ||
if (!scaleBefore) { | ||
sx = Math.sqrt(data[0] * data[0] + data[2] * data[2]); | ||
sy = Math.sqrt(data[1] * data[1] + data[3] * data[3]); | ||
transforms.push({ name: 'scale', data: [sx, sy] }); | ||
} | ||
var a1 = mth.acos(data[0] / sx, floatPrecision), | ||
a2 = mth.asin(data[1] / (rowsSum ? sx : sy), floatPrecision), | ||
rotate = [a1.toFixed(floatPrecision) * (data[1] < 0 ? -1 : 1)]; | ||
a1 = a2 < 0 ? -a1 : a1; | ||
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate }); | ||
if (Math.round(a1) === Math.round(a2)) { | ||
transform.name = 'rotate'; | ||
transform.data = [a1]; | ||
if (rowsSum && colsSum) transforms.push({ | ||
name: 'skewX', | ||
data: [mth.atan(colsSum / (sx * sx), floatPrecision)] | ||
}) | ||
// rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point) | ||
if (rotate[0] && (data[4] || data[5])) { | ||
transforms.shift(); | ||
var cos = data[0] / sx, | ||
sin = data[1] / (scaleBefore ? sx : sy), | ||
x = data[4] * (scaleBefore || sy), | ||
y = data[5] * (scaleBefore || sx), | ||
denom = (Math.pow(1 - cos, 2) + Math.pow(sin, 2)) * (scaleBefore || sx * sy); | ||
rotate.push(((1 - cos) * x - sin * y) / denom); | ||
rotate.push(((1 - cos) * y + sin * x) / denom); | ||
} | ||
// [1, 0, tan(a), 1, 0, 0] → skewX(a) | ||
} else if ( | ||
data[0] === 1 && | ||
data[1] === 0 && | ||
data[3] === 1 && | ||
data[4] === 0 && | ||
data[5] === 0 | ||
) { | ||
transform.name = 'skewX'; | ||
transform.data = [mth.atan(data[2], params.floatPrecision)]; | ||
// [1, tan(a), 0, 1, 0, 0] → skewY(a) | ||
} else if ( | ||
data[0] === 1 && | ||
data[2] === 0 && | ||
data[3] === 1 && | ||
data[4] === 0 && | ||
data[5] === 0 | ||
) { | ||
transform.name = 'skewY'; | ||
transform.data = [mth.atan(data[1], params.floatPrecision)]; | ||
// Too many transformations, return original matrix if it isn't just a scale/translate | ||
} else if (data[1] || data[2]) { | ||
return transform; | ||
} | ||
return transform; | ||
if (scaleBefore && (sx != 1 || sy != 1) || !transforms.length) transforms.push({ | ||
name: 'scale', | ||
data: sx == sy ? [sx] : [sx, sy] | ||
}); | ||
return transforms; | ||
}; | ||
@@ -239,3 +198,3 @@ | ||
*/ | ||
var transformToMatrix = exports.transformToMatrix = function(transform) { | ||
function transformToMatrix(transform) { | ||
@@ -246,3 +205,3 @@ if (transform.name === 'matrix') return transform.data; | ||
switch(transform.name) { | ||
switch (transform.name) { | ||
case 'translate': | ||
@@ -257,7 +216,9 @@ // [1, 0, 0, 1, tx, ty] | ||
case 'rotate': | ||
// [cos(a), sin(a), -sin(a), cos(a), 0, 0] | ||
// [cos(a), sin(a), -sin(a), cos(a), x, y] | ||
var cos = mth.cos(transform.data[0]), | ||
sin = mth.sin(transform.data[0]); | ||
sin = mth.sin(transform.data[0]), | ||
cx = transform.data[1] || 0, | ||
cy = transform.data[2] || 0; | ||
matrix = [cos, sin, -sin, cos, 0, 0]; | ||
matrix = [cos, sin, -sin, cos, (1 - cos) * cx + sin * cy, (1 - cos) * cy - sin * cx]; | ||
break; | ||
@@ -279,2 +240,56 @@ case 'skewX': | ||
/** | ||
* Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it | ||
* by the transformation matrix and use a singular value decomposition to represent in a form | ||
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ. | ||
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}}) | ||
* | ||
* @param {Array} arc [a, b, rotation in deg] | ||
* @param {Array} transform transformation matrix | ||
* @return {Array} arc transformed input arc | ||
*/ | ||
exports.transformArc = function(arc, transform) { | ||
var a = arc[0], | ||
b = arc[1], | ||
rot = arc[2] * Math.PI / 180, | ||
cos = Math.cos(rot), | ||
sin = Math.sin(rot), | ||
h = Math.pow(arc[5] * cos - arc[6] * sin, 2) / (4 * a * a) + | ||
Math.pow(arc[5] * sin + arc[6] * cos, 2) / (4 * b * b); | ||
if (h > 1) { | ||
h = Math.sqrt(h); | ||
a *= h; | ||
b *= h; | ||
} | ||
var ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0], | ||
m = multiplyTransformMatrices(transform, ellipse), | ||
// Decompose the new ellipse matrix | ||
lastCol = m[2] * m[2] + m[3] * m[3], | ||
squareSum = m[0] * m[0] + m[1] * m[1] + lastCol, | ||
root = Math.sqrt( | ||
(Math.pow(m[0] - m[3], 2) + Math.pow(m[1] + m[2], 2)) * | ||
(Math.pow(m[0] + m[3], 2) + Math.pow(m[1] - m[2], 2)) | ||
); | ||
if (!root) { // circle | ||
arc[0] = arc[1] = Math.sqrt(squareSum / 2); | ||
arc[2] = 0; | ||
} else { | ||
var majorAxisSqr = (squareSum + root) / 2, | ||
minorAxisSqr = (squareSum - root) / 2, | ||
major = Math.abs(majorAxisSqr - lastCol) > 1e-6, | ||
sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol, | ||
rowsSum = m[0] * m[2] + m[1] * m[3], | ||
term1 = m[0] * sub + m[2] * rowsSum, | ||
term2 = m[1] * sub + m[3] * rowsSum; | ||
arc[0] = Math.sqrt(majorAxisSqr); | ||
arc[1] = Math.sqrt(minorAxisSqr); | ||
arc[2] = ((major ? term2 < 0 : term1 > 0) ? -1 : 1) * | ||
Math.acos((major ? term1 : term2) / Math.sqrt(term1 * term1 + term2 * term2)) * 180 / Math.PI; | ||
} | ||
return arc; | ||
}; | ||
/** | ||
* Multiply transformation matrices. | ||
@@ -289,10 +304,10 @@ * | ||
return [ | ||
+(a[0] * b[0] + a[2] * b[1]).toFixed(3), | ||
+(a[1] * b[0] + a[3] * b[1]).toFixed(3), | ||
+(a[0] * b[2] + a[2] * b[3]).toFixed(3), | ||
+(a[1] * b[2] + a[3] * b[3]).toFixed(3), | ||
+(a[0] * b[4] + a[2] * b[5] + a[4]).toFixed(3), | ||
+(a[1] * b[4] + a[3] * b[5] + a[5]).toFixed(3) | ||
a[0] * b[0] + a[2] * b[1], | ||
a[1] * b[0] + a[3] * b[1], | ||
a[0] * b[2] + a[2] * b[3], | ||
a[1] * b[2] + a[3] * b[3], | ||
a[0] * b[4] + a[2] * b[5] + a[4], | ||
a[1] * b[4] + a[3] * b[5] + a[5] | ||
]; | ||
} |
@@ -14,2 +14,3 @@ 'use strict'; | ||
floatPrecision: 3, | ||
transformPrecision: 5, | ||
removeUseless: true, | ||
@@ -62,3 +63,3 @@ collapseRepeated: true, | ||
if (params.applyTransforms) { | ||
data = applyTransforms(item, data, params.applyTransformsStroked, params.floatPrecision); | ||
data = applyTransforms(item, data, params); | ||
} | ||
@@ -65,0 +66,0 @@ |
@@ -8,12 +8,14 @@ 'use strict'; | ||
exports.params = { | ||
convertToShorts: true, | ||
floatPrecision: 3, | ||
matrixToTransform: true, | ||
shortTranslate: true, | ||
shortScale: true, | ||
shortRotate: true, | ||
removeUseless: true, | ||
collapseIntoOne: true, | ||
leadingZero: true, | ||
negativeExtraSpace: false | ||
convertToShorts: true, | ||
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default | ||
floatPrecision: 3, | ||
transformPrecision: 5, | ||
matrixToTransform: true, | ||
shortTranslate: true, | ||
shortScale: true, | ||
shortRotate: true, | ||
removeUseless: true, | ||
collapseIntoOne: true, | ||
leadingZero: true, | ||
negativeExtraSpace: false | ||
}; | ||
@@ -71,7 +73,32 @@ | ||
function convertTransform(item, attrName, params) { | ||
var data = transform2js(item.attr(attrName).value), | ||
matrixData = data.reduce(function(a, b) { return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a }, []), | ||
degPrecision = params.floatPrecision, | ||
significantDigits = params.transformPrecision; | ||
var data = transform2js(item.attr(attrName).value); | ||
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value. | ||
if (matrixData.length) { | ||
params.transformPrecision = Math.min(params.transformPrecision, | ||
Math.max.apply(Math, matrixData.map(function(n) { | ||
return (n = String(n)).slice(n.indexOf('.')).length - 1; // Number of digits after point. 0.125 → 3 | ||
})) || params.transformPrecision); | ||
significantDigits = Math.max.apply(Math, matrixData.map(function(n) { | ||
return String(n).replace(/\D+/g, '').length; // Number of digits in number. 123.45 → 5 | ||
})); | ||
} | ||
// No sense in angle precision more then number of significant digits in matrix. | ||
if (!('degPrecision' in params)) { | ||
params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2)); | ||
} | ||
if (params.collapseIntoOne && data.length > 1) { | ||
data = [transformsMultiply(data)]; | ||
} | ||
if (params.convertToShorts) { | ||
data = convertToShorts(data, params); | ||
} else { | ||
data.forEach(function(transform) { | ||
transform = roundTransform(transform, params); | ||
}); | ||
} | ||
@@ -83,26 +110,3 @@ | ||
if ( | ||
params.collapseIntoOne && | ||
( | ||
data.length >= 3 || | ||
data.length == 2 && | ||
( | ||
data[0].name === 'matrix' || data[1].name === 'matrix' || | ||
data[0].name == data[1].name | ||
) | ||
) | ||
) { | ||
data = [transformsMultiply(data, params)]; | ||
if (params.matrixToTransform) { | ||
data = [matrixToTransform(data[0], params)]; | ||
} | ||
if (params.removeUseless) { | ||
data = removeUseless(data); | ||
} | ||
} | ||
item.attr(attrName).value = js2transform(data, params); | ||
} | ||
@@ -126,13 +130,20 @@ | ||
params.matrixToTransform && | ||
transforms.length < 3 && | ||
transform.name === 'matrix' | ||
) { | ||
transforms[i] = matrixToTransform(transform, params); | ||
var decomposed = matrixToTransform(transform, params); | ||
if (decomposed != transform && | ||
js2transform(decomposed, params).length <= js2transform([transform], params).length) { | ||
transforms.splice.apply(transforms, [i, 1].concat(decomposed)); | ||
} | ||
transform = transforms[i]; | ||
} | ||
transform = roundTransform(transform, params); | ||
// fixed-point numbers | ||
// 12.754997 → 12.755 | ||
if (params.floatPrecision !== false) { | ||
if (params.transformPrecision !== false) { | ||
transform.data = transform.data.map(function(num) { | ||
return +num.toFixed(params.floatPrecision); | ||
return +num.toFixed(params.transformPrecision); | ||
}); | ||
@@ -147,5 +158,5 @@ } | ||
transform.data.length === 2 && | ||
transform.data[1] === 0 | ||
!transform.data[1] | ||
) { | ||
transform.data = [transform.data[0]]; | ||
transform.data.pop(); | ||
} | ||
@@ -161,3 +172,3 @@ | ||
) { | ||
transform.data = [transform.data[0]]; | ||
transform.data.pop(); | ||
} | ||
@@ -207,19 +218,25 @@ | ||
// translate(0), rotate(0), skewX(0), skewY(0) | ||
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0) | ||
if ( | ||
['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 && | ||
(transform.data.length === 1 || transform.name === 'rotate') && | ||
transform.data[0] === 0 || | ||
transform.name === 'translate' && | ||
transform.data[0] === 0 && | ||
transform.data[1] === 0 | ||
(transform.data.length == 1 || transform.name == 'rotate') && | ||
!transform.data[0] || | ||
// translate(0, 0) | ||
transform.name == 'translate' && | ||
!transform.data[0] && | ||
!transform.data[1] || | ||
// scale(1) | ||
transform.name == 'scale' && | ||
transform.data[0] == 1 && | ||
(transform.data.length < 2 || transform.data[1] == 1) || | ||
// matrix(1 0 0 1 0 0) | ||
transform.name == 'matrix' && | ||
transform.data[0] == 1 && | ||
transform.data[3] == 1 && | ||
!(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5]) | ||
) { | ||
return false; | ||
// scale(1) | ||
} else if ( | ||
transform.name === 'scale' && | ||
transform.data.length === 1 && | ||
transform.data[0] === 1 | ||
) { | ||
return false; | ||
return false | ||
} | ||
@@ -246,5 +263,4 @@ | ||
transformJS.forEach(function(transform) { | ||
transformString += (transformString ? ' ' : '') + transform.name + '(' + cleanupOutData(transform.data, params) + ')'; | ||
transform = roundTransform(transform, params); | ||
transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')'; | ||
}); | ||
@@ -255,1 +271,52 @@ | ||
} | ||
function roundTransform(transform, params) { | ||
var floatRound = params.floatPrecision > 0 ? smartRound : round, | ||
transformRound = params.transformPrecision > 0 ? smartRound : round; | ||
switch (transform.name) { | ||
case 'translate': | ||
transform.data = floatRound(transform.data, params.floatPrecision); | ||
break; | ||
case 'rotate': | ||
transform.data = floatRound(transform.data.slice(0, 1), params.degPrecision) | ||
.concat(floatRound(transform.data.slice(1), params.floatPrecision)); | ||
break; | ||
case 'skewX': | ||
case 'skewY': | ||
transform.data = floatRound(transform.data, params.degPrecision); | ||
break; | ||
case 'scale': | ||
transform.data = transformRound(transform.data, params.transformPrecision); | ||
break; | ||
case 'matrix': | ||
transform.data = transformRound(transform.data.slice(0, 4), params.transformPrecision) | ||
.concat(floatRound(transform.data.slice(4), params.floatPrecision)); | ||
break; | ||
} | ||
return transform; | ||
} | ||
function round(data) { | ||
return data.map(Math.round); | ||
} | ||
/** | ||
* Decrease accuracy of floating-point numbers | ||
* in transforms keeping a specified number of decimals. | ||
* Smart rounds values like 2.349 to 2.35. | ||
* | ||
* @param {Array} data input data array | ||
* @param {Number} fixed number of decimals | ||
* @return {Array} output data array | ||
*/ | ||
function smartRound(data, precision) { | ||
for (var i = data.length, tolerance = Math.pow(.1, precision); i--;) { | ||
var rounded = +data[i].toFixed(precision - 1); | ||
data[i] = +Math.abs(rounded - data[i]).toFixed(precision) >= tolerance ? | ||
+data[i].toFixed(precision) : | ||
rounded; | ||
} | ||
return data; | ||
} |
@@ -15,3 +15,3 @@ 'use strict'; | ||
js2path = require('./_path.js').js2path, | ||
interesects = require('./_path.js').interesects; | ||
intersects = require('./_path.js').intersects; | ||
@@ -58,3 +58,3 @@ /** | ||
if (equalData && !interesects(prevPathJS, curPathJS)) { | ||
if (equalData && !intersects(prevPathJS, curPathJS)) { | ||
js2path(prevContentItem, prevPathJS.concat(curPathJS), params); | ||
@@ -61,0 +61,0 @@ return false; |
@@ -27,4 +27,5 @@ 'use strict'; | ||
return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty()); | ||
return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty() && | ||
(!item.isElem('pattern') || !item.hasAttr('xlink:href'))); | ||
}; |
@@ -24,3 +24,3 @@ 'use strict'; | ||
var regValidPath = /m.*[lhvcsqta]/i; | ||
var regValidPath = /M\s*(?:[+-]?(?:\d*\.\d+|\d+(?!\.\d)\.?)(?!\d)\s*,?\s*){2}\D*\d/i; | ||
@@ -27,0 +27,0 @@ /** |
@@ -37,3 +37,3 @@ "use strict"; | ||
if (item.hasAttr('id')) { | ||
if (item.hasAttr('id') || item.isElem('style')) { | ||
@@ -40,0 +40,0 @@ usefulItems.push(item); |
**english** | [русский](https://github.com/svg/svgo/blob/master/README.ru.md) | ||
- - - | ||
<img src="http://soulshine.in/svgo.svg" width="200" height="200" alt="logo"/> | ||
<img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/> | ||
@@ -21,35 +21,35 @@ ## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.png)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] cleanup attributes from newlines, trailing and repeating spaces | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] remove doctype declaration | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] remove XML processing instructions | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] remove comments | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] remove `<metadata>` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] remove `<title>` (disabled by default) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] remove `<desc>` (only non-meaningful by default) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] remove elements of `<defs>` without `id` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] remove editors namespaces, elements and attributes | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] remove empty attributes | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] remove hidden elements | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] remove empty Text elements | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] remove empty Container elements | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] remove `viewBox` attribute when possible (disabled by default) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] remove or cleanup `enable-background` attribute when possible | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] convert styles into attributes | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] convert Path data to relative or absolute whichever is shorter, convert one segment to another, trim useless delimiters, smart rounding and much more | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] collapse multiple transforms into one, convert matrices to the short aliases and much more | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] remove unknown elements content and attributes, remove attrs with default values | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] remove non-inheritable group's "presentation" attributes | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] remove useless stroke and fill attrs | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] remove unused namespaces declaration | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] remove unused and minify used IDs | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] round numeric values to the fixed precision, remove default 'px' units | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] move elements attributes to the existing group wrapper | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] move some group attributes to the content elements | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] collapse useless groups | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] remove raster images (disabled by default) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] merge multiple Paths into one | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] convert some basic shapes to path | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] sort element attributes for epic readability (disabled by default) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] apply transforms, crop by real width, center vertical alignment and resize SVG with one Path inside (disabled by default) | ||
* [ [ cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] cleanup attributes from newlines, trailing and repeating spaces | ||
* [ [ removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] remove doctype declaration | ||
* [ [ removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] remove XML processing instructions | ||
* [ [ removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] remove comments | ||
* [ [ removeMetaData](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] remove `<metadata>` | ||
* [ [ removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] remove `<title>` (disabled by default) | ||
* [ [ removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] remove `<desc>` (only non-meaningful by default) | ||
* [ [ removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] remove elements of `<defs>` without `id` | ||
* [ [ removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] remove editors namespaces, elements and attributes | ||
* [ [ removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] remove empty attributes | ||
* [ [ removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] remove hidden elements | ||
* [ [ removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] remove empty Text elements | ||
* [ [ removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] remove empty Container elements | ||
* [ [ removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] remove `viewBox` attribute when possible (disabled by default) | ||
* [ [ cleanUpEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] remove or cleanup `enable-background` attribute when possible | ||
* [ [ convertTyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] convert styles into attributes | ||
* [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | ||
* [ [ convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] convert Path data to relative or absolute whichever is shorter, convert one segment to another, trim useless delimiters, smart rounding and much more | ||
* [ [ convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] collapse multiple transforms into one, convert matrices to the short aliases and much more | ||
* [ [ removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] remove unknown elements content and attributes, remove attrs with default values | ||
* [ [ removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] remove non-inheritable group's "presentation" attributes | ||
* [ [ removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] remove useless stroke and fill attrs | ||
* [ [ removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] remove unused namespaces declaration | ||
* [ [ cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] remove unused and minify used IDs | ||
* [ [ cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] round numeric values to the fixed precision, remove default 'px' units | ||
* [ [ moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] move elements attributes to the existing group wrapper | ||
* [ [ moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] move some group attributes to the content elements | ||
* [ [ collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] collapse useless groups | ||
* [ [ removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] remove raster images (disabled by default) | ||
* [ [ mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] merge multiple Paths into one | ||
* [ [ convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] convert some basic shapes to path | ||
* [ [ sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] sort element attributes for epic readability (disabled by default) | ||
* [ [ transformsWithOnePath](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] apply transforms, crop by real width, center vertical alignment and resize SVG with one Path inside (disabled by default) | ||
@@ -56,0 +56,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). |
[english](https://github.com/svg/svgo/blob/master/README.md) | **русский** | ||
- - - | ||
<img src="http://soulshine.in/svgo.svg" width="200" height="200" alt="logo"/> | ||
<img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/> | ||
@@ -21,35 +21,35 @@ ## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.png)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] удаление переносов строк и лишних пробелов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] удаление doctype | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] удаление XML-инструкций | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] удаление комментариев | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] удаление `<metadata>` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] удаление `<title>` (отключена по умолчанию) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] удаление `<desc>` (по умолчанию только незначимых) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] удаление элементов в `<defs>` без `id` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] удаление пространств имён различных редакторов, их элементов и атрибутов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] удаление пустых атрибутов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] удаление скрытых элементов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] удаление пустых текстовых элементов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] удаление пустых элементов-контейнеров | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] удаление атрибута `viewBox`, когда это возможно | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] удаление или оптимизация атрибута `enable-background`, когда это возможно | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] конвертирование стилей в атрибуте `style` в отдельные svg-атрибуты | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] конвертирование цветовых значений: из `rgb()` в `#rrggbb`, из `#rrggbb` в `#rgb` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] конвертирование данных Path в относительные или абсолютные координаты, смотря что короче, конвертирование одних типов сегментов в другие, удаление ненужных разделителей, умное округление и тому подобное | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] схлопывание нескольких трансформаций в одну, конвертирование матриц в короткие алиасы и многое другое | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] удаление неизвестных элементов, контента и атрибутов | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] удаление ненаследуемых "презентационных" атрибутов групп | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] удаление неиспользуемых атрибутов stroke-* и fill-* | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] удаление деклараций неиспользуемых пространств имён | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] удаление неиспользуемых и сокращение используемых ID | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] округление дробных чисел до заданной точности, удаление `px` как единицы измерения по-умолчанию | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] перемещение совпадающих атрибутов у всех элементов внутри группы `<g>` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] перемещение некоторых атрибутов группы на элементы внутри | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] схлопывание бесполезных групп `<g>` | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] удаление растровых изображений (выключено по умолчанию) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] склеивание нескольких Path в одну кривую | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] конвертирование простых форм в Path | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] сортировка атрибутов элементов для удобочитаемости (выключено по умолчанию) | ||
* [ [>](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] применение трансформаций, обрезка по реальной ширине, вертикальное выравнивание по центру и изменение размеров SVG с одним Path внутри | ||
* [ [ cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] удаление переносов строк и лишних пробелов | ||
* [ [ removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] удаление doctype | ||
* [ [ removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] удаление XML-инструкций | ||
* [ [ removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] удаление комментариев | ||
* [ [ removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] удаление `<metadata>` | ||
* [ [ removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] удаление `<title>` (отключена по умолчанию) | ||
* [ [ removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] удаление `<desc>` (по умолчанию только незначимых) | ||
* [ [ removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] удаление элементов в `<defs>` без `id` | ||
* [ [ removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] удаление пространств имён различных редакторов, их элементов и атрибутов | ||
* [ [ removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] удаление пустых атрибутов | ||
* [ [ removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] удаление скрытых элементов | ||
* [ [ removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] удаление пустых текстовых элементов | ||
* [ [ removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] удаление пустых элементов-контейнеров | ||
* [ [ removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] удаление атрибута `viewBox`, когда это возможно | ||
* [ [ cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] удаление или оптимизация атрибута `enable-background`, когда это возможно | ||
* [ [ convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] конвертирование стилей в атрибуте `style` в отдельные svg-атрибуты | ||
* [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] конвертирование цветовых значений: из `rgb()` в `#rrggbb`, из `#rrggbb` в `#rgb` | ||
* [ [ convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] конвертирование данных Path в относительные или абсолютные координаты, смотря что короче, конвертирование одних типов сегментов в другие, удаление ненужных разделителей, умное округление и тому подобное | ||
* [ [ convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] схлопывание нескольких трансформаций в одну, конвертирование матриц в короткие алиасы и многое другое | ||
* [ [ removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] удаление неизвестных элементов, контента и атрибутов | ||
* [ [ removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] удаление ненаследуемых "презентационных" атрибутов групп | ||
* [ [ removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] удаление неиспользуемых атрибутов stroke-* и fill-* | ||
* [ [ removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] удаление деклараций неиспользуемых пространств имён | ||
* [ [ cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] удаление неиспользуемых и сокращение используемых ID | ||
* [ [ cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] округление дробных чисел до заданной точности, удаление `px` как единицы измерения по-умолчанию | ||
* [ [ moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] перемещение совпадающих атрибутов у всех элементов внутри группы `<g>` | ||
* [ [ moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] перемещение некоторых атрибутов группы на элементы внутри | ||
* [ [ collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] схлопывание бесполезных групп `<g>` | ||
* [ [ removeRasterImage](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] удаление растровых изображений (выключено по умолчанию) | ||
* [ [ mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] склеивание нескольких Path в одну кривую | ||
* [ [ convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] конвертирование простых форм в Path | ||
* [ [ sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] сортировка атрибутов элементов для удобочитаемости (выключено по умолчанию) | ||
* [ [ transformsWithOnePath](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] применение трансформаций, обрезка по реальной ширине, вертикальное выравнивание по центру и изменение размеров SVG с одним Path внутри | ||
@@ -56,0 +56,0 @@ Хотите узнать, как это работает и как написать свой плагин? [Конечно же, да!](https://github.com/svg/svgo/blob/master/docs/how-it-works/ru.md). |
Sorry, the diff of this file is not supported yet
290913
55
7814
+ Addedcolors@1.1.2(transitive)
+ Addedesprima@2.2.0(transitive)
+ Addedjs-yaml@3.3.1(transitive)
+ Addedsax@1.1.6(transitive)
- Removedcolors@1.0.3(transitive)
- Removedesprima@2.0.0(transitive)
- Removedjs-yaml@3.2.7(transitive)
- Removedsax@0.6.1(transitive)
Updatedcolors@~1.1.0
Updatedjs-yaml@~3.3.1
Updatedmkdirp@~0.5.1
Updatedsax@~1.1.1