Comparing version 2.2.2 to 2.3.0
@@ -189,6 +189,12 @@ 'use strict'; | ||
* @param {Object} elem style element | ||
* @return {string|Array} CSS string or empty array if no styles are set | ||
* @return {string} CSS string or empty array if no styles are set | ||
*/ | ||
function getCssStr(elem) { | ||
return elem.content[0].text || elem.content[0].cdata || []; | ||
if ( | ||
elem.children.length > 0 && | ||
(elem.children[0].type === 'text' || elem.children[0].type === 'cdata') | ||
) { | ||
return elem.children[0].value; | ||
} | ||
return ''; | ||
} | ||
@@ -201,14 +207,19 @@ | ||
* @param {string} css string to be set | ||
* @return {Object} reference to field with CSS | ||
* @return {string} reference to field with CSS | ||
*/ | ||
function setCssStr(elem, css) { | ||
// in case of cdata field | ||
if (elem.content[0].cdata) { | ||
elem.content[0].cdata = css; | ||
return elem.content[0].cdata; | ||
if (elem.children.length === 0) { | ||
elem.children.push({ | ||
type: 'text', | ||
value: '', | ||
}); | ||
} | ||
// in case of text field + if nothing was set yet | ||
elem.content[0].text = css; | ||
return elem.content[0].text; | ||
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') { | ||
return css; | ||
} | ||
elem.children[0].value = css; | ||
return css; | ||
} | ||
@@ -215,0 +226,0 @@ |
@@ -244,16 +244,8 @@ 'use strict'; | ||
const stringifyNumber = ({ number, precision }) => { | ||
let result; | ||
if (precision == null) { | ||
result = number.toString(); | ||
} else { | ||
result = number.toFixed(precision); | ||
if (result.includes('.')) { | ||
result = result.replace(/\.?0+$/, ''); | ||
} | ||
if (precision != null) { | ||
const ratio = 10 ** precision; | ||
number = Math.round(number * ratio) / ratio; | ||
} | ||
// remove zero whole from decimal number | ||
if (result !== '0') { | ||
result = result.replace(/^0/, '').replace(/^-0/, '-'); | ||
} | ||
return result; | ||
return number.toString().replace(/^0\./, '.').replace(/^-0\./, '-.'); | ||
}; | ||
@@ -260,0 +252,0 @@ |
@@ -145,22 +145,20 @@ 'use strict'; | ||
it('should configure precision', () => { | ||
const pathData = [ | ||
{ command: 'M', args: [0, -1.9876] }, | ||
{ command: 'L', args: [0.3, 3.14159265] }, | ||
{ command: 'L', args: [-0.3, -3.14159265] }, | ||
{ command: 'L', args: [100, 200] }, | ||
]; | ||
expect( | ||
stringifyPathData({ | ||
pathData: [ | ||
{ command: 'M', args: [0, -1.9876] }, | ||
{ command: 'L', args: [0.3, 3.14159265] }, | ||
{ command: 'L', args: [100, 200] }, | ||
], | ||
pathData, | ||
precision: 3, | ||
}) | ||
).to.equal('M0-1.988.3 3.142 100 200'); | ||
).to.equal('M0-1.988.3 3.142-.3-3.142 100 200'); | ||
expect( | ||
stringifyPathData({ | ||
pathData: [ | ||
{ command: 'M', args: [0, -1.9876] }, | ||
{ command: 'L', args: [0.3, 3.14159265] }, | ||
{ command: 'L', args: [100, 200] }, | ||
], | ||
pathData, | ||
precision: 0, | ||
}) | ||
).to.equal('M0-2 0 3 100 200'); | ||
).to.equal('M0-2 0 3 0-3 100 200'); | ||
}); | ||
@@ -167,0 +165,0 @@ it('allows to avoid spaces after arc flags', () => { |
@@ -84,8 +84,6 @@ 'use strict'; | ||
// collect attributes | ||
if (node.attrs) { | ||
for (const { name, value } of Object.values(node.attrs)) { | ||
if (attrsGroups.presentation.includes(name)) { | ||
computedStyle[name] = { type: 'static', inherited: false, value }; | ||
importantStyles.set(name, false); | ||
} | ||
for (const [name, value] of Object.entries(node.attributes)) { | ||
if (attrsGroups.presentation.includes(name)) { | ||
computedStyle[name] = { type: 'static', inherited: false, value }; | ||
importantStyles.set(name, false); | ||
} | ||
@@ -150,12 +148,14 @@ } | ||
const dynamic = | ||
styleNode.hasAttr('media') && styleNode.attr('media').value !== 'all'; | ||
styleNode.attributes.media != null && | ||
styleNode.attributes.media !== 'all'; | ||
if ( | ||
styleNode.hasAttr('type') === false || | ||
styleNode.attr('type').value === '' || | ||
styleNode.attr('type').value === 'text/css' | ||
styleNode.attributes.type == null || | ||
styleNode.attributes.type === '' || | ||
styleNode.attributes.type === 'text/css' | ||
) { | ||
const children = styleNode.content || []; | ||
const children = styleNode.children; | ||
for (const child of children) { | ||
const css = child.text || child.cdata; | ||
stylesheet.push(...parseStylesheet(css, dynamic)); | ||
if (child.type === 'text' || child.type === 'cdata') { | ||
stylesheet.push(...parseStylesheet(child.value, dynamic)); | ||
} | ||
} | ||
@@ -172,3 +172,3 @@ } | ||
let parent = node; | ||
while (parent.parentNode && parent.parentNode.elem !== '#document') { | ||
while (parent.parentNode && parent.parentNode.type !== 'root') { | ||
const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet); | ||
@@ -175,0 +175,0 @@ for (const [name, computed] of Object.entries(inheritedStyles)) { |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const { computeStyle } = require('./style.js'); | ||
const { querySelector } = require('./xast.js'); | ||
const svg2js = require('./svgo/svg2js.js'); | ||
@@ -34,20 +35,20 @@ | ||
`); | ||
expect(computeStyle(root.querySelector('#class'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#class'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'red' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#two-classes'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#two-classes'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'green' }, | ||
stroke: { type: 'static', inherited: false, value: 'black' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#attribute'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#attribute'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'purple' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#inline-style'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#inline-style'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'grey' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#inheritance'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#inheritance'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: true, value: 'yellow' }, | ||
}); | ||
expect( | ||
computeStyle(root.querySelector('#nested-inheritance')) | ||
computeStyle(querySelector(root, '#nested-inheritance')) | ||
).to.deep.equal({ | ||
@@ -74,14 +75,14 @@ fill: { type: 'static', inherited: true, value: 'blue' }, | ||
`); | ||
expect(computeStyle(root.querySelector('#complex-selector'))).to.deep.equal( | ||
{ | ||
fill: { type: 'static', inherited: false, value: 'red' }, | ||
} | ||
); | ||
expect( | ||
computeStyle(root.querySelector('#attribute-over-inheritance')) | ||
computeStyle(querySelector(root, '#complex-selector')) | ||
).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'red' }, | ||
}); | ||
expect( | ||
computeStyle(querySelector(root, '#attribute-over-inheritance')) | ||
).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'orange' }, | ||
}); | ||
expect( | ||
computeStyle(root.querySelector('#style-rule-over-attribute')) | ||
computeStyle(querySelector(root, '#style-rule-over-attribute')) | ||
).to.deep.equal({ | ||
@@ -91,3 +92,3 @@ fill: { type: 'static', inherited: false, value: 'blue' }, | ||
expect( | ||
computeStyle(root.querySelector('#inline-style-over-style-rule')) | ||
computeStyle(querySelector(root, '#inline-style-over-style-rule')) | ||
).to.deep.equal({ | ||
@@ -110,9 +111,4 @@ fill: { type: 'static', inherited: false, value: 'purple' }, | ||
`); | ||
expect(computeStyle(root.querySelector('#complex-selector'))).to.deep.equal( | ||
{ | ||
fill: { type: 'static', inherited: false, value: 'green' }, | ||
} | ||
); | ||
expect( | ||
computeStyle(root.querySelector('#style-rule-over-inline-style')) | ||
computeStyle(querySelector(root, '#complex-selector')) | ||
).to.deep.equal({ | ||
@@ -122,4 +118,9 @@ fill: { type: 'static', inherited: false, value: 'green' }, | ||
expect( | ||
computeStyle(root.querySelector('#inline-style-over-style-rule')) | ||
computeStyle(querySelector(root, '#style-rule-over-inline-style')) | ||
).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'green' }, | ||
}); | ||
expect( | ||
computeStyle(querySelector(root, '#inline-style-over-style-rule')) | ||
).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'purple' }, | ||
@@ -149,17 +150,17 @@ }); | ||
`); | ||
expect(computeStyle(root.querySelector('#media-query'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({ | ||
fill: { type: 'dynamic', inherited: false }, | ||
}); | ||
expect(computeStyle(root.querySelector('#hover'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#hover'))).to.deep.equal({ | ||
fill: { type: 'dynamic', inherited: false }, | ||
}); | ||
expect(computeStyle(root.querySelector('#inherited'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#inherited'))).to.deep.equal({ | ||
fill: { type: 'dynamic', inherited: true }, | ||
}); | ||
expect( | ||
computeStyle(root.querySelector('#inherited-overriden')) | ||
computeStyle(querySelector(root, '#inherited-overriden')) | ||
).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'blue' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#static'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'black' }, | ||
@@ -186,9 +187,9 @@ }); | ||
`); | ||
expect(computeStyle(root.querySelector('#media-query'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({ | ||
fill: { type: 'dynamic', inherited: false }, | ||
}); | ||
expect(computeStyle(root.querySelector('#kinda-static'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#kinda-static'))).to.deep.equal({ | ||
fill: { type: 'dynamic', inherited: false }, | ||
}); | ||
expect(computeStyle(root.querySelector('#static'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'blue' }, | ||
@@ -215,9 +216,11 @@ }); | ||
`); | ||
expect(computeStyle(root.querySelector('#valid-type'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#valid-type'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'red' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#empty-type'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#empty-type'))).to.deep.equal({ | ||
fill: { type: 'static', inherited: false, value: 'green' }, | ||
}); | ||
expect(computeStyle(root.querySelector('#invalid-type'))).to.deep.equal({}); | ||
expect(computeStyle(querySelector(root, '#invalid-type'))).to.deep.equal( | ||
{} | ||
); | ||
}); | ||
@@ -247,3 +250,3 @@ | ||
`); | ||
expect(computeStyle(root.querySelector('#element'))).to.deep.equal({ | ||
expect(computeStyle(querySelector(root, '#element'))).to.deep.equal({ | ||
animation: { | ||
@@ -250,0 +253,0 @@ type: 'static', |
'use strict'; | ||
/** | ||
* SVGO is a Nodejs-based tool for optimizing SVG vector graphics files. | ||
* | ||
* @see https://github.com/svg/svgo | ||
* | ||
* @author Kir Belevich <kir@soulshine.in> (https://github.com/deepsweet) | ||
* @copyright © 2012 Kir Belevich | ||
* @license MIT https://raw.githubusercontent.com/svg/svgo/master/LICENSE | ||
*/ | ||
const { | ||
@@ -14,0 +4,0 @@ defaultPlugins, |
@@ -59,5 +59,9 @@ 'use strict'; | ||
'-r, --recursive', | ||
"Use with '-f'. Optimizes *.svg files in folders recursively." | ||
"Use with '--folder'. Optimizes *.svg files in folders recursively." | ||
) | ||
.option( | ||
'--exclude <PATTERN...>', | ||
"Use with '--folder'. Exclude files matching regular expression pattern." | ||
) | ||
.option( | ||
'-q, --quiet', | ||
@@ -76,3 +80,3 @@ 'Only output error messages, not regular status messages' | ||
if (opts.precision != null) { | ||
const number = Number.parseInt(opts.precision, 0); | ||
const number = Number.parseInt(opts.precision, 10); | ||
if (Number.isNaN(number)) { | ||
@@ -102,3 +106,3 @@ console.error( | ||
if (opts.indent != null) { | ||
const number = Number.parseInt(opts.indent, 0); | ||
const number = Number.parseInt(opts.indent, 10); | ||
if (Number.isNaN(number)) { | ||
@@ -162,2 +166,7 @@ console.error( | ||
// --exclude | ||
config.exclude = opts.exclude | ||
? opts.exclude.map((pattern) => RegExp(pattern)) | ||
: []; | ||
// --precision | ||
@@ -298,3 +307,7 @@ if (opts.precision != null) { | ||
const filesInThisFolder = files | ||
.filter((name) => regSVGFile.test(name)) | ||
.filter( | ||
(name) => | ||
regSVGFile.test(name) && | ||
!config.exclude.some((regExclude) => regExclude.test(name)) | ||
) | ||
.map((name) => ({ | ||
@@ -301,0 +314,0 @@ inputPath: PATH.resolve(dir, name), |
@@ -13,2 +13,3 @@ 'use strict'; | ||
'cleanupAttrs', | ||
'mergeStyles', | ||
'inlineStyles', | ||
@@ -63,3 +64,3 @@ 'minifyStyles', | ||
name, | ||
...pluginsMap[name], | ||
active: pluginsMap[name].active, | ||
})); | ||
@@ -66,0 +67,0 @@ for (const plugin of plugins) { |
@@ -6,56 +6,13 @@ 'use strict'; | ||
this.classNames = new Set(); | ||
this.classAttr = null; | ||
//this.classValue = null; | ||
const value = node.attributes.class; | ||
if (value != null) { | ||
this.addClassValueHandler(); | ||
this.setClassValue(value); | ||
} | ||
}; | ||
/** | ||
* Performs a deep clone of this object. | ||
* | ||
* @param parentNode the parentNode to assign to the cloned result | ||
*/ | ||
CSSClassList.prototype.clone = function (parentNode) { | ||
var node = this; | ||
var nodeData = {}; | ||
Object.keys(node).forEach(function (key) { | ||
if (key !== 'parentNode') { | ||
nodeData[key] = node[key]; | ||
} | ||
}); | ||
// Deep-clone node data. | ||
nodeData = JSON.parse(JSON.stringify(nodeData)); | ||
var clone = new CSSClassList(parentNode); | ||
Object.assign(clone, nodeData); | ||
return clone; | ||
}; | ||
CSSClassList.prototype.hasClass = function () { | ||
this.classAttr = { | ||
// empty class attr | ||
name: 'class', | ||
value: null, | ||
}; | ||
this.addClassHandler(); | ||
}; | ||
// attr.class | ||
CSSClassList.prototype.addClassHandler = function () { | ||
Object.defineProperty(this.parentNode.attrs, 'class', { | ||
get: this.getClassAttr.bind(this), | ||
set: this.setClassAttr.bind(this), | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
this.addClassValueHandler(); | ||
}; | ||
// attr.class.value | ||
CSSClassList.prototype.addClassValueHandler = function () { | ||
Object.defineProperty(this.classAttr, 'value', { | ||
Object.defineProperty(this.parentNode.attributes, 'class', { | ||
get: this.getClassValue.bind(this), | ||
@@ -68,13 +25,2 @@ set: this.setClassValue.bind(this), | ||
CSSClassList.prototype.getClassAttr = function () { | ||
return this.classAttr; | ||
}; | ||
CSSClassList.prototype.setClassAttr = function (newClassAttr) { | ||
this.setClassValue(newClassAttr.value); // must before applying value handler! | ||
this.classAttr = newClassAttr; | ||
this.addClassValueHandler(); | ||
}; | ||
CSSClassList.prototype.getClassValue = function () { | ||
@@ -95,3 +41,3 @@ var arrClassNames = Array.from(this.classNames); | ||
CSSClassList.prototype.add = function (/* variadic */) { | ||
this.hasClass(); | ||
this.addClassValueHandler(); | ||
Object.values(arguments).forEach(this._addSingle.bind(this)); | ||
@@ -105,3 +51,3 @@ }; | ||
CSSClassList.prototype.remove = function (/* variadic */) { | ||
this.hasClass(); | ||
this.addClassValueHandler(); | ||
Object.values(arguments).forEach(this._removeSingle.bind(this)); | ||
@@ -108,0 +54,0 @@ }; |
@@ -8,3 +8,3 @@ 'use strict'; | ||
const isTag = (node) => { | ||
return node.isElem(); | ||
return node.type === 'element'; | ||
}; | ||
@@ -23,11 +23,11 @@ | ||
const getAttributeValue = (elem, name) => { | ||
return elem.hasAttr(name) ? elem.attr(name).value : undefined; | ||
return elem.attributes[name]; | ||
}; | ||
const getChildren = (node) => { | ||
return node.content || []; | ||
return node.children || []; | ||
}; | ||
const getName = (elemAst) => { | ||
return elemAst.elem; | ||
return elemAst.name; | ||
}; | ||
@@ -45,7 +45,10 @@ | ||
const getText = (node) => { | ||
return node.content[0].text || node.content[0].cdata || ''; | ||
if (node.children[0].type === 'text' && node.children[0].type === 'cdata') { | ||
return node.children[0].value; | ||
} | ||
return ''; | ||
}; | ||
const hasAttrib = (elem, name) => { | ||
return getAttributeValue(elem, name) !== undefined; | ||
return elem.attributes[name] !== undefined; | ||
}; | ||
@@ -52,0 +55,0 @@ |
@@ -12,58 +12,16 @@ 'use strict'; | ||
this.styleAttr = null; | ||
this.styleValue = null; | ||
this.parseError = false; | ||
const value = node.attributes.style; | ||
if (value != null) { | ||
this.addStyleValueHandler(); | ||
this.setStyleValue(value); | ||
} | ||
}; | ||
/** | ||
* Performs a deep clone of this object. | ||
* | ||
* @param parentNode the parentNode to assign to the cloned result | ||
*/ | ||
CSSStyleDeclaration.prototype.clone = function (parentNode) { | ||
var node = this; | ||
var nodeData = {}; | ||
Object.keys(node).forEach(function (key) { | ||
if (key !== 'parentNode') { | ||
nodeData[key] = node[key]; | ||
} | ||
}); | ||
// Deep-clone node data. | ||
nodeData = JSON.parse(JSON.stringify(nodeData)); | ||
var clone = new CSSStyleDeclaration(parentNode); | ||
Object.assign(clone, nodeData); | ||
return clone; | ||
}; | ||
CSSStyleDeclaration.prototype.hasStyle = function () { | ||
this.addStyleHandler(); | ||
}; | ||
// attr.style | ||
CSSStyleDeclaration.prototype.addStyleHandler = function () { | ||
this.styleAttr = { | ||
// empty style attr | ||
name: 'style', | ||
value: null, | ||
}; | ||
Object.defineProperty(this.parentNode.attrs, 'style', { | ||
get: this.getStyleAttr.bind(this), | ||
set: this.setStyleAttr.bind(this), | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
this.addStyleValueHandler(); | ||
}; | ||
// attr.style.value | ||
CSSStyleDeclaration.prototype.addStyleValueHandler = function () { | ||
Object.defineProperty(this.styleAttr, 'value', { | ||
Object.defineProperty(this.parentNode.attributes, 'style', { | ||
get: this.getStyleValue.bind(this), | ||
@@ -76,14 +34,2 @@ set: this.setStyleValue.bind(this), | ||
CSSStyleDeclaration.prototype.getStyleAttr = function () { | ||
return this.styleAttr; | ||
}; | ||
CSSStyleDeclaration.prototype.setStyleAttr = function (newStyleAttr) { | ||
this.setStyleValue(newStyleAttr.value); // must before applying value handler! | ||
this.styleAttr = newStyleAttr; | ||
this.addStyleValueHandler(); | ||
this.hasSynced = false; // raw css changed | ||
}; | ||
CSSStyleDeclaration.prototype.getStyleValue = function () { | ||
@@ -247,3 +193,3 @@ return this.getCssText(); | ||
this.hasStyle(); | ||
this.addStyleValueHandler(); | ||
@@ -275,3 +221,3 @@ var properties = this.getProperties(); | ||
this.hasStyle(); | ||
this.addStyleValueHandler(); | ||
@@ -278,0 +224,0 @@ var properties = this.getProperties(); |
@@ -96,20 +96,23 @@ 'use strict'; | ||
if (data.content) { | ||
this.indentLevel++; | ||
this.indentLevel++; | ||
data.content.forEach(function (item) { | ||
if (item.elem) { | ||
svg += this.createElem(item); | ||
} else if (item.text) { | ||
svg += this.createText(item.text); | ||
} else if (item.doctype) { | ||
svg += this.createDoctype(item.doctype); | ||
} else if (item.processinginstruction) { | ||
svg += this.createProcInst(item.processinginstruction); | ||
} else if (item.comment) { | ||
svg += this.createComment(item.comment); | ||
} else if (item.cdata) { | ||
svg += this.createCDATA(item.cdata); | ||
} | ||
}, this); | ||
for (const item of data.children) { | ||
if (item.type === 'element') { | ||
svg += this.createElem(item); | ||
} | ||
if (item.type === 'text') { | ||
svg += this.createText(item); | ||
} | ||
if (item.type === 'doctype') { | ||
svg += this.createDoctype(item); | ||
} | ||
if (item.type === 'instruction') { | ||
svg += this.createProcInst(item); | ||
} | ||
if (item.type === 'comment') { | ||
svg += this.createComment(item); | ||
} | ||
if (item.type === 'cdata') { | ||
svg += this.createCDATA(item); | ||
} | ||
} | ||
@@ -150,3 +153,4 @@ | ||
*/ | ||
JS2SVG.prototype.createDoctype = function (doctype) { | ||
JS2SVG.prototype.createDoctype = function (node) { | ||
const { doctype } = node.data; | ||
return this.config.doctypeStart + doctype + this.config.doctypeEnd; | ||
@@ -162,9 +166,6 @@ }; | ||
*/ | ||
JS2SVG.prototype.createProcInst = function (instruction) { | ||
JS2SVG.prototype.createProcInst = function (node) { | ||
const { name, value } = node; | ||
return ( | ||
this.config.procInstStart + | ||
instruction.name + | ||
' ' + | ||
instruction.body + | ||
this.config.procInstEnd | ||
this.config.procInstStart + name + ' ' + value + this.config.procInstEnd | ||
); | ||
@@ -180,4 +181,5 @@ }; | ||
*/ | ||
JS2SVG.prototype.createComment = function (comment) { | ||
return this.config.commentStart + comment + this.config.commentEnd; | ||
JS2SVG.prototype.createComment = function (node) { | ||
const { value } = node; | ||
return this.config.commentStart + value + this.config.commentEnd; | ||
}; | ||
@@ -192,5 +194,6 @@ | ||
*/ | ||
JS2SVG.prototype.createCDATA = function (cdata) { | ||
JS2SVG.prototype.createCDATA = function (node) { | ||
const { value } = node; | ||
return ( | ||
this.createIndent() + this.config.cdataStart + cdata + this.config.cdataEnd | ||
this.createIndent() + this.config.cdataStart + value + this.config.cdataEnd | ||
); | ||
@@ -208,9 +211,13 @@ }; | ||
// beautiful injection for obtaining SVG information :) | ||
if (data.isElem('svg') && data.hasAttr('width') && data.hasAttr('height')) { | ||
this.width = data.attr('width').value; | ||
this.height = data.attr('height').value; | ||
if ( | ||
data.name === 'svg' && | ||
data.attributes.width != null && | ||
data.attributes.height != null | ||
) { | ||
this.width = data.attributes.width; | ||
this.height = data.attributes.height; | ||
} | ||
// empty element and short tag | ||
if (data.isEmpty()) { | ||
if (data.children.length === 0) { | ||
if (this.config.useShortTags) { | ||
@@ -220,3 +227,3 @@ return ( | ||
this.config.tagShortStart + | ||
data.elem + | ||
data.name + | ||
this.createAttrs(data) + | ||
@@ -229,7 +236,7 @@ this.config.tagShortEnd | ||
this.config.tagShortStart + | ||
data.elem + | ||
data.name + | ||
this.createAttrs(data) + | ||
this.config.tagOpenEnd + | ||
this.config.tagCloseStart + | ||
data.elem + | ||
data.name + | ||
this.config.tagCloseEnd | ||
@@ -271,3 +278,3 @@ ); | ||
tagOpenStart + | ||
data.elem + | ||
data.name + | ||
this.createAttrs(data) + | ||
@@ -279,3 +286,3 @@ tagOpenEnd + | ||
tagCloseStart + | ||
data.elem + | ||
data.name + | ||
tagCloseEnd | ||
@@ -293,21 +300,15 @@ ); | ||
*/ | ||
JS2SVG.prototype.createAttrs = function (elem) { | ||
var attrs = ''; | ||
elem.eachAttr(function (attr) { | ||
if (attr.value !== undefined) { | ||
JS2SVG.prototype.createAttrs = function (element) { | ||
let attrs = ''; | ||
for (const [name, value] of Object.entries(element.attributes)) { | ||
if (value !== undefined) { | ||
const encodedValue = value | ||
.toString() | ||
.replace(this.config.regValEntities, this.config.encodeEntity); | ||
attrs += | ||
' ' + | ||
attr.name + | ||
this.config.attrStart + | ||
String(attr.value).replace( | ||
this.config.regValEntities, | ||
this.config.encodeEntity | ||
) + | ||
this.config.attrEnd; | ||
' ' + name + this.config.attrStart + encodedValue + this.config.attrEnd; | ||
} else { | ||
attrs += ' ' + attr.name; | ||
attrs += ' ' + name; | ||
} | ||
}, this); | ||
} | ||
return attrs; | ||
@@ -323,9 +324,10 @@ }; | ||
*/ | ||
JS2SVG.prototype.createText = function (text) { | ||
JS2SVG.prototype.createText = function (node) { | ||
const { value } = node; | ||
return ( | ||
this.createIndent() + | ||
this.config.textStart + | ||
text.replace(this.config.regEntities, this.config.encodeEntity) + | ||
value.replace(this.config.regEntities, this.config.encodeEntity) + | ||
(this.textContext ? '' : this.config.textEnd) | ||
); | ||
}; |
'use strict'; | ||
const { selectAll, selectOne, is } = require('css-select'); | ||
const { parseName } = require('./tools.js'); | ||
const svgoCssSelectAdapter = require('./css-select-adapter'); | ||
const CSSClassList = require('./css-class-list'); | ||
const CSSStyleDeclaration = require('./css-style-declaration'); | ||
@@ -11,5 +14,42 @@ var cssSelectOpts = { | ||
const attrsHandler = { | ||
get: (attributes, name) => { | ||
// eslint-disable-next-line no-prototype-builtins | ||
if (attributes.hasOwnProperty(name)) { | ||
return { | ||
name, | ||
get value() { | ||
return attributes[name]; | ||
}, | ||
set value(value) { | ||
attributes[name] = value; | ||
}, | ||
}; | ||
} | ||
}, | ||
set: (attributes, name, attr) => { | ||
attributes[name] = attr.value; | ||
return true; | ||
}, | ||
}; | ||
var JSAPI = function (data, parentNode) { | ||
Object.assign(this, data); | ||
if (parentNode) { | ||
if (this.type === 'element') { | ||
if (this.attributes == null) { | ||
this.attributes = {}; | ||
} | ||
if (this.children == null) { | ||
this.children = []; | ||
} | ||
Object.defineProperty(this, 'class', { | ||
writable: true, | ||
configurable: true, | ||
value: new CSSClassList(this), | ||
}); | ||
Object.defineProperty(this, 'style', { | ||
writable: true, | ||
configurable: true, | ||
value: new CSSStyleDeclaration(this), | ||
}); | ||
Object.defineProperty(this, 'parentNode', { | ||
@@ -19,2 +59,19 @@ writable: true, | ||
}); | ||
// temporary attrs polyfill | ||
// TODO remove after migration | ||
const element = this; | ||
Object.defineProperty(this, 'attrs', { | ||
configurable: true, | ||
get() { | ||
return new Proxy(element.attributes, attrsHandler); | ||
}, | ||
set(value) { | ||
const newAttributes = {}; | ||
for (const attr of Object.values(value)) { | ||
newAttributes[attr.name] = attr.value; | ||
} | ||
element.attributes = newAttributes; | ||
}, | ||
}); | ||
} | ||
@@ -30,28 +87,8 @@ }; | ||
JSAPI.prototype.clone = function () { | ||
var node = this; | ||
var nodeData = {}; | ||
Object.keys(node).forEach(function (key) { | ||
if (key !== 'class' && key !== 'style' && key !== 'content') { | ||
nodeData[key] = node[key]; | ||
} | ||
}); | ||
const { children, ...nodeData } = this; | ||
// Deep-clone node data. | ||
nodeData = JSON.parse(JSON.stringify(nodeData)); | ||
// parentNode gets set to a proper object by the parent clone, | ||
// but it needs to be true/false now to do the right thing | ||
// in the constructor. | ||
var clonedNode = new JSAPI(nodeData, !!node.parentNode); | ||
if (node.class) { | ||
clonedNode.class = node.class.clone(clonedNode); | ||
} | ||
if (node.style) { | ||
clonedNode.style = node.style.clone(clonedNode); | ||
} | ||
if (node.content) { | ||
clonedNode.content = node.content.map(function (childNode) { | ||
var clonedChild = childNode.clone(); | ||
const clonedNode = new JSAPI(JSON.parse(JSON.stringify(nodeData)), null); | ||
if (children) { | ||
clonedNode.children = children.map((child) => { | ||
const clonedChild = child.clone(); | ||
clonedChild.parentNode = clonedNode; | ||
@@ -61,3 +98,2 @@ return clonedChild; | ||
} | ||
return clonedNode; | ||
@@ -74,7 +110,12 @@ }; | ||
JSAPI.prototype.isElem = function (param) { | ||
if (!param) return !!this.elem; | ||
if (Array.isArray(param)) return !!this.elem && param.indexOf(this.elem) > -1; | ||
return !!this.elem && this.elem === param; | ||
if (this.type !== 'element') { | ||
return false; | ||
} | ||
if (param == null) { | ||
return true; | ||
} | ||
if (Array.isArray(param)) { | ||
return param.includes(this.name); | ||
} | ||
return this.name === param; | ||
}; | ||
@@ -89,3 +130,3 @@ | ||
JSAPI.prototype.renameElem = function (name) { | ||
if (name && typeof name === 'string') this.elem = this.local = name; | ||
if (name && typeof name === 'string') this.name = name; | ||
@@ -101,3 +142,3 @@ return this; | ||
JSAPI.prototype.isEmpty = function () { | ||
return !this.content || !this.content.length; | ||
return !this.children || !this.children.length; | ||
}; | ||
@@ -120,7 +161,7 @@ | ||
/** | ||
* Changes content by removing elements and/or adding new elements. | ||
* Changes children by removing elements and/or adding new elements. | ||
* | ||
* @param {Number} start Index at which to start changing the content. | ||
* @param {Number} start Index at which to start changing the children. | ||
* @param {Number} n Number of elements to remove. | ||
* @param {Array|Object} [insertion] Elements to add to the content. | ||
* @param {Array|Object} [insertion] Elements to add to the children. | ||
* @return {Array} Removed elements. | ||
@@ -138,3 +179,6 @@ */ | ||
return this.content.splice.apply(this.content, [start, n].concat(insertion)); | ||
return this.children.splice.apply( | ||
this.children, | ||
[start, n].concat(insertion) | ||
); | ||
}; | ||
@@ -151,10 +195,19 @@ | ||
JSAPI.prototype.hasAttr = function (name, val) { | ||
if (!this.attrs || !Object.keys(this.attrs).length) return false; | ||
if (!arguments.length) return !!this.attrs; | ||
if (val !== undefined) | ||
return !!this.attrs[name] && this.attrs[name].value === val.toString(); | ||
return !!this.attrs[name]; | ||
if (this.type !== 'element') { | ||
return false; | ||
} | ||
if (Object.keys(this.attributes).length === 0) { | ||
return false; | ||
} | ||
if (name == null) { | ||
return true; | ||
} | ||
// eslint-disable-next-line no-prototype-builtins | ||
if (this.attributes.hasOwnProperty(name) === false) { | ||
return false; | ||
} | ||
if (val !== undefined) { | ||
return this.attributes[name] === val.toString(); | ||
} | ||
return true; | ||
}; | ||
@@ -194,15 +247,19 @@ | ||
function nameTest(attr) { | ||
return attr.local === localName; | ||
const { local } = parseName(attr.name); | ||
return local === localName; | ||
} | ||
function stringValueTest(attr) { | ||
return attr.local === localName && val == attr.value; | ||
const { local } = parseName(attr.name); | ||
return local === localName && val == attr.value; | ||
} | ||
function regexpValueTest(attr) { | ||
return attr.local === localName && val.test(attr.value); | ||
const { local } = parseName(attr.name); | ||
return local === localName && val.test(attr.value); | ||
} | ||
function funcValueTest(attr) { | ||
return attr.local === localName && val(attr.value); | ||
const { local } = parseName(attr.name); | ||
return local === localName && val(attr.value); | ||
} | ||
@@ -220,8 +277,5 @@ }; | ||
JSAPI.prototype.attr = function (name, val) { | ||
if (!this.hasAttr() || !arguments.length) return undefined; | ||
if (val !== undefined) | ||
return this.hasAttr(name, val) ? this.attrs[name] : undefined; | ||
return this.attrs[name]; | ||
if (this.hasAttr(name, val)) { | ||
return this.attrs[name]; | ||
} | ||
}; | ||
@@ -240,3 +294,3 @@ | ||
var elem = this; | ||
elem && (!elem.hasAttr(name) || !elem.attr(name).value); | ||
elem && (!elem.hasAttr(name) || !elem.attributes[name]); | ||
elem = elem.parentNode | ||
@@ -248,3 +302,3 @@ ); | ||
} else if (elem && elem.hasAttr(name)) { | ||
return elem.attrs[name].value; | ||
return elem.attributes[name]; | ||
} | ||
@@ -260,18 +314,19 @@ }; | ||
*/ | ||
JSAPI.prototype.removeAttr = function (name, val, recursive) { | ||
if (!arguments.length) return false; | ||
JSAPI.prototype.removeAttr = function (name, val) { | ||
if (this.type !== 'element') { | ||
return false; | ||
} | ||
if (arguments.length === 0) { | ||
return false; | ||
} | ||
if (Array.isArray(name)) { | ||
name.forEach(this.removeAttr, this); | ||
for (const nameItem of name) { | ||
this.removeAttr(nameItem, val); | ||
} | ||
return false; | ||
} | ||
if (!this.hasAttr(name)) return false; | ||
if (!recursive && val && this.attrs[name].value !== val) return false; | ||
delete this.attrs[name]; | ||
if (!Object.keys(this.attrs).length) delete this.attrs; | ||
if (this.hasAttr(name, val) === false) { | ||
return false; | ||
} | ||
delete this.attributes[name]; | ||
return true; | ||
@@ -289,11 +344,5 @@ }; | ||
if ( | ||
attr.name === undefined || | ||
attr.prefix === undefined || | ||
attr.local === undefined | ||
) | ||
return false; | ||
if (attr.name === undefined) return false; | ||
this.attrs = this.attrs || {}; | ||
this.attrs[attr.name] = attr; | ||
this.attributes[attr.name] = attr.value; | ||
@@ -321,8 +370,11 @@ if (attr.name === 'class') { | ||
JSAPI.prototype.eachAttr = function (callback, context) { | ||
if (!this.hasAttr()) return false; | ||
if (this.type !== 'element') { | ||
return false; | ||
} | ||
if (callback == null) { | ||
return false; | ||
} | ||
for (const attr of Object.values(this.attrs)) { | ||
callback.call(context, attr); | ||
} | ||
return true; | ||
@@ -339,3 +391,5 @@ }; | ||
JSAPI.prototype.someAttr = function (callback, context) { | ||
if (!this.hasAttr()) return false; | ||
if (this.type !== 'element') { | ||
return false; | ||
} | ||
@@ -342,0 +396,0 @@ for (const attr of Object.values(this.attrs)) { |
'use strict'; | ||
const { visit } = require('../xast.js'); | ||
/** | ||
@@ -37,2 +39,10 @@ * Plugins engine. | ||
break; | ||
case 'visitor': | ||
for (const plugin of group) { | ||
if (plugin.active) { | ||
const visitor = plugin.fn(data, plugin.params, info); | ||
visit(data, visitor); | ||
} | ||
} | ||
break; | ||
} | ||
@@ -54,5 +64,5 @@ } | ||
function monkeys(items) { | ||
items.content = items.content.filter(function (item) { | ||
items.children = items.children.filter(function (item) { | ||
// reverse pass | ||
if (reverse && item.content) { | ||
if (reverse && item.children) { | ||
monkeys(item); | ||
@@ -73,3 +83,3 @@ } | ||
// direct pass | ||
if (!reverse && item.content) { | ||
if (!reverse && item.children) { | ||
monkeys(item); | ||
@@ -76,0 +86,0 @@ } |
'use strict'; | ||
var SAX = require('@trysound/sax'), | ||
JSAPI = require('./jsAPI.js'), | ||
CSSClassList = require('./css-class-list'), | ||
CSSStyleDeclaration = require('./css-style-declaration'), | ||
textElems = require('../../plugins/_collections.js').textElems, | ||
entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g; | ||
const SAX = require('@trysound/sax'); | ||
const JSAPI = require('./jsAPI.js'); | ||
const { textElems } = require('../../plugins/_collections.js'); | ||
var config = { | ||
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g; | ||
const config = { | ||
strict: true, | ||
@@ -25,13 +24,11 @@ trim: false, | ||
module.exports = function (data) { | ||
var sax = SAX.parser(config.strict, config), | ||
root = new JSAPI({ elem: '#document', content: [] }), | ||
current = root, | ||
stack = [root]; | ||
const sax = SAX.parser(config.strict, config); | ||
const root = new JSAPI({ type: 'root', children: [] }); | ||
let current = root; | ||
let stack = [root]; | ||
function pushToContent(content) { | ||
content = new JSAPI(content, current); | ||
(current.content = current.content || []).push(content); | ||
return content; | ||
function pushToContent(node) { | ||
const wrapped = new JSAPI(node, current); | ||
current.children.push(wrapped); | ||
return wrapped; | ||
} | ||
@@ -41,7 +38,12 @@ | ||
pushToContent({ | ||
doctype: doctype, | ||
type: 'doctype', | ||
// TODO parse doctype for name, public and system to match xast | ||
name: 'svg', | ||
data: { | ||
doctype, | ||
}, | ||
}); | ||
var subsetStart = doctype.indexOf('['), | ||
entityMatch; | ||
const subsetStart = doctype.indexOf('['); | ||
let entityMatch; | ||
@@ -59,3 +61,5 @@ if (subsetStart >= 0) { | ||
pushToContent({ | ||
processinginstruction: data, | ||
type: 'instruction', | ||
name: data.name, | ||
value: data.body, | ||
}); | ||
@@ -66,3 +70,4 @@ }; | ||
pushToContent({ | ||
comment: comment.trim(), | ||
type: 'comment', | ||
value: comment.trim(), | ||
}); | ||
@@ -73,3 +78,4 @@ }; | ||
pushToContent({ | ||
cdata: cdata, | ||
type: 'cdata', | ||
value: cdata, | ||
}); | ||
@@ -79,37 +85,17 @@ }; | ||
sax.onopentag = function (data) { | ||
var elem = { | ||
elem: data.name, | ||
prefix: data.prefix, | ||
local: data.local, | ||
attrs: {}, | ||
var element = { | ||
type: 'element', | ||
name: data.name, | ||
attributes: {}, | ||
children: [], | ||
}; | ||
elem.class = new CSSClassList(elem); | ||
elem.style = new CSSStyleDeclaration(elem); | ||
if (Object.keys(data.attributes).length) { | ||
for (const [name, attr] of Object.entries(data.attributes)) { | ||
if (name === 'class') { | ||
// has class attribute | ||
elem.class.hasClass(); | ||
} | ||
if (name === 'style') { | ||
// has style attribute | ||
elem.style.hasStyle(); | ||
} | ||
elem.attrs[name] = { | ||
name: name, | ||
value: attr.value, | ||
prefix: attr.prefix, | ||
local: attr.local, | ||
}; | ||
} | ||
for (const [name, attr] of Object.entries(data.attributes)) { | ||
element.attributes[name] = attr.value; | ||
} | ||
elem = pushToContent(elem); | ||
current = elem; | ||
element = pushToContent(element); | ||
current = element; | ||
stack.push(elem); | ||
stack.push(element); | ||
}; | ||
@@ -119,6 +105,12 @@ | ||
// prevent trimming of meaningful whitespace inside textual tags | ||
if (textElems.includes(current.elem) && !data.prefix) { | ||
pushToContent({ text: text }); | ||
if (textElems.includes(current.name) && !data.prefix) { | ||
pushToContent({ | ||
type: 'text', | ||
value: text, | ||
}); | ||
} else if (/\S/.test(text)) { | ||
pushToContent({ text: text.trim() }); | ||
pushToContent({ | ||
type: 'text', | ||
value: text.trim(), | ||
}); | ||
} | ||
@@ -125,0 +117,0 @@ }; |
@@ -106,3 +106,3 @@ 'use strict'; | ||
delimiter != '' && | ||
(item < 0 || (itemStr.charCodeAt(0) == 46 && prev % 1 !== 0)) | ||
(item < 0 || (itemStr.charAt(0) === '.' && prev % 1 !== 0)) | ||
) { | ||
@@ -134,5 +134,5 @@ delimiter = ''; | ||
if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) { | ||
if (0 < num && num < 1 && strNum.charAt(0) === '0') { | ||
strNum = strNum.slice(1); | ||
} else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) { | ||
} else if (-1 < num && num < 0 && strNum.charAt(1) === '0') { | ||
strNum = strNum.charAt(0) + strNum.slice(2); | ||
@@ -143,1 +143,28 @@ } | ||
exports.removeLeadingZero = removeLeadingZero; | ||
const parseName = (name) => { | ||
if (name == null) { | ||
return { | ||
prefix: '', | ||
local: '', | ||
}; | ||
} | ||
if (name === 'xmlns') { | ||
return { | ||
prefix: 'xmlns', | ||
local: '', | ||
}; | ||
} | ||
const chunks = name.split(':'); | ||
if (chunks.length === 1) { | ||
return { | ||
prefix: '', | ||
local: chunks[0], | ||
}; | ||
} | ||
return { | ||
prefix: chunks[0], | ||
local: chunks[1], | ||
}; | ||
}; | ||
exports.parseName = parseName; |
{ | ||
"name": "svgo", | ||
"version": "2.2.2", | ||
"version": "2.3.0", | ||
"description": "Nodejs-based tool for optimizing SVG vector graphics files", | ||
@@ -53,6 +53,7 @@ "keywords": [ | ||
"test": "c8 --reporter=html --reporter=text mocha \"test/*/_index.js\" \"**/*.test.js\" --ignore=\"node_modules/**\"", | ||
"lint": "eslint .", | ||
"lint": "eslint --ignore-path .gitignore . && prettier --list-different \"**/*.js\" --ignore-path .gitignore", | ||
"fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore", | ||
"typecheck": "tsc", | ||
"test-browser": "rollup -c && node ./test/browser.js", | ||
"test-regression": "NO_DIFF=1 node ./test/regression.js", | ||
"test-regression": "node ./test/regression-extract.js && NO_DIFF=1 node ./test/regression.js", | ||
"prepublishOnly": "rm -rf dist && rollup -c" | ||
@@ -64,6 +65,2 @@ }, | ||
"eslintConfig": { | ||
"ignorePatterns": [ | ||
"dist", | ||
"fixtures" | ||
], | ||
"parserOptions": { | ||
@@ -112,18 +109,18 @@ "ecmaVersion": "2021" | ||
"@rollup/plugin-node-resolve": "^11.2.0", | ||
"@types/mocha": "^8.2.1", | ||
"@types/mocha": "^8.2.2", | ||
"c8": "^7.6.0", | ||
"chai": "^4.3.0", | ||
"chai": "^4.3.4", | ||
"del": "^6.0.0", | ||
"eslint": "^7.20.0", | ||
"get-stream": "^6.0.0", | ||
"mocha": "^8.3.0", | ||
"eslint": "^7.22.0", | ||
"mocha": "^8.3.2", | ||
"mock-stdin": "^1.0.0", | ||
"node-fetch": "^2.6.1", | ||
"pixelmatch": "^5.2.1", | ||
"playwright": "^1.8.1", | ||
"playwright": "^1.9.2", | ||
"pngjs": "^6.0.0", | ||
"prettier": "^2.2.1", | ||
"rollup": "^2.39.0", | ||
"rollup": "^2.42.1", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"tar-stream": "^2.2.0", | ||
"typescript": "^4.2.2" | ||
"typescript": "^4.2.3" | ||
}, | ||
@@ -130,0 +127,0 @@ "engines": { |
@@ -5,12 +5,3 @@ '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; | ||
var prevCtrlPoint; | ||
@@ -27,3 +18,3 @@ /** | ||
const pathData = []; // JS representation of the path data | ||
const newPathData = parsePathData(path.attr('d').value); | ||
const newPathData = parsePathData(path.attributes.d); | ||
for (const { command, args } of newPathData) { | ||
@@ -101,200 +92,2 @@ if (command === 'Z' || command === 'z') { | ||
/** | ||
* Apply transformation(s) to the Path data. | ||
* | ||
* @param {Object} elem current element | ||
* @param {Array} path input path data | ||
* @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width) | ||
* @return {Array} output path data | ||
*/ | ||
exports.applyTransforms = function (elem, path, params) { | ||
// if there are no 'stroke' attr and references to other objects such as | ||
// gradiends or clip-path which are also subjects to transform. | ||
if ( | ||
!elem.hasAttr('transform') || | ||
!elem.attr('transform').value || | ||
// styles are not considered when applying transform | ||
// can be fixed properly with new style engine | ||
elem.hasAttr('style') || | ||
elem.someAttr( | ||
(attr) => | ||
referencesProps.includes(attr.name) && attr.value.includes('url(') | ||
) | ||
) { | ||
return path; | ||
} | ||
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; | ||
// "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) | ||
); | ||
if (!hasStrokeWidth) return path; | ||
} | ||
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 ( | ||
!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-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; | ||
} | ||
path.forEach(function (pathItem) { | ||
if (pathItem.data) { | ||
// h -> l | ||
if (pathItem.instruction === 'h') { | ||
pathItem.instruction = 'l'; | ||
pathItem.data[1] = 0; | ||
// v -> l | ||
} else if (pathItem.instruction === 'v') { | ||
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]; | ||
} | ||
} | ||
}); | ||
// remove transform attr | ||
elem.removeAttr('transform'); | ||
return path; | ||
}; | ||
/** | ||
* Apply transform 3x3 matrix to x-y point. | ||
* | ||
* @param {Array} matrix transform 3x3 matrix | ||
* @param {Array} point x-y point | ||
* @return {Array} point with new coordinates | ||
*/ | ||
function transformPoint(matrix, x, y) { | ||
return [ | ||
matrix[0] * x + matrix[2] * y + matrix[4], | ||
matrix[1] * x + matrix[3] * y + matrix[5], | ||
]; | ||
} | ||
/** | ||
* Compute Cubic Bézie bounding box. | ||
@@ -556,3 +349,3 @@ * | ||
path.attr('d').value = stringifyPathData({ | ||
path.attributes.d = stringifyPathData({ | ||
pathData, | ||
@@ -559,0 +352,0 @@ precision: params.floatPrecision, |
@@ -264,2 +264,3 @@ 'use strict'; | ||
* | ||
* @param {Array} cursor [x, y] | ||
* @param {Array} arc [a, b, rotation in deg] | ||
@@ -269,3 +270,5 @@ * @param {Array} transform transformation matrix | ||
*/ | ||
exports.transformArc = function (arc, transform) { | ||
exports.transformArc = function (cursor, arc, transform) { | ||
const x = arc[5] - cursor[0]; | ||
const y = arc[6] - cursor[1]; | ||
var a = arc[0], | ||
@@ -277,4 +280,4 @@ b = arc[1], | ||
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); | ||
Math.pow(x * cos + y * sin, 2) / (4 * a * a) + | ||
Math.pow(y * cos - x * sin, 2) / (4 * b * b); | ||
if (h > 1) { | ||
@@ -281,0 +284,0 @@ h = Math.sqrt(h); |
'use strict'; | ||
exports.type = 'full'; | ||
const { closestByName } = require('../lib/xast.js'); | ||
exports.type = 'perItem'; | ||
exports.active = false; | ||
@@ -46,3 +48,3 @@ | ||
] | ||
` | ||
`; | ||
@@ -54,38 +56,30 @@ /** | ||
*/ | ||
exports.fn = function(data, params) { | ||
exports.fn = (node, params) => { | ||
if ( | ||
node.type === 'element' && | ||
node.name === 'svg' && | ||
closestByName(node.parentNode, 'svg') == null | ||
) { | ||
if (!params || !(Array.isArray(params.attributes) || params.attribute)) { | ||
console.error(ENOCLS); | ||
return data; | ||
console.error(ENOCLS); | ||
return; | ||
} | ||
var attributes = params.attributes || [ params.attribute ], | ||
svg = data.content[0]; | ||
const attributes = params.attributes || [params.attribute]; | ||
if (svg.isElem('svg')) { | ||
attributes.forEach(function (attribute) { | ||
if (typeof attribute === 'string') { | ||
if (!svg.hasAttr(attribute)) { | ||
svg.addAttr({ | ||
name: attribute, | ||
prefix: '', | ||
local: attribute | ||
}); | ||
} | ||
} else if (typeof attribute === 'object') { | ||
Object.keys(attribute).forEach(function (key) { | ||
if (!svg.hasAttr(key)) { | ||
svg.addAttr({ | ||
name: key, | ||
value: attribute[key], | ||
prefix: '', | ||
local: key | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
for (const attribute of attributes) { | ||
if (typeof attribute === 'string') { | ||
if (node.attributes[attribute] == null) { | ||
node.attributes[attribute] = undefined; | ||
} | ||
} | ||
if (typeof attribute === 'object') { | ||
for (const key of Object.keys(attribute)) { | ||
if (node.attributes[key] == null) { | ||
node.attributes[key] = attribute[key]; | ||
} | ||
} | ||
} | ||
} | ||
return data; | ||
} | ||
}; |
@@ -35,17 +35,22 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(data, params) { | ||
if (!params || !(Array.isArray(params.classNames) && params.classNames.some(String) || params.className)) { | ||
console.error(ENOCLS); | ||
return data; | ||
} | ||
exports.fn = function (data, params) { | ||
if ( | ||
!params || | ||
!( | ||
(Array.isArray(params.classNames) && params.classNames.some(String)) || | ||
params.className | ||
) | ||
) { | ||
console.error(ENOCLS); | ||
return data; | ||
} | ||
var classNames = params.classNames || [ params.className ], | ||
svg = data.content[0]; | ||
var classNames = params.classNames || [params.className], | ||
svg = data.children[0]; | ||
if (svg.isElem('svg')) { | ||
svg.class.add.apply(svg.class, classNames); | ||
} | ||
if (svg.isElem('svg')) { | ||
svg.class.add.apply(svg.class, classNames); | ||
} | ||
return data; | ||
return data; | ||
}; |
@@ -7,13 +7,14 @@ 'use strict'; | ||
exports.description = 'cleanups attributes from newlines, trailing and repeating spaces'; | ||
exports.description = | ||
'cleanups attributes from newlines, trailing and repeating spaces'; | ||
exports.params = { | ||
newlines: true, | ||
trim: true, | ||
spaces: true | ||
newlines: true, | ||
trim: true, | ||
spaces: true, | ||
}; | ||
var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g, | ||
regNewlines = /\r?\n/g, | ||
regSpaces = /\s{2,}/g; | ||
regNewlines = /\r?\n/g, | ||
regSpaces = /\s{2,}/g; | ||
@@ -29,30 +30,25 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element') { | ||
for (const name of Object.keys(item.attributes)) { | ||
if (params.newlines) { | ||
// new line which requires a space instead of themselve | ||
item.attributes[name] = item.attributes[name].replace( | ||
regNewlinesNeedSpace, | ||
(match, p1, p2) => p1 + ' ' + p2 | ||
); | ||
if (item.isElem()) { | ||
// simple new line | ||
item.attributes[name] = item.attributes[name].replace(regNewlines, ''); | ||
} | ||
item.eachAttr(function(attr) { | ||
if (params.trim) { | ||
item.attributes[name] = item.attributes[name].trim(); | ||
} | ||
if (params.newlines) { | ||
// new line which requires a space instead of themselve | ||
attr.value = attr.value.replace(regNewlinesNeedSpace, function(match, p1, p2) { | ||
return p1 + ' ' + p2; | ||
}); | ||
// simple new line | ||
attr.value = attr.value.replace(regNewlines, ''); | ||
} | ||
if (params.trim) { | ||
attr.value = attr.value.trim(); | ||
} | ||
if (params.spaces) { | ||
attr.value = attr.value.replace(regSpaces, ' '); | ||
} | ||
}); | ||
if (params.spaces) { | ||
item.attributes[name] = item.attributes[name].replace(regSpaces, ' '); | ||
} | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { traverse } = require('../lib/xast.js'); | ||
exports.type = 'full'; | ||
@@ -7,3 +9,4 @@ | ||
exports.description = 'remove or cleanup enable-background attribute when possible'; | ||
exports.description = | ||
'remove or cleanup enable-background attribute when possible'; | ||
@@ -20,3 +23,3 @@ /** | ||
* | ||
* @param {Object} item current iteration item | ||
* @param {Object} root current iteration item | ||
* @return {Boolean} if false, item will be filtered out | ||
@@ -26,62 +29,48 @@ * | ||
*/ | ||
exports.fn = function(data) { | ||
exports.fn = function (root) { | ||
const regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/; | ||
let hasFilter = false; | ||
const elems = ['svg', 'mask', 'pattern']; | ||
var regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/, | ||
hasFilter = false, | ||
elems = ['svg', 'mask', 'pattern']; | ||
traverse(root, (node) => { | ||
if (node.type === 'element') { | ||
if ( | ||
elems.includes(node.name) && | ||
node.attributes['enable-background'] != null && | ||
node.attributes.width != null && | ||
node.attributes.height != null | ||
) { | ||
const match = node.attributes['enable-background'].match( | ||
regEnableBackground | ||
); | ||
function checkEnableBackground(item) { | ||
if ( | ||
item.isElem(elems) && | ||
item.hasAttr('enable-background') && | ||
item.hasAttr('width') && | ||
item.hasAttr('height') | ||
) { | ||
var match = item.attr('enable-background').value.match(regEnableBackground); | ||
if (match) { | ||
if ( | ||
item.attr('width').value === match[1] && | ||
item.attr('height').value === match[3] | ||
) { | ||
if (item.isElem('svg')) { | ||
item.removeAttr('enable-background'); | ||
} else { | ||
item.attr('enable-background').value = 'new'; | ||
} | ||
} | ||
if (match) { | ||
if ( | ||
node.attributes.width === match[1] && | ||
node.attributes.height === match[3] | ||
) { | ||
if (node.name === 'svg') { | ||
delete node.attributes['enable-background']; | ||
} else { | ||
node.attributes['enable-background'] = 'new'; | ||
} | ||
} | ||
} | ||
} | ||
if (node.name === 'filter') { | ||
hasFilter = true; | ||
} | ||
} | ||
}); | ||
function checkForFilter(item) { | ||
if (item.isElem('filter')) { | ||
hasFilter = true; | ||
} | ||
} | ||
function monkeys(items, fn) { | ||
items.content.forEach(function(item) { | ||
fn(item); | ||
if (item.content) { | ||
monkeys(item, fn); | ||
} | ||
}); | ||
return items; | ||
} | ||
var firstStep = monkeys(data, function(item) { | ||
checkEnableBackground(item); | ||
if (!hasFilter) { | ||
checkForFilter(item); | ||
} | ||
if (hasFilter === false) { | ||
traverse(root, (node) => { | ||
if (node.type === 'element') { | ||
//we don't need 'enable-background' if we have no filters | ||
delete node.attributes['enable-background']; | ||
} | ||
}); | ||
} | ||
return hasFilter ? firstStep : monkeys(firstStep, function(item) { | ||
//we don't need 'enable-background' if we have no filters | ||
item.removeAttr('enable-background'); | ||
}); | ||
return root; | ||
}; |
'use strict'; | ||
const { traverse, traverseBreak } = require('../lib/xast.js'); | ||
const { parseName } = require('../lib/svgo/tools.js'); | ||
exports.type = 'full'; | ||
@@ -10,20 +13,70 @@ | ||
exports.params = { | ||
remove: true, | ||
minify: true, | ||
prefix: '', | ||
preserve: [], | ||
preservePrefixes: [], | ||
force: false | ||
remove: true, | ||
minify: true, | ||
prefix: '', | ||
preserve: [], | ||
preservePrefixes: [], | ||
force: false, | ||
}; | ||
var referencesProps = new Set(require('./_collections').referencesProps), | ||
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/, | ||
regReferencesHref = /^#(.+?)$/, | ||
regReferencesBegin = /(\w+)\./, | ||
styleOrScript = ['style', 'script'], | ||
generateIDchars = [ | ||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' | ||
], | ||
maxIDindex = generateIDchars.length - 1; | ||
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/, | ||
regReferencesHref = /^#(.+?)$/, | ||
regReferencesBegin = /(\w+)\./, | ||
styleOrScript = ['style', 'script'], | ||
generateIDchars = [ | ||
'a', | ||
'b', | ||
'c', | ||
'd', | ||
'e', | ||
'f', | ||
'g', | ||
'h', | ||
'i', | ||
'j', | ||
'k', | ||
'l', | ||
'm', | ||
'n', | ||
'o', | ||
'p', | ||
'q', | ||
'r', | ||
's', | ||
't', | ||
'u', | ||
'v', | ||
'w', | ||
'x', | ||
'y', | ||
'z', | ||
'A', | ||
'B', | ||
'C', | ||
'D', | ||
'E', | ||
'F', | ||
'G', | ||
'H', | ||
'I', | ||
'J', | ||
'K', | ||
'L', | ||
'M', | ||
'N', | ||
'O', | ||
'P', | ||
'Q', | ||
'R', | ||
'S', | ||
'T', | ||
'U', | ||
'V', | ||
'W', | ||
'X', | ||
'Y', | ||
'Z', | ||
], | ||
maxIDindex = generateIDchars.length - 1; | ||
@@ -39,124 +92,135 @@ /** | ||
*/ | ||
exports.fn = function(data, params) { | ||
var currentID, | ||
currentIDstring, | ||
IDs = new Map(), | ||
referencesIDs = new Map(), | ||
hasStyleOrScript = false, | ||
preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []), | ||
preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])), | ||
idValuePrefix = '#', | ||
idValuePostfix = '.'; | ||
exports.fn = function (root, params) { | ||
var currentID, | ||
currentIDstring, | ||
IDs = new Map(), | ||
referencesIDs = new Map(), | ||
hasStyleOrScript = false, | ||
preserveIDs = new Set( | ||
Array.isArray(params.preserve) | ||
? params.preserve | ||
: params.preserve | ||
? [params.preserve] | ||
: [] | ||
), | ||
preserveIDPrefixes = new Set( | ||
Array.isArray(params.preservePrefixes) | ||
? params.preservePrefixes | ||
: params.preservePrefixes | ||
? [params.preservePrefixes] | ||
: [] | ||
), | ||
idValuePrefix = '#', | ||
idValuePostfix = '.'; | ||
/** | ||
* Bananas! | ||
* | ||
* @param {Array} items input items | ||
* @return {Array} output items | ||
*/ | ||
function monkeys(items) { | ||
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) { | ||
var item = items.content[i]; | ||
traverse(root, (node) => { | ||
if (hasStyleOrScript === true) { | ||
return traverseBreak; | ||
} | ||
// quit if <style> or <script> present ('force' param prevents quitting) | ||
if (!params.force) { | ||
var isNotEmpty = Boolean(item.content); | ||
if (item.isElem(styleOrScript) && isNotEmpty) { | ||
hasStyleOrScript = true; | ||
continue; | ||
} | ||
// quit if <style> or <script> present ('force' param prevents quitting) | ||
if (!params.force) { | ||
if (node.isElem(styleOrScript) && node.children.length !== 0) { | ||
hasStyleOrScript = true; | ||
return; | ||
} | ||
// Don't remove IDs if the whole SVG consists only of defs. | ||
if (item.isElem('svg') && item.content) { | ||
var hasDefsOnly = true; | ||
for (var j = 0; j < item.content.length; j++) { | ||
if (!item.content[j].isElem('defs')) { | ||
hasDefsOnly = false; | ||
break; | ||
} | ||
} | ||
if (hasDefsOnly) { | ||
break; | ||
} | ||
} | ||
} | ||
// …and don't remove any ID if yes | ||
if (item.isElem()) { | ||
item.eachAttr(function(attr) { | ||
var key, match; | ||
// save IDs | ||
if (attr.name === 'id') { | ||
key = attr.value; | ||
if (IDs.has(key)) { | ||
item.removeAttr('id'); // remove repeated id | ||
} else { | ||
IDs.set(key, item); | ||
} | ||
return; | ||
} | ||
// save references | ||
if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) { | ||
key = match[2]; // url() reference | ||
} else if ( | ||
attr.local === 'href' && (match = attr.value.match(regReferencesHref)) || | ||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin)) | ||
) { | ||
key = match[1]; // href reference | ||
} | ||
if (key) { | ||
var ref = referencesIDs.get(key) || []; | ||
ref.push(attr); | ||
referencesIDs.set(key, ref); | ||
} | ||
}); | ||
} | ||
// go deeper | ||
if (item.content) { | ||
monkeys(item); | ||
} | ||
// Don't remove IDs if the whole SVG consists only of defs. | ||
if (node.type === 'element' && node.name === 'svg') { | ||
let hasDefsOnly = true; | ||
for (const child of node.children) { | ||
if (child.type !== 'element' || child.name !== 'defs') { | ||
hasDefsOnly = false; | ||
break; | ||
} | ||
} | ||
return items; | ||
if (hasDefsOnly) { | ||
return traverseBreak; | ||
} | ||
} | ||
} | ||
data = monkeys(data); | ||
// …and don't remove any ID if yes | ||
if (node.type === 'element') { | ||
for (const [name, value] of Object.entries(node.attributes)) { | ||
let key; | ||
let match; | ||
if (hasStyleOrScript) { | ||
return data; | ||
// save IDs | ||
if (name === 'id') { | ||
key = value; | ||
if (IDs.has(key)) { | ||
delete node.attributes.id; // remove repeated id | ||
} else { | ||
IDs.set(key, node); | ||
} | ||
} else { | ||
// save references | ||
const { local } = parseName(name); | ||
if ( | ||
referencesProps.has(name) && | ||
(match = value.match(regReferencesUrl)) | ||
) { | ||
key = match[2]; // url() reference | ||
} else if ( | ||
(local === 'href' && (match = value.match(regReferencesHref))) || | ||
(name === 'begin' && (match = value.match(regReferencesBegin))) | ||
) { | ||
key = match[1]; // href reference | ||
} | ||
if (key) { | ||
const refs = referencesIDs.get(key) || []; | ||
refs.push({ element: node, name, value }); | ||
referencesIDs.set(key, refs); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id); | ||
if (hasStyleOrScript) { | ||
return root; | ||
} | ||
for (var ref of referencesIDs) { | ||
var key = ref[0]; | ||
const idPreserved = (id) => | ||
preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id); | ||
if (IDs.has(key)) { | ||
// replace referenced IDs with the minified ones | ||
if (params.minify && !idPreserved(key)) { | ||
do { | ||
currentIDstring = getIDstring(currentID = generateID(currentID), params); | ||
} while (idPreserved(currentIDstring)); | ||
for (const [key, refs] of referencesIDs) { | ||
if (IDs.has(key)) { | ||
// replace referenced IDs with the minified ones | ||
if (params.minify && !idPreserved(key)) { | ||
do { | ||
currentIDstring = getIDstring( | ||
(currentID = generateID(currentID)), | ||
params | ||
); | ||
} while (idPreserved(currentIDstring)); | ||
IDs.get(key).attr('id').value = currentIDstring; | ||
IDs.get(key).attributes.id = currentIDstring; | ||
for (var attr of ref[1]) { | ||
attr.value = attr.value.includes(idValuePrefix) ? | ||
attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) : | ||
attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix); | ||
} | ||
} | ||
// don't remove referenced IDs | ||
IDs.delete(key); | ||
for (const { element, name, value } of refs) { | ||
element.attributes[name] = value.includes(idValuePrefix) | ||
? value.replace( | ||
idValuePrefix + key, | ||
idValuePrefix + currentIDstring | ||
) | ||
: value.replace( | ||
key + idValuePostfix, | ||
currentIDstring + idValuePostfix | ||
); | ||
} | ||
} | ||
// don't remove referenced IDs | ||
IDs.delete(key); | ||
} | ||
// remove non-referenced IDs attributes from elements | ||
if (params.remove) { | ||
for(var keyElem of IDs) { | ||
if (!idPreserved(keyElem[0])) { | ||
keyElem[1].removeAttr('id'); | ||
} | ||
} | ||
} | ||
// remove non-referenced IDs attributes from elements | ||
if (params.remove) { | ||
for (var keyElem of IDs) { | ||
if (!idPreserved(keyElem[0])) { | ||
delete keyElem[1].attributes.id; | ||
} | ||
} | ||
return data; | ||
} | ||
return root; | ||
}; | ||
@@ -172,6 +236,6 @@ | ||
function idMatchesPrefix(prefixArray, currentID) { | ||
if (!currentID) return false; | ||
if (!currentID) return false; | ||
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true; | ||
return false; | ||
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true; | ||
return false; | ||
} | ||
@@ -186,20 +250,20 @@ | ||
function generateID(currentID) { | ||
if (!currentID) return [0]; | ||
if (!currentID) return [0]; | ||
currentID[currentID.length - 1]++; | ||
currentID[currentID.length - 1]++; | ||
for(var i = currentID.length - 1; i > 0; i--) { | ||
if (currentID[i] > maxIDindex) { | ||
currentID[i] = 0; | ||
for (var i = currentID.length - 1; i > 0; i--) { | ||
if (currentID[i] > maxIDindex) { | ||
currentID[i] = 0; | ||
if (currentID[i - 1] !== undefined) { | ||
currentID[i - 1]++; | ||
} | ||
} | ||
if (currentID[i - 1] !== undefined) { | ||
currentID[i - 1]++; | ||
} | ||
} | ||
if (currentID[0] > maxIDindex) { | ||
currentID[0] = 0; | ||
currentID.unshift(0); | ||
} | ||
return currentID; | ||
} | ||
if (currentID[0] > maxIDindex) { | ||
currentID[0] = 0; | ||
currentID.unshift(0); | ||
} | ||
return currentID; | ||
} | ||
@@ -214,4 +278,4 @@ | ||
function getIDstring(arr, params) { | ||
var str = params.prefix; | ||
return str + arr.map(i => generateIDchars[i]).join(''); | ||
var str = params.prefix; | ||
return str + arr.map((i) => generateIDchars[i]).join(''); | ||
} |
'use strict'; | ||
const { removeLeadingZero } = require('../lib/svgo/tools.js'); | ||
exports.type = 'perItem'; | ||
@@ -10,18 +12,18 @@ | ||
exports.params = { | ||
floatPrecision: 3, | ||
leadingZero: true, | ||
defaultPx: true, | ||
convertToPx: true | ||
floatPrecision: 3, | ||
leadingZero: true, | ||
defaultPx: true, | ||
convertToPx: true, | ||
}; | ||
var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/, | ||
regSeparator = /\s+,?\s*|,\s*/, | ||
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, | ||
absoluteLengths = { // relative to px | ||
cm: 96/2.54, | ||
mm: 96/25.4, | ||
in: 96, | ||
pt: 4/3, | ||
pc: 16 | ||
}; | ||
const regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/; | ||
const regSeparator = /\s+,?\s*|,\s*/; | ||
const absoluteLengths = { | ||
// relative to px | ||
cm: 96 / 2.54, | ||
mm: 96 / 25.4, | ||
in: 96, | ||
pt: 4 / 3, | ||
pc: 16, | ||
}; | ||
@@ -48,94 +50,93 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type !== 'element') { | ||
return; | ||
} | ||
if (item.attributes.points != null) { | ||
item.attributes.points = roundValues(item.attributes.points); | ||
} | ||
if ( item.hasAttr('points') ) { | ||
roundValues(item.attrs.points); | ||
} | ||
if (item.attributes['enable-background'] != null) { | ||
item.attributes['enable-background'] = roundValues( | ||
item.attributes['enable-background'] | ||
); | ||
} | ||
if ( item.hasAttr('enable-background') ) { | ||
roundValues(item.attrs['enable-background']); | ||
} | ||
if (item.attributes.viewBox != null) { | ||
item.attributes.viewBox = roundValues(item.attributes.viewBox); | ||
} | ||
if ( item.hasAttr('viewBox') ) { | ||
roundValues(item.attrs.viewBox); | ||
} | ||
if (item.attributes['stroke-dasharray'] != null) { | ||
item.attributes['stroke-dasharray'] = roundValues( | ||
item.attributes['stroke-dasharray'] | ||
); | ||
} | ||
if ( item.hasAttr('stroke-dasharray') ) { | ||
roundValues(item.attrs['stroke-dasharray']); | ||
} | ||
if (item.attributes.dx != null) { | ||
item.attributes.dx = roundValues(item.attributes.dx); | ||
} | ||
if ( item.hasAttr('dx') ) { | ||
roundValues(item.attrs.dx); | ||
} | ||
if (item.attributes.dy != null) { | ||
item.attributes.dy = roundValues(item.attributes.dy); | ||
} | ||
if ( item.hasAttr('dy') ) { | ||
roundValues(item.attrs.dy); | ||
} | ||
if (item.attributes.x != null) { | ||
item.attributes.x = roundValues(item.attributes.x); | ||
} | ||
if ( item.hasAttr('x') ) { | ||
roundValues(item.attrs.x); | ||
} | ||
if (item.attributes.y != null) { | ||
item.attributes.y = roundValues(item.attributes.y); | ||
} | ||
if ( item.hasAttr('y') ) { | ||
roundValues(item.attrs.y); | ||
} | ||
function roundValues(lists) { | ||
var num, | ||
units, | ||
match, | ||
matchNew, | ||
listsArr = lists.split(regSeparator), | ||
roundedList = []; | ||
for (const elem of listsArr) { | ||
match = elem.match(regNumericValues); | ||
matchNew = elem.match(/new/); | ||
function roundValues($prop){ | ||
// if attribute value matches regNumericValues | ||
if (match) { | ||
// round it to the fixed precision | ||
(num = +(+match[1]).toFixed(params.floatPrecision)), | ||
(units = match[3] || ''); | ||
var num, units, | ||
match, | ||
matchNew, | ||
lists = $prop.value, | ||
listsArr = lists.split(regSeparator), | ||
roundedListArr = [], | ||
roundedList; | ||
// convert absolute values to pixels | ||
if (params.convertToPx && units && units in absoluteLengths) { | ||
var pxNum = +(absoluteLengths[units] * match[1]).toFixed( | ||
params.floatPrecision | ||
); | ||
listsArr.forEach(function(elem){ | ||
if (String(pxNum).length < match[0].length) | ||
(num = pxNum), (units = 'px'); | ||
} | ||
match = elem.match(regNumericValues); | ||
matchNew = elem.match(/new/); | ||
// and remove leading zero | ||
if (params.leadingZero) { | ||
num = removeLeadingZero(num); | ||
} | ||
// if attribute value matches regNumericValues | ||
if (match) { | ||
// round it to the fixed precision | ||
num = +(+match[1]).toFixed(params.floatPrecision), | ||
units = match[3] || ''; | ||
// remove default 'px' units | ||
if (params.defaultPx && units === 'px') { | ||
units = ''; | ||
} | ||
// convert absolute values to pixels | ||
if (params.convertToPx && units && (units in absoluteLengths)) { | ||
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision); | ||
if (String(pxNum).length < match[0].length) | ||
num = pxNum, | ||
units = 'px'; | ||
} | ||
// and remove leading zero | ||
if (params.leadingZero) { | ||
num = removeLeadingZero(num); | ||
} | ||
// remove default 'px' units | ||
if (params.defaultPx && units === 'px') { | ||
units = ''; | ||
} | ||
roundedListArr.push(num+units); | ||
} | ||
// if attribute value is "new"(only enable-background). | ||
else if (matchNew) { | ||
roundedListArr.push('new'); | ||
} else if (elem) { | ||
roundedListArr.push(elem); | ||
} | ||
}); | ||
roundedList = roundedListArr.join(' '); | ||
$prop.value = roundedList; | ||
roundedList.push(num + units); | ||
} | ||
// if attribute value is "new"(only enable-background). | ||
else if (matchNew) { | ||
roundedList.push('new'); | ||
} else if (elem) { | ||
roundedList.push(elem); | ||
} | ||
} | ||
return roundedList.join(' '); | ||
} | ||
}; |
@@ -7,20 +7,22 @@ 'use strict'; | ||
exports.description = 'rounds numeric values to the fixed precision, removes default ‘px’ units'; | ||
exports.description = | ||
'rounds numeric values to the fixed precision, removes default ‘px’ units'; | ||
exports.params = { | ||
floatPrecision: 3, | ||
leadingZero: true, | ||
defaultPx: true, | ||
convertToPx: true | ||
floatPrecision: 3, | ||
leadingZero: true, | ||
defaultPx: true, | ||
convertToPx: true, | ||
}; | ||
var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/, | ||
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, | ||
absoluteLengths = { // relative to px | ||
cm: 96/2.54, | ||
mm: 96/25.4, | ||
in: 96, | ||
pt: 4/3, | ||
pc: 16 | ||
}; | ||
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, | ||
absoluteLengths = { | ||
// relative to px | ||
cm: 96 / 2.54, | ||
mm: 96 / 25.4, | ||
in: 96, | ||
pt: 4 / 3, | ||
pc: 16, | ||
}; | ||
@@ -37,54 +39,56 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element') { | ||
var floatPrecision = params.floatPrecision; | ||
if (item.isElem()) { | ||
if (item.attributes.viewBox != null) { | ||
var nums = item.attributes.viewBox.split(/\s,?\s*|,\s*/g); | ||
item.attributes.viewBox = nums | ||
.map(function (value) { | ||
var num = +value; | ||
return isNaN(num) ? value : +num.toFixed(floatPrecision); | ||
}) | ||
.join(' '); | ||
} | ||
var floatPrecision = params.floatPrecision; | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
// The `version` attribute is a text string and cannot be rounded | ||
if (name === 'version') { | ||
continue; | ||
} | ||
if (item.hasAttr('viewBox')) { | ||
var nums = item.attr('viewBox').value.split(/\s,?\s*|,\s*/g); | ||
item.attr('viewBox').value = nums.map(function(value) { | ||
var num = +value; | ||
return isNaN(num) ? value : +num.toFixed(floatPrecision); | ||
}).join(' '); | ||
} | ||
var match = value.match(regNumericValues); | ||
item.eachAttr(function(attr) { | ||
// The `version` attribute is a text string and cannot be rounded | ||
if (attr.name === 'version') { return } | ||
// if attribute value matches regNumericValues | ||
if (match) { | ||
// round it to the fixed precision | ||
var num = +(+match[1]).toFixed(floatPrecision), | ||
units = match[3] || ''; | ||
var match = attr.value.match(regNumericValues); | ||
// convert absolute values to pixels | ||
if (params.convertToPx && units && units in absoluteLengths) { | ||
var pxNum = +(absoluteLengths[units] * match[1]).toFixed( | ||
floatPrecision | ||
); | ||
// if attribute value matches regNumericValues | ||
if (match) { | ||
// round it to the fixed precision | ||
var num = +(+match[1]).toFixed(floatPrecision), | ||
units = match[3] || ''; | ||
if (String(pxNum).length < match[0].length) { | ||
num = pxNum; | ||
units = 'px'; | ||
} | ||
} | ||
// convert absolute values to pixels | ||
if (params.convertToPx && units && (units in absoluteLengths)) { | ||
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision); | ||
// and remove leading zero | ||
if (params.leadingZero) { | ||
num = removeLeadingZero(num); | ||
} | ||
if (String(pxNum).length < match[0].length) { | ||
num = pxNum; | ||
units = 'px'; | ||
} | ||
} | ||
// remove default 'px' units | ||
if (params.defaultPx && units === 'px') { | ||
units = ''; | ||
} | ||
// and remove leading zero | ||
if (params.leadingZero) { | ||
num = removeLeadingZero(num); | ||
} | ||
// remove default 'px' units | ||
if (params.defaultPx && units === 'px') { | ||
units = ''; | ||
} | ||
attr.value = num + units; | ||
} | ||
}); | ||
item.attributes[name] = num + units; | ||
} | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { inheritableAttrs, elemsGroups } = require('./_collections'); | ||
exports.type = 'perItemReverse'; | ||
@@ -9,9 +11,12 @@ | ||
var collections = require('./_collections'), | ||
attrsInheritable = collections.inheritableAttrs, | ||
animationElems = collections.elemsGroups.animation; | ||
function hasAnimatedAttr(item) { | ||
return item.isElem(animationElems) && item.hasAttr('attributeName', this) || | ||
!item.isEmpty() && item.content.some(hasAnimatedAttr, this); | ||
function hasAnimatedAttr(item, name) { | ||
if (item.type === 'element') { | ||
return ( | ||
(elemsGroups.animation.includes(item.name) && | ||
item.attributes.attributeName === name) || | ||
(item.children.length !== 0 && | ||
item.children.some((child) => hasAnimatedAttr(child, name))) | ||
); | ||
} | ||
return false; | ||
} | ||
@@ -42,47 +47,59 @@ | ||
*/ | ||
exports.fn = function(item) { | ||
exports.fn = function (item) { | ||
// non-empty elements | ||
if ( | ||
item.type === 'element' && | ||
item.name !== 'switch' && | ||
item.children.length !== 0 | ||
) { | ||
item.children.forEach(function (g, i) { | ||
// non-empty groups | ||
if (g.type === 'element' && g.name === 'g' && g.children.length !== 0) { | ||
// move group attibutes to the single child element | ||
if (Object.keys(g.attributes).length !== 0 && g.children.length === 1) { | ||
var inner = g.children[0]; | ||
// non-empty elements | ||
if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) { | ||
item.content.forEach(function(g, i) { | ||
// non-empty groups | ||
if (g.isElem('g') && !g.isEmpty()) { | ||
// move group attibutes to the single content element | ||
if (g.hasAttr() && g.content.length === 1) { | ||
var inner = g.content[0]; | ||
if ( | ||
inner.type === 'element' && | ||
inner.attributes.id == null && | ||
g.attributes.filter == null && | ||
(g.attributes.class == null || inner.attributes.class == null) && | ||
((g.attributes['clip-path'] == null && g.attributes.mask == null) || | ||
(inner.type === 'element' && | ||
inner.name === 'g' && | ||
g.attributes.transform == null && | ||
inner.attributes.transform == null)) | ||
) { | ||
for (const [name, value] of Object.entries(g.attributes)) { | ||
if (g.children.some((item) => hasAnimatedAttr(item, name))) | ||
return; | ||
if (inner.isElem() && !inner.hasAttr('id') && !g.hasAttr('filter') && | ||
!(g.hasAttr('class') && inner.hasAttr('class')) && ( | ||
!g.hasAttr('clip-path') && !g.hasAttr('mask') || | ||
inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform') | ||
) | ||
) { | ||
g.eachAttr(function(attr) { | ||
if (g.content.some(hasAnimatedAttr, attr.name)) return; | ||
if (inner.attributes[name] == null) { | ||
inner.attributes[name] = value; | ||
} else if (name == 'transform') { | ||
inner.attributes[name] = value + ' ' + inner.attributes[name]; | ||
} else if (inner.attributes[name] === 'inherit') { | ||
inner.attributes[name] = value; | ||
} else if ( | ||
inheritableAttrs.includes(name) === false && | ||
inner.attributes[name] !== value | ||
) { | ||
return; | ||
} | ||
if (!inner.hasAttr(attr.name)) { | ||
inner.addAttr(attr); | ||
} else if (attr.name == 'transform') { | ||
inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value; | ||
} else if (inner.hasAttr(attr.name, 'inherit')) { | ||
inner.attr(attr.name).value = attr.value; | ||
} else if ( | ||
attrsInheritable.indexOf(attr.name) < 0 && | ||
!inner.hasAttr(attr.name, attr.value) | ||
) { | ||
return; | ||
} | ||
delete g.attributes[name]; | ||
} | ||
} | ||
} | ||
g.removeAttr(attr.name); | ||
}); | ||
} | ||
} | ||
// collapse groups without attributes | ||
if (!g.hasAttr() && !g.content.some(function(item) { return item.isElem(animationElems) })) { | ||
item.spliceContent(i, 1, g.content); | ||
} | ||
} | ||
}); | ||
} | ||
// collapse groups without attributes | ||
if ( | ||
Object.keys(g.attributes).length === 0 && | ||
!g.children.some((item) => item.isElem(elemsGroups.animation)) | ||
) { | ||
item.spliceContent(i, 1, g.children); | ||
} | ||
} | ||
}); | ||
} | ||
}; |
@@ -10,15 +10,17 @@ 'use strict'; | ||
exports.params = { | ||
currentColor: false, | ||
names2hex: true, | ||
rgb2hex: true, | ||
shorthex: true, | ||
shortname: true | ||
currentColor: false, | ||
names2hex: true, | ||
rgb2hex: true, | ||
shorthex: true, | ||
shortname: true, | ||
}; | ||
var collections = require('./_collections'), | ||
rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)', | ||
rComma = '\\s*,\\s*', | ||
regRGB = new RegExp('^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'), | ||
regHEX = /^#(([a-fA-F0-9])\2){3}$/, | ||
none = /\bnone\b/i; | ||
rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)', | ||
rComma = '\\s*,\\s*', | ||
regRGB = new RegExp( | ||
'^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$' | ||
), | ||
regHEX = /^#(([a-fA-F0-9])\2){3}$/, | ||
none = /\bnone\b/i; | ||
@@ -51,65 +53,56 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element') { | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
if (collections.colorsProps.includes(name)) { | ||
let val = value; | ||
let match; | ||
if (item.elem) { | ||
// Convert colors to currentColor | ||
if (params.currentColor) { | ||
if (typeof params.currentColor === 'string') { | ||
match = val === params.currentColor; | ||
} else if (params.currentColor.exec) { | ||
match = params.currentColor.exec(val); | ||
} else { | ||
match = !val.match(none); | ||
} | ||
if (match) { | ||
val = 'currentColor'; | ||
} | ||
} | ||
item.eachAttr(function(attr) { | ||
// Convert color name keyword to long hex | ||
if (params.names2hex && val.toLowerCase() in collections.colorsNames) { | ||
val = collections.colorsNames[val.toLowerCase()]; | ||
} | ||
if (collections.colorsProps.indexOf(attr.name) > -1) { | ||
// Convert rgb() to long hex | ||
if (params.rgb2hex && (match = val.match(regRGB))) { | ||
match = match.slice(1, 4).map(function (m) { | ||
if (m.indexOf('%') > -1) m = Math.round(parseFloat(m) * 2.55); | ||
var val = attr.value, | ||
match; | ||
return Math.max(0, Math.min(m, 255)); | ||
}); | ||
// Convert colors to currentColor | ||
if (params.currentColor) { | ||
if (typeof params.currentColor === 'string') { | ||
match = val === params.currentColor; | ||
} else if (params.currentColor.exec) { | ||
match = params.currentColor.exec(val); | ||
} else { | ||
match = !val.match(none); | ||
} | ||
if (match) { | ||
val = 'currentColor'; | ||
} | ||
} | ||
val = rgb2hex(match); | ||
} | ||
// Convert color name keyword to long hex | ||
if (params.names2hex && val.toLowerCase() in collections.colorsNames) { | ||
val = collections.colorsNames[val.toLowerCase()]; | ||
} | ||
// Convert long hex to short hex | ||
if (params.shorthex && (match = val.match(regHEX))) { | ||
val = '#' + match[0][1] + match[0][3] + match[0][5]; | ||
} | ||
// Convert rgb() to long hex | ||
if (params.rgb2hex && (match = val.match(regRGB))) { | ||
match = match.slice(1, 4).map(function(m) { | ||
if (m.indexOf('%') > -1) | ||
m = Math.round(parseFloat(m) * 2.55); | ||
// Convert hex to short name | ||
if (params.shortname) { | ||
var lowerVal = val.toLowerCase(); | ||
if (lowerVal in collections.colorsShortNames) { | ||
val = collections.colorsShortNames[lowerVal]; | ||
} | ||
} | ||
return Math.max(0, Math.min(m, 255)); | ||
}); | ||
val = rgb2hex(match); | ||
} | ||
// Convert long hex to short hex | ||
if (params.shorthex && (match = val.match(regHEX))) { | ||
val = '#' + match[0][1] + match[0][3] + match[0][5]; | ||
} | ||
// Convert hex to short name | ||
if (params.shortname) { | ||
var lowerVal = val.toLowerCase(); | ||
if (lowerVal in collections.colorsShortNames) { | ||
val = collections.colorsShortNames[lowerVal]; | ||
} | ||
} | ||
attr.value = val; | ||
} | ||
}); | ||
item.attributes[name] = val; | ||
} | ||
} | ||
} | ||
}; | ||
@@ -131,3 +124,8 @@ | ||
function rgb2hex(rgb) { | ||
return '#' + ('00000' + (rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16)).slice(-6).toUpperCase(); | ||
return ( | ||
'#' + | ||
('00000' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16)) | ||
.slice(-6) | ||
.toUpperCase() | ||
); | ||
} |
@@ -19,22 +19,19 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('ellipse')) { | ||
var rx = item.hasAttr('rx') && item.attr('rx').value || 0; | ||
var ry = item.hasAttr('ry') && item.attr('ry').value || 0; | ||
exports.fn = function (item) { | ||
if (item.isElem('ellipse')) { | ||
const rx = item.attributes.rx || 0; | ||
const ry = item.attributes.ry || 0; | ||
if (rx === ry || | ||
rx === 'auto' || ry === 'auto' // SVG2 | ||
) { | ||
var radius = rx !== 'auto' ? rx : ry; | ||
item.renameElem('circle'); | ||
item.removeAttr(['rx', 'ry']); | ||
item.addAttr({ | ||
name: 'r', | ||
value: radius, | ||
prefix: '', | ||
local: 'r', | ||
}); | ||
} | ||
if ( | ||
rx === ry || | ||
rx === 'auto' || | ||
ry === 'auto' // SVG2 | ||
) { | ||
var radius = rx !== 'auto' ? rx : ry; | ||
item.renameElem('circle'); | ||
delete item.attributes.rx; | ||
delete item.attributes.ry; | ||
item.attributes.r = radius; | ||
} | ||
} | ||
return; | ||
}; |
@@ -5,3 +5,4 @@ 'use strict'; | ||
const { pathElems } = require('./_collections.js'); | ||
const { path2js, js2path, applyTransforms } = require('./_path.js'); | ||
const { path2js, js2path } = require('./_path.js'); | ||
const { applyTransforms } = require('./_applyTransforms.js'); | ||
const { cleanupOutData } = require('../lib/svgo/tools'); | ||
@@ -60,3 +61,7 @@ | ||
exports.fn = function (item, params) { | ||
if (item.isElem(pathElems) && item.hasAttr('d')) { | ||
if ( | ||
item.type === 'element' && | ||
pathElems.includes(item.name) && | ||
item.attributes.d != null | ||
) { | ||
const computedStyle = computeStyle(item); | ||
@@ -87,8 +92,8 @@ precision = params.floatPrecision; | ||
if (data.length) { | ||
convertToRelative(data); | ||
if (params.applyTransforms) { | ||
data = applyTransforms(item, data, params); | ||
applyTransforms(item, data, params); | ||
} | ||
convertToRelative(data); | ||
data = filters(data, params, { | ||
@@ -115,139 +120,166 @@ maybeHasStrokeAndLinecap, | ||
*/ | ||
function convertToRelative(path) { | ||
var point = [0, 0], | ||
subpathPoint = [0, 0], | ||
baseItem; | ||
const convertToRelative = (pathData) => { | ||
let start = [0, 0]; | ||
let cursor = [0, 0]; | ||
let prevCoords = [0, 0]; | ||
path.forEach(function (item, index) { | ||
var instruction = item.instruction, | ||
data = item.data; | ||
for (let i = 0; i < pathData.length; i += 1) { | ||
const pathItem = pathData[i]; | ||
let { instruction: command, data: args } = pathItem; | ||
// 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]; | ||
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]; | ||
} | ||
// convert absolute path data coordinates to relative | ||
// if "M" was not transformed from "m" | ||
// moveto (x y) | ||
if (command === 'm') { | ||
// update start and cursor | ||
cursor[0] += args[0]; | ||
cursor[1] += args[1]; | ||
start[0] = cursor[0]; | ||
start[1] = cursor[1]; | ||
} | ||
if (command === 'M') { | ||
// M → m | ||
if (instruction === 'M') { | ||
if (index > 0) instruction = 'm'; | ||
data[0] -= point[0]; | ||
data[1] -= point[1]; | ||
subpathPoint[0] = point[0] += data[0]; | ||
subpathPoint[1] = point[1] += data[1]; | ||
baseItem = item; | ||
// skip first moveto | ||
if (i !== 0) { | ||
command = 'm'; | ||
} | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
// update start and cursor | ||
cursor[0] += args[0]; | ||
cursor[1] += args[1]; | ||
start[0] = cursor[0]; | ||
start[1] = cursor[1]; | ||
} | ||
// lineto (x y) | ||
if (command === 'l') { | ||
cursor[0] += args[0]; | ||
cursor[1] += args[1]; | ||
} | ||
if (command === 'L') { | ||
// L → l | ||
// T → t | ||
else if ('LT'.indexOf(instruction) > -1) { | ||
instruction = instruction.toLowerCase(); | ||
command = 'l'; | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
cursor[0] += args[0]; | ||
cursor[1] += args[1]; | ||
} | ||
// x y | ||
// 0 1 | ||
data[0] -= point[0]; | ||
data[1] -= point[1]; | ||
// horizontal lineto (x) | ||
if (command === 'h') { | ||
cursor[0] += args[0]; | ||
} | ||
if (command === 'H') { | ||
// H → h | ||
command = 'h'; | ||
args[0] -= cursor[0]; | ||
cursor[0] += args[0]; | ||
} | ||
point[0] += data[0]; | ||
point[1] += data[1]; | ||
// vertical lineto (y) | ||
if (command === 'v') { | ||
cursor[1] += args[0]; | ||
} | ||
if (command === 'V') { | ||
// V → v | ||
command = 'v'; | ||
args[0] -= cursor[1]; | ||
cursor[1] += args[0]; | ||
} | ||
// C → c | ||
} else if (instruction === 'C') { | ||
instruction = 'c'; | ||
// curveto (x1 y1 x2 y2 x y) | ||
if (command === 'c') { | ||
cursor[0] += args[4]; | ||
cursor[1] += args[5]; | ||
} | ||
if (command === 'C') { | ||
// C → c | ||
command = 'c'; | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
args[2] -= cursor[0]; | ||
args[3] -= cursor[1]; | ||
args[4] -= cursor[0]; | ||
args[5] -= cursor[1]; | ||
cursor[0] += args[4]; | ||
cursor[1] += args[5]; | ||
} | ||
// 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]; | ||
// smooth curveto (x2 y2 x y) | ||
if (command === 's') { | ||
cursor[0] += args[2]; | ||
cursor[1] += args[3]; | ||
} | ||
if (command === 'S') { | ||
// S → s | ||
command = 's'; | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
args[2] -= cursor[0]; | ||
args[3] -= cursor[1]; | ||
cursor[0] += args[2]; | ||
cursor[1] += args[3]; | ||
} | ||
point[0] += data[4]; | ||
point[1] += data[5]; | ||
// quadratic Bézier curveto (x1 y1 x y) | ||
if (command === 'q') { | ||
cursor[0] += args[2]; | ||
cursor[1] += args[3]; | ||
} | ||
if (command === 'Q') { | ||
// Q → q | ||
command = 'q'; | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
args[2] -= cursor[0]; | ||
args[3] -= cursor[1]; | ||
cursor[0] += args[2]; | ||
cursor[1] += args[3]; | ||
} | ||
// S → s | ||
// Q → q | ||
} else if ('SQ'.indexOf(instruction) > -1) { | ||
instruction = instruction.toLowerCase(); | ||
// smooth quadratic Bézier curveto (x y) | ||
if (command === 't') { | ||
cursor[0] += args[0]; | ||
cursor[1] += args[1]; | ||
} | ||
if (command === 'T') { | ||
// T → t | ||
command = 't'; | ||
args[0] -= cursor[0]; | ||
args[1] -= cursor[1]; | ||
cursor[0] += args[0]; | ||
cursor[1] += args[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]; | ||
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); | ||
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y) | ||
if (command === 'a') { | ||
cursor[0] += args[5]; | ||
cursor[1] += args[6]; | ||
} | ||
if (command === 'A') { | ||
// A → a | ||
command = 'a'; | ||
args[5] -= cursor[0]; | ||
args[6] -= cursor[1]; | ||
cursor[0] += args[5]; | ||
cursor[1] += args[6]; | ||
} | ||
// !data === z, reset current point | ||
else if (instruction == 'z') { | ||
if (baseItem) { | ||
item.coords = baseItem.coords; | ||
} | ||
point[0] = subpathPoint[0]; | ||
point[1] = subpathPoint[1]; | ||
// closepath | ||
if (command === 'Z' || command === 'z') { | ||
// reset cursor | ||
cursor[0] = start[0]; | ||
cursor[1] = start[1]; | ||
} | ||
item.base = index > 0 ? path[index - 1].coords : [0, 0]; | ||
}); | ||
pathItem.instruction = command; | ||
pathItem.data = args; | ||
// store absolute coordinates for later use | ||
// base should preserve reference from other element | ||
pathItem.base = prevCoords; | ||
pathItem.coords = [cursor[0], cursor[1]]; | ||
prevCoords = pathItem.coords; | ||
} | ||
return path; | ||
} | ||
return pathData; | ||
}; | ||
@@ -254,0 +286,0 @@ /** |
@@ -16,3 +16,2 @@ 'use strict'; | ||
const none = { value: 0 }; | ||
const regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g; | ||
@@ -39,11 +38,11 @@ | ||
item.isElem('rect') && | ||
item.hasAttr('width') && | ||
item.hasAttr('height') && | ||
!item.hasAttr('rx') && | ||
!item.hasAttr('ry') | ||
item.attributes.width != null && | ||
item.attributes.height != null && | ||
item.attributes.rx == null && | ||
item.attributes.ry == null | ||
) { | ||
var x = +(item.attr('x') || none).value, | ||
y = +(item.attr('y') || none).value, | ||
width = +item.attr('width').value, | ||
height = +item.attr('height').value; | ||
const x = Number(item.attributes.x || '0'); | ||
const y = Number(item.attributes.y || '0'); | ||
const width = Number(item.attributes.width); | ||
const height = Number(item.attributes.height); | ||
// Values like '100%' compute to NaN, thus running after | ||
@@ -60,16 +59,15 @@ // cleanupNumericValues when 'px' units has already been removed. | ||
]; | ||
item.addAttr({ | ||
name: 'd', | ||
value: stringifyPathData({ pathData, precision }), | ||
prefix: '', | ||
local: 'd', | ||
}); | ||
item.renameElem('path').removeAttr(['x', 'y', 'width', 'height']); | ||
item.attributes.d = stringifyPathData({ pathData, precision }); | ||
item.renameElem('path'); | ||
delete item.attributes.x; | ||
delete item.attributes.y; | ||
delete item.attributes.width; | ||
delete item.attributes.height; | ||
} | ||
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; | ||
const x1 = Number(item.attributes.x1 || '0'); | ||
const y1 = Number(item.attributes.y1 || '0'); | ||
const x2 = Number(item.attributes.x2 || '0'); | ||
const y2 = Number(item.attributes.y2 || '0'); | ||
if (isNaN(x1 - y1 + x2 - y2)) return; | ||
@@ -80,9 +78,8 @@ const pathData = [ | ||
]; | ||
item.addAttr({ | ||
name: 'd', | ||
value: stringifyPathData({ pathData, precision }), | ||
prefix: '', | ||
local: 'd', | ||
}); | ||
item.renameElem('path').removeAttr(['x1', 'y1', 'x2', 'y2']); | ||
item.attributes.d = stringifyPathData({ pathData, precision }); | ||
item.renameElem('path'); | ||
delete item.attributes.x1; | ||
delete item.attributes.y1; | ||
delete item.attributes.x2; | ||
delete item.attributes.y2; | ||
} | ||
@@ -92,5 +89,5 @@ | ||
(item.isElem('polyline') || item.isElem('polygon')) && | ||
item.hasAttr('points') | ||
item.attributes.points != null | ||
) { | ||
var coords = (item.attr('points').value.match(regNumber) || []).map(Number); | ||
const coords = (item.attributes.points.match(regNumber) || []).map(Number); | ||
if (coords.length < 4) return false; | ||
@@ -107,15 +104,11 @@ const pathData = []; | ||
} | ||
item.addAttr({ | ||
name: 'd', | ||
value: stringifyPathData({ pathData, precision }), | ||
prefix: '', | ||
local: 'd', | ||
}); | ||
item.renameElem('path').removeAttr('points'); | ||
item.attributes.d = stringifyPathData({ pathData, precision }); | ||
item.renameElem('path'); | ||
delete item.attributes.points; | ||
} | ||
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; | ||
const cx = Number(item.attributes.cx || '0'); | ||
const cy = Number(item.attributes.cy || '0'); | ||
const r = Number(item.attributes.r || '0'); | ||
if (isNaN(cx - cy + r)) { | ||
@@ -130,16 +123,14 @@ return; | ||
]; | ||
item.addAttr({ | ||
name: 'd', | ||
value: stringifyPathData({ pathData, precision }), | ||
prefix: '', | ||
local: 'd', | ||
}); | ||
item.renameElem('path').removeAttr(['cx', 'cy', 'r']); | ||
item.attributes.d = stringifyPathData({ pathData, precision }); | ||
item.renameElem('path'); | ||
delete item.attributes.cx; | ||
delete item.attributes.cy; | ||
delete item.attributes.r; | ||
} | ||
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; | ||
const ecx = Number(item.attributes.cx || '0'); | ||
const ecy = Number(item.attributes.cy || '0'); | ||
const rx = Number(item.attributes.rx || '0'); | ||
const ry = Number(item.attributes.ry || '0'); | ||
if (isNaN(ecx - ecy + rx - ry)) { | ||
@@ -154,10 +145,9 @@ return; | ||
]; | ||
item.addAttr({ | ||
name: 'd', | ||
value: stringifyPathData({ pathData, precision }), | ||
prefix: '', | ||
local: 'd', | ||
}); | ||
item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']); | ||
item.attributes.d = stringifyPathData({ pathData, precision }); | ||
item.renameElem('path'); | ||
delete item.attributes.cx; | ||
delete item.attributes.cy; | ||
delete item.attributes.rx; | ||
delete item.attributes.ry; | ||
} | ||
}; |
@@ -10,31 +10,43 @@ 'use strict'; | ||
exports.params = { | ||
keepImportant: false | ||
keepImportant: false, | ||
}; | ||
var stylingProps = require('./_collections').attrsGroups.presentation, | ||
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space. | ||
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’ | ||
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth' | ||
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth" | ||
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'), | ||
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space. | ||
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’ | ||
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth' | ||
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth" | ||
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'), | ||
// Parentheses, E.g.: url(data:image/png;base64,iVBO...). | ||
// ':' and ';' inside of it should be threated as is. (Just like in strings.) | ||
rParenthesis = | ||
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)', | ||
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input. | ||
rValue = | ||
'\\s*(' + | ||
g( | ||
'[^!\'"();\\\\]+?', | ||
rEscape, | ||
rSingleQuotes, | ||
rQuotes, | ||
rParenthesis, | ||
'[^;]*?' | ||
) + | ||
'*?' + | ||
')', | ||
// End of declaration. Spaces outside of capturing groups help to do natural trimming. | ||
rDeclEnd = '\\s*(?:;\\s*|$)', | ||
// Important rule | ||
rImportant = '(\\s*!important(?![-(\\w]))?', | ||
// Final RegExp to parse CSS declarations. | ||
regDeclarationBlock = new RegExp( | ||
rAttr + ':' + rValue + rImportant + rDeclEnd, | ||
'ig' | ||
), | ||
// Comments expression. Honors escape sequences and strings. | ||
regStripComments = new RegExp( | ||
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), | ||
'ig' | ||
); | ||
// Parentheses, E.g.: url(data:image/png;base64,iVBO...). | ||
// ':' and ';' inside of it should be threated as is. (Just like in strings.) | ||
rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)', | ||
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input. | ||
rValue = '\\s*(' + g('[^!\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')', | ||
// End of declaration. Spaces outside of capturing groups help to do natural trimming. | ||
rDeclEnd = '\\s*(?:;\\s*|$)', | ||
// Important rule | ||
rImportant = '(\\s*!important(?![-(\\w]))?', | ||
// Final RegExp to parse CSS declarations. | ||
regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rImportant + rDeclEnd, 'ig'), | ||
// Comments expression. Honors escape sequences and strings. | ||
regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig'); | ||
/** | ||
@@ -58,69 +70,63 @@ * Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect. | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element' && item.attributes.style != null) { | ||
// ['opacity: 1', 'color: #000'] | ||
let styles = []; | ||
const newAttributes = {}; | ||
if (item.elem && item.hasAttr('style')) { | ||
// ['opacity: 1', 'color: #000'] | ||
var styleValue = item.attr('style').value, | ||
styles = [], | ||
attrs = {}; | ||
// Strip CSS comments preserving escape sequences and strings. | ||
const styleValue = item.attributes.style.replace( | ||
regStripComments, | ||
(match) => { | ||
return match[0] == '/' | ||
? '' | ||
: match[0] == '\\' && /[-g-z]/i.test(match[1]) | ||
? match[1] | ||
: match; | ||
} | ||
); | ||
// Strip CSS comments preserving escape sequences and strings. | ||
styleValue = styleValue.replace(regStripComments, function(match) { | ||
return match[0] == '/' ? '' : | ||
match[0] == '\\' && /[-g-z]/i.test(match[1]) ? match[1] : match; | ||
}); | ||
regDeclarationBlock.lastIndex = 0; | ||
// eslint-disable-next-line no-cond-assign | ||
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) { | ||
if (!params.keepImportant || !rule[3]) { | ||
styles.push([rule[1], rule[2]]); | ||
} | ||
} | ||
regDeclarationBlock.lastIndex = 0; | ||
// eslint-disable-next-line no-cond-assign | ||
for (var rule; rule = regDeclarationBlock.exec(styleValue);) { | ||
if (!params.keepImportant || !rule[3]) { | ||
styles.push([rule[1], rule[2]]); | ||
} | ||
} | ||
if (styles.length) { | ||
styles = styles.filter(function (style) { | ||
if (style[0]) { | ||
var prop = style[0].toLowerCase(), | ||
val = style[1]; | ||
if (styles.length) { | ||
if (rQuotedString.test(val)) { | ||
val = val.slice(1, -1); | ||
} | ||
styles = styles.filter(function(style) { | ||
if (style[0]) { | ||
var prop = style[0].toLowerCase(), | ||
val = style[1]; | ||
if (stylingProps.includes(prop)) { | ||
newAttributes[prop] = val; | ||
if (rQuotedString.test(val)) { | ||
val = val.slice(1, -1); | ||
} | ||
return false; | ||
} | ||
} | ||
if (stylingProps.indexOf(prop) > -1) { | ||
return true; | ||
}); | ||
attrs[prop] = { | ||
name: prop, | ||
value: val, | ||
local: prop, | ||
prefix: '' | ||
}; | ||
Object.assign(item.attributes, newAttributes); | ||
return false; | ||
} | ||
} | ||
return true; | ||
}); | ||
Object.assign(item.attrs, attrs); | ||
if (styles.length) { | ||
item.attr('style').value = styles | ||
.map(function(declaration) { return declaration.join(':') }) | ||
.join(';'); | ||
} else { | ||
item.removeAttr('style'); | ||
} | ||
} | ||
if (styles.length) { | ||
item.attributes.style = styles | ||
.map((declaration) => declaration.join(':')) | ||
.join(';'); | ||
} else { | ||
delete item.attributes.style; | ||
} | ||
} | ||
} | ||
}; | ||
function g() { | ||
return '(?:' + Array.prototype.join.call(arguments, '|') + ')'; | ||
return '(?:' + Array.prototype.join.call(arguments, '|') + ')'; | ||
} |
@@ -10,23 +10,23 @@ 'use strict'; | ||
exports.params = { | ||
convertToShorts: true, | ||
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default | ||
floatPrecision: 3, | ||
transformPrecision: 5, | ||
matrixToTransform: true, | ||
shortTranslate: true, | ||
shortScale: true, | ||
shortRotate: true, | ||
removeUseless: true, | ||
collapseIntoOne: true, | ||
leadingZero: true, | ||
negativeExtraSpace: false | ||
convertToShorts: true, | ||
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default | ||
floatPrecision: 3, | ||
transformPrecision: 5, | ||
matrixToTransform: true, | ||
shortTranslate: true, | ||
shortScale: true, | ||
shortRotate: true, | ||
removeUseless: true, | ||
collapseIntoOne: true, | ||
leadingZero: true, | ||
negativeExtraSpace: false, | ||
}; | ||
var cleanupOutData = require('../lib/svgo/tools').cleanupOutData, | ||
transform2js = require('./_transforms.js').transform2js, | ||
transformsMultiply = require('./_transforms.js').transformsMultiply, | ||
matrixToTransform = require('./_transforms.js').matrixToTransform, | ||
degRound, | ||
floatRound, | ||
transformRound; | ||
transform2js = require('./_transforms.js').transform2js, | ||
transformsMultiply = require('./_transforms.js').transformsMultiply, | ||
matrixToTransform = require('./_transforms.js').matrixToTransform, | ||
degRound, | ||
floatRound, | ||
transformRound; | ||
@@ -47,23 +47,19 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element') { | ||
// transform | ||
if (item.attributes.transform != null) { | ||
convertTransform(item, 'transform', params); | ||
} | ||
if (item.elem) { | ||
// gradientTransform | ||
if (item.attributes.gradientTransform != null) { | ||
convertTransform(item, 'gradientTransform', params); | ||
} | ||
// transform | ||
if (item.hasAttr('transform')) { | ||
convertTransform(item, 'transform', params); | ||
} | ||
// gradientTransform | ||
if (item.hasAttr('gradientTransform')) { | ||
convertTransform(item, 'gradientTransform', params); | ||
} | ||
// patternTransform | ||
if (item.hasAttr('patternTransform')) { | ||
convertTransform(item, 'patternTransform', params); | ||
} | ||
// patternTransform | ||
if (item.attributes.patternTransform != null) { | ||
convertTransform(item, 'patternTransform', params); | ||
} | ||
} | ||
}; | ||
@@ -79,24 +75,24 @@ | ||
function convertTransform(item, attrName, params) { | ||
var data = transform2js(item.attr(attrName).value); | ||
params = definePrecision(data, params); | ||
let data = transform2js(item.attributes[attrName]); | ||
params = definePrecision(data, params); | ||
if (params.collapseIntoOne && data.length > 1) { | ||
data = [transformsMultiply(data)]; | ||
} | ||
if (params.collapseIntoOne && data.length > 1) { | ||
data = [transformsMultiply(data)]; | ||
} | ||
if (params.convertToShorts) { | ||
data = convertToShorts(data, params); | ||
} else { | ||
data.forEach(roundTransform); | ||
} | ||
if (params.convertToShorts) { | ||
data = convertToShorts(data, params); | ||
} else { | ||
data.forEach(roundTransform); | ||
} | ||
if (params.removeUseless) { | ||
data = removeUseless(data); | ||
} | ||
if (params.removeUseless) { | ||
data = removeUseless(data); | ||
} | ||
if (data.length) { | ||
item.attr(attrName).value = js2transform(data, params); | ||
} else { | ||
item.removeAttr(attrName); | ||
} | ||
if (data.length) { | ||
item.attributes[attrName] = js2transform(data, params); | ||
} else { | ||
delete item.attributes[attrName]; | ||
} | ||
} | ||
@@ -116,33 +112,45 @@ | ||
function definePrecision(data, params) { | ||
var matrixData = data.reduce(getMatrixData, []), | ||
significantDigits = params.transformPrecision; | ||
var matrixData = data.reduce(getMatrixData, []), | ||
significantDigits = params.transformPrecision; | ||
// Clone params so it don't affect other elements transformations. | ||
params = Object.assign({}, params); | ||
// Clone params so it don't affect other elements transformations. | ||
params = Object.assign({}, params); | ||
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value. | ||
if (matrixData.length) { | ||
params.transformPrecision = Math.min(params.transformPrecision, | ||
Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision); | ||
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value. | ||
if (matrixData.length) { | ||
params.transformPrecision = Math.min( | ||
params.transformPrecision, | ||
Math.max.apply(Math, matrixData.map(floatDigits)) || | ||
params.transformPrecision | ||
); | ||
significantDigits = Math.max.apply(Math, matrixData.map(function(n) { | ||
return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5 | ||
})); | ||
} | ||
// No sense in angle precision more then number of significant digits in matrix. | ||
if (!('degPrecision' in params)) { | ||
params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2)); | ||
} | ||
significantDigits = Math.max.apply( | ||
Math, | ||
matrixData.map(function (n) { | ||
return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5 | ||
}) | ||
); | ||
} | ||
// No sense in angle precision more then number of significant digits in matrix. | ||
if (!('degPrecision' in params)) { | ||
params.degPrecision = Math.max( | ||
0, | ||
Math.min(params.floatPrecision, significantDigits - 2) | ||
); | ||
} | ||
floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ? | ||
smartRound.bind(this, params.floatPrecision) : | ||
round; | ||
degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ? | ||
smartRound.bind(this, params.degPrecision) : | ||
round; | ||
transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ? | ||
smartRound.bind(this, params.transformPrecision) : | ||
round; | ||
floatRound = | ||
params.floatPrecision >= 1 && params.floatPrecision < 20 | ||
? smartRound.bind(this, params.floatPrecision) | ||
: round; | ||
degRound = | ||
params.degPrecision >= 1 && params.floatPrecision < 20 | ||
? smartRound.bind(this, params.degPrecision) | ||
: round; | ||
transformRound = | ||
params.transformPrecision >= 1 && params.floatPrecision < 20 | ||
? smartRound.bind(this, params.transformPrecision) | ||
: round; | ||
return params; | ||
return params; | ||
} | ||
@@ -158,3 +166,3 @@ | ||
function getMatrixData(a, b) { | ||
return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a; | ||
return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a; | ||
} | ||
@@ -166,3 +174,3 @@ | ||
function floatDigits(n) { | ||
return (n = String(n)).slice(n.indexOf('.')).length - 1; | ||
return (n = String(n)).slice(n.indexOf('.')).length - 1; | ||
} | ||
@@ -178,75 +186,70 @@ | ||
function convertToShorts(transforms, params) { | ||
for (var i = 0; i < transforms.length; i++) { | ||
var transform = transforms[i]; | ||
for(var i = 0; i < transforms.length; i++) { | ||
// convert matrix to the short aliases | ||
if (params.matrixToTransform && transform.name === 'matrix') { | ||
var decomposed = matrixToTransform(transform, params); | ||
if ( | ||
decomposed != transform && | ||
js2transform(decomposed, params).length <= | ||
js2transform([transform], params).length | ||
) { | ||
transforms.splice.apply(transforms, [i, 1].concat(decomposed)); | ||
} | ||
transform = transforms[i]; | ||
} | ||
var transform = transforms[i]; | ||
// fixed-point numbers | ||
// 12.754997 → 12.755 | ||
roundTransform(transform); | ||
// convert matrix to the short aliases | ||
if ( | ||
params.matrixToTransform && | ||
transform.name === 'matrix' | ||
) { | ||
var decomposed = matrixToTransform(transform, params); | ||
if (decomposed != transform && | ||
js2transform(decomposed, params).length <= js2transform([transform], params).length) { | ||
// convert long translate transform notation to the shorts one | ||
// translate(10 0) → translate(10) | ||
if ( | ||
params.shortTranslate && | ||
transform.name === 'translate' && | ||
transform.data.length === 2 && | ||
!transform.data[1] | ||
) { | ||
transform.data.pop(); | ||
} | ||
transforms.splice.apply(transforms, [i, 1].concat(decomposed)); | ||
} | ||
transform = transforms[i]; | ||
} | ||
// convert long scale transform notation to the shorts one | ||
// scale(2 2) → scale(2) | ||
if ( | ||
params.shortScale && | ||
transform.name === 'scale' && | ||
transform.data.length === 2 && | ||
transform.data[0] === transform.data[1] | ||
) { | ||
transform.data.pop(); | ||
} | ||
// fixed-point numbers | ||
// 12.754997 → 12.755 | ||
roundTransform(transform); | ||
// convert long rotate transform notation to the short one | ||
// translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy) | ||
if ( | ||
params.shortRotate && | ||
transforms[i - 2] && | ||
transforms[i - 2].name === 'translate' && | ||
transforms[i - 1].name === 'rotate' && | ||
transforms[i].name === 'translate' && | ||
transforms[i - 2].data[0] === -transforms[i].data[0] && | ||
transforms[i - 2].data[1] === -transforms[i].data[1] | ||
) { | ||
transforms.splice(i - 2, 3, { | ||
name: 'rotate', | ||
data: [ | ||
transforms[i - 1].data[0], | ||
transforms[i - 2].data[0], | ||
transforms[i - 2].data[1], | ||
], | ||
}); | ||
// convert long translate transform notation to the shorts one | ||
// translate(10 0) → translate(10) | ||
if ( | ||
params.shortTranslate && | ||
transform.name === 'translate' && | ||
transform.data.length === 2 && | ||
!transform.data[1] | ||
) { | ||
transform.data.pop(); | ||
} | ||
// convert long scale transform notation to the shorts one | ||
// scale(2 2) → scale(2) | ||
if ( | ||
params.shortScale && | ||
transform.name === 'scale' && | ||
transform.data.length === 2 && | ||
transform.data[0] === transform.data[1] | ||
) { | ||
transform.data.pop(); | ||
} | ||
// convert long rotate transform notation to the short one | ||
// translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy) | ||
if ( | ||
params.shortRotate && | ||
transforms[i - 2] && | ||
transforms[i - 2].name === 'translate' && | ||
transforms[i - 1].name === 'rotate' && | ||
transforms[i].name === 'translate' && | ||
transforms[i - 2].data[0] === -transforms[i].data[0] && | ||
transforms[i - 2].data[1] === -transforms[i].data[1] | ||
) { | ||
transforms.splice(i - 2, 3, { | ||
name: 'rotate', | ||
data: [ | ||
transforms[i - 1].data[0], | ||
transforms[i - 2].data[0], | ||
transforms[i - 2].data[1] | ||
] | ||
}); | ||
// splice compensation | ||
i -= 2; | ||
} | ||
// splice compensation | ||
i -= 2; | ||
} | ||
} | ||
return transforms; | ||
return transforms; | ||
} | ||
@@ -261,34 +264,32 @@ | ||
function removeUseless(transforms) { | ||
return transforms.filter(function (transform) { | ||
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0) | ||
if ( | ||
(['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 && | ||
(transform.data.length == 1 || transform.name == 'rotate') && | ||
!transform.data[0]) || | ||
// translate(0, 0) | ||
(transform.name == 'translate' && | ||
!transform.data[0] && | ||
!transform.data[1]) || | ||
// scale(1) | ||
(transform.name == 'scale' && | ||
transform.data[0] == 1 && | ||
(transform.data.length < 2 || transform.data[1] == 1)) || | ||
// matrix(1 0 0 1 0 0) | ||
(transform.name == 'matrix' && | ||
transform.data[0] == 1 && | ||
transform.data[3] == 1 && | ||
!( | ||
transform.data[1] || | ||
transform.data[2] || | ||
transform.data[4] || | ||
transform.data[5] | ||
)) | ||
) { | ||
return false; | ||
} | ||
return transforms.filter(function(transform) { | ||
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0) | ||
if ( | ||
['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 && | ||
(transform.data.length == 1 || transform.name == 'rotate') && | ||
!transform.data[0] || | ||
// translate(0, 0) | ||
transform.name == 'translate' && | ||
!transform.data[0] && | ||
!transform.data[1] || | ||
// scale(1) | ||
transform.name == 'scale' && | ||
transform.data[0] == 1 && | ||
(transform.data.length < 2 || transform.data[1] == 1) || | ||
// matrix(1 0 0 1 0 0) | ||
transform.name == 'matrix' && | ||
transform.data[0] == 1 && | ||
transform.data[3] == 1 && | ||
!(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5]) | ||
) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return true; | ||
}); | ||
} | ||
@@ -304,35 +305,42 @@ | ||
function js2transform(transformJS, params) { | ||
var transformString = ''; | ||
var transformString = ''; | ||
// collect output value string | ||
transformJS.forEach(function (transform) { | ||
roundTransform(transform); | ||
transformString += | ||
(transformString && ' ') + | ||
transform.name + | ||
'(' + | ||
cleanupOutData(transform.data, params) + | ||
')'; | ||
}); | ||
// collect output value string | ||
transformJS.forEach(function(transform) { | ||
roundTransform(transform); | ||
transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')'; | ||
}); | ||
return transformString; | ||
return transformString; | ||
} | ||
function roundTransform(transform) { | ||
switch (transform.name) { | ||
case 'translate': | ||
transform.data = floatRound(transform.data); | ||
break; | ||
case 'rotate': | ||
transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1))); | ||
break; | ||
case 'skewX': | ||
case 'skewY': | ||
transform.data = degRound(transform.data); | ||
break; | ||
case 'scale': | ||
transform.data = transformRound(transform.data); | ||
break; | ||
case 'matrix': | ||
transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4))); | ||
break; | ||
} | ||
return transform; | ||
switch (transform.name) { | ||
case 'translate': | ||
transform.data = floatRound(transform.data); | ||
break; | ||
case 'rotate': | ||
transform.data = degRound(transform.data.slice(0, 1)).concat( | ||
floatRound(transform.data.slice(1)) | ||
); | ||
break; | ||
case 'skewX': | ||
case 'skewY': | ||
transform.data = degRound(transform.data); | ||
break; | ||
case 'scale': | ||
transform.data = transformRound(transform.data); | ||
break; | ||
case 'matrix': | ||
transform.data = transformRound(transform.data.slice(0, 4)).concat( | ||
floatRound(transform.data.slice(4)) | ||
); | ||
break; | ||
} | ||
return transform; | ||
} | ||
@@ -347,3 +355,3 @@ | ||
function round(data) { | ||
return data.map(Math.round); | ||
return data.map(Math.round); | ||
} | ||
@@ -361,11 +369,17 @@ | ||
function smartRound(precision, data) { | ||
for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) { | ||
if (data[i].toFixed(precision) != data[i]) { | ||
var rounded = +data[i].toFixed(precision - 1); | ||
data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ? | ||
+data[i].toFixed(precision) : | ||
rounded; | ||
} | ||
for ( | ||
var i = data.length, | ||
tolerance = +Math.pow(0.1, precision).toFixed(precision); | ||
i--; | ||
) { | ||
if (data[i].toFixed(precision) != data[i]) { | ||
var rounded = +data[i].toFixed(precision - 1); | ||
data[i] = | ||
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance | ||
? +data[i].toFixed(precision) | ||
: rounded; | ||
} | ||
return data; | ||
} | ||
return data; | ||
} |
'use strict'; | ||
const csstree = require('css-tree'); | ||
const { querySelectorAll, closestByName } = require('../lib/xast.js'); | ||
const cssTools = require('../lib/css-tools'); | ||
exports.type = 'full'; | ||
@@ -16,5 +20,2 @@ | ||
var csstree = require('css-tree'), | ||
cssTools = require('../lib/css-tools'); | ||
/** | ||
@@ -39,3 +40,3 @@ * Moves + merges styles from style elements to element styles | ||
* | ||
* @param {Object} document document element | ||
* @param {Object} root document element | ||
* @param {Object} opts plugin params | ||
@@ -45,9 +46,9 @@ * | ||
*/ | ||
exports.fn = function (document, opts) { | ||
exports.fn = function (root, opts) { | ||
// collect <style/>s | ||
var styleEls = document.querySelectorAll('style'); | ||
var styleEls = querySelectorAll(root, 'style'); | ||
//no <styles/>s, nothing to do | ||
if (styleEls === null) { | ||
return document; | ||
if (styleEls.length === 0) { | ||
return root; | ||
} | ||
@@ -61,5 +62,5 @@ | ||
if ( | ||
styleEl.hasAttr('type') && | ||
styleEl.attr('type').value !== '' && | ||
styleEl.attr('type').value !== 'text/css' | ||
styleEl.attributes.type != null && | ||
styleEl.attributes.type !== '' && | ||
styleEl.attributes.type !== 'text/css' | ||
) { | ||
@@ -69,3 +70,6 @@ continue; | ||
// skip empty <style/>s or <foreignObject> content. | ||
if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) { | ||
if ( | ||
styleEl.children.length === 0 || | ||
closestByName(styleEl, 'foreignObject') | ||
) { | ||
continue; | ||
@@ -116,3 +120,3 @@ } | ||
try { | ||
selectedEls = document.querySelectorAll(selectorStr); | ||
selectedEls = querySelectorAll(root, selectorStr); | ||
} catch (selectError) { | ||
@@ -123,3 +127,3 @@ // console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError); | ||
if (selectedEls === null) { | ||
if (selectedEls.length === 0) { | ||
// nothing selected | ||
@@ -191,3 +195,3 @@ continue; | ||
if (!opts.removeMatchedSelectors) { | ||
return document; // no further processing required | ||
return root; // no further processing required | ||
} | ||
@@ -218,3 +222,3 @@ | ||
if (typeof selectedEl.class.item(0) === 'undefined') { | ||
selectedEl.removeAttr('class'); | ||
delete selectedEl.attributes.class; | ||
} | ||
@@ -224,3 +228,5 @@ | ||
if (firstSubSelector.type === 'IdSelector') { | ||
selectedEl.removeAttr('id', firstSubSelector.name); | ||
if (selectedEl.attributes.id === firstSubSelector.name) { | ||
delete selectedEl.attributes.id; | ||
} | ||
} | ||
@@ -257,11 +263,14 @@ } | ||
styleParentEl.spliceContent( | ||
styleParentEl.content.indexOf(style.styleEl), | ||
styleParentEl.children.indexOf(style.styleEl), | ||
1 | ||
); | ||
if (styleParentEl.elem === 'defs' && styleParentEl.content.length === 0) { | ||
if ( | ||
styleParentEl.name === 'defs' && | ||
styleParentEl.children.length === 0 | ||
) { | ||
// also clean up now empty <def/>s | ||
var defsParentEl = styleParentEl.parentNode; | ||
defsParentEl.spliceContent( | ||
defsParentEl.content.indexOf(styleParentEl), | ||
defsParentEl.children.indexOf(styleParentEl), | ||
1 | ||
@@ -278,3 +287,3 @@ ); | ||
return document; | ||
return root; | ||
}; |
@@ -29,16 +29,16 @@ 'use strict'; | ||
exports.fn = function (item, params) { | ||
if (!item.isElem() || item.isEmpty()) return; | ||
if (item.type !== 'element' || item.children.length === 0) return; | ||
var prevContentItem = null, | ||
prevContentItemKeys = null; | ||
let prevContentItem = null; | ||
let prevContentItemKeys = null; | ||
item.content = item.content.filter(function (contentItem) { | ||
item.children = item.children.filter(function (contentItem) { | ||
if ( | ||
prevContentItem && | ||
prevContentItem.isElem('path') && | ||
prevContentItem.isEmpty() && | ||
prevContentItem.hasAttr('d') && | ||
prevContentItem.children.length === 0 && | ||
prevContentItem.attributes.d != null && | ||
contentItem.isElem('path') && | ||
contentItem.isEmpty() && | ||
contentItem.hasAttr('d') | ||
contentItem.children.length === 0 && | ||
contentItem.attributes.d != null | ||
) { | ||
@@ -55,17 +55,17 @@ const computedStyle = computeStyle(contentItem); | ||
if (!prevContentItemKeys) { | ||
prevContentItemKeys = Object.keys(prevContentItem.attrs); | ||
prevContentItemKeys = Object.keys(prevContentItem.attributes); | ||
} | ||
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); | ||
const contentItemAttrs = Object.keys(contentItem.attributes); | ||
const equalData = | ||
prevContentItemKeys.length == contentItemAttrs.length && | ||
contentItemAttrs.every(function (key) { | ||
return ( | ||
key == 'd' || | ||
(prevContentItem.attributes[key] != null && | ||
prevContentItem.attributes[key] == contentItem.attributes[key]) | ||
); | ||
}); | ||
const prevPathJS = path2js(prevContentItem); | ||
const curPathJS = path2js(contentItem); | ||
@@ -72,0 +72,0 @@ if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) { |
'use strict'; | ||
const csso = require('csso'); | ||
const { traverse } = require('../lib/xast.js'); | ||
exports.type = 'full'; | ||
@@ -7,18 +10,17 @@ | ||
exports.description = 'minifies styles and removes unused styles based on usage data'; | ||
exports.description = | ||
'minifies styles and removes unused styles based on usage data'; | ||
exports.params = { | ||
// ... CSSO options goes here | ||
// ... CSSO options goes here | ||
// additional | ||
usage: { | ||
force: false, // force to use usage data even if it unsafe (document contains <script> or on* attributes) | ||
ids: true, | ||
classes: true, | ||
tags: true | ||
} | ||
// additional | ||
usage: { | ||
force: false, // force to use usage data even if it unsafe (document contains <script> or on* attributes) | ||
ids: true, | ||
classes: true, | ||
tags: true, | ||
}, | ||
}; | ||
var csso = require('csso'); | ||
/** | ||
@@ -29,128 +31,123 @@ * Minifies styles (<style> element + style attribute) using CSSO | ||
*/ | ||
exports.fn = function(ast, options) { | ||
options = options || {}; | ||
exports.fn = function (ast, options) { | ||
options = options || {}; | ||
var minifyOptionsForStylesheet = cloneObject(options); | ||
var minifyOptionsForAttribute = cloneObject(options); | ||
var elems = findStyleElems(ast); | ||
var minifyOptionsForStylesheet = cloneObject(options); | ||
var minifyOptionsForAttribute = cloneObject(options); | ||
var elems = findStyleElems(ast); | ||
minifyOptionsForStylesheet.usage = collectUsageData(ast, options); | ||
minifyOptionsForAttribute.usage = null; | ||
minifyOptionsForStylesheet.usage = collectUsageData(ast, options); | ||
minifyOptionsForAttribute.usage = null; | ||
elems.forEach(function(elem) { | ||
if (elem.isElem('style')) { | ||
// <style> element | ||
var styleCss = elem.content[0].text || elem.content[0].cdata || []; | ||
var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text'; | ||
elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css; | ||
elems.forEach(function (elem) { | ||
if (elem.isElem('style')) { | ||
if ( | ||
elem.children[0].type === 'text' || | ||
elem.children[0].type === 'cdata' | ||
) { | ||
const styleCss = elem.children[0].value; | ||
const minified = csso.minify(styleCss, minifyOptionsForStylesheet).css; | ||
// preserve cdata if necessary | ||
// TODO split cdata -> text optimisation into separate plugin | ||
if (styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0) { | ||
elem.children[0].type = 'cdata'; | ||
elem.children[0].value = minified; | ||
} else { | ||
// style attribute | ||
var elemStyle = elem.attr('style').value; | ||
elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css; | ||
elem.children[0].type = 'text'; | ||
elem.children[0].value = minified; | ||
} | ||
}); | ||
} | ||
} else { | ||
// style attribute | ||
var elemStyle = elem.attributes.style; | ||
return ast; | ||
elem.attributes.style = csso.minifyBlock( | ||
elemStyle, | ||
minifyOptionsForAttribute | ||
).css; | ||
} | ||
}); | ||
return ast; | ||
}; | ||
function cloneObject(obj) { | ||
return {...obj}; | ||
return { ...obj }; | ||
} | ||
function findStyleElems(ast) { | ||
function walk(items, styles) { | ||
for (var i = 0; i < items.content.length; i++) { | ||
var item = items.content[i]; | ||
// go deeper | ||
if (item.content) { | ||
walk(item, styles); | ||
} | ||
if (item.isElem('style') && !item.isEmpty()) { | ||
styles.push(item); | ||
} else if (item.isElem() && item.hasAttr('style')) { | ||
styles.push(item); | ||
} | ||
} | ||
return styles; | ||
const nodesWithStyles = []; | ||
traverse(ast, (node) => { | ||
if (node.type === 'element') { | ||
if (node.name === 'style' && node.children.length !== 0) { | ||
nodesWithStyles.push(node); | ||
} else if (node.attributes.style != null) { | ||
nodesWithStyles.push(node); | ||
} | ||
} | ||
return walk(ast, []); | ||
}); | ||
return nodesWithStyles; | ||
} | ||
function shouldFilter(options, name) { | ||
if ('usage' in options === false) { | ||
return true; | ||
} | ||
if ('usage' in options === false) { | ||
return true; | ||
} | ||
if (options.usage && name in options.usage === false) { | ||
return true; | ||
} | ||
if (options.usage && name in options.usage === false) { | ||
return true; | ||
} | ||
return Boolean(options.usage && options.usage[name]); | ||
return Boolean(options.usage && options.usage[name]); | ||
} | ||
function collectUsageData(ast, options) { | ||
let safe = true; | ||
const usageData = {}; | ||
let hasData = false; | ||
const rawData = { | ||
ids: Object.create(null), | ||
classes: Object.create(null), | ||
tags: Object.create(null), | ||
}; | ||
function walk(items, usageData) { | ||
for (var i = 0; i < items.content.length; i++) { | ||
var item = items.content[i]; | ||
traverse(ast, (node) => { | ||
if (node.type === 'element') { | ||
if (node.name === 'script') { | ||
safe = false; | ||
} | ||
// go deeper | ||
if (item.content) { | ||
walk(item, usageData); | ||
} | ||
rawData.tags[node.name] = true; | ||
if (item.isElem('script')) { | ||
safe = false; | ||
} | ||
if (node.attributes.id != null) { | ||
rawData.ids[node.attributes.id] = true; | ||
} | ||
if (item.isElem()) { | ||
usageData.tags[item.elem] = true; | ||
if (node.attributes.class != null) { | ||
node.attributes.class | ||
.replace(/^\s+|\s+$/g, '') | ||
.split(/\s+/) | ||
.forEach((className) => { | ||
rawData.classes[className] = true; | ||
}); | ||
} | ||
if (item.hasAttr('id')) { | ||
usageData.ids[item.attr('id').value] = true; | ||
} | ||
if (item.hasAttr('class')) { | ||
item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) { | ||
usageData.classes[className] = true; | ||
}); | ||
} | ||
if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) { | ||
safe = false; | ||
} | ||
} | ||
} | ||
return usageData; | ||
if (Object.keys(node.attributes).some((name) => /^on/i.test(name))) { | ||
safe = false; | ||
} | ||
} | ||
}); | ||
var safe = true; | ||
var usageData = {}; | ||
var hasData = false; | ||
var rawData = walk(ast, { | ||
ids: Object.create(null), | ||
classes: Object.create(null), | ||
tags: Object.create(null) | ||
}); | ||
if (!safe && options.usage && options.usage.force) { | ||
safe = true; | ||
} | ||
if (!safe && options.usage && options.usage.force) { | ||
safe = true; | ||
for (const [key, data] of Object.entries(rawData)) { | ||
if (shouldFilter(options, key)) { | ||
usageData[key] = Object.keys(data); | ||
hasData = true; | ||
} | ||
} | ||
for (const [key, data] of Object.entries(rawData)) { | ||
if (shouldFilter(options, key)) { | ||
usageData[key] = Object.keys(data); | ||
hasData = true; | ||
} | ||
} | ||
return safe && hasData ? usageData : null; | ||
return safe && hasData ? usageData : null; | ||
} |
'use strict'; | ||
const { inheritableAttrs, pathElems } = require('./_collections'); | ||
exports.type = 'perItemReverse'; | ||
@@ -9,5 +11,2 @@ | ||
var inheritableAttrs = require('./_collections').inheritableAttrs, | ||
pathElems = require('./_collections.js').pathElems; | ||
/** | ||
@@ -37,61 +36,62 @@ * Collapse content's intersected and inheritable | ||
*/ | ||
exports.fn = function(item) { | ||
exports.fn = function (item) { | ||
if ( | ||
item.type === 'element' && | ||
item.name === 'g' && | ||
item.children.length > 1 | ||
) { | ||
var intersection = {}, | ||
hasTransform = false, | ||
hasClip = | ||
item.attributes['clip-path'] != null || item.attributes.mask != null, | ||
intersected = item.children.every(function (inner) { | ||
if ( | ||
inner.type === 'element' && | ||
Object.keys(inner.attributes).length !== 0 | ||
) { | ||
// don't mess with possible styles (hack until CSS parsing is implemented) | ||
if (inner.attributes.class) return false; | ||
if (!Object.keys(intersection).length) { | ||
intersection = inner.attributes; | ||
} else { | ||
intersection = intersectInheritableAttrs( | ||
intersection, | ||
inner.attributes | ||
); | ||
if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) { | ||
if (!intersection) return false; | ||
} | ||
var intersection = {}, | ||
hasTransform = false, | ||
hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'), | ||
intersected = item.content.every(function(inner) { | ||
if (inner.isElem() && inner.hasAttr()) { | ||
// don't mess with possible styles (hack until CSS parsing is implemented) | ||
if (inner.hasAttr('class')) return false; | ||
if (!Object.keys(intersection).length) { | ||
intersection = inner.attrs; | ||
} else { | ||
intersection = intersectInheritableAttrs(intersection, inner.attrs); | ||
return true; | ||
} | ||
}), | ||
allPath = item.children.every(function (inner) { | ||
return inner.isElem(pathElems); | ||
}); | ||
if (!intersection) return false; | ||
} | ||
if (intersected) { | ||
item.children.forEach(function (g) { | ||
for (const [name, value] of Object.entries(intersection)) { | ||
if ((!allPath && !hasClip) || name !== 'transform') { | ||
delete g.attributes[name]; | ||
return true; | ||
if (name === 'transform') { | ||
if (!hasTransform) { | ||
if (item.attributes.transform != null) { | ||
item.attributes.transform = | ||
item.attributes.transform + ' ' + value; | ||
} else { | ||
item.attributes.transform = value; | ||
} | ||
}), | ||
allPath = item.content.every(function(inner) { | ||
return inner.isElem(pathElems); | ||
}); | ||
if (intersected) { | ||
item.content.forEach(function(g) { | ||
for (const [name, attr] of Object.entries(intersection)) { | ||
if (!allPath && !hasClip || name !== 'transform') { | ||
g.removeAttr(name); | ||
if (name === 'transform') { | ||
if (!hasTransform) { | ||
if (item.hasAttr('transform')) { | ||
item.attr('transform').value += ' ' + attr.value; | ||
} else { | ||
item.addAttr(attr); | ||
} | ||
hasTransform = true; | ||
} | ||
} else { | ||
item.addAttr(attr); | ||
} | ||
} | ||
} | ||
}); | ||
hasTransform = true; | ||
} | ||
} else { | ||
item.attributes[name] = value; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
@@ -108,23 +108,18 @@ | ||
function intersectInheritableAttrs(a, b) { | ||
var c = {}; | ||
var c = {}; | ||
for (const [n, attr] of Object.entries(a)) { | ||
if ( | ||
// eslint-disable-next-line no-prototype-builtins | ||
b.hasOwnProperty(n) && | ||
inheritableAttrs.indexOf(n) > -1 && | ||
attr.name === b[n].name && | ||
attr.value === b[n].value && | ||
attr.prefix === b[n].prefix && | ||
attr.local === b[n].local | ||
) { | ||
c[n] = attr; | ||
} | ||
for (const [name, value] of Object.entries(a)) { | ||
if ( | ||
// eslint-disable-next-line no-prototype-builtins | ||
b.hasOwnProperty(name) && | ||
inheritableAttrs.includes(name) && | ||
value === b[name] | ||
) { | ||
c[name] = value; | ||
} | ||
} | ||
if (!Object.keys(c).length) return false; | ||
if (!Object.keys(c).length) return false; | ||
return c; | ||
return c; | ||
} |
'use strict'; | ||
const { pathElems, referencesProps } = require('./_collections.js'); | ||
exports.type = 'perItem'; | ||
@@ -9,5 +11,3 @@ | ||
var collections = require('./_collections.js'), | ||
pathElems = collections.pathElems.concat(['g', 'text']), | ||
referencesProps = collections.referencesProps; | ||
const pathElemsWithGroupsAndText = [...pathElems, 'g', 'text']; | ||
@@ -33,33 +33,30 @@ /** | ||
*/ | ||
exports.fn = function(item) { | ||
// move group transform attr to content's pathElems | ||
if ( | ||
item.isElem('g') && | ||
item.hasAttr('transform') && | ||
!item.isEmpty() && | ||
!item.someAttr(function(attr) { | ||
return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url('); | ||
}) && | ||
item.content.every(function(inner) { | ||
return inner.isElem(pathElems) && !inner.hasAttr('id'); | ||
}) | ||
) { | ||
item.content.forEach(function(inner) { | ||
var attr = item.attr('transform'); | ||
if (inner.hasAttr('transform')) { | ||
inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value; | ||
} else { | ||
inner.addAttr({ | ||
'name': attr.name, | ||
'local': attr.local, | ||
'prefix': attr.prefix, | ||
'value': attr.value | ||
}); | ||
} | ||
}); | ||
item.removeAttr('transform'); | ||
exports.fn = function (item) { | ||
// move group transform attr to content's pathElems | ||
if ( | ||
item.type === 'element' && | ||
item.name === 'g' && | ||
item.children.length !== 0 && | ||
item.attributes.transform != null && | ||
Object.entries(item.attributes).some( | ||
([name, value]) => | ||
referencesProps.includes(name) && value.includes('url(') | ||
) === false && | ||
item.children.every( | ||
(inner) => | ||
pathElemsWithGroupsAndText.includes(inner.name) && | ||
inner.attributes.id == null | ||
) | ||
) { | ||
for (const inner of item.children) { | ||
const value = item.attributes.transform; | ||
if (inner.attributes.transform != null) { | ||
inner.attributes.transform = value + ' ' + inner.attributes.transform; | ||
} else { | ||
inner.attributes.transform = value; | ||
} | ||
} | ||
delete item.attributes.transform; | ||
} | ||
}; |
@@ -17,2 +17,3 @@ 'use strict'; | ||
exports.convertTransform = require('./convertTransform.js'); | ||
exports.mergeStyles = require('./mergeStyles.js'); | ||
exports.inlineStyles = require('./inlineStyles.js'); | ||
@@ -19,0 +20,0 @@ exports.mergePaths = require('./mergePaths.js'); |
@@ -8,5 +8,5 @@ 'use strict'; | ||
exports.params = { | ||
delim: '__', | ||
prefixIds: true, | ||
prefixClassNames: true, | ||
delim: '__', | ||
prefixIds: true, | ||
prefixClassNames: true, | ||
}; | ||
@@ -17,9 +17,9 @@ | ||
var csstree = require('css-tree'), | ||
collections = require('./_collections.js'), | ||
referencesProps = collections.referencesProps, | ||
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name | ||
addPrefix = null; | ||
collections = require('./_collections.js'), | ||
referencesProps = collections.referencesProps, | ||
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name | ||
addPrefix = null; | ||
const unquote = (string) => { | ||
const first = string.charAt(0) | ||
const first = string.charAt(0); | ||
if (first === "'" || first === '"') { | ||
@@ -31,123 +31,131 @@ if (first === string.charAt(string.length - 1)) { | ||
return string; | ||
} | ||
}; | ||
// Escapes a string for being used as ID | ||
var escapeIdentifierName = function(str) { | ||
return str.replace(/[. ]/g, '_'); | ||
var escapeIdentifierName = function (str) { | ||
return str.replace(/[. ]/g, '_'); | ||
}; | ||
// Matches an #ID value, captures the ID name | ||
var matchId = function(urlVal) { | ||
var idUrlMatches = urlVal.match(rxId); | ||
if (idUrlMatches === null) { | ||
return false; | ||
} | ||
return idUrlMatches[1]; | ||
var matchId = function (urlVal) { | ||
var idUrlMatches = urlVal.match(rxId); | ||
if (idUrlMatches === null) { | ||
return false; | ||
} | ||
return idUrlMatches[1]; | ||
}; | ||
// Matches an url(...) value, captures the URL | ||
var matchUrl = function(val) { | ||
var urlMatches = /url\((.*?)\)/gi.exec(val); | ||
if (urlMatches === null) { | ||
return false; | ||
} | ||
return urlMatches[1]; | ||
var matchUrl = function (val) { | ||
var urlMatches = /url\((.*?)\)/gi.exec(val); | ||
if (urlMatches === null) { | ||
return false; | ||
} | ||
return urlMatches[1]; | ||
}; | ||
// Checks if attribute is empty | ||
var attrNotEmpty = function(attr) { | ||
return (attr && attr.value && attr.value.length > 0); | ||
}; | ||
// prefixes an #ID | ||
var prefixId = function(val) { | ||
var idName = matchId(val); | ||
if (!idName) { | ||
return false; | ||
} | ||
return '#' + addPrefix(idName); | ||
var prefixId = function (val) { | ||
var idName = matchId(val); | ||
if (!idName) { | ||
return false; | ||
} | ||
return '#' + addPrefix(idName); | ||
}; | ||
// attr.value helper methods | ||
// prefixes a class attribute value | ||
var addPrefixToClassAttr = function(attr) { | ||
if (!attrNotEmpty(attr)) { | ||
return; | ||
} | ||
const addPrefixToClassAttr = (element, name) => { | ||
if ( | ||
element.attributes[name] == null || | ||
element.attributes[name].length === 0 | ||
) { | ||
return; | ||
} | ||
attr.value = attr.value.split(/\s+/).map(addPrefix).join(' '); | ||
element.attributes[name] = element.attributes[name] | ||
.split(/\s+/) | ||
.map(addPrefix) | ||
.join(' '); | ||
}; | ||
// prefixes an ID attribute value | ||
var addPrefixToIdAttr = function(attr) { | ||
if (!attrNotEmpty(attr)) { | ||
return; | ||
} | ||
const addPrefixToIdAttr = (element, name) => { | ||
if ( | ||
element.attributes[name] == null || | ||
element.attributes[name].length === 0 | ||
) { | ||
return; | ||
} | ||
attr.value = addPrefix(attr.value); | ||
element.attributes[name] = addPrefix(element.attributes[name]); | ||
}; | ||
// prefixes a href attribute value | ||
var addPrefixToHrefAttr = function(attr) { | ||
if (!attrNotEmpty(attr)) { | ||
return; | ||
} | ||
const addPrefixToHrefAttr = (element, name) => { | ||
if ( | ||
element.attributes[name] == null || | ||
element.attributes[name].length === 0 | ||
) { | ||
return; | ||
} | ||
var idPrefixed = prefixId(attr.value); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
attr.value = idPrefixed; | ||
const idPrefixed = prefixId(element.attributes[name]); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
element.attributes[name] = idPrefixed; | ||
}; | ||
// prefixes an URL attribute value | ||
var addPrefixToUrlAttr = function(attr) { | ||
if (!attrNotEmpty(attr)) { | ||
return; | ||
} | ||
const addPrefixToUrlAttr = (element, name) => { | ||
if ( | ||
element.attributes[name] == null || | ||
element.attributes[name].length === 0 | ||
) { | ||
return; | ||
} | ||
// url(...) in value | ||
var urlVal = matchUrl(attr.value); | ||
if (!urlVal) { | ||
return; | ||
} | ||
// url(...) in value | ||
const urlVal = matchUrl(element.attributes[name]); | ||
if (!urlVal) { | ||
return; | ||
} | ||
var idPrefixed = prefixId(urlVal); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
const idPrefixed = prefixId(urlVal); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
attr.value = 'url(' + idPrefixed + ')'; | ||
element.attributes[name] = 'url(' + idPrefixed + ')'; | ||
}; | ||
// prefixes begin/end attribute value | ||
var addPrefixToBeginEndAttr = function(attr) { | ||
if (!attrNotEmpty(attr)) { | ||
return; | ||
} | ||
const addPrefixToBeginEndAttr = (element, name) => { | ||
if ( | ||
element.attributes[name] == null || | ||
element.attributes[name].length === 0 | ||
) { | ||
return; | ||
} | ||
var parts = attr.value.split('; ').map(function(val) { | ||
val = val.trim(); | ||
const parts = element.attributes[name].split('; ').map((val) => { | ||
val = val.trim(); | ||
if (val.endsWith('.end') || val.endsWith('.start')) { | ||
var idPostfix = val.split('.'), | ||
id = idPostfix[0], | ||
postfix = idPostfix[1]; | ||
if (val.endsWith('.end') || val.endsWith('.start')) { | ||
const [id, postfix] = val.split('.'); | ||
var idPrefixed = prefixId(`#${id}`); | ||
let idPrefixed = prefixId(`#${id}`); | ||
if (!idPrefixed) { | ||
return val; | ||
} | ||
if (!idPrefixed) { | ||
return val; | ||
} | ||
idPrefixed = idPrefixed.slice(1); | ||
return `${idPrefixed}.${postfix}`; | ||
} else { | ||
return val; | ||
} | ||
}); | ||
idPrefixed = idPrefixed.slice(1); | ||
return `${idPrefixed}.${postfix}`; | ||
} else { | ||
return val; | ||
} | ||
}); | ||
attr.value = parts.join('; '); | ||
element.attributes[name] = parts.join('; '); | ||
}; | ||
@@ -173,121 +181,122 @@ | ||
*/ | ||
exports.fn = function(node, opts, extra) { | ||
exports.fn = function (node, opts, extra) { | ||
// skip subsequent passes when multipass is used | ||
if (extra.multipassCount && extra.multipassCount > 0) { | ||
return; | ||
} | ||
// skip subsequent passes when multipass is used | ||
if(extra.multipassCount && extra.multipassCount > 0) { | ||
return node; | ||
// prefix, from file name or option | ||
var prefix = 'prefix'; | ||
if (opts.prefix) { | ||
if (typeof opts.prefix === 'function') { | ||
prefix = opts.prefix(node, extra); | ||
} else { | ||
prefix = opts.prefix; | ||
} | ||
} else if (opts.prefix === false) { | ||
prefix = false; | ||
} else if (extra && extra.path && extra.path.length > 0) { | ||
var filename = getBasename(extra.path); | ||
prefix = filename; | ||
} | ||
// prefix, from file name or option | ||
var prefix = 'prefix'; | ||
if (opts.prefix) { | ||
if (typeof opts.prefix === 'function') { | ||
prefix = opts.prefix(node, extra); | ||
} else { | ||
prefix = opts.prefix; | ||
} | ||
} else if (opts.prefix === false) { | ||
prefix = false; | ||
} else if (extra && extra.path && extra.path.length > 0) { | ||
var filename = getBasename(extra.path); | ||
prefix = filename; | ||
// prefixes a normal value | ||
addPrefix = function (name) { | ||
if (prefix === false) { | ||
return escapeIdentifierName(name); | ||
} | ||
return escapeIdentifierName(prefix + opts.delim + name); | ||
}; | ||
// <style/> property values | ||
// prefixes a normal value | ||
addPrefix = function(name) { | ||
if(prefix === false){ | ||
return escapeIdentifierName(name); | ||
} | ||
return escapeIdentifierName(prefix + opts.delim + name); | ||
}; | ||
if (node.type === 'element' && node.name === 'style') { | ||
if (node.children.length === 0) { | ||
// skip empty <style/>s | ||
return; | ||
} | ||
var cssStr = ''; | ||
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') { | ||
cssStr = node.children[0].value; | ||
} | ||
// <style/> property values | ||
var cssAst = {}; | ||
try { | ||
cssAst = csstree.parse(cssStr, { | ||
parseValue: true, | ||
parseCustomProperty: false, | ||
}); | ||
} catch (parseError) { | ||
console.warn( | ||
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' + | ||
parseError | ||
); | ||
return; | ||
} | ||
if (node.elem === 'style') { | ||
if (node.isEmpty()) { | ||
// skip empty <style/>s | ||
return node; | ||
} | ||
var idPrefixed = ''; | ||
csstree.walk(cssAst, function (node) { | ||
// #ID, .class | ||
if ( | ||
((opts.prefixIds && node.type === 'IdSelector') || | ||
(opts.prefixClassNames && node.type === 'ClassSelector')) && | ||
node.name | ||
) { | ||
node.name = addPrefix(node.name); | ||
return; | ||
} | ||
var cssStr = node.content[0].text || node.content[0].cdata || []; | ||
var cssAst = {}; | ||
try { | ||
cssAst = csstree.parse(cssStr, { | ||
parseValue: true, | ||
parseCustomProperty: false | ||
}); | ||
} catch (parseError) { | ||
console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError); | ||
return node; | ||
// url(...) in value | ||
if ( | ||
node.type === 'Url' && | ||
node.value.value && | ||
node.value.value.length > 0 | ||
) { | ||
idPrefixed = prefixId(unquote(node.value.value)); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
node.value.value = idPrefixed; | ||
} | ||
}); | ||
var idPrefixed = ''; | ||
csstree.walk(cssAst, function(node) { | ||
// update <style>s | ||
node.children[0].value = csstree.generate(cssAst); | ||
return; | ||
} | ||
// #ID, .class | ||
if (((opts.prefixIds && node.type === 'IdSelector') || | ||
(opts.prefixClassNames && node.type === 'ClassSelector')) && | ||
node.name) { | ||
node.name = addPrefix(node.name); | ||
return; | ||
} | ||
// element attributes | ||
// url(...) in value | ||
if (node.type === 'Url' && | ||
node.value.value && node.value.value.length > 0) { | ||
idPrefixed = prefixId(unquote(node.value.value)); | ||
if (!idPrefixed) { | ||
return; | ||
} | ||
node.value.value = idPrefixed; | ||
} | ||
if (node.type !== 'element') { | ||
return; | ||
} | ||
}); | ||
// Nodes | ||
// update <style>s | ||
node.content[0].text = csstree.generate(cssAst); | ||
return node; | ||
} | ||
if (opts.prefixIds) { | ||
// ID | ||
addPrefixToIdAttr(node, 'id'); | ||
} | ||
if (opts.prefixClassNames) { | ||
// Class | ||
addPrefixToClassAttr(node, 'class'); | ||
} | ||
// element attributes | ||
// References | ||
if (!node.attrs) { | ||
return node; | ||
} | ||
// href | ||
addPrefixToHrefAttr(node, 'href'); | ||
// (xlink:)href (deprecated, must be still supported) | ||
addPrefixToHrefAttr(node, 'xlink:href'); | ||
// Nodes | ||
// (referenceable) properties | ||
for (var referencesProp of referencesProps) { | ||
addPrefixToUrlAttr(node, referencesProp); | ||
} | ||
if(opts.prefixIds) { | ||
// ID | ||
addPrefixToIdAttr(node.attrs.id); | ||
} | ||
if(opts.prefixClassNames) { | ||
// Class | ||
addPrefixToClassAttr(node.attrs.class); | ||
} | ||
// References | ||
// href | ||
addPrefixToHrefAttr(node.attrs.href); | ||
// (xlink:)href (deprecated, must be still supported) | ||
addPrefixToHrefAttr(node.attrs['xlink:href']); | ||
// (referenceable) properties | ||
for (var referencesProp of referencesProps) { | ||
addPrefixToUrlAttr(node.attrs[referencesProp]); | ||
} | ||
addPrefixToBeginEndAttr(node.attrs.begin); | ||
addPrefixToBeginEndAttr(node.attrs.end); | ||
return node; | ||
addPrefixToBeginEndAttr(node, 'begin'); | ||
addPrefixToBeginEndAttr(node, 'end'); | ||
}; |
@@ -7,5 +7,5 @@ 'use strict'; | ||
exports.description = 'removes attributes of elements that match a css selector'; | ||
exports.description = | ||
'removes attributes of elements that match a css selector'; | ||
/** | ||
@@ -27,3 +27,3 @@ * Removes attributes of elements that match a css selector. | ||
* ↓ | ||
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/> | ||
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/> | ||
* | ||
@@ -40,3 +40,3 @@ * <caption>A selector removing multiple attributes</caption> | ||
* ↓ | ||
* <rect x="0" y="0" width="100" height="100"/> | ||
* <rect x="0" y="0" width="100" height="100"/> | ||
* | ||
@@ -63,12 +63,16 @@ * <caption>Multiple selectors removing attributes</caption> | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
var selectors = Array.isArray(params.selectors) ? params.selectors : [params]; | ||
var selectors = Array.isArray(params.selectors) ? params.selectors : [params]; | ||
selectors.map(function(i) { | ||
if (item.matches(i.selector)) { | ||
item.removeAttr(i.attributes); | ||
selectors.map(({ selector, attributes }) => { | ||
if (item.matches(selector)) { | ||
if (Array.isArray(attributes)) { | ||
for (const name of attributes) { | ||
delete item.attributes[name]; | ||
} | ||
}); | ||
} else { | ||
delete item.attributes[attributes]; | ||
} | ||
} | ||
}); | ||
}; |
@@ -12,5 +12,5 @@ 'use strict'; | ||
exports.params = { | ||
elemSeparator: DEFAULT_SEPARATOR, | ||
preserveCurrentColor: false, | ||
attrs: [] | ||
elemSeparator: DEFAULT_SEPARATOR, | ||
preserveCurrentColor: false, | ||
attrs: [], | ||
}; | ||
@@ -85,68 +85,64 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
// wrap into an array if params is not | ||
if (!Array.isArray(params.attrs)) { | ||
params.attrs = [params.attrs]; | ||
} | ||
exports.fn = function (item, params) { | ||
// wrap into an array if params is not | ||
if (!Array.isArray(params.attrs)) { | ||
params.attrs = [params.attrs]; | ||
} | ||
if (item.isElem()) { | ||
var elemSeparator = typeof params.elemSeparator == 'string' ? params.elemSeparator : DEFAULT_SEPARATOR; | ||
var preserveCurrentColor = typeof params.preserveCurrentColor == 'boolean' ? params.preserveCurrentColor : false; | ||
if (item.type === 'element') { | ||
var elemSeparator = | ||
typeof params.elemSeparator == 'string' | ||
? params.elemSeparator | ||
: DEFAULT_SEPARATOR; | ||
var preserveCurrentColor = | ||
typeof params.preserveCurrentColor == 'boolean' | ||
? params.preserveCurrentColor | ||
: false; | ||
// prepare patterns | ||
var patterns = params.attrs.map(function(pattern) { | ||
// prepare patterns | ||
var patterns = params.attrs.map(function (pattern) { | ||
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value* | ||
if (pattern.indexOf(elemSeparator) === -1) { | ||
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(''); | ||
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value* | ||
if (pattern.indexOf(elemSeparator) === -1) { | ||
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(''); | ||
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value | ||
} else if (pattern.split(elemSeparator).length < 3) { | ||
pattern = [pattern, elemSeparator, '.*'].join(''); | ||
} | ||
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value | ||
} else if (pattern.split(elemSeparator).length < 3) { | ||
pattern = [pattern, elemSeparator, '.*'].join(''); | ||
} | ||
// create regexps for element, attribute name, and attribute value | ||
return pattern.split(elemSeparator).map(function (value) { | ||
// adjust single * to match anything | ||
if (value === '*') { | ||
value = '.*'; | ||
} | ||
// create regexps for element, attribute name, and attribute value | ||
return pattern.split(elemSeparator) | ||
.map(function(value) { | ||
return new RegExp(['^', value, '$'].join(''), 'i'); | ||
}); | ||
}); | ||
// adjust single * to match anything | ||
if (value === '*') { value = '.*'; } | ||
// loop patterns | ||
patterns.forEach(function (pattern) { | ||
// matches element | ||
if (pattern[0].test(item.name)) { | ||
// loop attributes | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
var isFillCurrentColor = | ||
preserveCurrentColor && name == 'fill' && value == 'currentColor'; | ||
var isStrokeCurrentColor = | ||
preserveCurrentColor && name == 'stroke' && value == 'currentColor'; | ||
return new RegExp(['^', value, '$'].join(''), 'i'); | ||
}); | ||
}); | ||
// loop patterns | ||
patterns.forEach(function(pattern) { | ||
// matches element | ||
if (pattern[0].test(item.elem)) { | ||
// loop attributes | ||
item.eachAttr(function(attr) { | ||
var name = attr.name; | ||
var value = attr.value; | ||
var isFillCurrentColor = preserveCurrentColor && name == 'fill' && value == 'currentColor'; | ||
var isStrokeCurrentColor = preserveCurrentColor && name == 'stroke' && value == 'currentColor'; | ||
if (!(isFillCurrentColor || isStrokeCurrentColor)) { | ||
// matches attribute name | ||
if (pattern[1].test(name)) { | ||
// matches attribute value | ||
if (pattern[2].test(attr.value)) { | ||
item.removeAttr(name); | ||
} | ||
} | ||
} | ||
}); | ||
if (!(isFillCurrentColor || isStrokeCurrentColor)) { | ||
// matches attribute name | ||
if (pattern[1].test(name)) { | ||
// matches attribute value | ||
if (pattern[2].test(value)) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
}; |
@@ -21,8 +21,6 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.comment && item.comment.charAt(0) !== '!') { | ||
return false; | ||
} | ||
exports.fn = function (item) { | ||
if (item.type === 'comment' && item.value.charAt(0) !== '!') { | ||
return false; | ||
} | ||
}; |
@@ -8,3 +8,3 @@ 'use strict'; | ||
exports.params = { | ||
removeAny: true | ||
removeAny: true, | ||
}; | ||
@@ -28,7 +28,12 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
return !item.isElem('desc') || !(params.removeAny || item.isEmpty() || | ||
standardDescs.test(item.content[0].text)); | ||
exports.fn = function (item, params) { | ||
return ( | ||
!item.isElem('desc') || | ||
!( | ||
params.removeAny || | ||
item.children.length === 0 || | ||
(item.children[0].type === 'text' && | ||
standardDescs.test(item.children[0].value)) | ||
) | ||
); | ||
}; |
@@ -7,3 +7,4 @@ 'use strict'; | ||
exports.description = 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)'; | ||
exports.description = | ||
'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)'; | ||
@@ -23,29 +24,20 @@ /** | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('svg')) { | ||
if (item.hasAttr('viewBox')) { | ||
item.removeAttr('width'); | ||
item.removeAttr('height'); | ||
} else if ( | ||
item.hasAttr('width') && | ||
item.hasAttr('height') && | ||
!isNaN(Number(item.attr('width').value)) && | ||
!isNaN(Number(item.attr('height').value)) | ||
) { | ||
item.addAttr({ | ||
name: 'viewBox', | ||
value: | ||
'0 0 ' + | ||
Number(item.attr('width').value) + | ||
' ' + | ||
Number(item.attr('height').value), | ||
prefix: '', | ||
local: 'viewBox' | ||
}); | ||
item.removeAttr('width'); | ||
item.removeAttr('height'); | ||
} | ||
exports.fn = function (item) { | ||
if (item.type === 'element' && item.name === 'svg') { | ||
if (item.attributes.viewBox != null) { | ||
delete item.attributes.width; | ||
delete item.attributes.height; | ||
} else if ( | ||
item.attributes.width != null && | ||
item.attributes.height != null && | ||
Number.isNaN(Number(item.attributes.width)) === false && | ||
Number.isNaN(Number(item.attributes.height)) === false | ||
) { | ||
const width = Number(item.attributes.width); | ||
const height = Number(item.attributes.height); | ||
item.attributes.viewBox = `0 0 ${width} ${height}`; | ||
delete item.attributes.width; | ||
delete item.attributes.height; | ||
} | ||
} | ||
}; |
@@ -34,8 +34,6 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.doctype) { | ||
return false; | ||
} | ||
exports.fn = function (item) { | ||
if (item.type === 'doctype') { | ||
return false; | ||
} | ||
}; |
'use strict'; | ||
const { parseName } = require('../lib/svgo/tools.js'); | ||
const { editorNamespaces } = require('./_collections'); | ||
exports.type = 'perItem'; | ||
@@ -9,7 +12,6 @@ | ||
var editorNamespaces = require('./_collections').editorNamespaces, | ||
prefixes = []; | ||
const prefixes = []; | ||
exports.params = { | ||
additionalNamespaces: [] | ||
additionalNamespaces: [], | ||
}; | ||
@@ -31,37 +33,35 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
let namespaces = editorNamespaces; | ||
if (Array.isArray(params.additionalNamespaces)) { | ||
namespaces = [...editorNamespaces, ...params.additionalNamespaces]; | ||
} | ||
if (Array.isArray(params.additionalNamespaces)) { | ||
editorNamespaces = editorNamespaces.concat(params.additionalNamespaces); | ||
} | ||
if (item.type === 'element') { | ||
if (item.isElem('svg')) { | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
const { prefix, local } = parseName(name); | ||
if (prefix === 'xmlns' && namespaces.includes(value)) { | ||
prefixes.push(local); | ||
if (item.elem) { | ||
if (item.isElem('svg')) { | ||
item.eachAttr(function(attr) { | ||
if (attr.prefix === 'xmlns' && editorNamespaces.indexOf(attr.value) > -1) { | ||
prefixes.push(attr.local); | ||
// <svg xmlns:sodipodi=""> | ||
item.removeAttr(attr.name); | ||
} | ||
}); | ||
// <svg xmlns:sodipodi=""> | ||
delete item.attributes[name]; | ||
} | ||
} | ||
} | ||
// <* sodipodi:*=""> | ||
item.eachAttr(function(attr) { | ||
if (prefixes.indexOf(attr.prefix) > -1) { | ||
item.removeAttr(attr.name); | ||
} | ||
}); | ||
// <* sodipodi:*=""> | ||
for (const name of Object.keys(item.attributes)) { | ||
const { prefix } = parseName(name); | ||
if (prefixes.includes(prefix)) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
// <sodipodi:*> | ||
if (prefixes.indexOf(item.prefix) > -1) { | ||
return false; | ||
} | ||
// <sodipodi:*> | ||
const { prefix } = parseName(item.name); | ||
if (prefixes.includes(prefix)) { | ||
return false; | ||
} | ||
} | ||
}; |
@@ -7,7 +7,8 @@ 'use strict'; | ||
exports.description = 'removes arbitrary elements by ID or className (disabled by default)'; | ||
exports.description = | ||
'removes arbitrary elements by ID or className (disabled by default)'; | ||
exports.params = { | ||
id: [], | ||
class: [] | ||
class: [], | ||
}; | ||
@@ -54,7 +55,7 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
// wrap params in an array if not already | ||
['id', 'class'].forEach(function(key) { | ||
['id', 'class'].forEach(function (key) { | ||
if (!Array.isArray(params[key])) { | ||
params[key] = [ params[key] ]; | ||
params[key] = [params[key]]; | ||
} | ||
@@ -64,3 +65,3 @@ }); | ||
// abort if current item is no an element | ||
if (!item.isElem()) { | ||
if (item.type !== 'element') { | ||
return; | ||
@@ -70,13 +71,11 @@ } | ||
// remove element if it's `id` matches configured `id` params | ||
const elemId = item.attr('id'); | ||
if (elemId && params.id.length !== 0) { | ||
return params.id.includes(elemId.value) === false; | ||
if (item.attributes.id != null && params.id.length !== 0) { | ||
return params.id.includes(item.attributes.id) === false; | ||
} | ||
// remove element if it's `class` contains any of the configured `class` params | ||
const elemClass = item.attr('class'); | ||
if (elemClass && params.class.length !== 0) { | ||
const classList = elemClass.value.split(' '); | ||
return params.class.some(item => classList.includes(item)) === false; | ||
if (item.attributes.class && params.class.length !== 0) { | ||
const classList = item.attributes.class.split(' '); | ||
return params.class.some((item) => classList.includes(item)) === false; | ||
} | ||
}; |
@@ -20,13 +20,13 @@ 'use strict'; | ||
exports.fn = function (item) { | ||
if (item.elem) { | ||
item.eachAttr(function (attr) { | ||
if (item.type === 'element') { | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
if ( | ||
attr.value === '' && | ||
value === '' && | ||
// empty conditional processing attributes prevents elements from rendering | ||
attrsGroups.conditionalProcessing.includes(attr.name) === false | ||
attrsGroups.conditionalProcessing.includes(name) === false | ||
) { | ||
item.removeAttr(attr.name); | ||
delete item.attributes[name]; | ||
} | ||
}); | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { elemsGroups } = require('./_collections'); | ||
exports.type = 'perItemReverse'; | ||
@@ -9,4 +11,2 @@ | ||
var container = require('./_collections').elemsGroups.container; | ||
/** | ||
@@ -29,14 +29,17 @@ * Remove empty containers. | ||
exports.fn = function (item) { | ||
return ( | ||
item.isElem(container) === false || | ||
item.isEmpty() === false || | ||
item.isElem('svg') || | ||
// 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 | ||
// to be created and filled with pattern. | ||
(item.isElem('g') && item.hasAttr('filter')) || | ||
// empty <mask> hides masked element | ||
(item.isElem('mask') && item.hasAttr('id')) | ||
); | ||
if (item.type === 'element') { | ||
return ( | ||
item.children.length !== 0 || | ||
elemsGroups.container.includes(item.name) === false || | ||
item.name === 'svg' || | ||
// empty patterns may contain reusable configuration | ||
(item.name === 'pattern' && Object.keys(item.attributes).length !== 0) || | ||
// The 'g' may not have content, but the filter may cause a rectangle | ||
// to be created and filled with pattern. | ||
(item.name === 'g' && item.attributes.filter != null) || | ||
// empty <mask> hides masked element | ||
(item.name === 'mask' && item.attributes.id != null) | ||
); | ||
} | ||
return true; | ||
}; |
@@ -10,5 +10,5 @@ 'use strict'; | ||
exports.params = { | ||
text: true, | ||
tspan: true, | ||
tref: true | ||
text: true, | ||
tspan: true, | ||
tref: true, | ||
}; | ||
@@ -37,25 +37,23 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
if (item.type === 'element') { | ||
// Remove empty text element | ||
if ( | ||
params.text && | ||
item.isElem('text') && | ||
item.isEmpty() | ||
) return false; | ||
if (params.text && item.name === 'text' && item.children.length === 0) { | ||
return false; | ||
} | ||
// Remove empty tspan element | ||
if ( | ||
params.tspan && | ||
item.isElem('tspan') && | ||
item.isEmpty() | ||
) return false; | ||
if (params.tspan && item.name === 'tspan' && item.children.length === 0) { | ||
return false; | ||
} | ||
// Remove tref with empty xlink:href attribute | ||
if ( | ||
params.tref && | ||
item.isElem('tref') && | ||
!item.hasAttrLocal('href') | ||
) return false; | ||
params.tref && | ||
item.name === 'tref' && | ||
item.attributes['xlink:href'] == null | ||
) { | ||
return false; | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { querySelector, closestByName } = require('../lib/xast.js'); | ||
const { computeStyle } = require('../lib/style.js'); | ||
@@ -51,3 +52,3 @@ const { parsePathData } = require('../lib/path.js'); | ||
exports.fn = function (item, params) { | ||
if (item.elem) { | ||
if (item.type === 'element') { | ||
// Removes hidden elements | ||
@@ -62,3 +63,3 @@ // https://www.w3schools.com/cssref/pr_class_visibility.asp | ||
// keep if any descendant enables visibility | ||
item.querySelector('[visibility=visible]') == null | ||
querySelector(item, '[visibility=visible]') == null | ||
) { | ||
@@ -93,3 +94,3 @@ return false; | ||
// transparent element inside clipPath still affect clipped elements | ||
item.closestElem('clipPath') == null | ||
closestByName(item, 'clipPath') == null | ||
) { | ||
@@ -108,4 +109,4 @@ return false; | ||
item.isElem('circle') && | ||
item.isEmpty() && | ||
item.hasAttr('r', '0') | ||
item.children.length === 0 && | ||
item.attributes.r === '0' | ||
) { | ||
@@ -124,4 +125,4 @@ return false; | ||
item.isElem('ellipse') && | ||
item.isEmpty() && | ||
item.hasAttr('rx', '0') | ||
item.children.length === 0 && | ||
item.attributes.rx === '0' | ||
) { | ||
@@ -140,4 +141,4 @@ return false; | ||
item.isElem('ellipse') && | ||
item.isEmpty() && | ||
item.hasAttr('ry', '0') | ||
item.children.length === 0 && | ||
item.attributes.ry === '0' | ||
) { | ||
@@ -156,4 +157,4 @@ return false; | ||
item.isElem('rect') && | ||
item.isEmpty() && | ||
item.hasAttr('width', '0') | ||
item.children.length === 0 && | ||
item.attributes.width === '0' | ||
) { | ||
@@ -173,4 +174,4 @@ return false; | ||
item.isElem('rect') && | ||
item.isEmpty() && | ||
item.hasAttr('height', '0') | ||
item.children.length === 0 && | ||
item.attributes.height === '0' | ||
) { | ||
@@ -189,3 +190,3 @@ return false; | ||
item.isElem('pattern') && | ||
item.hasAttr('width', '0') | ||
item.attributes.width === '0' | ||
) { | ||
@@ -204,3 +205,3 @@ return false; | ||
item.isElem('pattern') && | ||
item.hasAttr('height', '0') | ||
item.attributes.height === '0' | ||
) { | ||
@@ -219,3 +220,3 @@ return false; | ||
item.isElem('image') && | ||
item.hasAttr('width', '0') | ||
item.attributes.width === '0' | ||
) { | ||
@@ -234,3 +235,3 @@ return false; | ||
item.isElem('image') && | ||
item.hasAttr('height', '0') | ||
item.attributes.height === '0' | ||
) { | ||
@@ -246,6 +247,6 @@ return false; | ||
if (params.pathEmptyD && item.isElem('path')) { | ||
if (item.hasAttr('d') === false) { | ||
if (item.attributes.d == null) { | ||
return false; | ||
} | ||
const pathData = parsePathData(item.attr('d').value); | ||
const pathData = parsePathData(item.attributes.d); | ||
if (pathData.length === 0) { | ||
@@ -273,3 +274,3 @@ return false; | ||
item.isElem('polyline') && | ||
!item.hasAttr('points') | ||
item.attributes.points == null | ||
) { | ||
@@ -287,3 +288,3 @@ return false; | ||
item.isElem('polygon') && | ||
!item.hasAttr('points') | ||
item.attributes.points == null | ||
) { | ||
@@ -290,0 +291,0 @@ return false; |
@@ -19,6 +19,4 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
return !item.isElem('metadata'); | ||
exports.fn = function (item) { | ||
return !item.isElem('metadata'); | ||
}; |
@@ -7,7 +7,10 @@ 'use strict'; | ||
exports.description = 'removes non-inheritable group’s presentational attributes'; | ||
exports.description = | ||
'removes non-inheritable group’s presentational attributes'; | ||
var inheritableAttrs = require('./_collections').inheritableAttrs, | ||
attrsGroups = require('./_collections').attrsGroups, | ||
applyGroups = require('./_collections').presentationNonInheritableGroupAttrs; | ||
const { | ||
inheritableAttrs, | ||
attrsGroups, | ||
presentationNonInheritableGroupAttrs, | ||
} = require('./_collections'); | ||
@@ -22,18 +25,14 @@ /** | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('g')) { | ||
item.eachAttr(function(attr) { | ||
if ( | ||
~attrsGroups.presentation.indexOf(attr.name) && | ||
!~inheritableAttrs.indexOf(attr.name) && | ||
!~applyGroups.indexOf(attr.name) | ||
) { | ||
item.removeAttr(attr.name); | ||
} | ||
}); | ||
exports.fn = function (item) { | ||
if (item.type === 'element' && item.name === 'g') { | ||
for (const name of Object.keys(item.attributes)) { | ||
if ( | ||
attrsGroups.presentation.includes(name) === true && | ||
inheritableAttrs.includes(name) === false && | ||
presentationNonInheritableGroupAttrs.includes(name) === false | ||
) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
} | ||
}; |
@@ -7,11 +7,12 @@ 'use strict'; | ||
exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)'; | ||
exports.description = | ||
'removes elements that are drawn outside of the viewbox (disabled by default)'; | ||
const JSAPI = require('../lib/svgo/jsAPI.js'); | ||
var _path = require('./_path.js'), | ||
intersects = _path.intersects, | ||
path2js = _path.path2js, | ||
viewBox, | ||
viewBoxJS; | ||
var _path = require('./_path.js'), | ||
intersects = _path.intersects, | ||
path2js = _path.path2js, | ||
viewBox, | ||
viewBoxJS; | ||
@@ -26,29 +27,29 @@ /** | ||
*/ | ||
exports.fn = function(item) { | ||
exports.fn = function (item) { | ||
if ( | ||
item.type === 'element' && | ||
item.name === 'path' && | ||
item.attributes.d != null && | ||
typeof viewBox !== 'undefined' | ||
) { | ||
// Consider that any item with a transform attribute or a M instruction | ||
// within the viewBox is visible | ||
if (hasTransform(item) || pathMovesWithinViewBox(item.attributes.d)) { | ||
return true; | ||
} | ||
if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined') | ||
{ | ||
// Consider that any item with a transform attribute or a M instruction | ||
// within the viewBox is visible | ||
if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value)) | ||
{ | ||
return true; | ||
} | ||
var pathJS = path2js(item); | ||
if (pathJS.length === 2) { | ||
// Use a closed clone of the path if it's too short for intersects() | ||
pathJS = JSON.parse(JSON.stringify(pathJS)); | ||
pathJS.push({ instruction: 'z' }); | ||
} | ||
var pathJS = path2js(item); | ||
if (pathJS.length === 2) | ||
{ | ||
// Use a closed clone of the path if it's too short for intersects() | ||
pathJS = JSON.parse(JSON.stringify(pathJS)); | ||
pathJS.push({ instruction: 'z' }); | ||
} | ||
return intersects(viewBoxJS, pathJS); | ||
} | ||
if (item.type === 'element' && item.name === 'svg') { | ||
parseViewBox(item); | ||
} | ||
return intersects(viewBoxJS, pathJS); | ||
} | ||
if (item.isElem('svg')) | ||
{ | ||
parseViewBox(item); | ||
} | ||
return true; | ||
return true; | ||
}; | ||
@@ -62,5 +63,9 @@ | ||
*/ | ||
function hasTransform(item) | ||
{ | ||
return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode)); | ||
function hasTransform(item) { | ||
return ( | ||
item.attributes.transform != null || | ||
(item.parentNode && | ||
item.parentNode.type === 'element' && | ||
hasTransform(item.parentNode)) | ||
); | ||
} | ||
@@ -73,46 +78,43 @@ | ||
*/ | ||
function parseViewBox(svg) | ||
{ | ||
var viewBoxData = ''; | ||
if (svg.hasAttr('viewBox')) | ||
{ | ||
// Remove commas and plus signs, normalize and trim whitespace | ||
viewBoxData = svg.attr('viewBox').value; | ||
} | ||
else if (svg.hasAttr('height') && svg.hasAttr('width')) | ||
{ | ||
viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value; | ||
} | ||
function parseViewBox(svg) { | ||
var viewBoxData = ''; | ||
if (svg.attributes.viewBox != null) { | ||
// Remove commas and plus signs, normalize and trim whitespace | ||
viewBoxData = svg.attributes.viewBox; | ||
} else if (svg.attributes.height != null && svg.attributes.width != null) { | ||
viewBoxData = `0 0 ${svg.attributes.width} ${svg.attributes.height}`; | ||
} | ||
// Remove commas and plus signs, normalize and trim whitespace | ||
viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, ''); | ||
// Remove commas and plus signs, normalize and trim whitespace | ||
viewBoxData = viewBoxData | ||
.replace(/[,+]|px/g, ' ') | ||
.replace(/\s+/g, ' ') | ||
.replace(/^\s*|\s*$/g, ''); | ||
// Ensure that the dimensions are 4 values separated by space | ||
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData); | ||
if (!m) | ||
{ | ||
return; | ||
} | ||
// Ensure that the dimensions are 4 values separated by space | ||
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec( | ||
viewBoxData | ||
); | ||
if (!m) { | ||
return; | ||
} | ||
// Store the viewBox boundaries | ||
viewBox = { | ||
left: parseFloat(m[1]), | ||
top: parseFloat(m[2]), | ||
right: parseFloat(m[1]) + parseFloat(m[3]), | ||
bottom: parseFloat(m[2]) + parseFloat(m[4]) | ||
}; | ||
// Store the viewBox boundaries | ||
viewBox = { | ||
left: parseFloat(m[1]), | ||
top: parseFloat(m[2]), | ||
right: parseFloat(m[1]) + parseFloat(m[3]), | ||
bottom: parseFloat(m[2]) + parseFloat(m[4]), | ||
}; | ||
var path = new JSAPI({ | ||
elem: 'path', | ||
prefix: '', | ||
local: 'path' | ||
}); | ||
path.addAttr({ | ||
name: 'd', | ||
prefix: '', | ||
local: 'd', | ||
value: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z' | ||
}); | ||
var path = new JSAPI({ | ||
type: 'element', | ||
name: 'path', | ||
attributes: { | ||
d: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z', | ||
}, | ||
content: [], | ||
}); | ||
viewBoxJS = path2js(path); | ||
viewBoxJS = path2js(path); | ||
} | ||
@@ -126,14 +128,17 @@ | ||
*/ | ||
function pathMovesWithinViewBox(path) | ||
{ | ||
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m; | ||
while (null !== (m = regexp.exec(path))) | ||
{ | ||
if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom) | ||
{ | ||
return true; | ||
} | ||
} | ||
function pathMovesWithinViewBox(path) { | ||
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, | ||
m; | ||
while (null !== (m = regexp.exec(path))) { | ||
if ( | ||
m[1] >= viewBox.left && | ||
m[1] <= viewBox.right && | ||
m[2] >= viewBox.top && | ||
m[2] <= viewBox.bottom | ||
) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
return false; | ||
} |
@@ -19,11 +19,11 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
if ( | ||
item.isElem('image') && | ||
item.hasAttrLocal('href', /(\.|image\/)(jpg|png|gif)/) | ||
) { | ||
return false; | ||
} | ||
exports.fn = function (item) { | ||
if ( | ||
item.type === 'element' && | ||
item.name === 'image' && | ||
item.attributes['xlink:href'] != null && | ||
/(\.|image\/)(jpg|png|gif)/.test(item.attributes['xlink:href']) | ||
) { | ||
return false; | ||
} | ||
}; |
@@ -19,6 +19,4 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
return !item.isElem('script'); | ||
exports.fn = function (item) { | ||
return !item.isElem('script'); | ||
}; |
@@ -19,6 +19,4 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
return !item.isElem('style'); | ||
exports.fn = function (item) { | ||
return !item.isElem('style'); | ||
}; |
@@ -19,6 +19,4 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
return !item.isElem('title'); | ||
exports.fn = function (item) { | ||
return !item.isElem('title'); | ||
}; |
'use strict'; | ||
const { parseName } = require('../lib/svgo/tools.js'); | ||
exports.type = 'perItem'; | ||
@@ -7,50 +9,50 @@ | ||
exports.description = 'removes unknown elements content and attributes, removes attrs with default values'; | ||
exports.description = | ||
'removes unknown elements content and attributes, removes attrs with default values'; | ||
exports.params = { | ||
unknownContent: true, | ||
unknownAttrs: true, | ||
defaultAttrs: true, | ||
uselessOverrides: true, | ||
keepDataAttrs: true, | ||
keepAriaAttrs: true, | ||
keepRoleAttr: false | ||
unknownContent: true, | ||
unknownAttrs: true, | ||
defaultAttrs: true, | ||
uselessOverrides: true, | ||
keepDataAttrs: true, | ||
keepAriaAttrs: true, | ||
keepRoleAttr: false, | ||
}; | ||
var collections = require('./_collections'), | ||
elems = collections.elems, | ||
attrsGroups = collections.attrsGroups, | ||
elemsGroups = collections.elemsGroups, | ||
attrsGroupsDefaults = collections.attrsGroupsDefaults, | ||
attrsInheritable = collections.inheritableAttrs, | ||
applyGroups = collections.presentationNonInheritableGroupAttrs; | ||
elems = collections.elems, | ||
attrsGroups = collections.attrsGroups, | ||
elemsGroups = collections.elemsGroups, | ||
attrsGroupsDefaults = collections.attrsGroupsDefaults, | ||
attrsInheritable = collections.inheritableAttrs, | ||
applyGroups = collections.presentationNonInheritableGroupAttrs; | ||
// collect and extend all references | ||
for (const elem of Object.values(elems)) { | ||
if (elem.attrsGroups) { | ||
elem.attrs = elem.attrs || []; | ||
if (elem.attrsGroups) { | ||
elem.attrs = elem.attrs || []; | ||
elem.attrsGroups.forEach(function(attrsGroupName) { | ||
elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]); | ||
elem.attrsGroups.forEach(function (attrsGroupName) { | ||
elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]); | ||
var groupDefaults = attrsGroupsDefaults[attrsGroupName]; | ||
var groupDefaults = attrsGroupsDefaults[attrsGroupName]; | ||
if (groupDefaults) { | ||
elem.defaults = elem.defaults || {}; | ||
if (groupDefaults) { | ||
elem.defaults = elem.defaults || {}; | ||
for (const [attrName, attr] of Object.entries(groupDefaults)) { | ||
elem.defaults[attrName] = attr; | ||
} | ||
} | ||
}); | ||
for (const [attrName, attr] of Object.entries(groupDefaults)) { | ||
elem.defaults[attrName] = attr; | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
if (elem.contentGroups) { | ||
elem.content = elem.content || []; | ||
if (elem.contentGroups) { | ||
elem.content = elem.content || []; | ||
elem.contentGroups.forEach(function(contentGroupName) { | ||
elem.content = elem.content.concat(elemsGroups[contentGroupName]); | ||
}); | ||
} | ||
elem.contentGroups.forEach(function (contentGroupName) { | ||
elem.content = elem.content.concat(elemsGroups[contentGroupName]); | ||
}); | ||
} | ||
} | ||
@@ -68,83 +70,61 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
// elems w/o namespace prefix | ||
if (item.type === 'element' && !parseName(item.name).prefix) { | ||
var elem = item.name; | ||
// elems w/o namespace prefix | ||
if (item.isElem() && !item.prefix) { | ||
var elem = item.elem; | ||
// remove unknown element's content | ||
// remove unknown element's content | ||
if ( | ||
params.unknownContent && | ||
elems[elem] && // make sure we know of this element before checking its children | ||
elem !== 'foreignObject' // Don't check foreignObject | ||
) { | ||
item.children.forEach(function (content, i) { | ||
if ( | ||
params.unknownContent && | ||
!item.isEmpty() && | ||
elems[elem] && // make sure we know of this element before checking its children | ||
elem !== 'foreignObject' // Don't check foreignObject | ||
content.type === 'element' && | ||
!parseName(content.name).prefix && | ||
((elems[elem].content && // Do we have a record of its permitted content? | ||
elems[elem].content.indexOf(content.name) === -1) || | ||
(!elems[elem].content && // we dont know about its permitted content | ||
!elems[content.name])) // check that we know about the element at all | ||
) { | ||
item.content.forEach(function(content, i) { | ||
if ( | ||
content.isElem() && | ||
!content.prefix && | ||
( | ||
( | ||
elems[elem].content && // Do we have a record of its permitted content? | ||
elems[elem].content.indexOf(content.elem) === -1 | ||
) || | ||
( | ||
!elems[elem].content && // we dont know about its permitted content | ||
!elems[content.elem] // check that we know about the element at all | ||
) | ||
) | ||
) { | ||
item.content.splice(i, 1); | ||
} | ||
}); | ||
item.children.splice(i, 1); | ||
} | ||
}); | ||
} | ||
// remove element's unknown attrs and attrs with default values | ||
if (elems[elem] && elems[elem].attrs) { | ||
item.eachAttr(function(attr) { | ||
if ( | ||
attr.name !== 'xmlns' && | ||
(attr.prefix === 'xml' || !attr.prefix) && | ||
(!params.keepDataAttrs || attr.name.indexOf('data-') != 0) && | ||
(!params.keepAriaAttrs || attr.name.indexOf('aria-') != 0) && | ||
(!params.keepRoleAttr || attr.name != 'role') | ||
) { | ||
if ( | ||
// unknown attrs | ||
( | ||
params.unknownAttrs && | ||
elems[elem].attrs.indexOf(attr.name) === -1 | ||
) || | ||
// attrs with default values | ||
( | ||
params.defaultAttrs && | ||
!item.hasAttr('id') && | ||
elems[elem].defaults && | ||
elems[elem].defaults[attr.name] === attr.value && ( | ||
attrsInheritable.indexOf(attr.name) < 0 || | ||
!item.parentNode.computedAttr(attr.name) | ||
) | ||
) || | ||
// useless overrides | ||
( | ||
params.uselessOverrides && | ||
!item.hasAttr('id') && | ||
applyGroups.indexOf(attr.name) < 0 && | ||
attrsInheritable.indexOf(attr.name) > -1 && | ||
item.parentNode.computedAttr(attr.name, attr.value) | ||
) | ||
) { | ||
item.removeAttr(attr.name); | ||
} | ||
} | ||
}); | ||
// remove element's unknown attrs and attrs with default values | ||
if (elems[elem] && elems[elem].attrs) { | ||
for (const [name, value] of Object.entries(item.attributes)) { | ||
const { prefix } = parseName(name); | ||
if ( | ||
name !== 'xmlns' && | ||
(prefix === 'xml' || !prefix) && | ||
(!params.keepDataAttrs || name.indexOf('data-') != 0) && | ||
(!params.keepAriaAttrs || name.indexOf('aria-') != 0) && | ||
(!params.keepRoleAttr || name != 'role') | ||
) { | ||
if ( | ||
// unknown attrs | ||
(params.unknownAttrs && elems[elem].attrs.indexOf(name) === -1) || | ||
// attrs with default values | ||
(params.defaultAttrs && | ||
item.attributes.id == null && | ||
elems[elem].defaults && | ||
elems[elem].defaults[name] === value && | ||
(attrsInheritable.includes(name) === false || | ||
!item.parentNode.computedAttr(name))) || | ||
// useless overrides | ||
(params.uselessOverrides && | ||
item.attributes.id == null && | ||
applyGroups.includes(name) === false && | ||
attrsInheritable.includes(name) === true && | ||
item.parentNode.computedAttr(name, value)) | ||
) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { traverse } = require('../lib/xast.js'); | ||
const { parseName } = require('../lib/svgo/tools.js'); | ||
exports.type = 'full'; | ||
@@ -17,94 +20,62 @@ | ||
*/ | ||
exports.fn = function(data) { | ||
exports.fn = function (root) { | ||
let svgElem; | ||
const xmlnsCollection = []; | ||
var svgElem, | ||
xmlnsCollection = []; | ||
/** | ||
* Remove namespace from collection. | ||
* | ||
* @param {String} ns namescape name | ||
*/ | ||
function removeNSfromCollection(ns) { | ||
const pos = xmlnsCollection.indexOf(ns); | ||
/** | ||
* Remove namespace from collection. | ||
* | ||
* @param {String} ns namescape name | ||
*/ | ||
function removeNSfromCollection(ns) { | ||
// if found - remove ns from the namespaces collection | ||
if (pos > -1) { | ||
xmlnsCollection.splice(pos, 1); | ||
} | ||
} | ||
var pos = xmlnsCollection.indexOf(ns); | ||
traverse(root, (node) => { | ||
if (node.type === 'element') { | ||
if (node.name === 'svg') { | ||
for (const name of Object.keys(node.attributes)) { | ||
const { prefix, local } = parseName(name); | ||
// collect namespaces | ||
if (prefix === 'xmlns' && local) { | ||
xmlnsCollection.push(local); | ||
} | ||
} | ||
// if found - remove ns from the namespaces collection | ||
if (pos > -1) { | ||
xmlnsCollection.splice(pos, 1); | ||
// if svg element has ns-attr | ||
if (xmlnsCollection.length) { | ||
// save svg element | ||
svgElem = node; | ||
} | ||
} | ||
} | ||
if (xmlnsCollection.length) { | ||
const { prefix } = parseName(node.name); | ||
// check node for the ns-attrs | ||
if (prefix) { | ||
removeNSfromCollection(prefix); | ||
} | ||
/** | ||
* Bananas! | ||
* | ||
* @param {Array} items input items | ||
* | ||
* @return {Array} output items | ||
*/ | ||
function monkeys(items) { | ||
var i = 0, | ||
length = items.content.length; | ||
while(i < length) { | ||
var item = items.content[i]; | ||
if (item.isElem('svg')) { | ||
item.eachAttr(function(attr) { | ||
// collect namespaces | ||
if (attr.prefix === 'xmlns' && attr.local) { | ||
xmlnsCollection.push(attr.local); | ||
} | ||
}); | ||
// if svg element has ns-attr | ||
if (xmlnsCollection.length) { | ||
// save svg element | ||
svgElem = item; | ||
} | ||
} | ||
if (xmlnsCollection.length) { | ||
// check item for the ns-attrs | ||
if (item.prefix) { | ||
removeNSfromCollection(item.prefix); | ||
} | ||
// check each attr for the ns-attrs | ||
item.eachAttr(function(attr) { | ||
removeNSfromCollection(attr.prefix); | ||
}); | ||
} | ||
// if nothing is found - go deeper | ||
if (xmlnsCollection.length && item.content) { | ||
monkeys(item); | ||
} | ||
i++; | ||
// check each attr for the ns-attrs | ||
for (const name of Object.keys(node.attributes)) { | ||
const { prefix } = parseName(name); | ||
removeNSfromCollection(prefix); | ||
} | ||
return items; | ||
} | ||
} | ||
}); | ||
data = monkeys(data); | ||
// remove svg element ns-attributes if they are not used even once | ||
if (xmlnsCollection.length) { | ||
xmlnsCollection.forEach(function(name) { | ||
svgElem.removeAttr('xmlns:' + name); | ||
}); | ||
// remove svg element ns-attributes if they are not used even once | ||
if (xmlnsCollection.length) { | ||
for (const name of xmlnsCollection) { | ||
delete svgElem.attributes['xmlns:' + name]; | ||
} | ||
} | ||
return data; | ||
return root; | ||
}; |
'use strict'; | ||
const { elemsGroups } = require('./_collections'); | ||
exports.type = 'perItem'; | ||
@@ -9,4 +11,2 @@ | ||
var nonRendering = require('./_collections').elemsGroups.nonRendering; | ||
/** | ||
@@ -20,36 +20,31 @@ * Removes content of defs and properties that aren't rendered directly without ids. | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('defs')) { | ||
if (item.content) { | ||
item.content = getUsefulItems(item, []); | ||
} | ||
if (item.isEmpty()) return false; | ||
} else if (item.isElem(nonRendering) && !item.hasAttr('id')) { | ||
exports.fn = function (item) { | ||
if (item.type === 'element') { | ||
if (item.name === 'defs') { | ||
item.children = getUsefulItems(item, []); | ||
if (item.children.length === 0) { | ||
return false; | ||
} | ||
} else if ( | ||
elemsGroups.nonRendering.includes(item.name) && | ||
item.attributes.id == null | ||
) { | ||
return false; | ||
} | ||
} | ||
}; | ||
function getUsefulItems(item, usefulItems) { | ||
for (const child of item.children) { | ||
if (child.type === 'element') { | ||
if (child.attributes.id != null || child.name === 'style') { | ||
usefulItems.push(child); | ||
child.parentNode = item; | ||
} else { | ||
child.children = getUsefulItems(child, usefulItems); | ||
} | ||
} | ||
} | ||
item.content.forEach(function(child) { | ||
if (child.hasAttr('id') || child.isElem('style')) { | ||
usefulItems.push(child); | ||
child.parentNode = item; | ||
} else if (!child.isEmpty()) { | ||
child.content = getUsefulItems(child, usefulItems); | ||
} | ||
}); | ||
return usefulItems; | ||
return usefulItems; | ||
} |
@@ -10,12 +10,12 @@ 'use strict'; | ||
exports.params = { | ||
stroke: true, | ||
fill: true, | ||
removeNone: false, | ||
hasStyleOrScript: false | ||
stroke: true, | ||
fill: true, | ||
removeNone: false, | ||
hasStyleOrScript: false, | ||
}; | ||
var shape = require('./_collections').elemsGroups.shape, | ||
regStrokeProps = /^stroke/, | ||
regFillProps = /^fill-/, | ||
styleOrScript = ['style', 'script']; | ||
regStrokeProps = /^stroke/, | ||
regFillProps = /^fill-/, | ||
styleOrScript = ['style', 'script']; | ||
@@ -31,79 +31,64 @@ /** | ||
*/ | ||
exports.fn = function(item, params) { | ||
if (item.isElem(styleOrScript)) { | ||
params.hasStyleOrScript = true; | ||
} | ||
exports.fn = function (item, params) { | ||
if (item.isElem(styleOrScript)) { | ||
params.hasStyleOrScript = true; | ||
} | ||
if (!params.hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) { | ||
if ( | ||
!params.hasStyleOrScript && | ||
item.isElem(shape) && | ||
!item.computedAttr('id') | ||
) { | ||
var stroke = params.stroke && item.computedAttr('stroke'), | ||
fill = params.fill && !item.computedAttr('fill', 'none'); | ||
var stroke = params.stroke && item.computedAttr('stroke'), | ||
fill = params.fill && !item.computedAttr('fill', 'none'); | ||
// remove stroke* | ||
if ( | ||
params.stroke && | ||
(!stroke || | ||
stroke == 'none' || | ||
item.computedAttr('stroke-opacity', '0') || | ||
item.computedAttr('stroke-width', '0')) | ||
) { | ||
// stroke-width may affect the size of marker-end | ||
if ( | ||
item.computedAttr('stroke-width', '0') === true || | ||
item.computedAttr('marker-end') == null | ||
) { | ||
var parentStroke = item.parentNode.computedAttr('stroke'), | ||
declineStroke = parentStroke && parentStroke != 'none'; | ||
// remove stroke* | ||
if ( | ||
params.stroke && | ||
( | ||
!stroke || | ||
stroke == 'none' || | ||
item.computedAttr('stroke-opacity', '0') || | ||
item.computedAttr('stroke-width', '0') | ||
) | ||
) { | ||
// stroke-width may affect the size of marker-end | ||
if ( | ||
item.computedAttr('stroke-width', '0') === true || | ||
item.computedAttr('marker-end') == null | ||
) { | ||
var parentStroke = item.parentNode.computedAttr('stroke'), | ||
declineStroke = parentStroke && parentStroke != 'none'; | ||
item.eachAttr(function(attr) { | ||
if (regStrokeProps.test(attr.name)) { | ||
item.removeAttr(attr.name); | ||
} | ||
}); | ||
if (declineStroke) item.addAttr({ | ||
name: 'stroke', | ||
value: 'none', | ||
prefix: '', | ||
local: 'stroke' | ||
}); | ||
} | ||
for (const name of Object.keys(item.attributes)) { | ||
if (regStrokeProps.test(name)) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
// remove fill* | ||
if ( | ||
params.fill && | ||
(!fill || item.computedAttr('fill-opacity', '0')) | ||
) { | ||
item.eachAttr(function(attr) { | ||
if (regFillProps.test(attr.name)) { | ||
item.removeAttr(attr.name); | ||
} | ||
}); | ||
if (fill) { | ||
if (item.hasAttr('fill')) | ||
item.attr('fill').value = 'none'; | ||
else | ||
item.addAttr({ | ||
name: 'fill', | ||
value: 'none', | ||
prefix: '', | ||
local: 'fill' | ||
}); | ||
} | ||
if (declineStroke) { | ||
item.attributes.stroke = 'none'; | ||
} | ||
} | ||
} | ||
if (params.removeNone && | ||
(!stroke || item.hasAttr('stroke') && item.attr('stroke').value=='none') && | ||
(!fill || item.hasAttr('fill') && item.attr('fill').value=='none')) { | ||
return false; | ||
// remove fill* | ||
if (params.fill && (!fill || item.computedAttr('fill-opacity', '0'))) { | ||
for (const name of Object.keys(item.attributes)) { | ||
if (regFillProps.test(name)) { | ||
delete item.attributes[name]; | ||
} | ||
} | ||
if (fill) { | ||
item.attributes.fill = 'none'; | ||
} | ||
} | ||
if ( | ||
params.removeNone && | ||
(!stroke || item.attributes.stroke == 'none') && | ||
(!fill || item.attributes.fill == 'none') | ||
) { | ||
return false; | ||
} | ||
} | ||
}; |
'use strict'; | ||
const { closestByName } = require('../lib/xast.js'); | ||
exports.type = 'perItem'; | ||
@@ -9,3 +11,3 @@ | ||
var viewBoxElems = ['svg', 'pattern', 'symbol']; | ||
const viewBoxElems = ['svg', 'pattern', 'symbol']; | ||
@@ -29,13 +31,14 @@ /** | ||
if ( | ||
item.isElem(viewBoxElems) && | ||
item.hasAttr('viewBox') && | ||
item.hasAttr('width') && | ||
item.hasAttr('height') | ||
item.type === 'element' && | ||
viewBoxElems.includes(item.name) && | ||
item.attributes.viewBox != null && | ||
item.attributes.width != null && | ||
item.attributes.height != null | ||
) { | ||
// TODO remove width/height for such case instead | ||
if (item.isElem('svg') && item.closestElem('svg')) { | ||
if (item.name === 'svg' && closestByName(item.parentNode, 'svg')) { | ||
return; | ||
} | ||
var nums = item.attr('viewBox').value.split(/[ ,]+/g); | ||
const nums = item.attributes.viewBox.split(/[ ,]+/g); | ||
@@ -45,8 +48,8 @@ if ( | ||
nums[1] === '0' && | ||
item.attr('width').value.replace(/px$/, '') === nums[2] && // could use parseFloat too | ||
item.attr('height').value.replace(/px$/, '') === nums[3] | ||
item.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too | ||
item.attributes.height.replace(/px$/, '') === nums[3] | ||
) { | ||
item.removeAttr('viewBox'); | ||
delete item.attributes.viewBox; | ||
} | ||
} | ||
}; |
@@ -7,3 +7,4 @@ 'use strict'; | ||
exports.description = 'removes xmlns attribute (for inline svg, disabled by default)'; | ||
exports.description = | ||
'removes xmlns attribute (for inline svg, disabled by default)'; | ||
@@ -23,8 +24,6 @@ /** | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('svg') && item.hasAttr('xmlns')) { | ||
item.removeAttr('xmlns'); | ||
} | ||
}; | ||
exports.fn = function (item) { | ||
if (item.type === 'element' && item.name === 'svg') { | ||
delete item.attributes.xmlns; | ||
} | ||
}; |
@@ -20,6 +20,7 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
return !(item.processinginstruction && item.processinginstruction.name === 'xml'); | ||
exports.fn = function (item) { | ||
if (item.type === 'instruction' && item.name === 'xml') { | ||
return false; | ||
} | ||
return true; | ||
}; |
@@ -1,82 +0,5 @@ | ||
/** | ||
* @license | ||
* The MIT License | ||
* | ||
* Copyright © 2012–2016 Kir Belevich | ||
* | ||
* Permission is hereby granted, free of charge, to any person | ||
* obtaining a copy of this software and associated documentation | ||
* files (the "Software"), to deal in the Software without | ||
* restriction, including without limitation the rights to use, | ||
* copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the | ||
* Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
* OTHER DEALINGS IN THE SOFTWARE. | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* | ||
* Лицензия MIT | ||
* | ||
* Copyright © 2012–2016 Кир Белевич | ||
* | ||
* Данная лицензия разрешает лицам, получившим копию | ||
* данного | ||
* программного обеспечения и сопутствующей | ||
* документации | ||
* (в дальнейшем именуемыми «Программное Обеспечение»), | ||
* безвозмездно | ||
* использовать Программное Обеспечение без | ||
* ограничений, включая | ||
* неограниченное право на использование, копирование, | ||
* изменение, | ||
* добавление, публикацию, распространение, | ||
* сублицензирование | ||
* и/или продажу копий Программного Обеспечения, также | ||
* как и лицам, | ||
* которым предоставляется данное Программное | ||
* Обеспечение, | ||
* при соблюдении следующих условий: | ||
* | ||
* Указанное выше уведомление об авторском праве и | ||
* данные условия | ||
* должны быть включены во все копии или значимые части | ||
* данного | ||
* Программного Обеспечения. | ||
* | ||
* ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК | ||
* ЕСТЬ», | ||
* БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ | ||
* ПОДРАЗУМЕВАЕМЫХ, | ||
* ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ | ||
* ПРИГОДНОСТИ, | ||
* СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И | ||
* ОТСУТСТВИЯ НАРУШЕНИЙ | ||
* ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ | ||
* НЕСУТ | ||
* ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ | ||
* ИЛИ ДРУГИХ | ||
* ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ | ||
* ИНОМУ, | ||
* ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С | ||
* ПРОГРАММНЫМ | ||
* ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО | ||
* ОБЕСПЕЧЕНИЯ | ||
* ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. | ||
*/ | ||
'use strict'; | ||
var JSAPI = require('../lib/svgo/jsAPI'); | ||
const { traverse } = require('../lib/xast.js'); | ||
const JSAPI = require('../lib/svgo/jsAPI'); | ||
@@ -87,5 +10,6 @@ exports.type = 'full'; | ||
exports.description = 'Finds <path> elements with the same d, fill, and ' + | ||
'stroke, and converts them to <use> elements ' + | ||
'referencing a single <path> def.'; | ||
exports.description = | ||
'Finds <path> elements with the same d, fill, and ' + | ||
'stroke, and converts them to <use> elements ' + | ||
'referencing a single <path> def.'; | ||
@@ -98,17 +22,21 @@ /** | ||
*/ | ||
exports.fn = function(data) { | ||
exports.fn = function (root) { | ||
const seen = new Map(); | ||
let count = 0; | ||
const defs = []; | ||
traverse(data, item => { | ||
if (!item.isElem('path') || !item.hasAttr('d')) { | ||
traverse(root, (node) => { | ||
if ( | ||
node.type !== 'element' || | ||
node.name !== 'path' || | ||
node.attributes.d == null | ||
) { | ||
return; | ||
} | ||
const d = item.attr('d').value; | ||
const fill = (item.hasAttr('fill') && item.attr('fill').value) || ''; | ||
const stroke = (item.hasAttr('stroke') && item.attr('stroke').value) || ''; | ||
const d = node.attributes.d; | ||
const fill = node.attributes.fill || ''; | ||
const stroke = node.attributes.stroke || ''; | ||
const key = d + ';s:' + stroke + ';f:' + fill; | ||
const hasSeen = seen.get(key); | ||
if (!hasSeen) { | ||
seen.set(key, {elem: item, reused: false}); | ||
seen.set(key, { elem: node, reused: false }); | ||
return; | ||
@@ -118,14 +46,20 @@ } | ||
hasSeen.reused = true; | ||
if (!hasSeen.elem.hasAttr('id')) { | ||
hasSeen.elem.addAttr({name: 'id', local: 'id', | ||
prefix: '', value: 'reuse-' + (count++)}); | ||
if (hasSeen.elem.attributes.id == null) { | ||
hasSeen.elem.attributes.id = 'reuse-' + count++; | ||
} | ||
defs.push(hasSeen.elem); | ||
} | ||
convertToUse(item, hasSeen.elem.attr('id').value); | ||
convertToUse(node, hasSeen.elem.attributes.id); | ||
}); | ||
if (defs.length > 0) { | ||
const defsTag = new JSAPI({ | ||
elem: 'defs', prefix: '', local: 'defs', content: [], attrs: []}, data); | ||
data.content[0].spliceContent(0, 0, defsTag); | ||
const defsTag = new JSAPI( | ||
{ | ||
type: 'element', | ||
name: 'defs', | ||
attributes: {}, | ||
children: [], | ||
}, | ||
root | ||
); | ||
root.children[0].spliceContent(0, 0, defsTag); | ||
for (let def of defs) { | ||
@@ -142,10 +76,10 @@ // Remove class and style before copying to avoid circular refs in | ||
def.class = defClass; | ||
defClone.removeAttr('transform'); | ||
delete defClone.attributes.transform; | ||
defsTag.spliceContent(0, 0, defClone); | ||
// Convert the original def to a use so the first usage isn't duplicated. | ||
def = convertToUse(def, defClone.attr('id').value); | ||
def.removeAttr('id'); | ||
def = convertToUse(def, defClone.attributes.id); | ||
delete def.attributes.id; | ||
} | ||
} | ||
return data; | ||
return root; | ||
}; | ||
@@ -156,20 +90,8 @@ | ||
item.renameElem('use'); | ||
item.removeAttr('d'); | ||
item.removeAttr('stroke'); | ||
item.removeAttr('fill'); | ||
item.addAttr({name: 'xlink:href', local: 'xlink:href', | ||
prefix: 'none', value: '#' + href}); | ||
delete item.attributes.d; | ||
delete item.attributes.stroke; | ||
delete item.attributes.fill; | ||
item.attributes['xlink:href'] = '#' + href; | ||
delete item.pathJS; | ||
return item; | ||
} | ||
/** */ | ||
function traverse(parent, callback) { | ||
if (parent.isEmpty()) { | ||
return; | ||
} | ||
for (let child of parent.content) { | ||
callback(child); | ||
traverse(child, callback); | ||
} | ||
} |
'use strict'; | ||
const { parseName } = require('../lib/svgo/tools.js'); | ||
exports.type = 'perItem'; | ||
@@ -10,11 +12,21 @@ | ||
exports.params = { | ||
order: [ | ||
'id', | ||
'width', 'height', | ||
'x', 'x1', 'x2', | ||
'y', 'y1', 'y2', | ||
'cx', 'cy', 'r', | ||
'fill', 'stroke', 'marker', | ||
'd', 'points' | ||
] | ||
order: [ | ||
'id', | ||
'width', | ||
'height', | ||
'x', | ||
'x1', | ||
'x2', | ||
'y', | ||
'y1', | ||
'y2', | ||
'cx', | ||
'cy', | ||
'r', | ||
'fill', | ||
'stroke', | ||
'marker', | ||
'd', | ||
'points', | ||
], | ||
}; | ||
@@ -30,57 +42,49 @@ | ||
*/ | ||
exports.fn = function(item, params) { | ||
exports.fn = function (item, params) { | ||
const orderlen = params.order.length + 1; | ||
const xmlnsOrder = params.xmlnsOrder || 'front'; | ||
var attrs = [], | ||
sorted = {}, | ||
orderlen = params.order.length + 1, | ||
xmlnsOrder = params.xmlnsOrder || 'front'; | ||
if (item.type === 'element') { | ||
const attrs = Object.entries(item.attributes); | ||
if (item.elem) { | ||
attrs.sort(([aName], [bName]) => { | ||
const { prefix: aPrefix } = parseName(aName); | ||
const { prefix: bPrefix } = parseName(bName); | ||
if (aPrefix != bPrefix) { | ||
// xmlns attributes implicitly have the prefix xmlns | ||
if (xmlnsOrder == 'front') { | ||
if (aPrefix === 'xmlns') return -1; | ||
if (bPrefix === 'xmlns') return 1; | ||
} | ||
return aPrefix < bPrefix ? -1 : 1; | ||
} | ||
item.eachAttr(function(attr) { | ||
attrs.push(attr); | ||
}); | ||
let aindex = orderlen; | ||
let bindex = orderlen; | ||
attrs.sort(function(a, b) { | ||
if (a.prefix != b.prefix) { | ||
// xmlns attributes implicitly have the prefix xmlns | ||
if (xmlnsOrder == 'front') { | ||
if (a.prefix == 'xmlns') | ||
return -1; | ||
if (b.prefix == 'xmlns') | ||
return 1; | ||
} | ||
return a.prefix < b.prefix ? -1 : 1; | ||
} | ||
for (let i = 0; i < params.order.length; i++) { | ||
if (aName == params.order[i]) { | ||
aindex = i; | ||
} else if (aName.indexOf(params.order[i] + '-') === 0) { | ||
aindex = i + 0.5; | ||
} | ||
if (bName == params.order[i]) { | ||
bindex = i; | ||
} else if (bName.indexOf(params.order[i] + '-') === 0) { | ||
bindex = i + 0.5; | ||
} | ||
} | ||
var aindex = orderlen; | ||
var bindex = orderlen; | ||
if (aindex != bindex) { | ||
return aindex - bindex; | ||
} | ||
return aName < bName ? -1 : 1; | ||
}); | ||
for (var i = 0; i < params.order.length; i++) { | ||
if (a.name == params.order[i]) { | ||
aindex = i; | ||
} else if (a.name.indexOf(params.order[i] + '-') === 0) { | ||
aindex = i + .5; | ||
} | ||
if (b.name == params.order[i]) { | ||
bindex = i; | ||
} else if (b.name.indexOf(params.order[i] + '-') === 0) { | ||
bindex = i + .5; | ||
} | ||
} | ||
if (aindex != bindex) { | ||
return aindex - bindex; | ||
} | ||
return a.name < b.name ? -1 : 1; | ||
}); | ||
attrs.forEach(function (attr) { | ||
sorted[attr.name] = attr; | ||
}); | ||
item.attrs = sorted; | ||
} | ||
const sorted = {}; | ||
for (const [name, value] of attrs) { | ||
sorted[name] = value; | ||
} | ||
item.attributes = sorted; | ||
} | ||
}; |
@@ -18,31 +18,25 @@ 'use strict'; | ||
*/ | ||
exports.fn = function(item) { | ||
if (item.isElem('defs')) { | ||
if (item.content) { | ||
var frequency = item.content.reduce(function (frequency, child) { | ||
if (child.elem in frequency) { | ||
frequency[child.elem]++; | ||
} else { | ||
frequency[child.elem] = 1; | ||
} | ||
return frequency; | ||
}, {}); | ||
item.content.sort(function (a, b) { | ||
var frequencyComparison = frequency[b.elem] - frequency[a.elem]; | ||
if (frequencyComparison !== 0 ) { | ||
return frequencyComparison; | ||
} | ||
var lengthComparison = b.elem.length - a.elem.length; | ||
if (lengthComparison !== 0) { | ||
return lengthComparison; | ||
} | ||
return a.elem != b.elem ? a.elem > b.elem ? -1 : 1 : 0; | ||
}); | ||
} | ||
return true; | ||
} | ||
exports.fn = function (item) { | ||
if (item.isElem('defs')) { | ||
var frequency = item.children.reduce(function (frequency, child) { | ||
if (child.name in frequency) { | ||
frequency[child.name]++; | ||
} else { | ||
frequency[child.name] = 1; | ||
} | ||
return frequency; | ||
}, {}); | ||
item.children.sort(function (a, b) { | ||
var frequencyComparison = frequency[b.name] - frequency[a.name]; | ||
if (frequencyComparison !== 0) { | ||
return frequencyComparison; | ||
} | ||
var lengthComparison = b.name.length - a.name.length; | ||
if (lengthComparison !== 0) { | ||
return lengthComparison; | ||
} | ||
return a.name != b.name ? (a.name > b.name ? -1 : 1) : 0; | ||
}); | ||
return true; | ||
} | ||
}; |
@@ -125,2 +125,3 @@ <div align="center"> | ||
'cleanupAttrs', | ||
'mergeStyles', | ||
'inlineStyles', | ||
@@ -212,2 +213,3 @@ 'minifyStyles', | ||
| [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` | | ||
| [mergeStyles](https://github.com/svg/svgo/blob/master/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` | | ||
| [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` | | ||
@@ -214,0 +216,0 @@ | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` | |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
79
294
907121
14023
1