123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- using System.Globalization;
- using System.Text.Json;
- using System.Text.Json.Serialization;
- using System.Text.RegularExpressions;
- namespace Terminal.Gui;
- /// <summary>
- /// Json converter for <see cref="Rune"/>. Supports Json converter for <see cref="Rune"/>. Supports A string as
- /// one of: - unicode char (e.g. "☑") - U+hex format (e.g. "U+2611") - \u format (e.g. "\\u2611") A number - The
- /// unicode code in decimal
- /// </summary>
- internal class RuneJsonConverter : JsonConverter<Rune>
- {
- public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- switch (reader.TokenType)
- {
- case JsonTokenType.String:
- {
- string value = reader.GetString ();
- int first = RuneExtensions.MaxUnicodeCodePoint + 1;
- int second = RuneExtensions.MaxUnicodeCodePoint + 1;
- if (value.StartsWith ("U+", StringComparison.OrdinalIgnoreCase)
- || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase))
- {
- // Handle encoded single char, surrogate pair, or combining mark + char
- uint [] codePoints = Regex.Matches (value, @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
- .Select (
- match => uint.Parse (
- match.Groups [1].Value,
- NumberStyles.HexNumber
- )
- )
- .ToArray ();
- if (codePoints.Length == 0 || codePoints.Length > 2)
- {
- throw new JsonException ($"Invalid Rune: {value}.");
- }
- if (codePoints.Length > 0)
- {
- first = (int)codePoints [0];
- }
- if (codePoints.Length == 2)
- {
- second = (int)codePoints [1];
- }
- }
- else
- {
- // Handle single character, surrogate pair, or combining mark + char
- if (value.Length == 0 || value.Length > 2)
- {
- throw new JsonException ($"Invalid Rune: {value}.");
- }
- if (value.Length > 0)
- {
- first = value [0];
- }
- if (value.Length == 2)
- {
- second = value [1];
- }
- }
- Rune result;
- if (second == RuneExtensions.MaxUnicodeCodePoint + 1)
- {
- // Single codepoint
- if (!Rune.TryCreate (first, out result))
- {
- throw new JsonException ($"Invalid Rune: {value}.");
- }
- return result;
- }
- // Surrogate pair?
- if (Rune.TryCreate ((char)first, (char)second, out result))
- {
- return result;
- }
- if (!Rune.IsValid (second))
- {
- throw new JsonException ($"The second codepoint is not valid: {second} in ({value})");
- }
- var cm = new Rune (second);
- if (!cm.IsCombiningMark ())
- {
- throw new JsonException ($"The second codepoint is not a combining mark: {cm} in ({value})");
- }
- // not a surrogate pair, so a combining mark + char?
- string combined = string.Concat ((char)first, (char)second).Normalize ();
- if (!Rune.IsValid (combined [0]))
- {
- throw new JsonException ($"Invalid combined Rune ({value})");
- }
- return new Rune (combined [0]);
- }
- case JsonTokenType.Number:
- {
- uint num = reader.GetUInt32 ();
- if (Rune.IsValid (num))
- {
- return new Rune (num);
- }
- throw new JsonException ($"Invalid Rune (not a scalar Unicode value): {num}.");
- }
- default:
- throw new JsonException ($"Unexpected token when parsing Rune: {reader.TokenType}.");
- }
- }
- public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOptions options)
- {
- // HACK: Writes a JSON comment in addition to the glyph to ease debugging.
- // Technically, JSON comments are not valid, but we use relaxed decoding
- // (ReadCommentHandling = JsonCommentHandling.Skip)
- //writer.WriteCommentValue ($"(U+{value.Value:X8})");
- //var printable = value.MakePrintable ();
- //if (printable == Rune.ReplacementChar) {
- // writer.WriteStringValue (value.ToString ());
- //} else {
- // //writer.WriteRawValue ($"\"{value}\"");
- //}
- writer.WriteNumberValue (value.Value);
- }
- }
- #pragma warning restore 1591
|