text: #!/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)
  