From 063d9c47a402bf78f57e051f88c56acc08eedfdf Mon Sep 17 00:00:00 2001 From: Stanislav Meduna Date: Sun, 23 May 2021 22:24:54 +0200 Subject: [PATCH] Refactor font creation to save stack (#1707) --- esphome/codegen.py | 1 + esphome/components/display/display_buffer.cpp | 56 +++++++++---------- esphome/components/display/display_buffer.h | 21 ++++--- esphome/components/font/__init__.py | 26 ++++++++- esphome/cpp_generator.py | 19 +++++++ 5 files changed, 80 insertions(+), 43 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 90f59f75bc..8361faeb81 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -19,6 +19,7 @@ from esphome.cpp_generator import ( # noqa Statement, LineComment, progmem_array, + static_const_array, statement, variable, new_variable, diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 994997e6e0..c66f87a7aa 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -170,7 +170,7 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align // Unknown char, skip ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); if (!font->get_glyphs().empty()) { - uint8_t glyph_width = font->get_glyphs()[0].width_; + uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width; for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) for (int glyph_y = 0; glyph_y < height; glyph_y++) this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); @@ -193,7 +193,7 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align } } - x_at += glyph.width_ + glyph.offset_x_; + x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; } @@ -345,35 +345,27 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time: } #endif -Glyph::Glyph(const char *a_char, const uint8_t *data_start, uint32_t offset, int offset_x, int offset_y, int width, - int height) - : char_(a_char), - data_(data_start + offset), - offset_x_(offset_x), - offset_y_(offset_y), - width_(width), - height_(height) {} bool Glyph::get_pixel(int x, int y) const { - const int x_data = x - this->offset_x_; - const int y_data = y - this->offset_y_; - if (x_data < 0 || x_data >= this->width_ || y_data < 0 || y_data >= this->height_) + const int x_data = x - this->glyph_data_->offset_x; + const int y_data = y - this->glyph_data_->offset_y; + if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) return false; - const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; + const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; const uint32_t pos = x_data + y_data * width_8; - return pgm_read_byte(this->data_ + (pos / 8u)) & (0x80 >> (pos % 8u)); + return pgm_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); } -const char *Glyph::get_char() const { return this->char_; } +const char *Glyph::get_char() const { return this->glyph_data_->a_char; } bool Glyph::compare_to(const char *str) const { // 1 -> this->char_ // 2 -> str for (uint32_t i = 0;; i++) { - if (this->char_[i] == '\0') + if (this->glyph_data_->a_char[i] == '\0') return true; if (str[i] == '\0') return false; - if (this->char_[i] > str[i]) + if (this->glyph_data_->a_char[i] > str[i]) return false; - if (this->char_[i] < str[i]) + if (this->glyph_data_->a_char[i] < str[i]) return true; } // this should not happen @@ -381,19 +373,19 @@ bool Glyph::compare_to(const char *str) const { } int Glyph::match_length(const char *str) const { for (uint32_t i = 0;; i++) { - if (this->char_[i] == '\0') + if (this->glyph_data_->a_char[i] == '\0') return i; - if (str[i] != this->char_[i]) + if (str[i] != this->glyph_data_->a_char[i]) return 0; } // this should not happen return 0; } void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { - *x1 = this->offset_x_; - *y1 = this->offset_y_; - *width = this->width_; - *height = this->height_; + *x1 = this->glyph_data_->offset_x; + *y1 = this->glyph_data_->offset_y; + *width = this->glyph_data_->width; + *height = this->glyph_data_->height; } int Font::match_next_glyph(const char *str, int *match_length) { int lo = 0; @@ -423,17 +415,17 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in if (glyph_n < 0) { // Unknown char, skip if (!this->get_glyphs().empty()) - x += this->get_glyphs()[0].width_; + x += this->get_glyphs()[0].glyph_data_->width; i++; continue; } const Glyph &glyph = this->glyphs_[glyph_n]; if (!has_char) - min_x = glyph.offset_x_; + min_x = glyph.glyph_data_->offset_x; else - min_x = std::min(min_x, x + glyph.offset_x_); - x += glyph.width_ + glyph.offset_x_; + min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); + x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; has_char = true; @@ -442,8 +434,10 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *width = x - min_x; } const std::vector &Font::get_glyphs() const { return this->glyphs_; } -Font::Font(std::vector &&glyphs, int baseline, int bottom) - : glyphs_(std::move(glyphs)), baseline_(baseline), bottom_(bottom) {} +Font::Font(const GlyphData *data, int data_nr, int baseline, int bottom) : baseline_(baseline), bottom_(bottom) { + for (int i = 0; i < data_nr; ++i) + glyphs_.emplace_back(data + i); +} bool Image::get_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 71a6189990..42e3c5871f 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -338,10 +338,18 @@ class DisplayPage { DisplayPage *next_{nullptr}; }; +struct GlyphData { + const char *a_char; + const uint8_t *data; + int offset_x; + int offset_y; + int width; + int height; +}; + class Glyph { public: - Glyph(const char *a_char, const uint8_t *data_start, uint32_t offset, int offset_x, int offset_y, int width, - int height); + Glyph(const GlyphData *data) : glyph_data_(data) {} bool get_pixel(int x, int y) const; @@ -357,12 +365,7 @@ class Glyph { friend Font; friend DisplayBuffer; - const char *char_; - const uint8_t *data_; - int offset_x_; - int offset_y_; - int width_; - int height_; + const GlyphData *glyph_data_; }; class Font { @@ -373,7 +376,7 @@ class Font { * @param baseline The y-offset from the top of the text to the baseline. * @param bottom The y-offset from the top of the text to the bottom (i.e. height). */ - Font(std::vector &&glyphs, int baseline, int bottom); + Font(const GlyphData *data, int data_nr, int baseline, int bottom); int match_next_glyph(const char *str, int *match_length); diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index c414d37c40..b54342d586 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -12,6 +12,7 @@ MULTI_CONF = True Font = display.display_ns.class_("Font") Glyph = display.display_ns.class_("Glyph") +GlyphData = display.display_ns.struct("GlyphData") def validate_glyphs(value): @@ -75,6 +76,7 @@ DEFAULT_GLYPHS = ( ' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_DATA_ID = "raw_data_id" +CONF_RAW_GLYPH_ID = "raw_glyph_id" FONT_SCHEMA = cv.Schema( { @@ -83,6 +85,7 @@ FONT_SCHEMA = cv.Schema( cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData), } ) @@ -120,8 +123,25 @@ def to_code(config): rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - glyphs = [] + glyph_initializer = [] for glyph in config[CONF_GLYPHS]: - glyphs.append(Glyph(glyph, prog_arr, *glyph_args[glyph])) + glyph_initializer.append( + cg.StructInitializer( + GlyphData, + ("a_char", glyph), + ( + "data", + cg.RawExpression(str(prog_arr) + " + " + str(glyph_args[glyph][0])), + ), + ("offset_x", glyph_args[glyph][1]), + ("offset_y", glyph_args[glyph][2]), + ("width", glyph_args[glyph][3]), + ("height", glyph_args[glyph][4]), + ) + ) - cg.new_Pvariable(config[CONF_ID], glyphs, ascent, ascent + descent) + glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) + + cg.new_Pvariable( + config[CONF_ID], glyphs, len(glyph_initializer), ascent, ascent + descent + ) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 70c3826d27..5325526c23 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -411,6 +411,16 @@ class ProgmemAssignmentExpression(AssignmentExpression): return f"static const {self.type} {self.name}[] PROGMEM = {self.rhs}" +class StaticConstAssignmentExpression(AssignmentExpression): + __slots__ = () + + def __init__(self, type_, name, rhs, obj): + super().__init__(type_, "", name, rhs, obj) + + def __str__(self): + return f"static const {self.type} {self.name}[] = {self.rhs}" + + def progmem_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) obj = MockObj(id_, ".") @@ -420,6 +430,15 @@ def progmem_array(id_, rhs) -> "MockObj": return obj +def static_const_array(id_, rhs) -> "MockObj": + rhs = safe_exp(rhs) + obj = MockObj(id_, ".") + assignment = StaticConstAssignmentExpression(id_.type, id_, rhs, obj) + CORE.add(assignment) + CORE.register_variable(id_, obj) + return obj + + def statement(expression: Union[Expression, Statement]) -> Statement: """Convert expression into a statement unless is already a statement.""" if isinstance(expression, Statement):