Galerie und tage

This commit is contained in:
2021-11-23 17:56:26 +01:00
parent ff35366279
commit 5f873bee89
4693 changed files with 149659 additions and 301447 deletions

View File

@@ -1,6 +1,6 @@
'use strict';
// http://www.w3.org/TR/SVG11/intro.html#Definitions
// http://www.w3.org/TR/SVG/intro.html#Definitions
exports.elemsGroups = {
animation: ['animate', 'animateColor', 'animateMotion', 'animateTransform', 'set'],
descriptive: ['desc', 'metadata', 'title'],
@@ -17,7 +17,7 @@ exports.elemsGroups = {
exports.pathElems = ['path', 'glyph', 'missing-glyph'];
// http://www.w3.org/TR/SVG11/intro.html#Definitions
// http://www.w3.org/TR/SVG/intro.html#Definitions
exports.attrsGroups = {
animationAddition: ['additive', 'accumulate'],
animationAttributeTarget: ['attributeType', 'attributeName'],
@@ -30,6 +30,7 @@ exports.attrsGroups = {
presentation: [
'alignment-baseline',
'baseline-shift',
'buffered-rendering',
'clip',
'clip-path',
'clip-rule',
@@ -59,6 +60,7 @@ exports.attrsGroups = {
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'kerning',
'letter-spacing',
'lighting-color',
'marker-end',
@@ -67,9 +69,10 @@ exports.attrsGroups = {
'mask',
'opacity',
'overflow',
'paint-order',
'pointer-events',
'shape-rendering',
'solid-color',
'solid-opacity',
'stop-color',
'stop-opacity',
'stroke',
@@ -80,14 +83,18 @@ exports.attrsGroups = {
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'paint-order',
'text-anchor',
'text-decoration',
'text-overflow',
'white-space',
'text-rendering',
'transform',
'unicode-bidi',
'vector-effect',
'viewport-fill',
'viewport-fill-opacity',
'visibility',
'white-space',
'word-spacing',
'writing-mode'
],
@@ -106,6 +113,8 @@ exports.attrsGroupsDefaults = {
'clip-rule': 'nonzero',
mask: 'none',
opacity: '1',
'solid-color': '#000',
'solid-opacity': '1',
'stop-color': '#000',
'stop-opacity': '1',
'fill-opacity': '1',
@@ -121,6 +130,8 @@ exports.attrsGroupsDefaults = {
'stroke-opacity': '1',
'paint-order': 'normal',
'vector-effect': 'none',
'viewport-fill': 'none',
'viewport-fill-opacity': '1',
display: 'inline',
visibility: 'visible',
'marker-start': 'none',
@@ -132,6 +143,7 @@ exports.attrsGroupsDefaults = {
'shape-rendering': 'auto',
'text-rendering': 'auto',
'image-rendering': 'auto',
'buffered-rendering': 'auto',
'font-style': 'normal',
'font-variant': 'normal',
'font-weight': 'normal',
@@ -156,7 +168,7 @@ exports.attrsGroupsDefaults = {
transferFunction: {slope: '1', intercept: '0', amplitude: '1', exponent: '1', offset: '0'}
};
// http://www.w3.org/TR/SVG11/eltindex.html
// http://www.w3.org/TR/SVG/eltindex.html
exports.elems = {
a: {
attrsGroups: [
@@ -776,7 +788,6 @@ exports.elems = {
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'href',
'xlink:href'
],
defaults: {
@@ -991,7 +1002,6 @@ exports.elems = {
'filterRes',
'filterUnits',
'primitiveUnits',
'href',
'xlink:href'
],
defaults: {
@@ -1130,7 +1140,6 @@ exports.elems = {
'xlink'
],
attrs: [
'href',
'xlink:href'
],
content: [
@@ -1375,7 +1384,6 @@ exports.elems = {
'y',
'width',
'height',
'href',
'xlink:href'
],
defaults: {
@@ -1433,7 +1441,6 @@ exports.elems = {
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href'
],
defaults: {
@@ -1613,7 +1620,6 @@ exports.elems = {
],
attrs: [
'externalResourcesRequired',
'href',
'xlink:href'
],
contentGroups: [
@@ -1660,7 +1666,6 @@ exports.elems = {
'patternUnits',
'patternContentUnits',
'patternTransform',
'href',
'xlink:href'
],
defaults: {
@@ -1757,7 +1762,6 @@ exports.elems = {
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href'
],
defaults: {
@@ -1867,7 +1871,6 @@ exports.elems = {
attrs: [
'externalResourcesRequired',
'type',
'href',
'xlink:href'
]
},
@@ -1912,7 +1915,7 @@ exports.elems = {
'offset',
'path'
],
content: [
contentGroups: [
'animate',
'animateColor',
'set'
@@ -2116,7 +2119,6 @@ exports.elems = {
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href',
'startOffset',
'method',
@@ -2162,7 +2164,6 @@ exports.elems = {
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href'
],
contentGroups: [
@@ -2223,7 +2224,6 @@ exports.elems = {
'y',
'width',
'height',
'href',
'xlink:href'
],
defaults: {
@@ -2279,18 +2279,10 @@ exports.editorNamespaces = [
'http://ns.adobe.com/Flows/1.0/',
'http://ns.adobe.com/ImageReplacement/1.0/',
'http://ns.adobe.com/GenericCustomNamespace/1.0/',
'http://ns.adobe.com/XPath/1.0/',
'http://schemas.microsoft.com/visio/2003/SVGExtensions/',
'http://taptrix.com/vectorillustrator/svg_extensions',
'http://www.figma.com/figma/ns',
'http://purl.org/dc/elements/1.1/',
'http://creativecommons.org/ns#',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'http://www.serif.com/',
'http://www.vector.evaxdesign.sk'
'http://ns.adobe.com/XPath/1.0/'
];
// http://www.w3.org/TR/SVG11/linking.html#processingIRI
// http://www.w3.org/TR/SVG/linking.html#processingIRI
exports.referencesProps = [
'clip-path',
'color-profile',
@@ -2304,7 +2296,7 @@ exports.referencesProps = [
'style'
];
// http://www.w3.org/TR/SVG11/propidx.html
// http://www.w3.org/TR/SVG/propidx.html
exports.inheritableAttrs = [
'clip-rule',
'color',
@@ -2314,7 +2306,6 @@ exports.inheritableAttrs = [
'color-rendering',
'cursor',
'direction',
'dominant-baseline',
'fill',
'fill-opacity',
'fill-rule',
@@ -2329,12 +2320,12 @@ exports.inheritableAttrs = [
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'kerning',
'letter-spacing',
'marker',
'marker-end',
'marker-mid',
'marker-start',
'paint-order',
'pointer-events',
'shape-rendering',
'stroke',
@@ -2349,23 +2340,12 @@ exports.inheritableAttrs = [
'text-rendering',
'transform',
'visibility',
'white-space',
'word-spacing',
'writing-mode'
];
exports.presentationNonInheritableGroupAttrs = [
'display',
'clip-path',
'filter',
'mask',
'opacity',
'text-decoration',
'transform',
'unicode-bidi',
'visibility'
];
// http://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
// http://www.w3.org/TR/SVG/single-page.html#types-ColorKeywords
exports.colorsNames = {
'aliceblue': '#f0f8ff',
'antiquewhite': '#faebd7',
@@ -2393,7 +2373,6 @@ exports.colorsNames = {
'darkgoldenrod': '#b8860b',
'darkgray': '#a9a9a9',
'darkgreen': '#006400',
'darkgrey': '#a9a9a9',
'darkkhaki': '#bdb76b',
'darkmagenta': '#8b008b',
'darkolivegreen': '#556b2f',
@@ -2404,13 +2383,11 @@ exports.colorsNames = {
'darkseagreen': '#8fbc8f',
'darkslateblue': '#483d8b',
'darkslategray': '#2f4f4f',
'darkslategrey': '#2f4f4f',
'darkturquoise': '#00ced1',
'darkviolet': '#9400d3',
'deeppink': '#ff1493',
'deepskyblue': '#00bfff',
'dimgray': '#696969',
'dimgrey': '#696969',
'dodgerblue': '#1e90ff',
'firebrick': '#b22222',
'floralwhite': '#fffaf0',
@@ -2423,7 +2400,6 @@ exports.colorsNames = {
'gray': '#808080',
'green': '#008000',
'greenyellow': '#adff2f',
'grey': '#808080',
'honeydew': '#f0fff0',
'hotpink': '#ff69b4',
'indianred': '#cd5c5c',
@@ -2438,7 +2414,6 @@ exports.colorsNames = {
'lightcoral': '#f08080',
'lightcyan': '#e0ffff',
'lightgoldenrodyellow': '#fafad2',
'lightgray': '#d3d3d3',
'lightgreen': '#90ee90',
'lightgrey': '#d3d3d3',
'lightpink': '#ffb6c1',
@@ -2446,7 +2421,6 @@ exports.colorsNames = {
'lightseagreen': '#20b2aa',
'lightskyblue': '#87cefa',
'lightslategray': '#789',
'lightslategrey': '#789',
'lightsteelblue': '#b0c4de',
'lightyellow': '#ffffe0',
'lime': '#0f0',
@@ -2486,7 +2460,6 @@ exports.colorsNames = {
'plum': '#dda0dd',
'powderblue': '#b0e0e6',
'purple': '#800080',
'rebeccapurple': '#639',
'red': '#f00',
'rosybrown': '#bc8f8f',
'royalblue': '#4169e1',
@@ -2500,7 +2473,6 @@ exports.colorsNames = {
'skyblue': '#87ceeb',
'slateblue': '#6a5acd',
'slategray': '#708090',
'slategrey': '#708090',
'snow': '#fffafa',
'springgreen': '#00ff7f',
'steelblue': '#4682b4',
@@ -2540,7 +2512,6 @@ exports.colorsShortNames = {
'#dda0dd': 'plum',
'#800080': 'purple',
'#f00': 'red',
'#ff0000': 'red',
'#fa8072': 'salmon',
'#a0522d': 'sienna',
'#c0c0c0': 'silver',
@@ -2552,7 +2523,7 @@ exports.colorsShortNames = {
'#f5deb3': 'wheat'
};
// http://www.w3.org/TR/SVG11/single-page.html#types-DataTypeColor
// http://www.w3.org/TR/SVG/single-page.html#types-DataTypeColor
exports.colorsProps = [
'color', 'fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color'
];

64
node_modules/svgo/plugins/_path.js generated vendored
View File

@@ -1,16 +1,8 @@
/* global a2c */
'use strict';
var rNumber = String.raw`[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?\s*`,
rCommaWsp = String.raw`(?:\s,?\s*|,\s*)`,
rNumberCommaWsp = `(${rNumber})` + rCommaWsp,
rFlagCommaWsp = `([01])${rCommaWsp}?`,
rCoordinatePair = String.raw`(${rNumber})${rCommaWsp}?(${rNumber})`,
rArcSeq = (rNumberCommaWsp + '?').repeat(2) + rNumberCommaWsp + rFlagCommaWsp.repeat(2) + rCoordinatePair;
var regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
regCoordinateSequence = new RegExp(rNumber, 'g'),
regArcArgumentSequence = new RegExp(rArcSeq, 'g'),
regPathData = /[-+]?(?:\d*\.\d+|\d+\.?)([eE][-+]?\d+)?/g,
regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/,
transform2js = require('./_transforms').transform2js,
transformsMultiply = require('./_transforms').transformsMultiply,
@@ -61,21 +53,11 @@ exports.path2js = function(path) {
}
// data item
} else {
/* jshint boss: true */
if (instruction == 'A' || instruction == 'a') {
var newData = [];
for (var args; (args = regArcArgumentSequence.exec(data));) {
for (var i = 1; i < args.length; i++) {
newData.push(args[i]);
}
}
data = newData;
} else {
data = data.match(regCoordinateSequence);
}
data = data.match(regPathData);
if (!data) return;
data = data.map(Number);
// Subsequent moveto pairs of coordinates are threated as implicit lineto commands
// http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
if (instruction == 'M' || instruction == 'm') {
@@ -115,7 +97,7 @@ var relative2absolute = exports.relative2absolute = function(data) {
subpathPoint = [0, 0],
i;
return data.map(function(item) {
data = data.map(function(item) {
var instruction = item.instruction,
itemData = item.data && item.data.slice();
@@ -178,6 +160,8 @@ var relative2absolute = exports.relative2absolute = function(data) {
};
});
return data;
};
/**
@@ -226,22 +210,16 @@ exports.applyTransforms = function(elem, path, params) {
if (scale !== 1) {
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
if (!elem.hasAttr('vector-effect') || elem.attr('vector-effect').value !== 'non-scaling-stroke') {
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 {
elem.addAttr({
name: 'stroke-width',
prefix: '',
local: 'stroke-width',
value: strokeWidth.replace(regNumericValues, function(num) {
return removeLeadingZero(num * scale);
})
});
}
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 {
elem.addAttr({
name: 'stroke-width',
prefix: '',
local: 'stroke-width',
value: strokeWidth.replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) })
});
}
}
} else if (id) { // Stroke and stroke-width can be redefined with <use>
@@ -553,11 +531,7 @@ exports.js2path = function(path, data, params) {
}
path.attr('d').value = data.reduce(function(pathString, item) {
var strData = '';
if (item.data) {
strData = cleanupOutData(item.data, params, item.instruction);
}
return pathString += item.instruction + strData;
return pathString += item.instruction + (item.data ? cleanupOutData(item.data, params) : '');
}, '');
};
@@ -780,7 +754,7 @@ function gatherPoints(points, item, index, path) {
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
break;
case 'T':
if (prev.instruction == 'Q' || prev.instruction == 'T') {
if (prev.instruction == 'Q' && prev.instruction == 'T') {
ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
addPoint(subPath, ctrlPoint);
prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
@@ -794,7 +768,7 @@ function gatherPoints(points, item, index, path) {
prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
break;
case 'S':
if (prev.instruction == 'C' || prev.instruction == 'S') {
if (prev.instruction == 'C' && prev.instruction == 'S') {
addPoint(subPath, [basePoint[0] + .5 * prevCtrlPoint[0], basePoint[1] + .5 * prevCtrlPoint[1]]);
ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
}

View File

@@ -42,8 +42,8 @@ exports.transform2js = function(transformString) {
}
});
// return empty array if broken transform (no data)
return current && current.data ? transforms : [];
return transforms;
};
/**
@@ -65,7 +65,9 @@ exports.transformsMultiply = function(transforms) {
// multiply all matrices into one
transforms = {
name: 'matrix',
data: transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : []
data: transforms.reduce(function(a, b) {
return multiplyTransformMatrices(a, b);
})
};
return transforms;
@@ -115,7 +117,7 @@ var mth = exports.mth = {
/**
* Decompose matrix into simple transforms. See
* http://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
* 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|Array} transforms array or original transform object
@@ -124,11 +126,11 @@ exports.matrixToTransform = function(transform, params) {
var floatPrecision = params.floatPrecision,
data = transform.data,
transforms = [],
sx = +Math.hypot(data[0], data[1]).toFixed(params.transformPrecision),
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 != 0 || sx == sy;
scaleBefore = rowsSum || +(sx == sy);
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
if (data[4] || data[5]) {
@@ -149,12 +151,11 @@ exports.matrixToTransform = function(transform, params) {
// [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 = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
sx = (data[0] < 0 ? -1 : 1) * Math.sqrt(data[0] * data[0] + data[2] * data[2]);
sy = (data[3] < 0 ? -1 : 1) * Math.sqrt(data[1] * data[1] + data[3] * data[3]);
transforms.push({ name: 'scale', data: [sx, sy] });
}
var angle = Math.min(Math.max(-1, data[0] / sx), 1),
rotate = [mth.acos(angle, floatPrecision) * ((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1)];
var rotate = [mth.acos(data[0] / sx, floatPrecision) * (data[1] * sy < 0 ? -1 : 1)];
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
@@ -261,7 +262,10 @@ exports.transformArc = function(arc, transform) {
// 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.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[2]);
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);
@@ -277,14 +281,8 @@ exports.transformArc = function(arc, transform) {
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.hypot(term1, term2)) * 180 / Math.PI;
Math.acos((major ? term1 : term2) / Math.sqrt(term1 * term1 + term2 * term2)) * 180 / Math.PI;
}
if ((transform[0] < 0) !== (transform[3] < 0)) {
// Flip the sweep flag if coordinates are being flipped horizontally XOR vertically
arc[4] = 1 - arc[4];
}
return arc;
};

View File

@@ -1,82 +0,0 @@
'use strict';
exports.type = 'full';
exports.active = false;
exports.description = 'adds attributes to an outer <svg> element';
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
It should have a list of "attributes" or one "attribute".
Config example:
plugins:
- addAttributesToSVGElement:
attribute: "mySvg"
plugins:
- addAttributesToSVGElement:
attributes: ["mySvg", "size-big"]
plugins:
- addAttributesToSVGElement:
attributes:
- focusable: false
- data-image: icon`;
/**
* Add attributes to an outer <svg> element. Example config:
*
* plugins:
* - addAttributesToSVGElement:
* attribute: 'data-icon'
*
* plugins:
* - addAttributesToSVGElement:
* attributes: ['data-icon', 'data-disabled']
*
* plugins:
* - addAttributesToSVGElement:
* attributes:
* - focusable: false
* - data-image: icon
*
* @author April Arcus
*/
exports.fn = function(data, params) {
if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
console.error(ENOCLS);
return data;
}
var attributes = params.attributes || [ params.attribute ],
svg = data.content[0];
if (svg.isElem('svg')) {
attributes.forEach(function (attribute) {
if (typeof attribute === 'string') {
if (!svg.hasAttr(attribute)) {
svg.addAttr({
name: attribute,
prefix: '',
local: attribute
});
}
} else if (typeof attribute === 'object') {
Object.keys(attribute).forEach(function (key) {
if (!svg.hasAttr(key)) {
svg.addAttr({
name: key,
value: attribute[key],
prefix: '',
local: key
});
}
});
}
});
}
return data;
};

View File

@@ -6,18 +6,17 @@ exports.active = false;
exports.description = 'adds classnames to an outer <svg> element';
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
It should have a list of classes in "classNames" or one "className".
Config example:
plugins:
- addClassesToSVGElement:
className: "mySvg"
plugins:
- addClassesToSVGElement:
classNames: ["mySvg", "size-big"]
`;
var ENOCLS = 'Error in plugin "addClassesToSVGElement": absent parameters.\n\
It should have a list of classes in "classNames" or one "className".\n\
Config example:\n\n\
\
plugins:\n\
- addClassesToSVGElement:\n\
className: "mySvg"\n\n\
\
plugins:\n\
- addClassesToSVGElement:\n\
classNames: ["mySvg", "size-big"]\n';
/**
* Add classnames to an outer <svg> element. Example config:
@@ -42,7 +41,20 @@ exports.fn = function(data, params) {
svg = data.content[0];
if (svg.isElem('svg')) {
svg.class.add.apply(svg.class, classNames);
if (svg.hasAttr('class')) {
svg.attr('class').value =
svg.attr('class').value
.split(' ')
.concat(classNames)
.join(' ');
} else {
svg.addAttr({
name: 'class',
value: classNames.join(' '),
prefix: '',
local: 'class'
});
}
}
return data;

View File

@@ -12,8 +12,8 @@ exports.params = {
spaces: true
};
var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g,
regNewlines = /\r?\n/g,
var regNewlinesNeedSpace = /(\S)\n(\S)/g,
regNewlines = /\n/g,
regSpaces = /\s{2,}/g;
/**

View File

@@ -9,16 +9,13 @@ exports.description = 'removes unused IDs and minifies used';
exports.params = {
remove: true,
minify: true,
prefix: '',
preserve: [],
preservePrefixes: [],
force: false
prefix: ''
};
var referencesProps = new Set(require('./_collections').referencesProps),
var referencesProps = require('./_collections').referencesProps,
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
regReferencesHref = /^#(.+?)$/,
regReferencesBegin = /(\w+)\./,
regReferencesBegin = /^(\w+?)\./,
styleOrScript = ['style', 'script'],
generateIDchars = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
@@ -36,15 +33,13 @@ var referencesProps = new Set(require('./_collections').referencesProps),
* @author Kir Belevich
*/
exports.fn = function(data, params) {
var currentID,
currentIDstring,
IDs = new Map(),
referencesIDs = new Map(),
hasStyleOrScript = false,
preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []),
preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])),
idValuePrefix = '#',
idValuePostfix = '.';
IDs = Object.create(null),
referencesIDs = Object.create(null),
idPrefix = 'id-', // prefix IDs so that values like '__proto__' don't break the work
hasStyleOrScript = false;
/**
* Bananas!
@@ -53,66 +48,71 @@ exports.fn = function(data, params) {
* @return {Array} output items
*/
function monkeys(items) {
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
var item = items.content[i];
// quit if <style> or <script> present ('force' param prevents quitting)
if (!params.force) {
if (item.isElem(styleOrScript)) {
hasStyleOrScript = true;
continue;
}
// Don't remove IDs if the whole SVG consists only of defs.
if (item.isElem('defs') && item.parentNode.isElem('svg')) {
var hasDefsOnly = true;
for (var j = i + 1; j < items.content.length; j++) {
if (items.content[j].isElem()) {
hasDefsOnly = false;
break;
}
}
if (hasDefsOnly) {
break;
}
}
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
var item = items.content[i],
match;
// check if <style> of <script> presents
if (item.isElem(styleOrScript)) {
hasStyleOrScript = true;
continue;
}
// …and don't remove any ID if yes
if (item.isElem()) {
item.eachAttr(function(attr) {
var key, match;
item.eachAttr(function(attr) {
var key;
// save IDs
if (attr.name === 'id') {
key = attr.value;
if (IDs.has(key)) {
item.removeAttr('id'); // remove repeated id
key = idPrefix + attr.value;
if (key in IDs) {
item.removeAttr('id');
} else {
IDs.set(key, item);
IDs[key] = item;
}
return;
}
// save references
if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) {
key = match[2]; // url() reference
} else if (
// save IDs url() references
else if (referencesProps.indexOf(attr.name) > -1) {
match = attr.value.match(regReferencesUrl);
if (match) {
key = idPrefix + match[2];
if (referencesIDs[key]) {
referencesIDs[key].push(attr);
} else {
referencesIDs[key] = [attr];
}
}
}
// save IDs href references
else if (
attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
) {
key = match[1]; // href reference
}
if (key) {
var ref = referencesIDs.get(key) || [];
ref.push(attr);
referencesIDs.set(key, ref);
key = idPrefix + match[1];
if (referencesIDs[key]) {
referencesIDs[key].push(attr);
} else {
referencesIDs[key] = [attr];
}
}
});
}
// go deeper
if (item.content) {
monkeys(item);
}
}
return items;
}
data = monkeys(data);
@@ -121,55 +121,43 @@ exports.fn = function(data, params) {
return data;
}
const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
for (var k in referencesIDs) {
if (IDs[k]) {
for (var ref of referencesIDs) {
var key = ref[0];
if (IDs.has(key)) {
// replace referenced IDs with the minified ones
if (params.minify && !idPreserved(key)) {
do {
currentIDstring = getIDstring(currentID = generateID(currentID), params);
} while (idPreserved(currentIDstring));
if (params.minify) {
IDs.get(key).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 + '.');
});
for (var attr of ref[1]) {
attr.value = attr.value.includes(idValuePrefix) ?
attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) :
attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix);
}
}
// don't remove referenced IDs
IDs.delete(key);
delete IDs[idPrefix + k];
}
}
// remove non-referenced IDs attributes from elements
if (params.remove) {
for(var keyElem of IDs) {
if (!idPreserved(keyElem[0])) {
keyElem[1].removeAttr('id');
}
for(var ID in IDs) {
IDs[ID].removeAttr('id');
}
}
return data;
};
/**
* Check if an ID starts with any one of a list of strings.
*
* @param {Array} of prefix strings
* @param {String} current ID
* @return {Boolean} if currentID starts with one of the strings in prefixArray
*/
function idMatchesPrefix(prefixArray, currentID) {
if (!currentID) return false;
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true;
return false;
}
/**
* Generate unique minimal ID.
*
@@ -177,6 +165,7 @@ function idMatchesPrefix(prefixArray, currentID) {
* @return {Array} generated ID array
*/
function generateID(currentID) {
if (!currentID) return [0];
currentID[currentID.length - 1]++;
@@ -190,11 +179,14 @@ function generateID(currentID) {
}
}
}
if (currentID[0] > maxIDindex) {
currentID[0] = 0;
currentID.unshift(0);
}
return currentID;
}
/**
@@ -204,6 +196,13 @@ function generateID(currentID) {
* @return {String} output ID string
*/
function getIDstring(arr, params) {
var str = params.prefix;
return str + arr.map(i => generateIDchars[i]).join('');
arr.forEach(function(i) {
str += generateIDchars[i];
});
return str;
}

View File

@@ -96,7 +96,8 @@ exports.fn = function(item, params) {
matchNew = elem.match(/new/);
// if attribute value matches regNumericValues
if (match) {
if(match){
// round it to the fixed precision
num = +(+match[1]).toFixed(params.floatPrecision),
units = match[3] || '';
@@ -121,12 +122,13 @@ exports.fn = function(item, params) {
}
roundedListArr.push(num+units);
}
// if attribute value is "new"(only enable-background).
else if (matchNew) {
else if(matchNew){
roundedListArr.push('new');
} else if (elem) {
roundedListArr.push(elem);
}
});

View File

@@ -37,36 +37,24 @@ exports.fn = function(item, params) {
if (item.isElem()) {
var floatPrecision = params.floatPrecision;
if (item.hasAttr('viewBox')) {
var nums = item.attr('viewBox').value.split(/\s,?\s*|,\s*/g);
item.attr('viewBox').value = nums.map(function(value) {
var num = +value;
return isNaN(num) ? value : +num.toFixed(floatPrecision);
}).join(' ');
}
var match;
item.eachAttr(function(attr) {
// The `version` attribute is a text string and cannot be rounded
if (attr.name === 'version') { return }
var match = attr.value.match(regNumericValues);
match = attr.value.match(regNumericValues);
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
var num = +(+match[1]).toFixed(floatPrecision),
var num = +(+match[1]).toFixed(params.floatPrecision),
units = match[3] || '';
// convert absolute values to pixels
if (params.convertToPx && units && (units in absoluteLengths)) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision);
if (String(pxNum).length < match[0].length) {
num = pxNum;
if (String(pxNum).length < match[0].length)
num = pxNum,
units = 'px';
}
}
// and remove leading zero

View File

@@ -6,12 +6,9 @@ exports.active = true;
exports.description = 'collapses useless groups';
var collections = require('./_collections'),
attrsInheritable = collections.inheritableAttrs,
animationElems = collections.elemsGroups.animation;
var animationElems = require('./_collections').elemsGroups.animation;
function hasAnimatedAttr(item) {
/* jshint validthis:true */
return item.isElem(animationElems) && item.hasAttr('attributeName', this) ||
!item.isEmpty() && item.content.some(hasAnimatedAttr, this);
}
@@ -43,35 +40,28 @@ exports.fn = function(item) {
// non-empty elements
if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) {
item.content.forEach(function(g, i) {
// non-empty groups
if (g.isElem('g') && !g.isEmpty()) {
// move group attibutes to the single content element
if (g.hasAttr() && g.content.length === 1) {
var inner = g.content[0];
if (inner.isElem() && !inner.hasAttr('id') && !g.hasAttr('filter') &&
!(g.hasAttr('class') && inner.hasAttr('class')) && (
!g.hasAttr('clip-path') && !g.hasAttr('mask') ||
inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform')
)
) {
if (inner.isElem() && !inner.hasAttr('id') && (
!g.hasAttr('clip-path') ||
inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform')
)) {
g.eachAttr(function(attr) {
if (g.content.some(hasAnimatedAttr, attr.name)) return;
if (!inner.hasAttr(attr.name)) {
inner.addAttr(attr);
} else if (attr.name == 'transform') {
} else if (attr.name == 'transform' || attr.name == 'class') {
inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value;
} else if (inner.hasAttr(attr.name, 'inherit')) {
inner.attr(attr.name).value = attr.value;
} else if (
attrsInheritable.indexOf(attr.name) < 0 &&
!inner.hasAttr(attr.name, attr.value)
) {
return;
}
g.removeAttr(attr.name);
});
}
@@ -82,6 +72,9 @@ exports.fn = function(item) {
item.spliceContent(i, 1, g.content);
}
}
});
}
};

View File

@@ -59,17 +59,8 @@ exports.fn = function(item, params) {
match;
// Convert colors to currentColor
if (params.currentColor) {
if (typeof params.currentColor === 'string') {
match = val === params.currentColor;
} else if (params.currentColor.exec) {
match = params.currentColor.exec(val);
} else {
match = !val.match(none);
}
if (match) {
val = 'currentColor';
}
if (params.currentColor && (match = !val.match(none))) {
val = 'currentColor';
}
// Convert color name keyword to long hex
@@ -95,11 +86,8 @@ exports.fn = function(item, params) {
}
// Convert hex to short name
if (params.shortname) {
var lowerVal = val.toLowerCase();
if (lowerVal in collections.colorsShortNames) {
val = collections.colorsShortNames[lowerVal];
}
if (params.shortname && val in collections.colorsShortNames) {
val = collections.colorsShortNames[val];
}
attr.value = val;

View File

@@ -1,39 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.description = 'converts non-eccentric <ellipse>s to <circle>s';
/**
* Converts non-eccentric <ellipse>s to <circle>s.
*
* @see http://www.w3.org/TR/SVG/shapes.html
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author Taylor Hunt
*/
exports.fn = function(item) {
if (item.isElem('ellipse')) {
var rx = item.attr('rx').value || 0;
var ry = item.attr('ry').value || 0;
if (rx === ry ||
rx === 'auto' || ry === 'auto' // SVG2
) {
var radius = rx !== 'auto' ? rx : ry;
item.renameElem('circle');
item.removeAttr(['rx', 'ry']);
item.addAttr({
name: 'r',
value: radius,
prefix: '',
local: 'r',
});
}
}
return;
};

View File

@@ -22,9 +22,7 @@ exports.params = {
collapseRepeated: true,
utilizeAbsolute: true,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: true,
forceAbsolutePath: false
negativeExtraSpace: true
};
var pathElems = require('./_collections.js').pathElems,
@@ -37,8 +35,7 @@ var pathElems = require('./_collections.js').pathElems,
error,
arcThreshold,
arcTolerance,
hasMarkerMid,
hasStrokeLinecap;
hasMarkerMid;
/**
* Convert absolute Path to relative,
@@ -69,10 +66,6 @@ exports.fn = function(item, params) {
}
hasMarkerMid = item.hasAttr('marker-mid');
var stroke = item.computedAttr('stroke'),
strokeLinecap = item.computedAttr('stroke');
hasStrokeLinecap = stroke && stroke != 'none' && strokeLinecap && strokeLinecap != 'butt';
var data = path2js(item);
// TODO: get rid of functions returns
@@ -335,13 +328,12 @@ function filters(path, params) {
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
var prevAngle = findArcAngle(prevData,
angle += findArcAngle(prevData,
{
center: [prevData[4] + circle.center[0], prevData[5] + circle.center[1]],
center: [prevData[4] + relCenter[0], prevData[5] + relCenter[1]],
radius: circle.radius
}
);
angle += prevAngle;
if (angle > Math.PI) arc.data[3] = 1;
hasPrev = 1;
}
@@ -591,7 +583,7 @@ function filters(path, params) {
}
// remove useless non-first path segments
if (params.removeUseless && !hasStrokeLinecap) {
if (params.removeUseless) {
// l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
if (
@@ -679,12 +671,11 @@ function convertToMixed(path, params) {
var absoluteDataStr = cleanupOutData(adata, params),
relativeDataStr = cleanupOutData(data, params);
// Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
// Convert to absolute coordinates if it's shorter.
// v-20 -> V0
// Don't convert if it fits following previous instruction.
// l20 30-10-50 instead of l20 30L20 30
if (
params.forceAbsolutePath || (
absoluteDataStr.length < relativeDataStr.length &&
!(
params.negativeExtraSpace &&
@@ -692,7 +683,7 @@ function convertToMixed(path, params) {
prev.instruction.charCodeAt(0) > 96 &&
absoluteDataStr.length == relativeDataStr.length - 1 &&
(data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1)
))
)
) {
item.instruction = instruction.toUpperCase();
item.data = adata;
@@ -849,7 +840,7 @@ function makeLonghand(item, data) {
*/
function getDistance(point1, point2) {
return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
return Math.sqrt(Math.pow(point1[0] - point2[0], 2) + Math.pow(point1[1] - point2[1], 2));
}
/**
@@ -894,22 +885,21 @@ function findCircle(curve) {
radius = center && getDistance([0, 0], center),
tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100);
if (center && radius < 1e15 &&
[1/4, 3/4].every(function(point) {
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 circle.
* Checks if a curve fits the given circe.
*
* @param {Object} circle
* @param {Array} curve
* @return {Boolean}
*/
function isArc(curve, circle) {
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) {
@@ -918,14 +908,14 @@ function isArc(curve, circle) {
}
/**
* Checks if a previous curve fits the given circle.
* Checks if a previos curve fits the given circe.
*
* @param {Object} circle
* @param {Array} curve
* @return {Boolean}
*/
function isArcPrev(curve, circle) {
function isArcPrev(curve, circle) {
return isArc(curve, {
center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
radius: circle.radius
@@ -962,10 +952,6 @@ function findArcAngle(curve, relCircle) {
function data2Path(params, pathData) {
return pathData.reduce(function(pathString, item) {
var strData = '';
if (item.data) {
strData = cleanupOutData(roundData(item.data.slice()), params);
}
return pathString + item.instruction + strData;
return pathString += item.instruction + (item.data ? cleanupOutData(roundData(item.data.slice()), params) : '');
}, '');
}

View File

@@ -6,10 +6,6 @@ exports.active = true;
exports.description = 'converts basic shapes to more compact path form';
exports.params = {
convertArcs: false
};
var none = { value: 0 },
regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
@@ -26,8 +22,7 @@ var none = { value: 0 },
*
* @author Lev Solntsev
*/
exports.fn = function(item, params) {
var convertArcs = params && params.convertArcs;
exports.fn = function(item) {
if (
item.isElem('rect') &&
@@ -103,47 +98,6 @@ exports.fn = function(item, params) {
item.renameElem('path')
.removeAttr('points');
} else if (item.isElem('circle') && convertArcs) {
var cx = +(item.attr('cx') || none).value;
var cy = +(item.attr('cy') || none).value;
var r = +(item.attr('r') || none).value;
if (isNaN(cx - cy + r)) {
return;
}
var cPathData =
'M' + cx + ' ' + (cy - r) +
'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy + r) +
'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy - r) +
'Z';
item.addAttr({
name: 'd',
value: cPathData,
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
} else if (item.isElem('ellipse') && convertArcs) {
var ecx = +(item.attr('cx') || none).value;
var ecy = +(item.attr('cy') || none).value;
var rx = +(item.attr('rx') || none).value;
var ry = +(item.attr('ry') || none).value;
if (isNaN(ecx - ecy + rx - ry)) {
return;
}
var ePathData =
'M' + ecx + ' ' + (ecy - ry) +
'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy + ry) +
'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy - ry) +
'Z';
item.addAttr({
name: 'd',
value: ePathData,
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
}
};

View File

@@ -7,11 +7,8 @@ exports.active = true;
exports.description = 'converts style to attributes';
exports.params = {
keepImportant: false
};
var stylingProps = require('./_collections').attrsGroups.presentation,
var EXTEND = require('whet.extend'),
stylingProps = require('./_collections').attrsGroups.presentation,
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space.
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like fill
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
@@ -23,16 +20,13 @@ var stylingProps = require('./_collections').attrsGroups.presentation,
rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
rValue = '\\s*(' + g('[^!\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')',
rValue = '\\s*(' + g('[^\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')',
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
rDeclEnd = '\\s*(?:;\\s*|$)',
// Important rule
rImportant = '(\\s*!important(?![-(\w]))?',
// Final RegExp to parse CSS declarations.
regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rImportant + rDeclEnd, 'ig'),
regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rDeclEnd, 'ig'),
// Comments expression. Honors escape sequences and strings.
regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig');
@@ -55,7 +49,7 @@ var stylingProps = require('./_collections').attrsGroups.presentation,
*
* @author Kir Belevich
*/
exports.fn = function(item, params) {
exports.fn = function(item) {
/* jshint boss: true */
if (item.elem && item.hasAttr('style')) {
@@ -72,9 +66,7 @@ exports.fn = function(item, params) {
regDeclarationBlock.lastIndex = 0;
for (var rule; rule = regDeclarationBlock.exec(styleValue);) {
if (!params.keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
styles.push([rule[1], rule[2]]);
}
if (styles.length) {
@@ -104,7 +96,7 @@ exports.fn = function(item, params) {
return true;
});
Object.assign(item.attrs, attrs);
EXTEND(item.attrs, attrs);
if (styles.length) {
item.attr('style').value = styles

View File

@@ -22,6 +22,7 @@ exports.params = {
};
var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
EXTEND = require('whet.extend'),
transform2js = require('./_transforms.js').transform2js,
transformsMultiply = require('./_transforms.js').transformsMultiply,
matrixToTransform = require('./_transforms.js').matrixToTransform,
@@ -115,7 +116,7 @@ function definePrecision(data, params) {
significantDigits = params.transformPrecision;
// Clone params so it don't affect other elements transformations.
params = Object.assign({}, params);
params = EXTEND({}, params);
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
if (matrixData.length) {

View File

@@ -1,245 +0,0 @@
'use strict';
exports.type = 'full';
exports.active = true;
exports.params = {
onlyMatchedOnce: true,
removeMatchedSelectors: true,
useMqs: ['', 'screen'],
usePseudos: ['']
};
exports.description = 'inline styles (additional options)';
var csstree = require('css-tree'),
cssTools = require('../lib/css-tools');
/**
* Moves + merges styles from style elements to element styles
*
* Options
* onlyMatchedOnce (default: true)
* inline only selectors that match once
*
* removeMatchedSelectors (default: true)
* clean up matched selectors,
* leave selectors that hadn't matched
*
* useMqs (default: ['', 'screen'])
* what media queries to be used
* empty string element for styles outside media queries
*
* usePseudos (default: [''])
* what pseudo-classes/-elements to be used
* empty string element for all non-pseudo-classes and/or -elements
*
* @param {Object} document document element
* @param {Object} opts plugin params
*
* @author strarsis <strarsis@gmail.com>
*/
exports.fn = function(document, opts) {
// collect <style/>s
var styleEls = document.querySelectorAll('style');
//no <styles/>s, nothing to do
if (styleEls === null) {
return document;
}
var styles = [],
selectors = [];
for (var styleEl of styleEls) {
if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) {
// skip empty <style/>s or <foreignObject> content.
continue;
}
var cssStr = cssTools.getCssStr(styleEl);
// collect <style/>s and their css ast
var cssAst = {};
try {
cssAst = csstree.parse(cssStr, {
parseValue: false,
parseCustomProperty: false
});
} catch (parseError) {
// console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
continue;
}
styles.push({
styleEl: styleEl,
cssAst: cssAst
});
selectors = selectors.concat(cssTools.flattenToSelectors(cssAst));
}
// filter for mediaqueries to be used or without any mediaquery
var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
// filter for pseudo elements to be used
var selectorsPseudo = cssTools.filterByPseudos(selectorsMq, opts.usePseudos);
// remove PseudoClass from its SimpleSelector for proper matching
cssTools.cleanPseudos(selectorsPseudo);
// stable sort selectors
var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
var selector,
selectedEl;
// match selectors
for (selector of sortedSelectors) {
var selectorStr = csstree.generate(selector.item.data),
selectedEls = null;
try {
selectedEls = document.querySelectorAll(selectorStr);
} catch (selectError) {
if (selectError.constructor === SyntaxError) {
// console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
continue;
}
throw selectError;
}
if (selectedEls === null) {
// nothing selected
continue;
}
selector.selectedEls = selectedEls;
}
// apply <style/> styles to matched elements
for (selector of sortedSelectors) {
if(!selector.selectedEls) {
continue;
}
if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
// skip selectors that match more than once if option onlyMatchedOnce is enabled
continue;
}
// apply <style/> to matched elements
for (selectedEl of selector.selectedEls) {
if (selector.rule === null) {
continue;
}
// merge declarations
csstree.walk(selector.rule, {visit: 'Declaration', enter: function(styleCsstreeDeclaration) {
// existing inline styles have higher priority
// no inline styles, external styles, external styles used
// inline styles, external styles same priority as inline styles, inline styles used
// inline styles, external styles higher priority than inline styles, external styles used
var styleDeclaration = cssTools.csstreeToStyleDeclaration(styleCsstreeDeclaration);
if (selectedEl.style.getPropertyValue(styleDeclaration.name) !== null &&
selectedEl.style.getPropertyPriority(styleDeclaration.name) >= styleDeclaration.priority) {
return;
}
selectedEl.style.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority);
}});
}
if (opts.removeMatchedSelectors && selector.selectedEls !== null && selector.selectedEls.length > 0) {
// clean up matching simple selectors if option removeMatchedSelectors is enabled
selector.rule.prelude.children.remove(selector.item);
}
}
if (!opts.removeMatchedSelectors) {
return document; // no further processing required
}
// clean up matched class + ID attribute values
for (selector of sortedSelectors) {
if(!selector.selectedEls) {
continue;
}
if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
// skip selectors that match more than once if option onlyMatchedOnce is enabled
continue;
}
for (selectedEl of selector.selectedEls) {
// class
var firstSubSelector = selector.item.data.children.first();
if(firstSubSelector.type === 'ClassSelector') {
selectedEl.class.remove(firstSubSelector.name);
}
// clean up now empty class attributes
if(typeof selectedEl.class.item(0) === 'undefined') {
selectedEl.removeAttr('class');
}
// ID
if(firstSubSelector.type === 'IdSelector') {
selectedEl.removeAttr('id', firstSubSelector.name);
}
}
}
// clean up now empty elements
for (var style of styles) {
csstree.walk(style.cssAst, {visit: 'Rule', enter: function(node, item, list) {
// clean up <style/> atrules without any rulesets left
if (node.type === 'Atrule' &&
// only Atrules containing rulesets
node.block !== null &&
node.block.children.isEmpty()) {
list.remove(item);
return;
}
// clean up <style/> rulesets without any css selectors left
if (node.type === 'Rule' &&
node.prelude.children.isEmpty()) {
list.remove(item);
}
}});
if (style.cssAst.children.isEmpty()) {
// clean up now emtpy <style/>s
var styleParentEl = style.styleEl.parentNode;
styleParentEl.spliceContent(styleParentEl.content.indexOf(style.styleEl), 1);
if (styleParentEl.elem === 'defs' &&
styleParentEl.content.length === 0) {
// also clean up now empty <def/>s
var defsParentEl = styleParentEl.parentNode;
defsParentEl.spliceContent(defsParentEl.content.indexOf(styleParentEl), 1);
}
continue;
}
// update existing, left over <style>s
cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
}
return document;
};

View File

@@ -8,10 +8,8 @@ exports.description = 'merges multiple paths in one if possible';
exports.params = {
collapseRepeated: true,
force: false,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: true
negativeExtraSpace: true
};
var path2js = require('./_path.js').path2js,
@@ -58,7 +56,7 @@ exports.fn = function(item, params) {
prevPathJS = path2js(prevContentItem),
curPathJS = path2js(contentItem);
if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
if (equalData && !intersects(prevPathJS, curPathJS)) {
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
return false;
}

View File

@@ -1,160 +1,45 @@
'use strict';
exports.type = 'full';
exports.type = 'perItem';
exports.active = true;
exports.description = 'minifies styles and removes unused styles based on usage data';
exports.params = {
// ... CSSO options goes here
// additional
usage: {
force: false, // force to use usage data even if it unsafe (document contains <script> or on* attributes)
ids: true,
classes: true,
tags: true
}
svgo: {}
};
exports.description = 'minifies existing styles in svg';
var csso = require('csso');
/**
* Minifies styles (<style> element + style attribute) using CSSO
* Minifies styles (<style> element + style attribute) using svgo
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author strarsis <strarsis@gmail.com>
*/
exports.fn = function(ast, options) {
options = options || {};
exports.fn = function(item, svgoOptions) {
var minifyOptionsForStylesheet = cloneObject(options);
var minifyOptionsForAttribute = cloneObject(options);
var elems = findStyleElems(ast);
if(item.elem) {
if(item.isElem('style') && !item.isEmpty()) {
var styleCss = item.content[0].text || item.content[0].cdata || [],
DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text';
if(styleCss.length > 0) {
var styleCssMinified = csso.minify(styleCss, svgoOptions);
item.content[0][DATA] = styleCssMinified.css;
}
}
minifyOptionsForStylesheet.usage = collectUsageData(ast, options);
minifyOptionsForAttribute.usage = null;
if(item.hasAttr('style')) {
var itemCss = item.attr('style').value;
if(itemCss.length > 0) {
var itemCssMinified = csso.minifyBlock(itemCss, svgoOptions);
item.attr('style').value = itemCssMinified.css;
}
}
}
elems.forEach(function(elem) {
if (elem.isElem('style')) {
// <style> element
var styleCss = elem.content[0].text || elem.content[0].cdata || [];
var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text';
elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css;
} else {
// style attribute
var elemStyle = elem.attr('style').value;
elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css;
}
});
return ast;
return item;
};
function cloneObject(obj) {
var result = {};
for (var key in obj) {
result[key] = obj[key];
}
return result;
}
function findStyleElems(ast) {
function walk(items, styles) {
for (var i = 0; i < items.content.length; i++) {
var item = items.content[i];
// go deeper
if (item.content) {
walk(item, styles);
}
if (item.isElem('style') && !item.isEmpty()) {
styles.push(item);
} else if (item.isElem() && item.hasAttr('style')) {
styles.push(item);
}
}
return styles;
}
return walk(ast, []);
}
function shouldFilter(options, name) {
if ('usage' in options === false) {
return true;
}
if (options.usage && name in options.usage === false) {
return true;
}
return Boolean(options.usage && options.usage[name]);
}
function collectUsageData(ast, options) {
function walk(items, usageData) {
for (var i = 0; i < items.content.length; i++) {
var item = items.content[i];
// go deeper
if (item.content) {
walk(item, usageData);
}
if (item.isElem('script')) {
safe = false;
}
if (item.isElem()) {
usageData.tags[item.elem] = true;
if (item.hasAttr('id')) {
usageData.ids[item.attr('id').value] = true;
}
if (item.hasAttr('class')) {
item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) {
usageData.classes[className] = true;
});
}
if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) {
safe = false;
}
}
}
return usageData;
}
var safe = true;
var usageData = {};
var hasData = false;
var rawData = walk(ast, {
ids: Object.create(null),
classes: Object.create(null),
tags: Object.create(null)
});
if (!safe && options.usage && options.usage.force) {
safe = true;
}
for (var key in rawData) {
if (shouldFilter(options, key)) {
usageData[key] = Object.keys(rawData[key]);
hasData = true;
}
}
return safe && hasData ? usageData : null;
}

View File

@@ -44,16 +44,10 @@ exports.fn = function(item) {
})
) {
item.content.forEach(function(inner) {
var attr = item.attr('transform');
if (inner.hasAttr('transform')) {
inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value;
inner.attr('transform').value = item.attr('transform').value + ' ' + inner.attr('transform').value;
} else {
inner.addAttr({
'name': attr.name,
'local': attr.local,
'prefix': attr.prefix,
'value': attr.value
});
inner.addAttr(item.attr('transform'));
}
});

View File

@@ -1,243 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.params = {
delim: '__',
prefixIds: true,
prefixClassNames: true,
};
exports.description = 'prefix IDs';
var path = require('path'),
csstree = require('css-tree'),
unquote = require('unquote'),
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
addPrefix = null;
// Escapes a string for being used as ID
var escapeIdentifierName = function(str) {
return str.replace(/[\. ]/g, '_');
};
// Matches an #ID value, captures the ID name
var matchId = function(urlVal) {
var idUrlMatches = urlVal.match(rxId);
if (idUrlMatches === null) {
return false;
}
return idUrlMatches[1];
};
// Matches an url(...) value, captures the URL
var matchUrl = function(val) {
var urlMatches = /url\((.*?)\)/gi.exec(val);
if (urlMatches === null) {
return false;
}
return urlMatches[1];
};
// Checks if attribute is empty
var attrNotEmpty = function(attr) {
return (attr && attr.value && attr.value.length > 0);
};
// prefixes an #ID
var prefixId = function(val) {
var idName = matchId(val);
if (!idName) {
return false;
}
return '#' + addPrefix(idName);
};
// attr.value helper methods
// prefixes a class attribute value
var addPrefixToClassAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
};
// prefixes an ID attribute value
var addPrefixToIdAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
attr.value = addPrefix(attr.value);
};
// prefixes a href attribute value
var addPrefixToHrefAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
var idPrefixed = prefixId(attr.value);
if (!idPrefixed) {
return;
}
attr.value = idPrefixed;
};
// prefixes an URL attribute value
var addPrefixToUrlAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
// url(...) in value
var urlVal = matchUrl(attr.value);
if (!urlVal) {
return;
}
var idPrefixed = prefixId(urlVal);
if (!idPrefixed) {
return;
}
attr.value = 'url(' + idPrefixed + ')';
};
/**
* Prefixes identifiers
*
* @param {Object} node node
* @param {Object} opts plugin params
* @param {Object} extra plugin extra information
*
* @author strarsis <strarsis@gmail.com>
*/
exports.fn = function(node, opts, extra) {
// skip subsequent passes when multipass is used
if(extra.multipassCount && extra.multipassCount > 0) {
return node;
}
// prefix, from file name or option
var prefix = 'prefix';
if (opts.prefix) {
if (typeof opts.prefix === 'function') {
prefix = opts.prefix(node, extra);
} else {
prefix = opts.prefix;
}
} else if (opts.prefix === false) {
prefix = false;
} else if (extra && extra.path && extra.path.length > 0) {
var filename = path.basename(extra.path);
prefix = filename;
}
// prefixes a normal value
addPrefix = function(name) {
if(prefix === false){
return escapeIdentifierName(name);
}
return escapeIdentifierName(prefix + opts.delim + name);
};
// <style/> property values
if (node.elem === 'style') {
if (node.isEmpty()) {
// skip empty <style/>s
return node;
}
var cssStr = node.content[0].text || node.content[0].cdata || [];
var cssAst = {};
try {
cssAst = csstree.parse(cssStr, {
parseValue: true,
parseCustomProperty: false
});
} catch (parseError) {
console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
return node;
}
var idPrefixed = '';
csstree.walk(cssAst, function(node) {
// #ID, .class
if (((opts.prefixIds && node.type === 'IdSelector') ||
(opts.prefixClassNames && node.type === 'ClassSelector')) &&
node.name) {
node.name = addPrefix(node.name);
return;
}
// url(...) in value
if (node.type === 'Url' &&
node.value.value && node.value.value.length > 0) {
idPrefixed = prefixId(unquote(node.value.value));
if (!idPrefixed) {
return;
}
node.value.value = idPrefixed;
}
});
// update <style>s
node.content[0].text = csstree.generate(cssAst);
return node;
}
// element attributes
if (!node.attrs) {
return node;
}
// Nodes
if(opts.prefixIds) {
// ID
addPrefixToIdAttr(node.attrs.id);
}
if(opts.prefixClassNames) {
// Class
addPrefixToClassAttr(node.attrs.class);
}
// References
// href
addPrefixToHrefAttr(node.attrs.href);
// (xlink:)href (deprecated, must be still supported)
addPrefixToHrefAttr(node.attrs['xlink:href']);
// (referenceable) properties
for (var referencesProp of referencesProps) {
addPrefixToUrlAttr(node.attrs[referencesProp]);
}
return node;
};

View File

@@ -1,70 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.description = 'removes attributes of elements that match a css selector';
/**
* Removes attributes of elements that match a css selector.
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @example
* <caption>A selector removing a single attribute</caption>
* plugins:
* - removeAttributesBySelector:
* selector: "[fill='#00ff00']"
* attributes: "fill"
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
*
* <caption>A selector removing multiple attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selector: "[fill='#00ff00']"
* attributes:
* - fill
* - stroke
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
* <rect x="0" y="0" width="100" height="100"/>
*
* <caption>Multiple selectors removing attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selectors:
* - selector: "[fill='#00ff00']"
* attributes: "fill"
*
* - selector: "#remove"
* attributes:
* - stroke
* - id
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
* <rect x="0" y="0" width="100" height="100"/>
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors}
*
* @author Bradley Mease
*/
exports.fn = function(item, params) {
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
selectors.map(function(i) {
if (item.matches(i.selector)) {
item.removeAttr(i.attributes);
}
});
};

View File

@@ -1,6 +1,6 @@
'use strict';
var DEFAULT_SEPARATOR = ':';
var ELEM_SEP = ':';
exports.type = 'perItem';
@@ -9,27 +9,18 @@ exports.active = false;
exports.description = 'removes specified attributes';
exports.params = {
elemSeparator: DEFAULT_SEPARATOR,
preserveCurrentColor: false,
attrs: []
};
/**
* Remove attributes
*
* @param elemSeparator
* format: string
*
* @param preserveCurrentColor
* format: boolean
*
* @param attrs:
*
* format: [ element* : attribute* : value* ]
* format: [ element* : attribute* ]
*
* element : regexp (wrapped into ^...$), single * or omitted > all elements (must be present when value is used)
* element : regexp (wrapped into ^...$), single * or omitted > all elements
* attribute : regexp (wrapped into ^...$)
* value : regexp (wrapped into ^...$), single * or omitted > all values
*
* examples:
*
@@ -42,10 +33,6 @@ exports.params = {
* ---
* attrs: 'path:fill'
*
* > remove fill attribute on path element where value is none
* ---
* attrs: 'path:fill:none'
*
*
* > remove all fill and stroke attribute
* ---
@@ -65,10 +52,6 @@ exports.params = {
*
* attrs: '.*:(fill|stroke)'
*
* [is same as]
*
* attrs: '.*:(fill|stroke):.*'
*
*
* > remove all stroke related attributes
* ----
@@ -82,29 +65,24 @@ exports.params = {
* @author Benny Schudel
*/
exports.fn = function(item, params) {
// wrap into an array if params is not
if (!Array.isArray(params.attrs)) {
params.attrs = [params.attrs];
}
if (item.isElem()) {
var elemSeparator = typeof params.elemSeparator == 'string' ? params.elemSeparator : DEFAULT_SEPARATOR;
var preserveCurrentColor = typeof params.preserveCurrentColor == 'boolean' ? params.preserveCurrentColor : false;
// prepare patterns
var patterns = params.attrs.map(function(pattern) {
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.indexOf(elemSeparator) === -1) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
} else if (pattern.split(elemSeparator).length < 3) {
pattern = [pattern, elemSeparator, '.*'].join('');
// apply to all elements if specifc element is omitted
if (pattern.indexOf(ELEM_SEP) === -1) {
pattern = ['.*', ELEM_SEP, pattern].join('');
}
// create regexps for element, attribute name, and attribute value
return pattern.split(elemSeparator)
// create regexps for element and attribute name
return pattern.split(ELEM_SEP)
.map(function(value) {
// adjust single * to match anything
@@ -124,19 +102,10 @@ exports.fn = function(item, params) {
// loop attributes
item.eachAttr(function(attr) {
var name = attr.name;
var value = attr.value;
var isFillCurrentColor = preserveCurrentColor && name == 'fill' && value == 'currentColor';
var isStrokeCurrentColor = preserveCurrentColor && name == 'stroke' && value == 'currentColor';
if (!(isFillCurrentColor || isStrokeCurrentColor)) {
// matches attribute name
if (pattern[1].test(name)) {
// matches attribute value
if (pattern[2].test(attr.value)) {
item.removeAttr(name);
}
}
if (pattern[1].test(name)) {
item.removeAttr(name);
}
});

View File

@@ -5,12 +5,12 @@ exports.type = 'perItem';
exports.active = true;
exports.params = {
removeAny: true
removeAny: false
};
exports.description = 'removes <desc>';
exports.description = 'removes <desc> (only non-meaningful by default)';
var standardDescs = /^(Created with|Created using)/;
var standardDescs = /^Created with/;
/**
* Removes <desc>.

View File

@@ -4,15 +4,15 @@ exports.type = 'perItem';
exports.active = false;
exports.description = 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
exports.description = 'removes width and height in presence of viewBox';
/**
* Remove width/height attributes and add the viewBox attribute if it's missing
* Remove width/height attributes when a viewBox attribute is present.
*
* @example
* <svg width="100" height="50" />
* <svg width="100" height="50" viewBox="0 0 100 50">
* ↓
* <svg viewBox="0 0 100 50" />
* <svg viewBox="0 0 100 50">
*
* @param {Object} item current iteration item
* @return {Boolean} if true, with and height will be filtered out
@@ -21,29 +21,12 @@ exports.description = 'removes width and height in presence of viewBox (opposite
*/
exports.fn = function(item) {
if (item.isElem('svg')) {
if (item.hasAttr('viewBox')) {
item.removeAttr('width');
item.removeAttr('height');
} else if (
item.hasAttr('width') &&
item.hasAttr('height') &&
!isNaN(Number(item.attr('width').value)) &&
!isNaN(Number(item.attr('height').value))
) {
item.addAttr({
name: 'viewBox',
value:
'0 0 ' +
Number(item.attr('width').value) +
' ' +
Number(item.attr('height').value),
prefix: '',
local: 'viewBox'
});
item.removeAttr('width');
item.removeAttr('height');
}
if (
item.isElem('svg') &&
item.hasAttr('viewBox')
) {
item.removeAttr('width');
item.removeAttr('height');
}
};

View File

@@ -1,80 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.description = 'removes arbitrary elements by ID or className (disabled by default)';
exports.params = {
id: [],
class: []
};
/**
* Remove arbitrary SVG elements by ID or className.
*
* @param id
* examples:
*
* > single: remove element with ID of `elementID`
* ---
* removeElementsByAttr:
* id: 'elementID'
*
* > list: remove multiple elements by ID
* ---
* removeElementsByAttr:
* id:
* - 'elementID'
* - 'anotherID'
*
* @param class
* examples:
*
* > single: remove all elements with class of `elementClass`
* ---
* removeElementsByAttr:
* class: 'elementClass'
*
* > list: remove all elements with class of `elementClass` or `anotherClass`
* ---
* removeElementsByAttr:
* class:
* - 'elementClass'
* - 'anotherClass'
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @author Eli Dupuis (@elidupuis)
*/
exports.fn = function(item, params) {
var elemId, elemClass;
// wrap params in an array if not already
['id', 'class'].forEach(function(key) {
if (!Array.isArray(params[key])) {
params[key] = [ params[key] ];
}
});
// abort if current item is no an element
if (!item.isElem()) {
return;
}
// remove element if it's `id` matches configured `id` params
elemId = item.attr('id');
if (elemId) {
return params.id.indexOf(elemId.value) === -1;
}
// remove element if it's `class` contains any of the configured `class` params
elemClass = item.attr('class');
if (elemClass) {
var hasClassRegex = new RegExp(params.class.join('|'));
return !hasClassRegex.test(elemClass.value);
}
};

View File

@@ -7,7 +7,6 @@ exports.active = true;
exports.description = 'removes hidden elements (zero sized, with absent attributes)';
exports.params = {
isHidden: true,
displayNone: true,
opacity0: true,
circleR0: true,
@@ -45,15 +44,9 @@ var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\
*
* @author Kir Belevich
*/
exports.fn = function (item, params) {
exports.fn = function(item, params) {
if (item.elem) {
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
if (
params.isHidden &&
item.hasAttr('visibility', 'hidden')
) return false;
// display="none"
//

View File

@@ -8,7 +8,7 @@ exports.description = 'removes non-inheritable groups presentational attribut
var inheritableAttrs = require('./_collections').inheritableAttrs,
attrsGroups = require('./_collections').attrsGroups,
applyGroups = require('./_collections').presentationNonInheritableGroupAttrs;
excludedAttrs = ['display', 'opacity'];
/**
* Remove non-inheritable group's "presentation" attributes.
@@ -25,8 +25,11 @@ exports.fn = function(item) {
item.eachAttr(function(attr) {
if (
~attrsGroups.presentation.indexOf(attr.name) &&
!~inheritableAttrs.indexOf(attr.name) &&
!~applyGroups.indexOf(attr.name)
~attrsGroups.graphicalEvent.indexOf(attr.name) &&
~attrsGroups.core.indexOf(attr.name) &&
~attrsGroups.conditionalProcessing.indexOf(attr.name) &&
!~excludedAttrs.indexOf(attr.name) &&
!~inheritableAttrs.indexOf(attr.name)
) {
item.removeAttr(attr.name);
}

View File

@@ -1,133 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)';
var SVGO = require('../lib/svgo.js'),
_path = require('./_path.js'),
intersects = _path.intersects,
path2js = _path.path2js,
viewBox,
viewBoxJS;
/**
* Remove elements that are drawn outside of the viewbox.
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author JoshyPHP
*/
exports.fn = function(item) {
if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined')
{
// Consider that any item with a transform attribute or a M instruction
// within the viewBox is visible
if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value))
{
return true;
}
var pathJS = path2js(item);
if (pathJS.length === 2)
{
// Use a closed clone of the path if it's too short for intersects()
pathJS = JSON.parse(JSON.stringify(pathJS));
pathJS.push({ instruction: 'z' });
}
return intersects(viewBoxJS, pathJS);
}
if (item.isElem('svg'))
{
parseViewBox(item);
}
return true;
};
/**
* Test whether given item or any of its ancestors has a transform attribute.
*
* @param {String} path
* @return {Boolean}
*/
function hasTransform(item)
{
return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode));
}
/**
* Parse the viewBox coordinates and compute the JS representation of its path.
*
* @param {Object} svg svg element item
*/
function parseViewBox(svg)
{
var viewBoxData = '';
if (svg.hasAttr('viewBox'))
{
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = svg.attr('viewBox').value;
}
else if (svg.hasAttr('height') && svg.hasAttr('width'))
{
viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value;
}
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, '');
// Ensure that the dimensions are 4 values separated by space
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData);
if (!m)
{
return;
}
// Store the viewBox boundaries
viewBox = {
left: parseFloat(m[1]),
top: parseFloat(m[2]),
right: parseFloat(m[1]) + parseFloat(m[3]),
bottom: parseFloat(m[2]) + parseFloat(m[4])
};
var path = new SVGO().createContentItem({
elem: 'path',
prefix: '',
local: 'path'
});
path.addAttr({
name: 'd',
prefix: '',
local: 'd',
value: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z'
});
viewBoxJS = path2js(path);
}
/**
* Test whether given path has a M instruction with coordinates within the viewBox.
*
* @param {String} path
* @return {Boolean}
*/
function pathMovesWithinViewBox(path)
{
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m;
while (null !== (m = regexp.exec(path)))
{
if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom)
{
return true;
}
}
return false;
}

View File

@@ -1,23 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.description = 'removes <script> elements (disabled by default)';
/**
* Remove <script>.
*
* https://www.w3.org/TR/SVG/script.html
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author Patrick Klingemann
*/
exports.fn = function(item) {
return !item.isElem('script');
};

View File

@@ -2,12 +2,13 @@
exports.type = 'perItem';
exports.active = true;
exports.active = false;
exports.description = 'removes <title>';
exports.description = 'removes <title> (disabled by default)';
/**
* Remove <title>.
* Disabled by default cause it may be used for accessibility.
*
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
*

View File

@@ -11,9 +11,7 @@ exports.params = {
unknownAttrs: true,
defaultAttrs: true,
uselessOverrides: true,
keepDataAttrs: true,
keepAriaAttrs: true,
keepRoleAttr: false
keepDataAttrs: true
};
var collections = require('./_collections'),
@@ -21,8 +19,7 @@ var collections = require('./_collections'),
attrsGroups = collections.attrsGroups,
elemsGroups = collections.elemsGroups,
attrsGroupsDefaults = collections.attrsGroupsDefaults,
attrsInheritable = collections.inheritableAttrs,
applyGroups = collections.presentationNonInheritableGroupAttrs;
attrsInheritable = collections.inheritableAttrs;
// collect and extend all references
for (var elem in elems) {
@@ -108,9 +105,7 @@ exports.fn = function(item, params) {
if (
attr.name !== 'xmlns' &&
(attr.prefix === 'xml' || !attr.prefix) &&
(!params.keepDataAttrs || attr.name.indexOf('data-') != 0) &&
(!params.keepAriaAttrs || attr.name.indexOf('aria-') != 0) &&
(!params.keepRoleAttr || attr.name != 'role')
(!params.keepDataAttrs || attr.name.indexOf('data-') != 0)
) {
if (
// unknown attrs
@@ -121,7 +116,6 @@ exports.fn = function(item, params) {
// attrs with default values
(
params.defaultAttrs &&
!item.hasAttr('id') &&
elems[elem].defaults &&
elems[elem].defaults[attr.name] === attr.value && (
attrsInheritable.indexOf(attr.name) < 0 ||
@@ -131,8 +125,6 @@ exports.fn = function(item, params) {
// useless overrides
(
params.uselessOverrides &&
!item.hasAttr('id') &&
applyGroups.indexOf(attr.name) < 0 &&
attrsInheritable.indexOf(attr.name) > -1 &&
item.parentNode.computedAttr(attr.name, attr.value)
)

View File

@@ -66,9 +66,7 @@ exports.fn = function(data) {
svgElem = item;
}
}
if (xmlnsCollection.length) {
} else if (xmlnsCollection.length) {
// check item for the ns-attrs
if (item.prefix) {

View File

@@ -6,7 +6,8 @@ exports.active = true;
exports.description = 'removes elements in <defs> without id';
var nonRendering = require('./_collections').elemsGroups.nonRendering;
var nonRendering = require('./_collections').elemsGroups.nonRendering,
defs;
/**
* Removes content of defs and properties that aren't rendered directly without ids.
@@ -20,10 +21,9 @@ exports.fn = function(item) {
if (item.isElem('defs')) {
if (item.content) {
item.content = getUsefulItems(item, []);
}
defs = item;
item.content = (item.content || []).reduce(getUsefulItems, []);
if (item.isEmpty()) return false;
} else if (item.isElem(nonRendering) && !item.hasAttr('id')) {
@@ -34,20 +34,18 @@ exports.fn = function(item) {
};
function getUsefulItems(item, usefulItems) {
function getUsefulItems(usefulItems, item) {
item.content.forEach(function(child) {
if (child.hasAttr('id') || child.isElem('style')) {
if (item.hasAttr('id') || item.isElem('style')) {
usefulItems.push(child);
child.parentNode = item;
usefulItems.push(item);
item.parentNode = defs;
} else if (!child.isEmpty()) {
} else if (!item.isEmpty()) {
child.content = getUsefulItems(child, usefulItems);
item.content.reduce(getUsefulItems, usefulItems);
}
});
}
return usefulItems;
}

View File

@@ -8,15 +8,14 @@ exports.description = 'removes useless stroke and fill attributes';
exports.params = {
stroke: true,
fill: true,
removeNone: false,
hasStyleOrScript: false
fill: true
};
var shape = require('./_collections').elemsGroups.shape,
regStrokeProps = /^stroke/,
regFillProps = /^fill-/,
styleOrScript = ['style', 'script'];
styleOrScript = ['style', 'script'],
hasStyleOrScript = false;
/**
* Remove useless stroke and fill attrs.
@@ -28,12 +27,12 @@ var shape = require('./_collections').elemsGroups.shape,
* @author Kir Belevich
*/
exports.fn = function(item, params) {
if (item.isElem(styleOrScript)) {
params.hasStyleOrScript = true;
hasStyleOrScript = true;
}
if (!params.hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) {
if (!hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) {
var stroke = params.stroke && item.computedAttr('stroke'),
fill = params.fill && !item.computedAttr('fill', 'none');
@@ -88,13 +87,6 @@ exports.fn = function(item, params) {
}
}
if (params.removeNone &&
(!stroke || item.hasAttr('stroke') && item.attr('stroke').value=='none') &&
(!fill || item.hasAttr('fill') && item.attr('fill').value=='none')) {
return false;
}
}
};

View File

@@ -2,11 +2,12 @@
exports.type = 'perItem';
exports.active = true;
exports.active = false;
exports.description = 'removes viewBox attribute when possible';
exports.description = 'removes viewBox attribute when possible (disabled by default)';
var viewBoxElems = ['svg', 'pattern', 'symbol'];
var regViewBox = /^0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/,
viewBoxElems = ['svg', 'pattern'];
/**
* Remove viewBox attr which coincides with a width/height box.
@@ -32,15 +33,15 @@ exports.fn = function(item) {
item.hasAttr('height')
) {
var nums = item.attr('viewBox').value.split(/[ ,]+/g);
var match = item.attr('viewBox').value.match(regViewBox);
if (
nums[0] === '0' &&
nums[1] === '0' &&
item.attr('width').value.replace(/px$/, '') === nums[2] && // could use parseFloat too
item.attr('height').value.replace(/px$/, '') === nums[3]
) {
item.removeAttr('viewBox');
if (match) {
if (
item.attr('width').value === match[1] &&
item.attr('height').value === match[3]
) {
item.removeAttr('viewBox');
}
}
}

View File

@@ -1,28 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
exports.description = 'removes xmlns attribute (for inline svg, disabled by default)';
/**
* Remove the xmlns attribute when present.
*
* @example
* <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
* ↓
* <svg viewBox="0 0 100 50">
*
* @param {Object} item current iteration item
* @return {Boolean} if true, xmlns will be filtered out
*
* @author Ricardo Tomasi
*/
exports.fn = function(item) {
if (item.isElem('svg') && item.hasAttr('xmlns')) {
item.removeAttr('xmlns');
}
};

View File

@@ -1,168 +0,0 @@
/**
* @license
* The MIT License
*
* Copyright © 20122016 Kir Belevich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Лицензия MIT
*
* Copyright © 20122016 Кир Белевич
*
* Данная лицензия разрешает лицам, получившим копию
* данного
* программного обеспечения и сопутствующей
* документации
* (в дальнейшем именуемыми «Программное Обеспечение»),
* безвозмездно
* использовать Программное Обеспечение без
* ограничений, включая
* неограниченное право на использование, копирование,
* изменение,
* добавление, публикацию, распространение,
* сублицензирование
* и/или продажу копий Программного Обеспечения, также
* как и лицам,
* которым предоставляется данное Программное
* Обеспечение,
* при соблюдении следующих условий:
*
* Указанное выше уведомление об авторском праве и
* данные условия
* должны быть включены во все копии или значимые части
* данного
* Программного Обеспечения.
*
* ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК
* ЕСТЬ»,
* БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ
* ПОДРАЗУМЕВАЕМЫХ,
* ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ
* ПРИГОДНОСТИ,
* СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
* ОТСУТСТВИЯ НАРУШЕНИЙ
* ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
* НЕСУТ
* ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ
* ИЛИ ДРУГИХ
* ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ
* ИНОМУ,
* ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С
* ПРОГРАММНЫМ
* ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО
* ОБЕСПЕЧЕНИЯ
* ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
*/
'use strict';
var JSAPI = require('../lib/svgo/jsAPI');
exports.type = 'full';
exports.active = false;
exports.description = 'Finds <path> elements with the same d, fill, and ' +
'stroke, and converts them to <use> elements ' +
'referencing a single <path> def.';
/**
* Finds <path> elements with the same d, fill, and stroke, and converts them to
* <use> elements referencing a single <path> def.
*
* @author Jacob Howcroft
*/
exports.fn = function(data) {
const seen = new Map();
let count = 0;
const defs = [];
traverse(data, item => {
if (!item.isElem('path') || !item.hasAttr('d')) {
return;
}
const d = item.attr('d').value;
const fill = (item.hasAttr('fill') && item.attr('fill').value) || '';
const stroke = (item.hasAttr('stroke') && item.attr('stroke').value) || '';
const key = d + ';s:' + stroke + ';f:' + fill;
const hasSeen = seen.get(key);
if (!hasSeen) {
seen.set(key, {elem: item, reused: false});
return;
}
if (!hasSeen.reused) {
hasSeen.reused = true;
if (!hasSeen.elem.hasAttr('id')) {
hasSeen.elem.addAttr({name: 'id', local: 'id',
prefix: '', value: 'reuse-' + (count++)});
}
defs.push(hasSeen.elem);
}
item = convertToUse(item, hasSeen.elem.attr('id').value);
});
const defsTag = new JSAPI({
elem: 'defs', prefix: '', local: 'defs', content: [], attrs: []}, data);
data.content[0].spliceContent(0, 0, defsTag);
for (let def of defs) {
// Remove class and style before copying to avoid circular refs in
// JSON.stringify. This is fine because we don't actually want class or
// style information to be copied.
const style = def.style;
const defClass = def.class;
delete def.style;
delete def.class;
const defClone = def.clone();
def.style = style;
def.class = defClass;
defClone.removeAttr('transform');
defsTag.spliceContent(0, 0, defClone);
// Convert the original def to a use so the first usage isn't duplicated.
def = convertToUse(def, defClone.attr('id').value);
def.removeAttr('id');
}
return data;
};
/** */
function convertToUse(item, href) {
item.renameElem('use');
item.removeAttr('d');
item.removeAttr('stroke');
item.removeAttr('fill');
item.addAttr({name: 'xlink:href', local: 'xlink:href',
prefix: 'none', value: '#' + href});
delete item.pathJS;
return item;
}
/** */
function traverse(parent, callback) {
if (parent.isEmpty()) {
return;
}
for (let child of parent.content) {
callback(child);
traverse(child, callback);
}
}

View File

@@ -8,12 +8,14 @@ exports.description = 'sorts element attributes (disabled by default)';
exports.params = {
order: [
'xmlns',
'id',
'width', 'height',
'x', 'x1', 'x2',
'y', 'y1', 'y2',
'cx', 'cy', 'r',
'fill', 'stroke', 'marker',
'fill', 'fill-opacity', 'fill-rule',
'stroke', 'stroke-opacity', 'stroke-width', 'stroke-miterlimit', 'stroke-dashoffset',
'd', 'points'
]
};
@@ -30,8 +32,7 @@ exports.fn = function(item, params) {
var attrs = [],
sorted = {},
orderlen = params.order.length + 1,
xmlnsOrder = params.xmlnsOrder || 'front';
orderlen = params.order.length + 1;
if (item.elem) {
@@ -40,37 +41,8 @@ exports.fn = function(item, params) {
});
attrs.sort(function(a, b) {
if (a.prefix != b.prefix) {
// xmlns attributes implicitly have the prefix xmlns
if (xmlnsOrder == 'front') {
if (a.prefix == 'xmlns')
return -1;
if (b.prefix == 'xmlns')
return 1;
}
return a.prefix < b.prefix ? -1 : 1;
}
var aindex = orderlen;
var bindex = orderlen;
for (var i = 0; i < params.order.length; i++) {
if (a.name == params.order[i]) {
aindex = i;
} else if (a.name.indexOf(params.order[i] + '-') === 0) {
aindex = i + .5;
}
if (b.name == params.order[i]) {
bindex = i;
} else if (b.name.indexOf(params.order[i] + '-') === 0) {
bindex = i + .5;
}
}
if (aindex != bindex) {
return aindex - bindex;
}
return a.name < b.name ? -1 : 1;
return ((a = params.order.indexOf(a.name)) > -1 ? a : orderlen) -
((b = params.order.indexOf(b.name)) > -1 ? b : orderlen);
});
attrs.forEach(function (attr) {

View File

@@ -1,47 +0,0 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.description = 'Sorts children of <defs> to improve compression';
/**
* Sorts children of defs in order to improve compression.
* Sorted first by frequency then by element name length then by element name (to ensure grouping).
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author David Leston
*/
exports.fn = function(item) {
if (item.isElem('defs')) {
if (item.content) {
var frequency = item.content.reduce(function (frequency, child) {
if (child.elem in frequency) {
frequency[child.elem]++;
} else {
frequency[child.elem] = 1;
}
return frequency;
}, {});
item.content.sort(function (a, b) {
var frequencyComparison = frequency[b.elem] - frequency[a.elem];
if (frequencyComparison !== 0 ) {
return frequencyComparison;
}
var lengthComparison = b.elem.length - a.elem.length;
if (lengthComparison !== 0) {
return lengthComparison;
}
return a.elem != b.elem ? a.elem > b.elem ? -1 : 1 : 0;
});
}
return true;
}
};

327
node_modules/svgo/plugins/transformsWithOnePath.js generated vendored Normal file
View File

@@ -0,0 +1,327 @@
'use strict';
/*
* Thanks to http://fontello.com project for sponsoring this plugin
*/
exports.type = 'full';
exports.active = false;
exports.description = 'performs a set of operations on SVG with one path inside (disabled by default)';
exports.params = {
// width and height to resize SVG and rescale inner Path
width: false,
height: false,
// scale inner Path without resizing SVG
scale: false,
// shiftX/Y inner Path
shiftX: false,
shiftY: false,
// crop SVG width along the real width of inner Path
hcrop: false,
// vertical center inner Path inside SVG height
vcenter: false,
// stringify params
floatPrecision: 3,
leadingZero: true,
negativeExtraSpace: true
};
var _path = require('./_path.js'),
relative2absolute = _path.relative2absolute,
computeCubicBoundingBox = _path.computeCubicBoundingBox,
computeQuadraticBoundingBox = _path.computeQuadraticBoundingBox,
applyTransforms = _path.applyTransforms,
js2path = _path.js2path,
path2js = _path.path2js,
EXTEND = require('whet.extend');
exports.fn = function(data, params) {
data.content.forEach(function(item) {
// only for SVG with one Path inside
if (item.isElem('svg') &&
item.content.length === 1 &&
item.content[0].isElem('path')
) {
var svgElem = item,
pathElem = svgElem.content[0],
// get absoluted Path data
path = relative2absolute(EXTEND(true, [], path2js(pathElem))),
xs = [],
ys = [],
cubicСontrolPoint = [0, 0],
quadraticСontrolPoint = [0, 0],
lastPoint = [0, 0],
cubicBoundingBox,
quadraticBoundingBox,
i,
segment;
path.forEach(function(pathItem) {
// ML
if ('ML'.indexOf(pathItem.instruction) > -1) {
for (i = 0; i < pathItem.data.length; i++) {
if (i % 2 === 0) {
xs.push(pathItem.data[i]);
} else {
ys.push(pathItem.data[i]);
}
}
lastPoint = cubicСontrolPoint = quadraticСontrolPoint = pathItem.data.slice(-2);
// H
} else if (pathItem.instruction === 'H') {
pathItem.data.forEach(function(d) {
xs.push(d);
});
lastPoint[0] = cubicСontrolPoint[0] = quadraticСontrolPoint[0] = pathItem.data[pathItem.data.length - 2];
// V
} else if (pathItem.instruction === 'V') {
pathItem.data.forEach(function(d) {
ys.push(d);
});
lastPoint[1] = cubicСontrolPoint[1] = quadraticСontrolPoint[1] = pathItem.data[pathItem.data.length - 1];
// C
} else if (pathItem.instruction === 'C') {
for (i = 0; i < pathItem.data.length; i += 6) {
segment = pathItem.data.slice(i, i + 6);
cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(segment));
xs.push(cubicBoundingBox.minx);
xs.push(cubicBoundingBox.maxx);
ys.push(cubicBoundingBox.miny);
ys.push(cubicBoundingBox.maxy);
// reflected control point for the next possible S
cubicСontrolPoint = [
2 * segment[4] - segment[2],
2 * segment[5] - segment[3]
];
lastPoint = segment.slice(-2);
}
// S
} else if (pathItem.instruction === 'S') {
for (i = 0; i < pathItem.data.length; i += 4) {
segment = pathItem.data.slice(i, i + 4);
cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(cubicСontrolPoint).concat(segment));
xs.push(cubicBoundingBox.minx);
xs.push(cubicBoundingBox.maxx);
ys.push(cubicBoundingBox.miny);
ys.push(cubicBoundingBox.maxy);
// reflected control point for the next possible S
cubicСontrolPoint = [
2 * segment[2] - cubicСontrolPoint[0],
2 * segment[3] - cubicСontrolPoint[1],
];
lastPoint = segment.slice(-2);
}
// Q
} else if (pathItem.instruction === 'Q') {
for (i = 0; i < pathItem.data.length; i += 4) {
segment = pathItem.data.slice(i, i + 4);
quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(segment));
xs.push(quadraticBoundingBox.minx);
xs.push(quadraticBoundingBox.maxx);
ys.push(quadraticBoundingBox.miny);
ys.push(quadraticBoundingBox.maxy);
// reflected control point for the next possible T
quadraticСontrolPoint = [
2 * segment[2] - segment[0],
2 * segment[3] - segment[1]
];
lastPoint = segment.slice(-2);
}
// S
} else if (pathItem.instruction === 'T') {
for (i = 0; i < pathItem.data.length; i += 2) {
segment = pathItem.data.slice(i, i + 2);
quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(quadraticСontrolPoint).concat(segment));
xs.push(quadraticBoundingBox.minx);
xs.push(quadraticBoundingBox.maxx);
ys.push(quadraticBoundingBox.miny);
ys.push(quadraticBoundingBox.maxy);
// reflected control point for the next possible T
quadraticСontrolPoint = [
2 * segment[0] - quadraticСontrolPoint[0],
2 * segment[1] - quadraticСontrolPoint[1]
];
lastPoint = segment.slice(-2);
}
}
});
var xmin = Math.min.apply(this, xs).toFixed(params.floatPrecision),
xmax = Math.max.apply(this, xs).toFixed(params.floatPrecision),
ymin = Math.min.apply(this, ys).toFixed(params.floatPrecision),
ymax = Math.max.apply(this, ys).toFixed(params.floatPrecision),
svgWidth = +svgElem.attr('width').value,
svgHeight = +svgElem.attr('height').value,
realWidth = Math.round(xmax - xmin),
realHeight = Math.round(ymax - ymin),
transform = '',
scale;
// width & height
if (params.width && params.height) {
scale = Math.min(params.width / svgWidth, params.height / svgHeight);
realWidth = realWidth * scale;
realHeight = realHeight * scale;
svgWidth = svgElem.attr('width').value = params.width;
svgHeight = svgElem.attr('height').value = params.height;
transform += ' scale(' + scale + ')';
// width
} else if (params.width && !params.height) {
scale = params.width / svgWidth;
realWidth = realWidth * scale;
realHeight = realHeight * scale;
svgWidth = svgElem.attr('width').value = params.width;
svgHeight = svgElem.attr('height').value = svgHeight * scale;
transform += ' scale(' + scale + ')';
// height
} else if (params.height && !params.width) {
scale = params.height / svgHeight;
realWidth = realWidth * scale;
realHeight = realHeight * scale;
svgWidth = svgElem.attr('width').value = svgWidth * scale;
svgHeight = svgElem.attr('height').value = params.height;
transform += ' scale(' + scale + ')';
}
// shiftX
if (params.shiftX) {
transform += ' translate(' + realWidth * params.shiftX + ', 0)';
}
// shiftY
if (params.shiftY) {
transform += ' translate(0, ' + realHeight * params.shiftY + ')';
}
// scale
if (params.scale) {
scale = params.scale;
var shiftX = svgWidth / 2,
shiftY = svgHeight / 2;
realWidth = realWidth * scale;
realHeight = realHeight * scale;
if (params.shiftX || params.shiftY) {
transform += ' scale(' + scale + ')';
} else {
transform += ' translate(' + shiftX + ' ' + shiftY + ') scale(' + scale + ') translate(-' + shiftX + ' -' + shiftY + ')';
}
}
// hcrop
if (params.hcrop) {
transform += ' translate(' + (-xmin) + ' 0)';
svgElem.attr('width').value = realWidth;
}
// vcenter
if (params.vcenter) {
transform += ' translate(0 ' + (((svgHeight - realHeight) / 2) - ymin) + ')';
}
if (transform) {
pathElem.addAttr({
name: 'transform',
prefix: '',
local: 'transform',
value: transform
});
path = applyTransforms(pathElem, pathElem.pathJS, true, params.floatPrecision);
// transformed data rounding
path.forEach(function(pathItem) {
if (pathItem.data) {
pathItem.data = pathItem.data.map(function(num) {
return +num.toFixed(params.floatPrecision);
});
}
});
// save new
js2path(pathElem, path, params);
}
}
});
return data;
};