mathjax / MathJax

Beautiful and accessible math in all browsers
http://www.mathjax.org/
Apache License 2.0
10.14k stars 1.16k forks source link

v4-beta.3 linebreak not respected with `\text` (and other issues) #3098

Open TrebledJ opened 1 year ago

TrebledJ commented 1 year ago

Issue Summary

Linebreak issue with text elements.

Steps to Reproduce:

  1. Open web page.
  2. See math sticking out of container and not breaking.
  3. Cry.

Linebreak not working with text. Math is a bit better, but also sticks out a bit.

For example:

$\texttt{The quick brown } \text{fox jumps } \textsf{over the lazy dog}\dots\ \texttt{And } \texttt{over } \texttt{the } \texttt{rainbow } \texttt{dost } \texttt{thy } \texttt{chicken } \texttt{poop } \texttt{fly}\dots$

There are also instances when output errors occur for regular LaTeX and for \breaks.

$$
e^{i\pi} + 1 = 0
\qquad
\begin{align}
x + y + z &= 1 \\\\
2x - y + 3z &= 2 \\\\
4x - 5y + 10z &= 3 \\\\
\end{align}
$$

$\texttt{Maybe (Either (a, a) (Bool, a))} \break \equiv \texttt{(Maybe a, Maybe a)}$
$\texttt{Maybe (Either (a, a) (Bool, a))} \break = \texttt{(Maybe a, Maybe a)}$

All of these are shown in the supporting demo.

Technical details:

I am using the following MathJax configuration:

MathJax = {
    tex: {
        inlineMath: [
            [
                '$', '$'
            ],
            [
                '\\(', '\\)'
            ]
        ],
    },
    svg: {
        fontCache: 'global',
        displayOverflow: 'linebreak',
    },
};

and loading MathJax via

<script defer id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.3/tex-mml-svg.js" type="text/javascript"></script>

Supporting information:

pkra commented 1 year ago

I believe you need to load the textmacros extension to get \\ to work inside \text.

dpvc commented 1 year ago

I believe you need to load the textmacros extension to get \\ to work inside \text.

While true, none of the example use \\ inside \text.

dpvc commented 1 year ago

MathJax's rules for breaking in-line math are based on the TeX rules for in-line breaks, which are basically that breaks can occur at characters that have class BIN or REL (binary operators and relations) at the top level of the expression. Your first example has no binary or relational operator (the \dots produce characters with class INNER), so there is no valid breakpoint in that expression. In particular, the spaces inside the \text elements are not valid breakpoints in TeX; if you put this expression into actual TeX, it also will not break. So MathJax is doing what it was intended to do in this case.

Your second example, with the align environment, is not valid LaTeX markup; the error message is correct and similar to the one you get if you put this expression into actual LaTeX. The align environment is a top-level environment, and can't be placed inside other expressions. For that, you need to use the aligned environment. If you switch to that, the expression should work for you.

Your third and fourth examples that use \break are more subtle. MathJax translates your expressions into MathML internally, and there are some side-effects of the MathML that are occurring here. MathML has a concept called an embellished operator, which is intended to allow things like an equal sign with a question mark over top to be treated (for spacing and line-breaking purposes) as the operator at their core, so that the embellishments don't interfere with the spacing. An operator is embellished if it is in a container whose only other elements are "spacelike" elements (there are also some other ways to be embellished). A spacelike element is either an mspace or an mtext element (or some other more complicated things), and a container can be the expression itself. It is natural to consider mspace to be spacelike, but I've never fully understood why the specification makes mtext be spacelike. Apparently, it is because some people use mtext with space characters to perform spacing, but that seems a poor reason to make all mtext elements be spacelike.

In your expression \texttt{Maybe (Either (a, a) (Bool, a))} \break \equiv \texttt{(Maybe a, Maybe a)}, the underlying MathML is

<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mtext mathvariant="monospace">Maybe (Either (a, a) (Bool, a))</mtext>
  <mspace linebreak="newline"></mspace>
  <mo>&#x2261;</mo>
  <mtext mathvariant="monospace">(Maybe a, Maybe a)</mtext>
</math>

Note that this consists of an operator (the equivalent sign) together with mtext and mspace elements, all contained in the math element container, so the mo is considered an embellished operator. That means that it, together with its embellishments (the mspace and mtext elements) are all treated as a unit. That is why the break indicated by the mspace is not being shown.

This is the same issue for your case that involves the equal sign as well. Ironically, it is also the case for the long text example at the bottom of your supporting demo page. There is one operator in that expression (the period), and the rest are text elements, so the entire sentence is an embellished operator, and treated as a single (unbreakable) unit, so no line-breaking occurs. This illustrates why I find the idea that mtext elements are to be "spacelike" to be so inappropriate.

One way around the problem is to add any non-text, non-space element into the expression. For example, adding {} to the beginning of the expression, as in

{}\texttt{Maybe (Either (a, a) (Bool, a))} \break \equiv \texttt{(Maybe a, Maybe a)}

would make the operator no longer be an embellished one, and so line breaking would occur as expected. The same goes for the long expression in your demo (or you could put the period inside one of the preceding \text{} macro, so there would be no operator at all).

Alternatively, you could adjust how MathJax interprets "spacelike" to make mtext elements spacelike only if they consist only of actual space characters, and not make mspace spacelike if it is specifying a line break. To do that, you can incorporate

MathJax = {
  startup: {
    ready() {
      const {MML} = MathJax._.core.MmlTree.MML;
      MML.mspace = class myMmlMspace extends MML.mspace {
        get isSpacelike() {
          return this.attributes.getExplicit('linebreak') === undefined && this.canBreak;
        }
      }
      MML.mtext = class myMmlMtext extends MML.mtext {
        get isSpacelike() {
          const attributes = this.attributes;
          const spaces = !!this.getText().match(/^\s*$/);
          return spaces && (attributes.getExplicit('mathbackground') === undefined &&
                            attributes.getExplicit('background') === undefined &&
                            attributes.getExplicit('style') === undefined);
        }
      }
      MathJax.startup.defaultReady();
    }
  }
};

into your MathJax configuration. Then the expression you have used should break as you would expect.

dpvc commented 1 year ago

I forgot to mention the error messages you received. That is due to having switched to CHTML output using the contextual menu. It turns out that the way exports are handled in ESM modules is causing a problem loading the output/chtml and output/svg extensions used to switch renderers. I will have a fix for that in the next beta release, but for now, you can use the contextual menu to switch back to SVG and the message should go away.

dpvc commented 1 year ago

Finally, it would help us if you only include one type of problem in one issue post. Your line-break issues could all be in one, but the problem with the expression involving the align environment, and the issue with the error messages in the console would be better in separate issues, as they are unrelated to the line breaking. Having a separate issue tracker for each makes it easier for us to track and resolve them independently. Thanks!

TrebledJ commented 1 year ago

Thanks for the comprehensive explanation!

After some testing, placing {} at the start didn't seem to work for me, but placing it somewhere in the middle works. E.g.

\texttt{Maybe (Either (a, a) (Bool, a))} {} \break \equiv \texttt{(Maybe a, Maybe a)}

Another thing I forgot to mention was automatic linebreaking (i.e. without \break). I've put that in a different issue: #3099.

P.S. Apologies for not testing my align tex beforehand and conflating this issue. I had assumed the example would work. 🥲

dpvc commented 1 year ago

It shouldn't matter where the {} is, but wherever it works for you is fine.

Note that it should only matter when there is only one operator in the expression. If you have \text{...} \equiv \text{...} \equiv \text{...} it should not be needed.