[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | const { visit } = require('../lib/xast.js');
|
---|
| 4 | const { inheritableAttrs, pathElems } = require('./_collections.js');
|
---|
| 5 |
|
---|
| 6 | exports.type = 'visitor';
|
---|
| 7 | exports.name = 'moveElemsAttrsToGroup';
|
---|
| 8 | exports.active = true;
|
---|
| 9 | exports.description = 'Move common attributes of group children to the group';
|
---|
| 10 |
|
---|
| 11 | /**
|
---|
| 12 | * Move common attributes of group children to the group
|
---|
| 13 | *
|
---|
| 14 | * @example
|
---|
| 15 | * <g attr1="val1">
|
---|
| 16 | * <g attr2="val2">
|
---|
| 17 | * text
|
---|
| 18 | * </g>
|
---|
| 19 | * <circle attr2="val2" attr3="val3"/>
|
---|
| 20 | * </g>
|
---|
| 21 | * ⬇
|
---|
| 22 | * <g attr1="val1" attr2="val2">
|
---|
| 23 | * <g>
|
---|
| 24 | * text
|
---|
| 25 | * </g>
|
---|
| 26 | * <circle attr3="val3"/>
|
---|
| 27 | * </g>
|
---|
| 28 | *
|
---|
| 29 | * @author Kir Belevich
|
---|
| 30 | *
|
---|
| 31 | * @type {import('../lib/types').Plugin<void>}
|
---|
| 32 | */
|
---|
| 33 | exports.fn = (root) => {
|
---|
| 34 | // find if any style element is present
|
---|
| 35 | let deoptimizedWithStyles = false;
|
---|
| 36 | visit(root, {
|
---|
| 37 | element: {
|
---|
| 38 | enter: (node) => {
|
---|
| 39 | if (node.name === 'style') {
|
---|
| 40 | deoptimizedWithStyles = true;
|
---|
| 41 | }
|
---|
| 42 | },
|
---|
| 43 | },
|
---|
| 44 | });
|
---|
| 45 |
|
---|
| 46 | return {
|
---|
| 47 | element: {
|
---|
| 48 | exit: (node) => {
|
---|
| 49 | // process only groups with more than 1 children
|
---|
| 50 | if (node.name !== 'g' || node.children.length <= 1) {
|
---|
| 51 | return;
|
---|
| 52 | }
|
---|
| 53 |
|
---|
| 54 | // deoptimize the plugin when style elements are present
|
---|
| 55 | // selectors may rely on id, classes or tag names
|
---|
| 56 | if (deoptimizedWithStyles) {
|
---|
| 57 | return;
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 | /**
|
---|
| 61 | * find common attributes in group children
|
---|
| 62 | * @type {Map<string, string>}
|
---|
| 63 | */
|
---|
| 64 | const commonAttributes = new Map();
|
---|
| 65 | let initial = true;
|
---|
| 66 | let everyChildIsPath = true;
|
---|
| 67 | for (const child of node.children) {
|
---|
| 68 | if (child.type === 'element') {
|
---|
| 69 | if (pathElems.includes(child.name) === false) {
|
---|
| 70 | everyChildIsPath = false;
|
---|
| 71 | }
|
---|
| 72 | if (initial) {
|
---|
| 73 | initial = false;
|
---|
| 74 | // collect all inheritable attributes from first child element
|
---|
| 75 | for (const [name, value] of Object.entries(child.attributes)) {
|
---|
| 76 | // consider only inheritable attributes
|
---|
| 77 | if (inheritableAttrs.includes(name)) {
|
---|
| 78 | commonAttributes.set(name, value);
|
---|
| 79 | }
|
---|
| 80 | }
|
---|
| 81 | } else {
|
---|
| 82 | // exclude uncommon attributes from initial list
|
---|
| 83 | for (const [name, value] of commonAttributes) {
|
---|
| 84 | if (child.attributes[name] !== value) {
|
---|
| 85 | commonAttributes.delete(name);
|
---|
| 86 | }
|
---|
| 87 | }
|
---|
| 88 | }
|
---|
| 89 | }
|
---|
| 90 | }
|
---|
| 91 |
|
---|
| 92 | // preserve transform on children when group has clip-path or mask
|
---|
| 93 | if (
|
---|
| 94 | node.attributes['clip-path'] != null ||
|
---|
| 95 | node.attributes.mask != null
|
---|
| 96 | ) {
|
---|
| 97 | commonAttributes.delete('transform');
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | // preserve transform when all children are paths
|
---|
| 101 | // so the transform could be applied to path data by other plugins
|
---|
| 102 | if (everyChildIsPath) {
|
---|
| 103 | commonAttributes.delete('transform');
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | // add common children attributes to group
|
---|
| 107 | for (const [name, value] of commonAttributes) {
|
---|
| 108 | if (name === 'transform') {
|
---|
| 109 | if (node.attributes.transform != null) {
|
---|
| 110 | node.attributes.transform = `${node.attributes.transform} ${value}`;
|
---|
| 111 | } else {
|
---|
| 112 | node.attributes.transform = value;
|
---|
| 113 | }
|
---|
| 114 | } else {
|
---|
| 115 | node.attributes[name] = value;
|
---|
| 116 | }
|
---|
| 117 | }
|
---|
| 118 |
|
---|
| 119 | // delete common attributes from children
|
---|
| 120 | for (const child of node.children) {
|
---|
| 121 | if (child.type === 'element') {
|
---|
| 122 | for (const [name] of commonAttributes) {
|
---|
| 123 | delete child.attributes[name];
|
---|
| 124 | }
|
---|
| 125 | }
|
---|
| 126 | }
|
---|
| 127 | },
|
---|
| 128 | },
|
---|
| 129 | };
|
---|
| 130 | };
|
---|