2016-03-26 22:46:18 +00:00
|
|
|
#!/usr/bin/env python3
|
2017-10-01 17:52:43 +00:00
|
|
|
|
|
|
|
# script used to generate /embed/extmod/modtrezorui/font_*_*.c
|
|
|
|
|
2023-05-04 12:28:20 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from pathlib import Path
|
2017-10-01 17:52:43 +00:00
|
|
|
|
2016-03-26 22:46:18 +00:00
|
|
|
import freetype
|
|
|
|
|
2023-05-04 12:28:20 +00:00
|
|
|
HERE = Path(__file__).parent
|
|
|
|
FONTS_DIR = HERE / "fonts"
|
2023-12-12 12:34:33 +00:00
|
|
|
OUT_DIR = HERE / ".." / ".." / "embed" / "lib" / "fonts"
|
2023-05-04 12:28:20 +00:00
|
|
|
|
2018-07-31 09:35:09 +00:00
|
|
|
MIN_GLYPH = ord(" ")
|
|
|
|
MAX_GLYPH = ord("~")
|
2016-03-26 22:46:18 +00:00
|
|
|
|
2016-10-18 13:05:55 +00:00
|
|
|
# metrics explanation: https://www.freetype.org/freetype2/docs/glyphs/metrics.png
|
|
|
|
|
2017-06-13 14:50:03 +00:00
|
|
|
|
2023-12-12 12:34:33 +00:00
|
|
|
def process_bitmap_buffer(buf: list[int], bpp: int, width: int, height: int) -> list[int]:
|
2020-07-17 14:56:37 +00:00
|
|
|
res = buf[:]
|
2020-07-27 16:33:05 +00:00
|
|
|
if bpp == 1:
|
|
|
|
for _ in range(8 - len(res) % 8):
|
|
|
|
res.append(0)
|
|
|
|
res = [
|
|
|
|
(
|
|
|
|
(a & 0x80)
|
|
|
|
| ((b & 0x80) >> 1)
|
|
|
|
| ((c & 0x80) >> 2)
|
|
|
|
| ((d & 0x80) >> 3)
|
|
|
|
| ((e & 0x80) >> 4)
|
|
|
|
| ((f & 0x80) >> 5)
|
|
|
|
| ((g & 0x80) >> 6)
|
|
|
|
| ((h & 0x80) >> 7)
|
|
|
|
)
|
|
|
|
for a, b, c, d, e, f, g, h in [
|
|
|
|
res[i : i + 8] for i in range(0, len(res), 8)
|
|
|
|
]
|
|
|
|
]
|
|
|
|
elif bpp == 2:
|
2020-07-17 14:56:37 +00:00
|
|
|
for _ in range(4 - len(res) % 4):
|
|
|
|
res.append(0)
|
|
|
|
res = [
|
|
|
|
((a & 0xC0) | ((b & 0xC0) >> 2) | ((c & 0xC0) >> 4) | ((d & 0xC0) >> 6))
|
|
|
|
for a, b, c, d in [res[i : i + 4] for i in range(0, len(res), 4)]
|
|
|
|
]
|
2020-07-27 16:33:05 +00:00
|
|
|
elif bpp == 4:
|
2023-12-12 12:34:33 +00:00
|
|
|
res = []
|
|
|
|
for y in range(0, height):
|
|
|
|
row = buf[y * width: (y+1) * width]
|
|
|
|
for a, b in zip(row[::2], row[1::2]):
|
|
|
|
res.append(((b & 0xF0) | (a >> 4)))
|
|
|
|
if width & 1 != 0:
|
|
|
|
res.append(row[-1] >> 4)
|
2020-07-27 16:33:05 +00:00
|
|
|
elif bpp == 8:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise ValueError
|
2020-07-17 14:56:37 +00:00
|
|
|
return res
|
|
|
|
|
|
|
|
|
2023-05-04 12:28:20 +00:00
|
|
|
def drop_left_columns(buf: list[int], width: int, drop: int) -> list[int]:
|
|
|
|
res: list[int] = []
|
2023-03-02 09:38:04 +00:00
|
|
|
for i in range(len(buf)):
|
|
|
|
if i % width >= drop:
|
|
|
|
res.append(buf[i])
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2023-05-04 12:28:20 +00:00
|
|
|
def process_face(
|
|
|
|
name: str,
|
|
|
|
style: str,
|
|
|
|
size: int,
|
|
|
|
bpp: int = 4,
|
|
|
|
shaveX: int = 0,
|
|
|
|
ext: str = "ttf",
|
|
|
|
) -> None:
|
2018-07-31 09:35:09 +00:00
|
|
|
print("Processing ... %s %s %s" % (name, style, size))
|
2023-05-04 12:28:20 +00:00
|
|
|
file_name = FONTS_DIR / f"{name}-{style}.{ext}"
|
|
|
|
face = freetype.Face(str(file_name))
|
2016-03-26 22:46:18 +00:00
|
|
|
face.set_pixel_sizes(0, size)
|
2018-07-31 09:35:09 +00:00
|
|
|
fontname = "%s_%s_%d" % (name.lower(), style.lower(), size)
|
2022-08-18 13:12:02 +00:00
|
|
|
font_ymin = 0
|
|
|
|
font_ymax = 0
|
|
|
|
|
2023-12-12 12:34:33 +00:00
|
|
|
with open(OUT_DIR / f"font_{fontname}.c", "wt") as f:
|
2020-07-27 16:33:05 +00:00
|
|
|
f.write("#include <stdint.h>\n\n")
|
2018-07-31 09:35:09 +00:00
|
|
|
f.write("// clang-format off\n\n")
|
2020-07-28 08:55:27 +00:00
|
|
|
f.write("// - the first two bytes are width and height of the glyph\n")
|
2023-05-04 12:28:20 +00:00
|
|
|
f.write(
|
|
|
|
"// - the third, fourth and fifth bytes are advance, bearingX and bearingY of the horizontal metrics of the glyph\n"
|
|
|
|
)
|
2020-07-28 08:55:27 +00:00
|
|
|
f.write("// - the rest is packed %d-bit glyph data\n\n" % bpp)
|
2016-03-26 22:46:18 +00:00
|
|
|
for i in range(MIN_GLYPH, MAX_GLYPH + 1):
|
|
|
|
c = chr(i)
|
2018-02-08 14:04:33 +00:00
|
|
|
face.load_char(c, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_NORMAL)
|
2016-03-26 22:46:18 +00:00
|
|
|
bitmap = face.glyph.bitmap
|
|
|
|
metrics = face.glyph.metrics
|
2018-02-08 14:04:33 +00:00
|
|
|
assert metrics.width // 64 == bitmap.width
|
|
|
|
assert metrics.height // 64 == bitmap.rows
|
|
|
|
assert metrics.width % 64 == 0
|
|
|
|
assert metrics.height % 64 == 0
|
|
|
|
assert metrics.horiAdvance % 64 == 0
|
|
|
|
assert metrics.horiBearingX % 64 == 0
|
|
|
|
assert metrics.horiBearingY % 64 == 0
|
2017-06-13 14:50:03 +00:00
|
|
|
assert bitmap.width == bitmap.pitch
|
|
|
|
assert len(bitmap.buffer) == bitmap.pitch * bitmap.rows
|
2018-02-07 17:38:29 +00:00
|
|
|
width = bitmap.width
|
|
|
|
rows = bitmap.rows
|
2018-02-08 14:04:33 +00:00
|
|
|
advance = metrics.horiAdvance // 64
|
|
|
|
bearingX = metrics.horiBearingX // 64
|
2022-08-18 13:12:02 +00:00
|
|
|
|
2023-03-02 09:38:04 +00:00
|
|
|
remove_left = shaveX
|
2020-08-17 15:22:49 +00:00
|
|
|
# discard space on the left side
|
2023-03-02 09:38:04 +00:00
|
|
|
if shaveX > 0:
|
|
|
|
diff = min(advance, bearingX, shaveX)
|
|
|
|
advance -= diff
|
|
|
|
bearingX -= diff
|
|
|
|
remove_left -= diff
|
2018-02-08 14:04:33 +00:00
|
|
|
# the following code is here just for some letters (listed below)
|
|
|
|
# not using negative bearingX makes life so much easier; add it to advance instead
|
2021-12-16 19:37:21 +00:00
|
|
|
if bearingX < 0:
|
|
|
|
if c in "AXYjxy}),/_":
|
|
|
|
advance += -bearingX
|
|
|
|
bearingX = 0
|
|
|
|
else:
|
|
|
|
raise ValueError("Negative bearingX for character '%s'" % c)
|
2018-02-08 14:04:33 +00:00
|
|
|
bearingY = metrics.horiBearingY // 64
|
|
|
|
assert advance >= 0 and advance <= 255
|
2018-02-07 17:38:29 +00:00
|
|
|
assert bearingX >= 0 and bearingX <= 255
|
2023-05-04 12:28:20 +00:00
|
|
|
if bearingY < 0: # HACK
|
2021-12-16 19:37:21 +00:00
|
|
|
print("normalizing bearingY %d for '%s'" % (bearingY, c))
|
|
|
|
bearingY = 0
|
2018-02-07 17:38:29 +00:00
|
|
|
assert bearingY >= 0 and bearingY <= 255
|
2023-03-02 09:38:04 +00:00
|
|
|
buf = list(bitmap.buffer)
|
|
|
|
# discard non-space pixels on the left side
|
|
|
|
if remove_left > 0 and width > 0:
|
|
|
|
assert bearingX == 0
|
|
|
|
buf = drop_left_columns(buf, width, remove_left)
|
|
|
|
assert width > remove_left
|
|
|
|
width -= remove_left
|
|
|
|
assert advance > remove_left
|
|
|
|
advance -= remove_left
|
2023-05-04 12:28:20 +00:00
|
|
|
print(
|
|
|
|
'Glyph "%c": removed %d pixel columns from the left'
|
|
|
|
% (c, remove_left)
|
|
|
|
)
|
2018-07-31 09:35:09 +00:00
|
|
|
print(
|
|
|
|
'Loaded glyph "%c" ... %d x %d @ %d grays (%d bytes, metrics: %d, %d, %d)'
|
|
|
|
% (
|
|
|
|
c,
|
|
|
|
bitmap.width,
|
|
|
|
bitmap.rows,
|
|
|
|
bitmap.num_grays,
|
|
|
|
len(bitmap.buffer),
|
|
|
|
advance,
|
|
|
|
bearingX,
|
|
|
|
bearingY,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
f.write(
|
|
|
|
"/* %c */ static const uint8_t Font_%s_%s_%d_glyph_%d[] = { %d, %d, %d, %d, %d"
|
|
|
|
% (c, name, style, size, i, width, rows, advance, bearingX, bearingY)
|
|
|
|
)
|
2016-03-27 21:30:35 +00:00
|
|
|
if len(buf) > 0:
|
2020-07-17 14:56:37 +00:00
|
|
|
f.write(
|
2020-07-27 16:33:05 +00:00
|
|
|
", "
|
2023-12-12 12:34:33 +00:00
|
|
|
+ ", ".join(["%d" % x for x in process_bitmap_buffer(buf, bpp, width, rows)])
|
2020-07-17 14:56:37 +00:00
|
|
|
)
|
2018-07-31 09:35:09 +00:00
|
|
|
f.write(" };\n")
|
2020-07-17 14:56:37 +00:00
|
|
|
|
|
|
|
if i == ord("?"):
|
|
|
|
nonprintable = (
|
|
|
|
"\nconst uint8_t Font_%s_%s_%d_glyph_nonprintable[] = { %d, %d, %d, %d, %d"
|
|
|
|
% (name, style, size, width, rows, advance, bearingX, bearingY)
|
|
|
|
)
|
|
|
|
nonprintable += ", " + ", ".join(
|
2023-12-12 12:34:33 +00:00
|
|
|
["%d" % (x ^ 0xFF) for x in process_bitmap_buffer(buf, bpp, width, rows)]
|
2020-07-17 14:56:37 +00:00
|
|
|
)
|
|
|
|
nonprintable += " };\n"
|
|
|
|
|
2022-08-18 13:12:02 +00:00
|
|
|
yMin = bearingY - rows
|
|
|
|
yMax = yMin + rows
|
|
|
|
font_ymin = min(font_ymin, yMin)
|
|
|
|
font_ymax = max(font_ymax, yMax)
|
|
|
|
|
2020-07-17 14:56:37 +00:00
|
|
|
f.write(nonprintable)
|
|
|
|
|
2018-07-31 09:35:09 +00:00
|
|
|
f.write(
|
|
|
|
"\nconst uint8_t * const Font_%s_%s_%d[%d + 1 - %d] = {\n"
|
|
|
|
% (name, style, size, MAX_GLYPH, MIN_GLYPH)
|
|
|
|
)
|
2016-03-26 22:46:18 +00:00
|
|
|
for i in range(MIN_GLYPH, MAX_GLYPH + 1):
|
2018-07-31 09:35:09 +00:00
|
|
|
f.write(" Font_%s_%s_%d_glyph_%d,\n" % (name, style, size, i))
|
|
|
|
f.write("};\n")
|
2016-03-26 22:46:18 +00:00
|
|
|
|
2023-12-12 12:34:33 +00:00
|
|
|
with open(OUT_DIR / f"font_{fontname}.h", "wt") as f:
|
2022-08-18 13:12:02 +00:00
|
|
|
f.write("#include <stdint.h>\n\n")
|
|
|
|
f.write("#if TREZOR_FONT_BPP != %d\n" % bpp)
|
|
|
|
f.write("#error Wrong TREZOR_FONT_BPP (expected %d)\n" % bpp)
|
|
|
|
f.write("#endif\n")
|
|
|
|
|
|
|
|
f.write("#define Font_%s_%s_%d_HEIGHT %d\n" % (name, style, size, size))
|
2023-05-04 12:28:20 +00:00
|
|
|
f.write(
|
|
|
|
"#define Font_%s_%s_%d_MAX_HEIGHT %d\n"
|
|
|
|
% (name, style, size, font_ymax - font_ymin)
|
|
|
|
)
|
2022-08-18 13:12:02 +00:00
|
|
|
f.write("#define Font_%s_%s_%d_BASELINE %d\n" % (name, style, size, -font_ymin))
|
|
|
|
f.write(
|
|
|
|
"extern const uint8_t* const Font_%s_%s_%d[%d + 1 - %d];\n"
|
|
|
|
% (name, style, size, MAX_GLYPH, MIN_GLYPH)
|
|
|
|
)
|
|
|
|
f.write(
|
|
|
|
"extern const uint8_t Font_%s_%s_%d_glyph_nonprintable[];\n"
|
|
|
|
% (name, style, size)
|
|
|
|
)
|
|
|
|
|
2017-09-05 21:15:47 +00:00
|
|
|
|
2020-07-27 16:33:05 +00:00
|
|
|
process_face("Roboto", "Regular", 20)
|
|
|
|
process_face("Roboto", "Bold", 20)
|
2021-12-16 19:37:21 +00:00
|
|
|
|
2023-03-02 09:38:04 +00:00
|
|
|
process_face("TTHoves", "Regular", 21, ext="otf")
|
|
|
|
process_face("TTHoves", "DemiBold", 21, ext="otf")
|
|
|
|
process_face("TTHoves", "Bold", 17, ext="otf")
|
2023-04-03 10:03:39 +00:00
|
|
|
process_face("RobotoMono", "Medium", 20)
|
2020-07-27 16:33:05 +00:00
|
|
|
|
2023-03-02 09:38:04 +00:00
|
|
|
process_face("PixelOperator", "Regular", 8, bpp=1, shaveX=1)
|
|
|
|
process_face("PixelOperator", "Bold", 8, bpp=1, shaveX=1)
|
|
|
|
process_face("PixelOperatorMono", "Regular", 8, bpp=1, shaveX=1)
|
2023-05-12 09:19:35 +00:00
|
|
|
|
|
|
|
# For model R
|
|
|
|
process_face("Unifont", "Regular", 16, bpp=1, shaveX=1, ext="otf")
|
|
|
|
process_face("Unifont", "Bold", 16, bpp=1, shaveX=1, ext="otf")
|