Ensure all fonttools CLI tools have help documentation (#1948)

Note UI change : `fonttools varLib.models` now takes prefixed options `-d` or `-l` instead of guessing the intended feature from the number of arguments.

We have a number of command line tools which are somewhat opaque. (varLib.models in particular was very confusing.) This ensures that they all use argparse to have a consistent interface, and all have --help documentation which at least details their parameters, and hopefully therefore gives more of a clue about what they do. Those which use logging have had a command-line logging parameter added.
This commit is contained in:
Simon Cozens 2020-05-12 15:11:30 +01:00 committed by GitHub
parent a114ec2c20
commit 089f24da6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 48 deletions

View File

@ -146,16 +146,30 @@ def optimizeWidths(widths):
return default, nominal
def main():
def main(args=None):
"""Calculate optimum defaultWidthX/nominalWidthX values"""
for fontfile in sys.argv[1:]:
import argparse
parser = argparse.ArgumentParser(
"fonttools cffLib.width",
description=main.__doc__,
)
parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
help="Input TTF files")
parser.add_argument('-b', '--brute-force', dest="brute", action="store_true",
help="Use brute-force approach (VERY slow)")
args = parser.parse_args(args)
for fontfile in args.inputs:
font = TTFont(fontfile)
hmtx = font['hmtx']
widths = [m[0] for m in hmtx.metrics.values()]
if args.brute:
default, nominal = optimizeWidthsBruteforce(widths)
else:
default, nominal = optimizeWidths(widths)
print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
#default, nominal = optimizeWidthsBruteforce(widths)
#print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
if __name__ == '__main__':
import sys

View File

@ -1164,17 +1164,31 @@ def main(args=None, font=None):
# comment this out to enable debug messages from mtiLib's logger
# log.setLevel(logging.DEBUG)
import argparse
parser = argparse.ArgumentParser(
"fonttools mtiLib",
description=main.__doc__,
)
parser.add_argument('--font', '-f', metavar='FILE', dest="font",
help="Input TTF files (used for glyph classes and sorting coverage tables)")
parser.add_argument('--table', '-t', metavar='TABLE', dest="tableTag",
help="Table to fill (sniffed from input file if not provided)")
parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
help="Input FontDame .txt files")
args = parser.parse_args(args)
if font is None:
if args.font:
font = ttLib.TTFont(args.font)
else:
font = MockFont()
tableTag = None
if args[0].startswith('-t'):
tableTag = args[0][2:]
del args[0]
for f in args:
for f in args.inputs:
log.debug("Processing %s", f)
with open(f, 'rt', encoding="utf-8") as f:
table = build(f, font, tableTag=tableTag)
table = build(f, font, tableTag=args.tableTag)
blob = table.compile(font) # Make sure it compiles
decompiled = table.__class__()
decompiled.decompile(blob, font) # Make sure it decompiles!

View File

@ -1395,6 +1395,17 @@ def main(args=None):
from fontTools import configLogger
from fontTools.ttx import makeOutputFileName
class _HelpAction(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
subparsers_actions = [
action for action in parser._actions
if isinstance(action, argparse._SubParsersAction)]
for subparsers_action in subparsers_actions:
for choice, subparser in subparsers_action.choices.items():
print(subparser.format_help())
parser.exit()
class _NoGlyfTransformAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
namespace.transform_tables.difference_update({"glyf", "loca"})
@ -1405,12 +1416,18 @@ def main(args=None):
parser = argparse.ArgumentParser(
prog="fonttools ttLib.woff2",
description=main.__doc__
description=main.__doc__,
add_help = False
)
parser.add_argument('-h', '--help', action=_HelpAction,
help='show this help message and exit')
parser_group = parser.add_subparsers(title="sub-commands")
parser_compress = parser_group.add_parser("compress")
parser_decompress = parser_group.add_parser("decompress")
parser_compress = parser_group.add_parser("compress",
description = "Compress a TTF or OTF font to WOFF2")
parser_decompress = parser_group.add_parser("decompress",
description = "Decompress a WOFF2 font to OTF")
for subparser in (parser_compress, parser_decompress):
group = subparser.add_mutually_exclusive_group(required=False)

View File

@ -1011,7 +1011,7 @@ def main(args=None):
from argparse import ArgumentParser
from fontTools import configLogger
parser = ArgumentParser(prog='varLib')
parser = ArgumentParser(prog='varLib', description = main.__doc__)
parser.add_argument('designspace')
parser.add_argument(
'-o',

View File

@ -157,23 +157,31 @@ def test(glyphsets, glyphs=None, names=None):
#for x in hist:
# print(x)
def main(args):
def main(args=None):
"""Test for interpolatability issues between fonts"""
filenames = args
import argparse
parser = argparse.ArgumentParser(
"fonttools varLib.interpolatable",
description=main.__doc__,
)
parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
help="Input TTF files")
args = parser.parse_args(args)
glyphs = None
#glyphs = ['uni08DB', 'uniFD76']
#glyphs = ['uni08DE', 'uni0034']
#glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
from os.path import basename
names = [basename(filename).rsplit('.', 1)[0] for filename in filenames]
names = [basename(filename).rsplit('.', 1)[0] for filename in args.inputs]
from fontTools.ttLib import TTFont
fonts = [TTFont(filename) for filename in filenames]
fonts = [TTFont(filename) for filename in args.inputs]
glyphsets = [font.getGlyphSet() for font in fonts]
test(glyphsets, glyphs=glyphs, names=names)
if __name__ == '__main__':
import sys
main(sys.argv[1:])
main()

View File

@ -60,28 +60,40 @@ def interpolate_layout(designspace, loc, master_finder=lambda s:s, mapped=False)
def main(args=None):
"""Interpolate GDEF/GPOS/GSUB tables for a point on a designspace"""
from fontTools import configLogger
import argparse
import sys
if args is None:
args = sys.argv[1:]
designspace_filename = args[0]
locargs = args[1:]
outfile = os.path.splitext(designspace_filename)[0] + '-instance.ttf'
parser = argparse.ArgumentParser(
"fonttools varLib.interpolate_layout",
description=main.__doc__,
)
parser.add_argument('designspace_filename', metavar='DESIGNSPACE',
help="Input TTF files")
parser.add_argument('locations', metavar='LOCATION', type=str, nargs='+',
help="Axis locations (e.g. wdth=120")
parser.add_argument('-o', '--output', metavar='OUTPUT',
help="Output font file (defaults to <designspacename>-instance.ttf)")
parser.add_argument('-l', '--loglevel', metavar='LEVEL', default="INFO",
help="Logging level (defaults to INFO)")
# TODO: allow user to configure logging via command-line options
configLogger(level="INFO")
args = parser.parse_args(args)
if not args.output:
args.output = os.path.splitext(args.designspace_filename)[0] + '-instance.ttf'
configLogger(level=args.loglevel)
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf')
loc = {}
for arg in locargs:
for arg in args.locations:
tag,val = arg.split('=')
loc[tag] = float(val)
font = interpolate_layout(designspace_filename, loc, finder)
log.info("Saving font %s", outfile)
font.save(outfile)
font = interpolate_layout(args.designspace_filename, loc, finder)
log.info("Saving font %s", args.output)
font.save(args.output)
if __name__ == "__main__":

View File

@ -422,27 +422,32 @@ def piecewiseLinearMap(v, mapping):
return va + (vb - va) * (v - a) / (b - a)
def main(args):
def main(args=None):
"""Normalize locations on a given designspace"""
from fontTools import configLogger
import argparse
args = args[1:]
parser = argparse.ArgumentParser(
"fonttools varLib.models",
description=main.__doc__,
)
parser.add_argument('--loglevel', metavar='LEVEL', default="INFO",
help="Logging level (defaults to INFO)")
# TODO: allow user to configure logging via command-line options
configLogger(level="INFO")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-d', '--designspace',metavar="DESIGNSPACE",type=str)
group.add_argument('-l', '--locations', metavar='LOCATION', nargs='+',
help="Master locations as comma-separate coordinates. One must be all zeros.")
if len(args) < 1:
print("usage: fonttools varLib.models source.designspace", file=sys.stderr)
print(" or")
print("usage: fonttools varLib.models location1 location2 ...", file=sys.stderr)
sys.exit(1)
args = parser.parse_args(args)
configLogger(level=args.loglevel)
from pprint import pprint
if len(args) == 1 and args[0].endswith('.designspace'):
if args.designspacefile:
from fontTools.designspaceLib import DesignSpaceDocument
doc = DesignSpaceDocument()
doc.read(args[0])
doc.read(args.designspacefile)
locs = [s.location for s in doc.sources]
print("Original locations:")
pprint(locs)
@ -452,7 +457,7 @@ def main(args):
pprint(locs)
else:
axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args]
locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args.locations]
model = VariationModel(locs)
print("Sorted locations:")
@ -464,6 +469,6 @@ if __name__ == "__main__":
import doctest, sys
if len(sys.argv) > 1:
sys.exit(main(sys.argv))
sys.exit(main())
sys.exit(doctest.testmod().failed)

View File

@ -551,7 +551,7 @@ def main(args=None):
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
parser = ArgumentParser(prog='varLib.varStore')
parser = ArgumentParser(prog='varLib.varStore', description= main.__doc__)
parser.add_argument('fontfile')
parser.add_argument('outfile', nargs='?')
options = parser.parse_args(args)