operator-dictionary.py (4526B)
1 #!/usr/bin/env python3 2 3 from lxml import etree 4 from utils.misc import downloadWithProgressBar, UnicodeXMLURL, InlineAxisOperatorsURL 5 import json 6 import re 7 from utils import mathfont 8 9 NonBreakingSpace = 0x00A0 10 11 12 def parseHexaNumber(string): 13 return int("0x%s" % string, 16) 14 15 16 def parseHexaSequence(string): 17 return tuple(map(parseHexaNumber, string[1:].split("-"))) 18 19 20 def parseSpaces(value, entry, names): 21 for name in names: 22 attributeValue = entry.get(name) 23 if attributeValue is not None: 24 value[name] = int(attributeValue) 25 26 27 def parseProperties(value, entry, names): 28 attributeValue = entry.get("properties") 29 if attributeValue is not None: 30 for name in names: 31 if attributeValue.find(name) >= 0: 32 value[name] = True 33 34 35 def buildKeyAndValueFrom(characters, form): 36 # Concatenate characters and form to build the key. 37 key = "" 38 for c in characters: 39 key += chr(c) 40 key += " " + form 41 # But save characters as an individual property for easier manipulation in 42 # this Python script. 43 value = { 44 "characters": characters, 45 } 46 return key, value 47 48 49 # Retrieve the spec files. 50 inlineAxisOperatorsTXT = downloadWithProgressBar(InlineAxisOperatorsURL) 51 unicodeXML = downloadWithProgressBar(UnicodeXMLURL) 52 53 # Extract the operator dictionary. 54 xsltTransform = etree.XSLT(etree.parse("./operator-dictionary.xsl")) 55 56 # Put the operator dictionary into a Python structure. 57 inlineAxisOperators = {} 58 with open(inlineAxisOperatorsTXT, mode="r") as f: 59 for line in f: 60 hexaString = re.match(r"^U\+([0-9A-F]+)", line).group(1) 61 inlineAxisOperators[parseHexaNumber(hexaString)] = True 62 63 operatorDictionary = {} 64 root = xsltTransform(etree.parse(unicodeXML)).getroot() 65 for entry in root: 66 characters = parseHexaSequence(entry.get("unicode")) 67 assert characters != (NonBreakingSpace) 68 key, value = buildKeyAndValueFrom(characters, entry.get("form")) 69 # There is no dictionary-specified minsize/maxsize values, so no need to 70 # parse them. 71 # The fence, separator and priority properties don't have any effect on math 72 # layout, so they are not added to the JSON file. 73 parseSpaces(value, entry, ["lspace", "rspace"]) 74 parseProperties(value, entry, ["stretchy", "symmetric", "largeop", 75 "movablelimits", "accent"]) 76 if (len(characters) == 1 and characters[0] in inlineAxisOperators): 77 value["horizontal"] = True 78 operatorDictionary[key] = value 79 80 # Create entries for the non-breaking space in all forms in order to test the 81 # default for operators outside the official dictionary. 82 for form in ["infix", "prefix", "suffix"]: 83 key, value = buildKeyAndValueFrom(tuple([NonBreakingSpace]), form) 84 operatorDictionary[key] = value 85 86 # Create a WOFF font with glyphs for all the operator strings. 87 font = mathfont.create("operators", "Copyright (c) 2019 Igalia S.L.") 88 89 # Set parameters for largeop and stretchy tests. 90 font.math.DisplayOperatorMinHeight = 2 * mathfont.em 91 font.math.MinConnectorOverlap = mathfont.em // 2 92 93 # Set parameters for accent tests so that we only have large gap when 94 # overscript is an accent. 95 font.math.UpperLimitBaselineRiseMin = 0 96 font.math.StretchStackTopShiftUp = 0 97 font.math.AccentBaseHeight = 2 * mathfont.em 98 font.math.OverbarVerticalGap = 0 99 100 mathfont.createSizeVariants(font, True) 101 102 # Ensure a glyph exists for the combining characters that are handled specially 103 # in the specification: 104 # U+0338 COMBINING LONG SOLIDUS OVERLAY 105 # U+20D2 COMBINING LONG VERTICAL LINE OVERLAY 106 for combining_character in [0x338, 0x20D2]: 107 mathfont.createSquareGlyph(font, combining_character) 108 109 for key in operatorDictionary: 110 value = operatorDictionary[key] 111 for c in value["characters"]: 112 if c in font: 113 continue 114 if c == NonBreakingSpace: 115 g = font.createChar(c) 116 mathfont.drawRectangleGlyph(g, mathfont.em, mathfont.em // 3, 0) 117 else: 118 mathfont.createSquareGlyph(font, c) 119 mathfont.createStretchy(font, c, c in inlineAxisOperators) 120 mathfont.save(font) 121 122 # Generate the python file. 123 for key in operatorDictionary: 124 del operatorDictionary[key]["characters"] # Remove this temporary value. 125 JSON = { 126 "comment": "This file was automatically generated by operator-dictionary.py. Do not edit.", 127 "dictionary": operatorDictionary 128 } 129 with open('../support/operator-dictionary.json', 'w') as fp: 130 json.dump(JSON, fp, sort_keys=True, ensure_ascii=True)