@@ -11,6 +11,11 @@ namespace MS.Internal;
1111/// </summary>
1212internal static partial class Parsers
1313{
14+ /// <summary>
15+ /// The prefix for any <see cref="ColorKind.ContextColor"/> format.
16+ /// </summary>
17+ private const string ContextColor = "ContextColor " ;
18+
1419 /// <summary>
1520 /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.
1621 /// </summary>
@@ -83,75 +88,24 @@ private static Color ParseHexColor(ReadOnlySpan<char> trimmedColor)
8388 return Color . FromArgb ( ( byte ) a , ( byte ) r , ( byte ) g , ( byte ) b ) ;
8489 }
8590
86- internal const string ContextColor = "ContextColor " ;
87-
88- private static Color ParseContextColor ( ReadOnlySpan < char > trimmedColor , IFormatProvider formatProvider , ITypeDescriptorContext context )
89- {
90- if ( ! trimmedColor . StartsWith ( ContextColor , StringComparison . OrdinalIgnoreCase ) )
91- throw new FormatException ( SR . Parsers_IllegalToken ) ;
92-
93- // Skip "ContextColor " prefix
94- ReadOnlySpan < char > tokens = trimmedColor . Slice ( ContextColor . Length ) . Trim ( ) ;
95-
96- // Check whether the format is at least e.g. "file://profile.icc 1.0"
97- Span < Range > splitSegments = stackalloc Range [ 4 ] ;
98-
99- if ( tokens . Split ( splitSegments , ' ' ) < 2 )
100- throw new FormatException ( SR . Parsers_IllegalToken ) ;
101-
102- // Retrieve "file://profile.icc" part
103- string profileString = tokens [ splitSegments [ 0 ] ] . ToString ( ) ;
104- // Skip "file://profile.icc" part
105- ReadOnlySpan < char > colorPart = tokens . Slice ( profileString . Length ) ;
106-
107- // Retrieve alpha value
108- ValueTokenizerHelper tokenizer = new ( colorPart , formatProvider ) ;
109- float alpha = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
110-
111- // While we do not support colors with more than 8 channels, the underlying initialization code will take care of it,
112- // so here we just silently count the color values and let it throw NotImplementedException in the color translation code-path.
113- int numTokens = colorPart . Count ( ',' ) ;
114- Span < float > values = stackalloc float [ numTokens ] ;
115-
116- for ( int i = 0 ; i < values . Length ; i ++ )
117- values [ i ] = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
118-
119- UriHolder uriHolder = TypeConverterHelper . GetUriFromUriContext ( context , profileString ) ;
120- Uri profileUri = uriHolder . BaseUri is not null ? new Uri ( uriHolder . BaseUri , uriHolder . OriginalUri ) : uriHolder . OriginalUri ;
121-
122- // If the number of color values found does not match the number of channels in the profile, FromAValues will throw
123- return Color . FromAValues ( alpha , values , profileUri ) ;
124- }
125-
126- private static Color ParseScRgbColor ( ReadOnlySpan < char > trimmedColor , IFormatProvider formatProvider )
91+ /// <summary>
92+ /// Matches the input string against known color formats.
93+ /// </summary>
94+ /// <param name="colorString">The color string to categorize.</param>
95+ /// <returns>A <see cref="ColorKind"/> specifying the input string format.</returns>
96+ /// <remarks><see cref="ColorKind.KnownColor"/> is used as a fallback value.</remarks>
97+ private static ColorKind MatchColor ( ReadOnlySpan < char > colorString )
12798 {
128- if ( ! trimmedColor . StartsWith ( "sc#" , StringComparison . Ordinal ) )
129- throw new FormatException ( SR . Parsers_IllegalToken ) ;
99+ if ( ( colorString . Length is 4 or 5 or 7 or 9 ) && ( colorString [ 0 ] == '#' ) )
100+ return ColorKind . NumericColor ;
130101
131- // Skip prefix ( sc#)
132- ReadOnlySpan < char > tokens = trimmedColor . Slice ( 3 ) ;
102+ if ( colorString . StartsWith ( " sc#" , StringComparison . Ordinal ) )
103+ return ColorKind . ScRgbColor ;
133104
134- // The tokenizer helper will tokenize a list based on the IFormatProvider.
135- ValueTokenizerHelper tokenizer = new ( tokens , formatProvider ) ;
136- Span < float > values = stackalloc float [ 4 ] ;
105+ if ( colorString . StartsWith ( ContextColor , StringComparison . OrdinalIgnoreCase ) )
106+ return ColorKind . ContextColor ;
137107
138- for ( int i = 0 ; i < 3 ; i ++ )
139- values [ i ] = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
140-
141- if ( tokenizer . NextToken ( ) )
142- {
143- values [ 3 ] = float . Parse ( tokenizer . GetCurrentToken ( ) , formatProvider ) ;
144-
145- // We should be out of tokens at this point
146- if ( tokenizer . NextToken ( ) )
147- {
148- throw new FormatException ( SR . Parsers_IllegalToken ) ;
149- }
150-
151- return Color . FromScRgb ( values [ 0 ] , values [ 1 ] , values [ 2 ] , values [ 3 ] ) ;
152- }
153-
154- return Color . FromScRgb ( 1.0f , values [ 0 ] , values [ 1 ] , values [ 2 ] ) ;
108+ return ColorKind . KnownColor ;
155109 }
156110
157111 /// <summary>
@@ -173,7 +127,7 @@ internal static Color ParseColor(string color, IFormatProvider formatProvider)
173127 internal static Color ParseColor ( string color , IFormatProvider formatProvider , ITypeDescriptorContext context )
174128 {
175129 ReadOnlySpan < char > trimmedColor = color . AsSpan ( ) . Trim ( ) ;
176- ColorKind colorKind = KnownColors . MatchColor ( trimmedColor ) ;
130+ ColorKind colorKind = MatchColor ( trimmedColor ) ;
177131
178132 // Check that our assumption stays true
179133 Debug . Assert ( colorKind is ColorKind . NumericColor or ColorKind . ContextColor or ColorKind . ScRgbColor or ColorKind . KnownColor ) ;
@@ -193,18 +147,16 @@ internal static Color ParseColor(string color, IFormatProvider formatProvider, I
193147 }
194148
195149 /// <summary>
196- /// ParseBrush
197- /// <param name="brush"> string with brush description </param>
198- /// <param name="formatProvider">IFormatProvider for processing string</param>
199- /// <param name="context">ITypeDescriptorContext</param>
150+ /// Parses a brush from the <paramref name="brush"/> string. This is in essence same as <see cref="ParseColor"/>,
151+ /// but instead of getting a <see cref="Color"/> out, you get a <see cref="SolidColorBrush"/> instance.
200152 /// </summary>
201153 internal static Brush ParseBrush ( string brush , IFormatProvider formatProvider , ITypeDescriptorContext context )
202154 {
203155 ReadOnlySpan < char > trimmedColor = brush . AsSpan ( ) . Trim ( ) ;
204156 if ( trimmedColor . IsEmpty )
205157 throw new FormatException ( SR . Parser_Empty ) ;
206158
207- ColorKind colorKind = KnownColors . MatchColor ( trimmedColor ) ;
159+ ColorKind colorKind = MatchColor ( trimmedColor ) ;
208160
209161 // Check that our assumption stays true
210162 Debug . Assert ( colorKind is ColorKind . NumericColor or ColorKind . ContextColor or ColorKind . ScRgbColor or ColorKind . KnownColor ) ;
@@ -227,6 +179,74 @@ internal static Brush ParseBrush(string brush, IFormatProvider formatProvider, I
227179 return solidColorBrush is not null ? solidColorBrush : throw new FormatException ( SR . Parsers_IllegalToken ) ;
228180 }
229181
182+ private static Color ParseContextColor ( ReadOnlySpan < char > trimmedColor , IFormatProvider formatProvider , ITypeDescriptorContext context )
183+ {
184+ if ( ! trimmedColor . StartsWith ( ContextColor , StringComparison . OrdinalIgnoreCase ) )
185+ throw new FormatException ( SR . Parsers_IllegalToken ) ;
186+
187+ // Skip "ContextColor " prefix
188+ ReadOnlySpan < char > tokens = trimmedColor . Slice ( ContextColor . Length ) . Trim ( ) ;
189+
190+ // Check whether the format is at least e.g. "file://profile.icc 1.0"
191+ Span < Range > splitSegments = stackalloc Range [ 4 ] ;
192+
193+ if ( tokens . Split ( splitSegments , ' ' ) < 2 )
194+ throw new FormatException ( SR . Parsers_IllegalToken ) ;
195+
196+ // Retrieve "file://profile.icc" part
197+ string profileString = tokens [ splitSegments [ 0 ] ] . ToString ( ) ;
198+ // Skip "file://profile.icc" part
199+ ReadOnlySpan < char > colorPart = tokens . Slice ( profileString . Length ) ;
200+
201+ // Retrieve alpha value
202+ ValueTokenizerHelper tokenizer = new ( colorPart , formatProvider ) ;
203+ float alpha = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
204+
205+ // While we do not support colors with more than 8 channels, the underlying initialization code will take care of it,
206+ // so here we just silently count the color values and let it throw NotImplementedException in the color translation code-path.
207+ int numTokens = colorPart . Count ( ',' ) ;
208+ Span < float > values = stackalloc float [ numTokens ] ;
209+
210+ for ( int i = 0 ; i < values . Length ; i ++ )
211+ values [ i ] = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
212+
213+ UriHolder uriHolder = TypeConverterHelper . GetUriFromUriContext ( context , profileString ) ;
214+ Uri profileUri = uriHolder . BaseUri is not null ? new Uri ( uriHolder . BaseUri , uriHolder . OriginalUri ) : uriHolder . OriginalUri ;
215+
216+ // If the number of color values found does not match the number of channels in the profile, FromAValues will throw
217+ return Color . FromAValues ( alpha , values , profileUri ) ;
218+ }
219+
220+ private static Color ParseScRgbColor ( ReadOnlySpan < char > trimmedColor , IFormatProvider formatProvider )
221+ {
222+ if ( ! trimmedColor . StartsWith ( "sc#" , StringComparison . Ordinal ) )
223+ throw new FormatException ( SR . Parsers_IllegalToken ) ;
224+
225+ // Skip prefix (sc#)
226+ ReadOnlySpan < char > tokens = trimmedColor . Slice ( 3 ) ;
227+
228+ // The tokenizer helper will tokenize a list based on the IFormatProvider.
229+ ValueTokenizerHelper tokenizer = new ( tokens , formatProvider ) ;
230+ Span < float > values = stackalloc float [ 4 ] ;
231+
232+ for ( int i = 0 ; i < 3 ; i ++ )
233+ values [ i ] = float . Parse ( tokenizer . NextTokenRequired ( ) , formatProvider ) ;
234+
235+ if ( tokenizer . NextToken ( ) )
236+ {
237+ values [ 3 ] = float . Parse ( tokenizer . GetCurrentToken ( ) , formatProvider ) ;
238+
239+ // We should be out of tokens at this point
240+ if ( tokenizer . NextToken ( ) )
241+ {
242+ throw new FormatException ( SR . Parsers_IllegalToken ) ;
243+ }
244+
245+ return Color . FromScRgb ( values [ 0 ] , values [ 1 ] , values [ 2 ] , values [ 3 ] ) ;
246+ }
247+
248+ return Color . FromScRgb ( 1.0f , values [ 0 ] , values [ 1 ] , values [ 2 ] ) ;
249+ }
230250
231251 /// <summary>
232252 /// ParseTransform - parse a Transform from a string
0 commit comments