Repository: KaTeX/KaTeX (js)

Issue:
Support \textup
`\textup{text}` renders text that is always upright (i.e. not italicised), regardless of its content.

Pull request description:
Support \textup and \textmd
This PR adds support for `\textup` and `\textmd`, which are the inverses of `\textit` and `\textbf`.

Unlike bare `\text`, they result in `textup` and `textmd` classes being applied, but those have no CSS rules, so they act the same as bare `\text`.

Fixes #1909.

Perhaps notably, this PR does not add support for `\upshape` or `\mdseries`, as we don't currently support `\itshape` or `\bfseries` (could be added easily in another PR). But it also doesn't support `\up` and `\md`, despite existing support `\it` and `\bf`. Not sure if anyone ever uses those old-style commands, though.  See [this cheat sheet](https://www.harding.edu/lmurray/latex_files/latexref/latexcheatsheet.pdf) for example.

Patch:
diff --git a/docs/support_table.md b/docs/support_table.md
index e1972ff1..80217cd7 100644
--- a/docs/support_table.md
+++ b/docs/support_table.md
@@ -129,6 +129,7 @@ table td {
 |\beth|$\beth$||
 |\between|$\between$||
 |\bf|$\bf AaBb12$|`\bf AaBb12`|
+|\bfseries|<span style="color:firebrick;">Not supported</span>||
 |\big|$\big(\big)$|`\big(\big)`|
 |\Big|$\Big(\Big)$|`\Big(\Big)`|
 |\bigcap|$\bigcap$||
@@ -343,6 +344,8 @@ table td {
 |:---------------|:------------|:-----------------|
 |\ell|$\ell$||
 |\else|<span style="color:firebrick;">Not supported</span>|[Issue #1003](https://github.com/KaTeX/KaTeX/issues/1003)|
+|\em|<span style="color:firebrick;">Not supported</span>||
+|\emph|<span style="color:firebrick;">Not supported</span>||
 |\empty|$\empty$||
 |\emptyset|$\emptyset$||
 |\enclose|<span style="color:firebrick;">Not supported</span>|Non standard
@@ -488,6 +491,7 @@ table td {
 |\iota|$\iota$||
 |\isin|$\isin$||
 |\it|${\it AaBb}$|`{\it AaBb}`|
+|\itshape|<span style="color:firebrick;">Not supported</span>||
 
 ## JK
 
@@ -640,6 +644,8 @@ table td {
 |{matrix}|$\begin{matrix}a&b\\c&d\end{matrix}$|`\begin{matrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{matrix}`|
 |\max|$\max$||
 |\mbox|<span style="color:firebrick;">Not supported</span>||
+|\md|<span style="color:firebrick;">Not supported</span>||
+|\mdseries|<span style="color:firebrick;">Not supported</span>||
 |\measuredangle|$\measuredangle$||
 |\medspace|$a\medspace b$|`a\medspace b`|
 |\mho|$\mho$||
@@ -700,6 +706,7 @@ table td {
 |\nobreak|||
 |\nobreakspace|$a\nobreakspace b$|`a\nobreakspace b`|
 |\nolimits|$\lim\nolimits_x$|`\lim\nolimits_x`|
+|\normalfont|<span style="color:firebrick;">Not supported</span>||
 |\normalsize|$\normalsize normalsize$|`\normalsize normalsize`|
 |\not|$\not =$|`\not =`|
 |\notag|<span style="color:firebrick;">Not supported</span>||
@@ -885,6 +892,7 @@ table td {
 |\S|$\text{\S}$|`\text{\S}`|
 |\Sampi|<span style="color:firebrick;">Not supported</span>||
 |\sampi|<span style="color:firebrick;">Not supported</span>||
+|\sc|<span style="color:firebrick;">Not supported</span>|[Issue #471](https://github.com/KaTeX/KaTeX/issues/471)|
 |\scalebox|<span style="color:firebrick;">Not supported</span>||
 |\scr|<span style="color:firebrick;">Not supported</span>|See `\mathscr`|
 |\scriptscriptstyle|$\scriptscriptstyle \frac cd$|`\scriptscriptstyle \frac cd`|
@@ -913,6 +921,7 @@ table td {
 |\sh|$\sh$||
 |\skew|<span style="color:firebrick;">Not supported</span>||
 |\skip|<span style="color:firebrick;">Not supported</span>||
+|\sl|<span style="color:firebrick;">Not supported</span>||
 |\small|$\small small$|`\small small`|
 |\smallfrown|$\smallfrown$||
 |\smallint|$\smallint$||
@@ -1007,6 +1016,7 @@ table td {
 |\textgreater|$\text{\textgreater}$|`\text{\textgreater}`|
 |\textit|$\textit{AaBb}$|`\textit{AaBb}`|
 |\textless|$\text{\textless}$|`\text{\textless}`|
+|\textmd|$\textmd{AaBb123}$|`\textmd{AaBb123}`|
 |\textnormal|$\textnormal{AB}$|`\textnormal{AB}`|
 |\textquotedblleft|$\text{\textquotedblleft}$|`\text{\textquotedblleft}`|
 |\textquotedblright|$\text{\textquotedblright}$|`\text{\textquotedblright}`|
@@ -1016,11 +1026,13 @@ table td {
 |\textrm|$\textrm{AaBb123}$|`\textrm{AaBb123}`|
 |\textsc|<span style="color:firebrick;">Not supported</span>|[Issue #471](https://github.com/KaTeX/KaTeX/issues/471)|
 |\textsf|$\textsf{AaBb123}$|`\textsf{AaBb123}`|
+|\textsl|<span style="color:firebrick;">Not supported</span>||
 |\textsterling|$\text{\textsterling}$|`\text{\textsterling}`|
 |\textstyle|$\textstyle\sum_0^n$|`\textstyle\sum_0^n`|
 |\texttip|<span style="color:firebrick;">Not supported</span>||
 |\texttt|$\texttt{AaBb123}$|`\texttt{AaBb123}`|
 |\textunderscore|$\text{\textunderscore}$|`\text{\textunderscore}`|
+|\textup|$\textup{AaBb123}$|`\textup{AaBb123}`|
 |\textvisiblespace|<span style="color:firebrick;">Not supported</span>||
 |\tfrac|$\tfrac ab$|`\tfrac ab`|
 |\tg|$\tg$||
@@ -1074,6 +1086,7 @@ table td {
 |\unicode|<span style="color:firebrick;">Not supported</span>||
 |\unlhd|$\unlhd$||
 |\unrhd|$\unrhd$||
+|\up|<span style="color:firebrick;">Not supported</span>||
 |\Uparrow|$\Uparrow$||
 |\uparrow|$\uparrow$||
 |\Updownarrow|$\Updownarrow$||
@@ -1082,6 +1095,7 @@ table td {
 |\upharpoonright|$\upharpoonright$||
 |\uplus|$\uplus$||
 |\uproot|<span style="color:firebrick;">Not supported</span>||
+|\upshape|<span style="color:firebrick;">Not supported</span>||
 |\Upsilon|$\Upsilon$||
 |\upsilon|$\upsilon$||
 |\upuparrows|$\upuparrows$||
diff --git a/docs/supported.md b/docs/supported.md
index 96a78a67..0482f05b 100644
--- a/docs/supported.md
+++ b/docs/supported.md
@@ -516,15 +516,16 @@ For color definition, KaTeX color functions will accept the standard HTML [pred
 
 ||||
 |:------------------------------|:------------------------------|:-----
-|$\mathrm{Ab0}$ `\mathrm{Ab0}`  |$\mathbf{Ab0}$ `\mathbf{Ab0}`  |$\mathit{Ab}$ `\mathit{Ab}`
-|$\mathnormal{Ab0}$ `\mathnormal{Ab0}`|$\textbf{Ab0}$ `\textbf{Ab0}`  |$\textit{Ab}$ `\textit{Ab}`
-|$\textrm{Ab0}$ `\textrm{Ab0}`  |$\bf Ab0$ `\bf Ab0`            |$\it Ab$ `\it Ab`
-|$\rm Ab0$ `\rm Ab0`            |$\bold{Ab0}$ `\bold{Ab0}`|$\Bbb{AB}$ `\Bbb{AB}`
-|$\textnormal{Ab0}$ `\textnormal{Ab0}`|$\boldsymbol{Ab}$ `\boldsymbol{Ab}`|$\mathbb{AB}$ `\mathbb{AB}`
-|$\text{Ab0}$ `\text{Ab0}`      |$\bm{Ab}$ `\bm{Ab}`            |$\frak{Ab0}$ `\frak{Ab0}`
-|$\mathsf{Ab0}$ `\mathsf{Ab0}`  |$\mathtt{Ab0}$ `\mathtt{Ab0}`  |$\mathfrak{Ab0}$ `\mathfrak{Ab0}`
-|$\textsf{Ab0}$ `\textsf{Ab0}`  |$\texttt{Ab0}$ `\texttt{Ab0}`  |$\mathcal{AB0}$ `\mathcal{AB0}`
-|$\sf Ab0$ `\sf Ab0`            |$\tt Ab0$ `\tt Ab0`            |$\mathscr{AB}$ `\mathscr{AB}`
+|$\mathrm{Ab0}$ `\mathrm{Ab0}`  |$\mathbf{Ab0}$ `\mathbf{Ab0}`  |$\mathit{Ab0}$ `\mathit{Ab0}`
+|$\mathnormal{Ab0}$ `\mathnormal{Ab0}`|$\textbf{Ab0}$ `\textbf{Ab0}`  |$\textit{Ab0}$ `\textit{Ab0}`
+|$\textrm{Ab0}$ `\textrm{Ab0}`  |$\bf Ab0$ `\bf Ab0`            |$\it Ab0$ `\it Ab0`
+|$\rm Ab0$ `\rm Ab0`            |$\bold{Ab0}$ `\bold{Ab0}`      |$\textup{Ab0}$ `\textup{Ab0}`
+|$\textnormal{Ab0}$ `\textnormal{Ab0}`|$\boldsymbol{Ab0}$ `\boldsymbol{Ab}`|$\Bbb{AB}$ `\Bbb{AB}`
+|$\text{Ab0}$ `\text{Ab0}`      |$\bm{Ab0}$ `\bm{Ab0}`          |$\mathbb{AB}$ `\mathbb{AB}`
+|$\mathsf{Ab0}$ `\mathsf{Ab0}`  |$\textmd{Ab0}$ `\textmd{Ab0}`  |$\frak{Ab0}$ `\frak{Ab0}`
+|$\textsf{Ab0}$ `\textsf{Ab0}`  |$\mathtt{Ab0}$ `\mathtt{Ab0}`  |$\mathfrak{Ab0}$ `\mathfrak{Ab0}`
+|$\sf Ab0$ `\sf Ab0`            |$\texttt{Ab0}$ `\texttt{Ab0}`  |$\mathcal{AB0}$ `\mathcal{AB0}`
+|                               |$\tt Ab0$ `\tt Ab0`            |$\mathscr{AB}$ `\mathscr{AB}`
 
 One can stack font family, font weight, and font shape by using the `\textXX` versions of the font functions. So `\textsf{\textbf{H}}` will produce $\textsf{\textbf{H}}$. The other versions do not stack, e.g., `\mathsf{\mathbf{H}}` will produce $\mathsf{\mathbf{H}}$.
 
diff --git a/src/Options.js b/src/Options.js
index b0d7fc51..cd164604 100644
--- a/src/Options.js
+++ b/src/Options.js
@@ -36,6 +36,10 @@ const sizeAtStyle = function(size: number, style: StyleInterface): number {
     return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
 };
 
+// In these types, "" (empty string) means "no change".
+export type FontWeight = "textbf" | "textmd" | "";
+export type FontShape = "textit" | "textup" | "";
+
 export type OptionsData = {
     style: StyleInterface;
     color?: string | void;
@@ -44,8 +48,8 @@ export type OptionsData = {
     phantom?: boolean;
     font?: string;
     fontFamily?: string;
-    fontWeight?: string;
-    fontShape?: string;
+    fontWeight?: FontWeight;
+    fontShape?: FontShape;
     sizeMultiplier?: number;
     maxSize: number;
 };
@@ -68,8 +72,8 @@ class Options {
     // See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm
     font: string;
     fontFamily: string;
-    fontWeight: string;
-    fontShape: string;
+    fontWeight: FontWeight;
+    fontShape: FontShape;
     sizeMultiplier: number;
     maxSize: number;
     _fontMetrics: FontMetrics | void;
@@ -244,7 +248,7 @@ class Options {
     /**
      * Creates a new options object with the given font weight
      */
-    withTextFontWeight(fontWeight: string): Options {
+    withTextFontWeight(fontWeight: FontWeight): Options {
         return this.extend({
             fontWeight,
             font: "",
@@ -254,7 +258,7 @@ class Options {
     /**
      * Creates a new options object with the given font weight
      */
-    withTextFontShape(fontShape: string): Options {
+    withTextFontShape(fontShape: FontShape): Options {
         return this.extend({
             fontShape,
             font: "",
diff --git a/src/functions/text.js b/src/functions/text.js
index ad724526..a5fbe392 100644
--- a/src/functions/text.js
+++ b/src/functions/text.js
@@ -13,10 +13,12 @@ const textFontFamilies = {
 
 const textFontWeights = {
     "\\textbf": "textbf",
+    "\\textmd": "textmd",
 };
 
 const textFontShapes = {
     "\\textit": "textit",
+    "\\textup": "textup",
 };
 
 const optionsWithFont = (group, options) => {
@@ -39,9 +41,9 @@ defineFunction({
         // Font families
         "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal",
         // Font weights
-        "\\textbf",
+        "\\textbf", "\\textmd",
         // Font Shapes
-        "\\textit",
+        "\\textit", "\\textup",
     ],
     props: {
         numArgs: 1,


Test patch:
diff --git a/test/katex-spec.js b/test/katex-spec.js
index 800cd6b5..72598be0 100644
--- a/test/katex-spec.js
+++ b/test/katex-spec.js
@@ -1708,6 +1708,15 @@ describe("An HTML font tree-builder", function() {
         expect(markup).toContain("<span class=\"mord textit\">R</span>");
     });
 
+    it("should render \\textup{R} with the correct font", function() {
+        const markup1 = katex.renderToString(r`\textup{R}`);
+        expect(markup1).toContain("<span class=\"mord textup\">R</span>");
+        const markup2 = katex.renderToString(r`\textit{\textup{R}}`);
+        expect(markup2).toContain("<span class=\"mord textup\">R</span>");
+        const markup3 = katex.renderToString(r`\textup{\textit{R}}`);
+        expect(markup3).toContain("<span class=\"mord textit\">R</span>");
+    });
+
     it("should render \\text{R\\textit{S}T} with the correct fonts", function() {
         const markup = katex.renderToString(r`\text{R\textit{S}T}`);
         expect(markup).toContain("<span class=\"mord\">R</span>");
@@ -1720,6 +1729,15 @@ describe("An HTML font tree-builder", function() {
         expect(markup).toContain("<span class=\"mord textbf\">R</span>");
     });
 
+    it("should render \\textmd{R} with the correct font", function() {
+        const markup1 = katex.renderToString(r`\textmd{R}`);
+        expect(markup1).toContain("<span class=\"mord textmd\">R</span>");
+        const markup2 = katex.renderToString(r`\textbf{\textmd{R}}`);
+        expect(markup2).toContain("<span class=\"mord textmd\">R</span>");
+        const markup3 = katex.renderToString(r`\textmd{\textbf{R}}`);
+        expect(markup3).toContain("<span class=\"mord textbf\">R</span>");
+    });
+
     it("should render \\textsf{R} with the correct font", function() {
         const markup = katex.renderToString(r`\textsf{R}`);
         expect(markup).toContain("<span class=\"mord textsf\">R</span>");