Dynamic line breaking
Although line breaking works in Mathjax 4.0.0, the equations do not break themselves as the window size decreases and I have to refresh the website with in the smaller screen size so that the equations break properly. Is there any way to have the equations dynamically break so that the content does not go outside the window margins?
Here is my MathJax setup:
window.MathJax = {
section: {
n: -1,
useLetters: false,
letters: "AABCDEFGHIJKLMNOPQRSTUVWXYZ"
},
loader: {load: ['[tex]/tagformat', '[tex]/mathtools', 'output/chtml']},
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']], //allow inline math
displayMath: [['$$','$$']],
tagSide: 'right', //location of equation numbers
tags: 'all',
packages: {'[+]': ['tagformat', 'sections', 'autoload-all', 'mathtools']},
tagformat: {
number: (n) => {
const section = MathJax.config.section;
return (section.useLetters ? section.letters[section.n] : section.n) + '.' + n;
}
}},
chtml: {
mtextInheritFont: true, // font to use for mtext, if not inheriting (empty means use MathJax fonts)
displayOverflow: 'linebreak'
},
linebreaks: { // options for when overflow is linebreak
inline: true, // true for browser-based breaking of inline equations
width: '100%', // a fixed size or a percentage of the container width
lineleading: 2, // the default lineleading in em units
LinebreakVisitor: null, // The LinebreakVisitor to use
},
startup: {
ready() {
const {CommonWrapper} = MathJax._.output.common.Wrapper;
const {LineBBox} = MathJax._.output.common.LineBBox;
const Configuration = MathJax._.input.tex.Configuration.Configuration;
const CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
new CommandMap('sections', {
nextSection: 'NextSection',
setSection: 'SetSection',
}, {
NextSection(parser, name) {
MathJax.config.section.n++;
parser.tags.counter = parser.tags.allCounter = 0;
},
SetSection(parser, name) {
const section = MathJax.config.section;
const c = parser.GetArgument(name);
const n = section.letters.indexOf(c);
if (n >= 0) {
section.n = n;
section.useLetters = true;
} else {
section.n = parseInt(c);
section.useLetters = false;
}
},
});
Object.assign(CommonWrapper.prototype, {
invalidateBBox(bubble = true) {
if (this.bboxComputed || this._breakCount >= 0) {
this.bboxComputed = false;
this.lineBBox = [];
this._breakCount = -1;
if (this.parent && bubble) {
this.parent.invalidateBBox();
}
}
},
_getLineBBox: CommonWrapper.prototype.getLineBBox,
getLineBBox(i) {
if (!this.lineBBox[i] && !this.breakCount) {
const obox = this.getOuterBBox();
this.lineBBox[i] = LineBBox.from(obox, this.linebreakOptions.lineleading);
}
return this._getLineBBox(i);
}
});
Configuration.create(
'sections', {handler: {macro: ['sections']}}
);
MathJax.startup.defaultReady();
}
}
};
MathJax wraps displayed equations to the initial size of the window, and does not reflow equations when the window changes (as you have found). Breaking displayed equations is an expensive process, and MathJax does not currently have a process for triggering equations to be re-rendered when their container size changes. It would be possible to write an extension that does that, and that would make a nice contribution to the project, if you or anyone is interested in doing so. I can make suggestions about how to do so if anyone does want to try it.
Sounds good. If the project is not too demanding, I would be interested.
Well, the main idea would be to use a ResizeObserver and register all the displayed equations with it, then when a resize event occurs loop through the nodes that are changed (these are passed to the observer) and set the state for the associated math items back to before the metrics action (e.g., mathitem.state(STATE.METRICS - 1)).
You can use a renderAction that follows the update action (e.g., STATE.INSERTED + 1) and run through the document's math list for new displayed equations (mathitem.display is true for displayed equations), and add any new ones to the resize observer. You can use the mathitem.outputData to store a flag that indicates whether the math item is already in the resize observer (e.g., set mathitem.outputData.resizeObserved = true when you add to the resize observer). The mathitem.typesetRoot should be a pointer to the mjx-container DOM element, which should have display: block for a displayed equation, and so should be able to generate the needed resize events. You will want to keep a Map() object that ties the DOM node back to the MathItem object so that when the resize event occurs, you can recover the MathItem and set its state back to STATE.METRICS - 1, as indicated above.
Then remove the DOM node from the ResizeObserver (since when the math is re-typeset, it will get a new mix-container, and your renderAction will add that to the resize observer again.
Once you have looped through all the math items whose sizes have changed, you can ask the document to typeset again, and it should remeasure and re-typeset the math items for which you have set the state back.
That's the main idea. There are details to be worked out, and eventually some additional structure to make it an actual extension, but you can start by just working with a configuration that sets up the renderActions and come globally defined functions for now. There are examples of renderActions in various issues here, so search for renderActions and you should get several examples. A render action has a name and an array that consists of a number (giving its order in the list), a function that has one argument (the MathDocument being used) that is called when the document is typeset, and a function with two arguments (a MathItem and the MathDocument it is in) that is called when an individual equation is re-rendered (e.g., when an maction item changes the equation).
If you want to give it a try, see how far you can get, and I can give you a hand once you have a start at it.
Ok sounds good. I will set aside some time next week to try this out. Quick comment: My equation numbers still seem to go outside of the margins on small screens (320px wide) even when using the above MathJax setup. Here is one such example:
\begin{align}
\log\big(L(\theta|h,n)\big) &= \log {n \choose h}\ + h\log(\theta) + (n-h)\log(1-\theta)
\label{eq:binom-log-likelihood}
\end{align}
My equation numbers still seem to go outside of the margins
Yes, that is a separate issue, which I corrected in mathjax/MathJax-src#926, but didn't give you a patch for. You can add
const {ChtmlMtable} = MathJax._.output.chtml.Wrappers.mtable;
Object.assign(ChtmlMtable.prototype, {
adjustWideTable() {
const attributes = this.node.attributes;
if (attributes.get('width') !== 'auto') return;
const [pad, align] = this.getPadAlignShift(attributes.get('side'));
const W = Math.max(this.containerWidth / 10, this.containerWidth - pad - (align === 'center' ? pad : 0));
this.naturalWidth() > W && this.adjustColumnWidths(W);
}
});
to your startup.ready function and that should take care of it.
There is also a PR that improves the results for breaking in tables, but it is too big to make a patch for it. But see mathjax/Mathjax-src#927 for details. Note that you can use \vbox{} (or \mathmakebox{} from the mathtools extension) around the left to prevent it from breaking.
Okay perfect! Thanks again for the very fast responses! The equation numbers are now within the margin of the post.
Great! Thanks for the confirmation.
Inline equations do automatically adjust linebreaks when the window is resized, presumably because it's left to the browser. Is there anything stopping display equations from acting in this way (or having an option for that to happen) - i.e. splitting the equation into multiple parts which can then be broken accordingly by the browser?
Is there anything stopping display equations from acting in this way
Yes. The breaking for displayed equations is much more sophisticated than for in-line expressions, as it takes into account the nesting depth, good- and bad-break indicators, balancing the width of lines, and lots of other factors, not just how much material can fit on one line. The browser's line breaking is based solely on maximizing the content per line, and is not appropriate for breaking displayed equations.
Handling changing window sizes would involve an extension like the one I describe above that would re-break wide expressions when needed. It's not something that the browser will be able to handle itself.