├── src ├── Pomicons.otf ├── devicons.ttf ├── octicons.ttf ├── FontAwesome.otf ├── font-logos.ttf ├── PowerlineSymbols.otf ├── original-source.otf ├── config.sample.json ├── PowerlineExtraSymbols.otf ├── font-awesome-extension.ttf ├── Symbols Template 1000 em.ttf ├── Symbols Template 2048 em.ttf ├── Unicode_IEC_symbol_font.otf ├── materialdesignicons-webfont.ttf ├── weathericons-regular-webfont.ttf ├── Symbols-1000-em Nerd Font Complete.ttf ├── Symbols-2048-em Nerd Font Complete.ttf ├── NerdFontsSymbols 1000 EM Nerd Font Complete Blank.sfd └── NerdFontsSymbols 2048 EM Nerd Font Complete Blank.sfd ├── README.md └── font-patcher /src/Pomicons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Pomicons.otf -------------------------------------------------------------------------------- /src/devicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/devicons.ttf -------------------------------------------------------------------------------- /src/octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/octicons.ttf -------------------------------------------------------------------------------- /src/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/FontAwesome.otf -------------------------------------------------------------------------------- /src/font-logos.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/font-logos.ttf -------------------------------------------------------------------------------- /src/PowerlineSymbols.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/PowerlineSymbols.otf -------------------------------------------------------------------------------- /src/original-source.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/original-source.otf -------------------------------------------------------------------------------- /src/config.sample.json: -------------------------------------------------------------------------------- 1 | [Subtables] 2 | ligatures: [ 3 | "Single Substitution lookup 1 subtable" ] 4 | -------------------------------------------------------------------------------- /src/PowerlineExtraSymbols.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/PowerlineExtraSymbols.otf -------------------------------------------------------------------------------- /src/font-awesome-extension.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/font-awesome-extension.ttf -------------------------------------------------------------------------------- /src/Symbols Template 1000 em.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Symbols Template 1000 em.ttf -------------------------------------------------------------------------------- /src/Symbols Template 2048 em.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Symbols Template 2048 em.ttf -------------------------------------------------------------------------------- /src/Unicode_IEC_symbol_font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Unicode_IEC_symbol_font.otf -------------------------------------------------------------------------------- /src/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /src/weathericons-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/weathericons-regular-webfont.ttf -------------------------------------------------------------------------------- /src/Symbols-1000-em Nerd Font Complete.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Symbols-1000-em Nerd Font Complete.ttf -------------------------------------------------------------------------------- /src/Symbols-2048-em Nerd Font Complete.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NerdFonts/patcher/HEAD/src/Symbols-2048-em Nerd Font Complete.ttf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # patcher 2 | 3 | This is just a test. We will move the patcher here once patcher changes stablize and the next release goes out (v2.2.0) 4 | -------------------------------------------------------------------------------- /src/NerdFontsSymbols 1000 EM Nerd Font Complete Blank.sfd: -------------------------------------------------------------------------------- 1 | SplineFontDB: 3.0 2 | FontName: Symbols-1000-em 3 | FullName: Symbols-1000-em 4 | FamilyName: Symbols 5 | Weight: Regular 6 | Copyright: Copyright (c) 2016, Ryan McIntyre 7 | Version: 001.000 8 | ItalicAngle: 0 9 | UnderlinePosition: -100 10 | UnderlineWidth: 50 11 | Ascent: 800 12 | Descent: 200 13 | InvalidEm: 0 14 | LayerCount: 2 15 | Layer: 0 0 "Back" 1 16 | Layer: 1 0 "Fore" 0 17 | XUID: [1021 913 -638292798 6571593] 18 | FSType: 0 19 | OS2Version: 0 20 | OS2_WeightWidthSlopeOnly: 0 21 | OS2_UseTypoMetrics: 1 22 | CreationTime: 1480466430 23 | ModificationTime: 1480467813 24 | PfmFamily: 17 25 | TTFWeight: 400 26 | TTFWidth: 5 27 | LineGap: 90 28 | VLineGap: 0 29 | OS2TypoAscent: 0 30 | OS2TypoAOffset: 1 31 | OS2TypoDescent: 0 32 | OS2TypoDOffset: 1 33 | OS2TypoLinegap: 90 34 | OS2WinAscent: 0 35 | OS2WinAOffset: 1 36 | OS2WinDescent: 0 37 | OS2WinDOffset: 1 38 | HheadAscent: 0 39 | HheadAOffset: 1 40 | HheadDescent: 0 41 | HheadDOffset: 1 42 | OS2Vendor: 'PfEd' 43 | MarkAttachClasses: 1 44 | DEI: 91125 45 | LangName: 1033 46 | Encoding: UnicodeFull 47 | UnicodeInterp: none 48 | NameList: AGL For New Fonts 49 | DisplaySize: -72 50 | AntiAlias: 1 51 | FitToEm: 0 52 | WinInfo: 64 8 8 53 | OnlyBitmaps: 1 54 | BeginPrivate: 0 55 | EndPrivate 56 | TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144 57 | BeginChars: 1114112 0 58 | EndChars 59 | EndSplineFont 60 | -------------------------------------------------------------------------------- /src/NerdFontsSymbols 2048 EM Nerd Font Complete Blank.sfd: -------------------------------------------------------------------------------- 1 | SplineFontDB: 3.0 2 | FontName: Symbols-2048-em 3 | FullName: Symbols-2048-em 4 | FamilyName: Symbols 5 | Weight: Regular 6 | Copyright: Copyright (c) 2016, Ryan McIntyre 7 | Version: 001.000 8 | ItalicAngle: 0 9 | UnderlinePosition: -204 10 | UnderlineWidth: 102 11 | Ascent: 1638 12 | Descent: 410 13 | InvalidEm: 0 14 | LayerCount: 2 15 | Layer: 0 0 "Back" 1 16 | Layer: 1 0 "Fore" 0 17 | XUID: [1021 913 -638292798 6571593] 18 | FSType: 0 19 | OS2Version: 0 20 | OS2_WeightWidthSlopeOnly: 0 21 | OS2_UseTypoMetrics: 1 22 | CreationTime: 1480466430 23 | ModificationTime: 1480467841 24 | PfmFamily: 17 25 | TTFWeight: 400 26 | TTFWidth: 5 27 | LineGap: 184 28 | VLineGap: 0 29 | OS2TypoAscent: 0 30 | OS2TypoAOffset: 1 31 | OS2TypoDescent: 0 32 | OS2TypoDOffset: 1 33 | OS2TypoLinegap: 184 34 | OS2WinAscent: 0 35 | OS2WinAOffset: 1 36 | OS2WinDescent: 0 37 | OS2WinDOffset: 1 38 | HheadAscent: 0 39 | HheadAOffset: 1 40 | HheadDescent: 0 41 | HheadDOffset: 1 42 | OS2Vendor: 'PfEd' 43 | MarkAttachClasses: 1 44 | DEI: 91125 45 | LangName: 1033 46 | Encoding: UnicodeFull 47 | UnicodeInterp: none 48 | NameList: AGL For New Fonts 49 | DisplaySize: -72 50 | AntiAlias: 1 51 | FitToEm: 0 52 | WinInfo: 64 8 8 53 | OnlyBitmaps: 1 54 | BeginPrivate: 0 55 | EndPrivate 56 | TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144 57 | BeginChars: 1114112 0 58 | EndChars 59 | EndSplineFont 60 | -------------------------------------------------------------------------------- /font-patcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf8 3 | # Nerd Fonts Version: 2.1.0 4 | # script version: 3.0.1 5 | 6 | from __future__ import absolute_import, print_function, unicode_literals 7 | 8 | version = "2.1.0" 9 | projectName = "Nerd Fonts" 10 | projectNameAbbreviation = "NF" 11 | projectNameSingular = projectName[:-1] 12 | 13 | import sys 14 | try: 15 | import psMat 16 | except ImportError: 17 | sys.exit(projectName + ": FontForge module is probably not installed. [See: http://designwithfontforge.com/en-US/Installing_Fontforge.html]") 18 | import re 19 | import os 20 | import argparse 21 | from argparse import RawTextHelpFormatter 22 | import errno 23 | import subprocess 24 | import json 25 | try: 26 | import configparser 27 | except ImportError: 28 | sys.exit(projectName + ": configparser module is probably not installed. Try `pip install configparser` or equivalent") 29 | try: 30 | import fontforge 31 | except ImportError: 32 | sys.exit( 33 | projectName + ( 34 | ": FontForge module could not be loaded. Try installing fontforge python bindings " 35 | "[e.g. on Linux Debian or Ubuntu: `sudo apt install fontforge python-fontforge`]" 36 | ) 37 | ) 38 | 39 | 40 | class font_patcher: 41 | def __init__(self): 42 | self.args = None # class 'argparse.Namespace' 43 | self.sym_font_args = [] 44 | self.config = None # class 'configparser.ConfigParser' 45 | self.sourceFont = None # class 'fontforge.font' 46 | self.octiconsExactEncodingPosition = True 47 | self.fontlinuxExactEncodingPosition = True 48 | self.patch_set = None # class 'list' 49 | self.font_dim = None # class 'dict' 50 | self.onlybitmaps = 0 51 | self.extension = "" 52 | self.setup_arguments() 53 | self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) 54 | self.sourceFont = fontforge.open(self.args.font) 55 | self.setup_font_names() 56 | self.remove_ligatures() 57 | make_sure_path_exists(self.args.outputdir) 58 | self.check_position_conflicts() 59 | self.setup_patch_set() 60 | self.setup_line_dimensions() 61 | self.get_sourcefont_dimensions() 62 | self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available 63 | self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used 64 | if self.args.extension == "": 65 | self.extension = os.path.splitext(self.sourceFont.path)[1] 66 | else: 67 | self.extension = '.' + self.args.extension 68 | 69 | 70 | def patch(self): 71 | if self.args.single: 72 | # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. 73 | # This needs to be done on all characters, as some information seems to be lost from the original font file. 74 | self.set_sourcefont_glyph_widths() 75 | 76 | # Prevent opening and closing the fontforge font. Makes things faster when patching 77 | # multiple ranges using the same symbol font. 78 | PreviousSymbolFilename = "" 79 | symfont = None 80 | 81 | for patch in self.patch_set: 82 | if patch['Enabled']: 83 | if PreviousSymbolFilename != patch['Filename']: 84 | # We have a new symbol font, so close the previous one if it exists 85 | if symfont: 86 | symfont.close() 87 | symfont = None 88 | symfont = fontforge.open(__dir__ + "/src/glyphs/" + patch['Filename']) 89 | 90 | # Match the symbol font size to the source font size 91 | symfont.em = self.sourceFont.em 92 | PreviousSymbolFilename = patch['Filename'] 93 | 94 | # If patch table doesn't include a source start and end, re-use the symbol font values 95 | SrcStart = patch['SrcStart'] 96 | SrcEnd = patch['SrcEnd'] 97 | if not SrcStart: 98 | SrcStart = patch['SymStart'] 99 | if not SrcEnd: 100 | SrcEnd = patch['SymEnd'] 101 | self.copy_glyphs(SrcStart, SrcEnd, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes']) 102 | 103 | if symfont: 104 | symfont.close() 105 | print("\nDone with Patch Sets, generating font...") 106 | 107 | # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. 108 | if self.sourceFont.fullname != None: 109 | self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.fullname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) 110 | print("\nGenerated: {}".format(self.sourceFont.fontname)) 111 | else: 112 | self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) 113 | print("\nGenerated: {}".format(self.sourceFont.fullname)) 114 | 115 | if self.args.postprocess: 116 | subprocess.call([self.args.postprocess, self.args.outputdir + "/" + self.sourceFont.fullname + self.extension]) 117 | print("\nPost Processed: {}".format(self.sourceFont.fullname)) 118 | 119 | 120 | def setup_arguments(self): 121 | parser = argparse.ArgumentParser( 122 | description=( 123 | 'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n' 124 | '* Website: https://www.nerdfonts.com\n' 125 | '* Version: ' + version + '\n' 126 | '* Development Website: https://github.com/ryanoasis/nerd-fonts\n' 127 | '* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md'), 128 | formatter_class=RawTextHelpFormatter 129 | ) 130 | 131 | # optional arguments 132 | parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)') 133 | parser.add_argument('-v', '--version', action='version', version=projectName + ": %(prog)s (" + version + ")") 134 | 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)') 135 | 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)') 136 | parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output') 137 | parser.add_argument('-w', '--windows', dest='windows', default=False, action='store_true', help='Limit the internal font name to 31 characters (for Windows compatibility)') 138 | parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs') 139 | parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected') 140 | parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file') 141 | parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing') 142 | 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)') 143 | 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.') 144 | parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)') 145 | parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') 146 | 147 | # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse 148 | progressbars_group_parser = parser.add_mutually_exclusive_group(required=False) 149 | progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set') 150 | progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') 151 | parser.set_defaults(progressbars=True) 152 | 153 | # symbol fonts to include arguments 154 | sym_font_group = parser.add_argument_group('Symbol Fonts') 155 | sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') 156 | 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/)') 157 | sym_font_group.add_argument('--fontlinux', '--fontlogos', dest='fontlinux', default=False, action='store_true', help='Add Font Linux and other open source Glyphs (https://github.com/Lukas-W/font-logos)') 158 | sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') 159 | sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') 160 | sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)') 161 | sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs') 162 | sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)') 163 | 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)') 164 | sym_font_group.add_argument('--weather', '--weathericons', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)') 165 | 166 | self.args = parser.parse_args() 167 | 168 | # if you add a new font, set it to True here inside the if condition 169 | if self.args.complete: 170 | self.args.fontawesome = True 171 | self.args.fontawesomeextension = True 172 | self.args.fontlinux = True 173 | self.args.octicons = True 174 | self.args.powersymbols = True 175 | self.args.pomicons = True 176 | self.args.powerline = True 177 | self.args.powerlineextra = True 178 | self.args.material = True 179 | self.args.weather = True 180 | 181 | if not self.args.complete: 182 | # add the list of arguments for each symbol font to the list self.sym_font_args 183 | for action in sym_font_group._group_actions: 184 | self.sym_font_args.append(action.__dict__['option_strings']) 185 | 186 | # determine whether or not all symbol fonts are to be used 187 | font_complete = True 188 | for sym_font_arg_aliases in self.sym_font_args: 189 | found = False 190 | for alias in sym_font_arg_aliases: 191 | if alias in sys.argv: 192 | found = True 193 | if found is not True: 194 | font_complete = False 195 | self.args.complete = font_complete 196 | 197 | # this one also works but it needs to be updated every time a font is added 198 | # it was a conditional in self.setup_font_names() before, but it was missing 199 | # a symbol font, so it would name the font complete without being so sometimes. 200 | # that's why i did the above. 201 | # 202 | # if you add a new font, put it in here too, as the others are 203 | # self.args.complete = all([ 204 | # self.args.fontawesome is True, 205 | # self.args.fontawesomeextension is True, 206 | # self.args.fontlinux is True, 207 | # self.args.octicons is True, 208 | # self.args.powersymbols is True, 209 | # self.args.pomicons is True, 210 | # self.args.powerline is True, 211 | # self.args.powerlineextra is True, 212 | # self.args.material is True, 213 | # self.args.weather is True 214 | # ]) 215 | 216 | 217 | def setup_font_names(self): 218 | verboseAdditionalFontNameSuffix = " " + projectNameSingular 219 | if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later 220 | additionalFontNameSuffix = " " + projectNameAbbreviation 221 | else: 222 | additionalFontNameSuffix = verboseAdditionalFontNameSuffix 223 | if not self.args.complete: 224 | # NOTE not all symbol fonts have appended their suffix here 225 | if self.args.fontawesome: 226 | additionalFontNameSuffix += " A" 227 | verboseAdditionalFontNameSuffix += " Plus Font Awesome" 228 | if self.args.fontawesomeextension: 229 | additionalFontNameSuffix += " AE" 230 | verboseAdditionalFontNameSuffix += " Plus Font Awesome Extension" 231 | if self.args.octicons: 232 | additionalFontNameSuffix += " O" 233 | verboseAdditionalFontNameSuffix += " Plus Octicons" 234 | if self.args.powersymbols: 235 | additionalFontNameSuffix += " PS" 236 | verboseAdditionalFontNameSuffix += " Plus Power Symbols" 237 | if self.args.pomicons: 238 | additionalFontNameSuffix += " P" 239 | verboseAdditionalFontNameSuffix += " Plus Pomicons" 240 | if self.args.fontlinux: 241 | additionalFontNameSuffix += " L" 242 | verboseAdditionalFontNameSuffix += " Plus Font Logos (Font Linux)" 243 | if self.args.material: 244 | additionalFontNameSuffix += " MDI" 245 | verboseAdditionalFontNameSuffix += " Plus Material Design Icons" 246 | if self.args.weather: 247 | additionalFontNameSuffix += " WEA" 248 | verboseAdditionalFontNameSuffix += " Plus Weather Icons" 249 | 250 | # if all source glyphs included simplify the name 251 | else: 252 | additionalFontNameSuffix = " " + projectNameSingular + " Complete" 253 | verboseAdditionalFontNameSuffix = " " + projectNameSingular + " Complete" 254 | 255 | # add mono signifier to end of name 256 | if self.args.single: 257 | additionalFontNameSuffix += " M" 258 | verboseAdditionalFontNameSuffix += " Mono" 259 | 260 | # basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold) 261 | # this does not seem very reliable so only use the style here as a fallback if the font does not 262 | # have an internal style defined (in sfnt_names) 263 | # using '([^-]*?)' to get the item before the first dash "-" 264 | # using '([^-]*(?!.*-))' to get the item after the last dash "-" 265 | fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", self.sourceFont.fontname).groups() 266 | 267 | # dont trust 'sourceFont.familyname' 268 | familyname = fontname 269 | 270 | # fullname (filename) can always use long/verbose font name, even in windows 271 | if self.sourceFont.fullname != None: 272 | fullname = self.sourceFont.fullname + verboseAdditionalFontNameSuffix 273 | else: 274 | fullname = self.sourceFont.cidfontname + verboseAdditionalFontNameSuffix 275 | 276 | fontname = fontname + additionalFontNameSuffix.replace(" ", "") 277 | 278 | # let us try to get the 'style' from the font info in sfnt_names and fallback to the 279 | # parse fontname if it fails: 280 | try: 281 | # search tuple: 282 | subFamilyTupleIndex = [x[1] for x in self.sourceFont.sfnt_names].index("SubFamily") 283 | 284 | # String ID is at the second index in the Tuple lists 285 | sfntNamesStringIDIndex = 2 286 | 287 | # now we have the correct item: 288 | subFamily = self.sourceFont.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex] 289 | except IndexError: 290 | sys.stderr.write("{}: Could not find 'SubFamily' for given font, falling back to parsed fontname\n".format(projectName)) 291 | subFamily = fallbackStyle 292 | 293 | # some fonts have inaccurate 'SubFamily', if it is Regular let us trust the filename more: 294 | if subFamily == "Regular": 295 | subFamily = fallbackStyle 296 | if self.args.windows: 297 | maxFamilyLength = 31 298 | maxFontLength = maxFamilyLength - len('-' + subFamily) 299 | familyname += " " + projectNameAbbreviation 300 | fullname += " Windows Compatible" 301 | 302 | # now make sure less than 32 characters name length 303 | if len(fontname) > maxFontLength: 304 | fontname = fontname[:maxFontLength] 305 | if len(familyname) > maxFamilyLength: 306 | familyname = familyname[:maxFamilyLength] 307 | else: 308 | familyname += " " + projectNameSingular 309 | if self.args.single: 310 | familyname += " Mono" 311 | 312 | # Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with 313 | # the same name as the same font, even if subFamily is different. 314 | fontname += '-' + subFamily 315 | 316 | # rename font 317 | # 318 | # comply with SIL Open Font License (OFL) 319 | reservedFontNameReplacements = { 320 | 'source' : 'sauce', 321 | 'Source' : 'Sauce', 322 | 'hermit' : 'hurmit', 323 | 'Hermit' : 'Hurmit', 324 | 'hasklig' : 'hasklug', 325 | 'Hasklig' : 'Hasklug', 326 | 'Share' : 'Shure', 327 | 'share' : 'shure', 328 | 'IBMPlex' : 'Blex', 329 | 'ibmplex' : 'blex', 330 | 'IBM-Plex' : 'Blex', 331 | 'IBM Plex' : 'Blex', 332 | 'terminus' : 'terminess', 333 | 'Terminus' : 'Terminess', 334 | 'liberation' : 'literation', 335 | 'Liberation' : 'Literation', 336 | 'iAWriter' : 'iMWriting', 337 | 'iA Writer' : 'iM Writing', 338 | 'iA-Writer' : 'iM-Writing', 339 | 'Anka/Coder' : 'AnaConder', 340 | 'anka/coder' : 'anaconder', 341 | 'Cascadia Code' : 'Caskaydia Cove', 342 | 'cascadia code' : 'caskaydia cove', 343 | 'CascadiaCode' : 'CaskaydiaCove', 344 | 'cascadiacode' : 'caskaydiacove' 345 | } 346 | 347 | # remove overly verbose font names 348 | # particularly regarding Powerline sourced Fonts (https://github.com/powerline/fonts) 349 | additionalFontNameReplacements = { 350 | 'for Powerline': '', 351 | 'ForPowerline': '' 352 | } 353 | 354 | additionalFontNameReplacements2 = { 355 | 'Powerline': '' 356 | } 357 | 358 | projectInfo = ( 359 | "Patched with '" + projectName + " Patcher' (https://github.com/ryanoasis/nerd-fonts)\n\n" 360 | "* Website: https://www.nerdfonts.com\n" 361 | "* Version: " + version + "\n" 362 | "* Development Website: https://github.com/ryanoasis/nerd-fonts\n" 363 | "* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md" 364 | ) 365 | 366 | familyname = replace_font_name(familyname, reservedFontNameReplacements) 367 | fullname = replace_font_name(fullname, reservedFontNameReplacements) 368 | fontname = replace_font_name(fontname, reservedFontNameReplacements) 369 | familyname = replace_font_name(familyname, additionalFontNameReplacements) 370 | fullname = replace_font_name(fullname, additionalFontNameReplacements) 371 | fontname = replace_font_name(fontname, additionalFontNameReplacements) 372 | familyname = replace_font_name(familyname, additionalFontNameReplacements2) 373 | fullname = replace_font_name(fullname, additionalFontNameReplacements2) 374 | fontname = replace_font_name(fontname, additionalFontNameReplacements2) 375 | 376 | # replace any extra whitespace characters: 377 | self.sourceFont.familyname = " ".join(familyname.split()) 378 | self.sourceFont.fullname = " ".join(fullname.split()) 379 | self.sourceFont.fontname = " ".join(fontname.split()) 380 | 381 | self.sourceFont.appendSFNTName(str('English (US)'), str('Preferred Family'), self.sourceFont.familyname) 382 | self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname) 383 | self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname) 384 | self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) 385 | self.sourceFont.comment = projectInfo 386 | self.sourceFont.fontlog = projectInfo 387 | 388 | # TODO version not being set for all font types (e.g. ttf) 389 | # print("Version was {}".format(sourceFont.version)) 390 | if self.sourceFont.version != None: 391 | self.sourceFont.version += ";" + projectName + " " + version 392 | else: 393 | self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version 394 | # print("Version now is {}".format(sourceFont.version)) 395 | 396 | 397 | def remove_ligatures(self): 398 | # let's deal with ligatures (mostly for monospaced fonts) 399 | if self.args.configfile and self.config.read(self.args.configfile): 400 | if self.args.removeligatures: 401 | print("Removing ligatures from configfile `Subtables` section") 402 | ligature_subtables = json.loads(self.config.get("Subtables", "ligatures")) 403 | for subtable in ligature_subtables: 404 | print("Removing subtable:", subtable) 405 | try: 406 | self.sourceFont.removeLookupSubtable(subtable) 407 | print("Successfully removed subtable:", subtable) 408 | except Exception: 409 | print("Failed to remove subtable:", subtable) 410 | elif self.args.removeligatures: 411 | print("Unable to read configfile, unable to remove ligatures") 412 | else: 413 | print("No configfile given, skipping configfile related actions") 414 | 415 | 416 | def check_position_conflicts(self): 417 | # Prevent glyph encoding position conflicts between glyph sets 418 | if self.args.fontawesome and self.args.octicons: 419 | self.octiconsExactEncodingPosition = False 420 | if self.args.fontawesome or self.args.octicons: 421 | self.fontlinuxExactEncodingPosition = False 422 | 423 | 424 | def setup_patch_set(self): 425 | """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ 426 | # Supported params: overlap | careful 427 | # Powerline dividers 428 | SYM_ATTR_POWERLINE = { 429 | 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''}, 430 | 431 | # Arrow tips 432 | 0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 433 | 0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 434 | 0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 435 | 0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 436 | 437 | # Rounded arcs 438 | 0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 439 | 0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 440 | 0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 441 | 0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 442 | 443 | # Bottom Triangles 444 | 0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 445 | 0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 446 | 0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 447 | 0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 448 | 449 | # Top Triangles 450 | 0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 451 | 0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 452 | 0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 453 | 0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 454 | 455 | # Flames 456 | 0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 457 | 0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 458 | 0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 459 | 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 460 | 461 | # Small squares 462 | 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 463 | 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 464 | 465 | # Bigger squares 466 | 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 467 | 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 468 | 469 | # Waveform 470 | 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 471 | 472 | # Hexagons 473 | 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 474 | 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 475 | 476 | # Legos 477 | 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 478 | 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': ''}, 479 | 0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 480 | 481 | # Top and bottom trapezoid 482 | 0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 483 | 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}} 484 | } 485 | 486 | SYM_ATTR_DEFAULT = { 487 | # 'pa' == preserve aspect ratio 488 | 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''} 489 | } 490 | 491 | SYM_ATTR_FONTA = { 492 | # 'pa' == preserve aspect ratio 493 | 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''}, 494 | 495 | # Don't center these arrows vertically 496 | 0xf0dc: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''}, 497 | 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''}, 498 | 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''} 499 | } 500 | 501 | CUSTOM_ATTR = { 502 | # 'pa' == preserve aspect ratio 503 | 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': ''} 504 | } 505 | 506 | # Most glyphs we want to maximize during the scale. However, there are some 507 | # that need to be small or stay relative in size to each other. 508 | # The following list are those glyphs. A tuple represents a range. 509 | DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, 'GlyphsToScale': [(0xe6bd, 0xe6c3)]} 510 | FONTA_SCALE_LIST = {'ScaleGlyph': 0xF17A, 'GlyphsToScale': [0xf005, 0xf006, (0xf026, 0xf028), 0xf02b, 0xf02c, (0xf031, 0xf035), (0xf044, 0xf054), (0xf060, 0xf063), 0xf077, 0xf078, 0xf07d, 0xf07e, 0xf089, (0xf0d7, 0xf0da), (0xf0dc, 0xf0de), (0xf100, 0xf107), 0xf141, 0xf142, (0xf153, 0xf15a), (0xf175, 0xf178), 0xf182, 0xf183, (0xf221, 0xf22d), (0xf255, 0xf25b)]} 511 | OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, 'GlyphsToScale': [(0xf03d, 0xf040), 0xf044, (0xf051, 0xf053), 0xf05a, 0xf05b, 0xf071, 0xf078, (0xf09f, 0xf0aa), 0xf0ca]} 512 | 513 | # Define the character ranges 514 | # Symbol font ranges 515 | self.patch_set = [ 516 | {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE52F, 'SrcStart': 0xE5FA, 'SrcEnd': 0xE62F, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, 517 | {'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}, 518 | {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, 519 | {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, 520 | {'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}, 521 | {'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}, 522 | {'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}, 523 | {'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}, 524 | {'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}, 525 | {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, 526 | {'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 527 | {'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 528 | {'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) 529 | {'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}, 530 | {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, 531 | {'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': self.fontlinuxExactEncodingPosition, 'SymStart': 0xF100, 'SymEnd': 0xF11C, 'SrcStart': 0xF300, 'SrcEnd': 0xF31C, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, 532 | {'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 533 | {'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 534 | {'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 535 | {'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 536 | {'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} 537 | ] 538 | 539 | 540 | def setup_line_dimensions(self): 541 | # win_ascent and win_descent are used to set the line height for windows fonts. 542 | # hhead_ascent and hhead_descent are used to set the line height for mac fonts. 543 | # 544 | # Make the total line size even. This seems to make the powerline separators 545 | # center more evenly. 546 | if self.args.adjustLineHeight: 547 | if (self.sourceFont.os2_winascent + self.sourceFont.os2_windescent) % 2 != 0: 548 | self.sourceFont.os2_winascent += 1 549 | 550 | # Make the line size identical for windows and mac 551 | self.sourceFont.hhea_ascent = self.sourceFont.os2_winascent 552 | self.sourceFont.hhea_descent = -self.sourceFont.os2_windescent 553 | 554 | # Line gap add extra space on the bottom of the line which 555 | # doesn't allow the powerline glyphs to fill the entire line. 556 | self.sourceFont.hhea_linegap = 0 557 | self.sourceFont.os2_typolinegap = 0 558 | 559 | 560 | def get_sourcefont_dimensions(self): 561 | # Initial font dimensions 562 | self.font_dim = { 563 | 'xmin' : 0, 564 | 'ymin' : -self.sourceFont.os2_windescent, 565 | 'xmax' : 0, 566 | 'ymax' : self.sourceFont.os2_winascent, 567 | 'width' : 0, 568 | 'height': 0, 569 | } 570 | 571 | # Find the biggest char width 572 | # Ignore the y-values, os2_winXXXXX values set above are used for line height 573 | # 574 | # 0x00-0x17f is the Latin Extended-A range 575 | for glyph in range(0x00, 0x17f): 576 | try: 577 | (_, _, xmax, _) = self.sourceFont[glyph].boundingBox() 578 | except TypeError: 579 | continue 580 | if self.font_dim['width'] < self.sourceFont[glyph].width: 581 | self.font_dim['width'] = self.sourceFont[glyph].width 582 | if xmax > self.font_dim['xmax']: 583 | self.font_dim['xmax'] = xmax 584 | 585 | # Calculate font height 586 | self.font_dim['height'] = abs(self.font_dim['ymin']) + self.font_dim['ymax'] 587 | 588 | 589 | def get_scale_factor(self, sym_dim): 590 | scale_ratio = 1 591 | 592 | # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit 593 | scale_ratio_x = self.font_dim['width'] / sym_dim['width'] 594 | 595 | # font_dim['height'] represents total line height, keep our symbols sized based upon font's em 596 | # NOTE: is this comment correct? font_dim['height'] isn't used here 597 | scale_ratio_y = self.sourceFont.em / sym_dim['height'] 598 | if scale_ratio_x > scale_ratio_y: 599 | scale_ratio = scale_ratio_y 600 | else: 601 | scale_ratio = scale_ratio_x 602 | return scale_ratio 603 | 604 | 605 | def copy_glyphs(self, sourceFontStart, sourceFontEnd, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes): 606 | """ Copies symbol glyphs into self.sourceFont """ 607 | progressText = '' 608 | careful = False 609 | glyphSetLength = 0 610 | 611 | if self.args.careful: 612 | careful = True 613 | 614 | if exactEncoding is False: 615 | sourceFontList = [] 616 | sourceFontCounter = 0 617 | for i in range(sourceFontStart, sourceFontEnd + 1): 618 | sourceFontList.append(format(i, 'X')) 619 | 620 | scale_factor = 0 621 | if scaleGlyph: 622 | sym_dim = get_glyph_dimensions(symbolFont[scaleGlyph['ScaleGlyph']]) 623 | scale_factor = self.get_scale_factor(sym_dim) 624 | 625 | # Create glyphs from symbol font 626 | # 627 | # If we are going to copy all Glyphs, then assume we want to be careful 628 | # and only copy those that are not already contained in the source font 629 | if symbolFontStart == 0: 630 | symbolFont.selection.all() 631 | self.sourceFont.selection.all() 632 | careful = True 633 | else: 634 | symbolFont.selection.select((str("ranges"), str("unicode")), symbolFontStart, symbolFontEnd) 635 | self.sourceFont.selection.select((str("ranges"), str("unicode")), sourceFontStart, sourceFontEnd) 636 | 637 | # Get number of selected non-empty glyphs @TODO FIXME 638 | for index, sym_glyph in enumerate(symbolFont.selection.byGlyphs): 639 | glyphSetLength += 1 640 | # end for 641 | 642 | if self.args.quiet is False: 643 | sys.stdout.write("Adding " + str(max(1, glyphSetLength)) + " Glyphs from " + setName + " Set \n") 644 | 645 | for index, sym_glyph in enumerate(symbolFont.selection.byGlyphs): 646 | index = max(1, index) 647 | 648 | try: 649 | sym_attr = attributes[sym_glyph.unicode] 650 | except KeyError: 651 | sym_attr = attributes['default'] 652 | 653 | if exactEncoding: 654 | # use the exact same hex values for the source font as for the symbol font 655 | currentSourceFontGlyph = sym_glyph.encoding 656 | 657 | # Save as a hex string without the '0x' prefix 658 | copiedToSlot = format(sym_glyph.unicode, 'X') 659 | else: 660 | # use source font defined hex values based on passed in start and end 661 | # convince that this string really is a hex: 662 | currentSourceFontGlyph = int("0x" + sourceFontList[sourceFontCounter], 16) 663 | copiedToSlot = sourceFontList[sourceFontCounter] 664 | sourceFontCounter += 1 665 | 666 | if int(copiedToSlot, 16) < 0: 667 | print("Found invalid glyph slot number. Skipping.") 668 | continue 669 | 670 | if self.args.quiet is False: 671 | if self.args.progressbars: 672 | update_progress(round(float(index + 1) / glyphSetLength, 2)) 673 | else: 674 | progressText = "\nUpdating glyph: " + str(sym_glyph) + " " + str(sym_glyph.glyphname) + " putting at: " + copiedToSlot 675 | sys.stdout.write(progressText) 676 | sys.stdout.flush() 677 | 678 | # Prepare symbol glyph dimensions 679 | sym_dim = get_glyph_dimensions(sym_glyph) 680 | 681 | # check if a glyph already exists in this location 682 | if careful or 'careful' in sym_attr['params']: 683 | if copiedToSlot.startswith("uni"): 684 | copiedToSlot = copiedToSlot[3:] 685 | codepoint = int("0x" + copiedToSlot, 16) 686 | if codepoint in self.sourceFont: 687 | if self.args.quiet is False: 688 | print(" Found existing Glyph at {}. Skipping...".format(copiedToSlot)) 689 | 690 | # We don't want to touch anything so move to next Glyph 691 | continue 692 | 693 | # Select and copy symbol from its encoding point 694 | # We need to do this select after the careful check, this way we don't 695 | # reset our selection before starting the next loop 696 | symbolFont.selection.select(sym_glyph.encoding) 697 | symbolFont.copy() 698 | 699 | # Paste it 700 | self.sourceFont.selection.select(currentSourceFontGlyph) 701 | self.sourceFont.paste() 702 | self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname 703 | scale_ratio_x = 1 704 | scale_ratio_y = 1 705 | 706 | # Now that we have copy/pasted the glyph, if we are creating a monospace 707 | # font we need to scale and move the glyphs. It is possible to have 708 | # empty glyphs, so we need to skip those. 709 | if self.args.single and sym_dim['width'] and sym_dim['height']: 710 | # If we want to preserve that aspect ratio of the glyphs we need to 711 | # find the largest possible scaling factor that will allow the glyph 712 | # to fit in both the x and y directions 713 | if sym_attr['stretch'] == 'pa': 714 | if scale_factor and use_scale_glyph(sym_glyph.unicode, scaleGlyph['GlyphsToScale']): 715 | # We want to preserve the relative size of each glyph to other glyphs 716 | # in the same symbol font. 717 | scale_ratio_x = scale_factor 718 | scale_ratio_y = scale_factor 719 | else: 720 | # In this case, each glyph is sized independently to each other 721 | scale_ratio_x = self.get_scale_factor(sym_dim) 722 | scale_ratio_y = scale_ratio_x 723 | else: 724 | if 'x' in sym_attr['stretch']: 725 | # Stretch the glyph horizontally to fit the entire available width 726 | scale_ratio_x = self.font_dim['width'] / sym_dim['width'] 727 | # end if single width 728 | 729 | # non-monospace (double width glyphs) 730 | # elif sym_dim['width'] and sym_dim['height']: 731 | # any special logic we want to apply for double-width variation 732 | # would go here 733 | 734 | if 'y' in sym_attr['stretch']: 735 | # Stretch the glyph vertically to total line height (good for powerline separators) 736 | # Currently stretching vertically for both monospace and double-width 737 | scale_ratio_y = self.font_dim['height'] / sym_dim['height'] 738 | 739 | if scale_ratio_x != 1 or scale_ratio_y != 1: 740 | if 'overlap' in sym_attr['params']: 741 | scale_ratio_x *= 1 + sym_attr['params']['overlap'] 742 | scale_ratio_y *= 1 + sym_attr['params']['overlap'] 743 | self.sourceFont.transform(psMat.scale(scale_ratio_x, scale_ratio_y)) 744 | 745 | # Use the dimensions from the newly pasted and stretched glyph 746 | sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) 747 | y_align_distance = 0 748 | if sym_attr['valign'] == 'c': 749 | # Center the symbol vertically by matching the center of the line height and center of symbol 750 | sym_ycenter = sym_dim['ymax'] - (sym_dim['height'] / 2) 751 | font_ycenter = self.font_dim['ymax'] - (self.font_dim['height'] / 2) 752 | y_align_distance = font_ycenter - sym_ycenter 753 | 754 | # Handle glyph l/r/c alignment 755 | x_align_distance = 0 756 | if sym_attr['align']: 757 | # First find the baseline x-alignment (left alignment amount) 758 | x_align_distance = self.font_dim['xmin'] - sym_dim['xmin'] 759 | if sym_attr['align'] == 'c': 760 | # Center align 761 | x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2) 762 | elif sym_attr['align'] == 'r': 763 | # Right align 764 | x_align_distance += self.font_dim['width'] - sym_dim['width'] 765 | 766 | if 'overlap' in sym_attr['params']: 767 | overlap_width = self.font_dim['width'] * sym_attr['params']['overlap'] 768 | if sym_attr['align'] == 'l': 769 | x_align_distance -= overlap_width 770 | if sym_attr['align'] == 'r': 771 | x_align_distance += overlap_width 772 | 773 | align_matrix = psMat.translate(x_align_distance, y_align_distance) 774 | self.sourceFont.transform(align_matrix) 775 | 776 | # Needed for setting 'advance width' on each glyph so they do not overlap, 777 | # also ensures the font is considered monospaced on Windows by setting the 778 | # same width for all character glyphs. This needs to be done for all glyphs, 779 | # even the ones that are empty and didn't go through the scaling operations. 780 | self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph]) 781 | 782 | # Ensure after horizontal adjustments and centering that the glyph 783 | # does not overlap the bearings (edges) 784 | self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) 785 | 786 | # reset selection so iteration works properly @TODO fix? rookie misunderstanding? 787 | # This is likely needed because the selection was changed when the glyph was copy/pasted 788 | if symbolFontStart == 0: 789 | symbolFont.selection.all() 790 | else: 791 | symbolFont.selection.select((str("ranges"), str("unicode")), symbolFontStart, symbolFontEnd) 792 | # end for 793 | 794 | if self.args.quiet is False or self.args.progressbars: 795 | sys.stdout.write("\n") 796 | 797 | 798 | def set_sourcefont_glyph_widths(self): 799 | """ Makes self.sourceFont monospace compliant """ 800 | 801 | for glyph in self.sourceFont.glyphs(): 802 | if (glyph.width == self.font_dim['width']): 803 | # Don't touch the (negative) bearings if the width is ok 804 | # Ligartures will have these. 805 | continue 806 | 807 | if (glyph.width != 0): 808 | # If the width is zero this glyph is intened to be printed on top of another one. 809 | # In this case we need to keep the negative bearings to shift it 'left'. 810 | # Things like Ä have these: composed of U+0041 'A' and U+0308 'double dot above' 811 | # 812 | # If width is not zero, correct the bearings such that they are within the width: 813 | self.remove_glyph_neg_bearings(glyph) 814 | 815 | self.set_glyph_width_mono(glyph) 816 | 817 | 818 | def remove_glyph_neg_bearings(self, glyph): 819 | """ Sets passed glyph's bearings 0 if they are negative. """ 820 | try: 821 | if glyph.left_side_bearing < 0: 822 | glyph.left_side_bearing = 0 823 | if glyph.right_side_bearing < 0: 824 | glyph.right_side_bearing = 0 825 | except: 826 | pass 827 | 828 | 829 | def set_glyph_width_mono(self, glyph): 830 | """ Sets passed glyph.width to self.font_dim.width. 831 | 832 | self.font_dim.width is set with self.get_sourcefont_dimensions(). 833 | """ 834 | try: 835 | glyph.width = self.font_dim['width'] 836 | except: 837 | pass 838 | 839 | 840 | def replace_font_name(font_name, replacement_dict): 841 | """ Replaces all keys with vals from replacement_dict in font_name. """ 842 | for key, val in replacement_dict.items(): 843 | font_name = font_name.replace(key, val) 844 | return font_name 845 | 846 | 847 | def make_sure_path_exists(path): 848 | """ Verifies path passed to it exists. """ 849 | try: 850 | os.makedirs(path) 851 | except OSError as exception: 852 | if exception.errno != errno.EEXIST: 853 | raise 854 | 855 | 856 | def get_glyph_dimensions(glyph): 857 | """ Returns dict of the dimesions of the glyph passed to it. """ 858 | bbox = glyph.boundingBox() 859 | return { 860 | 'xmin' : bbox[0], 861 | 'ymin' : bbox[1], 862 | 'xmax' : bbox[2], 863 | 'ymax' : bbox[3], 864 | 'width' : bbox[2] + (-bbox[0]), 865 | 'height': bbox[3] + (-bbox[1]), 866 | } 867 | 868 | 869 | def use_scale_glyph(unicode_value, glyph_list): 870 | """ Determines whether or not to use scaled glyphs for glyphs in passed glyph_list """ 871 | for i in glyph_list: 872 | if isinstance(i, tuple): 873 | if unicode_value >= i[0] and unicode_value <= i[1]: 874 | return True 875 | else: 876 | if unicode_value == i: 877 | return True 878 | return False 879 | 880 | 881 | def update_progress(progress): 882 | """ Updates progress bar length. 883 | 884 | Accepts a float between 0.0 and 1.0. Any int will be converted to a float. 885 | A value at 1 or bigger represents 100% 886 | modified from: https://stackoverflow.com/questions/3160699/python-progress-bar 887 | """ 888 | barLength = 40 # Modify this to change the length of the progress bar 889 | if isinstance(progress, int): 890 | progress = float(progress) 891 | if progress >= 1: 892 | progress = 1 893 | status = "Done...\r\n" # NOTE: status initialized and never used 894 | block = int(round(barLength * progress)) 895 | text = "\r╢{0}╟ {1}%".format("█" * block + "░" * (barLength - block), int(progress * 100)) 896 | sys.stdout.write(text) 897 | sys.stdout.flush() 898 | 899 | 900 | def check_fontforge_min_version(): 901 | """ Verifies installed FontForge version meets minimum requirement. """ 902 | minimumVersion = 20141231 903 | actualVersion = int(fontforge.version()) 904 | 905 | # un-comment following line for testing invalid version error handling 906 | # actualVersion = 20120731 907 | 908 | # versions tested: 20150612, 20150824 909 | if actualVersion < minimumVersion: 910 | sys.stderr.write("{}: You seem to be using an unsupported (old) version of fontforge: {}\n".format(projectName, actualVersion)) 911 | sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion)) 912 | sys.exit(1) 913 | 914 | 915 | def main(): 916 | check_fontforge_min_version() 917 | patcher = font_patcher() 918 | patcher.patch() 919 | 920 | 921 | if __name__ == "__main__": 922 | __dir__ = os.path.dirname(os.path.abspath(__file__)) 923 | main() 924 | --------------------------------------------------------------------------------