From 32a93386bda4d0dbc5434462ff03635d7f72180e Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 15 Mar 2023 01:26:02 +0000 Subject: [PATCH] chore: change batteries --- bin/font-patcher | 180 +++++++++++++++++++-------- src/glyphs/PowerlineExtraSymbols.otf | Bin 84852 -> 84952 bytes src/glyphs/extraglyphs.sfd | Bin 3234 -> 15109 bytes 3 files changed, 128 insertions(+), 52 deletions(-) diff --git a/bin/font-patcher b/bin/font-patcher index 0f9861b..581eaee 100755 --- a/bin/font-patcher +++ b/bin/font-patcher @@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals # Change the script version when you edit this script: -script_version = "3.5.11" +script_version = "3.6.1" version = "2.3.3" projectName = "Nerd Fonts" @@ -292,6 +292,7 @@ class font_patcher: self.patch_set = None # class 'list' self.font_dim = None # class 'dict' self.font_extrawide = False + self.source_monospaced = None # Later True or False self.onlybitmaps = 0 self.essential = set() self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) @@ -301,8 +302,7 @@ class font_patcher: self.setup_version() self.get_essential_references() self.setup_name_backup(font) - if not self.args.nonmono: - self.assert_monospace() + self.assert_monospace() self.remove_ligatures() self.setup_patch_set() self.get_sourcefont_dimensions() @@ -343,6 +343,7 @@ class font_patcher: 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'])) + symfont.encoding = 'UnicodeFull' # Match the symbol font size to the source font size symfont.em = self.sourceFont.em @@ -448,7 +449,6 @@ class font_patcher: def setup_font_names(self, font): - print(font.persistent) font.fontname = font.persistent["fontname"] if isinstance(font.persistent["fullname"], str): font.fullname = font.persistent["fullname"] @@ -716,6 +716,9 @@ class font_patcher: def assert_monospace(self): # Check if the sourcefont is monospaced width_mono, offending_char = is_monospaced(self.sourceFont) + self.source_monospaced = width_mono + if self.args.nonmono: + return 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): @@ -735,12 +738,36 @@ class font_patcher: def setup_patch_set(self): """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ - # Supported params: overlap | careful + + box_enabled = self.source_monospaced # Box glyph only for monospaced + if box_enabled: + self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f) + box_glyphs_target = len(list(self.sourceFont.selection)) + 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 + 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)) + box_keep = False + box_enabled = True + else: + box_keep = True # just scale do not copy + box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this + + # Stretch 'xz' or 'pa' (preserve aspect ratio) + # Supported params: overlap | careful | xy-ratio | dont_copy # Overlap value is used horizontally but vertically limited to 0.01 + # Careful does not overwrite/modify existing glyphs # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) # '1' means occupu 1 cell (default for 'xy') # '2' means occupy 2 cells (default for 'pa') - # Powerline dividers + # '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up) + # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones + + SYM_ATTR_DEFAULT = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} + } SYM_ATTR_POWERLINE = { 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, @@ -800,12 +827,9 @@ class font_patcher: 0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}, 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}} } - - SYM_ATTR_DEFAULT = { - # 'pa' == preserve aspect ratio - 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} + SYM_ATTR_TRIGRAPH = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa1!', 'params': {'overlap': -0.10, 'careful': True}} } - SYM_ATTR_FONTA = { # 'pa' == preserve aspect ratio 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, @@ -818,7 +842,13 @@ class font_patcher: SYM_ATTR_HEAVYBRACKETS = { 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': True}} } - + SYM_ATTR_BOX = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}}, + # No overlap with checkered greys (commented out because that raises problems on rescaling clients) + # 0x2591: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2592: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + } CUSTOM_ATTR = { # 'pa' == preserve aspect ratio 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} @@ -867,10 +897,24 @@ class font_patcher: # For historic reasons ScaleGroups is sometimes called 'new method' and ScaleGlyph 'old'. # The codepoints mentioned here are symbol-font-codepoints. - DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo + BOX_SCALE_LIST = {'ScaleGroups': [ + [*range(0x2500, 0x2570 + 1), *range(0x2574, 0x257f + 1)], # box drawing + range(0x2571, 0x2573 + 1), # diagonals + [*range(0x2580, 0x2590 + 1), 0x2594, 0x2595], # blocks + range(0x2591, 0x2593 + 1), # greys + range(0x2594, 0x259f + 1), # quards (Note: quard 2597 in Hack is wrong, scales like block!) + ]} + CODI_SCALE_LIST = {'ScaleGroups': [ + range(0xea99, 0xeaa1 + 1), # arrows + range(0xeb6e, 0xeb71 + 1), # triangles + range(0xeab4, 0xeab7 + 1), # chevrons + [0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles + [0xeacc, 0xeaba], # dash + ]} + DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo 'GlyphsToScale': [ (0xe6bd, 0xe6c3) # very small things - ]} + ]} FONTA_SCALE_LIST = {'ScaleGroups': [ [0xf005, 0xf006, 0xf089], # star, star empty, half star range(0xf026, 0xf028 + 1), # volume off, down, up @@ -892,7 +936,7 @@ class font_patcher: range(0xf221, 0xf22d + 1), # gender or so range(0xf255, 0xf25b + 1), # hand symbols ]} - OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?) + OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?) 'GlyphsToScale': [ (0xf03d, 0xf040), # arrows 0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles @@ -900,7 +944,7 @@ class font_patcher: 0xf071, 0xf09f, 0xf0a0, 0xf0a1, # small arrows 0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons 0xf0ca, # dash - ]} + ]} WEATH_SCALE_LIST = {'ScaleGroups': [ [0xf03c, 0xf042, 0xf045 ], # degree signs [0xf043, 0xf044, 0xf048, 0xf04b, 0xf04c, 0xf04d, 0xf057, 0xf058, 0xf087, 0xf088], # arrows @@ -921,11 +965,13 @@ class font_patcher: ]} MDI_SCALE_LIST = None # Maybe later add some selected ScaleGroups + # 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': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, - {'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_HEAVYBRACKETS}, + {'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_HEAVYBRACKETS}, + {'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX}, {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleRules': 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, 'ScaleRules': 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, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, @@ -933,6 +979,7 @@ class font_patcher: {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0x2630, 'SymEnd': 0x2630, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_TRIGRAPH}, {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'ScaleRules': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize @@ -946,7 +993,7 @@ class font_patcher: {'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.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': None, '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} ] @@ -976,9 +1023,10 @@ class font_patcher: self.add_glyphrefs_to_essential(altcode) # From fontforge documentation: # glyph.references return a tuple of tuples containing, for each reference in foreground, - # a glyph name, a transformation matrix, and whether the reference is currently selected. + # a glyph name, a transformation matrix, and (depending on ff version) whether the + # reference is currently selected. references = self.sourceFont[unicode].references - for refcode in [ self.sourceFont[n].unicode for n, m, s in references ]: + for refcode in [ self.sourceFont[n].unicode for n, *_ in references ]: # tuple of 2 or 3 depending on ff version if refcode not in self.essential and refcode >= 0: self.add_glyphrefs_to_essential(refcode) @@ -1075,7 +1123,8 @@ class font_patcher: # Step 2 # Find the biggest char width and advance width # 0x00-0x17f is the Latin Extended-A range - warned = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target + warned1 = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target + warned2 = warned1 for glyph in range(0x21, 0x17f): if glyph in range(0x7F, 0xBF) or glyph in [ 0x132, 0x133, # IJ, ij (in Overpass Mono) @@ -1091,30 +1140,39 @@ class font_patcher: # print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) if self.font_dim['width'] < self.sourceFont[glyph].width: self.font_dim['width'] = self.sourceFont[glyph].width - if not warned and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z + 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))) - warned = True + 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") + 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") + self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used # print("FINAL", self.font_dim) + def get_target_width(self, stretch): + """ Get the target width (1 or 2 'cell') for a given stretch parameter """ + # For monospaced fonts all chars need to be maximum 'one' space wide + # other fonts allows double width glyphs for 'pa' or if requested with '2' + if self.args.single or ('pa' not in stretch and '2' not in stretch) or '1' in stretch: + return 1 + return 2 + def get_scale_factors(self, sym_dim, stretch): """ Get scale in x and y as tuple """ # It is possible to have empty glyphs, so we need to skip those. if not sym_dim['width'] or not sym_dim['height']: return (1.0, 1.0) - # For monospaced fonts all chars need to be maximum 'one' space wide - # other fonts allows double width glyphs for 'pa' or if requested with '2' - if self.args.single or ('pa' not in stretch and '2' not in stretch) or '1' in stretch: - relative_width = 1.0 - else: - relative_width = 2.0 - target_width = self.font_dim['width'] * relative_width + target_width = self.font_dim['width'] * self.get_target_width(stretch) scale_ratio_x = target_width / sym_dim['width'] # font_dim['height'] represents total line height, keep our symbols sized based upon font's em @@ -1125,7 +1183,7 @@ class font_patcher: if 'pa' in stretch: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) - if not self.args.single: + if not self.args.single and not '!' in stretch: # non monospaced fonts just scale down on 'pa', not up scale_ratio_x = min(scale_ratio_x, 1.0) scale_ratio_y = scale_ratio_x @@ -1163,7 +1221,9 @@ class font_patcher: glyphSetLength = len(symbolFontSelection) if not self.args.quiet: - sys.stdout.write("Adding {} Glyphs from {} Set\n".format(glyphSetLength, setName)) + modify = attributes['default']['params'].get('dont_copy') + sys.stdout.write("{} {} Glyphs from {} Set\n".format( + "Adding" if not modify else "Rescaling", glyphSetLength, setName)) currentSourceFontGlyph = -1 # initialize for the exactEncoding case width_warning = False @@ -1223,29 +1283,37 @@ class font_patcher: if currentSourceFontGlyph in self.sourceFont: self.sourceFont[currentSourceFontGlyph].removePosSub("*") - # This will destroy any content currently in currentSourceFontGlyph, so do it first - glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None + dont_copy = sym_attr['params'].get('dont_copy') - # Select and copy symbol from its encoding point - # We need to do this select after the careful check, this way we don't - # reset our selection before starting the next loop - symbolFont.selection.select(sym_glyph.encoding) - symbolFont.copy() + if dont_copy: + # Just prepare scaling of existing glyphs + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None + else: + # This will destroy any content currently in currentSourceFontGlyph, so do it first + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None - # Paste it - self.sourceFont.selection.select(currentSourceFontGlyph) - self.sourceFont.paste() - self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname - self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols + # Select and copy symbol from its encoding point + # We need to do this select after the careful check, this way we don't + # reset our selection before starting the next loop + symbolFont.selection.select(sym_glyph.encoding) + symbolFont.copy() + + # Paste it + self.sourceFont.selection.select(currentSourceFontGlyph) + self.sourceFont.paste() + self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname + self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols # Prepare symbol glyph dimensions sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) if glyph_scale_data is not None: if glyph_scale_data[1] is not None: sym_dim = glyph_scale_data[1] # Use combined bounding box - # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') - # Except we do not have glyph_scale_data[1] always... - (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) + (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch']) + else: + # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') + # Except we do not have glyph_scale_data[1] always... + (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) else: (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch']) @@ -1299,16 +1367,24 @@ class font_patcher: x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2) elif sym_attr['align'] == 'r': # Right align - x_align_distance += self.font_dim['width'] - sym_dim['width'] - if not self.args.single and '2' in sym_attr['stretch']: - x_align_distance += self.font_dim['width'] + x_align_distance += self.font_dim['width'] * self.get_target_width(sym_attr['stretch']) - sym_dim['width'] # If symbol glyph is wider than target font cell, just left-align - x_align_distance = max(-sym_dim['xmin'], x_align_distance) + x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance) if overlap: overlap_width = self.font_dim['width'] * overlap if sym_attr['align'] == 'l': x_align_distance -= overlap_width + elif sym_attr['align'] == 'c': + if overlap_width > 0: + x_align_distance -= overlap_width / 2 + elif sym_attr['align'] == 'r': + # Check and correct overlap; it can go wrong if we have a xy-ratio limit + target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(sym_attr['stretch']) + target_xmax += overlap_width + glyph_xmax = sym_dim['xmax'] + x_align_distance + correction = target_xmax - glyph_xmax + x_align_distance += correction align_matrix = psMat.translate(x_align_distance, y_align_distance) self.sourceFont[currentSourceFontGlyph].transform(align_matrix) @@ -1531,7 +1607,7 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None): if glyph is None: # Glyph has been in defining range but is not in the actual font continue - if destGlyph: + if destGlyph and glyph.font != destGlyph.font: glyph.font.selection.select(glyph) glyph.font.copy() destGlyph.font.selection.select(destGlyph) diff --git a/src/glyphs/PowerlineExtraSymbols.otf b/src/glyphs/PowerlineExtraSymbols.otf index 5cf327b9327dfd5ae669ba4d068c77a8958b6c66..ef60f81a10d4ec173030e2a96eb3cacfc48c9c36 100644 GIT binary patch delta 4068 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 delta 3960 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 W_^AN?e}c+ilMA2KTRO(DAO8d77KVNR diff --git a/src/glyphs/extraglyphs.sfd b/src/glyphs/extraglyphs.sfd index 45f63aa61c9947cff1dee3cec0d1355b880b072b..dc5d252d74250961d205a710b815a2945c0db813 100644 GIT binary patch delta 12716 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 delta 733 zcmV<30wVo|cA^=uWdRdLa%mt&Wnv&PFd#8DIx{jlFf_Ax0b&6WGB7qEEipMTATcm9 zHy|-IIkW2mrvd`+1hbR{Yyy8ft8-E33;bq5eq1;V+e!8dK+wu%!ZL)#PvKO=S5sVX zjZ1LMn9!t*!wF8dV^Uw50oiNqGxVIwu)b}7+a%TVkoMUj>tIi-r}ERx%%wF|f-8e* z_JWy*!25#p42o0yZ#E9Rq2WGu@cKF|&!EvjT?1xjtuTB#06BMj3c`Q4<4xfVa>I!< zvI5$R%U%M<_J$?5!fll~5U_pYw#g+*;8Nsdr4aXFmz6MJCxA;%yCqr>flvXD(K0WS ziH47CB^beM5=R`U1TZXgDCiu?&A~a$mlrMzuVQnwe3(tK)bOCz#*4)-#{{iTvrL^0 zKgy&OZq^+6{D+EH3@Lv-p6S~*Jx)taQm_RUyENQL^6kN>QYd&524dUt*Ijp;$)`q{ z7B7?JAJ50}{|FN8344wj^a(W#e&a<`;J{Th6mkT&8WO$yc9*}7jpq@G*sCT<(IHfX zDY+gk))6z^%VlVfg~o$n!mFZ8{ITm?p>`q00F#d)W_^2VrvHCBi9uW1s-7w z1{IJE#&=D){lXZ)KxIm1mEsqj0mSpW+(L~=tv9AG#^+~3LhHiropk=bP9A~2mb$HQ zo^6Y8AbKoJe(t25hTzBSrqHPW`awhkBocLJ@&V-F$LHGG)8APqK6VQ>U}%NJlvrXD zGyQq=8Y2)i!;61X-+!<@E!u8AU3Nv3)@%a=xqoX2Q#;KfvJ!p|^65lZNOzkF(1Bvr z;?}7U?p(%@V8GT$d}t+AY>F-OzFJD9RaoMp-9d>)&`F&W$peg%hf>`u6tO9PD zeRXyMYb_>h4