# StrokeRenderer.cs source code in C# .NET

## Source code for the .NET framework in C#

```                        Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Core / CSharp / MS / Internal / Ink / StrokeRenderer.cs / 1 / StrokeRenderer.cs

//#define DEBUG_RENDERING_FEEDBACK
//------------------------------------------------------------------------
//
//
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Media;
using System.Windows.Input;
using System.Diagnostics;

using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
using MS.Internal;
using MS.Internal.PresentationCore;

namespace MS.Internal.Ink
{
///
/// An internal utility class that knows how to render a stroke
/// into an Avalon's DrawingContext.
///
internal static class StrokeRenderer
{
#region Static API

///
/// Calculate the StreamGeometry for the StrokeNodes.
/// This method is one of our most sensitive perf paths.  It has been optimized to
/// create the minimum path figures in the StreamGeometry.  There are two structures
/// we create for each point in a stroke, the strokenode and the connecting quad.  Adding
/// strokenodes is very expensive later when MIL renders it, so this method has been optimized
/// to only add strokenodes when either pressure changes, or the angle of the stroke changes.
///
internal static void CalcGeometryAndBoundsWithTransform(StrokeNodeIterator iterator,
DrawingAttributes drawingAttributes,
MatrixTypes stylusTipMatrixType,
bool calculateBounds,
out Geometry geometry,
out Rect bounds)
{
Debug.Assert(iterator != null);
Debug.Assert(drawingAttributes != null);

StreamGeometry streamGeometry = new StreamGeometry();
streamGeometry.FillRule = FillRule.Nonzero;

StreamGeometryContext context = streamGeometry.Open();
geometry = streamGeometry;
bounds = Rect.Empty;
try
{
List connectingQuadPoints = new List(iterator.Count * 4);

//the index that the cb quad points are copied to
int cdIndex = iterator.Count * 2;
//the index that the ab quad points are copied to
int abIndex = 0;
for (int x = 0; x < cdIndex; x++)
{
//initialize so we can start copying to cdIndex later
}

List strokeNodePoints = new List();
double lastAngle = 0.0d;
bool previousPreviousNodeRendered = false;

Rect lastRect = new Rect(0, 0, 0, 0);

for (int index = 0; index < iterator.Count; index++)
{
StrokeNode strokeNode = iterator[index];
System.Diagnostics.Debug.Assert(true == strokeNode.IsValid);

//the only code that calls this with !calculateBounds
//is dynamic rendering, which already draws enough strokeNodes
//to hide any visual artifacts.
//static rendering calculatesBounds, and we use those
//bounds below to figure out what angle to lay strokeNodes down for.
Rect strokeNodeBounds = strokeNode.GetBounds();
if (calculateBounds)
{
bounds.Union(strokeNodeBounds);
}

//if the angle between this and the last position has changed
//too much relative to the angle between the last+1 position and the last position
//we need to lay down stroke node
double delta = Math.Abs(GetAngleDeltaFromLast(strokeNode.PreviousPosition, strokeNode.Position, ref lastAngle));

double angleTolerance = 45d;
if (stylusTipMatrixType == MatrixTypes.TRANSFORM_IS_UNKNOWN)
{
//probably a skew is thrown in, we need to fall back to being very conservative
//about how many strokeNodes we prune
angleTolerance = 10d;
}
else if (strokeNodeBounds.Height > 40d || strokeNodeBounds.Width > 40d)
{
//if the strokeNode gets above a certain size, we need to lay down more strokeNodes
//to prevent visual artifacts
angleTolerance = 20d;
}
bool directionChanged = delta > angleTolerance && delta < (360d - angleTolerance);

double prevArea = lastRect.Height * lastRect.Width;
double currArea = strokeNodeBounds.Height * strokeNodeBounds.Width;
bool areaChangedOverThreshold = false;
if ((Math.Min(prevArea, currArea) / Math.Max(prevArea, currArea)) <= 0.70d)
{
//the min area is < 70% of the max area
areaChangedOverThreshold = true;
}

lastRect = strokeNodeBounds;

//render the stroke node for the first two nodes and last two nodes always
if (index <= 1 || index >= iterator.Count - 2 || directionChanged || areaChangedOverThreshold)
{
//special case... the direction has changed and we need to
//insert a stroke node in the StreamGeometry before we render the current one
if (directionChanged && !previousPreviousNodeRendered && index > 1 && index < iterator.Count - 1)
{
//insert a stroke node for the previous node
strokeNodePoints.Clear();
strokeNode.GetPreviousContourPoints(strokeNodePoints);

previousPreviousNodeRendered = true;
}

//render the stroke node
strokeNodePoints.Clear();
strokeNode.GetContourPoints(strokeNodePoints);
}

if (!directionChanged)
{
previousPreviousNodeRendered = false;
}

{
}

if (strokeNode.IsLastNode)
{
Debug.Assert(index == iterator.Count - 1);
if (abIndex > 0)
{
//now we need to do three things
//1) Shift the dc points down to the ab points
int cbStartIndex = iterator.Count * 2;
int cbEndIndex = connectingQuadPoints.Count - 1;
for (int i = abIndex, j = cbStartIndex; j <= cbEndIndex; i++, j++)
{
}

//2) trim the exess off the end of the array
int countToRemove = cbStartIndex - abIndex;
connectingQuadPoints.RemoveRange((cbEndIndex - countToRemove) + 1, countToRemove);

//3) reverse the dc points to make them cd points
for (int i = abIndex, j = connectingQuadPoints.Count - 1; i < j; i++, j--)
{
}

//now render away!
}
}
}
}
finally
{
context.Close();
geometry.Freeze();
}
}

///
/// Calculate the StreamGeometry for the StrokeNodes.
/// This method is one of our most sensitive perf paths.  It has been optimized to
/// create the minimum path figures in the StreamGeometry.  There are two structures
/// we create for each point in a stroke, the strokenode and the connecting quad.  Adding
/// strokenodes is very expensive later when MIL renders it, so this method has been optimized
/// to only add strokenodes when either pressure changes, or the angle of the stroke changes.
///
[FriendAccessAllowed]
internal static void CalcGeometryAndBounds(StrokeNodeIterator iterator,
DrawingAttributes drawingAttributes,
#if DEBUG_RENDERING_FEEDBACK
DrawingContext debugDC,
double feedbackSize,
bool showFeedback,
#endif
bool calculateBounds,
out Geometry geometry,
out Rect bounds)
{

Debug.Assert(iterator != null && drawingAttributes != null);

//we can use our new algorithm for identity only.
Matrix stylusTipTransform = drawingAttributes.StylusTipTransform;
if (stylusTipTransform != Matrix.Identity && stylusTipTransform._type != MatrixTypes.TRANSFORM_IS_SCALING)
{
//second best optimization
CalcGeometryAndBoundsWithTransform(iterator, drawingAttributes, stylusTipTransform._type, calculateBounds, out geometry, out bounds);
}
else
{

StreamGeometry streamGeometry = new StreamGeometry();
streamGeometry.FillRule = FillRule.Nonzero;

StreamGeometryContext context = streamGeometry.Open();
geometry = streamGeometry;
Rect empty = Rect.Empty;
bounds = empty;
try
{
//
// We keep track of three StrokeNodes as we iterate across
// the Stroke. Since these are structs, the default ctor will
// be called and .IsValid will be false until we initialize them
//
StrokeNode emptyStrokeNode = new StrokeNode();
StrokeNode prevPrevStrokeNode = new StrokeNode();
StrokeNode prevStrokeNode = new StrokeNode();
StrokeNode strokeNode = new StrokeNode();

Rect prevPrevStrokeNodeBounds = empty;
Rect prevStrokeNodeBounds = empty;
Rect strokeNodeBounds = empty;

//percentIntersect is a function of drawingAttributes height / width
double percentIntersect = 95d;
double maxExtent = Math.Max(drawingAttributes.Height, drawingAttributes.Width);
percentIntersect += Math.Min(4.99999d, ((maxExtent / 20d) * 5d));

double prevAngle = double.MinValue;
bool isStartOfSegment = true;
bool isEllipse = drawingAttributes.StylusTip == StylusTip.Ellipse;
bool ignorePressure = drawingAttributes.IgnorePressure;
//
// Two List's that get reused for adding figures
// to the streamgeometry.
//
List pathFigureABSide = new List();//don't prealloc.  It causes Gen2 collections to rise and doesn't help execution time
List pathFigureDCSide = new List();
List polyLinePoints =  new List(4);

int iteratorCount = iterator.Count;
for (int index = 0, previousIndex = -1; index < iteratorCount; )
{
if (!prevPrevStrokeNode.IsValid)
{
if (prevStrokeNode.IsValid)
{
//we're sliding our pointers forward
prevPrevStrokeNode = prevStrokeNode;
prevPrevStrokeNodeBounds = prevStrokeNodeBounds;
prevStrokeNode = emptyStrokeNode;
}
else
{
prevPrevStrokeNode = iterator[index++, previousIndex++];
prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds();
continue; //so we always check if index < iterator.Count
}
}

//we know prevPrevStrokeNode is valid
if (!prevStrokeNode.IsValid)
{
if (strokeNode.IsValid)
{
//we're sliding our pointers forward
prevStrokeNode = strokeNode;
prevStrokeNodeBounds = strokeNodeBounds;
strokeNode = emptyStrokeNode;
}
else
{
//get the next strokeNode, but don't automatically update previousIndex
prevStrokeNode = iterator[index++, previousIndex];
prevStrokeNodeBounds = prevStrokeNode.GetBounds();

RectCompareResult result =
FuzzyContains(  prevStrokeNodeBounds,
prevPrevStrokeNodeBounds,
isStartOfSegment ? 99.99999d : percentIntersect);

if (result == RectCompareResult.Rect1ContainsRect2)
{
// this node already contains the prevPrevStrokeNodeBounds (PP):
//
//  |------------|
//  | |----|     |
//  | | PP |  P  |
//  | |----|     |
//  |------------|
//
prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1]; ;
prevPrevStrokeNodeBounds = Rect.Union(prevStrokeNodeBounds, prevPrevStrokeNodeBounds);

// at this point prevPrevStrokeNodeBounds already contains this node
// we can just ignore this node
prevStrokeNode = emptyStrokeNode;

// update previousIndex to point to this node
previousIndex = index - 1;

// go back to our main loop
continue;
}
else if (result == RectCompareResult.Rect2ContainsRect1)
{
// this prevPrevStrokeNodeBounds (PP) already contains this node:
//
//  |------------|
//  |      |----||
//  |  PP  | P  ||
//  |      |----||
//  |------------|
//

//we can just ignore this node
prevStrokeNode = emptyStrokeNode;

// go back to our main loop, but do not update previousIndex
// because it should continue to point to previousPrevious
continue;
}

// if neither was true, we now have two of our three nodes required to
// start our computation, we need to update previousIndex to point
// to our current, valid prevStrokeNode
previousIndex = index - 1;
continue; //so we always check if index < iterator.Count
}
}

//we know prevPrevStrokeNode and prevStrokeNode are both valid
if (!strokeNode.IsValid)
{
strokeNode = iterator[index++, previousIndex];
strokeNodeBounds = strokeNode.GetBounds();

RectCompareResult result =
FuzzyContains(  strokeNodeBounds,
prevStrokeNodeBounds,
isStartOfSegment ? 99.99999 : percentIntersect);

RectCompareResult result2 =
FuzzyContains(  strokeNodeBounds,
prevPrevStrokeNodeBounds,
isStartOfSegment ? 99.99999 : percentIntersect);

if ( isStartOfSegment &&
result == RectCompareResult.Rect1ContainsRect2 &&
result2 == RectCompareResult.Rect1ContainsRect2)
{
if (pathFigureABSide.Count > 0)
{
//we've started a stroke, we need to end it before resetting
//prevPrev
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);
}
//we're resetting
//prevPrevStrokeNode.  We need to gen one
prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1];
prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds();
prevStrokeNode = emptyStrokeNode;
strokeNode = emptyStrokeNode;

// increment previousIndex to to point to this node
previousIndex = index - 1;
continue;

}
else if (result == RectCompareResult.Rect1ContainsRect2)
{
// this node (C) already contains the prevStrokeNodeBounds (P):
//
//          |------------|
//  |----|  | |----|     |
//  | PP |  | | P  |  C  |
//  |----|  | |----|     |
//          |------------|
//
//we have to generate a new stroke node that points
//to pp since the connecting quad from C to P could be empty
//if they have the same point
strokeNode = iterator[index - 1, prevStrokeNode.Index - 1];
{
//only update prevStrokeNode if we have a valid connecting quad
prevStrokeNode = strokeNode;
prevStrokeNodeBounds = Rect.Union(strokeNodeBounds, prevStrokeNodeBounds);

// update previousIndex, since it should point to this node now
previousIndex = index - 1;
}

// at this point we can just ignore this node
strokeNode = emptyStrokeNode;
//strokeNodeBounds = empty;

prevAngle = double.MinValue; //invalidate

// go back to our main loop
continue;
}
else if (result == RectCompareResult.Rect2ContainsRect1)
{
// this prevStrokeNodeBounds (P) already contains this node (C):
//
//          |------------|
// |----|   |      |----||
// | PP |   |  P   | C  ||
// |----|   |      |----||
//          |------------|
//
//we can just ignore this node
strokeNode = emptyStrokeNode;

// go back to our main loop, but do not update previousIndex
// because it should continue to point to previous
continue;
}

//
// NOTE: we do not check if C contains PP, or PP contains C because
// that indicates a change in direction, which we handle below
//
// if neither was true P and C are separate,
// we now have all three nodes required to
// start our computation, we need to update previousIndex to point
// to our current, valid prevStrokeNode
previousIndex = index - 1;
}

// see if we have an overlap between the first and third node
bool overlap = prevPrevStrokeNodeBounds.IntersectsWith(strokeNodeBounds);

// prevPrevStrokeNode, prevStrokeNode and strokeNode are all
// valid nodes now.  Now we need to figure out what do add to our
// PathFigure.  First calc bounds on the strokeNode we know we need to render
if (calculateBounds)
{
bounds.Union(prevStrokeNodeBounds);
}

// determine what points to add to pathFigureABSide and pathFigureDCSide
// from prevPrevStrokeNode
if (pathFigureABSide.Count == 0)
{
Debug.Assert(pathFigureDCSide.Count == 0);
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
}

if (isStartOfSegment && overlap)
{
//render a complete first stroke node or we can get artifacts
prevPrevStrokeNode.GetContourPoints(polyLinePoints);
polyLinePoints.Clear();
}

// we're starting a new pathfigure
// we need to add parts of the prevPrevStrokeNode contour
// to pathFigureABSide and pathFigureDCSide
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide);
#endif

//set our marker, we're no longer at the start of the stroke
isStartOfSegment = false;
}

if (prevAngle == double.MinValue)
{
//prevAngle is no longer valid
prevAngle = GetAngleBetween(prevPrevStrokeNode.Position, prevStrokeNode.Position);
}
double delta = GetAngleDeltaFromLast(prevStrokeNode.Position, strokeNode.Position, ref prevAngle);
bool directionChangedOverAbsoluteThreshold = Math.Abs(delta) > 90d && Math.Abs(delta) < (360d - 90d);
bool directionChangedOverOverlapThreshold = overlap && !(ignorePressure || strokeNode.PressureFactor == 1f) && Math.Abs(delta) > 30d && Math.Abs(delta) < (360d - 30d);

double prevArea = prevStrokeNodeBounds.Height * prevStrokeNodeBounds.Width;
double currArea = strokeNodeBounds.Height * strokeNodeBounds.Width;

bool areaChanged = !(prevArea == currArea && prevArea == (prevPrevStrokeNodeBounds.Height * prevPrevStrokeNodeBounds.Width));
bool areaChangeOverThreshold = false;
if (overlap && areaChanged)
{
if ((Math.Min(prevArea, currArea) / Math.Max(prevArea, currArea)) <= 0.90d)
{
//the min area is < 70% of the max area
areaChangeOverThreshold = true;
}
}

if (areaChanged || delta != 0.0d || index >= iteratorCount)
{
//the area changed between the three nodes OR there was an angle delta OR we're at the end
//of the stroke...  either way, this is a significant node.  If not, we're going to drop it.
if ((overlap && (directionChangedOverOverlapThreshold || areaChangeOverThreshold)) ||
directionChangedOverAbsoluteThreshold)
{
//
// we need to stop the pathfigure at P
// and render the pathfigure
//
//  |--|      |--|    |--||--|   |------|
//  |PP|------|P |    |PP||P |   |PP P C|
//  |--|      |--|    |--||--|   |------|
//           /           |C |
//      |--|             |--|
//      |C |
//      |--|

#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//end the figure
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);

if (areaChangeOverThreshold)
{
//render a complete stroke node or we can get artifacts
prevStrokeNode.GetContourPoints(polyLinePoints);
polyLinePoints.Clear();
}
}
else
{
//
// direction didn't change over the threshold, add the midpoint data
//  |--|      |--|
//  |PP|------|P |
//  |--|      |--|
//                \
//                  |--|
//                  |C |
//                  |--|
bool endSegment; //flag that tell us if we missed an intersection
#if DEBUG_RENDERING_FEEDBACK
strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment, debugDC, feedbackSize, showFeedback);
#else
strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment);
#endif
if (endSegment)
{
//we have a missing intersection, we need to end the
//segment at P
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//end the figure
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);
}
}
}

//
// either way... slide our pointers forward, to do this, we simply mark
// our first pointer as 'empty'
//
prevPrevStrokeNode = emptyStrokeNode;
prevPrevStrokeNodeBounds = empty;

}

//
// anything left to render?
//
if (prevPrevStrokeNode.IsValid)
{
if (prevStrokeNode.IsValid)
{
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
bounds.Union(prevStrokeNodeBounds);
}
Debug.Assert(!strokeNode.IsValid);
//
// we never made it to strokeNode, render two points, OR
// strokeNode was a dupe
//
if (pathFigureABSide.Count > 0)
{
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//
// strokeNode was a dupe, we just need to render the end of the stroke
// which is at prevStrokeNode
//
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/);
}
else
{
// we've only seen two points to render
Debug.Assert(pathFigureDCSide.Count == 0);
//contains all the logic to render two stroke nodes
RenderTwoStrokeNodes(   context,
prevPrevStrokeNode,
prevPrevStrokeNodeBounds,
prevStrokeNode,
prevStrokeNodeBounds,
pathFigureABSide,
pathFigureDCSide,
polyLinePoints
#if DEBUG_RENDERING_FEEDBACK
,debugDC,
feedbackSize,
showFeedback
#endif
);

}
}
else
{
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
}

// we only have a single point to render
Debug.Assert(pathFigureABSide.Count == 0);
prevPrevStrokeNode.GetContourPoints(pathFigureABSide);

}
}
else if (prevStrokeNode.IsValid && strokeNode.IsValid)
{
if (calculateBounds)
{
bounds.Union(prevStrokeNodeBounds);
bounds.Union(strokeNodeBounds);
}

// typical case, we hit the end of the stroke
// see if we need to start a stroke, or just end one
if (pathFigureABSide.Count > 0)
{
#if DEBUG_RENDERING_FEEDBACK
strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif

//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/);

if (FuzzyContains(strokeNodeBounds, prevStrokeNodeBounds, 70d) != RectCompareResult.NoItersection)
{
//render a complete stroke node or we can get artifacts
strokeNode.GetContourPoints(polyLinePoints);
}
}
else
{
Debug.Assert(pathFigureDCSide.Count == 0);
//contains all the logic to render two stroke nodes
RenderTwoStrokeNodes(   context,
prevStrokeNode,
prevStrokeNodeBounds,
strokeNode,
strokeNodeBounds,
pathFigureABSide,
pathFigureDCSide,
polyLinePoints
#if DEBUG_RENDERING_FEEDBACK
,debugDC,
feedbackSize,
showFeedback
#endif
);

}
}
}
finally
{
context.Close();
geometry.Freeze();
}
}
}

///
/// Helper routine to render two distinct stroke nodes
///
private static void RenderTwoStrokeNodes(   StreamGeometryContext context,
StrokeNode strokeNodePrevious,
Rect strokeNodePreviousBounds,
StrokeNode strokeNodeCurrent,
Rect strokeNodeCurrentBounds,
List pointBuffer1,
List pointBuffer2,
List pointBuffer3
#if DEBUG_RENDERING_FEEDBACK
,DrawingContext debugDC,
double feedbackSize,
bool showFeedback
#endif
)
{
Debug.Assert(pointBuffer1 != null);
Debug.Assert(pointBuffer2 != null);
Debug.Assert(pointBuffer3 != null);
Debug.Assert(context != null);

//see if we need to render a quad - if there is not at least a 70% overlap
if (FuzzyContains(strokeNodePreviousBounds, strokeNodeCurrentBounds, 70d) != RectCompareResult.NoItersection)
{
//we're between 100% and 70% overlapped
//just render two distinct figures with a connecting quad (if needed)
strokeNodePrevious.GetContourPoints(pointBuffer1);

{
}

strokeNodeCurrent.GetContourPoints(pointBuffer2);
}
else
{
//we're less than 70% overlapped, it's safe to run our optimization
#if DEBUG_RENDERING_FEEDBACK
strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback);
strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback);
#else
strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2);
strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2);
#endif
//render
ReverseDCPointsRenderAndClear(context, pointBuffer1, pointBuffer2, pointBuffer3, strokeNodeCurrent.IsEllipse, false/*clear the point collections*/);
}
}

///
/// ReverseDCPointsRenderAndClear
///
private static void ReverseDCPointsRenderAndClear(StreamGeometryContext context, List abPoints, List dcPoints, List polyLinePoints, bool isEllipse, bool clear)
{
//we need to reverse the cd side points
Point temp;
for (int i = 0, j = dcPoints.Count - 1; i < j; i++, j--)
{
temp = dcPoints[i];
dcPoints[i] = dcPoints[j];
dcPoints[j] = temp;
}
if (isEllipse)
{
}
else
{
//for rectangles, render a single path figure by combining both sides
}

if (clear)
{
abPoints.Clear();
dcPoints.Clear();
}
}
///
/// FuzzyContains for two rects
///
private static RectCompareResult FuzzyContains(Rect rect1, Rect rect2, double percentIntersect)
{
Debug.Assert(percentIntersect >= 0.0 && percentIntersect <= 100.0d);

double intersectLeft = Math.Max(rect1.Left, rect2.Left);
double intersectTop = Math.Max(rect1.Top, rect2.Top);
double intersectWidth = Math.Max((double)(Math.Min(rect1.Right, rect2.Right) - intersectLeft), (double)0);
double intersectHeight = Math.Max((double)(Math.Min(rect1.Bottom, rect2.Bottom) - intersectTop), (double)0);

if (intersectWidth == 0.0d || intersectHeight == 0.0d)
{
return RectCompareResult.NoItersection;
}

//we have an intersection, see if it is enough
double rect1Area = rect1.Height * rect1.Width;
double rect2Area = rect2.Height * rect2.Width;
double minArea = Math.Min(rect1Area, rect2Area);
double intersectionArea = intersectWidth * intersectHeight;
double intersect = (intersectionArea / minArea) * 100d;
if (intersect >= percentIntersect)
{
if (rect1Area >= rect2Area)
{
return RectCompareResult.Rect1ContainsRect2;
}
return RectCompareResult.Rect2ContainsRect1;
}

return RectCompareResult.NoItersection;
}

///
/// Private helper to render a path figure to the SGC
///
private static void AddFigureToStreamGeometryContext(StreamGeometryContext context, List points, bool isBezierFigure)
{
Debug.Assert(context != null);
Debug.Assert(points != null);
Debug.Assert(points.Count > 0);

context.BeginFigure(points[points.Count - 1], //start point
true,   //isFilled
true);  //IsClosed

if (isBezierFigure)
{
context.PolyBezierTo(points,
true,      //isStroked
true);     //isSmoothJoin
}
else
{
context.PolyLineTo(points,
true,      //isStroked
true);     //isSmoothJoin
}
}

///
/// Private helper to render a path figure to the SGC
///
private static void AddPolylineFigureToStreamGeometryContext(StreamGeometryContext context, List abPoints, List dcPoints)
{
Debug.Assert(context != null);
Debug.Assert(abPoints != null && dcPoints != null);
Debug.Assert(abPoints.Count > 0 && dcPoints.Count > 0);

context.BeginFigure(abPoints, //start point
true,   //isFilled
true);  //IsClosed

context.PolyLineTo(abPoints,
true,      //isStroked
true);     //isSmoothJoin

context.PolyLineTo(dcPoints,
true,      //isStroked
true);     //isSmoothJoin

}

///
/// Private helper to render a path figure to the SGC
///
private static void AddArcToFigureToStreamGeometryContext(StreamGeometryContext context, List abPoints, List dcPoints, List polyLinePoints)
{
Debug.Assert(context != null);
Debug.Assert(abPoints != null && dcPoints != null);
Debug.Assert(polyLinePoints != null);
//Debug.Assert(abPoints.Count > 0 && dcPoints.Count > 0);
if (abPoints.Count == 0 || dcPoints.Count == 0)
{
return;
}

context.BeginFigure(abPoints, //start point
true,   //isFilled
true);  //IsClosed

for (int j = 0; j < 2; j++)
{
List points = j == 0 ? abPoints : dcPoints;
int startIndex = j == 0 ? 1 : 0;
for (int i = startIndex; i < points.Count; )
{
Point next = points[i];
if (next == StrokeRenderer.ArcToMarker)
{
if (polyLinePoints.Count > 0)
{
//polyline first
context.PolyLineTo(  polyLinePoints,
true,      //isStroked
true);     //isSmoothJoin
polyLinePoints.Clear();
}
//we're arcing, pull out height, width and the arc to point
Debug.Assert(i + 2 < points.Count);
if (i + 2 < points.Count)
{
Point sizePoint = points[i + 1];
Size ellipseSize = new Size(sizePoint.X / 2/*width*/, sizePoint.Y / 2/*height*/);
Point arcToPoint = points[i + 2];

bool isLargeArc = false; //>= 180

context.ArcTo(  arcToPoint,
ellipseSize,
0d,             //rotation
isLargeArc,     //isLargeArc
SweepDirection.Clockwise,
true,           //isStroked
true);          //isSmoothJoin
}
i += 3; //advance past this arcTo block
}
else
{
//walk forward until we find an arc marker or the end
i++;
}
}
if (polyLinePoints.Count > 0)
{
//polyline
context.PolyLineTo(polyLinePoints,
true,      //isStroked
true);     //isSmoothJoin
polyLinePoints.Clear();
}
}
}

///
/// calculates the angle between the previousPosition and the current one and then computes the delta between
/// the lastAngle.  lastAngle is also updated
///
private static double GetAngleDeltaFromLast(Point previousPosition, Point currentPosition, ref double lastAngle)
{
double delta = 0.0d;

//input points typically come in very close to each other
double dx = (currentPosition.X * 1000) - (previousPosition.X * 1000);
double dy = (currentPosition.Y * 1000) - (previousPosition.Y * 1000);
if ((Int64)dx == 0 && (Int64)dy == 0)
{
//the points are close enough not to matter
//don't update lastAngle
return delta;
}

double angle = GetAngleBetween(previousPosition, currentPosition);

//special case when angle / lastAngle span 0 degrees
if (lastAngle >= 270 && angle <= 90)
{
delta = lastAngle - (360d + angle);
}
else if (lastAngle <= 90 && angle >= 270)
{
delta = (360d + lastAngle) - angle;
}
else
{
delta = (lastAngle - angle);
}
lastAngle = angle;

// Return
return delta;
}

///
/// calculates the angle between the previousPosition and the current one and then computes the delta between
/// the lastAngle.  lastAngle is also updated
///
private static double GetAngleBetween(Point previousPosition, Point currentPosition)
{
double angle = 0.0d;

//input points typically come in very close to each other
double dx = (currentPosition.X * 1000) - (previousPosition.X * 1000);
double dy = (currentPosition.Y * 1000) - (previousPosition.Y * 1000);
if ((Int64)dx == 0 && (Int64)dy == 0)
{
//the points are close enough not to matter
return angle;
}

// Calculate angle
if (dx == 0.0)
{
if (dy == 0.0)
{
angle = 0.0;
}
else if (dy > 0.0)
{
angle = Math.PI / 2.0;
}
else
{
angle = Math.PI * 3.0 / 2.0;
}
}
else if (dy == 0.0)
{
if (dx > 0.0)
{
angle = 0.0;
}
else
{
angle = Math.PI;
}
}
else
{
if (dx < 0.0)
{
angle = Math.Atan(dy / dx) + Math.PI;
}
else if (dy < 0.0)
{
angle = Math.Atan(dy / dx) + (2 * Math.PI);
}
else
{
angle = Math.Atan(dy / dx);
}
}

// Convert to degrees
angle = angle * 180 / Math.PI;

// Return
return angle;
}

///
/// Get the DrawingAttributes to use for a highlighter stroke. The return value is a copy of
/// the DA passed in if color.A != 255 with color.A overriden to be 255. Otherwise it returns
/// the DA passed in.
///
internal static DrawingAttributes GetHighlighterAttributes(Stroke stroke, DrawingAttributes da)
{
System.Diagnostics.Debug.Assert(da.IsHighlighter = true);
if (da.Color.A != SolidStrokeAlpha)
{
DrawingAttributes copy = stroke.DrawingAttributes.Clone();
copy.Color = GetHighlighterColor(copy.Color);
return copy;
}

return da;
}

///
/// Get the color used to draw a highlighter.
///
internal static Color GetHighlighterColor(Color color)
{
// For a highlighter stroke, the color.A is overriden to be 255
color.A = SolidStrokeAlpha;
return color;
}

// Opacity for highlighter container visuals
internal static readonly double HighlighterOpacity = 0.5;
internal static readonly byte SolidStrokeAlpha = 0xFF;
internal static readonly Point ArcToMarker = new Point(Double.MinValue, Double.MinValue);

///
/// Simple helper enum
///
private enum RectCompareResult
{
Rect1ContainsRect2,
Rect2ContainsRect1,
NoItersection,
}
#endregion
}
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
﻿//#define DEBUG_RENDERING_FEEDBACK
//------------------------------------------------------------------------
//
//
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Media;
using System.Windows.Input;
using System.Diagnostics;

using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
using MS.Internal;
using MS.Internal.PresentationCore;

namespace MS.Internal.Ink
{
///
/// An internal utility class that knows how to render a stroke
/// into an Avalon's DrawingContext.
///
internal static class StrokeRenderer
{
#region Static API

///
/// Calculate the StreamGeometry for the StrokeNodes.
/// This method is one of our most sensitive perf paths.  It has been optimized to
/// create the minimum path figures in the StreamGeometry.  There are two structures
/// we create for each point in a stroke, the strokenode and the connecting quad.  Adding
/// strokenodes is very expensive later when MIL renders it, so this method has been optimized
/// to only add strokenodes when either pressure changes, or the angle of the stroke changes.
///
internal static void CalcGeometryAndBoundsWithTransform(StrokeNodeIterator iterator,
DrawingAttributes drawingAttributes,
MatrixTypes stylusTipMatrixType,
bool calculateBounds,
out Geometry geometry,
out Rect bounds)
{
Debug.Assert(iterator != null);
Debug.Assert(drawingAttributes != null);

StreamGeometry streamGeometry = new StreamGeometry();
streamGeometry.FillRule = FillRule.Nonzero;

StreamGeometryContext context = streamGeometry.Open();
geometry = streamGeometry;
bounds = Rect.Empty;
try
{
List connectingQuadPoints = new List(iterator.Count * 4);

//the index that the cb quad points are copied to
int cdIndex = iterator.Count * 2;
//the index that the ab quad points are copied to
int abIndex = 0;
for (int x = 0; x < cdIndex; x++)
{
//initialize so we can start copying to cdIndex later
}

List strokeNodePoints = new List();
double lastAngle = 0.0d;
bool previousPreviousNodeRendered = false;

Rect lastRect = new Rect(0, 0, 0, 0);

for (int index = 0; index < iterator.Count; index++)
{
StrokeNode strokeNode = iterator[index];
System.Diagnostics.Debug.Assert(true == strokeNode.IsValid);

//the only code that calls this with !calculateBounds
//is dynamic rendering, which already draws enough strokeNodes
//to hide any visual artifacts.
//static rendering calculatesBounds, and we use those
//bounds below to figure out what angle to lay strokeNodes down for.
Rect strokeNodeBounds = strokeNode.GetBounds();
if (calculateBounds)
{
bounds.Union(strokeNodeBounds);
}

//if the angle between this and the last position has changed
//too much relative to the angle between the last+1 position and the last position
//we need to lay down stroke node
double delta = Math.Abs(GetAngleDeltaFromLast(strokeNode.PreviousPosition, strokeNode.Position, ref lastAngle));

double angleTolerance = 45d;
if (stylusTipMatrixType == MatrixTypes.TRANSFORM_IS_UNKNOWN)
{
//probably a skew is thrown in, we need to fall back to being very conservative
//about how many strokeNodes we prune
angleTolerance = 10d;
}
else if (strokeNodeBounds.Height > 40d || strokeNodeBounds.Width > 40d)
{
//if the strokeNode gets above a certain size, we need to lay down more strokeNodes
//to prevent visual artifacts
angleTolerance = 20d;
}
bool directionChanged = delta > angleTolerance && delta < (360d - angleTolerance);

double prevArea = lastRect.Height * lastRect.Width;
double currArea = strokeNodeBounds.Height * strokeNodeBounds.Width;
bool areaChangedOverThreshold = false;
if ((Math.Min(prevArea, currArea) / Math.Max(prevArea, currArea)) <= 0.70d)
{
//the min area is < 70% of the max area
areaChangedOverThreshold = true;
}

lastRect = strokeNodeBounds;

//render the stroke node for the first two nodes and last two nodes always
if (index <= 1 || index >= iterator.Count - 2 || directionChanged || areaChangedOverThreshold)
{
//special case... the direction has changed and we need to
//insert a stroke node in the StreamGeometry before we render the current one
if (directionChanged && !previousPreviousNodeRendered && index > 1 && index < iterator.Count - 1)
{
//insert a stroke node for the previous node
strokeNodePoints.Clear();
strokeNode.GetPreviousContourPoints(strokeNodePoints);

previousPreviousNodeRendered = true;
}

//render the stroke node
strokeNodePoints.Clear();
strokeNode.GetContourPoints(strokeNodePoints);
}

if (!directionChanged)
{
previousPreviousNodeRendered = false;
}

{
}

if (strokeNode.IsLastNode)
{
Debug.Assert(index == iterator.Count - 1);
if (abIndex > 0)
{
//now we need to do three things
//1) Shift the dc points down to the ab points
int cbStartIndex = iterator.Count * 2;
int cbEndIndex = connectingQuadPoints.Count - 1;
for (int i = abIndex, j = cbStartIndex; j <= cbEndIndex; i++, j++)
{
}

//2) trim the exess off the end of the array
int countToRemove = cbStartIndex - abIndex;
connectingQuadPoints.RemoveRange((cbEndIndex - countToRemove) + 1, countToRemove);

//3) reverse the dc points to make them cd points
for (int i = abIndex, j = connectingQuadPoints.Count - 1; i < j; i++, j--)
{
}

//now render away!
}
}
}
}
finally
{
context.Close();
geometry.Freeze();
}
}

///
/// Calculate the StreamGeometry for the StrokeNodes.
/// This method is one of our most sensitive perf paths.  It has been optimized to
/// create the minimum path figures in the StreamGeometry.  There are two structures
/// we create for each point in a stroke, the strokenode and the connecting quad.  Adding
/// strokenodes is very expensive later when MIL renders it, so this method has been optimized
/// to only add strokenodes when either pressure changes, or the angle of the stroke changes.
///
[FriendAccessAllowed]
internal static void CalcGeometryAndBounds(StrokeNodeIterator iterator,
DrawingAttributes drawingAttributes,
#if DEBUG_RENDERING_FEEDBACK
DrawingContext debugDC,
double feedbackSize,
bool showFeedback,
#endif
bool calculateBounds,
out Geometry geometry,
out Rect bounds)
{

Debug.Assert(iterator != null && drawingAttributes != null);

//we can use our new algorithm for identity only.
Matrix stylusTipTransform = drawingAttributes.StylusTipTransform;
if (stylusTipTransform != Matrix.Identity && stylusTipTransform._type != MatrixTypes.TRANSFORM_IS_SCALING)
{
//second best optimization
CalcGeometryAndBoundsWithTransform(iterator, drawingAttributes, stylusTipTransform._type, calculateBounds, out geometry, out bounds);
}
else
{

StreamGeometry streamGeometry = new StreamGeometry();
streamGeometry.FillRule = FillRule.Nonzero;

StreamGeometryContext context = streamGeometry.Open();
geometry = streamGeometry;
Rect empty = Rect.Empty;
bounds = empty;
try
{
//
// We keep track of three StrokeNodes as we iterate across
// the Stroke. Since these are structs, the default ctor will
// be called and .IsValid will be false until we initialize them
//
StrokeNode emptyStrokeNode = new StrokeNode();
StrokeNode prevPrevStrokeNode = new StrokeNode();
StrokeNode prevStrokeNode = new StrokeNode();
StrokeNode strokeNode = new StrokeNode();

Rect prevPrevStrokeNodeBounds = empty;
Rect prevStrokeNodeBounds = empty;
Rect strokeNodeBounds = empty;

//percentIntersect is a function of drawingAttributes height / width
double percentIntersect = 95d;
double maxExtent = Math.Max(drawingAttributes.Height, drawingAttributes.Width);
percentIntersect += Math.Min(4.99999d, ((maxExtent / 20d) * 5d));

double prevAngle = double.MinValue;
bool isStartOfSegment = true;
bool isEllipse = drawingAttributes.StylusTip == StylusTip.Ellipse;
bool ignorePressure = drawingAttributes.IgnorePressure;
//
// Two List's that get reused for adding figures
// to the streamgeometry.
//
List pathFigureABSide = new List();//don't prealloc.  It causes Gen2 collections to rise and doesn't help execution time
List pathFigureDCSide = new List();
List polyLinePoints =  new List(4);

int iteratorCount = iterator.Count;
for (int index = 0, previousIndex = -1; index < iteratorCount; )
{
if (!prevPrevStrokeNode.IsValid)
{
if (prevStrokeNode.IsValid)
{
//we're sliding our pointers forward
prevPrevStrokeNode = prevStrokeNode;
prevPrevStrokeNodeBounds = prevStrokeNodeBounds;
prevStrokeNode = emptyStrokeNode;
}
else
{
prevPrevStrokeNode = iterator[index++, previousIndex++];
prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds();
continue; //so we always check if index < iterator.Count
}
}

//we know prevPrevStrokeNode is valid
if (!prevStrokeNode.IsValid)
{
if (strokeNode.IsValid)
{
//we're sliding our pointers forward
prevStrokeNode = strokeNode;
prevStrokeNodeBounds = strokeNodeBounds;
strokeNode = emptyStrokeNode;
}
else
{
//get the next strokeNode, but don't automatically update previousIndex
prevStrokeNode = iterator[index++, previousIndex];
prevStrokeNodeBounds = prevStrokeNode.GetBounds();

RectCompareResult result =
FuzzyContains(  prevStrokeNodeBounds,
prevPrevStrokeNodeBounds,
isStartOfSegment ? 99.99999d : percentIntersect);

if (result == RectCompareResult.Rect1ContainsRect2)
{
// this node already contains the prevPrevStrokeNodeBounds (PP):
//
//  |------------|
//  | |----|     |
//  | | PP |  P  |
//  | |----|     |
//  |------------|
//
prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1]; ;
prevPrevStrokeNodeBounds = Rect.Union(prevStrokeNodeBounds, prevPrevStrokeNodeBounds);

// at this point prevPrevStrokeNodeBounds already contains this node
// we can just ignore this node
prevStrokeNode = emptyStrokeNode;

// update previousIndex to point to this node
previousIndex = index - 1;

// go back to our main loop
continue;
}
else if (result == RectCompareResult.Rect2ContainsRect1)
{
// this prevPrevStrokeNodeBounds (PP) already contains this node:
//
//  |------------|
//  |      |----||
//  |  PP  | P  ||
//  |      |----||
//  |------------|
//

//we can just ignore this node
prevStrokeNode = emptyStrokeNode;

// go back to our main loop, but do not update previousIndex
// because it should continue to point to previousPrevious
continue;
}

// if neither was true, we now have two of our three nodes required to
// start our computation, we need to update previousIndex to point
// to our current, valid prevStrokeNode
previousIndex = index - 1;
continue; //so we always check if index < iterator.Count
}
}

//we know prevPrevStrokeNode and prevStrokeNode are both valid
if (!strokeNode.IsValid)
{
strokeNode = iterator[index++, previousIndex];
strokeNodeBounds = strokeNode.GetBounds();

RectCompareResult result =
FuzzyContains(  strokeNodeBounds,
prevStrokeNodeBounds,
isStartOfSegment ? 99.99999 : percentIntersect);

RectCompareResult result2 =
FuzzyContains(  strokeNodeBounds,
prevPrevStrokeNodeBounds,
isStartOfSegment ? 99.99999 : percentIntersect);

if ( isStartOfSegment &&
result == RectCompareResult.Rect1ContainsRect2 &&
result2 == RectCompareResult.Rect1ContainsRect2)
{
if (pathFigureABSide.Count > 0)
{
//we've started a stroke, we need to end it before resetting
//prevPrev
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);
}
//we're resetting
//prevPrevStrokeNode.  We need to gen one
prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1];
prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds();
prevStrokeNode = emptyStrokeNode;
strokeNode = emptyStrokeNode;

// increment previousIndex to to point to this node
previousIndex = index - 1;
continue;

}
else if (result == RectCompareResult.Rect1ContainsRect2)
{
// this node (C) already contains the prevStrokeNodeBounds (P):
//
//          |------------|
//  |----|  | |----|     |
//  | PP |  | | P  |  C  |
//  |----|  | |----|     |
//          |------------|
//
//we have to generate a new stroke node that points
//to pp since the connecting quad from C to P could be empty
//if they have the same point
strokeNode = iterator[index - 1, prevStrokeNode.Index - 1];
{
//only update prevStrokeNode if we have a valid connecting quad
prevStrokeNode = strokeNode;
prevStrokeNodeBounds = Rect.Union(strokeNodeBounds, prevStrokeNodeBounds);

// update previousIndex, since it should point to this node now
previousIndex = index - 1;
}

// at this point we can just ignore this node
strokeNode = emptyStrokeNode;
//strokeNodeBounds = empty;

prevAngle = double.MinValue; //invalidate

// go back to our main loop
continue;
}
else if (result == RectCompareResult.Rect2ContainsRect1)
{
// this prevStrokeNodeBounds (P) already contains this node (C):
//
//          |------------|
// |----|   |      |----||
// | PP |   |  P   | C  ||
// |----|   |      |----||
//          |------------|
//
//we can just ignore this node
strokeNode = emptyStrokeNode;

// go back to our main loop, but do not update previousIndex
// because it should continue to point to previous
continue;
}

//
// NOTE: we do not check if C contains PP, or PP contains C because
// that indicates a change in direction, which we handle below
//
// if neither was true P and C are separate,
// we now have all three nodes required to
// start our computation, we need to update previousIndex to point
// to our current, valid prevStrokeNode
previousIndex = index - 1;
}

// see if we have an overlap between the first and third node
bool overlap = prevPrevStrokeNodeBounds.IntersectsWith(strokeNodeBounds);

// prevPrevStrokeNode, prevStrokeNode and strokeNode are all
// valid nodes now.  Now we need to figure out what do add to our
// PathFigure.  First calc bounds on the strokeNode we know we need to render
if (calculateBounds)
{
bounds.Union(prevStrokeNodeBounds);
}

// determine what points to add to pathFigureABSide and pathFigureDCSide
// from prevPrevStrokeNode
if (pathFigureABSide.Count == 0)
{
Debug.Assert(pathFigureDCSide.Count == 0);
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
}

if (isStartOfSegment && overlap)
{
//render a complete first stroke node or we can get artifacts
prevPrevStrokeNode.GetContourPoints(polyLinePoints);
polyLinePoints.Clear();
}

// we're starting a new pathfigure
// we need to add parts of the prevPrevStrokeNode contour
// to pathFigureABSide and pathFigureDCSide
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide);
#endif

//set our marker, we're no longer at the start of the stroke
isStartOfSegment = false;
}

if (prevAngle == double.MinValue)
{
//prevAngle is no longer valid
prevAngle = GetAngleBetween(prevPrevStrokeNode.Position, prevStrokeNode.Position);
}
double delta = GetAngleDeltaFromLast(prevStrokeNode.Position, strokeNode.Position, ref prevAngle);
bool directionChangedOverAbsoluteThreshold = Math.Abs(delta) > 90d && Math.Abs(delta) < (360d - 90d);
bool directionChangedOverOverlapThreshold = overlap && !(ignorePressure || strokeNode.PressureFactor == 1f) && Math.Abs(delta) > 30d && Math.Abs(delta) < (360d - 30d);

double prevArea = prevStrokeNodeBounds.Height * prevStrokeNodeBounds.Width;
double currArea = strokeNodeBounds.Height * strokeNodeBounds.Width;

bool areaChanged = !(prevArea == currArea && prevArea == (prevPrevStrokeNodeBounds.Height * prevPrevStrokeNodeBounds.Width));
bool areaChangeOverThreshold = false;
if (overlap && areaChanged)
{
if ((Math.Min(prevArea, currArea) / Math.Max(prevArea, currArea)) <= 0.90d)
{
//the min area is < 70% of the max area
areaChangeOverThreshold = true;
}
}

if (areaChanged || delta != 0.0d || index >= iteratorCount)
{
//the area changed between the three nodes OR there was an angle delta OR we're at the end
//of the stroke...  either way, this is a significant node.  If not, we're going to drop it.
if ((overlap && (directionChangedOverOverlapThreshold || areaChangeOverThreshold)) ||
directionChangedOverAbsoluteThreshold)
{
//
// we need to stop the pathfigure at P
// and render the pathfigure
//
//  |--|      |--|    |--||--|   |------|
//  |PP|------|P |    |PP||P |   |PP P C|
//  |--|      |--|    |--||--|   |------|
//           /           |C |
//      |--|             |--|
//      |C |
//      |--|

#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//end the figure
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);

if (areaChangeOverThreshold)
{
//render a complete stroke node or we can get artifacts
prevStrokeNode.GetContourPoints(polyLinePoints);
polyLinePoints.Clear();
}
}
else
{
//
// direction didn't change over the threshold, add the midpoint data
//  |--|      |--|
//  |PP|------|P |
//  |--|      |--|
//                \
//                  |--|
//                  |C |
//                  |--|
bool endSegment; //flag that tell us if we missed an intersection
#if DEBUG_RENDERING_FEEDBACK
strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment, debugDC, feedbackSize, showFeedback);
#else
strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment);
#endif
if (endSegment)
{
//we have a missing intersection, we need to end the
//segment at P
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//end the figure
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/);
}
}
}

//
// either way... slide our pointers forward, to do this, we simply mark
// our first pointer as 'empty'
//
prevPrevStrokeNode = emptyStrokeNode;
prevPrevStrokeNodeBounds = empty;

}

//
// anything left to render?
//
if (prevPrevStrokeNode.IsValid)
{
if (prevStrokeNode.IsValid)
{
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
bounds.Union(prevStrokeNodeBounds);
}
Debug.Assert(!strokeNode.IsValid);
//
// we never made it to strokeNode, render two points, OR
// strokeNode was a dupe
//
if (pathFigureABSide.Count > 0)
{
#if DEBUG_RENDERING_FEEDBACK
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
//
// strokeNode was a dupe, we just need to render the end of the stroke
// which is at prevStrokeNode
//
prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif
//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/);
}
else
{
// we've only seen two points to render
Debug.Assert(pathFigureDCSide.Count == 0);
//contains all the logic to render two stroke nodes
RenderTwoStrokeNodes(   context,
prevPrevStrokeNode,
prevPrevStrokeNodeBounds,
prevStrokeNode,
prevStrokeNodeBounds,
pathFigureABSide,
pathFigureDCSide,
polyLinePoints
#if DEBUG_RENDERING_FEEDBACK
,debugDC,
feedbackSize,
showFeedback
#endif
);

}
}
else
{
if (calculateBounds)
{
bounds.Union(prevPrevStrokeNodeBounds);
}

// we only have a single point to render
Debug.Assert(pathFigureABSide.Count == 0);
prevPrevStrokeNode.GetContourPoints(pathFigureABSide);

}
}
else if (prevStrokeNode.IsValid && strokeNode.IsValid)
{
if (calculateBounds)
{
bounds.Union(prevStrokeNodeBounds);
bounds.Union(strokeNodeBounds);
}

// typical case, we hit the end of the stroke
// see if we need to start a stroke, or just end one
if (pathFigureABSide.Count > 0)
{
#if DEBUG_RENDERING_FEEDBACK
strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback);
#else
strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide);
#endif

//render
ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/);

if (FuzzyContains(strokeNodeBounds, prevStrokeNodeBounds, 70d) != RectCompareResult.NoItersection)
{
//render a complete stroke node or we can get artifacts
strokeNode.GetContourPoints(polyLinePoints);
}
}
else
{
Debug.Assert(pathFigureDCSide.Count == 0);
//contains all the logic to render two stroke nodes
RenderTwoStrokeNodes(   context,
prevStrokeNode,
prevStrokeNodeBounds,
strokeNode,
strokeNodeBounds,
pathFigureABSide,
pathFigureDCSide,
polyLinePoints
#if DEBUG_RENDERING_FEEDBACK
,debugDC,
feedbackSize,
showFeedback
#endif
);

}
}
}
finally
{
context.Close();
geometry.Freeze();
}
}
}

///
/// Helper routine to render two distinct stroke nodes
///
private static void RenderTwoStrokeNodes(   StreamGeometryContext context,
StrokeNode strokeNodePrevious,
Rect strokeNodePreviousBounds,
StrokeNode strokeNodeCurrent,
Rect strokeNodeCurrentBounds,
List pointBuffer1,
List pointBuffer2,
List pointBuffer3
#if DEBUG_RENDERING_FEEDBACK
,DrawingContext debugDC,
double feedbackSize,
bool showFeedback
#endif
)
{
Debug.Assert(pointBuffer1 != null);
Debug.Assert(pointBuffer2 != null);
Debug.Assert(pointBuffer3 != null);
Debug.Assert(context != null);

//see if we need to render a quad - if there is not at least a 70% overlap
if (FuzzyContains(strokeNodePreviousBounds, strokeNodeCurrentBounds, 70d) != RectCompareResult.NoItersection)
{
//we're between 100% and 70% overlapped
//just render two distinct figures with a connecting quad (if needed)
strokeNodePrevious.GetContourPoints(pointBuffer1);

{
}

strokeNodeCurrent.GetContourPoints(pointBuffer2);
}
else
{
//we're less than 70% overlapped, it's safe to run our optimization
#if DEBUG_RENDERING_FEEDBACK
strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback);
strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback);
#else
strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2);
strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2);
#endif
//render
ReverseDCPointsRenderAndClear(context, pointBuffer1, pointBuffer2, pointBuffer3, strokeNodeCurrent.IsEllipse, false/*clear the point collections*/);
}
}

///
/// ReverseDCPointsRenderAndClear
///
private static void ReverseDCPointsRenderAndClear(StreamGeometryContext context, List abPoints, List dcPoints, List polyLinePoints, bool isEllipse, bool clear)
{
//we need to reverse the cd side points
Point temp;
for (int i = 0, j = dcPoints.Count - 1; i < j; i++, j--)
{
temp = dcPoints[i];
dcPoints[i] = dcPoints[j];
dcPoints[j] = temp;
}
if (isEllipse)
{
}
else
{
//for rectangles, render a single path figure by combining both sides
}

if (clear)
{
abPoints.Clear();
dcPoints.Clear();
}
}
///
/// FuzzyContains for two rects
///
private static RectCompareResult FuzzyContains(Rect rect1, Rect rect2, double percentIntersect)
{
Debug.Assert(percentIntersect >= 0.0 && percentIntersect <= 100.0d);

double intersectLeft = Math.Max(rect1.Left, rect2.Left);
double intersectTop = Math.Max(rect1.Top, rect2.Top);
double intersectWidth = Math.Max((double)(Math.Min(rect1.Right, rect2.Right) - intersectLeft), (double)0);
double intersectHeight = Math.Max((double)(Math.Min(rect1.Bottom, rect2.Bottom) - intersectTop), (double)0);

if (intersectWidth == 0.0d || intersectHeight == 0.0d)
{
return RectCompareResult.NoItersection;
}

//we have an intersection, see if it is enough
double rect1Area = rect1.Height * rect1.Width;
double rect2Area = rect2.Height * rect2.Width;
double minArea = Math.Min(rect1Area, rect2Area);
double intersectionArea = intersectWidth * intersectHeight;
double intersect = (intersectionArea / minArea) * 100d;
if (intersect >= percentIntersect)
{
if (rect1Area >= rect2Area)
{
return RectCompareResult.Rect1ContainsRect2;
}
return RectCompareResult.Rect2ContainsRect1;
}

return RectCompareResult.NoItersection;
}

///
/// Private helper to render a path figure to the SGC
///
private static void AddFigureToStreamGeometryContext(StreamGeometryContext context, List points, bool isBezierFigure)
{
Debug.Assert(context != null);
Debug.Assert(points != null);
Debug.Assert(points.Count > 0);

context.BeginFigure(points[points.Count - 1], //start point
true,   //isFilled
true);  //IsClosed

if (isBezierFigure)
{
context.PolyBezierTo(points,
true,      //isStroked
true);     //isSmoothJoin
}
else
{
context.PolyLineTo(points,
true,      //isStroked
true);     //isSmoothJoin
}
}

///
/// Private helper to render a path figure to the SGC
///
private static void AddPolylineFigureToStreamGeometryContext(StreamGeometryContext context, List abPoints, List dcPoints)
{
Debug.Assert(context != null);
Debug.Assert(abPoints != null && dcPoints != null);
Debug.Assert(abPoints.Count > 0 && dcPoints.Count > 0);

context.BeginFigure(abPoints, //start point
true,   //isFilled
true);  //IsClosed

context.PolyLineTo(abPoints,
true,      //isStroked
true);     //isSmoothJoin

context.PolyLineTo(dcPoints,
true,      //isStroked
true);     //isSmoothJoin

}

///
/// Private helper to render a path figure to the SGC
///
private static void AddArcToFigureToStreamGeometryContext(StreamGeometryContext context, List abPoints, List dcPoints, List polyLinePoints)
{
Debug.Assert(context != null);
Debug.Assert(abPoints != null && dcPoints != null);
Debug.Assert(polyLinePoints != null);
//Debug.Assert(abPoints.Count > 0 && dcPoints.Count > 0);
if (abPoints.Count == 0 || dcPoints.Count == 0)
{
return;
}

context.BeginFigure(abPoints, //start point
true,   //isFilled
true);  //IsClosed

for (int j = 0; j < 2; j++)
{
List points = j == 0 ? abPoints : dcPoints;
int startIndex = j == 0 ? 1 : 0;
for (int i = startIndex; i < points.Count; )
{
Point next = points[i];
if (next == StrokeRenderer.ArcToMarker)
{
if (polyLinePoints.Count > 0)
{
//polyline first
context.PolyLineTo(  polyLinePoints,
true,      //isStroked
true);     //isSmoothJoin
polyLinePoints.Clear();
}
//we're arcing, pull out height, width and the arc to point
Debug.Assert(i + 2 < points.Count);
if (i + 2 < points.Count)
{
Point sizePoint = points[i + 1];
Size ellipseSize = new Size(sizePoint.X / 2/*width*/, sizePoint.Y / 2/*height*/);
Point arcToPoint = points[i + 2];

bool isLargeArc = false; //>= 180

context.ArcTo(  arcToPoint,
ellipseSize,
0d,             //rotation
isLargeArc,     //isLargeArc
SweepDirection.Clockwise,
true,           //isStroked
true);          //isSmoothJoin
}
i += 3; //advance past this arcTo block
}
else
{
//walk forward until we find an arc marker or the end
i++;
}
}
if (polyLinePoints.Count > 0)
{
//polyline
context.PolyLineTo(polyLinePoints,
true,      //isStroked
true);     //isSmoothJoin
polyLinePoints.Clear();
}
}
}

///
/// calculates the angle between the previousPosition and the current one and then computes the delta between
/// the lastAngle.  lastAngle is also updated
///
private static double GetAngleDeltaFromLast(Point previousPosition, Point currentPosition, ref double lastAngle)
{
double delta = 0.0d;

//input points typically come in very close to each other
double dx = (currentPosition.X * 1000) - (previousPosition.X * 1000);
double dy = (currentPosition.Y * 1000) - (previousPosition.Y * 1000);
if ((Int64)dx == 0 && (Int64)dy == 0)
{
//the points are close enough not to matter
//don't update lastAngle
return delta;
}

double angle = GetAngleBetween(previousPosition, currentPosition);

//special case when angle / lastAngle span 0 degrees
if (lastAngle >= 270 && angle <= 90)
{
delta = lastAngle - (360d + angle);
}
else if (lastAngle <= 90 && angle >= 270)
{
delta = (360d + lastAngle) - angle;
}
else
{
delta = (lastAngle - angle);
}
lastAngle = angle;

// Return
return delta;
}

///
/// calculates the angle between the previousPosition and the current one and then computes the delta between
/// the lastAngle.  lastAngle is also updated
///
private static double GetAngleBetween(Point previousPosition, Point currentPosition)
{
double angle = 0.0d;

//input points typically come in very close to each other
double dx = (currentPosition.X * 1000) - (previousPosition.X * 1000);
double dy = (currentPosition.Y * 1000) - (previousPosition.Y * 1000);
if ((Int64)dx == 0 && (Int64)dy == 0)
{
//the points are close enough not to matter
return angle;
}

// Calculate angle
if (dx == 0.0)
{
if (dy == 0.0)
{
angle = 0.0;
}
else if (dy > 0.0)
{
angle = Math.PI / 2.0;
}
else
{
angle = Math.PI * 3.0 / 2.0;
}
}
else if (dy == 0.0)
{
if (dx > 0.0)
{
angle = 0.0;
}
else
{
angle = Math.PI;
}
}
else
{
if (dx < 0.0)
{
angle = Math.Atan(dy / dx) + Math.PI;
}
else if (dy < 0.0)
{
angle = Math.Atan(dy / dx) + (2 * Math.PI);
}
else
{
angle = Math.Atan(dy / dx);
}
}

// Convert to degrees
angle = angle * 180 / Math.PI;

// Return
return angle;
}

///
/// Get the DrawingAttributes to use for a highlighter stroke. The return value is a copy of
/// the DA passed in if color.A != 255 with color.A overriden to be 255. Otherwise it returns
/// the DA passed in.
///
internal static DrawingAttributes GetHighlighterAttributes(Stroke stroke, DrawingAttributes da)
{
System.Diagnostics.Debug.Assert(da.IsHighlighter = true);
if (da.Color.A != SolidStrokeAlpha)
{
DrawingAttributes copy = stroke.DrawingAttributes.Clone();
copy.Color = GetHighlighterColor(copy.Color);
return copy;
}

return da;
}

///
/// Get the color used to draw a highlighter.
///
internal static Color GetHighlighterColor(Color color)
{
// For a highlighter stroke, the color.A is overriden to be 255
color.A = SolidStrokeAlpha;
return color;
}

// Opacity for highlighter container visuals
internal static readonly double HighlighterOpacity = 0.5;
internal static readonly byte SolidStrokeAlpha = 0xFF;
internal static readonly Point ArcToMarker = new Point(Double.MinValue, Double.MinValue);

///
/// Simple helper enum
///
private enum RectCompareResult
{
Rect1ContainsRect2,
Rect2ContainsRect1,
NoItersection,
}
#endregion
}
}

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

SyntaxHighlighter.all()

```
{"ft_lang_label":"__label__en","ft_lang_prob":0.50247014,"math_prob":0.83870196,"size":67189,"snap":"2020-45-2020-50","text_gpt3_token_len":16390,"char_repetition_ratio":0.19054848,"word_repetition_ratio":0.9679733,"special_character_ratio":0.24771912,"punctuation_ratio":0.23159096,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9593445,"pos_list":[0,1,2],"im_url_duplicate_count":[null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2020-11-29T14:02:57Z\",\"WARC-Record-ID\":\"<urn:uuid:6684a9d3-04af-4980-af9d-1254e1a43759>\",\"Content-Length\":\"169856\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:4108851f-f9f6-4df0-9431-25f4eef64da3>\",\"WARC-Concurrent-To\":\"<urn:uuid:5c7e0c10-8bb2-4554-85ba-9ed3a42ae1d1>\",\"WARC-IP-Address\":\"78.129.237.3\",\"WARC-Target-URI\":\"http://reflector.webtropy.com/default.aspx/Dotnetfx_Vista_SP2/Dotnetfx_Vista_SP2/8@0@50727@4016/DEVDIV/depot/DevDiv/releases/Orcas/QFE/wpf/src/Core/CSharp/MS/Internal/Ink/StrokeRenderer@cs/1/StrokeRenderer@cs\",\"WARC-Payload-Digest\":\"sha1:EQGBNRVXKSNGJBPBSQCVYXGXDRSXDSKX\",\"WARC-Block-Digest\":\"sha1:NTXKSGBVSZOM7YZEGBIYWBYZSPLOSWDO\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2020/CC-MAIN-2020-50/CC-MAIN-2020-50_segments_1606141198409.43_warc_CC-MAIN-20201129123729-20201129153729-00674.warc.gz\"}"}