Description
Bug summary
I think in LaTeX, if \usepackage
is called multiple times for the same package, it is only actually loaded on the first call. Any desired options must be included in this first call. If a subsequent call contains different options, it will cause an "option clash" error. If a subsequent call contains no options, it will not cause this error and will silently be ignored.
Given the above mechanics, there is a bug in matplotlib. Matplotlib needs certain packages (hyperref
, geometry
, etc.), but there is no way to know if the user defined custom preamble (pgf.preamble
) will also includes these. Currently, matplotlib loads some of these packages before and some after the custom preamble.
To reproduce the bug, a custom preamble can be created that loads all of the packages that matplotlib depends on, but passes different options to them.
Code for reproduction
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.use('pgf')
preamble = '\n'.join([
r'\usepackage[no-math]{fontspec}',
r'\usepackage[nohyphen]{underscore}',
r'\usepackage[demo]{graphicx}',
r'\usepackage[draft]{hyperref}',
r'\usepackage[margin=1in]{geometry}',
r'\usepackage[draft]{pgf}',
r'\usepackage[ascii]{inputenc}',
r'\usepackage[safe]{textcomp}',
])
mpl.rcParams['pgf.preamble'] = preamble
# Define the x and y data
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]
# Create a figure and axis object
fig, ax = plt.subplots()
# Plot the data
ax.plot(x, y)
# Set the title and axis labels
ax.set_title('Line Plot')
ax.set_xlabel('X-axis')
ax.set_ylabel('Y-axis')
# Save the plot as a PNG file
plt.savefig('plot.png')
Actual outcome
Matplotlib crashes because the LaTeX compiler produces an option clash error.
Expected outcome
Matplotlib will use the user defined options, but override them when applicable.
Additional information
Solution:
This problem can be solved using the \PassOptionsToPackage
command. This command can contain the options that matplotlib requires and be called prior to the custom preamble in a pre-processing phase. Then after the custom preamble, the package can be loaded by calling \usepackage
without any options in a post-processing phase.
There are 2 possible outcomes:
- The custom preamble does not contain the package. In this case, the post-processing phase loads the package by appending the options stated in the pre-processing phase.
- The custom preamble contains the package. In this case, the package will be loaded in the custom preamble by appending the options stated in the pre-processing phase. Then the post-processing phase is silently ignored.
Note: When matplotlib loads packages after the custom preamble and without any options (such as fontspec
), no error will occur, so the code is fine as it is.
This issue is solved by #28167.
Operating system
Ubuntu
Matplotlib Version
3.7.5
Matplotlib Backend
pgf
Python version
3.8
Jupyter version
No response
Installation
pip