TeX’s font-loading optimisation
A user of Peter Wilson’s fonttable
package reported an interesting bug in the interaction between it and the Spanish module (if not others) of babel
.
Here’s the problem in a nutshell:
\documentclass{article}
\usepackage[spanish]{babel}
\begin{document}
$\lim x_n$
{\font\x=cmr10\x hello}
$\lim x_n$
\end{document}
Can you see the problem? It certainly wasn’t immediately obvious to me.
I probably wouldn’t have had time to work through the problem (after all, it’s clearly babel
’s problem, not fonttable
’s) but the user that reported the problem had also tracked down the root of it already (thanks!). In Spanish, the maths operator \lim
is written as ‘lím’, which requires the use (in 8-bit LaTeX) of an acute accent combined with a dotless ‘i’. And inside the definition for \dotlessi
in babel
’s Spanish module, you’ll find
\expandafter\expandafter\expandafter
\split@name\expandafter\string\the\textfont\mathgroup\@nil
Note the \split@name
command, which takes an NFSS font name such as OT1/cmr10/m/n/10
and split it into its components (resp.: encoding, family, series, shape, size). It’s operating on \the\textfont\mathgroup
there, and this is where the problem comes from.
By default in a 10pt LaTeX document, \textfont
is defined as OT1/cmr10/m/n/10
.
In pseudocode, LaTeX originally calls something like this:
\font \OT1/cmr10/m/n/10 = cmr10 at 10pt
\textfont\fam=\OT1/cmr10/m/n/10
(This would not execute without ‘/
’ having \catcode
‘letter’.) The problem comes in when someone comes along and writes something like
\font\x=cmr10 at 10pt
Because TeX does not want to load the same font more than once, it optimises its references to font names to point to the last name a specific font was requested with (here ‘font’ means ‘font face + size’).
At this point, the \textfont
now points to \x
instead of \OT1/cmr10/m/n/10
, which causes \split@name
to break pretty spectacularly. And this even happens if the font loading happens inside a group:
\font\1=cmr10 at 9pt
{\font\2=cmr10 at 9pt}
\textfont\fam=\1
\showthe\textfont\fam
The above code returns ‘\2
’, quite unintuitively. So what can be done about this problem? Let’s assume for now that we can’t change how babel
does things (I don’t know the practicality of that in any event).
One way to avoid this problem occurring might be to dynamically check the current ‘values’ of \textfont
, \scriptfont
, etc., and then not reload the same font under a different name if it’s the same as the font you would like to load. This could be implemented in a wrapper around \font
like \newcommand
is around \def
, and this might be something we look into for expl3
.
A less general solution is to simply load the new font like this:
\font\x=cmr10 at 9.9999pt
Because the font is technically at a different size (although the difference is imperceptible), TeX considers it a separate font and will no longer replace its old pointer to \OT1/cmr10/m/n/10
with \x
. (I also perform this trick in unicode-math
to load the same font with different \fontdimen
s for assigning to different math groups.)
This is certainly less robust than an explicit check, and will not always be an acceptable solution, but it’s simple to implement in fonttable
and serves its purpose there quite well.
In conclusion, due to this fragility in babel
(I don’t know if it’s the Spanish module only but it seems likely the problem could occur elsewhere), be careful loading certain fonts with \font
unless you are confident it won’t also be used as a maths font. If possible, use the LaTeX font loading interface instead.