137 lines
3.8 KiB
JavaScript
137 lines
3.8 KiB
JavaScript
|
'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 {?{
|
||
|
* 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);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
};
|
||
|
};
|