Vojtech Bocek | 277f742 | 2014-05-08 23:05:13 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf8 -*- |
| 3 | import codecs,os,gzip,ctypes,ctypes.util,sys |
| 4 | from struct import * |
| 5 | from 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 | |
| 13 | class 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 | |
| 23 | quiet = Reference(False) |
| 24 | |
| 25 | def log(text): |
| 26 | if not quiet.get(): |
| 27 | sys.stdout.write(text) |
| 28 | |
| 29 | def 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 | |
| 36 | if __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) |