From 44a6337669c29361cbb7dd6b743bea1fc3cb2539 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:49:37 +0530 Subject: [PATCH] Implement underline logic as in TeX --- .../next_whats_new/underline-23616.rst | 12 ++++++++++++ lib/matplotlib/_mathtext.py | 18 ++++++++++++++++++ .../test_mathtext/mathtext1_dejavusans_09.png | Bin 0 -> 2548 bytes lib/matplotlib/tests/test_mathtext.py | 1 + 4 files changed, 31 insertions(+) create mode 100644 doc/release/next_whats_new/underline-23616.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png diff --git a/doc/release/next_whats_new/underline-23616.rst b/doc/release/next_whats_new/underline-23616.rst new file mode 100644 index 000000000000..184c588b842c --- /dev/null +++ b/doc/release/next_whats_new/underline-23616.rst @@ -0,0 +1,12 @@ +Underlining text while using Mathtext +------------------------------------- + +Mathtext now supports the ``\underline`` command. + +.. code-block:: python + + import matplotlib.pyplot as plt + + plt.text(0.4, 0.7, r'This is $\underline{underlined}$ text.') + plt.text(0.4, 0.3, r'So is $\underline{\mathrm{this}}$.') + plt.show() diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 21ec24d73286..b04386e7666a 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2207,6 +2207,8 @@ def csnames(group: str, names: Iterable[str]) -> Regex: p.overline = cmd(r"\overline", p.required_group("body")) + p.underline = cmd(r"\underline", p.required_group("body")) + p.overset = cmd( r"\overset", p.optional_group("annotation") + p.optional_group("body")) @@ -2259,6 +2261,7 @@ def csnames(group: str, names: Iterable[str]) -> Regex: | p.underset | p.sqrt | p.overline + | p.underline | p.text | p.boldsymbol | p.substack @@ -2945,6 +2948,21 @@ def overline(self, toks: ParseResults) -> T.Any: hlist = Hlist([rightside]) return [hlist] + def underline(self, toks: ParseResults) -> T.Any: + body = toks["body"] + state = self.get_state() + thickness = state.get_current_underline_thickness() + # Place the underline below `body` (node735). + vlist = Vlist([ + Hlist([body]), + Kern(3 * thickness), + Hrule(state, thickness), + ]) + delta = vlist.height + vlist.depth + thickness + vlist.height = body.height + vlist.depth = delta - vlist.height + return [Hlist([vlist])] + def _auto_sized_delimiter(self, front: str, middle: list[Box | Char | str], back: str) -> T.Any: diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png new file mode 100644 index 0000000000000000000000000000000000000000..9d96e8eb87b9c411c40faad7d8cc67ea20306a10 GIT binary patch literal 2548 zcmVV>bE;TMNE@WkPF*P|iF)L?dHfCjIH#atBE@Uz= zGBz+YF)}P5XmoUNb2=|CZDDk9Y;SaIX<{yKa%V5m9hA2K000SaNLh0L01sgR01sgS zs6VG^000R?NklDQmZtjF5fU+D0g>b4s5K+K{D~q@Y!7|8(MhAbiREz?o8G2t6AFQ&4M@DSaD;j2G+kD)Ws=-y~?z;pqTLz_axpy#pE!zNFz>~0Ax*z3yyP*DGQgrKZ zpkQz@x@x~vSRD$7R1G(ctAh^#a8JN_AUXg(kQ^XeK7^@WX~07sCxC~$0mMx}chYNN zO+wovs)n0M%rpS18%F_*7zj@W0Bn^M=s6sq_x%8OjRIiwS~Oe7Pw>8D52}Wn$m!SS}ZURZ;-FFS*zyd20tvQm|+#4DDoaan58SlzWb6ir4EH>}KF6XvB z@4Oo{RI*`(SLDX$I&~e5CPnTUuws4BfD16q>%ORu7J5E~CRP%GvW}yPkpdvE>1=dc z4vAY*Ib3)usGJof+(c0-dROMtoFr`RWEU|h?K=w6j0G{9+@gE6>m4dol> zO*RdYZ^62A;S|$2c^}PuNdV|D9$SmvHsCWkh~`z#Qeet0=+=5>0yKF5dX3T=h(j~m zBhQ}C5h}eW0*nYj*Zv{+*86F=;`u!6l$D^HDupyaxfta&(p_taZ#`&9ta+5Lh0Nft zi`JWG%P};gx&aV4jnlFkV7nxv85a!DYb?6^LjWp0Uf>&v1$bM&Lo;{)!0Vo+B*|ug zz$kR4HrNKWyae1ih_X)IEuae(3k}htFUH`eH550W3=n)b4D3|~O!ull6F31NO*XJi z5&)w8ucG-~1W@j^7+t_rfMnUsW=R5=))I}i=`4VUyTdxk0=}2`(5;Z{_hc2oY~QN6 z5|6_GZ*)>G=xzovtX0gXE0L6Te&i_X9=3UatK%>`RK;^7r=UtW>#{& zBW)CiiXIMIBn9|EmZ8sQF7U2ISKZV{_UPsLc%Q;4C=i$F6&Zt@*1#MYd~fgqNrX+l zJ%)FB+wn>9is!1QM|twgp{QP(XO!vAu@^%e;Dxxowv0 zKr_$J0>n!KHH}^X7}*zxFbPM~NuCC$p8os{%jFubuZwy%+@SQ`$E|0~CF;mTrj zKAPm+6n%cE0;n-(*phdp<8XWzKw<(86<@~ZmH90O$csN*x!kh|%~yK?a?Isam~#O3 z9>rmZcRF^K&Mhy2Q-~iwW2F>veQmp93~pP)FXnGv*=Q_%9;6-;1hhdRdbXV}trCzfJY@}FHNZPe|@xPMh(mm7Dun%Z{AX0G+$ zP#?eWeex*~8{+HJ_W-c4@fX9Jd4y!+v{%+*3~pb8J1LNt!0Djxq7utrnoBOsYPPAe zG|KN3jPeUjwVe!@h|@|*!5CBxuITc6oUQ~;1#InG!Y0`PR5gx))hz};aG)T6YdQb=39whTkRbcez2s8`?2|({tZr0- zL8uyB@_MT(&<*Qsh3XK=AWc@G8_~}>N3MHd3Pz!7aLMK7J3#lL%sqQzZ0n|02n&e) z{>*_HJE2t+!1%5>WO=4y6siW-#0&$S?VaB3!#@4PNT6)Vz*~AwN`SNe8v*)E!{M1R z7>26BHRYWXamhLVbPPk);G*3FthnMy_uZ{Q)o|nHtiT0F2UTGlss>kC!cG7nbpYV< zkbDe8)!?$?2