Wrapping wide KaTeX equations

NOTE 1: View this page on a phone 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.

Here is that equation rendered via KaTeX, but it wraps in a way that is phone-friendly, as well as working accceptably on a desktop.

`err=(int_oo^oo(P(x)-f(x))^2dx)^0.5`

`=(sum_(i=0)^(s^2-1) int_(x in D_i)(p_i-f(x))^2dx :}` `{:+ int_(x notin D)f(x)^2dx)^0.5`

To achieve the above:

  1. The example (as all the ones on this page) uses ASCIIMath input (not LaTeX), but I expect equation wrapping can be done in a similar way with LaTeX.
  2. ASCIIMath doesn't have any \align or \equation environments, so when we want to align equal signs we need to add a class alignAtEquals to the <div> containing the math. The script that runs after KaTeX has finished determines where the "=" in the first line is, then indents the second line (and any remaining equals signs) by the same amount.
  3. I created a break at the "+" sign in the second row, like this:
    ... f(x))^2dx :}` `{:+ int_(x notin D)...
  4. The :} symbol invisibly closes the large opening bracket for the second line, while the other one, {: invisibly opens the remaining large bracket section, thus allowing the equation to break at that point.

See also the relevant code below.

Example not involving equals after first line

The next example demonstrates a tidy way to wrap long equations.

Default KaTeX wrapping

KaTeX wraps wide equations at any "+" or "−" signs it comes across, as follows:

`(1+x)^n=1+nx+(n(n-1))/(2!)x^2+(n(n-1)(n-2))/(3!)x^3+(n(n-1)(n-2)(n-3))/(4!)x^4+ ...`

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 overlaps the one below.

Improved wrapping

This next one produces a cleaner wrapped equation:

`(1+x)^n=1+nx+(n(n-1))/(2!)x^2` `+\ (n(n-1)(n-2))/(3!)x^3` `+\ (n(n-1)(n-2)(n-3))/(4!)x^4` `+\ ...`

To achieve the above:

  1. Once again I've added the class alignAtEquals 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 after the equals sign, like this:
    ... /(2!)x^2` `+\ (n(n-1)...
  3. I added some padding above each line to separate them nicely.

Improved wrapping: after + sign, like LaTeX

LaTeX (and KaTex as seen in the example above) 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:

`(1+x)^n=1+nx+(n(n-1))/(2!)x^2\ +` `(n(n-1)(n-2))/(3!)x^3\ +` `(n(n-1)(n-2)(n-3))/(4!)x^4\ +` `...`

I've added some spacing near the + signs for both ofthe above example so it looks better.

Equations with wide left sides

Making use of KaTeX wrapping

In this next case, the LHS of the equation is wide. The equal signs still line up, as before, and I'm making use of KaTeX's default breaks at + signs:

`1 + m + n^2 + p^3 + q^4 + r^5 + s^6 + t^7 + u^8 + v^9 + w^10 = 3^3`

`=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:

`sqrt(1 + m + n^2 + p^3 + q^4 + r^5 + s^6 + t^7 + u^8 + v^9 + w^10)`

`= 3^3`

`=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 KaTeX's default wrapping at the + sign:

Making use of the √ sign and ( )

`sqrt{::} (1 + m + n^2 + p^3 + q^4 + r^5 + s^6 :}` `+\ t^7+u^8+v^9+` `{:w^10)`

`=3^3`

`=27`

Aligning the equals is tricky in such cases, so they have been placed on separate lines.

Making use of ( ) and exponent

`(1 + m + n^2 + p^3 + q^4 + r^5 + s^6 :}` `+\ t^7+u^8+v^9+` `{:w^10)^0.5`

`=27`

`=3^3`

For the above two examples, I made use of a block near the end, as follows: `{:w^10)^0.5 ` so that the final right bracket with exponent wraps together with the `w^10` term.

The code

doAlign = function() {
  selector = document.querySelectorAll('.alignEquals');
  
  // Loop through all .alignEquals DIVs
  for (i = 0; i < selector.length; i++) {
    ps = selector[i].querySelectorAll('p');
	
    // Loop through <p>s
    for (k = 0; k < ps.length; k++) {
		
      // Remove any styles if resizing
      ps[k].removeAttribute("style");
	  
      // Find indent of the "=" sign in the first paragraph
      if (k == 0) {
        if (ps[0].querySelector('.mrel')) {
          var alignEqualsLeft = selector[i].getBoundingClientRect().left;
          eqSignPos = ps[0].querySelector('.mrel').getBoundingClientRect().left - alignEqualsLeft;
        }
      } else if (ps[k].innerHTML.indexOf("mrel") > 0) {
        ps[k].style.marginLeft = eqSignPos + "px";
      } else if (ps[k].innerHTML.indexOf("mord") > 0) {
        ps[k].style.marginLeft = (eqSignPos) + "px";
      }

      // If the <p> has multiple math spans
      var mathIds = ps[k].querySelectorAll("span[id^='mathId']");

      if (mathIds.length > 1) {
        var mathId0Top = mathIds[0].getBoundingClientRect().top;
        for (m = 1; m < mathIds.length; m++) {
          mathIds[m].removeAttribute("style");
          var mathIdmTop = mathIds[m].getBoundingClientRect().top;
		  
          // If wrapping has occured, indent wrapped elements and add spacing above
          if(mathIdmTop > mathId0Top+30 ){
            mathIds[m].style.display = "block";
            if (!ps[k].style.marginLeft) {
              mathIds[m].style.marginLeft = (eqSignPos + 20) + "px";
            } else {
              mathIds[m].style.marginLeft = 20 + "px";
            }
            mathIds[m].style.paddingTop = 0.75 + "em";
          }
        }
      }
    }
    eqSignPos = 0;
  }
}