Skip to content

Commit 8836fcc

Browse files
committed
woff2otf
1 parent 69c8620 commit 8836fcc

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

scripts/woff2otf.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/opt/local/bin/python3.4
2+
#
3+
# Copyright 2012, Steffen Hanikel (https://github.com/hanikesn)
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# A tool to convert a WOFF back to a TTF/OTF font file, in pure Python
18+
19+
import struct
20+
import sys
21+
import zlib
22+
23+
24+
def convert_streams(infile, outfile):
25+
WOFFHeader = {'signature': struct.unpack(">I", infile.read(4))[0],
26+
'flavor': struct.unpack(">I", infile.read(4))[0],
27+
'length': struct.unpack(">I", infile.read(4))[0],
28+
'numTables': struct.unpack(">H", infile.read(2))[0],
29+
'reserved': struct.unpack(">H", infile.read(2))[0],
30+
'totalSfntSize': struct.unpack(">I", infile.read(4))[0],
31+
'majorVersion': struct.unpack(">H", infile.read(2))[0],
32+
'minorVersion': struct.unpack(">H", infile.read(2))[0],
33+
'metaOffset': struct.unpack(">I", infile.read(4))[0],
34+
'metaLength': struct.unpack(">I", infile.read(4))[0],
35+
'metaOrigLength': struct.unpack(">I", infile.read(4))[0],
36+
'privOffset': struct.unpack(">I", infile.read(4))[0],
37+
'privLength': struct.unpack(">I", infile.read(4))[0]}
38+
39+
outfile.write(struct.pack(">I", WOFFHeader['flavor']));
40+
outfile.write(struct.pack(">H", WOFFHeader['numTables']));
41+
maximum = list(filter(lambda x: x[1] <= WOFFHeader['numTables'], [(n, 2**n) for n in range(64)]))[-1];
42+
searchRange = maximum[1] * 16
43+
outfile.write(struct.pack(">H", searchRange));
44+
entrySelector = maximum[0]
45+
outfile.write(struct.pack(">H", entrySelector));
46+
rangeShift = WOFFHeader['numTables'] * 16 - searchRange;
47+
outfile.write(struct.pack(">H", rangeShift));
48+
49+
offset = outfile.tell()
50+
51+
TableDirectoryEntries = []
52+
for i in range(0, WOFFHeader['numTables']):
53+
TableDirectoryEntries.append({'tag': struct.unpack(">I", infile.read(4))[0],
54+
'offset': struct.unpack(">I", infile.read(4))[0],
55+
'compLength': struct.unpack(">I", infile.read(4))[0],
56+
'origLength': struct.unpack(">I", infile.read(4))[0],
57+
'origChecksum': struct.unpack(">I", infile.read(4))[0]})
58+
offset += 4*4
59+
60+
for TableDirectoryEntry in TableDirectoryEntries:
61+
outfile.write(struct.pack(">I", TableDirectoryEntry['tag']))
62+
outfile.write(struct.pack(">I", TableDirectoryEntry['origChecksum']))
63+
outfile.write(struct.pack(">I", offset))
64+
outfile.write(struct.pack(">I", TableDirectoryEntry['origLength']))
65+
TableDirectoryEntry['outOffset'] = offset
66+
offset += TableDirectoryEntry['origLength']
67+
if (offset % 4) != 0:
68+
offset += 4 - (offset % 4)
69+
70+
for TableDirectoryEntry in TableDirectoryEntries:
71+
infile.seek(TableDirectoryEntry['offset'])
72+
compressedData = infile.read(TableDirectoryEntry['compLength'])
73+
if TableDirectoryEntry['compLength'] != TableDirectoryEntry['origLength']:
74+
uncompressedData = zlib.decompress(compressedData)
75+
else:
76+
uncompressedData = compressedData
77+
outfile.seek(TableDirectoryEntry['outOffset'])
78+
outfile.write(uncompressedData)
79+
offset = TableDirectoryEntry['outOffset'] + TableDirectoryEntry['origLength'];
80+
padding = 0
81+
if (offset % 4) != 0:
82+
padding = 4 - (offset % 4)
83+
outfile.write(bytearray(padding));
84+
85+
86+
def convert(infilename, outfilename):
87+
with open(infilename , mode='rb') as infile:
88+
with open(outfilename, mode='wb') as outfile:
89+
convert_streams(infile, outfile)
90+
91+
92+
def main(argv):
93+
if len(argv) == 1 or len(argv) > 3:
94+
print('I convert *.woff files to *.otf files. (one at a time :)\n'
95+
'Usage: woff2otf.py web_font.woff [converted_filename.otf]\n'
96+
'If the target file name is ommited, it will be guessed. Have fun!\n')
97+
return
98+
99+
source_file_name = argv[1]
100+
if len(argv) == 3:
101+
target_file_name = argv[2]
102+
else:
103+
target_file_name = source_file_name.split('.')[0] + '.otf'
104+
105+
convert(source_file_name, target_file_name)
106+
return 0
107+
108+
109+
if __name__ == '__main__':
110+
sys.exit(main(sys.argv))

0 commit comments

Comments
 (0)