Re: [mathjax-users] How to make inline math copyable?

996 views
Skip to first unread message

Davide Cervone

unread,
Mar 18, 2020, 9:59:12 AM3/18/20
to mathja...@googlegroups.com
MathJax's output is not intended for direct copying via selection, and it would make sense to do so only for the simplest of expressions (e.g., no fractions, square roots, exponents, and so on).

It is possible to provide the ability to copy the original TeX source when the expression is selected, however.  You could use the following configuration to do so:

    <script>
    MathJax = {
      options: {
        renderActions: {
          addCopyText: [155,
            (doc) => {for (const math of doc.math) MathJax.config.addCopyText(math, doc)},
            (math, doc) => MathJax.config.addCopyText(math, doc)
          ]
        }
      },
      addCopyText(math, doc) {
        const adaptor = doc.adaptor;
        const text = adaptor.node('mjx-copytext', {'aria-hidden': true}, [
          adaptor.text(math.start.delim + math.math + math.end.delim)
        ]);
        adaptor.append(math.typesetRoot, text);
      },
      startup: {
        ready() {
          MathJax._.output.chtml_ts.CHTML.commonStyles['mjx-copytext'] = {
            display: 'inline-block',
            position: 'absolute',
            top: 0, left: 0, width: 0, height: 0,
            opacity: 0,
            overflow: 'hidden'
          };
          MathJax.startup.defaultReady();
        }
      }
    }
    </script>

This attaches a visually hidden element to the MathJax output that contains the original TeX code as text that will be selected when the typeset math is part of a selection.  That means it will be copied to the clipboard in place of the MathJax output (which can't be selected, as you point out).  This should allow you to select a paragraph with typeset math and be able to copy it so that it includes the TeX markup as though it were not typeset.

This only allows selection of the entire expression (not sub-expressions).  Also, there is no visual indication that the expression is selected (I could not find a way to include highlighting of the expression when selected).  So this is useful but perhaps not optimal.

The reason you can't select the typeset characters themselves is that the CommonHTML output uses CSS content style rules to insert he needed characters into the pag, and CSS content is not selectable.  (This was not the case in CommonHTML output in v2, so you could select pieces of if there, but you could not select SVG output, so even in v2, this was not always possible.)

In order to make the text selectable, you would need to override the portions of MathJax that handle the CSS and the character output.  That can be done with the following configuration:

    <script>
    MathJax = {
      startup: {
        ready() {
          const {TeXFont} = MathJax._.output.chtml.fonts.tex_ts;
          const {CHTMLFontData} = MathJax._.output.chtml.FontData;
          const {CHTMLTextNode} = MathJax._.output.chtml.Wrappers.TextNode;

          for (const name of Object.keys(TeXFont.defaultChars)) {
            var chars = TeXFont.defaultChars[name];
            for (const n of Object.keys(chars)) {
              const options = chars[n][3];
              if (options && options.c) {
                options.c = options.c.replace(/\\[0-9A-F]+/ig, (x) => String.fromCodePoint(parseInt(x.substr(1), 16)));
              } else if (options) {
                options.c = String.fromCodePoint(parseInt(n));
              } else {
                chars[n][3] = {c: String.fromCodePoint(parseInt(n))};
              }
            }
          };
          delete TeXFont.defaultStyles['mjx-c::before'];
          
          CHTMLFontData.prototype.addCharStyles = function (styles, vclass, n, data, charUsed) {
            const [h, d, w, options] = data;
            if (this.options.adaptiveCSS && !options.used) return;
            const css = {};
            const selector = 'mjx-c' + this.charSelector(n);
            const root = this.cssRoot;
            css.padding = this.padding(data, 0, options.ic || 0);
            if (options.f !== undefined) {
              css['font-family'] = 'MJXZERO, MJXTEX' + (options.f ? '-' + options.f : '') + '!important';
            }
            const char = (vclass ? vclass + ' ': '') + selector;
            styles[root + char] = css;
            if (options.ic) {
              const [MJX, noIC] = [root + 'mjx-', '[noIC]' + char + ':last-child'];
              styles[MJX + 'mi' + noIC] =
              styles[MJX + 'mo' + noIC] = {
                'padding-right': this.em(w)
              };
            }
          };
          
          CHTMLTextNode.prototype.toCHTML = function (parent) {
            this.markUsed();
            const adaptor = this.adaptor;
            const variant = this.parent.variant;
            const text = this.node.getText();
            if (variant === '-explicitFont') {
              const font = this.jax.getFontData(this.parent.styles);
              adaptor.append(parent, this.jax.unknownText(text, variant, font));
            } else {
              const c = this.parent.stretch.c;
              const chars = this.parent.remapChars(c ? [c] : this.unicodeChars(text));
              for (const n of chars) {
                const data = this.getVariantChar(variant, n)[3];
                const node = (data.unknown ?
                              this.jax.unknownText(String.fromCodePoint(n), variant) :
                              this.html('mjx-c', {class: this.char(n)}, [this.text(data.c)]));
                adaptor.append(parent, node);
                data.used = true;
              }
            }
          };
          CHTMLTextNode.styles['mjx-c'].width = 0;

          MathJax.startup.defaultReady();
        }
      }
    }
    </script>

This should be made into an extension (and would be a nice project for a first-tie contributor).  For now, you could put it into a separate script file that you load before loading MathJax's tex-html.js file.

Note, however, that although the characters are now selectable, the selection background does not cover the complete glyph.  This is due to the way that CommonHTML handles the fonts, and would be difficult to patch by hand like this.  We are planning a font update for this summer, an that may change during the font redesign.

I hope one of these does what you need.  Of course, you will need to merge these into your existing configuration object.

Davide


On Mar 16, 2020, at 2:57 PM, servi...@gmail.com wrote:

Here is my config:

<script>
      window.MathJax = {
        options: {
          renderActions: {
            addMenu: [],
            checkLoading: []
          },
          ignoreHtmlClass: 'tex2jax_ignore',
          processHtmlClass: 'tex2jax_process'
        },
        tex: {
          inlineMath: [['$','$'], ['\\(','\\)']]
        }
      };
      </script>
      <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>

The script converts text between $$ into mjx-container tags that can't be copied to clipboard.
How can I change the config so that either the tags have some fallback or they themselves can be copied to plaintext?

--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/mathjax-users/128a0cf2-2167-40cc-8e98-d67475bc27cf%40googlegroups.com.

dr.j.r....@gmail.com

unread,
Mar 18, 2020, 11:31:35 AM3/18/20
to MathJax Users


On Wednesday, 18 March 2020 13:59:12 UTC, dpvc wrote:
MathJax's output is not intended for direct copying via selection, and it would make sense to do so only for the simplest of expressions (e.g., no fractions, square roots, exponents, and so on).

Just in case anybody has not realised it, MathJax's output can be copied graphically with selection by the Windows Snipping Tool and similar, and pasted into Windows Paint, GMail, etc.  I have just copied one-and-a half lines of equations, and got the expected result.

Thanks for all your efforts,

-- 
  (c) John Stockton, near London, UK.  Using Google Groups.           |
 Mail: J.R.""""""""@physics.org - or as Reply-To, if any.             |

Nico Schlömer

unread,
May 19, 2020, 5:32:05 AM5/19/20
to MathJax Users
I've tried the first part, and unfortunately it's not working for me. It does add an extra element with the LaTeX code to the DOM, e.g.,
```
<mjx-copytext aria-hidden="true">$U$</mjx-copytext>
```
but this isn't copied when selecting it (with the surrounding text) in the browser.
On Mar 16, 2020, at 2:57 PM, serv...@gmail.com wrote:

Here is my config:

<script>
      window.MathJax = {
        options: {
          renderActions: {
            addMenu: [],
            checkLoading: []
          },
          ignoreHtmlClass: 'tex2jax_ignore',
          processHtmlClass: 'tex2jax_process'
        },
        tex: {
          inlineMath: [['$','$'], ['\\(','\\)']]
        }
      };
      </script>
      <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>

The script converts text between $$ into mjx-container tags that can't be copied to clipboard.
How can I change the config so that either the tags have some fallback or they themselves can be copied to plaintext?

--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathja...@googlegroups.com.

Davide Cervone

unread,
May 19, 2020, 10:29:09 AM5/19/20
to mathja...@googlegroups.com
> I've tried the first part, and unfortunately it's not working for me. It does add an extra element with the LaTeX code to the DOM, e.g.,
> ```
> <mjx-copytext aria-hidden="true">$U$</mjx-copytext>
> ```
> but this isn't copied when selecting it (with the surrounding text) in the browser.

What browser and OS are you using?

Also, did you actually paste the result into a text editor to see that it didn't include the text? Note that there will be no visual indication that it is selected, so you actually have to paste it somewhere to test this.

If it really isn't being copied, you might try changing the width and height from 0 to 1px and see if that changes anything.

Davide

Roman L.

unread,
Oct 7, 2021, 5:32:34 PM10/7/21
to MathJax Users
Hi Davide,

In our web app users are able to select text and highlight it, this includes math equations. We are looking to upgrade to MathJax 3.x version, but noticed that version 3.x doesn't allow selecting text.
I see you posted a solution on how to make MathJax text/content selectable sometime ago. I tried your solution, it had some minor issues which I was able to fix, but there is one large issue. It displays all variables (x, y, g, f) as "undefined". This behavior was with version 3.2. 
I would like to find out if there is a way to make math expressions selectable and if MathJax team will implement a solid fix for this issue. I see you have open issue regarding selecting math expressions on GitHub (https://github.com/mathjax/MathJax/issues/2240) and many people were looking for a solution. 
We see a huge performance improvement in MathJax 3.x and would like this issue to be solved in v3.

Davide Cervone

unread,
Oct 8, 2021, 4:47:31 PM10/8/21
to mathja...@googlegroups.com
I see you posted a solution on how to make MathJax text/content selectable sometime ago. I tried your solution, it had some minor issues which I was able to fix, but there is one large issue. It displays all variables (x, y, g, f) as "undefined". This behavior was with version 3.2. 

Sorry, I'm not sure exactly what you mean by this.  Can you be more specific?  The patch may no longer work as it once did (the underlying code may have changed since it was originally posted).

I would like to find out if there is a way to make math expressions selectable and if MathJax team will implement a solid fix for this issue.

There is a pull request that makes the needed changes


that is intended to be part of the next release.  

We see a huge performance improvement in MathJax 3.x and would like this issue to be solved in v3.

I had hoped to have that release out by now, but we had to slow down both for personal reasons on my part, and due to running out of funding for the year.  I'm not sure when the work will be completed for the next release.

While this will make the CHTML output copyable, doing so is not generally a good idea, as anything but the most basic math with not reproduce properly.  Even something as simple as superscripts won't copy and past properly, so encouraging people to do this is just going to lead to trouble, confusion, and frustration in the long run.

Davide


Roman L.

unread,
Oct 11, 2021, 5:33:55 PM10/11/21
to MathJax Users
I see you posted a solution on how to make MathJax text/content selectable sometime ago. I tried your solution, it had some minor issues which I was able to fix, but there is one large issue. It displays all variables (x, y, g, f) as "undefined". This behavior was with version 3.2. 

Sorry, I'm not sure exactly what you mean by this.  Can you be more specific?  The patch may no longer work as it once did (the underlying code may have changed since it was originally posted).
 
Sorry, I was referring to this solution (which is posted in a first comment of this chat):
While this will make the CHTML output copyable, doing so is not generally a good idea, as anything but the most basic math with not reproduce properly.  Even something as simple as superscripts won't copy and past properly, so encouraging people to do this is just going to lead to trouble, confusion, and frustration in the long run.
Our needs to make content selectable is for highlighting functionality, it's not intended for copying math expressions.

Do you have any approximate date when next release might happen?

Davide Cervone

unread,
Oct 23, 2021, 6:49:40 AM10/23/21
to mathja...@googlegroups.com
The code that this replaces has been updated in recent versions of MathJax, and so this older patch i snot longer compatible with the current code base. 

Here is a version of the MathJax configuration that works with v3.2.

    MathJax = {
      startup: {
        ready() {
          const {TeXFont} = MathJax._.output.chtml.fonts.tex_ts;
          const {CHTMLFontData} = MathJax._.output.chtml.FontData;
          const {CHTMLTextNode} = MathJax._.output.chtml.Wrappers.TextNode;

          for (const name of Object.keys(TeXFont.defaultChars)) {
            var chars = TeXFont.defaultChars[name];
            for (const n of Object.keys(chars)) {
              const options = chars[n][3];
              if (options && options.c) {
                options.c = options.c.replace(/\\[0-9A-F]+/ig, (x) => String.fromCodePoint(parseInt(x.substr(1), 16)));
              } else if (options) {
                options.c = String.fromCodePoint(parseInt(n));
              } else {
                chars[n][3] = {c: String.fromCodePoint(parseInt(n))};
              }
            }
          };
          delete TeXFont.defaultStyles['mjx-c::before'];
          
          CHTMLFontData.prototype.addCharStyles = function (styles, vclass, n, data, charUsed) {
            const options = data[3];
            const letter = (options.f !== undefined ? options.f : vletter);
            const selector = 'mjx-c' + this.charSelector(n) + (letter ? '.TEX-' + letter : '');
            styles[selector + '::before'] = {padding: this.padding(data, 0, options.ic || 0)};
          };
          
          CHTMLTextNode.prototype.toCHTML = function (parent) {
            this.markUsed();
            const adaptor = this.adaptor;
            const variant = this.parent.variant;
            const text = this.node.getText();
            if (variant === '-explicitFont') {
              adaptor.append(parent, this.jax.unknownText(text, variant, this.getBBox().w));
            } else {
              let utext = '';
              const chars = this.remappedText(text, variant);
              for (const n of chars) {
                const data = this.getVariantChar(variant, n)[3];
                if (data.unknown) {
                  utext += String.fromCodePoint(n);
                } else {
                  utext = this.addUtext(utext, variant, parent);
                  const font = (data.f ? ' TEX-' + data.f : '');
                  adaptor.append(parent, this.html('mjx-c', {class: this.char(n) + font}, [
                    this.text(data.c || String.fromCodePoint(n))
                  ]));
                  this.font.charUsage.add([variant, n]);
                }
              }
              this.addUtext(utext, variant, parent);
            }
          };
          CHTMLTextNode.prototype.addUtext = function (utext, variant, parent) {
            if (utext) {
              this.adaptor.append(parent, this.jax.unknownText(utext, variant));
            }
            return '';
          };

          MathJax.startup.defaultReady();
        }
      }
    }

The original caveat about the selection background not covering the whole glyph still applies.

Davide


--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages