matrix.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import { createSvgElement } from './elem';
  2. const transformRegex = /(\w+)\(([^,)]+),?([^)]+)?\)/gi;
  3. const transformSeparatorRegex = /[ ,]+/;
  4. const transformationListRegex = /^(\w+)\((.*)\)/;
  5. /**
  6. * Returns a SVG point object initialized with the `x` and `y` coordinates.
  7. * @see https://developer.mozilla.org/en/docs/Web/API/SVGPoint
  8. */
  9. export function createSVGPoint(x, y) {
  10. const svgDocument = createSvgElement('svg');
  11. const p = svgDocument.createSVGPoint();
  12. p.x = x;
  13. p.y = y;
  14. return p;
  15. }
  16. /**
  17. * Returns the SVG transformation matrix initialized with the given matrix.
  18. *
  19. * The given matrix is an object of the form:
  20. * {
  21. * a: number
  22. * b: number
  23. * c: number
  24. * d: number
  25. * e: number
  26. * f: number
  27. * }
  28. *
  29. * @see https://developer.mozilla.org/en/docs/Web/API/SVGMatrix
  30. */
  31. export function createSVGMatrix(matrix) {
  32. const svgDocument = createSvgElement('svg');
  33. const mat = svgDocument.createSVGMatrix();
  34. if (matrix != null) {
  35. const source = matrix;
  36. const target = mat;
  37. // eslint-disable-next-line
  38. for (const key in source) {
  39. target[key] = source[key];
  40. }
  41. }
  42. return mat;
  43. }
  44. /**
  45. * Returns a SVG transform object.
  46. * @see https://developer.mozilla.org/en/docs/Web/API/SVGTransform
  47. */
  48. export function createSVGTransform(matrix) {
  49. const svgDocument = createSvgElement('svg');
  50. if (matrix != null) {
  51. if (!(matrix instanceof DOMMatrix)) {
  52. matrix = createSVGMatrix(matrix); // eslint-disable-line
  53. }
  54. return svgDocument.createSVGTransformFromMatrix(matrix);
  55. }
  56. return svgDocument.createSVGTransform();
  57. }
  58. /**
  59. * Returns the SVG transformation matrix built from the `transformString`.
  60. *
  61. * E.g. 'translate(10,10) scale(2,2)' will result in matrix:
  62. * `{ a: 2, b: 0, c: 0, d: 2, e: 10, f: 10}`
  63. */
  64. export function transformStringToMatrix(transform) {
  65. let mat = createSVGMatrix();
  66. const matches = transform != null && transform.match(transformRegex);
  67. if (!matches) {
  68. return mat;
  69. }
  70. for (let i = 0, n = matches.length; i < n; i += 1) {
  71. const transformationString = matches[i];
  72. const transformationMatch = transformationString.match(transformationListRegex);
  73. if (transformationMatch) {
  74. let sx;
  75. let sy;
  76. let tx;
  77. let ty;
  78. let angle;
  79. let ctm = createSVGMatrix();
  80. const args = transformationMatch[2].split(transformSeparatorRegex);
  81. switch (transformationMatch[1].toLowerCase()) {
  82. case 'scale':
  83. sx = parseFloat(args[0]);
  84. sy = args[1] === undefined ? sx : parseFloat(args[1]);
  85. ctm = ctm.scaleNonUniform(sx, sy);
  86. break;
  87. case 'translate':
  88. tx = parseFloat(args[0]);
  89. ty = parseFloat(args[1]);
  90. ctm = ctm.translate(tx, ty);
  91. break;
  92. case 'rotate':
  93. angle = parseFloat(args[0]);
  94. tx = parseFloat(args[1]) || 0;
  95. ty = parseFloat(args[2]) || 0;
  96. if (tx !== 0 || ty !== 0) {
  97. ctm = ctm.translate(tx, ty).rotate(angle).translate(-tx, -ty);
  98. }
  99. else {
  100. ctm = ctm.rotate(angle);
  101. }
  102. break;
  103. case 'skewx':
  104. angle = parseFloat(args[0]);
  105. ctm = ctm.skewX(angle);
  106. break;
  107. case 'skewy':
  108. angle = parseFloat(args[0]);
  109. ctm = ctm.skewY(angle);
  110. break;
  111. case 'matrix':
  112. ctm.a = parseFloat(args[0]);
  113. ctm.b = parseFloat(args[1]);
  114. ctm.c = parseFloat(args[2]);
  115. ctm.d = parseFloat(args[3]);
  116. ctm.e = parseFloat(args[4]);
  117. ctm.f = parseFloat(args[5]);
  118. break;
  119. default:
  120. continue;
  121. }
  122. mat = mat.multiply(ctm);
  123. }
  124. }
  125. return mat;
  126. }
  127. export function matrixToTransformString(matrix) {
  128. const m = matrix || {};
  129. const a = m.a != null ? m.a : 1;
  130. const b = m.b != null ? m.b : 0;
  131. const c = m.c != null ? m.c : 0;
  132. const d = m.d != null ? m.d : 1;
  133. const e = m.e != null ? m.e : 0;
  134. const f = m.f != null ? m.f : 0;
  135. return `matrix(${a},${b},${c},${d},${e},${f})`;
  136. }
  137. export function parseTransformString(transform) {
  138. let translation;
  139. let rotation;
  140. let scale;
  141. if (transform) {
  142. const separator = transformSeparatorRegex;
  143. // Allow reading transform string with a single matrix
  144. if (transform.trim().indexOf('matrix') >= 0) {
  145. const matrix = transformStringToMatrix(transform);
  146. const decomposedMatrix = decomposeMatrix(matrix);
  147. translation = [decomposedMatrix.translateX, decomposedMatrix.translateY];
  148. rotation = [decomposedMatrix.rotation];
  149. scale = [decomposedMatrix.scaleX, decomposedMatrix.scaleY];
  150. const transformations = [];
  151. if (translation[0] !== 0 || translation[1] !== 0) {
  152. transformations.push(`translate(${translation.join(',')})`);
  153. }
  154. if (scale[0] !== 1 || scale[1] !== 1) {
  155. transformations.push(`scale(${scale.join(',')})`);
  156. }
  157. if (rotation[0] !== 0) {
  158. transformations.push(`rotate(${rotation[0]})`);
  159. }
  160. transform = transformations.join(' '); // eslint-disable-line
  161. }
  162. else {
  163. const translateMatch = transform.match(/translate\((.*?)\)/);
  164. if (translateMatch) {
  165. translation = translateMatch[1].split(separator);
  166. }
  167. const rotateMatch = transform.match(/rotate\((.*?)\)/);
  168. if (rotateMatch) {
  169. rotation = rotateMatch[1].split(separator);
  170. }
  171. const scaleMatch = transform.match(/scale\((.*?)\)/);
  172. if (scaleMatch) {
  173. scale = scaleMatch[1].split(separator);
  174. }
  175. }
  176. }
  177. const sx = scale && scale[0] ? parseFloat(scale[0]) : 1;
  178. return {
  179. raw: transform || '',
  180. translation: {
  181. tx: translation && translation[0]
  182. ? parseInt(translation[0], 10)
  183. : 0,
  184. ty: translation && translation[1]
  185. ? parseInt(translation[1], 10)
  186. : 0,
  187. },
  188. rotation: {
  189. angle: rotation && rotation[0] ? parseInt(rotation[0], 10) : 0,
  190. cx: rotation && rotation[1]
  191. ? parseInt(rotation[1], 10)
  192. : undefined,
  193. cy: rotation && rotation[2]
  194. ? parseInt(rotation[2], 10)
  195. : undefined,
  196. },
  197. scale: {
  198. sx,
  199. sy: scale && scale[1] ? parseFloat(scale[1]) : sx,
  200. },
  201. };
  202. }
  203. function deltaTransformPoint(matrix, point) {
  204. const dx = point.x * matrix.a + point.y * matrix.c + 0;
  205. const dy = point.x * matrix.b + point.y * matrix.d + 0;
  206. return { x: dx, y: dy };
  207. }
  208. /**
  209. * Decomposes the SVG transformation matrix into separate transformations.
  210. *
  211. * Returns an object of the form:
  212. * {
  213. * translateX: number
  214. * translateY: number
  215. * scaleX: number
  216. * scaleY: number
  217. * skewX: number
  218. * skewY: number
  219. * rotation: number
  220. * }
  221. *
  222. * @see https://developer.mozilla.org/en/docs/Web/API/SVGMatrix
  223. */
  224. export function decomposeMatrix(matrix) {
  225. // @see https://gist.github.com/2052247
  226. const px = deltaTransformPoint(matrix, { x: 0, y: 1 });
  227. const py = deltaTransformPoint(matrix, { x: 1, y: 0 });
  228. const skewX = (180 / Math.PI) * Math.atan2(px.y, px.x) - 90;
  229. const skewY = (180 / Math.PI) * Math.atan2(py.y, py.x);
  230. return {
  231. skewX,
  232. skewY,
  233. translateX: matrix.e,
  234. translateY: matrix.f,
  235. scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
  236. scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
  237. rotation: skewX,
  238. };
  239. }
  240. export function matrixToScale(matrix) {
  241. let a;
  242. let b;
  243. let c;
  244. let d;
  245. if (matrix) {
  246. a = matrix.a == null ? 1 : matrix.a;
  247. d = matrix.d == null ? 1 : matrix.d;
  248. b = matrix.b;
  249. c = matrix.c;
  250. }
  251. else {
  252. a = d = 1;
  253. }
  254. return {
  255. sx: b ? Math.sqrt(a * a + b * b) : a,
  256. sy: c ? Math.sqrt(c * c + d * d) : d,
  257. };
  258. }
  259. export function matrixToRotation(matrix) {
  260. let p = { x: 0, y: 1 };
  261. if (matrix) {
  262. p = deltaTransformPoint(matrix, p);
  263. }
  264. const deg = (((180 * Math.atan2(p.y, p.x)) / Math.PI) % 360) - 90;
  265. const angle = (deg % 360) + (deg < 0 ? 360 : 0);
  266. return {
  267. angle,
  268. };
  269. }
  270. export function matrixToTranslation(matrix) {
  271. return {
  272. tx: (matrix && matrix.e) || 0,
  273. ty: (matrix && matrix.f) || 0,
  274. };
  275. }
  276. //# sourceMappingURL=matrix.js.map