summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/mathml/tools
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/mathml/tools')
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/axisheight.py27
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/fractions.py163
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/largeop.py64
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/limits.py78
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/math-text.py64
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/mathvariant-transforms.py202
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/operator-dictionary.py130
-rw-r--r--testing/web-platform/tests/mathml/tools/operator-dictionary.xsl73
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/percentscaledown.py24
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/radicals.py133
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/scripts.py155
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/stacks.py80
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/stretchstacks.py78
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/stretchy-centered-on-baseline.py44
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/stretchy.py43
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/underover.py88
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/use-typo-lineheight.py53
-rw-r--r--testing/web-platform/tests/mathml/tools/utils/__init__.py1
-rw-r--r--testing/web-platform/tests/mathml/tools/utils/mathfont.py232
-rw-r--r--testing/web-platform/tests/mathml/tools/utils/misc.py35
-rwxr-xr-xtesting/web-platform/tests/mathml/tools/xHeight.py12
21 files changed, 1779 insertions, 0 deletions
diff --git a/testing/web-platform/tests/mathml/tools/axisheight.py b/testing/web-platform/tests/mathml/tools/axisheight.py
new file mode 100755
index 0000000000..7640c4f789
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/axisheight.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+verticalArrowCodePoint = 0x21A8
+v1 = 5 * mathfont.em
+v2 = 14 * mathfont.em
+f = mathfont.create("axisheight%d-verticalarrow%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v1
+f.math.MinConnectorOverlap = 0
+mathfont.createSquareGlyph(f, verticalArrowCodePoint)
+g = f.createChar(-1, "size1")
+mathfont.drawRectangleGlyph(g, mathfont.em, v2 / 2, 0)
+g = f.createChar(-1, "size2")
+mathfont.drawRectangleGlyph(g, mathfont.em, v2, 0)
+g = f.createChar(-1, "bot")
+mathfont.drawRectangleGlyph(g, mathfont.em, v2 + v1, 0)
+g = f.createChar(-1, "ext")
+mathfont.drawRectangleGlyph(g, mathfont.em, mathfont.em, 0)
+f[verticalArrowCodePoint].verticalVariants = "uni21A8 size1 size2"
+# Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
+f[verticalArrowCodePoint].verticalComponents = \
+ (("bot", False, 0, mathfont.em, v2 + v1),
+ ("ext", True, mathfont.em, mathfont.em, mathfont.em))
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/fractions.py b/testing/web-platform/tests/mathml/tools/fractions.py
new file mode 100755
index 0000000000..bd39fc2fdb
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/fractions.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+v1 = 7 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-axisheight%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v1
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 5 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-denominatordisplaystylegapmin%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.FractionDenominatorDisplayStyleGapMin = v1
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 6 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-denominatordisplaystyleshiftdown%d-axisheight%d-rulethickness%d" % (v1, v2, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = v1
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 4 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-denominatorgapmin%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = v1
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 3 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-denominatorshiftdown%d-axisheight%d-rulethickness%d" % (v1, v2, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = v1
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 8 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-numeratordisplaystylegapmin%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = v1
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 2 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-numeratordisplaystyleshiftup%d-axisheight%d-rulethickness%d" % (v1, v2, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = v1
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 9 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-numeratorgapmin%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = v1
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 11 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("fraction-numeratorshiftup%d-axisheight%d-rulethickness%d" % (v1, v2, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = v1
+f.math.FractionRuleThickness = v2
+mathfont.save(f)
+
+v1 = 10 * mathfont.em
+f = mathfont.create("fraction-rulethickness%d" % v1,
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.FractionDenominatorDisplayStyleGapMin = 0
+f.math.FractionDenominatorDisplayStyleShiftDown = 0
+f.math.FractionDenominatorGapMin = 0
+f.math.FractionDenominatorShiftDown = 0
+f.math.FractionNumeratorDisplayStyleGapMin = 0
+f.math.FractionNumeratorDisplayStyleShiftUp = 0
+f.math.FractionNumeratorGapMin = 0
+f.math.FractionNumeratorShiftUp = 0
+f.math.FractionRuleThickness = v1
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/largeop.py b/testing/web-platform/tests/mathml/tools/largeop.py
new file mode 100755
index 0000000000..9832ff0039
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/largeop.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+nAryWhiteVerticalBarCodePoint = 0x2AFF
+v1 = 5 * mathfont.em
+f = mathfont.create("largeop-displayoperatorminheight%d" % v1,
+ "Copyright (c) 2016 MathML Association")
+f.math.DisplayOperatorMinHeight = v1
+mathfont.createSquareGlyph(f, nAryWhiteVerticalBarCodePoint)
+g = f.createChar(-1, "uni2AFF.display")
+mathfont.drawRectangleGlyph(g, mathfont.em, v1, 0)
+f[nAryWhiteVerticalBarCodePoint].verticalVariants = "uni2AFF uni2AFF.display"
+mathfont.save(f)
+
+v1 = 2 * mathfont.em
+v2 = 3 * mathfont.em
+f = mathfont.create("largeop-displayoperatorminheight%d-2AFF-italiccorrection%d" % (v1, v2),
+ "Copyright (c) 2018 Igalia S.L.")
+f.math.DisplayOperatorMinHeight = v1
+mathfont.createSquareGlyph(f, nAryWhiteVerticalBarCodePoint)
+g = f.createChar(-1, "uni2AFF.display")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(v2, v1)
+p.lineTo(v2 + mathfont.em, v1)
+p.lineTo(mathfont.em, 0)
+p.closePath()
+g.width = mathfont.em + v2
+g.italicCorrection = v2
+f[nAryWhiteVerticalBarCodePoint].verticalVariants = "uni2AFF uni2AFF.display"
+mathfont.save(f)
+
+v1 = 7 * mathfont.em
+v2 = 5 * mathfont.em
+f = mathfont.create("largeop-displayoperatorminheight%d-2AFF-italiccorrection%d" % (v1, v2),
+ "Copyright (c) 2020 Igalia S.L.")
+f.math.DisplayOperatorMinHeight = v1
+f.math.MinConnectorOverlap = 0
+mathfont.createSquareGlyph(f, nAryWhiteVerticalBarCodePoint)
+g = f.createChar(-1, "uni2AFF.bot")
+mathfont.drawRectangleGlyph(g,
+ width=2 * mathfont.em,
+ ascent=mathfont.em)
+g = f.createChar(-1, "uni2AFF.ext")
+mathfont.drawRectangleGlyph(g,
+ width=mathfont.em,
+ ascent=2 * mathfont.em,
+ padding_left=mathfont.em)
+g = f.createChar(-1, "uni2AFF.top")
+mathfont.drawRectangleGlyph(g,
+ width=v2 + mathfont.em,
+ ascent=mathfont.em,
+ padding_left=mathfont.em)
+f[nAryWhiteVerticalBarCodePoint].verticalVariants = "uni2AFF"
+# Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
+f[nAryWhiteVerticalBarCodePoint].verticalComponents = \
+ (("uni2AFF.bot", False, 0, mathfont.em // 2, mathfont.em),
+ ("uni2AFF.ext", True, mathfont.em // 2, mathfont.em // 2, 2 * mathfont.em),
+ ("uni2AFF.top", False, mathfont.em // 2, 0, mathfont.em)
+ )
+f[nAryWhiteVerticalBarCodePoint].verticalComponentItalicCorrection = v2
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/limits.py b/testing/web-platform/tests/mathml/tools/limits.py
new file mode 100755
index 0000000000..db4437c1b0
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/limits.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+nArySumCodePoint = 0x2211 # largeop operator
+
+v = 3 * mathfont.em
+f = mathfont.create("limits-lowerlimitbaselinedropmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, nArySumCodePoint)
+f.math.LowerLimitBaselineDropMin = v
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 11 * mathfont.em
+f = mathfont.create("limits-lowerlimitgapmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, nArySumCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = v
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 5 * mathfont.em
+f = mathfont.create("limits-upperlimitbaselinerisemin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, nArySumCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = v
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 7 * mathfont.em
+f = mathfont.create("limits-upperlimitgapmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, nArySumCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = v
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/math-text.py b/testing/web-platform/tests/mathml/tools/math-text.py
new file mode 100755
index 0000000000..2c3e3257dc
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/math-text.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+import fontforge
+
+font = fontforge.font()
+font.em = 1000
+lineHeight = 5000
+name = "math-text"
+font.fontname = name
+font.familyname = name
+font.fullname = name
+font.copyright = "Copyright (c) 2019 Igalia"
+
+glyph = font.createChar(ord(" "), "space")
+glyph.width = 1000
+glyph = font.createChar(ord("A"))
+pen = glyph.glyphPen()
+pen.moveTo(0, -500)
+pen.lineTo(0, 500)
+pen.lineTo(1000, 500)
+pen.lineTo(1000, -500)
+pen.closePath()
+
+glyph = font.createChar(ord("B"))
+pen = glyph.glyphPen()
+pen.moveTo(0, 0)
+pen.lineTo(0, 1000)
+pen.lineTo(1000, 1000)
+pen.lineTo(1000, 0)
+pen.closePath()
+
+glyph = font.createChar(ord("C"))
+pen = glyph.glyphPen()
+pen.moveTo(0, -1000)
+pen.lineTo(0, 0)
+pen.lineTo(1000, 0)
+pen.lineTo(1000, -1000)
+pen.closePath()
+
+font.os2_typoascent_add = False
+font.os2_typoascent = lineHeight // 2
+font.os2_typodescent_add = False
+font.os2_typodescent = -lineHeight // 2
+font.os2_typolinegap = 0
+font.hhea_ascent = lineHeight // 2
+font.hhea_ascent_add = False
+font.hhea_descent = -lineHeight // 2
+font.hhea_descent_add = False
+font.hhea_linegap = 0
+font.os2_winascent = lineHeight // 2
+font.os2_winascent_add = False
+font.os2_windescent = lineHeight // 2
+font.os2_windescent_add = False
+
+font.os2_use_typo_metrics = True
+
+path = "../../fonts/math/math-text.woff"
+print("Generating %s..." % path, end="")
+font.generate(path)
+if font.validate() == 0:
+ print(" done.")
+else:
+ print(" validation error!")
+ exit(1)
diff --git a/testing/web-platform/tests/mathml/tools/mathvariant-transforms.py b/testing/web-platform/tests/mathml/tools/mathvariant-transforms.py
new file mode 100755
index 0000000000..e4857d2a3e
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/mathvariant-transforms.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+
+from lxml import etree
+from utils.misc import downloadWithProgressBar, UnicodeXMLURL
+from utils import mathfont
+
+# Retrieve the unicode.xml file if necessary.
+unicodeXML = downloadWithProgressBar(UnicodeXMLURL)
+
+# Extract the mathvariants transformation.
+xsltTransform = etree.XSLT(etree.XML('''\
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:strip-space elements="*"/>
+ <xsl:template match="charlist">
+ <root><xsl:apply-templates select="character"/></root>
+ </xsl:template>
+ <xsl:template match="character">
+ <xsl:if test="surrogate">
+ <entry>
+ <xsl:attribute name="mathvariant">
+ <xsl:value-of select="surrogate/@mathvariant"/>
+ </xsl:attribute>
+ <xsl:attribute name="baseChar">
+ <xsl:value-of select="surrogate/@ref"/>
+ </xsl:attribute>
+ <xsl:attribute name="transformedChar">
+ <xsl:choose>
+ <xsl:when test="bmp">
+ <xsl:value-of select="bmp/@ref"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@id"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ </entry>
+ </xsl:if>
+ </xsl:template>
+</xsl:stylesheet>'''))
+
+# Put the mathvariant transforms into a Python structure.
+mathvariantTransforms = {}
+root = xsltTransform(etree.parse(unicodeXML)).getroot()
+
+
+def parseCodePoint(aHexaString):
+ return int("0x%s" % aHexaString[1:], 16)
+
+
+for entry in root:
+ mathvariant = entry.get("mathvariant")
+ baseChar = parseCodePoint(entry.get("baseChar"))
+ transformedChar = parseCodePoint(entry.get("transformedChar"))
+ if mathvariant not in mathvariantTransforms:
+ mathvariantTransforms[mathvariant] = {}
+ mathvariantTransforms[mathvariant][baseChar] = transformedChar
+
+# There is no "isolated" mathvariant.
+del mathvariantTransforms["isolated"]
+
+# Automatic mathvariant uses the same transform as italic.
+# It is handled specially (see below).
+mathvariantTransforms["auto"] = mathvariantTransforms["italic"]
+
+# Create a WOFF font for each mathvariant.
+for mathvariant in mathvariantTransforms:
+ if mathvariant == "auto":
+ continue
+ font = mathfont.create("mathvariant-%s" % mathvariant,
+ "Copyright (c) 2016 MathML Association")
+ for baseChar in mathvariantTransforms[mathvariant]:
+ if baseChar not in font:
+ mathfont.createGlyphFromValue(font, baseChar)
+ transformedChar = mathvariantTransforms[mathvariant][baseChar]
+ mathfont.createGlyphFromValue(font, transformedChar)
+ mathfont.save(font)
+
+# Common function to generate test for MathML mathvariant / CSS text-transform.
+
+
+def generateTestFor(mathvariant, mathml):
+ assert mathml or mathvariant == "auto", "These tests have been removed!"
+ print("Generating tests for %s..." % mathvariant, end="")
+ if mathml:
+ reftest = open(
+ "../relations/css-styling/mathvariant-%s.html" % mathvariant, "w")
+ reftestReference = open(
+ "../relations/css-styling/mathvariant-%s-ref.html" % mathvariant, "w")
+ else:
+ reftest = open(
+ "../../css/css-text/text-transform/math/text-transform-math-%s-001.html" % mathvariant, "w")
+ reftestReference = open(
+ "../../css/css-text/text-transform/math/text-transform-math-%s-001-ref.html" % mathvariant, "w")
+ source = '\
+<!DOCTYPE html>\n\
+<html>\n\
+<head>\n\
+<meta charset="utf-8"/>\n\
+<title>%s</title>\n'
+ if mathml:
+ reftest.write(source % ("mathvariant %s" % mathvariant))
+ reftestReference.write(
+ source % ("mathvariant %s (reference)" % mathvariant))
+ else:
+ reftest.write(source % ("text-transform math-%s" % mathvariant))
+ reftestReference.write(
+ source % ("text-transform math-%s (reference)" % mathvariant))
+ if mathvariant == "auto":
+ mathAssert = "Verify that a single-char <mi> is equivalent to an <mi> with the transformed italic unicode character."
+ mapping = "italic"
+ else:
+ mathAssert = "Verify that a single-char <mtext> with a %s mathvariant is equivalent to an <mtext> with the transformed unicode character." % mathvariant
+ mapping = mathvariant
+ if mathml:
+ source = '\
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">\n\
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">\n\
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">\n\
+<link rel="help" href="https://w3c.github.io/mathml-core/#%s-mappings">\n\
+<link rel="match" href="mathvariant-%s-ref.html"/>\n\
+<meta name="assert" content="%s">\n'
+ reftest.write(source % (mapping, mathvariant, mathAssert))
+ else:
+ source = '\
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/3745"/>\n\
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">\n\
+<link rel="help" href="https://w3c.github.io/mathml-core/#%s-mappings">\n\
+<link rel="match" href="text-transform-math-%s-001-ref.html"/>\n\
+<meta name="assert" content="Verify that a character with \'text-transform: math-%s\' renders the same as the transformed unicode character.">\n'
+ reftest.write(source % (mapping, mathvariant, mathvariant))
+ WOFFfont = "mathvariant-%s.woff" % mapping
+ source = '\
+<style>\n\
+ @font-face {\n\
+ font-family: TestFont;\n\
+ src: url("/fonts/math/%s");\n\
+ }\n\
+ body > span {\n\
+ padding: 10px;\n\
+ }\n\
+ span > span {\n\
+ font-family: monospace;\n\
+ font-size: 10px;\n\
+ }\n\
+ .testfont {\n\
+ font-family: TestFont;\n\
+ font-size: 10px;\n\
+ }\n\
+</style>\n\
+<body>\n\
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->\n\
+ <p>Test passes if all the equalities below are true.</p>\n' % WOFFfont
+ if mathml:
+ reftest.write(source)
+ reftestReference.write(source)
+ else:
+ reftest.write(source)
+ reftestReference.write(source)
+ charIndex = 0
+ for baseChar in mathvariantTransforms[mathvariant]:
+ transformedChar = mathvariantTransforms[mathvariant][baseChar]
+ if mathvariant == "auto":
+ tokenTag = '<mi>&#x%0X;</mi>' % baseChar
+ tokenTagRef = '<mi>&#x%0X;</mi>' % transformedChar
+ else:
+ tokenTag = '<mtext mathvariant="%s">&#x%0X;</mtext>' % (
+ mathvariant, baseChar)
+ tokenTagRef = '<mtext>&#x%0X;</mtext>' % transformedChar
+ if mathml:
+ reftest.write(' <span><math class="testfont">%s</math>=<span>%05X</span></span>' %
+ (tokenTag, transformedChar))
+ reftestReference.write(
+ ' <span><math class="testfont">%s</math>=<span>%05X</span></span>' % (tokenTagRef, transformedChar))
+ else:
+ reftest.write(' <span><span class="testfont" style="text-transform: math-%s">&#x%0X;</span>=<span>%05X</span></span>' %
+ (mathvariant, baseChar, transformedChar))
+ reftestReference.write(
+ ' <span><span class="testfont">&#x%0X;</span>=<span>%05X</span></span>' % (transformedChar, transformedChar))
+ charIndex += 1
+ if charIndex % 10 == 0:
+ reftest.write('<br/>')
+ reftestReference.write('<br/>')
+ reftest.write('\n')
+ reftestReference.write('\n')
+ source = '</body>\n</html>\n'
+ reftest.write(source)
+ reftestReference.write(source)
+ reftest.close()
+ reftestReference.close()
+ print(" done.")
+
+
+# Generate css/css-text/text-transform/math/text-transform-math-auto-001.html
+generateTestFor(mathvariant="auto", mathml=False)
+generateTestFor(mathvariant="auto", mathml=True)
+
+# Other mathvariant tests can be generated by the following command. They are
+# still use internally by browsers implementing full mathvariant support.
+# See https://github.com/w3c/mathml-core/issues/182
+# for mathvariant in mathvariantTransforms:
+# generateTestFor(mathvariant, mathml=True)
diff --git a/testing/web-platform/tests/mathml/tools/operator-dictionary.py b/testing/web-platform/tests/mathml/tools/operator-dictionary.py
new file mode 100755
index 0000000000..8de654df15
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/operator-dictionary.py
@@ -0,0 +1,130 @@
+#!/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)
diff --git a/testing/web-platform/tests/mathml/tools/operator-dictionary.xsl b/testing/web-platform/tests/mathml/tools/operator-dictionary.xsl
new file mode 100644
index 0000000000..8c75317672
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/operator-dictionary.xsl
@@ -0,0 +1,73 @@
+<!-- -*- Mode: nXML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:strip-space elements="*"/>
+
+ <xsl:template match="charlist">
+ <root><xsl:apply-templates select="character"/></root>
+ </xsl:template>
+
+ <xsl:template match="character">
+ <xsl:if test="operator-dictionary">
+ <xsl:for-each select="operator-dictionary">
+ <entry>
+
+ <xsl:attribute name="unicode">
+ <xsl:value-of select="../@id"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="form">
+ <xsl:value-of select="@form"/>
+ </xsl:attribute>
+
+ <!-- begin operator-dictionary -->
+ <xsl:if test="@lspace">
+ <xsl:attribute name="lspace">
+ <xsl:value-of select="@lspace"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@rspace">
+ <xsl:attribute name="rspace">
+ <xsl:value-of select="@rspace"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@minsize">
+ <xsl:attribute name="minsize">
+ <xsl:value-of select="@minsize"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@*[.='true']">
+ <xsl:attribute name="properties">
+ <!-- largeop, movablelimits, stretchy, separator, accent, fence,
+ symmetric -->
+ <xsl:for-each select="@*[.='true']">
+ <xsl:value-of select="name()"/>
+ <xsl:text> </xsl:text>
+ </xsl:for-each>
+ <xsl:if test="../unicodedata/@mirror = 'Y'">
+ <xsl:text>mirrorable </xsl:text>
+ </xsl:if>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@priority">
+ <xsl:attribute name="priority">
+ <xsl:value-of select="@priority"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@linebreakstyle">
+ <xsl:attribute name="linebreakstyle">
+ <xsl:value-of select="@linebreakstyle"/>
+ </xsl:attribute>
+ </xsl:if>
+ <!-- end operator-dictionary -->
+
+ <xsl:attribute name="description">
+ <xsl:value-of select="../description"/>
+ </xsl:attribute>
+
+ </entry>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/testing/web-platform/tests/mathml/tools/percentscaledown.py b/testing/web-platform/tests/mathml/tools/percentscaledown.py
new file mode 100755
index 0000000000..ef40d1fd87
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/percentscaledown.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+v1 = 80
+v2 = 40
+f = mathfont.create("scriptpercentscaledown%d-scriptscriptpercentscaledown%d" % (v1, v2),
+ "Copyright (c) 2019 Igalia S.L.")
+f.math.ScriptPercentScaleDown = v1
+f.math.ScriptScriptPercentScaleDown = v2
+mathfont.save(f)
+
+f = mathfont.create("scriptpercentscaledown0-scriptscriptpercentscaledown%d" % v2,
+ "Copyright (c) 2019 Igalia S.L.")
+f.math.ScriptPercentScaleDown = 0
+f.math.ScriptScriptPercentScaleDown = v2
+mathfont.save(f)
+
+f = mathfont.create("scriptpercentscaledown%d-scriptscriptpercentscaledown0" % v1,
+ "Copyright (c) 2019 Igalia S.L.")
+f.math.ScriptPercentScaleDown = v1
+f.math.ScriptScriptPercentScaleDown = 0
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/radicals.py b/testing/web-platform/tests/mathml/tools/radicals.py
new file mode 100755
index 0000000000..c4d9ece813
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/radicals.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+
+def createStretchyRadical(aFont):
+ radicalCodePoint = 0x221a
+ mathfont.createSquareGlyph(aFont, radicalCodePoint)
+ g = aFont.createChar(-1, "size1")
+ mathfont.drawRectangleGlyph(g, mathfont.em, 2 * mathfont.em, 0)
+ g = aFont.createChar(-1, "size2")
+ mathfont.drawRectangleGlyph(g, mathfont.em, 3 * mathfont.em, 0)
+ g = aFont.createChar(-1, "size3")
+ mathfont.drawRectangleGlyph(g, mathfont.em, 4 * mathfont.em, 0)
+ aFont[radicalCodePoint].verticalVariants = "radical size1 size2 size3"
+ # Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
+ aFont.math.MinConnectorOverlap = 0
+ aFont[radicalCodePoint].verticalComponents = \
+ (("size2", False, 0, mathfont.em, 3 * mathfont.em),
+ ("size1", True, mathfont.em, mathfont.em, 2 * mathfont.em))
+
+
+v1 = 25
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-degreebottomraisepercent%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = v1
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v1 = 7 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-displaystyleverticalgap%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = v1
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v1 = 3 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-extraascender%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = v1
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v1 = 5 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-kernafterdegreeminus%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = -v1
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v1 = 4 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-kernbeforedegree%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = v1
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v = 8 * mathfont.em
+f = mathfont.create("radical-rulethickness%d" % v,
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
+
+v1 = 6 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-verticalgap%d-rulethickness%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = 0
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = v1
+mathfont.save(f)
+
+v1 = 1 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("radical-negativekernbeforedegree%d-rulethickness%d" %
+ (v1, v2), "Copyright (c) 2020 Igalia S.L.")
+createStretchyRadical(f)
+f.math.RadicalDegreeBottomRaisePercent = 0
+f.math.RadicalDisplayStyleVerticalGap = 0
+f.math.RadicalExtraAscender = 0
+f.math.RadicalKernAfterDegree = 0
+f.math.RadicalKernBeforeDegree = -v1
+f.math.RadicalRuleThickness = v2
+f.math.RadicalVerticalGap = 0
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/scripts.py b/testing/web-platform/tests/mathml/tools/scripts.py
new file mode 100755
index 0000000000..e1da482a00
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/scripts.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+v = 3 * mathfont.em
+f = mathfont.create("scripts-spaceafterscript%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = v
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 7 * mathfont.em
+f = mathfont.create("scripts-superscriptshiftup%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = v
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 5 * mathfont.em
+f = mathfont.create("scripts-superscriptshiftupcramped%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = v
+mathfont.save(f)
+
+v = 6 * mathfont.em
+f = mathfont.create("scripts-subscriptshiftdown%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = v
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 11 * mathfont.em
+f = mathfont.create("scripts-subsuperscriptgapmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = v
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v1 = 11 * mathfont.em
+v2 = 3 * mathfont.em
+f = mathfont.create("scripts-subsuperscriptgapmin%d-superscriptbottommaxwithsubscript%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = v1
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = v2
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 4 * mathfont.em
+f = mathfont.create("scripts-subscripttopmax%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = v
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 8 * mathfont.em
+f = mathfont.create("scripts-superscriptbottommin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = v
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 9 * mathfont.em
+f = mathfont.create("scripts-subscriptbaselinedropmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = v
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = 0
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
+
+v = 10 * mathfont.em
+f = mathfont.create("scripts-superscriptbaselinedropmax%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.SpaceAfterScript = 0
+f.math.SubSuperscriptGapMin = 0
+f.math.SubscriptBaselineDropMin = 0
+f.math.SubscriptShiftDown = 0
+f.math.SubscriptTopMax = 0
+f.math.SuperscriptBaselineDropMax = v
+f.math.SuperscriptBottomMaxWithSubscript = 0
+f.math.SuperscriptBottomMin = 0
+f.math.SuperscriptShiftUp = 0
+f.math.SuperscriptShiftUpCramped = 0
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/stacks.py b/testing/web-platform/tests/mathml/tools/stacks.py
new file mode 100755
index 0000000000..b2ecec5386
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/stacks.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+v1 = 5 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("stack-bottomdisplaystyleshiftdown%d-axisheight%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.StackBottomDisplayStyleShiftDown = v1
+f.math.StackBottomShiftDown = 0
+f.math.StackDisplayStyleGapMin = 0
+f.math.StackGapMin = 0
+f.math.StackTopDisplayStyleShiftUp = 0
+f.math.StackTopShiftUp = 0
+mathfont.save(f)
+
+v1 = 6 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("stack-bottomshiftdown%d-axisheight%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.StackBottomDisplayStyleShiftDown = 0
+f.math.StackBottomShiftDown = v1
+f.math.StackDisplayStyleGapMin = 0
+f.math.StackGapMin = 0
+f.math.StackTopDisplayStyleShiftUp = 0
+f.math.StackTopShiftUp = 0
+mathfont.save(f)
+
+v = 4 * mathfont.em
+f = mathfont.create("stack-displaystylegapmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.StackBottomDisplayStyleShiftDown = 0
+f.math.StackBottomShiftDown = 0
+f.math.StackDisplayStyleGapMin = v
+f.math.StackGapMin = 0
+f.math.StackTopDisplayStyleShiftUp = 0
+f.math.StackTopShiftUp = 0
+mathfont.save(f)
+
+v = 8 * mathfont.em
+f = mathfont.create("stack-gapmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = 0
+f.math.StackBottomDisplayStyleShiftDown = 0
+f.math.StackBottomShiftDown = 0
+f.math.StackDisplayStyleGapMin = 0
+f.math.StackGapMin = v
+f.math.StackTopDisplayStyleShiftUp = 0
+f.math.StackTopShiftUp = 0
+mathfont.save(f)
+
+v1 = 3 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("stack-topdisplaystyleshiftup%d-axisheight%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.StackBottomDisplayStyleShiftDown = 0
+f.math.StackBottomShiftDown = 0
+f.math.StackDisplayStyleGapMin = 0
+f.math.StackGapMin = 0
+f.math.StackTopDisplayStyleShiftUp = v1
+f.math.StackTopShiftUp = 0
+mathfont.save(f)
+
+v1 = 9 * mathfont.em
+v2 = 1 * mathfont.em
+f = mathfont.create("stack-topshiftup%d-axisheight%d" % (v1, v2),
+ "Copyright (c) 2016 MathML Association")
+f.math.AxisHeight = v2
+f.math.StackBottomDisplayStyleShiftDown = 0
+f.math.StackBottomShiftDown = 0
+f.math.StackDisplayStyleGapMin = 0
+f.math.StackGapMin = 0
+f.math.StackTopDisplayStyleShiftUp = 0
+f.math.StackTopShiftUp = v1
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/stretchstacks.py b/testing/web-platform/tests/mathml/tools/stretchstacks.py
new file mode 100755
index 0000000000..e6c0b8b4dd
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/stretchstacks.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+arrowCodePoint = 0x2192 # horizontal stretch operator
+
+v = 3 * mathfont.em
+f = mathfont.create("stretchstack-bottomshiftdown%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, arrowCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = v
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 11 * mathfont.em
+f = mathfont.create("stretchstack-gapbelowmin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, arrowCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = v
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 5 * mathfont.em
+f = mathfont.create("stretchstack-topshiftup%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, arrowCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = v
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 7 * mathfont.em
+f = mathfont.create("stretchstack-gapabovemin%d" % v,
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, arrowCodePoint)
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = v
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/stretchy-centered-on-baseline.py b/testing/web-platform/tests/mathml/tools/stretchy-centered-on-baseline.py
new file mode 100755
index 0000000000..5ccfca3f1e
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/stretchy-centered-on-baseline.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+# Create a WOFF font with glyphs for all the operator strings.
+font = mathfont.create("stretchy-centered-on-baseline",
+ "Copyright (c) 2023 Igalia S.L.")
+
+# Set parameters for stretchy tests.
+font.math.MinConnectorOverlap = mathfont.em // 2
+
+# Make sure that underover parameters don't add extra spacing.
+font.math.LowerLimitBaselineDropMin = 0
+font.math.LowerLimitGapMin = 0
+font.math.StretchStackBottomShiftDown = 0
+font.math.StretchStackGapAboveMin = 0
+font.math.UnderbarVerticalGap = 0
+font.math.UnderbarExtraDescender = 0
+font.math.UpperLimitBaselineRiseMin = 0
+font.math.UpperLimitGapMin = 0
+font.math.StretchStackTopShiftUp = 0
+font.math.StretchStackGapBelowMin = 0
+font.math.OverbarVerticalGap = 0
+font.math.AccentBaseHeight = 0
+font.math.OverbarExtraAscender = 0
+
+# These two characters will be stretchable in both directions.
+horizontalArrow = 0x295A # LEFTWARDS HARPOON WITH BARB UP FROM BAR
+verticalArrow = 0x295C # UPWARDS HARPOON WITH BARB RIGHT FROM BAR
+
+mathfont.createSizeVariants(font, aUsePUA=True, aCenterOnBaseline=True)
+
+# Add stretchy vertical and horizontal constructions for the horizontal arrow.
+mathfont.createSquareGlyph(font, horizontalArrow)
+mathfont.createStretchy(font, horizontalArrow, True)
+mathfont.createStretchy(font, horizontalArrow, False)
+
+# Add stretchy vertical and horizontal constructions for the vertical arrow.
+mathfont.createSquareGlyph(font, verticalArrow)
+mathfont.createStretchy(font, verticalArrow, True)
+mathfont.createStretchy(font, verticalArrow, False)
+
+mathfont.save(font)
diff --git a/testing/web-platform/tests/mathml/tools/stretchy.py b/testing/web-platform/tests/mathml/tools/stretchy.py
new file mode 100755
index 0000000000..34530f5792
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/stretchy.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+# Create a WOFF font with glyphs for all the operator strings.
+font = mathfont.create("stretchy", "Copyright (c) 2021 Igalia S.L.")
+
+# Set parameters for stretchy tests.
+font.math.MinConnectorOverlap = mathfont.em // 2
+
+# Make sure that underover parameters don't add extra spacing.
+font.math.LowerLimitBaselineDropMin = 0
+font.math.LowerLimitGapMin = 0
+font.math.StretchStackBottomShiftDown = 0
+font.math.StretchStackGapAboveMin = 0
+font.math.UnderbarVerticalGap = 0
+font.math.UnderbarExtraDescender = 0
+font.math.UpperLimitBaselineRiseMin = 0
+font.math.UpperLimitGapMin = 0
+font.math.StretchStackTopShiftUp = 0
+font.math.StretchStackGapBelowMin = 0
+font.math.OverbarVerticalGap = 0
+font.math.AccentBaseHeight = 0
+font.math.OverbarExtraAscender = 0
+
+# These two characters will be stretchable in both directions.
+horizontalArrow = 0x295A # LEFTWARDS HARPOON WITH BARB UP FROM BAR
+verticalArrow = 0x295C # UPWARDS HARPOON WITH BARB RIGHT FROM BAR
+
+mathfont.createSizeVariants(font, aUsePUA=True, aCenterOnBaseline=False)
+
+# Add stretchy vertical and horizontal constructions for the horizontal arrow.
+mathfont.createSquareGlyph(font, horizontalArrow)
+mathfont.createStretchy(font, horizontalArrow, True)
+mathfont.createStretchy(font, horizontalArrow, False)
+
+# Add stretchy vertical and horizontal constructions for the vertical arrow.
+mathfont.createSquareGlyph(font, verticalArrow)
+mathfont.createStretchy(font, verticalArrow, True)
+mathfont.createStretchy(font, verticalArrow, False)
+
+mathfont.save(font)
diff --git a/testing/web-platform/tests/mathml/tools/underover.py b/testing/web-platform/tests/mathml/tools/underover.py
new file mode 100755
index 0000000000..469829cba2
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/underover.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+breveCodePoint = 0x2D8 # accent operator
+degreeCodePoint = 0xB0 # nonaccent operator
+accentBaseHeight = 4 * mathfont.em
+
+v = 3 * mathfont.em
+f = mathfont.create("underover-accentbaseheight%d-overbarextraascender%d" % (accentBaseHeight, v),
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, breveCodePoint)
+mathfont.createSquareGlyph(f, degreeCodePoint)
+f.math.AccentBaseHeight = accentBaseHeight
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = v
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 11 * mathfont.em
+f = mathfont.create("underover-accentbaseheight%d-overbarverticalgap%d" % (accentBaseHeight, v),
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, breveCodePoint)
+mathfont.createSquareGlyph(f, degreeCodePoint)
+f.math.AccentBaseHeight = accentBaseHeight
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = v
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 5 * mathfont.em
+f = mathfont.create("underover-accentbaseheight%d-underbarextradescender%d" % (accentBaseHeight, v),
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, breveCodePoint)
+mathfont.createSquareGlyph(f, degreeCodePoint)
+f.math.AccentBaseHeight = accentBaseHeight
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = v
+f.math.UnderbarVerticalGap = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
+
+v = 7 * mathfont.em
+f = mathfont.create("underover-accentbaseheight%d-underbarverticalgap%d" % (accentBaseHeight, v),
+ "Copyright (c) 2016 MathML Association")
+mathfont.createSquareGlyph(f, breveCodePoint)
+mathfont.createSquareGlyph(f, degreeCodePoint)
+f.math.AccentBaseHeight = accentBaseHeight
+f.math.LowerLimitBaselineDropMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.OverbarExtraAscender = 0
+f.math.OverbarVerticalGap = 0
+f.math.StretchStackBottomShiftDown = 0
+f.math.StretchStackGapAboveMin = 0
+f.math.StretchStackGapBelowMin = 0
+f.math.StretchStackTopShiftUp = 0
+f.math.UnderbarExtraDescender = 0
+f.math.UnderbarVerticalGap = v
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.UpperLimitGapMin = 0
+mathfont.save(f)
diff --git a/testing/web-platform/tests/mathml/tools/use-typo-lineheight.py b/testing/web-platform/tests/mathml/tools/use-typo-lineheight.py
new file mode 100755
index 0000000000..6509a65175
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/use-typo-lineheight.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+import fontforge
+
+font = fontforge.font()
+font.em = 1000
+typoLineHeight = 2300
+winHeight = 5000
+name = "font-lineheight%d-typolineheight%d" % (winHeight, typoLineHeight)
+font.fontname = name
+font.familyname = name
+font.fullname = name
+font.copyright = "Copyright (c) 2016 MathML Association"
+
+glyph = font.createChar(ord(" "), "space")
+glyph.width = 1000
+glyph = font.createChar(ord("O"))
+pen = glyph.glyphPen()
+pen.moveTo(0, -200)
+pen.lineTo(0, 800)
+pen.lineTo(1000, 800)
+pen.lineTo(1000, -200)
+pen.closePath()
+
+font.os2_typoascent_add = False
+font.os2_typoascent = 800
+font.os2_typodescent_add = False
+font.os2_typodescent = -200
+font.os2_typolinegap = typoLineHeight - \
+ (font.os2_typoascent - font.os2_typodescent)
+
+font.hhea_ascent = winHeight // 2
+font.hhea_ascent_add = False
+font.hhea_descent = -winHeight // 2
+font.hhea_descent_add = False
+font.hhea_linegap = 0
+
+font.os2_winascent = winHeight // 2
+font.os2_winascent_add = False
+font.os2_windescent = winHeight // 2
+font.os2_windescent_add = False
+
+font.os2_use_typo_metrics = True
+
+path = "../../fonts/math/lineheight%d-typolineheight%d.woff" % (
+ winHeight, typoLineHeight)
+print("Generating %s..." % path, end="")
+font.generate(path)
+if font.validate() == 0:
+ print(" done.")
+else:
+ print(" validation error!")
+ exit(1)
diff --git a/testing/web-platform/tests/mathml/tools/utils/__init__.py b/testing/web-platform/tests/mathml/tools/utils/__init__.py
new file mode 100644
index 0000000000..c1e4c6d32e
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/utils/__init__.py
@@ -0,0 +1 @@
+# This file is required for Python to search this directory for modules.
diff --git a/testing/web-platform/tests/mathml/tools/utils/mathfont.py b/testing/web-platform/tests/mathml/tools/utils/mathfont.py
new file mode 100644
index 0000000000..3eff0ac03d
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/utils/mathfont.py
@@ -0,0 +1,232 @@
+import fontforge
+
+PUA_startCodePoint = 0xE000
+em = 1000
+
+
+def create(aName, aCopyRight):
+ print("Generating %s.woff..." % aName, end="")
+ mathFont = fontforge.font()
+ mathFont.fontname = aName
+ mathFont.familyname = aName
+ mathFont.fullname = aName
+ mathFont.copyright = aCopyRight
+ mathFont.encoding = "UnicodeFull"
+
+ # Create a space character. Also force the creation of some MATH subtables
+ # so that OTS will not reject the MATH table.
+ g = mathFont.createChar(ord(" "), "space")
+ g.width = em
+ g.italicCorrection = 0
+ g.topaccent = 0
+ g.mathKern.bottomLeft = tuple([(0, 0)])
+ g.mathKern.bottomRight = tuple([(0, 0)])
+ g.mathKern.topLeft = tuple([(0, 0)])
+ g.mathKern.topRight = tuple([(0, 0)])
+ mathFont[ord(" ")].horizontalVariants = "space"
+ mathFont[ord(" ")].verticalVariants = "space"
+ return mathFont
+
+
+def drawRectangleGlyph(glyph, width, ascent, descent=0, padding_left=0):
+ p = glyph.glyphPen()
+ p.moveTo(padding_left, -descent)
+ p.lineTo(padding_left, ascent)
+ p.lineTo(padding_left + width, ascent)
+ p.lineTo(padding_left + width, -descent)
+ p.closePath()
+ glyph.width = padding_left + width
+
+
+def createSquareGlyph(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ drawRectangleGlyph(g, em, em, 0)
+
+
+def drawHexaDigit(aGlyph, aX, aValue):
+ t = em / 10
+ p = aGlyph.glyphPen(replace=False)
+ if aValue == 0:
+ p.moveTo(aX + t, t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.closePath()
+ elif aValue == 1:
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ elif aValue == 2:
+ p.moveTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + t, em / 2)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ elif aValue == 3:
+ p.moveTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + t, t)
+ p.endPath()
+ p.moveTo(aX + t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 4:
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ p.moveTo(aX + t, em - t)
+ p.lineTo(aX + t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 5:
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, em / 2)
+ p.lineTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + t, t)
+ p.endPath()
+ elif aValue == 6:
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 7:
+ p.moveTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ elif aValue == 8:
+ p.moveTo(aX + t, t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.closePath()
+ p.moveTo(aX + 2.5 * t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 9:
+ p.moveTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 10: # A
+ p.moveTo(aX + t, t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ p.moveTo(aX + 2.5 * t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 11: # b
+ p.moveTo(aX + t, em - t)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 12: # C
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ elif aValue == 13: # d
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + t, em / 2)
+ p.lineTo(aX + em / 2 - 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 14: # E
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, t)
+ p.lineTo(aX + em / 2 - t, t)
+ p.endPath()
+ p.moveTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + 2.5 * t, em / 2)
+ p.endPath()
+ elif aValue == 15: # F
+ p.moveTo(aX + em / 2 - t, em - t)
+ p.lineTo(aX + t, em - t)
+ p.lineTo(aX + t, t)
+ p.endPath()
+ p.moveTo(aX + em / 2 - t, em / 2)
+ p.lineTo(aX + 2.5 * t, em / 2)
+ p.endPath()
+
+
+def createGlyphFromValue(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ value = aCodePoint
+ for i in range(0, 5):
+ drawHexaDigit(g, (5 - (i + 1)) * em / 2, value % 16)
+ value /= 16
+ g.width = 5 * em // 2
+ g.stroke("circular", em / 10, "square", "miter", "cleanup")
+
+
+def createSizeVariants(aFont, aUsePUA=False, aCenterOnBaseline=False):
+ if aUsePUA:
+ codePoint = PUA_startCodePoint
+ else:
+ codePoint = -1
+ for size in (0, 1, 2, 3):
+ g = aFont.createChar(codePoint, "v%d" % size)
+ if aCenterOnBaseline:
+ drawRectangleGlyph(g, em, (size + 1) * em / 2, (size + 1) * em / 2)
+ else:
+ drawRectangleGlyph(g, em, (size + 1) * em, 0)
+ if aUsePUA:
+ codePoint += 1
+ g = aFont.createChar(codePoint, "h%d" % size)
+ if aCenterOnBaseline:
+ drawRectangleGlyph(g, (size + 1) * em, em / 2, em / 2)
+ else:
+ drawRectangleGlyph(g, (size + 1) * em, em, 0)
+ if aUsePUA:
+ codePoint += 1
+
+
+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, em, 3 * em),
+ ("h1", True, em, em, 2 * em))
+ else:
+ aFont[codePoint].verticalVariants = "v0 v1 v2 v3"
+ # Part: (glyphName, isExtender, startConnector, endConnector, fullAdvance)
+ aFont[codePoint].verticalComponents = \
+ (("v2", False, 0, em, 3 * em),
+ ("v1", True, em, em, 2 * em))
+
+
+def save(aFont):
+ aFont.em = em
+ aFont.ascent = aFont.hhea_ascent = aFont.os2_typoascent = em
+ aFont.descent = aFont.hhea_descent = aFont.os2_typodescent = 0
+ # aFont.os2_winascent, aFont.os2_windescent should be the maximum of
+ # ascent/descent for all glyphs. Does fontforge compute them automatically?
+ aFont.hhea_ascent_add = aFont.hhea_descent_add = 0
+ aFont.os2_typoascent_add = aFont.os2_typodescent_add = 0
+ aFont.os2_winascent_add = aFont.os2_windescent_add = 0
+ aFont.os2_use_typo_metrics = True
+ aFont.generate("../../fonts/math/%s.woff" % aFont.fontname)
+ if aFont.validate() == 0:
+ print(" done.")
+ else:
+ print(" validation error!")
+ exit(1)
diff --git a/testing/web-platform/tests/mathml/tools/utils/misc.py b/testing/web-platform/tests/mathml/tools/utils/misc.py
new file mode 100644
index 0000000000..cc3b21906e
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/utils/misc.py
@@ -0,0 +1,35 @@
+import os
+import progressbar
+from urllib.request import urlopen
+
+UnicodeXMLURL = "https://raw.githubusercontent.com/w3c/xml-entities/gh-pages/unicode.xml"
+InlineAxisOperatorsURL = "https://w3c.github.io/mathml-core/tables/inline-axis-operators.txt"
+
+
+def downloadWithProgressBar(url, outputDirectory="./", forceDownload=False):
+
+ baseName = os.path.basename(url)
+ fileName = os.path.join(outputDirectory, baseName)
+
+ if not forceDownload and os.path.exists(fileName):
+ return fileName
+
+ request = urlopen(url)
+ totalSize = int(request.info().get('Content-Length').strip())
+ bar = progressbar.ProgressBar(maxval=totalSize).start()
+
+ chunkSize = 16 * 1024
+ downloaded = 0
+ print("Downloading %s" % url)
+ os.umask(0o002)
+ with open(fileName, 'wb') as fp:
+ while True:
+ chunk = request.read(chunkSize)
+ downloaded += len(chunk)
+ bar.update(downloaded)
+ if not chunk:
+ break
+ fp.write(chunk)
+ bar.finish()
+
+ return fileName
diff --git a/testing/web-platform/tests/mathml/tools/xHeight.py b/testing/web-platform/tests/mathml/tools/xHeight.py
new file mode 100755
index 0000000000..ad0f8b9e02
--- /dev/null
+++ b/testing/web-platform/tests/mathml/tools/xHeight.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+
+from utils import mathfont
+import fontforge
+
+v = mathfont.em / 2
+f = mathfont.create("xheight%d" % v,
+ "Copyright (c) 2016 MathML Association")
+g = f.createChar(ord('x'))
+mathfont.drawRectangleGlyph(g, mathfont.em, v, 0)
+assert f.xHeight == v, "Bad x-height value!"
+mathfont.save(f)