You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
3.8 KiB
136 lines
3.8 KiB
'use strict'; |
|
|
|
/** |
|
* @typedef {import('../lib/types').PathDataItem} PathDataItem |
|
*/ |
|
|
|
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js'); |
|
const { parsePathData } = require('../lib/path.js'); |
|
const { intersects } = require('./_path.js'); |
|
|
|
exports.name = 'removeOffCanvasPaths'; |
|
exports.description = |
|
'removes elements that are drawn outside of the viewbox (disabled by default)'; |
|
|
|
/** |
|
* Remove elements that are drawn outside of the viewbox. |
|
* |
|
* @author JoshyPHP |
|
* |
|
* @type {import('./plugins-types').Plugin<'removeOffCanvasPaths'>} |
|
*/ |
|
exports.fn = () => { |
|
/** |
|
* @type {null | { |
|
* top: number, |
|
* right: number, |
|
* bottom: number, |
|
* left: number, |
|
* width: number, |
|
* height: number |
|
* }} |
|
*/ |
|
let viewBoxData = null; |
|
|
|
return { |
|
element: { |
|
enter: (node, parentNode) => { |
|
if (node.name === 'svg' && parentNode.type === 'root') { |
|
let viewBox = ''; |
|
// find viewbox |
|
if (node.attributes.viewBox != null) { |
|
// remove commas and plus signs, normalize and trim whitespace |
|
viewBox = node.attributes.viewBox; |
|
} else if ( |
|
node.attributes.height != null && |
|
node.attributes.width != null |
|
) { |
|
viewBox = `0 0 ${node.attributes.width} ${node.attributes.height}`; |
|
} |
|
|
|
// parse viewbox |
|
// remove commas and plus signs, normalize and trim whitespace |
|
viewBox = viewBox |
|
.replace(/[,+]|px/g, ' ') |
|
.replace(/\s+/g, ' ') |
|
.replace(/^\s*|\s*$/g, ''); |
|
// ensure that the dimensions are 4 values separated by space |
|
const m = |
|
/^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec( |
|
viewBox |
|
); |
|
if (m == null) { |
|
return; |
|
} |
|
const left = Number.parseFloat(m[1]); |
|
const top = Number.parseFloat(m[2]); |
|
const width = Number.parseFloat(m[3]); |
|
const height = Number.parseFloat(m[4]); |
|
|
|
// store the viewBox boundaries |
|
viewBoxData = { |
|
left, |
|
top, |
|
right: left + width, |
|
bottom: top + height, |
|
width, |
|
height, |
|
}; |
|
} |
|
|
|
// consider that any item with a transform attribute is visible |
|
if (node.attributes.transform != null) { |
|
return visitSkip; |
|
} |
|
|
|
if ( |
|
node.name === 'path' && |
|
node.attributes.d != null && |
|
viewBoxData != null |
|
) { |
|
const pathData = parsePathData(node.attributes.d); |
|
|
|
// consider that a M command within the viewBox is visible |
|
let visible = false; |
|
for (const pathDataItem of pathData) { |
|
if (pathDataItem.command === 'M') { |
|
const [x, y] = pathDataItem.args; |
|
if ( |
|
x >= viewBoxData.left && |
|
x <= viewBoxData.right && |
|
y >= viewBoxData.top && |
|
y <= viewBoxData.bottom |
|
) { |
|
visible = true; |
|
} |
|
} |
|
} |
|
if (visible) { |
|
return; |
|
} |
|
|
|
if (pathData.length === 2) { |
|
// close the path too short for intersects() |
|
pathData.push({ command: 'z', args: [] }); |
|
} |
|
|
|
const { left, top, width, height } = viewBoxData; |
|
/** |
|
* @type {Array<PathDataItem>} |
|
*/ |
|
const viewBoxPathData = [ |
|
{ command: 'M', args: [left, top] }, |
|
{ command: 'h', args: [width] }, |
|
{ command: 'v', args: [height] }, |
|
{ command: 'H', args: [left] }, |
|
{ command: 'z', args: [] }, |
|
]; |
|
|
|
if (intersects(viewBoxPathData, pathData) === false) { |
|
detachNodeFromParent(node, parentNode); |
|
} |
|
} |
|
}, |
|
}, |
|
}; |
|
};
|
|
|