diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 1a0f9219a498..9ad6d1de74d8 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -114,12 +114,12 @@ def glyph_name_or_index(self): charmap. """ # The last section is only true on luatex since luaotfload 3.23; this - # must be checked by the code generated by texmanager. (luaotfload's - # docs states "No one should rely on the mapping between DVI character - # codes and font glyphs [prior to v3.15] unless they tightly - # control all involved versions and are deeply familiar with the - # implementation", but a further mapping bug was fixed in luaotfload - # commit 8f2dca4, first included in v3.23). + # is checked by the code generated by texmanager. (luaotfload's docs + # states "No one should rely on the mapping between DVI character codes + # and font glyphs [prior to v3.15] unless they tightly control all + # involved versions and are deeply familiar with the implementation", + # but a further mapping bug was fixed in luaotfload commit 8f2dca4, + # first included in v3.23). entry = self._get_pdftexmap_entry() return (_parse_enc(entry.encoding)[self.glyph] if entry.encoding is not None else self.glyph) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 83e567a414c9..d01e08f02592 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -327,6 +327,7 @@ # zapf chancery, charter, serif, sans-serif, helvetica, # avant garde, courier, monospace, computer modern roman, # computer modern sans serif, computer modern typewriter +#text.latex.engine: latex #text.latex.preamble: # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 80d25659888e..4afebdb6bdf5 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1038,6 +1038,7 @@ def _convert_validator_spec(key, conv): # text props "text.color": validate_color, "text.usetex": validate_bool, + "text.latex.engine": ["latex", "xelatex", "lualatex"], "text.latex.preamble": validate_string, "text.hinting": ["default", "no_autohint", "force_autohint", "no_hinting", "auto", "native", "either", "none"], diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 35651a94aa85..0f3a2635805a 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -210,15 +210,32 @@ def _get_tex_source(cls, tex, fontsize): font_preamble, fontcmd = cls._get_font_preamble_and_command() baselineskip = 1.25 * fontsize return "\n".join([ + rf"% !TeX program = {mpl.rcParams['text.latex.engine']}", r"\RequirePackage{fix-cm}", r"\documentclass{article}", r"% Pass-through \mathdefault, which is used in non-usetex mode", r"% to use the default text font but was historically suppressed", r"% in usetex mode.", r"\newcommand{\mathdefault}[1]{#1}", - font_preamble, + r"\usepackage{iftex}", + r"\ifpdftex", r"\usepackage[utf8]{inputenc}", r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}", + font_preamble, + r"\fi", + r"\ifluatex", + r"\begingroup\catcode`\%=12\relax\gdef\percent{%}\endgroup", + r"\directlua{", + r" v = luaotfload.version", + r" major, minor = string.match(v, '(\percent d+).(\percent d+)')", + r" major = tonumber(major)", + r" minor = tonumber(minor) - (string.sub(v, -4) == '-dev' and .5 or 0)", + r" if major < 3 or major == 3 and minor < 23 then", + r" tex.error(string.format(", + r" 'luaotfload>=3.23 is required; you have \percent s', v))", + r" end", + r"}", + r"\fi", r"% geometry is loaded before the custom preamble as ", r"% convert_psfrags relies on a custom preamble to change the ", r"% geometry.", @@ -284,7 +301,9 @@ def make_dvi(cls, tex, fontsize): Return the file name. """ - dvipath = cls._get_base_path(tex, fontsize).with_suffix(".dvi") + ext = {"latex": "dvi", "xelatex": "xdv", "lualatex": "dvi"}[ + mpl.rcParams["text.latex.engine"]] + dvipath = cls._get_base_path(tex, fontsize).with_suffix(f".{ext}") if not dvipath.exists(): # Generate the tex and dvi in a temporary directory to avoid race # conditions e.g. if multiple processes try to process the same tex @@ -298,10 +317,15 @@ def make_dvi(cls, tex, fontsize): with TemporaryDirectory(dir=dvipath.parent) as tmpdir: Path(tmpdir, "file.tex").write_text( cls._get_tex_source(tex, fontsize), encoding='utf-8') + cmd = { + "latex": ["latex"], + "xelatex": ["xelatex", "-no-pdf"], + "lualatex": ["lualatex", "--output-format=dvi"], + }[mpl.rcParams["text.latex.engine"]] cls._run_checked_subprocess( - ["latex", "-interaction=nonstopmode", "--halt-on-error", + [*cmd, "-interaction=nonstopmode", "--halt-on-error", "file.tex"], tex, cwd=tmpdir) - Path(tmpdir, "file.dvi").replace(dvipath) + Path(tmpdir, f"file.{ext}").replace(dvipath) # Also move the tex source to the main cache directory, but # only for backcompat. Path(tmpdir, "file.tex").replace(dvipath.with_suffix(".tex"))