emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* Update Org to MathJax 3
@ 2022-10-07 13:36 Rudolf Adamkovič
  2022-10-08  7:20 ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-10-07 13:36 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1371 bytes --]

Hello smart people!

With all the talk about Emacs 29, I figured we should update Org Mode to
use MathJax 3, to catch up with the rest of the world.

From the documentation (for MathJax 3.0 released in 2019):

> Version 3.0 of MathJax is a complete rewrite of MathJax from the
> ground up, and its usage and configuration is significantly different
> from that of MathJax version 2.

In practice, MathJax 3.2 renders mathematics faster and better, plus it
significantly improves LaTeX support.  For instance, one can typeset
calculus with the built-in (!) 'physics' package, like in LaTeX.

More information on the recent progress (made in 2019-2021):

https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html

See the attached [working, but WIP] patch.

My question for you:

How do we change the 'org-html-mathjax-options'?

- 'scale' has now the value in [0, 1] and not in [0, 100]
- 'scale' should exist as a number and not string
- 'font' did not make it to MathJax 3 [*]
- 'linebreaks' did not make it to MathJax 3 [*]
- 'autonumber' has the values in lowercase now
- 'autonumber' became 'tags' in MathJax terminology

[*] coming in MathJax 4, currently in alpha

How does Org mode approach these kind of breaking changes?

Rudy


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-ox-html-Use-MathJax-3-instead-of-2.7.patch --]
[-- Type: text/x-patch, Size: 2770 bytes --]

From ca2eaf5bcc9ea01e764f6088e37d74ec2be6e426 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Use MathJax 3 instead of 2.7

* lisp/ox-html.el (org-html-mathjax-options): WIP
* lisp/ox-html.el (org-html-mathjax-template): WIP
---
 lisp/ox-html.el | 51 +++++++++++++++++++++++++------------------------
 1 file changed, 26 insertions(+), 25 deletions(-)

diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 9b77e4f8d..c6242d4bc 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,12 +1166,12 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale "1") ;; TODO: 0-1 AND NOT 0-100; NUMBER AND NOT STRING
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "TeX") ;; TODO: NOT SUPPORTED
+    (linebreaks "false") ;; TODO: NOT SUPPORTED
+    (autonumber "ams") ;; TODO: NOW CALLED TAGS AND IN LOWERCASE
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
@@ -1244,27 +1244,28 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%AUTONUMBER',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script id=\"MathJax-script\" async src=\"%PATH\"></script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
-- 
2.37.3


[-- Attachment #3: Type: text/plain, Size: 300 bytes --]

-- 
"Mathematics takes us still further from what is human into the region
of absolute necessity, to which not only the actual world, but every
possible world, must conform."
-- Bertrand Russell, 1902

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: Update Org to MathJax 3
  2022-10-07 13:36 Update Org to MathJax 3 Rudolf Adamkovič
@ 2022-10-08  7:20 ` Ihor Radchenko
  2022-10-08 21:32   ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-10-08  7:20 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Hello smart people!
>
> With all the talk about Emacs 29, I figured we should update Org Mode to
> use MathJax 3, to catch up with the rest of the world.
>
> From the documentation (for MathJax 3.0 released in 2019):
>
>> Version 3.0 of MathJax is a complete rewrite of MathJax from the
>> ground up, and its usage and configuration is significantly different
>> from that of MathJax version 2.
>
> In practice, MathJax 3.2 renders mathematics faster and better, plus it
> significantly improves LaTeX support.  For instance, one can typeset
> calculus with the built-in (!) 'physics' package, like in LaTeX.
>
> More information on the recent progress (made in 2019-2021):
>
> https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
> https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
> https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html

Thanks a lot for this patch!

> See the attached [working, but WIP] patch.
>
> My question for you:
>
> How do we change the 'org-html-mathjax-options'?
>
> - 'scale' has now the value in [0, 1] and not in [0, 100]
> - 'scale' should exist as a number and not string
> - 'font' did not make it to MathJax 3 [*]
> - 'linebreaks' did not make it to MathJax 3 [*]
> - 'autonumber' has the values in lowercase now
> - 'autonumber' became 'tags' in MathJax terminology
>
> [*] coming in MathJax 4, currently in alpha
>
> How does Org mode approach these kind of breaking changes?

The general rule is "do not break working Org files" or backwards
compatibility in other words. See
https://bzg.fr/en/the-software-maintainers-pledge/

In particular, it means that the existing #+HTML_MATHJAX options should
not be broken when a user uses default value of
org-html-mathjax-options and org-html-mathjax-template.

WRT this patch, we can employ two different approaches:
1. Keep old Mathjax as default, but add MathJax 3 to customization
   options and parse the other options depending on the MathJax version
   in the :path.
2. Change the defaults to MathJax 3. Then, we will also need to make
   sure that incompatible option syntax in historic Org files does not
   break the export.

The option (1) is easy. We just need to choose two different sets of
options in custom interface for org-html-mathjax-options and
org-html-mathjax-template. The in-buffer options will then be parsed
depending on the :path.

The option (2) will make Org export nicer by default, but more tricky to
implement.

In (2), all the aspects of (1) should still be retained. But in
addition, you will also need to detect option values incompatible with
MathJax 3 during export. If there is mismatch between the option format
and the MathJax version, (a) try to convert the options, which should be
possible e.g. for scale and autonumber. (b) if conversion is not
possible (say, the options contain font setting), throw a warning and
fallback to compatible MathJax version.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: Update Org to MathJax 3
  2022-10-08  7:20 ` Ihor Radchenko
@ 2022-10-08 21:32   ` Rudolf Adamkovič
  2022-11-05  0:20     ` [PATCH] " Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-10-08 21:32 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

Ihor Radchenko <yantar92@gmail.com> writes:

> The option (2) will make Org export nicer by default, but more tricky
> to implement.

Say no more.  Rolling with the option (2).  :)

> In (2), all the aspects of (1) should still be retained. But in
> addition, you will also need to detect option values incompatible with
> MathJax 3 during export. If there is mismatch between the option
> format and the MathJax version, (a) try to convert the options, which
> should be possible e.g. for scale and autonumber. (b) if conversion is
> not possible (say, the options contain font setting), throw a warning
> and fallback to compatible MathJax version.

Perfect.  All clear!

I will resurrect the thread when I have something to show.

Rudy
-- 
"'Contrariwise,' continued Tweedledee, 'if it was so, it might be; and
if it were so, it would be; but as it isn't, it ain't.  That's logic.'"
-- Lewis Carroll, Through the Looking Glass, 1871/1872

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH] Re: Update Org to MathJax 3
  2022-10-08 21:32   ` Rudolf Adamkovič
@ 2022-11-05  0:20     ` Rudolf Adamkovič
  2022-11-05 12:01       ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-05  0:20 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 203 bytes --]

Rudolf Adamkovič <salutis@me.com> writes:

> I will resurrect the thread when I have something to show.

All right, I have finished the second version of the patch.

What do you think?

Rudy


[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 47113 bytes --]

From aee823f34498cccd6c08bcedb177c82d5de40269 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update to MathJax 3 while maintaining compatibility with MathJax 2.
All legacy options get converted automatically, except for the 'path'
option which must now point to MathJax 3 or later and not MathJax 2.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax.

Reported-by: Rudolf Adamkovič <salutis@me.com>
Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 etc/ORG-NEWS                 |  25 ++
 lisp/ox-html.el              | 247 +++++++++---
 testing/lisp/test-ox-html.el | 762 +++++++++++++++++++++++++++++++++++
 3 files changed, 968 insertions(+), 66 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..7ad17bd70 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,31 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
+in 2019.  The new version brings modularity, better and faster
+rendering, improved LaTeX support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+For instance, one can typeset calculus with the built-in =physics=
+package or chemistry with the built-in =mhchem= package, like in
+LaTeX.
+
+During the export, Org automatically converts all legacy MathJax 2
+options to the corresponding MathJax 3+ options, except for the =path=
+which must now point to a file containing MathJax version 3 or later.
+
+Further, if you need to use a non-default =font= or =linebreaks= (now
+=overflow=), then the =path= must point to MathJax 4 or later.
+
+See the updated =org-html-mathjax-options= for more details.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..9436623e0 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1948,18 +2003,78 @@ INFO is a plist used as a communication channel."
 	     (org-element-map (plist-get info :parse-tree)
 		 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
+	  (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     (append
+                      (list (list (if (plist-member options 'autonumber)
+                                      'tags 'autonumber)
+                                  nil)
+                            (list (if (plist-member options 'linebreaks)
+                                      'overflow 'linebreaks)
+                                  nil))
+                      options)))
 	  (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+	(let ((symbol (car e))
+	      (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              ('font
+               (when-let
+                   ((new-value (cond
+                                ((string= value "TeX")
+                                 "mathjax-tex")
+                                ((string= value "STIX-Web")
+                                 "mathjax-stix2")
+                                ((string= value "Asana-Math")
+                                 "mathjax-asana")
+                                ((string= value "Neo-Euler")
+                                 "mathjax-euler")
+                                ((string= value "Gyre-Pagella")
+                                 "mathjax-pagella")
+                                ((string= value "Gyre-Termes")
+                                 "mathjax-termes")
+                                ((string= value "Latin-Modern")
+                                 "mathjax-modern")
+                                nil)))
+                 (setq value new-value)))
+              ('linebreaks
+               (message "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              ('scale
+               (when (stringp value)
+                 (setq value (string-to-number value)))
+               (when (>= value 10)
+                 (let ((new-value (/ (float value) 100)))
+                   (message "Converting legacy MathJax scale: %s to %s"
+                            value
+                            new-value)
+                   (setq value new-value))))
+              ('autonumber
+               (message "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags)
+               (setq value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..65d987dd7
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,762 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (string= "Converting legacy MathJax scale: 20 to 0.2"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options
+                     (cons '(scale "20") org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (string= "Converting legacy MathJax scale: 20 to 0.2"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (string= "Converting legacy MathJax option: autonumber"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options
+                     (cons '(autonumber "AMS") org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (string= "Converting legacy MathJax option: autonumber"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (string= "Converting legacy MathJax option: linebreaks"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options (cons '(linebreaks "true")
+                                                    org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (string= "Converting legacy MathJax option: linebreaks"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 257 bytes --]

-- 
"Logic is a science of the necessary laws of thought, without which no
employment of the understanding and the reason takes place."
-- Immanuel Kant, 1785

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-05  0:20     ` [PATCH] " Rudolf Adamkovič
@ 2022-11-05 12:01       ` Ihor Radchenko
  2022-11-05 16:56         ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-05 12:01 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Rudolf Adamkovič <salutis@me.com> writes:
>
>> I will resurrect the thread when I have something to show.
>
> All right, I have finished the second version of the patch.

Thanks!

> What do you think?

Your tests are failing on my side...


Also,

In org-html--build-mathjax-config:
ox-html.el:1999:40: Warning: malformed cond form: ‘nil’

I'll wait for a new version of the patch with tests passing before I do
a more elaborate review.

Some quick comments below.

> ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax.

You forgot changelog entries for ORG-NEWS.

Also, you did not update the relevant manual sections.

> Reported-by: Rudolf Adamkovič <salutis@me.com>

There is no need to add Reported-by here. It is not a bug.

> +*** HTML export uses MathJax 3+ instead of MathJax 2
> +
> +Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
> +in 2019.  The new version brings modularity, better and faster
> +rendering, improved LaTeX support, and more.
> +
> +For more information about new features, see:
> +
> +https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
> +https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
> +https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html

This paragraph would look better at the end, after you discuss specific
features.

> +
> +See the updated =org-html-mathjax-options= for more details.

Please use code markup for symbols:  ~org-html-mathjax-options~.

>  (defcustom org-html-mathjax-options
> -  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
> -    (scale "100")
> +  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
> +    (scale 1.0)

It will be worth mentioning that we change the JS provider.
People at least need to take note about different privacy policy.

> +          (when value
> +            (pcase symbol
> +              ('font

It is recommended to use backquote `font in favor of direct quote 'font.

> +               (when-let
> +                   ((new-value (cond
> +                                ((string= value "TeX")

> +                                 "mathjax-tex")
> +                                ((string= value "STIX-Web")
> +                                 "mathjax-stix2")
> +                                ((string= value "Asana-Math")
> +                                 "mathjax-asana")
> +                                ((string= value "Neo-Euler")
> +                                 "mathjax-euler")
> +                                ((string= value "Gyre-Pagella")
> +                                 "mathjax-pagella")
> +                                ((string= value "Gyre-Termes")
> +                                 "mathjax-termes")
> +                                ((string= value "Latin-Modern")
> +                                 "mathjax-modern")
> +                                nil)))

Why not pcase?


> +                 (setq value new-value)))
> +              ('linebreaks
> +               (message "Converting legacy MathJax option: linebreaks")

Maybe warning?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-05 12:01       ` Ihor Radchenko
@ 2022-11-05 16:56         ` Rudolf Adamkovič
  2022-11-05 22:44           ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-05 16:56 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

Thank you for taking a look, Ihor!

> Your tests are failing on my side...

Oh, I see!  The tests *pass* when ran in-buffer but *fail* when ran via
'make test'.  Just the tests that call (current-message) have this
problem.  Interesting.

> ...

Good points; I will fix everything.

Rudy
-- 
"'Contrariwise,' continued Tweedledee, 'if it was so, it might be; and
if it were so, it would be; but as it isn't, it ain't.  That's logic.'"
-- Lewis Carroll, Through the Looking Glass, 1871/1872

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-05 16:56         ` Rudolf Adamkovič
@ 2022-11-05 22:44           ` Rudolf Adamkovič
  2022-11-06  3:52             ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-05 22:44 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Good points; I will fix everything.

Please see the attached (3rd) revision of the patch.

However, the tests still fail when ran via `make test'.  See below.

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> Your tests are failing on my side...
>
> The tests *pass* when ran in-buffer but *fail* when ran via 'make
> test'.  Just the tests that call (current-message) have this problem.
> Interesting.

I cannot figure out why these tests pass when ran from inside of Emacs
but fail when ran via 'make tests'.  I found in `default.mk' that Make
runs `ert-run-tests-batch', but even that *passes* from within Emacs:

1. load `testing/org-test.el',

2. evaluate `ox-html/mathjax-legacy-scale-message-in-buffer` test in
`testing/lisp/test-ox-html.el', and

3. run `M-: ert-run-tests-batch'.

It produces the following output in the `*Messages*' buffer:

  Running 1 tests (2022-11-05 23:17:56+0100, selector ‘t’)
  Converting legacy MathJax scale: 20 to 0.2
     passed  1/1  ox-html/mathjax-legacy-scale-message-in-buffer (0.008186 sec)
  
  Ran 1 tests, 1 results as expected, 0 unexpected (2022-11-05 23:17:56+0100, 0.010790 sec)

It seems that some part of the *batch* testing swallows the messages in
a way that makes `(current-message)' work incorrectly in tests.

Any ideas?  Asking for an Emacs veteran.  :)

P.S. As a workaround I also tried to set `set-message-function',
dependency-injection style, but it did not work either.

Rudy
-- 
"'Contrariwise,' continued Tweedledee, 'if it was so, it might be; and
if it were so, it would be; but as it isn't, it ain't.  That's logic.'"
-- Lewis Carroll, Through the Looking Glass, 1871/1872

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-05 22:44           ` Rudolf Adamkovič
@ 2022-11-06  3:52             ` Ihor Radchenko
  2022-11-06 23:49               ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-06  3:52 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Rudolf Adamkovič <salutis@me.com> writes:
>
>> Good points; I will fix everything.
>
> Please see the attached (3rd) revision of the patch.

Nothing is attached.
If you are using Emacs to send email, I suggest you to enable checks for
attachments, as I do in notmuch
(add-hook 'notmuch-mua-send-hook #'notmuch-mua-attachment-check)

I am sure that similar functionality exists in other Emacs mail clients.

> It seems that some part of the *batch* testing swallows the messages in
> a way that makes `(current-message)' work incorrectly in tests.
>
> Any ideas?  Asking for an Emacs veteran.  :)

AFAIK, `current-message' always returns nil in noninteractive batch
mode. Messages are written directly into stderr. You need to use a
different approach for testing.

> P.S. As a workaround I also tried to set `set-message-function',
> dependency-injection style, but it did not work either.

I do not think that this function is ever executed in noninteractive
mode either.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-06  3:52             ` Ihor Radchenko
@ 2022-11-06 23:49               ` Rudolf Adamkovič
  2022-11-07  3:02                 ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-06 23:49 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 831 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> Nothing is attached.

I apologize.  Please see the attached patch.

> If you are using Emacs to send email, I suggest you to enable checks
> for attachments, as I do in notmuch [...]

TIL and thank you!  Added to my configuration.

> AFAIK, `current-message' always returns nil in noninteractive batch
> mode. Messages are written directly into stderr. You need to use a
> different approach for testing.
>
>> P.S. As a workaround I also tried to set `set-message-function',
>> dependency-injection style, but it did not work either.
>
> I do not think that this function is ever executed in noninteractive
> mode either.

Bummer!  Do you have any suggestions in mind?  Perhaps we could define
an advice for '(current-message)', if that ever runs?  I would welcome
any ideas.

Rudy


[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 48925 bytes --]

From 7bdc7cbd7e6f5c06773138657325502adc2fe999 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  11 +-
 etc/ORG-NEWS                 |  32 ++
 lisp/ox-html.el              | 240 ++++++++---
 testing/lisp/test-ox-html.el | 762 +++++++++++++++++++++++++++++++++++
 4 files changed, 971 insertions(+), 74 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..7500202c5 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13229,23 +13229,18 @@ LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
 with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
+[[https://www.jsdelivr.com/][jsDelivr]] as recommended by
 [[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
 options can be configured via ~org-html-mathjax-options~, or in the
 buffer.  For example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..f063c80d2 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,38 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ in
+~org-html-mathjax-options~ which must now point to a file containing
+MathJax version 3 or later.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
+in 2019.  The new version brings modularity, better and faster
+rendering, improved LaTeX support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+For instance, one can typeset calculus with the built-in ~physics~
+package or chemistry with the built-in ~mhchem~ package, like in
+LaTeX.
+
+Please note that MathJax 3 changed the default JavaScript content
+delivery network (CDN) provider from CloudFlare to jsDelivr.  You can
+find the new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..9c079b08d 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1948,18 +2003,71 @@ INFO is a plist used as a communication channel."
 	     (org-element-map (plist-get info :parse-tree)
 		 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
+	  (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     (append
+                      (list (list (if (plist-member options 'autonumber)
+                                      'tags 'autonumber)
+                                  nil)
+                            (list (if (plist-member options 'linebreaks)
+                                      'overflow 'linebreaks)
+                                  nil))
+                      options)))
 	  (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+	(let ((symbol (car e))
+	      (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((new-value
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value new-value)))
+              (`linebreaks
+               (message "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (setq value (string-to-number value)))
+               (when (>= value 10)
+                 (let ((new-value (/ (float value) 100)))
+                   (message "Converting legacy MathJax scale: %s to %s"
+                            value
+                            new-value)
+                   (setq value new-value))))
+              (`autonumber
+               (message "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags)
+               (setq value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..65d987dd7
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,762 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (string= "Converting legacy MathJax scale: 20 to 0.2"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options
+                     (cons '(scale "20") org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (string= "Converting legacy MathJax scale: 20 to 0.2"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (string= "Converting legacy MathJax option: autonumber"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options
+                     (cons '(autonumber "AMS") org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (string= "Converting legacy MathJax option: autonumber"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (string= "Converting legacy MathJax option: linebreaks"
+            (org-test-with-temp-text "$x$"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil)
+                    (org-html-mathjax-options (cons '(linebreaks "true")
+                                                    org-html-mathjax-options)))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (string= "Converting legacy MathJax option: linebreaks"
+            (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+              (let ((export-buffer "*Test HTML Export*")
+                    (org-export-show-temporary-export-buffer nil))
+                (org-export-to-buffer 'html export-buffer
+                  nil nil nil nil nil
+                  #'html-mode)
+                (current-message))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 329 bytes --]

-- 
"Programming reliably -- must be an activity of an undeniably
mathematical nature […] You see, mathematics is about thinking, and
doing mathematics is always trying to think as well as possible."
-- Edsger W. Dijkstra, 1981

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-06 23:49               ` Rudolf Adamkovič
@ 2022-11-07  3:02                 ` Ihor Radchenko
  2022-11-07 20:56                   ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-07  3:02 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Bummer!  Do you have any suggestions in mind?  Perhaps we could define
> an advice for '(current-message)', if that ever runs?  I would welcome
> any ideas.

Using `current-message' would not be reliable even if it worked. It is
not future-compatible if we decide to emit new messages during export.

The proper test is not presence of the message, but the correct
conversion of the legacy options in the actual html output.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-07  3:02                 ` Ihor Radchenko
@ 2022-11-07 20:56                   ` Rudolf Adamkovič
  2022-11-08  5:20                     ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-07 20:56 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

> The proper test is not presence of the message, but the correct
> conversion of the legacy options in the actual html output.

We do test the results in the HTML output, of course.  Still, I would
like to test the user messages too.  By my definition, "proper" tests
verify all requirements.  Hence, I ask: Do we want to reliably notify
the user?  If we do not, we can remove the messages.  If we do, then we
either test the messages or they will break at some random point in time
and generate yet another pointless mail thread.

If we do not have any better idea, then I can define a variable that
points to the 'message' function by default.  The relevant tests can
then set the variable to a function that gathers messages in a list.
With that, the tests can verify that the list contains exactly one
instance of the expected message.

Rudy
-- 
"It is no paradox to say that in our most theoretical moods we may be
nearest to our most practical applications."
-- Alfred North Whitehead, 1861-1947

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-07 20:56                   ` Rudolf Adamkovič
@ 2022-11-08  5:20                     ` Ihor Radchenko
  2022-11-08 23:37                       ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-08  5:20 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> The proper test is not presence of the message, but the correct
>> conversion of the legacy options in the actual html output.
>
> We do test the results in the HTML output, of course.  Still, I would
> like to test the user messages too.

Then, you can `cl-letf'-bind the message function in a new macro added
to org-test.el. The binding will accumulate messages in some list. Then,
use that macro to test the messages.

Also, I think that conversion message may worth a warning.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-08  5:20                     ` Ihor Radchenko
@ 2022-11-08 23:37                       ` Rudolf Adamkovič
  2022-11-09  0:05                         ` Rudolf Adamkovič
  2022-11-09  2:49                         ` Ihor Radchenko
  0 siblings, 2 replies; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-08 23:37 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> Then, you can `cl-letf'-bind the message function in a new macro added
> to org-test.el. The binding will accumulate messages in some
> list. Then, use that macro to test the messages.

What a great piece of advice.  Thank you, Ihor!

See the 4th version of the patch attached below.

> Also, I think that conversion message may worth a warning.

You have mentioned this twice, but you have never said why.  I remain
open, but I need know the reason.  To give you mine:

We need to inform the user, not necessarily warn them.  They can keep
using the older options as long as they want, and I engineered the patch
specifically so that everything will work going forward, without the
need for the user to spin the "update now" hamster wheel.

However, if you have a sound, technical reason for issuing warnings to
the user, I will make it happen.  Let me know!

Rudy

[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 50410 bytes --]

From 8d23b1f6f8a964f2f27499147ee977344d8719a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  11 +-
 etc/ORG-NEWS                 |  32 ++
 lisp/ox-html.el              | 240 ++++++++---
 testing/lisp/test-ox-html.el | 785 +++++++++++++++++++++++++++++++++++
 testing/org-test.el          |  16 +
 5 files changed, 1010 insertions(+), 74 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..7500202c5 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13229,23 +13229,18 @@ LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
 with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
+[[https://www.jsdelivr.com/][jsDelivr]] as recommended by
 [[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
 options can be configured via ~org-html-mathjax-options~, or in the
 buffer.  For example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..f063c80d2 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,38 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ in
+~org-html-mathjax-options~ which must now point to a file containing
+MathJax version 3 or later.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
+in 2019.  The new version brings modularity, better and faster
+rendering, improved LaTeX support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+For instance, one can typeset calculus with the built-in ~physics~
+package or chemistry with the built-in ~mhchem~ package, like in
+LaTeX.
+
+Please note that MathJax 3 changed the default JavaScript content
+delivery network (CDN) provider from CloudFlare to jsDelivr.  You can
+find the new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..9c079b08d 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1948,18 +2003,71 @@ INFO is a plist used as a communication channel."
 	     (org-element-map (plist-get info :parse-tree)
 		 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
+	  (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     (append
+                      (list (list (if (plist-member options 'autonumber)
+                                      'tags 'autonumber)
+                                  nil)
+                            (list (if (plist-member options 'linebreaks)
+                                      'overflow 'linebreaks)
+                                  nil))
+                      options)))
 	  (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+	(let ((symbol (car e))
+	      (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((new-value
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value new-value)))
+              (`linebreaks
+               (message "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (setq value (string-to-number value)))
+               (when (>= value 10)
+                 (let ((new-value (/ (float value) 100)))
+                   (message "Converting legacy MathJax scale: %s to %s"
+                            value
+                            new-value)
+                   (setq value new-value))))
+              (`autonumber
+               (message "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags)
+               (setq value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..93323189e
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,785 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax scale: 20 to 0.2"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "20") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (seq-count
+    (lambda (message)
+      (string= "Converting legacy MathJax scale: 20 to 0.2"
+               message))
+    (org-test-capture-messages
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(autonumber "AMS") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options (cons '(linebreaks "true")
+                                                 org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/org-test.el b/testing/org-test.el
index 9f7bab9ea..12f0681bd 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -531,6 +531,22 @@ TIME can be a non-nil Lisp time value, or a string specifying a date and time."
 				    (or a ,at) (or b ,at)))))
 	 ,@body))))
 
+(defmacro org-test-capture-messages (&rest body)
+  "Return the list of all messages put in the echo area in the BODY."
+  ;; TODO Despite its name, this macro has nothing to do with Org and
+  ;; belongs to a more general package in Emacs.
+  (declare (indent 0) (debug t))
+  `(let ((messages (list)))
+     (cl-letf (((symbol-function 'message)
+                (lambda (format-string &rest args)
+                  (setq messages
+                        (cons (apply #'format
+                                     (cons format-string
+                                           args))
+                              messages)))))
+       ,@body)
+     (reverse messages)))
+
 (provide 'org-test)
 
 ;;; org-test.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 178 bytes --]

-- 
"Genius is 1% inspiration and 99% perspiration."
-- Thomas Alva Edison, 1932

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-08 23:37                       ` Rudolf Adamkovič
@ 2022-11-09  0:05                         ` Rudolf Adamkovič
  2022-11-09  5:40                           ` Ihor Radchenko
  2022-11-09  2:49                         ` Ihor Radchenko
  1 sibling, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-09  0:05 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 375 bytes --]

Rudolf Adamkovič <salutis@me.com> writes:

> See the 4th version of the patch attached below.

And, of course, I forgot to update the commit message with the new
macro.  Please see the 5th version of the patch attached below.

P.S. I also fixed a preposition in the documentation comment of the new
macro, now saying "in the BODY" instead of "by the BODY".

Rudy

[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 50533 bytes --]

From 88b9ee017b89bb1a474786873f3d951c4b730cf3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* testing/org-test.el (org-test-capture-messages): Add a new macro
useful for testing the messages put in the echo area.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  11 +-
 etc/ORG-NEWS                 |  32 ++
 lisp/ox-html.el              | 240 ++++++++---
 testing/lisp/test-ox-html.el | 785 +++++++++++++++++++++++++++++++++++
 testing/org-test.el          |  16 +
 5 files changed, 1010 insertions(+), 74 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..7500202c5 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13229,23 +13229,18 @@ LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
 with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
+[[https://www.jsdelivr.com/][jsDelivr]] as recommended by
 [[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
 options can be configured via ~org-html-mathjax-options~, or in the
 buffer.  For example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..f063c80d2 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,38 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ in
+~org-html-mathjax-options~ which must now point to a file containing
+MathJax version 3 or later.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
+in 2019.  The new version brings modularity, better and faster
+rendering, improved LaTeX support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+For instance, one can typeset calculus with the built-in ~physics~
+package or chemistry with the built-in ~mhchem~ package, like in
+LaTeX.
+
+Please note that MathJax 3 changed the default JavaScript content
+delivery network (CDN) provider from CloudFlare to jsDelivr.  You can
+find the new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..9c079b08d 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1948,18 +2003,71 @@ INFO is a plist used as a communication channel."
 	     (org-element-map (plist-get info :parse-tree)
 		 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
+	  (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     (append
+                      (list (list (if (plist-member options 'autonumber)
+                                      'tags 'autonumber)
+                                  nil)
+                            (list (if (plist-member options 'linebreaks)
+                                      'overflow 'linebreaks)
+                                  nil))
+                      options)))
 	  (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+	(let ((symbol (car e))
+	      (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((new-value
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value new-value)))
+              (`linebreaks
+               (message "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (setq value (string-to-number value)))
+               (when (>= value 10)
+                 (let ((new-value (/ (float value) 100)))
+                   (message "Converting legacy MathJax scale: %s to %s"
+                            value
+                            new-value)
+                   (setq value new-value))))
+              (`autonumber
+               (message "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags)
+               (setq value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..93323189e
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,785 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax scale: 20 to 0.2"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "20") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (seq-count
+    (lambda (message)
+      (string= "Converting legacy MathJax scale: 20 to 0.2"
+               message))
+    (org-test-capture-messages
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(autonumber "AMS") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options (cons '(linebreaks "true")
+                                                 org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-messages
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/org-test.el b/testing/org-test.el
index 9f7bab9ea..652fcc4d3 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -531,6 +531,22 @@ TIME can be a non-nil Lisp time value, or a string specifying a date and time."
 				    (or a ,at) (or b ,at)))))
 	 ,@body))))
 
+(defmacro org-test-capture-messages (&rest body)
+  "Return the list of all messages put in the echo area by the BODY."
+  ;; TODO Despite its name, this macro has nothing to do with Org and
+  ;; belongs to a more general package in Emacs.
+  (declare (indent 0) (debug t))
+  `(let ((messages (list)))
+     (cl-letf (((symbol-function 'message)
+                (lambda (format-string &rest args)
+                  (setq messages
+                        (cons (apply #'format
+                                     (cons format-string
+                                           args))
+                              messages)))))
+       ,@body)
+     (reverse messages)))
+
 (provide 'org-test)
 
 ;;; org-test.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 239 bytes --]

-- 
"The introduction of suitable abstractions is our only mental aid to
organize and master complexity."
-- Edsger Wybe Dijkstra, 1930-2002

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-08 23:37                       ` Rudolf Adamkovič
  2022-11-09  0:05                         ` Rudolf Adamkovič
@ 2022-11-09  2:49                         ` Ihor Radchenko
  1 sibling, 0 replies; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-09  2:49 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

>> Also, I think that conversion message may worth a warning.
>
> You have mentioned this twice, but you have never said why.  I remain
> open, but I need know the reason.  To give you mine:
>
> We need to inform the user, not necessarily warn them.  They can keep
> using the older options as long as they want, and I engineered the patch
> specifically so that everything will work going forward, without the
> need for the user to spin the "update now" hamster wheel.
>
> However, if you have a sound, technical reason for issuing warnings to
> the user, I will make it happen.  Let me know!

Mostly because supporting MathJax 2 is an extra maintenance burden.
I do not see much point catering bugs of MathJax2 -> MathJax3
conversion. If users want to use MathJax2 specifically, they should be
able to customize Org to use MathJax2. If they want MathJax3, they need
to fix the options. Relying on auto-conversion is calling for trouble.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-09  0:05                         ` Rudolf Adamkovič
@ 2022-11-09  5:40                           ` Ihor Radchenko
  2022-11-09 23:01                             ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-09  5:40 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Rudolf Adamkovič <salutis@me.com> writes:
>
>> See the 4th version of the patch attached below.
>
> And, of course, I forgot to update the commit message with the new
> macro.  Please see the 5th version of the patch attached below.

Thanks!

>  with Org[fn:: By default Org loads MathJax from
> -[[https://cdnjs.com][cdnjs.com]] as recommended by
> +[[https://www.jsdelivr.com/][jsDelivr]] as recommended by
>  [[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display

Is it really recommended by mathjax? I cannot find any mention of
jsdelivr.com. You may update the URL to something more precise.

>  #+texinfo: @noindent
>  equation labels are displayed on the left margin and equations are
> -five em from the left margin.  In addition, it loads the two MathJax
> -extensions =cancel.js= and =noErrors.js=[fn:: See
> -[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
> -and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
> -manual]] to learn about extensions.].
> +five em from the left margin.

Are these extensions no longer supported?
  
> -	  (options (plist-get info :html-mathjax-options))
> +	  (options (let ((options (plist-get info :html-mathjax-options)))
> +                     ;; If the user customized some legacy option, set
> +                     ;; the corresponding new option to nil, so that
> +                     ;; the legacy user choice overrides the default.
> +                     ;; Otherwise, the user did not set the legacy
> +                     ;; option, in which case still set the legacy
> +                     ;; option but to no value, so that the code can
> +                     ;; find its in-buffer value, if set.
> +                     (append
> +                      (list (list (if (plist-member options 'autonumber)
> +                                      'tags 'autonumber)
> +                                  nil)
> +                            (list (if (plist-member options 'linebreaks)
> +                                      'overflow 'linebreaks)
> +                                  nil))
> +                      options)))

It would be more compact to use `(...) notation.
 
> +(defmacro org-test-capture-messages (&rest body)
> +  "Return the list of all messages put in the echo area by the BODY."
> +  ;; TODO Despite its name, this macro has nothing to do with Org and
> +  ;; belongs to a more general package in Emacs.
> +  (declare (indent 0) (debug t))
> +  `(let ((messages (list)))
> +     (cl-letf (((symbol-function 'message)
> +                (lambda (format-string &rest args)
> +                  (setq messages
> +                        (cons (apply #'format
> +                                     (cons format-string
> +                                           args))
> +                              messages)))))
> +       ,@body)
> +     (reverse messages)))

nreverse will be more memory-efficient.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-09  5:40                           ` Ihor Radchenko
@ 2022-11-09 23:01                             ` Rudolf Adamkovič
  2022-11-10  2:34                               ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-09 23:01 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 2711 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> Is it really recommended by mathjax?

Yes.

> I cannot find any mention of jsdelivr.com.

1. Open MathJax docs (https://docs.mathjax.org/en/latest/).
2. Type "jsDelivr" into the search box.
3. Check the search results.

> You may update the URL to something more precise.

Fixed.

> Are these extensions no longer supported?

MathJax 3+ supports extensions, and one can load them in multiple ways.
See [1].  It deserves some thinking, for the extensions mirror LaTeX
extensions.  So, to make both HTML and PDF work, one has:

  Use the Physics package in both LaTeX and MathJax.

  #+latex_header: \usepackage{physics}
  
  #+html_head_extra: <div style="display: none">
  #+html_head_extra:   \(
  #+html_head_extra:     \require{physics}
  #+html_head_extra:   \)
  #+html_head_extra: </div>

(The example taken from my Org notebook.)

[1]: https://docs.mathjax.org/en/latest/input/tex/extensions.html

That said, I do not currently have bandwidth to extend the scope of the
patch to include MathJax extensions.  (Note that they have never worked
anyway, so the user loses nothing, and we correct documentation.)

A slight digression, just FYI:

I realized that I should have improved LaTeX to SVG exports instead of
focusing on MathJax.  Org supports LaTeX environments, but outside of
PDFs, it does so poorly.  Packages such as TikZ, for example, do not
work out of the box, so people use Babel to hack around it.  Worse
still, Org does not even properly adjust baselines for inline math, so
HTML exports remain barely usable for any mathematical work.

If we fix LaTeX, Org could have fast and good mathematics, with no
JavaScript, like Wikipedia has.  Then, we could use it by default,
instead of MathJax.

Having good support for LaTeX would position Org as the king of markup
editors, because LaTeX can do everything under the sun.  For instance,
one can typeset a chess game in a couple of lines.  Or sheet music.  Or
molecules in three dimensions.  No mainstream Markdown editor or web
authoring tool can do that.  Yet, Org can *almost* do it.

> It would be more compact to use `(...) notation.

Fixed.

> nreverse will be more memory-efficient.

Fixed and TIL.

> Mostly because supporting MathJax 2 is an extra maintenance burden.  I
> do not see much point catering bugs of MathJax2 -> MathJax3
> conversion. If users want to use MathJax2 specifically, they should be
> able to customize Org to use MathJax2. If they want MathJax3, they
> need to fix the options. Relying on auto-conversion is calling for
> trouble.

Fixed.

Please see the 6th version of the patch, attached below.

P.S. I also replaced two subsequent `setq' calls with one.

Rudy

[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 50720 bytes --]

From 5009350869879cce6f083a9b38961b95f1c51f58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* testing/org-test.el (org-test-capture-messages): Add a new macro
useful for testing the messages put in the echo area.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  20 +-
 etc/ORG-NEWS                 |  32 ++
 lisp/ox-html.el              | 242 ++++++++---
 testing/lisp/test-ox-html.el | 785 +++++++++++++++++++++++++++++++++++
 testing/org-test.el          |  11 +
 5 files changed, 1012 insertions(+), 78 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..3514f84e4 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13228,24 +13228,20 @@ as-is.
 LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
-with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
-[[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
-options can be configured via ~org-html-mathjax-options~, or in the
-buffer.  For example, with the following settings,
+with Org[fn:: By default, Org loads MathJax from
+[[https://www.jsdelivr.com/][jsDelivr]], as recommended in
+[[https://docs.mathjax.org/en/latest/web/start.html][Getting Started
+with MathJax Components]].][fn:46].  Some MathJax display options can
+be configured via ~org-html-mathjax-options~, or in the buffer.  For
+example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..f063c80d2 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,38 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ in
+~org-html-mathjax-options~ which must now point to a file containing
+MathJax version 3 or later.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+Org 9.6 uses MathJax 3, a ground-up rewrite of MathJax 2 released
+in 2019.  The new version brings modularity, better and faster
+rendering, improved LaTeX support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+For instance, one can typeset calculus with the built-in ~physics~
+package or chemistry with the built-in ~mhchem~ package, like in
+LaTeX.
+
+Please note that MathJax 3 changed the default JavaScript content
+delivery network (CDN) provider from CloudFlare to jsDelivr.  You can
+find the new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..09a57bf11 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1948,18 +2003,73 @@ INFO is a plist used as a communication channel."
 	     (org-element-map (plist-get info :parse-tree)
 		 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
+	  (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     (append `((,(if (plist-member options 'autonumber)
+                                     'tags 'autonumber)
+                                nil)
+                               (,(if (plist-member options 'linebreaks)
+                                     'overflow 'linebreaks)
+                                nil))
+                             options)))
 	  (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+	(let ((symbol (car e))
+	      (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((new-value
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value new-value)))
+              (`linebreaks
+               (org-display-warning
+                "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (setq value (string-to-number value)))
+               (when (>= value 10)
+                 (let ((new-value (/ (float value) 100)))
+                   (org-display-warning
+                    (format "Converting legacy MathJax scale: %s to %s"
+                            value
+                            new-value))
+                   (setq value new-value))))
+              (`autonumber
+               (org-display-warning
+                "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags
+                     value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..7cf8b3afb
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,785 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax scale: 20 to 0.2"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "20") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (seq-count
+    (lambda (message)
+      (string= "Converting legacy MathJax scale: 20 to 0.2"
+               message))
+    (org-test-capture-warnings
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(autonumber "AMS") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options (cons '(linebreaks "true")
+                                                 org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/org-test.el b/testing/org-test.el
index 9f7bab9ea..880d9e845 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -33,6 +33,7 @@
 
 (require 'org)
 (require 'org-id)
+(require 'org-macs)
 
 ;;; Ob constants
 
@@ -531,6 +532,16 @@ TIME can be a non-nil Lisp time value, or a string specifying a date and time."
 				    (or a ,at) (or b ,at)))))
 	 ,@body))))
 
+(defmacro org-test-capture-warnings (&rest body)
+  "Capture all warnings passed to `org-display-warning' within BODY."
+  (declare (indent 0) (debug t))
+  `(let ((messages (list)))
+     (cl-letf (((symbol-function 'org-display-warning)
+                (lambda (message)
+                  (setq messages (cons message messages)))))
+       ,@body)
+     (nreverse messages)))
+
 (provide 'org-test)
 
 ;;; org-test.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 257 bytes --]

-- 
"Logic is a science of the necessary laws of thought, without which no
employment of the understanding and the reason takes place."
-- Immanuel Kant, 1785

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-09 23:01                             ` Rudolf Adamkovič
@ 2022-11-10  2:34                               ` Ihor Radchenko
  2022-11-11 12:50                                 ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-10  2:34 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

>> Are these extensions no longer supported?
>
> MathJax 3+ supports extensions, and one can load them in multiple ways.
> See [1].  It deserves some thinking, for the extensions mirror LaTeX
> extensions.  So, to make both HTML and PDF work, one has:
>
>   Use the Physics package in both LaTeX and MathJax.
>
>   #+latex_header: \usepackage{physics}
>   
>   #+html_head_extra: <div style="display: none">
>   #+html_head_extra:   \(
>   #+html_head_extra:     \require{physics}
>   #+html_head_extra:   \)
>   #+html_head_extra: </div>
>
> (The example taken from my Org notebook.)
>
> [1]: https://docs.mathjax.org/en/latest/input/tex/extensions.html
>
> That said, I do not currently have bandwidth to extend the scope of the
> patch to include MathJax extensions.  (Note that they have never worked
> anyway, so the user loses nothing, and we correct documentation.)

Then, please mention this in the NEWS. Something like "we drop extension
support as it never worked anyway".

> A slight digression, just FYI:
>
> I realized that I should have improved LaTeX to SVG exports instead of
> focusing on MathJax.  Org supports LaTeX environments, but outside of
> PDFs, it does so poorly.  Packages such as TikZ, for example, do not
> work out of the box, so people use Babel to hack around it.  Worse
> still, Org does not even properly adjust baselines for inline math, so
> HTML exports remain barely usable for any mathematical work.
>
> If we fix LaTeX, Org could have fast and good mathematics, with no
> JavaScript, like Wikipedia has.  Then, we could use it by default,
> instead of MathJax.
>
> Having good support for LaTeX would position Org as the king of markup
> editors, because LaTeX can do everything under the sun.  For instance,
> one can typeset a chess game in a couple of lines.  Or sheet music.  Or
> molecules in three dimensions.  No mainstream Markdown editor or web
> authoring tool can do that.  Yet, Org can *almost* do it.

Patches are always welcome ;)

> +                     (append `((,(if (plist-member options 'autonumber)
> +                                     'tags 'autonumber)
> +                                nil)
> +                               (,(if (plist-member options 'linebreaks)
> +                                     'overflow 'linebreaks)
> +                                nil))
> +                             options)))

You may even drop append and use ,@options inside `(...). Not a big deal though.

> +              (`scale
> +               (when (stringp value)
> +                 (setq value (string-to-number value)))
> +               (when (>= value 10)

If value is invalid string (not an actual number), `string-to-number'
will return 0. This may cause very strange export output I think. 

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-10  2:34                               ` Ihor Radchenko
@ 2022-11-11 12:50                                 ` Rudolf Adamkovič
  2022-11-13  4:24                                   ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-11 12:50 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 624 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> Then, please mention this in the NEWS. Something like "we drop
> extension support as it never worked anyway".

Fixed.  I also reworded the surrounding text to match.

> You may even drop append and use ,@options inside `(...). Not a big
> deal though.

Fixed.

> If value is invalid string (not an actual number), `string-to-number'
> will return 0. This may cause very strange export output I think.

Fixed and added two new tests:

- ox-html/mathjax-legacy-scale-invalid
- ox-html/mathjax-legacy-scale-invalid-message

Below, I attach the 7th revision of the patch.

Rudy


[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 53138 bytes --]

From 49f1dc5e58720c68546a2aedfeb8f87df60ac6cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* testing/org-test.el (org-test-capture-messages): Add a new macro
useful for testing the messages put in the echo area.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  20 +-
 etc/ORG-NEWS                 |  38 ++
 lisp/ox-html.el              | 257 ++++++++---
 testing/lisp/test-ox-html.el | 818 +++++++++++++++++++++++++++++++++++
 testing/org-test.el          |  11 +
 5 files changed, 1063 insertions(+), 81 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..3514f84e4 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13228,24 +13228,20 @@ as-is.
 LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
-with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
-[[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
-options can be configured via ~org-html-mathjax-options~, or in the
-buffer.  For example, with the following settings,
+with Org[fn:: By default, Org loads MathJax from
+[[https://www.jsdelivr.com/][jsDelivr]], as recommended in
+[[https://docs.mathjax.org/en/latest/web/start.html][Getting Started
+with MathJax Components]].][fn:46].  Some MathJax display options can
+be configured via ~org-html-mathjax-options~, or in the buffer.  For
+example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..d9a80a2e5 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,44 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ in
+~org-html-mathjax-options~ which must now point to a file containing
+MathJax version 3 or later.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+MathJax 3, a ground-up rewrite of MathJax 2 came out in 2019.  The new
+version brings modularity, better and faster rendering, improved LaTeX
+support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+MathJax 3 comes with useful extensions.  For instance, you can typeset
+calculus with the ~physics~ extension or chemistry with the ~mhchem~
+extension, like in LaTeX.
+
+Note that the Org manual does not discuss loading of MathJax
+extensions via ~+HTML_MATHJAX~ anymore.  It has never worked anyway.
+To actually load extensions, consult the official documentation:
+
+https://docs.mathjax.org/en/latest/input/tex/extensions.html
+
+Lastly, MathJax 3 changed the default JavaScript content delivery
+network (CDN) provider from CloudFlare to jsDelivr.  You can find the
+new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..443ae4ebe 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1945,21 +2000,85 @@ INFO is a plist used as a communication channel."
   "Insert the user setup into the mathjax template.
 INFO is a plist used as a communication channel."
   (when (and (memq (plist-get info :with-latex) '(mathjax t))
-	     (org-element-map (plist-get info :parse-tree)
-		 '(latex-fragment latex-environment) #'identity info t nil t))
+             (org-element-map (plist-get info :parse-tree)
+                 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
-	  (in-buffer (or (plist-get info :html-mathjax) "")))
+          (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     `((,(if (plist-member options 'autonumber)
+                             'tags 'autonumber)
+                        nil)
+                       (,(if (plist-member options 'linebreaks)
+                             'overflow 'linebreaks)
+                        nil)
+                       ,@options)))
+          (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+        (let ((symbol (car e))
+              (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((value-new
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value value-new)))
+              (`linebreaks
+               (org-display-warning
+                "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (let ((value-maybe (string-to-number value)))
+                   (setq value
+                         (if (= value-maybe 0)
+                             (progn
+                               (org-display-warning
+                                (format "Non-numerical MathJax scale: %s"
+                                        value))
+                               1.0)
+                           value-maybe))))
+               (when (>= value 10)
+                 (setq value
+                       (let ((value-new (/ (float value) 100)))
+                         (org-display-warning
+                          (format "Converting legacy MathJax scale: %s to %s"
+                                  value
+                                  value-new))
+                         value-new))))
+              (`autonumber
+               (org-display-warning
+                "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags
+                     value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..e1b492733
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,818 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax scale: 20 to 0.2"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "20") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (seq-count
+    (lambda (message)
+      (string= "Converting legacy MathJax scale: 20 to 0.2"
+               message))
+    (org-test-capture-warnings
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-invalid ()
+  "Test the legacy scale conversion with an invalid value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "xxx") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-invalid-message ()
+  "Test the invalid legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Non-numerical MathJax scale: xxx"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "xxx") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(autonumber "AMS") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options (cons '(linebreaks "true")
+                                                 org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/org-test.el b/testing/org-test.el
index 9f7bab9ea..880d9e845 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -33,6 +33,7 @@
 
 (require 'org)
 (require 'org-id)
+(require 'org-macs)
 
 ;;; Ob constants
 
@@ -531,6 +532,16 @@ TIME can be a non-nil Lisp time value, or a string specifying a date and time."
 				    (or a ,at) (or b ,at)))))
 	 ,@body))))
 
+(defmacro org-test-capture-warnings (&rest body)
+  "Capture all warnings passed to `org-display-warning' within BODY."
+  (declare (indent 0) (debug t))
+  `(let ((messages (list)))
+     (cl-letf (((symbol-function 'org-display-warning)
+                (lambda (message)
+                  (setq messages (cons message messages)))))
+       ,@body)
+     (nreverse messages)))
+
 (provide 'org-test)
 
 ;;; org-test.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 246 bytes --]

-- 
"One can begin to reason only when a clear picture has been formed in
the imagination."
-- Walter Warwick Sawyer, Mathematician's Delight, 1943

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-11 12:50                                 ` Rudolf Adamkovič
@ 2022-11-13  4:24                                   ` Ihor Radchenko
  2022-11-13 20:52                                     ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-13  4:24 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Below, I attach the 7th revision of the patch.

Thanks!

I note that you did not mention in the news that MathJax 2 support is
dropped. This is a very important thing we must highlight and ring all
the bells about.

Consider that someone has customized path option to a local copy of
MathJax version 2. Then, things will get broken after the update!

Of course, MathJax 2 is severely outdated, and we may not want to support
it completely (though see
https://orgmode.org/list/87h6z69myp.fsf@localhost). However, it could be
at least a good idea to warn the users who are trying to use MathJax 2.
Would it be possible?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-13  4:24                                   ` Ihor Radchenko
@ 2022-11-13 20:52                                     ` Rudolf Adamkovič
  2022-11-14  4:33                                       ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-13 20:52 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Ihor Radchenko, emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1063 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> I note that you did not mention in the news that MathJax 2 support is
> dropped. This is a very important thing we must highlight and ring all
> the bells about.

Fixed.  See the 8th revision of the patch attached below.  Better?

> Consider that someone has customized path option to a local copy of
> MathJax version 2. Then, things will get broken after the update!

Correct, and we spell it out in the manual, in the "... and breaking
changes" section.

Digression [no comment needed; just venting]: This confirms, again, that
we should focus on LaTeX, the strength of Org, instead of defaulting to
the unstable world of JavaScript, copying popular Markdown editors that
have to use MathJax do due to their lack of integration with LaTeX.

> However, it could be at least a good idea to warn the users who are
> trying to use MathJax 2.  Would it be possible?

As far as I know, we have no reliable way of knowing the version of
MathJax without actually reading the JavaScript file, locally and
remotely.

Rudy

[-- Attachment #2: 0001-ox-html-Update-from-MathJax-2-to-MathJax-3.patch --]
[-- Type: text/x-patch, Size: 53176 bytes --]

From b886755450e052c3584e793fe0960914b0733001 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rudolf=20Adamkovi=C4=8D?= <salutis@me.com>
Date: Fri, 7 Oct 2022 15:03:48 +0200
Subject: [PATCH] ox-html: Update from MathJax 2 to MathJax 3+

* lisp/ox-html.el (
org-html-mathjax-options,
org-html-mathjax-template,
org-html--build-mathjax-config
): Update from MathJax 2 to 3 while maintaining compatibility.  All
legacy options should continue to work, except for the 'path' option
which must now point to MathJax 3 or later.
* testing/lisp/test-ox-html.el (
ox-html/mathjax-path-none,
ox-html/mathjax-path-default,
ox-html/mathjax-path-custom,
ox-html/mathjax-path-in-buffer,
ox-html/mathjax-options-default,
ox-html/mathjax-options-custom,
ox-html/mathjax-options-in-buffer,
ox-html/mathjax-legacy-scale-default,
ox-html/mathjax-legacy-scale-custom,
ox-html/mathjax-legacy-scale-in-buffer,
ox-html/mathjax-legacy-scale-message,
ox-html/mathjax-legacy-scale-message-in-buffer,
ox-html/mathjax-legacy-scale-ignore,
ox-html/mathjax-legacy-autonumber-ams,
ox-html/mathjax-legacy-autonumber-ams-in-buffer,
ox-html/mathjax-legacy-autonumber-none,
ox-html/mathjax-legacy-autonumber-none-in-buffer,
ox-html/mathjax-legacy-autonumber-all,
ox-html/mathjax-legacy-autonumber-all-in-buffer,
ox-html/mathjax-legacy-autonumber-message,
ox-html/mathjax-legacy-autonumber-message-in-buffer,
ox-html/mathjax-legacy-font-tex,
ox-html/mathjax-legacy-font-tex-in-buffer,
ox-html/mathjax-legacy-font-stix-web,
ox-html/mathjax-legacy-font-stix-web-in-buffer,
ox-html/mathjax-legacy-font-asana-math,
ox-html/mathjax-legacy-font-asana-math-in-buffer,
ox-html/mathjax-legacy-font-neo-euler,
ox-html/mathjax-legacy-font-neo-euler-in-buffer,
ox-html/mathjax-legacy-font-gyre-pagella,
ox-html/mathjax-legacy-font-gyre-pagella-in-buffer,
ox-html/mathjax-legacy-font-gyre-termes,
ox-html/mathjax-legacy-font-gyre-termes-in-buffer,
ox-html/mathjax-legacy-font-latin-modern,
ox-html/mathjax-legacy-font-latin-modern-in-buffer,
ox-html/mathjax-legacy-line-breaks-true,
ox-html/mathjax-legacy-line-breaks-true-in-buffer,
ox-html/mathjax-legacy-line-breaks-false,
ox-html/mathjax-legacy-line-breaks-false-in-buffer,
ox-html/mathjax-legacy-line-breaks-message,
ox-html/mathjax-legacy-line-breaks-message-in-buffer): Test MathJax in
general and also the conversion of legacy options from MathJax 2 to 3.
* testing/org-test.el (org-test-capture-messages): Add a new macro
useful for testing the messages put in the echo area.
* etc/ORG-NEWS: Document MathJax 2 to 3 upgrade, highlighting the
benefits of the new version but also mentioning the fact that the user
may need to update the `path' option in `org-html-mathjax-options'.
* doc/org-manual.org (Math formatting in HTML export): Update the link
to the MathJax CDN and the example of how to use `+HTML_MATHJAX' with
MathJax 3.  Also, remove the note on MathJax extensions, as they did
not work (and do not work) as documented.

Link: https://list.orgmode.org/orgmode/m2a667n4ax.fsf@me.com/
---
 doc/org-manual.org           |  20 +-
 etc/ORG-NEWS                 |  38 ++
 lisp/ox-html.el              | 257 ++++++++---
 testing/lisp/test-ox-html.el | 818 +++++++++++++++++++++++++++++++++++
 testing/org-test.el          |  11 +
 5 files changed, 1063 insertions(+), 81 deletions(-)
 create mode 100644 testing/lisp/test-ox-html.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index dc2fc57cd..3514f84e4 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13228,24 +13228,20 @@ as-is.
 LaTeX math snippets (see [[*LaTeX fragments]]) can be displayed in two
 different ways on HTML pages.  The default is to use the
 [[https://www.mathjax.org][MathJax]], which should work out of the box
-with Org[fn:: By default Org loads MathJax from
-[[https://cdnjs.com][cdnjs.com]] as recommended by
-[[https://www.mathjax.org][MathJax]].][fn:46].  Some MathJax display
-options can be configured via ~org-html-mathjax-options~, or in the
-buffer.  For example, with the following settings,
+with Org[fn:: By default, Org loads MathJax from
+[[https://www.jsdelivr.com/][jsDelivr]], as recommended in
+[[https://docs.mathjax.org/en/latest/web/start.html][Getting Started
+with MathJax Components]].][fn:46].  Some MathJax display options can
+be configured via ~org-html-mathjax-options~, or in the buffer.  For
+example, with the following settings,
 
 #+begin_example
-,#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
-,#+HTML_MATHJAX: cancel.js noErrors.js
+,#+HTML_MATHJAX: align: left indent: 5em tagside: left
 #+end_example
 
 #+texinfo: @noindent
 equation labels are displayed on the left margin and equations are
-five em from the left margin.  In addition, it loads the two MathJax
-extensions =cancel.js= and =noErrors.js=[fn:: See
-[[https://docs.mathjax.org/en/latest/input/tex/extensions.html#tex-and-latex-extensions][TeX
-and LaTeX extensions]] in the [[https://docs.mathjax.org][MathJax
-manual]] to learn about extensions.].
+five em from the left margin.
 
 #+vindex: org-html-mathjax-template
 See the docstring of ~org-html-mathjax-options~ for all supported
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b542da34b..404a907c6 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -139,6 +139,44 @@ rely on the details of visibility state implementation in
 backend.  From now on, using =outline-*= functions is strongly
 discouraged when working with Org files.
 
+*** HTML export uses MathJax 3+ instead of MathJax 2
+
+Org now uses MathJax 3 by default instead of MathJax 2.  During HTML
+exports, Org automatically converts all legacy MathJax 2 options to
+the corresponding MathJax 3+ options, except for the ~path~ option in
+which now /must/ point to a file containing MathJax version 3 or
+later.  The new Org does /not/ work with the legacy MathJax 2.
+
+Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
+~overflow~), then the ~path~ must point to MathJax 4 or later.
+
+See the updated ~org-html-mathjax-options~ for more details.
+
+MathJax 3, a ground-up rewrite of MathJax 2 came out in 2019.  The new
+version brings modularity, better and faster rendering, improved LaTeX
+support, and more.
+
+For more information about new features, see:
+
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.1.html
+https://docs.mathjax.org/en/latest/upgrading/whats-new-3.2.html
+
+MathJax 3 comes with useful extensions.  For instance, you can typeset
+calculus with the ~physics~ extension or chemistry with the ~mhchem~
+extension, like in LaTeX.
+
+Note that the Org manual does not discuss loading of MathJax
+extensions via ~+HTML_MATHJAX~ anymore.  It has never worked anyway.
+To actually load extensions, consult the official documentation:
+
+https://docs.mathjax.org/en/latest/input/tex/extensions.html
+
+Lastly, MathJax 3 changed the default JavaScript content delivery
+network (CDN) provider from CloudFlare to jsDelivr.  You can find the
+new terms of service, including the privacy policy, at
+https://www.jsdelivr.com/terms.
+
 ** New features
 *** Clock table can now produce quarterly reports
 
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index cad06aebf..443ae4ebe 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1166,72 +1166,116 @@ See `format-time-string' for more information on its components."
 ;;;; Template :: Mathjax
 
 (defcustom org-html-mathjax-options
-  '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
-    (scale "100")
+  '((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
+    (scale 1.0)
     (align "center")
-    (font "TeX")
-    (linebreaks "false")
-    (autonumber "AMS")
+    (font "mathjax-modern")
+    (overflow "overflow")
+    (tags "ams")
     (indent "0em")
     (multlinewidth "85%")
     (tagindent ".8em")
     (tagside "right"))
   "Options for MathJax setup.
 
-Alist of the following elements.  All values are strings.
+Alist of the following elements.
 
-path          The path to MathJax.
+path          The path to MathJax version 3 or later.
 scale         Scaling with HTML-CSS, MathML and SVG output engines.
 align         How to align display math: left, center, or right.
-font          The font to use with HTML-CSS and SVG output.  As of MathJax 2.5
-              the following values are understood: \"TeX\", \"STIX-Web\",
-              \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
-              \"Gyre-Termes\", and \"Latin-Modern\".
+font          The font to use with HTML-CSS and SVG output.  Needs
+              MathJax version 4+.  MathJax 4 provides 11 fonts:
+              \"mathjax-modern\"   Latin-Modern font, default in MathJax 4+
+              \"mathjax-asana\"    Asana-Math font
+              \"mathjax-bonum\"    Gyre Bonum font
+              \"mathjax-dejavu\"   Gyre DejaVu font
+              \"mathjax-pagella\"  Gyre Pagella font
+              \"mathjax-schola\"   Gyre Schola font
+              \"mathjax-termes\"   Gyre Termes font
+              \"mathjax-stix2\"    STIX2 font
+              \"mathjax-fira\"     Fira and Fira-Math fonts
+              \"mathjax-euler\"    Neo Euler font that extends Latin-Modern
+              \"mathjax-tex\"      The original MathJax TeX font
+overflow      How to break displayed equations when too large. Needs
+              MathJax 4 or newer.  Supported options include
+              \"overflow\", \"scale\", \"scroll\", \"truncate\",
+              \"linebreak\", and \"elide\".
 linebreaks    Let MathJax perform automatic linebreaks.  Valid values
               are \"true\" and \"false\".
 indent        If align is not center, how far from the left/right side?  For
               example, \"1em\".
 multlinewidth The width of the multline environment.
-autonumber    How to number equations.  Valid values are \"none\",
-              \"all\" and \"AMS\".
+tags          How to number equations.  Valid values are \"none\",
+              \"all\" and \"ams\".
 tagindent     The amount tags are indented.
 tagside       Which side to show tags/labels on.  Valid values are
               \"left\" and \"right\"
 
-You can also customize this for each buffer, using something like
+You can also customize this for some buffer, using something like
 
-#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+#+HTML_MATHJAX: align: left indent: 5em tagside: left
 
 For further information about MathJax options, see the MathJax documentation:
 
-  https://docs.mathjax.org/"
+  https://docs.mathjax.org/
+
+To maintain compatibility with pre-9.6 Org that used MathJax 2,
+the following conversions take place.
+
+The legacy \"autonumber\" option, with the value \"AMS\",
+\"None\", or \"All\", becomes the \"tags\" option set to the
+value \"ams\", \"none\", or \"all\", respectively.
+
+Any legacy values of the \"scale\" option, specified as
+percentage strings, become converted to unit-interval numbers.
+For example, a legacy scale of \"150\" becomes a scale of 1.5.
+
+The legacy \"linebreaks\" option, with the value \"true\" or
+\"false\", becomes the \"overflow\" option set to the value
+\"linebreak\" or \"overflow\", respectively.
+
+The legacy values of the \"font\" option, namely \"TeX\",
+\"STIX-Web\", \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+\"Gyre-Termes\", \"Latin-Modern\", become converted to the
+corresponding MathJax 4+ font names.
+
+Legacy options and values always take precedence.
+"
   :group 'org-export-html
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "9.6")
   :type '(list :greedy t
 	       (list :tag "path   (the path from where to load MathJax.js)"
 		     (const :format "       " path) (string))
 	       (list :tag "scale  (scaling for the displayed math)"
-		     (const :format "       " scale) (string))
+		     (const :format "   " scale) (float))
 	       (list :tag "align  (alignment of displayed equations)"
 		     (const :format "       " align) (string))
-	       (list :tag "font (used to display math)"
-		     (const :format "            " font)
-		     (choice (const "TeX")
-			     (const "STIX-Web")
-			     (const "Asana-Math")
-			     (const "Neo-Euler")
-			     (const "Gyre-Pagella")
-			     (const "Gyre-Termes")
-			     (const "Latin-Modern")))
-	       (list :tag "linebreaks (automatic line-breaking)"
-		     (const :format "      " linebreaks)
-		     (choice (const "true")
-			     (const "false")))
-	       (list :tag "autonumber (when should equations be numbered)"
-		     (const :format "      " autonumber)
-		     (choice (const "AMS")
-			     (const "None")
-			     (const "All")))
+               (list :tag "font (used to typeset math)"
+		     (const :format "               " font)
+                     (choice (const "mathjax-modern")
+                             (const "mathjax-asana")
+                             (const "mathjax-bonum")
+                             (const "mathjax-dejavu")
+                             (const "mathjax-pagella")
+                             (const "mathjax-schola")
+                             (const "mathjax-termes")
+                             (const "mathjax-stix2")
+                             (const "mathjax-fira")
+                             (const "mathjax-euler")
+                             (const "mathjax-tex")))
+               (list :tag "overflow (how to break displayed math)"
+		     (const :format "         " overflow)
+                     (choice (const "overflow")
+                             (const "scale")
+                             (const "scroll")
+                             (const "truncate")
+                             (const "linebreak")
+                             (const "elide")))
+	       (list :tag "tags (whether equations are numbered and how)"
+		     (const :format "    " tags)
+		     (choice (const "ams")
+			     (const "none")
+			     (const "all")))
 	       (list :tag "indent (indentation with left or right alignment)"
 		     (const :format "       " indent) (string))
 	       (list :tag "multlinewidth (width to use for the multline environment)"
@@ -1244,27 +1288,38 @@ For further information about MathJax options, see the MathJax documentation:
 			     (const "right")))))
 
 (defcustom org-html-mathjax-template
-  "<script type=\"text/x-mathjax-config\">
-    MathJax.Hub.Config({
-        displayAlign: \"%ALIGN\",
-        displayIndent: \"%INDENT\",
-
-        \"HTML-CSS\": { scale: %SCALE,
-                        linebreaks: { automatic: \"%LINEBREAKS\" },
-                        webFont: \"%FONT\"
-                       },
-        SVG: {scale: %SCALE,
-              linebreaks: { automatic: \"%LINEBREAKS\" },
-              font: \"%FONT\"},
-        NativeMML: {scale: %SCALE},
-        TeX: { equationNumbers: {autoNumber: \"%AUTONUMBER\"},
-               MultLineWidth: \"%MULTLINEWIDTH\",
-               TagSide: \"%TAGSIDE\",
-               TagIndent: \"%TAGINDENT\"
-             }
-});
+  "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '%MULTLINEWIDTH'
+      },
+      tags: '%TAGS',
+      tagSide: '%TAGSIDE',
+      tagIndent: '%TAGINDENT'
+    },
+    chtml: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    svg: {
+      scale: %SCALE,
+      displayAlign: '%ALIGN',
+      displayIndent: '%INDENT'
+    },
+    output: {
+      font: '%FONT',
+      displayOverflow: '%OVERFLOW'
+    }
+  };
 </script>
-<script src=\"%PATH\"></script>"
+
+<script
+  id=\"MathJax-script\"
+  async
+  src=\"%PATH\">
+</script>"
   "The MathJax template.  See also `org-html-mathjax-options'."
   :group 'org-export-html
   :type 'string)
@@ -1945,21 +2000,85 @@ INFO is a plist used as a communication channel."
   "Insert the user setup into the mathjax template.
 INFO is a plist used as a communication channel."
   (when (and (memq (plist-get info :with-latex) '(mathjax t))
-	     (org-element-map (plist-get info :parse-tree)
-		 '(latex-fragment latex-environment) #'identity info t nil t))
+             (org-element-map (plist-get info :parse-tree)
+                 '(latex-fragment latex-environment) #'identity info t nil t))
     (let ((template (plist-get info :html-mathjax-template))
-	  (options (plist-get info :html-mathjax-options))
-	  (in-buffer (or (plist-get info :html-mathjax) "")))
+          (options (let ((options (plist-get info :html-mathjax-options)))
+                     ;; If the user customized some legacy option, set
+                     ;; the corresponding new option to nil, so that
+                     ;; the legacy user choice overrides the default.
+                     ;; Otherwise, the user did not set the legacy
+                     ;; option, in which case still set the legacy
+                     ;; option but to no value, so that the code can
+                     ;; find its in-buffer value, if set.
+                     `((,(if (plist-member options 'autonumber)
+                             'tags 'autonumber)
+                        nil)
+                       (,(if (plist-member options 'linebreaks)
+                             'overflow 'linebreaks)
+                        nil)
+                       ,@options)))
+          (in-buffer (or (plist-get info :html-mathjax) "")))
       (dolist (e options (org-element-normalize-string template))
-	(let ((name (car e))
-	      (val (nth 1 e)))
-	  (when (string-match (concat "\\<" (symbol-name name) ":") in-buffer)
-	    (setq val
-		  (car (read-from-string (substring in-buffer (match-end 0))))))
-	  (unless (stringp val) (setq val (format "%s" val)))
-	  (while (string-match (concat "%" (upcase (symbol-name name)))
-			       template)
-	    (setq template (replace-match val t t template))))))))
+        (let ((symbol (car e))
+              (value (nth 1 e)))
+          (when (string-match (concat "\\<" (symbol-name symbol) ":")
+                              in-buffer)
+            (setq value
+                  (car (split-string (substring in-buffer
+                                                (match-end 0))))))
+          (when value
+            (pcase symbol
+              (`font
+               (when-let
+                   ((value-new
+                     (pcase value
+                       ("TeX" "mathjax-tex")
+                       ("STIX-Web" "mathjax-stix2")
+                       ("Asana-Math" "mathjax-asana")
+                       ("Neo-Euler" "mathjax-euler")
+                       ("Gyre-Pagella" "mathjax-pagella")
+                       ("Gyre-Termes" "mathjax-termes")
+                       ("Latin-Modern" "mathjax-modern"))))
+                 (setq value value-new)))
+              (`linebreaks
+               (org-display-warning
+                "Converting legacy MathJax option: linebreaks")
+               (setq symbol 'overflow
+                     value (if (string= value "true")
+                               "linebreak"
+                             "overflow")))
+              (`scale
+               (when (stringp value)
+                 (let ((value-maybe (string-to-number value)))
+                   (setq value
+                         (if (= value-maybe 0)
+                             (progn
+                               (org-display-warning
+                                (format "Non-numerical MathJax scale: %s"
+                                        value))
+                               1.0)
+                           value-maybe))))
+               (when (>= value 10)
+                 (setq value
+                       (let ((value-new (/ (float value) 100)))
+                         (org-display-warning
+                          (format "Converting legacy MathJax scale: %s to %s"
+                                  value
+                                  value-new))
+                         value-new))))
+              (`autonumber
+               (org-display-warning
+                "Converting legacy MathJax option: autonumber")
+               (setq symbol 'tags
+                     value (downcase value))))
+            (while (string-match (format "\\(%%%s\\)[^A-Z]"
+                                         (upcase (symbol-name symbol)))
+                                 template)
+              (setq template
+                    (replace-match (format "%s" value)
+                                   t
+                                   t template 1)))))))))
 
 (defun org-html-format-spec (info)
   "Return format specification for preamble and postamble.
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..e1b492733
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,818 @@
+;;; test-ox-html.el --- Tests for ox-html.el
+
+;; Copyright (C) 2022  Rudolf Adamkovič
+
+;; Author: Rudolf Adamkovič <salutis@me.com>
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ox-html)
+
+\f
+;;; Loading MathJax
+
+(ert-deftest ox-html/mathjax-path-none ()
+  "Test that MathJax does not load when not needed."
+  (should-not
+   (org-test-with-temp-text "No LaTeX here."
+     (let ((export-buffer "*Test HTML Export*")
+           (org-export-show-temporary-export-buffer nil))
+       (org-export-to-buffer 'html export-buffer
+         nil nil nil nil nil
+         #'html-mode)
+       (with-current-buffer export-buffer
+         (let ((case-fold-search t))
+           (search-forward "MathJax" nil t)))))))
+
+(ert-deftest ox-html/mathjax-path-default ()
+  "Test the default path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-custom ()
+  "Test a customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil)
+                (org-html-mathjax-options
+                 '((path "./mathjax/es5/tex-mml-chtml.js"))))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-path-in-buffer ()
+  "Test a in-buffer customized path from which MathJax loads."
+  (should
+   (= 1 (org-test-with-temp-text "
+#+HTML_MATHJAX: path: ./mathjax/es5/tex-mml-chtml.js
+$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script
+  id=\"MathJax-script\"
+  async
+  src=\"./mathjax/es5/tex-mml-chtml.js\">
+</script>"))))))))
+
+\f
+;;; Configuring MathJax with options
+
+(ert-deftest ox-html/mathjax-options-default ()
+  "Test the default MathJax options."
+  (should
+   (= 1 (org-test-with-temp-text "$x$"
+          (let ((export-buffer "*Test HTML Export*")
+                (org-export-show-temporary-export-buffer nil))
+            (org-export-to-buffer 'html export-buffer
+              nil nil nil nil nil
+              #'html-mode)
+            (with-current-buffer export-buffer
+              (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '85%'
+      },
+      tags: 'ams',
+      tagSide: 'right',
+      tagIndent: '.8em'
+    },
+    chtml: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    svg: {
+      scale: 1.0,
+      displayAlign: 'center',
+      displayIndent: '0em'
+    },
+    output: {
+      font: 'mathjax-modern',
+      displayOverflow: 'overflow'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-custom ()
+  "Test customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               '((path "<unused>")      ; tested elsewhere
+                 (scale 0.5)
+                 (align "right")
+                 (font "mathjax-euler")
+                 (overflow "scale")
+                 (tags "all")
+                 (indent "1em")
+                 (multlinewidth "100%")
+                 (tagindent "2em")
+                 (tagside "left"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+(ert-deftest ox-html/mathjax-options-in-buffer ()
+  "Test in-buffer customized MathJax options."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 0.5
+#+HTML_MATHJAX: align: right
+#+HTML_MATHJAX: font: mathjax-euler
+#+HTML_MATHJAX: overflow: scale
+#+HTML_MATHJAX: tags: all
+#+HTML_MATHJAX: indent: 1em
+#+HTML_MATHJAX: multlinewidth: 100%
+#+HTML_MATHJAX: tagindent: 2em
+#+HTML_MATHJAX: tagside: left"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx "<script>
+  window.MathJax = {
+    tex: {
+      ams: {
+        multlineWidth: '100%'
+      },
+      tags: 'all',
+      tagSide: 'left',
+      tagIndent: '2em'
+    },
+    chtml: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    svg: {
+      scale: 0.5,
+      displayAlign: 'right',
+      displayIndent: '1em'
+    },
+    output: {
+      font: 'mathjax-euler',
+      displayOverflow: 'scale'
+    }
+  };
+</script>"))))))))
+
+\f
+;;; Converting legacy MathJax scales
+
+;; Define a legacy scale as any scale given as a percentage string,
+;; such as "150", instead of a unit-interval float, such as 1.5.
+
+(ert-deftest ox-html/mathjax-legacy-scale-default ()
+  "Test the legacy scale conversion with the old default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "100") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-custom ()
+  "Test the legacy scale conversion with a non-default value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "10") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-in-buffer ()
+  "Test the legacy scale conversion with an in-buffer value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 10"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 0.1" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message ()
+  "Test the legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax scale: 20 to 0.2"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "20") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (seq-count
+    (lambda (message)
+      (string= "Converting legacy MathJax scale: 20 to 0.2"
+               message))
+    (org-test-capture-warnings
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: scale: 20"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-ignore ()
+  "Test the legacy scale conversion ignores small values."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options '((scale "9"))))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 9" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-invalid ()
+  "Test the legacy scale conversion with an invalid value."
+  (should
+   (= 2
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(scale "xxx") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "scale: 1.0" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-scale-invalid-message ()
+  "Test the invalid legacy scale conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Non-numerical MathJax scale: xxx"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(scale "xxx") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax auto-numbering
+
+;; NOTE: AMS stands for American Mathematical Society.
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams ()
+  "Test legacy auto-numbering, when AMS."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "AMS") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-ams-in-buffer ()
+  "Test legacy auto-numbering, when AMS in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'ams'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none ()
+  "Test legacy auto-numbering, when disabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "None") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-none-in-buffer ()
+  "Test legacy auto-numbering, when disabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: None"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'none'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all ()
+  "Test legacy auto-numbering, when enabled."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(autonumber "All") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-all-in-buffer ()
+  "Test legacy auto-numbering, when enabled in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: All"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "tags: 'all'" (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options
+                  (cons '(autonumber "AMS") org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-autonumber-message-in-buffer ()
+  "Test legacy auto-numbering conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: autonumber"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: autonumber: AMS"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+\f
+;;; Converting legacy MathJax fonts
+
+(ert-deftest ox-html/mathjax-legacy-font-tex ()
+  "Test legacy font, when TeX."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "TeX") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-tex-in-buffer ()
+  "Test legacy font, when TeX in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: TeX"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-tex'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web ()
+  "Test legacy font, when STIX-Web."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "STIX-Web") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-stix-web-in-buffer ()
+  "Test legacy font, when STIX-Web in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: STIX-Web"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-stix2'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math ()
+  "Test legacy font, when Asana-Math."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Asana-Math") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-asana-math-in-buffer ()
+  "Test legacy font, when Asana-Math in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Asana-Math"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-asana'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler ()
+  "Test legacy font, when Neo-Euler."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Neo-Euler") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-neo-euler-in-buffer ()
+  "Test legacy font, when Neo-Euler in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Neo-Euler"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-euler'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella ()
+  "Test legacy font, when Gyre-Pagella."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Pagella") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-pagella-in-buffer ()
+  "Test legacy font, when Gyre-Pagella in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Pagella"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-pagella'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes ()
+  "Test legacy font, when Gyre-Termes."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Gyre-Termes") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-gyre-termes-in-buffer ()
+  "Test legacy font, when Gyre-Termes in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Gyre-Termes"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-termes'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern ()
+  "Test legacy font, when Latin-Modern."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(font "Latin-Modern") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-font-latin-modern-in-buffer ()
+  "Test legacy font, when Latin-Modern in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: font: Latin-Modern"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "font: 'mathjax-modern'"
+                               (or "," "\n"))))))))))
+
+\f
+;;; Converting legacy MathJax line breaks
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true ()
+  "Test legacy line breaks, when true."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "true")
+                         (overflow "overflow"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-true-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "overflow") org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'linebreak'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false ()
+  "Test legacy line breaks, when false."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (append '((linebreaks "false")
+                         (overflow "linebreak"))
+                       org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-false-in-buffer ()
+  "Test legacy line breaks, when true in-buffer."
+  (should
+   (= 1
+      (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: false"
+        (let ((export-buffer "*Test HTML Export*")
+              (org-export-show-temporary-export-buffer nil)
+              (org-html-mathjax-options
+               (cons '(overflow "linebreak")
+                     org-html-mathjax-options)))
+          (org-export-to-buffer 'html export-buffer
+            nil nil nil nil nil
+            #'html-mode)
+          (with-current-buffer export-buffer
+            (how-many (rx (seq "displayOverflow: 'overflow'"
+                               (or "," "\n"))))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message ()
+  "Test the legacy line breaks conversion message."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil)
+                 (org-html-mathjax-options (cons '(linebreaks "true")
+                                                 org-html-mathjax-options)))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(ert-deftest ox-html/mathjax-legacy-line-breaks-message-in-buffer ()
+  "Test the legacy scale conversion message for an in-buffer value."
+  (should
+   (= 1
+      (seq-count
+       (lambda (message)
+         (string= "Converting legacy MathJax option: linebreaks"
+                  message))
+       (org-test-capture-warnings
+         (org-test-with-temp-text "$x$
+#+HTML_MATHJAX: linebreaks: true"
+           (let ((export-buffer "*Test HTML Export*")
+                 (org-export-show-temporary-export-buffer nil))
+             (org-export-to-buffer 'html export-buffer
+               nil nil nil nil nil
+               #'html-mode))))))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/org-test.el b/testing/org-test.el
index 9f7bab9ea..880d9e845 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -33,6 +33,7 @@
 
 (require 'org)
 (require 'org-id)
+(require 'org-macs)
 
 ;;; Ob constants
 
@@ -531,6 +532,16 @@ TIME can be a non-nil Lisp time value, or a string specifying a date and time."
 				    (or a ,at) (or b ,at)))))
 	 ,@body))))
 
+(defmacro org-test-capture-warnings (&rest body)
+  "Capture all warnings passed to `org-display-warning' within BODY."
+  (declare (indent 0) (debug t))
+  `(let ((messages (list)))
+     (cl-letf (((symbol-function 'org-display-warning)
+                (lambda (message)
+                  (setq messages (cons message messages)))))
+       ,@body)
+     (nreverse messages)))
+
 (provide 'org-test)
 
 ;;; org-test.el ends here
-- 
2.38.1


[-- Attachment #3: Type: text/plain, Size: 329 bytes --]

-- 
"Programming reliably -- must be an activity of an undeniably
mathematical nature […] You see, mathematics is about thinking, and
doing mathematics is always trying to think as well as possible."
-- Edsger W. Dijkstra, 1981

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-13 20:52                                     ` Rudolf Adamkovič
@ 2022-11-14  4:33                                       ` Ihor Radchenko
  2022-11-19 13:10                                         ` Bastien Guerry
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-14  4:33 UTC (permalink / raw)
  To: Rudolf Adamkovič, Bastien; +Cc: Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> I note that you did not mention in the news that MathJax 2 support is
>> dropped. This is a very important thing we must highlight and ring all
>> the bells about.
>
> Fixed.  See the 8th revision of the patch attached below.  Better?

Thanks! Yes, better.

>> However, it could be at least a good idea to warn the users who are
>> trying to use MathJax 2.  Would it be possible?
>
> As far as I know, we have no reliable way of knowing the version of
> MathJax without actually reading the JavaScript file, locally and
> remotely.

I see. Then, we probably cannot have automatic and full backwards
compatibility anyway.

Before we proceed with the merge, I'd like to ping Bastien in case if he
has any objections.

Bastien, this is a breaking change that potentially affects the users
who use custom path to MathJax 2 in their config. Unfortunately, there
is no way to introduce MathJax 3 yet not breaking things for such users.
Is the current news entry is sufficient? Or should we do something in
addition to announce backwards-incompatible change?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-14  4:33                                       ` Ihor Radchenko
@ 2022-11-19 13:10                                         ` Bastien Guerry
  2022-11-21  2:51                                           ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Bastien Guerry @ 2022-11-19 13:10 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Rudolf Adamkovič, Ihor Radchenko, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

> Is the current news entry is sufficient? 

The ORG-NEWS entry in the patch looks good, thanks.

> Or should we do something in
> addition to announce backwards-incompatible change?

I suggest to make this change visible on update.orgmode.org by sending
an announcement to the list using X-Woof-Change: 9.6 as a header.

-- 
 Bastien


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-19 13:10                                         ` Bastien Guerry
@ 2022-11-21  2:51                                           ` Ihor Radchenko
  2022-11-22 20:56                                             ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-21  2:51 UTC (permalink / raw)
  To: Bastien Guerry; +Cc: Rudolf Adamkovič, Ihor Radchenko, emacs-orgmode

Bastien Guerry <bzg@gnu.org> writes:

>> Or should we do something in
>> addition to announce backwards-incompatible change?
>
> I suggest to make this change visible on update.orgmode.org by sending
> an announcement to the list using X-Woof-Change: 9.6 as a header.

Done. Hopefully it is going to work:
https://orgmode.org/list/87o7t1ja2a.fsf@localhost

Rudolf, I have applied your patch onto main with amendments. I have
replaced "MathJax 4" instances with "MathJax 3". I guess it was a typo.
https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=62e1513b5

Thanks a lot of the important contribution!

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-21  2:51                                           ` Ihor Radchenko
@ 2022-11-22 20:56                                             ` Rudolf Adamkovič
  2022-11-23 11:11                                               ` Ihor Radchenko
  0 siblings, 1 reply; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-22 20:56 UTC (permalink / raw)
  To: Ihor Radchenko, Bastien Guerry; +Cc: Ihor Radchenko, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

> Rudolf, I have applied your patch onto main with amendments.

Thank you!

> I have replaced "MathJax 4" instances with "MathJax 3". I guess it was
> a typo.

The patch (correctly) distinguishes between:

- MathJax 2 that we deprecated,
- MathJax 3 that we now use by default, and
- MathJax 4 that we support but do *not* use by default.

Most notably, MathJax 4 *re*-adds the support for (1) custom fonts and
(2) line breaks.  From the manual:

> Further, if you need to use a non-default ~font~ or ~linebreaks~ (now
> +~overflow~), then the ~path~ must point to MathJax 4 or later.

(The docstring also explicitly documents the two options as requiring
MathJax 4+.)

Even though MathJax 4 have yet to see a stable release, the patch works
with it, and the tests exercise every MathJax 4+ option as well.

Rationale:

(1) we can keep all the customization options from before,

(2) the more advanced users can use MathJax 4+ if they wish, and

(3) Org will need zero-to-none updates when 4 comes out [*].

[*]: In the expected case, we just updating the `path'.

Rudy
-- 
"Thinking is a momentary dismissal of irrelevancies."
-- Richard Buckminster Fuller, 1969

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-22 20:56                                             ` Rudolf Adamkovič
@ 2022-11-23 11:11                                               ` Ihor Radchenko
  2022-11-23 23:08                                                 ` Rudolf Adamkovič
  0 siblings, 1 reply; 27+ messages in thread
From: Ihor Radchenko @ 2022-11-23 11:11 UTC (permalink / raw)
  To: Rudolf Adamkovič; +Cc: Bastien Guerry, Ihor Radchenko, emacs-orgmode

Rudolf Adamkovič <salutis@me.com> writes:

>> I have replaced "MathJax 4" instances with "MathJax 3". I guess it was
>> a typo.
>
> The patch (correctly) distinguishes between:
>
> - MathJax 2 that we deprecated,
> - MathJax 3 that we now use by default, and
> - MathJax 4 that we support but do *not* use by default.

Oops. I did not see MathJax 4 on the website and missed the point.
Fixed now.
https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=7f4d91040a4fbf31fc78b250d4e47efb92e5fbd2

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] Re: Update Org to MathJax 3
  2022-11-23 11:11                                               ` Ihor Radchenko
@ 2022-11-23 23:08                                                 ` Rudolf Adamkovič
  0 siblings, 0 replies; 27+ messages in thread
From: Rudolf Adamkovič @ 2022-11-23 23:08 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Bastien Guerry, Ihor Radchenko, emacs-orgmode

Ihor Radchenko <yantar92@posteo.net> writes:

> Fixed now.

Fantastic!  And thank you again, Ihor, for the guidance.

Rudy
-- 
"I love deadlines.  I love the whooshing noise they make as they go by."
-- Douglas Adams, The Salmon of Doubt, 2002

Rudolf Adamkovič <salutis@me.com> [he/him]
Studenohorská 25
84103 Bratislava
Slovakia


^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2022-11-23 23:09 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-07 13:36 Update Org to MathJax 3 Rudolf Adamkovič
2022-10-08  7:20 ` Ihor Radchenko
2022-10-08 21:32   ` Rudolf Adamkovič
2022-11-05  0:20     ` [PATCH] " Rudolf Adamkovič
2022-11-05 12:01       ` Ihor Radchenko
2022-11-05 16:56         ` Rudolf Adamkovič
2022-11-05 22:44           ` Rudolf Adamkovič
2022-11-06  3:52             ` Ihor Radchenko
2022-11-06 23:49               ` Rudolf Adamkovič
2022-11-07  3:02                 ` Ihor Radchenko
2022-11-07 20:56                   ` Rudolf Adamkovič
2022-11-08  5:20                     ` Ihor Radchenko
2022-11-08 23:37                       ` Rudolf Adamkovič
2022-11-09  0:05                         ` Rudolf Adamkovič
2022-11-09  5:40                           ` Ihor Radchenko
2022-11-09 23:01                             ` Rudolf Adamkovič
2022-11-10  2:34                               ` Ihor Radchenko
2022-11-11 12:50                                 ` Rudolf Adamkovič
2022-11-13  4:24                                   ` Ihor Radchenko
2022-11-13 20:52                                     ` Rudolf Adamkovič
2022-11-14  4:33                                       ` Ihor Radchenko
2022-11-19 13:10                                         ` Bastien Guerry
2022-11-21  2:51                                           ` Ihor Radchenko
2022-11-22 20:56                                             ` Rudolf Adamkovič
2022-11-23 11:11                                               ` Ihor Radchenko
2022-11-23 23:08                                                 ` Rudolf Adamkovič
2022-11-09  2:49                         ` Ihor Radchenko

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).