aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/lib/latextools.py
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/ipython/py3/IPython/lib/latextools.py
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/ipython/py3/IPython/lib/latextools.py')
-rw-r--r--contrib/python/ipython/py3/IPython/lib/latextools.py257
1 files changed, 257 insertions, 0 deletions
diff --git a/contrib/python/ipython/py3/IPython/lib/latextools.py b/contrib/python/ipython/py3/IPython/lib/latextools.py
new file mode 100644
index 0000000000..f2aa572884
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/lib/latextools.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+"""Tools for handling LaTeX."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from io import BytesIO, open
+import os
+import tempfile
+import shutil
+import subprocess
+from base64 import encodebytes
+import textwrap
+
+from pathlib import Path
+
+from IPython.utils.process import find_cmd, FindCmdError
+from traitlets.config import get_config
+from traitlets.config.configurable import SingletonConfigurable
+from traitlets import List, Bool, Unicode
+from IPython.utils.py3compat import cast_unicode
+
+
+class LaTeXTool(SingletonConfigurable):
+ """An object to store configuration of the LaTeX tool."""
+ def _config_default(self):
+ return get_config()
+
+ backends = List(
+ Unicode(), ["matplotlib", "dvipng"],
+ help="Preferred backend to draw LaTeX math equations. "
+ "Backends in the list are checked one by one and the first "
+ "usable one is used. Note that `matplotlib` backend "
+ "is usable only for inline style equations. To draw "
+ "display style equations, `dvipng` backend must be specified. ",
+ # It is a List instead of Enum, to make configuration more
+ # flexible. For example, to use matplotlib mainly but dvipng
+ # for display style, the default ["matplotlib", "dvipng"] can
+ # be used. To NOT use dvipng so that other repr such as
+ # unicode pretty printing is used, you can use ["matplotlib"].
+ ).tag(config=True)
+
+ use_breqn = Bool(
+ True,
+ help="Use breqn.sty to automatically break long equations. "
+ "This configuration takes effect only for dvipng backend.",
+ ).tag(config=True)
+
+ packages = List(
+ ['amsmath', 'amsthm', 'amssymb', 'bm'],
+ help="A list of packages to use for dvipng backend. "
+ "'breqn' will be automatically appended when use_breqn=True.",
+ ).tag(config=True)
+
+ preamble = Unicode(
+ help="Additional preamble to use when generating LaTeX source "
+ "for dvipng backend.",
+ ).tag(config=True)
+
+
+def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
+ scale=1.0):
+ """Render a LaTeX string to PNG.
+
+ Parameters
+ ----------
+ s : str
+ The raw string containing valid inline LaTeX.
+ encode : bool, optional
+ Should the PNG data base64 encoded to make it JSON'able.
+ backend : {matplotlib, dvipng}
+ Backend for producing PNG data.
+ wrap : bool
+ If true, Automatically wrap `s` as a LaTeX equation.
+ color : string
+ Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
+ format, e.g. '#AA20FA'.
+ scale : float
+ Scale factor for the resulting PNG.
+ None is returned when the backend cannot be used.
+
+ """
+ s = cast_unicode(s)
+ allowed_backends = LaTeXTool.instance().backends
+ if backend is None:
+ backend = allowed_backends[0]
+ if backend not in allowed_backends:
+ return None
+ if backend == 'matplotlib':
+ f = latex_to_png_mpl
+ elif backend == 'dvipng':
+ f = latex_to_png_dvipng
+ if color.startswith('#'):
+ # Convert hex RGB color to LaTeX RGB color.
+ if len(color) == 7:
+ try:
+ color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
+ textwrap.wrap(color[1:], 2)]))
+ except ValueError as e:
+ raise ValueError('Invalid color specification {}.'.format(color)) from e
+ else:
+ raise ValueError('Invalid color specification {}.'.format(color))
+ else:
+ raise ValueError('No such backend {0}'.format(backend))
+ bin_data = f(s, wrap, color, scale)
+ if encode and bin_data:
+ bin_data = encodebytes(bin_data)
+ return bin_data
+
+
+def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
+ try:
+ from matplotlib import figure, font_manager, mathtext
+ from matplotlib.backends import backend_agg
+ from pyparsing import ParseFatalException
+ except ImportError:
+ return None
+
+ # mpl mathtext doesn't support display math, force inline
+ s = s.replace('$$', '$')
+ if wrap:
+ s = u'${0}$'.format(s)
+
+ try:
+ prop = font_manager.FontProperties(size=12)
+ dpi = 120 * scale
+ buffer = BytesIO()
+
+ # Adapted from mathtext.math_to_image
+ parser = mathtext.MathTextParser("path")
+ width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
+ fig = figure.Figure(figsize=(width / 72, height / 72))
+ fig.text(0, depth / height, s, fontproperties=prop, color=color)
+ backend_agg.FigureCanvasAgg(fig)
+ fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
+ return buffer.getvalue()
+ except (ValueError, RuntimeError, ParseFatalException):
+ return None
+
+
+def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
+ try:
+ find_cmd('latex')
+ find_cmd('dvipng')
+ except FindCmdError:
+ return None
+
+ startupinfo = None
+ if os.name == "nt":
+ # prevent popup-windows
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+
+ try:
+ workdir = Path(tempfile.mkdtemp())
+ tmpfile = "tmp.tex"
+ dvifile = "tmp.dvi"
+ outfile = "tmp.png"
+
+ with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
+ f.writelines(genelatex(s, wrap))
+
+ subprocess.check_call(
+ ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
+ cwd=workdir,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ startupinfo=startupinfo,
+ )
+
+ resolution = round(150 * scale)
+ subprocess.check_call(
+ [
+ "dvipng",
+ "-T",
+ "tight",
+ "-D",
+ str(resolution),
+ "-z",
+ "9",
+ "-bg",
+ "Transparent",
+ "-o",
+ outfile,
+ dvifile,
+ "-fg",
+ color,
+ ],
+ cwd=workdir,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ startupinfo=startupinfo,
+ )
+
+ with workdir.joinpath(outfile).open("rb") as f:
+ return f.read()
+ except subprocess.CalledProcessError:
+ return None
+ finally:
+ shutil.rmtree(workdir)
+
+
+def kpsewhich(filename):
+ """Invoke kpsewhich command with an argument `filename`."""
+ try:
+ find_cmd("kpsewhich")
+ proc = subprocess.Popen(
+ ["kpsewhich", filename],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = proc.communicate()
+ return stdout.strip().decode('utf8', 'replace')
+ except FindCmdError:
+ pass
+
+
+def genelatex(body, wrap):
+ """Generate LaTeX document for dvipng backend."""
+ lt = LaTeXTool.instance()
+ breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
+ yield r'\documentclass{article}'
+ packages = lt.packages
+ if breqn:
+ packages = packages + ['breqn']
+ for pack in packages:
+ yield r'\usepackage{{{0}}}'.format(pack)
+ yield r'\pagestyle{empty}'
+ if lt.preamble:
+ yield lt.preamble
+ yield r'\begin{document}'
+ if breqn:
+ yield r'\begin{dmath*}'
+ yield body
+ yield r'\end{dmath*}'
+ elif wrap:
+ yield u'$${0}$$'.format(body)
+ else:
+ yield body
+ yield u'\\end{document}'
+
+
+_data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
+
+def latex_to_html(s, alt='image'):
+ """Render LaTeX to HTML with embedded PNG data using data URIs.
+
+ Parameters
+ ----------
+ s : str
+ The raw string containing valid inline LateX.
+ alt : str
+ The alt text to use for the HTML.
+ """
+ base64_data = latex_to_png(s, encode=True).decode('ascii')
+ if base64_data:
+ return _data_uri_template_png % (base64_data, alt)
+
+