diff options
| author | milde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2021-05-20 12:22:27 +0000 |
|---|---|---|
| committer | milde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2021-05-20 12:22:27 +0000 |
| commit | 2b81f7ddfedb4859cc4d545bc0aafed3f48c8cb8 (patch) | |
| tree | 6ea2a3fe2ee2816addb4ec4a751c5ba586d646a0 /docutils | |
| parent | a6e1d037d605f6d7340170c88372c2a66883c0a5 (diff) | |
| download | docutils-2b81f7ddfedb4859cc4d545bc0aafed3f48c8cb8.tar.gz | |
MathML: support more functions and symbols.
- Support emellished identifiers,
- support more functions and symbols from "amsmath",
- map HYPHEN-MINUS -> MINUS SIGN and COLON -> RATIO im math context,
- simplify doctests with starred import,
- dont wrap content in <mrow> when it is inferred by an element
(let these elements inherit from "mrow").
- tex2unichar: Fix/extend character mappings.
git-svn-id: https://svn.code.sf.net/p/docutils/code/trunk@8748 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
Diffstat (limited to 'docutils')
| -rw-r--r-- | docutils/docutils/utils/math/latex2mathml.py | 392 | ||||
| -rw-r--r-- | docutils/docutils/utils/math/tex2unichar.py | 53 | ||||
| -rw-r--r-- | docutils/test/functional/expected/math_output_mathml.html | 230 | ||||
| -rw-r--r-- | docutils/test/functional/input/data/comprehensive-math-test.txt | 200 |
4 files changed, 508 insertions, 367 deletions
diff --git a/docutils/docutils/utils/math/latex2mathml.py b/docutils/docutils/utils/math/latex2mathml.py index 5a3bce492..769ced771 100644 --- a/docutils/docutils/utils/math/latex2mathml.py +++ b/docutils/docutils/utils/math/latex2mathml.py @@ -22,7 +22,7 @@ # # Usage: # -# >>> import latex2mathml as l2m +# >>> from latex2mathml import * import collections import re @@ -54,12 +54,17 @@ greek_capitals = { functions = ['arccos', 'arcsin', 'arctan', 'arg', 'cos', 'cosh', 'cot', 'coth', 'csc', 'deg', 'det', 'dim', 'exp', 'gcd', 'hom', 'inf', 'ker', 'lg', - 'lim', 'liminf', 'limsup', 'ln', 'log', 'max', - 'min', 'Pr', 'sec', 'sin', 'sinh', 'sup', - 'tan', 'tanh', 'injlim', 'varinjlim', 'projlim', - 'varlimsup', 'varliminf', 'varprojlim'] - -# font selection -> <mi mathvariant=...> + 'lim', 'ln', 'log', 'max', 'min', 'Pr', + 'sec', 'sin', 'sinh', 'sup', 'tan', 'tanh'] +functions = dict((name, name) for name in functions) +functions.update({# functions with a space in the name + 'liminf': u'lim\u202finf', 'limsup': u'lim\u202fsup', + 'injlim': u'inj\u202flim', 'projlim': u'proj\u202flim', + # embellished function names (see handle_keyword() below) + 'varlimsup': 'lim', 'varliminf': 'lim', + 'varprojlim': 'lim', 'varinjlim': 'lim'}) + +# math font selection -> <mi mathvariant=...> or <mstyle mathvariant=...> math_alphabets = {# 'cmdname': 'mathvariant value' # package 'boldsymbol': 'bold', 'mathbb': 'double-struck', # amssymb @@ -79,19 +84,6 @@ math_alphabets = {# 'cmdname': 'mathvariant value' # package # bold-sans-serif } -# blackboar bold (Greek characters not working with "mathvariant" (Firefox 78) -mathbb = {u'Γ': u'\u213E', # ℾ - u'Π': u'\u213F', # ℿ - u'Σ': u'\u2140', # ⅀ - u'γ': u'\u213D', # ℽ - u'π': u'\u213C', # ℼ - r'\Gamma': u'\u213E', # ℾ - r'\Pi': u'\u213F', # ℿ - r'\Sigma': u'\u2140', # ⅀ - r'\gamma': u'\u213D', # ℽ - r'\pi': u'\u213C', # ℼ - } - # operator, fence, or separator -> <mo> operators = tex2unichar.mathbin # Binary symbols @@ -101,24 +93,58 @@ operators.update(tex2unichar.mathop) # Variable-sized symbols operators.update(tex2unichar.mathopen) # Braces operators.update(tex2unichar.mathclose) # Braces operators.update(tex2unichar.mathfence) - -# >>> '{' in l2m.operators.values() +operators.update({# negated symbols without pre-composed Unicode character + 'nleqq': u'\u2266\u0338', # ≦̸ + 'ngeqq': u'\u2267\u0338', # ≧̸ + 'nleqslant': u'\u2a7d\u0338', # ⩽̸ + 'ngeqslant': u'\u2a7e\u0338', # ⩾̸ + 'nsubseteqq': u'\u2AC5\u0338', # ⫅̸ + 'nsupseteqq': u'\u2AC6\u0338', # ⫆̸ + }) + +# >>> '{' in operators.values() # True # special cases +thick_operators = {# style='font-weight: bold;' + 'thicksim': u'\u223C', # ∼ + 'thickapprox':u'\u2248', # ≈ + } + +small_operators = {# mathsize='75%' + 'shortmid': u'\u2223', # ∣ + 'shortparallel': u'\u2225', # ∥ + 'nshortmid': u'\u2224', # ∤ + 'nshortparallel': u'\u2226', # ∦ + 'smallfrown': u'\u2322', # ⌢ FROWN + 'smallsmile': u'\u2323', # ⌣ SMILE + 'smallint': u'\u222b', # ∫ INTEGRAL + } + +left_delimiters = {# rspace='0', lspace='0.22em' + 'lvert': '|', + 'lVert': u'\u2016' # ‖ DOUBLE VERTICAL LINE + } + +right_delimiters = {# lspace='0', rspace='0.22em' + 'rvert': '|', + 'rVert': u'\u2016', # ‖ DOUBLE VERTICAL LINE + } + sumintprod = ''.join([operators[symbol] for symbol in ['sum', 'int', 'oint', 'prod']]) -# >>> print(l2m.sumintprod) +# >>> print(sumintprod) # ∑∫∮∏ -# +# pre-composed characters for negated symbols +# see https://www.w3.org/TR/xml-entity-names/#combining negatables = {'=': u'\u2260', r'\in': u'\u2209', r'\equiv': u'\u2262'} -# cmds/characters allowed in left/right cmds +# extensible delimiters allowed in left/right cmds stretchables = {'(': '(', ')': ')', '[': '[', @@ -147,7 +173,7 @@ for (key, value) in tex2unichar.mathclose.items(): # **tex2unichar.mathfence}.items(): # stretchables['\\'+key] = value -# >>> print(' '.join(sorted(set(l2m.stretchables.values())))) +# >>> print(' '.join(sorted(set(stretchables.values())))) # ( ) / [ \ ] { | } ‖ ↑ ↓ ↕ ⇑ ⇓ ⇕ ⌈ ⌉ ⌊ ⌋ ⌜ ⌝ ⌞ ⌟ ⟅ ⟆ ⟦ ⟧ ⟨ ⟩ ⟮ ⟯ ⦇ ⦈ @@ -171,33 +197,53 @@ spaces = {'qquad': '2em', # two \quad # accents -> <mover> # TeX spacing combining -over = {'acute': u'\u00B4', # u'\u0301', - 'bar': u'\u00AF', # u'\u0304', - 'breve': u'\u02D8', # u'\u0306', - 'check': u'\u02C7', # u'\u030C', - 'dot': u'\u02D9', # u'\u0307', - 'ddot': u'\u00A8', # u'\u0308', - 'dddot': u'\u20DB', +over = {'acute': u'´', # u'\u0301', + 'bar': u'-', # u'\u0304', + 'breve': u'˘', # u'\u0306', + 'check': u'ˇ', # u'\u030C', + 'dot': u'·', # u'\u0307', + 'ddot': u'··', # u'\u0308', + 'dddot': u'···', # u'\u20DB', 'grave': u'`', # u'\u0300', 'hat': u'^', # u'\u0302', - 'mathring': u'\u02DA', # u'\u030A', - 'overleftrightarrow': u'\u20e1', - # 'overline': # u'\u0305', - 'tilde': u'\u02DC', # u'\u0303', - 'vec': u'\u20D7'} + 'mathring': u'°', # u'\u030A', + 'tilde': u'\u223C', # u'\u0303', + 'vec': u'\u20D7', + } + +wideover = {'overbrace': u'\u23DE', # TOP CURLY BRACKET + 'overleftarrow': u'\u2190', + 'overleftrightarrow': u'\u2194', + 'overline': u'¯', + 'overrightarrow': u'\u2192', + 'widehat': u'^', + 'widetilde': u'~'} +wideunder = {'underbrace': u'\u23DF', + 'underleftarrow': u'\u2190', + 'underleftrightarrow': u'\u2194', + 'underline': u'_', + 'underrightarrow': u'\u2192'} + +# Character translations +# ---------------------- +# characters with preferred alternative in mathematical use +anomalous_chars = {'-': u'\u2212', # HYPHEN-MINUS -> MINUS SIGN + ':': u'\u2236', # COLON -> RATIO + } +# blackboard bold (Greek characters not working with "mathvariant" (Firefox 78) +mathbb = {u'Γ': u'\u213E', # ℾ + u'Π': u'\u213F', # ℿ + u'Σ': u'\u2140', # ⅀ + u'γ': u'\u213D', # ℽ + u'π': u'\u213C', # ℼ + r'\Gamma': u'\u213E', # ℾ + r'\Pi': u'\u213F', # ℿ + r'\Sigma': u'\u2140', # ⅀ + r'\gamma': u'\u213D', # ℽ + r'\pi': u'\u213C', # ℼ + } -# all supported math-characters: -#mathcharacters = dict(letters) -#mathcharacters.update(operators) -#mathcharacters.update(tex2unichar.space) -# -## >>> l2m.mathcharacters['alpha'] -## 'α' -## >>> l2m.mathcharacters['{'] -## '{' -## >>> l2m.mathcharacters['pm'] -## '±' # MathML element classes # ---------------------- @@ -205,8 +251,10 @@ over = {'acute': u'\u00B4', # u'\u0301', class math(object): """Base class for MathML elements.""" - nchildren = 1000000 - """Required/Supported number of children""" + nchildren = None + """Expected number of children or None""" + parent = None + """Parent node in MathML DOM tree.""" _level = 0 # indentation level (static class variable) def __init__(self, children=None, inline=None, **kwargs): @@ -216,7 +264,7 @@ class math(object): self.children = [] if children is not None: - if not isinstance(children, list): + if not isinstance(children, (list, tuple)): children = [children] for child in children: self.append(child) @@ -226,7 +274,6 @@ class math(object): self.attributes['xmlns'] = 'http://www.w3.org/1998/Math/MathML' if inline is False: self.attributes['display'] = 'block' - # self.attributes['displaystyle'] = 'true' # sort kwargs for predictable functional tests # as self.attributes.update(kwargs) does not keep order in Python < 3.6 for key in sorted(kwargs.keys()): @@ -242,8 +289,8 @@ class math(object): def full(self): """Room for more children?""" - - return len(self.children) >= self.nchildren + return (self.nchildren is not None + and len(self.children) >= self.nchildren) def append(self, child): """append(child) -> element @@ -255,7 +302,7 @@ class math(object): self.children.append(child) child.parent = self node = self - while node.full(): + while node is not None and node.full(): node = node.parent return node @@ -310,42 +357,55 @@ class math(object): last_child = child return xml -# >>> l2m.math(l2m.mn(2)) +# >>> math(mn(2)) # math(mn(2)) -# >>> l2m.math(l2m.mn(2)).xml() +# >>> math(mn(2)).xml() # ['<math>', '\n ', '<mn>', '2', '</mn>', '\n', '</math>'] # -# >>> l2m.math(id='eq3') +# >>> math(id='eq3') # math(id='eq3') -# >>> l2m.math(id='eq3').xml() +# >>> math(id='eq3').xml() # ['<math id="eq3">', '\n', '</math>'] # # use CLASS to get "class" in XML -# >>> l2m.math(CLASS='test') +# >>> math(CLASS='test') # math(CLASS='test') -# >>> l2m.math(CLASS='test').xml() +# >>> math(CLASS='test').xml() # ['<math class="test">', '\n', '</math>'] -# >>> l2m.math(inline=True) +# >>> math(inline=True) # math(xmlns='http://www.w3.org/1998/Math/MathML') -# >>> l2m.math(inline=True).xml() +# >>> math(inline=True).xml() # ['<math xmlns="http://www.w3.org/1998/Math/MathML">', '\n', '</math>'] -# >>> l2m.math(inline=False) +# >>> math(inline=False) # math(xmlns='http://www.w3.org/1998/Math/MathML', display='block') -# >>> l2m.math(inline=False).xml() +# >>> math(inline=False).xml() # ['<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">', '\n', '</math>'] -class mrow(math): pass +class mrow(math): + """Group sub-expressions as a horizontal row. + """ + def __init__(self, children=None, nchildren=None, **kwargs): + self.nchildren = nchildren + math.__init__(self, children) + self.attributes = kwargs -# >>> l2m.mrow(displaystyle='false') +# >>> mrow(displaystyle='false') # mrow(displaystyle='false') class mtable(math): pass -# >>> l2m.mtable(displaystyle='true') +# >>> mtable(displaystyle='true') # mtable(displaystyle='true') -# >>> l2m.math(l2m.mtable(displaystyle='true')).xml() +# >>> math(mtable(displaystyle='true')).xml() # ['<math>', '\n ', '<mtable displaystyle="true">', '\n ', '</mtable>', '\n', '</math>'] + + +# The elements <msqrt>, <mstyle>, <merror>, <mpadded>, <mphantom>, <menclose>, +# <mtd>, <mscarry>, and <math> treat their contents as a single inferred mrow +# formed from all their children. +class msqrt(mrow): pass +class mstyle(mrow): pass class mtr(mrow): pass class mtd(mrow): pass @@ -367,9 +427,9 @@ class mn(mx): pass class mo(mx): pass class mtext(mx): pass -# >>> l2m.mo(u'<') +# >>> mo(u'<') # mo('<') -# >>> l2m.mo(u'<').xml() +# >>> mo(u'<').xml() # ['<mo>', '<', '</mo>'] class msub(math): @@ -378,9 +438,6 @@ class msub(math): class msup(math): nchildren = 2 -class msqrt(math): - nchildren = 1 - class mroot(math): nchildren = 2 @@ -403,18 +460,11 @@ class msubsup(math): class mspace(math): nchildren = 0 -class mstyle(math): - def __init__(self, children=None, nchildren=None, **kwargs): - if nchildren is not None: - self.nchildren = nchildren - math.__init__(self, children) - self.attributes = kwargs - class mover(math): nchildren = 2 - def __init__(self, children=None, reversed=False): + def __init__(self, children=None, reversed=False, **kwargs): self.reversed = reversed - math.__init__(self, children) + math.__init__(self, children, **kwargs) def xml(self): if self.reversed: @@ -422,7 +472,10 @@ class mover(math): self.reversed = False return math.xml(self) -class munder(math): +# >>> mover(children=[mi('lim'), mo('-')], accent='false') +# mover(mi('lim'), mo('-'), accent='false') + +class munder(mover): nchildren = 2 class munderover(math): @@ -440,11 +493,11 @@ class munderover(math): def tex_cmdname(string): """Return leading TeX command name from `string`. - >>> l2m.tex_cmdname('name2') # up to first non-letter + >>> tex_cmdname('name2') # up to first non-letter ('name', '2') - >>> l2m.tex_cmdname('name 2') # strip trailing whitespace + >>> tex_cmdname('name 2') # strip trailing whitespace ('name', '2') - >>> l2m.tex_cmdname('_2') # single non-letter character + >>> tex_cmdname('_2') # single non-letter character ('_', '2') """ @@ -455,21 +508,15 @@ def tex_cmdname(string): # Test: # -# >>> l2m.tex_cmdname('name_2') # first non-letter terminates +# >>> tex_cmdname('name_2') # first non-letter terminates # ('name', '_2') -# >>> l2m.tex_cmdname(' next') # leading whitespace is returned +# >>> tex_cmdname(' next') # leading whitespace is returned # (' ', 'next') -# >>> l2m.tex_cmdname('1 2') # whitespace after non-letter is kept +# >>> tex_cmdname('1 2') # whitespace after non-letter is kept # ('1', ' 2') -# >>> l2m.tex_cmdname('') # empty string +# >>> tex_cmdname('') # empty string # ('', '') - -# TODO: check for Inferred <mrow>s: - -# The elements <msqrt>, <mstyle>, <merror>, <mpadded>, <mphantom>, <menclose>, -# <mtd, mscarry>, and <math> treat their contents as a single inferred mrow -# formed from all their children # # --- https://www.w3.org/TR/MathML3/chapter3.html#id.3.1.3.2 @@ -478,11 +525,11 @@ def tex_token(string): Return token and remainder. - >>> l2m.tex_token('{first simple group} {without brackets}') + >>> tex_token('{first simple group} {without brackets}') ('first simple group', ' {without brackets}') - >>> l2m.tex_token('\\command{without argument}') + >>> tex_token('\\command{without argument}') ('\\command', '{without argument}') - >>> l2m.tex_token(' first non-white character') + >>> tex_token(' first non-white character') ('f', 'irst non-white character') """ @@ -505,19 +552,19 @@ def tex_token(string): # Test: # -# >>> l2m.tex_token('{opening bracket of group with {nested group}}') +# >>> tex_token('{opening bracket of group with {nested group}}') # ('{', 'opening bracket of group with {nested group}}') -# >>> l2m.tex_token('{group with \\{escaped\\} brackets}') +# >>> tex_token('{group with \\{escaped\\} brackets}') # ('group with \\{escaped\\} brackets', '') -# >>> l2m.tex_token('{group followed by closing bracket}} from outer group') +# >>> tex_token('{group followed by closing bracket}} from outer group') # ('group followed by closing bracket', '} from outer group') -# >>> l2m.tex_token(' {skip leading whitespace}') +# >>> tex_token(' {skip leading whitespace}') # ('skip leading whitespace', '') -# >>> l2m.tex_token(' \\skip{leading whitespace}') +# >>> tex_token(' \\skip{leading whitespace}') # ('\\skip', '{leading whitespace}') -# >>> l2m.tex_token('\\skip whitespace after macro name') +# >>> tex_token('\\skip whitespace after macro name') # ('\\skip', 'whitespace after macro name') -# >>> l2m.tex_token('') # empty string. +# >>> tex_token('') # empty string. # ('', '') @@ -527,8 +574,8 @@ def parse_latex_math(string, inline=True): Return a MathML-tree parsed from `string`. Set `inline` to False for displayed math. - >>> l2m.parse_latex_math('\\alpha') - math(mrow(mi('α')), xmlns='http://www.w3.org/1998/Math/MathML') + >>> parse_latex_math('\\alpha') + math(mi('α'), xmlns='http://www.w3.org/1998/Math/MathML') """ @@ -537,8 +584,8 @@ def parse_latex_math(string, inline=True): # Set up: tree is the whole tree and node is the current element. if inline: - node = mrow() - tree = math(node, inline=True) + node = math(inline=True) + tree = node else: # block: emulate align* environment with a math table node = mtd() @@ -550,7 +597,7 @@ def parse_latex_math(string, inline=True): c, string = string[0], string[1:] if c == ' ': - continue + continue # whitespace is ignored in LaTeX math mode if c == '\\': # start of a LaTeX macro cmdname, string = tex_cmdname(string) @@ -561,7 +608,10 @@ def parse_latex_math(string, inline=True): node = node.append(mn(c)) elif c in "/()[]|": node = node.append(mo(c, stretchy='false')) - elif c in "+-*=<>,.!?':;@": + # use dedicated mathematical operator characters + elif c in anomalous_chars: + node = node.append(mo(anomalous_chars[c])) + elif c in "+*=<>,.!?';@": node = node.append(mo(c)) elif c == '_': child = node.delete_child() @@ -603,11 +653,14 @@ def parse_latex_math(string, inline=True): # Test: -# >>> l2m.parse_latex_math(' \\sqrt{ \\alpha}') -# math(mrow(msqrt(mrow(mi('α')))), xmlns='http://www.w3.org/1998/Math/MathML') -# >>> l2m.parse_latex_math('\\alpha', inline=False) +# >>> parse_latex_math(' \\sqrt{ \\alpha}') +# math(msqrt(mi('α')), xmlns='http://www.w3.org/1998/Math/MathML') +# >>> parse_latex_math('\\alpha', inline=False) # math(mtable(mtr(mtd(mi('α'))), CLASS='align', displaystyle='true'), xmlns='http://www.w3.org/1998/Math/MathML', display='block') - +# >>> parse_latex_math('\\sqrt 2 \\ne 3') +# math(msqrt(mn('2')), mo('≠'), mn('3'), xmlns='http://www.w3.org/1998/Math/MathML') +# >>> parse_latex_math('\\sqrt{2 + 3} < 3') +# math(msqrt(mn('2'), mo('+'), mn('3')), mo('<'), mn('3'), xmlns='http://www.w3.org/1998/Math/MathML') def handle_keyword(name, node, string): """Process LaTeX macro `name` followed by `string`. @@ -615,9 +668,9 @@ def handle_keyword(name, node, string): If needed, parse `string` for macro argument. Return updated current node and remainder: - >>> l2m.handle_keyword('hbar', l2m.math(), r' \frac') + >>> handle_keyword('hbar', math(), r' \frac') (math(mi('ℏ')), ' \\frac') - >>> l2m.handle_keyword('hspace', l2m.math(), r'{1ex} (x)') + >>> handle_keyword('hspace', math(), r'{1ex} (x)') (math(mspace(width='1ex')), ' (x)') """ @@ -637,20 +690,31 @@ def handle_keyword(name, node, string): node = node.append(mi(letters[name])) return node, string - if name in functions: + if (name in functions or name == 'operatorname'): # use <mi> followed by invisible function applicator character # (see https://www.w3.org/TR/MathML3/chapter3.html#presm.mi) - node = node.append(mi(name)) - node = node.append(mo('⁡')) - return node, string - - if name == 'operatorname': - # custom function name ``\operatorname{abs}(x)`` - arg, string = tex_token(string) - node = node.append(mi(arg, mathvariant='normal')) + if name == 'operatorname': + # custom function name ``\operatorname{abs}(x)`` + arg, string = tex_token(string) + identifier = mi(arg, mathvariant='normal') + else: + identifier = mi(functions[name]) + # functions with embellished names + if name == 'varliminf': # \underline\lim + identifier = munder([identifier, mo(u'_')], accent='false') + elif name == 'varlimsup': # \overline\lim + identifier = mover([identifier, mo(u'¯')], accent='false') + elif name == 'varprojlim': # \underleftarrow\lim + identifier = munder([identifier, mo(u'\u2190')], accent='false') + elif name == 'varinjlim': # \underrightarrow\lim + identifier = munder([identifier, mo(u'\u2192')], accent='false') + + node = node.append(identifier) + # TODO: only add ApplyFunction when appropriate (not \sin ^2(x), say) node = node.append(mo('⁡')) # '\u2061' return node, string + if name in math_alphabets: arg, remainder = tex_token(string) if arg[0] == '\\': @@ -665,6 +729,8 @@ def handle_keyword(name, node, string): kwargs = {'style': 'font-weight: bold'} else: kwargs = {'mathvariant': math_alphabets[name]} + if name == 'mathscr': + kwargs['style'] = 'font-family: STIX' # alternative script letter shapes # one symbol (single letter, name, or number) if arg.isalpha(): @@ -685,10 +751,26 @@ def handle_keyword(name, node, string): # operator, fence, or separator -> <mo> - if name == 'colon': # "normal" colon, not binary operator + if name in left_delimiters: # opening delimiters + node = node.append(mo(left_delimiters[name], rspace='0')) + return node, string + + if name in right_delimiters: # closing delimiters + node = node.append(mo(right_delimiters[name], lspace='0', )) + return node, string + + if name == 'colon': # trailing punctuation, not binary relation node = node.append(mo(':', lspace='0', rspace='0.28em')) return node, string + if name in thick_operators: + node = node.append(mo(thick_operators[name], style='font-weight: bold')) + return node, string + + if name in small_operators: + node = node.append(mo(small_operators[name], mathsize='75%')) + return node, string + if name in operators: node = node.append(mo(operators[name])) return node, string @@ -714,7 +796,7 @@ def handle_keyword(name, node, string): try: node = node.append(mo(negatables[arg])) except KeyError: - raise SyntaxError(u'Expected something to negate: "\\not ..."!') + raise SyntaxError(u'"\\not: Cannot negate: %s!'%arg) return node, string # arbitrary text (usually comments) -> <mtext> @@ -741,7 +823,11 @@ def handle_keyword(name, node, string): # ================================== if name == 'sqrt': - sqrt = msqrt() + if string.startswith('{'): # argument is a group + string = string[1:] # mrow implied, skip opening bracket + sqrt = msqrt() + else: # no group, enclose only one element + sqrt = msqrt(nchildren=1) node.append(sqrt) return sqrt, string @@ -757,8 +843,17 @@ def handle_keyword(name, node, string): node = entry return node, string - if name in over: - ovr = mover(mo(over[name]), reversed=True) + if name in over or name in wideover: + try: + ovr = mover(mo(over[name]), reversed=True) + except KeyError: + ovr = mover(mo(wideover[name]), reversed=True, accent='false') + node.append(ovr) + node = ovr + return node, string + + if name in wideunder: + ovr = munder(mo(wideunder[name]), reversed=True, accent='false') node.append(ovr) node = ovr return node, string @@ -794,32 +889,35 @@ def handle_keyword(name, node, string): raise SyntaxError(u'Unknown LaTeX command: ' + name) -# >>> l2m.handle_keyword('left', l2m.math(), '[a\\right]') +# >>> handle_keyword('left', math(), '[a\\right]') # (mrow(mo('[')), 'a\\right]') -# >>> l2m.handle_keyword('left', l2m.math(), '. a)') # emtpy \left +# >>> handle_keyword('left', math(), '. a)') # emtpy \left # (mrow(), ' a)') -# >>> l2m.handle_keyword('left', l2m.math(), '\\uparrow a)') # cmd +# >>> handle_keyword('left', math(), '\\uparrow a)') # cmd # (mrow(mo('↑')), 'a)') -# >>> l2m.handle_keyword('not', l2m.math(), '\\equiv \\alpha)') # cmd +# >>> handle_keyword('not', math(), '\\equiv \\alpha)') # cmd # (math(mo('≢')), '\\alpha)') -# >>> l2m.handle_keyword('text', l2m.math(), '{ for } i>0') # group +# >>> handle_keyword('text', math(), '{ for } i>0') # group # (math(mtext(' for ')), ' i>0') -# >>> l2m.handle_keyword('text', l2m.math(), '{B}T') # group +# >>> handle_keyword('text', math(), '{B}T') # group # (math(mtext('B')), 'T') -# >>> l2m.handle_keyword('text', l2m.math(), '{number of apples}}') # group +# >>> handle_keyword('text', math(), '{number of apples}}') # group # (math(mtext('number of apples')), '}') -# >>> l2m.handle_keyword('text', l2m.math(), 'i \\sin(x)') # single char +# >>> handle_keyword('text', math(), 'i \\sin(x)') # single char # (math(mtext('i')), ' \\sin(x)') -# >>> l2m.handle_keyword('sin', l2m.math(), '(\\alpha)') +# >>> handle_keyword('sin', math(), '(\\alpha)') # (math(mi('sin'), mo('⁡')), '(\\alpha)') -# >>> l2m.handle_keyword('sin', l2m.math(), ' \\alpha') +# >>> handle_keyword('sin', math(), ' \\alpha') # (math(mi('sin'), mo('⁡')), ' \\alpha') -# >>> l2m.handle_keyword('operatorname', l2m.math(), '{abs}(x)') +# >>> handle_keyword('operatorname', math(), '{abs}(x)') # (math(mi('abs', mathvariant='normal'), mo('⁡')), '(x)') -# >>> l2m.handle_keyword('mathrm', l2m.math(), '\\alpha') +# >>> handle_keyword('mathrm', math(), '\\alpha') # (math(mi('α', mathvariant='normal')), '') -# >>> l2m.handle_keyword('mathrm', l2m.math(), '{out} = 3') +# >>> handle_keyword('mathrm', math(), '{out} = 3') # (math(mi('out', mathvariant='normal')), ' = 3') +# >>> handle_keyword('overline', math(), '{981}') +# (mover(mo('¯'), accent='false'), '{981}') + def tex2mathml(tex_math, inline=True): """Return string with MathML code corresponding to `tex_math`. diff --git a/docutils/docutils/utils/math/tex2unichar.py b/docutils/docutils/utils/math/tex2unichar.py index 434c0e8c3..84001d34a 100644 --- a/docutils/docutils/utils/math/tex2unichar.py +++ b/docutils/docutils/utils/math/tex2unichar.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf8 -*- # LaTeX math to Unicode symbols translation dictionaries. # Generated with ``write_tex2unichar.py`` from the data in @@ -7,30 +7,30 @@ # Includes commands from: wasysym, stmaryrd, mathdots, mathabx, esint, bbold, amsxtra, amsmath, amssymb, standard LaTeX mathaccent = { - 'acute': u'\u0301', # x́ COMBINING ACUTE ACCENT - 'bar': u'\u0304', # x̄ COMBINING MACRON - 'breve': u'\u0306', # x̆ COMBINING BREVE - 'check': u'\u030c', # x̌ COMBINING CARON + 'acute': u'\u0301', # ́ COMBINING ACUTE ACCENT + 'bar': u'\u0304', # ̄ COMBINING MACRON + 'breve': u'\u0306', # ̆ COMBINING BREVE + 'check': u'\u030c', # ̌ COMBINING CARON 'ddddot': u'\u20dc', # x⃜ COMBINING FOUR DOTS ABOVE 'dddot': u'\u20db', # x⃛ COMBINING THREE DOTS ABOVE - 'ddot': u'\u0308', # ẍ COMBINING DIAERESIS - 'dot': u'\u0307', # ẋ COMBINING DOT ABOVE - 'grave': u'\u0300', # x̀ COMBINING GRAVE ACCENT - 'hat': u'\u0302', # x̂ COMBINING CIRCUMFLEX ACCENT - 'mathring': u'\u030a', # x̊ COMBINING RING ABOVE - 'not': u'\u0338', # x̸ COMBINING LONG SOLIDUS OVERLAY + 'ddot': u'\u0308', # ̈ COMBINING DIAERESIS + 'dot': u'\u0307', # ̇ COMBINING DOT ABOVE + 'grave': u'\u0300', # ̀ COMBINING GRAVE ACCENT + 'hat': u'\u0302', # ̂ COMBINING CIRCUMFLEX ACCENT + 'mathring': u'\u030a', # ̊ COMBINING RING ABOVE + 'not': u'\u0338', # ̸ COMBINING LONG SOLIDUS OVERLAY 'overleftarrow': u'\u20d6', # x⃖ COMBINING LEFT ARROW ABOVE 'overleftrightarrow': u'\u20e1', # x⃡ COMBINING LEFT RIGHT ARROW ABOVE - 'overline': u'\u0305', # x̅ COMBINING OVERLINE + 'overline': u'\u0305', # ̅ COMBINING OVERLINE 'overrightarrow': u'\u20d7', # x⃗ COMBINING RIGHT ARROW ABOVE - 'tilde': u'\u0303', # x̃ COMBINING TILDE - 'underbar': u'\u0331', # x̱ COMBINING MACRON BELOW + 'tilde': u'\u0303', # ̃ COMBINING TILDE + 'underbar': u'\u0331', # ̱ COMBINING MACRON BELOW 'underleftarrow': u'\u20ee', # x⃮ COMBINING LEFT ARROW BELOW - 'underline': u'\u0332', # x̲ COMBINING LOW LINE + 'underline': u'\u0332', # ̲ COMBINING LOW LINE 'underrightarrow': u'\u20ef', # x⃯ COMBINING RIGHT ARROW BELOW 'vec': u'\u20d7', # x⃗ COMBINING RIGHT ARROW ABOVE - 'widehat': u'\u0302', # x̂ COMBINING CIRCUMFLEX ACCENT - 'widetilde': u'\u0303', # x̃ COMBINING TILDE + 'widehat': u'\u0302', # ̂ COMBINING CIRCUMFLEX ACCENT + 'widetilde': u'\u0303', # ̃ COMBINING TILDE } mathalpha = { 'Bbbk': u'\U0001d55c', # 𝕜 MATHEMATICAL DOUBLE-STRUCK SMALL K @@ -54,7 +54,7 @@ mathalpha = { 'chi': u'\u03c7', # χ GREEK SMALL LETTER CHI 'daleth': u'\u2138', # ℸ DALET SYMBOL 'delta': u'\u03b4', # δ GREEK SMALL LETTER DELTA - 'digamma': u'\u03dc', # Ϝ GREEK LETTER DIGAMMA + 'digamma': u'\u03dd', # ϝ GREEK SMALL LETTER DIGAMMA 'ell': u'\u2113', # ℓ SCRIPT SMALL L 'epsilon': u'\u03f5', # ϵ GREEK LUNATE EPSILON SYMBOL 'eta': u'\u03b7', # η GREEK SMALL LETTER ETA @@ -91,7 +91,7 @@ mathalpha = { 'varUpsilon': u'\U0001d6f6', # 𝛶 MATHEMATICAL ITALIC CAPITAL UPSILON 'varXi': u'\U0001d6ef', # 𝛯 MATHEMATICAL ITALIC CAPITAL XI 'varepsilon': u'\u03b5', # ε GREEK SMALL LETTER EPSILON - 'varkappa': u'\U0001d718', # 𝜘 MATHEMATICAL ITALIC KAPPA SYMBOL + 'varkappa': u'\u03f0', # ϰ GREEK KAPPA SYMBOL 'varphi': u'\u03c6', # φ GREEK SMALL LETTER PHI 'varpi': u'\u03d6', # ϖ GREEK PI SYMBOL 'varrho': u'\u03f1', # ϱ GREEK RHO SYMBOL @@ -110,10 +110,12 @@ mathbin = { 'amalg': u'\u2a3f', # ⨿ AMALGAMATION OR COPRODUCT 'ast': u'\u2217', # ∗ ASTERISK OPERATOR 'barwedge': u'\u22bc', # ⊼ NAND + 'bigcirc': u'\u25ef', # ◯ LARGE CIRCLE 'bigtriangledown': u'\u25bd', # ▽ WHITE DOWN-POINTING TRIANGLE 'bigtriangleup': u'\u25b3', # △ WHITE UP-POINTING TRIANGLE 'bindnasrepma': u'\u214b', # ⅋ TURNED AMPERSAND 'blacklozenge': u'\u29eb', # ⧫ BLACK LOZENGE + 'blacktriangle': u'\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE 'blacktriangledown': u'\u25be', # ▾ BLACK DOWN-POINTING SMALL TRIANGLE 'blacktriangleleft': u'\u25c2', # ◂ BLACK LEFT-POINTING SMALL TRIANGLE 'blacktriangleright': u'\u25b8', # ▸ BLACK RIGHT-POINTING SMALL TRIANGLE @@ -128,7 +130,7 @@ mathbin = { 'boxplus': u'\u229e', # ⊞ SQUARED PLUS 'boxslash': u'\u29c4', # ⧄ SQUARED RISING DIAGONAL SLASH 'boxtimes': u'\u22a0', # ⊠ SQUARED TIMES - 'bullet': u'\u2219', # ∙ BULLET OPERATOR + 'bullet': u'\u2022', # • BULLET 'cap': u'\u2229', # ∩ INTERSECTION 'cdot': u'\u22c5', # ⋅ DOT OPERATOR 'circ': u'\u2218', # ∘ RING OPERATOR @@ -320,9 +322,12 @@ mathord = { 'dasharrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW 'dashleftarrow': u'\u21e0', # ⇠ LEFTWARDS DASHED ARROW 'dashrightarrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW + 'diagdown': u'\u27cd', # ⟍ + 'diagup': u'\u27cb', # ⟋ 'diameter': u'\u2300', # ⌀ DIAMETER SIGN 'diamondsuit': u'\u2662', # ♢ WHITE DIAMOND SUIT 'earth': u'\u2641', # ♁ EARTH + 'emptyset': u'\u2205', # ∅ EMPTY SET 'exists': u'\u2203', # ∃ THERE EXISTS 'female': u'\u2640', # ♀ FEMALE SIGN 'flat': u'\u266d', # ♭ MUSIC FLAT SIGN @@ -333,6 +338,7 @@ mathord = { 'girl': u'\u2640', # ♀ FEMALE SIGN 'heartsuit': u'\u2661', # ♡ WHITE HEART SUIT 'infty': u'\u221e', # ∞ INFINITY + 'invdiameter': u'\u2349', # ⍉ APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH 'invneg': u'\u2310', # ⌐ REVERSED NOT SIGN 'jupiter': u'\u2643', # ♃ JUPITER 'ldots': u'\u2026', # … HORIZONTAL ELLIPSIS @@ -386,7 +392,7 @@ mathord = { 'twonotes': u'\u266b', # ♫ BEAMED EIGHTH NOTES 'uranus': u'\u2645', # ♅ URANUS 'varEarth': u'\u2641', # ♁ EARTH - 'varnothing': u'\u2205', # ∅ EMPTY SET + 'varnothing': u'\u2300', # ⌀ DIAMETER SIGN 'virgo': u'\u264d', # ♍ VIRGO 'wasylozenge': u'\u2311', # ⌑ SQUARE LOZENGE 'wasytherefore': u'\u2234', # ∴ THEREFORE @@ -397,7 +403,6 @@ mathover = { 'wideparen': u'\u23dc', # ⏜ TOP PARENTHESIS } mathradical = { - 'sqrt': u'\u221a', # √ SQUARE ROOT 'sqrt[3]': u'\u221b', # ∛ CUBE ROOT 'sqrt[4]': u'\u221c', # ∜ FOURTH ROOT } @@ -476,6 +481,7 @@ mathrel = { 'gg': u'\u226b', # ≫ MUCH GREATER-THAN 'ggcurly': u'\u2abc', # ⪼ DOUBLE SUCCEEDS 'ggg': u'\u22d9', # ⋙ VERY MUCH GREATER-THAN + 'gggtr': u'\u22d9', # ⋙ VERY MUCH GREATER-THAN 'gnapprox': u'\u2a8a', # ⪊ GREATER-THAN AND NOT APPROXIMATE 'gneq': u'\u2a88', # ⪈ GREATER-THAN AND SINGLE-LINE NOT EQUAL TO 'gneqq': u'\u2269', # ≩ GREATER-THAN BUT NOT EQUAL TO @@ -523,6 +529,7 @@ mathrel = { 'll': u'\u226a', # ≪ MUCH LESS-THAN 'llcurly': u'\u2abb', # ⪻ DOUBLE PRECEDES 'lll': u'\u22d8', # ⋘ VERY MUCH LESS-THAN + 'llless': u'\u22d8', # ⋘ VERY MUCH LESS-THAN 'lnapprox': u'\u2a89', # ⪉ LESS-THAN AND NOT APPROXIMATE 'lneq': u'\u2a87', # ⪇ LESS-THAN AND SINGLE-LINE NOT EQUAL TO 'lneqq': u'\u2268', # ≨ LESS-THAN BUT NOT EQUAL TO @@ -585,6 +592,7 @@ mathrel = { 'preccurlyeq': u'\u227c', # ≼ PRECEDES OR EQUAL TO 'preceq': u'\u2aaf', # ⪯ PRECEDES ABOVE SINGLE-LINE EQUALS SIGN 'precnapprox': u'\u2ab9', # ⪹ PRECEDES ABOVE NOT ALMOST EQUAL TO + 'precneqq': u'\u2ab5', # ⪵ PRECEDES ABOVE NOT EQUAL TO 'precnsim': u'\u22e8', # ⋨ PRECEDES BUT NOT EQUIVALENT TO 'precsim': u'\u227e', # ≾ PRECEDES OR EQUIVALENT TO 'propto': u'\u221d', # ∝ PROPORTIONAL TO @@ -623,6 +631,7 @@ mathrel = { 'succcurlyeq': u'\u227d', # ≽ SUCCEEDS OR EQUAL TO 'succeq': u'\u2ab0', # ⪰ SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN 'succnapprox': u'\u2aba', # ⪺ SUCCEEDS ABOVE NOT ALMOST EQUAL TO + 'succneqq': u'\u2ab6', # ⪶ SUCCEEDS ABOVE NOT EQUAL TO 'succnsim': u'\u22e9', # ⋩ SUCCEEDS BUT NOT EQUIVALENT TO 'succsim': u'\u227f', # ≿ SUCCEEDS OR EQUIVALENT TO 'supset': u'\u2283', # ⊃ SUPERSET OF diff --git a/docutils/test/functional/expected/math_output_mathml.html b/docutils/test/functional/expected/math_output_mathml.html index 5cb03d5a1..d5f95a3bf 100644 --- a/docutils/test/functional/expected/math_output_mathml.html +++ b/docutils/test/functional/expected/math_output_mathml.html @@ -14,31 +14,27 @@ <p>Docutils supports inline math with the prefix or postfix <span class="docutils literal">:math:</span> role specificator, <math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mi>n</mi><mo>!</mo><mo>+</mo><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo> - <msubsup> - <mi>x</mi><mi>n</mi><mn>2</mn> - </msubsup> - <mo stretchy="false">)</mo> - </mrow> + <mi>n</mi><mo>!</mo><mo>+</mo><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo> + <msubsup> + <mi>x</mi><mi>n</mi><mn>2</mn> + </msubsup> + <mo stretchy="false">)</mo> </math> and <math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <msub> - <mi>A</mi><mtext>c</mtext> - </msub> - <mo>=</mo> - <mfrac> - <mrow> - <mi>π</mi> - </mrow> - <mrow> - <mn>4</mn> - </mrow> - </mfrac> - <msup> - <mi>d</mi><mn>2</mn> - </msup> - </mrow> + <msub> + <mi>A</mi><mtext>c</mtext> + </msub> + <mo>=</mo> + <mfrac> + <mrow> + <mi>π</mi> + </mrow> + <mrow> + <mn>4</mn> + </mrow> + </mfrac> + <msup> + <mi>d</mi><mn>2</mn> + </msup> </math>, as well as displayed math via the <cite>math</cite> directive:</p> <div> @@ -132,9 +128,7 @@ See <a class="reference internal" href="#eq-m">eq:M</a> and <a class="reference </math> </div> <p>is <math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mo stretchy="false">|</mo><mi mathvariant="bold">M</mi><mo stretchy="false">|</mo><mo>=</mo><mi>a</mi><mi>d</mi><mo>-</mo><mi>b</mi><mi>c</mi> - </mrow> + <mo stretchy="false">|</mo><mi mathvariant="bold">M</mi><mo stretchy="false">|</mo><mo>=</mo><mi>a</mi><mi>d</mi><mo>−</mo><mi>b</mi><mi>c</mi> </math>.</p> <p>More than one display math block can be put in one math directive. For example, the following sum and integral with limits:</p> @@ -220,9 +214,7 @@ directives:</p> </math> </div> <p>with the <em>wave function</em> <math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mi class="capital-greek">Ψ</mi> - </mrow> + <mi class="capital-greek">Ψ</mi> </math>, describes how the quantum state of a physical system changes in time.</p> <dl> @@ -235,127 +227,103 @@ physical system changes in time.</p> </colgroup> <tbody> <tr><td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>a</mi> - </mrow> - <mo>´</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>a</mi> + </mrow> + <mo>´</mo> + </mover> </math> <span class="docutils literal">\acute{a}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>t</mi> - </mrow> - <mo>˙</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>t</mi> + </mrow> + <mo>·</mo> + </mover> </math> <span class="docutils literal">\dot{t}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>γ</mi> - </mrow> - <mo>^</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>γ</mi> + </mrow> + <mo>^</mo> + </mover> </math> <span class="docutils literal"><span class="pre">\hat{\gamma}</span></span></p></td> </tr> <tr><td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>a</mi> - </mrow> - <mo>`</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>a</mi> + </mrow> + <mo>`</mo> + </mover> </math> <span class="docutils literal">\grave{a}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>t</mi> - </mrow> - <mo>¨</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>t</mi> + </mrow> + <mo>··</mo> + </mover> </math> <span class="docutils literal">\ddot{t}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>α</mi> - </mrow> - <mo>˜</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>α</mi> + </mrow> + <mo>∼</mo> + </mover> </math> <span class="docutils literal"><span class="pre">\tilde{\alpha}</span></span></p></td> </tr> <tr><td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>x</mi> - </mrow> - <mo>˘</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>x</mi> + </mrow> + <mo>˘</mo> + </mover> </math> <span class="docutils literal">\breve{x}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>t</mi> - </mrow> - <mo>⃛</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>t</mi> + </mrow> + <mo>···</mo> + </mover> </math> <span class="docutils literal">\dddot{t}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>ı</mi> - </mrow> - <mo>⃗</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>ı</mi> + </mrow> + <mo>⃗</mo> + </mover> </math> <span class="docutils literal"><span class="pre">\vec{\imath}</span></span></p></td> </tr> <tr><td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>a</mi> - </mrow> - <mo>ˇ</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>a</mi> + </mrow> + <mo>ˇ</mo> + </mover> </math> <span class="docutils literal">\check{a}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>a</mi> - </mrow> - <mo>¯</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>a</mi> + </mrow> + <mo>-</mo> + </mover> </math> <span class="docutils literal">\bar{a}</span></p></td> <td><p><math xmlns="http://www.w3.org/1998/Math/MathML"> - <mrow> - <mover> - <mrow> - <mi>R</mi> - </mrow> - <mo>⃗</mo> - </mover> - </mrow> + <mover> + <mrow> + <mi>R</mi> + </mrow> + <mo>⃗</mo> + </mover> </math> <span class="docutils literal">\vec{R}</span></p></td> </tr> </tbody> @@ -403,7 +371,7 @@ physical system changes in time.</p> <munderover> <mo>∫</mo> <mrow> - <mo>-</mo><mo>∞</mo> + <mo>−</mo><mo>∞</mo> </mrow> <mrow> <mo>∞</mo> @@ -432,7 +400,7 @@ physical system changes in time.</p> <munderover> <mo>∫</mo> <mrow> - <mo>-</mo><mo>∞</mo> + <mo>−</mo><mo>∞</mo> </mrow> <mrow> <mo>∞</mo> @@ -480,7 +448,7 @@ physical system changes in time.</p> <msub> <mi>s</mi><mi>δ</mi> </msub> - <mo stretchy="false">(</mo><mi>x</mi><mo>-</mo><mi>x</mi><mo>'</mo><mo stretchy="false">)</mo> + <mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mi>x</mi><mo>'</mo><mo stretchy="false">)</mo> </mtd> </mtr> <mtr> @@ -498,7 +466,7 @@ physical system changes in time.</p> <msub> <mi>s</mi><mi>δ</mi> </msub> - <mo stretchy="false">(</mo><mi>x</mi><mo>-</mo><mi>x</mi><mo>'</mo><mo stretchy="false">)</mo><mi mathvariant="normal">d</mi><mi>x</mi><mo>'</mo> + <mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mi>x</mi><mo>'</mo><mo stretchy="false">)</mo><mi mathvariant="normal">d</mi><mi>x</mi><mo>'</mo> </mtd> </mtr> </mtable> @@ -516,7 +484,7 @@ physical system changes in time.</p> <mtable> <mtr> <mtd> - <mo>-</mo><mn>1</mn> + <mo>−</mo><mn>1</mn> </mtd> <mtd> <mi>x</mi><mo><</mo><mn>0</mn> diff --git a/docutils/test/functional/input/data/comprehensive-math-test.txt b/docutils/test/functional/input/data/comprehensive-math-test.txt index 4324709f1..a696789ef 100644 --- a/docutils/test/functional/input/data/comprehensive-math-test.txt +++ b/docutils/test/functional/input/data/comprehensive-math-test.txt @@ -136,23 +136,35 @@ Accents .. class:: colwidths-auto =========== ============= =========== ============= ============== ================ - `\acute{x}` ``\acute{x}`` `\dot{x}` ``\dot{x}`` `\hat{H}` ``\hat{H}`` - `\bar{v}` ``\bar{v}`` `\ddot{x}` ``\ddot{x}`` `\mathring{x}` ``\mathring{x}`` - `\breve{x}` ``\breve{x}`` `\dddot{x}` ``\dddot{x}`` `\tilde{n}` ``\tilde{n}`` - `\check{x}` ``\check{x}`` `\grave{x}` ``\grave{x}`` `\vec{R}` ``\vec{R}`` + `\acute{x}` ``\acute{x}`` `\dot{t}` ``\dot{t}`` `\hat{H}` ``\hat{H}`` + `\bar{v}` ``\bar{v}`` `\ddot{t}` ``\ddot{t}`` `\mathring{x}` ``\mathring{x}`` + `\breve{x}` ``\breve{x}`` `\dddot{t}` ``\dddot{t}`` `\tilde{n}` ``\tilde{n}`` + `\check{x}` ``\check{x}`` `\grave{x}` ``\grave{x}`` `\vec{x}` ``\vec{x}`` =========== ============= =========== ============= ============== ================ When adding an accent to an i or j in math, dotless variants can be obtained with ``\imath`` and ``\jmath``: `\bar \imath`, `\hat{\jmath}` +(MathML drops the dot automatically). + +For adornment that span multiple symbols, see `top and bottom +embellishments`_. + +Font switches +------------- + +TeX’s *math alphabets* correspond to the +:t:`mathematical alphanumeric symbols` block in Unicode and the +"mathvariant" `style attribute`__ in MathML. They are “to be used for +mathematical variables where style variations are important +semantically”. + +__ https://developer.mozilla.org/en-US/docs/Web/MathML/Attribute -alphabets ---------- .. class:: colwidths-auto =============== ============================ ========================== command example result =============== ============================ ========================== - ``\boldsymbol`` ``\boldsymbol{\alpha + 3}`` `\boldsymbol{\alpha + 3}` ``\mathbf`` ``\mathbf{r}^2=x^2+y^2+z^2`` `\mathbf{r}^2=x^2+y^2+z^2` ``\mathbb`` ``\mathbb{R \subset C}`` `\mathbb{R \subset C}` ``\mathcal`` ``\mathcal{F}f(x)`` `\mathcal{F}f(x)` @@ -160,14 +172,40 @@ alphabets ``\mathit`` ``\mathit{\Gamma}`` `\mathit{\Gamma}` ``\mathrm`` ``s_\mathrm{out}`` `s_\mathrm{out}` ``\mathsf`` ``\mathsf x`` `\mathsf x` - ``\mathtt`` ``\mathtt{0.12}`` `\mathtt{0.12}` + ``\mathtt`` ``\mathtt{0.12}`` `\mathtt{0.12}` =============== ============================ ========================== -.. with isomath: - 'mathbfit': 'bold-italic', # isomath - 'mathsfit': 'sans-serif-italic', # isomath - 'mathsfbfit': 'sans-serif-bold-italic', # isomath - with mathrsfs, ... 'mathscr' +Additional alphabets are defined in LaTeX packages, e.g. + +.. class:: colwidths-auto + + =========== ============= ====================== + TeX command LaTeX package MathML "mathvariant" + =========== ============= ====================== + mathbfit isomath_ bold-italic + mathsfit isomath_ sans-serif-italic + mathsfbfit isomath_ sans-serif-bold-italic + mathscr mathrsfs_ script + =========== ============= ====================== +.. _isomath: https://www.ctan.org/pkg/isomath +.. _mathrsfs: https://www.ctan.org/pkg/mathrsfs + +This can be used to typeset vector symbols in **bold** *italic* +in line with the International Standard [ISO-80000-2]. +``\mathbfit{r}^2=x^2+y^2+z^2`` becomes + +.. math:: \mathbfit{r}^2=x^2+y^2+z^2. + + + +In contrast to the math alphabet selectors, ``\boldsymbol`` only changes +the *font weight*. In LaTeX, it can be used to get a bold version of any +mathematical symbol (for other output formats, results are mixed): + +.. math:: + \boldsymbol{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R} + + Arrows @@ -201,27 +239,26 @@ Binary operators .. class:: colwidths-auto ================== ==================== ================= =================== ================== ==================== - `*` ``*`` `\circledast` ``\circledast`` `\odot` ``\odot`` - `+` ``+`` `\circledcirc` ``\circledcirc`` `\ominus` ``\ominus`` - `-` ``-`` `\circleddash` ``\circleddash`` `\oplus` ``\oplus`` - `:` ``:`` `\cup` ``\cup`` `\oslash` ``\oslash`` - `\Cap` ``\Cap`` `\curlyvee` ``\curlyvee`` `\otimes` ``\otimes`` - `\Cup` ``\Cup`` `\curlywedge` ``\curlywedge`` `\pm` ``\pm`` - `\amalg` ``\amalg`` `\dagger` ``\dagger`` `\rightthreetimes` ``\rightthreetimes`` - `\ast` ``\ast`` `\ddagger` ``\ddagger`` `\rtimes` ``\rtimes`` - `\bigcirc` ``\bigcirc`` `\diamond` ``\diamond`` `\setminus` ``\setminus`` - `\bigtriangledown` ``\bigtriangledown`` `\div` ``\div`` `\smallsetminus` ``\smallsetminus`` - `\bigtriangleup` ``\bigtriangleup`` `\divideontimes` ``\divideontimes`` `\sqcap` ``\sqcap`` - `\boxdot` ``\boxdot`` `\dotplus` ``\dotplus`` `\sqcup` ``\sqcup`` - `\boxminus` ``\boxminus`` `\doublebarwedge` ``\doublebarwedge`` `\star` ``\star`` - `\boxplus` ``\boxplus`` `\gtrdot` ``\gtrdot`` `\times` ``\times`` - `\boxtimes` ``\boxtimes`` `\intercal` ``\intercal`` `\triangleleft` ``\triangleleft`` - `\bullet` ``\bullet`` `\leftthreetimes` ``\leftthreetimes`` `\triangleright` ``\triangleright`` - `\cap` ``\cap`` `\lessdot` ``\lessdot`` `\uplus` ``\uplus`` - `\cdot` ``\cdot`` `\ltimes` ``\ltimes`` `\vee` ``\vee`` - `\centerdot` ``\centerdot`` `\mp` ``\mp`` `\veebar` ``\veebar`` - `\circ` ``\circ`` `\wedge` ``\wedge`` - .. `\wr` ``\wr`` + `*` ``*`` `\circledast` ``\circledast`` `\ominus` ``\ominus`` + `+` ``+`` `\circledcirc` ``\circledcirc`` `\oplus` ``\oplus`` + `-` ``-`` `\circleddash` ``\circleddash`` `\oslash` ``\oslash`` + `:` ``:`` `\cup` ``\cup`` `\otimes` ``\otimes`` + `\Cap` ``\Cap`` `\curlyvee` ``\curlyvee`` `\pm` ``\pm`` + `\Cup` ``\Cup`` `\curlywedge` ``\curlywedge`` `\rightthreetimes` ``\rightthreetimes`` + `\amalg` ``\amalg`` `\dagger` ``\dagger`` `\rtimes` ``\rtimes`` + `\ast` ``\ast`` `\ddagger` ``\ddagger`` `\setminus` ``\setminus`` + `\bigcirc` ``\bigcirc`` `\diamond` ``\diamond`` `\smallsetminus` ``\smallsetminus`` + `\bigtriangledown` ``\bigtriangledown`` `\div` ``\div`` `\sqcap` ``\sqcap`` + `\bigtriangleup` ``\bigtriangleup`` `\divideontimes` ``\divideontimes`` `\sqcup` ``\sqcup`` + `\boxdot` ``\boxdot`` `\dotplus` ``\dotplus`` `\star` ``\star`` + `\boxminus` ``\boxminus`` `\doublebarwedge` ``\doublebarwedge`` `\times` ``\times`` + `\boxplus` ``\boxplus`` `\gtrdot` ``\gtrdot`` `\triangleleft` ``\triangleleft`` + `\boxtimes` ``\boxtimes`` `\intercal` ``\intercal`` `\triangleright` ``\triangleright`` + `\bullet` ``\bullet`` `\leftthreetimes` ``\leftthreetimes`` `\uplus` ``\uplus`` + `\cap` ``\cap`` `\lessdot` ``\lessdot`` `\vee` ``\vee`` + `\cdot` ``\cdot`` `\ltimes` ``\ltimes`` `\veebar` ``\veebar`` + `\centerdot` ``\centerdot`` `\mp` ``\mp`` `\wedge` ``\wedge`` + `\circ` ``\circ`` `\odot` ``\odot`` `\wr` ``\wr`` ================== ==================== ================= =================== ================== ==================== @@ -264,7 +301,7 @@ Letterlike symbols `\mho` ``\mho`` `\complement` ``\complement`` `\gimel` ``\gimel`` `\hslash` ``\hslash`` `\Bbbk` ``\Bbbk`` `\Finv` ``\Finv`` `\daleth` ``\daleth`` `\imath` ``\imath`` `\ell` ``\ell`` `\Game` ``\Game`` `\nabla` ``\nabla`` `\partial` ``\partial`` -`\wp` ``\wp`` +`\wp` ``\wp`` ======= ========== ============= =============== ========= =========== ========== ============ @@ -365,7 +402,18 @@ Use ``.`` for "empty" delimiters: Top and bottom embellishments ----------------------------- -TODO +Visually similar to accents_ but generally applied to multiple symbols. + +.. class:: colwidths-auto + + ========================== ============================ =========================== ============================= + `\widetilde{abi}` ``\widetilde{abi}`` `\widehat{abi}` ``\widehat{abi}`` + `\overline{abi}` ``\overline{abi}`` `\underline{abi}` ``\underline{abi}`` + `\overbrace{abi}` ``\overbrace{abi}`` `\underbrace{abi}` ``\underbrace{abi}`` + `\overleftarrow{abi}` ``\overleftarrow{abi}`` `\underleftarrow{abi}` ``\underleftarrow{abi}`` + `\overrightarrow{abi}` ``\overrightarrow{abi}`` `\underrightarrow{abi}` ``\underrightarrow{abi}`` + `\overleftrightarrow{abi}` ``\overleftrightarrow{abi}`` `\underleftrightarrow{abi}` ``\underleftrightarrow{abi}`` + ========================== ============================ =========================== ============================= Extensible arrows ----------------- @@ -416,47 +464,65 @@ internal LaTeX2MathML * Shorthands for combined named operators - =============== ================= - `\liminf` ``\liminf`` - `\limsup` ``\limsup`` - `\injlim` ``\injlim`` - `\projlim` ``\projlim`` - `\varinjlim` ``\varinjlim`` - `\varliminf` ``\varliminf`` - `\varlimsup` ``\varlimsup`` - `\varprojlim` ``\varprojlim`` - =============== ================= + ========== ============ ============= =============== ================== + `\liminf` ``\liminf`` `\varliminf` ``\varliminf`` `\underline{\lim}` + `\limsup` ``\limsup`` `\varlimsup` ``\varlimsup`` `\overline{\lim}` + `\injlim` ``\injlim`` `varinjlim` ``\varinjlim`` + `\projlim` ``\projlim`` `varprojlim` ``\varprojlim`` + ========== ============ ============= =============== ================== * Implement ``\circledS``? (in short-math-guide.pdf but not in mathematical Unicode characters) - ``\widetilde{xxx}`` - ``\widehat{xxx}`` - Tests ========== +Font changes +------------ -LICR macros in different alphabets: +Math alphabet macros change the default alphabet ("mathvariant" in +MathML), leaving some symbols unchanged: -.. math:: +:normal: `abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R` +:mathrm: `\mathrm{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R}` +:mathit: `\mathit{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R}` +:mathsf: `\mathsf{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R}` +:mathbb: `\mathbb{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R}` +:mathbf: `\mathbf{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R` - \text{normal: } & - abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar \\ - \text{mathrm: } & - \mathrm{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} \\ - \text{mathit: } & - \mathit{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} \\ - \text{mathsf: } & - \mathsf{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} \\ - \text{mathbb: } & - \mathbb{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} \\ - \text{mathbf: } & - \mathbf{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} \\ - \text{boldsymbol: } & - \boldsymbol{abs(x) \pm \alpha \approx \Gamma \forall x \in R \bigstar} - -All blackboard-bold characters: +Unicode supports the following blackboard-bold characters: `\mathbb{a \ldots z A \ldots Z 0 \ldots 9 \mathbb\Gamma \mathbb{\Pi} \mathbb {\Sigma}\mathbb\gamma \mathbb\pi}`. +The package mathrsfs_ (and some drop-in replacements) define the ``\mathscr`` +macro that selects a differently shaped "script" alphabet. +Compare `\mathscr{A, B, …, Z, a, b, …, z}` +with `\mathcal{A, B, …, Z, a, b, …, z}`. + +.. _rsfs: https://ctan.org/pkg/mathrsfs + +Inferred <mrow>s in MathML +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The elements <msqrt>, <mstyle>, <merror>, <mpadded>, <mphantom>, <menclose>, +<mtd, mscarry>, and <math> treat their contents as a single inferred mrow +formed from all their children. + +.. math:: a = \sqrt 2, b = \sqrt{1+x^2}, c = \sqrt\frac{sin(x)}{23} + +inline: :math:`a = \sqrt 2, b = \sqrt{1+x^2}, c = \sqrt\frac{sin(x)}{23}`. + +Accents vs. embellishments +-------------------------- + +MathML drops dots on "i" and "j" with accents: + +.. math:: \vec i \ne \overrightarrow i + \text{ and } \vec\lim \ne \overrightarrow\lim. + +Accents should be nearer to the base (in Firefox 78, it's vice versa!): + +.. math:: \vec a \vec l \ne \overrightarrow a \overrightarrow l + + \bar a \bar l \ne \overline a \overline l + |
