From fe0b87c2d05c172b064c54da1d3ae33d028f3ef5 2019-08-10 21:59:32 From: Matthias Bussonnier Date: 2019-08-10 21:59:32 Subject: [PATCH] Merge pull request #11840 from oscargus/enablelatexfontcolor Enable changing the font color for LaTeX rendering --- diff --git a/IPython/lib/latextools.py b/IPython/lib/latextools.py index b56b360..cbcc7d9 100644 --- a/IPython/lib/latextools.py +++ b/IPython/lib/latextools.py @@ -10,6 +10,7 @@ import tempfile import shutil import subprocess from base64 import encodebytes +import textwrap from IPython.utils.process import find_cmd, FindCmdError from traitlets.config import get_config @@ -22,7 +23,7 @@ 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. " @@ -55,7 +56,8 @@ class LaTeXTool(SingletonConfigurable): ).tag(config=True) -def latex_to_png(s, encode=False, backend=None, wrap=False): +def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black', + scale=1.0): """Render a LaTeX string to PNG. Parameters @@ -68,6 +70,11 @@ def latex_to_png(s, encode=False, backend=None, wrap=False): 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. @@ -82,15 +89,25 @@ def latex_to_png(s, encode=False, backend=None, wrap=False): 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: + raise ValueError('Invalid color specification {}.'.format(color)) + else: + raise ValueError('Invalid color specification {}.'.format(color)) else: raise ValueError('No such backend {0}'.format(backend)) - bin_data = f(s, wrap) + 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): +def latex_to_png_mpl(s, wrap, color='Black', scale=1.0): try: from matplotlib import mathtext from pyparsing import ParseFatalException @@ -105,13 +122,14 @@ def latex_to_png_mpl(s, wrap): try: mt = mathtext.MathTextParser('bitmap') f = BytesIO() - mt.to_png(f, s, fontsize=12) + dpi = 120*scale + mt.to_png(f, s, fontsize=12, dpi=dpi, color=color) return f.getvalue() except (ValueError, RuntimeError, ParseFatalException): return None -def latex_to_png_dvipng(s, wrap): +def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): try: find_cmd('latex') find_cmd('dvipng') @@ -131,10 +149,11 @@ def latex_to_png_dvipng(s, wrap): ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], cwd=workdir, stdout=devnull, stderr=devnull) + resolution = round(150*scale) subprocess.check_call( - ["dvipng", "-T", "tight", "-x", "1500", "-z", "9", - "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir, - stdout=devnull, stderr=devnull) + ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9", + "-bg", "transparent", "-o", outfile, dvifile, "-fg", color], + cwd=workdir, stdout=devnull, stderr=devnull) with open(outfile, "rb") as f: return f.read() diff --git a/IPython/lib/tests/test_latextools.py b/IPython/lib/tests/test_latextools.py index 2772784..2cbdb17 100644 --- a/IPython/lib/tests/test_latextools.py +++ b/IPython/lib/tests/test_latextools.py @@ -62,7 +62,7 @@ def test_latex_to_png_mpl_runs(): @skipif_not_matplotlib def test_latex_to_html(): img = latextools.latex_to_html("$x^2$") - nt.assert_in("data:image/png;base64,iVBOR", img) + nt.assert_in("data:image/png;base64,iVBOR", img) def test_genelatex_no_wrap(): @@ -132,3 +132,50 @@ def test_genelatex_wrap_without_breqn(): \begin{document} $$x^2$$ \end{document}''') + + +@skipif_not_matplotlib +@onlyif_cmds_exist('latex', 'dvipng') +def test_latex_to_png_color(): + """ + Test color settings for latex_to_png. + """ + latex_string = "$x^2$" + default_value = latextools.latex_to_png(latex_string, wrap=False) + default_hexblack = latextools.latex_to_png(latex_string, wrap=False, + color='#000000') + dvipng_default = latextools.latex_to_png_dvipng(latex_string, False) + dvipng_black = latextools.latex_to_png_dvipng(latex_string, False, 'Black') + nt.assert_equal(dvipng_default, dvipng_black) + mpl_default = latextools.latex_to_png_mpl(latex_string, False) + mpl_black = latextools.latex_to_png_mpl(latex_string, False, 'Black') + nt.assert_equal(mpl_default, mpl_black) + nt.assert_in(default_value, [dvipng_black, mpl_black]) + nt.assert_in(default_hexblack, [dvipng_black, mpl_black]) + + # Test that dvips name colors can be used without error + dvipng_maroon = latextools.latex_to_png_dvipng(latex_string, False, + 'Maroon') + # And that it doesn't return the black one + nt.assert_not_equal(dvipng_black, dvipng_maroon) + + mpl_maroon = latextools.latex_to_png_mpl(latex_string, False, 'Maroon') + nt.assert_not_equal(mpl_black, mpl_maroon) + mpl_white = latextools.latex_to_png_mpl(latex_string, False, 'White') + mpl_hexwhite = latextools.latex_to_png_mpl(latex_string, False, '#FFFFFF') + nt.assert_equal(mpl_white, mpl_hexwhite) + + mpl_white_scale = latextools.latex_to_png_mpl(latex_string, False, + 'White', 1.2) + nt.assert_not_equal(mpl_white, mpl_white_scale) + + +def test_latex_to_png_invalid_hex_colors(): + """ + Test that invalid hex colors provided to dvipng gives an exception. + """ + latex_string = "$x^2$" + nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string, + backend='dvipng', color="#f00bar")) + nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string, + backend='dvipng', color="#f00"))