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