% language=us runpath=texruns:manuals/ontarget \startcomponent ontarget-gettingridof \environment ontarget-style \startchapter[title={Issues in math fonts}] \startsection[title=Introduction] After trying to improve math rendering of \OPENTYPE\ math fonts, we \footnote {Mikael Sundqvist and Hans Hagen} ended up with a mix of improving the engine and fixing fonts runtime, and we are rather satisfied with the results so far. However, as we progress and also improve the more structural and input related features of \CONTEXT, we wonder why we don't simply are more drastic when it comes to fonts. The \OPENTYPE\ specifications are vague, and most existing \OPENTYPE\ math fonts use a mixture of the \OPENTYPE\ features and the old \TEX\ habits, so we are sort of on our own. The advantage of this situation is that we feel free to experiment and do as we like. In another article we discuss our issues with \UNICODE\ math, and we have realized that good working solutions will be bound to a macro package anyway. Also, math typesetting has not evolved much after Don Knuth set the standard, even if the limitations of those times in terms of memory, processing speed and font technologies have been lifted already for a while. And right from the start Don invited users to extend and adapt \TEX\ to one's needs. Here we will zoom in on a few aspects: font parameters, glyph dimensions and properties and kerning of scripts and atoms. We discuss \OPENTYPE\ math fonts only, and start with a summary of how we tweak them. We leave a detailed engine discussion to a future article, since that would demand way more pages, and could confuse the reader. \stopsection \startsection[title={Tweaks, also known as goodies}] The easiest tweaks to describe are those that wipe features. Because the \TEXGYRE\ fonts have many bad top accent anchors (they sit above the highest point of the shape) the \typ {wipeanchors} tweak can remove them, and we do that per specified alphabet. \bgroup \definefontfeature[mathextra][goodies=] \switchtobodyfont[modern] \startformula \scale[s=2]{$\widehat{7}$} % \widehat{7} \stopformula \egroup \stopformulas In a similar fashion we \typ {wipeitalics} from upright shapes. Okay, maybe they can play a role for subscript placement, but then they can also interfere, and they do not fit with the \OPENTYPE\ specification. The \typ {wipecues} tweak zeros the dimensions of the invisible times and friends so that they don't interfere and \typ {wipevariants} gets rid of bad variants of specified characters. The fixers is another category, and the names indicate what gets fixed. Tweaks like these take lists of code points and specific properties to fix. We could leave it to your imagination what \typ {fixaccents}, \typ {fixanchors}, \typ {fixellipses}, \typ {fixoldschool}, \typ {fixprimes}, \typ {fixradicals} and \typ {fixslashes} do, but here are some details. Inconsistencies in the dimensions of accents make them jump all over the place so we normalize them. We support horizontal stretching at the engine level. \startformula \scale[s=2]{\dm{\widehat{a+b+c+d} = \widetilde{u+v+w+x+y}}} \stopformula It required only a few lines of code thanks to already present scaling features. % MPS: I thought of an example showing the accents, but I could % not get it to work fine with the goodies loaded the second % time the font was loaded % % \startformulas % \bgroup \definefontfeature[mathextra][goodies=]\setupbodyfont[modern] % \startformula[width=2em] % \widehat{7} % \stopformula % \egroup % \bgroup\setupbodyfont[modern] % \startformula[width=2em] % \widehat{7} % \stopformula % \egroup % \stopformulas Anchors can be off so we fix these in a way so that they look better on especially italic shapes. We make sure that the automated sizing works consistently, as this is driven by width and overshoot. Several kind of ellipses can be inconsistent with each other as well as with periods (shape and size wise) so we have to deal with that. Radicals and other extensibles have old school dimensions (\TEX\ fonts have a limited set of widths and heights). We need to fix for instance fences of various size because we want to apply kerns to scripts on the four possible corners for which we need to know the real height and depth, Discussing primes would take many paragraphs so we stick to mentioning that they are a mess. We now have native prime support in the engine as well as assume properly dimensioned symbols to be used. Slashes are used for skewed fractions so we'd better make sure they are set up right. A nice tweak is \typ {replacealphabets}. We use this to provide alternative script (roundhand) and calligraphic (chancery) alphabets (yes we have both natively in \CONTEXT\ while \UNICODE\ combines them in one alphabet). Many available \OPENTYPE\ math fonts come with one of the two alphabets only, some with roundhand and some with chancery. For the record: this tweak replaces the older \typ {variants} tweak that filtered scripts from a stylistic font feature. We also use the \typ {replacealphabets} tweak to drop in Arabic shapes so that we can do bidirectional math. In practice that doesn't really boil down to a replacement but more to an addition. The \typ {addmirrors} features accompanies this, and it is again a rather small extension to the engine to make sure we can do this efficiently: when a character is looked up we check a mirror variant when we are in r2l mode, just like we look up a smaller variant when we're in compact font mode (a \CONTEXT\ feature). \bgroup \definefontfeature[mathextra][xitsarabic=yes] \switchtobodyfont[bonum] \setupmathematics[bidi=yes,align=righttoleft]\par \setupalign[righttoleft] % \startformula \scale[s=2]{\dm{ \sum_{\char"1EE4E=\char"1EE01}^{\char"1EE02} \char"1EE03^{\char"1EE4E} = \char"1EE03^{\char"1EE01}\frac{\char"0661 - \char"1EE03^{\char"1EE02 - \char"1EE01 + \char"0661}}{\char"0661 - \char"1EE03} \quad (\char"1EE03\neq \char"0661) }} % \int_{\char"0627}^{\char"0628} \char"1EE03 '(\char"1EE4E) % %\mathatom class \mathdifferentialcode {\char"062F} % \dd % \char"1EE4E % = \char"1EE03(\char"0628) - \char"1EE03(\char"0627) % \stopformula % \startformula % \sqrt[\char"0663](\char"1EE30) = (\char"1EE30)^{1/\char"0663} \stopformula \egroup Another application of \typ {replacealphabets} is to drop in single characters from another font. We use this for instance to replace the \quote {not really an alpha} in Bonum by one of our own liking. Below we show a math italic a and the original alpha, together with the modified alpha. \startformula \scale[s=2]{\dm{ a + \text{\getnamedglyphdirect{file:TeXGyreBonumMath-Companion.otf}{alpha.old}} + \alpha }} \stopformula For that we ship a companion font. On our disks (and in the distribution) you can find: \starttyping /tex/texmf-fonts/fonts/data/cms/companion/RalphSmithsFormalScript-Companion.otf /tex/texmf-fonts/fonts/data/cms/companion/TeXGyreBonumMath-Companion.otf /tex/texmf-fonts/fonts/data/cms/companion/XITSMath-Companion.otf \stoptyping All these are efficient drop|-|ins that are injected by the \typ {replacealphabets}, some under user control, some always. We tried to limit the overhead and actually bidirectional math could be simplified which also had the benefit that when one does tens of thousands of bodyfont switches a bit of runtime is gained. There are more addition tweaks: \typ {addactuarian} creates the relevant symbols which is actually a right sided radical (the engine has support for two|-|sided radicals). It takes a bit of juggling with virtual glyphs and extensible recipes, but the results are rewarding. \setupmathradical[annuity][strut=no] \definemathdelimited [myannuity] [topoffset=.2\exheight, strut=no, rightmargin=.05\emwidth, right=\delimitedrightanutityuc] \startformula \scale[s=2]{\dm{ \widebar{A}__{\myannuity{m}}^^{2}_{x:\annuity{n}}^{1} }} \stopformula In a similar fashion we try to add missing extensible arrows with \typ {addarrows}, bars with \typ {addbars}, equals with \typ {addequals} and again using the radical mechanism fourier notation symbols (like hats) with \typ {addfourier}. That one involves subtle kerning because these symbols end up at the right top of a fence like symbol. \startformula \scale[s=2]{\dm{ \widehat{f \ast g \ast h}(\xi) = \fourier{\F1\left(f\ast g \ast h\right)}(\xi) }} \stopformula It was actually one of the reasons to introduce a more advanced kerning mechanism in the engine, which is not entirely trivial because one has to carry around more information, since all this is font and character bound, and when wrapped in boxes that gets hard to analyze. The \typ {addrules} makes sure that we can do bars over and under constructs properly. The \typ {addparts} is there to add extensible recipes to characters. Some of these tweaks actually are not new and are also available in \MKIV\ but more as features (optionally driven by the goodie file). An example is \typ {addscripts} that is there for specially positioned and scaled signs (high minus and such) but that tweak will probably be redone as part of the \quotation {deal with all these plus and minus issues}. The dedicated to Alan Braslau \typ {addprivates} tweak is an example of this: we add specific variants for unary minus and plus that users can enable on demand, which in turn of course gives class specific spacing, but we promised not to discuss those engine features here. \startformula \scale[s=2]{\dm{ \int_1^2 \left[(x+2)^{\frac[strut=no]{1}{2}} - (x+2)^{\um\frac[strut=no]{1}{2}}\right] \dd x }} \stopformula There is a handful of tweaks that deals with fixing glyph properties (in detail). We mention: \typ {dimensions} and \typ {accentdimensions} that can reposition in the boundingbox, fix the width and italic correction, squeeze and expand etc. The \typ {kernpairs} tweak adds kern pairs to combinations of characters. The \typ {kerns} provides a way to add top left, bottom left, top right and bottom right kerns and those really make the results look better so we love it! \startformula \scale[s=2]{\showglyphs\dm{\F3\left(\frac{1}{1+x^2}\right)^n \quad x^2/(1+x)}} \stopformula The \typ {margins} tweak sets margin fields that the engine can use to calculate accents over the base character better. The same is true for \typ {setovershoots} that can make accents lean over a bit. The \typ {staircase} feature can be used to add the somewhat complicated \OPENTYPE\ kerns. From all this you can deduce that the engine has all types of kerning that \OPENTYPE\ requires, and more. Accents as specified in fonts can be a pain to deal with so we have more tweaks for them: \typ {copyaccents} moves them to the right slots and \typ {extendaccents} makes sure that we can extend them. Not all font makers have the same ideas about where these symbols should sit and what their dimensions should be. The \typ {checkspacing} tweak fixes bad or missing spacing related to \UNICODE\ character entries in the font, because after all, we might need them. We need to keep for instance \MATHML\ in mind, which means: processing content that we don't see and that can contain whatever an editor puts in. The \typ {replacements} feature replaces one character by another from the same font. The \typ {substitutes} replaces a character by one from a stylistic feature. Relatively late we added the \typ {setoptions} which was needed to control the engine for specific fonts. The rendering is controlled by a bunch of options (think of kerning, italic correction, and such). Some are per font, many per class. Because we can (and do) use mixed math fonts in a document, we might need to adapt the engine level options per font, and that is what this tweak does: it passes options to the font so that the engine can consult them and prefer them over the \quote {global} ones. We needed this for some fonts that have old school dimensions for extensibles (like Lucida), simply because they imitated Computer Modern. Normally that goes unnoticed, but, as mentioned before, it interferes with our optional kerning. The \typ {fixoldschool} tweak sort of can fix that too so \typ {setoptions} is seldom needed. Luckily, some font providers are willing to fix their fonts! We set and configure all these tweaks in a so-called goodie file, basically a runtime module that returns a \LUA\ table with specifications. In addition to the tweaks subtable in the math namespace, there is a subtable that overloads the font parameters: the ones that \OPENTYPE\ specifies, but also new ones that we added. In the next section we elaborate more on these font bound parameters. \stopsection \startsection[title=Font parameters] At some point in the upgrading of the math machinery we discussed some of the inconsistencies between the math constants of the XITS and STIX fonts. Now, one has to keep in mind that XITS was based on a first release of STIX that only had \TYPEONE\ fonts so what follows should not to be seen as criticism, but more as observations and reason for discussion, as well as a basis for decisions to be made. One thing we have to mention in advance, is that we often wonder why some weird and|/|or confusing stuff in math fonts go unnoticed. We have some suggestions: \startitemize \startitem The user doesn't care that much how math comes out. This can easily be observed when you run into documents on the internet or posts on forums. And publishers don't always seem to care either. Consistency with old documents sometimes seems to be more important than quality. \stopitem \startitem The user switches to another math font when the current one doesn't handle its intended math domain well. We have seen that happening and it's the easiest way out when you have not much control anyway (for instance when using online tools). \stopitem \startitem The user eventually adds some skips and kerns to get things right, because after all \TEX\ is also about tweaking. \stopitem \startitem The user doesn't typeset that complex math. It's mostly inline math with an occasional alignment (also in text style) and very few multi|-|level display math (with left and right fences that span at most a fraction). \stopitem \stopitemize We do not claim to be perfect, but we care for details, so let's go on. The next table shows the math constants as they can be found in the \STIX\ (two) and \XITS\ (one) fonts. When you typeset with these fonts you will notice that \XITS\ is somewhat smaller, so two additional columns show the values compensated for the axis height and accent base height. \startluacode local one = { ["AccentBaseHeight"]=450, ["AxisHeight"]=250, ["DelimitedSubFormulaMinHeight"]=1500, ["DisplayOperatorMinHeight"]=1450, ["FlattenedAccentBaseHeight"]=662, ["FractionDenominatorDisplayStyleGapMin"]=198, ["FractionDenominatorDisplayStyleShiftDown"]=700, ["FractionDenominatorGapMin"]=66, ["FractionDenominatorShiftDown"]=480, ["FractionNumeratorDisplayStyleGapMin"]=198, ["FractionNumeratorDisplayStyleShiftUp"]=580, ["FractionNumeratorGapMin"]=66, ["FractionNumeratorShiftUp"]=480, ["FractionRuleThickness"]=66, ["LowerLimitBaselineDropMin"]=600, ["LowerLimitGapMin"]=150, ["MathLeading"]=150, ["MinConnectorOverlap"]=50, ["OverbarExtraAscender"]=66, ["OverbarRuleThickness"]=66, ["OverbarVerticalGap"]=198, ["RadicalDegreeBottomRaisePercent"]=70, ["RadicalDisplayStyleVerticalGap"]=186, ["RadicalExtraAscender"]=66, ["RadicalKernAfterDegree"]=-555, ["RadicalKernBeforeDegree"]=277, ["RadicalRuleThickness"]=66, ["RadicalVerticalGap"]=82, ["ScriptPercentScaleDown"]=75, ["ScriptScriptPercentScaleDown"]=60, ["SkewedFractionHorizontalGap"]=300, ["SkewedFractionVerticalGap"]=66, ["SpaceAfterScript"]=41, ["StackBottomDisplayStyleShiftDown"]=900, ["StackBottomShiftDown"]=800, ["StackDisplayStyleGapMin"]=462, ["StackGapMin"]=198, ["StackTopDisplayStyleShiftUp"]=580, ["StackTopShiftUp"]=480, ["StretchStackBottomShiftDown"]=600, ["StretchStackGapAboveMin"]=150, ["StretchStackGapBelowMin"]=150, ["StretchStackTopShiftUp"]=300, ["SubSuperscriptGapMin"]=264, ["SubscriptBaselineDropMin"]=50, ["SubscriptShiftDown"]=250, ["SubscriptTopMax"]=400, ["SuperscriptBaselineDropMax"]=375, ["SuperscriptBottomMaxWithSubscript"]=400, ["SuperscriptBottomMin"]=125, ["SuperscriptShiftUp"]=400, ["SuperscriptShiftUpCramped"]=275, ["UnderbarExtraDescender"]=66, ["UnderbarRuleThickness"]=66, ["UnderbarVerticalGap"]=198, ["UpperLimitBaselineRiseMin"]=300, ["UpperLimitGapMin"]=150, } local two = { ["AccentBaseHeight"]=480, ["AxisHeight"]=258, ["DelimitedSubFormulaMinHeight"]=1325, ["DisplayOperatorMinHeight"]=1800, ["FlattenedAccentBaseHeight"]=656, ["FractionDenominatorDisplayStyleGapMin"]=150, ["FractionDenominatorDisplayStyleShiftDown"]=640, ["FractionDenominatorGapMin"]=68, ["FractionDenominatorShiftDown"]=585, ["FractionNumeratorDisplayStyleGapMin"]=150, ["FractionNumeratorDisplayStyleShiftUp"]=640, ["FractionNumeratorGapMin"]=68, ["FractionNumeratorShiftUp"]=585, ["FractionRuleThickness"]=68, ["LowerLimitBaselineDropMin"]=670, ["LowerLimitGapMin"]=135, ["MathLeading"]=150, ["MinConnectorOverlap"]=100, ["OverbarExtraAscender"]=68, ["OverbarRuleThickness"]=68, ["OverbarVerticalGap"]=175, ["RadicalDegreeBottomRaisePercent"]=55, ["RadicalDisplayStyleVerticalGap"]=170, ["RadicalExtraAscender"]=78, ["RadicalKernAfterDegree"]=-335, ["RadicalKernBeforeDegree"]=65, ["RadicalRuleThickness"]=68, ["RadicalVerticalGap"]=85, ["ScriptPercentScaleDown"]=70, ["ScriptScriptPercentScaleDown"]=55, ["SkewedFractionHorizontalGap"]=350, ["SkewedFractionVerticalGap"]=68, ["SpaceAfterScript"]=40, ["StackBottomDisplayStyleShiftDown"]=690, ["StackBottomShiftDown"]=385, ["StackDisplayStyleGapMin"]=300, ["StackGapMin"]=150, ["StackTopDisplayStyleShiftUp"]=780, ["StackTopShiftUp"]=470, ["StretchStackBottomShiftDown"]=590, ["StretchStackGapAboveMin"]=68, ["StretchStackGapBelowMin"]=68, ["StretchStackTopShiftUp"]=800, ["SubSuperscriptGapMin"]=150, ["SubscriptBaselineDropMin"]=160, ["SubscriptShiftDown"]=210, ["SubscriptTopMax"]=368, ["SuperscriptBaselineDropMax"]=230, ["SuperscriptBottomMaxWithSubscript"]=380, ["SuperscriptBottomMin"]=120, ["SuperscriptShiftUp"]=360, ["SuperscriptShiftUpCramped"]=252, ["UnderbarExtraDescender"]=68, ["UnderbarRuleThickness"]=68, ["UnderbarVerticalGap"]=175, ["UpperLimitBaselineRiseMin"]=300, ["UpperLimitGapMin"]=135, } local designrelated = { ["AccentBaseHeight"] = "optional**", ["AxisHeight"] = "mandate", ["FlattenedAccentBaseHeight"] = "optional**", ["FractionRuleThickness"] = "optional", ["MinConnectorOverlap"] = "mandate", ["OverbarRuleThickness"] = "optional*", ["RadicalDegreeBottomRaisePercent"] = "mandate", ["UnderbarRuleThickness"] = "optional*", } local a1 = two.AccentBaseHeight / one.AccentBaseHeight local a2 = two.AxisHeight / one.AxisHeight context.starttabulate { "|l|r|r|r|r|l|" } context.FL() context.BC() context("constant") context.BC() context("stix") context.BC() context("xits") context.BC() context("base") context.BC() context("axis") context.BC() context("relevance") context.BC() context.NR() context.ML() for key, oldvalue in table.sortedhash(one) do local newvalue = two[key] local accvalue = math.round(oldvalue * a1) local axivalue = math.round(oldvalue * a2) context.NC() context(key) context.NC() context(newvalue) context.NC() context(oldvalue) context.NC() if newvalue == accvalue then context.bold(accvalue) else context(accvalue) end context.NC() if newvalue == axivalue then context.bold(axivalue) else context(axivalue) end context.NC() context(designrelated[key]) context.NC() context.NR() end context.LL() context.stoptabulate() \stopluacode Very few values are the same. So, what exactly do these constants tell us? You can even wonder why they are there at all. Just think of this: we want to typeset math, and we have an engine that we can control. We know how we want it to look. So, what do these constants actually contribute? Plenty relates to the height and depth of the nucleus and|/|or the axis. The fact that we have to fix some in the goodie files, and the fact that we actually need more variables that control positioning, makes for a good argument to just ignore most of the ones provided by the font, especially when they seem somewhat arbitrarily. Can it be that font designers are just gambling a bit, looking at another font, and starting from there? The relationship between \TEX's math font parameters and the \OPENTYPE\ math constants is not one|-|to|-|one. Mapping them onto each other is possible but actually is font dependent. However, we can assume that the values of Computer Modern are leading. The \typ {AxisHeight}, \typ {AccentBaseHeight} and \typ {FlattenedAccentBaseHeight} are set to the x|-|height, a value that is defined in all fonts. The \typ {SkewedFractionVerticalGap} also gets that value. Other variables relate to the em|-|width (or \type {\quad}), for instance the \typ {SkewedFractionHorizontalGap} that gets half that value. Of course these last two then assume that the engine handles skewed fractions. Variables that directly map onto each other are \typ {StretchStackGapBelowMin} as \typ {bigopspacing1}, \typ {StretchStackTopShiftUp} as \typ {bigopspacing3}, \typ {StretchStackGapAboveMin} as \typ {bigopspacing2} and \typ {StretchStackBottomShiftDown} as \typ {bigopspacing4}. However, these clash with \typ {UpperLimitBaselineRiseMin} as \typ {bigopspacing3}, \typ {UpperLimitGapMin} as \typ {bigopspacing1}, \typ {LowerLimitBaselineDropMin} as \typ {bigopspacing4} and \typ {LowerLimitGapMin} as \typ {bigopspacing2}. Where in traditional fonts these are the same, in \OPENTYPE\ they can be different. Should they be? Internally we use different names for variables, simply because the engine has some parameters that \OPENTYPE\ maths hasn't. So we have \typ {limit_above_kern} and \typ {limit_below_kern} for \typ {bigopspacing5}. A couple of parameters have different values for (cramped) displaystyle. The \typ {FractionDelimiterSize} and \typ {FractionDelimiterDisplayStyleSize} use \typ {delim2} and \typ {delim1}. The \typ {FractionDenominatorShiftDown} and \typ {FractionDenominatorDisplayStyleShiftDown} map onto \typ {denom2} and \typ {denom1} and their numerator counterparts from \typ {num2} and \typ {num1}. The \typ {Stack*} parameters also use these. The \typ {sub1}, \typ{sub2}, \typ{sup1}, \typ{sup2}, \typ{sup3}, and \typ {supdrop} can populate the \type {Sub*} and \type {Super*} parameters, also in different styles. The rest of the parameters can be defined in terms of the default rulethickness, quad or xheight, often multiplied by a factor. For some we see the \type {1/18} show up a number that we also see with muskips. Some constants can be set from registers, like \typ {SpaceAfterScript} which is just \type {\scriptspace}. If you look at the \LUATEX\ source you wil find a section where this mapping is done in the case of a traditional font, that is: one without a math constants table. In \LUAMETATEX\ we don't need to do this because font loading happens in \LUA. So we simply issue an error when the math engine can't resolve a mandate parameter. The fact that we have a partial mapping from math constants onto traditional parameters and that \LUATEX\ has to deal with the traditional ones too make for a somewhat confusing landscape. When in \LUAMETATEX\ we assume wide fonts to be used that have a math constants table, we can probably clean up some of this. We need to keep in mind that Cambria was the starting point, and it did borrow some concepts from \TEX. But \TEX\ had parameters because there was not enough information in the glyphs! Also, Cambria was meant for \MSWORD, and a word processor is unlikely to provide the level of control that \TEX\ offers, so it needs some directions with respect to e.g.\ spacing. Without user control, it has to come up with acceptable compromises. So actually the \LUAMETATEX\ math engine can be made a bit cleaner when we just get rid of these parameters. So, which constants are actually essential? The \typ {AxisHeight} is important and also design related. Quite likely this is where the minus sits above the baseline. It is used for displacements of the baseline so that for instance fractions nicely align. When testing script anchored to fences we noticed that the parenthesis in XITS had too little depth while STIX had the expected amount. This relates to anchoring relative to the math axis. Is there a reason why \typ {UnderbarRuleThickness} and \typ {OverbarRuleThickness} should differ? If not, then we only need a variable that somehow tells us what thickness fits best with the other top and bottom accents. It is quite likely the same as the \typ {RadicalRuleThickness}, which is needed to extend the radical symbol. So, here three constants can be replaced by one design related one. The \typ {FractionRuleThickness} can also be derived from that, but more likely is that it is a quantity that the macro package sets up anyway, maybe related to rules used elsewhere. The \typ {MinConnectorOverlap} and \typ {RadicalDegreeBottomRaisePercent} also are related to the design although one could abuse the top accent anchor for the second one. So they are important. However, given the small number of extensibles, they could have been part of the extensible recipes. The \typ {AccentBaseHeight} and \typ {FlattenedAccentBaseHeight} might relate to the margin that the designer put below the accent as part of the glyph, so it is kind of a design related constant. Nevertheless, we fix quite some accents in the goodie files because they can be inconsistent. That makes these constants somewhat dubious too. If we have to check a font, we can just as well set up constants that we need in the goodie file. Also, isn't it weird that there are no bottom variants? We can forget about \typ {MathLeading} as it serves no purpose in \TEX. The \typ {DisplayOperatorMinHeight} is often set wrong so although we fix that in the goodie file it might be that we just can use an internal variable. It is not the font designer who decides that anyway. The same is true for \typ {DelimitedSubFormulaMinHeight}. If we handle skewed fractions, \typ {SkewedFractionHorizontalGap} and \typ {SkewedFractionVerticalGap} might give an indication of the tilt but why do we need two? It is design related though, so they have some importance, when set right. The rest can be grouped, and basically we can replace them by a consistent set of engine parameters. We can still set them up per font, but at least we can then use a clean set. Currently, we already have more. For instance, why only \typ {SpaceAfterScript} and not one for before, and how about prescripts and primes? If we have to complement them with additional ones and also fix them, we can as well set up all these script related variables. For fractions the font provides \typ {FractionDenominatorDisplayStyleGapMin}, \typ {FractionDenominatorDisplayStyleShiftDown}, \typ {FractionDenominatorGapMin}, \typ {FractionDenominatorShiftDown}, \typ {FractionNumeratorDisplayStyleGapMin}, \typ {FractionNumeratorDisplayStyleShiftUp}, \typ {FractionNumeratorGapMin} and \typ {FractionNumeratorShiftUp}. We might try to come up with a simpler model. Limits have: \typ {LowerLimitBaselineDropMin}, \typ {LowerLimitGapMin}, \typ {UpperLimitBaselineRiseMin} and \typ {UpperLimitGapMin}. Limits are tricky anyway as they also depend on abusing the italic correction for anchoring. Horizontal bars are driven by \typ {OverbarExtraAscender}, \typ {OverbarVerticalGap}, \typ {UnderbarExtraDescender} and \typ {UnderbarVerticalGap}, but for e.g.\ arrows we are on our own, so again a not so useful set. Then radicals: we need some more than these \typ {RadicalDisplayStyleVerticalGap}, \typ {RadicalExtraAscender}, \typ {RadicalKernAfterDegree}, \typ {RadicalKernBeforeDegree} and \typ {RadicalVerticalGap}, and because we really need to check these there is no gain having them in the font. Isn't it more a decision by the macro package how script and scriptscript should be scaled? Currently we listen to \typ {ScriptPercentScaleDown} and \typ {ScriptScriptPercentScaleDown}, but maybe it relates more to usage. We need more control than just \typ {SpaceAfterScript} and an engine could provide it more consistently. It's a loner. How about \typ {StackBottomDisplayStyleShiftDown}, \typ {StackBottomShiftDown}, \typ {StackDisplayStyleGapMin}, \typ {StackGapMin}, \typ {StackTopDisplayStyleShiftUp} and \typ {StackTopShiftUp}? And isn't this more for the renderer to decide: \typ {StretchStackBottomShiftDown}, \typ {StretchStackGapAboveMin}, \typ {StretchStackGapBelowMin} and \typ {StretchStackTopShiftUp}? This messy bit can also be handled more convenient so what exactly is the relationship with the font design of \typ {SubSuperscriptGapMin}, \typ {SubscriptBaselineDropMin}, \typ {SubscriptShiftDown}, \typ {SubscriptTopMax}, \typ {SuperscriptBaselineDropMax}, \typ {SuperscriptBottomMaxWithSubscript}, \typ {SuperscriptBottomMin}, \typ {SuperscriptShiftUp} and \typ {SuperscriptShiftUpCramped}? Just for the record, here are the (font related) ones we added so far. A set of prime related constants similar to the script ones: \typ {PrimeRaisePercent}, \typ {PrimeRaiseComposedPercent}, \typ {PrimeShiftUp}, \typ {PrimeBaselineDropMax}, \typ {PrimeShiftUpCramped}, \typ {PrimeSpaceAfter} and \typ {PrimeWidthPercent}. Of course, we also added \typ {SpaceBeforeScript} just because we want to be symmetrical in the engine where we also have to deal with prescripts. These we provide for some further limit positioning: \typ {NoLimitSupFactor} and \typ {NoLimitSubFactor}; these for delimiters: \typ {DelimiterPercent} and \typ {DelimiterShortfall}; and these for radicals in order to compensate for sloping shapes: \typ {RadicalKernAfterExtensible} and \typ {RadicalKernBeforeExtensible} because we have doublesided radicals. Finally, there are quite some (horrible) accent tuning parameters: \typ {AccentTopShiftUp}, \typ {AccentBottomShiftDown}, \typ {FlattenedAccentTopShiftUp}, \typ {FlattenedAccentBottomShiftDown}, \typ {AccentBaseDepth}, \typ {AccentFlattenedBaseDepth}, \typ {AccentTopOvershoot}, \typ {AccentBottomOvershoot}, \typ {AccentSuperscriptDrop}, \typ {AccentSuperscriptPercent} and \typ {AccentExtendMargin}, but we tend to move some of that to the tweaks on a per accent basis. Setting these parameters right is not trivial, and also a bit subjective. We might, however, assume that for instance the math axis is set right, but alas, when we were fixing the less and greater symbols in Lucida Bright Math, we found that all symbols actually were designed for a math axis of 325, instead of the given value 313, and that difference can be seen! \startbuffer \scale[s=2]{\dm{ 2 > -\left\{\frac{1}{1+x^2}\right\} }} \stopbuffer \startlinecorrection \startcombination[nx=2] \startcontent % \definefontfeature[mathextra][goodies=] \switchtobodyfont[lucidaold] \showglyphs \getbuffer \stopcontent \startcaption Old Lucida \stopcaption \startcontent \switchtobodyfont[lucida] \showglyphs \getbuffer \stopcontent \startcaption New Lucida \stopcaption \stopcombination \stoplinecorrection The assumption is that the axis goes trough the middle of the minus. Luckily it was relatively easy to fix these two symbols (they also had to be scaled, maybe they originate in the text font?) and adapt the axis. We still need to check all the other fonts, but it looks like they are okay, which is good because the math axis plays an important role in rendering math. It is one of the few parameters that has to be present and right. A nice side effect of this is that we end up with discussing new (\CONTEXT) features. One can for instance shift all non-character symbols down just a little and lower the math axis, to get a bit more tolerance in lines with many inline fractions, radicals or superscripts, that otherwise would result in interline skips. A first step in getting out of this mess is to define {\em all} these parameters in the goodie file where we fix them anyway. That way we are at least not dependent on changes in the font. We are not a word processor so we have way more freedom to control matters. And preset font parameters sometimes do more harm than good. A side effect of a cleanup can be that we get rid of the evolved mix of uppercase and lowercase math control variables and can be more consistent. Ever since \LUATEX\ got support for \OPENTYPE, math constants names have been mapped and matched to traditional \TEX\ font parameters. \stopsection \startsection[title=Metrics] With metrics we refer to the dimensions and other properties of math glyphs. The origin of digital math fonts is definitely Computer Modern and thereby the storage of properties is bound to the \TFM\ file format. That format is binary and can be loaded fast. It can also be stored in the format, unless you're using \LUATEX\ or \LUAMETATEX\ where \LUA\ is the storage format. A \TFM\ file stores per character the width, height, depth and italic correction. The file also contains font parameters. In math fonts there are extensible recipes and there is information about next in size glyphs. The file has kerning and ligature tables too. Given the times \TEX\ evolved in, the format is rather compact. For instance, the height, depth and italic correction are shared and indices to three shared values are used. There can be 16 heights and depths and 64 italic corrections. That way much fits into a memory word. The documentation tells us that \quotation {The italic correction of a character has two different uses. (a)~In ordinary text, the italic correction is added to the width only if the \TEX\ user specifies \quote{\type {\/}} after the character. (b)~In math formulas, the italic correction is always added to the width, except with respect to the positioning of subscripts.} It is this last phenomena that gives us some trouble with fonts in \OPENTYPE\ math. The fact that traditional fonts cheat with the width and that we add and selectively remove or ignore the correction makes for fuzzy code in \LUATEX\ although splitting the code paths and providing options to control all this helps a bit. In \LUAMETATEX\ we have more control but also expect an \OPENTYPE\ font. In \OPENTYPE\ math there are italic corrections, and we even have the peculiar usage of it in positioning limits. However, the idea was that staircase kerns do the detailed relative positioning. Before we dive into this a bit more, it is worth mentioning that Don Knuth paid a lot of attention to details. The italic alphabet in math uses the same shapes as the text italic but metrics are different as is shown below. We have also met fonts where it looked like the text italics were taken, and where the metrics were handled via more excessive italic correction, sometimes combined with staircase kerns that basically were corrections for the side bearing. This is why we always come back to Latin Modern and Cambria when we investigate fonts: one is based on the traditional \TEX\ model, with carefully chosen italic corrections, and the other is based on the \OPENTYPE\ model with staircase kerning. They are our reference fonts. \startlinecorrection \startcombination[nx=1,ny=2] \startcontent \definedfont[file:lmroman10italic.otf]% \showglyphs \scale[s=3]{abcdefghijklmnopqrstuvwxyz} \stopcontent \startcaption Latin Modern Roman Italic \stopcaption \startcontent \definedfont[file:latinmodernmath.otf]% \showglyphs \scale[s=3]{𝑎𝑏𝑐𝑑𝑒𝑓𝑔ℎ𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧} \stopcontent \startcaption Latin Modern Math Italic \stopcaption \stopcombination \stoplinecorrection In \CONTEXT\ \MKIV\ we played a lot with italic correction in math and there were ways to enforce, ignore, selectively apply it, etc. But, because fonts actually demand a mixture, in \LUAMETATEX\ we ended up with more extensive runtime patching of them. Another reason for this was that math fonts can have weird properties. It looks like when these standards are set and fonts are made, the font makers can do as they like as long as the average formula comes out right, and metrics to some extent resemble a traditional font. However, when testing how well a font behaves in a real situation there can be all kind of interferences from the macro package: inter|-|atom kerning, spacing corrections macros, specific handling of cases, etc. We even see \OPENTYPE\ fonts that seem to have the same limited number of heights, depths and italic corrections. And, as a consequence we get for instance larger sizes of fences having the same depth for all the size variants, something that is pretty odd for an \OPENTYPE\ font with no limitations. The italic correction in traditional \TEX\ math gets added to the width. When a subscript is attached to a kernel character it sits tight against that character: its position is driven by the width of the kernel. A superscript on the other hand is moved over the italic width so that it doesn't overlap or touch the likely sticking out bit of the kernel. This means that a traditional font (and quite some \OPENTYPE\ math fonts are modelled after Computer Modern) have to find compromises of width and italic correction for characters where the subscript is supposed to move left (inside the bounding box of the kernel). The \OPENTYPE\ specification has some vague remarks about applying italic correction between the last in a series of slanted shapes and operators, as well as positioning limits, and suggests that it relates to relative super- and subscript positioning. It doesn't mention that the correction is to be added to the width. However, the main mechanism for anchoring script are these top and bottom edge kerns. It's why in fonts that provide these, we are unlikely to find italic correction unless it is used for positioning limits. It is for that reason that an engine can produce reasonable results for fonts that either provide italics or provide kerns for anchoring: having both on the same glyph would mean troubles. It means that we can configure the engine options to add italic correction as well as kerns, assuming distinctive usage of those features. For a font that uses both we need to make a choice (this is possible, since we can configure options per font). But that will never lead to always nicely typeset math. In fact, without tweaks many fonts will look right because in practice they use some mixture. But we are not aiming at partial success, we want all to look good. Here is another thing to keep in mind (although now we are guessing a bit). There is a limited number of heights and depths in \TEX\ fonts possible (16), but four times as many italic corrections can be defined (64). Is it because Don Knuth wanted to properly position the sub- and subscripts? Adding italic correction to the width is pretty safe: shapes should not overlap. Choosing the right width for a subscript needs more work because it's is more visual. In the end we have a width that is mostly driven by superscript placement! That also means that as soon as we remove the italic correction things start looking bad. In fact, because also upright math characters have italic correction the term \quote {italic} is a bit of a cheat: it's all about script positioning and has little to do with the slope of the shapes. One of the reasons why for instance spacing between an italic shape and an upright one in \TEX\ works out okay is that in most cases they come from a different font, which can be used as criterium for keeping the correction; between a sequence of same|-|font characters it gets removed. However, in \OPENTYPE\ math there is a good chance that all comes from the same font (at least in \CONTEXT), unless one populates many families as in traditional \TEX. We have no clue how other macro packages deal with this but it might well be the case that using many families (one for each alphabet) works better in the end. The engine is really shape and alphabet agnostic, but one can actually wonder if we should add a glyph property indicating the distinctive range. It would provide engine level control over a run of glyphs (like multiplying a variable represented by a greek alpha by another variable presented by an upright b). But glyph properties cannot really be used here because we are still dealing with characters when the engine transforms the noad list into a node list. So, when we discussed this, we started wondering how the engine could know about a specific shape (and tilt) property at all, and that brought us to pondering about an additional axis of options. We already group characters in classes, but we can also group them with properties like \typ {tilted}, \typ {dotless}, \typ {bold}. When we pair atoms we can apply options, spacing and such based on the specific class pair, and we can do something similar with category pairs. It basically boils down to for instance \type {\mccode} that binds a character to a category. Then we add a command like \typ {\setmathcategorization} (analogue to \typ {\setmathspacing}) that binds options to pairs of categories. An easier variant of this might be to let the \type {\mccode} carry a (bit)set of options that then get added to the already existing options that can be bound to character noads as we create them. This saves us some configuration. Deciding what suits best depends on what we want to do: the fact that \TEX\ doesn't do this means that probably no one ever gave it much thought, but once we do have this mechanism it might actually trigger demand, if only by staring at existing documents where characters of a different kind sit next to each other (take this \quote {a} invisible times \quote {x}). It would not be the first time that (in \CONTEXT) the availability of some feature triggers creative (ab)usage. Because the landscape has settled, because we haven't seen much fundamental evolution in \OPENTYPE\ math, because in general \TEX\ math doesn't really evolve, and because \CONTEXT\ in the past has not been seen as suitable for math, we can, as mentioned before, basically decide what approach we follow. So, that is why we can pick up on this italic correction in a more drastic way: we can add the correction to the width, thereby creating a nicely bounded glyph, and moving the original correction to the right bottom kern, as that is something we already support. In fact, this feature is already available, we only had to add setting the right bottom kern. The good news is that we don't need to waste time on trying to get something extra in the font format, which is unlikely to happen anyway after two decades. It is worth noticing that when we were exploring this as part of using \METAPOST\ to analyze and visualize these aspects, we also reviewed the \typ {wipeitalics} tweak and wondered if, in retrospect, it might be a dangerous one when applied to alphabets (for digits and blackboard bold letters it definitely makes sense): it can make traditional super- and subscript anchoring less optimal. However, for some fonts we found that improper bounding boxes can badly interfere anyway: for instance the upright \quote {f} in EBGaramond sticks out left and right, and has staircase kerns that make scripts overlap. The right top of the shape sticks out a lot and that is because the text font variant is used. We already decided to add a \typ {moveitalics} tweak that moves italic kerns into the width and then setting a right bottom kern that compensates it that can be a pretty good starting point for our further exploration of optimal kerns at the corners. That tweak also fixes the side bearings (negative llx) and compensates left kerns (when present) accordingly. An additional \typ {simplifykerns} tweak can later migrate staircase kerns to simple kerns. So, does that free us from tweaks like \typ {dimensions} and \typ {kerns}? Not completely. But we can forget about the italic correction in most cases. We have to set up less lower right kerns and maybe correct a few. It is just a more natural solution. So how about these kerns that we need to define? After all, we also have to deal with proper top kerns, and like to add kerns that are not there simply because the mentioned comprise between width, italic and the combination was impossible. More about that in the next section. \stopsection \startsection[title=Kerning] In the next pictures we will try to explain more visual what we have in mind and are experimenting with as we write this. In the traditional approach we have shapes that can communicate the width, height, depth and italic correction to the engine so that is what the engine can work with. The engine also has the challenge to anchor subscripts and superscripts in a visual pleasing way. \startMPdefinitions numeric UsedUnit ; UsedUnit = 1mm ; numeric UsedWidth ; UsedWidth := 10UsedUnit ; numeric UsedItalic ; UsedItalic := 2UsedUnit ; numeric UsedScript ; UsedScript = 5UsedUnit; picture LeftCharA ; LeftCharA := image( draw origin -- (UsedWidth,UsedWidth) withpen pencircle scaled 2UsedUnit withcolor .4white ; path p ; p := boundingbox currentpicture ; draw rightboundary currentpicture bottomenlarged -5UsedUnit withpen pencircle scaled .5UsedUnit ; setbounds currentpicture to p ; draw origin withpen pencircle scaled 1UsedUnit withcolor .1white ; setbounds currentpicture to boundingbox currentpicture leftenlarged -UsedItalic rightenlarged -UsedItalic ; draw boundingbox currentpicture withpen pencircle scaled .1UsedUnit ; ) ; picture RightCharA ; RightCharA := image( draw (0,UsedWidth) -- (UsedWidth,0) withpen pencircle scaled 2UsedUnit withcolor .6white ; path p ; p := boundingbox currentpicture ; draw rightboundary currentpicture bottomenlarged -5UsedUnit withpen pencircle scaled .5UsedUnit ; setbounds currentpicture to p ; draw origin withpen pencircle scaled 1UsedUnit withcolor .1white ; setbounds currentpicture to boundingbox currentpicture leftenlarged -UsedItalic rightenlarged -UsedItalic ; draw boundingbox currentpicture withpen pencircle scaled .1UsedUnit ; ) ; picture LeftCharB ; LeftCharB := image( draw origin -- (UsedWidth,UsedWidth) withpen pencircle scaled 2UsedUnit withcolor .4white ; path p ; p := boundingbox currentpicture ; draw origin withpen pencircle scaled 1UsedUnit withcolor .1white ; draw boundingbox currentpicture withpen pencircle scaled .1UsedUnit ; draw lrcorner p shifted (-UsedItalic,0) withpen pencircle scaled 1UsedUnit withcolor .1white ; draw urcorner p withpen pencircle scaled 1UsedUnit withcolor .1white ; setbounds currentpicture to p ; ) ; picture RightCharB ; RightCharB := image( draw (0,UsedWidth) -- (UsedWidth,0) withpen pencircle scaled 2UsedUnit withcolor .6white ; path p ; p := boundingbox currentpicture ; draw origin withpen pencircle scaled 1UsedUnit withcolor .1white ; draw boundingbox currentpicture withpen pencircle scaled .1UsedUnit ; draw lrcorner p shifted (-UsedItalic,0) withpen pencircle scaled 1UsedUnit withcolor .1white ; draw urcorner p withpen pencircle scaled 1mm withcolor .1white ; setbounds currentpicture to p ; ) ; picture SuperScript ; SuperScript := image( draw unitsquare scaled UsedScript shifted (0,-UsedScript/2) ) ; picture SubScript ; SubScript := image( draw unitsquare scaled UsedScript shifted (0,-UsedScript/2) ) ; def WidenResultA = setbounds currentpicture to boundingbox currentpicture leftenlarged 6UsedUnit rightenlarged 6UsedUnit; enddef ; def WidenResultB = setbounds currentpicture to boundingbox currentpicture topenlarged .5UsedScript leftenlarged 6UsedUnit rightenlarged 6UsedUnit; enddef ; \stopMPdefinitions \startlinecorrection \startcombination[nx=3,ny=1] \startcontent \startMPcode draw LeftCharA ; draw RightCharA xshifted (UsedWidth+UsedItalic+3UsedUnit) ; WidenResultA ; \stopMPcode \stopcontent \startcaption two characters \stopcaption \startcontent \startMPcode draw LeftCharA ; draw RightCharA xshifted UsedWidth ; WidenResultA ; \stopMPcode \stopcontent \startcaption width only \stopcaption \startcontent \startMPcode draw LeftCharA ; draw RightCharA xshifted (UsedWidth+UsedItalic) ; WidenResultA ; \stopMPcode \stopcontent \startcaption with italic \stopcaption \stopcombination \stoplinecorrection In this graphic we show two pseudo characters. The shown bounding box indicates the width as seen by the engine. An example of such a shape is the math italic~f, and as it is used a lot in formulas it is also one of the most hard ones to handle when it comes to spacing: in nearly all fonts the right top sticks out and in some fonts the left part also does that. Imagine how that works out with scripts, fences and preceding characters. When we put two such characters together they will overlap, and this is why we need to add the italic correction. That is also why the \TEX\ documentation speaks in terms of \quotation {always add the italic correction to the width}. This also means that we need to remove it occasionally, something that you will notice when you study for instance the \LUATEX\ source, that has a mix of traditional and \OPENTYPE\ code paths. Actually, compensating can either be done by changing the width property of a glyph node or by explicitly adding a kern. In \LUAMETATEX\ we always add real kerns because we can then trace better. The last graphic in the above set shows how we compensate the width for the bit that sticks out. It also shows that we definitely need to take neighboring shapes into account when we determine the width and italic correction, especially when the later is {\em not} applied (read: removed). \startlinecorrection \startcombination[nx=3,ny=1] \startcontent \startMPcode draw LeftCharA ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p xshifted UsedScript ; draw SubScript shifted lrcorner p xshifted UsedScript ; WidenResultB ; \stopMPcode \stopcontent \startcaption kernel \stopcaption \startcontent \startMPcode draw LeftCharA ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p ; draw SubScript shifted lrcorner p ; WidenResultB ; \stopMPcode \stopcontent \startcaption subscript \stopcaption \startcontent \startMPcode draw LeftCharA ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p xshifted UsedItalic ; draw SubScript shifted lrcorner p ; WidenResultB ; \stopMPcode \stopcontent \startcaption superscript \stopcaption \stopcombination \stoplinecorrection Here we anchored a super- and subscript. The subscript position it tight to the advance width, again indicated by the box. The superscript however is moved by the italic correction and in the engine additional spacing before and after can be applied as well, but we leave that for now. It will be clear that when the font designer chooses the width and italic correction, the fact that scripts get attached has to be taken into account. \startlinecorrection \startcombination[nx=2,ny=1] \startcontent \startMPcode draw LeftCharB ; draw RightCharB xshifted (UsedWidth+UsedItalic+3UsedUnit) ; WidenResultA ; \stopMPcode \stopcontent \startcaption two characters \stopcaption \startcontent \startMPcode draw LeftCharB ; draw RightCharB xshifted (UsedWidth+UsedItalic) ; WidenResultA ; \stopMPcode \stopcontent \startcaption width only \stopcaption \stopcombination \stoplinecorrection In this graphic we combine the italic correction with the width. Keep in mind that in these examples we use tight values but in practice that correction can also add some extra right side bearing (white space). This addition is an operation that we can do when loading a font. At the same time we also compensate the left edge for which we can use the x coordinate of the left corner of the glyphs real bounding box. The advance width starts at zero and that corner is then left of the origin. By looking at shapes we concluded that in most cases that shift is valid for usage in math where we don't need that visual overlap. In fact, when we tested some of that we found that the results can be quite horrible when you don't do that; not all fonts have left bottom kerning implemented. The dot at the right is actually indicating the old italic correction. Here we let it sit on the edge but as mentioned there can be additional (or maybe less) italic correction than tight. \startlinecorrection \startcombination[nx=3,ny=1] \startcontent \startMPcode draw LeftCharB ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p xshifted UsedScript ; draw SubScript shifted lrcorner p xshifted UsedScript ; WidenResultB ; \stopMPcode \stopcontent \startcaption kernel \stopcaption \startcontent \startMPcode draw LeftCharB ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p ; draw SubScript shifted lrcorner p ; WidenResultB ; \stopMPcode \stopcontent \startcaption superscript \stopcaption \startcontent \startMPcode draw LeftCharB ; path p ; p := boundingbox currentpicture ; draw SuperScript shifted urcorner p ; draw SubScript shifted (-UsedItalic,0) shifted lrcorner p ; WidenResultB ; \stopMPcode \stopcontent \startcaption subscript \stopcaption \stopcombination \stoplinecorrection Finally we add the scripts here. This time we position the superscript and subscript at the top and bottom anchors. The bottom anchor is, as mentioned, the old italic correction, and the top one currently just the edge. And this is what our next project is about: identify the ideal anchors and use these instead. In the \CONTEXT\ goodie files (the files that tweak the math fonts runtime) we can actually already set these top and bottom anchors and the engine will use them when set. These kerns are not to be confused with the more complicated staircase kerns. They are much simpler and lightweight. The fact that we already have them makes it relatively easy to experiment with this. It must be noted that we talk about three kinds of kerns: inter character kerns, corner kerns and staircase kerns. We can set them all up with tweaks but so far we only did that for the most significant ones, like integrals. The question is: can we automate this? We should be careful because the bad top accent anchors in the \TEXGYRE\ fonts demonstrate how flawed heuristics can be. Interesting is that the developers of these font used \METAPOST\ and are highly qualified in that area. And for us using \METAPOST\ is also natural! The approach that we follow is somewhat interactive. When working on the math update we like to chat (with zoom) about these matters. We discuss and explore plenty and with these kerns we do the same. Because \METAPOST\ produces such nice and crispy graphics, and because \METAFUN\ is well integrated into \CONTEXT\ we can link all these subsystems and just look at what we get. A lot is about visualization: if we discuss so called \quote {grayness} in the perspective of kerning, we end up with calculating areas, then look at what it tells us and as a next step figure out some heuristic. And of course we challenge each other into new trickery. % THIS WILL BECOME A MODULE! \startluacode local formatters = string.formatters local glyph = nil local mpdata = nil local f_boundingbox = formatters["((%N,%N)--(%N,%N)--(%N,%N)--(%N,%N)--cycle)"] local f_vertical = formatters["((%N,%N)--(%N,%N))"] function mp.lmt_glyphshape_start(id,character) if type(id) == "string" then id = fonts.definers.internal({ name = id } ,"") end local fontid = (id and id ~= 0 and id) or font.current() local shapedata = fonts.hashes.shapes[fontid] -- by index local characters = fonts.hashes.characters[fontid] -- by unicode local descriptions = fonts.hashes.descriptions[fontid] -- by unicode local shapeglyphs = shapedata.glyphs or { } if type(character) == "string" and character ~= "" then local hex = string.match(character,"^0x(.+)") if hex then character = tonumber(hex,16) else character = utf.byte(character) end else character = tonumber(character) end local chardata = characters[character] local descdata = descriptions[character] if chardata then glyph = shapeglyphs[chardata.index] if glyph and (glyph.segments or glyph.sequence) and not glyph.paths then local units = shapedata.units or 1000 local factor = 100/units local width = (descdata.width or 0) * factor local height = descdata.boundingbox[4] * factor local depth = descdata.boundingbox[2] * factor local math = descdata.math local italic = (math and math.italic or 0) * factor local accent = (math and math.accent or 0) * factor mpdata = { paths = fonts.metapost.paths(glyph,factor), boundingbox = fonts.metapost.boundingbox(glyph,factor), baseline = fonts.metapost.baseline(glyph,factor), width = width, height = height, depth = depth, italic = italic, accent = accent, usedbox = f_boundingbox(0,depth,width,depth,width,height,0,height), usedline = f_vertical(0,0,width,0), } end else print("NO",id,character) end end function mp.lmt_glyphshape_stop() glyph = nil mpdata = nil end function mp.lmt_glyphshape_n() if mpdata then mp.print(#mpdata.paths) else mp.inject.numeric(0) end end function mp.lmt_glyphshape_path(i) if mpdata then mp.print(mpdata.paths[i]) else mp.inject.pair(0,0) end end function mp.lmt_glyphshape_boundingbox() if mpdata then mp.print(mpdata.boundingbox) else mp.inject.pair(0,0) end end function mp.lmt_glyphshape_usedbox() if mpdata then mp.print(mpdata.usedbox) else mp.inject.pair(0,0) end end function mp.lmt_glyphshape_baseline() if mpdata then mp.print(mpdata.baseline) else mp.inject.pair(0,0) end end function mp.lmt_glyphshape_usedline() if mpdata then mp.print(mpdata.usedline) else mp.inject.pair(0,0) end end function mp.lmt_glyphshape_width () mp.print(mpdata and mpdata.width or 0) end function mp.lmt_glyphshape_depth () mp.print(mpdata and mpdata.depth or 0) end function mp.lmt_glyphshape_height() mp.print(mpdata and mpdata.height or 0) end function mp.lmt_glyphshape_italic() mp.print(mpdata and mpdata.italic or 0) end function mp.lmt_glyphshape_accent() mp.print(mpdata and mpdata.accent or 0) end \stopluacode \startMPdefinitions presetparameters "glyphshape" [ % id = "", % character = "", shape = true, boundingbox = false, baseline = false, usedline = true, usedbox = true, ] ; def lmt_glyphshape = applyparameters "glyphshape" "lmt_do_glyphshape" enddef ; vardef glyphshape_start(expr id, character) = lua.mp.lmt_glyphshape_start(id, character) ; enddef ; vardef glyphshape_stop = lua.mp.lmt_glyphshape_stop() ; enddef ; vardef glyphshape_n = lua.mp.lmt_glyphshape_n() enddef ; vardef glyphshape_path(expr i) = lua.mp.lmt_glyphshape_path(i) enddef ; vardef glyphshape_boundingbox = lua.mp.lmt_glyphshape_boundingbox() enddef ; vardef glyphshape_baseline = lua.mp.lmt_glyphshape_baseline() enddef ; vardef glyphshape_usedbox = lua.mp.lmt_glyphshape_usedbox() enddef ; vardef glyphshape_usedline = lua.mp.lmt_glyphshape_usedline() enddef ; vardef glyphshape_width = lua.mp.lmt_glyphshape_width() enddef ; vardef glyphshape_height = lua.mp.lmt_glyphshape_height() enddef ; vardef glyphshape_depth = lua.mp.lmt_glyphshape_depth() enddef ; vardef glyphshape_italic = lua.mp.lmt_glyphshape_italic() enddef ; vardef glyphshape_accent = lua.mp.lmt_glyphshape_accent() enddef ; vardef lmt_do_glyphshape = image ( pushparameters "glyphshape" ; lua.mp.lmt_glyphshape_start(getparameter "id", getparameter "character") ; if getparameter "shape" : draw for i=1 upto lua.mp.lmt_glyphshape_n() : lua.mp.lmt_glyphshape_path(i) && endfor cycle ; fi ; if getparameter "boundingbox" : draw lua.mp.lmt_glyphshape_boundingbox() withcolor red ; fi ; if getparameter "usedline" : draw lua.mp.lmt_glyphshape_usedline() withcolor green ; fi ; if getparameter "usedbox" : draw lua.mp.lmt_glyphshape_usedbox() withcolor blue ; fi ; lua.mp.lmt_glyphshape_stop() ; popparameters ; ) enddef ; \stopMPdefinitions \startplacefigure[location=none] \startMPcode[offset=1dk] picture leftchar ; picture rightchar ; path leftbbox ; path rightbbox ; numeric leftitalic ; numeric rightitalic ; numeric leftaccent ; numeric rightaccent ; numeric N ; N := 50 ; glyphshape_start("file:texgyrebonum-math.otf", "0x1D453") ; leftchar := image (draw for i=1 upto glyphshape_n : glyphshape_path(i) && endfor cycle ;) ; leftbbox := glyphshape_usedbox ; leftaccent := glyphshape_accent ; leftitalic := xpart urcorner leftbbox - glyphshape_italic ; glyphshape_stop ; glyphshape_start("file:texgyrebonum-math.otf", "0x1D45A") ; rightchar := image (draw for i=1 upto glyphshape_n : glyphshape_path(i) && endfor cycle ;) ; rightbbox := glyphshape_usedbox ; rightaccent := glyphshape_accent ; rightitalic := xpart urcorner rightbbox - glyphshape_italic ; glyphshape_stop ; rightchar := rightchar xshifted (xpart lrcorner leftbbox) ; rightbbox := rightbbox xshifted (xpart lrcorner leftbbox) ; rightaccent := rightaccent + xpart lrcorner leftbbox ; rightitalic := rightitalic + xpart lrcorner leftbbox ; numeric d ; d := (xpart lrcorner leftbbox) - leftitalic ; rightchar := rightchar shifted (d,0); rightbbox := rightbbox shifted (d,0); draw leftbbox withcolor 0.5white ; draw rightbbox withcolor 0.5white ; draw leftchar withpen pencircle scaled 1 ; draw rightchar withpen pencircle scaled 1 ; numeric miny, maxy ; miny := max(ypart lrcorner leftbbox, ypart llcorner rightbbox) ; maxy := min(ypart urcorner leftbbox, ypart ulcorner rightbbox) ; path testv ; testv := ((0,miny) -- (0,maxy)) xshifted (xpart lrcorner leftbbox) ; % % testv := testv shifted (d,0); % draw testv withcolor darkred ; path midpath, leftintersections, rightintersections ; pair leftintersection[], rightintersection[] ; numeric yta ; yta := 0 ; numeric minl ; minl := 1000 ; for i = 1 upto (N-1) : midpath := (0, ypart point (i/N) along testv) -- (xpart urcorner rightbbox, ypart point (i/N) along testv); for j within leftchar : midpath := midpath cutbeforelast pathpart j ; endfor for j within rightchar : midpath := midpath cutafterfirst pathpart j ; endfor if ( (i = 1) or ((xpart point 1 of midpath) - (xpart point 0 of midpath) < minl) ) : minl := (xpart point 1 of midpath) - (xpart point 0 of midpath) ; fi if ((xpart point 0 of midpath) < eps) or ((xpart point 1 of midpath) > ((xpart urcorner rightbbox) - eps)) : draw midpath withpen pencircle scaled 1 withcolor 0.1[white,darkgreen] withtransparency (1,0.5) ; midpath := (point 0 of midpath) && cycle ; fi draw midpath withcolor 0.4[white,darkgreen] ; draw point 0 of midpath withpen pencircle scaled 1 withcolor darkgreen ; draw point 1 of midpath withpen pencircle scaled 1.25 withcolor darkgreen ; yta := yta + (1/N)*((xpart point 1 of midpath) - (xpart point 0 of midpath)) ; endfor drawarrow (origin -- ((xpart lrcorner leftbbox) - leftitalic,0)) shifted (urcorner leftbbox) withcolor "orange" ; drawarrow (origin -- ((xpart lrcorner rightbbox) - rightitalic - d,0)) shifted (urcorner rightbbox) withcolor "orange" ; % draw (leftaccent, (ypart urcorner leftbbox )) withcolor "darkblue" withpen pencircle scaled 3 ; % draw (rightaccent + d, (ypart urcorner rightbbox)) withcolor "darkblue" withpen pencircle scaled 3 ; \stopMPcode \stopplacefigure We are sure that getting this next stage in the perfection of math typesetting in \CONTEXT\ and \LUAMETATEX\ will take quite some time, but the good news is that all machinery is in place. We also have to admit that it all might not work out well, so that we stick to what we have now. But at least we had the fun then. And it is also a nice example of both applying mathematics and programming graphics. That said, if it works out well, we can populate the goodie files with output from \METAPOST, tweak a little when needed, and that saves us some time. One danger is that when we try to improve rendering the whole system also evolves which in turn will give different output, but we can always implement all this as features because after all \CONTEXT\ is very much about configuration. And it makes nice topics for articles and talks too! The kerns discussed in the previous paragraphs are not the ones that we find in \OPENTYPE\ fonts. There we have \quote {staircase} kerns that stepwise go up or down by height and kern. So, one can have different kerns depending on the height and sort of follow the shape. This permits quite precise kerning between for instance the right bottom of a kernel and left top of a subscript. So how is that used in practice? The reference font Cambria has these kerns but close inspection shows that these are not that accurate. Fortunately, we never enter the danger zone with subscripts, because other parameters prevent that. If we look at for instance Lucida and Garamond, then we see that their kerns are mostly used as side bearing, and not really as staircase kerns. \usemodule[s][fonts-shapes] \startlinecorrection \startcombination[nx=5,ny=1] \startcontent \ShowGlyphShape{name:cambria-math}{100bp}{0x1D6FD} \stopcontent \startcaption \type {U+1D6FD} \stopcaption \startcontent \ShowGlyphShape{name:cambria-math}{100bp}{0x003A4} \stopcontent \startcaption \type {U+003A4} \stopcaption \startcontent \ShowGlyphShape{name:cambria-math}{100bp}{0x1D4CC} \stopcontent \startcaption \type {U+1D4CC} \stopcaption \startcontent \ShowGlyphShape{name:cambria-math}{100bp}{0x1D6B8} \stopcontent \startcaption \type {U+1D6B8} \stopcaption \startcontent \ShowGlyphShape{name:cambria-math}{100bp}{0x1D70C} \stopcontent \startcaption \type {U+1D70C} \stopcaption \stopcombination \stoplinecorrection In these figures you see a few glyphs from cambria with staircase kerns and although we show them small you will notice that some kern boundaries touch the shape. As subscripts never go that high it goes unnoticed but it also shows that sticking to the lowest boundary makes sense. We conclude that we can simplify these kerns, and just transform them into our (upto four) corner kerns. It is unlikely that Cambria gets updates and that other fonts become more advanced. One can even wonder if multiple steps really give better results. The risk of overlap increases with more granularity because not every pair of glyphs is checked. Also, the repertoire of math characters will likely not grow and include shapes that differ much from what we can look at now. Reducing these kerns to simple ones, that can easily be patched at will in a goodie file, has advantages. We can even simplify the engine. \stopsection \startsection[title=Conclusion] So how can we summarize the above? The first conclusion is that we can only get good results when we runtime patch fonts to suite the engine and our (\CONTEXT) need. The second conclusion is that we should seriously consider to drop (read: ignore) most math font parameter and|/|or to reorganize them. There is no need to be conforming, because these parameters are often not that well implemented (thumb in mouth). The third conclusion (or observation) is that we should get rid of the excessive use of italic correction, and go for our new corner kerns instead. Last, we can conclude that it makes sense to explore how we can use \METAPOST\ to analyze the shapes in such a way that we can improve inter character kerning, corner kerns and maybe even, in a limited way, staircase kerns. And, to come back to accents: very few characters need a top kern. Most can be handled with centered anchors, and we need tweaks for margins and overshoot anyway. The same is true for many other tweaks: they are there to stay. This is how we plan to go forward: \startitemize[packed] \startitem We pass no italic corrections in the math fonts to the engine, but instead we have four dedicated simple corner kerns, top and bottom anchors, and we also compensate negative left side bearing. We should have gone that route earlier (as follow up on a \MKIV\ feature) but were still in some backward compatibility mindset. \stopitem \startitem The \LUAMETATEX\ math engine might then be simplified by removing all code related to italic correction. Of course it hurts that we spent so much time on that over the years. We can anyway disable engine options related to italic correction in the \CONTEXT\ setup. Of course the engine is less old school generic then but that is the price of progress. \stopitem \startitem A default goodie file is applied that takes care of this when no goodie file is provided. We could do some in the engine, but there is no real need for that. We can simplify the mid 2022 goodie files because we have to fix less glyphs. \stopitem \startitem If we ever need italic correction (that is: backtrack) then we use the (new) \type {\mccode} option code that can identity sloped shapes. But, given that ignoring the correction between sloped shapes looks pretty bad, we can as well forget about this. After all, italic correction never really was about correcting italics, but more about anchoring scripts. \stopitem \startitem Staircase kerns can be reduced to simple corner kerns and the engine can be simplified a bit more. In the end, all we need is true widths and simple corner kerns. \stopitem \startitem We reorganize the math parameters and get rid of those that are not really font design dependent. This also removes a bit of overlap. This will be done as we document. \stopitem \startitem Eventually we can remove tweaks that are no longer needed in the new setup, which is a good thing as it also save us some documenting and maintenance. \stopitem \stopitemize All this will happen in the perspective of \CONTEXT\ and \LUAMETATEX\ but we expect that after a few years of usage we can with confidence come to some conclusions that can trickle back in the other engines so that other macro packages can benefit from a somewhat radical different but reliable approach to math rendering, one that works well with the old and new fonts. \stopsection \stopchapter \stopcomponent