Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

font-patcher: Allow to rehint some Cascadia glyphs #1613

Merged
merged 7 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 16 additions & 39 deletions bin/scripts/gotta-patch-em-all-font-patcher!.sh
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config_patch_flags="--makegroups 2"
[Config]
commandline: --makegroups 2
3 changes: 2 additions & 1 deletion src/unpatched-fonts/BitstreamVeraSansMono/config.cfg
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config_patch_flags="--xavgcharwidth 500"
[Config]
commandline: --xavgcharwidth 500
3 changes: 2 additions & 1 deletion src/unpatched-fonts/DaddyTimeMono/config.cfg
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config_patch_flags="--ext ttf"
[Config]
commandline: --ext ttf
3 changes: 2 additions & 1 deletion src/unpatched-fonts/DejaVuSansMono/config.cfg
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
4 changes: 2 additions & 2 deletions src/unpatched-fonts/Iosevka/config.cfg
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
config_has_powerline=1
config_patch_flags="--makegroups 4"
[Config]
commandline: --makegroups 4
Loading