1import os 2import argparse 3import logging 4from fontTools.misc.cliTools import makeOutputFileName 5from fontTools.ttLib import TTFont 6from fontTools.pens.qu2cuPen import Qu2CuPen 7from fontTools.pens.ttGlyphPen import TTGlyphPen 8import fontTools 9 10 11logger = logging.getLogger("fontTools.qu2cu") 12 13 14def _font_to_cubic(input_path, output_path=None, **kwargs): 15 font = TTFont(input_path) 16 logger.info("Converting curves for %s", input_path) 17 18 stats = {} if kwargs["dump_stats"] else None 19 qu2cu_kwargs = { 20 "stats": stats, 21 "max_err": kwargs["max_err_em"] * font["head"].unitsPerEm, 22 "all_cubic": kwargs["all_cubic"], 23 } 24 25 assert "gvar" not in font, "Cannot convert variable font" 26 glyphSet = font.getGlyphSet() 27 glyphOrder = font.getGlyphOrder() 28 glyf = font["glyf"] 29 for glyphName in glyphOrder: 30 glyph = glyphSet[glyphName] 31 ttpen = TTGlyphPen(glyphSet) 32 pen = Qu2CuPen(ttpen, **qu2cu_kwargs) 33 glyph.draw(pen) 34 glyf[glyphName] = ttpen.glyph(dropImpliedOnCurves=True) 35 36 font["head"].glyphDataFormat = 1 37 38 if kwargs["dump_stats"]: 39 logger.info("Stats: %s", stats) 40 41 logger.info("Saving %s", output_path) 42 font.save(output_path) 43 44 45def main(args=None): 46 """Convert an OpenType font from quadratic to cubic curves""" 47 parser = argparse.ArgumentParser(prog="qu2cu") 48 parser.add_argument("--version", action="version", version=fontTools.__version__) 49 parser.add_argument( 50 "infiles", 51 nargs="+", 52 metavar="INPUT", 53 help="one or more input TTF source file(s).", 54 ) 55 parser.add_argument("-v", "--verbose", action="count", default=0) 56 parser.add_argument( 57 "-e", 58 "--conversion-error", 59 type=float, 60 metavar="ERROR", 61 default=0.001, 62 help="maxiumum approximation error measured in EM (default: 0.001)", 63 ) 64 parser.add_argument( 65 "-c", 66 "--all-cubic", 67 default=False, 68 action="store_true", 69 help="whether to only use cubic curves", 70 ) 71 72 output_parser = parser.add_mutually_exclusive_group() 73 output_parser.add_argument( 74 "-o", 75 "--output-file", 76 default=None, 77 metavar="OUTPUT", 78 help=("output filename for the converted TTF."), 79 ) 80 output_parser.add_argument( 81 "-d", 82 "--output-dir", 83 default=None, 84 metavar="DIRECTORY", 85 help="output directory where to save converted TTFs", 86 ) 87 88 options = parser.parse_args(args) 89 90 if not options.verbose: 91 level = "WARNING" 92 elif options.verbose == 1: 93 level = "INFO" 94 else: 95 level = "DEBUG" 96 logging.basicConfig(level=level) 97 98 if len(options.infiles) > 1 and options.output_file: 99 parser.error("-o/--output-file can't be used with multile inputs") 100 101 if options.output_dir: 102 output_dir = options.output_dir 103 if not os.path.exists(output_dir): 104 os.mkdir(output_dir) 105 elif not os.path.isdir(output_dir): 106 parser.error("'%s' is not a directory" % output_dir) 107 output_paths = [ 108 os.path.join(output_dir, os.path.basename(p)) for p in options.infiles 109 ] 110 elif options.output_file: 111 output_paths = [options.output_file] 112 else: 113 output_paths = [ 114 makeOutputFileName(p, overWrite=True, suffix=".cubic") 115 for p in options.infiles 116 ] 117 118 kwargs = dict( 119 dump_stats=options.verbose > 0, 120 max_err_em=options.conversion_error, 121 all_cubic=options.all_cubic, 122 ) 123 124 for input_path, output_path in zip(options.infiles, output_paths): 125 _font_to_cubic(input_path, output_path, **kwargs) 126