Skip to content

Commit 59522d0

Browse files
committed
Raise an exception when find_tex_file fails to find a file.
The exception message is clearer for end users than downstream callers failing to `open()` a file named `""`. Also update the function's docstring. _tfmfile now never returns None (an exception would have been raised earlier by find_tex_file), so remove the corresponding branch.
1 parent 97ba9d4 commit 59522d0

File tree

6 files changed

+82
-51
lines changed

6 files changed

+82
-51
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
In the future, ``dviread.find_tex_file`` will raise a ``FileNotFoundError`` for missing files
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Previously, it would return an empty string in such cases. Raising an
4+
exception allows attaching a user-friendly message instead. During the
5+
transition period, a warning is raised.

lib/matplotlib/backends/backend_pdf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ def dviFontName(self, dvifont):
887887
if dvi_info is not None:
888888
return dvi_info.pdfname
889889

890-
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
890+
tex_font_map = dviread.PsfontsMap(dviread._find_tex_file('pdftex.map'))
891891
psfont = tex_font_map[dvifont.texname]
892892
if psfont.filename is None:
893893
raise ValueError(

lib/matplotlib/dviread.py

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -470,13 +470,12 @@ def _fnt_def_real(self, k, c, s, d, a, l):
470470
n = self.file.read(a + l)
471471
fontname = n[-l:].decode('ascii')
472472
tfm = _tfmfile(fontname)
473-
if tfm is None:
474-
raise FileNotFoundError("missing font metrics file: %s" % fontname)
475473
if c != 0 and tfm.checksum != 0 and c != tfm.checksum:
476474
raise ValueError('tfm checksum mismatch: %s' % n)
477-
478-
vf = _vffile(fontname)
479-
475+
try:
476+
vf = _vffile(fontname)
477+
except FileNotFoundError:
478+
vf = None
480479
self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf)
481480

482481
@_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1'))
@@ -938,9 +937,9 @@ def _parse_and_cache_line(self, line):
938937
if basename is None:
939938
basename = tfmname
940939
if encodingfile is not None:
941-
encodingfile = find_tex_file(encodingfile)
940+
encodingfile = _find_tex_file(encodingfile)
942941
if fontfile is not None:
943-
fontfile = find_tex_file(fontfile)
942+
fontfile = _find_tex_file(fontfile)
944943
self._parsed[tfmname] = PsFont(
945944
texname=tfmname, psname=basename, effects=effects,
946945
encoding=encodingfile, filename=fontfile)
@@ -992,21 +991,20 @@ def search(self, filename):
992991
self._proc.stdin.write(os.fsencode(filename) + b"\n")
993992
self._proc.stdin.flush()
994993
out = self._proc.stdout.readline().rstrip()
995-
return "" if out == b"nil" else os.fsdecode(out)
994+
return None if out == b"nil" else os.fsdecode(out)
996995

997996

998997
@lru_cache()
999998
@_api.delete_parameter("3.5", "format")
1000-
def find_tex_file(filename, format=None):
999+
def _find_tex_file(filename, format=None):
10011000
"""
1002-
Find a file in the texmf tree.
1001+
Find a file in the texmf tree using kpathsea_.
10031002
1004-
Calls :program:`kpsewhich` which is an interface to the kpathsea
1005-
library [1]_. Most existing TeX distributions on Unix-like systems use
1006-
kpathsea. It is also available as part of MikTeX, a popular
1007-
distribution on Windows.
1003+
The kpathsea library, provided by most existing TeX distributions, both
1004+
on Unix-like systems and on Windows (MikTeX), is invoked via a long-lived
1005+
luatex process if luatex is installed, or via kpsewhich otherwise.
10081006
1009-
*If the file is not found, an empty string is returned*.
1007+
.. _kpathsea: https://www.tug.org/kpathsea/
10101008
10111009
Parameters
10121010
----------
@@ -1016,10 +1014,10 @@ def find_tex_file(filename, format=None):
10161014
Could be e.g. 'tfm' or 'vf' to limit the search to that type of files.
10171015
Deprecated.
10181016
1019-
References
1020-
----------
1021-
.. [1] `Kpathsea documentation <http://www.tug.org/kpathsea/>`_
1022-
The library that :program:`kpsewhich` is part of.
1017+
Raises
1018+
------
1019+
FileNotFoundError
1020+
If the file is not found.
10231021
"""
10241022

10251023
# we expect these to always be ascii encoded, but use utf-8
@@ -1029,39 +1027,63 @@ def find_tex_file(filename, format=None):
10291027
if isinstance(format, bytes):
10301028
format = format.decode('utf-8', errors='replace')
10311029

1032-
if format is None:
1030+
try:
1031+
lk = _LuatexKpsewhich()
1032+
except FileNotFoundError:
1033+
lk = None # Fallback to directly calling kpsewhich, as below.
1034+
1035+
if lk and format is None:
1036+
path = lk.search(filename)
1037+
1038+
else:
1039+
if os.name == 'nt':
1040+
# On Windows only, kpathsea can use utf-8 for cmd args and output.
1041+
# The `command_line_encoding` environment variable is set to force
1042+
# it to always use utf-8 encoding. See Matplotlib issue #11848.
1043+
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1044+
'encoding': 'utf-8'}
1045+
else: # On POSIX, run through the equivalent of os.fsdecode().
1046+
kwargs = {'encoding': sys.getfilesystemencoding(),
1047+
'errors': 'surrogateescape'}
1048+
1049+
cmd = ['kpsewhich']
1050+
if format is not None:
1051+
cmd += ['--format=' + format]
1052+
cmd += [filename]
10331053
try:
1034-
lk = _LuatexKpsewhich()
1035-
except FileNotFoundError:
1036-
pass # Fallback to directly calling kpsewhich, as below.
1037-
else:
1038-
return lk.search(filename)
1039-
1040-
if os.name == 'nt':
1041-
# On Windows only, kpathsea can use utf-8 for cmd args and output.
1042-
# The `command_line_encoding` environment variable is set to force it
1043-
# to always use utf-8 encoding. See Matplotlib issue #11848.
1044-
kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1045-
'encoding': 'utf-8'}
1046-
else: # On POSIX, run through the equivalent of os.fsdecode().
1047-
kwargs = {'encoding': sys.getfilesystemencoding(),
1048-
'errors': 'surrogatescape'}
1049-
1050-
cmd = ['kpsewhich']
1051-
if format is not None:
1052-
cmd += ['--format=' + format]
1053-
cmd += [filename]
1054+
path = (cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1055+
.rstrip('\n'))
1056+
except (FileNotFoundError, RuntimeError):
1057+
path = None
1058+
1059+
if path:
1060+
return path
1061+
else:
1062+
raise FileNotFoundError(
1063+
f"Matplotlib's TeX implementation searched for a file named "
1064+
f"{filename!r} in your texmf tree, but could not find it")
1065+
1066+
1067+
# After the deprecation period elapses, delete this shim and rename
1068+
# _find_tex_file to find_tex_file everywhere.
1069+
@_api.delete_parameter("3.5", "format")
1070+
def find_tex_file(filename, format=None):
10541071
try:
1055-
result = cbook._check_and_log_subprocess(cmd, _log, **kwargs)
1056-
except (FileNotFoundError, RuntimeError):
1057-
return ''
1058-
return result.rstrip('\n')
1072+
return (_find_tex_file(filename, format) if format is not None else
1073+
_find_tex_file(filename))
1074+
except FileNotFoundError as exc:
1075+
_api.warn_deprecated(
1076+
"3.6", message=f"{exc.args[0]}; in the future, this will raise a "
1077+
f"FileNotFoundError.")
1078+
return ""
1079+
1080+
1081+
find_tex_file.__doc__ = _find_tex_file.__doc__
10591082

10601083

10611084
@lru_cache()
10621085
def _fontfile(cls, suffix, texname):
1063-
filename = find_tex_file(texname + suffix)
1064-
return cls(filename) if filename else None
1086+
return cls(_find_tex_file(texname + suffix))
10651087

10661088

10671089
_tfmfile = partial(_fontfile, Tfm, ".tfm")
@@ -1077,7 +1099,7 @@ def _fontfile(cls, suffix, texname):
10771099
parser.add_argument("dpi", nargs="?", type=float, default=None)
10781100
args = parser.parse_args()
10791101
with Dvi(args.filename, args.dpi) as dvi:
1080-
fontmap = PsfontsMap(find_tex_file('pdftex.map'))
1102+
fontmap = PsfontsMap(_find_tex_file('pdftex.map'))
10811103
for page in dvi:
10821104
print(f"=== new page === "
10831105
f"(w: {page.width}, h: {page.height}, d: {page.descent})")

lib/matplotlib/testing/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,8 @@ def _check_for_pgf(texsystem):
7878

7979

8080
def _has_tex_package(package):
81-
return bool(mpl.dviread.find_tex_file(f"{package}.sty"))
81+
try:
82+
mpl.dviread._find_tex_file(f"{package}.sty")
83+
return True
84+
except FileNotFoundError:
85+
return False

lib/matplotlib/tests/test_dviread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
def test_PsfontsMap(monkeypatch):
10-
monkeypatch.setattr(dr, 'find_tex_file', lambda x: x)
10+
monkeypatch.setattr(dr, '_find_tex_file', lambda x: x)
1111

1212
filename = str(Path(__file__).parent / 'baseline_images/dviread/test.map')
1313
fontmap = dr.PsfontsMap(filename)

lib/matplotlib/textpath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
279279
@staticmethod
280280
@functools.lru_cache(50)
281281
def _get_ps_font_and_encoding(texname):
282-
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
282+
tex_font_map = dviread.PsfontsMap(dviread._find_tex_file('pdftex.map'))
283283
psfont = tex_font_map[texname]
284284
if psfont.filename is None:
285285
raise ValueError(

0 commit comments

Comments
 (0)