Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / System / Windows / Ink / Stroke2.cs / 1 / Stroke2.cs
//#define DEBUG_RENDERING_FEEDBACK
//------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
using MS.Utility;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MS.Internal;
using MS.Internal.Ink;
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
using MS.Internal.PresentationCore;
// Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET
namespace System.Windows.Ink
{
///
/// The hit-testing API of Stroke
///
public partial class Stroke : INotifyPropertyChanged
{
#region Public APIs
#region Public Methods
///
/// Computes the bounds of the stroke in the default rendering context
///
///
public virtual Rect GetBounds()
{
if (_cachedBounds.IsEmpty)
{
StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, this.DrawingAttributes);
for (int i = 0; i < iterator.Count; i++)
{
StrokeNode strokeNode = iterator[i];
_cachedBounds.Union(strokeNode.GetBounds());
}
}
return _cachedBounds;
}
///
/// Render the Stroke under the specified DrawingContext. The draw method is a
/// batch operationg that uses the rendering methods exposed off of DrawingContext
///
///
public void Draw(DrawingContext context)
{
if (null == context)
{
throw new System.ArgumentNullException("context");
}
//our code never calls this public API so we can assume that opacity
//has not been set up
//call our public Draw method with the strokes.DA
this.Draw(context, this.DrawingAttributes);
}
///
/// Render the StrokeCollection under the specified DrawingContext. This draw method uses the
/// passing in drawing attribute to override that on the stroke.
///
///
///
public void Draw(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (null == drawingContext)
{
throw new System.ArgumentNullException("context");
}
if (null == drawingAttributes)
{
throw new System.ArgumentNullException("drawingAttributes");
}
// context.VerifyAccess();
//our code never calls this public API so we can assume that opacity
//has not been set up
if (drawingAttributes.IsHighlighter)
{
drawingContext.PushOpacity(StrokeRenderer.HighlighterOpacity);
try
{
this.DrawInternal(drawingContext, StrokeRenderer.GetHighlighterAttributes(this, this.DrawingAttributes), false);
}
finally
{
drawingContext.Pop();
}
}
else
{
this.DrawInternal(drawingContext, drawingAttributes, false);
}
}
///
/// Clip with rect. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// A Rect to clip with
/// The after-clipping strokes.
public StrokeCollection GetClipResult(Rect bounds)
{
return this.GetClipResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft });
}
///
/// Clip with lasso. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// The lasso points to clip with
/// The after-clipping strokes
public StrokeCollection GetClipResult(IEnumerable lassoPoints)
{
// Check the input parameters
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if (IEnumerablePointHelper.GetCount(lassoPoints) == 0)
{
throw new ArgumentException(SR.Get(SRID.EmptyArray));
}
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
return this.Clip(this.HitTest(lasso));
}
///
/// Erase with a rect. Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation.
///
/// A Rect to clip with
/// The after-erasing strokes
public StrokeCollection GetEraseResult(Rect bounds)
{
return this.GetEraseResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft });
}
///
/// Erase with lasso points.
///
/// Lasso points to erase with
/// The after-erasing strokes
public StrokeCollection GetEraseResult(IEnumerable lassoPoints)
{
// Check the input parameters
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if (IEnumerablePointHelper.GetCount(lassoPoints) == 0)
{
throw new ArgumentException(SR.Get(SRID.EmptyArray));
}
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
return this.Erase(this.HitTest(lasso));
}
///
/// Erase with an eraser with passed in shape
///
/// The path to erase
/// Shape of the eraser
///
public StrokeCollection GetEraseResult(IEnumerable eraserPath, StylusShape eraserShape)
{
// Check the input parameters
if (eraserShape == null)
{
throw new System.ArgumentNullException("eraserShape");
}
if (eraserPath == null)
{
throw new System.ArgumentNullException("eraserPath");
}
return this.Erase(this.EraseTest(eraserPath, eraserShape));
}
///
/// Tap-hit. Hit tests with a point. Internally does Stroke.HitTest(Point, 1pxlRectShape).
///
/// The location to do the hitest
/// True is this stroke is hit, false otherwise
public bool HitTest(Point point)
{
return HitTest(new Point[]{point}, new EllipseStylusShape(TapHitPointSize, TapHitPointSize, TapHitRotation));
}
///
/// Tap-hit. Hit tests with a point.
///
/// The location to do the hittest
/// diameter of the tip
/// true if hit, false otherwise
public bool HitTest(Point point, double diameter)
{
if (Double.IsNaN(diameter) || diameter < DrawingAttributes.MinWidth || diameter > DrawingAttributes.MaxWidth)
{
throw new ArgumentOutOfRangeException("diameter", SR.Get(SRID.InvalidDiameter));
}
return HitTest(new Point[]{point}, new EllipseStylusShape(diameter, diameter, TapHitRotation));
}
///
/// Check whether a certain percentage of the stroke is within the Rect passed in.
///
///
///
///
public bool HitTest(Rect bounds, int percentageWithinBounds)
{
if ((percentageWithinBounds < 0) || (percentageWithinBounds > 100))
{
throw new System.ArgumentOutOfRangeException("percentageWithinBounds");
}
if (percentageWithinBounds == 0)
{
return true;
}
StrokeInfo strokeInfo = null;
try
{
strokeInfo = new StrokeInfo(this);
StylusPointCollection stylusPoints = strokeInfo.StylusPoints;
double target = strokeInfo.TotalWeight * percentageWithinBounds / 100.0f - PercentageTolerance;
for (int i = 0; i < stylusPoints.Count; i++)
{
if (true == bounds.Contains((Point)stylusPoints[i]))
{
target -= strokeInfo.GetPointWeight(i);
if (DoubleUtil.LessThanOrClose(target, 0d))
{
return true;
}
}
}
return false;
}
finally
{
if (strokeInfo != null)
{
//detach from event handlers, or else we leak.
strokeInfo.Detach();
}
}
}
///
/// Check whether a certain percentage of the stroke is within the lasso
///
///
///
///
public bool HitTest(IEnumerable lassoPoints, int percentageWithinLasso)
{
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if ((percentageWithinLasso < 0) || (percentageWithinLasso > 100))
{
throw new System.ArgumentOutOfRangeException("percentageWithinLasso");
}
if (percentageWithinLasso == 0)
{
return true;
}
StrokeInfo strokeInfo = null;
try
{
strokeInfo = new StrokeInfo(this);
StylusPointCollection stylusPoints = strokeInfo.StylusPoints;
double target = strokeInfo.TotalWeight * percentageWithinLasso / 100.0f - PercentageTolerance;
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
for (int i = 0; i < stylusPoints.Count; i++)
{
if (true == lasso.Contains((Point)stylusPoints[i]))
{
target -= strokeInfo.GetPointWeight(i);
if (DoubleUtil.LessThan(target, 0f))
{
return true;
}
}
}
return false;
}
finally
{
if (strokeInfo != null)
{
//detach from event handlers, or else we leak.
strokeInfo.Detach();
}
}
}
///
///
///
///
///
///
public bool HitTest(IEnumerable path, StylusShape stylusShape)
{
// Check the input parameters
if (path == null)
{
throw new System.ArgumentNullException("path");
}
if (stylusShape == null)
{
throw new System.ArgumentNullException("stylusShape");
}
if (IEnumerablePointHelper.GetCount(path) == 0)
{
return false;
}
ErasingStroke erasingStroke = new ErasingStroke(stylusShape);
erasingStroke.MoveTo(path);
Rect erasingBounds = erasingStroke.Bounds;
if (erasingBounds.IsEmpty)
{
return false;
}
if (erasingBounds.IntersectsWith(this.GetBounds()))
{
return erasingStroke.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes));
}
return false;
}
#endregion
#endregion
#region Protected APIs
///
/// The core functionality to draw a stroke. The function can be called from the following code paths.
/// i) From StrokeVisual.OnRender
/// a. Highlighter strokes have been grouped and the correct opacity has been set on the container visual.
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow can be true, i.e., Selected stroke is drawn as hollow
/// ii) From StrokeCollection.Draw.
/// a. Highlighter strokes have been grouped and the correct opacity has been pushed.
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow
/// iii) From Stroke.Draw
/// a. The correct opacity has been pushed for a highlighter stroke
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow
/// We need to document the following:
/// 1) our default implementation so developers can see what we've done here -
/// including how we handle IsHollow
/// 2) the fact that opacity has already been set up correctly for the call.
/// 3) that developers should not call base.DrawCore if they override this
///
/// DrawingContext to draw on
/// DrawingAttributes to draw with
protected virtual void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (null == drawingContext)
{
throw new System.ArgumentNullException("drawingContext");
}
if (null == drawingAttributes)
{
throw new System.ArgumentNullException("drawingAttributes");
}
if (_drawAsHollow == true)
{
// Draw as hollow. Our profiler result shows that the two-pass-rendering approach is about 5 times
// faster that using GetOutlinePathGeometry.
// also, the minimum display size for selected ink is our default width / height
Matrix innerTransform, outerTransform;
DrawingAttributes selectedDA = drawingAttributes.Clone();
selectedDA.Height = Math.Max(selectedDA.Height, DrawingAttributes.DefaultHeight);
selectedDA.Width = Math.Max(selectedDA.Width, DrawingAttributes.DefaultWidth);
CalcHollowTransforms(selectedDA, out innerTransform, out outerTransform);
// First pass drawing. Use drawingAttributes.Color to create a solid color brush. The stroke will be drawn as
// 1 avalon-unit higher and wider (HollowLineSize = 1.0f)
selectedDA.StylusTipTransform = outerTransform;
SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
brush.Freeze();
drawingContext.DrawGeometry(brush, null, GetGeometry(selectedDA));
//Second pass drawing with a white color brush. The stroke will be drawn as
// 1 avalon-unit shorter and narrower (HollowLineSize = 1.0f) if the actual-width/height (considering StylusTipTransform)
// is larger than HollowLineSize. Otherwise the same size stroke is drawn.
selectedDA.StylusTipTransform = innerTransform;
drawingContext.DrawGeometry(Brushes.White, null, GetGeometry(selectedDA));
}
else
{
#if DEBUG_RENDERING_FEEDBACK
//render debug feedback?
Guid guid = new Guid("52053C24-CBDD-4547-AAA1-DEFEBF7FD1E1");
if (this.ContainsPropertyData(guid))
{
double thickness = (double)this.GetPropertyData(guid);
//first, draw the outline of the stroke
drawingContext.DrawGeometry(null,
new Pen(Brushes.Black, thickness),
GetGeometry());
Geometry g2;
Rect b2;
//next, overlay the connecting quad points
StrokeRenderer.CalcGeometryAndBounds(StrokeNodeIterator.GetIterator(this, drawingAttributes),
drawingAttributes,
drawingContext, thickness, true,
true, //calc bounds
out g2,
out b2);
}
else
{
#endif
SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
brush.Freeze();
drawingContext.DrawGeometry(brush, null, GetGeometry(drawingAttributes));
#if DEBUG_RENDERING_FEEDBACK
}
#endif
}
}
///
/// Returns the Geometry of this stroke.
///
///
public Geometry GetGeometry()
{
return GetGeometry(this.DrawingAttributes);
}
///
/// Get the Geometry of the Stroke
///
///
///
public Geometry GetGeometry(DrawingAttributes drawingAttributes)
{
if (drawingAttributes == null)
{
throw new ArgumentNullException("drawingAttributes");
}
bool geometricallyEqual = DrawingAttributes.GeometricallyEqual(drawingAttributes, this.DrawingAttributes);
// need to recalculate the PathGemetry if the DA passed in is "geometrically" different from
// this DA, or if the cached PathGeometry is dirty.
if (false == geometricallyEqual || (true == geometricallyEqual && null == _cachedGeometry))
{
//Recalculate _pathGeometry;
StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, drawingAttributes);
Geometry geometry;
Rect bounds;
StrokeRenderer.CalcGeometryAndBounds(iterator,
drawingAttributes,
#if DEBUG_RENDERING_FEEDBACK
null, 0d, false,
#endif
true, //calc bounds
out geometry,
out bounds);
// return the calculated value directly. We cannot cache the result since the DA passed in
// is "geometrically" different from this.DrawingAttributes.
if (false == geometricallyEqual)
{
return geometry;
}
// Cache the value and set _isPathGeometryDirty to false;
SetGeometry(geometry);
SetBounds(bounds);
return geometry;
}
// return a ref to our _cachedGeometry
System.Diagnostics.Debug.Assert(_cachedGeometry != null && _cachedGeometry.IsFrozen);
return _cachedGeometry;
}
#endregion
#region Internal APIs
///
/// our code - StrokeVisual.OnRender and StrokeCollection.Draw - always calls this
/// so we can assume the correct opacity has already been pushed on dc. The flag drawAsHollow is set
/// to true when this function is called from Renderer and this.IsSelected == true.
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal void DrawInternal(DrawingContext dc, DrawingAttributes DrawingAttributes, bool drawAsHollow)
{
if (drawAsHollow == true)
{
// The Stroke.DrawCore may be overriden in the 3rd party code.
// The out-side code could throw exception. We use try/finally block to protect our status.
try
{
_drawAsHollow = true; // temporarily set the flag to be true
this.DrawCore(dc, DrawingAttributes);
}
finally
{
_drawAsHollow = false; // reset _drawAsHollow
}
}
else
{
// IsSelected can be true or false, but _drawAsHollow must be false
System.Diagnostics.Debug.Assert(false == _drawAsHollow);
this.DrawCore(dc, DrawingAttributes);
}
}
///
/// Used by Inkcanvas to draw selected stroke as hollow.
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
// Raise Invalidated event. This will cause Renderer to repaint and call back DrawCore
OnInvalidated(EventArgs.Empty);
}
}
}
///
/// Set the path geometry
///
internal void SetGeometry(Geometry geometry)
{
System.Diagnostics.Debug.Assert(geometry != null);
_cachedGeometry = geometry;
}
///
/// Set the bounds
///
internal void SetBounds(Rect newBounds)
{
System.Diagnostics.Debug.Assert(newBounds.IsEmpty == false);
_cachedBounds = newBounds;
}
/// Hit tests all segments within a contour generated with shape and path
///
///
/// StrokeIntersection array for these segments
internal StrokeIntersection[] EraseTest(IEnumerable path, StylusShape shape)
{
System.Diagnostics.Debug.Assert(shape != null);
System.Diagnostics.Debug.Assert(path != null);
if (IEnumerablePointHelper.GetCount(path) == 0)
{
return new StrokeIntersection[0];
}
ErasingStroke erasingStroke = new ErasingStroke(shape, path);
List intersections = new List();
erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes), intersections);
return intersections.ToArray();
}
///
/// Hit tests all segments within the lasso loops
///
/// a StrokeIntersection array for these segments
internal StrokeIntersection[] HitTest(Lasso lasso)
{
// Check the input parameters
System.Diagnostics.Debug.Assert(lasso != null);
if (lasso.IsEmpty)
{
return new StrokeIntersection[0];
}
// The following will check whether all the points are within the lasso.
// If yes, return the whole stroke as being hit.
if (!lasso.Bounds.IntersectsWith(this.GetBounds()))
{
return new StrokeIntersection[0];
}
return lasso.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes));
}
///
/// Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation.
///
/// Array of intersections indicating the erasing locations
///
internal StrokeCollection Erase(StrokeIntersection[] cutAt)
{
System.Diagnostics.Debug.Assert(cutAt != null);
// Nothing needs to be erased
if(cutAt.Length == 0)
{
StrokeCollection strokes = new StrokeCollection();
strokes.Add(this.Clone()); //clip and erase always return clones for this condition
return strokes;
}
// Two assertions are deferred to the private erase function to avoid duplicate code.
// 1. AssertSortedNoOverlap
// 2. Check whether the insegments are out of range with the packets
StrokeFIndices[] hitSegments = StrokeIntersection.GetHitSegments(cutAt);
return this.Erase(hitSegments);
}
///
/// Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// Array of intersections indicating the clipping locations
/// The resulting StrokeCollection
internal StrokeCollection Clip(StrokeIntersection[] cutAt)
{
System.Diagnostics.Debug.Assert(cutAt != null);
// Nothing is inside
if (cutAt.Length == 0)
{
return new StrokeCollection();
}
// Get the "in-segments"
StrokeFIndices[] inSegments = StrokeIntersection.GetInSegments(cutAt);
// For special case like cutAt is {BF, AL, BF, 0.67}, the inSegments are empty
if (inSegments.Length == 0)
{
return new StrokeCollection();
}
// Two other validations are deferred to the private clip function to avoid duplicate code.
// 1. ValidateSortedNoOverlap
// 2. Check whether the insegments are out of range with the packets
return this.Clip(inSegments);
}
internal double TapHitPointSize = 1.0;
internal double TapHitRotation = 0;
#endregion
#region Private APIs
///
/// Calculate the two transforms for two-pass rendering used to draw as hollow. The resulting outerTransform will make the
/// first-pass-rendering 1 avalon-unit wider/heigher. The resulting innerTransform will make the second-pass-rendering 1 avalon-unit
/// narrower/shorter.
///
private static void CalcHollowTransforms(DrawingAttributes originalDa, out Matrix innerTransform, out Matrix outerTransform)
{
System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetX) && DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetY));
innerTransform = outerTransform = Matrix.Identity;
Point w = originalDa.StylusTipTransform.Transform(new Point(originalDa.Width, 0));
Point h = originalDa.StylusTipTransform.Transform(new Point(0, originalDa.Height));
// the newWidth and newHeight are the actual width/height of the stylus shape considering StylusTipTransform.
// The assumption is TylusTipTransform has no translation component.
double newWidth = Math.Sqrt(w.X * w.X + w.Y * w.Y);
double newHeight = Math.Sqrt(h.X * h.X + h.Y * h.Y);
double xTransform = DoubleUtil.GreaterThan(newWidth, HollowLineSize) ?
(newWidth - HollowLineSize) / newWidth : 1.0f;
double yTransform = DoubleUtil.GreaterThan(newHeight, HollowLineSize) ?
(newHeight - HollowLineSize) / newHeight : 1.0f;
innerTransform.Scale(xTransform, yTransform);
innerTransform *= originalDa.StylusTipTransform;
outerTransform.Scale((newWidth + HollowLineSize) / newWidth,
(newHeight + HollowLineSize) / newHeight);
outerTransform *= originalDa.StylusTipTransform;
}
#region Private fields
private Geometry _cachedGeometry = null;
private bool _isSelected = false;
private bool _drawAsHollow = false;
private bool _cloneStylusPoints = true;
private bool _delayRaiseInvalidated = false;
private static readonly double HollowLineSize = 1.0f;
private Rect _cachedBounds = Rect.Empty;
// The private PropertyChanged event
private PropertyChangedEventHandler _propertyChanged;
private const string DrawingAttributesName = "DrawingAttributes";
private const string StylusPointsName = "StylusPoints";
#endregion
internal static readonly double PercentageTolerance = 0.0001d;
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//#define DEBUG_RENDERING_FEEDBACK
//------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
using MS.Utility;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MS.Internal;
using MS.Internal.Ink;
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
using MS.Internal.PresentationCore;
// Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET
namespace System.Windows.Ink
{
///
/// The hit-testing API of Stroke
///
public partial class Stroke : INotifyPropertyChanged
{
#region Public APIs
#region Public Methods
///
/// Computes the bounds of the stroke in the default rendering context
///
///
public virtual Rect GetBounds()
{
if (_cachedBounds.IsEmpty)
{
StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, this.DrawingAttributes);
for (int i = 0; i < iterator.Count; i++)
{
StrokeNode strokeNode = iterator[i];
_cachedBounds.Union(strokeNode.GetBounds());
}
}
return _cachedBounds;
}
///
/// Render the Stroke under the specified DrawingContext. The draw method is a
/// batch operationg that uses the rendering methods exposed off of DrawingContext
///
///
public void Draw(DrawingContext context)
{
if (null == context)
{
throw new System.ArgumentNullException("context");
}
//our code never calls this public API so we can assume that opacity
//has not been set up
//call our public Draw method with the strokes.DA
this.Draw(context, this.DrawingAttributes);
}
///
/// Render the StrokeCollection under the specified DrawingContext. This draw method uses the
/// passing in drawing attribute to override that on the stroke.
///
///
///
public void Draw(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (null == drawingContext)
{
throw new System.ArgumentNullException("context");
}
if (null == drawingAttributes)
{
throw new System.ArgumentNullException("drawingAttributes");
}
// context.VerifyAccess();
//our code never calls this public API so we can assume that opacity
//has not been set up
if (drawingAttributes.IsHighlighter)
{
drawingContext.PushOpacity(StrokeRenderer.HighlighterOpacity);
try
{
this.DrawInternal(drawingContext, StrokeRenderer.GetHighlighterAttributes(this, this.DrawingAttributes), false);
}
finally
{
drawingContext.Pop();
}
}
else
{
this.DrawInternal(drawingContext, drawingAttributes, false);
}
}
///
/// Clip with rect. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// A Rect to clip with
/// The after-clipping strokes.
public StrokeCollection GetClipResult(Rect bounds)
{
return this.GetClipResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft });
}
///
/// Clip with lasso. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// The lasso points to clip with
/// The after-clipping strokes
public StrokeCollection GetClipResult(IEnumerable lassoPoints)
{
// Check the input parameters
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if (IEnumerablePointHelper.GetCount(lassoPoints) == 0)
{
throw new ArgumentException(SR.Get(SRID.EmptyArray));
}
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
return this.Clip(this.HitTest(lasso));
}
///
/// Erase with a rect. Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation.
///
/// A Rect to clip with
/// The after-erasing strokes
public StrokeCollection GetEraseResult(Rect bounds)
{
return this.GetEraseResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft });
}
///
/// Erase with lasso points.
///
/// Lasso points to erase with
/// The after-erasing strokes
public StrokeCollection GetEraseResult(IEnumerable lassoPoints)
{
// Check the input parameters
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if (IEnumerablePointHelper.GetCount(lassoPoints) == 0)
{
throw new ArgumentException(SR.Get(SRID.EmptyArray));
}
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
return this.Erase(this.HitTest(lasso));
}
///
/// Erase with an eraser with passed in shape
///
/// The path to erase
/// Shape of the eraser
///
public StrokeCollection GetEraseResult(IEnumerable eraserPath, StylusShape eraserShape)
{
// Check the input parameters
if (eraserShape == null)
{
throw new System.ArgumentNullException("eraserShape");
}
if (eraserPath == null)
{
throw new System.ArgumentNullException("eraserPath");
}
return this.Erase(this.EraseTest(eraserPath, eraserShape));
}
///
/// Tap-hit. Hit tests with a point. Internally does Stroke.HitTest(Point, 1pxlRectShape).
///
/// The location to do the hitest
/// True is this stroke is hit, false otherwise
public bool HitTest(Point point)
{
return HitTest(new Point[]{point}, new EllipseStylusShape(TapHitPointSize, TapHitPointSize, TapHitRotation));
}
///
/// Tap-hit. Hit tests with a point.
///
/// The location to do the hittest
/// diameter of the tip
/// true if hit, false otherwise
public bool HitTest(Point point, double diameter)
{
if (Double.IsNaN(diameter) || diameter < DrawingAttributes.MinWidth || diameter > DrawingAttributes.MaxWidth)
{
throw new ArgumentOutOfRangeException("diameter", SR.Get(SRID.InvalidDiameter));
}
return HitTest(new Point[]{point}, new EllipseStylusShape(diameter, diameter, TapHitRotation));
}
///
/// Check whether a certain percentage of the stroke is within the Rect passed in.
///
///
///
///
public bool HitTest(Rect bounds, int percentageWithinBounds)
{
if ((percentageWithinBounds < 0) || (percentageWithinBounds > 100))
{
throw new System.ArgumentOutOfRangeException("percentageWithinBounds");
}
if (percentageWithinBounds == 0)
{
return true;
}
StrokeInfo strokeInfo = null;
try
{
strokeInfo = new StrokeInfo(this);
StylusPointCollection stylusPoints = strokeInfo.StylusPoints;
double target = strokeInfo.TotalWeight * percentageWithinBounds / 100.0f - PercentageTolerance;
for (int i = 0; i < stylusPoints.Count; i++)
{
if (true == bounds.Contains((Point)stylusPoints[i]))
{
target -= strokeInfo.GetPointWeight(i);
if (DoubleUtil.LessThanOrClose(target, 0d))
{
return true;
}
}
}
return false;
}
finally
{
if (strokeInfo != null)
{
//detach from event handlers, or else we leak.
strokeInfo.Detach();
}
}
}
///
/// Check whether a certain percentage of the stroke is within the lasso
///
///
///
///
public bool HitTest(IEnumerable lassoPoints, int percentageWithinLasso)
{
if (lassoPoints == null)
{
throw new System.ArgumentNullException("lassoPoints");
}
if ((percentageWithinLasso < 0) || (percentageWithinLasso > 100))
{
throw new System.ArgumentOutOfRangeException("percentageWithinLasso");
}
if (percentageWithinLasso == 0)
{
return true;
}
StrokeInfo strokeInfo = null;
try
{
strokeInfo = new StrokeInfo(this);
StylusPointCollection stylusPoints = strokeInfo.StylusPoints;
double target = strokeInfo.TotalWeight * percentageWithinLasso / 100.0f - PercentageTolerance;
Lasso lasso = new SingleLoopLasso();
lasso.AddPoints(lassoPoints);
for (int i = 0; i < stylusPoints.Count; i++)
{
if (true == lasso.Contains((Point)stylusPoints[i]))
{
target -= strokeInfo.GetPointWeight(i);
if (DoubleUtil.LessThan(target, 0f))
{
return true;
}
}
}
return false;
}
finally
{
if (strokeInfo != null)
{
//detach from event handlers, or else we leak.
strokeInfo.Detach();
}
}
}
///
///
///
///
///
///
public bool HitTest(IEnumerable path, StylusShape stylusShape)
{
// Check the input parameters
if (path == null)
{
throw new System.ArgumentNullException("path");
}
if (stylusShape == null)
{
throw new System.ArgumentNullException("stylusShape");
}
if (IEnumerablePointHelper.GetCount(path) == 0)
{
return false;
}
ErasingStroke erasingStroke = new ErasingStroke(stylusShape);
erasingStroke.MoveTo(path);
Rect erasingBounds = erasingStroke.Bounds;
if (erasingBounds.IsEmpty)
{
return false;
}
if (erasingBounds.IntersectsWith(this.GetBounds()))
{
return erasingStroke.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes));
}
return false;
}
#endregion
#endregion
#region Protected APIs
///
/// The core functionality to draw a stroke. The function can be called from the following code paths.
/// i) From StrokeVisual.OnRender
/// a. Highlighter strokes have been grouped and the correct opacity has been set on the container visual.
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow can be true, i.e., Selected stroke is drawn as hollow
/// ii) From StrokeCollection.Draw.
/// a. Highlighter strokes have been grouped and the correct opacity has been pushed.
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow
/// iii) From Stroke.Draw
/// a. The correct opacity has been pushed for a highlighter stroke
/// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255.
/// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow
/// We need to document the following:
/// 1) our default implementation so developers can see what we've done here -
/// including how we handle IsHollow
/// 2) the fact that opacity has already been set up correctly for the call.
/// 3) that developers should not call base.DrawCore if they override this
///
/// DrawingContext to draw on
/// DrawingAttributes to draw with
protected virtual void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (null == drawingContext)
{
throw new System.ArgumentNullException("drawingContext");
}
if (null == drawingAttributes)
{
throw new System.ArgumentNullException("drawingAttributes");
}
if (_drawAsHollow == true)
{
// Draw as hollow. Our profiler result shows that the two-pass-rendering approach is about 5 times
// faster that using GetOutlinePathGeometry.
// also, the minimum display size for selected ink is our default width / height
Matrix innerTransform, outerTransform;
DrawingAttributes selectedDA = drawingAttributes.Clone();
selectedDA.Height = Math.Max(selectedDA.Height, DrawingAttributes.DefaultHeight);
selectedDA.Width = Math.Max(selectedDA.Width, DrawingAttributes.DefaultWidth);
CalcHollowTransforms(selectedDA, out innerTransform, out outerTransform);
// First pass drawing. Use drawingAttributes.Color to create a solid color brush. The stroke will be drawn as
// 1 avalon-unit higher and wider (HollowLineSize = 1.0f)
selectedDA.StylusTipTransform = outerTransform;
SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
brush.Freeze();
drawingContext.DrawGeometry(brush, null, GetGeometry(selectedDA));
//Second pass drawing with a white color brush. The stroke will be drawn as
// 1 avalon-unit shorter and narrower (HollowLineSize = 1.0f) if the actual-width/height (considering StylusTipTransform)
// is larger than HollowLineSize. Otherwise the same size stroke is drawn.
selectedDA.StylusTipTransform = innerTransform;
drawingContext.DrawGeometry(Brushes.White, null, GetGeometry(selectedDA));
}
else
{
#if DEBUG_RENDERING_FEEDBACK
//render debug feedback?
Guid guid = new Guid("52053C24-CBDD-4547-AAA1-DEFEBF7FD1E1");
if (this.ContainsPropertyData(guid))
{
double thickness = (double)this.GetPropertyData(guid);
//first, draw the outline of the stroke
drawingContext.DrawGeometry(null,
new Pen(Brushes.Black, thickness),
GetGeometry());
Geometry g2;
Rect b2;
//next, overlay the connecting quad points
StrokeRenderer.CalcGeometryAndBounds(StrokeNodeIterator.GetIterator(this, drawingAttributes),
drawingAttributes,
drawingContext, thickness, true,
true, //calc bounds
out g2,
out b2);
}
else
{
#endif
SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
brush.Freeze();
drawingContext.DrawGeometry(brush, null, GetGeometry(drawingAttributes));
#if DEBUG_RENDERING_FEEDBACK
}
#endif
}
}
///
/// Returns the Geometry of this stroke.
///
///
public Geometry GetGeometry()
{
return GetGeometry(this.DrawingAttributes);
}
///
/// Get the Geometry of the Stroke
///
///
///
public Geometry GetGeometry(DrawingAttributes drawingAttributes)
{
if (drawingAttributes == null)
{
throw new ArgumentNullException("drawingAttributes");
}
bool geometricallyEqual = DrawingAttributes.GeometricallyEqual(drawingAttributes, this.DrawingAttributes);
// need to recalculate the PathGemetry if the DA passed in is "geometrically" different from
// this DA, or if the cached PathGeometry is dirty.
if (false == geometricallyEqual || (true == geometricallyEqual && null == _cachedGeometry))
{
//Recalculate _pathGeometry;
StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, drawingAttributes);
Geometry geometry;
Rect bounds;
StrokeRenderer.CalcGeometryAndBounds(iterator,
drawingAttributes,
#if DEBUG_RENDERING_FEEDBACK
null, 0d, false,
#endif
true, //calc bounds
out geometry,
out bounds);
// return the calculated value directly. We cannot cache the result since the DA passed in
// is "geometrically" different from this.DrawingAttributes.
if (false == geometricallyEqual)
{
return geometry;
}
// Cache the value and set _isPathGeometryDirty to false;
SetGeometry(geometry);
SetBounds(bounds);
return geometry;
}
// return a ref to our _cachedGeometry
System.Diagnostics.Debug.Assert(_cachedGeometry != null && _cachedGeometry.IsFrozen);
return _cachedGeometry;
}
#endregion
#region Internal APIs
///
/// our code - StrokeVisual.OnRender and StrokeCollection.Draw - always calls this
/// so we can assume the correct opacity has already been pushed on dc. The flag drawAsHollow is set
/// to true when this function is called from Renderer and this.IsSelected == true.
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal void DrawInternal(DrawingContext dc, DrawingAttributes DrawingAttributes, bool drawAsHollow)
{
if (drawAsHollow == true)
{
// The Stroke.DrawCore may be overriden in the 3rd party code.
// The out-side code could throw exception. We use try/finally block to protect our status.
try
{
_drawAsHollow = true; // temporarily set the flag to be true
this.DrawCore(dc, DrawingAttributes);
}
finally
{
_drawAsHollow = false; // reset _drawAsHollow
}
}
else
{
// IsSelected can be true or false, but _drawAsHollow must be false
System.Diagnostics.Debug.Assert(false == _drawAsHollow);
this.DrawCore(dc, DrawingAttributes);
}
}
///
/// Used by Inkcanvas to draw selected stroke as hollow.
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
// Raise Invalidated event. This will cause Renderer to repaint and call back DrawCore
OnInvalidated(EventArgs.Empty);
}
}
}
///
/// Set the path geometry
///
internal void SetGeometry(Geometry geometry)
{
System.Diagnostics.Debug.Assert(geometry != null);
_cachedGeometry = geometry;
}
///
/// Set the bounds
///
internal void SetBounds(Rect newBounds)
{
System.Diagnostics.Debug.Assert(newBounds.IsEmpty == false);
_cachedBounds = newBounds;
}
/// Hit tests all segments within a contour generated with shape and path
///
///
/// StrokeIntersection array for these segments
internal StrokeIntersection[] EraseTest(IEnumerable path, StylusShape shape)
{
System.Diagnostics.Debug.Assert(shape != null);
System.Diagnostics.Debug.Assert(path != null);
if (IEnumerablePointHelper.GetCount(path) == 0)
{
return new StrokeIntersection[0];
}
ErasingStroke erasingStroke = new ErasingStroke(shape, path);
List intersections = new List();
erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes), intersections);
return intersections.ToArray();
}
///
/// Hit tests all segments within the lasso loops
///
/// a StrokeIntersection array for these segments
internal StrokeIntersection[] HitTest(Lasso lasso)
{
// Check the input parameters
System.Diagnostics.Debug.Assert(lasso != null);
if (lasso.IsEmpty)
{
return new StrokeIntersection[0];
}
// The following will check whether all the points are within the lasso.
// If yes, return the whole stroke as being hit.
if (!lasso.Bounds.IntersectsWith(this.GetBounds()))
{
return new StrokeIntersection[0];
}
return lasso.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes));
}
///
/// Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation.
///
/// Array of intersections indicating the erasing locations
///
internal StrokeCollection Erase(StrokeIntersection[] cutAt)
{
System.Diagnostics.Debug.Assert(cutAt != null);
// Nothing needs to be erased
if(cutAt.Length == 0)
{
StrokeCollection strokes = new StrokeCollection();
strokes.Add(this.Clone()); //clip and erase always return clones for this condition
return strokes;
}
// Two assertions are deferred to the private erase function to avoid duplicate code.
// 1. AssertSortedNoOverlap
// 2. Check whether the insegments are out of range with the packets
StrokeFIndices[] hitSegments = StrokeIntersection.GetHitSegments(cutAt);
return this.Erase(hitSegments);
}
///
/// Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation.
///
/// Array of intersections indicating the clipping locations
/// The resulting StrokeCollection
internal StrokeCollection Clip(StrokeIntersection[] cutAt)
{
System.Diagnostics.Debug.Assert(cutAt != null);
// Nothing is inside
if (cutAt.Length == 0)
{
return new StrokeCollection();
}
// Get the "in-segments"
StrokeFIndices[] inSegments = StrokeIntersection.GetInSegments(cutAt);
// For special case like cutAt is {BF, AL, BF, 0.67}, the inSegments are empty
if (inSegments.Length == 0)
{
return new StrokeCollection();
}
// Two other validations are deferred to the private clip function to avoid duplicate code.
// 1. ValidateSortedNoOverlap
// 2. Check whether the insegments are out of range with the packets
return this.Clip(inSegments);
}
internal double TapHitPointSize = 1.0;
internal double TapHitRotation = 0;
#endregion
#region Private APIs
///
/// Calculate the two transforms for two-pass rendering used to draw as hollow. The resulting outerTransform will make the
/// first-pass-rendering 1 avalon-unit wider/heigher. The resulting innerTransform will make the second-pass-rendering 1 avalon-unit
/// narrower/shorter.
///
private static void CalcHollowTransforms(DrawingAttributes originalDa, out Matrix innerTransform, out Matrix outerTransform)
{
System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetX) && DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetY));
innerTransform = outerTransform = Matrix.Identity;
Point w = originalDa.StylusTipTransform.Transform(new Point(originalDa.Width, 0));
Point h = originalDa.StylusTipTransform.Transform(new Point(0, originalDa.Height));
// the newWidth and newHeight are the actual width/height of the stylus shape considering StylusTipTransform.
// The assumption is TylusTipTransform has no translation component.
double newWidth = Math.Sqrt(w.X * w.X + w.Y * w.Y);
double newHeight = Math.Sqrt(h.X * h.X + h.Y * h.Y);
double xTransform = DoubleUtil.GreaterThan(newWidth, HollowLineSize) ?
(newWidth - HollowLineSize) / newWidth : 1.0f;
double yTransform = DoubleUtil.GreaterThan(newHeight, HollowLineSize) ?
(newHeight - HollowLineSize) / newHeight : 1.0f;
innerTransform.Scale(xTransform, yTransform);
innerTransform *= originalDa.StylusTipTransform;
outerTransform.Scale((newWidth + HollowLineSize) / newWidth,
(newHeight + HollowLineSize) / newHeight);
outerTransform *= originalDa.StylusTipTransform;
}
#region Private fields
private Geometry _cachedGeometry = null;
private bool _isSelected = false;
private bool _drawAsHollow = false;
private bool _cloneStylusPoints = true;
private bool _delayRaiseInvalidated = false;
private static readonly double HollowLineSize = 1.0f;
private Rect _cachedBounds = Rect.Empty;
// The private PropertyChanged event
private PropertyChangedEventHandler _propertyChanged;
private const string DrawingAttributesName = "DrawingAttributes";
private const string StylusPointsName = "StylusPoints";
#endregion
internal static readonly double PercentageTolerance = 0.0001d;
#endregion
}
}
// 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
- Renderer.cs
- AspNetHostingPermission.cs
- PageRanges.cs
- MobileControl.cs
- AssociatedControlConverter.cs
- ErrorFormatterPage.cs
- PrinterSettings.cs
- Trace.cs
- CircleEase.cs
- HyperLinkDesigner.cs
- ColumnHeader.cs
- WindowsScroll.cs
- FileSystemEventArgs.cs
- RuntimeWrappedException.cs
- ClipboardData.cs
- NonParentingControl.cs
- VisualTreeHelper.cs
- SoapEnumAttribute.cs
- ConfigurationValue.cs
- PreservationFileReader.cs
- ActiveXMessageFormatter.cs
- HideDisabledControlAdapter.cs
- XamlFilter.cs
- SqlDependencyListener.cs
- ContentFilePart.cs
- HashCodeCombiner.cs
- PolicyStatement.cs
- InvokeMemberBinder.cs
- ControlSerializer.cs
- ComboBoxDesigner.cs
- _Connection.cs
- PolyLineSegmentFigureLogic.cs
- XmlText.cs
- ExternalException.cs
- TemplateEditingService.cs
- EnvironmentPermission.cs
- ClientProxyGenerator.cs
- VariableElement.cs
- SoapExtensionReflector.cs
- BamlBinaryWriter.cs
- InternalConfigConfigurationFactory.cs
- RelatedPropertyManager.cs
- CacheSection.cs
- PreProcessInputEventArgs.cs
- FontResourceCache.cs
- CodeObjectCreateExpression.cs
- QuaternionRotation3D.cs
- PersonalizationProvider.cs
- HtmlAnchor.cs
- HttpServerVarsCollection.cs
- EnumValAlphaComparer.cs
- OdbcFactory.cs
- Workspace.cs
- TextView.cs
- ComponentResourceKeyConverter.cs
- DrawingContextDrawingContextWalker.cs
- Binding.cs
- SqlClientWrapperSmiStream.cs
- CompilerTypeWithParams.cs
- XmlEncodedRawTextWriter.cs
- AnimatedTypeHelpers.cs
- Listbox.cs
- DBParameter.cs
- MemoryFailPoint.cs
- XmlNamespaceMappingCollection.cs
- DeviceContext2.cs
- HostedElements.cs
- NativeMethods.cs
- XmlLinkedNode.cs
- ToolStripItemRenderEventArgs.cs
- OutputScopeManager.cs
- DataControlFieldCell.cs
- ChannelTerminatedException.cs
- MetadataPropertyAttribute.cs
- GroupLabel.cs
- UrlMappingCollection.cs
- NullableLongMinMaxAggregationOperator.cs
- SuppressMergeCheckAttribute.cs
- RepeaterCommandEventArgs.cs
- SQLGuidStorage.cs
- UnsafeNativeMethods.cs
- xmlsaver.cs
- Main.cs
- DataKey.cs
- SecurityElement.cs
- SqlConnectionPoolGroupProviderInfo.cs
- BitmapEffectDrawingContextState.cs
- BaseDataBoundControl.cs
- AudioSignalProblemOccurredEventArgs.cs
- SuppressMessageAttribute.cs
- DataGridCommandEventArgs.cs
- securitycriticaldataformultiplegetandset.cs
- CodeRegionDirective.cs
- HttpCapabilitiesEvaluator.cs
- TabletDevice.cs
- BehaviorEditorPart.cs
- RectangleGeometry.cs
- Vector3DIndependentAnimationStorage.cs
- OutputCacheSection.cs
- FragmentQueryProcessor.cs