mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
Feature fontmetrics (#8978)
This commit is contained in:
parent
cd22723623
commit
1a47164876
@ -1,6 +1,7 @@
|
||||
from collections.abc import MutableMapping
|
||||
import functools
|
||||
import hashlib
|
||||
from itertools import accumulate
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
@ -468,8 +469,9 @@ class EFont:
|
||||
|
||||
|
||||
class GlyphInfo:
|
||||
def __init__(self, data_len, advance, offset_x, offset_y, width, height):
|
||||
self.data_len = data_len
|
||||
def __init__(self, glyph, data, advance, offset_x, offset_y, width, height):
|
||||
self.glyph = glyph
|
||||
self.bitmap_data = data
|
||||
self.advance = advance
|
||||
self.offset_x = offset_x
|
||||
self.offset_y = offset_y
|
||||
@ -477,6 +479,62 @@ class GlyphInfo:
|
||||
self.height = height
|
||||
|
||||
|
||||
def glyph_to_glyphinfo(glyph, font, size, bpp):
|
||||
scale = 256 // (1 << bpp)
|
||||
if not font.is_scalable:
|
||||
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||
if size in sizes:
|
||||
font.select_size(sizes.index(size))
|
||||
else:
|
||||
font.set_pixel_sizes(size, 0)
|
||||
flags = FT_LOAD_RENDER
|
||||
if bpp != 1:
|
||||
flags |= FT_LOAD_NO_BITMAP
|
||||
else:
|
||||
flags |= FT_LOAD_TARGET_MONO
|
||||
font.load_char(glyph, flags)
|
||||
width = font.glyph.bitmap.width
|
||||
height = font.glyph.bitmap.rows
|
||||
buffer = font.glyph.bitmap.buffer
|
||||
pitch = font.glyph.bitmap.pitch
|
||||
glyph_data = [0] * ((height * width * bpp + 7) // 8)
|
||||
src_mode = font.glyph.bitmap.pixel_mode
|
||||
pos = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if src_mode == ft_pixel_mode_mono:
|
||||
pixel = (
|
||||
(1 << bpp) - 1
|
||||
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
|
||||
else 0
|
||||
)
|
||||
else:
|
||||
pixel = buffer[y * pitch + x] // scale
|
||||
for bit_num in range(bpp):
|
||||
if pixel & (1 << (bpp - bit_num - 1)):
|
||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
pos += 1
|
||||
ascender = pt_to_px(font.size.ascender)
|
||||
if ascender == 0:
|
||||
if not font.is_scalable:
|
||||
ascender = size
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Unable to determine ascender of font %s %s",
|
||||
font.family_name,
|
||||
font.style_name,
|
||||
)
|
||||
return GlyphInfo(
|
||||
glyph,
|
||||
glyph_data,
|
||||
pt_to_px(font.glyph.metrics.horiAdvance),
|
||||
font.glyph.bitmap_left,
|
||||
ascender - font.glyph.bitmap_top,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
"""
|
||||
Collect all glyph codepoints, construct a map from a codepoint to a font file.
|
||||
@ -506,98 +564,47 @@ async def to_code(config):
|
||||
|
||||
codepoints = list(point_set)
|
||||
codepoints.sort(key=functools.cmp_to_key(glyph_comparator))
|
||||
glyph_args = {}
|
||||
data = []
|
||||
bpp = config[CONF_BPP]
|
||||
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.is_scalable:
|
||||
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||
if size in sizes:
|
||||
font.select_size(sizes.index(size))
|
||||
else:
|
||||
font.set_pixel_sizes(size, 0)
|
||||
flags = FT_LOAD_RENDER
|
||||
if bpp != 1:
|
||||
flags |= FT_LOAD_NO_BITMAP
|
||||
else:
|
||||
flags |= FT_LOAD_TARGET_MONO
|
||||
font.load_char(codepoint, flags)
|
||||
width = font.glyph.bitmap.width
|
||||
height = font.glyph.bitmap.rows
|
||||
buffer = font.glyph.bitmap.buffer
|
||||
pitch = font.glyph.bitmap.pitch
|
||||
glyph_data = [0] * ((height * width * bpp + 7) // 8)
|
||||
src_mode = font.glyph.bitmap.pixel_mode
|
||||
pos = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if src_mode == ft_pixel_mode_mono:
|
||||
pixel = (
|
||||
(1 << bpp) - 1
|
||||
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
|
||||
else 0
|
||||
)
|
||||
else:
|
||||
pixel = buffer[y * pitch + x] // scale
|
||||
for bit_num in range(bpp):
|
||||
if pixel & (1 << (bpp - bit_num - 1)):
|
||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
pos += 1
|
||||
ascender = pt_to_px(font.size.ascender)
|
||||
if ascender == 0:
|
||||
if not font.is_scalable:
|
||||
ascender = size
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Unable to determine ascender of font %s", config[CONF_FILE]
|
||||
)
|
||||
glyph_args[codepoint] = GlyphInfo(
|
||||
len(data),
|
||||
pt_to_px(font.glyph.metrics.horiAdvance),
|
||||
font.glyph.bitmap_left,
|
||||
ascender - font.glyph.bitmap_top,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
data += glyph_data
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
glyph_args = [
|
||||
glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints
|
||||
]
|
||||
rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
# Create the glyph table that points to data in the above array.
|
||||
glyph_initializer = []
|
||||
for codepoint in codepoints:
|
||||
glyph_initializer.append(
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(
|
||||
f"(const uint8_t *){cpp_string_escape(codepoint)}"
|
||||
),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(
|
||||
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
|
||||
),
|
||||
),
|
||||
("advance", glyph_args[codepoint].advance),
|
||||
("offset_x", glyph_args[codepoint].offset_x),
|
||||
("offset_y", glyph_args[codepoint].offset_y),
|
||||
("width", glyph_args[codepoint].width),
|
||||
("height", glyph_args[codepoint].height),
|
||||
)
|
||||
glyph_initializer = [
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
|
||||
),
|
||||
("advance", x.advance),
|
||||
("offset_x", x.offset_x),
|
||||
("offset_y", x.offset_y),
|
||||
("width", x.width),
|
||||
("height", x.height),
|
||||
)
|
||||
for (x, y) in zip(
|
||||
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
|
||||
)
|
||||
]
|
||||
|
||||
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
|
||||
font_height = pt_to_px(base_font.size.height)
|
||||
ascender = pt_to_px(base_font.size.ascender)
|
||||
descender = abs(pt_to_px(base_font.size.descender))
|
||||
g = glyph_to_glyphinfo("x", base_font, size, bpp)
|
||||
xheight = g.height if len(g.bitmap_data) > 1 else 0
|
||||
g = glyph_to_glyphinfo("X", base_font, size, bpp)
|
||||
capheight = g.height if len(g.bitmap_data) > 1 else 0
|
||||
if font_height == 0:
|
||||
if not base_font.is_scalable:
|
||||
font_height = size
|
||||
@ -610,5 +617,8 @@ async def to_code(config):
|
||||
len(glyph_initializer),
|
||||
ascender,
|
||||
font_height,
|
||||
descender,
|
||||
xheight,
|
||||
capheight,
|
||||
bpp,
|
||||
)
|
||||
|
@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||
*height = this->glyph_data_->height;
|
||||
}
|
||||
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp)
|
||||
: baseline_(baseline), height_(height), bpp_(bpp) {
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp)
|
||||
: baseline_(baseline),
|
||||
height_(height),
|
||||
descender_(descender),
|
||||
linegap_(height - baseline - descender),
|
||||
xheight_(xheight),
|
||||
capheight_(capheight),
|
||||
bpp_(bpp) {
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
|
@ -50,11 +50,17 @@ class Font
|
||||
public:
|
||||
/** Construct the font with the given glyphs.
|
||||
*
|
||||
* @param glyphs A vector of glyphs, must be sorted lexicographically.
|
||||
* @param data A vector of glyphs, must be sorted lexicographically.
|
||||
* @param data_nr The number of glyphs in data.
|
||||
* @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 height The y-offset from the top of the text to the bottom.
|
||||
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
|
||||
* @param xheight The height of lowercase letters, usually measured at the "x" glyph.
|
||||
* @param capheight The height of capital letters, usually measured at the "X" glyph.
|
||||
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
|
||||
*/
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1);
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp = 1);
|
||||
|
||||
int match_next_glyph(const uint8_t *str, int *match_length);
|
||||
|
||||
@ -65,6 +71,11 @@ class Font
|
||||
#endif
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
inline int get_height() { return this->height_; }
|
||||
inline int get_ascender() { return this->baseline_; }
|
||||
inline int get_descender() { return this->descender_; }
|
||||
inline int get_linegap() { return this->linegap_; }
|
||||
inline int get_xheight() { return this->xheight_; }
|
||||
inline int get_capheight() { return this->capheight_; }
|
||||
inline int get_bpp() { return this->bpp_; }
|
||||
|
||||
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
@ -73,6 +84,10 @@ class Font
|
||||
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
int descender_;
|
||||
int linegap_;
|
||||
int xheight_;
|
||||
int capheight_;
|
||||
uint8_t bpp_; // bits per pixel
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user