blob: cf76ca5b08fc1f3215987a5b3b0a63cd207bde85 [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]
jvre0912bb2004-12-24 15:59:35 +0000148 checkSum = long(entry.checkSum)
149 if checkSum < 0:
150 checkSum = checkSum + 0x100000000L
151 checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
jvr1c803b62002-09-12 17:33:12 +0000152 print format % (tag, checksum, entry.length, entry.offset)
153 print
154 ttf.close()
155
156
157def ttDump(input, output, options):
158 print 'Dumping "%s" to "%s"...' % (input, output)
159 ttf = TTFont(input, 0, verbose=options.verbose)
160 ttf.saveXML(output,
161 tables=options.onlyTables,
162 skipTables=options.skipTables,
163 splitTables=options.splitTables,
164 disassembleInstructions=options.disassembleInstructions)
165 ttf.close()
166
167
168def ttCompile(input, output, options):
169 print 'Compiling "%s" to "%s"...' % (input, output)
170 ttf = TTFont(options.mergeFile,
171 recalcBBoxes=options.recalcBBoxes,
172 verbose=options.verbose)
173 ttf.importXML(input)
174 ttf.save(output)
175
176 if options.verbose:
177 import time
178 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
179
180
181def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000182 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000183 try:
184 f = open(fileName, "rb")
185 except IOError:
186 return None
jvr3d24a102003-08-24 19:52:03 +0000187 if not have_broken_macsupport:
jvr2e838ce2003-08-22 18:50:44 +0000188 try:
jvr3d24a102003-08-24 19:52:03 +0000189 import MacOS
jvr2e838ce2003-08-22 18:50:44 +0000190 except ImportError:
191 pass
192 else:
jvr3d24a102003-08-24 19:52:03 +0000193 cr, tp = MacOS.GetCreatorAndType(fileName)
jvr2e838ce2003-08-22 18:50:44 +0000194 if tp in ("sfnt", "FFIL"):
195 return "TTF"
196 if ext == ".dfont":
197 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000198 header = f.read(256)
199 head = header[:4]
200 if head == "OTTO":
201 return "OTF"
202 elif head in ("\0\1\0\0", "true"):
203 return "TTF"
204 elif head.lower() == "<?xm":
205 if header.find('sfntVersion="OTTO"') > 0:
206 return "OTX"
207 else:
208 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000209 return None
210
211
212def parseOptions(args):
213 try:
214 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
215 except getopt.GetoptError:
216 usage()
217
218 if not files:
219 usage()
220
221 options = Options(rawOptions, len(files))
222 jobs = []
223
224 for input in files:
225 tp = guessFileType(input)
226 if tp in ("OTF", "TTF"):
227 extension = ".ttx"
228 if options.listTables:
229 action = ttList
230 else:
231 action = ttDump
232 elif tp == "TTX":
233 extension = ".ttf"
234 action = ttCompile
235 elif tp == "OTX":
236 extension = ".otf"
237 action = ttCompile
238 else:
239 print 'Unknown file type: "%s"' % input
240 continue
241
242 output = makeOutputFileName(input, options.outputDir, extension)
243 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000244 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000245
246
247def process(jobs, options):
248 for action, input, output in jobs:
249 action(input, output, options)
250
251
252def waitForKeyPress():
253 """Force the DOS Prompt window to stay open so the user gets
254 a chance to see what's wrong."""
255 import msvcrt
256 print '(Hit any key to exit)'
257 while not msvcrt.kbhit():
258 pass
259
260
261def main(args):
262 jobs, options = parseOptions(args)
263 try:
264 process(jobs, options)
265 except KeyboardInterrupt:
266 print "(Cancelled.)"
267 except SystemExit:
268 if sys.platform == "win32":
269 waitForKeyPress()
270 else:
271 raise
272 except:
273 if sys.platform == "win32":
274 import traceback
275 traceback.print_exc()
276 waitForKeyPress()
277 else:
278 raise
279
280
281if __name__ == "__main__":
282 main(sys.argv[1:])