Files
renpy/scripts/unicode/make_emoji_trie.py
T
Tom Rothamel e41a4f85e9 reorg: Eliminate use of the module directory.
This directory contained a mix of both c/cython source code, scripts,
and build information. Now, source is in src, scripts are in scripts,
and setup.py is in the root.
2024-12-15 18:14:26 -05:00

215 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
import collections
import graphlib
from pathlib import Path
import pprint
class trie_node(dict):
name = None
def __missing__(self, key):
self[key] = trie_node()
self[key][''] = 0
return self[key]
@property
def key(self):
return tuple(sorted(self.items()))
def __hash__(self):
return hash(self.key)
def __eq__(self, other):
return self.key == other.key
emoji = trie_node()
LEVEL = {
"unqualified" : 1,
"component" : 2,
"minimally-qualified" : 3,
"fully-qualified" : 4,
}
def load():
"""
Load the emoji.
"""
emoji_test_text = Path(__file__).parent / "emoji-test.txt"
lines = emoji_test_text.read_text().splitlines()
for l in lines:
l = l.partition("#")[0].strip()
if not l:
continue
codepoints, qualifier = l.split(";")
level = LEVEL[qualifier.strip()]
d = emoji
for i in codepoints.strip().split():
i = chr(int(i, 16))
d = d[i]
d[''] = level
emoji[''] = 0
def merge(d):
"""
Merge tries with identical subtrees.
"""
unique_tries = { }
def pass1(d):
unique_tries[d] = d
for v in d.values():
if type(v) is not int:
pass1(v)
def pass2(d):
d = unique_tries[d]
for k, v in d.items():
if type(v) is not int:
d[k] = pass2(v)
return d
pass1(d)
return pass2(d)
dict_by_name = { }
def assign_names(d):
"""
Assigns a unique name to each trie node.
"""
serial = 0
def assign(d):
if type(d) is int or type(d) is str:
return
if d.name is not None:
return
nonlocal serial
d.name = f"e{serial}"
dict_by_name[d.name] = d
serial += 1
for v in d.values():
assign(v)
assign(d)
d.name = "emoji"
dict_by_name[d.name] = d
order = [ ]
def sort():
"""
Sorts the dicts into dependency order.
"""
graph = { }
def add(d):
graph[d.name] = {v.name for v in d.values() if type(v) is not int}
for v in d.values():
if type(v) is not int:
add(v)
add(emoji)
ts = graphlib.TopologicalSorter(graph)
global order
order = list(ts.static_order())
def generate(f, d):
"""
Generates Python code for a trie node.
"""
l = [ f"{d.name} = {{" ]
for k in sorted(d.keys()):
v = d[k]
if type(v) is int:
l.append(f"'{k}': {v},")
else:
l.append(f"'{k}': {v.name},")
l.append("}")
print(" ".join(l), file=f)
def main():
dest = Path(__file__).parent.parent.parent / "renpy"/ "text"/ "emoji_trie.py"
with open(dest, "w") as f:
print("""\
# coding: utf-8
#
# This file is generated by module/emoji/make_emoji_trie.py. Do not
# edit it directly.
#
# Emoji trie data.
#
#
# It contains a data structure that can be used to determine if a
# sequence of codepoints is an emoji and if it should be rendered
# in an emoji font.
#
# Each trie node is a dictionary mapping codepoints to other dictionaries.
# If a codepoint is not present in the dictionarly, look up the '' key
# in the dictionary to find what it maps to. (It will be one of the constants
# below.
#
# The top-level dictionary is named emoji.
# The codepoint is not an emoji.
NOT_EMOJI = 0
# The codepoints are an emoji, but it may or may not be rendered in an emoji font.
UNQUALIFIED = 1
# The codepoints are part of a larger emoji sequence, and should be rendered
# in an emoji font when standing alone.
COMPONENT = 2
# The codepoints are an emoji, and should be rendered in an emoji font, but
# aren't fully qualified.
MINIMALLY_QUALIFIED = 3
# The codepoints are an emoji, and should be rendered in an emoji font.
QUALIFIED = 4
""", file=f)
global emoji
load()
emoji = merge(emoji)
assign_names(emoji)
sort()
for name in order:
generate(f, dict_by_name[name])
if __name__ == "__main__":
main()