FormattedText.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / System / Windows / Media / FormattedText.cs / 1305600 / FormattedText.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Description: Implementation of FormattedText class. The FormattedText class is targeted at programmers 
// needing to add some simple text to a MIL visual.
// 
// History: 
//  04/10/2003 : mleonov - Created
// 
//---------------------------------------------------------------------------

using System;
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Diagnostics; 
using System.Globalization;
using System.Windows; 
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using System.Runtime.InteropServices;
using MS.Internal; 
using MS.Internal.TextFormatting;
using MS.Internal.FontFace; 
 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID; 

#pragma warning disable 1634, 1691
//Allow suppression of Presharp warnings
 
namespace System.Windows.Media
{ 
    ///  
    /// The FormattedText class is targeted at programmers needing to add some simple text to a MIL visual.
    ///  
    public class FormattedText
    {
        #region Construction
 
        /// 
        /// Construct a FormattedText object. 
        ///  
        /// String of text to be displayed.
        /// Culture of text. 
        /// Flow direction of text.
        /// Type face used to display text.
        /// Font em size in visual units (1/96 of an inch).
        /// Foreground brush used to render text. 
        public FormattedText(
            string textToFormat, 
            CultureInfo culture, 
            FlowDirection flowDirection,
            Typeface typeface, 
            double emSize,
            Brush foreground) : this(
                textToFormat,
                culture, 
                flowDirection,
                typeface, 
                emSize, 
                foreground,
                null, 
                TextFormattingMode.Ideal
                )

        { 
        }
 
        ///  
        /// Construct a FormattedText object.
        ///  
        /// String of text to be displayed.
        /// Culture of text.
        /// Flow direction of text.
        /// Type face used to display text. 
        /// Font em size in visual units (1/96 of an inch).
        /// Foreground brush used to render text. 
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number number method for the text culture is used.
        public FormattedText( 
            string textToFormat,
            CultureInfo culture,
            FlowDirection flowDirection,
            Typeface typeface, 
            double emSize,
            Brush foreground, 
            NumberSubstitution numberSubstitution) : this( 
                textToFormat,
                culture, 
                flowDirection,
                typeface,
                emSize,
                foreground, 
                numberSubstitution,
                TextFormattingMode.Ideal 
            ) 
        {
        } 

        /// 
        /// Construct a FormattedText object.
        ///  
        /// String of text to be displayed.
        /// Culture of text. 
        /// Flow direction of text. 
        /// Type face used to display text.
        /// Font em size in visual units (1/96 of an inch). 
        /// Foreground brush used to render text.
        /// Number substitution behavior to apply to the text; can be null,
        /// in which case the default number number method for the text culture is used.
        public FormattedText( 
            string textToFormat,
            CultureInfo culture, 
            FlowDirection flowDirection, 
            Typeface typeface,
            double emSize, 
            Brush foreground,
            NumberSubstitution numberSubstitution,
            TextFormattingMode textFormattingMode)
        { 
            if (textToFormat == null)
                throw new ArgumentNullException("textToFormat"); 
 
            if (typeface == null)
                throw new ArgumentNullException("typeface"); 

            ValidateCulture(culture);
            ValidateFlowDirection(flowDirection, "flowDirection");
            ValidateFontSize(emSize); 

            _textFormattingMode = textFormattingMode; 
            _text = textToFormat; 
            GenericTextRunProperties runProps = new GenericTextRunProperties(
                                     typeface, 
                                     emSize,
                                     12.0f, // default hinting size
                                     null,  // decorations
                                     foreground, 
                                     null,  // highlight background
                                     BaselineAlignment.Baseline, 
                                     culture, 
                                     numberSubstitution
                                     ); 
            _latestPosition = _formatRuns.SetValue(0, _text.Length, runProps, _latestPosition);

            _defaultParaProps = new GenericTextParagraphProperties(
                flowDirection, 
                TextAlignment.Left,
                false, 
                false, 
                runProps,
                TextWrapping.WrapWithOverflow, 
                0,  // line height not specified
                0   // indentation not specified
                );
 
            InvalidateMetrics();
        } 
 

        ///  
        /// Returns the string of text to be displayed
        /// 
        public string Text
        { 
            get
            { 
                return _text; 
            }
        } 

        #endregion

        #region Formatting properties 

        private static void ValidateCulture(CultureInfo culture) 
        { 
            if (culture == null)
                throw new ArgumentNullException("culture"); 
        }

        private static void ValidateFontSize(double emSize)
        { 
            if (emSize <= 0)
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterMustBeGreaterThanZero)); 
 
            if (emSize > MaxFontEmSize)
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterCannotBeGreaterThan, MaxFontEmSize)); 

            if (DoubleUtil.IsNaN(emSize))
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterValueCannotBeNaN));
        } 

        private static void ValidateFlowDirection(FlowDirection flowDirection, string parameterName) 
        { 
            if ((int)flowDirection < 0 || (int)flowDirection > (int)FlowDirection.RightToLeft)
                throw new InvalidEnumArgumentException(parameterName, (int)flowDirection, typeof(FlowDirection)); 
        }

        private int ValidateRange(int startIndex, int count)
        { 
            if (startIndex < 0 || startIndex > _text.Length)
                throw new ArgumentOutOfRangeException("startIndex"); 
 
            int limit = startIndex + count;
 
            if (count < 0 || limit < startIndex || limit > _text.Length)
                throw new ArgumentOutOfRangeException("count");

            return limit; 
        }
 
        private void InvalidateMetrics() 
        {
            _metrics = null; 
            _minWidth = double.MinValue;
        }

        ///  
        /// Sets foreground brush used for drawing text
        ///  
        /// Foreground brush 
        public void SetForegroundBrush(Brush foregroundBrush)
        { 
            SetForegroundBrush(foregroundBrush, 0, _text.Length);
        }

        ///  
        /// Sets foreground brush used for drawing text
        ///  
        /// Foreground brush 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetForegroundBrush(Brush foregroundBrush, int startIndex, int count)
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);
 
                if (runProps.ForegroundBrush == foregroundBrush) 
                    continue;
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface,
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    foregroundBrush, 
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
            }
        } 
 
        /// 
        /// Sets or changes the font family for the text object 
        /// 
        /// Font family name
        public void SetFontFamily(string fontFamily)
        { 
            SetFontFamily(fontFamily, 0, _text.Length);
        } 
 
        /// 
        /// Sets or changes the font family for the text object 
        /// 
        /// Font family name
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontFamily(string fontFamily, int startIndex, int count)
        { 
            if (fontFamily == null) 
                throw new ArgumentNullException("fontFamily");
 
            SetFontFamily(new FontFamily(fontFamily), startIndex, count);
        }

        ///  
        /// Sets or changes the font family for the text object
        ///  
        /// Font family 
        public void SetFontFamily(FontFamily fontFamily)
        { 
            SetFontFamily(fontFamily, 0, _text.Length);
        }

        ///  
        /// Sets or changes the font family for the text object
        ///  
        /// Font family 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontFamily(FontFamily fontFamily, int startIndex, int count)
        {
            if (fontFamily == null)
                throw new ArgumentNullException("fontFamily"); 

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 
 
                Invariant.Assert(runProps != null);
 
                Typeface oldTypeface = runProps.Typeface;
                if (fontFamily.Equals(oldTypeface.FontFamily))
                    continue;
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight, oldTypeface.Stretch), 
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            }
        }

 
        /// 
        /// Sets or changes the font em size measured in MIL units 
        ///  
        /// Font em size
        public void SetFontSize(double emSize) 
        {
            SetFontSize(emSize, 0, _text.Length);
        }
 
        /// 
        /// Sets or changes the font em size measured in MIL units 
        ///  
        /// Font em size
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetFontSize(double emSize, int startIndex, int count)
        {
            ValidateFontSize(emSize); 

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 
 
                Invariant.Assert(runProps != null);
 
                if (runProps.FontRenderingEmSize == emSize)
                    continue;

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    runProps.Typeface,
                    emSize, 
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    );
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
#pragma warning restore 6506 
                InvalidateMetrics();
            } 
        }

        /// 
        /// Sets or changes the culture for the text object. 
        /// 
        /// The new culture for the text object. 
        public void SetCulture(CultureInfo culture) 
        {
            SetCulture(culture, 0, _text.Length); 
        }

        /// 
        /// Sets or changes the culture for the text object. 
        /// 
        /// The new culture for the text object. 
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetCulture(CultureInfo culture, int startIndex, int count) 
        {
            ValidateCulture(culture);

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit; )
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null); 
 
                if (runProps.CultureInfo.Equals(culture))
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface,
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    culture,
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            } 
        }
 
        /// 
        /// Sets or changes the number substitution behavior for the text.
        /// 
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number substitution method for the text culture is used.
        public void SetNumberSubstitution( 
            NumberSubstitution numberSubstitution 
            )
        { 
            SetNumberSubstitution(numberSubstitution, 0, _text.Length);
        }

        ///  
        /// Sets or changes the number substitution behavior for a range of text.
        ///  
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number substitution method for the text culture is used.
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetNumberSubstitution(
            NumberSubstitution numberSubstitution,
            int startIndex, 
            int count
            ) 
        { 
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit; ) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length);
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);

                if (numberSubstitution != null)
                { 
                    if (numberSubstitution.Equals(runProps.NumberSubstitution))
                        continue; 
                } 
                else
                { 
                    if (runProps.NumberSubstitution == null)
                        continue;
                }
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface, 
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    numberSubstitution
                    ); 
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            }
        }

        ///  
        /// Sets or changes the font weight
        ///  
        /// Font weight 
        public void SetFontWeight(FontWeight weight)
        { 
            SetFontWeight(weight, 0, _text.Length);
        }

        ///  
        /// Sets or changes the font weight
        ///  
        /// Font weight 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontWeight(FontWeight weight, int startIndex, int count)
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);
 
                Typeface oldTypeface = runProps.Typeface; 
                if (oldTypeface.Weight == weight)
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight, oldTypeface.Stretch),
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            } 
        }
 
        /// 
        /// Sets or changes the font style
        /// 
        /// Font style 
        public void SetFontStyle(FontStyle style)
        { 
            SetFontStyle(style, 0, _text.Length); 
        }
 
        /// 
        /// Sets or changes the font style
        /// 
        /// Font style 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontStyle(FontStyle style, int startIndex, int count) 
        {
            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;)
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null);

                Typeface oldTypeface = runProps.Typeface; 
                if (oldTypeface.Style == style)
                    continue; 
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight, oldTypeface.Stretch), 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations,
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506

                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); // invalidate cached metrics 
            }
        } 
 
        /// 
        /// Sets or changes the font stretch 
        /// 
        /// Font stretch
        public void SetFontStretch(FontStretch stretch)
        { 
            SetFontStretch(stretch, 0, _text.Length);
        } 
 
        /// 
        /// Sets or changes the font stretch 
        /// 
        /// Font stretch
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontStretch(FontStretch stretch, int startIndex, int count)
        { 
            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;)
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null); 

                Typeface oldTypeface = runProps.Typeface;
                if (oldTypeface.Stretch == stretch)
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    new Typeface(oldTypeface.FontFamily, oldTypeface.Style, oldTypeface.Weight, stretch), 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    ); 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
#pragma warning restore 6506 

                InvalidateMetrics();
            }
        } 

        ///  
        /// Sets or changes the type face 
        /// 
        /// Typeface 
        public void SetFontTypeface(Typeface typeface)
        {
            SetFontTypeface(typeface, 0, _text.Length);
        } 

        ///  
        /// Sets or changes the type face 
        /// 
        /// Typeface 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to.
        public void SetFontTypeface(Typeface typeface, int startIndex, int count)
        { 
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null); 

                if (runProps.Typeface == typeface) 
                    continue;

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    typeface, 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment,
                    runProps.CultureInfo,
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506
 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
                InvalidateMetrics();
            } 
        }

        /// 
        /// Sets or changes the text decorations 
        /// 
        /// Text decorations 
        public void SetTextDecorations(TextDecorationCollection textDecorations) 
        {
            SetTextDecorations(textDecorations, 0, _text.Length); 
        }

        /// 
        /// Sets or changes the text decorations 
        /// 
        /// Text decorations 
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetTextDecorations(TextDecorationCollection textDecorations, int startIndex, int count) 
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;)
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;

                Invariant.Assert(runProps != null); 

                if (runProps.TextDecorations == textDecorations) 
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    runProps.Typeface,
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize,
                    textDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    );
#pragma warning restore 6506

                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
            }
        } 
 
        #endregion
 
        #region Line enumerator
        /// Note: enumeration is temporarily made private
        /// because of PS #828532
        /// 
        /// 
        /// Strongly typed enumerator used for enumerating text lines 
        ///  
        private struct LineEnumerator : IEnumerator, IDisposable
        { 
            int             _textStorePosition;
            int             _lineCount;
            double          _totalHeight;
            TextLine        _currentLine; 
            TextLine        _nextLine;
            TextFormatter   _formatter; 
            FormattedText   _that; 

            // these are needed because _currentLine can be disposed before the next MoveNext() call 
            double          _previousHeight;
            int             _previousLength;

            // line break before _currentLine, needed in case we have to reformat it with collapsing symbol 
            TextLineBreak       _previousLineBreak;
 
            internal LineEnumerator(FormattedText text) 
            {
                _previousHeight = 0; 
                _previousLength = 0;
                _previousLineBreak = null;

                _textStorePosition = 0; 
                _lineCount = 0;
                _totalHeight = 0; 
                _currentLine = null; 
                _nextLine = null;
                _formatter = TextFormatter.FromCurrentDispatcher(text._textFormattingMode); 
                _that = text;
                if (_that._textSourceImpl == null)
                    _that._textSourceImpl = new TextSourceImplementation(_that);
            } 

            public void Dispose() 
            { 
                if (_currentLine != null)
                { 
                    _currentLine.Dispose();
                    _currentLine = null;
                }
 
                if (_nextLine != null)
                { 
                    _nextLine.Dispose(); 
                    _nextLine = null;
                } 
            }

            internal int Position
            { 
                get
                { 
                    return _textStorePosition; 
                }
            } 

            internal int Length
            {
                get 
                {
                    return _previousLength; 
                } 
            }
 
            /// 
            /// Gets the current text line in the collection
            /// 
            public TextLine Current 
            {
                get 
                { 
                    return _currentLine;
                } 
            }

            /// 
            /// Gets the current text line in the collection 
            /// 
            object IEnumerator.Current 
            { 
                get
                { 
                    return (Current);
                }
            }
 
            /// 
            /// Gets the paragraph width used to format the current text line 
            ///  
            internal double CurrentParagraphWidth
            { 
                get
                {
                    return MaxLineLength(_lineCount);
                } 
            }
 
            private double MaxLineLength(int line) 
            {
                if (_that._maxTextWidths == null) 
                    return _that._maxTextWidth;
                return _that._maxTextWidths[Math.Min(line, _that._maxTextWidths.Length - 1)];
            }
 
            /// 
            /// Advances the enumerator to the next text line of the collection 
            ///  
            /// true if the enumerator was successfully advanced to the next element;
            /// false if the enumerator has passed the end of the collection 
            public bool MoveNext()
            {
                if (_currentLine == null)
                {   // this is the first line 

                    if (_that._text.Length == 0) 
                        return false; 

                    _currentLine = FormatLine( 
                        _that._textSourceImpl,
                        _textStorePosition,
                        MaxLineLength(_lineCount),
                        _that._defaultParaProps, 
                        null // no previous line break
                        ); 
 
                    // check if this line fits the text height
                    if (_totalHeight + _currentLine.Height > _that._maxTextHeight) 
                    {
                        _currentLine.Dispose();
                        _currentLine = null;
                        return false; 
                    }
                    Debug.Assert(_nextLine == null); 
                } 
                else
                { 
                    // there is no next line or it didn't fit
                    // either way we're finished
                    if (_nextLine == null)
                        return false; 

                    _totalHeight += _previousHeight; 
                    _textStorePosition += _previousLength; 
                    ++_lineCount;
 
                    _currentLine = _nextLine;
                    _nextLine = null;
                }
 
                TextLineBreak currentLineBreak = _currentLine.GetTextLineBreak();
 
                // this line is guaranteed to fit the text height 
                Debug.Assert(_totalHeight + _currentLine.Height <= _that._maxTextHeight);
 
                // now, check if the next line fits, we need to do this on this iteration
                // because we might need to add ellipsis to the current line
                // as a result of the next line measurement
 
                // maybe there is no next line at all
                if (_textStorePosition + _currentLine.Length < _that._text.Length) 
                { 
                    bool nextLineFits;
 
                    if (_lineCount + 1 >= _that._maxLineCount)
                        nextLineFits = false;
                    else
                    { 
                        _nextLine = FormatLine(
                            _that._textSourceImpl, 
                            _textStorePosition + _currentLine.Length, 
                            MaxLineLength(_lineCount + 1),
                            _that._defaultParaProps, 
                            currentLineBreak
                            );
                        nextLineFits = (_totalHeight + _currentLine.Height + _nextLine.Height <= _that._maxTextHeight);
                    } 

                    if (!nextLineFits) 
                    { 
                        // next line doesn't fit
                        if (_nextLine != null) 
                        {
                            _nextLine.Dispose();
                            _nextLine = null;
                        } 

                        if (_that._trimming != TextTrimming.None && !_currentLine.HasCollapsed) 
                        { 
                            // recreate the current line with ellipsis added
                            // Note: Paragraph ellipsis is not supported today. We'll workaround 
                            // it here by faking a non-wrap text on finite column width.
                            TextWrapping currentWrap = _that._defaultParaProps.TextWrapping;
                            _that._defaultParaProps.SetTextWrapping(TextWrapping.NoWrap);
 
                            if (currentLineBreak != null)
                                currentLineBreak.Dispose(); 
 
                            _currentLine.Dispose();
                            _currentLine = FormatLine( 
                                _that._textSourceImpl,
                                _textStorePosition,
                                MaxLineLength(_lineCount),
                                _that._defaultParaProps, 
                                _previousLineBreak
                                ); 
 
                            currentLineBreak = _currentLine.GetTextLineBreak();
                            _that._defaultParaProps.SetTextWrapping(currentWrap); 
                        }
                    }
                }
                _previousHeight = _currentLine.Height; 
                _previousLength = _currentLine.Length;
 
                if (_previousLineBreak != null) 
                    _previousLineBreak.Dispose();
 
                _previousLineBreak = currentLineBreak;

                return true;
            } 

 
            ///  
            /// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed.
            ///  
            private TextLine FormatLine(TextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak lineBreak)
            {
                TextLine line = _formatter.FormatLine(
                    textSource, 
                    textSourcePosition,
                    maxLineLength, 
                    paraProps, 
                    lineBreak
                    ); 

                if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0)
                {
                    // what I really need here is the last displayed text run of the line 
                    // textSourcePosition + line.Length - 1 works except the end of paragraph case,
                    // where line length includes the fake paragraph break run 
                    Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1); 

                    SpanRider thatFormatRider = new SpanRider( 
                        _that._formatRuns,
                        _that._latestPosition,
                        Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1)
                        ); 

                    GenericTextRunProperties lastRunProps = thatFormatRider.CurrentElement as GenericTextRunProperties; 
 
                    TextCollapsingProperties trailingEllipsis;
 
                    if (_that._trimming == TextTrimming.CharacterEllipsis)
                        trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps);
                    else
                    { 
                        Debug.Assert(_that._trimming == TextTrimming.WordEllipsis);
                        trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); 
                    } 

                    TextLine collapsedLine = line.Collapse(trailingEllipsis); 

                    if (collapsedLine != line)
                    {
                        line.Dispose(); 
                        line = collapsedLine;
                    } 
                } 
                return line;
            } 


            /// 
            /// Sets the enumerator to its initial position, 
            /// which is before the first element in the collection
            ///  
            public void Reset() 
            {
                _textStorePosition = 0; 
                _lineCount = 0;
                _totalHeight = 0;
                _currentLine = null;
                _nextLine = null; 
            }
        } 
 
        /// 
        /// Returns an enumerator that can iterate through the text line collection 
        /// 
        private LineEnumerator GetEnumerator()
        {
            return new LineEnumerator(this); 
        }
#if NEVER 
        ///  
        /// Returns an enumerator that can iterate through the text line collection
        ///  
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        } 
#endif
 
        private void AdvanceLineOrigin(ref Point lineOrigin, TextLine currentLine) 
        {
            double height = currentLine.Height; 
            // advance line origin according to the flow direction
            switch (_defaultParaProps.FlowDirection)
            {
                case FlowDirection.LeftToRight: 
                case FlowDirection.RightToLeft:
                    lineOrigin.Y += height; 
                    break; 
            }
        } 

        #endregion

        #region Measurement and layout properties 

        private class CachedMetrics 
        { 
            // vertical
            public double Height; 
            public double Baseline;

            // horizontal
            public double Width; 
            public double WidthIncludingTrailingWhitespace;
 
            // vertical bounding box metrics 
            public double Extent;
            public double OverhangAfter; 

            // horizontal bounding box metrics
            public double OverhangLeading;
            public double OverhangTrailing; 
        }
 
        ///  
        /// Defines the flow direction
        ///  
        public FlowDirection FlowDirection
        {
            set
            { 
                ValidateFlowDirection(value, "value");
                _defaultParaProps.SetFlowDirection(value); 
                InvalidateMetrics(); 
            }
            get 
            {
                return _defaultParaProps.FlowDirection;
            }
        } 

        ///  
        /// Defines the alignment of text within the column 
        /// 
        public TextAlignment TextAlignment 
        {
            set
            {
                _defaultParaProps.SetTextAlignment(value); 
                InvalidateMetrics();
            } 
            get 
            {
                return _defaultParaProps.TextAlignment; 
            }
        }

        ///  
        /// Gets or sets the height of, or the spacing between, each line where
        /// zero represents the default line height. 
        ///  
        public double LineHeight
        { 
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterCannotBeNegative)); 

                _defaultParaProps.SetLineHeight(value); 
                InvalidateMetrics(); 
            }
            get 
            {
                return _defaultParaProps.LineHeight;
            }
        } 

        ///  
        /// The MaxTextWidth property defines the alignment edges for the FormattedText. 
        /// For example, left aligned text is wrapped such that the leftmost glyph alignment point
        /// on each line falls exactly on the left edge of the rectangle. 
        /// Note that for many fonts, especially in italic style, some glyph strokes may extend beyond the edges of the alignment rectangle.
        /// For this reason, it is recommended that clients draw text with at least 1/6 em (i.e of the font size) unused margin space either side.
        /// Zero value of MaxTextWidth is equivalent to the maximum possible paragraph width.
        ///  
        public double MaxTextWidth
        { 
            set 
            {
                if (value < 0) 
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterCannotBeNegative));
                _maxTextWidth = value;
                InvalidateMetrics();
            } 
            get
            { 
                return _maxTextWidth; 
            }
        } 

        /// 
        /// Sets the array of lengths,
        /// which will be applied to each line of text in turn. 
        /// If the text covers more lines than there are entries in the length array,
        /// the last entry is reused as many times as required. 
        /// The maxTextWidths array overrides the MaxTextWidth property. 
        /// 
        /// The max text width array 
        public void SetMaxTextWidths(double [] maxTextWidths)
        {
            if (maxTextWidths == null || maxTextWidths.Length <= 0)
                throw new ArgumentNullException("maxTextWidths"); 
            _maxTextWidths = maxTextWidths;
            InvalidateMetrics(); 
        } 

        ///  
        /// Obtains a copy of the array of lengths,
        /// which will be applied to each line of text in turn.
        /// If the text covers more lines than there are entries in the length array,
        /// the last entry is reused as many times as required. 
        /// The maxTextWidths array overrides the MaxTextWidth property.
        ///  
        /// The copy of max text width array 
        public double [] GetMaxTextWidths()
        { 
            return (_maxTextWidths == null) ? null : (double [])_maxTextWidths.Clone();
        }

        ///  
        /// Sets the maximum length of a column of text.
        /// The last line of text displayed is the last whole line that will fit within this limit, 
        /// or the nth line as specified by MaxLineCount, whichever occurs first. 
        /// Use the Trimming property to control how the omission of text is indicated.
        ///  
        public double MaxTextHeight
        {
            set
            { 
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyMustBeGreaterThanZero, "MaxTextHeight")); 
 
                if (DoubleUtil.IsNaN(value))
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyValueCannotBeNaN, "MaxTextHeight")); 

                _maxTextHeight = value;
                InvalidateMetrics();
            } 
            get
            { 
                return _maxTextHeight; 
            }
        } 

        /// 
        /// Defines the maximum number of lines to display.
        /// The last line of text displayed is the lineCount-1'th line, 
        /// or the last whole line that will fit within the count set by MaxTextHeight,
        /// whichever occurs first. 
        /// Use the Trimming property to control how the omission of text is indicated 
        /// 
        public int MaxLineCount 
        {
            set
            {
                if (value <= 0) 
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterMustBeGreaterThanZero));
                _maxLineCount = value; 
                InvalidateMetrics(); 
            }
            get 
            {
                return _maxLineCount;
            }
        } 

 
        ///  
        /// Defines how omission of text is indicated.
        /// CharacterEllipsis trimming allows partial words to be displayed, 
        /// while WordEllipsis removes whole words to fit.
        /// Both guarantee to include an ellipsis ('...') at the end of the lines
        /// where text has been trimmed as a result of line and column limits.
        ///  
        public TextTrimming Trimming
        { 
            set 
            {
                if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis) 
                    throw new InvalidEnumArgumentException("value", (int)value, typeof(TextTrimming));

                _trimming = value;
                if (_trimming == TextTrimming.None) 
                {
                    // if trimming is disabled, enforce emergency wrap 
                    _defaultParaProps.SetTextWrapping(TextWrapping.Wrap); 
                }
                else 
                {
                    _defaultParaProps.SetTextWrapping(TextWrapping.WrapWithOverflow);
                }
 
                InvalidateMetrics();
            } 
            get 
            {
                return _trimming; 
            }
        }

 
        /// 
        /// Lazily initializes the cached metrics EXCEPT for black box metrics and 
        /// returns the CachedMetrics structure. 
        /// 
        private CachedMetrics Metrics 
        {
            get
            {
                if (_metrics == null) 
                {
                    // We need to obtain the metrics. DON'T compute black box metrics here because 
                    // they probably won't be needed and computing them requires GlyphRun creation. 
                    // In the common case where a client measures and then draws, we'll format twice
                    // but create GlyphRuns only during drawing. 

                    _metrics = DrawAndCalculateMetrics(
                        null,           // drawing context
                        new Point(),    // drawing offset 
                        false);         // don't calculate black box metrics
                } 
                return _metrics; 
            }
        } 


        /// 
        /// Lazily initializes the cached metrics INCLUDING black box metrics and 
        /// returns the CachedMetrics structure.
        ///  
        private CachedMetrics BlackBoxMetrics 
        {
            get 
            {
                if (_metrics == null || double.IsNaN(_metrics.Extent))
                {
                    // We need to obtain the metrics, including black box metrics. 

                    _metrics = DrawAndCalculateMetrics( 
                        null,           // drawing context 
                        new Point(),    // drawing offset
                        true);          // calculate black box metrics 
                }
                return _metrics;
            }
        } 

 
        ///  
        /// The distance from the top of the first line to the bottom of the last line.
        ///  
        public double Height
        {
            get
            { 
                return Metrics.Height;
            } 
        } 

        ///  
        /// The distance from the topmost black pixel of the first line
        /// to the bottommost black pixel of the last line.
        /// 
        public double Extent 
        {
            get 
            { 
                return BlackBoxMetrics.Extent;
            } 
        }

        /// 
        /// The distance from the top of the first line to the baseline of the first line. 
        /// 
        public double Baseline 
        { 
            get
            { 
                return Metrics.Baseline;
            }
        }
 
        /// 
        /// The distance from the bottom of the last line to the extent bottom. 
        ///  
        public double OverhangAfter
        { 
            get
            {
                return BlackBoxMetrics.OverhangAfter;
            } 
        }
 
        ///  
        /// The maximum distance from the leading black pixel to the leading alignment point of a line.
        ///  
        public double OverhangLeading
        {
            get
            { 
                return BlackBoxMetrics.OverhangLeading;
            } 
        } 

        ///  
        /// The maximum distance from the trailing black pixel to the trailing alignment point of a line.
        /// 
        public double OverhangTrailing
        { 
            get
            { 
                return BlackBoxMetrics.OverhangTrailing; 
            }
        } 

        /// 
        /// The maximum advance width between the leading and trailing alignment points of a line,
        /// excluding the width of whitespace characters at the end of the line. 
        /// 
        public double Width 
        { 
            get
            { 
                return Metrics.Width;
            }
        }
 
        /// 
        /// The maximum advance width between the leading and trailing alignment points of a line, 
        /// including the width of whitespace characters at the end of the line. 
        /// 
        public double WidthIncludingTrailingWhitespace 
        {
            get
            {
                return Metrics.WidthIncludingTrailingWhitespace; 
            }
        } 
 
        /// 
        /// The minimum line width that can be specified without causing any word to break. 
        /// 
        public double MinWidth
        {
            get 
            {
                if (_minWidth != double.MinValue) 
                    return _minWidth; 

                if (_textSourceImpl == null) 
                    _textSourceImpl = new TextSourceImplementation(this);

                _minWidth = TextFormatter.FromCurrentDispatcher(_textFormattingMode).FormatMinMaxParagraphWidth(
                    _textSourceImpl, 
                    0,  // textSourceCharacterIndex
                    _defaultParaProps 
                    ).MinWidth; 

                return _minWidth; 
            }
        }

 
        /// 
        /// Builds a highlight geometry object. 
        ///  
        /// The origin of the highlight region
        /// Geometry that surrounds the text. 
        public Geometry BuildHighlightGeometry(Point origin)
        {
            return BuildHighlightGeometry(origin, 0, _text.Length);
        } 

        ///  
        /// Obtains geometry for the text, including underlines and strikethroughs. 
        /// 
        /// The left top origin of the resulting geometry. 
        /// The geometry returned contains the combined geometry
        /// of all of the glyphs, underlines and strikeThroughs that represent the formatted text.
        /// Overlapping contours are merged by performing a Boolean union operation.
        public Geometry BuildGeometry(Point origin) 
        {
            GeometryGroup accumulatedGeometry = null; 
            Point lineOrigin = origin; 

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

            // we can't use foreach because it requires GetEnumerator and associated classes to be public
            // foreach (TextLine currentLine in this) 

            using (LineEnumerator enumerator = GetEnumerator()) 
            { 
                while (enumerator.MoveNext())
                { 
                    using (TextLine currentLine = enumerator.Current)
                    {
                        currentLine.Draw(ctx, lineOrigin, InvertAxes.None);
                        AdvanceLineOrigin(ref lineOrigin, currentLine); 
                    }
                } 
            } 

            ctx.Close(); 

            //  recursively go down the DrawingGroup to build up the geometry
            CombineGeometryRecursive(drawing, ref accumulatedGeometry);
 
            // Make sure to always return Geometry.Empty from public methods for empty geometries.
            if (accumulatedGeometry == null || accumulatedGeometry.IsEmpty()) 
                return Geometry.Empty; 
            return accumulatedGeometry;
        } 

        /// 
        /// Builds a highlight geometry object for a given character range.
        ///  
        /// The origin of the highlight region.
        /// The start index of initial character the bounds should be obtained for. 
        /// The number of characters the bounds should be obtained for. 
        /// Geometry that surrounds the specified character range.
        public Geometry BuildHighlightGeometry(Point origin, int startIndex, int count) 
        {
            ValidateRange(startIndex, count);

            PathGeometry accumulatedBounds = null; 
            using (LineEnumerator enumerator = GetEnumerator())
            { 
 
                Point lineOrigin = origin;
 
                while (enumerator.MoveNext())
                {
                    using (TextLine currentLine = enumerator.Current)
                    { 
                        int x0 = Math.Max(enumerator.Position, startIndex);
                        int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count); 
 
                        // check if this line is intersects with the specified character range
                        if (x0 < x1) 
                        {
                            IList highlightBounds = currentLine.GetTextBounds(
                                x0,
                                x1 - x0 
                                );
 
                            if (highlightBounds != null) 
                            {
                                foreach (TextBounds bound in highlightBounds) 
                                {
                                    Rect rect = bound.Rectangle;

                                    if (FlowDirection == FlowDirection.RightToLeft) 
                                    {
                                        // Convert logical units (which extend leftward from the right edge 
                                        // of the paragraph) to physical units. 
                                        //
                                        // Note that since rect is in logical units, rect.Right corresponds to 
                                        // the visual *left* edge of the rectangle in the RTL case. Specifically,
                                        // is the distance leftward from the right edge of the formatting rectangle
                                        // whose width is the paragraph width passed to FormatLine.
                                        // 
                                        rect.X = enumerator.CurrentParagraphWidth - rect.Right;
                                    } 
 
                                    rect.X += lineOrigin.X;
                                    rect.Y += lineOrigin.Y; 

                                    RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
                                    if (accumulatedBounds == null)
                                        accumulatedBounds = rectangleGeometry.GetAsPathGeometry(); 
                                    else
                                        accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union, null); 
                                } 
                            }
                        } 
                        AdvanceLineOrigin(ref lineOrigin, currentLine);
                    }
                }
            } 

            if (accumulatedBounds == null  ||  accumulatedBounds.IsEmpty()) 
                return null; 

            return accumulatedBounds; 
        }

        #endregion
 
        #region Drawing
        ///  
        /// Draws the text object 
        /// 
        internal void Draw( 
            DrawingContext  dc,
            Point           origin
            )
        { 
            Point lineOrigin = origin;
 
            if (_metrics != null && !double.IsNaN(_metrics.Extent)) 
            {
                // we can't use foreach because it requires GetEnumerator and associated classes to be public 
                // foreach (TextLine currentLine in this)

                using (LineEnumerator enumerator = GetEnumerator())
                { 
                    while (enumerator.MoveNext())
                    { 
                        using (TextLine currentLine = enumerator.Current) 
                        {
                            currentLine.Draw(dc, lineOrigin, InvertAxes.None); 
                            AdvanceLineOrigin(ref lineOrigin, currentLine);
                        }
                    }
                } 
            }
            else 
            { 
                // Calculate metrics as we draw to avoid formatting again if we need metrics later; we compute
                // black box metrics too because these are already known as a side-effect of drawing 

                _metrics = DrawAndCalculateMetrics(dc, origin, true);
            }
        } 

        private CachedMetrics DrawAndCalculateMetrics(DrawingContext dc, Point drawingOffset, bool getBlackBoxMetrics) 
        { 
            // The calculation for FormattedText.Width and Overhangs was wrong for Right and Center alignment.
            // Thus the fix of this bug is based on the fact that FormattedText always had 0 indent and no 
            // TextMarkerProperties. These assumptions enabled us to remove TextLine.Start from the calculation
            // of the Width. TextLine.Start caused the calculation of FormattedText to be incorrect in cases
            // of Right and Center alignment because it took on -ve values when ParagraphWidth was 0 (which indicates infinite width).
            // This was a result of how TextFormatter interprets TextLine.Start. In the simplest case, it computes 
            // TextLine.Start as Paragraph Width - Line Width (for Right alignment).
            // So, the following two Debug.Asserts verify that the assumptions over which the bug fix was made are still valid 
            // and not changed by adding features to FormattedText. Incase these assumptions were invalidated, the bug fix 
            // should be revised and it will possibly involve alot of changes elsewhere.
            Debug.Assert(_defaultParaProps.Indent == 0.0, "FormattedText was assumed to always have 0 indent. This assumption has changed and thus the calculation of Width and Overhangs should be revised."); 
            Debug.Assert(_defaultParaProps.TextMarkerProperties == null, "FormattedText was assumed to always have no TextMarkerProperties. This assumption has changed and thus the calculation of Width and Overhangs should be revised.");
            CachedMetrics metrics = new CachedMetrics();

            if (_text.Length == 0) 
            {
                return metrics; 
            } 

            // we can't use foreach because it requires GetEnumerator and associated classes to be public 
            // foreach (TextLine currentLine in this)

            using (LineEnumerator enumerator = GetEnumerator())
            { 
                bool first = true;
 
                double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; 
                accBlackBoxLeft = accBlackBoxTop = double.MaxValue;
                accBlackBoxRight = accBlackBoxBottom = double.MinValue; 

                Point origin = new Point(0, 0);

                // Holds the TextLine.Start of the longest line. Thus it will hold the minimum value 
                // of TextLine.Start among all the lines that forms the text. The overhangs (leading and trailing)
                // are calculated with an offset as a result of the same issue with TextLine.Start. 
                // So, we compute this offset and remove it later from the values of the overhangs. 
                double lineStartOfLongestLine = Double.MaxValue;
                while (enumerator.MoveNext()) 
                {
                    // enumerator will dispose the currentLine
                    using (TextLine currentLine = enumerator.Current)
                    { 
                        // if we're drawing, do it first as this will compute black box metrics as a side-effect
                        if (dc != null) 
                        { 
                            currentLine.Draw(
                                dc, 
                                new Point(origin.X + drawingOffset.X, origin.Y + drawingOffset.Y),
                                InvertAxes.None
                                );
                        } 

                        if (getBlackBoxMetrics) 
                        { 
                            double blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;
                            double blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing; 
                            double blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter;
                            double blackBoxTop = blackBoxBottom - currentLine.Extent;

                            accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); 
                            accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight);
                            accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); 
                            accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); 

                            metrics.OverhangAfter = currentLine.OverhangAfter; 
                        }

                        metrics.Height += currentLine.Height;
                        metrics.Width = Math.Max(metrics.Width, currentLine.Width); 
                        metrics.WidthIncludingTrailingWhitespace = Math.Max(metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);
                        lineStartOfLongestLine = Math.Min(lineStartOfLongestLine, currentLine.Start); 
 
                        if (first)
                        { 
                            metrics.Baseline = currentLine.Baseline;
                            first = false;
                        }
 
                        AdvanceLineOrigin(ref origin, currentLine);
                    } 
                } 

                if (getBlackBoxMetrics) 
                {
                    metrics.Extent = accBlackBoxBottom - accBlackBoxTop;
                    metrics.OverhangLeading = accBlackBoxLeft - lineStartOfLongestLine;
                    metrics.OverhangTrailing = metrics.Width - (accBlackBoxRight - lineStartOfLongestLine); 
                }
                else 
                { 
                    // indicate that black box metrics are not known
                    metrics.Extent = double.NaN; 
                }
            }

            return metrics; 
        }
 
        #endregion 

        #region TextSource implementation 

        private class TextSourceImplementation : TextSource
        {
            private FormattedText   _that; 

            public TextSourceImplementation(FormattedText text) 
            { 
                _that = text;
            } 

            /// 
            /// TextFormatter to get a text run started at specified text source position
            ///  
            /// character index to specify where in the source text the fetch is to start.
            public override TextRun GetTextRun( 
                int         textSourceCharacterIndex 
                )
            { 
                if (textSourceCharacterIndex >= _that._text.Length)
                {
                    return new TextEndOfParagraph(1);
                } 

                SpanRider thatFormatRider = new SpanRider( 
                    _that._formatRuns, 
                    _that._latestPosition,
                    textSourceCharacterIndex 
                    );

                return new TextCharacters(_that._text,
                    textSourceCharacterIndex, 
                    thatFormatRider.Length,
                    thatFormatRider.CurrentElement as GenericTextRunProperties 
                    ); 
            }
 

            /// 
            /// TextFormatter to get text immediately before specified text source position.
            ///  
            /// character index to specify where in the source text the text retrieval stops.
            /// character string immediately before the specify text source character index. 
            public override TextSpan GetPrecedingText( 
                int         textSourceCharacterIndexLimit
                ) 
            {
                CharacterBufferRange charString = CharacterBufferRange.Empty;
                CultureInfo culture = null;
 
                if (textSourceCharacterIndexLimit > 0)
                { 
                    SpanRider thatFormatRider = new SpanRider( 
                        _that._formatRuns,
                        _that._latestPosition, 
                        textSourceCharacterIndexLimit - 1
                        );

                    charString = new CharacterBufferRange( 
                        new CharacterBufferReference(_that._text, thatFormatRider.CurrentSpanStart),
                        textSourceCharacterIndexLimit - thatFormatRider.CurrentSpanStart 
                        ); 

                    culture = ((TextRunProperties)thatFormatRider.CurrentElement).CultureInfo; 
                }

                return new TextSpan (
                    charString.Length, 
                    new CultureSpecificCharacterBufferRange(culture, charString)
                    ); 
            } 

            ///  
            /// TextFormatter to map a text source character index to a text effect character index
            /// 
            ///  text source character index 
            ///  the text effect index corresponding to the text effect character index  
            public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(
                int textSourceCharacterIndex 
                ) 
            {
                throw new NotSupportedException(); 
            }
        };

        #endregion 

        #region private methods 
        private void CombineGeometryRecursive(Drawing drawing, ref GeometryGroup accumulatedGeometry) 
        {
            DrawingGroup group = drawing as DrawingGroup; 
            if (group != null)
            {
                // recursively go down for DrawingGroup
                foreach (Drawing child in group.Children) 
                {
                    CombineGeometryRecursive(child, ref accumulatedGeometry); 
                } 
            }
            else 
            {
                GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing;
                if (glyphRunDrawing != null)
                { 
                    // process glyph run
                    GlyphRun glyphRun = glyphRunDrawing.GlyphRun; 
                    if (glyphRun != null) 
                    {
                        Geometry glyphRunGeometry = glyphRun.BuildGeometry(); 

                        if (!glyphRunGeometry.IsEmpty())
                        {
                            if (accumulatedGeometry == null) 
                            {
                                accumulatedGeometry = new GeometryGroup(); 
                                accumulatedGeometry.FillRule = FillRule.Nonzero; 
                            }
                            accumulatedGeometry.Children.Add(glyphRunGeometry); 
                        }
                    }
                }
                else 
                {
                    GeometryDrawing geometryDrawing = drawing as GeometryDrawing; 
                    if (geometryDrawing != null) 
                    {
                        // process geometry (i.e. TextDecoration on the line) 
                        Geometry geometry = geometryDrawing.Geometry;

                        if (geometry != null)
                        { 
                            LineGeometry lineGeometry = geometry as LineGeometry;
                            if (lineGeometry != null) 
                            { 
                                // For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no
                                // bounding area. So this line won't show up. Work aroud it by increase the Bounding rect 
                                // to be Pen's thickness

                                Rect bound  = lineGeometry.Bounds;
                                if (bound.Height == 0) 
                                {
                                    bound.Height = geometryDrawing.Pen.Thickness; 
                                } 
                                else if (bound.Width == 0)
                                { 
                                    bound.Width = geometryDrawing.Pen.Thickness;
                                }

                                // convert the line geometry into a rectangle geometry 
                                // we lost line cap info here
                                geometry = new RectangleGeometry(bound); 
                            } 
                            if (accumulatedGeometry == null)
                            { 
                                accumulatedGeometry = new GeometryGroup();
                                accumulatedGeometry.FillRule = FillRule.Nonzero;
                            }
                            accumulatedGeometry.Children.Add(geometry); 
                        }
                    } 
                } 
            }
        } 
        #endregion

        #region Private fields
 
        // properties and format runs
        private string                          _text; 
 
        private SpanVector                      _formatRuns = new SpanVector(null);
        private SpanPosition                    _latestPosition = new SpanPosition(); 

        private GenericTextParagraphProperties  _defaultParaProps;

        private double                          _maxTextWidth; 
        private double []                       _maxTextWidths;
        private double                          _maxTextHeight = double.MaxValue; 
        private int                             _maxLineCount = int.MaxValue; 
        private TextTrimming                    _trimming = TextTrimming.WordEllipsis;
 
        private TextFormattingMode              _textFormattingMode;

        // text source callbacks
        private TextSourceImplementation        _textSourceImpl; 

        // cached metrics 
        private CachedMetrics                   _metrics; 
        private double                          _minWidth;
 
        #endregion

        #region Constants
 
        const double MaxFontEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;
 
        #endregion 
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------------- 
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Description: Implementation of FormattedText class. The FormattedText class is targeted at programmers 
// needing to add some simple text to a MIL visual.
// 
// History: 
//  04/10/2003 : mleonov - Created
// 
//---------------------------------------------------------------------------

using System;
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Diagnostics; 
using System.Globalization;
using System.Windows; 
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using System.Runtime.InteropServices;
using MS.Internal; 
using MS.Internal.TextFormatting;
using MS.Internal.FontFace; 
 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID; 

#pragma warning disable 1634, 1691
//Allow suppression of Presharp warnings
 
namespace System.Windows.Media
{ 
    ///  
    /// The FormattedText class is targeted at programmers needing to add some simple text to a MIL visual.
    ///  
    public class FormattedText
    {
        #region Construction
 
        /// 
        /// Construct a FormattedText object. 
        ///  
        /// String of text to be displayed.
        /// Culture of text. 
        /// Flow direction of text.
        /// Type face used to display text.
        /// Font em size in visual units (1/96 of an inch).
        /// Foreground brush used to render text. 
        public FormattedText(
            string textToFormat, 
            CultureInfo culture, 
            FlowDirection flowDirection,
            Typeface typeface, 
            double emSize,
            Brush foreground) : this(
                textToFormat,
                culture, 
                flowDirection,
                typeface, 
                emSize, 
                foreground,
                null, 
                TextFormattingMode.Ideal
                )

        { 
        }
 
        ///  
        /// Construct a FormattedText object.
        ///  
        /// String of text to be displayed.
        /// Culture of text.
        /// Flow direction of text.
        /// Type face used to display text. 
        /// Font em size in visual units (1/96 of an inch).
        /// Foreground brush used to render text. 
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number number method for the text culture is used.
        public FormattedText( 
            string textToFormat,
            CultureInfo culture,
            FlowDirection flowDirection,
            Typeface typeface, 
            double emSize,
            Brush foreground, 
            NumberSubstitution numberSubstitution) : this( 
                textToFormat,
                culture, 
                flowDirection,
                typeface,
                emSize,
                foreground, 
                numberSubstitution,
                TextFormattingMode.Ideal 
            ) 
        {
        } 

        /// 
        /// Construct a FormattedText object.
        ///  
        /// String of text to be displayed.
        /// Culture of text. 
        /// Flow direction of text. 
        /// Type face used to display text.
        /// Font em size in visual units (1/96 of an inch). 
        /// Foreground brush used to render text.
        /// Number substitution behavior to apply to the text; can be null,
        /// in which case the default number number method for the text culture is used.
        public FormattedText( 
            string textToFormat,
            CultureInfo culture, 
            FlowDirection flowDirection, 
            Typeface typeface,
            double emSize, 
            Brush foreground,
            NumberSubstitution numberSubstitution,
            TextFormattingMode textFormattingMode)
        { 
            if (textToFormat == null)
                throw new ArgumentNullException("textToFormat"); 
 
            if (typeface == null)
                throw new ArgumentNullException("typeface"); 

            ValidateCulture(culture);
            ValidateFlowDirection(flowDirection, "flowDirection");
            ValidateFontSize(emSize); 

            _textFormattingMode = textFormattingMode; 
            _text = textToFormat; 
            GenericTextRunProperties runProps = new GenericTextRunProperties(
                                     typeface, 
                                     emSize,
                                     12.0f, // default hinting size
                                     null,  // decorations
                                     foreground, 
                                     null,  // highlight background
                                     BaselineAlignment.Baseline, 
                                     culture, 
                                     numberSubstitution
                                     ); 
            _latestPosition = _formatRuns.SetValue(0, _text.Length, runProps, _latestPosition);

            _defaultParaProps = new GenericTextParagraphProperties(
                flowDirection, 
                TextAlignment.Left,
                false, 
                false, 
                runProps,
                TextWrapping.WrapWithOverflow, 
                0,  // line height not specified
                0   // indentation not specified
                );
 
            InvalidateMetrics();
        } 
 

        ///  
        /// Returns the string of text to be displayed
        /// 
        public string Text
        { 
            get
            { 
                return _text; 
            }
        } 

        #endregion

        #region Formatting properties 

        private static void ValidateCulture(CultureInfo culture) 
        { 
            if (culture == null)
                throw new ArgumentNullException("culture"); 
        }

        private static void ValidateFontSize(double emSize)
        { 
            if (emSize <= 0)
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterMustBeGreaterThanZero)); 
 
            if (emSize > MaxFontEmSize)
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterCannotBeGreaterThan, MaxFontEmSize)); 

            if (DoubleUtil.IsNaN(emSize))
                throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterValueCannotBeNaN));
        } 

        private static void ValidateFlowDirection(FlowDirection flowDirection, string parameterName) 
        { 
            if ((int)flowDirection < 0 || (int)flowDirection > (int)FlowDirection.RightToLeft)
                throw new InvalidEnumArgumentException(parameterName, (int)flowDirection, typeof(FlowDirection)); 
        }

        private int ValidateRange(int startIndex, int count)
        { 
            if (startIndex < 0 || startIndex > _text.Length)
                throw new ArgumentOutOfRangeException("startIndex"); 
 
            int limit = startIndex + count;
 
            if (count < 0 || limit < startIndex || limit > _text.Length)
                throw new ArgumentOutOfRangeException("count");

            return limit; 
        }
 
        private void InvalidateMetrics() 
        {
            _metrics = null; 
            _minWidth = double.MinValue;
        }

        ///  
        /// Sets foreground brush used for drawing text
        ///  
        /// Foreground brush 
        public void SetForegroundBrush(Brush foregroundBrush)
        { 
            SetForegroundBrush(foregroundBrush, 0, _text.Length);
        }

        ///  
        /// Sets foreground brush used for drawing text
        ///  
        /// Foreground brush 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetForegroundBrush(Brush foregroundBrush, int startIndex, int count)
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);
 
                if (runProps.ForegroundBrush == foregroundBrush) 
                    continue;
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface,
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    foregroundBrush, 
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
            }
        } 
 
        /// 
        /// Sets or changes the font family for the text object 
        /// 
        /// Font family name
        public void SetFontFamily(string fontFamily)
        { 
            SetFontFamily(fontFamily, 0, _text.Length);
        } 
 
        /// 
        /// Sets or changes the font family for the text object 
        /// 
        /// Font family name
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontFamily(string fontFamily, int startIndex, int count)
        { 
            if (fontFamily == null) 
                throw new ArgumentNullException("fontFamily");
 
            SetFontFamily(new FontFamily(fontFamily), startIndex, count);
        }

        ///  
        /// Sets or changes the font family for the text object
        ///  
        /// Font family 
        public void SetFontFamily(FontFamily fontFamily)
        { 
            SetFontFamily(fontFamily, 0, _text.Length);
        }

        ///  
        /// Sets or changes the font family for the text object
        ///  
        /// Font family 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontFamily(FontFamily fontFamily, int startIndex, int count)
        {
            if (fontFamily == null)
                throw new ArgumentNullException("fontFamily"); 

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 
 
                Invariant.Assert(runProps != null);
 
                Typeface oldTypeface = runProps.Typeface;
                if (fontFamily.Equals(oldTypeface.FontFamily))
                    continue;
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight, oldTypeface.Stretch), 
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            }
        }

 
        /// 
        /// Sets or changes the font em size measured in MIL units 
        ///  
        /// Font em size
        public void SetFontSize(double emSize) 
        {
            SetFontSize(emSize, 0, _text.Length);
        }
 
        /// 
        /// Sets or changes the font em size measured in MIL units 
        ///  
        /// Font em size
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetFontSize(double emSize, int startIndex, int count)
        {
            ValidateFontSize(emSize); 

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 
 
                Invariant.Assert(runProps != null);
 
                if (runProps.FontRenderingEmSize == emSize)
                    continue;

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    runProps.Typeface,
                    emSize, 
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    );
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
#pragma warning restore 6506 
                InvalidateMetrics();
            } 
        }

        /// 
        /// Sets or changes the culture for the text object. 
        /// 
        /// The new culture for the text object. 
        public void SetCulture(CultureInfo culture) 
        {
            SetCulture(culture, 0, _text.Length); 
        }

        /// 
        /// Sets or changes the culture for the text object. 
        /// 
        /// The new culture for the text object. 
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetCulture(CultureInfo culture, int startIndex, int count) 
        {
            ValidateCulture(culture);

            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit; )
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length);
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null); 
 
                if (runProps.CultureInfo.Equals(culture))
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface,
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    culture,
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            } 
        }
 
        /// 
        /// Sets or changes the number substitution behavior for the text.
        /// 
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number substitution method for the text culture is used.
        public void SetNumberSubstitution( 
            NumberSubstitution numberSubstitution 
            )
        { 
            SetNumberSubstitution(numberSubstitution, 0, _text.Length);
        }

        ///  
        /// Sets or changes the number substitution behavior for a range of text.
        ///  
        /// Number substitution behavior to apply to the text; can be null, 
        /// in which case the default number substitution method for the text culture is used.
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetNumberSubstitution(
            NumberSubstitution numberSubstitution,
            int startIndex, 
            int count
            ) 
        { 
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit; ) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length);
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);

                if (numberSubstitution != null)
                { 
                    if (numberSubstitution.Equals(runProps.NumberSubstitution))
                        continue; 
                } 
                else
                { 
                    if (runProps.NumberSubstitution == null)
                        continue;
                }
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    runProps.Typeface, 
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment,
                    runProps.CultureInfo, 
                    numberSubstitution
                    ); 
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            }
        }

        ///  
        /// Sets or changes the font weight
        ///  
        /// Font weight 
        public void SetFontWeight(FontWeight weight)
        { 
            SetFontWeight(weight, 0, _text.Length);
        }

        ///  
        /// Sets or changes the font weight
        ///  
        /// Font weight 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontWeight(FontWeight weight, int startIndex, int count)
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); 
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null);
 
                Typeface oldTypeface = runProps.Typeface; 
                if (oldTypeface.Weight == weight)
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight, oldTypeface.Stretch),
                    runProps.FontRenderingEmSize, 
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution
                    );
#pragma warning restore 6506 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); 
            } 
        }
 
        /// 
        /// Sets or changes the font style
        /// 
        /// Font style 
        public void SetFontStyle(FontStyle style)
        { 
            SetFontStyle(style, 0, _text.Length); 
        }
 
        /// 
        /// Sets or changes the font style
        /// 
        /// Font style 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontStyle(FontStyle style, int startIndex, int count) 
        {
            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;)
            {
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null);

                Typeface oldTypeface = runProps.Typeface; 
                if (oldTypeface.Style == style)
                    continue; 
 
                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight, oldTypeface.Stretch), 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize,
                    runProps.TextDecorations,
                    runProps.ForegroundBrush, 
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo, 
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506

                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
                InvalidateMetrics(); // invalidate cached metrics 
            }
        } 
 
        /// 
        /// Sets or changes the font stretch 
        /// 
        /// Font stretch
        public void SetFontStretch(FontStretch stretch)
        { 
            SetFontStretch(stretch, 0, _text.Length);
        } 
 
        /// 
        /// Sets or changes the font stretch 
        /// 
        /// Font stretch
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to. 
        public void SetFontStretch(FontStretch stretch, int startIndex, int count)
        { 
            int limit = ValidateRange(startIndex, count); 
            for (int i = startIndex; i < limit;)
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length);

#pragma warning disable 6506 
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; 

                Invariant.Assert(runProps != null); 

                Typeface oldTypeface = runProps.Typeface;
                if (oldTypeface.Stretch == stretch)
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    new Typeface(oldTypeface.FontFamily, oldTypeface.Style, oldTypeface.Weight, stretch), 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations,
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush,
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    ); 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
#pragma warning restore 6506 

                InvalidateMetrics();
            }
        } 

        ///  
        /// Sets or changes the type face 
        /// 
        /// Typeface 
        public void SetFontTypeface(Typeface typeface)
        {
            SetFontTypeface(typeface, 0, _text.Length);
        } 

        ///  
        /// Sets or changes the type face 
        /// 
        /// Typeface 
        /// The start index of initial character to apply the change to.
        /// The number of characters the change should be applied to.
        public void SetFontTypeface(Typeface typeface, int startIndex, int count)
        { 
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;) 
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 

#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null
                // because the rider is already checked to be in range 
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;
 
                Invariant.Assert(runProps != null); 

                if (runProps.Typeface == typeface) 
                    continue;

                GenericTextRunProperties newProps = new GenericTextRunProperties(
                    typeface, 
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize, 
                    runProps.TextDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment,
                    runProps.CultureInfo,
                    runProps.NumberSubstitution
                    ); 
#pragma warning restore 6506
 
                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
                InvalidateMetrics();
            } 
        }

        /// 
        /// Sets or changes the text decorations 
        /// 
        /// Text decorations 
        public void SetTextDecorations(TextDecorationCollection textDecorations) 
        {
            SetTextDecorations(textDecorations, 0, _text.Length); 
        }

        /// 
        /// Sets or changes the text decorations 
        /// 
        /// Text decorations 
        /// The start index of initial character to apply the change to. 
        /// The number of characters the change should be applied to.
        public void SetTextDecorations(TextDecorationCollection textDecorations, int startIndex, int count) 
        {
            int limit = ValidateRange(startIndex, count);
            for (int i = startIndex; i < limit;)
            { 
                SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i);
                i = Math.Min(limit, i + formatRider.Length); 
 
#pragma warning disable 6506
                // Presharp warns that runProps is not validated, but it can never be null 
                // because the rider is already checked to be in range
                GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties;

                Invariant.Assert(runProps != null); 

                if (runProps.TextDecorations == textDecorations) 
                    continue; 

                GenericTextRunProperties newProps = new GenericTextRunProperties( 
                    runProps.Typeface,
                    runProps.FontRenderingEmSize,
                    runProps.FontHintingEmSize,
                    textDecorations, 
                    runProps.ForegroundBrush,
                    runProps.BackgroundBrush, 
                    runProps.BaselineAlignment, 
                    runProps.CultureInfo,
                    runProps.NumberSubstitution 
                    );
#pragma warning restore 6506

                _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); 
            }
        } 
 
        #endregion
 
        #region Line enumerator
        /// Note: enumeration is temporarily made private
        /// because of PS #828532
        /// 
        /// 
        /// Strongly typed enumerator used for enumerating text lines 
        ///  
        private struct LineEnumerator : IEnumerator, IDisposable
        { 
            int             _textStorePosition;
            int             _lineCount;
            double          _totalHeight;
            TextLine        _currentLine; 
            TextLine        _nextLine;
            TextFormatter   _formatter; 
            FormattedText   _that; 

            // these are needed because _currentLine can be disposed before the next MoveNext() call 
            double          _previousHeight;
            int             _previousLength;

            // line break before _currentLine, needed in case we have to reformat it with collapsing symbol 
            TextLineBreak       _previousLineBreak;
 
            internal LineEnumerator(FormattedText text) 
            {
                _previousHeight = 0; 
                _previousLength = 0;
                _previousLineBreak = null;

                _textStorePosition = 0; 
                _lineCount = 0;
                _totalHeight = 0; 
                _currentLine = null; 
                _nextLine = null;
                _formatter = TextFormatter.FromCurrentDispatcher(text._textFormattingMode); 
                _that = text;
                if (_that._textSourceImpl == null)
                    _that._textSourceImpl = new TextSourceImplementation(_that);
            } 

            public void Dispose() 
            { 
                if (_currentLine != null)
                { 
                    _currentLine.Dispose();
                    _currentLine = null;
                }
 
                if (_nextLine != null)
                { 
                    _nextLine.Dispose(); 
                    _nextLine = null;
                } 
            }

            internal int Position
            { 
                get
                { 
                    return _textStorePosition; 
                }
            } 

            internal int Length
            {
                get 
                {
                    return _previousLength; 
                } 
            }
 
            /// 
            /// Gets the current text line in the collection
            /// 
            public TextLine Current 
            {
                get 
                { 
                    return _currentLine;
                } 
            }

            /// 
            /// Gets the current text line in the collection 
            /// 
            object IEnumerator.Current 
            { 
                get
                { 
                    return (Current);
                }
            }
 
            /// 
            /// Gets the paragraph width used to format the current text line 
            ///  
            internal double CurrentParagraphWidth
            { 
                get
                {
                    return MaxLineLength(_lineCount);
                } 
            }
 
            private double MaxLineLength(int line) 
            {
                if (_that._maxTextWidths == null) 
                    return _that._maxTextWidth;
                return _that._maxTextWidths[Math.Min(line, _that._maxTextWidths.Length - 1)];
            }
 
            /// 
            /// Advances the enumerator to the next text line of the collection 
            ///  
            /// true if the enumerator was successfully advanced to the next element;
            /// false if the enumerator has passed the end of the collection 
            public bool MoveNext()
            {
                if (_currentLine == null)
                {   // this is the first line 

                    if (_that._text.Length == 0) 
                        return false; 

                    _currentLine = FormatLine( 
                        _that._textSourceImpl,
                        _textStorePosition,
                        MaxLineLength(_lineCount),
                        _that._defaultParaProps, 
                        null // no previous line break
                        ); 
 
                    // check if this line fits the text height
                    if (_totalHeight + _currentLine.Height > _that._maxTextHeight) 
                    {
                        _currentLine.Dispose();
                        _currentLine = null;
                        return false; 
                    }
                    Debug.Assert(_nextLine == null); 
                } 
                else
                { 
                    // there is no next line or it didn't fit
                    // either way we're finished
                    if (_nextLine == null)
                        return false; 

                    _totalHeight += _previousHeight; 
                    _textStorePosition += _previousLength; 
                    ++_lineCount;
 
                    _currentLine = _nextLine;
                    _nextLine = null;
                }
 
                TextLineBreak currentLineBreak = _currentLine.GetTextLineBreak();
 
                // this line is guaranteed to fit the text height 
                Debug.Assert(_totalHeight + _currentLine.Height <= _that._maxTextHeight);
 
                // now, check if the next line fits, we need to do this on this iteration
                // because we might need to add ellipsis to the current line
                // as a result of the next line measurement
 
                // maybe there is no next line at all
                if (_textStorePosition + _currentLine.Length < _that._text.Length) 
                { 
                    bool nextLineFits;
 
                    if (_lineCount + 1 >= _that._maxLineCount)
                        nextLineFits = false;
                    else
                    { 
                        _nextLine = FormatLine(
                            _that._textSourceImpl, 
                            _textStorePosition + _currentLine.Length, 
                            MaxLineLength(_lineCount + 1),
                            _that._defaultParaProps, 
                            currentLineBreak
                            );
                        nextLineFits = (_totalHeight + _currentLine.Height + _nextLine.Height <= _that._maxTextHeight);
                    } 

                    if (!nextLineFits) 
                    { 
                        // next line doesn't fit
                        if (_nextLine != null) 
                        {
                            _nextLine.Dispose();
                            _nextLine = null;
                        } 

                        if (_that._trimming != TextTrimming.None && !_currentLine.HasCollapsed) 
                        { 
                            // recreate the current line with ellipsis added
                            // Note: Paragraph ellipsis is not supported today. We'll workaround 
                            // it here by faking a non-wrap text on finite column width.
                            TextWrapping currentWrap = _that._defaultParaProps.TextWrapping;
                            _that._defaultParaProps.SetTextWrapping(TextWrapping.NoWrap);
 
                            if (currentLineBreak != null)
                                currentLineBreak.Dispose(); 
 
                            _currentLine.Dispose();
                            _currentLine = FormatLine( 
                                _that._textSourceImpl,
                                _textStorePosition,
                                MaxLineLength(_lineCount),
                                _that._defaultParaProps, 
                                _previousLineBreak
                                ); 
 
                            currentLineBreak = _currentLine.GetTextLineBreak();
                            _that._defaultParaProps.SetTextWrapping(currentWrap); 
                        }
                    }
                }
                _previousHeight = _currentLine.Height; 
                _previousLength = _currentLine.Length;
 
                if (_previousLineBreak != null) 
                    _previousLineBreak.Dispose();
 
                _previousLineBreak = currentLineBreak;

                return true;
            } 

 
            ///  
            /// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed.
            ///  
            private TextLine FormatLine(TextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak lineBreak)
            {
                TextLine line = _formatter.FormatLine(
                    textSource, 
                    textSourcePosition,
                    maxLineLength, 
                    paraProps, 
                    lineBreak
                    ); 

                if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0)
                {
                    // what I really need here is the last displayed text run of the line 
                    // textSourcePosition + line.Length - 1 works except the end of paragraph case,
                    // where line length includes the fake paragraph break run 
                    Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1); 

                    SpanRider thatFormatRider = new SpanRider( 
                        _that._formatRuns,
                        _that._latestPosition,
                        Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1)
                        ); 

                    GenericTextRunProperties lastRunProps = thatFormatRider.CurrentElement as GenericTextRunProperties; 
 
                    TextCollapsingProperties trailingEllipsis;
 
                    if (_that._trimming == TextTrimming.CharacterEllipsis)
                        trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps);
                    else
                    { 
                        Debug.Assert(_that._trimming == TextTrimming.WordEllipsis);
                        trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); 
                    } 

                    TextLine collapsedLine = line.Collapse(trailingEllipsis); 

                    if (collapsedLine != line)
                    {
                        line.Dispose(); 
                        line = collapsedLine;
                    } 
                } 
                return line;
            } 


            /// 
            /// Sets the enumerator to its initial position, 
            /// which is before the first element in the collection
            ///  
            public void Reset() 
            {
                _textStorePosition = 0; 
                _lineCount = 0;
                _totalHeight = 0;
                _currentLine = null;
                _nextLine = null; 
            }
        } 
 
        /// 
        /// Returns an enumerator that can iterate through the text line collection 
        /// 
        private LineEnumerator GetEnumerator()
        {
            return new LineEnumerator(this); 
        }
#if NEVER 
        ///  
        /// Returns an enumerator that can iterate through the text line collection
        ///  
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        } 
#endif
 
        private void AdvanceLineOrigin(ref Point lineOrigin, TextLine currentLine) 
        {
            double height = currentLine.Height; 
            // advance line origin according to the flow direction
            switch (_defaultParaProps.FlowDirection)
            {
                case FlowDirection.LeftToRight: 
                case FlowDirection.RightToLeft:
                    lineOrigin.Y += height; 
                    break; 
            }
        } 

        #endregion

        #region Measurement and layout properties 

        private class CachedMetrics 
        { 
            // vertical
            public double Height; 
            public double Baseline;

            // horizontal
            public double Width; 
            public double WidthIncludingTrailingWhitespace;
 
            // vertical bounding box metrics 
            public double Extent;
            public double OverhangAfter; 

            // horizontal bounding box metrics
            public double OverhangLeading;
            public double OverhangTrailing; 
        }
 
        ///  
        /// Defines the flow direction
        ///  
        public FlowDirection FlowDirection
        {
            set
            { 
                ValidateFlowDirection(value, "value");
                _defaultParaProps.SetFlowDirection(value); 
                InvalidateMetrics(); 
            }
            get 
            {
                return _defaultParaProps.FlowDirection;
            }
        } 

        ///  
        /// Defines the alignment of text within the column 
        /// 
        public TextAlignment TextAlignment 
        {
            set
            {
                _defaultParaProps.SetTextAlignment(value); 
                InvalidateMetrics();
            } 
            get 
            {
                return _defaultParaProps.TextAlignment; 
            }
        }

        ///  
        /// Gets or sets the height of, or the spacing between, each line where
        /// zero represents the default line height. 
        ///  
        public double LineHeight
        { 
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterCannotBeNegative)); 

                _defaultParaProps.SetLineHeight(value); 
                InvalidateMetrics(); 
            }
            get 
            {
                return _defaultParaProps.LineHeight;
            }
        } 

        ///  
        /// The MaxTextWidth property defines the alignment edges for the FormattedText. 
        /// For example, left aligned text is wrapped such that the leftmost glyph alignment point
        /// on each line falls exactly on the left edge of the rectangle. 
        /// Note that for many fonts, especially in italic style, some glyph strokes may extend beyond the edges of the alignment rectangle.
        /// For this reason, it is recommended that clients draw text with at least 1/6 em (i.e of the font size) unused margin space either side.
        /// Zero value of MaxTextWidth is equivalent to the maximum possible paragraph width.
        ///  
        public double MaxTextWidth
        { 
            set 
            {
                if (value < 0) 
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterCannotBeNegative));
                _maxTextWidth = value;
                InvalidateMetrics();
            } 
            get
            { 
                return _maxTextWidth; 
            }
        } 

        /// 
        /// Sets the array of lengths,
        /// which will be applied to each line of text in turn. 
        /// If the text covers more lines than there are entries in the length array,
        /// the last entry is reused as many times as required. 
        /// The maxTextWidths array overrides the MaxTextWidth property. 
        /// 
        /// The max text width array 
        public void SetMaxTextWidths(double [] maxTextWidths)
        {
            if (maxTextWidths == null || maxTextWidths.Length <= 0)
                throw new ArgumentNullException("maxTextWidths"); 
            _maxTextWidths = maxTextWidths;
            InvalidateMetrics(); 
        } 

        ///  
        /// Obtains a copy of the array of lengths,
        /// which will be applied to each line of text in turn.
        /// If the text covers more lines than there are entries in the length array,
        /// the last entry is reused as many times as required. 
        /// The maxTextWidths array overrides the MaxTextWidth property.
        ///  
        /// The copy of max text width array 
        public double [] GetMaxTextWidths()
        { 
            return (_maxTextWidths == null) ? null : (double [])_maxTextWidths.Clone();
        }

        ///  
        /// Sets the maximum length of a column of text.
        /// The last line of text displayed is the last whole line that will fit within this limit, 
        /// or the nth line as specified by MaxLineCount, whichever occurs first. 
        /// Use the Trimming property to control how the omission of text is indicated.
        ///  
        public double MaxTextHeight
        {
            set
            { 
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyMustBeGreaterThanZero, "MaxTextHeight")); 
 
                if (DoubleUtil.IsNaN(value))
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyValueCannotBeNaN, "MaxTextHeight")); 

                _maxTextHeight = value;
                InvalidateMetrics();
            } 
            get
            { 
                return _maxTextHeight; 
            }
        } 

        /// 
        /// Defines the maximum number of lines to display.
        /// The last line of text displayed is the lineCount-1'th line, 
        /// or the last whole line that will fit within the count set by MaxTextHeight,
        /// whichever occurs first. 
        /// Use the Trimming property to control how the omission of text is indicated 
        /// 
        public int MaxLineCount 
        {
            set
            {
                if (value <= 0) 
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.ParameterMustBeGreaterThanZero));
                _maxLineCount = value; 
                InvalidateMetrics(); 
            }
            get 
            {
                return _maxLineCount;
            }
        } 

 
        ///  
        /// Defines how omission of text is indicated.
        /// CharacterEllipsis trimming allows partial words to be displayed, 
        /// while WordEllipsis removes whole words to fit.
        /// Both guarantee to include an ellipsis ('...') at the end of the lines
        /// where text has been trimmed as a result of line and column limits.
        ///  
        public TextTrimming Trimming
        { 
            set 
            {
                if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis) 
                    throw new InvalidEnumArgumentException("value", (int)value, typeof(TextTrimming));

                _trimming = value;
                if (_trimming == TextTrimming.None) 
                {
                    // if trimming is disabled, enforce emergency wrap 
                    _defaultParaProps.SetTextWrapping(TextWrapping.Wrap); 
                }
                else 
                {
                    _defaultParaProps.SetTextWrapping(TextWrapping.WrapWithOverflow);
                }
 
                InvalidateMetrics();
            } 
            get 
            {
                return _trimming; 
            }
        }

 
        /// 
        /// Lazily initializes the cached metrics EXCEPT for black box metrics and 
        /// returns the CachedMetrics structure. 
        /// 
        private CachedMetrics Metrics 
        {
            get
            {
                if (_metrics == null) 
                {
                    // We need to obtain the metrics. DON'T compute black box metrics here because 
                    // they probably won't be needed and computing them requires GlyphRun creation. 
                    // In the common case where a client measures and then draws, we'll format twice
                    // but create GlyphRuns only during drawing. 

                    _metrics = DrawAndCalculateMetrics(
                        null,           // drawing context
                        new Point(),    // drawing offset 
                        false);         // don't calculate black box metrics
                } 
                return _metrics; 
            }
        } 


        /// 
        /// Lazily initializes the cached metrics INCLUDING black box metrics and 
        /// returns the CachedMetrics structure.
        ///  
        private CachedMetrics BlackBoxMetrics 
        {
            get 
            {
                if (_metrics == null || double.IsNaN(_metrics.Extent))
                {
                    // We need to obtain the metrics, including black box metrics. 

                    _metrics = DrawAndCalculateMetrics( 
                        null,           // drawing context 
                        new Point(),    // drawing offset
                        true);          // calculate black box metrics 
                }
                return _metrics;
            }
        } 

 
        ///  
        /// The distance from the top of the first line to the bottom of the last line.
        ///  
        public double Height
        {
            get
            { 
                return Metrics.Height;
            } 
        } 

        ///  
        /// The distance from the topmost black pixel of the first line
        /// to the bottommost black pixel of the last line.
        /// 
        public double Extent 
        {
            get 
            { 
                return BlackBoxMetrics.Extent;
            } 
        }

        /// 
        /// The distance from the top of the first line to the baseline of the first line. 
        /// 
        public double Baseline 
        { 
            get
            { 
                return Metrics.Baseline;
            }
        }
 
        /// 
        /// The distance from the bottom of the last line to the extent bottom. 
        ///  
        public double OverhangAfter
        { 
            get
            {
                return BlackBoxMetrics.OverhangAfter;
            } 
        }
 
        ///  
        /// The maximum distance from the leading black pixel to the leading alignment point of a line.
        ///  
        public double OverhangLeading
        {
            get
            { 
                return BlackBoxMetrics.OverhangLeading;
            } 
        } 

        ///  
        /// The maximum distance from the trailing black pixel to the trailing alignment point of a line.
        /// 
        public double OverhangTrailing
        { 
            get
            { 
                return BlackBoxMetrics.OverhangTrailing; 
            }
        } 

        /// 
        /// The maximum advance width between the leading and trailing alignment points of a line,
        /// excluding the width of whitespace characters at the end of the line. 
        /// 
        public double Width 
        { 
            get
            { 
                return Metrics.Width;
            }
        }
 
        /// 
        /// The maximum advance width between the leading and trailing alignment points of a line, 
        /// including the width of whitespace characters at the end of the line. 
        /// 
        public double WidthIncludingTrailingWhitespace 
        {
            get
            {
                return Metrics.WidthIncludingTrailingWhitespace; 
            }
        } 
 
        /// 
        /// The minimum line width that can be specified without causing any word to break. 
        /// 
        public double MinWidth
        {
            get 
            {
                if (_minWidth != double.MinValue) 
                    return _minWidth; 

                if (_textSourceImpl == null) 
                    _textSourceImpl = new TextSourceImplementation(this);

                _minWidth = TextFormatter.FromCurrentDispatcher(_textFormattingMode).FormatMinMaxParagraphWidth(
                    _textSourceImpl, 
                    0,  // textSourceCharacterIndex
                    _defaultParaProps 
                    ).MinWidth; 

                return _minWidth; 
            }
        }

 
        /// 
        /// Builds a highlight geometry object. 
        ///  
        /// The origin of the highlight region
        /// Geometry that surrounds the text. 
        public Geometry BuildHighlightGeometry(Point origin)
        {
            return BuildHighlightGeometry(origin, 0, _text.Length);
        } 

        ///  
        /// Obtains geometry for the text, including underlines and strikethroughs. 
        /// 
        /// The left top origin of the resulting geometry. 
        /// The geometry returned contains the combined geometry
        /// of all of the glyphs, underlines and strikeThroughs that represent the formatted text.
        /// Overlapping contours are merged by performing a Boolean union operation.
        public Geometry BuildGeometry(Point origin) 
        {
            GeometryGroup accumulatedGeometry = null; 
            Point lineOrigin = origin; 

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

            // we can't use foreach because it requires GetEnumerator and associated classes to be public
            // foreach (TextLine currentLine in this) 

            using (LineEnumerator enumerator = GetEnumerator()) 
            { 
                while (enumerator.MoveNext())
                { 
                    using (TextLine currentLine = enumerator.Current)
                    {
                        currentLine.Draw(ctx, lineOrigin, InvertAxes.None);
                        AdvanceLineOrigin(ref lineOrigin, currentLine); 
                    }
                } 
            } 

            ctx.Close(); 

            //  recursively go down the DrawingGroup to build up the geometry
            CombineGeometryRecursive(drawing, ref accumulatedGeometry);
 
            // Make sure to always return Geometry.Empty from public methods for empty geometries.
            if (accumulatedGeometry == null || accumulatedGeometry.IsEmpty()) 
                return Geometry.Empty; 
            return accumulatedGeometry;
        } 

        /// 
        /// Builds a highlight geometry object for a given character range.
        ///  
        /// The origin of the highlight region.
        /// The start index of initial character the bounds should be obtained for. 
        /// The number of characters the bounds should be obtained for. 
        /// Geometry that surrounds the specified character range.
        public Geometry BuildHighlightGeometry(Point origin, int startIndex, int count) 
        {
            ValidateRange(startIndex, count);

            PathGeometry accumulatedBounds = null; 
            using (LineEnumerator enumerator = GetEnumerator())
            { 
 
                Point lineOrigin = origin;
 
                while (enumerator.MoveNext())
                {
                    using (TextLine currentLine = enumerator.Current)
                    { 
                        int x0 = Math.Max(enumerator.Position, startIndex);
                        int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count); 
 
                        // check if this line is intersects with the specified character range
                        if (x0 < x1) 
                        {
                            IList highlightBounds = currentLine.GetTextBounds(
                                x0,
                                x1 - x0 
                                );
 
                            if (highlightBounds != null) 
                            {
                                foreach (TextBounds bound in highlightBounds) 
                                {
                                    Rect rect = bound.Rectangle;

                                    if (FlowDirection == FlowDirection.RightToLeft) 
                                    {
                                        // Convert logical units (which extend leftward from the right edge 
                                        // of the paragraph) to physical units. 
                                        //
                                        // Note that since rect is in logical units, rect.Right corresponds to 
                                        // the visual *left* edge of the rectangle in the RTL case. Specifically,
                                        // is the distance leftward from the right edge of the formatting rectangle
                                        // whose width is the paragraph width passed to FormatLine.
                                        // 
                                        rect.X = enumerator.CurrentParagraphWidth - rect.Right;
                                    } 
 
                                    rect.X += lineOrigin.X;
                                    rect.Y += lineOrigin.Y; 

                                    RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
                                    if (accumulatedBounds == null)
                                        accumulatedBounds = rectangleGeometry.GetAsPathGeometry(); 
                                    else
                                        accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union, null); 
                                } 
                            }
                        } 
                        AdvanceLineOrigin(ref lineOrigin, currentLine);
                    }
                }
            } 

            if (accumulatedBounds == null  ||  accumulatedBounds.IsEmpty()) 
                return null; 

            return accumulatedBounds; 
        }

        #endregion
 
        #region Drawing
        ///  
        /// Draws the text object 
        /// 
        internal void Draw( 
            DrawingContext  dc,
            Point           origin
            )
        { 
            Point lineOrigin = origin;
 
            if (_metrics != null && !double.IsNaN(_metrics.Extent)) 
            {
                // we can't use foreach because it requires GetEnumerator and associated classes to be public 
                // foreach (TextLine currentLine in this)

                using (LineEnumerator enumerator = GetEnumerator())
                { 
                    while (enumerator.MoveNext())
                    { 
                        using (TextLine currentLine = enumerator.Current) 
                        {
                            currentLine.Draw(dc, lineOrigin, InvertAxes.None); 
                            AdvanceLineOrigin(ref lineOrigin, currentLine);
                        }
                    }
                } 
            }
            else 
            { 
                // Calculate metrics as we draw to avoid formatting again if we need metrics later; we compute
                // black box metrics too because these are already known as a side-effect of drawing 

                _metrics = DrawAndCalculateMetrics(dc, origin, true);
            }
        } 

        private CachedMetrics DrawAndCalculateMetrics(DrawingContext dc, Point drawingOffset, bool getBlackBoxMetrics) 
        { 
            // The calculation for FormattedText.Width and Overhangs was wrong for Right and Center alignment.
            // Thus the fix of this bug is based on the fact that FormattedText always had 0 indent and no 
            // TextMarkerProperties. These assumptions enabled us to remove TextLine.Start from the calculation
            // of the Width. TextLine.Start caused the calculation of FormattedText to be incorrect in cases
            // of Right and Center alignment because it took on -ve values when ParagraphWidth was 0 (which indicates infinite width).
            // This was a result of how TextFormatter interprets TextLine.Start. In the simplest case, it computes 
            // TextLine.Start as Paragraph Width - Line Width (for Right alignment).
            // So, the following two Debug.Asserts verify that the assumptions over which the bug fix was made are still valid 
            // and not changed by adding features to FormattedText. Incase these assumptions were invalidated, the bug fix 
            // should be revised and it will possibly involve alot of changes elsewhere.
            Debug.Assert(_defaultParaProps.Indent == 0.0, "FormattedText was assumed to always have 0 indent. This assumption has changed and thus the calculation of Width and Overhangs should be revised."); 
            Debug.Assert(_defaultParaProps.TextMarkerProperties == null, "FormattedText was assumed to always have no TextMarkerProperties. This assumption has changed and thus the calculation of Width and Overhangs should be revised.");
            CachedMetrics metrics = new CachedMetrics();

            if (_text.Length == 0) 
            {
                return metrics; 
            } 

            // we can't use foreach because it requires GetEnumerator and associated classes to be public 
            // foreach (TextLine currentLine in this)

            using (LineEnumerator enumerator = GetEnumerator())
            { 
                bool first = true;
 
                double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; 
                accBlackBoxLeft = accBlackBoxTop = double.MaxValue;
                accBlackBoxRight = accBlackBoxBottom = double.MinValue; 

                Point origin = new Point(0, 0);

                // Holds the TextLine.Start of the longest line. Thus it will hold the minimum value 
                // of TextLine.Start among all the lines that forms the text. The overhangs (leading and trailing)
                // are calculated with an offset as a result of the same issue with TextLine.Start. 
                // So, we compute this offset and remove it later from the values of the overhangs. 
                double lineStartOfLongestLine = Double.MaxValue;
                while (enumerator.MoveNext()) 
                {
                    // enumerator will dispose the currentLine
                    using (TextLine currentLine = enumerator.Current)
                    { 
                        // if we're drawing, do it first as this will compute black box metrics as a side-effect
                        if (dc != null) 
                        { 
                            currentLine.Draw(
                                dc, 
                                new Point(origin.X + drawingOffset.X, origin.Y + drawingOffset.Y),
                                InvertAxes.None
                                );
                        } 

                        if (getBlackBoxMetrics) 
                        { 
                            double blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;
                            double blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing; 
                            double blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter;
                            double blackBoxTop = blackBoxBottom - currentLine.Extent;

                            accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); 
                            accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight);
                            accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); 
                            accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); 

                            metrics.OverhangAfter = currentLine.OverhangAfter; 
                        }

                        metrics.Height += currentLine.Height;
                        metrics.Width = Math.Max(metrics.Width, currentLine.Width); 
                        metrics.WidthIncludingTrailingWhitespace = Math.Max(metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace);
                        lineStartOfLongestLine = Math.Min(lineStartOfLongestLine, currentLine.Start); 
 
                        if (first)
                        { 
                            metrics.Baseline = currentLine.Baseline;
                            first = false;
                        }
 
                        AdvanceLineOrigin(ref origin, currentLine);
                    } 
                } 

                if (getBlackBoxMetrics) 
                {
                    metrics.Extent = accBlackBoxBottom - accBlackBoxTop;
                    metrics.OverhangLeading = accBlackBoxLeft - lineStartOfLongestLine;
                    metrics.OverhangTrailing = metrics.Width - (accBlackBoxRight - lineStartOfLongestLine); 
                }
                else 
                { 
                    // indicate that black box metrics are not known
                    metrics.Extent = double.NaN; 
                }
            }

            return metrics; 
        }
 
        #endregion 

        #region TextSource implementation 

        private class TextSourceImplementation : TextSource
        {
            private FormattedText   _that; 

            public TextSourceImplementation(FormattedText text) 
            { 
                _that = text;
            } 

            /// 
            /// TextFormatter to get a text run started at specified text source position
            ///  
            /// character index to specify where in the source text the fetch is to start.
            public override TextRun GetTextRun( 
                int         textSourceCharacterIndex 
                )
            { 
                if (textSourceCharacterIndex >= _that._text.Length)
                {
                    return new TextEndOfParagraph(1);
                } 

                SpanRider thatFormatRider = new SpanRider( 
                    _that._formatRuns, 
                    _that._latestPosition,
                    textSourceCharacterIndex 
                    );

                return new TextCharacters(_that._text,
                    textSourceCharacterIndex, 
                    thatFormatRider.Length,
                    thatFormatRider.CurrentElement as GenericTextRunProperties 
                    ); 
            }
 

            /// 
            /// TextFormatter to get text immediately before specified text source position.
            ///  
            /// character index to specify where in the source text the text retrieval stops.
            /// character string immediately before the specify text source character index. 
            public override TextSpan GetPrecedingText( 
                int         textSourceCharacterIndexLimit
                ) 
            {
                CharacterBufferRange charString = CharacterBufferRange.Empty;
                CultureInfo culture = null;
 
                if (textSourceCharacterIndexLimit > 0)
                { 
                    SpanRider thatFormatRider = new SpanRider( 
                        _that._formatRuns,
                        _that._latestPosition, 
                        textSourceCharacterIndexLimit - 1
                        );

                    charString = new CharacterBufferRange( 
                        new CharacterBufferReference(_that._text, thatFormatRider.CurrentSpanStart),
                        textSourceCharacterIndexLimit - thatFormatRider.CurrentSpanStart 
                        ); 

                    culture = ((TextRunProperties)thatFormatRider.CurrentElement).CultureInfo; 
                }

                return new TextSpan (
                    charString.Length, 
                    new CultureSpecificCharacterBufferRange(culture, charString)
                    ); 
            } 

            ///  
            /// TextFormatter to map a text source character index to a text effect character index
            /// 
            ///  text source character index 
            ///  the text effect index corresponding to the text effect character index  
            public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(
                int textSourceCharacterIndex 
                ) 
            {
                throw new NotSupportedException(); 
            }
        };

        #endregion 

        #region private methods 
        private void CombineGeometryRecursive(Drawing drawing, ref GeometryGroup accumulatedGeometry) 
        {
            DrawingGroup group = drawing as DrawingGroup; 
            if (group != null)
            {
                // recursively go down for DrawingGroup
                foreach (Drawing child in group.Children) 
                {
                    CombineGeometryRecursive(child, ref accumulatedGeometry); 
                } 
            }
            else 
            {
                GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing;
                if (glyphRunDrawing != null)
                { 
                    // process glyph run
                    GlyphRun glyphRun = glyphRunDrawing.GlyphRun; 
                    if (glyphRun != null) 
                    {
                        Geometry glyphRunGeometry = glyphRun.BuildGeometry(); 

                        if (!glyphRunGeometry.IsEmpty())
                        {
                            if (accumulatedGeometry == null) 
                            {
                                accumulatedGeometry = new GeometryGroup(); 
                                accumulatedGeometry.FillRule = FillRule.Nonzero; 
                            }
                            accumulatedGeometry.Children.Add(glyphRunGeometry); 
                        }
                    }
                }
                else 
                {
                    GeometryDrawing geometryDrawing = drawing as GeometryDrawing; 
                    if (geometryDrawing != null) 
                    {
                        // process geometry (i.e. TextDecoration on the line) 
                        Geometry geometry = geometryDrawing.Geometry;

                        if (geometry != null)
                        { 
                            LineGeometry lineGeometry = geometry as LineGeometry;
                            if (lineGeometry != null) 
                            { 
                                // For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no
                                // bounding area. So this line won't show up. Work aroud it by increase the Bounding rect 
                                // to be Pen's thickness

                                Rect bound  = lineGeometry.Bounds;
                                if (bound.Height == 0) 
                                {
                                    bound.Height = geometryDrawing.Pen.Thickness; 
                                } 
                                else if (bound.Width == 0)
                                { 
                                    bound.Width = geometryDrawing.Pen.Thickness;
                                }

                                // convert the line geometry into a rectangle geometry 
                                // we lost line cap info here
                                geometry = new RectangleGeometry(bound); 
                            } 
                            if (accumulatedGeometry == null)
                            { 
                                accumulatedGeometry = new GeometryGroup();
                                accumulatedGeometry.FillRule = FillRule.Nonzero;
                            }
                            accumulatedGeometry.Children.Add(geometry); 
                        }
                    } 
                } 
            }
        } 
        #endregion

        #region Private fields
 
        // properties and format runs
        private string                          _text; 
 
        private SpanVector                      _formatRuns = new SpanVector(null);
        private SpanPosition                    _latestPosition = new SpanPosition(); 

        private GenericTextParagraphProperties  _defaultParaProps;

        private double                          _maxTextWidth; 
        private double []                       _maxTextWidths;
        private double                          _maxTextHeight = double.MaxValue; 
        private int                             _maxLineCount = int.MaxValue; 
        private TextTrimming                    _trimming = TextTrimming.WordEllipsis;
 
        private TextFormattingMode              _textFormattingMode;

        // text source callbacks
        private TextSourceImplementation        _textSourceImpl; 

        // cached metrics 
        private CachedMetrics                   _metrics; 
        private double                          _minWidth;
 
        #endregion

        #region Constants
 
        const double MaxFontEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;
 
        #endregion 
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

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