Skip to content

Commit

Permalink
Merge pull request #819 from googlefonts/math-table
Browse files Browse the repository at this point in the history
outlineCompiler: Support generating MATH table
  • Loading branch information
khaledhosny authored Feb 17, 2024
2 parents 5586805 + 10937d4 commit 6656977
Show file tree
Hide file tree
Showing 27 changed files with 747 additions and 2 deletions.
5 changes: 5 additions & 0 deletions Lib/ufo2ft/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
183 changes: 183 additions & 0 deletions Lib/ufo2ft/outlineCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -94,6 +98,7 @@ class BaseOutlineCompiler:
"vhea",
"COLR",
"CPAL",
"MATH",
"meta",
]
)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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":
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand Down
4 changes: 4 additions & 0 deletions tests/data/TestMathFont-Regular.ufo/features.fea
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Prefix: Languagesystems
languagesystem DFLT dflt;
languagesystem math dflt;

46 changes: 46 additions & 0 deletions tests/data/TestMathFont-Regular.ufo/fontinfo.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ascender</key>
<integer>800</integer>
<key>capHeight</key>
<integer>656</integer>
<key>descender</key>
<integer>-200</integer>
<key>familyName</key>
<string>Test Math Font</string>
<key>italicAngle</key>
<integer>0</integer>
<key>openTypeHeadCreated</key>
<string>2024/02/12 18:20:25</string>
<key>openTypeOS2Type</key>
<array>
<integer>3</integer>
</array>
<key>postscriptBlueValues</key>
<array>
<integer>-18</integer>
<integer>0</integer>
</array>
<key>postscriptOtherBlues</key>
<array>
<integer>-201</integer>
<integer>-200</integer>
</array>
<key>postscriptUnderlinePosition</key>
<integer>-150</integer>
<key>postscriptUnderlineThickness</key>
<integer>50</integer>
<key>styleName</key>
<string>Regular</string>
<key>unitsPerEm</key>
<integer>1000</integer>
<key>versionMajor</key>
<integer>1</integer>
<key>versionMinor</key>
<integer>0</integer>
<key>xHeight</key>
<integer>449</integer>
</dict>
</plist>
38 changes: 38 additions & 0 deletions tests/data/TestMathFont-Regular.ufo/glyphs/contents.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>parenleft</key>
<string>parenleft.glif</string>
<key>parenleft.bot</key>
<string>parenleft.bot.glif</string>
<key>parenleft.ext</key>
<string>parenleft.ext.glif</string>
<key>parenleft.size1</key>
<string>parenleft.size1.glif</string>
<key>parenleft.size2</key>
<string>parenleft.size2.glif</string>
<key>parenleft.size3</key>
<string>parenleft.size3.glif</string>
<key>parenleft.size4</key>
<string>parenleft.size4.glif</string>
<key>parenleft.top</key>
<string>parenleft.top.glif</string>
<key>parenright</key>
<string>parenright.glif</string>
<key>parenright.bot</key>
<string>parenright.bot.glif</string>
<key>parenright.ext</key>
<string>parenright.ext.glif</string>
<key>parenright.size1</key>
<string>parenright.size1.glif</string>
<key>parenright.size2</key>
<string>parenright.size2.glif</string>
<key>parenright.size3</key>
<string>parenright.size3.glif</string>
<key>parenright.size4</key>
<string>parenright.size4.glif</string>
<key>parenright.top</key>
<string>parenright.top.glif</string>
</dict>
</plist>
18 changes: 18 additions & 0 deletions tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.bot.glif
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='UTF-8'?>
<glyph name="parenleft.bot" format="2">
<advance width="645"/>
<outline>
<contour>
<point x="595" y="-1129" type="line"/>
<point x="291" y="-729"/>
<point x="136" y="-184"/>
<point x="136" y="373" type="curve" smooth="yes"/>
<point x="136" y="687" type="line"/>
<point x="90" y="687" type="line"/>
<point x="90" y="373" type="line" smooth="yes"/>
<point x="90" y="-192"/>
<point x="247" y="-749"/>
<point x="559" y="-1157" type="curve"/>
</contour>
</outline>
</glyph>
12 changes: 12 additions & 0 deletions tests/data/TestMathFont-Regular.ufo/glyphs/parenleft.ext.glif
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<glyph name="parenleft.ext" format="2">
<advance width="645"/>
<outline>
<contour>
<point x="136" y="0" type="line"/>
<point x="136" y="800" type="line"/>
<point x="90" y="800" type="line"/>
<point x="90" y="0" type="line"/>
</contour>
</outline>
</glyph>
Loading

0 comments on commit 6656977

Please sign in to comment.