blob: 75a02b1f88812254a80af1cf21d422846e417966 [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):
jvrf7f0f742002-09-14 15:31:26 +0000131 import string
jvr1c803b62002-09-12 17:33:12 +0000132 ttf = TTFont(input)
133 reader = ttf.reader
134 tags = reader.keys()
135 tags.sort()
136 print 'Listing table info for "%s":' % input
137 format = " %4s %10s %7s %7s"
138 print format % ("tag ", " checksum", " length", " offset")
139 print format % ("----", "----------", "-------", "-------")
140 for tag in tags:
141 entry = reader.tables[tag]
jvrf7f0f742002-09-14 15:31:26 +0000142 checksum = "0x" + string.zfill(hex(entry.checkSum)[2:], 8)
jvr1c803b62002-09-12 17:33:12 +0000143 print format % (tag, checksum, entry.length, entry.offset)
144 print
145 ttf.close()
146
147
148def ttDump(input, output, options):
149 print 'Dumping "%s" to "%s"...' % (input, output)
150 ttf = TTFont(input, 0, verbose=options.verbose)
151 ttf.saveXML(output,
152 tables=options.onlyTables,
153 skipTables=options.skipTables,
154 splitTables=options.splitTables,
155 disassembleInstructions=options.disassembleInstructions)
156 ttf.close()
157
158
159def ttCompile(input, output, options):
160 print 'Compiling "%s" to "%s"...' % (input, output)
161 ttf = TTFont(options.mergeFile,
162 recalcBBoxes=options.recalcBBoxes,
163 verbose=options.verbose)
164 ttf.importXML(input)
165 ttf.save(output)
166
167 if options.verbose:
168 import time
169 print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
170
171
172def guessFileType(fileName):
173 try:
174 f = open(fileName, "rb")
175 except IOError:
176 return None
jvr70e2cb82002-09-12 22:59:09 +0000177 try:
178 import macfs
179 except ImportError:
180 pass
181 else:
182 cr, tp = macfs.FSSpec(fileName).GetCreatorType()
183 if tp == "FFIL":
184 return "TTF"
jvr1c803b62002-09-12 17:33:12 +0000185 header = f.read(256)
186 head = header[:4]
187 if head == "OTTO":
188 return "OTF"
189 elif head in ("\0\1\0\0", "true"):
190 return "TTF"
191 elif head.lower() == "<?xm":
192 if header.find('sfntVersion="OTTO"') > 0:
193 return "OTX"
194 else:
195 return "TTX"
jvr1c803b62002-09-12 17:33:12 +0000196 return None
197
198
199def parseOptions(args):
200 try:
201 rawOptions, files = getopt.getopt(args, "ld:vht:x:sim:b")
202 except getopt.GetoptError:
203 usage()
204
205 if not files:
206 usage()
207
208 options = Options(rawOptions, len(files))
209 jobs = []
210
211 for input in files:
212 tp = guessFileType(input)
213 if tp in ("OTF", "TTF"):
214 extension = ".ttx"
215 if options.listTables:
216 action = ttList
217 else:
218 action = ttDump
219 elif tp == "TTX":
220 extension = ".ttf"
221 action = ttCompile
222 elif tp == "OTX":
223 extension = ".otf"
224 action = ttCompile
225 else:
226 print 'Unknown file type: "%s"' % input
227 continue
228
229 output = makeOutputFileName(input, options.outputDir, extension)
230 jobs.append((action, input, output))
jvr2921bb22002-09-12 20:05:23 +0000231 return jobs, options
jvr1c803b62002-09-12 17:33:12 +0000232
233
234def process(jobs, options):
235 for action, input, output in jobs:
236 action(input, output, options)
237
238
239def waitForKeyPress():
240 """Force the DOS Prompt window to stay open so the user gets
241 a chance to see what's wrong."""
242 import msvcrt
243 print '(Hit any key to exit)'
244 while not msvcrt.kbhit():
245 pass
246
247
248def main(args):
249 jobs, options = parseOptions(args)
250 try:
251 process(jobs, options)
252 except KeyboardInterrupt:
253 print "(Cancelled.)"
254 except SystemExit:
255 if sys.platform == "win32":
256 waitForKeyPress()
257 else:
258 raise
259 except:
260 if sys.platform == "win32":
261 import traceback
262 traceback.print_exc()
263 waitForKeyPress()
264 else:
265 raise
266
267
268if __name__ == "__main__":
269 main(sys.argv[1:])