blob: b6cc184a5846128fcfe5acf9bed33150b3d693c5 [file] [log] [blame]
jvr1c803b62002-09-12 17:33:12 +00001"""\
2usage: 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
pabs3ca75e432011-10-30 12:26:09 +000012 never overwritten.
jvr1c803b62002-09-12 17:33:12 +000013
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 -v Verbose: more messages will be written to stdout about what
19 is being done.
jvr823f8cd2006-10-21 14:12:38 +000020 -a allow virtual glyphs ID's on compile or decompile.
jvr1c803b62002-09-12 17:33:12 +000021
22 Dump options:
23 -l List table info: instead of dumping to a TTX file, list some
24 minimal info about each table.
25 -t <table> Specify a table to dump. Multiple -t options
26 are allowed. When no -t option is specified, all tables
27 will be dumped.
28 -x <table> Specify a table to exclude from the dump. Multiple
29 -x options are allowed. -t and -x are mutually exclusive.
30 -s Split tables: save the TTX data into separate TTX files per
31 table and write one small TTX file that contains references
32 to the individual table dumps. This file can be used as
33 input to ttx, as long as the table files are in the
34 same directory.
35 -i Do NOT disassemble TT instructions: when this option is given,
36 all TrueType programs (glyph programs, the font program and the
37 pre-program) will be written to the TTX file as hex data
38 instead of assembly. This saves some time and makes the TTX
39 file smaller.
jvr1bcc11d2008-03-01 09:42:58 +000040 -e Don't ignore decompilation errors, but show a full traceback
41 and abort.
pabs30a6dea02009-11-08 15:53:24 +000042 -y <number> Select font number for TrueType Collection,
pabs37e91e772009-02-22 08:55:00 +000043 starting from 0.
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.
pabs3ca75e432011-10-30 12:26:09 +000049 -b Don't recalc glyph bounding boxes: use the values in the TTX
jvr1c803b62002-09-12 17:33:12 +000050 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 = []
pabs37e91e772009-02-22 08:55:00 +0000102 self.fontNumber = -1
jvr1c803b62002-09-12 17:33:12 +0000103 for option, value in rawOptions:
104 # general options
105 if option == "-h":
106 print __doc__ % version
107 sys.exit(0)
108 elif option == "-d":
109 if not os.path.isdir(value):
110 print "The -d option value must be an existing directory"
111 sys.exit(2)
112 self.outputDir = value
113 elif option == "-v":
114 self.verbose = 1
115 # dump options
116 elif option == "-l":
117 self.listTables = 1
118 elif option == "-t":
119 self.onlyTables.append(value)
120 elif option == "-x":
121 self.skipTables.append(value)
122 elif option == "-s":
123 self.splitTables = 1
124 elif option == "-i":
125 self.disassembleInstructions = 0
pabs37e91e772009-02-22 08:55:00 +0000126 elif option == "-y":
127 self.fontNumber = int(value)
jvr1c803b62002-09-12 17:33:12 +0000128 # compile options
129 elif option == "-m":
130 self.mergeFile = value
131 elif option == "-b":
132 self.recalcBBoxes = 0
jvr823f8cd2006-10-21 14:12:38 +0000133 elif option == "-a":
134 self.allowVID = 1
jvr1bcc11d2008-03-01 09:42:58 +0000135 elif option == "-e":
136 self.ignoreDecompileErrors = False
jvr1c803b62002-09-12 17:33:12 +0000137 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000138 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000139 sys.exit(2)
140 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000141 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000142 sys.exit(2)
143
144
145def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000146 import string
pabs37e91e772009-02-22 08:55:00 +0000147 ttf = TTFont(input, fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000148 reader = ttf.reader
149 tags = reader.keys()
150 tags.sort()
151 print 'Listing table info for "%s":' % input
152 format = " %4s %10s %7s %7s"
153 print format % ("tag ", " checksum", " length", " offset")
154 print format % ("----", "----------", "-------", "-------")
155 for tag in tags:
156 entry = reader.tables[tag]
jvre0912bb2004-12-24 15:59:35 +0000157 checkSum = long(entry.checkSum)
158 if checkSum < 0:
159 checkSum = checkSum + 0x100000000L
160 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000161 print format % (tag, checksum, entry.length, entry.offset)
162 print
163 ttf.close()
164
165
166def ttDump(input, output, options):
167 print 'Dumping "%s" to "%s"...' % (input, output)
jvr1bcc11d2008-03-01 09:42:58 +0000168 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
pabs37e91e772009-02-22 08:55:00 +0000169 ignoreDecompileErrors=options.ignoreDecompileErrors,
170 fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000171 ttf.saveXML(output,
172 tables=options.onlyTables,
173 skipTables=options.skipTables,
174 splitTables=options.splitTables,
175 disassembleInstructions=options.disassembleInstructions)
176 ttf.close()
177
178
179def ttCompile(input, output, options):
180 print 'Compiling "%s" to "%s"...' % (input, output)
181 ttf = TTFont(options.mergeFile,
182 recalcBBoxes=options.recalcBBoxes,
jvr823f8cd2006-10-21 14:12:38 +0000183 verbose=options.verbose, allowVID=options.allowVID)
jvr1c803b62002-09-12 17:33:12 +0000184 ttf.importXML(input)
jvr823f8cd2006-10-21 14:12:38 +0000185 try:
186 ttf.save(output)
187 except OTLOffsetOverflowError, e:
jvr142506b2008-03-09 20:39:38 +0000188 # XXX This shouldn't be here at all, it should be as close to the
189 # OTL code as possible.
jvr823f8cd2006-10-21 14:12:38 +0000190 overflowRecord = e.value
191 print "Attempting to fix OTLOffsetOverflowError", e
192 lastItem = overflowRecord
193 while 1:
194 ok = 0
195 if overflowRecord.itemName == None:
196 ok = fixLookupOverFlows(ttf, overflowRecord)
197 else:
198 ok = fixSubTableOverFlows(ttf, overflowRecord)
199 if not ok:
200 raise
201
202 try:
203 ttf.save(output)
204 break
205 except OTLOffsetOverflowError, e:
206 print "Attempting to fix OTLOffsetOverflowError", e
207 overflowRecord = e.value
208 if overflowRecord == lastItem:
209 raise
jvr1c803b62002-09-12 17:33:12 +0000210
211 if options.verbose:
212 import time
213 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
214
215
216def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000217 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000218 try:
219 f = open(fileName, "rb")
220 except IOError:
221 return None
jvr45d1f3b2008-03-01 11:34:54 +0000222 cr, tp = getMacCreatorAndType(fileName)
223 if tp in ("sfnt", "FFIL"):
224 return "TTF"
225 if ext == ".dfont":
226 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000227 header = f.read(256)
228 head = header[:4]
229 if head == "OTTO":
230 return "OTF"
pabs37e91e772009-02-22 08:55:00 +0000231 elif head == "ttcf":
232 return "TTC"
jvr1c803b62002-09-12 17:33:12 +0000233 elif head in ("\0\1\0\0", "true"):
234 return "TTF"
235 elif head.lower() == "<?xm":
236 if header.find('sfntVersion="OTTO"') > 0:
237 return "OTX"
238 else:
239 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000240 return None
241
242
243def parseOptions(args):
244 try:
pabs37e91e772009-02-22 08:55:00 +0000245 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:baey:")
jvr1c803b62002-09-12 17:33:12 +0000246 except getopt.GetoptError:
247 usage()
248
249 if not files:
250 usage()
251
252 options = Options(rawOptions, len(files))
253 jobs = []
254
255 for input in files:
256 tp = guessFileType(input)
pabs37e91e772009-02-22 08:55:00 +0000257 if tp in ("OTF", "TTF", "TTC"):
jvr1c803b62002-09-12 17:33:12 +0000258 extension = ".ttx"
259 if options.listTables:
260 action = ttList
261 else:
262 action = ttDump
263 elif tp == "TTX":
264 extension = ".ttf"
265 action = ttCompile
266 elif tp == "OTX":
267 extension = ".otf"
268 action = ttCompile
269 else:
270 print 'Unknown file type: "%s"' % input
271 continue
272
273 output = makeOutputFileName(input, options.outputDir, extension)
274 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000275 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000276
277
278def process(jobs, options):
279 for action, input, output in jobs:
280 action(input, output, options)
281
282
283def waitForKeyPress():
284 """Force the DOS Prompt window to stay open so the user gets
285 a chance to see what's wrong."""
286 import msvcrt
287 print '(Hit any key to exit)'
288 while not msvcrt.kbhit():
289 pass
290
291
292def main(args):
293 jobs, options = parseOptions(args)
294 try:
295 process(jobs, options)
296 except KeyboardInterrupt:
297 print "(Cancelled.)"
298 except SystemExit:
299 if sys.platform == "win32":
300 waitForKeyPress()
301 else:
302 raise
303 except:
304 if sys.platform == "win32":
305 import traceback
306 traceback.print_exc()
307 waitForKeyPress()
308 else:
309 raise
310
311
312if __name__ == "__main__":
313 main(sys.argv[1:])