blob: 0968a29b0d4253f134826823e8c10bc97bb6f64e [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.
jvr823f8cd2006-10-21 14:12:38 +000021 -a allow virtual glyphs ID's on compile or decompile.
jvr1c803b62002-09-12 17:33:12 +000022
23 Dump options:
24 -l List table info: instead of dumping to a TTX file, list some
25 minimal info about each table.
26 -t <table> Specify a table to dump. Multiple -t options
27 are allowed. When no -t option is specified, all tables
28 will be dumped.
29 -x <table> Specify a table to exclude from the dump. Multiple
30 -x options are allowed. -t and -x are mutually exclusive.
31 -s Split tables: save the TTX data into separate TTX files per
32 table and write one small TTX file that contains references
33 to the individual table dumps. This file can be used as
34 input to ttx, as long as the table files are in the
35 same directory.
36 -i Do NOT disassemble TT instructions: when this option is given,
37 all TrueType programs (glyph programs, the font program and the
38 pre-program) will be written to the TTX file as hex data
39 instead of assembly. This saves some time and makes the TTX
40 file smaller.
jvr1bcc11d2008-03-01 09:42:58 +000041 -e Don't ignore decompilation errors, but show a full traceback
42 and abort.
pabs30a6dea02009-11-08 15:53:24 +000043 -y <number> Select font number for TrueType Collection,
pabs37e91e772009-02-22 08:55:00 +000044 starting from 0.
jvr1c803b62002-09-12 17:33:12 +000045
46 Compile options:
47 -m Merge with TrueType-input-file: specify a TrueType or OpenType
48 font file to be merged with the TTX file. This option is only
49 valid when at most one TTX file is specified.
pabs3ca75e432011-10-30 12:26:09 +000050 -b Don't recalc glyph bounding boxes: use the values in the TTX
jvr1c803b62002-09-12 17:33:12 +000051 file as-is.
52"""
53
54
55import sys
56import os
57import getopt
58import re
59from fontTools.ttLib import TTFont
jvr823f8cd2006-10-21 14:12:38 +000060from fontTools.ttLib.tables.otBase import OTLOffsetOverflowError
61from fontTools.ttLib.tables.otTables import fixLookupOverFlows, fixSubTableOverFlows
jvr45d1f3b2008-03-01 11:34:54 +000062from fontTools.misc.macCreatorType import getMacCreatorAndType
jvr1c803b62002-09-12 17:33:12 +000063from fontTools import version
64
65def usage():
66 print __doc__ % version
67 sys.exit(2)
68
jvr2e838ce2003-08-22 18:50:44 +000069
jvr1c803b62002-09-12 17:33:12 +000070numberAddedRE = re.compile("(.*)#\d+$")
71
72def makeOutputFileName(input, outputDir, extension):
73 dir, file = os.path.split(input)
74 file, ext = os.path.splitext(file)
75 if outputDir:
76 dir = outputDir
77 output = os.path.join(dir, file + extension)
78 m = numberAddedRE.match(file)
79 if m:
80 file = m.group(1)
81 n = 1
82 while os.path.exists(output):
83 output = os.path.join(dir, file + "#" + repr(n) + extension)
84 n = n + 1
85 return output
86
87
88class Options:
89
90 listTables = 0
91 outputDir = None
pabs3fb37a242013-06-22 06:43:01 +000092 outputFile = None
jvr1c803b62002-09-12 17:33:12 +000093 verbose = 0
94 splitTables = 0
95 disassembleInstructions = 1
96 mergeFile = None
97 recalcBBoxes = 1
jvr823f8cd2006-10-21 14:12:38 +000098 allowVID = 0
jvr1bcc11d2008-03-01 09:42:58 +000099 ignoreDecompileErrors = True
100
jvr1c803b62002-09-12 17:33:12 +0000101 def __init__(self, rawOptions, numFiles):
102 self.onlyTables = []
103 self.skipTables = []
pabs37e91e772009-02-22 08:55:00 +0000104 self.fontNumber = -1
jvr1c803b62002-09-12 17:33:12 +0000105 for option, value in rawOptions:
106 # general options
107 if option == "-h":
108 print __doc__ % version
109 sys.exit(0)
110 elif option == "-d":
111 if not os.path.isdir(value):
112 print "The -d option value must be an existing directory"
113 sys.exit(2)
114 self.outputDir = value
pabs3fb37a242013-06-22 06:43:01 +0000115 elif option == "-o":
116 self.outputFile = value
jvr1c803b62002-09-12 17:33:12 +0000117 elif option == "-v":
118 self.verbose = 1
119 # dump options
120 elif option == "-l":
121 self.listTables = 1
122 elif option == "-t":
123 self.onlyTables.append(value)
124 elif option == "-x":
125 self.skipTables.append(value)
126 elif option == "-s":
127 self.splitTables = 1
128 elif option == "-i":
129 self.disassembleInstructions = 0
pabs37e91e772009-02-22 08:55:00 +0000130 elif option == "-y":
131 self.fontNumber = int(value)
jvr1c803b62002-09-12 17:33:12 +0000132 # compile options
133 elif option == "-m":
134 self.mergeFile = value
135 elif option == "-b":
136 self.recalcBBoxes = 0
jvr823f8cd2006-10-21 14:12:38 +0000137 elif option == "-a":
138 self.allowVID = 1
jvr1bcc11d2008-03-01 09:42:58 +0000139 elif option == "-e":
140 self.ignoreDecompileErrors = False
jvr1c803b62002-09-12 17:33:12 +0000141 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000142 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000143 sys.exit(2)
144 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000145 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000146 sys.exit(2)
147
148
149def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000150 import string
pabs37e91e772009-02-22 08:55:00 +0000151 ttf = TTFont(input, fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000152 reader = ttf.reader
153 tags = reader.keys()
154 tags.sort()
155 print 'Listing table info for "%s":' % input
156 format = " %4s %10s %7s %7s"
157 print format % ("tag ", " checksum", " length", " offset")
158 print format % ("----", "----------", "-------", "-------")
159 for tag in tags:
160 entry = reader.tables[tag]
jvre0912bb2004-12-24 15:59:35 +0000161 checkSum = long(entry.checkSum)
162 if checkSum < 0:
163 checkSum = checkSum + 0x100000000L
164 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000165 print format % (tag, checksum, entry.length, entry.offset)
166 print
167 ttf.close()
168
169
170def ttDump(input, output, options):
171 print 'Dumping "%s" to "%s"...' % (input, output)
jvr1bcc11d2008-03-01 09:42:58 +0000172 ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
pabs37e91e772009-02-22 08:55:00 +0000173 ignoreDecompileErrors=options.ignoreDecompileErrors,
174 fontNumber=options.fontNumber)
jvr1c803b62002-09-12 17:33:12 +0000175 ttf.saveXML(output,
176 tables=options.onlyTables,
177 skipTables=options.skipTables,
178 splitTables=options.splitTables,
179 disassembleInstructions=options.disassembleInstructions)
180 ttf.close()
181
182
183def ttCompile(input, output, options):
184 print 'Compiling "%s" to "%s"...' % (input, output)
185 ttf = TTFont(options.mergeFile,
186 recalcBBoxes=options.recalcBBoxes,
jvr823f8cd2006-10-21 14:12:38 +0000187 verbose=options.verbose, allowVID=options.allowVID)
jvr1c803b62002-09-12 17:33:12 +0000188 ttf.importXML(input)
jvr823f8cd2006-10-21 14:12:38 +0000189 try:
190 ttf.save(output)
191 except OTLOffsetOverflowError, e:
jvr142506b2008-03-09 20:39:38 +0000192 # XXX This shouldn't be here at all, it should be as close to the
193 # OTL code as possible.
jvr823f8cd2006-10-21 14:12:38 +0000194 overflowRecord = e.value
195 print "Attempting to fix OTLOffsetOverflowError", e
196 lastItem = overflowRecord
197 while 1:
198 ok = 0
199 if overflowRecord.itemName == None:
200 ok = fixLookupOverFlows(ttf, overflowRecord)
201 else:
202 ok = fixSubTableOverFlows(ttf, overflowRecord)
203 if not ok:
204 raise
205
206 try:
207 ttf.save(output)
208 break
209 except OTLOffsetOverflowError, e:
210 print "Attempting to fix OTLOffsetOverflowError", e
211 overflowRecord = e.value
212 if overflowRecord == lastItem:
213 raise
jvr1c803b62002-09-12 17:33:12 +0000214
215 if options.verbose:
216 import time
217 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
218
219
220def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000221 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000222 try:
223 f = open(fileName, "rb")
224 except IOError:
225 return None
jvr45d1f3b2008-03-01 11:34:54 +0000226 cr, tp = getMacCreatorAndType(fileName)
227 if tp in ("sfnt", "FFIL"):
228 return "TTF"
229 if ext == ".dfont":
230 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000231 header = f.read(256)
232 head = header[:4]
233 if head == "OTTO":
234 return "OTF"
pabs37e91e772009-02-22 08:55:00 +0000235 elif head == "ttcf":
236 return "TTC"
jvr1c803b62002-09-12 17:33:12 +0000237 elif head in ("\0\1\0\0", "true"):
238 return "TTF"
239 elif head.lower() == "<?xm":
240 if header.find('sfntVersion="OTTO"') > 0:
241 return "OTX"
242 else:
243 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000244 return None
245
246
247def parseOptions(args):
248 try:
pabs3fb37a242013-06-22 06:43:01 +0000249 rawOptions, files = getopt.getopt(args, "ld:o:vht:x:sim:baey:")
jvr1c803b62002-09-12 17:33:12 +0000250 except getopt.GetoptError:
251 usage()
252
253 if not files:
254 usage()
255
256 options = Options(rawOptions, len(files))
257 jobs = []
258
259 for input in files:
260 tp = guessFileType(input)
pabs37e91e772009-02-22 08:55:00 +0000261 if tp in ("OTF", "TTF", "TTC"):
jvr1c803b62002-09-12 17:33:12 +0000262 extension = ".ttx"
263 if options.listTables:
264 action = ttList
265 else:
266 action = ttDump
267 elif tp == "TTX":
268 extension = ".ttf"
269 action = ttCompile
270 elif tp == "OTX":
271 extension = ".otf"
272 action = ttCompile
273 else:
274 print 'Unknown file type: "%s"' % input
275 continue
276
pabs3fb37a242013-06-22 06:43:01 +0000277 if options.outputFile:
278 output = options.outputFile
279 else:
280 output = makeOutputFileName(input, options.outputDir, extension)
jvr1c803b62002-09-12 17:33:12 +0000281 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000282 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000283
284
285def process(jobs, options):
286 for action, input, output in jobs:
287 action(input, output, options)
288
289
290def waitForKeyPress():
291 """Force the DOS Prompt window to stay open so the user gets
292 a chance to see what's wrong."""
293 import msvcrt
294 print '(Hit any key to exit)'
295 while not msvcrt.kbhit():
296 pass
297
298
299def main(args):
300 jobs, options = parseOptions(args)
301 try:
302 process(jobs, options)
303 except KeyboardInterrupt:
304 print "(Cancelled.)"
305 except SystemExit:
306 if sys.platform == "win32":
307 waitForKeyPress()
308 else:
309 raise
310 except:
311 if sys.platform == "win32":
312 import traceback
313 traceback.print_exc()
314 waitForKeyPress()
315 else:
316 raise
317
318
319if __name__ == "__main__":
320 main(sys.argv[1:])