mergePaths.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. 'use strict';
  2. const { detachNodeFromParent } = require('../lib/xast.js');
  3. const { collectStylesheet, computeStyle } = require('../lib/style.js');
  4. const { path2js, js2path, intersects } = require('./_path.js');
  5. exports.type = 'visitor';
  6. exports.name = 'mergePaths';
  7. exports.active = true;
  8. exports.description = 'merges multiple paths in one if possible';
  9. /**
  10. * Merge multiple Paths into one.
  11. *
  12. * @author Kir Belevich, Lev Solntsev
  13. *
  14. * @type {import('../lib/types').Plugin<{
  15. * force?: boolean,
  16. * floatPrecision?: number,
  17. * noSpaceAfterFlags?: boolean
  18. * }>}
  19. */
  20. exports.fn = (root, params) => {
  21. const {
  22. force = false,
  23. floatPrecision,
  24. noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
  25. } = params;
  26. const stylesheet = collectStylesheet(root);
  27. return {
  28. element: {
  29. enter: (node) => {
  30. let prevChild = null;
  31. for (const child of node.children) {
  32. // skip if previous element is not path or contains animation elements
  33. if (
  34. prevChild == null ||
  35. prevChild.type !== 'element' ||
  36. prevChild.name !== 'path' ||
  37. prevChild.children.length !== 0 ||
  38. prevChild.attributes.d == null
  39. ) {
  40. prevChild = child;
  41. continue;
  42. }
  43. // skip if element is not path or contains animation elements
  44. if (
  45. child.type !== 'element' ||
  46. child.name !== 'path' ||
  47. child.children.length !== 0 ||
  48. child.attributes.d == null
  49. ) {
  50. prevChild = child;
  51. continue;
  52. }
  53. // preserve paths with markers
  54. const computedStyle = computeStyle(stylesheet, child);
  55. if (
  56. computedStyle['marker-start'] ||
  57. computedStyle['marker-mid'] ||
  58. computedStyle['marker-end']
  59. ) {
  60. prevChild = child;
  61. continue;
  62. }
  63. const prevChildAttrs = Object.keys(prevChild.attributes);
  64. const childAttrs = Object.keys(child.attributes);
  65. let attributesAreEqual = prevChildAttrs.length === childAttrs.length;
  66. for (const name of childAttrs) {
  67. if (name !== 'd') {
  68. if (
  69. prevChild.attributes[name] == null ||
  70. prevChild.attributes[name] !== child.attributes[name]
  71. ) {
  72. attributesAreEqual = false;
  73. }
  74. }
  75. }
  76. const prevPathJS = path2js(prevChild);
  77. const curPathJS = path2js(child);
  78. if (
  79. attributesAreEqual &&
  80. (force || !intersects(prevPathJS, curPathJS))
  81. ) {
  82. js2path(prevChild, prevPathJS.concat(curPathJS), {
  83. floatPrecision,
  84. noSpaceAfterFlags,
  85. });
  86. detachNodeFromParent(child, node);
  87. continue;
  88. }
  89. prevChild = child;
  90. }
  91. },
  92. },
  93. };
  94. };