From 90c96a0a0fd3733f7772f8cca0fe0948df64e541 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 14 Mar 2025 20:17:16 +1100 Subject: [PATCH 1/3] [font] Fix issues with bitmap fonts (#8407) --- esphome/components/font/__init__.py | 113 ++++++++++++---------------- 1 file changed, 48 insertions(+), 65 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 426680604a..084574d09f 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -146,6 +146,13 @@ def check_missing_glyphs(file, codepoints, warning: bool = False): raise cv.Invalid(message) +def pt_to_px(pt): + """ + Convert a point size to pixels, rounding up to the nearest pixel + """ + return (pt + 63) // 64 + + def validate_font_config(config): """ Check for duplicate codepoints, then check that all requested codepoints actually @@ -172,42 +179,43 @@ def validate_font_config(config): ) # Make setpoints and glyphspoints disjoint setpoints.difference_update(glyphspoints) - if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: - if any(x >= 256 for x in setpoints.copy().union(glyphspoints)): - raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255") - else: - # for TT fonts, check that glyphs are actually present - # Check extras against their own font, exclude from parent font codepoints - for extra in config[CONF_EXTRAS]: - points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} - glyphspoints.difference_update(points) - setpoints.difference_update(points) - check_missing_glyphs(extra[CONF_FILE], points) + # check that glyphs are actually present + # Check extras against their own font, exclude from parent font codepoints + for extra in config[CONF_EXTRAS]: + points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} + glyphspoints.difference_update(points) + setpoints.difference_update(points) + check_missing_glyphs(extra[CONF_FILE], points) - # A named glyph that can't be provided is an error + # A named glyph that can't be provided is an error - check_missing_glyphs(fileconf, glyphspoints) - # A missing glyph from a set is a warning. - if not config[CONF_IGNORE_MISSING_GLYPHS]: - check_missing_glyphs(fileconf, setpoints, warning=True) + check_missing_glyphs(fileconf, glyphspoints) + # A missing glyph from a set is a warning. + if not config[CONF_IGNORE_MISSING_GLYPHS]: + check_missing_glyphs(fileconf, setpoints, warning=True) # Populate the default after the above checks so that use of the default doesn't trigger errors + font = FONT_CACHE[fileconf] if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]: - if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: - config[CONF_GLYPHS] = [DEFAULT_GLYPHS] - else: - # set a default glyphset, intersected with what the font actually offers - font = FONT_CACHE[fileconf] - config[CONF_GLYPHS] = [ - chr(x) - for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) - if font.get_char_index(x) != 0 - ] + # set a default glyphset, intersected with what the font actually offers + config[CONF_GLYPHS] = [ + chr(x) + for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) + if font.get_char_index(x) != 0 + ] - if config[CONF_FILE][CONF_TYPE] == TYPE_LOCAL_BITMAP: - if CONF_SIZE in config: + if font.has_fixed_sizes: + sizes = [pt_to_px(x.size) for x in font.available_sizes] + if not sizes: raise cv.Invalid( - "Size is not a valid option for bitmap fonts, which are inherently fixed size" + f"Font {FontCache.get_name(fileconf)} has no available sizes" + ) + if CONF_SIZE not in config: + config[CONF_SIZE] = sizes[0] + elif config[CONF_SIZE] not in sizes: + sizes = ", ".join(str(x) for x in sizes) + raise cv.Invalid( + f"Font {FontCache.get_name(fileconf)} only has size{'s' if len(sizes) != 1 else ''} {sizes} available" ) elif CONF_SIZE not in config: config[CONF_SIZE] = 20 @@ -215,14 +223,7 @@ def validate_font_config(config): return config -FONT_EXTENSIONS = (".ttf", ".woff", ".otf") -BITMAP_EXTENSIONS = (".bdf", ".pcf") - - -def validate_bitmap_file(value): - if not any(map(value.lower().endswith, BITMAP_EXTENSIONS)): - raise cv.Invalid(f"Only {', '.join(BITMAP_EXTENSIONS)} files are supported.") - return CORE.relative_config_path(cv.file_(value)) +FONT_EXTENSIONS = (".ttf", ".woff", ".otf", "bdf", ".pcf") def validate_truetype_file(value): @@ -246,7 +247,6 @@ def add_local_file(value): TYPE_LOCAL = "local" -TYPE_LOCAL_BITMAP = "local_bitmap" TYPE_GFONTS = "gfonts" TYPE_WEB = "web" LOCAL_SCHEMA = cv.All( @@ -258,15 +258,6 @@ LOCAL_SCHEMA = cv.All( add_local_file, ) -LOCAL_BITMAP_SCHEMA = cv.All( - cv.Schema( - { - cv.Required(CONF_PATH): validate_bitmap_file, - } - ), - add_local_file, -) - FULLPATH_SCHEMA = cv.maybe_simple_value( {cv.Required(CONF_PATH): cv.string}, key=CONF_PATH ) @@ -403,15 +394,6 @@ def validate_file_shorthand(value): } ) - extension = Path(value).suffix - if extension in BITMAP_EXTENSIONS: - return font_file_schema( - { - CONF_TYPE: TYPE_LOCAL_BITMAP, - CONF_PATH: value, - } - ) - return font_file_schema( { CONF_TYPE: TYPE_LOCAL, @@ -424,7 +406,6 @@ TYPED_FILE_SCHEMA = cv.typed_schema( { TYPE_LOCAL: LOCAL_SCHEMA, TYPE_GFONTS: GFONTS_SCHEMA, - TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, TYPE_WEB: WEB_FONT_SCHEMA, } ) @@ -522,11 +503,13 @@ async def to_code(config): bpp = config[CONF_BPP] mode = ft_pixel_mode_grays scale = 256 // (1 << bpp) + size = config[CONF_SIZE] # create the data array for all glyphs for codepoint in codepoints: font = point_font_map[codepoint] - if not font.has_fixed_sizes: - font.set_pixel_sizes(config[CONF_SIZE], 0) + format = font.get_format().decode("utf-8") + if format != "PCF": + font.set_pixel_sizes(size, 0) font.load_char(codepoint) font.glyph.render(mode) width = font.glyph.bitmap.width @@ -550,17 +533,17 @@ async def to_code(config): if pixel & (1 << (bpp - bit_num - 1)): glyph_data[pos // 8] |= 0x80 >> (pos % 8) pos += 1 - ascender = font.size.ascender // 64 + ascender = pt_to_px(font.size.ascender) if ascender == 0: if font.has_fixed_sizes: - ascender = font.available_sizes[0].height + ascender = size else: _LOGGER.error( "Unable to determine ascender of font %s", config[CONF_FILE] ) glyph_args[codepoint] = GlyphInfo( len(data), - font.glyph.metrics.horiAdvance // 64, + pt_to_px(font.glyph.metrics.horiAdvance), font.glyph.bitmap_left, ascender - font.glyph.bitmap_top, width, @@ -599,11 +582,11 @@ async def to_code(config): glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) - font_height = base_font.size.height // 64 - ascender = base_font.size.ascender // 64 + font_height = pt_to_px(base_font.size.height) + ascender = pt_to_px(base_font.size.ascender) if font_height == 0: if base_font.has_fixed_sizes: - font_height = base_font.available_sizes[0].height + font_height = size ascender = font_height else: _LOGGER.error("Unable to determine height of font %s", config[CONF_FILE]) From fb1d178abc64a54c52e7e4a31b872a4d7f45aa84 Mon Sep 17 00:00:00 2001 From: Mikkel Jeppesen <2756925+Duckle29@users.noreply.github.com> Date: Sat, 15 Mar 2025 06:55:20 +0100 Subject: [PATCH 2/3] Added getters for graphs ymin and ymax (#8112) Co-authored-by: guillempages --- esphome/components/graph/graph.cpp | 4 ++++ esphome/components/graph/graph.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index cbe059b255..5abf2ade0d 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -132,6 +132,10 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo yrange = ymax - ymin; } + // Store graph limts + this->graph_limit_max_ = ymax; + this->graph_limit_min_ = ymin; + /// Draw grid if (!std::isnan(this->gridspacing_y_)) { for (int y = yn; y <= ym; y++) { diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 34accb7d3a..468583ca21 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -161,11 +161,15 @@ class Graph : public Component { uint32_t get_duration() { return duration_; } uint32_t get_width() { return width_; } uint32_t get_height() { return height_; } + float get_graph_limit_min() { return graph_limit_min_; } + float get_graph_limit_max() { return graph_limit_max_; } protected: uint32_t duration_; /// in seconds uint32_t width_; /// in pixels uint32_t height_; /// in pixels + float graph_limit_min_{NAN}; + float graph_limit_max_{NAN}; float min_value_{NAN}; float max_value_{NAN}; float min_range_{1.0}; From 9bd7060f6b5b9b72cf88fbc8f0494c7bc385652d Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 16 Mar 2025 01:23:06 -0500 Subject: [PATCH 3/3] Bump version to 2025.3.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index add7af55a7..6ca49bf8a6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.3.0b2" +__version__ = "2025.3.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (