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( 29 glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION): 30 quadGlyphs = {} 31 for gname in glyphs.keys(): 32 glyph = glyphs[gname] 33 ttPen = TTGlyphPen(glyphs) 34 cu2quPen = Cu2QuPen(ttPen, max_err, 35 reverse_direction=reverse_direction) 36 glyph.draw(cu2quPen) 37 quadGlyphs[gname] = ttPen.glyph() 38 return quadGlyphs 39 40 41def update_hmtx(ttFont, glyf): 42 hmtx = ttFont["hmtx"] 43 for glyphName, glyph in glyf.glyphs.items(): 44 if hasattr(glyph, 'xMin'): 45 hmtx[glyphName] = (hmtx[glyphName][0], glyph.xMin) 46 47 48def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs): 49 assert ttFont.sfntVersion == "OTTO" 50 assert "CFF " in ttFont 51 52 glyphOrder = ttFont.getGlyphOrder() 53 54 ttFont["loca"] = newTable("loca") 55 ttFont["glyf"] = glyf = newTable("glyf") 56 glyf.glyphOrder = glyphOrder 57 glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs) 58 del ttFont["CFF "] 59 glyf.compile(ttFont) 60 update_hmtx(ttFont, glyf) 61 62 ttFont["maxp"] = maxp = newTable("maxp") 63 maxp.tableVersion = 0x00010000 64 maxp.maxZones = 1 65 maxp.maxTwilightPoints = 0 66 maxp.maxStorage = 0 67 maxp.maxFunctionDefs = 0 68 maxp.maxInstructionDefs = 0 69 maxp.maxStackElements = 0 70 maxp.maxSizeOfInstructions = 0 71 maxp.maxComponentElements = max( 72 len(g.components if hasattr(g, 'components') else []) 73 for g in glyf.glyphs.values()) 74 maxp.compile(ttFont) 75 76 post = ttFont["post"] 77 post.formatType = post_format 78 post.extraNames = [] 79 post.mapping = {} 80 post.glyphOrder = glyphOrder 81 try: 82 post.compile(ttFont) 83 except OverflowError: 84 post.formatType = 3 85 log.warning("Dropping glyph names, they do not fit in 'post' table.") 86 87 ttFont.sfntVersion = "\000\001\000\000" 88 89 90def main(args=None): 91 configLogger(logger=log) 92 93 parser = argparse.ArgumentParser() 94 parser.add_argument("input", nargs='+', metavar="INPUT") 95 parser.add_argument("-o", "--output") 96 parser.add_argument("-e", "--max-error", type=float, default=MAX_ERR) 97 parser.add_argument("--post-format", type=float, default=POST_FORMAT) 98 parser.add_argument( 99 "--keep-direction", dest='reverse_direction', action='store_false') 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("-o/--output option must be a directory when " 107 "processing multiple fonts") 108 109 for path in options.input: 110 if options.output and not os.path.isdir(options.output): 111 output = options.output 112 else: 113 output = makeOutputFileName(path, outputDir=options.output, 114 extension='.ttf', 115 overWrite=options.overwrite) 116 117 font = TTFont(path, fontNumber=options.face_index) 118 otf_to_ttf(font, 119 post_format=options.post_format, 120 max_err=options.max_error, 121 reverse_direction=options.reverse_direction) 122 font.save(output) 123 124 125if __name__ == "__main__": 126 sys.exit(main()) 127