mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Refactor font creation to save stack (#1707)
This commit is contained in:
		| @@ -19,6 +19,7 @@ from esphome.cpp_generator import (  # noqa | ||||
|     Statement, | ||||
|     LineComment, | ||||
|     progmem_array, | ||||
|     static_const_array, | ||||
|     statement, | ||||
|     variable, | ||||
|     new_variable, | ||||
|   | ||||
| @@ -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<Glyph> &Font::get_glyphs() const { return this->glyphs_; } | ||||
| Font::Font(std::vector<Glyph> &&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_) | ||||
|   | ||||
| @@ -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<Glyph> &&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); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|     ) | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user