// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; namespace SixLabors.Fonts { /// /// Stores a set of four single precision floating points that represent the location and size of a rectangle. /// public readonly struct FontRectangle : IEquatable { /// /// Represents a that has X, Y, Width, and Height values set to zero. /// public static readonly FontRectangle Empty; /// /// Initializes a new instance of the struct. /// /// The horizontal position of the rectangle. /// The vertical position of the rectangle. /// The width of the rectangle. /// The height of the rectangle. public FontRectangle(float x, float y, float width, float height) { this.X = x; this.Y = y; this.Width = width; this.Height = height; } /// /// Initializes a new instance of the struct. /// /// /// The which specifies the rectangles point in a two-dimensional plane. /// /// /// The which specifies the rectangles height and width. /// public FontRectangle(Vector2 point, Vector2 size) : this(point.X, point.Y, size.X, size.Y) { } /// /// Initializes a new instance of the structure using the specified bounding box. /// /// The bounding box that defines the position and size of the rectangle. internal FontRectangle(in Bounds bound) { this.X = bound.Min.X; this.Y = bound.Min.Y; Vector2 size = bound.Max - bound.Min; this.Width = size.X; this.Height = size.Y; } /// /// Gets the x-coordinate of this . /// public float X { get; } /// /// Gets the y-coordinate of this . /// public float Y { get; } /// /// Gets the width of this . /// public float Width { get; } /// /// Gets the height of this . /// public float Height { get; } /// /// Gets the coordinates of the upper-left corner of the rectangular region represented by this . /// [EditorBrowsable(EditorBrowsableState.Never)] public readonly Vector2 Location { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(this.X, this.Y); } /// /// Gets the size of this . /// [EditorBrowsable(EditorBrowsableState.Never)] public readonly Vector2 Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(this.Width, this.Height); } /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] public readonly bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); /// /// Gets the y-coordinate of the top edge of this . /// public readonly float Top => this.Y; /// /// Gets the x-coordinate of the right edge of this . /// public float Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.X + this.Width; } /// /// Gets the y-coordinate of the bottom edge of this . /// public float Bottom { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.Y + this.Height; } /// /// Gets the x-coordinate of the left edge of this . /// public float Left => this.X; /// /// Compares two objects for equality. /// /// The on the left side of the operand. /// The on the right side of the operand. /// /// True if the current left is equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(in FontRectangle left, in FontRectangle right) => left.Equals(right); /// /// Compares two objects for inequality. /// /// The on the left side of the operand. /// The on the right side of the operand. /// /// True if the current left is unequal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(in FontRectangle left, in FontRectangle right) => !left.Equals(right); /// /// Creates a new with the specified location and size. /// The left coordinate of the rectangle. /// The top coordinate of the rectangle. /// The right coordinate of the rectangle. /// The bottom coordinate of the rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] // ReSharper disable once InconsistentNaming public static FontRectangle FromLTRB(float left, float top, float right, float bottom) => new(left, top, right - left, bottom - top); /// /// Returns the center point of the given . /// /// The rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Center(in FontRectangle rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); /// /// Creates a rectangle that represents the intersection between and /// . If there is no intersection, an empty rectangle is returned. /// /// The first rectangle. /// The second rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FontRectangle Intersect(in FontRectangle a, in FontRectangle b) { float x1 = MathF.Max(a.X, b.X); float x2 = MathF.Min(a.Right, b.Right); float y1 = MathF.Max(a.Y, b.Y); float y2 = MathF.Min(a.Bottom, b.Bottom); if (x2 >= x1 && y2 >= y1) { return new FontRectangle(x1, y1, x2 - x1, y2 - y1); } return Empty; } /// /// Creates a new from the given /// that is inflated by the specified amount. /// /// The rectangle. /// The amount to inflate the width by. /// The amount to inflate the height by. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FontRectangle Inflate(in FontRectangle rectangle, float x, float y) => rectangle.Inflate(x, y); /// /// Creates a new by transforming the given rectangle by the given matrix. /// /// The source rectangle. /// The transformation matrix. /// A transformed . public static FontRectangle Transform(in FontRectangle rectangle, Matrix3x2 matrix) { Vector2 bottomRight = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); Vector2 topLeft = Vector2.Transform(rectangle.Location, matrix); Vector2 size = bottomRight - topLeft; return new FontRectangle(topLeft, size); } /// /// Creates a rectangle that represents the union between and . /// /// The first rectangle. /// The second rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FontRectangle Union(in FontRectangle a, in FontRectangle b) { float x1 = MathF.Min(a.X, b.X); float x2 = MathF.Max(a.Right, b.Right); float y1 = MathF.Min(a.Y, b.Y); float y2 = MathF.Max(a.Bottom, b.Bottom); return new FontRectangle(x1, y1, x2 - x1, y2 - y1); } /// /// Deconstructs this rectangle into four floats. /// /// The out value for X. /// The out value for Y. /// The out value for the width. /// The out value for the height. public void Deconstruct(out float x, out float y, out float width, out float height) { x = this.X; y = this.Y; width = this.Width; height = this.Height; } /// /// Creates a FontRectangle that represents the intersection between this FontRectangle and the . /// /// The rectangle. /// New representing the intersections between the two rectangles. [MethodImpl(MethodImplOptions.AggressiveInlining)] public FontRectangle Intersect(in FontRectangle rectangle) => Intersect(rectangle, this); /// /// Creates a new inflated by the specified amount. /// /// The width. /// The height. /// New representing the inflated rectangle [MethodImpl(MethodImplOptions.AggressiveInlining)] public FontRectangle Inflate(float width, float height) => new( this.X - width, this.Y - height, this.Width + (2 * width), this.Height + (2 * height)); /// /// Creates a new inflated by the specified amount. /// /// The size. /// New representing the inflated rectangle [MethodImpl(MethodImplOptions.AggressiveInlining)] public FontRectangle Inflate(Vector2 size) => this.Inflate(size.X, size.Y); /// /// Determines if the specified point is contained within the rectangular region defined by /// this . /// /// The x-coordinate of the given point. /// The y-coordinate of the given point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; /// /// Determines if the specified point is contained within the rectangular region defined by this . /// /// The point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(Vector2 point) => this.Contains(point.X, point.Y); /// /// Determines if the rectangular region represented by is entirely contained /// within the rectangular region represented by this . /// /// The rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(in FontRectangle rectangle) => (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); /// /// Determines if the specified intersects the rectangular region defined by /// this . /// /// The other rectangle. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IntersectsWith(in FontRectangle rectangle) => (rectangle.X < this.Right) && (this.X < rectangle.Right) && (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); /// /// Adjusts the location of this rectangle by the specified amount. /// /// The point. /// New representing the offset rectangle. [MethodImpl(MethodImplOptions.AggressiveInlining)] public FontRectangle Offset(Vector2 point) => this.Offset(point.X, point.Y); /// /// Adjusts the location of this rectangle by the specified amount. /// /// The amount to offset the x-coordinate. /// The amount to offset the y-coordinate. /// New representing the inflated rectangle. [MethodImpl(MethodImplOptions.AggressiveInlining)] public FontRectangle Offset(float dx, float dy) => new(this.X + dx, this.Y + dy, this.Width, this.Height); /// public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Width, this.Height); /// public override string ToString() => $"FontRectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; /// public override bool Equals(object? obj) => obj is FontRectangle other && this.Equals(other); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(FontRectangle other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && this.Height.Equals(other.Height); } }