TextStore.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 / TextStore.cs / 1 / TextStore.cs

                            //------------------------------------------------------------------------ 
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation
// 
//  File:      TextStore.cs
// 
//  Contents:  FullTextLine text store 
//
//  Created:   10-3-2002 Worachai Chaoweeraprasit (wchao) 
//
//-----------------------------------------------------------------------

 
using System;
using System.Text; 
using System.Globalization; 
using System.Windows;
using System.Windows.Media; 
using System.Windows.Media.TextFormatting;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; 
using System.Runtime.InteropServices;
using MS.Internal.Shaping; 
using MS.Internal.Generic; 
using System.Security;
using System.Security.Permissions; 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;

namespace MS.Internal.TextFormatting 
{
    ///  
    /// FullTextLine text store 
    /// 
    ///  
    ///
    ///     Text store produces and keeps 'lsrun' of different type. Each type has its own
    ///     characteristics and each lsrun involves with three different 'character length'
    ///     values created and used for different purpose by different component. 
    ///
    ///     plsrunSpan.length:  character length created by text store used by LS. 
    ///     lsrun.Length:       character length assigned and used by TextSource. 
    ///     lsrun.StringLength: character length created by text store used by bidi algorithm.
    /// 
    ///
    ///                     plsrunSpan.length  lsrun.Length  lsrun.StringLength
    ///     CloseAnchor             1               0               1
    ///     Reverse                 1               0               1 
    ///     FakeLineBreak           1               0               1
    ///     FormatAnchor            1               1               1 
    ///     Hidden                  n               n               1 
    ///     Text                    n               n               n
    ///     InlineObject            1               n               1 
    ///     LineBreak               1               n               1
    ///
    ///     FakeLineBreak is used to chop off a line by simulating a line break when
    ///     none is actually present in the backing store. This is done as a security 
    ///     mitigation for malicious text where we might otherwise spend too much time
    ///     formatting a line. The IsForceBreakRequired method contains the mitigation 
    ///     logic; see also the comment for MaxCharactersPerLine. 
    ///
    ///  
    internal class TextStore
    {
        private FormatSettings          _settings;                  // format settings
        private int                     _lscpFirstValue;            // first lscp value 
        private int                     _cpFirst;                   // store first cp (both cp and lscp start out the same)
        private int                     _lscchUpTo;                 // number of lscp resolved 
        private int                     _cchUpTo;                   // number of cp resolved 
        private int                     _cchEol;                    // number of chars for end-of-line mark
        private int                     _accNominalWidthSoFar;      // accumulated nominal width so far 
        private int                     _accTextLengthSoFar;        // accumulated count of text characters so far
        private NumberContext           _numberContext;             // cached number context for contextual digit substitution
        private int                     _cpNumberContext;           // cp at which _numberContext is valid
 
        private SpanVector              _plsrunVector;
        private SpanPosition            _plsrunVectorLatestPosition; 
        private ArrayList               _lsrunList;                 // lsrun list 
        private BidiState               _bidiState;                 // (defer initialization until FetchRun)
        private TextModifierScope       _modifierScope;             // top-most frame of the text modifier stack, or null 

        private int                     _formatWidth;               // formatting width LS sees
        private SpanVector              _textObjectMetricsVector;   // inline object cache
 
        internal static LSRun[]          ControlRuns;               // Control text runs e.g. Bidi reversal
 
 
        /// 
        /// Initialize all static members 
        /// 
        static TextStore()
        {
            EscStringInfo esc = new EscStringInfo(); 

            UnsafeNativeMethods.LoGetEscString(ref esc); 
 
            ControlRuns = new LSRun[3];
 
            ControlRuns[0] = new LSRun(Plsrun.CloseAnchor, esc.szObjectTerminator);
            ControlRuns[1] = new LSRun(Plsrun.Reverse, esc.szObjectReplacement);
            ControlRuns[2] = new LSRun(Plsrun.FakeLineBreak, esc.szLineSeparator);
 
            PwchNbsp              = esc.szNbsp;
            PwchHidden            = esc.szHidden; 
            PwchParaSeparator     = esc.szParaSeparator; 
            PwchLineSeparator     = esc.szLineSeparator;
            PwchObjectReplacement = esc.szObjectReplacement; 
            PwchObjectTerminator  = esc.szObjectTerminator;
        }

 
        /// 
        /// Constructing an intermediate text store for FullTextLine 
        ///  
        /// text formatting settings
        /// first cp of the line 
        /// lscp first value
        /// formatting width LS sees
        public TextStore(
            FormatSettings          settings, 
            int                     cpFirst,
            int                     lscpFirstValue, 
            int                     formatWidth 
            )
        { 
            _settings = settings;
            _formatWidth = formatWidth;

            _cpFirst = cpFirst; 
            _lscpFirstValue = lscpFirstValue;
 
            _lsrunList = new ArrayList(2); 
            _plsrunVector = new SpanVector(null);
            _plsrunVectorLatestPosition = new SpanPosition(); 

            // Recreate the stack of text modifiers if there is one.
            TextLineBreak previousLineBreak = settings.PreviousLineBreak;
 
            if (    previousLineBreak != null
                &&  previousLineBreak.TextModifierScope != null) 
            { 
                _modifierScope = previousLineBreak.TextModifierScope.CloneStack();
 
                // Construct bidi state from input settings and modifier scopes
                _bidiState = new BidiState(_settings, _cpFirst, _modifierScope);
            }
        } 

 
        ///  
        /// Fetch lsrun at the specified LSCP
        ///  
        /// lscp to fetch
        /// plsrun of lsrun being fetched
        /// offset from the start of the LSRun to the specified lscp
        /// distance from the specified lscp to the end of the LSRun 
        /// lsrun being fetched
        internal LSRun FetchLSRun( 
            int             lscpFetch, 
            out Plsrun      plsrun,
            out int         lsrunOffset, 
            out int         lsrunLength
            )
        {
            lscpFetch -= _lscpFirstValue; 

            Invariant.Assert(lscpFetch >= _cpFirst); 
 
            if (_cpFirst + _lscchUpTo <= lscpFetch)
            { 
                ushort charFlagsSoFar = 0;
                ushort bidiCharFlagsSoFar = 0;
                int cchResolved = 0;
                int cchFetched = _cchUpTo; 
                int cch = 0;
                int cchText = 0; 
 
                SpanVector runInfoVector     = new SpanVector(null);
                SpanVector textEffectsVector = new SpanVector(null); 
                byte[] bidiLevels = null;
                int lastBidiLevel = GetLastLevel();

                // Fetch runs until enough characters get resolved by bidi algorithm 
                do
                { 
                    // Read runs up ahead to the point where accumulated run width exceeds the upper limit value. 
                    // We do this to optimize the cost of breaking down lsruns by doing as much as we can in
                    // one go, thus generating smaller setup cost of run fetching. 

                    TextRunInfo runInfo;

                    do 
                    {
                        runInfo = FetchTextRun(_cpFirst + cchFetched); 
 
                        if (runInfo == null)
                        { 
                            // no more content to fetch
                            break;
                        }
 
                        if (  _bidiState == null
                            && 
                               (   IsDirectionalModifier(runInfo.TextRun as TextModifier) 
                                || IsEndOfDirectionalModifier(runInfo)
                               ) 
                            )
                        {
                            // When directional embedding TextModifier or corresponding TextEndOfSegment
                            // is encountered, we need to do bidi analysis to correctly update the bidi state. 
                            // We create a bidi state to trigger bidi analysis.
                            _bidiState = new BidiState(_settings, _cpFirst); 
                        } 

                        int stringLength = runInfo.StringLength; 

                        if(runInfo.TextRun is ITextSymbols)
                        {
                            // Let stopMask specify which types of characters need to be isolated in special runs. 
                            // Let bidiMask specify which types of characters require bidi analysis.
                            ushort stopMask; 
                            ushort bidiMask; 

                            if (!runInfo.IsSymbol) 
                            {
                                // It's an ordinary Unicode font. Isolate various line breaks and format anchor.
                                stopMask = (ushort)(CharacterAttributeFlags.CharacterLineBreak |
                                        CharacterAttributeFlags.CharacterParaBreak | 
                                        CharacterAttributeFlags.CharacterFormatAnchor);
 
                                // Mask of character flags that require us to perform bidi analysis. 
                                bidiMask = (ushort)(CharacterAttributeFlags.CharacterRTL);
                            } 
                            else
                            {
                                // It's a non-Unicode font, meaning code points have non-standard meanings. The only
                                // characters we recognize as line breaks are LF (0x0A) and CR (0x0D). 
                                stopMask = (ushort)(CharacterAttributeFlags.CharacterCRLF |
                                        CharacterAttributeFlags.CharacterFormatAnchor); 
 
                                // Layout is always left-to-right for non-Unicode fonts.
                                bidiMask = 0; 
                            }

                            // Scan until end-of-run or one of the characters specified by stopMask. The accumulated
                            // flags of the characters we advanced past are stored in charFlags. 
                            ushort charFlags;
                            stringLength = Classification.AdvanceUntilUTF16( 
                                runInfo.CharacterBuffer, 
                                runInfo.OffsetToFirstChar,
                                runInfo.Length, // text is chopped at run length not string length 
                                stopMask,
                                out charFlags
                                );
 
                            // If it's a non-Unicode font we may have advanced past line break characters,
                            // but if so we don't want to treat them as such. 
                            charFlags &= (ushort)~(CharacterAttributeFlags.CharacterLineBreak | CharacterAttributeFlags.CharacterParaBreak); 

                            if(stringLength <= 0) 
                            {
                                // There are special characters such as various linebreaks or format anchor
                                // character in the middle of text stream. We isolate such characters into
                                // a separate run and treat them accordingly. 

                                runInfo = CreateSpecialRunFromTextContent(runInfo, cchFetched); 
                                stringLength = runInfo.StringLength; 
                                charFlags = runInfo.CharacterAttributeFlags;
 
                                Debug.Assert(stringLength > 0 && runInfo.Length > 0);
                            }
                            else if(stringLength != runInfo.Length)
                            { 
                                // shorten the run length if the character string is being cut short
                                runInfo.Length = stringLength; 
                            } 

                            runInfo.CharacterAttributeFlags |= charFlags; 
                            charFlagsSoFar |= charFlags;
                            bidiCharFlagsSoFar |= (ushort)(charFlags & bidiMask);
                            cchText += stringLength;
                        } 

                        _accNominalWidthSoFar += runInfo.GetRoughWidth(_settings.Formatter.ToIdeal); 
 
                        // store up the run info in a span indexed by actual character index
                        runInfoVector.SetReference(cch, stringLength, runInfo); 

                        TextEffectCollection textEffects = (runInfo.Properties != null) ? runInfo.Properties.TextEffects : null;
                        if (textEffects != null && textEffects.Count != 0)
                        { 
                            SetTextEffectsVector(textEffectsVector, cch, runInfo, textEffects);
                        } 
 
                        cch += stringLength;
 
                        cchFetched += runInfo.Length;

                    } while(
                            _accNominalWidthSoFar < _formatWidth 
                        && !runInfo.IsEndOfLine
                        && !IsNewline(charFlagsSoFar) 
                        && _accTextLengthSoFar + cchText <= MaxCharactersPerLine 
                        );
 

                    // if bidi is detected, resolve all fetched runs

                    if (   lastBidiLevel > 0 
                        || bidiCharFlagsSoFar != 0
                        || _bidiState != null 
                       ) 
                    {
                        cchResolved = BidiAnalyze(runInfoVector, cch, out bidiLevels); 

                        // for security reasons, limit how far we'll scan ahead to resolve bidi levels
                        if (cchResolved == 0 && _accTextLengthSoFar + cchText >= MaxCharactersPerLine)
                        { 
                            cchResolved = cch;
                            bidiLevels = null; 
                        } 
                    }
                    else 
                    {
                        cchResolved = cch;
                    }
 
                } while(cchResolved <= 0);
 
                Debug.Assert( 
                        runInfoVector != null
                    &&  (   bidiLevels == null 
                        ||  cchResolved <= bidiLevels.Length)
                    );

                bool forceBreak = IsForceBreakRequired(runInfoVector, ref cchResolved); 

                if(bidiLevels == null) 
                { 
                    // no bidi detected, all characters are left-to-right
                    CreateLSRunsUniformBidiLevel( 
                        runInfoVector,
                        textEffectsVector,
                        _cchUpTo,
                        0, 
                        cchResolved,
                        0,  // uniformBidiLevel 
                        ref lastBidiLevel 
                        );
                } 
                else
                {
                    int runInfoFirstCp = _cchUpTo;
                    int ichUniform = 0; 

                    while(ichUniform < cchResolved) 
                    { 
                        int cchUniform = 1;
                        int uniformBidiLevel = bidiLevels[ichUniform]; 

                        while(  ichUniform + cchUniform < cchResolved
                            &&  bidiLevels[ichUniform + cchUniform] == uniformBidiLevel)
                        { 
                            cchUniform++;
                        } 
 
                        // create lsruns within a range of uniform level
                        CreateLSRunsUniformBidiLevel( 
                            runInfoVector,
                            textEffectsVector,
                            runInfoFirstCp,
                            ichUniform, 
                            cchUniform,
                            uniformBidiLevel, 
                            ref lastBidiLevel 
                            );
 
                        ichUniform += cchUniform;
                    }
                }
 
                if (forceBreak)
                { 
                    // close reverse runs 
                    if (lastBidiLevel != 0)
                    { 
                        lastBidiLevel = CreateReverseLSRuns(BaseBidiLevel, lastBidiLevel);
                    }

                    // add a fake linebreak 
                    _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition);
                    _lscchUpTo += 1; 
                } 
            }
 
            // lsrun at the specified lscp was created, just grab it and go
            return GrabLSRun(
                lscpFetch,
                out plsrun, 
                out lsrunOffset,
                out lsrunLength 
                ); 
        }
 
        /// 
        /// Wrapper to TextRun fetching from the cache
        /// 
        internal TextRunInfo  FetchTextRun(int cpFetch) 
        {
            int runLength; 
            TextRun textRun; 

            // fetch TextRun from the formatting state 
            CharacterBufferRange charString = _settings.FetchTextRun(
                cpFetch,
                _cpFirst,
                out textRun, 
                out runLength
                ); 
 
            CultureInfo digitCulture = null;
            bool contextualSubstitution = false; 
            bool symbolTypeface = false;

            Plsrun runType = TextRunInfo.GetRunType(textRun);
 
            if (runType == Plsrun.Text)
            { 
                TextRunProperties properties = textRun.Properties; 
                symbolTypeface = properties.Typeface.Symbol;
                if (!symbolTypeface) 
                {
                    _settings.DigitState.SetTextRunProperties(properties);
                    digitCulture = _settings.DigitState.DigitCulture;
                    contextualSubstitution = _settings.DigitState.Contextual; 
                }
            } 
 
            TextModifierScope currentScope = _modifierScope;
            TextModifier modifier = textRun as TextModifier; 

            if (modifier != null)
            {
                _modifierScope = new TextModifierScope( 
                    _modifierScope,
                    modifier, 
                    cpFetch 
                    );
 
                // The new scope inclues the current TextModifier run
                currentScope = _modifierScope;
            }
            else if (_modifierScope != null && textRun is TextEndOfSegment) 
            {
                // The new scope only affects subsequent runs. TextEndOfSegment run itself is 
                // still in the old scope such that its coresponding TextModifier run can be tracked. 
                _modifierScope = _modifierScope.ParentScope;
            } 

            return new TextRunInfo(
                charString,
                runLength, 
                cpFetch - _cpFirst, // offsetToFirstCp
                textRun, 
                runType, 
                0,   // charFlags
                digitCulture, 
                contextualSubstitution,
                symbolTypeface,
                currentScope
                ); 
        }
 
 
        /// 
        /// Split a TextRunInfo into multiple ranges each with a uniform set of 
        /// TextEffects.
        /// 
        /// 
        /// A TextRun can have a collection of TextEffect. Each of them can be applied to 
        /// an arbitrary range of text. This method breaks the TextRunInfo into sub-ranges
        /// that have identical set of TextEffects. For example 
        /// 
        /// Current Run :   |----------------------------------------|
        /// Effect 1:     |-----------------------------------------------------| 
        /// Effect 2:                  |------------------------|
        /// Splitted runs:  |----------|------------------------|----|
        ///
        /// It can be observed that the effected ranges are dividied at the boundaries of the 
        /// TextEffects. We sort all the boundaries of TextEffects according to their positions
        /// and create the effected range in between of any two ajacent boundaries. For each efffected 
        /// range, we store all the active TextEffect into a list. 
        /// 
        private void SetTextEffectsVector( 
            SpanVector              textEffectsVector,
            int                     ich,
            TextRunInfo             runInfo,
            TextEffectCollection    textEffects 
            )
        { 
            // We already check for empty text effects at the call site. 
            Debug.Assert(textEffects != null && textEffects.Count != 0);
 
            int cpFetched = _cpFirst + _cchUpTo + ich; // get text source character index

            // Offset from client Cp to text effect index.
            int offset = cpFetched - _settings.TextSource.GetTextEffectCharacterIndexFromTextSourceCharacterIndex(cpFetched); 

            int textEffectsCount = textEffects.Count; 
            TextEffectBoundary[] bounds = new TextEffectBoundary[textEffectsCount * 2]; 
            for (int i = 0; i < textEffectsCount; i++)
            { 
                TextEffect effect = textEffects[i];
                bounds[2 * i] = new TextEffectBoundary(effect.PositionStart, true); // effect starting boundary
                bounds[2 * i + 1] = new TextEffectBoundary(effect.PositionStart + effect.PositionCount, false); // effect end boundary
            } 

            Array.Sort(bounds); // sort the TextEffect bounds. 
 
            int effectedRangeStart = Math.Max(cpFetched - offset, bounds[0].Position);
            int effectedRangeEnd   = Math.Min(cpFetched - offset + runInfo.Length, bounds[bounds.Length - 1].Position); 

            int currentEffectsCount = 0;
            int currentPosition = effectedRangeStart;
            for (int i = 0; i < bounds.Length && currentPosition < effectedRangeEnd; i++) 
            {
                // Have we reached the end of a non-empty subrange with at least one text effect? 
                if (currentPosition < bounds[i].Position && currentEffectsCount > 0) 
                {
                    // Let [currentPosition,currentRangeEnd) delimit the subrange ending at bounds[i]. 
                    int currentRangeEnd = Math.Min(bounds[i].Position, effectedRangeEnd);

                    // Consolidate all the active effects in the subrange.
                    IList activeEffects = new TextEffect[currentEffectsCount]; 
                    int effectIndex = 0;
                    for (int j = 0; j < textEffectsCount; j++) 
                    { 
                        TextEffect effect = textEffects[j];
                        if (currentPosition >= effect.PositionStart && currentPosition < (effect.PositionStart + effect.PositionCount)) 
                        {
                            activeEffects[effectIndex++] = effect;
                        }
                    } 

                    Invariant.Assert(effectIndex == currentEffectsCount); 
 
                    // Set the active effects for this CP subrange. The vector index is relative
                    // to the starting cp of the current run-fetching loop. 
                    textEffectsVector.SetReference(
                        currentPosition + offset - _cchUpTo - _cpFirst,    // client cp index
                        currentRangeEnd - currentPosition,                 // length
                        activeEffects                                      // text effects 
                        );
 
                    currentPosition = currentRangeEnd; 
                }
 
                // Adjust the current count depending on if it is a TextEffect's starting or ending boundary.
                currentEffectsCount += (bounds[i].IsStart ? 1 : -1);

                if (currentEffectsCount == 0 && i < bounds.Length - 1) 
                {
                   // There is no effect on the current position. Move it to the start of next TextEffect. 
                   Invariant.Assert(bounds[i + 1].IsStart); 
                   currentPosition = Math.Max(currentPosition, bounds[i + 1].Position);
                } 
            }
        }

        ///  
        /// Structure representing one boundary of a TextEffect. Each TextEffect has
        /// two boundaries: the beginning and the end. 
        ///  
        private struct TextEffectBoundary : IComparable
        { 
            private readonly int _position;
            private readonly bool _isStart;

            internal TextEffectBoundary(int position, bool isStart) 
            {
                _position = position; 
                _isStart = isStart; 
            }
 
            internal int Position
            {
                get { return _position; }
            } 

            internal bool IsStart 
            { 
                get { return _isStart; }
            } 

            public int CompareTo(TextEffectBoundary other)
            {
                if (Position != other.Position) 
                    return Position - other.Position;
 
                if (IsStart == other.IsStart) return 0; 

                // Starting edge is always in front. 
                return IsStart ? -1 : 1;
            }
        }
 

        ///  
        /// Create special run that matches the content of specified text run 
        /// 
        ///  
        ///    Critical: This code has unsafe code block that uses pointers.
        ///    TreatAsSafe: This code does not expose the pointer and the call does bounds checking.
        /// 
        [SecurityCritical,SecurityTreatAsSafe] 
        private TextRunInfo CreateSpecialRunFromTextContent(
            TextRunInfo     runInfo, 
            int             cchFetched 
            )
        { 
            // -FORMAT ANCHOR-
            //
            // Format anchor character is what we create internally to drive LS. If it
            // is present in the middle of text stream sent from the client, we will 
            // have to filter it out and replace it with NBSP. This is to protect LS
            // from running into a bad state due to misinterpreting such character as 
            // our format anchor. Following is the list of anchor character we use todate. 
            //
            //      "\uFFFB" (Unicode 'Annotation Terminator') 
            //
            // -LINEBREAK-
            //
            // Following the Unicode guideline on newline characters, we recognize 
            // both LS (U+2028) and PS (U+2029) as explicit linebreak (PS also breaks
            // paragraph but that's handled outside line level formatting). We also 
            // treat the following sequence of characters as linebreak 
            //
            //      "CR"    ("\u000D") 
            //      "LF"    ("\u000A")
            //      "CRLF"  ("\u000D\u000A")
            //      "NEL"   ("\u0085")
            //      "VT"    ("\u000B") 
            //      "FF"    ("\u000C")
            // 
            // Note: http://www.unicode.org/unicode/reports/tr13/tr13-9.html 
            Debug.Assert(runInfo.StringLength > 0 && runInfo.Length > 0);
 
            CharacterBuffer charBuffer = runInfo.CharacterBuffer;
            int offsetToFirstChar = runInfo.OffsetToFirstChar;
            char firstChar = charBuffer[offsetToFirstChar];
            ushort charFlags; 

            charFlags = (ushort)Classification.CharAttributeOf( 
                (int)Classification.GetUnicodeClassUTF16(firstChar) 
                ).Flags;
 
            if ((charFlags & (ushort)CharacterAttributeFlags.CharacterLineBreak) != 0)
            {
                // Get cp length of newline sequence
                // 
                // It is possible that client run ends in between two codepoints that
                // make up a single newline sequence e.g. CRLF. Therefore, when we 
                // encounter the first codepoint of the sequence, we need to make sure 
                // we have enough codepoints to determine the correct whole sequence.
                // In an uncommon event, we may be forced to look ahead by fetching more 
                // runs. [wchao, PS bug 910308]

                int newlineLength = 1;  // most sequences take one cp
 
                if (firstChar == '\r')
                { 
                    if (runInfo.Length > 1) 
                    {
                        newlineLength += ((charBuffer[offsetToFirstChar + 1] == '\n') ? 1 : 0); 
                    }
                    else
                    {
                        TextRunInfo nextRunInfo = FetchTextRun(_cpFirst + cchFetched + 1); 

                        if (nextRunInfo != null && nextRunInfo.TextRun is ITextSymbols) 
                        { 
                            newlineLength += ((nextRunInfo.CharacterBuffer[nextRunInfo.OffsetToFirstChar] == '\n') ? 1 : 0);
                        } 
                    }
                }

                unsafe 
                {
                    runInfo = new TextRunInfo( 
                        new CharacterBufferRange((char*)PwchLineSeparator, 1), 
                        newlineLength, // run length
                        runInfo.OffsetToFirstCp, 
                        runInfo.TextRun,
                        Plsrun.LineBreak, // LineBreak run
                        charFlags,
                        null,  // digit culture 
                        false, // contextual substitution
                        false, // is not Unicode 
                        runInfo.TextModifierScope 
                        );
                } 
            }
            else if ((charFlags & (ushort)CharacterAttributeFlags.CharacterParaBreak) != 0)
            {
                unsafe 
                {
                    // This character is a paragraph separator. Split it into a 
                    // separate run. 
                    runInfo = new TextRunInfo(
                        new CharacterBufferRange((char*)PwchParaSeparator, 1), 
                        1,
                        runInfo.OffsetToFirstCp,
                        runInfo.TextRun,
                        Plsrun.ParaBreak,  // parabreak run 
                        charFlags,
                        null,   // digit culture 
                        false,  // contextual substitution 
                        false,  // is not Unicode
                        runInfo.TextModifierScope 
                        );
                }
            }
            else 
            {
                Invariant.Assert((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0); 
 
                unsafe
                { 
                    runInfo = new TextRunInfo(
                        new CharacterBufferRange((char*)PwchNbsp, 1),
                        1, // run length
                        runInfo.OffsetToFirstCp, 
                        runInfo.TextRun,
                        runInfo.Plsrun, 
                        charFlags, 
                        null,   // digit culture
                        false,  // contextual substitution 
                        false,  // is not Unicode
                        runInfo.TextModifierScope
                        );
                } 
            }
 
            return runInfo; 
        }
 

        /// 
        /// Grab existing lsrun at specified LSCP
        ///  
        private LSRun GrabLSRun(
            int             lscpFetch, 
            out Plsrun      plsrun, 
            out int         lsrunOffset,
            out int         lsrunLength 
            )
        {
            int offsetToFirstCp = lscpFetch - _cpFirst;
 
            SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, offsetToFirstCp);
            _plsrunVectorLatestPosition = rider.SpanPosition; 
            plsrun = (Plsrun)rider.CurrentElement; 

            LSRun lsrun; 
            if (plsrun < Plsrun.FormatAnchor)
            {
                lsrun = ControlRuns[(int)plsrun];
                lsrunOffset = 0; 
            }
            else 
            { 
                lsrun = (LSRun)_lsrunList[(int)(ToIndex(plsrun) - Plsrun.FormatAnchor)];
                lsrunOffset = offsetToFirstCp - rider.CurrentSpanStart; 
            }

            if (_lscpFirstValue != 0)
            { 
                // this is marker store, differentiate the plsrun from
                // plsrun from the main text store. 
                plsrun = MakePlsrunMarker(plsrun); 
            }
 
            // SpanRider.Length yields the distance to the end of the Span, not
            // the total length of the Span.
            lsrunLength = rider.Length;
 
            return lsrun;
        } 
 

        ///  
        /// Get the Bidi level of the character before the currently fetched one
        /// 
        private int GetLastLevel()
        { 
            if (_lscchUpTo > 0)
            { 
                SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, _lscchUpTo - 1); 
                _plsrunVectorLatestPosition = rider.SpanPosition;
                return GetRun((Plsrun)rider.CurrentElement).BidiLevel; 
            }
            return BaseBidiLevel;
        }
 

        ///  
        /// Base bidi level 
        /// 
        private int BaseBidiLevel 
        {
            get { return _settings.Pap.RightToLeft ? 1 : 0; }
        }
 
        /// 
        /// Analyze bidirectional level of runs 
        ///  
        /// run info vector indexed by ich
        /// character length of string to be analyzed 
        /// array of bidi levels, each for a character
        /// Number of characters resolved
        /// 
        /// BiDi Analysis in line layout imposes a higher level protocol on top of Unicode bidi algorithm 
        /// to support rich editing behavior. Explicit directional embedding controls is to be done
        /// through TextModifier runs and corresponding TextEndOfSegment. Directional controls (such as 
        /// LRE, RLE, PDF, etc) in the text stream are ignored in the Bidi Analysis to avoid conflict with the higher 
        /// level protocol.
        /// 
        /// The implementation analyzes directional embedding one level at a time. Input text runs are divided
        /// at the point where directional embedding level is changed.
        /// 
        private int BidiAnalyze( 
            SpanVector                  runInfoVector,
            int                         stringLength, 
            out byte[]                  bidiLevels 
            )
        { 
            CharacterBuffer charBuffer = null;
            int offsetToFirstChar;

            SpanRider runInfoSpanRider = new SpanRider(runInfoVector); 
            if (runInfoSpanRider.Length >= stringLength)
            { 
                // typical case, only one string is analyzed 
                TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;
 
                if (!runInfo.IsSymbol)
                {
                    charBuffer = runInfo.CharacterBuffer;
                    offsetToFirstChar = runInfo.OffsetToFirstChar; 
                    Debug.Assert(runInfo.StringLength >= stringLength);
                } 
                else 
                {
                    // Treat all characters in non-Unicode runs as strong left-to-right. 
                    // The literal 'A' could be any Latin character.
                    charBuffer = new StringCharacterBuffer(new string('A', stringLength));
                    offsetToFirstChar = 0;
                } 
            }
            else 
            { 
                // build up a consolidated character buffer for bidi analysis of
                // concatenated strings in multiple textruns. 
                int ich = 0;
                int cch;

                StringBuilder stringBuilder = new StringBuilder(stringLength); 

                while(ich < stringLength) 
                { 
                    runInfoSpanRider.At(ich);
                    cch = runInfoSpanRider.Length; 
                    TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;

                    Debug.Assert(cch <= runInfo.StringLength);
 
                    if (!runInfo.IsSymbol)
                    { 
                        runInfo.CharacterBuffer.AppendToStringBuilder( 
                            stringBuilder,
                            runInfo.OffsetToFirstChar, 
                            cch
                            );
                    }
                    else 
                    {
                        // Treat all characters in non-Unicode runs as strong left-to-right. 
                        // The literal 'A' could be any Latin character. 
                        stringBuilder.Append('A', cch);
                    } 

                    ich += cch;
                }
 
                charBuffer = new StringCharacterBuffer(stringBuilder.ToString());
                offsetToFirstChar = 0; 
            } 

            if(_bidiState == null) 
            {
                // make sure the initial state is setup
                _bidiState = new BidiState(_settings, _cpFirst);
            } 

            bidiLevels = new byte[stringLength]; 
            DirectionClass[] directionClasses = new DirectionClass[stringLength]; 

            int resolvedLength = 0; 

            for(int i = 0; i < runInfoVector.Count; i++)
            {
                int cchResolved = 0; 

                TextRunInfo currentRunInfo = (TextRunInfo) runInfoVector[i].element; 
                TextModifier modifier = currentRunInfo.TextRun as TextModifier; 

                if (IsDirectionalModifier(modifier)) 
                {
                    bidiLevels[resolvedLength] = AnalyzeDirectionalModifier(_bidiState, modifier.FlowDirection);
                    cchResolved = 1;
                } 
                else if (IsEndOfDirectionalModifier(currentRunInfo))
                { 
                    bidiLevels[resolvedLength] = AnalyzeEndOfDirectionalModifier(_bidiState); 
                    cchResolved = 1;
                } 
                else
                {
                    int ich = resolvedLength;
                    do 
                    {
                        CultureInfo culture = CultureMapper.GetSpecificCulture(currentRunInfo.Properties == null ? null : currentRunInfo.Properties.CultureInfo); 
                        DirectionClass europeanNumberOverride = _bidiState.GetEuropeanNumberClassOverride(culture); 

                        // 
                        // The European number in the input text is explictly set to AN or EN base on the
                        // culture of the text. We set the input DirectionClass of this range of text to
                        // AN or EN to indicate that any EN in this range should be explicitly set to this override
                        // value. 
                        //
                        for(int k = 0; k < runInfoVector[i].length; k++) 
                        { 
                            directionClasses[ich + k] = europeanNumberOverride;
                        } 

                        ich += runInfoVector[i].length;
                        if ((++i) >= runInfoVector.Count)
                            break; // end of all runs. 

                        currentRunInfo = (TextRunInfo) runInfoVector[i].element; 
                        if ( currentRunInfo.Plsrun == Plsrun.Hidden && 
                              (  IsDirectionalModifier(currentRunInfo.TextRun as TextModifier)
                              || IsEndOfDirectionalModifier(currentRunInfo) 
                              )
                           )
                        {
                            i--; 
                            break;   // break bidi analysis at the point of embedding level change
                        } 
                    } 
                    while (true);
 
                    const Bidi.Flags BidiFlags = Bidi.Flags.ContinueAnalysis | Bidi.Flags.IgnoreDirectionalControls | Bidi.Flags.OverrideEuropeanNumberResolution;

                    // The last runs will be marked as IncompleteText as their resolution
                    // may depend on following runs that haven't been fetched yet. 
                    Bidi.Flags flags = (i < runInfoVector.Count) ?
                            BidiFlags 
                          : BidiFlags | Bidi.Flags.IncompleteText; 

 
                    Bidi.BidiAnalyzeInternal(
                        charBuffer,
                        offsetToFirstChar + resolvedLength,
                        ich - resolvedLength, 
                        0, // no max hint
                        flags, 
                        _bidiState, 
                        new PartialArray(bidiLevels, resolvedLength, ich - resolvedLength),
                        new PartialArray(directionClasses, resolvedLength, ich - resolvedLength), 
                        out cchResolved
                        );

                    // Text must be completely resolved if there is no IncompleteText flag. 
                    Invariant.Assert(cchResolved == ich - resolvedLength || (flags & Bidi.Flags.IncompleteText) != 0);
                } 
 
                resolvedLength += cchResolved;
            } 

            Invariant.Assert(resolvedLength <= bidiLevels.Length);
            return resolvedLength;
        } 

        ///  
        /// Update BidiState base to the new directional embedding level. 
        /// 
        ///  
        /// The method returns the embedding level before the start of the Modifier.
        /// Contents inside the modifier scope is at a higher embedding level and hence
        /// separated from the content before the modifier scope.
        ///  
        private byte AnalyzeDirectionalModifier(
            BidiState       state, 
            FlowDirection   flowDirection 
            )
        { 
            bool leftToRight = (flowDirection == FlowDirection.LeftToRight);

            ulong levelStack = state.LevelStack;
 
            byte parentLevel = Bidi.BidiStack.GetMaximumLevel(levelStack);
 
            byte topLevel; 

            // Push to Bidi stack. Increment overflow counter if so. 
            if (!Bidi.BidiStack.Push(ref levelStack, leftToRight, out topLevel))
            {
                state.Overflow++;
            } 

            state.LevelStack = levelStack; 
 
            // set the default last strong such that text without CultureInfo
            // can be resolved correctly. 
            state.SetLastDirectionClassesAtLevelChange();
            return parentLevel;
        }
 
        /// 
        /// Update BidiState at the end of a directional emebedding level. 
        ///  
        /// 
        /// The method returns the embedding level after the end of the modifier. 
        /// Contents inside the modifier scope is at a higher embedding level and hence separated
        /// from the content after the modifier scope.
        /// 
        private byte AnalyzeEndOfDirectionalModifier(BidiState state) 
        {
            // Pop level stack 
            if (state.Overflow > 0) 
            {
                state.Overflow --; 
                return state.CurrentLevel;
            }

            byte parentLevel; 
            ulong stack = state.LevelStack;
 
            bool success = Bidi.BidiStack.Pop(ref stack, out parentLevel); 
            Invariant.Assert(success);
            state.LevelStack = stack; 

            // set the default last strong such that text without CultureInfo
            // can be resolved correctly.
            state.SetLastDirectionClassesAtLevelChange(); 
            return parentLevel;
        } 
 
        private bool IsEndOfDirectionalModifier(TextRunInfo runInfo)
        { 
            return (  runInfo.TextModifierScope != null
                   && runInfo.TextModifierScope.TextModifier.HasDirectionalEmbedding
                   && runInfo.TextRun is TextEndOfSegment
                   ); 
        }
 
        private bool IsDirectionalModifier(TextModifier modifier) 
        {
            return modifier != null && modifier.HasDirectionalEmbedding; 
        }

        internal bool InsertFakeLineBreak(int cpLimit)
        { 
            for (int i = 0, cp = 0, lscp = 0; i < _plsrunVector.Count; ++i)
            { 
                Span span = _plsrunVector[i]; 
                Plsrun plsrun = (Plsrun)span.element;
 
                // Is it a normal, non-static, LSRun?
                if (plsrun >= Plsrun.FormatAnchor)
                {
                    // Get the run. 
                    LSRun lsrun = GetRun(plsrun);
 
                    // Have we reached the limit? 
                    if (cp + lsrun.Length >= cpLimit)
                    { 
                        // Remove all subsequent runs.
                        _plsrunVector.Delete(i + 1, _plsrunVector.Count - (i + 1), ref _plsrunVectorLatestPosition);

                        // Truncate the run if it exeeds the limit. 
                        if (lsrun.Type == Plsrun.Text && cp + lsrun.Length > cpLimit)
                        { 
                            lsrun.Truncate(cpLimit - cp); 
                            span.length = lsrun.Length;
                        } 

                        _lscchUpTo = lscp + lsrun.Length;

                        // Close any reverse runs. 
                        CreateReverseLSRuns(BaseBidiLevel, lsrun.BidiLevel);
 
                        // Add the fake line break. 
                        _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition);
                        _lscchUpTo += 1; 

                        return true;
                    }
 
                    cp += lsrun.Length;
                } 
 
                lscp += span.length;
            } 

            return false;
        }
 
        /// 
        /// Determines whether a line needs to be truncated for security reasons due to exceeding 
        /// the maximum number of characters per line. See the comment for MaxCharactersPerLine. 
        /// 
        /// Vector of fetched text runs. 
        /// Number of cp to be added to _plsrunVector; the method
        /// may change this value if the line needs to be truncated.
        /// Returns true if the line should be truncated, false it not.
        private bool IsForceBreakRequired(SpanVector runInfoVector, ref int cchToAdd) 
        {
            bool forceBreak = false; 
            int ichRun = 0; 

            for (int i = 0; i < runInfoVector.Count && ichRun < cchToAdd; ++i) 
            {
                Span span = runInfoVector[i];
                TextRunInfo runInfo = (TextRunInfo)span.element;
 
                int runLength = Math.Min(span.length, cchToAdd - ichRun);
 
                // Only Plsrun.Text runs count against the limit 
                if (runInfo.Plsrun == Plsrun.Text && !IsNewline((ushort)runInfo.CharacterAttributeFlags))
                { 
                    if (_accTextLengthSoFar + runLength <= MaxCharactersPerLine)
                    {
                        // we're still under the limit; accumulate the number of characters so far
                        _accTextLengthSoFar += runLength; 
                    }
                    else 
                    { 
                        // accumulated number of characters has exceeded the maximum allowed number
                        // of characters per line; we need to generate a fake line break 
                        runLength = MaxCharactersPerLine - _accTextLengthSoFar;
                        _accTextLengthSoFar = MaxCharactersPerLine;
                        cchToAdd = ichRun + runLength;
                        forceBreak = true; 
                    }
                } 
 
                ichRun += runLength;
            } 

            return forceBreak;
        }
 
        [Flags]
        private enum NumberContext 
        { 
            Unknown             = 0,
 
            Arabic              = 0x0001,
            European            = 0x0002,
            Mask                = 0x0003,
 
            FromLetter          = 0x0004,
            FromFlowDirection   = 0x0008 
        } 

        private NumberContext GetNumberContext(TextModifierScope scope) 
        {
            int limitCp = _cpFirst + _cchUpTo;
            int firstCp = _cpNumberContext;
            NumberContext cachedNumberContext = _numberContext; 

            // Is there a current bidi scope? 
            for (; scope != null; scope = scope.ParentScope) 
            {
                if (scope.TextModifier.HasDirectionalEmbedding) 
                {
                    int cpScope = scope.TextSourceCharacterIndex;
                    if (cpScope >= _cpNumberContext)
                    { 
                        // Only scan back to the start of the current scope and don't use the cached number
                        // context since it's outside the current scope. 
                        firstCp = cpScope; 
                        cachedNumberContext = NumberContext.Unknown;
                    } 
                    break;
                }
            }
 
            // Is it a right to left context?
            bool rightToLeft = (scope != null) ? 
                scope.TextModifier.FlowDirection == FlowDirection.RightToLeft : 
                Pap.RightToLeft;
 
            // Scan for a preceding letter.
            while (limitCp > firstCp)
            {
                TextSpan textSpan = _settings.GetPrecedingText(limitCp); 

                // Stop if there's an empty TextSpan 
                if (textSpan.Length <= 0) 
                {
                    break; 
                }

                CharacterBufferRange charRange = textSpan.Value.CharacterBufferRange;
                if (!charRange.IsEmpty) 
                {
                    CharacterBuffer charBuffer = charRange.CharacterBuffer; 
 
                    // Index just past the last character in the range.
                    int limit = charRange.OffsetToFirstChar + charRange.Length; 

                    // Index of the first character in the range, not including any characters before firstCp.
                    int first = limit - Math.Min(charRange.Length, limitCp - firstCp);
 
                    // We'll stop scanning at letter or line break.
                    const ushort flagsMask = 
                        (ushort)CharacterAttributeFlags.CharacterLetter | 
                        (ushort)CharacterAttributeFlags.CharacterLineBreak;
 
                    // Iterate over the characters in reverse order.
                    for (int i = limit - 1; i >= first; --i)
                    {
                        char ch = charBuffer[i]; 
                        CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch));
 
                        ushort flags = (ushort)(charAttributes.Flags & flagsMask); 
                        if (flags != 0)
                        { 
                            if ((flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0)
                            {
                                // It's a letter so the number context depends on its script.
                                return (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ? 
                                    NumberContext.Arabic | NumberContext.FromLetter :
                                    NumberContext.European | NumberContext.FromLetter; 
                            } 
                            else
                            { 
                                // It's a line break. There are no preceding letters so number context depends only on
                                // whether the current bidi scope is right to left.
                                return rightToLeft ?
                                    NumberContext.Arabic | NumberContext.FromFlowDirection : 
                                    NumberContext.European | NumberContext.FromFlowDirection;
                            } 
                        } 
                    }
                } 

                limitCp -= textSpan.Length;
            }
 
            // If we have a cached number context that's still valid the use it. Valid means (1) we
            // scanned back as far as the cp of the number context, and (2) the number context was 
            // determined from a letter. (A cached number context derived from flow direction might 
            // not be valid because an embedded bidi level may have ended.)
            if (limitCp <= firstCp && (cachedNumberContext & NumberContext.FromLetter) != 0) 
            {
                return cachedNumberContext;
            }
 
            // There are no preceding letters so number context depends only on whether the current
            // bidi scope is right to left. 
            return rightToLeft ? 
                NumberContext.Arabic | NumberContext.FromFlowDirection :
                NumberContext.European | NumberContext.FromFlowDirection; 
        }

        /// 
        /// Create lsruns within a range of uniform bidi level. 
        /// 
        private void CreateLSRunsUniformBidiLevel( 
            SpanVector              runInfoVector, 
            SpanVector              textEffectsVector,
            int                     runInfoFirstCp, 
            int                     ichUniform,
            int                     cchUniform,
            int                     uniformBidiLevel,
            ref int                 lastBidiLevel 
            )
        { 
            int ichRun = 0; 

            // a range of characters with uniform level may span multiple 
            // textruns. Create lsrun at runInfo boundary.

            SpanRider runInfoSpanRider = new SpanRider(runInfoVector);
            SpanRider textEffectsSpanRider = new SpanRider(textEffectsVector); 

            while(ichRun < cchUniform) 
            { 
                runInfoSpanRider.At(ichUniform + ichRun);
                textEffectsSpanRider.At(ichUniform + ichRun); 

                // Limit the span base on effected ranges.
                int spanLength = Math.Min(runInfoSpanRider.Length, textEffectsSpanRider.Length);
                int ichEnd = Math.Min(ichRun + spanLength, cchUniform); 

                int textRunLength; 
 
                TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;
                IList textEffects = (IList)textEffectsSpanRider.CurrentElement; 

                // Initialize digitCulture only if there are digits.
                CultureInfo digitCulture = null;
 
                // Number context; used only if we do contextual digit substitution.
                NumberContext numberContext = NumberContext.Unknown; 
 
                if ((runInfo.CharacterAttributeFlags & (ushort)CharacterAttributeFlags.CharacterDigit) == 0)
                { 
                    // No digits so digitCulture isn't used.
                }
                else if (!runInfo.ContextualSubstitution)
                { 
                    // Render all numbers using the digit culture of the run.
                    digitCulture = runInfo.DigitCulture; 
                } 
                else
                { 
                    // Contextual number substitution means the digit culture of a given number depends on the
                    // nearest preceding letter. If it's an Arabic letter we use the digit culture of the run;
                    // otherwise we use European digits (null digit culture).
 
                    // Number context of the previous number, if any.
                    NumberContext previousNumberContext = NumberContext.Unknown; 
 
                    CharacterBuffer charBuffer = runInfo.CharacterBuffer;
 
                    // The cha----r indexes ichRun, ich, etc., are relative to the start of the uniform range;
                    // In order to yield an index into charBuffer, we need to calculate the offset from the
                    // start of the uniform range to the start of the character buffer.
                    // 
                    //
                    // _cpFirst 
                    //    |----_cchUpTo---->|-----------------runInfoVector----------------------->| 
                    //                      |
                    //                      |--ichUniform-->|----ich------>| 
                    //                      |               |              |
                    //                      |-----CurrentSpanStart-->|=====x=====runInfo========|
                    //                                      |        |     |
                    // charBuffer=> [---runInfo.OffsetToFirstChar--->|-----x----------------------------] 
                    //              |                       |              |

                    //              |---characterOffset---->|----ich------>| 
                    // 
 					
                    int characterOffset = 
                        ichUniform                           // start of the uniform range
                        - runInfoSpanRider.CurrentSpanStart  // make relative to the the start of the runInfo
                        + runInfo.OffsetToFirstChar;         // make relative to the start of the character buffer in runInfo
 
                    for (int ich = ichRun; ich < ichEnd; ++ich)
                    { 
                        char ch = charBuffer[ich + characterOffset]; 
                        CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch));
 
                        if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterDigit) != 0)
                        {
                            // If there were no preceding letters in the current run we need to scan backwards to
                            // determine the current number context. 
                            if (numberContext == NumberContext.Unknown)
                            { 
                                numberContext = GetNumberContext(runInfo.TextModifierScope); 
                            }
 
                            // We need to set the digit culture if
                            //   (a) we haven't set it yet (i.e., this is the first number) or
                            //   (b) we set it but the previous number had a different number context
                            if ((previousNumberContext & NumberContext.Mask) != (numberContext & NumberContext.Mask)) 
                            {
                                // If there was a previous number with a different digit culture we need to split the run. 
                                if (previousNumberContext != NumberContext.Unknown) 
                                {
                                    CreateLSRuns( 
                                        runInfo,
                                        textEffects,
                                        digitCulture,
                                        ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart, 
                                        ich - ichRun,
                                        uniformBidiLevel, 
                                        ref lastBidiLevel, 
                                        out textRunLength
                                        ); 
                                    _cchUpTo += textRunLength;
                                    ichRun = ich;
                                }
 
                                // Set the digitCulture to use for this and subsequent characters.
                                digitCulture = (numberContext & NumberContext.Mask) == NumberContext.Arabic ? 
                                    runInfo.DigitCulture :      // subsequent digits use Arabic symbols 
                                    null;                       // subsequent digits use European symbols
 
                                previousNumberContext = numberContext;
                            }
                        }
                        else if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0) 
                        {
                            // It's a letter so set the current number context based on the letter's script. 
                            // Don't set the digit culture until we actually encounter a number. 
                            numberContext = (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ?
                                NumberContext.Arabic | NumberContext.FromLetter : 
                                NumberContext.European | NumberContext.FromLetter;
                        }
                    }
                } 

                // Even if we split the run we still have to add the last part. 
                Debug.Assert(ichRun < ichEnd); 

                // Add the run (or what's left of it). 
                CreateLSRuns(
                    runInfo,
                    textEffects,
                    digitCulture, 
                    ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart,
                    ichEnd - ichRun, 
                    uniformBidiLevel, 
                    ref lastBidiLevel,
                    out textRunLength 
                    );
                _cchUpTo += textRunLength;
                ichRun = ichEnd;
 
                // Save the number of context if known. This reduces the number of calls to GetPrecedingText for
                // lines with more than one number. We do this now, after calling CreateLSRuns, so that _cchUpTo 
                // holds the correct cp that corresponds to all the characters scanned so far. 
                if (numberContext != NumberContext.Unknown)
                { 
                    _numberContext = numberContext;
                    _cpNumberContext = _cpFirst + _cchUpTo;
                }
            } 

            Debug.Assert(ichRun == cchUniform); 
        } 

 

        /// 
        /// Create reverse lsruns
        ///  
        /// current bidi level
        /// last bidi level 
        /// updated last bidi Level 
        private int CreateReverseLSRuns(
            int     currentBidiLevel, 
            int     lastBidiLevel
            )
        {
            Plsrun plsrun; 
            int levelDiff = currentBidiLevel - lastBidiLevel;
 
            if(levelDiff > 0) 
            {
                // level up 
                plsrun = Plsrun.Reverse;
            }
            else
            { 
                // level down
                plsrun = Plsrun.CloseAnchor; 
                levelDiff = -levelDiff; 
            }
 
            for(int i = 0; i < levelDiff; i++)
            {
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, plsrun, _plsrunVectorLatestPosition);
                _lscchUpTo++; 
            }
            return currentBidiLevel; 
        } 

 

        /// 
        /// Create lsrun(s)
        ///  
        /// run info
        /// The applicable TextEffects on the LSRun.  
        /// digit culture for number substitution 
        /// offset the first char from start of run info
        /// lsrun character length 
        /// uniform bidi level
        /// last bidi level
        /// text run length
        private void CreateLSRuns( 
            TextRunInfo       runInfo,
            IList textEffects, 
            CultureInfo       digitCulture, 
            int               offsetToFirstChar,
            int               stringLength, 
            int               uniformBidiLevel,
            ref int           lastBidiLevel,
            out int           textRunLength
            ) 
        {
            LSRun lsrun = null; 
            int lsrunLength = 0; 
            textRunLength = 0;
 
            switch (runInfo.Plsrun)
            {
                case Plsrun.Text:
                { 
                    ushort charFlags = (ushort)runInfo.CharacterAttributeFlags;
 
                    // LineBreak & ParaBreak are separated into individual TextRunInfo with Plsrun.LineBreak or Plsrun.ParaBreak. 
                    Invariant.Assert(!IsNewline(charFlags));
 
                    if ((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0)
                    {
                        lsrun = new LSRun(
                            runInfo, 
                            Plsrun.FormatAnchor,
                            PwchNbsp, 
                            1, 
                            runInfo.OffsetToFirstCp,
                            (byte) uniformBidiLevel 
                            );

                        lsrunLength = textRunLength = lsrun.StringLength;
                    } 
                    else
                    { 
                        // Normal text, run length is character length 

                        textRunLength = lsrunLength = stringLength; 
                        Debug.Assert(runInfo.OffsetToFirstChar + offsetToFirstChar + lsrunLength <= runInfo.CharacterBuffer.Count);

                        CreateTextLSRuns(
                            runInfo, 
                            textEffects,
                            digitCulture, 
                            offsetToFirstChar, 
                            stringLength,
                            uniformBidiLevel, 
                            ref lastBidiLevel
                            );
                    }
 
                    break;
                } 
 
                case Plsrun.InlineObject:
                { 
                    Debug.Assert(offsetToFirstChar == 0);

                    double realToIdeal = _settings.Formatter.ToIdeal;
 
                    lsrun = new LSRun(
                        runInfo, 
                        textEffects, 
                        Plsrun.InlineObject,
                        runInfo.OffsetToFirstCp, 
                        runInfo.Length,
                        (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize),
                        0,          // character flags
                        new CharacterBufferRange(runInfo.CharacterBuffer, 0, stringLength), 
                        null,       // no shapeable
                        realToIdeal, 
                        (byte)uniformBidiLevel 
                        );
 
                    lsrunLength = stringLength;
                    textRunLength = runInfo.Length;
                    break;
                } 

                case Plsrun.LineBreak: 
                { 
                    //
                    // Line Separator's BIDI class is Neutral (WS). It would take the class of surrounding 
                    // characters so it might end up in a reverse run. However, LS would not process Line Separator
                    // in reverse run. Here we always override the BIDI level of Line Separator to paragraph's
                    // embedding level such that it is out of reverse run and LS would process it correctly.
                    // 
                    uniformBidiLevel = (Pap.RightToLeft ? 1 : 0);
                    lsrun = CreateLineBreakLSRun( 
                        runInfo, 
                        stringLength,
                        out lsrunLength, 
                        out textRunLength
                        );
                    break;
                } 

                case Plsrun.ParaBreak: 
                { 
                    //
                    // Paragraph Separator ends the paragraph. Its bidi level must be the embedding level. 
                    //
                    Debug.Assert(uniformBidiLevel == (Pap.RightToLeft ? 1 : 0));
                    lsrun = CreateLineBreakLSRun(
                        runInfo, 
                        stringLength,
                        out lsrunLength, 
                        out textRunLength 
                        );
                    break; 
                }
                case Plsrun.Hidden:
                {
                    // hidden run yields the same cp as its lscp 
                    lsrunLength = runInfo.Length - offsetToFirstChar;
                    textRunLength = lsrunLength; 
                    lsrun = new LSRun( 
                        runInfo,
                        Plsrun.Hidden, 
                        PwchHidden,
                        textRunLength,
                        runInfo.OffsetToFirstCp,
                        (byte) uniformBidiLevel 
                        );
 
                    break; 
                }
            } 

            if(lsrun != null)
            {
                Debug.Assert(lsrunLength > 0); 

                if (lastBidiLevel != uniformBidiLevel) 
                { 
                    lastBidiLevel = CreateReverseLSRuns(uniformBidiLevel, lastBidiLevel);
                } 

                // Add the plsrun to the span vector.
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, lsrunLength, AddLSRun(lsrun), _plsrunVectorLatestPosition);
                _lscchUpTo += lsrunLength; 
            }
        } 
 

 
        /// 
        /// Break down text with uniform level into multiple shapeable runs,
        /// then create one LSRun for each of them.
        ///  
        private void CreateTextLSRuns(
            TextRunInfo       runInfo, 
            IList textEffects, 
            CultureInfo       digitCulture,
            int               offsetToFirstChar, 
            int               stringLength,
            int               uniformBidiLevel,
            ref int           lastBidiLevel
            ) 
        {
            ICollection shapeables = null; 
 
            ITextSymbols textSymbols = runInfo.TextRun as ITextSymbols;
 
            if (textSymbols != null)
            {
                shapeables = textSymbols.GetTextShapeableSymbols(
                    _settings.Formatter.GlyphingCache, 
                    new CharacterBufferReference(
                        runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar 
                        ), 
                    stringLength,
                    (uniformBidiLevel & 1) != 0, 
                    digitCulture,
                    runInfo.TextModifierScope
                    );
            } 
            else
            { 
                TextShapeableSymbols textShapeableSymbols = runInfo.TextRun as TextShapeableSymbols; 

                if (textShapeableSymbols != null) 
                {
                    shapeables = new TextShapeableSymbols[] { textShapeableSymbols };
                }
            } 

            if (shapeables == null) 
            { 
                Debug.Assert(false);
                throw new NotSupportedException(); 
            }

            double realToIdeal = _settings.Formatter.ToIdeal;
            int ich = 0; 

            foreach (TextShapeableSymbols shapeable in shapeables) 
            { 
                int cch = shapeable.Length;
                Debug.Assert(cch > 0 && cch <= stringLength - ich); 

                int currentBidiLevel = uniformBidiLevel;

                LSRun lsrun = new LSRun( 
                    runInfo,
                    textEffects, 
                    Plsrun.Text, 
                    runInfo.OffsetToFirstCp + offsetToFirstChar + ich,
                    cch, 
                    (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize),
                    runInfo.CharacterAttributeFlags,
                    new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar + ich, cch),
                    shapeable, 
                    realToIdeal,
                    (byte)currentBidiLevel 
                    ); 

                if (currentBidiLevel != lastBidiLevel) 
                {
                    lastBidiLevel = CreateReverseLSRuns(currentBidiLevel, lastBidiLevel);
                }
 
                // set up an LSRun for each shapeable.
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, cch, AddLSRun(lsrun), _plsrunVectorLatestPosition); 
                _lscchUpTo += cch; 

                ich += cch; 
            }
        }

 

        ///  
        /// Create LSRun for a linebreak run 
        /// 
        private LSRun CreateLineBreakLSRun( 
            TextRunInfo     runInfo,
            int             stringLength,
            out int         lsrunLength,
            out int         textRunLength 
            )
        { 
            lsrunLength = stringLength; 
            textRunLength = runInfo.Length;
 
            return new LSRun(
                runInfo,
                null, // No TextEffects on LineBreak
                runInfo.Plsrun, 
                runInfo.OffsetToFirstCp,
                runInfo.Length, 
                0,      // emSize 
                runInfo.CharacterAttributeFlags,
                new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar, stringLength), 
                null,   // no shapebale
                _settings.Formatter.ToIdeal,
                (byte)(Pap.RightToLeft ? 1 : 0)
                ); 
        }
 
 

 
        /// 
        /// Add new lsrun to lsrun list
        /// 
        /// lsrun to add 
        /// plsrun of added lsrun
        private Plsrun AddLSRun(LSRun lsrun) 
        { 
            if(lsrun.Type < Plsrun.FormatAnchor)
            { 
                return lsrun.Type;
            }

            Plsrun plsrun = (Plsrun)((uint)_lsrunList.Count + Plsrun.FormatAnchor); 

            if (lsrun.IsSymbol) 
            { 
                plsrun = MakePlsrunSymbol(plsrun);
            } 

            _lsrunList.Add(lsrun);

            return plsrun; 
        }
 
 
        #region lsrun/cp mapping
 
        /// 
        /// Map internal LSCP to text source cp
        /// 
        ///  
        /// This method does not handle mapping of LSCP beyond the last one
        ///  
        internal int GetExternalCp(int lscp) 
        {
            lscp -= _lscpFirstValue; 

            SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, lscp - _cpFirst);
            _plsrunVectorLatestPosition = rider.SpanPosition;
 
            return GetRun((Plsrun)rider.CurrentElement).OffsetToFirstCp +
                    lscp - rider.CurrentSpanStart; 
        } 

 
        /// 
        /// Get LSRun from plsrun
        /// 
        internal LSRun GetRun(Plsrun plsrun) 
        {
            plsrun = ToIndex(plsrun); 
 
            return  (LSRun)(
                IsContent(plsrun) ? 
                _lsrunList[(int)(plsrun - Plsrun.FormatAnchor)] :
                ControlRuns[(int)plsrun]
                );
        } 

 
        ///  
        /// Check if plsrun is marker
        ///  
        internal static bool IsMarker(Plsrun plsrun)
        {
            return (plsrun & Plsrun.IsMarker) != 0;
        } 

 
        ///  
        /// Make this plsrun a marker plsrun
        ///  
        internal static Plsrun MakePlsrunMarker(Plsrun plsrun)
        {
            return (plsrun | Plsrun.IsMarker);
        } 

 
        ///  
        /// Make this plsrun a symbol plsrun
        ///  
        internal static Plsrun MakePlsrunSymbol(Plsrun plsrun)
        {
            return (plsrun | Plsrun.IsSymbol);
        } 

 
        ///  
        /// Convert plsrun to index to lsrun list
        ///  
        internal static Plsrun ToIndex(Plsrun plsrun)
        {
            return (plsrun & Plsrun.UnmaskAll);
        } 

 
        ///  
        /// Check if run is content
        ///  
        internal static bool IsContent(Plsrun plsrun)
        {
            plsrun = ToIndex(plsrun);
            return plsrun >= Plsrun.FormatAnchor; 
        }
 
 
        /// 
        /// Check if character is space 
        /// 
        internal static bool IsSpace(char ch)
        {
            return ch == ' ' || ch == '\u00a0'; 
        }
 
 
        /// 
        /// Check if character is of strong directional type 
        /// 
        internal static bool IsStrong(char ch)
        {
            int unicodeClass = Classification.GetUnicodeClass(ch); 
            ItemClass itemClass = (ItemClass)Classification.CharAttributeOf(unicodeClass).ItemClass;
            return itemClass == ItemClass.StrongClass; 
        } 

 
        /// 
        /// Check if the run is a line break or paragraph break
        /// 
        internal static bool IsNewline (Plsrun plsrun) 
        {
            return plsrun == Plsrun.LineBreak || plsrun == Plsrun.ParaBreak; 
        } 

        ///  
        /// Check if the character is a line break or paragraph break character
        /// 
        internal static bool IsNewline(ushort flags)
        { 
            return ( (flags & (ushort) CharacterAttributeFlags.CharacterLineBreak) != 0
                  || (flags & (ushort) CharacterAttributeFlags.CharacterParaBreak) != 0 ); 
        } 

        #endregion 


        /// 
        /// Repositioning text lsruns according to its BaselineAlignmentment property 
        /// 
        internal void AdjustRunsVerticalOffset( 
            int             dcpLimit, 
            int             height,
            int             baselineOffset, 
            out int         cellHeight,
            out int         cellAscent
            )
        { 
            // Following are all alignment point offsets from the baseline of the line.
            // Value grows positively in paragraph flow direction. 
            int top = 0; 
            int bottom = 0;
            int textTop = 0; 
            int textBottom = 0;
            int super = 0;
            int sub = 0;
            int center = 0; 

            ArrayList lsruns = new ArrayList(3); 
 
            // Find TextTop from all Baseline lsruns
 
            int dcp = 0;
            int i = 0;
            while(dcp < dcpLimit)
            { 
                Debug.Assert(i < _plsrunVector.Count);
 
                Span span = _plsrunVector[i++]; 
                LSRun lsrun = GetRun((Plsrun)span.element);
 
                if(     lsrun.Type == Plsrun.Text
                    ||  lsrun.Type == Plsrun.InlineObject)
                {
                    if(lsrun.RunProp.BaselineAlignment == BaselineAlignment.Baseline) 
                    {
                        textTop = Math.Max(textTop, lsrun.BaselineOffset); 
                        textBottom = Math.Max(textBottom, lsrun.Descent); 
                    }
 
                    lsruns.Add(lsrun);
                }

                dcp += span.length; 
            }
 
            textTop = -textTop; // offset from the baseline in paragraph flow direction 

            top = height > 0 ? -baselineOffset : textTop; 

            // Finalize Bottom by ignoring all but Top, TextTop and Baseline lsruns

            foreach(LSRun lsrun in lsruns) 
            {
                switch (lsrun.RunProp.BaselineAlignment) 
                { 
                    case BaselineAlignment.Top:
                        textBottom = Math.Max(textBottom, lsrun.Height + top); 
                        break;

                    case BaselineAlignment.TextTop:
                        textBottom = Math.Max(textBottom, lsrun.Height + textTop); 
                        break;
                } 
            } 

            bottom = height > 0 ? height - baselineOffset : textBottom; 


            // hardcode the positions for now
            center = (top + bottom) / 2; 
            sub = bottom / 2;
            super = top * 2 / 3; 
 

            // Now move all lsruns according to its BaselineAlignment property 

            cellAscent = 0;
            int cellDescent = 0;
 
            foreach(LSRun lsrun in lsruns)
            { 
                int move = 0; 

                switch (lsrun.RunProp.BaselineAlignment) 
                {
                    case BaselineAlignment.Top:
                        // lsrun top to line top
                        move = top + lsrun.BaselineOffset; 
                        break;
 
                    case BaselineAlignment.Bottom: 
                        // lsrun bottom to line bottom
                        move = bottom - lsrun.Height + lsrun.BaselineOffset; 
                        break;

                    case BaselineAlignment.TextTop:
                        // lsrun top to line text top 
                        move = textTop + lsrun.BaselineOffset;
                        break; 
 
                    case BaselineAlignment.TextBottom:
                        // lsrun bottom to line text bottom 
                        move = textBottom - lsrun.Height + lsrun.BaselineOffset;
                        break;

                    case BaselineAlignment.Center: 
                        // lsrun center to line center
                        move = center - lsrun.Height/2 + lsrun.BaselineOffset; 
                        break; 

                    case BaselineAlignment.Superscript: 
                        // lsrun baseline to line superscript
                        move = super;
                        break;
 
                    case BaselineAlignment.Subscript:
                        // lsrun baseline to line subscript 
                        move = sub; 
                        break;
                } 

                lsrun.Move(move);

                // Recalculate line ascent and descent 
                cellAscent = Math.Max(cellAscent, lsrun.BaselineOffset - move);
                cellDescent = Math.Max(cellDescent, lsrun.Descent + move); 
            } 

            cellHeight = cellAscent + cellDescent; 
        }


        ///  
        /// Collect a piece of raw text that makes up a word containing the specified LSCP.
        /// The text returned from this method is used for hyphenation of a single word. 
        /// In addition to the raw text, it also returns the mapping between the raw character 
        /// indices and the LSCP indices in plsrunVector. This is used later on when we
        /// map the lexical result back to the positions used to communicate with LS. 
        /// 
        /// 
        /// "word" here is not meant for a linguistic term. It only means array of characters
        /// from space to space i.e. a word in SE Asian language is not separated by spaces. 
        /// 
        internal char[] CollectRawWord( 
            int                 lscpCurrent, 
            bool                isCurrentAtWordStart,
            out int             lscpChunk, 
            out int             lscchChunk,
            out CultureInfo     textCulture,
            out int             cchWordMax,
            out SpanVector textVector 
            )
        { 
            // Fetch the lsrun containing the current position make sure 
            // all LSCP before it are properly retained in plsrun vector.
            // We need this before we could reliably walk back the plsrun 
            // vector to establish the chunk's start position in the following
            // step.

            textVector = new SpanVector(); 
            textCulture = null;
 
            lscpChunk = lscpCurrent; 
            lscchChunk = 0;
            cchWordMax = 0; 

            Plsrun plsrun;
            int lsrunOffset;
            int lsrunLength; 

            LSRun lsrun = FetchLSRun( 
                lscpCurrent, 
                out plsrun,
                out lsrunOffset, 
                out lsrunLength
                );

            if (lsrun == null) 
                return null;
 
            textCulture = lsrun.TextCulture; 

            int lscpLim; 
            int cchBefore = 0;

            if (!isCurrentAtWordStart && lscpChunk > _cpFirst)
            { 
                // The specified position may not be start of word.
                // Expand backward to the first non-space character following a space 
                // before the current position. If the current position is already 
                // at space, no skip is needed.
 
                SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition);

                do
                { 
                    rider.At(lscpChunk - _cpFirst - 1);
 
                    lscpLim = rider.CurrentSpanStart + _cpFirst; 

                    lsrun = GetRun((Plsrun)rider.CurrentElement); 

                    if (   IsNewline(lsrun.Type)
                        || lsrun.Type == Plsrun.InlineObject)
                    { 
                        // Stop expanding due to hard break
                        break; 
                    } 

                    if (lsrun.Type == Plsrun.Text) 
                    {
                        if (!lsrun.TextCulture.Equals(textCulture))
                        {
                            // Stop expanding due to change of text culture 
                            break;
                        } 
 
                        int cchLim = lscpChunk - lscpLim;
                        int ichFirst = lsrun.OffsetToFirstChar + lscpChunk - _cpFirst - rider.CurrentSpanStart; 
                        int cch = 0;

                        // Skip all non-space characters until a space is found
                        while (cch < cchLim && !IsSpace(lsrun.CharacterBuffer[ichFirst - cch - 1])) 
                            cch++;
 
                        cchBefore += cch; 

                        if (cch < cchLim) 
                        {
                            // Start of chunk is found
                            lscpChunk -= cch;
                            break; 
                        }
                    } 
 
                    // Reposition start of chunk to the beginning of the current run and continue expanding
                    Invariant.Assert(lscpLim < lscpChunk); 
                    lscpChunk = lscpLim;

                } while (lscpChunk > _cpFirst && cchBefore <= MaxCchWordToHyphenate);
 
                _plsrunVectorLatestPosition = rider.SpanPosition;
            } 
 
            if (cchBefore > MaxCchWordToHyphenate)
            { 
                // The word is unusually long. This is already a situation we dont want
                // bring hyphenation into the picture.
                return null;
            } 

 
            // Expand forward from the beginning of a word to the end of the word. 
            // If the start position is already at space, skip passed all leading spaces
 
            StringBuilder stringBuilder = null;
            int lscp = lscpChunk;
            int cchText = 0;
            int cchLastWord = 0; 

            do 
            { 
                lsrun = FetchLSRun(
                    lscp, 
                    out plsrun,
                    out lsrunOffset,
                    out lsrunLength
                    ); 

                if (lsrun == null) 
                    return null; 

                lscpLim = lscp + lsrunLength; 

                if (   IsNewline(lsrun.Type)
                    || lsrun.Type == Plsrun.InlineObject)
                { 
                    // Stop expanding due to hard break
                    break; 
                } 

                if (lsrun.Type == Plsrun.Text) 
                {
                    if (!lsrun.TextCulture.Equals(textCulture))
                    {
                        // Stop expanding due to change of text culture 
                        break;
                    } 
 
                    int cchLim = lscpLim - lscp;
                    int ichFirst = lsrun.OffsetToFirstChar + lsrun.Length - lsrunLength; 
                    int cch = 0;

                    if (cchText == 0)
                    { 
                        // Skip all leading spaces
                        while (cch < cchLim && IsSpace(lsrun.CharacterBuffer[ichFirst + cch])) 
                            cch++; 
                    }
 
                    // Skip all non-space characters until a following space is found
                    char ch;
                    int cchWord = cchLastWord;
 
                    while (     cch < cchLim
                            &&  cchText + cch < MaxCchWordToHyphenate 
                            &&  !IsSpace((ch = lsrun.CharacterBuffer[ichFirst + cch]))) 
                    {
                        cch++; 

                        if (IsStrong(ch))
                        {
                            cchWord++; 
                        }
                        else 
                        { 
                            // Non-strong character marks the end of the current word length,
                            // Keep the length of the greatest length word found so far. 
                            if (cchWord > cchWordMax)
                                cchWordMax = cchWord;

                            cchWord = 0; 
                        }
                    } 
 
                    // Keep the length so far of the last word found.
                    cchLastWord = cchWord; 

                    if (cchLastWord > cchWordMax)
                    {
                        // Keep the length of the greatest length word found so far. 
                        cchWordMax = cchLastWord;
                    } 
 
                    if (stringBuilder == null)
                        stringBuilder = new StringBuilder(); 

                    // Gathering the raw text
                    lsrun.CharacterBuffer.AppendToStringBuilder(stringBuilder, ichFirst, cch);
 
                    // Keep the map between indices to raw text and its correspondent LSCP
                    textVector.Set(cchText, cch, lscp - lscpChunk); 
 
                    cchText += cch;
 
                    if (cch < cchLim)
                    {
                        // End of chunk is found
                        lscp += cch; 
                        break;
                    } 
                } 

                Invariant.Assert(lscpLim > lscp); 
                lscp = lscpLim;

            } while (cchText < MaxCchWordToHyphenate);
 
            if (stringBuilder == null)
                return null; 
 
            lscchChunk = lscp - lscpChunk;
            Invariant.Assert(stringBuilder.Length == cchText); 

            char[] rawText = new char[stringBuilder.Length];
            stringBuilder.CopyTo(0, rawText, 0, rawText.Length);
            return rawText; 
        }
 
 
        /// 
        /// Fetch cached inline metrics 
        /// 
        /// text object to format
        /// firs cp of text object
        /// inline's current pen position 
        /// line's right margin
        /// inline info 
        ///  
        /// Right margin is not necessarily the same as column max width. Right margin
        /// is usually greater than actual column width during object formatting. LS 
        /// increases the margin to 1/32 of the column width to provide a leaway for
        /// breaking.
        ///
        /// However TextBlock/TextFlow functions in such a way that it needs to know the exact width 
        /// left in the line in order to compute the inline's correct size. We make sure
        /// that it'll never get oversize max width. 
        /// 
        /// Inline object's reported size can be so huge that it may overflow LS's maximum value.
        /// If a given width is a finite value, we'll respect that and out-of-range exception may be thrown as appropriate. 
        /// If the width is Positive Infinity, the width is trimmed to the maximum remaining value that LS can handle. This is
        /// appropriate for the cases where client measures inline objects at Infinite size.
        /// 
        internal TextEmbeddedObjectMetrics FormatTextObject( 
            TextEmbeddedObject  textObject,
            int                 cpFirst, 
            int                 currentPosition, 
            int                 rightMargin
            ) 
        {
            if(_textObjectMetricsVector == null)
            {
                _textObjectMetricsVector = new SpanVector(null); 
            }
 
            SpanRider rider = new SpanRider(_textObjectMetricsVector); 
            rider.At(cpFirst);
 
            TextEmbeddedObjectMetrics metrics = (TextEmbeddedObjectMetrics)rider.CurrentElement;

            if(metrics == null)
            { 
                int widthLeft = _formatWidth - currentPosition;
 
                if(widthLeft <= 0) 
                {
                    // we're formatting this object outside the actual column width, 
                    // we give the host the max width from the current position up
                    // to the margin.
                    widthLeft = rightMargin - _formatWidth;
                } 

                double idealToReal = _settings.Formatter.ToReal; 
 
                metrics = textObject.Format(idealToReal * widthLeft);
 
                if (Double.IsPositiveInfinity(metrics.Width))
                {
                    // If the inline object has Width to be positive infinity, trim the width to
                    // the maximum value that LS can handle. 
                    metrics = new TextEmbeddedObjectMetrics(
                        idealToReal * (Constants.InfiniteWidth - currentPosition), 
                        metrics.Height, 
                        metrics.Baseline
                        ); 
                }
                else if (metrics.Width > idealToReal * (Constants.InfiniteWidth - currentPosition))
                {
                    // LS cannot compute value greater than its maximum computable value 
                    throw new ArgumentException(SR.Get(SRID.TextObjectMetrics_WidthOutOfRange));
                } 
 
                _textObjectMetricsVector.SetReference(cpFirst, textObject.Length, metrics);
            } 

            Debug.Assert(metrics != null);
            return metrics;
        } 

 
        #region ENUMERATIONS & CONST 

        // first negative cp of bullet marker 
        internal const int LscpFirstMarker = (-0x7FFFFFFF);

        // Note: Trident uses this figure
        internal const int TypicalCharactersPerLine = 100; 

        // Hyphen 
        internal const char CharHyphen = '\x002d'; 

        internal const char CharLineSeparator = '\x2028'; 
        internal const char CharParaSeparator = '\x2029';
        internal const char CharLinefeed      = '\x000a';

        // Hardcoded strings in LS memory 
        // They are kept as a pointer such that it would be fast to return
        // them as pointers back into LS. 
        internal static IntPtr PwchParaSeparator; 
        internal static IntPtr PwchLineSeparator;
        internal static IntPtr PwchNbsp; 
        internal static IntPtr PwchHidden;
        internal static IntPtr PwchObjectTerminator;
        internal static IntPtr PwchObjectReplacement;
 

        /// !! DO NOT update this enum without looking at its unmanaged pair in lslo.cpp !! 
        /// [wchao, 10-1-2001] 
        //
        internal enum ObjectId : ushort 
        {
            Reverse         = 0,
            MaxNative       = 1,
            InlineObject    = 1, 
            Max             = 2,
            Text_chp        = 0xffff, 
        } 

        // Maximum number of characters per line. If we exceed this number of characters 
        // without breaking in a normal way, we chop off the line by generating a fake
        // line break run. This is to mitigate potential denial-of-service attacks by
        // ensuring that there is a reasonable upper bound on the time required to
        // format a single line. 
        //
        // In ordinary documents with word-wrap enabled, we should always reach the 
        // right margin long before MaxCharactersPerLine characters. The only reason 
        // we might forcibly break the line in such cases would be:
        //   (a) Extreme right margin 
        //   (b) Extreme number of zero-width characters
        //   (c) Extremely small font size (i.e., fraction of a pixel)
        //   (d) Lack of break opportunity (e.g., no spaces)
        // All of these are security cases, for which chopping off the line is a 
        // reasonable mitigation. Limiting the line length addresses all of these
        // potential attacks so we don't need separate mitigations for, e.g., 
        // zero-width characters. 
        //
        // Extremely long lines are less unlikely in nowrap scenarios, such as a code 
        // editor. However, the same security issues apply so we still chop off the line
        // if we exceed the maximum number of characters with no line break. Note that
        // Notepad (with nowrap) does the same thing.
        // 
        // The value chosen corresponds roughly to four pages of text at 60 characters
        // per line and 40 lines per page. Testing shows this to be a resonable limit 
        // in terms of run time. 
        private const int MaxCharactersPerLine = 9600; // 60 * 40 * 4
 
        /// 
        /// The maximum number of characters within a single word that is still considered a legitimate
        /// input for hyphenation. This value is suggested by Stefanie Schiller - the NLG expert when
        /// considering a theoretical example of a German compound word which consists of 12 compound 
        /// segments. The following is that word.
        /// 
        /// "DONAUDAMPFSCHIFFAHRTSELEKTRIZITAETENHAUPTBETRIEBSWERKBAUUNTERBEAMTENGESELLSCHAFT" 
        ///
        /// [Wchao, 3/15/2006] 
        /// 
        private const int MaxCchWordToHyphenate = 80;

        #endregion 

        #region Properties 
        internal FormatSettings Settings 
        {
            get { return _settings; } 
        }

        internal ParaProp Pap
        { 
            get { return _settings.Pap; }
        } 
 
        internal int CpFirst
        { 
            get { return _cpFirst; }
        }

        internal SpanVector PlsrunVector 
        {
            get { return _plsrunVector; } 
        } 

        internal ArrayList LsrunList 
        {
            get { return _lsrunList; }
        }
 
        internal int FormatWidth
        { 
            get { return _formatWidth; } 
        }
 
        internal int CchEol
        {
            get { return _cchEol; }
            set { _cchEol = value; } 
        }
        #endregion 
    } 

 
    /// 
    /// Bidi state that applie across line. If no preceding state is available internally,
    /// it calls back to the client to obtain additional Bidi control and explicit embedding level.
    ///  
    internal sealed class BidiState : Bidi.State
    { 
        public BidiState(FormatSettings settings, int cpFirst) 
            : this(settings, cpFirst, null)
        { 
        }

        public BidiState(FormatSettings settings, int cpFirst, TextModifierScope modifierScope)
            : base (settings.Pap.RightToLeft) 
        {
            _settings = settings; 
            _cpFirst = cpFirst; 

            NumberClass = DirectionClass.ClassInvalid; 
            StrongCharClass = DirectionClass.ClassInvalid;


            // find the top most scope that has the direction embedding 
            while ( modifierScope != null && !modifierScope.TextModifier.HasDirectionalEmbedding)
            { 
                modifierScope = modifierScope.ParentScope; 
            }
 
            if (modifierScope != null)
            {
                _cpFirstScope = modifierScope.TextSourceCharacterIndex;
 
                // Initialize Bidi stack base on modifier scope
                Bidi.BidiStack stack = new Bidi.BidiStack(); 
                stack.Init(LevelStack); 

                ushort overflowLevels = 0; 
                InitLevelStackFromModifierScope(stack, modifierScope, ref overflowLevels);

                LevelStack = stack.GetData();
                Overflow = overflowLevels; 
            }
        } 
 

        ///  
        /// Set the default last strongs when an embedding level is changed such that
        /// ambiguous characters (i.e. characters with null or InvariantCulture) at the beginning
        /// of the current embedding level can be resolved correctly.
        ///  
        internal void SetLastDirectionClassesAtLevelChange()
        { 
            if ((CurrentLevel & 1) == 0) 
            {
                LastStrongClass = DirectionClass.Left; 
                LastNumberClass = DirectionClass.Left;
            }
            else
            { 
                LastStrongClass = DirectionClass.ArabicLetter;
                LastNumberClass = DirectionClass.ArabicNumber; 
            } 
        }
 
        internal byte CurrentLevel
        {
            get { return Bidi.BidiStack.GetMaximumLevel(LevelStack); }
        } 

 
        ///  
        /// Method to get the last number class overridden by bidi algorithm implementer
        ///  
        public override DirectionClass LastNumberClass
        {
            get
            { 
                if (this.NumberClass == DirectionClass.ClassInvalid )
                { 
                    GetLastDirectionClasses(); 
                }
 
                return this.NumberClass;
            }

            set { this.NumberClass = value; } 
        }
 
 
        /// 
        /// Method to get the last strong class overridden by bidi algorithm implementer 
        /// 
        public override DirectionClass LastStrongClass
        {
            get 
            {
                if (this.StrongCharClass == DirectionClass.ClassInvalid) 
                { 
                    GetLastDirectionClasses();
                } 
                return this.StrongCharClass;
            }

            set 
            {
                this.StrongCharClass = value; 
                this.NumberClass = value; 
            }
        } 


        /// 
        /// Last strong class not found internally, call out to client 
        /// 
        private void GetLastDirectionClasses() 
        { 
            DirectionClass  strongClass = DirectionClass.ClassInvalid;
            DirectionClass  numberClass = DirectionClass.ClassInvalid; 

            // It is a flag to indicate whether to continue calling GetPrecedingText.
            // Because Bidi algorithm works within a paragraph only, we should terminate the
            // loop at paragraph boundary and fall back to the appropriate defaults. 

            bool continueScanning = true; 
 
            while (continueScanning && _cpFirst > _cpFirstScope)
            { 
                TextSpan textSpan = _settings.GetPrecedingText(_cpFirst);
                CultureSpecificCharacterBufferRange charString = textSpan.Value;

                if (textSpan.Length <= 0) 
                {
                    break;  // stop when preceding text span has length 0. 
                } 

                if (!charString.CharacterBufferRange.IsEmpty) 
                {
                    continueScanning = Bidi.GetLastStongAndNumberClass(
                        charString.CharacterBufferRange,
                        ref strongClass, 
                        ref numberClass
                        ); 
 
                    if (strongClass != DirectionClass.ClassInvalid)
                    { 
                        this.StrongCharClass = strongClass;

                        if (this.NumberClass == DirectionClass.ClassInvalid)
                        { 
                            if (numberClass == DirectionClass.EuropeanNumber)
                            { 
                                // Override EuropeanNumber class as appropriate. 
                                numberClass = GetEuropeanNumberClassOverride(CultureMapper.GetSpecificCulture(charString.CultureInfo));
                            } 

                            this.NumberClass = numberClass;
                        }
 
                        break;
                    } 
                } 

                _cpFirst -= textSpan.Length; 
            }


            // If we don't have the strong class and/or number class, select appropriate defaults 
            // according to the base bidi level.
            // 
            // To determine the base bidi level, we look at bit 0 if the LevelStack. This is NOT 
            // an even/odd test. LevelStack is an array of bits corresponding to all of the bidl
            // levels on the stack. Thus, bit 0 is set if and only if the base bidi level is zero, 
            // i.e., it's a left-to-right paragraph.

            if(strongClass == DirectionClass.ClassInvalid)
            { 
                this.StrongCharClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicLetter;
            } 
 
            if(numberClass == DirectionClass.ClassInvalid)
            { 
                this.NumberClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicNumber;
            }
        }
 
        /// 
        /// Walk the TextModifierScope to reinitialize the bidi stack. 
        /// We push to bidi-stack from the earliest directional modifier (i.e. from bottom of the 
        /// the scope chain onwards). We use a stack to reverse the scope chain first.
        ///  
        private static void InitLevelStackFromModifierScope(
            Bidi.BidiStack    stack,
            TextModifierScope scope,
            ref ushort        overflowLevels 
            )
        { 
            Stack directionalEmbeddingStack = new Stack(32); 

            for (TextModifierScope currentScope = scope; currentScope != null; currentScope = currentScope.ParentScope) 
            {
                if (currentScope.TextModifier.HasDirectionalEmbedding)
                {
                    directionalEmbeddingStack.Push(currentScope.TextModifier); 
                }
            } 
 
            while (directionalEmbeddingStack.Count > 0)
            { 
                TextModifier modifier = directionalEmbeddingStack.Pop();

                if (overflowLevels > 0)
                { 
                    // Bidi level stack overflows. Just increment the bidi stack overflow number
                    overflowLevels ++; 
                } 
                else if (!stack.Push(modifier.FlowDirection == FlowDirection.LeftToRight))
                { 
                    // Push stack not successful. Stack starts to overflow.
                    overflowLevels = 1;
                }
 
            }
        } 
 
        /// 
        /// Obtain the explict direction class of European number based on culture and current flow direction. 
        /// European numbers in Arabic/---- culture and RTL flow direction are to be considered as Arabic numbers.
        /// 
        internal DirectionClass GetEuropeanNumberClassOverride(CultureInfo cultureInfo)
        { 
            if (   cultureInfo != null
                 &&(   (cultureInfo.LCID & 0xFF) == 0x01 // Arabic culture 
                    || (cultureInfo.LCID & 0xFF) == 0x29 // ---- culture 
                   )
                 && (CurrentLevel & 1) != 0 // RTL flow direction 
                )
            {
                return DirectionClass.ArabicNumber;
            } 

            return DirectionClass.EuropeanNumber; 
        } 

        private FormatSettings  _settings; 
        private int             _cpFirst;
        private int             _cpFirstScope; // The first Cp of the current scope. GetLastStrong() should not go beyond it.
    }
} 

// 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
// 
//  File:      TextStore.cs
// 
//  Contents:  FullTextLine text store 
//
//  Created:   10-3-2002 Worachai Chaoweeraprasit (wchao) 
//
//-----------------------------------------------------------------------

 
using System;
using System.Text; 
using System.Globalization; 
using System.Windows;
using System.Windows.Media; 
using System.Windows.Media.TextFormatting;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; 
using System.Runtime.InteropServices;
using MS.Internal.Shaping; 
using MS.Internal.Generic; 
using System.Security;
using System.Security.Permissions; 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;

namespace MS.Internal.TextFormatting 
{
    ///  
    /// FullTextLine text store 
    /// 
    ///  
    ///
    ///     Text store produces and keeps 'lsrun' of different type. Each type has its own
    ///     characteristics and each lsrun involves with three different 'character length'
    ///     values created and used for different purpose by different component. 
    ///
    ///     plsrunSpan.length:  character length created by text store used by LS. 
    ///     lsrun.Length:       character length assigned and used by TextSource. 
    ///     lsrun.StringLength: character length created by text store used by bidi algorithm.
    /// 
    ///
    ///                     plsrunSpan.length  lsrun.Length  lsrun.StringLength
    ///     CloseAnchor             1               0               1
    ///     Reverse                 1               0               1 
    ///     FakeLineBreak           1               0               1
    ///     FormatAnchor            1               1               1 
    ///     Hidden                  n               n               1 
    ///     Text                    n               n               n
    ///     InlineObject            1               n               1 
    ///     LineBreak               1               n               1
    ///
    ///     FakeLineBreak is used to chop off a line by simulating a line break when
    ///     none is actually present in the backing store. This is done as a security 
    ///     mitigation for malicious text where we might otherwise spend too much time
    ///     formatting a line. The IsForceBreakRequired method contains the mitigation 
    ///     logic; see also the comment for MaxCharactersPerLine. 
    ///
    ///  
    internal class TextStore
    {
        private FormatSettings          _settings;                  // format settings
        private int                     _lscpFirstValue;            // first lscp value 
        private int                     _cpFirst;                   // store first cp (both cp and lscp start out the same)
        private int                     _lscchUpTo;                 // number of lscp resolved 
        private int                     _cchUpTo;                   // number of cp resolved 
        private int                     _cchEol;                    // number of chars for end-of-line mark
        private int                     _accNominalWidthSoFar;      // accumulated nominal width so far 
        private int                     _accTextLengthSoFar;        // accumulated count of text characters so far
        private NumberContext           _numberContext;             // cached number context for contextual digit substitution
        private int                     _cpNumberContext;           // cp at which _numberContext is valid
 
        private SpanVector              _plsrunVector;
        private SpanPosition            _plsrunVectorLatestPosition; 
        private ArrayList               _lsrunList;                 // lsrun list 
        private BidiState               _bidiState;                 // (defer initialization until FetchRun)
        private TextModifierScope       _modifierScope;             // top-most frame of the text modifier stack, or null 

        private int                     _formatWidth;               // formatting width LS sees
        private SpanVector              _textObjectMetricsVector;   // inline object cache
 
        internal static LSRun[]          ControlRuns;               // Control text runs e.g. Bidi reversal
 
 
        /// 
        /// Initialize all static members 
        /// 
        static TextStore()
        {
            EscStringInfo esc = new EscStringInfo(); 

            UnsafeNativeMethods.LoGetEscString(ref esc); 
 
            ControlRuns = new LSRun[3];
 
            ControlRuns[0] = new LSRun(Plsrun.CloseAnchor, esc.szObjectTerminator);
            ControlRuns[1] = new LSRun(Plsrun.Reverse, esc.szObjectReplacement);
            ControlRuns[2] = new LSRun(Plsrun.FakeLineBreak, esc.szLineSeparator);
 
            PwchNbsp              = esc.szNbsp;
            PwchHidden            = esc.szHidden; 
            PwchParaSeparator     = esc.szParaSeparator; 
            PwchLineSeparator     = esc.szLineSeparator;
            PwchObjectReplacement = esc.szObjectReplacement; 
            PwchObjectTerminator  = esc.szObjectTerminator;
        }

 
        /// 
        /// Constructing an intermediate text store for FullTextLine 
        ///  
        /// text formatting settings
        /// first cp of the line 
        /// lscp first value
        /// formatting width LS sees
        public TextStore(
            FormatSettings          settings, 
            int                     cpFirst,
            int                     lscpFirstValue, 
            int                     formatWidth 
            )
        { 
            _settings = settings;
            _formatWidth = formatWidth;

            _cpFirst = cpFirst; 
            _lscpFirstValue = lscpFirstValue;
 
            _lsrunList = new ArrayList(2); 
            _plsrunVector = new SpanVector(null);
            _plsrunVectorLatestPosition = new SpanPosition(); 

            // Recreate the stack of text modifiers if there is one.
            TextLineBreak previousLineBreak = settings.PreviousLineBreak;
 
            if (    previousLineBreak != null
                &&  previousLineBreak.TextModifierScope != null) 
            { 
                _modifierScope = previousLineBreak.TextModifierScope.CloneStack();
 
                // Construct bidi state from input settings and modifier scopes
                _bidiState = new BidiState(_settings, _cpFirst, _modifierScope);
            }
        } 

 
        ///  
        /// Fetch lsrun at the specified LSCP
        ///  
        /// lscp to fetch
        /// plsrun of lsrun being fetched
        /// offset from the start of the LSRun to the specified lscp
        /// distance from the specified lscp to the end of the LSRun 
        /// lsrun being fetched
        internal LSRun FetchLSRun( 
            int             lscpFetch, 
            out Plsrun      plsrun,
            out int         lsrunOffset, 
            out int         lsrunLength
            )
        {
            lscpFetch -= _lscpFirstValue; 

            Invariant.Assert(lscpFetch >= _cpFirst); 
 
            if (_cpFirst + _lscchUpTo <= lscpFetch)
            { 
                ushort charFlagsSoFar = 0;
                ushort bidiCharFlagsSoFar = 0;
                int cchResolved = 0;
                int cchFetched = _cchUpTo; 
                int cch = 0;
                int cchText = 0; 
 
                SpanVector runInfoVector     = new SpanVector(null);
                SpanVector textEffectsVector = new SpanVector(null); 
                byte[] bidiLevels = null;
                int lastBidiLevel = GetLastLevel();

                // Fetch runs until enough characters get resolved by bidi algorithm 
                do
                { 
                    // Read runs up ahead to the point where accumulated run width exceeds the upper limit value. 
                    // We do this to optimize the cost of breaking down lsruns by doing as much as we can in
                    // one go, thus generating smaller setup cost of run fetching. 

                    TextRunInfo runInfo;

                    do 
                    {
                        runInfo = FetchTextRun(_cpFirst + cchFetched); 
 
                        if (runInfo == null)
                        { 
                            // no more content to fetch
                            break;
                        }
 
                        if (  _bidiState == null
                            && 
                               (   IsDirectionalModifier(runInfo.TextRun as TextModifier) 
                                || IsEndOfDirectionalModifier(runInfo)
                               ) 
                            )
                        {
                            // When directional embedding TextModifier or corresponding TextEndOfSegment
                            // is encountered, we need to do bidi analysis to correctly update the bidi state. 
                            // We create a bidi state to trigger bidi analysis.
                            _bidiState = new BidiState(_settings, _cpFirst); 
                        } 

                        int stringLength = runInfo.StringLength; 

                        if(runInfo.TextRun is ITextSymbols)
                        {
                            // Let stopMask specify which types of characters need to be isolated in special runs. 
                            // Let bidiMask specify which types of characters require bidi analysis.
                            ushort stopMask; 
                            ushort bidiMask; 

                            if (!runInfo.IsSymbol) 
                            {
                                // It's an ordinary Unicode font. Isolate various line breaks and format anchor.
                                stopMask = (ushort)(CharacterAttributeFlags.CharacterLineBreak |
                                        CharacterAttributeFlags.CharacterParaBreak | 
                                        CharacterAttributeFlags.CharacterFormatAnchor);
 
                                // Mask of character flags that require us to perform bidi analysis. 
                                bidiMask = (ushort)(CharacterAttributeFlags.CharacterRTL);
                            } 
                            else
                            {
                                // It's a non-Unicode font, meaning code points have non-standard meanings. The only
                                // characters we recognize as line breaks are LF (0x0A) and CR (0x0D). 
                                stopMask = (ushort)(CharacterAttributeFlags.CharacterCRLF |
                                        CharacterAttributeFlags.CharacterFormatAnchor); 
 
                                // Layout is always left-to-right for non-Unicode fonts.
                                bidiMask = 0; 
                            }

                            // Scan until end-of-run or one of the characters specified by stopMask. The accumulated
                            // flags of the characters we advanced past are stored in charFlags. 
                            ushort charFlags;
                            stringLength = Classification.AdvanceUntilUTF16( 
                                runInfo.CharacterBuffer, 
                                runInfo.OffsetToFirstChar,
                                runInfo.Length, // text is chopped at run length not string length 
                                stopMask,
                                out charFlags
                                );
 
                            // If it's a non-Unicode font we may have advanced past line break characters,
                            // but if so we don't want to treat them as such. 
                            charFlags &= (ushort)~(CharacterAttributeFlags.CharacterLineBreak | CharacterAttributeFlags.CharacterParaBreak); 

                            if(stringLength <= 0) 
                            {
                                // There are special characters such as various linebreaks or format anchor
                                // character in the middle of text stream. We isolate such characters into
                                // a separate run and treat them accordingly. 

                                runInfo = CreateSpecialRunFromTextContent(runInfo, cchFetched); 
                                stringLength = runInfo.StringLength; 
                                charFlags = runInfo.CharacterAttributeFlags;
 
                                Debug.Assert(stringLength > 0 && runInfo.Length > 0);
                            }
                            else if(stringLength != runInfo.Length)
                            { 
                                // shorten the run length if the character string is being cut short
                                runInfo.Length = stringLength; 
                            } 

                            runInfo.CharacterAttributeFlags |= charFlags; 
                            charFlagsSoFar |= charFlags;
                            bidiCharFlagsSoFar |= (ushort)(charFlags & bidiMask);
                            cchText += stringLength;
                        } 

                        _accNominalWidthSoFar += runInfo.GetRoughWidth(_settings.Formatter.ToIdeal); 
 
                        // store up the run info in a span indexed by actual character index
                        runInfoVector.SetReference(cch, stringLength, runInfo); 

                        TextEffectCollection textEffects = (runInfo.Properties != null) ? runInfo.Properties.TextEffects : null;
                        if (textEffects != null && textEffects.Count != 0)
                        { 
                            SetTextEffectsVector(textEffectsVector, cch, runInfo, textEffects);
                        } 
 
                        cch += stringLength;
 
                        cchFetched += runInfo.Length;

                    } while(
                            _accNominalWidthSoFar < _formatWidth 
                        && !runInfo.IsEndOfLine
                        && !IsNewline(charFlagsSoFar) 
                        && _accTextLengthSoFar + cchText <= MaxCharactersPerLine 
                        );
 

                    // if bidi is detected, resolve all fetched runs

                    if (   lastBidiLevel > 0 
                        || bidiCharFlagsSoFar != 0
                        || _bidiState != null 
                       ) 
                    {
                        cchResolved = BidiAnalyze(runInfoVector, cch, out bidiLevels); 

                        // for security reasons, limit how far we'll scan ahead to resolve bidi levels
                        if (cchResolved == 0 && _accTextLengthSoFar + cchText >= MaxCharactersPerLine)
                        { 
                            cchResolved = cch;
                            bidiLevels = null; 
                        } 
                    }
                    else 
                    {
                        cchResolved = cch;
                    }
 
                } while(cchResolved <= 0);
 
                Debug.Assert( 
                        runInfoVector != null
                    &&  (   bidiLevels == null 
                        ||  cchResolved <= bidiLevels.Length)
                    );

                bool forceBreak = IsForceBreakRequired(runInfoVector, ref cchResolved); 

                if(bidiLevels == null) 
                { 
                    // no bidi detected, all characters are left-to-right
                    CreateLSRunsUniformBidiLevel( 
                        runInfoVector,
                        textEffectsVector,
                        _cchUpTo,
                        0, 
                        cchResolved,
                        0,  // uniformBidiLevel 
                        ref lastBidiLevel 
                        );
                } 
                else
                {
                    int runInfoFirstCp = _cchUpTo;
                    int ichUniform = 0; 

                    while(ichUniform < cchResolved) 
                    { 
                        int cchUniform = 1;
                        int uniformBidiLevel = bidiLevels[ichUniform]; 

                        while(  ichUniform + cchUniform < cchResolved
                            &&  bidiLevels[ichUniform + cchUniform] == uniformBidiLevel)
                        { 
                            cchUniform++;
                        } 
 
                        // create lsruns within a range of uniform level
                        CreateLSRunsUniformBidiLevel( 
                            runInfoVector,
                            textEffectsVector,
                            runInfoFirstCp,
                            ichUniform, 
                            cchUniform,
                            uniformBidiLevel, 
                            ref lastBidiLevel 
                            );
 
                        ichUniform += cchUniform;
                    }
                }
 
                if (forceBreak)
                { 
                    // close reverse runs 
                    if (lastBidiLevel != 0)
                    { 
                        lastBidiLevel = CreateReverseLSRuns(BaseBidiLevel, lastBidiLevel);
                    }

                    // add a fake linebreak 
                    _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition);
                    _lscchUpTo += 1; 
                } 
            }
 
            // lsrun at the specified lscp was created, just grab it and go
            return GrabLSRun(
                lscpFetch,
                out plsrun, 
                out lsrunOffset,
                out lsrunLength 
                ); 
        }
 
        /// 
        /// Wrapper to TextRun fetching from the cache
        /// 
        internal TextRunInfo  FetchTextRun(int cpFetch) 
        {
            int runLength; 
            TextRun textRun; 

            // fetch TextRun from the formatting state 
            CharacterBufferRange charString = _settings.FetchTextRun(
                cpFetch,
                _cpFirst,
                out textRun, 
                out runLength
                ); 
 
            CultureInfo digitCulture = null;
            bool contextualSubstitution = false; 
            bool symbolTypeface = false;

            Plsrun runType = TextRunInfo.GetRunType(textRun);
 
            if (runType == Plsrun.Text)
            { 
                TextRunProperties properties = textRun.Properties; 
                symbolTypeface = properties.Typeface.Symbol;
                if (!symbolTypeface) 
                {
                    _settings.DigitState.SetTextRunProperties(properties);
                    digitCulture = _settings.DigitState.DigitCulture;
                    contextualSubstitution = _settings.DigitState.Contextual; 
                }
            } 
 
            TextModifierScope currentScope = _modifierScope;
            TextModifier modifier = textRun as TextModifier; 

            if (modifier != null)
            {
                _modifierScope = new TextModifierScope( 
                    _modifierScope,
                    modifier, 
                    cpFetch 
                    );
 
                // The new scope inclues the current TextModifier run
                currentScope = _modifierScope;
            }
            else if (_modifierScope != null && textRun is TextEndOfSegment) 
            {
                // The new scope only affects subsequent runs. TextEndOfSegment run itself is 
                // still in the old scope such that its coresponding TextModifier run can be tracked. 
                _modifierScope = _modifierScope.ParentScope;
            } 

            return new TextRunInfo(
                charString,
                runLength, 
                cpFetch - _cpFirst, // offsetToFirstCp
                textRun, 
                runType, 
                0,   // charFlags
                digitCulture, 
                contextualSubstitution,
                symbolTypeface,
                currentScope
                ); 
        }
 
 
        /// 
        /// Split a TextRunInfo into multiple ranges each with a uniform set of 
        /// TextEffects.
        /// 
        /// 
        /// A TextRun can have a collection of TextEffect. Each of them can be applied to 
        /// an arbitrary range of text. This method breaks the TextRunInfo into sub-ranges
        /// that have identical set of TextEffects. For example 
        /// 
        /// Current Run :   |----------------------------------------|
        /// Effect 1:     |-----------------------------------------------------| 
        /// Effect 2:                  |------------------------|
        /// Splitted runs:  |----------|------------------------|----|
        ///
        /// It can be observed that the effected ranges are dividied at the boundaries of the 
        /// TextEffects. We sort all the boundaries of TextEffects according to their positions
        /// and create the effected range in between of any two ajacent boundaries. For each efffected 
        /// range, we store all the active TextEffect into a list. 
        /// 
        private void SetTextEffectsVector( 
            SpanVector              textEffectsVector,
            int                     ich,
            TextRunInfo             runInfo,
            TextEffectCollection    textEffects 
            )
        { 
            // We already check for empty text effects at the call site. 
            Debug.Assert(textEffects != null && textEffects.Count != 0);
 
            int cpFetched = _cpFirst + _cchUpTo + ich; // get text source character index

            // Offset from client Cp to text effect index.
            int offset = cpFetched - _settings.TextSource.GetTextEffectCharacterIndexFromTextSourceCharacterIndex(cpFetched); 

            int textEffectsCount = textEffects.Count; 
            TextEffectBoundary[] bounds = new TextEffectBoundary[textEffectsCount * 2]; 
            for (int i = 0; i < textEffectsCount; i++)
            { 
                TextEffect effect = textEffects[i];
                bounds[2 * i] = new TextEffectBoundary(effect.PositionStart, true); // effect starting boundary
                bounds[2 * i + 1] = new TextEffectBoundary(effect.PositionStart + effect.PositionCount, false); // effect end boundary
            } 

            Array.Sort(bounds); // sort the TextEffect bounds. 
 
            int effectedRangeStart = Math.Max(cpFetched - offset, bounds[0].Position);
            int effectedRangeEnd   = Math.Min(cpFetched - offset + runInfo.Length, bounds[bounds.Length - 1].Position); 

            int currentEffectsCount = 0;
            int currentPosition = effectedRangeStart;
            for (int i = 0; i < bounds.Length && currentPosition < effectedRangeEnd; i++) 
            {
                // Have we reached the end of a non-empty subrange with at least one text effect? 
                if (currentPosition < bounds[i].Position && currentEffectsCount > 0) 
                {
                    // Let [currentPosition,currentRangeEnd) delimit the subrange ending at bounds[i]. 
                    int currentRangeEnd = Math.Min(bounds[i].Position, effectedRangeEnd);

                    // Consolidate all the active effects in the subrange.
                    IList activeEffects = new TextEffect[currentEffectsCount]; 
                    int effectIndex = 0;
                    for (int j = 0; j < textEffectsCount; j++) 
                    { 
                        TextEffect effect = textEffects[j];
                        if (currentPosition >= effect.PositionStart && currentPosition < (effect.PositionStart + effect.PositionCount)) 
                        {
                            activeEffects[effectIndex++] = effect;
                        }
                    } 

                    Invariant.Assert(effectIndex == currentEffectsCount); 
 
                    // Set the active effects for this CP subrange. The vector index is relative
                    // to the starting cp of the current run-fetching loop. 
                    textEffectsVector.SetReference(
                        currentPosition + offset - _cchUpTo - _cpFirst,    // client cp index
                        currentRangeEnd - currentPosition,                 // length
                        activeEffects                                      // text effects 
                        );
 
                    currentPosition = currentRangeEnd; 
                }
 
                // Adjust the current count depending on if it is a TextEffect's starting or ending boundary.
                currentEffectsCount += (bounds[i].IsStart ? 1 : -1);

                if (currentEffectsCount == 0 && i < bounds.Length - 1) 
                {
                   // There is no effect on the current position. Move it to the start of next TextEffect. 
                   Invariant.Assert(bounds[i + 1].IsStart); 
                   currentPosition = Math.Max(currentPosition, bounds[i + 1].Position);
                } 
            }
        }

        ///  
        /// Structure representing one boundary of a TextEffect. Each TextEffect has
        /// two boundaries: the beginning and the end. 
        ///  
        private struct TextEffectBoundary : IComparable
        { 
            private readonly int _position;
            private readonly bool _isStart;

            internal TextEffectBoundary(int position, bool isStart) 
            {
                _position = position; 
                _isStart = isStart; 
            }
 
            internal int Position
            {
                get { return _position; }
            } 

            internal bool IsStart 
            { 
                get { return _isStart; }
            } 

            public int CompareTo(TextEffectBoundary other)
            {
                if (Position != other.Position) 
                    return Position - other.Position;
 
                if (IsStart == other.IsStart) return 0; 

                // Starting edge is always in front. 
                return IsStart ? -1 : 1;
            }
        }
 

        ///  
        /// Create special run that matches the content of specified text run 
        /// 
        ///  
        ///    Critical: This code has unsafe code block that uses pointers.
        ///    TreatAsSafe: This code does not expose the pointer and the call does bounds checking.
        /// 
        [SecurityCritical,SecurityTreatAsSafe] 
        private TextRunInfo CreateSpecialRunFromTextContent(
            TextRunInfo     runInfo, 
            int             cchFetched 
            )
        { 
            // -FORMAT ANCHOR-
            //
            // Format anchor character is what we create internally to drive LS. If it
            // is present in the middle of text stream sent from the client, we will 
            // have to filter it out and replace it with NBSP. This is to protect LS
            // from running into a bad state due to misinterpreting such character as 
            // our format anchor. Following is the list of anchor character we use todate. 
            //
            //      "\uFFFB" (Unicode 'Annotation Terminator') 
            //
            // -LINEBREAK-
            //
            // Following the Unicode guideline on newline characters, we recognize 
            // both LS (U+2028) and PS (U+2029) as explicit linebreak (PS also breaks
            // paragraph but that's handled outside line level formatting). We also 
            // treat the following sequence of characters as linebreak 
            //
            //      "CR"    ("\u000D") 
            //      "LF"    ("\u000A")
            //      "CRLF"  ("\u000D\u000A")
            //      "NEL"   ("\u0085")
            //      "VT"    ("\u000B") 
            //      "FF"    ("\u000C")
            // 
            // Note: http://www.unicode.org/unicode/reports/tr13/tr13-9.html 
            Debug.Assert(runInfo.StringLength > 0 && runInfo.Length > 0);
 
            CharacterBuffer charBuffer = runInfo.CharacterBuffer;
            int offsetToFirstChar = runInfo.OffsetToFirstChar;
            char firstChar = charBuffer[offsetToFirstChar];
            ushort charFlags; 

            charFlags = (ushort)Classification.CharAttributeOf( 
                (int)Classification.GetUnicodeClassUTF16(firstChar) 
                ).Flags;
 
            if ((charFlags & (ushort)CharacterAttributeFlags.CharacterLineBreak) != 0)
            {
                // Get cp length of newline sequence
                // 
                // It is possible that client run ends in between two codepoints that
                // make up a single newline sequence e.g. CRLF. Therefore, when we 
                // encounter the first codepoint of the sequence, we need to make sure 
                // we have enough codepoints to determine the correct whole sequence.
                // In an uncommon event, we may be forced to look ahead by fetching more 
                // runs. [wchao, PS bug 910308]

                int newlineLength = 1;  // most sequences take one cp
 
                if (firstChar == '\r')
                { 
                    if (runInfo.Length > 1) 
                    {
                        newlineLength += ((charBuffer[offsetToFirstChar + 1] == '\n') ? 1 : 0); 
                    }
                    else
                    {
                        TextRunInfo nextRunInfo = FetchTextRun(_cpFirst + cchFetched + 1); 

                        if (nextRunInfo != null && nextRunInfo.TextRun is ITextSymbols) 
                        { 
                            newlineLength += ((nextRunInfo.CharacterBuffer[nextRunInfo.OffsetToFirstChar] == '\n') ? 1 : 0);
                        } 
                    }
                }

                unsafe 
                {
                    runInfo = new TextRunInfo( 
                        new CharacterBufferRange((char*)PwchLineSeparator, 1), 
                        newlineLength, // run length
                        runInfo.OffsetToFirstCp, 
                        runInfo.TextRun,
                        Plsrun.LineBreak, // LineBreak run
                        charFlags,
                        null,  // digit culture 
                        false, // contextual substitution
                        false, // is not Unicode 
                        runInfo.TextModifierScope 
                        );
                } 
            }
            else if ((charFlags & (ushort)CharacterAttributeFlags.CharacterParaBreak) != 0)
            {
                unsafe 
                {
                    // This character is a paragraph separator. Split it into a 
                    // separate run. 
                    runInfo = new TextRunInfo(
                        new CharacterBufferRange((char*)PwchParaSeparator, 1), 
                        1,
                        runInfo.OffsetToFirstCp,
                        runInfo.TextRun,
                        Plsrun.ParaBreak,  // parabreak run 
                        charFlags,
                        null,   // digit culture 
                        false,  // contextual substitution 
                        false,  // is not Unicode
                        runInfo.TextModifierScope 
                        );
                }
            }
            else 
            {
                Invariant.Assert((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0); 
 
                unsafe
                { 
                    runInfo = new TextRunInfo(
                        new CharacterBufferRange((char*)PwchNbsp, 1),
                        1, // run length
                        runInfo.OffsetToFirstCp, 
                        runInfo.TextRun,
                        runInfo.Plsrun, 
                        charFlags, 
                        null,   // digit culture
                        false,  // contextual substitution 
                        false,  // is not Unicode
                        runInfo.TextModifierScope
                        );
                } 
            }
 
            return runInfo; 
        }
 

        /// 
        /// Grab existing lsrun at specified LSCP
        ///  
        private LSRun GrabLSRun(
            int             lscpFetch, 
            out Plsrun      plsrun, 
            out int         lsrunOffset,
            out int         lsrunLength 
            )
        {
            int offsetToFirstCp = lscpFetch - _cpFirst;
 
            SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, offsetToFirstCp);
            _plsrunVectorLatestPosition = rider.SpanPosition; 
            plsrun = (Plsrun)rider.CurrentElement; 

            LSRun lsrun; 
            if (plsrun < Plsrun.FormatAnchor)
            {
                lsrun = ControlRuns[(int)plsrun];
                lsrunOffset = 0; 
            }
            else 
            { 
                lsrun = (LSRun)_lsrunList[(int)(ToIndex(plsrun) - Plsrun.FormatAnchor)];
                lsrunOffset = offsetToFirstCp - rider.CurrentSpanStart; 
            }

            if (_lscpFirstValue != 0)
            { 
                // this is marker store, differentiate the plsrun from
                // plsrun from the main text store. 
                plsrun = MakePlsrunMarker(plsrun); 
            }
 
            // SpanRider.Length yields the distance to the end of the Span, not
            // the total length of the Span.
            lsrunLength = rider.Length;
 
            return lsrun;
        } 
 

        ///  
        /// Get the Bidi level of the character before the currently fetched one
        /// 
        private int GetLastLevel()
        { 
            if (_lscchUpTo > 0)
            { 
                SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, _lscchUpTo - 1); 
                _plsrunVectorLatestPosition = rider.SpanPosition;
                return GetRun((Plsrun)rider.CurrentElement).BidiLevel; 
            }
            return BaseBidiLevel;
        }
 

        ///  
        /// Base bidi level 
        /// 
        private int BaseBidiLevel 
        {
            get { return _settings.Pap.RightToLeft ? 1 : 0; }
        }
 
        /// 
        /// Analyze bidirectional level of runs 
        ///  
        /// run info vector indexed by ich
        /// character length of string to be analyzed 
        /// array of bidi levels, each for a character
        /// Number of characters resolved
        /// 
        /// BiDi Analysis in line layout imposes a higher level protocol on top of Unicode bidi algorithm 
        /// to support rich editing behavior. Explicit directional embedding controls is to be done
        /// through TextModifier runs and corresponding TextEndOfSegment. Directional controls (such as 
        /// LRE, RLE, PDF, etc) in the text stream are ignored in the Bidi Analysis to avoid conflict with the higher 
        /// level protocol.
        /// 
        /// The implementation analyzes directional embedding one level at a time. Input text runs are divided
        /// at the point where directional embedding level is changed.
        /// 
        private int BidiAnalyze( 
            SpanVector                  runInfoVector,
            int                         stringLength, 
            out byte[]                  bidiLevels 
            )
        { 
            CharacterBuffer charBuffer = null;
            int offsetToFirstChar;

            SpanRider runInfoSpanRider = new SpanRider(runInfoVector); 
            if (runInfoSpanRider.Length >= stringLength)
            { 
                // typical case, only one string is analyzed 
                TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;
 
                if (!runInfo.IsSymbol)
                {
                    charBuffer = runInfo.CharacterBuffer;
                    offsetToFirstChar = runInfo.OffsetToFirstChar; 
                    Debug.Assert(runInfo.StringLength >= stringLength);
                } 
                else 
                {
                    // Treat all characters in non-Unicode runs as strong left-to-right. 
                    // The literal 'A' could be any Latin character.
                    charBuffer = new StringCharacterBuffer(new string('A', stringLength));
                    offsetToFirstChar = 0;
                } 
            }
            else 
            { 
                // build up a consolidated character buffer for bidi analysis of
                // concatenated strings in multiple textruns. 
                int ich = 0;
                int cch;

                StringBuilder stringBuilder = new StringBuilder(stringLength); 

                while(ich < stringLength) 
                { 
                    runInfoSpanRider.At(ich);
                    cch = runInfoSpanRider.Length; 
                    TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;

                    Debug.Assert(cch <= runInfo.StringLength);
 
                    if (!runInfo.IsSymbol)
                    { 
                        runInfo.CharacterBuffer.AppendToStringBuilder( 
                            stringBuilder,
                            runInfo.OffsetToFirstChar, 
                            cch
                            );
                    }
                    else 
                    {
                        // Treat all characters in non-Unicode runs as strong left-to-right. 
                        // The literal 'A' could be any Latin character. 
                        stringBuilder.Append('A', cch);
                    } 

                    ich += cch;
                }
 
                charBuffer = new StringCharacterBuffer(stringBuilder.ToString());
                offsetToFirstChar = 0; 
            } 

            if(_bidiState == null) 
            {
                // make sure the initial state is setup
                _bidiState = new BidiState(_settings, _cpFirst);
            } 

            bidiLevels = new byte[stringLength]; 
            DirectionClass[] directionClasses = new DirectionClass[stringLength]; 

            int resolvedLength = 0; 

            for(int i = 0; i < runInfoVector.Count; i++)
            {
                int cchResolved = 0; 

                TextRunInfo currentRunInfo = (TextRunInfo) runInfoVector[i].element; 
                TextModifier modifier = currentRunInfo.TextRun as TextModifier; 

                if (IsDirectionalModifier(modifier)) 
                {
                    bidiLevels[resolvedLength] = AnalyzeDirectionalModifier(_bidiState, modifier.FlowDirection);
                    cchResolved = 1;
                } 
                else if (IsEndOfDirectionalModifier(currentRunInfo))
                { 
                    bidiLevels[resolvedLength] = AnalyzeEndOfDirectionalModifier(_bidiState); 
                    cchResolved = 1;
                } 
                else
                {
                    int ich = resolvedLength;
                    do 
                    {
                        CultureInfo culture = CultureMapper.GetSpecificCulture(currentRunInfo.Properties == null ? null : currentRunInfo.Properties.CultureInfo); 
                        DirectionClass europeanNumberOverride = _bidiState.GetEuropeanNumberClassOverride(culture); 

                        // 
                        // The European number in the input text is explictly set to AN or EN base on the
                        // culture of the text. We set the input DirectionClass of this range of text to
                        // AN or EN to indicate that any EN in this range should be explicitly set to this override
                        // value. 
                        //
                        for(int k = 0; k < runInfoVector[i].length; k++) 
                        { 
                            directionClasses[ich + k] = europeanNumberOverride;
                        } 

                        ich += runInfoVector[i].length;
                        if ((++i) >= runInfoVector.Count)
                            break; // end of all runs. 

                        currentRunInfo = (TextRunInfo) runInfoVector[i].element; 
                        if ( currentRunInfo.Plsrun == Plsrun.Hidden && 
                              (  IsDirectionalModifier(currentRunInfo.TextRun as TextModifier)
                              || IsEndOfDirectionalModifier(currentRunInfo) 
                              )
                           )
                        {
                            i--; 
                            break;   // break bidi analysis at the point of embedding level change
                        } 
                    } 
                    while (true);
 
                    const Bidi.Flags BidiFlags = Bidi.Flags.ContinueAnalysis | Bidi.Flags.IgnoreDirectionalControls | Bidi.Flags.OverrideEuropeanNumberResolution;

                    // The last runs will be marked as IncompleteText as their resolution
                    // may depend on following runs that haven't been fetched yet. 
                    Bidi.Flags flags = (i < runInfoVector.Count) ?
                            BidiFlags 
                          : BidiFlags | Bidi.Flags.IncompleteText; 

 
                    Bidi.BidiAnalyzeInternal(
                        charBuffer,
                        offsetToFirstChar + resolvedLength,
                        ich - resolvedLength, 
                        0, // no max hint
                        flags, 
                        _bidiState, 
                        new PartialArray(bidiLevels, resolvedLength, ich - resolvedLength),
                        new PartialArray(directionClasses, resolvedLength, ich - resolvedLength), 
                        out cchResolved
                        );

                    // Text must be completely resolved if there is no IncompleteText flag. 
                    Invariant.Assert(cchResolved == ich - resolvedLength || (flags & Bidi.Flags.IncompleteText) != 0);
                } 
 
                resolvedLength += cchResolved;
            } 

            Invariant.Assert(resolvedLength <= bidiLevels.Length);
            return resolvedLength;
        } 

        ///  
        /// Update BidiState base to the new directional embedding level. 
        /// 
        ///  
        /// The method returns the embedding level before the start of the Modifier.
        /// Contents inside the modifier scope is at a higher embedding level and hence
        /// separated from the content before the modifier scope.
        ///  
        private byte AnalyzeDirectionalModifier(
            BidiState       state, 
            FlowDirection   flowDirection 
            )
        { 
            bool leftToRight = (flowDirection == FlowDirection.LeftToRight);

            ulong levelStack = state.LevelStack;
 
            byte parentLevel = Bidi.BidiStack.GetMaximumLevel(levelStack);
 
            byte topLevel; 

            // Push to Bidi stack. Increment overflow counter if so. 
            if (!Bidi.BidiStack.Push(ref levelStack, leftToRight, out topLevel))
            {
                state.Overflow++;
            } 

            state.LevelStack = levelStack; 
 
            // set the default last strong such that text without CultureInfo
            // can be resolved correctly. 
            state.SetLastDirectionClassesAtLevelChange();
            return parentLevel;
        }
 
        /// 
        /// Update BidiState at the end of a directional emebedding level. 
        ///  
        /// 
        /// The method returns the embedding level after the end of the modifier. 
        /// Contents inside the modifier scope is at a higher embedding level and hence separated
        /// from the content after the modifier scope.
        /// 
        private byte AnalyzeEndOfDirectionalModifier(BidiState state) 
        {
            // Pop level stack 
            if (state.Overflow > 0) 
            {
                state.Overflow --; 
                return state.CurrentLevel;
            }

            byte parentLevel; 
            ulong stack = state.LevelStack;
 
            bool success = Bidi.BidiStack.Pop(ref stack, out parentLevel); 
            Invariant.Assert(success);
            state.LevelStack = stack; 

            // set the default last strong such that text without CultureInfo
            // can be resolved correctly.
            state.SetLastDirectionClassesAtLevelChange(); 
            return parentLevel;
        } 
 
        private bool IsEndOfDirectionalModifier(TextRunInfo runInfo)
        { 
            return (  runInfo.TextModifierScope != null
                   && runInfo.TextModifierScope.TextModifier.HasDirectionalEmbedding
                   && runInfo.TextRun is TextEndOfSegment
                   ); 
        }
 
        private bool IsDirectionalModifier(TextModifier modifier) 
        {
            return modifier != null && modifier.HasDirectionalEmbedding; 
        }

        internal bool InsertFakeLineBreak(int cpLimit)
        { 
            for (int i = 0, cp = 0, lscp = 0; i < _plsrunVector.Count; ++i)
            { 
                Span span = _plsrunVector[i]; 
                Plsrun plsrun = (Plsrun)span.element;
 
                // Is it a normal, non-static, LSRun?
                if (plsrun >= Plsrun.FormatAnchor)
                {
                    // Get the run. 
                    LSRun lsrun = GetRun(plsrun);
 
                    // Have we reached the limit? 
                    if (cp + lsrun.Length >= cpLimit)
                    { 
                        // Remove all subsequent runs.
                        _plsrunVector.Delete(i + 1, _plsrunVector.Count - (i + 1), ref _plsrunVectorLatestPosition);

                        // Truncate the run if it exeeds the limit. 
                        if (lsrun.Type == Plsrun.Text && cp + lsrun.Length > cpLimit)
                        { 
                            lsrun.Truncate(cpLimit - cp); 
                            span.length = lsrun.Length;
                        } 

                        _lscchUpTo = lscp + lsrun.Length;

                        // Close any reverse runs. 
                        CreateReverseLSRuns(BaseBidiLevel, lsrun.BidiLevel);
 
                        // Add the fake line break. 
                        _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition);
                        _lscchUpTo += 1; 

                        return true;
                    }
 
                    cp += lsrun.Length;
                } 
 
                lscp += span.length;
            } 

            return false;
        }
 
        /// 
        /// Determines whether a line needs to be truncated for security reasons due to exceeding 
        /// the maximum number of characters per line. See the comment for MaxCharactersPerLine. 
        /// 
        /// Vector of fetched text runs. 
        /// Number of cp to be added to _plsrunVector; the method
        /// may change this value if the line needs to be truncated.
        /// Returns true if the line should be truncated, false it not.
        private bool IsForceBreakRequired(SpanVector runInfoVector, ref int cchToAdd) 
        {
            bool forceBreak = false; 
            int ichRun = 0; 

            for (int i = 0; i < runInfoVector.Count && ichRun < cchToAdd; ++i) 
            {
                Span span = runInfoVector[i];
                TextRunInfo runInfo = (TextRunInfo)span.element;
 
                int runLength = Math.Min(span.length, cchToAdd - ichRun);
 
                // Only Plsrun.Text runs count against the limit 
                if (runInfo.Plsrun == Plsrun.Text && !IsNewline((ushort)runInfo.CharacterAttributeFlags))
                { 
                    if (_accTextLengthSoFar + runLength <= MaxCharactersPerLine)
                    {
                        // we're still under the limit; accumulate the number of characters so far
                        _accTextLengthSoFar += runLength; 
                    }
                    else 
                    { 
                        // accumulated number of characters has exceeded the maximum allowed number
                        // of characters per line; we need to generate a fake line break 
                        runLength = MaxCharactersPerLine - _accTextLengthSoFar;
                        _accTextLengthSoFar = MaxCharactersPerLine;
                        cchToAdd = ichRun + runLength;
                        forceBreak = true; 
                    }
                } 
 
                ichRun += runLength;
            } 

            return forceBreak;
        }
 
        [Flags]
        private enum NumberContext 
        { 
            Unknown             = 0,
 
            Arabic              = 0x0001,
            European            = 0x0002,
            Mask                = 0x0003,
 
            FromLetter          = 0x0004,
            FromFlowDirection   = 0x0008 
        } 

        private NumberContext GetNumberContext(TextModifierScope scope) 
        {
            int limitCp = _cpFirst + _cchUpTo;
            int firstCp = _cpNumberContext;
            NumberContext cachedNumberContext = _numberContext; 

            // Is there a current bidi scope? 
            for (; scope != null; scope = scope.ParentScope) 
            {
                if (scope.TextModifier.HasDirectionalEmbedding) 
                {
                    int cpScope = scope.TextSourceCharacterIndex;
                    if (cpScope >= _cpNumberContext)
                    { 
                        // Only scan back to the start of the current scope and don't use the cached number
                        // context since it's outside the current scope. 
                        firstCp = cpScope; 
                        cachedNumberContext = NumberContext.Unknown;
                    } 
                    break;
                }
            }
 
            // Is it a right to left context?
            bool rightToLeft = (scope != null) ? 
                scope.TextModifier.FlowDirection == FlowDirection.RightToLeft : 
                Pap.RightToLeft;
 
            // Scan for a preceding letter.
            while (limitCp > firstCp)
            {
                TextSpan textSpan = _settings.GetPrecedingText(limitCp); 

                // Stop if there's an empty TextSpan 
                if (textSpan.Length <= 0) 
                {
                    break; 
                }

                CharacterBufferRange charRange = textSpan.Value.CharacterBufferRange;
                if (!charRange.IsEmpty) 
                {
                    CharacterBuffer charBuffer = charRange.CharacterBuffer; 
 
                    // Index just past the last character in the range.
                    int limit = charRange.OffsetToFirstChar + charRange.Length; 

                    // Index of the first character in the range, not including any characters before firstCp.
                    int first = limit - Math.Min(charRange.Length, limitCp - firstCp);
 
                    // We'll stop scanning at letter or line break.
                    const ushort flagsMask = 
                        (ushort)CharacterAttributeFlags.CharacterLetter | 
                        (ushort)CharacterAttributeFlags.CharacterLineBreak;
 
                    // Iterate over the characters in reverse order.
                    for (int i = limit - 1; i >= first; --i)
                    {
                        char ch = charBuffer[i]; 
                        CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch));
 
                        ushort flags = (ushort)(charAttributes.Flags & flagsMask); 
                        if (flags != 0)
                        { 
                            if ((flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0)
                            {
                                // It's a letter so the number context depends on its script.
                                return (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ? 
                                    NumberContext.Arabic | NumberContext.FromLetter :
                                    NumberContext.European | NumberContext.FromLetter; 
                            } 
                            else
                            { 
                                // It's a line break. There are no preceding letters so number context depends only on
                                // whether the current bidi scope is right to left.
                                return rightToLeft ?
                                    NumberContext.Arabic | NumberContext.FromFlowDirection : 
                                    NumberContext.European | NumberContext.FromFlowDirection;
                            } 
                        } 
                    }
                } 

                limitCp -= textSpan.Length;
            }
 
            // If we have a cached number context that's still valid the use it. Valid means (1) we
            // scanned back as far as the cp of the number context, and (2) the number context was 
            // determined from a letter. (A cached number context derived from flow direction might 
            // not be valid because an embedded bidi level may have ended.)
            if (limitCp <= firstCp && (cachedNumberContext & NumberContext.FromLetter) != 0) 
            {
                return cachedNumberContext;
            }
 
            // There are no preceding letters so number context depends only on whether the current
            // bidi scope is right to left. 
            return rightToLeft ? 
                NumberContext.Arabic | NumberContext.FromFlowDirection :
                NumberContext.European | NumberContext.FromFlowDirection; 
        }

        /// 
        /// Create lsruns within a range of uniform bidi level. 
        /// 
        private void CreateLSRunsUniformBidiLevel( 
            SpanVector              runInfoVector, 
            SpanVector              textEffectsVector,
            int                     runInfoFirstCp, 
            int                     ichUniform,
            int                     cchUniform,
            int                     uniformBidiLevel,
            ref int                 lastBidiLevel 
            )
        { 
            int ichRun = 0; 

            // a range of characters with uniform level may span multiple 
            // textruns. Create lsrun at runInfo boundary.

            SpanRider runInfoSpanRider = new SpanRider(runInfoVector);
            SpanRider textEffectsSpanRider = new SpanRider(textEffectsVector); 

            while(ichRun < cchUniform) 
            { 
                runInfoSpanRider.At(ichUniform + ichRun);
                textEffectsSpanRider.At(ichUniform + ichRun); 

                // Limit the span base on effected ranges.
                int spanLength = Math.Min(runInfoSpanRider.Length, textEffectsSpanRider.Length);
                int ichEnd = Math.Min(ichRun + spanLength, cchUniform); 

                int textRunLength; 
 
                TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement;
                IList textEffects = (IList)textEffectsSpanRider.CurrentElement; 

                // Initialize digitCulture only if there are digits.
                CultureInfo digitCulture = null;
 
                // Number context; used only if we do contextual digit substitution.
                NumberContext numberContext = NumberContext.Unknown; 
 
                if ((runInfo.CharacterAttributeFlags & (ushort)CharacterAttributeFlags.CharacterDigit) == 0)
                { 
                    // No digits so digitCulture isn't used.
                }
                else if (!runInfo.ContextualSubstitution)
                { 
                    // Render all numbers using the digit culture of the run.
                    digitCulture = runInfo.DigitCulture; 
                } 
                else
                { 
                    // Contextual number substitution means the digit culture of a given number depends on the
                    // nearest preceding letter. If it's an Arabic letter we use the digit culture of the run;
                    // otherwise we use European digits (null digit culture).
 
                    // Number context of the previous number, if any.
                    NumberContext previousNumberContext = NumberContext.Unknown; 
 
                    CharacterBuffer charBuffer = runInfo.CharacterBuffer;
 
                    // The cha----r indexes ichRun, ich, etc., are relative to the start of the uniform range;
                    // In order to yield an index into charBuffer, we need to calculate the offset from the
                    // start of the uniform range to the start of the character buffer.
                    // 
                    //
                    // _cpFirst 
                    //    |----_cchUpTo---->|-----------------runInfoVector----------------------->| 
                    //                      |
                    //                      |--ichUniform-->|----ich------>| 
                    //                      |               |              |
                    //                      |-----CurrentSpanStart-->|=====x=====runInfo========|
                    //                                      |        |     |
                    // charBuffer=> [---runInfo.OffsetToFirstChar--->|-----x----------------------------] 
                    //              |                       |              |

                    //              |---characterOffset---->|----ich------>| 
                    // 
 					
                    int characterOffset = 
                        ichUniform                           // start of the uniform range
                        - runInfoSpanRider.CurrentSpanStart  // make relative to the the start of the runInfo
                        + runInfo.OffsetToFirstChar;         // make relative to the start of the character buffer in runInfo
 
                    for (int ich = ichRun; ich < ichEnd; ++ich)
                    { 
                        char ch = charBuffer[ich + characterOffset]; 
                        CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch));
 
                        if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterDigit) != 0)
                        {
                            // If there were no preceding letters in the current run we need to scan backwards to
                            // determine the current number context. 
                            if (numberContext == NumberContext.Unknown)
                            { 
                                numberContext = GetNumberContext(runInfo.TextModifierScope); 
                            }
 
                            // We need to set the digit culture if
                            //   (a) we haven't set it yet (i.e., this is the first number) or
                            //   (b) we set it but the previous number had a different number context
                            if ((previousNumberContext & NumberContext.Mask) != (numberContext & NumberContext.Mask)) 
                            {
                                // If there was a previous number with a different digit culture we need to split the run. 
                                if (previousNumberContext != NumberContext.Unknown) 
                                {
                                    CreateLSRuns( 
                                        runInfo,
                                        textEffects,
                                        digitCulture,
                                        ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart, 
                                        ich - ichRun,
                                        uniformBidiLevel, 
                                        ref lastBidiLevel, 
                                        out textRunLength
                                        ); 
                                    _cchUpTo += textRunLength;
                                    ichRun = ich;
                                }
 
                                // Set the digitCulture to use for this and subsequent characters.
                                digitCulture = (numberContext & NumberContext.Mask) == NumberContext.Arabic ? 
                                    runInfo.DigitCulture :      // subsequent digits use Arabic symbols 
                                    null;                       // subsequent digits use European symbols
 
                                previousNumberContext = numberContext;
                            }
                        }
                        else if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0) 
                        {
                            // It's a letter so set the current number context based on the letter's script. 
                            // Don't set the digit culture until we actually encounter a number. 
                            numberContext = (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ?
                                NumberContext.Arabic | NumberContext.FromLetter : 
                                NumberContext.European | NumberContext.FromLetter;
                        }
                    }
                } 

                // Even if we split the run we still have to add the last part. 
                Debug.Assert(ichRun < ichEnd); 

                // Add the run (or what's left of it). 
                CreateLSRuns(
                    runInfo,
                    textEffects,
                    digitCulture, 
                    ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart,
                    ichEnd - ichRun, 
                    uniformBidiLevel, 
                    ref lastBidiLevel,
                    out textRunLength 
                    );
                _cchUpTo += textRunLength;
                ichRun = ichEnd;
 
                // Save the number of context if known. This reduces the number of calls to GetPrecedingText for
                // lines with more than one number. We do this now, after calling CreateLSRuns, so that _cchUpTo 
                // holds the correct cp that corresponds to all the characters scanned so far. 
                if (numberContext != NumberContext.Unknown)
                { 
                    _numberContext = numberContext;
                    _cpNumberContext = _cpFirst + _cchUpTo;
                }
            } 

            Debug.Assert(ichRun == cchUniform); 
        } 

 

        /// 
        /// Create reverse lsruns
        ///  
        /// current bidi level
        /// last bidi level 
        /// updated last bidi Level 
        private int CreateReverseLSRuns(
            int     currentBidiLevel, 
            int     lastBidiLevel
            )
        {
            Plsrun plsrun; 
            int levelDiff = currentBidiLevel - lastBidiLevel;
 
            if(levelDiff > 0) 
            {
                // level up 
                plsrun = Plsrun.Reverse;
            }
            else
            { 
                // level down
                plsrun = Plsrun.CloseAnchor; 
                levelDiff = -levelDiff; 
            }
 
            for(int i = 0; i < levelDiff; i++)
            {
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, plsrun, _plsrunVectorLatestPosition);
                _lscchUpTo++; 
            }
            return currentBidiLevel; 
        } 

 

        /// 
        /// Create lsrun(s)
        ///  
        /// run info
        /// The applicable TextEffects on the LSRun.  
        /// digit culture for number substitution 
        /// offset the first char from start of run info
        /// lsrun character length 
        /// uniform bidi level
        /// last bidi level
        /// text run length
        private void CreateLSRuns( 
            TextRunInfo       runInfo,
            IList textEffects, 
            CultureInfo       digitCulture, 
            int               offsetToFirstChar,
            int               stringLength, 
            int               uniformBidiLevel,
            ref int           lastBidiLevel,
            out int           textRunLength
            ) 
        {
            LSRun lsrun = null; 
            int lsrunLength = 0; 
            textRunLength = 0;
 
            switch (runInfo.Plsrun)
            {
                case Plsrun.Text:
                { 
                    ushort charFlags = (ushort)runInfo.CharacterAttributeFlags;
 
                    // LineBreak & ParaBreak are separated into individual TextRunInfo with Plsrun.LineBreak or Plsrun.ParaBreak. 
                    Invariant.Assert(!IsNewline(charFlags));
 
                    if ((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0)
                    {
                        lsrun = new LSRun(
                            runInfo, 
                            Plsrun.FormatAnchor,
                            PwchNbsp, 
                            1, 
                            runInfo.OffsetToFirstCp,
                            (byte) uniformBidiLevel 
                            );

                        lsrunLength = textRunLength = lsrun.StringLength;
                    } 
                    else
                    { 
                        // Normal text, run length is character length 

                        textRunLength = lsrunLength = stringLength; 
                        Debug.Assert(runInfo.OffsetToFirstChar + offsetToFirstChar + lsrunLength <= runInfo.CharacterBuffer.Count);

                        CreateTextLSRuns(
                            runInfo, 
                            textEffects,
                            digitCulture, 
                            offsetToFirstChar, 
                            stringLength,
                            uniformBidiLevel, 
                            ref lastBidiLevel
                            );
                    }
 
                    break;
                } 
 
                case Plsrun.InlineObject:
                { 
                    Debug.Assert(offsetToFirstChar == 0);

                    double realToIdeal = _settings.Formatter.ToIdeal;
 
                    lsrun = new LSRun(
                        runInfo, 
                        textEffects, 
                        Plsrun.InlineObject,
                        runInfo.OffsetToFirstCp, 
                        runInfo.Length,
                        (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize),
                        0,          // character flags
                        new CharacterBufferRange(runInfo.CharacterBuffer, 0, stringLength), 
                        null,       // no shapeable
                        realToIdeal, 
                        (byte)uniformBidiLevel 
                        );
 
                    lsrunLength = stringLength;
                    textRunLength = runInfo.Length;
                    break;
                } 

                case Plsrun.LineBreak: 
                { 
                    //
                    // Line Separator's BIDI class is Neutral (WS). It would take the class of surrounding 
                    // characters so it might end up in a reverse run. However, LS would not process Line Separator
                    // in reverse run. Here we always override the BIDI level of Line Separator to paragraph's
                    // embedding level such that it is out of reverse run and LS would process it correctly.
                    // 
                    uniformBidiLevel = (Pap.RightToLeft ? 1 : 0);
                    lsrun = CreateLineBreakLSRun( 
                        runInfo, 
                        stringLength,
                        out lsrunLength, 
                        out textRunLength
                        );
                    break;
                } 

                case Plsrun.ParaBreak: 
                { 
                    //
                    // Paragraph Separator ends the paragraph. Its bidi level must be the embedding level. 
                    //
                    Debug.Assert(uniformBidiLevel == (Pap.RightToLeft ? 1 : 0));
                    lsrun = CreateLineBreakLSRun(
                        runInfo, 
                        stringLength,
                        out lsrunLength, 
                        out textRunLength 
                        );
                    break; 
                }
                case Plsrun.Hidden:
                {
                    // hidden run yields the same cp as its lscp 
                    lsrunLength = runInfo.Length - offsetToFirstChar;
                    textRunLength = lsrunLength; 
                    lsrun = new LSRun( 
                        runInfo,
                        Plsrun.Hidden, 
                        PwchHidden,
                        textRunLength,
                        runInfo.OffsetToFirstCp,
                        (byte) uniformBidiLevel 
                        );
 
                    break; 
                }
            } 

            if(lsrun != null)
            {
                Debug.Assert(lsrunLength > 0); 

                if (lastBidiLevel != uniformBidiLevel) 
                { 
                    lastBidiLevel = CreateReverseLSRuns(uniformBidiLevel, lastBidiLevel);
                } 

                // Add the plsrun to the span vector.
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, lsrunLength, AddLSRun(lsrun), _plsrunVectorLatestPosition);
                _lscchUpTo += lsrunLength; 
            }
        } 
 

 
        /// 
        /// Break down text with uniform level into multiple shapeable runs,
        /// then create one LSRun for each of them.
        ///  
        private void CreateTextLSRuns(
            TextRunInfo       runInfo, 
            IList textEffects, 
            CultureInfo       digitCulture,
            int               offsetToFirstChar, 
            int               stringLength,
            int               uniformBidiLevel,
            ref int           lastBidiLevel
            ) 
        {
            ICollection shapeables = null; 
 
            ITextSymbols textSymbols = runInfo.TextRun as ITextSymbols;
 
            if (textSymbols != null)
            {
                shapeables = textSymbols.GetTextShapeableSymbols(
                    _settings.Formatter.GlyphingCache, 
                    new CharacterBufferReference(
                        runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar 
                        ), 
                    stringLength,
                    (uniformBidiLevel & 1) != 0, 
                    digitCulture,
                    runInfo.TextModifierScope
                    );
            } 
            else
            { 
                TextShapeableSymbols textShapeableSymbols = runInfo.TextRun as TextShapeableSymbols; 

                if (textShapeableSymbols != null) 
                {
                    shapeables = new TextShapeableSymbols[] { textShapeableSymbols };
                }
            } 

            if (shapeables == null) 
            { 
                Debug.Assert(false);
                throw new NotSupportedException(); 
            }

            double realToIdeal = _settings.Formatter.ToIdeal;
            int ich = 0; 

            foreach (TextShapeableSymbols shapeable in shapeables) 
            { 
                int cch = shapeable.Length;
                Debug.Assert(cch > 0 && cch <= stringLength - ich); 

                int currentBidiLevel = uniformBidiLevel;

                LSRun lsrun = new LSRun( 
                    runInfo,
                    textEffects, 
                    Plsrun.Text, 
                    runInfo.OffsetToFirstCp + offsetToFirstChar + ich,
                    cch, 
                    (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize),
                    runInfo.CharacterAttributeFlags,
                    new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar + ich, cch),
                    shapeable, 
                    realToIdeal,
                    (byte)currentBidiLevel 
                    ); 

                if (currentBidiLevel != lastBidiLevel) 
                {
                    lastBidiLevel = CreateReverseLSRuns(currentBidiLevel, lastBidiLevel);
                }
 
                // set up an LSRun for each shapeable.
                _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, cch, AddLSRun(lsrun), _plsrunVectorLatestPosition); 
                _lscchUpTo += cch; 

                ich += cch; 
            }
        }

 

        ///  
        /// Create LSRun for a linebreak run 
        /// 
        private LSRun CreateLineBreakLSRun( 
            TextRunInfo     runInfo,
            int             stringLength,
            out int         lsrunLength,
            out int         textRunLength 
            )
        { 
            lsrunLength = stringLength; 
            textRunLength = runInfo.Length;
 
            return new LSRun(
                runInfo,
                null, // No TextEffects on LineBreak
                runInfo.Plsrun, 
                runInfo.OffsetToFirstCp,
                runInfo.Length, 
                0,      // emSize 
                runInfo.CharacterAttributeFlags,
                new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar, stringLength), 
                null,   // no shapebale
                _settings.Formatter.ToIdeal,
                (byte)(Pap.RightToLeft ? 1 : 0)
                ); 
        }
 
 

 
        /// 
        /// Add new lsrun to lsrun list
        /// 
        /// lsrun to add 
        /// plsrun of added lsrun
        private Plsrun AddLSRun(LSRun lsrun) 
        { 
            if(lsrun.Type < Plsrun.FormatAnchor)
            { 
                return lsrun.Type;
            }

            Plsrun plsrun = (Plsrun)((uint)_lsrunList.Count + Plsrun.FormatAnchor); 

            if (lsrun.IsSymbol) 
            { 
                plsrun = MakePlsrunSymbol(plsrun);
            } 

            _lsrunList.Add(lsrun);

            return plsrun; 
        }
 
 
        #region lsrun/cp mapping
 
        /// 
        /// Map internal LSCP to text source cp
        /// 
        ///  
        /// This method does not handle mapping of LSCP beyond the last one
        ///  
        internal int GetExternalCp(int lscp) 
        {
            lscp -= _lscpFirstValue; 

            SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, lscp - _cpFirst);
            _plsrunVectorLatestPosition = rider.SpanPosition;
 
            return GetRun((Plsrun)rider.CurrentElement).OffsetToFirstCp +
                    lscp - rider.CurrentSpanStart; 
        } 

 
        /// 
        /// Get LSRun from plsrun
        /// 
        internal LSRun GetRun(Plsrun plsrun) 
        {
            plsrun = ToIndex(plsrun); 
 
            return  (LSRun)(
                IsContent(plsrun) ? 
                _lsrunList[(int)(plsrun - Plsrun.FormatAnchor)] :
                ControlRuns[(int)plsrun]
                );
        } 

 
        ///  
        /// Check if plsrun is marker
        ///  
        internal static bool IsMarker(Plsrun plsrun)
        {
            return (plsrun & Plsrun.IsMarker) != 0;
        } 

 
        ///  
        /// Make this plsrun a marker plsrun
        ///  
        internal static Plsrun MakePlsrunMarker(Plsrun plsrun)
        {
            return (plsrun | Plsrun.IsMarker);
        } 

 
        ///  
        /// Make this plsrun a symbol plsrun
        ///  
        internal static Plsrun MakePlsrunSymbol(Plsrun plsrun)
        {
            return (plsrun | Plsrun.IsSymbol);
        } 

 
        ///  
        /// Convert plsrun to index to lsrun list
        ///  
        internal static Plsrun ToIndex(Plsrun plsrun)
        {
            return (plsrun & Plsrun.UnmaskAll);
        } 

 
        ///  
        /// Check if run is content
        ///  
        internal static bool IsContent(Plsrun plsrun)
        {
            plsrun = ToIndex(plsrun);
            return plsrun >= Plsrun.FormatAnchor; 
        }
 
 
        /// 
        /// Check if character is space 
        /// 
        internal static bool IsSpace(char ch)
        {
            return ch == ' ' || ch == '\u00a0'; 
        }
 
 
        /// 
        /// Check if character is of strong directional type 
        /// 
        internal static bool IsStrong(char ch)
        {
            int unicodeClass = Classification.GetUnicodeClass(ch); 
            ItemClass itemClass = (ItemClass)Classification.CharAttributeOf(unicodeClass).ItemClass;
            return itemClass == ItemClass.StrongClass; 
        } 

 
        /// 
        /// Check if the run is a line break or paragraph break
        /// 
        internal static bool IsNewline (Plsrun plsrun) 
        {
            return plsrun == Plsrun.LineBreak || plsrun == Plsrun.ParaBreak; 
        } 

        ///  
        /// Check if the character is a line break or paragraph break character
        /// 
        internal static bool IsNewline(ushort flags)
        { 
            return ( (flags & (ushort) CharacterAttributeFlags.CharacterLineBreak) != 0
                  || (flags & (ushort) CharacterAttributeFlags.CharacterParaBreak) != 0 ); 
        } 

        #endregion 


        /// 
        /// Repositioning text lsruns according to its BaselineAlignmentment property 
        /// 
        internal void AdjustRunsVerticalOffset( 
            int             dcpLimit, 
            int             height,
            int             baselineOffset, 
            out int         cellHeight,
            out int         cellAscent
            )
        { 
            // Following are all alignment point offsets from the baseline of the line.
            // Value grows positively in paragraph flow direction. 
            int top = 0; 
            int bottom = 0;
            int textTop = 0; 
            int textBottom = 0;
            int super = 0;
            int sub = 0;
            int center = 0; 

            ArrayList lsruns = new ArrayList(3); 
 
            // Find TextTop from all Baseline lsruns
 
            int dcp = 0;
            int i = 0;
            while(dcp < dcpLimit)
            { 
                Debug.Assert(i < _plsrunVector.Count);
 
                Span span = _plsrunVector[i++]; 
                LSRun lsrun = GetRun((Plsrun)span.element);
 
                if(     lsrun.Type == Plsrun.Text
                    ||  lsrun.Type == Plsrun.InlineObject)
                {
                    if(lsrun.RunProp.BaselineAlignment == BaselineAlignment.Baseline) 
                    {
                        textTop = Math.Max(textTop, lsrun.BaselineOffset); 
                        textBottom = Math.Max(textBottom, lsrun.Descent); 
                    }
 
                    lsruns.Add(lsrun);
                }

                dcp += span.length; 
            }
 
            textTop = -textTop; // offset from the baseline in paragraph flow direction 

            top = height > 0 ? -baselineOffset : textTop; 

            // Finalize Bottom by ignoring all but Top, TextTop and Baseline lsruns

            foreach(LSRun lsrun in lsruns) 
            {
                switch (lsrun.RunProp.BaselineAlignment) 
                { 
                    case BaselineAlignment.Top:
                        textBottom = Math.Max(textBottom, lsrun.Height + top); 
                        break;

                    case BaselineAlignment.TextTop:
                        textBottom = Math.Max(textBottom, lsrun.Height + textTop); 
                        break;
                } 
            } 

            bottom = height > 0 ? height - baselineOffset : textBottom; 


            // hardcode the positions for now
            center = (top + bottom) / 2; 
            sub = bottom / 2;
            super = top * 2 / 3; 
 

            // Now move all lsruns according to its BaselineAlignment property 

            cellAscent = 0;
            int cellDescent = 0;
 
            foreach(LSRun lsrun in lsruns)
            { 
                int move = 0; 

                switch (lsrun.RunProp.BaselineAlignment) 
                {
                    case BaselineAlignment.Top:
                        // lsrun top to line top
                        move = top + lsrun.BaselineOffset; 
                        break;
 
                    case BaselineAlignment.Bottom: 
                        // lsrun bottom to line bottom
                        move = bottom - lsrun.Height + lsrun.BaselineOffset; 
                        break;

                    case BaselineAlignment.TextTop:
                        // lsrun top to line text top 
                        move = textTop + lsrun.BaselineOffset;
                        break; 
 
                    case BaselineAlignment.TextBottom:
                        // lsrun bottom to line text bottom 
                        move = textBottom - lsrun.Height + lsrun.BaselineOffset;
                        break;

                    case BaselineAlignment.Center: 
                        // lsrun center to line center
                        move = center - lsrun.Height/2 + lsrun.BaselineOffset; 
                        break; 

                    case BaselineAlignment.Superscript: 
                        // lsrun baseline to line superscript
                        move = super;
                        break;
 
                    case BaselineAlignment.Subscript:
                        // lsrun baseline to line subscript 
                        move = sub; 
                        break;
                } 

                lsrun.Move(move);

                // Recalculate line ascent and descent 
                cellAscent = Math.Max(cellAscent, lsrun.BaselineOffset - move);
                cellDescent = Math.Max(cellDescent, lsrun.Descent + move); 
            } 

            cellHeight = cellAscent + cellDescent; 
        }


        ///  
        /// Collect a piece of raw text that makes up a word containing the specified LSCP.
        /// The text returned from this method is used for hyphenation of a single word. 
        /// In addition to the raw text, it also returns the mapping between the raw character 
        /// indices and the LSCP indices in plsrunVector. This is used later on when we
        /// map the lexical result back to the positions used to communicate with LS. 
        /// 
        /// 
        /// "word" here is not meant for a linguistic term. It only means array of characters
        /// from space to space i.e. a word in SE Asian language is not separated by spaces. 
        /// 
        internal char[] CollectRawWord( 
            int                 lscpCurrent, 
            bool                isCurrentAtWordStart,
            out int             lscpChunk, 
            out int             lscchChunk,
            out CultureInfo     textCulture,
            out int             cchWordMax,
            out SpanVector textVector 
            )
        { 
            // Fetch the lsrun containing the current position make sure 
            // all LSCP before it are properly retained in plsrun vector.
            // We need this before we could reliably walk back the plsrun 
            // vector to establish the chunk's start position in the following
            // step.

            textVector = new SpanVector(); 
            textCulture = null;
 
            lscpChunk = lscpCurrent; 
            lscchChunk = 0;
            cchWordMax = 0; 

            Plsrun plsrun;
            int lsrunOffset;
            int lsrunLength; 

            LSRun lsrun = FetchLSRun( 
                lscpCurrent, 
                out plsrun,
                out lsrunOffset, 
                out lsrunLength
                );

            if (lsrun == null) 
                return null;
 
            textCulture = lsrun.TextCulture; 

            int lscpLim; 
            int cchBefore = 0;

            if (!isCurrentAtWordStart && lscpChunk > _cpFirst)
            { 
                // The specified position may not be start of word.
                // Expand backward to the first non-space character following a space 
                // before the current position. If the current position is already 
                // at space, no skip is needed.
 
                SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition);

                do
                { 
                    rider.At(lscpChunk - _cpFirst - 1);
 
                    lscpLim = rider.CurrentSpanStart + _cpFirst; 

                    lsrun = GetRun((Plsrun)rider.CurrentElement); 

                    if (   IsNewline(lsrun.Type)
                        || lsrun.Type == Plsrun.InlineObject)
                    { 
                        // Stop expanding due to hard break
                        break; 
                    } 

                    if (lsrun.Type == Plsrun.Text) 
                    {
                        if (!lsrun.TextCulture.Equals(textCulture))
                        {
                            // Stop expanding due to change of text culture 
                            break;
                        } 
 
                        int cchLim = lscpChunk - lscpLim;
                        int ichFirst = lsrun.OffsetToFirstChar + lscpChunk - _cpFirst - rider.CurrentSpanStart; 
                        int cch = 0;

                        // Skip all non-space characters until a space is found
                        while (cch < cchLim && !IsSpace(lsrun.CharacterBuffer[ichFirst - cch - 1])) 
                            cch++;
 
                        cchBefore += cch; 

                        if (cch < cchLim) 
                        {
                            // Start of chunk is found
                            lscpChunk -= cch;
                            break; 
                        }
                    } 
 
                    // Reposition start of chunk to the beginning of the current run and continue expanding
                    Invariant.Assert(lscpLim < lscpChunk); 
                    lscpChunk = lscpLim;

                } while (lscpChunk > _cpFirst && cchBefore <= MaxCchWordToHyphenate);
 
                _plsrunVectorLatestPosition = rider.SpanPosition;
            } 
 
            if (cchBefore > MaxCchWordToHyphenate)
            { 
                // The word is unusually long. This is already a situation we dont want
                // bring hyphenation into the picture.
                return null;
            } 

 
            // Expand forward from the beginning of a word to the end of the word. 
            // If the start position is already at space, skip passed all leading spaces
 
            StringBuilder stringBuilder = null;
            int lscp = lscpChunk;
            int cchText = 0;
            int cchLastWord = 0; 

            do 
            { 
                lsrun = FetchLSRun(
                    lscp, 
                    out plsrun,
                    out lsrunOffset,
                    out lsrunLength
                    ); 

                if (lsrun == null) 
                    return null; 

                lscpLim = lscp + lsrunLength; 

                if (   IsNewline(lsrun.Type)
                    || lsrun.Type == Plsrun.InlineObject)
                { 
                    // Stop expanding due to hard break
                    break; 
                } 

                if (lsrun.Type == Plsrun.Text) 
                {
                    if (!lsrun.TextCulture.Equals(textCulture))
                    {
                        // Stop expanding due to change of text culture 
                        break;
                    } 
 
                    int cchLim = lscpLim - lscp;
                    int ichFirst = lsrun.OffsetToFirstChar + lsrun.Length - lsrunLength; 
                    int cch = 0;

                    if (cchText == 0)
                    { 
                        // Skip all leading spaces
                        while (cch < cchLim && IsSpace(lsrun.CharacterBuffer[ichFirst + cch])) 
                            cch++; 
                    }
 
                    // Skip all non-space characters until a following space is found
                    char ch;
                    int cchWord = cchLastWord;
 
                    while (     cch < cchLim
                            &&  cchText + cch < MaxCchWordToHyphenate 
                            &&  !IsSpace((ch = lsrun.CharacterBuffer[ichFirst + cch]))) 
                    {
                        cch++; 

                        if (IsStrong(ch))
                        {
                            cchWord++; 
                        }
                        else 
                        { 
                            // Non-strong character marks the end of the current word length,
                            // Keep the length of the greatest length word found so far. 
                            if (cchWord > cchWordMax)
                                cchWordMax = cchWord;

                            cchWord = 0; 
                        }
                    } 
 
                    // Keep the length so far of the last word found.
                    cchLastWord = cchWord; 

                    if (cchLastWord > cchWordMax)
                    {
                        // Keep the length of the greatest length word found so far. 
                        cchWordMax = cchLastWord;
                    } 
 
                    if (stringBuilder == null)
                        stringBuilder = new StringBuilder(); 

                    // Gathering the raw text
                    lsrun.CharacterBuffer.AppendToStringBuilder(stringBuilder, ichFirst, cch);
 
                    // Keep the map between indices to raw text and its correspondent LSCP
                    textVector.Set(cchText, cch, lscp - lscpChunk); 
 
                    cchText += cch;
 
                    if (cch < cchLim)
                    {
                        // End of chunk is found
                        lscp += cch; 
                        break;
                    } 
                } 

                Invariant.Assert(lscpLim > lscp); 
                lscp = lscpLim;

            } while (cchText < MaxCchWordToHyphenate);
 
            if (stringBuilder == null)
                return null; 
 
            lscchChunk = lscp - lscpChunk;
            Invariant.Assert(stringBuilder.Length == cchText); 

            char[] rawText = new char[stringBuilder.Length];
            stringBuilder.CopyTo(0, rawText, 0, rawText.Length);
            return rawText; 
        }
 
 
        /// 
        /// Fetch cached inline metrics 
        /// 
        /// text object to format
        /// firs cp of text object
        /// inline's current pen position 
        /// line's right margin
        /// inline info 
        ///  
        /// Right margin is not necessarily the same as column max width. Right margin
        /// is usually greater than actual column width during object formatting. LS 
        /// increases the margin to 1/32 of the column width to provide a leaway for
        /// breaking.
        ///
        /// However TextBlock/TextFlow functions in such a way that it needs to know the exact width 
        /// left in the line in order to compute the inline's correct size. We make sure
        /// that it'll never get oversize max width. 
        /// 
        /// Inline object's reported size can be so huge that it may overflow LS's maximum value.
        /// If a given width is a finite value, we'll respect that and out-of-range exception may be thrown as appropriate. 
        /// If the width is Positive Infinity, the width is trimmed to the maximum remaining value that LS can handle. This is
        /// appropriate for the cases where client measures inline objects at Infinite size.
        /// 
        internal TextEmbeddedObjectMetrics FormatTextObject( 
            TextEmbeddedObject  textObject,
            int                 cpFirst, 
            int                 currentPosition, 
            int                 rightMargin
            ) 
        {
            if(_textObjectMetricsVector == null)
            {
                _textObjectMetricsVector = new SpanVector(null); 
            }
 
            SpanRider rider = new SpanRider(_textObjectMetricsVector); 
            rider.At(cpFirst);
 
            TextEmbeddedObjectMetrics metrics = (TextEmbeddedObjectMetrics)rider.CurrentElement;

            if(metrics == null)
            { 
                int widthLeft = _formatWidth - currentPosition;
 
                if(widthLeft <= 0) 
                {
                    // we're formatting this object outside the actual column width, 
                    // we give the host the max width from the current position up
                    // to the margin.
                    widthLeft = rightMargin - _formatWidth;
                } 

                double idealToReal = _settings.Formatter.ToReal; 
 
                metrics = textObject.Format(idealToReal * widthLeft);
 
                if (Double.IsPositiveInfinity(metrics.Width))
                {
                    // If the inline object has Width to be positive infinity, trim the width to
                    // the maximum value that LS can handle. 
                    metrics = new TextEmbeddedObjectMetrics(
                        idealToReal * (Constants.InfiniteWidth - currentPosition), 
                        metrics.Height, 
                        metrics.Baseline
                        ); 
                }
                else if (metrics.Width > idealToReal * (Constants.InfiniteWidth - currentPosition))
                {
                    // LS cannot compute value greater than its maximum computable value 
                    throw new ArgumentException(SR.Get(SRID.TextObjectMetrics_WidthOutOfRange));
                } 
 
                _textObjectMetricsVector.SetReference(cpFirst, textObject.Length, metrics);
            } 

            Debug.Assert(metrics != null);
            return metrics;
        } 

 
        #region ENUMERATIONS & CONST 

        // first negative cp of bullet marker 
        internal const int LscpFirstMarker = (-0x7FFFFFFF);

        // Note: Trident uses this figure
        internal const int TypicalCharactersPerLine = 100; 

        // Hyphen 
        internal const char CharHyphen = '\x002d'; 

        internal const char CharLineSeparator = '\x2028'; 
        internal const char CharParaSeparator = '\x2029';
        internal const char CharLinefeed      = '\x000a';

        // Hardcoded strings in LS memory 
        // They are kept as a pointer such that it would be fast to return
        // them as pointers back into LS. 
        internal static IntPtr PwchParaSeparator; 
        internal static IntPtr PwchLineSeparator;
        internal static IntPtr PwchNbsp; 
        internal static IntPtr PwchHidden;
        internal static IntPtr PwchObjectTerminator;
        internal static IntPtr PwchObjectReplacement;
 

        /// !! DO NOT update this enum without looking at its unmanaged pair in lslo.cpp !! 
        /// [wchao, 10-1-2001] 
        //
        internal enum ObjectId : ushort 
        {
            Reverse         = 0,
            MaxNative       = 1,
            InlineObject    = 1, 
            Max             = 2,
            Text_chp        = 0xffff, 
        } 

        // Maximum number of characters per line. If we exceed this number of characters 
        // without breaking in a normal way, we chop off the line by generating a fake
        // line break run. This is to mitigate potential denial-of-service attacks by
        // ensuring that there is a reasonable upper bound on the time required to
        // format a single line. 
        //
        // In ordinary documents with word-wrap enabled, we should always reach the 
        // right margin long before MaxCharactersPerLine characters. The only reason 
        // we might forcibly break the line in such cases would be:
        //   (a) Extreme right margin 
        //   (b) Extreme number of zero-width characters
        //   (c) Extremely small font size (i.e., fraction of a pixel)
        //   (d) Lack of break opportunity (e.g., no spaces)
        // All of these are security cases, for which chopping off the line is a 
        // reasonable mitigation. Limiting the line length addresses all of these
        // potential attacks so we don't need separate mitigations for, e.g., 
        // zero-width characters. 
        //
        // Extremely long lines are less unlikely in nowrap scenarios, such as a code 
        // editor. However, the same security issues apply so we still chop off the line
        // if we exceed the maximum number of characters with no line break. Note that
        // Notepad (with nowrap) does the same thing.
        // 
        // The value chosen corresponds roughly to four pages of text at 60 characters
        // per line and 40 lines per page. Testing shows this to be a resonable limit 
        // in terms of run time. 
        private const int MaxCharactersPerLine = 9600; // 60 * 40 * 4
 
        /// 
        /// The maximum number of characters within a single word that is still considered a legitimate
        /// input for hyphenation. This value is suggested by Stefanie Schiller - the NLG expert when
        /// considering a theoretical example of a German compound word which consists of 12 compound 
        /// segments. The following is that word.
        /// 
        /// "DONAUDAMPFSCHIFFAHRTSELEKTRIZITAETENHAUPTBETRIEBSWERKBAUUNTERBEAMTENGESELLSCHAFT" 
        ///
        /// [Wchao, 3/15/2006] 
        /// 
        private const int MaxCchWordToHyphenate = 80;

        #endregion 

        #region Properties 
        internal FormatSettings Settings 
        {
            get { return _settings; } 
        }

        internal ParaProp Pap
        { 
            get { return _settings.Pap; }
        } 
 
        internal int CpFirst
        { 
            get { return _cpFirst; }
        }

        internal SpanVector PlsrunVector 
        {
            get { return _plsrunVector; } 
        } 

        internal ArrayList LsrunList 
        {
            get { return _lsrunList; }
        }
 
        internal int FormatWidth
        { 
            get { return _formatWidth; } 
        }
 
        internal int CchEol
        {
            get { return _cchEol; }
            set { _cchEol = value; } 
        }
        #endregion 
    } 

 
    /// 
    /// Bidi state that applie across line. If no preceding state is available internally,
    /// it calls back to the client to obtain additional Bidi control and explicit embedding level.
    ///  
    internal sealed class BidiState : Bidi.State
    { 
        public BidiState(FormatSettings settings, int cpFirst) 
            : this(settings, cpFirst, null)
        { 
        }

        public BidiState(FormatSettings settings, int cpFirst, TextModifierScope modifierScope)
            : base (settings.Pap.RightToLeft) 
        {
            _settings = settings; 
            _cpFirst = cpFirst; 

            NumberClass = DirectionClass.ClassInvalid; 
            StrongCharClass = DirectionClass.ClassInvalid;


            // find the top most scope that has the direction embedding 
            while ( modifierScope != null && !modifierScope.TextModifier.HasDirectionalEmbedding)
            { 
                modifierScope = modifierScope.ParentScope; 
            }
 
            if (modifierScope != null)
            {
                _cpFirstScope = modifierScope.TextSourceCharacterIndex;
 
                // Initialize Bidi stack base on modifier scope
                Bidi.BidiStack stack = new Bidi.BidiStack(); 
                stack.Init(LevelStack); 

                ushort overflowLevels = 0; 
                InitLevelStackFromModifierScope(stack, modifierScope, ref overflowLevels);

                LevelStack = stack.GetData();
                Overflow = overflowLevels; 
            }
        } 
 

        ///  
        /// Set the default last strongs when an embedding level is changed such that
        /// ambiguous characters (i.e. characters with null or InvariantCulture) at the beginning
        /// of the current embedding level can be resolved correctly.
        ///  
        internal void SetLastDirectionClassesAtLevelChange()
        { 
            if ((CurrentLevel & 1) == 0) 
            {
                LastStrongClass = DirectionClass.Left; 
                LastNumberClass = DirectionClass.Left;
            }
            else
            { 
                LastStrongClass = DirectionClass.ArabicLetter;
                LastNumberClass = DirectionClass.ArabicNumber; 
            } 
        }
 
        internal byte CurrentLevel
        {
            get { return Bidi.BidiStack.GetMaximumLevel(LevelStack); }
        } 

 
        ///  
        /// Method to get the last number class overridden by bidi algorithm implementer
        ///  
        public override DirectionClass LastNumberClass
        {
            get
            { 
                if (this.NumberClass == DirectionClass.ClassInvalid )
                { 
                    GetLastDirectionClasses(); 
                }
 
                return this.NumberClass;
            }

            set { this.NumberClass = value; } 
        }
 
 
        /// 
        /// Method to get the last strong class overridden by bidi algorithm implementer 
        /// 
        public override DirectionClass LastStrongClass
        {
            get 
            {
                if (this.StrongCharClass == DirectionClass.ClassInvalid) 
                { 
                    GetLastDirectionClasses();
                } 
                return this.StrongCharClass;
            }

            set 
            {
                this.StrongCharClass = value; 
                this.NumberClass = value; 
            }
        } 


        /// 
        /// Last strong class not found internally, call out to client 
        /// 
        private void GetLastDirectionClasses() 
        { 
            DirectionClass  strongClass = DirectionClass.ClassInvalid;
            DirectionClass  numberClass = DirectionClass.ClassInvalid; 

            // It is a flag to indicate whether to continue calling GetPrecedingText.
            // Because Bidi algorithm works within a paragraph only, we should terminate the
            // loop at paragraph boundary and fall back to the appropriate defaults. 

            bool continueScanning = true; 
 
            while (continueScanning && _cpFirst > _cpFirstScope)
            { 
                TextSpan textSpan = _settings.GetPrecedingText(_cpFirst);
                CultureSpecificCharacterBufferRange charString = textSpan.Value;

                if (textSpan.Length <= 0) 
                {
                    break;  // stop when preceding text span has length 0. 
                } 

                if (!charString.CharacterBufferRange.IsEmpty) 
                {
                    continueScanning = Bidi.GetLastStongAndNumberClass(
                        charString.CharacterBufferRange,
                        ref strongClass, 
                        ref numberClass
                        ); 
 
                    if (strongClass != DirectionClass.ClassInvalid)
                    { 
                        this.StrongCharClass = strongClass;

                        if (this.NumberClass == DirectionClass.ClassInvalid)
                        { 
                            if (numberClass == DirectionClass.EuropeanNumber)
                            { 
                                // Override EuropeanNumber class as appropriate. 
                                numberClass = GetEuropeanNumberClassOverride(CultureMapper.GetSpecificCulture(charString.CultureInfo));
                            } 

                            this.NumberClass = numberClass;
                        }
 
                        break;
                    } 
                } 

                _cpFirst -= textSpan.Length; 
            }


            // If we don't have the strong class and/or number class, select appropriate defaults 
            // according to the base bidi level.
            // 
            // To determine the base bidi level, we look at bit 0 if the LevelStack. This is NOT 
            // an even/odd test. LevelStack is an array of bits corresponding to all of the bidl
            // levels on the stack. Thus, bit 0 is set if and only if the base bidi level is zero, 
            // i.e., it's a left-to-right paragraph.

            if(strongClass == DirectionClass.ClassInvalid)
            { 
                this.StrongCharClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicLetter;
            } 
 
            if(numberClass == DirectionClass.ClassInvalid)
            { 
                this.NumberClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicNumber;
            }
        }
 
        /// 
        /// Walk the TextModifierScope to reinitialize the bidi stack. 
        /// We push to bidi-stack from the earliest directional modifier (i.e. from bottom of the 
        /// the scope chain onwards). We use a stack to reverse the scope chain first.
        ///  
        private static void InitLevelStackFromModifierScope(
            Bidi.BidiStack    stack,
            TextModifierScope scope,
            ref ushort        overflowLevels 
            )
        { 
            Stack directionalEmbeddingStack = new Stack(32); 

            for (TextModifierScope currentScope = scope; currentScope != null; currentScope = currentScope.ParentScope) 
            {
                if (currentScope.TextModifier.HasDirectionalEmbedding)
                {
                    directionalEmbeddingStack.Push(currentScope.TextModifier); 
                }
            } 
 
            while (directionalEmbeddingStack.Count > 0)
            { 
                TextModifier modifier = directionalEmbeddingStack.Pop();

                if (overflowLevels > 0)
                { 
                    // Bidi level stack overflows. Just increment the bidi stack overflow number
                    overflowLevels ++; 
                } 
                else if (!stack.Push(modifier.FlowDirection == FlowDirection.LeftToRight))
                { 
                    // Push stack not successful. Stack starts to overflow.
                    overflowLevels = 1;
                }
 
            }
        } 
 
        /// 
        /// Obtain the explict direction class of European number based on culture and current flow direction. 
        /// European numbers in Arabic/---- culture and RTL flow direction are to be considered as Arabic numbers.
        /// 
        internal DirectionClass GetEuropeanNumberClassOverride(CultureInfo cultureInfo)
        { 
            if (   cultureInfo != null
                 &&(   (cultureInfo.LCID & 0xFF) == 0x01 // Arabic culture 
                    || (cultureInfo.LCID & 0xFF) == 0x29 // ---- culture 
                   )
                 && (CurrentLevel & 1) != 0 // RTL flow direction 
                )
            {
                return DirectionClass.ArabicNumber;
            } 

            return DirectionClass.EuropeanNumber; 
        } 

        private FormatSettings  _settings; 
        private int             _cpFirst;
        private int             _cpFirstScope; // The first Cp of the current scope. GetLastStrong() should not go beyond it.
    }
} 

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