Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7286e90

Browse files
committed
Use QuickLook instead of gs and inkscape on MacOS
qlmanage is built into MacOS since 10.5 and is faster to call than either of the alternatives
1 parent 1caf17b commit 7286e90

File tree

4 files changed

+74
-12
lines changed

4 files changed

+74
-12
lines changed

INSTALL.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ To run the test suite:
5555
directories from the source distribution.
5656
* install test dependencies: `pytest <https://pypi.org/project/pytest>`_,
5757
MiKTeX, GhostScript, ffmpeg, avconv, ImageMagick, and `Inkscape
58-
<https://inkscape.org/>`_.
58+
<https://inkscape.org/>`_. On MacOS, GhostScript and Inkscape are not
59+
needed.
5960
* run ``python -mpytest``.
6061

6162
Third-party distributions of Matplotlib
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Image comparisons use QuickLook on MacOS
2+
----------------------------------------
3+
4+
Previously, you had to install Inkscape and GhostScript to convert the
5+
svg and pdf files generated by the test suite into bitmap files. Now,
6+
we use MacOS's built-in QuickLook instead.

lib/matplotlib/__init__.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ def _get_executable_info(name):
268268
----------
269269
name : str
270270
The executable to query. The following values are currently supported:
271-
"dvipng", "gs", "inkscape", "magick", "pdftops". This list is subject
272-
to change without notice.
271+
"dvipng", "gs", "inkscape", "magick", "pdftops", "qlmanage". This list
272+
is subject to change without notice.
273273
274274
Returns
275275
-------
@@ -378,6 +378,19 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False):
378378
f"You have pdftops version {info.version} but the minimum "
379379
f"version supported by Matplotlib is 3.0")
380380
return info
381+
elif name == "qlmanage":
382+
try:
383+
subprocess.check_call(
384+
["qlmanage", "-h"],
385+
stdout=subprocess.DEVNULL,
386+
stderr=subprocess.DEVNULL
387+
)
388+
# qlmanage has no version number, use the OS version
389+
version = subprocess.check_output(["sw_vers", "-productVersion"])
390+
except (FileNotFoundError, subprocess.CalledProcessError):
391+
raise ExecutableNotFoundError()
392+
else:
393+
return _ExecInfo("qlmanage", version)
381394
else:
382395
raise ValueError("Unknown executable: {!r}".format(name))
383396

lib/matplotlib/testing/compare.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_cache_dir():
3636
return str(cache_dir)
3737

3838

39-
def get_file_hash(path, block_size=2 ** 20):
39+
def get_file_hash(path, block_size=2 ** 20, converter=None):
4040
md5 = hashlib.md5()
4141
with open(path, 'rb') as fd:
4242
while True:
@@ -45,11 +45,13 @@ def get_file_hash(path, block_size=2 ** 20):
4545
break
4646
md5.update(data)
4747

48-
if Path(path).suffix == '.pdf':
49-
md5.update(str(mpl._get_executable_info("gs").version)
48+
if converter is not None:
49+
md5.update(str(converter._version).encode('utf-8'))
50+
elif Path(path).suffix == '.pdf':
51+
md5.update(str(mpl._get_executable_info("gs")._version)
5052
.encode('utf-8'))
5153
elif Path(path).suffix == '.svg':
52-
md5.update(str(mpl._get_executable_info("inkscape").version)
54+
md5.update(str(mpl._get_executable_info("inkscape")._version)
5355
.encode('utf-8'))
5456

5557
return md5.hexdigest()
@@ -118,7 +120,34 @@ def _read_until(self, terminator):
118120
return bytes(buf[:-len(terminator)])
119121

120122

123+
class _QLConverter(_Converter):
124+
def __init__(self):
125+
super().__init__()
126+
self._proc = None
127+
self._tmpdir = TemporaryDirectory()
128+
self._tmppath = Path(self._tmpdir.name)
129+
self._version = mpl._get_executable_info("qlmanage").version
130+
131+
def __call__(self, orig, dest):
132+
try:
133+
# qlmanage does not follow symlinks so copy the file
134+
copied = str(self._tmppath / orig.name)
135+
shutil.copy(orig, copied)
136+
subprocess.check_call(
137+
["qlmanage", "-t", "-f4", "-o", self._tmpdir.name, copied],
138+
stdout=subprocess.DEVNULL,
139+
stderr=subprocess.DEVNULL
140+
)
141+
(self._tmppath / (orig.name + ".png")).rename(dest)
142+
except Exception as e:
143+
raise _ConverterError(e)
144+
145+
121146
class _GSConverter(_Converter):
147+
def __init__(self):
148+
super().__init__()
149+
self._version = mpl._get_executable_info("gs").version
150+
122151
def __call__(self, orig, dest):
123152
if not self._proc:
124153
self._proc = subprocess.Popen(
@@ -157,8 +186,12 @@ def encode_and_escape(name):
157186

158187

159188
class _SVGConverter(_Converter):
189+
def __init__(self):
190+
super().__init__()
191+
self._version = mpl._get_executable_info("inkscape").version
192+
160193
def __call__(self, orig, dest):
161-
old_inkscape = mpl._get_executable_info("inkscape").version < "1"
194+
old_inkscape = self._version < "1"
162195
terminator = b"\n>" if old_inkscape else b"> "
163196
if not hasattr(self, "_tmpdir"):
164197
self._tmpdir = TemporaryDirectory()
@@ -226,18 +259,26 @@ def __del__(self):
226259

227260

228261
def _update_converter():
262+
try:
263+
mpl._get_executable_info("qlmanage")
264+
except mpl.ExecutableNotFoundError:
265+
pass
266+
else:
267+
converter['pdf'] = converter['svg'] = _QLConverter()
229268
try:
230269
mpl._get_executable_info("gs")
231270
except mpl.ExecutableNotFoundError:
232271
pass
233272
else:
234-
converter['pdf'] = converter['eps'] = _GSConverter()
273+
conv = _GSConverter()
274+
converter.setdefault('eps', conv)
275+
converter.setdefault('pdf', conv)
235276
try:
236277
mpl._get_executable_info("inkscape")
237278
except mpl.ExecutableNotFoundError:
238279
pass
239280
else:
240-
converter['svg'] = _SVGConverter()
281+
converter.setdefault('svg', _SVGConverter())
241282

242283

243284
#: A dictionary that maps filename extensions to functions which
@@ -283,15 +324,16 @@ def convert(filename, cache):
283324
# is out of date.
284325
if not newpath.exists() or newpath.stat().st_mtime < path.stat().st_mtime:
285326
cache_dir = Path(get_cache_dir()) if cache else None
327+
cvt = converter[path.suffix[1:]]
286328

287329
if cache_dir is not None:
288-
hash_value = get_file_hash(path)
330+
hash_value = get_file_hash(path, converter=cvt)
289331
cached_path = cache_dir / (hash_value + newpath.suffix)
290332
if cached_path.exists():
291333
shutil.copyfile(cached_path, newpath)
292334
return str(newpath)
293335

294-
converter[path.suffix[1:]](path, newpath)
336+
cvt(path, newpath)
295337

296338
if cache_dir is not None:
297339
shutil.copyfile(newpath, cached_path)

0 commit comments

Comments
 (0)