TextStore.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / MS / Internal / TextFormatting / TextStore.cs / 1305600 / 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
        /// The layout mode
        /// Whether the text in the run should be sideways
        /// 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, 
            TextFormattingMode  textFormattingMode,
            bool            isSideways,
            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(TextFormatterImp.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
                        textFormattingMode,
                        isSideways,
                        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,
                            textFormattingMode, 
                            isSideways,
                            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,
            TextFormattingMode          textFormattingMode,
            bool                    isSideways, 
            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,
                                        textFormattingMode, 
                                        isSideways,
                                        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,
                    textFormattingMode,
                    isSideways, 
                    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 
        /// The layout mode
        /// Whether the text in the run should be sideways 
        /// last bidi level
        /// text run length
        private void CreateLSRuns(
            TextRunInfo       runInfo, 
            IList textEffects,
            CultureInfo       digitCulture, 
            int               offsetToFirstChar, 
            int               stringLength,
            int               uniformBidiLevel, 
            TextFormattingMode    textFormattingMode,
            bool              isSideways,
            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,
                            textFormattingMode,
                            isSideways,
                            ref lastBidiLevel 
                            );
                    } 
 
                    break;
                } 

                case Plsrun.InlineObject:
                {
                    Debug.Assert(offsetToFirstChar == 0); 

                    double realToIdeal = TextFormatterImp.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,
            TextFormattingMode    textFormattingMode, 
            bool              isSideways, 
            ref int           lastBidiLevel
            ) 
        {
            ICollection shapeables = null;

            ITextSymbols textSymbols = runInfo.TextRun as ITextSymbols; 

            if (textSymbols != null) 
            { 
                bool isRightToLeftParagraph = (runInfo.TextModifierScope != null) ?
                    runInfo.TextModifierScope.TextModifier.FlowDirection == FlowDirection.RightToLeft : 
                    _settings.Pap.RightToLeft;

                shapeables = textSymbols.GetTextShapeableSymbols(
                    _settings.Formatter.GlyphingCache, 
                    new CharacterBufferReference(
                        runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar 
                        ), 
                    stringLength,
                    (uniformBidiLevel & 1) != 0,    // Indicates the RTL based on the bidi level of text. 
                    isRightToLeftParagraph,
                    digitCulture,
                    runInfo.TextModifierScope,
                    textFormattingMode, 
                    isSideways
                    ); 
            } 
            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 = TextFormatterImp.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 
                TextFormatterImp.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,
            bool                isSideways,
            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,
                Settings.Formatter.TextFormattingMode,
                isSideways, 
                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, 
                    Settings.Formatter.TextFormattingMode, 
                    isSideways,
                    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;
                }
 
                metrics = textObject.Format(_settings.Formatter.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(
                        _settings.Formatter.IdealToReal((Constants.IdealInfiniteWidth - currentPosition)),
                        metrics.Height, 
                        metrics.Baseline
                        ); 
                } 
                else if (metrics.Width > _settings.Formatter.IdealToReal((Constants.IdealInfiniteWidth - 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;

        internal const char CharLineSeparator = '\x2028';
        internal const char CharParaSeparator = '\x2029'; 
        internal const char CharLineFeed      = '\x000a';
        internal const char CharCarriageReturn= '\x000d'; 
 
        // 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