blob: d41b063a2646445cf8b9320faa81ce08ee964122 [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):
64 # the macfs module shipping with Jaguar's Python 2.2 is too
65 # broken to be of any use
66 have_broken_macfs = 1
67else:
68 have_broken_macfs = 0
69
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
92 verbose = 0
93 splitTables = 0
94 disassembleInstructions = 1
95 mergeFile = None
96 recalcBBoxes = 1
97
98 def __init__(self, rawOptions, numFiles):
99 self.onlyTables = []
100 self.skipTables = []
101 for option, value in rawOptions:
102 # general options
103 if option == "-h":
104 print __doc__ % version
105 sys.exit(0)
106 elif option == "-d":
107 if not os.path.isdir(value):
108 print "The -d option value must be an existing directory"
109 sys.exit(2)
110 self.outputDir = value
111 elif option == "-v":
112 self.verbose = 1
113 # dump options
114 elif option == "-l":
115 self.listTables = 1
116 elif option == "-t":
117 self.onlyTables.append(value)
118 elif option == "-x":
119 self.skipTables.append(value)
120 elif option == "-s":
121 self.splitTables = 1
122 elif option == "-i":
123 self.disassembleInstructions = 0
124 # compile options
125 elif option == "-m":
126 self.mergeFile = value
127 elif option == "-b":
128 self.recalcBBoxes = 0
129 if self.onlyTables and self.skipTables:
130 print "-t and -x options are mutually exlusive"
131 sys.exit(2)
132 if self.mergeFile and numFiles > 1:
133 print "Must specify exactly one TTX source file when using -i"
134 sys.exit(2)
135
136
137def ttList(input, output, options):
jvrf7f0f742002-09-14 15:31:26 +0000138 import string
jvr1c803b62002-09-12 17:33:12 +0000139 ttf = TTFont(input)
140 reader = ttf.reader
141 tags = reader.keys()
142 tags.sort()
143 print 'Listing table info for "%s":' % input
144 format = " %4s %10s %7s %7s"
145 print format % ("tag ", " checksum", " length", " offset")
146 print format % ("----", "----------", "-------", "-------")
147 for tag in tags:
148 entry = reader.tables[tag]
jvrf7f0f742002-09-14 15:31:26 +0000149 checksum = "0x" + string.zfill(hex(entry.checkSum)[2:], 8)
jvr1c803b62002-09-12 17:33:12 +0000150 print format % (tag, checksum, entry.length, entry.offset)
151 print
152 ttf.close()
153
154
155def ttDump(input, output, options):
156 print 'Dumping "%s" to "%s"...' % (input, output)
157 ttf = TTFont(input, 0, verbose=options.verbose)
158 ttf.saveXML(output,
159 tables=options.onlyTables,
160 skipTables=options.skipTables,
161 splitTables=options.splitTables,
162 disassembleInstructions=options.disassembleInstructions)
163 ttf.close()
164
165
166def ttCompile(input, output, options):
167 print 'Compiling "%s" to "%s"...' % (input, output)
168 ttf = TTFont(options.mergeFile,
169 recalcBBoxes=options.recalcBBoxes,
170 verbose=options.verbose)
171 ttf.importXML(input)
172 ttf.save(output)
173
174 if options.verbose:
175 import time
176 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
177
178
179def guessFileType(fileName):
jvr2e838ce2003-08-22 18:50:44 +0000180 base, ext = os.path.splitext(fileName)
jvr1c803b62002-09-12 17:33:12 +0000181 try:
182 f = open(fileName, "rb")
183 except IOError:
184 return None
jvr2e838ce2003-08-22 18:50:44 +0000185 if not have_broken_macfs:
186 try:
187 import macfs
188 except ImportError:
189 pass
190 else:
191 cr, tp = macfs.FSSpec(fileName).GetCreatorType()
192 if tp in ("sfnt", "FFIL"):
193 return "TTF"
194 if ext == ".dfont":
195 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000196 header = f.read(256)
197 head = header[:4]
198 if head == "OTTO":
199 return "OTF"
200 elif head in ("\0\1\0\0", "true"):
201 return "TTF"
202 elif head.lower() == "<?xm":
203 if header.find('sfntVersion="OTTO"') > 0:
204 return "OTX"
205 else:
206 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000207 return None
208
209
210def parseOptions(args):
211 try:
212 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
213 except getopt.GetoptError:
214 usage()
215
216 if not files:
217 usage()
218
219 options = Options(rawOptions, len(files))
220 jobs = []
221
222 for input in files:
223 tp = guessFileType(input)
224 if tp in ("OTF", "TTF"):
225 extension = ".ttx"
226 if options.listTables:
227 action = ttList
228 else:
229 action = ttDump
230 elif tp == "TTX":
231 extension = ".ttf"
232 action = ttCompile
233 elif tp == "OTX":
234 extension = ".otf"
235 action = ttCompile
236 else:
237 print 'Unknown file type: "%s"' % input
238 continue
239
240 output = makeOutputFileName(input, options.outputDir, extension)
241 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000242 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000243
244
245def process(jobs, options):
246 for action, input, output in jobs:
247 action(input, output, options)
248
249
250def waitForKeyPress():
251 """Force the DOS Prompt window to stay open so the user gets
252 a chance to see what's wrong."""
253 import msvcrt
254 print '(Hit any key to exit)'
255 while not msvcrt.kbhit():
256 pass
257
258
259def main(args):
260 jobs, options = parseOptions(args)
261 try:
262 process(jobs, options)
263 except KeyboardInterrupt:
264 print "(Cancelled.)"
265 except SystemExit:
266 if sys.platform == "win32":
267 waitForKeyPress()
268 else:
269 raise
270 except:
271 if sys.platform == "win32":
272 import traceback
273 traceback.print_exc()
274 waitForKeyPress()
275 else:
276 raise
277
278
279if __name__ == "__main__":
280 main(sys.argv[1:])