8000 Merge pull request #16203 from anntzer/fontweight · matplotlib/matplotlib@a6f31e3 · GitHub
[go: up one dir, main page]

Skip to content

Commit a6f31e3

Browse files
authored
Merge pull request #16203 from anntzer/fontweight
Port fontconfig's font weight detection to font_manager.
2 parents a3d7a42 + dd3d5e6 commit a6f31e3

File tree

2 files changed

+84
-10
lines changed

2 files changed

+84
-10
lines changed

lib/matplotlib/font_manager.py

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from numbers import Number
3030
import os
3131
from pathlib import Path
32+
import re
3233
import subprocess
3334
import sys
3435
try:
@@ -86,6 +87,33 @@
8687
'extra bold': 800,
8788
'black': 900,
8889
}
90+
_weight_regexes = [
91+
# From fontconfig's FcFreeTypeQueryFaceInternal; not the same as
92+
# weight_dict!
93+
("thin", 100),
94+
("extralight", 200),
95+
("ultralight", 200),
96+
("demilight", 350),
97+
("semilight", 350),
98+
("light", 300), # Needs to come *after* demi/semilight!
99+
("book", 380),
100+
("regular", 400),
101+
("normal", 400),
102+
("medium", 500),
103+
("demibold", 600),
104+
("demi", 600),
105+
("semibold", 600),
106+
("extrabold", 800),
107+
("superbold", 800),
108+
("ultrabold", 800),
109+
("bold", 700), # Needs to come *after* extra/super/ultrabold!
110+
("ultrablack", 1000),
111+
("superblack", 1000),
112+
("extrablack", 1000),
113+
(r"\bultra", 1000),
114+
("black", 900), # Needs to come *after* ultra/super/extrablack!
115+
("heavy", 900),
116+
]
89117
font_family_aliases = {
90118
'serif',
91119
'sans-serif',
@@ -95,6 +123,8 @@
95123
'monospace',
96124
'sans',
97125
}
126+
127+
98128
# OS Font paths
99129
MSFolders = \
100130
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
@@ -356,14 +386,21 @@ def ttfFontProperty(font):
356386
# Styles are: italic, oblique, and normal (default)
357387

358388
sfnt = font.get_sfnt()
389+
mac_key = (1, # platform: macintosh
390+
0, # id: roman
391+
0) # langid: english
392+
ms_key = (3, # platform: microsoft
393+
1, # id: unicode_cs
394+
0x0409) # langid: english_united_states
395+
359396
# These tables are actually mac_roman-encoded, but mac_roman support may be
360397
# missing in some alternative Python implementations and we are only going
361398
# to look for ASCII substrings, where any ASCII-compatible encoding works
362399
# - or big-endian UTF-16, since important Microsoft fonts use that.
363-
sfnt2 = (sfnt.get((1, 0, 0, 2), b'').decode('latin-1').lower() or
364-
sfnt.get((3, 1, 0x0409, 2), b'').decode('utf_16_be').lower())
365-
sfnt4 = (sfnt.get((1, 0, 0, 4), b'').decode('latin-1').lower() or
366-
sfnt.get((3, 1, 0x0409, 4), b'').decode('utf_16_be').lower())
400+
sfnt2 = (sfnt.get((*mac_key, 2), b'').decode('latin-1').lower() or
401+
sfnt.get((*ms_key, 2), b'').decode('utf_16_be').lower())
402+
sfnt4 = (sfnt.get((*mac_key, 4), b'').decode('latin-1').lower() or
403+
sfnt.get((*ms_key, 4), b'').decode('utf_16_be').lower())
367404

368405
if sfnt4.find('oblique') >= 0:
369406
style = 'oblique'
@@ -384,10 +421,47 @@ def ttfFontProperty(font):
384421
else:
385422
variant = 'normal'
386423

387-
if font.style_flags & ft2font.BOLD:
388-
weight = 700
389-
else:
390-
weight = next((w for w in weight_dict if w in sfnt4), 400)
424+
# The weight-guessing algorithm is directly translated from fontconfig
425+
# 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
426+
wws_subfamily = 22
427+
typographic_subfamily = 16
428+
font_subfamily = 2
429+
styles = [
430+
sfnt.get((*mac_key, wws_subfamily), b'').decode('latin-1'),
431+
sfnt.get((*mac_key, typographic_subfamily), b'').decode('latin-1'),
432+
sfnt.get((*mac_key, font_subfamily), b'').decode('latin-1'),
433+
sfnt.get((*ms_key, wws_subfamily), b'').decode('utf-16-be'),
434+
sfnt.get((*ms_key, typographic_subfamily), b&# 9E81 39;').decode('utf-16-be'),
435+
sfnt.get((*ms_key, font_subfamily), b'').decode('utf-16-be'),
436+
]
437+
styles = [*filter(None, styles)] or [font.style_name]
438+
439+
def get_weight(): # From fontconfig's FcFreeTypeQueryFaceInternal.
440+
# OS/2 table weight.
441+
os2 = font.get_sfnt_table("OS/2")
442+
if os2 and os2["version"] != 0xffff:
443+
return os2["usWeightClass"]
444+
# PostScript font info weight.
445+
try:
446+
ps_font_info_weight = (
447+
font.get_ps_font_info()["weight"].replace(" ", "") or "")
448+
except ValueError:
449+
pass
450+
else:
451+
for regex, weight in _weight_regexes:
452+
if re.fullmatch(regex, ps_font_info_weight, re.I):
453+
return weight
454+
# Style name weight.
455+
for style in styles:
456+
style = style.replace(" ", "")
457+
for regex, weight in _weight_regexes:
458+
if re.search(regex, style, re.I):
459+
return weight
460+
if font.style_flags & ft2font.BOLD:
461+
return 700 # "bold"
462+
return 500 # "medium", not "regular"!
463+
464+
weight = int(get_weight())
391465

392466
# Stretch can be absolute and relative
393467
# Absolute stretches are: ultra-condensed, extra-condensed, condensed,
@@ -956,7 +1030,7 @@ class FontManager:
9561030
# Increment this version number whenever the font cache data
9571031
# format or behavior has changed and requires a existing font
9581032
# cache files to be rebuilt.
959-
__version__ = 310
1033+
__version__ = 330
9601034

9611035
def __init__(self, size=None, weight='normal'):
9621036
self._version = self.__version__

lib/matplotlib/tests/test_font_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def test_utf16m_sfnt():
105105
else:
106106
# Check that we successfully read "semibold" from the font's sfnt table
107107
# and set its weight accordingly.
108-
assert entry.weight == "semibold"
108+
assert entry.weight == 600
109109

110110

111111
def test_find_ttc():

0 commit comments

Comments
 (0)
0