From 9be4835c29db2e1cf218ec3311cfd81ee3beb28d Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Wed, 19 Apr 2023 15:57:50 +0200 Subject: [PATCH] font-patcher: Introduce less severe family name shortening [why] We want to keep "Nerd Font" in the font name if possible and instead shorten the weight part with accepted abbreviations. But these abbrevs are hard to read and sometimes a more mild abbreviating might be sufficient to get the desired name length. [how] Introduce a new shortening method for the weight parts of a family name. It takes a longer word (often un-shortened) when a weight stands on its own, but when a modifier is used together with the weight the more aggressive two-letter abbreviations are used. That new shortening method becomes the default and all the functions get a new parameter to enforce completely aggressive shortening, i.e. always use the shortest possible form. The new way to shorten is exposed all the way out to the font-patcher user who can select the shortening method as parameter to the --makegroups option. That option is undocumented because I expect some changes later on, still. Signed-off-by: Fini Jastrow --- bin/scripts/name_parser/FontnameParser.py | 14 ++-- bin/scripts/name_parser/FontnameTools.py | 91 +++++++++++++---------- font-patcher | 24 +++--- 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/bin/scripts/name_parser/FontnameParser.py b/bin/scripts/name_parser/FontnameParser.py index 29dbb7fc3..4b97b6880 100644 --- a/bin/scripts/name_parser/FontnameParser.py +++ b/bin/scripts/name_parser/FontnameParser.py @@ -10,7 +10,7 @@ class FontnameParser: def __init__(self, filename, logger): """Parse a font filename and store the results""" self.parse_ok = False - self.use_short_families = (False, False) # ( camelcase name, short styles ) + self.use_short_families = (False, False, False) # ( camelcase name, short styles, aggressive ) self.keep_regular_in_family = None # None = auto, True, False self.suppress_preferred_if_identical = True self.family_suff = '' @@ -65,13 +65,13 @@ class FontnameParser: self.short_family_suff = short_family.strip() return self - def enable_short_families(self, camelcase_name, prefix): + def enable_short_families(self, camelcase_name, prefix, aggressive): """Enable short styles in Family when (original) font name starts with prefix; enable CamelCase basename in (Typog.) Family""" # camelcase_name is boolean # prefix is either a string or False/True if isinstance(prefix, str): prefix = self._basename.startswith(prefix) - self.use_short_families = ( camelcase_name, prefix ) + self.use_short_families = ( camelcase_name, prefix, aggressive ) return self def add_name_substitution_table(self, table): @@ -145,15 +145,15 @@ class FontnameParser: (weights, styles) = FontnameTools.make_oblique_style(weights, styles) (name, rest) = self._shortened_name() if self.use_short_families[1]: - [ weights, styles ] = FontnameTools.short_styles([ weights, styles ]) + [ weights, styles ] = FontnameTools.short_styles([ weights, styles ], self.use_short_families[2]) return FontnameTools.concat(name, rest, self.other_token, self.short_family_suff, weights, styles) def psname(self): """Get the SFNT PostScriptName (ID 6)""" # This is almost self.family() + '-' + self.subfamily() (name, rest) = self._shortened_name() - styles = FontnameTools.short_styles(self.style_token) - weights = FontnameTools.short_styles(self.weight_token) + styles = FontnameTools.short_styles(self.style_token, self.use_short_families[2]) + weights = FontnameTools.short_styles(self.weight_token, self.use_short_families[2]) fam = FontnameTools.camel_casify(FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff)) sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles)) if len(sub) > 0: @@ -190,7 +190,7 @@ class FontnameParser: other = self.other_token weight = self.weight_token if self.use_short_families[1]: - [ other, weight ] = FontnameTools.short_styles([ other, weight ]) + [ other, weight ] = FontnameTools.short_styles([ other, weight ], self.use_short_families[2]) return FontnameTools.concat(name, rest, other, self.short_family_suff, weight) def subfamily(self): diff --git a/bin/scripts/name_parser/FontnameTools.py b/bin/scripts/name_parser/FontnameTools.py index 9a0554648..ec9704f60 100644 --- a/bin/scripts/name_parser/FontnameTools.py +++ b/bin/scripts/name_parser/FontnameTools.py @@ -93,31 +93,38 @@ class FontnameTools: return None @staticmethod - def shorten_style_name(name): + def shorten_style_name(name, aggressive): """Substitude some known styles to short form""" + # If aggressive is False create the mild short form + # aggressive == True: Always use first form of everything + # aggressive == False: + # - has no modifier: use the second form + # - has modifier: use second form of mod plus first form of main name_rest = name name_pre = '' + form = 0 if aggressive else 1 for mod in FontnameTools.known_modifiers: if name.startswith(mod) and len(name) > len(mod): # Second condition specifically for 'Demi' - name_pre = FontnameTools.known_modifiers[mod] + name_pre = FontnameTools.known_modifiers[mod][form] name_rest = name[len(mod):] break + form = 0 if aggressive or len(name_pre) else 1 subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights2, FontnameTools.known_widths ]) - if isinstance(subst, str): - return name_pre + subst + if isinstance(subst, tuple): + return name_pre + subst[form] if not len(name_pre): # The following sets do not allow modifiers subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights1, FontnameTools.known_slopes ]) - if isinstance(subst, str): - return subst + if isinstance(subst, tuple): + return subst[form] return name @staticmethod - def short_styles(lists): + def short_styles(lists, aggressive): """Shorten all style names in a list or a list of lists""" if not len(lists) or not isinstance(lists[0], list): - return list(map(FontnameTools.shorten_style_name, lists)) - return [ list(map(FontnameTools.shorten_style_name, styles)) for styles in lists ] + return list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), lists)) + return [ list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles)) for styles in lists ] @staticmethod def make_oblique_style(weights, styles): @@ -198,44 +205,52 @@ class FontnameTools: ] # From https://adobe-type-tools.github.io/font-tech-notes/pdfs/5088.FontNames.pdf + # The first short variant is from the linked table. + # The second (longer) short variant is from diverse fonts like Noto. + # We can + # - use the long form + # - use the very short form (first) + # - use mild short form: + # - has no modifier: use the second form + # - has modifier: use second form of mod plus first form of main known_weights1 = { # can not take modifiers - 'Medium': 'Md', - 'Nord': 'Nd', - 'Book': 'Bk', - 'Poster': 'Po', - 'Demi': 'Dm', # Demi is sometimes used as a weight, sometimes as a modifier - 'Regular': 'Rg', - 'Display': 'DS', - 'Super': 'Su', - 'Retina': 'Rt', + 'Medium': ('Md', 'Med'), + 'Nord': ('Nd', 'Nord'), + 'Book': ('Bk', 'Book'), + 'Poster': ('Po', 'Poster'), + 'Demi': ('Dm', 'Demi'), # Demi is sometimes used as a weight, sometimes as a modifier + 'Regular': ('Rg', 'Reg'), + 'Display': ('DS', 'Disp'), + 'Super': ('Su', 'Sup'), + 'Retina': ('Rt', 'Ret'), } known_weights2 = { # can take modifiers - 'Black': 'Blk', - 'Bold': 'Bd', - 'Heavy': 'Hv', - 'Thin': 'Th', - 'Light': 'Lt', + 'Black': ('Blk', 'Black'), + 'Bold': ('Bd', 'Bold'), + 'Heavy': ('Hv', 'Heavy'), + 'Thin': ('Th', 'Thin'), + 'Light': ('Lt', 'Light'), } known_widths = { # can take modifiers - 'Compressed': 'Cm', - 'Extended': 'Ex', - 'Condensed': 'Cn', - 'Narrow': 'Nr', - 'Compact': 'Ct', + 'Compressed': ('Cm', 'Comp'), + 'Extended': ('Ex', 'Extd'), + 'Condensed': ('Cn', 'Cond'), + 'Narrow': ('Nr', 'Narrow'), + 'Compact': ('Ct', 'Compact'), } known_slopes = { - 'Inclined': 'Ic', - 'Oblique': 'Obl', - 'Italic': 'It', - 'Upright': 'Up', - 'Kursiv': 'Ks', - 'Sloped': 'Sl', + 'Inclined': ('Ic', 'Incl'), + 'Oblique': ('Obl', 'Obl'), + 'Italic': ('It', 'Italic'), + 'Upright': ('Up', 'Uprght'), + 'Kursiv': ('Ks', 'Kurs'), + 'Sloped': ('Sl', 'Slop'), } known_modifiers = { - 'Demi': 'Dm', - 'Ultra': 'Ult', - 'Semi': 'Sm', - 'Extra': 'X', + 'Demi': ('Dm', 'Dem'), + 'Ultra': ('Ult', 'Ult'), + 'Semi': ('Sm', 'Sem'), + 'Extra': ('X', 'Ext'), } @staticmethod diff --git a/font-patcher b/font-patcher index 3460ef6c0..1fc8f77fd 100755 --- a/font-patcher +++ b/font-patcher @@ -569,7 +569,7 @@ class font_patcher: if not n.parse_ok: logger.warning("Have only minimal naming information, check resulting name. Maybe omit --makegroups option") n.drop_for_powerline() - n.enable_short_families(True, self.args.makegroups in [ 1, 3]) + n.enable_short_families(True, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ]) # All the following stuff is ignored in makegroups-mode @@ -723,7 +723,7 @@ class font_patcher: font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname) font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) else: - short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups in [ 2, 3] else projectNameSingular + variant_full + short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full # inject_suffix(family, ps_fontname, short_family) n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family) n.rename_font(font) @@ -1805,14 +1805,18 @@ def setup_arguments(): parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)') parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching') - parser.add_argument('--makegroups', dest='makegroups', default=0, type=int, nargs='?', help='Use alternative method to name patched fonts (recommended)', const=3, choices=range(0, 4 + 1)) - # --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 3'. - # Possible values with examples: - # 0 - turned off, use old naming scheme - # 1 - turned on, shortening 'Bold' to 'Bd' - # 2 - turned on, shortening 'Nerd Font' to 'NF' - # 3 - turned on, shortening 'Nerd Font' to 'NF' and 'Bold' to 'Bd' - # 4 - turned on, no shortening + parser.add_argument('--makegroups', dest='makegroups', default=0, type=int, nargs='?', help='Use alternative method to name patched fonts (recommended)', const=1, choices=range(0, 6 + 1)) + # --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'. + # Original font name: Hugo Sans Mono ExtraCondensed Light Italic + # NF Fam agg. + # 0 turned off, use old naming scheme [-] [-] [-] + # 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ] + # 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ] + # 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X] + # 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ] + # 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ] + # 6 HugoSansMono NF XCn Lt It [X] [X] [X] + parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")') # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse