mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-12 07:32:48 +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:
parent
5bef8574b0
commit
8c52f0d5e8
@ -4,8 +4,10 @@ set -e
|
||||
CWD=`dirname "$0"`
|
||||
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
|
||||
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto* -not -path *solana*"
|
||||
# Search both in `core/src` and `core/embed`
|
||||
# - 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_FAIL=0
|
||||
|
59
core/tools/codegen/gen_font.mako
Normal file
59
core/tools/codegen/gen_font.mako
Normal 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
|
@ -1,6 +1,8 @@
|
||||
#!/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
|
||||
|
||||
@ -13,6 +15,7 @@ import json
|
||||
|
||||
# pip install freetype-py
|
||||
import freetype
|
||||
from mako.template import Template
|
||||
|
||||
from foreign_chars import all_languages
|
||||
|
||||
@ -24,9 +27,12 @@ def _normalize(s: str) -> str:
|
||||
HERE = Path(__file__).parent
|
||||
CORE_ROOT = HERE.parent.parent
|
||||
FONTS_DIR = HERE / "fonts"
|
||||
RUST_MAKO_TMPL = HERE / "gen_font.mako"
|
||||
OUT_DIR = HERE / ".." / ".." / "embed" / "gfx" / "fonts"
|
||||
C_FONTS_DEST = CORE_ROOT / "embed" / "gfx" / "fonts"
|
||||
JSON_FONTS_DEST = CORE_ROOT / "translations" / "fonts"
|
||||
RUST_FONTS_DEST = CORE_ROOT / "embed" / "rust" / "src" / "ui"
|
||||
LAYOUT_NAME = ""
|
||||
|
||||
MIN_GLYPH = ord(" ")
|
||||
MAX_GLYPH = ord("~")
|
||||
@ -182,6 +188,7 @@ class Glyph:
|
||||
print(f"normalizing bearingY {bearingY} for '{c}'")
|
||||
bearingY = 0
|
||||
assert bearingY >= 0 and bearingY <= 255
|
||||
|
||||
buf = list(bitmap.buffer)
|
||||
# discard non-space pixels on the left side
|
||||
if remove_left > 0 and width > 0:
|
||||
@ -275,6 +282,7 @@ class FaceProcessor:
|
||||
self.ext = ext
|
||||
self.gen_normal = gen_normal
|
||||
self.gen_upper = gen_upper
|
||||
|
||||
self.face = freetype.Face(str(FONTS_DIR / f"{name}-{style}.{ext}"))
|
||||
self.face.set_pixel_sizes(0, size) # type: ignore
|
||||
self.fontname = f"{name.lower()}_{style.lower()}_{size}"
|
||||
@ -293,14 +301,27 @@ class FaceProcessor:
|
||||
def _h_file_name(self) -> Path:
|
||||
return C_FONTS_DEST / f"font_{self.fontname}.h"
|
||||
|
||||
def write_files(self) -> None:
|
||||
self.write_c_files()
|
||||
@property
|
||||
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:
|
||||
self.write_foreign_json(upper_cased=False)
|
||||
if self.gen_upper:
|
||||
self.write_foreign_json(upper_cased=True)
|
||||
if WRITE_WIDTHS:
|
||||
self.write_char_widths_files()
|
||||
self.write_rust_file()
|
||||
|
||||
def write_c_files(self) -> None:
|
||||
self._write_c_file()
|
||||
@ -468,19 +489,138 @@ class FaceProcessor:
|
||||
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():
|
||||
FaceProcessor("Roboto", "Regular", 20).write_files()
|
||||
FaceProcessor("Roboto", "Bold", 20).write_files()
|
||||
FaceProcessor("TTHoves", "Regular", 21, ext="otf").write_files()
|
||||
FaceProcessor("TTHoves", "DemiBold", 21, ext="otf").write_files()
|
||||
# 2) Nonprintable glyph.
|
||||
self._load_char("?")
|
||||
glyph_np = Glyph.from_face(self.face, "?", self.shaveX, inverse_colors=True)
|
||||
arr_bytes_np = glyph_np.to_bytes(self.bpp)
|
||||
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(
|
||||
"TTHoves", "Bold", 17, ext="otf", gen_normal=False, gen_upper=True
|
||||
).write_files()
|
||||
FaceProcessor("RobotoMono", "Medium", 20).write_files()
|
||||
).write_files(gen_c)
|
||||
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(
|
||||
"PixelOperator",
|
||||
"Regular",
|
||||
@ -489,7 +629,7 @@ def gen_layout_caesar():
|
||||
shaveX=1,
|
||||
gen_normal=True,
|
||||
gen_upper=True,
|
||||
).write_files()
|
||||
).write_files(gen_c)
|
||||
FaceProcessor(
|
||||
"PixelOperator",
|
||||
"Bold",
|
||||
@ -498,18 +638,23 @@ def gen_layout_caesar():
|
||||
shaveX=1,
|
||||
gen_normal=True,
|
||||
gen_upper=True,
|
||||
).write_files()
|
||||
FaceProcessor("PixelOperatorMono", "Regular", 8, bpp=1, shaveX=1).write_files()
|
||||
FaceProcessor("Unifont", "Regular", 16, bpp=1, shaveX=1, ext="otf").write_files()
|
||||
).write_files(gen_c)
|
||||
FaceProcessor("PixelOperatorMono", "Regular", 8, bpp=1, shaveX=1).write_files(gen_c)
|
||||
FaceProcessor("Unifont", "Regular", 16, bpp=1, shaveX=1, ext="otf").write_files(
|
||||
gen_c
|
||||
)
|
||||
# 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():
|
||||
FaceProcessor("TTSatoshi", "DemiBold", 42, ext="otf").write_files()
|
||||
FaceProcessor("TTSatoshi", "DemiBold", 21, ext="otf").write_files()
|
||||
FaceProcessor("TTSatoshi", "DemiBold", 18, ext="otf").write_files()
|
||||
FaceProcessor("RobotoMono", "Medium", 21).write_files()
|
||||
def gen_layout_delizia(gen_c: bool = False):
|
||||
global LAYOUT_NAME
|
||||
LAYOUT_NAME = "delizia"
|
||||
FaceProcessor("TTSatoshi", "DemiBold", 42, ext="otf").write_files(gen_c)
|
||||
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 = {
|
||||
"bolt": gen_layout_bolt,
|
||||
@ -517,6 +662,7 @@ LAYOUTS = {
|
||||
"delizia": gen_layout_delizia,
|
||||
}
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--layout",
|
||||
@ -531,19 +677,25 @@ LAYOUTS = {
|
||||
default=False,
|
||||
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."""
|
||||
global WRITE_WIDTHS
|
||||
WRITE_WIDTHS = write_widths
|
||||
|
||||
if layout:
|
||||
click.echo(f"Generating fonts for layout: {layout}")
|
||||
LAYOUTS[layout]()
|
||||
LAYOUTS[layout](gen_c)
|
||||
else:
|
||||
click.echo("Generating all fonts")
|
||||
for layout_name, layout_func in LAYOUTS.items():
|
||||
click.echo(f"\nGenerating {layout_name} layout:")
|
||||
layout_func()
|
||||
layout_func(gen_c)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
Reference in New Issue
Block a user