1*e1fe3e4aSElliott Hughes"""\ 2*e1fe3e4aSElliott Hughesusage: ttx [options] inputfile1 [... inputfileN] 3*e1fe3e4aSElliott Hughes 4*e1fe3e4aSElliott HughesTTX -- From OpenType To XML And Back 5*e1fe3e4aSElliott Hughes 6*e1fe3e4aSElliott HughesIf an input file is a TrueType or OpenType font file, it will be 7*e1fe3e4aSElliott Hughesdecompiled to a TTX file (an XML-based text format). 8*e1fe3e4aSElliott HughesIf an input file is a TTX file, it will be compiled to whatever 9*e1fe3e4aSElliott Hughesformat the data is in, a TrueType or OpenType/CFF font file. 10*e1fe3e4aSElliott HughesA special input value of - means read from the standard input. 11*e1fe3e4aSElliott Hughes 12*e1fe3e4aSElliott HughesOutput files are created so they are unique: an existing file is 13*e1fe3e4aSElliott Hughesnever overwritten. 14*e1fe3e4aSElliott Hughes 15*e1fe3e4aSElliott HughesGeneral options 16*e1fe3e4aSElliott Hughes=============== 17*e1fe3e4aSElliott Hughes 18*e1fe3e4aSElliott Hughes-h Help print this message. 19*e1fe3e4aSElliott Hughes--version show version and exit. 20*e1fe3e4aSElliott Hughes-d <outputfolder> Specify a directory where the output files are 21*e1fe3e4aSElliott Hughes to be created. 22*e1fe3e4aSElliott Hughes-o <outputfile> Specify a file to write the output to. A special 23*e1fe3e4aSElliott Hughes value of - would use the standard output. 24*e1fe3e4aSElliott Hughes-f Overwrite existing output file(s), ie. don't append 25*e1fe3e4aSElliott Hughes numbers. 26*e1fe3e4aSElliott Hughes-v Verbose: more messages will be written to stdout 27*e1fe3e4aSElliott Hughes about what is being done. 28*e1fe3e4aSElliott Hughes-q Quiet: No messages will be written to stdout about 29*e1fe3e4aSElliott Hughes what is being done. 30*e1fe3e4aSElliott Hughes-a allow virtual glyphs ID's on compile or decompile. 31*e1fe3e4aSElliott Hughes 32*e1fe3e4aSElliott HughesDump options 33*e1fe3e4aSElliott Hughes============ 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes-l List table info: instead of dumping to a TTX file, list 36*e1fe3e4aSElliott Hughes some minimal info about each table. 37*e1fe3e4aSElliott Hughes-t <table> Specify a table to dump. Multiple -t options 38*e1fe3e4aSElliott Hughes are allowed. When no -t option is specified, all tables 39*e1fe3e4aSElliott Hughes will be dumped. 40*e1fe3e4aSElliott Hughes-x <table> Specify a table to exclude from the dump. Multiple 41*e1fe3e4aSElliott Hughes -x options are allowed. -t and -x are mutually exclusive. 42*e1fe3e4aSElliott Hughes-s Split tables: save the TTX data into separate TTX files per 43*e1fe3e4aSElliott Hughes table and write one small TTX file that contains references 44*e1fe3e4aSElliott Hughes to the individual table dumps. This file can be used as 45*e1fe3e4aSElliott Hughes input to ttx, as long as the table files are in the 46*e1fe3e4aSElliott Hughes same directory. 47*e1fe3e4aSElliott Hughes-g Split glyf table: Save the glyf data into separate TTX files 48*e1fe3e4aSElliott Hughes per glyph and write a small TTX for the glyf table which 49*e1fe3e4aSElliott Hughes contains references to the individual TTGlyph elements. 50*e1fe3e4aSElliott Hughes NOTE: specifying -g implies -s (no need for -s together 51*e1fe3e4aSElliott Hughes with -g) 52*e1fe3e4aSElliott Hughes-i Do NOT disassemble TT instructions: when this option is 53*e1fe3e4aSElliott Hughes given, all TrueType programs (glyph programs, the font 54*e1fe3e4aSElliott Hughes program and the pre-program) will be written to the TTX 55*e1fe3e4aSElliott Hughes file as hex data instead of assembly. This saves some time 56*e1fe3e4aSElliott Hughes and makes the TTX file smaller. 57*e1fe3e4aSElliott Hughes-z <format> Specify a bitmap data export option for EBDT: 58*e1fe3e4aSElliott Hughes {'raw', 'row', 'bitwise', 'extfile'} or for the CBDT: 59*e1fe3e4aSElliott Hughes {'raw', 'extfile'} Each option does one of the following: 60*e1fe3e4aSElliott Hughes 61*e1fe3e4aSElliott Hughes -z raw 62*e1fe3e4aSElliott Hughes export the bitmap data as a hex dump 63*e1fe3e4aSElliott Hughes -z row 64*e1fe3e4aSElliott Hughes export each row as hex data 65*e1fe3e4aSElliott Hughes -z bitwise 66*e1fe3e4aSElliott Hughes export each row as binary in an ASCII art style 67*e1fe3e4aSElliott Hughes -z extfile 68*e1fe3e4aSElliott Hughes export the data as external files with XML references 69*e1fe3e4aSElliott Hughes 70*e1fe3e4aSElliott Hughes If no export format is specified 'raw' format is used. 71*e1fe3e4aSElliott Hughes-e Don't ignore decompilation errors, but show a full traceback 72*e1fe3e4aSElliott Hughes and abort. 73*e1fe3e4aSElliott Hughes-y <number> Select font number for TrueType Collection (.ttc/.otc), 74*e1fe3e4aSElliott Hughes starting from 0. 75*e1fe3e4aSElliott Hughes--unicodedata <UnicodeData.txt> 76*e1fe3e4aSElliott Hughes Use custom database file to write character names in the 77*e1fe3e4aSElliott Hughes comments of the cmap TTX output. 78*e1fe3e4aSElliott Hughes--newline <value> 79*e1fe3e4aSElliott Hughes Control how line endings are written in the XML file. It 80*e1fe3e4aSElliott Hughes can be 'LF', 'CR', or 'CRLF'. If not specified, the 81*e1fe3e4aSElliott Hughes default platform-specific line endings are used. 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott HughesCompile options 84*e1fe3e4aSElliott Hughes=============== 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes-m Merge with TrueType-input-file: specify a TrueType or 87*e1fe3e4aSElliott Hughes OpenType font file to be merged with the TTX file. This 88*e1fe3e4aSElliott Hughes option is only valid when at most one TTX file is specified. 89*e1fe3e4aSElliott Hughes-b Don't recalc glyph bounding boxes: use the values in the 90*e1fe3e4aSElliott Hughes TTX file as-is. 91*e1fe3e4aSElliott Hughes--recalc-timestamp 92*e1fe3e4aSElliott Hughes Set font 'modified' timestamp to current time. 93*e1fe3e4aSElliott Hughes By default, the modification time of the TTX file will be 94*e1fe3e4aSElliott Hughes used. 95*e1fe3e4aSElliott Hughes--no-recalc-timestamp 96*e1fe3e4aSElliott Hughes Keep the original font 'modified' timestamp. 97*e1fe3e4aSElliott Hughes--flavor <type> 98*e1fe3e4aSElliott Hughes Specify flavor of output font file. May be 'woff' or 'woff2'. 99*e1fe3e4aSElliott Hughes Note that WOFF2 requires the Brotli Python extension, 100*e1fe3e4aSElliott Hughes available at https://github.com/google/brotli 101*e1fe3e4aSElliott Hughes--with-zopfli 102*e1fe3e4aSElliott Hughes Use Zopfli instead of Zlib to compress WOFF. The Python 103*e1fe3e4aSElliott Hughes extension is available at https://pypi.python.org/pypi/zopfli 104*e1fe3e4aSElliott Hughes""" 105*e1fe3e4aSElliott Hughes 106*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, TTLibError 107*e1fe3e4aSElliott Hughesfrom fontTools.misc.macCreatorType import getMacCreatorAndType 108*e1fe3e4aSElliott Hughesfrom fontTools.unicode import setUnicodeData 109*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import Tag, tostr 110*e1fe3e4aSElliott Hughesfrom fontTools.misc.timeTools import timestampSinceEpoch 111*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import Timer 112*e1fe3e4aSElliott Hughesfrom fontTools.misc.cliTools import makeOutputFileName 113*e1fe3e4aSElliott Hughesimport os 114*e1fe3e4aSElliott Hughesimport sys 115*e1fe3e4aSElliott Hughesimport getopt 116*e1fe3e4aSElliott Hughesimport re 117*e1fe3e4aSElliott Hughesimport logging 118*e1fe3e4aSElliott Hughes 119*e1fe3e4aSElliott Hughes 120*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.ttx") 121*e1fe3e4aSElliott Hughes 122*e1fe3e4aSElliott HughesopentypeheaderRE = re.compile("""sfntVersion=['"]OTTO["']""") 123*e1fe3e4aSElliott Hughes 124*e1fe3e4aSElliott Hughes 125*e1fe3e4aSElliott Hughesclass Options(object): 126*e1fe3e4aSElliott Hughes listTables = False 127*e1fe3e4aSElliott Hughes outputDir = None 128*e1fe3e4aSElliott Hughes outputFile = None 129*e1fe3e4aSElliott Hughes overWrite = False 130*e1fe3e4aSElliott Hughes verbose = False 131*e1fe3e4aSElliott Hughes quiet = False 132*e1fe3e4aSElliott Hughes splitTables = False 133*e1fe3e4aSElliott Hughes splitGlyphs = False 134*e1fe3e4aSElliott Hughes disassembleInstructions = True 135*e1fe3e4aSElliott Hughes mergeFile = None 136*e1fe3e4aSElliott Hughes recalcBBoxes = True 137*e1fe3e4aSElliott Hughes ignoreDecompileErrors = True 138*e1fe3e4aSElliott Hughes bitmapGlyphDataFormat = "raw" 139*e1fe3e4aSElliott Hughes unicodedata = None 140*e1fe3e4aSElliott Hughes newlinestr = "\n" 141*e1fe3e4aSElliott Hughes recalcTimestamp = None 142*e1fe3e4aSElliott Hughes flavor = None 143*e1fe3e4aSElliott Hughes useZopfli = False 144*e1fe3e4aSElliott Hughes 145*e1fe3e4aSElliott Hughes def __init__(self, rawOptions, numFiles): 146*e1fe3e4aSElliott Hughes self.onlyTables = [] 147*e1fe3e4aSElliott Hughes self.skipTables = [] 148*e1fe3e4aSElliott Hughes self.fontNumber = -1 149*e1fe3e4aSElliott Hughes for option, value in rawOptions: 150*e1fe3e4aSElliott Hughes # general options 151*e1fe3e4aSElliott Hughes if option == "-h": 152*e1fe3e4aSElliott Hughes print(__doc__) 153*e1fe3e4aSElliott Hughes sys.exit(0) 154*e1fe3e4aSElliott Hughes elif option == "--version": 155*e1fe3e4aSElliott Hughes from fontTools import version 156*e1fe3e4aSElliott Hughes 157*e1fe3e4aSElliott Hughes print(version) 158*e1fe3e4aSElliott Hughes sys.exit(0) 159*e1fe3e4aSElliott Hughes elif option == "-d": 160*e1fe3e4aSElliott Hughes if not os.path.isdir(value): 161*e1fe3e4aSElliott Hughes raise getopt.GetoptError( 162*e1fe3e4aSElliott Hughes "The -d option value must be an existing directory" 163*e1fe3e4aSElliott Hughes ) 164*e1fe3e4aSElliott Hughes self.outputDir = value 165*e1fe3e4aSElliott Hughes elif option == "-o": 166*e1fe3e4aSElliott Hughes self.outputFile = value 167*e1fe3e4aSElliott Hughes elif option == "-f": 168*e1fe3e4aSElliott Hughes self.overWrite = True 169*e1fe3e4aSElliott Hughes elif option == "-v": 170*e1fe3e4aSElliott Hughes self.verbose = True 171*e1fe3e4aSElliott Hughes elif option == "-q": 172*e1fe3e4aSElliott Hughes self.quiet = True 173*e1fe3e4aSElliott Hughes # dump options 174*e1fe3e4aSElliott Hughes elif option == "-l": 175*e1fe3e4aSElliott Hughes self.listTables = True 176*e1fe3e4aSElliott Hughes elif option == "-t": 177*e1fe3e4aSElliott Hughes # pad with space if table tag length is less than 4 178*e1fe3e4aSElliott Hughes value = value.ljust(4) 179*e1fe3e4aSElliott Hughes self.onlyTables.append(value) 180*e1fe3e4aSElliott Hughes elif option == "-x": 181*e1fe3e4aSElliott Hughes # pad with space if table tag length is less than 4 182*e1fe3e4aSElliott Hughes value = value.ljust(4) 183*e1fe3e4aSElliott Hughes self.skipTables.append(value) 184*e1fe3e4aSElliott Hughes elif option == "-s": 185*e1fe3e4aSElliott Hughes self.splitTables = True 186*e1fe3e4aSElliott Hughes elif option == "-g": 187*e1fe3e4aSElliott Hughes # -g implies (and forces) splitTables 188*e1fe3e4aSElliott Hughes self.splitGlyphs = True 189*e1fe3e4aSElliott Hughes self.splitTables = True 190*e1fe3e4aSElliott Hughes elif option == "-i": 191*e1fe3e4aSElliott Hughes self.disassembleInstructions = False 192*e1fe3e4aSElliott Hughes elif option == "-z": 193*e1fe3e4aSElliott Hughes validOptions = ("raw", "row", "bitwise", "extfile") 194*e1fe3e4aSElliott Hughes if value not in validOptions: 195*e1fe3e4aSElliott Hughes raise getopt.GetoptError( 196*e1fe3e4aSElliott Hughes "-z does not allow %s as a format. Use %s" 197*e1fe3e4aSElliott Hughes % (option, validOptions) 198*e1fe3e4aSElliott Hughes ) 199*e1fe3e4aSElliott Hughes self.bitmapGlyphDataFormat = value 200*e1fe3e4aSElliott Hughes elif option == "-y": 201*e1fe3e4aSElliott Hughes self.fontNumber = int(value) 202*e1fe3e4aSElliott Hughes # compile options 203*e1fe3e4aSElliott Hughes elif option == "-m": 204*e1fe3e4aSElliott Hughes self.mergeFile = value 205*e1fe3e4aSElliott Hughes elif option == "-b": 206*e1fe3e4aSElliott Hughes self.recalcBBoxes = False 207*e1fe3e4aSElliott Hughes elif option == "-e": 208*e1fe3e4aSElliott Hughes self.ignoreDecompileErrors = False 209*e1fe3e4aSElliott Hughes elif option == "--unicodedata": 210*e1fe3e4aSElliott Hughes self.unicodedata = value 211*e1fe3e4aSElliott Hughes elif option == "--newline": 212*e1fe3e4aSElliott Hughes validOptions = ("LF", "CR", "CRLF") 213*e1fe3e4aSElliott Hughes if value == "LF": 214*e1fe3e4aSElliott Hughes self.newlinestr = "\n" 215*e1fe3e4aSElliott Hughes elif value == "CR": 216*e1fe3e4aSElliott Hughes self.newlinestr = "\r" 217*e1fe3e4aSElliott Hughes elif value == "CRLF": 218*e1fe3e4aSElliott Hughes self.newlinestr = "\r\n" 219*e1fe3e4aSElliott Hughes else: 220*e1fe3e4aSElliott Hughes raise getopt.GetoptError( 221*e1fe3e4aSElliott Hughes "Invalid choice for --newline: %r (choose from %s)" 222*e1fe3e4aSElliott Hughes % (value, ", ".join(map(repr, validOptions))) 223*e1fe3e4aSElliott Hughes ) 224*e1fe3e4aSElliott Hughes elif option == "--recalc-timestamp": 225*e1fe3e4aSElliott Hughes self.recalcTimestamp = True 226*e1fe3e4aSElliott Hughes elif option == "--no-recalc-timestamp": 227*e1fe3e4aSElliott Hughes self.recalcTimestamp = False 228*e1fe3e4aSElliott Hughes elif option == "--flavor": 229*e1fe3e4aSElliott Hughes self.flavor = value 230*e1fe3e4aSElliott Hughes elif option == "--with-zopfli": 231*e1fe3e4aSElliott Hughes self.useZopfli = True 232*e1fe3e4aSElliott Hughes if self.verbose and self.quiet: 233*e1fe3e4aSElliott Hughes raise getopt.GetoptError("-q and -v options are mutually exclusive") 234*e1fe3e4aSElliott Hughes if self.verbose: 235*e1fe3e4aSElliott Hughes self.logLevel = logging.DEBUG 236*e1fe3e4aSElliott Hughes elif self.quiet: 237*e1fe3e4aSElliott Hughes self.logLevel = logging.WARNING 238*e1fe3e4aSElliott Hughes else: 239*e1fe3e4aSElliott Hughes self.logLevel = logging.INFO 240*e1fe3e4aSElliott Hughes if self.mergeFile and self.flavor: 241*e1fe3e4aSElliott Hughes raise getopt.GetoptError("-m and --flavor options are mutually exclusive") 242*e1fe3e4aSElliott Hughes if self.onlyTables and self.skipTables: 243*e1fe3e4aSElliott Hughes raise getopt.GetoptError("-t and -x options are mutually exclusive") 244*e1fe3e4aSElliott Hughes if self.mergeFile and numFiles > 1: 245*e1fe3e4aSElliott Hughes raise getopt.GetoptError( 246*e1fe3e4aSElliott Hughes "Must specify exactly one TTX source file when using -m" 247*e1fe3e4aSElliott Hughes ) 248*e1fe3e4aSElliott Hughes if self.flavor != "woff" and self.useZopfli: 249*e1fe3e4aSElliott Hughes raise getopt.GetoptError("--with-zopfli option requires --flavor 'woff'") 250*e1fe3e4aSElliott Hughes 251*e1fe3e4aSElliott Hughes 252*e1fe3e4aSElliott Hughesdef ttList(input, output, options): 253*e1fe3e4aSElliott Hughes ttf = TTFont(input, fontNumber=options.fontNumber, lazy=True) 254*e1fe3e4aSElliott Hughes reader = ttf.reader 255*e1fe3e4aSElliott Hughes tags = sorted(reader.keys()) 256*e1fe3e4aSElliott Hughes print('Listing table info for "%s":' % input) 257*e1fe3e4aSElliott Hughes format = " %4s %10s %8s %8s" 258*e1fe3e4aSElliott Hughes print(format % ("tag ", " checksum", " length", " offset")) 259*e1fe3e4aSElliott Hughes print(format % ("----", "----------", "--------", "--------")) 260*e1fe3e4aSElliott Hughes for tag in tags: 261*e1fe3e4aSElliott Hughes entry = reader.tables[tag] 262*e1fe3e4aSElliott Hughes if ttf.flavor == "woff2": 263*e1fe3e4aSElliott Hughes # WOFF2 doesn't store table checksums, so they must be calculated 264*e1fe3e4aSElliott Hughes from fontTools.ttLib.sfnt import calcChecksum 265*e1fe3e4aSElliott Hughes 266*e1fe3e4aSElliott Hughes data = entry.loadData(reader.transformBuffer) 267*e1fe3e4aSElliott Hughes checkSum = calcChecksum(data) 268*e1fe3e4aSElliott Hughes else: 269*e1fe3e4aSElliott Hughes checkSum = int(entry.checkSum) 270*e1fe3e4aSElliott Hughes if checkSum < 0: 271*e1fe3e4aSElliott Hughes checkSum = checkSum + 0x100000000 272*e1fe3e4aSElliott Hughes checksum = "0x%08X" % checkSum 273*e1fe3e4aSElliott Hughes print(format % (tag, checksum, entry.length, entry.offset)) 274*e1fe3e4aSElliott Hughes print() 275*e1fe3e4aSElliott Hughes ttf.close() 276*e1fe3e4aSElliott Hughes 277*e1fe3e4aSElliott Hughes 278*e1fe3e4aSElliott Hughes@Timer(log, "Done dumping TTX in %(time).3f seconds") 279*e1fe3e4aSElliott Hughesdef ttDump(input, output, options): 280*e1fe3e4aSElliott Hughes input_name = input 281*e1fe3e4aSElliott Hughes if input == "-": 282*e1fe3e4aSElliott Hughes input, input_name = sys.stdin.buffer, sys.stdin.name 283*e1fe3e4aSElliott Hughes output_name = output 284*e1fe3e4aSElliott Hughes if output == "-": 285*e1fe3e4aSElliott Hughes output, output_name = sys.stdout, sys.stdout.name 286*e1fe3e4aSElliott Hughes log.info('Dumping "%s" to "%s"...', input_name, output_name) 287*e1fe3e4aSElliott Hughes if options.unicodedata: 288*e1fe3e4aSElliott Hughes setUnicodeData(options.unicodedata) 289*e1fe3e4aSElliott Hughes ttf = TTFont( 290*e1fe3e4aSElliott Hughes input, 291*e1fe3e4aSElliott Hughes 0, 292*e1fe3e4aSElliott Hughes ignoreDecompileErrors=options.ignoreDecompileErrors, 293*e1fe3e4aSElliott Hughes fontNumber=options.fontNumber, 294*e1fe3e4aSElliott Hughes ) 295*e1fe3e4aSElliott Hughes ttf.saveXML( 296*e1fe3e4aSElliott Hughes output, 297*e1fe3e4aSElliott Hughes tables=options.onlyTables, 298*e1fe3e4aSElliott Hughes skipTables=options.skipTables, 299*e1fe3e4aSElliott Hughes splitTables=options.splitTables, 300*e1fe3e4aSElliott Hughes splitGlyphs=options.splitGlyphs, 301*e1fe3e4aSElliott Hughes disassembleInstructions=options.disassembleInstructions, 302*e1fe3e4aSElliott Hughes bitmapGlyphDataFormat=options.bitmapGlyphDataFormat, 303*e1fe3e4aSElliott Hughes newlinestr=options.newlinestr, 304*e1fe3e4aSElliott Hughes ) 305*e1fe3e4aSElliott Hughes ttf.close() 306*e1fe3e4aSElliott Hughes 307*e1fe3e4aSElliott Hughes 308*e1fe3e4aSElliott Hughes@Timer(log, "Done compiling TTX in %(time).3f seconds") 309*e1fe3e4aSElliott Hughesdef ttCompile(input, output, options): 310*e1fe3e4aSElliott Hughes input_name = input 311*e1fe3e4aSElliott Hughes if input == "-": 312*e1fe3e4aSElliott Hughes input, input_name = sys.stdin, sys.stdin.name 313*e1fe3e4aSElliott Hughes output_name = output 314*e1fe3e4aSElliott Hughes if output == "-": 315*e1fe3e4aSElliott Hughes output, output_name = sys.stdout.buffer, sys.stdout.name 316*e1fe3e4aSElliott Hughes log.info('Compiling "%s" to "%s"...' % (input_name, output)) 317*e1fe3e4aSElliott Hughes if options.useZopfli: 318*e1fe3e4aSElliott Hughes from fontTools.ttLib import sfnt 319*e1fe3e4aSElliott Hughes 320*e1fe3e4aSElliott Hughes sfnt.USE_ZOPFLI = True 321*e1fe3e4aSElliott Hughes ttf = TTFont( 322*e1fe3e4aSElliott Hughes options.mergeFile, 323*e1fe3e4aSElliott Hughes flavor=options.flavor, 324*e1fe3e4aSElliott Hughes recalcBBoxes=options.recalcBBoxes, 325*e1fe3e4aSElliott Hughes recalcTimestamp=options.recalcTimestamp, 326*e1fe3e4aSElliott Hughes ) 327*e1fe3e4aSElliott Hughes ttf.importXML(input) 328*e1fe3e4aSElliott Hughes 329*e1fe3e4aSElliott Hughes if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin: 330*e1fe3e4aSElliott Hughes # use TTX file modification time for head "modified" timestamp 331*e1fe3e4aSElliott Hughes mtime = os.path.getmtime(input) 332*e1fe3e4aSElliott Hughes ttf["head"].modified = timestampSinceEpoch(mtime) 333*e1fe3e4aSElliott Hughes 334*e1fe3e4aSElliott Hughes ttf.save(output) 335*e1fe3e4aSElliott Hughes 336*e1fe3e4aSElliott Hughes 337*e1fe3e4aSElliott Hughesdef guessFileType(fileName): 338*e1fe3e4aSElliott Hughes if fileName == "-": 339*e1fe3e4aSElliott Hughes header = sys.stdin.buffer.peek(256) 340*e1fe3e4aSElliott Hughes ext = "" 341*e1fe3e4aSElliott Hughes else: 342*e1fe3e4aSElliott Hughes base, ext = os.path.splitext(fileName) 343*e1fe3e4aSElliott Hughes try: 344*e1fe3e4aSElliott Hughes with open(fileName, "rb") as f: 345*e1fe3e4aSElliott Hughes header = f.read(256) 346*e1fe3e4aSElliott Hughes except IOError: 347*e1fe3e4aSElliott Hughes return None 348*e1fe3e4aSElliott Hughes 349*e1fe3e4aSElliott Hughes if header.startswith(b"\xef\xbb\xbf<?xml"): 350*e1fe3e4aSElliott Hughes header = header.lstrip(b"\xef\xbb\xbf") 351*e1fe3e4aSElliott Hughes cr, tp = getMacCreatorAndType(fileName) 352*e1fe3e4aSElliott Hughes if tp in ("sfnt", "FFIL"): 353*e1fe3e4aSElliott Hughes return "TTF" 354*e1fe3e4aSElliott Hughes if ext == ".dfont": 355*e1fe3e4aSElliott Hughes return "TTF" 356*e1fe3e4aSElliott Hughes head = Tag(header[:4]) 357*e1fe3e4aSElliott Hughes if head == "OTTO": 358*e1fe3e4aSElliott Hughes return "OTF" 359*e1fe3e4aSElliott Hughes elif head == "ttcf": 360*e1fe3e4aSElliott Hughes return "TTC" 361*e1fe3e4aSElliott Hughes elif head in ("\0\1\0\0", "true"): 362*e1fe3e4aSElliott Hughes return "TTF" 363*e1fe3e4aSElliott Hughes elif head == "wOFF": 364*e1fe3e4aSElliott Hughes return "WOFF" 365*e1fe3e4aSElliott Hughes elif head == "wOF2": 366*e1fe3e4aSElliott Hughes return "WOFF2" 367*e1fe3e4aSElliott Hughes elif head == "<?xm": 368*e1fe3e4aSElliott Hughes # Use 'latin1' because that can't fail. 369*e1fe3e4aSElliott Hughes header = tostr(header, "latin1") 370*e1fe3e4aSElliott Hughes if opentypeheaderRE.search(header): 371*e1fe3e4aSElliott Hughes return "OTX" 372*e1fe3e4aSElliott Hughes else: 373*e1fe3e4aSElliott Hughes return "TTX" 374*e1fe3e4aSElliott Hughes return None 375*e1fe3e4aSElliott Hughes 376*e1fe3e4aSElliott Hughes 377*e1fe3e4aSElliott Hughesdef parseOptions(args): 378*e1fe3e4aSElliott Hughes rawOptions, files = getopt.getopt( 379*e1fe3e4aSElliott Hughes args, 380*e1fe3e4aSElliott Hughes "ld:o:fvqht:x:sgim:z:baey:", 381*e1fe3e4aSElliott Hughes [ 382*e1fe3e4aSElliott Hughes "unicodedata=", 383*e1fe3e4aSElliott Hughes "recalc-timestamp", 384*e1fe3e4aSElliott Hughes "no-recalc-timestamp", 385*e1fe3e4aSElliott Hughes "flavor=", 386*e1fe3e4aSElliott Hughes "version", 387*e1fe3e4aSElliott Hughes "with-zopfli", 388*e1fe3e4aSElliott Hughes "newline=", 389*e1fe3e4aSElliott Hughes ], 390*e1fe3e4aSElliott Hughes ) 391*e1fe3e4aSElliott Hughes 392*e1fe3e4aSElliott Hughes options = Options(rawOptions, len(files)) 393*e1fe3e4aSElliott Hughes jobs = [] 394*e1fe3e4aSElliott Hughes 395*e1fe3e4aSElliott Hughes if not files: 396*e1fe3e4aSElliott Hughes raise getopt.GetoptError("Must specify at least one input file") 397*e1fe3e4aSElliott Hughes 398*e1fe3e4aSElliott Hughes for input in files: 399*e1fe3e4aSElliott Hughes if input != "-" and not os.path.isfile(input): 400*e1fe3e4aSElliott Hughes raise getopt.GetoptError('File not found: "%s"' % input) 401*e1fe3e4aSElliott Hughes tp = guessFileType(input) 402*e1fe3e4aSElliott Hughes if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"): 403*e1fe3e4aSElliott Hughes extension = ".ttx" 404*e1fe3e4aSElliott Hughes if options.listTables: 405*e1fe3e4aSElliott Hughes action = ttList 406*e1fe3e4aSElliott Hughes else: 407*e1fe3e4aSElliott Hughes action = ttDump 408*e1fe3e4aSElliott Hughes elif tp == "TTX": 409*e1fe3e4aSElliott Hughes extension = "." + options.flavor if options.flavor else ".ttf" 410*e1fe3e4aSElliott Hughes action = ttCompile 411*e1fe3e4aSElliott Hughes elif tp == "OTX": 412*e1fe3e4aSElliott Hughes extension = "." + options.flavor if options.flavor else ".otf" 413*e1fe3e4aSElliott Hughes action = ttCompile 414*e1fe3e4aSElliott Hughes else: 415*e1fe3e4aSElliott Hughes raise getopt.GetoptError('Unknown file type: "%s"' % input) 416*e1fe3e4aSElliott Hughes 417*e1fe3e4aSElliott Hughes if options.outputFile: 418*e1fe3e4aSElliott Hughes output = options.outputFile 419*e1fe3e4aSElliott Hughes else: 420*e1fe3e4aSElliott Hughes if input == "-": 421*e1fe3e4aSElliott Hughes raise getopt.GetoptError("Must provide -o when reading from stdin") 422*e1fe3e4aSElliott Hughes output = makeOutputFileName( 423*e1fe3e4aSElliott Hughes input, options.outputDir, extension, options.overWrite 424*e1fe3e4aSElliott Hughes ) 425*e1fe3e4aSElliott Hughes # 'touch' output file to avoid race condition in choosing file names 426*e1fe3e4aSElliott Hughes if action != ttList: 427*e1fe3e4aSElliott Hughes open(output, "a").close() 428*e1fe3e4aSElliott Hughes jobs.append((action, input, output)) 429*e1fe3e4aSElliott Hughes return jobs, options 430*e1fe3e4aSElliott Hughes 431*e1fe3e4aSElliott Hughes 432*e1fe3e4aSElliott Hughesdef process(jobs, options): 433*e1fe3e4aSElliott Hughes for action, input, output in jobs: 434*e1fe3e4aSElliott Hughes action(input, output, options) 435*e1fe3e4aSElliott Hughes 436*e1fe3e4aSElliott Hughes 437*e1fe3e4aSElliott Hughesdef main(args=None): 438*e1fe3e4aSElliott Hughes """Convert OpenType fonts to XML and back""" 439*e1fe3e4aSElliott Hughes from fontTools import configLogger 440*e1fe3e4aSElliott Hughes 441*e1fe3e4aSElliott Hughes if args is None: 442*e1fe3e4aSElliott Hughes args = sys.argv[1:] 443*e1fe3e4aSElliott Hughes try: 444*e1fe3e4aSElliott Hughes jobs, options = parseOptions(args) 445*e1fe3e4aSElliott Hughes except getopt.GetoptError as e: 446*e1fe3e4aSElliott Hughes print("%s\nERROR: %s" % (__doc__, e), file=sys.stderr) 447*e1fe3e4aSElliott Hughes sys.exit(2) 448*e1fe3e4aSElliott Hughes 449*e1fe3e4aSElliott Hughes configLogger(level=options.logLevel) 450*e1fe3e4aSElliott Hughes 451*e1fe3e4aSElliott Hughes try: 452*e1fe3e4aSElliott Hughes process(jobs, options) 453*e1fe3e4aSElliott Hughes except KeyboardInterrupt: 454*e1fe3e4aSElliott Hughes log.error("(Cancelled.)") 455*e1fe3e4aSElliott Hughes sys.exit(1) 456*e1fe3e4aSElliott Hughes except SystemExit: 457*e1fe3e4aSElliott Hughes raise 458*e1fe3e4aSElliott Hughes except TTLibError as e: 459*e1fe3e4aSElliott Hughes log.error(e) 460*e1fe3e4aSElliott Hughes sys.exit(1) 461*e1fe3e4aSElliott Hughes except: 462*e1fe3e4aSElliott Hughes log.exception("Unhandled exception has occurred") 463*e1fe3e4aSElliott Hughes sys.exit(1) 464*e1fe3e4aSElliott Hughes 465*e1fe3e4aSElliott Hughes 466*e1fe3e4aSElliott Hughesif __name__ == "__main__": 467*e1fe3e4aSElliott Hughes sys.exit(main()) 468