blob: 43924097832cb6270f8da8544d56269f7551b02e [file] [log] [blame]
Vojtech Bocek277f7422014-05-08 23:05:13 +02001#!/usr/bin/env python
2# -*- coding: utf8 -*-
3import codecs,os,gzip,ctypes,ctypes.util,sys
4from struct import *
5from PIL import Image, ImageDraw, ImageFont
6
7# ====== Python script to convert TrueTypeFonts to TWRP's .dat format ======
8# This script was originally made by https://github.com/suky for his chinese version of TWRP
9# and then translated to English by feilplane at #twrp of irc.freenode.net.
10# However, it was not compatible with vanilla TWRP, so https://github.com/Tasssadar rewrote
11# most of it and it now has very little in common with the original script.
12
13class Reference():
14 def __init__(self, val):
15 self.__value = val
16
17 def get(self):
18 return self.__value
19
20 def set(self, val):
21 self.__value = val
22
23quiet = Reference(False)
24
25def log(text):
26 if not quiet.get():
27 sys.stdout.write(text)
28
29def write_data(f, width, height, offsets, data):
30 f.write(pack("<I", width))
31 f.write(pack("<I", height))
32 for off in offsets:
33 f.write(pack("<I", off))
34 f.write(data)
35
36if __name__ == "__main__":
37 fontsize = Reference(20)
38 out_fname = Reference("font.dat")
39 voffset = Reference(None)
40 padding = Reference(0)
41 font_fname = Reference(None)
42 preview = Reference(None)
43
44 arg_parser = [
45 ["-s", "--size=", fontsize, int],
46 ["-o", "--output=", out_fname, str],
47 ["-p", "--preview=", preview, str],
48 [None, "--padding=", padding, int],
49 ["-q", "--quiet", quiet, None],
50 [None, "--voffset=", voffset, int]
51 ]
52
53 argv = sys.argv
54 argc = len(argv)
55 i = 1
56 while i < argc:
57 arg = argv[i]
58 arg_next = argv[i+1] if i+1 < argc else None
59
60 if arg == "--help" or arg == "-h":
61 print ("This script converts TrueTypeFonts to .dat file for TWRP recovery.\n\n"
62 "Usage: %s [SWITCHES] [TRUETYPE FILE]\n\n"
63 " -h, --help - print help\n"
64 " -o, --output=[FILE] - output file or '-' for stdout (default: font.dat)\n"
65 " -p, --preview=[FILE] - generate font preview to png file\n"
66 " --padding=[PIXELS] - horizontal padding around each character (default: 0)\n"
67 " -q, --quiet - Do not print any output\n"
68 " -s, --size=[SIZE IN PIXELS] - specify font size in points (default: 20)\n"
69 " --voffset=[PIXELS] - vertical offset (default: font size*0.25)\n\n"
70 "Example:\n"
71 " %s -s 40 -o ComicSans_40.dat -p preview.png ComicSans.ttf\n") % (
72 sys.argv[0], sys.argv[0]
73 )
74 exit(0)
75
76 found = False
77 for p in arg_parser:
78 if p[0] and arg == p[0] and (arg_next or not p[3]):
79 if p[3]:
80 p[2].set(p[3](arg_next))
81 else:
82 p[2].set(True)
83 i += 1
84 found = True
85 break
86 elif p[1] and arg.startswith(p[1]):
87 if p[3]:
88 p[2].set(p[3](arg[len(p[1]):]))
89 else:
90 p[2].set(True)
91 found = True
92 break
93
94 if not found:
95 font_fname.set(arg)
96
97 i += 1
98
99 if not voffset.get():
100 voffset.set(int(fontsize.get()*0.25))
101
102 if out_fname.get() == "-":
103 quiet.set(True)
104
105 log("Loading font %s...\n" % font_fname.get())
106 font = ImageFont.truetype(font_fname.get(), fontsize.get(), 0, "utf-32be")
107 cwidth = 0
108 cheight = font.getsize('A')[1]
109 offsets = []
110 renders = []
111 data = bytes()
112
113 # temp Image and ImageDraw to get access to textsize
114 res = Image.new('L', (1, 1), 0)
115 res_draw = ImageDraw.Draw(res)
116
117 # Measure each character and render it to separate Image
118 log("Rendering characters...\n")
119 for i in range(32, 128):
120 w, h = res_draw.textsize(chr(i), font)
121 w += padding.get()*2
122 offsets.append(cwidth)
123 cwidth += w
124 if h > cheight:
125 cheight = h
126 ichr = Image.new('L', (w, cheight*2))
127 ichr_draw = ImageDraw.Draw(ichr)
128 ichr_draw.text((padding.get(), 0), chr(i), 255, font)
129 renders.append(ichr)
130
131 # Twice the height to account for under-the-baseline characters
132 cheight *= 2
133
134 # Create the result bitmap
135 log("Creating result bitmap...\n")
136 res = Image.new('L', (cwidth, cheight), 0)
137 res_draw = ImageDraw.Draw(res)
138
139 # Paste all characters into result bitmap
140 for i in range(len(renders)):
141 res.paste(renders[i], (offsets[i], 0))
142 # uncomment to draw lines separating each character (for debug)
143 #res_draw.rectangle([offsets[i], 0, offsets[i], cheight], outline="blue")
144
145 # crop the blank areas on top and bottom
146 (_, start_y, _, end_y) = res.getbbox()
147 res = res.crop((0, start_y, cwidth, end_y))
148 cheight = (end_y - start_y) + voffset.get()
149 new_res = Image.new('L', (cwidth, cheight))
150 new_res.paste(res, (0, voffset.get()))
151 res = new_res
152
153 # save the preview
154 if preview.get():
155 log("Saving preview to %s...\n" % preview.get())
156 res.save(preview.get())
157
158 # Pack the data.
159 # The "data" is a B/W bitmap with all 96 characters next to each other
160 # on one line. It is as wide as all the characters combined and as
161 # high as the tallest character, plus padding.
162 # Each byte contains info about eight pixels, starting from
163 # highest to lowest bit:
164 # bits: | 7 6 5 4 3 2 1 0 | 15 14 13 12 11 10 9 8 | ...
165 # pixels: | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | ...
166 log("Packing data...\n")
167 bit = 0
168 bit_itr = 0
169 for c in res.tostring():
170 # FIXME: How to handle antialiasing?
171 # if c != '\x00':
172 # In Python3, c is int, in Python2, c is string. Because of reasons.
173 try:
174 fill = (ord(c) >= 127)
175 except TypeError:
176 fill = (c >= 127)
177 if fill:
178 bit |= (1 << (7-bit_itr))
179 bit_itr += 1
180 if bit_itr >= 8:
181 data += pack("<B", bit)
182 bit_itr = 0
183 bit = 0
184
185 # Write them to the file.
186 # Format:
187 # 000: width
188 # 004: height
189 # 008: offsets of each characters (96*uint32)
190 # 392: data as described above
191 log("Writing to %s...\n" % out_fname.get())
192 if out_fname.get() == "-":
193 write_data(sys.stdout, cwidth, cheight, offsets, data)
194 else:
195 with open(out_fname.get(), 'wb') as f:
196 write_data(f, cwidth, cheight, offsets, data)
197
198 exit(0)