Created
December 28, 2024 19:20
-
-
Save nikhiljha/b6a0dc124b689608f5c324092989098e to your computer and use it in GitHub Desktop.
Numderline Minimal (https://github.com/trishume/numderline)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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