Wrapping wide equations in SVGMath
NOTE 1: View this page on a phone (or in developer mode so you can change width) so you can see the wrapping behaviour.
NOTE 2: These are not robust solutions, rather intended as proof of concept.
Introduction
A KaTeX user couldn't get the line break to work in a \split
environment, as reported in GitHub.
I created a page experimenting with possible wrapping solutions with KaTeX. There was some discussion about whether there would need to be a "hybrid" solution, where KaTeX would be used for elements that wrap. The experiment page shows that would not actually be necessary.
SVGMath
In the meantime, I developed SVGMath, a way to present math on the Web with super-fast load times. As an exercise, I worked on the same equations I used for the KaTEX wrapping demo, but for this page, I'm using SVGMath.
Here is the equation from the Github discussion, rendered via SVGMath, wrapping in a way that is phone-friendly, as well as working appropriately on a desktop.
To achieve the above:
- As in the KaTeX wrapping page, I added a class
alignEquals
(has magenta border) to the <div> containing the math. The script that runs after SVG Math loads determines where the "=" in the first line is, then indents the second line (and any remaining equals signs) by the same amount. - I created a break at the "+" sign in the second row, like this:
... }^{2}dx\right.}
then{\left.\; +\ldots
,
which invisibly closes the large opening bracket for the second line, while at the end, we invisibly open the remaining large bracket section, thus allowing the equation to break at that point.
See also the relevant code below.
Example not involving equals on each line
The next example demonstrates a tidy way to wrap long equations.
Basic wrapping - splitting up the equation
In this next example, I split the equation into pieces on any "+" signs (something like KaTeX does) to give this result:
The problem is, any wrapped elements go to the far left of the screen, and in this example the line height means the bottom of each fraction very nearly overlaps the one below. It's pretty ugly and difficult to read.
Improved wrapping - indentation
This next one produces a cleaner wrapped equation:
To achieve the above:
- Once again I've added the class
alignEquals
to the <div> containing the math. This time the post-processing script has determined where the "=" in the first line is once again, then indents the second and subsequent lines by the same amount. - I created breaks at each "+" sign on the right hand side, like this:
... /(2!)x^2 [break] +\ (n(n-1)...
- I added some padding above each line to separate them nicely (otherwise they very nearly overlap each other).
Improved wrapping: after + signs, like LaTeX
LaTeX (and mathJax and KaTex) allow line breaks after + (or −) signs. I actually prefer when they come before (as above), since it's clearer in most cases, but to be consistent with LaTeX, I changed where the breaks come for the next example which has the plus signs at the end of each line as it wraps:
I've added some spacing near the + signs for both of the above examples so it looks better.
Equations with wide left sides
Making use of split equation wrapping
In this next case, the LHS of the equation is wide. The equal signs still line up, as before, and I'm adding breaks at the + signs:
Making use of overflow-x:auto
The square root in the next one gives us a dilemma since it's hard to break the expression under the square root so that it can wrap. In this next case, I make use of overflow-x:auto
so the user can scroll to the right to see the whole equation:
However, the majority of users (according to user testing) don't scroll left-right, either because they don't see the scroll bar, or are scanning the page quickly so they don't pause to interact with it. This means a key aspect of the equation could be missed.
Here are two possible improvements, which make use of breaking the equations at the + signs, as before:
Making use of the sign and ( )
Making use of ( ) and exponent
For the above two examples, I made use of a block near the end, as follows: \left. +\ w^{10} \right)^{0.5}
so that the final right bracket with exponent wraps together with the w^10 term.
The script
function doAlign(alignEqIndex) { // On scroll, process one .alignEquals only // On resize, do the lot if(typeof(alignEqIndex) != 'undefined') { selector = document.querySelectorAll('.alignEquals'); start = alignEqIndex; selectorLength = 1; } else { selector = document.querySelectorAll('.alignEquals'); selectorLength = selector.length; start = 0; } // Loop through one (or all) .alignEquals DIVs for (i = start; i < start + selectorLength; i++) { if( !selector[i].classList.contains('dunOredy') ) { ps = selector[i].querySelectorAll('p'); eqSignPos = 0; // Loop through p's for (k = 0; k < ps.length; k++) { pskLeft = ps[k].getBoundingClientRect().left; // Remove any styles on resize ps[k].removeAttribute('style'); // Find indent of the (first) '=' sign in the first paragraph if (k == 0) { if (ps[0].querySelector('.mrel')) { var eqSignNode = ps[0].querySelector('.mrel'); } else if (ps[0].querySelector('path[data-c='3D']')) { var eqSignNode = ps[0].querySelector('svg path[data-c='3D']'); } else if (ps[0].querySelector('img[data-eqls]')) { var eqSignNode = ps[0].querySelector('img[data-eqls]'); eqSignNodeLeft = eqSignNode.getBoundingClientRect().left; imgEqLeft = ps[0].querySelector('img[data-eqls]').getAttribute('data-eqls'); eqSignPos = 1*eqSignNodeLeft + 1*imgEqLeft - 1*pskLeft; } else { eqSignNode = null; } var alignEqualsLeft = selector[i].getBoundingClientRect().left; } else { ps[k].style.display = 'inline-block'; ps[k].style.marginTop = '0'; ps[k].style.marginLeft = eqSignPos + 'px'; } // If the <p> has multiple math spans var mathIds = (ps[k].querySelectorAll('img[data-f]')); if (mathIds.length > 1) { var mathId0Top = mathIds[0].getBoundingClientRect().top; numRows = 1 for (m = 1; m < mathIds.length; m++) { mathIdsMBCR = mathIds[m].getBoundingClientRect(); mathIds[m].style.marginLeft = ''; mathIds[m].style.marginTop = ''; var mathIdmTop = mathIdsMBCR.top; var mathIdmBott = mathIdsMBCR.top - mathIdsMBCR.height; // If wrapping has occured, indent wrapped elements and add spacing above if (mathIdmTop >= mathIds[m - 1].getBoundingClientRect().bottom) { numRows++; if (ps.length === 1) { mathIds[m].style.marginLeft = (1 * eqSignPos + 20) + 'px'; } else if (mathIds[0].hasAttribute('data-eqls')) { mathIds[m].style.marginLeft = (20) + 'px'; } mathIds[m].style.marginTop = 0.85 + 'em'; } } } } selector[i].classList.add('dunOredy'); } } }