#region PDFsharp - A .NET library for processing PDF
//
// Authors:
//   Stefan Lange
//
// Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics;
using System.Collections.Generic;
#if GDI
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
#endif
#if WPF
using System.Windows.Media;
using SysPoint = System.Windows.Point;
using SysSize = System.Windows.Size;
#endif
#if NETFX_CORE ||  UWP
using Windows.UI.Xaml.Media;
using SysPoint = Windows.Foundation.Point;
using SysSize = Windows.Foundation.Size;
#endif
using PdfSharp.Internal;

// ReSharper disable RedundantNameQualifier
// ReSharper disable CompareOfFloatsByEqualityOperator

namespace PdfSharp.Drawing
{
    /// <summary>
    /// Helper class for Geometry paths.
    /// </summary>
    static class GeometryHelper
    {
#if WPF || NETFX_CORE
        /// <summary>
        /// Appends a Bézier segment from a curve.
        /// </summary>
        public static BezierSegment CreateCurveSegment(XPoint pt0, XPoint pt1, XPoint pt2, XPoint pt3, double tension3)
        {
#if !SILVERLIGHT && !NETFX_CORE
            return new BezierSegment(
                new SysPoint(pt1.X + tension3 * (pt2.X - pt0.X), pt1.Y + tension3 * (pt2.Y - pt0.Y)),
                new SysPoint(pt2.X - tension3 * (pt3.X - pt1.X), pt2.Y - tension3 * (pt3.Y - pt1.Y)),
                new SysPoint(pt2.X, pt2.Y), true);
#else
            BezierSegment bezierSegment = new BezierSegment();
            bezierSegment.Point1 = new SysPoint(pt1.X + tension3 * (pt2.X - pt0.X), pt1.Y + tension3 * (pt2.Y - pt0.Y));
            bezierSegment.Point2 = new SysPoint(pt2.X - tension3 * (pt3.X - pt1.X), pt2.Y - tension3 * (pt3.Y - pt1.Y));
            bezierSegment.Point3 = new SysPoint(pt2.X, pt2.Y);
            return bezierSegment;
#endif
        }
#endif

#if WPF || NETFX_CORE
        /// <summary>
        /// Creates a path geometry from a polygon.
        /// </summary>
        public static PathGeometry CreatePolygonGeometry(SysPoint[] points, XFillMode fillMode, bool closed)
        {
            PolyLineSegment seg = new PolyLineSegment();
            int count = points.Length;
            // For correct drawing the start point of the segment must not be the same as the first point.
            for (int idx = 1; idx < count; idx++)
                seg.Points.Add(new SysPoint(points[idx].X, points[idx].Y));
#if !SILVERLIGHT &&  !NETFX_CORE
            seg.IsStroked = true;
#endif
            PathFigure fig = new PathFigure();
            fig.StartPoint = new SysPoint(points[0].X, points[0].Y);
            fig.Segments.Add(seg);
            fig.IsClosed = closed;
            PathGeometry geo = new PathGeometry();
            geo.FillRule = fillMode == XFillMode.Winding ? FillRule.Nonzero : FillRule.EvenOdd;
            geo.Figures.Add(fig);
            return geo;
        }
#endif

#if WPF || NETFX_CORE
        /// <summary>
        /// Creates a path geometry from a polygon.
        /// </summary>
        public static PolyLineSegment CreatePolyLineSegment(SysPoint[] points, XFillMode fillMode, bool closed)
        {
            PolyLineSegment seg = new PolyLineSegment();
            int count = points.Length;
            // For correct drawing the start point of the segment must not be the same as the first point.
            for (int idx = 1; idx < count; idx++)
                seg.Points.Add(new SysPoint(points[idx].X, points[idx].Y));
#if !SILVERLIGHT && !NETFX_CORE
            seg.IsStroked = true;
#endif
            return seg;
        }
#endif

#if WPF || NETFX_CORE
        /// <summary>
        /// Creates the arc segment from parameters of the GDI+ DrawArc function.
        /// </summary>
        public static ArcSegment CreateArcSegment(double x, double y, double width, double height, double startAngle,
          double sweepAngle, out SysPoint startPoint)
        {
            // Normalize the angles.
            double α = startAngle;
            if (α < 0)
                α = α + (1 + Math.Floor((Math.Abs(α) / 360))) * 360;
            else if (α > 360)
                α = α - Math.Floor(α / 360) * 360;
            Debug.Assert(α >= 0 && α <= 360);

            if (Math.Abs(sweepAngle) >= 360)
                sweepAngle = Math.Sign(sweepAngle) * 360;
            double β = startAngle + sweepAngle;
            if (β < 0)
                β = β + (1 + Math.Floor((Math.Abs(β) / 360))) * 360;
            else if (β > 360)
                β = β - Math.Floor(β / 360) * 360;

            if (α == 0 && β < 0)
                α = 360;
            else if (α == 360 && β > 0)
                α = 0;

            // Scanling factor.
            double δx = width / 2;
            double δy = height / 2;

            // Center of ellipse.
            double x0 = x + δx;
            double y0 = y + δy;

            double cosα, cosβ, sinα, sinβ;
            if (width == height)
            {
                // Circular arc needs no correction.
                α = α * Calc.Deg2Rad;
                β = β * Calc.Deg2Rad;
            }
            else
            {
                // Elliptic arc needs the angles to be adjusted such that the scaling transformation is compensated.
                α = α * Calc.Deg2Rad;
                sinα = Math.Sin(α);
                if (Math.Abs(sinα) > 1E-10)
                {
                    if (α < Math.PI)
                        α = Math.PI / 2 - Math.Atan(δy * Math.Cos(α) / (δx * sinα));
                    else
                        α = 3 * Math.PI / 2 - Math.Atan(δy * Math.Cos(α) / (δx * sinα));
                }
                //α = Calc.πHalf - Math.Atan(δy * Math.Cos(α) / (δx * sinα));
                β = β * Calc.Deg2Rad;
                sinβ = Math.Sin(β);
                if (Math.Abs(sinβ) > 1E-10)
                {
                    if (β < Math.PI)
                        β = Math.PI / 2 - Math.Atan(δy * Math.Cos(β) / (δx * sinβ));
                    else
                        β = 3 * Math.PI / 2 - Math.Atan(δy * Math.Cos(β) / (δx * sinβ));
                }
                //β = Calc.πHalf - Math.Atan(δy * Math.Cos(β) / (δx * sinβ));
            }

            sinα = Math.Sin(α);
            cosα = Math.Cos(α);
            sinβ = Math.Sin(β);
            cosβ = Math.Cos(β);

            startPoint = new SysPoint(x0 + δx * cosα, y0 + δy * sinα);
            SysPoint destPoint = new SysPoint(x0 + δx * cosβ, y0 + δy * sinβ);
            SysSize size = new SysSize(δx, δy);
            bool isLargeArc = Math.Abs(sweepAngle) >= 180;
            SweepDirection sweepDirection = sweepAngle > 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
#if !SILVERLIGHT && !NETFX_CORE
            bool isStroked = true;
            ArcSegment seg = new ArcSegment(destPoint, size, 0, isLargeArc, sweepDirection, isStroked);
#else
            ArcSegment seg = new ArcSegment();
            seg.Point = destPoint;
            seg.Size = size;
            seg.RotationAngle = 0;
            seg.IsLargeArc = isLargeArc;
            seg.SweepDirection = sweepDirection;
            // isStroked does not exist in Silverlight 3
#endif
            return seg;
        }
#endif

        /// <summary>
        /// Creates between 1 and 5 Béziers curves from parameters specified like in GDI+.
        /// </summary>
        public static List<XPoint> BezierCurveFromArc(double x, double y, double width, double height, double startAngle, double sweepAngle,
            PathStart pathStart, ref XMatrix matrix)
        {
            List<XPoint> points = new List<XPoint>();

            // Normalize the angles.
            double α = startAngle;
            if (α < 0)
                α = α + (1 + Math.Floor((Math.Abs(α) / 360))) * 360;
            else if (α > 360)
                α = α - Math.Floor(α / 360) * 360;
            Debug.Assert(α >= 0 && α <= 360);

            double β = sweepAngle;
            if (β < -360)
                β = -360;
            else if (β > 360)
                β = 360;

            if (α == 0 && β < 0)
                α = 360;
            else if (α == 360 && β > 0)
                α = 0;

            // Is it possible that the arc is small starts and ends in same quadrant?
            bool smallAngle = Math.Abs(β) <= 90;

            β = α + β;
            if (β < 0)
                β = β + (1 + Math.Floor((Math.Abs(β) / 360))) * 360;

            bool clockwise = sweepAngle > 0;
            int startQuadrant = Quadrant(α, true, clockwise);
            int endQuadrant = Quadrant(β, false, clockwise);

            if (startQuadrant == endQuadrant && smallAngle)
                AppendPartialArcQuadrant(points, x, y, width, height, α, β, pathStart, matrix);
            else
            {
                int currentQuadrant = startQuadrant;
                bool firstLoop = true;
                do
                {
                    if (currentQuadrant == startQuadrant && firstLoop)
                    {
                        double ξ = currentQuadrant * 90 + (clockwise ? 90 : 0);
                        AppendPartialArcQuadrant(points, x, y, width, height, α, ξ, pathStart, matrix);
                    }
                    else if (currentQuadrant == endQuadrant)
                    {
                        double ξ = currentQuadrant * 90 + (clockwise ? 0 : 90);
                        AppendPartialArcQuadrant(points, x, y, width, height, ξ, β, PathStart.Ignore1st, matrix);
                    }
                    else
                    {
                        double ξ1 = currentQuadrant * 90 + (clockwise ? 0 : 90);
                        double ξ2 = currentQuadrant * 90 + (clockwise ? 90 : 0);
                        AppendPartialArcQuadrant(points, x, y, width, height, ξ1, ξ2, PathStart.Ignore1st, matrix);
                    }

                    // Don't stop immediately if arc is greater than 270 degrees.
                    if (currentQuadrant == endQuadrant && smallAngle)
                        break;
                    smallAngle = true;

                    if (clockwise)
                        currentQuadrant = currentQuadrant == 3 ? 0 : currentQuadrant + 1;
                    else
                        currentQuadrant = currentQuadrant == 0 ? 3 : currentQuadrant - 1;

                    firstLoop = false;
                } while (true);
            }
            return points;
        }

        /// <summary>
        /// Calculates the quadrant (0 through 3) of the specified angle. If the angle lies on an edge
        /// (0, 90, 180, etc.) the result depends on the details how the angle is used.
        /// </summary>
        static int Quadrant(double φ, bool start, bool clockwise)
        {
            Debug.Assert(φ >= 0);
            if (φ > 360)
                φ = φ - Math.Floor(φ / 360) * 360;

            int quadrant = (int)(φ / 90);
            if (quadrant * 90 == φ)
            {
                if ((start && !clockwise) || (!start && clockwise))
                    quadrant = quadrant == 0 ? 3 : quadrant - 1;
            }
            else
                quadrant = clockwise ? ((int)Math.Floor(φ / 90)) % 4 : (int)Math.Floor(φ / 90);
            return quadrant;
        }

        /// <summary>
        /// Appends a Bézier curve for an arc within a full quadrant.
        /// </summary>
        static void AppendPartialArcQuadrant(List<XPoint> points, double x, double y, double width, double height, double α, double β, PathStart pathStart, XMatrix matrix)
        {
            Debug.Assert(α >= 0 && α <= 360);
            Debug.Assert(β >= 0);
            if (β > 360)
                β = β - Math.Floor(β / 360) * 360;
            Debug.Assert(Math.Abs(α - β) <= 90);

            // Scanling factor.
            double δx = width / 2;
            double δy = height / 2;

            // Center of ellipse.
            double x0 = x + δx;
            double y0 = y + δy;

            // We have the following quarters:
            //     |
            //   2 | 3
            // ----+-----
            //   1 | 0
            //     |
            // If the angles lie in quarter 2 or 3, their values are subtracted by 180 and the
            // resulting curve is reflected at the center. This algorithm works as expected (simply tried out).
            // There may be a mathematically more elegant solution...
            bool reflect = false;
            if (α >= 180 && β >= 180)
            {
                α -= 180;
                β -= 180;
                reflect = true;
            }

            double cosα, cosβ, sinα, sinβ;
            if (width == height)
            {
                // Circular arc needs no correction.
                α = α * Calc.Deg2Rad;
                β = β * Calc.Deg2Rad;
            }
            else
            {
                // Elliptic arc needs the angles to be adjusted such that the scaling transformation is compensated.
                α = α * Calc.Deg2Rad;
                sinα = Math.Sin(α);
                if (Math.Abs(sinα) > 1E-10)
                    α = Math.PI / 2 - Math.Atan(δy * Math.Cos(α) / (δx * sinα));
                β = β * Calc.Deg2Rad;
                sinβ = Math.Sin(β);
                if (Math.Abs(sinβ) > 1E-10)
                    β = Math.PI / 2 - Math.Atan(δy * Math.Cos(β) / (δx * sinβ));
            }

            double κ = 4 * (1 - Math.Cos((α - β) / 2)) / (3 * Math.Sin((β - α) / 2));
            sinα = Math.Sin(α);
            cosα = Math.Cos(α);
            sinβ = Math.Sin(β);
            cosβ = Math.Cos(β);

            //XPoint pt1, pt2, pt3;
            if (!reflect)
            {
                // Calculation for quarter 0 and 1.
                switch (pathStart)
                {
                    case PathStart.MoveTo1st:
                        points.Add(matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)));
                        break;

                    case PathStart.LineTo1st:
                        points.Add(matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)));
                        break;

                    case PathStart.Ignore1st:
                        break;
                }
                points.Add(matrix.Transform(new XPoint(x0 + δx * (cosα - κ * sinα), y0 + δy * (sinα + κ * cosα))));
                points.Add(matrix.Transform(new XPoint(x0 + δx * (cosβ + κ * sinβ), y0 + δy * (sinβ - κ * cosβ))));
                points.Add(matrix.Transform(new XPoint(x0 + δx * cosβ, y0 + δy * sinβ)));
            }
            else
            {
                // Calculation for quarter 2 and 3.
                switch (pathStart)
                {
                    case PathStart.MoveTo1st:
                        points.Add(matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)));
                        break;

                    case PathStart.LineTo1st:
                        points.Add(matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)));
                        break;

                    case PathStart.Ignore1st:
                        break;
                }
                points.Add(matrix.Transform(new XPoint(x0 - δx * (cosα - κ * sinα), y0 - δy * (sinα + κ * cosα))));
                points.Add(matrix.Transform(new XPoint(x0 - δx * (cosβ + κ * sinβ), y0 - δy * (sinβ - κ * cosβ))));
                points.Add(matrix.Transform(new XPoint(x0 - δx * cosβ, y0 - δy * sinβ)));
            }
        }

        /// <summary>
        /// Creates between 1 and 5 Béziers curves from parameters specified like in WPF.
        /// </summary>
        public static List<XPoint> BezierCurveFromArc(XPoint point1, XPoint point2, XSize size,
            double rotationAngle, bool isLargeArc, bool clockwise, PathStart pathStart)
        {
            // See also http://www.charlespetzold.com/blog/blog.xml from January 2, 2008: 
            // http://www.charlespetzold.com/blog/2008/01/Mathematics-of-ArcSegment.html
            double δx = size.Width;
            double δy = size.Height;
            Debug.Assert(δx * δy > 0);
            double factor = δy / δx;
            bool isCounterclockwise = !clockwise;

            // Adjust for different radii and rotation angle.
            XMatrix matrix = new XMatrix();
            matrix.RotateAppend(-rotationAngle);
            matrix.ScaleAppend(δy / δx, 1);
            XPoint pt1 = matrix.Transform(point1);
            XPoint pt2 = matrix.Transform(point2);

            // Get info about chord that connects both points.
            XPoint midPoint = new XPoint((pt1.X + pt2.X) / 2, (pt1.Y + pt2.Y) / 2);
            XVector vect = pt2 - pt1;
            double halfChord = vect.Length / 2;

            // Get vector from chord to center.
            XVector vectRotated;

            // (comparing two Booleans here!)
            if (isLargeArc == isCounterclockwise)
                vectRotated = new XVector(-vect.Y, vect.X);
            else
                vectRotated = new XVector(vect.Y, -vect.X);

            vectRotated.Normalize();

            // Distance from chord to center.
            double centerDistance = Math.Sqrt(δy * δy - halfChord * halfChord);
            if (double.IsNaN(centerDistance))
                centerDistance = 0;

            // Calculate center point.
            XPoint center = midPoint + centerDistance * vectRotated;

            // Get angles from center to the two points.
            double α = Math.Atan2(pt1.Y - center.Y, pt1.X - center.X);
            double β = Math.Atan2(pt2.Y - center.Y, pt2.X - center.X);

            // (another comparison of two Booleans!)
            if (isLargeArc == (Math.Abs(β - α) < Math.PI))
            {
                if (α < β)
                    α += 2 * Math.PI;
                else
                    β += 2 * Math.PI;
            }

            // Invert matrix for final point calculation.
            matrix.Invert();
            double sweepAngle = β - α;

            // Let the algorithm of GDI+ DrawArc to Bézier curves do the rest of the job
            return BezierCurveFromArc(center.X - δx * factor, center.Y - δy, 2 * δx * factor, 2 * δy,
              α / Calc.Deg2Rad, sweepAngle / Calc.Deg2Rad, pathStart, ref matrix);
        }







        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        // The code below comes from WPF source code, because I was not able to convert an arc
        // to a series of Bezier curves exactly the way WPF renders the arc. I tested my own code
        // with the MinBar Test Suite from QualityLogic and could not find out why it does not match.
        // My Bezier curves came very close to the arc, but in some cases they do simply not match.
        // So I gave up and use the WPF code.
#if WPF || NETFX_CORE

        // ReSharper disable InconsistentNaming
        const double FUZZ = 1e-6;           // Relative 0
        // ReSharper restore InconsistentNaming

        //+-------------------------------------------------------------------------------------------------

        // 
        //  Function: GetArcAngle
        // 
        //  Synopsis: Get the number of Bezier arcs, and sine & cosine of each 
        //
        //  Notes:    This is a private utility used by ArcToBezier 
        //            We break the arc into pieces so that no piece will span more than 90 degrees.
        //            The input points are on the unit circle
        //
        //------------------------------------------------------------------------------------------------- 
        public static void
        GetArcAngle(
            XPoint startPoint,      // Start point 
            XPoint endPoint,        // End point
            bool isLargeArc,     // Choose the larger of the 2 possible arcs if TRUE 
            //SweepDirection sweepDirection,      // Direction n which to sweep the arc.
            bool isClockwise,
            out double cosArcAngle, // Cosine of a the sweep angle of one arc piece
            out double sinArcAngle, // Sine of a the sweep angle of one arc piece
            out int pieces)      // Out: The number of pieces 
        {
            double angle;

            // The points are on the unit circle, so:
            cosArcAngle = startPoint.X * endPoint.X + startPoint.Y * endPoint.Y;
            sinArcAngle = startPoint.X * endPoint.Y - startPoint.Y * endPoint.X;

            if (cosArcAngle >= 0)
            {
                if (isLargeArc)
                {
                    // The angle is between 270 and 360 degrees, so 
                    pieces = 4;
                }
                else
                {
                    // The angle is between 0 and 90 degrees, so
                    pieces = 1;
                    return; // We already have the cosine and sine of the angle
                }
            }
            else
            {
                if (isLargeArc)
                {
                    // The angle is between 180 and 270 degrees, so
                    pieces = 3;
                }
                else
                {
                    // The angle is between 90 and 180 degrees, so
                    pieces = 2;
                }
            }

            // We have to chop the arc into the computed number of pieces.  For cPieces=2 and 4 we could 
            // have uses the half-angle trig formulas, but for pieces=3 it requires solving a cubic
            // equation; the performance difference is not worth the extra code, so we'll get the angle, 
            // divide it, and get its sine and cosine. 

            Debug.Assert(pieces > 0);
            angle = Math.Atan2(sinArcAngle, cosArcAngle);

            if (isClockwise)
            {
                if (angle < 0)
                    angle += Math.PI * 2;
            }
            else
            {
                if (angle > 0)
                    angle -= Math.PI * 2;
            }

            angle /= pieces;
            cosArcAngle = Math.Cos(angle);
            sinArcAngle = Math.Sin(angle);
        }

        /******************************************************************************\
        * 
        * Function Description: 
        *
        * Get the distance from a circular arc's endpoints to the control points of the 
        * Bezier arc that approximates it, as a fraction of the arc's radius.
        *
        * Since the result is relative to the arc's radius, it depends strictly on the
        * arc's angle. The arc is assumed to be of 90 degrees of less, so the angle is 
        * determined by the cosine of that angle, which is derived from rDot = the dot
        * product of two radius vectors.  We need the Bezier curve that agrees with 
        * the arc's points and tangents at the ends and midpoint.  Here we compute the 
        * distance from the curve's endpoints to its control points.
        * 
        * Since we are looking for the relative distance, we can work on the unit
        * circle. Place the center of the circle at the origin, and put the X axis as
        * the bisector between the 2 vectors.  Let a be the angle between the vectors.
        * Then the X coordinates of the 1st & last points are cos(a/2).  Let x be the X 
        * coordinate of the 2nd & 3rd points.  At t=1/2 we have a point at (1,0).
        * But the terms of the polynomial there are all equal: 
        * 
        *           (1-t)^3 = t*(1-t)^2 = 2^2*(1-t) = t^3 = 1/8,
        * 
        * so from the Bezier formula there we have:
        *
        *           1 = (1/8) * (cos(a/2) + 3x + 3x + cos(a/2)),
        * hence 
        *           x = (1 - cos(a/2)) / 3
        * 
        * The X difference between that and the 1st point is: 
        *
        *           DX = x - cos(a/2) = 4(1 - cos(a/2)) / 3. 
        *
        * But DX = distance / sin(a/2), hence the distance is
        *
        *           dist = (4/3)*(1 - cos(a/2)) / sin(a/2). 
        *
        * Created:  5/29/2001 [....] 
        * 
        /*****************************************************************************/
        public static double
        GetBezierDistance(  // Return the distance as a fraction of the radius
            double dot,    // In: The dot product of the two radius vectors
            double radius) // In: The radius of the arc's circle (optional=1)
        {
            double radSquared = radius * radius;  // Squared radius

            Debug.Assert(dot >= -radSquared * .1);  // angle < 90 degrees 
            Debug.Assert(dot <= radSquared * 1.1);  // as dot product of 2 radius vectors

            double dist = 0;   // Acceptable fallback value

            /* Rather than the angle a, we are given rDot = R^2 * cos(a), so we
                multiply top and bottom by R: 

                                dist = (4/3)*(R - Rcos(a/2)) / Rsin(a/2) 
 
                and use some trig:
                                    __________ 
                        cos(a/2) = \/1 + cos(a) / 2
                                        ________________         __________
                        R*cos(a/2) = \/R^2 + R^2 cos(a) / 2 = \/R^2 + rDot / 2 */

            double cos = (radSquared + dot) / 2;   // =(R*cos(a))^2
            if (cos < 0)
                return dist;
            //                 __________________
            //  R*sin(a/2) = \/R^2 - R^2 cos(a/2)

            double sin = radSquared - cos;         // =(R*sin(a))^2 
            if (sin <= 0)
                return dist;

            sin = Math.Sqrt(sin); //   = R*cos(a) 
            cos = Math.Sqrt(cos); //   = R*sin(a)

            dist = 4 * (radius - cos) / 3;
            if (dist <= sin * FUZZ)
                dist = 0;
            else
                dist = 4 * (radius - cos) / sin / 3;

            return dist;
        }

        //+------------------------------------------------------------------------------------------------- 
        //
        //  Function: ArcToBezier 
        //
        //  Synopsis: Compute the Bezier approximation of an arc
        //
        //  Notes:    This utilitycomputes the Bezier approximation for an elliptical arc as it is defined 
        //            in the SVG arc spec. The ellipse from which the arc is carved is axis-aligned in its
        //            own coordinates, and defined there by its x and y radii. The rotation angle defines 
        //            how the ellipse's axes are rotated relative to our x axis. The start and end points 
        //            define one of 4 possible arcs; the sweep and large-arc flags determine which one of
        //            these arcs will be chosen. See SVG spec for details. 
        //
        //            Returning pieces = 0 indicates a line instead of an arc
        //                      pieces = -1 indicates that the arc degenerates to a point
        // 
        //--------------------------------------------------------------------------------------------------
        public static PointCollection ArcToBezier(double xStart, double yStart, double xRadius, double yRadius, double rotationAngle,
            bool isLargeArc, bool isClockwise, double xEnd, double yEnd, out int pieces)
        {
            double cosArcAngle, sinArcAngle, xCenter, yCenter, r, bezDist;
            XVector vecToBez1, vecToBez2;
            XMatrix matToEllipse;

            double fuzz2 = FUZZ * FUZZ;
            bool isZeroCenter = false;

            pieces = -1;

            // In the following, the line segment between between the arc's start and
            // end points is referred to as "the chord".

            // Transform 1: Shift the origin to the chord's midpoint 
            double x = (xEnd - xStart) / 2;
            double y = (yEnd - yStart) / 2;

            double halfChord2 = x * x + y * y;     // (half chord length)^2

            // Degenerate case: single point
            if (halfChord2 < fuzz2)
            {
                // The chord degeneartes to a point, the arc will be ignored 
                return null;
            }

            // Degenerate case: straight line
            if (!AcceptRadius(halfChord2, fuzz2, ref xRadius) || !AcceptRadius(halfChord2, fuzz2, ref yRadius))
            {
                // We have a zero radius, add a straight line segment instead of an arc
                pieces = 0;
                return null;
            }

            if (xRadius == 0 || yRadius == 0)
            {
                // We have a zero radius, add a straight line segment instead of an arc
                pieces = 0;
                return null;
            }

            // Transform 2: Rotate to the ellipse's coordinate system
            rotationAngle = -rotationAngle * Calc.Deg2Rad;

            double cos = Math.Cos(rotationAngle);
            double sin = Math.Sin(rotationAngle);

            r = x * cos - y * sin;
            y = x * sin + y * cos;
            x = r;

            // Transform 3: Scale so that the ellipse will become a unit circle 
            x /= xRadius;
            y /= yRadius;

            // We get to the center of that circle along a verctor perpendicular to the chord 
            // from the origin, which is the chord's midpoint. By Pythagoras, the length of that
            // vector is sqrt(1 - (half chord)^2). 

            halfChord2 = x * x + y * y;   // now in the circle coordinates

            if (halfChord2 > 1)
            {
                // The chord is longer than the circle's diameter; we scale the radii uniformly so
                // that the chord will be a diameter. The center will then be the chord's midpoint, 
                // which is now the origin.
                r = Math.Sqrt(halfChord2);
                xRadius *= r;
                yRadius *= r;
                xCenter = yCenter = 0;
                isZeroCenter = true;

                // Adjust the unit-circle coordinates x and y
                x /= r;
                y /= r;
            }
            else
            {
                // The length of (-y,x) or (x,-y) is sqrt(rHalfChord2), and we want a vector 
                // of length sqrt(1 - rHalfChord2), so we'll multiply it by:
                r = Math.Sqrt((1 - halfChord2) / halfChord2);
                //if (isLargeArc != (eSweepDirection == SweepDirection.Clockwise))
                if (isLargeArc != isClockwise)
                // Going to the center from the origin=chord-midpoint 
                {
                    // in the direction of (-y, x) 
                    xCenter = -r * y;
                    yCenter = r * x;
                }
                else
                {
                    // in the direction of (y, -x)
                    xCenter = r * y;
                    yCenter = -r * x;
                }
            }

            // Transformation 4: shift the origin to the center of the circle, which then becomes 
            // the unit circle. Since the chord's midpoint is the origin, the start point is (-x, -y)
            // and the endpoint is (x, y).
            XPoint ptStart = new XPoint(-x - xCenter, -y - yCenter);
            XPoint ptEnd = new XPoint(x - xCenter, y - yCenter);

            // Set up the matrix that will take us back to our coordinate system.  This matrix is 
            // the inverse of the combination of transformation 1 thru 4. 
            matToEllipse = new XMatrix(cos * xRadius, -sin * xRadius,
                                      sin * yRadius, cos * yRadius,
                                      (xEnd + xStart) / 2, (yEnd + yStart) / 2);

            if (!isZeroCenter)
            {
                // Prepend the translation that will take the origin to the circle's center
                matToEllipse.OffsetX += (matToEllipse.M11 * xCenter + matToEllipse.M21 * yCenter);
                matToEllipse.OffsetY += (matToEllipse.M12 * xCenter + matToEllipse.M22 * yCenter);
            }

            // Get the sine & cosine of the angle that will generate the arc pieces
            GetArcAngle(ptStart, ptEnd, isLargeArc, isClockwise, out cosArcAngle, out sinArcAngle, out pieces);

            // Get the vector to the first Bezier control point 
            bezDist = GetBezierDistance(cosArcAngle, 1);

            //if (eSweepDirection == SweepDirection.Counterclockwise)
            if (!isClockwise)
                bezDist = -bezDist;

            vecToBez1 = new XVector(-bezDist * ptStart.Y, bezDist * ptStart.X);

            PointCollection result = new PointCollection();

            // Add the arc pieces, except for the last 
            for (int idx = 1; idx < pieces; idx++)
            {
                // Get the arc piece's endpoint
                XPoint ptPieceEnd = new XPoint(ptStart.X * cosArcAngle - ptStart.Y * sinArcAngle, ptStart.X * sinArcAngle + ptStart.Y * cosArcAngle);
                vecToBez2 = new XVector(-bezDist * ptPieceEnd.Y, bezDist * ptPieceEnd.X);

                result.Add(matToEllipse.Transform(ptStart + vecToBez1));
                result.Add(matToEllipse.Transform(ptPieceEnd - vecToBez2));
                result.Add(matToEllipse.Transform(ptPieceEnd));

                // Move on to the next arc
                ptStart = ptPieceEnd;
                vecToBez1 = vecToBez2;
            }

            // Last arc - we know the endpoint 
            vecToBez2 = new XVector(-bezDist * ptEnd.Y, bezDist * ptEnd.X);

            result.Add(matToEllipse.Transform(ptStart + vecToBez1));
            result.Add(matToEllipse.Transform(ptEnd - vecToBez2));
            result.Add(new XPoint(xEnd, yEnd));

            return result;
        }

        /// <summary>
        /// Gets a value indicating whether radius large enough compared to the chord length.
        /// </summary>
        /// <param name="halfChord2">(1/2 chord length)squared </param>
        /// <param name="fuzz2">Squared fuzz.</param>
        /// <param name="radius">The radius to accept (or not).</param>
        static bool AcceptRadius(double halfChord2, double fuzz2, ref double radius)
        {
            Debug.Assert(halfChord2 >= fuzz2);   // Otherewise we have no guarantee that the radius is not 0, and we need to divide by the radius
            bool accept = radius * radius > halfChord2 * fuzz2;
            if (accept)
            {
                if (radius < 0)
                    radius = 0;
            }
            return accept;
        }
#endif
    }
}