Skip to content

Commit

Permalink
Merge pull request #1613 from ryanoasis/bugfix/Caskaydia-long-arrows
Browse files Browse the repository at this point in the history
font-patcher: Allow to rehint some Cascadia glyphs
  • Loading branch information
Finii committed Apr 22, 2024
2 parents f07ad16 + cd80b9e commit 6871fdd
Show file tree
Hide file tree
Showing 55 changed files with 220 additions and 173 deletions.
55 changes: 16 additions & 39 deletions bin/scripts/gotta-patch-em-all-font-patcher!.sh
Expand Up @@ -214,79 +214,56 @@ function patch_font {
config_parent_dir=$( cd "$( dirname "$f" )" && cd ".." && pwd)
config_dir=$( cd "$( dirname "$f" )" && pwd)

# source the font config file if exists:
# fetches for example config_patch_flags
unset config_patch_flags
# find the font config file:
if [ -f "$config_dir/config.cfg" ]
then
# shellcheck source=/dev/null
source "$config_dir/config.cfg"
font_config="--configfile=$config_dir/config.cfg"
elif [ -f "$config_parent_dir/config.cfg" ]
then
# shellcheck source=/dev/null
source "$config_parent_dir/config.cfg"
font_config="--configfile=$config_parent_dir/config.cfg"
elif [ -f "$(find_font_root "$config_parent_dir")/config.cfg" ]
then
# shellcheck source=/dev/null
source "$(find_font_root "$config_parent_dir")/config.cfg"
fi

if [ -f "$config_dir/config.json" ]
then
# load font configuration file and remove selected ligatures:
font_config="--removeligatures --configfile $config_dir/config.json"
elif [ -f "$config_parent_dir/config.json" ]
then
font_config="--removeligatures --configfile $config_parent_dir/config.json"
else
font_config=""
fi

if [ "$post_process" ]
then
# There is no postprocess active anymore, see the commit that introduced
# this comment for the Hack postprocess we once had. It called e.g. ttfautohint.
post_process="--postprocess=${repo_root_dir}/${post_process}"
font_config="--configfile=$(find_font_root "$config_parent_dir")/config.cfg"
else
post_process=""
# We need to give some argument because empty arguments will break the patcher call
font_config="-q"
fi

cd "$repo_root_dir" || {
echo >&2 "# Could not find project parent directory"
exit 3
}
# Add logfile always (but can be overridden by config_patch_flags in config.cfg and env var NERDFONTS)
config_patch_flags="--debug 1 ${config_patch_flags}"
# Add logfile always (but can be overridden by config.cfg and env var NERDFONTS)
# 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 ${font_config} $post_process -c --no-progressbars --outputdir \"${patched_font_dir}\" $config_patch_flags ${NERDFONTS}"
echo "fontforge -quiet -script \"${PWD}/font-patcher\" --debug 1 \"$f\" -q \"${font_config}\" -c --no-progressbars --outputdir \"${patched_font_dir}\" ${NERDFONTS}"
fi
# shellcheck disable=SC2086 # We want splitting for the unquoted variables to get multiple options out of them
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" "$f" -q ${font_config} $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" --debug 1 "$f" -q "${font_config}" -c --no-progressbars \
--outputdir "${patched_font_dir}" ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
# shellcheck disable=SC2181 # Checking the code directly is very unreadable here, as we execute a whole block
if [ $? -ne 0 ]; then printf "%s\nPatcher run aborted!\n\n" "$OUT"; fi
# Create "Nerd Font Mono"
if [ -n "${verbose}" ]
then
echo "fontforge -quiet -script \"${PWD}/font-patcher\" \"$f\" -q -s ${font_config} $post_process -c --no-progressbars --outputdir \"${patched_font_dir}\" $config_patch_flags ${NERDFONTS}"
echo "fontforge -quiet -script \"${PWD}/font-patcher\" --debug 1 \"$f\" -q -s \"${font_config}\" -c --no-progressbars --outputdir \"${patched_font_dir}\" ${NERDFONTS}"
fi
# shellcheck disable=SC2086 # We want splitting for the unquoted variables to get multiple options out of them
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" "$f" -q -s ${font_config} $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" --debug 1 "$f" -q -s "${font_config}" -c --no-progressbars \
--outputdir "${patched_font_dir}" ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
# shellcheck disable=SC2181 # Checking the code directly is very unreadable here, as we execute a whole block
if [ $? -ne 0 ]; then printf "%s\nPatcher run aborted!\n\n" "$OUT"; fi
# Create "Nerd Font Propo"
if [ -n "${verbose}" ]
then
echo "fontforge -quiet -script \"${PWD}/font-patcher\" \"$f\" -q --variable ${font_config} $post_process -c --no-progressbars --outputdir \"${patched_font_dir}\" $config_patch_flags ${NERDFONTS}"
echo "fontforge -quiet -script \"${PWD}/font-patcher\" --debug 1 \"$f\" -q --variable \"${font_config}\" -c --no-progressbars --outputdir \"${patched_font_dir}\" ${NERDFONTS}"
fi
# shellcheck disable=SC2086 # We want splitting for the unquoted variables to get multiple options out of them
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" "$f" -q --variable ${font_config} $post_process -c --no-progressbars \
--outputdir "${patched_font_dir}" $config_patch_flags ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
{ OUT=$(fontforge -quiet -script "${PWD}/font-patcher" --debug 1 "$f" -q --variable "${font_config}" -c --no-progressbars \
--outputdir "${patched_font_dir}" ${NERDFONTS} 2>&1 1>&3 3>&- ); } 3>&1
# shellcheck disable=SC2181 # Checking the code directly is very unreadable here, as we execute a whole block
if [ $? -ne 0 ]; then printf "%s\nPatcher run aborted!\n\n" "$OUT"; fi

Expand Down
115 changes: 78 additions & 37 deletions font-patcher
Expand Up @@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals

# Change the script version when you edit this script:
script_version = "4.13.1"
script_version = "4.14.0"

version = "3.2.1"
projectName = "Nerd Fonts"
Expand Down Expand Up @@ -320,10 +320,10 @@ def create_filename(fonts):


class font_patcher:
def __init__(self, args):
def __init__(self, args, conf):
self.args = args # class 'argparse.Namespace'
self.sym_font_args = []
self.config = None # class 'configparser.ConfigParser'
self.config = conf # class 'configparser.ConfigParser'
self.sourceFont = None # class 'fontforge.font'
self.patch_set = None # class 'list'
self.font_dim = None # class 'dict'
Expand All @@ -332,14 +332,14 @@ class font_patcher:
self.symbolsonly = False # Are we generating the SymbolsOnly font?
self.onlybitmaps = 0
self.essential = set()
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
self.xavgwidth = [] # list of ints

def patch(self, font):
self.sourceFont = font
self.setup_version()
self.assert_monospace()
self.remove_ligatures()
self.manipulate_hints()
self.get_essential_references()
self.get_sourcefont_dimensions()
self.setup_patch_set()
Expand Down Expand Up @@ -772,20 +772,39 @@ class font_patcher:
def remove_ligatures(self):
# let's deal with ligatures (mostly for monospaced fonts)
# Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot'
if self.args.configfile and self.config.read(self.args.configfile):
if self.args.removeligatures:
logger.info("Removing ligatures from configfile `Subtables` section")
ligature_subtables = json.loads(self.config.get("Subtables", "ligatures"))
for subtable in ligature_subtables:
logger.debug("Removing subtable: %s", subtable)
try:
self.sourceFont.removeLookupSubtable(subtable)
logger.debug("Successfully removed subtable: %s", subtable)
except Exception:
logger.error("Failed to remove subtable: %s", subtable)
elif self.args.removeligatures:
logger.error("Unable to read configfile, unable to remove ligatures")
if self.args.removeligatures:
logger.info("Removing ligatures from configfile `Subtables` section")
if 'Subtables' not in self.config:
logger.warning("No ligature data (config file missing?)")
return
ligature_subtables = json.loads(self.config.get('Subtables', 'ligatures', fallback='[]'))
for subtable in ligature_subtables:
logger.debug("Removing subtable: %s", subtable)
try:
self.sourceFont.removeLookupSubtable(subtable)
logger.debug("Successfully removed subtable: %s", subtable)
except Exception:
logger.error("Failed to remove subtable: %s", subtable)


def manipulate_hints(self):
""" Redo the hinting on some problematic glyphs """
if 'Hinting' not in self.config:
return
redo = json.loads(self.config.get('Hinting', 're_hint', fallback='[]'))
if not len(redo):
return
logger.debug("Working on {} rehinting rules (this may create a lot of fontforge warnings)".format(len(redo)))
count = 0
for gname in self.sourceFont:
for regex in redo:
if re.fullmatch(regex, gname):
glyph = self.sourceFont[gname]
glyph.autoHint()
glyph.autoInstr()
count += 1
break
logger.info("Rehinted {} glyphs".format(count))

def assert_monospace(self):
# Check if the sourcefont is monospaced
Expand Down Expand Up @@ -1886,6 +1905,7 @@ def check_version_with_git(version):
return False

def setup_arguments():
""" Parse the command line parameters and load the config file if needed """
parser = argparse.ArgumentParser(
description=(
'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n'
Expand Down Expand Up @@ -1935,7 +1955,7 @@ def setup_arguments():

expert_group = parser.add_argument_group('Expert Options')
expert_group.add_argument('--boxdrawing', dest='forcebox', default=False, action='store_true', help='Force patching in (over existing) box drawing glyphs')
expert_group.add_argument('--configfile', dest='configfile', default=False, type=str, help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)')
expert_group.add_argument('--configfile', dest='configfile', default=False, type=str, help='Specify a file path for configuration file (see sample: src/config.sample.cfg)')
expert_group.add_argument('--custom', dest='custom', default=False, type=str, help='Specify a custom symbol font, all glyphs will be copied; absolute path suggested')

expert_group.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming')
Expand All @@ -1946,7 +1966,7 @@ def setup_arguments():
expert_group.add_argument('--name', dest='force_name', default=None, type=str, help='Specify naming source (\'full\', \'postscript\', \'filename\', or concrete free name-string)')
expert_group.add_argument('--postprocess', dest='postprocess', default=False, type=str, help='Specify a Script for Post Processing')
progressbars_group_parser = expert_group.add_mutually_exclusive_group(required=False)
expert_group.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file (needs --configfile)')
expert_group.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in configuration file (needs --configfile)')
expert_group.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:
Expand All @@ -1960,6 +1980,23 @@ def setup_arguments():
expert_group.set_defaults(progressbars=True)

args = parser.parse_args()
setup_global_logger(args)

# if we have a config file: fetch commandline arguments from there and process again with all arguments
config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
if args.configfile:
if not os.path.isfile(args.configfile):
logger.critical("Configfile does not exist: %s", args.configfile)
sys.exit(1)
if not os.access(args.configfile, os.R_OK):
logger.critical("Can not open configfile for reading: %s", args.configfile)
sys.exit(1)
config.read(args.configfile)
extraflags = config.get("Config", "commandline", fallback='')
if len(extraflags):
logger.info("Adding config commandline options: %s", extraflags)
extraflags += ' ' + args.font # Need to re-add the mandatory argument
args = parser.parse_args(extraflags.split(), args)

if args.makegroups > 0 and not FontnameParserOK:
logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0")
Expand Down Expand Up @@ -2042,25 +2079,11 @@ def setup_arguments():
logger.critical("--xavgcharwidth takes only numbers up to 16384")
sys.exit(2)

return args
return (args, config)

def main():
def setup_global_logger(args):
""" Set up the logger and take options into account """
global logger
logger = logging.getLogger("start") # Use start logger until we can set up something sane
s_handler = logging.StreamHandler(stream=sys.stdout)
s_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logger.addHandler(s_handler)

global version
git_version = check_version_with_git(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()

logger = logging.getLogger(os.path.basename(args.font))
logger.setLevel(logging.DEBUG)
log_to_file = (args.debugmode & 1 == 1)
Expand All @@ -2080,9 +2103,27 @@ def main():
logger.addHandler(c_handler)
if (args.debugmode & 1 == 1) and not log_to_file:
logger.info("Can not write logfile, disabling")

def main():
global logger
logger = logging.getLogger("start") # Use start logger until we can set up something sane
s_handler = logging.StreamHandler(stream=sys.stdout)
s_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logger.addHandler(s_handler)

global version
git_version = check_version_with_git(version)
global allversions
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, conf) = setup_arguments()
logger.debug("Naming mode %d", args.makegroups)

patcher = font_patcher(args)
patcher = font_patcher(args, conf)

sourceFonts = []
all_fonts = fontforge.fontsInFile(args.font)
Expand Down
9 changes: 9 additions & 0 deletions src/config.sample.cfg
@@ -0,0 +1,9 @@
# These config files are read by the font-patcher
# They are INI style files, but some keys have JSON values
[Config]
commandline: --removeligatures --makegroups 2
[Subtables]
ligatures: [
"'dlig' Discretionary Ligatures lookup 9 subtable",
"'dlig' Discretionary Ligatures lookup 11 subtable",
"'dlig' Discretionary Ligatures lookup 12 contextual 0" ]
18 changes: 0 additions & 18 deletions src/config.sample.json

This file was deleted.

3 changes: 2 additions & 1 deletion src/unpatched-fonts/3270/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--makegroups 2"
[Config]
commandline: --makegroups 2
3 changes: 2 additions & 1 deletion src/unpatched-fonts/BitstreamVeraSansMono/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--has-no-italic"
[Config]
commandline: --has-no-italic
8 changes: 7 additions & 1 deletion src/unpatched-fonts/CascadiaCode/config.cfg
@@ -1 +1,7 @@
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
[Hinting]
re_hint: [
".*hyphen.*\\.(liga|seq)",
".*equal.*\\.(liga|seq)",
".*numbersign.*\\.(liga|seq)" ]
8 changes: 7 additions & 1 deletion src/unpatched-fonts/CascadiaMono/config.cfg
@@ -1 +1,7 @@
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
[Hinting]
re_hint: [
".*hyphen.*\\.(liga|seq)",
".*equal.*\\.(liga|seq)",
".*numbersign.*\\.(liga|seq)" ]
3 changes: 2 additions & 1 deletion src/unpatched-fonts/D2Coding/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--xavgcharwidth 500"
[Config]
commandline: --xavgcharwidth 500
3 changes: 2 additions & 1 deletion src/unpatched-fonts/DaddyTimeMono/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--ext ttf"
[Config]
commandline: --ext ttf
3 changes: 2 additions & 1 deletion src/unpatched-fonts/DejaVuSansMono/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--has-no-italic"
[Config]
commandline: --has-no-italic
1 change: 0 additions & 1 deletion src/unpatched-fonts/DroidSansMono/config.cfg

This file was deleted.

1 change: 0 additions & 1 deletion src/unpatched-fonts/FantasqueSansMono/config.cfg

This file was deleted.

4 changes: 2 additions & 2 deletions src/unpatched-fonts/FiraCode/config.cfg
@@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 2"
[Config]
commandline: --makegroups 2
3 changes: 2 additions & 1 deletion src/unpatched-fonts/GeistMono/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
1 change: 0 additions & 1 deletion src/unpatched-fonts/Hack/config.cfg

This file was deleted.

4 changes: 2 additions & 2 deletions src/unpatched-fonts/Hasklig/config.cfg
@@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 2"
[Config]
commandline: --makegroups 2
1 change: 0 additions & 1 deletion src/unpatched-fonts/Hermit/config.cfg

This file was deleted.

1 change: 0 additions & 1 deletion src/unpatched-fonts/IBMPlexMono/config.cfg

This file was deleted.

3 changes: 2 additions & 1 deletion src/unpatched-fonts/IntelOneMono/config.cfg
@@ -1 +1,2 @@
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
4 changes: 2 additions & 2 deletions src/unpatched-fonts/Iosevka/config.cfg
@@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
4 changes: 2 additions & 2 deletions src/unpatched-fonts/IosevkaTerm/config.cfg
@@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4

0 comments on commit 6871fdd

Please sign in to comment.