diff --git a/bin/font-patcher b/bin/font-patcher index fc64a38..68a9dcc 100755 --- a/bin/font-patcher +++ b/bin/font-patcher @@ -1,14 +1,14 @@ #!/usr/bin/env python # coding=utf8 -# Nerd Fonts Version: 2.3.3 +# Nerd Fonts Version: 3.0.0 # 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.7.1" +script_version = "4.1.1" -version = "2.3.3" +version = "3.0.0" projectName = "Nerd Fonts" projectNameAbbreviation = "NF" projectNameSingular = projectName[:-1] @@ -22,6 +22,7 @@ import errno import subprocess import json from enum import Enum +import logging try: import configparser except ImportError: @@ -239,10 +240,10 @@ def force_panose_monospaced(font): panose = list(font.os2_panose) if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default) panose[0] = 2 # make kind latin text and display - print(" Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')") + logger.info("Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')") font.os2_panose = tuple(panose) if panose[0] == 2 and panose[3] != 9: - print(" Setting Panose 'Proportion' to 'Monospaced' (was '{}')".format(panose_proportion_to_text(panose[3]))) + logger.info("Setting Panose 'Proportion' to 'Monospaced' (was '%s')", panose_proportion_to_text(panose[3])) panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced font.os2_panose = tuple(panose) @@ -296,10 +297,22 @@ def get_old_average_x_width(font): } for g in weights: if g not in font: - sys.exit("{}: Can not determine ancient style xAvgCharWidth".format(projectName)) + logger.critical("Can not determine ancient style xAvgCharWidth") + sys.exit(1) s += font[g].width * weights[g] return int(s / 1000) +def create_filename(fonts): + """ Determine filename from font object(s) """ + sfnt = { k: v for l, k, v in fonts[0].sfnt_names } + sfnt_pfam = sfnt.get('Preferred Family', sfnt['Family']) + sfnt_psubfam = sfnt.get('Preferred Styles', sfnt['SubFamily']) + if len(fonts) > 1: + return sfnt_pfam + if len(sfnt_psubfam) > 0: + sfnt_psubfam = '-' + sfnt_psubfam + return (sfnt_pfam + sfnt_psubfam).replace(' ', '') + class font_patcher: def __init__(self, args): @@ -311,6 +324,7 @@ class font_patcher: self.font_dim = None # class 'dict' self.font_extrawide = False self.source_monospaced = None # Later True or False + self.symbolsonly = False self.onlybitmaps = 0 self.essential = set() self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) @@ -336,7 +350,7 @@ class font_patcher: # For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2: - print("Very wide and short font, disabling 2 cell Powerline glyphs") + logger.warning("Very wide and short font, disabling 2 cell Powerline glyphs") self.font_extrawide = True # Prevent opening and closing the fontforge font. Makes things faster when patching @@ -345,8 +359,12 @@ class font_patcher: symfont = None if not os.path.isdir(self.args.glyphdir): - sys.exit("{}: Can not find symbol glyph directory {} " - "(probably you need to download the src/glyphs/ directory?)".format(projectName, self.args.glyphdir)) + logger.critical("Can not find symbol glyph directory %s " + "(probably you need to download the src/glyphs/ directory?)", self.args.glyphdir) + sys.exit(1) + + if self.args.dry_run: + return for patch in self.patch_set: if patch['Enabled']: @@ -356,11 +374,13 @@ class font_patcher: symfont.close() symfont = None if not os.path.isfile(self.args.glyphdir + patch['Filename']): - sys.exit("{}: Can not find symbol source for '{}'\n{:>{}} (i.e. {})".format( - projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename'])) + logger.critical("Can not find symbol source for '%s' (i.e. %s)", + patch['Name'], self.args.glyphdir + patch['Filename']) + sys.exit(1) if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK): - sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format( - projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename'])) + logger.critical("Can not open symbol source for '%s' (i.e. %s)", + patch['Name'], self.args.glyphdir + patch['Filename']) + sys.exit(1) symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename'])) symfont.encoding = 'UnicodeFull' @@ -402,11 +422,11 @@ class font_patcher: break outfile = os.path.normpath(os.path.join( sanitize_filename(self.args.outputdir, True), - sanitize_filename(sourceFont.familyname) + ".ttc")) + sanitize_filename(create_filename(sourceFonts)) + ".ttc")) sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer) message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile) else: - fontname = sourceFont.fullname + fontname = create_filename(sourceFonts) if not fontname: fontname = sourceFont.cidfontname outfile = os.path.normpath(os.path.join( @@ -414,9 +434,11 @@ class font_patcher: sanitize_filename(fontname) + self.args.extension)) bitmaps = str() if len(self.sourceFont.bitmapSizes): - if not self.args.quiet: - print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) + logger.debug("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) bitmaps = str('otf') # otf/ttf, both is bf_ttf + if self.args.dry_run: + logger.debug("=====> Filename '{}'".format(outfile)) + return sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags) message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile) @@ -426,8 +448,7 @@ class font_patcher: source_font = TableHEADWriter(self.args.font) dest_font = TableHEADWriter(outfile) for idx in range(source_font.num_fonts): - if not self.args.quiet: - print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts)) + logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts) xwidth_s = '' xwidth = self.xavgwidth[idx] if isinstance(xwidth, int): @@ -438,26 +459,23 @@ class font_patcher: dest_font.find_table([b'OS/2'], idx) d_xwidth = dest_font.getshort('avgWidth') if d_xwidth != xwidth: - if not self.args.quiet: - print("Changing xAvgCharWidth from {} to {}{}".format(d_xwidth, xwidth, xwidth_s)) + logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s) dest_font.putshort(xwidth, 'avgWidth') dest_font.reset_table_checksum() source_font.find_head_table(idx) dest_font.find_head_table(idx) if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: - if not self.args.quiet: - print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08)) + logger.debug("Changing flags from 0x%X to 0x%X", 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: - if not self.args.quiet: - print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem)) + logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem) dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') if dest_font.modified: dest_font.reset_table_checksum() if dest_font.modified: dest_font.reset_full_checksum() except Exception as error: - print("Can not handle font flags ({})".format(repr(error))) + logger.error("Can not handle font flags (%s)", repr(error)) finally: try: source_font.close() @@ -465,12 +483,13 @@ class font_patcher: except: pass if self.args.is_variable: - print("Warning: Source font is a variable open type font (VF) and the patch results will most likely not be what you want") + logger.critical("Source font is a variable open type font (VF) and the patch results will most likely not be what you want") print(message) if self.args.postprocess: subprocess.call([self.args.postprocess, outfile]) - print("\nPost Processed: {}".format(outfile)) + print("\n") + logger.info("Post Processed: %s", outfile) def setup_name_backup(self, font): @@ -488,11 +507,8 @@ class font_patcher: font.fullname = font.persistent["fullname"] if isinstance(font.persistent["familyname"], str): font.familyname = font.persistent["familyname"] - verboseAdditionalFontNameSuffix = " " + projectNameSingular - if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later - additionalFontNameSuffix = " " + projectNameAbbreviation - else: - additionalFontNameSuffix = verboseAdditionalFontNameSuffix + verboseAdditionalFontNameSuffix = "" + additionalFontNameSuffix = "" if not self.args.complete: # NOTE not all symbol fonts have appended their suffix here if self.args.fontawesome: @@ -523,17 +539,24 @@ class font_patcher: additionalFontNameSuffix += " WEA" verboseAdditionalFontNameSuffix += " Plus Weather Icons" - # if all source glyphs included simplify the name - else: - additionalFontNameSuffix = " " + projectNameSingular + " Complete" - verboseAdditionalFontNameSuffix = " " + projectNameSingular + " Complete" - - # add mono signifier to end of name + # add mono signifier to beginning of name suffix if self.args.single: - additionalFontNameSuffix += " M" - verboseAdditionalFontNameSuffix += " Mono" + variant_abbrev = "M" + variant_full = " Mono" + elif self.args.nonmono and not self.symbolsonly: + variant_abbrev = "P" + variant_full = " Propo" + else: + variant_abbrev = "" + variant_full = "" - if FontnameParserOK and self.args.makegroups: + ps_suffix = projectNameAbbreviation + variant_abbrev + additionalFontNameSuffix + + # add 'Nerd Font' to beginning of name suffix + verboseAdditionalFontNameSuffix = " " + projectNameSingular + variant_full + verboseAdditionalFontNameSuffix + additionalFontNameSuffix = " " + projectNameSingular + variant_full + additionalFontNameSuffix + + if FontnameParserOK and self.args.makegroups > 0: use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse # Use fullname if it is 'equal' to the fontname if font.fullname: @@ -545,12 +568,14 @@ class font_patcher: # 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) + n = FontnameParser(parser_name, logger) if not n.parse_ok: - print("Have only minimal naming information, check resulting name. Maybe omit --makegroups option") + logger.warning("Have only minimal naming information, check resulting name. Maybe specify --makegroups 0") n.drop_for_powerline() - n.enable_short_families(True, "Noto") - n.set_for_windows(self.args.windows) + n.enable_short_families(True, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ]) + if not n.set_expect_no_italic(self.args.noitalic): + logger.critical("Detected 'Italic' slant but --has-no-italic specified") + sys.exit(1) # All the following stuff is ignored in makegroups-mode @@ -598,23 +623,7 @@ class font_patcher: if len(subFamily) == 0: subFamily = "Regular" - if self.args.windows: - maxFamilyLength = 31 - maxFontLength = maxFamilyLength - len('-' + subFamily) - familyname += " " + projectNameAbbreviation - if self.args.single: - familyname += "M" - fullname += " Windows Compatible" - - # now make sure less than 32 characters name length - if len(fontname) > maxFontLength: - fontname = fontname[:maxFontLength] - if len(familyname) > maxFamilyLength: - familyname = familyname[:maxFamilyLength] - else: - familyname += " " + projectNameSingular - if self.args.single: - familyname += " Mono" + familyname += " " + projectNameSingular + variant_full # 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. Make sure to @@ -627,6 +636,10 @@ class font_patcher: reservedFontNameReplacements = { 'source' : 'sauce', 'Source' : 'Sauce', + 'Bitstream Vera Sans Mono' : 'Bitstrom Wera', + 'BitstreamVeraSansMono' : 'BitstromWera', + 'bitstream vera sans mono' : 'bitstrom wera', + 'bitstreamverasansmono' : 'bitstromwera', 'hermit' : 'hurmit', 'Hermit' : 'Hurmit', 'hasklig' : 'hasklug', @@ -693,7 +706,7 @@ class font_patcher: fullname = replace_font_name(fullname, additionalFontNameReplacements2) fontname = replace_font_name(fontname, additionalFontNameReplacements2) - if not (FontnameParserOK and self.args.makegroups): + if not (FontnameParserOK and self.args.makegroups > 0): # replace any extra whitespace characters: font.familyname = " ".join(familyname.split()) font.fullname = " ".join(fullname.split()) @@ -704,13 +717,9 @@ class font_patcher: 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 - if self.args.single: - if self.args.windows: - fam_suffix += 'M' - else: - fam_suffix += ' Mono' - n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix) + short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full + # inject_suffix(family, ps_fontname, short_family) + n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family) n.rename_font(font) font.comment = projectInfo @@ -726,6 +735,7 @@ class font_patcher: self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version) + # The Version SFNT name is later reused by the NameParser for UniqueID # print("Version now is {}".format(sourceFont.version)) @@ -734,17 +744,17 @@ class font_patcher: # the tables have been removed from the repo with >this< commit if self.args.configfile and self.config.read(self.args.configfile): if self.args.removeligatures: - print("Removing ligatures from configfile `Subtables` section") + logger.info("Removing ligatures from configfile `Subtables` section") ligature_subtables = json.loads(self.config.get("Subtables", "ligatures")) for subtable in ligature_subtables: - print("Removing subtable:", subtable) + logger.debug("Removing subtable: %s", subtable) try: self.sourceFont.removeLookupSubtable(subtable) - print("Successfully removed subtable:", subtable) + logger.debug("Successfully removed subtable: %s", subtable) except Exception: - print("Failed to remove subtable:", subtable) + logger.error("Failed to remove subtable: %s", subtable) elif self.args.removeligatures: - print("Unable to read configfile, unable to remove ligatures") + logger.error("Unable to read configfile, unable to remove ligatures") def assert_monospace(self): @@ -756,16 +766,17 @@ class font_patcher: 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(" {} and {}".format( + logger.warning("Monospaced check: Panose assumed to be wrong") + logger.warning(" %s and %s", report_advance_widths(self.sourceFont), - panose_check_to_text(panose_mono, self.sourceFont.os2_panose))) + panose_check_to_text(panose_mono, self.sourceFont.os2_panose)) if self.args.single and not width_mono: - print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless") + logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless") if offending_char is not None: - print(" Offending char: 0x{:X}".format(offending_char)) + logger.warning(" Offending char: %X", offending_char) 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") + logger.critical("Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching") + sys.exit(1) if width_mono: force_panose_monospaced(self.sourceFont) @@ -781,9 +792,9 @@ class font_patcher: box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs)) if box_glyphs_target > box_glyphs_current: # Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing) - if not self.args.quiet and box_glyphs_current > 0: - print("INFO: {}/{} box drawing glyphs will be replaced".format( - box_glyphs_current, box_glyphs_target)) + if box_glyphs_current > 0: + logger.debug("%d/%d box drawing glyphs will be replaced", + box_glyphs_current, box_glyphs_target) box_enabled = True else: # Sourcefont does have all of these glyphs @@ -1020,14 +1031,14 @@ class font_patcher: {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': 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, 'ScaleRules': 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, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) - {'Enabled': self.args.material, 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': False , 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, '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, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': 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, 'ScaleRules': 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, 'ScaleRules': 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, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF305, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR} ] @@ -1102,11 +1113,22 @@ class font_patcher: our_btb = typo_btb if use_typo else win_btb if our_btb == hhea_btb: metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font + elif abs(our_btb - hhea_btb) / our_btb < 0.03: + logger.info("Font vertical metrics slightly off (%.1f%)", (our_btb - hhea_btb) / our_btb * 100.0) + metrics = Metric.TYPO if use_typo else Metric.WIN else: - # We trust the WIN metric more, see experiments in #1056 - print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb)) - our_btb = win_btb - metrics = Metric.WIN + # Try the other metric + our_btb = typo_btb if not use_typo else win_btb + if our_btb == hhea_btb: + logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", 'True' if not use_typo else 'False') + use_typo = not use_typo + self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0 + metrics = Metric.TYPO if use_typo else Metric.WIN + else: + # We trust the WIN metric more, see experiments in #1056 + logger.warning("Font vertical metrics inconsistent (HHEA %d / TYPO %d / WIN %d), using WIN", hhea_btb, typo_btb, win_btb) + our_btb = win_btb + metrics = Metric.WIN # print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) @@ -1129,6 +1151,7 @@ class font_patcher: if self.font_dim['height'] == 0: # This can only happen if the input font is empty # Assume we are using our prepared templates + self.symbolsonly = True self.font_dim = { 'xmin' : 0, 'ymin' : -self.sourceFont.descent, @@ -1139,7 +1162,8 @@ class font_patcher: } our_btb = self.sourceFont.descent + self.sourceFont.ascent elif self.font_dim['height'] < 0: - sys.exit("{}: Can not detect sane font height".format(projectName)) + logger.critical("Can not detect sane font height") + sys.exit(1) # Make all metrics equal self.sourceFont.os2_typolinegap = 0 @@ -1153,12 +1177,13 @@ class font_patcher: self.sourceFont.os2_use_typo_metrics = 1 (check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont) if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb: - sys.exit("{}: Error in baseline to baseline code detected".format(projectName)) + logger.critical("Error in baseline to baseline code detected") + sys.exit(1) # Step 2 # Find the biggest char width and advance width # 0x00-0x17f is the Latin Extended-A range - warned1 = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target + warned1 = self.args.nonmono # Do not warn if proportional target warned2 = warned1 for glyph in range(0x21, 0x17f): if glyph in range(0x7F, 0xBF) or glyph in [ @@ -1176,19 +1201,18 @@ 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 - print("Warning: Extended glyphs wider than basic glyphs, results might be useless\n {}".format( - report_advance_widths(self.sourceFont))) + logger.debug("Extended glyphs wider than basic glyphs, results might be useless\n %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']: self.font_dim['xmax'] = xmax if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z - print("Info: Extended glyphs wider bounding box than basic glyphs") + logger.debug("Extended glyphs wider bounding box than basic glyphs") warned2 = True # print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) if self.font_dim['width'] < self.font_dim['xmax']: - if not self.args.quiet: - print("Warning: Font has negative right side bearing in extended glyphs") + 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) @@ -1288,7 +1312,7 @@ class font_patcher: if sym_glyph.altuni: possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] if len(possible_codes) == 0: - print(" Can not determine codepoint of {:X}. Skipping...".format(sym_glyph.unicode)) + logger.warning("Can not determine codepoint of %X. Skipping...", sym_glyph.unicode) continue currentSourceFontGlyph = min(possible_codes) else: @@ -1311,9 +1335,8 @@ class font_patcher: # check if a glyph already exists in this location if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential: if currentSourceFontGlyph in self.sourceFont: - if not self.args.quiet: - careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' - print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph)) + careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' + logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph) # We don't want to touch anything so move to next Glyph continue else: @@ -1461,8 +1484,8 @@ class font_patcher: if self.args.single: (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() if int(xmax - xmin) > self.font_dim['width'] * (1 + (overlap or 0)): - print("\n Warning: Scaled glyph U+{:X} wider than one monospace width ({} / {} (overlap {}))".format( - currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap)) + logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %f))", + currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap) # end for @@ -1603,7 +1626,7 @@ def half_gap(gap, top): gap_top = int(gap / 2) gap_bottom = gap - gap_top if top: - print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom)) + logger.info("Redistributing line gap of %d (%d top and %d bottom)", gap, gap_top, gap_bottom) return gap_top return gap_bottom @@ -1728,8 +1751,8 @@ def check_fontforge_min_version(): # versions tested: 20150612, 20150824 if actualVersion < minimumVersion: - sys.stderr.write("{}: You seem to be using an unsupported (old) version of fontforge: {}\n".format(projectName, actualVersion)) - sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion)) + logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion) + logger.critical("Please use at least version: %d", minimumVersion) sys.exit(1) def check_version_with_git(version): @@ -1777,7 +1800,6 @@ def setup_arguments(): 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') @@ -1787,15 +1809,28 @@ def setup_arguments(): parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)') parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching') - parser.add_argument('--makegroups', dest='makegroups', default=False, action='store_true', help='Use alternative method to name patched fonts (experimental)') + parser.add_argument('--makegroups', dest='makegroups', default=1, type=int, nargs='?', help='Use alternative method to name patched fonts (recommended)', const=1, choices=range(0, 6 + 1)) + # --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'. + # Original font name: Hugo Sans Mono ExtraCondensed Light Italic + # NF Fam agg. + # 0 turned off, use old naming scheme [-] [-] [-] + # 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ] + # 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ] + # 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X] + # 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ] + # 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ] + # 6 HugoSansMono NF XCn Lt It [X] [X] [X] + parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")') + parser.add_argument('--has-no-italic', dest='noitalic', default=False, action='store_true', help='Font family does not have Italic (but Oblique)') # 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('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)') 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') + parser.add_argument('--debug', dest='debugmode', default=False, action='store_true', help='Verbose mode') + parser.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming') parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True) # --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts # Possible values with examples: @@ -1819,8 +1854,9 @@ def setup_arguments(): args = parser.parse_args() - if args.makegroups and not FontnameParserOK: - sys.exit("{}: FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName)) + if args.makegroups > 0 and not FontnameParserOK: + logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0") + sys.exit(1) # if you add a new font, set it to True here inside the if condition if args.complete: @@ -1853,24 +1889,23 @@ def setup_arguments(): font_complete = False args.complete = font_complete - if args.alsowindows: - args.windows = False - if args.nonmono and args.single: - print("Warning: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.") + logging.warning("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)) + logging.critical("Font file does not exist: %s", args.font) + sys.exit(1) if not os.access(args.font, os.R_OK): - sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font)) + logging.critical("Can not open font file for reading: %s", args.font) + sys.exit(1) is_ttc = len(fontforge.fontsInFile(args.font)) > 1 try: source_font_test = TableHEADWriter(args.font) args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0) if args.is_variable: - print(" Warning: Source font is a variable open type font (VF), opening might fail...") + logging.warning("Source font is a variable open type font (VF), opening might fail...") except: args.is_variable = False finally: @@ -1885,16 +1920,20 @@ def setup_arguments(): 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") + logging.critical("Can not create True Type Collections from single font files") + sys.exit(1) else: if is_ttc: - sys.exit(projectName + ": Can not create single font files from True Type Collections") + logging.critical("Can not create single font files from True Type Collections") + sys.exit(1) if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool): if args.xavgwidth < 0: - sys.exit(projectName + ": --xavgcharwidth takes no negative numbers") + logging.critical("--xavgcharwidth takes no negative numbers") + sys.exit(2) if args.xavgwidth > 16384: - sys.exit(projectName + ": --xavgcharwidth takes only numbers up to 16384") + logging.critical("--xavgcharwidth takes only numbers up to 16384") + sys.exit(2) return args @@ -1902,24 +1941,43 @@ def setup_arguments(): def main(): global version git_version = check_version_with_git(version) - print("{} Patcher v{} ({}) (ff {}) executing".format( - projectName, git_version if git_version else version, script_version, fontforge.version())) + allversions = "Patcher v{} ({}) (ff {})".format( + git_version if git_version else version, script_version, fontforge.version()) + print("{} {}".format(projectName, allversions)) if git_version: version = git_version check_fontforge_min_version() args = setup_arguments() + + global logger + logger = logging.getLogger(os.path.basename(args.font)) + logger.setLevel(logging.DEBUG) + f_handler = logging.FileHandler('font-patcher-log.txt') + f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s')) + logger.addHandler(f_handler) + logger.debug(allversions) + logger.debug("Options %s", repr(sys.argv[1:])) + c_handler = logging.StreamHandler(stream=sys.stdout) + c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + if not args.debugmode: + c_handler.setLevel(logging.INFO) + logger.addHandler(c_handler) + logger.debug("Naming mode %d", args.makegroups) + patcher = font_patcher(args) sourceFonts = [] all_fonts = fontforge.fontsInFile(args.font) for i, subfont in enumerate(all_fonts): if len(all_fonts) > 1: - print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts))) + print("\n") + logger.info("Processing %s (%d/%d)", 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)) + logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information", + subfont) + sys.exit(1) patcher.patch(sourceFonts[-1]) @@ -1928,13 +1986,6 @@ def main(): patcher.setup_font_names(f) patcher.generate(sourceFonts) - # This mainly helps to improve CI runtime - if patcher.args.alsowindows: - patcher.args.windows = True - for f in sourceFonts: - patcher.setup_font_names(f) - patcher.generate(sourceFonts) - for f in sourceFonts: f.close() diff --git a/src/glyphs/octicons/LICENSE b/src/glyphs/octicons/LICENSE new file mode 100644 index 0000000..163074d --- /dev/null +++ b/src/glyphs/octicons/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/glyphs/octicons/analyze_octicons b/src/glyphs/octicons/analyze_octicons new file mode 100755 index 0000000..35fa961 --- /dev/null +++ b/src/glyphs/octicons/analyze_octicons @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# coding=utf8 + +# This extracts the names and source and destination codepoints +# of the old octicons font file to keep their codepoints stable +# +# You do not need to redo it, the result is in the repo +# +# Usage: +# fontforge analyze_octicons > mapping + +import fontforge + +octi_orig = "octicons.ttf" +current_cp = 0xF400 + +print('# Examining {}'.format(octi_orig)) + +font = fontforge.open(octi_orig) +for glyph in font.glyphs('encoding'): + point = glyph.unicode + if point < 0: + continue + desti = glyph.unicode + if point < 0xF000: + desti = point + else: + desti = current_cp + current_cp += 1 + print("{:X} {:X} {}".format(point, desti, glyph.glyphname)) + +font.close() diff --git a/src/glyphs/octicons/generate b/src/glyphs/octicons/generate new file mode 100755 index 0000000..b9e7722 --- /dev/null +++ b/src/glyphs/octicons/generate @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +# coding=utf8 + +import sys +import os +import re +import subprocess +import fontforge + +# Double-quotes required here, for version-bump.sh: +version = "2.3.3" + +archive = 'v18.3.0.tar.gz' + +vectorsdir = 'icons' +fontdir = '.' +fontfile = 'octicons.ttf' +glyphsetfile = 'i_oct.sh' +glyphsetsdir = '../../../bin/scripts/lib' + +subset = '-16' # use 16 px subset if possible +subset_other = '-24' # use 24 px subset otherwise + +def renamer(old_name): + """ Return new equivalent icon name """ + return { + 'trashcan' : 'trash', + 'cloud-download' : 'download', + 'cloud-upload' : 'upload', + 'clippy' : 'paste', + 'mail-read' : 'read', + 'primitive-dot' : 'dot-fill', + 'primitive-square' : 'square-fill', + 'settings' : 'sliders', + 'dashboard' : 'meter', + 'paintcan' : 'paintbrush', + }.get(old_name, old_name) + +def addIcon(codepoint, name, filename): + """ Add one outline file and rescale/move """ + dBB = [120, 0, 1000-120, 900] # just some nice sizes + filename = os.path.join(vectorsdir, filename) + glyph = font.createChar(codepoint, name) + glyph.importOutlines(filename) + glyph.manualHints = True + +def createGlyphInfo(icon_datasets, filepathname, into): + """ Write the glyphinfo file """ + with open(filepathname, 'w', encoding = 'utf8') as f: + f.write(u'#!/usr/bin/env bash\n') + f.write(intro) + f.write(u'# Script Version: (autogenerated)\n') + f.write(u'test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n') + for _, codepoint, name in icon_datasets: + codepoint = int(codepoint, 16) + f.write(u"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace('-', '_'))) + f.write(u'unset i\n') + +print('\nReading mapping file') +old_mapping = [] +with open('mapping', 'r') as f: + for line in f.readlines(): + if line.startswith('#'): + continue + old_mapping.append(tuple(re.split(' +', line.strip()))) +print('Found {} entries'.format(len(old_mapping))) +old_mapping.sort(key=(lambda x: x[0])) + +print('Fetching octicons archive "{}"\n'.format(archive)) +if subprocess.call('curl -OL https://github.com/primer/octicons/archive/' + archive, shell=True): + sys.exit('Error fetching octicons archive') +print('\nUnpacking octicons archive') +if subprocess.call('rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-*', shell=True): + sys.exit('Error unpacking archive') + +svgs = os.listdir(vectorsdir) +print('Found {} svgs'.format(len(svgs))) +names = { s[0:-len('-xx.svg')] for s in svgs if s.endswith(subset + '.svg') or s.endswith(subset_other + '.svg') } +print('Found {} icons after de-duplicating\n'.format(len(names))) + +num_found = 0 +num_missing = 0 +misslist = '' +renamelist = '' +freeslots = [] + +new_mapping = [] +for i, j, old_n in old_mapping: + if old_n in names: + names.remove(old_n) + new_mapping.append((i, j, old_n)) + num_found += 1 + continue + new_n = renamer(old_n) + if new_n in names: + renamelist += 'Renamed {} -> {}\n'.format(old_n, new_n) + names.remove(new_n) + new_mapping.append((i, j, new_n)) + num_found += 1 + continue + misslist += 'Missing {}\n'.format(old_n) + freeslots.append((i, j)) + num_missing += 1 + +print(renamelist) +print(misslist) +print('Found {} (of {}, missing {}) and new {}'.format(num_found, len(old_mapping), num_missing, len(names))) + +names = list(names) +names.sort() +for n in list(names): + if len(freeslots) == 0: + break + i, j = freeslots[0] + new_mapping.append((i, j, n)) + names.remove(n) + freeslots = freeslots[1:] + +print('Filled in missing, remaining new {}'.format(len(names))) + +i_max = 0 +j_max = 0 +for i, j, _ in new_mapping: + i = int(i, 16) + j = int(j, 16) + if i > i_max: + i_max = i + if j > j_max: + j_max = j + +for n in names: + i_max += 1 + j_max += 1 + new_mapping.append(('{:X}'.format(i_max), '{:X}'.format(j_max), n)) + +print('Appended remaining new, total new mapping {}'.format(len(new_mapping))) + +new_mapping.sort(key=(lambda x: x[0])) +with open('mapping', 'w') as f: + for i, j, n in new_mapping: + f.write('{} {} {}\n'.format(i, j, n)) + +font = fontforge.font() +font.fontname = 'OcticonsNerdFont-Regular' +font.fullname = 'Octicons Nerd Font Regular' +font.familyname = 'Octicons Nerd Font' +font.em = 2048 +font.encoding = 'UnicodeFull' + +# Add valid space glyph to avoid "unknown character" box on IE11 +glyph = font.createChar(32) +glyph.width = 200 + +font.sfntRevision = None # Auto-set (refreshed) by fontforge +font.version = version +font.copyright = 'GitHub Inc.' +font.appendSFNTName('English (US)', 'Version', archive + '; ' + version) +font.appendSFNTName('English (US)', 'Vendor URL', 'https://github.com/ryanoasis/nerd-fonts') +font.appendSFNTName('English (US)', 'Copyright', 'GitHub Inc.') + +for codepoint, _, name in new_mapping: + codepoint = int(codepoint, 16) + filename = name + subset + '.svg' + if filename not in svgs: + filename = name + subset_other + '.svg' + addIcon(codepoint, name, filename) + +num_icons = len(new_mapping) + +print('Generating {} with {} glyphs'.format(fontfile, num_icons)) +font.generate(os.path.join(fontdir, fontfile), flags=("no-FFTM-table",)) + +codepoints = [ int(p, 16) for _, p, _ in new_mapping ] +intro = u'# Octicons ({} icons)\n'.format(num_icons) +intro += u'# Codepoints: {:X}-{:X} with gaps\n'.format(min(codepoints), max(codepoints)) +intro += u'# Nerd Fonts Version: {}\n'.format(version) + +print('Generating GlyphInfo {}'.format(glyphsetfile)) +createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro) +print('Finished') diff --git a/src/glyphs/octicons/mapping b/src/glyphs/octicons/mapping new file mode 100644 index 0000000..880fca0 --- /dev/null +++ b/src/glyphs/octicons/mapping @@ -0,0 +1,309 @@ +2665 2665 heart +26A1 26A1 zap +F000 F400 light-bulb +F001 F401 repo +F002 F402 repo-forked +F005 F403 repo-push +F006 F404 repo-pull +F007 F405 book +F008 F406 accessibility +F009 F407 git-pull-request +F00A F408 mark-github +F00B F409 download +F00C F40A upload +F00D F40B accessibility-inset +F00E F40C alert-fill +F010 F40D file-code +F011 F40E apps +F012 F40F file-media +F013 F410 file-zip +F014 F411 archive +F015 F412 tag +F016 F413 file-directory +F017 F414 file-submodule +F018 F415 person +F019 F416 arrow-both +F01F F417 git-commit +F020 F418 git-branch +F023 F419 git-merge +F024 F41A mirror +F026 F41B issue-opened +F027 F41C issue-reopened +F028 F41D issue-closed +F02A F41E star +F02B F41F comment +F02C F420 question +F02D F421 alert +F02E F422 search +F02F F423 gear +F030 F424 arrow-down-left +F031 F425 tools +F032 F426 sign-out +F033 F427 rocket +F034 F428 rss +F035 F429 paste +F036 F42A sign-in +F037 F42B organization +F038 F42C device-mobile +F039 F42D unfold +F03A F42E check +F03B F42F mail +F03C F430 read +F03D F431 arrow-up +F03E F432 arrow-right +F03F F433 arrow-down +F040 F434 arrow-left +F041 F435 pin +F042 F436 gift +F043 F437 graph +F044 F438 triangle-left +F045 F439 credit-card +F046 F43A clock +F047 F43B ruby +F048 F43C broadcast +F049 F43D key +F04A F43E arrow-down-right +F04C F43F repo-clone +F04D F440 diff +F04E F441 eye +F04F F442 comment-discussion +F051 F443 arrow-switch +F052 F444 dot-fill +F053 F445 square-fill +F056 F446 device-camera +F057 F447 device-camera-video +F058 F448 pencil +F059 F449 info +F05A F44A triangle-right +F05B F44B triangle-down +F05C F44C link +F05D F44D plus +F05E F44E three-bars +F05F F44F code +F060 F450 location +F061 F451 list-unordered +F062 F452 list-ordered +F063 F453 quote +F064 F454 versions +F068 F455 calendar +F06A F456 lock +F06B F457 diff-added +F06C F458 diff-removed +F06D F459 diff-modified +F06E F45A diff-renamed +F070 F45B horizontal-rule +F071 F45C arrow-up-left +F075 F45D milestone +F076 F45E checklist +F077 F45F megaphone +F078 F460 chevron-right +F07B F461 bookmark +F07C F462 sliders +F07D F463 meter +F07E F464 history +F07F F465 link-external +F080 F466 mute +F081 F467 x +F084 F468 circle-slash +F085 F469 pulse +F087 F46A sync +F088 F46B telescope +F08C F46C arrow-up-right +F08D F46D home +F08F F46E stop +F091 F46F bug +F092 F470 logo-github +F094 F471 file-binary +F096 F472 database +F097 F473 server +F099 F474 diff-ignored +F09A F475 ellipsis +F09C F476 bell-fill +F09D F477 hubot +F09F F478 bell-slash +F0A0 F479 blocked +F0A1 F47A bookmark-fill +F0A2 F47B chevron-up +F0A3 F47C chevron-down +F0A4 F47D chevron-left +F0AA F47E triangle-up +F0AC F47F git-compare +F0AD F480 logo-gist +F0B0 F481 file-symlink-file +F0B1 F482 bookmark-slash +F0B2 F483 squirrel +F0B6 F484 globe +F0BA F485 unmute +F0BE F486 mention +F0C4 F487 package +F0C5 F488 browser +F0C8 F489 terminal +F0C9 F48A markdown +F0CA F48B dash +F0CC F48C fold +F0CF F48D inbox +F0D0 F48E trash +F0D1 F48F paintbrush +F0D2 F490 flame +F0D3 F491 briefcase +F0D4 F492 plug +F0D6 F493 bookmark-slash-fill +F0D7 F494 mortar-board +F0D8 F495 law +F0DA F496 thumbsup +F0DB F497 thumbsdown +F0DC F498 desktop-download +F0DD F499 beaker +F0DE F49A bell +F0E0 F49B cache +F0E1 F49C shield +F0E2 F49D bold +F0E3 F49E check-circle +F0E4 F49F italic +F0E5 F4A0 tasklist +F0E6 F4A1 verified +F0E7 F4A2 smiley +F0E8 F4A3 unverified +F101 F4A4 check-circle-fill +F102 F4A5 file +F103 F4A6 grabber +F104 F4A7 checkbox +F105 F4A8 reply +F27C F4A9 device-desktop +F27D F4AA circle +F27E F4AB clock-fill +F27F F4AC cloud +F280 F4AD cloud-offline +F281 F4AE code-of-conduct +F282 F4AF code-review +F283 F4B0 code-square +F284 F4B1 codescan +F285 F4B2 codescan-checkmark +F286 F4B3 codespaces +F287 F4B4 columns +F288 F4B5 command-palette +F289 F4B6 commit +F28A F4B7 container +F28B F4B8 copilot +F28C F4B9 copilot-error +F28D F4BA copilot-warning +F28E F4BB copy +F28F F4BC cpu +F290 F4BD cross-reference +F291 F4BE dependabot +F292 F4BF diamond +F293 F4C0 discussion-closed +F294 F4C1 discussion-duplicate +F295 F4C2 discussion-outdated +F296 F4C3 dot +F297 F4C4 duplicate +F298 F4C5 eye-closed +F299 F4C6 feed-discussion +F29A F4C7 feed-forked +F29B F4C8 feed-heart +F29C F4C9 feed-merged +F29D F4CA feed-person +F29E F4CB feed-repo +F29F F4CC feed-rocket +F2A0 F4CD feed-star +F2A1 F4CE feed-tag +F2A2 F4CF feed-trophy +F2A3 F4D0 file-added +F2A4 F4D1 file-badge +F2A5 F4D2 file-diff +F2A6 F4D3 file-directory-fill +F2A7 F4D4 file-directory-open-fill +F2A8 F4D5 file-moved +F2A9 F4D6 file-removed +F2AA F4D7 filter +F2AB F4D8 fiscal-host +F2AC F4D9 fold-down +F2AD F4DA fold-up +F2AE F4DB git-merge-queue +F2AF F4DC git-pull-request-closed +F2B0 F4DD git-pull-request-draft +F2B1 F4DE goal +F2B2 F4DF hash +F2B3 F4E0 heading +F2B4 F4E1 heart-fill +F2B5 F4E2 home-fill +F2B6 F4E3 hourglass +F2B7 F4E4 id-badge +F2B8 F4E5 image +F2B9 F4E6 infinity +F2BA F4E7 issue-draft +F2BB F4E8 issue-tracked-by +F2BC F4E9 issue-tracks +F2BD F4EA iterations +F2BE F4EB kebab-horizontal +F2BF F4EC key-asterisk +F2C0 F4ED log +F2C1 F4EE moon +F2C2 F4EF move-to-bottom +F2C3 F4F0 move-to-end +F2C4 F4F1 move-to-start +F2C5 F4F2 move-to-top +F2C6 F4F3 multi-select +F2C7 F4F4 no-entry +F2C8 F4F5 north-star +F2C9 F4F6 note +F2CA F4F7 number +F2CB F4F8 package-dependencies +F2CC F4F9 package-dependents +F2CD F4FA paper-airplane +F2CE F4FB paperclip +F2CF F4FC passkey-fill +F2D0 F4FD people +F2D1 F4FE person-add +F2D2 F4FF person-fill +F2D3 F500 play +F2D4 F501 plus-circle +F2D5 F502 project +F2D6 F503 project-roadmap +F2D7 F504 project-symlink +F2D8 F505 project-template +F2D9 F506 rel-file-path +F2DA F507 repo-deleted +F2DB F508 repo-locked +F2DC F509 repo-template +F2DD F50A report +F2DE F50B rows +F2DF F50C screen-full +F2E0 F50D screen-normal +F2E1 F50E share +F2E2 F50F share-android +F2E3 F510 shield-check +F2E4 F511 shield-lock +F2E5 F512 shield-slash +F2E6 F513 shield-x +F2E7 F514 sidebar-collapse +F2E8 F515 sidebar-expand +F2E9 F516 single-select +F2EA F517 skip +F2EB F518 skip-fill +F2EC F519 sort-asc +F2ED F51A sort-desc +F2EE F51B sparkle-fill +F2EF F51C sponsor-tiers +F2F0 F51D square +F2F1 F51E stack +F2F2 F51F star-fill +F2F3 F520 stopwatch +F2F4 F521 strikethrough +F2F5 F522 sun +F2F6 F523 tab +F2F7 F524 tab-external +F2F8 F525 table +F2F9 F526 telescope-fill +F2FA F527 trophy +F2FB F528 typography +F2FC F529 unlink +F2FD F52A unlock +F2FE F52B unread +F2FF F52C video +F300 F52D webhook +F301 F52E workflow +F302 F52F x-circle +F303 F530 x-circle-fill +F304 F531 zoom-in +F305 F532 zoom-out diff --git a/src/glyphs/octicons/octicons.ttf b/src/glyphs/octicons/octicons.ttf new file mode 100644 index 0000000..f864314 Binary files /dev/null and b/src/glyphs/octicons/octicons.ttf differ