-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgen_c_sharp.py
289 lines (239 loc) · 12.5 KB
/
gen_c_sharp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
from emoji_parser import EmojiParseResult, Emoji, Status, SkinTone, Group
import os
import re
from uharfbuzz import Face, Font, Buffer, ot_font_set_funcs, shape
import sys
class GenCSharp:
def __init__(self, fontPath: str, srcUrl: str):
# Load font:
with open(fontPath, 'rb') as fontfile:
self.fontdata = fontfile.read()
self.srcUrl = srcUrl
def __genCamelCaseName(self, emoji: Emoji) -> str:
name: str = "".join([s.capitalize() for s in emoji.searchTerms if s.isalnum()])
if emoji.eNumber:
name += "_" + emoji.eNumber.replace('.', '_')
return name
def __genSearchTerms(self, emoji: Emoji) -> str:
return "\"" + "\", \"".join(emoji.searchTerms) + "\""
def __genSkinTones(self, emoji: Emoji) -> str:
if len(emoji.skinTones) > 1 or len(emoji.skinTones) == 1 and emoji.skinTones[0] != SkinTone.NONE:
return "new Codepoint[] { SkinTones." + ", SkinTones.".join([self.__genSkinToneString(tone) for tone in emoji.skinTones if tone != SkinTone.NONE]) + " }"
else:
return "SingleEmoji.NoSkinTones"
def __genENumber(self, emoji: Emoji) -> float:
if emoji.eNumber and 'E' in emoji.eNumber:
return emoji.eNumber.replace('E', '')
return "1.0"
def __genSkinToneString(self, skinTone: SkinTone) -> str:
return "".join([s.lower().capitalize() for s in skinTone.name.split("_")])
def __genCodePoints(self, emoji: Emoji) -> str:
return ", ".join([hex(cp) for cp in emoji.codePoints])
def __genGroup(self, emoji: Emoji) -> str:
return "SkinTone." + ", SkinTone.".join([tone.name for tone in emoji.skinTones])
def __genMachinegeneratedHeader(self) -> str:
return ("\t// This file is machine-generated based on the official Unicode Consortium publication (" + self.srcUrl + ").\n"
"\t// See https://github.com/UWPX/Emoji-List-Parser for the generator.\n")
def genEmojiString(self, emoji: Emoji):
return ("\t\t/* " + emoji.emoji + " */\n"
"\t\tpublic static readonly SingleEmoji " + self.__genCamelCaseName(emoji) + " = new SingleEmoji(\n"
"\t\t\tsequence: new UnicodeSequence(new int[] { " + self.__genCodePoints(emoji) + " }),\n"
"\t\t\tname: \"" + emoji.name + "\",\n"
"\t\t\tsearchTerms: new string[] { " + self.__genSearchTerms(emoji) + " },\n"
"\t\t\tskinTones: " + self.__genSkinTones(emoji) + ",\n"
"\t\t\teNumber: " + self.__genENumber(emoji) + ",\n"
"\t\t\tgroup: Group." + emoji.group.name + ",\n"
"\t\t\tsubgroup: Subgroups." + self.__genSubGroupName(emoji.subgroup) + ",\n"
"\t\t\thasGlyph: " + str(self.__isEmojiSupportedByFont(emoji)).lower() + ",\n"
"\t\t\tsortOrder: " + str(emoji.index) + "\n"
"\t\t);\n")
def genEmojiDeclarationsFile(self, result: EmojiParseResult):
print("Generating \"Emoji-Emojis.cs\"...")
outFile = self.__openFile("Emoji-Emojis.cs")
output = ("namespace NeoSmart.Unicode\n"
"{\n"
+ self.__genMachinegeneratedHeader()
+ "\tpublic static partial class Emoji\n"
"\t{\n")
output += "\n".join([self.genEmojiString(e) for e in result.emoji if e.status == Status.COMPONENT or e.status == Status.FULLY_QUALIFIED])
output += "\t}\n}\n"
self.__writeAndCloseFile(outFile, output)
print("Finished generating \"Emoji-Emojis.cs\".")
def genEmojiAllFile(self, result: EmojiParseResult):
print("Generating \"Emoji-All.cs\"...")
outFile = self.__openFile("Emoji-All.cs")
output = ("using System.Collections.Generic;\n"
"\n"
"namespace NeoSmart.Unicode\n"
"{\n"
+ self.__genMachinegeneratedHeader()
+ "\tpublic static partial class Emoji\n"
"\t{\n"
"\t\t/// <summary>\n"
"\t\t/// A (sorted) enumeration of all emoji.\n"
"\t\t/// Only contains fully-qualified and component emoji.\n"
"\t\t/// <summary>\n")
output += self.__genSingleEmojiStart("All")
for e in result.emoji:
if e.status == Status.COMPONENT or e.status == Status.FULLY_QUALIFIED:
output += "\t\t\t/* " + e.emoji + " */ " + self.__genCamelCaseName(e) + ",\n"
output += "\t\t};\n\t}\n}\n"
self.__writeAndCloseFile(outFile, output)
print("Finished generating \"Emoji-All.cs\".")
def genEmojiGroupFile(self, result: EmojiParseResult, group: Group):
groupName = "".join([s.lower().capitalize() for s in group.name.split("_")])
print("Generating \"Emoji-" + groupName + ".cs\"...")
outFile = self.__openFile("Emoji-" + groupName + ".cs")
output = ("using System.Collections.Generic;\n"
"\n"
"namespace NeoSmart.Unicode\n"
"{\n"
+ self.__genMachinegeneratedHeader()
+ "\tpublic static partial class Emoji\n"
"\t{\n"
"\t\t/// <summary>\n"
"\t\t/// A (sorted) enumeration of all emoji in group: " + group.name + "\n"
"\t\t/// Only contains fully-qualified and component emoji.\n"
"\t\t/// <summary>\n")
output += self.__genSingleEmojiStart(groupName)
for e in result.emoji:
if (e.status == Status.COMPONENT or e.status == Status.FULLY_QUALIFIED) and e.group == group:
output += "\t\t\t/* " + e.emoji + " */ " + self.__genCamelCaseName(e) + ",\n"
output += "\t\t};\n\t}\n}\n"
self.__writeAndCloseFile(outFile, output)
print("Finished generating \"Emoji-" + groupName + ".cs\".")
def __isEmojiSupportedByFont(self, emoji: Emoji) -> bool:
# Load font (has to be done for call):
face = Face(self.fontdata)
font = Font(face)
upem = face.upem
font.scale = (upem, upem)
ot_font_set_funcs(font)
# Create text buffer:
buf = Buffer()
buf.add_str(emoji.emoji)
buf.guess_segment_properties()
# Shape text:
features = {"kern": True, "liga": True}
shape(font, buf, features)
infos = buf.glyph_infos
# Remove all variant selectors:
while len(infos) > 0 and infos[-1].codepoint == 3:
infos = infos[:-1]
# Filter empty:
if len(infos) <= 0:
return False
# Remove uncombined ending with skin tone like "👭🏿":
lastCp = infos[-1].codepoint
if lastCp == 1076 or lastCp == 1079 or lastCp == 1082 or lastCp == 1085 or lastCp == 1088:
return False
# If there is a code point 0 => Emoji not fully supported by font:
return all(info.codepoint != 0 and info.codepoint != 3 for info in infos)
def testIsEmojiSupportedByFont(self):
self.__testEvalIsEmojiSupportedByFont("☹️", True)
self.__testEvalIsEmojiSupportedByFont("👨👨👧👦", True)
self.__testEvalIsEmojiSupportedByFont("🐱👤", True)
self.__testEvalIsEmojiSupportedByFont("❤", True)
self.__testEvalIsEmojiSupportedByFont("🐱👤", True)
self.__testEvalIsEmojiSupportedByFont("☺️", True)
self.__testEvalIsEmojiSupportedByFont("🕵🏻♀️", True)
self.__testEvalIsEmojiSupportedByFont("⛷️", True)
self.__testEvalIsEmojiSupportedByFont("👁️🗨️", True)
self.__testEvalIsEmojiSupportedByFont("🧔🏼", True)
self.__testEvalIsEmojiSupportedByFont("", False)
self.__testEvalIsEmojiSupportedByFont("🧏🏻♀️", True)
self.__testEvalIsEmojiSupportedByFont("🧍🏼", True)
self.__testEvalIsEmojiSupportedByFont("🧍🏿♀", True)
self.__testEvalIsEmojiSupportedByFont("👩🏿🦽", True)
self.__testEvalIsEmojiSupportedByFont("👩🏽🤝👨🏿", True)
self.__testEvalIsEmojiSupportedByFont("👭🏻", True)
self.__testEvalIsEmojiSupportedByFont("👭🏼", True)
self.__testEvalIsEmojiSupportedByFont("👭🏽", True)
self.__testEvalIsEmojiSupportedByFont("👭🏾", True)
self.__testEvalIsEmojiSupportedByFont("👭🏿", True)
def __testEvalIsEmojiSupportedByFont(self, emoji: str, expected: bool):
emojiObj: Emoji([], emoji, "", [], [], Status.FULLY_QUALIFIED, Group.COMPONENT, "", 0)
isSupported: bool = self.__isEmojiSupportedByFont(emojiObj)
print(f"{emoji} : {expected == isSupported}")
def __genSingleEmojiStart(self, name: str):
return ("#if NET20 || NET30 || NET35\n"
"\t\tpublic static readonly List<SingleEmoji> " + name + " = new List<SingleEmoji>() {\n"
"#else\n"
"\t\tpublic static SortedSet<SingleEmoji> " + name + " => new SortedSet<SingleEmoji>() {\n"
"#endif\n")
def genEmojiBasicFile(self, result: EmojiParseResult):
outFile = self.__openFile("Emoji-Basic.cs")
output = ("using System.Collections.Generic;\n"
"\n"
"namespace NeoSmart.Unicode\n"
"{\n"
+ self.__genMachinegeneratedHeader()
+ "\tpublic static partial class Emoji\n"
"\t{\n"
"\t\t/// <summary>\n"
"\t\t/// A (sorted) enumeration of all emoji without skin variations and no duplicate gendered vs gender-neutral emoji, ideal for displaying.\n"
"\t\t/// Emoji without supported glyphs in Segoe UI Emoji are also omitted from this list.\n"
"\t\t/// <summary>\n")
output += self.__genSingleEmojiStart("Basic")
# The path to the Segoe UI Emoji font file under Windows 10:
for e in result.emoji:
if (e.status == Status.COMPONENT or e.status == Status.FULLY_QUALIFIED) and SkinTone.NONE in e.skinTones and self.__isEmojiSupportedByFont(e):
output += "\t\t\t/* " + e.emoji + " */ " + self.__genCamelCaseName(e) + ",\n"
output += "\t\t};\n\t}\n}\n"
self.__writeAndCloseFile(outFile, output)
print("Finished generating \"Emoji-Basic.cs\".")
def __genSubGroupName(self, subgroup: str) -> str:
parts = re.sub(r"[,.'’“”!():\-&]", " ", subgroup).split()
return "_".join(part.upper() for part in parts if part)
def __openFile(self, name: str):
if not os.path.exists("out"):
os.makedirs("out")
return open("out/" + name, "w", encoding="utf-8")
def __writeAndCloseFile(self, file, text: str):
# Replace \t with 4 spaces to match the VS identation:
file.write(text.replace("\t", " "))
file.close()
def genSubgroupsFile(self, result: EmojiParseResult):
print("Generating \"Emoji-Subgroups.cs\"...")
outFile = self.__openFile("Emoji-Subgroups.cs")
output = ("namespace NeoSmart.Unicode\n"
"{\n"
+ self.__genMachinegeneratedHeader()
+ "\tpublic static partial class Emoji\n"
"\t{\n"
"\t\tpublic static class Subgroups\n"
"\t\t{\n")
output += "\n".join("\t\t\tpublic static readonly string " + self.__genSubGroupName(subgroup) + " = \"" + subgroup + "\";" for subgroup in result.subgroups) + "\n"
output += "\t\t}\n\t}\n}\n"
self.__writeAndCloseFile(outFile, output)
print("Finished generating \"Subgroups.cs\".")
def gen(self, result: EmojiParseResult):
# Emoji-Emojis.cs
self.genEmojiDeclarationsFile(result)
# Subgroups.cs
self.genSubgroupsFile(result)
# Emoji-All.cs
self.genEmojiAllFile(result)
# Emoji-Basic.cs
self.genEmojiBasicFile(result)
# Emoji-SmileysAndEmotion.cs
self.genEmojiGroupFile(result, Group.SMILEYS_AND_EMOTION)
# Emoji-PeopleAndBody.cs
self.genEmojiGroupFile(result, Group.PEOPLE_AND_BODY)
# Emoji-Component.cs
self.genEmojiGroupFile(result, Group.COMPONENT)
# Emoji-AnimalsAndNature.cs
self.genEmojiGroupFile(result, Group.ANIMALS_AND_NATURE)
# Emoji-FoodAndDrink.cs
self.genEmojiGroupFile(result, Group.FOOD_AND_DRINK)
# Emoji-TravelAndPlaces.cs
self.genEmojiGroupFile(result, Group.TRAVEL_AND_PLACES)
# Emoji-Activities.cs
self.genEmojiGroupFile(result, Group.ACTIVITIES)
# Emoji-Objects.cs
self.genEmojiGroupFile(result, Group.OBJECTS)
# Emoji-Symbols.cs
self.genEmojiGroupFile(result, Group.SYMBOLS)
# Emoji-Flags.cs
self.genEmojiGroupFile(result, Group.FLAGS)
print("Done generating all C# source code files!")