From 9eb507579ae880f3ea251fbd54916ad2c0780af2 Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Thu, 13 Oct 2022 10:33:48 -0500 Subject: [PATCH] update NF batteries --- bin/font-patcher | 638 +++++++++++++++++++-------------- src/glyphs/original-source.otf | Bin 57768 -> 50032 bytes 2 files changed, 376 insertions(+), 262 deletions(-) diff --git a/bin/font-patcher b/bin/font-patcher index 435c2ed..c245d3b 100755 --- a/bin/font-patcher +++ b/bin/font-patcher @@ -1,14 +1,14 @@ #!/usr/bin/env python # coding=utf8 -# Nerd Fonts Version: 2.2.2 +# Nerd Fonts Version: 2.3.0-RC # Script version is further down from __future__ import absolute_import, print_function, unicode_literals # Change the script version when you edit this script: -script_version = "3.0.6" +script_version = "3.1.1" -version = "2.2.2" +version = "2.3.0-RC" projectName = "Nerd Fonts" projectNameAbbreviation = "NF" projectNameSingular = projectName[:-1] @@ -82,15 +82,32 @@ class TableHEADWriter: i += 4 extra = 0 for j in range(4): + extra = extra << 8 if i + j <= end: extra += ord(self.f.read(1)) - extra = extra << 8 checksum = (checksum + extra) & 0xFFFFFFFF return checksum - def find_head_table(self): + def find_head_table(self, idx): """ Search all tables for the HEAD table and store its metadata """ - self.f.seek(4) + # Use font with index idx if this is a font collection file + self.f.seek(0, 0) + tag = self.f.read(4) + if tag == b'ttcf': + self.f.seek(2*2, 1) + self.num_fonts = self.getlong() + if (idx >= self.num_fonts): + raise Exception('Trying to access subfont index {} but have only {} fonts'.format(idx, num_fonts)) + for _ in range(idx + 1): + offset = self.getlong() + self.f.seek(offset, 0) + elif idx != 0: + raise Exception('Trying to access subfont but file is no collection') + else: + self.f.seek(0, 0) + self.num_fonts = 1 + + self.f.seek(4, 1) numtables = self.getshort() self.f.seek(3*2, 1) @@ -102,7 +119,7 @@ class TableHEADWriter: self.tab_length = self.getlong() if tab_name == b'head': return - raise Exception('No HEAD table found') + raise Exception('No HEAD table found in font idx {}'.format(idx)) def goto(self, where): """ Go to a named location in the file or to the specified index """ @@ -146,60 +163,97 @@ class TableHEADWriter: self.modified = False self.f = open(filename, 'r+b') - self.find_head_table() + self.find_head_table(0) self.flags = self.getshort('flags') self.lowppem = self.getshort('lowestRecPPEM') self.checksum_adj = self.getlong('checksumAdjustment') +def check_panose_monospaced(font): + """ Check if the font's Panose flags say it is monospaced """ + # https://forum.high-logic.com/postedfiles/Panose.pdf + panose = list(font.os2_panose) + if panose[0] < 2 or panose[0] > 5: + return -1 # invalid Panose info + panose_mono = ((panose[0] == 2 and panose[3] == 9) or + (panose[0] == 3 and panose[3] == 3)) + return 1 if panose_mono else 0 + +def is_monospaced(font): + """ Check if a font is probably monospaced """ + # Some fonts lie (or have not any Panose flag set), spot check monospaced: + width = -1 + width_mono = True + for glyph in [ 0x49, 0x4D, 0x57, 0x61, 0x69, 0x2E ]: # wide and slim glyphs 'I', 'M', 'W', 'a', 'i', '.' + if not glyph in font: + # A 'strange' font, believe Panose + return check_panose_monospaced(font) == 1 + # print(" -> {} {}".format(glyph, font[glyph].width)) + if width < 0: + width = font[glyph].width + continue + if font[glyph].width != width: + # Exception for fonts like Code New Roman Regular or Hermit Light/Bold: + # Allow small 'i' and dot to be smaller than normal + # I believe the source fonts are buggy + if glyph in [ 0x69, 0x2E ]: + if width > font[glyph].width: + continue + (xmin, _, xmax, _) = font[glyph].boundingBox() + if width > xmax - xmin: + continue + width_mono = False + break + # We believe our own check more then Panose ;-D + return width_mono + +def get_advance_width(font, extended, minimum): + """ Get the maximum/minimum advance width in the extended(?) range """ + width = 0 + if extended: + end = 0x17f + else: + end = 0x07e + for glyph in range(0x21, end): + if not glyph in font: + continue + if glyph in range(0x7F, 0xBF): + continue # ignore special characters like '1/4' etc + if width == 0: + width = font[glyph].width + continue + if not minimum and width < font[glyph].width: + width = font[glyph].width + elif minimum and width > font[glyph].width: + width = font[glyph].width + return width + class font_patcher: - def __init__(self): - self.args = None # class 'argparse.Namespace' + def __init__(self, args): + self.args = args # class 'argparse.Namespace' self.sym_font_args = [] self.config = None # class 'configparser.ConfigParser' self.sourceFont = None # class 'fontforge.font' - self.octiconsExactEncodingPosition = True self.patch_set = None # class 'list' self.font_dim = None # class 'dict' self.onlybitmaps = 0 - self.extension = "" self.essential = set() - self.setup_arguments() self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) - if not os.path.isfile(self.args.font): - sys.exit("{}: Font file does not exist: {}".format(projectName, self.args.font)) - if not os.access(self.args.font, os.R_OK): - sys.exit("{}: Can not open font file for reading: {}".format(projectName, self.args.font)) - if len(fontforge.fontsInFile(self.args.font)) > 1: - sys.exit("{}: Font file contains {} fonts, can only handle single font files".format(projectName, - len(fontforge.fontsInFile(self.args.font)))) - try: - 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") + + def patch(self, font): + self.sourceFont = font self.setup_version() self.get_essential_references() - self.setup_name_backup() + self.setup_name_backup(font) + if self.args.single: + self.assert_monospace() self.remove_ligatures() - make_sure_path_exists(self.args.outputdir) - self.check_position_conflicts() self.setup_patch_set() self.setup_line_dimensions() self.get_sourcefont_dimensions() self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used - if self.args.extension == "": - self.extension = os.path.splitext(self.args.font)[1] - else: - self.extension = '.' + self.args.extension - if re.match("\.ttc$", self.extension, re.IGNORECASE): - sys.exit(projectName + ": Can not create True Type Collections") - - - def patch(self): - - 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. @@ -241,14 +295,11 @@ class font_patcher: symfont.em = self.sourceFont.em PreviousSymbolFilename = patch['Filename'] - # If patch table doesn't include a source start and end, re-use the symbol font values + # If patch table doesn't include a source start, re-use the symbol font values SrcStart = patch['SrcStart'] - SrcEnd = patch['SrcEnd'] if not SrcStart: SrcStart = patch['SymStart'] - if not SrcEnd: - SrcEnd = patch['SymEnd'] - self.copy_glyphs(SrcStart, SrcEnd, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes']) + self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes']) if symfont: symfont.close() @@ -262,30 +313,49 @@ class font_patcher: 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: - 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) + def generate(self, sourceFonts): + sourceFont = sourceFonts[0] + if len(sourceFonts) > 1: + layer = None + # use first non-background layer + for l in sourceFont.layers: + if not sourceFont.layers[l].is_background: + layer = l + break + outfile = os.path.normpath(os.path.join(self.args.outputdir, sourceFont.familyname + ".ttc")) + # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. + sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=(str('opentype'), str('PfEd-comments')), layer=layer) + message = "\nGenerated: {} fonts in '{}'".format(len(sourceFonts), outfile) else: - 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) + fontname = sourceFont.fullname + if not fontname: + fontname = sourceFont.cidfontname + outfile = os.path.normpath(os.path.join(self.args.outputdir, fontname + self.args.extension)) + # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. + bitmaps = str() + if len(self.sourceFont.bitmapSizes): + print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) + bitmaps = str('otf') # otf/ttf, both is bf_ttf + sourceFont.generate(outfile, bitmap_type=bitmaps, flags=(str('opentype'), str('PfEd-comments'))) + message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, 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() + for idx in range(source_font.num_fonts): + print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts)) + source_font.find_head_table(idx) + dest_font.find_head_table(idx) + 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: @@ -301,130 +371,19 @@ class font_patcher: print("\nPost Processed: {}".format(outfile)) - def setup_arguments(self): - parser = argparse.ArgumentParser( - description=( - 'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n' - '* Website: https://www.nerdfonts.com\n' - '* Version: ' + version + '\n' - '* Development Website: https://github.com/ryanoasis/nerd-fonts\n' - '* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md'), - formatter_class=RawTextHelpFormatter - ) - - # optional arguments - parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)') - parser.add_argument('-v', '--version', action='version', version=projectName + ": %(prog)s (" + version + ")") - parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='store_true', help='Whether to generate the glyphs as single-width not double-width (default is double-width)') - parser.add_argument('-l', '--adjust-line-height', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)') - parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output') - parser.add_argument('-w', '--windows', dest='windows', default=False, action='store_true', help='Limit the internal font name to 31 characters (for Windows compatibility)') - parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs') - parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected') - parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file') - parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing') - parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)') - parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font. All new glyphs will be copied, with no scaling applied.') - 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)') - 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 - 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') - sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') - sym_font_group.add_argument('--fontawesomeextension', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)') - sym_font_group.add_argument('--fontlogos', '--fontlinux', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)') - sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') - sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)') - sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') - sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)') - sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs') - sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)') - sym_font_group.add_argument('--material', '--materialdesignicons', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)') - sym_font_group.add_argument('--weather', '--weathericons', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)') - - 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 - self.args.fontawesomeextension = True - self.args.fontlogos = True - self.args.octicons = True - self.args.codicons = True - self.args.powersymbols = True - self.args.pomicons = True - self.args.powerline = True - self.args.powerlineextra = True - self.args.material = True - self.args.weather = True - - if not self.args.complete: - # add the list of arguments for each symbol font to the list self.sym_font_args - for action in sym_font_group._group_actions: - self.sym_font_args.append(action.__dict__['option_strings']) - - # determine whether or not all symbol fonts are to be used - font_complete = True - for sym_font_arg_aliases in self.sym_font_args: - found = False - for alias in sym_font_arg_aliases: - if alias in sys.argv: - found = True - if found is not True: - font_complete = False - self.args.complete = font_complete - - if self.args.alsowindows: - self.args.windows = False - - if self.args.nonmono and self.args.single: - print("Warniung: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.") - self.args.nonmono = 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. - # that's why i did the above. - # - # if you add a new font, put it in here too, as the others are - # self.args.complete = all([ - # self.args.fontawesome is True, - # self.args.fontawesomeextension is True, - # self.args.fontlogos is True, - # self.args.octicons is True, - # self.args.powersymbols is True, - # self.args.pomicons is True, - # self.args.powerline is True, - # self.args.powerlineextra is True, - # self.args.material is True, - # self.args.weather is True - # ]) - - - def setup_name_backup(self): + def setup_name_backup(self, font): """ 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 + font.persistent = { + "fontname": font.fontname, + "fullname": font.fullname, + "familyname": font.familyname, + } - def setup_font_names(self): - self.sourceFont.fontname = self.original_fontname - self.sourceFont.fullname = self.original_fullname - self.sourceFont.familyname = self.original_familyname + def setup_font_names(self, font): + font.fontname = font.persistent["fontname"] + font.fullname = font.persistent["fullname"] + font.familyname = font.persistent["familyname"] verboseAdditionalFontNameSuffix = " " + projectNameSingular if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later additionalFontNameSuffix = " " + projectNameAbbreviation @@ -471,14 +430,14 @@ class font_patcher: verboseAdditionalFontNameSuffix += " Mono" if FontnameParserOK and self.args.makegroups: - use_fullname = type(self.sourceFont.fullname) == str # Usually the fullname is better to parse + use_fullname = type(font.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() + if font.fullname: + use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.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 + use_fullname |= font.fontname.lower().startswith(hit.lower()) + parser_name = font.fullname if use_fullname else font.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] @@ -496,16 +455,16 @@ class font_patcher: # have an internal style defined (in sfnt_names) # using '([^-]*?)' to get the item before the first dash "-" # using '([^-]*(?!.*-))' to get the item after the last dash "-" - fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", self.sourceFont.fontname).groups() + fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", font.fontname).groups() - # dont trust 'sourceFont.familyname' + # dont trust 'font.familyname' familyname = fontname # fullname (filename) can always use long/verbose font name, even in windows - if self.sourceFont.fullname != None: - fullname = self.sourceFont.fullname + verboseAdditionalFontNameSuffix + if font.fullname != None: + fullname = font.fullname + verboseAdditionalFontNameSuffix else: - fullname = self.sourceFont.cidfontname + verboseAdditionalFontNameSuffix + fullname = font.cidfontname + verboseAdditionalFontNameSuffix fontname = fontname + additionalFontNameSuffix.replace(" ", "") @@ -513,13 +472,13 @@ class font_patcher: # parse fontname if it fails: try: # search tuple: - subFamilyTupleIndex = [x[1] for x in self.sourceFont.sfnt_names].index("SubFamily") + subFamilyTupleIndex = [x[1] for x in font.sfnt_names].index("SubFamily") # String ID is at the second index in the Tuple lists sfntNamesStringIDIndex = 2 # now we have the correct item: - subFamily = self.sourceFont.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex] + subFamily = font.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex] except IndexError: sys.stderr.write("{}: Could not find 'SubFamily' for given font, falling back to parsed fontname\n".format(projectName)) subFamily = fallbackStyle @@ -629,22 +588,22 @@ class font_patcher: 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()) + font.familyname = " ".join(familyname.split()) + font.fullname = " ".join(fullname.split()) + font.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) + font.appendSFNTName(str('English (US)'), str('Preferred Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname) + font.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) + n.rename_font(font) - self.sourceFont.comment = projectInfo - self.sourceFont.fontlog = projectInfo + font.comment = projectInfo + font.fontlog = projectInfo def setup_version(self): @@ -678,10 +637,19 @@ class font_patcher: print("No configfile given, skipping configfile related actions") - def check_position_conflicts(self): - # Prevent glyph encoding position conflicts between glyph sets - if self.args.fontawesome and self.args.octicons: - self.octiconsExactEncodingPosition = False + def assert_monospace(self): + # Check if the sourcefont is monospaced + width_mono = is_monospaced(self.sourceFont) + panose_mono = check_panose_monospaced(self.sourceFont) + # 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): + print(" Warning: Monospaced check: Panose assumed to be wrong") + print(" Glyph widths {} / {} - {} and Panose says \"monospace {}\" ({})".format(get_advance_width(self.sourceFont, False, True), + get_advance_width(self.sourceFont, False, False), get_advance_width(self.sourceFont, True, False), panose_mono, list(self.sourceFont.os2_panose))) + if not width_mono: + print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless") + if self.args.single <= 1: + sys.exit(projectName + ": Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching") def setup_patch_set(self): @@ -805,28 +773,28 @@ class font_patcher: # Define the character ranges # Symbol font ranges self.patch_set = [ - {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE534, 'SrcStart': 0xE5FA, 'SrcEnd': 0xE634, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'SrcEnd': 0xE7C5, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, - {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, - {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'SrcEnd': 0xE2A9, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize - {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep - {'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.fontlogos, 'Name': "Font Logos", '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 - {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'SrcEnd': 0xF4A9, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop - {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': 0x0000, 'SrcEnd': 0x0000, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR} + {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE534, 'SrcStart': 0xE5FA, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, + {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': 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, '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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop + {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR} ] def setup_line_dimensions(self): @@ -846,11 +814,6 @@ class font_patcher: self.sourceFont.hhea_ascent = self.sourceFont.os2_winascent self.sourceFont.hhea_descent = -self.sourceFont.os2_windescent - # Line gap add extra space on the bottom of the line which - # doesn't allow the powerline glyphs to fill the entire line. - self.sourceFont.hhea_linegap = 0 - self.sourceFont.os2_typolinegap = 0 - def get_essential_references(self): """Find glyphs that are needed for the basic glyphs""" # Sometimes basic glyphs are constructed from multiple other glyphs. @@ -877,6 +840,38 @@ class font_patcher: self.font_dim['ymin'] = self.sourceFont.os2_typodescent self.font_dim['ymax'] = self.sourceFont.os2_typoascent + # Calculate font height + self.font_dim['height'] = -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': self.sourceFont.descent + self.sourceFont.ascent, + } + + # Line gap add extra space on the bottom of the line which + # doesn't allow the powerline glyphs to fill the entire line. + # Put half of the gap into the 'cell', each top and bottom + gap = max(self.sourceFont.hhea_linegap, self.sourceFont.os2_typolinegap) # TODO probably wrong + if self.sourceFont.os2_use_typo_metrics: + gap = self.sourceFont.os2_typolinegap + self.sourceFont.hhea_linegap = 0 + self.sourceFont.os2_typolinegap = 0 + if gap > 0: + gap_top = int(gap / 2) + gap_bottom = gap - gap_top + self.font_dim['ymin'] -= gap_bottom + self.font_dim['ymax'] += gap_top + self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax'] + self.sourceFont.os2_typoascent = self.sourceFont.os2_typoascent + gap_top + self.sourceFont.os2_typodescent = self.sourceFont.os2_typodescent - gap_bottom + # TODO Check what to do with win and hhea values + # Find the biggest char width # Ignore the y-values, os2_winXXXXX values set above are used for line height # @@ -890,22 +885,10 @@ class font_patcher: continue if self.font_dim['width'] < self.sourceFont[glyph].width: self.font_dim['width'] = self.sourceFont[glyph].width + # print("New MAXWIDTH-A {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax)) if xmax > self.font_dim['xmax']: self.font_dim['xmax'] = xmax - - # 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, - } + # print("New MAXWIDTH-B {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax)) def get_scale_factor(self, sym_dim): @@ -921,19 +904,16 @@ class font_patcher: return scale_ratio - def copy_glyphs(self, sourceFontStart, sourceFontEnd, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes): + def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes): """ Copies symbol glyphs into self.sourceFont """ progressText = '' careful = False glyphSetLength = 0 + sourceFontCounter = 0 if self.args.careful: careful = True - if exactEncoding is False: - sourceFontList = list(range(sourceFontStart, sourceFontEnd + 1)) - sourceFontCounter = 0 - # Create glyphs from symbol font # # If we are going to copy all Glyphs, then assume we want to be careful @@ -974,8 +954,8 @@ class font_patcher: possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] currentSourceFontGlyph = min(possible_codes) else: - # use source font defined hex values based on passed in start and end - currentSourceFontGlyph = sourceFontList[sourceFontCounter] + # use source font defined hex values based on passed in start (fills gaps; symbols are packed) + currentSourceFontGlyph = sourceFontStart + sourceFontCounter sourceFontCounter += 1 if self.args.quiet is False: @@ -1292,19 +1272,153 @@ def check_fontforge_min_version(): sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion)) sys.exit(1) +def setup_arguments(): + parser = argparse.ArgumentParser( + description=( + 'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n' + '* Website: https://www.nerdfonts.com\n' + '* Version: ' + version + '\n' + '* Development Website: https://github.com/ryanoasis/nerd-fonts\n' + '* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md'), + formatter_class=RawTextHelpFormatter + ) + + # optional arguments + parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)') + parser.add_argument('-v', '--version', action='version', version=projectName + ": %(prog)s (" + version + ")") + parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='count', help='Whether to generate the glyphs as single-width not double-width (default is double-width)') + parser.add_argument('-l', '--adjust-line-height', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)') + parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output') + parser.add_argument('-w', '--windows', dest='windows', default=False, action='store_true', help='Limit the internal font name to 31 characters (for Windows compatibility)') + parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs') + parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected') + parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file') + parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing') + parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)') + parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font. All new glyphs will be copied, with no scaling applied.') + 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)') + 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 + 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') + sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') + sym_font_group.add_argument('--fontawesomeextension', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)') + sym_font_group.add_argument('--fontlogos', '--fontlinux', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)') + sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') + sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)') + sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') + sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)') + sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs') + sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)') + sym_font_group.add_argument('--material', '--materialdesignicons', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)') + sym_font_group.add_argument('--weather', '--weathericons', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)') + + args = parser.parse_args() + + if 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 args.complete: + args.fontawesome = True + args.fontawesomeextension = True + args.fontlogos = True + args.octicons = True + args.codicons = True + args.powersymbols = True + args.pomicons = True + args.powerline = True + args.powerlineextra = True + args.material = True + args.weather = True + + if not args.complete: + sym_font_args = [] + # add the list of arguments for each symbol font to the list sym_font_args + for action in sym_font_group._group_actions: + sym_font_args.append(action.__dict__['option_strings']) + + # determine whether or not all symbol fonts are to be used + font_complete = True + for sym_font_arg_aliases in sym_font_args: + found = False + for alias in sym_font_arg_aliases: + if alias in sys.argv: + found = True + if found is not True: + font_complete = False + args.complete = font_complete + + if args.alsowindows: + args.windows = False + + if args.nonmono and args.single: + print("Warniung: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.") + args.nonmono = False + + make_sure_path_exists(args.outputdir) + if not os.path.isfile(args.font): + sys.exit("{}: Font file does not exist: {}".format(projectName, args.font)) + if not os.access(args.font, os.R_OK): + sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font)) + is_ttc = len(fontforge.fontsInFile(args.font)) > 1 + + if args.extension == "": + args.extension = os.path.splitext(args.font)[1] + else: + args.extension = '.' + args.extension + if re.match("\.ttc$", args.extension, re.IGNORECASE): + if not is_ttc: + sys.exit(projectName + ": Can not create True Type Collections from single font files") + else: + if is_ttc: + sys.exit(projectName + ": Can not create single font files from True Type Collections") + + return args + def main(): + print("{} Patcher v{} ({}) executing".format(projectName, version, script_version)) check_fontforge_min_version() - patcher = font_patcher() - patcher.patch() + args = setup_arguments() + patcher = font_patcher(args) + + sourceFonts = [] + all_fonts = fontforge.fontsInFile(args.font) + for i, subfont in enumerate(all_fonts): + print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts))) + try: + sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",)) + except Exception: + sys.exit("{}: Can not open font '{}', try to open with fontforge interactively to get more information".format( + projectName, subfont)) + + patcher.patch(sourceFonts[-1]) + print("\nDone with Patch Sets, generating font...\n") - patcher.setup_font_names() - patcher.generate() + for f in sourceFonts: + patcher.setup_font_names(f) + patcher.generate(sourceFonts) + # This mainly helps to improve CI runtime if patcher.args.alsowindows: patcher.args.windows = True - patcher.setup_font_names() - patcher.generate() + for f in sourceFonts: + patcher.setup_font_names(f) + patcher.generate(sourceFonts) + + for f in sourceFonts: + f.close() if __name__ == "__main__": diff --git a/src/glyphs/original-source.otf b/src/glyphs/original-source.otf index be42b8833fc9347149f2d0e55e7f9921ca2d6b59..ab9f5df4c9dc2a2664835defc2138b702991fcbc 100644 GIT binary patch delta 1176 zcmXw&e@v8h9LL|!^W2@lVPNqi^qPnlhH-dzk3-C8k0Sn|1i}2v=8VFoTZpr^SkCUr zRcu|8@YxD*ZEZ2uTJzTxuA=;=lWeh;KgtzZoFrM9(Gdi5@ag@u>yLZi>+|`%KcDaS z^W1aqoHZt1HUbS>w>BUXHVov}Y}!*;4+0$Jws8hBw9Yj9P^1l~*^d=^r8A_3%zbF* z2AU_6eN|d%n!U)?4y4(KsMeikKkBt{r&Pa^8Q~Q}h#>5EjCKBq8H$8M(Qq^h=Tm*H zbI@3xZ=wQWdYVuJL@UgSaHy#!al+`<@7|Sjk9)gZswcU~Gmzsf_h72 zevFiRmfe5dCiz~0$e*hzf5R3T{Lm6)kCCp=SRyy}lM*rd?vKJf-`z0qs1o^oo-*4> z&-la4Wxc5c`D;k4-n9j~$*`b|RFt&1Uppl;k74jFC0J4dLDe~qPE~P$T;l?Avyrj< zphV7hP+m>3@(pK6-)p4l3!D(k9syU$Vs}o9qI=S%VS47LNO=}>BW0Gz*kPM{H-ceO z@p;STl{C#PvWaqwhJQo}inl2qTVx3eZ_qk9PS40@=6>m?3_tZCD>C<`IcA4mXICe%h%2N^LMawtd7aD(-fW0 z0tJmEt&mi@gVV0Av3Z9=OXRyaMf#aL_c3$R8%Y;GS9}J%gdFAVB;m2Rew5+yLWZ+( mO3(W?mp(%paF2}kkc^)%DCfD+rCQwS9X(|5@8jH;wEqQ!-5DwX delta 8972 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