8000 Steal fontconfig's font weight guessing algorithm, using fc weights. · matplotlib/matplotlib@29ef76e · GitHub
[go: up one dir, main page]

Skip to content

Commit 29ef76e

Browse files
committed
Steal fontconfig's font weight guessing algorithm, using fc weights.
1 parent 28f41f9 commit 29ef76e

File tree

1 file changed

+112
-8
lines changed

1 file changed

+112
-8
lines changed

lib/matplotlib/font_manager.py

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,23 @@
2323
# - setWeights function needs improvement
2424
# - 'light' is an invalid weight value, remove it.
2525

26+
from enum import IntEnum
2627
from functools import lru_cache
2728
import json
2829
import logging
2930
from numbers import Number
3031
import os
3132
from pathlib import Path
33+
import re
3234
import subprocess
3335
import sys
3436
try:
3537
from threading import Timer
3638
except ImportError:
3739
from dummy_threading import Timer
3840

41+
import numpy as np
42+
3943
import matplotlib as mpl
4044
from matplotlib import afm, cbook, ft2font, rcParams
4145
from matplotlib.fontconfig_pattern import (
@@ -86,6 +90,61 @@
8690
'extra bold': 800,
8791
'black': 900,
8892
}
93+
94+
95+
class _Weight(IntEnum):
96+
Thin = 0
97+
Extralight = Ultralight = 40
98+
Light = 50
99+
Demilight = Semilight = 55
100+
Book = 75
101+
Regular = Normal = 80
102+
Medium = 100
103+
Demibold = Semibold = 180
104+
Bold = 200
105+
Extrabold = Ultrabold = 205
106+
Black = Heavy = 210
107+
Extrablack = Ultrablack = 215
108+
109+
@classmethod
110+
def from_opentype(cls, ot_weight):
111+
fc_weights = [0, 40, 50, 55, 75, 80, 100, 180, 200, 205, 210, 215]
112+
ot_weights = [
113+
100, 200, 300, 350, 380, 400, 500, 600, 700, 800, 900, 1000]
114+
weight = int(np.interp(ot_weight, ot_weights, fc_weights) + .5)
115+
try:
116+
return _Weight(weight)
117+
except ValueError:
118+
return weight
119+
120+
121+
_weight_regexes = [
122+
("thin", _Weight.Thin),
123+
("extralight", _Weight.Extralight),
124+
("ultralight", _Weight.Ultralight),
125+
("demilight", _Weight.Demilight),
126+
("semilight", _Weight.Semilight),
127+
("light", _Weight.Light),
128+
("book", _Weight.Book),
129+
("regular", _Weight.Regular),
130+
("normal", _Weight.Normal),
131+
("medium", _Weight.Medium),
132+
("demibold", _Weight.Demibold),
133+
("demi", _Weight.Demibold),
134+
("semibold", _Weight.Semibold),
135+
("extrabold", _Weight.Extrabold),
136+
("superbold", _Weight.Extrabold),
137+
("ultrabold", _Weight.Ultrabold),
138+
("bold", _Weight.Bold),
139+
("ultrablack", _Weight.Ultrablack),
140+
("superblack", _Weight.Extrablack),
141+
("extrablack", _Weight.Extrablack),
142+
(r"\bultra", _Weight.Ultrabold),
143+
("black", _Weight.Black),
144+
("heavy", _Weight.Heavy),
145+
]
146+
147+
89148
font_family_aliases = {
90149
'serif',
91150
'sans-serif',
@@ -95,6 +154,8 @@
95154
'monospace',
96155
'sans',
97156
}
157+
158+
98159
# OS Font paths
99160
MSFolders = \
100161
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
@@ -356,14 +417,21 @@ def ttfFontProperty(font):
356417
# Styles are: italic, oblique, and normal (default)
357418

358419
sfnt = font.get_sfnt()
420+
mac_key = (1, # platform: macintosh
421+
0, # id: roman
422+
0) # langid: english
423+
ms_key = (3, # platform: microsoft
424+
1, # id: unicode_cs
425+
0x0409) # langid: english_united_states
426+
359427
# These tables are actually mac_roman-encoded, but mac_roman support may be
360428
# missing in some alternative Python implementations and we are only going
361429
# to look for ASCII substrings, where any ASCII-compatible encoding works
362430
# - 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())
431+
sfnt2 = (sfnt.get((*mac_key, 2), b'').decode('latin-1').lower() or
432+
sfnt.get((*ms_key, 2), b'').decode('utf_16_be').lower())
433+
sfnt4 = (sfnt.get((*mac_key, 4), b'').decode('latin-1').lower() or
434+
sfnt.get((*ms_key, 4), b'').decode('utf_16_be').lower())
367435

368436
if sfnt4.find('oblique') >= 0:
369437
style = 'oblique'
@@ -384,10 +452,46 @@ def ttfFontProperty(font):
384452
else:
385453
variant = 'normal'
386454

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)
455+
# The weight-guessing algorithm is directly translated from fontconfig
456+
# 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
457+
wws_subfamily = 22
458+
typographic_subfamily = 16
459+
font_subfamily = 2
460+
styles = [
461+
sfnt.get((*mac_key, wws_subfamily), b'').decode('latin-1'),
462+
sfnt.get((*mac_key, typographic_subfamily), b'').decode('latin-1'),
463+
sfnt.get((*mac_key, font_subfamily), b'').decode('latin-1'),
464+
sfnt.get((*ms_key, wws_subfamily), b'').decode('utf-16-be'),
465+
sfnt.get((*ms_key, typographic_subfamily), b'').decode('utf-16-be'),
466+
sfnt.get((*ms_key, font_subfamily), b'').decode('utf-16-be'),
467+
]
468+
styles = [*filter(None, styles)] or [font.style_name]
469+
470+
def get_weight():
471+
# OS/2 table weight.
472+
os2 = font.get_sfnt_table("OS/2")
473+
if os2 and os2["version"] != 0xffff:
474+
return _Weight.from_opentype(os2["usWeightClass"])
475+
# PostScript font info weight.
476+
try:
477+
ps_font_info_weight = (
478+
font.get_ps_font_info()["weight"].replace(" ", "") or "")
479+
except ValueError:
480+
pass
481+
else:
482+
for regex, weight in _weight_regexes:
483+
if re.fullmatch(regex, ps_font_info_weight, re.I):
484+
return weight
485+
# Style name weight.
486+
for style in styles:
487+
for regex, weight in _weight_regexes:
488+
if re.search(regex, style.replace(" ", ""), re.I):
489+
return weight
490+
if font.style_flags & ft2font.BOLD:
491+
return _Weight.BOLD
492+
return _Weight.REGULAR
493+
494+
weight = int(get_weight())
391495

392496
# Stretch can be absolute and relative
393497
# Absolute stretches are: ultra-condensed, extra-condensed, condensed,

0 commit comments

Comments
 (0)
0