Feature fontmetrics (#8978)

This commit is contained in:
JonasB2497 2025-06-23 06:47:47 +02:00 committed by GitHub
parent cd22723623
commit 1a47164876
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 119 additions and 87 deletions

View File

@ -1,6 +1,7 @@
from collections.abc import MutableMapping from collections.abc import MutableMapping
import functools import functools
import hashlib import hashlib
from itertools import accumulate
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
@ -468,8 +469,9 @@ class EFont:
class GlyphInfo: class GlyphInfo:
def __init__(self, data_len, advance, offset_x, offset_y, width, height): def __init__(self, glyph, data, advance, offset_x, offset_y, width, height):
self.data_len = data_len self.glyph = glyph
self.bitmap_data = data
self.advance = advance self.advance = advance
self.offset_x = offset_x self.offset_x = offset_x
self.offset_y = offset_y self.offset_y = offset_y
@ -477,6 +479,62 @@ class GlyphInfo:
self.height = height 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): async def to_code(config):
""" """
Collect all glyph codepoints, construct a map from a codepoint to a font file. 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 = list(point_set)
codepoints.sort(key=functools.cmp_to_key(glyph_comparator)) codepoints.sort(key=functools.cmp_to_key(glyph_comparator))
glyph_args = {}
data = []
bpp = config[CONF_BPP] bpp = config[CONF_BPP]
scale = 256 // (1 << bpp)
size = config[CONF_SIZE] size = config[CONF_SIZE]
# create the data array for all glyphs # create the data array for all glyphs
for codepoint in codepoints: glyph_args = [
font = point_font_map[codepoint] glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints
if not font.is_scalable: ]
sizes = [pt_to_px(x.size) for x in font.available_sizes] rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])]
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]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
# Create the glyph table that points to data in the above array. # Create the glyph table that points to data in the above array.
glyph_initializer = [] glyph_initializer = [
for codepoint in codepoints: cg.StructInitializer(
glyph_initializer.append( GlyphData,
cg.StructInitializer( (
GlyphData, "a_char",
( cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
"a_char", ),
cg.RawExpression( (
f"(const uint8_t *){cpp_string_escape(codepoint)}" "data",
), cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
), ),
( ("advance", x.advance),
"data", ("offset_x", x.offset_x),
cg.RawExpression( ("offset_y", x.offset_y),
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" ("width", x.width),
), ("height", x.height),
),
("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),
)
) )
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) glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
font_height = pt_to_px(base_font.size.height) font_height = pt_to_px(base_font.size.height)
ascender = pt_to_px(base_font.size.ascender) 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 font_height == 0:
if not base_font.is_scalable: if not base_font.is_scalable:
font_height = size font_height = size
@ -610,5 +617,8 @@ async def to_code(config):
len(glyph_initializer), len(glyph_initializer),
ascender, ascender,
font_height, font_height,
descender,
xheight,
capheight,
bpp, bpp,
) )

View File

@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
*height = this->glyph_data_->height; *height = this->glyph_data_->height;
} }
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp) Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
: baseline_(baseline), height_(height), bpp_(bpp) { uint8_t bpp)
: baseline_(baseline),
height_(height),
descender_(descender),
linegap_(height - baseline - descender),
xheight_(xheight),
capheight_(capheight),
bpp_(bpp) {
glyphs_.reserve(data_nr); glyphs_.reserve(data_nr);
for (int i = 0; i < data_nr; ++i) for (int i = 0; i < data_nr; ++i)
glyphs_.emplace_back(&data[i]); glyphs_.emplace_back(&data[i]);

View File

@ -50,11 +50,17 @@ class Font
public: public:
/** Construct the font with the given glyphs. /** 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 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); int match_next_glyph(const uint8_t *str, int *match_length);
@ -65,6 +71,11 @@ class Font
#endif #endif
inline int get_baseline() { return this->baseline_; } inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; } 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_; } inline int get_bpp() { return this->bpp_; }
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
@ -73,6 +84,10 @@ class Font
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_; std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
int baseline_; int baseline_;
int height_; int height_;
int descender_;
int linegap_;
int xheight_;
int capheight_;
uint8_t bpp_; // bits per pixel uint8_t bpp_; // bits per pixel
}; };