Home | History | Annotate | Download | only in fontTools
      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