Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MathJax or similar: render arbitrary HTML element inside expression?

I have a specific need regarding mathematical expression rendering in a web application, and while I've so far been mostly looking at MathJax, I'm certainly not wedded to it.

What I need is the ability to render a mathematical expression where one or more of the terms can be, essentially, an arbitrary HTML box element, like so: Example output

...It would be preferable if the layout responded to the size of the HTML box (e.g. parenthesis would auto-size to match its height, like any other "normal" LaTeX/MathJax element), but if I need to specify an exact size in pixels or something like that, that'd be OK too. Would even be OK if I had to e.g. insert a "placeholder" element in the actual visualization, but could know exactly where it wound up in the output so I could overlay the HTML element precisely on top of it.

In other words, I'm open to outside-the-box or even hackish solutions; just need something that works.

Also possibly relevant: I'm really only going to be using pretty basic LaTeX elements: parentheses, fractions, normal operators. I don't even really need to be able to support the summation in the above example, if that's a complicating factor.

like image 304
DanM Avatar asked Nov 15 '19 18:11

DanM


1 Answers

There are a couple of ways to go about this. Your suggestion of inserting a placeholder and overlaying it is one, which can be done as follows:

<script type="text/x-mathjax-config">
MathJax.Hub.Queue(function () {
  var PX = function (x) {return x.toFixed(2) + 'px'};
  var space = document.getElementById('space');
  var sbox = space.getBoundingClientRect();
  var math = MathJax.Hub.getJaxFor(space).SourceElement().previousSibling;
  var mbox = math.getBoundingClientRect();
  var x = sbox.left - mbox.left;
  var y = sbox.top - mbox.top;
  var html = MathJax.HTML.Element('div', {id:'html', style: {
    position:'absolute', top:PX(y), left:PX(x), 
    width:PX(sbox.width), height:PX(sbox.height), 'line-height':'normal'
  }}, [[
    'table',{style:{height:'100%', width:'100%', background:'red', border:'5px solid green'}}, [[
      'tr',{},[[
        'td',{},['abc']
      ]]
    ]]
  ]]);
  math.style.position = 'relative';
  math.appendChild(html);
});
</script>
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS_CHTML" defer></script>

<div style="xfont-size: 150%">
$$x + \left(\,\cssId{space}{\Space{100px}{55px}{45px}}\,\right) + y$$
</div>

Here we use \Space to insert a blank area of the desired width, height, and depth, and \cssId to mark the space so we can retrieve it later. We use MathJax.Hub.Queue() to queue a function to run after MathJax typesets the math. This function looks up the space-holder element and finds the container element that MathJax uses to hold the math. It gets the bounding box information for each, and computes the position of the overlay in relation to the math. Then it created the overlay (the content is hardcoded in this instance), with the proper size and potion to lay over top of the space-holder.

This works, but is a bit awkward, and for displayed equations, if the window size changes, the overlay may not stay in the correct position. Also, it is only set up to work for CommonHTML output.


An alternative is the following, which uses the a <semantics> element to include HTML into the internal MathML structure used by MathJax. It adds a new macro \insertHTML{} that does it.

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
  CommonHTML: {
    styles: {
      //
      // remove CSS for '.mjx-math *'
      //
      '.mjx-math *': {
        display: null,
        '-webkit-box-sizing': null,
        '-moz-box-sizing': null,
        'box-sizing': null,
        'tex-align': null
      },
      //
      // add CSS for .mjx-math span instead
      //
      '.mjx-math span': {
        display: 'inline-block',
        '-webkit-box-sizing': 'context-box !important',
        '-moz-box-sizing': 'context-box !important',
        'box-sizing': 'context-box !important',
        'tex-align': 'left'
      },
      //
      // override display for .mjx-char spans
      //
      'span.mjx-char': {
        display: 'block'
      }
    }
  }
});
MathJax.Hub.Register.StartupHook("TeX Jax Ready", function () {
  var MML = MathJax.ElementJax.mml;
  var TEX = MathJax.InputJax.TeX;
  TEX.Definitions.macros.insertHTML = 'InsertHTML';
  TEX.Parse.Augment({
    InsertHTML: function (name) {
      var html = this.GetArgument(name).replace(/^\s*<!--\s*/,'').replace(/\s*-->\s*$/,'');
      var span = MathJax.HTML.Element('mjx-reset', {style: {display:'inline-block'}});
      span.innerHTML = html;  // serious security risk if users can enter math
      span.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
      var mml = MML["annotation-xml"](MML.xml(span)).With({encoding:"application/xhtml+xml",isToken:true});
      this.Push(MML.semantics(mml));
    }
  });
});
</script>
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS_CHTML" defer></script>

<div style="xfont-size: 150%">
$$x + \left(\,\insertHTML{<!--
<table width="100" height="100"
  style="display:inline-table; vertical-align:-.25em; background:red; border:5px solid green;
  box-sizing:border-box !important">
<tr><td style="text-align:center">abc</td></tr>
</table>
-->}\,\right) + y$$
</div>


$$x+\left(\insertHTML{<!--
<i>this</i> is <b>html</b>
-->}\right)+y$$

Because MathJax won't process math that contains HTML tag directly, you have to put the HTML in a comment, which you see in the two example expressions above. This version works on all output formats (even MathML output), but it does require a bit of configuration to override some of the CommonHTML CSS that would apply to the content due to it being contained internally within the MathJax layout.

The HTML in this case will stay properly positioned no matter what happens to the window, and it has the advantage of allowing the HTML to be given in the expression itself. Note, however, that this presents a security risk if your site allows users to enter mathematics, since it means that they can enter arbitrary HTML into your page. In this case, you would want to sanitize the HTML prior to setting the innerHTML in the InsertHTML function.

like image 55
Davide Cervone Avatar answered Oct 08 '22 12:10

Davide Cervone