ttx revision ef0c4137fed8ba4bc7bdfe76cea24f30f6c1bf9b
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
63numberAddedRE = re.compile("(.*)#\d+$")
64
65def makeOutputFileName(input, outputDir, extension):
66	dir, file = os.path.split(input)
67	file, ext = os.path.splitext(file)
68	if outputDir:
69		dir = outputDir
70	output = os.path.join(dir, file + extension)
71	m = numberAddedRE.match(file)
72	if m:
73		file = m.group(1)
74	n = 1
75	while os.path.exists(output):
76		output = os.path.join(dir, file + "#" + repr(n) + extension)
77		n = n + 1
78	return output
79
80
81class Options:
82
83	listTables = 0
84	outputDir = None
85	verbose = 0
86	splitTables = 0
87	disassembleInstructions = 1
88	mergeFile = None
89	recalcBBoxes = 1
90	
91	def __init__(self, rawOptions, numFiles):
92		self.onlyTables = []
93		self.skipTables = []
94		for option, value in rawOptions:
95			# general options
96			if option == "-h":
97				print __doc__ % version
98				sys.exit(0)
99			elif option == "-d":
100				if not os.path.isdir(value):
101					print "The -d option value must be an existing directory"
102					sys.exit(2)
103				self.outputDir = value
104			elif option == "-v":
105				self.verbose = 1
106			# dump options
107			elif option == "-l":
108				self.listTables = 1
109			elif option == "-t":
110				self.onlyTables.append(value)
111			elif option == "-x":
112				self.skipTables.append(value)
113			elif option == "-s":
114				self.splitTables = 1
115			elif option == "-i":
116				self.disassembleInstructions = 0
117			# compile options
118			elif option == "-m":
119				self.mergeFile = value
120			elif option == "-b":
121				self.recalcBBoxes = 0
122		if self.onlyTables and self.skipTables:
123			print "-t and -x options are mutually exlusive"
124			sys.exit(2)
125		if self.mergeFile and numFiles > 1:
126			print "Must specify exactly one TTX source file when using -i"
127			sys.exit(2)
128
129
130def ttList(input, extension, options):
131	ttf = TTFont(input)
132	reader = ttf.reader
133	tags = reader.keys()
134	tags.sort()
135	print 'Listing table info for "%s":' % input
136	format = "    %4s  %10s  %7s  %7s"
137	print format % ("tag ", "  checksum", " length", " offset")
138	print format % ("----", "----------", "-------", "-------")
139	for tag in tags:
140		entry = reader.tables[tag]
141		checksum = "0x" + hex(entry.checkSum)[2:].zfill(8)
142		print format % (tag, checksum, entry.length, entry.offset)
143	print
144	ttf.close()
145
146
147def ttDump(input, extension, options):
148	output = makeOutputFileName(input, options.outputDir, extension)
149	print 'Dumping "%s" to "%s"...' % (input, output)
150	ttf = TTFont(input, 0, verbose=options.verbose)
151	ttf.saveXML(output,
152			tables=options.onlyTables,
153			skipTables=options.skipTables, 
154			splitTables=options.splitTables,
155			disassembleInstructions=options.disassembleInstructions)
156	ttf.close()
157
158
159def ttCompile(input, extension, options):
160	output = makeOutputFileName(input, options.outputDir, extension)
161	print 'Compiling "%s" to "%s"...' % (input, output)
162	ttf = TTFont(options.mergeFile,
163			recalcBBoxes=options.recalcBBoxes,
164			verbose=options.verbose)
165	ttf.importXML(input)
166	ttf.save(output)
167
168	if options.verbose:
169		import time
170		print "%s finished at" % sys.argv[0], time.strftime("%H:%M:%S", time.localtime(time.time()))
171
172
173def guessFileType(fileName):
174	try:
175		f = open(fileName, "rb")
176	except IOError:
177		return None
178	header = f.read(256)
179	head = header[:4]
180	if head == "OTTO":
181		return "OTF"
182	elif head in ("\0\1\0\0", "true"):
183		return "TTF"
184	elif head.lower() == "<?xm":
185		if header.find('sfntVersion="OTTO"') > 0:
186			return "OTX"
187		else:
188			return "TTX"
189	# XXX Mac suitcase!
190	return None
191
192
193def main(args):
194	try:
195		rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
196	except getopt.GetoptError:
197		usage()
198	
199	if not files:
200		usage()
201	
202	options = Options(rawOptions, len(files))
203	
204	for input in files:
205		tp = guessFileType(input)
206		if tp in ("OTF", "TTF"):
207			extension = ".ttx"
208			if options.listTables:
209				action = ttList
210			else:
211				action = ttDump
212		elif tp == "TTX":
213			extension = ".ttf"
214			action = ttCompile
215		elif tp == "OTX":
216			extension = ".otf"
217			action = ttCompile
218		else:
219			print 'Unknown file type: "%s"' % input
220			continue
221		
222		action(input, extension, options)
223
224
225def waitForKeyPress():
226	"""Force the DOS Prompt window to stay open so the user gets
227	a chance to see what's wrong."""
228	import msvcrt
229	print '(Hit any key to exit)'
230	while not msvcrt.kbhit():
231		pass
232
233
234if __name__ == "__main__":
235	try:
236		main(sys.argv[1:])
237	except KeyboardInterrupt:
238		print "(Cancelled.)"
239	except SystemExit:
240		if sys.platform == "win32":
241			waitForKeyPress()
242		else:
243			raise
244	except:
245		if sys.platform == "win32":
246			import traceback
247			traceback.print_exc()
248			waitForKeyPress()
249		else:
250			raise
251