Request to Reintroduce MatchWebFonts Option in MathJax v3/v4
Is your feature request related to a problem? Please describe. The MatchWebFonts configuration option was available in MathJax v2 to ensure that web fonts were correctly measured and used in the rendering pipeline once loaded. In MathJax v3 and v4, this option is no longer available, and there is currently no equivalent feature. This creates issues for users who need explicit control over font re-measurement to avoid layout shifts or mismatches when web fonts are loaded after initial typesetting.
Describe alternatives you've considered
- Manually setting scale factor (not always easy to synchronize with measurement)
Here is a configuration that may do the trick for you.
MathJax = {
startup: {
async pageReady() {
const {STATE} = MathJax._.core.MathItem;
const status = document.fonts.status;
await MathJax.startup.defaultPageReady();
if (status !== 'loaded') {
await document.fonts.ready;
MathJax.startup.document.state(STATE.METRICS - 1);
MathJax.typeset();
}
}
}
};
This checks if all fonts have been loaded when MathJax does its initial typesetting, and then if there were unloaded fonts, it was for all the fonts to load and serenaders the document. Setting thew state back to before the metrics were determined will cause the new metrics to be used, but it will also remove the typeset math from the page, so you will likely get a flash. An alternative is
MathJax = {
startup: {
async pageReady() {
const {STATE} = MathJax._.core.MathItem;
const status = document.fonts.status;
await document.fonts.ready;
await MathJax.startup.defaultPageReady();
if (status !== 'loaded') {
MathJax.startup.document.state(STATE.METRICS - 1);
MathJax.typeset();
}
}
}
};
where you wait for all the page fonts to load before doing any MathJax typesetting.
Neither of these is quite what the old MathWebFonts extension did, but perhaps one or the other is sufficient for your needs for now.
Here is a configuration that works more like the origin MatchWebFonts (by checking for which expressions need to be updated and only updating those), so should produce less jitter.
MathJax = {
startup: {
async pageReady() {
const jax = MathJax.startup.document.outputJax;
jax.updateMetrics = function (mdoc) {
const adaptor = this.adaptor;
const tests = [new Map(), new Map()];
for (const math of mdoc.math) {
const node = adaptor.parent(math.start.node);
const map = tests[math.display ? 1 : 0];
if (!map.has(node)) {
map.set(node, this.getTestElement(node, math.display));
}
}
const metrics = [new Map(), new Map()];
for (const i of tests.keys()) {
for (const [node, test] of tests[i].entries()) {
metrics[i].set(node, this.measureMetrics(test)/*.ex*/);
}
}
for (const test of tests.values()) {
for (const node of test.values()) {
adaptor.remove(node);
}
}
for (const math of mdoc.math) {
const node = adaptor.parent(math.start.node);
const {ex, scale} = metrics[math.display ? 1 : 0].get(node);
if (math.metrics.scale !== scale) {
math.metrics.ex = ex;
math.metrics.scale = scale;
math.rerender(mdoc);
}
}
};
const status = document.fonts.status;
await MathJax.startup.defaultPageReady();
if (status !== 'loaded') {
await document.fonts.ready;
jax.updateMetrics(MathJax.startup.document);
}
}
}
};
The test elements used to determine the ex-size are inserted all at once (and only one per parent node so if a parent contains several expressions, it is only tested once). Then they are all measured once (in order to do only one page reflow rather than a reflow for each expression as in the old MatchWebFonts extension). The ex-size and scale are cached, and then the test nodes are removed. Finally, we look through all the math nodes to see if their original scale differs from the new scale, and if so, we update the metrics and rerender the expression.