Compare commits

...

3 commits

Author SHA1 Message Date
github-actions
2b201ce570 chore: change batteries 2023-01-16 18:45:51 +00:00
2daca7d2e8 docs: remove note about batteries thanks to CI 2023-01-16 12:45:11 -06:00
d0adac8824 ci: add change batteries cron job 2023-01-16 12:44:35 -06:00
10 changed files with 495 additions and 210 deletions

34
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: "Update Nerd Fonts"
on:
workflow_dispatch:
schedule:
# 1st and 15th at 12:00 AM
- cron: "0 0 1,15 * *"
permissions:
contents: write
jobs:
change-batteries:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Git Bot
run: |
git --version
git config user.name github-actions
git config user.email github-actions@github.com
- name: Check Nerd-Fonts Source
run: make update-src
- name: Commit Updates
run: |
if [[ -n "$(git status --porcelain)" ]]; then
git add -A
git commit -m "chore: change batteries"
git push
fi

View file

@ -76,10 +76,6 @@ make update-fonts
You can verify the fonts have been added with `make check`.
## Changing the Batteries
If I haven't committed to this repo in a while it's likely a good idea to run `make update-src` to update the fonts, icons and patcher script from nerd fonts.
## Contributing
Before making changes to to any of the scripts in `bin` you should first install `pre-commit`.

View file

@ -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.2.2"
script_version = "3.4.3"
version = "2.3.0-RC"
projectName = "Nerd Fonts"
@ -88,8 +88,8 @@ class TableHEADWriter:
checksum = (checksum + extra) & 0xFFFFFFFF
return checksum
def find_head_table(self, idx):
""" Search all tables for the HEAD table and store its metadata """
def find_table(self, tablenames, idx):
""" 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
self.f.seek(0, 0)
tag = self.f.read(4)
@ -117,9 +117,17 @@ class TableHEADWriter:
self.tab_check = self.getlong()
self.tab_offset = self.getlong()
self.tab_length = self.getlong()
if tab_name == b'head':
return
raise Exception('No HEAD table found in font idx {}'.format(idx))
if tab_name in tablenames:
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))
def goto(self, where):
""" Go to a named location in the file or to the specified index """
@ -237,6 +245,7 @@ class font_patcher:
self.sourceFont = None # class 'fontforge.font'
self.patch_set = None # class 'list'
self.font_dim = None # class 'dict'
self.font_extrawide = False
self.onlybitmaps = 0
self.essential = set()
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
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
# multiple ranges using the same symbol font.
PreviousSymbolFilename = ""
@ -299,7 +313,7 @@ class font_patcher:
SrcStart = patch['SrcStart']
if not SrcStart:
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:
symfont.close()
@ -315,6 +329,11 @@ class font_patcher:
def generate(self, sourceFonts):
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:
layer = None
# use first non-background layer
@ -325,9 +344,8 @@ class font_patcher:
outfile = os.path.normpath(os.path.join(
sanitize_filename(self.args.outputdir, True),
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=(str('opentype'), str('PfEd-comments')), layer=layer)
message = "\nGenerated: {} fonts in '{}'".format(len(sourceFonts), outfile)
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer)
message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile)
else:
fontname = sourceFont.fullname
if not fontname:
@ -335,39 +353,45 @@ class font_patcher:
outfile = os.path.normpath(os.path.join(
sanitize_filename(self.args.outputdir, True),
sanitize_filename(fontname) + self.args.extension))
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
bitmaps = str()
if len(self.sourceFont.bitmapSizes):
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
if not self.args.quiet:
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
bitmaps = str('otf') # otf/ttf, both is bf_ttf
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=(str('opentype'), str('PfEd-comments')))
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile)
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
# Adjust flags that can not be changed via fontforge
try:
source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile)
for idx in range(source_font.num_fonts):
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
source_font.find_head_table(idx)
dest_font.find_head_table(idx)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
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'
if source_font.lowppem != dest_font.lowppem:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified:
dest_font.reset_table_checksum()
dest_font.reset_full_checksum()
except Exception as error:
print("Can not handle font flags ({})".format(repr(error)))
finally:
if re.search('\\.[ot]tf$', self.args.font, re.IGNORECASE) and re.search('\\.[ot]tf$', outfile, re.IGNORECASE):
try:
source_font.close()
dest_font.close()
except:
pass
source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile)
for idx in range(source_font.num_fonts):
if not self.args.quiet:
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
source_font.find_head_table(idx)
dest_font.find_head_table(idx)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
if not self.args.quiet:
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
if source_font.lowppem != dest_font.lowppem:
if not self.args.quiet:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified:
dest_font.reset_table_checksum()
dest_font.reset_full_checksum()
except Exception as error:
print("Can not handle font flags ({})".format(repr(error)))
finally:
try:
source_font.close()
dest_font.close()
except:
pass
if self.args.is_variable:
print("Warning: Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
print(message)
if self.args.postprocess:
@ -488,13 +512,16 @@ class font_patcher:
subFamily = fallbackStyle
# 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
# This is meant to cover the case where the SubFamily is "Italic" and the filename is *-BoldItalic.
if len(subFamily) < len(fallbackStyle):
subFamily = fallbackStyle
if len(subFamily) == 0:
subFamily = "Regular"
if self.args.windows:
maxFamilyLength = 31
maxFontLength = maxFamilyLength - len('-' + subFamily)
@ -638,8 +665,6 @@ class font_patcher:
print("Failed to remove subtable:", subtable)
elif self.args.removeligatures:
print("Unable to read configfile, unable to remove ligatures")
else:
print("No configfile given, skipping configfile related actions")
def assert_monospace(self):
@ -660,39 +685,40 @@ 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
# Overlap value is used horizontally but vertically limited to 0.01
# Powerline dividers
SYM_ATTR_POWERLINE = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}},
# Arrow tips
0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0b3: {'align': 'r', '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': {'xy-ratio': 0.7}},
0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}},
0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.7}},
# Rounded arcs
0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0b7: {'align': 'r', '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': {'xy-ratio': 0.5}},
0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01, 'xy-ratio': 0.59}},
0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.5}},
# Bottom Triangles
0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bb: {'align': 'r', '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': 'xy2', 'params': {}},
0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Top Triangles
0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bf: {'align': 'r', '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': 'xy2', 'params': {}},
0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}},
0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Flames
0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.01}},
0xe0c3: {'align': 'r', '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': 'xy2', 'params': {}},
0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Small squares
0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}},
@ -703,10 +729,11 @@ class font_patcher:
0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}},
# 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
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': {}},
# Legos
@ -715,8 +742,8 @@ class font_patcher:
0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
# Top and bottom trapezoid
0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0d4: {'align': 'r', '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, 'xy-ratio': 0.7}}
}
SYM_ATTR_DEFAULT = {
@ -739,14 +766,54 @@ class font_patcher:
'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}}
}
# Most glyphs we want to maximize during the scale. However, there are some
# that need to be small or stay relative in size to each other.
# The following list are those glyphs. A tuple represents a range.
# Most glyphs we want to maximize (individually) during the scale
# However, there are some that need to be small or stay relative in
# 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
'GlyphsToScale': [
(0xe6bd, 0xe6c3) # very small things
]}
FONTA_SCALE_LIST = {'GlyphsToScale': [
FONTA_SCALE_LIST = {'ScaleGroups': [
[0xf005, 0xf006, 0xf089], # star, star empty, half star
range(0xf026, 0xf028 + 1), # volume off, down, up
range(0xf02b, 0xf02c + 1), # tag, tags
@ -756,8 +823,10 @@ class font_patcher:
range(0xf060, 0xf063 + 1), # arrows
[0xf053, 0xf054, 0xf077, 0xf078], # chevron all directions
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(0xf130, 0xf131 + 1), # mic
range(0xf141, 0xf142 + 1), # ellipsis
range(0xf153, 0xf15a + 1), # currencies
range(0xf175, 0xf178 + 1), # long arrows
@ -774,32 +843,44 @@ class font_patcher:
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons
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
# Symbol font ranges
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': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, '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, '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, '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, '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, '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, '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, 'ScaleGlyph': 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.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.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.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': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': 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, '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, '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, 'ScaleGlyph': None, '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.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': 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': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleGlyph': 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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR}
{'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, '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},
{'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, '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.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
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
{'Enabled': self.args.material, 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop
{'Enabled': self.args.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):
@ -870,6 +951,7 @@ class font_patcher:
if gap > 0:
gap_top = int(gap / 2)
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['ymax'] += gap_top
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
#
# 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):
if glyph in range(0x7F, 0xBF):
continue # ignore special characters like '1/4' etc
if glyph in range(0x7F, 0xBF) or glyph in [
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:
(_, _, xmax, _) = self.sourceFont[glyph].boundingBox()
except TypeError:
continue
# 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
# 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']:
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):
scale_ratio = 1
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)
# 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']
scale_ratio_y = self.font_dim['height'] / sym_dim['height']
if scale_ratio_x > scale_ratio_y:
scale_ratio = scale_ratio_y
# 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 (stretch != 'pa' and '2' not in stretch):
relative_width = 1.0
else:
scale_ratio = scale_ratio_x
return scale_ratio
relative_width = 2.0
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 """
progressText = ''
careful = False
@ -933,19 +1049,23 @@ class font_patcher:
symbolFontSelection = [ x for x in symbolFont.selection.byGlyphs if x.unicode >= 0 ]
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")
currentSourceFontGlyph = -1 # initialize for the exactEncoding case
width_warning = False
for index, sym_glyph in enumerate(symbolFontSelection):
index = max(1, index)
try:
sym_attr = attributes[sym_glyph.unicode]
except KeyError:
sym_attr = attributes.get(sym_glyph.unicode)
if sym_attr is None:
sym_attr = attributes['default']
if self.font_extrawide:
# Do not allow 'xy2' scaling
sym_attr['stretch'] = sym_attr['stretch'].replace('2', '')
if exactEncoding:
# 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
@ -966,7 +1086,11 @@ class font_patcher:
currentSourceFontGlyph = sourceFontStart + sourceFontCounter
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:
update_progress(round(float(index + 1) / glyphSetLength, 2))
else:
@ -977,7 +1101,7 @@ class font_patcher:
# check if a glyph already exists in this location
if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential:
if currentSourceFontGlyph in self.sourceFont:
if self.args.quiet is False:
if not self.args.quiet:
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph))
# We don't want to touch anything so move to next Glyph
@ -988,6 +1112,9 @@ 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
# 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
@ -998,54 +1125,48 @@ class font_patcher:
self.sourceFont.selection.select(currentSourceFontGlyph)
self.sourceFont.paste()
self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname
scale_ratio_x = 1
scale_ratio_y = 1
self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols
# Prepare symbol glyph dimensions
sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph])
# Now that we have copy/pasted the glyph, if we are creating a monospace
# font we need to scale and move the glyphs. It is possible to have
# empty glyphs, so we need to skip those.
if self.args.single and sym_dim['width'] and sym_dim['height']:
# If we want to preserve that aspect ratio of the glyphs we need to
# 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:
if 'x' in 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']
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])
else:
(scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch'])
overlap = sym_attr['params'].get('overlap')
if overlap:
scale_ratio_x *= 1.0 + (self.font_dim['width'] / (sym_dim['width'] * scale_ratio_x)) * 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
if scale_ratio_x != 1 or scale_ratio_y != 1:
if overlap:
scale_ratio_x *= 1 + overlap
scale_ratio_y *= 1 + 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))
# 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])
# 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
if sym_attr['valign'] == 'c':
# 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
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)
x_align_distance = self.font_dim['xmin'] - sym_dim['xmin']
if sym_attr['align'] == 'c':
@ -1064,12 +1189,15 @@ class font_patcher:
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']
if overlap:
overlap_width = self.font_dim['width'] * overlap
if sym_attr['align'] == 'l':
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
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,
# 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])
# Re-remove negative bearings for target font with variable advance width
if self.args.nonmono:
self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph])
if not self.args.nonmono:
self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph])
else:
# Target font with variable advance width get the icons with their native widths
# and keeping possible (right and/or negative) bearings in effect
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
if self.args.single:
@ -1100,7 +1241,7 @@ class font_patcher:
# 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")
@ -1149,53 +1290,82 @@ class font_patcher:
except:
pass
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
# 'scales': List of associated scale factors, one for each entry in 'GlyphsToScale' (generated by this function)
def prepareScaleRules(self, scaleRules, symbolFont, destGlyph):
""" Prepare raw ScaleRules data for use """
# The scaleRules is/will be a dict with these (possible) entries:
# '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 '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:
# { 'GlyphsToScale': [ range(1, 3), [ 7, 10 ], ],
# 'scales': [ 1.23, 1.33, ] }
# { 'ScaleGroups': [ range(1, 3), [ 7, 10 ], ],
# '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
# as rescaled all with the same and maximum possible (for the included glyphs) factor.
# 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) 'pa' factor.
# If the 'bbdims' is present they all shall be shifted in the same way.
#
# Previously this structure has been used:
# '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
# a specific character, which needs to be manually selected (on each symbol font update).
# 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
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):
""" 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.sourceFont[unicode_value])
for glyph_list, scale in zip(scaleGlyph['GlyphsToScale'], scaleGlyph['scales']):
if unicode_value in glyph_list:
return scale
return False
scaleRules['scales'] = []
scaleRules['bbdims'] = []
if 'ScaleGroups' not in scaleRules:
scaleRules['ScaleGroups'] = []
for group in scaleRules['ScaleGroups']:
sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ], destGlyph)
scale = self.get_scale_factors(sym_dim, 'pa')[0]
scaleRules['scales'].append(scale)
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):
@ -1232,7 +1402,7 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None):
# 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 ]
bbox = [ None, 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
@ -1244,23 +1414,52 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None):
destGlyph.font.paste()
glyph = destGlyph
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[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[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 {
'xmin' : bbox[0],
'ymin' : bbox[1],
'xmax' : bbox[2],
'ymax' : bbox[3],
'width' : bbox[2] + (-bbox[0]),
'height': bbox[3] + (-bbox[1]),
'xmin' : bbox[0],
'ymin' : bbox[1],
'xmax' : bbox[2],
'ymax' : bbox[3],
'width' : bbox[2] + (-bbox[0]),
'height' : bbox[3] + (-bbox[1]),
'advance': bbox[4], # advance width if monospaced
}
def get_glyph_dimensions(glyph):
""" Returns dict of the dimesions of the glyph passed to it. """
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):
""" Updates progress bar length.
@ -1377,7 +1576,7 @@ def setup_arguments():
for alias in sym_font_arg_aliases:
if alias in sys.argv:
found = True
if found is not True:
if not found:
font_complete = False
args.complete = font_complete
@ -1385,7 +1584,7 @@ def setup_arguments():
args.windows = False
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
make_sure_path_exists(args.outputdir)
@ -1394,6 +1593,18 @@ def setup_arguments():
if not os.access(args.font, os.R_OK):
sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font))
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 == "":
args.extension = os.path.splitext(args.font)[1]
@ -1418,7 +1629,8 @@ def main():
sourceFonts = []
all_fonts = fontforge.fontsInFile(args.font)
for i, subfont in enumerate(all_fonts):
print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts)))
if len(all_fonts) > 1:
print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts)))
try:
sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",))
except Exception:
@ -1427,7 +1639,7 @@ def main():
patcher.patch(sourceFonts[-1])
print("\nDone with Patch Sets, generating font...\n")
print("Done with Patch Sets, generating font...")
for f in sourceFonts:
patcher.setup_font_names(f)
patcher.generate(sourceFonts)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
REPO_URL=git@github.com:ryanoasis/nerd-fonts.git
REPO_URL='https://github.com/ryanoasis/nerd-fonts.git'
rm -rf nerd-fonts

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.