dom-matrix-2d.js (8830B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * Returns a matrix for the scaling given. 9 * Calling `scale()` or `scale(1) returns a new identity matrix. 10 * 11 * @param {number} [sx = 1] 12 * the abscissa of the scaling vector. 13 * If unspecified, it will equal to `1`. 14 * @param {number} [sy = sx] 15 * The ordinate of the scaling vector. 16 * If not present, its default value is `sx`, leading to a uniform scaling. 17 * @return {Array} 18 * The new matrix. 19 */ 20 const scale = (sx = 1, sy = sx) => [sx, 0, 0, 0, sy, 0, 0, 0, 1]; 21 exports.scale = scale; 22 23 /** 24 * Returns a matrix for the translation given. 25 * Calling `translate()` or `translate(0) returns a new identity matrix. 26 * 27 * @param {number} [tx = 0] 28 * The abscissa of the translating vector. 29 * If unspecified, it will equal to `0`. 30 * @param {number} [ty = tx] 31 * The ordinate of the translating vector. 32 * If unspecified, it will equal to `tx`. 33 * @return {Array} 34 * The new matrix. 35 */ 36 const translate = (tx = 0, ty = tx) => [1, 0, tx, 0, 1, ty, 0, 0, 1]; 37 exports.translate = translate; 38 39 /** 40 * Returns a matrix that reflects about the Y axis. For example, the point (x1, y1) would 41 * become (-x1, y1). 42 * 43 * @return {Array} 44 * The new matrix. 45 */ 46 const reflectAboutY = () => [-1, 0, 0, 0, 1, 0, 0, 0, 1]; 47 exports.reflectAboutY = reflectAboutY; 48 49 /** 50 * Returns a matrix for the rotation given. 51 * Calling `rotate()` or `rotate(0)` returns a new identity matrix. 52 * 53 * @param {number} [angle = 0] 54 * The angle, in radians, for which to return a corresponding rotation matrix. 55 * If unspecified, it will equal `0`. 56 * @return {Array} 57 * The new matrix. 58 */ 59 const rotate = (angle = 0) => { 60 const cos = Math.cos(angle); 61 const sin = Math.sin(angle); 62 63 return [cos, sin, 0, -sin, cos, 0, 0, 0, 1]; 64 }; 65 exports.rotate = rotate; 66 67 /** 68 * Returns a new identity matrix. 69 * 70 * @return {Array} 71 * The new matrix. 72 */ 73 const identity = () => [1, 0, 0, 0, 1, 0, 0, 0, 1]; 74 exports.identity = identity; 75 76 /** 77 * Multiplies two matrices and returns a new matrix with the result. 78 * 79 * @param {Array} M1 80 * The first operand. 81 * @param {Array} M2 82 * The second operand. 83 * @return {Array} 84 * The resulting matrix. 85 */ 86 const multiply = (M1, M2) => { 87 const c11 = M1[0] * M2[0] + M1[1] * M2[3] + M1[2] * M2[6]; 88 const c12 = M1[0] * M2[1] + M1[1] * M2[4] + M1[2] * M2[7]; 89 const c13 = M1[0] * M2[2] + M1[1] * M2[5] + M1[2] * M2[8]; 90 91 const c21 = M1[3] * M2[0] + M1[4] * M2[3] + M1[5] * M2[6]; 92 const c22 = M1[3] * M2[1] + M1[4] * M2[4] + M1[5] * M2[7]; 93 const c23 = M1[3] * M2[2] + M1[4] * M2[5] + M1[5] * M2[8]; 94 95 const c31 = M1[6] * M2[0] + M1[7] * M2[3] + M1[8] * M2[6]; 96 const c32 = M1[6] * M2[1] + M1[7] * M2[4] + M1[8] * M2[7]; 97 const c33 = M1[6] * M2[2] + M1[7] * M2[5] + M1[8] * M2[8]; 98 99 return [c11, c12, c13, c21, c22, c23, c31, c32, c33]; 100 }; 101 exports.multiply = multiply; 102 103 /** 104 * Applies the given matrix to a point. 105 * 106 * @param {Array} M 107 * The matrix to apply. 108 * @param {Array} P 109 * The point's vector. 110 * @return {Array} 111 * The resulting point's vector. 112 */ 113 const apply = (M, P) => [ 114 M[0] * P[0] + M[1] * P[1] + M[2], 115 M[3] * P[0] + M[4] * P[1] + M[5], 116 ]; 117 exports.apply = apply; 118 119 /** 120 * Returns `true` if the given matrix is a identity matrix. 121 * 122 * @param {Array} M 123 * The matrix to check 124 * @return {boolean} 125 * `true` if the matrix passed is a identity matrix, `false` otherwise. 126 */ 127 const isIdentity = M => 128 M[0] === 1 && 129 M[1] === 0 && 130 M[2] === 0 && 131 M[3] === 0 && 132 M[4] === 1 && 133 M[5] === 0 && 134 M[6] === 0 && 135 M[7] === 0 && 136 M[8] === 1; 137 exports.isIdentity = isIdentity; 138 139 /** 140 * Get the change of basis matrix and inverted change of basis matrix 141 * for the coordinate system based on the two given vectors, as well as 142 * the lengths of the two given vectors. 143 * 144 * @param {Array} u 145 * The first vector, serving as the "x axis" of the coordinate system. 146 * @param {Array} v 147 * The second vector, serving as the "y axis" of the coordinate system. 148 * @return {object} 149 * { basis, invertedBasis, uLength, vLength } 150 * basis and invertedBasis are the change of basis matrices. uLength and 151 * vLength are the lengths of u and v. 152 */ 153 const getBasis = (u, v) => { 154 const uLength = Math.abs(Math.sqrt(u[0] ** 2 + u[1] ** 2)); 155 const vLength = Math.abs(Math.sqrt(v[0] ** 2 + v[1] ** 2)); 156 const basis = [ 157 u[0] / uLength, 158 v[0] / vLength, 159 0, 160 u[1] / uLength, 161 v[1] / vLength, 162 0, 163 0, 164 0, 165 1, 166 ]; 167 const determinant = 1 / (basis[0] * basis[4] - basis[1] * basis[3]); 168 const invertedBasis = [ 169 basis[4] / determinant, 170 -basis[1] / determinant, 171 0, 172 -basis[3] / determinant, 173 basis[0] / determinant, 174 0, 175 0, 176 0, 177 1, 178 ]; 179 return { basis, invertedBasis, uLength, vLength }; 180 }; 181 exports.getBasis = getBasis; 182 183 /** 184 * Convert the given matrix to a new coordinate system, based on the change of basis 185 * matrix. 186 * 187 * @param {Array} M 188 * The matrix to convert 189 * @param {Array} basis 190 * The change of basis matrix 191 * @param {Array} invertedBasis 192 * The inverted change of basis matrix 193 * @return {Array} 194 * The converted matrix. 195 */ 196 const changeMatrixBase = (M, basis, invertedBasis) => { 197 return multiply(invertedBasis, multiply(M, basis)); 198 }; 199 exports.changeMatrixBase = changeMatrixBase; 200 201 /** 202 * Returns the transformation matrix for the given node, relative to the ancestor passed 203 * as second argument; considering the ancestor transformation too. 204 * If no ancestor is specified, it will returns the transformation matrix relative to the 205 * node's parent element. 206 * 207 * @param {DOMNode} node 208 * The node. 209 * @param {DOMNode} ancestor 210 * The ancestor of the node given. 211 * @return {Array} 212 * The transformation matrix. 213 */ 214 function getNodeTransformationMatrix(node, ancestor = node.parentElement) { 215 const { a, b, c, d, e, f } = ancestor 216 .getTransformToParent() 217 .multiply(node.getTransformToAncestor(ancestor)); 218 219 return [a, c, e, b, d, f, 0, 0, 1]; 220 } 221 exports.getNodeTransformationMatrix = getNodeTransformationMatrix; 222 223 /** 224 * Returns the matrix to rotate, translate, and reflect (if needed) from the element's 225 * top-left origin into the actual writing mode and text direction applied to the element. 226 * 227 * @param {object} size 228 * An element's untransformed content `width` and `height` (excluding any margin, 229 * borders, or padding). 230 * @param {object} style 231 * The computed `writingMode` and `direction` properties for the element. 232 * @return {Array} 233 * The matrix with adjustments for writing mode and text direction, if any. 234 */ 235 function getWritingModeMatrix(size, style) { 236 let currentMatrix = identity(); 237 const { width, height } = size; 238 const { direction, writingMode } = style; 239 240 switch (writingMode) { 241 case "horizontal-tb": 242 // This is the initial value. No further adjustment needed. 243 break; 244 case "vertical-rl": 245 currentMatrix = multiply(translate(width, 0), rotate(-Math.PI / 2)); 246 break; 247 case "vertical-lr": 248 currentMatrix = multiply(reflectAboutY(), rotate(-Math.PI / 2)); 249 break; 250 case "sideways-rl": 251 currentMatrix = multiply(translate(width, 0), rotate(-Math.PI / 2)); 252 break; 253 case "sideways-lr": 254 currentMatrix = multiply(rotate(Math.PI / 2), translate(-height, 0)); 255 break; 256 default: 257 console.error(`Unexpected writing-mode: ${writingMode}`); 258 } 259 260 switch (direction) { 261 case "ltr": 262 // This is the initial value. No further adjustment needed. 263 break; 264 case "rtl": { 265 let rowLength = width; 266 if (writingMode != "horizontal-tb") { 267 rowLength = height; 268 } 269 currentMatrix = multiply(currentMatrix, translate(rowLength, 0)); 270 currentMatrix = multiply(currentMatrix, reflectAboutY()); 271 break; 272 } 273 default: 274 console.error(`Unexpected direction: ${direction}`); 275 } 276 277 return currentMatrix; 278 } 279 exports.getWritingModeMatrix = getWritingModeMatrix; 280 281 /** 282 * Convert from the matrix format used in this module: 283 * a, c, e, 284 * b, d, f, 285 * 0, 0, 1 286 * to the format used by the `matrix()` CSS transform function: 287 * a, b, c, d, e, f 288 * 289 * @param {Array} M 290 * The matrix in this module's 9 element format. 291 * @return {string} 292 * The matching 6 element CSS transform function. 293 */ 294 function getCSSMatrixTransform(M) { 295 const [a, c, e, b, d, f] = M; 296 return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`; 297 } 298 exports.getCSSMatrixTransform = getCSSMatrixTransform;