File: Serialization\DrawingContextFlattener.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
 
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;
using Microsoft.Internal.AlphaFlattener;
 
using MS.Internal;
 
namespace System.Windows.Xps.Serialization
{
    #region internal class DrawingContextFlattener
    /// <summary>
    /// DrawingContext flattening filter
    /// 1) Convert animation to static value
    /// 2) Rasterize video/3D to bitmap
    /// 3) Convert shapes to Geometry
    /// 4) Convert VisualBrush to DrawingBrush
    /// 5) Convert DrawingImage to ImageBrush
    /// 6) Flatten DrawDrawing
    /// </summary>
    internal class DrawingContextFlattener
    {
        #region Constants
 
        /// <summary>
        /// Percentage to inflate rasterization clip rectangle to prevent prematurely clipping
        /// Visuals at page edges.
        /// </summary>
        private const double RasterizationClipInflate = 0.2;
 
        #endregion
        
        #region Private Fields
 
        private IMetroDrawingContext _dc;
 
        // Stores pushed transforms. Each transform contains all prior transforms.
        private List<Matrix> _fullTransform = new List<Matrix>();
 
        // Pushed combined clipping in world space.
        private List<Geometry> _fullClip = new List<Geometry>();
 
        private Size     _pageSize;
        
        // Used to track visual brushes whos visuals are being traversed. We do this to detect cycles in the visual tree
        TreeWalkProgress _treeWalkProgress;
        
        #endregion
 
        #region Constructors
 
        internal DrawingContextFlattener(IMetroDrawingContext dc, Size pageSize, TreeWalkProgress treeWalkProgress)
        {
            _dc       = dc;
            _pageSize = pageSize;
            _treeWalkProgress = treeWalkProgress;
        }
 
        #endregion
 
        #region Public State
 
        public void Push(
            Transform transform,
            Geometry clip,
            double opacity,
            Brush opacityMask,
            Rect maskBounds,
            bool onePrimitive,
            String nameAttr,
            Visual node,
            Uri navigateUri,
            EdgeMode edgeMode)
        {
            Debug.Assert(Utility.IsValid(opacity), "Invalid opacity should clip subtree");
 
            Matrix mat = Matrix.Identity;
 
            if (transform != null)
            {
                mat = transform.Value;
            }
 
            // opacity mask might be VisualBrush, hence ReduceBrush to reduce to DrawingBrush
            Debug.Assert(!BrushProxy.IsEmpty(opacityMask), "empty opacity mask should not result in Push");
            _dc.Push(
                mat,
                clip,
                opacity,
                ReduceBrush(opacityMask, maskBounds),
                maskBounds,
                onePrimitive,
                nameAttr,
                node,
                navigateUri,
                edgeMode
                );
 
            // prepend to transforms and clipping stack
            mat.Append(Transform);
            _fullTransform.Add(mat);
 
            // transform clip to world space, intersect with current clip, and push
            if (clip == null)
            {
                // push current clipping
                clip = Clip;
            }
            else
            {
                clip = Utility.TransformGeometry(clip, Transform);
 
                bool empty;
                clip = Utility.Intersect(clip, Clip, Matrix.Identity, out empty);
 
                if (empty)
                {
                    clip = Geometry.Empty;
                }
            }
 
            _fullClip.Add(clip);
        }
 
        /// <summary>
        /// Pop the most recent Push operation
        /// </summary>
        public void Pop()
        {
            _dc.Pop();
 
            Debug.Assert(_fullTransform.Count == _fullClip.Count);
 
            int lastIndex = _fullTransform.Count - 1;
 
            _fullTransform.RemoveAt(lastIndex);
            _fullClip.RemoveAt(lastIndex);
        }
 
        /// <summary>
        /// Transformation representing all pushed transformations.
        /// </summary>
        public Matrix Transform
        {
            get
            {
                if (_fullTransform.Count == 0)
                {
                    return Matrix.Identity;
                }
                else
                {
                    return _fullTransform[_fullTransform.Count - 1];
                }
            }
        }
 
        /// <summary>
        /// Intersection of all pushed clipping in world space.
        /// </summary>
        public Geometry Clip
        {
            get
            {
                if (_fullClip.Count == 0)
                {
                    return null;
                }
                else
                {
                    return _fullClip[_fullClip.Count - 1];
                }
            }
        }
 
        #endregion
 
        #region Public Methods
 
#if DEBUG
        /// <summary>
        /// Add comment to output, as debugging aid or add extra information like document structure
        /// </summary>
        /// <param name="str"></param>
        public void Comment(String str)
        {
            _dc.Comment(str);
        }
#endif
 
        /// <summary>
        /// Simplifies brush so we don't have to handle as many cases.
        /// </summary>
        /// <param name="brush"></param>
        /// <param name="bounds">Brush fill bounds; must not be empty if VisualBrush</param>
        /// <returns></returns>
        /// <remarks>
        /// Cases simplified:
        /// - A lot of empty brush cases. See BrushProxy.IsEmpty.
        /// - GradientBrush where gradient colors are similar enough to be SolidColorBrush.
        /// - Reduce VisualBrush to DrawingBrush.
        /// - Reduce ImageBrush with DrawingImage to DrawingBrush.
        /// </remarks>
        private Brush ReduceBrush(Brush brush, Rect bounds)
        {
            return BrushProxy.ReduceBrush(brush, bounds, Transform, _pageSize, _treeWalkProgress);            
        }
 
        private Pen ReducePen(Pen pen, Rect bounds)
        {
            if (PenProxy.IsNull(pen))
            {
                return null;
            }
 
            Brush b = ReduceBrush(pen.Brush, bounds);
 
            if (b == null)
            {
                return null;
            }
 
            if (! Object.ReferenceEquals(b, pen.Brush))
            {
                pen = pen.CloneCurrentValue();
 
                pen.Brush = b;
            }
 
            return pen;
        }
        
        /// <summary>
        /// Draw a Geometry with the provided Brush and/or Pen.
        /// </summary>
        public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
        {
            if (geometry != null)
            {
                if (brush != null)
                {
                    Rect bounds = geometry.Bounds;
 
                    brush = ReduceBrush(brush, bounds);
                }
                
                if ((pen != null) && (pen.Brush != null))
                {
                    Rect bounds = Rect.Empty;
 
                    if (VisualSerializer.NeedBounds(pen.Brush))
                    {
                        bounds = geometry.GetRenderBounds(pen);
                    }
                                            
                    pen = ReducePen(pen, bounds);
                }                    
                else
                {
                    pen = null;
                }
                                    
                // Draw even if brush/pen is null since geometry may be a hyperlink.
                // _dc should cull invisible geometries when necessary.
                _dc.DrawGeometry(brush, pen, geometry);
            }
        }
 
        /// <summary>
        /// Draw an Image into the region specified by the Rect.
        /// </summary>
        public void DrawImage(ImageSource image, Rect rectangle)
        {
            if (image != null)
            {
                DrawingImage drawingImage;
                D3DImage d3dimage;
                
                if (image is BitmapSource)
                {
                    // Apparently, IMetroDrawingContext.DrawImage only handles BitmapSources...
                    _dc.DrawImage(image, rectangle);
                }
                else if ((drawingImage = image as DrawingImage) != null)
                {
                    DrawGeometry(new DrawingBrush(drawingImage.Drawing), null, new RectangleGeometry(rectangle));
                }
                else if ((d3dimage = image as D3DImage) != null)
                {
                    _dc.DrawImage(d3dimage.CopyBackBuffer(), rectangle);
                }
                else
                {
                    Invariant.Assert(false, "Unhandled ImageSource type!");
                }
            }
            else
            {
                // no image, but may still be a hyperlink
                _dc.DrawImage(null, rectangle);
            }
        }
 
        /// <summary>
        /// Clip visual bounds to rasterization clip rectangle.
        /// </summary>
        /// <param name="visualBounds"></param>
        /// <param name="visualToWorldTransform"></param>
        /// <returns></returns>
        private Rect PerformRasterizationClip(Rect visualBounds, Matrix visualToWorldTransform)
        {
            if (! _pageSize.IsEmpty)
            {
                Rect pageBox = new Rect(0, 0, _pageSize.Width, _pageSize.Height);
                
                pageBox.Inflate(
                    RasterizationClipInflate * pageBox.Width,
                    RasterizationClipInflate * pageBox.Height
                    );
 
                visualBounds.Intersect(pageBox);
            }
            
            return visualBounds;
        }
 
        /// <summary>
        /// Rasterizes Visual and its descendents with optional bitmap effect. Also handles Visual opacity.
        /// </summary>
        /// <param name="visual"></param>
        /// <param name="nameAttr">Preserve Visual name attribute</param>
        /// <param name="navigateUri">Preserve FixedPage.NavigateUri</param>
        /// <param name="edgeMode">Preserve RenderOptions.EdgeMode</param>
        /// <param name="visualTransform">Visual.Transform</param>
        /// <param name="visualToWorldTransform">Full transform from Visual to world space, including visual transform prepended</param>
        /// <param name="inheritedTransformHint">
        /// Transformation above VisualTreeFlattener instance. This is needed if we're reducing VisualBrush to
        /// DrawingBrush to increase rasterization fidelity if brush is small, but will eventually fill large region.
        /// </param>
        /// <param name="clip">Clip in world space</param>
        /// <param name="effect">Optional bitmap effect</param>
        public void DrawRasterizedVisual(
            Visual visual,
            string nameAttr,
            Uri navigateUri,
            EdgeMode edgeMode,
            Transform visualTransform,
            Matrix visualToWorldTransform,
            Matrix inheritedTransformHint,
            Geometry clip,
            Effect effect
            )
        {
            Debug.Assert(visual != null);
 
            // Compute the bounding box of the visual and its descendants
            Rect bounds = VisualTreeHelper.GetContentBounds(visual);
            bounds.Union(VisualTreeHelper.GetDescendantBounds(visual));
 
            if (!Utility.IsRenderVisible(bounds))
                return;
 
            // transform clip to visual space
            if (clip != null)
            {
                Matrix worldToVisualTransform = visualToWorldTransform;
                worldToVisualTransform.Invert();
 
                clip = Utility.TransformGeometry(clip, worldToVisualTransform);
            }
 
            //
            // Clip visual bounds to rasterization clipping geometry.
            // We can't clip to Visual clipping geometry, since bitmap effects are applied
            // without clipping. For example, the blur effect looks different if you clip first
            // then apply effect, compared to applying the effect and then clipping.
            //
            bounds = PerformRasterizationClip(bounds, visualToWorldTransform);
 
            if (!Utility.IsRenderVisible(bounds))
                return;
 
            //
            // Rasterize Visual to IMetroDrawingContext with optional banding, depending
            // on whether bitmap effect is present. We can Push/Pop/Draw directly on _dc
            // since the input we provide it is already normalized (no DrawingImage, for example).
            // We also don't need Transform or Clip to be updated.
            //
            _dc.Push(
                visualTransform == null ? Matrix.Identity : visualTransform.Value,
                clip,
                1.0,
                null,
                Rect.Empty,
                /*onePrimitive=*/false, // we Push and DrawImage below, which counts as 2 primitives
                nameAttr,
                visual,
                navigateUri,
                edgeMode
                );
 
            Matrix bitmapToVisualTransform;
            BitmapSource bitmap;
 
            // If we have an Effect, we may need to inflate the bounds to account for the effect's output.
            if (effect != null)
            {
                bounds = effect.GetRenderBounds(bounds);
            }
            
            //
            // Rasterize visual in its entirety. Banding is not useful at this point since
            // the resulting bands are all kept in memory anyway. Plus transformation is applied
            // to the band, which causes gaps between bands if they're rotated (bug 1562237).
            //
            // Banding is performed at GDIExporter layer just prior to sending images to the printer.
            //
            bitmap = Utility.RasterizeVisual(
                visual,
                bounds,
                visualToWorldTransform * inheritedTransformHint,
                out bitmapToVisualTransform
                );
 
 
            if (bitmap != null)
            {
                _dc.Push(bitmapToVisualTransform, null, 1.0, null, Rect.Empty, /*onePrimitive=*/true, null, null, null, EdgeMode.Unspecified);
                _dc.DrawImage(bitmap, new Rect(0, 0, bitmap.Width, bitmap.Height));
                _dc.Pop();
            }
            
            _dc.Pop();
        }
 
        /// <summary>
        /// Draw a GlyphRun.
        /// </summary>
        public void DrawGlyphRun(Brush foreground, GlyphRun glyphRun)
        {
            if (glyphRun != null)
            {
                foreground = ReduceBrush(foreground, glyphRun.ComputeInkBoundingBox());
 
                // foreground may be null, but glyphrun may still be a hyperlink
                _dc.DrawGlyphRun(foreground, glyphRun);
            }
        }
 
        #endregion
    }
    #endregion
 
    internal static class GeometryHelper
    {
        const double FUZZ = 1e-6;           // Relative 0
        const double PI_OVER_180 = Math.PI / 180;  // PI/180
 
        //  Function: AcceptRadius
        //  Synopsis: Accept one radius
        //  Return:   false if the radius is too small compared to the chord length
        public static bool
        AcceptRadius(
            double rHalfChord2,    // (1/2 chord length)squared
            double rFuzz2,         // Squared fuzz
            ref double rRadius)   // The radius to accept (or not)
        {
            Debug.Assert(rHalfChord2 >= rFuzz2);   // Otherewise we have no guarantee that the radius is not 0,
            // and we need to divide by the radius
            bool fAccept = (rRadius * rRadius > rHalfChord2 * rFuzz2);
 
            if (fAccept)
            {
                if (rRadius < 0)
                {
                    rRadius = 0;
                }
            }
 
            return fAccept;
        }
 
        public static Point Add(Point a, Point b)
        {
            return new Point(a.X + b.X, a.Y + b.Y);
        }
 
        public static Point Sub(Point a, Point b)
        {
            return new Point(a.X - b.X, a.Y - b.Y);
        }
 
        // Dot Product
        public static double DotProduct(Point a, Point b)
        {
            return a.X * b.X + a.Y * b.Y;
        }
 
        public static double Determinant(Point a, Point b)
        {
            return a.X * b.Y - a.Y * b.X;
        }
 
        //+-------------------------------------------------------------------------------------------------
 
        //
        //  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(
            Point ptStart,      // Start point
            Point ptEnd,        // End point
            bool fIsLargeArc,     // Choose the larger of the 2 possible arcs if TRUE
            SweepDirection eSweepDirection,      // Direction n which to sweep the arc.
            out double rCosArcAngle, // Cosine of a the sweep angle of one arc piece
            out double rSinArcAngle, // Sine of a the sweep angle of one arc piece
            out int cPieces)      // Out: The number of pieces
        {
            double rAngle;
 
            // The points are on the unit circle, so:
            rCosArcAngle = DotProduct(ptStart, ptEnd);
            rSinArcAngle = Determinant(ptStart, ptEnd);
 
            if (rCosArcAngle >= 0)
            {
                if (fIsLargeArc)
                {
                    // The angle is between 270 and 360 degrees, so
                    cPieces = 4;
                }
                else
                {
                    // The angle is between 0 and 90 degrees, so
                    cPieces = 1;
                    return; // We already have the cosine and sine of the angle
                }
            }
            else
            {
                if (fIsLargeArc)
                {
                    // The angle is between 180 and 270 degrees, so
                    cPieces = 3;
                }
                else
                {
                    // The angle is between 90 and 180 degrees, so
                    cPieces = 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 cPieces=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(cPieces > 0);
            rAngle = Math.Atan2(rSinArcAngle, rCosArcAngle);
 
            if (eSweepDirection == SweepDirection.Clockwise)
            {
                if (rAngle < 0)
                {
                    rAngle += Math.PI * 2;
                }
            }
            else
            {
                if (rAngle > 0)
                {
                    rAngle -= Math.PI * 2;
                }
            }
 
            rAngle /= cPieces;
            rCosArcAngle = Math.Cos(rAngle);
            rSinArcAngle = Math.Sin(rAngle);
        }
 
        /*****************************************************************************\
        *
        * 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 MichKa
        *
        /*****************************************************************************/
        public static double
        GetBezierDistance(  // Return the distance as a fraction of the radius
            double rDot,    // In: The dot product of the two radius vectors
            double rRadius) // In: The radius of the arc's circle (optional=1)
        {
            double rRadSquared = rRadius * rRadius;  // Squared radius
 
            Debug.Assert(rDot >= -rRadSquared * .1);  // angle < 90 degrees
            Debug.Assert(rDot <= rRadSquared * 1.1);  // as dot product of 2 radius vectors
 
            double rDist = 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 rCos = (rRadSquared + rDot) / 2;   // =(R*cos(a))^2
            if (rCos < 0)   // Shouldn't happen but dist=0 will work
            {
                return rDist;
            }
            //                 __________________
            //  R*sin(a/2) = \/R^2 - R^2 cos(a/2)  
 
            double rSin = rRadSquared - rCos;         // =(R*sin(a))^2
            if (rSin <= 0)
                // 0 angle, we shouldn't be rounding the corner, but dist=0 is OK
                return rDist;
 
            rSin = Math.Sqrt(rSin); //   = R*cos(a)
            rCos = Math.Sqrt(rCos); //   = R*sin(a)
 
            rDist = 4 * (rRadius - rCos) / 3;
            if (rDist <= rSin * FUZZ)
            {
                rDist = 0;
            }
            else
            {
                rDist = 4 * (rRadius - rCos) / rSin / 3;
            }
 
            return rDist;
        }
 
        //+-------------------------------------------------------------------------------------------------
        //
        //  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 cPieces = 0 indicates a line instead of an arc
        //                      cPieces = -1 indicates that the arc degenerates to a point 
        //
        //--------------------------------------------------------------------------------------------------
        public static PointCollection ArcToBezier(
            double xStart,     // X coordinate of the last point
            double yStart,     // Y coordinate of the last point
            double xRadius,    // The ellipse's X radius
            double yRadius,    // The ellipse's Y radius
            double rRotation,  // Rotation angle of the ellipse's x axis
            bool fIsLargeArc,  // Choose the larger of the 2 possible arcs if TRUE
            SweepDirection eSweepDirection,   // Sweep the arc while increasing the angle if TRUE
            double xEnd,       // X coordinate of the last point
            double yEnd,       // Y coordinate of the last point
            out int cPieces)    // The number of output Bezier curves
        {
            double rCosArcAngle, rSinArcAngle, xCenter, yCenter, r, rBezDist;
            Point vecToBez1, vecToBez2;
            Matrix matToEllipse;
 
            double rFuzz2 = FUZZ * FUZZ;
            bool fZeroCenter = false;
 
            cPieces = -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 rHalfChord2 = x * x + y * y;     // (half chord length)^2
 
            // Degenerate case: single point
            if (rHalfChord2 < rFuzz2)
            {
                // The chord degeneartes to a point, the arc will be ignored
                return null;
            }
 
            // Degenerate case: straight line
            if (!AcceptRadius(rHalfChord2, rFuzz2, ref xRadius) ||
                !AcceptRadius(rHalfChord2, rFuzz2, ref yRadius))
            {
                // We have a zero radius, add a straight line segment instead of an arc
                cPieces = 0;
                return null;
            }
 
            // Transform 2: Rotate to the ellipse's coordinate system
            rRotation = -rRotation * PI_OVER_180;
 
            double rCos = Math.Cos(rRotation);
            double rSin = Math.Sin(rRotation);
 
            r = x * rCos - y * rSin;
            y = x * rSin + y * rCos;
            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).
 
            rHalfChord2 = x * x + y * y;   // now in the circle coordinates   
 
            if (rHalfChord2 > 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(rHalfChord2);
                xRadius *= r;
                yRadius *= r;
                xCenter = yCenter = 0;
                fZeroCenter = 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 - rHalfChord2) / rHalfChord2);
                if (fIsLargeArc != (eSweepDirection == SweepDirection.Clockwise))
                // 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).
            Point ptStart = new Point(-x - xCenter, -y - yCenter);
            Point ptEnd = new Point(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 Matrix(rCos * xRadius, -rSin * xRadius,
                                      rSin * yRadius, rCos * yRadius,
                                      (xEnd + xStart) / 2, (yEnd + yStart) / 2);
 
            if (!fZeroCenter)
            {
                // 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, fIsLargeArc, eSweepDirection, out rCosArcAngle, out rSinArcAngle, out cPieces);
 
            // Get the vector to the first Bezier control point
            rBezDist = GetBezierDistance(rCosArcAngle, 1);
 
            if (eSweepDirection == SweepDirection.Counterclockwise)
            {
                rBezDist = -rBezDist;
            }
 
            vecToBez1 = new Point(-rBezDist * ptStart.Y, rBezDist * ptStart.X);
 
            PointCollection rslt = new PointCollection();
 
            // Add the arc pieces, except for the last
            for (int i = 1; i < cPieces; i++)
            {
                // Get the arc piece's endpoint
                Point ptPieceEnd = new Point(ptStart.X * rCosArcAngle - ptStart.Y * rSinArcAngle,
                                    ptStart.X * rSinArcAngle + ptStart.Y * rCosArcAngle);
                vecToBez2 = new Point(-rBezDist * ptPieceEnd.Y, rBezDist * ptPieceEnd.X);
 
                rslt.Add(matToEllipse.Transform(Add(ptStart, vecToBez1)));
                rslt.Add(matToEllipse.Transform(Sub(ptPieceEnd, vecToBez2)));
                rslt.Add(matToEllipse.Transform(ptPieceEnd));
 
                // Move on to the next arc
                ptStart = ptPieceEnd;
                vecToBez1 = vecToBez2;
            }
 
            // Last arc - we know the endpoint
            vecToBez2 = new Point(-rBezDist * ptEnd.Y, rBezDist * ptEnd.X);
 
            rslt.Add(matToEllipse.Transform(Add(ptStart, vecToBez1)));
            rslt.Add(matToEllipse.Transform(Sub(ptEnd, vecToBez2)));
            rslt.Add(new Point(xEnd, yEnd));
 
            return rslt;
        }
    };
 
    /// <summary>
    /// IMetroDrawingContext implementation to convert VisualBrush to DrawingBrush to
    /// reduce the number of Brush types we need to handle.
    /// </summary>
    internal class DrawingFlattenDrawingContext : IMetroDrawingContext
    {
        #region Public Properties
 
        private DrawingContext _context = null;
 
        // Records number of DrawingContext.Push calls part of each DrawingFlattenDrawingContext.Push call
        // for use in stack popping.
        private Stack _push = new Stack();
 
        #endregion
 
        #region Constructors
 
        public DrawingFlattenDrawingContext(DrawingContext context)
        {
            Debug.Assert(context != null);
 
            _context = context;
        }
 
        #endregion
 
        #region IMetroDrawingContext Members
 
        public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
        {
            _context.DrawGeometry(brush, pen, geometry);
        }
 
        public void DrawImage(ImageSource image, Rect rectangle)
        {
            _context.DrawImage(image, rectangle);
        }
 
        public void DrawGlyphRun(Brush foreground, GlyphRun glyphRun)
        {
            _context.DrawGlyphRun(foreground, glyphRun);
        }
 
        public void Push(
            Matrix transform,
            Geometry clip,
            double opacity,
            Brush opacityMask,
            Rect maskBounds,
            bool onePrimitive,
 
            // serialization attributes
            String nameAttr,
            Visual node,
            Uri navigateUri,
            EdgeMode edgeMode
            )
        {
            opacity = Utility.NormalizeOpacity(opacity);
 
            int pushCount = 0;
 
            if (!transform.IsIdentity)
            {
                _context.PushTransform(new MatrixTransform(transform));
                pushCount++;
            }
 
            if (clip != null)
            {
                _context.PushClip(clip);
                pushCount++;
            }
 
            if (!Utility.IsOpaque(opacity))
            {
                _context.PushOpacity(opacity);
                pushCount++;
            }
 
            if (opacityMask != null)
            {
                _context.PushOpacityMask(opacityMask);
                pushCount++;
            }
 
            _push.Push(pushCount);
        }
 
        public void Pop()
        {
            int popCount = (int)_push.Pop();
 
            for (int index = 0; index < popCount; index++)
            {
                _context.Pop();
            }
        }
 
        public void Comment(string message)
        {
        }
 
        #endregion
    }
}