4444 from dummy_threading import Timer
4545import warnings
4646
47+ import matplotlib as mpl
4748from matplotlib import afm , cbook , ft2font , rcParams , get_cachedir
4849from matplotlib .fontconfig_pattern import (
4950 parse_fontconfig_pattern , generate_fontconfig_pattern )
@@ -828,15 +829,23 @@ def copy(self):
828829class JSONEncoder (json .JSONEncoder ):
829830 def default (self , o ):
830831 if isinstance (o , FontManager ):
831- return dict (o .__dict__ , _class = 'FontManager' )
832+ return dict (o .__dict__ , __class__ = 'FontManager' )
832833 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
834843 else :
835844 return super ().default (o )
836845
837846
838847def _json_decode (o ):
839- cls = o .pop ('_class ' , None )
848+ cls = o .pop ('__class__ ' , None )
840849 if cls is None :
841850 return o
842851 elif cls == 'FontManager' :
@@ -846,15 +855,21 @@ def _json_decode(o):
846855 elif cls == 'FontEntry' :
847856 r = FontEntry .__new__ (FontEntry )
848857 r .__dict__ .update (o )
858+ if not os .path .isabs (r .fname ):
859+ r .fname = os .path .join (mpl .get_data_path (), r .fname )
849860 return r
850861 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 )
852863
853864
854865def 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.
857868
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+ """
858873 with open (filename , 'w' ) as fh :
859874 try :
860875 json .dump (data , fh , cls = JSONEncoder , indent = 2 )
@@ -863,9 +878,13 @@ def json_dump(data, filename):
863878
864879
865880def 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.
868883
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+ """
869888 with open (filename , 'r' ) as fh :
870889 return json .load (fh , object_hook = _json_decode )
871890
@@ -951,30 +970,32 @@ def __init__(self, size=None, weight='normal'):
951970 _log .debug ('font search path %s' , str (paths ))
952971 # Load TrueType fonts and create font dictionary.
953972
954- self .ttffiles = findSystemFonts (paths ) + findSystemFonts ()
955973 self .defaultFamily = {
956974 'ttf' : 'DejaVu Sans' ,
957975 'afm' : 'Helvetica' }
958976 self .defaultFont = {}
959977
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 ]
968-
969- self .ttflist = createFontList (self .ttffiles )
970-
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
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 )
984+
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
989+
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 ]
978999
9791000 def get_default_weight (self ):
9801001 """
0 commit comments