mirror of
https://github.com/ryanoasis/nerd-fonts.git
synced 2024-09-19 09:51:48 +02:00
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 <ulf.fini.jastrow@desy.de>
This commit is contained in:
parent
ff2be6af81
commit
9be4835c29
3 changed files with 74 additions and 55 deletions
|
@ -10,7 +10,7 @@ class FontnameParser:
|
||||||
def __init__(self, filename, logger):
|
def __init__(self, filename, logger):
|
||||||
"""Parse a font filename and store the results"""
|
"""Parse a font filename and store the results"""
|
||||||
self.parse_ok = False
|
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.keep_regular_in_family = None # None = auto, True, False
|
||||||
self.suppress_preferred_if_identical = True
|
self.suppress_preferred_if_identical = True
|
||||||
self.family_suff = ''
|
self.family_suff = ''
|
||||||
|
@ -65,13 +65,13 @@ class FontnameParser:
|
||||||
self.short_family_suff = short_family.strip()
|
self.short_family_suff = short_family.strip()
|
||||||
return self
|
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"""
|
"""Enable short styles in Family when (original) font name starts with prefix; enable CamelCase basename in (Typog.) Family"""
|
||||||
# camelcase_name is boolean
|
# camelcase_name is boolean
|
||||||
# prefix is either a string or False/True
|
# prefix is either a string or False/True
|
||||||
if isinstance(prefix, str):
|
if isinstance(prefix, str):
|
||||||
prefix = self._basename.startswith(prefix)
|
prefix = self._basename.startswith(prefix)
|
||||||
self.use_short_families = ( camelcase_name, prefix )
|
self.use_short_families = ( camelcase_name, prefix, aggressive )
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_name_substitution_table(self, table):
|
def add_name_substitution_table(self, table):
|
||||||
|
@ -145,15 +145,15 @@ class FontnameParser:
|
||||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||||
(name, rest) = self._shortened_name()
|
(name, rest) = self._shortened_name()
|
||||||
if self.use_short_families[1]:
|
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)
|
return FontnameTools.concat(name, rest, self.other_token, self.short_family_suff, weights, styles)
|
||||||
|
|
||||||
def psname(self):
|
def psname(self):
|
||||||
"""Get the SFNT PostScriptName (ID 6)"""
|
"""Get the SFNT PostScriptName (ID 6)"""
|
||||||
# This is almost self.family() + '-' + self.subfamily()
|
# This is almost self.family() + '-' + self.subfamily()
|
||||||
(name, rest) = self._shortened_name()
|
(name, rest) = self._shortened_name()
|
||||||
styles = FontnameTools.short_styles(self.style_token)
|
styles = FontnameTools.short_styles(self.style_token, self.use_short_families[2])
|
||||||
weights = FontnameTools.short_styles(self.weight_token)
|
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))
|
fam = FontnameTools.camel_casify(FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff))
|
||||||
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
|
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
|
||||||
if len(sub) > 0:
|
if len(sub) > 0:
|
||||||
|
@ -190,7 +190,7 @@ class FontnameParser:
|
||||||
other = self.other_token
|
other = self.other_token
|
||||||
weight = self.weight_token
|
weight = self.weight_token
|
||||||
if self.use_short_families[1]:
|
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)
|
return FontnameTools.concat(name, rest, other, self.short_family_suff, weight)
|
||||||
|
|
||||||
def subfamily(self):
|
def subfamily(self):
|
||||||
|
|
|
@ -93,31 +93,38 @@ class FontnameTools:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def shorten_style_name(name):
|
def shorten_style_name(name, aggressive):
|
||||||
"""Substitude some known styles to short form"""
|
"""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_rest = name
|
||||||
name_pre = ''
|
name_pre = ''
|
||||||
|
form = 0 if aggressive else 1
|
||||||
for mod in FontnameTools.known_modifiers:
|
for mod in FontnameTools.known_modifiers:
|
||||||
if name.startswith(mod) and len(name) > len(mod): # Second condition specifically for 'Demi'
|
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):]
|
name_rest = name[len(mod):]
|
||||||
break
|
break
|
||||||
|
form = 0 if aggressive or len(name_pre) else 1
|
||||||
subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights2, FontnameTools.known_widths ])
|
subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights2, FontnameTools.known_widths ])
|
||||||
if isinstance(subst, str):
|
if isinstance(subst, tuple):
|
||||||
return name_pre + subst
|
return name_pre + subst[form]
|
||||||
if not len(name_pre):
|
if not len(name_pre):
|
||||||
# The following sets do not allow modifiers
|
# The following sets do not allow modifiers
|
||||||
subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights1, FontnameTools.known_slopes ])
|
subst = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights1, FontnameTools.known_slopes ])
|
||||||
if isinstance(subst, str):
|
if isinstance(subst, tuple):
|
||||||
return subst
|
return subst[form]
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def short_styles(lists):
|
def short_styles(lists, aggressive):
|
||||||
"""Shorten all style names in a list or a list of lists"""
|
"""Shorten all style names in a list or a list of lists"""
|
||||||
if not len(lists) or not isinstance(lists[0], list):
|
if not len(lists) or not isinstance(lists[0], list):
|
||||||
return list(map(FontnameTools.shorten_style_name, lists))
|
return list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), lists))
|
||||||
return [ list(map(FontnameTools.shorten_style_name, styles)) for styles in lists ]
|
return [ list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles)) for styles in lists ]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_oblique_style(weights, styles):
|
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
|
# 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
|
known_weights1 = { # can not take modifiers
|
||||||
'Medium': 'Md',
|
'Medium': ('Md', 'Med'),
|
||||||
'Nord': 'Nd',
|
'Nord': ('Nd', 'Nord'),
|
||||||
'Book': 'Bk',
|
'Book': ('Bk', 'Book'),
|
||||||
'Poster': 'Po',
|
'Poster': ('Po', 'Poster'),
|
||||||
'Demi': 'Dm', # Demi is sometimes used as a weight, sometimes as a modifier
|
'Demi': ('Dm', 'Demi'), # Demi is sometimes used as a weight, sometimes as a modifier
|
||||||
'Regular': 'Rg',
|
'Regular': ('Rg', 'Reg'),
|
||||||
'Display': 'DS',
|
'Display': ('DS', 'Disp'),
|
||||||
'Super': 'Su',
|
'Super': ('Su', 'Sup'),
|
||||||
'Retina': 'Rt',
|
'Retina': ('Rt', 'Ret'),
|
||||||
}
|
}
|
||||||
known_weights2 = { # can take modifiers
|
known_weights2 = { # can take modifiers
|
||||||
'Black': 'Blk',
|
'Black': ('Blk', 'Black'),
|
||||||
'Bold': 'Bd',
|
'Bold': ('Bd', 'Bold'),
|
||||||
'Heavy': 'Hv',
|
'Heavy': ('Hv', 'Heavy'),
|
||||||
'Thin': 'Th',
|
'Thin': ('Th', 'Thin'),
|
||||||
'Light': 'Lt',
|
'Light': ('Lt', 'Light'),
|
||||||
}
|
}
|
||||||
known_widths = { # can take modifiers
|
known_widths = { # can take modifiers
|
||||||
'Compressed': 'Cm',
|
'Compressed': ('Cm', 'Comp'),
|
||||||
'Extended': 'Ex',
|
'Extended': ('Ex', 'Extd'),
|
||||||
'Condensed': 'Cn',
|
'Condensed': ('Cn', 'Cond'),
|
||||||
'Narrow': 'Nr',
|
'Narrow': ('Nr', 'Narrow'),
|
||||||
'Compact': 'Ct',
|
'Compact': ('Ct', 'Compact'),
|
||||||
}
|
}
|
||||||
known_slopes = {
|
known_slopes = {
|
||||||
'Inclined': 'Ic',
|
'Inclined': ('Ic', 'Incl'),
|
||||||
'Oblique': 'Obl',
|
'Oblique': ('Obl', 'Obl'),
|
||||||
'Italic': 'It',
|
'Italic': ('It', 'Italic'),
|
||||||
'Upright': 'Up',
|
'Upright': ('Up', 'Uprght'),
|
||||||
'Kursiv': 'Ks',
|
'Kursiv': ('Ks', 'Kurs'),
|
||||||
'Sloped': 'Sl',
|
'Sloped': ('Sl', 'Slop'),
|
||||||
}
|
}
|
||||||
known_modifiers = {
|
known_modifiers = {
|
||||||
'Demi': 'Dm',
|
'Demi': ('Dm', 'Dem'),
|
||||||
'Ultra': 'Ult',
|
'Ultra': ('Ult', 'Ult'),
|
||||||
'Semi': 'Sm',
|
'Semi': ('Sm', 'Sem'),
|
||||||
'Extra': 'X',
|
'Extra': ('X', 'Ext'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
24
font-patcher
24
font-patcher
|
@ -569,7 +569,7 @@ class font_patcher:
|
||||||
if not n.parse_ok:
|
if not n.parse_ok:
|
||||||
logger.warning("Have only minimal naming information, check resulting name. Maybe omit --makegroups option")
|
logger.warning("Have only minimal naming information, check resulting name. Maybe omit --makegroups option")
|
||||||
n.drop_for_powerline()
|
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
|
# 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('Compatible Full'), font.fullname)
|
||||||
font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
|
font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
|
||||||
else:
|
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)
|
# inject_suffix(family, ps_fontname, short_family)
|
||||||
n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family)
|
n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family)
|
||||||
n.rename_font(font)
|
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('-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('-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('--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))
|
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 3'.
|
# --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'.
|
||||||
# Possible values with examples:
|
# Original font name: Hugo Sans Mono ExtraCondensed Light Italic
|
||||||
# 0 - turned off, use old naming scheme
|
# NF Fam agg.
|
||||||
# 1 - turned on, shortening 'Bold' to 'Bd'
|
# 0 turned off, use old naming scheme [-] [-] [-]
|
||||||
# 2 - turned on, shortening 'Nerd Font' to 'NF'
|
# 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ]
|
||||||
# 3 - turned on, shortening 'Nerd Font' to 'NF' and 'Bold' to 'Bd'
|
# 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ]
|
||||||
# 4 - turned on, no shortening
|
# 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")')
|
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
|
# progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
|
||||||
|
|
Loading…
Reference in a new issue