1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-12 15:42:40 +00:00

feat(core): add arg to gen_font.py to gen C data

- usage with `--gen-c`
- default is now Rust

[no changelog]
This commit is contained in:
obrusvit 2025-01-22 14:32:48 +01:00 committed by Vít Obrusník
parent 5bef8574b0
commit 8c52f0d5e8
3 changed files with 239 additions and 26 deletions

View File

@ -4,8 +4,10 @@ set -e
CWD=`dirname "$0"` CWD=`dirname "$0"`
RENDER="$CWD/../vendor/trezor-common/tools/cointool.py render -M T1B1" RENDER="$CWD/../vendor/trezor-common/tools/cointool.py render -M T1B1"
# Search both in `core/src` and `core/embed` - Solana templates are excluded since those are handled separately # Search both in `core/src` and `core/embed`
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto* -not -path *solana*" # - Solana templates are excluded since those are handled separately
# - `gen_font.mako` is excluded as it's used from `gen_font.py`
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto* -not -path *solana* -not -name gen_font.mako"
check_results() { check_results() {
CHECK_FAIL=0 CHECK_FAIL=0

View File

@ -0,0 +1,59 @@
//! This file is generated by core/tools/codegen/gen_font.py
#![cfg_attr(any(), rustfmt::skip)]
#![allow(non_upper_case_globals)]
// Each glyph:
// - first two bytes: width, height
// - next three bytes: advance, bearingX, bearingY
// - rest is packed ${bpp}-bit glyph data
use crate::ui::display::font::FontInfo;
% for g in glyphs:
/// '${g["char"]}' (ASCII ${g["ascii"]})
const ${g["var_name"]}: [u8; ${g["arr_len"]}] = [ ${g["arr_content"]} ];
% endfor
/// Nonprintable glyph (inverse colors of '?')
const ${nonprintable["var_name"]}: [u8; ${nonprintable["arr_len"]}] = [ ${nonprintable["arr_content"]} ];
% if gen_normal:
/// Array of references for '${name}' normal ASCII glyphs
const Font_${name}: [&[u8]; ${len(glyph_array)}] = [
% for ref in glyph_array:
${ref},
% endfor
];
% endif
% if gen_upper:
/// Array of references for '${name}' ASCII glyphs (forced uppercase)
const Font_${name}_upper: [&[u8]; ${len(glyph_array_upper)}] = [
% for ref in glyph_array_upper:
${ref},
% endfor
];
% endif
% if gen_normal:
/// FontInfo struct for normal ASCII usage
pub const Font_${name}_info: FontInfo = FontInfo {
translation_blob_idx: ${font_info["translation_blob_idx"]},
height: ${font_info["height"]},
max_height: ${font_info["max_height"]},
baseline: ${font_info["baseline"]},
glyph_data: &${font_info["glyph_array"]},
glyph_nonprintable: &${font_info["nonprintable"]},
};
% endif
% if gen_upper:
/// FontInfo struct for forced uppercase usage
pub const Font_${name}_upper_info: FontInfo = FontInfo {
translation_blob_idx: ${font_info_upper["translation_blob_idx"]},
height: ${font_info_upper["height"]},
max_height: ${font_info_upper["max_height"]},
baseline: ${font_info_upper["baseline"]},
glyph_data: &${font_info_upper["glyph_array"]},
glyph_nonprintable: &${font_info_upper["nonprintable"]},
};
% endif

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# script used to generate core/embed/gfx/fonts/font_*_*.c # script used to generate
# 1) core/embed/gfx/fonts/font_*_*.c for `prodtest` and `bootloader_ci`
# 2) rust/src/ui/layout_*/fonts/font_*_*.rs for `bootloader` and `firmware`
from __future__ import annotations from __future__ import annotations
@ -13,6 +15,7 @@ import json
# pip install freetype-py # pip install freetype-py
import freetype import freetype
from mako.template import Template
from foreign_chars import all_languages from foreign_chars import all_languages
@ -24,9 +27,12 @@ def _normalize(s: str) -> str:
HERE = Path(__file__).parent HERE = Path(__file__).parent
CORE_ROOT = HERE.parent.parent CORE_ROOT = HERE.parent.parent
FONTS_DIR = HERE / "fonts" FONTS_DIR = HERE / "fonts"
RUST_MAKO_TMPL = HERE / "gen_font.mako"
OUT_DIR = HERE / ".." / ".." / "embed" / "gfx" / "fonts" OUT_DIR = HERE / ".." / ".." / "embed" / "gfx" / "fonts"
C_FONTS_DEST = CORE_ROOT / "embed" / "gfx" / "fonts" C_FONTS_DEST = CORE_ROOT / "embed" / "gfx" / "fonts"
JSON_FONTS_DEST = CORE_ROOT / "translations" / "fonts" JSON_FONTS_DEST = CORE_ROOT / "translations" / "fonts"
RUST_FONTS_DEST = CORE_ROOT / "embed" / "rust" / "src" / "ui"
LAYOUT_NAME = ""
MIN_GLYPH = ord(" ") MIN_GLYPH = ord(" ")
MAX_GLYPH = ord("~") MAX_GLYPH = ord("~")
@ -182,6 +188,7 @@ class Glyph:
print(f"normalizing bearingY {bearingY} for '{c}'") print(f"normalizing bearingY {bearingY} for '{c}'")
bearingY = 0 bearingY = 0
assert bearingY >= 0 and bearingY <= 255 assert bearingY >= 0 and bearingY <= 255
buf = list(bitmap.buffer) buf = list(bitmap.buffer)
# discard non-space pixels on the left side # discard non-space pixels on the left side
if remove_left > 0 and width > 0: if remove_left > 0 and width > 0:
@ -275,6 +282,7 @@ class FaceProcessor:
self.ext = ext self.ext = ext
self.gen_normal = gen_normal self.gen_normal = gen_normal
self.gen_upper = gen_upper self.gen_upper = gen_upper
self.face = freetype.Face(str(FONTS_DIR / f"{name}-{style}.{ext}")) self.face = freetype.Face(str(FONTS_DIR / f"{name}-{style}.{ext}"))
self.face.set_pixel_sizes(0, size) # type: ignore self.face.set_pixel_sizes(0, size) # type: ignore
self.fontname = f"{name.lower()}_{style.lower()}_{size}" self.fontname = f"{name.lower()}_{style.lower()}_{size}"
@ -293,14 +301,27 @@ class FaceProcessor:
def _h_file_name(self) -> Path: def _h_file_name(self) -> Path:
return C_FONTS_DEST / f"font_{self.fontname}.h" return C_FONTS_DEST / f"font_{self.fontname}.h"
def write_files(self) -> None: @property
self.write_c_files() def _rs_file_name(self) -> Path:
return (
RUST_FONTS_DEST
/ f"layout_{LAYOUT_NAME}"
/ "fonts"
/ f"font_{self.fontname}.rs"
)
def write_files(self, gen_c: bool = False) -> None:
# C files:
if gen_c:
self.write_c_files()
# JSON files:
if self.gen_normal: if self.gen_normal:
self.write_foreign_json(upper_cased=False) self.write_foreign_json(upper_cased=False)
if self.gen_upper: if self.gen_upper:
self.write_foreign_json(upper_cased=True) self.write_foreign_json(upper_cased=True)
if WRITE_WIDTHS: if WRITE_WIDTHS:
self.write_char_widths_files() self.write_char_widths_files()
self.write_rust_file()
def write_c_files(self) -> None: def write_c_files(self) -> None:
self._write_c_file() self._write_c_file()
@ -468,19 +489,138 @@ class FaceProcessor:
f"extern const font_info_t Font_{self._name_style_size}_upper_info;\n" f"extern const font_info_t Font_{self._name_style_size}_upper_info;\n"
) )
# --------------------------------------------------------------------
# Rust code generation
# --------------------------------------------------------------------
def write_rust_file(self) -> None:
"""
Write a Rust source file using a Mako template.
"""
# Build a dict with all data needed by the template.
# 1) Gather ASCII glyph definitions.
glyphs = []
for i in range(MIN_GLYPH, MAX_GLYPH + 1):
c = chr(i)
if c.islower() and not self.gen_normal:
continue
self._load_char(c)
glyph = Glyph.from_face(self.face, c, self.shaveX)
arr_bytes = glyph.to_bytes(self.bpp)
glyphs.append(
{
"ascii": i,
"char": glyph.char,
"var_name": f"Font_{self._name_style_size}_glyph_{i}",
"arr_len": len(arr_bytes),
"arr_content": ", ".join(str(n) for n in arr_bytes),
}
)
def gen_layout_bolt(): # 2) Nonprintable glyph.
FaceProcessor("Roboto", "Regular", 20).write_files() self._load_char("?")
FaceProcessor("Roboto", "Bold", 20).write_files() glyph_np = Glyph.from_face(self.face, "?", self.shaveX, inverse_colors=True)
FaceProcessor("TTHoves", "Regular", 21, ext="otf").write_files() arr_bytes_np = glyph_np.to_bytes(self.bpp)
FaceProcessor("TTHoves", "DemiBold", 21, ext="otf").write_files() nonprintable = {
"var_name": f"Font_{self._name_style_size}_glyph_nonprintable",
"arr_len": len(arr_bytes_np),
"arr_content": ", ".join(str(n) for n in arr_bytes_np),
}
# 3) Build arrays of glyph references.
glyph_array = []
glyph_array_upper = []
if self.gen_normal:
glyph_array = [
f"&Font_{self._name_style_size}_glyph_{i}"
for i in range(MIN_GLYPH, MAX_GLYPH + 1)
]
if self.gen_upper:
for i in range(MIN_GLYPH, MAX_GLYPH + 1):
if chr(i).islower():
c_to = chr(i).upper()
i_mapped = ord(c_to)
glyph_array_upper.append(
f"&Font_{self._name_style_size}_glyph_{i_mapped}, // {chr(i)} -> {c_to}"
)
else:
glyph_array_upper.append(f"&Font_{self._name_style_size}_glyph_{i}")
# 4) Recompute font_ymin and font_ymax.
self.font_ymin = 0
self.font_ymax = 0
for i in range(MIN_GLYPH, MAX_GLYPH + 1):
c = chr(i)
if c.islower() and not self.gen_normal:
continue
self._load_char(c)
glyph = Glyph.from_face(self.face, c, self.shaveX)
yMin = glyph.bearingY - glyph.rows
yMax = yMin + glyph.rows
self.font_ymin = min(self.font_ymin, yMin)
self.font_ymax = max(self.font_ymax, yMax)
# 5) Build FontInfo definitions.
font_info = None
font_info_upper = None
if self.gen_normal:
font_info = {
"variant": "normal",
"height": self.size,
"max_height": self.font_ymax - self.font_ymin,
"baseline": -self.font_ymin,
"glyph_array": f"Font_{self._name_style_size}",
"nonprintable": f"Font_{self._name_style_size}_glyph_nonprintable",
}
if self.gen_upper:
font_info_upper = {
"variant": "upper",
"height": self.size,
"max_height": self.font_ymax - self.font_ymin,
"baseline": -self.font_ymin,
"glyph_array": f"Font_{self._name_style_size}_upper",
"nonprintable": f"Font_{self._name_style_size}_glyph_nonprintable",
}
data = {
"bpp": self.bpp,
"name": self._name_style_size,
"glyphs": glyphs,
"nonprintable": nonprintable,
"glyph_array": glyph_array,
"glyph_array_upper": glyph_array_upper,
"gen_normal": self.gen_normal,
"gen_upper": self.gen_upper,
"font_info": font_info,
"font_info_upper": font_info_upper,
}
# Load the Mako template from the same directory.
with open(RUST_MAKO_TMPL, "r") as f:
template_content = f.read()
template = Template(template_content)
rendered = template.render(**data)
# Write the rendered template into the Rust file.
with open(self._rs_file_name, "wt") as f:
f.write(rendered)
def gen_layout_bolt(gen_c: bool = False):
global LAYOUT_NAME
LAYOUT_NAME = "bolt"
FaceProcessor("Roboto", "Regular", 20).write_files(gen_c)
FaceProcessor("Roboto", "Bold", 20).write_files(gen_c)
FaceProcessor("TTHoves", "Regular", 21, ext="otf").write_files(gen_c)
FaceProcessor("TTHoves", "DemiBold", 21, ext="otf").write_files(gen_c)
FaceProcessor( FaceProcessor(
"TTHoves", "Bold", 17, ext="otf", gen_normal=False, gen_upper=True "TTHoves", "Bold", 17, ext="otf", gen_normal=False, gen_upper=True
).write_files() ).write_files(gen_c)
FaceProcessor("RobotoMono", "Medium", 20).write_files() FaceProcessor("RobotoMono", "Medium", 20).write_files(gen_c)
def gen_layout_caesar(): def gen_layout_caesar(gen_c: bool = False):
global LAYOUT_NAME
LAYOUT_NAME = "caesar"
FaceProcessor( FaceProcessor(
"PixelOperator", "PixelOperator",
"Regular", "Regular",
@ -489,7 +629,7 @@ def gen_layout_caesar():
shaveX=1, shaveX=1,
gen_normal=True, gen_normal=True,
gen_upper=True, gen_upper=True,
).write_files() ).write_files(gen_c)
FaceProcessor( FaceProcessor(
"PixelOperator", "PixelOperator",
"Bold", "Bold",
@ -498,18 +638,23 @@ def gen_layout_caesar():
shaveX=1, shaveX=1,
gen_normal=True, gen_normal=True,
gen_upper=True, gen_upper=True,
).write_files() ).write_files(gen_c)
FaceProcessor("PixelOperatorMono", "Regular", 8, bpp=1, shaveX=1).write_files() FaceProcessor("PixelOperatorMono", "Regular", 8, bpp=1, shaveX=1).write_files(gen_c)
FaceProcessor("Unifont", "Regular", 16, bpp=1, shaveX=1, ext="otf").write_files() FaceProcessor("Unifont", "Regular", 16, bpp=1, shaveX=1, ext="otf").write_files(
gen_c
)
# NOTE: Unifont Bold does not seem to have czech characters # NOTE: Unifont Bold does not seem to have czech characters
FaceProcessor("Unifont", "Bold", 16, bpp=1, shaveX=1, ext="otf").write_files() FaceProcessor("Unifont", "Bold", 16, bpp=1, shaveX=1, ext="otf").write_files(gen_c)
def gen_layout_delizia(): def gen_layout_delizia(gen_c: bool = False):
FaceProcessor("TTSatoshi", "DemiBold", 42, ext="otf").write_files() global LAYOUT_NAME
FaceProcessor("TTSatoshi", "DemiBold", 21, ext="otf").write_files() LAYOUT_NAME = "delizia"
FaceProcessor("TTSatoshi", "DemiBold", 18, ext="otf").write_files() FaceProcessor("TTSatoshi", "DemiBold", 42, ext="otf").write_files(gen_c)
FaceProcessor("RobotoMono", "Medium", 21).write_files() FaceProcessor("TTSatoshi", "DemiBold", 21, ext="otf").write_files(gen_c)
FaceProcessor("TTSatoshi", "DemiBold", 18, ext="otf").write_files(gen_c)
FaceProcessor("RobotoMono", "Medium", 21).write_files(gen_c)
LAYOUTS = { LAYOUTS = {
"bolt": gen_layout_bolt, "bolt": gen_layout_bolt,
@ -517,6 +662,7 @@ LAYOUTS = {
"delizia": gen_layout_delizia, "delizia": gen_layout_delizia,
} }
@click.command() @click.command()
@click.option( @click.option(
"--layout", "--layout",
@ -531,19 +677,25 @@ LAYOUTS = {
default=False, default=False,
help="Generate character width files", help="Generate character width files",
) )
def main(layout: str | None, write_widths: bool): @click.option(
"--gen-c",
is_flag=True,
default=False,
help="Also generate C font files",
)
def main(layout: str | None, write_widths: bool, gen_c: bool):
"""Generate font files for Trezor firmware.""" """Generate font files for Trezor firmware."""
global WRITE_WIDTHS global WRITE_WIDTHS
WRITE_WIDTHS = write_widths WRITE_WIDTHS = write_widths
if layout: if layout:
click.echo(f"Generating fonts for layout: {layout}") click.echo(f"Generating fonts for layout: {layout}")
LAYOUTS[layout]() LAYOUTS[layout](gen_c)
else: else:
click.echo("Generating all fonts") click.echo("Generating all fonts")
for layout_name, layout_func in LAYOUTS.items(): for layout_name, layout_func in LAYOUTS.items():
click.echo(f"\nGenerating {layout_name} layout:") click.echo(f"\nGenerating {layout_name} layout:")
layout_func() layout_func(gen_c)
if __name__ == "__main__": if __name__ == "__main__":