1
0
mirror of https://github.com/ryanoasis/nerd-fonts.git synced 2025-01-06 21:49:40 +02:00

Merge pull request #1028 from ryanoasis/feature/reorganize-naming

Pull 'Mono' to front in names and other naming changes
This commit is contained in:
Fini 2023-04-24 18:42:33 +02:00 committed by GitHub
commit 20e7a8e390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 429 additions and 2859 deletions

View File

@ -79,11 +79,11 @@ jobs:
- name: Check if patched font generated
run: |
[ -e "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf" ] && echo "File exists" || exit 1
[ -e "$GITHUB_WORKSPACE/temp/HackNerdFont-Regular.ttf" ] && echo "File exists" || exit 1
- name: Setup Spot check font information 1
run: |
echo FONT_INFO=$(fc-scan --format "%{family}:%{fullname}:%{style}\n" "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf") >> $GITHUB_ENV
echo FONT_INFO=$(fc-scan --format "%{family}:%{fullname}:%{style}\n" "$GITHUB_WORKSPACE/temp/HackNerdFont-Regular.ttf") >> $GITHUB_ENV
# TODO fix issues setting and getting fullname and style with GITHUB_ENV :(
- name: Setup Spot check font information 2
@ -97,9 +97,9 @@ jobs:
- name: Spot check font properties
run: |
${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf" | grep -q 'File Checksum.*diff=0\s*$' && echo "TTF checksum ok" || exit 1
${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/HackNerdFont-Regular.ttf" | grep -q 'File Checksum.*diff=0\s*$' && echo "TTF checksum ok" || exit 1
ORIG_MINPPEM=$(${{ env.SHOWTTF }} -c "src/unpatched-fonts/Hack/Regular/Hack-Regular.ttf" | grep 'lowestppem=' )
PATCH_MINPPEM=$(${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf" | grep 'lowestppem=' )
PATCH_MINPPEM=$(${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/HackNerdFont-Regular.ttf" | grep 'lowestppem=' )
echo "${ORIG_MINPPEM} == ${PATCH_MINPPEM}"
[[ ${ORIG_MINPPEM} == ${PATCH_MINPPEM} ]] && echo "lowestRecPPEM matches" || exit 1
@ -111,7 +111,7 @@ jobs:
- name: Check if patched font generated
run: |
[ -e "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete Mono.ttf" ] && echo "File exists" || exit 1
[ -e "$GITHUB_WORKSPACE/temp/HackNerdFontMono-Regular.ttf" ] && echo "File exists" || exit 1
- name: Patcher OTF, Bold variant, and RFN compliance
run: |
@ -121,11 +121,11 @@ jobs:
- name: Check if patched font generated
run: |
[ -e "$GITHUB_WORKSPACE/temp/Caskaydia Cove Bold Nerd Font Complete.otf" ] && echo "File exists" || exit 1
[ -e "$GITHUB_WORKSPACE/temp/CaskaydiaCoveNerdFont-Bold.otf" ] && echo "File exists" || exit 1
- name: Check if font with references is patched
# (patch result not checked)
run: |
fontforge --script ./font-patcher src/unpatched-fonts/UbuntuMono/Regular/UbuntuMono-R.ttf \
--quiet --no-progressbars --outputdir $GITHUB_WORKSPACE/temp/
[ -e "$GITHUB_WORKSPACE/temp/Ubuntu Mono Nerd Font.ttf" ] && echo "File exists" || exit 1
[ -e "$GITHUB_WORKSPACE/temp/UbuntuMonoNerdFont-Regular.ttf" ] && echo "File exists" || exit 1

View File

@ -1,6 +1,9 @@
#!/usr/bin/env bash
# Nerd Fonts Version: 2.3.3
# Script Version: 1.2.1
# Script Version: 1.3.0
#
# You can supply options to the font-patcher via environment variable NERDFONTS
# That option will override the defaults (also defaults of THIS script).
# used for debugging
# set -x
@ -248,10 +251,10 @@ function patch_font {
if [ "$config_has_powerline" -gt 0 ]
then
powerline=""
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --use-single-width-glyphs',}{' --windows',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --use-single-width-glyphs',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
else
powerline="--powerline"
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --powerline',}{' --use-single-width-glyphs',}{' --windows',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --powerline',}{' --use-single-width-glyphs',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
fi
cd "$repo_root_dir" || {
@ -260,19 +263,29 @@ function patch_font {
}
# Use absolute path to allow fontforge being an AppImage (used in CI)
PWD=`pwd`
# Create "Nerd Font"
if [ -n "${verbose}" ]
then
echo "fontforge -quiet -script ${PWD}/font-patcher "$f" -q --also-windows $powerline $post_process --complete --no-progressbars --outputdir "${patched_font_dir}complete/" $config_patch_flags"
echo "fontforge -quiet -script ${PWD}/font-patcher "$f" -q $powerline $post_process -c --no-progressbars --outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS}"
fi
{ OUT=$(fontforge -quiet -script ${PWD}/font-patcher "$f" -q --also-windows $powerline $post_process --complete --no-progressbars \
--outputdir "${patched_font_dir}complete/" $config_patch_flags 2>&1 1>&3 3>&- ); } 3>&1
{ OUT=$(fontforge -quiet -script ${PWD}/font-patcher "$f" -q $powerline $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
if [ $? -ne 0 ]; then printf "$OUT\nPatcher run aborted!\n\n"; fi
# Create "Nerd Font Mono"
if [ -n "${verbose}" ]
then
echo "fontforge -quiet -script ${PWD}/font-patcher "$f" -q -s ${font_config} --also-windows $powerline $post_process --complete --no-progressbars --outputdir "${patched_font_dir}complete/" $config_patch_flags"
echo "fontforge -quiet -script ${PWD}/font-patcher "$f" -q -s ${font_config} $powerline $post_process -c --no-progressbars --outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS}"
fi
{ OUT=$(fontforge -quiet -script ${PWD}/font-patcher "$f" -q -s ${font_config} --also-windows $powerline $post_process --complete --no-progressbars \
--outputdir "${patched_font_dir}complete/" $config_patch_flags 2>&1 1>&3 3>&- ); } 3>&1
{ OUT=$(fontforge -quiet -script ${PWD}/font-patcher "$f" -q -s ${font_config} $powerline $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
if [ $? -ne 0 ]; then printf "$OUT\nPatcher run aborted!\n\n"; fi
# Create "Nerd Font Propo"
if [ -n "${verbose}" ]
then
echo "fontforge -quiet -script ${PWD}/font-patcher "$f" -q --variable $powerline $post_process -c --no-progressbars --outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS}"
fi
{ OUT=$(fontforge -quiet -script ${PWD}/font-patcher "$f" -q --variable $powerline $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}complete/" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
if [ $? -ne 0 ]; then printf "$OUT\nPatcher run aborted!\n\n"; fi
# wait for this group of background processes to finish to avoid forking too many processes
# that can add up quickly with the number of combinations
@ -325,10 +338,10 @@ function generate_info {
if [ "$config_has_powerline" -gt 0 ]
then
powerline=""
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --use-single-width-glyphs',}{' --windows',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --use-single-width-glyphs',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
else
powerline="--powerline"
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --powerline',}{' --use-single-width-glyphs',}{' --windows',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
combinations=$(printf "./font-patcher ${f##*/} %s\\n" {' --powerline',}{' --use-single-width-glyphs',}{' --fontawesome',}{' --octicons',}{' --fontlogos',}{' --pomicons',}{' --powerlineextra',}{' --fontawesomeextension',}{' --powersymbols',}{' --weather',}{' --material',})
fi
font_families_count=$((font_families_count+1))

View File

@ -7,38 +7,39 @@ from FontnameTools import FontnameTools
class FontnameParser:
"""Parse a font name and generate all kinds of names"""
def __init__(self, filename):
def __init__(self, filename, logger):
"""Parse a font filename and store the results"""
self.parse_ok = False
self.for_windows = False
self.use_short_families = (False, False) # ( camelcase name, short styles )
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.fullname_suff = ''
self.fontname_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.basename = self._basename
self.rest = self._rest
self.add_name_substitution_table(FontnameTools.SIL_TABLE)
self.logger = logger
def _make_ps_name(self, n, is_family):
"""Helper to limit font name length in PS names"""
fam = 'family ' if is_family else ''
if not self.for_windows or len(n) <= 31:
limit = 31 if is_family else 63
if len(n) <= limit:
return n
r = re.search('(.*)(-.*)', n)
if not r:
new_n = n[:31]
new_n = n[:limit]
else:
q = 31 - len(r.groups()[1])
q = limit - len(r.groups()[1])
if q < 1:
q = 1
print('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:
print('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):
@ -46,12 +47,8 @@ class FontnameParser:
if not self.use_short_families[0]:
return (self.basename, self.rest)
else:
return (FontnameTools.concat(self.basename, self.rest).replace(' ', ''), '')
def set_for_windows(self, for_windows):
"""Create slightly different names, suitable for Windows use"""
self.for_windows = for_windows
return self
rest = self.rest.replace('Oblique', 'Obl')
return (FontnameTools.concat(self.basename, rest).replace(' ', ''), '')
def set_keep_regular_in_family(self, keep):
"""Familyname may contain 'Regular' where it should normally be suppressed"""
@ -61,43 +58,20 @@ class FontnameParser:
"""Suppress ID16/17 if it is identical to ID1/2 (True is default)"""
self.suppress_preferred_if_identical = suppress
def inject_suffix(self, fullname, fontname, family):
def inject_suffix(self, family, ps_fontname, short_family):
"""Add a custom additonal string that shows up in the resulting names"""
self.fullname_suff = fullname.strip()
self.fontname_suff = fontname.replace(' ', '')
self.family_suff = family.strip()
self.ps_fontname_suff = ps_fontname.replace(' ', '')
self.short_family_suff = short_family.strip()
return self
# font-patcher behavior:
# verboseSuff = "Nerd Font"
# shortSuff = win ? "NF" : "Nerd Font"
# verboseSuff += "Plus Font Awesome"
# shortSuff += "A"
# OR when complete:
# shortSuff = "Nerd Font Complete"
# verboseSuff = "Nerd Font Complete"
# AND
# shortSuff += "M"
# verboseSuff += "Mono"
#
# fullname += verboseSuff
# fontname += shortSuff
# if win familyname += "NF"
# else familyname += "Nerd Font"
# if win fullname += "Windows Compatible"
# if !win familyname += "Mono"
#
# THUS:
# fontname => shortSuff
# fullname => verboseSuff {{ we do the following already: }} + win ? "Windows Compatible" : ""
# family => win ? "NF" : "Nerd Font" + mono ? "Mono" : ""
def enable_short_families(self, camelcase_name, prefix):
def enable_short_families(self, camelcase_name, prefix, aggressive):
"""Enable short styles in Family when (original) font name starts with prefix; enable CamelCase basename in (Typog.) Family"""
# camelcase_name is boolean
# prefix is either a string or False
if prefix:
# prefix is either a string or False/True
if isinstance(prefix, str):
prefix = self._basename.startswith(prefix)
self.use_short_families = ( camelcase_name, prefix )
self.use_short_families = ( camelcase_name, prefix, aggressive )
return self
def add_name_substitution_table(self, table):
@ -107,18 +81,17 @@ class FontnameParser:
self.name_subst = table
self.basename = self._basename
self.rest = self._rest
base_and_rest = self._basename + (' ' + self._rest if len(self._rest) else '')
for regex, replacement in self.name_subst:
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))
i = len(self.basename) - len(m.group(0))
if i < 0:
self.basename = m.expand(replacement)
self.rest = self._rest[-(i+1):].lstrip()
self.basename = m.expand(replacement).rstrip()
self.rest = self.rest[-(i+1):].lstrip()
else:
self.basename = m.expand(replacement) + self._basename[len(m.group(0)):]
break
self.basename = m.expand(replacement) + self.basename[len(m.group(0)):]
return self
def drop_for_powerline(self):
@ -157,10 +130,6 @@ class FontnameParser:
def fullname(self):
"""Get the SFNT Fullname (ID 4)"""
if self.for_windows:
win = 'Windows Compatible'
else:
win = ''
styles = self.style_token
weights = self.weight_token
if self.keep_regular_in_family == None:
@ -174,41 +143,48 @@ class FontnameParser:
styles.remove('Regular')
# For naming purposes we want Oblique to be part of the styles
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
return FontnameTools.concat(self.basename, self.rest, self.other_token, self.fullname_suff, win, 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)
def psname(self):
"""Get the SFNT PostScriptName (ID 6)"""
# This is almost self.family() + '-' + self.subfamily() but without short styles
fam = FontnameTools.camel_casify(FontnameTools.concat(self.basename, self.rest, self.other_token, self.fontname_suff))
sub = FontnameTools.camel_casify(FontnameTools.concat(self.weight_token, self.style_token))
# This is almost self.family() + '-' + self.subfamily()
(name, rest) = self._shortened_name()
styles = self.style_token
weights = self.weight_token
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))
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
if len(sub) > 0:
sub = '-' + sub
fam = FontnameTools.postscript_char_filter(fam)
sub = FontnameTools.postscript_char_filter(sub)
# The name string must be no longer than 63 characters
if len(fam) + len(sub) > 63:
print('Shortening too long PostScriptName')
fam = fam[:(63 - len(sub))]
return fam + sub
return self._make_ps_name(fam + sub, False)
def preferred_family(self):
"""Get the SFNT Preferred Familyname (ID 16)"""
if self.suppress_preferred_if_identical and len(self.weight_token) == 0:
(name, rest) = self._shortened_name()
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 ''
(name, rest) = self._shortened_name()
return FontnameTools.concat(name, rest, self.other_token, self.family_suff)
return pfn
def preferred_styles(self):
"""Get the SFNT Preferred Styles (ID 17)"""
styles = self.style_token
weights = self.weight_token
if self.suppress_preferred_if_identical and len(weights) == 0:
# Do not set if identical to ID 2
return ''
# For naming purposes we want Oblique to be part of the styles
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
return FontnameTools.concat(weights, styles)
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 pfs
def family(self):
"""Get the SFNT Familyname (ID 1)"""
@ -216,10 +192,10 @@ class FontnameParser:
(name, rest) = self._shortened_name()
other = self.other_token
weight = self.weight_token
aggressive = self.use_short_families[2]
if self.use_short_families[1]:
other = FontnameTools.short_styles(other)
weight = FontnameTools.short_styles(weight)
return FontnameTools.concat(name, rest, other, self.family_suff, weight)
[ other, weight ] = FontnameTools.short_styles([ other, weight ], aggressive)
return FontnameTools.concat(name, rest, other, self.short_family_suff, weight)
def subfamily(self):
"""Get the SFNT SubFamily (ID 2)"""
@ -233,15 +209,10 @@ class FontnameParser:
def ps_familyname(self):
"""Get the PS Familyname"""
return self._make_ps_name(self.family(), True)
def ps_fontname(self):
"""Get the PS fontname"""
# This Adobe restriction is classically ignored
# if len(n) > 29:
# print('Shortening too long PS fontname')
# return n[:29]
return self._make_ps_name(self.psname(), False)
fam = self.preferred_family()
if len(fam) < 1:
fam = self.family()
return self._make_ps_name(fam, True)
def macstyle(self, style):
"""Modify a given macStyle value for current name, just bits 0 and 1 touched"""
@ -267,9 +238,18 @@ class FontnameParser:
b |= WWS # We assert this by our naming process
return b
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))
else:
self.logger.error('====-< {:18} too long ({:2} > {:2}): {}'.format(entry_id, len(name), max_len, name))
return name
def rename_font(self, font):
"""Rename the font to include all information we found (font is fontforge font object)"""
font.fontname = self.ps_fontname()
font.fondname = None
font.fontname = self.psname()
font.fullname = self.fullname()
font.familyname = self.ps_familyname()
@ -299,27 +279,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 = ''
sfnt_list = []
TO_DEL = ['Family', 'SubFamily', 'Fullname', 'Postscriptname', 'Preferred Family',
'Preferred Styles', 'Compatible Full', 'WWS Family', 'WWS Subfamily']
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 += [( 'English (US)', 'Family', self.family() )]
sfnt_list += [( 'English (US)', 'SubFamily', self.subfamily() )]
sfnt_list += [( 'English (US)', 'Fullname', self.fullname() )]
sfnt_list += [( 'English (US)', 'PostScriptName', self.psname() )]
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', p_fam )]
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', p_sty )]
sfnt_list += [( 'English (US)', 'Preferred Styles', self.checklen(31, 'PrefStyles (ID 17)', p_sty) )] # 17
font.sfnt_names = tuple(sfnt_list)

View File

@ -68,7 +68,6 @@ class FontnameTools:
'book': '',
'text': '',
'ce': 'CE',
'(ttf)': '(TTF)',
#'semibold': 'Demi',
'ob': 'Oblique',
'it': 'Italic',
@ -85,27 +84,57 @@ class FontnameTools:
return style_name
@staticmethod
def shorten_style_name(name):
def find_in_dicts(key, dicts):
"""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 (None, 0)
@staticmethod
def get_shorten_form_idx(aggressive, prefix, form_if_prefixed):
"""Get the tuple index of known_* data tables"""
if aggressive:
return 0
if len(prefix):
return form_if_prefixed
return 1
@staticmethod
def shorten_style_name(name, aggressive):
"""Substitude some known styles to short form"""
known_names = {
# Chiefly from Noto
'SemiCondensed': 'SemCond',
'Condensed': 'Cond',
'ExtraCondensed': 'ExtCond',
'SemiBold': 'SemBd',
'ExtraBold': 'ExtBd',
'Medium': 'Med',
'ExtraLight': 'ExtLt',
'Black': 'Blk',
}
if name in known_names:
return known_names[name]
# If aggressive is False create the mild short form
# aggressive == True: Always use first form of everything
# aggressive == False:
# - has no modifier: use the second form
# - 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)
for mod in FontnameTools.known_modifiers:
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):]
break
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 ])
if isinstance(subst, tuple):
return subst[form]
return name
@staticmethod
def short_styles(styles):
"""Shorten all style names in a list"""
return list(map(FontnameTools.shorten_style_name, styles))
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 ]
@staticmethod
def make_oblique_style(weights, styles):
"""Move "Oblique" from weights to styles for font naming purposes"""
@ -174,12 +203,77 @@ class FontnameTools:
( '(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 :->
( '(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
# 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
]
# From https://adobe-type-tools.github.io/font-tech-notes/pdfs/5088.FontNames.pdf
# The first short variant is from the linked table.
# The second (longer) short variant is from diverse fonts like Noto.
# We can
# - use the long form
# - use the very short form (first)
# - use mild short form:
# - has no modifier: use the second form
# - has modifier: use second form of mod plus first form of weights2
# - 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'),
}
known_weights2 = { # can take modifiers
'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'),
}
known_slopes = { # can not take modifiers
'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'),
}
@staticmethod
def is_keep_regular(basename):
"""This has been decided by the font designers, we need to mimic that (for comparison purposes)"""
@ -241,18 +335,18 @@ 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 = [ 'Thin', 'Light', 'ExtraLight', 'SemiBold', 'Demi',
'SemiLight', 'Medium', 'Black', 'ExtraBold', 'Heavy',
'Oblique', 'Condensed', 'SemiCondensed', 'ExtraCondensed',
'Narrow', 'SemiNarrow', 'Retina', ]
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)
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'\(TTF\)', # Terminus (TTF)
r'\bCE\b', # ProggycleanTT CE
r'[12][cmp]n?', # MPlus
r'(?:uni-)?1[14]', # GohuFont uni

View File

@ -54,105 +54,8 @@ work as it always did.
### The Tests
In this directory there are two tests.
1. The first test checks the basics of the algorithm. It takes the filenames of all fonts in
`src/unpatched-fonts/`, then it calculates the naming and compares it to the original
naming in the font files. Ideally they would be equal.
2. The second test does a 'production run'. It patches each font in `src/unpatched-fonts/`
and patches it two times: Once without `--makegroups` and once with. Then it compares the
naming, and it also shows the original font naming (for comparison).
All tests base on these assumptions
* Fullname must be roughly equal
* Fontname must be roughly equal
* Familyname must roughly equal, order of all words does not matter
_(Order of words is ignored with test 2 only)_
* SubFamilyname must be equal, order of words does not matter
_(First word must be equal, order of other words is ignored with test 2 only)_
* Typographic names can be empty if the correct typographic name would be equal to the ordinary name
* Tests are done case insensitive
* Some special exemptions are made (see `lenient_cmp()` in test scripts)
#### Test 1
`fontforge name_parser_test1 ../../../src/unpatched-fonts/**/*.[ot]tf 2>/dev/null`
This test takes the filename of a font, parses it and generates names from it. Then the actual
font is opened and the generated names are compared with the stored names. This test is used
to test the algorithm itself. Of course no SIL table is active as we want to preserve the original
names.
The output shows all the names, always two lines: first the generated names, then the readout
names. If there are differences the generated names are tagged with `+` and the readout ones
with `-`. If there are differences the actually different name part is marked with an `X`.
The differences have reasons, and there is a file with textual explanations for them. So far
all differences are 'ok'. A new run of the script will compare all differences with the stored
ones and alert the user if a new difference is detected (or a difference vanished). In this
way changes of the algorithm can be tested with a wide base of inputs.
#### Test 2
`fontforge name_parser_test2 ../../../src/unpatched-fonts/**/*.[ot]tf 2>/dev/null`
This test compares actually patched fonts. Every font in `src/unpatched-fonts/` is patched two
times: First with the 'old/classic' `font-patcher` naming, and second with the new naming
algorithm in action (by specifying `--makegroups`). Again the name parts are compared with some
lenience and an output generated like test 1 does.
Also again a file with known differences (with explanations) is read, and any new or vanished
differences are reported. In the report an additional line is given, tagged with `>`, that
contains the names of the original font, for human interpretation (often the reason
for a difference is obvious, because the classic `font-patcher` dropped information.
_Note: Fonts `NotoColorEmoji` and `Lilex-VF` are not patchable, and thus ignored_
_Note: Fonts `iosevka-heavyoblique`, `iosevka-term-heavyoblique`, `iosevka-mediumoblique` crash my machine and are ignored_
### Differences
The naming of the patched fonts, if `--makegroups` is applied, will be different. Of course, that is the goal.
What are the differences in particular:
* `Nerd Font` is not added in the end, but after the extended base name before the style
* The SubFamily contains only 4 Styles max: Regular, Bold, Italic, Bold-Italic
* The Noto fonts retain their abbreviated style names in the Family information
* `Nerd Font Mono` fonts get a `M` in windows mode (I believe that has been left out accidentally before)
Apart from these general things, all changes are documented in detail in the `name_parser_test2` issues file.
Here is an overview over all the things that get renamed and why:
| Occurences | Description |
|------------|-------------|
| 511 | Add weight/style to family |
| 43 | The fonts name is M+ not Mplus |
| 36 | Drop unneeded Typogr.Family/Typogr.Style |
| 26 | 'Term' is missing from Family |
| 22 | Change regular-equivalent name to Regular |
| 19 | Put Oblique into own SubFamily (and mark it as italic) |
| 5 | Drop Regular from Style |
| 4 | We handle (TTF) as sub-name |
| 4 | Fullname has been missing 'Nerd Font' |
| 4 | Bold / Bold-Italic are just a styles of Regular |
| 2 | Original font broken (Light in Family) |
| 2 | Classify Medium as own weigt and not Bold |
| 2 | Bold and Italic are styles of a basefont |
| 1 | Weight Condensed does not belong to base name |
| 1 | Use only Regular/Bold/Italic in SubFamily |
| 1 | Handle Retina as Weight and not Style |
| 1 | Do not call Semibold Light-Bold |
From the count we see that almost all fonts are affected by incorrect Family naming.
### Further steps
One can examine all the (current) naming differences in the `name_parser_test2.known_issues`
file. The Explanation is followed by three lines of names: source-file, patched-with-makegroups,
and patched-classic.
The Explanation sorts most differences into common groups. This helps to weed out
explanations that might do not need much attention.
In this directory were two tests. If interested you need to go back in the git history.
They are not needed anymore.
### Helper scripts
@ -166,30 +69,3 @@ way:
* `query_version` `font_name`
They can be invoked like this `$ fontforge query_sfnt foo.ttf`.
### Appendix: The `name_parser_test*.known_issues` files
All differences of 'old' to 'new' naming (if not one of the very general kind like resorting of
the words) are documented in the `known_issues` files. For each difference a reason is given.
The files consist of entries that spans 3 (for test 1) or 4 (for test 2) lines.
| Line starts with | Contents |
|------------------|----------|
| # | Reson for the difference (or `AUTOGENERATED`) |
| > | Naming fo the original/source font (only test 2) |
| + | Naming with `--makegroups` (new naming) |
| - | Naming classically generated by font-patcher |
After any test run a `known_issues.new` file is generated. It contains all the issues
from the `known_issues` file that were detected. Original issues that are not
existing anymore are at the bottom of the new file, clearly marked as such. If new
(previously unexplained) issues were detected they show up with the `AUTOGENERATED`
reason.
After adding new fonts or replacing font files the test can be rerun. If there are issues
in the `.new` file they should be documented there, and the `.new` file replace the
original `known_issues` file (after removing possible 'obsolete' issues that are listed in
the bottom of the new file).
In this way one can tweak the parser code and compare very easily what a change
means for all the fonts, which will break or be repaired.

View File

@ -1,19 +0,0 @@
cat name_parser_test2.known_issues | grep '####' | sed -E 's/#### //;s/\] /]\n/g;s/ \[[0-9]+\]//g' | sort | uniq -c | sort -nr
511 Add weight/style to family
43 The fonts name is M+ not Mplus
36 Drop unneeded Typogr.Family/Typogr.Style
26 'Term' is missing from Family
22 Change regular-equivalent name to Regular
19 Put Oblique into own SubFamily (and mark it as italic)
5 Drop Regular from Style
4 We handle (TTF) as sub-name
4 Fullname has been missing 'Nerd Font'
4 Bold / Bold-Italic are just a styles of Regular
2 Original font broken (Light in Family)
2 Classify Medium as own weigt and not Bold
2 Bold and Italic are styles of a basefont
1 Weight Condensed does not belong to base name
1 Use only Regular/Bold/Italic in SubFamily
1 Handle Retina as Weight and not Style
1 Do not call Semibold Light-Bold

View File

@ -1,160 +0,0 @@
#!/usr/bin/env python3
# coding=utf8
import sys
import re
import os.path
import fontforge
from FontnameParser import FontnameParser
###### Some helpers
def get_sfnt_dict(font):
"""Extract SFNT table as nice dict"""
d = []
for i, el in enumerate(font.sfnt_names):
d += [(el[1], el[2])]
return dict(d)
def format_names(header, *stuff):
"""Unify outputs (with header)"""
f = '{:1.1}|{:50.50} |{:1.1}| {:50.50} |{:1.1}| {:30.30} |{:1.1}| {:30.30} |{:1.1}| {:30.30} |{:1.1}| {:.30}'
if header:
d = '------------------------------------------------------------'
return f.format(*stuff) + '\n' + f.format('', d, d, d, d, d, d, d, d, d, d, d)
return f.format(*stuff).rstrip()
def lenient_cmp(s1, s2):
"""Compare two font name (parts) but be a bit lenient ;->"""
# We do not care about:
# - Case
# - "Display" vs "Disp" (in Noto)
# Allow for "IBM 3278" name
s = [ s1, s2 ]
for i in range(2):
# Usually given transform from 'their' to 'our' style
s[i] = s[i].lower()
s[i] = re.sub(r'\bdisp\b', 'display', s[i]) # Noto
s[i] = s[i].replace('ibm 3270', '3270') # 3270
s[i] = s[i].replace('3270-', '3270 ') # 3270
s[i] = s[i].replace('lekton-', 'lekton ') # Lekton
s[i] = s[i].replace('semi-narrow', 'seminarrow') # 3270
s[i] = s[i].replace('bolditalic', 'bold italic')
s[i] = re.sub(r'\bfor\b', '', s[i]) # Meslo, Monofur
s[i] = re.sub(r'\bpowerline\b', '', s[i]) # Meslo, Monofur
s[i] = s[i].replace('fira mono', 'fura mono') # Obviously someone forgot to rename the fonts in Fira/
s[i] = s[i].replace('aurulentsansmono-', 'aurulent sans mono ') # Aurulent fullname oddity
s[i] = s[i].replace('mononoki-', 'mononoki ') # Mononoki has somtimes a dash
s[i] = re.sub(r'\br\b', 'regular', s[i]) # Nonstandard style in Agave
s[i] = re.sub(r'(bitstream vera sans mono.*) oblique', r'\1 italic', s[i]) # They call it Oblique but the filename says Italic
s[i] = re.sub(r'gohufont (uni-)?(11|14)', 'gohufont', s[i]) # They put the 'name' into the subfamily/weight
s[i] = s[i].replace('xltobl', 'extralight oblique') # Iosevka goes inventing names
s[i] = re.sub(r'proggyclean(?!TT)( ?)', 'proggycleantt\1', s[i]) # ProggyClean has no TT in filename
s[i] = re.sub(r' +', ' ', s[i]).strip()
return s[0] == s[1]
TEST_TABLE = [
( '(m)plus', '\\1+'),
( 'IAWriter', 'iA Writer'),
( 'IBMPlex', 'IBM Plex'),
( 'Vera', 'Bitstream Vera Sans'),
]
###### Let's go!
if len(sys.argv) < 2:
print('Usage: {} font_name [font_name ...]\n'.format(sys.argv[0]))
sys.exit(1)
try:
with open(sys.argv[0] + '.known_issues', 'r') as f:
known_issues = f.read().splitlines()
# known_issues = [line.rstrip() for line in known_issues]
print('Found {:.0f} known issues'.format(len(known_issues) / 3)) # approx ;)
except OSError:
print('Can not open known_issues file')
known_issues = []
new_issues = open(sys.argv[0] + '.known_issues.new', 'w')
print('Examining {} font files'.format(len(sys.argv) - 1))
all_files = 0
issue_files = 0
known_files = 0
print(format_names(True, '', 'Filename', '', 'Fullname', '', 'Family', '', 'Subfamily', '', 'Typogr. Family', '', 'Typogr. Subfamily'))
for filename in sys.argv[1:]:
fullfile = os.path.basename(filename)
fname = os.path.splitext(fullfile)[0]
if fname == 'NotoColorEmoji':
continue # font is not patchable
n = FontnameParser(fname).enable_short_families(False, 'Noto').add_name_substitution_table(TEST_TABLE)
# Example for name injection:
# n.inject_suffix("Nerd Font Complete Mono", "Nerd Font Complete M", "Nerd Font Mono")
font = fontforge.open(filename, 1)
sfnt = get_sfnt_dict(font)
font.close()
all_files += 1
sfnt_full = sfnt['Fullname']
sfnt_fam = sfnt['Family']
sfnt_subfam = sfnt['SubFamily']
sfnt_pfam = sfnt['Preferred Family'] if 'Preferred Family' in sfnt else ''
sfnt_psubfam = sfnt['Preferred Styles'] if 'Preferred Styles' in sfnt else ''
t1 = not lenient_cmp(sfnt_full, n.fullname())
t2 = not lenient_cmp(sfnt_fam, n.family())
t3 = not lenient_cmp(sfnt_subfam, n.subfamily())
t4 = not lenient_cmp(sfnt_pfam, n.preferred_family())
t5 = not lenient_cmp(sfnt_psubfam, n.preferred_styles())
# Lenience: Allow for dropping unneeded prefered stuff:
# New (sub)family is same as old preferred sub(family)
if t4 and n.preferred_family() == '' and sfnt_pfam.lower() == n.family().lower():
t4 = False
if t5 and n.preferred_styles() == '' and sfnt_psubfam.lower() == n.subfamily().lower():
t5 = False
if t1 or t2 or t3 or t4 or t5:
m1 = '+'; m2 = '-'
else:
m1 = ''; m2 = ''
if not n.parse_ok:
m1 = '!'
t1_ = 'X' if t1 else ''
t2_ = 'X' if t2 else ''
t3_ = 'X' if t3 else ''
t4_ = 'X' if t4 else ''
t5_ = 'X' if t5 else ''
o1 = format_names(False, m1, fullfile, t1_, n.fullname(), t2_, n.family(), t3_, n.subfamily(), t4_, n.preferred_family(), t5_, n.preferred_styles())
o2 = format_names(False, m2, fullfile, '', sfnt_full, '', sfnt_fam, '', sfnt_subfam, '', sfnt_pfam, '', sfnt_psubfam)
if len(m1):
issue_files += 1
if not o1 in known_issues or not o2 in known_issues:
new_issues.writelines(['#### AUTOGENERATED\n', o1 + '\n', o2 + '\n'])
else:
known_files += 1
idx = known_issues.index(o1) - 1 # should be the index of the explanation line
if known_issues[idx][0] != '#':
sys.exit('Problem with known issues file, line', known_issues.index(o1))
new_issues.writelines([known_issues[idx] + '\n', o1 + '\n', o2 + '\n'])
# remove known_issue triplet
known_issues.pop(idx)
known_issues.pop(idx)
known_issues.pop(idx)
print(o1, o2, sep='\n')
print('Fonts with different name rendering: {}/{} ({}/{} are in known_issues)'.format(issue_files, all_files, known_files, issue_files))
if len(known_issues) > 0:
print('There are {} lines not needed in known_issues, appending commented out in new known_issues'.format(len(known_issues)))
new_issues.write('\n#### The following lines are not needed anymore\n\n')
for l in known_issues:
new_issues.writelines([' ', l, '\n'])
new_issues.close()

View File

@ -1,207 +0,0 @@
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|3270Medium.otf | | 3270 Medium |X| 3270 Medium |X| Regular |X| 3270 |X| Medium
-|3270Medium.otf | | 3270-Medium | | IBM 3270 | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|3270Medium.ttf | | 3270 Medium |X| 3270 Medium |X| Regular |X| 3270 |X| Medium
-|3270Medium.ttf | | 3270-Medium | | IBM 3270 | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, obviously for them Medium is Regular
+|3270Narrow.otf | | 3270 Narrow | | 3270 Narrow |X| Regular |X| 3270 |X| Narrow
-|3270Narrow.otf | | 3270 Narrow | | IBM 3270 Narrow | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, obviously for them Medium is Regular
+|3270Narrow.ttf | | 3270 Narrow | | 3270 Narrow |X| Regular |X| 3270 |X| Narrow
-|3270Narrow.ttf | | 3270 Narrow | | IBM 3270 Narrow | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, obviously for them Medium is Regular
+|3270SemiNarrow.otf | | 3270 SemiNarrow | | 3270 SemiNarrow |X| Regular |X| 3270 |X| SemiNarrow
-|3270SemiNarrow.otf | | 3270 Semi-Narrow | | IBM 3270 Semi-Narrow | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, obviously for them Medium is Regular
+|3270SemiNarrow.ttf | | 3270 SemiNarrow | | 3270 SemiNarrow |X| Regular |X| 3270 |X| SemiNarrow
-|3270SemiNarrow.ttf | | 3270 Semi-Narrow | | IBM 3270 Semi-Narrow | | Medium | | | |
#### Drop special/unexpected name in Typographic Family
+|Anonymice Powerline.ttf | | Anonymice Powerline | | Anonymice Powerline | | Regular |X| | |
-|Anonymice Powerline.ttf | | Anonymice Powerline | | Anonymice Powerline | | Regular | | Anonymous Pro for Powerline | | Regular
#### Font file says it is italic and not oblique
+|VeraMono-Bold-Italic.ttf | | Bitstream Vera Sans Mono Bold Italic | | Bitstream Vera Sans Mono |X| Bold Italic | | | |
-|VeraMono-Bold-Italic.ttf | | Bitstream Vera Sans Mono Bold Oblique | | Bitstream Vera Sans Mono | | Bold Oblique | | | |
#### Font file says it is italic and not oblique
+|VeraMono-Italic.ttf | | Bitstream Vera Sans Mono Italic | | Bitstream Vera Sans Mono |X| Italic | | | |
-|VeraMono-Italic.ttf | | Bitstream Vera Sans Mono Oblique | | Bitstream Vera Sans Mono | | Oblique | | | |
#### Limit Subfamiliy to 4 standard styles, Roman is usually equivalent to Regular
+|VeraMono.ttf | | Bitstream Vera Sans Mono | | Bitstream Vera Sans Mono |X| Regular | | | |
-|VeraMono.ttf | | Bitstream Vera Sans Mono | | Bitstream Vera Sans Mono | | Roman | | | |
#### Limit Subfamiliy to 4 standard styles, Book is usually equivalent to Regular
!|DaddyTimeMono.ttf | | DaddyTimeMono | | DaddyTimeMono |X| Regular | | | |
-|DaddyTimeMono.ttf | | DaddyTimeMono | | DaddyTimeMono | | Book | | DaddyTimeMono | |
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|DejaVuSansMono-BoldOblique.ttf | | DejaVu Sans Mono Bold Oblique |X| DejaVu Sans Mono Oblique |X| Bold Italic |X| DejaVu Sans Mono |X| Bold Oblique
-|DejaVuSansMono-BoldOblique.ttf | | DejaVu Sans Mono Bold Oblique | | DejaVu Sans Mono | | Bold Oblique | | | |
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|DejaVuSansMono-Oblique.ttf | | DejaVu Sans Mono Oblique |X| DejaVu Sans Mono Oblique |X| Italic |X| DejaVu Sans Mono |X| Oblique
-|DejaVuSansMono-Oblique.ttf | | DejaVu Sans Mono Oblique | | DejaVu Sans Mono | | Oblique | | | |
#### Limit Subfamiliy to 4 standard styles, Book is usually equivalent to Regular
+|DejaVuSansMono.ttf | | DejaVu Sans Mono | | DejaVu Sans Mono |X| Regular | | | |
-|DejaVuSansMono.ttf | | DejaVu Sans Mono | | DejaVu Sans Mono | | Book | | | |
#### No need to have Typographic Family/Subfamily if it is identical to normal Family/Subfamily
+|FuraMono-Bold Powerline.otf | | Fura Mono Powerline Bold | | Fura Mono Powerline | | Bold |X| | |
-|FuraMono-Bold Powerline.otf | | Fira Mono Bold for Powerline | | Fira Mono for Powerline | | Bold | | Fira Mono for Powerline | |
#### False positive, move Powerline from end to middle, Powerline will be dropped when patching anyhow
+|FuraMono-Medium Powerline.otf | | Fura Mono Powerline Medium | | Fura Mono Powerline Medium | | Regular |X| Fura Mono Powerline | | Medium
-|FuraMono-Medium Powerline.otf | | Fira Mono Medium for Powerline | | Fira Mono Medium for Powerline | | Regular | | Fira Mono Medium for Powerline | | Medium
#### No need to have Typographic Family/Subfamily if it is identical to normal Family/Subfamily
+|FuraMono-Regular Powerline.otf | | Fura Mono Powerline | | Fura Mono Powerline | | Regular |X| | |
-|FuraMono-Regular Powerline.otf | | Fira Mono | | Fira Mono for Powerline | | Regular | | Fira Mono for Powerline | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|gohufont-11.ttf | | Gohufont 11 | | Gohufont 11 |X| Regular | | | |
-|gohufont-11.ttf | | GohuFont | | GohuFont | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|gohufont-14.ttf | | Gohufont 14 | | Gohufont 14 |X| Regular | | | |
-|gohufont-14.ttf | | GohuFont | | GohuFont | | 14 | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|gohufont-uni-11.ttf | | Gohufont uni-11 | | Gohufont uni-11 |X| Regular | | | |
-|gohufont-uni-11.ttf | | GohuFont | | GohuFont | | Medium | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|gohufont-uni-14.ttf | | Gohufont uni-14 | | Gohufont uni-14 |X| Regular | | | |
-|gohufont-uni-14.ttf | | GohuFont | | GohuFont | | uni-14 | | | |
#### Limit Subfamiliy to 4 standard styles, Normal is usually equivalent to Regular
+|heavy_data.ttf | | Heavy Data | | Heavy Data |X| Regular | | | |
-|heavy_data.ttf | | Heavy Data | | Heavy Data | | Normal | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|Hermit-light.otf | | Hermit Light |X| Hermit Light |X| Regular |X| Hermit |X| Light
-|Hermit-light.otf | | Hermit Light | | Hermit | | light | | | |
#### Limit Subfamiliy to 4 standard styles, put Subfamily name into Family instead
+|Hermit-medium.otf | | Hermit Medium |X| Hermit Medium |X| Regular |X| Hermit |X| Medium
-|Hermit-medium.otf | | Hermit Medium | | Hermit | | medium | | | |
#### Limit Subfamiliy to 4 standard styles, put Bold into Subfamily name
+|iAWriterDuospace-Bold.otf | | iA Writer Duospace Bold |X| iA Writer Duospace |X| Bold | | | |
-|iAWriterDuospace-Bold.otf | | iA Writer Duospace Bold | | iA Writer Duospace Bold | | Regular | | iA Writer Duospace | | Bold
#### Limit Subfamiliy to 4 standard styles, put Bold into Subfamily name
+|iAWriterDuospace-Bold.ttf | | iA Writer Duospace Bold |X| iA Writer Duospace |X| Bold | | | |
-|iAWriterDuospace-Bold.ttf | | iA Writer Duospace Bold | | iA Writer Duospace Bold | | Regular | | iA Writer Duospace | | Bold
#### Limit Subfamiliy to 4 standard styles, put Bold into Subfamily name
+|iAWriterDuospace-BoldItalic.otf | | iA Writer Duospace Bold Italic |X| iA Writer Duospace |X| Bold Italic | | |X|
-|iAWriterDuospace-BoldItalic.otf | | iA Writer Duospace BoldItalic | | iA Writer Duospace Bold | | Italic | | iA Writer Duospace | | BoldItalic
#### Limit Subfamiliy to 4 standard styles, put Bold into Subfamily name
+|iAWriterDuospace-BoldItalic.ttf | | iA Writer Duospace Bold Italic |X| iA Writer Duospace |X| Bold Italic | | |X|
-|iAWriterDuospace-BoldItalic.ttf | | iA Writer Duospace BoldItalic | | iA Writer Duospace Bold | | Italic | | iA Writer Duospace | | BoldItalic
#### Ignore naming part Text
+|IBMPlexMono-TextItalic.ttf | | IBM Plex Mono Text Italic | | IBM Plex Mono Text | | Italic |X| |X|
-|IBMPlexMono-TextItalic.ttf | | IBM Plex Mono Text Italic | | IBM Plex Mono Text | | Italic | | IBM Plex Mono | | Text Italic
#### Ignore naming part Text
+|IBMPlexMono-Text.ttf | | IBM Plex Mono Text | | IBM Plex Mono Text | | Regular |X| |X|
-|IBMPlexMono-Text.ttf | | IBM Plex Mono Text | | IBM Plex Mono Text | | Regular | | IBM Plex Mono | | Text
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-boldoblique.ttf | | Iosevka Bold Oblique | | Iosevka Oblique |X| Bold Italic | | Iosevka | | Bold Oblique
-|iosevka-boldoblique.ttf | | Iosevka Bold Oblique | | Iosevka Oblique | | Bold | | Iosevka | | Bold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-boldoblique.ttf | | Iosevka Term Bold Oblique | | Iosevka Term Oblique |X| Bold Italic | | Iosevka Term | | Bold Oblique
-|iosevka-term-boldoblique.ttf | | Iosevka Term Bold Oblique | | Iosevka Term Oblique | | Bold | | Iosevka Term | | Bold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-extraboldoblique.ttf | | Iosevka ExtraBold Oblique | | Iosevka ExtraBold Oblique |X| Italic | | Iosevka | | ExtraBold Oblique
-|iosevka-extraboldoblique.ttf | | Iosevka Extrabold Oblique | | Iosevka Extrabold Oblique | | Regular | | Iosevka | | Extrabold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-extraboldoblique.ttf | | Iosevka Term ExtraBold Oblique | | Iosevka Term ExtraBold Oblique |X| Italic | | Iosevka Term | | ExtraBold Oblique
-|iosevka-term-extraboldoblique.ttf | | Iosevka Term Extrabold Oblique | | Iosevka Term Extrabold Oblique | | Regular | | Iosevka Term | | Extrabold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-extralightoblique.ttf | | Iosevka ExtraLight Oblique | | Iosevka ExtraLight Oblique |X| Italic | | Iosevka | | ExtraLight Oblique
-|iosevka-extralightoblique.ttf | | Iosevka Extralight Oblique | | Iosevka Extralight Oblique | | Regular | | Iosevka | | Extralight Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-extralightoblique.ttf | | Iosevka Term ExtraLight Oblique | | Iosevka Term ExtraLight Obliqu |X| Italic | | Iosevka Term | | ExtraLight Oblique
-|iosevka-term-extralightoblique.ttf | | Iosevka Term Extralight Oblique | | Iosevka Term XLtObl | | Regular | | Iosevka Term | | Extralight Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-heavyoblique.ttf | | Iosevka Heavy Oblique | | Iosevka Heavy Oblique |X| Italic | | Iosevka | | Heavy Oblique
-|iosevka-heavyoblique.ttf | | Iosevka Heavy Oblique | | Iosevka Heavy Oblique | | Regular | | Iosevka | | Heavy Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-heavyoblique.ttf | | Iosevka Term Heavy Oblique | | Iosevka Term Heavy Oblique |X| Italic | | Iosevka Term | | Heavy Oblique
-|iosevka-term-heavyoblique.ttf | | Iosevka Term Heavy Oblique | | Iosevka Term Heavy Oblique | | Regular | | Iosevka Term | | Heavy Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-lightoblique.ttf | | Iosevka Light Oblique | | Iosevka Light Oblique |X| Italic | | Iosevka | | Light Oblique
-|iosevka-lightoblique.ttf | | Iosevka Light Oblique | | Iosevka Light Oblique | | Regular | | Iosevka | | Light Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-lightoblique.ttf | | Iosevka Term Light Oblique | | Iosevka Term Light Oblique |X| Italic | | Iosevka Term | | Light Oblique
-|iosevka-term-lightoblique.ttf | | Iosevka Term Light Oblique | | Iosevka Term Light Oblique | | Regular | | Iosevka Term | | Light Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-mediumoblique.ttf | | Iosevka Medium Oblique | | Iosevka Medium Oblique |X| Italic | | Iosevka | | Medium Oblique
-|iosevka-mediumoblique.ttf | | Iosevka Medium Oblique | | Iosevka Medium Oblique | | Regular | | Iosevka | | Medium Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-mediumoblique.ttf | | Iosevka Term Medium Oblique | | Iosevka Term Medium Oblique |X| Italic | | Iosevka Term | | Medium Oblique
-|iosevka-term-mediumoblique.ttf | | Iosevka Term Medium Oblique | | Iosevka Term Medium Oblique | | Regular | | Iosevka Term | | Medium Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-oblique.ttf | | Iosevka Oblique | | Iosevka Oblique |X| Italic | | Iosevka | | Oblique
-|iosevka-oblique.ttf | | Iosevka Oblique | | Iosevka Oblique | | Regular | | Iosevka | | Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-oblique.ttf | | Iosevka Term Oblique | | Iosevka Term Oblique |X| Italic | | Iosevka Term | | Oblique
-|iosevka-term-oblique.ttf | | Iosevka Term Oblique | | Iosevka Term Oblique | | Regular | | Iosevka Term | | Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-semiboldoblique.ttf | | Iosevka SemiBold Oblique | | Iosevka SemiBold Oblique |X| Italic | | Iosevka | | SemiBold Oblique
-|iosevka-semiboldoblique.ttf | | Iosevka Semibold Oblique | | Iosevka Semibold Oblique | | Regular | | Iosevka | | Semibold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-semiboldoblique.ttf | | Iosevka Term SemiBold Oblique | | Iosevka Term SemiBold Oblique |X| Italic | | Iosevka Term | | SemiBold Oblique
-|iosevka-term-semiboldoblique.ttf | | Iosevka Term Semibold Oblique | | Iosevka Term Semibold Oblique | | Regular | | Iosevka Term | | Semibold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-term-thinoblique.ttf | | Iosevka Term Thin Oblique | | Iosevka Term Thin Oblique |X| Italic | | Iosevka Term | | Thin Oblique
-|iosevka-term-thinoblique.ttf | | Iosevka Term Thin Oblique | | Iosevka Term Thin Oblique | | Regular | | Iosevka Term | | Thin Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead (and mark it Italic so applications know it's slanted) & Keep the grouping in Typographic Family
+|iosevka-thinoblique.ttf | | Iosevka Thin Oblique | | Iosevka Thin Oblique |X| Italic | | Iosevka | | Thin Oblique
-|iosevka-thinoblique.ttf | | Iosevka Thin Oblique | | Iosevka Thin Oblique | | Regular | | Iosevka | | Thin Oblique
#### Limit Subfamiliy to 4 standard styles, put Retina into Family instead
+|Monoid-Retina.ttf | | Monoid Retina |X| Monoid Retina |X| Regular |X| Monoid |X| Retina
-|Monoid-Retina.ttf | | Monoid Retina | | Monoid | | Retina | | | |
#### Remove nonstandard BoldItalic typographic style
+|mononoki-BoldItalic.ttf | | Mononoki Bold Italic | | Mononoki | | Bold Italic | | |X|
-|mononoki-BoldItalic.ttf | | mononoki Bold Italic | | mononoki | | Bold Italic | | | | BoldItalic
#### They say SemiBold is the same as Light Bold, we can not generalize this and make SemiBold self standing
+|overpass-mono-semibold.otf | | Overpass Mono SemiBold |X| Overpass Mono SemiBold |X| Regular | | Overpass Mono | | SemiBold
-|overpass-mono-semibold.otf | | Overpass Mono SemiBold | | Overpass Mono Light | | Bold | | Overpass Mono | | SemiBold
#### They say SemiBold is the same as Light Bold, we can not generalize this and make SemiBold self standing
+|overpass-semibold.otf | | Overpass SemiBold |X| Overpass SemiBold |X| Regular | | Overpass | | SemiBold
-|overpass-semibold.otf | | Overpass SemiBold | | Overpass Light | | Bold | | Overpass | | SemiBold
#### Nonstandard font naming: fullname shall be same as familyname plus more
+|ProFontIIx.ttf | | ProFont IIx |X| ProFont IIx | | Regular | | | |
-|ProFontIIx.ttf | | ProFont IIx | | ProFontIIx | | Regular | | | |
#### We are fine here (just list with exclamation mark because it is a potentially problematic case)
!|ProFontWindows.ttf | | ProFontWindows | | ProFontWindows | | Regular | | | |
|ProFontWindows.ttf | | ProFontWindows | | ProFontWindows | | Regular | | | |
#### No mention of TT in file name
+|ProggyCleanCE.ttf |X| ProggyClean CE |X| ProggyClean CE | | Regular | | | |
-|ProggyCleanCE.ttf | | ProggyCleanTT CE | | ProggyCleanTT CE | | Regular | | | |
#### No mention of TT in file name
!|ProggyClean.ttf |X| ProggyClean |X| ProggyClean | | Regular | | | |
-|ProggyClean.ttf | | ProggyCleanTT | | ProggyCleanTT | | Regular | | | |
#### No mention of TT in file name
+|ProggyCleanSZ.ttf |X| ProggyClean SZ |X| ProggyClean SZ | | Regular | | | |
-|ProggyCleanSZ.ttf | | ProggyCleanTTSZ | | ProggyCleanTTSZ | | Regular | | | |
#### They put one name part in parens
+|TerminusTTF-Bold Italic-4.40.1.ttf |X| Terminus TTF Bold Italic |X| Terminus TTF | | Bold Italic | | | |
-|TerminusTTF-Bold Italic-4.40.1.ttf | | Terminus (TTF) Bold Italic | | Terminus (TTF) | | Bold Italic | | | |
#### They put one name part in parens
+|TerminusTTF-Bold-4.40.1.ttf |X| Terminus TTF Bold |X| Terminus TTF | | Bold | | | |
-|TerminusTTF-Bold-4.40.1.ttf | | Terminus (TTF) Bold | | Terminus (TTF) | | Bold | | | |
#### They put one name part in parens
+|TerminusTTF-Italic-4.40.1.ttf |X| Terminus TTF Italic |X| Terminus TTF | | Italic | | | |
-|TerminusTTF-Italic-4.40.1.ttf | | Terminus (TTF) Italic | | Terminus (TTF) | | Italic | | | |
#### They put one name part in parens
+|TerminusTTF-4.40.1.ttf |X| Terminus TTF |X| Terminus TTF |X| Regular | | | |
-|TerminusTTF-4.40.1.ttf | | Terminus (TTF) | | Terminus (TTF) | | Medium | | | |
#### Ubuntu Condensed should be grouped with Ubuntu, that they didn't is an error?
+|Ubuntu-C.ttf | | Ubuntu Condensed | | Ubuntu Condensed | | Regular |X| Ubuntu |X| Condensed
-|Ubuntu-C.ttf | | Ubuntu Condensed | | Ubuntu Condensed | | Regular | | Ubuntu Condensed | | Regular
#### They say Medium is the same as Light Bold, we can not generalize this and make Medium self standing
+|Ubuntu-MI.ttf | | Ubuntu Medium Italic |X| Ubuntu Medium |X| Italic | | Ubuntu | | Medium Italic
-|Ubuntu-MI.ttf | | Ubuntu Medium Italic | | Ubuntu Light | | Bold Italic | | Ubuntu | | Medium Italic
#### They say Medium is the same as Light Bold, we can not generalize this and make Medium self standing
+|Ubuntu-M.ttf | | Ubuntu Medium |X| Ubuntu Medium |X| Regular | | Ubuntu | | Medium
-|Ubuntu-M.ttf | | Ubuntu Medium | | Ubuntu Light | | Bold | | Ubuntu | | Medium
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead
+|VictorMono-ExtraLightOblique.ttf | | Victor Mono ExtraLight Oblique |X| Victor Mono ExtraLight Oblique | | Italic | | Victor Mono | | ExtraLight Oblique
-|VictorMono-ExtraLightOblique.ttf | | Victor Mono ExtraLight Oblique | | Victor Mono ExtraLight | | Italic | | Victor Mono | | ExtraLight Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead
+|VictorMono-LightOblique.ttf | | Victor Mono Light Oblique |X| Victor Mono Light Oblique | | Italic | | Victor Mono | | Light Oblique
-|VictorMono-LightOblique.ttf | | Victor Mono Light Oblique | | Victor Mono Light | | Italic | | Victor Mono | | Light Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead
+|VictorMono-MediumOblique.ttf | | Victor Mono Medium Oblique |X| Victor Mono Medium Oblique | | Italic | | Victor Mono | | Medium Oblique
-|VictorMono-MediumOblique.ttf | | Victor Mono Medium Oblique | | Victor Mono Medium | | Italic | | Victor Mono | | Medium Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead
+|VictorMono-SemiBoldOblique.ttf | | Victor Mono SemiBold Oblique |X| Victor Mono SemiBold Oblique | | Italic | | Victor Mono | | SemiBold Oblique
-|VictorMono-SemiBoldOblique.ttf | | Victor Mono SemiBold Oblique | | Victor Mono SemiBold | | Italic | | Victor Mono | | SemiBold Oblique
#### Limit Subfamiliy to 4 standard styles, put Oblique into Family instead
+|VictorMono-ThinOblique.ttf | | Victor Mono Thin Oblique |X| Victor Mono Thin Oblique | | Italic | | Victor Mono | | Thin Oblique
-|VictorMono-ThinOblique.ttf | | Victor Mono Thin Oblique | | Victor Mono Thin | | Italic | | Victor Mono | | Thin Oblique

View File

@ -1,195 +0,0 @@
#!/usr/bin/env python3
# coding=utf8
import sys
import re
import os.path
import glob
import subprocess
import fontforge
###### Some helpers
def get_sfnt_dict(font):
"""Extract SFNT table as nice dict"""
d = []
for i, el in enumerate(font.sfnt_names):
d += [(el[1], el[2])]
return dict(d)
def extract_sfnt_data(sfnt):
"""Get the usual names out of the SFNT table"""
sfnt_full = sfnt['Fullname']
sfnt_fam = sfnt['Family']
sfnt_subfam = sfnt['SubFamily']
sfnt_pfam = sfnt['Preferred Family'] if 'Preferred Family' in sfnt else ''
sfnt_psubfam = sfnt['Preferred Styles'] if 'Preferred Styles' in sfnt else ''
return (sfnt_full, sfnt_fam, sfnt_subfam, sfnt_pfam, sfnt_psubfam)
def format_names(header, *stuff):
"""Unify outputs (with header)"""
f = '{:1.1}|{:50.50} |{:1.1}| {:65.65} |{:1.1}| {:55.55} |{:1.1}| {:30.30} |{:1.1}| {:40.40} |{:1.1}| {:.40}'
if header:
d = '------------------------------------------------------------'
return f.format(*stuff) + '\n' + f.format('', d, d, d, d, d, d, d, d, d, d, d)
return f.format(*stuff).rstrip()
def lenient_cmp(s1, s2, allow_shuffle_all):
"""Compare two font name (parts) but be a bit lenient ;->"""
# We do not care about:
# - Case
# - "Display" vs "Disp" (in Noto)
# Allow for "IBM 3278" name
s = [ s1, s2 ]
for i in range(2):
# Usually given transform from 'their' to 'our' style
s[i] = s[i].lower()
s[i] = re.sub(r'\bdisp\b', 'display', s[i]) # Noto
s[i] = s[i].replace('ibm 3270', '3270') # 3270
s[i] = s[i].replace('3270-', '3270 ') # 3270
s[i] = s[i].replace('lekton-', 'lekton ') # Lekton
s[i] = s[i].replace('semi-narrow', 'seminarrow') # 3270
s[i] = s[i].replace('bolditalic', 'bold italic')
s[i] = re.sub(r'\bfor\b', '', s[i]) # Meslo, Monofur
s[i] = re.sub(r'\bpowerline\b', '', s[i]) # Meslo, Monofur
s[i] = s[i].replace('fira mono', 'fura mono') # Obviously someone forgot to rename the fonts in Fira/
s[i] = s[i].replace('aurulentsansmono-', 'aurulent sans mono ') # Aurulent fullname oddity
s[i] = s[i].replace('mononoki-', 'mononoki ') # Mononoki has somtimes a dash
s[i] = re.sub(r'\br\b', 'regular', s[i]) # Nonstandard style in Agave
s[i] = re.sub(r'(bitstream vera sans mono.*) oblique', r'\1 italic', s[i]) # They call it Oblique but the filename says Italic
s[i] = re.sub(r'gohufont (uni-)?(11|14)', 'gohufont', s[i]) # They put the 'name' into the subfamily/weight
s[i] = s[i].replace('xltobl', 'extralight oblique') # Iosevka goes inventing names
s[i] = re.sub(r'proggyclean(?!TT)( ?)', 'proggycleantt\1', s[i]) # ProggyClean has no TT in filename
s[i] = re.sub(r' +', ' ', s[i]).strip()
p = []
for e in s:
parts = e.split(' ')
if not allow_shuffle_all and len(parts) > 2:
tail = parts[1:]
tail.sort()
parts = [parts[0]] + tail
elif len(parts) > 1:
parts.sort()
p.append(' '.join(parts))
return p[0] == p[1]
###### Let's go!
if len(sys.argv) < 2:
print('Usage: {} font_name [font_name ...]\n'.format(sys.argv[0]))
sys.exit(1)
font_patcher = os.path.realpath(os.path.dirname(os.path.realpath(sys.argv[0]))+'/../../../font-patcher')
existing_font = glob.glob('*.[ot]tf')
if len(existing_font):
sys.exit('Would overwrite any existing *.ttf and *.otf, bailing out (remove them first)')
try:
with open(sys.argv[0] + '.known_issues', 'r') as f:
known_issues = f.read().splitlines()
# known_issues = [line.rstrip() for line in known_issues]
print('Found {:.0f} known issues'.format(len(known_issues) / 4)) # approx ;)
except OSError:
print('Can not open known_issues file')
known_issues = []
new_issues = open(sys.argv[0] + '.known_issues.new', 'w')
print('Examining {} font files'.format(len(sys.argv) - 1))
all_files = 0
issue_files = 0
known_files = 0
print(format_names(True, '', 'Filename', '', 'Fullname', '', 'Family', '', 'Subfamily', '', 'Typogr. Family', '', 'Typogr. Subfamily'))
for filename in sys.argv[1:]:
data = []
fullfile = os.path.basename(filename)
fname = os.path.splitext(fullfile)[0]
if fname == 'NotoColorEmoji':
continue # font is not patchable
if fname in [ 'iosevka-heavyoblique', 'iosevka-term-heavyoblique', 'iosevka-mediumoblique', 'Lilex-VF' ]:
continue # Patch resultant font not openable
log = open("log", "w")
log.write(filename)
log.close()
for option in ['--makegroups', '']:
cmd = ['fontforge', '--script', font_patcher, '--powerline', option, filename ]
cmd = [ c for c in cmd if len(c) ]
ff = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, encoding='utf8')
#ff = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8')
if ff.returncode:
print("\nERROR running command:\n {}\n\n{}".format(' '.join(cmd), ''))#ff.stdout))
sys.exit(1)
new_font = glob.glob('*.[ot]tf')
font = fontforge.open(new_font[0], 1)
sfnt = get_sfnt_dict(font)
font.close()
os.system('rm -f *.[ot]tf')
(sfnt_full, sfnt_fam, sfnt_subfam, sfnt_pfam, sfnt_psubfam) = extract_sfnt_data(sfnt)
data.append(( os.path.basename(new_font[0]), sfnt_full, sfnt_fam, sfnt_subfam, sfnt_pfam, sfnt_psubfam ))
all_files += 1
t1 = not lenient_cmp(data[0][1], data[1][1], False)
t2 = not lenient_cmp(data[0][2], data[1][2], False)
t3 = not lenient_cmp(data[0][3], data[1][3], True)
t4 = not lenient_cmp(data[0][4], data[1][4], False)
t5 = not lenient_cmp(data[0][5], data[1][5], True)
# Lenience: Allow for dropping unneeded prefered stuff:
# New (sub)family is same as old preferred sub(family)
if t4 and data[0][4] == '' and data[1][4].lower() == data[0][2].lower():
t4 = False
if t5 and data[0][5] == '' and data[1][5].lower() == data[0][3].lower():
t5 = False
if t1 or t2 or t3 or t4 or t5:
m1 = '+'; m2 = '-'
else:
m1 = ''; m2 = ''
t1_ = 'X' if t1 else ''
t2_ = 'X' if t2 else ''
t3_ = 'X' if t3 else ''
t4_ = 'X' if t4 else ''
t5_ = 'X' if t5 else ''
o1 = format_names(False, m1, data[0][0], t1_, data[0][1], t2_, data[0][2], t3_, data[0][3], t4_, data[0][4], t5_, data[0][5])
o2 = format_names(False, m2, data[1][0], '', data[1][1], '', data[1][2], '', data[1][3], '', data[1][4], '', data[1][5])
if len(m1):
issue_files += 1
font = fontforge.open(filename, 1)
sfnt = get_sfnt_dict(font)
font.close()
(sfnt_full, sfnt_fam, sfnt_subfam, sfnt_pfam, sfnt_psubfam) = extract_sfnt_data(sfnt)
o3 = format_names(False, '>', os.path.basename(filename), '', sfnt_full, '', sfnt_fam, '', sfnt_subfam, '', sfnt_pfam, '', sfnt_psubfam)
if not o1 in known_issues or not o2 in known_issues:
new_issues.writelines(['#### AUTOGENERATED\n', o3 + '\n', o1 + '\n', o2 + '\n'])
else:
known_files += 1
idx = known_issues.index(o1) - 2 # should be the index of the explanation line
if known_issues[idx][0] != '#':
sys.exit('Problem with known issues file, line', known_issues.index(o1))
new_issues.writelines([known_issues[idx] + '\n', o3 + '\n', o1 + '\n', o2 + '\n'])
# remove known_issue triplet
known_issues.pop(idx)
known_issues.pop(idx)
known_issues.pop(idx)
known_issues.pop(idx)
print(o1, o2, sep='\n')
print('Fonts with different name rendering: {}/{} ({}/{} are in known_issues)'.format(issue_files, all_files, known_files, issue_files))
if len(known_issues) > 0:
print('There are {} lines not needed in known_issues, appending commented out in new known_issues'.format(len(known_issues)))
new_issues.write('\n#### The following lines are not needed anymore\n\n')
for l in known_issues:
new_issues.writelines([' ', l, '\n'])
new_issues.close()

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
#!/usr/bin/env python3
# coding=utf8
#
# Usually called via
# $ fontforge query_names fontfile.tff 2>/dev/null
import sys
import os.path
@ -13,10 +16,10 @@ def get_sfnt_dict(font):
def format_names(header, *stuff):
"""Unify outputs (with header)"""
f = '{:1.1}|{:50.50} |{:1.1}| {:65.65} |{:1.1}| {:55.55} |{:1.1}| {:30.30} |{:1.1}| {:40.40} |{:1.1}| {:.40}'
f = '| {:50.50}|{:>2.2}| {:64.64}|{:>2.2}| {:64.64}|{:>2.2}| {:55.55}|{:>2.2}| {:30.30}|{:>2.2}| {:40.40}|{:>2.2}| {:40.40}|{:>2.2}|'
if header:
d = '------------------------------------------------------------'
return f.format(*stuff) + '\n' + f.format('', d, d, d, d, d, d, d, d, d, d, d)
d = ''
return f.format(*stuff) + '\n' + f.format(d, d, d, d, d, d, d, d, d, d, d, d, d, d).replace(' ', '-')
return f.format(*stuff).rstrip()
###### Let's go!
@ -27,7 +30,7 @@ if len(sys.argv) < 2:
print('Examining {} font files'.format(len(sys.argv) - 1))
print(format_names(True, '', 'Filename', '', 'Fullname', '', 'Family', '', 'Subfamily', '', 'Typogr. Family', '', 'Typogr. Subfamily'))
print(format_names(True, 'Filename', '', 'PS Name', '', 'Fullname', '', 'Family', '', 'Subfamily', '', 'Typogr. Family', '', 'Typogr. Subfamily', ''))
for filename in sys.argv[1:]:
fullfile = os.path.basename(filename)
@ -35,6 +38,7 @@ for filename in sys.argv[1:]:
font = fontforge.open(filename, 1)
sfnt = get_sfnt_dict(font)
psname = font.fontname
font.close()
sfnt_full = sfnt['Fullname']
@ -43,6 +47,14 @@ for filename in sys.argv[1:]:
sfnt_pfam = sfnt['Preferred Family'] if 'Preferred Family' in sfnt else ''
sfnt_psubfam = sfnt['Preferred Styles'] if 'Preferred Styles' in sfnt else ''
o2 = format_names(False, '', fullfile, '', sfnt_full, '', sfnt_fam, '', sfnt_subfam, '', sfnt_pfam, '', sfnt_psubfam)
o2 = format_names(False,
fullfile, str(len(fullfile)),
psname, str(len(psname)),
sfnt_full, str(len(sfnt_full)),
sfnt_fam, str(len(sfnt_fam)),
sfnt_subfam, str(len(sfnt_subfam)),
# show length zero if a zero length string is stored, show nothing if nothing is stored:
sfnt_pfam, str(len(sfnt_pfam)) if 'Preferred Family' in sfnt else '',
sfnt_psubfam, str(len(sfnt_psubfam)) if 'Preferred Family' in sfnt else '')
print(o2)

View File

@ -1,14 +0,0 @@
Add weight/style to family [1]
Use only Regular/Bold/Italic in SubFamily [2]
Classify Medium as own weight and not Bold [3]
Change regular-equivalent name to Regular [4]
Drop unneeded Typogr.Family/Typogr.Style [5]
Do not call Semibold Light-Bold [6]
Fullname has been missing 'Nerd Font' [7]
The fonts name is M+ not Mplus [8]
Handle Retina as Weight and not Style [9]
Bold and Italic are styles of a basefont [10]
Put Oblique into own SubFamily (and mark it as italic) [11]
'Term' is missing from Family [12]
Bold / Bold-Italic are just a styles of Regular [13]
Drop Regular from Style [14]

View File

@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals
# Change the script version when you edit this script:
script_version = "3.7.1"
script_version = "4.0.0"
version = "2.3.3"
projectName = "Nerd Fonts"
@ -22,6 +22,7 @@ import errno
import subprocess
import json
from enum import Enum
import logging
try:
import configparser
except ImportError:
@ -239,10 +240,10 @@ def force_panose_monospaced(font):
panose = list(font.os2_panose)
if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default)
panose[0] = 2 # make kind latin text and display
print(" Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')")
logger.info("Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')")
font.os2_panose = tuple(panose)
if panose[0] == 2 and panose[3] != 9:
print(" Setting Panose 'Proportion' to 'Monospaced' (was '{}')".format(panose_proportion_to_text(panose[3])))
logger.info("Setting Panose 'Proportion' to 'Monospaced' (was '%s')", panose_proportion_to_text(panose[3]))
panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced
font.os2_panose = tuple(panose)
@ -296,10 +297,22 @@ def get_old_average_x_width(font):
}
for g in weights:
if g not in font:
sys.exit("{}: Can not determine ancient style xAvgCharWidth".format(projectName))
logger.critical("Can not determine ancient style xAvgCharWidth")
sys.exit(1)
s += font[g].width * weights[g]
return int(s / 1000)
def create_filename(fonts):
""" Determine filename from font object(s) """
sfnt = { k: v for l, k, v in fonts[0].sfnt_names }
sfnt_pfam = sfnt.get('Preferred Family', sfnt['Family'])
sfnt_psubfam = sfnt.get('Preferred Styles', sfnt['SubFamily'])
if len(fonts) > 1:
return sfnt_pfam
if len(sfnt_psubfam) > 0:
sfnt_psubfam = '-' + sfnt_psubfam
return (sfnt_pfam + sfnt_psubfam).replace(' ', '')
class font_patcher:
def __init__(self, args):
@ -311,6 +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.onlybitmaps = 0
self.essential = set()
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
@ -336,7 +350,7 @@ class font_patcher:
# For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs
if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2:
print("Very wide and short font, disabling 2 cell Powerline glyphs")
logger.warning("Very wide and short font, disabling 2 cell Powerline glyphs")
self.font_extrawide = True
# Prevent opening and closing the fontforge font. Makes things faster when patching
@ -345,8 +359,12 @@ class font_patcher:
symfont = None
if not os.path.isdir(self.args.glyphdir):
sys.exit("{}: Can not find symbol glyph directory {} "
"(probably you need to download the src/glyphs/ directory?)".format(projectName, self.args.glyphdir))
logger.critical("Can not find symbol glyph directory %s "
"(probably you need to download the src/glyphs/ directory?)", self.args.glyphdir)
sys.exit(1)
if self.args.dry_run:
return
for patch in self.patch_set:
if patch['Enabled']:
@ -356,11 +374,13 @@ class font_patcher:
symfont.close()
symfont = None
if not os.path.isfile(self.args.glyphdir + patch['Filename']):
sys.exit("{}: Can not find symbol source for '{}'\n{:>{}} (i.e. {})".format(
projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename']))
logger.critical("Can not find symbol source for '%s' (i.e. %s)",
patch['Name'], self.args.glyphdir + patch['Filename'])
sys.exit(1)
if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK):
sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format(
projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename']))
logger.critical("Can not open symbol source for '%s' (i.e. %s)",
patch['Name'], self.args.glyphdir + patch['Filename'])
sys.exit(1)
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename']))
symfont.encoding = 'UnicodeFull'
@ -402,11 +422,11 @@ class font_patcher:
break
outfile = os.path.normpath(os.path.join(
sanitize_filename(self.args.outputdir, True),
sanitize_filename(sourceFont.familyname) + ".ttc"))
sanitize_filename(create_filename(sourceFonts)) + ".ttc"))
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer)
message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile)
else:
fontname = sourceFont.fullname
fontname = create_filename(sourceFonts)
if not fontname:
fontname = sourceFont.cidfontname
outfile = os.path.normpath(os.path.join(
@ -414,9 +434,11 @@ class font_patcher:
sanitize_filename(fontname) + self.args.extension))
bitmaps = str()
if len(self.sourceFont.bitmapSizes):
if not self.args.quiet:
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
logger.debug("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
bitmaps = str('otf') # otf/ttf, both is bf_ttf
if self.args.dry_run:
logger.debug("=====> Filename '{}'".format(outfile))
return
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
@ -426,8 +448,7 @@ class font_patcher:
source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile)
for idx in range(source_font.num_fonts):
if not self.args.quiet:
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts)
xwidth_s = ''
xwidth = self.xavgwidth[idx]
if isinstance(xwidth, int):
@ -438,26 +459,23 @@ class font_patcher:
dest_font.find_table([b'OS/2'], idx)
d_xwidth = dest_font.getshort('avgWidth')
if d_xwidth != xwidth:
if not self.args.quiet:
print("Changing xAvgCharWidth from {} to {}{}".format(d_xwidth, xwidth, xwidth_s))
logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s)
dest_font.putshort(xwidth, 'avgWidth')
dest_font.reset_table_checksum()
source_font.find_head_table(idx)
dest_font.find_head_table(idx)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
if not self.args.quiet:
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
logger.debug("Changing flags from 0x%X to 0x%X", dest_font.flags, dest_font.flags & ~0x08)
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
if source_font.lowppem != dest_font.lowppem:
if not self.args.quiet:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem)
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified:
dest_font.reset_table_checksum()
if dest_font.modified:
dest_font.reset_full_checksum()
except Exception as error:
print("Can not handle font flags ({})".format(repr(error)))
logger.error("Can not handle font flags (%s)", repr(error))
finally:
try:
source_font.close()
@ -465,12 +483,13 @@ class font_patcher:
except:
pass
if self.args.is_variable:
print("Warning: Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
logger.error("Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
print(message)
if self.args.postprocess:
subprocess.call([self.args.postprocess, outfile])
print("\nPost Processed: {}".format(outfile))
print("\n")
logger.info("Post Processed: %s", outfile)
def setup_name_backup(self, font):
@ -488,11 +507,8 @@ class font_patcher:
font.fullname = font.persistent["fullname"]
if isinstance(font.persistent["familyname"], str):
font.familyname = font.persistent["familyname"]
verboseAdditionalFontNameSuffix = " " + projectNameSingular
if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later
additionalFontNameSuffix = " " + projectNameAbbreviation
else:
additionalFontNameSuffix = verboseAdditionalFontNameSuffix
verboseAdditionalFontNameSuffix = ""
additionalFontNameSuffix = ""
if not self.args.complete:
# NOTE not all symbol fonts have appended their suffix here
if self.args.fontawesome:
@ -523,17 +539,24 @@ class font_patcher:
additionalFontNameSuffix += " WEA"
verboseAdditionalFontNameSuffix += " Plus Weather Icons"
# if all source glyphs included simplify the name
else:
additionalFontNameSuffix = " " + projectNameSingular + " Complete"
verboseAdditionalFontNameSuffix = " " + projectNameSingular + " Complete"
# add mono signifier to end of name
# add mono signifier to beginning of name suffix
if self.args.single:
additionalFontNameSuffix += " M"
verboseAdditionalFontNameSuffix += " Mono"
variant_abbrev = "M"
variant_full = " Mono"
elif self.args.nonmono and not self.symbolsonly:
variant_abbrev = "P"
variant_full = " Propo"
else:
variant_abbrev = ""
variant_full = ""
if FontnameParserOK and self.args.makegroups:
ps_suffix = projectNameAbbreviation + variant_abbrev + additionalFontNameSuffix
# add 'Nerd Font' to beginning of name suffix
verboseAdditionalFontNameSuffix = " " + projectNameSingular + variant_full + verboseAdditionalFontNameSuffix
additionalFontNameSuffix = " " + projectNameSingular + variant_full + additionalFontNameSuffix
if FontnameParserOK and self.args.makegroups > 0:
use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse
# Use fullname if it is 'equal' to the fontname
if font.fullname:
@ -545,12 +568,11 @@ class font_patcher:
# Gohu fontnames hide the weight, but the file names are ok...
if parser_name.startswith('Gohu'):
parser_name = os.path.splitext(os.path.basename(self.args.font))[0]
n = FontnameParser(parser_name)
n = FontnameParser(parser_name, logger)
if not n.parse_ok:
print("Have only minimal naming information, check resulting name. Maybe omit --makegroups option")
logger.warning("Have only minimal naming information, check resulting name. Maybe specify --makegroups 0")
n.drop_for_powerline()
n.enable_short_families(True, "Noto")
n.set_for_windows(self.args.windows)
n.enable_short_families(True, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ])
# All the following stuff is ignored in makegroups-mode
@ -598,23 +620,7 @@ class font_patcher:
if len(subFamily) == 0:
subFamily = "Regular"
if self.args.windows:
maxFamilyLength = 31
maxFontLength = maxFamilyLength - len('-' + subFamily)
familyname += " " + projectNameAbbreviation
if self.args.single:
familyname += "M"
fullname += " Windows Compatible"
# now make sure less than 32 characters name length
if len(fontname) > maxFontLength:
fontname = fontname[:maxFontLength]
if len(familyname) > maxFamilyLength:
familyname = familyname[:maxFamilyLength]
else:
familyname += " " + projectNameSingular
if self.args.single:
familyname += " Mono"
familyname += " " + projectNameSingular + variant_full
# Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with
# the same name as the same font, even if subFamily is different. Make sure to
@ -693,7 +699,7 @@ class font_patcher:
fullname = replace_font_name(fullname, additionalFontNameReplacements2)
fontname = replace_font_name(fontname, additionalFontNameReplacements2)
if not (FontnameParserOK and self.args.makegroups):
if not (FontnameParserOK and self.args.makegroups > 0):
# replace any extra whitespace characters:
font.familyname = " ".join(familyname.split())
font.fullname = " ".join(fullname.split())
@ -704,13 +710,9 @@ class font_patcher:
font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname)
font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
else:
fam_suffix = projectNameSingular if not self.args.windows else projectNameAbbreviation
if self.args.single:
if self.args.windows:
fam_suffix += 'M'
else:
fam_suffix += ' Mono'
n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix)
short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full
# inject_suffix(family, ps_fontname, short_family)
n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family)
n.rename_font(font)
font.comment = projectInfo
@ -726,6 +728,7 @@ class font_patcher:
self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version
self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge
self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version)
# The Version SFNT name is later reused by the NameParser for UniqueID
# print("Version now is {}".format(sourceFont.version))
@ -734,17 +737,17 @@ class font_patcher:
# the tables have been removed from the repo with >this< commit
if self.args.configfile and self.config.read(self.args.configfile):
if self.args.removeligatures:
print("Removing ligatures from configfile `Subtables` section")
logger.info("Removing ligatures from configfile `Subtables` section")
ligature_subtables = json.loads(self.config.get("Subtables", "ligatures"))
for subtable in ligature_subtables:
print("Removing subtable:", subtable)
logger.debug("Removing subtable: %s", subtable)
try:
self.sourceFont.removeLookupSubtable(subtable)
print("Successfully removed subtable:", subtable)
logger.debug("Successfully removed subtable: %s", subtable)
except Exception:
print("Failed to remove subtable:", subtable)
logger.error("Failed to remove subtable: %s", subtable)
elif self.args.removeligatures:
print("Unable to read configfile, unable to remove ligatures")
logger.error("Unable to read configfile, unable to remove ligatures")
def assert_monospace(self):
@ -756,16 +759,17 @@ class font_patcher:
panose_mono = check_panose_monospaced(self.sourceFont)
# The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown'
if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1):
print(" Warning: Monospaced check: Panose assumed to be wrong")
print(" {} and {}".format(
logger.warning("Monospaced check: Panose assumed to be wrong")
logger.warning(" %s and %s",
report_advance_widths(self.sourceFont),
panose_check_to_text(panose_mono, self.sourceFont.os2_panose)))
panose_check_to_text(panose_mono, self.sourceFont.os2_panose))
if self.args.single and not width_mono:
print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
if offending_char is not None:
print(" Offending char: 0x{:X}".format(offending_char))
logger.warning(" Offending char: %X", offending_char)
if self.args.single <= 1:
sys.exit(projectName + ": Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching")
logger.critical("Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching")
sys.exit(1)
if width_mono:
force_panose_monospaced(self.sourceFont)
@ -781,9 +785,9 @@ class font_patcher:
box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs))
if box_glyphs_target > box_glyphs_current:
# Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing)
if not self.args.quiet and box_glyphs_current > 0:
print("INFO: {}/{} box drawing glyphs will be replaced".format(
box_glyphs_current, box_glyphs_target))
if box_glyphs_current > 0:
logger.debug("%d/%d box drawing glyphs will be replaced",
box_glyphs_current, box_glyphs_target)
box_enabled = True
else:
# Sourcefont does have all of these glyphs
@ -1104,7 +1108,7 @@ class font_patcher:
metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font
else:
# We trust the WIN metric more, see experiments in #1056
print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb))
logger.warning("Font vertical metrics inconsistent (HHEA %d / TYPO %d / WIN %d), using WIN", hhea_btb, typo_btb, win_btb)
our_btb = win_btb
metrics = Metric.WIN
@ -1129,6 +1133,7 @@ class font_patcher:
if self.font_dim['height'] == 0:
# This can only happen if the input font is empty
# Assume we are using our prepared templates
self.symbolsonly = True
self.font_dim = {
'xmin' : 0,
'ymin' : -self.sourceFont.descent,
@ -1139,7 +1144,8 @@ class font_patcher:
}
our_btb = self.sourceFont.descent + self.sourceFont.ascent
elif self.font_dim['height'] < 0:
sys.exit("{}: Can not detect sane font height".format(projectName))
logger.critical("Can not detect sane font height")
sys.exit(1)
# Make all metrics equal
self.sourceFont.os2_typolinegap = 0
@ -1153,12 +1159,13 @@ class font_patcher:
self.sourceFont.os2_use_typo_metrics = 1
(check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont)
if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb:
sys.exit("{}: Error in baseline to baseline code detected".format(projectName))
logger.critical("Error in baseline to baseline code detected")
sys.exit(1)
# Step 2
# Find the biggest char width and advance width
# 0x00-0x17f is the Latin Extended-A range
warned1 = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target
warned1 = self.args.nonmono # Do not warn if proportional target
warned2 = warned1
for glyph in range(0x21, 0x17f):
if glyph in range(0x7F, 0xBF) or glyph in [
@ -1176,19 +1183,18 @@ class font_patcher:
if self.font_dim['width'] < self.sourceFont[glyph].width:
self.font_dim['width'] = self.sourceFont[glyph].width
if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
print("Warning: Extended glyphs wider than basic glyphs, results might be useless\n {}".format(
report_advance_widths(self.sourceFont)))
logger.debug("Extended glyphs wider than basic glyphs, results might be useless\n %s",
report_advance_widths(self.sourceFont))
warned1 = True
# print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if xmax > self.font_dim['xmax']:
self.font_dim['xmax'] = xmax
if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
print("Info: Extended glyphs wider bounding box than basic glyphs")
logger.debug("Extended glyphs wider bounding box than basic glyphs")
warned2 = True
# print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if self.font_dim['width'] < self.font_dim['xmax']:
if not self.args.quiet:
print("Warning: Font has negative right side bearing in extended glyphs")
logger.debug("Font has negative right side bearing in extended glyphs")
self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used
# print("FINAL", self.font_dim)
@ -1288,7 +1294,7 @@ class font_patcher:
if sym_glyph.altuni:
possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ]
if len(possible_codes) == 0:
print(" Can not determine codepoint of {:X}. Skipping...".format(sym_glyph.unicode))
logger.warning("Can not determine codepoint of %X. Skipping...", sym_glyph.unicode)
continue
currentSourceFontGlyph = min(possible_codes)
else:
@ -1311,9 +1317,8 @@ class font_patcher:
# check if a glyph already exists in this location
if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential:
if currentSourceFontGlyph in self.sourceFont:
if not self.args.quiet:
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph))
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph)
# We don't want to touch anything so move to next Glyph
continue
else:
@ -1461,8 +1466,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)):
print("\n Warning: Scaled glyph U+{:X} wider than one monospace width ({} / {} (overlap {}))".format(
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap))
logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %f))",
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap)
# end for
@ -1603,7 +1608,7 @@ def half_gap(gap, top):
gap_top = int(gap / 2)
gap_bottom = gap - gap_top
if top:
print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom))
logger.info("Redistributing line gap of %d (%d top and %d bottom)", gap, gap_top, gap_bottom)
return gap_top
return gap_bottom
@ -1728,8 +1733,8 @@ def check_fontforge_min_version():
# versions tested: 20150612, 20150824
if actualVersion < minimumVersion:
sys.stderr.write("{}: You seem to be using an unsupported (old) version of fontforge: {}\n".format(projectName, actualVersion))
sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion))
logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion)
logger.critical("Please use at least version: %d", minimumVersion)
sys.exit(1)
def check_version_with_git(version):
@ -1777,7 +1782,6 @@ def setup_arguments():
parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='count', help='Whether to generate the glyphs as single-width not double-width (default is double-width)')
parser.add_argument('-l', '--adjust-line-height', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)')
parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output')
parser.add_argument('-w', '--windows', dest='windows', default=False, action='store_true', help='Limit the internal font name to 31 characters (for Windows compatibility)')
parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs')
parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected')
parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file')
@ -1787,15 +1791,27 @@ def setup_arguments():
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')
parser.add_argument('--makegroups', dest='makegroups', default=False, action='store_true', help='Use alternative method to name patched fonts (experimental)')
parser.add_argument('--makegroups', dest='makegroups', default=1, type=int, nargs='?', help='Use alternative method to name patched fonts (recommended)', const=1, choices=range(0, 6 + 1))
# --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'.
# Original font name: Hugo Sans Mono ExtraCondensed Light Italic
# NF Fam agg.
# 0 turned off, use old naming scheme [-] [-] [-]
# 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ]
# 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ]
# 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X]
# 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ]
# 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ]
# 6 HugoSansMono NF XCn Lt It [X] [X] [X]
parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")')
# progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
progressbars_group_parser = parser.add_mutually_exclusive_group(required=False)
progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set')
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('--also-windows', dest='alsowindows', default=False, action='store_true', help='Create two fonts, the normal and the --windows version')
parser.add_argument('--debug', dest='debugmode', default=False, action='store_true', help='Verbose mode')
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
# Possible values with examples:
@ -1819,8 +1835,9 @@ def setup_arguments():
args = parser.parse_args()
if args.makegroups and not FontnameParserOK:
sys.exit("{}: FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
if args.makegroups > 0 and not FontnameParserOK:
logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0")
sys.exit(1)
# if you add a new font, set it to True here inside the if condition
if args.complete:
@ -1853,24 +1870,23 @@ def setup_arguments():
font_complete = False
args.complete = font_complete
if args.alsowindows:
args.windows = False
if args.nonmono and args.single:
print("Warning: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
logging.warning("Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
args.nonmono = False
make_sure_path_exists(args.outputdir)
if not os.path.isfile(args.font):
sys.exit("{}: Font file does not exist: {}".format(projectName, args.font))
logging.critical("Font file does not exist: %s", args.font)
sys.exit(1)
if not os.access(args.font, os.R_OK):
sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font))
logging.critical("Can not open font file for reading: %s", args.font)
sys.exit(1)
is_ttc = len(fontforge.fontsInFile(args.font)) > 1
try:
source_font_test = TableHEADWriter(args.font)
args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0)
if args.is_variable:
print(" Warning: Source font is a variable open type font (VF), opening might fail...")
logging.warning("Source font is a variable open type font (VF), opening might fail...")
except:
args.is_variable = False
finally:
@ -1885,16 +1901,20 @@ def setup_arguments():
args.extension = '.' + args.extension
if re.match("\.ttc$", args.extension, re.IGNORECASE):
if not is_ttc:
sys.exit(projectName + ": Can not create True Type Collections from single font files")
logging.critical("Can not create True Type Collections from single font files")
sys.exit(1)
else:
if is_ttc:
sys.exit(projectName + ": Can not create single font files from True Type Collections")
logging.critical("Can not create single font files from True Type Collections")
sys.exit(1)
if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool):
if args.xavgwidth < 0:
sys.exit(projectName + ": --xavgcharwidth takes no negative numbers")
logging.critical("--xavgcharwidth takes no negative numbers")
sys.exit(2)
if args.xavgwidth > 16384:
sys.exit(projectName + ": --xavgcharwidth takes only numbers up to 16384")
logging.critical("--xavgcharwidth takes only numbers up to 16384")
sys.exit(2)
return args
@ -1902,24 +1922,43 @@ def setup_arguments():
def main():
global version
git_version = check_version_with_git(version)
print("{} Patcher v{} ({}) (ff {}) executing".format(
projectName, git_version if git_version else version, script_version, fontforge.version()))
allversions = "Patcher v{} ({}) (ff {})".format(
git_version if git_version else version, script_version, fontforge.version())
print("{} {}".format(projectName, allversions))
if git_version:
version = git_version
check_fontforge_min_version()
args = setup_arguments()
global logger
logger = logging.getLogger(os.path.basename(args.font))
logger.setLevel(logging.DEBUG)
f_handler = logging.FileHandler('font-patcher-log.txt')
f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s'))
logger.addHandler(f_handler)
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:
c_handler.setLevel(logging.INFO)
logger.addHandler(c_handler)
logger.debug("Naming mode %d", args.makegroups)
patcher = font_patcher(args)
sourceFonts = []
all_fonts = fontforge.fontsInFile(args.font)
for i, subfont in enumerate(all_fonts):
if len(all_fonts) > 1:
print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts)))
print("\n")
logger.info("Processing %s (%d/%d)", subfont, i + 1, len(all_fonts))
try:
sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",))
except Exception:
sys.exit("{}: Can not open font '{}', try to open with fontforge interactively to get more information".format(
projectName, subfont))
logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information",
subfont)
sys.exit(1)
patcher.patch(sourceFonts[-1])
@ -1928,13 +1967,6 @@ def main():
patcher.setup_font_names(f)
patcher.generate(sourceFonts)
# This mainly helps to improve CI runtime
if patcher.args.alsowindows:
patcher.args.windows = True
for f in sourceFonts:
patcher.setup_font_names(f)
patcher.generate(sourceFonts)
for f in sourceFonts:
f.close()

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 2"

View File

@ -1,3 +1,3 @@
config_rfn="Cascadia Code"
config_rfn_substitue="Caskaydia Cove"
config_patch_flags="--makegroups"
config_patch_flags="--makegroups 4"

View File

@ -1 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 2"

View File

@ -1,3 +1,4 @@
config_has_powerline=1
config_rfn=Hasklig
config_rfn_substitue=Hasklug
config_patch_flags="--makegroups 2"

View File

@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups"
config_patch_flags="--makegroups 4"

View File

@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups"
config_patch_flags="--makegroups 4"

View File

@ -1 +1 @@
config_patch_flags="--makegroups"
config_patch_flags="--makegroups 4"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 3"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 2"

View File

@ -1 +0,0 @@
config_patch_flags="--makegroups"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 5"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 2"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 3"

View File

@ -1,3 +1,4 @@
config_has_powerline=1
config_rfn=Source
config_rfn_substitue=Sauce
config_patch_flags="--makegroups 4"

View File

@ -1 +1 @@
config_has_powerline=0
config_patch_flags="--makegroups 2"

View File

@ -1 +1 @@
config_has_powerline=0
config_patch_flags="--makegroups 2"

View File

@ -0,0 +1 @@
config_patch_flags="--makegroups 4"

View File

@ -1,2 +1,5 @@
# iA-Fonts
These are fonts from iA. Please read the licensing files before using them in any way.
# iA Writer 2.000
These are fonts from iA. Duo and Quattro are almost monospaced but allow more room for some letters.
See http://ia.net/topics/in-search-of-the-perfect-writing-font/
For more information have a look at the upstream website: https://github.com/iaolo/iA-Fonts

View File

@ -1,100 +0,0 @@
# iA Writer Typeface
Copyright © 2018 Information Architects Inc. with Reserved Font Name "iA Writer"
# Based on IBM Plex Typeface
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
# License
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,11 +0,0 @@
iA Writer Duospace comes bundled with [iA Writer for Mac and iOS](https://ia.net/writer/buy/)
For in depth explanation of iA Writer Duospace please read our [blog entry](http://ia.net/topics/in-search-of-the-perfect-writing-font/)
This is a modification of IBM's Plex font.
The upstream project is [here](https://github.com/IBM/type)
As required by IBM, we named it differently.
Please read the licensing file before working with it.
If you fork or reuse our version, please reference us, too.