/** * @module Geometry */ /** * @typedef {(Point|Size|number|number[]|{x: number, y:number})} PointLike * @description A point-like object. Can be a Point, Size, number, array, or object with x and y properties. */ /** * @param {PointLike} pointLike The point-like object to parse * @param {function} callback The callback to call with the parsed point * @throws {Error} If the point-like object is invalid * @private */ function pointLikeParsing(pointLike, y, callback) { if (typeof pointLike === 'number' && typeof y === 'number') { return callback(pointLike, y); } else if (pointLike instanceof Point) { return callback(pointLike.x, pointLike.y); } else if (pointLike instanceof Size) { return callback(pointLike.width, pointLike.height); } else if (typeof pointLike === 'number') { if (typeof y === 'number') { return callback(pointLike, y); } else { throw new Error('Invalid arguments. If the first argument is a number, the second argument must be a number'); } } else if (Array.isArray(pointLike)) { return callback(pointLike[0], pointLike[1]); } else if (typeof pointLike?.x === 'number' && typeof pointLike?.y === 'number') { } else { throw new Error('Invalid arguments. The first argument must be a Point, Size, number, array, or object with x and y properties'); } } /** * A point in 2D space * @class * @example * // Create a new point at (10, 10) * const point = new Point(10, 10); */ class Point { /** * Creates a new Point * @param {number} x The x coordinate. (Can be any number from 0 to process.stdout.columns) * @param {number} y The y coordinate. (Can be any number from 0 to process.stdout.rows) * @throws {Error} If the arguments are invalid or out of bounds */ constructor(x, y) { if (!Point.inBounds(x, y)) throw new Error('Invalid arguments. Point coordinates must be within the bounds of the terminal'); /** * The x coordinate * @type {number} */ this.x = Math.round(x); /** * The y coordinate * @type {number} */ this.y = Math.round(y); } /** * Transforms the point by the given matrix * @param {Matrix} matrix The matrix to transform the point by */ transform(matrix) { this.x = Math.round(this.x * matrix.a + this.y * matrix.c + matrix.e); this.y = Math.round(this.x * matrix.b + this.y * matrix.d + matrix.f); } /** * Determines whether or not the point is equal to another point * @param {PointLike} x The point to compare to, or the x coordinate * @param {number} [y] The y coordinate * @returns {boolean} Whether or not the points are equal */ equals(x, y) { return pointLikeParsing(x, y, (x, y) => this.x === x && this.y === y); } /** * Creates a new point at the center of the terminal * @returns {Point} The center point */ static center() { return new Point(process.stdout.columns / 2, process.stdout.rows / 2); } /** * * @param {PointLike} x The point or size to center, or the x coordinate * @param {number} [y] The y coordinate * @returns {Point} */ static centerFor(x, y) { return pointLikeParsing(x, y, (width, height) => new Point(process.stdout.columns / 2 - width / 2, process.stdout.rows / 2 - height / 2)); } /** * Creates a new point at (0, 0) * @returns {Point} A new point at (0, 0) */ static zero() { return new Point(0, 0); } /** * Determines whether or not the given point is within the bounds of the terminal * @param {PointLike} px The point or size to check, or the x coordinate * @param {number} [y] The y coordinate * @returns {boolean} Whether or not the point is within the bounds of the terminal * @throws {Error} If the arguments are invalid */ static inBounds(px, y) { return pointLikeParsing(px, y, (x, y) => x >= 0 && x < process.stdout.columns && y >= 0 && y < process.stdout.rows); } /** * Creates a new point from the given point, size, array, or coordinates * @param {(Point|Size|number|number[]|{x: number, y:number})} psx The point or size to create a new point from, or the x coordinate * @param {number} [y] The y coordinate * @returns {Point} The new point */ static of(psx, y) { return pointLikeParsing(psx, y, (x, y) => new Point(x, y)); } } /** * A matrix for transforming points * @class */ class Matrix { /** * The identity matrix * @param {number} a The a value * @param {number} b The b value * @param {number} c The c value * @param {number} d The d value * @param {number} e The e value * @param {number} f The f value */ constructor(a, b, c, d, e, f) { /** * The a value * @type {number} */ this.a = a; /** * The b value * @type {number} */ this.b = b; /** * The c value * @type {number} */ this.c = c; /** * The d value * @type {number} */ this.d = d; /** * The e value * @type {number} */ this.e = e; /** * The f value * @type {number} */ this.f = f; } } /** * A size in 2D space * @class * @example * // Create a new size of (10, 10) * const size = new Size(10, 10); */ class Size { /** * Creates a new Size * @param {number} width The width * @param {number} height The height */ constructor(width, height) { if (!Point.inBounds(width, height)) throw new Error('Invalid arguments. Size coordinates must be within the bounds of the terminal'); /** * The width * @type {number} */ this.width = Math.round(width); /** * The height * @type {number} */ this.height = Math.round(height); } } export { Point, Matrix, Size }