blob: 56f211a61145a0b6989c21f8b09b08c38513339f [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.
pabs35f419332013-06-22 06:47:34 +000018 -o <outputfile> Specify a file to write the output to.
jvr1c803b62002-09-12 17:33:12 +000019 -v Verbose: more messages will be written to stdout about what
20 is being done.
Dave Crosslandb1585972013-09-04 13:16:39 +010021 -q Quiet: No messages will be written to stdout about what
22 is being done.
jvr823f8cd2006-10-21 14:12:38 +000023 -a allow virtual glyphs ID's on compile or decompile.
jvr1c803b62002-09-12 17:33:12 +000024
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.
Matt Fontaine7baa1362013-08-09 13:25:15 -070043 -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.
jvr1bcc11d2008-03-01 09:42:58 +000055 -e Don't ignore decompilation errors, but show a full traceback
56 and abort.
pabs30a6dea02009-11-08 15:53:24 +000057 -y <number> Select font number for TrueType Collection,
pabs37e91e772009-02-22 08:55:00 +000058 starting from 0.
jvr1c803b62002-09-12 17:33:12 +000059
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.
pabs3ca75e432011-10-30 12:26:09 +000064 -b Don't recalc glyph bounding boxes: use the values in the TTX
jvr1c803b62002-09-12 17:33:12 +000065 file as-is.
66"""
67
68
69import sys
70import os
71import getopt
72import re
73from fontTools.ttLib import TTFont
jvr823f8cd2006-10-21 14:12:38 +000074from fontTools.ttLib.tables.otBase import OTLOffsetOverflowError
75from fontTools.ttLib.tables.otTables import fixLookupOverFlows, fixSubTableOverFlows
jvr45d1f3b2008-03-01 11:34:54 +000076from fontTools.misc.macCreatorType import getMacCreatorAndType
jvr1c803b62002-09-12 17:33:12 +000077from fontTools import version
78
79def usage():
80 print __doc__ % version
81 sys.exit(2)
82
jvr2e838ce2003-08-22 18:50:44 +000083
Behdad Esfahbodeac107f2013-11-01 00:43:06 +000084numberAddedRE = re.compile("#\d+$")
pabs3278d4d82013-06-22 08:16:33 +000085opentypeheaderRE = re.compile('''sfntVersion=['"]OTTO["']''')
jvr1c803b62002-09-12 17:33:12 +000086
87def makeOutputFileName(input, outputDir, extension):
88 dir, file = os.path.split(input)
89 file, ext = os.path.splitext(file)
90 if outputDir:
91 dir = outputDir
92 output = os.path.join(dir, file + extension)
Behdad Esfahbodeac107f2013-11-01 00:43:06 +000093 m = numberAddedRE.search(file)
jvr1c803b62002-09-12 17:33:12 +000094 if m:
95 file = m.group(1)
96 n = 1
97 while os.path.exists(output):
98 output = os.path.join(dir, file + "#" + repr(n) + extension)
99 n = n + 1
100 return output
101
102
103class Options:
104
105 listTables = 0
106 outputDir = None
pabs3fb37a242013-06-22 06:43:01 +0000107 outputFile = None
jvr1c803b62002-09-12 17:33:12 +0000108 verbose = 0
Dave Crosslandb1585972013-09-04 13:16:39 +0100109 quiet = 0
jvr1c803b62002-09-12 17:33:12 +0000110 splitTables = 0
111 disassembleInstructions = 1
112 mergeFile = None
113 recalcBBoxes = 1
jvr823f8cd2006-10-21 14:12:38 +0000114 allowVID = 0
jvr1bcc11d2008-03-01 09:42:58 +0000115 ignoreDecompileErrors = True
Matt Fontaine7baa1362013-08-09 13:25:15 -0700116 bitmapGlyphDataFormat = 'raw'
jvr1bcc11d2008-03-01 09:42:58 +0000117
jvr1c803b62002-09-12 17:33:12 +0000118 def __init__(self, rawOptions, numFiles):
119 self.onlyTables = []
120 self.skipTables = []
pabs37e91e772009-02-22 08:55:00 +0000121 self.fontNumber = -1
jvr1c803b62002-09-12 17:33:12 +0000122 for option, value in rawOptions:
123 # general options
124 if option == "-h":
125 print __doc__ % version
126 sys.exit(0)
127 elif option == "-d":
128 if not os.path.isdir(value):
129 print "The -d option value must be an existing directory"
130 sys.exit(2)
131 self.outputDir = value
pabs3fb37a242013-06-22 06:43:01 +0000132 elif option == "-o":
133 self.outputFile = value
jvr1c803b62002-09-12 17:33:12 +0000134 elif option == "-v":
135 self.verbose = 1
Dave Crosslandb1585972013-09-04 13:16:39 +0100136 elif option == "-q":
137 self.quiet = 1
jvr1c803b62002-09-12 17:33:12 +0000138 # dump options
139 elif option == "-l":
140 self.listTables = 1
141 elif option == "-t":
142 self.onlyTables.append(value)
143 elif option == "-x":
144 self.skipTables.append(value)
145 elif option == "-s":
146 self.splitTables = 1
147 elif option == "-i":
148 self.disassembleInstructions = 0
Matt Fontaine7baa1362013-08-09 13:25:15 -0700149 elif option == "-z":
150 validOptions = ('raw', 'row', 'bitwise', 'extfile')
151 if value not in validOptions:
152 print "-z does not allow %s as a format. Use %s" % (option, validOptions)
153 sys.exit(2)
154 self.bitmapGlyphDataFormat = value
pabs37e91e772009-02-22 08:55:00 +0000155 elif option == "-y":
156 self.fontNumber = int(value)
jvr1c803b62002-09-12 17:33:12 +0000157 # compile options
158 elif option == "-m":
159 self.mergeFile = value
160 elif option == "-b":
161 self.recalcBBoxes = 0
jvr823f8cd2006-10-21 14:12:38 +0000162 elif option == "-a":
163 self.allowVID = 1
jvr1bcc11d2008-03-01 09:42:58 +0000164 elif option == "-e":
165 self.ignoreDecompileErrors = False
jvr1c803b62002-09-12 17:33:12 +0000166 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000167 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000168 sys.exit(2)
169 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000170 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000171 sys.exit(2)
172
173
174def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000175 import string
pabs37e91e772009-02-22 08:55:00 +0000176 ttf = TTFont(input, fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000177 reader = ttf.reader
178 tags = reader.keys()
179 tags.sort()
180 print 'Listing table info for "%s":' % input
181 format = " %4s %10s %7s %7s"
182 print format % ("tag ", " checksum", " length", " offset")
183 print format % ("----", "----------", "-------", "-------")
184 for tag in tags:
185 entry = reader.tables[tag]
jvre0912bb2004-12-24 15:59:35 +0000186 checkSum = long(entry.checkSum)
187 if checkSum < 0:
188 checkSum = checkSum + 0x100000000L
189 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000190 print format % (tag, checksum, entry.length, entry.offset)
191 print
192 ttf.close()
193
194
195def ttDump(input, output, options):
Dave Crosslandb1585972013-09-04 13:16:39 +0100196 if not options.quiet:
197 print 'Dumping "%s" to "%s"...' % (input, output)
jvr1bcc11d2008-03-01 09:42:58 +0000198 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
Dave Crosslandd7efd562013-09-04 14:51:16 +0100199 quiet=options.quiet,
pabs37e91e772009-02-22 08:55:00 +0000200 ignoreDecompileErrors=options.ignoreDecompileErrors,
201 fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000202 ttf.saveXML(output,
Dave Crosslandd7efd562013-09-04 14:51:16 +0100203 quiet=options.quiet,
jvr1c803b62002-09-12 17:33:12 +0000204 tables=options.onlyTables,
205 skipTables=options.skipTables,
206 splitTables=options.splitTables,
Matt Fontaine7baa1362013-08-09 13:25:15 -0700207 disassembleInstructions=options.disassembleInstructions,
208 bitmapGlyphDataFormat=options.bitmapGlyphDataFormat)
jvr1c803b62002-09-12 17:33:12 +0000209 ttf.close()
210
211
212def ttCompile(input, output, options):
Dave Crossland85af40e2013-09-04 13:30:21 +0100213 if not options.quiet:
214 print 'Compiling "%s" to "%s"...' % (input, output)
jvr1c803b62002-09-12 17:33:12 +0000215 ttf = TTFont(options.mergeFile,
216 recalcBBoxes=options.recalcBBoxes,
jvr823f8cd2006-10-21 14:12:38 +0000217 verbose=options.verbose, allowVID=options.allowVID)
Dave Crossland85af40e2013-09-04 13:30:21 +0100218 ttf.importXML(input, quiet=options.quiet)
jvr823f8cd2006-10-21 14:12:38 +0000219 try:
220 ttf.save(output)
221 except OTLOffsetOverflowError, e:
jvr142506b2008-03-09 20:39:38 +0000222 # XXX This shouldn't be here at all, it should be as close to the
223 # OTL code as possible.
jvr823f8cd2006-10-21 14:12:38 +0000224 overflowRecord = e.value
225 print "Attempting to fix OTLOffsetOverflowError", e
226 lastItem = overflowRecord
227 while 1:
228 ok = 0
229 if overflowRecord.itemName == None:
230 ok = fixLookupOverFlows(ttf, overflowRecord)
231 else:
232 ok = fixSubTableOverFlows(ttf, overflowRecord)
233 if not ok:
234 raise
235
236 try:
237 ttf.save(output)
238 break
239 except OTLOffsetOverflowError, e:
240 print "Attempting to fix OTLOffsetOverflowError", e
241 overflowRecord = e.value
242 if overflowRecord == lastItem:
243 raise
jvr1c803b62002-09-12 17:33:12 +0000244
245 if options.verbose:
246 import time
247 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
248
249
250def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000251 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000252 try:
253 f = open(fileName, "rb")
254 except IOError:
255 return None
jvr45d1f3b2008-03-01 11:34:54 +0000256 cr, tp = getMacCreatorAndType(fileName)
257 if tp in ("sfnt", "FFIL"):
258 return "TTF"
259 if ext == ".dfont":
260 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000261 header = f.read(256)
262 head = header[:4]
263 if head == "OTTO":
264 return "OTF"
pabs37e91e772009-02-22 08:55:00 +0000265 elif head == "ttcf":
266 return "TTC"
jvr1c803b62002-09-12 17:33:12 +0000267 elif head in ("\0\1\0\0", "true"):
268 return "TTF"
269 elif head.lower() == "<?xm":
Behdad Esfahbodeac107f2013-11-01 00:43:06 +0000270 if opentypeheaderRE.search(header):
jvr1c803b62002-09-12 17:33:12 +0000271 return "OTX"
272 else:
273 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000274 return None
275
276
277def parseOptions(args):
278 try:
Dave Crossland5ffb91e2013-09-04 14:49:37 +0100279 rawOptions, files = getopt.getopt(args, "ld:o:vqht:x:sim:z:baey:")
jvr1c803b62002-09-12 17:33:12 +0000280 except getopt.GetoptError:
281 usage()
282
283 if not files:
284 usage()
285
286 options = Options(rawOptions, len(files))
287 jobs = []
288
289 for input in files:
290 tp = guessFileType(input)
pabs37e91e772009-02-22 08:55:00 +0000291 if tp in ("OTF", "TTF", "TTC"):
jvr1c803b62002-09-12 17:33:12 +0000292 extension = ".ttx"
293 if options.listTables:
294 action = ttList
295 else:
296 action = ttDump
297 elif tp == "TTX":
298 extension = ".ttf"
299 action = ttCompile
300 elif tp == "OTX":
301 extension = ".otf"
302 action = ttCompile
303 else:
304 print 'Unknown file type: "%s"' % input
305 continue
306
pabs3fb37a242013-06-22 06:43:01 +0000307 if options.outputFile:
308 output = options.outputFile
309 else:
310 output = makeOutputFileName(input, options.outputDir, extension)
jvr1c803b62002-09-12 17:33:12 +0000311 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000312 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000313
314
315def process(jobs, options):
316 for action, input, output in jobs:
317 action(input, output, options)
318
319
320def waitForKeyPress():
321 """Force the DOS Prompt window to stay open so the user gets
322 a chance to see what's wrong."""
323 import msvcrt
324 print '(Hit any key to exit)'
325 while not msvcrt.kbhit():
326 pass
327
328
329def main(args):
330 jobs, options = parseOptions(args)
331 try:
332 process(jobs, options)
333 except KeyboardInterrupt:
334 print "(Cancelled.)"
335 except SystemExit:
336 if sys.platform == "win32":
337 waitForKeyPress()
338 else:
339 raise
340 except:
341 if sys.platform == "win32":
342 import traceback
343 traceback.print_exc()
344 waitForKeyPress()
345 else:
346 raise
347
348
349if __name__ == "__main__":
350 main(sys.argv[1:])