mathjax / MathJax

Beautiful and accessible math in all browsers
http://www.mathjax.org/
Apache License 2.0
10.14k stars 1.16k forks source link

Invalid SVG markup in MathJax 4.0.0-beta6 #3226

Open grillonbleu opened 5 months ago

grillonbleu commented 5 months ago

Issue Summary

When using MathJax to render maths as SVG, there are various "data-" attributes on the tags, some of which contain illegal characters for XML (namely "<" and ">"). "data-semantic-speech" is one such attribute. SVG files need to be valid XML, therefore using MathJax to produce SVG files yields invalid results.

Steps to Reproduce:

  1. Go on page with LaTeX and MathJax script "tex-svg.js".
  2. Right-click on renderered expression.
  3. Do "Show Math As / SVG Image".
  4. Copy and paste displayed code in "math.svg".
  5. Try to open "math.svg" in Chrome, Firefox or other compatible applications. An error is displayed.

Technical details:

I am not using any MathJax configuration.

and loading MathJax via

<script type='text/javascript' src='https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.6/tex-svg.js'></script>

Supporting information:

dpvc commented 5 months ago

Thanks for the report. You are correct, there is an error in producing the SVG: I called the HTML serializer rather than the XML serializer in error. I will make a PR to fix it.

In the meantime, there are two approaches you could take.

  1. Turn off the "Semantic Enrichment" item in the "Options" menu near the bottom of the MathJax contextual menu. This will prevent all the speech, latex, and semantic data tags from being added to the SVG output; that may be what you are looking for.

  2. Use the following configuration to fix the issue (the only change from the original is to use serializeXML() rather than outerHTML().

MathJax = {
  startup: {
    ready() {
      const {Menu} = MathJax._.ui.menu.Menu;
      Menu.prototype.toSVG = function (math) {
        const jax = this.jax.SVG;
        if (!jax) return Promise.resolve('SVG can\'t be produced.<br>Try switching to SVG output first.');
        const adaptor = jax.adaptor;
        const cache = jax.options.fontCache;
        const breaks = !!math.root.getProperty('process-breaks');
        if (cache !== 'global' && (math.display || !breaks) &&
            adaptor.getAttribute(math.typesetRoot, 'jax') === 'SVG') {
          for (const child of adaptor.childNodes(math.typesetRoot)) {
            if (adaptor.kind(child) === 'svg') {
              return Promise.resolve(this.formatSvg(adaptor.serializeXML(child)));
            }
          }
        }
        return this.typesetSVG(math, cache, breaks);
      }
      MathJax.startup.defaultReady();
    }
  }
};

See if that does the trick for you.

grillonbleu commented 5 months ago

Thank you for your thorough response! I didn’t want to complicate things in the report, so I didn’t mention that I retrieve some SVG renders programmatically with MathJax.tex2svgPromise. My current workaround is to strip out the data- attributes:

MathJax.tex2svgPromise(latex).then(function(svg) {
  svg.querySelectorAll("*").forEach(element =>
    Object.keys(element.dataset).forEach(dataKey => delete element.dataset[dataKey])
  );
  // do stuff with the svg
});