-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
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
[Bug]: Matplotlib not work with MiKTeX. #28212
Comments
from matplotlib import ticker
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'Times'
plt.figtext(.5, .5, "$-1$")
fig.savefig('test.pdf')
|
@anntzer Thanks so much for you reply.
matplotlib.dviread output:
|
Updates: I created a pure Windows 11 virtual machine. And install MiKTeX and Miniconda 3 from scratch. The bug can still be reproduced. Then, I tried TexLive, and it works. So the bug is that MiKTeX does not work well with Matplotlib? |
Possibly, though we should still try to fix that! I suspect this may be due to misconfiguration on your side (so perhaps the fix would be a better error message), but will need to investigate more (likely not in the very short term). |
For this, I created a virtual machine with VMware (to control variables, I'm even using the English Windows version instead of the locale one I'm on). Install MiKTex and Python with its official installer. VMware reproduce steps:
from matplotlib import ticker
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'Times'
# plt.set_loglevel("debug")
plt.figtext(.5, .5, "$-1$")
plt.savefig('test.pdf') I compare the dvi file generated by texlive and miktex. Here is the result:
TeXLive:
|
ax.xaxis.set_major_locator
will let matplotlib to throw error.
Thanks. I have repro'd the issue (you can also install miktex on mac/linux) and the fix is not obvious :/ |
OK, I think I figured this out. The short writeup is that zptmcm7y is a so-called Virtual Font (vf), which is basically a kind of dvi file where each glyph is represented by a dvi page where glyph(s) from other "real" font(s) are drawn. zptmcm7y.vf uses some glyphs from obscure "real" fonts (in this case, rsfs10.tfm) to represent some obscure glyphs, and these other fonts don't get installed by default. This is not needed by TeX & friends, likely because they only load these other glyphs on demand. On the contrary, Matplotlib's dvi parser (which is reused as a vf parser) tries to load everything immediately. This ultimately leads to trying to load rsfs10.tfm, which is not present, causes a FileNotFoundError, which bubbles out and makes Matplotlib believe wrongly that zptmcm7y.vf is absent. Matplotlib then interprets this as meaning that zptmcm7y is a real font itself, but the pdf renderer then fails to load it (as it indeed doesn't exist), hence the crash. The fix seems likely to involve turning the Vf parser into something that lazy-loads the glyphs on demand (we can't really do that for the Dvi parser itself I think), which is a bit annoying work but should probably(?) not involve big technical difficulties... |
Actually, looking at it again it wasn't that bad, the following patch fixes the issue, I believe (can you confirm?): diff --git i/lib/matplotlib/dviread.py w/lib/matplotlib/dviread.py
index 82f43b5629..7d61367fd6 100644
--- i/lib/matplotlib/dviread.py
+++ w/lib/matplotlib/dviread.py
@@ -230,6 +230,7 @@ class Dvi:
self.dpi = dpi
self.fonts = {}
self.state = _dvistate.pre
+ self._missing_font = None
def __enter__(self):
"""Context manager enter method, does nothing."""
@@ -337,6 +338,8 @@ class Dvi:
while True:
byte = self.file.read(1)[0]
self._dtable[byte](self, byte)
+ if self._missing_font:
+ raise self._missing_font
name = self._dtable[byte].__name__
if name == "_push":
down_stack.append(down_stack[-1])
@@ -364,11 +367,15 @@ class Dvi:
@_dispatch(min=0, max=127, state=_dvistate.inpage)
def _set_char_immediate(self, char):
self._put_char_real(char)
+ if isinstance(self.fonts[self.f], FileNotFoundError):
+ return
self.h += self.fonts[self.f]._width_of(char)
@_dispatch(min=128, max=131, state=_dvistate.inpage, args=('olen1',))
def _set_char(self, char):
self._put_char_real(char)
+ if isinstance(self.fonts[self.f], FileNotFoundError):
+ return
self.h += self.fonts[self.f]._width_of(char)
@_dispatch(132, state=_dvistate.inpage, args=('s4', 's4'))
@@ -382,7 +389,9 @@ class Dvi:
def _put_char_real(self, char):
font = self.fonts[self.f]
- if font._vf is None:
+ if isinstance(font, FileNotFoundError):
+ self._missing_font = font
+ elif font._vf is None:
self.text.append(Text(self.h, self.v, font, char,
font._width_of(char)))
else:
@@ -486,7 +495,16 @@ class Dvi:
def _fnt_def_real(self, k, c, s, d, a, l):
n = self.file.read(a + l)
fontname = n[-l:].decode('ascii')
- tfm = _tfmfile(fontname)
+ try:
+ tfm = _tfmfile(fontname)
+ except FileNotFoundError as exc:
+ # Explicitly allow defining missing fonts for Vf support; we only
+ # register an error when trying to load a glyph from a missing font
+ # and throw that error in Dvi._read. For Vf, _finalize_packet
+ # checks whether a missing glyph has been used, and in that case
+ # skips the glyph definition.
+ self.fonts[k] = exc
+ return
if c != 0 and tfm.checksum != 0 and c != tfm.checksum:
raise ValueError('tfm checksum mismatch: %s' % n)
try:
@@ -712,12 +730,14 @@ class Vf(Dvi):
self.h, self.v, self.w, self.x, self.y, self.z = 0, 0, 0, 0, 0, 0
self.stack, self.text, self.boxes = [], [], []
self.f = self._first_font
+ self._missing_font = None
return self.file.tell() + pl
def _finalize_packet(self, packet_char, packet_width):
- self._chars[packet_char] = Page(
- text=self.text, boxes=self.boxes, width=packet_width,
- height=None, descent=None)
+ if not self._missing_font: # Otherwise we don't have full glyph definition.
+ self._chars[packet_char] = Page(
+ text=self.text, boxes=self.boxes, width=packet_width,
+ height=None, descent=None)
self.state = _dvistate.outer
def _pre(self, i, x, cs, ds): (Overloading Dvi.fonts to also record exceptions is a bit ugly, but the quickest solution I found.) Assuming this works, turning this into a PR is left as an exercise to the reader. |
It did work now! Thanks so much. |
Bug summary
ax.xaxis.set_major_locator
will let matplotlib throw a font not found error. But if we removeax.xaxis.set_major_locator
, the error disappears.Code for reproduction
Actual outcome
Throws error:
Expected outcome
No error.
Additional information
MiKTeX version
Operating system
Windows
Matplotlib Version
3.8.4
Matplotlib Backend
TkAgg
Python version
Python 3.9.19
Jupyter version
No response
Installation
pip
The text was updated successfully, but these errors were encountered: