xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttx.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""\
2*e1fe3e4aSElliott Hughesusage: ttx [options] inputfile1 [... inputfileN]
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott HughesTTX -- From OpenType To XML And Back
5*e1fe3e4aSElliott Hughes
6*e1fe3e4aSElliott HughesIf an input file is a TrueType or OpenType font file, it will be
7*e1fe3e4aSElliott Hughesdecompiled to a TTX file (an XML-based text format).
8*e1fe3e4aSElliott HughesIf an input file is a TTX file, it will be compiled to whatever
9*e1fe3e4aSElliott Hughesformat the data is in, a TrueType or OpenType/CFF font file.
10*e1fe3e4aSElliott HughesA special input value of - means read from the standard input.
11*e1fe3e4aSElliott Hughes
12*e1fe3e4aSElliott HughesOutput files are created so they are unique: an existing file is
13*e1fe3e4aSElliott Hughesnever overwritten.
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott HughesGeneral options
16*e1fe3e4aSElliott Hughes===============
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hughes-h Help            print this message.
19*e1fe3e4aSElliott Hughes--version          show version and exit.
20*e1fe3e4aSElliott Hughes-d <outputfolder>  Specify a directory where the output files are
21*e1fe3e4aSElliott Hughes                   to be created.
22*e1fe3e4aSElliott Hughes-o <outputfile>    Specify a file to write the output to. A special
23*e1fe3e4aSElliott Hughes                   value of - would use the standard output.
24*e1fe3e4aSElliott Hughes-f                 Overwrite existing output file(s), ie. don't append
25*e1fe3e4aSElliott Hughes                   numbers.
26*e1fe3e4aSElliott Hughes-v                 Verbose: more messages will be written to stdout
27*e1fe3e4aSElliott Hughes                   about what is being done.
28*e1fe3e4aSElliott Hughes-q                 Quiet: No messages will be written to stdout about
29*e1fe3e4aSElliott Hughes                   what is being done.
30*e1fe3e4aSElliott Hughes-a                 allow virtual glyphs ID's on compile or decompile.
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott HughesDump options
33*e1fe3e4aSElliott Hughes============
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes-l           List table info: instead of dumping to a TTX file, list
36*e1fe3e4aSElliott Hughes             some minimal info about each table.
37*e1fe3e4aSElliott Hughes-t <table>   Specify a table to dump. Multiple -t options
38*e1fe3e4aSElliott Hughes             are allowed. When no -t option is specified, all tables
39*e1fe3e4aSElliott Hughes             will be dumped.
40*e1fe3e4aSElliott Hughes-x <table>   Specify a table to exclude from the dump. Multiple
41*e1fe3e4aSElliott Hughes             -x options are allowed. -t and -x are mutually exclusive.
42*e1fe3e4aSElliott Hughes-s           Split tables: save the TTX data into separate TTX files per
43*e1fe3e4aSElliott Hughes             table and write one small TTX file that contains references
44*e1fe3e4aSElliott Hughes             to the individual table dumps. This file can be used as
45*e1fe3e4aSElliott Hughes             input to ttx, as long as the table files are in the
46*e1fe3e4aSElliott Hughes             same directory.
47*e1fe3e4aSElliott Hughes-g           Split glyf table: Save the glyf data into separate TTX files
48*e1fe3e4aSElliott Hughes             per glyph and write a small TTX for the glyf table which
49*e1fe3e4aSElliott Hughes             contains references to the individual TTGlyph elements.
50*e1fe3e4aSElliott Hughes             NOTE: specifying -g implies -s (no need for -s together
51*e1fe3e4aSElliott Hughes             with -g)
52*e1fe3e4aSElliott Hughes-i           Do NOT disassemble TT instructions: when this option is
53*e1fe3e4aSElliott Hughes             given, all TrueType programs (glyph programs, the font
54*e1fe3e4aSElliott Hughes             program and the pre-program) will be written to the TTX
55*e1fe3e4aSElliott Hughes             file as hex data instead of assembly. This saves some time
56*e1fe3e4aSElliott Hughes             and makes the TTX file smaller.
57*e1fe3e4aSElliott Hughes-z <format>  Specify a bitmap data export option for EBDT:
58*e1fe3e4aSElliott Hughes             {'raw', 'row', 'bitwise', 'extfile'} or for the CBDT:
59*e1fe3e4aSElliott Hughes             {'raw', 'extfile'} Each option does one of the following:
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughes             -z raw
62*e1fe3e4aSElliott Hughes               export the bitmap data as a hex dump
63*e1fe3e4aSElliott Hughes             -z row
64*e1fe3e4aSElliott Hughes               export each row as hex data
65*e1fe3e4aSElliott Hughes             -z bitwise
66*e1fe3e4aSElliott Hughes               export each row as binary in an ASCII art style
67*e1fe3e4aSElliott Hughes             -z extfile
68*e1fe3e4aSElliott Hughes               export the data as external files with XML references
69*e1fe3e4aSElliott Hughes
70*e1fe3e4aSElliott Hughes             If no export format is specified 'raw' format is used.
71*e1fe3e4aSElliott Hughes-e           Don't ignore decompilation errors, but show a full traceback
72*e1fe3e4aSElliott Hughes             and abort.
73*e1fe3e4aSElliott Hughes-y <number>  Select font number for TrueType Collection (.ttc/.otc),
74*e1fe3e4aSElliott Hughes             starting from 0.
75*e1fe3e4aSElliott Hughes--unicodedata <UnicodeData.txt>
76*e1fe3e4aSElliott Hughes             Use custom database file to write character names in the
77*e1fe3e4aSElliott Hughes             comments of the cmap TTX output.
78*e1fe3e4aSElliott Hughes--newline <value>
79*e1fe3e4aSElliott Hughes             Control how line endings are written in the XML file. It
80*e1fe3e4aSElliott Hughes             can be 'LF', 'CR', or 'CRLF'. If not specified, the
81*e1fe3e4aSElliott Hughes             default platform-specific line endings are used.
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott HughesCompile options
84*e1fe3e4aSElliott Hughes===============
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes-m           Merge with TrueType-input-file: specify a TrueType or
87*e1fe3e4aSElliott Hughes             OpenType font file to be merged with the TTX file. This
88*e1fe3e4aSElliott Hughes             option is only valid when at most one TTX file is specified.
89*e1fe3e4aSElliott Hughes-b           Don't recalc glyph bounding boxes: use the values in the
90*e1fe3e4aSElliott Hughes             TTX file as-is.
91*e1fe3e4aSElliott Hughes--recalc-timestamp
92*e1fe3e4aSElliott Hughes             Set font 'modified' timestamp to current time.
93*e1fe3e4aSElliott Hughes             By default, the modification time of the TTX file will be
94*e1fe3e4aSElliott Hughes             used.
95*e1fe3e4aSElliott Hughes--no-recalc-timestamp
96*e1fe3e4aSElliott Hughes             Keep the original font 'modified' timestamp.
97*e1fe3e4aSElliott Hughes--flavor <type>
98*e1fe3e4aSElliott Hughes             Specify flavor of output font file. May be 'woff' or 'woff2'.
99*e1fe3e4aSElliott Hughes             Note that WOFF2 requires the Brotli Python extension,
100*e1fe3e4aSElliott Hughes             available at https://github.com/google/brotli
101*e1fe3e4aSElliott Hughes--with-zopfli
102*e1fe3e4aSElliott Hughes             Use Zopfli instead of Zlib to compress WOFF. The Python
103*e1fe3e4aSElliott Hughes             extension is available at https://pypi.python.org/pypi/zopfli
104*e1fe3e4aSElliott Hughes"""
105*e1fe3e4aSElliott Hughes
106*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, TTLibError
107*e1fe3e4aSElliott Hughesfrom fontTools.misc.macCreatorType import getMacCreatorAndType
108*e1fe3e4aSElliott Hughesfrom fontTools.unicode import setUnicodeData
109*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import Tag, tostr
110*e1fe3e4aSElliott Hughesfrom fontTools.misc.timeTools import timestampSinceEpoch
111*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import Timer
112*e1fe3e4aSElliott Hughesfrom fontTools.misc.cliTools import makeOutputFileName
113*e1fe3e4aSElliott Hughesimport os
114*e1fe3e4aSElliott Hughesimport sys
115*e1fe3e4aSElliott Hughesimport getopt
116*e1fe3e4aSElliott Hughesimport re
117*e1fe3e4aSElliott Hughesimport logging
118*e1fe3e4aSElliott Hughes
119*e1fe3e4aSElliott Hughes
120*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.ttx")
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott HughesopentypeheaderRE = re.compile("""sfntVersion=['"]OTTO["']""")
123*e1fe3e4aSElliott Hughes
124*e1fe3e4aSElliott Hughes
125*e1fe3e4aSElliott Hughesclass Options(object):
126*e1fe3e4aSElliott Hughes    listTables = False
127*e1fe3e4aSElliott Hughes    outputDir = None
128*e1fe3e4aSElliott Hughes    outputFile = None
129*e1fe3e4aSElliott Hughes    overWrite = False
130*e1fe3e4aSElliott Hughes    verbose = False
131*e1fe3e4aSElliott Hughes    quiet = False
132*e1fe3e4aSElliott Hughes    splitTables = False
133*e1fe3e4aSElliott Hughes    splitGlyphs = False
134*e1fe3e4aSElliott Hughes    disassembleInstructions = True
135*e1fe3e4aSElliott Hughes    mergeFile = None
136*e1fe3e4aSElliott Hughes    recalcBBoxes = True
137*e1fe3e4aSElliott Hughes    ignoreDecompileErrors = True
138*e1fe3e4aSElliott Hughes    bitmapGlyphDataFormat = "raw"
139*e1fe3e4aSElliott Hughes    unicodedata = None
140*e1fe3e4aSElliott Hughes    newlinestr = "\n"
141*e1fe3e4aSElliott Hughes    recalcTimestamp = None
142*e1fe3e4aSElliott Hughes    flavor = None
143*e1fe3e4aSElliott Hughes    useZopfli = False
144*e1fe3e4aSElliott Hughes
145*e1fe3e4aSElliott Hughes    def __init__(self, rawOptions, numFiles):
146*e1fe3e4aSElliott Hughes        self.onlyTables = []
147*e1fe3e4aSElliott Hughes        self.skipTables = []
148*e1fe3e4aSElliott Hughes        self.fontNumber = -1
149*e1fe3e4aSElliott Hughes        for option, value in rawOptions:
150*e1fe3e4aSElliott Hughes            # general options
151*e1fe3e4aSElliott Hughes            if option == "-h":
152*e1fe3e4aSElliott Hughes                print(__doc__)
153*e1fe3e4aSElliott Hughes                sys.exit(0)
154*e1fe3e4aSElliott Hughes            elif option == "--version":
155*e1fe3e4aSElliott Hughes                from fontTools import version
156*e1fe3e4aSElliott Hughes
157*e1fe3e4aSElliott Hughes                print(version)
158*e1fe3e4aSElliott Hughes                sys.exit(0)
159*e1fe3e4aSElliott Hughes            elif option == "-d":
160*e1fe3e4aSElliott Hughes                if not os.path.isdir(value):
161*e1fe3e4aSElliott Hughes                    raise getopt.GetoptError(
162*e1fe3e4aSElliott Hughes                        "The -d option value must be an existing directory"
163*e1fe3e4aSElliott Hughes                    )
164*e1fe3e4aSElliott Hughes                self.outputDir = value
165*e1fe3e4aSElliott Hughes            elif option == "-o":
166*e1fe3e4aSElliott Hughes                self.outputFile = value
167*e1fe3e4aSElliott Hughes            elif option == "-f":
168*e1fe3e4aSElliott Hughes                self.overWrite = True
169*e1fe3e4aSElliott Hughes            elif option == "-v":
170*e1fe3e4aSElliott Hughes                self.verbose = True
171*e1fe3e4aSElliott Hughes            elif option == "-q":
172*e1fe3e4aSElliott Hughes                self.quiet = True
173*e1fe3e4aSElliott Hughes            # dump options
174*e1fe3e4aSElliott Hughes            elif option == "-l":
175*e1fe3e4aSElliott Hughes                self.listTables = True
176*e1fe3e4aSElliott Hughes            elif option == "-t":
177*e1fe3e4aSElliott Hughes                # pad with space if table tag length is less than 4
178*e1fe3e4aSElliott Hughes                value = value.ljust(4)
179*e1fe3e4aSElliott Hughes                self.onlyTables.append(value)
180*e1fe3e4aSElliott Hughes            elif option == "-x":
181*e1fe3e4aSElliott Hughes                # pad with space if table tag length is less than 4
182*e1fe3e4aSElliott Hughes                value = value.ljust(4)
183*e1fe3e4aSElliott Hughes                self.skipTables.append(value)
184*e1fe3e4aSElliott Hughes            elif option == "-s":
185*e1fe3e4aSElliott Hughes                self.splitTables = True
186*e1fe3e4aSElliott Hughes            elif option == "-g":
187*e1fe3e4aSElliott Hughes                # -g implies (and forces) splitTables
188*e1fe3e4aSElliott Hughes                self.splitGlyphs = True
189*e1fe3e4aSElliott Hughes                self.splitTables = True
190*e1fe3e4aSElliott Hughes            elif option == "-i":
191*e1fe3e4aSElliott Hughes                self.disassembleInstructions = False
192*e1fe3e4aSElliott Hughes            elif option == "-z":
193*e1fe3e4aSElliott Hughes                validOptions = ("raw", "row", "bitwise", "extfile")
194*e1fe3e4aSElliott Hughes                if value not in validOptions:
195*e1fe3e4aSElliott Hughes                    raise getopt.GetoptError(
196*e1fe3e4aSElliott Hughes                        "-z does not allow %s as a format. Use %s"
197*e1fe3e4aSElliott Hughes                        % (option, validOptions)
198*e1fe3e4aSElliott Hughes                    )
199*e1fe3e4aSElliott Hughes                self.bitmapGlyphDataFormat = value
200*e1fe3e4aSElliott Hughes            elif option == "-y":
201*e1fe3e4aSElliott Hughes                self.fontNumber = int(value)
202*e1fe3e4aSElliott Hughes            # compile options
203*e1fe3e4aSElliott Hughes            elif option == "-m":
204*e1fe3e4aSElliott Hughes                self.mergeFile = value
205*e1fe3e4aSElliott Hughes            elif option == "-b":
206*e1fe3e4aSElliott Hughes                self.recalcBBoxes = False
207*e1fe3e4aSElliott Hughes            elif option == "-e":
208*e1fe3e4aSElliott Hughes                self.ignoreDecompileErrors = False
209*e1fe3e4aSElliott Hughes            elif option == "--unicodedata":
210*e1fe3e4aSElliott Hughes                self.unicodedata = value
211*e1fe3e4aSElliott Hughes            elif option == "--newline":
212*e1fe3e4aSElliott Hughes                validOptions = ("LF", "CR", "CRLF")
213*e1fe3e4aSElliott Hughes                if value == "LF":
214*e1fe3e4aSElliott Hughes                    self.newlinestr = "\n"
215*e1fe3e4aSElliott Hughes                elif value == "CR":
216*e1fe3e4aSElliott Hughes                    self.newlinestr = "\r"
217*e1fe3e4aSElliott Hughes                elif value == "CRLF":
218*e1fe3e4aSElliott Hughes                    self.newlinestr = "\r\n"
219*e1fe3e4aSElliott Hughes                else:
220*e1fe3e4aSElliott Hughes                    raise getopt.GetoptError(
221*e1fe3e4aSElliott Hughes                        "Invalid choice for --newline: %r (choose from %s)"
222*e1fe3e4aSElliott Hughes                        % (value, ", ".join(map(repr, validOptions)))
223*e1fe3e4aSElliott Hughes                    )
224*e1fe3e4aSElliott Hughes            elif option == "--recalc-timestamp":
225*e1fe3e4aSElliott Hughes                self.recalcTimestamp = True
226*e1fe3e4aSElliott Hughes            elif option == "--no-recalc-timestamp":
227*e1fe3e4aSElliott Hughes                self.recalcTimestamp = False
228*e1fe3e4aSElliott Hughes            elif option == "--flavor":
229*e1fe3e4aSElliott Hughes                self.flavor = value
230*e1fe3e4aSElliott Hughes            elif option == "--with-zopfli":
231*e1fe3e4aSElliott Hughes                self.useZopfli = True
232*e1fe3e4aSElliott Hughes        if self.verbose and self.quiet:
233*e1fe3e4aSElliott Hughes            raise getopt.GetoptError("-q and -v options are mutually exclusive")
234*e1fe3e4aSElliott Hughes        if self.verbose:
235*e1fe3e4aSElliott Hughes            self.logLevel = logging.DEBUG
236*e1fe3e4aSElliott Hughes        elif self.quiet:
237*e1fe3e4aSElliott Hughes            self.logLevel = logging.WARNING
238*e1fe3e4aSElliott Hughes        else:
239*e1fe3e4aSElliott Hughes            self.logLevel = logging.INFO
240*e1fe3e4aSElliott Hughes        if self.mergeFile and self.flavor:
241*e1fe3e4aSElliott Hughes            raise getopt.GetoptError("-m and --flavor options are mutually exclusive")
242*e1fe3e4aSElliott Hughes        if self.onlyTables and self.skipTables:
243*e1fe3e4aSElliott Hughes            raise getopt.GetoptError("-t and -x options are mutually exclusive")
244*e1fe3e4aSElliott Hughes        if self.mergeFile and numFiles > 1:
245*e1fe3e4aSElliott Hughes            raise getopt.GetoptError(
246*e1fe3e4aSElliott Hughes                "Must specify exactly one TTX source file when using -m"
247*e1fe3e4aSElliott Hughes            )
248*e1fe3e4aSElliott Hughes        if self.flavor != "woff" and self.useZopfli:
249*e1fe3e4aSElliott Hughes            raise getopt.GetoptError("--with-zopfli option requires --flavor 'woff'")
250*e1fe3e4aSElliott Hughes
251*e1fe3e4aSElliott Hughes
252*e1fe3e4aSElliott Hughesdef ttList(input, output, options):
253*e1fe3e4aSElliott Hughes    ttf = TTFont(input, fontNumber=options.fontNumber, lazy=True)
254*e1fe3e4aSElliott Hughes    reader = ttf.reader
255*e1fe3e4aSElliott Hughes    tags = sorted(reader.keys())
256*e1fe3e4aSElliott Hughes    print('Listing table info for "%s":' % input)
257*e1fe3e4aSElliott Hughes    format = "    %4s  %10s  %8s  %8s"
258*e1fe3e4aSElliott Hughes    print(format % ("tag ", "  checksum", "  length", "  offset"))
259*e1fe3e4aSElliott Hughes    print(format % ("----", "----------", "--------", "--------"))
260*e1fe3e4aSElliott Hughes    for tag in tags:
261*e1fe3e4aSElliott Hughes        entry = reader.tables[tag]
262*e1fe3e4aSElliott Hughes        if ttf.flavor == "woff2":
263*e1fe3e4aSElliott Hughes            # WOFF2 doesn't store table checksums, so they must be calculated
264*e1fe3e4aSElliott Hughes            from fontTools.ttLib.sfnt import calcChecksum
265*e1fe3e4aSElliott Hughes
266*e1fe3e4aSElliott Hughes            data = entry.loadData(reader.transformBuffer)
267*e1fe3e4aSElliott Hughes            checkSum = calcChecksum(data)
268*e1fe3e4aSElliott Hughes        else:
269*e1fe3e4aSElliott Hughes            checkSum = int(entry.checkSum)
270*e1fe3e4aSElliott Hughes        if checkSum < 0:
271*e1fe3e4aSElliott Hughes            checkSum = checkSum + 0x100000000
272*e1fe3e4aSElliott Hughes        checksum = "0x%08X" % checkSum
273*e1fe3e4aSElliott Hughes        print(format % (tag, checksum, entry.length, entry.offset))
274*e1fe3e4aSElliott Hughes    print()
275*e1fe3e4aSElliott Hughes    ttf.close()
276*e1fe3e4aSElliott Hughes
277*e1fe3e4aSElliott Hughes
278*e1fe3e4aSElliott Hughes@Timer(log, "Done dumping TTX in %(time).3f seconds")
279*e1fe3e4aSElliott Hughesdef ttDump(input, output, options):
280*e1fe3e4aSElliott Hughes    input_name = input
281*e1fe3e4aSElliott Hughes    if input == "-":
282*e1fe3e4aSElliott Hughes        input, input_name = sys.stdin.buffer, sys.stdin.name
283*e1fe3e4aSElliott Hughes    output_name = output
284*e1fe3e4aSElliott Hughes    if output == "-":
285*e1fe3e4aSElliott Hughes        output, output_name = sys.stdout, sys.stdout.name
286*e1fe3e4aSElliott Hughes    log.info('Dumping "%s" to "%s"...', input_name, output_name)
287*e1fe3e4aSElliott Hughes    if options.unicodedata:
288*e1fe3e4aSElliott Hughes        setUnicodeData(options.unicodedata)
289*e1fe3e4aSElliott Hughes    ttf = TTFont(
290*e1fe3e4aSElliott Hughes        input,
291*e1fe3e4aSElliott Hughes        0,
292*e1fe3e4aSElliott Hughes        ignoreDecompileErrors=options.ignoreDecompileErrors,
293*e1fe3e4aSElliott Hughes        fontNumber=options.fontNumber,
294*e1fe3e4aSElliott Hughes    )
295*e1fe3e4aSElliott Hughes    ttf.saveXML(
296*e1fe3e4aSElliott Hughes        output,
297*e1fe3e4aSElliott Hughes        tables=options.onlyTables,
298*e1fe3e4aSElliott Hughes        skipTables=options.skipTables,
299*e1fe3e4aSElliott Hughes        splitTables=options.splitTables,
300*e1fe3e4aSElliott Hughes        splitGlyphs=options.splitGlyphs,
301*e1fe3e4aSElliott Hughes        disassembleInstructions=options.disassembleInstructions,
302*e1fe3e4aSElliott Hughes        bitmapGlyphDataFormat=options.bitmapGlyphDataFormat,
303*e1fe3e4aSElliott Hughes        newlinestr=options.newlinestr,
304*e1fe3e4aSElliott Hughes    )
305*e1fe3e4aSElliott Hughes    ttf.close()
306*e1fe3e4aSElliott Hughes
307*e1fe3e4aSElliott Hughes
308*e1fe3e4aSElliott Hughes@Timer(log, "Done compiling TTX in %(time).3f seconds")
309*e1fe3e4aSElliott Hughesdef ttCompile(input, output, options):
310*e1fe3e4aSElliott Hughes    input_name = input
311*e1fe3e4aSElliott Hughes    if input == "-":
312*e1fe3e4aSElliott Hughes        input, input_name = sys.stdin, sys.stdin.name
313*e1fe3e4aSElliott Hughes    output_name = output
314*e1fe3e4aSElliott Hughes    if output == "-":
315*e1fe3e4aSElliott Hughes        output, output_name = sys.stdout.buffer, sys.stdout.name
316*e1fe3e4aSElliott Hughes    log.info('Compiling "%s" to "%s"...' % (input_name, output))
317*e1fe3e4aSElliott Hughes    if options.useZopfli:
318*e1fe3e4aSElliott Hughes        from fontTools.ttLib import sfnt
319*e1fe3e4aSElliott Hughes
320*e1fe3e4aSElliott Hughes        sfnt.USE_ZOPFLI = True
321*e1fe3e4aSElliott Hughes    ttf = TTFont(
322*e1fe3e4aSElliott Hughes        options.mergeFile,
323*e1fe3e4aSElliott Hughes        flavor=options.flavor,
324*e1fe3e4aSElliott Hughes        recalcBBoxes=options.recalcBBoxes,
325*e1fe3e4aSElliott Hughes        recalcTimestamp=options.recalcTimestamp,
326*e1fe3e4aSElliott Hughes    )
327*e1fe3e4aSElliott Hughes    ttf.importXML(input)
328*e1fe3e4aSElliott Hughes
329*e1fe3e4aSElliott Hughes    if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
330*e1fe3e4aSElliott Hughes        # use TTX file modification time for head "modified" timestamp
331*e1fe3e4aSElliott Hughes        mtime = os.path.getmtime(input)
332*e1fe3e4aSElliott Hughes        ttf["head"].modified = timestampSinceEpoch(mtime)
333*e1fe3e4aSElliott Hughes
334*e1fe3e4aSElliott Hughes    ttf.save(output)
335*e1fe3e4aSElliott Hughes
336*e1fe3e4aSElliott Hughes
337*e1fe3e4aSElliott Hughesdef guessFileType(fileName):
338*e1fe3e4aSElliott Hughes    if fileName == "-":
339*e1fe3e4aSElliott Hughes        header = sys.stdin.buffer.peek(256)
340*e1fe3e4aSElliott Hughes        ext = ""
341*e1fe3e4aSElliott Hughes    else:
342*e1fe3e4aSElliott Hughes        base, ext = os.path.splitext(fileName)
343*e1fe3e4aSElliott Hughes        try:
344*e1fe3e4aSElliott Hughes            with open(fileName, "rb") as f:
345*e1fe3e4aSElliott Hughes                header = f.read(256)
346*e1fe3e4aSElliott Hughes        except IOError:
347*e1fe3e4aSElliott Hughes            return None
348*e1fe3e4aSElliott Hughes
349*e1fe3e4aSElliott Hughes    if header.startswith(b"\xef\xbb\xbf<?xml"):
350*e1fe3e4aSElliott Hughes        header = header.lstrip(b"\xef\xbb\xbf")
351*e1fe3e4aSElliott Hughes    cr, tp = getMacCreatorAndType(fileName)
352*e1fe3e4aSElliott Hughes    if tp in ("sfnt", "FFIL"):
353*e1fe3e4aSElliott Hughes        return "TTF"
354*e1fe3e4aSElliott Hughes    if ext == ".dfont":
355*e1fe3e4aSElliott Hughes        return "TTF"
356*e1fe3e4aSElliott Hughes    head = Tag(header[:4])
357*e1fe3e4aSElliott Hughes    if head == "OTTO":
358*e1fe3e4aSElliott Hughes        return "OTF"
359*e1fe3e4aSElliott Hughes    elif head == "ttcf":
360*e1fe3e4aSElliott Hughes        return "TTC"
361*e1fe3e4aSElliott Hughes    elif head in ("\0\1\0\0", "true"):
362*e1fe3e4aSElliott Hughes        return "TTF"
363*e1fe3e4aSElliott Hughes    elif head == "wOFF":
364*e1fe3e4aSElliott Hughes        return "WOFF"
365*e1fe3e4aSElliott Hughes    elif head == "wOF2":
366*e1fe3e4aSElliott Hughes        return "WOFF2"
367*e1fe3e4aSElliott Hughes    elif head == "<?xm":
368*e1fe3e4aSElliott Hughes        # Use 'latin1' because that can't fail.
369*e1fe3e4aSElliott Hughes        header = tostr(header, "latin1")
370*e1fe3e4aSElliott Hughes        if opentypeheaderRE.search(header):
371*e1fe3e4aSElliott Hughes            return "OTX"
372*e1fe3e4aSElliott Hughes        else:
373*e1fe3e4aSElliott Hughes            return "TTX"
374*e1fe3e4aSElliott Hughes    return None
375*e1fe3e4aSElliott Hughes
376*e1fe3e4aSElliott Hughes
377*e1fe3e4aSElliott Hughesdef parseOptions(args):
378*e1fe3e4aSElliott Hughes    rawOptions, files = getopt.getopt(
379*e1fe3e4aSElliott Hughes        args,
380*e1fe3e4aSElliott Hughes        "ld:o:fvqht:x:sgim:z:baey:",
381*e1fe3e4aSElliott Hughes        [
382*e1fe3e4aSElliott Hughes            "unicodedata=",
383*e1fe3e4aSElliott Hughes            "recalc-timestamp",
384*e1fe3e4aSElliott Hughes            "no-recalc-timestamp",
385*e1fe3e4aSElliott Hughes            "flavor=",
386*e1fe3e4aSElliott Hughes            "version",
387*e1fe3e4aSElliott Hughes            "with-zopfli",
388*e1fe3e4aSElliott Hughes            "newline=",
389*e1fe3e4aSElliott Hughes        ],
390*e1fe3e4aSElliott Hughes    )
391*e1fe3e4aSElliott Hughes
392*e1fe3e4aSElliott Hughes    options = Options(rawOptions, len(files))
393*e1fe3e4aSElliott Hughes    jobs = []
394*e1fe3e4aSElliott Hughes
395*e1fe3e4aSElliott Hughes    if not files:
396*e1fe3e4aSElliott Hughes        raise getopt.GetoptError("Must specify at least one input file")
397*e1fe3e4aSElliott Hughes
398*e1fe3e4aSElliott Hughes    for input in files:
399*e1fe3e4aSElliott Hughes        if input != "-" and not os.path.isfile(input):
400*e1fe3e4aSElliott Hughes            raise getopt.GetoptError('File not found: "%s"' % input)
401*e1fe3e4aSElliott Hughes        tp = guessFileType(input)
402*e1fe3e4aSElliott Hughes        if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"):
403*e1fe3e4aSElliott Hughes            extension = ".ttx"
404*e1fe3e4aSElliott Hughes            if options.listTables:
405*e1fe3e4aSElliott Hughes                action = ttList
406*e1fe3e4aSElliott Hughes            else:
407*e1fe3e4aSElliott Hughes                action = ttDump
408*e1fe3e4aSElliott Hughes        elif tp == "TTX":
409*e1fe3e4aSElliott Hughes            extension = "." + options.flavor if options.flavor else ".ttf"
410*e1fe3e4aSElliott Hughes            action = ttCompile
411*e1fe3e4aSElliott Hughes        elif tp == "OTX":
412*e1fe3e4aSElliott Hughes            extension = "." + options.flavor if options.flavor else ".otf"
413*e1fe3e4aSElliott Hughes            action = ttCompile
414*e1fe3e4aSElliott Hughes        else:
415*e1fe3e4aSElliott Hughes            raise getopt.GetoptError('Unknown file type: "%s"' % input)
416*e1fe3e4aSElliott Hughes
417*e1fe3e4aSElliott Hughes        if options.outputFile:
418*e1fe3e4aSElliott Hughes            output = options.outputFile
419*e1fe3e4aSElliott Hughes        else:
420*e1fe3e4aSElliott Hughes            if input == "-":
421*e1fe3e4aSElliott Hughes                raise getopt.GetoptError("Must provide -o when reading from stdin")
422*e1fe3e4aSElliott Hughes            output = makeOutputFileName(
423*e1fe3e4aSElliott Hughes                input, options.outputDir, extension, options.overWrite
424*e1fe3e4aSElliott Hughes            )
425*e1fe3e4aSElliott Hughes            # 'touch' output file to avoid race condition in choosing file names
426*e1fe3e4aSElliott Hughes            if action != ttList:
427*e1fe3e4aSElliott Hughes                open(output, "a").close()
428*e1fe3e4aSElliott Hughes        jobs.append((action, input, output))
429*e1fe3e4aSElliott Hughes    return jobs, options
430*e1fe3e4aSElliott Hughes
431*e1fe3e4aSElliott Hughes
432*e1fe3e4aSElliott Hughesdef process(jobs, options):
433*e1fe3e4aSElliott Hughes    for action, input, output in jobs:
434*e1fe3e4aSElliott Hughes        action(input, output, options)
435*e1fe3e4aSElliott Hughes
436*e1fe3e4aSElliott Hughes
437*e1fe3e4aSElliott Hughesdef main(args=None):
438*e1fe3e4aSElliott Hughes    """Convert OpenType fonts to XML and back"""
439*e1fe3e4aSElliott Hughes    from fontTools import configLogger
440*e1fe3e4aSElliott Hughes
441*e1fe3e4aSElliott Hughes    if args is None:
442*e1fe3e4aSElliott Hughes        args = sys.argv[1:]
443*e1fe3e4aSElliott Hughes    try:
444*e1fe3e4aSElliott Hughes        jobs, options = parseOptions(args)
445*e1fe3e4aSElliott Hughes    except getopt.GetoptError as e:
446*e1fe3e4aSElliott Hughes        print("%s\nERROR: %s" % (__doc__, e), file=sys.stderr)
447*e1fe3e4aSElliott Hughes        sys.exit(2)
448*e1fe3e4aSElliott Hughes
449*e1fe3e4aSElliott Hughes    configLogger(level=options.logLevel)
450*e1fe3e4aSElliott Hughes
451*e1fe3e4aSElliott Hughes    try:
452*e1fe3e4aSElliott Hughes        process(jobs, options)
453*e1fe3e4aSElliott Hughes    except KeyboardInterrupt:
454*e1fe3e4aSElliott Hughes        log.error("(Cancelled.)")
455*e1fe3e4aSElliott Hughes        sys.exit(1)
456*e1fe3e4aSElliott Hughes    except SystemExit:
457*e1fe3e4aSElliott Hughes        raise
458*e1fe3e4aSElliott Hughes    except TTLibError as e:
459*e1fe3e4aSElliott Hughes        log.error(e)
460*e1fe3e4aSElliott Hughes        sys.exit(1)
461*e1fe3e4aSElliott Hughes    except:
462*e1fe3e4aSElliott Hughes        log.exception("Unhandled exception has occurred")
463*e1fe3e4aSElliott Hughes        sys.exit(1)
464*e1fe3e4aSElliott Hughes
465*e1fe3e4aSElliott Hughes
466*e1fe3e4aSElliott Hughesif __name__ == "__main__":
467*e1fe3e4aSElliott Hughes    sys.exit(main())
468