1 | 'use strict';
|
---|
2 |
|
---|
3 | /**
|
---|
4 | * @typedef {import('../lib/types').XastElement} XastElement
|
---|
5 | * @typedef {import('../lib/types').XastParent} XastParent
|
---|
6 | * @typedef {import('../lib/types').XastNode} XastNode
|
---|
7 | */
|
---|
8 |
|
---|
9 | const JSAPI = require('../lib/svgo/jsAPI.js');
|
---|
10 |
|
---|
11 | exports.type = 'visitor';
|
---|
12 | exports.name = 'reusePaths';
|
---|
13 | exports.active = false;
|
---|
14 | exports.description =
|
---|
15 | 'Finds <path> elements with the same d, fill, and ' +
|
---|
16 | 'stroke, and converts them to <use> elements ' +
|
---|
17 | 'referencing a single <path> def.';
|
---|
18 |
|
---|
19 | /**
|
---|
20 | * Finds <path> elements with the same d, fill, and stroke, and converts them to
|
---|
21 | * <use> elements referencing a single <path> def.
|
---|
22 | *
|
---|
23 | * @author Jacob Howcroft
|
---|
24 | *
|
---|
25 | * @type {import('../lib/types').Plugin<void>}
|
---|
26 | */
|
---|
27 | exports.fn = () => {
|
---|
28 | /**
|
---|
29 | * @type {Map<string, Array<XastElement>>}
|
---|
30 | */
|
---|
31 | const paths = new Map();
|
---|
32 |
|
---|
33 | return {
|
---|
34 | element: {
|
---|
35 | enter: (node) => {
|
---|
36 | if (node.name === 'path' && node.attributes.d != null) {
|
---|
37 | const d = node.attributes.d;
|
---|
38 | const fill = node.attributes.fill || '';
|
---|
39 | const stroke = node.attributes.stroke || '';
|
---|
40 | const key = d + ';s:' + stroke + ';f:' + fill;
|
---|
41 | let list = paths.get(key);
|
---|
42 | if (list == null) {
|
---|
43 | list = [];
|
---|
44 | paths.set(key, list);
|
---|
45 | }
|
---|
46 | list.push(node);
|
---|
47 | }
|
---|
48 | },
|
---|
49 |
|
---|
50 | exit: (node, parentNode) => {
|
---|
51 | if (node.name === 'svg' && parentNode.type === 'root') {
|
---|
52 | /**
|
---|
53 | * @type {XastElement}
|
---|
54 | */
|
---|
55 | const rawDefs = {
|
---|
56 | type: 'element',
|
---|
57 | name: 'defs',
|
---|
58 | attributes: {},
|
---|
59 | children: [],
|
---|
60 | };
|
---|
61 | /**
|
---|
62 | * @type {XastElement}
|
---|
63 | */
|
---|
64 | const defsTag = new JSAPI(rawDefs, node);
|
---|
65 | let index = 0;
|
---|
66 | for (const list of paths.values()) {
|
---|
67 | if (list.length > 1) {
|
---|
68 | // add reusable path to defs
|
---|
69 | /**
|
---|
70 | * @type {XastElement}
|
---|
71 | */
|
---|
72 | const rawPath = {
|
---|
73 | type: 'element',
|
---|
74 | name: 'path',
|
---|
75 | attributes: { ...list[0].attributes },
|
---|
76 | children: [],
|
---|
77 | };
|
---|
78 | delete rawPath.attributes.transform;
|
---|
79 | let id;
|
---|
80 | if (rawPath.attributes.id == null) {
|
---|
81 | id = 'reuse-' + index;
|
---|
82 | index += 1;
|
---|
83 | rawPath.attributes.id = id;
|
---|
84 | } else {
|
---|
85 | id = rawPath.attributes.id;
|
---|
86 | delete list[0].attributes.id;
|
---|
87 | }
|
---|
88 | /**
|
---|
89 | * @type {XastElement}
|
---|
90 | */
|
---|
91 | const reusablePath = new JSAPI(rawPath, defsTag);
|
---|
92 | defsTag.children.push(reusablePath);
|
---|
93 | // convert paths to <use>
|
---|
94 | for (const pathNode of list) {
|
---|
95 | pathNode.name = 'use';
|
---|
96 | pathNode.attributes['xlink:href'] = '#' + id;
|
---|
97 | delete pathNode.attributes.d;
|
---|
98 | delete pathNode.attributes.stroke;
|
---|
99 | delete pathNode.attributes.fill;
|
---|
100 | }
|
---|
101 | }
|
---|
102 | }
|
---|
103 | if (defsTag.children.length !== 0) {
|
---|
104 | if (node.attributes['xmlns:xlink'] == null) {
|
---|
105 | node.attributes['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
|
---|
106 | }
|
---|
107 | node.children.unshift(defsTag);
|
---|
108 | }
|
---|
109 | }
|
---|
110 | },
|
---|
111 | },
|
---|
112 | };
|
---|
113 | };
|
---|