# Generate embedded 1-bit bitmap fonts from a TrueType/OpenType font.
#
# FontForge's direct monochrome XBM export is too thin for this display, especially
# at footer sizes. This exports antialiased BMP glyphs and packs 4-bit alpha.
#
# Run with FontForge, not python3:
#   fontforge -lang=py -script scripts/generate-fonts.py \
#     '/Volumes/Drawer/Fonts/iAFonts/iA Writer Quattro/Static/iAWriterQuattroS-Regular.ttf' \
#     src/ui/generated_fonts.rs

import os
import struct
import sys
import tempfile

import fontforge

SIZES = [
    ("QUATTRO_SMALL", 24),
    ("QUATTRO_BODY", 30),
    ("QUATTRO_READER", 34),
    ("QUATTRO_TITLE", 38),
]

CODEPOINTS = list(range(32, 127)) + [
    0x00A0,  # no-break space
    0x2018,  # left single quotation mark
    0x2019,  # right single quotation mark
    0x201C,  # left double quotation mark
    0x201D,  # right double quotation mark
    0x2013,  # en dash
    0x2014,  # em dash
    0x2026,  # ellipsis
]



def parse_bmp(path):
    with open(path, "rb") as file:
        data = file.read()

    if data[0:2] != b"BM":
        raise RuntimeError("not a BMP file: {}".format(path))

    pixel_offset = struct.unpack_from("<I", data, 10)[0]
    header_size = struct.unpack_from("<I", data, 14)[0]
    width = struct.unpack_from("<i", data, 18)[0]
    height = struct.unpack_from("<i", data, 22)[0]
    planes = struct.unpack_from("<H", data, 26)[0]
    bits_per_pixel = struct.unpack_from("<H", data, 28)[0]
    compression = struct.unpack_from("<I", data, 30)[0]

    if width <= 0 or height == 0 or planes != 1 or bits_per_pixel != 8 or compression != 0:
        raise RuntimeError("unsupported BMP format: {}".format(path))

    palette_start = 14 + header_size
    palette = []
    for index in range(256):
        offset = palette_start + index * 4
        blue, green, red, _alpha = data[offset : offset + 4]
        palette.append((int(red) + int(green) + int(blue)) // 3)

    top_down = height < 0
    height = abs(height)
    bmp_row_bytes = ((width * bits_per_pixel + 31) // 32) * 4
    packed = [0] * ((width * height + 1) // 2)

    for row in range(height):
        source_row = row if top_down else height - 1 - row
        source_offset = pixel_offset + source_row * bmp_row_bytes
        for column in range(width):
            palette_index = data[source_offset + column]
            alpha = (255 - palette[palette_index] + 8) // 17
            alpha = min(15, max(0, alpha))
            pixel_index = row * width + column
            byte_index = pixel_index // 2
            if pixel_index % 2 == 0:
                packed[byte_index] |= alpha << 4
            else:
                packed[byte_index] |= alpha

    return width, height, packed


def has_codepoint(font, codepoint):
    try:
        glyph = font[codepoint]
    except Exception:
        return False

    return glyph is not None and glyph.isWorthOutputting()


def rust_array(values, indent="    ", per_line=16):
    lines = []
    for index in range(0, len(values), per_line):
        chunk = values[index : index + per_line]
        lines.append(indent + ", ".join("0x{:02x}".format(value) for value in chunk) + ",")
    return "\n".join(lines)


def generate_font(font, temp_dir, name, pixelsize):
    glyphs = []
    bitmap = []
    cell_height = 0

    for codepoint in CODEPOINTS:
        if not has_codepoint(font, codepoint):
            continue

        glyph = font[codepoint]
        path = os.path.join(temp_dir, "glyph_{:04x}_{}.bmp".format(codepoint, pixelsize))
        glyph.export(path, pixelsize=pixelsize)
        width, height, data = parse_bmp(path)

        cell_height = max(cell_height, height)

        glyphs.append((codepoint, width, height, len(bitmap)))
        bitmap.extend(data)

    if cell_height == 0:
        raise RuntimeError("no glyphs generated for {}".format(name))

    return {
        "name": name,
        "pixelsize": pixelsize,
        "cell_height": cell_height,
        "glyphs": glyphs,
        "bitmap": bitmap,
    }


def emit_font(output, generated):
    name = generated["name"]
    glyphs = generated["glyphs"]
    bitmap = generated["bitmap"]

    output.write(
        "pub(crate) static {name}: BitmapFont = BitmapFont {{\n"
        "    cell_height: {cell_height},\n"
        "    glyphs: &{name}_GLYPHS,\n"
        "    bitmap: &{name}_BITMAP,\n"
        "}};\n\n".format(name=name, cell_height=generated["cell_height"])
    )

    output.write("#[rustfmt::skip]\n")
    output.write(
        "static {name}_GLYPHS: [Glyph; {count}] = [\n".format(
            name=name, count=len(glyphs)
        )
    )
    for codepoint, width, height, offset in glyphs:
        output.write(
            "    Glyph {{ codepoint: 0x{codepoint:04x}, width: {width}, height: {height}, bitmap_offset: {offset} }},\n".format(
                codepoint=codepoint, width=width, height=height, offset=offset
            )
        )
    output.write("];\n\n")

    output.write("#[rustfmt::skip]\n")
    output.write(
        "static {name}_BITMAP: [u8; {count}] = [\n".format(
            name=name, count=len(bitmap)
        )
    )
    if bitmap:
        output.write(rust_array(bitmap))
        output.write("\n")
    output.write("];\n\n")


def main():
    if len(sys.argv) != 3:
        print("usage: fontforge -lang=py -script scripts/generate-fonts.py FONT.ttf OUTPUT.rs", file=sys.stderr)
        return 2

    font_path = sys.argv[1]
    output_path = sys.argv[2]

    font = fontforge.open(font_path)
    with tempfile.TemporaryDirectory() as temp_dir:
        generated_fonts = [
            generate_font(font, temp_dir, name, pixelsize) for name, pixelsize in SIZES
        ]

    with open(output_path, "w", encoding="utf-8") as output:
        output.write("// @generated by scripts/generate-fonts.py. Do not edit by hand.\n")
        output.write("// Source font: {}\n\n".format(font_path))
        output.write("use crate::ui::typography::{BitmapFont, Glyph};\n\n")
        for generated in generated_fonts:
            output.write(
                "// {} px; cell height {} px; {} glyphs; {} alpha bytes.\n".format(
                    generated["pixelsize"],
                    generated["cell_height"],
                    len(generated["glyphs"]),
                    len(generated["bitmap"]),
                )
            )
            emit_font(output, generated)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
