blob: 411687780c7e3a33345e221830180554004bb498 [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:
184 overflowRecord = e.value
185 print "Attempting to fix OTLOffsetOverflowError", e
186 lastItem = overflowRecord
187 while 1:
188 ok = 0
189 if overflowRecord.itemName == None:
190 ok = fixLookupOverFlows(ttf, overflowRecord)
191 else:
192 ok = fixSubTableOverFlows(ttf, overflowRecord)
193 if not ok:
194 raise
195
196 try:
197 ttf.save(output)
198 break
199 except OTLOffsetOverflowError, e:
200 print "Attempting to fix OTLOffsetOverflowError", e
201 overflowRecord = e.value
202 if overflowRecord == lastItem:
203 raise
jvr1c803b62002-09-12 17:33:12 +0000204
205 if options.verbose:
206 import time
207 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
208
209
210def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000211 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000212 try:
213 f = open(fileName, "rb")
214 except IOError:
215 return None
jvr45d1f3b2008-03-01 11:34:54 +0000216 cr, tp = getMacCreatorAndType(fileName)
217 if tp in ("sfnt", "FFIL"):
218 return "TTF"
219 if ext == ".dfont":
220 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000221 header = f.read(256)
222 head = header[:4]
223 if head == "OTTO":
224 return "OTF"
225 elif head in ("\0\1\0\0", "true"):
226 return "TTF"
227 elif head.lower() == "<?xm":
228 if header.find('sfntVersion="OTTO"') > 0:
229 return "OTX"
230 else:
231 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000232 return None
233
234
235def parseOptions(args):
236 try:
jvr1bcc11d2008-03-01 09:42:58 +0000237 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:bae")
jvr1c803b62002-09-12 17:33:12 +0000238 except getopt.GetoptError:
239 usage()
240
241 if not files:
242 usage()
243
244 options = Options(rawOptions, len(files))
245 jobs = []
246
247 for input in files:
248 tp = guessFileType(input)
249 if tp in ("OTF", "TTF"):
250 extension = ".ttx"
251 if options.listTables:
252 action = ttList
253 else:
254 action = ttDump
255 elif tp == "TTX":
256 extension = ".ttf"
257 action = ttCompile
258 elif tp == "OTX":
259 extension = ".otf"
260 action = ttCompile
261 else:
262 print 'Unknown file type: "%s"' % input
263 continue
264
265 output = makeOutputFileName(input, options.outputDir, extension)
266 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000267 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000268
269
270def process(jobs, options):
271 for action, input, output in jobs:
272 action(input, output, options)
273
274
275def waitForKeyPress():
276 """Force the DOS Prompt window to stay open so the user gets
277 a chance to see what's wrong."""
278 import msvcrt
279 print '(Hit any key to exit)'
280 while not msvcrt.kbhit():
281 pass
282
283
284def main(args):
285 jobs, options = parseOptions(args)
286 try:
287 process(jobs, options)
288 except KeyboardInterrupt:
289 print "(Cancelled.)"
290 except SystemExit:
291 if sys.platform == "win32":
292 waitForKeyPress()
293 else:
294 raise
295 except:
296 if sys.platform == "win32":
297 import traceback
298 traceback.print_exc()
299 waitForKeyPress()
300 else:
301 raise
302
303
304if __name__ == "__main__":
305 main(sys.argv[1:])