mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Refactor font creation to save stack (#1707)
This commit is contained in:
parent
8d8d421286
commit
063d9c47a4
5 changed files with 80 additions and 43 deletions
|
@ -19,6 +19,7 @@ from esphome.cpp_generator import ( # noqa
|
||||||
Statement,
|
Statement,
|
||||||
LineComment,
|
LineComment,
|
||||||
progmem_array,
|
progmem_array,
|
||||||
|
static_const_array,
|
||||||
statement,
|
statement,
|
||||||
variable,
|
variable,
|
||||||
new_variable,
|
new_variable,
|
||||||
|
|
|
@ -170,7 +170,7 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
|
||||||
// Unknown char, skip
|
// Unknown char, skip
|
||||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||||
if (!font->get_glyphs().empty()) {
|
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_x = 0; glyph_x < glyph_width; glyph_x++)
|
||||||
for (int glyph_y = 0; glyph_y < height; glyph_y++)
|
for (int glyph_y = 0; glyph_y < height; glyph_y++)
|
||||||
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
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;
|
i += match_length;
|
||||||
}
|
}
|
||||||
|
@ -345,35 +345,27 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time:
|
||||||
}
|
}
|
||||||
#endif
|
#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 {
|
bool Glyph::get_pixel(int x, int y) const {
|
||||||
const int x_data = x - this->offset_x_;
|
const int x_data = x - this->glyph_data_->offset_x;
|
||||||
const int y_data = y - this->offset_y_;
|
const int y_data = y - this->glyph_data_->offset_y;
|
||||||
if (x_data < 0 || x_data >= this->width_ || y_data < 0 || y_data >= this->height_)
|
if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
|
||||||
return false;
|
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;
|
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 {
|
bool Glyph::compare_to(const char *str) const {
|
||||||
// 1 -> this->char_
|
// 1 -> this->char_
|
||||||
// 2 -> str
|
// 2 -> str
|
||||||
for (uint32_t i = 0;; i++) {
|
for (uint32_t i = 0;; i++) {
|
||||||
if (this->char_[i] == '\0')
|
if (this->glyph_data_->a_char[i] == '\0')
|
||||||
return true;
|
return true;
|
||||||
if (str[i] == '\0')
|
if (str[i] == '\0')
|
||||||
return false;
|
return false;
|
||||||
if (this->char_[i] > str[i])
|
if (this->glyph_data_->a_char[i] > str[i])
|
||||||
return false;
|
return false;
|
||||||
if (this->char_[i] < str[i])
|
if (this->glyph_data_->a_char[i] < str[i])
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// this should not happen
|
// this should not happen
|
||||||
|
@ -381,19 +373,19 @@ bool Glyph::compare_to(const char *str) const {
|
||||||
}
|
}
|
||||||
int Glyph::match_length(const char *str) const {
|
int Glyph::match_length(const char *str) const {
|
||||||
for (uint32_t i = 0;; i++) {
|
for (uint32_t i = 0;; i++) {
|
||||||
if (this->char_[i] == '\0')
|
if (this->glyph_data_->a_char[i] == '\0')
|
||||||
return i;
|
return i;
|
||||||
if (str[i] != this->char_[i])
|
if (str[i] != this->glyph_data_->a_char[i])
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// this should not happen
|
// this should not happen
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||||
*x1 = this->offset_x_;
|
*x1 = this->glyph_data_->offset_x;
|
||||||
*y1 = this->offset_y_;
|
*y1 = this->glyph_data_->offset_y;
|
||||||
*width = this->width_;
|
*width = this->glyph_data_->width;
|
||||||
*height = this->height_;
|
*height = this->glyph_data_->height;
|
||||||
}
|
}
|
||||||
int Font::match_next_glyph(const char *str, int *match_length) {
|
int Font::match_next_glyph(const char *str, int *match_length) {
|
||||||
int lo = 0;
|
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) {
|
if (glyph_n < 0) {
|
||||||
// Unknown char, skip
|
// Unknown char, skip
|
||||||
if (!this->get_glyphs().empty())
|
if (!this->get_glyphs().empty())
|
||||||
x += this->get_glyphs()[0].width_;
|
x += this->get_glyphs()[0].glyph_data_->width;
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Glyph &glyph = this->glyphs_[glyph_n];
|
const Glyph &glyph = this->glyphs_[glyph_n];
|
||||||
if (!has_char)
|
if (!has_char)
|
||||||
min_x = glyph.offset_x_;
|
min_x = glyph.glyph_data_->offset_x;
|
||||||
else
|
else
|
||||||
min_x = std::min(min_x, x + glyph.offset_x_);
|
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||||
x += glyph.width_ + glyph.offset_x_;
|
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||||
|
|
||||||
i += match_length;
|
i += match_length;
|
||||||
has_char = true;
|
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;
|
*width = x - min_x;
|
||||||
}
|
}
|
||||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
||||||
Font::Font(std::vector<Glyph> &&glyphs, int baseline, int bottom)
|
Font::Font(const GlyphData *data, int data_nr, int baseline, int bottom) : baseline_(baseline), bottom_(bottom) {
|
||||||
: glyphs_(std::move(glyphs)), 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 {
|
bool Image::get_pixel(int x, int y) const {
|
||||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||||
|
|
|
@ -338,10 +338,18 @@ class DisplayPage {
|
||||||
DisplayPage *next_{nullptr};
|
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 {
|
class Glyph {
|
||||||
public:
|
public:
|
||||||
Glyph(const char *a_char, const uint8_t *data_start, uint32_t offset, int offset_x, int offset_y, int width,
|
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
||||||
int height);
|
|
||||||
|
|
||||||
bool get_pixel(int x, int y) const;
|
bool get_pixel(int x, int y) const;
|
||||||
|
|
||||||
|
@ -357,12 +365,7 @@ class Glyph {
|
||||||
friend Font;
|
friend Font;
|
||||||
friend DisplayBuffer;
|
friend DisplayBuffer;
|
||||||
|
|
||||||
const char *char_;
|
const GlyphData *glyph_data_;
|
||||||
const uint8_t *data_;
|
|
||||||
int offset_x_;
|
|
||||||
int offset_y_;
|
|
||||||
int width_;
|
|
||||||
int height_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Font {
|
class Font {
|
||||||
|
@ -373,7 +376,7 @@ class Font {
|
||||||
* @param baseline The y-offset from the top of the text to the baseline.
|
* @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).
|
* @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);
|
int match_next_glyph(const char *str, int *match_length);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ MULTI_CONF = True
|
||||||
|
|
||||||
Font = display.display_ns.class_("Font")
|
Font = display.display_ns.class_("Font")
|
||||||
Glyph = display.display_ns.class_("Glyph")
|
Glyph = display.display_ns.class_("Glyph")
|
||||||
|
GlyphData = display.display_ns.struct("GlyphData")
|
||||||
|
|
||||||
|
|
||||||
def validate_glyphs(value):
|
def validate_glyphs(value):
|
||||||
|
@ -75,6 +76,7 @@ DEFAULT_GLYPHS = (
|
||||||
' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||||
)
|
)
|
||||||
CONF_RAW_DATA_ID = "raw_data_id"
|
CONF_RAW_DATA_ID = "raw_data_id"
|
||||||
|
CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
||||||
|
|
||||||
FONT_SCHEMA = cv.Schema(
|
FONT_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
|
@ -83,6 +85,7 @@ FONT_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
||||||
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
|
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_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]
|
rhs = [HexInt(x) for x in data]
|
||||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||||
|
|
||||||
glyphs = []
|
glyph_initializer = []
|
||||||
for glyph in config[CONF_GLYPHS]:
|
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}"
|
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":
|
def progmem_array(id_, rhs) -> "MockObj":
|
||||||
rhs = safe_exp(rhs)
|
rhs = safe_exp(rhs)
|
||||||
obj = MockObj(id_, ".")
|
obj = MockObj(id_, ".")
|
||||||
|
@ -420,6 +430,15 @@ def progmem_array(id_, rhs) -> "MockObj":
|
||||||
return obj
|
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:
|
def statement(expression: Union[Expression, Statement]) -> Statement:
|
||||||
"""Convert expression into a statement unless is already a statement."""
|
"""Convert expression into a statement unless is already a statement."""
|
||||||
if isinstance(expression, Statement):
|
if isinstance(expression, Statement):
|
||||||
|
|
Loading…
Reference in a new issue