Socket
Socket
Sign inDemoInstall

svgo

Package Overview
Dependencies
Maintainers
3
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

svgo - npm Package Compare versions

Comparing version 2.2.0 to 2.2.1

lib/style.js

20

lib/css-tools.js

@@ -11,3 +11,3 @@ 'use strict';

*
* @param {Object} cssAst css-tree AST to flatten
* @param {import('css-tree').CssNode} cssAst css-tree AST to flatten
* @return {Array} selectors

@@ -33,3 +33,3 @@ */

rule: rule,
pseudos: [],
pseudos: /** @type {{item: any; list: any[]}[]} */ ([]),
};

@@ -113,3 +113,3 @@

* @param {Array} selectors to clean
* @return {Array} Selectors without pseudo-elements and/or -classes
* @return {void}
*/

@@ -130,3 +130,3 @@ function cleanPseudos(selectors) {

* @param {Array} bSpecificity Specificity of selector B
* @return {Number} Score of selector specificity A compared to selector specificity B
* @return {number} Score of selector specificity A compared to selector specificity B
*/

@@ -150,3 +150,3 @@ function compareSpecificity(aSpecificity, bSpecificity) {

* @param {Object} bSimpleSelectorNode Simple selector B
* @return {Number} Score of selector A compared to selector B
* @return {number} Score of selector A compared to selector B
*/

@@ -176,3 +176,3 @@ function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {

*
* @param {Object} declaration css-tree style declaration
* @param {import('css-tree').CssNode} declaration css-tree style declaration
* @return {Object} CSSStyleDeclaration property

@@ -194,4 +194,4 @@ */

*
* @param {Object} element style element
* @return {String|Array} CSS string or empty array if no styles are set
* @param {Object} elem style element
* @return {string|Array} CSS string or empty array if no styles are set
*/

@@ -205,4 +205,4 @@ function getCssStr(elem) {

*
* @param {Object} element style element
* @param {String} CSS string to be set
* @param {Object} elem style element
* @param {string} css string to be set
* @return {Object} reference to field with CSS

@@ -209,0 +209,0 @@ */

@@ -28,2 +28,5 @@ 'use strict';

/**
* @param {string} c
*/
const isCommand = (c) => {

@@ -33,2 +36,5 @@ return c in argsCountPerCommand;

/**
* @param {string} c
*/
const isWsp = (c) => {

@@ -44,12 +50,26 @@ const codePoint = c.codePointAt(0);

/**
* @param {string} c
*/
const isDigit = (c) => {
const codePoint = c.codePointAt(0);
if (codePoint == null) {
return false;
}
return 48 <= codePoint && codePoint <= 57;
};
/**
* @typedef {'none' | 'sign' | 'whole' | 'decimal_point' | 'decimal' | 'e' | 'exponent_sign' | 'exponent'} ReadNumberState
*/
/**
* @param {string} string
* @param {number} cursor
* @return {[number, number | null]}
*/
const readNumber = (string, cursor) => {
let i = cursor;
let value = '';
// none | sign | whole | decimal_point | decimal | e | exponent_sign | exponent
let state = 'none';
let state = /** @type {ReadNumberState} */ ('none');
for (; i < string.length; i += 1) {

@@ -64,3 +84,3 @@ const c = string[i];

if (state === 'e') {
state === 'exponent_sign';
state = 'exponent_sign';
value += c;

@@ -116,11 +136,13 @@ continue;

/**
* @param {string} string
*/
const parsePathData = (string) => {
const pathData = [];
let i = 0;
let command = null;
let args;
let argsCount;
let args = /** @type {number[]} */ ([]);
let argsCount = 0;
let canHaveComma = false;
let hadComma = false;
for (; i < string.length; i += 1) {
for (let i = 0; i < string.length; i += 1) {
const c = string.charAt(i);

@@ -217,2 +239,11 @@ if (isWsp(c)) {

/**
* @typedef {{
* number: number;
* precision?: number;
* }} StringifyNumberOptions
*/
/**
* @param {StringifyNumberOptions} param
*/
const stringifyNumber = ({ number, precision }) => {

@@ -225,3 +256,3 @@ let result;

if (result.includes('.')) {
result = result.replace(/\.?0+$/, '')
result = result.replace(/\.?0+$/, '');
}

@@ -236,14 +267,40 @@ }

// elliptical arc large-arc and sweep flags are rendered with spaces
// because many non-browser environments are not able to parse such paths
const stringifyArgs = ({ args, precision }) => {
/**
* @typedef {{
* command: string;
* args: number[];
* precision?: number;
* disableSpaceAfterFlags?: boolean;
* }} StringifyArgsOptions
*/
/**
*
* Elliptical arc large-arc and sweep flags are rendered with spaces
* because many non-browser environments are not able to parse such paths
*
* @param {StringifyArgsOptions} param
*/
const stringifyArgs = ({
command,
args,
precision,
disableSpaceAfterFlags,
}) => {
let result = '';
let prev;
let prev = '';
for (let i = 0; i < args.length; i += 1) {
const number = args[i];
const numberString = stringifyNumber({ number, precision });
// avoid space before first and negative numbers
if (i === 0 || numberString.startsWith('-')) {
if (
disableSpaceAfterFlags &&
(command === 'A' || command === 'a') &&
(i === 4 || i === 5)
) {
result += numberString;
} else if (i === 0 || numberString.startsWith('-')) {
// avoid space before first and negative numbers
result += numberString;
} else if (prev.includes('.') && numberString.startsWith('.')) {
// remove space before decimal with zero whole
// only when previous number is also decimal
result += numberString;

@@ -258,3 +315,20 @@ } else {

const stringifyPathData = ({ pathData, precision }) => {
/**
*
* @typedef {{
* command: string;
* args: number[];
* }} Command
*/
/**
* @typedef {{
* pathData: Command[];
* precision?: number;
* disableSpaceAfterFlags?: boolean;
* }} StringifyPathDataOptions
*/
/**
* @param {StringifyPathDataOptions} param
*/
const stringifyPathData = ({ pathData, precision, disableSpaceAfterFlags }) => {
// combine sequence of the same commands

@@ -293,3 +367,5 @@ let combined = [];

for (const { command, args } of combined) {
result += command + stringifyArgs({ args, precision });
result +=
command +
stringifyArgs({ command, args, precision, disableSpaceAfterFlags });
}

@@ -296,0 +372,0 @@ return result;

@@ -50,2 +50,7 @@ 'use strict';

});
it('should stop on invalid scientific notation', () => {
expect(parsePathData('M 0 5e++1 L 0 0')).to.deep.equal([
{ command: 'M', args: [0, 5] },
]);
});
it('should stop on invalid numbers', () => {

@@ -62,4 +67,4 @@ expect(parsePathData('M ...')).to.deep.equal([]);

25,50 -30 0,1 50,-25
25,75 -30 0,1 50,-25
a25,100 -30 0,1 50,-25
25,75 -30 01.2,-25
a25,100 -30 0150,-25
l 50,-25

@@ -73,3 +78,3 @@ `

{ command: 'a', args: [25, 50, -30, 0, 1, 50, -25] },
{ command: 'a', args: [25, 75, -30, 0, 1, 50, -25] },
{ command: 'a', args: [25, 75, -30, 0, 1, 0.2, -25] },
{ command: 'a', args: [25, 100, -30, 0, 1, 50, -25] },

@@ -164,2 +169,21 @@ { command: 'l', args: [50, -25] },

});
it('allows to avoid spaces after arc flags', () => {
const pathData = [
{ command: 'M', args: [0, 0] },
{ command: 'A', args: [50, 50, 10, 1, 0, 0.2, 20] },
{ command: 'a', args: [50, 50, 10, 1, 0, 0.2, 20] },
];
expect(
stringifyPathData({
pathData,
disableSpaceAfterFlags: false,
})
).to.equal('M0 0A50 50 10 1 0 .2 20a50 50 10 1 0 .2 20');
expect(
stringifyPathData({
pathData,
disableSpaceAfterFlags: true,
})
).to.equal('M0 0A50 50 10 10.2 20a50 50 10 10.2 20');
});
});

@@ -104,3 +104,3 @@ 'use strict';

...plugin,
params: { configParams, ...plugin.params },
params: { ...configParams, ...plugin.params },
};

@@ -107,0 +107,0 @@ } else {

'use strict';
var baseCssAdapter = require('css-select-base-adapter');
/**
* DOMUtils API for SVGO AST (used by css-select)
* @param {any} node
* @return {node is any}
*/
var svgoCssSelectAdapterMin = {
// is the node a tag?
// isTag: ( node:Node ) => isTag:Boolean
isTag: function (node) {
return node.isElem();
},
const isTag = (node) => {
return node.isElem();
};
// get the parent of the node
// getParent: ( node:Node ) => parentNode:Node
// returns null when no parent exists
getParent: function (node) {
return node.parentNode || null;
},
const existsOne = (test, elems) => {
return elems.some((elem) => {
if (isTag(elem)) {
return test(elem) || existsOne(test, getChildren(elem));
} else {
return false;
}
});
};
// get the node's children
// getChildren: ( node:Node ) => children:[Node]
getChildren: function (node) {
return node.content || [];
},
const getAttributeValue = (elem, name) => {
return elem.hasAttr(name) ? elem.attr(name).value : undefined;
};
// get the name of the tag
// getName: ( elem:ElementNode ) => tagName:String
getName: function (elemAst) {
return elemAst.elem;
},
const getChildren = (node) => {
return node.content || [];
};
// get the text content of the node, and its children if it has any
// getText: ( node:Node ) => text:String
// returns empty string when there is no text
getText: function (node) {
return node.content[0].text || node.content[0].cdata || '';
},
const getName = (elemAst) => {
return elemAst.elem;
};
// get the attribute value
// getAttributeValue: ( elem:ElementNode, name:String ) => value:String
// returns null when attribute doesn't exist
getAttributeValue: function (elem, name) {
return elem.hasAttr(name) ? elem.attr(name).value : null;
},
const getParent = (node) => {
return node.parentNode || null;
};
// use base adapter for default implementation
var svgoCssSelectAdapter = baseCssAdapter(svgoCssSelectAdapterMin);
const getSiblings = (elem) => {
var parent = getParent(elem);
return parent ? getChildren(parent) : [];
};
const getText = (node) => {
return node.content[0].text || node.content[0].cdata || '';
};
const hasAttrib = (elem, name) => {
return getAttributeValue(elem, name) !== undefined;
};
const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
let ancestor;
let replace;
// Check if each node (or one of its ancestors) is already contained in the
// array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.includes(ancestor)) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor);
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
};
const findAll = (test, elems) => {
const result = [];
for (const elem of elems) {
if (isTag(elem)) {
if (test(elem)) {
result.push(elem);
}
result.push(...findAll(test, getChildren(elem)));
}
}
return result;
};
const findOne = (test, elems) => {
for (const elem of elems) {
if (isTag(elem)) {
if (test(elem)) {
return elem;
}
const result = findOne(test, getChildren(elem));
if (result) {
return result;
}
}
}
return null;
};
const svgoCssSelectAdapter = {
isTag,
existsOne,
getAttributeValue,
getChildren,
getName,
getParent,
getSiblings,
getText,
hasAttrib,
removeSubsets,
findAll,
findOne,
};
module.exports = svgoCssSelectAdapter;

@@ -142,3 +142,3 @@ 'use strict';

*
* @return {String} Textual representation of the declaration block (empty string for no properties)
* @return {string} Textual representation of the declaration block (empty string for no properties)
*/

@@ -187,4 +187,4 @@ CSSStyleDeclaration.prototype.getCssText = function () {

*
* @param {String} propertyName representing the property name to be checked.
* @return {String} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.
* @param {string} propertyName representing the property name to be checked.
* @return {string} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.
*/

@@ -199,4 +199,4 @@ CSSStyleDeclaration.prototype.getPropertyPriority = function (propertyName) {

*
* @param {String} propertyName representing the property name to be checked.
* @return {String} value containing the value of the property. If not set, returns the empty string.
* @param {string} propertyName representing the property name to be checked.
* @return {string} value containing the value of the property. If not set, returns the empty string.
*/

@@ -211,4 +211,4 @@ CSSStyleDeclaration.prototype.getPropertyValue = function (propertyName) {

*
* @param {Number} index of the node to be fetched. The index is zero-based.
* @return {String} propertyName that is the name of the CSS property at the specified index.
* @param {number} index of the node to be fetched. The index is zero-based.
* @return {string} propertyName that is the name of the CSS property at the specified index.
*/

@@ -241,4 +241,4 @@ CSSStyleDeclaration.prototype.item = function (index) {

*
* @param {String} propertyName representing the property name to be removed.
* @return {String} oldValue equal to the value of the CSS property before it was removed.
* @param {string} propertyName representing the property name to be removed.
* @return {string} oldValue equal to the value of the CSS property before it was removed.
*/

@@ -263,6 +263,6 @@ CSSStyleDeclaration.prototype.removeProperty = function (propertyName) {

*
* @param {String} propertyName representing the CSS property name to be modified.
* @param {String} [value] containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
* @param {String} [priority] allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
* @return {undefined}
* @param {string} propertyName representing the CSS property name to be modified.
* @param {string} value containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
* @param {string} priority allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
* @return {{value: string, priority: string}}
*/

@@ -269,0 +269,0 @@ CSSStyleDeclaration.prototype.setProperty = function (

@@ -10,3 +10,3 @@ 'use strict';

* @param {Object} info extra information
* @param {Object} plugins plugins object from config
* @param {Array} plugins plugins object from config
* @return {Object} output data

@@ -49,3 +49,3 @@ */

* @param {Array} plugins plugins list to process
* @param {Boolean} [reverse] reverse pass?
* @param {boolean} [reverse] reverse pass?
* @return {Object} output data

@@ -52,0 +52,0 @@ */

@@ -6,5 +6,5 @@ 'use strict';

*
* @param {String} str input string
* @param {String} type Data URI type
* @return {String} output string
* @param {string} str input string
* @param {string} type Data URI type
* @return {string} output string
*/

@@ -31,3 +31,3 @@ exports.encodeSVGDatauri = function (str, type) {

* @param {string} str input string
* @return {String} output string
* @return {string} output string
*/

@@ -56,2 +56,6 @@ exports.decodeSVGDatauri = function (str) {

/**
* @param {any[]} a
* @param {any[]} b
*/
exports.intersectArrays = function (a, b) {

@@ -71,3 +75,3 @@ return a.filter(function (n) {

* @param {Object} params
* @param {string?} command path data instruction
* @param {string} [command] path data instruction
* @return {string}

@@ -97,5 +101,5 @@ */

// -0.5 → -.5
if (params.leadingZero) {
item = removeLeadingZero(item);
}
const itemStr = params.leadingZero
? removeLeadingZero(item)
: item.toString();

@@ -107,3 +111,3 @@ // no extra space in front of negative number or

delimiter != '' &&
(item < 0 || (String(item).charCodeAt(0) == 46 && prev % 1 !== 0))
(item < 0 || (itemStr.charCodeAt(0) == 46 && prev % 1 !== 0))
) {

@@ -114,3 +118,3 @@ delimiter = '';

prev = item;
str += delimiter + item;
str += delimiter + itemStr;
});

@@ -129,5 +133,5 @@ return str;

*
* @param {Float} num input number
* @param {number} num input number
*
* @return {String} output number as string
* @return {string} output number as string
*/

@@ -134,0 +138,0 @@ var removeLeadingZero = function (num) {

{
"name": "svgo",
"version": "2.2.0",
"version": "2.2.1",
"description": "Nodejs-based tool for optimizing SVG vector graphics files",

@@ -54,3 +54,5 @@ "keywords": [

"lint": "eslint .",
"typecheck": "tsc",
"test-browser": "rollup -c && node ./test/browser.js",
"test-regression": "NO_DIFF=1 node ./test/regression.js",
"prepublishOnly": "rm -rf dist && rollup -c"

@@ -101,3 +103,2 @@ },

"css-select": "^3.1.2",
"css-select-base-adapter": "^0.1.1",
"css-tree": "^1.1.2",

@@ -111,2 +112,3 @@ "csso": "^4.2.0",

"@rollup/plugin-node-resolve": "^11.2.0",
"@types/mocha": "^8.2.1",
"c8": "^7.6.0",

@@ -116,7 +118,13 @@ "chai": "^4.3.0",

"eslint": "^7.20.0",
"get-stream": "^6.0.0",
"mocha": "^8.3.0",
"mock-stdin": "^1.0.0",
"node-fetch": "^2.6.1",
"pixelmatch": "^5.2.1",
"playwright": "^1.8.1",
"pngjs": "^6.0.0",
"prettier": "^2.2.1",
"rollup": "^2.39.0"
"rollup": "^2.39.0",
"tar-stream": "^2.2.0",
"typescript": "^4.2.2"
},

@@ -123,0 +131,0 @@ "engines": {

@@ -5,13 +5,81 @@ 'use strict';

exports.elemsGroups = {
animation: ['animate', 'animateColor', 'animateMotion', 'animateTransform', 'set'],
descriptive: ['desc', 'metadata', 'title'],
shape: ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'],
structural: ['defs', 'g', 'svg', 'symbol', 'use'],
paintServer: ['solidColor', 'linearGradient', 'radialGradient', 'meshGradient', 'pattern', 'hatch'],
nonRendering: ['linearGradient', 'radialGradient', 'pattern', 'clipPath', 'mask', 'marker', 'symbol', 'filter', 'solidColor'],
container: ['a', 'defs', 'g', 'marker', 'mask', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol', 'foreignObject'],
textContent: ['altGlyph', 'altGlyphDef', 'altGlyphItem', 'glyph', 'glyphRef', 'textPath', 'text', 'tref', 'tspan'],
textContentChild: ['altGlyph', 'textPath', 'tref', 'tspan'],
lightSource: ['feDiffuseLighting', 'feSpecularLighting', 'feDistantLight', 'fePointLight', 'feSpotLight'],
filterPrimitive: ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence']
animation: [
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'set',
],
descriptive: ['desc', 'metadata', 'title'],
shape: ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'],
structural: ['defs', 'g', 'svg', 'symbol', 'use'],
paintServer: [
'solidColor',
'linearGradient',
'radialGradient',
'meshGradient',
'pattern',
'hatch',
],
nonRendering: [
'linearGradient',
'radialGradient',
'pattern',
'clipPath',
'mask',
'marker',
'symbol',
'filter',
'solidColor',
],
container: [
'a',
'defs',
'g',
'marker',
'mask',
'missing-glyph',
'pattern',
'svg',
'switch',
'symbol',
'foreignObject',
],
textContent: [
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'glyph',
'glyphRef',
'textPath',
'text',
'tref',
'tspan',
],
textContentChild: ['altGlyph', 'textPath', 'tref', 'tspan'],
lightSource: [
'feDiffuseLighting',
'feSpecularLighting',
'feDistantLight',
'fePointLight',
'feSpotLight',
],
filterPrimitive: [
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feFlood',
'feGaussianBlur',
'feImage',
'feMerge',
'feMorphology',
'feOffset',
'feSpecularLighting',
'feTile',
'feTurbulence',
],
};

@@ -25,137 +93,198 @@

exports.attrsGroups = {
animationAddition: ['additive', 'accumulate'],
animationAttributeTarget: ['attributeType', 'attributeName'],
animationEvent: ['onbegin', 'onend', 'onrepeat', 'onload'],
animationTiming: ['begin', 'dur', 'end', 'min', 'max', 'restart', 'repeatCount', 'repeatDur', 'fill'],
animationValue: ['calcMode', 'values', 'keyTimes', 'keySplines', 'from', 'to', 'by'],
conditionalProcessing: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'],
core: ['id', 'tabindex', 'xml:base', 'xml:lang', 'xml:space'],
graphicalEvent: ['onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload'],
presentation: [
'alignment-baseline',
'baseline-shift',
'clip',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cursor',
'direction',
'display',
'dominant-baseline',
'enable-background',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'letter-spacing',
'lighting-color',
'marker-end',
'marker-mid',
'marker-start',
'mask',
'opacity',
'overflow',
'paint-order',
'pointer-events',
'shape-rendering',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-decoration',
'text-overflow',
'text-rendering',
'transform',
'unicode-bidi',
'vector-effect',
'visibility',
'word-spacing',
'writing-mode'
],
xlink: ['xlink:href', 'xlink:show', 'xlink:actuate', 'xlink:type', 'xlink:role', 'xlink:arcrole', 'xlink:title'],
documentEvent: ['onunload', 'onabort', 'onerror', 'onresize', 'onscroll', 'onzoom'],
filterPrimitive: ['x', 'y', 'width', 'height', 'result'],
transferFunction: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset']
animationAddition: ['additive', 'accumulate'],
animationAttributeTarget: ['attributeType', 'attributeName'],
animationEvent: ['onbegin', 'onend', 'onrepeat', 'onload'],
animationTiming: [
'begin',
'dur',
'end',
'min',
'max',
'restart',
'repeatCount',
'repeatDur',
'fill',
],
animationValue: [
'calcMode',
'values',
'keyTimes',
'keySplines',
'from',
'to',
'by',
],
conditionalProcessing: [
'requiredFeatures',
'requiredExtensions',
'systemLanguage',
],
core: ['id', 'tabindex', 'xml:base', 'xml:lang', 'xml:space'],
graphicalEvent: [
'onfocusin',
'onfocusout',
'onactivate',
'onclick',
'onmousedown',
'onmouseup',
'onmouseover',
'onmousemove',
'onmouseout',
'onload',
],
presentation: [
'alignment-baseline',
'baseline-shift',
'clip',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cursor',
'direction',
'display',
'dominant-baseline',
'enable-background',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'letter-spacing',
'lighting-color',
'marker-end',
'marker-mid',
'marker-start',
'mask',
'opacity',
'overflow',
'paint-order',
'pointer-events',
'shape-rendering',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-decoration',
'text-overflow',
'text-rendering',
'transform',
'unicode-bidi',
'vector-effect',
'visibility',
'word-spacing',
'writing-mode',
],
xlink: [
'xlink:href',
'xlink:show',
'xlink:actuate',
'xlink:type',
'xlink:role',
'xlink:arcrole',
'xlink:title',
],
documentEvent: [
'onunload',
'onabort',
'onerror',
'onresize',
'onscroll',
'onzoom',
],
filterPrimitive: ['x', 'y', 'width', 'height', 'result'],
transferFunction: [
'type',
'tableValues',
'slope',
'intercept',
'amplitude',
'exponent',
'offset',
],
};
exports.attrsGroupsDefaults = {
core: {'xml:space': 'default'},
filterPrimitive: {x: '0', y: '0', width: '100%', height: '100%'},
presentation: {
clip: 'auto',
'clip-path': 'none',
'clip-rule': 'nonzero',
mask: 'none',
opacity: '1',
'stop-color': '#000',
'stop-opacity': '1',
'fill-opacity': '1',
'fill-rule': 'nonzero',
fill: '#000',
stroke: 'none',
'stroke-width': '1',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-miterlimit': '4',
'stroke-dasharray': 'none',
'stroke-dashoffset': '0',
'stroke-opacity': '1',
'paint-order': 'normal',
'vector-effect': 'none',
display: 'inline',
visibility: 'visible',
'marker-start': 'none',
'marker-mid': 'none',
'marker-end': 'none',
'color-interpolation': 'sRGB',
'color-interpolation-filters': 'linearRGB',
'color-rendering': 'auto',
'shape-rendering': 'auto',
'text-rendering': 'auto',
'image-rendering': 'auto',
'font-style': 'normal',
'font-variant': 'normal',
'font-weight': 'normal',
'font-stretch': 'normal',
'font-size': 'medium',
'font-size-adjust': 'none',
kerning: 'auto',
'letter-spacing': 'normal',
'word-spacing': 'normal',
'text-decoration': 'none',
'text-anchor': 'start',
'text-overflow': 'clip',
'writing-mode': 'lr-tb',
'glyph-orientation-vertical': 'auto',
'glyph-orientation-horizontal': '0deg',
direction: 'ltr',
'unicode-bidi': 'normal',
'dominant-baseline': 'auto',
'alignment-baseline': 'baseline',
'baseline-shift': 'baseline'
},
transferFunction: {slope: '1', intercept: '0', amplitude: '1', exponent: '1', offset: '0'}
core: { 'xml:space': 'default' },
presentation: {
clip: 'auto',
'clip-path': 'none',
'clip-rule': 'nonzero',
mask: 'none',
opacity: '1',
'stop-color': '#000',
'stop-opacity': '1',
'fill-opacity': '1',
'fill-rule': 'nonzero',
fill: '#000',
stroke: 'none',
'stroke-width': '1',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-miterlimit': '4',
'stroke-dasharray': 'none',
'stroke-dashoffset': '0',
'stroke-opacity': '1',
'paint-order': 'normal',
'vector-effect': 'none',
display: 'inline',
visibility: 'visible',
'marker-start': 'none',
'marker-mid': 'none',
'marker-end': 'none',
'color-interpolation': 'sRGB',
'color-interpolation-filters': 'linearRGB',
'color-rendering': 'auto',
'shape-rendering': 'auto',
'text-rendering': 'auto',
'image-rendering': 'auto',
'font-style': 'normal',
'font-variant': 'normal',
'font-weight': 'normal',
'font-stretch': 'normal',
'font-size': 'medium',
'font-size-adjust': 'none',
kerning: 'auto',
'letter-spacing': 'normal',
'word-spacing': 'normal',
'text-decoration': 'none',
'text-anchor': 'start',
'text-overflow': 'clip',
'writing-mode': 'lr-tb',
'glyph-orientation-vertical': 'auto',
'glyph-orientation-horizontal': '0deg',
direction: 'ltr',
'unicode-bidi': 'normal',
'dominant-baseline': 'auto',
'alignment-baseline': 'baseline',
'baseline-shift': 'baseline',
},
transferFunction: {
slope: '1',
intercept: '0',
amplitude: '1',
exponent: '1',
offset: '0',
},
};

@@ -165,2109 +294,1548 @@

exports.elems = {
a: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'target'
],
defaults: {
target: '_self'
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
// not spec compliant
'tspan',
]
a: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'target',
],
defaults: {
target: '_self',
},
altGlyph: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'dx',
'dy',
'glyphRef',
'format',
'rotate'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
// not spec compliant
'tspan',
],
},
altGlyph: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'dx',
'dy',
'glyphRef',
'format',
'rotate',
],
},
altGlyphDef: {
attrsGroups: ['core'],
content: ['glyphRef'],
},
altGlyphItem: {
attrsGroups: ['core'],
content: ['glyphRef', 'altGlyphItem'],
},
animate: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationAddition',
'animationAttributeTarget',
'animationEvent',
'animationTiming',
'animationValue',
'presentation',
'xlink',
],
attrs: ['externalResourcesRequired'],
contentGroups: ['descriptive'],
},
animateColor: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationAttributeTarget',
'animationTiming',
'animationValue',
'animationAddition',
'presentation',
],
attrs: ['externalResourcesRequired'],
contentGroups: ['descriptive'],
},
animateMotion: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationTiming',
'animationValue',
'animationAddition',
],
attrs: [
'externalResourcesRequired',
'path',
'keyPoints',
'rotate',
'origin',
],
defaults: {
rotate: '0',
},
altGlyphDef: {
attrsGroups: [
'core'
],
content: [
'glyphRef'
]
contentGroups: ['descriptive'],
content: ['mpath'],
},
animateTransform: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationAttributeTarget',
'animationTiming',
'animationValue',
'animationAddition',
],
attrs: ['externalResourcesRequired', 'type'],
contentGroups: ['descriptive'],
},
circle: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'cx',
'cy',
'r',
],
defaults: {
cx: '0',
cy: '0',
},
altGlyphItem: {
attrsGroups: [
'core'
],
content: [
'glyphRef',
'altGlyphItem'
]
contentGroups: ['animation', 'descriptive'],
},
clipPath: {
attrsGroups: ['conditionalProcessing', 'core', 'presentation'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'clipPathUnits',
],
defaults: {
clipPathUnits: 'userSpaceOnUse',
},
animate: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationAddition',
'animationAttributeTarget',
'animationEvent',
'animationTiming',
'animationValue',
'presentation',
'xlink'
],
attrs: [
'externalResourcesRequired'
],
contentGroups: [
'descriptive'
]
contentGroups: ['animation', 'descriptive', 'shape'],
content: ['text', 'use'],
},
'color-profile': {
attrsGroups: ['core', 'xlink'],
attrs: ['local', 'name', 'rendering-intent'],
defaults: {
name: 'sRGB',
'rendering-intent': 'auto',
},
animateColor: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationAttributeTarget',
'animationTiming',
'animationValue',
'animationAddition',
'presentation'
],
attrs: [
'externalResourcesRequired'
],
contentGroups: [
'descriptive'
]
contentGroups: ['descriptive'],
},
cursor: {
attrsGroups: ['core', 'conditionalProcessing', 'xlink'],
attrs: ['externalResourcesRequired', 'x', 'y'],
defaults: {
x: '0',
y: '0',
},
animateMotion: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationTiming',
'animationValue',
'animationAddition'
],
attrs: [
'externalResourcesRequired',
'path',
'keyPoints',
'rotate',
'origin'
],
defaults: {
'rotate': '0'
},
contentGroups: [
'descriptive'
],
content: [
'mpath'
]
contentGroups: ['descriptive'],
},
defs: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: ['class', 'style', 'externalResourcesRequired', 'transform'],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
desc: {
attrsGroups: ['core'],
attrs: ['class', 'style'],
},
ellipse: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'cx',
'cy',
'rx',
'ry',
],
defaults: {
cx: '0',
cy: '0',
},
animateTransform: {
attrsGroups: [
'conditionalProcessing',
'core',
'animationEvent',
'xlink',
'animationAttributeTarget',
'animationTiming',
'animationValue',
'animationAddition'
],
attrs: [
'externalResourcesRequired',
'type'
],
contentGroups: [
'descriptive'
]
contentGroups: ['animation', 'descriptive'],
},
feBlend: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
// TODO: in - 'If no value is provided and this is the first filter primitive,
// then this filter primitive will use SourceGraphic as its input'
'in',
'in2',
'mode',
],
defaults: {
mode: 'normal',
},
circle: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'cx',
'cy',
'r'
],
defaults: {
cx: '0',
cy: '0'
},
contentGroups: [
'animation',
'descriptive'
]
content: ['animate', 'set'],
},
feColorMatrix: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in', 'type', 'values'],
defaults: {
type: 'matrix',
},
clipPath: {
attrsGroups: [
'conditionalProcessing',
'core',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'clipPathUnits'
],
defaults: {
clipPathUnits: 'userSpaceOnUse'
},
contentGroups: [
'animation',
'descriptive',
'shape'
],
content: [
'text',
'use'
]
content: ['animate', 'set'],
},
feComponentTransfer: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in'],
content: ['feFuncA', 'feFuncB', 'feFuncG', 'feFuncR'],
},
feComposite: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in', 'in2', 'operator', 'k1', 'k2', 'k3', 'k4'],
defaults: {
operator: 'over',
k1: '0',
k2: '0',
k3: '0',
k4: '0',
},
'color-profile': {
attrsGroups: [
'core',
'xlink'
],
attrs: [
'local',
'name',
'rendering-intent'
],
defaults: {
name: 'sRGB',
'rendering-intent': 'auto'
},
contentGroups: [
'descriptive'
]
content: ['animate', 'set'],
},
feConvolveMatrix: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
'in',
'order',
'kernelMatrix',
// TODO: divisor - 'The default value is the sum of all values in kernelMatrix,
// with the exception that if the sum is zero, then the divisor is set to 1'
'divisor',
'bias',
// TODO: targetX - 'By default, the convolution matrix is centered in X over each
// pixel of the input image (i.e., targetX = floor ( orderX / 2 ))'
'targetX',
'targetY',
'edgeMode',
// TODO: kernelUnitLength - 'The first number is the <dx> value. The second number
// is the <dy> value. If the <dy> value is not specified, it defaults to the same value as <dx>'
'kernelUnitLength',
'preserveAlpha',
],
defaults: {
order: '3',
bias: '0',
edgeMode: 'duplicate',
preserveAlpha: 'false',
},
cursor: {
attrsGroups: [
'core',
'conditionalProcessing',
'xlink'
],
attrs: [
'externalResourcesRequired',
'x',
'y'
],
defaults: {
x: '0',
y: '0'
},
contentGroups: [
'descriptive'
]
content: ['animate', 'set'],
},
feDiffuseLighting: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
'in',
'surfaceScale',
'diffuseConstant',
'kernelUnitLength',
],
defaults: {
surfaceScale: '1',
diffuseConstant: '1',
},
defs: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform'
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
contentGroups: ['descriptive'],
content: [
// TODO: 'exactly one light source element, in any order'
'feDistantLight',
'fePointLight',
'feSpotLight',
],
},
feDisplacementMap: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
'in',
'in2',
'scale',
'xChannelSelector',
'yChannelSelector',
],
defaults: {
scale: '0',
xChannelSelector: 'A',
yChannelSelector: 'A',
},
desc: {
attrsGroups: [
'core'
],
attrs: [
'class',
'style'
]
content: ['animate', 'set'],
},
feDistantLight: {
attrsGroups: ['core'],
attrs: ['azimuth', 'elevation'],
defaults: {
azimuth: '0',
elevation: '0',
},
ellipse: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'cx',
'cy',
'rx',
'ry'
],
defaults: {
cx: '0',
cy: '0'
},
contentGroups: [
'animation',
'descriptive'
]
content: ['animate', 'set'],
},
feFlood: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style'],
content: ['animate', 'animateColor', 'set'],
},
feFuncA: {
attrsGroups: ['core', 'transferFunction'],
content: ['set', 'animate'],
},
feFuncB: {
attrsGroups: ['core', 'transferFunction'],
content: ['set', 'animate'],
},
feFuncG: {
attrsGroups: ['core', 'transferFunction'],
content: ['set', 'animate'],
},
feFuncR: {
attrsGroups: ['core', 'transferFunction'],
content: ['set', 'animate'],
},
feGaussianBlur: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in', 'stdDeviation'],
defaults: {
stdDeviation: '0',
},
feBlend: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
// TODO: in - 'If no value is provided and this is the first filter primitive,
// then this filter primitive will use SourceGraphic as its input'
'in',
'in2',
'mode'
],
defaults: {
mode: 'normal'
},
content: [
'animate',
'set'
]
content: ['set', 'animate'],
},
feImage: {
attrsGroups: ['core', 'presentation', 'filterPrimitive', 'xlink'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'href',
'xlink:href',
],
defaults: {
preserveAspectRatio: 'xMidYMid meet',
},
feColorMatrix: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'type',
'values'
],
defaults: {
type: 'matrix'
},
content: [
'animate',
'set'
]
content: ['animate', 'animateTransform', 'set'],
},
feMerge: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style'],
content: ['feMergeNode'],
},
feMergeNode: {
attrsGroups: ['core'],
attrs: ['in'],
content: ['animate', 'set'],
},
feMorphology: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in', 'operator', 'radius'],
defaults: {
operator: 'erode',
radius: '0',
},
feComponentTransfer: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in'
],
content: [
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR'
]
content: ['animate', 'set'],
},
feOffset: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in', 'dx', 'dy'],
defaults: {
dx: '0',
dy: '0',
},
feComposite: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'in2',
'operator',
'k1',
'k2',
'k3',
'k4'
],
defaults: {
operator: 'over',
k1: '0',
k2: '0',
k3: '0',
k4: '0'
},
content: [
'animate',
'set'
]
content: ['animate', 'set'],
},
fePointLight: {
attrsGroups: ['core'],
attrs: ['x', 'y', 'z'],
defaults: {
x: '0',
y: '0',
z: '0',
},
feConvolveMatrix: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'order',
'kernelMatrix',
// TODO: divisor - 'The default value is the sum of all values in kernelMatrix,
// with the exception that if the sum is zero, then the divisor is set to 1'
'divisor',
'bias',
// TODO: targetX - 'By default, the convolution matrix is centered in X over each
// pixel of the input image (i.e., targetX = floor ( orderX / 2 ))'
'targetX',
'targetY',
'edgeMode',
// TODO: kernelUnitLength - 'The first number is the <dx> value. The second number
// is the <dy> value. If the <dy> value is not specified, it defaults to the same value as <dx>'
'kernelUnitLength',
'preserveAlpha'
],
defaults: {
order: '3',
bias: '0',
edgeMode: 'duplicate',
preserveAlpha: 'false'
},
content: [
'animate',
'set'
]
content: ['animate', 'set'],
},
feSpecularLighting: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
'in',
'surfaceScale',
'specularConstant',
'specularExponent',
'kernelUnitLength',
],
defaults: {
surfaceScale: '1',
specularConstant: '1',
specularExponent: '1',
},
feDiffuseLighting: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'surfaceScale',
'diffuseConstant',
'kernelUnitLength'
],
defaults: {
surfaceScale: '1',
diffuseConstant: '1'
},
contentGroups: [
'descriptive'
],
content: [
// TODO: 'exactly one light source element, in any order'
'feDistantLight',
'fePointLight',
'feSpotLight'
]
contentGroups: [
'descriptive',
// TODO: exactly one 'light source element'
'lightSource',
],
},
feSpotLight: {
attrsGroups: ['core'],
attrs: [
'x',
'y',
'z',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'specularExponent',
'limitingConeAngle',
],
defaults: {
x: '0',
y: '0',
z: '0',
pointsAtX: '0',
pointsAtY: '0',
pointsAtZ: '0',
specularExponent: '1',
},
feDisplacementMap: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'in2',
'scale',
'xChannelSelector',
'yChannelSelector'
],
defaults: {
scale: '0',
xChannelSelector: 'A',
yChannelSelector: 'A'
},
content: [
'animate',
'set'
]
content: ['animate', 'set'],
},
feTile: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: ['class', 'style', 'in'],
content: ['animate', 'set'],
},
feTurbulence: {
attrsGroups: ['core', 'presentation', 'filterPrimitive'],
attrs: [
'class',
'style',
'baseFrequency',
'numOctaves',
'seed',
'stitchTiles',
'type',
],
defaults: {
baseFrequency: '0',
numOctaves: '1',
seed: '0',
stitchTiles: 'noStitch',
type: 'turbulence',
},
feDistantLight: {
attrsGroups: [
'core'
],
attrs: [
'azimuth',
'elevation'
],
defaults: {
azimuth: '0',
elevation: '0'
},
content: [
'animate',
'set'
]
content: ['animate', 'set'],
},
filter: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'width',
'height',
'filterRes',
'filterUnits',
'primitiveUnits',
'href',
'xlink:href',
],
defaults: {
primitiveUnits: 'userSpaceOnUse',
x: '-10%',
y: '-10%',
width: '120%',
height: '120%',
},
feFlood: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style'
],
content: [
'animate',
'animateColor',
'set'
]
contentGroups: ['descriptive', 'filterPrimitive'],
content: ['animate', 'set'],
},
font: {
attrsGroups: ['core', 'presentation'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'horiz-origin-x',
'horiz-origin-y',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y',
],
defaults: {
'horiz-origin-x': '0',
'horiz-origin-y': '0',
},
feFuncA: {
attrsGroups: [
'core',
'transferFunction'
],
content: [
'set',
'animate'
]
contentGroups: ['descriptive'],
content: ['font-face', 'glyph', 'hkern', 'missing-glyph', 'vkern'],
},
'font-face': {
attrsGroups: ['core'],
attrs: [
'font-family',
'font-style',
'font-variant',
'font-weight',
'font-stretch',
'font-size',
'unicode-range',
'units-per-em',
'panose-1',
'stemv',
'stemh',
'slope',
'cap-height',
'x-height',
'accent-height',
'ascent',
'descent',
'widths',
'bbox',
'ideographic',
'alphabetic',
'mathematical',
'hanging',
'v-ideographic',
'v-alphabetic',
'v-mathematical',
'v-hanging',
'underline-position',
'underline-thickness',
'strikethrough-position',
'strikethrough-thickness',
'overline-position',
'overline-thickness',
],
defaults: {
'font-style': 'all',
'font-variant': 'normal',
'font-weight': 'all',
'font-stretch': 'normal',
'unicode-range': 'U+0-10FFFF',
'units-per-em': '1000',
'panose-1': '0 0 0 0 0 0 0 0 0 0',
slope: '0',
},
feFuncB: {
attrsGroups: [
'core',
'transferFunction'
],
content: [
'set',
'animate'
]
contentGroups: ['descriptive'],
content: [
// TODO: "at most one 'font-face-src' element"
'font-face-src',
],
},
// TODO: empty content
'font-face-format': {
attrsGroups: ['core'],
attrs: ['string'],
},
'font-face-name': {
attrsGroups: ['core'],
attrs: ['name'],
},
'font-face-src': {
attrsGroups: ['core'],
content: ['font-face-name', 'font-face-uri'],
},
'font-face-uri': {
attrsGroups: ['core', 'xlink'],
attrs: ['href', 'xlink:href'],
content: ['font-face-format'],
},
foreignObject: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height',
],
defaults: {
x: 0,
y: 0,
},
feFuncG: {
attrsGroups: [
'core',
'transferFunction'
],
content: [
'set',
'animate'
]
},
g: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: ['class', 'style', 'externalResourcesRequired', 'transform'],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
glyph: {
attrsGroups: ['core', 'presentation'],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y',
'unicode',
'glyph-name',
'orientation',
'arabic-form',
'lang',
],
defaults: {
'arabic-form': 'initial',
},
feFuncR: {
attrsGroups: [
'core',
'transferFunction'
],
content: [
'set',
'animate'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
glyphRef: {
attrsGroups: ['core', 'presentation'],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y',
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
hatch: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: [
'class',
'style',
'x',
'y',
'pitch',
'rotate',
'hatchUnits',
'hatchContentUnits',
'transform',
],
defaults: {
hatchUnits: 'objectBoundingBox',
hatchContentUnits: 'userSpaceOnUse',
x: '0',
y: '0',
pitch: '0',
rotate: '0',
},
feGaussianBlur: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'stdDeviation'
],
defaults: {
stdDeviation: '0'
},
content: [
'set',
'animate'
]
contentGroups: ['animation', 'descriptive'],
content: ['hatchPath'],
},
hatchPath: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: ['class', 'style', 'd', 'offset'],
defaults: {
offset: '0',
},
feImage: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'href',
'xlink:href'
],
defaults: {
preserveAspectRatio: 'xMidYMid meet'
},
content: [
'animate',
'animateTransform',
'set'
]
contentGroups: ['animation', 'descriptive'],
},
hkern: {
attrsGroups: ['core'],
attrs: ['u1', 'g1', 'u2', 'g2', 'k'],
},
image: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'xlink',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'transform',
'x',
'y',
'width',
'height',
'href',
'xlink:href',
],
defaults: {
x: '0',
y: '0',
preserveAspectRatio: 'xMidYMid meet',
},
feMerge: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style'
],
content: [
'feMergeNode'
]
contentGroups: ['animation', 'descriptive'],
},
line: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x1',
'y1',
'x2',
'y2',
],
defaults: {
x1: '0',
y1: '0',
x2: '0',
y2: '0',
},
feMergeNode: {
attrsGroups: [
'core'
],
attrs: [
'in'
],
content: [
'animate',
'set'
]
contentGroups: ['animation', 'descriptive'],
},
linearGradient: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x1',
'y1',
'x2',
'y2',
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href',
],
defaults: {
x1: '0',
y1: '0',
x2: '100%',
y2: '0',
spreadMethod: 'pad',
},
feMorphology: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'operator',
'radius'
],
defaults: {
operator: 'erode',
radius: '0'
},
content: [
'animate',
'set'
]
contentGroups: ['descriptive'],
content: ['animate', 'animateTransform', 'set', 'stop'],
},
marker: {
attrsGroups: ['core', 'presentation'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'refX',
'refY',
'markerUnits',
'markerWidth',
'markerHeight',
'orient',
],
defaults: {
markerUnits: 'strokeWidth',
refX: '0',
refY: '0',
markerWidth: '3',
markerHeight: '3',
},
feOffset: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'dx',
'dy'
],
defaults: {
dx: '0',
dy: '0'
},
content: [
'animate',
'set'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
mask: {
attrsGroups: ['conditionalProcessing', 'core', 'presentation'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'width',
'height',
'mask-type',
'maskUnits',
'maskContentUnits',
],
defaults: {
maskUnits: 'objectBoundingBox',
maskContentUnits: 'userSpaceOnUse',
x: '-10%',
y: '-10%',
width: '120%',
height: '120%',
},
fePointLight: {
attrsGroups: [
'core'
],
attrs: [
'x',
'y',
'z'
],
defaults: {
x: '0',
y: '0',
z: '0'
},
content: [
'animate',
'set'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
metadata: {
attrsGroups: ['core'],
},
'missing-glyph': {
attrsGroups: ['core', 'presentation'],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y',
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
mpath: {
attrsGroups: ['core', 'xlink'],
attrs: ['externalResourcesRequired', 'href', 'xlink:href'],
contentGroups: ['descriptive'],
},
path: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'd',
'pathLength',
],
contentGroups: ['animation', 'descriptive'],
},
pattern: {
attrsGroups: ['conditionalProcessing', 'core', 'presentation', 'xlink'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'x',
'y',
'width',
'height',
'patternUnits',
'patternContentUnits',
'patternTransform',
'href',
'xlink:href',
],
defaults: {
patternUnits: 'objectBoundingBox',
patternContentUnits: 'userSpaceOnUse',
x: '0',
y: '0',
width: '0',
height: '0',
preserveAspectRatio: 'xMidYMid meet',
},
feSpecularLighting: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in',
'surfaceScale',
'specularConstant',
'specularExponent',
'kernelUnitLength'
],
defaults: {
surfaceScale: '1',
specularConstant: '1',
specularExponent: '1'
},
contentGroups: [
'descriptive',
// TODO: exactly one 'light source element'
'lightSource'
]
contentGroups: [
'animation',
'descriptive',
'paintServer',
'shape',
'structural',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
polygon: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'points',
],
contentGroups: ['animation', 'descriptive'],
},
polyline: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'points',
],
contentGroups: ['animation', 'descriptive'],
},
radialGradient: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'cx',
'cy',
'r',
'fx',
'fy',
'fr',
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href',
],
defaults: {
gradientUnits: 'objectBoundingBox',
cx: '50%',
cy: '50%',
r: '50%',
},
feSpotLight: {
attrsGroups: [
'core'
],
attrs: [
'x',
'y',
'z',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'specularExponent',
'limitingConeAngle'
],
defaults: {
x: '0',
y: '0',
z: '0',
pointsAtX: '0',
pointsAtY: '0',
pointsAtZ: '0',
specularExponent: '1'
},
content: [
'animate',
'set'
]
contentGroups: ['descriptive'],
content: ['animate', 'animateTransform', 'set', 'stop'],
},
meshGradient: {
attrsGroups: ['core', 'presentation', 'xlink'],
attrs: ['class', 'style', 'x', 'y', 'gradientUnits', 'transform'],
contentGroups: ['descriptive', 'paintServer', 'animation'],
content: ['meshRow'],
},
meshRow: {
attrsGroups: ['core', 'presentation'],
attrs: ['class', 'style'],
contentGroups: ['descriptive'],
content: ['meshPatch'],
},
meshPatch: {
attrsGroups: ['core', 'presentation'],
attrs: ['class', 'style'],
contentGroups: ['descriptive'],
content: ['stop'],
},
rect: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height',
'rx',
'ry',
],
defaults: {
x: '0',
y: '0',
},
feTile: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'in'
],
content: [
'animate',
'set'
]
contentGroups: ['animation', 'descriptive'],
},
script: {
attrsGroups: ['core', 'xlink'],
attrs: ['externalResourcesRequired', 'type', 'href', 'xlink:href'],
},
set: {
attrsGroups: [
'conditionalProcessing',
'core',
'animation',
'xlink',
'animationAttributeTarget',
'animationTiming',
],
attrs: ['externalResourcesRequired', 'to'],
contentGroups: ['descriptive'],
},
solidColor: {
attrsGroups: ['core', 'presentation'],
attrs: ['class', 'style'],
contentGroups: ['paintServer'],
},
stop: {
attrsGroups: ['core', 'presentation'],
attrs: ['class', 'style', 'offset', 'path'],
content: ['animate', 'animateColor', 'set'],
},
style: {
attrsGroups: ['core'],
attrs: ['type', 'media', 'title'],
defaults: {
type: 'text/css',
},
feTurbulence: {
attrsGroups: [
'core',
'presentation',
'filterPrimitive'
],
attrs: [
'class',
'style',
'baseFrequency',
'numOctaves',
'seed',
'stitchTiles',
'type'
],
defaults: {
baseFrequency: '0',
numOctaves: '1',
seed: '0',
stitchTiles: 'noStitch',
type: 'turbulence'
},
content: [
'animate',
'set'
]
},
svg: {
attrsGroups: [
'conditionalProcessing',
'core',
'documentEvent',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'x',
'y',
'width',
'height',
'viewBox',
'preserveAspectRatio',
'zoomAndPan',
'version',
'baseProfile',
'contentScriptType',
'contentStyleType',
],
defaults: {
x: '0',
y: '0',
width: '100%',
height: '100%',
preserveAspectRatio: 'xMidYMid meet',
zoomAndPan: 'magnify',
version: '1.1',
baseProfile: 'none',
contentScriptType: 'application/ecmascript',
contentStyleType: 'text/css',
},
filter: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'width',
'height',
'filterRes',
'filterUnits',
'primitiveUnits',
'href',
'xlink:href'
],
defaults: {
primitiveUnits: 'userSpaceOnUse',
x: '-10%',
y: '-10%',
width: '120%',
height: '120%'
},
contentGroups: [
'descriptive',
'filterPrimitive'
],
content: [
'animate',
'set'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
switch: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: ['class', 'style', 'externalResourcesRequired', 'transform'],
contentGroups: ['animation', 'descriptive', 'shape'],
content: [
'a',
'foreignObject',
'g',
'image',
'svg',
'switch',
'text',
'use',
],
},
symbol: {
attrsGroups: ['core', 'graphicalEvent', 'presentation'],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'viewBox',
'refX',
'refY',
],
defaults: {
refX: 0,
refY: 0,
},
font: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'horiz-origin-x',
'horiz-origin-y',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y'
],
defaults: {
'horiz-origin-x': '0',
'horiz-origin-y': '0'
},
contentGroups: [
'descriptive'
],
content: [
'font-face',
'glyph',
'hkern',
'missing-glyph',
'vkern'
]
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer',
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view',
],
},
text: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'lengthAdjust',
'x',
'y',
'dx',
'dy',
'rotate',
'textLength',
],
defaults: {
x: '0',
y: '0',
lengthAdjust: 'spacing',
},
'font-face': {
attrsGroups: [
'core'
],
attrs: [
'font-family',
'font-style',
'font-variant',
'font-weight',
'font-stretch',
'font-size',
'unicode-range',
'units-per-em',
'panose-1',
'stemv',
'stemh',
'slope',
'cap-height',
'x-height',
'accent-height',
'ascent',
'descent',
'widths',
'bbox',
'ideographic',
'alphabetic',
'mathematical',
'hanging',
'v-ideographic',
'v-alphabetic',
'v-mathematical',
'v-hanging',
'underline-position',
'underline-thickness',
'strikethrough-position',
'strikethrough-thickness',
'overline-position',
'overline-thickness'
],
defaults: {
'font-style': 'all',
'font-variant': 'normal',
'font-weight': 'all',
'font-stretch': 'normal',
'unicode-range': 'U+0-10FFFF',
'units-per-em': '1000',
'panose-1': '0 0 0 0 0 0 0 0 0 0',
'slope': '0'
},
contentGroups: [
'descriptive'
],
content: [
// TODO: "at most one 'font-face-src' element"
'font-face-src'
]
contentGroups: ['animation', 'descriptive', 'textContentChild'],
content: ['a'],
},
textPath: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href',
'startOffset',
'method',
'spacing',
'd',
],
defaults: {
startOffset: '0',
method: 'align',
spacing: 'exact',
},
// TODO: empty content
'font-face-format': {
attrsGroups: [
'core'
],
attrs: [
'string'
]
contentGroups: ['descriptive'],
content: [
'a',
'altGlyph',
'animate',
'animateColor',
'set',
'tref',
'tspan',
],
},
title: {
attrsGroups: ['core'],
attrs: ['class', 'style'],
},
tref: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href',
],
contentGroups: ['descriptive'],
content: ['animate', 'animateColor', 'set'],
},
tspan: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'dx',
'dy',
'rotate',
'textLength',
'lengthAdjust',
],
contentGroups: ['descriptive'],
content: [
'a',
'altGlyph',
'animate',
'animateColor',
'set',
'tref',
'tspan',
],
},
use: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'presentation',
'xlink',
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height',
'href',
'xlink:href',
],
defaults: {
x: '0',
y: '0',
},
'font-face-name': {
attrsGroups: [
'core'
],
attrs: [
'name'
]
},
'font-face-src': {
attrsGroups: [
'core'
],
content: [
'font-face-name',
'font-face-uri'
]
},
'font-face-uri': {
attrsGroups: [
'core',
'xlink'
],
attrs: [
'href',
'xlink:href'
],
content: [
'font-face-format'
]
},
foreignObject: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height'
],
defaults: {
x: 0,
y: 0
}
},
g: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform'
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
glyph: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y',
'unicode',
'glyph-name',
'orientation',
'arabic-form',
'lang'
],
defaults: {
'arabic-form': 'initial'
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
],
},
glyphRef: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y'
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
hatch: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'x',
'y',
'pitch',
'rotate',
'hatchUnits',
'hatchContentUnits',
'transform'
],
defaults: {
hatchUnits: 'objectBoundingBox',
hatchContentUnits: 'userSpaceOnUse',
x: '0',
y: '0',
pitch: '0',
rotate: '0'
},
contentGroups: [
'animation',
'descriptive'
],
content: [
'hatchPath'
]
},
hatchPath: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'd',
'offset'
],
defaults: {
offset: '0'
},
contentGroups: [
'animation',
'descriptive'
]
},
hkern: {
attrsGroups: [
'core'
],
attrs: [
'u1',
'g1',
'u2',
'g2',
'k'
]
},
image: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'xlink',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'transform',
'x',
'y',
'width',
'height',
'href',
'xlink:href'
],
defaults: {
x: '0',
y: '0',
preserveAspectRatio: 'xMidYMid meet'
},
contentGroups: [
'animation',
'descriptive'
]
},
line: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x1',
'y1',
'x2',
'y2'
],
defaults: {
x1: '0',
y1: '0',
x2: '0',
y2: '0'
},
contentGroups: [
'animation',
'descriptive'
]
},
linearGradient: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x1',
'y1',
'x2',
'y2',
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href'
],
defaults: {
x1: '0',
y1: '0',
x2: '100%',
y2: '0',
spreadMethod: 'pad'
},
contentGroups: [
'descriptive'
],
content: [
'animate',
'animateTransform',
'set',
'stop'
]
},
marker: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'refX',
'refY',
'markerUnits',
'markerWidth',
'markerHeight',
'orient'
],
defaults: {
markerUnits: 'strokeWidth',
refX: '0',
refY: '0',
markerWidth: '3',
markerHeight: '3'
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
mask: {
attrsGroups: [
'conditionalProcessing',
'core',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'width',
'height',
'mask-type',
'maskUnits',
'maskContentUnits'
],
defaults: {
maskUnits: 'objectBoundingBox',
maskContentUnits: 'userSpaceOnUse',
x: '-10%',
y: '-10%',
width: '120%',
height: '120%'
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
metadata: {
attrsGroups: [
'core'
]
},
'missing-glyph': {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'd',
'horiz-adv-x',
'vert-origin-x',
'vert-origin-y',
'vert-adv-y'
],
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
mpath: {
attrsGroups: [
'core',
'xlink'
],
attrs: [
'externalResourcesRequired',
'href',
'xlink:href'
],
contentGroups: [
'descriptive'
]
},
path: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'd',
'pathLength'
],
contentGroups: [
'animation',
'descriptive'
]
},
pattern: {
attrsGroups: [
'conditionalProcessing',
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'x',
'y',
'width',
'height',
'patternUnits',
'patternContentUnits',
'patternTransform',
'href',
'xlink:href'
],
defaults: {
patternUnits: 'objectBoundingBox',
patternContentUnits: 'userSpaceOnUse',
x: '0',
y: '0',
width: '0',
height: '0',
preserveAspectRatio: 'xMidYMid meet'
},
contentGroups: [
'animation',
'descriptive',
'paintServer',
'shape',
'structural'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
polygon: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'points'
],
contentGroups: [
'animation',
'descriptive'
]
},
polyline: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'points'
],
contentGroups: [
'animation',
'descriptive'
]
},
radialGradient: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'cx',
'cy',
'r',
'fx',
'fy',
'fr',
'gradientUnits',
'gradientTransform',
'spreadMethod',
'href',
'xlink:href'
],
defaults: {
gradientUnits: 'objectBoundingBox',
cx: '50%',
cy: '50%',
r: '50%'
},
contentGroups: [
'descriptive'
],
content: [
'animate',
'animateTransform',
'set',
'stop'
]
},
meshGradient: {
attrsGroups: [
'core',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'x',
'y',
'gradientUnits',
'transform'
],
contentGroups: [
'descriptive',
'paintServer',
'animation',
],
content: [
'meshRow'
]
},
meshRow: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style'
],
contentGroups: [
'descriptive'
],
content: [
'meshPatch'
]
},
meshPatch: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style'
],
contentGroups: [
'descriptive'
],
content: [
'stop'
]
},
rect: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height',
'rx',
'ry'
],
defaults: {
x: '0',
y: '0'
},
contentGroups: [
'animation',
'descriptive'
]
},
script: {
attrsGroups: [
'core',
'xlink'
],
attrs: [
'externalResourcesRequired',
'type',
'href',
'xlink:href'
]
},
set: {
attrsGroups: [
'conditionalProcessing',
'core',
'animation',
'xlink',
'animationAttributeTarget',
'animationTiming',
],
attrs: [
'externalResourcesRequired',
'to'
],
contentGroups: [
'descriptive'
]
},
solidColor: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style'
],
contentGroups: [
'paintServer'
]
},
stop: {
attrsGroups: [
'core',
'presentation'
],
attrs: [
'class',
'style',
'offset',
'path'
],
content: [
'animate',
'animateColor',
'set'
]
},
style: {
attrsGroups: [
'core'
],
attrs: [
'type',
'media',
'title'
],
defaults: {
type: 'text/css'
}
},
svg: {
attrsGroups: [
'conditionalProcessing',
'core',
'documentEvent',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'x',
'y',
'width',
'height',
'viewBox',
'preserveAspectRatio',
'zoomAndPan',
'version',
'baseProfile',
'contentScriptType',
'contentStyleType'
],
defaults: {
x: '0',
y: '0',
width: '100%',
height: '100%',
preserveAspectRatio: 'xMidYMid meet',
zoomAndPan: 'magnify',
version: '1.1',
baseProfile: 'none',
contentScriptType: 'application/ecmascript',
contentStyleType: 'text/css'
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
switch: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform'
],
contentGroups: [
'animation',
'descriptive',
'shape'
],
content: [
'a',
'foreignObject',
'g',
'image',
'svg',
'switch',
'text',
'use'
]
},
symbol: {
attrsGroups: [
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'preserveAspectRatio',
'viewBox',
'refX',
'refY'
],
defaults: {
refX: 0,
refY: 0
},
contentGroups: [
'animation',
'descriptive',
'shape',
'structural',
'paintServer'
],
content: [
'a',
'altGlyphDef',
'clipPath',
'color-profile',
'cursor',
'filter',
'font',
'font-face',
'foreignObject',
'image',
'marker',
'mask',
'pattern',
'script',
'style',
'switch',
'text',
'view'
]
},
text: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'lengthAdjust',
'x',
'y',
'dx',
'dy',
'rotate',
'textLength'
],
defaults: {
x: '0',
y: '0',
lengthAdjust: 'spacing'
},
contentGroups: [
'animation',
'descriptive',
'textContentChild'
],
content: [
'a'
]
},
textPath: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href',
'startOffset',
'method',
'spacing',
'd'
],
defaults: {
startOffset: '0',
method: 'align',
spacing: 'exact'
},
contentGroups: [
'descriptive'
],
content: [
'a',
'altGlyph',
'animate',
'animateColor',
'set',
'tref',
'tspan'
]
},
title: {
attrsGroups: [
'core'
],
attrs: [
'class',
'style'
]
},
tref: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'href',
'xlink:href'
],
contentGroups: [
'descriptive'
],
content: [
'animate',
'animateColor',
'set'
]
},
tspan: {
attrsGroups: [
'conditionalProcessing',
'core',
'graphicalEvent',
'presentation'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'x',
'y',
'dx',
'dy',
'rotate',
'textLength',
'lengthAdjust'
],
contentGroups: [
'descriptive'
],
content: [
'a',
'altGlyph',
'animate',
'animateColor',
'set',
'tref',
'tspan'
]
},
use: {
attrsGroups: [
'core',
'conditionalProcessing',
'graphicalEvent',
'presentation',
'xlink'
],
attrs: [
'class',
'style',
'externalResourcesRequired',
'transform',
'x',
'y',
'width',
'height',
'href',
'xlink:href'
],
defaults: {
x: '0',
y: '0'
},
contentGroups: [
'animation',
'descriptive'
]
},
view: {
attrsGroups: [
'core'
],
attrs: [
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'zoomAndPan',
'viewTarget'
],
contentGroups: [
'descriptive'
]
},
vkern: {
attrsGroups: [
'core'
],
attrs: [
'u1',
'g1',
'u2',
'g2',
'k'
]
}
contentGroups: ['animation', 'descriptive'],
},
view: {
attrsGroups: ['core'],
attrs: [
'externalResourcesRequired',
'viewBox',
'preserveAspectRatio',
'zoomAndPan',
'viewTarget',
],
contentGroups: ['descriptive'],
},
vkern: {
attrsGroups: ['core'],
attrs: ['u1', 'g1', 'u2', 'g2', 'k'],
},
};

@@ -2277,24 +1845,24 @@

exports.editorNamespaces = [
'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd',
'http://www.inkscape.org/namespaces/inkscape',
'http://www.bohemiancoding.com/sketch/ns',
'http://ns.adobe.com/AdobeIllustrator/10.0/',
'http://ns.adobe.com/Graphs/1.0/',
'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
'http://ns.adobe.com/Variables/1.0/',
'http://ns.adobe.com/SaveForWeb/1.0/',
'http://ns.adobe.com/Extensibility/1.0/',
'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://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd',
'http://www.inkscape.org/namespaces/inkscape',
'http://www.bohemiancoding.com/sketch/ns',
'http://ns.adobe.com/AdobeIllustrator/10.0/',
'http://ns.adobe.com/Graphs/1.0/',
'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
'http://ns.adobe.com/Variables/1.0/',
'http://ns.adobe.com/SaveForWeb/1.0/',
'http://ns.adobe.com/Extensibility/1.0/',
'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',
];

@@ -2304,12 +1872,12 @@

exports.referencesProps = [
'clip-path',
'color-profile',
'fill',
'filter',
'marker-start',
'marker-mid',
'marker-end',
'mask',
'stroke',
'style'
'clip-path',
'color-profile',
'fill',
'filter',
'marker-start',
'marker-mid',
'marker-end',
'mask',
'stroke',
'style',
];

@@ -2319,59 +1887,59 @@

exports.inheritableAttrs = [
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cursor',
'direction',
'dominant-baseline',
'fill',
'fill-opacity',
'fill-rule',
'font',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'letter-spacing',
'marker',
'marker-end',
'marker-mid',
'marker-start',
'paint-order',
'pointer-events',
'shape-rendering',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-rendering',
'transform',
'visibility',
'word-spacing',
'writing-mode'
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cursor',
'direction',
'dominant-baseline',
'fill',
'fill-opacity',
'fill-rule',
'font',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'glyph-orientation-horizontal',
'glyph-orientation-vertical',
'image-rendering',
'letter-spacing',
'marker',
'marker-end',
'marker-mid',
'marker-start',
'paint-order',
'pointer-events',
'shape-rendering',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-rendering',
'transform',
'visibility',
'word-spacing',
'writing-mode',
];
exports.presentationNonInheritableGroupAttrs = [
'display',
'clip-path',
'filter',
'mask',
'opacity',
'text-decoration',
'transform',
'unicode-bidi',
'visibility'
'display',
'clip-path',
'filter',
'mask',
'opacity',
'text-decoration',
'transform',
'unicode-bidi',
'visibility',
];

@@ -2381,150 +1949,150 @@

exports.colorsNames = {
'aliceblue': '#f0f8ff',
'antiquewhite': '#faebd7',
'aqua': '#0ff',
'aquamarine': '#7fffd4',
'azure': '#f0ffff',
'beige': '#f5f5dc',
'bisque': '#ffe4c4',
'black': '#000',
'blanchedalmond': '#ffebcd',
'blue': '#00f',
'blueviolet': '#8a2be2',
'brown': '#a52a2a',
'burlywood': '#deb887',
'cadetblue': '#5f9ea0',
'chartreuse': '#7fff00',
'chocolate': '#d2691e',
'coral': '#ff7f50',
'cornflowerblue': '#6495ed',
'cornsilk': '#fff8dc',
'crimson': '#dc143c',
'cyan': '#0ff',
'darkblue': '#00008b',
'darkcyan': '#008b8b',
'darkgoldenrod': '#b8860b',
'darkgray': '#a9a9a9',
'darkgreen': '#006400',
'darkgrey': '#a9a9a9',
'darkkhaki': '#bdb76b',
'darkmagenta': '#8b008b',
'darkolivegreen': '#556b2f',
'darkorange': '#ff8c00',
'darkorchid': '#9932cc',
'darkred': '#8b0000',
'darksalmon': '#e9967a',
'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',
'forestgreen': '#228b22',
'fuchsia': '#f0f',
'gainsboro': '#dcdcdc',
'ghostwhite': '#f8f8ff',
'gold': '#ffd700',
'goldenrod': '#daa520',
'gray': '#808080',
'green': '#008000',
'greenyellow': '#adff2f',
'grey': '#808080',
'honeydew': '#f0fff0',
'hotpink': '#ff69b4',
'indianred': '#cd5c5c',
'indigo': '#4b0082',
'ivory': '#fffff0',
'khaki': '#f0e68c',
'lavender': '#e6e6fa',
'lavenderblush': '#fff0f5',
'lawngreen': '#7cfc00',
'lemonchiffon': '#fffacd',
'lightblue': '#add8e6',
'lightcoral': '#f08080',
'lightcyan': '#e0ffff',
'lightgoldenrodyellow': '#fafad2',
'lightgray': '#d3d3d3',
'lightgreen': '#90ee90',
'lightgrey': '#d3d3d3',
'lightpink': '#ffb6c1',
'lightsalmon': '#ffa07a',
'lightseagreen': '#20b2aa',
'lightskyblue': '#87cefa',
'lightslategray': '#789',
'lightslategrey': '#789',
'lightsteelblue': '#b0c4de',
'lightyellow': '#ffffe0',
'lime': '#0f0',
'limegreen': '#32cd32',
'linen': '#faf0e6',
'magenta': '#f0f',
'maroon': '#800000',
'mediumaquamarine': '#66cdaa',
'mediumblue': '#0000cd',
'mediumorchid': '#ba55d3',
'mediumpurple': '#9370db',
'mediumseagreen': '#3cb371',
'mediumslateblue': '#7b68ee',
'mediumspringgreen': '#00fa9a',
'mediumturquoise': '#48d1cc',
'mediumvioletred': '#c71585',
'midnightblue': '#191970',
'mintcream': '#f5fffa',
'mistyrose': '#ffe4e1',
'moccasin': '#ffe4b5',
'navajowhite': '#ffdead',
'navy': '#000080',
'oldlace': '#fdf5e6',
'olive': '#808000',
'olivedrab': '#6b8e23',
'orange': '#ffa500',
'orangered': '#ff4500',
'orchid': '#da70d6',
'palegoldenrod': '#eee8aa',
'palegreen': '#98fb98',
'paleturquoise': '#afeeee',
'palevioletred': '#db7093',
'papayawhip': '#ffefd5',
'peachpuff': '#ffdab9',
'peru': '#cd853f',
'pink': '#ffc0cb',
'plum': '#dda0dd',
'powderblue': '#b0e0e6',
'purple': '#800080',
'rebeccapurple': '#639',
'red': '#f00',
'rosybrown': '#bc8f8f',
'royalblue': '#4169e1',
'saddlebrown': '#8b4513',
'salmon': '#fa8072',
'sandybrown': '#f4a460',
'seagreen': '#2e8b57',
'seashell': '#fff5ee',
'sienna': '#a0522d',
'silver': '#c0c0c0',
'skyblue': '#87ceeb',
'slateblue': '#6a5acd',
'slategray': '#708090',
'slategrey': '#708090',
'snow': '#fffafa',
'springgreen': '#00ff7f',
'steelblue': '#4682b4',
'tan': '#d2b48c',
'teal': '#008080',
'thistle': '#d8bfd8',
'tomato': '#ff6347',
'turquoise': '#40e0d0',
'violet': '#ee82ee',
'wheat': '#f5deb3',
'white': '#fff',
'whitesmoke': '#f5f5f5',
'yellow': '#ff0',
'yellowgreen': '#9acd32'
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#0ff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000',
blanchedalmond: '#ffebcd',
blue: '#00f',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#0ff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
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',
forestgreen: '#228b22',
fuchsia: '#f0f',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#789',
lightslategrey: '#789',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#0f0',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#f0f',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#639',
red: '#f00',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#fff',
whitesmoke: '#f5f5f5',
yellow: '#ff0',
yellowgreen: '#9acd32',
};

@@ -2564,3 +2132,3 @@

'#ee82ee': 'violet',
'#f5deb3': 'wheat'
'#f5deb3': 'wheat',
};

@@ -2570,3 +2138,8 @@

exports.colorsProps = [
'color', 'fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color'
'color',
'fill',
'stroke',
'stop-color',
'flood-color',
'lighting-color',
];

@@ -6,10 +6,11 @@ 'use strict';

var regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g,
transform2js = require('./_transforms').transform2js,
transformsMultiply = require('./_transforms').transformsMultiply,
transformArc = require('./_transforms').transformArc,
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'],
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
prevCtrlPoint;
transform2js = require('./_transforms').transform2js,
transformsMultiply = require('./_transforms').transformsMultiply,
transformArc = require('./_transforms').transformArc,
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
defaultStrokeWidth =
collections.attrsGroupsDefaults.presentation['stroke-width'],
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
prevCtrlPoint;

@@ -23,5 +24,5 @@ /**

*/
exports.path2js = function(path) {
exports.path2js = function (path) {
if (path.pathJS) return path.pathJS;
const pathData = []; // JS representation of the path data
const pathData = []; // JS representation of the path data
const newPathData = parsePathData(path.attr('d').value);

@@ -49,72 +50,52 @@ for (const { command, args } of newPathData) {

*/
var relative2absolute = exports.relative2absolute = function(data) {
var currentPoint = [0, 0],
subpathPoint = [0, 0],
i;
var relative2absolute = (exports.relative2absolute = function (data) {
var currentPoint = [0, 0],
subpathPoint = [0, 0],
i;
return data.map(function(item) {
return data.map(function (item) {
var instruction = item.instruction,
itemData = item.data && item.data.slice();
var instruction = item.instruction,
itemData = item.data && item.data.slice();
if (instruction == 'M') {
set(currentPoint, itemData);
set(subpathPoint, itemData);
} else if ('mlcsqt'.indexOf(instruction) > -1) {
for (i = 0; i < itemData.length; i++) {
itemData[i] += currentPoint[i % 2];
}
set(currentPoint, itemData);
if (instruction == 'M') {
if (instruction == 'm') {
set(subpathPoint, itemData);
}
} else if (instruction == 'a') {
itemData[5] += currentPoint[0];
itemData[6] += currentPoint[1];
set(currentPoint, itemData);
} else if (instruction == 'h') {
itemData[0] += currentPoint[0];
currentPoint[0] = itemData[0];
} else if (instruction == 'v') {
itemData[0] += currentPoint[1];
currentPoint[1] = itemData[0];
} else if ('MZLCSQTA'.indexOf(instruction) > -1) {
set(currentPoint, itemData);
} else if (instruction == 'H') {
currentPoint[0] = itemData[0];
} else if (instruction == 'V') {
currentPoint[1] = itemData[0];
} else if (instruction == 'z') {
set(currentPoint, subpathPoint);
}
set(currentPoint, itemData);
set(subpathPoint, itemData);
return instruction == 'z'
? { instruction: 'z' }
: {
instruction: instruction.toUpperCase(),
data: itemData,
};
});
});
} else if ('mlcsqt'.indexOf(instruction) > -1) {
for (i = 0; i < itemData.length; i++) {
itemData[i] += currentPoint[i % 2];
}
set(currentPoint, itemData);
if (instruction == 'm') {
set(subpathPoint, itemData);
}
} else if (instruction == 'a') {
itemData[5] += currentPoint[0];
itemData[6] += currentPoint[1];
set(currentPoint, itemData);
} else if (instruction == 'h') {
itemData[0] += currentPoint[0];
currentPoint[0] = itemData[0];
} else if (instruction == 'v') {
itemData[0] += currentPoint[1];
currentPoint[1] = itemData[0];
} else if ('MZLCSQTA'.indexOf(instruction) > -1) {
set(currentPoint, itemData);
} else if (instruction == 'H') {
currentPoint[0] = itemData[0];
} else if (instruction == 'V') {
currentPoint[1] = itemData[0];
} else if (instruction == 'z') {
set(currentPoint, subpathPoint);
}
return instruction == 'z' ?
{ instruction: 'z' } :
{
instruction: instruction.toUpperCase(),
data: itemData
};
});
};
/**

@@ -128,3 +109,3 @@ * Apply transformation(s) to the Path data.

*/
exports.applyTransforms = function(elem, path, params) {
exports.applyTransforms = function (elem, path, params) {
// if there are no 'stroke' attr and references to other objects such as

@@ -138,147 +119,167 @@ // gradiends or clip-path which are also subjects to transform.

elem.hasAttr('style') ||
elem.someAttr((attr) =>
referencesProps.includes(attr.name) && attr.value.includes('url(')
elem.someAttr(
(attr) =>
referencesProps.includes(attr.name) && attr.value.includes('url(')
)
) {
return path;
return path;
}
var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
stroke = elem.computedAttr('stroke'),
id = elem.computedAttr('id'),
transformPrecision = params.transformPrecision,
newPoint, scale;
var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
stroke = elem.computedAttr('stroke'),
id = elem.computedAttr('id'),
transformPrecision = params.transformPrecision,
newPoint,
scale;
if (stroke && stroke != '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 (stroke && stroke != '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;
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
if (id) {
var idElem = elem,
hasStrokeWidth = false;
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
if (id) {
var idElem = elem,
hasStrokeWidth = false;
do {
if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
} while (!idElem.hasAttr('id', id) && !hasStrokeWidth && (idElem = idElem.parentNode));
do {
if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
} while (
!idElem.hasAttr('id', id) &&
!hasStrokeWidth &&
(idElem = idElem.parentNode)
);
if (!hasStrokeWidth) return path;
}
if (!hasStrokeWidth) return path;
}
scale = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(transformPrecision);
scale = +Math.sqrt(
matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
).toFixed(transformPrecision);
if (scale !== 1) {
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
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('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-dashoffset')) {
elem.attrs['stroke-dashoffset'].value = elem.attrs['stroke-dashoffset'].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
if (elem.hasAttr('stroke-dashoffset')) {
elem.attrs['stroke-dashoffset'].value = elem.attrs[
'stroke-dashoffset'
].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
if (elem.hasAttr('stroke-dasharray')) {
elem.attrs['stroke-dasharray'].value = elem.attrs['stroke-dasharray'].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
}
if (elem.hasAttr('stroke-dasharray')) {
elem.attrs['stroke-dasharray'].value = elem.attrs[
'stroke-dasharray'
].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
} else if (id) { // Stroke and stroke-width can be redefined with <use>
return path;
}
}
} else if (id) {
// Stroke and stroke-width can be redefined with <use>
return path;
}
path.forEach(function(pathItem) {
path.forEach(function (pathItem) {
if (pathItem.data) {
// h -> l
if (pathItem.instruction === 'h') {
pathItem.instruction = 'l';
pathItem.data[1] = 0;
if (pathItem.data) {
// v -> l
} else if (pathItem.instruction === 'v') {
pathItem.instruction = 'l';
pathItem.data[1] = pathItem.data[0];
pathItem.data[0] = 0;
}
// h -> l
if (pathItem.instruction === 'h') {
// if there is a translate() transform
if (
pathItem.instruction === 'M' &&
(matrix.data[4] !== 0 || matrix.data[5] !== 0)
) {
// then apply it only to the first absoluted M
newPoint = transformPoint(
matrix.data,
pathItem.data[0],
pathItem.data[1]
);
set(pathItem.data, newPoint);
set(pathItem.coords, newPoint);
pathItem.instruction = 'l';
pathItem.data[1] = 0;
// clear translate() data from transform matrix
matrix.data[4] = 0;
matrix.data[5] = 0;
} else {
if (pathItem.instruction == 'a') {
transformArc(pathItem.data, matrix.data);
// v -> l
} else if (pathItem.instruction === 'v') {
// 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);
}
pathItem.instruction = 'l';
pathItem.data[1] = pathItem.data[0];
pathItem.data[0] = 0;
}
// if there is a translate() transform
if (pathItem.instruction === 'M' &&
(matrix.data[4] !== 0 ||
matrix.data[5] !== 0)
) {
// then apply it only to the first absoluted M
newPoint = transformPoint(matrix.data, pathItem.data[0], pathItem.data[1]);
set(pathItem.data, newPoint);
set(pathItem.coords, newPoint);
// clear translate() data from transform matrix
matrix.data[4] = 0;
matrix.data[5] = 0;
} else {
if (pathItem.instruction == 'a') {
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]);
pathItem.data[5] = newPoint[0];
pathItem.data[6] = newPoint[1];
} else {
for (var i = 0; i < pathItem.data.length; i += 2) {
newPoint = transformPoint(matrix.data, pathItem.data[i], pathItem.data[i + 1]);
pathItem.data[i] = newPoint[0];
pathItem.data[i + 1] = newPoint[1];
}
}
pathItem.coords[0] = pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
pathItem.coords[1] = pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
}
newPoint = transformPoint(
matrix.data,
pathItem.data[5],
pathItem.data[6]
);
pathItem.data[5] = newPoint[0];
pathItem.data[6] = newPoint[1];
} else {
for (var i = 0; i < pathItem.data.length; i += 2) {
newPoint = transformPoint(
matrix.data,
pathItem.data[i],
pathItem.data[i + 1]
);
pathItem.data[i] = newPoint[0];
pathItem.data[i + 1] = newPoint[1];
}
}
});
pathItem.coords[0] =
pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
pathItem.coords[1] =
pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
}
}
});
// remove transform attr
elem.removeAttr('transform');
// remove transform attr
elem.removeAttr('transform');
return path;
return path;
};

@@ -294,8 +295,6 @@

function transformPoint(matrix, x, y) {
return [
matrix[0] * x + matrix[2] * y + matrix[4],
matrix[1] * x + matrix[3] * y + matrix[5]
];
return [
matrix[0] * x + matrix[2] * y + matrix[4],
matrix[1] * x + matrix[3] * y + matrix[5],
];
}

@@ -319,65 +318,83 @@

*/
exports.computeCubicBoundingBox = function(xa, ya, xb, yb, xc, yc, xd, yd) {
exports.computeCubicBoundingBox = function (xa, ya, xb, yb, xc, yc, xd, yd) {
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
ts,
t,
x,
y,
i;
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
ts,
t,
x,
y,
i;
// X
if (xa < minx) {
minx = xa;
}
if (xa > maxx) {
maxx = xa;
}
if (xd < minx) {
minx = xd;
}
if (xd > maxx) {
maxx = xd;
}
// X
if (xa < minx) { minx = xa; }
if (xa > maxx) { maxx = xa; }
if (xd < minx) { minx= xd; }
if (xd > maxx) { maxx = xd; }
ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
for (i = 0; i < ts.length; i++) {
t = ts[i];
for (i = 0; i < ts.length; i++) {
if (t >= 0 && t <= 1) {
x = computeCubicBaseValue(t, xa, xb, xc, xd);
// y = computeCubicBaseValue(t, ya, yb, yc, yd);
t = ts[i];
if (t >= 0 && t <= 1) {
x = computeCubicBaseValue(t, xa, xb, xc, xd);
// y = computeCubicBaseValue(t, ya, yb, yc, yd);
if (x < minx) { minx = x; }
if (x > maxx) { maxx = x; }
}
if (x < minx) {
minx = x;
}
if (x > maxx) {
maxx = x;
}
}
}
// Y
if (ya < miny) { miny = ya; }
if (ya > maxy) { maxy = ya; }
if (yd < miny) { miny = yd; }
if (yd > maxy) { maxy = yd; }
// Y
if (ya < miny) {
miny = ya;
}
if (ya > maxy) {
maxy = ya;
}
if (yd < miny) {
miny = yd;
}
if (yd > maxy) {
maxy = yd;
}
ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
for (i = 0; i < ts.length; i++) {
for (i = 0; i < ts.length; i++) {
t = ts[i];
t = ts[i];
if (t >= 0 && t <= 1) {
// x = computeCubicBaseValue(t, xa, xb, xc, xd);
y = computeCubicBaseValue(t, ya, yb, yc, yd);
if (t >= 0 && t <= 1) {
// x = computeCubicBaseValue(t, xa, xb, xc, xd);
y = computeCubicBaseValue(t, ya, yb, yc, yd);
if (y < miny) { miny = y; }
if (y > maxy) { maxy = y; }
}
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
}
}
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy
};
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy,
};
};

@@ -387,7 +404,7 @@

function computeCubicBaseValue(t, a, b, c, d) {
var mt = 1 - t;
var mt = 1 - t;
return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d;
return (
mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d
);
}

@@ -397,15 +414,13 @@

function computeCubicFirstDerivativeRoots(a, b, c, d) {
var result = [-1, -1],
tl = -a + 2 * b - c,
tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c),
dn = -a + 3 * b - 3 * c + d;
var result = [-1, -1],
tl = -a + 2 * b - c,
tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c),
dn = -a + 3 * b - 3 * c + d;
if (dn !== 0) {
result[0] = (tl + tr) / dn;
result[1] = (tl - tr) / dn;
}
if (dn !== 0) {
result[0] = (tl + tr) / dn;
result[1] = (tl - tr) / dn;
}
return result;
return result;
}

@@ -427,52 +442,73 @@

*/
exports.computeQuadraticBoundingBox = function(xa, ya, xb, yb, xc, yc) {
exports.computeQuadraticBoundingBox = function (xa, ya, xb, yb, xc, yc) {
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
t,
x,
y;
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
t,
x,
y;
// X
if (xa < minx) {
minx = xa;
}
if (xa > maxx) {
maxx = xa;
}
if (xc < minx) {
minx = xc;
}
if (xc > maxx) {
maxx = xc;
}
// X
if (xa < minx) { minx = xa; }
if (xa > maxx) { maxx = xa; }
if (xc < minx) { minx = xc; }
if (xc > maxx) { maxx = xc; }
t = computeQuadraticFirstDerivativeRoot(xa, xb, xc);
t = computeQuadraticFirstDerivativeRoot(xa, xb, xc);
if (t >= 0 && t <= 1) {
x = computeQuadraticBaseValue(t, xa, xb, xc);
// y = computeQuadraticBaseValue(t, ya, yb, yc);
if (t >= 0 && t <= 1) {
x = computeQuadraticBaseValue(t, xa, xb, xc);
// y = computeQuadraticBaseValue(t, ya, yb, yc);
if (x < minx) { minx = x; }
if (x > maxx) { maxx = x; }
if (x < minx) {
minx = x;
}
if (x > maxx) {
maxx = x;
}
}
// Y
if (ya < miny) { miny = ya; }
if (ya > maxy) { maxy = ya; }
if (yc < miny) { miny = yc; }
if (yc > maxy) { maxy = yc; }
// Y
if (ya < miny) {
miny = ya;
}
if (ya > maxy) {
maxy = ya;
}
if (yc < miny) {
miny = yc;
}
if (yc > maxy) {
maxy = yc;
}
t = computeQuadraticFirstDerivativeRoot(ya, yb, yc);
t = computeQuadraticFirstDerivativeRoot(ya, yb, yc);
if (t >= 0 && t <=1 ) {
// x = computeQuadraticBaseValue(t, xa, xb, xc);
y = computeQuadraticBaseValue(t, ya, yb, yc);
if (t >= 0 && t <= 1) {
// x = computeQuadraticBaseValue(t, xa, xb, xc);
y = computeQuadraticBaseValue(t, ya, yb, yc);
if (y < miny) { miny = y; }
if (y > maxy) { maxy = y ; }
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
}
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy
};
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy,
};
};

@@ -482,7 +518,5 @@

function computeQuadraticBaseValue(t, a, b, c) {
var mt = 1 - t;
var mt = 1 - t;
return mt * mt * a + 2 * mt * t * b + t * t * c;
return mt * mt * a + 2 * mt * t * b + t * t * c;
}

@@ -492,12 +526,10 @@

function computeQuadraticFirstDerivativeRoot(a, b, c) {
var t = -1,
denominator = a - 2 * b + c;
var t = -1,
denominator = a - 2 * b + c;
if (denominator !== 0) {
t = (a - b) / denominator;
}
if (denominator !== 0) {
t = (a - b) / denominator;
}
return t;
return t;
}

@@ -512,4 +544,3 @@

*/
exports.js2path = function(path, data, params) {
exports.js2path = function (path, data, params) {
path.pathJS = data;

@@ -538,2 +569,3 @@

precision: params.floatPrecision,
disableSpaceAfterFlags: params.noSpaceAfterFlags,
});

@@ -543,5 +575,5 @@ };

function set(dest, source) {
dest[0] = source[source.length - 2];
dest[1] = source[source.length - 1];
return dest;
dest[0] = source[source.length - 2];
dest[1] = source[source.length - 1];
return dest;
}

@@ -558,221 +590,264 @@

*/
exports.intersects = function(path1, path2) {
// Collect points of every subpath.
var points1 = relative2absolute(path1).reduce(gatherPoints, []),
points2 = relative2absolute(path2).reduce(gatherPoints, []);
exports.intersects = function (path1, path2) {
// Collect points of every subpath.
var points1 = relative2absolute(path1).reduce(gatherPoints, []),
points2 = relative2absolute(path2).reduce(gatherPoints, []);
// Axis-aligned bounding box check.
if (points1.maxX <= points2.minX || points2.maxX <= points1.minX ||
points1.maxY <= points2.minY || points2.maxY <= points1.minY ||
points1.every(function (set1) {
return points2.every(function (set2) {
return set1[set1.maxX][0] <= set2[set2.minX][0] ||
set2[set2.maxX][0] <= set1[set1.minX][0] ||
set1[set1.maxY][1] <= set2[set2.minY][1] ||
set2[set2.maxY][1] <= set1[set1.minY][1];
});
})
) return false;
// Axis-aligned bounding box check.
if (
points1.maxX <= points2.minX ||
points2.maxX <= points1.minX ||
points1.maxY <= points2.minY ||
points2.maxY <= points1.minY ||
points1.every(function (set1) {
return points2.every(function (set2) {
return (
set1[set1.maxX][0] <= set2[set2.minX][0] ||
set2[set2.maxX][0] <= set1[set1.minX][0] ||
set1[set1.maxY][1] <= set2[set2.minY][1] ||
set2[set2.maxY][1] <= set1[set1.minY][1]
);
});
})
)
return false;
// Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
var hullNest1 = points1.map(convexHull),
hullNest2 = points2.map(convexHull);
// Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
var hullNest1 = points1.map(convexHull),
hullNest2 = points2.map(convexHull);
// Check intersection of every subpath of the first path with every subpath of the second.
return hullNest1.some(function(hull1) {
if (hull1.length < 3) return false;
// Check intersection of every subpath of the first path with every subpath of the second.
return hullNest1.some(function (hull1) {
if (hull1.length < 3) return false;
return hullNest2.some(function(hull2) {
if (hull2.length < 3) return false;
return hullNest2.some(function (hull2) {
if (hull2.length < 3) return false;
var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
direction = minus(simplex[0]); // set the direction to point towards the origin
var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
direction = minus(simplex[0]); // set the direction to point towards the origin
var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-constant-condition
if (iterations-- == 0) {
console.error('Error: infinite loop while processing mergePaths plugin.');
return true; // true is the safe value that means “do nothing with paths”
}
// add a new point
simplex.push(getSupport(hull1, hull2, direction));
// see if the new point was on the correct side of the origin
if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
// process the simplex
if (processSimplex(simplex, direction)) return true;
}
});
var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-constant-condition
if (iterations-- == 0) {
console.error(
'Error: infinite loop while processing mergePaths plugin.'
);
return true; // true is the safe value that means “do nothing with paths”
}
// add a new point
simplex.push(getSupport(hull1, hull2, direction));
// see if the new point was on the correct side of the origin
if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
// process the simplex
if (processSimplex(simplex, direction)) return true;
}
});
});
function getSupport(a, b, direction) {
return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
}
function getSupport(a, b, direction) {
return sub(supportPoint(a, direction), supportPoint(b, minus(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) {
var index = direction[1] >= 0 ?
direction[0] < 0 ? polygon.maxY : polygon.maxX :
direction[0] < 0 ? polygon.minX : polygon.minY,
max = -Infinity,
value;
while ((value = dot(polygon[index], direction)) > max) {
max = value;
index = ++index % polygon.length;
}
return polygon[(index || polygon.length) - 1];
// 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) {
var index =
direction[1] >= 0
? direction[0] < 0
? polygon.maxY
: polygon.maxX
: direction[0] < 0
? polygon.minX
: polygon.minY,
max = -Infinity,
value;
while ((value = dot(polygon[index], direction)) > max) {
max = value;
index = ++index % polygon.length;
}
return polygon[(index || polygon.length) - 1];
}
};
function processSimplex(simplex, direction) {
// we only need to handle to 1-simplex and 2-simplex
if (simplex.length == 2) {
// 1-simplex
let a = simplex[1],
b = simplex[0],
AO = minus(simplex[1]),
AB = sub(b, a);
// AO is in the same direction as AB
if (dot(AO, AB) > 0) {
// get the vector perpendicular to AB facing O
set(direction, orth(AB, a));
} else {
set(direction, AO);
// only A remains in the simplex
simplex.shift();
}
} else {
// 2-simplex
let a = simplex[2], // [a, b, c] = simplex
b = simplex[1],
c = simplex[0],
AB = sub(b, a),
AC = sub(c, a),
AO = minus(a),
ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
// we only need to handle to 1-simplex and 2-simplex
if (simplex.length == 2) { // 1-simplex
let a = simplex[1],
b = simplex[0],
AO = minus(simplex[1]),
AB = sub(b, a);
// AO is in the same direction as AB
if (dot(AO, AB) > 0) {
// get the vector perpendicular to AB facing O
set(direction, orth(AB, a));
} else {
set(direction, AO);
// only A remains in the simplex
simplex.shift();
}
} else { // 2-simplex
let a = simplex[2], // [a, b, c] = simplex
b = simplex[1],
c = simplex[0],
AB = sub(b, a),
AC = sub(c, a),
AO = minus(a),
ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
if (dot(ACB, AO) > 0) {
if (dot(AB, AO) > 0) { // region 4
set(direction, ACB);
simplex.shift(); // simplex = [b, a]
} else { // region 5
set(direction, AO);
simplex.splice(0, 2); // simplex = [a]
}
} else if (dot(ABC, AO) > 0) {
if (dot(AC, AO) > 0) { // region 6
set(direction, ABC);
simplex.splice(1, 1); // simplex = [c, a]
} else { // region 5 (again)
set(direction, AO);
simplex.splice(0, 2); // simplex = [a]
}
} else // region 7
return true;
}
return false;
if (dot(ACB, AO) > 0) {
if (dot(AB, AO) > 0) {
// region 4
set(direction, ACB);
simplex.shift(); // simplex = [b, a]
} else {
// region 5
set(direction, AO);
simplex.splice(0, 2); // simplex = [a]
}
} else if (dot(ABC, AO) > 0) {
if (dot(AC, AO) > 0) {
// region 6
set(direction, ABC);
simplex.splice(1, 1); // simplex = [c, a]
} else {
// region 5 (again)
set(direction, AO);
simplex.splice(0, 2); // simplex = [a]
}
} // region 7
else return true;
}
return false;
}
function minus(v) {
return [-v[0], -v[1]];
return [-v[0], -v[1]];
}
function sub(v1, v2) {
return [v1[0] - v2[0], v1[1] - v2[1]];
return [v1[0] - v2[0], v1[1] - v2[1]];
}
function dot(v1, v2) {
return v1[0] * v2[0] + v1[1] * v2[1];
return v1[0] * v2[0] + v1[1] * v2[1];
}
function orth(v, from) {
var o = [-v[1], v[0]];
return dot(o, minus(from)) < 0 ? minus(o) : o;
var o = [-v[1], v[0]];
return dot(o, minus(from)) < 0 ? minus(o) : o;
}
function gatherPoints(points, item, index, path) {
var subPath = points.length && points[points.length - 1],
prev = index && path[index - 1],
basePoint = subPath.length && subPath[subPath.length - 1],
data = item.data,
ctrlPoint = basePoint;
var subPath = points.length && points[points.length - 1],
prev = index && path[index - 1],
basePoint = subPath.length && subPath[subPath.length - 1],
data = item.data,
ctrlPoint = basePoint;
switch (item.instruction) {
case 'M':
points.push((subPath = []));
break;
case 'H':
addPoint(subPath, [data[0], basePoint[1]]);
break;
case 'V':
addPoint(subPath, [basePoint[0], data[0]]);
break;
case 'Q':
addPoint(subPath, data.slice(0, 2));
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
break;
case '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]];
}
break;
case 'C':
// Approximate quibic Bezier curve with middle points between control points
addPoint(subPath, [
0.5 * (basePoint[0] + data[0]),
0.5 * (basePoint[1] + data[1]),
]);
addPoint(subPath, [0.5 * (data[0] + data[2]), 0.5 * (data[1] + data[3])]);
addPoint(subPath, [0.5 * (data[2] + data[4]), 0.5 * (data[3] + data[5])]);
prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
break;
case 'S':
if (prev.instruction == 'C' || prev.instruction == 'S') {
addPoint(subPath, [
basePoint[0] + 0.5 * prevCtrlPoint[0],
basePoint[1] + 0.5 * prevCtrlPoint[1],
]);
ctrlPoint = [
basePoint[0] + prevCtrlPoint[0],
basePoint[1] + prevCtrlPoint[1],
];
}
addPoint(subPath, [
0.5 * (ctrlPoint[0] + data[0]),
0.5 * (ctrlPoint[1] + data[1]),
]);
addPoint(subPath, [0.5 * (data[0] + data[2]), 0.5 * (data[1] + data[3])]);
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
break;
case 'A':
// Convert the arc to bezier curves and use the same approximation
var curves = a2c.apply(0, basePoint.concat(data));
for (var cData; (cData = curves.splice(0, 6).map(toAbsolute)).length; ) {
addPoint(subPath, [
0.5 * (basePoint[0] + cData[0]),
0.5 * (basePoint[1] + cData[1]),
]);
addPoint(subPath, [
0.5 * (cData[0] + cData[2]),
0.5 * (cData[1] + cData[3]),
]);
addPoint(subPath, [
0.5 * (cData[2] + cData[4]),
0.5 * (cData[3] + cData[5]),
]);
if (curves.length) addPoint(subPath, (basePoint = cData.slice(-2)));
}
break;
}
// Save final command coordinates
if (data && data.length >= 2) addPoint(subPath, data.slice(-2));
return points;
switch (item.instruction) {
case 'M':
points.push(subPath = []);
break;
case 'H':
addPoint(subPath, [data[0], basePoint[1]]);
break;
case 'V':
addPoint(subPath, [basePoint[0], data[0]]);
break;
case 'Q':
addPoint(subPath, data.slice(0, 2));
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
break;
case '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]];
}
break;
case 'C':
// Approximate quibic Bezier curve with middle points between control points
addPoint(subPath, [.5 * (basePoint[0] + data[0]), .5 * (basePoint[1] + data[1])]);
addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
addPoint(subPath, [.5 * (data[2] + data[4]), .5 * (data[3] + data[5])]);
prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
break;
case '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]];
}
addPoint(subPath, [.5 * (ctrlPoint[0] + data[0]), .5 * (ctrlPoint[1]+ data[1])]);
addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
break;
case 'A':
// Convert the arc to bezier curves and use the same approximation
var curves = a2c.apply(0, basePoint.concat(data));
for (var cData; (cData = curves.splice(0,6).map(toAbsolute)).length;) {
addPoint(subPath, [.5 * (basePoint[0] + cData[0]), .5 * (basePoint[1] + cData[1])]);
addPoint(subPath, [.5 * (cData[0] + cData[2]), .5 * (cData[1] + cData[3])]);
addPoint(subPath, [.5 * (cData[2] + cData[4]), .5 * (cData[3] + cData[5])]);
if (curves.length) addPoint(subPath, basePoint = cData.slice(-2));
}
break;
}
// Save final command coordinates
if (data && data.length >= 2) addPoint(subPath, data.slice(-2));
return points;
function toAbsolute(n, i) {
return n + basePoint[i % 2];
}
function toAbsolute(n, i) { return n + basePoint[i % 2] }
// Writes data about the extreme points on each axle
function addPoint(path, point) {
if (!path.length || point[1] > path[path.maxY][1]) {
path.maxY = path.length;
points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
}
if (!path.length || point[0] > path[path.maxX][0]) {
path.maxX = path.length;
points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
}
if (!path.length || point[1] < path[path.minY][1]) {
path.minY = path.length;
points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
}
if (!path.length || point[0] < path[path.minX][0]) {
path.minX = path.length;
points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
}
path.push(point);
// Writes data about the extreme points on each axle
function addPoint(path, point) {
if (!path.length || point[1] > path[path.maxY][1]) {
path.maxY = path.length;
points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
}
if (!path.length || point[0] > path[path.maxX][0]) {
path.maxX = path.length;
points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
}
if (!path.length || point[1] < path[path.minY][1]) {
path.minY = path.length;
points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
}
if (!path.length || point[0] < path[path.minX][0]) {
path.minX = path.length;
points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
}
path.push(point);
}
}

@@ -787,51 +862,56 @@

function convexHull(points) {
points.sort(function (a, b) {
return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
});
points.sort(function(a, b) {
return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
});
var lower = [],
minY = 0,
bottom = 0;
for (let i = 0; i < points.length; i++) {
while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
lower.pop();
}
if (points[i][1] < points[minY][1]) {
minY = i;
bottom = lower.length;
}
lower.push(points[i]);
var lower = [],
minY = 0,
bottom = 0;
for (let i = 0; i < points.length; i++) {
while (
lower.length >= 2 &&
cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0
) {
lower.pop();
}
if (points[i][1] < points[minY][1]) {
minY = i;
bottom = lower.length;
}
lower.push(points[i]);
}
var upper = [],
maxY = points.length - 1,
top = 0;
for (let i = points.length; i--;) {
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
upper.pop();
}
if (points[i][1] > points[maxY][1]) {
maxY = i;
top = upper.length;
}
upper.push(points[i]);
var upper = [],
maxY = points.length - 1,
top = 0;
for (let i = points.length; i--; ) {
while (
upper.length >= 2 &&
cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0
) {
upper.pop();
}
if (points[i][1] > points[maxY][1]) {
maxY = i;
top = upper.length;
}
upper.push(points[i]);
}
// last points are equal to starting points of the other part
upper.pop();
lower.pop();
// last points are equal to starting points of the other part
upper.pop();
lower.pop();
var hull = lower.concat(upper);
var hull = lower.concat(upper);
hull.minX = 0; // by sorting
hull.maxX = lower.length;
hull.minY = bottom;
hull.maxY = (lower.length + top) % hull.length;
hull.minX = 0; // by sorting
hull.maxX = lower.length;
hull.minY = bottom;
hull.maxY = (lower.length + top) % hull.length;
return hull;
return hull;
}
function cross(o, a, b) {
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
}

@@ -843,81 +923,113 @@

function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
// for more information of where this Math came from visit:
// https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
var _120 = Math.PI * 120 / 180,
rad = Math.PI / 180 * (+angle || 0),
res = [],
rotateX = function(x, y, rad) { return x * Math.cos(rad) - y * Math.sin(rad) },
rotateY = function(x, y, rad) { return x * Math.sin(rad) + y * Math.cos(rad) };
if (!recursive) {
x1 = rotateX(x1, y1, -rad);
y1 = rotateY(x1, y1, -rad);
x2 = rotateX(x2, y2, -rad);
y2 = rotateY(x2, y2, -rad);
var x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
if (h > 1) {
h = Math.sqrt(h);
rx = h * rx;
ry = h * ry;
}
var rx2 = rx * rx,
ry2 = ry * ry,
k = (large_arc_flag == sweep_flag ? -1 : 1) *
Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
cx = k * rx * y / ry + (x1 + x2) / 2,
cy = k * -ry * x / rx + (y1 + y2) / 2,
f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
function a2c(
x1,
y1,
rx,
ry,
angle,
large_arc_flag,
sweep_flag,
x2,
y2,
recursive
) {
// for more information of where this Math came from visit:
// https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
var _120 = (Math.PI * 120) / 180,
rad = (Math.PI / 180) * (+angle || 0),
res = [],
rotateX = function (x, y, rad) {
return x * Math.cos(rad) - y * Math.sin(rad);
},
rotateY = function (x, y, rad) {
return x * Math.sin(rad) + y * Math.cos(rad);
};
if (!recursive) {
x1 = rotateX(x1, y1, -rad);
y1 = rotateY(x1, y1, -rad);
x2 = rotateX(x2, y2, -rad);
y2 = rotateY(x2, y2, -rad);
var x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
if (h > 1) {
h = Math.sqrt(h);
rx = h * rx;
ry = h * ry;
}
var rx2 = rx * rx,
ry2 = ry * ry,
k =
(large_arc_flag == sweep_flag ? -1 : 1) *
Math.sqrt(
Math.abs(
(rx2 * ry2 - rx2 * y * y - ry2 * x * x) /
(rx2 * y * y + ry2 * x * x)
)
),
cx = (k * rx * y) / ry + (x1 + x2) / 2,
cy = (k * -ry * x) / rx + (y1 + y2) / 2,
f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
f1 = x1 < cx ? Math.PI - f1 : f1;
f2 = x2 < cx ? Math.PI - f2 : f2;
f1 < 0 && (f1 = Math.PI * 2 + f1);
f2 < 0 && (f2 = Math.PI * 2 + f2);
if (sweep_flag && f1 > f2) {
f1 = f1 - Math.PI * 2;
}
if (!sweep_flag && f2 > f1) {
f2 = f2 - Math.PI * 2;
}
} else {
f1 = recursive[0];
f2 = recursive[1];
cx = recursive[2];
cy = recursive[3];
f1 = x1 < cx ? Math.PI - f1 : f1;
f2 = x2 < cx ? Math.PI - f2 : f2;
f1 < 0 && (f1 = Math.PI * 2 + f1);
f2 < 0 && (f2 = Math.PI * 2 + f2);
if (sweep_flag && f1 > f2) {
f1 = f1 - Math.PI * 2;
}
var df = f2 - f1;
if (Math.abs(df) > _120) {
var f2old = f2,
x2old = x2,
y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
x2 = cx + rx * Math.cos(f2);
y2 = cy + ry * Math.sin(f2);
res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
if (!sweep_flag && f2 > f1) {
f2 = f2 - Math.PI * 2;
}
df = f2 - f1;
var c1 = Math.cos(f1),
s1 = Math.sin(f1),
c2 = Math.cos(f2),
s2 = Math.sin(f2),
t = Math.tan(df / 4),
hx = 4 / 3 * rx * t,
hy = 4 / 3 * ry * t,
m = [
- hx * s1, hy * c1,
x2 + hx * s2 - x1, y2 - hy * c2 - y1,
x2 - x1, y2 - y1
];
if (recursive) {
return m.concat(res);
} else {
res = m.concat(res);
var newres = [];
for (var i = 0, n = res.length; i < n; i++) {
newres[i] = i % 2 ? rotateY(res[i - 1], res[i], rad) : rotateX(res[i], res[i + 1], rad);
}
return newres;
} else {
f1 = recursive[0];
f2 = recursive[1];
cx = recursive[2];
cy = recursive[3];
}
var df = f2 - f1;
if (Math.abs(df) > _120) {
var f2old = f2,
x2old = x2,
y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
x2 = cx + rx * Math.cos(f2);
y2 = cy + ry * Math.sin(f2);
res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [
f2,
f2old,
cx,
cy,
]);
}
df = f2 - f1;
var c1 = Math.cos(f1),
s1 = Math.sin(f1),
c2 = Math.cos(f2),
s2 = Math.sin(f2),
t = Math.tan(df / 4),
hx = (4 / 3) * rx * t,
hy = (4 / 3) * ry * t,
m = [
-hx * s1,
hy * c1,
x2 + hx * s2 - x1,
y2 - hy * c2 - y1,
x2 - x1,
y2 - y1,
];
if (recursive) {
return m.concat(res);
} else {
res = m.concat(res);
var newres = [];
for (var i = 0, n = res.length; i < n; i++) {
newres[i] =
i % 2
? rotateY(res[i - 1], res[i], rad)
: rotateX(res[i], res[i + 1], rad);
}
return newres;
}
}
'use strict';
var regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/,
regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,
regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,
regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;

@@ -14,35 +14,32 @@ /**

*/
exports.transform2js = function(transformString) {
exports.transform2js = function (transformString) {
// JS representation of the transform data
var transforms = [],
// current transform context
current;
// JS representation of the transform data
var transforms = [],
// current transform context
current;
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
transformString.split(regTransformSplit).forEach(function (item) {
var num;
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
transformString.split(regTransformSplit).forEach(function(item) {
var num;
if (item) {
// if item is a translate function
if (regTransformTypes.test(item)) {
// then collect it and change current context
transforms.push(current = { name: item });
// else if item is data
} else {
// then split it into [10, 50] and collect as context.data
// eslint-disable-next-line no-cond-assign
while (num = regNumericValues.exec(item)) {
num = Number(num);
if (current.data)
current.data.push(num);
else
current.data = [num];
}
}
if (item) {
// if item is a translate function
if (regTransformTypes.test(item)) {
// then collect it and change current context
transforms.push((current = { name: item }));
// else if item is data
} else {
// then split it into [10, 50] and collect as context.data
// eslint-disable-next-line no-cond-assign
while ((num = regNumericValues.exec(item))) {
num = Number(num);
if (current.data) current.data.push(num);
else current.data = [num];
}
});
}
}
});
// return empty array if broken transform (no data)
return current && current.data ? transforms : [];
// return empty array if broken transform (no data)
return current && current.data ? transforms : [];
};

@@ -56,20 +53,19 @@

*/
exports.transformsMultiply = function(transforms) {
exports.transformsMultiply = function (transforms) {
// convert transforms objects to the matrices
transforms = transforms.map(function (transform) {
if (transform.name === 'matrix') {
return transform.data;
}
return transformToMatrix(transform);
});
// convert transforms objects to the matrices
transforms = transforms.map(function(transform) {
if (transform.name === 'matrix') {
return transform.data;
}
return transformToMatrix(transform);
});
// multiply all matrices into one
transforms = {
name: 'matrix',
data:
transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : [],
};
// multiply all matrices into one
transforms = {
name: 'matrix',
data: transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : []
};
return transforms;
return transforms;
};

@@ -82,38 +78,36 @@

*/
var mth = exports.mth = {
var mth = (exports.mth = {
rad: function (deg) {
return (deg * Math.PI) / 180;
},
rad: function(deg) {
return deg * Math.PI / 180;
},
deg: function (rad) {
return (rad * 180) / Math.PI;
},
deg: function(rad) {
return rad * 180 / Math.PI;
},
cos: function (deg) {
return Math.cos(this.rad(deg));
},
cos: function(deg) {
return Math.cos(this.rad(deg));
},
acos: function (val, floatPrecision) {
return +this.deg(Math.acos(val)).toFixed(floatPrecision);
},
acos: function(val, floatPrecision) {
return +(this.deg(Math.acos(val)).toFixed(floatPrecision));
},
sin: function (deg) {
return Math.sin(this.rad(deg));
},
sin: function(deg) {
return Math.sin(this.rad(deg));
},
asin: function (val, floatPrecision) {
return +this.deg(Math.asin(val)).toFixed(floatPrecision);
},
asin: function(val, floatPrecision) {
return +(this.deg(Math.asin(val)).toFixed(floatPrecision));
},
tan: function (deg) {
return Math.tan(this.rad(deg));
},
tan: function(deg) {
return Math.tan(this.rad(deg));
},
atan: function (val, floatPrecision) {
return +this.deg(Math.atan(val)).toFixed(floatPrecision);
},
});
atan: function(val, floatPrecision) {
return +(this.deg(Math.atan(val)).toFixed(floatPrecision));
}
};
/**

@@ -126,68 +120,85 @@ * Decompose matrix into simple transforms. See

*/
exports.matrixToTransform = function(transform, params) {
var floatPrecision = params.floatPrecision,
data = transform.data,
transforms = [],
sx = +Math.hypot(data[0], 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;
exports.matrixToTransform = function (transform, params) {
var floatPrecision = params.floatPrecision,
data = transform.data,
transforms = [],
sx = +Math.hypot(data[0], 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;
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
if (data[4] || data[5]) {
transforms.push({ name: 'translate', data: data.slice(4, data[5] ? 6 : 5) });
}
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
if (data[4] || data[5]) {
transforms.push({
name: 'translate',
data: data.slice(4, data[5] ? 6 : 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, 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, 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];
} else if (data[1] && !data[2]) {
transforms.push({
name: 'skewY',
data: [mth.atan(data[1] / data[0], floatPrecision)],
});
sx = data[0];
sy = data[3];
// [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 = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], 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)];
} 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]);
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),
];
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
if (rowsSum && colsSum) transforms.push({
name: 'skewX',
data: [mth.atan(colsSum / (sx * sx), floatPrecision)]
});
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);
}
// 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);
}
// Too many transformations, return original matrix if it isn't just a scale/translate
} else if (data[1] || data[2]) {
return transform;
}
} else if (data[1] || data[2]) {
return transform;
}
if (scaleBefore && (sx != 1 || sy != 1) || !transforms.length) transforms.push({
name: 'scale',
data: sx == sy ? [sx] : [sx, sy]
if ((scaleBefore && (sx != 1 || sy != 1)) || !transforms.length)
transforms.push({
name: 'scale',
data: sx == sy ? [sx] : [sx, sy],
});
return transforms;
return transforms;
};

@@ -202,37 +213,49 @@

function transformToMatrix(transform) {
if (transform.name === 'matrix') return transform.data;
if (transform.name === 'matrix') return transform.data;
var matrix;
var matrix;
switch (transform.name) {
case 'translate':
// [1, 0, 0, 1, tx, ty]
matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
break;
case 'scale':
// [sx, 0, 0, sy, 0, 0]
matrix = [
transform.data[0],
0,
0,
transform.data[1] || transform.data[0],
0,
0,
];
break;
case 'rotate':
// [cos(a), sin(a), -sin(a), cos(a), x, y]
var cos = mth.cos(transform.data[0]),
sin = mth.sin(transform.data[0]),
cx = transform.data[1] || 0,
cy = transform.data[2] || 0;
switch (transform.name) {
case 'translate':
// [1, 0, 0, 1, tx, ty]
matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
break;
case 'scale':
// [sx, 0, 0, sy, 0, 0]
matrix = [transform.data[0], 0, 0, transform.data[1] || transform.data[0], 0, 0];
break;
case 'rotate':
// [cos(a), sin(a), -sin(a), cos(a), x, y]
var cos = mth.cos(transform.data[0]),
sin = mth.sin(transform.data[0]),
cx = transform.data[1] || 0,
cy = transform.data[2] || 0;
matrix = [
cos,
sin,
-sin,
cos,
(1 - cos) * cx + sin * cy,
(1 - cos) * cy - sin * cx,
];
break;
case 'skewX':
// [1, 0, tan(a), 1, 0, 0]
matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
break;
case 'skewY':
// [1, tan(a), 0, 1, 0, 0]
matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
break;
}
matrix = [cos, sin, -sin, cos, (1 - cos) * cx + sin * cy, (1 - cos) * cy - sin * cx];
break;
case 'skewX':
// [1, 0, tan(a), 1, 0, 0]
matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
break;
case 'skewY':
// [1, tan(a), 0, 1, 0, 0]
matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
break;
}
return matrix;
return matrix;
}

@@ -250,47 +273,52 @@

*/
exports.transformArc = function(arc, transform) {
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[6] * cos - arc[5] * sin, 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.hypot(m[0] - m[3], m[1] + m[2]) *
Math.hypot(m[0] + m[3], m[1] - m[2]);
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[6] * cos - arc[5] * sin, 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.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[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.hypot(term1, term2)) *
180) /
Math.PI;
}
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.hypot(term1, 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];
}
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;
return arc;
};

@@ -306,12 +334,10 @@

function multiplyTransformMatrices(a, b) {
return [
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]
];
return [
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],
];
}
'use strict';
const { computeStyle } = require('../lib/style.js');
const { pathElems } = require('./_collections.js');
const { path2js, js2path, applyTransforms } = require('./_path.js');
const { cleanupOutData } = require('../lib/svgo/tools');
exports.type = 'perItem';

@@ -7,37 +12,31 @@

exports.description = 'optimizes path data: writes in shorter form, applies transformations';
exports.description =
'optimizes path data: writes in shorter form, applies transformations';
exports.params = {
applyTransforms: true,
applyTransformsStroked: true,
makeArcs: {
threshold: 2.5, // coefficient of rounding error
tolerance: 0.5 // percentage of radius
},
straightCurves: true,
lineShorthands: true,
curveSmoothShorthands: true,
floatPrecision: 3,
transformPrecision: 5,
removeUseless: true,
collapseRepeated: true,
utilizeAbsolute: true,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
forceAbsolutePath: false,
applyTransforms: true,
applyTransformsStroked: true,
makeArcs: {
threshold: 2.5, // coefficient of rounding error
tolerance: 0.5, // percentage of radius
},
straightCurves: true,
lineShorthands: true,
curveSmoothShorthands: true,
floatPrecision: 3,
transformPrecision: 5,
removeUseless: true,
collapseRepeated: true,
utilizeAbsolute: true,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
forceAbsolutePath: false,
};
var pathElems = require('./_collections.js').pathElems,
path2js = require('./_path.js').path2js,
js2path = require('./_path.js').js2path,
applyTransforms = require('./_path.js').applyTransforms,
cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
roundData,
precision,
error,
arcThreshold,
arcTolerance,
hasMarkerMid,
hasStrokeLinecap;
let roundData;
let precision;
let error;
let arcThreshold;
let arcTolerance;

@@ -60,42 +59,47 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.isElem(pathElems) && item.hasAttr('d')) {
const computedStyle = computeStyle(item);
precision = params.floatPrecision;
error =
precision !== false ? +Math.pow(0.1, precision).toFixed(precision) : 1e-2;
roundData = precision > 0 && precision < 20 ? strongRound : round;
if (params.makeArcs) {
arcThreshold = params.makeArcs.threshold;
arcTolerance = params.makeArcs.tolerance;
}
const hasMarkerMid = computedStyle['marker-mid'] != null;
if (item.isElem(pathElems) && item.hasAttr('d')) {
const maybeHasStroke =
computedStyle.stroke &&
(computedStyle.stroke.type === 'dynamic' ||
computedStyle.stroke.value !== 'none');
const maybeHasLinecap =
computedStyle['stroke-linecap'] &&
(computedStyle['stroke-linecap'].type === 'dynamic' ||
computedStyle['stroke-linecap'].value !== 'butt');
const maybeHasStrokeAndLinecap = maybeHasStroke && maybeHasLinecap;
precision = params.floatPrecision;
error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2;
roundData = precision > 0 && precision < 20 ? strongRound : round;
if (params.makeArcs) {
arcThreshold = params.makeArcs.threshold;
arcTolerance = params.makeArcs.tolerance;
}
hasMarkerMid = item.hasAttr('marker-mid');
var data = path2js(item);
const stroke = item.computedAttr('stroke');
const strokeLinecap = item.computedAttr('stroke-linecap');
// stroke-linecap may exist in inline styles which are not parsed for now
hasStrokeLinecap = item.hasAttr('style') ||
stroke && stroke != 'none' && strokeLinecap && strokeLinecap != 'butt';
// TODO: get rid of functions returns
if (data.length) {
convertToRelative(data);
var data = path2js(item);
if (params.applyTransforms) {
data = applyTransforms(item, data, params);
}
// TODO: get rid of functions returns
if (data.length) {
convertToRelative(data);
data = filters(data, params, {
maybeHasStrokeAndLinecap,
hasMarkerMid,
});
if (params.applyTransforms) {
data = applyTransforms(item, data, params);
}
if (params.utilizeAbsolute) {
data = convertToMixed(data, params);
}
data = filters(data, params);
if (params.utilizeAbsolute) {
data = convertToMixed(data, params);
}
js2path(item, data, params);
}
js2path(item, data, params);
}
}
};

@@ -111,158 +115,137 @@

function convertToRelative(path) {
var point = [0, 0],
subpathPoint = [0, 0],
baseItem;
var point = [0, 0],
subpathPoint = [0, 0],
baseItem;
path.forEach(function (item, index) {
var instruction = item.instruction,
data = item.data;
path.forEach(function(item, index) {
// data !== !z
if (data) {
// already relative
// recalculate current point
if ('mcslqta'.indexOf(instruction) > -1) {
point[0] += data[data.length - 2];
point[1] += data[data.length - 1];
var instruction = item.instruction,
data = item.data;
if (instruction === 'm') {
subpathPoint[0] = point[0];
subpathPoint[1] = point[1];
baseItem = item;
}
} else if (instruction === 'h') {
point[0] += data[0];
} else if (instruction === 'v') {
point[1] += data[0];
}
// data !== !z
if (data) {
// convert absolute path data coordinates to relative
// if "M" was not transformed from "m"
// M → m
if (instruction === 'M') {
if (index > 0) instruction = 'm';
// already relative
// recalculate current point
if ('mcslqta'.indexOf(instruction) > -1) {
data[0] -= point[0];
data[1] -= point[1];
point[0] += data[data.length - 2];
point[1] += data[data.length - 1];
subpathPoint[0] = point[0] += data[0];
subpathPoint[1] = point[1] += data[1];
if (instruction === 'm') {
subpathPoint[0] = point[0];
subpathPoint[1] = point[1];
baseItem = item;
}
baseItem = item;
}
} else if (instruction === 'h') {
// L → l
// T → t
else if ('LT'.indexOf(instruction) > -1) {
instruction = instruction.toLowerCase();
point[0] += data[0];
// x y
// 0 1
data[0] -= point[0];
data[1] -= point[1];
} else if (instruction === 'v') {
point[0] += data[0];
point[1] += data[1];
point[1] += data[0];
// C → c
} else if (instruction === 'C') {
instruction = 'c';
}
// x1 y1 x2 y2 x y
// 0 1 2 3 4 5
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
data[4] -= point[0];
data[5] -= point[1];
// convert absolute path data coordinates to relative
// if "M" was not transformed from "m"
// M → m
if (instruction === 'M') {
point[0] += data[4];
point[1] += data[5];
if (index > 0) instruction = 'm';
// S → s
// Q → q
} else if ('SQ'.indexOf(instruction) > -1) {
instruction = instruction.toLowerCase();
data[0] -= point[0];
data[1] -= point[1];
// x1 y1 x y
// 0 1 2 3
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
subpathPoint[0] = point[0] += data[0];
subpathPoint[1] = point[1] += data[1];
point[0] += data[2];
point[1] += data[3];
baseItem = item;
// A → a
} else if (instruction === 'A') {
instruction = 'a';
}
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
// 0 1 2 3 4 5 6
data[5] -= point[0];
data[6] -= point[1];
// L → l
// T → t
else if ('LT'.indexOf(instruction) > -1) {
point[0] += data[5];
point[1] += data[6];
instruction = instruction.toLowerCase();
// H → h
} else if (instruction === 'H') {
instruction = 'h';
// x y
// 0 1
data[0] -= point[0];
data[1] -= point[1];
data[0] -= point[0];
point[0] += data[0];
point[1] += data[1];
point[0] += data[0];
// C → c
} else if (instruction === 'C') {
// V → v
} else if (instruction === 'V') {
instruction = 'v';
instruction = 'c';
data[0] -= point[1];
// x1 y1 x2 y2 x y
// 0 1 2 3 4 5
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
data[4] -= point[0];
data[5] -= point[1];
point[1] += data[0];
}
point[0] += data[4];
point[1] += data[5];
item.instruction = instruction;
item.data = data;
// S → s
// Q → q
} else if ('SQ'.indexOf(instruction) > -1) {
// store absolute coordinates for later use
item.coords = point.slice(-2);
}
instruction = instruction.toLowerCase();
// !data === z, reset current point
else if (instruction == 'z') {
if (baseItem) {
item.coords = baseItem.coords;
}
point[0] = subpathPoint[0];
point[1] = subpathPoint[1];
}
// x1 y1 x y
// 0 1 2 3
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
item.base = index > 0 ? path[index - 1].coords : [0, 0];
});
point[0] += data[2];
point[1] += data[3];
// A → a
} else if (instruction === 'A') {
instruction = 'a';
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
// 0 1 2 3 4 5 6
data[5] -= point[0];
data[6] -= point[1];
point[0] += data[5];
point[1] += data[6];
// H → h
} else if (instruction === 'H') {
instruction = 'h';
data[0] -= point[0];
point[0] += data[0];
// V → v
} else if (instruction === 'V') {
instruction = 'v';
data[0] -= point[1];
point[1] += data[0];
}
item.instruction = instruction;
item.data = data;
// store absolute coordinates for later use
item.coords = point.slice(-2);
}
// !data === z, reset current point
else if (instruction == 'z') {
if (baseItem) {
item.coords = baseItem.coords;
}
point[0] = subpathPoint[0];
point[1] = subpathPoint[1];
}
item.base = index > 0 ? path[index - 1].coords : [0, 0];
});
return path;
return path;
}

@@ -277,370 +260,356 @@

*/
function filters(path, params) {
function filters(path, params, { maybeHasStrokeAndLinecap, hasMarkerMid }) {
var stringify = data2Path.bind(null, params),
relSubpoint = [0, 0],
pathBase = [0, 0],
prev = {};
var stringify = data2Path.bind(null, params),
relSubpoint = [0, 0],
pathBase = [0, 0],
prev = {};
path = path.filter(function (item, index, path) {
var instruction = item.instruction,
data = item.data,
next = path[index + 1];
path = path.filter(function(item, index, path) {
if (data) {
var sdata = data,
circle;
var instruction = item.instruction,
data = item.data,
next = path[index + 1];
if (instruction === 's') {
sdata = [0, 0].concat(data);
if (data) {
if ('cs'.indexOf(prev.instruction) > -1) {
var pdata = prev.data,
n = pdata.length;
var sdata = data,
circle;
// (-x, -y) of the prev tangent point relative to the current point
sdata[0] = pdata[n - 2] - pdata[n - 4];
sdata[1] = pdata[n - 1] - pdata[n - 3];
}
}
if (instruction === 's') {
sdata = [0, 0].concat(data);
// convert curves to arcs if possible
if (
params.makeArcs &&
(instruction == 'c' || instruction == 's') &&
isConvex(sdata) &&
(circle = findCircle(sdata))
) {
var r = roundData([circle.radius])[0],
angle = findArcAngle(sdata, circle),
sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
arc = {
instruction: 'a',
data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
coords: item.coords.slice(),
base: item.base,
},
output = [arc],
// relative coordinates to adjust the found circle
relCenter = [
circle.center[0] - sdata[4],
circle.center[1] - sdata[5],
],
relCircle = { center: relCenter, radius: circle.radius },
arcCurves = [item],
hasPrev = 0,
suffix = '',
nextLonghand;
if ('cs'.indexOf(prev.instruction) > -1) {
var pdata = prev.data,
n = pdata.length;
if (
(prev.instruction == 'c' &&
isConvex(prev.data) &&
isArcPrev(prev.data, circle)) ||
(prev.instruction == 'a' &&
prev.sdata &&
isArcPrev(prev.sdata, circle))
) {
arcCurves.unshift(prev);
arc.base = prev.base;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
var prevAngle = findArcAngle(prevData, {
center: [
prevData[4] + circle.center[0],
prevData[5] + circle.center[1],
],
radius: circle.radius,
});
angle += prevAngle;
if (angle > Math.PI) arc.data[3] = 1;
hasPrev = 1;
}
// (-x, -y) of the prev tangent point relative to the current point
sdata[0] = pdata[n - 2] - pdata[n - 4];
sdata[1] = pdata[n - 1] - pdata[n - 3];
}
// check if next curves are fitting the arc
for (
var j = index;
(next = path[++j]) && ~'cs'.indexOf(next.instruction);
) {
var nextData = next.data;
if (next.instruction == 's') {
nextLonghand = makeLonghand(
{ instruction: 's', data: next.data.slice() },
path[j - 1].data
);
nextData = nextLonghand.data;
nextLonghand.data = nextData.slice(0, 2);
suffix = stringify([nextLonghand]);
}
if (isConvex(nextData) && isArc(nextData, relCircle)) {
angle += findArcAngle(nextData, relCircle);
if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
if (angle > Math.PI) arc.data[3] = 1;
arcCurves.push(next);
if (2 * Math.PI - angle > 1e-3) {
// less than 360°
arc.coords = next.coords;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
} else {
// full circle, make a half-circle arc and add a second one
arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
arc.coords = [
arc.base[0] + arc.data[5],
arc.base[1] + arc.data[6],
];
arc = {
instruction: 'a',
data: [
r,
r,
0,
0,
sweep,
next.coords[0] - arc.coords[0],
next.coords[1] - arc.coords[1],
],
coords: next.coords,
base: arc.coords,
};
output.push(arc);
j++;
break;
}
relCenter[0] -= nextData[4];
relCenter[1] -= nextData[5];
} else break;
}
// convert curves to arcs if possible
if (
params.makeArcs &&
(instruction == 'c' || instruction == 's') &&
isConvex(sdata) &&
(circle = findCircle(sdata))
) {
var r = roundData([circle.radius])[0],
angle = findArcAngle(sdata, circle),
sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
arc = {
instruction: 'a',
data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
coords: item.coords.slice(),
base: item.base
},
output = [arc],
// relative coordinates to adjust the found circle
relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]],
relCircle = { center: relCenter, radius: circle.radius },
arcCurves = [item],
hasPrev = 0,
suffix = '',
nextLonghand;
if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
if (path[j] && path[j].instruction == 's') {
makeLonghand(path[j], path[j - 1].data);
}
if (hasPrev) {
var prevArc = output.shift();
roundData(prevArc.data);
relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
prev.instruction = 'a';
prev.data = prevArc.data;
item.base = prev.coords = prevArc.coords;
}
arc = output.shift();
if (arcCurves.length == 1) {
item.sdata = sdata.slice(); // preserve curve data for future checks
} else if (arcCurves.length - 1 - hasPrev > 0) {
// filter out consumed next items
path.splice.apply(
path,
[index + 1, arcCurves.length - 1 - hasPrev].concat(output)
);
}
if (!arc) return false;
instruction = 'a';
data = arc.data;
item.coords = arc.coords;
}
}
if (
prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) ||
prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle)
) {
arcCurves.unshift(prev);
arc.base = prev.base;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
var prevAngle = findArcAngle(prevData,
{
center: [prevData[4] + circle.center[0], prevData[5] + circle.center[1]],
radius: circle.radius
}
);
angle += prevAngle;
if (angle > Math.PI) arc.data[3] = 1;
hasPrev = 1;
}
// Rounding relative coordinates, taking in account accummulating error
// to get closer to absolute coordinates. Sum of rounded value remains same:
// l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2
if (precision !== false) {
if ('mltqsc'.indexOf(instruction) > -1) {
for (var i = data.length; i--; ) {
data[i] += item.base[i % 2] - relSubpoint[i % 2];
}
} else if (instruction == 'h') {
data[0] += item.base[0] - relSubpoint[0];
} else if (instruction == 'v') {
data[0] += item.base[1] - relSubpoint[1];
} else if (instruction == 'a') {
data[5] += item.base[0] - relSubpoint[0];
data[6] += item.base[1] - relSubpoint[1];
}
roundData(data);
// check if next curves are fitting the arc
for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) {
var nextData = next.data;
if (next.instruction == 's') {
nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() },
path[j - 1].data);
nextData = nextLonghand.data;
nextLonghand.data = nextData.slice(0, 2);
suffix = stringify([nextLonghand]);
}
if (isConvex(nextData) && isArc(nextData, relCircle)) {
angle += findArcAngle(nextData, relCircle);
if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
if (angle > Math.PI) arc.data[3] = 1;
arcCurves.push(next);
if (2 * Math.PI - angle > 1e-3) { // less than 360°
arc.coords = next.coords;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
} else {
// full circle, make a half-circle arc and add a second one
arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]];
arc = {
instruction: 'a',
data: [r, r, 0, 0, sweep,
next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]],
coords: next.coords,
base: arc.coords
};
output.push(arc);
j++;
break;
}
relCenter[0] -= nextData[4];
relCenter[1] -= nextData[5];
} else break;
}
if (instruction == 'h') relSubpoint[0] += data[0];
else if (instruction == 'v') relSubpoint[1] += data[0];
else {
relSubpoint[0] += data[data.length - 2];
relSubpoint[1] += data[data.length - 1];
}
roundData(relSubpoint);
if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
if (path[j] && path[j].instruction == 's') {
makeLonghand(path[j], path[j - 1].data);
}
if (hasPrev) {
var prevArc = output.shift();
roundData(prevArc.data);
relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
prev.instruction = 'a';
prev.data = prevArc.data;
item.base = prev.coords = prevArc.coords;
}
arc = output.shift();
if (arcCurves.length == 1) {
item.sdata = sdata.slice(); // preserve curve data for future checks
} else if (arcCurves.length - 1 - hasPrev > 0) {
// filter out consumed next items
path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output));
}
if (!arc) return false;
instruction = 'a';
data = arc.data;
item.coords = arc.coords;
}
}
if (instruction.toLowerCase() == 'm') {
pathBase[0] = relSubpoint[0];
pathBase[1] = relSubpoint[1];
}
}
// Rounding relative coordinates, taking in account accummulating error
// to get closer to absolute coordinates. Sum of rounded value remains same:
// l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2
if (precision !== false) {
if ('mltqsc'.indexOf(instruction) > -1) {
for (var i = data.length; i--;) {
data[i] += item.base[i % 2] - relSubpoint[i % 2];
}
} else if (instruction == 'h') {
data[0] += item.base[0] - relSubpoint[0];
} else if (instruction == 'v') {
data[0] += item.base[1] - relSubpoint[1];
} else if (instruction == 'a') {
data[5] += item.base[0] - relSubpoint[0];
data[6] += item.base[1] - relSubpoint[1];
}
roundData(data);
// convert straight curves into lines segments
if (params.straightCurves) {
if (
(instruction === 'c' && isCurveStraightLine(data)) ||
(instruction === 's' && isCurveStraightLine(sdata))
) {
if (next && next.instruction == 's') makeLonghand(next, data); // fix up next curve
instruction = 'l';
data = data.slice(-2);
} else if (instruction === 'q' && isCurveStraightLine(data)) {
if (next && next.instruction == 't') makeLonghand(next, data); // fix up next curve
instruction = 'l';
data = data.slice(-2);
} else if (
instruction === 't' &&
prev.instruction !== 'q' &&
prev.instruction !== 't'
) {
instruction = 'l';
data = data.slice(-2);
} else if (instruction === 'a' && (data[0] === 0 || data[1] === 0)) {
instruction = 'l';
data = data.slice(-2);
}
}
if (instruction == 'h') relSubpoint[0] += data[0];
else if (instruction == 'v') relSubpoint[1] += data[0];
else {
relSubpoint[0] += data[data.length - 2];
relSubpoint[1] += data[data.length - 1];
}
roundData(relSubpoint);
// horizontal and vertical line shorthands
// l 50 0 → h 50
// l 0 50 → v 50
if (params.lineShorthands && instruction === 'l') {
if (data[1] === 0) {
instruction = 'h';
data.pop();
} else if (data[0] === 0) {
instruction = 'v';
data.shift();
}
}
if (instruction.toLowerCase() == 'm') {
pathBase[0] = relSubpoint[0];
pathBase[1] = relSubpoint[1];
}
}
// collapse repeated commands
// h 20 h 30 -> h 50
if (
params.collapseRepeated &&
hasMarkerMid === false &&
'mhv'.indexOf(instruction) > -1 &&
prev.instruction &&
instruction == prev.instruction.toLowerCase() &&
((instruction != 'h' && instruction != 'v') ||
prev.data[0] >= 0 == data[0] >= 0)
) {
prev.data[0] += data[0];
if (instruction != 'h' && instruction != 'v') {
prev.data[1] += data[1];
}
prev.coords = item.coords;
path[index] = prev;
return false;
}
// convert straight curves into lines segments
if (params.straightCurves) {
// convert curves into smooth shorthands
if (params.curveSmoothShorthands && prev.instruction) {
// curveto
if (instruction === 'c') {
// c + c → c + s
if (
prev.instruction === 'c' &&
data[0] === -(prev.data[2] - prev.data[4]) &&
data[1] === -(prev.data[3] - prev.data[5])
) {
instruction = 's';
data = data.slice(2);
}
if (
instruction === 'c' &&
isCurveStraightLine(data) ||
instruction === 's' &&
isCurveStraightLine(sdata)
) {
if (next && next.instruction == 's')
makeLonghand(next, data); // fix up next curve
instruction = 'l';
data = data.slice(-2);
}
// s + c → s + s
else if (
prev.instruction === 's' &&
data[0] === -(prev.data[0] - prev.data[2]) &&
data[1] === -(prev.data[1] - prev.data[3])
) {
instruction = 's';
data = data.slice(2);
}
else if (
instruction === 'q' &&
isCurveStraightLine(data)
) {
if (next && next.instruction == 't')
makeLonghand(next, data); // fix up next curve
instruction = 'l';
data = data.slice(-2);
}
// [^cs] + c → [^cs] + s
else if (
'cs'.indexOf(prev.instruction) === -1 &&
data[0] === 0 &&
data[1] === 0
) {
instruction = 's';
data = data.slice(2);
}
}
else if (
instruction === 't' &&
prev.instruction !== 'q' &&
prev.instruction !== 't'
) {
instruction = 'l';
data = data.slice(-2);
}
// quadratic Bézier curveto
else if (instruction === 'q') {
// q + q → q + t
if (
prev.instruction === 'q' &&
data[0] === prev.data[2] - prev.data[0] &&
data[1] === prev.data[3] - prev.data[1]
) {
instruction = 't';
data = data.slice(2);
}
else if (
instruction === 'a' &&
(data[0] === 0 || data[1] === 0)
) {
instruction = 'l';
data = data.slice(-2);
}
}
// t + q → t + t
else if (
prev.instruction === 't' &&
data[2] === prev.data[0] &&
data[3] === prev.data[1]
) {
instruction = 't';
data = data.slice(2);
}
}
}
// horizontal and vertical line shorthands
// l 50 0 → h 50
// l 0 50 → v 50
if (
params.lineShorthands &&
instruction === 'l'
) {
if (data[1] === 0) {
instruction = 'h';
data.pop();
} else if (data[0] === 0) {
instruction = 'v';
data.shift();
}
}
// remove useless non-first path segments
if (params.removeUseless && !maybeHasStrokeAndLinecap) {
// 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 (
'lhvqtcs'.indexOf(instruction) > -1 &&
data.every(function (i) {
return i === 0;
})
) {
path[index] = prev;
return false;
}
// collapse repeated commands
// h 20 h 30 -> h 50
if (
params.collapseRepeated &&
!hasMarkerMid &&
('mhv'.indexOf(instruction) > -1) &&
prev.instruction &&
instruction == prev.instruction.toLowerCase() &&
(
(instruction != 'h' && instruction != 'v') ||
(prev.data[0] >= 0) == (data[0] >= 0)
)) {
prev.data[0] += data[0];
if (instruction != 'h' && instruction != 'v') {
prev.data[1] += data[1];
}
prev.coords = item.coords;
path[index] = prev;
return false;
}
// convert curves into smooth shorthands
if (params.curveSmoothShorthands && prev.instruction) {
// curveto
if (instruction === 'c') {
// c + c → c + s
if (
prev.instruction === 'c' &&
data[0] === -(prev.data[2] - prev.data[4]) &&
data[1] === -(prev.data[3] - prev.data[5])
) {
instruction = 's';
data = data.slice(2);
}
// s + c → s + s
else if (
prev.instruction === 's' &&
data[0] === -(prev.data[0] - prev.data[2]) &&
data[1] === -(prev.data[1] - prev.data[3])
) {
instruction = 's';
data = data.slice(2);
}
// [^cs] + c → [^cs] + s
else if (
'cs'.indexOf(prev.instruction) === -1 &&
data[0] === 0 &&
data[1] === 0
) {
instruction = 's';
data = data.slice(2);
}
}
// quadratic Bézier curveto
else if (instruction === 'q') {
// q + q → q + t
if (
prev.instruction === 'q' &&
data[0] === (prev.data[2] - prev.data[0]) &&
data[1] === (prev.data[3] - prev.data[1])
) {
instruction = 't';
data = data.slice(2);
}
// t + q → t + t
else if (
prev.instruction === 't' &&
data[2] === prev.data[0] &&
data[3] === prev.data[1]
) {
instruction = 't';
data = data.slice(2);
}
}
}
// remove useless non-first path segments
if (params.removeUseless && !hasStrokeLinecap) {
// 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 (
(
'lhvqtcs'.indexOf(instruction) > -1
) &&
data.every(function(i) { return i === 0; })
) {
path[index] = prev;
return false;
}
// a 25,25 -30 0,1 0,0
if (
instruction === 'a' &&
data[5] === 0 &&
data[6] === 0
) {
path[index] = prev;
return false;
}
}
item.instruction = instruction;
item.data = data;
prev = item;
} else {
// z resets coordinates
relSubpoint[0] = pathBase[0];
relSubpoint[1] = pathBase[1];
if (prev.instruction == 'z') return false;
prev = item;
// a 25,25 -30 0,1 0,0
if (instruction === 'a' && data[5] === 0 && data[6] === 0) {
path[index] = prev;
return false;
}
}
return true;
item.instruction = instruction;
item.data = data;
});
prev = item;
} else {
// z resets coordinates
relSubpoint[0] = pathBase[0];
relSubpoint[1] = pathBase[1];
if (prev.instruction == 'z') return false;
prev = item;
}
return path;
return true;
});
return path;
}

@@ -655,62 +624,59 @@

function convertToMixed(path, params) {
var prev = path[0];
var prev = path[0];
path = path.filter(function (item, index) {
if (index == 0) return true;
if (!item.data) {
prev = item;
return true;
}
path = path.filter(function(item, index) {
var instruction = item.instruction,
data = item.data,
adata = data && data.slice(0);
if (index == 0) return true;
if (!item.data) {
prev = item;
return true;
}
if ('mltqsc'.indexOf(instruction) > -1) {
for (var i = adata.length; i--; ) {
adata[i] += item.base[i % 2];
}
} else if (instruction == 'h') {
adata[0] += item.base[0];
} else if (instruction == 'v') {
adata[0] += item.base[1];
} else if (instruction == 'a') {
adata[5] += item.base[0];
adata[6] += item.base[1];
}
var instruction = item.instruction,
data = item.data,
adata = data && data.slice(0);
roundData(adata);
if ('mltqsc'.indexOf(instruction) > -1) {
for (var i = adata.length; i--;) {
adata[i] += item.base[i % 2];
}
} else if (instruction == 'h') {
adata[0] += item.base[0];
} else if (instruction == 'v') {
adata[0] += item.base[1];
} else if (instruction == 'a') {
adata[5] += item.base[0];
adata[6] += item.base[1];
}
var absoluteDataStr = cleanupOutData(adata, params),
relativeDataStr = cleanupOutData(data, params);
roundData(adata);
// Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
// 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 &&
instruction == prev.instruction &&
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;
}
var absoluteDataStr = cleanupOutData(adata, params),
relativeDataStr = cleanupOutData(data, params);
prev = item;
// Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
// 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 &&
instruction == prev.instruction &&
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;
}
return true;
});
prev = item;
return true;
});
return path;
return path;
}

@@ -726,11 +692,20 @@

function isConvex(data) {
var center = getIntersection([
0,
0,
data[2],
data[3],
data[0],
data[1],
data[4],
data[5],
]);
var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]);
return center &&
(data[2] < center[0] == center[0] < 0) &&
(data[3] < center[1] == center[1] < 0) &&
(data[4] < center[0] == center[0] < data[0]) &&
(data[5] < center[1] == center[1] < data[1]);
return (
center &&
data[2] < center[0] == center[0] < 0 &&
data[3] < center[1] == center[1] < 0 &&
data[4] < center[0] == center[0] < data[0] &&
data[5] < center[1] == center[1] < data[1]
);
}

@@ -745,27 +720,23 @@

function getIntersection(coords) {
// Prev line equation parameters.
var a1 = coords[1] - coords[3], // y1 - y2
b1 = coords[2] - coords[0], // x2 - x1
c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
// Next line equation parameters
a2 = coords[5] - coords[7], // y1 - y2
b2 = coords[6] - coords[4], // x2 - x1
c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
denom = a1 * b2 - a2 * b1;
// Prev line equation parameters.
var a1 = coords[1] - coords[3], // y1 - y2
b1 = coords[2] - coords[0], // x2 - x1
c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
if (!denom) return; // parallel lines havn't an intersection
// Next line equation parameters
a2 = coords[5] - coords[7], // y1 - y2
b2 = coords[6] - coords[4], // x2 - x1
c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
denom = (a1 * b2 - a2 * b1);
if (!denom) return; // parallel lines havn't an intersection
var cross = [
(b1 * c2 - b2 * c1) / denom,
(a1 * c2 - a2 * c1) / -denom
];
if (
!isNaN(cross[0]) && !isNaN(cross[1]) &&
isFinite(cross[0]) && isFinite(cross[1])
) {
return cross;
}
var cross = [(b1 * c2 - b2 * c1) / denom, (a1 * c2 - a2 * c1) / -denom];
if (
!isNaN(cross[0]) &&
!isNaN(cross[1]) &&
isFinite(cross[0]) &&
isFinite(cross[1])
) {
return cross;
}
}

@@ -783,11 +754,12 @@

function strongRound(data) {
for (var i = data.length; i-- > 0;) {
if (data[i].toFixed(precision) != data[i]) {
var rounded = +data[i].toFixed(precision - 1);
data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error ?
+data[i].toFixed(precision) :
rounded;
}
for (var i = data.length; i-- > 0; ) {
if (data[i].toFixed(precision) != data[i]) {
var rounded = +data[i].toFixed(precision - 1);
data[i] =
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= error
? +data[i].toFixed(precision)
: rounded;
}
return data;
}
return data;
}

@@ -802,6 +774,6 @@

function round(data) {
for (var i = data.length; i-- > 0;) {
data[i] = Math.round(data[i]);
}
return data;
for (var i = data.length; i-- > 0; ) {
data[i] = Math.round(data[i]);
}
return data;
}

@@ -819,19 +791,17 @@

function isCurveStraightLine(data) {
// Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
var i = data.length - 2,
a = -data[i + 1], // y1 − y2 (y1 = 0)
b = data[i], // x2 − x1 (x1 = 0)
d = 1 / (a * a + b * b); // same part for all points
// Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
var i = data.length - 2,
a = -data[i + 1], // y1 − y2 (y1 = 0)
b = data[i], // x2 − x1 (x1 = 0)
d = 1 / (a * a + b * b); // same part for all points
if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
// Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
while ((i -= 2) >= 0) {
if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
return false;
}
// Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
while ((i -= 2) >= 0) {
if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
return false;
}
return true;
return true;
}

@@ -847,8 +817,15 @@

function makeLonghand(item, data) {
switch (item.instruction) {
case 's': item.instruction = 'c'; break;
case 't': item.instruction = 'q'; break;
}
item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]);
return item;
switch (item.instruction) {
case 's':
item.instruction = 'c';
break;
case 't':
item.instruction = 'q';
break;
}
item.data.unshift(
data[data.length - 2] - data[data.length - 4],
data[data.length - 1] - data[data.length - 3]
);
return item;
}

@@ -865,3 +842,3 @@

function getDistance(point1, point2) {
return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
}

@@ -880,11 +857,11 @@

function getCubicBezierPoint(curve, t) {
var sqrT = t * t,
cubT = sqrT * t,
mt = 1 - t,
sqrMt = mt * mt;
var sqrT = t * t,
cubT = sqrT * t,
mt = 1 - t,
sqrMt = mt * mt;
return [
3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5]
];
return [
3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5],
];
}

@@ -900,19 +877,30 @@

function findCircle(curve) {
var midPoint = getCubicBezierPoint(curve, 1/2),
m1 = [midPoint[0] / 2, midPoint[1] / 2],
m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
center = getIntersection([
m1[0], m1[1],
m1[0] + m1[1], m1[1] - m1[0],
m2[0], m2[1],
m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0])
]),
radius = center && getDistance([0, 0], center),
tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100);
var midPoint = getCubicBezierPoint(curve, 1 / 2),
m1 = [midPoint[0] / 2, midPoint[1] / 2],
m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
center = getIntersection([
m1[0],
m1[1],
m1[0] + m1[1],
m1[1] - m1[0],
m2[0],
m2[1],
m2[0] + (m2[1] - midPoint[1]),
m2[1] - (m2[0] - midPoint[0]),
]),
radius = center && getDistance([0, 0], center),
tolerance = Math.min(arcThreshold * error, (arcTolerance * radius) / 100);
if (center && radius < 1e15 &&
[1/4, 3/4].every(function(point) {
return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance;
}))
return { center: center, radius: radius};
if (
center &&
radius < 1e15 &&
[1 / 4, 3 / 4].every(function (point) {
return (
Math.abs(
getDistance(getCubicBezierPoint(curve, point), center) - radius
) <= tolerance
);
})
)
return { center: center, radius: radius };
}

@@ -929,7 +917,15 @@

function isArc(curve, circle) {
var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100);
var tolerance = Math.min(
arcThreshold * error,
(arcTolerance * circle.radius) / 100
);
return [0, 1/4, 1/2, 3/4, 1].every(function(point) {
return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance;
});
return [0, 1 / 4, 1 / 2, 3 / 4, 1].every(function (point) {
return (
Math.abs(
getDistance(getCubicBezierPoint(curve, point), circle.center) -
circle.radius
) <= tolerance
);
});
}

@@ -946,6 +942,6 @@

function isArcPrev(curve, circle) {
return isArc(curve, {
center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
radius: circle.radius
});
return isArc(curve, {
center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
radius: circle.radius,
});
}

@@ -962,11 +958,10 @@

function findArcAngle(curve, relCircle) {
var x1 = -relCircle.center[0],
y1 = -relCircle.center[1],
x2 = curve[4] - relCircle.center[0],
y2 = curve[5] - relCircle.center[1];
var x1 = -relCircle.center[0],
y1 = -relCircle.center[1],
x2 = curve[4] - relCircle.center[0],
y2 = curve[5] - relCircle.center[1];
return Math.acos(
(x1 * x2 + y1 * y2) /
Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
);
return Math.acos(
(x1 * x2 + y1 * y2) / Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
);
}

@@ -983,9 +978,9 @@

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 pathData.reduce(function (pathString, item) {
var strData = '';
if (item.data) {
strData = cleanupOutData(roundData(item.data.slice()), params);
}
return pathString + item.instruction + strData;
}, '');
}
'use strict';
const { stringifyPathData } = require('../lib/path.js');
exports.type = 'perItem';

@@ -10,7 +12,8 @@

exports.params = {
convertArcs: false
convertArcs: false,
floatPrecision: null,
};
var none = { value: 0 },
regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
const none = { value: 0 };
const regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;

@@ -30,122 +33,125 @@ /**

*/
exports.fn = function(item, params) {
var convertArcs = params && params.convertArcs;
exports.fn = function (item, params) {
const precision = params ? params.floatPrecision : null;
const convertArcs = params && params.convertArcs;
if (
item.isElem('rect') &&
item.hasAttr('width') &&
item.hasAttr('height') &&
!item.hasAttr('rx') &&
!item.hasAttr('ry')
) {
if (
item.isElem('rect') &&
item.hasAttr('width') &&
item.hasAttr('height') &&
!item.hasAttr('rx') &&
!item.hasAttr('ry')
) {
var x = +(item.attr('x') || none).value,
y = +(item.attr('y') || none).value,
width = +item.attr('width').value,
height = +item.attr('height').value;
// Values like '100%' compute to NaN, thus running after
// cleanupNumericValues when 'px' units has already been removed.
// TODO: Calculate sizes from % and non-px units if possible.
if (isNaN(x - y + width - height)) return;
const pathData = [
{ command: 'M', args: [x, y] },
{ command: 'H', args: [x + width] },
{ command: 'V', args: [y + height] },
{ command: 'H', args: [x] },
{ command: 'z', args: [] },
];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['x', 'y', 'width', 'height']);
}
var x = +(item.attr('x') || none).value,
y = +(item.attr('y') || none).value,
width = +item.attr('width').value,
height = +item.attr('height').value;
if (item.isElem('line')) {
var x1 = +(item.attr('x1') || none).value,
y1 = +(item.attr('y1') || none).value,
x2 = +(item.attr('x2') || none).value,
y2 = +(item.attr('y2') || none).value;
if (isNaN(x1 - y1 + x2 - y2)) return;
const pathData = [
{ command: 'M', args: [x1, y1] },
{ command: 'L', args: [x2, y2] },
];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['x1', 'y1', 'x2', 'y2']);
}
// Values like '100%' compute to NaN, thus running after
// cleanupNumericValues when 'px' units has already been removed.
// TODO: Calculate sizes from % and non-px units if possible.
if (isNaN(x - y + width - height)) return;
if (
(item.isElem('polyline') || item.isElem('polygon')) &&
item.hasAttr('points')
) {
var coords = (item.attr('points').value.match(regNumber) || []).map(Number);
if (coords.length < 4) return false;
const pathData = [];
for (let i = 0; i < coords.length; i += 2) {
pathData.push({
command: i === 0 ? 'M' : 'L',
args: coords.slice(i, i + 2),
});
}
if (item.isElem('polygon')) {
pathData.push({ command: 'z', args: [] });
}
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr('points');
}
var pathData =
'M' + x + ' ' + y +
'H' + (x + width) +
'V' + (y + height) +
'H' + x +
'z';
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;
}
const pathData = [
{ command: 'M', args: [cx, cy - r] },
{ command: 'A', args: [r, r, 0, 1, 0, cx, cy + r] },
{ command: 'A', args: [r, r, 0, 1, 0, cx, cy - r] },
{ command: 'z', args: [] },
];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
}
item.addAttr({
name: 'd',
value: pathData,
prefix: '',
local: 'd'
});
item.renameElem('path')
.removeAttr(['x', 'y', 'width', 'height']);
} else if (item.isElem('line')) {
var x1 = +(item.attr('x1') || none).value,
y1 = +(item.attr('y1') || none).value,
x2 = +(item.attr('x2') || none).value,
y2 = +(item.attr('y2') || none).value;
if (isNaN(x1 - y1 + x2 - y2)) return;
item.addAttr({
name: 'd',
value: 'M' + x1 + ' ' + y1 + 'L' + x2 + ' ' + y2,
prefix: '',
local: 'd'
});
item.renameElem('path')
.removeAttr(['x1', 'y1', 'x2', 'y2']);
} else if ((
item.isElem('polyline') ||
item.isElem('polygon')
) &&
item.hasAttr('points')
) {
var coords = (item.attr('points').value.match(regNumber) || []).map(Number);
if (coords.length < 4) return false;
item.addAttr({
name: 'd',
value: 'M' + coords.slice(0,2).join(' ') +
'L' + coords.slice(2).join(' ') +
(item.isElem('polygon') ? 'z' : ''),
prefix: '',
local: 'd'
});
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']);
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;
}
const pathData = [
{ command: 'M', args: [ecx, ecy - ry] },
{ command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy + ry] },
{ command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy - ry] },
{ command: 'z', args: [] },
];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
}
};

@@ -8,6 +8,6 @@ 'use strict';

exports.params = {
onlyMatchedOnce: true,
removeMatchedSelectors: true,
useMqs: ['', 'screen'],
usePseudos: ['']
onlyMatchedOnce: true,
removeMatchedSelectors: true,
useMqs: ['', 'screen'],
usePseudos: [''],
};

@@ -17,5 +17,4 @@

var csstree = require('css-tree'),
cssTools = require('../lib/css-tools');
cssTools = require('../lib/css-tools');

@@ -46,200 +45,227 @@ /**

*/
exports.fn = function(document, opts) {
exports.fn = function (document, opts) {
// collect <style/>s
var styleEls = document.querySelectorAll('style');
// collect <style/>s
var styleEls = document.querySelectorAll('style');
//no <styles/>s, nothing to do
if (styleEls === null) {
return document;
}
//no <styles/>s, nothing to do
if (styleEls === null) {
return document;
var styles = [],
selectors = [];
for (var styleEl of styleEls) {
// values other than the empty string or text/css are not used
if (
styleEl.hasAttr('type') &&
styleEl.attr('type').value !== '' &&
styleEl.attr('type').value !== 'text/css'
) {
continue;
}
// skip empty <style/>s or <foreignObject> content.
if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) {
continue;
}
var styles = [],
selectors = [];
var cssStr = cssTools.getCssStr(styleEl);
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));
// 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,
});
// filter for mediaqueries to be used or without any mediaquery
var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
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);
// 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);
// remove PseudoClass from its SimpleSelector for proper matching
cssTools.cleanPseudos(selectorsPseudo);
// stable sort selectors
var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
// 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;
var selector,
selectedEl;
try {
selectedEls = document.querySelectorAll(selectorStr);
} catch (selectError) {
// console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
continue;
}
// match selectors
for (selector of sortedSelectors) {
var selectorStr = csstree.generate(selector.item.data),
selectedEls = null;
if (selectedEls === null) {
// nothing selected
continue;
}
try {
selectedEls = document.querySelectorAll(selectorStr);
} catch (selectError) {
// console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
continue;
}
selector.selectedEls = selectedEls;
}
if (selectedEls === null) {
// nothing selected
continue;
}
// apply <style/> styles to matched elements
for (selector of sortedSelectors) {
if (!selector.selectedEls) {
continue;
}
selector.selectedEls = selectedEls;
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;
}
// apply <style/> styles to matched elements
for (selector of sortedSelectors) {
if(!selector.selectedEls) {
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.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
// skip selectors that match more than once if option onlyMatchedOnce is enabled
continue;
}
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);
}
}
// apply <style/> to matched elements
for (selectedEl of selector.selectedEls) {
if (selector.rule === null) {
continue;
}
if (!opts.removeMatchedSelectors) {
return document; // no further processing required
}
// merge declarations
csstree.walk(selector.rule, {visit: 'Declaration', enter: function(styleCsstreeDeclaration) {
// clean up matched class + ID attribute values
for (selector of sortedSelectors) {
if (!selector.selectedEls) {
continue;
}
// 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.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');
}
if (!opts.removeMatchedSelectors) {
return document; // no further processing required
// ID
if (firstSubSelector.type === 'IdSelector') {
selectedEl.removeAttr('id', firstSubSelector.name);
}
}
}
// clean up matched class + ID attribute values
for (selector of sortedSelectors) {
if(!selector.selectedEls) {
continue;
// 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;
}
if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
// skip selectors that match more than once if option onlyMatchedOnce is enabled
continue;
// clean up <style/> rulesets without any css selectors left
if (node.type === 'Rule' && node.prelude.children.isEmpty()) {
list.remove(item);
}
},
});
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');
}
if (style.cssAst.children.isEmpty()) {
// clean up now emtpy <style/>s
var styleParentEl = style.styleEl.parentNode;
styleParentEl.spliceContent(
styleParentEl.content.indexOf(style.styleEl),
1
);
// ID
if(firstSubSelector.type === 'IdSelector') {
selectedEl.removeAttr('id', firstSubSelector.name);
}
}
}
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
);
}
// 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));
continue;
}
// update existing, left over <style>s
cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
}
return document;
return document;
};
'use strict';
const { computeStyle } = require('../lib/style.js');
const { path2js, js2path, intersects } = require('./_path.js');
exports.type = 'perItem';

@@ -10,13 +13,9 @@

exports.params = {
collapseRepeated: true,
force: false,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
collapseRepeated: true,
force: false,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
};
var path2js = require('./_path.js').path2js,
js2path = require('./_path.js').js2path,
intersects = require('./_path.js').intersects;
/**

@@ -30,46 +29,54 @@ * Merge multiple Paths into one.

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (!item.isElem() || item.isEmpty()) return;
if (!item.isElem() || item.isEmpty()) return;
var prevContentItem = null,
prevContentItemKeys = null;
var prevContentItem = null,
prevContentItemKeys = null;
item.content = item.content.filter(function (contentItem) {
if (
prevContentItem &&
prevContentItem.isElem('path') &&
prevContentItem.isEmpty() &&
prevContentItem.hasAttr('d') &&
contentItem.isElem('path') &&
contentItem.isEmpty() &&
contentItem.hasAttr('d')
) {
const computedStyle = computeStyle(contentItem);
// keep path to not break markers
if (
computedStyle['marker-start'] ||
computedStyle['marker-mid'] ||
computedStyle['marker-end']
) {
return true;
}
if (!prevContentItemKeys) {
prevContentItemKeys = Object.keys(prevContentItem.attrs);
}
item.content = item.content.filter(function(contentItem) {
var contentItemAttrs = Object.keys(contentItem.attrs),
equalData =
prevContentItemKeys.length == contentItemAttrs.length &&
contentItemAttrs.every(function (key) {
return (
key == 'd' ||
(prevContentItem.hasAttr(key) &&
prevContentItem.attr(key).value == contentItem.attr(key).value)
);
}),
prevPathJS = path2js(prevContentItem),
curPathJS = path2js(contentItem);
if (prevContentItem &&
prevContentItem.isElem('path') &&
prevContentItem.isEmpty() &&
prevContentItem.hasAttr('d') &&
contentItem.isElem('path') &&
contentItem.isEmpty() &&
contentItem.hasAttr('d')
) {
if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
return false;
}
}
if (!prevContentItemKeys) {
prevContentItemKeys = Object.keys(prevContentItem.attrs);
}
var contentItemAttrs = Object.keys(contentItem.attrs),
equalData = prevContentItemKeys.length == contentItemAttrs.length &&
contentItemAttrs.every(function(key) {
return key == 'd' ||
prevContentItem.hasAttr(key) &&
prevContentItem.attr(key).value == contentItem.attr(key).value;
}),
prevPathJS = path2js(prevContentItem),
curPathJS = path2js(contentItem);
if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
return false;
}
}
prevContentItem = contentItem;
prevContentItemKeys = null;
return true;
});
prevContentItem = contentItem;
prevContentItemKeys = null;
return true;
});
};
'use strict';
const { attrsGroups } = require('./_collections.js');
exports.type = 'perItem';

@@ -17,14 +19,14 @@

*/
exports.fn = function(item) {
if (item.elem) {
item.eachAttr(function(attr) {
if (attr.value === '') {
item.removeAttr(attr.name);
}
});
}
exports.fn = function (item) {
if (item.elem) {
item.eachAttr(function (attr) {
if (
attr.value === '' &&
// empty conditional processing attributes prevents elements from rendering
attrsGroups.conditionalProcessing.includes(attr.name) === false
) {
item.removeAttr(attr.name);
}
});
}
};

@@ -27,3 +27,3 @@ 'use strict';

*/
exports.fn = function(item) {
exports.fn = function (item) {
return (

@@ -33,3 +33,4 @@ item.isElem(container) === false ||

item.isElem('svg') ||
(item.isElem('pattern') && item.hasAttrLocal('href')) ||
// empty patterns may contain reusable configuration
(item.isElem('pattern') && Object.keys(item.attrs).length !== 0) ||
// The 'g' may not have content, but the filter may cause a rectangle

@@ -36,0 +37,0 @@ // to be created and filled with pattern.

'use strict';
const { computeStyle } = require('../lib/style.js');
const { parsePathData } = require('../lib/path.js');
exports.type = 'perItem';

@@ -7,24 +10,23 @@

exports.description = 'removes hidden elements (zero sized, with absent attributes)';
exports.description =
'removes hidden elements (zero sized, with absent attributes)';
exports.params = {
isHidden: true,
displayNone: true,
opacity0: true,
circleR0: true,
ellipseRX0: true,
ellipseRY0: true,
rectWidth0: true,
rectHeight0: true,
patternWidth0: true,
patternHeight0: true,
imageWidth0: true,
imageHeight0: true,
pathEmptyD: true,
polylineEmptyPoints: true,
polygonEmptyPoints: true
isHidden: true,
displayNone: true,
opacity0: true,
circleR0: true,
ellipseRX0: true,
ellipseRY0: true,
rectWidth0: true,
rectHeight0: true,
patternWidth0: true,
patternHeight0: true,
imageWidth0: true,
imageHeight0: true,
pathEmptyD: true,
polylineEmptyPoints: true,
polygonEmptyPoints: true,
};
var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\d)\s*,?\s*){2}\D*\d/i;
/**

@@ -50,182 +52,229 @@ * Remove hidden elements with disabled rendering:

exports.fn = function (item, params) {
if (item.elem) {
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
const computedStyle = computeStyle(item);
if (
params.isHidden &&
computedStyle.visibility &&
computedStyle.visibility.type === 'static' &&
computedStyle.visibility.value === 'hidden' &&
// keep if any descendant enables visibility
item.querySelector('[visibility=visible]') == null
) {
return false;
}
if (item.elem) {
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
if (
params.isHidden &&
item.hasAttr('visibility', 'hidden') &&
// keep if any descendant enables visibility
item.querySelector('[visibility=visible]') == null
) return false;
// display="none"
//
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
// "A value of display: none indicates that the given element
// and its children shall not be rendered directly"
if (
params.displayNone &&
computedStyle.display &&
computedStyle.display.type === 'static' &&
computedStyle.display.value === 'none' &&
// markers with display: none still rendered
item.isElem('marker') === false
) {
return false;
}
// display="none"
//
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
// "A value of display: none indicates that the given element
// and its children shall not be rendered directly"
if (
params.displayNone &&
item.hasAttr('display', 'none')
) return false;
// opacity="0"
//
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
if (
params.opacity0 &&
computedStyle.opacity &&
computedStyle.opacity.type === 'static' &&
computedStyle.opacity.value === '0' &&
// transparent element inside clipPath still affect clipped elements
item.closestElem('clipPath') == null
) {
return false;
}
// opacity="0"
//
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
if (
params.opacity0 &&
item.hasAttr('opacity', '0') &&
// transparent element inside clipPath still affect clipped elements
item.closestElem('clipPath') == null
) return false;
// Circles with zero radius
//
// https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
// "A value of zero disables rendering of the element"
//
// <circle r="0">
if (
params.circleR0 &&
item.isElem('circle') &&
item.isEmpty() &&
item.hasAttr('r', '0')
) {
return false;
}
// Circles with zero radius
//
// https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
// "A value of zero disables rendering of the element"
//
// <circle r="0">
if (
params.circleR0 &&
item.isElem('circle') &&
item.isEmpty() &&
item.hasAttr('r', '0')
) return false;
// Ellipse with zero x-axis radius
//
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRXAttribute
// "A value of zero disables rendering of the element"
//
// <ellipse rx="0">
if (
params.ellipseRX0 &&
item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('rx', '0')
) {
return false;
}
// Ellipse with zero x-axis radius
//
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRXAttribute
// "A value of zero disables rendering of the element"
//
// <ellipse rx="0">
if (
params.ellipseRX0 &&
item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('rx', '0')
) return false;
// Ellipse with zero y-axis radius
//
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRYAttribute
// "A value of zero disables rendering of the element"
//
// <ellipse ry="0">
if (
params.ellipseRY0 &&
item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('ry', '0')
) {
return false;
}
// Ellipse with zero y-axis radius
//
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRYAttribute
// "A value of zero disables rendering of the element"
//
// <ellipse ry="0">
if (
params.ellipseRY0 &&
item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('ry', '0')
) return false;
// Rectangle with zero width
//
// https://www.w3.org/TR/SVG11/shapes.html#RectElementWidthAttribute
// "A value of zero disables rendering of the element"
//
// <rect width="0">
if (
params.rectWidth0 &&
item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('width', '0')
) {
return false;
}
// Rectangle with zero width
//
// https://www.w3.org/TR/SVG11/shapes.html#RectElementWidthAttribute
// "A value of zero disables rendering of the element"
//
// <rect width="0">
if (
params.rectWidth0 &&
item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('width', '0')
) return false;
// Rectangle with zero height
//
// https://www.w3.org/TR/SVG11/shapes.html#RectElementHeightAttribute
// "A value of zero disables rendering of the element"
//
// <rect height="0">
if (
params.rectHeight0 &&
params.rectWidth0 &&
item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('height', '0')
) {
return false;
}
// Rectangle with zero height
//
// https://www.w3.org/TR/SVG11/shapes.html#RectElementHeightAttribute
// "A value of zero disables rendering of the element"
//
// <rect height="0">
if (
params.rectHeight0 &&
params.rectWidth0 &&
item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('height', '0')
) return false;
// Pattern with zero width
//
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementWidthAttribute
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
//
// <pattern width="0">
if (
params.patternWidth0 &&
item.isElem('pattern') &&
item.hasAttr('width', '0')
) {
return false;
}
// Pattern with zero width
//
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementWidthAttribute
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
//
// <pattern width="0">
if (
params.patternWidth0 &&
item.isElem('pattern') &&
item.hasAttr('width', '0')
) return false;
// Pattern with zero height
//
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementHeightAttribute
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
//
// <pattern height="0">
if (
params.patternHeight0 &&
item.isElem('pattern') &&
item.hasAttr('height', '0')
) {
return false;
}
// Pattern with zero height
//
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementHeightAttribute
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
//
// <pattern height="0">
if (
params.patternHeight0 &&
item.isElem('pattern') &&
item.hasAttr('height', '0')
) return false;
// Image with zero width
//
// https://www.w3.org/TR/SVG11/struct.html#ImageElementWidthAttribute
// "A value of zero disables rendering of the element"
//
// <image width="0">
if (
params.imageWidth0 &&
item.isElem('image') &&
item.hasAttr('width', '0')
) {
return false;
}
// Image with zero width
//
// https://www.w3.org/TR/SVG11/struct.html#ImageElementWidthAttribute
// "A value of zero disables rendering of the element"
//
// <image width="0">
if (
params.imageWidth0 &&
item.isElem('image') &&
item.hasAttr('width', '0')
) return false;
// Image with zero height
//
// https://www.w3.org/TR/SVG11/struct.html#ImageElementHeightAttribute
// "A value of zero disables rendering of the element"
//
// <image height="0">
if (
params.imageHeight0 &&
item.isElem('image') &&
item.hasAttr('height', '0')
) {
return false;
}
// Image with zero height
//
// https://www.w3.org/TR/SVG11/struct.html#ImageElementHeightAttribute
// "A value of zero disables rendering of the element"
//
// <image height="0">
if (
params.imageHeight0 &&
item.isElem('image') &&
item.hasAttr('height', '0')
) return false;
// Path with empty data
//
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
//
// <path d=""/>
if (params.pathEmptyD && item.isElem('path')) {
if (item.hasAttr('d') === false) {
return false;
}
const pathData = parsePathData(item.attr('d').value);
if (pathData.length === 0) {
return false;
}
// keep single point paths for markers
if (
pathData.length === 1 &&
computedStyle['marker-start'] == null &&
computedStyle['marker-end'] == null
) {
return false;
}
return true;
}
// Path with empty data
//
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
//
// <path d=""/>
if (
params.pathEmptyD &&
item.isElem('path') &&
(!item.hasAttr('d') || !regValidPath.test(item.attr('d').value))
) return false;
// Polyline with empty points
//
// https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
//
// <polyline points="">
if (
params.polylineEmptyPoints &&
item.isElem('polyline') &&
!item.hasAttr('points')
) {
return false;
}
// Polyline with empty points
//
// https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
//
// <polyline points="">
if (
params.polylineEmptyPoints &&
item.isElem('polyline') &&
!item.hasAttr('points')
) return false;
// Polygon with empty points
//
// https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
//
// <polygon points="">
if (
params.polygonEmptyPoints &&
item.isElem('polygon') &&
!item.hasAttr('points')
) return false;
// Polygon with empty points
//
// https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
//
// <polygon points="">
if (
params.polygonEmptyPoints &&
item.isElem('polygon') &&
!item.hasAttr('points')
) {
return false;
}
}
};

@@ -26,24 +26,25 @@ 'use strict';

*/
exports.fn = function(item) {
exports.fn = function (item) {
if (
item.isElem(viewBoxElems) &&
item.hasAttr('viewBox') &&
item.hasAttr('width') &&
item.hasAttr('height')
) {
// TODO remove width/height for such case instead
if (item.isElem('svg') && item.closestElem('svg')) {
return;
}
var nums = item.attr('viewBox').value.split(/[ ,]+/g);
if (
item.isElem(viewBoxElems) &&
item.hasAttr('viewBox') &&
item.hasAttr('width') &&
item.hasAttr('height')
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]
) {
var nums = item.attr('viewBox').value.split(/[ ,]+/g);
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');
}
item.removeAttr('viewBox');
}
}
};

@@ -29,3 +29,3 @@ <div align="center">

```sh
svgo one.svg two.svg -p one.min.svg two.min.svg
svgo one.svg two.svg -o one.min.svg two.min.svg
```

@@ -32,0 +32,0 @@

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc