8000 Port fontconfig's font weight detection to font_manager. by anntzer · Pull Request #16203 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Port fontconfig's font weight detection to font_manager. #16203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 83 additions & 9 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from numbers import Number
import os
from pathlib import Path
import re
import subprocess
import sys
try:
Expand Down Expand Up @@ -86,6 +87,33 @@
'extra bold': 800,
'black': 900,
}
_weight_regexes = [
# From fontconfig's FcFreeTypeQueryFaceInternal; not the same as
# weight_dict!
("thin", 100),
("extralight", 200),
("ultralight", 200),
("demilight", 350),
("semilight", 350),
("light", 300), # Needs to come *after* demi/semilight!
("book", 380),
("regular", 400),
("normal", 400),
("medium", 500),
("demibold", 600),
("demi", 600),
("semibold", 600),
("extrabold", 800),
("superbold", 800),
("ultrabold", 800),
("bold", 700), # Needs to come *after* extra/super/ultrabold!
("ultrablack", 1000),
("superblack", 1000),
("extrablack", 1000),
(r"\bultra", 1000),
("black", 900), # Needs to come *after* ultra/super/extrablack!
("heavy", 900),
]
font_family_aliases = {
'serif',
'sans-serif',
Expand All @@ -95,6 +123,8 @@
'monospace',
'sans',
}


# OS Font paths
MSFolders = \
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
Expand Down Expand Up @@ -356,14 +386,21 @@ def ttfFontProperty(font):
# Styles are: italic, oblique, and normal (default)

sfnt = font.get_sfnt()
mac_key = (1, # platform: macintosh
0, # id: roman
0) # langid: english
ms_key = (3, # platform: microsoft
1, # id: unicode_cs
0x0409) # langid: english_united_states

# These tables are actually mac_roman-encoded, but mac_roman support may be
# missing in some alternative Python implementations and we are only going
# to look for ASCII substrings, where any ASCII-compatible encoding works
# - or big-endian UTF-16, since important Microsoft fonts use that.
sfnt2 = (sfnt.get((1, 0, 0, 2), b'').decode('latin-1').lower() or
sfnt.get((3, 1, 0x0409, 2), b'').decode('utf_16_be').lower())
sfnt4 = (sfnt.get((1, 0, 0, 4), b'').decode('latin-1').lower() or
sfnt.get((3, 1, 0x0409, 4), b'').decode('utf_16_be').lower())
sfnt2 = (sfnt.get((*mac_key, 2), b'').decode('latin-1').lower() or
sfnt.get((*ms_key, 2), b'').decode('utf_16_be').lower())
sfnt4 = (sfnt.get((*mac_key, 4), b'').decode('latin-1').lower() or
sfnt.get((*ms_key, 4), b'').decode('utf_16_be').lower())

if sfnt4.find('oblique') >= 0:
style = 'oblique'
Expand All @@ -384,10 +421,47 @@ def ttfFontProperty(font):
else:
variant = 'normal'

if font.style_flags & ft2font.BOLD:
weight = 700
else:
weight = next((w for w in weight_dict if w in sfnt4), 400)
# The weight-guessing algorithm is directly translated from fontconfig
# 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
wws_subfamily = 22
typographic_subfamily = 16
font_subfamily = 2
styles = [
sfnt.get((*mac_key, wws_subfamily), b'').decode('latin-1'),
sfnt.get((*mac_key, typographic_subfamily), b'').decode('latin-1'),
sfnt.get((*mac_key, font_subfamily), b'').decode('latin-1'),
sfnt.get((*ms_key, wws_subfamily), b'').decode('utf-16-be'),
sfnt.get((*ms_key, typographic_subfamily), b'').decode('utf-16-be'),
sfnt.get((*ms_key, font_subfamily), b'').decode('utf-16-be'),
]
styles = [*filter(None, styles)] or [font.style_name]

def get_weight(): # From fontconfig's FcFreeTypeQueryFaceInternal.
# OS/2 table weight.
os2 = font.get_sfnt_table("OS/2")
if os2 and os2["version"] != 0xffff:
return os2["usWeightClass"]
# PostScript font info weight.
try:
ps_font_info_weight = (
font.get_ps_font_info()["weight"].replace(" ", "") or "")
except ValueError:
pass
else:
for regex, weight in _weight_regexes:
if re.fullmatch(regex, ps_font_info_weight, re.I):
return weight
# Style name weight.
for style in styles:
style = style.replace(" ", "")
for regex, weight in _weight_regexes:
if re.search(regex, style, re.I):
return weight
if font.style_flags & ft2font.BOLD:
return 700 # "bold"
return 500 # "medium", not "regular"!

weight = int(get_weight())

# Stretch can be absolute and relative
# Absolute stretches are: ultra-condensed, extra-condensed, condensed,
Expand Down Expand Up @@ -944,7 +1018,7 @@ class FontManager:
# Increment this version number whenever the font cache data
# format or behavior has changed and requires a existing font
# cache files to be rebuilt.
__version__ = 310
__version__ = 330

def __init__(self, size=None, weight='normal'):
self._version = self.__version__
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/tests/test_font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_utf16m_sfnt():
else:
# Check that we successfully read "semibold" from the font's sfnt table
# and set its weight accordingly.
assert entry.weight == "semibold"
assert entry.weight == 600


def test_find_ttc():
Expand Down
0