Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions plugins/inlineDefs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
'use strict';

/**
* @typedef {import('../lib/types').XastParent} XastParent
* @typedef {import('../lib/types').XastElement} XastElement
*/

exports.name = 'inlineDefs';
exports.type = 'visitor';
exports.active = true;
exports.description = 'inlines svg definitions';

/**
* @typedef {(element: XastElement, parentNode: XastParent) => void} VisitCallback
*/

/**
* @type {(element: XastParent, fn: VisitCallback) => void}
*/
const visitElements = (node, fn) => {
for (const child of node.children) {
if (child.type === 'element') {
fn(child, node);
visitElements(child, fn);
}
}
};

/**
* Replaces use tag with the corresponding definitions
* if onlyUnique is enabled, replaces only use tags with definitions referred to only once
*
* @type {import('../lib/types').Plugin<{
* onlyUnique?: boolean
* }>}
*/
exports.fn = (root, params) => {
const { onlyUnique = true } = params;
// hacky extract JSAPI class to avoid imports from other modules
const JSAPI = root.constructor;

/**
* @type {[XastElement, XastParent][]}
*/
const uses = [];
/**
* @type {Map<string, number>}
*/
const useCounts = new Map();
/**
* @type {Map<string, XastElement>}
*/
const referencedElements = new Map();

// collect defs container and all uses
visitElements(root, (node, parentNode) => {
if (node.name === 'use') {
uses.push([node, parentNode]);
const href = node.attributes['xlink:href'] || node.attributes.href;
const count = useCounts.get(href) || 0;
useCounts.set(href, count + 1);
}
});

return {
element: {
enter: (node, parentNode) => {
// find elements referenced by all <use>
if (node.attributes.id == null) {
return;
}
const href = `#${node.attributes.id}`;
const count = useCounts.get(href);
// not referenced
if (count == null) {
return;
}
referencedElements.set(href, node);
/// remove id attribute when referenced yb <use> more than once
if (onlyUnique === false && count > 1) {
delete node.attributes.id;
}
// remove elements referenced by <use> only once
if (onlyUnique === true && count === 1) {
parentNode.children = parentNode.children.filter(
(child) => child !== node
);
}
},

exit(node, parentNode) {
// remove empty <defs> container
if (node.name === 'defs') {
if (onlyUnique === false || node.children.length === 0) {
parentNode.children = parentNode.children.filter(
(child) => child !== node
);
}
}
},
},

root: {
exit: () => {
for (const [use, useParentNode] of uses) {
const href = use.attributes['xlink:href'] || use.attributes['href'];
const count = useCounts.get(href) || 0;
const referenced = referencedElements.get(href);

if (onlyUnique === true && count > 1) {
continue;
}
if (referenced == null) {
continue;
}

// copy attrubutes from <use> to referenced element
for (const [name, value] of Object.entries(use.attributes)) {
if (
name !== 'x' &&
name !== 'y' &&
name !== 'xlink:href' &&
name !== 'href'
) {
referenced.attributes[name] = value;
}
}

const x = use.attributes.x;
const y = use.attributes.y;
let attrValue = null;
if (x != null && y != null) {
attrValue = `translate(${x}, ${y})`;
} else if (x != null) {
attrValue = `translate(${x})`;
}

let replacement = referenced;
// wrap referenced element with <g> when <use> had coordinates
if (attrValue != null) {
/**
* @type {XastElement}
*/
const g = {
type: 'element',
name: 'g',
attributes: {
transform: attrValue,
},
children: [referenced],
};
// @ts-ignore
replacement = new JSAPI(g);
}
useParentNode.children = useParentNode.children.map((child) => {
if (child === use) {
return replacement;
} else {
return child;
}
});
}
},
},
};
};
1 change: 1 addition & 0 deletions plugins/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ exports.removeXMLProcInst = require('./removeXMLProcInst.js');
exports.reusePaths = require('./reusePaths.js');
exports.sortAttrs = require('./sortAttrs.js');
exports.sortDefsChildren = require('./sortDefsChildren.js');
exports.inlineDefs = require('./inlineDefs.js');
12 changes: 12 additions & 0 deletions test/plugins/inlineDefs.01.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions test/plugins/inlineDefs.02.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions test/plugins/inlineDefs.03.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions test/plugins/inlineDefs.04.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions test/plugins/inlineDefs.05.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions test/plugins/inlineDefs.06.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions test/plugins/inlineDefs.07.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions test/plugins/inlineDefs.08.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions test/plugins/inlineDefs.09.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions test/plugins/inlineDefs.10.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions test/plugins/inlineDefs.11.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions test/plugins/inlineDefs.12.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading