blob: 2a4eb4ae862d6cd3b20957d61e8a883bdd08ece5 [file] [log] [blame]
jvr1c803b62002-09-12 17:33:12 +00001#! /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.
jvr823f8cd2006-10-21 14:12:38 +000022 -a allow virtual glyphs ID's on compile or decompile.
jvr1c803b62002-09-12 17:33:12 +000023
24 Dump options:
25 -l List table info: instead of dumping to a TTX file, list some
26 minimal info about each table.
27 -t <table> Specify a table to dump. Multiple -t options
28 are allowed. When no -t option is specified, all tables
29 will be dumped.
30 -x <table> Specify a table to exclude from the dump. Multiple
31 -x options are allowed. -t and -x are mutually exclusive.
32 -s Split tables: save the TTX data into separate TTX files per
33 table and write one small TTX file that contains references
34 to the individual table dumps. This file can be used as
35 input to ttx, as long as the table files are in the
36 same directory.
37 -i Do NOT disassemble TT instructions: when this option is given,
38 all TrueType programs (glyph programs, the font program and the
39 pre-program) will be written to the TTX file as hex data
40 instead of assembly. This saves some time and makes the TTX
41 file smaller.
jvr1bcc11d2008-03-01 09:42:58 +000042 -e Don't ignore decompilation errors, but show a full traceback
43 and abort.
jvr1c803b62002-09-12 17:33:12 +000044
45 Compile options:
46 -m Merge with TrueType-input-file: specify a TrueType or OpenType
47 font file to be merged with the TTX file. This option is only
48 valid when at most one TTX file is specified.
49 -b Don't recalc glyph boundig boxes: use the values in the TTX
50 file as-is.
51"""
52
53
54import sys
55import os
56import getopt
57import re
58from fontTools.ttLib import TTFont
jvr823f8cd2006-10-21 14:12:38 +000059from fontTools.ttLib.tables.otBase import OTLOffsetOverflowError
60from fontTools.ttLib.tables.otTables import fixLookupOverFlows, fixSubTableOverFlows
jvr45d1f3b2008-03-01 11:34:54 +000061from fontTools.misc.macCreatorType import getMacCreatorAndType
jvr1c803b62002-09-12 17:33:12 +000062from fontTools import version
63
64def usage():
65 print __doc__ % version
66 sys.exit(2)
67
jvr2e838ce2003-08-22 18:50:44 +000068
jvr1c803b62002-09-12 17:33:12 +000069numberAddedRE = 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
jvr823f8cd2006-10-21 14:12:38 +000096 allowVID = 0
jvr1bcc11d2008-03-01 09:42:58 +000097 ignoreDecompileErrors = True
98
jvr1c803b62002-09-12 17:33:12 +000099 def __init__(self, rawOptions, numFiles):
100 self.onlyTables = []
101 self.skipTables = []
102 for option, value in rawOptions:
103 # general options
104 if option == "-h":
105 print __doc__ % version
106 sys.exit(0)
107 elif option == "-d":
108 if not os.path.isdir(value):
109 print "The -d option value must be an existing directory"
110 sys.exit(2)
111 self.outputDir = value
112 elif option == "-v":
113 self.verbose = 1
114 # dump options
115 elif option == "-l":
116 self.listTables = 1
117 elif option == "-t":
118 self.onlyTables.append(value)
119 elif option == "-x":
120 self.skipTables.append(value)
121 elif option == "-s":
122 self.splitTables = 1
123 elif option == "-i":
124 self.disassembleInstructions = 0
125 # compile options
126 elif option == "-m":
127 self.mergeFile = value
128 elif option == "-b":
129 self.recalcBBoxes = 0
jvr823f8cd2006-10-21 14:12:38 +0000130 elif option == "-a":
131 self.allowVID = 1
jvr1bcc11d2008-03-01 09:42:58 +0000132 elif option == "-e":
133 self.ignoreDecompileErrors = False
jvr1c803b62002-09-12 17:33:12 +0000134 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000135 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000136 sys.exit(2)
137 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000138 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000139 sys.exit(2)
140
141
142def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000143 import string
jvr1c803b62002-09-12 17:33:12 +0000144 ttf = TTFont(input)
145 reader = ttf.reader
146 tags = reader.keys()
147 tags.sort()
148 print 'Listing table info for "%s":' % input
149 format = " %4s %10s %7s %7s"
150 print format % ("tag ", " checksum", " length", " offset")
151 print format % ("----", "----------", "-------", "-------")
152 for tag in tags:
153 entry = reader.tables[tag]
jvre0912bb2004-12-24 15:59:35 +0000154 checkSum = long(entry.checkSum)
155 if checkSum < 0:
156 checkSum = checkSum + 0x100000000L
157 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000158 print format % (tag, checksum, entry.length, entry.offset)
159 print
160 ttf.close()
161
162
163def ttDump(input, output, options):
164 print 'Dumping "%s" to "%s"...' % (input, output)
jvr1bcc11d2008-03-01 09:42:58 +0000165 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
166 ignoreDecompileErrors=options.ignoreDecompileErrors)
jvr1c803b62002-09-12 17:33:12 +0000167 ttf.saveXML(output,
168 tables=options.onlyTables,
169 skipTables=options.skipTables,
170 splitTables=options.splitTables,
171 disassembleInstructions=options.disassembleInstructions)
172 ttf.close()
173
174
175def ttCompile(input, output, options):
176 print 'Compiling "%s" to "%s"...' % (input, output)
177 ttf = TTFont(options.mergeFile,
178 recalcBBoxes=options.recalcBBoxes,
jvr823f8cd2006-10-21 14:12:38 +0000179 verbose=options.verbose, allowVID=options.allowVID)
jvr1c803b62002-09-12 17:33:12 +0000180 ttf.importXML(input)
jvr823f8cd2006-10-21 14:12:38 +0000181 try:
182 ttf.save(output)
183 except OTLOffsetOverflowError, e:
jvr142506b2008-03-09 20:39:38 +0000184 # XXX This shouldn't be here at all, it should be as close to the
185 # OTL code as possible.
jvr823f8cd2006-10-21 14:12:38 +0000186 overflowRecord = e.value
187 print "Attempting to fix OTLOffsetOverflowError", e
188 lastItem = overflowRecord
189 while 1:
190 ok = 0
191 if overflowRecord.itemName == None:
192 ok = fixLookupOverFlows(ttf, overflowRecord)
193 else:
194 ok = fixSubTableOverFlows(ttf, overflowRecord)
195 if not ok:
196 raise
197
198 try:
199 ttf.save(output)
200 break
201 except OTLOffsetOverflowError, e:
202 print "Attempting to fix OTLOffsetOverflowError", e
203 overflowRecord = e.value
204 if overflowRecord == lastItem:
205 raise
jvr1c803b62002-09-12 17:33:12 +0000206
207 if options.verbose:
208 import time
209 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
210
211
212def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000213 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000214 try:
215 f = open(fileName, "rb")
216 except IOError:
217 return None
jvr45d1f3b2008-03-01 11:34:54 +0000218 cr, tp = getMacCreatorAndType(fileName)
219 if tp in ("sfnt", "FFIL"):
220 return "TTF"
221 if ext == ".dfont":
222 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000223 header = f.read(256)
224 head = header[:4]
225 if head == "OTTO":
226 return "OTF"
227 elif head in ("\0\1\0\0", "true"):
228 return "TTF"
229 elif head.lower() == "<?xm":
230 if header.find('sfntVersion="OTTO"') > 0:
231 return "OTX"
232 else:
233 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000234 return None
235
236
237def parseOptions(args):
238 try:
jvr1bcc11d2008-03-01 09:42:58 +0000239 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:bae")
jvr1c803b62002-09-12 17:33:12 +0000240 except getopt.GetoptError:
241 usage()
242
243 if not files:
244 usage()
245
246 options = Options(rawOptions, len(files))
247 jobs = []
248
249 for input in files:
250 tp = guessFileType(input)
251 if tp in ("OTF", "TTF"):
252 extension = ".ttx"
253 if options.listTables:
254 action = ttList
255 else:
256 action = ttDump
257 elif tp == "TTX":
258 extension = ".ttf"
259 action = ttCompile
260 elif tp == "OTX":
261 extension = ".otf"
262 action = ttCompile
263 else:
264 print 'Unknown file type: "%s"' % input
265 continue
266
267 output = makeOutputFileName(input, options.outputDir, extension)
268 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000269 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000270
271
272def process(jobs, options):
273 for action, input, output in jobs:
274 action(input, output, options)
275
276
277def waitForKeyPress():
278 """Force the DOS Prompt window to stay open so the user gets
279 a chance to see what's wrong."""
280 import msvcrt
281 print '(Hit any key to exit)'
282 while not msvcrt.kbhit():
283 pass
284
285
286def main(args):
287 jobs, options = parseOptions(args)
288 try:
289 process(jobs, options)
290 except KeyboardInterrupt:
291 print "(Cancelled.)"
292 except SystemExit:
293 if sys.platform == "win32":
294 waitForKeyPress()
295 else:
296 raise
297 except:
298 if sys.platform == "win32":
299 import traceback
300 traceback.print_exc()
301 waitForKeyPress()
302 else:
303 raise
304
305
306if __name__ == "__main__":
307 main(sys.argv[1:])