44
44
from dummy_threading import Timer
45
45
import warnings
46
46
47
+ import matplotlib as mpl
47
48
from matplotlib import afm , cbook , ft2font , rcParams , get_cachedir
48
49
from matplotlib .fontconfig_pattern import (
49
50
parse_fontconfig_pattern , generate_fontconfig_pattern )
@@ -828,15 +829,23 @@ def copy(self):
828
829
class JSONEncoder (json .JSONEncoder ):
829
830
def default (self , o ):
830
831
if isinstance (o , FontManager ):
831
- return dict (o .__dict__ , _class = 'FontManager' )
832
+ return dict (o .__dict__ , __class__ = 'FontManager' )
832
833
elif isinstance (o , FontEntry ):
833
- return dict (o .__dict__ , _class = 'FontEntry' )
834
+ d = dict (o .__dict__ , __class__ = 'FontEntry' )
835
+ try :
836
+ # Cache paths of fonts shipped with mpl relative to the mpl
837
+ # data path, which helps in the presence of venvs.
838
+ d ["fname" ] = str (
839
+ Path (d ["fname" ]).relative_to (mpl .get_data_path ()))
840
+ except ValueError :
841
+ pass
842
+ return d
834
843
else :
835
844
return super ().default (o )
836
845
837
846
838
847
def _json_decode (o ):
839
- cls = o .pop ('_class ' , None )
848
+ cls = o .pop ('__class__ ' , None )
840
849
if cls is None :
841
850
return o
842
851
elif cls == 'FontManager' :
@@ -846,15 +855,21 @@ def _json_decode(o):
846
855
elif cls == 'FontEntry' :
847
856
r = FontEntry .__new__ (FontEntry )
848
857
r .__dict__ .update (o )
858
+ if not os .path .isabs (r .fname ):
859
+ r .fname = os .path .join (mpl .get_data_path (), r .fname )
849
860
return r
850
861
else :
851
- raise ValueError ("don't know how to deserialize _class =%s" % cls )
862
+ raise ValueError ("don't know how to deserialize __class__ =%s" % cls )
852
863
853
864
854
865
def json_dump (data , filename ):
855
- """Dumps a data structure as JSON in the named file.
856
- Handles FontManager and its fields."""
866
+ """
867
+ Dumps a data structure as JSON in the named file.
857
868
869
+ Handles FontManager and its fields. File paths that are children of the
870
+ Matplotlib data path (typically, fonts shipped with Matplotlib) are stored
871
+ relative to that data path (to remain valid across virtualenvs).
872
+ """
858
873
with open (filename , 'w' ) as fh :
859
874
try :
860
875
json .dump (data , fh , cls = JSONEncoder , indent = 2 )
@@ -863,9 +878,13 @@ def json_dump(data, filename):
863
878
864
879
865
880
def json_load (filename ):
866
- """Loads a data structure as JSON from the named file.
867
- Handles FontManager and its fields."""
881
+ """
882
+ Loads a data structure as JSON from the named file.
868
883
884
+ Handles FontManager and its fields. Relative file paths are interpreted
885
+ as being relative to the Matplotlib data path, and transformed into
886
+ absolute paths.
887
+ """
869
888
with open (filename , 'r' ) as fh :
870
889
return json .load (fh , object_hook = _json_decode )
871
890
@@ -926,7 +945,7 @@ class FontManager(object):
926
945
# Increment this version number whenever the font cache data
927
946
# format or behavior has changed and requires a existing font
928
947
# cache files to be rebuilt.
929
- __version__ = 201
948
+ __version__ = 300
930
949
931
950
def __init__ (self , size = None , weight = 'normal' ):
932
951
self ._version = self .__version__
@@ -951,30 +970,32 @@ def __init__(self, size=None, weight='normal'):
951
970
_log .debug ('font search path %s' , str (paths ))
952
971
# Load TrueType fonts and create font dictionary.
953
972
954
- self .ttffiles = findSystemFonts (paths ) + findSystemFonts ()
955
973
self .defaultFamily = {
956
974
'ttf' : 'DejaVu Sans' ,
957
975
'afm' : 'Helvetica' }
958
976
self .defaultFont = {}
959
977
960
- for fname in self .ttffiles :
961
- _log .debug ('trying fontname %s' , fname )
962
- if fname .lower ().find ('DejaVuSans.ttf' )>= 0 :
963
- self .defaultFont ['ttf' ] = fname
964
- break
965
- else :
966
- # use anything
967
- self .defaultFont ['ttf' ] = self .ttffiles [0 ]
978
+ ttffiles = findSystemFonts (paths ) + findSystemFonts ()
979
+ self .defaultFont ['ttf' ] = next (
980
+ (fname for fname in ttffiles
981
+ if fname .lower ().endswith ("dejavusans.ttf" )),
982
+ ttffiles [0 ])
983
+ self .ttflist = createFontList (ttffiles )
968
984
969
- self .ttflist = createFontList (self .ttffiles )
985
+ afmfiles = (findSystemFonts (paths , fontext = 'afm' )
986
+ + findSystemFonts (fontext = 'afm' ))
987
+ self .afmlist = createFontList (afmfiles , fontext = 'afm' )
988
+ self .defaultFont ['afm' ] = afmfiles [0 ] if afmfiles else None
970
989
971
- self .afmfiles = (findSystemFonts (paths , fontext = 'afm' )
972
- + findSystemFonts (fontext = 'afm' ))
973
- self .afmlist = createFontList (self .afmfiles , fontext = 'afm' )
974
- if len (self .afmfiles ):
975
- self .defaultFont ['afm' ] = self .afmfiles [0 ]
976
- else :
977
- self .defaultFont ['afm' ] = None
990
+ @property
991
+ @cbook .deprecated ("3.0" )
992
+ def ttffiles (self ):
993
+ return [font .fname for font in self .ttflist ]
994
+
995
+ @property
996
+ @cbook .deprecated ("3.0" )
997
+ def afmfiles (self ):
998
+ return [font .fname for font in self .afmlist ]
978
999
979
1000
def get_default_weight (self ):
980
1001
"""
@@ -1312,7 +1333,8 @@ def findfont(prop, fontext='ttf'):
1312
1333
1313
1334
cachedir = get_cachedir ()
1314
1335
if cachedir is not None :
1315
- _fmcache = os .path .join (cachedir , 'fontList.json' )
1336
+ _fmcache = os .path .join (
1337
+ cachedir , 'fontlist-v{}.json' .format (FontManager .__version__ ))
1316
1338
1317
1339
fontManager = None
1318
1340
0 commit comments