mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	[font] Fix issues with bitmap fonts (#8407)
This commit is contained in:
		| @@ -146,6 +146,13 @@ def check_missing_glyphs(file, codepoints, warning: bool = False): | |||||||
|             raise cv.Invalid(message) |             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): | def validate_font_config(config): | ||||||
|     """ |     """ | ||||||
|     Check for duplicate codepoints, then check that all requested codepoints actually |     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 |     # Make setpoints and glyphspoints disjoint | ||||||
|     setpoints.difference_update(glyphspoints) |     setpoints.difference_update(glyphspoints) | ||||||
|     if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: |     # check that glyphs are actually present | ||||||
|         if any(x >= 256 for x in setpoints.copy().union(glyphspoints)): |     # Check extras against their own font, exclude from parent font codepoints | ||||||
|             raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255") |     for extra in config[CONF_EXTRAS]: | ||||||
|     else: |         points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} | ||||||
|         # for TT fonts, check that glyphs are actually present |         glyphspoints.difference_update(points) | ||||||
|         # Check extras against their own font, exclude from parent font codepoints |         setpoints.difference_update(points) | ||||||
|         for extra in config[CONF_EXTRAS]: |         check_missing_glyphs(extra[CONF_FILE], points) | ||||||
|             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) |     check_missing_glyphs(fileconf, glyphspoints) | ||||||
|         # A missing glyph from a set is a warning. |     # A missing glyph from a set is a warning. | ||||||
|         if not config[CONF_IGNORE_MISSING_GLYPHS]: |     if not config[CONF_IGNORE_MISSING_GLYPHS]: | ||||||
|             check_missing_glyphs(fileconf, setpoints, warning=True) |         check_missing_glyphs(fileconf, setpoints, warning=True) | ||||||
|  |  | ||||||
|     # Populate the default after the above checks so that use of the default doesn't trigger errors |     # 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 not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]: | ||||||
|         if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: |         # set a default glyphset, intersected with what the font actually offers | ||||||
|             config[CONF_GLYPHS] = [DEFAULT_GLYPHS] |         config[CONF_GLYPHS] = [ | ||||||
|         else: |             chr(x) | ||||||
|             # set a default glyphset, intersected with what the font actually offers |             for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) | ||||||
|             font = FONT_CACHE[fileconf] |             if font.get_char_index(x) != 0 | ||||||
|             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 font.has_fixed_sizes: | ||||||
|         if CONF_SIZE in config: |         sizes = [pt_to_px(x.size) for x in font.available_sizes] | ||||||
|  |         if not sizes: | ||||||
|             raise cv.Invalid( |             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: |     elif CONF_SIZE not in config: | ||||||
|         config[CONF_SIZE] = 20 |         config[CONF_SIZE] = 20 | ||||||
| @@ -215,14 +223,7 @@ def validate_font_config(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| FONT_EXTENSIONS = (".ttf", ".woff", ".otf") | FONT_EXTENSIONS = (".ttf", ".woff", ".otf", "bdf", ".pcf") | ||||||
| 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)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_truetype_file(value): | def validate_truetype_file(value): | ||||||
| @@ -246,7 +247,6 @@ def add_local_file(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| TYPE_LOCAL = "local" | TYPE_LOCAL = "local" | ||||||
| TYPE_LOCAL_BITMAP = "local_bitmap" |  | ||||||
| TYPE_GFONTS = "gfonts" | TYPE_GFONTS = "gfonts" | ||||||
| TYPE_WEB = "web" | TYPE_WEB = "web" | ||||||
| LOCAL_SCHEMA = cv.All( | LOCAL_SCHEMA = cv.All( | ||||||
| @@ -258,15 +258,6 @@ LOCAL_SCHEMA = cv.All( | |||||||
|     add_local_file, |     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( | FULLPATH_SCHEMA = cv.maybe_simple_value( | ||||||
|     {cv.Required(CONF_PATH): cv.string}, key=CONF_PATH |     {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( |     return font_file_schema( | ||||||
|         { |         { | ||||||
|             CONF_TYPE: TYPE_LOCAL, |             CONF_TYPE: TYPE_LOCAL, | ||||||
| @@ -424,7 +406,6 @@ TYPED_FILE_SCHEMA = cv.typed_schema( | |||||||
|     { |     { | ||||||
|         TYPE_LOCAL: LOCAL_SCHEMA, |         TYPE_LOCAL: LOCAL_SCHEMA, | ||||||
|         TYPE_GFONTS: GFONTS_SCHEMA, |         TYPE_GFONTS: GFONTS_SCHEMA, | ||||||
|         TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, |  | ||||||
|         TYPE_WEB: WEB_FONT_SCHEMA, |         TYPE_WEB: WEB_FONT_SCHEMA, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
| @@ -522,11 +503,13 @@ async def to_code(config): | |||||||
|     bpp = config[CONF_BPP] |     bpp = config[CONF_BPP] | ||||||
|     mode = ft_pixel_mode_grays |     mode = ft_pixel_mode_grays | ||||||
|     scale = 256 // (1 << bpp) |     scale = 256 // (1 << bpp) | ||||||
|  |     size = config[CONF_SIZE] | ||||||
|     # create the data array for all glyphs |     # create the data array for all glyphs | ||||||
|     for codepoint in codepoints: |     for codepoint in codepoints: | ||||||
|         font = point_font_map[codepoint] |         font = point_font_map[codepoint] | ||||||
|         if not font.has_fixed_sizes: |         format = font.get_format().decode("utf-8") | ||||||
|             font.set_pixel_sizes(config[CONF_SIZE], 0) |         if format != "PCF": | ||||||
|  |             font.set_pixel_sizes(size, 0) | ||||||
|         font.load_char(codepoint) |         font.load_char(codepoint) | ||||||
|         font.glyph.render(mode) |         font.glyph.render(mode) | ||||||
|         width = font.glyph.bitmap.width |         width = font.glyph.bitmap.width | ||||||
| @@ -550,17 +533,17 @@ async def to_code(config): | |||||||
|                     if pixel & (1 << (bpp - bit_num - 1)): |                     if pixel & (1 << (bpp - bit_num - 1)): | ||||||
|                         glyph_data[pos // 8] |= 0x80 >> (pos % 8) |                         glyph_data[pos // 8] |= 0x80 >> (pos % 8) | ||||||
|                     pos += 1 |                     pos += 1 | ||||||
|         ascender = font.size.ascender // 64 |         ascender = pt_to_px(font.size.ascender) | ||||||
|         if ascender == 0: |         if ascender == 0: | ||||||
|             if font.has_fixed_sizes: |             if font.has_fixed_sizes: | ||||||
|                 ascender = font.available_sizes[0].height |                 ascender = size | ||||||
|             else: |             else: | ||||||
|                 _LOGGER.error( |                 _LOGGER.error( | ||||||
|                     "Unable to determine ascender of font %s", config[CONF_FILE] |                     "Unable to determine ascender of font %s", config[CONF_FILE] | ||||||
|                 ) |                 ) | ||||||
|         glyph_args[codepoint] = GlyphInfo( |         glyph_args[codepoint] = GlyphInfo( | ||||||
|             len(data), |             len(data), | ||||||
|             font.glyph.metrics.horiAdvance // 64, |             pt_to_px(font.glyph.metrics.horiAdvance), | ||||||
|             font.glyph.bitmap_left, |             font.glyph.bitmap_left, | ||||||
|             ascender - font.glyph.bitmap_top, |             ascender - font.glyph.bitmap_top, | ||||||
|             width, |             width, | ||||||
| @@ -599,11 +582,11 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) |     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) | ||||||
|  |  | ||||||
|     font_height = base_font.size.height // 64 |     font_height = pt_to_px(base_font.size.height) | ||||||
|     ascender = base_font.size.ascender // 64 |     ascender = pt_to_px(base_font.size.ascender) | ||||||
|     if font_height == 0: |     if font_height == 0: | ||||||
|         if base_font.has_fixed_sizes: |         if base_font.has_fixed_sizes: | ||||||
|             font_height = base_font.available_sizes[0].height |             font_height = size | ||||||
|             ascender = font_height |             ascender = font_height | ||||||
|         else: |         else: | ||||||
|             _LOGGER.error("Unable to determine height of font %s", config[CONF_FILE]) |             _LOGGER.error("Unable to determine height of font %s", config[CONF_FILE]) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user