File: System\Windows\TextDecorationCollectionConverter.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel.Design.Serialization;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
 
namespace System.Windows
{
    /// <summary>
    /// Provides a type converter to convert from <see cref="string"/> to <see cref="TextDecorationCollection"/> only.
    /// </summary>     
    public sealed class TextDecorationCollectionConverter : TypeConverter
    {
        /// <summary>
        /// Returns whether this converter can convert the object to the specified
        /// <paramref name="destinationType"/>, using the specified <paramref name="context"/>.
        /// </summary>
        /// <param name="context">Context information used for conversion.</param>
        /// <param name="destinationType">Type being evaluated for conversion.</param>
        /// <returns>
        /// <see langword="false"/> will always be returned because <see cref="TextDecorations"/> cannot be converted to any other type.
        /// </returns>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            // Return false for any other target type. Don't call base.CanConvertTo() because it would be confusing 
            // in some cases. For example, for destination typeof(string), base TypeConverter just converts the
            // ITypeDescriptorContext to the full name string of the given type.
            return destinationType == typeof(InstanceDescriptor);
        }
 
        /// <summary>
        /// Returns whether this class can convert specific <see cref="Type"/> into <see cref="TextDecorationCollection"/>.
        /// </summary>
        /// <param name="context"> ITypeDescriptorContext </param>
        /// <param name="sourceType">Type being evaluated for conversion.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="sourceType"/> is <see langword="string"/>, otherwise <see langword="false"/>.
        /// </returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }
 
        /// <summary>
        /// Converts <paramref name="input"/> of <see langword="string"/> type to its <see cref="TextDecorationCollection"/> representation.
        /// </summary>
        /// <param name="context">Context information used for conversion, ignored currently.</param>
        /// <param name="culture">The culture specifier to use, ignored currently.</param>        
        /// <param name="input">The string to convert from.</param>
        /// <returns>A <see cref="TextDecorationCollection"/> representing the <see langword="string"/> specified by <paramref name="input"/>.</returns>
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object input)
        {
            if (input is null)
                throw GetConvertFromException(input);
 
            if (input is not string value)
                throw new ArgumentException(SR.Format(SR.General_BadType, "ConvertFrom"), nameof(input));
 
            return ConvertFromString(value);
        }
 
        /// <summary>
        /// Converts <paramref name="text"/> to its <see cref="TextDecorationCollection"/> representation.
        /// </summary>
        /// <param name="text">The string to be converted into TextDecorationCollection object.</param>
        /// <returns>A <see cref="TextDecorationCollection"/> representing the <see cref="string"/> specified by <paramref name="text"/>.</returns>
        /// <remarks>
        /// The text parameter can be either be <see langword="null"/>; <see cref="string.Empty"/>; the string "None"
        /// or a combination of the predefined <see cref="TextDecorations"/> names delimited by commas (,).
        /// One or more blanks spaces can precede  or follow each text decoration name or comma.
        /// There can't be duplicate TextDecoration names in the string. The operation is case-insensitive. 
        /// </remarks>
        public static new TextDecorationCollection ConvertFromString(string text)
        {
            if (text is null)
                return null;
 
            // Flags indicating which pre-defined TextDecoration have been matched
            Decorations matchedDecorations = Decorations.None;
 
            // Sanitize the input
            ReadOnlySpan<char> decorationsSpan = text.AsSpan().Trim();
 
            // Test for "None", which equals to empty collection and needs to be specified alone
            if (decorationsSpan.IsEmpty || decorationsSpan.Equals("None", StringComparison.OrdinalIgnoreCase))
                return new TextDecorationCollection();
 
            // Create new collection, save re-allocations
            TextDecorationCollection textDecorations = new(1 + decorationsSpan.Count(','));
            foreach (Range segment in decorationsSpan.Split(','))
            {
                ReadOnlySpan<char> decoration = decorationsSpan[segment].Trim();
 
                if (decoration.Equals("Overline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.OverlineMatch))
                {
                    textDecorations.Add(TextDecorations.OverLine[0]);
                    matchedDecorations |= Decorations.OverlineMatch;
                }
                else if (decoration.Equals("Baseline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.BaselineMatch))
                {
                    textDecorations.Add(TextDecorations.Baseline[0]);
                    matchedDecorations |= Decorations.BaselineMatch;
                }
                else if (decoration.Equals("Underline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.UnderlineMatch))
                {
                    textDecorations.Add(TextDecorations.Underline[0]);
                    matchedDecorations |= Decorations.UnderlineMatch;
                }
                else if (decoration.Equals("Strikethrough", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.StrikethroughMatch))
                {
                    textDecorations.Add(TextDecorations.Strikethrough[0]);
                    matchedDecorations |= Decorations.StrikethroughMatch;
                }
                else
                {
                    throw new ArgumentException(SR.Format(SR.InvalidTextDecorationCollectionString, text));
                }
            }
 
            return textDecorations;
        }
 
        /// <summary>
        /// Converts a <paramref name="value"/> of <see cref="TextDecorationCollection"/> to the specified <paramref name="destinationType"/>.
        /// </summary>
        /// <param name="context">Context information used for conversion.</param>
        /// <param name="culture">The culture specifier to use.</param>
        /// <param name="value">Duration value to convert from.</param>
        /// <param name="destinationType">Type being evaluated for conversion.</param>
        /// <returns><see langword="null"/> will always be returned because <see cref="TextDecorations"/> cannot be converted to any other type.</returns>        
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor) && value is IEnumerable<TextDecoration>)
            {
                ConstructorInfo ci = typeof(TextDecorationCollection).GetConstructor(new Type[] { typeof(IEnumerable<TextDecoration>) });
 
                return new InstanceDescriptor(ci, new object[] { value });
            }
 
            // Pass unhandled cases to base class (which will throw exceptions for null value or destinationType.)
            return base.ConvertTo(context, culture, value, destinationType);
        }
 
        /// <summary>
        /// Abstraction helper of matched decorations during conversion.
        /// </summary>
        [Flags]
        private enum Decorations : byte
        {
            None = 0,
            OverlineMatch = 1 << 0,
            BaselineMatch = 1 << 1,
            UnderlineMatch = 1 << 2,
            StrikethroughMatch = 1 << 3,
        }
    }
}