blob: b5b7801dfd5e6fe2de046f6d075ef018a047c1ef [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/******** Constants ********/
/** Geometric orientation axes. */
const ORIENTATION = checkEnum({
HORIZ: 0,
VERT: 1,
});
/******** Math Utilities ********/
/** Constrains a value between a lower and upper bound. */
function clip(lo, v, hi) {
return Math.max(lo, Math.min(v, hi));
}
/**
* Formats a number like toFixed(), but strips trailing zeros and period.
*/
function capFixed(v, prec) {
return v.toFixed(prec).replace(/(?:\.0+|(\.\d*[1-9])0+)$/, '$1');
}
/******** Point2D ********/
/**
* Represents a 2D vector or point using an Array backend for performance.
* Base class for specialized coordinate types like PointXY and Dims2D.
*/
class Point2D extends Array {
/**
* @param {number} v0 The first component (e.g., X or U).
* @param {number} v1 The second component (e.g., Y or V).
*/
constructor(v0, v1) {
super(v0, v1);
}
/** @return {!this} A copy of this point, preserving the subclass type. */
clone() {
return new this.constructor(this[0], this[1]);
}
/**
* Updates both components in-place.
* @return {!Point2D} This point for chaining.
*/
assign(v0, v1) {
this[0] = v0;
this[1] = v1;
return this;
}
/** Multiplies this point by a scalar in-place, chainable. */
mulBy(s) {
this[0] *= s;
this[1] *= s;
return this;
}
// clang-format off
/** @return {number} Dot product with another point. */
dot(o) { return this[0] * o[0] + this[1] * o[1]; }
/** @return {number} Dot product with literal X/Y components. */
dotxy(x, y) { return this[0] * x + this[1] * y; }
// clang-format on
}
/******** PointXY ********/
/**
* Spatial coordinates X / Y, typically for global World or Scaled domains.
*/
class PointXY extends Point2D {
// clang-format off
get x() { return this[0]; }
get y() { return this[1]; }
set x(t) { this[0] = t; }
set y(t) { this[1] = t; }
// clang-format on
}
/******** Dims2D ********/
/**
* Sizing quantity {w, h}, should have non-negative values.
*/
class Dims2D extends Point2D {
// clang-format off
/**
* @param {Point2D} axisAlignedVect
* @return {number} Size projected along an axis vector (non-negative).
*/
sizeAlong(axisAlignedVect) {
return Math.abs(this.dot(axisAlignedVect));
}
get w() { return this[0]; }
get h() { return this[1]; }
set w(t) { this[0] = t; }
set h(t) { this[1] = t; }
// clang-format on
}
/******** Rectangle ********/
/**
* An axis-aligned rectangle with inclusive left-top and exclusive right-bottom.
*/
class Rectangle {
/**
* @param {!PointXY} leftTop
* @param {!PointXY} rightBottom
*/
constructor(leftTop, rightBottom) {
this.leftTop = leftTop;
this.rightBottom = rightBottom;
}
/** @return {!Rectangle} */
static fromComponents(x1, y1, x2, y2) {
return new Rectangle(new PointXY(x1, y1), new PointXY(x2, y2));
}
/**
* Parses Android XML bounds string "[x1,y1][x2,y2]".
* @param {?string} rectStr
* @return {!Rectangle} The parsed rectangle, or a 0x0 rectangle on error.
*/
static fromAndroidBounds(rectStr) {
if (rectStr) {
const match = rectStr.match(/-?\d+/g);
if (match && match.length >= 4) {
return Rectangle.fromComponents(...match.map(Number));
}
}
console.warn(`Bad rectangle: ${rectStr}`);
return Rectangle.fromComponents(0, 0, 0, 0);
}
// clang-format off
get left() { return this.leftTop.x; }
get top() { return this.leftTop.y; }
get right() { return this.rightBottom.x; }
get bottom() { return this.rightBottom.y; }
get width() { return this.right - this.left; }
get height() { return this.bottom - this.top; }
// clang-format on
}