123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- """\
- usage: ttx [options] inputfile1 [... inputfileN]
- TTX -- From OpenType To XML And Back
- If an input file is a TrueType or OpenType font file, it will be
- decompiled to a TTX file (an XML-based text format).
- If an input file is a TTX file, it will be compiled to whatever
- format the data is in, a TrueType or OpenType/CFF font file.
- A special input value of - means read from the standard input.
- Output files are created so they are unique: an existing file is
- never overwritten.
- General options
- ===============
- -h Help print this message.
- --version show version and exit.
- -d <outputfolder> Specify a directory where the output files are
- to be created.
- -o <outputfile> Specify a file to write the output to. A special
- value of - would use the standard output.
- -f Overwrite existing output file(s), ie. don't append
- numbers.
- -v Verbose: more messages will be written to stdout
- about what is being done.
- -q Quiet: No messages will be written to stdout about
- what is being done.
- -a allow virtual glyphs ID's on compile or decompile.
- 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.
- -g Split glyf table: Save the glyf data into separate TTX files
- per glyph and write a small TTX for the glyf table which
- contains references to the individual TTGlyph elements.
- NOTE: specifying -g implies -s (no need for -s together
- with -g)
- -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.
- -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 references
- If no export format is specified 'raw' format is used.
- -e Don't ignore decompilation errors, but show a full traceback
- and abort.
- -y <number> Select font number for TrueType Collection (.ttc/.otc),
- starting from 0.
- --unicodedata <UnicodeData.txt>
- Use custom database file to write character names in the
- comments of the cmap TTX output.
- --newline <value>
- Control how line endings are written in the XML file. It
- can be 'LF', 'CR', or 'CRLF'. If not specified, the
- default platform-specific line endings are used.
- 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.
- -b Don't recalc glyph bounding boxes: use the values in the
- TTX file as-is.
- --recalc-timestamp
- Set font 'modified' timestamp to current time.
- By default, the modification time of the TTX file will be
- used.
- --no-recalc-timestamp
- Keep the original font 'modified' timestamp.
- --flavor <type>
- Specify flavor of output font file. May be 'woff' or 'woff2'.
- Note that WOFF2 requires the Brotli Python extension,
- available at https://github.com/google/brotli
- --with-zopfli
- Use Zopfli instead of Zlib to compress WOFF. The Python
- extension is available at https://pypi.python.org/pypi/zopfli
- """
- from fontTools.ttLib import TTFont, TTLibError
- from fontTools.misc.macCreatorType import getMacCreatorAndType
- from fontTools.unicode import setUnicodeData
- from fontTools.misc.textTools import Tag, tostr
- from fontTools.misc.timeTools import timestampSinceEpoch
- from fontTools.misc.loggingTools import Timer
- from fontTools.misc.cliTools import makeOutputFileName
- import os
- import sys
- import getopt
- import re
- import logging
- log = logging.getLogger("fontTools.ttx")
- opentypeheaderRE = re.compile("""sfntVersion=['"]OTTO["']""")
- class Options(object):
- listTables = False
- outputDir = None
- outputFile = None
- overWrite = False
- verbose = False
- quiet = False
- splitTables = False
- splitGlyphs = False
- disassembleInstructions = True
- mergeFile = None
- recalcBBoxes = True
- ignoreDecompileErrors = True
- bitmapGlyphDataFormat = "raw"
- unicodedata = None
- newlinestr = "\n"
- recalcTimestamp = None
- flavor = None
- useZopfli = False
- def __init__(self, rawOptions, numFiles):
- self.onlyTables = []
- self.skipTables = []
- self.fontNumber = -1
- for option, value in rawOptions:
- # general options
- if option == "-h":
- print(__doc__)
- sys.exit(0)
- elif option == "--version":
- from fontTools import version
- print(version)
- sys.exit(0)
- elif option == "-d":
- if not os.path.isdir(value):
- raise getopt.GetoptError(
- "The -d option value must be an existing directory"
- )
- self.outputDir = value
- elif option == "-o":
- self.outputFile = value
- elif option == "-f":
- self.overWrite = True
- elif option == "-v":
- self.verbose = True
- elif option == "-q":
- self.quiet = True
- # dump options
- elif option == "-l":
- self.listTables = True
- elif option == "-t":
- # pad with space if table tag length is less than 4
- value = value.ljust(4)
- self.onlyTables.append(value)
- elif option == "-x":
- # pad with space if table tag length is less than 4
- value = value.ljust(4)
- self.skipTables.append(value)
- elif option == "-s":
- self.splitTables = True
- elif option == "-g":
- # -g implies (and forces) splitTables
- self.splitGlyphs = True
- self.splitTables = True
- elif option == "-i":
- self.disassembleInstructions = False
- elif option == "-z":
- validOptions = ("raw", "row", "bitwise", "extfile")
- if value not in validOptions:
- raise getopt.GetoptError(
- "-z does not allow %s as a format. Use %s"
- % (option, validOptions)
- )
- self.bitmapGlyphDataFormat = value
- elif option == "-y":
- self.fontNumber = int(value)
- # compile options
- elif option == "-m":
- self.mergeFile = value
- elif option == "-b":
- self.recalcBBoxes = False
- elif option == "-e":
- self.ignoreDecompileErrors = False
- elif option == "--unicodedata":
- self.unicodedata = value
- elif option == "--newline":
- validOptions = ("LF", "CR", "CRLF")
- if value == "LF":
- self.newlinestr = "\n"
- elif value == "CR":
- self.newlinestr = "\r"
- elif value == "CRLF":
- self.newlinestr = "\r\n"
- else:
- raise getopt.GetoptError(
- "Invalid choice for --newline: %r (choose from %s)"
- % (value, ", ".join(map(repr, validOptions)))
- )
- elif option == "--recalc-timestamp":
- self.recalcTimestamp = True
- elif option == "--no-recalc-timestamp":
- self.recalcTimestamp = False
- elif option == "--flavor":
- self.flavor = value
- elif option == "--with-zopfli":
- self.useZopfli = True
- if self.verbose and self.quiet:
- raise getopt.GetoptError("-q and -v options are mutually exclusive")
- if self.verbose:
- self.logLevel = logging.DEBUG
- elif self.quiet:
- self.logLevel = logging.WARNING
- else:
- self.logLevel = logging.INFO
- if self.mergeFile and self.flavor:
- raise getopt.GetoptError("-m and --flavor options are mutually exclusive")
- if self.onlyTables and self.skipTables:
- raise getopt.GetoptError("-t and -x options are mutually exclusive")
- if self.mergeFile and numFiles > 1:
- raise getopt.GetoptError(
- "Must specify exactly one TTX source file when using -m"
- )
- if self.flavor != "woff" and self.useZopfli:
- raise getopt.GetoptError("--with-zopfli option requires --flavor 'woff'")
- def ttList(input, output, options):
- ttf = TTFont(input, fontNumber=options.fontNumber, lazy=True)
- reader = ttf.reader
- tags = sorted(reader.keys())
- print('Listing table info for "%s":' % input)
- format = " %4s %10s %8s %8s"
- print(format % ("tag ", " checksum", " length", " offset"))
- print(format % ("----", "----------", "--------", "--------"))
- for tag in tags:
- entry = reader.tables[tag]
- if ttf.flavor == "woff2":
- # WOFF2 doesn't store table checksums, so they must be calculated
- from fontTools.ttLib.sfnt import calcChecksum
- data = entry.loadData(reader.transformBuffer)
- checkSum = calcChecksum(data)
- else:
- checkSum = int(entry.checkSum)
- if checkSum < 0:
- checkSum = checkSum + 0x100000000
- checksum = "0x%08X" % checkSum
- print(format % (tag, checksum, entry.length, entry.offset))
- print()
- ttf.close()
- @Timer(log, "Done dumping TTX in %(time).3f seconds")
- def ttDump(input, output, options):
- input_name = input
- if input == "-":
- input, input_name = sys.stdin.buffer, sys.stdin.name
- output_name = output
- if output == "-":
- output, output_name = sys.stdout, sys.stdout.name
- log.info('Dumping "%s" to "%s"...', input_name, output_name)
- if options.unicodedata:
- setUnicodeData(options.unicodedata)
- ttf = TTFont(
- input,
- 0,
- ignoreDecompileErrors=options.ignoreDecompileErrors,
- fontNumber=options.fontNumber,
- )
- ttf.saveXML(
- output,
- tables=options.onlyTables,
- skipTables=options.skipTables,
- splitTables=options.splitTables,
- splitGlyphs=options.splitGlyphs,
- disassembleInstructions=options.disassembleInstructions,
- bitmapGlyphDataFormat=options.bitmapGlyphDataFormat,
- newlinestr=options.newlinestr,
- )
- ttf.close()
- @Timer(log, "Done compiling TTX in %(time).3f seconds")
- def ttCompile(input, output, options):
- input_name = input
- if input == "-":
- input, input_name = sys.stdin, sys.stdin.name
- output_name = output
- if output == "-":
- output, output_name = sys.stdout.buffer, sys.stdout.name
- log.info('Compiling "%s" to "%s"...' % (input_name, output))
- if options.useZopfli:
- from fontTools.ttLib import sfnt
- sfnt.USE_ZOPFLI = True
- ttf = TTFont(
- options.mergeFile,
- flavor=options.flavor,
- recalcBBoxes=options.recalcBBoxes,
- recalcTimestamp=options.recalcTimestamp,
- )
- ttf.importXML(input)
- if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
- # use TTX file modification time for head "modified" timestamp
- mtime = os.path.getmtime(input)
- ttf["head"].modified = timestampSinceEpoch(mtime)
- ttf.save(output)
- def guessFileType(fileName):
- if fileName == "-":
- header = sys.stdin.buffer.peek(256)
- ext = ""
- else:
- base, ext = os.path.splitext(fileName)
- try:
- with open(fileName, "rb") as f:
- header = f.read(256)
- except IOError:
- return None
- if header.startswith(b"\xef\xbb\xbf<?xml"):
- header = header.lstrip(b"\xef\xbb\xbf")
- cr, tp = getMacCreatorAndType(fileName)
- if tp in ("sfnt", "FFIL"):
- return "TTF"
- if ext == ".dfont":
- return "TTF"
- head = Tag(header[:4])
- if head == "OTTO":
- return "OTF"
- elif head == "ttcf":
- return "TTC"
- elif head in ("\0\1\0\0", "true"):
- return "TTF"
- elif head == "wOFF":
- return "WOFF"
- elif head == "wOF2":
- return "WOFF2"
- elif head == "<?xm":
- # Use 'latin1' because that can't fail.
- header = tostr(header, "latin1")
- if opentypeheaderRE.search(header):
- return "OTX"
- else:
- return "TTX"
- return None
- def parseOptions(args):
- rawOptions, files = getopt.getopt(
- args,
- "ld:o:fvqht:x:sgim:z:baey:",
- [
- "unicodedata=",
- "recalc-timestamp",
- "no-recalc-timestamp",
- "flavor=",
- "version",
- "with-zopfli",
- "newline=",
- ],
- )
- options = Options(rawOptions, len(files))
- jobs = []
- if not files:
- raise getopt.GetoptError("Must specify at least one input file")
- for input in files:
- if input != "-" and not os.path.isfile(input):
- raise getopt.GetoptError('File not found: "%s"' % input)
- tp = guessFileType(input)
- if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"):
- extension = ".ttx"
- if options.listTables:
- action = ttList
- else:
- action = ttDump
- elif tp == "TTX":
- extension = "." + options.flavor if options.flavor else ".ttf"
- action = ttCompile
- elif tp == "OTX":
- extension = "." + options.flavor if options.flavor else ".otf"
- action = ttCompile
- else:
- raise getopt.GetoptError('Unknown file type: "%s"' % input)
- if options.outputFile:
- output = options.outputFile
- else:
- if input == "-":
- raise getopt.GetoptError("Must provide -o when reading from stdin")
- output = makeOutputFileName(
- input, options.outputDir, extension, options.overWrite
- )
- # 'touch' output file to avoid race condition in choosing file names
- if action != ttList:
- open(output, "a").close()
- jobs.append((action, input, output))
- return jobs, options
- def process(jobs, options):
- for action, input, output in jobs:
- action(input, output, options)
- def main(args=None):
- """Convert OpenType fonts to XML and back"""
- from fontTools import configLogger
- if args is None:
- args = sys.argv[1:]
- try:
- jobs, options = parseOptions(args)
- except getopt.GetoptError as e:
- print("%s\nERROR: %s" % (__doc__, e), file=sys.stderr)
- sys.exit(2)
- configLogger(level=options.logLevel)
- try:
- process(jobs, options)
- except KeyboardInterrupt:
- log.error("(Cancelled.)")
- sys.exit(1)
- except SystemExit:
- raise
- except TTLibError as e:
- log.error(e)
- sys.exit(1)
- except:
- log.exception("Unhandled exception has occurred")
- sys.exit(1)
- if __name__ == "__main__":
- sys.exit(main())
|