From 90ef76cd4a3fec3601d1dc23452c8798d32e1f3a Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Mon, 5 Sep 2022 18:40:36 -0500 Subject: [PATCH] update NF batteries --- bin/font-patcher | 276 ++++++++++++++++++++++++++++++++++---- src/glyphs/font-logos.ttf | Bin 29524 -> 29116 bytes 2 files changed, 249 insertions(+), 27 deletions(-) diff --git a/bin/font-patcher b/bin/font-patcher index 7d063ab..fe7d9e8 100755 --- a/bin/font-patcher +++ b/bin/font-patcher @@ -1,11 +1,14 @@ #!/usr/bin/env python # coding=utf8 -# Nerd Fonts Version: 2.1.0 -# script version: 3.0.1 +# Nerd Fonts Version: 2.2.1 +# Script version is further down from __future__ import absolute_import, print_function, unicode_literals -version = "2.1.0" +# Change the script version when you edit this script: +script_version = "3.0.3" + +version = "2.2.1" projectName = "Nerd Fonts" projectNameAbbreviation = "NF" projectNameSingular = projectName[:-1] @@ -36,6 +39,122 @@ except ImportError: ) ) +# This is for experimenting +sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])) + '/bin/scripts/name_parser/') +try: + from FontnameParser import FontnameParser + from FontnameTools import FontnameTools + FontnameParserOK = True +except ImportError: + FontnameParserOK = False + +class TableHEADWriter: + """ Access to the HEAD table without external dependencies """ + def getlong(self, pos = None): + """ Get four bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 24) + (ord(self.f.read(1)) << 16) + (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def getshort(self, pos = None): + """ Get two bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def putlong(self, num, pos = None): + """ Put number as four bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 24) & 0xFF, (num >> 16) & 0xFF ,(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def putshort(self, num, pos = None): + """ Put number as two bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def calc_checksum(self, start, end, checksum = 0): + """ Calculate a font table checksum, optionally ignoring another embedded checksum value (for table 'head') """ + self.f.seek(start) + for i in range(start, end - 4, 4): + checksum += self.getlong() + checksum &= 0xFFFFFFFF + i += 4 + extra = 0 + for j in range(4): + if i + j <= end: + extra += ord(self.f.read(1)) + extra = extra << 8 + checksum = (checksum + extra) & 0xFFFFFFFF + return checksum + + def find_head_table(self): + """ Search all tables for the HEAD table and store its metadata """ + self.f.seek(4) + numtables = self.getshort() + self.f.seek(3*2, 1) + + for i in range(numtables): + tab_name = self.f.read(4) + self.tab_check_offset = self.f.tell() + self.tab_check = self.getlong() + self.tab_offset = self.getlong() + self.tab_length = self.getlong() + if tab_name == b'head': + return + raise Exception('No HEAD table found') + + def goto(self, where): + """ Go to a named location in the file or to the specified index """ + if type(where) is str: + positions = {'checksumAdjustment': 2+2+4, + 'flags': 2+2+4+4+4, + 'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2, + } + where = self.tab_offset + positions[where] + self.f.seek(where) + + + def calc_full_checksum(self, check = False): + """ Calculate the whole file's checksum """ + self.f.seek(0, 2) + self.end = self.f.tell() + full_check = self.calc_checksum(0, self.end, (-self.checksum_adj) & 0xFFFFFFFF) + if check and (0xB1B0AFBA - full_check) & 0xFFFFFFFF != self.checksum_adj: + sys.exit("Checksum of whole font is bad") + return full_check + + def calc_table_checksum(self, check = False): + tab_check_new = self.calc_checksum(self.tab_offset, self.tab_offset + self.tab_length - 1, (-self.checksum_adj) & 0xFFFFFFFF) + if check and tab_check_new != self.tab_check: + sys.exit("Checksum of 'head' in font is bad") + return tab_check_new + + def reset_table_checksum(self): + new_check = self.calc_table_checksum() + self.putlong(new_check, self.tab_check_offset) + + def reset_full_checksum(self): + new_adj = (0xB1B0AFBA - self.calc_full_checksum()) & 0xFFFFFFFF + self.putlong(new_adj, 'checksumAdjustment') + + def close(self): + self.f.close() + + + def __init__(self, filename): + self.modified = False + self.f = open(filename, 'r+b') + + self.find_head_table() + + self.flags = self.getshort('flags') + self.lowppem = self.getshort('lowestRecPPEM') + self.checksum_adj = self.getlong('checksumAdjustment') + class font_patcher: def __init__(self): @@ -44,7 +163,6 @@ class font_patcher: self.config = None # class 'configparser.ConfigParser' self.sourceFont = None # class 'fontforge.font' self.octiconsExactEncodingPosition = True - self.fontlinuxExactEncodingPosition = True self.patch_set = None # class 'list' self.font_dim = None # class 'dict' self.onlybitmaps = 0 @@ -62,7 +180,8 @@ class font_patcher: self.sourceFont = fontforge.open(self.args.font, 1) # 1 = ("fstypepermitted",)) except Exception: sys.exit(projectName + ": Can not open font, try to open with fontforge interactively to get more information") - self.setup_font_names() + self.setup_version() + self.setup_name_backup() self.remove_ligatures() make_sure_path_exists(self.args.outputdir) self.check_position_conflicts() @@ -81,7 +200,7 @@ class font_patcher: def patch(self): - print("{} Patcher v{} executing\n".format(projectName, version)) + print("{} Patcher v{} ({}) executing\n".format(projectName, version, script_version)) if self.args.single: # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. @@ -124,19 +243,53 @@ class font_patcher: if symfont: symfont.close() - print("\nDone with Patch Sets, generating font...") + # The grave accent and fontforge: + # If the type is 'auto' fontforge changes it to 'mark' on export. + # We can not prevent this. So set it to 'baseglyph' instead, as + # that resembles the most common expectations. + # This is not needed with fontforge March 2022 Release anymore. + if "grave" in self.sourceFont: + self.sourceFont["grave"].glyphclass="baseglyph" + + + def generate(self): # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. if self.sourceFont.fullname != None: - self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.fullname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) - print("\nGenerated: {}".format(self.sourceFont.fontname)) + outfile = self.args.outputdir + "/" + self.sourceFont.fullname + self.extension + self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments'))) + message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile) else: - self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) - print("\nGenerated: {}".format(self.sourceFont.fullname)) + outfile = self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension + self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments'))) + message = "\nGenerated: {} in '{}'".format(self.sourceFont.fontname, outfile) + + # Adjust flags that can not be changed via fontforge + try: + source_font = TableHEADWriter(self.args.font) + dest_font = TableHEADWriter(outfile) + if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: + print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08)) + dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int' + if source_font.lowppem != dest_font.lowppem: + print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem)) + dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') + if dest_font.modified: + dest_font.reset_table_checksum() + dest_font.reset_full_checksum() + except Exception as error: + print("Can not handle font flags ({})".format(repr(error))) + finally: + try: + source_font.close() + dest_font.close() + except: + pass + print(message) if self.args.postprocess: - subprocess.call([self.args.postprocess, self.args.outputdir + "/" + self.sourceFont.fullname + self.extension]) - print("\nPost Processed: {}".format(self.sourceFont.fullname)) + subprocess.call([self.args.postprocess, outfile]) + print("\nPost Processed: {}".format(outfile)) def setup_arguments(self): @@ -166,12 +319,14 @@ class font_patcher: 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=False, action='store_true', help='Use alternative method to name patched fonts (experimental)') # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse progressbars_group_parser = parser.add_mutually_exclusive_group(required=False) progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set') progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') parser.set_defaults(progressbars=True) + parser.add_argument('--also-windows', dest='alsowindows', default=False, action='store_true', help='Create two fonts, the normal and the --windows version') # symbol fonts to include arguments sym_font_group = parser.add_argument_group('Symbol Fonts') @@ -189,6 +344,9 @@ class font_patcher: self.args = parser.parse_args() + if self.args.makegroups and not FontnameParserOK: + sys.exit(projectName + ": FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName)) + # if you add a new font, set it to True here inside the if condition if self.args.complete: self.args.fontawesome = True @@ -219,6 +377,9 @@ class font_patcher: font_complete = False self.args.complete = font_complete + if self.args.alsowindows: + self.args.windows = False + # this one also works but it needs to be updated every time a font is added # it was a conditional in self.setup_font_names() before, but it was missing # a symbol font, so it would name the font complete without being so sometimes. @@ -239,7 +400,17 @@ class font_patcher: # ]) + def setup_name_backup(self): + """ Store the original font names to be able to rename the font multiple times """ + self.original_fontname = self.sourceFont.fontname + self.original_fullname = self.sourceFont.fullname + self.original_familyname = self.sourceFont.familyname + + def setup_font_names(self): + self.sourceFont.fontname = self.original_fontname + self.sourceFont.fullname = self.original_fullname + self.sourceFont.familyname = self.original_familyname verboseAdditionalFontNameSuffix = " " + projectNameSingular if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later additionalFontNameSuffix = " " + projectNameAbbreviation @@ -285,6 +456,27 @@ class font_patcher: additionalFontNameSuffix += " M" verboseAdditionalFontNameSuffix += " Mono" + if FontnameParserOK and self.args.makegroups: + use_fullname = type(self.sourceFont.fullname) == str # Usually the fullname is better to parse + # Use fullname if it is 'equal' to the fontname + if self.sourceFont.fullname: + use_fullname |= self.sourceFont.fontname.lower() == FontnameTools.postscript_char_filter(self.sourceFont.fullname).lower() + # Use fullname for any of these source fonts (that are impossible to disentangle from the fontname, we need the blanks) + for hit in [ 'Meslo' ]: + use_fullname |= self.sourceFont.fontname.lower().startswith(hit.lower()) + parser_name = self.sourceFont.fullname if use_fullname else self.sourceFont.fontname + # Gohu fontnames hide the weight, but the file names are ok... + if parser_name.startswith('Gohu'): + parser_name = os.path.splitext(os.path.basename(self.args.font))[0] + n = FontnameParser(parser_name) + if not n.parse_ok: + print("Have only minimal naming information, check resulting name. Maybe omit --makegroups option") + n.drop_for_powerline() + n.enable_short_families(True, "Noto") + n.set_for_windows(self.args.windows) + + # All the following stuff is ignored in makegroups-mode + # basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold) # this does not seem very reliable so only use the style here as a fallback if the font does not # have an internal style defined (in sfnt_names) @@ -343,8 +535,9 @@ class font_patcher: familyname += " Mono" # Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with - # the same name as the same font, even if subFamily is different. - fontname += '-' + subFamily + # the same name as the same font, even if subFamily is different. Make sure to + # keep the resulting fontname (PostScript name) valid by removing spaces. + fontname += '-' + subFamily.replace(' ', '') # rename font # @@ -418,18 +611,28 @@ class font_patcher: fullname = replace_font_name(fullname, additionalFontNameReplacements2) fontname = replace_font_name(fontname, additionalFontNameReplacements2) - # replace any extra whitespace characters: - self.sourceFont.familyname = " ".join(familyname.split()) - self.sourceFont.fullname = " ".join(fullname.split()) - self.sourceFont.fontname = " ".join(fontname.split()) + if not (FontnameParserOK and self.args.makegroups): + # replace any extra whitespace characters: + self.sourceFont.familyname = " ".join(familyname.split()) + self.sourceFont.fullname = " ".join(fullname.split()) + self.sourceFont.fontname = " ".join(fontname.split()) + + self.sourceFont.appendSFNTName(str('English (US)'), str('Preferred Family'), self.sourceFont.familyname) + self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname) + self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname) + self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) + else: + fam_suffix = projectNameSingular if not self.args.windows else projectNameAbbreviation + fam_suffix += ' Mono' if self.args.single else '' + n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix) + n.rename_font(self.sourceFont) - self.sourceFont.appendSFNTName(str('English (US)'), str('Preferred Family'), self.sourceFont.familyname) - self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname) - self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname) - self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) self.sourceFont.comment = projectInfo self.sourceFont.fontlog = projectInfo + + def setup_version(self): + """ Add the Nerd Font version to the original version """ # print("Version was {}".format(sourceFont.version)) if self.sourceFont.version != None: self.sourceFont.version += ";" + projectName + " " + version @@ -463,8 +666,6 @@ class font_patcher: # Prevent glyph encoding position conflicts between glyph sets if self.args.fontawesome and self.args.octicons: self.octiconsExactEncodingPosition = False - if self.args.fontawesome or self.args.octicons: - self.fontlinuxExactEncodingPosition = False def setup_patch_set(self): @@ -603,7 +804,7 @@ class font_patcher: {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'SrcEnd': 0xFD46, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': self.fontlinuxExactEncodingPosition, 'SymStart': 0xF100, 'SymEnd': 0xF12D, 'SrcStart': 0xF300, 'SrcEnd': 0xF32D, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'SrcEnd': None , 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'SrcEnd': 0xF505, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap @@ -653,7 +854,9 @@ class font_patcher: # Ignore the y-values, os2_winXXXXX values set above are used for line height # # 0x00-0x17f is the Latin Extended-A range - for glyph in range(0x00, 0x17f): + for glyph in range(0x21, 0x17f): + if glyph in range(0x7F, 0xBF): + continue # ignore special characters like '1/4' etc try: (_, _, xmax, _) = self.sourceFont[glyph].boundingBox() except TypeError: @@ -665,6 +868,17 @@ class font_patcher: # Calculate font height self.font_dim['height'] = abs(self.font_dim['ymin']) + self.font_dim['ymax'] + if self.font_dim['height'] == 0: + # This can only happen if the input font is empty + # Assume we are using our prepared templates + self.font_dim = { + 'xmin' : 0, + 'ymin' : -self.sourceFont.descent, + 'xmax' : self.sourceFont.em, + 'ymax' : self.sourceFont.ascent, + 'width' : self.sourceFont.em, + 'height': abs(self.sourceFont.descent) + self.sourceFont.ascent, + } def get_scale_factor(self, sym_dim): @@ -1030,6 +1244,14 @@ def main(): check_fontforge_min_version() patcher = font_patcher() patcher.patch() + print("\nDone with Patch Sets, generating font...\n") + patcher.setup_font_names() + patcher.generate() + # This mainly helps to improve CI runtime + if patcher.args.alsowindows: + patcher.args.windows = True + patcher.setup_font_names() + patcher.generate() if __name__ == "__main__": diff --git a/src/glyphs/font-logos.ttf b/src/glyphs/font-logos.ttf index fa1175de0c1f4bba56bb6f2bf387be5aa83a557e..e8cea1c39dbe3927f2d27780a1b2582ef09bf545 100644 GIT binary patch delta 13252 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 delta 13800 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