diff --git a/bin/scripts/name_parser/FontnameParser.py b/bin/scripts/name_parser/FontnameParser.py index 544452a..2c5aff4 100644 --- a/bin/scripts/name_parser/FontnameParser.py +++ b/bin/scripts/name_parser/FontnameParser.py @@ -7,8 +7,8 @@ from FontnameTools import FontnameTools class FontnameParser: """Parse a font name and generate all kinds of names""" - def __init__(self, filename, logger): - """Parse a font filename and store the results""" + def __init__(self, fontname, logger): + """Parse a fontname and store the results""" self.parse_ok = False self.use_short_families = (False, False, False) # ( camelcase name, short styles, aggressive ) self.keep_regular_in_family = None # None = auto, True, False @@ -17,7 +17,7 @@ class FontnameParser: self.ps_fontname_suff = '' self.short_family_suff = '' self.name_subst = [] - [ self.parse_ok, self._basename, self.weight_token, self.style_token, self.other_token, self._rest ] = FontnameTools.parse_font_name(filename) + [ self.parse_ok, self._basename, self.weight_token, self.style_token, self.other_token, self._rest ] = FontnameTools.parse_font_name(fontname) self.basename = self._basename self.rest = self._rest self.add_name_substitution_table(FontnameTools.SIL_TABLE) @@ -252,6 +252,9 @@ class FontnameParser: # Ignore Italic if we have Oblique if 'Oblique' in self.weight_token: b |= OBLIQUE + if not self.rename_oblique: + # If we have no dedicated italic, than oblique = italic + b |= ITALIC elif 'Italic' in self.style_token: b |= ITALIC # Regular is just the basic weight diff --git a/bin/scripts/name_parser/FontnameTools.py b/bin/scripts/name_parser/FontnameTools.py index f4a9c13..a2bfc90 100644 --- a/bin/scripts/name_parser/FontnameTools.py +++ b/bin/scripts/name_parser/FontnameTools.py @@ -5,7 +5,7 @@ import re import sys class FontnameTools: - """Deconstruct a font filename to get standardized name parts""" + """Deconstruct a fontname to get standardized name parts""" @staticmethod def front_upper(word): @@ -64,24 +64,11 @@ class FontnameTools: known_names = { # Source of the table is the current sourcefonts # Left side needs to be lower case - '-': '', 'book': '', - 'text': '', 'ce': 'CE', - #'semibold': 'Demi', - 'ob': 'Oblique', - 'it': 'Italic', - 'i': 'Italic', - 'b': 'Bold', 'normal': 'Regular', - 'c': 'Condensed', - 'r': 'Regular', - 'm': 'Medium', - 'l': 'Light', } - if style_name in known_names: - return known_names[style_name.lower()] - return style_name + return known_names.get(style_name.lower(), style_name) @staticmethod def find_in_dicts(key, dicts): @@ -146,7 +133,7 @@ class FontnameTools: return (weights, styles) @staticmethod - def get_name_token(name, tokens, allow_regex_token = False): + def get_name_token(name, tokens): """Try to find any case insensitive token from tokens in the name, return tuple with found token-list and rest""" # The default mode (allow_regex_token = False) will try to find any verbatim string in the # tokens list (case insensitive matching) and give that tokens list item back with @@ -160,7 +147,11 @@ class FontnameTools: not_matched = "" all_tokens = [] j = 1 - regex = re.compile('(.*?)(' + '|'.join(tokens) + ')(.*)', re.IGNORECASE) + token_regex = '|'.join(tokens) + # Allow a dash between CamelCase token word parts, i.e. Camel-Case + # This allows for styles like Extra-Bold + token_regex = re.sub(r'(?<=[a-z])(?=[A-Z])', '-?', token_regex) + regex = re.compile('(.*?)(' + token_regex + ')(.*)', re.IGNORECASE) while j: j = regex.match(name) if not j: @@ -169,6 +160,7 @@ class FontnameTools: sys.exit('Malformed regex in FontnameTools.get_name_token()') not_matched += ' ' + j.groups()[0] # Blanc prevents unwanted concatenation of unmatched substrings tok = j.groups()[1].lower() + tok = tok.replace('-', '') # Remove dashes between CamelCase token words if tok in lower_tokens: tok = tokens[lower_tokens.index(tok)] tok = FontnameTools.unify_style_names(tok) @@ -238,6 +230,7 @@ class FontnameTools: 'Medium': ('Md', 'Med'), 'Nord': ('Nd', 'Nord'), 'Book': ('Bk', 'Book'), + 'Text': ('Txt', 'Text'), 'Poster': ('Po', 'Poster'), 'Demi': ('Dm', 'Demi'), # Demi is sometimes used as a weight, sometimes as a modifier 'Regular': ('Rg', 'Reg'), @@ -306,8 +299,9 @@ class FontnameTools: @staticmethod def _parse_simple_font_name(name): - """Parse a filename that does not follow the 'FontFamilyName-FontStyle' pattern""" - # No dash in name, maybe we have blanc separated filename? + """Parse a fontname that does not follow the 'FontFamilyName-FontStyle' pattern""" + # This is the usual case, because the font-patcher usually uses the fullname and + # not the PS name if ' ' in name: return FontnameTools.parse_font_name(name.replace(' ', '-')) # Do we have a number-name boundary? @@ -322,8 +316,15 @@ class FontnameTools: @staticmethod def parse_font_name(name): - """Expects a filename following the 'FontFamilyName-FontStyle' pattern and returns ... parts""" - name = re.sub(r'\bsemi-condensed\b', 'SemiCondensed', name, 1, re.IGNORECASE) # Just for "3270 Semi-Condensed" :-/ + """Expects a fontname following the 'FontFamilyName-FontStyle' pattern and returns ... parts""" + # This could parse filenames in the beginning but that was never used in production; code removed with this commit + for special in [ + ('ExtLt', 'ExtraLight'), # IBM-Plex + ('Medm', 'Medium'), # IBM-Plex + ('Semi-Condensed', 'SemiCondensed'), # 3270 + ('SmBld', 'SemiBold'), # IBM-Plex + ]: + name = re.sub(r'\b' + special[0] + r'\b', special[1], name, 1, re.IGNORECASE) name = re.sub('[_\s]+', ' ', name) matches = re.match(r'([^-]+)(?:-(.*))?', name) familyname = FontnameTools.camel_casify(matches.group(1)) @@ -345,7 +346,6 @@ class FontnameTools: # Some font specialities: other = [ '-', 'Book', 'For', 'Powerline', - 'Text', # Plex 'IIx', # Profont IIx 'LGC', # Inconsolata LGC r'\bCE\b', # ProggycleanTT CE @@ -353,19 +353,9 @@ class FontnameTools: r'(?:uni-)?1[14]', # GohuFont uni ] - # Sometimes used abbreviations - weight_abbrevs = [ 'ob', 'c', 'm', 'l', ] - style_abbrevs = [ 'it', 'r', 'b', 'i', ] - ( style, weight_token ) = FontnameTools.get_name_token(style, weights) ( style, style_token ) = FontnameTools.get_name_token(style, styles) - ( style, other_token ) = FontnameTools.get_name_token(style, other, True) - if (len(style) < 4 - and style.lower() != 'pro'): # Prevent 'r' of Pro to be detected as style_abbrev - ( style, weight_token_abbrevs ) = FontnameTools.get_name_token(style, weight_abbrevs) - ( style, style_token_abbrevs ) = FontnameTools.get_name_token(style, style_abbrevs) - weight_token += weight_token_abbrevs - style_token += style_token_abbrevs + ( 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 style_token.remove('Regular') diff --git a/font-patcher b/font-patcher index ede9298..8119752 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.3.1" +script_version = "4.3.4" version = "3.0.1" projectName = "Nerd Fonts" @@ -250,11 +250,11 @@ def force_panose_monospaced(font): def get_advance_width(font, extended, minimum): """ Get the maximum/minimum advance width in the extended(?) range """ width = 0 - if extended: - end = 0x17f + if not extended: + r = range(0x021, 0x07e) else: - end = 0x07e - for glyph in range(0x21, end): + r = range(0x07f, 0x17f) + for glyph in r: if not glyph in font: continue if glyph in range(0x7F, 0xBF): @@ -270,8 +270,8 @@ def get_advance_width(font, extended, minimum): def report_advance_widths(font): return "Advance widths (base/extended): {} - {} / {} - {}".format( - get_advance_width(font, True, True), get_advance_width(font, False, True), - get_advance_width(font, False, False), get_advance_width(font, True, False)) + get_advance_width(font, False, True), get_advance_width(font, False, False), + get_advance_width(font, True, True), get_advance_width(font, True, False)) def get_btb_metrics(font): """ Get the baseline to baseline distance for all three metrics """ @@ -765,16 +765,18 @@ class font_patcher: if self.args.nonmono: return panose_mono = check_panose_monospaced(self.sourceFont) + logger.debug("Monospace check: %s; glyph-width-mono %s", + panose_check_to_text(panose_mono, self.sourceFont.os2_panose), repr(width_mono)) # The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown' if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1): logger.warning("Monospaced check: Panose assumed to be wrong") - logger.warning(" %s and %s", + logger.warning("Monospaced check: %s and %s", report_advance_widths(self.sourceFont), panose_check_to_text(panose_mono, self.sourceFont.os2_panose)) if self.args.single and not width_mono: - logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless") - if offending_char is not None: - logger.warning(" Offending char: %X", offending_char) + logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, " + "results might be useless%s", + " - offending char: {:X}".format(offending_char) if offending_char is not None else "") if self.args.single <= 1: logger.critical("Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching") sys.exit(1) @@ -920,6 +922,7 @@ class font_patcher: # with the same scaling and shifting. The basis for it is a 'combined # bounding box' of all glyphs in that group. All glyphs are handled as # if they fill that combined bounding box. + # (- ScaleGroupsVert: Removed with this commit) # # The ScaleGlyph method: You set 'ScaleGlyph' to the unicode of the reference glyph. # Note that there can be only one per patch-set. @@ -955,14 +958,8 @@ class font_patcher: range(0x2594, 0x259f + 1), # quards (Note: quard 2597 in Hack is wrong, scales like block!) ]} CODI_SCALE_LIST = {'ScaleGroups': [ - range(0xea99, 0xeaa1 + 1), # arrows range(0xeb6e, 0xeb71 + 1), # triangles range(0xeab4, 0xeab7 + 1), # chevrons - [0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles - [0xeacc, 0xeaba], # dash - [0xea75, 0xebe7], # lock pair - [0xeacf, 0xebe0], # debug-continue pair - [0xeb91, 0xeba8], # debug-alt pair ]} DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo 'GlyphsToScale': [ @@ -1205,6 +1202,7 @@ class font_patcher: 0x132, 0x133, # IJ, ij (in Overpass Mono) 0x022, 0x027, 0x060, # Single and double quotes in Inconsolata LGC 0x0D0, 0x10F, 0x110, 0x111, 0x127, 0x13E, 0x140, 0x165, # Eth and others with stroke or caron in RobotoMono + 0x149, # napostrophe in DaddyTimeMono 0x02D, # hyphen for Monofur ]: continue # ignore special characters like '1/4' etc and some specifics @@ -1216,8 +1214,8 @@ class font_patcher: if self.font_dim['width'] < self.sourceFont[glyph].width: self.font_dim['width'] = self.sourceFont[glyph].width if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z - logger.debug("Extended glyphs wider than basic glyphs, results might be useless\n %s", - report_advance_widths(self.sourceFont)) + logger.debug("Extended glyphs wider than basic glyphs, results might be useless") + logger.debug("%s", report_advance_widths(self.sourceFont)) warned1 = True # print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) if xmax > self.font_dim['xmax']: @@ -1229,7 +1227,7 @@ class font_patcher: if self.font_dim['width'] < self.font_dim['xmax']: logger.debug("Font has negative right side bearing in extended glyphs") self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used - # print("FINAL", self.font_dim) + logger.debug("Final font cell dimensions %d w x %d h", self.font_dim['width'], self.font_dim['height']) self.xavgwidth.append(self.args.xavgwidth) if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: