diff --git a/bin/font-patcher b/bin/font-patcher index fe7d9e8..bb8a112 100755 --- a/bin/font-patcher +++ b/bin/font-patcher @@ -1,23 +1,19 @@ #!/usr/bin/env python # coding=utf8 -# Nerd Fonts Version: 2.2.1 +# Nerd Fonts Version: 2.2.2 # Script version is further down from __future__ import absolute_import, print_function, unicode_literals # Change the script version when you edit this script: -script_version = "3.0.3" +script_version = "3.0.6" -version = "2.2.1" +version = "2.2.2" projectName = "Nerd Fonts" projectNameAbbreviation = "NF" projectNameSingular = projectName[:-1] import sys -try: - import psMat -except ImportError: - sys.exit(projectName + ": FontForge module is probably not installed. [See: http://designwithfontforge.com/en-US/Installing_Fontforge.html]") import re import os import argparse @@ -30,12 +26,13 @@ try: except ImportError: sys.exit(projectName + ": configparser module is probably not installed. Try `pip install configparser` or equivalent") try: + import psMat import fontforge except ImportError: sys.exit( projectName + ( ": FontForge module could not be loaded. Try installing fontforge python bindings " - "[e.g. on Linux Debian or Ubuntu: `sudo apt install fontforge python-fontforge`]" + "[e.g. on Linux Debian or Ubuntu: `sudo apt install fontforge python3-fontforge`]" ) ) @@ -167,6 +164,7 @@ class font_patcher: self.font_dim = None # class 'dict' self.onlybitmaps = 0 self.extension = "" + self.essential = set() self.setup_arguments() self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) if not os.path.isfile(self.args.font): @@ -181,6 +179,7 @@ class font_patcher: except Exception: sys.exit(projectName + ": Can not open font, try to open with fontforge interactively to get more information") self.setup_version() + self.get_essential_references() self.setup_name_backup() self.remove_ligatures() make_sure_path_exists(self.args.outputdir) @@ -219,6 +218,10 @@ class font_patcher: PreviousSymbolFilename = "" 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)) + for patch in self.patch_set: if patch['Enabled']: if PreviousSymbolFilename != patch['Filename']: @@ -226,7 +229,13 @@ class font_patcher: if symfont: symfont.close() symfont = None - symfont = fontforge.open(self.args.glyphdir + patch['Filename']) + 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'])) + 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'])) + symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename'])) # Match the symbol font size to the source font size symfont.em = self.sourceFont.em @@ -320,6 +329,7 @@ class font_patcher: parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching') parser.add_argument('--makegroups', dest='makegroups', default=False, action='store_true', help='Use alternative method to name patched fonts (experimental)') + parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")') # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse progressbars_group_parser = parser.add_mutually_exclusive_group(required=False) @@ -332,7 +342,7 @@ class font_patcher: sym_font_group = parser.add_argument_group('Symbol Fonts') sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') sym_font_group.add_argument('--fontawesomeextension', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)') - sym_font_group.add_argument('--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)') + sym_font_group.add_argument('--fontlogos', '--fontlinux', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)') sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)') sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') @@ -351,7 +361,7 @@ class font_patcher: if self.args.complete: self.args.fontawesome = True self.args.fontawesomeextension = True - self.args.fontlinux = True + self.args.fontlogos = True self.args.octicons = True self.args.codicons = True self.args.powersymbols = True @@ -380,6 +390,10 @@ class font_patcher: if self.args.alsowindows: self.args.windows = False + if self.args.nonmono and self.args.single: + print("Warniung: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.") + self.args.nonmono = False + # this one also works but it needs to be updated every time a font is added # it was a conditional in self.setup_font_names() before, but it was missing # a symbol font, so it would name the font complete without being so sometimes. @@ -389,7 +403,7 @@ class font_patcher: # self.args.complete = all([ # self.args.fontawesome is True, # self.args.fontawesomeextension is True, - # self.args.fontlinux is True, + # self.args.fontlogos is True, # self.args.octicons is True, # self.args.powersymbols is True, # self.args.pomicons is True, @@ -436,9 +450,9 @@ class font_patcher: if self.args.pomicons: additionalFontNameSuffix += " P" verboseAdditionalFontNameSuffix += " Plus Pomicons" - if self.args.fontlinux: + if self.args.fontlogos: additionalFontNameSuffix += " L" - verboseAdditionalFontNameSuffix += " Plus Font Logos (Font Linux)" + verboseAdditionalFontNameSuffix += " Plus Font Logos" if self.args.material: additionalFontNameSuffix += " MDI" verboseAdditionalFontNameSuffix += " Plus Material Design Icons" @@ -522,6 +536,8 @@ class font_patcher: 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 @@ -673,7 +689,7 @@ class font_patcher: # Supported params: overlap | careful # Powerline dividers SYM_ATTR_POWERLINE = { - 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''}, + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, # Arrow tips 0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, @@ -706,23 +722,23 @@ class font_patcher: 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, # Small squares - 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, - 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': ''}, + 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, + 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}}, # Bigger squares - 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, - 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': ''}, + 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, + 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}}, # Waveform 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, # Hexagons - 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, - 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, + 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, + 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, # Legos - 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': ''}, - 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': ''}, + 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, + 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, # Top and bottom trapezoid @@ -732,22 +748,22 @@ class font_patcher: SYM_ATTR_DEFAULT = { # 'pa' == preserve aspect ratio - 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''} + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} } SYM_ATTR_FONTA = { # 'pa' == preserve aspect ratio - 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': ''}, + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, # Don't center these arrows vertically - 0xf0dc: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''}, - 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''}, - 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': ''} + 0xf0dc: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}} } CUSTOM_ATTR = { # 'pa' == preserve aspect ratio - 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': ''} + 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} } # Most glyphs we want to maximize during the scale. However, there are some @@ -789,7 +805,7 @@ class font_patcher: # Define the character ranges # Symbol font ranges self.patch_set = [ - {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE531, 'SrcStart': 0xE5FA, 'SrcEnd': 0xE631, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE534, 'SrcStart': 0xE5FA, 'SrcEnd': 0xE634, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'SrcEnd': 0xE7C5, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, @@ -804,7 +820,7 @@ class font_patcher: {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'SrcEnd': 0xFD46, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'SrcEnd': None , 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'SrcEnd': None , 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'SrcEnd': 0xF505, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap @@ -835,6 +851,17 @@ class font_patcher: self.sourceFont.hhea_linegap = 0 self.sourceFont.os2_typolinegap = 0 + def get_essential_references(self): + """Find glyphs that are needed for the basic glyphs""" + # Sometimes basic glyphs are constructed from multiple other glyphs. + # Find out which other glyphs are also needed to keep the basic + # glyphs intact. + # 0x00-0x17f is the Latin Extended-A range + for glyph in range(0x21, 0x17f): + if not glyph in self.sourceFont: + continue + for r in self.sourceFont[glyph].references: + self.essential.add(self.sourceFont[r[0]].unicode) def get_sourcefont_dimensions(self): # Initial font dimensions @@ -886,10 +913,7 @@ class font_patcher: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = self.font_dim['width'] / sym_dim['width'] - - # font_dim['height'] represents total line height, keep our symbols sized based upon font's em - # NOTE: is this comment correct? font_dim['height'] isn't used here - scale_ratio_y = self.sourceFont.em / sym_dim['height'] + scale_ratio_y = self.font_dim['height'] / sym_dim['height'] if scale_ratio_x > scale_ratio_y: scale_ratio = scale_ratio_y else: @@ -927,6 +951,8 @@ class font_patcher: if self.args.quiet is False: sys.stdout.write("Adding " + str(max(1, glyphSetLength)) + " Glyphs from " + setName + " Set \n") + currentSourceFontGlyph = -1 # initialize for the exactEncoding case + for index, sym_glyph in enumerate(symbolFontSelection): index = max(1, index) @@ -936,8 +962,17 @@ class font_patcher: sym_attr = attributes['default'] if exactEncoding: - # use the exact same hex values for the source font as for the symbol font - currentSourceFontGlyph = sym_glyph.encoding + # Use the exact same hex values for the source font as for the symbol font. + # Problem is we do not know the codepoint of the sym_glyph and because it + # came from a selection.byGlyphs there might be skipped over glyphs. + # The iteration is still in the order of the selection by codepoint, + # so we take the next allowed codepoint of the current glyph + possible_codes = [ ] + if sym_glyph.unicode > currentSourceFontGlyph: + possible_codes += [ sym_glyph.unicode ] + if sym_glyph.altuni: + possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] + currentSourceFontGlyph = min(possible_codes) else: # use source font defined hex values based on passed in start and end currentSourceFontGlyph = sourceFontList[sourceFontCounter] @@ -952,10 +987,11 @@ class font_patcher: sys.stdout.flush() # check if a glyph already exists in this location - if careful or 'careful' in sym_attr['params']: + if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential: if currentSourceFontGlyph in self.sourceFont: if self.args.quiet is False: - print(" Found existing Glyph at {:X}. Skipping...".format(currentSourceFontGlyph)) + careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' + print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph)) # We don't want to touch anything so move to next Glyph continue else: @@ -1012,13 +1048,10 @@ class font_patcher: # Currently stretching vertically for both monospace and double-width scale_ratio_y = self.font_dim['height'] / sym_dim['height'] - if 'overlap' in sym_attr['params']: - overlap = sym_attr['params']['overlap'] - else: - overlap = 0 + overlap = sym_attr['params'].get('overlap') if scale_ratio_x != 1 or scale_ratio_y != 1: - if overlap != 0: + if overlap: scale_ratio_x *= 1 + overlap scale_ratio_y *= 1 + overlap self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) @@ -1044,7 +1077,7 @@ class font_patcher: # Right align x_align_distance += self.font_dim['width'] - sym_dim['width'] - if overlap != 0: + if overlap: overlap_width = self.font_dim['width'] * overlap if sym_attr['align'] == 'l': x_align_distance -= overlap_width @@ -1054,20 +1087,26 @@ class font_patcher: align_matrix = psMat.translate(x_align_distance, y_align_distance) self.sourceFont[currentSourceFontGlyph].transform(align_matrix) + # Ensure after horizontal adjustments and centering that the glyph + # does not overlap the bearings (edges) + if not overlap: + self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) + # Needed for setting 'advance width' on each glyph so they do not overlap, # also ensures the font is considered monospaced on Windows by setting the # same width for all character glyphs. This needs to be done for all glyphs, # even the ones that are empty and didn't go through the scaling operations. + # It should come after setting the glyph bearings self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph]) - # Ensure after horizontal adjustments and centering that the glyph - # does not overlap the bearings (edges) - self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) + # Re-remove negative bearings for target font with variable advance width + if self.args.nonmono: + self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) # Check if the inserted glyph is scaled correctly for monospace if self.args.single: (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() - if int(xmax - xmin) > self.font_dim['width'] * (1 + overlap): + 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)) @@ -1114,11 +1153,15 @@ class font_patcher: self.font_dim.width is set with self.get_sourcefont_dimensions(). """ try: + # Fontforge handles the width change like this: + # - Keep existing left_side_bearing + # - Set width + # - Calculate and set new right_side_bearing glyph.width = self.font_dim['width'] except: pass - def prepareScaleGlyph(self, scaleGlyph, symbolFont): + def prepareScaleGlyph(self, scaleGlyph, symbolFont, destGlyph): """ Prepare raw ScaleGlyph data for use """ # The GlyphData is a dict with these (possible) entries: # 'GlyphsToScale': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled @@ -1153,13 +1196,14 @@ class font_patcher: else: scaleGlyph['scales'] = [] for group in scaleGlyph['GlyphsToScale']: - sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ]) + sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ], destGlyph) scaleGlyph['scales'].append(self.get_scale_factor(sym_dim)) def get_glyph_scale(self, unicode_value, scaleGlyph, symbolFont): """ Determines whether or not to use scaled glyphs for glyphs in passed glyph_list """ + # Potentially destorys the contents of self.sourceFont[unicode_value] if not 'scales' in scaleGlyph: - self.prepareScaleGlyph(scaleGlyph, symbolFont) + self.prepareScaleGlyph(scaleGlyph, symbolFont, self.sourceFont[unicode_value]) for glyph_list, scale in zip(scaleGlyph['GlyphsToScale'], scaleGlyph['scales']): if unicode_value in glyph_list: return scale @@ -1181,13 +1225,22 @@ def make_sure_path_exists(path): if exception.errno != errno.EEXIST: raise -def get_multiglyph_boundingBox(glyphs): - """ Returns dict of the dimensions of multiple glyphs combined """ +def get_multiglyph_boundingBox(glyphs, destGlyph = None): + """ Returns dict of the dimensions of multiple glyphs combined(, as if they are copied into destGlyph) """ + # If destGlyph is given the glyph(s) are first copied over into that + # glyph and measured in that font (to avoid rounding errors) + # Leaves the destGlyph in unknown state! bbox = [ None, None, None, None ] for glyph in glyphs: if glyph is None: # Glyph has been in defining range but is not in the actual font continue + if destGlyph: + glyph.font.selection.select(glyph) + glyph.font.copy() + destGlyph.font.selection.select(destGlyph) + destGlyph.font.paste() + glyph = destGlyph gbb = glyph.boundingBox() bbox[0] = gbb[0] if bbox[0] is None or bbox[0] > gbb[0] else bbox[0] bbox[1] = gbb[1] if bbox[1] is None or bbox[1] > gbb[1] else bbox[1] diff --git a/src/glyphs/original-source.otf b/src/glyphs/original-source.otf index 92b0b96..ea10f57 100644 Binary files a/src/glyphs/original-source.otf and b/src/glyphs/original-source.otf differ