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:
parent
5bef8574b0
commit
8c52f0d5e8
@ -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
|
||||||
|
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
|
#!/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__":
|
||||||
|
Loading…
Reference in New Issue
Block a user