mirror of
https://github.com/ToxicFrog/Ligaturizer.git
synced 2024-08-16 13:20:41 +03:00
Completely rework build process, and rename files for license compliance
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.
This commit is contained in:
parent
a9e24954a9
commit
01b7c2bb01
23
Makefile
23
Makefile
@ -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
README.md
30
README.md
@ -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.
|
||||
|
||||
|
95
build.py
Normal file
95
build.py
Normal file
@ -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)
|
126
ligaturize.py
126
ligaturize.py
@ -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.
|
||||
@ -84,7 +87,7 @@ class LigatureCreator(object):
|
||||
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))
|
||||
@ -104,7 +107,7 @@ class LigatureCreator(object):
|
||||
|
||||
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))
|
||||
@ -219,25 +222,82 @@ class LigatureCreator(object):
|
||||
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"
|
||||
@ -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()
|
||||
def main():
|
||||
ligaturize_font(**vars(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)
|
||||
|
||||
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 not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMono-Bold.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMono-Bold.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMono-BoldItalic.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMono-Italic.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMono-Italic.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMono-Regular.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMono-Regular.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Bold.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Bold.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-BoldItalic.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Italic.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Italic.ttf
Normal file
Binary file not shown.
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Regular.ttf
Normal file
BIN
output-fonts/LigaFantasqueSansMonoNoLoopK-Regular.ttf
Normal file
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.
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.
Loading…
Reference in New Issue
Block a user