130 lines
4.4 KiB
Python
Executable file
130 lines
4.4 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
from lxml import etree
|
|
from utils.misc import downloadWithProgressBar, UnicodeXMLURL, InlineAxisOperatorsURL
|
|
import json
|
|
import 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 += chr(c)
|
|
key += " " + form
|
|
# But save characters as an individual property for easier manipulation in
|
|
# this Python script.
|
|
value = {
|
|
"characters": characters,
|
|
}
|
|
return key, value
|
|
|
|
|
|
# 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(r"^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
|
|
|
|
mathfont.createSizeVariants(font, True)
|
|
|
|
# Ensure a glyph exists for the combining characters that are handled specially
|
|
# in the specification:
|
|
# U+0338 COMBINING LONG SOLIDUS OVERLAY
|
|
# U+20D2 COMBINING LONG VERTICAL LINE OVERLAY
|
|
for combining_character in [0x338, 0x20D2]:
|
|
mathfont.createSquareGlyph(font, combining_character)
|
|
|
|
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)
|
|
mathfont.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)
|