1#!/usr/bin/env python3 2 3import argparse 4import logging 5import os 6import sys 7 8from fontTools.pens.cu2quPen import Cu2QuPen 9from fontTools import configLogger 10from fontTools.misc.cliTools import makeOutputFileName 11from fontTools.pens.ttGlyphPen import TTGlyphPen 12from fontTools.ttLib import TTFont, newTable 13 14 15log = logging.getLogger() 16 17# default approximation error, measured in UPEM 18MAX_ERR = 1.0 19 20# default 'post' table format 21POST_FORMAT = 2.0 22 23# assuming the input contours' direction is correctly set (counter-clockwise), 24# we just flip it to clockwise 25REVERSE_DIRECTION = True 26 27 28def glyphs_to_quadratic(glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION): 29 quadGlyphs = {} 30 for gname in glyphs.keys(): 31 glyph = glyphs[gname] 32 ttPen = TTGlyphPen(glyphs) 33 cu2quPen = Cu2QuPen(ttPen, max_err, reverse_direction=reverse_direction) 34 glyph.draw(cu2quPen) 35 quadGlyphs[gname] = ttPen.glyph() 36 return quadGlyphs 37 38 39def update_hmtx(ttFont, glyf): 40 hmtx = ttFont["hmtx"] 41 for glyphName, glyph in glyf.glyphs.items(): 42 if hasattr(glyph, "xMin"): 43 hmtx[glyphName] = (hmtx[glyphName][0], glyph.xMin) 44 45 46def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs): 47 assert ttFont.sfntVersion == "OTTO" 48 assert "CFF " in ttFont 49 50 glyphOrder = ttFont.getGlyphOrder() 51 52 ttFont["loca"] = newTable("loca") 53 ttFont["glyf"] = glyf = newTable("glyf") 54 glyf.glyphOrder = glyphOrder 55 glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs) 56 del ttFont["CFF "] 57 glyf.compile(ttFont) 58 update_hmtx(ttFont, glyf) 59 60 ttFont["maxp"] = maxp = newTable("maxp") 61 maxp.tableVersion = 0x00010000 62 maxp.maxZones = 1 63 maxp.maxTwilightPoints = 0 64 maxp.maxStorage = 0 65 maxp.maxFunctionDefs = 0 66 maxp.maxInstructionDefs = 0 67 maxp.maxStackElements = 0 68 maxp.maxSizeOfInstructions = 0 69 maxp.maxComponentElements = max( 70 len(g.components if hasattr(g, "components") else []) 71 for g in glyf.glyphs.values() 72 ) 73 maxp.compile(ttFont) 74 75 post = ttFont["post"] 76 post.formatType = post_format 77 post.extraNames = [] 78 post.mapping = {} 79 post.glyphOrder = glyphOrder 80 try: 81 post.compile(ttFont) 82 except OverflowError: 83 post.formatType = 3 84 log.warning("Dropping glyph names, they do not fit in 'post' table.") 85 86 ttFont.sfntVersion = "\000\001\000\000" 87 88 89def main(args=None): 90 configLogger(logger=log) 91 92 parser = argparse.ArgumentParser() 93 parser.add_argument("input", nargs="+", metavar="INPUT") 94 parser.add_argument("-o", "--output") 95 parser.add_argument("-e", "--max-error", type=float, default=MAX_ERR) 96 parser.add_argument("--post-format", type=float, default=POST_FORMAT) 97 parser.add_argument( 98 "--keep-direction", dest="reverse_direction", action="store_false" 99 ) 100 parser.add_argument("--face-index", type=int, default=0) 101 parser.add_argument("--overwrite", action="store_true") 102 options = parser.parse_args(args) 103 104 if options.output and len(options.input) > 1: 105 if not os.path.isdir(options.output): 106 parser.error( 107 "-o/--output option must be a directory when " 108 "processing multiple fonts" 109 ) 110 111 for path in options.input: 112 if options.output and not os.path.isdir(options.output): 113 output = options.output 114 else: 115 output = makeOutputFileName( 116 path, 117 outputDir=options.output, 118 extension=".ttf", 119 overWrite=options.overwrite, 120 ) 121 122 font = TTFont(path, fontNumber=options.face_index) 123 otf_to_ttf( 124 font, 125 post_format=options.post_format, 126 max_err=options.max_error, 127 reverse_direction=options.reverse_direction, 128 ) 129 font.save(output) 130 131 132if __name__ == "__main__": 133 sys.exit(main()) 134