Skip to content

Commit

Permalink
Completely rework build process, and rename files for license compliance
Browse files Browse the repository at this point in the history
This addresses #25 and (partially) #42; fonts that we can't make derivative
works of are no longer checked in, and fonts that need to be renamed for
license compliance have been.

The build process is also now a bit more robust.
  • Loading branch information
ToxicFrog committed Aug 31, 2018
1 parent a9e2495 commit 01b7c2b
Show file tree
Hide file tree
Showing 63 changed files with 205 additions and 69 deletions.
23 changes: 6 additions & 17 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
# Alternate options for ligaturize.py.
# Run `fontforge -lang=py ligaturize.py --help` for details.
LIGATURIZE_OPTS=--prefix=Liga
# LIGATURIZE_OPTS+=--copy-character-glyphs
# LIGATURIZE_OPTS+=--scale-character-glyphs-threshold=0.0
# LIGATURIZE_OPTS+=--scale-character-glyphs-threshold=2.0
# To build with different settings (e.g. turn on character glyph copying),
# edit build.py and then "make".

TTF_SRCS=$(wildcard input-fonts/*.ttf)
OTF_SRCS=$(wildcard input-fonts/*.otf)

TTF_OUTS=$(patsubst input-fonts/%,output-fonts/Liga%,${TTF_SRCS})
OTF_OUTS=$(patsubst input-fonts/%.otf,output-fonts/Liga%.ttf,${OTF_SRCS})

all: ${TTF_OUTS} ${OTF_OUTS}

output-fonts/Liga%.ttf: input-fonts/%.* ligatures.py ligaturize.py
fontforge -lang=py -script ligaturize.py $(LIGATURIZE_OPTS) "$<" "$@" 2>&1 \
| fgrep -v 'This contextual rule applies no lookups.'
all:
fontforge -lang=py -script build.py 2>&1 \
| fgrep -v 'This contextual rule applies no lookups.' \
| fgrep -v 'Bad device table'
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,35 @@ Here's a couple examples of the fonts generated: SF Mono & Menlo with ligatures

Use automatic mode to easily convert 1 or more font(s).

1. Put the font(s) you want into `input-fonts/`.
2. Edit `ligatures.py` to disable any ligatures you don't want, and/or enable any (non-ligature) characters you want from Fira Code in addition to the ligatures.
3. Run `make`.
4. Retrieve the ligaturized fonts from `output-fonts/`.
5. The output fonts will be renamed with the prefix "Liga".
1. Put the font(s) you want into `input-fonts/`.
1. Edit `ligatures.py` to disable any ligatures you don't want, and/or enable any (non-ligature) characters you want from Fira Code in addition to the ligatures.
1. Edit `build.py` to add your new font(s) to the `prefixed_fonts` list. It supports globbing, so if (e.g.) you want to ligaturize all the different weights of FooFont you can add `'FooFont*'` to the list.
1. Run `make`.
1. Retrieve the ligaturized fonts from `output-fonts/`.
1. The output fonts will be renamed with the prefix "Liga".

### Manual ###

1. Move/copy the font you want to ligaturize into `input-fonts/` (or somewhere else convenient).
2. Edit `ligatures.py` to disable any ligatures you don't want.
3. Run the script: `$ fontforge -lang=py -script ligaturize.py <INPUT> <OUTPUT>`, e.g. `$ fontforge -lang=py ligaturize.py input-fonts/Cousine-Regular.ttf output-fonts/CousineLigaturized-Regular.ttf`
3. Run the script:

The font family and weight for the output font (as recorded in the file) will be automatically set based on the name; if the output is `CousineLigaturized-Regular.ttf`, the font family will be `CousineLigaturized` and the font weight will be `Regular`. If no weight is specified, `Regular` is the default.
```
$ fontforge -lang py -script ligaturize.py path/to/input/font.ttf
--output-dir=path/to/output/dir/ \
--output-name='Name of Ligaturized Font'
```
e.g.

```
$ fontforge -lang py -script ligaturize.py fonts/Cousine-Regular.ttf
--output-dir='fonts/' \
--output-name='Ligaturized Cousine'
```

Which will produce `fonts/LigaturizedCousine-Regular.ttf`.

The font weight will be inherited from the original file; the font name will be replaced with whatever you specified in `--output-name`. You can also use `--prefix` instead, in which case the original name will be preserved and whatever you put in `--prefix` will be prepended to it.

`ligatures.py` supports some additional command line options to (e.g.) change which font ligatures are copied from or enable copying of individual character glyphs; run `fontforge -lang=py ligaturize.py --help` to list them.

Expand Down
95 changes: 95 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# Rebuild script for ligaturized fonts.
# Uses ligaturize.py to do the heavy lifting; this file basically just contains
# the mapping from input font paths to output fonts.

#### User configurable settings ####

# For the prefixed_fonts below, what word do we stick in front of the font name?
LIGATURIZED_FONT_NAME_PREFIX = "Liga"

# Should we copy some individual punctuations characters like &, ~, and <>,
# as well as ligatures? The full list is in ligatures.py.
COPY_CHARACTER_GLYPHS = False

# If copying individual characters, how different in width (relative to the font
# we're ligaturizing) should they be before we attempt to width-correct them?
# The default (0.1) means to width-correct if they're +/- 10%. Values >1.0
# effectively disable this feature.
SCALE_CHARACTER_GLYPHS_THRESHOLD = 0.1

#### Fonts that should be prefixed with "Liga" when ligaturized. ####
# Don't put fonts licensed under UFL here, and don't put fonts licensed under
# SIL OFL here either unless they haven't specified a Reserved Font Name.

prefixed_fonts = [
# Apache 2.0 license
'Cousine*',
'Droid*',
'Meslo*',
'Roboto*',

# MIT license
'DejaVu*',
'Hack*',

# SIL OFL with no Reserved Font Name
'Edlo*',
'FantasqueSansMono-Normal/*',
'Inconsolata*',
]

#### Fonts that need to be renamed. ####
# These are fonts that either have name collisions with the prefixed_fonts
# above, or are released under licenses that permit modification only if we
# change the name of the modified fonts.

renamed_fonts = {
# This doesn't have a reserved name, but if we don't rename it it'll collide
# with its sibling Fantasque Sans Mono Normal, listed above.
'FantasqueSansMono-NoLoopK/*': 'Liga Fantasque Sans Mono NoLoopK',

# SIL OFL with reserved name
'Anonymous*': 'Liganymous',
'IBMPlexMono*': 'Ligalex Mono',
'OxygenMono*': 'Liga O2 Mono',
'SourceCodePro*': 'LigaSrc Pro',
'SourceCodeVariable*': 'LigaSrc Variable',

# UFL
'UbuntuMono*': 'Ubuntu Mono Ligaturized',
}

#### Fonts we can't ligaturize. ####
# Fonts that we can't ligaturize because their licences do not permit derivative
# works of any kind.
# Individual users may still be able to make ligaturized versions for personal
# use, but we can't check them into the repo or include them in releases.

# prefixed_fonts += [
# 'CamingoCode*',
# 'SFMono*',
# ]

#### No user serviceable parts below this line. ####

from glob import glob
from os import path
from ligaturize import ligaturize_font

for pattern in prefixed_fonts:
for input_file in glob(path.join('input-fonts', pattern)):
ligaturize_font(
input_file, ligature_font_file=None, output_dir='output-fonts/',
prefix=LIGATURIZED_FONT_NAME_PREFIX, output_name=None,
copy_character_glyphs=COPY_CHARACTER_GLYPHS,
scale_character_glyphs_threshold=SCALE_CHARACTER_GLYPHS_THRESHOLD)

for pattern,name in renamed_fonts.iteritems():
for input_file in glob(path.join('input-fonts', pattern)):
ligaturize_font(
input_file, ligature_font_file=None, output_dir='output-fonts/',
prefix=None, output_name=name,
copy_character_glyphs=COPY_CHARACTER_GLYPHS,
scale_character_glyphs_threshold=SCALE_CHARACTER_GLYPHS_THRESHOLD)
File renamed without changes.
126 changes: 81 additions & 45 deletions ligaturize.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ def get_ligature_source(fontname):

class LigatureCreator(object):

def __init__(self, font, firacode, opts):
def __init__(self, font, firacode,
scale_character_glyphs_threshold,
copy_character_glyphs):
self.font = font
self.firacode = firacode
self.opts = opts
self.scale_character_glyphs_threshold = scale_character_glyphs_threshold
self.should_copy_character_glyphs = copy_character_glyphs
self._lig_counter = 0

# Scale firacode to correct em height.
Expand Down Expand Up @@ -84,7 +87,7 @@ def correct_character_width(self, glyph):
return

widthdelta = float(abs(glyph.width - self.emwidth)) / self.emwidth
if widthdelta >= self.opts.scale_character_glyphs_threshold:
if widthdelta >= self.scale_character_glyphs_threshold:
# Character is too wide/narrow compared to output font; scale it.
scale = float(self.emwidth) / glyph.width
glyph.transform(psMat.scale(scale, 1.0))
Expand All @@ -104,7 +107,7 @@ def correct_character_width(self, glyph):

def copy_character_glyphs(self, chars):
"""Copy individual (non-ligature) characters from the ligature font."""
if not self.opts.copy_character_glyphs:
if not self.should_copy_character_glyphs:
return
print("Copying %d character glyphs from %s..." % (
len(chars), self.firacode.fullname))
Expand Down Expand Up @@ -219,25 +222,82 @@ def add_calt(self, calt_name, subtable_name, spec, **kwargs):
self.font.addContextualSubtable(calt_name, subtable_name, 'glyph', spec)


def update_font_metadata(font, prefix):
font.fontname = "%s%s" % (prefix, font.fontname)
font.fullname = "%s %s" % (prefix, font.fullname)
font.familyname = "%s %s" % (prefix, font.familyname)
def update_font_metadata(font, new_name):
# Figure out the input font's real name (i.e. without a hyphenated suffix)
# and hyphenated suffix (if present)
old_name = font.familyname
try:
suffix = font.fontname.split('-')[1]
except IndexError:
suffix = None

# Replace the old name with the new name whether or not a suffix was present.
# If a suffix was present, append it accordingly.
font.familyname = new_name
if suffix:
font.fullname = "%s %s" % (new_name, suffix)
font.fontname = "%s-%s" % (new_name.replace(' ', ''), suffix)
else:
font.fullname = new_name
font.fontname = new_name.replace(' ', '')

print("Ligaturizing font '%s' as '%s'" % (old_name, new_name))

font.copyright += COPYRIGHT
font.sfnt_names = tuple(
(row[0], 'UniqueID', '%s-%s' % (prefix, row[2]))
(row[0], 'UniqueID', '%s; Ligaturized' % font.fullname)
if row[1] == 'UniqueID' else row
for row in font.sfnt_names
)

def ligaturize_font(input_font_file, output_dir, ligature_font_file,
output_name, prefix, **kwargs):
font = fontforge.open(input_font_file)

if not ligature_font_file:
ligature_font_file = get_ligature_source(font.fontname)

if output_name:
name = output_name
else:
name = font.familyname
if prefix:
name = "%s %s" % (prefix, name)

update_font_metadata(font, name)

print('Reading ligatures from %s' % ligature_font_file)
firacode = fontforge.open(ligature_font_file)

creator = LigatureCreator(font, firacode, **kwargs)
ligature_length = lambda lig: len(lig['chars'])
for lig_spec in sorted(ligatures, key = ligature_length):
try:
creator.add_ligature(lig_spec['chars'], lig_spec['firacode_ligature_name'])
except Exception as e:
print('Exception while adding ligature: {}'.format(lig_spec))
raise

# Work around a bug in Fontforge where the underline height is subtracted from
# the underline width when you call generate().
font.upos += font.uwidth

# Generate font & move to output directory
output_font_file = path.join(output_dir, font.fontname + '.ttf')
print("Saved ligaturized font '%s' as %s" % (font.fullname, output_font_file))
font.generate(output_font_file)


def parse_args():
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("input_font_path",
parser.add_argument("input_font_file",
help="The TTF or OTF font to add ligatures to.")
parser.add_argument("output_font_path",
help="The file to save the ligaturized font in.")
parser.add_argument("--ligature-font-path",
parser.add_argument("--output-dir",
help="The directory to save the ligaturized font in. The actual filename"
" will be automatically generated based on the input font name and"
" the --prefix and --output-name flags.")
parser.add_argument("--ligature-font-file",
type=str, default='', metavar='PATH',
help="The file to copy ligatures from. If unspecified, ligaturize will"
" attempt to pick a suitable one from fira/distr/otf/ based on the input"
Expand All @@ -258,39 +318,15 @@ def parse_args():
" all copied character glyphs; a value of 2 effectively disables"
" character glyph scaling.")
parser.add_argument("--prefix",
type=str, default="Liga ",
type=str, default="Liga",
help="String to prefix the name of the generated font with.")
parser.add_argument("--output-name",
type=str, default="",
help="Name of the generated font. Completely replaces the original.")
return parser.parse_args()

args = parse_args()

# print('Converting %s to %s using ligatures from %s' % (
# args.input_font_path, args.output_font_path, args.ligature_font_path))
font = fontforge.open(args.input_font_path)
def main():
ligaturize_font(**vars(parse_args()))

if args.ligature_font_path:
ligature_font_path = args.ligature_font_path
else:
ligature_font_path = get_ligature_source(font.fontname)

print('Reading ligatures from %s' % ligature_font_path)
firacode = fontforge.open(ligature_font_path)

update_font_metadata(font, args.prefix)

creator = LigatureCreator(font, firacode, args)
ligature_length = lambda lig: len(lig['chars'])
for lig_spec in sorted(ligatures, key = ligature_length):
try:
creator.add_ligature(lig_spec['chars'], lig_spec['firacode_ligature_name'])
except Exception as e:
print('Exception while adding ligature: {}'.format(lig_spec))
raise

# Work around a bug in Fontforge where the underline height is subtracted from
# the underline width when you call generate().
font.upos += font.uwidth

# Generate font & move to output directory
font.generate(args.output_font_path)
print("Generated ligaturized font %s in %s" % (font.fullname, args.output_font_path))
if __name__ == '__main__':
main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed output-fonts/LigaCamingoCode-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaCousine-Bold.ttf
Binary file not shown.
Binary file not shown.
Binary file modified output-fonts/LigaDejaVuSansMono.ttf
Binary file not shown.
Binary file modified output-fonts/LigaDroidSansMono.ttf
Binary file not shown.
Binary file not shown.
Binary file added output-fonts/LigaFantasqueSansMono-Bold.ttf
Binary file not shown.
Binary file not shown.
Binary file added output-fonts/LigaFantasqueSansMono-Italic.ttf
Binary file not shown.
Binary file added output-fonts/LigaFantasqueSansMono-Regular.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified output-fonts/LigaHack-Bold.ttf
Binary file not shown.
Binary file modified output-fonts/LigaHack-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaInconsolata-Regular.ttf
Binary file not shown.
Binary file not shown.
Binary file modified output-fonts/LigaInconsolata.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGL-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGLDZ-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGM-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGMDZ-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGS-Regular.ttf
Binary file not shown.
Binary file modified output-fonts/LigaMesloLGSDZ-Regular.ttf
Binary file not shown.
Binary file not shown.
Binary file modified output-fonts/LigaRobotoMono-Regular.ttf
Binary file not shown.
Binary file removed output-fonts/LigaSFMono-Regular.ttf
Binary file not shown.
Binary file removed output-fonts/LigaSFMono-Semibold.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 01b7c2b

Please sign in to comment.