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(ListbezierPoints) { 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(IEnumerableienum) { 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(IEnumerableienum) { 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
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- XsdDataContractExporter.cs
- ActivityTypeCodeDomSerializer.cs
- MessageBox.cs
- SiteMembershipCondition.cs
- RedirectionProxy.cs
- SoapFormatter.cs
- ImageSourceValueSerializer.cs
- ErrorRuntimeConfig.cs
- WorkerRequest.cs
- SqlError.cs
- XmlAtomicValue.cs
- DataGridViewRowCancelEventArgs.cs
- SafeTokenHandle.cs
- ObjectRef.cs
- DataDocumentXPathNavigator.cs
- OrderedDictionary.cs
- _UriTypeConverter.cs
- _Connection.cs
- TreeViewHitTestInfo.cs
- TableTextElementCollectionInternal.cs
- CompensationHandlingFilter.cs
- SubstitutionList.cs
- TraceListeners.cs
- AnonymousIdentificationModule.cs
- ReliableRequestSessionChannel.cs
- XmlSchemaException.cs
- Hash.cs
- XamlSerializerUtil.cs
- WindowsIPAddress.cs
- _ContextAwareResult.cs
- NestPullup.cs
- MappedMetaModel.cs
- HMAC.cs
- SmiXetterAccessMap.cs
- Metafile.cs
- DataGridViewDataErrorEventArgs.cs
- SecurityManager.cs
- DesignTimeResourceProviderFactoryAttribute.cs
- ApplicationDirectory.cs
- LiteralDesigner.cs
- FileDialog_Vista_Interop.cs
- ContainerParaClient.cs
- ContentPresenter.cs
- RequiredFieldValidator.cs
- HebrewCalendar.cs
- StrongNameIdentityPermission.cs
- ImpersonationContext.cs
- WorkflowElementDialogWindow.xaml.cs
- TimelineGroup.cs
- HtmlShimManager.cs
- storepermissionattribute.cs
- PropertyDescriptorCollection.cs
- SafeCertificateContext.cs
- DataQuery.cs
- Html32TextWriter.cs
- TableLayoutSettings.cs
- WinCategoryAttribute.cs
- UiaCoreTypesApi.cs
- ExceptionHandlersDesigner.cs
- WindowsStatusBar.cs
- ClosureBinding.cs
- XmlEventCache.cs
- COM2ExtendedUITypeEditor.cs
- Listen.cs
- ComponentChangedEvent.cs
- DataGridViewCellCancelEventArgs.cs
- Int64AnimationUsingKeyFrames.cs
- milrender.cs
- BaseDataListPage.cs
- StylusButtonEventArgs.cs
- SByteConverter.cs
- ComboBox.cs
- XmlSchemaObjectTable.cs
- CompositeTypefaceMetrics.cs
- ByteStreamMessageEncodingElement.cs
- WindowsScrollBarBits.cs
- FileIOPermission.cs
- CompilationLock.cs
- SegmentInfo.cs
- Rect.cs
- Keywords.cs
- ServiceSettingsResponseInfo.cs
- GuidelineCollection.cs
- IgnoreSection.cs
- DesignerOptionService.cs
- RuntimeCompatibilityAttribute.cs
- LocalizationComments.cs
- OdbcTransaction.cs
- ListItemCollection.cs
- hresults.cs
- SqlConnectionHelper.cs
- BindingElementCollection.cs
- BooleanStorage.cs
- Root.cs
- Int16AnimationBase.cs
- WebPartChrome.cs
- DefaultMemberAttribute.cs
- UrlMappingsModule.cs
- GregorianCalendar.cs
- SymmetricAlgorithm.cs