summaryrefslogtreecommitdiffstats
path: root/devtools/shared/layout/dom-matrix-2d.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/layout/dom-matrix-2d.js')
-rw-r--r--devtools/shared/layout/dom-matrix-2d.js297
1 files changed, 297 insertions, 0 deletions
diff --git a/devtools/shared/layout/dom-matrix-2d.js b/devtools/shared/layout/dom-matrix-2d.js
new file mode 100644
index 0000000000..f6e3e73067
--- /dev/null
+++ b/devtools/shared/layout/dom-matrix-2d.js
@@ -0,0 +1,297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Returns a matrix for the scaling given.
+ * Calling `scale()` or `scale(1) returns a new identity matrix.
+ *
+ * @param {Number} [sx = 1]
+ * the abscissa of the scaling vector.
+ * If unspecified, it will equal to `1`.
+ * @param {Number} [sy = sx]
+ * The ordinate of the scaling vector.
+ * If not present, its default value is `sx`, leading to a uniform scaling.
+ * @return {Array}
+ * The new matrix.
+ */
+const scale = (sx = 1, sy = sx) => [sx, 0, 0, 0, sy, 0, 0, 0, 1];
+exports.scale = scale;
+
+/**
+ * Returns a matrix for the translation given.
+ * Calling `translate()` or `translate(0) returns a new identity matrix.
+ *
+ * @param {Number} [tx = 0]
+ * The abscissa of the translating vector.
+ * If unspecified, it will equal to `0`.
+ * @param {Number} [ty = tx]
+ * The ordinate of the translating vector.
+ * If unspecified, it will equal to `tx`.
+ * @return {Array}
+ * The new matrix.
+ */
+const translate = (tx = 0, ty = tx) => [1, 0, tx, 0, 1, ty, 0, 0, 1];
+exports.translate = translate;
+
+/**
+ * Returns a matrix that reflects about the Y axis. For example, the point (x1, y1) would
+ * become (-x1, y1).
+ *
+ * @return {Array}
+ * The new matrix.
+ */
+const reflectAboutY = () => [-1, 0, 0, 0, 1, 0, 0, 0, 1];
+exports.reflectAboutY = reflectAboutY;
+
+/**
+ * Returns a matrix for the rotation given.
+ * Calling `rotate()` or `rotate(0)` returns a new identity matrix.
+ *
+ * @param {Number} [angle = 0]
+ * The angle, in radians, for which to return a corresponding rotation matrix.
+ * If unspecified, it will equal `0`.
+ * @return {Array}
+ * The new matrix.
+ */
+const rotate = (angle = 0) => {
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+
+ return [cos, sin, 0, -sin, cos, 0, 0, 0, 1];
+};
+exports.rotate = rotate;
+
+/**
+ * Returns a new identity matrix.
+ *
+ * @return {Array}
+ * The new matrix.
+ */
+const identity = () => [1, 0, 0, 0, 1, 0, 0, 0, 1];
+exports.identity = identity;
+
+/**
+ * Multiplies two matrices and returns a new matrix with the result.
+ *
+ * @param {Array} M1
+ * The first operand.
+ * @param {Array} M2
+ * The second operand.
+ * @return {Array}
+ * The resulting matrix.
+ */
+const multiply = (M1, M2) => {
+ const c11 = M1[0] * M2[0] + M1[1] * M2[3] + M1[2] * M2[6];
+ const c12 = M1[0] * M2[1] + M1[1] * M2[4] + M1[2] * M2[7];
+ const c13 = M1[0] * M2[2] + M1[1] * M2[5] + M1[2] * M2[8];
+
+ const c21 = M1[3] * M2[0] + M1[4] * M2[3] + M1[5] * M2[6];
+ const c22 = M1[3] * M2[1] + M1[4] * M2[4] + M1[5] * M2[7];
+ const c23 = M1[3] * M2[2] + M1[4] * M2[5] + M1[5] * M2[8];
+
+ const c31 = M1[6] * M2[0] + M1[7] * M2[3] + M1[8] * M2[6];
+ const c32 = M1[6] * M2[1] + M1[7] * M2[4] + M1[8] * M2[7];
+ const c33 = M1[6] * M2[2] + M1[7] * M2[5] + M1[8] * M2[8];
+
+ return [c11, c12, c13, c21, c22, c23, c31, c32, c33];
+};
+exports.multiply = multiply;
+
+/**
+ * Applies the given matrix to a point.
+ *
+ * @param {Array} M
+ * The matrix to apply.
+ * @param {Array} P
+ * The point's vector.
+ * @return {Array}
+ * The resulting point's vector.
+ */
+const apply = (M, P) => [
+ M[0] * P[0] + M[1] * P[1] + M[2],
+ M[3] * P[0] + M[4] * P[1] + M[5],
+];
+exports.apply = apply;
+
+/**
+ * Returns `true` if the given matrix is a identity matrix.
+ *
+ * @param {Array} M
+ * The matrix to check
+ * @return {Boolean}
+ * `true` if the matrix passed is a identity matrix, `false` otherwise.
+ */
+const isIdentity = M =>
+ M[0] === 1 &&
+ M[1] === 0 &&
+ M[2] === 0 &&
+ M[3] === 0 &&
+ M[4] === 1 &&
+ M[5] === 0 &&
+ M[6] === 0 &&
+ M[7] === 0 &&
+ M[8] === 1;
+exports.isIdentity = isIdentity;
+
+/**
+ * Get the change of basis matrix and inverted change of basis matrix
+ * for the coordinate system based on the two given vectors, as well as
+ * the lengths of the two given vectors.
+ *
+ * @param {Array} u
+ * The first vector, serving as the "x axis" of the coordinate system.
+ * @param {Array} v
+ * The second vector, serving as the "y axis" of the coordinate system.
+ * @return {Object}
+ * { basis, invertedBasis, uLength, vLength }
+ * basis and invertedBasis are the change of basis matrices. uLength and
+ * vLength are the lengths of u and v.
+ */
+const getBasis = (u, v) => {
+ const uLength = Math.abs(Math.sqrt(u[0] ** 2 + u[1] ** 2));
+ const vLength = Math.abs(Math.sqrt(v[0] ** 2 + v[1] ** 2));
+ const basis = [
+ u[0] / uLength,
+ v[0] / vLength,
+ 0,
+ u[1] / uLength,
+ v[1] / vLength,
+ 0,
+ 0,
+ 0,
+ 1,
+ ];
+ const determinant = 1 / (basis[0] * basis[4] - basis[1] * basis[3]);
+ const invertedBasis = [
+ basis[4] / determinant,
+ -basis[1] / determinant,
+ 0,
+ -basis[3] / determinant,
+ basis[0] / determinant,
+ 0,
+ 0,
+ 0,
+ 1,
+ ];
+ return { basis, invertedBasis, uLength, vLength };
+};
+exports.getBasis = getBasis;
+
+/**
+ * Convert the given matrix to a new coordinate system, based on the change of basis
+ * matrix.
+ *
+ * @param {Array} M
+ * The matrix to convert
+ * @param {Array} basis
+ * The change of basis matrix
+ * @param {Array} invertedBasis
+ * The inverted change of basis matrix
+ * @return {Array}
+ * The converted matrix.
+ */
+const changeMatrixBase = (M, basis, invertedBasis) => {
+ return multiply(invertedBasis, multiply(M, basis));
+};
+exports.changeMatrixBase = changeMatrixBase;
+
+/**
+ * Returns the transformation matrix for the given node, relative to the ancestor passed
+ * as second argument; considering the ancestor transformation too.
+ * If no ancestor is specified, it will returns the transformation matrix relative to the
+ * node's parent element.
+ *
+ * @param {DOMNode} node
+ * The node.
+ * @param {DOMNode} ancestor
+ * The ancestor of the node given.
+ * @return {Array}
+ * The transformation matrix.
+ */
+function getNodeTransformationMatrix(node, ancestor = node.parentElement) {
+ const { a, b, c, d, e, f } = ancestor
+ .getTransformToParent()
+ .multiply(node.getTransformToAncestor(ancestor));
+
+ return [a, c, e, b, d, f, 0, 0, 1];
+}
+exports.getNodeTransformationMatrix = getNodeTransformationMatrix;
+
+/**
+ * Returns the matrix to rotate, translate, and reflect (if needed) from the element's
+ * top-left origin into the actual writing mode and text direction applied to the element.
+ *
+ * @param {Object} size
+ * An element's untransformed content `width` and `height` (excluding any margin,
+ * borders, or padding).
+ * @param {Object} style
+ * The computed `writingMode` and `direction` properties for the element.
+ * @return {Array}
+ * The matrix with adjustments for writing mode and text direction, if any.
+ */
+function getWritingModeMatrix(size, style) {
+ let currentMatrix = identity();
+ const { width, height } = size;
+ const { direction, writingMode } = style;
+
+ switch (writingMode) {
+ case "horizontal-tb":
+ // This is the initial value. No further adjustment needed.
+ break;
+ case "vertical-rl":
+ currentMatrix = multiply(translate(width, 0), rotate(-Math.PI / 2));
+ break;
+ case "vertical-lr":
+ currentMatrix = multiply(reflectAboutY(), rotate(-Math.PI / 2));
+ break;
+ case "sideways-rl":
+ currentMatrix = multiply(translate(width, 0), rotate(-Math.PI / 2));
+ break;
+ case "sideways-lr":
+ currentMatrix = multiply(rotate(Math.PI / 2), translate(-height, 0));
+ break;
+ default:
+ console.error(`Unexpected writing-mode: ${writingMode}`);
+ }
+
+ switch (direction) {
+ case "ltr":
+ // This is the initial value. No further adjustment needed.
+ break;
+ case "rtl":
+ let rowLength = width;
+ if (writingMode != "horizontal-tb") {
+ rowLength = height;
+ }
+ currentMatrix = multiply(currentMatrix, translate(rowLength, 0));
+ currentMatrix = multiply(currentMatrix, reflectAboutY());
+ break;
+ default:
+ console.error(`Unexpected direction: ${direction}`);
+ }
+
+ return currentMatrix;
+}
+exports.getWritingModeMatrix = getWritingModeMatrix;
+
+/**
+ * Convert from the matrix format used in this module:
+ * a, c, e,
+ * b, d, f,
+ * 0, 0, 1
+ * to the format used by the `matrix()` CSS transform function:
+ * a, b, c, d, e, f
+ *
+ * @param {Array} M
+ * The matrix in this module's 9 element format.
+ * @return {String}
+ * The matching 6 element CSS transform function.
+ */
+function getCSSMatrixTransform(M) {
+ const [a, c, e, b, d, f] = M;
+ return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
+}
+exports.getCSSMatrixTransform = getCSSMatrixTransform;