From 823fd95a06afb139c55b9abe1d8393081da7cc91 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 8 Dec 2016 17:50:59 -0800 Subject: [PATCH] Delay fc-list warning by 5s. We do not need to check filename validity in `_call_fc_list` as `findSystemFonts` already does it. --- doc/api/api_changes/2016-12-09-AL_afm.rst | 7 ++ lib/matplotlib/font_manager.py | 89 ++++++++++++----------- 2 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 doc/api/api_changes/2016-12-09-AL_afm.rst diff --git a/doc/api/api_changes/2016-12-09-AL_afm.rst b/doc/api/api_changes/2016-12-09-AL_afm.rst new file mode 100644 index 000000000000..0df780a78544 --- /dev/null +++ b/doc/api/api_changes/2016-12-09-AL_afm.rst @@ -0,0 +1,7 @@ +`afm.get_fontconfig_fonts` returns a list of paths and does not check for existence +``````````````````````````````````````````````````````````````````````````````````` + +`afm.get_fontconfig_fonts` used to return a set of paths encoded as a +``{key: 1, ...}`` dict, and checked for the existence of the paths. It now +returns a list and dropped the existence check, as the same check is performed +by the caller (`afm.findSystemFonts`) as well. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index f26d0c1c898e..223f230aed96 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -47,18 +47,19 @@ see license/LICENSE_TTFQUERY. """ -import json -import os, sys, warnings from collections import Iterable +import json +import os +import sys +from threading import Timer +import warnings + import matplotlib -from matplotlib import afm -from matplotlib import ft2font -from matplotlib import rcParams, get_cachedir +from matplotlib import afm, cbook, ft2font, rcParams, get_cachedir from matplotlib.cbook import is_string_like -import matplotlib.cbook as cbook from matplotlib.compat import subprocess -from matplotlib.fontconfig_pattern import \ - parse_fontconfig_pattern, generate_fontconfig_pattern +from matplotlib.fontconfig_pattern import ( + parse_fontconfig_pattern, generate_fontconfig_pattern) try: from functools import lru_cache @@ -262,39 +263,39 @@ def OSXInstalledFonts(directories=None, fontext='ttf'): files.extend(list_fonts(path, fontext)) return files -def get_fontconfig_fonts(fontext='ttf'): + +@lru_cache() +def _call_fc_list(): + """Cache and list the font filenames known to `fc-list`. """ - Grab a list of all the fonts that are being tracked by fontconfig - by making a system call to ``fc-list``. This is an easy way to - grab all of the fonts the user wants to be made available to - applications, without needing knowing where all of them reside. + # Delay the warning by 5s. + timer = Timer(5, lambda: warnings.warn( + 'Matplotlib is building the font cache using fc-list. ' + 'This may take a moment.')) + timer.start() + try: + out = subprocess.check_output(['fc-list', '--format=%{file}']) + except (OSError, subprocess.CalledProcessError): + return [] + finally: + timer.cancel() + fnames = [] + for fname in out.split(b'\n'): + try: + fname = six.text_type(fname, sys.getfilesystemencoding()) + except UnicodeDecodeError: + continue + fnames.append(fname) + return fnames + + +def get_fontconfig_fonts(fontext='ttf'): + """List the font filenames known to `fc-list` having the given extension. """ fontext = get_fontext_synonyms(fontext) + return [fname for fname in _call_fc_list() + if os.path.splitext(fname)[1][1:] in fontext] - fontfiles = {} - try: - warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.') - pipe = subprocess.Popen(['fc-list', '--format=%{file}\\n'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = pipe.communicate()[0] - except (OSError, IOError): - # Calling fc-list did not work, so we'll just return nothing - return fontfiles - - if pipe.returncode == 0: - # The line breaks between results are in ascii, but each entry - # is in in sys.filesystemencoding(). - for fname in output.split(b'\n'): - try: - fname = six.text_type(fname, sys.getfilesystemencoding()) - except UnicodeDecodeError: - continue - if (os.path.splitext(fname)[1][1:] in fontext and - os.path.exists(fname)): - fontfiles[fname] = 1 - - return fontfiles def findSystemFonts(fontpaths=None, fontext='ttf'): """ @@ -304,7 +305,7 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): available. A list of TrueType fonts are returned by default with AFM fonts as an option. """ - fontfiles = {} + fontfiles = set() fontexts = get_fontext_synonyms(fontext) if fontpaths is None: @@ -316,16 +317,16 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): for f in win32InstalledFonts(fontdir): base, ext = os.path.splitext(f) if len(ext)>1 and ext[1:].lower() in fontexts: - fontfiles[f] = 1 + fontfiles.add(f) else: fontpaths = X11FontDirectories # check for OS X & load its fonts if present if sys.platform == 'darwin': for f in OSXInstalledFonts(fontext=fontext): - fontfiles[f] = 1 + fontfiles.add(f) for f in get_fontconfig_fonts(fontext): - fontfiles[f] = 1 + fontfiles.add(f) elif isinstance(fontpaths, six.string_types): fontpaths = [fontpaths] @@ -333,9 +334,9 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): for path in fontpaths: files = list_fonts(path, fontexts) for fname in files: - fontfiles[os.path.abspath(fname)] = 1 + fontfiles.add(os.path.abspath(fname)) - return [fname for fname in six.iterkeys(fontfiles) if os.path.exists(fname)] + return [fname for fname in fontfiles if os.path.exists(fname)] def weight_as_number(weight): """ @@ -833,7 +834,7 @@ def set_family(self, family): family = rcParams['font.family'] if is_string_like(family): family = [six.text_type(family)] - elif (not is_string_like(family) and isinstance(family, Iterable)): + elif not is_string_like(family) and isinstance(family, Iterable): family = [six.text_type(f) for f in family] self._family = family set_name = set_family