TextBoxView.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Framework / MS / Internal / documents / TextBoxView.cs / 3 / TextBoxView.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// File: TextBoxView.cs 
//
// Description: Content presenter for the TextBox. 
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Controls
{
    using System.Windows.Documents;
    using System.Windows.Controls.Primitives; 
    using System.Windows.Media;
    using System.Windows.Threading; 
    using System.Collections.Generic; 
    using System.Collections.ObjectModel;
    using MS.Internal; 
    using MS.Internal.Text;
    using MS.Internal.Documents;
    using MS.Internal.PtsHost;
    using System.Windows.Media.TextFormatting; 

    // Content presenter for the TextBox. 
    internal class TextBoxView : FrameworkElement, ITextView, IScrollInfo, IServiceProvider 
    {
        //----------------------------------------------------- 
        //
        //  Constructors
        //
        //----------------------------------------------------- 

        #region Constructors 
 
        // Static constructor.
        static TextBoxView() 
        {
            // Set a margin so that the bidi caret has room to render at the edges of content.
            MarginProperty.OverrideMetadata(typeof(TextBoxView), new FrameworkPropertyMetadata(new Thickness(CaretElement.BidiCaretIndicatorWidth, 0, CaretElement.BidiCaretIndicatorWidth, 0)));
        } 

        // Constructor. 
        internal TextBoxView(ITextBoxViewHost host) 
        {
            Invariant.Assert(host is Control); 
            _host = host;
        }

        #endregion Constructors 

        //------------------------------------------------------ 
        // 
        //  Public Methods
        // 
        //-----------------------------------------------------

        #region Public Methods
 
        // IServiceProvider for TextEditor/renderscope contract.
        // Provides access to our ITextView implementation. 
        object IServiceProvider.GetService(Type serviceType) 
        {
            object service = null; 

            if (serviceType == typeof(ITextView))
            {
                service = this; 
            }
 
            return service; 
        }
 
        /// 
        /// 
        /// 
        void IScrollInfo.LineUp() 
        {
            if (_scrollData != null) 
            { 
                _scrollData.LineUp(this);
            } 
        }

        /// 
        ///  
        /// 
        void IScrollInfo.LineDown() 
        { 
            if (_scrollData != null)
            { 
                _scrollData.LineDown(this);
            }
        }
 
        /// 
        ///  
        ///  
        void IScrollInfo.LineLeft()
        { 
            if (_scrollData != null)
            {
                _scrollData.LineLeft(this);
            } 
        }
 
        ///  
        /// 
        ///  
        void IScrollInfo.LineRight()
        {
            if (_scrollData != null)
            { 
                _scrollData.LineRight(this);
            } 
        } 

        ///  
        /// 
        /// 
        void IScrollInfo.PageUp()
        { 
            if (_scrollData != null)
            { 
                _scrollData.PageUp(this); 
            }
        } 

        /// 
        /// 
        ///  
        void IScrollInfo.PageDown()
        { 
            if (_scrollData != null) 
            {
                _scrollData.PageDown(this); 
            }
        }

        ///  
        /// 
        ///  
        void IScrollInfo.PageLeft() 
        {
            if (_scrollData != null) 
            {
                _scrollData.PageLeft(this);
            }
        } 

        ///  
        ///  
        /// 
        void IScrollInfo.PageRight() 
        {
            if (_scrollData != null)
            {
                _scrollData.PageRight(this); 
            }
        } 
 
        /// 
        ///  
        /// 
        void IScrollInfo.MouseWheelUp()
        {
            if (_scrollData != null) 
            {
                _scrollData.MouseWheelUp(this); 
            } 
        }
 
        /// 
        /// 
        /// 
        void IScrollInfo.MouseWheelDown() 
        {
            if (_scrollData != null) 
            { 
                _scrollData.MouseWheelDown(this);
            } 
        }

        /// 
        ///  
        /// 
        void IScrollInfo.MouseWheelLeft() 
        { 
            if (_scrollData != null)
            { 
                _scrollData.MouseWheelLeft(this);
            }
        }
 
        /// 
        ///  
        ///  
        void IScrollInfo.MouseWheelRight()
        { 
            if (_scrollData != null)
            {
                _scrollData.MouseWheelRight(this);
            } 
        }
 
        ///  
        /// 
        ///  
        void IScrollInfo.SetHorizontalOffset(double offset)
        {
            if (_scrollData != null)
            { 
                _scrollData.SetHorizontalOffset(this, offset);
            } 
        } 

        ///  
        /// 
        /// 
        void IScrollInfo.SetVerticalOffset(double offset)
        { 
            if (_scrollData != null)
            { 
                _scrollData.SetVerticalOffset(this, offset); 
            }
        } 

        /// 
        /// 
        ///  
        Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle)
        { 
            if (_scrollData == null) 
            {
                rectangle = Rect.Empty; 
            }
            else
            {
                rectangle = _scrollData.MakeVisible(this, visual, rectangle); 
            }
 
            return rectangle; 
        }
 
        /// 
        /// 
        /// 
        bool IScrollInfo.CanVerticallyScroll 
        {
            get 
            { 
                return (_scrollData != null) ? _scrollData.CanVerticallyScroll : false;
            } 
            set
            {
                if (_scrollData != null)
                { 
                    _scrollData.CanVerticallyScroll = value;
                } 
            } 
        }
 
        /// 
        /// 
        /// 
        bool IScrollInfo.CanHorizontallyScroll 
        {
            get 
            { 
                return (_scrollData != null) ? _scrollData.CanHorizontallyScroll : false;
            } 
            set
            {
                if (_scrollData != null)
                { 
                    _scrollData.CanHorizontallyScroll = value;
                } 
            } 
        }
 
        /// 
        /// 
        /// 
        double IScrollInfo.ExtentWidth 
        {
            get 
            { 
                return (_scrollData != null) ? _scrollData.ExtentWidth : 0;
            } 
        }

        /// 
        ///  
        /// 
        double IScrollInfo.ExtentHeight 
        { 
            get
            { 
                return (_scrollData != null) ? _scrollData.ExtentHeight : 0;
            }
        }
 
        /// 
        ///  
        ///  
        double IScrollInfo.ViewportWidth
        { 
            get
            {
                return (_scrollData != null) ? _scrollData.ViewportWidth : 0;
            } 
        }
 
        ///  
        /// 
        ///  
        double IScrollInfo.ViewportHeight
        {
            get
            { 
                return (_scrollData != null) ? _scrollData.ViewportHeight : 0;
            } 
        } 

        ///  
        /// 
        /// 
        double IScrollInfo.HorizontalOffset
        { 
            get
            { 
                return (_scrollData != null) ? _scrollData.HorizontalOffset : 0; 
            }
        } 

        /// 
        /// 
        ///  
        double IScrollInfo.VerticalOffset
        { 
            get 
            {
                return (_scrollData != null) ? _scrollData.VerticalOffset : 0; 
            }
        }

        ///  
        /// 
        ///  
        ScrollViewer IScrollInfo.ScrollOwner 
        {
            get 
            {
                return (_scrollData != null) ? _scrollData.ScrollOwner : null;
            }
 
            set
            { 
                if (_scrollData == null) 
                {
                    // Create cached scroll info. 
                    _scrollData = new ScrollData();
                }
                _scrollData.SetScrollOwner(this, value);
            } 
        }
 
        #endregion Public Methods 

        //------------------------------------------------------ 
        //
        //  Protected Methods
        //
        //------------------------------------------------------ 

        #region Protected Methods 
 
        // Calculates ideal content size.
        protected override Size MeasureOverride(Size constraint) 
        {
            // Lazy init TextContainer listeners on the first measure.
            EnsureTextContainerListeners();
 
            // Lazy allocate _lineMetrics on the first measure.
            if (_lineMetrics == null) 
            { 
                _lineMetrics = new List(1);
            } 

            Size desiredSize;

            // Init a cache we'll use here and in the following ArrangeOverride call. 
            _cache = null;
            EnsureCache(); 
 
            LineProperties lineProperties = _cache.LineProperties;
 
            // Skip the measure if constraints have not changed.
            bool widthChanged = !DoubleUtil.AreClose(constraint.Width, _previousConstraint.Width);

            // If width changed and TextAlignment is Center or Right the visual offsets of the visible 
            // lines need to be recalculated.
            if (widthChanged && lineProperties.TextAlignment != TextAlignment.Left) 
            { 
                _viewportLineVisuals = null;
            } 

            bool constraintschanged = widthChanged &&
                                      lineProperties.TextWrapping != TextWrapping.NoWrap;
 
            if (_lineMetrics.Count == 0 || constraintschanged)
            { 
                // Null out the dirty list when constraints change -- everything's dirty. 
                _dirtyList = null;
            } 
            else if (_dirtyList == null && !this.IsBackgroundLayoutPending)
            {
                // No dirty region, no constraint change, no pending background layout.
                desiredSize = _contentSize; 
                goto Exit;
            } 
 
            // Treat an insert into an empty document just like a full invalidation,
            // to allow background layout to run. 
            if (_dirtyList != null &&
                _lineMetrics.Count == 1 && _lineMetrics[0].EndOffset == 0)
            {
                _lineMetrics.Clear(); 
                _viewportLineVisuals = null;
                _dirtyList = null; 
            } 

            Size safeConstraint = constraint; 
            // Make sure that TextFormatter limitations are not exceeded.
            //
            TextDpi.EnsureValidLineWidth(ref safeConstraint);
 
            // Do the measure.
            if (_dirtyList == null) 
            { 
                if (constraintschanged)
                { 
                    _lineMetrics.Clear();
                    _viewportLineVisuals = null;
                }
                desiredSize = FullMeasureTick(safeConstraint.Width, lineProperties); 
            }
            else 
            { 
                desiredSize = IncrementalMeasure(safeConstraint.Width, lineProperties);
            } 
            Invariant.Assert(_lineMetrics.Count >= 1);

            _dirtyList = null;
 
            double oldWidth = _contentSize.Width;
            _contentSize = desiredSize; 
 
            // If the width has changed we need to reformat if we're centered or right aligned so the
            // spacing gets properly updated. 
            if (oldWidth != desiredSize.Width && lineProperties.TextAlignment != TextAlignment.Left)
            {
                Rerender();
            } 

Exit: 
            // DesiredSize is set to the calculated size of the content. 
            // If hosted by ScrollViewer, desired size is limited to constraint.
            if (_scrollData != null) 
            {
                desiredSize.Width = Math.Min(constraint.Width, desiredSize.Width);
                desiredSize.Height = Math.Min(constraint.Height, desiredSize.Height);
            } 

            _previousConstraint = constraint; 
 
            return desiredSize;
        } 

        // Arranges content within a specified constraint.
        protected override Size ArrangeOverride(Size arrangeSize)
        { 
            if (_lineMetrics == null || _lineMetrics.Count == 0)
            { 
                // No matching MeasureOverride call. 
                goto Exit;
            } 

            EnsureCache();

            ArrangeScrollData(arrangeSize); 
            ArrangeVisuals(arrangeSize);
 
            _cache = null; 

            FireTextViewUpdatedEvent(); 
            InvalidateVisual();

Exit:
            return arrangeSize; 
        }
 
        // Render callback for this TextBoxView. 
        protected override void OnRender(DrawingContext context)
        { 
            // Render a transparent Rect to enable hit-testing even when content does not fill
            // the entire viewport.
            //
            context.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height)); 
        }
 
        ///  
        ///   Derived class must implement to support Visual children. The method must return
        ///    the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. 
        ///
        ///    By default a Visual does not have any children.
        ///
        ///  Remark: 
        ///       During this virtual call it is not valid to modify the Visual tree.
        ///  
        protected override Visual GetVisualChild(int index) 
        {
            if (index >= this.VisualChildrenCount) 
            {
                throw new ArgumentOutOfRangeException("index");
            }
 
            return _visualChildren[index];
        } 
 
        #endregion Protected Methods
 
        //-----------------------------------------------------
        //
        //  Protected Properties
        // 
        //------------------------------------------------------
 
        #region Protected Properties 

        ///  
        ///  Derived classes override this property to enable the Visual code to enumerate
        ///  the Visual children. Derived classes need to return the number of children
        ///  from this method.
        /// 
        ///    By default a Visual does not have any children.
        /// 
        ///  Remark: 
        ///      During this virtual method the Visual tree must not be modified.
        ///  
        protected override int VisualChildrenCount
        {
            get
            { 
                return (_visualChildren == null) ? 0 : _visualChildren.Count;
            } 
        } 

        #endregion Protected Properties 

        //-----------------------------------------------------
        //
        //  Internal Methods 
        //
        //----------------------------------------------------- 
 
        #region Internal Methods
 
        /// 
        /// 
        /// 
        ITextPointer ITextView.GetTextPositionFromPoint(Point point, bool snapToText) 
        {
            Invariant.Assert(this.IsLayoutValid); 
 
            point = TransformToDocumentSpace(point);
 
            int lineIndex = GetLineIndexFromPoint(point, snapToText);
            ITextPointer position;

            if (lineIndex == -1) 
            {
                position = null; 
            } 
            else
            { 
                position = GetTextPositionFromDistance(lineIndex, point.X);
                position.Freeze();
            }
 
            return position;
        } 
 
        /// 
        ///  
        /// 
        Rect ITextView.GetRectangleFromTextPosition(ITextPointer position)
        {
            Rect rect; 

            Invariant.Assert(this.IsLayoutValid); 
            Invariant.Assert(Contains(position)); 

            int offset = position.Offset; 
            if (offset > 0 && position.LogicalDirection == LogicalDirection.Backward)
            {
                // TextBoxLine always gets the forward Rect, so back up to preceding char.
                offset--; 
            }
 
            int lineIndex = GetLineIndexFromOffset(offset); 
            FlowDirection flowDirection;
            LineProperties lineProperties; 

            using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties))
            {
                rect = line.GetBoundsFromTextPosition(offset, out flowDirection); 
            }
 
            if (!rect.IsEmpty) // Empty rects can't be modified. 
            {
                rect.Y += lineIndex * _lineHeight; 

                // Return only TopLeft and Height.
                // Adjust rect.Left by taking into account flow direction of the
                // content and orientation of input position. 
                if (lineProperties.FlowDirection != flowDirection)
                { 
                    if (position.LogicalDirection == LogicalDirection.Forward || position.Offset == 0) 
                    {
                        rect.X = rect.Right; 
                    }
                }
                else
                { 
                    if (position.LogicalDirection == LogicalDirection.Backward && position.Offset > 0)
                    { 
                        rect.X = rect.Right; 
                    }
                } 

                rect.Width = 0;
            }
 
            return TransformToVisualSpace(rect);
        } 
 
        /// 
        ///  
        /// 
        Rect ITextView.GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
        {
            transform = Transform.Identity; 

            return ((ITextView)this).GetRectangleFromTextPosition(position); 
        } 

        ///  
        /// 
        /// 
        Geometry ITextView.GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
        { 
            Invariant.Assert(this.IsLayoutValid);
 
            Geometry geometry = null; 
            double endOfParaGlyphWidth = ((Control)_host).FontSize * CaretElement.c_endOfParaMagicMultiplier;
 
            // Since background layout may be running, clip to the computed region.
            int startOffset = Math.Min(_lineMetrics[_lineMetrics.Count-1].EndOffset, startPosition.Offset);
            int endOffset = Math.Min(_lineMetrics[_lineMetrics.Count - 1].EndOffset, endPosition.Offset);
 
            // Find the intersection of the viewport with the requested range.
            int firstLineIndex; 
            int lastLineIndex; 
            GetVisibleLines(out firstLineIndex, out lastLineIndex);
 
            firstLineIndex = Math.Max(firstLineIndex, GetLineIndexFromOffset(startOffset, LogicalDirection.Forward));
            lastLineIndex = Math.Min(lastLineIndex, GetLineIndexFromOffset(endOffset, LogicalDirection.Backward));

            if (firstLineIndex > lastLineIndex) 
            {
                // Visible region does not intersect with geometry. 
                return null; 
            }
 
            // Partially covered lines require a line format, so we'll handle them specially.
            // Only the first and last line are potentially partially covered.
            bool firstLinePartiallyCovered = _lineMetrics[firstLineIndex].Offset < startOffset ||
                                             _lineMetrics[firstLineIndex].EndOffset > endOffset; 
            bool lastLinePartiallyCovered =  _lineMetrics[lastLineIndex].Offset < startOffset ||
                                             _lineMetrics[lastLineIndex].EndOffset > endOffset; 
 
            TextAlignment alignment = this.CalculatedTextAlignment;
            int lineIndex = firstLineIndex; 

            // If we don't cover the entire first line, special case it.
            if (firstLinePartiallyCovered)
            { 
                GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
                lineIndex++; 
            } 

            // If it is completely covered, adjust lastLineIndex such that we handle 
            // the last line in the loop below.
            if (firstLineIndex <= lastLineIndex && !lastLinePartiallyCovered)
            {
                lastLineIndex++; 
            }
 
            // Handle all the lines that are entirely covered -- they don't require any heavy lifting. 
            for (; lineIndex < lastLineIndex; lineIndex++)
            { 
                double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment);
                Rect rect = new Rect(contentOffset, lineIndex * _lineHeight, _lineMetrics[lineIndex].Width, _lineHeight);

                // Add extra padding at the end of lines with linebreaks. 
                ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].EndOffset, LogicalDirection.Backward);
                if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward)) 
                { 
                    rect.Width += endOfParaGlyphWidth;
                } 

                rect = TransformToVisualSpace(rect);
                CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
            } 

            // If we don't cover the entire last line, special case it. 
            // Otherwise, we already handled it in the loop above. 
            if (lineIndex == lastLineIndex && lastLinePartiallyCovered)
            { 
                GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
            }

            return geometry; 
        }
 
        ///  
        /// 
        ///  
        ITextPointer ITextView.GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved)
        {
            Invariant.Assert(this.IsLayoutValid);
            Invariant.Assert(Contains(position)); 

            newSuggestedX = suggestedX; 
            int lineIndex = GetLineIndexFromPosition(position); 
            int nextLineIndex = Math.Max(0, Math.Min(_lineMetrics.Count - 1, lineIndex + count));
            linesMoved = nextLineIndex - lineIndex; 

            ITextPointer nextLinePosition;
            if (linesMoved == 0)
            { 
                nextLinePosition = position.GetFrozenPointer(position.LogicalDirection);
            } 
            else if (DoubleUtil.IsNaN(suggestedX)) 
            {
                nextLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex + linesMoved].Offset, LogicalDirection.Forward); 
            }
            else
            {
                suggestedX -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width)); 
                nextLinePosition = GetTextPositionFromDistance(nextLineIndex, suggestedX);
            } 
 
            nextLinePosition.Freeze();
            return nextLinePosition; 
        }

        /// 
        ///  
        /// 
        ITextPointer ITextView.GetPositionAtNextPage(ITextPointer position, Point suggestedOffset, int count, out Point newSuggestedOffset, out int pagesMoved) 
        { 
            // This method is not expected to be called.
            // Caller should only call this method when !ITextView.Contains(position). 
            // Since TextBox is not paginated, this view always contains all TextContainer positions.
            Invariant.Assert(false);

            newSuggestedOffset = new Point(); 
            pagesMoved = 0;
            return null; 
        } 

        ///  
        /// 
        /// 
        bool ITextView.IsAtCaretUnitBoundary(ITextPointer position)
        { 
            Invariant.Assert(this.IsLayoutValid);
            Invariant.Assert(Contains(position)); 
            bool boundary = false; 

            int lineIndex = GetLineIndexFromPosition(position); 

            CharacterHit sourceCharacterHit = new CharacterHit();
            if (position.LogicalDirection == LogicalDirection.Forward)
            { 
                // Forward context, go to leading edge of position offset
                sourceCharacterHit = new CharacterHit(position.Offset, 0); 
            } 
            else if (position.LogicalDirection == LogicalDirection.Backward)
            { 
                if (position.Offset > _lineMetrics[lineIndex].Offset)
                {
                    // For backward context, go to trailing edge of previous character
                    sourceCharacterHit = new CharacterHit(position.Offset - 1, 1); 
                }
                else 
                { 
                    // There is no previous trailing edge on this line. We don't consider this a unit boundary.
                    return false; 
                }
            }

            using (TextBoxLine line = GetFormattedLine(lineIndex)) 
            {
                boundary = line.IsAtCaretCharacterHit(sourceCharacterHit); 
            } 

            return boundary; 
        }

        /// 
        ///  
        /// 
        ITextPointer ITextView.GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction) 
        { 
            Invariant.Assert(this.IsLayoutValid);
            Invariant.Assert(Contains(position)); 

            // Special case document start/end.
            if (position.Offset == 0 && direction == LogicalDirection.Backward)
            { 
                return position.GetFrozenPointer(LogicalDirection.Forward);
            } 
            else if (position.Offset == _host.TextContainer.SymbolCount && direction == LogicalDirection.Forward) 
            {
                return position.GetFrozenPointer(LogicalDirection.Backward); 
            }

            int lineIndex = GetLineIndexFromPosition(position);
 
            CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0);
            CharacterHit nextCharacterHit; 
 
            using (TextBoxLine line = GetFormattedLine(lineIndex))
            { 
                if (direction == LogicalDirection.Forward)
                {
                    // Get the next caret position from the line
                    nextCharacterHit = line.GetNextCaretCharacterHit(sourceCharacterHit); 
                }
                else 
                { 
                    // Get previous caret position from the line
                    nextCharacterHit = line.GetPreviousCaretCharacterHit(sourceCharacterHit); 
                }
            }

            // Determine logical direction for next caret index and create TextPointer from it. 
            LogicalDirection logicalDirection;
            if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].EndOffset && 
                direction == LogicalDirection.Forward) 
            {
                // Going forward brought us to the end of a line, context must be forward for next line. 
                if (lineIndex == _lineMetrics.Count - 1)
                {
                    // Last line so context must stay backward.
                    logicalDirection = LogicalDirection.Backward; 
                }
                else 
                { 
                    logicalDirection = LogicalDirection.Forward;
                } 
            }
            else if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset &&
                     direction == LogicalDirection.Backward)
            { 
                // Going backward brought us to the start of a line, context must be backward for previous line.
                if (lineIndex == 0) 
                { 
                    // First line, so we will stay forward.
                    logicalDirection = LogicalDirection.Forward; 
                }
                else
                {
                    logicalDirection = LogicalDirection.Backward; 
                }
            } 
            else 
            {
                logicalDirection = (nextCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward; 
            }

            ITextPointer nextCaretUnitPosition = _host.TextContainer.CreatePointerAtOffset(nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength, logicalDirection);
            nextCaretUnitPosition.Freeze(); 
            return nextCaretUnitPosition;
        } 
 
        /// 
        ///  
        /// 
        ITextPointer ITextView.GetBackspaceCaretUnitPosition(ITextPointer position)
        {
            Invariant.Assert(this.IsLayoutValid); 
            Invariant.Assert(Contains(position));
 
            // Special case document start. 
            if (position.Offset == 0)
            { 
                return position.GetFrozenPointer(LogicalDirection.Forward);
            }

            int lineIndex = GetLineIndexFromPosition(position, LogicalDirection.Backward); 

            CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0); 
            CharacterHit backspaceCharacterHit; 

            using (TextBoxLine line = GetFormattedLine(lineIndex)) 
            {
                backspaceCharacterHit = line.GetBackspaceCaretCharacterHit(sourceCharacterHit);
            }
 
            LogicalDirection logicalDirection;
            if (backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset) 
            { 
                // Going backward brought us to the start of a line, context must be backward for previous line
                if (lineIndex == 0) 
                {
                    // First line, so we will stay forward.
                    logicalDirection = LogicalDirection.Forward;
                } 
                else
                { 
                    logicalDirection = LogicalDirection.Backward; 
                }
            } 
            else
            {
                logicalDirection = (backspaceCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
            } 

            ITextPointer backspaceUnitPosition = _host.TextContainer.CreatePointerAtOffset(backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength, logicalDirection); 
            backspaceUnitPosition.Freeze(); 
            return backspaceUnitPosition;
        } 

        /// 
        /// 
        ///  
        TextSegment ITextView.GetLineRange(ITextPointer position)
        { 
            Invariant.Assert(this.IsLayoutValid); 
            Invariant.Assert(Contains(position));
 
            int lineIndex = GetLineIndexFromPosition(position);

            ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset, LogicalDirection.Forward);
            ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset + _lineMetrics[lineIndex].ContentLength, LogicalDirection.Forward); 

            return new TextSegment(start, end, true); 
        } 

        ///  
        /// 
        /// 
        ReadOnlyCollection ITextView.GetGlyphRuns(ITextPointer start, ITextPointer end)
        { 
            // This method is not expected to be called.
            Invariant.Assert(false); 
            return null; 
        }
 
        /// 
        /// 
        /// 
        bool ITextView.Contains(ITextPointer position) 
        {
            return Contains(position); 
        } 

        ///  
        /// 
        /// 
        void ITextView.BringPositionIntoViewAsync(ITextPointer position, object userState)
        { 
            // This method is not expected to be called.
            // Caller should only call this method when !ITextView.Contains(position). 
            // Since TextBox is not paginated, this view always contains all TextContainer positions. 
            Invariant.Assert(false);
        } 

        /// 
        /// 
        ///  
        void ITextView.BringPointIntoViewAsync(Point point, object userState)
        { 
            // This method is not expected to be called. 
            // Caller should only call this method when !ITextView.Contains(position).
            // Since TextBox is not paginated, this view always contains all TextContainer positions. 
            Invariant.Assert(false);
        }

        ///  
        /// 
        ///  
        void ITextView.BringLineIntoViewAsync(ITextPointer position, double suggestedX, int count, object userState) 
        {
            // This method is not expected to be called. 
            // Caller should only call this method when !ITextView.Contains(position).
            // Since TextBox is not paginated, this view always contains all TextContainer positions.
            Invariant.Assert(false);
        } 

        ///  
        ///  
        /// 
        void ITextView.BringPageIntoViewAsync(ITextPointer position, Point suggestedOffset, int count, object userState) 
        {
            // This method is not expected to be called.
            // Caller should only call this method when !ITextView.Contains(position).
            // Since TextBox is not paginated, this view always contains all TextContainer positions. 
            Invariant.Assert(false);
        } 
 
        /// 
        ///  
        /// 
        void ITextView.CancelAsync(object userState)
        {
            // This method is not expected to be called. 
            // Caller should only call this method when !ITextView.Contains(position).
            // Since TextBox is not paginated, this view always contains all TextContainer positions. 
            Invariant.Assert(false); 
        }
 
        /// 
        /// 
        /// 
        bool ITextView.Validate() 
        {
            UpdateLayout(); 
            return this.IsLayoutValid; 
        }
 
        /// 
        /// 
        /// 
        bool ITextView.Validate(Point point) 
        {
            return ((ITextView)this).Validate(); 
        } 

        ///  
        /// 
        /// 
        bool ITextView.Validate(ITextPointer position)
        { 
            if (position.TextContainer != _host.TextContainer)
                return false; 
 
            if (!this.IsLayoutValid)
            { 
                // UpdateLayout has side-effects even when measure and arrange are clean,
                // so avoid calling it unless we must.
                UpdateLayout();
 
                if (!this.IsLayoutValid)
                { 
                    // If we can't get the layout system to give us a valid 
                    // measure/arrange, there's no hope.
                    return false; 
                }
            }

            // Force background layout iterations until we catch up 
            // with the position.
 
            int lastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset; 

            while (!Contains(position)) 
            {
                InvalidateMeasure();
                UpdateLayout();
 
                // UpdateLayout may invalidate the view.
                if (!this.IsLayoutValid) 
                    break; 

                // Break if background layout is not progressing. 
                int newLastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
                if (lastValidOffset >= newLastValidOffset)
                    break;
                lastValidOffset = newLastValidOffset; 
            }
 
            return this.IsLayoutValid && Contains(position); 
        }
 
        /// 
        /// 
        /// 
        void ITextView.ThrottleBackgroundTasksForUserInput() 
        {
            if (_throttleBackgroundTimer == null) 
            { 
                // Start up a timer.  Until the timer fires, we'll disable
                // all background layout.  This leaves the TextBox responsive 
                // to user input.
                _throttleBackgroundTimer = new DispatcherTimer(DispatcherPriority.Background);
                _throttleBackgroundTimer.Interval = new TimeSpan(0, 0, _throttleBackgroundSeconds);
                _throttleBackgroundTimer.Tick += new EventHandler(OnThrottleBackgroundTimeout); 
            }
            else 
            { 
                // Reset the timer.
                _throttleBackgroundTimer.Stop(); 
            }

            _throttleBackgroundTimer.Start();
        } 

        // Forces a full document invalidation. 
        // Called when properties that do affect layout (eg, FontSize) 
        // change value.
        internal void Remeasure() 
        {
            if (_lineMetrics != null)
            {
                _lineMetrics.Clear(); 
                _viewportLineVisuals = null;
            } 
            InvalidateMeasure(); 
        }
 
        // Forces a visual invalidation.
        // Called when properties that do not affect layout (eg, ForegroundColor)
        // change value.
        internal void Rerender() 
        {
            _viewportLineVisuals = null; 
            InvalidateArrange(); 
        }
 
        #endregion Internal Methods

        //-----------------------------------------------------
        // 
        //  Internal Properties
        // 
        //------------------------------------------------------ 

        #region Internal Properties 

        // Control that owns this TextBoxView.
        internal ITextBoxViewHost Host
        { 
            get
            { 
                return _host; 
            }
        } 

        /// 
        /// 
        ///  
        UIElement ITextView.RenderScope
        { 
            get 
            {
                return this; 
            }
        }

        ///  
        /// 
        ///  
        ITextContainer ITextView.TextContainer 
        {
            get 
            {
                return _host.TextContainer;
            }
        } 

        ///  
        ///  
        /// 
        bool ITextView.IsValid 
        {
            get
            {
                return this.IsLayoutValid; 
            }
        } 
 
        /// 
        ///  
        /// 
        bool ITextView.RendersOwnSelection
        {
            get 
            {
                return false; 
            } 
        }
 

        /// 
        /// 
        ///  
        ReadOnlyCollection ITextView.TextSegments
        { 
            get 
            {
                List segments = new List(1); 
                if (_lineMetrics != null)
                {
                    ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[0].Offset, LogicalDirection.Backward);
                    ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[_lineMetrics.Count-1].EndOffset, LogicalDirection.Forward); 

                    segments.Add(new TextSegment(start, end, true)); 
                } 
                return new ReadOnlyCollection(segments);
            } 
        }

        #endregion Internal Properties
 
        //-----------------------------------------------------
        // 
        //  Internal Events 
        //
        //------------------------------------------------------ 

        #region Internal Events

        ///  
        /// 
        ///  
        // Caller should only call this method when !ITextView.Contains(position). 
        // Since TextBox is not paginated, this view always contains all TextContainer positions.
        event BringPositionIntoViewCompletedEventHandler ITextView.BringPositionIntoViewCompleted 
        {
            add { Invariant.Assert(false); }
            remove { Invariant.Assert(false); }
        } 

        ///  
        ///  
        /// 
        // Caller should only call this method when !ITextView.Contains(position). 
        // Since TextBox is not paginated, this view always contains all TextContainer positions.
        event BringPointIntoViewCompletedEventHandler ITextView.BringPointIntoViewCompleted
        {
            add { Invariant.Assert(false); } 
            remove { Invariant.Assert(false); }
        } 
 
        /// 
        ///  
        /// 
        // Caller should only call this method when !ITextView.Contains(position).
        // Since TextBox is not paginated, this view always contains all TextContainer positions.
        event BringLineIntoViewCompletedEventHandler ITextView.BringLineIntoViewCompleted 
        {
            add { Invariant.Assert(false); } 
            remove { Invariant.Assert(false); } 
        }
 
        /// 
        /// 
        /// 
        // Caller should only call this method when !ITextView.Contains(position). 
        // Since TextBox is not paginated, this view always contains all TextContainer positions.
        event BringPageIntoViewCompletedEventHandler ITextView.BringPageIntoViewCompleted 
        { 
            add { Invariant.Assert(false); }
            remove { Invariant.Assert(false); } 
        }

        /// 
        ///  
        /// 
        event EventHandler ITextView.Updated 
        { 
            add { UpdatedEvent += value; }
            remove { UpdatedEvent -= value; } 
        }

        #endregion Internal Events
 
        //------------------------------------------------------
        // 
        //  Private Methods 
        //
        //----------------------------------------------------- 

        #region Private Methods

        // Initializes TextContainer event listeners. 
        // Called on the first Measure.
        // We delay the init to avoid responding to events before we're attached 
        // to the visual tree, when it doesn't matter. 
        private void EnsureTextContainerListeners()
        { 
            if (CheckFlags(Flags.TextContainerListenersInitialized))
                return;

            _host.TextContainer.Changing += new EventHandler(OnTextContainerChanging); 
            _host.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange);
            _host.TextContainer.Highlights.Changed += new HighlightChangedEventHandler(OnHighlightChanged); 
 
            SetFlags(true, Flags.TextContainerListenersInitialized);
        } 

        // Initializes state used across a measure/arrange calculation.
        private void EnsureCache()
        { 
            if (_cache == null)
            { 
                _cache = new TextCache(this); 
            }
        } 

        // Reads the current (interesting) property values on the owning TextBox.
        private LineProperties GetLineProperties()
        { 
            TextProperties defaultTextProperties = new TextProperties((Control)_host, _host.IsTypographyDefaultValue);
 
            // Pass page width and height as double.MaxValue when creating LineProperties, since TextBox does not restrict 
            // TextIndent or LineHeight.
            return new LineProperties((Control)_host, (Control)_host, defaultTextProperties, null, this.CalculatedTextAlignment); 
        }

        // Callback from the TextContainer when a change block starts.
        private void OnTextContainerChanging(object sender, EventArgs args) 
        {
            // 
        } 

        // Callback from the TextContainer on a document edit. 
        private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args)
        {
            if (args.Count == 0)
            { 
                // A no-op for this control.  Happens when IMECharCount updates happen
                // without corresponding SymbolCount changes. 
                return; 
            }
 
            //
            // Add the change to our dirty list.
            //
 
            if (_dirtyList == null)
            { 
                _dirtyList = new DtrList(); 
            }
 
            DirtyTextRange dirtyTextRange = new DirtyTextRange(args);
            _dirtyList.Merge(dirtyTextRange);

            // 
            // Force a re-measure.
            // 
            InvalidateMeasure(); 
        }
 
        // Callback from the TextContainer when a highlight changes.
        private void OnHighlightChanged(object sender, HighlightChangedEventArgs args)
        {
            // The only supported highlight type for TextBoxView is SpellerHighlight. 
            if (args.OwnerType != typeof(SpellerHighlightLayer))
            { 
                return; 
            }
 
            //
            // Add the change to our dirty list.
            //
 
            foreach (TextSegment segment in args.Ranges)
            { 
                // Skip segments that don't intersect with the viewport. 
                if (this.IsLayoutValid)
                { 
                    int firstLineIndex;
                    int lastLineIndex;

                    GetVisibleLines(out firstLineIndex, out lastLineIndex); 

                    if (_lineMetrics[firstLineIndex].Offset >= segment.End.Offset) 
                    { 
                        // This segment preceeds the viewport, skip it.
                        continue; 
                    }
                    else if (_lineMetrics[lastLineIndex].EndOffset <= segment.Start.Offset)
                    {
                        // This segment follows the viewport, no need to look at it or following segments. 
                        break;
                    } 
                } 

                // This segment is visible, prepare to invalidate arrange. 
                if (_dirtyList == null)
                {
                    _dirtyList = new DtrList();
                } 

                int positionsCovered = segment.End.Offset - segment.Start.Offset; 
                DirtyTextRange dirtyTextRange = new DirtyTextRange(segment.Start.Offset, positionsCovered, positionsCovered, true /* affectsRenderOnly */); 
                _dirtyList.Merge(dirtyTextRange);
            } 

            //
            // Force a visual update.
            // 

            if (_dirtyList != null) 
            { 
                InvalidateArrange();
            } 
        }

        // Sets boolean state.
        private void SetFlags(bool value, Flags flags) 
        {
            _flags = value ? (_flags | flags) : (_flags & (~flags)); 
        } 

        // Reads boolean state. 
        private bool CheckFlags(Flags flags)
        {
            return ((_flags & flags) == flags);
        } 

        // Announces a layout change to any listeners. 
        private void FireTextViewUpdatedEvent() 
        {
            if (UpdatedEvent != null) 
            {
                UpdatedEvent(this, EventArgs.Empty);
            }
        } 

        // Returns the index of a line containing point, or -1 if no such 
        // line exists.  If snapToText is true, the closest match is returned. 
        //
        // Point must be in document space. 
        private int GetLineIndexFromPoint(Point point, bool snapToText)
        {
            Invariant.Assert(_lineMetrics.Count >= 1);
 
            // Special case points above or below the content.
 
            if (point.Y < 0) 
            {
                return snapToText ? 0 : -1; 
            }
            if (point.Y >= _lineHeight * _lineMetrics.Count)
            {
                return snapToText ? _lineMetrics.Count-1 : -1; 
            }
 
            // Do a binary search to find the matching line. 

            int index = -1; 
            int min = 0;
            int max = _lineMetrics.Count;

            while (min < max) 
            {
                index = min + (max - min) / 2; 
                LineRecord record = _lineMetrics[index]; 
                double lineY = _lineHeight * index;
 
                if (point.Y < lineY)
                {
                    max = index;
                } 
                else if (point.Y >= lineY + _lineHeight)
                { 
                    min = index + 1; 
                }
                else 
                {
                    if (!snapToText &&
                        (point.X < 0 || point.X >= record.Width))
                    { 
                        index = -1;
                    } 
                    break; 
                }
            } 

            return (min < max) ? index : -1;
        }
 
        // Returns the index of the line containing position.
        private int GetLineIndexFromPosition(ITextPointer position) 
        { 
            return GetLineIndexFromOffset(position.Offset, position.LogicalDirection);
        } 

        // Returns the index of the line containing position.
        private int GetLineIndexFromPosition(ITextPointer position, LogicalDirection direction)
        { 
            return GetLineIndexFromOffset(position.Offset, direction);
        } 
 
        // Returns the index of the line containing the specified offset.
        private int GetLineIndexFromOffset(int offset, LogicalDirection direction) 
        {
            if (offset > 0 && direction == LogicalDirection.Backward)
            {
                // GetLineIndexFromOffset has forward bias, so backup for backward search. 
                offset--;
            } 
 
            return GetLineIndexFromOffset(offset);
        } 

        // Returns the index of the line containing the specified offset.
        // Offset has forward direction -- we always return the following
        // line in ambiguous cases. 
        private int GetLineIndexFromOffset(int offset)
        { 
            int index = -1; 
            int min = 0;
            int max = _lineMetrics.Count; 

            Invariant.Assert(_lineMetrics.Count >= 1);

            while (true) 
            {
                Invariant.Assert(min < max, "Couldn't find offset!"); 
 
                index = min + (max - min) / 2;
                LineRecord record = _lineMetrics[index]; 

                if (offset < record.Offset)
                {
                    max = index; 
                }
                else if (offset > record.EndOffset) 
                { 
                    min = index + 1;
                } 
                else
                {
                    if (offset == record.EndOffset && index < _lineMetrics.Count - 1)
                    { 
                        // Go to the next line if we're between two lines.
                        index++; 
                    } 
                    break;
                } 
            }

            return index;
        } 

        // Returns a formatted TextBoxLine at the specified index. 
        // Caller must Dispose the TextBoxLine. 
        // This method is expensive.
        private TextBoxLine GetFormattedLine(int lineIndex) 
        {
            LineProperties lineProperties;
            return GetFormattedLine(lineIndex, out lineProperties);
        } 

        // Returns a formatted TextBoxLine at the specified index. 
        // Caller must Dispose the TextBoxLine. 
        // This method is expensive.
        private TextBoxLine GetFormattedLine(int lineIndex, out LineProperties lineProperties) 
        {
            TextBoxLine line = new TextBoxLine(this);
            LineRecord metrics = _lineMetrics[lineIndex];
            lineProperties = GetLineProperties(); 
            TextFormatter formatter = TextFormatter.FromCurrentDispatcher();
 
            double width = GetWrappingWidth(this.RenderSize.Width); 
            double formatWidth = GetWrappingWidth(_previousConstraint.Width);
 
            line.Format(metrics.Offset, formatWidth, width, lineProperties, new TextRunCache(), formatter);
            Invariant.Assert(metrics.Length == line.Length, "Line is out of sync with metrics!");

            return line; 
        }
 
        // Returns a TextPointer at the position closest to pixel offset x 
        // on a specified line.
        private ITextPointer GetTextPositionFromDistance(int lineIndex, double x) 
        {
            LineProperties lineProperties;
            CharacterHit charIndex;
            LogicalDirection logicalDirection; 

            using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties)) 
            { 
                charIndex = line.GetTextPositionFromDistance(x);
 
                logicalDirection = (charIndex.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
            }

            return _host.TextContainer.CreatePointerAtOffset(charIndex.FirstCharacterIndex + charIndex.TrailingLength, logicalDirection); 
        }
 
        // Updates IScrollInfo related state on an ArrangeOverride call. 
        private void ArrangeScrollData(Size arrangeSize)
        { 
            if (_scrollData == null)
            {
                return;
            } 

            bool invalidateScrollInfo = false; 
 
            if (!DoubleUtil.AreClose(_scrollData.Viewport, arrangeSize))
            { 
                _scrollData.Viewport = arrangeSize;
                invalidateScrollInfo = true;
            }
 
            if (!DoubleUtil.AreClose(_scrollData.Extent, _contentSize))
            { 
                _scrollData.Extent = _contentSize; 
                invalidateScrollInfo = true;
            } 

            Vector offset = new Vector(
                Math.Max(0, Math.Min(_scrollData.ExtentWidth - _scrollData.ViewportWidth, _scrollData.HorizontalOffset)),
                Math.Max(0, Math.Min(_scrollData.ExtentHeight - _scrollData.ViewportHeight, _scrollData.VerticalOffset))); 

            if (!DoubleUtil.AreClose(offset, _scrollData.Offset)) 
            { 
                _scrollData.Offset = offset;
                invalidateScrollInfo = true; 
            }

            if (invalidateScrollInfo && _scrollData.ScrollOwner != null)
            { 
                _scrollData.ScrollOwner.InvalidateScrollInfo();
            } 
        } 

        // Updates line visuals on an ArrangeOverride call. 
        private void ArrangeVisuals(Size arrangeSize)
        {
            // If _dirtyList is non-null here, it means we
            // have pending highlight changes to sync to. 
            // These changes never affect line metrics, but
            // they will clear out any cached Visuals affected. 
            if (_dirtyList != null) 
            {
                InvalidateDirtyVisuals(); 
                _dirtyList = null;
            }

            // 
            // Clear all visual children and initize state.
            // 
 
            if (_visualChildren == null)
            { 
                _visualChildren = new VisualCollection(this);
            }
            _visualChildren.Clear();
 
            EnsureCache();
 
            LineProperties lineProperties = _cache.LineProperties; 
            TextBoxLine line = new TextBoxLine(this);
 
            //
            // Calculate the current viewport extent, in lines.
            // We won't do any work for lines that aren't visible.
            // 

            int firstLineIndex; 
            int lastLineIndex; 

            GetVisibleLines(out firstLineIndex, out lastLineIndex); 

            SetViewportLines(firstLineIndex, lastLineIndex);

            double width = GetWrappingWidth(arrangeSize.Width); 

            double horizontalOffset = GetTextAlignmentCorrection(lineProperties.TextAlignment, width); 
            double verticalOffset = this.VerticalAlignmentOffset; 

            if (_scrollData != null) 
            {
                horizontalOffset -= _scrollData.HorizontalOffset;
                verticalOffset -= _scrollData.VerticalOffset;
            } 

            // 
            // Iterate across the visible lines. 
            // If we have a cached visual, simply update its current offset.
            // Otherwise, allocate and render a new visual. 
            //

            double formatWidth = GetWrappingWidth(_previousConstraint.Width);
 
            for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++)
            { 
                DrawingVisual lineVisual = GetLineVisual(lineIndex); 

                if (lineVisual == null) 
                {
                    LineRecord metrics = _lineMetrics[lineIndex];

                    using (line) 
                    {
                        line.Format(metrics.Offset, formatWidth, width, lineProperties, _cache.TextRunCache, _cache.TextFormatter); 
 
                        // We should be in sync with current metrics, unless background layout is pending.
                        if (!this.IsBackgroundLayoutPending) 
                        {
                            Invariant.Assert(metrics.Length == line.Length, "Line is out of sync with metrics!");
                        }
 
                        lineVisual = line.CreateVisual();
                    } 
 
                    SetLineVisual(lineIndex, lineVisual);
                } 

                lineVisual.Offset = new Vector(horizontalOffset, verticalOffset + lineIndex * _lineHeight);
                _visualChildren.Add(lineVisual);
            } 
        }
 
        // Called during Arrange, clears any cached line Visuals that intersect with highligh changes 
        // stored in the dirty range list.
        private void InvalidateDirtyVisuals() 
        {
            // Find the affected line, and reset its visual.
            // Highlights never affect measure.
            for (int i = 0; i < _dirtyList.Length; i++) 
            {
                DirtyTextRange range = _dirtyList[i]; 
 
                Invariant.Assert(range.AffectsRenderOnly); // We should never get any deltas affecting measure here.
                Invariant.Assert(range.PositionsAdded == range.PositionsRemoved); // AffectsRenderOnly never changes document size. 

                int firstLineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
                int endOffset = Math.Min(range.StartIndex + range.PositionsAdded, _host.TextContainer.SymbolCount);
                int lastLineIndex = GetLineIndexFromOffset(endOffset, LogicalDirection.Backward); 

                for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++) 
                { 
                    ClearLineVisual(lineIndex);
                } 
            }
        }

        // Transforms a Point in visual space (where (0, 0) is the upper-left 
        // corner of this FrameworkElement) to document space (where (0, 0) is
        // the upper-left corner of the document, which may be scrolled to a 
        // negative offset relative to visual space). 
        private Point TransformToDocumentSpace(Point point)
        { 
            if (_scrollData != null)
            {
                point = new Point(point.X + _scrollData.HorizontalOffset, point.Y + _scrollData.VerticalOffset);
            } 

            point.X -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width)); 
            point.Y -= this.VerticalAlignmentOffset; 

            return point; 
        }

        // Transforms a Rect in document space (where (0, 0) is
        // the upper-left corner of the document, which may be scrolled to a 
        // negative offset relative to visual space) to visual space
        // (where (0, 0) is the upper-left corner of this FrameworkElement). 
        private Rect TransformToVisualSpace(Rect rect) 
        {
            if (_scrollData != null) 
            {
                rect.X -= _scrollData.HorizontalOffset;
                rect.Y -= _scrollData.VerticalOffset;
            } 

            rect.X += GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width)); 
            rect.Y += this.VerticalAlignmentOffset; 

            return rect; 
        }

        // Helper for GetTightBoundingGeometryFromTextPositions.
        // Calculates the geometry of a single line intersected with a pair of document offsets. 
        private void GetTightBoundingGeometryFromLineIndex(int lineIndex, int unclippedStartOffset, int unclippedEndOffset, TextAlignment alignment, double endOfParaGlyphWidth, ref Geometry geometry)
        { 
            IList bounds; 

            int startOffset = Math.Max(_lineMetrics[lineIndex].Offset, unclippedStartOffset); 
            int endOffset = Math.Min(_lineMetrics[lineIndex].EndOffset, unclippedEndOffset);

            if (startOffset == endOffset) // GetRangeBounds does not accept empty runs.
            { 
                // If we have any empty intersection, the only case to handle is when
                // the empty range is exactly at the end of a line with a hard break. 
                // In that case we need to add the newline whitespace geometry. 
                if (unclippedStartOffset == _lineMetrics[lineIndex].EndOffset)
                { 
                    ITextPointer position = _host.TextContainer.CreatePointerAtOffset(unclippedStartOffset, LogicalDirection.Backward);
                    if (TextPointerBase.IsNextToPlainLineBreak(position, LogicalDirection.Backward))
                    {
                        Rect rect = new Rect(0, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight); 
                        CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
                    } 
                } 
                else
                { 
                    Invariant.Assert(endOffset == _lineMetrics[lineIndex].Offset);
                }
            }
            else 
            {
                using (TextBoxLine line = GetFormattedLine(lineIndex)) 
                { 
                    bounds = line.GetRangeBounds(startOffset, endOffset - startOffset, 0, lineIndex * _lineHeight);
                } 

                for (int i = 0; i < bounds.Count; i++)
                {
                    Rect rect = TransformToVisualSpace(bounds[i]); 
                    CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
                } 
 
                // Add the Rect representing end-of-line, if the range covers the line end
                // and the line has a hard line break. 
                if (unclippedEndOffset >= _lineMetrics[lineIndex].EndOffset)
                {
                    ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(endOffset, LogicalDirection.Backward);
 
                    if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward))
                    { 
                        double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment); 
                        Rect rect = new Rect(contentOffset + _lineMetrics[lineIndex].Width, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight);
                        rect = TransformToVisualSpace(rect); 
                        CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
                    }
                }
            } 
        }
 
        // Returns the indices of the first and last lines that intersect 
        // with the current viewport.
        private void GetVisibleLines(out int firstLineIndex, out int lastLineIndex) 
        {
            Rect viewport = this.Viewport;

            if (!viewport.IsEmpty) 
            {
                firstLineIndex = (int)(viewport.Y / _lineHeight); 
                lastLineIndex = (int)Math.Ceiling((viewport.Y + viewport.Height) / _lineHeight) - 1; 

                // There may not be enough lines to fill the viewport, clip appropriately. 
                firstLineIndex = Math.Max(0, Math.Min(firstLineIndex, _lineMetrics.Count - 1));
                lastLineIndex = Math.Max(0, Math.Min(lastLineIndex, _lineMetrics.Count - 1));
            }
            else 
            {
                // If we're not hosted by a ScrollViewer, the viewport is the whole doc. 
                firstLineIndex = 0; 
                lastLineIndex = _lineMetrics.Count - 1;
            } 
        }

        // Performs one iteration of background measure.
        // Background measure always works at the end of the current 
        // line metrics array -- invalidations to prevoiusly examined
        // content is handled by incremental layout, synchronously. 
        // 
        // Returns the full content size, omitting any unanalyzed content
        // at the document end. 
        private Size FullMeasureTick(double constraintWidth, LineProperties lineProperties)
        {
            Size desiredSize;
            TextBoxLine line = new TextBoxLine(this); 
            int lineOffset;
            bool endOfParagraph; 
 
            // Find the next position for this iteration.
 
            if (_lineMetrics.Count == 0)
            {
                desiredSize = new Size();
                lineOffset = 0; 
            }
            else 
            { 
                desiredSize = _contentSize;
                lineOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset; 
            }

            // Calculate a stop time.
            // We limit work to just a few milliseconds per iteration 
            // to avoid blocking the thread.
            DateTime stopTime; 
 
            if ((ScrollBarVisibility)((Control)_host).GetValue(ScrollViewer.VerticalScrollBarVisibilityProperty) == ScrollBarVisibility.Auto)
            { 
                // Workaround for bug 1766924.
                // When VerticalScrollBarVisiblity == Auto, there's a problem with
                // our interaction with ScrollViewer.  Disable background layout to
                // mitigate the problem until we can take a real fix in v.next. 
                //
                stopTime = DateTime.MaxValue; 
            } 
            else
            { 
                stopTime = DateTime.Now.AddMilliseconds(_maxMeasureTimeMs);
            }

            // Format lines until we hit the end of document or run out of time. 

            do 
            { 
                using (line)
                { 
                    line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);

                    // This is a loop invariant, but has negligable cost.
                    // 
                    _lineHeight = lineProperties.CalcLineAdvance(line.Height);
 
                    _lineMetrics.Add(new LineRecord(lineOffset, line)); 

                    // Desired width is always max of calculated line widths. 
                    // Desired height is sum of all line heights.
                    desiredSize.Width = Math.Max(desiredSize.Width, line.Width);
                    desiredSize.Height += _lineHeight;
 
                    lineOffset += line.Length;
                    endOfParagraph = line.EndOfParagraph; 
                } 
            }
            while (!endOfParagraph && DateTime.Now < stopTime); 

            if (!endOfParagraph)
            {
                // Ran out of time.  Defer to background layout. 
                SetFlags(true, Flags.BackgroundLayoutPending);
                this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundMeasure), null); 
            } 
            else
            { 
                // Finished the entire document.  Stop background layout.
                SetFlags(false, Flags.BackgroundLayoutPending);
            }
 
            return desiredSize;
        } 
 
        // Callback for the next background layout tick.
        private object OnBackgroundMeasure(object o) 
        {
            if (_throttleBackgroundTimer == null)
            {
                InvalidateMeasure(); 
            }
 
            return null; 
        }
 
        // Measures content invalidated due to a TextContainer change (rather than
        // a constraint change).
        //
        // Returns the full content size, omitting any unanalyzed content 
        // at the document end (due to pending background layout).
        private Size IncrementalMeasure(double constraintWidth, LineProperties lineProperties) 
        { 
            Invariant.Assert(_dirtyList != null);
            Invariant.Assert(_dirtyList.Length > 0); // We only allocate _dirtyList when it has content. 

            Size desiredSize = _contentSize;
            DirtyTextRange range = _dirtyList[0];
 
            // Background layout may be running, in which case we need to
            // "clip" the scope of this incremental edit.  We want to ignore 
            // changes that extend past the area of the document we're already 
            // tracking.
            if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset) 
            {
                Invariant.Assert(this.IsBackgroundLayoutPending);
                return desiredSize;
            } 

            // Merge the dirty list into a single superset DirtyTextRange. 
            // 

 


            int previousOffset = range.StartIndex;
            int positionsAdded = range.PositionsAdded; 
            int positionsRemoved = range.PositionsRemoved;
 
            for (int i = 1; i < _dirtyList.Length; i++) 
            {
                range = _dirtyList[i]; 

                if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
                {
                    Invariant.Assert(this.IsBackgroundLayoutPending); 
                    break;
                } 
 
                int rangeDistance = range.StartIndex - previousOffset;
                positionsAdded += rangeDistance + range.PositionsAdded; 
                positionsRemoved += rangeDistance + range.PositionsRemoved;

                previousOffset = range.StartIndex;
            } 

            range = new DirtyTextRange(_dirtyList[0].StartIndex, positionsAdded, positionsRemoved); 
 
            if (range.PositionsAdded >= range.PositionsRemoved)
            { 
                IncrementalMeasureLinesAfterInsert(constraintWidth, lineProperties, range, ref desiredSize);
            }
            else if (range.PositionsAdded < range.PositionsRemoved)
            { 
                IncrementalMeasureLinesAfterDelete(constraintWidth, lineProperties, range, ref desiredSize);
            } 
 
            return desiredSize;
        } 

        // Measures content invalidated due to a TextContainer change.
        private void IncrementalMeasureLinesAfterInsert(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
        { 
            int delta = range.PositionsAdded - range.PositionsRemoved;
            Invariant.Assert(delta >= 0); 
 
            int lineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
 
            if (delta > 0)
            {
                // Increment of the offsets of all following lines.
                // 
                for (int i = lineIndex + 1; i < _lineMetrics.Count; i++)
                { 
                    _lineMetrics[i].Offset += delta; 
                }
            } 

            TextBoxLine line = new TextBoxLine(this);
            int lineOffset;
            bool endOfParagraph = false; 

            // We need to re-format the previous line, because if someone inserted 
            // a hard break, the first directly affected line might now be shorter 
            // and mergeable with its predecessor.
            if (lineIndex > 0) // 
            {
                FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph);
            }
            else 
            {
                lineOffset = _lineMetrics[lineIndex].Offset; 
            } 

            // Format the line directly affected by the change. 
            // If endOfParagraph == true, then the line was absorbed into its
            // predessor (because its new content is thinner, or because the
            // TextWrapping property changed).
            if (!endOfParagraph) 
            {
                using (line) 
                { 
                    line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
 
                    _lineMetrics[lineIndex] = new LineRecord(lineOffset, line);

                    lineOffset += line.Length;
                    endOfParagraph = line.EndOfParagraph; 
                }
                ClearLineVisual(lineIndex); 
                lineIndex++; 
            }
 
            // Recalc the following lines not directly affected as needed.
            SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset);

            desiredSize = BruteForceCalculateDesiredSize(); 
        }
 
        // Measures content invalidated due to a TextContainer change. 
        private void IncrementalMeasureLinesAfterDelete(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
        { 
            int delta = range.PositionsAdded - range.PositionsRemoved;
            Invariant.Assert(delta < 0);

            int firstLineIndex = GetLineIndexFromOffset(range.StartIndex); 

            // Clip the scope of the affected lines to the region of the document 
            // we've already inspected.  Clipping happens when background layout 
            // has not yet completed but an incremental update happens.
            int endOffset = range.StartIndex + -delta - 1; 
            if (endOffset > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
            {
                Invariant.Assert(this.IsBackgroundLayoutPending);
                endOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset; 
                if (range.StartIndex == endOffset)
                { 
                    // Nothing left to do until background layout runs. 
                    return;
                } 
            }

            int lastLineIndex = GetLineIndexFromOffset(endOffset);
 
            // Increment the offsets of all following lines.
            // 
            for (int i = lastLineIndex + 1; i < _lineMetrics.Count; i++) 
            {
                _lineMetrics[i].Offset += delta; 
            }

            TextBoxLine line = new TextBoxLine(this);
            int lineIndex = firstLineIndex; 
            int lineOffset;
            bool endOfParagraph; 
 
            // We need to re-format the previous line, because if someone inserted
            // a hard break, the first directly affected line might now be shorter 
            // and mergeable with its predecessor.
            if (lineIndex > 0) //
            {
                FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph); 
            }
            else 
            { 
                lineOffset = _lineMetrics[lineIndex].Offset;
                endOfParagraph = false; 
            }

            //
 

 
            // Update the first affected line.  If it's completely covered, remove it entirely below. 
            if (!endOfParagraph &&
                (range.StartIndex > lineOffset || range.StartIndex + -delta < _lineMetrics[lineIndex].EndOffset)) 
            {
                // Only part of the line is covered, reformat it.
                using (line)
                { 
                    line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
 
                    _lineMetrics[lineIndex] = new LineRecord(lineOffset, line); 

                    lineOffset += line.Length; 
                    endOfParagraph = line.EndOfParagraph;
                }
                ClearLineVisual(lineIndex);
                lineIndex++; 
            }
 
            // Remove all the following lines that are completely covered. 
            //
            _lineMetrics.RemoveRange(lineIndex, lastLineIndex - lineIndex + 1); 
            RemoveLineVisualRange(lineIndex, lastLineIndex - lineIndex + 1);

            // Recalc the following lines not directly affected as needed.
            SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset); 

            desiredSize = BruteForceCalculateDesiredSize(); 
        } 

        // Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete. 
        // Formats the line preceding the first directly affected line after a TextContainer change.
        // In general this line might grow as content in the following line is absorbed.
        private void FormatFirstIncrementalLine(int lineIndex, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
            out int lineOffset, out bool endOfParagraph) 
        {
            int originalEndOffset = _lineMetrics[lineIndex].EndOffset; 
            lineOffset = _lineMetrics[lineIndex].Offset; 

            using (line) 
            {
                line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);

                _lineMetrics[lineIndex] = new LineRecord(lineOffset, line); 

                lineOffset += line.Length; 
                endOfParagraph = line.EndOfParagraph; 
            }
 
            // Don't clear the cached Visual unless something changed.
            if (originalEndOffset != _lineMetrics[lineIndex].EndOffset)
            {
                ClearLineVisual(lineIndex); 
            }
        } 
 
        // Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete.
        // Formats line until we hit a synchronization point, a position where we know 
        // following lines could not be affected by the change.
        private void SyncLineMetrics(DirtyTextRange range, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
            bool endOfParagraph, int lineIndex, int lineOffset)
        { 
            bool offsetSyncOk = (range.PositionsAdded == 0 || range.PositionsRemoved == 0);
            int lastCoveredCharOffset = range.StartIndex + Math.Max(range.PositionsAdded, range.PositionsRemoved); 
 
            // Keep updating lines until we find a synchronized position.
            while (!endOfParagraph && 
                   (lineIndex == _lineMetrics.Count ||
                    !offsetSyncOk ||
                    lineOffset != _lineMetrics[lineIndex].Offset))
            { 
                if (lineIndex < _lineMetrics.Count &&
                    lineOffset >= _lineMetrics[lineIndex].EndOffset) 
                { 
                    // If the current line offset starts past the current line metric offset,
                    // remove the metric.  This happens when the previous line 
                    // frees up enough space to completely consume the following line.
                    // We can't simply replace the record without potentially missing our
                    // sync position.
                    _lineMetrics.RemoveAt(lineIndex); // 
                    RemoveLineVisualRange(lineIndex, 1);
                } 
                else 
                {
                    using (line) 
                    {
                        line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);

                        LineRecord record = new LineRecord(lineOffset, line); 

                        if (lineIndex == _lineMetrics.Count || 
                            lineOffset + line.Length <= _lineMetrics[lineIndex].Offset) 
                        {
                            // The new line preceeds the old line, insert a new record. 

                            //
                            _lineMetrics.Insert(lineIndex, record);
                            AddLineVisualPlaceholder(lineIndex); 
                        }
                        else 
                        { 
                            // We expect to be colliding with the old line directly.
                            // If we extend past it, we're in danger of needlessly 
                            // re-formatting the entire doc (ie, we miss the real
                            // sync position and don't stop until EndOfParagraph).
                            Invariant.Assert(lineOffset < _lineMetrics[lineIndex].EndOffset);
 
                            _lineMetrics[lineIndex] = record;
                            ClearLineVisual(lineIndex); 
 
                            // If this line ends past the invalidated region, and it
                            // has a hard line break, it's safe to synchronize on the next 
                            // line metric with a matching start offset.
                            offsetSyncOk |= lastCoveredCharOffset <= record.EndOffset && line.HasLineBreak;
                        }
 
                        lineIndex++;
                        lineOffset += line.Length; 
                        endOfParagraph = line.EndOfParagraph; 
                    }
                } 
            }

            // Remove any trailing lines that got absorbed into the new last line.
            if (endOfParagraph && lineIndex < _lineMetrics.Count) 
            {
                int count = _lineMetrics.Count - lineIndex; 
                _lineMetrics.RemoveRange(lineIndex, count); 
                RemoveLineVisualRange(lineIndex, count);
            } 
        }

        // Calculates the bounding box of the content.
        private Size BruteForceCalculateDesiredSize() 
        {
            Size desiredSize = new Size(); 
 
            //
            for (int i = 0; i < _lineMetrics.Count; i++) 
            {
                desiredSize.Width = Math.Max(desiredSize.Width, _lineMetrics[i].Width);
            }
            desiredSize.Height = _lineMetrics.Count * _lineHeight; 

            return desiredSize; 
        } 

        // Updates the array of cached Visuals matching lines in the viewport. 
        // Called on arrange as the viewport changes.
        private void SetViewportLines(int firstLineIndex, int lastLineIndex)
        {
            List oldLineVisuals = _viewportLineVisuals; 
            int oldLineVisualsIndex = _viewportLineVisualsIndex;
 
            // Assume we'll clear the cache. 

            _viewportLineVisuals = null; 
            _viewportLineVisualsIndex = -1;

            int count = lastLineIndex - firstLineIndex + 1;
 
            // Don't bother caching Visuals for single-line TextBoxes.
            // In this common case memory is important and the single line will 
            // always be the one invalidated on an edit. 
            if (count <= 1)
            { 
                return;
            }

            // Re-init the cache to match the new viewport size. 
            // Even if we don't have any Visuals to copy over from
            // the previous cache, it's useful to pre-allocate space 
            // in the cache that will be filled incrementally during 
            // Arrange.
 
            _viewportLineVisuals = new List(count);
            _viewportLineVisuals.AddRange(new DrawingVisual[count]); //
            _viewportLineVisualsIndex = firstLineIndex;
 
            if (oldLineVisuals == null)
            { 
                return; 
            }
 
            // Copy over the intersection of the old viewport Visuals cache
            // with the new one.

            // It would be convenient if the code below assumed that if 
            // viewport size has changed, we never make it this far (the
            // old viewport visuals should have been thrown away, since 
            // there's no way now to map to the new constraint). 
            //
            // However, because of rounding error, we can end up in the situation 
            // where the indices/lengths between the two arrays vary, after
            // an arrange invalidation.

            int oldLastLineIndex = oldLineVisualsIndex + oldLineVisuals.Count - 1; 

            if (oldLineVisualsIndex <= lastLineIndex && 
                oldLastLineIndex >= firstLineIndex) 
            {
                int lineIndex = Math.Max(oldLineVisualsIndex, firstLineIndex); 
                int lineCount = Math.Min(oldLastLineIndex, firstLineIndex + count - 1) - lineIndex + 1;

                for (int i = 0; i < lineCount; i++)
                { 
                    _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex + i] = oldLineVisuals[lineIndex - oldLineVisualsIndex + i];
                } 
            } 
        }
 
        // Retrives the cached line Visual matching a line index in the
        // current viewport.  Will return null if no value is cached.
        private DrawingVisual GetLineVisual(int lineIndex)
        { 
            DrawingVisual lineVisual = null;
 
            if (_viewportLineVisuals != null) 
            {
                lineVisual = _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex]; 
            }

            return lineVisual;
        } 

        // Adds a Visual to the line Visuals cache. 
        private void SetLineVisual(int lineIndex, DrawingVisual lineVisual) 
        {
            if (_viewportLineVisuals != null) 
            {
                _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = lineVisual;
            }
        } 

        // Adds an empty entry to the line Visuals cache. 
        private void AddLineVisualPlaceholder(int lineIndex) 
        {
            if (_viewportLineVisuals != null) 
            {
                // Clip to visible region.
                if (lineIndex >= _viewportLineVisualsIndex &&
                    lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count) 
                {
                    _viewportLineVisuals.Insert(lineIndex - _viewportLineVisualsIndex, null); 
                } 
            }
        } 

        // Invalidates a cached line Visual.
        private void ClearLineVisual(int lineIndex)
        { 
            if (_viewportLineVisuals != null)
            { 
                // Clip to visible region. 
                if (lineIndex >= _viewportLineVisualsIndex &&
                    lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count) 
                {
                    _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = null;
                }
            } 
        }
 
        // Removes a range of Visuals from the line Visual cache. 
        private void RemoveLineVisualRange(int lineIndex, int count)
        { 
            if (_viewportLineVisuals != null)
            {
                // Clip to visible region.
                if (lineIndex < _viewportLineVisualsIndex) 
                {
                    count -= _viewportLineVisualsIndex - lineIndex; 
                    count = Math.Max(0, count); 
                    lineIndex = _viewportLineVisualsIndex;
                } 
                if (lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
                {
                    count = Math.Min(count, _viewportLineVisuals.Count - (lineIndex - _viewportLineVisualsIndex));
                    _viewportLineVisuals.RemoveRange(lineIndex - _viewportLineVisualsIndex, count); 
                }
            } 
        } 

        // Callback for the background layout throttle timer. 
        // Resumes backgound layout.
        private void OnThrottleBackgroundTimeout(object sender, EventArgs e)
        {
            _throttleBackgroundTimer.Stop(); 
            _throttleBackgroundTimer = null;
 
            if (this.IsBackgroundLayoutPending) 
            {
                OnBackgroundMeasure(null); 
            }
        }

        // Returns the x-axis offset of content on a line, based on current 
        // text alignment.
        private double GetContentOffset(double lineWidth, TextAlignment aligment) 
        { 
            double contentOffset;
            double width = GetWrappingWidth(this.RenderSize.Width); 

            switch (aligment)
            {
                case TextAlignment.Right: 
                    contentOffset = width - lineWidth;
                    break; 
 
                case TextAlignment.Center:
                    contentOffset = (width - lineWidth) / 2; 
                    break;

                default:
                    // Default is Left alignment, in this case offset is 0. 
                    contentOffset = 0.0;
                    break; 
            } 

            return contentOffset; 
        }

        // Converts a HorizontalAlignment enum to a TextAlignment enum.
        private TextAlignment HorizontalAlignmentToTextAlignment(HorizontalAlignment horizontalAlignment) 
        {
            TextAlignment textAlignment; 
 
            switch (horizontalAlignment)
            { 
                case HorizontalAlignment.Left:
                default:
                    textAlignment = TextAlignment.Left;
                    break; 

                case HorizontalAlignment.Right: 
                    textAlignment = TextAlignment.Right; 
                    break;
 
                case HorizontalAlignment.Center:
                    textAlignment = TextAlignment.Center;
                    break;
 
                case HorizontalAlignment.Stretch:
                    textAlignment = TextAlignment.Justify; 
                    break; 
            }
 
            return textAlignment;
        }

        ///  
        /// 
        ///  
        private bool Contains(ITextPointer position) 
        {
            Invariant.Assert(this.IsLayoutValid); 

            return position.TextContainer == _host.TextContainer &&
                   _lineMetrics != null &&
                   _lineMetrics[_lineMetrics.Count - 1].EndOffset >= position.Offset; 
        }
 
        // Converts a render size width into a wrapping width for lines. 
        private double GetWrappingWidth(double width)
        { 
            if (width < _contentSize.Width)
            {
                width = _contentSize.Width;
            } 

            if (width > _previousConstraint.Width) 
            { 
                width = _previousConstraint.Width;
            } 

            // Make sure that TextFormatter limitations are not exceeded.
            //
            TextDpi.EnsureValidLineWidth(ref width); 

            return width; 
        } 

        // When the content size exceeds the viewport size, TextLine will align 
        // its content such that the "extra" is clipped in inappropriate ways.
        //
        // TextAlignment.Center: line offset = -(contentWidth - viewportWidth) / 2
        // TextAlignment.Right:  line offset = -(contentWidth - viewportWidth) 
        //
        // This method returns a value that exactly cancels out the undesired 
        // offset, which is used to adjust the content origin to local zero. 
        private double GetTextAlignmentCorrection(TextAlignment textAlignment, double width)
        { 
            double correction = 0;

            if (textAlignment != TextAlignment.Left &&
                _contentSize.Width > width) 
            {
                correction = -GetContentOffset(_contentSize.Width, textAlignment); 
            } 

            return correction; 
        }

        #endregion Private Methods
 
        //------------------------------------------------------
        // 
        //  Private Properties 
        //
        //----------------------------------------------------- 

        #region Private Properties

        // True when measure and arrange are valid. 
        private bool IsLayoutValid
        { 
            get 
            {
                return this.IsMeasureValid && this.IsArrangeValid; 
            }
        }

        // Current visible region in document space. 
        private Rect Viewport
        { 
            get 
            {
                return _scrollData == null ? Rect.Empty : 
                                             new Rect(_scrollData.HorizontalOffset, _scrollData.VerticalOffset, _scrollData.ViewportWidth, _scrollData.ViewportHeight);
            }
        }
 
        // True when background layout has not completed.
        private bool IsBackgroundLayoutPending 
        { 
            get
            { 
                return CheckFlags(Flags.BackgroundLayoutPending);
            }
        }
 
        // Offset in pixels of the first line due to VerticalContentAlignment.
        private double VerticalAlignmentOffset 
        { 
            get
            { 
                double offset;

                switch (((Control)_host).VerticalContentAlignment)
                { 
                    case VerticalAlignment.Top:
                    case VerticalAlignment.Stretch: 
                    default: 
                        offset = 0;
                        break; 

                    case VerticalAlignment.Center:
                        offset = this.VerticalPadding / 2;
                        break; 

                    case VerticalAlignment.Bottom: 
                        offset = this.VerticalPadding; 
                        break;
                } 

                return offset;
            }
        } 

        // Calculated TextAlignment property value. 
        // Takes into account collisions between TextAlignment and HorizontalContentAlignment properties. 
        //
        // TextAlignment always wins unless it has no local value and HorizontalContentAlignment does. 
        //
        // In order of precedence:
        // 1. Local value on TextAlignment.
        // 2. Local value on HorizontalContentAlignment. 
        // 3. TextAlignment.
        private TextAlignment CalculatedTextAlignment 
        { 
            get
            { 
                TextAlignment alignment;
                Control host = (Control)_host;

                object o = host.ReadLocalValue(TextBox.TextAlignmentProperty); 

                if (o != DependencyProperty.UnsetValue) 
                { 
                    // Re-evaluate o to resolve any databinding expressions.
                    o = host.GetValue(TextBox.TextAlignmentProperty); 
                    alignment = (TextAlignment)o;
                }
                else
                { 
                    o = host.ReadLocalValue(TextBox.HorizontalContentAlignmentProperty);
 
                    if (o != DependencyProperty.UnsetValue) 
                    {
                        // Re-evaluate o to resolve any databinding expressions. 
                        o = host.GetValue(TextBox.HorizontalContentAlignmentProperty);
                        alignment = HorizontalAlignmentToTextAlignment((HorizontalAlignment)o);
                    }
                    else 
                    {
                        alignment = (TextAlignment)host.GetValue(TextBox.TextAlignmentProperty); 
                    } 
                }
 
                return alignment;
            }
        }
 
        // The delta between the current viewport height and the content height.
        // Returns zero when content height is greater than viewport height. 
        private double VerticalPadding 
        {
            get 
            {
                double padding;
                Rect viewport = this.Viewport;
 
                if (viewport.IsEmpty)
                { 
                    padding = 0; 
                }
                else 
                {
                    padding = Math.Max(0, viewport.Height - _contentSize.Height);
                }
 
                return padding;
            } 
        } 

        #endregion Private Properties 

        //-----------------------------------------------------
        //
        //  Private Types 
        //
        //----------------------------------------------------- 
 
        #region Private Types
 
        // Booleans for the _flags field.
        [System.Flags]
        private enum Flags
        { 
            // When true, TextContainer listeners are hooked up.
            TextContainerListenersInitialized = 0x1, 
 
            // When true, background layout is still running.
            BackgroundLayoutPending = 0x2, 
        }

        // Caches state used across a measure/arrange calculation.
        // In addition to performance benefits, this ensures a consistent 
        // view of property values across measure/arrange.
        private class TextCache 
        { 
            internal TextCache(TextBoxView owner)
            { 
                _lineProperties = owner.GetLineProperties();
                _textRunCache = new TextRunCache();
            }
 
            internal LineProperties LineProperties
            { 
                get { return _lineProperties; } 
            }
 
            internal TextRunCache TextRunCache
            {
                get { return _textRunCache; }
            } 

            // Cached TextFormatter for this thread. 
            internal TextFormatter TextFormatter 
            {
                get 
                {
                    if (_textFormatter == null)
                    {
                        _textFormatter = TextFormatter.FromCurrentDispatcher(); 
                    }
                    return _textFormatter; 
                } 
            }
 
            private readonly LineProperties _lineProperties;
            private readonly TextRunCache _textRunCache;
            private TextFormatter _textFormatter;
        } 

        // Line metrics array entry. 
        private class LineRecord 
        {
            internal LineRecord(int offset, TextBoxLine line) 
            {
                _offset = offset;
                _length = line.Length;
                _contentLength = line.ContentLength; 
                _width = line.Width;
            } 
 
            internal int Offset
            { 
                get { return _offset; }
                set { _offset = value; }
            }
 
            internal int Length { get { return _length; } }
 
            internal int ContentLength { get { return _contentLength; } } 

            internal double Width { get { return _width; } } 

            internal int EndOffset { get { return _offset + _length; } }

            private int _offset; 
            private readonly int _length; //
            private readonly int _contentLength; 
            private readonly double _width; 
        }
 
        #endregion Private Types

        //------------------------------------------------------
        // 
        //  Private Fields
        // 
        //----------------------------------------------------- 

        #region Private Fields 

        // TextBox that owns this TextBoxView.
        private readonly ITextBoxViewHost _host;
 
        // Bounding box of the content, up to the point reached by background layout.
        private Size _contentSize; 
 
        // The most recent constraint passed to MeasureOverride.
        // this.PreviousConstraint cannot be used because it can be affected 
        // by Margin, Width/Min/MaxWidth propreties and ClipToBounds.
        private Size _previousConstraint;

        // Caches state used across a measure/arrange calculation. 
        // In addition to performance benefits, this ensures a consistent
        // view of property values across measure/arrange. 
        private TextCache _cache; 

        // Height of any line, in pixels. 
        private double _lineHeight;

        // Visuals tracked by GetVisualChild/VisualChilrenCount overrides.
        private VisualCollection _visualChildren; 

        // Array of cached line metrics. 
        private List _lineMetrics; 

        // Array of cached line Visuals for the current viewport. 
        private List _viewportLineVisuals;

        // Index of first line in the _viewportLineVisuals array.
        private int _viewportLineVisualsIndex; 

        // IScrollInfo state/code. 
        private ScrollData _scrollData; 

        // List of invalidated regions created by TextContainer changes. 
        private DtrList _dirtyList;

        // Timer used to disable background layout during user interaction.
        private DispatcherTimer _throttleBackgroundTimer; 

        // Boolean flags, set with Flags enum. 
        private Flags _flags; 

        // Updated event listeners. 
        private EventHandler UpdatedEvent;

        // Max time slice to run FullMeasureTick.
        private const uint _maxMeasureTimeMs = 200; 

        // Number of seconds to disable background layout after receiving 
        // user input. 
        private const int _throttleBackgroundSeconds = 2;
 
        #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