From bb3f1a99ef55dabc67a8d79110aef9ebfc5f3f9b Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 21 Feb 2022 01:46:04 +0300 Subject: [PATCH] [new plugin] reimplement inlineDefs The code is taken from https://github.com/svg/svgo/pull/976, refactored with new api, covered types and simplified. Plugin has no dependencies so can be used without changing. ``` const inlineDefs = require('./inlineDefs.js'); module.exports = { plugins: [ 'preset-default', inlineDefs ] }; ``` --- plugins/inlineDefs.js | 166 +++++++++++++++++++++++++++++++++ plugins/plugins.js | 1 + test/plugins/inlineDefs.01.svg | 12 +++ test/plugins/inlineDefs.02.svg | 20 ++++ test/plugins/inlineDefs.03.svg | 15 +++ test/plugins/inlineDefs.04.svg | 14 +++ test/plugins/inlineDefs.05.svg | 17 ++++ test/plugins/inlineDefs.06.svg | 22 +++++ test/plugins/inlineDefs.07.svg | 18 ++++ test/plugins/inlineDefs.08.svg | 12 +++ test/plugins/inlineDefs.09.svg | 20 ++++ test/plugins/inlineDefs.10.svg | 15 +++ test/plugins/inlineDefs.11.svg | 14 +++ test/plugins/inlineDefs.12.svg | 17 ++++ test/plugins/inlineDefs.13.svg | 22 +++++ test/plugins/inlineDefs.14.svg | 18 ++++ 16 files changed, 403 insertions(+) create mode 100644 plugins/inlineDefs.js create mode 100644 test/plugins/inlineDefs.01.svg create mode 100644 test/plugins/inlineDefs.02.svg create mode 100644 test/plugins/inlineDefs.03.svg create mode 100644 test/plugins/inlineDefs.04.svg create mode 100644 test/plugins/inlineDefs.05.svg create mode 100644 test/plugins/inlineDefs.06.svg create mode 100644 test/plugins/inlineDefs.07.svg create mode 100644 test/plugins/inlineDefs.08.svg create mode 100644 test/plugins/inlineDefs.09.svg create mode 100644 test/plugins/inlineDefs.10.svg create mode 100644 test/plugins/inlineDefs.11.svg create mode 100644 test/plugins/inlineDefs.12.svg create mode 100644 test/plugins/inlineDefs.13.svg create mode 100644 test/plugins/inlineDefs.14.svg diff --git a/plugins/inlineDefs.js b/plugins/inlineDefs.js new file mode 100644 index 00000000..977693bf --- /dev/null +++ b/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} + */ + const useCounts = new Map(); + /** + * @type {Map} + */ + 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 + 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 more than once + if (onlyUnique === false && count > 1) { + delete node.attributes.id; + } + // remove elements referenced by only once + if (onlyUnique === true && count === 1) { + parentNode.children = parentNode.children.filter( + (child) => child !== node + ); + } + }, + + exit(node, parentNode) { + // remove empty 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 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 when 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; + } + }); + } + }, + }, + }; +}; diff --git a/plugins/plugins.js b/plugins/plugins.js index 409e07b1..0b3fd443 100644 --- a/plugins/plugins.js +++ b/plugins/plugins.js @@ -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'); diff --git a/test/plugins/inlineDefs.01.svg b/test/plugins/inlineDefs.01.svg new file mode 100644 index 00000000..cf9f560d --- /dev/null +++ b/test/plugins/inlineDefs.01.svg @@ -0,0 +1,12 @@ + + + + + + + + @@@ + + + + diff --git a/test/plugins/inlineDefs.02.svg b/test/plugins/inlineDefs.02.svg new file mode 100644 index 00000000..d1448bc5 --- /dev/null +++ b/test/plugins/inlineDefs.02.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + @@@ + + + + + + + + + diff --git a/test/plugins/inlineDefs.03.svg b/test/plugins/inlineDefs.03.svg new file mode 100644 index 00000000..34080821 --- /dev/null +++ b/test/plugins/inlineDefs.03.svg @@ -0,0 +1,15 @@ + + + + + + + + + + @@@ + + + + + diff --git a/test/plugins/inlineDefs.04.svg b/test/plugins/inlineDefs.04.svg new file mode 100644 index 00000000..a468ff35 --- /dev/null +++ b/test/plugins/inlineDefs.04.svg @@ -0,0 +1,14 @@ + + + + + + + +@@@ + + + + + + diff --git a/test/plugins/inlineDefs.05.svg b/test/plugins/inlineDefs.05.svg new file mode 100644 index 00000000..f8f62d77 --- /dev/null +++ b/test/plugins/inlineDefs.05.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + @@@ + + + + + + diff --git a/test/plugins/inlineDefs.06.svg b/test/plugins/inlineDefs.06.svg new file mode 100644 index 00000000..28a2930b --- /dev/null +++ b/test/plugins/inlineDefs.06.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + @@@ + + + + + + + + diff --git a/test/plugins/inlineDefs.07.svg b/test/plugins/inlineDefs.07.svg new file mode 100644 index 00000000..feca58fb --- /dev/null +++ b/test/plugins/inlineDefs.07.svg @@ -0,0 +1,18 @@ + + + + + + + + + @@@ + + + + + + + @@@ + +{ "onlyUnique": false } diff --git a/test/plugins/inlineDefs.08.svg b/test/plugins/inlineDefs.08.svg new file mode 100644 index 00000000..62c1cf00 --- /dev/null +++ b/test/plugins/inlineDefs.08.svg @@ -0,0 +1,12 @@ + + + + + + + + @@@ + + + + diff --git a/test/plugins/inlineDefs.09.svg b/test/plugins/inlineDefs.09.svg new file mode 100644 index 00000000..a7e53de4 --- /dev/null +++ b/test/plugins/inlineDefs.09.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + @@@ + + + + + + + + + diff --git a/test/plugins/inlineDefs.10.svg b/test/plugins/inlineDefs.10.svg new file mode 100644 index 00000000..9e025137 --- /dev/null +++ b/test/plugins/inlineDefs.10.svg @@ -0,0 +1,15 @@ + + + + + + + + + + @@@ + + + + + diff --git a/test/plugins/inlineDefs.11.svg b/test/plugins/inlineDefs.11.svg new file mode 100644 index 00000000..46a61789 --- /dev/null +++ b/test/plugins/inlineDefs.11.svg @@ -0,0 +1,14 @@ + + + + + + + + @@@ + + + + + + diff --git a/test/plugins/inlineDefs.12.svg b/test/plugins/inlineDefs.12.svg new file mode 100644 index 00000000..3f355184 --- /dev/null +++ b/test/plugins/inlineDefs.12.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + @@@ + + + + + + diff --git a/test/plugins/inlineDefs.13.svg b/test/plugins/inlineDefs.13.svg new file mode 100644 index 00000000..24516d5f --- /dev/null +++ b/test/plugins/inlineDefs.13.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + @@@ + + + + + + + + diff --git a/test/plugins/inlineDefs.14.svg b/test/plugins/inlineDefs.14.svg new file mode 100644 index 00000000..04039f52 --- /dev/null +++ b/test/plugins/inlineDefs.14.svg @@ -0,0 +1,18 @@ + + + + + + + + + @@@ + + + + + + + @@@ + +{ "onlyUnique": false }