Skip to content

Instantly share code, notes, and snippets.

@nikhiljha
Created December 28, 2024 19:20
Show Gist options
  • Save nikhiljha/b6a0dc124b689608f5c324092989098e to your computer and use it in GitHub Desktop.
Save nikhiljha/b6a0dc124b689608f5c324092989098e to your computer and use it in GitHub Desktop.
import argparse
import sys
import re
import fontforge
try:
import fontforge
from fontTools.ttLib import TTFont
from fontTools.feaLib.builder import addOpenTypeFeatures
except ImportError as e:
sys.stderr.write(f"{e}\n")
sys.stderr.write('The required FontForge and fonttools modules could not be loaded.\n\n')
sys.stderr.write('You need FontForge with Python bindings for this script to work.\n')
sys.exit(1)
def get_argparser(ArgumentParser=argparse.ArgumentParser):
parser = ArgumentParser(
description='Font patcher for adding underlines to numbers. Requires FontForge with Python bindings.'
)
parser.add_argument('target_fonts', help='font files to patch', metavar='font',
nargs='*', type=argparse.FileType('rb'))
parser.add_argument('--no-rename', help='don\'t add " with Underlines" to the font name',
default=True, action='store_false', dest='rename_font')
return parser
FONT_NAME_RE = re.compile(r'^([^-]*)(?:(-.*))?$')
NUM_DIGIT_COPIES = 7
def gen_feature(digit_names):
feature = """
languagesystem DFLT dflt;
languagesystem latn dflt;
languagesystem cyrl dflt;
languagesystem grek dflt;
languagesystem kana dflt;
@digits=[{digit_names}];
{nds}
feature calt {{
sub @digits' @digits @digits @digits by @nd0;
sub @nd0 @digits' by @nd0;
reversesub @nd0' @nd0 by @nd1;
reversesub @nd0' @nd1 by @nd2;
reversesub @nd0' @nd2 by @nd3;
reversesub @nd0' @nd3 by @nd4;
reversesub @nd0' @nd4 by @nd5;
reversesub @nd0' @nd5 by @nd6;
reversesub @nd0' @nd6 by @nd1;
}} calt;
"""[1:]
nds = [' '.join(['nd{}.{}'.format(i,j) for j in range(10)]) for i in range(NUM_DIGIT_COPIES)]
nds = ['@nd{}=[{}];'.format(i,nds[i]) for i in range(NUM_DIGIT_COPIES)]
nds = "\n".join(nds)
feature = feature.format(digit_names=' '.join(digit_names), nds=nds)
with open('mods.fea', 'w') as f:
f.write(feature)
def patch_one_font(font, rename_font=True):
print(font.encoding)
font.encoding = 'ISO10646'
# Rename font
if rename_font:
font.familyname += ' with Underlines'
font.fullname += ' with Underlines'
fontname, style = FONT_NAME_RE.match(font.fontname).groups()
font.fontname = fontname + 'WithUnderlines'
if style is not None:
font.fontname += style
font.appendSFNTName('English (US)', 'Preferred Family', font.familyname)
font.appendSFNTName('English (US)', 'Compatible Full', font.fullname)
digit_names = [font[code].glyphname for code in range(ord('0'), ord('9')+1)]
underscore_name = font[ord('_')].glyphname
underscore_layer = font[underscore_name].layers[1]
# Use encoding range that's typically unused
encoding_start = 0xE900
# Create copies of digits with underlines
for copy_i in range(NUM_DIGIT_COPIES):
for digit_i in range(10):
encoding = encoding_start + (copy_i * 10) + digit_i
# Copy original digit
font.selection.select(digit_names[digit_i])
font.copy()
font.selection.select(encoding)
font.paste()
glyph = font[encoding]
glyph.glyphname = f'nd{copy_i}.{digit_i}'
# Add underline to alternating groups (3-5)
if copy_i >= 3 and copy_i < 6:
glyph.layers[1] += underscore_layer
# Preserve original width
glyph.width = font[digit_names[digit_i]].width
gen_feature(digit_names)
# Generate new font file
font.generate('out/tmp.ttf')
ft_font = TTFont('out/tmp.ttf')
addOpenTypeFeatures(ft_font, 'mods.fea', tables=['GSUB'])
out_name = font.fullname.replace('Source ', 'Sauce ')
out_path = f'out/{out_name}.ttf'
ft_font.save(out_path)
print(f"> Created '{out_name}'")
return out_name
def patch_fonts(target_files, rename_font=True):
res = None
for target_file in target_files:
target_font = fontforge.open(target_file.name)
try:
res = patch_one_font(target_font, rename_font)
finally:
target_font.close()
return res
def main(argv):
args = get_argparser().parse_args(argv)
return patch_fonts(args.target_fonts, args.rename_font)
if __name__ == '__main__':
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment