最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Matplotlib legend not respecting content size with lualatex - Stack Overflow

programmeradmin2浏览0评论

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:

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:

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.

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:

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:

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.

Share Improve this question asked Jan 18 at 1:22 HerpDerpingtonHerpDerpington 4,5035 gold badges32 silver badges50 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

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:

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

发布评论

评论列表(0)

  1. 暂无评论