TextDocumentView.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / documents / TextDocumentView.cs / 1305600 / TextDocumentView.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: TextView implementation for FlowDocument pages. 
// 
// History:
//  03/04/2003 : [....] - Created 
//  06/18/2003 : [....] - Ported to wcp tree
//  06/25/2004 : [....] - Performance work
//
//--------------------------------------------------------------------------- 

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel;
using System.Windows; 
using System.Windows.Controls.Primitives; // IScrollInfo
using System.Windows.Documents;
using System.Windows.Media;
using MS.Internal.PtsHost; 
using MS.Internal.Text;
 
namespace MS.Internal.Documents 
{
    ///  
    /// TextView implementation for FlowDocument pages.
    /// 
    internal class TextDocumentView : TextViewBase
    { 
        //-------------------------------------------------------------------
        // 
        //  Constructors 
        //
        //------------------------------------------------------------------- 

        #region Constructors

        ///  
        /// Constructor.
        ///  
        ///  
        /// Root of layout structure visualizing content.
        ///  
        /// 
        /// TextContainer providing content for this view.
        /// 
        internal TextDocumentView(FlowDocumentPage owner, ITextContainer textContainer) 
        {
            _owner = owner; 
            _textContainer = textContainer; 
        }
 
        #endregion Constructors

        //--------------------------------------------------------------------
        // 
        //  Internal Methods
        // 
        //------------------------------------------------------------------- 

        #region Internal Methods 

        /// 
        /// 
        ///  
        internal override ITextPointer GetTextPositionFromPoint(Point point, bool snapToText)
        { 
            // Verify that layout information is valid. Cannot continue if not valid. 
            if (!IsValid)
            { 
                throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout));
            }

            _owner.EnsureValidVisuals(); 

            // Transforms point to content's coordinate system. 
            TransformToContent(ref point); 

            // Search columns 
            return GetTextPositionFromPoint(Columns, FloatingElements, point, snapToText);
        }

        ///  
        /// 
        ///  
        ///  
        /// TextDocumentView does not calculate any transform for this function. Transform returned is always identity.
        ///  
        internal override Rect GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
        {
            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid) 
            {
                throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); 
            } 
            ValidationHelper.VerifyPosition(_textContainer, position, "position");
            if (!ContainsCore(position)) 
            {
                throw new ArgumentOutOfRangeException("position");
            }
            _owner.EnsureValidVisuals(); 

            Rect rect = GetRectangleFromTextPosition(Columns, FloatingElements, position); 
 
            // Transforms Rect from content's coordinate system.
            TransformFromContent(ref rect, out transform); 

            return rect;
        }
 
        /// 
        ///  
        ///  
        internal override Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
        { 
            Geometry geometry = null;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid) 
            {
                throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); 
            } 

            ValidationHelper.VerifyPosition(_textContainer, startPosition, "startPosition"); 
            ValidationHelper.VerifyPosition(_textContainer, endPosition, "endPosition");

            _owner.EnsureValidVisuals();
 
            // Get visible rect, adjusted to owner's content offset
            Rect visibleRect = CalculateViewportRect(); 
 
            // First check floating elements
            bool success = false; 
            if (FloatingElements.Count > 0)
            {
                Geometry floatingElementGeometry = GetTightBoundingGeometryFromTextPositionsInFloatingElements(FloatingElements, startPosition, endPosition, 0.0, visibleRect, out success);
                // Add it to a geometry 
                CaretElement.AddGeometry(ref geometry, floatingElementGeometry);
                // Add content't transform to geometry. 
                if (geometry != null) 
                {
                    TransformFromContent(geometry); 
                }
            }

            if (!success) 
            {
                // Not found in floating elements, check columns 
                Invariant.Assert(geometry == null); 

                //  Note: since flow may decide to do calculations in background we 
                //  have to clamp by the current pointer range read from text segments.
                ReadOnlyCollection textSegments = TextSegments;
                for (int segmentIndex = 0; segmentIndex < textSegments.Count; segmentIndex++)
                { 
                    TextSegment textSegment = textSegments[segmentIndex];
 
                    // Identify boundary positions for this segment 
                    ITextPointer startPositionInThisSegment = startPosition.CompareTo(textSegment.Start) > 0 ? startPosition : textSegment.Start;
                    ITextPointer endPositionInThisSegment = endPosition.CompareTo(textSegment.End) < 0 ? endPosition : textSegment.End; 

                    // Skip the segment if not crossed by the selection
                    if (startPositionInThisSegment.CompareTo(endPositionInThisSegment) >= 0)
                    { 
                        continue;
                    } 
                    // Geometry not found in floating elements 
                    // Run loop for all columns
                    ReadOnlyCollection columns = Columns; 
                    for (int columnIndex = 0; columnIndex < columns.Count; columnIndex++)
                    {
                        // Skip the column if it is not in visible area
                        Rect columnBox = columns[columnIndex].LayoutBox; 

                        // Ignore horizontal offset because TextBox page width != extent width. 
                        // It's ok to include content that doesn't strictly intersect -- this 
                        // is a perf optimization and the edge cases won't significantly hurt us.
                        columnBox.X = visibleRect.X; 

                        if (!columnBox.IntersectsWith(visibleRect))
                        {
                            continue; 
                        }
 
                        // Build a highlight for this column 
                        Geometry columnGeometry = GetTightBoundingGeometryFromTextPositionsHelper(columns[columnIndex].Paragraphs, startPositionInThisSegment, endPositionInThisSegment, 0.0, visibleRect);
 
                        // Add it to a geometry
                        CaretElement.AddGeometry(ref geometry, columnGeometry);
                    }
                    // Add content't transform to geometry. 
                    if (geometry != null)
                    { 
                        TransformFromContent(geometry); 
                    }
                } 
            }

            return (geometry);
        } 

        // ------------------------------------------------------------------ 
        /// CalculateViewportRect - Called to calculate the visible rect for this element 
        // ------------------------------------------------------------------
        private Rect CalculateViewportRect() 
        {
            Rect visibleRect = Rect.Empty;
            if (RenderScope is IScrollInfo)
            { 
                IScrollInfo scrollInfo = (IScrollInfo)RenderScope;
                if (scrollInfo.ViewportWidth != 0 && scrollInfo.ViewportHeight != 0) 
                { 
                    visibleRect = new Rect(scrollInfo.HorizontalOffset, scrollInfo.VerticalOffset, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
                } 
            }

            if (visibleRect.IsEmpty)
            { 
                visibleRect = _owner.Viewport;
            } 
 
            TransformToContent(ref visibleRect);
 
            return visibleRect;
        }

        ///  
        /// 
        ///  
        internal override ITextPointer GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved) 
        {
            ITextPointer positionOut; 
            bool positionFound;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid) 
            {
                throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); 
            } 
            ValidationHelper.VerifyPosition(_textContainer, position, "position");
            if (!ContainsCore(position)) 
            {
                throw new ArgumentOutOfRangeException("position");
            }
 
            _owner.EnsureValidVisuals();
 
            // Initialy set linesMoved to 0 and newSuggestedX to suggestedX. Transform suggestedX to content 
            Point point = new Point(suggestedX, 0);
            TransformToContent(ref point); 
            suggestedX = newSuggestedX = point.X;
            linesMoved = count;

            if (count == 0) 
            {
                return position; 
            } 

            positionOut = GetPositionAtNextLine(Columns, FloatingElements, position, suggestedX, ref count, out newSuggestedX, out positionFound); 
            linesMoved -= count;
            point = new Point(newSuggestedX, 0);
            TransformFromContent(ref point);
            newSuggestedX = point.X; 

            // There might be a case when returned position is not in the view. 
            // Example: 

A

and the figure is delayed to the next page. // In such case, TextSegments do not contain content of Figure element and

. // Paragraph itself has 2 lines. The second line is empty and its position // cannot be represented as TextPointer belonging to TextSegments, because // Backward direction belongs to the first line and the Forward direction // belongs to the next page. if (positionOut == null || !ContainsCore(positionOut)) { positionOut = position; linesMoved = 0; } return positionOut; } /// /// /// internal override bool IsAtCaretUnitBoundary(ITextPointer position) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, position, "position"); if (!ContainsCore(position)) { throw new ArgumentOutOfRangeException("position"); } return IsAtCaretUnitBoundary(Columns, FloatingElements, position); } /// /// /// internal override ITextPointer GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, position, "position"); ValidationHelper.VerifyDirection(direction, "direction"); if (!ContainsCore(position)) { throw new ArgumentOutOfRangeException("position"); } return GetNextCaretUnitPosition(Columns, FloatingElements, position, direction); } /// /// /// internal override ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, position, "position"); if (!ContainsCore(position)) { throw new ArgumentOutOfRangeException("position"); } return GetBackspaceCaretUnitPosition(Columns, FloatingElements, position); } /// /// /// internal override TextSegment GetLineRange(ITextPointer position) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, position, "position"); if (!ContainsCore(position)) { throw new ArgumentOutOfRangeException("position"); } return GetLineRangeFromPosition(Columns, FloatingElements, position); } /// /// /// internal override ReadOnlyCollection GetGlyphRuns(ITextPointer start, ITextPointer end) { List glyphRuns = new List(); // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, start, "start"); ValidationHelper.VerifyPosition(_textContainer, end, "end"); ValidationHelper.VerifyPositionPair(start, end); if (!ContainsCore(start)) { throw new ArgumentOutOfRangeException("start"); } if (!ContainsCore(end)) { throw new ArgumentOutOfRangeException("end"); } GetGlyphRuns(glyphRuns, start, end, Columns, FloatingElements); return new ReadOnlyCollection(glyphRuns); } /// /// /// internal override bool Contains(ITextPointer position) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } ValidationHelper.VerifyPosition(_textContainer, position, "position"); return ContainsCore(position); } /// /// /// internal override bool Validate() { return this.IsValid; } /// /// /// internal override void ThrottleBackgroundTasksForUserInput() { _owner.StructuralCache.ThrottleBackgroundFormatting(); } /// /// Returns a cellinfo class for a point that may be inside of a cell /// /// /// Point to hit test /// /// /// Filter out all results not specific to a given table /// /// /// Returns cellinfo structure. /// internal CellInfo GetCellInfoFromPoint(Point point, Table tableFilter) { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { throw new InvalidOperationException(SR.Get(SRID.TextViewInvalidLayout)); } return GetCellInfoFromPoint(Columns, FloatingElements, point, tableFilter); } /// /// Raise TextView.Updated event. /// internal void OnUpdated() { OnUpdated(EventArgs.Empty); } /// /// Invalidate TextView internal state. /// internal void Invalidate() { _columns = null; _segments = null; _floatingElements = null; } /// /// Determines whenever TextSegment collection contains specified position. /// /// A position to test. /// Collection of TextSegments to test against. /// /// True if TextSegment collection contains specified text position. /// Otherwise returns false. /// internal static bool Contains(ITextPointer position, ReadOnlyCollection segments) { bool contains = false; Invariant.Assert(segments != null); // Iterate through all segments and check if position is inside one of them. // Position is inside of a segment if: // a) it is between Start and End boundaries (exclusive), or // b) at the Start and direction is Forward, or // c) at the End and direction is Backward foreach (TextSegment segment in segments) { if (segment.Start.CompareTo(position) < 0 && segment.End.CompareTo(position) > 0) { contains = true; break; } if (segment.Start.CompareTo(position) == 0) { if (position.LogicalDirection == LogicalDirection.Forward) { // Position has forward context, and is always contained in the view whether the segment start has // forward or backward context contains = true; break; } else { // Position has backward context. It is contained in the segment only if the segment start also has backward context if (segment.Start.LogicalDirection == LogicalDirection.Backward) { contains = true; break; } } } if (segment.End.CompareTo(position) == 0) { if (position.LogicalDirection == LogicalDirection.Backward) { // Position has backward context, and is always contained in the view whether the segment end has // forward or backward context contains = true; break; } else { // Position has forward context. It is contained in the segment only if the segment end also has forward context if (segment.End.LogicalDirection == LogicalDirection.Forward) { contains = true; break; } } } } // If position is at the beginning or the end of TextContainer, ignore // its direction, because it is necessary to treat such positions as valid // for editing scenarios. if (!contains && segments.Count > 0) { if (position.TextContainer.Start.CompareTo(position) == 0 && position.LogicalDirection == LogicalDirection.Backward) { contains = (position.TextContainer.Start.CompareTo(segments[0].Start) == 0); } else if (position.TextContainer.End.CompareTo(position) == 0 && position.LogicalDirection == LogicalDirection.Forward) { contains = (position.TextContainer.End.CompareTo(segments[segments.Count - 1].End) == 0); } } return contains; } #endregion Internal Methods //------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------- #region Internal Properties /// /// /// internal override UIElement RenderScope { get { UIElement renderScope = null; if (!_owner.IsDisposed) { // The RenderScope in this case is typically DocumentPageView Visual visual = _owner.Visual; while (visual != null && !(visual is UIElement)) { visual = VisualTreeHelper.GetParent(visual) as Visual; } renderScope = visual as UIElement; } return renderScope; } } /// /// /// internal override ITextContainer TextContainer { get { return _textContainer; } } /// /// /// internal override bool IsValid { get { return _owner.IsLayoutDataValid; } } /// /// /// internal override ReadOnlyCollection TextSegments { get { // Verify that layout information is valid. Cannot continue if not valid. if (!IsValid) { return new ReadOnlyCollection(new List()); } return this.TextSegmentsCore; } } private ReadOnlyCollection TextSegmentsCore { get { if (_segments == null) { _segments = GetTextSegments(); Invariant.Assert(_segments != null, "TextSegment collection is empty."); } return _segments; } } /// /// Collection of ColumnResults for each line in the paragraph. /// internal ReadOnlyCollection Columns { get { Invariant.Assert(IsValid, "TextView is not updated."); if (_columns == null) { // When getting column results, query each on for text content, used to determine if the view has text content _columns = _owner.GetColumnResults(out _hasTextContent); Invariant.Assert(_columns != null, "Column collection is null."); } return _columns; } } /// /// Collection of ParagraphResults for floating elements /// internal ReadOnlyCollection FloatingElements { get { Invariant.Assert(IsValid, "TextView is not updated."); if (_floatingElements == null) { _floatingElements = _owner.FloatingElementResults; Invariant.Assert(_floatingElements != null, "Floating elements collection is null."); } return _floatingElements; } } #endregion Internal Properties //------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------- #region Private Methods /// /// Retrieves a position matching a point. Checks floating elements first. /// /// /// Collection of paragraphs. /// /// /// Collection of floating elements /// /// /// Point in pixel coordinates to test. /// /// /// If true, this method must always return a positioned text position /// (the closest position as calculated by the control's heuristics). /// If false, this method should return null position, if the test /// point does not fall within any character bounding box. /// /// /// Indicates that none of the paragraphs in the collection has any text content so we must return something from the floating elements collection /// /// /// A text position and its orientation matching or closest to the point. /// private ITextPointer GetTextPositionFromPoint(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, Point point, bool snapToText, bool snapToTextInFloatingElements) { ITextPointer position; int paragraphIndex; Invariant.Assert(paragraphs != null, "Paragraph collection is empty."); Invariant.Assert(floatingElements != null, "Floating element collection is empty."); // Figure out which paragraph is the closest to the input pixel position. First search floating elements paragraphIndex = GetParagraphFromPointInFloatingElements(floatingElements, point, snapToTextInFloatingElements); ParagraphResult paragraph; if (paragraphIndex < 0) { // Not found in floating elements Invariant.Assert(!snapToTextInFloatingElements || floatingElements.Count == 0, "When snap to text is enabled a valid text position is required if paragraphs exist."); if (snapToTextInFloatingElements) { return null; } else { // Keep searching paragraphs paragraphIndex = GetParagraphFromPoint(paragraphs, point, snapToText); // If no paragraph is hit, return null text position. // Otherwise hittest paragraph content. if (paragraphIndex < 0) { Invariant.Assert(!snapToText || paragraphs.Count == 0, "When snap to text is enabled a valid text position is required if paragraphs exist."); return null; } else { Invariant.Assert(paragraphIndex < paragraphs.Count); paragraph = paragraphs[paragraphIndex]; } } } else { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } position = GetTextPositionFromPoint(paragraph, point, snapToText); return position; } /// /// Retrieves a position matching a point from a given paragraph /// /// /// Para containing position /// /// /// Point in pixel coordinates to test. /// /// /// If true, this method must always return a positioned text position /// (the closest position as calculated by the control's heuristics). /// If false, this method should return null position, if the test /// point does not fall within any character bounding box. /// private ITextPointer GetTextPositionFromPoint(ParagraphResult paragraph, Point point, bool snapToText) { ITextPointer position = null; Rect paragraphBox = paragraph.LayoutBox; // Position is retrieved differently for different paragraph types: // a) ContainerParagraph, FigureParagraph, FloaterParagraph - hittest colleciton of nested paragraphs. // b) TextParagraph - hittest line collection. // c) TableParagraph - hittest in table // d) Other paragraphs - return position before/after paragraph element. if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - hittest colleciton of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be null in case of empty List element, Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { position = GetTextPositionFromPoint(nestedParagraphs, _emptyParagraphCollection, point, snapToText, /* snap to text for floating elements*/ false); } else { // Return position before/after paragraph element. if (point.X <= paragraphBox.Width) { position = paragraph.StartPosition.CreatePointer(LogicalDirection.Forward); } else { position = paragraph.EndPosition.CreatePointer(LogicalDirection.Backward); } } } else if (paragraph is TextParagraphResult) { // b) TextParagraph - hittest line collection. ReadOnlyCollection lines = ((TextParagraphResult)paragraph).Lines; Invariant.Assert(lines != null, "Lines collection is null"); if (!((TextParagraphResult)paragraph).HasTextContent) { position = null; } else { position = TextParagraphView.GetTextPositionFromPoint(lines, point, snapToText); } } else if (paragraph is TableParagraphResult) { ReadOnlyCollection rowParagraphs = ((TableParagraphResult)paragraph).Paragraphs; Invariant.Assert(rowParagraphs != null, "Paragraph collection is null."); int index = GetParagraphFromPoint(rowParagraphs, point, snapToText); if (index != -1) { ParagraphResult rowResult = rowParagraphs[index]; if (point.X > rowResult.LayoutBox.Right) { position = ((TextElement)rowResult.Element).ElementEnd; } else { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPoint(point, snapToText); position = GetTextPositionFromPoint(nestedParagraphs, _emptyParagraphCollection, point, snapToText, false); } } else { // Table is empty. position = null; // When snap to text is enabled a valid text position is required. if (snapToText) { position = ((TextElement)paragraph.Element).ContentStart; } } } else if (paragraph is SubpageParagraphResult) { // Subpage implies new coordinate system. SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; point.X -= subpageParagraphResult.ContentOffset.X; point.Y -= subpageParagraphResult.ContentOffset.Y; // WOOT! COLUMNS! position = GetTextPositionFromPoint(subpageParagraphResult.Columns, subpageParagraphResult.FloatingElements, point, snapToText); } else if (paragraph is FigureParagraphResult || paragraph is FloaterParagraphResult) { ReadOnlyCollection columns; ReadOnlyCollection nestedFloatingElements; if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; columns = floaterParagraphResult.Columns; nestedFloatingElements = floaterParagraphResult.FloatingElements; TransformToSubpage(ref point, floaterParagraphResult.ContentOffset); } else { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; columns = figureParagraphResult.Columns; nestedFloatingElements = figureParagraphResult.FloatingElements; TransformToSubpage(ref point, figureParagraphResult.ContentOffset); } // Paragraphs collection may be null in case of empty List element, Invariant.Assert(columns != null, "Columns collection is null."); Invariant.Assert(nestedFloatingElements != null, "Floating elements collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { position = GetTextPositionFromPoint(columns, nestedFloatingElements, point, snapToText); } else { position = null; } } else if (paragraph is UIElementParagraphResult) { BlockUIContainer blockUIContainer = paragraph.Element as BlockUIContainer; if (blockUIContainer != null) { position = null; if (paragraphBox.Contains(point) || snapToText) { // Point is with BUIC's layout box. Return paragraph's ContentStart/End as appropriate if (DoubleUtil.LessThanOrClose(point.X, paragraphBox.X + paragraphBox.Width / 2)) { position = blockUIContainer.ContentStart.CreatePointer(LogicalDirection.Forward); } else { position = blockUIContainer.ContentEnd.CreatePointer(LogicalDirection.Backward); } } } } else { // d) Other paragraphs - return position before/after paragraph element. if (point.X <= paragraphBox.Width) { position = paragraph.StartPosition.CreatePointer(LogicalDirection.Forward); } else { position = paragraph.EndPosition.CreatePointer(LogicalDirection.Backward); } } return position; } /// /// Retrieves a position matching a point in a paragraph collection. /// private ITextPointer GetTextPositionFromPoint(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, Point point, bool snapToText) { ITextPointer position = null; Invariant.Assert(floatingElements != null); int columnIndex = GetColumnFromPoint(columns, point, snapToText); // If no column is hit, return null text position. This can also occur if column count is 0 // Otherwise hittest column content. if (columnIndex < 0 && floatingElements.Count == 0) { position = null; } else { // Retrieve position from column. ReadOnlyCollection paragraphs; bool snapToTextInFloatingElements = false; if (columnIndex < columns.Count && columnIndex >= 0) { ColumnResult column = columns[columnIndex]; if (!(column.HasTextContent)) { snapToTextInFloatingElements = true; } paragraphs = column.Paragraphs; } else { paragraphs = _emptyParagraphCollection; } position = GetTextPositionFromPoint(paragraphs, floatingElements, point, snapToText, snapToTextInFloatingElements); } // There might be a case when returned position is not in the view. // Example:

A

and the figure is delayed to the next page. // In such case, TextSegments do not contain content of Figure element and

. // Paragraph itself has 2 lines. The second line is empty and its position // cannot be represented as TextPointer belonging to TextSegments, because // Backward direction belongs to the first line and the Forward direction // belongs to the next page. if (position != null && !ContainsCore(position)) { position = null; } return position; } /// /// Returns a cellinfo class for a point that may be inside of a cell /// /// /// Paras to hit test into /// /// /// Floating elements to hit test into /// /// /// Point to hit test /// /// /// Filter out all results not specific to a given table /// private CellInfo GetCellInfoFromPoint(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, Point point, Table tableFilter) { CellInfo cellInfo = null; Invariant.Assert(paragraphs != null, "Paragraph collection is empty."); Invariant.Assert(floatingElements != null, "Floating element collection is empty."); // Figure out which paragraph is the closest to the input pixel position. // Search floating elements first, then paragraphs collection. Do not snap to text in either floating elements or // main flow when searching for cell info. int paragraphIndex = GetParagraphFromPointInFloatingElements(floatingElements, point, false); ParagraphResult paragraph = null; if (paragraphIndex >= 0) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { paragraphIndex = GetParagraphFromPoint(paragraphs, point, false); if (paragraphIndex >= 0) { Invariant.Assert(paragraphIndex < paragraphs.Count); paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { cellInfo = GetCellInfoFromPoint(paragraph, point, tableFilter); } return cellInfo; } /// /// Returns a cellinfo class for a point that may be inside of a cell /// /// /// Para to hit test into /// /// /// Point to hit test /// /// /// Filter out all results not specific to a given table /// private CellInfo GetCellInfoFromPoint(ParagraphResult paragraph, Point point, Table tableFilter) { // Figure out which paragraph is the closest to the input pixel position. CellInfo cellInfo = null; if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - hittest colleciton of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be empty, but should never be null Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null"); if (nestedParagraphs.Count > 0) { cellInfo = GetCellInfoFromPoint(nestedParagraphs, _emptyParagraphCollection, point, tableFilter); } } else if (paragraph is TableParagraphResult) { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPoint(point, false); Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null"); if (nestedParagraphs.Count > 0) { cellInfo = GetCellInfoFromPoint(nestedParagraphs, _emptyParagraphCollection, point, tableFilter); } if (cellInfo == null) { cellInfo = ((TableParagraphResult)paragraph).GetCellInfoFromPoint(point); } } else if (paragraph is SubpageParagraphResult) { // Subpage implies new coordinate system. SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; point.X -= subpageParagraphResult.ContentOffset.X; point.Y -= subpageParagraphResult.ContentOffset.Y; // WOOT! COLUMNS! cellInfo = GetCellInfoFromPoint(subpageParagraphResult.Columns, subpageParagraphResult.FloatingElements, point, tableFilter); if (cellInfo != null) { cellInfo.Adjust(new Point(subpageParagraphResult.ContentOffset.X, subpageParagraphResult.ContentOffset.Y)); } } else if (paragraph is FigureParagraphResult) { // Subpage implies new coordinate system. FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; TransformToSubpage(ref point, figureParagraphResult.ContentOffset); cellInfo = GetCellInfoFromPoint(figureParagraphResult.Columns, figureParagraphResult.FloatingElements, point, tableFilter); if (cellInfo != null) { cellInfo.Adjust(new Point(figureParagraphResult.ContentOffset.X, figureParagraphResult.ContentOffset.Y)); } } else if (paragraph is FloaterParagraphResult) { // Subpage implies new coordinate system. FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; TransformToSubpage(ref point, floaterParagraphResult.ContentOffset); cellInfo = GetCellInfoFromPoint(floaterParagraphResult.Columns, floaterParagraphResult.FloatingElements, point, tableFilter); if (cellInfo != null) { cellInfo.Adjust(new Point(floaterParagraphResult.ContentOffset.X, floaterParagraphResult.ContentOffset.Y)); } } if (tableFilter != null && cellInfo != null && cellInfo.Cell.Table != tableFilter) { cellInfo = null; // Clear out result if not matching input filter } return cellInfo; } /// /// Retrieves a CellInfo from a given point, traversing through columns. /// private CellInfo GetCellInfoFromPoint(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, Point point, Table tableFilter) { Invariant.Assert(floatingElements != null); int columnIndex = GetColumnFromPoint(columns, point, false); CellInfo cellInfo; // If no column is hit, return null CellInfo. // Otherwise hittest column content. if (columnIndex < 0 && floatingElements.Count == 0) { cellInfo = null; } else { // Retrieve position from column. ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; cellInfo = GetCellInfoFromPoint(paragraphs, floatingElements, point, tableFilter); } return cellInfo; } /// /// Retrieves the height and offset, in pixels, of the edge of /// the object/character represented by position. /// /// Collection of paragraphs. /// collection of floating elements /// Position of an object/character. private Rect GetRectangleFromTextPosition(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position) { Invariant.Assert(paragraphs != null, "Paragraph collection is null"); Invariant.Assert(floatingElements != null, "Floating element collection is null"); Rect rect = Rect.Empty; // Figure out which paragraph contains text position. bool isFloatingPara = false; int paragraphIndex = GetParagraphFromPosition(paragraphs, floatingElements, position, out isFloatingPara); ParagraphResult paragraph = null; if (isFloatingPara) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { if (paragraphIndex < paragraphs.Count) { paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { rect = GetRectangleFromTextPosition(paragraph, position); } return rect; } /// /// Retrieves the height and offset, in pixels, of the edge of /// the object/character represented by position. /// /// Paragraph to search /// Position of an object/character. private Rect GetRectangleFromTextPosition(ParagraphResult paragraph, ITextPointer position) { Rect rect = Rect.Empty; // Rectangle is retrieved differently for different paragraph types: // a) ContainerParagraph - get rectangle from nested paragraphs. // b) TextParagraph - get rectangle from text paragraph's content. // c) TableParagraph - get rectangle from nested paras if (paragraph is ContainerParagraphResult) { rect = GetRectangleFromEdge(paragraph, position); if (rect == Rect.Empty) { // a) ContainerParagraph - check collection of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { rect = GetRectangleFromTextPosition(nestedParagraphs, _emptyParagraphCollection, position); } } } else if (paragraph is TextParagraphResult) { rect = ((TextParagraphResult)paragraph).GetRectangleFromTextPosition(position); } else if (paragraph is TableParagraphResult) { // c) TableParagraph - get rectangle from nested paras rect = GetRectangleFromEdge(paragraph, position); if (rect == Rect.Empty) { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPosition(position); Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { rect = GetRectangleFromTextPosition(nestedParagraphs, _emptyParagraphCollection, position); } else if (position is TextPointer && ((TextPointer)position).IsAtRowEnd) { rect = ((TableParagraphResult)paragraph).GetRectangleFromRowEndPosition(position); } } } else if (paragraph is SubpageParagraphResult) { // Subpage implies new coordinate system. SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; rect = GetRectangleFromTextPosition(subpageParagraphResult.Columns, subpageParagraphResult.FloatingElements, position); if (rect != Rect.Empty) { rect.X += subpageParagraphResult.ContentOffset.X; rect.Y += subpageParagraphResult.ContentOffset.Y; } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Columns collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (nestedFloatingElements.Count > 0 || columns.Count > 0) { rect = GetRectangleFromTextPosition(columns, nestedFloatingElements, position); // Add content offset to rect TransformFromSubpage(ref rect, floaterParagraphResult.ContentOffset); } } else if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Columns collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (nestedFloatingElements.Count > 0 || columns.Count > 0) { rect = GetRectangleFromTextPosition(columns, nestedFloatingElements, position); // Add content offset to rect TransformFromSubpage(ref rect, figureParagraphResult.ContentOffset); } } else if (paragraph is UIElementParagraphResult) { rect = GetRectangleFromEdge(paragraph, position); if (rect == Rect.Empty) { // For a UIElementParagraph, we should check if element is either at Element start/end or Content Start end // This is needed to enable selection of embedded object w/ mouse click rect = GetRectangleFromContentEdge(paragraph, position); } } return rect; } /// /// Returns a rectangle for a text position, traversing through columns. /// private Rect GetRectangleFromTextPosition(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position) { Rect rect = Rect.Empty; Invariant.Assert(floatingElements != null); // Figure out which column contains text position. int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count || floatingElements.Count > 0) { // Retrieve rectangle from the retrieved column. ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; rect = GetRectangleFromTextPosition(paragraphs, floatingElements, position); } return rect; } /// /// Delegates tight bounding geometry calculation to the appropriate paragraph result /// object depending on paragraph result type. /// Returns tight bounding path geometry. /// internal static Geometry GetTightBoundingGeometryFromTextPositionsHelper( ReadOnlyCollection paragraphs, ITextPointer startPosition, ITextPointer endPosition, double paragraphTopSpace, Rect visibleRect) { Geometry geometry = null; int paragraphCount = paragraphs.Count; for (int i = 0; i < paragraphCount; i++) { if (endPosition.CompareTo(paragraphs[i].StartPosition) <= 0) { // this paragraph starts after the range's end. // safe to break from the loop. break; } if (startPosition.CompareTo(paragraphs[i].EndPosition) > 0) { // this paragraph ends before the range's start // safe to skip to the next paragraph continue; } Rect layoutBox = GetLayoutBox(paragraphs[i]); // Ignore horizontal offset because TextBox page width != extent width. // It's ok to include content that doesn't strictly intersect -- this // is a perf optimization and the edge cases won't significantly hurt us. layoutBox.X = visibleRect.X; if (!layoutBox.IntersectsWith(visibleRect)) { // this paragraph falls beyond visible rectangle // safe to skip to the next paragraph continue; } Geometry paragraphGeometry = null; if (paragraphs[i] is ContainerParagraphResult) { paragraphGeometry = ((ContainerParagraphResult)paragraphs[i]).GetTightBoundingGeometryFromTextPositions(startPosition, endPosition, visibleRect); } else if (paragraphs[i] is TextParagraphResult) { paragraphGeometry = ((TextParagraphResult)paragraphs[i]).GetTightBoundingGeometryFromTextPositions(startPosition, endPosition, paragraphTopSpace, visibleRect); } else if (paragraphs[i] is TableParagraphResult) { paragraphGeometry = ((TableParagraphResult)paragraphs[i]).GetTightBoundingGeometryFromTextPositions(startPosition, endPosition, visibleRect); } else if (paragraphs[i] is UIElementParagraphResult) { paragraphGeometry = ((UIElementParagraphResult)paragraphs[i]).GetTightBoundingGeometryFromTextPositions(startPosition, endPosition); } CaretElement.AddGeometry(ref geometry, paragraphGeometry); } return geometry; } /// /// Delegates tight bounding geometry calculation to the appropriate paragraph result /// First checks floating paragraph results and then regular paragraph results /// internal static Geometry GetTightBoundingGeometryFromTextPositionsHelper( ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer startPosition, ITextPointer endPosition, double paragraphTopSpace, Rect visibleRect) { Geometry geometry = null; bool success = false; if (floatingElements != null && floatingElements.Count > 0) { geometry = GetTightBoundingGeometryFromTextPositionsInFloatingElements(floatingElements, startPosition, endPosition, paragraphTopSpace, visibleRect, out success); } if (!success) { // Not found in floaitng elements, check regular paragraph colleciton. geometry = GetTightBoundingGeometryFromTextPositionsHelper(paragraphs, startPosition, endPosition, paragraphTopSpace, visibleRect); } return geometry; } /// /// Delegates tight bounding geometry calculation to the appropriate paragraph result /// object depending on paragraph result type. Helper function for searching floating elements. /// Returns tight bounding path geometry. /// private static Geometry GetTightBoundingGeometryFromTextPositionsInFloatingElements( ReadOnlyCollection floatingElements, ITextPointer startPosition, ITextPointer endPosition, double paragraphTopSpace, Rect visibleRect, out bool success) { Geometry geometry = null; success = false; int paragraphCount = floatingElements.Count; for (int i = 0; i < paragraphCount; i++) { if (!(startPosition.CompareTo(floatingElements[i].StartPosition) > 0 && endPosition.CompareTo(floatingElements[i].EndPosition) < 0)) { // Selection range is not contained entirely within the floating element. // We cannot include any of it since we should give priority to any text content in this case. continue; } Rect layoutBox = GetLayoutBox(floatingElements[i]); Rect visibleRectThisPara = visibleRect; // Ignore horizontal offset because TextBox page width != extent width. // It's ok to include content that doesn't strictly intersect -- this // is a perf optimization and the edge cases won't significantly hurt us. layoutBox.X = visibleRectThisPara.X; if (!layoutBox.IntersectsWith(visibleRectThisPara)) { // this paragraph falls beyond visible rectangle // safe to skip to the next paragraph continue; } Geometry paragraphGeometry = null; Invariant.Assert(floatingElements[i] is FloaterParagraphResult || floatingElements[i] is FigureParagraphResult); if (floatingElements[i] is FloaterParagraphResult) { // Transform visible rect to subpage coordinates, and transform geometry from subpage coordinates FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)floatingElements[i]; TransformToSubpage(ref visibleRectThisPara, floaterParagraphResult.ContentOffset); paragraphGeometry = floaterParagraphResult.GetTightBoundingGeometryFromTextPositions(startPosition, endPosition, visibleRectThisPara, out success); // Geometry within the floater needs to be transformed from subpage content TransformFromSubpage(paragraphGeometry, floaterParagraphResult.ContentOffset); } else if (floatingElements[i] is FigureParagraphResult) { // Transform visible rect to subpage coordinates, and transform geometry from subpage coordinates FigureParagraphResult figureParagraphResult = (FigureParagraphResult)floatingElements[i]; TransformToSubpage(ref visibleRectThisPara, figureParagraphResult.ContentOffset); paragraphGeometry = figureParagraphResult.GetTightBoundingGeometryFromTextPositions(startPosition, endPosition, visibleRectThisPara, out success); // Geometry within the figure needs to be transformed from subpage content TransformFromSubpage(paragraphGeometry, figureParagraphResult.ContentOffset); } CaretElement.AddGeometry(ref geometry, paragraphGeometry); if (success) { // If we find geometry inside one floating element, we cannot find it inside another. Selection inside a floating element cannot leave the // floating element break; } } return geometry; } // Retreives a layout box for the paragraphResult private static Rect GetLayoutBox(ParagraphResult paragraph) { if (!(paragraph is SubpageParagraphResult) && !(paragraph is RowParagraphResult)) { return paragraph.LayoutBox; } return Rect.Empty; } /// /// Returns true if caret is at unit boundary /// /// Collection of paragraphs. /// Collection of floating elements /// Position of an object/character. private bool IsAtCaretUnitBoundary(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position) { Invariant.Assert(paragraphs != null, "Paragraph collection is null"); Invariant.Assert(floatingElements != null, "Floating element collection is null"); bool isAtCaretUnitBoundary = false; bool isFloatingPara; int paragraphIndex = GetParagraphFromPosition(paragraphs, floatingElements, position, out isFloatingPara); ParagraphResult paragraph = null; if (isFloatingPara) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { if (paragraphIndex < paragraphs.Count) { paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(paragraph, position); } return isAtCaretUnitBoundary; } /// /// Returns true if caret is at unit boundary /// /// Paragraph to search. /// Position of an object/character. private bool IsAtCaretUnitBoundary(ParagraphResult paragraph, ITextPointer position) { bool isAtCaretUnitBoundary = false; if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - go to collection of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is TextParagraphResult) { // b) TextParagraph - search inside it isAtCaretUnitBoundary = ((TextParagraphResult)paragraph).IsAtCaretUnitBoundary(position); } else if (paragraph is TableParagraphResult) { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPosition(position); Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is SubpageParagraphResult) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; ReadOnlyCollection columns = subpageParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = subpageParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(columns, nestedFloatingElements, position); } } else if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(columns, nestedFloatingElements, position); } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { isAtCaretUnitBoundary = IsAtCaretUnitBoundary(columns, nestedFloatingElements, position); } } return isAtCaretUnitBoundary; } /// /// Returns true if caret is at unit boundary /// private bool IsAtCaretUnitBoundary(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position) { int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count || floatingElements.Count > 0) { ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; return IsAtCaretUnitBoundary(paragraphs, floatingElements, position); } return false; } /// /// Finds and returns the next position at the edge of a caret unit in /// specified direction. /// /// Collection of paragraphs. /// Collection of floating elements. /// Position of an object/character. /// Direction in which we seek next caret position private ITextPointer GetNextCaretUnitPosition(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position, LogicalDirection direction) { Invariant.Assert(paragraphs != null, "Paragraph collection is null"); Invariant.Assert(floatingElements != null, "Floating element collection is null"); ITextPointer nextCaretPosition = position; bool isFloatingPara; int paragraphIndex = GetParagraphFromPosition(paragraphs, floatingElements, position, out isFloatingPara); ParagraphResult paragraph = null; if (isFloatingPara) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { if (paragraphIndex < paragraphs.Count) { paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { nextCaretPosition = GetNextCaretUnitPosition(paragraph, position, direction); } return nextCaretPosition; } /// /// Finds and returns the next position at the edge of a caret unit in /// specified direction. /// /// Paragraph to search /// Position of an object/character. /// Direction in which we seek next caret position private ITextPointer GetNextCaretUnitPosition(ParagraphResult paragraph, ITextPointer position, LogicalDirection direction) { ITextPointer nextCaretPosition = position; if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - go to collection of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { nextCaretPosition = GetNextCaretUnitPosition(nestedParagraphs, _emptyParagraphCollection, position, direction); } // Else: Illegal call from outside TextView, return same position } else if (paragraph is TextParagraphResult) { // b) TextParagraph - search inside it nextCaretPosition = ((TextParagraphResult)paragraph).GetNextCaretUnitPosition(position, direction); } else if (paragraph is TableParagraphResult) { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPosition(position); Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { nextCaretPosition = GetNextCaretUnitPosition(nestedParagraphs, _emptyParagraphCollection, position, direction); } } else if (paragraph is SubpageParagraphResult) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; ReadOnlyCollection columns = subpageParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = subpageParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { nextCaretPosition = GetNextCaretUnitPosition(columns, nestedFloatingElements, position, direction); } } else if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { nextCaretPosition = GetNextCaretUnitPosition(columns, nestedFloatingElements, position, direction); } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { nextCaretPosition = GetNextCaretUnitPosition(columns, nestedFloatingElements, position, direction); } } return nextCaretPosition; } /// /// Finds and returns the next position at the edge of a caret unit in /// specified direction. /// private ITextPointer GetNextCaretUnitPosition(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position, LogicalDirection direction) { int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count || floatingElements.Count > 0) { ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; return GetNextCaretUnitPosition(paragraphs, floatingElements, position, direction); } return position; } /// /// Finds and returns the backspace position of a caret unit /// /// Collection of paragraphs. /// Collection of floating elements /// Position of an object/character. private ITextPointer GetBackspaceCaretUnitPosition(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position) { Invariant.Assert(paragraphs != null, "Paragraph collection is null"); Invariant.Assert(floatingElements != null, "Floating element collection is null"); ITextPointer backspaceCaretPosition = position; bool isFloatingPara; int paragraphIndex = GetParagraphFromPosition(paragraphs, floatingElements, position, out isFloatingPara); ParagraphResult paragraph = null; if (isFloatingPara) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { if (paragraphIndex < paragraphs.Count) { paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(paragraph, position); } return backspaceCaretPosition; } /// /// Finds and returns the backspace position of a caret unit /// /// Paragraph to search. /// Position of an object/character. private ITextPointer GetBackspaceCaretUnitPosition(ParagraphResult paragraph, ITextPointer position) { ITextPointer backspaceCaretPosition = position; if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - go to collection of nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is TextParagraphResult) { // b) TextParagraph - search inside it backspaceCaretPosition = ((TextParagraphResult)paragraph).GetBackspaceCaretUnitPosition(position); } else if (paragraph is TableParagraphResult) { ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPosition(position); Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is SubpageParagraphResult) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; ReadOnlyCollection columns = subpageParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = subpageParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(columns, nestedFloatingElements, position); } } else if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(columns, nestedFloatingElements, position); } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { backspaceCaretPosition = GetBackspaceCaretUnitPosition(columns, nestedFloatingElements, position); } } return backspaceCaretPosition; } /// /// Finds and returns the backspace position of a caret unit /// private ITextPointer GetBackspaceCaretUnitPosition(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position) { int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count || floatingElements.Count > 0) { ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; return GetBackspaceCaretUnitPosition(paragraphs, floatingElements, position); } return position; } /// /// HitTest a column collection. /// /// Collection of columns. /// Point in pixel coordinates to test. /// /// If true, this method must always return a column index /// (the closest position as calculated by the control's heuristics). /// If false, this method should return -1, if the test /// point does not fall within any column bounding box. /// /// /// An index of column matching or closest to the point. /// private int GetColumnFromPoint(ReadOnlyCollection columns, Point point, bool snapToText) { int columnIndex; int lastColumnWithContent = -1; Rect columnBox; bool foundHit = false; Invariant.Assert(columns != null, "Column collection is null"); // Figure out which column is the closest horizontally to the input pixel position. // There are following assumptions made: // * column[N].LayoutBox.Left > column[N+1].LayoutBox.Left // // NOTE: Snapping, if necessary, is done first in horizontal direction. for (columnIndex = 0; columnIndex < columns.Count; columnIndex++) { columnBox = columns[columnIndex].LayoutBox; if (!(columns[columnIndex].HasTextContent)) { if (columnIndex == columns.Count - 1) { // We are at the last column, and if we didn't find anything else with content, we must stop here lastColumnWithContent = (lastColumnWithContent == -1) ? columnIndex : lastColumnWithContent; foundHit = snapToText; } // Since column has no text content, skip checking it's layout box. continue; } else { lastColumnWithContent = columnIndex; } Invariant.Assert(lastColumnWithContent == columnIndex); // There are following possibilities: // a) Point is to the left of the column.Left. // In this case consider the current column. This will be true only // for the first column, because all others should be taken care off // during b) & c) // b) Point is to the right of the column.Right. // If the point does not intersect with the next column, consider // the closest column (or this column if it is the last one). // c) Point intersects with the current column. // If this column does not overlap with the next one, return it. // But if it does overlap, it means that there is overflow in this column // and the next column should be considered. if (point.X < columnBox.Left) { // a) Point is to the left of the column.Left. // In this case consider the current column. This will be true only // for the first column, because all others should be taken care off // during b) & c) foundHit = snapToText; break; } else if (point.X > columnBox.Right) { // b) Point is to the right of the column.Right // If the point does not intersect with the next column, consider // the closest column (or this column if it is the last one). if (columnIndex < columns.Count - 1) { Rect nextColumnBox = columns[columnIndex + 1].LayoutBox; if (point.X < nextColumnBox.Left) { // Point is in the gap between columns. Use the closest one. double gap = nextColumnBox.Left - columnBox.Right; if (point.X > columnBox.Right + gap / 2 && columns[columnIndex + 1].HasTextContent) { ++columnIndex; lastColumnWithContent = columnIndex; } foundHit = snapToText; break; } // else continue to the next column } else { // This is the last column. foundHit = snapToText; break; } } else { // c) Point intersects with the current column. // If this column does not overlap with the next one, return it. // But if it does overlap, it means that there is an overflow in the columm // and the next column should be considered. if (columnIndex < columns.Count - 1) { Rect nextColumnBox = columns[columnIndex + 1].LayoutBox; if (point.X < nextColumnBox.Left) { // Point is not over the next column and this column has been hit. foundHit = true; break; } // else - Continue to the next column. } else { // This is the last column and it has been hit. foundHit = true; break; } } } // Check if the input pixel position is vertically inside column boundary. if (foundHit) { columnBox = columns[lastColumnWithContent].LayoutBox; foundHit = snapToText || (point.Y >= columnBox.Top && point.Y <= columnBox.Bottom); } Invariant.Assert(!foundHit || lastColumnWithContent < columns.Count, "Column not found."); return foundHit ? lastColumnWithContent : -1; } /// /// HitTest a paragraph collection. /// /// Collection of paragraphs. /// Point in pixel coordinates to test. /// /// If true, this method must always return a paragraph index /// (the closest position as calculated by the control's heuristics). /// If false, this method should return -1, if the test /// point does not fall within any paragraph bounding box. /// private int GetParagraphFromPoint(ReadOnlyCollection paragraphs, Point point, bool snapToText) { int paragraphIndex; int lastParagraphWithContent = -1; Rect paragraphBox; bool foundHit = false; Invariant.Assert(paragraphs != null, "Paragraph collection is null"); // Figure out which paragraph is the closest vertically to the input pixel position. // There are following assumptions made: // * paragraph[N].LayoutBox.Top < paragraph[N+1].LayoutBox.Top // // NOTE: Snapping, if necessary, is done first in vertical direction. for (paragraphIndex = 0; paragraphIndex < paragraphs.Count; paragraphIndex++) { paragraphBox = paragraphs[paragraphIndex].LayoutBox; if (!(paragraphs[paragraphIndex].HasTextContent)) { if (paragraphIndex == paragraphs.Count - 1) { // We are at the last paragraph, and if we didn't find anything else with content, we must stop here lastParagraphWithContent = (lastParagraphWithContent == -1) ? paragraphIndex : lastParagraphWithContent; foundHit = snapToText; } // Since paragraph has no text content, skip checking it's layout box. continue; } else { lastParagraphWithContent = paragraphIndex; } Invariant.Assert(lastParagraphWithContent == paragraphIndex); // There are following possibilities: // a) Point is to the top of the paragraph.Top. // In this case consider the current paragraph. This will be true only // for the first paragraph, because all others should be taken care off // during b) & c) // b) Point is to the bottom of the paragraph.Bottom. // If the point does not intersect with the next paragraph, consider // the closest paragraph (or this paragraph if it is the last one). // c) Point intersects with the current paragraph. // If this paragraph does not overlap with the next one, return it. // But if it does overlap, it means that there is overflow in this paragraph // and the next paragraph should be considered. if (point.Y < paragraphBox.Top) { // a) Point is to the top of the paragraph.Top. // In this case consider the current paragraph. This will be true only // for the first paragraph, because all others should be taken care off // during b) & c) foundHit = snapToText; break; } else if (point.Y > paragraphBox.Bottom) { // b) Point is to the bottom of the paragraph.Bottom. // If the point does not intersect with the next paragraph, consider // the closest paragraph (or this paragraph if it is the last one). if (paragraphIndex < paragraphs.Count - 1) { Rect nextParagraphBox = paragraphs[paragraphIndex + 1].LayoutBox; if (point.Y < nextParagraphBox.Top) { // Point is in the gap between paragraphs. Use the closest one. double gap = nextParagraphBox.Top - paragraphBox.Bottom; if (point.Y > paragraphBox.Bottom + gap / 2 && paragraphs[paragraphIndex + 1].HasTextContent) { ++paragraphIndex; lastParagraphWithContent = paragraphIndex; } foundHit = snapToText; break; } // else continue to the next paragraph } else { // This is the last paragraph. foundHit = snapToText; break; } } else { // c) Point intersects with the current paragraph. // If this paragraph does not overlap with the next one, return it. // But if it does overlap, it means that there is overflow in this paragraph // and the next paragraph should be considered. if (paragraphIndex < paragraphs.Count - 1) { Rect nextParagraphBox = paragraphs[paragraphIndex + 1].LayoutBox; if (point.Y < nextParagraphBox.Top) { // Point is not over the next paragraph and this paragraph has been hit. foundHit = true; break; } // else - Continue to the next paragraph. } else { // This is the last paragraph and it has been hit. foundHit = true; break; } } } // Check if the input pixel position is horizontally inside paragraph boundary. if (foundHit) { paragraphBox = paragraphs[lastParagraphWithContent].LayoutBox; foundHit = snapToText || (point.X >= paragraphBox.Left && point.X <= paragraphBox.Right); } Invariant.Assert(!foundHit || lastParagraphWithContent < paragraphs.Count, "Paragraph not found."); return foundHit ? lastParagraphWithContent : -1; } /// /// HitTest a floating elements collection for a point. We do not snap to text in floating elements collections, /// and we assume there is no overlap between paragraphs. If there is overlap, and the point lies in the overlapping /// region, we will return the first paragraph in the collection to contain the point. /// /// Collection of floating element paragraphs. /// Point in pixel coordinates to test. /// If snapToText is true, we must match the point to some floating element. private int GetParagraphFromPointInFloatingElements(ReadOnlyCollection floatingElements, Point point, bool snapToText) { Invariant.Assert(floatingElements != null, "Paragraph collection is null"); double closestDistance = Double.MaxValue; int closestIndex = -1; for (int paragraphIndex = 0; paragraphIndex < floatingElements.Count; paragraphIndex++) { Rect paragraphBox = floatingElements[paragraphIndex].LayoutBox; if (paragraphBox.Contains(point)) { return paragraphIndex; } else { Point midPoint = new Point(paragraphBox.X + paragraphBox.Width / 2, paragraphBox.Y + paragraphBox.Height / 2); double distance = Math.Abs(point.X - midPoint.X) + Math.Abs(point.Y - midPoint.Y); if (distance < closestDistance) { closestIndex = paragraphIndex; closestDistance = distance; } } } return snapToText ? closestIndex : -1; } /// /// Get column index from position. /// /// Collection of columns. /// Position to test. /// An index of column private int GetColumnFromPosition(ReadOnlyCollection columns, ITextPointer position) { // Column collection cannot be null Invariant.Assert(columns != null, "Column collection is null"); // If there is just one column, there is no point to check if it contains // the position, because range for this column is the same as range for // TextView. int columnIndex = 0; if (columns.Count > 0) { if (columns.Count == 1) { columnIndex = 0; } else { for (columnIndex = 0; columnIndex < columns.Count; columnIndex++) { if (columns[columnIndex].Contains(position, true)) { break; } } // Since strict containment rules are applied, allow loose boundaries // for the beginning and end of TextView content. if (columnIndex >= columns.Count) { if (position.CompareTo(columns[0].StartPosition) == 0) { columnIndex = 0; } else if (position.CompareTo(columns[columns.Count - 1].EndPosition) == 0) { columnIndex = columns.Count - 1; } } } } return columnIndex; } /// /// Get paragraph index from position. /// /// Collection of paragraphs. /// Collection of floating elements /// Position to test. /// True if paragraph found is a floating element para /// If paragraph count is 0, index returned is 0 which is equal to paragraphs.Count private static int GetParagraphFromPosition(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position, out bool isFloatingPara) { Invariant.Assert(paragraphs != null, "Paragraph collection is null."); Invariant.Assert(floatingElements != null, "Floating element collection is null."); isFloatingPara = false; // Search floating elements first int paragraphIndex = GetParagraphFromPosition(floatingElements, position); if (paragraphIndex < floatingElements.Count) { // Found isFloatingPara = true; return paragraphIndex; } // Search rest of paras return GetParagraphFromPosition(paragraphs, position); } /// /// Get paragraph index from position. /// /// Collection of paragraphs. /// Position to test. /// An index of paragraph. /// If paragraph count is 0, index returned is 0 which is equal to paragraphs.Count private static int GetParagraphFromPosition(ReadOnlyCollection paragraphs, ITextPointer position) { Invariant.Assert(paragraphs != null, "Paragraph collection is null."); // Iterate through paragraph to find out placement of ITextPointer. // Apply strict containment rules. int paragraphIndex = 0; int paragraphSearchIndexUpper = paragraphs.Count - 1; int paragraphSearchIndexLower = 0; bool found = false; if (paragraphs.Count > 0) { while (true) { paragraphIndex = (paragraphSearchIndexUpper + paragraphSearchIndexLower) / 2; if (paragraphs[paragraphIndex].Contains(position, true)) { found = true; break; } // If we're examining only one element, we've failed to find it. if (paragraphSearchIndexUpper == paragraphSearchIndexLower) { break; } if (position.CompareTo(paragraphs[paragraphIndex].StartPosition) < 0) { paragraphSearchIndexUpper = paragraphIndex - 1; } else { paragraphSearchIndexLower = paragraphIndex + 1; } // Check if lower and upper have swapped positions, if so, we've failed to find the element. if (paragraphSearchIndexUpper < paragraphSearchIndexLower) { break; } } if (!found) { // Since strict containment rules are applied, allow loose boundaries // for the beginning and end of paragraph's content. if (position.CompareTo(paragraphs[0].StartPosition) == 0) { paragraphIndex = 0; } else if (position.CompareTo(paragraphs[paragraphs.Count - 1].EndPosition) == 0) { paragraphIndex = paragraphs.Count - 1; } else { paragraphIndex = paragraphs.Count; } } } return paragraphIndex; } /// /// Returns a TextSegment that spans the line on which position is located. /// /// Collection of paragraphs. /// Collection of floating elements /// Any oriented text position on the line. private TextSegment GetLineRangeFromPosition(ReadOnlyCollection paragraphs, ReadOnlyCollection floatingElements, ITextPointer position) { Invariant.Assert(paragraphs != null, "Paragraph collection is null"); Invariant.Assert(floatingElements != null, "Floating element collection is null"); TextSegment lineRange = TextSegment.Null; bool isFloatingPara; int paragraphIndex = GetParagraphFromPosition(paragraphs, floatingElements, position, out isFloatingPara); ParagraphResult paragraph = null; if (isFloatingPara) { Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; } else { if (paragraphIndex < paragraphs.Count) { paragraph = paragraphs[paragraphIndex]; } } if (paragraph != null) { lineRange = GetLineRangeFromPosition(paragraph, position); } return lineRange; } /// /// Returns a TextSegment that spans the line on which position is located. /// /// Paragraph to search /// Any oriented text position on the line. private TextSegment GetLineRangeFromPosition(ParagraphResult paragraph, ITextPointer position) { TextSegment lineRange = TextSegment.Null; // Each paragraph type is handled differently: // a) ContainerParagraph - process nested paragraphs. // b) TextParagraph - find line index of a line containing input text position // and then return its range. // c) TableParagraph - process nested paragraphs. if (paragraph is ContainerParagraphResult) { // a) ContainerParagraph - process nested paragraphs. ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { lineRange = GetLineRangeFromPosition(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is TextParagraphResult) { // b) TextParagraph - find line index of a line containing input text position // and then return its range. ReadOnlyCollection lines = ((TextParagraphResult)paragraph).Lines; Invariant.Assert(lines != null, "Lines collection is null"); if (!((TextParagraphResult)paragraph).HasTextContent) { // Paragraph has no lines. // lineRange = new TextSegment(((TextParagraphResult)paragraph).EndPosition, ((TextParagraphResult)paragraph).EndPosition, true); } else { // Get index of the line that contains position. int lineIndex = TextParagraphView.GetLineFromPosition(lines, position); Invariant.Assert(lineIndex >= 0 && lineIndex < lines.Count, "Line not found."); lineRange = new TextSegment(lines[lineIndex].StartPosition, lines[lineIndex].GetContentEndPosition(), true); } } else if (paragraph is TableParagraphResult) { // c) TableParagraph - process nested paragraphs. ReadOnlyCollection nestedParagraphs = ((TableParagraphResult)paragraph).GetParagraphsFromPosition(position); // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { lineRange = GetLineRangeFromPosition(nestedParagraphs, _emptyParagraphCollection, position); } } else if (paragraph is SubpageParagraphResult) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraph; ReadOnlyCollection columns = subpageParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = subpageParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { lineRange = GetLineRangeFromPosition(columns, nestedFloatingElements, position); } } else if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { lineRange = GetLineRangeFromPosition(columns, nestedFloatingElements, position); } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { lineRange = GetLineRangeFromPosition(columns, nestedFloatingElements, position); } } else if (paragraph is UIElementParagraphResult) { // UIElement paragraph result - return content range between BlockUIContainer.ContentStart and ContentEnd BlockUIContainer blockUIContainer = paragraph.Element as BlockUIContainer; if (blockUIContainer != null) { lineRange = new TextSegment(blockUIContainer.ContentStart.CreatePointer(LogicalDirection.Forward), blockUIContainer.ContentEnd.CreatePointer(LogicalDirection.Backward)); } } return lineRange; } /// /// Returns a TextSegment that spans the line on which position is located. /// private TextSegment GetLineRangeFromPosition(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position) { int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count || floatingElements.Count > 0) { ReadOnlyCollection paragraphs = (columnIndex < columns.Count && columnIndex >= 0) ? columns[columnIndex].Paragraphs : _emptyParagraphCollection; return GetLineRangeFromPosition(paragraphs, floatingElements, position); } return TextSegment.Null; } /// /// Retrieves an oriented text position matching position advanced by /// a number of lines from its initial position. /// /// Collection of paragraphs. /// Initial text position of an object/character. /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// Number of lines to advance. Negative means move backwards. /// True if ths position was found in the paragraphs collection /// /// A TextPointer and its orientation matching suggestedX on the /// destination line. /// private ITextPointer GetPositionAtNextLine(ReadOnlyCollection paragraphs, ITextPointer position, double suggestedX, ref int count, out bool positionFound) { Invariant.Assert(paragraphs != null, "Paragraph collection is empty."); // If no position found in table, return original position. ITextPointer positionOut = position; positionFound = false; // Figure out which paragraph contains text position. int paragraphIndex = GetParagraphFromPosition(paragraphs, position); if (paragraphIndex < paragraphs.Count) { positionFound = true; // Each paragraph type is handled differently: // a) ContainerParagraph - process nested paragraphs. // b) TextParagraph - find line index of a line containing input text position // and find the previous/next line in the line array. // If new line (specified by count) is not in the range of this TextParagraph, // update count value by the delta between the current line index and the first/last line. // c) TableParagraph - process nested paragraphs. if (paragraphs[paragraphIndex] is ContainerParagraphResult) { // a) ContainerParagraph - process nested paragraphs. Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraphs[paragraphIndex]).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { positionOut = GetPositionAtNextLine(nestedParagraphs, position, suggestedX, ref count, out positionFound); } } else if (paragraphs[paragraphIndex] is TextParagraphResult) { // b) TextParagraph - find line index of a line containing input text position // and find the previous/next line in the line array. // If new line (specified by count) is not in the range of this TextParagraph, // update count value by the delta between the current line index and the first/last line. // Get index of the line that contains position. ReadOnlyCollection lines = ((TextParagraphResult)paragraphs[paragraphIndex]).Lines; Invariant.Assert(lines != null, "Lines collection is null"); if (!((TextParagraphResult)paragraphs[paragraphIndex]).HasTextContent) { // TextParagraph has no lines // positionOut = position; } else { int lineIndex = TextParagraphView.GetLineFromPosition(lines, position); Invariant.Assert(lineIndex >= 0 && lineIndex < lines.Count, "Line not found."); Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; // Advance line index by count. If new line index is within this paragraph // set the count to 0 and update line index. // But if the new line index is not within this paragraph, change the count // value by the delta between the current line index and the first/last line. int oldLineIndex = lineIndex; if (lineIndex + count < 0) { lineIndex = 0; count += oldLineIndex; } else if (lineIndex + count > lines.Count - 1) { lineIndex = lines.Count - 1; count -= (lines.Count - 1 - oldLineIndex); } else { lineIndex = lineIndex + count; count = 0; } // If count is 0, the new line is in this paragraph if (count == 0) { // Get position at suggested X. If suggested X is not provided, // use the first position in the line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[lineIndex].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[lineIndex].StartPosition.CreatePointer(LogicalDirection.Forward); } } else { // If count is not 0, the new line is in the next/previous paragraph. // If line has not been moved, return the same position. if (lineIndex == oldLineIndex) { positionOut = position; } else if (count < 0) { // Just in case there are no lines above, set position to the first line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[0].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[0].StartPosition.CreatePointer(LogicalDirection.Forward); } } else { // Just in case there are no lines below, set position to the last line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[lines.Count - 1].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[lines.Count - 1].StartPosition.CreatePointer(LogicalDirection.Forward); } } } } } else if (paragraphs[paragraphIndex] is TableParagraphResult) { // c) TableParagraph - process nested paragraphs. TableParagraphResult tableResult = (TableParagraphResult)paragraphs[paragraphIndex]; CellParaClient cpcStart = tableResult.GetCellParaClientFromPosition(position); CellParaClient cpcCur = cpcStart; Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; while (count != 0 && cpcCur != null && positionFound) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)cpcCur.CreateParagraphResult(); ReadOnlyCollection nestedParagraphs = subpageParagraphResult.Columns[0].Paragraphs; Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); // Paragraphs collection may be null in case of empty List. if (nestedParagraphs.Count > 0) { if (cpcCur != cpcStart) { int nesteParagraphIndex = (count > 0) ? 0 : nestedParagraphs.Count - 1; positionOut = GetPositionAtNextLineFromSiblingPara(nestedParagraphs, nesteParagraphIndex, suggestedX - TextDpi.FromTextDpi(cpcCur.Rect.u), ref count); if (positionOut == null) { positionOut = position; } } else { positionOut = GetPositionAtNextLine(nestedParagraphs, position, suggestedX - subpageParagraphResult.ContentOffset.X, ref count, out positionFound); } } if (count < 0 && positionFound) { cpcCur = tableResult.GetCellAbove(suggestedX, cpcCur.Cell.RowGroupIndex, cpcCur.Cell.RowIndex); } else if (count > 0 && positionFound) { cpcCur = tableResult.GetCellBelow(suggestedX, cpcCur.Cell.RowGroupIndex, cpcCur.Cell.RowIndex + cpcCur.Cell.RowSpan - 1); } } } else if (paragraphs[paragraphIndex] is SubpageParagraphResult) { double newSuggestedX; SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraphs[paragraphIndex]; positionOut = GetPositionAtNextLine(((SubpageParagraphResult)paragraphs[paragraphIndex]).Columns, subpageParagraphResult.FloatingElements, position, suggestedX - subpageParagraphResult.ContentOffset.X, ref count, out newSuggestedX, out positionFound); } // If the new line has not been found yet, iterate through sibling paragraphs to // find out a new line. if (count != 0 && positionFound) { if (count > 0) { ++paragraphIndex; } else { --paragraphIndex; } if (paragraphIndex >= 0 && paragraphIndex < paragraphs.Count) { positionOut = GetPositionAtNextLineFromSiblingPara(paragraphs, paragraphIndex, suggestedX, ref count); if (positionOut == null) { // This may happen if next para has no content positionOut = position; } } } } // Do not return null from this point. If positionOut was set to null at any point during the code, we should have // set it to the original position Invariant.Assert(positionOut != null); return positionOut; } /// /// Retrieves an oriented text position matching position advanced by /// a number of lines from its initial position. Helper function for searching floating elements, where we do not search siblings. /// /// Paragraphs to search /// Initial text position of an object/character. /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// True if position is found in a floating para /// Number of lines to advance. Negative means move backwards. /// Searches only in one floating element para and does not search in any siblings private ITextPointer GetPositionAtNextLineInFloatingElements(ReadOnlyCollection floatingElements, ITextPointer position, double suggestedX, ref int count, out bool positionFound) { // If no position found in table, return original position. ITextPointer positionOut = position; positionFound = false; ParagraphResult paragraph = null; int paragraphIndex = GetParagraphFromPosition(_emptyParagraphCollection, floatingElements, position, out positionFound); if (positionFound) { // Special search in floating element para. Even if the position is not found within the floating element para, i.e. it // is within the para but not normalized, we still want to return true for positionFound since this search should not cross floating // element boundary. Hence use a separate flag for checking if the position is found within floating element. bool positionFoundInNestedPara; Invariant.Assert(paragraphIndex < floatingElements.Count); paragraph = floatingElements[paragraphIndex]; Invariant.Assert(paragraph is FigureParagraphResult || paragraph is FloaterParagraphResult); if (paragraph is FigureParagraphResult) { double newSuggestedX; FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { positionOut = GetPositionAtNextLine(columns, nestedFloatingElements, position, suggestedX - figureParagraphResult.ContentOffset.X, ref count, out newSuggestedX, out positionFoundInNestedPara); } } else { double newSuggestedX; FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { positionOut = GetPositionAtNextLine(columns, nestedFloatingElements, position, suggestedX - floaterParagraphResult.ContentOffset.X, ref count, out newSuggestedX, out positionFoundInNestedPara); } } } Invariant.Assert(positionOut != null); return positionOut; } /// /// Retrieves an oriented text position matching position advanced by /// a number of lines from its initial position. /// private ITextPointer GetPositionAtNextLine(ReadOnlyCollection columns, ReadOnlyCollection floatingElements, ITextPointer position, double suggestedX, ref int count, out double newSuggestedX, out bool positionFound) { ITextPointer positionOut = null; newSuggestedX = suggestedX; positionFound = false; // Check floating elements collection if (floatingElements.Count > 0) { positionOut = GetPositionAtNextLineInFloatingElements(floatingElements, position, suggestedX, ref count, out positionFound); } if (!positionFound) { // No success in floating elements, try columns int columnIndex = GetColumnFromPosition(columns, position); if (columnIndex < columns.Count) { // Get the next line from the list of paragraphs. positionFound = true; positionOut = GetPositionAtNextLine(columns[columnIndex].Paragraphs, position, suggestedX, ref count, out positionFound); int oldColumnIndex = columnIndex; // If the new line has not been found yet, iterate through sibling columns to // find out a new line. if (count != 0 && positionFound) { if (count > 0) { ++columnIndex; } else { --columnIndex; } if (columnIndex >= 0 && columnIndex < columns.Count) { suggestedX = (suggestedX - columns[oldColumnIndex].LayoutBox.Left) + columns[columnIndex].LayoutBox.Left; ITextPointer siblingColumnPosition = GetPositionAtNextLineFromSiblingColumn(columns, columnIndex, suggestedX, ref newSuggestedX, ref count); if (siblingColumnPosition != null) { positionOut = siblingColumnPosition; } } } } } Invariant.Assert(positionOut != null); return positionOut; } /// /// Retrieves an oriented text position advancing by number of lines from its initial position. /// /// Collection of paragraphs. /// Current paragraph index. /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// Number of lines to advance. Negative means move backwards. /// /// A TextPointer and its orientation matching suggestedX on the /// destination line. /// private ITextPointer GetPositionAtNextLineFromSiblingPara(ReadOnlyCollection paragraphs, int paragraphIndex, double suggestedX, ref int count) { Invariant.Assert(count != 0); Invariant.Assert(paragraphIndex >= 0 && paragraphIndex < paragraphs.Count, "Paragraph collection is empty."); ITextPointer positionOut = null; // Iterate through sibling paragraphs to find out a new line. while (paragraphIndex >= 0 && paragraphIndex < paragraphs.Count) { // Each paragraph type is handled differently: // a) ContainerParagraph - process nested paragraphs starting from the first // or last nested paragraph (depending on the count value sign). // b) TextParagraph - start from the first or last line (depending on the count // value sign) and find the previous/next line in the line array. // If new line (specified by count) is not in the range of this TextParagraph, // update count value by the line count. // c) TableParagraph - if (paragraphs[paragraphIndex] is ContainerParagraphResult) { // a) ContainerParagraph - process nested paragraphs starting from the first // or last nested paragraph (depending on the count value sign). Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraphs[paragraphIndex]).Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { int nesteParagraphIndex = (count > 0) ? 0 : nestedParagraphs.Count - 1; positionOut = GetPositionAtNextLineFromSiblingPara(nestedParagraphs, nesteParagraphIndex, suggestedX, ref count); } } else if (paragraphs[paragraphIndex] is TextParagraphResult) { // b) TextParagraph - start from the first or last line (depending on the count // value sign) and find the previous/next line in the line array. // If new line (specified by count) is not in the range of this TextParagraph, // update count value by the line count. positionOut = GetPositionAtNextLineFromSiblingTextPara((TextParagraphResult)paragraphs[paragraphIndex], suggestedX, ref count); if (count == 0) { // Line has been found. break; } } else if (paragraphs[paragraphIndex] is TableParagraphResult) { TableParagraphResult tableResult = (TableParagraphResult)paragraphs[paragraphIndex]; Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; CellParaClient cpcCur = null; if (count < 0) { cpcCur = tableResult.GetCellAbove(suggestedX, int.MaxValue, int.MaxValue); } else if (count > 0) { cpcCur = tableResult.GetCellBelow(suggestedX, int.MinValue, int.MinValue); } while (count != 0 && cpcCur != null) { SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)cpcCur.CreateParagraphResult(); ReadOnlyCollection nestedParagraphs = subpageParagraphResult.Columns[0].Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { int nesteParagraphIndex = (count > 0) ? 0 : nestedParagraphs.Count - 1; positionOut = GetPositionAtNextLineFromSiblingPara(nestedParagraphs, nesteParagraphIndex, suggestedX - subpageParagraphResult.ContentOffset.X, ref count); } if (count < 0) { cpcCur = tableResult.GetCellAbove(suggestedX, cpcCur.Cell.RowGroupIndex, cpcCur.Cell.RowIndex); } else if (count > 0) { cpcCur = tableResult.GetCellBelow(suggestedX, cpcCur.Cell.RowGroupIndex, cpcCur.Cell.RowIndex + cpcCur.Cell.RowSpan - 1); } } } else if (paragraphs[paragraphIndex] is SubpageParagraphResult) { // a) ContainerParagraph - process nested paragraphs starting from the first // or last nested paragraph (depending on the count value sign). Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; SubpageParagraphResult subpageParagraphResult = (SubpageParagraphResult)paragraphs[paragraphIndex]; ReadOnlyCollection nestedParagraphs = subpageParagraphResult.Columns[0].Paragraphs; Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { int nesteParagraphIndex = (count > 0) ? 0 : nestedParagraphs.Count - 1; positionOut = GetPositionAtNextLineFromSiblingPara(nestedParagraphs, nesteParagraphIndex, suggestedX - subpageParagraphResult.ContentOffset.X, ref count); } } else if (paragraphs[paragraphIndex] is UIElementParagraphResult) { // UIElementParagraphResult - has only one line with 2 positions, at start and end of BUIC if (count < 0) { count++; } else { count--; } if (count == 0) { // We need to get a position in this paragraph Rect paragraphBox = paragraphs[paragraphIndex].LayoutBox; BlockUIContainer blockUIContainer = paragraphs[paragraphIndex].Element as BlockUIContainer; if (blockUIContainer != null) { if (DoubleUtil.LessThanOrClose(suggestedX, paragraphBox.Width / 2)) { positionOut = blockUIContainer.ContentStart.CreatePointer(LogicalDirection.Forward); } else { positionOut = blockUIContainer.ContentEnd.CreatePointer(LogicalDirection.Backward); } } } } // If count is not 0, the new line is in the next/previous paragraph. if (count < 0) { --paragraphIndex; } else if (count > 0) { ++paragraphIndex; } else { break; } } return positionOut; } /// /// Retrieves an oriented text position advancing by number of lines from its initial position. /// /// Text paragraph. /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// Number of lines to advance. Negative means move backwards. /// /// A TextPointer and its orientation matching suggestedX on the /// destination line. /// private ITextPointer GetPositionAtNextLineFromSiblingTextPara(TextParagraphResult paragraph, double suggestedX, ref int count) { ITextPointer positionOut = null; // TextParagraph - start from the first or last line (depending on the count // value sign) and find the previous/next line in the line array. // If new line (specified by count) is not in the range of this TextParagraph, // update count value by the line count. ReadOnlyCollection lines = paragraph.Lines; Invariant.Assert(lines != null, "Lines collection is null"); if (!paragraph.HasTextContent) { // Paragraph has no text content, which means it either has no lines at all or just figures and floaters. // In either case, we cannot step into it from GetPositionAtNextLine. We must set positionOut to null and // try to advance elsewhere. positionOut = null; } else { Rect paragraphBox = paragraph.LayoutBox; // We are entering this paragraph. Get index of the first/last line // (depending on the sing of count value) and try to find out line index. int lineIndex = (count > 0) ? 0 : lines.Count - 1; // We are about to analyze the first/last line in the paragraph, so adjust the // count to take it into account. if (count < 0) { ++count; } else { --count; } // If the new line is not in the range of this paragraph , change the count // value by the number of lines in the paragraph. if (lineIndex + count < 0) { count += lineIndex; } else if (lineIndex + count > lines.Count - 1) { count -= (lines.Count - 1 - lineIndex); } else { lineIndex = lineIndex + count; count = 0; } // If count is 0, the new line is in this paragraph if (count == 0) { // Get position at suggested X. If suggested X is not provided, // use the first position in the line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[lineIndex].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[lineIndex].StartPosition.CreatePointer(LogicalDirection.Forward); } } else { // If count is not 0, the new line is in the next/previous paragraph. if (count < 0) { // Just in case there are no lines above, set position to the first line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[0].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[0].StartPosition.CreatePointer(LogicalDirection.Forward); } } else { // Just in case there are no lines below, set position to the last line. if (!DoubleUtil.IsNaN(suggestedX)) { positionOut = lines[lines.Count - 1].GetTextPositionFromDistance(suggestedX); } else { positionOut = lines[lines.Count - 1].StartPosition.CreatePointer(LogicalDirection.Forward); } } } } return positionOut; } /// /// Retrieves an oriented text position advancing by number of lines from its initial position. /// /// Collection of columns. /// Current column index. /// /// The suggested X offset of text position on the destination line. /// In pixels and relative to the current column. /// /// /// newSuggestedX is the offset at the position moved (useful when moving /// between columns). /// /// Number of lines to advance. Negative means move backwards. /// /// A TextPointer and its orientation matching suggestedX on the /// destination line. /// private ITextPointer GetPositionAtNextLineFromSiblingColumn(ReadOnlyCollection columns, int columnIndex, double columnSuggestedX, ref double newSuggestedX, ref int count) { ITextPointer positionOut = null; // Iterate through sibling columns to find out a new line. while (columnIndex >= 0 && columnIndex < columns.Count) { double currentSuggestedX = columnSuggestedX + columns[columnIndex].LayoutBox.Left; ReadOnlyCollection paragraphs = columns[columnIndex].Paragraphs; // Paragraphs collection may be null in case of empty List. Invariant.Assert(paragraphs != null, "Paragraph collection is null."); if (paragraphs.Count > 0) { // Process paragraphs starting from the first or last nested paragraph // (depending on the count value sign). int paragraphIndex = (count > 0) ? 0 : paragraphs.Count - 1; positionOut = GetPositionAtNextLineFromSiblingPara(paragraphs, paragraphIndex, columnSuggestedX, ref count); } // Update new suggestedX position with position in the current column. newSuggestedX = columnSuggestedX; // If count is not 0, the new line is in the next/previous paragraph. if (count < 0) { --columnIndex; } else if (count > 0) { ++columnIndex; } else { break; } } return positionOut; } /// /// Determines whenever TextView contains specified position. /// /// A position to test. /// /// True if TextView contains specified text position. /// Otherwise returns false. /// private bool ContainsCore(ITextPointer position) { ReadOnlyCollection segments = this.TextSegmentsCore; Invariant.Assert(segments != null, "TextSegment collection is empty."); return Contains(position, segments); } /// /// Get glyph runs from paragraph collection. /// /// Preallocated collection of glyph runs. /// Start position. /// End position. /// Collection of paragraphs. /// True if needs to continue search. False if all glyph runs have been retrieved. private bool GetGlyphRunsFromParagraphs(List glyphRuns, ITextPointer start, ITextPointer end, ReadOnlyCollection paragraphs) { Invariant.Assert(paragraphs != null, "Paragraph collection is null."); bool cont = true; // Iterate through columns and get all glyph runs between Start and End for (int index = 0; index < paragraphs.Count; index++) { ParagraphResult paragraph = paragraphs[index]; if (paragraph is TextParagraphResult) { TextParagraphResult tpr = (TextParagraphResult)paragraph; if (start.CompareTo(tpr.EndPosition) < 0 && end.CompareTo(tpr.StartPosition) > 0) { ITextPointer startRange = start.CompareTo(tpr.StartPosition) < 0 ? tpr.StartPosition : start; ITextPointer endRange = end.CompareTo(tpr.EndPosition) < 0 ? end : tpr.EndPosition; tpr.GetGlyphRuns(glyphRuns, startRange, endRange); } // Do not continue, if end of requested range has been reached. if (end.CompareTo(tpr.EndPosition) < 0) { cont = false; break; } } else if (paragraph is ContainerParagraphResult) { ReadOnlyCollection nestedParagraphs = ((ContainerParagraphResult)paragraph).Paragraphs; Invariant.Assert(nestedParagraphs != null, "Paragraph collection is null."); if (nestedParagraphs.Count > 0) { cont = GetGlyphRunsFromParagraphs(glyphRuns, start, end, nestedParagraphs); // Do not continue, if end of requested range has been reached. if (!cont) { break; } } } } return cont; } /// /// Get glyph runs from floating element collection. /// /// Preallocated collection of glyph runs. Helper function for searching floating elements. /// In floating elements collection, if we match the start position to a para, we return runs either up to end position or to end of para, /// whichever is first. We do not collect glyph runs across multiple floating elements /// Start position. /// End position. /// Collection of paragraphs. /// True if the range starts in one of the floating elements private void GetGlyphRunsFromFloatingElements(List glyphRuns, ITextPointer start, ITextPointer end, ReadOnlyCollection floatingElements, out bool success) { Invariant.Assert(floatingElements != null, "Paragraph collection is null."); success = false; // Iterate through columns and get all glyph runs between Start and End for (int index = 0; index < floatingElements.Count; index++) { ParagraphResult paragraph = floatingElements[index]; Invariant.Assert(paragraph is FigureParagraphResult || paragraph is FloaterParagraphResult); if (paragraph.Contains(start, true)) { success = true; ITextPointer endThisPara = end.CompareTo(paragraph.EndPosition) < 0 ? end : paragraph.EndPosition; if (paragraph is FigureParagraphResult) { FigureParagraphResult figureParagraphResult = (FigureParagraphResult)paragraph; ReadOnlyCollection columns = figureParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = figureParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { GetGlyphRuns(glyphRuns, start, endThisPara, columns, nestedFloatingElements); } } else if (paragraph is FloaterParagraphResult) { FloaterParagraphResult floaterParagraphResult = (FloaterParagraphResult)paragraph; ReadOnlyCollection columns = floaterParagraphResult.Columns; ReadOnlyCollection nestedFloatingElements = floaterParagraphResult.FloatingElements; Invariant.Assert(columns != null, "Column collection is null."); Invariant.Assert(nestedFloatingElements != null, "Paragraph collection is null."); if (columns.Count > 0 || nestedFloatingElements.Count > 0) { GetGlyphRuns(glyphRuns, start, endThisPara, columns, nestedFloatingElements); } } break; } } } /// /// Get glyph runs from paragraph collection. /// private void GetGlyphRuns(List glyphRuns, ITextPointer start, ITextPointer end, ReadOnlyCollection columns, ReadOnlyCollection floatingElements) { int columnIndexStart; int columnIndexEnd; // Search floating elements first bool success = false; if (floatingElements.Count > 0) { GetGlyphRunsFromFloatingElements(glyphRuns, start, end, floatingElements, out success); } if (!success) { // Figure out which columns contain start and end for (columnIndexStart = 0; columnIndexStart < columns.Count; columnIndexStart++) { ColumnResult columnResult = columns[columnIndexStart]; if (start.CompareTo(columnResult.StartPosition) >= 0 && start.CompareTo(columnResult.EndPosition) <= 0) break; } for (columnIndexEnd = columnIndexStart; columnIndexEnd < columns.Count; columnIndexEnd++) { ColumnResult columnResult = columns[columnIndexStart]; if (end.CompareTo(columnResult.StartPosition) >= 0 && end.CompareTo(columnResult.EndPosition) <= 0) break; } Invariant.Assert(columnIndexStart < columns.Count && columnIndexEnd < columns.Count, "Start or End position does not belong to TextView's content range"); // Iterate through columns and get all glyph runs between Start and End while (columnIndexStart <= columnIndexEnd) { ReadOnlyCollection paragraphs = columns[columnIndexStart].Paragraphs; if (paragraphs != null && paragraphs.Count > 0) { GetGlyphRunsFromParagraphs(glyphRuns, start, end, paragraphs); } ++columnIndexStart; } } } /// /// Retrieve TextSegments collection for the content of the page represented /// by the TextView. /// private ReadOnlyCollection GetTextSegments() { ReadOnlyCollection textSegments; // For a bottomless page there is always one TextSegment, starting at // the beginning of TextContainer and ending at the last calculated // position, which is: // a) the end of TextContainer, or // b) the interruption position when background layout is in progress. // For a finite page there is no such optimization, so need to query all // columns and merge their TextSegments. if (!_owner.FinitePage) { ITextPointer segmentEnd = _textContainer.End; BackgroundFormatInfo backgroundFormatInfo = _owner.StructuralCache.BackgroundFormatInfo; if (backgroundFormatInfo != null && backgroundFormatInfo.CPInterrupted != -1) { segmentEnd = _textContainer.Start.CreatePointer(backgroundFormatInfo.CPInterrupted, LogicalDirection.Backward); } List segments = new List(1); segments.Add(new TextSegment(_textContainer.Start, segmentEnd, true)); textSegments = new ReadOnlyCollection(segments); } else { TextContentRange textContentRange = new TextContentRange(); // Merge ranges from all columns. ReadOnlyCollection columns = Columns; Invariant.Assert(columns != null, "Column collection is empty."); for (int index = 0; index < columns.Count; index++) { textContentRange.Merge(columns[index].TextContentRange); } textSegments = textContentRange.GetTextSegments(); } Invariant.Assert(textSegments != null); return textSegments; } /// /// Transforms point to content's coordinate system. /// /// Point to which transform is applied. private void TransformToContent(ref Point point) { Point newPoint; // DocumentPage.Visual for printing scenarions needs to be always returned // in LeftToRight FlowDirection. Hence, if the document is RightToLeft, // mirroring transform need to be applied to the content of DocumentPage.Visual. FlowDirection flowDirection = (FlowDirection)_owner.StructuralCache.PropertyOwner.GetValue(FlowDocument.FlowDirectionProperty); if (flowDirection == FlowDirection.RightToLeft) { MatrixTransform transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, _owner.Size.Width, 0.0); transform.TryTransform(point, out newPoint); point = newPoint; } } /// /// Transforms point to content's coordinate system. /// /// Rect to which transform is applied. private void TransformToContent(ref Rect rect) { // DocumentPage.Visual for printing scenarions needs to be always returned // in LeftToRight FlowDirection. Hence, if the document is RightToLeft, // mirroring transform need to be applied to the content of DocumentPage.Visual. FlowDirection flowDirection = (FlowDirection)_owner.StructuralCache.PropertyOwner.GetValue(FlowDocument.FlowDirectionProperty); if (flowDirection == FlowDirection.RightToLeft) { MatrixTransform transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, _owner.Size.Width, 0.0); rect = transform.TransformBounds(rect); } } /// /// Transforms Rect from content's coordinate system. /// /// Rect to which content's offset is applied. /// Content's transform. private void TransformFromContent(ref Rect rect, out Transform transform) { // Set transform to identity transform = Transform.Identity; if (rect == Rect.Empty) { return; } // DocumentPage.Visual for printing scenarions needs to be always returned // in LeftToRight FlowDirection. Hence, if the document is RightToLeft, // mirroring transform need to be applied to the content of DocumentPage.Visual. FlowDirection flowDirection = (FlowDirection)_owner.StructuralCache.PropertyOwner.GetValue(FlowDocument.FlowDirectionProperty); if (flowDirection == FlowDirection.RightToLeft) { transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, _owner.Size.Width, 0.0); } } /// /// Transforms point to content's coordinate system. /// /// Point to which transform is applied. private void TransformFromContent(ref Point point) { Point newPoint; // DocumentPage.Visual for printing scenarions needs to be always returned // in LeftToRight FlowDirection. Hence, if the document is RightToLeft, // mirroring transform need to be applied to the content of DocumentPage.Visual. FlowDirection flowDirection = (FlowDirection)_owner.StructuralCache.PropertyOwner.GetValue(FlowDocument.FlowDirectionProperty); if (flowDirection == FlowDirection.RightToLeft) { MatrixTransform transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, _owner.Size.Width, 0.0); transform.TryTransform(point, out newPoint); point = newPoint; } } /// /// Transforms Geometry from content's coordinate system. /// /// Geometry to which content's transform is applied. private void TransformFromContent(Geometry geometry) { // DocumentPage.Visual for printing scenarions needs to be always returned // in LeftToRight FlowDirection. Hence, if the document is RightToLeft, // mirroring transform need to be applied to the content of DocumentPage.Visual. FlowDirection flowDirection = (FlowDirection)_owner.StructuralCache.PropertyOwner.GetValue(FlowDocument.FlowDirectionProperty); if (flowDirection == FlowDirection.RightToLeft) { MatrixTransform transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, _owner.Size.Width, 0.0); CaretElement.AddTransformToGeometry(geometry, transform); } } /// /// Transforms point to subpage's coordinates. /// /// Point to which transform is applied. /// Offset of subpage private static void TransformToSubpage(ref Point point, Vector subpageOffset) { point -= subpageOffset; } /// /// Transforms rect to subpage's coordinates. /// /// Rect to which transform is applied. /// Subpage offset private static void TransformToSubpage(ref Rect rect, Vector subpageOffset) { if (rect == Rect.Empty) { return; } rect.Offset(-subpageOffset); } /// /// Transforms Rect from subpage coordinates /// /// Rect to which content's offset is applied. /// Subpage offset. private static void TransformFromSubpage(ref Rect rect, Vector subpageOffset) { if (rect == Rect.Empty) { return; } rect.Offset(subpageOffset); } /// /// Transforms Geometry from subpage's coordinate system. /// /// Geometry to which content's transform is applied. /// Subpage offset. private static void TransformFromSubpage(Geometry geometry, Vector subpageOffset) { if (geometry != null) { if (!DoubleUtil.IsZero(subpageOffset.X) || !DoubleUtil.IsZero(subpageOffset.Y)) { TranslateTransform translateTransform = new TranslateTransform(subpageOffset.X, subpageOffset.Y); CaretElement.AddTransformToGeometry(geometry, translateTransform); } } } /// /// Returns the layout box edge associated with this block if a text position is located at the end of the block element with appropriate /// Logical direction. /// /// Result to check edges. /// Text Pointer to compare against /// /// Returns rect for edge, or Rect.Empty if textposition not on edge /// private Rect GetRectangleFromEdge(ParagraphResult paragraphResult, ITextPointer textPointer) { TextElement textElement = paragraphResult.Element as TextElement; if (textElement != null) { if (textPointer.LogicalDirection == LogicalDirection.Forward && textPointer.CompareTo(textElement.ElementStart) == 0) { return new Rect(paragraphResult.LayoutBox.Left, paragraphResult.LayoutBox.Top, 0.0, paragraphResult.LayoutBox.Height); } if (textPointer.LogicalDirection == LogicalDirection.Backward && textPointer.CompareTo(textElement.ElementEnd) == 0) { return new Rect(paragraphResult.LayoutBox.Right, paragraphResult.LayoutBox.Top, 0.0, paragraphResult.LayoutBox.Height); } } return Rect.Empty; } /// /// Returns the layout box edge associated with this block if a text position is located at the end of the block content /// This is for elements like BlockUIContainer where the content start/end (regardless of direction)has the same rectangle as /// the element start/end, we treat BUC as a line with 2 positions at start and end /// /// Result to check edges. /// Text Pointer to compare against /// /// Returns rect for edge, or Rect.Empty if textposition not on edge /// private Rect GetRectangleFromContentEdge(ParagraphResult paragraphResult, ITextPointer textPointer) { TextElement textElement = paragraphResult.Element as TextElement; if (textElement != null) { // We enable this only for BlockUIContainer, it would be wrong to return layout box Rect from other elements' Content start/end Invariant.Assert(textElement is BlockUIContainer, "Expecting BlockUIContainer"); // No need to consider position's LogicalDirection here, ContentStart's backward context for BUIC should be ElementStart, for which // we also want the same rectangle; the same applies to ContentEnd if (textPointer.CompareTo(textElement.ContentStart) == 0) { return new Rect(paragraphResult.LayoutBox.Left, paragraphResult.LayoutBox.Top, 0.0, paragraphResult.LayoutBox.Height); } if (textPointer.CompareTo(textElement.ContentEnd) == 0) { return new Rect(paragraphResult.LayoutBox.Right, paragraphResult.LayoutBox.Top, 0.0, paragraphResult.LayoutBox.Height); } } return Rect.Empty; } #endregion Private Methods //------------------------------------------------------------------- // // Private Fields // //-------------------------------------------------------------------- #region Private Fields /// /// Root of layout structure visualizing content. /// private readonly FlowDocumentPage _owner; /// /// TextContainer providing content for this view. /// private readonly ITextContainer _textContainer; /// /// Cached collection of ColumnResults. /// private ReadOnlyCollection _columns; /// /// Cached collection of ColumnResults. /// private ReadOnlyCollection _floatingElements; /// /// Cached collection of ColumnResults. /// private static ReadOnlyCollection _emptyParagraphCollection = new ReadOnlyCollection(new List(0)); /// /// Cached collection of TextSegments. /// private ReadOnlyCollection _segments; /// /// True if the view has some text content (not just figures/floaters) /// private bool _hasTextContent; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK