Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[new plugin] reimplement inlineDefs #1655

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
166 changes: 166 additions & 0 deletions plugins/inlineDefs.js
@@ -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
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
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
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
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
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
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
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
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
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
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
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
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.