mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2024-11-13 18:07:52 -06:00
chore: change batteries
This commit is contained in:
parent
2d153f57c0
commit
a4d4640f01
14 changed files with 431 additions and 560 deletions
|
@ -4,32 +4,20 @@
|
|||
import re
|
||||
from FontnameTools import FontnameTools
|
||||
|
||||
|
||||
class FontnameParser:
|
||||
"""Parse a font name and generate all kinds of names"""
|
||||
|
||||
def __init__(self, filename, logger):
|
||||
"""Parse a font filename and store the results"""
|
||||
self.parse_ok = False
|
||||
self.use_short_families = (
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
) # ( camelcase name, short styles, aggressive )
|
||||
self.use_short_families = (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.family_suff = ""
|
||||
self.ps_fontname_suff = ""
|
||||
self.short_family_suff = ""
|
||||
self.family_suff = ''
|
||||
self.ps_fontname_suff = ''
|
||||
self.short_family_suff = ''
|
||||
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.rest = self._rest
|
||||
self.add_name_substitution_table(FontnameTools.SIL_TABLE)
|
||||
|
@ -38,25 +26,21 @@ class FontnameParser:
|
|||
|
||||
def _make_ps_name(self, n, is_family):
|
||||
"""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
|
||||
if len(n) <= limit:
|
||||
return n
|
||||
r = re.search("(.*)(-.*)", n)
|
||||
r = re.search('(.*)(-.*)', n)
|
||||
if not r:
|
||||
new_n = n[:limit]
|
||||
else:
|
||||
q = limit - len(r.groups()[1])
|
||||
if q < 1:
|
||||
q = 1
|
||||
self.logger.error(
|
||||
"====-< Shortening too long PS {}name: Garbage warning".format(fam)
|
||||
)
|
||||
self.logger.error('====-< Shortening too long PS {}name: Garbage warning'. format(fam))
|
||||
new_n = r.groups()[0][:q] + r.groups()[1]
|
||||
if new_n != n:
|
||||
self.logger.error(
|
||||
"====-< Shortening too long PS {}name: {} -> {}".format(fam, n, new_n)
|
||||
)
|
||||
self.logger.error('====-< Shortening too long PS {}name: {} -> {}'.format(fam, n, new_n))
|
||||
return new_n
|
||||
|
||||
def _shortened_name(self):
|
||||
|
@ -64,7 +48,7 @@ class FontnameParser:
|
|||
if not self.use_short_families[0]:
|
||||
return (self.basename, self.rest)
|
||||
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):
|
||||
"""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
|
||||
# font file level but needs to be specified per family from the outside.
|
||||
# Returns true if setting was successful.
|
||||
if "Italic" in self.style_token:
|
||||
if 'Italic' in self.style_token:
|
||||
self.rename_oblique = True
|
||||
return not noitalic
|
||||
self.rename_oblique = not noitalic
|
||||
|
@ -92,7 +76,7 @@ class FontnameParser:
|
|||
def inject_suffix(self, family, ps_fontname, short_family):
|
||||
"""Add a custom additonal string that shows up in the resulting names"""
|
||||
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()
|
||||
return self
|
||||
|
||||
|
@ -102,7 +86,7 @@ class FontnameParser:
|
|||
# prefix is either a string or False/True
|
||||
if isinstance(prefix, str):
|
||||
prefix = self._basename.startswith(prefix)
|
||||
self.use_short_families = (camelcase_name, prefix, aggressive)
|
||||
self.use_short_families = ( camelcase_name, prefix, aggressive )
|
||||
return self
|
||||
|
||||
def add_name_substitution_table(self, table):
|
||||
|
@ -113,28 +97,26 @@ class FontnameParser:
|
|||
self.basename = self._basename
|
||||
self.rest = self._rest
|
||||
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)
|
||||
if not m:
|
||||
continue
|
||||
i = len(self.basename) - len(m.group(0))
|
||||
if i < 0:
|
||||
self.basename = m.expand(replacement).rstrip()
|
||||
self.rest = self.rest[-(i + 1) :].lstrip()
|
||||
self.rest = self.rest[-(i+1):].lstrip()
|
||||
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
|
||||
|
||||
def drop_for_powerline(self):
|
||||
"""Remove 'for Powerline' from all names (can not be undone)"""
|
||||
if "Powerline" in self.other_token:
|
||||
idx = self.other_token.index("Powerline")
|
||||
if 'Powerline' in self.other_token:
|
||||
idx = self.other_token.index('Powerline')
|
||||
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._basename = re.sub(
|
||||
r"(\b|for\s?)?powerline\b", "", self._basename, 1, re.IGNORECASE
|
||||
).strip()
|
||||
self._basename = re.sub(r'(\b|for\s?)?powerline\b', '', self._basename, 1, re.IGNORECASE).strip()
|
||||
self.add_name_substitution_table(self.name_subst) # re-evaluate
|
||||
return self
|
||||
|
||||
|
@ -166,26 +148,20 @@ class FontnameParser:
|
|||
styles = self.style_token
|
||||
weights = self.weight_token
|
||||
if self.keep_regular_in_family == None:
|
||||
keep_regular = FontnameTools.is_keep_regular(
|
||||
self._basename + " " + self._rest
|
||||
)
|
||||
keep_regular = FontnameTools.is_keep_regular(self._basename + ' ' + self._rest)
|
||||
else:
|
||||
keep_regular = self.keep_regular_in_family
|
||||
if "Regular" in styles and (
|
||||
not keep_regular or len(self.weight_token) > 0
|
||||
): # This is actually a malformed font name
|
||||
if ('Regular' in styles
|
||||
and (not keep_regular
|
||||
or len(self.weight_token) > 0)): # This is actually a malformed font name
|
||||
styles = list(self.style_token)
|
||||
styles.remove("Regular")
|
||||
styles.remove('Regular')
|
||||
# For naming purposes we want Oblique to be part of the styles
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||
(name, rest) = self._shortened_name()
|
||||
if self.use_short_families[1]:
|
||||
[weights, styles] = FontnameTools.short_styles(
|
||||
[weights, styles], self.use_short_families[2]
|
||||
)
|
||||
return FontnameTools.concat(
|
||||
name, rest, self.other_token, self.short_family_suff, weights, styles
|
||||
)
|
||||
[ weights, styles ] = FontnameTools.short_styles([ weights, styles ], self.use_short_families[2])
|
||||
return FontnameTools.concat(name, rest, self.other_token, self.short_family_suff, weights, styles)
|
||||
|
||||
def psname(self):
|
||||
"""Get the SFNT PostScriptName (ID 6)"""
|
||||
|
@ -196,12 +172,10 @@ class FontnameParser:
|
|||
if self.use_short_families[1]:
|
||||
styles = FontnameTools.short_styles(styles, self.use_short_families[2])
|
||||
weights = FontnameTools.short_styles(weights, self.use_short_families[2])
|
||||
fam = FontnameTools.camel_casify(
|
||||
FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff)
|
||||
)
|
||||
fam = FontnameTools.camel_casify(FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff))
|
||||
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
|
||||
if len(sub) > 0:
|
||||
sub = "-" + sub
|
||||
sub = '-' + sub
|
||||
fam = FontnameTools.postscript_char_filter(fam)
|
||||
sub = FontnameTools.postscript_char_filter(sub)
|
||||
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)
|
||||
if self.suppress_preferred_if_identical and pfn == self.family():
|
||||
# Do not set if identical to ID 1
|
||||
return ""
|
||||
return ''
|
||||
return pfn
|
||||
|
||||
def preferred_styles(self):
|
||||
|
@ -224,7 +198,7 @@ class FontnameParser:
|
|||
pfs = FontnameTools.concat(weights, styles)
|
||||
if self.suppress_preferred_if_identical and pfs == self.subfamily():
|
||||
# Do not set if identical to ID 2
|
||||
return ""
|
||||
return ''
|
||||
return pfs
|
||||
|
||||
def family(self):
|
||||
|
@ -237,8 +211,8 @@ class FontnameParser:
|
|||
if not self.rename_oblique:
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, [])
|
||||
if self.use_short_families[1]:
|
||||
[other, weights] = FontnameTools.short_styles([other, weights], aggressive)
|
||||
weights = [w if w != "Oblique" else "Obl" for w in weights]
|
||||
[ other, weights ] = FontnameTools.short_styles([ other, weights ], aggressive)
|
||||
weights = [ w if w != 'Oblique' else 'Obl' for w in weights ]
|
||||
return FontnameTools.concat(name, rest, other, self.short_family_suff, weights)
|
||||
|
||||
def subfamily(self):
|
||||
|
@ -248,11 +222,11 @@ class FontnameParser:
|
|||
if not self.rename_oblique:
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||
if len(styles) == 0:
|
||||
if "Oblique" in weights:
|
||||
return FontnameTools.concat(styles, "Italic")
|
||||
return "Regular"
|
||||
if "Oblique" in weights and not "Italic" in styles:
|
||||
return FontnameTools.concat(styles, "Italic")
|
||||
if 'Oblique' in weights:
|
||||
return FontnameTools.concat(styles, 'Italic')
|
||||
return 'Regular'
|
||||
if 'Oblique' in weights and not 'Italic' in styles:
|
||||
return FontnameTools.concat(styles, 'Italic')
|
||||
return FontnameTools.concat(styles)
|
||||
|
||||
def ps_familyname(self):
|
||||
|
@ -265,27 +239,23 @@ class FontnameParser:
|
|||
def macstyle(self, style):
|
||||
"""Modify a given macStyle value for current name, just bits 0 and 1 touched"""
|
||||
b = style & (~3)
|
||||
b |= 1 if "Bold" in self.style_token else 0
|
||||
b |= 2 if "Italic" 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
|
||||
return b
|
||||
|
||||
def fs_selection(self, fs):
|
||||
"""Modify a given fsSelection value for current name, bits 0, 5, 6, 8, 9 touched"""
|
||||
ITALIC = 1 << 0
|
||||
BOLD = 1 << 5
|
||||
REGULAR = 1 << 6
|
||||
WWS = 1 << 8
|
||||
OBLIQUE = 1 << 9
|
||||
ITALIC = 1 << 0; BOLD = 1 << 5; REGULAR = 1 << 6; WWS = 1 << 8; OBLIQUE = 1 << 9
|
||||
b = fs & (~(ITALIC | BOLD | REGULAR | WWS | OBLIQUE))
|
||||
if "Bold" in self.style_token:
|
||||
if 'Bold' in self.style_token:
|
||||
b |= BOLD
|
||||
# Ignore Italic if we have Oblique
|
||||
if "Oblique" in self.weight_token:
|
||||
if 'Oblique' in self.weight_token:
|
||||
b |= OBLIQUE
|
||||
elif "Italic" in self.style_token:
|
||||
elif 'Italic' in self.style_token:
|
||||
b |= ITALIC
|
||||
# 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 |= WWS # We assert this by our naming process
|
||||
return b
|
||||
|
@ -293,17 +263,9 @@ class FontnameParser:
|
|||
def checklen(self, max_len, entry_id, name):
|
||||
"""Check the length of a name string and report violations"""
|
||||
if len(name) <= max_len:
|
||||
self.logger.debug(
|
||||
"=====> {:18} ok ({:2} <={:2}): {}".format(
|
||||
entry_id, len(name), max_len, name
|
||||
)
|
||||
)
|
||||
self.logger.debug('=====> {:18} ok ({:2} <={:2}): {}'.format(entry_id, len(name), max_len, name))
|
||||
else:
|
||||
self.logger.error(
|
||||
"====-< {:18} too long ({:2} > {:2}): {}".format(
|
||||
entry_id, len(name), max_len, name
|
||||
)
|
||||
)
|
||||
self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, name))
|
||||
return name
|
||||
|
||||
def rename_font(self, font):
|
||||
|
@ -339,78 +301,32 @@ class FontnameParser:
|
|||
# and it is actually embedded as empty string, but empty strings are not
|
||||
# shown if you query the sfnt_names *rolleyes*
|
||||
|
||||
version_tag = ""
|
||||
version_tag = ''
|
||||
sfnt_list = []
|
||||
TO_DEL = [
|
||||
"Family",
|
||||
"SubFamily",
|
||||
"Fullname",
|
||||
"PostScriptName",
|
||||
"Preferred Family",
|
||||
"Preferred Styles",
|
||||
"Compatible Full",
|
||||
"WWS Family",
|
||||
"WWS Subfamily",
|
||||
"UniqueID",
|
||||
"CID findfont Name",
|
||||
]
|
||||
TO_DEL = ['Family', 'SubFamily', '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
|
||||
# back, but only as 'English (US)'. This makes sure we do not leave contradicting
|
||||
# names over different languages.
|
||||
for l, k, v in list(font.sfnt_names):
|
||||
if not k in TO_DEL:
|
||||
sfnt_list += [(l, k, v)]
|
||||
if k == "Version" and l == "English (US)":
|
||||
version_tag = " " + v.split()[-1]
|
||||
sfnt_list += [( l, k, v )]
|
||||
if k == 'Version' and l == 'English (US)':
|
||||
version_tag = ' ' + v.split()[-1]
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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()
|
||||
if len(p_fam):
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Preferred Family",
|
||||
self.checklen(31, "PrefFamily (ID 16)", p_fam),
|
||||
)
|
||||
] # 16
|
||||
sfnt_list += [( 'English (US)', 'Preferred Family', self.checklen(31, 'PrefFamily (ID 16)', p_fam) )] # 16
|
||||
p_sty = self.preferred_styles()
|
||||
if len(p_sty):
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Preferred Styles",
|
||||
self.checklen(31, "PrefStyles (ID 17)", p_sty),
|
||||
)
|
||||
] # 17
|
||||
sfnt_list += [( 'English (US)', 'Preferred Styles', self.checklen(31, 'PrefStyles (ID 17)', p_sty) )] # 17
|
||||
|
||||
font.sfnt_names = tuple(sfnt_list)
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class FontnameTools:
|
||||
"""Deconstruct a font filename to get standardized name parts"""
|
||||
|
||||
|
@ -16,32 +15,32 @@ class FontnameTools:
|
|||
@staticmethod
|
||||
def camel_casify(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
|
||||
def camel_explode(word):
|
||||
"""Explode CamelCase -> Camel Case"""
|
||||
# But do not explode "JetBrains" etc at string start...
|
||||
excludes = [
|
||||
"JetBrains",
|
||||
"DejaVu",
|
||||
"OpenDyslexicAlta",
|
||||
"OpenDyslexicMono",
|
||||
"OpenDyslexic",
|
||||
"DaddyTimeMono",
|
||||
"InconsolataGo",
|
||||
"ProFontWindows",
|
||||
"ProFont",
|
||||
"ProggyClean",
|
||||
'JetBrains',
|
||||
'DejaVu',
|
||||
'OpenDyslexicAlta',
|
||||
'OpenDyslexicMono',
|
||||
'OpenDyslexic',
|
||||
'DaddyTimeMono',
|
||||
'InconsolataGo',
|
||||
'ProFontWindows',
|
||||
'ProFont',
|
||||
'ProggyClean',
|
||||
]
|
||||
m = re.match("(" + "|".join(excludes) + ")(.*)", word)
|
||||
(prefix, word) = m.group(1, 2) if m != None else ("", word)
|
||||
m = re.match('(' + '|'.join(excludes) + ')(.*)', word)
|
||||
(prefix, word) = m.group(1,2) if m != None else ('', word)
|
||||
if len(word) == 0:
|
||||
return prefix
|
||||
parts = re.split("(?<=[a-z0-9])(?=[A-Z])", word)
|
||||
parts = re.split('(?<=[a-z0-9])(?=[A-Z])', word)
|
||||
if len(prefix):
|
||||
parts.insert(0, prefix)
|
||||
return " ".join(parts)
|
||||
return ' '.join(parts)
|
||||
|
||||
@staticmethod
|
||||
def drop_empty(l):
|
||||
|
@ -57,7 +56,7 @@ class FontnameTools:
|
|||
all.append(thing)
|
||||
else:
|
||||
all += thing
|
||||
return " ".join(FontnameTools.drop_empty(all))
|
||||
return ' '.join(FontnameTools.drop_empty(all))
|
||||
|
||||
@staticmethod
|
||||
def unify_style_names(style_name):
|
||||
|
@ -65,20 +64,20 @@ class FontnameTools:
|
|||
known_names = {
|
||||
# Source of the table is the current sourcefonts
|
||||
# Left side needs to be lower case
|
||||
"-": "",
|
||||
"book": "",
|
||||
"text": "",
|
||||
"ce": "CE",
|
||||
'-': '',
|
||||
'book': '',
|
||||
'text': '',
|
||||
'ce': 'CE',
|
||||
#'semibold': 'Demi',
|
||||
"ob": "Oblique",
|
||||
"it": "Italic",
|
||||
"i": "Italic",
|
||||
"b": "Bold",
|
||||
"normal": "Regular",
|
||||
"c": "Condensed",
|
||||
"r": "Regular",
|
||||
"m": "Medium",
|
||||
"l": "Light",
|
||||
'ob': 'Oblique',
|
||||
'it': 'Italic',
|
||||
'i': 'Italic',
|
||||
'b': 'Bold',
|
||||
'normal': 'Regular',
|
||||
'c': 'Condensed',
|
||||
'r': 'Regular',
|
||||
'm': 'Medium',
|
||||
'l': 'Light',
|
||||
}
|
||||
if style_name in known_names:
|
||||
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"""
|
||||
for i, d in enumerate(dicts):
|
||||
if key in d:
|
||||
return (d[key], i)
|
||||
return ( d[key], i )
|
||||
return (None, 0)
|
||||
|
||||
@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 second form of widths
|
||||
name_rest = name
|
||||
name_pre = ""
|
||||
form = FontnameTools.get_shorten_form_idx(aggressive, "", 0)
|
||||
name_pre = ''
|
||||
form = FontnameTools.get_shorten_form_idx(aggressive, '', 0)
|
||||
for mod in FontnameTools.known_modifiers:
|
||||
if name.startswith(mod) and len(name) > len(
|
||||
mod
|
||||
): # Second condition specifically for 'Demi'
|
||||
if name.startswith(mod) and len(name) > len(mod): # Second condition specifically for 'Demi'
|
||||
name_pre = FontnameTools.known_modifiers[mod][form]
|
||||
name_rest = name[len(mod) :]
|
||||
name_rest = name[len(mod):]
|
||||
break
|
||||
subst, i = FontnameTools.find_in_dicts(
|
||||
name_rest, [FontnameTools.known_weights2, FontnameTools.known_widths]
|
||||
)
|
||||
subst, i = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights2, FontnameTools.known_widths ])
|
||||
form = FontnameTools.get_shorten_form_idx(aggressive, name_pre, i)
|
||||
if isinstance(subst, tuple):
|
||||
return name_pre + subst[form]
|
||||
if not len(name_pre):
|
||||
# The following sets do not allow modifiers
|
||||
subst, _ = FontnameTools.find_in_dicts(
|
||||
name_rest, [FontnameTools.known_weights1, FontnameTools.known_slopes]
|
||||
)
|
||||
subst, _ = FontnameTools.find_in_dicts(name_rest, [ FontnameTools.known_weights1, FontnameTools.known_slopes ])
|
||||
if isinstance(subst, tuple):
|
||||
return subst[form]
|
||||
return name
|
||||
|
@ -139,26 +132,21 @@ class FontnameTools:
|
|||
def short_styles(lists, aggressive):
|
||||
"""Shorten all style names in a list or a list of lists"""
|
||||
if not len(lists) or not isinstance(lists[0], list):
|
||||
return list(
|
||||
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), lists))
|
||||
return [ list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles)) for styles in lists ]
|
||||
|
||||
@staticmethod
|
||||
def make_oblique_style(weights, styles):
|
||||
"""Move "Oblique" from weights to styles for font naming purposes"""
|
||||
if "Oblique" in weights:
|
||||
if 'Oblique' in weights:
|
||||
weights = list(weights)
|
||||
weights.remove("Oblique")
|
||||
weights.remove('Oblique')
|
||||
styles = list(styles)
|
||||
styles.append("Oblique")
|
||||
styles.append('Oblique')
|
||||
return (weights, styles)
|
||||
|
||||
@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"""
|
||||
# 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
|
||||
|
@ -168,20 +156,18 @@ class FontnameTools:
|
|||
#
|
||||
# Token are always used in a regex and may not capture, use non capturing
|
||||
# grouping if needed (?: ... )
|
||||
lower_tokens = [t.lower() for t in tokens]
|
||||
lower_tokens = [ t.lower() for t in tokens ]
|
||||
not_matched = ""
|
||||
all_tokens = []
|
||||
j = 1
|
||||
regex = re.compile("(.*?)(" + "|".join(tokens) + ")(.*)", re.IGNORECASE)
|
||||
regex = re.compile('(.*?)(' + '|'.join(tokens) + ')(.*)', re.IGNORECASE)
|
||||
while j:
|
||||
j = regex.match(name)
|
||||
if not j:
|
||||
break
|
||||
if len(j.groups()) != 3:
|
||||
sys.exit("Malformed regex in FontnameTools.get_name_token()")
|
||||
not_matched += (
|
||||
" " + j.groups()[0]
|
||||
) # Blanc prevents unwanted concatenation of unmatched substrings
|
||||
sys.exit('Malformed regex in FontnameTools.get_name_token()')
|
||||
not_matched += ' ' + j.groups()[0] # Blanc prevents unwanted concatenation of unmatched substrings
|
||||
tok = j.groups()[1].lower()
|
||||
if tok in lower_tokens:
|
||||
tok = tokens[lower_tokens.index(tok)]
|
||||
|
@ -189,8 +175,8 @@ class FontnameTools:
|
|||
if len(tok):
|
||||
all_tokens.append(tok)
|
||||
name = j.groups()[2] # Recurse rest
|
||||
not_matched += " " + name
|
||||
return (not_matched.strip(), all_tokens)
|
||||
not_matched += ' ' + name
|
||||
return ( not_matched.strip(), all_tokens )
|
||||
|
||||
@staticmethod
|
||||
def postscript_char_filter(name):
|
||||
|
@ -199,48 +185,42 @@ class FontnameTools:
|
|||
# except for the 10 characters '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'
|
||||
out = ""
|
||||
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
|
||||
out += c
|
||||
return out
|
||||
|
||||
SIL_TABLE = [
|
||||
("(a)nonymous", r"\1nonymice"),
|
||||
("(b)itstream( ?)(v)era( ?sans ?mono)?", r"\1itstrom\2Wera"),
|
||||
("(s)ource", r"\1auce"),
|
||||
("(h)ermit", r"\1urmit"),
|
||||
("(h)asklig", r"\1asklug"),
|
||||
("(s)hare", r"\1hure"),
|
||||
("IBM[- ]?plex", r"Blex"), # We do not keep the case here
|
||||
("(t)erminus", r"\1erminess"),
|
||||
("(l)iberation", r"\1iteration"),
|
||||
("iA([- ]?)writer", r"iM\1Writing"),
|
||||
("(a)nka/(c)oder", r"\1na\2onder"),
|
||||
("(c)ascadia( ?)(c)ode", r"\1askaydia\2\3ove"),
|
||||
("(c)ascadia( ?)(m)ono", r"\1askaydia\2\3ono"),
|
||||
("(m)( ?)plus", r"\1+"), # Added this, because they use a plus symbol :->
|
||||
("Gohufont", r"GohuFont"), # Correct to CamelCase
|
||||
( '(a)nonymous', r'\1nonymice' ),
|
||||
( '(b)itstream( ?)(v)era( ?sans ?mono)?', r'\1itstrom\2Wera' ),
|
||||
( '(s)ource', r'\1auce' ),
|
||||
( '(h)ermit', r'\1urmit' ),
|
||||
( '(h)asklig', r'\1asklug' ),
|
||||
( '(s)hare', r'\1hure' ),
|
||||
( 'IBM[- ]?plex', r'Blex' ), # We do not keep the case here
|
||||
( '(t)erminus', r'\1erminess' ),
|
||||
( '(l)iberation', r'\1iteration' ),
|
||||
( 'iA([- ]?)writer', r'iM\1Writing' ),
|
||||
( '(a)nka/(c)oder', r'\1na\2onder' ),
|
||||
( '(c)ascadia( ?)(c)ode', r'\1askaydia\2\3ove' ),
|
||||
( '(c)ascadia( ?)(m)ono', r'\1askaydia\2\3ono' ),
|
||||
( '(m)( ?)plus', r'\1+'), # Added this, because they use a plus symbol :->
|
||||
( 'Gohufont', r'GohuFont'), # Correct to CamelCase
|
||||
# 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
|
||||
("(.*sans ?m)ono", r"\1"), # Various SomenameSansMono fonts
|
||||
("(.*code ?lat)in Expanded", r"\1X"), # for 'M PLUS Code Latin Expanded'
|
||||
("(.*code ?lat)in", r"\1"), # for 'M PLUS Code Latin'
|
||||
("(b)ig( ?)(b)lue( ?)(t)erminal", r"\1ig\3lue\5erm"), # Shorten BigBlueTerminal
|
||||
("(.*)437TT", r"\g<1>437"), # Shorten BigBlueTerminal 437 TT even further
|
||||
("(.*dyslexic ?alt)a", r"\1"), # Open Dyslexic Alta -> Open Dyslexic Alt
|
||||
("(.*dyslexic ?m)ono", r"\1"), # Open Dyslexic Mono -> Open Dyslexic M
|
||||
("(overpass ?m)ono", r"\1"), # Overpass Mono -> Overpass M
|
||||
("(proggyclean) ?tt", r"\1"), # Remove TT from ProggyClean
|
||||
(
|
||||
"(terminess) ?\(ttf\)",
|
||||
r"\1",
|
||||
), # 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
|
||||
( '(.*sans ?m)ono', r'\1'), # Various SomenameSansMono fonts
|
||||
( '(.*code ?lat)in Expanded', r'\1X'), # for 'M PLUS Code Latin Expanded'
|
||||
( '(.*code ?lat)in', r'\1'), # for 'M PLUS Code Latin'
|
||||
( '(b)ig( ?)(b)lue( ?)(t)erminal', r'\1ig\3lue\5erm'), # Shorten BigBlueTerminal
|
||||
( '(.*)437TT', r'\g<1>437'), # Shorten BigBlueTerminal 437 TT even further
|
||||
( '(.*dyslexic ?alt)a', r'\1'), # Open Dyslexic Alta -> Open Dyslexic Alt
|
||||
( '(.*dyslexic ?m)ono', r'\1'), # Open Dyslexic Mono -> Open Dyslexic M
|
||||
( '(overpass ?m)ono', r'\1'), # Overpass Mono -> Overpass M
|
||||
( '(proggyclean) ?tt', r'\1'), # Remove TT from ProggyClean
|
||||
( '(terminess) ?\(ttf\)', r'\1'), # 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
|
||||
|
@ -255,102 +235,97 @@ class FontnameTools:
|
|||
# - has modifier: use second form of mod plus second form of widths
|
||||
# This is encoded in get_shorten_form_idx()
|
||||
known_weights1 = { # can not take modifiers
|
||||
"Medium": ("Md", "Med"),
|
||||
"Nord": ("Nd", "Nord"),
|
||||
"Book": ("Bk", "Book"),
|
||||
"Poster": ("Po", "Poster"),
|
||||
"Demi": (
|
||||
"Dm",
|
||||
"Demi",
|
||||
), # Demi is sometimes used as a weight, sometimes as a modifier
|
||||
"Regular": ("Rg", "Reg"),
|
||||
"Display": ("DS", "Disp"),
|
||||
"Super": ("Su", "Sup"),
|
||||
"Retina": ("Rt", "Ret"),
|
||||
'Medium': ('Md', 'Med'),
|
||||
'Nord': ('Nd', 'Nord'),
|
||||
'Book': ('Bk', 'Book'),
|
||||
'Poster': ('Po', 'Poster'),
|
||||
'Demi': ('Dm', 'Demi'), # Demi is sometimes used as a weight, sometimes as a modifier
|
||||
'Regular': ('Rg', 'Reg'),
|
||||
'Display': ('DS', 'Disp'),
|
||||
'Super': ('Su', 'Sup'),
|
||||
'Retina': ('Rt', 'Ret'),
|
||||
}
|
||||
known_weights2 = { # can take modifiers
|
||||
"Black": ("Blk", "Black"),
|
||||
"Bold": ("Bd", "Bold"),
|
||||
"Heavy": ("Hv", "Heavy"),
|
||||
"Thin": ("Th", "Thin"),
|
||||
"Light": ("Lt", "Light"),
|
||||
" ": (), # Just for CodeClimate :-/
|
||||
'Black': ('Blk', 'Black'),
|
||||
'Bold': ('Bd', 'Bold'),
|
||||
'Heavy': ('Hv', 'Heavy'),
|
||||
'Thin': ('Th', 'Thin'),
|
||||
'Light': ('Lt', 'Light'),
|
||||
' ': (), # Just for CodeClimate :-/
|
||||
}
|
||||
known_widths = { # can take modifiers
|
||||
"Compressed": ("Cm", "Comp"),
|
||||
"Extended": ("Ex", "Extd"),
|
||||
"Condensed": ("Cn", "Cond"),
|
||||
"Narrow": ("Nr", "Narrow"),
|
||||
"Compact": ("Ct", "Compact"),
|
||||
'Compressed': ('Cm', 'Comp'),
|
||||
'Extended': ('Ex', 'Extd'),
|
||||
'Condensed': ('Cn', 'Cond'),
|
||||
'Narrow': ('Nr', 'Narrow'),
|
||||
'Compact': ('Ct', 'Compact'),
|
||||
}
|
||||
known_slopes = { # can not take modifiers
|
||||
"Inclined": ("Ic", "Incl"),
|
||||
"Oblique": ("Obl", "Obl"),
|
||||
"Italic": ("It", "Italic"),
|
||||
"Upright": ("Up", "Uprght"),
|
||||
"Kursiv": ("Ks", "Kurs"),
|
||||
"Sloped": ("Sl", "Slop"),
|
||||
'Inclined': ('Ic', 'Incl'),
|
||||
'Oblique': ('Obl', 'Obl'),
|
||||
'Italic': ('It', 'Italic'),
|
||||
'Upright': ('Up', 'Uprght'),
|
||||
'Kursiv': ('Ks', 'Kurs'),
|
||||
'Sloped': ('Sl', 'Slop'),
|
||||
}
|
||||
known_modifiers = {
|
||||
"Demi": ("Dm", "Dem"),
|
||||
"Ultra": ("Ult", "Ult"),
|
||||
"Semi": ("Sm", "Sem"),
|
||||
"Extra": ("X", "Ext"),
|
||||
'Demi': ('Dm', 'Dem'),
|
||||
'Ultra': ('Ult', 'Ult'),
|
||||
'Semi': ('Sm', 'Sem'),
|
||||
'Extra': ('X', 'Ext'),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def is_keep_regular(basename):
|
||||
"""This has been decided by the font designers, we need to mimic that (for comparison purposes)"""
|
||||
KEEP_REGULAR = [
|
||||
"Agave",
|
||||
"Arimo",
|
||||
"Aurulent",
|
||||
"Cascadia",
|
||||
"Cousine",
|
||||
"Fantasque",
|
||||
"Fira",
|
||||
"Overpass",
|
||||
"Lilex",
|
||||
"Inconsolata$", # not InconsolataGo
|
||||
"IAWriter",
|
||||
"Meslo",
|
||||
"Monoid",
|
||||
"Mononoki",
|
||||
"Hack",
|
||||
"JetBrains Mono",
|
||||
"Noto Sans",
|
||||
"Noto Serif",
|
||||
"Victor",
|
||||
'Agave',
|
||||
'Arimo',
|
||||
'Aurulent',
|
||||
'Cascadia',
|
||||
'Cousine',
|
||||
'Fantasque',
|
||||
'Fira',
|
||||
|
||||
'Overpass',
|
||||
'Lilex',
|
||||
'Inconsolata$', # not InconsolataGo
|
||||
'IAWriter',
|
||||
'Meslo',
|
||||
'Monoid',
|
||||
'Mononoki',
|
||||
'Hack',
|
||||
'JetBrains Mono',
|
||||
'Noto Sans',
|
||||
'Noto Serif',
|
||||
'Victor',
|
||||
]
|
||||
for kr in KEEP_REGULAR:
|
||||
if (basename.rstrip() + "$").startswith(kr):
|
||||
return True
|
||||
if (basename.rstrip() + '$').startswith(kr): return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _parse_simple_font_name(name):
|
||||
"""Parse a filename that does not follow the 'FontFamilyName-FontStyle' pattern"""
|
||||
# No dash in name, maybe we have blanc separated filename?
|
||||
if " " in name:
|
||||
return FontnameTools.parse_font_name(name.replace(" ", "-"))
|
||||
if ' ' in name:
|
||||
return FontnameTools.parse_font_name(name.replace(' ', '-'))
|
||||
# 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:
|
||||
return FontnameTools.parse_font_name("-".join(p))
|
||||
return FontnameTools.parse_font_name('-'.join(p))
|
||||
# Or do we have CamelCase?
|
||||
n = FontnameTools.camel_explode(name)
|
||||
if n != name:
|
||||
return FontnameTools.parse_font_name(n.replace(" ", "-"))
|
||||
return (False, FontnameTools.camel_casify(name), [], [], [], "")
|
||||
return FontnameTools.parse_font_name(n.replace(' ', '-'))
|
||||
return (False, FontnameTools.camel_casify(name), [], [], [], '')
|
||||
|
||||
@staticmethod
|
||||
def parse_font_name(name):
|
||||
"""Expects a filename following the 'FontFamilyName-FontStyle' pattern and returns ... parts"""
|
||||
name = re.sub(
|
||||
r"\bsemi-condensed\b", "SemiCondensed", name, 1, re.IGNORECASE
|
||||
) # Just for "3270 Semi-Condensed" :-/
|
||||
name = re.sub("[_\s]+", " ", name)
|
||||
matches = re.match(r"([^-]+)(?:-(.*))?", name)
|
||||
name = re.sub(r'\bsemi-condensed\b', 'SemiCondensed', name, 1, re.IGNORECASE) # Just for "3270 Semi-Condensed" :-/
|
||||
name = re.sub('[_\s]+', ' ', name)
|
||||
matches = re.match(r'([^-]+)(?:-(.*))?', name)
|
||||
familyname = FontnameTools.camel_casify(matches.group(1))
|
||||
style = matches.group(2)
|
||||
|
||||
|
@ -361,81 +336,47 @@ class FontnameTools:
|
|||
# Weights end up as Typographic Family parts ('after the dash')
|
||||
# Styles end up as Family parts (for classic grouping of four)
|
||||
# Others also end up in Typographic Family ('before the dash')
|
||||
weights = (
|
||||
[
|
||||
m + s
|
||||
for s in list(FontnameTools.known_weights2)
|
||||
+ list(FontnameTools.known_widths)
|
||||
for m in list(FontnameTools.known_modifiers) + [""]
|
||||
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]
|
||||
weights = [ m + s
|
||||
for s in list(FontnameTools.known_weights2) + list(FontnameTools.known_widths)
|
||||
for m in list(FontnameTools.known_modifiers) + [''] 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:
|
||||
other = [
|
||||
"-",
|
||||
"Book",
|
||||
"For",
|
||||
"Powerline",
|
||||
"Text", # Plex
|
||||
"IIx", # Profont IIx
|
||||
"LGC", # Inconsolata LGC
|
||||
r"\bCE\b", # ProggycleanTT CE
|
||||
r"[12][cmp]n?", # MPlus
|
||||
r"(?:uni-)?1[14]", # GohuFont uni
|
||||
'-', 'Book', 'For', 'Powerline',
|
||||
'Text', # Plex
|
||||
'IIx', # Profont IIx
|
||||
'LGC', # Inconsolata LGC
|
||||
r'\bCE\b', # ProggycleanTT CE
|
||||
r'[12][cmp]n?', # MPlus
|
||||
r'(?:uni-)?1[14]', # GohuFont uni
|
||||
]
|
||||
|
||||
# Sometimes used abbreviations
|
||||
weight_abbrevs = [
|
||||
"ob",
|
||||
"c",
|
||||
"m",
|
||||
"l",
|
||||
]
|
||||
style_abbrevs = [
|
||||
"it",
|
||||
"r",
|
||||
"b",
|
||||
"i",
|
||||
]
|
||||
weight_abbrevs = [ 'ob', 'c', 'm', 'l', ]
|
||||
style_abbrevs = [ 'it', 'r', 'b', 'i', ]
|
||||
|
||||
(style, weight_token) = FontnameTools.get_name_token(style, weights)
|
||||
(style, style_token) = FontnameTools.get_name_token(style, styles)
|
||||
(style, other_token) = FontnameTools.get_name_token(style, other, True)
|
||||
if (
|
||||
len(style) < 4 and style.lower() != "pro"
|
||||
): # Prevent 'r' of Pro to be detected as style_abbrev
|
||||
(style, weight_token_abbrevs) = FontnameTools.get_name_token(
|
||||
style, weight_abbrevs
|
||||
)
|
||||
(style, style_token_abbrevs) = FontnameTools.get_name_token(
|
||||
style, style_abbrevs
|
||||
)
|
||||
( style, weight_token ) = FontnameTools.get_name_token(style, weights)
|
||||
( style, style_token ) = FontnameTools.get_name_token(style, styles)
|
||||
( style, other_token ) = FontnameTools.get_name_token(style, other, True)
|
||||
if (len(style) < 4
|
||||
and style.lower() != 'pro'): # Prevent 'r' of Pro to be detected as style_abbrev
|
||||
( style, weight_token_abbrevs ) = FontnameTools.get_name_token(style, weight_abbrevs)
|
||||
( style, style_token_abbrevs ) = FontnameTools.get_name_token(style, style_abbrevs)
|
||||
weight_token += weight_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
|
||||
style_token.remove("Regular")
|
||||
style_token.remove('Regular')
|
||||
|
||||
# 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:
|
||||
return FontnameTools.parse_font_name(
|
||||
familyname + matches2.group(1) + "-" + matches2.group(2)
|
||||
)
|
||||
return FontnameTools.parse_font_name(familyname + matches2.group(1) + '-' + matches2.group(2))
|
||||
|
||||
style = re.sub(
|
||||
r"(^|\s)\d+(\.\d+)+(\s|$)", r"\1\3", style
|
||||
) # Remove (free standing) version numbers
|
||||
style_parts = FontnameTools.drop_empty(style.split(" "))
|
||||
style = " ".join(map(FontnameTools.front_upper, style_parts))
|
||||
style = re.sub(r'(^|\s)\d+(\.\d+)+(\s|$)', r'\1\3', style) # Remove (free standing) version numbers
|
||||
style_parts = FontnameTools.drop_empty(style.split(' '))
|
||||
style = ' '.join(map(FontnameTools.front_upper, style_parts))
|
||||
familyname = FontnameTools.camel_explode(familyname)
|
||||
return (True, familyname, weight_token, style_token, other_token, style)
|
||||
|
|
122
font-patcher
122
font-patcher
|
@ -1,14 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf8
|
||||
# Nerd Fonts Version: 3.0.0
|
||||
# Nerd Fonts Version: 3.0.1
|
||||
# Script version is further down
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Change the script version when you edit this script:
|
||||
script_version = "4.1.2"
|
||||
script_version = "4.3.1"
|
||||
|
||||
version = "3.0.0"
|
||||
version = "3.0.1"
|
||||
projectName = "Nerd Fonts"
|
||||
projectNameAbbreviation = "NF"
|
||||
projectNameSingular = projectName[:-1]
|
||||
|
@ -324,7 +324,7 @@ class font_patcher:
|
|||
self.font_dim = None # class 'dict'
|
||||
self.font_extrawide = 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.essential = set()
|
||||
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.assert_monospace()
|
||||
self.remove_ligatures()
|
||||
self.setup_patch_set()
|
||||
self.get_sourcefont_dimensions()
|
||||
self.setup_patch_set()
|
||||
self.improve_line_dimensions()
|
||||
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
|
||||
|
@ -373,15 +373,16 @@ class font_patcher:
|
|||
if symfont:
|
||||
symfont.close()
|
||||
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)",
|
||||
patch['Name'], self.args.glyphdir + patch['Filename'])
|
||||
patch['Name'], symfont_file)
|
||||
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)",
|
||||
patch['Name'], self.args.glyphdir + patch['Filename'])
|
||||
patch['Name'], symfont_file)
|
||||
sys.exit(1)
|
||||
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename']))
|
||||
symfont = fontforge.open(symfont_file)
|
||||
symfont.encoding = 'UnicodeFull'
|
||||
|
||||
# Match the symbol font size to the source font size
|
||||
|
@ -434,10 +435,10 @@ class font_patcher:
|
|||
sanitize_filename(fontname) + self.args.extension))
|
||||
bitmaps = str()
|
||||
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
|
||||
if self.args.dry_run:
|
||||
logger.debug("=====> Filename '{}'".format(outfile))
|
||||
logger.debug("=====> Filename '%s'", outfile)
|
||||
return
|
||||
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
|
||||
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
|
||||
|
@ -784,7 +785,7 @@ 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 """
|
||||
|
||||
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
|
||||
if box_enabled:
|
||||
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
|
||||
|
||||
# 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
|
||||
# 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)
|
||||
|
@ -810,6 +811,8 @@ class font_patcher:
|
|||
# '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)
|
||||
# Dont_copy does not overwrite existing glyphs but rescales the preexisting ones
|
||||
#
|
||||
# Be careful, stretch may not change within a ScaleRule!
|
||||
|
||||
SYM_ATTR_DEFAULT = {
|
||||
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}
|
||||
|
@ -886,7 +889,7 @@ class font_patcher:
|
|||
0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}
|
||||
}
|
||||
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 = {
|
||||
'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}},
|
||||
}
|
||||
CUSTOM_ATTR = {
|
||||
# 'pa' == preserve aspect ratio
|
||||
'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}}
|
||||
# previous custom scaling => do not touch the icons
|
||||
# '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
|
||||
|
@ -956,6 +960,9 @@ class font_patcher:
|
|||
range(0xeab4, 0xeab7 + 1), # chevrons
|
||||
[0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles
|
||||
[0xeacc, 0xeaba], # dash
|
||||
[0xea75, 0xebe7], # lock pair
|
||||
[0xeacf, 0xebe0], # debug-continue pair
|
||||
[0xeb91, 0xeba8], # debug-alt pair
|
||||
]}
|
||||
DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo
|
||||
'GlyphsToScale': [
|
||||
|
@ -982,14 +989,21 @@ class font_patcher:
|
|||
range(0xf221, 0xf22d + 1), # gender or so
|
||||
range(0xf255, 0xf25b + 1), # hand symbols
|
||||
]}
|
||||
OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?)
|
||||
HEAVY_SCALE_LIST = {'ScaleGlyph': 0x2771, # widest bracket, horizontally
|
||||
'GlyphsToScale': [
|
||||
(0xf03d, 0xf040), # arrows
|
||||
(0x276c, 0x2771) # all
|
||||
]}
|
||||
OCTI_SCALE_LIST = {'ScaleGroups': [
|
||||
[*range(0xf03d, 0xf040 + 1), 0xf019, 0xf030, 0xf04a, 0xf050, 0xf071, 0xf08c ], # arrows
|
||||
[0xF0E7, # Smily and ...
|
||||
0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles
|
||||
(0xf051, 0xf053), # small stuff
|
||||
0xf071, 0xf09f, 0xf0a0, 0xf0a1, # small arrows
|
||||
0xf052, 0xf053, 0x296, 0xf2f0, # small stuff
|
||||
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons
|
||||
0xf0ca, # dash
|
||||
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': [
|
||||
[0xf03c, 0xf042, 0xf045 ], # degree signs
|
||||
|
@ -1016,7 +1030,7 @@ class font_patcher:
|
|||
# Symbol font ranges
|
||||
self.patch_set = [
|
||||
{'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 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': 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},
|
||||
|
@ -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': 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': 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.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.
|
||||
# Find out which other glyphs are also needed to keep the basic
|
||||
# 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()
|
||||
# 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:
|
||||
continue
|
||||
basic_glyphs.add(glyph)
|
||||
|
@ -1120,8 +1135,8 @@ class font_patcher:
|
|||
# Try the other metric
|
||||
our_btb = typo_btb if not use_typo else win_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
|
||||
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
|
||||
metrics = Metric.TYPO if use_typo else Metric.WIN
|
||||
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))
|
||||
|
||||
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:
|
||||
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
|
||||
# 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']
|
||||
|
||||
if 'pa' in stretch:
|
||||
|
@ -1324,6 +1339,9 @@ class font_patcher:
|
|||
# if currentSourceFontGlyph != 0xe7bd:
|
||||
# continue
|
||||
|
||||
ypadding = sym_attr['params'].get('ypadding')
|
||||
self.font_dim['ypadding'] = ypadding or 0.0
|
||||
|
||||
if not self.args.quiet:
|
||||
if self.args.progressbars:
|
||||
update_progress(round(float(index + 1) / glyphSetLength, 2))
|
||||
|
@ -1333,7 +1351,8 @@ class font_patcher:
|
|||
sys.stdout.flush()
|
||||
|
||||
# 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:
|
||||
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
|
||||
logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph)
|
||||
|
@ -1345,14 +1364,15 @@ class font_patcher:
|
|||
if currentSourceFontGlyph in self.sourceFont:
|
||||
self.sourceFont[currentSourceFontGlyph].removePosSub("*")
|
||||
|
||||
stretch = sym_attr['stretch']
|
||||
dont_copy = sym_attr['params'].get('dont_copy')
|
||||
|
||||
if dont_copy:
|
||||
# Just prepare scaling of existing glyphs
|
||||
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None
|
||||
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None
|
||||
else:
|
||||
# This will destroy any content currently in currentSourceFontGlyph, so do it first
|
||||
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None
|
||||
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
|
||||
# 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[1] is not None:
|
||||
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:
|
||||
# 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'])
|
||||
(scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch)
|
||||
|
||||
overlap = sym_attr['params'].get('overlap')
|
||||
if overlap and ypadding:
|
||||
logger.critical("Conflicting params: overlap and ypadding")
|
||||
sys.exit(1)
|
||||
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
|
||||
|
@ -1429,7 +1452,7 @@ class font_patcher:
|
|||
x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2)
|
||||
elif sym_attr['align'] == 'r':
|
||||
# Right align
|
||||
x_align_distance += self.font_dim['width'] * 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
|
||||
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
|
||||
elif sym_attr['align'] == 'r':
|
||||
# Check and correct overlap; it can go wrong if we have a xy-ratio limit
|
||||
target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(sym_attr['stretch'])
|
||||
target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(stretch)
|
||||
target_xmax += overlap_width
|
||||
glyph_xmax = sym_dim['xmax'] + x_align_distance
|
||||
correction = target_xmax - glyph_xmax
|
||||
|
@ -1484,8 +1507,8 @@ class font_patcher:
|
|||
if self.args.single:
|
||||
(xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox()
|
||||
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))",
|
||||
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap)
|
||||
logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %s))",
|
||||
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], repr(overlap))
|
||||
|
||||
# end for
|
||||
|
||||
|
@ -1538,7 +1561,7 @@ class font_patcher:
|
|||
except:
|
||||
pass
|
||||
|
||||
def prepareScaleRules(self, scaleRules, symbolFont, destGlyph):
|
||||
def prepareScaleRules(self, scaleRules, stretch, 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
|
||||
|
@ -1572,7 +1595,7 @@ class font_patcher:
|
|||
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]
|
||||
scale = self.get_scale_factors(sym_dim, stretch)[0]
|
||||
scaleRules['scales'].append(scale)
|
||||
scaleRules['bbdims'].append(sym_dim)
|
||||
|
||||
|
@ -1591,7 +1614,7 @@ class font_patcher:
|
|||
else:
|
||||
group_list.append(i)
|
||||
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['scales'].append(scale)
|
||||
if plus:
|
||||
|
@ -1599,13 +1622,13 @@ class font_patcher:
|
|||
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):
|
||||
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 """
|
||||
# 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])
|
||||
self.prepareScaleRules(scaleRules, stretch, 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):
|
||||
|
@ -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('--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('--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('-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')
|
||||
|
@ -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('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
|
||||
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('--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
|
||||
|
@ -1952,16 +1975,23 @@ def main():
|
|||
global logger
|
||||
logger = logging.getLogger(os.path.basename(args.font))
|
||||
logger.setLevel(logging.DEBUG)
|
||||
log_to_file = (args.debugmode & 1 == 1)
|
||||
if log_to_file:
|
||||
try:
|
||||
f_handler = logging.FileHandler('font-patcher-log.txt')
|
||||
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.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
|
||||
if not args.debugmode:
|
||||
if not (args.debugmode & 2 == 2):
|
||||
c_handler.setLevel(logging.INFO)
|
||||
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)
|
||||
|
||||
patcher = font_patcher(args)
|
||||
|
|
|
@ -14,10 +14,10 @@ import fontforge
|
|||
octi_orig = "octicons.ttf"
|
||||
current_cp = 0xF400
|
||||
|
||||
print("# Examining {}".format(octi_orig))
|
||||
print('# Examining {}'.format(octi_orig))
|
||||
|
||||
font = fontforge.open(octi_orig)
|
||||
for glyph in font.glyphs("encoding"):
|
||||
for glyph in font.glyphs('encoding'):
|
||||
point = glyph.unicode
|
||||
if point < 0:
|
||||
continue
|
||||
|
|
1
src/glyphs/octicons/file-symlink-directory-16.svg
Normal file
1
src/glyphs/octicons/file-symlink-directory-16.svg
Normal 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 |
1
src/glyphs/octicons/file-symlink-directory-24.svg
Normal file
1
src/glyphs/octicons/file-symlink-directory-24.svg
Normal 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 |
|
@ -8,95 +8,81 @@ import subprocess
|
|||
import fontforge
|
||||
|
||||
# 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"
|
||||
fontdir = "."
|
||||
fontfile = "octicons.ttf"
|
||||
glyphsetfile = "i_oct.sh"
|
||||
glyphsetsdir = "../../../bin/scripts/lib"
|
||||
|
||||
subset = "-16" # use 16 px subset if possible
|
||||
subset_other = "-24" # use 24 px subset otherwise
|
||||
vectorsdir = 'icons'
|
||||
fontdir = '.'
|
||||
fontfile = 'octicons.ttf'
|
||||
glyphsetfile = 'i_oct.sh'
|
||||
glyphsetsdir = '../../../bin/scripts/lib'
|
||||
|
||||
subset = '-16' # use 16 px subset if possible
|
||||
subset_other = '-24' # use 24 px subset otherwise
|
||||
|
||||
def renamer(old_name):
|
||||
"""Return new equivalent icon name"""
|
||||
""" Return new equivalent icon name """
|
||||
return {
|
||||
"trashcan": "trash",
|
||||
"cloud-download": "download",
|
||||
"cloud-upload": "upload",
|
||||
"clippy": "paste",
|
||||
"mail-read": "read",
|
||||
"primitive-dot": "dot-fill",
|
||||
"primitive-square": "square-fill",
|
||||
"settings": "sliders",
|
||||
"dashboard": "meter",
|
||||
"paintcan": "paintbrush",
|
||||
'trashcan' : 'trash',
|
||||
'cloud-download' : 'download',
|
||||
'cloud-upload' : 'upload',
|
||||
'clippy' : 'paste',
|
||||
'mail-read' : 'read',
|
||||
'primitive-dot' : 'dot-fill',
|
||||
'primitive-square' : 'square-fill',
|
||||
'settings' : 'sliders',
|
||||
'dashboard' : 'meter',
|
||||
'paintcan' : 'paintbrush',
|
||||
}.get(old_name, old_name)
|
||||
|
||||
|
||||
def addIcon(codepoint, name, filename):
|
||||
"""Add one outline file and rescale/move"""
|
||||
dBB = [120, 0, 1000 - 120, 900] # just some nice sizes
|
||||
""" Add one outline file and rescale/move """
|
||||
dBB = [120, 0, 1000-120, 900] # just some nice sizes
|
||||
filename = os.path.join(vectorsdir, filename)
|
||||
glyph = font.createChar(codepoint, name)
|
||||
glyph.importOutlines(filename)
|
||||
glyph.manualHints = True
|
||||
|
||||
|
||||
def createGlyphInfo(icon_datasets, filepathname, into):
|
||||
"""Write the glyphinfo file"""
|
||||
with open(filepathname, "w", encoding="utf8") as f:
|
||||
f.write("#!/usr/bin/env bash\n")
|
||||
""" Write the glyphinfo file """
|
||||
with open(filepathname, 'w', encoding = 'utf8') as f:
|
||||
f.write(u'#!/usr/bin/env bash\n')
|
||||
f.write(intro)
|
||||
f.write("# Script Version: (autogenerated)\n")
|
||||
f.write('test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
f.write(u'# Script Version: (autogenerated)\n')
|
||||
f.write(u'test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
for _, codepoint, name in icon_datasets:
|
||||
codepoint = int(codepoint, 16)
|
||||
f.write(
|
||||
"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace("-", "_"))
|
||||
)
|
||||
f.write("unset i\n")
|
||||
f.write(u"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace('-', '_')))
|
||||
f.write(u'unset i\n')
|
||||
|
||||
|
||||
print("\nReading mapping file")
|
||||
print('\nReading mapping file')
|
||||
old_mapping = []
|
||||
with open("mapping", "r") as f:
|
||||
with open('mapping', 'r') as f:
|
||||
for line in f.readlines():
|
||||
if line.startswith("#"):
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
old_mapping.append(tuple(re.split(" +", line.strip())))
|
||||
print("Found {} entries".format(len(old_mapping)))
|
||||
old_mapping.append(tuple(re.split(' +', line.strip())))
|
||||
print('Found {} entries'.format(len(old_mapping)))
|
||||
old_mapping.sort(key=(lambda x: x[0]))
|
||||
|
||||
print('Fetching octicons archive "{}"\n'.format(archive))
|
||||
if subprocess.call(
|
||||
"curl -OL https://github.com/primer/octicons/archive/" + archive, shell=True
|
||||
):
|
||||
sys.exit("Error fetching octicons archive")
|
||||
print("\nUnpacking octicons archive")
|
||||
if subprocess.call(
|
||||
"rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-*",
|
||||
shell=True,
|
||||
):
|
||||
sys.exit("Error unpacking archive")
|
||||
if subprocess.call('curl -OL https://github.com/primer/octicons/archive/' + archive, shell=True):
|
||||
sys.exit('Error fetching octicons archive')
|
||||
print('\nUnpacking 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):
|
||||
sys.exit('Error unpacking archive')
|
||||
|
||||
svgs = os.listdir(vectorsdir)
|
||||
print("Found {} svgs".format(len(svgs)))
|
||||
names = {
|
||||
s[0 : -len("-xx.svg")]
|
||||
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)))
|
||||
print('Found {} svgs'.format(len(svgs)))
|
||||
names = { s[0:-len('-xx.svg')] 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_missing = 0
|
||||
misslist = ""
|
||||
renamelist = ""
|
||||
misslist = ''
|
||||
renamelist = ''
|
||||
freeslots = []
|
||||
|
||||
new_mapping = []
|
||||
|
@ -108,22 +94,18 @@ for i, j, old_n in old_mapping:
|
|||
continue
|
||||
new_n = renamer(old_n)
|
||||
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)
|
||||
new_mapping.append((i, j, new_n))
|
||||
num_found += 1
|
||||
continue
|
||||
misslist += "Missing {}\n".format(old_n)
|
||||
misslist += 'Missing {}\n'.format(old_n)
|
||||
freeslots.append((i, j))
|
||||
num_missing += 1
|
||||
|
||||
print(renamelist)
|
||||
print(misslist)
|
||||
print(
|
||||
"Found {} (of {}, missing {}) and new {}".format(
|
||||
num_found, len(old_mapping), num_missing, len(names)
|
||||
)
|
||||
)
|
||||
print('Found {} (of {}, missing {}) and new {}'.format(num_found, len(old_mapping), num_missing, len(names)))
|
||||
|
||||
names = list(names)
|
||||
names.sort()
|
||||
|
@ -135,7 +117,7 @@ for n in list(names):
|
|||
names.remove(n)
|
||||
freeslots = freeslots[1:]
|
||||
|
||||
print("Filled in missing, remaining new {}".format(len(names)))
|
||||
print('Filled in missing, remaining new {}'.format(len(names)))
|
||||
|
||||
i_max = 0
|
||||
j_max = 0
|
||||
|
@ -150,21 +132,21 @@ for i, j, _ in new_mapping:
|
|||
for n in names:
|
||||
i_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]))
|
||||
with open("mapping", "w") as f:
|
||||
with open('mapping', 'w') as f:
|
||||
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.fontname = "OcticonsNerdFont-Regular"
|
||||
font.fullname = "Octicons Nerd Font Regular"
|
||||
font.familyname = "Octicons Nerd Font"
|
||||
font.fontname = 'OcticonsNerdFont-Regular'
|
||||
font.fullname = 'Octicons Nerd Font Regular'
|
||||
font.familyname = 'Octicons Nerd Font'
|
||||
font.em = 2048
|
||||
font.encoding = "UnicodeFull"
|
||||
font.encoding = 'UnicodeFull'
|
||||
|
||||
# Add valid space glyph to avoid "unknown character" box on IE11
|
||||
glyph = font.createChar(32)
|
||||
|
@ -172,30 +154,28 @@ glyph.width = 200
|
|||
|
||||
font.sfntRevision = None # Auto-set (refreshed) by fontforge
|
||||
font.version = version
|
||||
font.copyright = "GitHub Inc."
|
||||
font.appendSFNTName("English (US)", "Version", archive + "; " + version)
|
||||
font.appendSFNTName(
|
||||
"English (US)", "Vendor URL", "https://github.com/ryanoasis/nerd-fonts"
|
||||
)
|
||||
font.appendSFNTName("English (US)", "Copyright", "GitHub Inc.")
|
||||
font.copyright = 'GitHub Inc.'
|
||||
font.appendSFNTName('English (US)', 'Version', archive + '; ' + version)
|
||||
font.appendSFNTName('English (US)', 'Vendor URL', 'https://github.com/ryanoasis/nerd-fonts')
|
||||
font.appendSFNTName('English (US)', 'Copyright', 'GitHub Inc.')
|
||||
|
||||
for codepoint, _, name in new_mapping:
|
||||
codepoint = int(codepoint, 16)
|
||||
filename = name + subset + ".svg"
|
||||
filename = name + subset + '.svg'
|
||||
if filename not in svgs:
|
||||
filename = name + subset_other + ".svg"
|
||||
filename = name + subset_other + '.svg'
|
||||
addIcon(codepoint, name, filename)
|
||||
|
||||
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",))
|
||||
|
||||
codepoints = [int(p, 16) for _, p, _ in new_mapping]
|
||||
intro = "# Octicons ({} icons)\n".format(num_icons)
|
||||
intro += "# Codepoints: {:X}-{:X} with gaps\n".format(min(codepoints), max(codepoints))
|
||||
intro += "# Nerd Fonts Version: {}\n".format(version)
|
||||
codepoints = [ int(p, 16) for _, p, _ in new_mapping ]
|
||||
intro = u'# Octicons ({} icons)\n'.format(num_icons)
|
||||
intro += u'# Codepoints: {:X}-{:X} with gaps\n'.format(min(codepoints), max(codepoints))
|
||||
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)
|
||||
print("Finished")
|
||||
print('Finished')
|
||||
|
|
|
@ -130,7 +130,7 @@ F0AA F47E triangle-up
|
|||
F0AC F47F git-compare
|
||||
F0AD F480 logo-gist
|
||||
F0B0 F481 file-symlink-file
|
||||
F0B1 F482 bookmark-slash
|
||||
F0B1 F482 file-symlink-directory
|
||||
F0B2 F483 squirrel
|
||||
F0B6 F484 globe
|
||||
F0BA F485 unmute
|
||||
|
@ -307,3 +307,4 @@ F302 F52F x-circle
|
|||
F303 F530 x-circle-fill
|
||||
F304 F531 zoom-in
|
||||
F305 F532 zoom-out
|
||||
F306 F533 bookmark-slash
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -433,3 +433,4 @@ Foobar.org is a distributed community of developers...
|
|||
Company.com is a small business who likes to support community designers...
|
||||
University.edu is a renowned educational institution with a strong design department...
|
||||
-----
|
||||
|
||||
|
|
Loading…
Reference in a new issue