diff --git a/Lib/ufo2ft/constants.py b/Lib/ufo2ft/constants.py index 696839fc..eed6f3a1 100644 --- a/Lib/ufo2ft/constants.py +++ b/Lib/ufo2ft/constants.py @@ -43,6 +43,11 @@ class CFFOptimization(IntEnum): # ] COLR_CLIP_BOXES_KEY = UFO2FT_PREFIX + "colrClipBoxes" +GLYPHS_MATH_PREFIX = "com.nagwa.MATHPlugin." +GLYPHS_MATH_CONSTANTS_KEY = GLYPHS_MATH_PREFIX + "constants" +GLYPHS_MATH_VARIANTS_KEY = GLYPHS_MATH_PREFIX + "variants" +GLYPHS_MATH_EXTENDED_SHAPE_KEY = GLYPHS_MATH_PREFIX + "extendedShape" + OBJECT_LIBS_KEY = "public.objectLibs" OPENTYPE_CATEGORIES_KEY = "public.openTypeCategories" OPENTYPE_META_KEY = "public.openTypeMeta" diff --git a/Lib/ufo2ft/outlineCompiler.py b/Lib/ufo2ft/outlineCompiler.py index 8bd2fffd..8ae12f39 100644 --- a/Lib/ufo2ft/outlineCompiler.py +++ b/Lib/ufo2ft/outlineCompiler.py @@ -30,6 +30,10 @@ COLOR_LAYERS_KEY, COLOR_PALETTES_KEY, COLR_CLIP_BOXES_KEY, + GLYPHS_MATH_CONSTANTS_KEY, + GLYPHS_MATH_EXTENDED_SHAPE_KEY, + GLYPHS_MATH_PREFIX, + GLYPHS_MATH_VARIANTS_KEY, OPENTYPE_META_KEY, OPENTYPE_POST_UNDERLINE_POSITION_KEY, UNICODE_VARIATION_SEQUENCES_KEY, @@ -94,6 +98,7 @@ class BaseOutlineCompiler: "vhea", "COLR", "CPAL", + "MATH", "meta", ] ) @@ -175,6 +180,8 @@ def compile(self): self.setupTable_CPAL() if self.meta: self.setupTable_meta() + if any(key.startswith(GLYPHS_MATH_PREFIX) for key in self.ufo.lib): + self.setupTable_MATH() self.setupOtherTables() if self.colorLayers and self.colrAutoClipBoxes: self._computeCOLRClipBoxes() @@ -1050,6 +1057,181 @@ def setupTable_meta(self): f"public.openTypeMeta '{key}' value should be bytes or a string." ) + def _bboxWidth(self, glyph): + bbox = self.glyphBoundingBoxes[glyph] + if bbox is None: + return 0 + return bbox.xMax - bbox.xMin + + def _bboxHeight(self, glyph): + bbox = self.glyphBoundingBoxes[glyph] + if bbox is None: + return 0 + return bbox.yMax - bbox.yMin + + def setupTable_MATH(self): + """ + This builds MATH table based on data in the UFO font. The data is stored + either in private font/glyph lib keys or as glyph anchors with specific + names. + + The data is based on GlyphsApp MATH plugin data as written out by + glyphsLib to the UFO font. + + The font lib keys are: + - com.nagwa.MATHPlugin.constants: a dictionary of MATH constants as + expected by fontTools.otlLib.builder.buildMathTable(). Example: + + ufo.lib["com.nagwa.MATHPlugin.constants"] = { + "ScriptPercentScaleDown": 70, + "ScriptScriptPercentScaleDown": 60, + ... + } + + - com.nagwa.MATHPlugin.extendedShape: a list of glyph names that + are extended shapes. Example: + + ufo.lib["com.nagwa.MATHPlugin.extendedShapes"] = ["integral", "radical"] + + The glyph lib keys are: + - com.nagwa.MATHPlugin.variants: a dictionary of MATH glyph variants + keyed by glyph names, and each value is a dictionary with keys + "hVariants", "vVariants", "hAssembly", and "vAssembly". Example: + + ufo["braceleft"].lib["com.nagwa.MATHPlugin.variants"] = { + "vVariants": ["braceleft", "braceleft.s1", "braceleft.s2"], + "vAssembly": [ + # glyph name, flags, start connector length, end connector length + ["braceleft.bottom", 0, 0, 200], + ["braceleft.extender", 1, 200, 200], + ["braceleft.middle", 0, 100, 100], + ["braceleft.extender", 1, 200, 200], + ["braceleft.top", 0, 200, 0], + ], + } + + The anchors are: + - math.ic: italic correction anchor + - math.ta: top accent attachment anchor + - math.tr*: top right kerning anchors + - math.tl*: top left kerning anchors + - math.br*: bottom right kerning anchors + - math.bl*: bottom left kerning anchors + """ + if "MATH" not in self.tables: + return + + from fontTools.otlLib.builder import buildMathTable + + ufo = self.ufo + constants = ufo.lib.get(GLYPHS_MATH_CONSTANTS_KEY) + min_connector_overlap = constants.pop("MinConnectorOverlap", 0) + + italics_correction = {} + top_accent_attachment = {} + math_kerns = {} + kerning_sides = { + "tr": "TopRight", + "tl": "TopLeft", + "br": "BottomRight", + "bl": "BottomLeft", + } + for name, glyph in self.allGlyphs.items(): + kerns = {} + for anchor in glyph.anchors: + if anchor.name == "math.ic": + # The anchor x position is absolute, but we want + # a value relative to the glyph's width. + italics_correction[name] = anchor.x - glyph.width + if anchor.name == "math.ta": + top_accent_attachment[name] = anchor.x + for aName in kerning_sides.keys(): + if anchor.name.startswith(f"math.{aName}"): + side = kerning_sides[aName] + # The anchor x positions are absolute, but we want + # values relative to the glyph's width/origin. + x, y = anchor.x, anchor.y + if side.endswith("Right"): + x -= glyph.width + elif side.endswith("Left"): + x = -x + kerns.setdefault(side, []).append([x, y]) + if kerns: + math_kerns[name] = {} + # Convert anchor positions to correction heights and kern + # values. + for side, pts in kerns.items(): + pts = sorted(pts, key=lambda pt: pt[1]) + # Y positions, the last one is ignored as the last kern + # value is applied to all heights greater than the last one. + correctionHeights = [pt[1] for pt in pts[:-1]] + # X positions + kernValues = [pt[0] for pt in pts] + math_kerns[name][side] = (correctionHeights, kernValues) + + # buildMathTable takes two dictionaries of glyph variants, one for + # horizontal variants and one for vertical variants, and items are + # tuples of glyph name and the advance width/height of the variant. + # Here we convert the UFO data to the expected format and measure the + # advances. + h_variants = {} + v_variants = {} + # It also takes two dictionaries of glyph assemblies, one for + # horizontal assemblies and one for vertical assemblies, and items are + # lists of tuples of assembly parts and italics correction, and the + # assembly part includes the advance width/height of the part. Here we + # convert the UFO data to the expected format and measure the advances. + h_assemblies = {} + v_assemblies = {} + for name, glyph in self.allGlyphs.items(): + if GLYPHS_MATH_VARIANTS_KEY in glyph.lib: + variants = glyph.lib[GLYPHS_MATH_VARIANTS_KEY] + if "hVariants" in variants: + h_variants[name] = [ + (n, self._bboxWidth(n)) for n in variants["hVariants"] + ] + if "vVariants" in variants: + v_variants[name] = [ + (n, self._bboxHeight(n)) for n in variants["vVariants"] + ] + if "hAssembly" in variants: + parts = variants["hAssembly"] + h_assemblies[name] = ( + [(*part, self._bboxWidth(part[0])) for part in parts], + # If the last part has italic correction, we use it as + # the assembly's. + italics_correction.pop(parts[-1][0], 0), + ) + if "vAssembly" in variants: + parts = variants["vAssembly"] + v_assemblies[name] = ( + [(*part, self._bboxHeight(part[0])) for part in parts], + # If the last part has italic correction, we use it as + # the assembly's. + italics_correction.pop(parts[-1][0], 0), + ) + + # Collect the set of extended shapes, and if a shape has vertical + # variants, add the variants to the set. + extended_shapes = set(ufo.lib.get(GLYPHS_MATH_EXTENDED_SHAPE_KEY, [])) + for name, variants in v_variants.items(): + if name in extended_shapes: + extended_shapes.update(v[0] for v in variants) + + buildMathTable( + self.otf, + constants=constants, + italicsCorrections=italics_correction, + topAccentAttachments=top_accent_attachment, + extendedShapes=extended_shapes, + mathKerns=math_kerns, + minConnectorOverlap=min_connector_overlap, + vertGlyphVariants=v_variants, + horizGlyphVariants=h_variants, + vertGlyphAssembly=v_assemblies, + horizGlyphAssembly=h_assemblies, + ) + def setupOtherTables(self): """ Make the other tables. The default implementation does nothing. @@ -1652,6 +1834,7 @@ def __init__( self.draw = self._drawDefaultNotdef self.drawPoints = self._drawDefaultNotdefPoints self.reverseContour = reverseContour + self.lib = {} def __len__(self): if self.name == ".notdef": diff --git a/requirements.txt b/requirements.txt index aed05ff6..4ddd37a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fonttools[ufo,lxml]==4.48.1 +fonttools[ufo,lxml]==4.49.0 defcon==0.10.3 compreffor==0.5.5 booleanOperations==0.9.0 diff --git a/setup.py b/setup.py index 762d06f9..e819092c 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup_requires=pytest_runner + wheel + ["setuptools_scm"], tests_require=["pytest>=2.8"], install_requires=[ - "fonttools[ufo]>=4.47.2", + "fonttools[ufo]>=4.49.0", "cffsubr>=0.3.0", "booleanOperations>=0.9.0", ], diff --git a/tests/data/TestMathFont-Regular.ufo/features.fea b/tests/data/TestMathFont-Regular.ufo/features.fea new file mode 100644 index 00000000..1dafe8fd --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/features.fea @@ -0,0 +1,4 @@ +# Prefix: Languagesystems +languagesystem DFLT dflt; +languagesystem math dflt; + diff --git a/tests/data/TestMathFont-Regular.ufo/fontinfo.plist b/tests/data/TestMathFont-Regular.ufo/fontinfo.plist new file mode 100644 index 00000000..b0b9f7a9 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/fontinfo.plist @@ -0,0 +1,46 @@ + + + + + ascender + 800 + capHeight + 656 + descender + -200 + familyName + Test Math Font + italicAngle + 0 + openTypeHeadCreated + 2024/02/12 18:20:25 + openTypeOS2Type + + 3 + + postscriptBlueValues + + -18 + 0 + + postscriptOtherBlues + + -201 + -200 + + postscriptUnderlinePosition + -150 + postscriptUnderlineThickness + 50 + styleName + Regular + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 449 + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/contents.plist b/tests/data/TestMathFont-Regular.ufo/glyphs/contents.plist new file mode 100644 index 00000000..455616bb --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/contents.plist @@ -0,0 +1,38 @@ + + + + + parenleft + parenleft.glif + parenleft.bot + parenleft.bot.glif + parenleft.ext + parenleft.ext.glif + parenleft.size1 + parenleft.size1.glif + parenleft.size2 + parenleft.size2.glif + parenleft.size3 + parenleft.size3.glif + parenleft.size4 + parenleft.size4.glif + parenleft.top + parenleft.top.glif + parenright + parenright.glif + parenright.bot + parenright.bot.glif + parenright.ext + parenright.ext.glif + parenright.size1 + parenright.size1.glif + parenright.size2 + parenright.size2.glif + parenright.size3 + parenright.size3.glif + parenright.size4 + parenright.size4.glif + parenright.top + parenright.top.glif + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.bot.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.bot.glif new file mode 100644 index 00000000..e1c7506b --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.bot.glif @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.ext.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.ext.glif new file mode 100644 index 00000000..2ed6d70d --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.ext.glif @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.glif new file mode 100644 index 00000000..b7545149 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.glif @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + com.nagwa.MATHPlugin.variants + + vAssembly + + + parenleft.bot + 0 + 0 + 314 + + + parenleft.ext + 1 + 630 + 630 + + + parenleft.top + 0 + 314 + 0 + + + vVariants + + parenleft + parenleft.size1 + parenleft.size2 + parenleft.size3 + parenleft.size4 + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size1.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size1.glif new file mode 100644 index 00000000..6d0b1d7b --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size1.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size2.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size2.glif new file mode 100644 index 00000000..a40544a0 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size2.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size3.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size3.glif new file mode 100644 index 00000000..8ef2734f --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size3.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size4.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size4.glif new file mode 100644 index 00000000..ff2c36b1 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.size4.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.top.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.top.glif new file mode 100644 index 00000000..faed22d9 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.top.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.bot.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.bot.glif new file mode 100644 index 00000000..f142d880 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.bot.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.ext.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.ext.glif new file mode 100644 index 00000000..dfa357dc --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.ext.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.glif new file mode 100644 index 00000000..04ce4166 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.glif @@ -0,0 +1,44 @@ + + + + + + + + + + com.nagwa.MATHPlugin.variants + + vAssembly + + + parenright.bot + 0 + 0 + 314 + + + parenright.ext + 1 + 630 + 630 + + + parenright.top + 0 + 314 + 0 + + + vVariants + + parenright + parenright.size1 + parenright.size2 + parenright.size3 + parenright.size4 + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size1.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size1.glif new file mode 100644 index 00000000..df14053e --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size1.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size2.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size2.glif new file mode 100644 index 00000000..7993e2ad --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size2.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size3.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size3.glif new file mode 100644 index 00000000..6db19801 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size3.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size4.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size4.glif new file mode 100644 index 00000000..a481dcdc --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.size4.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.top.glif b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.top.glif new file mode 100644 index 00000000..29212ee3 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/glyphs/parenright.top.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestMathFont-Regular.ufo/layercontents.plist b/tests/data/TestMathFont-Regular.ufo/layercontents.plist new file mode 100644 index 00000000..b9c1a4f2 --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/tests/data/TestMathFont-Regular.ufo/lib.plist b/tests/data/TestMathFont-Regular.ufo/lib.plist new file mode 100644 index 00000000..74ccf15b --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/lib.plist @@ -0,0 +1,147 @@ + + + + + com.github.googlei18n.ufo2ft.useProductionNames + + com.nagwa.MATHPlugin.constants + + AccentBaseHeight + 449 + AxisHeight + 250 + DelimitedSubFormulaMinHeight + 1010 + DisplayOperatorMinHeight + 2260 + FlattenedAccentBaseHeight + 656 + FractionDenomDisplayStyleGapMin + 137 + FractionDenominatorDisplayStyleShiftDown + 685 + FractionDenominatorGapMin + 45 + FractionDenominatorShiftDown + 345 + FractionNumDisplayStyleGapMin + 137 + FractionNumeratorDisplayStyleShiftUp + 677 + FractionNumeratorGapMin + 45 + FractionNumeratorShiftUp + 394 + FractionRuleThickness + 45 + LowerLimitBaselineDropMin + 600 + LowerLimitGapMin + 166 + MinConnectorOverlap + 20 + OverbarExtraAscender + 45 + OverbarRuleThickness + 45 + OverbarVerticalGap + 137 + RadicalDegreeBottomRaisePercent + 60 + RadicalDisplayStyleVerticalGap + 158 + RadicalExtraAscender + 70 + RadicalKernAfterDegree + -400 + RadicalKernBeforeDegree + 277 + RadicalRuleThickness + 45 + RadicalVerticalGap + 57 + ScriptPercentScaleDown + 70 + ScriptScriptPercentScaleDown + 50 + SkewedFractionHorizontalGap + 400 + SkewedFractionVerticalGap + 60 + SpaceAfterScript + 50 + StackBottomDisplayStyleShiftDown + 685 + StackBottomShiftDown + 345 + StackDisplayStyleGapMin + 321 + StackGapMin + 137 + StackTopDisplayStyleShiftUp + 677 + StackTopShiftUp + 444 + StretchStackBottomShiftDown + 600 + StretchStackGapAboveMin + 111 + StretchStackGapBelowMin + 166 + StretchStackTopShiftUp + 199 + SubSuperscriptGapMin + 183 + SubscriptBaselineDropMin + 50 + SubscriptShiftDown + 149 + SubscriptTopMax + 359 + SuperscriptBaselineDropMax + 385 + SuperscriptBottomMaxWithSubscript + 359 + SuperscriptBottomMin + 112 + SuperscriptShiftUp + 362 + SuperscriptShiftUpCramped + 289 + UnderbarExtraDescender + 45 + UnderbarRuleThickness + 45 + UnderbarVerticalGap + 137 + UpperLimitBaselineRiseMin + 199 + UpperLimitGapMin + 111 + + com.nagwa.MATHPlugin.extendedShape + + parenleft + parenright + + public.glyphOrder + + parenleft + parenleft.bot + parenleft.ext + parenleft.size1 + parenleft.size2 + parenleft.size3 + parenleft.size4 + parenleft.top + parenright + parenright.bot + parenright.ext + parenright.size1 + parenright.size2 + parenright.size3 + parenright.size4 + parenright.top + + + diff --git a/tests/data/TestMathFont-Regular.ufo/metainfo.plist b/tests/data/TestMathFont-Regular.ufo/metainfo.plist new file mode 100644 index 00000000..7b8b34ac --- /dev/null +++ b/tests/data/TestMathFont-Regular.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/tests/outlineCompiler_test.py b/tests/outlineCompiler_test.py index df52f4bf..86cc1b21 100644 --- a/tests/outlineCompiler_test.py +++ b/tests/outlineCompiler_test.py @@ -18,6 +18,9 @@ ) from ufo2ft.constants import ( GLYPHS_DONT_USE_PRODUCTION_NAMES, + GLYPHS_MATH_CONSTANTS_KEY, + GLYPHS_MATH_EXTENDED_SHAPE_KEY, + GLYPHS_MATH_VARIANTS_KEY, OPENTYPE_POST_UNDERLINE_POSITION_KEY, SPARSE_OTF_MASTER_TABLES, SPARSE_TTF_MASTER_TABLES, @@ -1403,6 +1406,28 @@ def test_achVendId_space_padded_if_less_than_4_chars( assert font["OS/2"].achVendID == expected +def test_MATH_table(FontClass): + ufo = FontClass(getpath("TestMathFont-Regular.ufo")) + result = compileTTF(ufo) + assert "MATH" in result + + math = result["MATH"].table + + for key, value in ufo.lib[GLYPHS_MATH_CONSTANTS_KEY].items(): + attr = getattr(math.MathConstants, key) + if isinstance(attr, int): + assert attr == value + else: + assert attr.Value == value + + extendedShapes = set(ufo.lib[GLYPHS_MATH_EXTENDED_SHAPE_KEY]) + for glyph in ufo.lib[GLYPHS_MATH_EXTENDED_SHAPE_KEY]: + if variants := ufo[glyph].lib.get(GLYPHS_MATH_VARIANTS_KEY): + extendedShapes.update(variants.get("vVariants", [])) + + assert set(math.MathGlyphInfo.ExtendedShapeCoverage.glyphs) == extendedShapes + + if __name__ == "__main__": import sys