transformsWithOnePath.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. 'use strict';
  2. /*
  3. * Thanks to http://fontello.com project for sponsoring this plugin
  4. */
  5. exports.type = 'full';
  6. exports.active = false;
  7. exports.description = 'performs a set of operations on SVG with one path inside (disabled by default)';
  8. exports.params = {
  9. // width and height to resize SVG and rescale inner Path
  10. width: false,
  11. height: false,
  12. // scale inner Path without resizing SVG
  13. scale: false,
  14. // shiftX/Y inner Path
  15. shiftX: false,
  16. shiftY: false,
  17. // crop SVG width along the real width of inner Path
  18. hcrop: false,
  19. // vertical center inner Path inside SVG height
  20. vcenter: false,
  21. // stringify params
  22. floatPrecision: 3,
  23. leadingZero: true,
  24. negativeExtraSpace: true
  25. };
  26. var _path = require('./_path.js'),
  27. relative2absolute = _path.relative2absolute,
  28. computeCubicBoundingBox = _path.computeCubicBoundingBox,
  29. computeQuadraticBoundingBox = _path.computeQuadraticBoundingBox,
  30. applyTransforms = _path.applyTransforms,
  31. js2path = _path.js2path,
  32. path2js = _path.path2js,
  33. EXTEND = require('whet.extend');
  34. exports.fn = function(data, params) {
  35. data.content.forEach(function(item) {
  36. // only for SVG with one Path inside
  37. if (item.isElem('svg') &&
  38. item.content.length === 1 &&
  39. item.content[0].isElem('path')
  40. ) {
  41. var svgElem = item,
  42. pathElem = svgElem.content[0],
  43. // get absoluted Path data
  44. path = relative2absolute(EXTEND(true, [], path2js(pathElem))),
  45. xs = [],
  46. ys = [],
  47. cubicСontrolPoint = [0, 0],
  48. quadraticСontrolPoint = [0, 0],
  49. lastPoint = [0, 0],
  50. cubicBoundingBox,
  51. quadraticBoundingBox,
  52. i,
  53. segment;
  54. path.forEach(function(pathItem) {
  55. // ML
  56. if ('ML'.indexOf(pathItem.instruction) > -1) {
  57. for (i = 0; i < pathItem.data.length; i++) {
  58. if (i % 2 === 0) {
  59. xs.push(pathItem.data[i]);
  60. } else {
  61. ys.push(pathItem.data[i]);
  62. }
  63. }
  64. lastPoint = cubicСontrolPoint = quadraticСontrolPoint = pathItem.data.slice(-2);
  65. // H
  66. } else if (pathItem.instruction === 'H') {
  67. pathItem.data.forEach(function(d) {
  68. xs.push(d);
  69. });
  70. lastPoint[0] = cubicСontrolPoint[0] = quadraticСontrolPoint[0] = pathItem.data[pathItem.data.length - 2];
  71. // V
  72. } else if (pathItem.instruction === 'V') {
  73. pathItem.data.forEach(function(d) {
  74. ys.push(d);
  75. });
  76. lastPoint[1] = cubicСontrolPoint[1] = quadraticСontrolPoint[1] = pathItem.data[pathItem.data.length - 1];
  77. // C
  78. } else if (pathItem.instruction === 'C') {
  79. for (i = 0; i < pathItem.data.length; i += 6) {
  80. segment = pathItem.data.slice(i, i + 6);
  81. cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(segment));
  82. xs.push(cubicBoundingBox.minx);
  83. xs.push(cubicBoundingBox.maxx);
  84. ys.push(cubicBoundingBox.miny);
  85. ys.push(cubicBoundingBox.maxy);
  86. // reflected control point for the next possible S
  87. cubicСontrolPoint = [
  88. 2 * segment[4] - segment[2],
  89. 2 * segment[5] - segment[3]
  90. ];
  91. lastPoint = segment.slice(-2);
  92. }
  93. // S
  94. } else if (pathItem.instruction === 'S') {
  95. for (i = 0; i < pathItem.data.length; i += 4) {
  96. segment = pathItem.data.slice(i, i + 4);
  97. cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(cubicСontrolPoint).concat(segment));
  98. xs.push(cubicBoundingBox.minx);
  99. xs.push(cubicBoundingBox.maxx);
  100. ys.push(cubicBoundingBox.miny);
  101. ys.push(cubicBoundingBox.maxy);
  102. // reflected control point for the next possible S
  103. cubicСontrolPoint = [
  104. 2 * segment[2] - cubicСontrolPoint[0],
  105. 2 * segment[3] - cubicСontrolPoint[1],
  106. ];
  107. lastPoint = segment.slice(-2);
  108. }
  109. // Q
  110. } else if (pathItem.instruction === 'Q') {
  111. for (i = 0; i < pathItem.data.length; i += 4) {
  112. segment = pathItem.data.slice(i, i + 4);
  113. quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(segment));
  114. xs.push(quadraticBoundingBox.minx);
  115. xs.push(quadraticBoundingBox.maxx);
  116. ys.push(quadraticBoundingBox.miny);
  117. ys.push(quadraticBoundingBox.maxy);
  118. // reflected control point for the next possible T
  119. quadraticСontrolPoint = [
  120. 2 * segment[2] - segment[0],
  121. 2 * segment[3] - segment[1]
  122. ];
  123. lastPoint = segment.slice(-2);
  124. }
  125. // S
  126. } else if (pathItem.instruction === 'T') {
  127. for (i = 0; i < pathItem.data.length; i += 2) {
  128. segment = pathItem.data.slice(i, i + 2);
  129. quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(quadraticСontrolPoint).concat(segment));
  130. xs.push(quadraticBoundingBox.minx);
  131. xs.push(quadraticBoundingBox.maxx);
  132. ys.push(quadraticBoundingBox.miny);
  133. ys.push(quadraticBoundingBox.maxy);
  134. // reflected control point for the next possible T
  135. quadraticСontrolPoint = [
  136. 2 * segment[0] - quadraticСontrolPoint[0],
  137. 2 * segment[1] - quadraticСontrolPoint[1]
  138. ];
  139. lastPoint = segment.slice(-2);
  140. }
  141. }
  142. });
  143. var xmin = Math.min.apply(this, xs).toFixed(params.floatPrecision),
  144. xmax = Math.max.apply(this, xs).toFixed(params.floatPrecision),
  145. ymin = Math.min.apply(this, ys).toFixed(params.floatPrecision),
  146. ymax = Math.max.apply(this, ys).toFixed(params.floatPrecision),
  147. svgWidth = +svgElem.attr('width').value,
  148. svgHeight = +svgElem.attr('height').value,
  149. realWidth = Math.round(xmax - xmin),
  150. realHeight = Math.round(ymax - ymin),
  151. transform = '',
  152. scale;
  153. // width & height
  154. if (params.width && params.height) {
  155. scale = Math.min(params.width / svgWidth, params.height / svgHeight);
  156. realWidth = realWidth * scale;
  157. realHeight = realHeight * scale;
  158. svgWidth = svgElem.attr('width').value = params.width;
  159. svgHeight = svgElem.attr('height').value = params.height;
  160. transform += ' scale(' + scale + ')';
  161. // width
  162. } else if (params.width && !params.height) {
  163. scale = params.width / svgWidth;
  164. realWidth = realWidth * scale;
  165. realHeight = realHeight * scale;
  166. svgWidth = svgElem.attr('width').value = params.width;
  167. svgHeight = svgElem.attr('height').value = svgHeight * scale;
  168. transform += ' scale(' + scale + ')';
  169. // height
  170. } else if (params.height && !params.width) {
  171. scale = params.height / svgHeight;
  172. realWidth = realWidth * scale;
  173. realHeight = realHeight * scale;
  174. svgWidth = svgElem.attr('width').value = svgWidth * scale;
  175. svgHeight = svgElem.attr('height').value = params.height;
  176. transform += ' scale(' + scale + ')';
  177. }
  178. // shiftX
  179. if (params.shiftX) {
  180. transform += ' translate(' + realWidth * params.shiftX + ', 0)';
  181. }
  182. // shiftY
  183. if (params.shiftY) {
  184. transform += ' translate(0, ' + realHeight * params.shiftY + ')';
  185. }
  186. // scale
  187. if (params.scale) {
  188. scale = params.scale;
  189. var shiftX = svgWidth / 2,
  190. shiftY = svgHeight / 2;
  191. realWidth = realWidth * scale;
  192. realHeight = realHeight * scale;
  193. if (params.shiftX || params.shiftY) {
  194. transform += ' scale(' + scale + ')';
  195. } else {
  196. transform += ' translate(' + shiftX + ' ' + shiftY + ') scale(' + scale + ') translate(-' + shiftX + ' -' + shiftY + ')';
  197. }
  198. }
  199. // hcrop
  200. if (params.hcrop) {
  201. transform += ' translate(' + (-xmin) + ' 0)';
  202. svgElem.attr('width').value = realWidth;
  203. }
  204. // vcenter
  205. if (params.vcenter) {
  206. transform += ' translate(0 ' + (((svgHeight - realHeight) / 2) - ymin) + ')';
  207. }
  208. if (transform) {
  209. pathElem.addAttr({
  210. name: 'transform',
  211. prefix: '',
  212. local: 'transform',
  213. value: transform
  214. });
  215. path = applyTransforms(pathElem, pathElem.pathJS, true, params.floatPrecision);
  216. // transformed data rounding
  217. path.forEach(function(pathItem) {
  218. if (pathItem.data) {
  219. pathItem.data = pathItem.data.map(function(num) {
  220. return +num.toFixed(params.floatPrecision);
  221. });
  222. }
  223. });
  224. // save new
  225. js2path(pathElem, path, params);
  226. }
  227. }
  228. });
  229. return data;
  230. };