/// <remarks> /// Can be quite slow for very, very long lines of text /// </remarks> private static TextPointer GetPositionFromPointByLinearScan(Point point, TextRange range, bool snapToText) { PhysicalCharInfo result = null; var lineInfos = TextPointerOperations.GetPhysicalLines(range).ToArray(); switch (lineInfos.Length) { case 0: { result = snapToText ? new PhysicalCharInfo(range.Start) : null; break; } case 1: { result = TopToBottomLineHitTest(lineInfos.First(), point, snapToText, range.Start); break; } default: { // Optimization, scan the last line to quickly determine if the point is below the bottom line result = BottomToTopLineHitTest(lineInfos.Last(), point, snapToText, range.End); if (result == null) { // scan lines from top to bottom var fwdLineResults = from lineInfo in lineInfos.Take(lineInfos.Length - 1) select TopToBottomLineHitTest(lineInfo, point, snapToText, range.Start); result = fwdLineResults.FirstOrDefault(x => x != null); } break; } } Assert.Implies(snapToText, result != null); return((result != null) ? result.NearPosition : null); }
private static PhysicalCharInfo BottomToTopLineHitTest(PhysicalLineInfo lineInfo, Point point, bool snapToNearest, TextPointer end) { Rect lineBounds; PhysicalCharInfo result = lineInfo.HitTest(point, snapToNearest, out lineBounds); if (result == null) { if (lineBounds.IsEmpty || point.Y > lineBounds.Bottom) { // The point is below the bottom of the current line // since lines are being scanned from bottom to top // no other line can contain the point. // snap to the end of the range if (snapToNearest) { result = new PhysicalCharInfo(end); } } } return(result); }
private static PhysicalCharInfo TopToBottomLineHitTest(PhysicalLineInfo lineInfo, Point point, bool snapToNearest, TextPointer start) { Rect lineBounds; PhysicalCharInfo result = lineInfo.HitTest(point, snapToNearest, out lineBounds); if (result == null) { if (lineBounds.IsEmpty || point.Y < lineBounds.Top) { // The point is above the top of the current line // since lines are being scanned from top to bottom // no other line can contain the point. // Snap to the beginning of the range if (snapToNearest) { result = new PhysicalCharInfo(start); } } } return(result); }
/// <remarks> /// Often correct for LTR text with fairly uniform height and much much faster than a linear scan /// </remarks> private static TextPointer GetPositionFromPointByBisection(Point point, TextRange range) { TextRange currRange = new TextRange(range.Start, range.End); int currSpan; do { currSpan = GetSpan(currRange); if (currSpan < 1) { break; } TextPointer mid = currRange.Start.GetPositionAtOffset(currSpan / 2); mid = mid.GetInsertionPosition(prevCharDirection); if (mid == null) { break; } PhysicalCharInfo midCharInfo = new PhysicalCharInfo(mid); Rect nearEdge = midCharInfo.GetNearEdge(); Rect farEdge = midCharInfo.GetFarEdge(); bool isLeftToRight = nearEdge.X <= farEdge.X; Rect midRect = Rect.Union(nearEdge, farEdge); if (!midRect.IsEmpty) { if (midRect.Top > point.Y) { if (BisectRange(currRange, mid, PhysicalDirection.Up, isLeftToRight)) { continue; } break; } if (midRect.Bottom < point.Y) { if (BisectRange(currRange, mid, PhysicalDirection.Down, isLeftToRight)) { continue; } break; } if (midRect.Left > point.X) { if (BisectRange(currRange, mid, PhysicalDirection.Left, isLeftToRight)) { continue; } break; } if (midRect.Right < point.X) { if (BisectRange(currRange, mid, PhysicalDirection.Right, isLeftToRight)) { continue; } break; } } return(midCharInfo.HitTestHorizontal(point)); }while (GetSpan(currRange) < currSpan); return(null); }
public PhysicalCharInfo HitTest(Point point, bool snapToText, out Rect bounds) { PhysicalCharInfo result = null; bounds = Rect.Empty; double nearestDistance = double.PositiveInfinity; PhysicalCharInfo nearest = null; PhysicalCharInfo xResult = null; bool yResult = false; PhysicalCharInfo prevChar = null; foreach (var currChar in this.Characters) { Rect charBounds = currChar.GetBounds(); bounds.Union(charBounds); if (!yResult) { yResult = !bounds.IsEmpty && bounds.Top <= point.Y && point.Y < bounds.Bottom; // If true we have found a line that overlaps the hit-test point on the y-axis } if (xResult == null) { TextPointer candidate = currChar.HitTestHorizontal(point); if (candidate != null) { if (candidate.GetOffsetToPosition(currChar.NearPosition) == 0) { xResult = currChar; } else { Assert.IsNotNull(prevChar); Assert.AreEqual(0, candidate.GetOffsetToPosition(prevChar.NearPosition)); xResult = prevChar; } } } if (yResult && xResult != null) { // We have found a character that is in range of the hit-test point on the x-axis and y-axis result = xResult; break; } else { // Even if the the hit test point is outside this lines vertical range we will keep searching because // We may find a character further down the line that extends the lines vertical range and makes the // hit test result valid. } if (snapToText) { double distance = Math.Abs(charBounds.X - point.X); if (distance < nearestDistance) { nearestDistance = distance; nearest = currChar; } } prevChar = currChar; } if (result == null) { if (snapToText && yResult) { result = nearest; } } else { Assert.IsTrue(bounds.Contains(point)); } return(result); }