Add script to convert TrueTypeFonts to TWRP's .dat format

Change-Id: I3d6cc65a83b7da9428adf37804cf9cbd0df99492
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
diff --git a/prebuilt/twrp_fonts.py b/prebuilt/twrp_fonts.py
new file mode 100755
index 0000000..4392409
--- /dev/null
+++ b/prebuilt/twrp_fonts.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+import codecs,os,gzip,ctypes,ctypes.util,sys
+from struct import *
+from PIL import Image, ImageDraw, ImageFont
+
+# ====== Python script to convert TrueTypeFonts to TWRP's .dat format ======
+# This script was originally made by https://github.com/suky for his chinese version of TWRP
+# and then translated to English by feilplane at #twrp of irc.freenode.net.
+# However, it was not compatible with vanilla TWRP, so https://github.com/Tasssadar rewrote
+# most of it and it now has very little in common with the original script.
+
+class Reference():
+    def __init__(self, val):
+        self.__value = val
+
+    def get(self):
+        return self.__value
+
+    def set(self, val):
+        self.__value = val
+
+quiet = Reference(False)
+
+def log(text):
+    if not quiet.get():
+        sys.stdout.write(text)
+
+def write_data(f, width, height, offsets, data):
+    f.write(pack("<I", width))
+    f.write(pack("<I", height))
+    for off in offsets:
+        f.write(pack("<I", off))
+    f.write(data)
+
+if __name__ == "__main__":
+    fontsize = Reference(20)
+    out_fname = Reference("font.dat")
+    voffset = Reference(None)
+    padding = Reference(0)
+    font_fname = Reference(None)
+    preview = Reference(None)
+
+    arg_parser = [
+        ["-s", "--size=", fontsize, int],
+        ["-o", "--output=", out_fname, str],
+        ["-p", "--preview=", preview, str],
+        [None, "--padding=", padding, int],
+        ["-q", "--quiet", quiet, None],
+        [None, "--voffset=", voffset, int]
+    ]
+
+    argv = sys.argv
+    argc = len(argv)
+    i = 1
+    while i < argc:
+        arg = argv[i]
+        arg_next = argv[i+1] if i+1 < argc else None
+
+        if arg == "--help" or arg == "-h":
+            print ("This script converts TrueTypeFonts to .dat file for TWRP recovery.\n\n"
+                "Usage: %s [SWITCHES] [TRUETYPE FILE]\n\n"
+                "  -h, --help                   - print help\n"
+                "  -o, --output=[FILE]          - output file or '-' for stdout (default: font.dat)\n"
+                "  -p, --preview=[FILE]         - generate font preview to png file\n"
+                "  --padding=[PIXELS]           - horizontal padding around each character (default: 0)\n"
+                "  -q, --quiet                  - Do not print any output\n"
+                "  -s, --size=[SIZE IN PIXELS]  - specify font size in points (default: 20)\n"
+                "  --voffset=[PIXELS]           - vertical offset (default: font size*0.25)\n\n"
+                "Example:\n"
+                "  %s -s 40 -o ComicSans_40.dat -p preview.png ComicSans.ttf\n") % (
+                    sys.argv[0], sys.argv[0]
+                )
+            exit(0)
+
+        found = False
+        for p in arg_parser:
+            if p[0] and arg == p[0] and (arg_next or not p[3]):
+                if p[3]:
+                    p[2].set(p[3](arg_next))
+                else:
+                    p[2].set(True)
+                i += 1
+                found = True
+                break
+            elif p[1] and arg.startswith(p[1]):
+                if p[3]:
+                    p[2].set(p[3](arg[len(p[1]):]))
+                else:
+                    p[2].set(True)
+                found = True
+                break
+
+        if not found:
+            font_fname.set(arg)
+
+        i += 1
+
+    if not voffset.get():
+        voffset.set(int(fontsize.get()*0.25))
+
+    if out_fname.get() == "-":
+        quiet.set(True)
+
+    log("Loading font %s...\n" % font_fname.get())
+    font = ImageFont.truetype(font_fname.get(), fontsize.get(), 0, "utf-32be")
+    cwidth = 0
+    cheight = font.getsize('A')[1]
+    offsets = []
+    renders = []
+    data = bytes()
+
+    # temp Image and ImageDraw to get access to textsize
+    res = Image.new('L', (1, 1), 0)
+    res_draw = ImageDraw.Draw(res)
+
+    # Measure each character and render it to separate Image
+    log("Rendering characters...\n")
+    for i in range(32, 128):
+        w, h = res_draw.textsize(chr(i), font)
+        w += padding.get()*2
+        offsets.append(cwidth)
+        cwidth += w
+        if h > cheight:
+            cheight = h
+        ichr = Image.new('L', (w, cheight*2))
+        ichr_draw = ImageDraw.Draw(ichr)
+        ichr_draw.text((padding.get(), 0), chr(i), 255, font)
+        renders.append(ichr)
+
+    # Twice the height to account for under-the-baseline characters
+    cheight *= 2
+
+    # Create the result bitmap
+    log("Creating result bitmap...\n")
+    res = Image.new('L', (cwidth, cheight), 0)
+    res_draw = ImageDraw.Draw(res)
+
+    # Paste all characters into result bitmap
+    for i in range(len(renders)):
+        res.paste(renders[i], (offsets[i], 0))
+        # uncomment to draw lines separating each character (for debug)
+        #res_draw.rectangle([offsets[i], 0, offsets[i], cheight], outline="blue")
+
+    # crop the blank areas on top and bottom
+    (_, start_y, _, end_y) = res.getbbox()
+    res = res.crop((0, start_y, cwidth, end_y))
+    cheight = (end_y - start_y) + voffset.get()
+    new_res = Image.new('L', (cwidth, cheight))
+    new_res.paste(res, (0, voffset.get()))
+    res = new_res
+
+    # save the preview
+    if preview.get():
+        log("Saving preview to %s...\n" % preview.get())
+        res.save(preview.get())
+
+    # Pack the data.
+    # The "data" is a B/W bitmap with all 96 characters next to each other
+    # on one line. It is as wide as all the characters combined and as
+    # high as the tallest character, plus padding.
+    # Each byte contains info about eight pixels, starting from
+    # highest to lowest bit:
+    # bits:   | 7  6  5  4  3  2  1  0 | 15 14 13 12 11 10 9  8  | ...
+    # pixels: | 0  1  2  3  4  5  6  7 | 8  9  10 11 12 13 14 15 | ...
+    log("Packing data...\n")
+    bit = 0
+    bit_itr = 0
+    for c in res.tostring():
+        # FIXME: How to handle antialiasing?
+        # if c != '\x00':
+        # In Python3, c is int, in Python2, c is string. Because of reasons.
+        try:
+            fill = (ord(c) >= 127)
+        except TypeError:
+            fill = (c >= 127)
+        if fill:
+            bit |= (1 << (7-bit_itr))
+        bit_itr += 1
+        if bit_itr >= 8:
+            data += pack("<B", bit)
+            bit_itr = 0
+            bit = 0
+
+    # Write them to the file.
+    # Format:
+    # 000: width
+    # 004: height
+    # 008: offsets of each characters (96*uint32)
+    # 392: data as described above
+    log("Writing to %s...\n" % out_fname.get())
+    if out_fname.get() == "-":
+        write_data(sys.stdout, cwidth, cheight, offsets, data)
+    else:
+        with open(out_fname.get(), 'wb') as f:
+            write_data(f, cwidth, cheight, offsets, data)
+
+    exit(0)