You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
9.5 KiB

  1. 'use strict';
  2. /**
  3. * Pathformer
  4. * Beta version
  5. *
  6. * Take any SVG version 1.1 and transform
  7. * child elements to 'path' elements
  8. *
  9. * This code is purely forked from
  10. * https://github.com/Waest/SVGPathConverter
  11. */
  12. /**
  13. * Class constructor
  14. *
  15. * @param {DOM|String} element Dom element of the SVG or id of it
  16. */
  17. function Pathformer(element) {
  18. // Test params
  19. if (typeof element === 'undefined') {
  20. throw new Error('Pathformer [constructor]: "element" parameter is required');
  21. }
  22. // Set the element
  23. if (element.constructor === String) {
  24. element = document.getElementById(element);
  25. if (!element) {
  26. throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');
  27. }
  28. }
  29. if (element instanceof window.SVGElement ||
  30. element instanceof window.SVGGElement ||
  31. /^svg$/i.test(element.nodeName)) {
  32. this.el = element;
  33. } else {
  34. throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');
  35. }
  36. // Start
  37. this.scan(element);
  38. }
  39. /**
  40. * List of tags which can be transformed
  41. * to path elements
  42. *
  43. * @type {Array}
  44. */
  45. Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect'];
  46. /**
  47. * List of attribute names which contain
  48. * data. This array list them to check if
  49. * they contain bad values, like percentage.
  50. *
  51. * @type {Array}
  52. */
  53. Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', 'y1', 'y2'];
  54. /**
  55. * Finds the elements compatible for transform
  56. * and apply the liked method
  57. *
  58. * @param {object} options Object from the constructor
  59. */
  60. Pathformer.prototype.scan = function (svg) {
  61. var fn, element, pathData, pathDom,
  62. elements = svg.querySelectorAll(this.TYPES.join(','));
  63. for (var i = 0; i < elements.length; i++) {
  64. element = elements[i];
  65. fn = this[element.tagName.toLowerCase() + 'ToPath'];
  66. pathData = fn(this.parseAttr(element.attributes));
  67. pathDom = this.pathMaker(element, pathData);
  68. element.parentNode.replaceChild(pathDom, element);
  69. }
  70. };
  71. /**
  72. * Read `line` element to extract and transform
  73. * data, to make it ready for a `path` object.
  74. *
  75. * @param {DOMelement} element Line element to transform
  76. * @return {object} Data for a `path` element
  77. */
  78. Pathformer.prototype.lineToPath = function (element) {
  79. var newElement = {},
  80. x1 = element.x1 || 0,
  81. y1 = element.y1 || 0,
  82. x2 = element.x2 || 0,
  83. y2 = element.y2 || 0;
  84. newElement.d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
  85. return newElement;
  86. };
  87. /**
  88. * Read `rect` element to extract and transform
  89. * data, to make it ready for a `path` object.
  90. * The radius-border is not taken in charge yet.
  91. * (your help is more than welcomed)
  92. *
  93. * @param {DOMelement} element Rect element to transform
  94. * @return {object} Data for a `path` element
  95. */
  96. Pathformer.prototype.rectToPath = function (element) {
  97. var newElement = {},
  98. x = parseFloat(element.x) || 0,
  99. y = parseFloat(element.y) || 0,
  100. width = parseFloat(element.width) || 0,
  101. height = parseFloat(element.height) || 0;
  102. if (element.rx || element.ry) {
  103. var rx = parseInt(element.rx, 10) || -1,
  104. ry = parseInt(element.ry, 10) || -1;
  105. rx = Math.min(Math.max(rx < 0 ? ry : rx, 0), width/2);
  106. ry = Math.min(Math.max(ry < 0 ? rx : ry, 0), height/2);
  107. newElement.d = 'M ' + (x + rx) + ',' + y + ' ' +
  108. 'L ' + (x + width - rx) + ',' + y + ' ' +
  109. 'A ' + rx + ',' + ry + ',0,0,1,' + (x + width) + ',' + (y + ry) + ' ' +
  110. 'L ' + (x + width) + ',' + (y + height - ry) + ' ' +
  111. 'A ' + rx + ',' + ry + ',0,0,1,' + (x + width - rx) + ',' + (y + height) + ' ' +
  112. 'L ' + (x + rx) + ',' + (y + height) + ' ' +
  113. 'A ' + rx + ',' + ry + ',0,0,1,' + x + ',' + (y + height - ry) + ' ' +
  114. 'L ' + x + ',' + (y + ry) + ' ' +
  115. 'A ' + rx + ',' + ry + ',0,0,1,' + (x + rx) + ',' + y;
  116. }
  117. else {
  118. newElement.d = 'M' + x + ' ' + y + ' ' +
  119. 'L' + (x + width) + ' ' + y + ' ' +
  120. 'L' + (x + width) + ' ' + (y + height) + ' ' +
  121. 'L' + x + ' ' + (y + height) + ' Z';
  122. }
  123. return newElement;
  124. };
  125. /**
  126. * Read `polyline` element to extract and transform
  127. * data, to make it ready for a `path` object.
  128. *
  129. * @param {DOMelement} element Polyline element to transform
  130. * @return {object} Data for a `path` element
  131. */
  132. Pathformer.prototype.polylineToPath = function (element) {
  133. var newElement = {},
  134. points = element.points.trim().split(' '),
  135. i, path;
  136. // Reformatting if points are defined without commas
  137. if (element.points.indexOf(',') === -1) {
  138. var formattedPoints = [];
  139. for (i = 0; i < points.length; i+=2) {
  140. formattedPoints.push(points[i] + ',' + points[i+1]);
  141. }
  142. points = formattedPoints;
  143. }
  144. // Generate the path.d value
  145. path = 'M' + points[0];
  146. for(i = 1; i < points.length; i++) {
  147. if (points[i].indexOf(',') !== -1) {
  148. path += 'L' + points[i];
  149. }
  150. }
  151. newElement.d = path;
  152. return newElement;
  153. };
  154. /**
  155. * Read `polygon` element to extract and transform
  156. * data, to make it ready for a `path` object.
  157. * This method rely on polylineToPath, because the
  158. * logic is similar. The path created is just closed,
  159. * so it needs an 'Z' at the end.
  160. *
  161. * @param {DOMelement} element Polygon element to transform
  162. * @return {object} Data for a `path` element
  163. */
  164. Pathformer.prototype.polygonToPath = function (element) {
  165. var newElement = Pathformer.prototype.polylineToPath(element);
  166. newElement.d += 'Z';
  167. return newElement;
  168. };
  169. /**
  170. * Read `ellipse` element to extract and transform
  171. * data, to make it ready for a `path` object.
  172. *
  173. * @param {DOMelement} element ellipse element to transform
  174. * @return {object} Data for a `path` element
  175. */
  176. Pathformer.prototype.ellipseToPath = function (element) {
  177. var newElement = {},
  178. rx = parseFloat(element.rx) || 0,
  179. ry = parseFloat(element.ry) || 0,
  180. cx = parseFloat(element.cx) || 0,
  181. cy = parseFloat(element.cy) || 0,
  182. startX = cx - rx,
  183. startY = cy,
  184. endX = parseFloat(cx) + parseFloat(rx),
  185. endY = cy;
  186. newElement.d = 'M' + startX + ',' + startY +
  187. 'A' + rx + ',' + ry + ' 0,1,1 ' + endX + ',' + endY +
  188. 'A' + rx + ',' + ry + ' 0,1,1 ' + startX + ',' + endY;
  189. return newElement;
  190. };
  191. /**
  192. * Read `circle` element to extract and transform
  193. * data, to make it ready for a `path` object.
  194. *
  195. * @param {DOMelement} element Circle element to transform
  196. * @return {object} Data for a `path` element
  197. */
  198. Pathformer.prototype.circleToPath = function (element) {
  199. var newElement = {},
  200. r = parseFloat(element.r) || 0,
  201. cx = parseFloat(element.cx) || 0,
  202. cy = parseFloat(element.cy) || 0,
  203. startX = cx - r,
  204. startY = cy,
  205. endX = parseFloat(cx) + parseFloat(r),
  206. endY = cy;
  207. newElement.d = 'M' + startX + ',' + startY +
  208. 'A' + r + ',' + r + ' 0,1,1 ' + endX + ',' + endY +
  209. 'A' + r + ',' + r + ' 0,1,1 ' + startX + ',' + endY;
  210. return newElement;
  211. };
  212. /**
  213. * Create `path` elements form original element
  214. * and prepared objects
  215. *
  216. * @param {DOMelement} element Original element to transform
  217. * @param {object} pathData Path data (from `toPath` methods)
  218. * @return {DOMelement} Path element
  219. */
  220. Pathformer.prototype.pathMaker = function (element, pathData) {
  221. var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path');
  222. for(i = 0; i < element.attributes.length; i++) {
  223. attr = element.attributes[i];
  224. if (this.ATTR_WATCH.indexOf(attr.name) === -1) {
  225. pathTag.setAttribute(attr.name, attr.value);
  226. }
  227. }
  228. for(i in pathData) {
  229. pathTag.setAttribute(i, pathData[i]);
  230. }
  231. return pathTag;
  232. };
  233. /**
  234. * Parse attributes of a DOM element to
  235. * get an object of attribute => value
  236. *
  237. * @param {NamedNodeMap} attributes Attributes object from DOM element to parse
  238. * @return {object} Object of attributes
  239. */
  240. Pathformer.prototype.parseAttr = function (element) {
  241. var attr, output = {};
  242. for (var i = 0; i < element.length; i++) {
  243. attr = element[i];
  244. // Check if no data attribute contains '%', or the transformation is impossible
  245. if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) {
  246. throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.');
  247. }
  248. output[attr.name] = attr.value;
  249. }
  250. return output;
  251. };