2020-01-05 20:08:01 +08:00
namespace UglyToad.PdfPig.Core
2018-11-14 04:45:54 +08:00
{
2018-11-15 06:22:51 +08:00
using System ;
2018-11-14 04:45:54 +08:00
using System.Collections.Generic ;
2018-11-15 06:22:51 +08:00
using System.Linq ;
2018-11-14 04:45:54 +08:00
using System.Text ;
2019-01-04 06:20:53 +08:00
/// <summary>
2020-04-02 23:19:38 +08:00
///
2019-01-04 06:20:53 +08:00
/// </summary>
2020-04-02 23:12:39 +08:00
public class PdfSubpath
2018-11-14 04:45:54 +08:00
{
2019-07-24 12:00:00 +08:00
private readonly List < IPathCommand > commands = new List < IPathCommand > ( ) ;
2019-08-03 22:42:19 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// The sequence of sub-paths which form this <see cref="PdfSubpath"/>.
2019-08-03 22:42:19 +08:00
/// </summary>
2019-07-24 12:00:00 +08:00
public IReadOnlyList < IPathCommand > Commands = > commands ;
2019-12-14 19:41:11 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// True if the <see cref="PdfSubpath"/> was originaly draw as a rectangle.
2019-12-14 19:41:11 +08:00
/// </summary>
2019-12-16 22:36:52 +08:00
public bool IsDrawnAsRectangle { get ; internal set ; }
2019-12-14 19:41:11 +08:00
2018-11-14 04:45:54 +08:00
private PdfPoint ? currentPosition ;
2019-10-04 21:37:41 +08:00
private double shoeLaceSum ;
/// <summary>
2019-10-04 21:50:22 +08:00
/// Return true if points are organised in a clockwise order. Works only with closed paths.
2019-10-04 21:37:41 +08:00
/// </summary>
/// <returns></returns>
public bool IsClockwise
{
get
{
if ( ! IsClosed ( ) ) return false ;
return shoeLaceSum > 0 ;
}
}
/// <summary>
2019-10-04 21:50:22 +08:00
/// Return true if points are organised in a counterclockwise order. Works only with closed paths.
2019-10-04 21:37:41 +08:00
/// </summary>
/// <returns></returns>
public bool IsCounterClockwise
{
get
{
if ( ! IsClosed ( ) ) return false ;
return shoeLaceSum < 0 ;
}
}
/// <summary>
2020-04-02 23:12:39 +08:00
/// Get the <see cref="PdfSubpath"/>'s centroid point.
2019-10-04 21:37:41 +08:00
/// </summary>
public PdfPoint GetCentroid ( )
{
2019-12-22 02:09:49 +08:00
var filtered = commands . Where ( c = > c is Line | | c is BezierCurve ) . ToList ( ) ;
if ( filtered . Count = = 0 ) return new PdfPoint ( ) ;
var points = filtered . Select ( GetStartPoint ) . ToList ( ) ;
points . AddRange ( filtered . Select ( GetEndPoint ) ) ;
2019-10-04 21:37:41 +08:00
return new PdfPoint ( points . Average ( p = > p . X ) , points . Average ( p = > p . Y ) ) ;
}
internal static PdfPoint GetStartPoint ( IPathCommand command )
{
if ( command is Line line )
{
return line . From ;
}
2020-01-05 00:38:18 +08:00
if ( command is BezierCurve curve )
2019-10-04 21:37:41 +08:00
{
return curve . StartPoint ;
}
2020-01-05 00:38:18 +08:00
if ( command is Move move )
2019-10-04 21:37:41 +08:00
{
return move . Location ;
}
2020-01-05 00:38:18 +08:00
throw new ArgumentException ( ) ;
2019-10-04 21:37:41 +08:00
}
internal static PdfPoint GetEndPoint ( IPathCommand command )
{
if ( command is Line line )
{
return line . To ;
}
2020-01-05 00:38:18 +08:00
if ( command is BezierCurve curve )
2019-10-04 21:37:41 +08:00
{
return curve . EndPoint ;
}
2020-01-05 00:38:18 +08:00
if ( command is Move move )
2019-10-04 21:37:41 +08:00
{
return move . Location ;
}
2020-01-05 00:38:18 +08:00
throw new ArgumentException ( ) ;
2019-10-04 21:37:41 +08:00
}
/// <summary>
2020-04-02 23:12:39 +08:00
/// Simplify this <see cref="PdfSubpath"/> by converting everything to <see cref="PdfLine"/>s.
2019-10-04 21:37:41 +08:00
/// </summary>
2019-12-14 19:41:11 +08:00
/// <param name="n">Number of lines required (minimum is 1).</param>
2020-04-02 23:12:39 +08:00
internal PdfSubpath Simplify ( int n = 4 )
2019-10-04 21:37:41 +08:00
{
2020-04-02 23:12:39 +08:00
PdfSubpath simplifiedPath = new PdfSubpath ( ) ;
2019-10-04 21:37:41 +08:00
var startPoint = GetStartPoint ( Commands . First ( ) ) ;
simplifiedPath . MoveTo ( startPoint . X , startPoint . Y ) ;
foreach ( var command in Commands )
{
if ( command is Line line )
{
simplifiedPath . LineTo ( line . To . X , line . To . Y ) ;
}
else if ( command is BezierCurve curve )
{
2019-12-14 19:41:11 +08:00
foreach ( var lineB in curve . ToLines ( n ) )
2019-10-04 21:37:41 +08:00
{
simplifiedPath . LineTo ( lineB . To . X , lineB . To . Y ) ;
}
}
}
// Check if Closed, if yes: make sure it is actually closed (last TO = first FROM)
if ( IsClosed ( ) )
{
var first = GetStartPoint ( simplifiedPath . Commands . First ( ) ) ;
if ( ! first . Equals ( GetEndPoint ( simplifiedPath . Commands . Last ( ) ) ) )
{
simplifiedPath . LineTo ( first . X , first . Y ) ;
}
}
return simplifiedPath ;
}
2020-01-05 20:08:01 +08:00
/// <summary>
/// Add a <see cref="Move"/> command to the path.
/// </summary>
public void MoveTo ( double x , double y )
2018-11-14 04:45:54 +08:00
{
2019-08-09 04:19:18 +08:00
currentPosition = new PdfPoint ( x , y ) ;
2019-07-24 12:00:00 +08:00
commands . Add ( new Move ( currentPosition . Value ) ) ;
2018-11-14 04:45:54 +08:00
}
2020-01-05 20:08:01 +08:00
/// <summary>
/// Add a <see cref="Line"/> command to the path.
/// </summary>
public void LineTo ( double x , double y )
2018-11-14 04:45:54 +08:00
{
if ( currentPosition . HasValue )
{
2019-12-22 02:09:49 +08:00
shoeLaceSum + = ( ( x - currentPosition . Value . X ) * ( y + currentPosition . Value . Y ) ) ;
2019-10-04 21:37:41 +08:00
2019-08-09 04:19:18 +08:00
var to = new PdfPoint ( x , y ) ;
2019-07-24 12:00:00 +08:00
commands . Add ( new Line ( currentPosition . Value , to ) ) ;
2018-11-14 04:45:54 +08:00
currentPosition = to ;
}
else
{
2020-04-03 00:21:51 +08:00
// PDF Reference 1.7 p226
throw new ArgumentNullException ( "LineTo(): currentPosition is null." ) ;
2018-11-14 04:45:54 +08:00
}
}
2020-01-05 20:08:01 +08:00
/// <summary>
/// Adds 4 <see cref="Line"/>s forming a rectangle to the path.
/// </summary>
public void Rectangle ( double x , double y , double width , double height )
{
2020-04-03 00:21:51 +08:00
// is equivalent to
MoveTo ( x , y ) ; // x y m
LineTo ( x + width , y ) ; // (x + width) y l
LineTo ( x + width , y + height ) ; // (x + width) (y + height) l
LineTo ( x , y + height ) ; // x (y + height) l
ClosePath ( ) ; // h
2020-01-05 20:08:01 +08:00
IsDrawnAsRectangle = true ;
}
2019-12-22 02:09:49 +08:00
internal void QuadraticCurveTo ( double x1 , double y1 , double x2 , double y2 ) { }
2018-11-14 04:45:54 +08:00
2020-01-05 20:08:01 +08:00
/// <summary>
/// Add a <see cref="BezierCurve"/> to the path.
/// </summary>
public void BezierCurveTo ( double x1 , double y1 , double x2 , double y2 , double x3 , double y3 )
2018-11-14 04:45:54 +08:00
{
if ( currentPosition . HasValue )
{
2019-12-30 22:46:07 +08:00
shoeLaceSum + = ( x1 - currentPosition . Value . X ) * ( y1 + currentPosition . Value . Y ) ;
shoeLaceSum + = ( x2 - x1 ) * ( y2 + y1 ) ;
shoeLaceSum + = ( x3 - x2 ) * ( y3 + y2 ) ;
2019-10-04 21:37:41 +08:00
2019-08-09 04:19:18 +08:00
var to = new PdfPoint ( x3 , y3 ) ;
2019-10-04 21:37:41 +08:00
commands . Add ( new BezierCurve ( currentPosition . Value , new PdfPoint ( x1 , y1 ) , new PdfPoint ( x2 , y2 ) , to ) ) ;
2018-11-14 04:45:54 +08:00
currentPosition = to ;
}
else
{
2020-04-03 00:21:51 +08:00
// PDF Reference 1.7 p226
throw new ArgumentNullException ( "BezierCurveTo(): currentPosition is null." ) ;
2018-11-14 04:45:54 +08:00
}
}
2020-02-24 19:29:06 +08:00
2020-01-05 20:08:01 +08:00
/// <summary>
/// Close the path.
/// </summary>
public void ClosePath ( )
2018-11-14 04:45:54 +08:00
{
2019-10-06 19:47:12 +08:00
if ( currentPosition . HasValue )
{
var startPoint = GetStartPoint ( commands . First ( ) ) ;
if ( ! startPoint . Equals ( currentPosition . Value ) )
{
2019-12-22 02:09:49 +08:00
shoeLaceSum + = ( startPoint . X - currentPosition . Value . X ) * ( startPoint . Y + currentPosition . Value . Y ) ;
2019-10-06 19:47:12 +08:00
}
}
2019-07-24 12:00:00 +08:00
commands . Add ( new Close ( ) ) ;
2018-11-14 04:45:54 +08:00
}
2020-02-24 19:29:06 +08:00
/// <summary>
/// Determines if the path is currently closed.
/// </summary>
public bool IsClosed ( )
{
// need to check if filled -> true if filled
if ( Commands . Any ( c = > c is Close ) ) return true ;
var filtered = Commands . Where ( c = > c is Line | | c is BezierCurve ) . ToList ( ) ;
if ( filtered . Count < 2 ) return false ;
if ( ! GetStartPoint ( filtered . First ( ) ) . Equals ( GetEndPoint ( filtered . Last ( ) ) ) ) return false ;
return true ;
}
2018-11-14 04:45:54 +08:00
2019-10-08 22:53:42 +08:00
/// <summary>
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
/// </summary>
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
2019-10-04 21:37:41 +08:00
public PdfRectangle ? GetBoundingRectangle ( )
2018-11-14 06:22:24 +08:00
{
2019-07-24 12:00:00 +08:00
if ( commands . Count = = 0 )
2018-11-16 03:52:42 +08:00
{
return null ;
}
2019-12-22 02:09:49 +08:00
var minX = double . MaxValue ;
var maxX = double . MinValue ;
2018-11-15 06:22:51 +08:00
2019-12-22 02:09:49 +08:00
var minY = double . MaxValue ;
var maxY = double . MinValue ;
2018-11-15 06:22:51 +08:00
2019-07-24 12:00:00 +08:00
foreach ( var command in commands )
2018-11-15 06:22:51 +08:00
{
var rect = command . GetBoundingRectangle ( ) ;
if ( rect = = null )
{
continue ;
}
if ( rect . Value . Left < minX )
{
minX = rect . Value . Left ;
}
if ( rect . Value . Right > maxX )
{
maxX = rect . Value . Right ;
}
if ( rect . Value . Bottom < minY )
{
minY = rect . Value . Bottom ;
}
if ( rect . Value . Top > maxY )
{
maxY = rect . Value . Top ;
}
}
2019-12-22 02:09:49 +08:00
// ReSharper disable CompareOfFloatsByEqualityOperator
if ( minX = = double . MaxValue | |
maxX = = double . MinValue | |
minY = = double . MaxValue | |
maxY = = double . MinValue )
2019-10-04 21:37:41 +08:00
{
return null ;
}
2019-12-22 02:09:49 +08:00
// ReSharper restore CompareOfFloatsByEqualityOperator
2019-10-04 21:37:41 +08:00
2018-11-15 06:22:51 +08:00
return new PdfRectangle ( minX , minY , maxX , maxY ) ;
2018-11-14 06:22:24 +08:00
}
2019-08-03 22:42:19 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// A command in a <see cref="PdfSubpath"/>.
2019-08-03 22:42:19 +08:00
/// </summary>
2019-07-16 12:35:29 +08:00
public interface IPathCommand
2018-11-14 04:45:54 +08:00
{
2019-08-03 22:42:19 +08:00
/// <summary>
/// Returns the smallest rectangle which contains the path region given by this command.
/// </summary>
/// <returns></returns>
2018-11-15 06:22:51 +08:00
PdfRectangle ? GetBoundingRectangle ( ) ;
2019-08-03 22:42:19 +08:00
/// <summary>
/// Converts from the path command to an SVG string representing the path operation.
/// </summary>
2020-04-03 00:29:03 +08:00
void WriteSvg ( StringBuilder builder , double height ) ;
2018-11-14 04:45:54 +08:00
}
2019-08-03 22:42:19 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// Close the current <see cref="PdfSubpath"/>.
2019-08-03 22:42:19 +08:00
/// </summary>
public class Close : IPathCommand
2018-11-14 04:45:54 +08:00
{
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2018-11-15 06:22:51 +08:00
public PdfRectangle ? GetBoundingRectangle ( )
{
return null ;
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2020-04-03 00:29:03 +08:00
public void WriteSvg ( StringBuilder builder , double height )
2018-11-14 04:45:54 +08:00
{
builder . Append ( "Z " ) ;
}
2019-12-14 19:41:11 +08:00
2019-12-22 02:09:49 +08:00
/// <inheritdoc />
2019-12-14 19:41:11 +08:00
public override bool Equals ( object obj )
{
return ( obj is Close ) ;
}
2019-12-22 02:09:49 +08:00
/// <inheritdoc />
2019-12-14 19:41:11 +08:00
public override int GetHashCode ( )
{
2019-12-22 02:09:49 +08:00
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
2019-12-14 19:41:11 +08:00
return base . GetHashCode ( ) ;
}
2018-11-14 04:45:54 +08:00
}
2019-08-03 22:42:19 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// Move drawing of the current <see cref="PdfSubpath"/> to the specified location.
2019-08-03 22:42:19 +08:00
/// </summary>
2019-07-16 12:35:29 +08:00
public class Move : IPathCommand
2018-11-14 04:45:54 +08:00
{
2019-08-03 22:42:19 +08:00
/// <summary>
/// The location to move to.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint Location { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// Create a new <see cref="Move"/> path command.
/// </summary>
/// <param name="location"></param>
2018-11-14 04:45:54 +08:00
public Move ( PdfPoint location )
{
Location = location ;
}
2019-08-03 22:42:19 +08:00
/// <summary>
/// Returns <see langword="null"/> since this generates no visible path.
/// </summary>
2018-11-15 06:22:51 +08:00
public PdfRectangle ? GetBoundingRectangle ( )
{
return null ;
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2020-04-03 00:29:03 +08:00
public void WriteSvg ( StringBuilder builder , double height )
2018-11-14 04:45:54 +08:00
{
2020-04-03 00:29:03 +08:00
builder . Append ( $"M {Location.X} {height - Location.Y} " ) ;
2018-11-14 04:45:54 +08:00
}
2019-12-14 19:41:11 +08:00
/// <inheritdoc />
public override bool Equals ( object obj )
{
if ( obj is Move move )
{
2019-12-22 02:09:49 +08:00
return Location . Equals ( move . Location ) ;
2019-12-14 19:41:11 +08:00
}
return false ;
}
/// <inheritdoc />
public override int GetHashCode ( )
{
2019-12-22 02:09:49 +08:00
return ( Location ) . GetHashCode ( ) ;
2019-12-14 19:41:11 +08:00
}
2018-11-14 04:45:54 +08:00
}
2019-08-03 22:42:19 +08:00
/// <summary>
/// Draw a straight line between two points.
/// </summary>
2019-07-16 12:35:29 +08:00
public class Line : IPathCommand
2018-11-14 04:45:54 +08:00
{
2019-08-03 22:42:19 +08:00
/// <summary>
/// The start of the line.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint From { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// The end of the line.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint To { get ; }
2020-02-17 23:37:31 +08:00
/// <summary>
/// Length of the line.
/// </summary>
public double Length
{
get
{
2020-02-19 21:48:55 +08:00
var dx = From . X - To . X ;
var dy = From . Y - To . Y ;
return Math . Sqrt ( dx * dx + dy * dy ) ;
2020-02-17 23:37:31 +08:00
}
}
2019-08-03 22:42:19 +08:00
/// <summary>
/// Create a new <see cref="Line"/>.
/// </summary>
2018-11-14 04:45:54 +08:00
public Line ( PdfPoint from , PdfPoint to )
{
From = from ;
To = to ;
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2018-11-15 06:22:51 +08:00
public PdfRectangle ? GetBoundingRectangle ( )
{
return new PdfRectangle ( From , To ) ;
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2020-04-03 00:29:03 +08:00
public void WriteSvg ( StringBuilder builder , double height )
2018-11-14 04:45:54 +08:00
{
2020-04-03 00:29:03 +08:00
builder . Append ( $"L {To.X} {height - To.Y} " ) ;
2018-11-14 04:45:54 +08:00
}
2019-12-14 19:41:11 +08:00
/// <inheritdoc />
public override bool Equals ( object obj )
{
if ( obj is Line line )
{
2019-12-22 02:09:49 +08:00
return From . Equals ( line . From ) & & To . Equals ( line . To ) ;
2019-12-14 19:41:11 +08:00
}
return false ;
}
/// <inheritdoc />
public override int GetHashCode ( )
{
2019-12-22 02:09:49 +08:00
return ( From , To ) . GetHashCode ( ) ;
2019-12-14 19:41:11 +08:00
}
2018-11-14 04:45:54 +08:00
}
2019-08-03 22:42:19 +08:00
/// <summary>
/// Draw a Bezier curve given by the start, control and end points.
/// </summary>
2019-07-16 12:35:29 +08:00
public class BezierCurve : IPathCommand
2018-11-14 04:45:54 +08:00
{
2019-08-03 22:42:19 +08:00
/// <summary>
/// The start point of the Bezier curve.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint StartPoint { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// The first control point of the curve.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint FirstControlPoint { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// The second control point of the curve.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint SecondControlPoint { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// The end point of the curve.
/// </summary>
2018-11-14 04:45:54 +08:00
public PdfPoint EndPoint { get ; }
2019-08-03 22:42:19 +08:00
/// <summary>
/// Create a Bezier curve at the provided points.
/// </summary>
2018-11-14 04:45:54 +08:00
public BezierCurve ( PdfPoint startPoint , PdfPoint firstControlPoint , PdfPoint secondControlPoint , PdfPoint endPoint )
{
StartPoint = startPoint ;
FirstControlPoint = firstControlPoint ;
SecondControlPoint = secondControlPoint ;
EndPoint = endPoint ;
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2018-11-15 06:22:51 +08:00
public PdfRectangle ? GetBoundingRectangle ( )
{
2018-11-25 19:37:00 +08:00
// Optimised
double minX ;
double maxX ;
if ( StartPoint . X < = EndPoint . X )
{
2019-12-22 02:09:49 +08:00
minX = StartPoint . X ;
maxX = EndPoint . X ;
2018-11-25 19:37:00 +08:00
}
else
{
2019-12-22 02:09:49 +08:00
minX = EndPoint . X ;
maxX = StartPoint . X ;
2018-11-25 19:37:00 +08:00
}
2018-11-15 06:22:51 +08:00
2018-11-25 19:37:00 +08:00
double minY ;
double maxY ;
if ( StartPoint . Y < = EndPoint . Y )
{
2019-12-22 02:09:49 +08:00
minY = StartPoint . Y ;
maxY = EndPoint . Y ;
2018-12-12 08:09:15 +08:00
}
else
{
2019-12-22 02:09:49 +08:00
minY = EndPoint . Y ;
maxY = StartPoint . Y ;
2018-11-25 19:37:00 +08:00
}
2018-11-15 06:22:51 +08:00
2018-11-25 19:37:00 +08:00
if ( TrySolveQuadratic ( true , minX , maxX , out var xSolutions ) )
2018-11-15 06:22:51 +08:00
{
minX = xSolutions . min ;
maxX = xSolutions . max ;
}
2018-11-25 19:37:00 +08:00
if ( TrySolveQuadratic ( false , minY , maxY , out var ySolutions ) )
2018-11-15 06:22:51 +08:00
{
minY = ySolutions . min ;
maxY = ySolutions . max ;
}
2019-12-22 02:09:49 +08:00
return new PdfRectangle ( minX , minY , maxX , maxY ) ;
2018-11-15 06:22:51 +08:00
}
2019-08-03 22:42:19 +08:00
/// <inheritdoc />
2020-04-03 00:29:03 +08:00
public void WriteSvg ( StringBuilder builder , double height )
2019-08-03 22:42:19 +08:00
{
2020-04-03 00:29:03 +08:00
builder . Append ( $"C {FirstControlPoint.X} { height - FirstControlPoint.Y}, { SecondControlPoint.X} {height - SecondControlPoint.Y}, {EndPoint.X} {height - EndPoint.Y} " ) ;
2019-08-03 22:42:19 +08:00
}
2018-11-25 19:37:00 +08:00
private bool TrySolveQuadratic ( bool isX , double currentMin , double currentMax , out ( double min , double max ) solutions )
2018-11-15 06:22:51 +08:00
{
2018-11-25 19:37:00 +08:00
solutions = default ( ( double , double ) ) ;
// This method has been optimised for performance by eliminating calls to Math.
2018-11-15 06:22:51 +08:00
// Given k points the general form is:
// P = (1-t)^(k - i - 1)*t^(i)*P_i
//
// For 4 points this gives:
// P = (1− t)^3*P_1 + 3(1− t)^2*t*P_2 + 3(1− t)*t^2*P_3 + t^3*P_4
// The differential is:
// P' = 3(1-t)^2(P_2 - P_1) + 6(1-t)^t(P_3 - P_2) + 3t^2(P_4 - P_3)
// P' = 3da(1-t)^2 + 6db(1-t)t + 3dct^2
// P' = 3da - 3dat - 3dat + 3dat^2 + 6dbt - 6dbt^2 + 3dct^2
// P' = (3da - 6db + 3dc)t^2 + (6db - 3da - 3da)t + 3da
2019-12-22 02:09:49 +08:00
var p1 = isX ? StartPoint . X : StartPoint . Y ;
var p2 = isX ? FirstControlPoint . X : FirstControlPoint . Y ;
var p3 = isX ? SecondControlPoint . X : SecondControlPoint . Y ;
var p4 = isX ? EndPoint . X : EndPoint . Y ;
2018-11-15 06:22:51 +08:00
var threeda = 3 * ( p2 - p1 ) ;
var sixdb = 6 * ( p3 - p2 ) ;
var threedc = 3 * ( p4 - p3 ) ;
var a = threeda - sixdb + threedc ;
var b = sixdb - threeda - threeda ;
var c = threeda ;
// P' = at^2 + bt + c
// t = (-b (+/-) sqrt(b ^ 2 - 4ac))/2a
2019-12-22 02:09:49 +08:00
var sqrtable = b * b - 4 * a * c ;
2018-11-15 06:22:51 +08:00
if ( sqrtable < 0 )
{
return false ;
}
2018-11-25 19:37:00 +08:00
var sqrt = Math . Sqrt ( sqrtable ) ;
var divisor = 2 * a ;
var t1 = ( - b + sqrt ) / divisor ;
var t2 = ( - b - sqrt ) / divisor ;
2018-11-15 06:22:51 +08:00
if ( t1 > = 0 & & t1 < = 1 )
{
2018-11-25 19:37:00 +08:00
var sol1 = ValueWithT ( p1 , p2 , p3 , p4 , t1 ) ;
2018-11-15 06:22:51 +08:00
if ( sol1 < currentMin )
{
currentMin = sol1 ;
}
if ( sol1 > currentMax )
{
currentMax = sol1 ;
}
}
if ( t2 > = 0 & & t2 < = 1 )
{
2018-11-25 19:37:00 +08:00
var sol2 = ValueWithT ( p1 , p2 , p3 , p4 , t2 ) ;
2018-11-15 06:22:51 +08:00
if ( sol2 < currentMin )
{
currentMin = sol2 ;
}
if ( sol2 > currentMax )
{
currentMax = sol2 ;
}
}
solutions = ( currentMin , currentMax ) ;
return true ;
}
2020-01-05 20:08:01 +08:00
/// <summary>
/// Calculate the value of the Bezier curve at t.
/// </summary>
public static double ValueWithT ( double p1 , double p2 , double p3 , double p4 , double t )
2018-11-15 06:22:51 +08:00
{
// P = (1− t)^3*P_1 + 3(1− t)^2*t*P_2 + 3(1− t)*t^2*P_3 + t^3*P_4
2018-11-25 19:37:00 +08:00
var oneMinusT = 1 - t ;
var p = ( ( oneMinusT * oneMinusT * oneMinusT ) * p1 )
2018-12-12 08:09:15 +08:00
+ ( 3 * ( oneMinusT * oneMinusT ) * t * p2 )
+ ( 3 * oneMinusT * ( t * t ) * p3 )
2018-11-25 19:37:00 +08:00
+ ( ( t * t * t ) * p4 ) ;
2018-11-15 06:22:51 +08:00
return p ;
}
2019-10-04 21:37:41 +08:00
/// <summary>
/// Converts the bezier curve into approximated lines.
/// </summary>
/// <param name="n">Number of lines required (minimum is 1).</param>
/// <returns></returns>
public IReadOnlyList < Line > ToLines ( int n )
{
if ( n < 1 )
{
throw new ArgumentException ( "BezierCurve.ToLines(): n must be greater than 0." ) ;
}
List < Line > lines = new List < Line > ( ) ;
var previousPoint = StartPoint ;
for ( int p = 1 ; p < = n ; p + + )
{
2019-12-22 02:09:49 +08:00
double t = p / ( double ) n ;
var currentPoint = new PdfPoint ( ValueWithT ( StartPoint . X , FirstControlPoint . X , SecondControlPoint . X , EndPoint . X , t ) ,
ValueWithT ( StartPoint . Y , FirstControlPoint . Y , SecondControlPoint . Y , EndPoint . Y , t ) ) ;
2019-10-04 21:37:41 +08:00
lines . Add ( new Line ( previousPoint , currentPoint ) ) ;
previousPoint = currentPoint ;
}
return lines ;
}
2019-12-14 19:41:11 +08:00
/// <inheritdoc />
public override bool Equals ( object obj )
{
if ( obj is BezierCurve curve )
{
2019-12-22 02:09:49 +08:00
return StartPoint . Equals ( curve . StartPoint ) & &
FirstControlPoint . Equals ( curve . FirstControlPoint ) & &
SecondControlPoint . Equals ( curve . SecondControlPoint ) & &
EndPoint . Equals ( curve . EndPoint ) ;
2019-12-14 19:41:11 +08:00
}
return false ;
}
/// <inheritdoc />
public override int GetHashCode ( )
{
2019-12-22 02:09:49 +08:00
return ( StartPoint , FirstControlPoint , SecondControlPoint , EndPoint ) . GetHashCode ( ) ;
2019-12-14 19:41:11 +08:00
}
2018-11-14 04:45:54 +08:00
}
2018-12-12 08:09:15 +08:00
2019-12-14 19:41:11 +08:00
/// <summary>
2020-04-02 23:12:39 +08:00
/// Compares two <see cref="PdfSubpath"/>s for equality. Paths will only be considered equal if the commands which construct the paths are in the same order.
2019-12-14 19:41:11 +08:00
/// </summary>
public override bool Equals ( object obj )
{
2020-04-02 23:12:39 +08:00
if ( obj is PdfSubpath path )
2019-12-14 19:41:11 +08:00
{
2019-12-22 02:09:49 +08:00
if ( Commands . Count ! = path . Commands . Count ) return false ;
2019-12-14 19:41:11 +08:00
2019-12-22 02:09:49 +08:00
for ( int i = 0 ; i < Commands . Count ; i + + )
2019-12-14 19:41:11 +08:00
{
2019-12-22 02:09:49 +08:00
if ( ! Commands [ i ] . Equals ( path . Commands [ i ] ) ) return false ;
2019-12-14 19:41:11 +08:00
}
return true ;
}
return false ;
}
/// <summary>
2019-12-16 22:36:52 +08:00
/// Get the hash code. Paths will only have the same hash code if the commands which construct the paths are in the same order.
2019-12-14 19:41:11 +08:00
/// </summary>
public override int GetHashCode ( )
{
2019-12-22 02:09:49 +08:00
var hash = Commands . Count + 1 ;
for ( int i = 0 ; i < Commands . Count ; i + + )
2019-12-14 19:41:11 +08:00
{
2019-12-22 02:09:49 +08:00
hash = hash * ( i + 1 ) * 17 + Commands [ i ] . GetHashCode ( ) ;
2019-12-14 19:41:11 +08:00
}
return hash ;
2018-12-12 08:09:15 +08:00
}
2018-11-14 04:45:54 +08:00
}
}