namespace UglyToad.PdfPig.Core { using System; using System.Globalization; /// /// A rectangle in a PDF file. /// /// /// PDF coordinates are defined with the origin at the lower left (0, 0). /// The Y-axis extends vertically upwards and the X-axis horizontally to the right. /// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch). /// public struct PdfRectangle { /// /// Top left point of the rectangle. /// public PdfPoint TopLeft { get; } /// /// Top right point of the rectangle. /// public PdfPoint TopRight { get; } /// /// Bottom right point of the rectangle. /// public PdfPoint BottomRight { get; } /// /// Bottom left point of the rectangle. /// public PdfPoint BottomLeft { get; } /// /// Centroid point of the rectangle. /// public readonly PdfPoint Centroid { get { var cx = (BottomRight.X + TopRight.X + TopLeft.X + BottomLeft.X) / 4.0; var cy = (BottomRight.Y + TopRight.Y + TopLeft.Y + BottomLeft.Y) / 4.0; return new PdfPoint(cx, cy); } } private double width; /// /// Width of the rectangle. /// A positive number. /// public double Width { get { if (double.IsNaN(width)) { GetWidthHeight(); } return width; } } private double height; /// /// Height of the rectangle. /// A positive number. /// public double Height { get { if (double.IsNaN(height)) { GetWidthHeight(); } return height; } } /// /// Rotation angle of the rectangle. Counterclockwise, in degrees. /// -180 ≤ θ ≤ 180 /// public readonly double Rotation => GetT() * 180 / Math.PI; /// /// Area of the rectangle. /// public double Area => Math.Abs(Width * Height); /// /// Left. This value is only valid if the rectangle is not rotated, check . /// public readonly double Left => TopLeft.X < TopRight.X ? TopLeft.X : TopRight.X; /// /// Top. This value is only valid if the rectangle is not rotated, check . /// public readonly double Top => TopLeft.Y > BottomLeft.Y ? TopLeft.Y : BottomLeft.Y; /// /// Right. This value is only valid if the rectangle is not rotated, check . /// public readonly double Right => BottomRight.X > BottomLeft.X ? BottomRight.X : BottomLeft.X; /// /// Bottom. This value is only valid if the rectangle is not rotated, check . /// public readonly double Bottom => BottomRight.Y < TopRight.Y ? BottomRight.Y : TopRight.Y; /// /// Create a new . /// /// Bottom left point of the rectangle. /// Top right point of the rectangle. public PdfRectangle(PdfPoint bottomLeft, PdfPoint topRight) : this(bottomLeft.X, bottomLeft.Y, topRight.X, topRight.Y) { } /// /// Create a new . /// /// Bottom left point's x coordinate of the rectangle. /// Bottom left point's y coordinate of the rectangle. /// Top right point's x coordinate of the rectangle. /// Top right point's y coordinate of the rectangle. public PdfRectangle(short x1, short y1, short x2, short y2) : this((double)x1, y1, x2, y2) { } /// /// Create a new . /// /// Bottom left point's x coordinate of the rectangle. /// Bottom left point's y coordinate of the rectangle. /// Top right point's x coordinate of the rectangle. /// Top right point's y coordinate of the rectangle. public PdfRectangle(double x1, double y1, double x2, double y2) : this(new PdfPoint(x1, y2), new PdfPoint(x2, y2), new PdfPoint(x1, y1), new PdfPoint(x2, y1)) { } /// /// Create a new . /// public PdfRectangle(PdfPoint topLeft, PdfPoint topRight, PdfPoint bottomLeft, PdfPoint bottomRight) { TopLeft = topLeft; TopRight = topRight; BottomLeft = bottomLeft; BottomRight = bottomRight; width = double.NaN; height = double.NaN; } /// /// Creates a new which is the current rectangle moved in the x and y directions relative to its current position by a value. /// /// The distance to move the rectangle in the x direction relative to its current location. /// The distance to move the rectangle in the y direction relative to its current location. /// A new rectangle shifted on the y axis by the given delta value. public readonly PdfRectangle Translate(double dx, double dy) { return new PdfRectangle(TopLeft.Translate(dx, dy), TopRight.Translate(dx, dy), BottomLeft.Translate(dx, dy), BottomRight.Translate(dx, dy)); } /// /// -π ≤ θ ≤ π /// private readonly double GetT() { if (!BottomRight.Equals(BottomLeft)) { return Math.Atan2(BottomRight.Y - BottomLeft.Y, BottomRight.X - BottomLeft.X); } // handle the case where both bottom points are identical return Math.Atan2(TopLeft.Y - BottomLeft.Y, TopLeft.X - BottomLeft.X) - Math.PI / 2; } private void GetWidthHeight() { var t = GetT(); var cos = Math.Cos(t); var sin = Math.Sin(t); var inverseRotation = new TransformationMatrix( cos, -sin, 0, sin, cos, 0, 0, 0, 1); // Using Abs as a proxy for Euclidean distance in 1D // as it might happen that points have negative coordinates. var bl = inverseRotation.Transform(BottomLeft); width = Math.Abs(inverseRotation.Transform(BottomRight).X - bl.X); height = Math.Abs(inverseRotation.Transform(TopLeft).Y - bl.Y); } /// public override string ToString() { return $"[{TopLeft}, {Width.ToString(CultureInfo.InvariantCulture)}, {Height.ToString(CultureInfo.InvariantCulture)}]"; } } }