command-linefontstruetypewoff

How to convert WOFF to TTF/OTF via command line?


I know about services like Online Font Converter, but I am interested in offline solution, preferably over command line. Does anyone know a tool or workflow how to convert WOFF to OTF/TTF offline?


Solution

  • I wrote a simple python script for that:

    import struct
    import sys
    import zlib
    
    
    def convert_streams(infile, outfile):
        WOFFHeader = {'signature': struct.unpack(">I", infile.read(4))[0],
                      'flavor': struct.unpack(">I", infile.read(4))[0],
                      'length': struct.unpack(">I", infile.read(4))[0],
                      'numTables': struct.unpack(">H", infile.read(2))[0],
                      'reserved': struct.unpack(">H", infile.read(2))[0],
                      'totalSfntSize': struct.unpack(">I", infile.read(4))[0],
                      'majorVersion': struct.unpack(">H", infile.read(2))[0],
                      'minorVersion': struct.unpack(">H", infile.read(2))[0],
                      'metaOffset': struct.unpack(">I", infile.read(4))[0],
                      'metaLength': struct.unpack(">I", infile.read(4))[0],
                      'metaOrigLength': struct.unpack(">I", infile.read(4))[0],
                      'privOffset': struct.unpack(">I", infile.read(4))[0],
                      'privLength': struct.unpack(">I", infile.read(4))[0]}
    
        outfile.write(struct.pack(">I", WOFFHeader['flavor']));
        outfile.write(struct.pack(">H", WOFFHeader['numTables']));
        maximum = list(filter(lambda x: x[1] <= WOFFHeader['numTables'], [(n, 2**n) for n in range(64)]))[-1]; 
        searchRange = maximum[1] * 16
        outfile.write(struct.pack(">H", searchRange));
        entrySelector = maximum[0]
        outfile.write(struct.pack(">H", entrySelector));
        rangeShift = WOFFHeader['numTables'] * 16 -  searchRange;
        outfile.write(struct.pack(">H", rangeShift));
    
        offset = outfile.tell()
    
        TableDirectoryEntries = []
        for i in range(0, WOFFHeader['numTables']):
            TableDirectoryEntries.append({'tag': struct.unpack(">I", infile.read(4))[0],
                                   'offset': struct.unpack(">I", infile.read(4))[0],
                                   'compLength': struct.unpack(">I", infile.read(4))[0],
                                   'origLength': struct.unpack(">I", infile.read(4))[0],
                                   'origChecksum': struct.unpack(">I", infile.read(4))[0]})
            offset += 4*4
            
        for TableDirectoryEntry in TableDirectoryEntries:   
            outfile.write(struct.pack(">I", TableDirectoryEntry['tag']))
            outfile.write(struct.pack(">I", TableDirectoryEntry['origChecksum']))
            outfile.write(struct.pack(">I", offset))
            outfile.write(struct.pack(">I", TableDirectoryEntry['origLength']))
            TableDirectoryEntry['outOffset'] = offset
            offset += TableDirectoryEntry['origLength']
            if (offset % 4) != 0:
                offset += 4 - (offset % 4)
                
        for TableDirectoryEntry in TableDirectoryEntries:
            infile.seek(TableDirectoryEntry['offset'])
            compressedData = infile.read(TableDirectoryEntry['compLength'])
            if TableDirectoryEntry['compLength'] != TableDirectoryEntry['origLength']:
                uncompressedData = zlib.decompress(compressedData)
            else:
                uncompressedData = compressedData
            outfile.seek(TableDirectoryEntry['outOffset'])
            outfile.write(uncompressedData)
            offset = TableDirectoryEntry['outOffset'] + TableDirectoryEntry['origLength'];
            padding = 0
            if (offset % 4) != 0:
                padding = 4 - (offset % 4)
            outfile.write(bytearray(padding));
    
    
    def convert(infilename, outfilename):
        with open(infilename , mode='rb') as infile:
            with open(outfilename, mode='wb') as outfile:
                convert_streams(infile, outfile)
    
    
    def main(argv):
        if len(argv) == 1 or len(argv) > 3:
            print('I convert *.woff files to *.otf files. (one at a time :)\n'
                  'Usage: woff2otf.py web_font.woff [converted_filename.otf]\n'
                  'If the target file name is omitted, it will be guessed. Have fun!\n')
            return
    
        source_file_name  = argv[1]
        if len(argv) == 3:
            target_file_name = argv[2]
        else:
            target_file_name = source_file_name.rsplit('.', 1)[0] + '.otf'
    
        convert(source_file_name, target_file_name)
        return 0
    
    
    if __name__ == '__main__':
        sys.exit(main(sys.argv))