|
@@ -97,49 +97,49 @@ public readonly partial record struct Color
|
|
|
)
|
|
|
{
|
|
|
return (formatString, formatProvider) switch
|
|
|
- {
|
|
|
- // Null or empty string and null formatProvider - Revert to 'g' case behavior
|
|
|
- (null or { Length: 0 }, null) => ToString (),
|
|
|
-
|
|
|
- // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
|
|
|
- (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
|
|
|
-
|
|
|
- // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
|
|
|
- (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
|
|
|
- string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
|
|
|
-
|
|
|
- // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
|
|
|
- (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
|
|
|
- $"#{R:X2}{G:X2}{B:X2}",
|
|
|
-
|
|
|
- // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
|
|
|
- ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
|
|
|
-
|
|
|
- // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
|
|
|
- (['g'], null) => ToString (),
|
|
|
-
|
|
|
- // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
|
|
|
- (['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
|
|
|
-
|
|
|
- // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
|
|
|
- (['d'], null) => $"rgb({R:D},{G:D},{B:D})",
|
|
|
-
|
|
|
- // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
|
|
|
- (['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
|
|
|
-
|
|
|
- // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
|
|
|
- ({ }, _) => string.Format (
|
|
|
- formatProvider ?? CultureInfo.InvariantCulture,
|
|
|
- CompositeFormat.Parse (formatString),
|
|
|
- R,
|
|
|
- G,
|
|
|
- B,
|
|
|
- A
|
|
|
- ),
|
|
|
- _ => throw new InvalidOperationException (
|
|
|
- $"Unable to create string from Color with value {Argb}, using format string {formatString}"
|
|
|
- )
|
|
|
- }
|
|
|
+ {
|
|
|
+ // Null or empty string and null formatProvider - Revert to 'g' case behavior
|
|
|
+ (null or { Length: 0 }, null) => ToString (),
|
|
|
+
|
|
|
+ // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
|
|
|
+ (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
|
|
|
+
|
|
|
+ // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
|
|
|
+ (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
|
|
|
+ string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
|
|
|
+
|
|
|
+ // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
|
|
|
+ (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
|
|
|
+ $"#{R:X2}{G:X2}{B:X2}",
|
|
|
+
|
|
|
+ // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
|
|
|
+ ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
|
|
|
+
|
|
|
+ // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
|
|
|
+ ( ['g'], null) => ToString (),
|
|
|
+
|
|
|
+ // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
|
|
|
+ ( ['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
|
|
|
+
|
|
|
+ // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
|
|
|
+ ( ['d'], null) => $"rgb({R:D},{G:D},{B:D})",
|
|
|
+
|
|
|
+ // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
|
|
|
+ ( ['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
|
|
|
+
|
|
|
+ // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
|
|
|
+ ({ }, _) => string.Format (
|
|
|
+ formatProvider ?? CultureInfo.InvariantCulture,
|
|
|
+ CompositeFormat.Parse (formatString),
|
|
|
+ R,
|
|
|
+ G,
|
|
|
+ B,
|
|
|
+ A
|
|
|
+ ),
|
|
|
+ _ => throw new InvalidOperationException (
|
|
|
+ $"Unable to create string from Color with value {Argb}, using format string {formatString}"
|
|
|
+ )
|
|
|
+ }
|
|
|
?? throw new InvalidOperationException (
|
|
|
$"Unable to create string from Color with value {Argb}, using format string {formatString}"
|
|
|
);
|
|
@@ -265,95 +265,99 @@ public readonly partial record struct Color
|
|
|
public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
|
|
|
{
|
|
|
return text switch
|
|
|
- {
|
|
|
- // Null string or empty span provided
|
|
|
- { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
|
|
|
- in text,
|
|
|
- "The text provided was null or empty.",
|
|
|
- in text
|
|
|
- ),
|
|
|
-
|
|
|
- // A valid ICustomColorFormatter was specified and the text wasn't null or empty
|
|
|
- { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
|
|
|
-
|
|
|
- // Input string is only whitespace
|
|
|
- { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
|
|
|
- in text,
|
|
|
- "The text provided consisted of only whitespace characters.",
|
|
|
- in text
|
|
|
- ),
|
|
|
-
|
|
|
- // Any string too short to possibly be any supported format.
|
|
|
- { Length: > 0 and < 3 } => throw new ColorParseException (
|
|
|
- in text,
|
|
|
- "Text was too short to be any possible supported format.",
|
|
|
- in text
|
|
|
- ),
|
|
|
-
|
|
|
- // The various hexadecimal cases
|
|
|
- ['#', ..] hexString => hexString switch
|
|
|
- {
|
|
|
- // #RGB
|
|
|
- ['#', var rChar, var gChar, var bChar] chars when chars [1..]
|
|
|
- .IsAllAsciiHexDigits () =>
|
|
|
- new Color (
|
|
|
- byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
|
|
|
- ),
|
|
|
-
|
|
|
- // #ARGB
|
|
|
- ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
|
|
|
- .IsAllAsciiHexDigits () =>
|
|
|
- new Color (
|
|
|
- byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
|
|
|
- ),
|
|
|
-
|
|
|
- // #RRGGBB
|
|
|
- [
|
|
|
- '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
|
|
|
- var b2Char
|
|
|
- ] chars when chars [1..].IsAllAsciiHexDigits () =>
|
|
|
- new Color (
|
|
|
- byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
|
|
|
- ),
|
|
|
-
|
|
|
- // #AARRGGBB
|
|
|
- [
|
|
|
- '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
|
|
|
- var g2Char, var b1Char, var b2Char
|
|
|
- ] chars when chars [1..].IsAllAsciiHexDigits () =>
|
|
|
- new Color (
|
|
|
- byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
|
|
|
- byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
|
|
|
- ),
|
|
|
- _ => throw new ColorParseException (
|
|
|
- in hexString,
|
|
|
- $"Color hex string {hexString} was not in a supported format",
|
|
|
- in hexString
|
|
|
- )
|
|
|
- },
|
|
|
-
|
|
|
- // rgb(r,g,b) or rgb(r,g,b,a)
|
|
|
- ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
|
|
|
-
|
|
|
- // rgba(r,g,b,a) or rgba(r,g,b)
|
|
|
- ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
|
|
|
-
|
|
|
- // Attempt to parse as a named color from the ColorName enum
|
|
|
- { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) =>
|
|
|
- new Color (colorName),
|
|
|
-
|
|
|
- // Any other input
|
|
|
- _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
|
|
|
- };
|
|
|
+ {
|
|
|
+ // Null string or empty span provided
|
|
|
+ { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
|
|
|
+ in text,
|
|
|
+ "The text provided was null or empty.",
|
|
|
+ in text
|
|
|
+ ),
|
|
|
+
|
|
|
+ // A valid ICustomColorFormatter was specified and the text wasn't null or empty
|
|
|
+ { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
|
|
|
+
|
|
|
+ // Input string is only whitespace
|
|
|
+ { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
|
|
|
+ in text,
|
|
|
+ "The text provided consisted of only whitespace characters.",
|
|
|
+ in text
|
|
|
+ ),
|
|
|
+
|
|
|
+ // Any string too short to possibly be any supported format.
|
|
|
+ { Length: > 0 and < 3 } => throw new ColorParseException (
|
|
|
+ in text,
|
|
|
+ "Text was too short to be any possible supported format.",
|
|
|
+ in text
|
|
|
+ ),
|
|
|
+
|
|
|
+ // The various hexadecimal cases
|
|
|
+ ['#', ..] hexString => hexString switch
|
|
|
+ {
|
|
|
+ // #RGB
|
|
|
+ ['#', var rChar, var gChar, var bChar] chars when chars [1..]
|
|
|
+ .IsAllAsciiHexDigits () =>
|
|
|
+ new Color (
|
|
|
+ byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
|
|
|
+ ),
|
|
|
+
|
|
|
+ // #ARGB
|
|
|
+ ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
|
|
|
+ .IsAllAsciiHexDigits () =>
|
|
|
+ new Color (
|
|
|
+ byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
|
|
|
+ ),
|
|
|
+
|
|
|
+ // #RRGGBB
|
|
|
+ [
|
|
|
+ '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
|
|
|
+ var b2Char
|
|
|
+ ] chars when chars [1..].IsAllAsciiHexDigits () =>
|
|
|
+ new Color (
|
|
|
+ byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
|
|
|
+ ),
|
|
|
+
|
|
|
+ // #AARRGGBB
|
|
|
+ [
|
|
|
+ '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
|
|
|
+ var g2Char, var b1Char, var b2Char
|
|
|
+ ] chars when chars [1..].IsAllAsciiHexDigits () =>
|
|
|
+ new Color (
|
|
|
+ byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
|
|
|
+ byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
|
|
|
+ ),
|
|
|
+ _ => throw new ColorParseException (
|
|
|
+ in hexString,
|
|
|
+ $"Color hex string {hexString} was not in a supported format",
|
|
|
+ in hexString
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
+ // rgb(r,g,b) or rgb(r,g,b,a)
|
|
|
+ ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
|
|
|
+
|
|
|
+ // rgba(r,g,b,a) or rgba(r,g,b)
|
|
|
+ ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
|
|
|
+
|
|
|
+ // Attempt to parse as a named color from the ColorStrings resources
|
|
|
+ { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text.ToString (), out Color color) =>
|
|
|
+ new Color (color),
|
|
|
+
|
|
|
+ // Attempt to parse as a named color from the ColorName enum
|
|
|
+ { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) =>
|
|
|
+ new Color (colorName),
|
|
|
+
|
|
|
+ // Any other input
|
|
|
+ _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
|
|
|
+ };
|
|
|
|
|
|
[Pure]
|
|
|
[SkipLocalsInit]
|
|
@@ -372,44 +376,44 @@ public readonly partial record struct Color
|
|
|
switch (rangeCount)
|
|
|
{
|
|
|
case 3:
|
|
|
- {
|
|
|
- // rgba(r,g,b)
|
|
|
- ParseRgbValues (
|
|
|
- in valuesSubstring,
|
|
|
- in valueRanges,
|
|
|
- in originalString,
|
|
|
- out ReadOnlySpan<char> rSpan,
|
|
|
- out ReadOnlySpan<char> gSpan,
|
|
|
- out ReadOnlySpan<char> bSpan
|
|
|
- );
|
|
|
-
|
|
|
- return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
|
|
|
- }
|
|
|
+ {
|
|
|
+ // rgba(r,g,b)
|
|
|
+ ParseRgbValues (
|
|
|
+ in valuesSubstring,
|
|
|
+ in valueRanges,
|
|
|
+ in originalString,
|
|
|
+ out ReadOnlySpan<char> rSpan,
|
|
|
+ out ReadOnlySpan<char> gSpan,
|
|
|
+ out ReadOnlySpan<char> bSpan
|
|
|
+ );
|
|
|
+
|
|
|
+ return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
|
|
|
+ }
|
|
|
case 4:
|
|
|
- {
|
|
|
- // rgba(r,g,b,a)
|
|
|
- ParseRgbValues (
|
|
|
- in valuesSubstring,
|
|
|
- in valueRanges,
|
|
|
- in originalString,
|
|
|
- out ReadOnlySpan<char> rSpan,
|
|
|
- out ReadOnlySpan<char> gSpan,
|
|
|
- out ReadOnlySpan<char> bSpan
|
|
|
- );
|
|
|
- ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
|
|
|
-
|
|
|
- if (!aSpan.IsAllAsciiDigits ())
|
|
|
{
|
|
|
- throw new ColorParseException (
|
|
|
- in originalString,
|
|
|
- "Value was not composed entirely of decimal digits.",
|
|
|
- in aSpan,
|
|
|
- nameof (A)
|
|
|
- );
|
|
|
+ // rgba(r,g,b,a)
|
|
|
+ ParseRgbValues (
|
|
|
+ in valuesSubstring,
|
|
|
+ in valueRanges,
|
|
|
+ in originalString,
|
|
|
+ out ReadOnlySpan<char> rSpan,
|
|
|
+ out ReadOnlySpan<char> gSpan,
|
|
|
+ out ReadOnlySpan<char> bSpan
|
|
|
+ );
|
|
|
+ ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
|
|
|
+
|
|
|
+ if (!aSpan.IsAllAsciiDigits ())
|
|
|
+ {
|
|
|
+ throw new ColorParseException (
|
|
|
+ in originalString,
|
|
|
+ "Value was not composed entirely of decimal digits.",
|
|
|
+ in aSpan,
|
|
|
+ nameof (A)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
|
|
|
}
|
|
|
-
|
|
|
- return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
|
|
|
- }
|
|
|
default:
|
|
|
throw new ColorParseException (
|
|
|
in originalString,
|
|
@@ -585,11 +589,14 @@ public readonly partial record struct Color
|
|
|
[SkipLocalsInit]
|
|
|
public override string ToString ()
|
|
|
{
|
|
|
- // If Values has an exact match with a named color (in _colorNames), use that.
|
|
|
- return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
|
|
|
- ? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
|
|
|
- : // Otherwise return as an RGB hex value.
|
|
|
- $"#{R:X2}{G:X2}{B:X2}";
|
|
|
+ string? name = ColorStrings.GetW3CColorName (this);
|
|
|
+
|
|
|
+ if (name is { })
|
|
|
+ {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $"#{R:X2}{G:X2}{B:X2}";
|
|
|
}
|
|
|
|
|
|
/// <summary>Converts the provided string to a new <see cref="Color"/> instance.</summary>
|