diff --git a/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj b/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj index 743e2d55..a7318f27 100644 --- a/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj +++ b/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj @@ -4,6 +4,12 @@ + + $(TargetFrameworks);net6.0 + $(DefineConstants);RECORD_STRUCT_SUPPORT + + + CSF.Screenplay.Selenium $(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/CSF.Screenplay.Selenium/Color.cs b/CSF.Screenplay.Selenium/Color.cs new file mode 100644 index 00000000..624c3615 --- /dev/null +++ b/CSF.Screenplay.Selenium/Color.cs @@ -0,0 +1,314 @@ +using System; +using SysColor = System.Drawing.Color; + +namespace CSF.Screenplay.Selenium +{ + /// + /// Immutable type represents a color on the web, in a manner which may be converted and compared between web-supported formats. + /// + /// + /// + /// An uninitialized instance of this type will represent "fully-transparent black". + /// Internally, this type stores color as three unsigned values (Red, Green, Blue) and a + /// representing the transparency (Alpha). The Alpha value must be between 0 (fully transparent) + /// and 1 (fully opaque). + /// + /// + /// This struct contains functionality to compare equality with, and convert to/from the .NET built-in . + /// It also contains parsing logic to parse/format an instance from/to string representations of color which are used by web browsers. + /// See the MDN writeup of web color for + /// more information about the valid formats. + /// + /// +#if RECORD_STRUCT_SUPPORT + public readonly record struct Color +#else + public struct Color +#endif + : IEquatable, IEquatable, IEquatable + { + readonly double alpha; + + /// + /// Gets the red component in the sRGB color space. + /// +#if RECORD_STRUCT_SUPPORT + public readonly byte Red { get; init; } +#else + public byte Red { get; private set; } +#endif + + /// + /// Gets the green component in the sRGB color space. + /// +#if RECORD_STRUCT_SUPPORT + public readonly byte Green { get; init; } +#else + public byte Green { get; private set; } +#endif + + /// + /// Gets the blue component in the sRGB color space. + /// +#if RECORD_STRUCT_SUPPORT + public readonly byte Blue { get; init; } +#else + public byte Blue { get; private set; } +#endif + + /// + /// Gets the alpha (transparency) component in the sRGB color space. + /// + /// + /// The value of this property will always be between zero (fully transparent) and one (fully opaque). + /// +#if RECORD_STRUCT_SUPPORT + public readonly double Alpha + { + get => alpha; + init => this.alpha = SanitizeAlpha(value); + } +#else + public double Alpha { get => alpha; } +#endif + + /// + /// Gets a representation of , as a byte. + /// + public byte AlphaAsByte => (byte) Math.Floor(Math.Round(Alpha * 255, 0, MidpointRounding.AwayFromZero)); + + /// + /// Converts this color to a . + /// + /// + /// + /// In some situations, it might be useful to convert to/from the .NET built-in + /// type. Use this method and to do so. + /// + /// + /// A representation of this color. + public SysColor ToSystemDrawingColor() => SysColor.FromArgb(AlphaAsByte, Red, Green, Blue); + + /// + public bool Equals(SysColor other) => ToSystemDrawingColor().Equals(other); + + /// + public bool Equals(Color other) => other.Red == Red + && other.Green == Green + && other.Blue == Blue + && other.AlphaAsByte == AlphaAsByte; + + /// + public bool Equals(string other) => TryParse(other, out var color) && Equals(color); + +#if !RECORD_STRUCT_SUPPORT + /// + public override bool Equals(object obj) + { + if(obj is Color color) return Equals(color); + if(obj is SysColor sysColor) return Equals(sysColor); + if(obj is string strColor) return Equals(strColor); + return false; + } +#endif + + /// + public override int GetHashCode() => HashCode.Combine(Red, Green, Blue, Alpha); + + /// + /// Creates a new instance of from the specified RGBA values. + /// + /// + /// + /// The byte data type for the R, G and B components of the color ensure that it is impossible to specify + /// values outside the permitted range. As for the , if a value of less than zero is specified + /// here then it is treated as zero. Likewise if a value of greater than one is specified then it will be treated as one. + /// + /// + /// The red component in the sRGB color space + /// The green component in the sRGB color space + /// The blue component in the sRGB color space + /// The alpha (transparency) component, which must be between zero and one. + public Color(byte red, byte green, byte blue, double alpha) + { + Red = red; + Green = green; + Blue = blue; + this.alpha = SanitizeAlpha(alpha); + } + + /// + /// Sanitizes a value intended for . + /// + /// + /// If is less than zero then this method returns zero. + /// If is more than one then this method returns one. + /// Otherwise, this method returns . + /// + /// The alpha value. + /// A sanitized alpha value. + static double SanitizeAlpha(double alpha) => alpha < 0 ? 0 : alpha > 1 ? 1 : alpha; + + /// + /// Converts a into a . + /// + /// + /// + /// In some situations, it might be useful to convert to/from the .NET built-in + /// type. Use this method and to do so. + /// + /// + /// A representation of the specified color. + public static Color FromSystemDrawingColour(SysColor color) + => new Color(color.R, color.G, color.B, color.A / 255); + + /// + /// Gets a value indicating whether the specified is a valid string representation + /// for a . If this returns then exposes the parsed + /// value. + /// + /// + /// + /// If this method returns then the output parameter will contain + /// the color parsed from the specified string. If this method returns then the output parameter + /// has an undefined value and must not be used. + /// + /// This method will not raise an exception and it is safe to use with unknown strings, including those which + /// might be . + /// + /// A string containing a color value which is compatible with the web. + /// See the MDN writeup on + /// web colors for more information. + /// If this method returns , then this parameter exposes the parsed color value. + /// If this method returns then the value of this parameter must be ignored. + /// if parsing is successful (the contains a valid & + /// recognized color representation); if not + public static bool TryParse(string webColorValue, out Color color) + { + color = default; + if(string.IsNullOrWhiteSpace(webColorValue)) return false; + + var allParsers = new IParsesColor[] + { + new RgbColorParser(), + new RgbPercentageColorParser(), + new RgbaColorParser(), + new RgbaPercentageColorParser(), + new Hex6ColorParser(), + new Hex3ColorParser(), + new HslColorParser(), + new HslaColorParser(), + new NamedColorParser(), + }; + + foreach(var parser in allParsers) + if(parser.TryParseColor(webColorValue, out color)) return true; + + color = default; + return false; + } + + /// + /// Parses a from a string representation of a color. + /// + /// + /// + /// If is a valid representation of a web-compatible color value then this method will + /// return that color as an instance of . If not, this method will throw an exception. + /// + /// If you are not certain that the represents a valid colour then use + /// instead. + /// + /// A string containing a color value which is compatible with the web. + /// See the MDN writeup on + /// web colors for more information. + /// The parsed color. + /// If is . + /// If is not a recognized representation of a color. + public static Color Parse(string webColorValue) + { + if (webColorValue is null) + throw new ArgumentNullException(nameof(webColorValue)); + + return TryParse(webColorValue, out var color) + ? color + : throw new FormatException("The string color representation must be a in a web-compatible recognized format."); + } + + /// + /// Gets a value indicating whether the specified and + /// are equal. + /// + /// + /// Despite the type differences, two colors are equal if they represent the same sRGB color. + /// + /// The + /// The + /// if the two different colors represent the same sRGB color; if not. + public static bool operator ==(Color color, SysColor sysColor) => color.Equals(sysColor); + + /// + /// Gets a value indicating whether the specified and + /// are not equal. + /// + /// + /// Despite the type differences, two colors are equal if they represent the same sRGB color. + /// + /// The + /// The + /// if the two different colors represent the same sRGB color; if not. + public static bool operator !=(Color color, SysColor sysColor) => !(color == sysColor); + + /// + /// Gets a value indicating whether the specified and representation of a color + /// are equal. + /// + /// + /// A color is equal to a string if the result of is + /// and if the resulting parsed instance is equal to the current color. + /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is . + /// + /// The + /// The string which represents a color + /// if the current color and the string color representation indicate the same sRGB color; if not. + public static bool operator ==(Color color, string stringColor) => color.Equals(stringColor); + + /// + /// Gets a value indicating whether the specified and representation of a color + /// are not equal. + /// + /// + /// A color is equal to a string if the result of is + /// and if the resulting parsed instance is equal to the current color. + /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is . + /// + /// The + /// The string which represents a color + /// if the current color and the string color representation indicate the same sRGB color; if not. + public static bool operator !=(Color color, string stringColor) => !(color == stringColor); + + #if !RECORD_STRUCT_SUPPORT + /// + /// Gets a value indicating whether the two specified instances are equal. + /// + /// + /// Two colors are equal if they represent the same sRGB color. + /// + /// The first + /// The second + /// if the two colors represent the same sRGB color; if not. + public static bool operator ==(Color first, Color second) => first.Equals(second); + + /// + /// Gets a value indicating whether the two specified instances are not equal. + /// + /// + /// Two colors are equal if they represent the same sRGB color. + /// + /// The first + /// The second + /// if the two colors represent the same sRGB color; if not. + public static bool operator !=(Color first, Color second) => !(first == second); +#endif + } +} \ No newline at end of file diff --git a/CSF.Screenplay.Selenium/ColorParser.cs b/CSF.Screenplay.Selenium/ColorParser.cs new file mode 100644 index 00000000..4eb3c946 --- /dev/null +++ b/CSF.Screenplay.Selenium/ColorParser.cs @@ -0,0 +1,243 @@ +using System; +using System.Text.RegularExpressions; + +namespace CSF.Screenplay.Selenium +{ + /// + /// An object which can get an instance of . + /// + internal interface IParsesColor + { + /// + /// Attempts to parse an instance of from the specified . + /// + /// + /// + /// If the parsing is successful then this method will return and the + /// will contain the parsed color value. + /// Otherwise it will return and the parameter must be ignored. + /// + /// + /// A string representing a color. + /// If this method returns then this parameter exposes the parsed + /// color; if not then this value must be ignored. + /// if the parsing was a success; if not. + bool TryParseColor(string colorValue, out Color color); + } + + internal abstract class NumericColorParser : IParsesColor + { + protected static readonly string + BytePart = @"\s*(\d{1,3})\s*", + DecimalAlphaPart = @"\s*(0|1|0\.\d+)\s*", + PercentDecimalPart = @"\s*(\d{1,3}|\d{1,2}\.\d+)%\s*", + PercentPart = @"\s*(\d{1,3})%\s*", + Hex2Part = @"\s*[1-9a-f]{2}\s*", + Hex1Part = @"\s*[1-9a-f]\s*"; + + /// + public virtual bool TryParseColor(string colorValue, out Color color) + { + color = default; + var match = GetParsingRegex(out var includesAlpha).Match(colorValue); + if(!match.Success) return false; + + color = new Color(GetByte(match.Groups[1]), + GetByte(match.Groups[2]), + GetByte(match.Groups[3]), + includesAlpha ? double.Parse(match.Groups[4].Value) : 1); + return true; + } + + /// + /// Gets a regex which parses a string color representation. + /// + /// if this regex includes a value for the alpha channel; if not. + /// A regex + protected virtual Regex GetParsingRegex(out bool includesAlpha) + => new Regex(GetParsingPattern(out includesAlpha), + RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, + TimeSpan.FromMilliseconds(500)); + + /// + /// Gets a regex pattern which parses a string color representation. + /// + /// if this regex includes a value for the alpha channel; if not. + /// A regex + protected abstract string GetParsingPattern(out bool includesAlpha); + + /// + /// Parses a byte value from the value of the match group. + /// + /// A regex match group + /// A byte value + protected virtual byte GetByte(Group matchGroup) => byte.Parse(matchGroup.Value); + } + + internal class RgbColorParser : NumericColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = false; + return @$"^\s*rgb\({BytePart},{BytePart},{BytePart}\)\s*$"; + } + } + + internal class RgbaColorParser : NumericColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = true; + return @$"^\s*rgba\({BytePart},{BytePart},{BytePart},{DecimalAlphaPart}\)\s*$"; + } + } + + internal class RgbPercentageColorParser : NumericColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = false; + return @$"^\s*rgb\({PercentDecimalPart},{PercentDecimalPart},{PercentDecimalPart}\)\s*$"; + } + + /// + protected override byte GetByte(Group matchGroup) => (byte) (double.Parse(matchGroup.Value) / 100D * 255D); + } + + internal class RgbaPercentageColorParser : RgbPercentageColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = true; + return @$"^\s*rgb\({PercentDecimalPart},{PercentDecimalPart},{PercentDecimalPart},{DecimalAlphaPart}\)\s*$"; + } + } + + internal class Hex3ColorParser : NumericColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = false; + return @$"^\s*#{Hex1Part}{Hex1Part}{Hex1Part}\s*$"; + } + + /// + protected override byte GetByte(Group matchGroup) => Convert.ToByte(matchGroup.Value + matchGroup.Value, 16); + } + + internal class Hex6ColorParser : NumericColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = false; + return @$"^\s*#{Hex2Part}{Hex2Part}{Hex2Part}\s*$"; + } + + /// + protected override byte GetByte(Group matchGroup) => Convert.ToByte(matchGroup.Value, 16); + } + + internal class HslColorParser : NumericColorParser + { + /// + public override bool TryParseColor(string colorValue, out Color color) + { + color = default; + var match = GetParsingRegex(out var includesAlpha).Match(colorValue); + if(!match.Success) return false; + + color = HslToRgb(short.Parse(match.Groups[1].Value), + byte.Parse(match.Groups[2].Value), + byte.Parse(match.Groups[3].Value), + includesAlpha ? double.Parse(match.Groups[4].Value) : 1); + return true; + } + + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = false; + return @$"^\s*hsl\({BytePart},{PercentPart},{PercentPart}\s*$"; + } + + /// + /// Converts an HSL color to the sRGB color space. + /// + /// + /// + /// This is based upon the algorithms available at + /// The + /// Selenium 4.40.0 HSL converter for Java. + /// + /// + /// The hue value in degrees (0 to 360) + /// The saturation percentage (0 to 100) + /// The lightness percentage (0 to 100) + /// The alpha (transparency) value (0 to 1) + /// An instance of in the sRGB color space + static Color HslToRgb(short h, byte s, byte l, double alpha) + { + var hue = h / 360D; + var saturation = s / 100D; + var lightness = l / 100D; + + // FYI: Saturation = zero means gray, making hue irrelevant + if(s == 0) + { + var allChannels = (byte) Math.Round(lightness * 255D, MidpointRounding.AwayFromZero); + return new Color(allChannels, allChannels, allChannels, alpha); + } + + var luminosity2 = (lightness < 0.5) + ? lightness * (saturation + 1D) + : lightness + saturation - lightness * saturation; + var luminosity1 = 2D * lightness - luminosity2; + + return new Color(HueValueToRgbByte(luminosity1, luminosity2, hue + 1D / 3D), + HueValueToRgbByte(luminosity1, luminosity2, hue), + HueValueToRgbByte(luminosity1, luminosity2, hue - 1D / 3D), + alpha); + } + + static byte HueValueToRgbByte(double luminosity1, + double luminosity2, + double hueValue) + => (byte) Math.Round(HueValueToRgb(luminosity1, luminosity2, hueValue) * 255D, MidpointRounding.AwayFromZero); + + static double HueValueToRgb(double luminosity1, + double luminosity2, + double hueValue) + { + if (hueValue < 0D) hueValue += 1; + if (hueValue > 1D) hueValue -= 1.0; + if (hueValue < 1D / 6D) return luminosity1 + (luminosity2 - luminosity1) * 6D * hueValue; + if (hueValue < 1D / 2D) return luminosity2; + if (hueValue < 2D / 3D) return luminosity1 + (luminosity2 - luminosity1) * (2D / 3D - hueValue) * 6D; + return luminosity1; + } + } + + internal class HslaColorParser : HslColorParser + { + /// + protected override string GetParsingPattern(out bool includesAlpha) + { + includesAlpha = true; + return @$"^\s*hsla\({BytePart},{PercentPart},{PercentPart},{DecimalAlphaPart}\s*$"; + } + } + + internal class NamedColorParser : IParsesColor + { + /// + public bool TryParseColor(string colorValue, out Color color) + => Colors.TryGetNamedColor(colorValue, out color); + } + +} \ No newline at end of file diff --git a/CSF.Screenplay.Selenium/Colors.cs b/CSF.Screenplay.Selenium/Colors.cs new file mode 100644 index 00000000..c391381f --- /dev/null +++ b/CSF.Screenplay.Selenium/Colors.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; + +namespace CSF.Screenplay.Selenium +{ + /// + /// Static type containing the pre-defined colors for the web. + /// + /// + /// + /// Each static property of this type corresponds to a well-known pre-defined color, according to + /// the W3C HTML4 spec. + /// + /// + public static class Colors + { + /// + /// Caches the named colors and their names, for performance reasons when using and/or + /// . + /// + static readonly ReadOnlyDictionary namedColors = GetNamedColorCache(); + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public static Color TRANSPARENT => new Color(0, 0, 0, 0); + public static Color ALICEBLUE => new Color(240, 248, 255, 1); + public static Color ANTIQUEWHITE => new Color(250, 235, 215, 1); + public static Color AQUA => new Color(0, 255, 255, 1); + public static Color AQUAMARINE => new Color(127, 255, 212, 1); + public static Color AZURE => new Color(240, 255, 255, 1); + public static Color BEIGE => new Color(245, 245, 220, 1); + public static Color BISQUE => new Color(255, 228, 196, 1); + public static Color BLACK => new Color(0, 0, 0, 1); + public static Color BLANCHEDALMOND => new Color(255, 235, 205, 1); + public static Color BLUE => new Color(0, 0, 255, 1); + public static Color BLUEVIOLET => new Color(138, 43, 226, 1); + public static Color BROWN => new Color(165, 42, 42, 1); + public static Color BURLYWOOD => new Color(222, 184, 135, 1); + public static Color CADETBLUE => new Color(95, 158, 160, 1); + public static Color CHARTREUSE => new Color(127, 255, 0, 1); + public static Color CHOCOLATE => new Color(210, 105, 30, 1); + public static Color CORAL => new Color(255, 127, 80, 1); + public static Color CORNFLOWERBLUE => new Color(100, 149, 237, 1); + public static Color CORNSILK => new Color(255, 248, 220, 1); + public static Color CRIMSON => new Color(220, 20, 60, 1); + public static Color CYAN => new Color(0, 255, 255, 1); + public static Color DARKBLUE => new Color(0, 0, 139, 1); + public static Color DARKCYAN => new Color(0, 139, 139, 1); + public static Color DARKGOLDENROD => new Color(184, 134, 11, 1); + public static Color DARKGRAY => new Color(169, 169, 169, 1); + public static Color DARKGREEN => new Color(0, 100, 0, 1); + public static Color DARKGREY => new Color(169, 169, 169, 1); + public static Color DARKKHAKI => new Color(189, 183, 107, 1); + public static Color DARKMAGENTA => new Color(139, 0, 139, 1); + public static Color DARKOLIVEGREEN => new Color(85, 107, 47, 1); + public static Color DARKORANGE => new Color(255, 140, 0, 1); + public static Color DARKORCHID => new Color(153, 50, 204, 1); + public static Color DARKRED => new Color(139, 0, 0, 1); + public static Color DARKSALMON => new Color(233, 150, 122, 1); + public static Color DARKSEAGREEN => new Color(143, 188, 143, 1); + public static Color DARKSLATEBLUE => new Color(72, 61, 139, 1); + public static Color DARKSLATEGRAY => new Color(47, 79, 79, 1); + public static Color DARKSLATEGREY => new Color(47, 79, 79, 1); + public static Color DARKTURQUOISE => new Color(0, 206, 209, 1); + public static Color DARKVIOLET => new Color(148, 0, 211, 1); + public static Color DEEPPINK => new Color(255, 20, 147, 1); + public static Color DEEPSKYBLUE => new Color(0, 191, 255, 1); + public static Color DIMGRAY => new Color(105, 105, 105, 1); + public static Color DIMGREY => new Color(105, 105, 105, 1); + public static Color DODGERBLUE => new Color(30, 144, 255, 1); + public static Color FIREBRICK => new Color(178, 34, 34, 1); + public static Color FLORALWHITE => new Color(255, 250, 240, 1); + public static Color FORESTGREEN => new Color(34, 139, 34, 1); + public static Color FUCHSIA => new Color(255, 0, 255, 1); + public static Color GAINSBORO => new Color(220, 220, 220, 1); + public static Color GHOSTWHITE => new Color(248, 248, 255, 1); + public static Color GOLD => new Color(255, 215, 0, 1); + public static Color GOLDENROD => new Color(218, 165, 32, 1); + public static Color GRAY => new Color(128, 128, 128, 1); + public static Color GREY => new Color(128, 128, 128, 1); + public static Color GREEN => new Color(0, 128, 0, 1); + public static Color GREENYELLOW => new Color(173, 255, 47, 1); + public static Color HONEYDEW => new Color(240, 255, 240, 1); + public static Color HOTPINK => new Color(255, 105, 180, 1); + public static Color INDIANRED => new Color(205, 92, 92, 1); + public static Color INDIGO => new Color(75, 0, 130, 1); + public static Color IVORY => new Color(255, 255, 240, 1); + public static Color KHAKI => new Color(240, 230, 140, 1); + public static Color LAVENDER => new Color(230, 230, 250, 1); + public static Color LAVENDERBLUSH => new Color(255, 240, 245, 1); + public static Color LAWNGREEN => new Color(124, 252, 0, 1); + public static Color LEMONCHIFFON => new Color(255, 250, 205, 1); + public static Color LIGHTBLUE => new Color(173, 216, 230, 1); + public static Color LIGHTCORAL => new Color(240, 128, 128, 1); + public static Color LIGHTCYAN => new Color(224, 255, 255, 1); + public static Color LIGHTGOLDENRODYELLOW => new Color(250, 250, 210, 1); + public static Color LIGHTGRAY => new Color(211, 211, 211, 1); + public static Color LIGHTGREEN => new Color(144, 238, 144, 1); + public static Color LIGHTGREY => new Color(211, 211, 211, 1); + public static Color LIGHTPINK => new Color(255, 182, 193, 1); + public static Color LIGHTSALMON => new Color(255, 160, 122, 1); + public static Color LIGHTSEAGREEN => new Color(32, 178, 170, 1); + public static Color LIGHTSKYBLUE => new Color(135, 206, 250, 1); + public static Color LIGHTSLATEGRAY => new Color(119, 136, 153, 1); + public static Color LIGHTSLATEGREY => new Color(119, 136, 153, 1); + public static Color LIGHTSTEELBLUE => new Color(176, 196, 222, 1); + public static Color LIGHTYELLOW => new Color(255, 255, 224, 1); + public static Color LIME => new Color(0, 255, 0, 1); + public static Color LIMEGREEN => new Color(50, 205, 50, 1); + public static Color LINEN => new Color(250, 240, 230, 1); + public static Color MAGENTA => new Color(255, 0, 255, 1); + public static Color MAROON => new Color(128, 0, 0, 1); + public static Color MEDIUMAQUAMARINE => new Color(102, 205, 170, 1); + public static Color MEDIUMBLUE => new Color(0, 0, 205, 1); + public static Color MEDIUMORCHID => new Color(186, 85, 211, 1); + public static Color MEDIUMPURPLE => new Color(147, 112, 219, 1); + public static Color MEDIUMSEAGREEN => new Color(60, 179, 113, 1); + public static Color MEDIUMSLATEBLUE => new Color(123, 104, 238, 1); + public static Color MEDIUMSPRINGGREEN => new Color(0, 250, 154, 1); + public static Color MEDIUMTURQUOISE => new Color(72, 209, 204, 1); + public static Color MEDIUMVIOLETRED => new Color(199, 21, 133, 1); + public static Color MIDNIGHTBLUE => new Color(25, 25, 112, 1); + public static Color MINTCREAM => new Color(245, 255, 250, 1); + public static Color MISTYROSE => new Color(255, 228, 225, 1); + public static Color MOCCASIN => new Color(255, 228, 181, 1); + public static Color NAVAJOWHITE => new Color(255, 222, 173, 1); + public static Color NAVY => new Color(0, 0, 128, 1); + public static Color OLDLACE => new Color(253, 245, 230, 1); + public static Color OLIVE => new Color(128, 128, 0, 1); + public static Color OLIVEDRAB => new Color(107, 142, 35, 1); + public static Color ORANGE => new Color(255, 165, 0, 1); + public static Color ORANGERED => new Color(255, 69, 0, 1); + public static Color ORCHID => new Color(218, 112, 214, 1); + public static Color PALEGOLDENROD => new Color(238, 232, 170, 1); + public static Color PALEGREEN => new Color(152, 251, 152, 1); + public static Color PALETURQUOISE => new Color(175, 238, 238, 1); + public static Color PALEVIOLETRED => new Color(219, 112, 147, 1); + public static Color PAPAYAWHIP => new Color(255, 239, 213, 1); + public static Color PEACHPUFF => new Color(255, 218, 185, 1); + public static Color PERU => new Color(205, 133, 63, 1); + public static Color PINK => new Color(255, 192, 203, 1); + public static Color PLUM => new Color(221, 160, 221, 1); + public static Color POWDERBLUE => new Color(176, 224, 230, 1); + public static Color PURPLE => new Color(128, 0, 128, 1); + public static Color REBECCAPURPLE => new Color(102, 51, 153, 1); + public static Color RED => new Color(255, 0, 0, 1); + public static Color ROSYBROWN => new Color(188, 143, 143, 1); + public static Color ROYALBLUE => new Color(65, 105, 225, 1); + public static Color SADDLEBROWN => new Color(139, 69, 19, 1); + public static Color SALMON => new Color(250, 128, 114, 1); + public static Color SANDYBROWN => new Color(244, 164, 96, 1); + public static Color SEAGREEN => new Color(46, 139, 87, 1); + public static Color SEASHELL => new Color(255, 245, 238, 1); + public static Color SIENNA => new Color(160, 82, 45, 1); + public static Color SILVER => new Color(192, 192, 192, 1); + public static Color SKYBLUE => new Color(135, 206, 235, 1); + public static Color SLATEBLUE => new Color(106, 90, 205, 1); + public static Color SLATEGRAY => new Color(112, 128, 144, 1); + public static Color SLATEGREY => new Color(112, 128, 144, 1); + public static Color SNOW => new Color(255, 250, 250, 1); + public static Color SPRINGGREEN => new Color(0, 255, 127, 1); + public static Color STEELBLUE => new Color(70, 130, 180, 1); + public static Color TAN => new Color(210, 180, 140, 1); + public static Color TEAL => new Color(0, 128, 128, 1); + public static Color THISTLE => new Color(216, 191, 216, 1); + public static Color TOMATO => new Color(255, 99, 71, 1); + public static Color TURQUOISE => new Color(64, 224, 208, 1); + public static Color VIOLET => new Color(238, 130, 238, 1); + public static Color WHEAT => new Color(245, 222, 179, 1); + public static Color WHITE => new Color(255, 255, 255, 1); + public static Color WHITESMOKE => new Color(245, 245, 245, 1); + public static Color YELLOW => new Color(255, 255, 0, 1); + public static Color YELLOWGREEN => new Color(154, 205, 50, 1); + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + /// + /// Gets a instance which matches the specified color-name. + /// + /// + /// + /// This method will throw an exception if does not correspond to a well-known color + /// name. This is a case-insensitive match. + /// If you are uncertain whether the is a well-known color name or not then consider + /// instead. + /// + /// + /// A named color (case insensitive) + /// A which corresponds to the specified named color. + /// If is + /// If does not correspond to a well-known named color + public static Color GetNamedColor(string colorName) + { + if (colorName is null) throw new ArgumentNullException(nameof(colorName)); + if(namedColors.TryGetValue(colorName, out var color)) return color; + throw new ArgumentException($"'{colorName}' is not a well-known named color."); + } + + /// + /// Gets a value indicating whether is a well-known color name. + /// Exposes a instance which corresponds to that name if it is. + /// + /// + /// + /// This method will never throw an exception for any value of . + /// Note that match with a well-known color name is performed in a case-insensitive manner. + /// + /// + /// A string which might correspond to a well-known color name. + /// If this method returns then this is a + /// which corresponds to the color indicated by ; if it returns + /// then this value is undefined and must be ignored. + /// if is a well-known color name; if not. + public static bool TryGetNamedColor(string colorName, out Color color) + { + color = BLACK; + if (colorName is null) return false; + return namedColors.TryGetValue(colorName, out color); + } + + /// + /// Gets a dictionary of the names/instances of the well-known colors. + /// + /// + /// + /// The keys of this dictionary are case insensitive and correspond to the names of the static properties of this type. + /// The values are instances of the that the corresponding property returns. + /// + /// + /// A cache of the named color values. + static ReadOnlyDictionary GetNamedColorCache() + { + var namedColorValues = typeof(Colors) + .GetProperties(BindingFlags.Static) + .ToDictionary(k => k.Name, v => (Color) v.GetValue(null), StringComparer.InvariantCultureIgnoreCase); + return new ReadOnlyDictionary(namedColorValues); + } + } +} \ No newline at end of file