diff --git a/.svgo.yml b/.svgo.yml index 5f76ca58..f4bd0c99 100644 --- a/.svgo.yml +++ b/.svgo.yml @@ -61,6 +61,7 @@ plugins: - removeStyleElement - removeScriptElement - addAttributesToSVGElement + - removeOffCanvasPaths # configure the indent (default 4 spaces) used by `--pretty` here: # diff --git a/plugins/removeOffCanvasPaths.js b/plugins/removeOffCanvasPaths.js new file mode 100644 index 00000000..5bcb9a1f --- /dev/null +++ b/plugins/removeOffCanvasPaths.js @@ -0,0 +1,133 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)'; + +var SVGO = require('../lib/svgo.js'), + _path = require('./_path.js'), + intersects = _path.intersects, + path2js = _path.path2js, + viewBox, + viewBoxJS; + +/** + * Remove elements that are drawn outside of the viewbox. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author JoshyPHP + */ +exports.fn = function(item) { + + if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined') + { + // Consider that any item with a transform attribute or a M instruction + // within the viewBox is visible + if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value)) + { + return true; + } + + var pathJS = path2js(item); + if (pathJS.length === 2) + { + // Use a closed clone of the path if it's too short for intersects() + pathJS = JSON.parse(JSON.stringify(pathJS)); + pathJS.push({ instruction: 'z' }); + } + + return intersects(viewBoxJS, pathJS); + } + if (item.isElem('svg')) + { + parseViewBox(item); + } + + return true; +}; + +/** + * Test whether given item or any of its ancestors has a transform attribute. + * + * @param {String} path + * @return {Boolean} + */ +function hasTransform(item) +{ + return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode)); +} + +/** + * Parse the viewBox coordinates and compute the JS representation of its path. + * + * @param {Object} svg svg element item + */ +function parseViewBox(svg) +{ + var viewBoxData = ''; + if (svg.hasAttr('viewBox')) + { + // Remove commas and plus signs, normalize and trim whitespace + viewBoxData = svg.attr('viewBox').value; + } + else if (svg.hasAttr('height') && svg.hasAttr('width')) + { + viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value; + } + + // Remove commas and plus signs, normalize and trim whitespace + viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, ''); + + // Ensure that the dimensions are 4 values separated by space + var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData); + if (!m) + { + return; + } + + // Store the viewBox boundaries + viewBox = { + left: parseFloat(m[1]), + top: parseFloat(m[2]), + right: parseFloat(m[1]) + parseFloat(m[3]), + bottom: parseFloat(m[2]) + parseFloat(m[4]) + }; + + var path = new SVGO().createContentItem({ + elem: 'path', + prefix: '', + local: 'path' + }); + path.addAttr({ + name: 'd', + prefix: '', + local: 'd', + value: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z' + }); + + viewBoxJS = path2js(path); +} + +/** + * Test whether given path has a M instruction with coordinates within the viewBox. + * + * @param {String} path + * @return {Boolean} + */ +function pathMovesWithinViewBox(path) +{ + var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m; + while (null !== (m = regexp.exec(path))) + { + if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom) + { + return true; + } + } + + return false; +} diff --git a/test/plugins/removeOffCanvasPaths.01.svg b/test/plugins/removeOffCanvasPaths.01.svg new file mode 100644 index 00000000..6bf8f431 --- /dev/null +++ b/test/plugins/removeOffCanvasPaths.01.svg @@ -0,0 +1,13 @@ + + + + + + + + +@@@ + + + + diff --git a/test/plugins/removeOffCanvasPaths.02.svg b/test/plugins/removeOffCanvasPaths.02.svg new file mode 100644 index 00000000..3597831a --- /dev/null +++ b/test/plugins/removeOffCanvasPaths.02.svg @@ -0,0 +1,15 @@ + + + + + + + + +@@@ + + + + + + diff --git a/test/plugins/removeOffCanvasPaths.03.svg b/test/plugins/removeOffCanvasPaths.03.svg new file mode 100644 index 00000000..c9a25d61 --- /dev/null +++ b/test/plugins/removeOffCanvasPaths.03.svg @@ -0,0 +1,12 @@ + + + + + + +@@@ + + + + + diff --git a/test/plugins/removeOffCanvasPaths.04.svg b/test/plugins/removeOffCanvasPaths.04.svg new file mode 100644 index 00000000..7ddb7f2e --- /dev/null +++ b/test/plugins/removeOffCanvasPaths.04.svg @@ -0,0 +1,9 @@ + + + + +@@@ + + + + diff --git a/test/plugins/removeOffCanvasPaths.05.svg b/test/plugins/removeOffCanvasPaths.05.svg new file mode 100644 index 00000000..1c762966 --- /dev/null +++ b/test/plugins/removeOffCanvasPaths.05.svg @@ -0,0 +1,15 @@ + + + + + + + +@@@ + + + + + + +