@@ -21,7 +21,8 @@ namespace Terminal.Gui.Configuration;
2121internal class RuneJsonConverter : JsonConverter < Rune >
2222{
2323 /// <summary>
24- /// Parses a configuration string using the same rules as <see cref="Read"/>.
24+ /// Parses a configuration string using the same rules as <see cref="Read"/> for a JSON string token.
25+ /// Does not round-trip through <see cref="JsonSerializer"/> (that path is trim-unsafe on NativeAOT).
2526 /// </summary>
2627 internal static bool TryParse ( string ? value , out Rune result )
2728 {
@@ -34,15 +35,7 @@ internal static bool TryParse (string? value, out Rune result)
3435
3536 try
3637 {
37- byte [ ] utf8 = JsonSerializer . SerializeToUtf8Bytes ( value ) ;
38- Utf8JsonReader reader = new ( utf8 ) ;
39-
40- if ( ! reader . Read ( ) )
41- {
42- return false ;
43- }
44-
45- result = new RuneJsonConverter ( ) . Read ( ref reader , typeof ( Rune ) , JsonSerializerOptions . Default ) ;
38+ result = ParseString ( value ) ;
4639
4740 return true ;
4841 }
@@ -57,99 +50,7 @@ public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSe
5750 switch ( reader . TokenType )
5851 {
5952 case JsonTokenType . String :
60- {
61- string ? value = reader . GetString ( ) ;
62- int first = RuneExtensions . MaxUnicodeCodePoint + 1 ;
63- int second = RuneExtensions . MaxUnicodeCodePoint + 1 ;
64-
65- if ( value is { } && ( value . StartsWith ( "U+" , StringComparison . OrdinalIgnoreCase )
66- || value . StartsWith ( "\\ U" , StringComparison . OrdinalIgnoreCase ) ) )
67- {
68- // Handle encoded single char, surrogate pair, or combining mark + char
69- uint [ ] codePoints = Regex . Matches ( value , @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})" )
70- . Select (
71- match => uint . Parse (
72- match . Groups [ 1 ] . Value ,
73- NumberStyles . HexNumber
74- )
75- )
76- . ToArray ( ) ;
77-
78- if ( codePoints . Length is 0 or > 2 )
79- {
80- throw new JsonException ( $ "{ value } : Invalid Rune.") ;
81- }
82-
83- if ( codePoints . Length > 0 )
84- {
85- first = ( int ) codePoints [ 0 ] ;
86- }
87-
88- if ( codePoints . Length == 2 )
89- {
90- second = ( int ) codePoints [ 1 ] ;
91- }
92- }
93- else
94- {
95- // Handle single character, surrogate pair, or combining mark + char
96- if ( value is { Length : 0 or > 2 } )
97- {
98- throw new JsonException ( $ "{ value } : Invalid Rune") ;
99- }
100-
101- if ( value is { Length : > 0 } )
102- {
103- first = value [ 0 ] ;
104- }
105-
106- if ( value is { Length : 2 } )
107- {
108- second = value [ 1 ] ;
109- }
110- }
111-
112- Rune result ;
113-
114- if ( second == RuneExtensions . MaxUnicodeCodePoint + 1 )
115- {
116- // Single codepoint
117- if ( ! Rune . TryCreate ( first , out result ) )
118- {
119- throw new JsonException ( $ "{ value } : Invalid Rune") ;
120- }
121-
122- return result ;
123- }
124-
125- // Surrogate pair?
126- if ( Rune . TryCreate ( ( char ) first , ( char ) second , out result ) )
127- {
128- return result ;
129- }
130-
131- if ( ! Rune . IsValid ( second ) )
132- {
133- throw new JsonException ( $ "{ value } : Invalid Rune. The second codepoint is not valid: { second } .") ;
134- }
135-
136- var cm = new Rune ( second ) ;
137-
138- if ( ! cm . IsCombiningMark ( ) )
139- {
140- throw new JsonException ( $ "{ value } : Invalid Rune. The second codepoint is not a combining mark: { cm } .") ;
141- }
142-
143- // not a surrogate pair, so a combining mark + char?
144- string combined = string . Concat ( ( char ) first , ( char ) second ) . Normalize ( ) ;
145-
146- if ( ! Rune . IsValid ( combined [ 0 ] ) )
147- {
148- throw new JsonException ( $ "{ value } : Invalid combined Rune.") ;
149- }
150-
151- return new ( combined [ 0 ] ) ;
152- }
53+ return ParseString ( reader . GetString ( ) ) ;
15354 case JsonTokenType . Number :
15455 {
15556 uint num = reader . GetUInt32 ( ) ;
@@ -182,5 +83,102 @@ public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOpt
18283 writer . WriteStringValue ( value . ToString ( ) ) ;
18384 }
18485 }
86+
87+ /// <summary>
88+ /// Parses a glyph, U+hex, or \u string using the same rules as a JSON string token in <see cref="Read"/>.
89+ /// </summary>
90+ private static Rune ParseString ( string ? value )
91+ {
92+ int first = RuneExtensions . MaxUnicodeCodePoint + 1 ;
93+ int second = RuneExtensions . MaxUnicodeCodePoint + 1 ;
94+
95+ if ( value is { } && ( value . StartsWith ( "U+" , StringComparison . OrdinalIgnoreCase )
96+ || value . StartsWith ( "\\ U" , StringComparison . OrdinalIgnoreCase ) ) )
97+ {
98+ // Handle encoded single char, surrogate pair, or combining mark + char
99+ uint [ ] codePoints = Regex . Matches ( value , @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})" )
100+ . Select (
101+ match => uint . Parse (
102+ match . Groups [ 1 ] . Value ,
103+ NumberStyles . HexNumber
104+ )
105+ )
106+ . ToArray ( ) ;
107+
108+ if ( codePoints . Length is 0 or > 2 )
109+ {
110+ throw new JsonException ( $ "{ value } : Invalid Rune.") ;
111+ }
112+
113+ if ( codePoints . Length > 0 )
114+ {
115+ first = ( int ) codePoints [ 0 ] ;
116+ }
117+
118+ if ( codePoints . Length == 2 )
119+ {
120+ second = ( int ) codePoints [ 1 ] ;
121+ }
122+ }
123+ else
124+ {
125+ // Handle single character, surrogate pair, or combining mark + char
126+ if ( value is { Length : 0 or > 2 } )
127+ {
128+ throw new JsonException ( $ "{ value } : Invalid Rune") ;
129+ }
130+
131+ if ( value is { Length : > 0 } )
132+ {
133+ first = value [ 0 ] ;
134+ }
135+
136+ if ( value is { Length : 2 } )
137+ {
138+ second = value [ 1 ] ;
139+ }
140+ }
141+
142+ Rune result ;
143+
144+ if ( second == RuneExtensions . MaxUnicodeCodePoint + 1 )
145+ {
146+ // Single codepoint
147+ if ( ! Rune . TryCreate ( first , out result ) )
148+ {
149+ throw new JsonException ( $ "{ value } : Invalid Rune") ;
150+ }
151+
152+ return result ;
153+ }
154+
155+ // Surrogate pair?
156+ if ( Rune . TryCreate ( ( char ) first , ( char ) second , out result ) )
157+ {
158+ return result ;
159+ }
160+
161+ if ( ! Rune . IsValid ( second ) )
162+ {
163+ throw new JsonException ( $ "{ value } : Invalid Rune. The second codepoint is not valid: { second } .") ;
164+ }
165+
166+ var cm = new Rune ( second ) ;
167+
168+ if ( ! cm . IsCombiningMark ( ) )
169+ {
170+ throw new JsonException ( $ "{ value } : Invalid Rune. The second codepoint is not a combining mark: { cm } .") ;
171+ }
172+
173+ // not a surrogate pair, so a combining mark + char?
174+ string combined = string . Concat ( ( char ) first , ( char ) second ) . Normalize ( ) ;
175+
176+ if ( ! Rune . IsValid ( combined [ 0 ] ) )
177+ {
178+ throw new JsonException ( $ "{ value } : Invalid combined Rune.") ;
179+ }
180+
181+ return new ( combined [ 0 ] ) ;
182+ }
185183}
186184#pragma warning restore 1591
0 commit comments