Example #1
0
        /// <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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        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);
        }