-
Notifications
You must be signed in to change notification settings - Fork 54
/
operator-dictionary.py
136 lines (117 loc) · 5.12 KB
/
operator-dictionary.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
#!/usr/bin/python
from lxml import etree
from utils.misc import downloadWithProgressBar, UnicodeXMLURL, InlineAxisOperatorsURL
import json, re
from utils import mathfont
NonBreakingSpace = 0x00A0
def parseHexaNumber(string):
return int("0x%s" % string, 16)
def parseHexaSequence(string):
return tuple(map(parseHexaNumber, string[1:].split("-")))
def parseSpaces(value, entry, names):
for name in names:
attributeValue = entry.get(name)
if attributeValue is not None:
value[name] = int(attributeValue)
def parseProperties(value, entry, names):
attributeValue = entry.get("properties")
if attributeValue is not None:
for name in names:
if attributeValue.find(name) >= 0:
value[name] = True
def buildKeyAndValueFrom(characters, form):
# Concatenate characters and form to build the key.
key = ""
for c in characters:
key += unichr(c)
key += " " + form
# But save characters as an individual property for easier manipulation in
# this Python script.
value = {
"characters": characters,
}
return key, value
def createSizeVariants(aFont):
for size in (0, 1, 2, 3):
g = aFont.createChar(-1, "v%d" % size)
mathfont.drawRectangleGlyph(g, mathfont.em, (size + 1) * mathfont.em, 0)
g = aFont.createChar(-1, "h%d" % size)
mathfont.drawRectangleGlyph(g, (size + 1) * mathfont.em, mathfont.em, 0)
def createStretchy(aFont, codePoint, isHorizontal):
if isHorizontal:
aFont[codePoint].horizontalVariants = "h0 h1 h2 h3"
# Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
aFont[codePoint].horizontalComponents = \
(("h2", False, 0, mathfont.em, 3 * mathfont.em), \
("h1", True, mathfont.em, mathfont.em, 2 * mathfont.em))
else:
aFont[codePoint].verticalVariants = "v0 v1 v2 v3"
# Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
aFont[codePoint].verticalComponents = \
(("v2", False, 0, mathfont.em, 3 * mathfont.em), \
("v1", True, mathfont.em, mathfont.em, 2 * mathfont.em))
# Retrieve the spec files.
inlineAxisOperatorsTXT = downloadWithProgressBar(InlineAxisOperatorsURL)
unicodeXML = downloadWithProgressBar(UnicodeXMLURL)
# Extract the operator dictionary.
xsltTransform = etree.XSLT(etree.parse("./operator-dictionary.xsl"))
# Put the operator dictionary into a Python structure.
inlineAxisOperators = {}
with open(inlineAxisOperatorsTXT, mode="r") as f:
for line in f:
hexaString = re.match("^U\+([0-9A-F]+)", line).group(1)
inlineAxisOperators[parseHexaNumber(hexaString)] = True
operatorDictionary = {}
root = xsltTransform(etree.parse(unicodeXML)).getroot()
for entry in root:
characters = parseHexaSequence(entry.get("unicode"))
assert characters != (NonBreakingSpace)
key, value = buildKeyAndValueFrom(characters, entry.get("form"))
# There is no dictionary-specified minsize/maxsize values, so no need to
# parse them.
# The fence, separator and priority properties don't have any effect on math
# layout, so they are not added to the JSON file.
parseSpaces(value, entry, ["lspace", "rspace"])
parseProperties(value, entry, ["stretchy", "symmetric", "largeop",
"movablelimits", "accent"])
if (len(characters) == 1 and characters[0] in inlineAxisOperators):
value["horizontal"] = True
operatorDictionary[key] = value
# Create entries for the non-breaking space in all forms in order to test the
# default for operators outside the official dictionary.
for form in ["infix", "prefix", "suffix"]:
key, value = buildKeyAndValueFrom(tuple([NonBreakingSpace]), form)
operatorDictionary[key] = value
# Create a WOFF font with glyphs for all the operator strings.
font = mathfont.create("operators", "Copyright (c) 2019 Igalia S.L.")
# Set parameters for largeop and stretchy tests.
font.math.DisplayOperatorMinHeight = 2 * mathfont.em
font.math.MinConnectorOverlap = mathfont.em / 2
# Set parameters for accent tests so that we only have large gap when
# overscript is an accent.
font.math.UpperLimitBaselineRiseMin = 0
font.math.StretchStackTopShiftUp = 0
font.math.AccentBaseHeight = 2 * mathfont.em
font.math.OverbarVerticalGap = 0
createSizeVariants(font)
for key in operatorDictionary:
value = operatorDictionary[key]
for c in value["characters"]:
if c in font:
continue
if c == NonBreakingSpace:
g = font.createChar(c)
mathfont.drawRectangleGlyph(g, mathfont.em, mathfont.em / 3, 0)
else:
mathfont.createSquareGlyph(font, c)
createStretchy(font, c, c in inlineAxisOperators)
mathfont.save(font)
# Generate the python file.
for key in operatorDictionary:
del operatorDictionary[key]["characters"] # Remove this temporary value.
JSON = {
"comment": "This file was automatically generated by operator-dictionary.py. Do not edit.",
"dictionary": operatorDictionary
}
with open('../support/operator-dictionary.json', 'w') as fp:
json.dump(JSON, fp, sort_keys=True, ensure_ascii=True)