@@ -310,6 +310,69 @@ def findSystemFonts(fontpaths=None, fontext='ttf'):
310310 return [fname for fname in fontfiles if os .path .exists (fname )]
311311
312312
313+ # To maintain backwards-compatibility with the current code we need to continue to
314+ # return a str. However to support indexing into the file we need to return both the
315+ # path and the index. Thus, we sub-class str to maintain compatibility and extend it to
316+ # carry the index.
317+ #
318+ # The other alternative would be to create a completely new API and deprecate the
319+ # existing one. In this case, sub-classing str is the simpler and less-disruptive
320+ # option.
321+ class FontPath (str ):
322+ """
323+ A class to describe a path to a font with a face index.
324+
325+ Parameters
326+ ----------
327+ path : str
328+ The path to a font.
329+ face_index : int
330+ The face index in the font.
331+ """
332+
333+ __match_args__ = ('path' , 'face_index' )
334+
335+ def __new__ (cls , path , face_index ):
336+ ret = super ().__new__ (cls , path )
337+ ret ._face_index = face_index
338+ return ret
339+
340+ @property
341+ def path (self ):
342+ """The path to a font."""
343+ return str (self )
344+
345+ @property
346+ def face_index (self ):
347+ """The face index in a font."""
348+ return self ._face_index
349+
350+ def _as_tuple (self ):
351+ return (self .path , self .face_index )
352+
353+ def __eq__ (self , other ):
354+ if isinstance (other , FontPath ):
355+ return self ._as_tuple () == other ._as_tuple ()
356+ return super ().__eq__ (other )
357+
358+ def __ne__ (self , other ):
359+ return not (self == other )
360+
361+ def __lt__ (self , other ):
362+ if isinstance (other , FontPath ):
363+ return self ._as_tuple () < other ._as_tuple ()
364+ return super ().__lt__ (other )
365+
366+ def __gt__ (self , other ):
367+ return not (self == other or self < other )
368+
369+ def __hash__ (self ):
370+ return hash (self ._as_tuple ())
371+
372+ def __repr__ (self ):
373+ return f'FontPath{ self ._as_tuple ()} '
374+
375+
313376@dataclasses .dataclass (frozen = True )
314377class FontEntry :
315378 """
@@ -1326,7 +1389,7 @@ def findfont(self, prop, fontext='ttf', directory=None,
13261389
13271390 Returns
13281391 -------
1329- str
1392+ FontPath
13301393 The filename of the best matching font.
13311394
13321395 Notes
@@ -1396,7 +1459,7 @@ def _find_fonts_by_props(self, prop, fontext='ttf', directory=None,
13961459
13971460 Returns
13981461 -------
1399- list[str ]
1462+ list[FontPath ]
14001463 The paths of the fonts found.
14011464
14021465 Notes
@@ -1542,7 +1605,7 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
15421605 # actually raised.
15431606 return cbook ._ExceptionInfo (ValueError , "No valid font could be found" )
15441607
1545- return _cached_realpath (result )
1608+ return FontPath ( _cached_realpath (result ), best_font . index )
15461609
15471610
15481611@_api .deprecated ("3.11" )
@@ -1562,15 +1625,16 @@ def is_opentype_cff_font(filename):
15621625@lru_cache (64 )
15631626def _get_font (font_filepaths , hinting_factor , * , _kerning_factor , thread_id ,
15641627 enable_last_resort ):
1565- first_fontpath , * rest = font_filepaths
1628+ ( first_fontpath , first_fontindex ) , * rest = font_filepaths
15661629 fallback_list = [
1567- ft2font .FT2Font (fpath , hinting_factor , _kerning_factor = _kerning_factor )
1568- for fpath in rest
1630+ ft2font .FT2Font (fpath , hinting_factor , face_index = index ,
1631+ _kerning_factor = _kerning_factor )
1632+ for fpath , index in rest
15691633 ]
15701634 last_resort_path = _cached_realpath (
15711635 cbook ._get_data_path ('fonts' , 'ttf' , 'LastResortHE-Regular.ttf' ))
15721636 try :
1573- last_resort_index = font_filepaths .index (last_resort_path )
1637+ last_resort_index = font_filepaths .index (( last_resort_path , 0 ) )
15741638 except ValueError :
15751639 last_resort_index = - 1
15761640 # Add Last Resort font so we always have glyphs regardless of font, unless we're
@@ -1582,7 +1646,7 @@ def _get_font(font_filepaths, hinting_factor, *, _kerning_factor, thread_id,
15821646 _warn_if_used = True ))
15831647 last_resort_index = len (fallback_list )
15841648 font = ft2font .FT2Font (
1585- first_fontpath , hinting_factor ,
1649+ first_fontpath , hinting_factor , face_index = first_fontindex ,
15861650 _fallback_list = fallback_list ,
15871651 _kerning_factor = _kerning_factor
15881652 )
@@ -1617,7 +1681,8 @@ def get_font(font_filepaths, hinting_factor=None):
16171681
16181682 Parameters
16191683 ----------
1620- font_filepaths : Iterable[str, bytes, os.PathLike], str, bytes, os.PathLike
1684+ font_filepaths : Iterable[str, bytes, os.PathLike, FontPath], \
1685+ str, bytes, os.PathLike, FontPath
16211686 Relative or absolute paths to the font files to be used.
16221687
16231688 If a single string, bytes, or `os.PathLike`, then it will be treated
@@ -1632,10 +1697,16 @@ def get_font(font_filepaths, hinting_factor=None):
16321697 `.ft2font.FT2Font`
16331698
16341699 """
1635- if isinstance (font_filepaths , (str , bytes , os .PathLike )):
1636- paths = (_cached_realpath (font_filepaths ),)
1637- else :
1638- paths = tuple (_cached_realpath (fname ) for fname in font_filepaths )
1700+ match font_filepaths :
1701+ case FontPath (path , index ):
1702+ paths = ((_cached_realpath (path ), index ), )
1703+ case str () | bytes () | os .PathLike () as path :
1704+ paths = ((_cached_realpath (path ), 0 ), )
1705+ case _:
1706+ paths = tuple (
1707+ (_cached_realpath (fname .path ), fname .face_index )
1708+ if isinstance (fname , FontPath ) else (_cached_realpath (fname ), 0 )
1709+ for fname in font_filepaths )
16391710
16401711 hinting_factor = mpl ._val_or_rc (hinting_factor , 'text.hinting_factor' )
16411712
0 commit comments