blob: 3ad5c1c90973b81bec2863233f99b9dc15864c08 [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.
22
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.
41
42 Compile options:
43 -m Merge with TrueType-input-file: specify a TrueType or OpenType
44 font file to be merged with the TTX file. This option is only
45 valid when at most one TTX file is specified.
46 -b Don't recalc glyph boundig boxes: use the values in the TTX
47 file as-is.
48"""
49
50
51import sys
52import os
53import getopt
54import re
55from fontTools.ttLib import TTFont
56from fontTools import version
57
58def usage():
59 print __doc__ % version
60 sys.exit(2)
61
62
jvr2e838ce2003-08-22 18:50:44 +000063if sys.platform == "darwin" and sys.version_info[:3] == (2, 2, 0):
jvr3d24a102003-08-24 19:52:03 +000064 # the Mac support of Jaguar's Python 2.2 is broken
65 have_broken_macsupport = 1
jvr2e838ce2003-08-22 18:50:44 +000066else:
jvr3d24a102003-08-24 19:52:03 +000067 have_broken_macsupport = 0
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
96
97 def __init__(self, rawOptions, numFiles):
98 self.onlyTables = []
99 self.skipTables = []
100 for option, value in rawOptions:
101 # general options
102 if option == "-h":
103 print __doc__ % version
104 sys.exit(0)
105 elif option == "-d":
106 if not os.path.isdir(value):
107 print "The -d option value must be an existing directory"
108 sys.exit(2)
109 self.outputDir = value
110 elif option == "-v":
111 self.verbose = 1
112 # dump options
113 elif option == "-l":
114 self.listTables = 1
115 elif option == "-t":
116 self.onlyTables.append(value)
117 elif option == "-x":
118 self.skipTables.append(value)
119 elif option == "-s":
120 self.splitTables = 1
121 elif option == "-i":
122 self.disassembleInstructions = 0
123 # compile options
124 elif option == "-m":
125 self.mergeFile = value
126 elif option == "-b":
127 self.recalcBBoxes = 0
128 if self.onlyTables and self.skipTables:
jvr6588c4e2004-09-25 07:35:05 +0000129 print "-t and -x options are mutually exclusive"
jvr1c803b62002-09-12 17:33:12 +0000130 sys.exit(2)
131 if self.mergeFile and numFiles > 1:
jvr6588c4e2004-09-25 07:35:05 +0000132 print "Must specify exactly one TTX source file when using -m"
jvr1c803b62002-09-12 17:33:12 +0000133 sys.exit(2)
134
135
136def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000137 import string
jvr1c803b62002-09-12 17:33:12 +0000138 ttf = TTFont(input)
139 reader = ttf.reader
140 tags = reader.keys()
141 tags.sort()
142 print 'Listing table info for "%s":' % input
143 format = " %4s %10s %7s %7s"
144 print format % ("tag ", " checksum", " length", " offset")
145 print format % ("----", "----------", "-------", "-------")
146 for tag in tags:
147 entry = reader.tables[tag]
jvrf7f0f742002-09-14 15:31:26 +0000148 checksum = "0x" + string.zfill(hex(entry.checkSum)[2:], 8)
jvr1c803b62002-09-12 17:33:12 +0000149 print format % (tag, checksum, entry.length, entry.offset)
150 print
151 ttf.close()
152
153
154def ttDump(input, output, options):
155 print 'Dumping "%s" to "%s"...' % (input, output)
156 ttf = TTFont(input, 0, verbose=options.verbose)
157 ttf.saveXML(output,
158 tables=options.onlyTables,
159 skipTables=options.skipTables,
160 splitTables=options.splitTables,
161 disassembleInstructions=options.disassembleInstructions)
162 ttf.close()
163
164
165def ttCompile(input, output, options):
166 print 'Compiling "%s" to "%s"...' % (input, output)
167 ttf = TTFont(options.mergeFile,
168 recalcBBoxes=options.recalcBBoxes,
169 verbose=options.verbose)
170 ttf.importXML(input)
171 ttf.save(output)
172
173 if options.verbose:
174 import time
175 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
176
177
178def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000179 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000180 try:
181 f = open(fileName, "rb")
182 except IOError:
183 return None
jvr3d24a102003-08-24 19:52:03 +0000184 if not have_broken_macsupport:
jvr2e838ce2003-08-22 18:50:44 +0000185 try:
jvr3d24a102003-08-24 19:52:03 +0000186 import MacOS
jvr2e838ce2003-08-22 18:50:44 +0000187 except ImportError:
188 pass
189 else:
jvr3d24a102003-08-24 19:52:03 +0000190 cr, tp = MacOS.GetCreatorAndType(fileName)
jvr2e838ce2003-08-22 18:50:44 +0000191 if tp in ("sfnt", "FFIL"):
192 return "TTF"
193 if ext == ".dfont":
194 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000195 header = f.read(256)
196 head = header[:4]
197 if head == "OTTO":
198 return "OTF"
199 elif head in ("\0\1\0\0", "true"):
200 return "TTF"
201 elif head.lower() == "<?xm":
202 if header.find('sfntVersion="OTTO"') > 0:
203 return "OTX"
204 else:
205 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000206 return None
207
208
209def parseOptions(args):
210 try:
211 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
212 except getopt.GetoptError:
213 usage()
214
215 if not files:
216 usage()
217
218 options = Options(rawOptions, len(files))
219 jobs = []
220
221 for input in files:
222 tp = guessFileType(input)
223 if tp in ("OTF", "TTF"):
224 extension = ".ttx"
225 if options.listTables:
226 action = ttList
227 else:
228 action = ttDump
229 elif tp == "TTX":
230 extension = ".ttf"
231 action = ttCompile
232 elif tp == "OTX":
233 extension = ".otf"
234 action = ttCompile
235 else:
236 print 'Unknown file type: "%s"' % input
237 continue
238
239 output = makeOutputFileName(input, options.outputDir, extension)
240 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000241 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000242
243
244def process(jobs, options):
245 for action, input, output in jobs:
246 action(input, output, options)
247
248
249def waitForKeyPress():
250 """Force the DOS Prompt window to stay open so the user gets
251 a chance to see what's wrong."""
252 import msvcrt
253 print '(Hit any key to exit)'
254 while not msvcrt.kbhit():
255 pass
256
257
258def main(args):
259 jobs, options = parseOptions(args)
260 try:
261 process(jobs, options)
262 except KeyboardInterrupt:
263 print "(Cancelled.)"
264 except SystemExit:
265 if sys.platform == "win32":
266 waitForKeyPress()
267 else:
268 raise
269 except:
270 if sys.platform == "win32":
271 import traceback
272 traceback.print_exc()
273 waitForKeyPress()
274 else:
275 raise
276
277
278if __name__ == "__main__":
279 main(sys.argv[1:])