svg-path-parser.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { SVGPathData } from 'svg-pathdata';
  2. import arcToBezier from 'svg-arc-to-cubic-bezier';
  3. const typeMap = {
  4. 1: 'Z',
  5. 2: 'M',
  6. 4: 'H',
  7. 8: 'V',
  8. 16: 'L',
  9. 32: 'C',
  10. 64: 'S',
  11. 128: 'Q',
  12. 256: 'T',
  13. 512: 'A',
  14. };
  15. /**
  16. * 简单解析SVG路径
  17. * @param d SVG path d属性
  18. */
  19. export const parseSvgPath = (d: string) => {
  20. const pathData = new SVGPathData(d);
  21. const ret = pathData.commands.map((item) => {
  22. return { ...item, type: typeMap[item.type] };
  23. });
  24. return ret;
  25. };
  26. export type SvgPath = ReturnType<typeof parseSvgPath>;
  27. /**
  28. * 解析SVG路径,并将圆弧(A)类型的路径转为三次贝塞尔(C)类型的路径
  29. * @param d SVG path d属性
  30. */
  31. export const toPoints = (d: string) => {
  32. const pathData = new SVGPathData(d);
  33. const points = [];
  34. for (const item of pathData.commands) {
  35. const type = typeMap[item.type];
  36. if (item.type === 2 || item.type === 16) {
  37. points.push({
  38. x: item.x,
  39. y: item.y,
  40. relative: item.relative,
  41. type,
  42. });
  43. }
  44. if (item.type === 32) {
  45. points.push({
  46. x: item.x,
  47. y: item.y,
  48. curve: {
  49. type: 'cubic',
  50. x1: item.x1,
  51. y1: item.y1,
  52. x2: item.x2,
  53. y2: item.y2,
  54. },
  55. relative: item.relative,
  56. type,
  57. });
  58. } else if (item.type === 128) {
  59. points.push({
  60. x: item.x,
  61. y: item.y,
  62. curve: {
  63. type: 'quadratic',
  64. x1: item.x1,
  65. y1: item.y1,
  66. },
  67. relative: item.relative,
  68. type,
  69. });
  70. } else if (item.type === 512) {
  71. const lastPoint = points[points.length - 1];
  72. if (!['M', 'L', 'Q', 'C'].includes(lastPoint.type)) continue;
  73. const cubicBezierPoints = arcToBezier({
  74. px: lastPoint.x as number,
  75. py: lastPoint.y as number,
  76. cx: item.x,
  77. cy: item.y,
  78. rx: item.rX,
  79. ry: item.rY,
  80. xAxisRotation: item.xRot,
  81. largeArcFlag: item.lArcFlag,
  82. sweepFlag: item.sweepFlag,
  83. });
  84. for (const cbPoint of cubicBezierPoints) {
  85. points.push({
  86. x: cbPoint.x,
  87. y: cbPoint.y,
  88. curve: {
  89. type: 'cubic',
  90. x1: cbPoint.x1,
  91. y1: cbPoint.y1,
  92. x2: cbPoint.x2,
  93. y2: cbPoint.y2,
  94. },
  95. relative: false,
  96. type: 'C',
  97. });
  98. }
  99. } else if (item.type === 1) {
  100. points.push({ close: true, type });
  101. } else continue;
  102. }
  103. return points;
  104. };
  105. export const getSvgPathRange = (path: string) => {
  106. try {
  107. const pathData = new SVGPathData(path);
  108. const xList = [];
  109. const yList = [];
  110. for (const item of pathData.commands) {
  111. const x = 'x' in item ? item.x : 0;
  112. const y = 'y' in item ? item.y : 0;
  113. xList.push(x);
  114. yList.push(y);
  115. }
  116. return {
  117. minX: Math.min(...xList),
  118. minY: Math.min(...yList),
  119. maxX: Math.max(...xList),
  120. maxY: Math.max(...yList),
  121. };
  122. } catch {
  123. return {
  124. minX: 0,
  125. minY: 0,
  126. maxX: 0,
  127. maxY: 0,
  128. };
  129. }
  130. };
  131. export type SvgPoints = ReturnType<typeof toPoints>;