pythonmatplotliblatextexpgf

Matplotlib legend not respecting content size with lualatex


I need to generate my matplotlib plots using lualatex instead of pdflatex. Among other things, I am using fontspec to change the document fonts. Below I am using this as an example and set lmroman10-regular.otf as the font. This creates a few issues. One is that the handles in the legends are not fully centered and are followed by some whitespace before the right border:

A big red arrow point at whitespace after the label text and before the right border of the legend.

The python code generating the intermediate .pgf file looks like this:

import matplotlib
import numpy

from matplotlib import pyplot

x = numpy.linspace(-1, 1)
y = x ** 2

matplotlib.rcParams["figure.figsize"] = (3, 2.5)
matplotlib.rcParams["font.family"] = "serif"
matplotlib.rcParams["font.size"] = 10
matplotlib.rcParams["legend.fontsize"] = 8
matplotlib.rcParams["pgf.texsystem"] = "lualatex"

PREAMBLE = r"""\usepackage{ifluatex}
\ifluatex
\usepackage{fontspec}
\setmainfont{lmroman10-regular.otf}
\fi
"""

matplotlib.rcParams["text.latex.preamble"] = PREAMBLE
matplotlib.rcParams["pgf.preamble"] = PREAMBLE
matplotlib.rcParams["text.usetex"] = True

pyplot.plot(x, y, label="this is the data")
pyplot.legend()
pyplot.xlabel("xlabel")
pyplot.tight_layout()
pyplot.savefig("lualatex_test.pgf")

The .pgf file is then embedded in a latex document. It seems it is not possible to directly compile to .pdf since then the document font will not be the selected font which can for example be seen by setting the font to someting more different like AntPoltExpd-Italic.otf. Also note note that the \ifluatex statement has to be added around the lualatex-only code since matplotlib uses pdflatex to determine the dimensions of text fragements, as will be seen below.

For the sake of this simple example, the .tex file to render the .pgf may be just:

\documentclass[11pt]{scrbook}
\usepackage{pgf}
\usepackage{fontspec}
\setmainfont{lmroman10-regular.otf}
\newcommand{\mathdefault}[1]{#1}
\begin{document}
\input{lualatex_test.pgf}
\end{document}

which can be typeset using lualatex <filename> and results in the figure shown above (without the red arrow).

I thought I had identified the reason for this but it seems I missed something. As mentioned above, matplotlib computes the dimensions of the text patches by actually placing them in a latex templated and compiling it using pdflatex. This happens in the matplotlib.texmanager.TexManager class in the corresponding file on the main branch for example. I thought I could fix it like this:


class TexManager:

    ...

    @classmethod
    def get_text_width_height_descent(cls, tex, fontsize, renderer=None):
        """Return width, height and descent of the text."""
        if tex.strip() == '':
            return 0, 0, 0
        dvifile = cls.make_dvi(tex, fontsize)
        dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
        with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
            page, = dvi
        # A total height (including the descent) needs to be returned.
        w = page.width
        # !!!
        if tex == "this is the data":
            w /= 1.14
            print("fixed width")
        # !!!
        return w, page.height + page.descent, page.descent

which, to my understanding, should trick matplotlib into thinking the text is shorter by a factor of 1.14 (just a guess, should be adapted once the solution works). The code definitely gets called, since "fixed width" gets printed. But the gap is not fixed:

Nearly the same as the first figure, without the arrow.

How can I fix this issue? How is matplotlib computing the legend content's width and can I maybe patch this to account for the correct width? Let's assume I know that the width error factor for the font size 8 is approximately 1.14. I can easily determine this for other fonts and font sizes.


Solution

  • It seems that when using custom font settings, you have to set the following rcParam:

    matplotlib.rcParams["pgf.rcfonts"] = False
    

    Otherwise, if I understand it correctly, the font settings are applied from the rcParams.

    This solves the spacing issue with your example:

    1

    For more details, see also the documentation of the .pgf backend where this parameter is exaplained.