File: System\Windows\Documents\TextEffectResolver.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: Text Effect Setter
// 
 
using System.Windows.Media;
 
using MS.Internal.Text;
 
namespace System.Windows.Documents
{
    /// <summary>
    /// Helper class to help set text effects into the Text container
    /// </summary>
    public static class TextEffectResolver
    {   
        //---------------------------
        // public static methods
        //---------------------------
        
        /// <summary>
        /// resolves text effect on a text range to a list of text effect targets.
        /// The method will walk the text and perform the following task:
        /// 1) For each continous block of text, create a text effect targeting the scoping element
        /// 2) For the text effect created, calculate the starting cp index and cp count for the effect
        /// 
        /// The method will create freezable copy of the TextEffect passed in and fill in 
        /// CharacterIndex and Count for the range.
        /// </summary>
        /// <param name="startPosition">starting text pointer</param>
        /// <param name="endPosition">end text pointer</param>
        /// <param name="effect">effect that is apply on the text</param>
        public static TextEffectTarget[] Resolve(
            TextPointer             startPosition, 
            TextPointer             endPosition,
            TextEffect              effect
            )
        {
            ArgumentNullException.ThrowIfNull(effect);
 
            ValidationHelper.VerifyPositionPair(startPosition, endPosition);            
            TextPointer   effectStart   = new TextPointer(startPosition);            
 
            // move to the first character symbol at or after Start position
            MoveToFirstCharacterSymbol(effectStart);
 
            TextEffect effectCopy;
            List<TextEffectTarget> list = new List<TextEffectTarget>();
 
            // we will now traverse the TOM and resolve text effects to the immediate parent 
            // of the characters. We are effectively applying the text effect onto 
            // block of continous text.
            while (effectStart.CompareTo(endPosition) < 0)
            {   
                // create a copy of the text effect 
                // so that we can set the CharacterIndex and Count
                effectCopy                 = effect.Clone();
 
                // create a position
                TextPointer continuousTextEnd = new TextPointer(effectStart);
                
                // move the position to the end of the continuous text block
                MoveToFirstNonCharacterSymbol(continuousTextEnd, endPosition);
 
                // make sure we are not out of the range
                continuousTextEnd = (TextPointer)TextPointerBase.Min(continuousTextEnd, endPosition);
 
                // set the character index to be the distance from the Start 
                // of this text block to the Start of the text container
                effectCopy.PositionStart = effectStart.TextContainer.Start.GetOffsetToPosition(effectStart);
 
                // count is the distance from the text block start to end
                effectCopy.PositionCount = effectStart.GetOffsetToPosition(continuousTextEnd);
 
                list.Add(
                    new TextEffectTarget(
                        effectStart.Parent, 
                        effectCopy
                        )
                 );
                
                // move the effectStart to the beginning of the next text block.
                effectStart = continuousTextEnd;
                MoveToFirstCharacterSymbol(effectStart);
            }
 
            return list.ToArray();
        }
 
 
        //---------------------------
        // Private static methods
        //---------------------------
        
        // move to the first character symbol
        private static void MoveToFirstCharacterSymbol(TextPointer navigator)
        {
            while (navigator.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text
                && navigator.MoveToNextContextPosition(LogicalDirection.Forward)) ;
        }
 
        // move to the first non-character symbol, but not pass beyond the limit
        private static void MoveToFirstNonCharacterSymbol(
            TextPointer navigator,   // navigator to move
            TextPointer  stopHint     // don't move further if we already pass beyond this point
            )         
        {
            while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text 
                && navigator.CompareTo(stopHint) < 0
                && navigator.MoveToNextContextPosition(LogicalDirection.Forward)) ;
        }
    }
 
    /// <summary>
    /// result from TextEffectSetter which contains the TextEffect created and the DependencyObject 
    /// to which the TextEffect should be set.
    /// </summary>
    public class TextEffectTarget
    {
        private DependencyObject   _element;
        private TextEffect         _effect;
 
        internal TextEffectTarget(
            DependencyObject element,          
            TextEffect       effect
            )
        {
            ArgumentNullException.ThrowIfNull(element);
            ArgumentNullException.ThrowIfNull(effect);
 
            _element = element;
            _effect  = effect;
        }
 
        /// <summary>
        /// The DependencyObject that the TextEffect is targetting.
        /// </summary>
        public DependencyObject Element
        {
            get { return _element; }
        }
            
        /// <summary>
        /// The TextEffect
        /// </summary>
        public TextEffect TextEffect
        {
            get { return _effect; }
        }
 
        /// <summary>
        /// Enable the TextEffect on the target. If the texteffect is 
        /// already enabled, this will be a no-op.
        /// </summary>
        public void Enable()
        {
            TextEffectCollection textEffects = DynamicPropertyReader.GetTextEffects(_element);
            if (textEffects == null)
            {
                textEffects = new TextEffectCollection();
                        
                // use it as reference to avoid creating a copy (Freezable pattern)
                _element.SetValue(TextElement.TextEffectsProperty, textEffects);
            }
 
 
            // check whether this instance is already enabled
            for (int i = 0; i < textEffects.Count; i++)
            {
                if (textEffects[i] == _effect)
                    return; // no-op
            }
 
            // use this as reference. 
            textEffects.Add(_effect);                    
        }
 
        /// <summary>
        /// Disable TextEffect on the target. If the texteffect is 
        /// already disabled, this will be a no-op.
        /// </summary>         
        public void Disable()
        {
            TextEffectCollection textEffects = DynamicPropertyReader.GetTextEffects(_element);
 
            if (textEffects != null)
            {
                for (int i = 0; i < textEffects.Count; i++)
                {
                    if (textEffects[i] == _effect)
                    {
                        // remove the exact instance of the effect from the collection
                        textEffects.RemoveAt(i);
                        return;
                    }
                }
            }
        }
 
        /// <summary>
        /// Return whether the text effect is enabled on the target element
        /// </summary>        
        public bool IsEnabled 
        {
            get 
            {
                TextEffectCollection textEffects = DynamicPropertyReader.GetTextEffects(_element);
                if (textEffects != null)
                {
                    for (int i = 0; i < textEffects.Count; i++)
                    {
                        if (textEffects[i] == _effect)
                        {
                           return true;
                        }
                    }
                }
 
                return false;
            }
        }
    }
}