diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index d86385e94589..3c937d3fe224 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -18,7 +18,7 @@ Empty, Forward, Literal, NotAny, oneOf, OneOrMore, Optional, ParseBaseException, ParseException, ParseExpression, ParseFatalException, ParserElement, ParseResults, QuotedString, Regex, StringEnd, ZeroOrMore, - pyparsing_common) + pyparsing_common, Group) import matplotlib as mpl from . import cbook @@ -27,6 +27,11 @@ from .font_manager import FontProperties, findfont, get_font from .ft2font import FT2Image, KERNING_DEFAULT +from pyparsing import __version__ as pyparsing_version +if tuple(int(x) for x in pyparsing_version.split(".")) < (3, 0, 0): + from pyparsing import nestedExpr as nested_expr +else: + from pyparsing import nested_expr ParserElement.enablePackrat() _log = logging.getLogger("matplotlib.mathtext") @@ -1865,7 +1870,7 @@ def csnames(group, names): + r"|\\(?:{})(?![A-Za-z])".format( "|".join(map(re.escape, tex2uni))) )("sym").leaveWhitespace() - p.unknown_symbol = Regex(r"\\[A-Za-z]*")("name") + p.unknown_symbol = Regex(r"\\[A-Za-z]+")("name") p.font = csnames("font", self._fontnames) p.start_group = Optional(r"\math" + oneOf(self._fontnames)("font")) + "{" @@ -1925,6 +1930,11 @@ def csnames(group, names): p.text = cmd(r"\text", QuotedString('{', '\\', endQuoteChar="}")) + p.substack = cmd(r"\substack", + nested_expr(opener="{", closer="}", + content=Group(OneOrMore(p.token)) + + ZeroOrMore(Literal("\\\\").suppress()))("parts")) + p.subsuper = ( (Optional(p.placeable)("nucleus") + OneOrMore(oneOf(["_", "^"]) - p.placeable)("subsuper") @@ -1963,6 +1973,7 @@ def csnames(group, names): | p.overline | p.text | p.boldsymbol + | p.substack ) mdelim = r"\middle" - (p.delim("mdelim") | Error("Expected a delimiter")) @@ -2648,3 +2659,25 @@ def boldsymbol(self, s, loc, toks): self.pop_state() return Hlist(hlist) + + def substack(self, s, loc, toks): + parts = toks["parts"] + state = self.get_state() + thickness = state.get_current_underline_thickness() + vlist = [] + + hlist = [Hlist(k) for k in parts[0]] + max_width = max(map(lambda c: c.width, hlist)) + + for sub in hlist: + cp = HCentered([sub]) + cp.hpack(max_width, 'exactly') + vlist.append(cp) + + vlist = [val for pair in zip(vlist, + [Vbox(0, thickness * 2)] * + len(vlist)) for val in pair] + del vlist[-1] + vlt = Vlist(vlist) + result = [Hlist([vlt])] + return result diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png new file mode 100644 index 000000000000..565464062e39 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_08.png differ diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index dab981f29a23..f281b1e47412 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -137,6 +137,7 @@ r'$\boldsymbol{abcde} \boldsymbol{+} \boldsymbol{\Gamma + \Omega} \boldsymbol{01234} \boldsymbol{\alpha * \beta}$', r'$\left\lbrace\frac{\left\lbrack A^b_c\right\rbrace}{\left\leftbrace D^e_f \right\rbrack}\right\rightbrace\ \left\leftparen\max_{x} \left\lgroup \frac{A}{B}\right\rgroup \right\rightparen$', r'$\left( a\middle. b \right)$ $\left( \frac{a}{b} \middle\vert x_i \in P^S \right)$ $\left[ 1 - \middle| a\middle| + \left( x - \left\lfloor \dfrac{a}{b}\right\rfloor \right) \right]$', + r'$\sum_{\substack{k = 1\\ k \neq \lfloor n/2\rfloor}}^{n}P(i,j) \sum_{\substack{i \neq 0\\ -1 \leq i \leq 3\\ 1 \leq j \leq 5}} F^i(x,y) \sum_{\substack{\left \lfloor \frac{n}{2} \right\rfloor}} F(n)$', ] digits = "0123456789"