OpenType MATH in HarfBuzz
TL;DR:
-
•
Work is in progress to add OpenType MATH support in HarfBuzz and will be instrumental for many math rendering engines relying on that library, including browsers.
-
•
For stretchy operators, an efficient way to determine the required number of glyphs and their overlaps has been implemented and is described here.
In the context of Igalia browser team effort to implement MathML support using TeX rules and OpenType features, I have started implementation of OpenType MATH support in HarfBuzz. This table from the OpenType standard is made of three subtables:
-
•
The MathConstants table, which contains layout constants. For example, the thickness of the fraction bar of .
-
•
The MathGlyphInfo table, which contains glyph properties. For instance, the italic correction indicating how slanted an integral is e.g. to properly place the subscript in .
-
•
The MathVariants table, which provides larger size variants for a base glyph or data to build a glyph assembly. For example, either a larger parenthesis or a assembly of U+239B, U+239C, U+239D to write something like:
Code to parse this table was added to Gecko and WebKit two years ago. The existing code to build glyph assembly in these Web engines was adapted to use the MathVariants data instead of only private tables. However, as we will see below the MathVariants data to build glyph assembly is more general, with arbitrary number of glyphs or with additional constraints on glyph overlaps. Also there are various fallback mechanisms for old fonts and other bugs that I think we could get rid of when we move to OpenType MATH fonts only.
In order to add MathML support in Blink, it is very easy to import the OpenType MATH parsing code from WebKit. However, after discussions with some Google developers, it seems that the best option is to directly add support for this table in HarfBuzz. Since this library is used by Gecko, by WebKit (at least the GTK port) and by many other applications such as Servo, XeTeX or LibreOffice it make senses to share the implementation to improve math rendering everywhere.
The idea for HarfBuzz is to add an API to
-
1.
Expose data from the MathConstants and MathGlyphInfo.
-
2.
Shape stretchy operators to some target size with the help of the MathVariants.
It is then up to a higher-level math rendering engine (e.g. TeX or MathML rendering engines) to beautifully display mathematical formulas using this API. The design choice for exposing MathConstants and MathGlyphInfo is almost obvious from the reading of the MATH table specification. The choice for the shaping API is a bit more complex and discussions is still in progress. For example because we want to accept stretching after glyph-level mirroring (e.g. to draw RTL clockwise integrals) we should accept any glyph and not just an input Unicode strings as it is the case for other HarfBuzz shaping functions. This shaping also depends on a stretching direction (horizontal/vertical) or on a target size (and Gecko even currently has various ways to approximate that target size). Finally, we should also have a way to expose italic correction for a glyph assembly or to approximate preferred width for Web rendering engines.
As I mentioned at the beginning, the data and algorithm to build glyph assembly is the most complex part of the OpenType MATH and deserves a special interest. The idea is that you have a list of glyphs available to build the assembly. For each , the glyph has advance in the stretch direction. Each has straight connector part at its start (of length ) and at its end (of length ) so that we can align the glyphs on the stretch axis and glue them together. Also, some of the glyphs are “extenders” which means that they can be repeated 0, 1 or more times to make the assembly as large as possible. Finally, the end/start connectors of consecutive glyphs must overlap by at least a fixed value to avoid gaps at some resolutions but of course without exceeding the length of the corresponding connectors. This gives some flexibility to adjust the size of the assembly and get closer to the target size .
To ensure that the width/height is distributed equally and the symmetry of the shape is preserved, the MATH table specification suggests the following iterative algorithm to determine the number of extenders and the connector overlaps to reach a minimal target size :
-
1.
Assemble all parts by overlapping connectors by maximum amount, and removing all extenders. This gives the smallest possible result.
-
2.
Determine how much extra width/height can be distributed into all connections between neighboring parts. If that is enough to achieve the size goal, extend each connection equally by changing overlaps of connectors to finish the job.
-
3.
If all connections have been extended to minimum overlap and further growth is needed, add one of each extender, and repeat the process from the first step.
We note that at each step, each extender is repeated the same number of times . So if (respectively ) is the set of indices such that is an extender (respectively is not an extender) we have (respectively ). The size we can reach at step is at most the one obtained with the minimal connector overlap that is
We let and be the number of extenders and non-extenders. We also let and be the sum of advances for extenders and non-extenders. If we want the advance of the glyph assembly to reach the minimal size then
We can assume or otherwise we would have the extreme case where the overlap takes at least the full advance of each extender. Then we obtain
This provides a first simplification of the algorithm sketched in the MATH table specification: Directly start iteration at step . Note that at each step we start at possibly different maximum overlaps and decrease all of them by a same value. It is not clear what to do when one of the overlap reaches while others can still be decreased. However, the sketched algorithm says all the connectors should reach minimum overlap before the next increment of , which means the target size will indeed be reached at step .
One possible interpretation is to stop overlap decreasing for the adjacent connectors that reached minimum overlap and to continue uniform decreasing for the others until all the connectors reach minimum overlap. In that case we may lose equal distribution or symmetry. In practice, this should probably not matter much. So we propose instead the dual option which should behave more or less the same in most cases: Start with all overlaps set to and increase them evenly to reach a same value . By the same reasoning as above we want the inequality
which can be rewritten
We note that is just the exact number of glyphs used in the assembly. If there is only a single glyph, then the overlap value is irrelevant so we can assume . This provides the greatest theorical value for the overlap :
Of course, we also have to take into account the limit imposed by the start and end connector lengths. So must also be at most for . But if then extender copies are connected and so must also be at most for . To summarize, is the minimum of , of for , of and possibly of (if ) and of of (if ).
With the algorithm described above , , , and and can all be obtained using simple loops on the glyphs and so the complexity is . In practice is small: For existing fonts, assemblies are made of at most three non-extenders and two extenders that is (incidentally, Gecko and WebKit do not currently support larger values of ). This means that all the operations described above can be considered to have constant complexity. This is much better than a naive implementation of the iterative algorithm sketched in the OpenType MATH table specification which seems to require at worst
and at least .
One of issue is that the number of extender repetitions and the number of glyphs in the assembly can become arbitrary large since the target size can take large values e.g. if one writes \underbrace{\hspace{65535em}} in LaTeX. The improvement proposed here does not solve that issue since setting the coordinates of each glyph in the assembly and painting them require operations as well as (in the case of HarfBuzz) a glyph buffer of size . However, such large stretchy operators do not happen in real-life mathematical formulas. Hence to avoid possible hangs in Web engines a solution is to impose a maximum limit for the number of glyph in the assembly so that the complexity is limited by the size of the DOM tree. Currently, the proposal for HarfBuzz is . This means that if each assembly glyph is 1em large you won’t be able to draw stretchy operators of size more than 128em, which sounds a quite reasonable bound. With the above proposal, and so can be determined very quickly and the cases rejected, so that we avoid losing time with such edge cases…
Finally, because in our proposal we use the same overlap everywhere an alternative for HarfBuzz would be to set the output buffer size to (i.e. ignore copies of each extender and only keep the first one). This will leave gaps that the client can fix by repeating extenders as long as is also provided. Then HarfBuzz math shaping can be done with a complexity in time and space of just and it will be up to the client to optimize or limit the painting of extenders for large values of …