reusePaths.js 3.3 KB

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