chore: change batteries

This commit is contained in:
github-actions 2023-01-16 18:45:51 +00:00
parent 2daca7d2e8
commit 2b201ce570
7 changed files with 460 additions and 205 deletions

View file

@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
# Change the script version when you edit this script: # Change the script version when you edit this script:
script_version = "3.2.2" script_version = "3.4.3"
version = "2.3.0-RC" version = "2.3.0-RC"
projectName = "Nerd Fonts" projectName = "Nerd Fonts"
@ -88,8 +88,8 @@ class TableHEADWriter:
checksum = (checksum + extra) & 0xFFFFFFFF checksum = (checksum + extra) & 0xFFFFFFFF
return checksum return checksum
def find_head_table(self, idx): def find_table(self, tablenames, idx):
""" Search all tables for the HEAD table and store its metadata """ """ Search all tables for one of the tables in tablenames and store its metadata """
# Use font with index idx if this is a font collection file # Use font with index idx if this is a font collection file
self.f.seek(0, 0) self.f.seek(0, 0)
tag = self.f.read(4) tag = self.f.read(4)
@ -117,10 +117,18 @@ class TableHEADWriter:
self.tab_check = self.getlong() self.tab_check = self.getlong()
self.tab_offset = self.getlong() self.tab_offset = self.getlong()
self.tab_length = self.getlong() self.tab_length = self.getlong()
if tab_name == b'head': if tab_name in tablenames:
return return True
return False
def find_head_table(self, idx):
""" Search all tables for the HEAD table and store its metadata """
# Use font with index idx if this is a font collection file
found = self.find_table([ b'head' ], idx)
if not found:
raise Exception('No HEAD table found in font idx {}'.format(idx)) raise Exception('No HEAD table found in font idx {}'.format(idx))
def goto(self, where): def goto(self, where):
""" Go to a named location in the file or to the specified index """ """ Go to a named location in the file or to the specified index """
if type(where) is str: if type(where) is str:
@ -237,6 +245,7 @@ class font_patcher:
self.sourceFont = None # class 'fontforge.font' self.sourceFont = None # class 'fontforge.font'
self.patch_set = None # class 'list' self.patch_set = None # class 'list'
self.font_dim = None # class 'dict' self.font_dim = None # class 'dict'
self.font_extrawide = False
self.onlybitmaps = 0 self.onlybitmaps = 0
self.essential = set() self.essential = set()
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
@ -267,6 +276,11 @@ class font_patcher:
panose[3] = 9 # 3 (4th value) = propotion; 9 = monospaced panose[3] = 9 # 3 (4th value) = propotion; 9 = monospaced
self.sourceFont.os2_panose = tuple(panose) self.sourceFont.os2_panose = tuple(panose)
# For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs
if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2:
print("Very wide and short font, disabling 2 cell Powerline glyphs")
self.font_extrawide = True
# Prevent opening and closing the fontforge font. Makes things faster when patching # Prevent opening and closing the fontforge font. Makes things faster when patching
# multiple ranges using the same symbol font. # multiple ranges using the same symbol font.
PreviousSymbolFilename = "" PreviousSymbolFilename = ""
@ -299,7 +313,7 @@ class font_patcher:
SrcStart = patch['SrcStart'] SrcStart = patch['SrcStart']
if not SrcStart: if not SrcStart:
SrcStart = patch['SymStart'] SrcStart = patch['SymStart']
self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes']) self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleRules'], patch['Name'], patch['Attributes'])
if symfont: if symfont:
symfont.close() symfont.close()
@ -315,6 +329,11 @@ class font_patcher:
def generate(self, sourceFonts): def generate(self, sourceFonts):
sourceFont = sourceFonts[0] sourceFont = sourceFonts[0]
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
if int(fontforge.version()) >= 20201107:
gen_flags = (str('opentype'), str('PfEd-comments'), str('no-FFTM-table'))
else:
gen_flags = (str('opentype'), str('PfEd-comments'))
if len(sourceFonts) > 1: if len(sourceFonts) > 1:
layer = None layer = None
# use first non-background layer # use first non-background layer
@ -325,9 +344,8 @@ class font_patcher:
outfile = os.path.normpath(os.path.join( outfile = os.path.normpath(os.path.join(
sanitize_filename(self.args.outputdir, True), sanitize_filename(self.args.outputdir, True),
sanitize_filename(sourceFont.familyname) + ".ttc")) sanitize_filename(sourceFont.familyname) + ".ttc"))
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer)
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=(str('opentype'), str('PfEd-comments')), layer=layer) message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile)
message = "\nGenerated: {} fonts in '{}'".format(len(sourceFonts), outfile)
else: else:
fontname = sourceFont.fullname fontname = sourceFont.fullname
if not fontname: if not fontname:
@ -335,26 +353,30 @@ class font_patcher:
outfile = os.path.normpath(os.path.join( outfile = os.path.normpath(os.path.join(
sanitize_filename(self.args.outputdir, True), sanitize_filename(self.args.outputdir, True),
sanitize_filename(fontname) + self.args.extension)) sanitize_filename(fontname) + self.args.extension))
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
bitmaps = str() bitmaps = str()
if len(self.sourceFont.bitmapSizes): if len(self.sourceFont.bitmapSizes):
if not self.args.quiet:
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
bitmaps = str('otf') # otf/ttf, both is bf_ttf bitmaps = str('otf') # otf/ttf, both is bf_ttf
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=(str('opentype'), str('PfEd-comments'))) sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile) message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
# Adjust flags that can not be changed via fontforge # Adjust flags that can not be changed via fontforge
if re.search('\\.[ot]tf$', self.args.font, re.IGNORECASE) and re.search('\\.[ot]tf$', outfile, re.IGNORECASE):
try: try:
source_font = TableHEADWriter(self.args.font) source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile) dest_font = TableHEADWriter(outfile)
for idx in range(source_font.num_fonts): for idx in range(source_font.num_fonts):
if not self.args.quiet:
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts)) print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
source_font.find_head_table(idx) source_font.find_head_table(idx)
dest_font.find_head_table(idx) dest_font.find_head_table(idx)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
if not self.args.quiet:
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08)) print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int' dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
if source_font.lowppem != dest_font.lowppem: if source_font.lowppem != dest_font.lowppem:
if not self.args.quiet:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem)) print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified: if dest_font.modified:
@ -368,6 +390,8 @@ class font_patcher:
dest_font.close() dest_font.close()
except: except:
pass pass
if self.args.is_variable:
print("Warning: Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
print(message) print(message)
if self.args.postprocess: if self.args.postprocess:
@ -488,13 +512,16 @@ class font_patcher:
subFamily = fallbackStyle subFamily = fallbackStyle
# some fonts have inaccurate 'SubFamily', if it is Regular let us trust the filename more: # some fonts have inaccurate 'SubFamily', if it is Regular let us trust the filename more:
if subFamily == "Regular": if subFamily == "Regular" and len(fallbackStyle):
subFamily = fallbackStyle subFamily = fallbackStyle
# This is meant to cover the case where the SubFamily is "Italic" and the filename is *-BoldItalic. # This is meant to cover the case where the SubFamily is "Italic" and the filename is *-BoldItalic.
if len(subFamily) < len(fallbackStyle): if len(subFamily) < len(fallbackStyle):
subFamily = fallbackStyle subFamily = fallbackStyle
if len(subFamily) == 0:
subFamily = "Regular"
if self.args.windows: if self.args.windows:
maxFamilyLength = 31 maxFamilyLength = 31
maxFontLength = maxFamilyLength - len('-' + subFamily) maxFontLength = maxFamilyLength - len('-' + subFamily)
@ -638,8 +665,6 @@ class font_patcher:
print("Failed to remove subtable:", subtable) print("Failed to remove subtable:", subtable)
elif self.args.removeligatures: elif self.args.removeligatures:
print("Unable to read configfile, unable to remove ligatures") print("Unable to read configfile, unable to remove ligatures")
else:
print("No configfile given, skipping configfile related actions")
def assert_monospace(self): def assert_monospace(self):
@ -660,39 +685,40 @@ class font_patcher:
def setup_patch_set(self): def setup_patch_set(self):
""" Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """
# Supported params: overlap | careful # Supported params: overlap | careful
# Overlap value is used horizontally but vertically limited to 0.01
# Powerline dividers # Powerline dividers
SYM_ATTR_POWERLINE = { SYM_ATTR_POWERLINE = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}},
# Arrow tips # Arrow tips
0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}},
0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.7}},
0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}},
0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.7}},
# Rounded arcs # Rounded arcs
0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01, 'xy-ratio': 0.59}},
0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.5}},
0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01, 'xy-ratio': 0.59}},
0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.5}},
# Bottom Triangles # Bottom Triangles
0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Top Triangles # Top Triangles
0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Flames # Flames
0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Small squares # Small squares
0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}},
@ -703,10 +729,11 @@ class font_patcher:
0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}},
# Waveform # Waveform
0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}}, 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
0xe0ca: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
# Hexagons # Hexagons
0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}},
# Legos # Legos
@ -715,8 +742,8 @@ class font_patcher:
0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
# Top and bottom trapezoid # Top and bottom trapezoid
0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 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}} 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}
} }
SYM_ATTR_DEFAULT = { SYM_ATTR_DEFAULT = {
@ -739,14 +766,54 @@ class font_patcher:
'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 # Most glyphs we want to maximize (individually) during the scale
# that need to be small or stay relative in size to each other. # However, there are some that need to be small or stay relative in
# The following list are those glyphs. A tuple represents a range. # size to each other.
# The glyph-specific behavior can be given as ScaleRules in the patch-set.
#
# ScaleRules can contain two different kind of rules (possibly in parallel):
# - ScaleGlyph:
# Here one specific glyph is used as 'scale blueprint'. Other glyphs are
# scaled by the same factor as this glyph. This is useful if you have one
# 'biggest' glyph and all others should stay relatively in size.
# Shifting in addition to scaling can be selected too (see below).
# - ScaleGroups:
# Here you specify a group of glyphs that should be handled together
# with the same scaling and shifting. The basis for it is a 'combined
# bounding box' of all glyphs in that group. All glyphs are handled as
# if they fill that combined bounding box.
#
# The ScaleGlyph method: You set 'ScaleGlyph' to the unicode of the reference glyph.
# Note that there can be only one per patch-set.
# Additionally you set 'GlyphsToScale' that contains all the glyphs that shall be
# handled (scaled) like the reference glyph.
# It is a List of: ((glyph code) or (tuple of two glyph codes that form a closed range))
# 'GlyphsToScale': [
# 0x0100, 0x0300, 0x0400, # The single glyphs 0x0100, 0x0300, and 0x0400
# (0x0200, 0x0210), # All glyphs 0x0200 to 0x0210 including both 0x0200 and 0x0210
# ]}
# If you want to not only scale but also shift as the refenerce glyph you give the
# data as 'GlyphsToScale+'. Note that only one set is used and the plus version is preferred.
#
# For the ScaleGroup method you define any number groups of glyphs and each group is
# handled separately. The combined bounding box of all glyphs in the group is determined
# and based on that the scale and shift for all the glyphs in the group.
# You define the groups as value of 'ScaleGroups'.
# It is a List of: ((lists of glyph codes) or (ranges of glyph codes))
# 'ScaleGroups': [
# [0x0100, 0x0300, 0x0400], # One group consists of glyphs 0x0100, 0x0300, and 0x0400
# range(0x0200, 0x0210 + 1), # Another group contains glyphs 0x0200 to 0x0210 incl.
#
# Note the subtle differences: tuple vs. range; closed vs open range; etc
# See prepareScaleRules() for some more details.
# 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 DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo
'GlyphsToScale': [ 'GlyphsToScale': [
(0xe6bd, 0xe6c3) # very small things (0xe6bd, 0xe6c3) # very small things
]} ]}
FONTA_SCALE_LIST = {'GlyphsToScale': [ FONTA_SCALE_LIST = {'ScaleGroups': [
[0xf005, 0xf006, 0xf089], # star, star empty, half star [0xf005, 0xf006, 0xf089], # star, star empty, half star
range(0xf026, 0xf028 + 1), # volume off, down, up range(0xf026, 0xf028 + 1), # volume off, down, up
range(0xf02b, 0xf02c + 1), # tag, tags range(0xf02b, 0xf02c + 1), # tag, tags
@ -756,8 +823,10 @@ class font_patcher:
range(0xf060, 0xf063 + 1), # arrows range(0xf060, 0xf063 + 1), # arrows
[0xf053, 0xf054, 0xf077, 0xf078], # chevron all directions [0xf053, 0xf054, 0xf077, 0xf078], # chevron all directions
range(0xf07d, 0xf07e + 1), # resize range(0xf07d, 0xf07e + 1), # resize
[0xf0d7, 0xf0da, 0xf0dc, 0xf0fe], # caret all directions and same looking sort range(0xf0a4, 0xf0a7 + 1), # pointing hands
[0xf0d7, 0xf0d8, 0xf0d9, 0xf0da, 0xf0dc, 0xf0dd, 0xf0de], # caret all directions and same looking sort
range(0xf100, 0xf107 + 1), # angle range(0xf100, 0xf107 + 1), # angle
range(0xf130, 0xf131 + 1), # mic
range(0xf141, 0xf142 + 1), # ellipsis range(0xf141, 0xf142 + 1), # ellipsis
range(0xf153, 0xf15a + 1), # currencies range(0xf153, 0xf15a + 1), # currencies
range(0xf175, 0xf178 + 1), # long arrows range(0xf175, 0xf178 + 1), # long arrows
@ -774,32 +843,44 @@ class font_patcher:
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons 0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons
0xf0ca, # dash 0xf0ca, # dash
]} ]}
WEATH_SCALE_LIST = {'ScaleGroups': [
range(0xf095, 0xf0b0 + 1), # moon phases
range(0xf0b7, 0xf0c3 + 1), # wind strengths
range(0xf053, 0xf055 + 1), # thermometer
[0xf06e, 0xf070 ], # solar eclipse
[0xf042, 0xf045 ], # degree sign
]}
MDI_SCALE_LIST = {'ScaleGlyph': 0xf068d, # 'solid' fills complete design space
'GlyphsToScale+': [
(0xf0000, 0xfffff) # all because they are very well scaled already
]}
# Define the character ranges # Define the character ranges
# Symbol font ranges # Symbol font ranges
self.patch_set = [ self.patch_set = [
{'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5AA, 'SrcStart': 0xE5FA, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5AA, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': 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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'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, '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, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, '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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.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, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, {'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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.material, 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap {'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': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop {'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.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'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.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR} {'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.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
] ]
def setup_line_dimensions(self): def setup_line_dimensions(self):
@ -870,6 +951,7 @@ class font_patcher:
if gap > 0: if gap > 0:
gap_top = int(gap / 2) gap_top = int(gap / 2)
gap_bottom = gap - gap_top gap_bottom = gap - gap_top
print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom))
self.font_dim['ymin'] -= gap_bottom self.font_dim['ymin'] -= gap_bottom
self.font_dim['ymax'] += gap_top self.font_dim['ymax'] += gap_top
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax'] self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
@ -881,35 +963,69 @@ class font_patcher:
# Ignore the y-values, os2_winXXXXX values set above are used for line height # Ignore the y-values, os2_winXXXXX values set above are used for line height
# #
# 0x00-0x17f is the Latin Extended-A range # 0x00-0x17f is the Latin Extended-A range
warned = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target
for glyph in range(0x21, 0x17f): for glyph in range(0x21, 0x17f):
if glyph in range(0x7F, 0xBF): if glyph in range(0x7F, 0xBF) or glyph in [
continue # ignore special characters like '1/4' etc 0x132, 0x134, # IJ, ij (in Overpass Mono)
0x022, 0x027, 0x060, # Single and double quotes in Inconsolata LGC
0x0D0, 0x10F, 0x110, 0x111, 0x127, 0x13E, 0x140, 0x165, # Eth and others with stroke or caron in RobotoMono
]:
continue # ignore special characters like '1/4' etc and some specifics
try: try:
(_, _, xmax, _) = self.sourceFont[glyph].boundingBox() (_, _, xmax, _) = self.sourceFont[glyph].boundingBox()
except TypeError: except TypeError:
continue continue
# print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if self.font_dim['width'] < self.sourceFont[glyph].width: if self.font_dim['width'] < self.sourceFont[glyph].width:
self.font_dim['width'] = self.sourceFont[glyph].width self.font_dim['width'] = self.sourceFont[glyph].width
# print("New MAXWIDTH-A {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax)) if not warned and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
print("Extended glyphs wider than basic glyphs")
warned = True
# print("New MAXWIDTH-A {} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if xmax > self.font_dim['xmax']: if xmax > self.font_dim['xmax']:
self.font_dim['xmax'] = xmax self.font_dim['xmax'] = xmax
# print("New MAXWIDTH-B {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax)) # print("New MAXWIDTH-B {} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
# print("FINAL", self.font_dim)
def get_scale_factor(self, sym_dim): def get_scale_factors(self, sym_dim, stretch):
scale_ratio = 1 """ 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)
# We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit # For monospaced fonts all chars need to be maximum 'one' space wide
scale_ratio_x = self.font_dim['width'] / sym_dim['width'] # other fonts allows double width glyphs for 'pa' or if requested with '2'
scale_ratio_y = self.font_dim['height'] / sym_dim['height'] if self.args.single or (stretch != 'pa' and '2' not in stretch):
if scale_ratio_x > scale_ratio_y: relative_width = 1.0
scale_ratio = scale_ratio_y
else: else:
scale_ratio = scale_ratio_x relative_width = 2.0
return scale_ratio target_width = self.font_dim['width'] * relative_width
scale_ratio_x = target_width / sym_dim['width']
# font_dim['height'] represents total line height, keep our symbols sized based upon font's em
# Use the font_dim['height'] only for explicit 'y' scaling (not 'pa')
target_height = self.font_dim['height']
scale_ratio_y = target_height / sym_dim['height']
if stretch == 'pa':
# 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:
# 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
else:
# Keep the not-stretched direction
if not 'x' in stretch:
scale_ratio_x = 1.0
if not 'y' in stretch:
scale_ratio_y = 1.0
return (scale_ratio_x, scale_ratio_y)
def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes): def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleRules, setName, attributes):
""" Copies symbol glyphs into self.sourceFont """ """ Copies symbol glyphs into self.sourceFont """
progressText = '' progressText = ''
careful = False careful = False
@ -933,19 +1049,23 @@ class font_patcher:
symbolFontSelection = [ x for x in symbolFont.selection.byGlyphs if x.unicode >= 0 ] symbolFontSelection = [ x for x in symbolFont.selection.byGlyphs if x.unicode >= 0 ]
glyphSetLength = len(symbolFontSelection) glyphSetLength = len(symbolFontSelection)
if self.args.quiet is False: if not self.args.quiet:
sys.stdout.write("Adding " + str(max(1, glyphSetLength)) + " Glyphs from " + setName + " Set \n") sys.stdout.write("Adding " + str(max(1, glyphSetLength)) + " Glyphs from " + setName + " Set \n")
currentSourceFontGlyph = -1 # initialize for the exactEncoding case currentSourceFontGlyph = -1 # initialize for the exactEncoding case
width_warning = False
for index, sym_glyph in enumerate(symbolFontSelection): for index, sym_glyph in enumerate(symbolFontSelection):
index = max(1, index) index = max(1, index)
try: sym_attr = attributes.get(sym_glyph.unicode)
sym_attr = attributes[sym_glyph.unicode] if sym_attr is None:
except KeyError:
sym_attr = attributes['default'] sym_attr = attributes['default']
if self.font_extrawide:
# Do not allow 'xy2' scaling
sym_attr['stretch'] = sym_attr['stretch'].replace('2', '')
if exactEncoding: if exactEncoding:
# Use the exact same hex values for the source font as for the symbol font. # 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 # Problem is we do not know the codepoint of the sym_glyph and because it
@ -966,7 +1086,11 @@ class font_patcher:
currentSourceFontGlyph = sourceFontStart + sourceFontCounter currentSourceFontGlyph = sourceFontStart + sourceFontCounter
sourceFontCounter += 1 sourceFontCounter += 1
if self.args.quiet is False: # For debugging process only limited glyphs
# if currentSourceFontGlyph != 0xe7bd:
# continue
if not self.args.quiet:
if self.args.progressbars: if self.args.progressbars:
update_progress(round(float(index + 1) / glyphSetLength, 2)) update_progress(round(float(index + 1) / glyphSetLength, 2))
else: else:
@ -977,7 +1101,7 @@ class font_patcher:
# check if a glyph already exists in this location # check if a glyph already exists in this location
if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential: if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential:
if currentSourceFontGlyph in self.sourceFont: if currentSourceFontGlyph in self.sourceFont:
if self.args.quiet is False: if not self.args.quiet:
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph)) print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph))
# We don't want to touch anything so move to next Glyph # We don't want to touch anything so move to next Glyph
@ -988,6 +1112,9 @@ class font_patcher:
if currentSourceFontGlyph in self.sourceFont: if currentSourceFontGlyph in self.sourceFont:
self.sourceFont[currentSourceFontGlyph].removePosSub("*") 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
# Select and copy symbol from its encoding point # Select and copy symbol from its encoding point
# We need to do this select after the careful check, this way we don't # We need to do this select after the careful check, this way we don't
# reset our selection before starting the next loop # reset our selection before starting the next loop
@ -998,54 +1125,48 @@ class font_patcher:
self.sourceFont.selection.select(currentSourceFontGlyph) self.sourceFont.selection.select(currentSourceFontGlyph)
self.sourceFont.paste() self.sourceFont.paste()
self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname
scale_ratio_x = 1 self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols
scale_ratio_y = 1
# Prepare symbol glyph dimensions # Prepare symbol glyph dimensions
sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph])
if glyph_scale_data is not None:
# Now that we have copy/pasted the glyph, if we are creating a monospace if glyph_scale_data[1] is not None:
# font we need to scale and move the glyphs. It is possible to have sym_dim = glyph_scale_data[1] # Use combined bounding box
# empty glyphs, so we need to skip those. # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa')
if self.args.single and sym_dim['width'] and sym_dim['height']: # Except we do not have glyph_scale_data[1] always...
# If we want to preserve that aspect ratio of the glyphs we need to (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0])
# find the largest possible scaling factor that will allow the glyph
# to fit in both the x and y directions
if sym_attr['stretch'] == 'pa':
scale_ratio_x = False
if scaleGlyph:
# We want to preserve the relative size of each glyph in a glyph group
scale_ratio_x = self.get_glyph_scale(sym_glyph.unicode, scaleGlyph, symbolFont)
if scale_ratio_x is False:
# In the remaining cases, each glyph is sized independently to each other
scale_ratio_x = self.get_scale_factor(sym_dim)
scale_ratio_y = scale_ratio_x
else: else:
if 'x' in sym_attr['stretch']: (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch'])
# Stretch the glyph horizontally to fit the entire available width
scale_ratio_x = self.font_dim['width'] / sym_dim['width']
# end if single width
# non-monospace (double width glyphs)
# elif sym_dim['width'] and sym_dim['height']:
# any special logic we want to apply for double-width variation
# would go here
if 'y' in sym_attr['stretch']:
# Stretch the glyph vertically to total line height (good for powerline separators)
# Currently stretching vertically for both monospace and double-width
scale_ratio_y = self.font_dim['height'] / sym_dim['height']
overlap = sym_attr['params'].get('overlap') overlap = sym_attr['params'].get('overlap')
if scale_ratio_x != 1 or scale_ratio_y != 1:
if overlap: if overlap:
scale_ratio_x *= 1 + overlap scale_ratio_x *= 1.0 + (self.font_dim['width'] / (sym_dim['width'] * scale_ratio_x)) * overlap
scale_ratio_y *= 1 + overlap y_overlap = min(0.01, overlap) # never aggressive vertical overlap
scale_ratio_y *= 1.0 + (self.font_dim['height'] / (sym_dim['height'] * scale_ratio_y)) * y_overlap
# Size in x to size in y ratio limit (to prevent over-wide glyphs)
xy_ratio_max = sym_attr['params'].get('xy-ratio')
if (xy_ratio_max):
xy_ratio = sym_dim['width'] * scale_ratio_x / (sym_dim['height'] * scale_ratio_y)
if xy_ratio > xy_ratio_max:
scale_ratio_x = scale_ratio_x * xy_ratio_max / xy_ratio
if scale_ratio_x != 1.0 or scale_ratio_y != 1.0:
self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y))
# Use the dimensions from the newly pasted and stretched glyph # We pasted and scaled now we want to align/move
# Use the dimensions from the newly pasted and stretched glyph to avoid any rounding errors
sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph])
# Use combined bounding box?
if glyph_scale_data is not None and glyph_scale_data[1] is not None:
scaleglyph_dim = scale_bounding_box(glyph_scale_data[1], scale_ratio_x, scale_ratio_y)
if scaleglyph_dim['advance'] is None:
# On monospaced symbol collections use their advance with, otherwise align horizontally individually
scaleglyph_dim['xmin'] = sym_dim['xmin']
scaleglyph_dim['xmax'] = sym_dim['xmax']
scaleglyph_dim['width'] = sym_dim['width']
sym_dim = scaleglyph_dim
y_align_distance = 0 y_align_distance = 0
if sym_attr['valign'] == 'c': if sym_attr['valign'] == 'c':
# Center the symbol vertically by matching the center of the line height and center of symbol # Center the symbol vertically by matching the center of the line height and center of symbol
@ -1055,7 +1176,11 @@ class font_patcher:
# Handle glyph l/r/c alignment # Handle glyph l/r/c alignment
x_align_distance = 0 x_align_distance = 0
if sym_attr['align']: if self.args.nonmono and sym_dim['advance'] is None:
# Remove left side bearing
# (i.e. do not remove left side bearing when combined BB is in use)
x_align_distance = -self.sourceFont[currentSourceFontGlyph].left_side_bearing
elif sym_attr['align']:
# First find the baseline x-alignment (left alignment amount) # First find the baseline x-alignment (left alignment amount)
x_align_distance = self.font_dim['xmin'] - sym_dim['xmin'] x_align_distance = self.font_dim['xmin'] - sym_dim['xmin']
if sym_attr['align'] == 'c': if sym_attr['align'] == 'c':
@ -1064,12 +1189,15 @@ class font_patcher:
elif sym_attr['align'] == 'r': elif sym_attr['align'] == 'r':
# Right align # Right align
x_align_distance += self.font_dim['width'] - sym_dim['width'] 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']
if overlap: if overlap:
overlap_width = self.font_dim['width'] * overlap overlap_width = self.font_dim['width'] * overlap
if sym_attr['align'] == 'l': if sym_attr['align'] == 'l':
x_align_distance -= overlap_width x_align_distance -= overlap_width
if sym_attr['align'] == 'r': if sym_attr['align'] == 'r' and not self.args.nonmono:
# Nonmono is 'left aligned' per definition, translation does not help here
x_align_distance += overlap_width x_align_distance += overlap_width
align_matrix = psMat.translate(x_align_distance, y_align_distance) align_matrix = psMat.translate(x_align_distance, y_align_distance)
@ -1085,11 +1213,24 @@ class font_patcher:
# same width for all character glyphs. This needs to be done for all glyphs, # 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. # even the ones that are empty and didn't go through the scaling operations.
# It should come after setting the glyph bearings # It should come after setting the glyph bearings
if not self.args.nonmono:
self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph]) self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph])
else:
# Re-remove negative bearings for target font with variable advance width # Target font with variable advance width get the icons with their native widths
if self.args.nonmono: # and keeping possible (right and/or negative) bearings in effect
self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) if sym_dim['advance'] is not None:
# 'Width' from monospaced scale group
width = sym_dim['advance']
else:
width = sym_dim['width']
# If we have overlap we need to subtract that to keep/get negative bearings
if overlap and (sym_attr['align'] == 'l' or sym_attr['align'] == 'r'):
width -= overlap_width
# Fontforge handles the width change like this:
# - Keep existing left_side_bearing
# - Set width
# - Calculate and set new right_side_bearing
self.sourceFont[currentSourceFontGlyph].width = int(width)
# Check if the inserted glyph is scaled correctly for monospace # Check if the inserted glyph is scaled correctly for monospace
if self.args.single: if self.args.single:
@ -1100,7 +1241,7 @@ class font_patcher:
# end for # end for
if self.args.quiet is False or self.args.progressbars: if not self.args.quiet or self.args.progressbars:
sys.stdout.write("\n") sys.stdout.write("\n")
@ -1149,53 +1290,82 @@ class font_patcher:
except: except:
pass pass
def prepareScaleGlyph(self, scaleGlyph, symbolFont, destGlyph): def prepareScaleRules(self, scaleRules, symbolFont, destGlyph):
""" Prepare raw ScaleGlyph data for use """ """ Prepare raw ScaleRules data for use """
# The GlyphData is a dict with these (possible) entries: # The scaleRules is/will be a dict with these (possible) entries:
# 'GlyphsToScale': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled # 'ScaleGroups': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled
# 'scales': List of associated scale factors, one for each entry in 'GlyphsToScale' (generated by this function) # 'scales': List of associated scale factors, one for each entry in 'ScaleGroups' (generated by this function)
# 'bbdims': List of associated sym_dim dicts, one for each entry in 'ScaleGroups' (generated by this function)
# Each dim_dict describes the combined bounding box of all glyphs in one ScaleGroups group
# Example: # Example:
# { 'GlyphsToScale': [ range(1, 3), [ 7, 10 ], ], # { 'ScaleGroups': [ range(1, 3), [ 7, 10 ], ],
# 'scales': [ 1.23, 1.33, ] } # 'scales': [ 1.23, 1.33, ],
# 'bbdims': [ dim_dict1, dim_dict2, ] }
# #
# Each item in 'GlyphsToScale' (a range or an explicit list) forms a group of glyphs that shall be # Each item in 'ScaleGroups' (a range or an explicit list) forms a group of glyphs that shall be
# as rescaled all with the same and maximum possible (for the included glyphs) factor. # as rescaled all with the same and maximum possible (for the included glyphs) 'pa' factor.
# If the 'bbdims' is present they all shall be shifted in the same way.
# #
# Previously this structure has been used: # Previously this structure has been used:
# 'ScaleGlyph' Lead glyph, which scaling factor is taken # 'ScaleGlyph' Lead glyph, which scaling factor is taken
# 'GlyphsToScale': List of (glyph code) or (list of two glyph codes that form a closed range)) that shall be scaled # 'GlyphsToScale': List of ((glyph code) or (tuple of two glyph codes that form a closed range)) that shall be scaled
# Note that this allows only one group for the whle symbol font, and that the scaling factor is defined by # Note that this allows only one group for the whle symbol font, and that the scaling factor is defined by
# a specific character, which needs to be manually selected (on each symbol font update). # a specific character, which needs to be manually selected (on each symbol font update).
# Previous entries are automatically rewritten to the new style. # Previous entries are automatically rewritten to the new style.
if 'scales' in scaleGlyph: #
# Note that scaleRules is overwritten with the added data.
if 'scales' in scaleRules:
# Already prepared... must not happen, ignore call # Already prepared... must not happen, ignore call
return return
if 'ScaleGlyph' in scaleGlyph:
# old method. Rewrite to new.
flat_list = []
for i in scaleGlyph['GlyphsToScale']:
if isinstance(i, tuple):
flat_list += list(range(i[0], i[1] + 1))
else:
flat_list.append(i)
scaleGlyph['GlyphsToScale'] = [ flat_list ]
sym_dim = get_glyph_dimensions(symbolFont[scaleGlyph['ScaleGlyph']])
scaleGlyph['scales'] = [ self.get_scale_factor(sym_dim) ]
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 ], destGlyph)
scaleGlyph['scales'].append(self.get_scale_factor(sym_dim))
def get_glyph_scale(self, unicode_value, scaleGlyph, symbolFont): scaleRules['scales'] = []
""" Determines whether or not to use scaled glyphs for glyphs in passed glyph_list """ scaleRules['bbdims'] = []
# Potentially destorys the contents of self.sourceFont[unicode_value] if 'ScaleGroups' not in scaleRules:
if not 'scales' in scaleGlyph: scaleRules['ScaleGroups'] = []
self.prepareScaleGlyph(scaleGlyph, symbolFont, self.sourceFont[unicode_value]) for group in scaleRules['ScaleGroups']:
for glyph_list, scale in zip(scaleGlyph['GlyphsToScale'], scaleGlyph['scales']): sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ], destGlyph)
if unicode_value in glyph_list: scale = self.get_scale_factors(sym_dim, 'pa')[0]
return scale scaleRules['scales'].append(scale)
return False scaleRules['bbdims'].append(sym_dim)
if 'ScaleGlyph' in scaleRules:
# Rewrite to equivalent ScaleGroup
group_list = []
if 'GlyphsToScale+' in scaleRules:
key = 'GlyphsToScale+'
plus = True
else:
key = 'GlyphsToScale'
plus = False
for i in scaleRules[key]:
if isinstance(i, tuple):
group_list.append(range(i[0], i[1] + 1))
else:
group_list.append(i)
sym_dim = get_glyph_dimensions(symbolFont[scaleRules['ScaleGlyph']])
scale = self.get_scale_factors(sym_dim, 'pa')[0]
scaleRules['ScaleGroups'].append(group_list)
scaleRules['scales'].append(scale)
if plus:
scaleRules['bbdims'].append(sym_dim)
else:
scaleRules['bbdims'].append(None) # The 'old' style keeps just the scale, not the positioning
def get_glyph_scale(self, symbol_unicode, scaleRules, symbolFont, dest_unicode):
""" Determines whether or not to use scaled glyphs for glyph in passed symbol_unicode """
# Potentially destorys the contents of self.sourceFont[dest_unicode]
if not 'scales' in scaleRules:
if not dest_unicode in self.sourceFont:
self.sourceFont.createChar(dest_unicode)
self.prepareScaleRules(scaleRules, symbolFont, self.sourceFont[dest_unicode])
for glyph_list, scale, box in zip(scaleRules['ScaleGroups'], scaleRules['scales'], scaleRules['bbdims']):
for e in glyph_list:
if isinstance(e, range):
if symbol_unicode in e:
return (scale, box)
elif symbol_unicode == e:
return (scale, box)
return None
def replace_font_name(font_name, replacement_dict): def replace_font_name(font_name, replacement_dict):
@ -1232,7 +1402,7 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None):
# If destGlyph is given the glyph(s) are first copied over into that # If destGlyph is given the glyph(s) are first copied over into that
# glyph and measured in that font (to avoid rounding errors) # glyph and measured in that font (to avoid rounding errors)
# Leaves the destGlyph in unknown state! # Leaves the destGlyph in unknown state!
bbox = [ None, None, None, None ] bbox = [ None, None, None, None, None ]
for glyph in glyphs: for glyph in glyphs:
if glyph is None: if glyph is None:
# Glyph has been in defining range but is not in the actual font # Glyph has been in defining range but is not in the actual font
@ -1244,23 +1414,52 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None):
destGlyph.font.paste() destGlyph.font.paste()
glyph = destGlyph glyph = destGlyph
gbb = glyph.boundingBox() gbb = glyph.boundingBox()
gadvance = glyph.width
if len(glyphs) > 1 and gbb[0] == gbb[2] and gbb[1] == gbb[3]:
# Ignore empty glyphs if we examine more than one glyph
continue
bbox[0] = gbb[0] if bbox[0] is None or bbox[0] > gbb[0] else bbox[0] 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] bbox[1] = gbb[1] if bbox[1] is None or bbox[1] > gbb[1] else bbox[1]
bbox[2] = gbb[2] if bbox[2] is None or bbox[2] < gbb[2] else bbox[2] bbox[2] = gbb[2] if bbox[2] is None or bbox[2] < gbb[2] else bbox[2]
bbox[3] = gbb[3] if bbox[3] is None or bbox[3] < gbb[3] else bbox[3] bbox[3] = gbb[3] if bbox[3] is None or bbox[3] < gbb[3] else bbox[3]
if not bbox[4]:
bbox[4] = -gadvance # Negative for one/first glyph
else:
if abs(bbox[4]) != gadvance:
bbox[4] = -1 # Marker for not-monospaced
else:
bbox[4] = gadvance # Positive for 2 or more glyphs
if bbox[4] and bbox[4] < 0:
# Not monospaced when only one glyph is used or multiple glyphs with different advance widths
bbox[4] = None
return { return {
'xmin' : bbox[0], 'xmin' : bbox[0],
'ymin' : bbox[1], 'ymin' : bbox[1],
'xmax' : bbox[2], 'xmax' : bbox[2],
'ymax' : bbox[3], 'ymax' : bbox[3],
'width' : bbox[2] + (-bbox[0]), 'width' : bbox[2] + (-bbox[0]),
'height': bbox[3] + (-bbox[1]), 'height' : bbox[3] + (-bbox[1]),
'advance': bbox[4], # advance width if monospaced
} }
def get_glyph_dimensions(glyph): def get_glyph_dimensions(glyph):
""" Returns dict of the dimesions of the glyph passed to it. """ """ Returns dict of the dimesions of the glyph passed to it. """
return get_multiglyph_boundingBox([ glyph ]) return get_multiglyph_boundingBox([ glyph ])
def scale_bounding_box(bbox, scale_x, scale_y):
""" Return a scaled version of a glyph dimensions dict """
# Simulate scaling on combined bounding box, round values for better simulation
new_dim = {
'xmin' : int(bbox['xmin'] * scale_x),
'ymin' : int(bbox['ymin'] * scale_y),
'xmax' : int(bbox['xmax'] * scale_x),
'ymax' : int(bbox['ymax'] * scale_y),
'advance': int(bbox['advance'] * scale_x) if bbox['advance'] is not None else None,
}
new_dim['width'] = new_dim['xmax'] + (-new_dim['xmin'])
new_dim['height'] = new_dim['ymax'] + (-new_dim['ymin'])
return new_dim
def update_progress(progress): def update_progress(progress):
""" Updates progress bar length. """ Updates progress bar length.
@ -1377,7 +1576,7 @@ def setup_arguments():
for alias in sym_font_arg_aliases: for alias in sym_font_arg_aliases:
if alias in sys.argv: if alias in sys.argv:
found = True found = True
if found is not True: if not found:
font_complete = False font_complete = False
args.complete = font_complete args.complete = font_complete
@ -1385,7 +1584,7 @@ def setup_arguments():
args.windows = False args.windows = False
if args.nonmono and args.single: if args.nonmono and args.single:
print("Warniung: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.") print("Warning: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
args.nonmono = False args.nonmono = False
make_sure_path_exists(args.outputdir) make_sure_path_exists(args.outputdir)
@ -1394,6 +1593,18 @@ def setup_arguments():
if not os.access(args.font, os.R_OK): if not os.access(args.font, os.R_OK):
sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font)) sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font))
is_ttc = len(fontforge.fontsInFile(args.font)) > 1 is_ttc = len(fontforge.fontsInFile(args.font)) > 1
try:
source_font_test = TableHEADWriter(args.font)
args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0)
if args.is_variable:
print(" Warning: Source font is a variable open type font (VF), opening might fail...")
except:
args.is_variable = False
finally:
try:
source_font_test.close()
except:
pass
if args.extension == "": if args.extension == "":
args.extension = os.path.splitext(args.font)[1] args.extension = os.path.splitext(args.font)[1]
@ -1418,6 +1629,7 @@ def main():
sourceFonts = [] sourceFonts = []
all_fonts = fontforge.fontsInFile(args.font) all_fonts = fontforge.fontsInFile(args.font)
for i, subfont in enumerate(all_fonts): for i, subfont in enumerate(all_fonts):
if len(all_fonts) > 1:
print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts))) print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts)))
try: try:
sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",)) sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",))
@ -1427,7 +1639,7 @@ def main():
patcher.patch(sourceFonts[-1]) patcher.patch(sourceFonts[-1])
print("\nDone with Patch Sets, generating font...\n") print("Done with Patch Sets, generating font...")
for f in sourceFonts: for f in sourceFonts:
patcher.setup_font_names(f) patcher.setup_font_names(f)
patcher.generate(sourceFonts) patcher.generate(sourceFonts)

Binary file not shown.

View file

@ -0,0 +1,20 @@
Pictogrammers Free License
--------------------------
This icon collection is released as free, open source, and GPL friendly by
the [Pictogrammers](http://pictogrammers.com/) icon group. You may use it
for commercial projects, open source projects, or anything really.
# Icons: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
Some of the icons are redistributed under the Apache 2.0 license. All other
icons are either redistributed under their respective licenses or are
distributed under the Apache 2.0 license.
# Fonts: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
All web and desktop fonts are distributed under the Apache 2.0 license. Web
and desktop fonts contain some icons that are redistributed under the Apache
2.0 license. All other icons are either redistributed under their respective
licenses or are distributed under the Apache 2.0 license.
# Code: MIT (https://opensource.org/licenses/MIT)
The MIT license applies to all non-font and non-icon files.

View file

@ -0,0 +1,23 @@
## Contents
This folder contains the source for the (current) Material Design Icons.
Source is https://github.com/Templarian/MaterialDesign-Font
Last fetch date is Oct 6, 2022.
After fetching a new file one needs to correct our cheat-sheet by updating `bin/scripts/lib/i_md.sh`.
Use the tool:
```
cd bin/scripts
mv lib/i_md.sh lib/i_md.sh_
python3 generate-glyph-info-from-set.py --start f0001 -end f1af0 -font ../../src/glyphs/materialdesign/MaterialDesignIconsDesktop.ttf -offset 0 -prefix md > lib/i_md.sh
```
Open old and new definitions shell script and copy the header from the old file to the autogenerated file. Adapt the values in the new file's header. Remove the last line in the new file (it contains the number of glyphs that is needed for the updated header). Yes, that is some manual labor.
## Source bugs fixed
Glyph 0xF1522 is broken in the original font. We fixed that one glyph manually.
See https://github.com/Templarian/MaterialDesign-Font/issues/9

Binary file not shown.