WindowsRichEditRange.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / UIAutomation / Win32Providers / MS / Internal / AutomationProxies / WindowsRichEditRange.cs / 1305600 / WindowsRichEditRange.cs

                            /****************************************************************************\ 
*
* File: WindowsRichEditRange.cs
*
* Description: 
* Common code to support the ITextPointerProvider interface
* 
* Revision History: 
* 2/12/04:  a-davidj created.
* 
* Copyright (C) 2004 by Microsoft Corporation.  All rights reserved.
*
\***************************************************************************/
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691 
 
using System;
using System.Collections; 
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Automation; 
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text; 
using System.ComponentModel; 
using MS.Win32;
 
namespace MS.Internal.AutomationProxies
{
    internal class WindowsRichEditRange : ITextRangeProvider
    { 
        internal WindowsRichEditRange(ITextRange range, WindowsRichEdit pattern)
        { 
            Debug.Assert(range != null); 
            Debug.Assert(pattern != null);
 
            _range = range;
            _pattern = pattern;
        }
 
        //-----------------------------------------------------
        // 
        //  Public Methods 
        //
        //----------------------------------------------------- 

        #region Public Methods

        ITextRangeProvider ITextRangeProvider.Clone() 
        {
            // Use ITextRange::GetDuplicate to duplicate the ITextRange. 
            ITextRange range = _range.GetDuplicate(); 
            return range!=null ? new WindowsRichEditRange(range, _pattern) : null;
        } 

        bool ITextRangeProvider.Compare(ITextRangeProvider range)
        {
            // Use ITextRange::IsEqual to compare ITextRanges. 
            WindowsRichEditRange otherRange = (WindowsRichEditRange)range;
            return _range.IsEqual(otherRange._range)==TomBool.tomTrue; 
        } 

        int ITextRangeProvider.CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) 
        {
            // Get the endpoint character positions using ITextRange::GetStart && ITextRange::GetEnd.
            // Subtract the character positions to get the return value.
            WindowsRichEditRange otherRange = (WindowsRichEditRange)targetRange; 
            int e1 = (endpoint == TextPatternRangeEndpoint.Start) ? _range.Start : _range.End;
            int e2 = (targetEndpoint == TextPatternRangeEndpoint.Start) ? otherRange._range.Start : otherRange._range.End; 
            return e1 - e2; 
        }
 
        void ITextRangeProvider.ExpandToEnclosingUnit(TextUnit unit)
        {
            Misc.SetFocus(_pattern._hwnd);
 
            switch (unit)
            { 
                case TextUnit.Format: 
                    {
                        // take the minimum of expanding by character and paragraph formatting. 
                        ITextRange charRange = _range.GetDuplicate();
                        charRange.Expand(TomUnit.tomCharFormat);

                        ITextRange paraRange = _range.GetDuplicate(); 
                        paraRange.Expand(TomUnit.tomParaFormat);
 
                        _range.SetRange(Math.Max(charRange.Start, paraRange.Start), Math.Min(charRange.End, paraRange.End)); 
                    }
                    break; 

                default:
                    _range.Expand(TomUnitFromTextUnit(unit, "unit"));
                    break; 
            }
        } 
 
        ITextRangeProvider ITextRangeProvider.FindAttribute(int attributeId, object val, bool backwards)
        { 
            AutomationTextAttribute attribute = AutomationTextAttribute.LookupById(attributeId);
            // for paragraph-level attributes (ITextPara) search by units of uniform paragraph formatting.
            // for character-level attributes (ITextFont) search by units of uniform character formatting.
            if (attribute == TextPattern.BulletStyleAttribute 
                || attribute == TextPattern.HorizontalTextAlignmentAttribute
                || attribute == TextPattern.IndentationFirstLineAttribute 
                || attribute == TextPattern.IndentationLeadingAttribute 
                || attribute == TextPattern.IndentationTrailingAttribute
                || attribute == TextPattern.TabsAttribute) 
            {
                if (backwards)
                {
                    return FindAttributeBackwards(attribute, val, TomUnit.tomParaFormat); 
                }
                else 
                { 
                    return FindAttributeForwards(attribute, val, TomUnit.tomParaFormat);
                } 
            }
            else
            {
                if (backwards) 
                {
                    return FindAttributeBackwards(attribute, val, TomUnit.tomCharFormat); 
                } 
                else
                { 
                    return FindAttributeForwards(attribute, val, TomUnit.tomCharFormat);
                }
            }
        } 

        ITextRangeProvider ITextRangeProvider.FindText(string text, bool backwards, bool ignoreCase) 
        { 
            // PerSharp/PreFast will flag this as warning 6507/56507: Prefer 'string.IsNullOrEmpty(text)' over checks for null and/or emptiness.
            // A null string is not should throw an ArgumentNullException while an empty string should throw an ArgumentException. 
            // Therefore we can not use IsNullOrEmpty() here, suppress the warning.
#pragma warning suppress 6507
            if (text == null)
            { 
                throw new ArgumentNullException("text");
            } 
#pragma warning suppress 6507 
            if (text.Length == 0)
            { 
                throw new ArgumentException(SR.Get(SRID.InvalidParameter));
            }

            // copy the our range and search from the start point or end point depending on 
            // whether we are searching backwards.
            ITextRange range = _range.GetDuplicate(); 
            TomMatch flags = ignoreCase ? 0 : TomMatch.tomMatchCase; 
            int max = range.End - range.Start;
            int length; 
            if (!backwards)
            {
                length = range.FindTextStart(text, max, flags);
                range.End = range.Start + length; 
            }
            else 
            { 
                length = range.FindTextEnd(text, -max, flags);
                range.Start = range.End - length; 
            }

            // return a new range if we found something or null otherwise.
            return length > 0 ? new WindowsRichEditRange(range, _pattern) : null; 
        }
 
        object ITextRangeProvider.GetAttributeValue(int attributeId) 
        {
            AutomationTextAttribute attribute = AutomationTextAttribute.LookupById(attributeId); 

            // note regarding UnderlineColorAttribute: richedit does not support colored underlines.  all underlines
            // are the same color as the text so richedit does not support UnderlineColorAttribute.
 
            return GetAttributeValueForRange(_range, attribute);
        } 
 
        double[] ITextRangeProvider.GetBoundingRectangles()
        { 
            // if the range is entirely off-screen then return an empty array of rectangles
            ITextRange visibleRange = _pattern.GetVisibleRange();
            int start = Math.Max(_range.Start, visibleRange.Start);
            int end = Math.Min(_range.End, visibleRange.End); 
            if (start > end)
            { 
                return new double[0]; 
            }
 
            // get the client area in screen coordinates.
            // we'll use it to "clip" ranges that are partially scrolled out of view.
            NativeMethods.Win32Rect w32rect = new NativeMethods.Win32Rect();
            Misc.GetClientRectInScreenCoordinates(_pattern.WindowHandle, ref w32rect); 
            Rect clientRect = new Rect(w32rect.left, w32rect.top, w32rect.right - w32rect.left, w32rect.bottom - w32rect.top);
 
            // for each line except the last add a bounding rectangle 
            // that spans from the start of the line (or start of the original
            // range in the case of the first line) to the end of the line. 
            ArrayList rects = new ArrayList();
            Rect rect;
            ITextRange range = _pattern.Document.Range(start, start);
            range.EndOf(TomUnit.tomLine, TomExtend.tomExtend); 
            while (range.End < end)
            { 
                rect = CalculateOneLineRangeRectangle(range, clientRect); 
                if (rect.Width > 0 && rect.Height > 0)
                { 
                    rects.Add(rect);
                }

                // move to the start of the next line and extend it to the end. 
                range.Move(TomUnit.tomLine, 1);
                range.EndOf(TomUnit.tomLine, TomExtend.tomExtend); 
            } 

            // add the bounding rectangle for last (and possibly only) line. 
            range.End = end;
            rect = CalculateOneLineRangeRectangle(range, clientRect);
            if (rect.Width > 0 && rect.Height > 0)
            { 
                rects.Add(rect);
            } 
 
            // convert our list of rectangles into an array and return it.
            Rect[] rectArray = new Rect[rects.Count]; 
            rects.CopyTo(rectArray);

            return Misc.RectArrayToDoubleArray(rectArray);
        } 

        IRawElementProviderSimple ITextRangeProvider.GetEnclosingElement() 
        { 
            // note: if we have hyperlink children we'll need something more sophisticated.
            return _pattern; 
        }

        string ITextRangeProvider.GetText(int maxLength)
        { 
            // if no maximum length is given then return the text of the entire range.
            string text; 
            if (maxLength < 0) 
            {
                text = _range.Text; 
            }
            else
            {
                // if the maximum length is greater than the length of the range then 
                // return the text of the entire range
                int start = _range.Start; 
                int end = _range.End; 
                if (end - start <= maxLength)
                { 
                    text = _range.Text;
                }
                else
                { 
                    // the range is greater than the maximum length so get the text from a
                    // cloned, truncated range. 
                    ITextRange range = _range.GetDuplicate(); 
                    range.End = range.Start + maxLength;
                    text = range.Text; 
                    // PerSharp/PreFast will flag this as a warning 6507/56507: Prefer 'string.IsNullOrEmpty(text)' over checks for null and/or emptiness.
                    // An empty strings is desirable, not a null string.  Cannot use IsNullOrEmpty().
                    // Suppress the warning.
#pragma warning suppress 6507 
                    Debug.Assert(text == null || text.Length == maxLength);
                } 
            } 

            // RichEdit returns null for an empty range rather than an empty string. 
            return string.IsNullOrEmpty(text) ? "" : text;
        }

        int ITextRangeProvider.Move(TextUnit unit, int count) 
        {
            Misc.SetFocus(_pattern._hwnd); 
 
            int moved;
            switch (unit) 
            {
                case TextUnit.Format:
                    moved = MoveFormatUnit(count);
                    break; 

                default: 
                    moved = _range.Move(TomUnitFromTextUnit(unit, "unit"), count); 
                    break;
            } 
            return moved;
        }

        int ITextRangeProvider.MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count) 
        {
            Misc.SetFocus(_pattern._hwnd); 
 
            int moved;
            switch (unit) 
            {
                case TextUnit.Format:
                    if (endpoint == TextPatternRangeEndpoint.Start)
                    { 
                        moved = MoveStartFormatUnit(count);
                    } 
                    else 
                    {
                        moved = MoveEndFormatUnit(count); 
                    }
                    break;

                default: 
                    if (endpoint == TextPatternRangeEndpoint.Start)
                    { 
                        moved = _range.MoveStart(TomUnitFromTextUnit(unit, "unit"), count); 
                    }
                    else 
                    {
                        moved = _range.MoveEnd(TomUnitFromTextUnit(unit, "unit"), count);
                    }
                    break; 
            }
            return moved; 
        } 

        void ITextRangeProvider.MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) 
        {
            Misc.SetFocus(_pattern._hwnd);

            // our our character position to the target character position 
            ITextRange range = ((WindowsRichEditRange)targetRange)._range;
            int cp = (targetEndpoint == TextPatternRangeEndpoint.Start) ? range.Start : range.End; 
            if (endpoint == TextPatternRangeEndpoint.Start) 
            {
                // TOM has an idiosyncracy that you can't set Start to after the final '\r'. 
                // If you attempt to do so then it will change End instead. Yuk!
                // So if they attempt to set the start of the range to the very end of the
                // document we'll place it immediately before the end of the document instead.
                int storyLength = _range.StoryLength; 
                _range.Start = cp= end) 
            {
                name = range.Font.Name; 
            } 
            else
            { 
                // iterate over blocks of uniformly-formatted text
                for (ITextRange unitRange = FirstUnit(range); NextUnit(end, unitRange, TomUnit.tomCharFormat); )
                {
                    // on the first iteration remember the font name. 
                    if (string.IsNullOrEmpty(name))
                    { 
                        name = unitRange.Font.Name; 
                    }
                    else 
                    {
                        // on subsequent iterations compare if the font name is the same.
                        if (string.Compare(name, unitRange.Font.Name, StringComparison.Ordinal) != 0)
                        { 
                            return TextPattern.MixedAttributeValue;
                        } 
                    } 
                }
            } 

            return name;
        }
 
        private static object GetFontSize(ITextFont font)
        { 
            float size = font.Size; 
            if ((TomConst)size == TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                return (double)size;
            } 
        } 

        private static object GetFontWeight(ITextFont font) 
        {
            int weight = font.Weight;
            if (weight == (int)TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            } 
            else 
            {
                return weight; 
            }
        }

        private static object GetForegroundColor(ITextFont font) 
        {
            int color = font.ForeColor; 
            switch (color) 
            {
                case (int)TomConst.tomAutocolor: 
                    // tomAutocolor means richedit is using the default system foreground color.
                    // review: if RichEdit sends a WM_CTLCOLOR message to it's parent then the
                    // text color can depend on whatever foreground color the parent supplies.
                    return SafeNativeMethods.GetSysColor(NativeMethods.COLOR_WINDOWTEXT); 

                case (int)TomConst.tomUndefined: 
                    return TextPattern.MixedAttributeValue; 

                default: 
                    // if the high-byte is zero then we have a COLORREF
                    if ((color & 0xff000000) == 0)
                    {
                        return color; 
                    }
                    else 
                    { 
                        // we have a PALETTEINDEX color
                        return AutomationElement.NotSupported; 
                    }
            }
        }
 
        private static object GetHorizontalTextAlignment(ITextPara para)
        { 
            // review: ITextPara::GetListAlignment? 
            TomAlignment alignment = para.Alignment;
            if (alignment == TomAlignment.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                // HorizontalTextAlignment enum matcheds TomAlignment enum 
                return (HorizontalTextAlignment)(int)para.Alignment; 
            }
        } 

        private static object GetIndentationFirstLine(ITextPara para)
        {
            float indent = para.FirstLineIndent; 
            if ((TomConst)indent == TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue; 
            }
            else 
            {
                return (double)indent;
            }
        } 

        private static object GetIndentationLeading(ITextPara para) 
        { 
            float indent = para.LeftIndent;
            if ((TomConst)indent == TomConst.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                return (double)indent; 
            } 
        }
 
        private static object GetIndentationTrailing(ITextPara para)
        {
            float indent = para.RightIndent;
            if ((TomConst)indent == TomConst.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            } 
            else
            { 
                return (double)indent;
            }
        }
 
        private static object GetHidden(ITextFont font)
        { 
            TomBool hidden = font.Hidden; 
            if (hidden == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                return hidden == TomBool.tomTrue;
            } 
        } 

        private static object GetItalic(ITextFont font) 
        {
            TomBool italic = font.Italic;
            if (italic == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            } 
            else 
            {
                return italic == TomBool.tomTrue; 
            }
        }

        private static object GetOutlineStyles(ITextFont font) 
        {
            TomBool outline = font.Outline; 
            TomBool shadow = font.Shadow; 
            TomBool emboss = font.Emboss;
            TomBool engrave = font.Engrave; 

            if (outline == TomBool.tomUndefined || shadow == TomBool.tomUndefined || emboss == TomBool.tomUndefined || engrave == TomBool.tomUndefined)
            {
                return TextPattern.MixedAttributeValue; 
            }
            else 
            { 
                OutlineStyles style = 0;
                style |= (outline == TomBool.tomTrue) ? OutlineStyles.Outline : 0; 
                style |= (shadow == TomBool.tomTrue) ? OutlineStyles.Shadow : 0;
                style |= (emboss == TomBool.tomTrue) ? OutlineStyles.Embossed : 0;
                style |= (engrave == TomBool.tomTrue) ? OutlineStyles.Engraved : 0;
                return style; 
            }
        } 
 
        private object GetReadOnly(ITextFont font)
        { 
            // if the entire pattern is read-only then every range within it is also
            // read only.
            if (_pattern.ReadOnly)
            { 
                return true;
            } 
 
            // check if the "Protected" font style is turned on.
            TomBool protect = font.Protected; 
            if (protect == TomBool.tomUndefined)
            {
                return TextPattern.MixedAttributeValue;
            } 
            else
            { 
                return protect == TomBool.tomTrue; 
            }
        } 

        private static object GetStrikethroughStyle(ITextFont font)
        {
            TomBool strike = font.StrikeThrough; 
            if (strike == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue; 
            }
            else 
            {
                return strike == TomBool.tomTrue ? TextDecorationLineStyle.Single : TextDecorationLineStyle.None;
            }
        } 

        private static object GetSubscript(ITextFont font) 
        { 
            TomBool sub = font.Subscript;
            if (sub == TomBool.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                return sub == TomBool.tomTrue; 
            } 
        }
 
        private static object GetSuperscript(ITextFont font)
        {
            TomBool super = font.Superscript;
            if (super == TomBool.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            } 
            else
            { 
                return super == TomBool.tomTrue;
            }
        }
 
        private static object GetTabs(ITextPara para)
        { 
            int count = para.TabCount; 
            if (count == (int)TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                double[] tabs = new double[count];
                for (int i = 0; i < count; i++) 
                { 
                    float tbPos;
                    TomAlignment tbAlign; 
                    TomLeader tbLeader;
                    para.GetTab(i, out tbPos, out tbAlign, out tbLeader);
                    tabs[i] = tbPos;
                } 
                return tabs;
            } 
        } 

        private static object GetUnderlineStyle(ITextFont font) 
        {
            // note: if a range spans different underline styles then it won't return tomUndefined.  instead it appears
            // to return the underline style at the endpoint.  if a range spans underlined and non-underlined text then
            // it returns tomUndefined properly. 

            TomUnderline underline = font.Underline; 
            if (underline == TomUnderline.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            }
            else
            {
                switch (underline) 
                {
                    case TomUnderline.tomTrue: 
                        return TextDecorationLineStyle.Single; 

                    default: 
                        // TextDecorationLineStyle enum matches TomUnderline enum
                        return (TextDecorationLineStyle)(int)underline;
                }
            } 
        }
 
        private int MoveFormatUnit(int count) 
        {
            int moved = 0; 

            if (count > 0)
            {
                // if it is a non-degenerate range then collapse it. 
                if (_range.Start < _range.End)
                { 
                    _range.Collapse(TomStartEnd.tomEnd); 
                }
 
                // move the start point forward the number of units.  the end point will get pushed along.
                moved += MoveStartFormatUnit(count);
            }
            else if (count < 0) 
            {
                // if it is a non-degenerate range then collapse it. 
                if (_range.Start < _range.End) 
                {
                    _range.Collapse(TomStartEnd.tomStart); 
                }

                // move the end point backward the number of units.  the start point will get pushed along.
                moved += MoveEndFormatUnit(count); 
            }
 
            return moved; 
        }
 
        private int MoveStartFormatUnit(int count)
        {
            // This is identical to MoveEndFormatUnit except we are calling MoveStartOneXXX instead of the MoveEndOneXXX versions.
            int moved = 0; 

            if (count > 0) 
            { 
                // loop until we have moved the requested number of units
                // or we can't move anymore and break out of the loop. 
                while (moved < count)
                {
                    if (MoveStartOneFormatUnitForward())
                    { 
                        moved++;
                    } 
                    else 
                    {
                        break; 
                    }
                }
            }
            else if (count < 0) 
            {
                // loop until we have moved the requested number of units 
                // or we can't move anymore and break out of the loop. 
                while (moved > count)
                { 
                    if (MoveStartOneFormatUnitBackward())
                    {
                        moved--;
                    } 
                    else
                    { 
                        break; 
                    }
                } 
            }

            return moved;
        } 

        private int MoveEndFormatUnit(int count) 
        { 
            // This is identical to MoveStartFormatUnit except we are calling MoveEndOneXXX instead of the MoveStartOneXXX versions.
            int moved = 0; 

            if (count > 0)
            {
                // loop until we have moved the requested number of units 
                // or we can't move anymore and break out of the loop.
                while (moved < count) 
                { 
                    if (MoveEndOneFormatUnitForward())
                    { 
                        moved++;
                    }
                    else
                    { 
                        break;
                    } 
                } 
            }
            else if (count < 0) 
            {
                // loop until we have moved the requested number of units
                // or we can't move anymore and break out of the loop.
                while (moved > count) 
                {
                    if (MoveEndOneFormatUnitBackward()) 
                    { 
                        moved--;
                    } 
                    else
                    {
                        break;
                    } 
                }
            } 
 
            return moved;
        } 

        private bool MoveStartOneFormatUnitForward()
        {
            // try moving the endpoint one char-format unit and one para-format unit 
            // and remember where each one ended up.
            ITextRange charRange = _range.GetDuplicate(); 
            int charMoved = charRange.MoveStart(TomUnit.tomCharFormat, 1); 
            Debug.Assert(charMoved == 0 || charMoved == 1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveStart(TomUnit.tomParaFormat, 1);
            Debug.Assert(paraMoved == 0 || paraMoved == 1);

            // ensure the endpoint is set to whichever moved the *least* 
            // and return true iff we moved successfully.
            if (charMoved == 1 && (paraMoved == 0 || charRange.Start <= paraRange.Start)) 
            { 
                _range = charRange;
                return true; 
            }
            else if (paraMoved == 1)
            {
                _range = paraRange; 
                return true;
            } 
            else 
            {
                return false; 
            }
        }

        private bool MoveStartOneFormatUnitBackward() 
        {
            // try moving the endpoint one char-format unit and one para-format unit 
            // and remember where each one ended up. 
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveStart(TomUnit.tomCharFormat, -1); 
            Debug.Assert(charMoved == 0 || charMoved == -1);
            ITextRange paraRange = _range.GetDuplicate();
            int paraMoved = paraRange.MoveStart(TomUnit.tomParaFormat, -1);
            Debug.Assert(paraMoved == 0 || paraMoved == -1); 

            // ensure the endpoint is set to whichever moved the *least* 
            // and return true iff we moved successfully. 
            if (charMoved == -1 && (paraMoved == 0 || charRange.Start >= paraRange.Start))
            { 
                _range = charRange;
                return true;
            }
            else if (paraMoved == -1) 
            {
                _range = paraRange; 
                return true; 
            }
            else 
            {
                return false;
            }
        } 

        private bool MoveEndOneFormatUnitForward() 
        { 
            // try moving the endpoint one char-format unit and one para-format unit
            // and remember where each one ended up. 
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveEnd(TomUnit.tomCharFormat, 1);
            Debug.Assert(charMoved == 0 || charMoved == 1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveEnd(TomUnit.tomParaFormat, 1);
            Debug.Assert(paraMoved == 0 || paraMoved == 1); 
 
            // ensure the endpoint is set to whichever moved the *least*
            // and return true iff we moved successfully. 
            if (charMoved == 1 && (paraMoved == 0 || charRange.End <= paraRange.End))
            {
                _range = charRange;
                return true; 
            }
            else if (paraMoved == 1) 
            { 
                _range = paraRange;
                return true; 
            }
            else
            {
                return false; 
            }
        } 
 
        private bool MoveEndOneFormatUnitBackward()
        { 
            // try moving the endpoint one char-format unit and one para-format unit
            // and remember where each one ended up.
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveEnd(TomUnit.tomCharFormat, -1); 
            Debug.Assert(charMoved == 0 || charMoved == -1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveEnd(TomUnit.tomParaFormat, -1); 
            Debug.Assert(paraMoved == 0 || paraMoved == -1);
 
            // ensure the endpoint is set to whichever moved the *least*
            // and return true iff we moved successfully.
            if (charMoved == -1 && (paraMoved == 0 || charRange.End >= paraRange.End))
            { 
                _range = charRange;
                return true; 
            } 
            else if (paraMoved == -1)
            { 
                _range = paraRange;
                return true;
            }
            else 
            {
                return false; 
            } 
        }
 
        // this wrapper around ITextRange.GetPoint returns true if GetPoint returned S_OK, false if GetPoint returned S_FALSE,
        // or throws an exception for an error hresult.
        internal static bool RangeGetPoint(ITextRange range, TomGetPoint type, out int x, out int y)
        { 
            int hr = range.GetPoint(type, out x, out y);
            if (hr < 0) 
            { 
                Marshal.ThrowExceptionForHR(hr);
            } 
            return hr == 0;
        }

        // converts one of our TextUnits to the corresponding TomUnit. 
        // if there isn't a corresponding one it throws an ArgumentException for the specified name.
        private static TomUnit TomUnitFromTextUnit(TextUnit unit, string name) 
        { 
            switch (unit)
            { 
                case TextUnit.Character:
                    return TomUnit.tomCharacter;

                case TextUnit.Word: 
                    return TomUnit.tomWord;
 
                case TextUnit.Line: 
                    return TomUnit.tomLine;
 
                case TextUnit.Paragraph:
                    return TomUnit.tomParagraph;

                case TextUnit.Page: 
                case TextUnit.Document:
                    return TomUnit.tomStory; 
 
                default:
                    throw new ArgumentException(name); 
            }
        }

        #endregion Private Methods 

        //------------------------------------------------------ 
        // 
        //  Static Methods
        // 
        //-----------------------------------------------------

        #region Static Methods
 
        // these helper functions make it convenient to walk through a range by unit as follows:
        //   int end = range.End; 
        //   for (ITextRange subrange=FirstUnit(range); NextUnit(end, subrange, tomUnit.tomCharFormat);)... 
        // and
        //   int start = range.Start; 
        //   for (ITextRange subrange=LastUnit(range); PreviousUnit(start, subrange, tomUnit.tomParaFormat);)...

        private static ITextRange FirstUnit(ITextRange range)
        { 
            // get a degenerate subrange positioned at the beginning of the range
            ITextRange subrange = range.GetDuplicate(); 
            subrange.Collapse(TomStartEnd.tomStart); 
            return subrange;
        } 

        private static ITextRange LastUnit(ITextRange range)
        {
            // get a degenerate subrange positioned at the end of the range 
            ITextRange subrange = range.GetDuplicate();
            subrange.Collapse(TomStartEnd.tomEnd); 
            return subrange; 
        }
 
        private static bool NextUnit(int end, ITextRange subrange, TomUnit unit)
        {
            if (subrange.End >= end)
            { 
                return false;
            } 
            else 
            {
                // collapse the range to the end and then extend it another unit. 
                subrange.Collapse(TomStartEnd.tomEnd);
                subrange.MoveEnd(unit, 1);

                // truncate if necessary to ensure it fits inside the range 
                if (subrange.End > end)
                { 
                    subrange.End = end; 
                }
 
                return true;
            }
        }
 
        private static bool PreviousUnit(int start, ITextRange subrange, TomUnit unit)
        { 
            if (subrange.Start <= start) 
            {
                return false; 
            }
            else
            {
                // collapse the range to the end and then extend it another unit. 
                subrange.Collapse(TomStartEnd.tomStart);
                subrange.MoveStart(unit, -1); 
 
                // truncate if necessary to ensure it fits inside the range
                if (subrange.Start < start) 
                {
                    subrange.Start = start;
                }
 
                return true;
            } 
        } 

        // this function gets the bounding rectangle for a range that does not wrap lines. 
        private static Rect CalculateOneLineRangeRectangle(ITextRange lineRange, Rect clientRect)
        {
            int start = lineRange.Start;
            int end = lineRange.End; 
            if (start < end)
            { 
                // make a working copy of the range. shrink it by one character since 
                // ITextRange.GetPoint returns the  coordinates of the character *after* the end of the
                // range rather than the one immediately *before* the end of the range. 
                ITextRange range = lineRange.GetDuplicate();
                range.MoveEnd(TomUnit.tomCharacter, -1);
                end--;
 
                // The ITextRange::SetRange method sets this range’s Start = min(cp1, cp2) and End = max(cp1, cp2).
                // If the range is a nondegenerate selection, cp2 is the active end; if it’s a degenerate selection, 
                // the ambiguous cp is displayed at the start of the line (rather than at the end of the previous line). 
                // Set the end to the start and the start to the end to create an ambiguous cp.  This was added to
                // resolve Windows OS Bug 1030882. 
                range.SetRange(range.End, range.Start);

                Rect rect = new Rect(clientRect.Location, clientRect.Size);
                bool trimmed = TrimRectangleByRangeCorners(range, ref rect); 
                if (!trimmed)
                { 
                    while (!trimmed && start < end) 
                    {
                        // shrink the range 
                        range.MoveStart(TomUnit.tomCharacter, 1);
                        // If the character just moved over was a '\r' the start will be out of sink with range.Start
                        start++;
                        if (start < end) 
                        {
                            range.MoveEnd(TomUnit.tomCharacter, -1); 
                            // If the character just moved over was a '\r' the end will be out of sink with range.End 
                            end--;
                        } 
                        //Debug.Assert(start == range.Start);
                        //Debug.Assert(end == range.End);

                        trimmed = TrimRectangleByRangeCorners(range, ref rect); 
                    }
 
                    if (trimmed) 
                    {
                        rect.X = clientRect.X; 
                        rect.Width = clientRect.Width;
                    }
                }
 
                if (!trimmed)
                { 
                    rect = Rect.Empty; 
                }
                return rect; 
            }
            else
            {
                // degenerate range.  return empty rectangle. 

                // it might be nice to return a zero-width rectangle with the appropriate height and location. 
                // however, when the degenerate range is at a line wrapping point then it is ambiguous as to 
                // whether the rectangle should be at the end of one line or the beginning of the next.  clients
                // can always extend a degenerate range by one character in either direction before getting a bounding 
                // rectangle if they want.

                return Rect.Empty;
            } 
        }
 
        // pass in a rectangle of the entire client area and this function will trim it based on visible corners 
        // of the range plus the character following the range. we get the extra character because
        // ITextRange.GetPoint(tomEnd,...) refers to the character following the endpoint rather than immediately 
        // preceding the endpoint.  callers have to take this into account and adjust their ranges accordingly.
        // it will trim the rectangle as much as possible based on the visible corners and leave the rest
        // of the rectangle unchanged.  it returns true if it found at least one visible corner.
        // note: there are some obscure cases that this won't handle.  if all four corners of a character cell 
        // are outside the client area but some part of the middle of the character is visible then this routine will
        // think that the character is entirely invisible.  this limitation is due to the fact that you can only 
        // get coordinates from ITextRange for a specific point on an endpoint character and if that specific 
        // point is offscreen then ITextRange::GetPoint returns S_FALSE.
        private static bool TrimRectangleByRangeCorners(ITextRange range, ref Rect rect) 
        {
            // it's easier to work with the rectangle components separately..
            double left = rect.Left;
            double top = rect.Top; 
            double right = rect.Right;
            double bottom = rect.Bottom; 
 
            // if the top-left corner is visible then trim off anything above that corner.
            // if we are on our first iteration then also trim anything to the left of it. 
            int x, y;
            bool GotTopLeft, GotTopRight, GotBottomLeft, GotBottomRight;
            GotTopRight = false; GotBottomLeft = false;
            if (GotTopLeft = RangeGetPoint(range, TomGetPoint.tomStart/*|TOP|LEFT*/, out x, out y)) 
            {
                left = x; 
                top = y; 
            }
 
            // if the bottom-right corner is visible then trim off anything below that corner.
            // if we are on our first iteration then also trim anything to the right of it.
            if (GotBottomRight = RangeGetPoint(range, /*tomEnd|*/TomGetPoint.TA_BOTTOM | TomGetPoint.TA_RIGHT, out x, out y))
            { 
                right = x;
                bottom = y; 
            } 

            // (if diagonal corners are visible then we are done since we have trimmed all four sides.) 

            // if only one or neither diagonal corner is visible...
            if (!GotTopLeft || !GotBottomRight)
            { 
                // if the top-right corner is visible then trim off anything above that corner.
                // if we are on our first iteration then also trim anything to the right of it. 
                if (GotTopRight = RangeGetPoint(range, /*tomEnd|TOP|*/TomGetPoint.TA_RIGHT, out x, out y)) 
                {
                    right = x; 
                    top = y;
                }
                else
                { 
                    // if the bottom-left corner is visible then trim off anything below that corner.
                    // if we are on our first iteration then also trim anything to the left of it. 
                    if (GotBottomLeft = RangeGetPoint(range, TomGetPoint.tomStart | TomGetPoint.TA_BOTTOM /*|LEFT*/, out x, out y)) 
                    {
                        left = x; 
                        bottom = y;
                    }
                }
            } 

            // update the rectangle 
            rect.X = left; 
            rect.Y = top;
            rect.Width = right - left; 
            rect.Height = bottom - top;

            return GotTopLeft || GotTopRight || GotBottomLeft || GotBottomRight;
        } 

        #endregion Static Methods 
 
        //------------------------------------------------------
        // 
        //  Private Fields
        //
        //-----------------------------------------------------
 
        #region Private Fields
 
        private ITextRange _range;  // alert: this can point to different ITextRange objects over the lifetime of this WindowsRichEditRange. 
        private WindowsRichEdit _pattern;
 
        #endregion private Fields
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
/****************************************************************************\ 
*
* File: WindowsRichEditRange.cs
*
* Description: 
* Common code to support the ITextPointerProvider interface
* 
* Revision History: 
* 2/12/04:  a-davidj created.
* 
* Copyright (C) 2004 by Microsoft Corporation.  All rights reserved.
*
\***************************************************************************/
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691 
 
using System;
using System.Collections; 
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Automation; 
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text; 
using System.ComponentModel; 
using MS.Win32;
 
namespace MS.Internal.AutomationProxies
{
    internal class WindowsRichEditRange : ITextRangeProvider
    { 
        internal WindowsRichEditRange(ITextRange range, WindowsRichEdit pattern)
        { 
            Debug.Assert(range != null); 
            Debug.Assert(pattern != null);
 
            _range = range;
            _pattern = pattern;
        }
 
        //-----------------------------------------------------
        // 
        //  Public Methods 
        //
        //----------------------------------------------------- 

        #region Public Methods

        ITextRangeProvider ITextRangeProvider.Clone() 
        {
            // Use ITextRange::GetDuplicate to duplicate the ITextRange. 
            ITextRange range = _range.GetDuplicate(); 
            return range!=null ? new WindowsRichEditRange(range, _pattern) : null;
        } 

        bool ITextRangeProvider.Compare(ITextRangeProvider range)
        {
            // Use ITextRange::IsEqual to compare ITextRanges. 
            WindowsRichEditRange otherRange = (WindowsRichEditRange)range;
            return _range.IsEqual(otherRange._range)==TomBool.tomTrue; 
        } 

        int ITextRangeProvider.CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) 
        {
            // Get the endpoint character positions using ITextRange::GetStart && ITextRange::GetEnd.
            // Subtract the character positions to get the return value.
            WindowsRichEditRange otherRange = (WindowsRichEditRange)targetRange; 
            int e1 = (endpoint == TextPatternRangeEndpoint.Start) ? _range.Start : _range.End;
            int e2 = (targetEndpoint == TextPatternRangeEndpoint.Start) ? otherRange._range.Start : otherRange._range.End; 
            return e1 - e2; 
        }
 
        void ITextRangeProvider.ExpandToEnclosingUnit(TextUnit unit)
        {
            Misc.SetFocus(_pattern._hwnd);
 
            switch (unit)
            { 
                case TextUnit.Format: 
                    {
                        // take the minimum of expanding by character and paragraph formatting. 
                        ITextRange charRange = _range.GetDuplicate();
                        charRange.Expand(TomUnit.tomCharFormat);

                        ITextRange paraRange = _range.GetDuplicate(); 
                        paraRange.Expand(TomUnit.tomParaFormat);
 
                        _range.SetRange(Math.Max(charRange.Start, paraRange.Start), Math.Min(charRange.End, paraRange.End)); 
                    }
                    break; 

                default:
                    _range.Expand(TomUnitFromTextUnit(unit, "unit"));
                    break; 
            }
        } 
 
        ITextRangeProvider ITextRangeProvider.FindAttribute(int attributeId, object val, bool backwards)
        { 
            AutomationTextAttribute attribute = AutomationTextAttribute.LookupById(attributeId);
            // for paragraph-level attributes (ITextPara) search by units of uniform paragraph formatting.
            // for character-level attributes (ITextFont) search by units of uniform character formatting.
            if (attribute == TextPattern.BulletStyleAttribute 
                || attribute == TextPattern.HorizontalTextAlignmentAttribute
                || attribute == TextPattern.IndentationFirstLineAttribute 
                || attribute == TextPattern.IndentationLeadingAttribute 
                || attribute == TextPattern.IndentationTrailingAttribute
                || attribute == TextPattern.TabsAttribute) 
            {
                if (backwards)
                {
                    return FindAttributeBackwards(attribute, val, TomUnit.tomParaFormat); 
                }
                else 
                { 
                    return FindAttributeForwards(attribute, val, TomUnit.tomParaFormat);
                } 
            }
            else
            {
                if (backwards) 
                {
                    return FindAttributeBackwards(attribute, val, TomUnit.tomCharFormat); 
                } 
                else
                { 
                    return FindAttributeForwards(attribute, val, TomUnit.tomCharFormat);
                }
            }
        } 

        ITextRangeProvider ITextRangeProvider.FindText(string text, bool backwards, bool ignoreCase) 
        { 
            // PerSharp/PreFast will flag this as warning 6507/56507: Prefer 'string.IsNullOrEmpty(text)' over checks for null and/or emptiness.
            // A null string is not should throw an ArgumentNullException while an empty string should throw an ArgumentException. 
            // Therefore we can not use IsNullOrEmpty() here, suppress the warning.
#pragma warning suppress 6507
            if (text == null)
            { 
                throw new ArgumentNullException("text");
            } 
#pragma warning suppress 6507 
            if (text.Length == 0)
            { 
                throw new ArgumentException(SR.Get(SRID.InvalidParameter));
            }

            // copy the our range and search from the start point or end point depending on 
            // whether we are searching backwards.
            ITextRange range = _range.GetDuplicate(); 
            TomMatch flags = ignoreCase ? 0 : TomMatch.tomMatchCase; 
            int max = range.End - range.Start;
            int length; 
            if (!backwards)
            {
                length = range.FindTextStart(text, max, flags);
                range.End = range.Start + length; 
            }
            else 
            { 
                length = range.FindTextEnd(text, -max, flags);
                range.Start = range.End - length; 
            }

            // return a new range if we found something or null otherwise.
            return length > 0 ? new WindowsRichEditRange(range, _pattern) : null; 
        }
 
        object ITextRangeProvider.GetAttributeValue(int attributeId) 
        {
            AutomationTextAttribute attribute = AutomationTextAttribute.LookupById(attributeId); 

            // note regarding UnderlineColorAttribute: richedit does not support colored underlines.  all underlines
            // are the same color as the text so richedit does not support UnderlineColorAttribute.
 
            return GetAttributeValueForRange(_range, attribute);
        } 
 
        double[] ITextRangeProvider.GetBoundingRectangles()
        { 
            // if the range is entirely off-screen then return an empty array of rectangles
            ITextRange visibleRange = _pattern.GetVisibleRange();
            int start = Math.Max(_range.Start, visibleRange.Start);
            int end = Math.Min(_range.End, visibleRange.End); 
            if (start > end)
            { 
                return new double[0]; 
            }
 
            // get the client area in screen coordinates.
            // we'll use it to "clip" ranges that are partially scrolled out of view.
            NativeMethods.Win32Rect w32rect = new NativeMethods.Win32Rect();
            Misc.GetClientRectInScreenCoordinates(_pattern.WindowHandle, ref w32rect); 
            Rect clientRect = new Rect(w32rect.left, w32rect.top, w32rect.right - w32rect.left, w32rect.bottom - w32rect.top);
 
            // for each line except the last add a bounding rectangle 
            // that spans from the start of the line (or start of the original
            // range in the case of the first line) to the end of the line. 
            ArrayList rects = new ArrayList();
            Rect rect;
            ITextRange range = _pattern.Document.Range(start, start);
            range.EndOf(TomUnit.tomLine, TomExtend.tomExtend); 
            while (range.End < end)
            { 
                rect = CalculateOneLineRangeRectangle(range, clientRect); 
                if (rect.Width > 0 && rect.Height > 0)
                { 
                    rects.Add(rect);
                }

                // move to the start of the next line and extend it to the end. 
                range.Move(TomUnit.tomLine, 1);
                range.EndOf(TomUnit.tomLine, TomExtend.tomExtend); 
            } 

            // add the bounding rectangle for last (and possibly only) line. 
            range.End = end;
            rect = CalculateOneLineRangeRectangle(range, clientRect);
            if (rect.Width > 0 && rect.Height > 0)
            { 
                rects.Add(rect);
            } 
 
            // convert our list of rectangles into an array and return it.
            Rect[] rectArray = new Rect[rects.Count]; 
            rects.CopyTo(rectArray);

            return Misc.RectArrayToDoubleArray(rectArray);
        } 

        IRawElementProviderSimple ITextRangeProvider.GetEnclosingElement() 
        { 
            // note: if we have hyperlink children we'll need something more sophisticated.
            return _pattern; 
        }

        string ITextRangeProvider.GetText(int maxLength)
        { 
            // if no maximum length is given then return the text of the entire range.
            string text; 
            if (maxLength < 0) 
            {
                text = _range.Text; 
            }
            else
            {
                // if the maximum length is greater than the length of the range then 
                // return the text of the entire range
                int start = _range.Start; 
                int end = _range.End; 
                if (end - start <= maxLength)
                { 
                    text = _range.Text;
                }
                else
                { 
                    // the range is greater than the maximum length so get the text from a
                    // cloned, truncated range. 
                    ITextRange range = _range.GetDuplicate(); 
                    range.End = range.Start + maxLength;
                    text = range.Text; 
                    // PerSharp/PreFast will flag this as a warning 6507/56507: Prefer 'string.IsNullOrEmpty(text)' over checks for null and/or emptiness.
                    // An empty strings is desirable, not a null string.  Cannot use IsNullOrEmpty().
                    // Suppress the warning.
#pragma warning suppress 6507 
                    Debug.Assert(text == null || text.Length == maxLength);
                } 
            } 

            // RichEdit returns null for an empty range rather than an empty string. 
            return string.IsNullOrEmpty(text) ? "" : text;
        }

        int ITextRangeProvider.Move(TextUnit unit, int count) 
        {
            Misc.SetFocus(_pattern._hwnd); 
 
            int moved;
            switch (unit) 
            {
                case TextUnit.Format:
                    moved = MoveFormatUnit(count);
                    break; 

                default: 
                    moved = _range.Move(TomUnitFromTextUnit(unit, "unit"), count); 
                    break;
            } 
            return moved;
        }

        int ITextRangeProvider.MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count) 
        {
            Misc.SetFocus(_pattern._hwnd); 
 
            int moved;
            switch (unit) 
            {
                case TextUnit.Format:
                    if (endpoint == TextPatternRangeEndpoint.Start)
                    { 
                        moved = MoveStartFormatUnit(count);
                    } 
                    else 
                    {
                        moved = MoveEndFormatUnit(count); 
                    }
                    break;

                default: 
                    if (endpoint == TextPatternRangeEndpoint.Start)
                    { 
                        moved = _range.MoveStart(TomUnitFromTextUnit(unit, "unit"), count); 
                    }
                    else 
                    {
                        moved = _range.MoveEnd(TomUnitFromTextUnit(unit, "unit"), count);
                    }
                    break; 
            }
            return moved; 
        } 

        void ITextRangeProvider.MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) 
        {
            Misc.SetFocus(_pattern._hwnd);

            // our our character position to the target character position 
            ITextRange range = ((WindowsRichEditRange)targetRange)._range;
            int cp = (targetEndpoint == TextPatternRangeEndpoint.Start) ? range.Start : range.End; 
            if (endpoint == TextPatternRangeEndpoint.Start) 
            {
                // TOM has an idiosyncracy that you can't set Start to after the final '\r'. 
                // If you attempt to do so then it will change End instead. Yuk!
                // So if they attempt to set the start of the range to the very end of the
                // document we'll place it immediately before the end of the document instead.
                int storyLength = _range.StoryLength; 
                _range.Start = cp= end) 
            {
                name = range.Font.Name; 
            } 
            else
            { 
                // iterate over blocks of uniformly-formatted text
                for (ITextRange unitRange = FirstUnit(range); NextUnit(end, unitRange, TomUnit.tomCharFormat); )
                {
                    // on the first iteration remember the font name. 
                    if (string.IsNullOrEmpty(name))
                    { 
                        name = unitRange.Font.Name; 
                    }
                    else 
                    {
                        // on subsequent iterations compare if the font name is the same.
                        if (string.Compare(name, unitRange.Font.Name, StringComparison.Ordinal) != 0)
                        { 
                            return TextPattern.MixedAttributeValue;
                        } 
                    } 
                }
            } 

            return name;
        }
 
        private static object GetFontSize(ITextFont font)
        { 
            float size = font.Size; 
            if ((TomConst)size == TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                return (double)size;
            } 
        } 

        private static object GetFontWeight(ITextFont font) 
        {
            int weight = font.Weight;
            if (weight == (int)TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            } 
            else 
            {
                return weight; 
            }
        }

        private static object GetForegroundColor(ITextFont font) 
        {
            int color = font.ForeColor; 
            switch (color) 
            {
                case (int)TomConst.tomAutocolor: 
                    // tomAutocolor means richedit is using the default system foreground color.
                    // review: if RichEdit sends a WM_CTLCOLOR message to it's parent then the
                    // text color can depend on whatever foreground color the parent supplies.
                    return SafeNativeMethods.GetSysColor(NativeMethods.COLOR_WINDOWTEXT); 

                case (int)TomConst.tomUndefined: 
                    return TextPattern.MixedAttributeValue; 

                default: 
                    // if the high-byte is zero then we have a COLORREF
                    if ((color & 0xff000000) == 0)
                    {
                        return color; 
                    }
                    else 
                    { 
                        // we have a PALETTEINDEX color
                        return AutomationElement.NotSupported; 
                    }
            }
        }
 
        private static object GetHorizontalTextAlignment(ITextPara para)
        { 
            // review: ITextPara::GetListAlignment? 
            TomAlignment alignment = para.Alignment;
            if (alignment == TomAlignment.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                // HorizontalTextAlignment enum matcheds TomAlignment enum 
                return (HorizontalTextAlignment)(int)para.Alignment; 
            }
        } 

        private static object GetIndentationFirstLine(ITextPara para)
        {
            float indent = para.FirstLineIndent; 
            if ((TomConst)indent == TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue; 
            }
            else 
            {
                return (double)indent;
            }
        } 

        private static object GetIndentationLeading(ITextPara para) 
        { 
            float indent = para.LeftIndent;
            if ((TomConst)indent == TomConst.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                return (double)indent; 
            } 
        }
 
        private static object GetIndentationTrailing(ITextPara para)
        {
            float indent = para.RightIndent;
            if ((TomConst)indent == TomConst.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            } 
            else
            { 
                return (double)indent;
            }
        }
 
        private static object GetHidden(ITextFont font)
        { 
            TomBool hidden = font.Hidden; 
            if (hidden == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                return hidden == TomBool.tomTrue;
            } 
        } 

        private static object GetItalic(ITextFont font) 
        {
            TomBool italic = font.Italic;
            if (italic == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            } 
            else 
            {
                return italic == TomBool.tomTrue; 
            }
        }

        private static object GetOutlineStyles(ITextFont font) 
        {
            TomBool outline = font.Outline; 
            TomBool shadow = font.Shadow; 
            TomBool emboss = font.Emboss;
            TomBool engrave = font.Engrave; 

            if (outline == TomBool.tomUndefined || shadow == TomBool.tomUndefined || emboss == TomBool.tomUndefined || engrave == TomBool.tomUndefined)
            {
                return TextPattern.MixedAttributeValue; 
            }
            else 
            { 
                OutlineStyles style = 0;
                style |= (outline == TomBool.tomTrue) ? OutlineStyles.Outline : 0; 
                style |= (shadow == TomBool.tomTrue) ? OutlineStyles.Shadow : 0;
                style |= (emboss == TomBool.tomTrue) ? OutlineStyles.Embossed : 0;
                style |= (engrave == TomBool.tomTrue) ? OutlineStyles.Engraved : 0;
                return style; 
            }
        } 
 
        private object GetReadOnly(ITextFont font)
        { 
            // if the entire pattern is read-only then every range within it is also
            // read only.
            if (_pattern.ReadOnly)
            { 
                return true;
            } 
 
            // check if the "Protected" font style is turned on.
            TomBool protect = font.Protected; 
            if (protect == TomBool.tomUndefined)
            {
                return TextPattern.MixedAttributeValue;
            } 
            else
            { 
                return protect == TomBool.tomTrue; 
            }
        } 

        private static object GetStrikethroughStyle(ITextFont font)
        {
            TomBool strike = font.StrikeThrough; 
            if (strike == TomBool.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue; 
            }
            else 
            {
                return strike == TomBool.tomTrue ? TextDecorationLineStyle.Single : TextDecorationLineStyle.None;
            }
        } 

        private static object GetSubscript(ITextFont font) 
        { 
            TomBool sub = font.Subscript;
            if (sub == TomBool.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue;
            }
            else 
            {
                return sub == TomBool.tomTrue; 
            } 
        }
 
        private static object GetSuperscript(ITextFont font)
        {
            TomBool super = font.Superscript;
            if (super == TomBool.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            } 
            else
            { 
                return super == TomBool.tomTrue;
            }
        }
 
        private static object GetTabs(ITextPara para)
        { 
            int count = para.TabCount; 
            if (count == (int)TomConst.tomUndefined)
            { 
                return TextPattern.MixedAttributeValue;
            }
            else
            { 
                double[] tabs = new double[count];
                for (int i = 0; i < count; i++) 
                { 
                    float tbPos;
                    TomAlignment tbAlign; 
                    TomLeader tbLeader;
                    para.GetTab(i, out tbPos, out tbAlign, out tbLeader);
                    tabs[i] = tbPos;
                } 
                return tabs;
            } 
        } 

        private static object GetUnderlineStyle(ITextFont font) 
        {
            // note: if a range spans different underline styles then it won't return tomUndefined.  instead it appears
            // to return the underline style at the endpoint.  if a range spans underlined and non-underlined text then
            // it returns tomUndefined properly. 

            TomUnderline underline = font.Underline; 
            if (underline == TomUnderline.tomUndefined) 
            {
                return TextPattern.MixedAttributeValue; 
            }
            else
            {
                switch (underline) 
                {
                    case TomUnderline.tomTrue: 
                        return TextDecorationLineStyle.Single; 

                    default: 
                        // TextDecorationLineStyle enum matches TomUnderline enum
                        return (TextDecorationLineStyle)(int)underline;
                }
            } 
        }
 
        private int MoveFormatUnit(int count) 
        {
            int moved = 0; 

            if (count > 0)
            {
                // if it is a non-degenerate range then collapse it. 
                if (_range.Start < _range.End)
                { 
                    _range.Collapse(TomStartEnd.tomEnd); 
                }
 
                // move the start point forward the number of units.  the end point will get pushed along.
                moved += MoveStartFormatUnit(count);
            }
            else if (count < 0) 
            {
                // if it is a non-degenerate range then collapse it. 
                if (_range.Start < _range.End) 
                {
                    _range.Collapse(TomStartEnd.tomStart); 
                }

                // move the end point backward the number of units.  the start point will get pushed along.
                moved += MoveEndFormatUnit(count); 
            }
 
            return moved; 
        }
 
        private int MoveStartFormatUnit(int count)
        {
            // This is identical to MoveEndFormatUnit except we are calling MoveStartOneXXX instead of the MoveEndOneXXX versions.
            int moved = 0; 

            if (count > 0) 
            { 
                // loop until we have moved the requested number of units
                // or we can't move anymore and break out of the loop. 
                while (moved < count)
                {
                    if (MoveStartOneFormatUnitForward())
                    { 
                        moved++;
                    } 
                    else 
                    {
                        break; 
                    }
                }
            }
            else if (count < 0) 
            {
                // loop until we have moved the requested number of units 
                // or we can't move anymore and break out of the loop. 
                while (moved > count)
                { 
                    if (MoveStartOneFormatUnitBackward())
                    {
                        moved--;
                    } 
                    else
                    { 
                        break; 
                    }
                } 
            }

            return moved;
        } 

        private int MoveEndFormatUnit(int count) 
        { 
            // This is identical to MoveStartFormatUnit except we are calling MoveEndOneXXX instead of the MoveStartOneXXX versions.
            int moved = 0; 

            if (count > 0)
            {
                // loop until we have moved the requested number of units 
                // or we can't move anymore and break out of the loop.
                while (moved < count) 
                { 
                    if (MoveEndOneFormatUnitForward())
                    { 
                        moved++;
                    }
                    else
                    { 
                        break;
                    } 
                } 
            }
            else if (count < 0) 
            {
                // loop until we have moved the requested number of units
                // or we can't move anymore and break out of the loop.
                while (moved > count) 
                {
                    if (MoveEndOneFormatUnitBackward()) 
                    { 
                        moved--;
                    } 
                    else
                    {
                        break;
                    } 
                }
            } 
 
            return moved;
        } 

        private bool MoveStartOneFormatUnitForward()
        {
            // try moving the endpoint one char-format unit and one para-format unit 
            // and remember where each one ended up.
            ITextRange charRange = _range.GetDuplicate(); 
            int charMoved = charRange.MoveStart(TomUnit.tomCharFormat, 1); 
            Debug.Assert(charMoved == 0 || charMoved == 1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveStart(TomUnit.tomParaFormat, 1);
            Debug.Assert(paraMoved == 0 || paraMoved == 1);

            // ensure the endpoint is set to whichever moved the *least* 
            // and return true iff we moved successfully.
            if (charMoved == 1 && (paraMoved == 0 || charRange.Start <= paraRange.Start)) 
            { 
                _range = charRange;
                return true; 
            }
            else if (paraMoved == 1)
            {
                _range = paraRange; 
                return true;
            } 
            else 
            {
                return false; 
            }
        }

        private bool MoveStartOneFormatUnitBackward() 
        {
            // try moving the endpoint one char-format unit and one para-format unit 
            // and remember where each one ended up. 
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveStart(TomUnit.tomCharFormat, -1); 
            Debug.Assert(charMoved == 0 || charMoved == -1);
            ITextRange paraRange = _range.GetDuplicate();
            int paraMoved = paraRange.MoveStart(TomUnit.tomParaFormat, -1);
            Debug.Assert(paraMoved == 0 || paraMoved == -1); 

            // ensure the endpoint is set to whichever moved the *least* 
            // and return true iff we moved successfully. 
            if (charMoved == -1 && (paraMoved == 0 || charRange.Start >= paraRange.Start))
            { 
                _range = charRange;
                return true;
            }
            else if (paraMoved == -1) 
            {
                _range = paraRange; 
                return true; 
            }
            else 
            {
                return false;
            }
        } 

        private bool MoveEndOneFormatUnitForward() 
        { 
            // try moving the endpoint one char-format unit and one para-format unit
            // and remember where each one ended up. 
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveEnd(TomUnit.tomCharFormat, 1);
            Debug.Assert(charMoved == 0 || charMoved == 1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveEnd(TomUnit.tomParaFormat, 1);
            Debug.Assert(paraMoved == 0 || paraMoved == 1); 
 
            // ensure the endpoint is set to whichever moved the *least*
            // and return true iff we moved successfully. 
            if (charMoved == 1 && (paraMoved == 0 || charRange.End <= paraRange.End))
            {
                _range = charRange;
                return true; 
            }
            else if (paraMoved == 1) 
            { 
                _range = paraRange;
                return true; 
            }
            else
            {
                return false; 
            }
        } 
 
        private bool MoveEndOneFormatUnitBackward()
        { 
            // try moving the endpoint one char-format unit and one para-format unit
            // and remember where each one ended up.
            ITextRange charRange = _range.GetDuplicate();
            int charMoved = charRange.MoveEnd(TomUnit.tomCharFormat, -1); 
            Debug.Assert(charMoved == 0 || charMoved == -1);
            ITextRange paraRange = _range.GetDuplicate(); 
            int paraMoved = paraRange.MoveEnd(TomUnit.tomParaFormat, -1); 
            Debug.Assert(paraMoved == 0 || paraMoved == -1);
 
            // ensure the endpoint is set to whichever moved the *least*
            // and return true iff we moved successfully.
            if (charMoved == -1 && (paraMoved == 0 || charRange.End >= paraRange.End))
            { 
                _range = charRange;
                return true; 
            } 
            else if (paraMoved == -1)
            { 
                _range = paraRange;
                return true;
            }
            else 
            {
                return false; 
            } 
        }
 
        // this wrapper around ITextRange.GetPoint returns true if GetPoint returned S_OK, false if GetPoint returned S_FALSE,
        // or throws an exception for an error hresult.
        internal static bool RangeGetPoint(ITextRange range, TomGetPoint type, out int x, out int y)
        { 
            int hr = range.GetPoint(type, out x, out y);
            if (hr < 0) 
            { 
                Marshal.ThrowExceptionForHR(hr);
            } 
            return hr == 0;
        }

        // converts one of our TextUnits to the corresponding TomUnit. 
        // if there isn't a corresponding one it throws an ArgumentException for the specified name.
        private static TomUnit TomUnitFromTextUnit(TextUnit unit, string name) 
        { 
            switch (unit)
            { 
                case TextUnit.Character:
                    return TomUnit.tomCharacter;

                case TextUnit.Word: 
                    return TomUnit.tomWord;
 
                case TextUnit.Line: 
                    return TomUnit.tomLine;
 
                case TextUnit.Paragraph:
                    return TomUnit.tomParagraph;

                case TextUnit.Page: 
                case TextUnit.Document:
                    return TomUnit.tomStory; 
 
                default:
                    throw new ArgumentException(name); 
            }
        }

        #endregion Private Methods 

        //------------------------------------------------------ 
        // 
        //  Static Methods
        // 
        //-----------------------------------------------------

        #region Static Methods
 
        // these helper functions make it convenient to walk through a range by unit as follows:
        //   int end = range.End; 
        //   for (ITextRange subrange=FirstUnit(range); NextUnit(end, subrange, tomUnit.tomCharFormat);)... 
        // and
        //   int start = range.Start; 
        //   for (ITextRange subrange=LastUnit(range); PreviousUnit(start, subrange, tomUnit.tomParaFormat);)...

        private static ITextRange FirstUnit(ITextRange range)
        { 
            // get a degenerate subrange positioned at the beginning of the range
            ITextRange subrange = range.GetDuplicate(); 
            subrange.Collapse(TomStartEnd.tomStart); 
            return subrange;
        } 

        private static ITextRange LastUnit(ITextRange range)
        {
            // get a degenerate subrange positioned at the end of the range 
            ITextRange subrange = range.GetDuplicate();
            subrange.Collapse(TomStartEnd.tomEnd); 
            return subrange; 
        }
 
        private static bool NextUnit(int end, ITextRange subrange, TomUnit unit)
        {
            if (subrange.End >= end)
            { 
                return false;
            } 
            else 
            {
                // collapse the range to the end and then extend it another unit. 
                subrange.Collapse(TomStartEnd.tomEnd);
                subrange.MoveEnd(unit, 1);

                // truncate if necessary to ensure it fits inside the range 
                if (subrange.End > end)
                { 
                    subrange.End = end; 
                }
 
                return true;
            }
        }
 
        private static bool PreviousUnit(int start, ITextRange subrange, TomUnit unit)
        { 
            if (subrange.Start <= start) 
            {
                return false; 
            }
            else
            {
                // collapse the range to the end and then extend it another unit. 
                subrange.Collapse(TomStartEnd.tomStart);
                subrange.MoveStart(unit, -1); 
 
                // truncate if necessary to ensure it fits inside the range
                if (subrange.Start < start) 
                {
                    subrange.Start = start;
                }
 
                return true;
            } 
        } 

        // this function gets the bounding rectangle for a range that does not wrap lines. 
        private static Rect CalculateOneLineRangeRectangle(ITextRange lineRange, Rect clientRect)
        {
            int start = lineRange.Start;
            int end = lineRange.End; 
            if (start < end)
            { 
                // make a working copy of the range. shrink it by one character since 
                // ITextRange.GetPoint returns the  coordinates of the character *after* the end of the
                // range rather than the one immediately *before* the end of the range. 
                ITextRange range = lineRange.GetDuplicate();
                range.MoveEnd(TomUnit.tomCharacter, -1);
                end--;
 
                // The ITextRange::SetRange method sets this range’s Start = min(cp1, cp2) and End = max(cp1, cp2).
                // If the range is a nondegenerate selection, cp2 is the active end; if it’s a degenerate selection, 
                // the ambiguous cp is displayed at the start of the line (rather than at the end of the previous line). 
                // Set the end to the start and the start to the end to create an ambiguous cp.  This was added to
                // resolve Windows OS Bug 1030882. 
                range.SetRange(range.End, range.Start);

                Rect rect = new Rect(clientRect.Location, clientRect.Size);
                bool trimmed = TrimRectangleByRangeCorners(range, ref rect); 
                if (!trimmed)
                { 
                    while (!trimmed && start < end) 
                    {
                        // shrink the range 
                        range.MoveStart(TomUnit.tomCharacter, 1);
                        // If the character just moved over was a '\r' the start will be out of sink with range.Start
                        start++;
                        if (start < end) 
                        {
                            range.MoveEnd(TomUnit.tomCharacter, -1); 
                            // If the character just moved over was a '\r' the end will be out of sink with range.End 
                            end--;
                        } 
                        //Debug.Assert(start == range.Start);
                        //Debug.Assert(end == range.End);

                        trimmed = TrimRectangleByRangeCorners(range, ref rect); 
                    }
 
                    if (trimmed) 
                    {
                        rect.X = clientRect.X; 
                        rect.Width = clientRect.Width;
                    }
                }
 
                if (!trimmed)
                { 
                    rect = Rect.Empty; 
                }
                return rect; 
            }
            else
            {
                // degenerate range.  return empty rectangle. 

                // it might be nice to return a zero-width rectangle with the appropriate height and location. 
                // however, when the degenerate range is at a line wrapping point then it is ambiguous as to 
                // whether the rectangle should be at the end of one line or the beginning of the next.  clients
                // can always extend a degenerate range by one character in either direction before getting a bounding 
                // rectangle if they want.

                return Rect.Empty;
            } 
        }
 
        // pass in a rectangle of the entire client area and this function will trim it based on visible corners 
        // of the range plus the character following the range. we get the extra character because
        // ITextRange.GetPoint(tomEnd,...) refers to the character following the endpoint rather than immediately 
        // preceding the endpoint.  callers have to take this into account and adjust their ranges accordingly.
        // it will trim the rectangle as much as possible based on the visible corners and leave the rest
        // of the rectangle unchanged.  it returns true if it found at least one visible corner.
        // note: there are some obscure cases that this won't handle.  if all four corners of a character cell 
        // are outside the client area but some part of the middle of the character is visible then this routine will
        // think that the character is entirely invisible.  this limitation is due to the fact that you can only 
        // get coordinates from ITextRange for a specific point on an endpoint character and if that specific 
        // point is offscreen then ITextRange::GetPoint returns S_FALSE.
        private static bool TrimRectangleByRangeCorners(ITextRange range, ref Rect rect) 
        {
            // it's easier to work with the rectangle components separately..
            double left = rect.Left;
            double top = rect.Top; 
            double right = rect.Right;
            double bottom = rect.Bottom; 
 
            // if the top-left corner is visible then trim off anything above that corner.
            // if we are on our first iteration then also trim anything to the left of it. 
            int x, y;
            bool GotTopLeft, GotTopRight, GotBottomLeft, GotBottomRight;
            GotTopRight = false; GotBottomLeft = false;
            if (GotTopLeft = RangeGetPoint(range, TomGetPoint.tomStart/*|TOP|LEFT*/, out x, out y)) 
            {
                left = x; 
                top = y; 
            }
 
            // if the bottom-right corner is visible then trim off anything below that corner.
            // if we are on our first iteration then also trim anything to the right of it.
            if (GotBottomRight = RangeGetPoint(range, /*tomEnd|*/TomGetPoint.TA_BOTTOM | TomGetPoint.TA_RIGHT, out x, out y))
            { 
                right = x;
                bottom = y; 
            } 

            // (if diagonal corners are visible then we are done since we have trimmed all four sides.) 

            // if only one or neither diagonal corner is visible...
            if (!GotTopLeft || !GotBottomRight)
            { 
                // if the top-right corner is visible then trim off anything above that corner.
                // if we are on our first iteration then also trim anything to the right of it. 
                if (GotTopRight = RangeGetPoint(range, /*tomEnd|TOP|*/TomGetPoint.TA_RIGHT, out x, out y)) 
                {
                    right = x; 
                    top = y;
                }
                else
                { 
                    // if the bottom-left corner is visible then trim off anything below that corner.
                    // if we are on our first iteration then also trim anything to the left of it. 
                    if (GotBottomLeft = RangeGetPoint(range, TomGetPoint.tomStart | TomGetPoint.TA_BOTTOM /*|LEFT*/, out x, out y)) 
                    {
                        left = x; 
                        bottom = y;
                    }
                }
            } 

            // update the rectangle 
            rect.X = left; 
            rect.Y = top;
            rect.Width = right - left; 
            rect.Height = bottom - top;

            return GotTopLeft || GotTopRight || GotBottomLeft || GotBottomRight;
        } 

        #endregion Static Methods 
 
        //------------------------------------------------------
        // 
        //  Private Fields
        //
        //-----------------------------------------------------
 
        #region Private Fields
 
        private ITextRange _range;  // alert: this can point to different ITextRange objects over the lifetime of this WindowsRichEditRange. 
        private WindowsRichEdit _pattern;
 
        #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