MathJax icon indicating copy to clipboard operation
MathJax copied to clipboard

HTMLAdaptor.fontSize returns NaN when node is removed from DOM

Open devkat opened this issue 4 months ago • 3 comments

Issue Summary

When a DOM node that MathJax.typesetPromise was called on is removed from the DOM during the rendering process, the font size of the mjx-test node cannot be computed. window.getComputedStyle returns an empty string as value of the fontSize property and HTMLAdaptor.fontSize returns NaN.

https://github.com/mathjax/MathJax-src/blob/master/ts/adaptors/HTMLAdaptor.ts#L587

This results in console errors:

Error: <svg> attribute height: Expected length, "NaNex".
Error: <svg> attribute viewBox: Expected number, "0 -666 NaN NaN".

In our case the removal of the node happens due to the re-rendering of the involved React components.

Steps to Reproduce:

  1. Call MathJax.typesetPromise on a DOM node
  2. Remove the node from the DOM

Technical details:

  • MathJax Version: 4.0.0
  • Client OS: macOS 15.6.1
  • Browser: Chrome 141.0.7390.108

I am using the following MathJax configuration:

MathJax = {
  startup: {
    typeset: false,
    pageReady() {
      …
    },
  },
  loader: {
    load: ['ui/safe'],
    source: {
      '[tex]/amsCd': '[tex]/amscd',
    },
  },
  output: {
    fontPath: `${mathJaxPath}/mathjax-newcm-font`,
  },
  options: {
    safeOptions: {
      allow: {
        //
        //  Values can be "all", "safe", or "none"
        //
        URLs: 'none', // 'none' to prevent cookie theft through external URLs
        classes: 'safe', // safe start with mjx- (can be set by pattern below)
        cssIDs: 'safe', // safe start with mjx- (can be set by pattern below)
        styles: 'safe', // safe are in safeStyles below
      },
      //
      //  Which styles are allowed
      //
      safeStyles: {
        color: true,
        backgroundColor: true,
        border: true,
        cursor: true,
        margin: true,
        padding: true,
        textShadow: true,
        fontFamily: true,
        fontSize: true,
        fontStyle: true,
        fontWeight: true,
        opacity: true,
        outline: true,
      },
      lengthMax: 3, // Largest padding/border/margin, etc. in em's
      scriptsizemultiplierRange: [0.6, 1], // Valid range for scriptsizemultiplier
      scriptlevelRange: [-2, 2], // Valid range for scriptlevel
      classPattern: /^mjx-[-a-zA-Z0-9_.]+$/, // Pattern for allowed class names
      idPattern: /^mjx-[-a-zA-Z0-9_.]+$/, // Pattern for allowed ids
      dataPattern: /^data-mjx-/, // Pattern for data attributes
    },
  },
  tex: {
    inlineMath: [['$', '$']],
    displayMath: [['$$', '$$']],
    processEscapes: true,
  },
};

and loading MathJax via

<script src="/mathjax/tex-svg.js"></script>

Supporting information:

  • Please supply a link to a (live) minimal example page, when possible.
  • If your issue is with the display of the mathematics produced by MathJax, include a screen snapshot that illustrates the problem, when possible.
  • Check your browser console window for any error messages, and include them here.
  • Include the MathJax configuration you are using, and the script tag that loads MathJax itself.

devkat avatar Oct 27 '25 21:10 devkat

Thank you for your report. You are correct about the source of the issue being in the adaptor's fontSize() method.

Running MathJax on DOM elements that are not part of an active DOM (or that are hidden via display: none) is problematic in a number of ways. MathJax can't do the measurements it needs, not just for the font size, but for the ex-height, the container width (for line breaking), or the widths of characters not in the MathJax fonts (less of an issue with the v4 fonts that have much greater coverage).

It is true, however, that having fontSize() return an actual number in this case would be useful, and I will submit a PR to do that. In the meantime, this configuration would provide a work-around:

MathJax = {
  startup: {
    ready() {
      MathJax.startup.defaultReady();
      MathJax.startup.document.adaptor.fontSize = function (node) {
        const style = this.window.getComputedStyle(node);
        return parseFloat(style.fontSize || '16');
      }
    },
  },
};

Here we return 16 px as the font size. The ex-size will be created from that using the output.exFactor configuration option when a direct measurement can't be made, and the default factor is .5 so the ex-height will be taken as 8 px. These don't have much affect on SVG output, as the width, height, and vertical-align values are set in ex units, but will affect any measurements in non-font-relative units like px or mm, and will control the width used for line breaking.

dpvc avatar Oct 29 '25 14:10 dpvc

I've made a PR to fix this.

dpvc avatar Oct 29 '25 17:10 dpvc

@dpvc , thank you very much for your response. The workaround works for us for the moment; we will update when the fix is available.

devkat avatar Nov 04 '25 07:11 devkat