167167]
168168
169169
170- @lru_cache (64 )
171- def _cached_realpath (path ):
172- return os .path .realpath (path )
173-
174-
175170def get_fontext_synonyms (fontext ):
176171 """
177172 Return a list of file extensions that are synonyms for
@@ -1358,7 +1353,110 @@ def get_font_names(self):
13581353 """Return the list of available fonts."""
13591354 return list (set ([font .name for font in self .ttflist ]))
13601355
1361- @lru_cache ()
1356+ def _find_fonts_by_props (self , prop , fontext = 'ttf' , directory = None ,
1357+ fallback_to_default = True , rebuild_if_missing = True ):
1358+ """
1359+ Find font families that most closely match the given properties.
1360+
1361+ Parameters
1362+ ----------
1363+ prop : str or `~matplotlib.font_manager.FontProperties`
1364+ The font properties to search for. This can be either a
1365+ `.FontProperties` object or a string defining a
1366+ `fontconfig patterns`_.
1367+
1368+ fontext : {'ttf', 'afm'}, default: 'ttf'
1369+ The extension of the font file:
1370+
1371+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
1372+ - 'afm': Adobe Font Metrics (.afm)
1373+
1374+ directory : str, optional
1375+ If given, only search this directory and its subdirectories.
1376+
1377+ fallback_to_default : bool
1378+ If True, will fallback to the default font family (usually
1379+ "DejaVu Sans" or "Helvetica") if none of the families were found.
1380+
1381+ rebuild_if_missing : bool
1382+ Whether to rebuild the font cache and search again if the first
1383+ match appears to point to a nonexisting font (i.e., the font cache
1384+ contains outdated entries).
1385+
1386+ Returns
1387+ -------
1388+ list[str]
1389+ The paths of the fonts found
1390+
1391+ Notes
1392+ -----
1393+ This is an extension/wrapper of the original findfont API, which only
1394+ returns a single font for given font properties. Instead, this API
1395+ returns an dict containing multiple fonts and their filepaths
1396+ which closely match the given font properties. Since this internally
1397+ uses the original API, there's no change to the logic of performing the
1398+ nearest neighbor search. See `findfont` for more details.
1399+
1400+ """
1401+
1402+ rc_params = tuple (tuple (rcParams [key ]) for key in [
1403+ "font.serif" , "font.sans-serif" , "font.cursive" , "font.fantasy" ,
1404+ "font.monospace" ])
1405+
1406+ prop = FontProperties ._from_any (prop )
1407+
1408+ fpaths = []
1409+ for family in prop .get_family ():
1410+ cprop = prop .copy ()
1411+
1412+ # set current prop's family
1413+ cprop .set_family (family )
1414+
1415+ # do not fall back to default font
1416+ try :
1417+ fpaths .append (
1418+ self ._findfont_cached (
1419+ cprop , fontext , directory ,
1420+ fallback_to_default = False ,
1421+ rebuild_if_missing = rebuild_if_missing ,
1422+ rc_params = rc_params ,
1423+ )
1424+ )
1425+ except ValueError :
1426+ if family in font_family_aliases :
1427+ _log .warning (
1428+ "findfont: Generic family %r not found because "
1429+ "none of the following families were found: %s" ,
1430+ family ,
1431+ ", " .join (self ._expand_aliases (family ))
1432+ )
1433+ else :
1434+ _log .warning (
1435+ 'findfont: Font family \' %s\' not found.' , family
1436+ )
1437+
1438+ # only add default family if no other font was found and
1439+ # fallback_to_default is enabled
1440+ if not fpaths :
1441+ if fallback_to_default :
1442+ dfamily = self .defaultFamily [fontext ]
1443+ cprop = prop .copy ()
1444+ cprop .set_family (dfamily )
1445+ fpaths .append (
1446+ self ._findfont_cached (
1447+ cprop , fontext , directory ,
1448+ fallback_to_default = True ,
1449+ rebuild_if_missing = rebuild_if_missing ,
1450+ rc_params = rc_params ,
1451+ )
1452+ )
1453+ else :
1454+ raise ValueError ("Failed to find any font, and fallback "
1455+ "to the default font was disabled." )
1456+
1457+ return fpaths
1458+
1459+ @lru_cache (1024 )
13621460 def _findfont_cached (self , prop , fontext , directory , fallback_to_default ,
13631461 rebuild_if_missing , rc_params ):
13641462
@@ -1451,9 +1549,19 @@ def is_opentype_cff_font(filename):
14511549
14521550
14531551@lru_cache (64 )
1454- def _get_font (filename , hinting_factor , * , _kerning_factor , thread_id ):
1552+ def _get_font (font_filepaths , hinting_factor , * , _kerning_factor , thread_id ):
1553+ first_fontpath , * rest = font_filepaths
14551554 return ft2font .FT2Font (
1456- filename , hinting_factor , _kerning_factor = _kerning_factor )
1555+ first_fontpath , hinting_factor ,
1556+ _fallback_list = [
1557+ ft2font .FT2Font (
1558+ fpath , hinting_factor ,
1559+ _kerning_factor = _kerning_factor
1560+ )
1561+ for fpath in rest
1562+ ],
1563+ _kerning_factor = _kerning_factor
1564+ )
14571565
14581566
14591567# FT2Font objects cannot be used across fork()s because they reference the same
@@ -1465,16 +1573,51 @@ def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id):
14651573 os .register_at_fork (after_in_child = _get_font .cache_clear )
14661574
14671575
1468- def get_font (filename , hinting_factor = None ):
1576+ @lru_cache (64 )
1577+ def _cached_realpath (path ):
14691578 # Resolving the path avoids embedding the font twice in pdf/ps output if a
14701579 # single font is selected using two different relative paths.
1471- filename = _cached_realpath (filename )
1580+ return os .path .realpath (path )
1581+
1582+
1583+ @_api .rename_parameter ('3.6' , "filepath" , "font_filepaths" )
1584+ def get_font (font_filepaths , hinting_factor = None ):
1585+ """
1586+ Get an `.ft2font.FT2Font` object given a list of file paths.
1587+
1588+ Parameters
1589+ ----------
1590+ font_filepaths : Iterable[str, Path, bytes], str, Path, bytes
1591+ Relative or absolute paths to the font files to be used.
1592+
1593+ If a single string, bytes, or `pathlib.Path`, then it will be treated
1594+ as a list with that entry only.
1595+
1596+ If more than one filepath is passed, then the returned FT2Font object
1597+ will fall back through the fonts, in the order given, to find a needed
1598+ glyph.
1599+
1600+ Returns
1601+ -------
1602+ `.ft2font.FT2Font`
1603+
1604+ """
1605+ if isinstance (font_filepaths , (str , Path , bytes )):
1606+ paths = (_cached_realpath (font_filepaths ),)
1607+ else :
1608+ paths = tuple (_cached_realpath (fname ) for fname in font_filepaths )
1609+
14721610 if hinting_factor is None :
14731611 hinting_factor = rcParams ['text.hinting_factor' ]
1474- # also key on the thread ID to prevent segfaults with multi-threading
1475- return _get_font (filename , hinting_factor ,
1476- _kerning_factor = rcParams ['text.kerning_factor' ],
1477- thread_id = threading .get_ident ())
1612+
1613+ return _get_font (
1614+ # must be a tuple to be cached
1615+ paths ,
1616+ hinting_factor ,
1617+ _kerning_factor = rcParams ['text.kerning_factor' ],
1618+ # also key on the thread ID to prevent segfaults with multi-threading
1619+ thread_id = threading .get_ident ()
1620+ )
14781621
14791622
14801623def _load_fontmanager (* , try_read_cache = True ):
0 commit comments