From 4fe686d65898a353b55e3829ccabd8cfd314b0c5 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Tue, 26 Sep 2023 14:40:56 +0200 Subject: [PATCH] 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 --- bin/scripts/name_parser/FontnameParser.py | 22 ++++++++++ bin/scripts/name_parser/FontnameTools.py | 51 +++++++++++++++++++++-- font-patcher | 2 +- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/bin/scripts/name_parser/FontnameParser.py b/bin/scripts/name_parser/FontnameParser.py index 2c5aff4b10e..fbfc7ef3dea 100644 --- a/bin/scripts/name_parser/FontnameParser.py +++ b/bin/scripts/name_parser/FontnameParser.py @@ -271,12 +271,34 @@ def checklen(self, max_len, entry_id, name): self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, 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): """Rename the font to include all information we found (font is fontforge font object)""" font.fondname = None font.fontname = self.psname() font.fullname = self.fullname() font.familyname = self.ps_familyname() + self.check_weights(font) # We have to work around several issues in fontforge: # diff --git a/bin/scripts/name_parser/FontnameTools.py b/bin/scripts/name_parser/FontnameTools.py index 0e664f6d2a5..ed8b24862dc 100644 --- a/bin/scripts/name_parser/FontnameTools.py +++ b/bin/scripts/name_parser/FontnameTools.py @@ -247,6 +247,9 @@ def postscript_char_filter(name): 'Light': ('Lt', 'Light'), ' ': (), # 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 'Compressed': ('Cm', 'Comp'), 'Extended': ('Ex', 'Extd'), @@ -268,6 +271,49 @@ def postscript_char_filter(name): 'Semi': ('Sm', 'Sem'), '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: + return "Thin" + if w < 250: + return "Extra-Light" + if w < 350: + return "Light" + if w < 450: + return "Regular" + if w < 550: + return "Medium" + if w < 650: + return "Semi-Bold" + if w < 750: + return "Bold" + if w < 850: + return "Extra-Bold" + return "Black" @staticmethod def is_keep_regular(basename): @@ -342,8 +388,7 @@ def parse_font_name(name): for s in list(FontnameTools.known_weights2) + list(FontnameTools.known_widths) for m in list(FontnameTools.known_modifiers) + [''] if m != s ] + list(FontnameTools.known_weights1) + list(FontnameTools.known_slopes) - styles = [ 'Bold', 'Italic', 'Regular', 'Normal', ] - weights = [ w for w in weights if w not in styles ] + weights = [ w for w in weights if w not in FontnameTools.known_styles ] # Some font specialities: other = [ '-', 'Book', 'For', 'Powerline', @@ -355,7 +400,7 @@ def parse_font_name(name): ] ( 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) while 'Regular' in style_token and len(style_token) > 1: # Correct situation where "Regular" and something else is given diff --git a/font-patcher b/font-patcher index 1d0f4a3e7d0..5efec62c261 100755 --- a/font-patcher +++ b/font-patcher @@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals # Change the script version when you edit this script: -script_version = "4.5.1" +script_version = "4.5.2" version = "3.0.2" projectName = "Nerd Fonts"