2020-01-23 04:42:27 +08:00
namespace UglyToad.PdfPig.Core
2017-11-23 02:41:34 +08:00
{
2020-01-23 04:42:27 +08:00
using System ;
2020-07-26 20:53:34 +08:00
using System.Globalization ;
2020-01-23 04:42:27 +08:00
2018-03-31 06:16:54 +08:00
/// <summary>
2023-10-19 06:44:11 +08:00
/// A rectangle in a PDF file.
2018-03-31 06:16:54 +08:00
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
2018-04-15 05:20:36 +08:00
public struct PdfRectangle
2017-11-23 02:41:34 +08:00
{
2018-03-31 06:16:54 +08:00
/// <summary>
/// Top left point of the rectangle.
/// </summary>
2017-11-23 02:41:34 +08:00
public PdfPoint TopLeft { get ; }
2018-03-31 06:16:54 +08:00
/// <summary>
/// Top right point of the rectangle.
/// </summary>
2017-11-23 02:41:34 +08:00
public PdfPoint TopRight { get ; }
2018-03-31 06:16:54 +08:00
/// <summary>
/// Bottom right point of the rectangle.
/// </summary>
public PdfPoint BottomRight { get ; }
/// <summary>
/// Bottom left point of the rectangle.
/// </summary>
2017-11-23 02:41:34 +08:00
public PdfPoint BottomLeft { get ; }
2019-08-10 00:22:16 +08:00
/// <summary>
/// Centroid point of the rectangle.
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly PdfPoint Centroid
2020-01-28 23:33:08 +08:00
{
get
{
2020-02-17 09:00:27 +08:00
var cx = ( BottomRight . X + TopRight . X + TopLeft . X + BottomLeft . X ) / 4.0 ;
var cy = ( BottomRight . Y + TopRight . Y + TopLeft . Y + BottomLeft . Y ) / 4.0 ;
2020-01-28 23:33:08 +08:00
return new PdfPoint ( cx , cy ) ;
}
}
2019-08-10 00:22:16 +08:00
2020-01-28 23:33:08 +08:00
private double width ;
2018-03-31 06:16:54 +08:00
/// <summary>
/// Width of the rectangle.
2021-01-15 19:20:45 +08:00
/// <para>A positive number.</para>
2018-03-31 06:16:54 +08:00
/// </summary>
2020-01-28 23:33:08 +08:00
public double Width
{
get
{
2020-01-29 20:54:15 +08:00
if ( double . IsNaN ( width ) )
2020-01-28 23:33:08 +08:00
{
GetWidthHeight ( ) ;
}
2020-02-01 00:17:39 +08:00
2020-01-28 23:33:08 +08:00
return width ;
}
}
2017-11-23 02:41:34 +08:00
2020-01-28 23:33:08 +08:00
private double height ;
2018-03-31 06:16:54 +08:00
/// <summary>
/// Height of the rectangle.
2021-01-15 19:20:45 +08:00
/// <para>A positive number.</para>
2018-03-31 06:16:54 +08:00
/// </summary>
2020-01-28 23:33:08 +08:00
public double Height
{
get
{
2020-01-29 20:54:15 +08:00
if ( double . IsNaN ( height ) )
2020-01-28 23:33:08 +08:00
{
GetWidthHeight ( ) ;
}
2020-02-01 00:17:39 +08:00
2020-01-28 23:33:08 +08:00
return height ;
}
}
2020-01-17 18:51:36 +08:00
/// <summary>
/// Rotation angle of the rectangle. Counterclockwise, in degrees.
2020-04-29 05:01:50 +08:00
/// <para>-180 ≤ θ ≤ 180</para>
2020-01-17 18:51:36 +08:00
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly double Rotation = > GetT ( ) * 180 / Math . PI ;
2017-11-23 02:41:34 +08:00
2018-03-31 06:16:54 +08:00
/// <summary>
/// Area of the rectangle.
/// </summary>
2020-01-28 23:33:08 +08:00
public double Area = > Math . Abs ( Width * Height ) ;
2017-11-23 02:41:34 +08:00
2018-04-15 05:20:36 +08:00
/// <summary>
2020-01-28 21:37:42 +08:00
/// Left. This value is only valid if the rectangle is not rotated, check <see cref="Rotation"/>.
2018-04-15 05:20:36 +08:00
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly double Left = > TopLeft . X < TopRight . X ? TopLeft . X : TopRight . X ;
2018-03-31 06:16:54 +08:00
2018-04-15 05:20:36 +08:00
/// <summary>
2020-01-28 21:37:42 +08:00
/// Top. This value is only valid if the rectangle is not rotated, check <see cref="Rotation"/>.
2018-04-15 05:20:36 +08:00
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly double Top = > TopLeft . Y > BottomLeft . Y ? TopLeft . Y : BottomLeft . Y ;
2018-03-31 06:16:54 +08:00
2018-04-15 05:20:36 +08:00
/// <summary>
2020-01-28 21:37:42 +08:00
/// Right. This value is only valid if the rectangle is not rotated, check <see cref="Rotation"/>.
2018-04-15 05:20:36 +08:00
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly double Right = > BottomRight . X > BottomLeft . X ? BottomRight . X : BottomLeft . X ;
2018-03-31 06:16:54 +08:00
2018-04-15 05:20:36 +08:00
/// <summary>
2020-01-28 21:37:42 +08:00
/// Bottom. This value is only valid if the rectangle is not rotated, check <see cref="Rotation"/>.
2018-04-15 05:20:36 +08:00
/// </summary>
2023-10-19 06:44:11 +08:00
public readonly double Bottom = > BottomRight . Y < TopRight . Y ? BottomRight . Y : TopRight . Y ;
2018-03-31 06:16:54 +08:00
2020-01-05 00:38:18 +08:00
/// <summary>
/// Create a new <see cref="PdfRectangle"/>.
/// </summary>
2020-01-16 22:43:06 +08:00
/// <param name="bottomLeft">Bottom left point of the rectangle.</param>
/// <param name="topRight">Top right point of the rectangle.</param>
public PdfRectangle ( PdfPoint bottomLeft , PdfPoint topRight ) :
this ( bottomLeft . X , bottomLeft . Y , topRight . X , topRight . Y )
{ }
2020-01-05 00:38:18 +08:00
/// <summary>
/// Create a new <see cref="PdfRectangle"/>.
/// </summary>
2020-01-16 22:43:06 +08:00
/// <param name="x1">Bottom left point's x coordinate of the rectangle.</param>
/// <param name="y1">Bottom left point's y coordinate of the rectangle.</param>
/// <param name="x2">Top right point's x coordinate of the rectangle.</param>
/// <param name="y2">Top right point's y coordinate of the rectangle.</param>
2020-01-17 18:51:36 +08:00
public PdfRectangle ( short x1 , short y1 , short x2 , short y2 ) :
2020-01-28 23:33:08 +08:00
this ( ( double ) x1 , y1 , x2 , y2 )
{ }
2019-05-13 02:34:00 +08:00
/// <summary>
2019-05-15 03:56:34 +08:00
/// Create a new <see cref="PdfRectangle"/>.
2019-05-13 02:34:00 +08:00
/// </summary>
2020-01-16 22:43:06 +08:00
/// <param name="x1">Bottom left point's x coordinate of the rectangle.</param>
/// <param name="y1">Bottom left point's y coordinate of the rectangle.</param>
/// <param name="x2">Top right point's x coordinate of the rectangle.</param>
/// <param name="y2">Top right point's y coordinate of the rectangle.</param>
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 ) )
{ }
2017-11-23 02:41:34 +08:00
2020-01-05 00:38:18 +08:00
/// <summary>
/// Create a new <see cref="PdfRectangle"/>.
/// </summary>
public PdfRectangle ( PdfPoint topLeft , PdfPoint topRight , PdfPoint bottomLeft , PdfPoint bottomRight )
2018-11-25 19:37:00 +08:00
{
TopLeft = topLeft ;
TopRight = topRight ;
BottomLeft = bottomLeft ;
BottomRight = bottomRight ;
2020-01-17 18:51:36 +08:00
2020-01-29 20:54:15 +08:00
width = double . NaN ;
height = double . NaN ;
2020-01-28 23:33:08 +08:00
}
2020-01-17 20:57:52 +08:00
2020-01-28 23:33:08 +08:00
/// <summary>
/// Creates a new <see cref="PdfRectangle"/> which is the current rectangle moved in the x and y directions relative to its current position by a value.
/// </summary>
/// <param name="dx">The distance to move the rectangle in the x direction relative to its current location.</param>
/// <param name="dy">The distance to move the rectangle in the y direction relative to its current location.</param>
/// <returns>A new rectangle shifted on the y axis by the given delta value.</returns>
2023-10-19 06:44:11 +08:00
public readonly PdfRectangle Translate ( double dx , double dy )
2020-01-28 23:33:08 +08:00
{
return new PdfRectangle ( TopLeft . Translate ( dx , dy ) , TopRight . Translate ( dx , dy ) ,
BottomLeft . Translate ( dx , dy ) , BottomRight . Translate ( dx , dy ) ) ;
}
2020-04-29 05:01:50 +08:00
/// <summary>
/// -π ≤ θ ≤ π
/// </summary>
2023-10-19 06:44:11 +08:00
private readonly double GetT ( )
2020-02-18 22:58:55 +08:00
{
if ( ! BottomRight . Equals ( BottomLeft ) )
{
return Math . Atan2 ( BottomRight . Y - BottomLeft . Y , BottomRight . X - BottomLeft . X ) ;
}
2023-10-19 06:44:11 +08:00
// handle the case where both bottom points are identical
return Math . Atan2 ( TopLeft . Y - BottomLeft . Y , TopLeft . X - BottomLeft . X ) - Math . PI / 2 ;
2020-02-18 22:58:55 +08:00
}
2020-01-28 23:33:08 +08:00
private void GetWidthHeight ( )
2019-10-17 18:21:49 +08:00
{
2020-02-18 22:58:55 +08:00
var t = GetT ( ) ;
var cos = Math . Cos ( t ) ;
var sin = Math . Sin ( t ) ;
2021-01-15 19:20:45 +08:00
var inverseRotation = new TransformationMatrix (
2020-02-18 22:58:55 +08:00
cos , - sin , 0 ,
sin , cos , 0 ,
0 , 0 , 1 ) ;
2021-01-15 19:20:45 +08:00
// 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 ) ;
2019-10-17 18:21:49 +08:00
}
2019-12-18 19:41:02 +08:00
/// <inheritdoc />
2017-11-23 02:41:34 +08:00
public override string ToString ( )
{
2020-07-26 20:53:34 +08:00
return $"[{TopLeft}, {Width.ToString(CultureInfo.InvariantCulture)}, {Height.ToString(CultureInfo.InvariantCulture)}]" ;
2017-11-23 02:41:34 +08:00
}
}
}