blob: 435b426b0768f0c46878f004429ca4129035ee4e [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
63numberAddedRE = re.compile("(.*)#\d+$")
64
65def makeOutputFileName(input, outputDir, extension):
66 dir, file = os.path.split(input)
67 file, ext = os.path.splitext(file)
68 if outputDir:
69 dir = outputDir
70 output = os.path.join(dir, file + extension)
71 m = numberAddedRE.match(file)
72 if m:
73 file = m.group(1)
74 n = 1
75 while os.path.exists(output):
76 output = os.path.join(dir, file + "#" + repr(n) + extension)
77 n = n + 1
78 return output
79
80
81class Options:
82
83 listTables = 0
84 outputDir = None
85 verbose = 0
86 splitTables = 0
87 disassembleInstructions = 1
88 mergeFile = None
89 recalcBBoxes = 1
90
91 def __init__(self, rawOptions, numFiles):
92 self.onlyTables = []
93 self.skipTables = []
94 for option, value in rawOptions:
95 # general options
96 if option == "-h":
97 print __doc__ % version
98 sys.exit(0)
99 elif option == "-d":
100 if not os.path.isdir(value):
101 print "The -d option value must be an existing directory"
102 sys.exit(2)
103 self.outputDir = value
104 elif option == "-v":
105 self.verbose = 1
106 # dump options
107 elif option == "-l":
108 self.listTables = 1
109 elif option == "-t":
110 self.onlyTables.append(value)
111 elif option == "-x":
112 self.skipTables.append(value)
113 elif option == "-s":
114 self.splitTables = 1
115 elif option == "-i":
116 self.disassembleInstructions = 0
117 # compile options
118 elif option == "-m":
119 self.mergeFile = value
120 elif option == "-b":
121 self.recalcBBoxes = 0
122 if self.onlyTables and self.skipTables:
123 print "-t and -x options are mutually exlusive"
124 sys.exit(2)
125 if self.mergeFile and numFiles > 1:
126 print "Must specify exactly one TTX source file when using -i"
127 sys.exit(2)
128
129
130def ttList(input, output, options):
131 ttf = TTFont(input)
132 reader = ttf.reader
133 tags = reader.keys()
134 tags.sort()
135 print 'Listing table info for "%s":' % input
136 format = " %4s %10s %7s %7s"
137 print format % ("tag ", " checksum", " length", " offset")
138 print format % ("----", "----------", "-------", "-------")
139 for tag in tags:
140 entry = reader.tables[tag]
141 checksum = "0x" + hex(entry.checkSum)[2:].zfill(8)
142 print format % (tag, checksum, entry.length, entry.offset)
143 print
144 ttf.close()
145
146
147def ttDump(input, output, options):
148 print 'Dumping "%s" to "%s"...' % (input, output)
149 ttf = TTFont(input, 0, verbose=options.verbose)
150 ttf.saveXML(output,
151 tables=options.onlyTables,
152 skipTables=options.skipTables,
153 splitTables=options.splitTables,
154 disassembleInstructions=options.disassembleInstructions)
155 ttf.close()
156
157
158def ttCompile(input, output, options):
159 print 'Compiling "%s" to "%s"...' % (input, output)
160 ttf = TTFont(options.mergeFile,
161 recalcBBoxes=options.recalcBBoxes,
162 verbose=options.verbose)
163 ttf.importXML(input)
164 ttf.save(output)
165
166 if options.verbose:
167 import time
168 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
169
170
171def guessFileType(fileName):
172 try:
173 f = open(fileName, "rb")
174 except IOError:
175 return None
jvr70e2cb82002-09-12 22:59:09 +0000176 try:
177 import macfs
178 except ImportError:
179 pass
180 else:
181 cr, tp = macfs.FSSpec(fileName).GetCreatorType()
182 if tp == "FFIL":
183 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000184 header = f.read(256)
185 head = header[:4]
186 if head == "OTTO":
187 return "OTF"
188 elif head in ("\0\1\0\0", "true"):
189 return "TTF"
190 elif head.lower() == "<?xm":
191 if header.find('sfntVersion="OTTO"') > 0:
192 return "OTX"
193 else:
194 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000195 return None
196
197
198def parseOptions(args):
199 try:
200 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
201 except getopt.GetoptError:
202 usage()
203
204 if not files:
205 usage()
206
207 options = Options(rawOptions, len(files))
208 jobs = []
209
210 for input in files:
211 tp = guessFileType(input)
212 if tp in ("OTF", "TTF"):
213 extension = ".ttx"
214 if options.listTables:
215 action = ttList
216 else:
217 action = ttDump
218 elif tp == "TTX":
219 extension = ".ttf"
220 action = ttCompile
221 elif tp == "OTX":
222 extension = ".otf"
223 action = ttCompile
224 else:
225 print 'Unknown file type: "%s"' % input
226 continue
227
228 output = makeOutputFileName(input, options.outputDir, extension)
229 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000230 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000231
232
233def process(jobs, options):
234 for action, input, output in jobs:
235 action(input, output, options)
236
237
238def waitForKeyPress():
239 """Force the DOS Prompt window to stay open so the user gets
240 a chance to see what's wrong."""
241 import msvcrt
242 print '(Hit any key to exit)'
243 while not msvcrt.kbhit():
244 pass
245
246
247def main(args):
248 jobs, options = parseOptions(args)
249 try:
250 process(jobs, options)
251 except KeyboardInterrupt:
252 print "(Cancelled.)"
253 except SystemExit:
254 if sys.platform == "win32":
255 waitForKeyPress()
256 else:
257 raise
258 except:
259 if sys.platform == "win32":
260 import traceback
261 traceback.print_exc()
262 waitForKeyPress()
263 else:
264 raise
265
266
267if __name__ == "__main__":
268 main(sys.argv[1:])