23
23
# - setWeights function needs improvement
24
24
# - 'light' is an invalid weight value, remove it.
25
25
26
+ from collections import OrderedDict
26
27
import dataclasses
27
28
from functools import lru_cache
28
29
import json
@@ -1308,6 +1309,118 @@ def findfont(self, prop, fontext='ttf', directory=None,
1308
1309
prop , fontext , directory , fallback_to_default , rebuild_if_missing ,
1309
1310
rc_params )
1310
1311
1312
+ def find_fontsprop (self , prop , fontext = 'ttf' , directory = None ,
1313
+ fallback_to_default = True , rebuild_if_missing = True ):
1314
+ """
1315
+ Find font families that most closely matches the given properties.
1316
+
1317
+ Parameters
1318
+ ----------
1319
+ prop : str or `~matplotlib.font_manager.FontProperties`
1320
+ The font properties to search for. This can be either a
1321
+ `.FontProperties` object or a string defining a
1322
+ `fontconfig patterns`_.
1323
+
1324
+ fontext : {'ttf', 'afm'}, default: 'ttf'
1325
+ The extension of the font file:
1326
+
1327
+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
1328
+ - 'afm': Adobe Font Metrics (.afm)
1329
+
1330
+ directory : str, optional
1331
+ If given, only search this directory and its subdirectories.
1332
+
1333
+ fallback_to_default : bool
1334
+ If True, will fallback to the default font family (usually
1335
+ "DejaVu Sans" or "Helvetica") if none of the families were found.
1336
+
1337
+ rebuild_if_missing : bool
1338
+ Whether to rebuild the font cache and search again if the first
1339
+ match appears to point to a nonexisting font (i.e., the font cache
1340
+ contains outdated entries).
1341
+
1342
+ Returns
1343
+ -------
1344
+ OrderedDict
1345
+ key, value pair of families and their corresponding filepaths.
1346
+
1347
+ Notes
1348
+ -----
1349
+ This is a plugin to original findfont API, which only returns a
1350
+ single font for given font properties. Instead, this API returns
1351
+ an OrderedDict containing multiple fonts and their filepaths which
1352
+ closely match the given font properties.
1353
+ Since this internally uses original API, there's no change
1354
+ to the logic of performing the nearest neighbor search.
1355
+ See `findfont` for more details.
1356
+
1357
+ """
1358
+
1359
+ rc_params = tuple (tuple (rcParams [key ]) for key in [
1360
+ "font.serif" , "font.sans-serif" , "font.cursive" , "font.fantasy" ,
1361
+ "font.monospace" ])
1362
+
1363
+ prop = FontProperties ._from_any (prop )
1364
+ ffamily = prop .get_family ()
1365
+
1366
+ fpaths = OrderedDict ()
1367
+ for fidx in range (len (ffamily )):
1368
+ cprop = prop .copy ()
1369
+
1370
+ # set current prop's family
1371
+ cprop .set_family (ffamily [fidx ])
1372
+
1373
+ # do not fall back to default font
1374
+ fpath = self ._findfontsprop_cached (
1375
+ ffamily [fidx ], cprop , fontext , directory ,
1376
+ False , rebuild_if_missing , rc_params
1377
+ )
1378
+ if fpath :
1379
+ fpaths [ffamily [fidx ]] = fpath
1380
+
1381
+ # only add default family if no other font was found
1382
+ # and fallback_to_default is enabled
1383
+ if not fpaths :
1384
+ if fallback_to_default :
1385
+ dfamily = self .defaultFamily [fontext ]
1386
+ cprop = prop .copy ().set_family (dfamily )
1387
+ fpath = self ._findfontsprop_cached (
1388
+ dfamily , cprop , fontext , directory ,
1389
+ True , rebuild_if_missing , rc_params
1390
+ )
1391
+ fpaths [dfamily ] = fpath
1392
+ else :
1393
+ raise ValueError ("Failed to find any font, and fallback "
1394
+ "to the default font was disabled." )
1395
+
1396
+ return fpaths
1397
+
1398
+
1399
+ @lru_cache ()
1400
+ def _findfontsprop_cached (
1401
+ self , family , prop , fontext , directory ,
1402
+ fallback_to_default , rebuild_if_missing , rc_params
1403
+ ):
1404
+ try :
1405
+ return self ._findfont_cached (
1406
+ prop , fontext , directory , fallback_to_default ,
1407
+ rebuild_if_missing , rc_params
1408
+ )
1409
+ except ValueError :
1410
+ if not fallback_to_default :
1411
+ if family .lower () in font_family_aliases :
1412
+ _log .warning (
1413
+ "findfont: Generic family %r not found because "
1414
+ "none of the following families were found: %s" ,
1415
+ family ,
1416
+ ", " .join (self ._expand_aliases (family ))
1417
+ )
1418
+ else :
1419
+ _log .warning (
1420
+ 'findfont: Font family \' %s\' not found.' , family
1421
+ )
1422
+
1423
+
1311
1424
@lru_cache ()
1312
1425
def _findfont_cached (self , prop , fontext , directory , fallback_to_default ,
1313
1426
rebuild_if_missing , rc_params ):
@@ -1401,9 +1514,23 @@ def is_opentype_cff_font(filename):
1401
1514
1402
1515
1403
1516
@lru_cache (64 )
1404
- def _get_font (filename , hinting_factor , * , _kerning_factor , thread_id ):
1405
- return ft2font .FT2Font (
1406
- filename , hinting_factor , _kerning_factor = _kerning_factor )
1517
+ def _get_font (fpaths , hinting_factor , * , _kerning_factor , thread_id ):
1518
+ ftobjects = []
1519
+ for fpath in fpaths [1 :]:
1520
+ ftobject = ft2font .FT2Font (
1521
+ fpath , hinting_factor ,
1522
+ _kerning_factor = _kerning_factor
1523
+ )
1524
+ ftobjects .append (ftobject )
1525
+
1526
+ print ("\n In Python:" , ftobjects , "\n " )
1527
+ ft2font_object = ft2font .FT2Font (
1528
+ fpaths [0 ], hinting_factor ,
1529
+ _fallback_list = ftobjects ,
1530
+ _kerning_factor = _kerning_factor
1531
+ )
1532
+ print ("\n Back to Python!\n " )
1533
+ return ft2font_object
1407
1534
1408
1535
1409
1536
# FT2Font objects cannot be used across fork()s because they reference the same
@@ -1417,11 +1544,14 @@ def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id):
1417
1544
def get_font (filename , hinting_factor = None ):
1418
1545
# Resolving the path avoids embedding the font twice in pdf/ps output if a
1419
1546
# single font is selected using two different relative paths.
1420
- filename = _cached_realpath (filename )
1547
+ if isinstance (filename , OrderedDict ):
1548
+ paths = tuple (_cached_realpath (fname ) for fname in filename .values ())
1549
+ else :
1550
+ paths = (_cached_realpath (filename ),)
1421
1551
if hinting_factor is None :
1422
1552
hinting_factor = rcParams ['text.hinting_factor' ]
1423
1553
# also key on the thread ID to prevent segfaults with multi-threading
1424
- return _get_font (filename , hinting_factor ,
1554
+ return _get_font (paths , hinting_factor ,
1425
1555
_kerning_factor = rcParams ['text.kerning_factor' ],
1426
1556
thread_id = threading .get_ident ())
1427
1557
@@ -1446,3 +1576,4 @@ def _load_fontmanager(*, try_read_cache=True):
1446
1576
1447
1577
fontManager = _load_fontmanager ()
1448
1578
findfont = fontManager .findfont
1579
+ find_fontsprop = fontManager .find_fontsprop
0 commit comments