using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; namespace Avalonia.Base.UnitTests.Media.TextFormatting { internal static class UnicodeEnumsGenerator { public static List CreateScriptEnum() { var entries = new List { new DataEntry("Unknown", "Zzzz", string.Empty), new DataEntry("Common", "Zyyy", string.Empty), new DataEntry("Inherited", "Zinh", string.Empty) }; ParseDataEntries("# Script (sc)", entries); using (var stream = File.Create("Generated\\Script.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum Script"); writer.WriteLine(" {"); foreach (var entry in entries) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } return entries; } public static List CreateGeneralCategoryEnum() { var entries = new List { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") }; ParseDataEntries("# General_Category (gc)", entries); using (var stream = File.Create("Generated\\GeneralCategory.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum GeneralCategory"); writer.WriteLine(" {"); foreach (var entry in entries) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } return entries; } public static List CreateGraphemeBreakTypeEnum() { var entries = new List { new DataEntry("Other", "XX", string.Empty) }; ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries); using (var stream = File.Create("Generated\\GraphemeBreakClass.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum GraphemeBreakClass"); writer.WriteLine(" {"); foreach (var entry in entries) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" ExtendedPictographic"); writer.WriteLine(" }"); writer.WriteLine("}"); } return entries; } private static List GenerateBreakPairTable() { var rows = new List(); using (var stream = typeof(UnicodeEnumsGenerator).Assembly.GetManifestResourceStream( "Avalonia.Base.UnitTests.Media.TextFormatting.BreakPairTable.txt")) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { var line = reader.ReadLine(); var columns = line.Split('\t'); rows.Add(columns); } } using (var stream = File.Create("Generated\\BreakPairTable.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" internal static class BreakPairTable"); writer.WriteLine(" {"); writer.WriteLine(" private static readonly byte[][] s_breakPairTable = "); writer.WriteLine(" {"); for (var i = 1; i < rows.Count; i++) { writer.Write(" new byte[] {"); writer.Write($"{GetBreakPairType(rows[i][1])}"); for (var index = 2; index < rows[i].Length; index++) { var column = rows[i][index]; writer.Write($",{GetBreakPairType(column)}"); } writer.Write("},"); writer.Write(Environment.NewLine); } writer.WriteLine(" };"); writer.WriteLine(); writer.WriteLine(" public static PairBreakType Map(LineBreakClass first, LineBreakClass second)"); writer.WriteLine(" {"); writer.WriteLine(" return (PairBreakType)s_breakPairTable[(int)first][(int)second];"); writer.WriteLine(" }"); writer.WriteLine(" }"); writer.WriteLine(); writer.WriteLine(" internal enum PairBreakType : byte"); writer.WriteLine(" {"); writer.WriteLine(" DI = 0, // Direct break opportunity"); writer.WriteLine(" IN = 1, // Indirect break opportunity"); writer.WriteLine(" CI = 2, // Indirect break opportunity for combining marks"); writer.WriteLine(" CP = 3, // Prohibited break for combining marks"); writer.WriteLine(" PR = 4 // Prohibited break"); writer.WriteLine(" }"); writer.WriteLine("}"); } return rows[0].Where(x => !string.IsNullOrEmpty(x)).ToList(); } public const byte DI_BRK = 0; // Direct break opportunity public const byte IN_BRK = 1; // Indirect break opportunity public const byte CI_BRK = 2; // Indirect break opportunity for combining marks public const byte CP_BRK = 3; // Prohibited break for combining marks public const byte PR_BRK = 4; // Prohibited break private static byte GetBreakPairType(string type) { switch (type) { case "_": return DI_BRK; case "%": return IN_BRK; case "#": return CI_BRK; case "@": return CP_BRK; case "^": return PR_BRK; default: return byte.MaxValue; } } public static List CreateLineBreakClassEnum() { var usedLineBreakClasses = GenerateBreakPairTable(); var entries = new List { new DataEntry("Unknown", "XX", string.Empty) }; ParseDataEntries("# Line_Break (lb)", entries); var orderedLineBreakEntries = new Dictionary(); foreach (var tag in usedLineBreakClasses) { var entry = entries.Single(x => x.Tag == tag); orderedLineBreakEntries.Add(tag, entry); } foreach (var entry in entries) { if (orderedLineBreakEntries.ContainsKey(entry.Tag)) { continue; } orderedLineBreakEntries.Add(entry.Tag, entry); } using (var stream = File.Create("Generated\\LineBreakClass.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum LineBreakClass"); writer.WriteLine(" {"); foreach (var entry in orderedLineBreakEntries.Values) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } return orderedLineBreakEntries.Values.ToList(); } public static List CreateBidiClassEnum() { var entries = new List { new DataEntry("Left_To_Right", "L", string.Empty) }; ParseDataEntries("# Bidi_Class (bc)", entries); using (var stream = File.Create("Generated\\BidiClass.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum BidiClass"); writer.WriteLine(" {"); foreach (var entry in entries) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } return entries; } public static List CreateBiDiPairedBracketTypeEnum() { var entries = new List { new DataEntry("None", "n", string.Empty) }; ParseDataEntries("# Bidi_Paired_Bracket_Type (bpt)", entries); using (var stream = File.Create("Generated\\BidiPairedBracketType.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" public enum BidiPairedBracketType"); writer.WriteLine(" {"); foreach (var entry in entries) { writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } return entries; } public static void CreatePropertyValueAliasHelper(UnicodeDataEntries unicodeDataEntries, BiDiDataEntries biDiDataEntries) { using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("using System.Collections.Generic;"); writer.WriteLine(); writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); writer.WriteLine(" internal static class PropertyValueAliasHelper"); writer.WriteLine(" {"); WritePropertyValueAliasGetTag(writer, unicodeDataEntries.Scripts, "Script", "Zzzz"); WritePropertyValueAlias(writer, unicodeDataEntries.Scripts, "Script", "Unknown"); WritePropertyValueAlias(writer, unicodeDataEntries.GeneralCategories, "GeneralCategory", "Other"); WritePropertyValueAlias(writer, unicodeDataEntries.LineBreakClasses, "LineBreakClass", "Unknown"); WritePropertyValueAlias(writer, biDiDataEntries.PairedBracketTypes, "BiDiPairedBracketType", "None"); WritePropertyValueAlias(writer, biDiDataEntries.BiDiClasses, "BiDiClass", "LeftToRight"); writer.WriteLine(" }"); writer.WriteLine("}"); } } public static void ParseDataEntries(string property, List entries) { using (var client = new HttpClient()) { var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt"); using (var result = client.GetAsync(url).GetAwaiter().GetResult()) { if (!result.IsSuccessStatusCode) { return; } using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { var line = reader.ReadLine(); if (string.IsNullOrEmpty(line)) { continue; } if (line != property) { continue; } reader.ReadLine(); break; } while (!reader.EndOfStream) { var line = reader.ReadLine(); if (string.IsNullOrEmpty(line) || line.StartsWith("#")) { break; } var elements = line.Split(';'); var tag = elements[1].Trim(); elements = elements[2].Split('#'); var name = elements[0].Trim(); if (entries.Any(x => x.Name == name)) { continue; } var comment = string.Empty; if (elements.Length > 1) { comment = elements[1]; } var entry = new DataEntry(name, tag, comment); entries.Add(entry); } } } } } private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable entries, string typeName, string defaultValue) { writer.WriteLine( $" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = "); writer.WriteLine($" new Dictionary<{typeName}, string>{{"); foreach (var entry in entries) { writer.WriteLine($" {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},"); } writer.WriteLine(" };"); writer.WriteLine(); writer.WriteLine($" public static string GetTag({typeName} {typeName.ToLower()})"); writer.WriteLine(" {"); writer.WriteLine($" if(!s_{typeName.ToLower()}ToTag.ContainsKey({typeName.ToLower()}))"); writer.WriteLine(" {"); writer.WriteLine($" return \"{defaultValue}\";"); writer.WriteLine(" }"); writer.WriteLine($" return s_{typeName.ToLower()}ToTag[{typeName.ToLower()}];"); writer.WriteLine(" }"); writer.WriteLine(); } private static void WritePropertyValueAlias(TextWriter writer, IEnumerable entries, string typeName, string defaultValue) { writer.WriteLine($" private static readonly Dictionary s_tagTo{typeName} = "); writer.WriteLine($" new Dictionary{{"); foreach (var entry in entries) { writer.WriteLine($" {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},"); } writer.WriteLine(" };"); writer.WriteLine(); writer.WriteLine($" public static {typeName} Get{typeName}(string tag)"); writer.WriteLine(" {"); writer.WriteLine($" if(!s_tagTo{typeName}.ContainsKey(tag))"); writer.WriteLine(" {"); writer.WriteLine($" return {typeName}.{defaultValue};"); writer.WriteLine(" }"); writer.WriteLine($" return s_tagTo{typeName}[tag];"); writer.WriteLine(" }"); writer.WriteLine(); } } public readonly struct DataEntry { public DataEntry(string name, string tag, string comment) { Name = name; Tag = tag; Comment = comment; } public string Name { get; } public string Tag { get; } public string Comment { get; } } }