chore: change batteries

This commit is contained in:
github-actions 2023-05-15 00:29:20 +00:00
parent 2d153f57c0
commit a4d4640f01
14 changed files with 431 additions and 560 deletions

View file

@ -4,32 +4,20 @@
import re import re
from FontnameTools import FontnameTools from FontnameTools import FontnameTools
class FontnameParser: class FontnameParser:
"""Parse a font name and generate all kinds of names""" """Parse a font name and generate all kinds of names"""
def __init__(self, filename, logger): def __init__(self, filename, logger):
"""Parse a font filename and store the results""" """Parse a font filename and store the results"""
self.parse_ok = False self.parse_ok = False
self.use_short_families = ( self.use_short_families = (False, False, False) # ( camelcase name, short styles, aggressive )
False, self.keep_regular_in_family = None # None = auto, True, False
False,
False,
) # ( camelcase name, short styles, aggressive )
self.keep_regular_in_family = None # None = auto, True, False
self.suppress_preferred_if_identical = True self.suppress_preferred_if_identical = True
self.family_suff = "" self.family_suff = ''
self.ps_fontname_suff = "" self.ps_fontname_suff = ''
self.short_family_suff = "" self.short_family_suff = ''
self.name_subst = [] self.name_subst = []
[ [ self.parse_ok, self._basename, self.weight_token, self.style_token, self.other_token, self._rest ] = FontnameTools.parse_font_name(filename)
self.parse_ok,
self._basename,
self.weight_token,
self.style_token,
self.other_token,
self._rest,
] = FontnameTools.parse_font_name(filename)
self.basename = self._basename self.basename = self._basename
self.rest = self._rest self.rest = self._rest
self.add_name_substitution_table(FontnameTools.SIL_TABLE) self.add_name_substitution_table(FontnameTools.SIL_TABLE)
@ -38,25 +26,21 @@ class FontnameParser:
def _make_ps_name(self, n, is_family): def _make_ps_name(self, n, is_family):
"""Helper to limit font name length in PS names""" """Helper to limit font name length in PS names"""
fam = "family " if is_family else "" fam = 'family ' if is_family else ''
limit = 31 if is_family else 63 limit = 31 if is_family else 63
if len(n) <= limit: if len(n) <= limit:
return n return n
r = re.search("(.*)(-.*)", n) r = re.search('(.*)(-.*)', n)
if not r: if not r:
new_n = n[:limit] new_n = n[:limit]
else: else:
q = limit - len(r.groups()[1]) q = limit - len(r.groups()[1])
if q < 1: if q < 1:
q = 1 q = 1
self.logger.error( self.logger.error('====-< Shortening too long PS {}name: Garbage warning'. format(fam))
"====-< Shortening too long PS {}name: Garbage warning".format(fam)
)
new_n = r.groups()[0][:q] + r.groups()[1] new_n = r.groups()[0][:q] + r.groups()[1]
if new_n != n: if new_n != n:
self.logger.error( self.logger.error('====-< Shortening too long PS {}name: {} -> {}'.format(fam, n, new_n))
"====-< Shortening too long PS {}name: {} -> {}".format(fam, n, new_n)
)
return new_n return new_n
def _shortened_name(self): def _shortened_name(self):
@ -64,7 +48,7 @@ class FontnameParser:
if not self.use_short_families[0]: if not self.use_short_families[0]:
return (self.basename, self.rest) return (self.basename, self.rest)
else: else:
return (FontnameTools.concat(self.basename, self.rest).replace(" ", ""), "") return (FontnameTools.concat(self.basename, self.rest).replace(' ', ''), '')
def set_keep_regular_in_family(self, keep): def set_keep_regular_in_family(self, keep):
"""Familyname may contain 'Regular' where it should normally be suppressed""" """Familyname may contain 'Regular' where it should normally be suppressed"""
@ -79,7 +63,7 @@ class FontnameParser:
# is not needed, or rather contraproductive. This can not be detected on a # is not needed, or rather contraproductive. This can not be detected on a
# font file level but needs to be specified per family from the outside. # font file level but needs to be specified per family from the outside.
# Returns true if setting was successful. # Returns true if setting was successful.
if "Italic" in self.style_token: if 'Italic' in self.style_token:
self.rename_oblique = True self.rename_oblique = True
return not noitalic return not noitalic
self.rename_oblique = not noitalic self.rename_oblique = not noitalic
@ -92,7 +76,7 @@ class FontnameParser:
def inject_suffix(self, family, ps_fontname, short_family): def inject_suffix(self, family, ps_fontname, short_family):
"""Add a custom additonal string that shows up in the resulting names""" """Add a custom additonal string that shows up in the resulting names"""
self.family_suff = family.strip() self.family_suff = family.strip()
self.ps_fontname_suff = ps_fontname.replace(" ", "") self.ps_fontname_suff = ps_fontname.replace(' ', '')
self.short_family_suff = short_family.strip() self.short_family_suff = short_family.strip()
return self return self
@ -102,7 +86,7 @@ class FontnameParser:
# prefix is either a string or False/True # prefix is either a string or False/True
if isinstance(prefix, str): if isinstance(prefix, str):
prefix = self._basename.startswith(prefix) prefix = self._basename.startswith(prefix)
self.use_short_families = (camelcase_name, prefix, aggressive) self.use_short_families = ( camelcase_name, prefix, aggressive )
return self return self
def add_name_substitution_table(self, table): def add_name_substitution_table(self, table):
@ -113,29 +97,27 @@ class FontnameParser:
self.basename = self._basename self.basename = self._basename
self.rest = self._rest self.rest = self._rest
for regex, replacement in self.name_subst: for regex, replacement in self.name_subst:
base_and_rest = self.basename + (" " + self.rest if len(self.rest) else "") base_and_rest = self.basename + (' ' + self.rest if len(self.rest) else '')
m = re.match(regex, base_and_rest, re.IGNORECASE) m = re.match(regex, base_and_rest, re.IGNORECASE)
if not m: if not m:
continue continue
i = len(self.basename) - len(m.group(0)) i = len(self.basename) - len(m.group(0))
if i < 0: if i < 0:
self.basename = m.expand(replacement).rstrip() self.basename = m.expand(replacement).rstrip()
self.rest = self.rest[-(i + 1) :].lstrip() self.rest = self.rest[-(i+1):].lstrip()
else: else:
self.basename = m.expand(replacement) + self.basename[len(m.group(0)) :] self.basename = m.expand(replacement) + self.basename[len(m.group(0)):]
return self return self
def drop_for_powerline(self): def drop_for_powerline(self):
"""Remove 'for Powerline' from all names (can not be undone)""" """Remove 'for Powerline' from all names (can not be undone)"""
if "Powerline" in self.other_token: if 'Powerline' in self.other_token:
idx = self.other_token.index("Powerline") idx = self.other_token.index('Powerline')
self.other_token.pop(idx) self.other_token.pop(idx)
if idx > 0 and self.other_token[idx - 1] == "For": if idx > 0 and self.other_token[idx - 1] == 'For':
self.other_token.pop(idx - 1) self.other_token.pop(idx - 1)
self._basename = re.sub( self._basename = re.sub(r'(\b|for\s?)?powerline\b', '', self._basename, 1, re.IGNORECASE).strip()
r"(\b|for\s?)?powerline\b", "", self._basename, 1, re.IGNORECASE self.add_name_substitution_table(self.name_subst) # re-evaluate
).strip()
self.add_name_substitution_table(self.name_subst) # re-evaluate
return self return self
### Following the creation of the name parts: ### Following the creation of the name parts:
@ -166,26 +148,20 @@ class FontnameParser:
styles = self.style_token styles = self.style_token
weights = self.weight_token weights = self.weight_token
if self.keep_regular_in_family == None: if self.keep_regular_in_family == None:
keep_regular = FontnameTools.is_keep_regular( keep_regular = FontnameTools.is_keep_regular(self._basename + ' ' + self._rest)
self._basename + " " + self._rest
)
else: else:
keep_regular = self.keep_regular_in_family keep_regular = self.keep_regular_in_family
if "Regular" in styles and ( if ('Regular' in styles
not keep_regular or len(self.weight_token) > 0 and (not keep_regular
): # This is actually a malformed font name or len(self.weight_token) > 0)): # This is actually a malformed font name
styles = list(self.style_token) styles = list(self.style_token)
styles.remove("Regular") styles.remove('Regular')
# For naming purposes we want Oblique to be part of the styles # For naming purposes we want Oblique to be part of the styles
(weights, styles) = FontnameTools.make_oblique_style(weights, styles) (weights, styles) = FontnameTools.make_oblique_style(weights, styles)
(name, rest) = self._shortened_name() (name, rest) = self._shortened_name()
if self.use_short_families[1]: if self.use_short_families[1]:
[weights, styles] = FontnameTools.short_styles( [ weights, styles ] = FontnameTools.short_styles([ weights, styles ], self.use_short_families[2])
[weights, styles], self.use_short_families[2] return FontnameTools.concat(name, rest, self.other_token, self.short_family_suff, weights, styles)
)
return FontnameTools.concat(
name, rest, self.other_token, self.short_family_suff, weights, styles
)
def psname(self): def psname(self):
"""Get the SFNT PostScriptName (ID 6)""" """Get the SFNT PostScriptName (ID 6)"""
@ -196,12 +172,10 @@ class FontnameParser:
if self.use_short_families[1]: if self.use_short_families[1]:
styles = FontnameTools.short_styles(styles, self.use_short_families[2]) styles = FontnameTools.short_styles(styles, self.use_short_families[2])
weights = FontnameTools.short_styles(weights, self.use_short_families[2]) weights = FontnameTools.short_styles(weights, self.use_short_families[2])
fam = FontnameTools.camel_casify( fam = FontnameTools.camel_casify(FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff))
FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff)
)
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles)) sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
if len(sub) > 0: if len(sub) > 0:
sub = "-" + sub sub = '-' + sub
fam = FontnameTools.postscript_char_filter(fam) fam = FontnameTools.postscript_char_filter(fam)
sub = FontnameTools.postscript_char_filter(sub) sub = FontnameTools.postscript_char_filter(sub)
return self._make_ps_name(fam + sub, False) return self._make_ps_name(fam + sub, False)
@ -212,7 +186,7 @@ class FontnameParser:
pfn = FontnameTools.concat(name, rest, self.other_token, self.family_suff) pfn = FontnameTools.concat(name, rest, self.other_token, self.family_suff)
if self.suppress_preferred_if_identical and pfn == self.family(): if self.suppress_preferred_if_identical and pfn == self.family():
# Do not set if identical to ID 1 # Do not set if identical to ID 1
return "" return ''
return pfn return pfn
def preferred_styles(self): def preferred_styles(self):
@ -224,7 +198,7 @@ class FontnameParser:
pfs = FontnameTools.concat(weights, styles) pfs = FontnameTools.concat(weights, styles)
if self.suppress_preferred_if_identical and pfs == self.subfamily(): if self.suppress_preferred_if_identical and pfs == self.subfamily():
# Do not set if identical to ID 2 # Do not set if identical to ID 2
return "" return ''
return pfs return pfs
def family(self): def family(self):
@ -237,8 +211,8 @@ class FontnameParser:
if not self.rename_oblique: if not self.rename_oblique:
(weights, styles) = FontnameTools.make_oblique_style(weights, []) (weights, styles) = FontnameTools.make_oblique_style(weights, [])
if self.use_short_families[1]: if self.use_short_families[1]:
[other, weights] = FontnameTools.short_styles([other, weights], aggressive) [ other, weights ] = FontnameTools.short_styles([ other, weights ], aggressive)
weights = [w if w != "Oblique" else "Obl" for w in weights] weights = [ w if w != 'Oblique' else 'Obl' for w in weights ]
return FontnameTools.concat(name, rest, other, self.short_family_suff, weights) return FontnameTools.concat(name, rest, other, self.short_family_suff, weights)
def subfamily(self): def subfamily(self):
@ -248,11 +222,11 @@ class FontnameParser:
if not self.rename_oblique: if not self.rename_oblique:
(weights, styles) = FontnameTools.make_oblique_style(weights, styles) (weights, styles) = FontnameTools.make_oblique_style(weights, styles)
if len(styles) == 0: if len(styles) == 0:
if "Oblique" in weights: if 'Oblique' in weights:
return FontnameTools.concat(styles, "Italic") return FontnameTools.concat(styles, 'Italic')
return "Regular" return 'Regular'
if "Oblique" in weights and not "Italic" in styles: if 'Oblique' in weights and not 'Italic' in styles:
return FontnameTools.concat(styles, "Italic") return FontnameTools.concat(styles, 'Italic')
return FontnameTools.concat(styles) return FontnameTools.concat(styles)
def ps_familyname(self): def ps_familyname(self):
@ -265,45 +239,33 @@ class FontnameParser:
def macstyle(self, style): def macstyle(self, style):
"""Modify a given macStyle value for current name, just bits 0 and 1 touched""" """Modify a given macStyle value for current name, just bits 0 and 1 touched"""
b = style & (~3) b = style & (~3)
b |= 1 if "Bold" in self.style_token else 0 b |= 1 if 'Bold' in self.style_token else 0
b |= 2 if "Italic" in self.style_token else 0 b |= 2 if 'Italic' in self.style_token else 0
return b return b
def fs_selection(self, fs): def fs_selection(self, fs):
"""Modify a given fsSelection value for current name, bits 0, 5, 6, 8, 9 touched""" """Modify a given fsSelection value for current name, bits 0, 5, 6, 8, 9 touched"""
ITALIC = 1 << 0 ITALIC = 1 << 0; BOLD = 1 << 5; REGULAR = 1 << 6; WWS = 1 << 8; OBLIQUE = 1 << 9
BOLD = 1 << 5
REGULAR = 1 << 6
WWS = 1 << 8
OBLIQUE = 1 << 9
b = fs & (~(ITALIC | BOLD | REGULAR | WWS | OBLIQUE)) b = fs & (~(ITALIC | BOLD | REGULAR | WWS | OBLIQUE))
if "Bold" in self.style_token: if 'Bold' in self.style_token:
b |= BOLD b |= BOLD
# Ignore Italic if we have Oblique # Ignore Italic if we have Oblique
if "Oblique" in self.weight_token: if 'Oblique' in self.weight_token:
b |= OBLIQUE b |= OBLIQUE
elif "Italic" in self.style_token: elif 'Italic' in self.style_token:
b |= ITALIC b |= ITALIC
# Regular is just the basic weight # Regular is just the basic weight
if len(self.weight_token) == 0: if len(self.weight_token) == 0 and not b & (ITALIC | BOLD | OBLIQUE):
b |= REGULAR b |= REGULAR
b |= WWS # We assert this by our naming process b |= WWS # We assert this by our naming process
return b return b
def checklen(self, max_len, entry_id, name): def checklen(self, max_len, entry_id, name):
"""Check the length of a name string and report violations""" """Check the length of a name string and report violations"""
if len(name) <= max_len: if len(name) <= max_len:
self.logger.debug( self.logger.debug('=====> {:18} ok ({:2} <={:2}): {}'.format(entry_id, len(name), max_len, name))
"=====> {:18} ok ({:2} <={:2}): {}".format(
entry_id, len(name), max_len, name
)
)
else: else:
self.logger.error( self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, name))
"====-< {:18} too long ({:2} > {:2}): {}".format(
entry_id, len(name), max_len, name
)
)
return name return name
def rename_font(self, font): def rename_font(self, font):
@ -339,78 +301,32 @@ class FontnameParser:
# and it is actually embedded as empty string, but empty strings are not # and it is actually embedded as empty string, but empty strings are not
# shown if you query the sfnt_names *rolleyes* # shown if you query the sfnt_names *rolleyes*
version_tag = "" version_tag = ''
sfnt_list = [] sfnt_list = []
TO_DEL = [ TO_DEL = ['Family', 'SubFamily', 'Fullname', 'PostScriptName', 'Preferred Family',
"Family", 'Preferred Styles', 'Compatible Full', 'WWS Family', 'WWS Subfamily',
"SubFamily", 'UniqueID', 'CID findfont Name']
"Fullname",
"PostScriptName",
"Preferred Family",
"Preferred Styles",
"Compatible Full",
"WWS Family",
"WWS Subfamily",
"UniqueID",
"CID findfont Name",
]
# Remove these entries in all languages and add (at least the vital ones) some # Remove these entries in all languages and add (at least the vital ones) some
# back, but only as 'English (US)'. This makes sure we do not leave contradicting # back, but only as 'English (US)'. This makes sure we do not leave contradicting
# names over different languages. # names over different languages.
for l, k, v in list(font.sfnt_names): for l, k, v in list(font.sfnt_names):
if not k in TO_DEL: if not k in TO_DEL:
sfnt_list += [(l, k, v)] sfnt_list += [( l, k, v )]
if k == "Version" and l == "English (US)": if k == 'Version' and l == 'English (US)':
version_tag = " " + v.split()[-1] version_tag = ' ' + v.split()[-1]
sfnt_list += [ sfnt_list += [( 'English (US)', 'Family', self.checklen(31, 'Family (ID 1)', self.family()) )] # 1
( sfnt_list += [( 'English (US)', 'SubFamily', self.checklen(31, 'SubFamily (ID 2)', self.subfamily()) )] # 2
"English (US)", sfnt_list += [( 'English (US)', 'UniqueID', self.fullname() + version_tag )] # 3
"Family", sfnt_list += [( 'English (US)', 'Fullname', self.checklen(63, 'Fullname (ID 4)', self.fullname()) )] # 4
self.checklen(31, "Family (ID 1)", self.family()), sfnt_list += [( 'English (US)', 'PostScriptName', self.checklen(63, 'PSN (ID 6)', self.psname()) )] # 6
)
] # 1
sfnt_list += [
(
"English (US)",
"SubFamily",
self.checklen(31, "SubFamily (ID 2)", self.subfamily()),
)
] # 2
sfnt_list += [("English (US)", "UniqueID", self.fullname() + version_tag)] # 3
sfnt_list += [
(
"English (US)",
"Fullname",
self.checklen(63, "Fullname (ID 4)", self.fullname()),
)
] # 4
sfnt_list += [
(
"English (US)",
"PostScriptName",
self.checklen(63, "PSN (ID 6)", self.psname()),
)
] # 6
p_fam = self.preferred_family() p_fam = self.preferred_family()
if len(p_fam): if len(p_fam):
sfnt_list += [ sfnt_list += [( 'English (US)', 'Preferred Family', self.checklen(31, 'PrefFamily (ID 16)', p_fam) )] # 16
(
"English (US)",
"Preferred Family",
self.checklen(31, "PrefFamily (ID 16)", p_fam),
)
] # 16
p_sty = self.preferred_styles() p_sty = self.preferred_styles()
if len(p_sty): if len(p_sty):
sfnt_list += [ sfnt_list += [( 'English (US)', 'Preferred Styles', self.checklen(31, 'PrefStyles (ID 17)', p_sty) )] # 17
(
"English (US)",
"Preferred Styles",
self.checklen(31, "PrefStyles (ID 17)", p_sty),
)
] # 17
font.sfnt_names = tuple(sfnt_list) font.sfnt_names = tuple(sfnt_list)

View file

@ -4,7 +4,6 @@
import re import re
import sys import sys
class FontnameTools: class FontnameTools:
"""Deconstruct a font filename to get standardized name parts""" """Deconstruct a font filename to get standardized name parts"""
@ -16,32 +15,32 @@ class FontnameTools:
@staticmethod @staticmethod
def camel_casify(word): def camel_casify(word):
"""Remove blanks and use CamelCase for the new word""" """Remove blanks and use CamelCase for the new word"""
return "".join(map(FontnameTools.front_upper, word.split(" "))) return ''.join(map(FontnameTools.front_upper, word.split(' ')))
@staticmethod @staticmethod
def camel_explode(word): def camel_explode(word):
"""Explode CamelCase -> Camel Case""" """Explode CamelCase -> Camel Case"""
# But do not explode "JetBrains" etc at string start... # But do not explode "JetBrains" etc at string start...
excludes = [ excludes = [
"JetBrains", 'JetBrains',
"DejaVu", 'DejaVu',
"OpenDyslexicAlta", 'OpenDyslexicAlta',
"OpenDyslexicMono", 'OpenDyslexicMono',
"OpenDyslexic", 'OpenDyslexic',
"DaddyTimeMono", 'DaddyTimeMono',
"InconsolataGo", 'InconsolataGo',
"ProFontWindows", 'ProFontWindows',
"ProFont", 'ProFont',
"ProggyClean", 'ProggyClean',
] ]
m = re.match("(" + "|".join(excludes) + ")(.*)", word) m = re.match('(' + '|'.join(excludes) + ')(.*)', word)
(prefix, word) = m.group(1, 2) if m != None else ("", word) (prefix, word) = m.group(1,2) if m != None else ('', word)
if len(word) == 0: if len(word) == 0:
return prefix return prefix
parts = re.split("(?<=[a-z0-9])(?=[A-Z])", word) parts = re.split('(?<=[a-z0-9])(?=[A-Z])', word)
if len(prefix): if len(prefix):
parts.insert(0, prefix) parts.insert(0, prefix)
return " ".join(parts) return ' '.join(parts)
@staticmethod @staticmethod
def drop_empty(l): def drop_empty(l):
@ -57,7 +56,7 @@ class FontnameTools:
all.append(thing) all.append(thing)
else: else:
all += thing all += thing
return " ".join(FontnameTools.drop_empty(all)) return ' '.join(FontnameTools.drop_empty(all))
@staticmethod @staticmethod
def unify_style_names(style_name): def unify_style_names(style_name):
@ -65,20 +64,20 @@ class FontnameTools:
known_names = { known_names = {
# Source of the table is the current sourcefonts # Source of the table is the current sourcefonts
# Left side needs to be lower case # Left side needs to be lower case
"-": "", '-': '',
"book": "", 'book': '',
"text": "", 'text': '',
"ce": "CE", 'ce': 'CE',
#'semibold': 'Demi', #'semibold': 'Demi',
"ob": "Oblique", 'ob': 'Oblique',
"it": "Italic", 'it': 'Italic',
"i": "Italic", 'i': 'Italic',
"b": "Bold", 'b': 'Bold',
"normal": "Regular", 'normal': 'Regular',
"c": "Condensed", 'c': 'Condensed',
"r": "Regular", 'r': 'Regular',
"m": "Medium", 'm': 'Medium',
"l": "Light", 'l': 'Light',
} }
if style_name in known_names: if style_name in known_names:
return known_names[style_name.lower()] return known_names[style_name.lower()]
@ -89,7 +88,7 @@ class FontnameTools:
"""Find an entry in a list of dicts, return entry and in which list it was""" """Find an entry in a list of dicts, return entry and in which list it was"""
for i, d in enumerate(dicts): for i, d in enumerate(dicts):
if key in d: if key in d:
return (d[key], i) return ( d[key], i )
return (None, 0) return (None, 0)
@staticmethod @staticmethod
@ -111,26 +110,20 @@ class FontnameTools:
# - has modifier: use second form of mod plus first form of weights2 # - has modifier: use second form of mod plus first form of weights2
# - has modifier: use second form of mod plus second form of widths # - has modifier: use second form of mod plus second form of widths
name_rest = name name_rest = name
name_pre = "" name_pre = ''
form = FontnameTools.get_shorten_form_idx(aggressive, "", 0) form = FontnameTools.get_shorten_form_idx(aggressive, '', 0)
for mod in FontnameTools.known_modifiers: for mod in FontnameTools.known_modifiers:
if name.startswith(mod) and len(name) > len( if name.startswith(mod) and len(name) > len(mod): # Second condition specifically for 'Demi'
mod
): # Second condition specifically for 'Demi'
name_pre = FontnameTools.known_modifiers[mod][form] name_pre = FontnameTools.known_modifiers[mod][form]
name_rest = name[len(mod) :] name_rest = name[len(mod):]
break break
subst, i = FontnameTools.find_in_dicts( subst, i = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights2, FontnameTools.known_widths ])
name_rest, [FontnameTools.known_weights2, FontnameTools.known_widths]
)
form = FontnameTools.get_shorten_form_idx(aggressive, name_pre, i) form = FontnameTools.get_shorten_form_idx(aggressive, name_pre, i)
if isinstance(subst, tuple): if isinstance(subst, tuple):
return name_pre + subst[form] return name_pre + subst[form]
if not len(name_pre): if not len(name_pre):
# The following sets do not allow modifiers # The following sets do not allow modifiers
subst, _ = FontnameTools.find_in_dicts( subst, _ = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights1, FontnameTools.known_slopes ])
name_rest, [FontnameTools.known_weights1, FontnameTools.known_slopes]
)
if isinstance(subst, tuple): if isinstance(subst, tuple):
return subst[form] return subst[form]
return name return name
@ -139,26 +132,21 @@ class FontnameTools:
def short_styles(lists, aggressive): def short_styles(lists, aggressive):
"""Shorten all style names in a list or a list of lists""" """Shorten all style names in a list or a list of lists"""
if not len(lists) or not isinstance(lists[0], list): if not len(lists) or not isinstance(lists[0], list):
return list( return list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), lists))
map(lambda x: FontnameTools.shorten_style_name(x, aggressive), lists) return [ list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles)) for styles in lists ]
)
return [
list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles))
for styles in lists
]
@staticmethod @staticmethod
def make_oblique_style(weights, styles): def make_oblique_style(weights, styles):
"""Move "Oblique" from weights to styles for font naming purposes""" """Move "Oblique" from weights to styles for font naming purposes"""
if "Oblique" in weights: if 'Oblique' in weights:
weights = list(weights) weights = list(weights)
weights.remove("Oblique") weights.remove('Oblique')
styles = list(styles) styles = list(styles)
styles.append("Oblique") styles.append('Oblique')
return (weights, styles) return (weights, styles)
@staticmethod @staticmethod
def get_name_token(name, tokens, allow_regex_token=False): def get_name_token(name, tokens, allow_regex_token = False):
"""Try to find any case insensitive token from tokens in the name, return tuple with found token-list and rest""" """Try to find any case insensitive token from tokens in the name, return tuple with found token-list and rest"""
# The default mode (allow_regex_token = False) will try to find any verbatim string in the # The default mode (allow_regex_token = False) will try to find any verbatim string in the
# tokens list (case insensitive matching) and give that tokens list item back with # tokens list (case insensitive matching) and give that tokens list item back with
@ -168,29 +156,27 @@ class FontnameTools:
# #
# Token are always used in a regex and may not capture, use non capturing # Token are always used in a regex and may not capture, use non capturing
# grouping if needed (?: ... ) # grouping if needed (?: ... )
lower_tokens = [t.lower() for t in tokens] lower_tokens = [ t.lower() for t in tokens ]
not_matched = "" not_matched = ""
all_tokens = [] all_tokens = []
j = 1 j = 1
regex = re.compile("(.*?)(" + "|".join(tokens) + ")(.*)", re.IGNORECASE) regex = re.compile('(.*?)(' + '|'.join(tokens) + ')(.*)', re.IGNORECASE)
while j: while j:
j = regex.match(name) j = regex.match(name)
if not j: if not j:
break break
if len(j.groups()) != 3: if len(j.groups()) != 3:
sys.exit("Malformed regex in FontnameTools.get_name_token()") sys.exit('Malformed regex in FontnameTools.get_name_token()')
not_matched += ( not_matched += ' ' + j.groups()[0] # Blanc prevents unwanted concatenation of unmatched substrings
" " + j.groups()[0]
) # Blanc prevents unwanted concatenation of unmatched substrings
tok = j.groups()[1].lower() tok = j.groups()[1].lower()
if tok in lower_tokens: if tok in lower_tokens:
tok = tokens[lower_tokens.index(tok)] tok = tokens[lower_tokens.index(tok)]
tok = FontnameTools.unify_style_names(tok) tok = FontnameTools.unify_style_names(tok)
if len(tok): if len(tok):
all_tokens.append(tok) all_tokens.append(tok)
name = j.groups()[2] # Recurse rest name = j.groups()[2] # Recurse rest
not_matched += " " + name not_matched += ' ' + name
return (not_matched.strip(), all_tokens) return ( not_matched.strip(), all_tokens )
@staticmethod @staticmethod
def postscript_char_filter(name): def postscript_char_filter(name):
@ -199,48 +185,42 @@ class FontnameTools:
# except for the 10 characters '[', ']', '(', ')', '{', '}', '<', '>', '/', '%' # except for the 10 characters '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'
out = "" out = ""
for c in name: for c in name:
if c in "[](){}<>/%" or ord(c) < 33 or ord(c) > 126: if c in '[](){}<>/%' or ord(c) < 33 or ord(c) > 126:
continue continue
out += c out += c
return out return out
SIL_TABLE = [ SIL_TABLE = [
("(a)nonymous", r"\1nonymice"), ( '(a)nonymous', r'\1nonymice' ),
("(b)itstream( ?)(v)era( ?sans ?mono)?", r"\1itstrom\2Wera"), ( '(b)itstream( ?)(v)era( ?sans ?mono)?', r'\1itstrom\2Wera' ),
("(s)ource", r"\1auce"), ( '(s)ource', r'\1auce' ),
("(h)ermit", r"\1urmit"), ( '(h)ermit', r'\1urmit' ),
("(h)asklig", r"\1asklug"), ( '(h)asklig', r'\1asklug' ),
("(s)hare", r"\1hure"), ( '(s)hare', r'\1hure' ),
("IBM[- ]?plex", r"Blex"), # We do not keep the case here ( 'IBM[- ]?plex', r'Blex' ), # We do not keep the case here
("(t)erminus", r"\1erminess"), ( '(t)erminus', r'\1erminess' ),
("(l)iberation", r"\1iteration"), ( '(l)iberation', r'\1iteration' ),
("iA([- ]?)writer", r"iM\1Writing"), ( 'iA([- ]?)writer', r'iM\1Writing' ),
("(a)nka/(c)oder", r"\1na\2onder"), ( '(a)nka/(c)oder', r'\1na\2onder' ),
("(c)ascadia( ?)(c)ode", r"\1askaydia\2\3ove"), ( '(c)ascadia( ?)(c)ode', r'\1askaydia\2\3ove' ),
("(c)ascadia( ?)(m)ono", r"\1askaydia\2\3ono"), ( '(c)ascadia( ?)(m)ono', r'\1askaydia\2\3ono' ),
("(m)( ?)plus", r"\1+"), # Added this, because they use a plus symbol :-> ( '(m)( ?)plus', r'\1+'), # Added this, because they use a plus symbol :->
("Gohufont", r"GohuFont"), # Correct to CamelCase ( 'Gohufont', r'GohuFont'), # Correct to CamelCase
# Noone cares that font names starting with a digit are forbidden: # Noone cares that font names starting with a digit are forbidden:
("IBM 3270", r"3270"), # for historical reasons and 'IBM' is a TM or something ( 'IBM 3270', r'3270'), # for historical reasons and 'IBM' is a TM or something
# Some name parts that are too long for us # Some name parts that are too long for us
("(.*sans ?m)ono", r"\1"), # Various SomenameSansMono fonts ( '(.*sans ?m)ono', r'\1'), # Various SomenameSansMono fonts
("(.*code ?lat)in Expanded", r"\1X"), # for 'M PLUS Code Latin Expanded' ( '(.*code ?lat)in Expanded', r'\1X'), # for 'M PLUS Code Latin Expanded'
("(.*code ?lat)in", r"\1"), # for 'M PLUS Code Latin' ( '(.*code ?lat)in', r'\1'), # for 'M PLUS Code Latin'
("(b)ig( ?)(b)lue( ?)(t)erminal", r"\1ig\3lue\5erm"), # Shorten BigBlueTerminal ( '(b)ig( ?)(b)lue( ?)(t)erminal', r'\1ig\3lue\5erm'), # Shorten BigBlueTerminal
("(.*)437TT", r"\g<1>437"), # Shorten BigBlueTerminal 437 TT even further ( '(.*)437TT', r'\g<1>437'), # Shorten BigBlueTerminal 437 TT even further
("(.*dyslexic ?alt)a", r"\1"), # Open Dyslexic Alta -> Open Dyslexic Alt ( '(.*dyslexic ?alt)a', r'\1'), # Open Dyslexic Alta -> Open Dyslexic Alt
("(.*dyslexic ?m)ono", r"\1"), # Open Dyslexic Mono -> Open Dyslexic M ( '(.*dyslexic ?m)ono', r'\1'), # Open Dyslexic Mono -> Open Dyslexic M
("(overpass ?m)ono", r"\1"), # Overpass Mono -> Overpass M ( '(overpass ?m)ono', r'\1'), # Overpass Mono -> Overpass M
("(proggyclean) ?tt", r"\1"), # Remove TT from ProggyClean ( '(proggyclean) ?tt', r'\1'), # Remove TT from ProggyClean
( ( '(terminess) ?\(ttf\)', r'\1'), # Remove TTF from Terminus (after renamed to Terminess)
"(terminess) ?\(ttf\)", ( '(im ?writing ?q)uattro', r'\1uat'), # Rename iM Writing Quattro to Quat
r"\1", ( '(im ?writing ?(mono|duo|quat)) ?s', r'\1'), # Remove S from all iM Writing styles
), # Remove TTF from Terminus (after renamed to Terminess)
("(im ?writing ?q)uattro", r"\1uat"), # Rename iM Writing Quattro to Quat
(
"(im ?writing ?(mono|duo|quat)) ?s",
r"\1",
), # Remove S from all iM Writing styles
] ]
# From https://adobe-type-tools.github.io/font-tech-notes/pdfs/5088.FontNames.pdf # From https://adobe-type-tools.github.io/font-tech-notes/pdfs/5088.FontNames.pdf
@ -254,103 +234,98 @@ class FontnameTools:
# - has modifier: use second form of mod plus first form of weights2 # - has modifier: use second form of mod plus first form of weights2
# - has modifier: use second form of mod plus second form of widths # - has modifier: use second form of mod plus second form of widths
# This is encoded in get_shorten_form_idx() # This is encoded in get_shorten_form_idx()
known_weights1 = { # can not take modifiers known_weights1 = { # can not take modifiers
"Medium": ("Md", "Med"), 'Medium': ('Md', 'Med'),
"Nord": ("Nd", "Nord"), 'Nord': ('Nd', 'Nord'),
"Book": ("Bk", "Book"), 'Book': ('Bk', 'Book'),
"Poster": ("Po", "Poster"), 'Poster': ('Po', 'Poster'),
"Demi": ( 'Demi': ('Dm', 'Demi'), # Demi is sometimes used as a weight, sometimes as a modifier
"Dm", 'Regular': ('Rg', 'Reg'),
"Demi", 'Display': ('DS', 'Disp'),
), # Demi is sometimes used as a weight, sometimes as a modifier 'Super': ('Su', 'Sup'),
"Regular": ("Rg", "Reg"), 'Retina': ('Rt', 'Ret'),
"Display": ("DS", "Disp"),
"Super": ("Su", "Sup"),
"Retina": ("Rt", "Ret"),
} }
known_weights2 = { # can take modifiers known_weights2 = { # can take modifiers
"Black": ("Blk", "Black"), 'Black': ('Blk', 'Black'),
"Bold": ("Bd", "Bold"), 'Bold': ('Bd', 'Bold'),
"Heavy": ("Hv", "Heavy"), 'Heavy': ('Hv', 'Heavy'),
"Thin": ("Th", "Thin"), 'Thin': ('Th', 'Thin'),
"Light": ("Lt", "Light"), 'Light': ('Lt', 'Light'),
" ": (), # Just for CodeClimate :-/ ' ': (), # Just for CodeClimate :-/
} }
known_widths = { # can take modifiers known_widths = { # can take modifiers
"Compressed": ("Cm", "Comp"), 'Compressed': ('Cm', 'Comp'),
"Extended": ("Ex", "Extd"), 'Extended': ('Ex', 'Extd'),
"Condensed": ("Cn", "Cond"), 'Condensed': ('Cn', 'Cond'),
"Narrow": ("Nr", "Narrow"), 'Narrow': ('Nr', 'Narrow'),
"Compact": ("Ct", "Compact"), 'Compact': ('Ct', 'Compact'),
} }
known_slopes = { # can not take modifiers known_slopes = { # can not take modifiers
"Inclined": ("Ic", "Incl"), 'Inclined': ('Ic', 'Incl'),
"Oblique": ("Obl", "Obl"), 'Oblique': ('Obl', 'Obl'),
"Italic": ("It", "Italic"), 'Italic': ('It', 'Italic'),
"Upright": ("Up", "Uprght"), 'Upright': ('Up', 'Uprght'),
"Kursiv": ("Ks", "Kurs"), 'Kursiv': ('Ks', 'Kurs'),
"Sloped": ("Sl", "Slop"), 'Sloped': ('Sl', 'Slop'),
} }
known_modifiers = { known_modifiers = {
"Demi": ("Dm", "Dem"), 'Demi': ('Dm', 'Dem'),
"Ultra": ("Ult", "Ult"), 'Ultra': ('Ult', 'Ult'),
"Semi": ("Sm", "Sem"), 'Semi': ('Sm', 'Sem'),
"Extra": ("X", "Ext"), 'Extra': ('X', 'Ext'),
} }
@staticmethod @staticmethod
def is_keep_regular(basename): def is_keep_regular(basename):
"""This has been decided by the font designers, we need to mimic that (for comparison purposes)""" """This has been decided by the font designers, we need to mimic that (for comparison purposes)"""
KEEP_REGULAR = [ KEEP_REGULAR = [
"Agave", 'Agave',
"Arimo", 'Arimo',
"Aurulent", 'Aurulent',
"Cascadia", 'Cascadia',
"Cousine", 'Cousine',
"Fantasque", 'Fantasque',
"Fira", 'Fira',
"Overpass",
"Lilex", 'Overpass',
"Inconsolata$", # not InconsolataGo 'Lilex',
"IAWriter", 'Inconsolata$', # not InconsolataGo
"Meslo", 'IAWriter',
"Monoid", 'Meslo',
"Mononoki", 'Monoid',
"Hack", 'Mononoki',
"JetBrains Mono", 'Hack',
"Noto Sans", 'JetBrains Mono',
"Noto Serif", 'Noto Sans',
"Victor", 'Noto Serif',
'Victor',
] ]
for kr in KEEP_REGULAR: for kr in KEEP_REGULAR:
if (basename.rstrip() + "$").startswith(kr): if (basename.rstrip() + '$').startswith(kr): return True
return True
return False return False
@staticmethod @staticmethod
def _parse_simple_font_name(name): def _parse_simple_font_name(name):
"""Parse a filename that does not follow the 'FontFamilyName-FontStyle' pattern""" """Parse a filename that does not follow the 'FontFamilyName-FontStyle' pattern"""
# No dash in name, maybe we have blanc separated filename? # No dash in name, maybe we have blanc separated filename?
if " " in name: if ' ' in name:
return FontnameTools.parse_font_name(name.replace(" ", "-")) return FontnameTools.parse_font_name(name.replace(' ', '-'))
# Do we have a number-name boundary? # Do we have a number-name boundary?
p = re.split("(?<=[0-9])(?=[a-zA-Z])", name) p = re.split('(?<=[0-9])(?=[a-zA-Z])', name)
if len(p) > 1: if len(p) > 1:
return FontnameTools.parse_font_name("-".join(p)) return FontnameTools.parse_font_name('-'.join(p))
# Or do we have CamelCase? # Or do we have CamelCase?
n = FontnameTools.camel_explode(name) n = FontnameTools.camel_explode(name)
if n != name: if n != name:
return FontnameTools.parse_font_name(n.replace(" ", "-")) return FontnameTools.parse_font_name(n.replace(' ', '-'))
return (False, FontnameTools.camel_casify(name), [], [], [], "") return (False, FontnameTools.camel_casify(name), [], [], [], '')
@staticmethod @staticmethod
def parse_font_name(name): def parse_font_name(name):
"""Expects a filename following the 'FontFamilyName-FontStyle' pattern and returns ... parts""" """Expects a filename following the 'FontFamilyName-FontStyle' pattern and returns ... parts"""
name = re.sub( name = re.sub(r'\bsemi-condensed\b', 'SemiCondensed', name, 1, re.IGNORECASE) # Just for "3270 Semi-Condensed" :-/
r"\bsemi-condensed\b", "SemiCondensed", name, 1, re.IGNORECASE name = re.sub('[_\s]+', ' ', name)
) # Just for "3270 Semi-Condensed" :-/ matches = re.match(r'([^-]+)(?:-(.*))?', name)
name = re.sub("[_\s]+", " ", name)
matches = re.match(r"([^-]+)(?:-(.*))?", name)
familyname = FontnameTools.camel_casify(matches.group(1)) familyname = FontnameTools.camel_casify(matches.group(1))
style = matches.group(2) style = matches.group(2)
@ -361,81 +336,47 @@ class FontnameTools:
# Weights end up as Typographic Family parts ('after the dash') # Weights end up as Typographic Family parts ('after the dash')
# Styles end up as Family parts (for classic grouping of four) # Styles end up as Family parts (for classic grouping of four)
# Others also end up in Typographic Family ('before the dash') # Others also end up in Typographic Family ('before the dash')
weights = ( weights = [ m + s
[ for s in list(FontnameTools.known_weights2) + list(FontnameTools.known_widths)
m + s for m in list(FontnameTools.known_modifiers) + [''] if m != s
for s in list(FontnameTools.known_weights2) ] + list(FontnameTools.known_weights1) + list(FontnameTools.known_slopes)
+ list(FontnameTools.known_widths) styles = [ 'Bold', 'Italic', 'Regular', 'Normal', ]
for m in list(FontnameTools.known_modifiers) + [""] weights = [ w for w in weights if w not in styles ]
if m != s
]
+ list(FontnameTools.known_weights1)
+ list(FontnameTools.known_slopes)
)
styles = [
"Bold",
"Italic",
"Regular",
"Normal",
]
weights = [w for w in weights if w not in styles]
# Some font specialities: # Some font specialities:
other = [ other = [
"-", '-', 'Book', 'For', 'Powerline',
"Book", 'Text', # Plex
"For", 'IIx', # Profont IIx
"Powerline", 'LGC', # Inconsolata LGC
"Text", # Plex r'\bCE\b', # ProggycleanTT CE
"IIx", # Profont IIx r'[12][cmp]n?', # MPlus
"LGC", # Inconsolata LGC r'(?:uni-)?1[14]', # GohuFont uni
r"\bCE\b", # ProggycleanTT CE
r"[12][cmp]n?", # MPlus
r"(?:uni-)?1[14]", # GohuFont uni
] ]
# Sometimes used abbreviations # Sometimes used abbreviations
weight_abbrevs = [ weight_abbrevs = [ 'ob', 'c', 'm', 'l', ]
"ob", style_abbrevs = [ 'it', 'r', 'b', 'i', ]
"c",
"m",
"l",
]
style_abbrevs = [
"it",
"r",
"b",
"i",
]
(style, weight_token) = FontnameTools.get_name_token(style, weights) ( style, weight_token ) = FontnameTools.get_name_token(style, weights)
(style, style_token) = FontnameTools.get_name_token(style, styles) ( style, style_token ) = FontnameTools.get_name_token(style, styles)
(style, other_token) = FontnameTools.get_name_token(style, other, True) ( style, other_token ) = FontnameTools.get_name_token(style, other, True)
if ( if (len(style) < 4
len(style) < 4 and style.lower() != "pro" and style.lower() != 'pro'): # Prevent 'r' of Pro to be detected as style_abbrev
): # Prevent 'r' of Pro to be detected as style_abbrev ( style, weight_token_abbrevs ) = FontnameTools.get_name_token(style, weight_abbrevs)
(style, weight_token_abbrevs) = FontnameTools.get_name_token( ( style, style_token_abbrevs ) = FontnameTools.get_name_token(style, style_abbrevs)
style, weight_abbrevs
)
(style, style_token_abbrevs) = FontnameTools.get_name_token(
style, style_abbrevs
)
weight_token += weight_token_abbrevs weight_token += weight_token_abbrevs
style_token += style_token_abbrevs style_token += style_token_abbrevs
while "Regular" in style_token and len(style_token) > 1: while 'Regular' in style_token and len(style_token) > 1:
# Correct situation where "Regular" and something else is given # Correct situation where "Regular" and something else is given
style_token.remove("Regular") style_token.remove('Regular')
# Recurse to see if unmatched stuff between dashes can belong to familyname # Recurse to see if unmatched stuff between dashes can belong to familyname
matches2 = re.match(r"(\w+)-(.*)", style) matches2 = re.match(r'(\w+)-(.*)', style)
if matches2: if matches2:
return FontnameTools.parse_font_name( return FontnameTools.parse_font_name(familyname + matches2.group(1) + '-' + matches2.group(2))
familyname + matches2.group(1) + "-" + matches2.group(2)
)
style = re.sub( style = re.sub(r'(^|\s)\d+(\.\d+)+(\s|$)', r'\1\3', style) # Remove (free standing) version numbers
r"(^|\s)\d+(\.\d+)+(\s|$)", r"\1\3", style style_parts = FontnameTools.drop_empty(style.split(' '))
) # Remove (free standing) version numbers style = ' '.join(map(FontnameTools.front_upper, style_parts))
style_parts = FontnameTools.drop_empty(style.split(" "))
style = " ".join(map(FontnameTools.front_upper, style_parts))
familyname = FontnameTools.camel_explode(familyname) familyname = FontnameTools.camel_explode(familyname)
return (True, familyname, weight_token, style_token, other_token, style) return (True, familyname, weight_token, style_token, other_token, style)

View file

@ -1,14 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding=utf8 # coding=utf8
# Nerd Fonts Version: 3.0.0 # Nerd Fonts Version: 3.0.1
# Script version is further down # Script version is further down
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 = "4.1.2" script_version = "4.3.1"
version = "3.0.0" version = "3.0.1"
projectName = "Nerd Fonts" projectName = "Nerd Fonts"
projectNameAbbreviation = "NF" projectNameAbbreviation = "NF"
projectNameSingular = projectName[:-1] projectNameSingular = projectName[:-1]
@ -324,7 +324,7 @@ class font_patcher:
self.font_dim = None # class 'dict' self.font_dim = None # class 'dict'
self.font_extrawide = False self.font_extrawide = False
self.source_monospaced = None # Later True or False self.source_monospaced = None # Later True or False
self.symbolsonly = False self.symbolsonly = False # Are we generating the SymbolsOnly font?
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)
@ -337,8 +337,8 @@ class font_patcher:
self.setup_name_backup(font) self.setup_name_backup(font)
self.assert_monospace() self.assert_monospace()
self.remove_ligatures() self.remove_ligatures()
self.setup_patch_set()
self.get_sourcefont_dimensions() self.get_sourcefont_dimensions()
self.setup_patch_set()
self.improve_line_dimensions() self.improve_line_dimensions()
self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available
self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used
@ -373,15 +373,16 @@ class font_patcher:
if symfont: if symfont:
symfont.close() symfont.close()
symfont = None symfont = None
if not os.path.isfile(self.args.glyphdir + patch['Filename']): symfont_file = os.path.join(self.args.glyphdir, patch['Filename'])
if not os.path.isfile(symfont_file):
logger.critical("Can not find symbol source for '%s' (i.e. %s)", logger.critical("Can not find symbol source for '%s' (i.e. %s)",
patch['Name'], self.args.glyphdir + patch['Filename']) patch['Name'], symfont_file)
sys.exit(1) sys.exit(1)
if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK): if not os.access(symfont_file, os.R_OK):
logger.critical("Can not open symbol source for '%s' (i.e. %s)", logger.critical("Can not open symbol source for '%s' (i.e. %s)",
patch['Name'], self.args.glyphdir + patch['Filename']) patch['Name'], symfont_file)
sys.exit(1) sys.exit(1)
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename'])) symfont = fontforge.open(symfont_file)
symfont.encoding = 'UnicodeFull' symfont.encoding = 'UnicodeFull'
# Match the symbol font size to the source font size # Match the symbol font size to the source font size
@ -434,10 +435,10 @@ class font_patcher:
sanitize_filename(fontname) + self.args.extension)) sanitize_filename(fontname) + self.args.extension))
bitmaps = str() bitmaps = str()
if len(self.sourceFont.bitmapSizes): if len(self.sourceFont.bitmapSizes):
logger.debug("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) logger.debug("Preserving bitmaps %s", repr(self.sourceFont.bitmapSizes))
bitmaps = str('otf') # otf/ttf, both is bf_ttf bitmaps = str('otf') # otf/ttf, both is bf_ttf
if self.args.dry_run: if self.args.dry_run:
logger.debug("=====> Filename '{}'".format(outfile)) logger.debug("=====> Filename '%s'", outfile)
return return
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags) sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile) message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
@ -784,7 +785,7 @@ 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 """
box_enabled = self.source_monospaced # Box glyph only for monospaced box_enabled = self.source_monospaced and not self.symbolsonly # Box glyph only for monospaced and not for Symbols Only
box_keep = False box_keep = False
if box_enabled: if box_enabled:
self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f) self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f)
@ -802,7 +803,7 @@ class font_patcher:
box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this
# Stretch 'xz' or 'pa' (preserve aspect ratio) # Stretch 'xz' or 'pa' (preserve aspect ratio)
# Supported params: overlap | careful | xy-ratio | dont_copy # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding
# Overlap value is used horizontally but vertically limited to 0.01 # Overlap value is used horizontally but vertically limited to 0.01
# Careful does not overwrite/modify existing glyphs # Careful does not overwrite/modify existing glyphs
# The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs)
@ -810,6 +811,8 @@ class font_patcher:
# '2' means occupy 2 cells (default for 'pa') # '2' means occupy 2 cells (default for 'pa')
# '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up) # '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up)
# Dont_copy does not overwrite existing glyphs but rescales the preexisting ones # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones
#
# Be careful, stretch may not change within a ScaleRule!
SYM_ATTR_DEFAULT = { SYM_ATTR_DEFAULT = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}
@ -886,7 +889,7 @@ class font_patcher:
0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}} 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}
} }
SYM_ATTR_HEAVYBRACKETS = { SYM_ATTR_HEAVYBRACKETS = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': True}} 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa1!', 'params': {'ypadding': 0.3, 'careful': True}}
} }
SYM_ATTR_BOX = { SYM_ATTR_BOX = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}}, 'default': {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}},
@ -896,8 +899,9 @@ class font_patcher:
# 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, # 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}},
} }
CUSTOM_ATTR = { CUSTOM_ATTR = {
# 'pa' == preserve aspect ratio # previous custom scaling => do not touch the icons
'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} # 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}}
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': self.args.careful}}
} }
# Most glyphs we want to maximize (individually) during the scale # Most glyphs we want to maximize (individually) during the scale
@ -956,6 +960,9 @@ class font_patcher:
range(0xeab4, 0xeab7 + 1), # chevrons range(0xeab4, 0xeab7 + 1), # chevrons
[0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles [0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles
[0xeacc, 0xeaba], # dash [0xeacc, 0xeaba], # dash
[0xea75, 0xebe7], # lock pair
[0xeacf, 0xebe0], # debug-continue pair
[0xeb91, 0xeba8], # debug-alt pair
]} ]}
DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo
'GlyphsToScale': [ 'GlyphsToScale': [
@ -982,14 +989,21 @@ class font_patcher:
range(0xf221, 0xf22d + 1), # gender or so range(0xf221, 0xf22d + 1), # gender or so
range(0xf255, 0xf25b + 1), # hand symbols range(0xf255, 0xf25b + 1), # hand symbols
]} ]}
OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?) HEAVY_SCALE_LIST = {'ScaleGlyph': 0x2771, # widest bracket, horizontally
'GlyphsToScale': [ 'GlyphsToScale': [
(0xf03d, 0xf040), # arrows (0x276c, 0x2771) # all
0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles ]}
(0xf051, 0xf053), # small stuff OCTI_SCALE_LIST = {'ScaleGroups': [
0xf071, 0xf09f, 0xf0a0, 0xf0a1, # small arrows [*range(0xf03d, 0xf040 + 1), 0xf019, 0xf030, 0xf04a, 0xf050, 0xf071, 0xf08c ], # arrows
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons [0xF0E7, # Smily and ...
0xf0ca, # dash 0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles
0xf052, 0xf053, 0x296, 0xf2f0, # small stuff
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons
0xf0ca, 0xf081, 0xf092, # dash, X, github-text
],
[0xf09c, 0xf09f, 0xf0de], # bells
range(0xf2c2, 0xf2c5 + 1), # move to
[0xf07b, 0xf0a1, 0xf0d6, 0xf306], # bookmarks
]} ]}
WEATH_SCALE_LIST = {'ScaleGroups': [ WEATH_SCALE_LIST = {'ScaleGroups': [
[0xf03c, 0xf042, 0xf045 ], # degree signs [0xf03c, 0xf042, 0xf045 ], # degree signs
@ -1016,7 +1030,7 @@ class font_patcher:
# 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': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_HEAVYBRACKETS}, {'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': HEAVY_SCALE_LIST, 'Attributes': SYM_ATTR_HEAVYBRACKETS},
{'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX}, {'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX},
{'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleRules': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': 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': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
@ -1038,7 +1052,7 @@ class font_patcher:
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/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/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/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/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/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/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/octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF305, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF306, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR} {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
] ]
@ -1081,10 +1095,11 @@ class font_patcher:
# Sometimes basic glyphs are constructed from multiple other glyphs. # Sometimes basic glyphs are constructed from multiple other glyphs.
# Find out which other glyphs are also needed to keep the basic # Find out which other glyphs are also needed to keep the basic
# glyphs intact. # glyphs intact.
# 0x00-0x17f is the Latin Extended-A range # 0x0000-0x017f is the Latin Extended-A range
# 0xfb00-0xfb06 are 'fi' and other ligatures
basic_glyphs = set() basic_glyphs = set()
# Collect substitution destinations # Collect substitution destinations
for glyph in range(0x21, 0x17f + 1): for glyph in [*range(0x21, 0x17f + 1), *range(0xfb00, 0xfb06 + 1)]:
if not glyph in self.sourceFont: if not glyph in self.sourceFont:
continue continue
basic_glyphs.add(glyph) basic_glyphs.add(glyph)
@ -1120,8 +1135,8 @@ class font_patcher:
# Try the other metric # Try the other metric
our_btb = typo_btb if not use_typo else win_btb our_btb = typo_btb if not use_typo else win_btb
if our_btb == hhea_btb: if our_btb == hhea_btb:
logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", 'True' if not use_typo else 'False')
use_typo = not use_typo use_typo = not use_typo
logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", repr(use_typo))
self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0 self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0
metrics = Metric.TYPO if use_typo else Metric.WIN metrics = Metric.TYPO if use_typo else Metric.WIN
else: else:
@ -1132,7 +1147,7 @@ class font_patcher:
# print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) # print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname))
self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0} self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0, 'ypadding': 0}
if metrics == Metric.HHEA: if metrics == Metric.HHEA:
self.font_dim['ymin'] = self.sourceFont.hhea_descent - half_gap(self.sourceFont.hhea_linegap, False) self.font_dim['ymin'] = self.sourceFont.hhea_descent - half_gap(self.sourceFont.hhea_linegap, False)
@ -1240,7 +1255,7 @@ class font_patcher:
# font_dim['height'] represents total line height, keep our symbols sized based upon font's em # 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') # Use the font_dim['height'] only for explicit 'y' scaling (not 'pa')
target_height = self.font_dim['height'] target_height = self.font_dim['height'] * (1.0 - self.font_dim['ypadding'])
scale_ratio_y = target_height / sym_dim['height'] scale_ratio_y = target_height / sym_dim['height']
if 'pa' in stretch: if 'pa' in stretch:
@ -1324,6 +1339,9 @@ class font_patcher:
# if currentSourceFontGlyph != 0xe7bd: # if currentSourceFontGlyph != 0xe7bd:
# continue # continue
ypadding = sym_attr['params'].get('ypadding')
self.font_dim['ypadding'] = ypadding or 0.0
if not self.args.quiet: 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))
@ -1333,7 +1351,8 @@ class font_patcher:
sys.stdout.flush() sys.stdout.flush()
# 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: do_careful = sym_attr['params'].get('careful', careful) # params take precedence
if do_careful or currentSourceFontGlyph in self.essential:
if currentSourceFontGlyph in self.sourceFont: if currentSourceFontGlyph in self.sourceFont:
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph) logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph)
@ -1345,14 +1364,15 @@ class font_patcher:
if currentSourceFontGlyph in self.sourceFont: if currentSourceFontGlyph in self.sourceFont:
self.sourceFont[currentSourceFontGlyph].removePosSub("*") self.sourceFont[currentSourceFontGlyph].removePosSub("*")
stretch = sym_attr['stretch']
dont_copy = sym_attr['params'].get('dont_copy') dont_copy = sym_attr['params'].get('dont_copy')
if dont_copy: if dont_copy:
# Just prepare scaling of existing glyphs # Just prepare scaling of existing glyphs
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None
else: else:
# This will destroy any content currently in currentSourceFontGlyph, so do it first # 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 glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, 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
@ -1371,15 +1391,18 @@ class font_patcher:
if glyph_scale_data is not None: if glyph_scale_data is not None:
if glyph_scale_data[1] is not None: if glyph_scale_data[1] is not None:
sym_dim = glyph_scale_data[1] # Use combined bounding box sym_dim = glyph_scale_data[1] # Use combined bounding box
(scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch']) (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch)
else: else:
# This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa')
# Except we do not have glyph_scale_data[1] always... # Except we do not have glyph_scale_data[1] always...
(scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0])
else: else:
(scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch']) (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch)
overlap = sym_attr['params'].get('overlap') overlap = sym_attr['params'].get('overlap')
if overlap and ypadding:
logger.critical("Conflicting params: overlap and ypadding")
sys.exit(1)
if overlap: if overlap:
scale_ratio_x *= 1.0 + (self.font_dim['width'] / (sym_dim['width'] * scale_ratio_x)) * 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 y_overlap = min(0.01, overlap) # never aggressive vertical overlap
@ -1429,7 +1452,7 @@ class font_patcher:
x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2) x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2)
elif sym_attr['align'] == 'r': elif sym_attr['align'] == 'r':
# Right align # Right align
x_align_distance += self.font_dim['width'] * self.get_target_width(sym_attr['stretch']) - sym_dim['width'] x_align_distance += self.font_dim['width'] * self.get_target_width(stretch) - sym_dim['width']
# If symbol glyph is wider than target font cell, just left-align # If symbol glyph is wider than target font cell, just left-align
x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance) x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance)
@ -1442,7 +1465,7 @@ class font_patcher:
x_align_distance -= overlap_width / 2 x_align_distance -= overlap_width / 2
elif sym_attr['align'] == 'r': elif sym_attr['align'] == 'r':
# Check and correct overlap; it can go wrong if we have a xy-ratio limit # Check and correct overlap; it can go wrong if we have a xy-ratio limit
target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(sym_attr['stretch']) target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(stretch)
target_xmax += overlap_width target_xmax += overlap_width
glyph_xmax = sym_dim['xmax'] + x_align_distance glyph_xmax = sym_dim['xmax'] + x_align_distance
correction = target_xmax - glyph_xmax correction = target_xmax - glyph_xmax
@ -1484,8 +1507,8 @@ class font_patcher:
if self.args.single: if self.args.single:
(xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox()
if int(xmax - xmin) > self.font_dim['width'] * (1 + (overlap or 0)): if int(xmax - xmin) > self.font_dim['width'] * (1 + (overlap or 0)):
logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %f))", logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %s))",
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap) currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], repr(overlap))
# end for # end for
@ -1538,7 +1561,7 @@ class font_patcher:
except: except:
pass pass
def prepareScaleRules(self, scaleRules, symbolFont, destGlyph): def prepareScaleRules(self, scaleRules, stretch, symbolFont, destGlyph):
""" Prepare raw ScaleRules data for use """ """ Prepare raw ScaleRules data for use """
# The scaleRules is/will be a dict with these (possible) entries: # 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 # 'ScaleGroups': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled
@ -1572,7 +1595,7 @@ class font_patcher:
scaleRules['ScaleGroups'] = [] scaleRules['ScaleGroups'] = []
for group in 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) 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] scale = self.get_scale_factors(sym_dim, stretch)[0]
scaleRules['scales'].append(scale) scaleRules['scales'].append(scale)
scaleRules['bbdims'].append(sym_dim) scaleRules['bbdims'].append(sym_dim)
@ -1591,7 +1614,7 @@ class font_patcher:
else: else:
group_list.append(i) group_list.append(i)
sym_dim = get_glyph_dimensions(symbolFont[scaleRules['ScaleGlyph']]) sym_dim = get_glyph_dimensions(symbolFont[scaleRules['ScaleGlyph']])
scale = self.get_scale_factors(sym_dim, 'pa')[0] scale = self.get_scale_factors(sym_dim, stretch)[0]
scaleRules['ScaleGroups'].append(group_list) scaleRules['ScaleGroups'].append(group_list)
scaleRules['scales'].append(scale) scaleRules['scales'].append(scale)
if plus: if plus:
@ -1599,13 +1622,13 @@ class font_patcher:
else: else:
scaleRules['bbdims'].append(None) # The 'old' style keeps just the scale, not the positioning 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): def get_glyph_scale(self, symbol_unicode, scaleRules, stretch, symbolFont, dest_unicode):
""" Determines whether or not to use scaled glyphs for glyph in passed symbol_unicode """ """ Determines whether or not to use scaled glyphs for glyph in passed symbol_unicode """
# Potentially destorys the contents of self.sourceFont[dest_unicode] # Potentially destorys the contents of self.sourceFont[dest_unicode]
if not 'scales' in scaleRules: if not 'scales' in scaleRules:
if not dest_unicode in self.sourceFont: if not dest_unicode in self.sourceFont:
self.sourceFont.createChar(dest_unicode) self.sourceFont.createChar(dest_unicode)
self.prepareScaleRules(scaleRules, symbolFont, self.sourceFont[dest_unicode]) self.prepareScaleRules(scaleRules, stretch, symbolFont, self.sourceFont[dest_unicode])
for glyph_list, scale, box in zip(scaleRules['ScaleGroups'], scaleRules['scales'], scaleRules['bbdims']): for glyph_list, scale, box in zip(scaleRules['ScaleGroups'], scaleRules['scales'], scaleRules['bbdims']):
for e in glyph_list: for e in glyph_list:
if isinstance(e, range): if isinstance(e, range):
@ -1805,7 +1828,7 @@ def setup_arguments():
parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file') parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file')
parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing') parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing')
parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)') parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)')
parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font. All new glyphs will be copied, with no scaling applied.') parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font, all glyphs will be copied; absolute path suggested')
parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)') parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)')
parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to')
parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching') parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching')
@ -1829,7 +1852,7 @@ def setup_arguments():
progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)') progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)')
progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
parser.set_defaults(progressbars=True) parser.set_defaults(progressbars=True)
parser.add_argument('--debug', dest='debugmode', default=False, action='store_true', help='Verbose mode') parser.add_argument('--debug', dest='debugmode', default=0, type=int, nargs='?', help='Verbose mode (optional: 1=just to file; 2*=just to terminal; 3=display and file)', const=2, choices=range(0, 3 + 1))
parser.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming') parser.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming')
parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True) parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True)
# --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts # --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts
@ -1952,16 +1975,23 @@ def main():
global logger global logger
logger = logging.getLogger(os.path.basename(args.font)) logger = logging.getLogger(os.path.basename(args.font))
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
f_handler = logging.FileHandler('font-patcher-log.txt') log_to_file = (args.debugmode & 1 == 1)
f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s')) if log_to_file:
logger.addHandler(f_handler) try:
logger.debug(allversions) f_handler = logging.FileHandler('font-patcher-log.txt')
logger.debug("Options %s", repr(sys.argv[1:])) f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s'))
logger.addHandler(f_handler)
except:
log_to_file = False
logger.debug(allversions)
logger.debug("Options %s", repr(sys.argv[1:]))
c_handler = logging.StreamHandler(stream=sys.stdout) c_handler = logging.StreamHandler(stream=sys.stdout)
c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
if not args.debugmode: if not (args.debugmode & 2 == 2):
c_handler.setLevel(logging.INFO) c_handler.setLevel(logging.INFO)
logger.addHandler(c_handler) logger.addHandler(c_handler)
if (args.debugmode & 1 == 1) and not log_to_file:
logger.info("Can not write logfile, disabling")
logger.debug("Naming mode %d", args.makegroups) logger.debug("Naming mode %d", args.makegroups)
patcher = font_patcher(args) patcher = font_patcher(args)

View file

@ -14,10 +14,10 @@ import fontforge
octi_orig = "octicons.ttf" octi_orig = "octicons.ttf"
current_cp = 0xF400 current_cp = 0xF400
print("# Examining {}".format(octi_orig)) print('# Examining {}'.format(octi_orig))
font = fontforge.open(octi_orig) font = fontforge.open(octi_orig)
for glyph in font.glyphs("encoding"): for glyph in font.glyphs('encoding'):
point = glyph.unicode point = glyph.unicode
if point < 0: if point < 0:
continue continue

View file

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m1.75 1c-0.966 0-1.75 0.784-1.75 1.75v1.75c0 1 1.5 1 1.5 0v-1.75c0-0.138 0.112-0.25 0.25-0.25h3.25c0.0783 1.53e-4 0.152 0.037 0.199 0.0996l0.9 1.2c0.33 0.44 0.85 0.699 1.4 0.699h6.75c0.138 0 0.25 0.112 0.25 0.25v8.5c0 0.138-0.112 0.25-0.25 0.25h-9c-1 0-1 1.5 0 1.5h9c0.966 0 1.75-0.784 1.75-1.75v-8.5c0-0.966-0.784-1.75-1.75-1.75h-6.75c-0.0783-1.53e-4 -0.152-0.037-0.199-0.0996l-0.9-1.2c-0.33-0.44-0.85-0.699-1.4-0.699zm2.49 5.32c-0.135 0.00422-0.242 0.115-0.242 0.25v1.92h-0.23c-2.08-0.0119-3.77 1.67-3.77 3.75v1.01c1.63e-4 1 1.5 1 1.5 0v-1.01c-1.69e-5 -1.25 1.01-2.26 2.26-2.25l0.238 0.00195v1.94c0.0015 0.217 0.261 0.329 0.42 0.182l2.88-2.68c0.107-0.099 0.107-0.268 0-0.367l-2.88-2.68c-0.0482-0.0446-0.112-0.0685-0.178-0.0664z"/></svg>

After

Width:  |  Height:  |  Size: 845 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m3.75 3c-0.966 0-1.75 0.784-1.75 1.75v4.74c0 1 1.5 1 1.5 0v-4.74c0-0.138 0.112-0.25 0.25-0.25h4.97c0.0828 2.38e-4 0.16 0.0411 0.207 0.109l1.4 2.06c0.326 0.479 0.868 0.766 1.45 0.766h8.47c0.138 0 0.25 0.112 0.25 0.25v11.6c0 0.138-0.112 0.25-0.25 0.25h-13.5c-1 0-1 1.5 0 1.5h13.5c0.966 0 1.75-0.784 1.75-1.75v-11.6c0-0.967-0.784-1.75-1.75-1.75h-8.47c-0.083 0-0.161-0.0404-0.207-0.109l-1.4-2.06c-0.327-0.479-0.867-0.766-1.45-0.766zm5.95 5.99c-0.0441 0.00229-0.0877 0.00751-0.131 0.0176-0.569 0.132-0.775 0.835-0.369 1.25l2.11 2.24h-5.22c-2.26 8.6e-5 -4.09 1.83-4.09 4.09v3.16c0 1 1.5 1 1.5 0v-3.16c8.6e-5 -1.43 1.16-2.59 2.59-2.59h5.22l-2.11 2.24c-0.188 0.194-0.258 0.475-0.182 0.734 0.164 0.564 0.882 0.729 1.28 0.293l3.29-3.5c0.271-0.289 0.271-0.739 0-1.03l-3.29-3.5c-0.12-0.133-0.284-0.218-0.461-0.24-0.044-0.00555-0.0887-0.00619-0.133-0.00391z"/></svg>

After

Width:  |  Height:  |  Size: 960 B

View file

@ -8,95 +8,81 @@ import subprocess
import fontforge import fontforge
# Double-quotes required here, for version-bump.sh: # Double-quotes required here, for version-bump.sh:
version = "2.3.3" # version-bump.sh is not working here, need to adjust manually!
version = "3.0.0"
archive = "v18.3.0.tar.gz" archive = 'v18.3.0.tar.gz'
vectorsdir = "icons" vectorsdir = 'icons'
fontdir = "." fontdir = '.'
fontfile = "octicons.ttf" fontfile = 'octicons.ttf'
glyphsetfile = "i_oct.sh" glyphsetfile = 'i_oct.sh'
glyphsetsdir = "../../../bin/scripts/lib" glyphsetsdir = '../../../bin/scripts/lib'
subset = "-16" # use 16 px subset if possible
subset_other = "-24" # use 24 px subset otherwise
subset = '-16' # use 16 px subset if possible
subset_other = '-24' # use 24 px subset otherwise
def renamer(old_name): def renamer(old_name):
"""Return new equivalent icon name""" """ Return new equivalent icon name """
return { return {
"trashcan": "trash", 'trashcan' : 'trash',
"cloud-download": "download", 'cloud-download' : 'download',
"cloud-upload": "upload", 'cloud-upload' : 'upload',
"clippy": "paste", 'clippy' : 'paste',
"mail-read": "read", 'mail-read' : 'read',
"primitive-dot": "dot-fill", 'primitive-dot' : 'dot-fill',
"primitive-square": "square-fill", 'primitive-square' : 'square-fill',
"settings": "sliders", 'settings' : 'sliders',
"dashboard": "meter", 'dashboard' : 'meter',
"paintcan": "paintbrush", 'paintcan' : 'paintbrush',
}.get(old_name, old_name) }.get(old_name, old_name)
def addIcon(codepoint, name, filename): def addIcon(codepoint, name, filename):
"""Add one outline file and rescale/move""" """ Add one outline file and rescale/move """
dBB = [120, 0, 1000 - 120, 900] # just some nice sizes dBB = [120, 0, 1000-120, 900] # just some nice sizes
filename = os.path.join(vectorsdir, filename) filename = os.path.join(vectorsdir, filename)
glyph = font.createChar(codepoint, name) glyph = font.createChar(codepoint, name)
glyph.importOutlines(filename) glyph.importOutlines(filename)
glyph.manualHints = True glyph.manualHints = True
def createGlyphInfo(icon_datasets, filepathname, into): def createGlyphInfo(icon_datasets, filepathname, into):
"""Write the glyphinfo file""" """ Write the glyphinfo file """
with open(filepathname, "w", encoding="utf8") as f: with open(filepathname, 'w', encoding = 'utf8') as f:
f.write("#!/usr/bin/env bash\n") f.write(u'#!/usr/bin/env bash\n')
f.write(intro) f.write(intro)
f.write("# Script Version: (autogenerated)\n") f.write(u'# Script Version: (autogenerated)\n')
f.write('test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n') f.write(u'test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
for _, codepoint, name in icon_datasets: for _, codepoint, name in icon_datasets:
codepoint = int(codepoint, 16) codepoint = int(codepoint, 16)
f.write( f.write(u"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace('-', '_')))
"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace("-", "_")) f.write(u'unset i\n')
)
f.write("unset i\n")
print('\nReading mapping file')
print("\nReading mapping file")
old_mapping = [] old_mapping = []
with open("mapping", "r") as f: with open('mapping', 'r') as f:
for line in f.readlines(): for line in f.readlines():
if line.startswith("#"): if line.startswith('#'):
continue continue
old_mapping.append(tuple(re.split(" +", line.strip()))) old_mapping.append(tuple(re.split(' +', line.strip())))
print("Found {} entries".format(len(old_mapping))) print('Found {} entries'.format(len(old_mapping)))
old_mapping.sort(key=(lambda x: x[0])) old_mapping.sort(key=(lambda x: x[0]))
print('Fetching octicons archive "{}"\n'.format(archive)) print('Fetching octicons archive "{}"\n'.format(archive))
if subprocess.call( if subprocess.call('curl -OL https://github.com/primer/octicons/archive/' + archive, shell=True):
"curl -OL https://github.com/primer/octicons/archive/" + archive, shell=True sys.exit('Error fetching octicons archive')
): print('\nUnpacking octicons archive')
sys.exit("Error fetching octicons archive") if subprocess.call('rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-* && cp file-symlink-directory-*.svg icons', shell=True):
print("\nUnpacking octicons archive") sys.exit('Error unpacking archive')
if subprocess.call(
"rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-*",
shell=True,
):
sys.exit("Error unpacking archive")
svgs = os.listdir(vectorsdir) svgs = os.listdir(vectorsdir)
print("Found {} svgs".format(len(svgs))) print('Found {} svgs'.format(len(svgs)))
names = { names = { s[0:-len('-xx.svg')] for s in svgs if s.endswith(subset + '.svg') or s.endswith(subset_other + '.svg') }
s[0 : -len("-xx.svg")] print('Found {} icons after de-duplicating\n'.format(len(names)))
for s in svgs
if s.endswith(subset + ".svg") or s.endswith(subset_other + ".svg")
}
print("Found {} icons after de-duplicating\n".format(len(names)))
num_found = 0 num_found = 0
num_missing = 0 num_missing = 0
misslist = "" misslist = ''
renamelist = "" renamelist = ''
freeslots = [] freeslots = []
new_mapping = [] new_mapping = []
@ -108,22 +94,18 @@ for i, j, old_n in old_mapping:
continue continue
new_n = renamer(old_n) new_n = renamer(old_n)
if new_n in names: if new_n in names:
renamelist += "Renamed {} -> {}\n".format(old_n, new_n) renamelist += 'Renamed {} -> {}\n'.format(old_n, new_n)
names.remove(new_n) names.remove(new_n)
new_mapping.append((i, j, new_n)) new_mapping.append((i, j, new_n))
num_found += 1 num_found += 1
continue continue
misslist += "Missing {}\n".format(old_n) misslist += 'Missing {}\n'.format(old_n)
freeslots.append((i, j)) freeslots.append((i, j))
num_missing += 1 num_missing += 1
print(renamelist) print(renamelist)
print(misslist) print(misslist)
print( print('Found {} (of {}, missing {}) and new {}'.format(num_found, len(old_mapping), num_missing, len(names)))
"Found {} (of {}, missing {}) and new {}".format(
num_found, len(old_mapping), num_missing, len(names)
)
)
names = list(names) names = list(names)
names.sort() names.sort()
@ -135,7 +117,7 @@ for n in list(names):
names.remove(n) names.remove(n)
freeslots = freeslots[1:] freeslots = freeslots[1:]
print("Filled in missing, remaining new {}".format(len(names))) print('Filled in missing, remaining new {}'.format(len(names)))
i_max = 0 i_max = 0
j_max = 0 j_max = 0
@ -150,52 +132,50 @@ for i, j, _ in new_mapping:
for n in names: for n in names:
i_max += 1 i_max += 1
j_max += 1 j_max += 1
new_mapping.append(("{:X}".format(i_max), "{:X}".format(j_max), n)) new_mapping.append(('{:X}'.format(i_max), '{:X}'.format(j_max), n))
print("Appended remaining new, total new mapping {}".format(len(new_mapping))) print('Appended remaining new, total new mapping {}'.format(len(new_mapping)))
new_mapping.sort(key=(lambda x: x[0])) new_mapping.sort(key=(lambda x: x[0]))
with open("mapping", "w") as f: with open('mapping', 'w') as f:
for i, j, n in new_mapping: for i, j, n in new_mapping:
f.write("{} {} {}\n".format(i, j, n)) f.write('{} {} {}\n'.format(i, j, n))
font = fontforge.font() font = fontforge.font()
font.fontname = "OcticonsNerdFont-Regular" font.fontname = 'OcticonsNerdFont-Regular'
font.fullname = "Octicons Nerd Font Regular" font.fullname = 'Octicons Nerd Font Regular'
font.familyname = "Octicons Nerd Font" font.familyname = 'Octicons Nerd Font'
font.em = 2048 font.em = 2048
font.encoding = "UnicodeFull" font.encoding = 'UnicodeFull'
# Add valid space glyph to avoid "unknown character" box on IE11 # Add valid space glyph to avoid "unknown character" box on IE11
glyph = font.createChar(32) glyph = font.createChar(32)
glyph.width = 200 glyph.width = 200
font.sfntRevision = None # Auto-set (refreshed) by fontforge font.sfntRevision = None # Auto-set (refreshed) by fontforge
font.version = version font.version = version
font.copyright = "GitHub Inc." font.copyright = 'GitHub Inc.'
font.appendSFNTName("English (US)", "Version", archive + "; " + version) font.appendSFNTName('English (US)', 'Version', archive + '; ' + version)
font.appendSFNTName( font.appendSFNTName('English (US)', 'Vendor URL', 'https://github.com/ryanoasis/nerd-fonts')
"English (US)", "Vendor URL", "https://github.com/ryanoasis/nerd-fonts" font.appendSFNTName('English (US)', 'Copyright', 'GitHub Inc.')
)
font.appendSFNTName("English (US)", "Copyright", "GitHub Inc.")
for codepoint, _, name in new_mapping: for codepoint, _, name in new_mapping:
codepoint = int(codepoint, 16) codepoint = int(codepoint, 16)
filename = name + subset + ".svg" filename = name + subset + '.svg'
if filename not in svgs: if filename not in svgs:
filename = name + subset_other + ".svg" filename = name + subset_other + '.svg'
addIcon(codepoint, name, filename) addIcon(codepoint, name, filename)
num_icons = len(new_mapping) num_icons = len(new_mapping)
print("Generating {} with {} glyphs".format(fontfile, num_icons)) print('Generating {} with {} glyphs'.format(fontfile, num_icons))
font.generate(os.path.join(fontdir, fontfile), flags=("no-FFTM-table",)) font.generate(os.path.join(fontdir, fontfile), flags=("no-FFTM-table",))
codepoints = [int(p, 16) for _, p, _ in new_mapping] codepoints = [ int(p, 16) for _, p, _ in new_mapping ]
intro = "# Octicons ({} icons)\n".format(num_icons) intro = u'# Octicons ({} icons)\n'.format(num_icons)
intro += "# Codepoints: {:X}-{:X} with gaps\n".format(min(codepoints), max(codepoints)) intro += u'# Codepoints: {:X}-{:X} with gaps\n'.format(min(codepoints), max(codepoints))
intro += "# Nerd Fonts Version: {}\n".format(version) intro += u'# Nerd Fonts Version: {}\n'.format(version)
print("Generating GlyphInfo {}".format(glyphsetfile)) print('Generating GlyphInfo {}'.format(glyphsetfile))
createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro) createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro)
print("Finished") print('Finished')

View file

@ -130,7 +130,7 @@ F0AA F47E triangle-up
F0AC F47F git-compare F0AC F47F git-compare
F0AD F480 logo-gist F0AD F480 logo-gist
F0B0 F481 file-symlink-file F0B0 F481 file-symlink-file
F0B1 F482 bookmark-slash F0B1 F482 file-symlink-directory
F0B2 F483 squirrel F0B2 F483 squirrel
F0B6 F484 globe F0B6 F484 globe
F0BA F485 unmute F0BA F485 unmute
@ -307,3 +307,4 @@ F302 F52F x-circle
F303 F530 x-circle-fill F303 F530 x-circle-fill
F304 F531 zoom-in F304 F531 zoom-in
F305 F532 zoom-out F305 F532 zoom-out
F306 F533 bookmark-slash

Binary file not shown.

Binary file not shown.

View file

@ -433,3 +433,4 @@ Foobar.org is a distributed community of developers...
Company.com is a small business who likes to support community designers... Company.com is a small business who likes to support community designers...
University.edu is a renowned educational institution with a strong design department... University.edu is a renowned educational institution with a strong design department...
----- -----