Stroke.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / System / Windows / Ink / Stroke.cs / 1 / Stroke.cs

                            //------------------------------------------------------------------------ 
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
//----------------------------------------------------------------------- 

using MS.Utility; 
using System; 
using System.ComponentModel;
using System.Diagnostics; 
using System.Windows;
using System.Windows.Media;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 
using System.Collections;
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.IO;
using System.Runtime.Serialization; 
using MS.Internal.Ink.InkSerializedFormat;
using MS.Internal;
using MS.Internal.Ink;
using System.Reflection; 
using System.Windows.Input;
 
using SR=MS.Internal.PresentationCore.SR; 
using SRID=MS.Internal.PresentationCore.SRID;
 
// Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET
namespace System.Windows.Ink
{
    ///  
    /// A Stroke object is the fundamental unit of ink data storage.
    ///  
    public partial class Stroke : INotifyPropertyChanged 
    {
        /// Create a stroke from a StylusPointCollection 
        /// 
        /// 
        /// StylusPointCollection that makes up the stroke
        public Stroke(StylusPointCollection stylusPoints) 
            : this (stylusPoints, new DrawingAttributes(), null)
        { 
        } 

        /// Create a stroke from a StylusPointCollection 
        /// 
        /// 
        /// StylusPointCollection that makes up the stroke
        /// drawingAttributes 
        public Stroke(StylusPointCollection stylusPoints, DrawingAttributes drawingAttributes)
            : this(stylusPoints, drawingAttributes, null) 
        { 
        }
 
        /// Create a stroke from a StylusPointCollection
        /// 
        /// 
        /// StylusPointCollection that makes up the stroke 
        /// drawingAttributes
        /// extendedProperties 
        internal Stroke(StylusPointCollection stylusPoints, DrawingAttributes drawingAttributes, ExtendedPropertyCollection extendedProperties) 
        {
            if (stylusPoints == null) 
            {
                throw new ArgumentNullException("stylusPoints");
            }
            if (stylusPoints.Count == 0) 
            {
                throw new ArgumentException(SR.Get(SRID.InvalidStylusPointCollectionZeroCount), "stylusPoints"); 
            } 
            if (drawingAttributes == null)
            { 
                throw new ArgumentNullException("drawingAttributes");
            }

            _drawingAttributes = drawingAttributes; 
            _stylusPoints = stylusPoints;
            _extendedProperties = extendedProperties; 
 
            Initialize();
        } 

        /// 
        /// Internal helper to set up listeners, called by ctor and by Clone
        ///  
        private void Initialize()
        { 
            _drawingAttributes.AttributeChanged += new PropertyDataChangedEventHandler(DrawingAttributes_Changed); 
            _stylusPoints.Changed += new EventHandler(StylusPoints_Changed);
            _stylusPoints.CountGoingToZero += new CancelEventHandler(StylusPoints_CountGoingToZero); 
        }

        /// Returns a new stroke that has a deep copy.
        /// Deep copied data includes points, point description, drawing attributes, and transform 
        /// Deep copy of current stroke
        public virtual Stroke Clone() 
        { 
            //
            // use MemberwiseClone, which will instance the most derived type 
            // We use this instead of Activator.CreateInstance because it does not
            // require ReflectionPermission.  One thing to note, all references
            // are shared, including event delegates, so we need to set those to null
            // 
            Stroke clone = (Stroke)this.MemberwiseClone();
 
            // 
            // null the delegates in the cloned strokes
            // 
            clone.DrawingAttributesChanged = null;
            clone.DrawingAttributesReplaced = null;
            clone.StylusPointsReplaced = null;
            clone.StylusPointsChanged = null; 
            clone.PropertyDataChanged = null;
            clone.Invalidated = null; 
            clone._propertyChanged = null; 

            //Clone is also called from Stroke.Copy internally for point 
            //erase.  In that case, we don't want to clone the StylusPoints
            //because they will be replaced after we call
            if (_cloneStylusPoints)
            { 
                clone._stylusPoints = _stylusPoints.Clone();
            } 
            clone._drawingAttributes = _drawingAttributes.Clone(); 
            if (_extendedProperties != null)
            { 
                clone._extendedProperties = _extendedProperties.Clone();
            }
            //set up listeners
            clone.Initialize(); 

            // 
            // copy state 
            //
            Debug.Assert(_cachedGeometry == null || _cachedGeometry.IsFrozen); 
            //we don't need to cache if this is frozen
            //if (null != _cachedGeometry)
            //{
            //    clone._cachedGeometry = _cachedGeometry.Clone(); 
            //}
            //don't need to clone these, they are value types 
            //and are copied by MemberwiseClone 
            //_isSelected
            //_drawAsHollow 
            //_cachedBounds

            //this need to be reset
            clone._cloneStylusPoints = true; 

            return clone; 
        } 

 
        /// Transforms the ink and also changes the StylusTip
        /// Matrix to transform the stroke by
        /// Boolean if true the transform matrix will be applied to StylusTip
        public virtual void Transform(Matrix transformMatrix, bool applyToStylusTip) 
        {
            if (transformMatrix.IsIdentity) 
            { 
                return;
            } 

            if (!transformMatrix.HasInverse)
            {
                throw new ArgumentException(SR.Get(SRID.MatrixNotInvertible), "transformMatrix"); 
            }
            else if ( MatrixHelper.ContainsNaN(transformMatrix)) 
            { 
                throw new ArgumentException(SR.Get(SRID.InvalidMatrixContainsNaN), "transformMatrix");
            } 
            else if ( MatrixHelper.ContainsInfinity(transformMatrix))
            {
                throw new ArgumentException(SR.Get(SRID.InvalidMatrixContainsInfinity), "transformMatrix");
            } 
            else
            { 
                // we need to force a recaculation of the cached path geometry right after the 
                // DrawingAttributes changed, beforet the events are raised.
                _cachedGeometry = null; 
                // Set the cached bounds to empty, which will force a re-calculation of the _cachedBounds upon next GetBounds call.
                _cachedBounds = Rect.Empty;

                if (applyToStylusTip) 
                {
                    //we use this flag to prevent this method from causing two 
                    //invalidates, which causes a good deal of memory thrash when 
                    //the strokes are being rendered
                    _delayRaiseInvalidated = true; 
                }

                try
                { 
                    _stylusPoints.Transform(new System.Windows.Media.MatrixTransform(transformMatrix));
 
                    if (applyToStylusTip) 
                    {
                        Matrix newMatrix = _drawingAttributes.StylusTipTransform; 
                        // Don't allow a Translation in the matrix
                        transformMatrix.OffsetX = 0;
                        transformMatrix.OffsetY = 0;
                        newMatrix *= transformMatrix; 
                        //only persist the StylusTipTransform if there is an inverse.
                        //there are cases where two invertible xf's result in a non-invertible one 
                        //we decided not to throw here because it is so unobvious 
                        if (newMatrix.HasInverse)
                        { 
                            _drawingAttributes.StylusTipTransform = newMatrix;
                        }
                    }
                    if (_delayRaiseInvalidated) 
                    {
                        OnInvalidated(EventArgs.Empty); 
                    } 
                    //else OnInvalidated was already raised
                } 
                finally
                {
                    //We do this in a finally block to reset
                    //our state in the event that an exception is thrown. 
                    _delayRaiseInvalidated = false;
                } 
            } 
        }
 
        /// 
        /// Returns a Bezier smoothed version of the StylusPoints
        /// 
        ///  
        public StylusPointCollection GetBezierStylusPoints()
        { 
            // Since we can't compute Bezier for single point stroke, we should return. 
            if (_stylusPoints.Count < 2)
            { 
                return _stylusPoints;
            }

            // Construct the Bezier approximation 
            Bezier bezier = new Bezier();
            if (!bezier.ConstructBezierState(   _stylusPoints, 
                                                DrawingAttributes.FittingError)) 
            {
                //construction failed, return a clone of the original points 
                return _stylusPoints.Clone();
            }

            double tolerance = 0.5; 
            StylusShape stylusShape = this.DrawingAttributes.StylusShape;
            if (null != stylusShape) 
            { 
                Rect shapeBoundingBox = stylusShape.BoundingBox;
                double min = Math.Min(shapeBoundingBox.Width, shapeBoundingBox.Height); 
                tolerance = Math.Log10(min + min);
                tolerance *= (StrokeCollectionSerializer.AvalonToHimetricMultiplier / 2);
                if (tolerance < 0.5)
                { 
                    //don't allow tolerance to drop below .5 or we
                    //can wind up with an huge amount of bezier points 
                    tolerance = 0.5; 
                }
            } 

            List bezierPoints = bezier.Flatten(tolerance);
            return GetInterpolatedStylusPoints(bezierPoints);
        } 

        ///  
        /// Interpolate packet / pressure data from _stylusPoints 
        /// 
        private StylusPointCollection GetInterpolatedStylusPoints(List bezierPoints) 
        {
            Debug.Assert(bezierPoints != null && bezierPoints.Count > 0);

            //new points need the same description 
            StylusPointCollection bezierStylusPoints =
                new StylusPointCollection(_stylusPoints.Description, bezierPoints.Count); 
 
            //
            // add the first point 
            //
            AddInterpolatedBezierPoint( bezierStylusPoints,
                                        bezierPoints[0],
                                        _stylusPoints[0].GetAdditionalData(), 
                                        _stylusPoints[0].PressureFactor);
 
            if (bezierPoints.Count == 1) 
            {
                return bezierStylusPoints; 
            }

            //
            // this is a little tricky... Bezier points are not equidistant, so we have to 
            // use the length between the points instead of the indexes to interpolate pressure
            // 
            //  Bezier points:   P0 ------------------------------ P1 ---------- P2 --------- P3 
            //  Stylus points:   P0 -------- P1 ------------ P2 ------------- P3 ---------- P4
            // 
            //  Or in terms of lengths...
            //  Bezier lengths:  L1 ------------------------------
            //                   L2 ---------------------------------------------
            //                   L3 --------------------------------------------------------- 
            //
            //  Stylus lengths   L1 -------- 
            //                   L2 ------------------------ 
            //                   L3 -----------------------------------------
            //                   L4 ------------------------------------------------------- 
            //
            //
            //
            double bezierLength = 0.0; 
            double prevUnbezierLength = 0.0;
            double unbezierLength = GetDistanceBetweenPoints((Point)_stylusPoints[0], (Point)_stylusPoints[1]); 
 
            int stylusPointsIndex = 1;
            int stylusPointsCount = _stylusPoints.Count; 
            //skip the first and last point
            for (int x = 1; x < bezierPoints.Count - 1; x++)
            {
                bezierLength += GetDistanceBetweenPoints(bezierPoints[x - 1], bezierPoints[x]); 
                while (stylusPointsCount > stylusPointsIndex)
                { 
                    if (bezierLength >= prevUnbezierLength && 
                        bezierLength < unbezierLength)
                    { 
                        Debug.Assert(stylusPointsCount > stylusPointsIndex);

                        StylusPoint prevStylusPoint = _stylusPoints[stylusPointsIndex - 1];
                        float percentFromPrev = 
                            ((float)bezierLength - (float)prevUnbezierLength) /
                            ((float)unbezierLength - (float)prevUnbezierLength); 
                        float pressureAtPrev = prevStylusPoint.PressureFactor; 
                        float pressureDelta = _stylusPoints[stylusPointsIndex].PressureFactor - pressureAtPrev;
                        float interopolatedPressure = (percentFromPrev * pressureDelta) + pressureAtPrev; 

                        AddInterpolatedBezierPoint(bezierStylusPoints,
                                                    bezierPoints[x],
                                                    prevStylusPoint.GetAdditionalData(), 
                                                    interopolatedPressure);
                        break; 
 
                    }
                    else 
                    {
                        Debug.Assert(bezierLength >= prevUnbezierLength);
                        //
                        // move our unbezier lengths forward... 
                        //
                        stylusPointsIndex++; 
                        if (stylusPointsCount > stylusPointsIndex) 
                        {
                            prevUnbezierLength = unbezierLength; 
                            unbezierLength +=
                                GetDistanceBetweenPoints((Point)_stylusPoints[stylusPointsIndex - 1],
                                                         (Point)_stylusPoints[stylusPointsIndex]);
                        } //else we'll break 
                    }
                } 
            } 

            // 
            // add the last point
            //
            AddInterpolatedBezierPoint( bezierStylusPoints,
                                        bezierPoints[bezierPoints.Count - 1], 
                                        _stylusPoints[stylusPointsCount - 1].GetAdditionalData(),
                                        _stylusPoints[stylusPointsCount - 1].PressureFactor); 
 
            return bezierStylusPoints;
        } 

        /// 
        /// Private helper used to get the length between two points
        ///  
        private double GetDistanceBetweenPoints(Point p1, Point p2)
        { 
            Vector spine = p2 - p1; 
            return Math.Sqrt(spine.LengthSquared);
        } 

        /// 
        /// Private helper for adding a StylusPoint to the BezierStylusPoints
        ///  
        private void AddInterpolatedBezierPoint(StylusPointCollection bezierStylusPoints,
                                                Point bezierPoint, 
                                                int[] additionalData, 
                                                float pressure)
        { 
            double xVal = bezierPoint.X > StylusPoint.MaxXY ?
                        StylusPoint.MaxXY :
                        (bezierPoint.X < StylusPoint.MinXY ? StylusPoint.MinXY : bezierPoint.X);
 
            double yVal = bezierPoint.Y > StylusPoint.MaxXY ?
                        StylusPoint.MaxXY : 
                        (bezierPoint.Y < StylusPoint.MinXY ? StylusPoint.MinXY : bezierPoint.Y); 

 
            StylusPoint newBezierPoint =
                new StylusPoint(xVal, yVal, pressure, bezierStylusPoints.Description, additionalData, false, false);

 
            bezierStylusPoints.Add(newBezierPoint);
        } 
 
        /// 
        /// Allows addition of objects to the EPC 
        /// 
        /// 
        /// 
        public void AddPropertyData(Guid propertyDataId, object propertyData) 
        {
            DrawingAttributes.ValidateStylusTipTransform(propertyDataId, propertyData); 
 
            object oldValue = null;
            if (ContainsPropertyData(propertyDataId)) 
            {
                oldValue = GetPropertyData(propertyDataId);
                this.ExtendedProperties[propertyDataId] = propertyData;
            } 
            else
            { 
                this.ExtendedProperties.Add(propertyDataId, propertyData); 
            }
 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, propertyData, oldValue));
        }
 

        ///  
        /// Allows removal of objects from the EPC 
        /// 
        ///  
        public void RemovePropertyData(Guid propertyDataId)
        {
            object propertyData = GetPropertyData(propertyDataId);
            this.ExtendedProperties.Remove(propertyDataId); 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, null, propertyData)); 
        } 

        ///  
        /// Allows retrieval of objects from the EPC
        /// 
        /// 
        public object GetPropertyData(Guid propertyDataId) 
        {
            return this.ExtendedProperties[propertyDataId]; 
        } 

        ///  
        /// Allows retrieval of a Array of guids that are contained in the EPC
        /// 
        public Guid[] GetPropertyDataIds()
        { 
            return this.ExtendedProperties.GetGuidArray();
        } 
 
        /// 
        /// Allows the checking of objects in the EPC 
        /// 
        /// 
        public bool ContainsPropertyData(Guid propertyDataId)
        { 
            return this.ExtendedProperties.Contains(propertyDataId);
        } 
 

        ///  
        /// Allows an application to configure the rendering state
        /// associated with this stroke (e.g. outline pen, brush, color,
        /// stylus tip, etc.)
        ///  
        /// 
        /// If the stroke has been deleted, this will return null for 'get'. 
        /// If the stroke has been deleted, the 'set' will no-op. 
        /// 
        /// The drawing attributes associated with the current stroke. 
        public DrawingAttributes DrawingAttributes
        {
            get
            { 
                return _drawingAttributes;
            } 
            set 
            {
                if (value == null) 
                {
                    throw new ArgumentNullException("value");
                }
 
                _drawingAttributes.AttributeChanged -= new PropertyDataChangedEventHandler(DrawingAttributes_Changed);
 
                DrawingAttributesReplacedEventArgs e = 
                    new DrawingAttributesReplacedEventArgs(value, _drawingAttributes);
 
                DrawingAttributes previousDa = _drawingAttributes;
                _drawingAttributes = value;

 
                // If the drawing attributes change involves Width, Height, StylusTipTransform, IgnorePressure, or FitToCurve,
                // we need to force a recaculation of the cached path geometry right after the 
                // DrawingAttributes changed, beforet the events are raised. 
                if (false == DrawingAttributes.GeometricallyEqual(previousDa, _drawingAttributes))
                { 
                    _cachedGeometry = null;
                    // Set the cached bounds to empty, which will force a re-calculation of the _cachedBounds upon next GetBounds call.
                    _cachedBounds = Rect.Empty;
                } 

                _drawingAttributes.AttributeChanged += new PropertyDataChangedEventHandler(DrawingAttributes_Changed); 
                OnDrawingAttributesReplaced(e); 
                OnInvalidated(EventArgs.Empty);
                OnPropertyChanged(DrawingAttributesName); 
            }
        }

        ///  
        /// StylusPoints
        ///  
        public StylusPointCollection StylusPoints 
        {
            get 
            {
                return _stylusPoints;
            }
            set 
            {
                if (null == value) 
                { 
                    throw new ArgumentNullException("value");
                } 
                if (value.Count == 0)
                {
                    //we don't allow this
                    throw new ArgumentException(SR.Get(SRID.InvalidStylusPointCollectionZeroCount)); 
                }
 
                // Force a recaculation of the cached path geometry 
                _cachedGeometry = null;
 
                // Set the cached bounds to empty, which will force a re-calculation of the _cachedBounds upon next GetBounds call.
                _cachedBounds = Rect.Empty;

                StylusPointsReplacedEventArgs e = 
                    new StylusPointsReplacedEventArgs(value, _stylusPoints);
 
                _stylusPoints.Changed -= new EventHandler(StylusPoints_Changed); 
                _stylusPoints.CountGoingToZero -= new CancelEventHandler(StylusPoints_CountGoingToZero);
 
                _stylusPoints = value;

                _stylusPoints.Changed += new EventHandler(StylusPoints_Changed);
                _stylusPoints.CountGoingToZero += new CancelEventHandler(StylusPoints_CountGoingToZero); 

                // fire notification 
                OnStylusPointsReplaced(e); 
                OnInvalidated(EventArgs.Empty);
                OnPropertyChanged(StylusPointsName); 
            }
        }

        /// Event that is fired when a drawing attribute is changed. 
        /// The event listener to add or remove in the listener chain
        public event PropertyDataChangedEventHandler DrawingAttributesChanged; 
 
        /// 
        /// Event that is fired when the DrawingAttributes have been replaced 
        /// 
        public event DrawingAttributesReplacedEventHandler DrawingAttributesReplaced;

        ///  
        /// Notifies listeners whenever the StylusPoints have been replaced
        ///  
        public event StylusPointsReplacedEventHandler StylusPointsReplaced; 

        ///  
        /// Notifies listeners whenever the StylusPoints have been changed
        /// 
        public event EventHandler StylusPointsChanged;
 
        /// 
        /// Notifies listeners whenever a change occurs in the propertyData 
        ///  
        /// PropertyDataChangedEventHandler
        public event PropertyDataChangedEventHandler PropertyDataChanged; 


        /// 
        /// Stroke would raise this event for PacketsChanged, DrawingAttributeChanged, or DrawingAttributeReplaced. 
        /// Renderer would simply listen to this. Stroke developer can raise this event by calling OnInvalidated when
        /// he wants the renderer to repaint. 
        ///  
        public event EventHandler Invalidated;
 
        /// 
        /// INotifyPropertyChanged.PropertyChanged event, explicitly implemented
        /// 
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 
        {
            add { _propertyChanged += value; } 
            remove { _propertyChanged -= value; } 
        }
 
        /// 
        /// Method called on derived classes whenever a drawing attribute
        /// is changed and event listeners must be notified.
        ///  
        /// Information on the drawing attributes that changed
        /// Derived classes should call this method (their base class) 
        /// to ensure that event listeners are notified 
        protected virtual void OnDrawingAttributesChanged(PropertyDataChangedEventArgs e)
        { 
            if (null == e)
            {
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            } 

            if (DrawingAttributesChanged != null) 
            { 
                DrawingAttributesChanged(this, e);
            } 
        }

        /// 
        /// Protected virtual version for developers deriving from InkCanvas. 
        /// This method is what actually throws the event.
        ///  
        /// DrawingAttributesReplacedEventArgs to raise the event with 
        protected virtual void OnDrawingAttributesReplaced(DrawingAttributesReplacedEventArgs e)
        { 
            if (e == null)
            {
                throw new ArgumentNullException("e");
            } 
            if (null != this.DrawingAttributesReplaced)
            { 
                DrawingAttributesReplaced(this, e); 
            }
        } 

        /// 
        /// Method called on derived classes whenever the StylusPoints are replaced
        ///  
        /// EventArgs
        protected virtual void OnStylusPointsReplaced(StylusPointsReplacedEventArgs e) 
        { 
            if (null == e)
            { 
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            }

            if (StylusPointsReplaced != null) 
                StylusPointsReplaced(this, e);
        } 
 
        /// 
        /// Method called on derived classes whenever the StylusPoints are changed 
        /// 
        /// EventArgs
        protected virtual void OnStylusPointsChanged(EventArgs e)
        { 
            if (null == e)
            { 
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull)); 
            }
 
            if (StylusPointsChanged != null)
                StylusPointsChanged(this, e);
        }
 
        /// 
        /// Method called on derived classes whenever a change occurs in 
        /// the PropertyData. 
        /// 
        /// Derived classes should call this method (their base class) 
        /// to ensure that event listeners are notified
        protected virtual void OnPropertyDataChanged(PropertyDataChangedEventArgs e)
        {
            if (null == e) 
            {
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull)); 
            } 

            if (PropertyDataChanged != null) 
            {
                PropertyDataChanged(this, e);
            }
        } 

 
        ///  
        /// Method called on derived classes whenever a stroke needs repaint. Developers who
        /// subclass Stroke and need a repaint could raise Invalidated through this protected virtual 
        /// 
        protected virtual void OnInvalidated(EventArgs e)
        {
            if (null == e) 
            {
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull)); 
            } 

            if (Invalidated != null) 
            {
                Invalidated(this, e);
            }
        } 

        ///  
        /// Method called when a property change occurs to the Stroke 
        /// 
        /// The EventArgs specifying the name of the changed property. 
        /// To follow the guidelines, this method should take a PropertyChangedEventArgs
        /// instance, but every other INotifyPropertyChanged implementation follows this pattern.
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        { 
            if (_propertyChanged != null)
            { 
                _propertyChanged(this, e); 
            }
        } 


        /// 
        /// ExtendedProperties 
        /// 
        internal ExtendedPropertyCollection ExtendedProperties 
        { 
            get
            { 
                if (_extendedProperties == null)
                {
                    _extendedProperties = new ExtendedPropertyCollection();
                } 

                return _extendedProperties; 
            } 
        }
 

        /// 
        /// Clip
        ///  
        /// Fragment markers for clipping
        private StrokeCollection Clip(StrokeFIndices[] cutAt) 
        { 
            System.Diagnostics.Debug.Assert(cutAt != null);
            System.Diagnostics.Debug.Assert(cutAt.Length != 0); 

#if DEBUG
            //
            // Assert there are  no overlaps between multiple StrokeFIndices 
            //
            AssertSortedNoOverlap(cutAt); 
#endif 

            StrokeCollection leftovers = new StrokeCollection(); 
            if (cutAt.Length == 0)
            {
                return leftovers;
            } 

            if ((cutAt.Length == 1) && cutAt[0].IsFull) 
            { 
                leftovers.Add(this.Clone()); //clip and erase always return clones
                return leftovers; 
            }


            StylusPointCollection sourceStylusPoints = this.StylusPoints; 
            if (this.DrawingAttributes.FitToCurve)
            { 
                sourceStylusPoints = this.GetBezierStylusPoints(); 
            }
 
            //
            // Assert the findices are NOT out of range with the packets
            //
            System.Diagnostics.Debug.Assert(false == ((!DoubleUtil.AreClose(cutAt[cutAt.Length - 1].EndFIndex, StrokeFIndices.AfterLast)) && 
                                        Math.Ceiling(cutAt[cutAt.Length - 1].EndFIndex) > sourceStylusPoints.Count - 1));
 
            for (int i = 0; i < cutAt.Length; i++) 
            {
                StrokeFIndices fragment = cutAt[i]; 
                if(DoubleUtil.GreaterThanOrClose(fragment.BeginFIndex, fragment.EndFIndex))
                {
                    //
 
                    System.Diagnostics.Debug.Assert(DoubleUtil.LessThan(fragment.BeginFIndex, fragment.EndFIndex));
                    continue; 
                } 

                Stroke stroke = Copy(sourceStylusPoints, fragment.BeginFIndex, fragment.EndFIndex); 

                // Add the stroke to the output collection
                leftovers.Add(stroke);
            } 

            return leftovers; 
        } 

        ///  
        ///
        /// 
        /// Fragment markers for clipping
        /// Survived fragments of current Stroke as a StrokeCollection 
        private StrokeCollection Erase(StrokeFIndices[] cutAt)
        { 
            System.Diagnostics.Debug.Assert(cutAt != null); 
            System.Diagnostics.Debug.Assert(cutAt.Length != 0);
 
#if DEBUG
            //
            // Assert there are  no overlaps between multiple StrokeFIndices
            // 
            AssertSortedNoOverlap(cutAt);
#endif 
 
            StrokeCollection leftovers = new StrokeCollection();
            // Return an empty collection if the entire stroke it to erase 
            if ((cutAt.Length == 0) || ((cutAt.Length == 1) && cutAt[0].IsFull))
            {
                return leftovers;
            } 

            StylusPointCollection sourceStylusPoints = this.StylusPoints; 
            if (this.DrawingAttributes.FitToCurve) 
            {
                sourceStylusPoints = this.GetBezierStylusPoints(); 
            }

            //
            // Assert the findices are NOT out of range with the packets 
            //
            System.Diagnostics.Debug.Assert(false == ((!DoubleUtil.AreClose(cutAt[cutAt.Length - 1].EndFIndex, StrokeFIndices.AfterLast)) && 
                                        Math.Ceiling(cutAt[cutAt.Length - 1].EndFIndex) > sourceStylusPoints.Count - 1)); 

 
            int i = 0;
            double beginFIndex = StrokeFIndices.BeforeFirst;
            if (cutAt[0].BeginFIndex == StrokeFIndices.BeforeFirst)
            { 
                beginFIndex = cutAt[0].EndFIndex;
                i++; 
            } 
            for (; i < cutAt.Length; i++)
            { 
                StrokeFIndices fragment = cutAt[i];
                if(DoubleUtil.GreaterThanOrClose(beginFIndex, fragment.BeginFIndex))
                {
                    // 

                    System.Diagnostics.Debug.Assert(DoubleUtil.LessThan(beginFIndex, fragment.BeginFIndex)); 
                    continue; 
                }
 

                Stroke stroke = Copy(sourceStylusPoints, beginFIndex, fragment.BeginFIndex);
                // Add the stroke to the output collection
                leftovers.Add(stroke); 

                beginFIndex = fragment.EndFIndex; 
            } 

            if (beginFIndex != StrokeFIndices.AfterLast) 
            {
                Stroke stroke = Copy(sourceStylusPoints, beginFIndex, StrokeFIndices.AfterLast);

                // Add the stroke to the output collection 
                leftovers.Add(stroke);
            } 
 
            return leftovers;
        } 


        /// 
        /// Creates a new stroke from a subset of the points 
        /// 
        private Stroke Copy(StylusPointCollection sourceStylusPoints, double beginFIndex, double endFIndex) 
        { 
            Debug.Assert(sourceStylusPoints != null);
            // 
            // get the floor and ceiling to copy from, we'll adjust the ends below
            //
            int beginIndex =
                (DoubleUtil.AreClose(StrokeFIndices.BeforeFirst, beginFIndex)) 
                    ? 0 : (int)Math.Floor(beginFIndex);
 
            int endIndex = 
                (DoubleUtil.AreClose(StrokeFIndices.AfterLast, endFIndex))
                    ? (sourceStylusPoints.Count - 1) : (int)Math.Ceiling(endFIndex); 

            int pointCount = endIndex - beginIndex + 1;
            System.Diagnostics.Debug.Assert(pointCount >= 1);
 
            StylusPointCollection stylusPoints =
                new StylusPointCollection(this.StylusPoints.Description, pointCount); 
 
            //
            // copy the data from the floor of beginIndex to the ceiling 
            //
            for (int i = 0; i < pointCount; i++)
            {
                System.Diagnostics.Debug.Assert(sourceStylusPoints.Count > i + beginIndex); 
                StylusPoint stylusPoint = sourceStylusPoints[i + beginIndex];
                stylusPoints.Add(stylusPoint); 
            } 
            System.Diagnostics.Debug.Assert(stylusPoints.Count == pointCount);
 
            //
            // at this point, the stroke has been reduced to one with n number of points
            // so we need to adjust the fIndices based on the new point data
            // 
            // for example, in a stroke with 4 points:
            // 0, 1, 2, 3 
            // 
            // if the fIndexes passed 1.1 and 2.7
            // at this point beginIndex is 1 and endIndex is 3 
            //
            // now that we've copied the stroke points 1, 2 and 3, we need to
            // adjust beginFIndex to .1 and endFIndex to 1.7
            // 
            if (!DoubleUtil.AreClose(beginFIndex, StrokeFIndices.BeforeFirst))
            { 
                beginFIndex = beginFIndex - beginIndex; 
            }
            if (!DoubleUtil.AreClose(endFIndex, StrokeFIndices.AfterLast)) 
            {
                endFIndex = endFIndex - beginIndex;
            }
 
            if (stylusPoints.Count > 1)
            { 
 
                Point begPoint = (Point)stylusPoints[0];
                Point endPoint = (Point)stylusPoints[stylusPoints.Count - 1]; 

                // Adjust the last point to fragment.EndFIndex.
                if ((!DoubleUtil.AreClose(endFIndex, StrokeFIndices.AfterLast)) && !DoubleUtil.AreClose(endIndex, endFIndex))
                { 
                    //
                    // for 1.7, we need to get .3, because that is the distance 
                    // we need to back up between the third point and the second 
                    //
                    // so this would be .3 = 2 - 1.7 
                    double ceiling = Math.Ceiling(endFIndex);
                    double fraction = ceiling - endFIndex;

                    endPoint = GetIntermediatePoint(stylusPoints[stylusPoints.Count - 1], 
                                                    stylusPoints[stylusPoints.Count - 2],
                                                    fraction); 
 
                }
 
                // Adjust the first point to fragment.BeginFIndex.
                if ((!DoubleUtil.AreClose(beginFIndex, StrokeFIndices.BeforeFirst)) && !DoubleUtil.AreClose(beginIndex, beginFIndex))
                {
                    begPoint = GetIntermediatePoint(stylusPoints[0], 
                                                    stylusPoints[1],
                                                    beginFIndex); 
 

                } 

                //
                // now set the end points
                // 
                StylusPoint tempEnd = stylusPoints[stylusPoints.Count - 1];
                tempEnd.X = endPoint.X; 
                tempEnd.Y = endPoint.Y; 
                stylusPoints[stylusPoints.Count - 1] = tempEnd;
 
                StylusPoint tempBegin = stylusPoints[0];
                tempBegin.X = begPoint.X;
                tempBegin.Y = begPoint.Y;
                stylusPoints[0] = tempBegin; 
            }
 
            Stroke stroke = null; 
            try
            { 
                //
                // set a flag that tells clone not to clone the StylusPoints
                // we do this in a try finally so we alway reset our state
                // even if Clone (which is virtual) throws 
                //
                _cloneStylusPoints = false; 
                stroke = this.Clone(); 
                if (stroke.DrawingAttributes.FitToCurve)
                { 
                    //
                    // we're using the beziered points for the new data,
                    // FitToCurve needs to be false to prevent re-bezier.
                    // 
                    stroke.DrawingAttributes.FitToCurve = false;
                } 
 
                //this will reset the cachedGeometry and cachedBounds
                stroke.StylusPoints = stylusPoints; 
            }
            finally
            {
                _cloneStylusPoints = true; 
            }
 
            return stroke; 
        }
 
        /// 
        /// Private helper that will generate a new point between two points at an findex
        /// 
        private Point GetIntermediatePoint(StylusPoint p1, StylusPoint p2, double findex) 
        {
            double xDistance = p2.X - p1.X; 
            double yDistance = p2.Y - p1.Y; 

            double xFDistance = xDistance * findex; 
            double yFDistance = yDistance * findex;

            return new Point(p1.X + xFDistance, p1.Y + yFDistance);
        } 

 
#if DEBUG 
        /// 
        /// Helper method used to validate that the strokefindices in the array 
        /// are sorted and there are no overlaps
        /// 
        /// fragments
        private void AssertSortedNoOverlap(StrokeFIndices[] fragments) 
        {
            if (fragments.Length == 0) 
            { 
                return;
            } 
            if (fragments.Length == 1)
            {
                System.Diagnostics.Debug.Assert(IsValidStrokeFIndices(fragments[0]));
                return; 
            }
            double current = StrokeFIndices.BeforeFirst; 
            for (int x = 0; x < fragments.Length; x++) 
            {
                if (fragments[x].BeginFIndex <= current) 
                {
                    //
                    // when x == 0, we're just starting, any value is valid
                    // 
                    System.Diagnostics.Debug.Assert(x == 0);
                } 
                current = fragments[x].BeginFIndex; 
                System.Diagnostics.Debug.Assert(IsValidStrokeFIndices(fragments[x]) && fragments[x].EndFIndex > current);
                current = fragments[x].EndFIndex; 
            }
        }

        private bool IsValidStrokeFIndices(StrokeFIndices findex) 
        {
            return (!double.IsNaN(findex.BeginFIndex) && !double.IsNaN(findex.EndFIndex) && findex.BeginFIndex < findex.EndFIndex); 
        } 

#endif 


        /// 
        /// Method called whenever the Stroke's drawing attributes are changed. 
        /// This method will trigger an event for any listeners interested in
        /// drawing attributes. 
        ///  
        /// The Drawing Attributes object that was changed
        /// More data about the change that occurred 
        private void DrawingAttributes_Changed(object sender, PropertyDataChangedEventArgs e)
        {
            // set Geometry flag to be dirty if the DA change will cause change in geometry
            if (DrawingAttributes.IsGeometricalDaGuid(e.PropertyGuid) == true) 
            {
                _cachedGeometry = null; 
                // Set the cached bounds to empty, which will force a re-calculation of the _cachedBounds upon next GetBounds call. 
                _cachedBounds = Rect.Empty;
            } 

            OnDrawingAttributesChanged(e);
            if (!_delayRaiseInvalidated)
            { 
                //when Stroke.Transform(Matrix, bool) is called, we don't raise invalidated from
                //here, but rather from the Stroke.Transform method. 
                OnInvalidated(EventArgs.Empty); 
            }
        } 

        /// 
        /// Method called whenever the Stroke's StylusPoints are changed.
        /// This method will trigger an event for any listeners interested in 
        /// Invalidate
        ///  
        /// The StylusPoints object that was changed 
        /// event args
        private void StylusPoints_Changed(object sender, EventArgs e) 
        {
            _cachedGeometry = null;
            _cachedBounds = Rect.Empty;
 
            OnStylusPointsChanged(EventArgs.Empty);
            if (!_delayRaiseInvalidated) 
            { 
                //when Stroke.Transform(Matrix, bool) is called, we don't raise invalidated from
                //here, but rather from the Stroke.Transform method. 
                OnInvalidated(EventArgs.Empty);
            }
        }
 
        /// 
        /// Private method called when StylusPoints are going to zero 
        ///  
        /// The StylusPoints object that is about to go to zero count
        /// event args 
        private void StylusPoints_CountGoingToZero(object sender, CancelEventArgs e)
        {
            e.Cancel = true;
            //StylusPoints will raise the exception 
        }
 
        private void OnPropertyChanged(string propertyName) 
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 
        }

            // Custom attributes associated with this stroke
        private ExtendedPropertyCollection _extendedProperties = null; 

            // Drawing attributes associated with this stroke 
        private DrawingAttributes _drawingAttributes = null; 

        private StylusPointCollection _stylusPoints = null; 

    }

        //internal helper to determine if a matix contains invalid values 
    internal static class MatrixHelper
    { 
        //returns true if any member is NaN 
        internal static bool ContainsNaN(Matrix matrix)
        { 
            if (Double.IsNaN(matrix.M11) ||
                Double.IsNaN(matrix.M12) ||
                Double.IsNaN(matrix.M21) ||
                Double.IsNaN(matrix.M22) || 
                Double.IsNaN(matrix.OffsetX) ||
                Double.IsNaN(matrix.OffsetY)) 
            { 
                return true;
            } 
            return false;
        }

        //returns true if any member is negative or positive infinity 
        internal static bool ContainsInfinity(Matrix matrix)
        { 
            if (Double.IsInfinity(matrix.M11) || 
                Double.IsInfinity(matrix.M12) ||
                Double.IsInfinity(matrix.M21) || 
                Double.IsInfinity(matrix.M22) ||
                Double.IsInfinity(matrix.OffsetX) ||
                Double.IsInfinity(matrix.OffsetY))
            { 
                return true;
            } 
            return false; 
        }
    } 

    /// 
    /// Helper for dealing with IEnumerable of Points
    ///  
    internal static class IEnumerablePointHelper
    { 
        ///  
        /// Returns the count of an IEumerable of Points by trying to cast
        /// to an ICollection of Points 
        /// 
        internal static int GetCount(IEnumerable ienum)
        {
            Debug.Assert(ienum != null); 
            ICollection icol = ienum as ICollection;
            if (icol != null) 
            { 
                return icol.Count;
            } 
            int count = 0;
            foreach (Point point in ienum)
            {
                count++; 
            }
            return count; 
        } 

        ///  
        /// Returns a Point[] for a given IEnumerable of Points.
        /// 
        internal static Point[] GetPointArray(IEnumerable ienum)
        { 
            Debug.Assert(ienum != null);
            Point[] points = ienum as Point[]; 
            if (points != null) 
            {
                return points; 
            }

            //
            // fall back to creating an array 
            //
            points = new Point[GetCount(ienum)]; 
            int index = 0; 
            foreach (Point point in ienum)
            { 
                points[index++] = point;
            }
            return points;
        } 
    }
 
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK