167
167
]
168
168
169
169
170
- @lru_cache (64 )
171
- def _cached_realpath (path ):
172
- return os .path .realpath (path )
173
-
174
-
175
170
def get_fontext_synonyms (fontext ):
176
171
"""
177
<
10000
code>172 Return a list of file extensions that are synonyms for
@@ -1358,7 +1353,110 @@ def get_font_names(self):
1358
1353
"""Return the list of available fonts."""
1359
1354
return list (set ([font .name for font in self .ttflist ]))
1360
1355
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 )
1362
1460
def _findfont_cached (self , prop , fontext , directory , fallback_to_default ,
1363
1461
rebuild_if_missing , rc_params ):
1364
1462
@@ -1451,9 +1549,19 @@ def is_opentype_cff_font(filename):
1451
1549
1452
1550
1453
1551
@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
1455
1554
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
+ )
1457
1565
1458
1566
1459
1567
# 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):
1465
1573
os .register_at_fork (after_in_child = _get_font .cache_clear )
1466
1574
1467
1575
1468
- def get_font (filename , hinting_factor = None ):
1576
+ @lru_cache (64 )
1577
+ def _cached_realpath (path ):
1469
1578
# Resolving the path avoids embedding the font twice in pdf/ps output if a
1470
1579
# 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
+
1472
1610
if hinting_factor is None :
1473
1611
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
+ )
1478
1621
1479
1622
1480
1623
def _load_fontmanager (* , try_read_cache = True ):
0 commit comments