Line.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 / PtsHost / Line.cs / 2 / Line.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// File: ContainerParagraph.cs 
//
// Description: Text line formatter. 
// 
// History:
//  05/05/2003 : [....] - moving from Avalon branch. 
//
//---------------------------------------------------------------------------
#pragma warning disable 1634, 1691  // avoid generating warnings about unknown
                                    // message numbers and unknown pragmas for PRESharp contol 

using System; 
using System.Diagnostics; 
using System.Collections;
using System.Collections.Generic; 
using System.Globalization;
using System.Security;                  // SecurityCritical
using System.Windows;
using System.Windows.Controls; 
using System.Windows.Documents;
using System.Windows.Media; 
using System.Windows.Media.TextFormatting; 
using MS.Internal.Text;
using MS.Internal.Documents; 

using MS.Internal.PtsHost.UnsafeNativeMethods;

namespace MS.Internal.PtsHost 
{
    ///  
    /// Text line formatter. 
    /// 
    ///  
    /// NOTE: All DCPs used during line formatting are related to cpPara.
    /// To get abosolute CP, add cpPara to a dcp value.
    /// 
    internal sealed class Line : LineBase 
    {
        //------------------------------------------------------------------- 
        // 
        //  Constructors
        // 
        //-------------------------------------------------------------------

        #region Constructors
 
        /// 
        /// Constructor. 
        ///  
        /// 
        /// TextFormatter host 
        /// 
        /// 
        /// Owner of the line
        ///  
        /// 
        /// CP of the beginning of the text paragraph 
        ///  
        internal Line(TextFormatterHost host, TextParaClient paraClient, int cpPara) : base(paraClient)
        { 
            _host = host;
            _cpPara = cpPara;
            _textAlignment = (TextAlignment)TextParagraph.Element.GetValue(Block.TextAlignmentProperty);
            _indent = 0.0; 
        }
 
        ///  
        /// Free all resources associated with the line. Prepare it for reuse.
        ///  
        public override void Dispose()
        {
            Debug.Assert(_line != null, "Line has been already disposed.");
            try 
            {
                if (_line != null) 
                { 
                    _line.Dispose();
                } 
            }
            finally
            {
                _line = null; 
                _runs = null;
                _hasFigures = false; 
                _hasFloaters = false; 
                base.Dispose();
            } 
        }

        #endregion Constructors
 
        // ------------------------------------------------------------------
        // 
        //  PTS Callbacks 
        //
        // ----------------------------------------------------------------- 

        #region PTS Callbacks

        ///  
        /// GetDvrSuppressibleBottomSpace
        ///  
        ///  
        /// OUT: empty space suppressible at the bottom
        ///  
        internal void GetDvrSuppressibleBottomSpace(
            out int dvrSuppressible)
        {
            dvrSuppressible = Math.Max(0, TextDpi.ToTextDpi(_line.OverhangAfter)); 
        }
 
        ///  
        /// GetDurFigureAnchor
        ///  
        /// 
        /// IN: FigureParagraph for which we require anchor dur
        /// 
        ///  
        /// IN: current direction
        ///  
        ///  
        /// OUT: distance from the beginning of the line to the anchor
        ///  
        internal void GetDurFigureAnchor(
            FigureParagraph paraFigure,
            uint fswdir,
            out int dur) 
        {
            int cpFigure = TextContainerHelper.GetCPFromElement(_paraClient.Paragraph.StructuralCache.TextContainer, paraFigure.Element, ElementEdge.BeforeStart); 
            int dcpFigure = cpFigure - _cpPara; 
            double distance = _line.GetDistanceFromCharacterHit(new CharacterHit(dcpFigure, 0));
            dur = TextDpi.ToTextDpi(distance); 
        }

        #endregion PTS Callbacks
 
        // ------------------------------------------------------------------
        // 
        //  TextSource Implementation 
        //
        // ------------------------------------------------------------------ 

        #region TextSource Implementation

        ///  
        /// Get a text run at specified text source position and return it.
        ///  
        ///  
        /// dcp of position relative to start of line
        ///  
        internal override TextRun GetTextRun(int dcp)
        {
            TextRun run = null;
            ITextContainer textContainer = _paraClient.Paragraph.StructuralCache.TextContainer; 
            StaticTextPointer position = textContainer.CreateStaticPointerAtOffset(_cpPara + dcp);
 
            switch (position.GetPointerContext(LogicalDirection.Forward)) 
            {
                case TextPointerContext.Text: 
                    run = HandleText(position);
                    break;

                case TextPointerContext.ElementStart: 
                    run = HandleElementStartEdge(position);
                    break; 
 
                case TextPointerContext.ElementEnd:
                    run = HandleElementEndEdge(position); 
                    break;

                case TextPointerContext.EmbeddedElement:
                    run = HandleEmbeddedObject(dcp, position); 
                    break;
 
                case TextPointerContext.None: 
                    run = new ParagraphBreakRun(_syntheticCharacterLength, PTS.FSFLRES.fsflrEndOfParagraph);
                    break; 
            }
            Invariant.Assert(run != null, "TextRun has not been created.");
            Invariant.Assert(run.Length > 0, "TextRun has to have positive length.");
            return run; 
        }
 
        ///  
        /// Get text immediately before specified text source position. Return CharacterBufferRange
        /// containing this text. 
        /// 
        /// 
        /// dcp of position relative to start of line
        ///  
        internal override TextSpan GetPrecedingText(int dcp)
        { 
            // Parameter validation 
            Invariant.Assert(dcp >= 0);
 
            int nonTextLength = 0;
            CharacterBufferRange precedingText = CharacterBufferRange.Empty;
            CultureInfo culture = null;
 
            if (dcp > 0)
            { 
                // Create TextPointer at dcp, and pointer at paragraph start to compare 
                ITextPointer startPosition = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara, LogicalDirection.Forward);
                ITextPointer position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara + dcp, LogicalDirection.Forward); 

                // Move backward until we find a position at the end of a text run, or reach start of TextContainer
                while (position.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text &&
                       position.CompareTo(startPosition) != 0) 
                {
                    position.MoveByOffset(-1); 
                    nonTextLength++; 
                }
 

                // Return text in run. If it is at start of TextContainer this will return an empty string
                string precedingTextString = position.GetTextInRun(LogicalDirection.Backward);
                precedingText = new CharacterBufferRange(precedingTextString, 0, precedingTextString.Length); 

                StaticTextPointer pointer = position.CreateStaticPointer(); 
                DependencyObject element = (pointer.Parent != null) ? pointer.Parent : _paraClient.Paragraph.Element; 
                culture = DynamicPropertyReader.GetCultureInfo(element);
            } 

            return new TextSpan(
                nonTextLength + precedingText.Length,
                new CultureSpecificCharacterBufferRange(culture, precedingText) 
                );
        } 
 
        /// 
        /// Get Text effect index from text source character index. Return int value of Text effect index. 
        /// 
        /// 
        /// dcp of CharacterHit relative to start of line
        ///  
        internal override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int dcp)
        { 
            return _cpPara + dcp; 
        }
 
        #endregion TextSource Implementation

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

        #region Internal Methods 

        /// 
        /// Create and format text line.
        ///  
        /// 
        /// Line formatting context. 
        ///  
        /// 
        /// Character position where the line starts. 
        /// 
        /// 
        /// Requested width of the line.
        ///  
        /// 
        /// Requested width of track. 
        ///  
        /// 
        /// Line properties. 
        /// 
        /// 
        /// Line break object.
        ///  
        internal void Format(FormattingContext ctx, int dcp, int width, int trackWidth, TextParagraphProperties lineProps, TextLineBreak textLineBreak)
        { 
            // Set formatting context 
            _formattingContext = ctx;
            _dcp = dcp; 
            _host.Context = this;
            _wrappingWidth = TextDpi.FromTextDpi(width);
            _trackWidth = TextDpi.FromTextDpi(trackWidth);
            _mirror = (lineProps.FlowDirection == FlowDirection.RightToLeft); 
            _indent = lineProps.Indent;
 
            try 
            {
                // Create line object 
                if(ctx.LineFormatLengthTarget == -1)
                {
                    _line = _host.TextFormatter.FormatLine(_host, dcp, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache);
                } 
                else
                { 
                    _line = _host.TextFormatter.RecreateLine(_host, dcp, ctx.LineFormatLengthTarget, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); 
                }
                _runs = _line.GetTextRunSpans(); 
                Invariant.Assert(_runs != null, "Cannot retrieve runs collection.");

                // Submit inline objects (only in measure mode)
                if (_formattingContext.MeasureMode) 
                {
                    List inlineObjects = new List(1); 
                    int dcpRun = _dcp; 
                    // Enumerate through all runs in the current line and retrieve
                    // all inline objects. 
                    // If there are any figures / floaters, store this information for later use.
                    foreach (TextSpan textSpan in _runs)
                    {
                        TextRun run = (TextRun)textSpan.Value; 
                        if (run is InlineObjectRun)
                        { 
                            inlineObjects.Add(new InlineObject(dcpRun, ((InlineObjectRun)run).UIElementIsland, (TextParagraph)_paraClient.Paragraph)); 
                        }
                        else if (run is FloatingRun) 
                        {
                            if (((FloatingRun)run).Figure)
                            {
                                _hasFigures = true; 
                            }
                            else 
                            { 
                                _hasFloaters = true;
                            } 
                        }

                        // Do not use TextRun.Length, because it gives total length of the run.
                        // So, if the run is broken between lines, it gives incorrect value. 
                        // Use length of the TextSpan instead, which gives the correct length here.
                        dcpRun += textSpan.Length; 
                    } 

                    // Submit inline objects to the paragraph cache 
                    if (inlineObjects.Count == 0)
                    {
                        inlineObjects = null;
                    } 
                    TextParagraph.SubmitInlineObjects(dcp, dcp + ActualLength, inlineObjects);
                } 
            } 
            finally
            { 
                // Clear formatting context
                _host.Context = null;
            }
        } 

        ///  
        /// Measure child UIElement. 
        /// 
        ///  
        /// Element whose size we are measuring
        /// 
        /// 
        /// Size of the child UIElement 
        /// 
        internal Size MeasureChild(InlineObjectRun inlineObject) 
        { 
            // Measure inline object only during measure pass. Otherwise
            // use cached data. 
            Size desiredSize;
            if (_formattingContext.MeasureMode)
            {
                Debug.Assert(!DoubleUtil.IsNaN(_trackWidth), "Track width must be set for measure pass."); 

                // Always measure at infinity for bottomless, consistent constraint. 
                double pageHeight = _paraClient.Paragraph.StructuralCache.CurrentFormatContext.DocumentPageSize.Height; 
                if (!_paraClient.Paragraph.StructuralCache.CurrentFormatContext.FinitePage)
                { 
                    pageHeight = Double.PositiveInfinity;
                }

                desiredSize = inlineObject.UIElementIsland.DoLayout(new Size(_trackWidth, pageHeight), true, true); 
            }
            else 
            { 
                desiredSize = inlineObject.UIElementIsland.Root.DesiredSize;
            } 
            return desiredSize;
        }

        ///  
        /// Create and return visual node for the line.
        ///  
        internal ContainerVisual CreateVisual() 
        {
            LineVisual visual = new LineVisual(); 

            // Set up the text source for rendering callback
            _host.Context = this;
 
            try
            { 
                // Handle text trimming. 
                IList> runs = _runs;
                System.Windows.Media.TextFormatting.TextLine line = _line; 
                if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None)
                {
                    line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties));
                    Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); 
                    runs = line.GetTextRunSpans();
                } 
 
                // Add visuals for all embedded elements.
                if (HasInlineObjects()) 
                {
                    VisualCollection visualChildren = visual.Children;

                    // Get flow direction of the paragraph element. 
                    DependencyObject paragraphElement = _paraClient.Paragraph.Element;
                    FlowDirection paragraphFlowDirection = (FlowDirection)paragraphElement.GetValue(FrameworkElement.FlowDirectionProperty); 
 
                    // Before text rendering, add all visuals for inline objects.
                    int dcpRun = _dcp; 
                    // Enumerate through all runs in the current line and connect visuals for all inline objects.
                    foreach (TextSpan textSpan in runs)
                    {
                        TextRun run = (TextRun)textSpan.Value; 
                        if (run is InlineObjectRun)
                        { 
                            InlineObjectRun inlineObject = (InlineObjectRun)run; 
                            FlowDirection flowDirection;
                            Rect rect = GetBoundsFromPosition(dcpRun, run.Length, out flowDirection); 
                            Debug.Assert(DoubleUtil.GreaterThanOrClose(rect.Width, 0), "Negative inline object's width.");

                            // Disconnect visual from its old parent, if necessary.
                            Visual currentParent = VisualTreeHelper.GetParent(inlineObject.UIElementIsland) as Visual; 
                            if (currentParent != null)
                            { 
                                ContainerVisual parent = currentParent as ContainerVisual; 
                                Invariant.Assert(parent != null, "Parent should always derives from ContainerVisual.");
                                parent.Children.Remove(inlineObject.UIElementIsland); 
                            }

                            if (!line.HasCollapsed || ((rect.Left + inlineObject.UIElementIsland.Root.DesiredSize.Width) < line.Width))
                            { 
                                // Check parent's FlowDirection to determine if mirroring is needed
                                if (inlineObject.UIElementIsland.Root is FrameworkElement) 
                                { 
                                    DependencyObject parent = ((FrameworkElement)inlineObject.UIElementIsland.Root).Parent;
                                    FlowDirection parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty); 
                                    PtsHelper.UpdateMirroringTransform(paragraphFlowDirection, parentFlowDirection, inlineObject.UIElementIsland, rect.Width);
                                }

                                visualChildren.Add(inlineObject.UIElementIsland); 
                                inlineObject.UIElementIsland.Offset = new Vector(rect.Left, rect.Top);
                            } 
                        } 

                        // Do not use TextRun.Length, because it gives total length of the run. 
                        // So, if the run is broken between lines, it gives incorrect value.
                        // Use length of the TextSpan instead, which gives the correct length here.
                        dcpRun += textSpan.Length;
                    } 
                }
 
                // Calculate shift in line offset to render trailing spaces or avoid clipping text 
                double delta = TextDpi.FromTextDpi(CalculateUOffsetShift());
                DrawingContext ctx = visual.Open(); 
                line.Draw(ctx, new Point(delta, 0), (_mirror ? InvertAxes.Horizontal : InvertAxes.None));
                ctx.Close();

                visual.WidthIncludingTrailingWhitespace = line.WidthIncludingTrailingWhitespace - _indent; 
            }
            finally 
            { 
                _host.Context = null; // clear the context
            } 

            return visual;
        }
 
        /// 
        /// Return bounds of an object/character at specified text position. 
        ///  
        /// 
        /// Position of the object/character 
        /// 
        /// 
        /// Flow direction of the object/character
        ///  
        internal Rect GetBoundsFromTextPosition(int textPosition, out FlowDirection flowDirection)
        { 
            return GetBoundsFromPosition(textPosition, 1, out flowDirection); 
        }
 
        /// 
        /// Returns an ArrayList of rectangles (Rect) that form the bounds of the region specified between
        /// the start and end points
        ///  
        /// 
        /// int offset indicating the starting point of the region for which bounds are required 
        ///  
        /// Length in characters of the region for which bounds are required
        ///  
        /// 
        /// Offset of line in x direction, to be added to line bounds to get actual rectangle for line
        /// 
        ///  
        /// Offset of line in y direction, to be added to line bounds to get actual rectangle for line
        ///  
        ///  
        /// This function calls GetTextBounds for the line, and then checks if there are text run bounds. If they exist,
        /// it uses those as the bounding rectangles. If not, it returns the rectangle for the first (and only) element 
        /// of the text bounds.
        /// 
        internal List GetRangeBounds(int cp, int cch, double xOffset, double yOffset)
        { 
            List rectangles = new List();
 
            // Calculate shift in line offset to render trailing spaces or avoid clipping text 
            double delta = TextDpi.FromTextDpi(CalculateUOffsetShift());
            double newUOffset = xOffset + delta; 
            IList textBounds;
            if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None)
            {
                // Verify that offset shift is 0 for this case. We should never shift offsets when ellipses are 
                // rendered.
                Invariant.Assert(DoubleUtil.AreClose(delta, 0)); 
                System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); 
                Invariant.Assert(line.HasCollapsed, "Line has not been collapsed");
                textBounds = line.GetTextBounds(cp, cch); 
            }
            else
            {
                textBounds = _line.GetTextBounds(cp, cch); 
            }
            Invariant.Assert(textBounds.Count > 0); 
 
            for (int boundIndex = 0; boundIndex < textBounds.Count; boundIndex++)
            { 
                Rect rect = textBounds[boundIndex].Rectangle;
                rect.X += newUOffset;
                rect.Y += yOffset;
                rectangles.Add(rect); 
            }
            return rectangles; 
        } 

        ///  
        /// Passes line break object out from underlying line object
        /// 
        internal TextLineBreak GetTextLineBreak()
        { 
            if(_line == null)
            { 
                return null; 
            }
 
            return _line.GetTextLineBreak();
        }

 
        /// 
        /// Return text position index from the given distance. 
        ///  
        /// 
        /// Distance relative to the beginning of the line. 
        /// 
        internal CharacterHit GetTextPositionFromDistance(int urDistance)
        {
            // Calculate shift in line offset to render trailing spaces or avoid clipping text 
            int delta = CalculateUOffsetShift();
            if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) 
            { 
                System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties));
                Invariant.Assert(delta == 0); 
                Invariant.Assert(line.HasCollapsed, "Line has not been collapsed");
                return line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance));
            }
            return _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance - delta)); 
        }
 
        ///  
        /// Hit tests to the correct ContentElement within the line.
        ///  
        /// 
        /// Offset within the line.
        /// 
        ///  
        /// ContentElement which has been hit.
        ///  
        internal IInputElement InputHitTest(int urOffset) 
        {
            DependencyObject element = null; 
            TextPointer position;
            TextPointerContext type = TextPointerContext.None;
            CharacterHit charIndex;
            int cp, delta; 

            // Calculate shift in line offset to render trailing spaces or avoid clipping text 
            delta = CalculateUOffsetShift(); 
            if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None)
            { 
                // We should not shift offset in this case
                Invariant.Assert(delta == 0);
                System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties));
                Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); 

                // Get TextPointer from specified distance. 
                charIndex = line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset)); 
            }
            else 
            {
                // Get TextPointer from specified distance.
                charIndex = _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset - delta));
            } 

            cp = _paraClient.Paragraph.ParagraphStartCharacterPosition + charIndex.FirstCharacterIndex + charIndex.TrailingLength; 
            position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, cp, LogicalDirection.Forward) as TextPointer; 

            if (position != null) 
            {
                // If start of character, look forward. Otherwise, look backward.
                type = position.GetPointerContext((charIndex.TrailingLength == 0) ? LogicalDirection.Forward : LogicalDirection.Backward);
 
                // Get element only for Text & Start/End element, for all other positions
                // return null (it means that the line owner has been hit). 
                if (type == TextPointerContext.Text || type == TextPointerContext.ElementEnd) 
                {
                    element = position.Parent; 
                }
                else if (type == TextPointerContext.ElementStart)
                {
                    element = position.GetAdjacentElementFromOuterPosition(LogicalDirection.Forward); 
                }
            } 
            return element as IInputElement; 
        }
 
        /// 
        /// Get length of content hidden by ellipses. Return integer length of this content.
        /// 
        internal int GetEllipsesLength() 
        {
            // There are no ellipses, if: 
            // * there is no overflow in the line 
            // * text trimming is turned off
            if (!_line.HasOverflowed) 
            {
                return 0;
            }
            if (TextParagraph.Properties.TextTrimming == TextTrimming.None) 
            {
                return 0; 
            } 

            // Create collapsed text line to get length of collapsed content. 
            System.Windows.Media.TextFormatting.TextLine collapsedLine = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties));
            Invariant.Assert(collapsedLine.HasCollapsed, "Line has not been collapsed");
            IList collapsedRanges = collapsedLine.GetTextCollapsedRanges();
            if (collapsedRanges != null) 
            {
                Invariant.Assert(collapsedRanges.Count == 1, "Multiple collapsed ranges are not supported."); 
                TextCollapsedRange collapsedRange = collapsedRanges[0]; 
                return collapsedRange.Length;
            } 
            return 0;
        }

        ///  
        /// Retrieves collection of GlyphRuns from a range of text.
        ///  
        ///  
        /// Glyph runs.
        ///  
        /// 
        /// Start dcp of range
        /// 
        ///  
        /// End dcp of range.
        ///  
        internal void GetGlyphRuns(System.Collections.Generic.List glyphRuns, int dcpStart, int dcpEnd) 
        {
            // NOTE: Following logic is only temporary workaround for lack 
            //       of appropriate API that should be exposed by TextLine.

            int dcp = dcpStart - _dcp;
            int cch = dcpEnd - dcpStart; 
            Debug.Assert(dcp >= 0 && (dcp + cch <= _line.Length));
 
            IList> spans = _line.GetTextRunSpans(); 

            DrawingGroup drawing = new DrawingGroup(); 
            DrawingContext ctx = drawing.Open();

            // Calculate shift in line offset to render trailing spaces or avoid clipping text
            double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); 

            _line.Draw(ctx, new Point(delta, 0), InvertAxes.None); 
            ctx.Close(); 

            // Copy glyph runs into separate array (for backward navigation). 
            // And count number of chracters in the glyph runs collection.
            int cchGlyphRuns = 0;
            ArrayList glyphRunsCollection = new ArrayList(4);
 
            AddGlyphRunRecursive(drawing, glyphRunsCollection, ref cchGlyphRuns);
 
            Debug.Assert(cchGlyphRuns > 0 && glyphRunsCollection.Count > 0); 
            // Count number of characters in text runs.
            int cchTextSpans = 0; 
            foreach (TextSpan textSpan in spans)
            {
                if (textSpan.Value is TextCharacters)
                { 
                    cchTextSpans += textSpan.Length;
                } 
            } 
            // If number of characters in glyph runs is greater than number of characters
            // in text runs, it means that there is bullet at the beginning of the line 
            // or hyphen at the end of the line.
            // For now hyphen case is ignored.
            // Remove those glyph runs from our colleciton.
            while (cchGlyphRuns > cchTextSpans) 
            {
                GlyphRun glyphRun = (GlyphRun)glyphRunsCollection[0]; 
                cchGlyphRuns -= (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count); 
                glyphRunsCollection.RemoveAt(0);
            } 

            int curDcp = 0;
            int runIndex = 0;
            foreach (TextSpan span in spans) 
            {
                if (span.Value is TextCharacters) 
                { 
                    int cchRunsInSpan = 0;
                    while (cchRunsInSpan < span.Length) 
                    {
                        Invariant.Assert(runIndex < glyphRunsCollection.Count);
                        GlyphRun run = (GlyphRun)glyphRunsCollection[runIndex];
                        int characterCount = (run.Characters == null ? 0 : run.Characters.Count); 
                        if ((dcp < curDcp + characterCount) && (dcp + cch > curDcp))
                        { 
                            glyphRuns.Add(run); 
                        }
                        cchRunsInSpan += characterCount; 
                        ++runIndex;
                    }
                    Invariant.Assert(cchRunsInSpan == span.Length);
 
                    // No need to continue, if dcpEnd has been reached.
                    if (dcp + cch <= curDcp + span.Length) 
                        break; 
                }
                curDcp += span.Length; 
            }
        }

        ///  
        /// Return text position for next caret position
        ///  
        ///  
        /// CharacterHit for current position
        ///  
        internal CharacterHit GetNextCaretCharacterHit(CharacterHit index)
        {
            return _line.GetNextCaretCharacterHit(index);
        } 

        ///  
        /// Return text position for previous caret position 
        /// 
        ///  
        /// CharacterHit for current position
        /// 
        internal CharacterHit GetPreviousCaretCharacterHit(CharacterHit index)
        { 
            return _line.GetPreviousCaretCharacterHit(index);
        } 
 
        /// 
        /// Return text position for backspace caret position 
        /// 
        /// 
        /// CharacterHit for current position
        ///  
        internal CharacterHit GetBackspaceCaretCharacterHit(CharacterHit index)
        { 
            return _line.GetBackspaceCaretCharacterHit(index); 
        }
 
        /// 
        /// Returns true of char hit is at caret unit boundary.
        /// 
        ///  
        /// CharacterHit to be tested.
        ///  
        internal bool IsAtCaretCharacterHit(CharacterHit charHit) 
        {
            return _line.IsAtCaretCharacterHit(charHit, _dcp); 
        }

        #endregion Internal Methods
 
        //-------------------------------------------------------------------
        // 
        //  Internal Properties 
        //
        //------------------------------------------------------------------- 

        #region Internal Properties

        ///  
        /// Distance from the beginning of paragraph edge to the line edge.
        ///  
        internal int Start 
        {
            get 
            {
                return TextDpi.ToTextDpi(_line.Start) + TextDpi.ToTextDpi(_indent) + CalculateUOffsetShift();
            }
        } 

        ///  
        /// Calculated width of the line. 
        /// 
        internal int Width 
        {
            get
            {
                int width; 
                if (IsWidthAdjusted)
                { 
                    width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace) - TextDpi.ToTextDpi(_indent); 
                }
                else 
                {
                    width = TextDpi.ToTextDpi(_line.Width) - TextDpi.ToTextDpi(_indent);
                }
                Invariant.Assert(width >= 0, "Line width cannot be negative"); 
                return width;
            } 
        } 

        ///  
        /// Height of the line; line advance distance.
        /// 
        internal int Height
        { 
            get
            { 
                return TextDpi.ToTextDpi(_line.Height); 
            }
        } 

        /// 
        /// Baseline offset from the top of the line.
        ///  
        internal int Baseline
        { 
            get 
            {
                return TextDpi.ToTextDpi(_line.Baseline); 
            }
        }

        ///  
        /// True if last line of paragraph
        ///  
        internal bool EndOfParagraph 
        {
            get 
            {
                // If there are no Newline characters, it is not the end of paragraph.
                if (_line.NewlineLength == 0)
                { 
                    return false;
                } 
                // Since there are Newline characters in the line, do more expensive and 
                // accurate check.
                return (((TextSpan)_runs[_runs.Count-1]).Value is ParagraphBreakRun); 
            }
        }

        ///  
        /// Length of the line including any synthetic characters.
        /// This length is PTS frendly. PTS does not like 0 length lines. 
        ///  
        internal int SafeLength
        { 
            get
            {
                return _line.Length;
            } 
        }
 
        ///  
        /// Length of the line excluding any synthetic characters.
        ///  
        internal int ActualLength
        {
            get
            { 
                return _line.Length - (EndOfParagraph ? _syntheticCharacterLength : 0);
            } 
        } 

        ///  
        /// Length of the line excluding any synthetic characters and line breaks.
        /// 
        internal int ContentLength
        { 
            get
            { 
                return _line.Length - _line.NewlineLength; 
            }
        } 

        /// 
        /// Number of characters after the end of the line which may affect
        /// line wrapping. 
        /// 
        internal int DependantLength 
        { 
            get
            { 
                return _line.DependentLength;
            }
        }
 
        /// 
        /// Was line truncated (forced broken)? 
        ///  
        internal bool IsTruncated
        { 
            get
            {
                return _line.IsTruncated;
            } 
        }
 
        ///  
        /// Formatting result of the line.
        ///  
        internal PTS.FSFLRES FormattingResult
        {
            get
            { 
                PTS.FSFLRES formatResult = PTS.FSFLRES.fsflrOutOfSpace;
                // If there are no Newline characters, we run out of space. 
                if (_line.NewlineLength == 0) 
                {
                    return formatResult; 
                }
                // Since there are Newline characters in the line, do more expensive and
                // accurate check.
                TextRun run = ((TextSpan)_runs[_runs.Count - 1]).Value as TextRun; 
                if (run is ParagraphBreakRun)
                { 
                    formatResult = ((ParagraphBreakRun)run).BreakReason; 
                }
                else if (run is LineBreakRun) 
                {
                    formatResult = ((LineBreakRun)run).BreakReason;
                }
                return formatResult; 
            }
        } 
 
        #endregion Internal Properties
 
        //-------------------------------------------------------------------
        //
        //  Private Methods
        // 
        //--------------------------------------------------------------------
 
        #region Private Methods 

        ///  
        /// Returns true if there are any inline objects, false otherwise.
        /// 
        private bool HasInlineObjects()
        { 
            bool hasInlineObjects = false;
 
            foreach (TextSpan textSpan in _runs) 
            {
                if (textSpan.Value is InlineObjectRun) 
                {
                    hasInlineObjects = true;
                    break;
                } 
            }
            return hasInlineObjects; 
        } 

        ///  
        /// Returns bounds of an object/character at specified text index.
        /// 
        /// 
        /// Character index of an object/character 
        /// 
        ///  
        /// Number of positions occupied by object/character 
        /// 
        ///  
        /// Flow direction of object/character
        /// 
        /// 
        private Rect GetBoundsFromPosition(int cp, int cch, out FlowDirection flowDirection) 
        {
            Rect rect; 
            // Calculate shift in line offset to render trailing spaces or avoid clipping text 
            double delta = TextDpi.FromTextDpi(CalculateUOffsetShift());
            IList textBounds; 

            if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None)
            {
                // We should not shift offset in this case 
                Invariant.Assert(DoubleUtil.AreClose(delta, 0));
                System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); 
                Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); 
                textBounds = line.GetTextBounds(cp, cch);
            } 
            else
            {
                textBounds = _line.GetTextBounds(cp, cch);
            } 
            Invariant.Assert(textBounds != null && textBounds.Count == 1, "Expecting exactly one TextBounds for a single text position.");
            IList runBounds = textBounds[0].TextRunBounds; 
            if (runBounds != null) 
            {
                Debug.Assert(runBounds.Count == 1, "Expecting exactly one TextRunBounds for a single text position."); 
                rect = runBounds[0].Rectangle;
            }
            else
            { 
                rect = textBounds[0].Rectangle;
            } 
 
            flowDirection = textBounds[0].FlowDirection;
            rect.X = rect.X + delta; 
            return rect;
        }

        ///  
        /// Returns Line collapsing properties
        ///  
        ///  
        /// Wrapping width for collapsed line.
        ///  
        /// 
        /// Paragraph properties
        /// 
        private TextCollapsingProperties GetCollapsingProps(double wrappingWidth, LineProperties paraProperties) 
        {
            Invariant.Assert(paraProperties.TextTrimming != TextTrimming.None, "Text trimming must be enabled."); 
            TextCollapsingProperties collapsingProps; 
            if (paraProperties.TextTrimming == TextTrimming.CharacterEllipsis)
            { 
                collapsingProps = new TextTrailingCharacterEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties);
            }
            else
            { 
                collapsingProps = new TextTrailingWordEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties);
            } 
 
            return collapsingProps;
        } 

        /// 
        /// Perform depth-first search on a drawing tree to add all the glyph
        /// runs to the collection 
        /// 
        ///  
        /// Drawing on which we perform DFS 
        /// 
        ///  
        /// Glyph run collection.
        /// 
        /// 
        /// Character length of glyph run collection 
        /// 
        private void AddGlyphRunRecursive( 
            Drawing drawing, 
            IList   glyphRunsCollection,
            ref int cchGlyphRuns) 
        {
            DrawingGroup group = drawing as DrawingGroup;
            if (group != null)
            { 
                foreach (Drawing child in group.Children)
                { 
                    AddGlyphRunRecursive(child, glyphRunsCollection, ref cchGlyphRuns); 
                }
            } 
            else
            {
                GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing;
                if (glyphRunDrawing != null) 
                {
                    // Add a glyph run 
                    GlyphRun glyphRun = glyphRunDrawing.GlyphRun; 
                    if (glyphRun != null)
                    { 
                        cchGlyphRuns += (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count);
                        glyphRunsCollection.Add(glyphRun);
                    }
                } 
            }
        } 
 
        /// 
        /// Returns amount of shift for X-offset to render trailing spaces 
        /// 
        internal int CalculateUOffsetShift()
        {
            int width; 
            int trailingSpacesDelta = 0;
 
            // Calculate amount by which to to move line back if trailing spaces are rendered 
            if (IsUOffsetAdjusted)
            { 
                width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace);
                trailingSpacesDelta =  TextDpi.ToTextDpi(_line.Width) - width;
                Invariant.Assert(trailingSpacesDelta <= 0);
            } 
            else
            { 
                width = TextDpi.ToTextDpi(_line.Width); 
                trailingSpacesDelta = 0;
            } 

            // Calculate amount to shift line forward in case we are clipping the front of the line.
            // If line is showing ellipsis do not perform this check since we should not be clipping the front
            // of the line anyway 
            int widthDelta = 0;
            if ((_textAlignment == TextAlignment.Center || _textAlignment == TextAlignment.Right) && !ShowEllipses) 
            { 
                if (width > TextDpi.ToTextDpi(_wrappingWidth))
                { 
                    widthDelta = width - TextDpi.ToTextDpi(_wrappingWidth);
                }
                else
                { 
                    widthDelta = 0;
                } 
            } 
            int totalShift;
            if (_textAlignment == TextAlignment.Center) 
            {
                // Divide shift by two to center line
                totalShift = (int)((widthDelta + trailingSpacesDelta) / 2);
            } 
            else
            { 
                totalShift = widthDelta + trailingSpacesDelta; 
            }
            return totalShift; 
        }

        #endregion Private methods
 
        //-------------------------------------------------------------------
        // 
        //  Private Properties 
        //
        //-------------------------------------------------------------------- 

        #region Private Properties

        ///  
        /// True if line ends in hard break
        ///  
        private bool HasLineBreak 
        {
            get 
            {
                return (_line.NewlineLength > 0);
            }
        } 

        ///  
        /// True if line's X-offset needs adjustment to render trailing spaces 
        /// 
        private bool IsUOffsetAdjusted 
        {
            get
            {
                return ((_textAlignment == TextAlignment.Right || _textAlignment == TextAlignment.Center) && IsWidthAdjusted); 
            }
        } 
 
        /// 
        /// True if line's width is adjusted to include trailing spaces. For right and center alignment we need to 
        /// adjust line offset as well, but for left alignment we need to only make a width asjustment
        /// 
        private bool IsWidthAdjusted
        { 
            get
            { 
                bool adjusted = false; 

                // Trailing spaces rendered only around hard breaks 
                if (HasLineBreak || EndOfParagraph)
                {
                    // Lines with ellipsis are not shifted because ellipsis would not appear after trailing spaces
                    if (!ShowEllipses) 
                    {
                        adjusted = true; 
                    } 
                }
                return adjusted; 
            }
        }

        ///  
        /// True if eliipsis is displayed in the line
        ///  
        private bool ShowEllipses 
        {
            get 
            {
                if (TextParagraph.Properties.TextTrimming == TextTrimming.None)
                {
                    return false; 
                }
                if (_line.HasOverflowed) 
                { 
                    return true;
                } 
                return false;
            }
        }
 
        /// 
        /// Text Paragraph this line is formatted for 
        ///  
        private TextParagraph TextParagraph
        { 
            get
            {
                return _paraClient.Paragraph as TextParagraph;
            } 
        }
 
 
        #endregion Private Properties
 
        //--------------------------------------------------------------------
        //
        //  Private Fields
        // 
        //-------------------------------------------------------------------
 
        #region Private Fields 

        ///  
        /// TextFormatter host
        /// 
        private readonly TextFormatterHost _host;
 
        /// 
        /// Character position at the beginning of text paragraph. All DCPs 
        /// of the line are relative to this value. 
        /// 
        private readonly int _cpPara; 

        /// 
        /// Line formatting context. Valid only during formatting.
        ///  
        private FormattingContext _formattingContext;
 
        ///  
        /// Text line objects
        ///  
        private System.Windows.Media.TextFormatting.TextLine _line;

        /// 
        /// Cached run list. This list needs to be in sync with _line object. 
        /// Every time the line is recreated, this list needs to be updated.
        ///  
        private IList> _runs; 

        ///  
        /// Character position at the beginning of the line.
        /// 
        private int _dcp;
 
        /// 
        /// Line wrapping width 
        ///  
        private double _wrappingWidth;
 
        /// 
        /// Track width (line width ignoring floats)
        /// 
        private double _trackWidth = Double.NaN; 

        ///  
        /// Is text mirrored? 
        /// 
        private bool _mirror; 

        /// 
        /// Text indent. 0 for all lines except the first line, which maybe have non-zero indent.
        ///  
        private double _indent;
 
        ///  
        /// TextAlignment of owner
        ///  
        private TextAlignment _textAlignment;

        #endregion Private Fields
 
        // ------------------------------------------------------------------
        // 
        // FormattingContext Class 
        //
        // ----------------------------------------------------------------- 

        #region FormattingContext Class

        ///  
        /// Text line formatting context
        ///  
        internal class FormattingContext 
        {
            internal FormattingContext(bool measureMode, bool clearOnLeft, bool clearOnRight, TextRunCache textRunCache) 
            {
                MeasureMode = measureMode;
                ClearOnLeft = clearOnLeft;
                ClearOnRight = clearOnRight; 
                TextRunCache = textRunCache;
                LineFormatLengthTarget = -1; 
            } 

            internal TextRunCache TextRunCache; 
            internal bool MeasureMode;
            internal bool ClearOnLeft;
            internal bool ClearOnRight;
            internal int LineFormatLengthTarget; 
        }
 
        #endregion FormattingContext Class 
    }
} 

#pragma warning enable 1634, 1691


// 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