1 """\ 2 usage: ttx [options] inputfile1 [... inputfileN] 3 4 TTX %s -- From OpenType To XML And Back 5 6 If an input file is a TrueType or OpenType font file, it will be 7 dumped to an TTX file (an XML-based text format). 8 If an input file is a TTX file, it will be compiled to a TrueType 9 or OpenType font file. 10 11 Output files are created so they are unique: an existing file is 12 never overwritten. 13 14 General options: 15 -h Help: print this message 16 -d <outputfolder> Specify a directory where the output files are 17 to be created. 18 -o <outputfile> Specify a file to write the output to. 19 -v Verbose: more messages will be written to stdout about what 20 is being done. 21 -q Quiet: No messages will be written to stdout about what 22 is being done. 23 -a allow virtual glyphs ID's on compile or decompile. 24 25 Dump options: 26 -l List table info: instead of dumping to a TTX file, list some 27 minimal info about each table. 28 -t <table> Specify a table to dump. Multiple -t options 29 are allowed. When no -t option is specified, all tables 30 will be dumped. 31 -x <table> Specify a table to exclude from the dump. Multiple 32 -x options are allowed. -t and -x are mutually exclusive. 33 -s Split tables: save the TTX data into separate TTX files per 34 table and write one small TTX file that contains references 35 to the individual table dumps. This file can be used as 36 input to ttx, as long as the table files are in the 37 same directory. 38 -i Do NOT disassemble TT instructions: when this option is given, 39 all TrueType programs (glyph programs, the font program and the 40 pre-program) will be written to the TTX file as hex data 41 instead of assembly. This saves some time and makes the TTX 42 file smaller. 43 -z <format> Specify a bitmap data export option for EBDT: 44 {'raw', 'row', 'bitwise', 'extfile'} or for the CBDT: 45 {'raw', 'extfile'} Each option does one of the following: 46 -z raw 47 * export the bitmap data as a hex dump 48 -z row 49 * export each row as hex data 50 -z bitwise 51 * export each row as binary in an ASCII art style 52 -z extfile 53 * export the data as external files with XML refences 54 If no export format is specified 'raw' format is used. 55 -e Don't ignore decompilation errors, but show a full traceback 56 and abort. 57 -y <number> Select font number for TrueType Collection, 58 starting from 0. 59 60 Compile options: 61 -m Merge with TrueType-input-file: specify a TrueType or OpenType 62 font file to be merged with the TTX file. This option is only 63 valid when at most one TTX file is specified. 64 -b Don't recalc glyph bounding boxes: use the values in the TTX 65 file as-is. 66 """ 67 68 69 from __future__ import print_function, division, absolute_import 70 from fontTools.misc.py23 import * 71 from fontTools.ttLib import TTFont, TTLibError 72 from fontTools.misc.macCreatorType import getMacCreatorAndType 73 import os 74 import sys 75 import getopt 76 import re 77 78 def usage(): 79 from fontTools import version 80 print(__doc__ % version) 81 sys.exit(2) 82 83 84 numberAddedRE = re.compile("#\d+$") 85 opentypeheaderRE = re.compile('''sfntVersion=['"]OTTO["']''') 86 87 def makeOutputFileName(input, outputDir, extension): 88 dirName, fileName = os.path.split(input) 89 fileName, ext = os.path.splitext(fileName) 90 if outputDir: 91 dirName = outputDir 92 fileName = numberAddedRE.split(fileName)[0] 93 output = os.path.join(dirName, fileName + extension) 94 n = 1 95 while os.path.exists(output): 96 output = os.path.join(dirName, fileName + "#" + repr(n) + extension) 97 n = n + 1 98 return output 99 100 101 class Options(object): 102 103 listTables = False 104 outputDir = None 105 outputFile = None 106 verbose = False 107 quiet = False 108 splitTables = False 109 disassembleInstructions = True 110 mergeFile = None 111 recalcBBoxes = True 112 allowVID = False 113 ignoreDecompileErrors = True 114 bitmapGlyphDataFormat = 'raw' 115 116 def __init__(self, rawOptions, numFiles): 117 self.onlyTables = [] 118 self.skipTables = [] 119 self.fontNumber = -1 120 for option, value in rawOptions: 121 # general options 122 if option == "-h": 123 from fontTools import version 124 print(__doc__ % version) 125 sys.exit(0) 126 elif option == "-d": 127 if not os.path.isdir(value): 128 print("The -d option value must be an existing directory") 129 sys.exit(2) 130 self.outputDir = value 131 elif option == "-o": 132 self.outputFile = value 133 elif option == "-v": 134 self.verbose = True 135 elif option == "-q": 136 self.quiet = True 137 # dump options 138 elif option == "-l": 139 self.listTables = True 140 elif option == "-t": 141 self.onlyTables.append(value) 142 elif option == "-x": 143 self.skipTables.append(value) 144 elif option == "-s": 145 self.splitTables = True 146 elif option == "-i": 147 self.disassembleInstructions = False 148 elif option == "-z": 149 validOptions = ('raw', 'row', 'bitwise', 'extfile') 150 if value not in validOptions: 151 print("-z does not allow %s as a format. Use %s" % (option, validOptions)) 152 sys.exit(2) 153 self.bitmapGlyphDataFormat = value 154 elif option == "-y": 155 self.fontNumber = int(value) 156 # compile options 157 elif option == "-m": 158 self.mergeFile = value 159 elif option == "-b": 160 self.recalcBBoxes = False 161 elif option == "-a": 162 self.allowVID = True 163 elif option == "-e": 164 self.ignoreDecompileErrors = False 165 if self.onlyTables and self.skipTables: 166 print("-t and -x options are mutually exclusive") 167 sys.exit(2) 168 if self.mergeFile and numFiles > 1: 169 print("Must specify exactly one TTX source file when using -m") 170 sys.exit(2) 171 172 173 def ttList(input, output, options): 174 ttf = TTFont(input, fontNumber=options.fontNumber, lazy=True) 175 reader = ttf.reader 176 tags = sorted(reader.keys()) 177 print('Listing table info for "%s":' % input) 178 format = " %4s %10s %7s %7s" 179 print(format % ("tag ", " checksum", " length", " offset")) 180 print(format % ("----", "----------", "-------", "-------")) 181 for tag in tags: 182 entry = reader.tables[tag] 183 checkSum = int(entry.checkSum) 184 if checkSum < 0: 185 checkSum = checkSum + 0x100000000 186 checksum = "0x%08X" % checkSum 187 print(format % (tag, checksum, entry.length, entry.offset)) 188 print() 189 ttf.close() 190 191 192 def ttDump(input, output, options): 193 if not options.quiet: 194 print('Dumping "%s" to "%s"...' % (input, output)) 195 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID, 196 quiet=options.quiet, 197 ignoreDecompileErrors=options.ignoreDecompileErrors, 198 fontNumber=options.fontNumber) 199 ttf.saveXML(output, 200 quiet=options.quiet, 201 tables=options.onlyTables, 202 skipTables=options.skipTables, 203 splitTables=options.splitTables, 204 disassembleInstructions=options.disassembleInstructions, 205 bitmapGlyphDataFormat=options.bitmapGlyphDataFormat) 206 ttf.close() 207 208 209 def ttCompile(input, output, options): 210 if not options.quiet: 211 print('Compiling "%s" to "%s"...' % (input, output)) 212 ttf = TTFont(options.mergeFile, 213 recalcBBoxes=options.recalcBBoxes, 214 verbose=options.verbose, allowVID=options.allowVID) 215 ttf.importXML(input, quiet=options.quiet) 216 ttf.save(output) 217 218 if options.verbose: 219 import time 220 print("finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))) 221 222 223 def guessFileType(fileName): 224 base, ext = os.path.splitext(fileName) 225 try: 226 f = open(fileName, "rb") 227 except IOError: 228 return None 229 cr, tp = getMacCreatorAndType(fileName) 230 if tp in ("sfnt", "FFIL"): 231 return "TTF" 232 if ext == ".dfont": 233 return "TTF" 234 header = f.read(256) 235 head = Tag(header[:4]) 236 if head == "OTTO": 237 return "OTF" 238 elif head == "ttcf": 239 return "TTC" 240 elif head in ("\0\1\0\0", "true"): 241 return "TTF" 242 elif head == "wOFF": 243 return "WOFF" 244 elif head.lower() == "<?xm": 245 # Use 'latin1' because that can't fail. 246 header = tostr(header, 'latin1') 247 if opentypeheaderRE.search(header): 248 return "OTX" 249 else: 250 return "TTX" 251 return None 252 253 254 def parseOptions(args): 255 try: 256 rawOptions, files = getopt.getopt(args, "ld:o:vqht:x:sim:z:baey:") 257 except getopt.GetoptError: 258 usage() 259 260 if not files: 261 usage() 262 263 options = Options(rawOptions, len(files)) 264 jobs = [] 265 266 for input in files: 267 tp = guessFileType(input) 268 if tp in ("OTF", "TTF", "TTC", "WOFF"): 269 extension = ".ttx" 270 if options.listTables: 271 action = ttList 272 else: 273 action = ttDump 274 elif tp == "TTX": 275 extension = ".ttf" 276 action = ttCompile 277 elif tp == "OTX": 278 extension = ".otf" 279 action = ttCompile 280 else: 281 print('Unknown file type: "%s"' % input) 282 continue 283 284 if options.outputFile: 285 output = options.outputFile 286 else: 287 output = makeOutputFileName(input, options.outputDir, extension) 288 jobs.append((action, input, output)) 289 return jobs, options 290 291 292 def process(jobs, options): 293 for action, input, output in jobs: 294 action(input, output, options) 295 296 297 def waitForKeyPress(): 298 """Force the DOS Prompt window to stay open so the user gets 299 a chance to see what's wrong.""" 300 import msvcrt 301 print('(Hit any key to exit)') 302 while not msvcrt.kbhit(): 303 pass 304 305 306 def main(args): 307 jobs, options = parseOptions(args) 308 try: 309 process(jobs, options) 310 except KeyboardInterrupt: 311 print("(Cancelled.)") 312 except SystemExit: 313 if sys.platform == "win32": 314 waitForKeyPress() 315 else: 316 raise 317 except TTLibError as e: 318 print("Error:",e) 319 except: 320 if sys.platform == "win32": 321 import traceback 322 traceback.print_exc() 323 waitForKeyPress() 324 else: 325 raise 326 327 328 if __name__ == "__main__": 329 main(sys.argv[1:]) 330