SimpleTextLine.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Core / CSharp / MS / Internal / TextFormatting / SimpleTextLine.cs / 1 / SimpleTextLine.cs

                            //------------------------------------------------------------------------ 
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation, 2001
// 
//  File:      SimpleTextLine.cs
// 
//  Contents:  Light-weight implementation of TextLine 
//
//  Created:   11-7-2001 Worachai Chaoweeraprasit (wchao) 
//
//-----------------------------------------------------------------------

using System; 
using System.Security;
using System.Windows; 
using System.Windows.Media; 
using System.Windows.Media.Animation;
using System.Windows.Media.TextFormatting; 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using MS.Internal.Shaping; 

using SR=MS.Internal.PresentationCore.SR; 
using SRID=MS.Internal.PresentationCore.SRID; 

namespace MS.Internal.TextFormatting 
{
    /// 
    /// Light-weight implementation of TextLine
    /// 
    /// Support following functionalities
    ///    o    Non-complex script text metrics through font CMAP/HMTX 
    ///    o    Multiple character formats, each limited to single font face 
    ///    o    Simple text underlining for individual run (no averaging)
    /// 
    /// In the event that either the incoming text or formatting is more
    /// complicated than what this implementation can handle. The .ctor
    /// simply stops and leaves this.Valid flag unset. The caller examines
    /// this flag and lets the full path takes over if needed. 
    /// 
    internal class SimpleTextLine : TextLine 
    { 
        private SimpleRun[]             _runs;                  // contained runs
        private int                     _cpFirst;               // line first cp 
        private int                     _cpLength;              // all characters
        private int                     _cpLengthEOT;           // newline characters
        private double                  _widthAtTrailing;       // width excluding trailing space
        private double                  _width;                 // whole width 
        private double                  _paragraphWidth;        // paragraph width
        private double                  _height;                // line height 
        private double                  _offset;                // offset to the first character 
        private double                  _baselineOffset;        // offset to baseline
        private int                     _trailing;              // trailing spaces 
        private Rect                    _boundingBox;           // line bounding rectangle
        private StatusFlags             _statusFlags;           // status flags
        private FormatSettings          _settings;              // formatting settings (only kept in an overflowed line for collapsing purpose only)
 

        [Flags] 
        private enum StatusFlags 
        {
            None                = 0, 
            BoundingBoxComputed = 0x00000001,   // bounding box has been computed
            HasOverflowed       = 0x00000002,   // line width overflows paragraph width
        }
 

 
        ///  
        /// Creating a lightweight text line
        ///  
        /// text formatting settings
        /// First cp of the line
        /// paragraph width
        /// TextLine instance 
        /// 
        /// This method breaks line using Ideal width such that it will be 
        /// consistent with FullTextLine 
        /// 
        static public TextLine  Create( 
            FormatSettings          settings,
            int                     cpFirst,
            int                     paragraphWidth
            ) 
        {
            ParaProp pap = settings.Pap; 
 
            if(    pap.RightToLeft
                || pap.Justify 
                || (   pap.FirstLineInParagraph
                    && pap.TextMarkerProperties != null)
                || settings.TextIndent != 0
                || pap.ParagraphIndent != 0 
                || pap.LineHeight > 0
                || pap.AlwaysCollapsible 
                || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) 
                )
            { 
                // unsupported paragraph properties
                return null;
            }
 
            int cp = cpFirst;
 
            // paragraphWidth == 0 means the format width is unlimited. 
            int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue;
 
            SimpleRun prev = null;

            SimpleRun run = SimpleRun.Create(
                settings, 
                cp,
                cpFirst, 
                widthLeft, 
                paragraphWidth
                ); 


            if(run == null)
            { 
                // fail to create run e.g. complex content encountered
                return null; 
            } 
            else if(!run.EOT && run.IdealWidth <= widthLeft)
            { 
                // create next run
                cp += run.Length;
                widthLeft -= run.IdealWidth;
                prev = run; 

                run = SimpleRun.Create( 
                    settings, 
                    cp,
                    cpFirst, 
                    widthLeft,
                    paragraphWidth
                    );
 
                if(run == null)
                { 
                    return null; 
                }
            } 


            int trailing = 0;
            ArrayList runs = new ArrayList(2); 

            if(prev != null) 
            { 
                AddRun(runs, prev, null);
            } 

            do
            {
                if(!run.EOT && run.IdealWidth > widthLeft) 
                {
                    // linebreaking required, even simple text requires classification-based linebreaking, 
                    // we'll now let LS handle this line. 
                    return null;
                } 

                AddRun(runs, run, null);

                prev = run; 
                cp += run.Length;
                widthLeft -= run.IdealWidth; 
 
                if(run.EOT)
                { 
                    // we're done
                    break;
                }
 
                run = SimpleRun.Create(
                    settings, 
                    cp, 
                    cpFirst,
                    widthLeft, 
                    paragraphWidth
                    );

                if(    run == null 
                    || (   run.Underline != null
                        && prev != null 
                        && prev.Underline != null 
                        && !prev.IsUnderlineCompatible(run))
                    ) 
                {
                    // fail to create run or
                    // runs cannot support averaging underline
                    return null; 
                }
 
            } while(true); 

            int trailingSpaceWidth = 0; 

            CollectTrailingSpaces(
                runs,
                settings.Formatter, 
                ref trailing,
                ref trailingSpaceWidth 
                ); 

            // create a simple line 
            return new SimpleTextLine(
                settings,
                cpFirst,
                paragraphWidth, 
                runs,
                ref trailing, 
                ref trailingSpaceWidth 
                ) as TextLine;
        } 



        ///  
        /// Constructing a lightweight text line
        ///  
        /// text formatting settings 
        /// line first cp
        /// paragraph width 
        /// collection of simple runs
        /// line trailing spaces
        /// line trailing spaces width
        ///  
        /// SimpleTextLine is constructed with Ideal width such that the line breaking
        /// behavior is consistent with the FullTextLine 
        ///  
        public SimpleTextLine(
            FormatSettings          settings, 
            int                     cpFirst,
            int                     paragraphWidth,
            ArrayList               runs,
            ref int                 trailing, 
            ref int                 trailingSpaceWidth
            ) 
        { 
            // Compute line metrics
            int count = 0; 

            double realAscent = 0;
            double realDescent = 0;
            double realHeight = 0; 

            ParaProp pap = settings.Pap; 
            TextFormatterImp formatter = settings.Formatter; 

            int idealWidth = 0; 
            while(count < runs.Count)
            {
                SimpleRun run = (SimpleRun)runs[count];
 
                if(run.Length > 0)
                { 
                    if(run.EOT) 
                    {
                        // EOT run has no effect on height, it is part of trailing spaces 
                        trailing += run.Length;
                        _cpLengthEOT += run.Length;
                    }
                    else 
                    {
                        realHeight = Math.Max(realHeight, run.Height); 
                        realAscent = Math.Max(realAscent, run.Baseline); 
                        realDescent = Math.Max(realDescent, run.Height - run.Baseline);
                    } 

                    _cpLength += run.Length;
                    idealWidth += run.IdealWidth;
                } 
                count++;
            } 
 
            // Roundtrip run baseline and height to take its precision back to the specified formatting resolution.
            // 
            // We have to do this to guarantee sameness of line alignment metrics produced by fast and full path.
            // This is critical for TextBlock/TextFlow. They rely on the fact that line created during Measure must
            // yield the same metrics as one created during Render, while there is no guarantee that the paragraph
            // properties of that same line remains the same in both timings e.g. Measure may not specify 
            // justification (which results in us formatting the line in fast path), while Render might
            // (which results in us formatting that same line in full path). 
 
            _baselineOffset = formatter.IdealToReal(formatter.RealToIdeal(realAscent));
 
            if (realAscent + realDescent == realHeight)
            {
                _height = formatter.IdealToReal(formatter.RealToIdeal(realHeight));
            } 
            else
            { 
                _height = formatter.IdealToReal(formatter.RealToIdeal(realAscent) + formatter.RealToIdeal(realDescent)); 
            }
 
            if(_height <= 0)
            {
                //  line is empty (containing only EOP)
                //  we need to work out the line height 

                // It needs to be exactly the same as in full path. 
                _height = formatter.IdealToReal((int) Math.Round(pap.EmSize * pap.DefaultTypeface.LineSpacing)); 
                _baselineOffset = formatter.IdealToReal((int) Math.Round(pap.EmSize * pap.DefaultTypeface.Baseline));
            } 

            // Initialize the array of runs and set the TrimTrailingUnderline flag
            // for runs that contain trailing spaces at the end of the line.
            _runs = new SimpleRun[count]; 
            for(int i = count - 1, t = trailing; i >= 0; --i)
            { 
                SimpleRun run = (SimpleRun)runs[i]; 

                if (t > 0) 
                {
                    run.TrimTrailingUnderline = true;
                    t -= run.Length;
                } 

                _runs[i] = run; 
            } 

            _cpFirst = cpFirst; 
            _trailing = trailing;

            int idealWidthAtTrailing = idealWidth - trailingSpaceWidth;
 
            if(pap.Align != TextAlignment.Left)
            { 
                switch(pap.Align) 
                {
                    case TextAlignment.Right: 
                        _offset = formatter.IdealToReal(paragraphWidth - idealWidthAtTrailing);
                        break;
                    case TextAlignment.Center:
                        // exactly consistent with FullTextLine 
                        _offset = formatter.IdealToReal((int) Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5));
                        break; 
                } 
            }
 
            // paragraphWidth == 0 means format width is unlimited and hence not overflowable.
            // we keep paragraphWidth for alignment calculation
            if (paragraphWidth > 0 && idealWidthAtTrailing > paragraphWidth)
            { 
                // keep formatting settings for future collapsing work
                _settings = settings; 
                _statusFlags |= StatusFlags.HasOverflowed; 
            }
 
            // converting all the ideal values to real values
            _width           = formatter.IdealToReal(idealWidth);
            _widthAtTrailing = formatter.IdealToReal(idealWidthAtTrailing);
            _paragraphWidth  = formatter.IdealToReal(paragraphWidth); 
        }
 
 
        /// 
        /// Nothing to release 
        /// 
        public override void Dispose() {}

 
        /// 
        /// Scanning the run list backward to collect run's trailing spaces. 
        ///  
        /// current runs in the line
        /// formatter 
        /// trailing spaces
        /// trailing spaces width in ideal values
        static private void CollectTrailingSpaces(
            ArrayList           runs, 
            TextFormatterImp    formatter,
            ref int             trailing, 
            ref int             trailingSpaceWidth 
            )
        { 
            int left = runs != null ? runs.Count : 0;

            SimpleRun run = null;
            bool continueCollecting = true; 

            while(left > 0 && continueCollecting) 
            { 
                run = (SimpleRun)runs[--left];
 
                continueCollecting = run.CollectTrailingSpaces(
                    formatter,
                    ref trailing,
                    ref trailingSpaceWidth 
                    );
            } 
        } 

 
        /// 
        /// Collecting glyph runs
        /// 
        static private void AddRun( 
            ArrayList       runs,
            SimpleRun       run, 
            SimpleRun       prev 
            )
        { 
            Invariant.Assert(
                prev == null || (runs.Count > 0 && prev == runs[runs.Count - 1]),
                "Trailing space run is not after the last existing run!"
                ); 

            if(run.Length > 0) 
            { 
                // dont add 0-length run
                runs.Add(run); 
            }
        }

 

        ///  
        /// Get distance from line start to the specified cp 
        /// 
        private double DistanceFromCp(int currentIndex) 
        {
            Invariant.Assert(currentIndex >= _cpFirst);

            double advance = 0; 
            int dcp = currentIndex - _cpFirst;
 
            foreach(SimpleRun run in _runs) 
            {
                advance += run.DistanceFromDcp(dcp); 

                if(dcp <= run.Length)
                {
                    break; 
                }
 
                dcp -= run.Length; 
            }
 
            return advance + _offset;
        }

 

        ///  
        /// Draw line 
        /// 
        /// drawing context 
        /// drawing origin
        /// indicate the inversion of the drawing surface
        public override void Draw(
            DrawingContext      drawingContext, 
            Point               origin,
            InvertAxes          inversion 
            ) 
        {
            if (drawingContext == null) 
            {
                throw new ArgumentNullException("drawingContext");
            }
 
            MatrixTransform antiInversion = TextFormatterImp.CreateAntiInversionTransform(
                inversion, 
                _paragraphWidth, 
                _height
                ); 

            if (antiInversion == null)
            {
                DrawTextLine(drawingContext, origin); 
            }
            else 
            { 
                // Apply anti-inversion transform to correct the visual
                drawingContext.PushTransform(antiInversion); 
                try
                {
                    DrawTextLine(drawingContext, origin);
                } 
                finally
                { 
                    drawingContext.Pop(); 
                }
            } 
        }


 
        /// 
        /// Client to collapse the line to fit for display 
        ///  
        /// a list of collapsing properties
        public override TextLine Collapse( 
            params TextCollapsingProperties[]   collapsingPropertiesList
            )
        {
            if (!HasOverflowed) 
                return this;
 
            Invariant.Assert(_settings != null); 

            // instantiate a collapsible full text line, collapse it and return the collapsed line 
            TextMetrics.FullTextLine textLine = new TextMetrics.FullTextLine(
                _settings,
                _cpFirst,
                0,  // lineLength 
                _settings.Formatter.RealToIdeal(_paragraphWidth),
                LineFlags.None 
                ); 

            Invariant.Assert(textLine.HasOverflowed); 
            TextLine collapsedTextLine = textLine.Collapse(collapsingPropertiesList);
            if (collapsedTextLine != textLine)
            {
                // if collapsed line is genuinely new, 
                // Dispose its maker as we no longer need it around, dispose it explicitly
                // to reduce unnecessary finalization of this intermediate line. 
                textLine.Dispose(); 
            }
            return collapsedTextLine; 
        }


        ///  
        /// Make sure the bounding box is calculated
        ///  
        private void CheckBoundingBox() 
        {
            if ((_statusFlags & StatusFlags.BoundingBoxComputed) == 0) 
            {
                DrawTextLine(null, new Point(0, 0));
            }
            Debug.Assert((_statusFlags & StatusFlags.BoundingBoxComputed) != 0); 
        }
 
 
        /// 
        /// Draw a simple text line 
        /// 
        /// a drawing bounding box
        private void DrawTextLine(
            DrawingContext drawingContext, 
            Point          origin
            ) 
        { 
            if (_runs.Length <= 0)
            { 
                _boundingBox = Rect.Empty;
                _statusFlags |= StatusFlags.BoundingBoxComputed;
                return;
            } 

            double x = origin.X + _offset; 
            double y = origin.Y + Baseline; 

            if (drawingContext != null) 
            {
                drawingContext.PushGuidelineY1(y);
            }
 
            Rect boundingBox = Rect.Empty;
 
            try 
            {
                foreach (SimpleRun run in _runs) 
                {
                    boundingBox.Union(
                        run.Draw(
                            drawingContext, 
                            x,
                            y, 
                            false 
                            )
                        ); 

                    x += run.IdealWidth * run.ToReal;
                }
            } 
            finally
            { 
                if (drawingContext != null) 
                {
                    drawingContext.Pop(); 
                }
            }

            if(boundingBox.IsEmpty) 
            {
                boundingBox = new Rect(Start, 0, 0, 0); 
            } 
            else
            { 
                boundingBox.X -= origin.X;
                boundingBox.Y -= origin.Y;
            }
 
            _boundingBox = boundingBox;
            _statusFlags |= StatusFlags.BoundingBoxComputed; 
        } 

 

        /// 
        /// Client to get the character hit corresponding to the specified
        /// distance from the beginning of the line. 
        /// 
        /// distance in text flow direction from the beginning of the line 
        /// character hit 
        public override CharacterHit GetCharacterHitFromDistance(
            double      distance 
            )
        {
            double advance = distance - _offset;
            int first = _cpFirst; 

            if (advance < 0) 
            { 
                // hit happens before the line, return the first position
                return new CharacterHit(_cpFirst, 0); 
            }

            // process hit that happens within the line
            SimpleRun run = null; 
            CharacterHit runIndex = new CharacterHit();
 
            for(int i = 0; i < _runs.Length;  i++) 
            {
                run = (SimpleRun)_runs[i]; 

                if (!run.EOT)
                {
                    // move forward to start of next non-EOT run 
                    first += runIndex.TrailingLength;
                    runIndex = run.DcpFromDistance(advance); 
                    first += runIndex.FirstCharacterIndex; 
                }
 
                if(advance <= run.IdealWidth * run.ToReal)
                {
                    break;
                } 

                advance -= run.IdealWidth * run.ToReal; 
            } 
            return new CharacterHit(first, runIndex.TrailingLength);
        } 


        /// 
        /// Client to get the distance from the beginning of the line from the specified 
        /// character hit.
        ///  
        /// character hit of the character to query the distance. 
        /// distance in text flow direction from the beginning of the line.
        public override double GetDistanceFromCharacterHit( 
            CharacterHit    characterHit
            )
        {
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); 
            return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
        } 
 

        ///  
        /// Client to get the next character hit for caret navigation
        /// 
        /// the current character hit
        /// the next character hit 
        public override CharacterHit GetNextCaretCharacterHit(
            CharacterHit    characterHit 
            ) 
        {
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); 

            int nextVisisbleCp;
            bool navigableCpFound;
            if (characterHit.TrailingLength == 0) 
            {
                navigableCpFound = FindNextVisisbleCp(characterHit.FirstCharacterIndex, out nextVisisbleCp); 
                if (navigableCpFound) 
                {
                    // Move from leading to trailing edge 
                    return new CharacterHit(nextVisisbleCp, 1);
                }
            }
 
            navigableCpFound = FindNextVisisbleCp(characterHit.FirstCharacterIndex + 1, out nextVisisbleCp);
            if (navigableCpFound) 
            { 
                // Move from trailing edge of current character to trailing edge of next
                return new CharacterHit(nextVisisbleCp, 1); 
            }

            // Can't move, we're after the last character
            return characterHit; 
        }
 
 
        /// 
        /// Client to get the previous character hit for caret navigation 
        /// 
        /// the current character hit
        /// the previous character hit
        public override CharacterHit GetPreviousCaretCharacterHit( 
            CharacterHit    characterHit
            ) 
        { 
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength);
            int previousVisisbleCp; 
            bool navigableCpFound;

            // Input can be right after the end of the current line. Snap it to be at the end of the line.
            int cpHit = Math.Min(characterHit.FirstCharacterIndex, _cpFirst + _cpLength - 1); 
            if (characterHit.TrailingLength != 0)
            { 
                navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisisbleCp); 
                if (navigableCpFound)
                { 
                    // Move from trailing to leading edge
                    return new CharacterHit(previousVisisbleCp, 0);
                }
            } 

            navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisisbleCp); 
            if (navigableCpFound) 
            {
                // Move from leading edge of current character to leading edge of previous 
                return new CharacterHit(previousVisisbleCp, 0);
            }

            // Can't move, we're before the first character 
            return characterHit;
        } 
 

        ///  
        /// Client to get the previous character hit after backspacing
        /// 
        /// the current character hit
        /// the character hit after backspacing 
        public override CharacterHit GetBackspaceCaretCharacterHit(
            CharacterHit    characterHit 
            ) 
        {
            // same operation as move-to-previous 
            return GetPreviousCaretCharacterHit(characterHit);
        }

 
        /// 
        /// Client to get an array of bounding rectangles of a range of characters within a text line. 
        ///  
        /// index of first character of specified range
        /// number of characters of the specified range 
        /// an array of bounding rectangles.
        public override IList GetTextBounds(
            int     firstTextSourceCharacterIndex,
            int     textLength 
            )
        { 
            if (textLength == 0) 
            {
                throw new ArgumentOutOfRangeException("textLength", SR.Get(SRID.ParameterMustBeGreaterThanZero)); 
            }

            if (textLength < 0)
            { 
                firstTextSourceCharacterIndex += textLength;
                textLength = -textLength; 
            } 

            if (firstTextSourceCharacterIndex < _cpFirst) 
            {
                textLength += (firstTextSourceCharacterIndex - _cpFirst);
                firstTextSourceCharacterIndex = _cpFirst;
            } 

            if (firstTextSourceCharacterIndex + textLength > _cpFirst + _cpLength) 
            { 
                textLength = _cpFirst + _cpLength - firstTextSourceCharacterIndex;
            } 


            double x1 = GetDistanceFromCharacterHit(
                new CharacterHit(firstTextSourceCharacterIndex, 0) 
                );
 
            double x2 = GetDistanceFromCharacterHit( 
                new CharacterHit(firstTextSourceCharacterIndex + textLength, 0)
                ); 

            IList boundsList = null;
            int dcp = firstTextSourceCharacterIndex - _cpFirst;
            int ich = 0; 

            boundsList = new List(2); 
 
            foreach(SimpleRun run in _runs)
            { 
                if(     !run.EOT
                    &&  !run.Ghost
                    &&  ich + run.Length > dcp)
                { 
                    if(ich >= dcp + textLength)
                        break; 
 
                    int first = Math.Max(ich, dcp) + _cpFirst;
                    int afterLast = Math.Min(ich + run.Length, dcp + textLength) + _cpFirst; 

                    boundsList.Add(
                        new TextRunBounds(
                            new Rect( 
                                new Point(
                                    DistanceFromCp(first), 
                                    _baselineOffset - run.Baseline 
                                    ),
                                new Point( 
                                    DistanceFromCp(afterLast),
                                    _baselineOffset - run.Baseline + run.Height
                                    )
                                ), 
                            first,
                            afterLast, 
                            run.TextRun 
                            )
                        ); 
                }
                ich += run.Length;
            }
 
            return new TextBounds[]
            { 
                new TextBounds( 
                    new Rect(
                        x1, 
                        0,
                        x2 - x1,
                        _height
                        ), 
                    FlowDirection.LeftToRight,
                    (boundsList == null || boundsList.Count == 0 ? null : boundsList) 
                ) 
            };
        } 


        /// 
        /// Client to get a collection of TextRun objects within a line 
        /// 
        public override IList> GetTextRunSpans() 
        { 
            TextSpan[] textRunSpans = new TextSpan[_runs.Length];
 
            for (int i = 0; i < _runs.Length; i++)
            {
                textRunSpans[i] = new TextSpan(_runs[i].Length, _runs[i].TextRun);
            } 

            return textRunSpans; 
        } 

        ///  
        /// Client to get a IEnumerable<IndexedGlyphRun> to enumerate GlyphRuns
        /// within in a line
        /// 
        ///  
        /// Critical - calls critical code, accepts pointer parameters, unsafe code
        ///  
        [SecurityCritical] 
        public override IEnumerable GetIndexedGlyphRuns()
        { 
            List indexedGlyphRuns = new List(_runs.Length);

            // create each GlyphRun at Point(0, 0)
            Point start = new Point(0, 0); 
            int currentCp = _cpFirst;
 
            foreach(SimpleRun run in _runs) 
            {
                if (run.Length > 0 && !run.Ghost) 
                {
                    IList displayGlyphAdvances = new ThousandthOfEmRealDoubles(run.EmSize, run.NominalAdvances.Length);
                    for(int i = 0; i <  displayGlyphAdvances.Count; i++)
                    { 
                        // convert ideal glyph advance width to real width for displaying
                        displayGlyphAdvances[i] = run.NominalAdvances[i] * run.ToReal; 
                    } 

                    GlyphTypeface glyphTypeface = run.Typeface.TryGetGlyphTypeface(); 
                    Invariant.Assert(glyphTypeface != null);

                    // this simple run has GlyphRun
                    GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( 
                        start,
                        new CharacterBufferRange(run.CharBufferReference, run.Length), 
                        displayGlyphAdvances, 
                        run.EmSize,
                        run.TextRun.Properties.FontHintingEmSize, 
                        run.Typeface.NullFont,
                        CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo),
                        null   // device font name
                        ); 

                    if (glyphRun != null) 
                    { 
                        indexedGlyphRuns.Add(
                            new IndexedGlyphRun( 
                                currentCp,
                                run.Length,
                                glyphRun
                            ) 
                         );
                    } 
                } 

                currentCp += run.Length; 
            }

            return indexedGlyphRuns;
        } 

 
        ///  
        /// Client to acquire a settings at the point where line is broken by line breaking process;
        /// can be null when the line ends by the ending of the paragraph. Client may pass this 
        /// value back to TextFormatter as an input argument to TextFormatter.FormatLine when
        /// formatting the next line within the same paragraph.
        /// 
        public override TextLineBreak GetTextLineBreak() 
        {
            // No line break implemented in simple text 
            return null; 
        }
 

        /// 
        /// Client to get a collection of collapsed cha----r ranges after a line has been collapsed
        ///  
        public override IList GetTextCollapsedRanges()
        { 
            // A collapsed line is never implemented as simple text line 
            Invariant.Assert(!HasCollapsed);
            return null; 
        }

        /// 
        /// Client to get the number of text source positions of this line 
        /// 
        public override int Length 
        { 
            get { return _cpLength; }
        } 


        /// 
        /// Client to get the number of whitespace characters at the end of the line. 
        /// 
        public override int TrailingWhitespaceLength 
        { 
            get { return _trailing; }
        } 


        /// 
        /// Client to get the number of characters following the last character 
        /// of the line that may trigger reformatting of the current line.
        ///  
        public override int DependentLength 
        {
            get { return 0; } 
        }


        ///  
        /// Client to get the number of newline characters at line end
        ///  
        public override int NewlineLength 
        {
            get { return _cpLengthEOT; } 
        }


        ///  
        /// Client to get distance from paragraph start to line start
        ///  
        public override double Start 
        {
            get { return _offset; } 
        }


        ///  
        /// Client to get the total width of this line
        ///  
        public override double Width 
        {
            get { return _widthAtTrailing; } 
        }


        ///  
        /// Client to get the total width of this line including width of whitespace characters at the end of the line.
        ///  
        public override double WidthIncludingTrailingWhitespace 
        {
            get { return _width; } 
        }


        ///  
        /// Client to get the height of the line
        ///  
        public override double Height 
        {
            get { return _height; } 
        }


        ///  
        /// Client to get the height of the text (or other content) in the line; this property may differ from the Height
        /// property if the client specified the line height 
        ///  
        public override double TextHeight
        { 
            // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0
            get { return _height; }
        }
 

        ///  
        /// Client to get the height of the actual black of the line 
        /// 
        public override double Extent 
        {
            get
            {
                CheckBoundingBox(); 
                return _boundingBox.Bottom - _boundingBox.Top;
            } 
        } 

 
        /// 
        /// Client to get the distance from top to baseline of this text line
        /// 
        public override double Baseline 
        {
            get { return _baselineOffset; } 
        } 

 
        /// 
        /// Client to get the distance from the top of the text (or other content) to the baseline of this text line;
        /// this property may differ from the Baseline property if the client specified the line height
        ///  
        public override double TextBaseline
        { 
            // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 
            get { return _baselineOffset; }
        } 


        /// 
        /// Client to get the distance from the before edge of line height 
        /// to the baseline of marker of the line if any.
        ///  
        public override double MarkerBaseline 
        {
            get { return Baseline; } 
        }


        ///  
        /// Client to get the overall height of the list items marker of the line if any.
        ///  
        public override double MarkerHeight 
        {
            get { return Height; } 
        }


        ///  
        /// Client to get the distance covering all black preceding the leading edge of the line.
        ///  
        public override double OverhangLeading 
        {
            get 
            {
                CheckBoundingBox();
                return _boundingBox.Left - Start;
            } 
        }
 
 
        /// 
        /// Client to get the distance covering all black following the trailing edge of the line. 
        /// 
        public override double OverhangTrailing
        {
            get 
            {
                CheckBoundingBox(); 
                return Start + Width - _boundingBox.Right; 
            }
        } 


        /// 
        /// Client to get the distance from the after edge of line height to the after edge of the extent of the line. 
        /// 
        public override double OverhangAfter 
        { 
            get
            { 
                CheckBoundingBox();
                return _boundingBox.Bottom - Height;
            }
        } 

 
        ///  
        /// Client to get a boolean value indicates whether content of the line overflows
        /// the specified paragraph width. 
        /// 
        public override bool HasOverflowed
        {
            get { return (_statusFlags & StatusFlags.HasOverflowed) != 0; } 
        }
 
 
        /// 
        /// Client to get a boolean value indicates whether a line has been collapsed 
        /// 
        public override bool HasCollapsed
        {
            // A collapsed line is never implemented as simple text line 
            get { return false; }
        } 
 
        /// 
        /// Search forward from the given cp index (inclusive) to find the next navigable cp index. 
        /// Return true if one such cp is found, false otherwise.
        /// 
        private bool FindNextVisisbleCp(int cp, out int cpVisible)
        { 
            cpVisible = cp;
            if (cp >= _cpFirst + _cpLength) 
            { 
                return false; // Cannot go forward anymore
            } 

            int cpRunStart, runIndex;
            GetRunIndexAtCp(cp, out runIndex, out cpRunStart);
 
            while (runIndex < _runs.Length)
            { 
                // When navigating forward, only the trailing edge of visible content is 
                // navigable.
                if (_runs[runIndex].IsVisible) 
                {
                    cpVisible = Math.Max(cpRunStart, cp);
                    return true;
                } 

                cpRunStart += _runs[runIndex++].Length; 
            } 

            return false; 
        }

        /// 
        /// Search backward from the given cp index (inclusive) to find the previous navigable cp index. 
        /// Return true if one such cp is found, false otherwise.
        ///  
        private bool FindPreviousVisibleCp(int cp, out int cpVisible) 
        {
            cpVisible = cp; 
            if (cp < _cpFirst)
            {
                return false; // Cannot go backward anymore.
            } 

            int cpRunEnd, runIndex; 
            // Position the cpRunEnd at the end of the span that contains the given cp 
            GetRunIndexAtCp(cp, out runIndex, out cpRunEnd);
            cpRunEnd += _runs[runIndex].Length - 1; 

            while (runIndex >= 0)
            {
                // Visible content has caret stops at its leading edge. 
                if (_runs[runIndex].IsVisible)
                { 
                    cpVisible = Math.Min(cpRunEnd, cp); 
                    return true;
                } 

                // Newline sequence has caret stops at its leading edge.
                if (_runs[runIndex].IsNewline)
                { 
                    // Get the cp index at the beginning of the newline sequence.
                    cpVisible = cpRunEnd - _runs[runIndex].Length + 1; 
                    return true; 
                }
 
                cpRunEnd -= _runs[runIndex--].Length;
            }

            return false; 
        }
 
        private void GetRunIndexAtCp( 
            int cp,
            out int runIndex, 
            out int cpRunStart
            )
        {
            Invariant.Assert(cp >= _cpFirst && cp < _cpFirst + _cpLength); 
            cpRunStart= _cpFirst;
            runIndex = 0; 
 
            // Find the span that contains the given cp
            while (runIndex < _runs.Length && cpRunStart + _runs[runIndex].Length <= cp) 
            {
                cpRunStart += _runs[runIndex++].Length;
            }
        } 
    }
 
 
    /// 
    /// Simple text run 
    /// 
    internal sealed class SimpleRun
    {
        public CharacterBufferReference CharBufferReference;    // character buffer reference 
        public int                      Length;                 // CP length
        public int[]                    NominalAdvances;        // nominal glyph advance widths in ideal units 
        public int                      IdealWidth;             // Ideal width of the line. Use ideal width to be consistent with FullTextLine in linebreaking 
        public double                   ToReal;                 // scaling factor to convert ideal value to real value
        public TextRun                  TextRun;                // text run 
        public TextDecoration           Underline;              // only support single underline
        public Flags                    RunFlags;               // run flags

        [Flags] 
        internal enum Flags : ushort
        { 
            None                  = 0, 
            EOT                   = 0x0001,   // end-of-text mark
            Ghost                 = 0x0002,   // non-existence run - only consume cp 
            TrimTrailingUnderline = 0x0004,   // trailing whitespace should not be underlined
        }

        internal bool EOT 
        {
            get { return (RunFlags & Flags.EOT) != 0; } 
        } 

        internal bool Ghost 
        {
            get { return (RunFlags & Flags.Ghost) != 0; }
        }
 
        internal bool TrimTrailingUnderline
        { 
            get { return (RunFlags & Flags.TrimTrailingUnderline) != 0; } 
            set
            { 
                if (value)
                {
                    RunFlags |= Flags.TrimTrailingUnderline;
                } 
                else
                { 
                    RunFlags &= ~Flags.TrimTrailingUnderline; 
                }
            } 
        }

        internal double Baseline
        { 
            get
            { 
                if (Ghost || EOT) 
                    return 0;
 
                return TextRun.Properties.FontRenderingEmSize * TextRun.Properties.Typeface.Baseline;
            }
        }
 
        internal double Height
        { 
            get 
            {
                if (Ghost || EOT) 
                    return 0;

                return TextRun.Properties.FontRenderingEmSize * TextRun.Properties.Typeface.LineSpacing;
            } 
        }
 
        internal Typeface Typeface 
        {
            get { return TextRun.Properties.Typeface; } 
        }

        internal double EmSize
        { 
            get { return TextRun.Properties.FontRenderingEmSize; }
        } 
 
        internal bool IsVisible
        { 
            get { return this.TextRun is TextCharacters; }
        }

        internal bool IsNewline 
        {
            get { return this.TextRun is TextEndOfLine; } 
        } 

        internal SimpleRun() {} 


        /// 
        /// Creating a simple text run 
        /// 
        /// text formatting settings 
        /// first cp of the run 
        /// first cp of the line
        /// maxium run width 
        /// maximum column width
        /// a SimpleRun object
        static public SimpleRun Create(
            FormatSettings          settings, 
            int                     cp,
            int                     cpFirst, 
            int                     widthLeft, 
            int                     widthMax
            ) 
        {
            TextRun textRun;
            int runLength;
 
            CharacterBufferRange charBufferRange = settings.FetchTextRun(
                cp, 
                cpFirst, 
                out textRun,
                out runLength 
                );

            return Create(
                settings, 
                charBufferRange,
                textRun, 
                runLength, 
                widthLeft
                ); 
        }


 
        /// 
        /// Creating a simple text run 
        ///  
        /// text formatting settings
        /// character string associated to textrun 
        /// text run
        /// run length
        /// maximum run width
        /// a SimpleRun object 
        static public SimpleRun Create(
            FormatSettings          settings, 
            CharacterBufferRange    charString, 
            TextRun                 textRun,
            int                     runLength, 
            int                     widthLeft
            )
        {
            SimpleRun run = null; 

            if (textRun is TextCharacters) 
            { 
                if (    textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline
                    ||  (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) 
                    )
                {
                    // fast path does not handle the following conditions
                    //  o  non-default baseline alignment 
                    //  o  text drawing effect (
                    return null; 
                } 

                TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; 

                if (    textDecorations != null
                    &&  textDecorations.Count != 0
                    &&  !textDecorations.ValueEquals(TextDecorations.Underline)) 
                {
                    // we only support a single underline 
                    return null; 
                }
 
                settings.DigitState.SetTextRunProperties(textRun.Properties);
                if (settings.DigitState.RequiresNumberSubstitution)
                {
                    // don't support number substitution in fast path 
                    return null;
                } 
 
                if (charString[0] == TextStore.CharLinefeed)
                { 
                    // LF in the middle of text stream treated as explicit paragraph break
                    // simple hard line break
                    runLength = 1;
                    return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost)); 
                }
 
                // attempt to create a simple run for text 
                run = CreateSimpleTextRun(
                    charString, 
                    textRun,
                    settings.Formatter,
                    widthLeft,
                    settings.Pap.EmergencyWrap 
                    );
 
                if (run == null) 
                {
                    // fail to create simple text run, the run content is too complex 
                    return null;
                }

                // Check for underline condition 
                if (textDecorations != null && textDecorations.Count == 1 )
                { 
                    run.Underline = textDecorations[0]; 
                }
            } 
            else if (textRun is TextEndOfLine)
            {
                run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost));
            } 
            else if (textRun is TextHidden)
            { 
                // hidden run 
                run = new SimpleRun(runLength, textRun, Flags.Ghost);
            } 

            return run;
        }
 

        ///  
        /// Create simple run of text, 
        /// returning null if the specified text run cannot be correctly formatted as simple run
        ///  
        static internal SimpleRun CreateSimpleTextRun(
            CharacterBufferRange    charBufferRange,
            TextRun                 textRun,
            TextFormatterImp        formatter, 
            int                     widthLeft,
            bool                    emergencyWrap 
            ) 
        {
            Invariant.Assert(textRun is TextCharacters); 

            SimpleRun run = new SimpleRun();
            run.CharBufferReference = charBufferRange.CharacterBufferReference;
            run.TextRun = textRun; 

            if (!run.TextRun.Properties.Typeface.CheckFastPathNominalGlyphs( 
                charBufferRange, 
                run.EmSize,
                formatter.IdealToReal(widthLeft), 
                !emergencyWrap,
                false,
                CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo),
                out run.Length 
                ))
            { 
                // Getting nominal glyphs is not supported by the font, 
                // or it is but it results in low typographic quality text
                // e.g. OpenType support is not utilized. 
                return null;
            }

            run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( 
                new CharacterBufferRange(run.CharBufferReference, run.Length),
                run.EmSize, 
                formatter.ToIdeal, 
                out run.NominalAdvances,
                out run.IdealWidth 
                );

            // store the ToReal factor which would be needed to convert all the ideal values
            // in the simple run into real values for hit-testing and rendering. 
            run.ToReal = formatter.ToReal;
 
            return run; 
        }
 


        /// 
        /// Construct simple text run 
        /// 
        /// run length 
        /// text run 
        /// run flags
        private SimpleRun( 
            int         length,
            TextRun     textRun,
            Flags       flags
            ) 
        {
            Length = length; 
            TextRun = textRun; 
            RunFlags = flags;
        } 


        /// 
        /// Draw a simple run 
        /// 
        /// drawing bounding box 
        ///  
        /// Critical - as this calls critical function ComputeUnshapedGlyphRun.
        /// Safe - as this just draws text and returns a rect. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal Rect Draw(
            DrawingContext      drawingContext, 
            double              x,
            double              y, 
            bool                visiCodePath 
            )
        { 
            if (Length <= 0 || this.Ghost)
            {
                return Rect.Empty;  // nothing to draw
            } 

            Brush foregroundBrush = TextRun.Properties.ForegroundBrush; 
 
            if(visiCodePath && foregroundBrush is SolidColorBrush)
            { 
                Color color = ((SolidColorBrush)foregroundBrush).Color;
                foregroundBrush = new SolidColorBrush(Color.FromArgb(
                    (byte)(color.A>>2), // * 0.25
                    color.R, 
                    color.G,
                    color.B 
                    )); 
            }
 
            Rect inkBoundingBox;

            IList displayGlyphAdvances = new ThousandthOfEmRealDoubles(EmSize, NominalAdvances.Length);
            for(int i = 0; i <  displayGlyphAdvances.Count; i++) 
            {
                // convert ideal glyph advance width to real width for displaying. 
                displayGlyphAdvances[i] = NominalAdvances[i] * ToReal; 
            }
 
            CharacterBufferRange charBufferRange = new CharacterBufferRange(CharBufferReference, Length);

            GlyphTypeface glyphTypeface = Typeface.TryGetGlyphTypeface();
            Invariant.Assert(glyphTypeface != null); 

            GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( 
                new Point(x, y), 
                charBufferRange,
                displayGlyphAdvances, 
                EmSize,
                TextRun.Properties.FontHintingEmSize,
                Typeface.NullFont,
                CultureMapper.GetSpecificCulture(TextRun.Properties.CultureInfo), 
                null  // device font name
                ); 
 
            if (glyphRun != null)
            { 
                inkBoundingBox = glyphRun.ComputeInkBoundingBox();
            }
            else
            { 
                inkBoundingBox = Rect.Empty;
            } 
 
            if (!inkBoundingBox.IsEmpty)
            { 
                // glyph run's ink bounding box is relative to its origin
                inkBoundingBox.X += glyphRun.BaselineOrigin.X;
                inkBoundingBox.Y += glyphRun.BaselineOrigin.Y;
            } 

            if (drawingContext != null) 
            { 
                if (glyphRun != null)
                { 
                    glyphRun.EmitBackground(drawingContext, TextRun.Properties.BackgroundBrush);
                    drawingContext.DrawGlyphRun(foregroundBrush, glyphRun);
                }
 

                // draw underline here 
                if (Underline != null) 
                {
                    // Determine number of characters to underline. We don't underline trailing spaces 
                    // if the TrimTrailingUnderline flag is set.
                    int underlineLength = Length;
                    if (TrimTrailingUnderline)
                    { 
                        while (underlineLength > 0 && TextStore.IsSpace(charBufferRange[underlineLength - 1]))
                        { 
                            --underlineLength; 
                        }
                    } 

                    // Determine the width of the underline.
                    double dxUnderline = 0;
                    for (int i = 0; i < underlineLength; ++i) 
                    {
                        dxUnderline += NominalAdvances[i] * ToReal; 
                    } 

                    // We know only TextDecoration.Underline will be handled in Simple Path. 
                    double offset = -Typeface.UnderlinePosition * EmSize;
                    double penThickness = Typeface.UnderlineThickness * EmSize;

                    Point lineOrigin = new Point(x, y + offset); 

                    Rect underlineRect = new Rect( 
                            lineOrigin.X, 
                            lineOrigin.Y - penThickness * 0.5,
                            dxUnderline, 
                            penThickness
                        );

                    // Apply the pair of guidelines: one for baseline and another 
                    // for top edge of undelining line. Both will be snapped to pixel grid.
                    // Guideline pairing algorithm detects the case when these two 
                    // guidelines happen to be close to one another and provides 
                    // synchronous snapping, so that the gap between baseline and
                    // undelining line does not depend on the position of text line. 
                    drawingContext.PushGuidelineY2(y, lineOrigin.Y - penThickness * 0.5 - y);

                    try
                    { 
                        drawingContext.DrawRectangle(
                            foregroundBrush, 
                            null,               // pen 
                            underlineRect
                            ); 
                    }
                    finally
                    {
                        drawingContext.Pop(); 
                    }
 
                    // underline pen thickness is always positive in fast path 
                    inkBoundingBox.Union(
                        underlineRect 
                        );
                }
            }
 
            return inkBoundingBox;
        } 
 

        ///  
        /// Scan backward to collect trailing spaces of the run
        /// 
        /// formatter
        /// trailing spaces 
        /// trailing spaces width
        /// continue collecting the previous run? 
        internal bool CollectTrailingSpaces( 
            TextFormatterImp formatter,
            ref int          trailing, 
            ref int          trailingSpaceWidth
            )
        {
            // As we are collecting trailing space cp, we also collect the trailing space width. 
            // In Full text line, TrailingSpaceWidth = ToReal(Sumof(ToIdeal(glyphsWidths));
            // we do the same thing here so that trailing space width is exactly the same 
            // as Full Text Line. 
            if(Ghost)
            { 
                if(!EOT)
                {
                    trailing += Length;
                    trailingSpaceWidth += IdealWidth; 
                }
                return true; 
            } 

            int offsetToFirstChar = CharBufferReference.OffsetToFirstChar; 
            CharacterBuffer charBuffer = CharBufferReference.CharacterBuffer;
            int dcp = Length;

            if (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) 
            {
                // scan backward to find the first blank following a non-blank 
                while (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) 
                {
                    // summing the ideal value of each glyph 
                    trailingSpaceWidth += NominalAdvances[dcp - 1];
                    dcp--;
                    trailing++;
                } 

                return dcp == 0; 
            } 

            return false; 
        }


        internal bool IsUnderlineCompatible(SimpleRun nextRun) 
        {
            return     Typeface.Equals(nextRun.Typeface) 
                    && EmSize == nextRun.EmSize 
                    && Baseline == nextRun.Baseline;
        } 


        internal double DistanceFromDcp(int dcp)
        { 
            if (Ghost)
            { 
                return dcp <= 0 ? 0 : IdealWidth * ToReal; 
            }
 
            if (dcp > Length)
            {
                dcp = Length;
            } 

            double distance = 0; 
 
            for(int i = 0; i < dcp; i++)
            { 
                distance += NominalAdvances[i] * ToReal;
            }

            return distance; 
        }
 
 
        internal CharacterHit DcpFromDistance(double distance)
        { 
            if (Ghost)
            {
                return (EOT || distance <= 0) ? new CharacterHit() : new CharacterHit(Length, 0);
            } 

            if (Length <= 0) 
            { 
                return new CharacterHit();
            } 

            int dcp = 0;
            double currentRealAdvance = 0;
            while (dcp < Length && distance > (currentRealAdvance = NominalAdvances[dcp] * ToReal)) 
            {
                distance -= currentRealAdvance; 
                dcp++; 
            }
 
            if (dcp < Length)
            {
                // hit occurs in this run
                return new CharacterHit(dcp, (distance > currentRealAdvance / 2 ? 1 : 0)); 
            }
 
            // hit doesn't occur in this run 
            return new CharacterHit(Length - 1, 1);
        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------ 
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation, 2001
// 
//  File:      SimpleTextLine.cs
// 
//  Contents:  Light-weight implementation of TextLine 
//
//  Created:   11-7-2001 Worachai Chaoweeraprasit (wchao) 
//
//-----------------------------------------------------------------------

using System; 
using System.Security;
using System.Windows; 
using System.Windows.Media; 
using System.Windows.Media.Animation;
using System.Windows.Media.TextFormatting; 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using MS.Internal.Shaping; 

using SR=MS.Internal.PresentationCore.SR; 
using SRID=MS.Internal.PresentationCore.SRID; 

namespace MS.Internal.TextFormatting 
{
    /// 
    /// Light-weight implementation of TextLine
    /// 
    /// Support following functionalities
    ///    o    Non-complex script text metrics through font CMAP/HMTX 
    ///    o    Multiple character formats, each limited to single font face 
    ///    o    Simple text underlining for individual run (no averaging)
    /// 
    /// In the event that either the incoming text or formatting is more
    /// complicated than what this implementation can handle. The .ctor
    /// simply stops and leaves this.Valid flag unset. The caller examines
    /// this flag and lets the full path takes over if needed. 
    /// 
    internal class SimpleTextLine : TextLine 
    { 
        private SimpleRun[]             _runs;                  // contained runs
        private int                     _cpFirst;               // line first cp 
        private int                     _cpLength;              // all characters
        private int                     _cpLengthEOT;           // newline characters
        private double                  _widthAtTrailing;       // width excluding trailing space
        private double                  _width;                 // whole width 
        private double                  _paragraphWidth;        // paragraph width
        private double                  _height;                // line height 
        private double                  _offset;                // offset to the first character 
        private double                  _baselineOffset;        // offset to baseline
        private int                     _trailing;              // trailing spaces 
        private Rect                    _boundingBox;           // line bounding rectangle
        private StatusFlags             _statusFlags;           // status flags
        private FormatSettings          _settings;              // formatting settings (only kept in an overflowed line for collapsing purpose only)
 

        [Flags] 
        private enum StatusFlags 
        {
            None                = 0, 
            BoundingBoxComputed = 0x00000001,   // bounding box has been computed
            HasOverflowed       = 0x00000002,   // line width overflows paragraph width
        }
 

 
        ///  
        /// Creating a lightweight text line
        ///  
        /// text formatting settings
        /// First cp of the line
        /// paragraph width
        /// TextLine instance 
        /// 
        /// This method breaks line using Ideal width such that it will be 
        /// consistent with FullTextLine 
        /// 
        static public TextLine  Create( 
            FormatSettings          settings,
            int                     cpFirst,
            int                     paragraphWidth
            ) 
        {
            ParaProp pap = settings.Pap; 
 
            if(    pap.RightToLeft
                || pap.Justify 
                || (   pap.FirstLineInParagraph
                    && pap.TextMarkerProperties != null)
                || settings.TextIndent != 0
                || pap.ParagraphIndent != 0 
                || pap.LineHeight > 0
                || pap.AlwaysCollapsible 
                || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) 
                )
            { 
                // unsupported paragraph properties
                return null;
            }
 
            int cp = cpFirst;
 
            // paragraphWidth == 0 means the format width is unlimited. 
            int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue;
 
            SimpleRun prev = null;

            SimpleRun run = SimpleRun.Create(
                settings, 
                cp,
                cpFirst, 
                widthLeft, 
                paragraphWidth
                ); 


            if(run == null)
            { 
                // fail to create run e.g. complex content encountered
                return null; 
            } 
            else if(!run.EOT && run.IdealWidth <= widthLeft)
            { 
                // create next run
                cp += run.Length;
                widthLeft -= run.IdealWidth;
                prev = run; 

                run = SimpleRun.Create( 
                    settings, 
                    cp,
                    cpFirst, 
                    widthLeft,
                    paragraphWidth
                    );
 
                if(run == null)
                { 
                    return null; 
                }
            } 


            int trailing = 0;
            ArrayList runs = new ArrayList(2); 

            if(prev != null) 
            { 
                AddRun(runs, prev, null);
            } 

            do
            {
                if(!run.EOT && run.IdealWidth > widthLeft) 
                {
                    // linebreaking required, even simple text requires classification-based linebreaking, 
                    // we'll now let LS handle this line. 
                    return null;
                } 

                AddRun(runs, run, null);

                prev = run; 
                cp += run.Length;
                widthLeft -= run.IdealWidth; 
 
                if(run.EOT)
                { 
                    // we're done
                    break;
                }
 
                run = SimpleRun.Create(
                    settings, 
                    cp, 
                    cpFirst,
                    widthLeft, 
                    paragraphWidth
                    );

                if(    run == null 
                    || (   run.Underline != null
                        && prev != null 
                        && prev.Underline != null 
                        && !prev.IsUnderlineCompatible(run))
                    ) 
                {
                    // fail to create run or
                    // runs cannot support averaging underline
                    return null; 
                }
 
            } while(true); 

            int trailingSpaceWidth = 0; 

            CollectTrailingSpaces(
                runs,
                settings.Formatter, 
                ref trailing,
                ref trailingSpaceWidth 
                ); 

            // create a simple line 
            return new SimpleTextLine(
                settings,
                cpFirst,
                paragraphWidth, 
                runs,
                ref trailing, 
                ref trailingSpaceWidth 
                ) as TextLine;
        } 



        ///  
        /// Constructing a lightweight text line
        ///  
        /// text formatting settings 
        /// line first cp
        /// paragraph width 
        /// collection of simple runs
        /// line trailing spaces
        /// line trailing spaces width
        ///  
        /// SimpleTextLine is constructed with Ideal width such that the line breaking
        /// behavior is consistent with the FullTextLine 
        ///  
        public SimpleTextLine(
            FormatSettings          settings, 
            int                     cpFirst,
            int                     paragraphWidth,
            ArrayList               runs,
            ref int                 trailing, 
            ref int                 trailingSpaceWidth
            ) 
        { 
            // Compute line metrics
            int count = 0; 

            double realAscent = 0;
            double realDescent = 0;
            double realHeight = 0; 

            ParaProp pap = settings.Pap; 
            TextFormatterImp formatter = settings.Formatter; 

            int idealWidth = 0; 
            while(count < runs.Count)
            {
                SimpleRun run = (SimpleRun)runs[count];
 
                if(run.Length > 0)
                { 
                    if(run.EOT) 
                    {
                        // EOT run has no effect on height, it is part of trailing spaces 
                        trailing += run.Length;
                        _cpLengthEOT += run.Length;
                    }
                    else 
                    {
                        realHeight = Math.Max(realHeight, run.Height); 
                        realAscent = Math.Max(realAscent, run.Baseline); 
                        realDescent = Math.Max(realDescent, run.Height - run.Baseline);
                    } 

                    _cpLength += run.Length;
                    idealWidth += run.IdealWidth;
                } 
                count++;
            } 
 
            // Roundtrip run baseline and height to take its precision back to the specified formatting resolution.
            // 
            // We have to do this to guarantee sameness of line alignment metrics produced by fast and full path.
            // This is critical for TextBlock/TextFlow. They rely on the fact that line created during Measure must
            // yield the same metrics as one created during Render, while there is no guarantee that the paragraph
            // properties of that same line remains the same in both timings e.g. Measure may not specify 
            // justification (which results in us formatting the line in fast path), while Render might
            // (which results in us formatting that same line in full path). 
 
            _baselineOffset = formatter.IdealToReal(formatter.RealToIdeal(realAscent));
 
            if (realAscent + realDescent == realHeight)
            {
                _height = formatter.IdealToReal(formatter.RealToIdeal(realHeight));
            } 
            else
            { 
                _height = formatter.IdealToReal(formatter.RealToIdeal(realAscent) + formatter.RealToIdeal(realDescent)); 
            }
 
            if(_height <= 0)
            {
                //  line is empty (containing only EOP)
                //  we need to work out the line height 

                // It needs to be exactly the same as in full path. 
                _height = formatter.IdealToReal((int) Math.Round(pap.EmSize * pap.DefaultTypeface.LineSpacing)); 
                _baselineOffset = formatter.IdealToReal((int) Math.Round(pap.EmSize * pap.DefaultTypeface.Baseline));
            } 

            // Initialize the array of runs and set the TrimTrailingUnderline flag
            // for runs that contain trailing spaces at the end of the line.
            _runs = new SimpleRun[count]; 
            for(int i = count - 1, t = trailing; i >= 0; --i)
            { 
                SimpleRun run = (SimpleRun)runs[i]; 

                if (t > 0) 
                {
                    run.TrimTrailingUnderline = true;
                    t -= run.Length;
                } 

                _runs[i] = run; 
            } 

            _cpFirst = cpFirst; 
            _trailing = trailing;

            int idealWidthAtTrailing = idealWidth - trailingSpaceWidth;
 
            if(pap.Align != TextAlignment.Left)
            { 
                switch(pap.Align) 
                {
                    case TextAlignment.Right: 
                        _offset = formatter.IdealToReal(paragraphWidth - idealWidthAtTrailing);
                        break;
                    case TextAlignment.Center:
                        // exactly consistent with FullTextLine 
                        _offset = formatter.IdealToReal((int) Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5));
                        break; 
                } 
            }
 
            // paragraphWidth == 0 means format width is unlimited and hence not overflowable.
            // we keep paragraphWidth for alignment calculation
            if (paragraphWidth > 0 && idealWidthAtTrailing > paragraphWidth)
            { 
                // keep formatting settings for future collapsing work
                _settings = settings; 
                _statusFlags |= StatusFlags.HasOverflowed; 
            }
 
            // converting all the ideal values to real values
            _width           = formatter.IdealToReal(idealWidth);
            _widthAtTrailing = formatter.IdealToReal(idealWidthAtTrailing);
            _paragraphWidth  = formatter.IdealToReal(paragraphWidth); 
        }
 
 
        /// 
        /// Nothing to release 
        /// 
        public override void Dispose() {}

 
        /// 
        /// Scanning the run list backward to collect run's trailing spaces. 
        ///  
        /// current runs in the line
        /// formatter 
        /// trailing spaces
        /// trailing spaces width in ideal values
        static private void CollectTrailingSpaces(
            ArrayList           runs, 
            TextFormatterImp    formatter,
            ref int             trailing, 
            ref int             trailingSpaceWidth 
            )
        { 
            int left = runs != null ? runs.Count : 0;

            SimpleRun run = null;
            bool continueCollecting = true; 

            while(left > 0 && continueCollecting) 
            { 
                run = (SimpleRun)runs[--left];
 
                continueCollecting = run.CollectTrailingSpaces(
                    formatter,
                    ref trailing,
                    ref trailingSpaceWidth 
                    );
            } 
        } 

 
        /// 
        /// Collecting glyph runs
        /// 
        static private void AddRun( 
            ArrayList       runs,
            SimpleRun       run, 
            SimpleRun       prev 
            )
        { 
            Invariant.Assert(
                prev == null || (runs.Count > 0 && prev == runs[runs.Count - 1]),
                "Trailing space run is not after the last existing run!"
                ); 

            if(run.Length > 0) 
            { 
                // dont add 0-length run
                runs.Add(run); 
            }
        }

 

        ///  
        /// Get distance from line start to the specified cp 
        /// 
        private double DistanceFromCp(int currentIndex) 
        {
            Invariant.Assert(currentIndex >= _cpFirst);

            double advance = 0; 
            int dcp = currentIndex - _cpFirst;
 
            foreach(SimpleRun run in _runs) 
            {
                advance += run.DistanceFromDcp(dcp); 

                if(dcp <= run.Length)
                {
                    break; 
                }
 
                dcp -= run.Length; 
            }
 
            return advance + _offset;
        }

 

        ///  
        /// Draw line 
        /// 
        /// drawing context 
        /// drawing origin
        /// indicate the inversion of the drawing surface
        public override void Draw(
            DrawingContext      drawingContext, 
            Point               origin,
            InvertAxes          inversion 
            ) 
        {
            if (drawingContext == null) 
            {
                throw new ArgumentNullException("drawingContext");
            }
 
            MatrixTransform antiInversion = TextFormatterImp.CreateAntiInversionTransform(
                inversion, 
                _paragraphWidth, 
                _height
                ); 

            if (antiInversion == null)
            {
                DrawTextLine(drawingContext, origin); 
            }
            else 
            { 
                // Apply anti-inversion transform to correct the visual
                drawingContext.PushTransform(antiInversion); 
                try
                {
                    DrawTextLine(drawingContext, origin);
                } 
                finally
                { 
                    drawingContext.Pop(); 
                }
            } 
        }


 
        /// 
        /// Client to collapse the line to fit for display 
        ///  
        /// a list of collapsing properties
        public override TextLine Collapse( 
            params TextCollapsingProperties[]   collapsingPropertiesList
            )
        {
            if (!HasOverflowed) 
                return this;
 
            Invariant.Assert(_settings != null); 

            // instantiate a collapsible full text line, collapse it and return the collapsed line 
            TextMetrics.FullTextLine textLine = new TextMetrics.FullTextLine(
                _settings,
                _cpFirst,
                0,  // lineLength 
                _settings.Formatter.RealToIdeal(_paragraphWidth),
                LineFlags.None 
                ); 

            Invariant.Assert(textLine.HasOverflowed); 
            TextLine collapsedTextLine = textLine.Collapse(collapsingPropertiesList);
            if (collapsedTextLine != textLine)
            {
                // if collapsed line is genuinely new, 
                // Dispose its maker as we no longer need it around, dispose it explicitly
                // to reduce unnecessary finalization of this intermediate line. 
                textLine.Dispose(); 
            }
            return collapsedTextLine; 
        }


        ///  
        /// Make sure the bounding box is calculated
        ///  
        private void CheckBoundingBox() 
        {
            if ((_statusFlags & StatusFlags.BoundingBoxComputed) == 0) 
            {
                DrawTextLine(null, new Point(0, 0));
            }
            Debug.Assert((_statusFlags & StatusFlags.BoundingBoxComputed) != 0); 
        }
 
 
        /// 
        /// Draw a simple text line 
        /// 
        /// a drawing bounding box
        private void DrawTextLine(
            DrawingContext drawingContext, 
            Point          origin
            ) 
        { 
            if (_runs.Length <= 0)
            { 
                _boundingBox = Rect.Empty;
                _statusFlags |= StatusFlags.BoundingBoxComputed;
                return;
            } 

            double x = origin.X + _offset; 
            double y = origin.Y + Baseline; 

            if (drawingContext != null) 
            {
                drawingContext.PushGuidelineY1(y);
            }
 
            Rect boundingBox = Rect.Empty;
 
            try 
            {
                foreach (SimpleRun run in _runs) 
                {
                    boundingBox.Union(
                        run.Draw(
                            drawingContext, 
                            x,
                            y, 
                            false 
                            )
                        ); 

                    x += run.IdealWidth * run.ToReal;
                }
            } 
            finally
            { 
                if (drawingContext != null) 
                {
                    drawingContext.Pop(); 
                }
            }

            if(boundingBox.IsEmpty) 
            {
                boundingBox = new Rect(Start, 0, 0, 0); 
            } 
            else
            { 
                boundingBox.X -= origin.X;
                boundingBox.Y -= origin.Y;
            }
 
            _boundingBox = boundingBox;
            _statusFlags |= StatusFlags.BoundingBoxComputed; 
        } 

 

        /// 
        /// Client to get the character hit corresponding to the specified
        /// distance from the beginning of the line. 
        /// 
        /// distance in text flow direction from the beginning of the line 
        /// character hit 
        public override CharacterHit GetCharacterHitFromDistance(
            double      distance 
            )
        {
            double advance = distance - _offset;
            int first = _cpFirst; 

            if (advance < 0) 
            { 
                // hit happens before the line, return the first position
                return new CharacterHit(_cpFirst, 0); 
            }

            // process hit that happens within the line
            SimpleRun run = null; 
            CharacterHit runIndex = new CharacterHit();
 
            for(int i = 0; i < _runs.Length;  i++) 
            {
                run = (SimpleRun)_runs[i]; 

                if (!run.EOT)
                {
                    // move forward to start of next non-EOT run 
                    first += runIndex.TrailingLength;
                    runIndex = run.DcpFromDistance(advance); 
                    first += runIndex.FirstCharacterIndex; 
                }
 
                if(advance <= run.IdealWidth * run.ToReal)
                {
                    break;
                } 

                advance -= run.IdealWidth * run.ToReal; 
            } 
            return new CharacterHit(first, runIndex.TrailingLength);
        } 


        /// 
        /// Client to get the distance from the beginning of the line from the specified 
        /// character hit.
        ///  
        /// character hit of the character to query the distance. 
        /// distance in text flow direction from the beginning of the line.
        public override double GetDistanceFromCharacterHit( 
            CharacterHit    characterHit
            )
        {
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); 
            return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
        } 
 

        ///  
        /// Client to get the next character hit for caret navigation
        /// 
        /// the current character hit
        /// the next character hit 
        public override CharacterHit GetNextCaretCharacterHit(
            CharacterHit    characterHit 
            ) 
        {
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); 

            int nextVisisbleCp;
            bool navigableCpFound;
            if (characterHit.TrailingLength == 0) 
            {
                navigableCpFound = FindNextVisisbleCp(characterHit.FirstCharacterIndex, out nextVisisbleCp); 
                if (navigableCpFound) 
                {
                    // Move from leading to trailing edge 
                    return new CharacterHit(nextVisisbleCp, 1);
                }
            }
 
            navigableCpFound = FindNextVisisbleCp(characterHit.FirstCharacterIndex + 1, out nextVisisbleCp);
            if (navigableCpFound) 
            { 
                // Move from trailing edge of current character to trailing edge of next
                return new CharacterHit(nextVisisbleCp, 1); 
            }

            // Can't move, we're after the last character
            return characterHit; 
        }
 
 
        /// 
        /// Client to get the previous character hit for caret navigation 
        /// 
        /// the current character hit
        /// the previous character hit
        public override CharacterHit GetPreviousCaretCharacterHit( 
            CharacterHit    characterHit
            ) 
        { 
            TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength);
            int previousVisisbleCp; 
            bool navigableCpFound;

            // Input can be right after the end of the current line. Snap it to be at the end of the line.
            int cpHit = Math.Min(characterHit.FirstCharacterIndex, _cpFirst + _cpLength - 1); 
            if (characterHit.TrailingLength != 0)
            { 
                navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisisbleCp); 
                if (navigableCpFound)
                { 
                    // Move from trailing to leading edge
                    return new CharacterHit(previousVisisbleCp, 0);
                }
            } 

            navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisisbleCp); 
            if (navigableCpFound) 
            {
                // Move from leading edge of current character to leading edge of previous 
                return new CharacterHit(previousVisisbleCp, 0);
            }

            // Can't move, we're before the first character 
            return characterHit;
        } 
 

        ///  
        /// Client to get the previous character hit after backspacing
        /// 
        /// the current character hit
        /// the character hit after backspacing 
        public override CharacterHit GetBackspaceCaretCharacterHit(
            CharacterHit    characterHit 
            ) 
        {
            // same operation as move-to-previous 
            return GetPreviousCaretCharacterHit(characterHit);
        }

 
        /// 
        /// Client to get an array of bounding rectangles of a range of characters within a text line. 
        ///  
        /// index of first character of specified range
        /// number of characters of the specified range 
        /// an array of bounding rectangles.
        public override IList GetTextBounds(
            int     firstTextSourceCharacterIndex,
            int     textLength 
            )
        { 
            if (textLength == 0) 
            {
                throw new ArgumentOutOfRangeException("textLength", SR.Get(SRID.ParameterMustBeGreaterThanZero)); 
            }

            if (textLength < 0)
            { 
                firstTextSourceCharacterIndex += textLength;
                textLength = -textLength; 
            } 

            if (firstTextSourceCharacterIndex < _cpFirst) 
            {
                textLength += (firstTextSourceCharacterIndex - _cpFirst);
                firstTextSourceCharacterIndex = _cpFirst;
            } 

            if (firstTextSourceCharacterIndex + textLength > _cpFirst + _cpLength) 
            { 
                textLength = _cpFirst + _cpLength - firstTextSourceCharacterIndex;
            } 


            double x1 = GetDistanceFromCharacterHit(
                new CharacterHit(firstTextSourceCharacterIndex, 0) 
                );
 
            double x2 = GetDistanceFromCharacterHit( 
                new CharacterHit(firstTextSourceCharacterIndex + textLength, 0)
                ); 

            IList boundsList = null;
            int dcp = firstTextSourceCharacterIndex - _cpFirst;
            int ich = 0; 

            boundsList = new List(2); 
 
            foreach(SimpleRun run in _runs)
            { 
                if(     !run.EOT
                    &&  !run.Ghost
                    &&  ich + run.Length > dcp)
                { 
                    if(ich >= dcp + textLength)
                        break; 
 
                    int first = Math.Max(ich, dcp) + _cpFirst;
                    int afterLast = Math.Min(ich + run.Length, dcp + textLength) + _cpFirst; 

                    boundsList.Add(
                        new TextRunBounds(
                            new Rect( 
                                new Point(
                                    DistanceFromCp(first), 
                                    _baselineOffset - run.Baseline 
                                    ),
                                new Point( 
                                    DistanceFromCp(afterLast),
                                    _baselineOffset - run.Baseline + run.Height
                                    )
                                ), 
                            first,
                            afterLast, 
                            run.TextRun 
                            )
                        ); 
                }
                ich += run.Length;
            }
 
            return new TextBounds[]
            { 
                new TextBounds( 
                    new Rect(
                        x1, 
                        0,
                        x2 - x1,
                        _height
                        ), 
                    FlowDirection.LeftToRight,
                    (boundsList == null || boundsList.Count == 0 ? null : boundsList) 
                ) 
            };
        } 


        /// 
        /// Client to get a collection of TextRun objects within a line 
        /// 
        public override IList> GetTextRunSpans() 
        { 
            TextSpan[] textRunSpans = new TextSpan[_runs.Length];
 
            for (int i = 0; i < _runs.Length; i++)
            {
                textRunSpans[i] = new TextSpan(_runs[i].Length, _runs[i].TextRun);
            } 

            return textRunSpans; 
        } 

        ///  
        /// Client to get a IEnumerable<IndexedGlyphRun> to enumerate GlyphRuns
        /// within in a line
        /// 
        ///  
        /// Critical - calls critical code, accepts pointer parameters, unsafe code
        ///  
        [SecurityCritical] 
        public override IEnumerable GetIndexedGlyphRuns()
        { 
            List indexedGlyphRuns = new List(_runs.Length);

            // create each GlyphRun at Point(0, 0)
            Point start = new Point(0, 0); 
            int currentCp = _cpFirst;
 
            foreach(SimpleRun run in _runs) 
            {
                if (run.Length > 0 && !run.Ghost) 
                {
                    IList displayGlyphAdvances = new ThousandthOfEmRealDoubles(run.EmSize, run.NominalAdvances.Length);
                    for(int i = 0; i <  displayGlyphAdvances.Count; i++)
                    { 
                        // convert ideal glyph advance width to real width for displaying
                        displayGlyphAdvances[i] = run.NominalAdvances[i] * run.ToReal; 
                    } 

                    GlyphTypeface glyphTypeface = run.Typeface.TryGetGlyphTypeface(); 
                    Invariant.Assert(glyphTypeface != null);

                    // this simple run has GlyphRun
                    GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( 
                        start,
                        new CharacterBufferRange(run.CharBufferReference, run.Length), 
                        displayGlyphAdvances, 
                        run.EmSize,
                        run.TextRun.Properties.FontHintingEmSize, 
                        run.Typeface.NullFont,
                        CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo),
                        null   // device font name
                        ); 

                    if (glyphRun != null) 
                    { 
                        indexedGlyphRuns.Add(
                            new IndexedGlyphRun( 
                                currentCp,
                                run.Length,
                                glyphRun
                            ) 
                         );
                    } 
                } 

                currentCp += run.Length; 
            }

            return indexedGlyphRuns;
        } 

 
        ///  
        /// Client to acquire a settings at the point where line is broken by line breaking process;
        /// can be null when the line ends by the ending of the paragraph. Client may pass this 
        /// value back to TextFormatter as an input argument to TextFormatter.FormatLine when
        /// formatting the next line within the same paragraph.
        /// 
        public override TextLineBreak GetTextLineBreak() 
        {
            // No line break implemented in simple text 
            return null; 
        }
 

        /// 
        /// Client to get a collection of collapsed cha----r ranges after a line has been collapsed
        ///  
        public override IList GetTextCollapsedRanges()
        { 
            // A collapsed line is never implemented as simple text line 
            Invariant.Assert(!HasCollapsed);
            return null; 
        }

        /// 
        /// Client to get the number of text source positions of this line 
        /// 
        public override int Length 
        { 
            get { return _cpLength; }
        } 


        /// 
        /// Client to get the number of whitespace characters at the end of the line. 
        /// 
        public override int TrailingWhitespaceLength 
        { 
            get { return _trailing; }
        } 


        /// 
        /// Client to get the number of characters following the last character 
        /// of the line that may trigger reformatting of the current line.
        ///  
        public override int DependentLength 
        {
            get { return 0; } 
        }


        ///  
        /// Client to get the number of newline characters at line end
        ///  
        public override int NewlineLength 
        {
            get { return _cpLengthEOT; } 
        }


        ///  
        /// Client to get distance from paragraph start to line start
        ///  
        public override double Start 
        {
            get { return _offset; } 
        }


        ///  
        /// Client to get the total width of this line
        ///  
        public override double Width 
        {
            get { return _widthAtTrailing; } 
        }


        ///  
        /// Client to get the total width of this line including width of whitespace characters at the end of the line.
        ///  
        public override double WidthIncludingTrailingWhitespace 
        {
            get { return _width; } 
        }


        ///  
        /// Client to get the height of the line
        ///  
        public override double Height 
        {
            get { return _height; } 
        }


        ///  
        /// Client to get the height of the text (or other content) in the line; this property may differ from the Height
        /// property if the client specified the line height 
        ///  
        public override double TextHeight
        { 
            // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0
            get { return _height; }
        }
 

        ///  
        /// Client to get the height of the actual black of the line 
        /// 
        public override double Extent 
        {
            get
            {
                CheckBoundingBox(); 
                return _boundingBox.Bottom - _boundingBox.Top;
            } 
        } 

 
        /// 
        /// Client to get the distance from top to baseline of this text line
        /// 
        public override double Baseline 
        {
            get { return _baselineOffset; } 
        } 

 
        /// 
        /// Client to get the distance from the top of the text (or other content) to the baseline of this text line;
        /// this property may differ from the Baseline property if the client specified the line height
        ///  
        public override double TextBaseline
        { 
            // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 
            get { return _baselineOffset; }
        } 


        /// 
        /// Client to get the distance from the before edge of line height 
        /// to the baseline of marker of the line if any.
        ///  
        public override double MarkerBaseline 
        {
            get { return Baseline; } 
        }


        ///  
        /// Client to get the overall height of the list items marker of the line if any.
        ///  
        public override double MarkerHeight 
        {
            get { return Height; } 
        }


        ///  
        /// Client to get the distance covering all black preceding the leading edge of the line.
        ///  
        public override double OverhangLeading 
        {
            get 
            {
                CheckBoundingBox();
                return _boundingBox.Left - Start;
            } 
        }
 
 
        /// 
        /// Client to get the distance covering all black following the trailing edge of the line. 
        /// 
        public override double OverhangTrailing
        {
            get 
            {
                CheckBoundingBox(); 
                return Start + Width - _boundingBox.Right; 
            }
        } 


        /// 
        /// Client to get the distance from the after edge of line height to the after edge of the extent of the line. 
        /// 
        public override double OverhangAfter 
        { 
            get
            { 
                CheckBoundingBox();
                return _boundingBox.Bottom - Height;
            }
        } 

 
        ///  
        /// Client to get a boolean value indicates whether content of the line overflows
        /// the specified paragraph width. 
        /// 
        public override bool HasOverflowed
        {
            get { return (_statusFlags & StatusFlags.HasOverflowed) != 0; } 
        }
 
 
        /// 
        /// Client to get a boolean value indicates whether a line has been collapsed 
        /// 
        public override bool HasCollapsed
        {
            // A collapsed line is never implemented as simple text line 
            get { return false; }
        } 
 
        /// 
        /// Search forward from the given cp index (inclusive) to find the next navigable cp index. 
        /// Return true if one such cp is found, false otherwise.
        /// 
        private bool FindNextVisisbleCp(int cp, out int cpVisible)
        { 
            cpVisible = cp;
            if (cp >= _cpFirst + _cpLength) 
            { 
                return false; // Cannot go forward anymore
            } 

            int cpRunStart, runIndex;
            GetRunIndexAtCp(cp, out runIndex, out cpRunStart);
 
            while (runIndex < _runs.Length)
            { 
                // When navigating forward, only the trailing edge of visible content is 
                // navigable.
                if (_runs[runIndex].IsVisible) 
                {
                    cpVisible = Math.Max(cpRunStart, cp);
                    return true;
                } 

                cpRunStart += _runs[runIndex++].Length; 
            } 

            return false; 
        }

        /// 
        /// Search backward from the given cp index (inclusive) to find the previous navigable cp index. 
        /// Return true if one such cp is found, false otherwise.
        ///  
        private bool FindPreviousVisibleCp(int cp, out int cpVisible) 
        {
            cpVisible = cp; 
            if (cp < _cpFirst)
            {
                return false; // Cannot go backward anymore.
            } 

            int cpRunEnd, runIndex; 
            // Position the cpRunEnd at the end of the span that contains the given cp 
            GetRunIndexAtCp(cp, out runIndex, out cpRunEnd);
            cpRunEnd += _runs[runIndex].Length - 1; 

            while (runIndex >= 0)
            {
                // Visible content has caret stops at its leading edge. 
                if (_runs[runIndex].IsVisible)
                { 
                    cpVisible = Math.Min(cpRunEnd, cp); 
                    return true;
                } 

                // Newline sequence has caret stops at its leading edge.
                if (_runs[runIndex].IsNewline)
                { 
                    // Get the cp index at the beginning of the newline sequence.
                    cpVisible = cpRunEnd - _runs[runIndex].Length + 1; 
                    return true; 
                }
 
                cpRunEnd -= _runs[runIndex--].Length;
            }

            return false; 
        }
 
        private void GetRunIndexAtCp( 
            int cp,
            out int runIndex, 
            out int cpRunStart
            )
        {
            Invariant.Assert(cp >= _cpFirst && cp < _cpFirst + _cpLength); 
            cpRunStart= _cpFirst;
            runIndex = 0; 
 
            // Find the span that contains the given cp
            while (runIndex < _runs.Length && cpRunStart + _runs[runIndex].Length <= cp) 
            {
                cpRunStart += _runs[runIndex++].Length;
            }
        } 
    }
 
 
    /// 
    /// Simple text run 
    /// 
    internal sealed class SimpleRun
    {
        public CharacterBufferReference CharBufferReference;    // character buffer reference 
        public int                      Length;                 // CP length
        public int[]                    NominalAdvances;        // nominal glyph advance widths in ideal units 
        public int                      IdealWidth;             // Ideal width of the line. Use ideal width to be consistent with FullTextLine in linebreaking 
        public double                   ToReal;                 // scaling factor to convert ideal value to real value
        public TextRun                  TextRun;                // text run 
        public TextDecoration           Underline;              // only support single underline
        public Flags                    RunFlags;               // run flags

        [Flags] 
        internal enum Flags : ushort
        { 
            None                  = 0, 
            EOT                   = 0x0001,   // end-of-text mark
            Ghost                 = 0x0002,   // non-existence run - only consume cp 
            TrimTrailingUnderline = 0x0004,   // trailing whitespace should not be underlined
        }

        internal bool EOT 
        {
            get { return (RunFlags & Flags.EOT) != 0; } 
        } 

        internal bool Ghost 
        {
            get { return (RunFlags & Flags.Ghost) != 0; }
        }
 
        internal bool TrimTrailingUnderline
        { 
            get { return (RunFlags & Flags.TrimTrailingUnderline) != 0; } 
            set
            { 
                if (value)
                {
                    RunFlags |= Flags.TrimTrailingUnderline;
                } 
                else
                { 
                    RunFlags &= ~Flags.TrimTrailingUnderline; 
                }
            } 
        }

        internal double Baseline
        { 
            get
            { 
                if (Ghost || EOT) 
                    return 0;
 
                return TextRun.Properties.FontRenderingEmSize * TextRun.Properties.Typeface.Baseline;
            }
        }
 
        internal double Height
        { 
            get 
            {
                if (Ghost || EOT) 
                    return 0;

                return TextRun.Properties.FontRenderingEmSize * TextRun.Properties.Typeface.LineSpacing;
            } 
        }
 
        internal Typeface Typeface 
        {
            get { return TextRun.Properties.Typeface; } 
        }

        internal double EmSize
        { 
            get { return TextRun.Properties.FontRenderingEmSize; }
        } 
 
        internal bool IsVisible
        { 
            get { return this.TextRun is TextCharacters; }
        }

        internal bool IsNewline 
        {
            get { return this.TextRun is TextEndOfLine; } 
        } 

        internal SimpleRun() {} 


        /// 
        /// Creating a simple text run 
        /// 
        /// text formatting settings 
        /// first cp of the run 
        /// first cp of the line
        /// maxium run width 
        /// maximum column width
        /// a SimpleRun object
        static public SimpleRun Create(
            FormatSettings          settings, 
            int                     cp,
            int                     cpFirst, 
            int                     widthLeft, 
            int                     widthMax
            ) 
        {
            TextRun textRun;
            int runLength;
 
            CharacterBufferRange charBufferRange = settings.FetchTextRun(
                cp, 
                cpFirst, 
                out textRun,
                out runLength 
                );

            return Create(
                settings, 
                charBufferRange,
                textRun, 
                runLength, 
                widthLeft
                ); 
        }


 
        /// 
        /// Creating a simple text run 
        ///  
        /// text formatting settings
        /// character string associated to textrun 
        /// text run
        /// run length
        /// maximum run width
        /// a SimpleRun object 
        static public SimpleRun Create(
            FormatSettings          settings, 
            CharacterBufferRange    charString, 
            TextRun                 textRun,
            int                     runLength, 
            int                     widthLeft
            )
        {
            SimpleRun run = null; 

            if (textRun is TextCharacters) 
            { 
                if (    textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline
                    ||  (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) 
                    )
                {
                    // fast path does not handle the following conditions
                    //  o  non-default baseline alignment 
                    //  o  text drawing effect (
                    return null; 
                } 

                TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; 

                if (    textDecorations != null
                    &&  textDecorations.Count != 0
                    &&  !textDecorations.ValueEquals(TextDecorations.Underline)) 
                {
                    // we only support a single underline 
                    return null; 
                }
 
                settings.DigitState.SetTextRunProperties(textRun.Properties);
                if (settings.DigitState.RequiresNumberSubstitution)
                {
                    // don't support number substitution in fast path 
                    return null;
                } 
 
                if (charString[0] == TextStore.CharLinefeed)
                { 
                    // LF in the middle of text stream treated as explicit paragraph break
                    // simple hard line break
                    runLength = 1;
                    return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost)); 
                }
 
                // attempt to create a simple run for text 
                run = CreateSimpleTextRun(
                    charString, 
                    textRun,
                    settings.Formatter,
                    widthLeft,
                    settings.Pap.EmergencyWrap 
                    );
 
                if (run == null) 
                {
                    // fail to create simple text run, the run content is too complex 
                    return null;
                }

                // Check for underline condition 
                if (textDecorations != null && textDecorations.Count == 1 )
                { 
                    run.Underline = textDecorations[0]; 
                }
            } 
            else if (textRun is TextEndOfLine)
            {
                run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost));
            } 
            else if (textRun is TextHidden)
            { 
                // hidden run 
                run = new SimpleRun(runLength, textRun, Flags.Ghost);
            } 

            return run;
        }
 

        ///  
        /// Create simple run of text, 
        /// returning null if the specified text run cannot be correctly formatted as simple run
        ///  
        static internal SimpleRun CreateSimpleTextRun(
            CharacterBufferRange    charBufferRange,
            TextRun                 textRun,
            TextFormatterImp        formatter, 
            int                     widthLeft,
            bool                    emergencyWrap 
            ) 
        {
            Invariant.Assert(textRun is TextCharacters); 

            SimpleRun run = new SimpleRun();
            run.CharBufferReference = charBufferRange.CharacterBufferReference;
            run.TextRun = textRun; 

            if (!run.TextRun.Properties.Typeface.CheckFastPathNominalGlyphs( 
                charBufferRange, 
                run.EmSize,
                formatter.IdealToReal(widthLeft), 
                !emergencyWrap,
                false,
                CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo),
                out run.Length 
                ))
            { 
                // Getting nominal glyphs is not supported by the font, 
                // or it is but it results in low typographic quality text
                // e.g. OpenType support is not utilized. 
                return null;
            }

            run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( 
                new CharacterBufferRange(run.CharBufferReference, run.Length),
                run.EmSize, 
                formatter.ToIdeal, 
                out run.NominalAdvances,
                out run.IdealWidth 
                );

            // store the ToReal factor which would be needed to convert all the ideal values
            // in the simple run into real values for hit-testing and rendering. 
            run.ToReal = formatter.ToReal;
 
            return run; 
        }
 


        /// 
        /// Construct simple text run 
        /// 
        /// run length 
        /// text run 
        /// run flags
        private SimpleRun( 
            int         length,
            TextRun     textRun,
            Flags       flags
            ) 
        {
            Length = length; 
            TextRun = textRun; 
            RunFlags = flags;
        } 


        /// 
        /// Draw a simple run 
        /// 
        /// drawing bounding box 
        ///  
        /// Critical - as this calls critical function ComputeUnshapedGlyphRun.
        /// Safe - as this just draws text and returns a rect. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal Rect Draw(
            DrawingContext      drawingContext, 
            double              x,
            double              y, 
            bool                visiCodePath 
            )
        { 
            if (Length <= 0 || this.Ghost)
            {
                return Rect.Empty;  // nothing to draw
            } 

            Brush foregroundBrush = TextRun.Properties.ForegroundBrush; 
 
            if(visiCodePath && foregroundBrush is SolidColorBrush)
            { 
                Color color = ((SolidColorBrush)foregroundBrush).Color;
                foregroundBrush = new SolidColorBrush(Color.FromArgb(
                    (byte)(color.A>>2), // * 0.25
                    color.R, 
                    color.G,
                    color.B 
                    )); 
            }
 
            Rect inkBoundingBox;

            IList displayGlyphAdvances = new ThousandthOfEmRealDoubles(EmSize, NominalAdvances.Length);
            for(int i = 0; i <  displayGlyphAdvances.Count; i++) 
            {
                // convert ideal glyph advance width to real width for displaying. 
                displayGlyphAdvances[i] = NominalAdvances[i] * ToReal; 
            }
 
            CharacterBufferRange charBufferRange = new CharacterBufferRange(CharBufferReference, Length);

            GlyphTypeface glyphTypeface = Typeface.TryGetGlyphTypeface();
            Invariant.Assert(glyphTypeface != null); 

            GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( 
                new Point(x, y), 
                charBufferRange,
                displayGlyphAdvances, 
                EmSize,
                TextRun.Properties.FontHintingEmSize,
                Typeface.NullFont,
                CultureMapper.GetSpecificCulture(TextRun.Properties.CultureInfo), 
                null  // device font name
                ); 
 
            if (glyphRun != null)
            { 
                inkBoundingBox = glyphRun.ComputeInkBoundingBox();
            }
            else
            { 
                inkBoundingBox = Rect.Empty;
            } 
 
            if (!inkBoundingBox.IsEmpty)
            { 
                // glyph run's ink bounding box is relative to its origin
                inkBoundingBox.X += glyphRun.BaselineOrigin.X;
                inkBoundingBox.Y += glyphRun.BaselineOrigin.Y;
            } 

            if (drawingContext != null) 
            { 
                if (glyphRun != null)
                { 
                    glyphRun.EmitBackground(drawingContext, TextRun.Properties.BackgroundBrush);
                    drawingContext.DrawGlyphRun(foregroundBrush, glyphRun);
                }
 

                // draw underline here 
                if (Underline != null) 
                {
                    // Determine number of characters to underline. We don't underline trailing spaces 
                    // if the TrimTrailingUnderline flag is set.
                    int underlineLength = Length;
                    if (TrimTrailingUnderline)
                    { 
                        while (underlineLength > 0 && TextStore.IsSpace(charBufferRange[underlineLength - 1]))
                        { 
                            --underlineLength; 
                        }
                    } 

                    // Determine the width of the underline.
                    double dxUnderline = 0;
                    for (int i = 0; i < underlineLength; ++i) 
                    {
                        dxUnderline += NominalAdvances[i] * ToReal; 
                    } 

                    // We know only TextDecoration.Underline will be handled in Simple Path. 
                    double offset = -Typeface.UnderlinePosition * EmSize;
                    double penThickness = Typeface.UnderlineThickness * EmSize;

                    Point lineOrigin = new Point(x, y + offset); 

                    Rect underlineRect = new Rect( 
                            lineOrigin.X, 
                            lineOrigin.Y - penThickness * 0.5,
                            dxUnderline, 
                            penThickness
                        );

                    // Apply the pair of guidelines: one for baseline and another 
                    // for top edge of undelining line. Both will be snapped to pixel grid.
                    // Guideline pairing algorithm detects the case when these two 
                    // guidelines happen to be close to one another and provides 
                    // synchronous snapping, so that the gap between baseline and
                    // undelining line does not depend on the position of text line. 
                    drawingContext.PushGuidelineY2(y, lineOrigin.Y - penThickness * 0.5 - y);

                    try
                    { 
                        drawingContext.DrawRectangle(
                            foregroundBrush, 
                            null,               // pen 
                            underlineRect
                            ); 
                    }
                    finally
                    {
                        drawingContext.Pop(); 
                    }
 
                    // underline pen thickness is always positive in fast path 
                    inkBoundingBox.Union(
                        underlineRect 
                        );
                }
            }
 
            return inkBoundingBox;
        } 
 

        ///  
        /// Scan backward to collect trailing spaces of the run
        /// 
        /// formatter
        /// trailing spaces 
        /// trailing spaces width
        /// continue collecting the previous run? 
        internal bool CollectTrailingSpaces( 
            TextFormatterImp formatter,
            ref int          trailing, 
            ref int          trailingSpaceWidth
            )
        {
            // As we are collecting trailing space cp, we also collect the trailing space width. 
            // In Full text line, TrailingSpaceWidth = ToReal(Sumof(ToIdeal(glyphsWidths));
            // we do the same thing here so that trailing space width is exactly the same 
            // as Full Text Line. 
            if(Ghost)
            { 
                if(!EOT)
                {
                    trailing += Length;
                    trailingSpaceWidth += IdealWidth; 
                }
                return true; 
            } 

            int offsetToFirstChar = CharBufferReference.OffsetToFirstChar; 
            CharacterBuffer charBuffer = CharBufferReference.CharacterBuffer;
            int dcp = Length;

            if (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) 
            {
                // scan backward to find the first blank following a non-blank 
                while (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) 
                {
                    // summing the ideal value of each glyph 
                    trailingSpaceWidth += NominalAdvances[dcp - 1];
                    dcp--;
                    trailing++;
                } 

                return dcp == 0; 
            } 

            return false; 
        }


        internal bool IsUnderlineCompatible(SimpleRun nextRun) 
        {
            return     Typeface.Equals(nextRun.Typeface) 
                    && EmSize == nextRun.EmSize 
                    && Baseline == nextRun.Baseline;
        } 


        internal double DistanceFromDcp(int dcp)
        { 
            if (Ghost)
            { 
                return dcp <= 0 ? 0 : IdealWidth * ToReal; 
            }
 
            if (dcp > Length)
            {
                dcp = Length;
            } 

            double distance = 0; 
 
            for(int i = 0; i < dcp; i++)
            { 
                distance += NominalAdvances[i] * ToReal;
            }

            return distance; 
        }
 
 
        internal CharacterHit DcpFromDistance(double distance)
        { 
            if (Ghost)
            {
                return (EOT || distance <= 0) ? new CharacterHit() : new CharacterHit(Length, 0);
            } 

            if (Length <= 0) 
            { 
                return new CharacterHit();
            } 

            int dcp = 0;
            double currentRealAdvance = 0;
            while (dcp < Length && distance > (currentRealAdvance = NominalAdvances[dcp] * ToReal)) 
            {
                distance -= currentRealAdvance; 
                dcp++; 
            }
 
            if (dcp < Length)
            {
                // hit occurs in this run
                return new CharacterHit(dcp, (distance > currentRealAdvance / 2 ? 1 : 0)); 
            }
 
            // hit doesn't occur in this run 
            return new CharacterHit(Length - 1, 1);
        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

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