font-patcher: Introduce weight check

[why]
Windows seems to construct the font names including the PS weight.
We have some sourcefonts that are broken (i.e. have in fact different
weights but have the same PS weight and/or OS2 weight.

That raises problems with the fonts on Windows.

[how]
Check and compare all weight metadata (except CID) and issue a warning
if they differ too much. That might fail with unusual weight names,
though.

See Issue #1333 and PR #1358.

Reported-by: LeoniePhiline
Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
This commit is contained in:
Fini Jastrow 2023-09-26 14:40:56 +02:00 committed by Fini
parent 35259124bd
commit 99688c40c7
3 changed files with 73 additions and 4 deletions

View file

@ -271,12 +271,34 @@ class FontnameParser:
self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, name)) self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, name))
return name return name
def check_weights(self, font):
""" Check weight metadata for consistency """
# Some weights are hidden in styles
ignore_token = list(FontnameTools.known_widths) + list(FontnameTools.known_slopes)
ignore_token += [ m + s
for s in list(FontnameTools.known_widths)
for m in list(FontnameTools.known_modifiers) ]
restored_weight_token = [ w for w in self.style_token + self.weight_token if w not in ignore_token ]
weight = ''.join(restored_weight_token)
os2_weight = font.os2_weight
ps_weight = FontnameTools.weight_string_to_number(font.weight)
name_weight = FontnameTools.weight_string_to_number(weight)
if name_weight is None:
self.logger.error('Can not parse name for weight: {}'.format(restored_weight_token))
return
if abs(os2_weight - ps_weight) > 50 or abs(os2_weight - name_weight) > 50:
self.logger.warning('Possible problem with the weight metadata detected, check with --debug')
self.logger.debug('Weight approximations: OS2/PS/Name: {}/{}/{} (from {}/\'{}\'/\'{}\')'.format(
os2_weight, ps_weight, name_weight,
font.os2_weight, font.weight, weight))
def rename_font(self, font): def rename_font(self, font):
"""Rename the font to include all information we found (font is fontforge font object)""" """Rename the font to include all information we found (font is fontforge font object)"""
font.fondname = None font.fondname = None
font.fontname = self.psname() font.fontname = self.psname()
font.fullname = self.fullname() font.fullname = self.fullname()
font.familyname = self.ps_familyname() font.familyname = self.ps_familyname()
self.check_weights(font)
# We have to work around several issues in fontforge: # We have to work around several issues in fontforge:
# #

View file

@ -247,6 +247,9 @@ class FontnameTools:
'Light': ('Lt', 'Light'), 'Light': ('Lt', 'Light'),
' ': (), # Just for CodeClimate :-/ ' ': (), # Just for CodeClimate :-/
} }
known_styles = [ # Keywords that end up as style (i.e. a RIBBI set)
'Bold', 'Italic', 'Regular', 'Normal'
]
known_widths = { # can take modifiers known_widths = { # can take modifiers
'Compressed': ('Cm', 'Comp'), 'Compressed': ('Cm', 'Comp'),
'Extended': ('Ex', 'Extd'), 'Extended': ('Ex', 'Extd'),
@ -268,6 +271,51 @@ class FontnameTools:
'Semi': ('Sm', 'Sem'), 'Semi': ('Sm', 'Sem'),
'Extra': ('X', 'Ext'), 'Extra': ('X', 'Ext'),
} }
equivalent_weights = {
100: ('thin', 'hairline'),
200: ('extralight', 'ultralight'),
300: ('light', ),
350: ('semilight', ),
400: ('regular', 'normal', 'book', 'text', 'nord', 'retina'),
500: ('medium', ),
600: ('semibold', 'demibold', 'demi'),
700: ('bold', ),
800: ('extrabold', 'ultrabold'),
900: ('black', 'heavy', 'poster', 'extrablack', 'ultrablack'),
}
@staticmethod
def weight_string_to_number(w):
""" Convert a common string approximation to a PS/2 weight value """
if not len(w):
return 400
for num, strs in FontnameTools.equivalent_weights.items():
if w.lower() in strs:
return num
return None
@staticmethod
def weight_to_string(w):
""" Convert a PS/2 weight value to the common string approximation """
if w < 150:
str = 'Thin'
elif w < 250:
str = 'Extra-Light'
elif w < 350:
str = 'Light'
elif w < 450:
str = 'Regular'
elif w < 550:
str = 'Medium'
elif w < 650:
str = 'Semi-Bold'
elif w < 750:
str = 'Bold'
elif w < 850:
str = 'Extra-Bold'
else:
str = 'Black'
return str
@staticmethod @staticmethod
def is_keep_regular(basename): def is_keep_regular(basename):
@ -342,8 +390,7 @@ class FontnameTools:
for s in list(FontnameTools.known_weights2) + list(FontnameTools.known_widths) for s in list(FontnameTools.known_weights2) + list(FontnameTools.known_widths)
for m in list(FontnameTools.known_modifiers) + [''] if m != s for m in list(FontnameTools.known_modifiers) + [''] if m != s
] + list(FontnameTools.known_weights1) + list(FontnameTools.known_slopes) ] + list(FontnameTools.known_weights1) + list(FontnameTools.known_slopes)
styles = [ 'Bold', 'Italic', 'Regular', 'Normal', ] weights = [ w for w in weights if w not in FontnameTools.known_styles ]
weights = [ w for w in weights if w not in styles ]
# Some font specialities: # Some font specialities:
other = [ other = [
'-', 'Book', 'For', 'Powerline', '-', 'Book', 'For', 'Powerline',
@ -355,7 +402,7 @@ class FontnameTools:
] ]
( style, weight_token ) = FontnameTools.get_name_token(style, weights) ( style, weight_token ) = FontnameTools.get_name_token(style, weights)
( style, style_token ) = FontnameTools.get_name_token(style, styles) ( style, style_token ) = FontnameTools.get_name_token(style, FontnameTools.known_styles)
( style, other_token ) = FontnameTools.get_name_token(style, other) ( style, other_token ) = FontnameTools.get_name_token(style, other)
while 'Regular' in style_token and len(style_token) > 1: while 'Regular' in style_token and len(style_token) > 1:
# Correct situation where "Regular" and something else is given # Correct situation where "Regular" and something else is given

View file

@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
# Change the script version when you edit this script: # Change the script version when you edit this script:
script_version = "4.5.1" script_version = "4.5.2"
version = "3.0.2" version = "3.0.2"
projectName = "Nerd Fonts" projectName = "Nerd Fonts"