Speller.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 / Framework / System / Windows / Documents / Speller.cs / 1305600 / Speller.cs

                            //---------------------------------------------------------------------------- 
//
// File: Speller.cs
//
// Description: Spell checking component for the TextEditor. 
//
//--------------------------------------------------------------------------- 
 
namespace System.Windows.Documents
{ 
    using MS.Internal;
    using System.Threading;
    using System.Windows.Threading;
    using System.Globalization; 
    using System.Collections;
    using System.Collections.Generic; 
    using System.Security; 
    using System.Security.Permissions;
    using System.Runtime.InteropServices; 
    using MS.Win32;
    using System.Windows.Controls;
    using System.Windows.Markup; // XmlLanguage
    using System.Windows.Input; 
    using System.IO;
    using System.Windows.Navigation; 
 
    // Spell checking component for the TextEditor.
    internal class Speller 
    {
        //-----------------------------------------------------
        //
        //  Constructors 
        //
        //----------------------------------------------------- 
 
        #region Constructors
 
        // Creates a new instance.  We have at most one Speller instance
        // per TextEditor.
        internal Speller(TextEditor textEditor)
        { 
            _textEditor = textEditor;
 
            _textEditor.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange); 

            // Schedule some idle time to start examining the document. 
            if (_textEditor.TextContainer.SymbolCount > 0)
            {
                ScheduleIdleCallback();
            } 

            _defaultCulture = InputLanguageManager.Current != null ? InputLanguageManager.Current.CurrentInputLanguage : 
                                                                     Thread.CurrentThread.CurrentCulture; 
        }
 
        #endregion Constructors

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

        #region Internal Methods 

        // Called by TextEditor to disable spelling.
        /// 
        /// Critical - this code resets the _textChunk member which is critical 
        /// TreatAsSafe - the operation is safe to expose
        ///  
        [SecurityCritical, SecurityTreatAsSafe] 
        internal void Detach()
        { 
            Invariant.Assert(_textEditor != null);

            _textEditor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
 
            if (_pendingCaretMovedCallback)
            { 
                _textEditor.Selection.Changed -= new EventHandler(OnCaretMoved); 
                _textEditor.UiScope.LostFocus -= new RoutedEventHandler(OnLostFocus);
                _pendingCaretMovedCallback = false; 
            }

            // Shutdown the highlight layer.
            if (_highlightLayer != null) 
            {
                _textEditor.TextContainer.Highlights.RemoveLayer(_highlightLayer); 
                _highlightLayer = null; 
            }
 
            // Shutdown the status table.
            _statusTable = null;

            // Release our nl6 objects. 
            if (_spellerInterop != null)
            { 
                _spellerInterop.Dispose(); 
                _spellerInterop = null;
            } 

            // Clear the TextEditor.  (Used as a sentinel to track Detachedness
            // from pending idle callback.)
            _textEditor = null; 
        }
 
        // Returns an object holding state about an error at the specified 
        // position, or null if no error is present.
        // 
        // If forceEvaluation is set true, the speller will analyze any dirty region
        // covered by the position.  Otherwise dirty regions will be treated as
        // non-errors.
        internal SpellingError GetError(ITextPointer position, LogicalDirection direction, bool forceEvaluation) 
        {
            ITextPointer start; 
            ITextPointer end; 
            SpellingError error;
 
            // Evaluate any pending dirty region.
            if (forceEvaluation &&
                EnsureInitialized() &&
                _statusTable.IsRunType(position.CreateStaticPointer(), direction, SpellerStatusTable.RunType.Dirty)) 
            {
                ScanPosition(position, direction); 
            } 

            // Get the error result. 
            if (_statusTable != null &&
                _statusTable.GetError(position.CreateStaticPointer(), direction, out start, out end))
            {
                error = new SpellingError(this, start, end); 
            }
            else 
            { 
                error = null;
            } 

            return error;
        }
 
        // Worker for TextBox/RichTextBox.GetNextSpellingErrorPosition.
        // Returns the start position of the next error, or null if no error exists. 
        // 
        // NB: this method will force an evaluation of any dirty regions between
        // position and the next error, which in the worst case is the rest of 
        // the document.
        internal ITextPointer GetNextSpellingErrorPosition(ITextPointer position, LogicalDirection direction)
        {
            if (!EnsureInitialized()) 
                return null;
 
            StaticTextPointer endPosition; 
            SpellerStatusTable.RunType runType;
 
            while (_statusTable.GetRun(position.CreateStaticPointer(), direction, out runType, out endPosition))
            {
                if (runType == SpellerStatusTable.RunType.Error)
                    break; 

                if (runType == SpellerStatusTable.RunType.Dirty) 
                { 
                    ScanPosition(position, direction);
 
                    _statusTable.GetRun(position.CreateStaticPointer(), direction, out runType, out endPosition);
                    Invariant.Assert(runType != SpellerStatusTable.RunType.Dirty);

                    if (runType == SpellerStatusTable.RunType.Error) 
                        break;
                } 
 
                position = endPosition.CreateDynamicTextPointer(direction);
            } 

            SpellingError spellingError = GetError(position, direction, false /* forceEvaluation */);
            return spellingError == null ? null : spellingError.Start;
        } 

        // Called by SpellingError to retreive a list of suggestions 
        // for an error range. 
        // This method actually runs the speller on the specified text,
        // re-evaluating the error from scratch. 
        /// 
        /// Critical - It calls SetContextOption() which is Critical.
        /// TreatAsSafe - it calls them with trusted parameters.
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        internal IList GetSuggestionsForError(SpellingError error) 
        { 
            ITextPointer contextStart;
            ITextPointer contextEnd; 
            ITextPointer contentStart;
            ITextPointer contentEnd;
            TextMap textMap;
            ArrayList suggestions; 

            suggestions = new ArrayList(1); 
 
            //
            // IMPORTANT!! 
            //
            // This logic here must match ScanRange, or else we might not
            // calculate the exact same error.  Keep the two methods in [....]!
            // 

            XmlLanguage language; 
            CultureInfo culture = GetCurrentCultureAndLanguage(error.Start, out language); 
            if (culture == null || !CanSpellCheck(culture))
            { 
                // Return an empty list.
            }
            else
            { 
                ExpandToWordBreakAndContext(error.Start, LogicalDirection.Backward, language, out contentStart, out contextStart);
                ExpandToWordBreakAndContext(error.End, LogicalDirection.Forward, language, out contentEnd, out contextEnd); 
 
                textMap = new TextMap(contextStart, contextEnd, contentStart, contentEnd);
 
                SetCulture(culture);

                _spellerInterop.SetContextOption("IsSpellChecking", true);
                _spellerInterop.SetContextOption("IsSpellVerifyOnly", false); 

                _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null, 
                    new SpellerInterop.EnumTextSegmentsCallback(ScanErrorTextSegment), new TextMapCallbackData(textMap, suggestions)); 
            }
 
            return suggestions;
        }

        // Worker for context menu's "Ignore All" item. 
        // Adds a word to the ignore list, and clears any matching errors.
        // 
        // 
        internal void IgnoreAll(string word)
        { 
            if (_ignoredWordsList == null)
            {
                _ignoredWordsList = new ArrayList(1);
            } 

            int index = _ignoredWordsList.BinarySearch(word, new CaseInsensitiveComparer(_defaultCulture)); 
 
            if (index < 0)
            { 
                // This is a new word to ignore.

                // Add it the list so we don't flag it later.
                _ignoredWordsList.Insert(~index, word); 

                // Then search through the error list, clearing any matching 
                // errors. 

                if (_statusTable != null) 
                {
                    StaticTextPointer pointer = _textEditor.TextContainer.CreateStaticPointerAtOffset(0);
                    ITextPointer errorStart;
                    ITextPointer errorEnd; 
                    Char[] charArray = null;
 
                    while (!pointer.IsNull) 
                    {
                        if (_statusTable.GetError(pointer, LogicalDirection.Forward, out errorStart, out errorEnd)) 
                        {
                            string error = TextRangeBase.GetTextInternal(errorStart, errorEnd, ref charArray);

                            if (String.Compare(word, error, true /* ignoreCase */, _defaultCulture) == 0) 
                            {
                                _statusTable.MarkCleanRange(errorStart, errorEnd); 
                            } 
                        }
 
                        pointer = _statusTable.GetNextErrorTransition(pointer, LogicalDirection.Forward);
                    }
                }
            } 
        }
 
        // Sets the speller engine spelling reform option. 
        internal void SetSpellingReform(SpellingReform spellingReform)
        { 
            if (_spellingReform != spellingReform)
            {
                _spellingReform = spellingReform;
 
                // Invalidate the whole document.
                ResetErrors(); 
            } 
        }
 
        /// 
        /// Loads/unloads custom dictionaries based on value of .
        /// 
        ///  
        /// 
        internal void SetCustomDictionaries(CustomDictionarySources dictionaryLocations, bool add) 
        { 
            if (!EnsureInitialized())
            { 
                return;
            }
            if (add)
            { 
                foreach (Uri item in dictionaryLocations)
                { 
                    OnDictionaryUriAdded(item); 
                }
            } 
            else
            {
                OnDictionaryUriCollectionCleared();
            } 
        }
 
        // Called when a global state change invalidates all cached errors. 
        internal void ResetErrors()
        { 
            if (_statusTable != null)
            {
                _statusTable.MarkDirtyRange(_textEditor.TextContainer.Start, _textEditor.TextContainer.End);
 
                if (_textEditor.TextContainer.SymbolCount > 0)
                { 
                    ScheduleIdleCallback(); 
                }
            } 
        }

        // Returns true if the specified property affects speller evaluation.
        internal static bool IsSpellerAffectingProperty(DependencyProperty property) 
        {
            return property == FrameworkElement.LanguageProperty || 
                   property == SpellCheck.SpellingReformProperty; 
        }
 
        /// 
        /// Loads custom Dictionary.
        /// 
        ///  
        /// 
        /// Critical - 
        /// 1. Works with file paths. 
        /// 2. Does file read/write operations.
        /// Safe - 
        /// 1. Does not return file path data to user.
        /// 2. Local file paths which this function works with are not discoverable in PartialTrust.
        /// Trying to access a given path in PartialTrust will throw access permission related
        /// exceptions before reaching any file APIs. For PartialTrust case we're demanding FileIOAccess 
        /// to every local file path passed to this function. For more details 
        /// and  
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        internal void OnDictionaryUriAdded(Uri uri) 
        {
            if (!EnsureInitialized())
            {
                return; 
            }
 
            // 
            // Re-adding same dictionary URI first requires to unload previously loaded one with same URI
            // 
            if (UriMap.ContainsKey(uri))
            {
                OnDictionaryUriRemoved(uri);
            } 

            Uri pathUri; 
            if (!uri.IsAbsoluteUri || uri.IsFile) 
            {
                pathUri = ResolvePathUri(uri); 
                SpellerInterop.ILexicon lexicon = _spellerInterop.LoadDictionary(pathUri.LocalPath);
                UriMap.Add(uri, new DictionaryInfo(pathUri, lexicon));
            }
            else 
            {
                LoadDictionaryFromPackUri(uri); 
            } 
            ResetErrors();
        } 

        /// 
        /// Removes specified custom dictionary from the list of loaded dictionaries.
        ///  
        /// 
        ///  
        /// Critical - works with critical member _spellerInterop. 
        /// Safe -
        /// 1. Does not disclose paht related information to caller, except the one which was passed in by user. 
        /// 2. Unloading dictionary has no security effect.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal void OnDictionaryUriRemoved(Uri uri) 
        {
            if (!EnsureInitialized()) 
            { 
                return;
            } 
            if (!UriMap.ContainsKey(uri))
            {
                return;
            } 
            DictionaryInfo info = UriMap[uri];
            try 
            { 
                _spellerInterop.UnloadDictionary(info.Lexicon);
            } 
            catch(Exception e)
            {
                // we're catching exception only to dump debug data, then rethrow.
                if (SecurityHelper.CheckUnmanagedCodePermission())//we're in full trust 
                {
                    System.Diagnostics.Trace.Write(string.Format(CultureInfo.InvariantCulture, "Unloading dictionary failed. Original Uri:{0}, file Uri:{1}, exception:{2}", uri.ToString(), info.PathUri.ToString(), e.ToString())); 
                } 
                throw;
            } 
            UriMap.Remove(uri);
            ResetErrors();
        }
 
        /// 
        /// Removes all custom dictionaries. 
        ///  
        /// 
        /// critical - works with critical _spellerInterop. 
        /// safe - does not expose critical member, does not return any data to the caller.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal void OnDictionaryUriCollectionCleared() 
        {
            if (!EnsureInitialized()) 
            { 
                return;
            } 
            // Unload all files
            _spellerInterop.ReleaseAllLexicons();
            UriMap.Clear();
            ResetErrors(); 
        }
 
        #endregion Internal methods 

        //------------------------------------------------------ 
        //
        //  Internal Properties
        //
        //------------------------------------------------------ 

        #region Internal Properties 
 
        // A run-length array tracking speller status of all text in the document.
        internal SpellerStatusTable StatusTable 
        {
            get
            {
                return _statusTable; 
            }
        } 
 
        #endregion Internal Properties
 
        //-----------------------------------------------------
        //
        //  Private Properties
        // 
        //------------------------------------------------------
 
        #region Private Properties 

        ///  
        /// A map between the original location specified by a user and actual path + reference to loaded custom dicitonary.
        /// 
        /// 
        /// critical - accesses security critical field which holds file paths and reference to a COM interface wrappers. 
        /// 
        private Dictionary UriMap 
        { 
            [SecurityCritical]
            get 
            {
                if (_uriMap == null)
                {
                    _uriMap = new Dictionary(); 
                }
                return _uriMap; 
            } 
        }
        #endregion Private Properties 

        //-----------------------------------------------------
        //
        //  Private Methods 
        //
        //----------------------------------------------------- 
 
        #region Private Methods
 
        // Initializes state for the Speller.
        // Delayed until the first text change event, or first idle callback.
        /// 
        /// Critical - This code calls into _textchunk and other unmanaged COM api. 
        /// TreatAsSafe - critical operations are not based on untrusted input. multiple calls don't involve any risk.
        ///  
        [SecurityCritical, SecurityTreatAsSafe] 
        private bool EnsureInitialized()
        { 
            if (_spellerInterop != null)
                return true;

            if (_failedToInit) 
                return false;
 
            Invariant.Assert(_highlightLayer == null); 
            Invariant.Assert(_statusTable == null);
 
            try
            {
                _spellerInterop = new SpellerInterop();
            } 
            catch (DllNotFoundException)
            { 
                _failedToInit = true; 
            }
            catch (EntryPointNotFoundException) 
            {
                _failedToInit = true;
            }
 
            if (_failedToInit)
                return false; 
 
            _highlightLayer = new SpellerHighlightLayer(this);
 
            _statusTable = new SpellerStatusTable(_textEditor.TextContainer.Start, _highlightLayer);

            _textEditor.TextContainer.Highlights.AddLayer(_highlightLayer);
 
            //
 
 
            _spellerInterop.SetContextOption("IsSpellSuggestingMWEs", false);
 
            _spellingReform = (SpellingReform)_textEditor.UiScope.GetValue(SpellCheck.SpellingReformProperty);

            return true;
        } 

        // Posts a background priority operation to the dispatcher queue. 
        // All scanning takes place during idle-time callbacks. 
        private void ScheduleIdleCallback()
        { 
            if (!_pendingIdleCallback)
            {
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new DispatcherOperationCallback(OnIdle), null);
                _pendingIdleCallback = true; 
            }
        } 
 
        // Enables the TextSelection.Changed listener.
        // We call this method when an otherwise clean document has text 
        // covered by the caret or an IME composition that must be analyzed
        // when the selection moves away.
        private void ScheduleCaretMovedCallback()
        { 
            if (!_pendingCaretMovedCallback)
            { 
                _textEditor.Selection.Changed += new EventHandler(OnCaretMoved); 
                _textEditor.UiScope.LostFocus += new RoutedEventHandler(OnLostFocus);
                _pendingCaretMovedCallback = true; 
            }
        }

        // Callback for document changes. 
        // Marks appropriate sections of the document as dirty then posts
        // an idle request for future analysis. 
        private void OnTextContainerChange(object sender, TextContainerChangeEventArgs e) 
        {
            Invariant.Assert(sender == _textEditor.TextContainer); 

            if (e.Count == 0 ||
                (e.TextChange == TextChangeType.PropertyModified && !IsSpellerAffectingProperty(e.Property)))
            { 
                // Speller doesn't care about most property changes.
                return; 
            } 

            if (_failedToInit) 
            {
                // Speller engine is not available.
                return;
            } 

            if (_statusTable != null) 
            { 
                _statusTable.OnTextChange(e);
            } 

            ScheduleIdleCallback();
        }
 
        // Runs the speller idle callback.
        // During this callback, we scan dirty portions of the document 
        // until all text is examined, or we exceed a set time limit. 
        // If we run out of time with more work to do, we post a new idle
        // callback request, yielding to any pending high-priority work 
        // (such as user input).
        //

 
        private object OnIdle(object unused)
        { 
            Invariant.Assert(_pendingIdleCallback); 

            // Reset _pendingIdleCallback. 
            _pendingIdleCallback = false;

            // _textEditor will be null if we've been detached since requesting the callback.
            if (_textEditor != null && 
                EnsureInitialized())
            { 
                ITextPointer start; 
                ITextPointer end;
                long timeLimit; 
                ScanStatus status;

                timeLimit = DateTime.Now.Ticks + MaxIdleTimeSliceNs;
 
                end = null;
                status = null; 
 
                // Iterate over chunks of dirty text until we run out of time
                // or finish with the entire document. 
                do
                {
                    if (!GetNextScanRange(end, out start, out end))
                        break; 

                    status = ScanRange(start, end, timeLimit); 
                } 
                while (!status.HasExceededTimeLimit);
 
                // Schedule any pending work before we yield.
                if (status != null)
                {
                    if (status.HasExceededTimeLimit) 
                    {
                        ScheduleIdleCallback(); 
                    } 
                }
            } 

            return null;
        }
 
        // Callback for TextSelection.Changed event.
        private void OnCaretMoved(object sender, EventArgs e) 
        { 
            OnCaretMovedWorker();
        } 

        // Callback for UiScope.LostFocus event.
        private void OnLostFocus(object sender, RoutedEventArgs e)
        { 
            OnCaretMovedWorker();
        } 
 
        // Callback for the TextSelection.Changed or UiScope.LostFocus events.
        // We enter this method when an otherwise clean document has text 
        // covered by the caret or an IME composition that must be analyzed
        // when the selection moves away.
        private void OnCaretMovedWorker()
        { 
            if (!_pendingCaretMovedCallback || _textEditor == null)
            { 
                // Because the event route caches the callback, we can get a 
                // callback even after removing the handler.
                // Just ignore the spurious callback. 
                return;
            }

            _textEditor.Selection.Changed -= new EventHandler(OnCaretMoved); 
            _textEditor.UiScope.LostFocus -= new RoutedEventHandler(OnLostFocus);
            _pendingCaretMovedCallback = false; 
 
            // Now that the caret's out of the way, analyze the text it
            // used to cover the next time the app goes idle. 
            ScheduleIdleCallback();
        }

        // Calculates the next run of text to feed to the speller. 
        // If there's nothing left to analyze, returns false and start/end will
        // be null on exit. 
        private bool GetNextScanRange(ITextPointer searchStart, out ITextPointer start, out ITextPointer end) 
        {
            ITextPointer rawStart; 
            ITextPointer rawEnd;

            start = null;
            end = null; 

            // 
 
            // First iteration of the scan loop, searchStart == null.
            if (searchStart == null) 
            {
                searchStart = _textEditor.TextContainer.Start;
            }
 
            // Grab the first dirty range.
            GetNextScanRangeRaw(searchStart, out rawStart, out rawEnd); 
 
            if (rawStart != null)
            { 
                // Skip over the caret and/or IME composition.
                AdjustScanRangeAroundComposition(rawStart, rawEnd, out start, out end);
            }
 
            return start != null;
        } 
 
        // Finds the next dirty range following searchStart, without considering
        // the current caret or IME composition. 
        private void GetNextScanRangeRaw(ITextPointer searchStart, out ITextPointer start, out ITextPointer end)
        {
            Invariant.Assert(searchStart != null);
 
            start = null;
            end = null; 
 
            // Grab the first dirty range.
            _statusTable.GetFirstDirtyRange(searchStart, out start, out end); 

            if (start != null)
            {
                Invariant.Assert(start.CompareTo(end) < 0); 

                // Cap the block size by a constant. 
                if (start.GetOffsetToPosition(end) > MaxScanBlockSize) 
                {
                    end = start.CreatePointer(MaxScanBlockSize); 
                }

                // Ensure the block has constant language.
                XmlLanguage language = GetCurrentLanguage(start); 
                end = GetNextLanguageTransition(start, LogicalDirection.Forward, language, end);
                Invariant.Assert(start.CompareTo(end) < 0); 
            } 
        }
 
        // Truncates a range of text if it overlaps the word containing the
        // caret, or an IME composition.
        /// 
        /// Critical - It calls SetContextOption(), which is Critical. 
        /// TreatAsSafe - it calls it with a well known option.
        ///  
        [SecurityCritical, SecurityTreatAsSafe] 
        private void AdjustScanRangeAroundComposition(ITextPointer rawStart, ITextPointer rawEnd,
            out ITextPointer start, out ITextPointer end) 
        {
            start = rawStart;
            end = rawEnd;
 
            if (!_textEditor.Selection.IsEmpty)
            { 
                // No caret to adjust around. 
                return;
            } 

            if (!_textEditor.UiScope.IsKeyboardFocused)
            {
                // Document isn't focused, no caret rendered. 
                return;
            } 
 
            // Get the word surrounding the caret.
 
            ITextPointer wordBreakLeft;
            ITextPointer wordBreakRight;
            ITextPointer caretPosition;
            TextMap textMap; 
            ArrayList segments;
 
            caretPosition = _textEditor.Selection.Start; 

            // Disable spell checking functionality since we're only 
            // interested in word breaks here.  This greatly cuts down
            // the engine's workload.
            _spellerInterop.SetContextOption("IsSpellChecking", false);
 
            XmlLanguage language = GetCurrentLanguage(caretPosition);
            wordBreakLeft = SearchForWordBreaks(caretPosition, LogicalDirection.Backward, language, 1, false /* stopOnError */); 
            wordBreakRight = SearchForWordBreaks(caretPosition, LogicalDirection.Forward, language, 1, false /* stopOnError */); 

            textMap = new TextMap(wordBreakLeft, wordBreakRight, caretPosition, caretPosition); 
            segments = new ArrayList(2);
            _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null,
                new SpellerInterop.EnumTextSegmentsCallback(ExpandToWordBreakCallback), segments);
 
            // We will have no segments when position is surrounded by
            // nothing but white space. 
            if (segments.Count != 0) 
            {
                int leftBreakOffset; 
                int rightBreakOffset;

                // Figure out where caretPosition lives in the segment list.
                FindPositionInSegmentList(textMap, LogicalDirection.Backward, segments, out leftBreakOffset, out rightBreakOffset); 

                wordBreakLeft = textMap.MapOffsetToPosition(leftBreakOffset); 
                wordBreakRight = textMap.MapOffsetToPosition(rightBreakOffset); 
            }
 
            // Overlap?
            if (wordBreakLeft.CompareTo(rawEnd) < 0 &&
                wordBreakRight.CompareTo(rawStart) > 0)
            { 
                if (wordBreakLeft.CompareTo(rawStart) > 0)
                { 
                    // Truncate the right half of the input range. 
                    end = wordBreakLeft;
                } 
                else if (wordBreakRight.CompareTo(rawEnd) < 0)
                {
                    // Truncate the left half of the input range.
                    start = wordBreakRight; 
                }
                else 
                { 
                    // The entire dirty range is covered by the caret word.
                    // Try to find a following dirty range. 
                    GetNextScanRangeRaw(wordBreakRight, out start, out end);
                }

                // Schedule a future callback to deal with the skipped 
                // overlapping section.
                ScheduleCaretMovedCallback(); 
            } 
        }
 
        // Analyzes a run of text.  The scan may be interrupted if we run out
        // of time along the way, in which case some subset of the contained
        // words will be left dirty.
        ///  
        /// Critical - It calls SetContextOption(), which is Critical.
        /// TreatAsSafe - it calls it with a well known option. 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private ScanStatus ScanRange(ITextPointer start, ITextPointer end, long timeLimit) 
        {
            ITextPointer contextStart;
            ITextPointer contextEnd;
            ITextPointer contentStart; 
            ITextPointer contentEnd;
            TextMap textMap; 
            ScanStatus status; 

            // 
            // IMPORTANT: the scan logic here (word break expansion, TextMap creation, etc.)
            // must match GetSuggestionForError exactly.  Keep the methods in [....]!
            //
 
            //
            // Expand the content to include whole words. 
            // Also get pointers to sufficient surrounding text to analyze 
            // multi-word errors correctly.
            // 

            status = new ScanStatus(timeLimit);

            XmlLanguage language; 
            CultureInfo culture = GetCurrentCultureAndLanguage(start, out language);
 
            if (culture == null) 
            {
                // Someone set a bogus language on the run -- ignore it. 
                _statusTable.MarkCleanRange(start, end);
            }
            else
            { 
                SetCulture(culture);
 
                ExpandToWordBreakAndContext(start, LogicalDirection.Backward, language, out contentStart, out contextStart); 
                ExpandToWordBreakAndContext(end, LogicalDirection.Forward, language, out contentEnd, out contextEnd);
 
                Invariant.Assert(contentStart.CompareTo(contentEnd) < 0);
                Invariant.Assert(contextStart.CompareTo(contextEnd) < 0);
                Invariant.Assert(contentStart.CompareTo(contextStart) >= 0);
                Invariant.Assert(contentEnd.CompareTo(contextEnd) <= 0); 

                // 
                // Mark the range clean, before we scan for errors. 
                //
                _statusTable.MarkCleanRange(contentStart, contentEnd); 

                //
                // Read the text.
                // 

                // Check for a compatible language. 
                if (CanSpellCheck(culture)) 
                {
                    _spellerInterop.SetContextOption("IsSpellChecking", true); 
                    _spellerInterop.SetContextOption("IsSpellVerifyOnly", true);

                    textMap = new TextMap(contextStart, contextEnd, contentStart, contentEnd);
 
                    //
                    // Iterate over sentences and segments. 
                    // 

                    _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, new SpellerInterop.EnumSentencesCallback(ScanRangeCheckTimeLimitCallback), 
                        new SpellerInterop.EnumTextSegmentsCallback(ScanTextSegment), new TextMapCallbackData(textMap, status));

                    if (status.TimeoutPosition != null)
                    { 
                        if (status.TimeoutPosition.CompareTo(end) < 0)
                        { 
                            // We ran out of time before analyzing the whole block. 
                            // Reset the dirty status of the remainder.
                            _statusTable.MarkDirtyRange(status.TimeoutPosition, end); 
                            // We should always make some forward progress, even just one word,
                            // otherwise we'll never finish checking the document.
                            if (status.TimeoutPosition.CompareTo(start) <= 0)
                            { 
                                // Diagnostic info for bug 1577085.
                                string debugMessage = "Speller is not advancing! \n" + 
                                                      "Culture = " + culture + "\n" + 
                                                      "Start offset = " + start.Offset + " parent = " + start.ParentType.Name + "\n" +
                                                      "ContextStart offset = " + contextStart.Offset + " parent = " + contextStart.ParentType.Name + "\n" + 
                                                      "ContentStart offset = " + contentStart.Offset + " parent = " + contentStart.ParentType.Name + "\n" +
                                                      "ContentEnd offset = " + contentEnd.Offset + " parent = " + contentEnd.ParentType.Name + "\n" +
                                                      "ContextEnd offset = " + contextEnd.Offset + " parent = " + contextEnd.ParentType.Name + "\n" +
                                                      "Timeout offset = " + status.TimeoutPosition.Offset + " parent = " + status.TimeoutPosition.ParentType.Name + "\n" + 
                                                      "textMap TextLength = " + textMap.TextLength + " text = " + new string(textMap.Text) + "\n" +
                                                      "Document = " + start.TextContainer.Parent.GetType().Name + "\n"; 
 
                                if (start is TextPointer)
                                { 
                                    debugMessage += "Xml = " + new TextRange((TextPointer)start.TextContainer.Start, (TextPointer)start.TextContainer.End).Xml;
                                }

                                Invariant.Assert(false, debugMessage); 
                            }
                        } 
                        else 
                        {
                            // We ran of time but finished the whole block. 
                            // TimeoutPosition should never be past contentEnd.
                            // It might be less than contentEnd if the dirty run ends
                            // with an element edge, in which case TimeoutPosition
                            // will preceed the final element edge(s). 
                            Invariant.Assert(status.TimeoutPosition.CompareTo(contentEnd) <= 0);
                        } 
                    } 
                }
            } 

            return status;
        }
 
        // Callback for the error segment scanned during error lookup.
        // Returns a list of correction suggestions. 
        private bool ScanErrorTextSegment(object textSegment, object o) 
        {
            TextMapCallbackData data = (TextMapCallbackData)o; 
            SpellerInterop.STextRange sTextRange = SpellerInterop.GetSegmentRange(textSegment);

            // Check if this segment falls outside the content range.
            // The region before/after the content is only for context -- 
            // to handle multi-word errors correctly.  We never want to mark it.
            if (sTextRange.Start + sTextRange.Length <= data.TextMap.ContentStartOffset) 
            { 
                // Preceeding context, skip this segment and keep going.
                return true; 
            }

            SpellerInterop.GetSuggestions(textSegment, (ArrayList)data.Data);
 
            // We only expect one error segment for this callback, so skip any
            // following context segments. 
            return false; 
        }
 
        // Scans a single segment (the engine's formal notion of a "word").
        // Called indirectly by ScanRange.
        // Returns true to continue the segment enumeration, false to
        // break out of the iteration. 
        private bool ScanTextSegment(object textSegment, object o)
        { 
            TextMapCallbackData data = (TextMapCallbackData)o; 
            SpellerInterop.STextRange sTextRange;
            char[] word; 

            sTextRange = SpellerInterop.GetSegmentRange(textSegment);

            // Check if this segment falls outside the content range. 
            // The region before/after the content is only for context --
            // to handle multi-word errors correctly.  We never want to mark it. 
            if (sTextRange.Start + sTextRange.Length <= data.TextMap.ContentStartOffset) 
            {
                // Preceeding context, skip this segment and keep going. 
                return true;
            }
            if (sTextRange.Start >= data.TextMap.ContentEndOffset)
            { 
                // Following context, skip this segment and stop iterating any remainder.
                return false; 
            } 

            if (sTextRange.Length > 1) // Ignore single letter errors. 
            {
                // Check if the segment has been marked "ignore" by the user.
                word = new char[sTextRange.Length];
                Array.Copy(data.TextMap.Text, sTextRange.Start, word, 0, sTextRange.Length); 

                if (!IsIgnoredWord(word)) 
                { 
                    SpellerInterop.RangeRole role = SpellerInterop.GetSegmentRole(textSegment);
 
                    if (role == SpellerInterop.RangeRole.ecrrIncorrect)
                    {
                        if (SpellerInterop.GetSegmentCount(textSegment) == 0)
                        { 
                            // We have an error.
                            MarkErrorRange(data.TextMap, sTextRange); 
                        } 
                        else
                        { 
                            // We have a subsegment with an error.
                            SpellerInterop.EnumSubsegments(textSegment,
                                new SpellerInterop.EnumTextSegmentsCallback(ScanTextSegment), data);
                        } 
                    }
                } 
            } 

            return true; 
        }

        // Called after we've scanned all the segments within a sentence from inside ScanRange.
        // This method returns false to the enumerator to end the scan if we've run out of time. 
        //
        // NB: we terminate the segment enumeration run by ScanRange only at sentence boundaries, 
        // not at more finely grained segment boundaries.  This is because the vast amount of 
        // work done takes place when preparing to iterate segments, so the incremental cost
        // of actually walking the segments once calculated is ignorable, but halting the scan 
        // after looking at a single segment would mean repeating the overhead on all segments
        // in the sentence.
        private bool ScanRangeCheckTimeLimitCallback(object sentence, object o)
        { 
            TextMapCallbackData data = (TextMapCallbackData)o;
            ScanStatus status; 
 
            status = (ScanStatus)data.Data;
 
            // Stop iterating if we exceed our time budget.
            // In which case, take note of where we left off.
            if (status.HasExceededTimeLimit)
            { 
                Invariant.Assert(status.TimeoutPosition == null); // We should only set this once....
 
                int sentenceEndOffset = SpellerInterop.GetSentenceEndOffset(sentence); 

                if (sentenceEndOffset >= 0) 
                {
                    // The end of this segment may extend past textMap.ContentEndOffset,
                    // in the case of multi-word errors.  So truncate.
                    // 
                    int timeOutOffset = Math.Min(data.TextMap.ContentEndOffset, sentenceEndOffset);
 
                    // Be careful not to stop the iteration if we haven't reached 
                    // the content start yet.  It's possible that the context text
                    // will extend backwards into another sentence.  We must always 
                    // make forward progress, even if doing so exceeds the timeout
                    // limit, otherwise we'll get stuck infinitely eating idle time.
                    //
                    // 

 
 

                    if (timeOutOffset > data.TextMap.ContentStartOffset) 
                    {
                        status.TimeoutPosition = data.TextMap.MapOffsetToPosition(timeOutOffset);
                    }
                } 
            }
 
            return (status.TimeoutPosition == null); 
        }
 
        // Flags a run of text with an error.
        // In two exceptional circumstances we schedule an idle-time callback
        // to re-analyze the run instead of marking it:
        // - when the caret is within the error text. 
        // - when an IME composition covers the text.
        private void MarkErrorRange(TextMap textMap, SpellerInterop.STextRange sTextRange) 
        { 
            ITextPointer errorStart;
            ITextPointer errorEnd; 

            if (sTextRange.Start + sTextRange.Length > textMap.ContentEndOffset)
            {
                // We found an error that starts in the content but extends into 
                // the context.  This must be a multi-word error.
                // For now, ignore it. 
                // 
                return;
            } 

            errorStart = textMap.MapOffsetToPosition(sTextRange.Start);
            errorEnd = textMap.MapOffsetToPosition(sTextRange.Start + sTextRange.Length);
 
            if (sTextRange.Start < textMap.ContentStartOffset)
            { 
                Invariant.Assert(sTextRange.Start + sTextRange.Length > textMap.ContentStartOffset); 

                // We've found an error that start in the context and extends into 
                // the content.  This can happen as more text is revealed to the
                // speller engine as the caret moves forward.
                // E.g., while scanning "avalon's" we flag an error over "avalon",
                // ignoring the "'s" because the caret is positioned within that segment. 
                // Then, the user hits space and now we analyze "'s" along with its
                // preceding context "avalon".  In this final scan, "avalon's" as a while 
                // is flagged as an error and we enter this if statement. 

                // We must mark the range clean before we can mark it dirty. 
                // _statusTable.MarkErrorRange can only handle clean runs.
                _statusTable.MarkCleanRange(errorStart, errorEnd);
            }
 
            _statusTable.MarkErrorRange(errorStart, errorEnd);
        } 
 
        // Examines a position and returns to two relative positions:
        // 
        // contentPosition -> position moved inward to the nearest word
        // break (opposite direction param, toward the content).
        //
        // contextPosition -> position moved outward away from content 
        // to a word break that includes sufficient text to handle multi-
        // word errors correctly. 
        ///  
        /// Critical - it calls SetContextOption(), which is Critical.
        /// TreatAsSafe - it calls it with a trusted parameter, that doesn't come from untrusted sources. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private void ExpandToWordBreakAndContext(ITextPointer position, LogicalDirection direction, XmlLanguage language,
            out ITextPointer contentPosition, out ITextPointer contextPosition) 
        {
            ITextPointer start; 
            ITextPointer end; 
            ITextPointer outwardPosition;
            ITextPointer inwardPosition; 
            TextMap textMap;
            ArrayList segments;
            SpellerInterop.STextRange sTextRange;
            LogicalDirection inwardDirection; 
            int i;
 
            contentPosition = position; 
            contextPosition = position;
 
            if (position.GetPointerContext(direction) == TextPointerContext.None)
            {
                // There is no following context, we're at document start/end.
                return; 
            }
 
            // Disable spell checking functionality since we're only 
            // interested in word breaks here.  This greatly cuts down
            // the engine's workload. 
            _spellerInterop.SetContextOption("IsSpellChecking", false);

            //
            // Build an array of wordbreak offsets surrounding the position. 
            //
 
            // 1. Search outward, into surrounding text.  We need MinWordBreaksForContext 
            // word breaks to handle multi-word errors.
            outwardPosition = SearchForWordBreaks(position, direction, language, MinWordBreaksForContext, true /* stopOnError */); 

            // 2. Search inward, towards content.  We just need one word break inward.
            inwardDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward;
            inwardPosition = SearchForWordBreaks(position, inwardDirection, language, 1, false /* stopOnError */); 

            // Get combined word breaks.  This may not be the same as we calculated 
            // in two parts above, since we don't know yet whether or not position is 
            // on a word break.
            if (direction == LogicalDirection.Backward) 
            {
                start = outwardPosition;
                end = inwardPosition;
            } 
            else
            { 
                start = inwardPosition; 
                end = outwardPosition;
            } 
            textMap = new TextMap(start, end, position, position);
            segments = new ArrayList(MinWordBreaksForContext + 1);
            _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null,
                new SpellerInterop.EnumTextSegmentsCallback(ExpandToWordBreakCallback), segments); 

            // 
            // Use our table of word breaks to calculate context and content positions. 
            //
            if (segments.Count == 0) 
            {
                // No segments.  This can happen if position is surrounded by
                // nothing but white space.  We've already initialized contentPosition
                // and contextPosition so there's nothing to do. 
            }
            else 
            { 
                int leftWordBreak;
                int rightWordBreak; 
                int contentOffset;
                int contextOffset;

                // Figure out where position lives in the segment list. 
                i = FindPositionInSegmentList(textMap, direction, segments, out leftWordBreak, out rightWordBreak);
 
                // contentPosition should be an edge on the segment we found. 
                if (direction == LogicalDirection.Backward)
                { 
                    contentOffset = textMap.ContentStartOffset == rightWordBreak ? rightWordBreak : leftWordBreak;
                }
                else
                { 
                    contentOffset = textMap.ContentStartOffset == leftWordBreak ? leftWordBreak : rightWordBreak;
                } 
                contentPosition = textMap.MapOffsetToPosition(contentOffset); 

                // contextPosition should be MinWordBreaksForContext - 1 words away. 
                if (direction == LogicalDirection.Backward)
                {
                    i -= (MinWordBreaksForContext - 1);
                    sTextRange = (SpellerInterop.STextRange)segments[Math.Max(i, 0)]; 
                    // We might actually follow contentOffset if we're at the document edge.
                    // Don't let that happen. 
                    contextOffset = Math.Min(sTextRange.Start, contentOffset); 
                }
                else 
                {
                    i += MinWordBreaksForContext;
                    sTextRange = (SpellerInterop.STextRange)segments[Math.Min(i, segments.Count-1)];
                    // We might actually preceed contentOffset if we're at the document edge. 
                    // Don't let that happen.
                    contextOffset = Math.Max(sTextRange.Start + sTextRange.Length, contentOffset); 
                } 
                contextPosition = textMap.MapOffsetToPosition(contextOffset);
            } 

            // Final fixup: if the dirty range covers only formatting (which is not passed
            // to the speller engine) then we might actually "expand" in the wrong
            // direction, since the TextMap will jump over formatting. 
            // Backup if necessary.
            if (direction == LogicalDirection.Backward) 
            { 
                if (position.CompareTo(contentPosition) < 0)
                { 
                    contentPosition = position;
                }
                if (position.CompareTo(contextPosition) < 0)
                { 
                    contextPosition = position;
                } 
            } 
            else
            { 
                if (position.CompareTo(contentPosition) > 0)
                {
                    contentPosition = position;
                } 
                if (position.CompareTo(contextPosition) > 0)
                { 
                    contextPosition = position; 
                }
            } 
        }

        // Helper for ExpandToWordBreakAndContext -- returns the index of a segment
        // containing or bordering a specified position (textMap.ContentStartOffset). 
        // Also returns the offset, within the TextMap, of the two word breaks surrounding
        // TextMap.ContentStartOffset.  The word breaks may be segment edges, or 
        // the extent of a run of whitespace between two segments. 
        private int FindPositionInSegmentList(TextMap textMap, LogicalDirection direction, ArrayList segments,
            out int leftWordBreak, out int rightWordBreak) 
        {
            SpellerInterop.STextRange sTextRange;
            int index;
 
            // Make the compiler happy by initializing the out's to bogus values.
            leftWordBreak = Int32.MaxValue; 
            rightWordBreak = -1; 

            // Check before the first segment, which start at the first 
            // non-whitespace char.
            sTextRange = (SpellerInterop.STextRange)segments[0];
            if (textMap.ContentStartOffset < sTextRange.Start)
            { 
                leftWordBreak = 0;
                rightWordBreak = sTextRange.Start; 
                index = -1; 
            }
            else 
            {
                // Check after the last segment, which does not include final whitespace.
                sTextRange = (SpellerInterop.STextRange)segments[segments.Count-1];
                if (textMap.ContentStartOffset > sTextRange.Start + sTextRange.Length) 
                {
                    leftWordBreak = sTextRange.Start + sTextRange.Length; 
                    rightWordBreak = textMap.TextLength; 
                    index = segments.Count;
                } 
                else
                {
                    // Walk the segment list, checking each segment and space in between.
                    for (index = 0; index < segments.Count; index++) 
                    {
                        sTextRange = (SpellerInterop.STextRange)segments[index]; 
 
                        leftWordBreak = sTextRange.Start;
                        rightWordBreak = sTextRange.Start + sTextRange.Length; 

                        // Check if we're inside this segment.
                        if (leftWordBreak <= textMap.ContentStartOffset &&
                            rightWordBreak >= textMap.ContentStartOffset) 
                        {
                            break; 
                        } 
                        // Or if we're between this segment and the next one --
                        // segments do not include white space. 
                        if (index < segments.Count - 1 &&
                            rightWordBreak < textMap.ContentStartOffset)
                        {
                            sTextRange = (SpellerInterop.STextRange)segments[index + 1]; 
                            leftWordBreak = rightWordBreak;
                            rightWordBreak = sTextRange.Start; 
 
                            if (rightWordBreak > textMap.ContentStartOffset)
                            { 
                                // position is between segments[i] and segments[i+1].
                                // Adjust i so that adding MinWordBreaksForContext below
                                // doesn't include an extra word.
                                if (direction == LogicalDirection.Backward) 
                                {
                                    index++; 
                                } 
                                break;
                            } 
                        }
                    }
                }
            } 

            Invariant.Assert(leftWordBreak <= textMap.ContentStartOffset && textMap.ContentStartOffset <= rightWordBreak); 
 
            return index;
        } 

        // Helper for ExpandToWordBreakAndContext -- returns the position
        // of the nth word break in the specified direction.
        // If stopOnError is true, the search will halt if an error run is 
        // encountered along the way.  The search is also halted if text in
        // a new language is encountered. 
        ///  
        /// Critical - it calls EnumTextSegments(), which is Critical.
        /// TreatAsSafe - it calls it passing a TextMap object constructed appropriately in this method. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private ITextPointer SearchForWordBreaks(ITextPointer position, LogicalDirection direction, XmlLanguage language, int minWordCount, bool stopOnError)
        { 
            ITextPointer closestErrorPosition;
            ITextPointer searchPosition; 
            ITextPointer start; 
            ITextPointer end;
            StaticTextPointer nextErrorTransition; 
            int segmentCount;
            TextMap textMap;

            searchPosition = position.CreatePointer(); 

            closestErrorPosition = null; 
            if (stopOnError) 
            {
                nextErrorTransition = _statusTable.GetNextErrorTransition(position.CreateStaticPointer(), direction); 
                if (!nextErrorTransition.IsNull)
                {
                    closestErrorPosition = nextErrorTransition.CreateDynamicTextPointer(LogicalDirection.Forward);
                } 
            }
 
            bool hitBreakPoint = false; 

            do 
            {
                searchPosition.MoveByOffset(direction == LogicalDirection.Backward ? -ContextBlockSize : +ContextBlockSize);

                // Don't go past closestErrorPosition. 
                if (closestErrorPosition != null)
                { 
                    if (direction == LogicalDirection.Backward && closestErrorPosition.CompareTo(searchPosition) > 0 || 
                        direction == LogicalDirection.Forward && closestErrorPosition.CompareTo(searchPosition) < 0)
                    { 
                        searchPosition.MoveToPosition(closestErrorPosition);
                        hitBreakPoint = true;
                    }
                } 

                // Don't venture into text in another language. 
                ITextPointer closestLanguageTransition = GetNextLanguageTransition(position, direction, language, searchPosition); 

                if (direction == LogicalDirection.Backward && closestLanguageTransition.CompareTo(searchPosition) > 0 || 
                    direction == LogicalDirection.Forward && closestLanguageTransition.CompareTo(searchPosition) < 0)
                {
                    searchPosition.MoveToPosition(closestLanguageTransition);
                    hitBreakPoint = true; 
                }
 
                if (direction == LogicalDirection.Backward) 
                {
                    start = searchPosition; 
                    end = position;
                }
                else
                { 
                    start = position;
                    end = searchPosition; 
                } 

                textMap = new TextMap(start, end, start, end); 
                segmentCount = _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null, null, null);
            }
            while (!hitBreakPoint &&
                   segmentCount < minWordCount + 1 && 
                   searchPosition.GetPointerContext(direction) != TextPointerContext.None);
 
            return searchPosition; 
        }
 
        // Returns the closest of either a halting position or the position preceding text
        // tagged with a differing XmlLanguage from a start position.
        private ITextPointer GetNextLanguageTransition(ITextPointer position, LogicalDirection direction, XmlLanguage language, ITextPointer haltPosition)
        { 
            ITextPointer navigator = position.CreatePointer();
 
            while ((direction == LogicalDirection.Forward && navigator.CompareTo(haltPosition) < 0) || 
                   (direction == LogicalDirection.Backward && navigator.CompareTo(haltPosition) > 0))
            { 
                if (GetCurrentLanguage(navigator) != language)
                    break;

                navigator.MoveToNextContextPosition(direction); 
            }
 
            // If we moved past haltPosition on the final MoveToNextContextPosition, move back. 
            if ((direction == LogicalDirection.Forward && navigator.CompareTo(haltPosition) > 0) ||
                (direction == LogicalDirection.Backward && navigator.CompareTo(haltPosition) < 0)) 
            {
                navigator.MoveToPosition(haltPosition);
            }
 
            return navigator;
        } 
 
        // Called indirectly by ExpandToWordBreakAndContext while iterating segments.
        // Builds up an array of segment offsets while iterating. 
        private bool ExpandToWordBreakCallback(object textSegment, object o)
        {
            ArrayList segments = (ArrayList)o;
            SpellerInterop.STextRange sTextRange = SpellerInterop.GetSegmentRange(textSegment); 

            segments.Add(sTextRange); 
 
            return true;
        } 

        // Returns true if a user has tagged the specified word with "Ignore All".
        private bool IsIgnoredWord(char[] word)
        { 
            bool isIgnoredWord = false;
 
            if (_ignoredWordsList != null) 
            {
                isIgnoredWord = _ignoredWordsList.BinarySearch(new string(word), new CaseInsensitiveComparer(_defaultCulture)) >= 0; 
            }

            return isIgnoredWord;
        } 

        // Returns true if we have an engine capable of proofing the specified 
        // language. 
        private static bool CanSpellCheck(CultureInfo culture)
        { 
            bool canSpellCheck;

            switch (culture.TwoLetterISOLanguageName)
            { 
                case "en":
                case "de": 
                case "fr": 
                case "es":
                    canSpellCheck = true; 
                    break;

                default:
                    canSpellCheck = false; 
                    break;
            } 
 
            return canSpellCheck;
        } 

        // Sets the speller engine language and spelling reform options.
        /// 
        /// Critical - It calls SetContextOption(), which is Critical. 
        /// TreatAsSafe - it calls it with a well known option.
        ///  
        [SecurityCritical, SecurityTreatAsSafe] 
        private void SetCulture(CultureInfo culture)
        { 
            //
            // Set the language.
            //
 
            _spellerInterop.SetLocale(culture.LCID);
 
            // 
            // Set spelling reform, if necessary.
            // 

            string option;

            // The engine only accepts values for german and french. 
            switch (culture.TwoLetterISOLanguageName)
            { 
                case "de": 
                    option = "GermanReform";
                    break; 

                case "fr":
                    option = "FrenchReform";
                    break; 

                default: 
                    option = null; 
                    break;
            } 

            if (option != null)
            {
                switch (_spellingReform) 
                {
                    case SpellingReform.Prereform: 
                        _spellerInterop.SetContextOption(option, SpellerInterop.SpellingReform.Prereform); 
                        break;
 
                    case SpellingReform.Postreform:
                        _spellerInterop.SetContextOption(option, SpellerInterop.SpellingReform.Postreform);
                        break;
 
                    case SpellingReform.PreAndPostreform:
                        if (option == "GermanReform") 
                        { 
                            // BothPreAndPost is disallowed for german -- the engine has undefined results.
                            _spellerInterop.SetContextOption(option, SpellerInterop.SpellingReform.Postreform); 
                        }
                        else
                        {
                            _spellerInterop.SetContextOption(option, SpellerInterop.SpellingReform.BothPreAndPost); 
                        }
                        break; 
                } 
            }
        } 

        // Scans the word containing a specified character.
        private void ScanPosition(ITextPointer position, LogicalDirection direction)
        { 
            ITextPointer start;
            ITextPointer end; 
 
            if (direction == LogicalDirection.Forward)
            { 
                start = position;
                end = position.CreatePointer(+1);
            }
            else 
            {
                start = position.CreatePointer(-1); 
                end = position; 
            }
 
            ScanRange(start, end, Int64.MaxValue /* timeLimit */);
        }

        // Returns the XmlLanguage of the parent element at position. 
        private XmlLanguage GetCurrentLanguage(ITextPointer position)
        { 
            XmlLanguage language; 

            GetCurrentCultureAndLanguage(position, out language); 

            return language;
        }
 
        // Returns the CultureInfo of the content at a position.
        // Returns null if there is no CultureInfo matching the current XmlLanguage. 
        private CultureInfo GetCurrentCultureAndLanguage(ITextPointer position, out XmlLanguage language) 
        {
            CultureInfo cultureInfo; 
            bool hasModifiers;

            // TextBox takes the input language iff no local LanguageProperty is set.
            if (!_textEditor.AcceptsRichContent && 
                _textEditor.UiScope.GetValueSource(FrameworkElement.LanguageProperty, null, out hasModifiers) == BaseValueSourceInternal.Default)
            { 
                cultureInfo = _defaultCulture; 
                language = XmlLanguage.GetLanguage(cultureInfo.IetfLanguageTag);
            } 
            else
            {
                language = (XmlLanguage)position.GetValue(FrameworkElement.LanguageProperty);
 
                if (language == null)
                { 
                    cultureInfo = null; 
                }
                else 
                {
                    try
                    {
                        cultureInfo = language.GetSpecificCulture(); 
                    }
                    catch (InvalidOperationException) 
                    { 
                        // Someone set a bogus language on the run.
                        cultureInfo = null; 
                    }
                }
            }
 
            return cultureInfo;
        } 
 
        /// 
        /// If give Uri is relative, creates full path by appending current directory. 
        /// 
        /// 
        /// 
        private static Uri ResolvePathUri(Uri uri) 
        {
            Uri fileUri; 
            if (!uri.IsAbsoluteUri) 
            {
                fileUri = new Uri(new Uri(Directory.GetCurrentDirectory() + "/"), uri); 
            }
            else
            {
                fileUri = uri; 
            }
 
            return fileUri; 
        }
 
        /// 
        /// Loads dictionary specified by a pack URI.
        /// Creates a temprorary file, copies dictionary data referenced by pack URI
        /// into a temp file and loads the temp file as a dictionary. 
        /// 
        ///  
        ///  
        /// critical - asserts EnvironmentPermission permission.
        ///  
        [SecurityCritical]
        private void LoadDictionaryFromPackUri(Uri item)
        {
            string tempFolder; 
            Uri tempLocationUri;
 
            // We need environment permission to call Path.GetTempPath() 
            new EnvironmentPermission(PermissionState.Unrestricted).Assert();
            try 
            {
                tempLocationUri = LoadPackFile(item);
                tempFolder = System.IO.Path.GetTempPath();
            } 
            finally
            { 
                EnvironmentPermission.RevertAssert(); 
            }
 
            SpellerInterop.ILexicon lexicon = LoadDictionaryInTempFile(tempFolder, tempLocationUri);
            UriMap.Add(item, new DictionaryInfo(tempLocationUri, lexicon));
        }
 
        /// 
        /// Loads dictionary from a temp. file 
        ///  
        /// 
        ///  
        /// 
        /// 
        /// critical -
        /// 1. Works with critical member _spellerInterop. 
        /// 2. Asserts FileIOPermission permission.
        ///  
        [SecurityCritical] 
        private SpellerInterop.ILexicon LoadDictionaryInTempFile(string tempFolder, Uri tempLocationUri)
        { 
            try
            {
                return _spellerInterop.LoadDictionary(tempLocationUri, tempFolder);
            } 
            finally
            { 
                // make sure all temp files are cleaned up. 
                if (tempLocationUri != null)
                { 
                    new FileIOPermission(PermissionState.Unrestricted).Assert();
                    try
                    {
                        System.IO.File.Delete(tempLocationUri.LocalPath); 
                    }
                    catch (Exception e) 
                    { 
                        // we're catching exception only to dump debug data, then rethrow.
                        if(SecurityHelper.CheckUnmanagedCodePermission())//we're in full trust 
                        {
                            System.Diagnostics.Trace.Write(string.Format(CultureInfo.InvariantCulture, "Failure to delete temporary file with custom dictionary data. file Uri:{0},exception:{1}", tempLocationUri.ToString(), e.ToString()));
                        }
                        throw; 
                    }
                    finally 
                    { 
                        FileIOPermission.RevertAssert();
                    } 
                }
            }
        }
 
        /// 
        /// Creates a temp file and copies content of dictionary file referenced by the input 
        ///  to the temp file. 
        /// The caller is responsible for deleting the file.
        ///  
        /// 
        /// Returns Uri corresponding to the newly created temp file.
        /// 
        ///  
        /// Critical - Returns local file path.
        ///  
        [SecurityCritical] 
        private static Uri LoadPackFile(Uri uri)
        { 
            Invariant.Assert(System.IO.Packaging.PackUriHelper.IsPackUri(uri));
            string tmpFilePath = null;
            string tempFolderPath = System.IO.Path.GetTempPath();
            // Assert is needed for Partial Trust case for GetTempFileName() and FileStream to work. 
            new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, tempFolderPath).Assert();
            try 
            { 
                tmpFilePath = System.IO.Path.GetTempFileName();
 
                using (FileStream outputStream = new FileStream(tmpFilePath, FileMode.Open, FileAccess.ReadWrite))
                {
                    Uri resolvedUri = MS.Internal.Utility.BindUriHelper.GetResolvedUri(BaseUriHelper.PackAppBaseUri, uri);
                    using (Stream sourceStream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(resolvedUri)) 
                    {
                        CopyStream(sourceStream, outputStream); 
                    } 
                }
            } 
            finally
            {
                FileIOPermission.RevertAssert();
            } 
            return new Uri(tmpFilePath);
        } 
 
        /// 
        /// Copies data from src stream into dst stream 
        /// 
        /// 
        /// 
        ///  
        private static void CopyStream(Stream source, FileStream destination)
        { 
            byte[] transferBuffer = new byte[1024]; 
            int bytesRead = 0;
            do 
            {
                bytesRead = source.Read(transferBuffer, 0, transferBuffer.Length);
                if (bytesRead > 0)
                { 
                    destination.Write(transferBuffer, 0, bytesRead);
                } 
            } while (bytesRead > 0); 
        }
 
        #endregion Private methods

        //-----------------------------------------------------
        // 
        //  Private Types
        // 
        //------------------------------------------------------ 

        #region Private Types 

        // Holds a run of text intended for the speller engine.
        // Because the engine only understands plain text, this class coverts
        // arbitrary runs of document text to speller-suitable plain text 
        // and keeps a table that allows it to efficiently map back from plain
        // text offsets (used by the engine) to ITextPointers. 
        private class TextMap 
        {
            // Creates a new instance. 
            // contextStart/End refer to the whole run of text.
            // contentStart/End are a subset of the text, which is what
            // the engine will actually tag with errors.
            // The space between context and content is used by the engine 
            // to correctly analyze multiple word phrase like "Los Angeles"
            // that could otherwise be truncated and incorrectly tagged. 
            internal TextMap(ITextPointer contextStart, ITextPointer contextEnd, 
                ITextPointer contentStart, ITextPointer contentEnd)
            { 
                ITextPointer position;
                int maxChars;
                int inlineCount;
                int runCount; 
                int i;
                int distance; 
 
                Invariant.Assert(contextStart.CompareTo(contentStart) <= 0);
                Invariant.Assert(contextEnd.CompareTo(contentEnd) >= 0); 

                _basePosition = contextStart.GetFrozenPointer(LogicalDirection.Backward);

                position = contextStart.CreatePointer(); 
                maxChars = contextStart.GetOffsetToPosition(contextEnd);
 
                _text = new char[maxChars]; 
                _positionMap = new int[maxChars+1];
 
                _textLength = 0;
                inlineCount = 0;

                _contentStartOffset = 0; 
                _contentEndOffset = 0;
 
                // Iterate over the run, building up a matching plain text buffer 
                // and a table that tells us how to map back to the original text.
                while (position.CompareTo(contextEnd) < 0) 
                {
                    if (position.CompareTo(contentStart) == 0)
                    {
                        _contentStartOffset = _textLength; 
                    }
                    if (position.CompareTo(contentEnd) == 0) 
                    { 
                        _contentEndOffset = _textLength;
                    } 

                    switch (position.GetPointerContext(LogicalDirection.Forward))
                    {
                        case TextPointerContext.Text: 
                            runCount = position.GetTextRunLength(LogicalDirection.Forward);
                            runCount = Math.Min(runCount, _text.Length - _textLength); 
                            runCount = Math.Min(runCount, position.GetOffsetToPosition(contextEnd)); 

                            position.GetTextInRun(LogicalDirection.Forward, _text, _textLength, runCount); 

                            for (i = _textLength; i < _textLength + runCount; i++)
                            {
                                _positionMap[i] = i + inlineCount; 
                            }
 
                            distance = position.GetOffsetToPosition(contentStart); 
                            if (distance >= 0 && distance <= runCount)
                            { 
                                _contentStartOffset = _textLength + position.GetOffsetToPosition(contentStart);
                            }
                            distance = position.GetOffsetToPosition(contentEnd);
                            if (distance >= 0 && distance <= runCount) 
                            {
                                _contentEndOffset = _textLength + position.GetOffsetToPosition(contentEnd); 
                            } 

                            position.MoveByOffset(runCount); 
                            _textLength += runCount;
                            break;

                        case TextPointerContext.ElementStart: 
                        case TextPointerContext.ElementEnd:
                            if (IsAdjacentToFormatElement(position)) 
                            { 
                                // Filter out formatting tags from the plain text.
                                inlineCount++; 
                            }
                            else
                            {
                                // Stick in a word break to account for the block element. 
                                _text[_textLength] = ' ';
                                _positionMap[_textLength] = _textLength + inlineCount; 
                                _textLength++; 
                            }
                            position.MoveToNextContextPosition(LogicalDirection.Forward); 
                            break;

                        case TextPointerContext.EmbeddedElement:
                            _text[_textLength] = '\xf8ff'; // Unicode private use. 
                            _positionMap[_textLength] = _textLength + inlineCount;
                            _textLength++; 
 
                            position.MoveToNextContextPosition(LogicalDirection.Forward);
                            break; 
                    }
                }

                if (position.CompareTo(contentEnd) == 0) 
                {
                    _contentEndOffset = _textLength; 
                } 

                if (_textLength > 0) 
                {
                    _positionMap[_textLength] = _positionMap[_textLength - 1] + 1;
                }
                else 
                {
                    _positionMap[0] = 0; 
                } 

                Invariant.Assert(_contentStartOffset <= _contentEndOffset); 
            }

            // Returns an ITextPointer in the document with position matching
            // an offset within the plain text. 
            internal ITextPointer MapOffsetToPosition(int offset)
            { 
                Invariant.Assert(offset >= 0 && offset <= _textLength); 

                return _basePosition.CreatePointer(_positionMap[offset]); 
            }

            // Offset in the plain text of the content start.
            internal int ContentStartOffset 
            {
                get { return _contentStartOffset; } 
            } 

            // Offset in the plain text of the content end. 
            internal int ContentEndOffset
            {
                get { return _contentEndOffset; }
            } 

            // Plain text representation of the document run. 
            // Do not use Text.Length!  The actual content size may be smaller, 
            // use the TextLength property instead.
            internal char[] Text 
            {
                get { return _text; }
            }
 
            // Length of the plain text.  This may be less than Text.Length --
            // we allocate a maximum value for the array which is not always used 
            // by the final content. 
            internal int TextLength
            { 
                get { return _textLength; }
            }

            // Returns true if pointer preceeds an Inline start or end edge. 
            private bool IsAdjacentToFormatElement(ITextPointer pointer)
            { 
                TextPointerContext context; 
                bool isAdjacentToFormatElement;
 
                isAdjacentToFormatElement = false;

                context = pointer.GetPointerContext(LogicalDirection.Forward);
 
                if (context == TextPointerContext.ElementStart &&
                    TextSchema.IsFormattingType(pointer.GetElementType(LogicalDirection.Forward))) 
                { 
                    isAdjacentToFormatElement = true;
                } 
                else if (context == TextPointerContext.ElementEnd &&
                         TextSchema.IsFormattingType(pointer.ParentType))
                {
                    isAdjacentToFormatElement = true; 
                }
 
                return isAdjacentToFormatElement; 
            }
 
            // Position of the plain text block within the document.
            private readonly ITextPointer _basePosition;

            // Plain text version of the document run. 
            private readonly char[] _text;
 
            // Map of plain text offsets to document symbol offsets relative 
            // to _basePosition.
            private readonly int[] _positionMap; 

            // Size of the content within _text.
            private readonly int _textLength;
 
            // Plain text offset of the content start.
            private readonly int _contentStartOffset; 
 
            // Plain text offset of the content end.
            private readonly int _contentEndOffset; 
        }

        // Holds state tracking the progress of speller scan,
        // used during idle time document analysis. 
        private class ScanStatus
        { 
            // Creates a new instance.  timeLimit is the maximum value of 
            // DateTime.Now.Ticks at which the scan should end.
            internal ScanStatus(long timeLimit) 
            {
                _timeLimit = timeLimit;
            }
 
            // Returns true if the scan has exceeded its time budget.
            internal bool HasExceededTimeLimit 
            { 
                get
                { 
                    long nowTicks = DateTime.Now.Ticks;

#if DEBUG
                    // Track how far over budget we are the first time we check. 
                    if (nowTicks >= _timeLimit && _debugMsOverTimeLimit == 0)
                    { 
                        _debugMsOverTimeLimit = (int)(((double)(nowTicks - _timeLimit)) / 10000); 
                    }
#endif // DEBUG 

                    return nowTicks >= _timeLimit;
                }
            } 

            // If we've timed out, holds the position we left off -- the remainder 
            // of the text run yet to be analyzed. 
            internal ITextPointer TimeoutPosition
            { 
                get { return _timeoutPosition; }
                set { _timeoutPosition = value; }
            }
 
            // Budget for this scan, in 100 nanosecond intervals.
            private readonly long _timeLimit; 
 
            // If we've timed out, holds the position we left off -- the remainder
            // of the text run yet to be analyzed. 
            private ITextPointer _timeoutPosition;

#if DEBUG
            // Number of milliseconds we've exceeded our time limit by. 
            private int _debugMsOverTimeLimit;
#endif // DEBUG 
        } 

        // Container used to hold state for SpellerInterop callbacks. 
        private class TextMapCallbackData
        {
            internal TextMapCallbackData(TextMap textmap, object data)
            { 
                _textmap = textmap;
                _data = data; 
            } 

            internal TextMap TextMap { get { return _textmap; } } 

            internal object Data { get { return _data; } }

            private readonly TextMap _textmap; 
            private readonly object _data;
        } 
 
        /// 
        /// Holds custom dicitonary related data. 
        /// 
        private class DictionaryInfo
        {
            ///  
            /// Constructor
            ///  
            ///  
            /// 
            ///  
            /// critical - accesses file members holdign critical fields: file path and COM interface wrapper.
            /// 
            [SecurityCritical]
            internal DictionaryInfo(Uri pathUri, SpellerInterop.ILexicon lexicon) 
            {
                _pathUri = pathUri; 
                _lexicon = lexicon; 
            }
 
            /// 
            /// critical - returns value of critical field whcih holds file path
            /// 
            internal Uri PathUri 
            {
                [SecurityCritical] 
                get 
                {
                    return _pathUri; 
                }
            }

            ///  
            /// critical - returns wrapper to COM interface.
            ///  
            internal SpellerInterop.ILexicon Lexicon 
            {
                [SecurityCritical] 
                get
                {
                    return _lexicon;
                } 
            }
 
            ///  
            /// 
            /// critical - a reference to internal wrapper to a COM interface. 
            /// 
            /// 
            [SecurityCritical]
            private readonly SpellerInterop.ILexicon _lexicon; 

            ///  
            /// File location where custom dictionary is loaded from . 
            /// 
            ///  
            /// critical - a file paht dicitonary file.
            /// 
            [SecurityCritical]
            private readonly Uri _pathUri; 

        } 
 
        #endregion Private Types
 
        //-----------------------------------------------------
        //
        //  Private Fields
        // 
        //------------------------------------------------------
 
        #region Private Fields 

        // Max time slice for speller background proofing, in milliseconds. 
        // Larger numbers mean we can scan entire documents more quickly,
        // but with less responsiveness to user interruptions.
        private const int MaxIdleTimeSliceMs = 20;
        // Max time slice for speller background proofing, in 100 nanosecond intervals. 
        private const long MaxIdleTimeSliceNs = MaxIdleTimeSliceMs*10000;
 
        // Max number of characters to pass to engine in a single call. 
        // Increasing this number will decrease the time to scan an entire
        // document, at the cost of more time spent in individual Idle callbacks 
        // (app is less respsonsive).
        // NLG devs have warned us not to set this value below 32, to avoid
        // cases where they don't have enough context to identify errors
        // correctly. 
        private const int MaxScanBlockSize = 64;
 
        // Number of characters to advance on each iteration while searching 
        // for context.  We need at least three words on either side of a text
        // run to give the speller enough context to identify multi-word 
        // errors (or non-errors, like Los Angeles).
        //
        // Larger numbers here will mean fewer text scans, but a smaller
        // minimum scan. 
        private const int ContextBlockSize = 32;
 
        // The minimum number of word breaks we need to have sufficient 
        // context for detecting multi-word errors.  This number is
        // a constant -- it is not something to adjust for perf. 
        private const int MinWordBreaksForContext = 4;

        // TextEditor that owns this Speller.
        private TextEditor _textEditor; 

        // A run-length array tracking speller status of all text in the document. 
        private SpellerStatusTable _statusTable; 

        // HighlightLayer used to display error squiggles. 
        private SpellerHighlightLayer _highlightLayer;

        // Engine object used to analyze runs of text.
        // kepowell from the nlg team suggests that we cache a single 
        // ITextChunk/ITextContext for the thread and reuse it across
        // Spellers.  FE TextChunks in particular are expensive, because 
        // they cache large amounts of data per instance, on the order 
        // of 10k's of data.
        ///  
        ///     Critical: This object could expose a COM object which can run code under elevation
        /// 
        [SecurityCritical]
        private SpellerInterop _spellerInterop; 

        // Current spelling reform setting. 
        private SpellingReform _spellingReform; 

        // true if we've already posted but not yet received a background queue item. 
        private bool _pendingIdleCallback;

        // true if we have an active TextSelection.Changed listener.
        private bool _pendingCaretMovedCallback; 

        // List of words tagged by the user as non-errors. 
        private ArrayList _ignoredWordsList; 

        // The CultureInfo associated with this speller. 
        // Used for ignored words comparison, and plain text controls (TextBox).
        private readonly CultureInfo _defaultCulture;

        // Set true if the nl6 library is unavailable. 
        private bool _failedToInit;
 
        ///  
        /// Holds mapping between original Uri passed in by user and COM reference to the loaded dictionary.
        /// This dictionary MUST NOT contain any null items. 
        /// 
        ///
        /// critical - holds file paths and reference to a COM interface wrappers.
        /// 
        [SecurityCritical]
        private Dictionary _uriMap; 
 
        #endregion Private Fields
    } 
}


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