cleanupNumericValues.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. const { removeLeadingZero } = require('../lib/svgo/tools');
  3. exports.name = 'cleanupNumericValues';
  4. exports.type = 'visitor';
  5. exports.active = true;
  6. exports.description =
  7. 'rounds numeric values to the fixed precision, removes default ‘px’ units';
  8. const regNumericValues =
  9. /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
  10. const absoluteLengths = {
  11. // relative to px
  12. cm: 96 / 2.54,
  13. mm: 96 / 25.4,
  14. in: 96,
  15. pt: 4 / 3,
  16. pc: 16,
  17. px: 1,
  18. };
  19. /**
  20. * Round numeric values to the fixed precision,
  21. * remove default 'px' units.
  22. *
  23. * @author Kir Belevich
  24. *
  25. * @type {import('../lib/types').Plugin<{
  26. * floatPrecision?: number,
  27. * leadingZero?: boolean,
  28. * defaultPx?: boolean,
  29. * convertToPx?: boolean
  30. * }>}
  31. */
  32. exports.fn = (_root, params) => {
  33. const {
  34. floatPrecision = 3,
  35. leadingZero = true,
  36. defaultPx = true,
  37. convertToPx = true,
  38. } = params;
  39. return {
  40. element: {
  41. enter: (node) => {
  42. if (node.attributes.viewBox != null) {
  43. const nums = node.attributes.viewBox.split(/\s,?\s*|,\s*/g);
  44. node.attributes.viewBox = nums
  45. .map((value) => {
  46. const num = Number(value);
  47. return Number.isNaN(num)
  48. ? value
  49. : Number(num.toFixed(floatPrecision));
  50. })
  51. .join(' ');
  52. }
  53. for (const [name, value] of Object.entries(node.attributes)) {
  54. // The `version` attribute is a text string and cannot be rounded
  55. if (name === 'version') {
  56. continue;
  57. }
  58. const match = value.match(regNumericValues);
  59. // if attribute value matches regNumericValues
  60. if (match) {
  61. // round it to the fixed precision
  62. let num = Number(Number(match[1]).toFixed(floatPrecision));
  63. /**
  64. * @type {any}
  65. */
  66. let matchedUnit = match[3] || '';
  67. /**
  68. * @type{'' | keyof typeof absoluteLengths}
  69. */
  70. let units = matchedUnit;
  71. // convert absolute values to pixels
  72. if (convertToPx && units !== '' && units in absoluteLengths) {
  73. const pxNum = Number(
  74. (absoluteLengths[units] * Number(match[1])).toFixed(
  75. floatPrecision
  76. )
  77. );
  78. if (pxNum.toString().length < match[0].length) {
  79. num = pxNum;
  80. units = 'px';
  81. }
  82. }
  83. // and remove leading zero
  84. let str;
  85. if (leadingZero) {
  86. str = removeLeadingZero(num);
  87. } else {
  88. str = num.toString();
  89. }
  90. // remove default 'px' units
  91. if (defaultPx && units === 'px') {
  92. units = '';
  93. }
  94. node.attributes[name] = str + units;
  95. }
  96. }
  97. },
  98. },
  99. };
  100. };