removeUselessStrokeAndFill.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. 'use strict';
  2. const { visit, visitSkip, detachNodeFromParent } = require('../lib/xast.js');
  3. const { collectStylesheet, computeStyle } = require('../lib/style.js');
  4. const { elemsGroups } = require('./_collections.js');
  5. exports.type = 'visitor';
  6. exports.name = 'removeUselessStrokeAndFill';
  7. exports.active = true;
  8. exports.description = 'removes useless stroke and fill attributes';
  9. /**
  10. * Remove useless stroke and fill attrs.
  11. *
  12. * @author Kir Belevich
  13. *
  14. * @type {import('../lib/types').Plugin<{
  15. * stroke?: boolean,
  16. * fill?: boolean,
  17. * removeNone?: boolean
  18. * }>}
  19. */
  20. exports.fn = (root, params) => {
  21. const {
  22. stroke: removeStroke = true,
  23. fill: removeFill = true,
  24. removeNone = false,
  25. } = params;
  26. // style and script elements deoptimise this plugin
  27. let hasStyleOrScript = false;
  28. visit(root, {
  29. element: {
  30. enter: (node) => {
  31. if (node.name === 'style' || node.name === 'script') {
  32. hasStyleOrScript = true;
  33. }
  34. },
  35. },
  36. });
  37. if (hasStyleOrScript) {
  38. return null;
  39. }
  40. const stylesheet = collectStylesheet(root);
  41. return {
  42. element: {
  43. enter: (node, parentNode) => {
  44. // id attribute deoptimise the whole subtree
  45. if (node.attributes.id != null) {
  46. return visitSkip;
  47. }
  48. if (elemsGroups.shape.includes(node.name) == false) {
  49. return;
  50. }
  51. const computedStyle = computeStyle(stylesheet, node);
  52. const stroke = computedStyle.stroke;
  53. const strokeOpacity = computedStyle['stroke-opacity'];
  54. const strokeWidth = computedStyle['stroke-width'];
  55. const markerEnd = computedStyle['marker-end'];
  56. const fill = computedStyle.fill;
  57. const fillOpacity = computedStyle['fill-opacity'];
  58. const computedParentStyle =
  59. parentNode.type === 'element'
  60. ? computeStyle(stylesheet, parentNode)
  61. : null;
  62. const parentStroke =
  63. computedParentStyle == null ? null : computedParentStyle.stroke;
  64. // remove stroke*
  65. if (removeStroke) {
  66. if (
  67. stroke == null ||
  68. (stroke.type === 'static' && stroke.value == 'none') ||
  69. (strokeOpacity != null &&
  70. strokeOpacity.type === 'static' &&
  71. strokeOpacity.value === '0') ||
  72. (strokeWidth != null &&
  73. strokeWidth.type === 'static' &&
  74. strokeWidth.value === '0')
  75. ) {
  76. // stroke-width may affect the size of marker-end
  77. // marker is not visible when stroke-width is 0
  78. if (
  79. (strokeWidth != null &&
  80. strokeWidth.type === 'static' &&
  81. strokeWidth.value === '0') ||
  82. markerEnd == null
  83. ) {
  84. for (const name of Object.keys(node.attributes)) {
  85. if (name.startsWith('stroke')) {
  86. delete node.attributes[name];
  87. }
  88. }
  89. // set explicit none to not inherit from parent
  90. if (
  91. parentStroke != null &&
  92. parentStroke.type === 'static' &&
  93. parentStroke.value !== 'none'
  94. ) {
  95. node.attributes.stroke = 'none';
  96. }
  97. }
  98. }
  99. }
  100. // remove fill*
  101. if (removeFill) {
  102. if (
  103. (fill != null && fill.type === 'static' && fill.value === 'none') ||
  104. (fillOpacity != null &&
  105. fillOpacity.type === 'static' &&
  106. fillOpacity.value === '0')
  107. ) {
  108. for (const name of Object.keys(node.attributes)) {
  109. if (name.startsWith('fill-')) {
  110. delete node.attributes[name];
  111. }
  112. }
  113. if (
  114. fill == null ||
  115. (fill.type === 'static' && fill.value !== 'none')
  116. ) {
  117. node.attributes.fill = 'none';
  118. }
  119. }
  120. }
  121. if (removeNone) {
  122. if (
  123. (stroke == null || node.attributes.stroke === 'none') &&
  124. ((fill != null &&
  125. fill.type === 'static' &&
  126. fill.value === 'none') ||
  127. node.attributes.fill === 'none')
  128. ) {
  129. detachNodeFromParent(node, parentNode);
  130. }
  131. }
  132. },
  133. },
  134. };
  135. };