mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 19:44:08 +00:00
[font] Keep the glyph table in flash (#20079)
This commit is contained in:
@@ -726,7 +726,8 @@ async def to_code(config):
|
||||
)
|
||||
]
|
||||
|
||||
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
# constexpr Glyph table in flash; on ESP8266 static_const_array would place it in RAM.
|
||||
glyphs = cg.progmem_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
|
||||
font_height = pt_to_px(base_font.size.height)
|
||||
ascender = pt_to_px(base_font.size.ascender)
|
||||
|
||||
@@ -309,7 +309,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
|
||||
if (!this->glyphs_.empty()) {
|
||||
uint8_t glyph_width = this->glyphs_[0].advance;
|
||||
// Full-width read: a narrowing byte load would fault on a PROGMEM table on ESP8266.
|
||||
int glyph_width = this->glyphs_[0].advance;
|
||||
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
x_at += glyph_width;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
#include "esphome/core/defines.h"
|
||||
@@ -36,6 +38,18 @@ class Glyph final {
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
// The glyph table lives in flash, read with plain loads, which ESP8266 only allows for whole words.
|
||||
static_assert(std::is_same_v<decltype(Glyph::code_point), const uint32_t>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::data), const uint8_t *>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::advance), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::offset_x), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::offset_y), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::width), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::height), int>, "Glyph fields must stay word sized");
|
||||
#ifdef USE_ESP8266
|
||||
static_assert(alignof(Glyph) == sizeof(uint32_t), "Glyph fields must stay word sized");
|
||||
static_assert(sizeof(Glyph) == 7 * sizeof(uint32_t), "Glyph is read from flash with word loads");
|
||||
#endif
|
||||
|
||||
class Font final
|
||||
#ifdef USE_DISPLAY
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
font:
|
||||
- file: ../NotoSans-Regular.ttf
|
||||
id: noto
|
||||
size: 12
|
||||
glyphs: "AB"
|
||||
@@ -4,8 +4,10 @@ Focuses on verifying that long multi-byte (Chinese/CJK) glyph strings
|
||||
are correctly processed through the font configuration pipeline.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
import functools
|
||||
from pathlib import Path
|
||||
import re
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -260,15 +262,12 @@ def mock_cg():
|
||||
with (
|
||||
patch("esphome.components.font.cg.add_define") as mock_define,
|
||||
patch("esphome.components.font.cg.progmem_array") as mock_progmem,
|
||||
patch("esphome.components.font.cg.static_const_array") as mock_static,
|
||||
patch("esphome.components.font.cg.new_Pvariable") as mock_new_pvar,
|
||||
):
|
||||
mock_progmem.return_value = MagicMock()
|
||||
mock_static.return_value = MagicMock()
|
||||
yield {
|
||||
"add_define": mock_define,
|
||||
"progmem_array": mock_progmem,
|
||||
"static_const_array": mock_static,
|
||||
"new_Pvariable": mock_new_pvar,
|
||||
}
|
||||
|
||||
@@ -288,13 +287,13 @@ async def test_to_code_long_latin_generates_all_glyphs(mock_cg):
|
||||
mock_cg["add_define"].assert_any_call("USE_FONT")
|
||||
|
||||
# progmem_array receives the combined bitmap data (non-empty)
|
||||
mock_cg["progmem_array"].assert_called_once()
|
||||
bitmap_data = mock_cg["progmem_array"].call_args.args[1]
|
||||
# bitmap data first, then the glyph table
|
||||
assert mock_cg["progmem_array"].call_count == 2
|
||||
bitmap_data = mock_cg["progmem_array"].call_args_list[0].args[1]
|
||||
assert len(bitmap_data) > 0
|
||||
|
||||
# static_const_array receives one entry per unique glyph
|
||||
mock_cg["static_const_array"].assert_called_once()
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
# the glyph table receives one entry per unique glyph
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
assert len(glyph_initializer) == glyph_count
|
||||
|
||||
# new_Pvariable is called with the correct glyph count
|
||||
@@ -314,7 +313,7 @@ async def test_to_code_glyph_entries_contain_expected_fields(mock_cg):
|
||||
|
||||
await to_code(config)
|
||||
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
for entry in glyph_initializer:
|
||||
assert len(entry) == 7, f"Glyph entry should have 7 fields, got {len(entry)}"
|
||||
codepoint = entry[0]
|
||||
@@ -332,6 +331,17 @@ async def test_to_code_glyphs_sorted_by_utf8(mock_cg):
|
||||
|
||||
await to_code(config)
|
||||
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
codepoints = [entry[0] for entry in glyph_initializer]
|
||||
assert codepoints == sorted(codepoints)
|
||||
|
||||
|
||||
def test_glyph_table_is_progmem(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The constexpr glyph table goes to flash, not a RAM static const array."""
|
||||
main_cpp = generate_main(component_config_path("glyph_table.yaml"))
|
||||
|
||||
assert re.search(r"static constexpr font::Glyph \w+\[\] PROGMEM = ", main_cpp)
|
||||
assert "static const font::Glyph" not in main_cpp
|
||||
|
||||
Reference in New Issue
Block a user