Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

PGF: Consistently set LaTeX document font size #26893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 7, 2024
34 changes: 27 additions & 7 deletions 34 lib/matplotlib/backends/backend_pgf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,55 @@
_create_pdf_info_dict, _datetime_to_pdf)
from matplotlib.path import Path
from matplotlib.figure import Figure
from matplotlib.font_manager import FontProperties
from matplotlib._pylab_helpers import Gcf

_log = logging.getLogger(__name__)


_DOCUMENTCLASS = r"\documentclass{article}"


Socob marked this conversation as resolved.
Show resolved Hide resolved
# Note: When formatting floating point values, it is important to use the
# %f/{:f} format rather than %s/{} to avoid triggering scientific notation,
# which is not recognized by TeX.

def _get_preamble():
"""Prepare a LaTeX preamble based on the rcParams configuration."""
font_size_pt = FontProperties(
size=mpl.rcParams["font.size"]
).get_size_in_points()
return "\n".join([
# Remove Matplotlib's custom command \mathdefault. (Not using
# \mathnormal instead since this looks odd with Computer Modern.)
r"\def\mathdefault#1{#1}",
# Use displaystyle for all math.
r"\everymath=\expandafter{\the\everymath\displaystyle}",
# Set up font sizes to match font.size setting.
# If present, use the KOMA package scrextend to adjust the standard
# LaTeX font commands (\tiny, ..., \normalsize, ..., \Huge) accordingly.
# Otherwise, only set \normalsize, manually.
r"\IfFileExists{scrextend.sty}{",
Socob marked this conversation as resolved.
Show resolved Hide resolved
r" \usepackage[fontsize=%fpt]{scrextend}" % font_size_pt,
r"}{",
r" \renewcommand{\normalsize}{\fontsize{%f}{%f}\selectfont}"
% (font_size_pt, 1.2 * font_size_pt),
r" \normalsize",
r"}",
# Allow pgf.preamble to override the above definitions.
mpl.rcParams["pgf.preamble"],
r"\ifdefined\pdftexversion\else % non-pdftex case.",
r" \usepackage{fontspec}",
*([
r"\ifdefined\pdftexversion\else % non-pdftex case.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the change here and immediately below?

Copy link
Contributor Author

@Socob Socob May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s 14a5900. I guess it’s only tangentially related to the rest of this PR, but I noticed that \usepackage{fontspec} was always included in the preamble, which is not necessarily desirable when doing custom font setup (i. e. pgf.rcfonts=False).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ability to exclude \usepackage{fontspec} is a very good improvement. It's sometimes necessary for consistency. From the documentation it sounds like fontspec should only be used when pgf.rcfonts=True, so this looks like an additional bugfix to me.

r" \usepackage{fontspec}",
] + [
r" \%s{%s}[Path=\detokenize{%s/}]"
% (command, path.name, path.parent.as_posix())
for command, path in zip(
["setmainfont", "setsansfont", "setmonofont"],
[pathlib.Path(fm.findfont(family))
for family in ["serif", "sans\\-serif", "monospace"]]
)
] if mpl.rcParams["pgf.rcfonts"] else []),
r"\fi",
] + [r"\fi"] if mpl.rcParams["pgf.rcfonts"] else []),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... i.e. here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

# Documented as "must come last".
mpl.texmanager._usepackage_if_not_loaded("underscore", option="strings"),
])
Expand Down Expand Up @@ -94,6 +112,8 @@ def _escape_and_apply_props(s, prop):
family = prop.get_family()[0]
if family in families:
commands.append(families[family])
elif not mpl.rcParams["pgf.rcfonts"]:
commands.append(r"\fontfamily{\familydefault}")
elif any(font.name == family for font in fm.fontManager.ttflist):
commands.append(
r"\ifdefined\pdftexversion\else\setmainfont{%s}\rmfamily\fi" % family)
Expand Down Expand Up @@ -185,7 +205,7 @@ class LatexManager:
@staticmethod
def _build_latex_header():
latex_header = [
r"\documentclass{article}",
Copy link
Contributor

@anntzer anntzer May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these could have been left as they were originally (but removing the 12pt below) no? especially considering that we may want to make the documentclass configurable in the future, which would be more awkward to do if referencing a global.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean? These all have to be identical, so introducing a single name for them is just DRY. And in fact one of the first things one would do when making the documentclass configurable. I don’t see how it matters whether it’s a global constant or something else – that can always be changed with no effort. The point is to use a single variable for things that have to be the same.

_DOCUMENTCLASS,
# Include TeX program name as a comment for cache invalidation.
# TeX does not allow this to be the first line.
rf"% !TeX program = {mpl.rcParams['pgf.texsystem']}",
Expand Down Expand Up @@ -814,7 +834,7 @@ def print_pdf(self, fname_or_fh, *, metadata=None, **kwargs):
self.print_pgf(tmppath / "figure.pgf", **kwargs)
(tmppath / "figure.tex").write_text(
"\n".join([
r"\documentclass[12pt]{article}",
_DOCUMENTCLASS,
r"\usepackage[pdfinfo={%s}]{hyperref}" % pdfinfo,
r"\usepackage[papersize={%fin,%fin}, margin=0in]{geometry}"
% (w, h),
Expand Down Expand Up @@ -924,7 +944,7 @@ def _write_header(self, width_inches, height_inches):
pdfinfo = ','.join(
_metadata_to_str(k, v) for k, v in self._info_dict.items())
latex_header = "\n".join([
r"\documentclass[12pt]{article}",
_DOCUMENTCLASS,
r"\usepackage[pdfinfo={%s}]{hyperref}" % pdfinfo,
r"\usepackage[papersize={%fin,%fin}, margin=0in]{geometry}"
% (width_inches, height_inches),
Expand Down
Binary file not shown.
24 changes: 24 additions & 0 deletions 24 lib/matplotlib/tests/test_backend_pgf.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,27 @@ def test_sketch_params():
# \pgfdecoratecurrentpath must be after the path definition and before the
# path is used (\pgfusepath)
assert baseline in buf


# test to make sure that the document font size is set consistently (see #26892)
@needs_pgf_xelatex
@pytest.mark.skipif(
not _has_tex_package('unicode-math'), reason='needs unicode-math.sty'
)
@pytest.mark.backend('pgf')
@image_comparison(['pgf_document_font_size.pdf'], style='default', remove_text=True)
def test_document_font_size():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. do you really need unicode-math for this test? I'd say it's not particularly relevant, no?
  2. you could do without the plot at all and simply use figtext() to draw text on an empty figure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Not unicode-math specifically, no. But if I remember correctly, unicode-math (and other math font packages) select the font variant they use at package load time based on the document font size, and these variants have different metrics even for the same font size, which is what caused the original issue. So I’d either need unicode-math or some random font package with the same behavior, and unicode-math seems much more universal (especially since matplotlib already uses it by default).
  2. I suppose I could. I just took the easiest path and used the example I already had in [Bug]: PGF font size mismatch between measurement and output #26892. Let me know if you really want me to make and test a new example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think I was mistaken with my comment that matplotlib uses unicode-math (I was thinking of fontspec). Nevertheless, the rest still applies.

mpl.rcParams.update({
'pgf.texsystem': 'xelatex',
'pgf.rcfonts': False,
'pgf.preamble': r'\usepackage{unicode-math}',
})
plt.figure()
plt.plot([],
label=r'$this is a very very very long math label a \times b + 10^{-3}$ '
r'and some text'
)
plt.plot([],
label=r'\normalsize the document font size is \the\fontdimen6\font'
)
plt.legend()
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.