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.

math expression: err equals left parenthesis integral Subscript negative normal infinity Superscript normal infinity Baseline left parenthesis upper P left parenthesis x right parenthesis minus f left parenthesis x right parenthesis right parenthesis squared right parenthesis Superscript 0.5

math expression: equals left parenthesis sigma summation Underscript i equals 0 Overscript s squared minus 1 Endscripts integral Underscript x element of upper D Subscript i Baseline Endscripts left parenthesis p Subscript i Baseline minus f left parenthesis x right parenthesis right parenthesis squared d x

math expression: plus integral Underscript x not an element of upper D Endscripts f left parenthesis x right parenthesis squared d x right parenthesis Superscript 0.5

To achieve the above:

  1. 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.
  2. 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:

math expression: left parenthesis 1 plus x right parenthesis Superscript n Baseline equals 1 plus n x

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis Over 2 factorial EndFraction x squared

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis Over 3 factorial EndFraction x cubed

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis left parenthesis n minus 3 right parenthesis Over 4 factorial EndFraction x Superscript 4

math expression: plus ellipsis

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:

math expression: left parenthesis 1 plus x right parenthesis Superscript n Baseline equals 1 plus n x

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis Over 2 factorial EndFraction x squared

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis Over 3 factorial EndFraction x cubed

math expression: plus StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis left parenthesis n minus 3 right parenthesis Over 4 factorial EndFraction x Superscript 4

math expression: plus ellipsis

To achieve the above:

  1. 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.
  2. I created breaks at each "+" sign on the right hand side, like this:
    ... /(2!)x^2 [break] +\ (n(n-1)...
  3. 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:

math expression: left parenthesis 1 plus x right parenthesis Superscript n Baseline equals 1 plus n x plus StartFraction n left parenthesis n minus 1 right parenthesis Over 2 factorial EndFraction x squared plus

math expression: StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis Over 3 factorial EndFraction x cubed plus

math expression: StartFraction n left parenthesis n minus 1 right parenthesis left parenthesis n minus 2 right parenthesis left parenthesis n minus 3 right parenthesis Over 4 factorial EndFraction x Superscript 4 Baseline plus

math expression: ellipsis

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:

math expression: 1 plus m plus n squared plus math expression: p cubed plus math expression: q Superscript 4 plus math expression: r Superscript 6 plus math expression: s Superscript 6 plus math expression: t Superscript 7 plus math expression: u Superscript 8 Baseline plus v Superscript 9 plus math expression: x Superscript 10 Baseline equals 3 cubed

math expression: equals 27

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:

math expression: StartRoot 1 plus m plus n squared plus p cubed plus q Superscript 4 Baseline plus r Superscript 6 Baseline plus s Superscript 6 Baseline plus t Superscript 7 Baseline plus u Superscript 8 Baseline plus v Superscript 9 Baseline plus x Superscript 10 Baseline plus y Superscript 11 Baseline plus z Superscript 12 Baseline EndRoot

math expression: equals 3 cubed

math expression: equals 27

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 math expression: StartRoot EndRoot sign and ( )

math expression: StartRoot EndRoot math expression: left parenthesis 1 plus m plus n squared plus p cubed math expression: plus q Superscript 4 math expression: plus r Superscript 5 math expression: plus s Superscript 6 math expression: plus t Superscript 7 math expression: plus u Superscript 8 math expression: plus v Superscript 9 math expression: plus w Superscript 10 Baseline right parenthesis equals 3 cubed

math expression: equals 27

Making use of ( ) and exponent

math expression: left parenthesis 1 plus m plus n squared plus p cubed math expression: plus q Superscript 4 math expression: plus r Superscript 5 math expression: plus s Superscript 6 math expression: plus t Superscript 7 math expression: plus u Superscript 8 math expression: plus v Superscript 9 math expression: plus w Superscript 10 Baseline right parenthesis Superscript 0.5 Baseline equals 27

math expression: equals 3 cubed

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');
    }
  }
}