ttx.py revision 6588c4e2dfa6d1995a69aadec386c4c1c467a666
1#! /usr/bin/env python 2 3"""\ 4usage: ttx [options] inputfile1 [... inputfileN] 5 6 TTX %s -- From OpenType To XML And Back 7 8 If an input file is a TrueType or OpenType font file, it will be 9 dumped to an TTX file (an XML-based text format). 10 If an input file is a TTX file, it will be compiled to a TrueType 11 or OpenType font file. 12 13 Output files are created so they are unique: an existing file is 14 never overwrritten. 15 16 General options: 17 -h Help: print this message 18 -d <outputfolder> Specify a directory where the output files are 19 to be created. 20 -v Verbose: more messages will be written to stdout about what 21 is being done. 22 23 Dump options: 24 -l List table info: instead of dumping to a TTX file, list some 25 minimal info about each table. 26 -t <table> Specify a table to dump. Multiple -t options 27 are allowed. When no -t option is specified, all tables 28 will be dumped. 29 -x <table> Specify a table to exclude from the dump. Multiple 30 -x options are allowed. -t and -x are mutually exclusive. 31 -s Split tables: save the TTX data into separate TTX files per 32 table and write one small TTX file that contains references 33 to the individual table dumps. This file can be used as 34 input to ttx, as long as the table files are in the 35 same directory. 36 -i Do NOT disassemble TT instructions: when this option is given, 37 all TrueType programs (glyph programs, the font program and the 38 pre-program) will be written to the TTX file as hex data 39 instead of assembly. This saves some time and makes the TTX 40 file smaller. 41 42 Compile options: 43 -m Merge with TrueType-input-file: specify a TrueType or OpenType 44 font file to be merged with the TTX file. This option is only 45 valid when at most one TTX file is specified. 46 -b Don't recalc glyph boundig boxes: use the values in the TTX 47 file as-is. 48""" 49 50 51import sys 52import os 53import getopt 54import re 55from fontTools.ttLib import TTFont 56from fontTools import version 57 58def usage(): 59 print __doc__ % version 60 sys.exit(2) 61 62 63if sys.platform == "darwin" and sys.version_info[:3] == (2, 2, 0): 64 # the Mac support of Jaguar's Python 2.2 is broken 65 have_broken_macsupport = 1 66else: 67 have_broken_macsupport = 0 68 69numberAddedRE = re.compile("(.*)#\d+$") 70 71def makeOutputFileName(input, outputDir, extension): 72 dir, file = os.path.split(input) 73 file, ext = os.path.splitext(file) 74 if outputDir: 75 dir = outputDir 76 output = os.path.join(dir, file + extension) 77 m = numberAddedRE.match(file) 78 if m: 79 file = m.group(1) 80 n = 1 81 while os.path.exists(output): 82 output = os.path.join(dir, file + "#" + repr(n) + extension) 83 n = n + 1 84 return output 85 86 87class Options: 88 89 listTables = 0 90 outputDir = None 91 verbose = 0 92 splitTables = 0 93 disassembleInstructions = 1 94 mergeFile = None 95 recalcBBoxes = 1 96 97 def __init__(self, rawOptions, numFiles): 98 self.onlyTables = [] 99 self.skipTables = [] 100 for option, value in rawOptions: 101 # general options 102 if option == "-h": 103 print __doc__ % version 104 sys.exit(0) 105 elif option == "-d": 106 if not os.path.isdir(value): 107 print "The -d option value must be an existing directory" 108 sys.exit(2) 109 self.outputDir = value 110 elif option == "-v": 111 self.verbose = 1 112 # dump options 113 elif option == "-l": 114 self.listTables = 1 115 elif option == "-t": 116 self.onlyTables.append(value) 117 elif option == "-x": 118 self.skipTables.append(value) 119 elif option == "-s": 120 self.splitTables = 1 121 elif option == "-i": 122 self.disassembleInstructions = 0 123 # compile options 124 elif option == "-m": 125 self.mergeFile = value 126 elif option == "-b": 127 self.recalcBBoxes = 0 128 if self.onlyTables and self.skipTables: 129 print "-t and -x options are mutually exclusive" 130 sys.exit(2) 131 if self.mergeFile and numFiles > 1: 132 print "Must specify exactly one TTX source file when using -m" 133 sys.exit(2) 134 135 136def ttList(input, output, options): 137 import string 138 ttf = TTFont(input) 139 reader = ttf.reader 140 tags = reader.keys() 141 tags.sort() 142 print 'Listing table info for "%s":' % input 143 format = " %4s %10s %7s %7s" 144 print format % ("tag ", " checksum", " length", " offset") 145 print format % ("----", "----------", "-------", "-------") 146 for tag in tags: 147 entry = reader.tables[tag] 148 checksum = "0x" + string.zfill(hex(entry.checkSum)[2:], 8) 149 print format % (tag, checksum, entry.length, entry.offset) 150 print 151 ttf.close() 152 153 154def ttDump(input, output, options): 155 print 'Dumping "%s" to "%s"...' % (input, output) 156 ttf = TTFont(input, 0, verbose=options.verbose) 157 ttf.saveXML(output, 158 tables=options.onlyTables, 159 skipTables=options.skipTables, 160 splitTables=options.splitTables, 161 disassembleInstructions=options.disassembleInstructions) 162 ttf.close() 163 164 165def ttCompile(input, output, options): 166 print 'Compiling "%s" to "%s"...' % (input, output) 167 ttf = TTFont(options.mergeFile, 168 recalcBBoxes=options.recalcBBoxes, 169 verbose=options.verbose) 170 ttf.importXML(input) 171 ttf.save(output) 172 173 if options.verbose: 174 import time 175 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time())) 176 177 178def guessFileType(fileName): 179 base, ext = os.path.splitext(fileName) 180 try: 181 f = open(fileName, "rb") 182 except IOError: 183 return None 184 if not have_broken_macsupport: 185 try: 186 import MacOS 187 except ImportError: 188 pass 189 else: 190 cr, tp = MacOS.GetCreatorAndType(fileName) 191 if tp in ("sfnt", "FFIL"): 192 return "TTF" 193 if ext == ".dfont": 194 return "TTF" 195 header = f.read(256) 196 head = header[:4] 197 if head == "OTTO": 198 return "OTF" 199 elif head in ("\0\1\0\0", "true"): 200 return "TTF" 201 elif head.lower() == "<?xm": 202 if header.find('sfntVersion="OTTO"') > 0: 203 return "OTX" 204 else: 205 return "TTX" 206 return None 207 208 209def parseOptions(args): 210 try: 211 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b") 212 except getopt.GetoptError: 213 usage() 214 215 if not files: 216 usage() 217 218 options = Options(rawOptions, len(files)) 219 jobs = [] 220 221 for input in files: 222 tp = guessFileType(input) 223 if tp in ("OTF", "TTF"): 224 extension = ".ttx" 225 if options.listTables: 226 action = ttList 227 else: 228 action = ttDump 229 elif tp == "TTX": 230 extension = ".ttf" 231 action = ttCompile 232 elif tp == "OTX": 233 extension = ".otf" 234 action = ttCompile 235 else: 236 print 'Unknown file type: "%s"' % input 237 continue 238 239 output = makeOutputFileName(input, options.outputDir, extension) 240 jobs.append((action, input, output)) 241 return jobs, options 242 243 244def process(jobs, options): 245 for action, input, output in jobs: 246 action(input, output, options) 247 248 249def waitForKeyPress(): 250 """Force the DOS Prompt window to stay open so the user gets 251 a chance to see what's wrong.""" 252 import msvcrt 253 print '(Hit any key to exit)' 254 while not msvcrt.kbhit(): 255 pass 256 257 258def main(args): 259 jobs, options = parseOptions(args) 260 try: 261 process(jobs, options) 262 except KeyboardInterrupt: 263 print "(Cancelled.)" 264 except SystemExit: 265 if sys.platform == "win32": 266 waitForKeyPress() 267 else: 268 raise 269 except: 270 if sys.platform == "win32": 271 import traceback 272 traceback.print_exc() 273 waitForKeyPress() 274 else: 275 raise 276 277 278if __name__ == "__main__": 279 main(sys.argv[1:]) 280