You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			243 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
r"""
 | 
						|
A role and directive to display mathtext in Sphinx
 | 
						|
==================================================
 | 
						|
 | 
						|
The ``mathmpl`` Sphinx extension creates a mathtext image in Matplotlib and
 | 
						|
shows it in html output. Thus, it is a true and faithful representation of what
 | 
						|
you will see if you pass a given LaTeX string to Matplotlib (see
 | 
						|
:ref:`mathtext`).
 | 
						|
 | 
						|
.. warning::
 | 
						|
    In most cases, you will likely want to use one of `Sphinx's builtin Math
 | 
						|
    extensions
 | 
						|
    <https://www.sphinx-doc.org/en/master/usage/extensions/math.html>`__
 | 
						|
    instead of this one. The builtin Sphinx math directive uses MathJax to
 | 
						|
    render mathematical expressions, and addresses accessibility concerns that
 | 
						|
    ``mathmpl`` doesn't address.
 | 
						|
 | 
						|
Mathtext may be included in two ways:
 | 
						|
 | 
						|
1. Inline, using the role::
 | 
						|
 | 
						|
     This text uses inline math: :mathmpl:`\alpha > \beta`.
 | 
						|
 | 
						|
   which produces:
 | 
						|
 | 
						|
     This text uses inline math: :mathmpl:`\alpha > \beta`.
 | 
						|
 | 
						|
2. Standalone, using the directive::
 | 
						|
 | 
						|
     Here is some standalone math:
 | 
						|
 | 
						|
     .. mathmpl::
 | 
						|
 | 
						|
         \alpha > \beta
 | 
						|
 | 
						|
   which produces:
 | 
						|
 | 
						|
     Here is some standalone math:
 | 
						|
 | 
						|
     .. mathmpl::
 | 
						|
 | 
						|
         \alpha > \beta
 | 
						|
 | 
						|
Options
 | 
						|
-------
 | 
						|
 | 
						|
The ``mathmpl`` role and directive both support the following options:
 | 
						|
 | 
						|
fontset : str, default: 'cm'
 | 
						|
    The font set to use when displaying math. See :rc:`mathtext.fontset`.
 | 
						|
 | 
						|
fontsize : float
 | 
						|
    The font size, in points. Defaults to the value from the extension
 | 
						|
    configuration option defined below.
 | 
						|
 | 
						|
Configuration options
 | 
						|
---------------------
 | 
						|
 | 
						|
The mathtext extension has the following configuration options:
 | 
						|
 | 
						|
mathmpl_fontsize : float, default: 10.0
 | 
						|
    Default font size, in points.
 | 
						|
 | 
						|
mathmpl_srcset : list of str, default: []
 | 
						|
    Additional image sizes to generate when embedding in HTML, to support
 | 
						|
    `responsive resolution images
 | 
						|
    <https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images>`__.
 | 
						|
    The list should contain additional x-descriptors (``'1.5x'``, ``'2x'``,
 | 
						|
    etc.) to generate (1x is the default and always included.)
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import hashlib
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
from docutils import nodes
 | 
						|
from docutils.parsers.rst import Directive, directives
 | 
						|
import sphinx
 | 
						|
from sphinx.errors import ConfigError, ExtensionError
 | 
						|
 | 
						|
import matplotlib as mpl
 | 
						|
from matplotlib import _api, mathtext
 | 
						|
from matplotlib.rcsetup import validate_float_or_None
 | 
						|
 | 
						|
 | 
						|
# Define LaTeX math node:
 | 
						|
class latex_math(nodes.General, nodes.Element):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
def fontset_choice(arg):
 | 
						|
    return directives.choice(arg, mathtext.MathTextParser._font_type_mapping)
 | 
						|
 | 
						|
 | 
						|
def math_role(role, rawtext, text, lineno, inliner,
 | 
						|
              options={}, content=[]):
 | 
						|
    i = rawtext.find('`')
 | 
						|
    latex = rawtext[i+1:-1]
 | 
						|
    node = latex_math(rawtext)
 | 
						|
    node['latex'] = latex
 | 
						|
    node['fontset'] = options.get('fontset', 'cm')
 | 
						|
    node['fontsize'] = options.get('fontsize',
 | 
						|
                                   setup.app.config.mathmpl_fontsize)
 | 
						|
    return [node], []
 | 
						|
math_role.options = {'fontset': fontset_choice,
 | 
						|
                     'fontsize': validate_float_or_None}
 | 
						|
 | 
						|
 | 
						|
class MathDirective(Directive):
 | 
						|
    """
 | 
						|
    The ``.. mathmpl::`` directive, as documented in the module's docstring.
 | 
						|
    """
 | 
						|
    has_content = True
 | 
						|
    required_arguments = 0
 | 
						|
    optional_arguments = 0
 | 
						|
    final_argument_whitespace = False
 | 
						|
    option_spec = {'fontset': fontset_choice,
 | 
						|
                   'fontsize': validate_float_or_None}
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        latex = ''.join(self.content)
 | 
						|
        node = latex_math(self.block_text)
 | 
						|
        node['latex'] = latex
 | 
						|
        node['fontset'] = self.options.get('fontset', 'cm')
 | 
						|
        node['fontsize'] = self.options.get('fontsize',
 | 
						|
                                            setup.app.config.mathmpl_fontsize)
 | 
						|
        return [node]
 | 
						|
 | 
						|
 | 
						|
# This uses mathtext to render the expression
 | 
						|
def latex2png(latex, filename, fontset='cm', fontsize=10, dpi=100):
 | 
						|
    with mpl.rc_context({'mathtext.fontset': fontset, 'font.size': fontsize}):
 | 
						|
        try:
 | 
						|
            depth = mathtext.math_to_image(
 | 
						|
                f"${latex}$", filename, dpi=dpi, format="png")
 | 
						|
        except Exception:
 | 
						|
            _api.warn_external(f"Could not render math expression {latex}")
 | 
						|
            depth = 0
 | 
						|
    return depth
 | 
						|
 | 
						|
 | 
						|
# LaTeX to HTML translation stuff:
 | 
						|
def latex2html(node, source):
 | 
						|
    inline = isinstance(node.parent, nodes.TextElement)
 | 
						|
    latex = node['latex']
 | 
						|
    fontset = node['fontset']
 | 
						|
    fontsize = node['fontsize']
 | 
						|
    name = 'math-{}'.format(
 | 
						|
        hashlib.sha256(
 | 
						|
            f'{latex}{fontset}{fontsize}'.encode(),
 | 
						|
            usedforsecurity=False,
 | 
						|
        ).hexdigest()[-10:])
 | 
						|
 | 
						|
    destdir = Path(setup.app.builder.outdir, '_images', 'mathmpl')
 | 
						|
    destdir.mkdir(parents=True, exist_ok=True)
 | 
						|
 | 
						|
    dest = destdir / f'{name}.png'
 | 
						|
    depth = latex2png(latex, dest, fontset, fontsize=fontsize)
 | 
						|
 | 
						|
    srcset = []
 | 
						|
    for size in setup.app.config.mathmpl_srcset:
 | 
						|
        filename = f'{name}-{size.replace(".", "_")}.png'
 | 
						|
        latex2png(latex, destdir / filename, fontset, fontsize=fontsize,
 | 
						|
                  dpi=100 * float(size[:-1]))
 | 
						|
        srcset.append(
 | 
						|
            f'{setup.app.builder.imgpath}/mathmpl/{filename} {size}')
 | 
						|
    if srcset:
 | 
						|
        srcset = (f'srcset="{setup.app.builder.imgpath}/mathmpl/{name}.png, ' +
 | 
						|
                  ', '.join(srcset) + '" ')
 | 
						|
 | 
						|
    if inline:
 | 
						|
        cls = ''
 | 
						|
    else:
 | 
						|
        cls = 'class="center" '
 | 
						|
    if inline and depth != 0:
 | 
						|
        style = 'style="position: relative; bottom: -%dpx"' % (depth + 1)
 | 
						|
    else:
 | 
						|
        style = ''
 | 
						|
 | 
						|
    return (f'<img src="{setup.app.builder.imgpath}/mathmpl/{name}.png"'
 | 
						|
            f' {srcset}{cls}{style}/>')
 | 
						|
 | 
						|
 | 
						|
def _config_inited(app, config):
 | 
						|
    # Check for srcset hidpi images
 | 
						|
    for i, size in enumerate(app.config.mathmpl_srcset):
 | 
						|
        if size[-1] == 'x':  # "2x" = "2.0"
 | 
						|
            try:
 | 
						|
                float(size[:-1])
 | 
						|
            except ValueError:
 | 
						|
                raise ConfigError(
 | 
						|
                    f'Invalid value for mathmpl_srcset parameter: {size!r}. '
 | 
						|
                    'Must be a list of strings with the multiplicative '
 | 
						|
                    'factor followed by an "x".  e.g. ["2.0x", "1.5x"]')
 | 
						|
        else:
 | 
						|
            raise ConfigError(
 | 
						|
                f'Invalid value for mathmpl_srcset parameter: {size!r}. '
 | 
						|
                'Must be a list of strings with the multiplicative '
 | 
						|
                'factor followed by an "x".  e.g. ["2.0x", "1.5x"]')
 | 
						|
 | 
						|
 | 
						|
def setup(app):
 | 
						|
    setup.app = app
 | 
						|
    app.add_config_value('mathmpl_fontsize', 10.0, True)
 | 
						|
    app.add_config_value('mathmpl_srcset', [], True)
 | 
						|
    try:
 | 
						|
        app.connect('config-inited', _config_inited)  # Sphinx 1.8+
 | 
						|
    except ExtensionError:
 | 
						|
        app.connect('env-updated', lambda app, env: _config_inited(app, None))
 | 
						|
 | 
						|
    # Add visit/depart methods to HTML-Translator:
 | 
						|
    def visit_latex_math_html(self, node):
 | 
						|
        source = self.document.attributes['source']
 | 
						|
        self.body.append(latex2html(node, source))
 | 
						|
 | 
						|
    def depart_latex_math_html(self, node):
 | 
						|
        pass
 | 
						|
 | 
						|
    # Add visit/depart methods to LaTeX-Translator:
 | 
						|
    def visit_latex_math_latex(self, node):
 | 
						|
        inline = isinstance(node.parent, nodes.TextElement)
 | 
						|
        if inline:
 | 
						|
            self.body.append('$%s$' % node['latex'])
 | 
						|
        else:
 | 
						|
            self.body.extend(['\\begin{equation}',
 | 
						|
                              node['latex'],
 | 
						|
                              '\\end{equation}'])
 | 
						|
 | 
						|
    def depart_latex_math_latex(self, node):
 | 
						|
        pass
 | 
						|
 | 
						|
    app.add_node(latex_math,
 | 
						|
                 html=(visit_latex_math_html, depart_latex_math_html),
 | 
						|
                 latex=(visit_latex_math_latex, depart_latex_math_latex))
 | 
						|
    app.add_role('mathmpl', math_role)
 | 
						|
    app.add_directive('mathmpl', MathDirective)
 | 
						|
    if sphinx.version_info < (1, 8):
 | 
						|
        app.add_role('math', math_role)
 | 
						|
        app.add_directive('math', MathDirective)
 | 
						|
 | 
						|
    metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
 | 
						|
    return metadata
 |