2002-09-12 17:33:12 +00:00
|
|
|
"""\
|
|
|
|
usage: ttx [options] inputfile1 [... inputfileN]
|
|
|
|
|
|
|
|
TTX %s -- From OpenType To XML And Back
|
|
|
|
|
|
|
|
If an input file is a TrueType or OpenType font file, it will be
|
|
|
|
dumped to an TTX file (an XML-based text format).
|
|
|
|
If an input file is a TTX file, it will be compiled to a TrueType
|
|
|
|
or OpenType font file.
|
|
|
|
|
|
|
|
Output files are created so they are unique: an existing file is
|
2011-10-30 12:26:09 +00:00
|
|
|
never overwritten.
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
General options:
|
|
|
|
-h Help: print this message
|
|
|
|
-d <outputfolder> Specify a directory where the output files are
|
|
|
|
to be created.
|
2013-06-22 06:47:34 +00:00
|
|
|
-o <outputfile> Specify a file to write the output to.
|
2002-09-12 17:33:12 +00:00
|
|
|
-v Verbose: more messages will be written to stdout about what
|
|
|
|
is being done.
|
2013-09-04 13:16:39 +01:00
|
|
|
-q Quiet: No messages will be written to stdout about what
|
|
|
|
is being done.
|
2006-10-21 14:12:38 +00:00
|
|
|
-a allow virtual glyphs ID's on compile or decompile.
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
Dump options:
|
|
|
|
-l List table info: instead of dumping to a TTX file, list some
|
|
|
|
minimal info about each table.
|
|
|
|
-t <table> Specify a table to dump. Multiple -t options
|
|
|
|
are allowed. When no -t option is specified, all tables
|
|
|
|
will be dumped.
|
|
|
|
-x <table> Specify a table to exclude from the dump. Multiple
|
|
|
|
-x options are allowed. -t and -x are mutually exclusive.
|
|
|
|
-s Split tables: save the TTX data into separate TTX files per
|
|
|
|
table and write one small TTX file that contains references
|
|
|
|
to the individual table dumps. This file can be used as
|
|
|
|
input to ttx, as long as the table files are in the
|
|
|
|
same directory.
|
|
|
|
-i Do NOT disassemble TT instructions: when this option is given,
|
|
|
|
all TrueType programs (glyph programs, the font program and the
|
|
|
|
pre-program) will be written to the TTX file as hex data
|
|
|
|
instead of assembly. This saves some time and makes the TTX
|
|
|
|
file smaller.
|
2013-08-09 13:25:15 -07:00
|
|
|
-z <format> Specify a bitmap data export option for EBDT:
|
|
|
|
{'raw', 'row', 'bitwise', 'extfile'} or for the CBDT:
|
|
|
|
{'raw', 'extfile'} Each option does one of the following:
|
|
|
|
-z raw
|
|
|
|
* export the bitmap data as a hex dump
|
|
|
|
-z row
|
|
|
|
* export each row as hex data
|
|
|
|
-z bitwise
|
|
|
|
* export each row as binary in an ASCII art style
|
|
|
|
-z extfile
|
|
|
|
* export the data as external files with XML refences
|
|
|
|
If no export format is specified 'raw' format is used.
|
2008-03-01 09:42:58 +00:00
|
|
|
-e Don't ignore decompilation errors, but show a full traceback
|
|
|
|
and abort.
|
2009-11-08 15:53:24 +00:00
|
|
|
-y <number> Select font number for TrueType Collection,
|
2009-02-22 08:55:00 +00:00
|
|
|
starting from 0.
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
Compile options:
|
|
|
|
-m Merge with TrueType-input-file: specify a TrueType or OpenType
|
|
|
|
font file to be merged with the TTX file. This option is only
|
|
|
|
valid when at most one TTX file is specified.
|
2011-10-30 12:26:09 +00:00
|
|
|
-b Don't recalc glyph bounding boxes: use the values in the TTX
|
2002-09-12 17:33:12 +00:00
|
|
|
file as-is.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import getopt
|
|
|
|
import re
|
|
|
|
from fontTools.ttLib import TTFont
|
2006-10-21 14:12:38 +00:00
|
|
|
from fontTools.ttLib.tables.otBase import OTLOffsetOverflowError
|
|
|
|
from fontTools.ttLib.tables.otTables import fixLookupOverFlows, fixSubTableOverFlows
|
2008-03-01 11:34:54 +00:00
|
|
|
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
2002-09-12 17:33:12 +00:00
|
|
|
from fontTools import version
|
|
|
|
|
|
|
|
def usage():
|
|
|
|
print __doc__ % version
|
|
|
|
sys.exit(2)
|
|
|
|
|
2003-08-22 18:50:44 +00:00
|
|
|
|
2013-11-01 00:43:06 +00:00
|
|
|
numberAddedRE = re.compile("#\d+$")
|
2013-06-22 08:16:33 +00:00
|
|
|
opentypeheaderRE = re.compile('''sfntVersion=['"]OTTO["']''')
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
def makeOutputFileName(input, outputDir, extension):
|
|
|
|
dir, file = os.path.split(input)
|
|
|
|
file, ext = os.path.splitext(file)
|
|
|
|
if outputDir:
|
|
|
|
dir = outputDir
|
2013-11-14 20:27:07 -05:00
|
|
|
file = numberAddedRE.split(file)[0]
|
2013-11-14 21:57:25 -05:00
|
|
|
output = os.path.join(dir, file + extension)
|
2002-09-12 17:33:12 +00:00
|
|
|
n = 1
|
|
|
|
while os.path.exists(output):
|
|
|
|
output = os.path.join(dir, file + "#" + repr(n) + extension)
|
|
|
|
n = n + 1
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
class Options:
|
|
|
|
|
2013-11-24 18:49:35 -05:00
|
|
|
listTables = False
|
2002-09-12 17:33:12 +00:00
|
|
|
outputDir = None
|
2013-06-22 06:43:01 +00:00
|
|
|
outputFile = None
|
2013-11-24 18:49:35 -05:00
|
|
|
verbose = False
|
|
|
|
quiet = False
|
|
|
|
splitTables = False
|
|
|
|
disassembleInstructions = True
|
2002-09-12 17:33:12 +00:00
|
|
|
mergeFile = None
|
2013-11-24 18:49:35 -05:00
|
|
|
recalcBBoxes = True
|
|
|
|
allowVID = False
|
2008-03-01 09:42:58 +00:00
|
|
|
ignoreDecompileErrors = True
|
2013-08-09 13:25:15 -07:00
|
|
|
bitmapGlyphDataFormat = 'raw'
|
2008-03-01 09:42:58 +00:00
|
|
|
|
2002-09-12 17:33:12 +00:00
|
|
|
def __init__(self, rawOptions, numFiles):
|
|
|
|
self.onlyTables = []
|
|
|
|
self.skipTables = []
|
2009-02-22 08:55:00 +00:00
|
|
|
self.fontNumber = -1
|
2002-09-12 17:33:12 +00:00
|
|
|
for option, value in rawOptions:
|
|
|
|
# general options
|
|
|
|
if option == "-h":
|
|
|
|
print __doc__ % version
|
|
|
|
sys.exit(0)
|
|
|
|
elif option == "-d":
|
|
|
|
if not os.path.isdir(value):
|
|
|
|
print "The -d option value must be an existing directory"
|
|
|
|
sys.exit(2)
|
|
|
|
self.outputDir = value
|
2013-06-22 06:43:01 +00:00
|
|
|
elif option == "-o":
|
|
|
|
self.outputFile = value
|
2002-09-12 17:33:12 +00:00
|
|
|
elif option == "-v":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.verbose = True
|
2013-09-04 13:16:39 +01:00
|
|
|
elif option == "-q":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.quiet = True
|
2002-09-12 17:33:12 +00:00
|
|
|
# dump options
|
|
|
|
elif option == "-l":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.listTables = True
|
2002-09-12 17:33:12 +00:00
|
|
|
elif option == "-t":
|
|
|
|
self.onlyTables.append(value)
|
|
|
|
elif option == "-x":
|
|
|
|
self.skipTables.append(value)
|
|
|
|
elif option == "-s":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.splitTables = True
|
2002-09-12 17:33:12 +00:00
|
|
|
elif option == "-i":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.disassembleInstructions = False
|
2013-08-09 13:25:15 -07:00
|
|
|
elif option == "-z":
|
|
|
|
validOptions = ('raw', 'row', 'bitwise', 'extfile')
|
|
|
|
if value not in validOptions:
|
|
|
|
print "-z does not allow %s as a format. Use %s" % (option, validOptions)
|
|
|
|
sys.exit(2)
|
|
|
|
self.bitmapGlyphDataFormat = value
|
2009-02-22 08:55:00 +00:00
|
|
|
elif option == "-y":
|
|
|
|
self.fontNumber = int(value)
|
2002-09-12 17:33:12 +00:00
|
|
|
# compile options
|
|
|
|
elif option == "-m":
|
|
|
|
self.mergeFile = value
|
|
|
|
elif option == "-b":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.recalcBBoxes = False
|
2006-10-21 14:12:38 +00:00
|
|
|
elif option == "-a":
|
2013-11-24 18:49:35 -05:00
|
|
|
self.allowVID = True
|
2008-03-01 09:42:58 +00:00
|
|
|
elif option == "-e":
|
|
|
|
self.ignoreDecompileErrors = False
|
2002-09-12 17:33:12 +00:00
|
|
|
if self.onlyTables and self.skipTables:
|
2004-09-25 07:35:05 +00:00
|
|
|
print "-t and -x options are mutually exclusive"
|
2002-09-12 17:33:12 +00:00
|
|
|
sys.exit(2)
|
|
|
|
if self.mergeFile and numFiles > 1:
|
2004-09-25 07:35:05 +00:00
|
|
|
print "Must specify exactly one TTX source file when using -m"
|
2002-09-12 17:33:12 +00:00
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
|
|
|
|
def ttList(input, output, options):
|
2002-09-14 15:31:26 +00:00
|
|
|
import string
|
2009-02-22 08:55:00 +00:00
|
|
|
ttf = TTFont(input, fontNumber=options.fontNumber)
|
2002-09-12 17:33:12 +00:00
|
|
|
reader = ttf.reader
|
|
|
|
tags = reader.keys()
|
|
|
|
tags.sort()
|
|
|
|
print 'Listing table info for "%s":' % input
|
|
|
|
format = " %4s %10s %7s %7s"
|
|
|
|
print format % ("tag ", " checksum", " length", " offset")
|
|
|
|
print format % ("----", "----------", "-------", "-------")
|
|
|
|
for tag in tags:
|
|
|
|
entry = reader.tables[tag]
|
2004-12-24 15:59:35 +00:00
|
|
|
checkSum = long(entry.checkSum)
|
|
|
|
if checkSum < 0:
|
|
|
|
checkSum = checkSum + 0x100000000L
|
|
|
|
checksum = "0x" + string.zfill(hex(checkSum)[2:-1], 8)
|
2002-09-12 17:33:12 +00:00
|
|
|
print format % (tag, checksum, entry.length, entry.offset)
|
|
|
|
print
|
|
|
|
ttf.close()
|
|
|
|
|
|
|
|
|
|
|
|
def ttDump(input, output, options):
|
2013-09-04 13:16:39 +01:00
|
|
|
if not options.quiet:
|
|
|
|
print 'Dumping "%s" to "%s"...' % (input, output)
|
2008-03-01 09:42:58 +00:00
|
|
|
ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
|
2013-09-04 14:51:16 +01:00
|
|
|
quiet=options.quiet,
|
2009-02-22 08:55:00 +00:00
|
|
|
ignoreDecompileErrors=options.ignoreDecompileErrors,
|
|
|
|
fontNumber=options.fontNumber)
|
2002-09-12 17:33:12 +00:00
|
|
|
ttf.saveXML(output,
|
2013-11-24 18:49:35 -05:00
|
|
|
quiet=options.quiet,
|
2002-09-12 17:33:12 +00:00
|
|
|
tables=options.onlyTables,
|
2013-11-24 18:49:35 -05:00
|
|
|
skipTables=options.skipTables,
|
2002-09-12 17:33:12 +00:00
|
|
|
splitTables=options.splitTables,
|
2013-08-09 13:25:15 -07:00
|
|
|
disassembleInstructions=options.disassembleInstructions,
|
|
|
|
bitmapGlyphDataFormat=options.bitmapGlyphDataFormat)
|
2002-09-12 17:33:12 +00:00
|
|
|
ttf.close()
|
|
|
|
|
|
|
|
|
|
|
|
def ttCompile(input, output, options):
|
2013-09-04 13:30:21 +01:00
|
|
|
if not options.quiet:
|
|
|
|
print 'Compiling "%s" to "%s"...' % (input, output)
|
2002-09-12 17:33:12 +00:00
|
|
|
ttf = TTFont(options.mergeFile,
|
|
|
|
recalcBBoxes=options.recalcBBoxes,
|
2006-10-21 14:12:38 +00:00
|
|
|
verbose=options.verbose, allowVID=options.allowVID)
|
2013-09-04 13:30:21 +01:00
|
|
|
ttf.importXML(input, quiet=options.quiet)
|
2006-10-21 14:12:38 +00:00
|
|
|
try:
|
|
|
|
ttf.save(output)
|
|
|
|
except OTLOffsetOverflowError, e:
|
2008-03-09 20:39:38 +00:00
|
|
|
# XXX This shouldn't be here at all, it should be as close to the
|
|
|
|
# OTL code as possible.
|
2006-10-21 14:12:38 +00:00
|
|
|
overflowRecord = e.value
|
|
|
|
print "Attempting to fix OTLOffsetOverflowError", e
|
|
|
|
lastItem = overflowRecord
|
|
|
|
while 1:
|
|
|
|
ok = 0
|
|
|
|
if overflowRecord.itemName == None:
|
|
|
|
ok = fixLookupOverFlows(ttf, overflowRecord)
|
|
|
|
else:
|
|
|
|
ok = fixSubTableOverFlows(ttf, overflowRecord)
|
|
|
|
if not ok:
|
|
|
|
raise
|
|
|
|
|
|
|
|
try:
|
|
|
|
ttf.save(output)
|
|
|
|
break
|
|
|
|
except OTLOffsetOverflowError, e:
|
|
|
|
print "Attempting to fix OTLOffsetOverflowError", e
|
|
|
|
overflowRecord = e.value
|
|
|
|
if overflowRecord == lastItem:
|
|
|
|
raise
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
if options.verbose:
|
|
|
|
import time
|
|
|
|
print "finished at", time.strftime("%H:%M:%S", time.localtime(time.time()))
|
|
|
|
|
|
|
|
|
|
|
|
def guessFileType(fileName):
|
2003-08-22 18:50:44 +00:00
|
|
|
base, ext = os.path.splitext(fileName)
|
2002-09-12 17:33:12 +00:00
|
|
|
try:
|
|
|
|
f = open(fileName, "rb")
|
|
|
|
except IOError:
|
|
|
|
return None
|
2008-03-01 11:34:54 +00:00
|
|
|
cr, tp = getMacCreatorAndType(fileName)
|
|
|
|
if tp in ("sfnt", "FFIL"):
|
|
|
|
return "TTF"
|
|
|
|
if ext == ".dfont":
|
|
|
|
return "TTF"
|
2002-09-12 17:33:12 +00:00
|
|
|
header = f.read(256)
|
|
|
|
head = header[:4]
|
|
|
|
if head == "OTTO":
|
|
|
|
return "OTF"
|
2009-02-22 08:55:00 +00:00
|
|
|
elif head == "ttcf":
|
|
|
|
return "TTC"
|
2002-09-12 17:33:12 +00:00
|
|
|
elif head in ("\0\1\0\0", "true"):
|
|
|
|
return "TTF"
|
|
|
|
elif head.lower() == "<?xm":
|
2013-11-01 00:43:06 +00:00
|
|
|
if opentypeheaderRE.search(header):
|
2002-09-12 17:33:12 +00:00
|
|
|
return "OTX"
|
|
|
|
else:
|
|
|
|
return "TTX"
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def parseOptions(args):
|
|
|
|
try:
|
2013-09-04 14:49:37 +01:00
|
|
|
rawOptions, files = getopt.getopt(args, "ld:o:vqht:x:sim:z:baey:")
|
2002-09-12 17:33:12 +00:00
|
|
|
except getopt.GetoptError:
|
|
|
|
usage()
|
|
|
|
|
|
|
|
if not files:
|
|
|
|
usage()
|
|
|
|
|
|
|
|
options = Options(rawOptions, len(files))
|
|
|
|
jobs = []
|
|
|
|
|
|
|
|
for input in files:
|
|
|
|
tp = guessFileType(input)
|
2009-02-22 08:55:00 +00:00
|
|
|
if tp in ("OTF", "TTF", "TTC"):
|
2002-09-12 17:33:12 +00:00
|
|
|
extension = ".ttx"
|
|
|
|
if options.listTables:
|
|
|
|
action = ttList
|
|
|
|
else:
|
|
|
|
action = ttDump
|
|
|
|
elif tp == "TTX":
|
|
|
|
extension = ".ttf"
|
|
|
|
action = ttCompile
|
|
|
|
elif tp == "OTX":
|
|
|
|
extension = ".otf"
|
|
|
|
action = ttCompile
|
|
|
|
else:
|
|
|
|
print 'Unknown file type: "%s"' % input
|
|
|
|
continue
|
|
|
|
|
2013-06-22 06:43:01 +00:00
|
|
|
if options.outputFile:
|
|
|
|
output = options.outputFile
|
|
|
|
else:
|
|
|
|
output = makeOutputFileName(input, options.outputDir, extension)
|
2002-09-12 17:33:12 +00:00
|
|
|
jobs.append((action, input, output))
|
2002-09-12 20:05:23 +00:00
|
|
|
return jobs, options
|
2002-09-12 17:33:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def process(jobs, options):
|
|
|
|
for action, input, output in jobs:
|
|
|
|
action(input, output, options)
|
|
|
|
|
|
|
|
|
|
|
|
def waitForKeyPress():
|
|
|
|
"""Force the DOS Prompt window to stay open so the user gets
|
|
|
|
a chance to see what's wrong."""
|
|
|
|
import msvcrt
|
|
|
|
print '(Hit any key to exit)'
|
|
|
|
while not msvcrt.kbhit():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def main(args):
|
|
|
|
jobs, options = parseOptions(args)
|
|
|
|
try:
|
|
|
|
process(jobs, options)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print "(Cancelled.)"
|
|
|
|
except SystemExit:
|
|
|
|
if sys.platform == "win32":
|
|
|
|
waitForKeyPress()
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
except:
|
|
|
|
if sys.platform == "win32":
|
|
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
waitForKeyPress()
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main(sys.argv[1:])
|