MathJax V2 vs V3 rendering
Hi,
We have identified a difference in the output of the MathML tags with v2 and v3 for the same equation.
With v2:
With v3:
Issue: There is a space after i(superscript), G(subscript), and C(subscript) in v2, whereas in v3 the space has been removed.
MathML Equation:
<mml:math display="block"><mml:msub><mml:mrow><mml:mo movablelimits="false">Hom</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="italic">G</mml:mi></mml:mrow></mml:msub><mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo><mml:mi mathvariant="italic">V</mml:mi><mml:mo mathvariant="normal">,</mml:mo><mml:mi mathvariant="italic">W</mml:mi><mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo><mml:msup><mml:mrow><mml:mo stretchy="false">↪</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="italic">i</mml:mi></mml:mrow></mml:msup><mml:msub><mml:mrow><mml:mo movablelimits="false">Hom</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="double-struck">C</mml:mi></mml:mrow></mml:msub><mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo><mml:mi mathvariant="italic">V</mml:mi><mml:mo mathvariant="normal">,</mml:mo><mml:mi mathvariant="italic">W</mml:mi><mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo><mml:mover><mml:mrow><mml:mo movablelimits="false">⟶</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="normal">Φ</mml:mi></mml:mrow></mml:mover><mml:msub><mml:mrow><mml:mo movablelimits="false">Hom</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="italic">G</mml:mi></mml:mrow></mml:msub><mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo><mml:mi mathvariant="italic">V</mml:mi><mml:mo mathvariant="normal">,</mml:mo><mml:mi mathvariant="italic">W</mml:mi><mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo><mml:msup><mml:mrow><mml:mo stretchy="false">↪</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="italic">i</mml:mi></mml:mrow></mml:msup><mml:msub><mml:mrow><mml:mo movablelimits="false">Hom</mml:mo></mml:mrow><mml:mrow><mml:mi mathvariant="double-struck">C</mml:mi></mml:mrow></mml:msub><mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo><mml:mi mathvariant="italic">V</mml:mi><mml:mo mathvariant="normal">,</mml:mo><mml:mi mathvariant="italic">W</mml:mi><mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo><mml:mo mathvariant="normal">,</mml:mo></mml:math>
Is there any reason for the difference that has been observed?
Thanks, Saksham Gambhir
Here's what's happening. By default, MathJax uses TeX spacing rules rather than MathML spacing rules, and TeX determines spacing by applying one of eight TeX classes to every item in the expression, and uses the pair of classed to determine the spacing between adjacent items. When MathJax processes an <mo> node, it looks up the operator in the MathML operator dictionary to find the TeX class to assign it, but if it doesn't appear in the dictionary, it gets TeX class REL (for relation). Because Hom is not in the dictionary, it gets TeX class REL via this rule. Note that the arrows in your expression also are TeX class REL. TeX sets the spacing between two relations to zero (so that something like <= will have to space between the two characters), which is why you are getting no space there.
In version 2 of MathJax, there was an exception made to the rule for mo nodes that aren't in the dictionary: if it is a multi-letter name (like Hom), then it would get TeX class OP (for operator) rather than REL. That rule was not included in v3 (an oversight). I have made a pull request to add that in.
In the meantime, there are several possible approaches you could take. First, you could configure MathJax's output to use MathML spacing rather than TeX spacing, which would include space around any operator that is not in the dictionary.
Another possibility would be to change <mo movablelimits="false">Hom</mo> to <mi>Hom</mi>, which is probably a better encoding anyway. It would be possible to add a MathML filter to the MathML input jax during MathJax's setup that would looks through the expression for such <mo> nodes and convert them to <mi>.
Another possibility would be to add Hom to the dictionary with TeX class OP so that MathJax could find it and apply the needed TeX class automatically, though if you use other operators, they would each have to be added to the dictionary separately.
Finally, you could use the following configuration to patch to add the special rule for multi-letter operators.
MathJax = {
startup: {
ready() {
const {mo} = MathJax._.core.MmlTree.MML.MML;
const {getRange} = MathJax._.core.MmlTree.OperatorDictionary;
const {TEXCLASS} = MathJax._.core.MmlTree.MmlNode;
mo.prototype.checkOperatorTable = function (mo) {
let [form1, form2, form3] = this.handleExplicitForm(this.getForms());
this.attributes.setInherited('form', form1);
let OPTABLE = this.constructor.OPTABLE;
let def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo];
let noTexClass = this.getProperty('texClass') === undefined;
if (def) {
if (noTexClass) {
this.texClass = def[2];
}
for (const name of Object.keys(def[3] || {})) {
this.attributes.setInherited(name, def[3][name]);
}
this.lspace = (def[0] + 1) / 18;
this.rspace = (def[1] + 1) / 18;
} else {
let limits = this.attributes.get('movablelimits');
let isOP = !!mo.match(/^[a-zA-Z]{2,}$/);
let range = getRange(mo);
if (range) {
if (noTexClass) {
this.texClass = (isOP && range[2] === TEXCLASS.REL) || limits ? TEXCLASS.OP : range[2];
}
const spacing = this.constructor.MMLSPACING[range[2]];
this.lspace = (spacing[0] + 1) / 18;
this.rspace = (spacing[1] + 1) / 18;
} else if (noTexClass) {
this.texClass = isOP || limits ? TEXCLASS.OP : TEXCLASS.REL;
}
}
};
MathJax.startup.defaultReady();
}
}
};
I think that will improve the situation for you.
You might also want to add stretchy="false" to the long right arrow, as right now it is shrinking to a smaller size that is sufficient to cover the capital phi above it.
Hi
Thank You for providing the in-depth details.
I have a couple of points to ask for the above solutions provided.
- How do we configure MathJax's output to use MathML? is there any specific property we need to add to the mathjax-config.js?
- How/Where do we add words to the dictionary with TeX class OP?
- We tried to add the patch code in the mathjax.config.js as below, but this didn't help as we observed no change even after adding the code.
Best, Saksham Gambhir
Hi Team,
Could you please update us on the queries posted above?
Best, Saksham Gambhir
We respond to questions when we are able to, and that sometimes takes time. Your message is still in my queue, and I haven't forgotten about you, but have not had time to respond. Poking me about it doesn't speed things up. In fact, it often makes it take longer, as I have to deal with extra messages and respond to them, like now.
You say that there was no effect, but did you clear the browser cache before trying? It may be that you have the original mathjax.config.js file in cache and aren't getting the new one. Try adding a console.log() message in the checkOperatorTable() function and make sure that it is actually being called. If not, add one in the top of the ready() function and make sure THAT is being called. If not, you may need to provide more than just a snippet of the configuration.
Hi,
Just to confirm that we are able to view the updated code and even after clearing the browser cache, we are not able to view the updates we are expecting. As suggested please find the attached mathjax-config file under which I have added the provided solution. Please review and let us know if it still requires more changes to incorporate the changes we are looking for.
Also, suggest a way to add words to the dictionary with TeX class OP, and how do we configure MathJax's output to use MathML? is there any specific property we need to add to the mathjax-config.js?
Best, Saksham Gambhir mathjax-config.zip
Just to confirm that we are able to view the updated code and even after clearing the browser cache, we are not able to view the updates we are expecting.
Does this mean that the mathjax-config.js file that you see in your browser does not include the changes that you have made and show in the screen-shot above? If that is the case, then you may need to check that you have actually updated the mathjax-config.js file is in the correct place on the server.
If you mean that the changes are in the mathjax-config.js file that you see in your browser, but the output from MathJax is not as you expect it to be, then that suggests that your mathajx-config.js file is not being processed at the correct time.
I have used your mathjax-config.js file verbatim, only removing the call to document.mathInputProcessed();, and it works for me as expected in this test file:
<!DOCTYPE html>
<html xmlns:mml="http://www.w3.org/1998/Math/MathML">
<head>
<title>Test configuration file</title>
<script src="mathjax-config.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js"></script>
</head>
<body>
<mml:math display="block">
<mml:msub>
<mml:mrow>
<mml:mo movablelimits="false">Hom</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="italic">G</mml:mi>
</mml:mrow>
</mml:msub>
<mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo>
<mml:mi mathvariant="italic">V</mml:mi>
<mml:mo mathvariant="normal">,</mml:mo>
<mml:mi mathvariant="italic">W</mml:mi>
<mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo>
<mml:msup>
<mml:mrow>
<mml:mo stretchy="false">↪</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="italic">i</mml:mi>
</mml:mrow>
</mml:msup>
<mml:msub>
<mml:mrow>
<mml:mo movablelimits="false">Hom</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="double-struck">C</mml:mi>
</mml:mrow>
</mml:msub>
<mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo>
<mml:mi mathvariant="italic">V</mml:mi>
<mml:mo mathvariant="normal">,</mml:mo>
<mml:mi mathvariant="italic">W</mml:mi>
<mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo>
<mml:mover>
<mml:mrow>
<mml:mo movablelimits="false" stretchy="false">⟶</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="normal">Φ</mml:mi>
</mml:mrow>
</mml:mover>
<mml:msub>
<mml:mrow>
<mml:mo movablelimits="false">Hom</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="italic">G</mml:mi>
</mml:mrow>
</mml:msub>
<mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo>
<mml:mi mathvariant="italic">V</mml:mi>
<mml:mo mathvariant="normal">,</mml:mo>
<mml:mi mathvariant="italic">W</mml:mi>
<mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo>
<mml:msup>
<mml:mrow>
<mml:mo stretchy="false">↪</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="italic">i</mml:mi>
</mml:mrow>
</mml:msup>
<mml:msub>
<mml:mrow>
<mml:mo movablelimits="false">Hom</mml:mo>
</mml:mrow>
<mml:mrow>
<mml:mi mathvariant="double-struck">C</mml:mi>
</mml:mrow>
</mml:msub>
<mml:mo mathvariant="normal" fence="true" stretchy="false">(</mml:mo>
<mml:mi mathvariant="italic">V</mml:mi>
<mml:mo mathvariant="normal">,</mml:mo>
<mml:mi mathvariant="italic">W</mml:mi>
<mml:mo mathvariant="normal" fence="true" stretchy="false">)</mml:mo>
<mml:mo mathvariant="normal">,</mml:mo>
</mml:math>
</body>
</html>
I have formatted the MathML to make it easier to read, and added stretchy="false" to the long right arrow, as I recommended above. Please try this file and see if that works for you. The output for me is
Because this does work for me, I suspect that the issue may be that your configuration is not being processed at the right time. When I mentioned that your screen shot of a snippet of the configuration was not enough to diagnose the problem, I actually meant that we needed to see not only the full configuration file, but also how it is loaded, where it is in your HTML file in comparison to the script that loads MathJax itself, and perhaps other details about your workflow in the browser. The configuration doesn't seem to be the issue.
Did you do the tests that I recommended above? Namely, inserting a console.log() call inside the checkOperatorTable() call to be sure that it is actually being used? I suspect it is not.
[Please] suggest a way to add words to the dictionary with TeX class OP
Here is a configuration that does that for several function names:
MathJax = {
startup: {
ready() {
const {OPTABLE} = MathJax._.core.MmlTree.OperatorDictionary;
const {TEXCLASS} = MathJax._.core.MmlTree.MmlNode;
const OP = [1, 2, TEXCLASS.OP];
[
'sin', 'cos', 'tan',
'log', 'ln',
'Hom'
].forEach(fn => OPTABLE.infix[fn] = OP);
MathJax.startup.defaultReady();
}
}
};
You can add more names to the list of functions to be added to the operator dictionary.
how do we configure MathJax's output to use MathML?
I'm a little confused by the question. MathJax v3 and above has two output formats: CommonHTML (CHTML) and SVG. It does not have a native MathML output mode, as v2 did. It does have a MathML input mode, but not an output one.
Since your math is already in MathML format, why would you need MathJax to produce MathML (your math is already MathML). So I'm not quite sure what you are asking for, here.
Hi Team,
Thank You for replying back on the queries we have raised so far.
As we are still not able to resolve the issue as per the output we are expecting here are some more pointers to be addressed:
First, I would like to get a confirmation on 1 point from the above example -> "http://www.w3.org/1998/Math/MathML" The above-mentioned schema is used for rendering according to V2 of MathML - I'm I correct on this point? As I observed this in the above example you have shared. If Yes, we are facing issues in V3 and once we process using V3 the spaces get removed.
Also, we want to execute the conversion as soon as the mathJax is initialized, so we have kept document.mathInputProcessed(); under mathjax-config.js as startup: { ready: function() {document.mathInputProcessed();} } - The original file was shared earlier.
We also added the patch provided by you and wrote console.log() along with it to see if the code is updated and not cached, but the patch is not working as expected even code was updated.
Another point, In the very first comment you mentioned: you could configure MathJax's output to use MathML spacing rather than TeX spacing, which would include space around any operator that is not in the dictionary. - how do we configure and check we are using MathML spacing instead of TeX spacing?
Note: we are looking for a solution that can be implemented from the code side and not from the content (as making any change in the content is not possible for us)
Best, Saksham Gambhir
As we are still not able to resolve the issue as per the output we are expecting
You didn't respond to whether my test file above (in the October 12th comment) works for you. Does that work or not?
The configuration that changes the OPTABLE should also have fixed the issue. If that didn't work for you, then can you provide an actual example file that includes it that doesn't work for you? It is not enough to simply say "it doesn't work", as I've tested both and they work for me, so there must be something different about what we are doing. I can't tell whether you have made the changes properly without seeing what you are actually doing.
The above-mentioned schema is used for rendering according to V2 of MathML ... ?
Both MathJax v2 and v3 are based on the MathML v3 specification, not MathML v2.
we are facing issues in V3 and once we process using V3 the spaces get removed
Yes, I indicated above why that is the case, and it is a bug in v3. I have made a fix for it that will be included in the next release, and gave you a patch (Aug 27th) that should resolve it for you. I have tested the patch, and it works for me. If that is not working for you, then you need to provide a complete page that illustrated the problem you are having, as there is something that you are doing differently from me. Saying it doesn't work without giving me what you are actually doing (not just the configuration, but the rest of the code that interacts with MathJax) gives me nothing to go on. I used your mathjax-config.js file unchanged except for the removal of document.mathInputProcessed(), which you didn't provide (so I could not test with that), and it works for me on the expression you provided. If that is not working for you, you need to provide a complete example page showing that.
we want to execute the conversion as soon as the mathJax is initialized, so we have kept
document.mathInputProcessed();undermathjax-config.jsasstartup: { ready: function() {document.mathInputProcessed();} }
MathJax may be initialized before the page is ready to be processed. That is, the ready() function may run before the page content is in place, so if you try to typeset then, you may be doing so before the math is actually available. E.g., if you used the async attribute on the script tag that loads MathJax, then the ready() function may run as soon as MathJax has been loaded, which can happen at any point as your document is being processed. You may either need to use defer instead of async, or use the pageReady() function instead of ready(), since that runs when both MathJax is initialized and the page contents are ready to be processed.
Note also that MathJax will typeset the page automatically as soon as it can, unless to set typeset: false in the startup section of the MathJax configuration. It is not clear whether you have set this, as you only give a screen shot of a portion of your configuration. It is also not clear what your mathInputProcessed() function is doing, since you don't provide it.
I'm assuming that startup: { ready: function() {document.mathInputProcessed();} } is just a short-hand version, not what you are actually doing, as without the MathJax.startup.defaultReady() being called in ready(), you will get no typesetting.
We also added the patch provided by you and wrote console.log() along with it
It is not clear from your comment whether that console log was performed or not: "the patch is not working as expected" could mean that log message didn't appear, or it could mean that it did but that your output still was not what you wanted. Can you clarify that?
how do we configure and check we are using MathML spacing instead of TeX spacing
As described in the documentation, you should combine
MathJax = {
chtml: {
mathmlSpacing: true;
},
svg: {
mathmlSpacing: true;
}
};
into you configuration to have MathJax use MathML spacing rules rather than TeX spacing rules.
You can test this by using
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<mi>x</mi>
<mo>+</mo>
<mo>=</mo>
<mo>-</mo>
<mi>y</mi>
</math>
and compare the output to when you don't have mathmlSpacing set. With mathmlspaling: true, you should see
and without it (or with mathmlspaling: false) you will see
making any change in the content is not possible for us
Note that MathJax allows you to add pre-filters that can be used to modify the MathML before MathJax processes it, so if there is problematic MathML in your original source files, it may still be possible to fix it on the fly as MathJax typesets it. For example, I had suggested adding stretchy="false" to the long-right arrow; it would be possible to use a pre-filter to look through the MathML for <mo> elements with content equal to U+27F6 (long right arrow), and add the needed attribute. Adding
MathJax = {
startup: {
ready() {
MathJax.startup.defaultReady();
MathJax.startup.document.inputJax[0].mmlFilters.add(function ({document, data}) {
const adaptor = document.adaptor;
for (const mo of adaptor.tags(data, 'mo')) {
if (adaptor.textContent(mo) === '\u27F6') {
adaptor.setAttribute(mo, 'stretchy', false);
}
}
});
}
}
};
to your configuration will accomplish that.