blob: 695e8fda5d9c59719eb8b795cc8247200eb3a873 [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.
pabs3fb37a242013-06-22 06:43:01 +000018 -o <outputfile> Specify a file to write the output to, use - for
19 standard output.
jvr1c803b62002-09-12 17:33:12 +000020 -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.
pabs30a6dea02009-11-08 15:53:24 +000044 -y <number> Select font number for TrueType Collection,
pabs37e91e772009-02-22 08:55:00 +000045 starting from 0.
jvr1c803b62002-09-12 17:33:12 +000046
47 Compile options:
48 -m Merge with TrueType-input-file: specify a TrueType or OpenType
49 font file to be merged with the TTX file. This option is only
50 valid when at most one TTX file is specified.
pabs3ca75e432011-10-30 12:26:09 +000051 -b Don't recalc glyph bounding boxes: use the values in the TTX
jvr1c803b62002-09-12 17:33:12 +000052 file as-is.
53"""
54
55
56import sys
57import os
58import getopt
59import re
60from fontTools.ttLib import TTFont
jvr823f8cd2006-10-21 14:12:38 +000061from fontTools.ttLib.tables.otBase import OTLOffsetOverflowError
62from fontTools.ttLib.tables.otTables import fixLookupOverFlows, fixSubTableOverFlows
jvr45d1f3b2008-03-01 11:34:54 +000063from fontTools.misc.macCreatorType import getMacCreatorAndType
jvr1c803b62002-09-12 17:33:12 +000064from fontTools import version
65
66def usage():
67 print __doc__ % version
68 sys.exit(2)
69
jvr2e838ce2003-08-22 18:50:44 +000070
jvr1c803b62002-09-12 17:33:12 +000071numberAddedRE = re.compile("(.*)#\d+$")
72
73def makeOutputFileName(input, outputDir, extension):
74 dir, file = os.path.split(input)
75 file, ext = os.path.splitext(file)
76 if outputDir:
77 dir = outputDir
78 output = os.path.join(dir, file + extension)
79 m = numberAddedRE.match(file)
80 if m:
81 file = m.group(1)
82 n = 1
83 while os.path.exists(output):
84 output = os.path.join(dir, file + "#" + repr(n) + extension)
85 n = n + 1
86 return output
87
88
89class Options:
90
91 listTables = 0
92 outputDir = None
pabs3fb37a242013-06-22 06:43:01 +000093 outputFile = None
jvr1c803b62002-09-12 17:33:12 +000094 verbose = 0
95 splitTables = 0
96 disassembleInstructions = 1
97 mergeFile = None
98 recalcBBoxes = 1
jvr823f8cd2006-10-21 14:12:38 +000099 allowVID = 0
jvr1bcc11d2008-03-01 09:42:58 +0000100 ignoreDecompileErrors = True
101
jvr1c803b62002-09-12 17:33:12 +0000102 def __init__(self, rawOptions, numFiles):
103 self.onlyTables = []
104 self.skipTables = []
pabs37e91e772009-02-22 08:55:00 +0000105 self.fontNumber = -1
jvr1c803b62002-09-12 17:33:12 +0000106 for option, value in rawOptions:
107 # general options
108 if option == "-h":
109 print __doc__ % version
110 sys.exit(0)
111 elif option == "-d":
112 if not os.path.isdir(value):
113 print "The -d option value must be an existing directory"
114 sys.exit(2)
115 self.outputDir = value
pabs3fb37a242013-06-22 06:43:01 +0000116 elif option == "-o":
117 self.outputFile = value
jvr1c803b62002-09-12 17:33:12 +0000118 elif option == "-v":
119 self.verbose = 1
120 # dump options
121 elif option == "-l":
122 self.listTables = 1
123 elif option == "-t":
124 self.onlyTables.append(value)
125 elif option == "-x":
126 self.skipTables.append(value)
127 elif option == "-s":
128 self.splitTables = 1
129 elif option == "-i":
130 self.disassembleInstructions = 0
pabs37e91e772009-02-22 08:55:00 +0000131 elif option == "-y":
132 self.fontNumber = int(value)
jvr1c803b62002-09-12 17:33:12 +0000133 # compile options
134 elif option == "-m":
135 self.mergeFile = value
136 elif option == "-b":
137 self.recalcBBoxes = 0
jvr823f8cd2006-10-21 14:12:38 +0000138 elif option == "-a":
139 self.allowVID = 1
jvr1bcc11d2008-03-01 09:42:58 +0000140 elif option == "-e":
141 self.ignoreDecompileErrors = False
jvr1c803b62002-09-12 17:33:12 +0000142 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000143 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000144 sys.exit(2)
145 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000146 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000147 sys.exit(2)
148
149
150def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000151 import string
pabs37e91e772009-02-22 08:55:00 +0000152 ttf = TTFont(input, fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000153 reader = ttf.reader
154 tags = reader.keys()
155 tags.sort()
156 print 'Listing table info for "%s":' % input
157 format = " %4s %10s %7s %7s"
158 print format % ("tag ", " checksum", " length", " offset")
159 print format % ("----", "----------", "-------", "-------")
160 for tag in tags:
161 entry = reader.tables[tag]
jvre0912bb2004-12-24 15:59:35 +0000162 checkSum = long(entry.checkSum)
163 if checkSum < 0:
164 checkSum = checkSum + 0x100000000L
165 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000166 print format % (tag, checksum, entry.length, entry.offset)
167 print
168 ttf.close()
169
170
171def ttDump(input, output, options):
172 print 'Dumping "%s" to "%s"...' % (input, output)
jvr1bcc11d2008-03-01 09:42:58 +0000173 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
pabs37e91e772009-02-22 08:55:00 +0000174 ignoreDecompileErrors=options.ignoreDecompileErrors,
175 fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000176 ttf.saveXML(output,
177 tables=options.onlyTables,
178 skipTables=options.skipTables,
179 splitTables=options.splitTables,
180 disassembleInstructions=options.disassembleInstructions)
181 ttf.close()
182
183
184def ttCompile(input, output, options):
185 print 'Compiling "%s" to "%s"...' % (input, output)
186 ttf = TTFont(options.mergeFile,
187 recalcBBoxes=options.recalcBBoxes,
jvr823f8cd2006-10-21 14:12:38 +0000188 verbose=options.verbose, allowVID=options.allowVID)
jvr1c803b62002-09-12 17:33:12 +0000189 ttf.importXML(input)
jvr823f8cd2006-10-21 14:12:38 +0000190 try:
191 ttf.save(output)
192 except OTLOffsetOverflowError, e:
jvr142506b2008-03-09 20:39:38 +0000193 # XXX This shouldn't be here at all, it should be as close to the
194 # OTL code as possible.
jvr823f8cd2006-10-21 14:12:38 +0000195 overflowRecord = e.value
196 print "Attempting to fix OTLOffsetOverflowError", e
197 lastItem = overflowRecord
198 while 1:
199 ok = 0
200 if overflowRecord.itemName == None:
201 ok = fixLookupOverFlows(ttf, overflowRecord)
202 else:
203 ok = fixSubTableOverFlows(ttf, overflowRecord)
204 if not ok:
205 raise
206
207 try:
208 ttf.save(output)
209 break
210 except OTLOffsetOverflowError, e:
211 print "Attempting to fix OTLOffsetOverflowError", e
212 overflowRecord = e.value
213 if overflowRecord == lastItem:
214 raise
jvr1c803b62002-09-12 17:33:12 +0000215
216 if options.verbose:
217 import time
218 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
219
220
221def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000222 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000223 try:
224 f = open(fileName, "rb")
225 except IOError:
226 return None
jvr45d1f3b2008-03-01 11:34:54 +0000227 cr, tp = getMacCreatorAndType(fileName)
228 if tp in ("sfnt", "FFIL"):
229 return "TTF"
230 if ext == ".dfont":
231 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000232 header = f.read(256)
233 head = header[:4]
234 if head == "OTTO":
235 return "OTF"
pabs37e91e772009-02-22 08:55:00 +0000236 elif head == "ttcf":
237 return "TTC"
jvr1c803b62002-09-12 17:33:12 +0000238 elif head in ("\0\1\0\0", "true"):
239 return "TTF"
240 elif head.lower() == "<?xm":
241 if header.find('sfntVersion="OTTO"') > 0:
242 return "OTX"
243 else:
244 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000245 return None
246
247
248def parseOptions(args):
249 try:
pabs3fb37a242013-06-22 06:43:01 +0000250 rawOptions, files = getopt.getopt(args, "ld:o:vht:x:sim:baey:")
jvr1c803b62002-09-12 17:33:12 +0000251 except getopt.GetoptError:
252 usage()
253
254 if not files:
255 usage()
256
257 options = Options(rawOptions, len(files))
258 jobs = []
259
260 for input in files:
261 tp = guessFileType(input)
pabs37e91e772009-02-22 08:55:00 +0000262 if tp in ("OTF", "TTF", "TTC"):
jvr1c803b62002-09-12 17:33:12 +0000263 extension = ".ttx"
264 if options.listTables:
265 action = ttList
266 else:
267 action = ttDump
268 elif tp == "TTX":
269 extension = ".ttf"
270 action = ttCompile
271 elif tp == "OTX":
272 extension = ".otf"
273 action = ttCompile
274 else:
275 print 'Unknown file type: "%s"' % input
276 continue
277
pabs3fb37a242013-06-22 06:43:01 +0000278 if options.outputFile:
279 output = options.outputFile
280 else:
281 output = makeOutputFileName(input, options.outputDir, extension)
jvr1c803b62002-09-12 17:33:12 +0000282 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000283 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000284
285
286def process(jobs, options):
287 for action, input, output in jobs:
288 action(input, output, options)
289
290
291def waitForKeyPress():
292 """Force the DOS Prompt window to stay open so the user gets
293 a chance to see what's wrong."""
294 import msvcrt
295 print '(Hit any key to exit)'
296 while not msvcrt.kbhit():
297 pass
298
299
300def main(args):
301 jobs, options = parseOptions(args)
302 try:
303 process(jobs, options)
304 except KeyboardInterrupt:
305 print "(Cancelled.)"
306 except SystemExit:
307 if sys.platform == "win32":
308 waitForKeyPress()
309 else:
310 raise
311 except:
312 if sys.platform == "win32":
313 import traceback
314 traceback.print_exc()
315 waitForKeyPress()
316 else:
317 raise
318
319
320if __name__ == "__main__":
321 main(sys.argv[1:])