ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
font.cpp
Go to the documentation of this file.
1#include "font.h"
2
4#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7namespace esphome::font {
8static const char *const TAG = "font";
9
10#ifdef USE_LVGL_FONT
11static const uint8_t OPA4_TABLE[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};
12
13static const uint8_t OPA2_TABLE[4] = {0, 85, 170, 255};
14
15const void *Font::get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf) {
16 const auto *font = dsc->resolved_font;
17 auto *const fe = (Font *) font->dsc;
18
19 const auto *gd = fe->get_glyph_data_(dsc->gid.index);
20 if (gd == nullptr) {
21 return nullptr;
22 }
23
24 const uint8_t *bitmap_in = gd->data;
25 uint8_t *bitmap_out_tmp = draw_buf->data;
26 int32_t i = 0;
27 int32_t x, y;
28 uint32_t stride = lv_draw_buf_width_to_stride(gd->width, LV_COLOR_FORMAT_A8);
29
30 switch (fe->get_bpp()) {
31 case 1: {
32 uint8_t mask = 0;
33 uint8_t byte = 0;
34 for (y = 0; y != gd->height; y++) {
35 for (x = 0; x != gd->width; x++) {
36 if (mask == 0) {
37 mask = 0x80;
38 byte = *bitmap_in++;
39 }
40 bitmap_out_tmp[x] = byte & mask ? 255 : 0;
41 mask >>= 1;
42 }
43 bitmap_out_tmp += stride;
44 }
45 } break;
46
47 case 2:
48 for (y = 0; y != gd->height; y++) {
49 for (x = 0; x != gd->width; x++, i++) {
50 switch (i & 0x3) {
51 default:
52 bitmap_out_tmp[x] = OPA2_TABLE[(*bitmap_in) >> 6];
53 break;
54 case 1:
55 bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 4) & 0x3];
56 break;
57 case 2:
58 bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 2) & 0x3];
59 break;
60 case 3:
61 bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 0) & 0x3];
62 bitmap_in++;
63 }
64 }
65 bitmap_out_tmp += stride;
66 }
67 break;
68
69 case 4:
70 for (y = 0; y != gd->height; y++) {
71 for (x = 0; x != gd->width; x++, i++) {
72 i = i & 0x1;
73 if (i == 0) {
74 bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) >> 4];
75 } else if (i == 1) {
76 bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) & 0xF];
77 bitmap_in++;
78 }
79 }
80 bitmap_out_tmp += stride;
81 }
82 break;
83
84 case 8:
85 memcpy(bitmap_out_tmp, bitmap_in, gd->width * gd->height);
86 break;
87 default:
88 ESP_LOGD(TAG, "Unknown bpp: %d", fe->get_bpp());
89 break;
90 }
91 return draw_buf;
92}
93
94bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
95 auto *fe = (Font *) font->dsc;
96 const auto *gd = fe->get_glyph_data_(unicode_letter);
97 if (gd == nullptr) {
98 return false;
99 }
100 dsc->adv_w = gd->advance;
101 dsc->ofs_x = gd->offset_x;
102 dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
103 dsc->box_w = gd->width;
104 dsc->box_h = gd->height;
105 dsc->is_placeholder = 0;
106 dsc->format = (lv_font_glyph_format_t) fe->get_bpp();
107 dsc->gid.index = unicode_letter;
108 return true;
109}
110
111const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
112 if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
113 return this->last_data_;
114 auto *glyph = this->find_glyph(unicode_letter);
115 if (glyph == nullptr) {
116 return nullptr;
117 }
118 this->last_data_ = glyph;
119 this->last_letter_ = unicode_letter;
120 return glyph;
121}
122#endif
123
134static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
135 // Safely cast to uint8_t* for correct bitwise operations on bytes
136 const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
137 uint32_t code_point = 0;
138 uint8_t c1 = *current++;
139
140 // check for end of string
141 if (c1 == 0) {
142 *length = 0;
143 return 0;
144 }
145
146 // --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
147 if (c1 < 0x80) {
148 // Valid ASCII byte.
149 code_point = c1;
150 // Optimization: No need to check for continuation bytes.
151 }
152 // --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
153 else if ((c1 & 0xE0) == 0xC0) {
154 uint8_t c2 = *current++;
155
156 // Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
157 if ((c2 & 0xC0) != 0x80) {
158 *length = 0;
159 return 0;
160 }
161
162 code_point = (c1 & 0x1F) << 6;
163 code_point |= (c2 & 0x3F);
164
165 // Error Check 2: Overlong check (2-byte must be > 0x7F)
166 if (code_point <= 0x7F) {
167 *length = 0;
168 return 0;
169 }
170 }
171 // --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
172 else if ((c1 & 0xF0) == 0xE0) {
173 uint8_t c2 = *current++;
174 uint8_t c3 = *current++;
175
176 // Error Check 1: Check continuation bytes
177 if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
178 *length = 0;
179 return 0;
180 }
181
182 code_point = (c1 & 0x0F) << 12;
183 code_point |= (c2 & 0x3F) << 6;
184 code_point |= (c3 & 0x3F);
185
186 // Error Check 2: Overlong check (3-byte must be > 0x7FF)
187 // Also check for surrogates (0xD800-0xDFFF)
188 if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
189 *length = 0;
190 return 0;
191 }
192 }
193 // --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
194 else if ((c1 & 0xF8) == 0xF0) {
195 uint8_t c2 = *current++;
196 uint8_t c3 = *current++;
197 uint8_t c4 = *current++;
198
199 // Error Check 1: Check continuation bytes
200 if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
201 *length = 0;
202 return 0;
203 }
204
205 code_point = (c1 & 0x07) << 18;
206 code_point |= (c2 & 0x3F) << 12;
207 code_point |= (c3 & 0x3F) << 6;
208 code_point |= (c4 & 0x3F);
209
210 // Error Check 2: Overlong check (4-byte must be > 0xFFFF)
211 // Also check for valid Unicode range (must be <= 0x10FFFF)
212 if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
213 *length = 0;
214 return 0;
215 }
216 }
217 // --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
218 else {
219 *length = 0;
220 return 0;
221 }
222 *length = current - reinterpret_cast<const uint8_t *>(utf8_str);
223 return code_point;
224}
225
226Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
227 uint8_t bpp)
228 : glyphs_(ConstVector(data, data_nr)),
229 baseline_(baseline),
230 height_(height),
231 descender_(descender),
232 linegap_(height - baseline - descender),
233 xheight_(xheight),
234 capheight_(capheight),
235 bpp_(bpp) {
236#ifdef USE_LVGL_FONT
237 this->lv_font_.dsc = this;
238 this->lv_font_.line_height = this->get_height();
239 this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
240 this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
241 this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
242 this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
243 this->lv_font_.underline_position = -1;
244 this->lv_font_.underline_thickness = 1;
245#endif
246}
247
248const Glyph *Font::find_glyph(uint32_t codepoint) const {
249 int lo = 0;
250 int hi = this->glyphs_.size() - 1;
251 while (lo != hi) {
252 int mid = (lo + hi + 1) / 2;
253 if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
254 lo = mid;
255 } else {
256 hi = mid - 1;
257 }
258 }
259 auto *result = &this->glyphs_[lo];
260 if (result->code_point == codepoint)
261 return result;
262 return nullptr;
263}
264
265#ifdef USE_DISPLAY
266void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
267 *baseline = this->baseline_;
268 *height = this->height_;
269 int min_x = 0;
270 bool has_char = false;
271 int x = 0;
272 for (;;) {
273 size_t length;
274 auto code_point = extract_unicode_codepoint(str, &length);
275 if (length == 0)
276 break;
277 str += length;
278 auto *glyph = this->find_glyph(code_point);
279 if (glyph == nullptr) {
280 // Unknown char, skip
281 if (!this->glyphs_.empty())
282 x += this->glyphs_[0].advance;
283 continue;
284 }
285
286 if (!has_char) {
287 min_x = glyph->offset_x;
288 } else {
289 min_x = std::min(min_x, x + glyph->offset_x);
290 }
291 x += glyph->advance;
292
293 has_char = true;
294 }
295 *x_offset = min_x;
296 *width = x - min_x;
297}
298
299void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
300 int x_at = x_start;
301 for (;;) {
302 size_t length;
303 auto code_point = extract_unicode_codepoint(text, &length);
304 if (length == 0)
305 break;
306 text += length;
307 auto *glyph = this->find_glyph(code_point);
308 if (glyph == nullptr) {
309 // Unknown char, skip
310 ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
311 if (!this->glyphs_.empty()) {
312 uint8_t glyph_width = this->glyphs_[0].advance;
313 display->rectangle(x_at, y_start, glyph_width, this->height_, color);
314 x_at += glyph_width;
315 }
316 continue;
317 }
318
319 const uint8_t *data = glyph->data;
320 const int max_x = x_at + glyph->offset_x + glyph->width;
321 const int max_y = y_start + glyph->offset_y + glyph->height;
322
323 uint8_t bitmask = 0;
324 uint8_t pixel_data = 0;
325 uint8_t bpp_max = (1 << this->bpp_) - 1;
326 auto diff_r = (float) color.r - (float) background.r;
327 auto diff_g = (float) color.g - (float) background.g;
328 auto diff_b = (float) color.b - (float) background.b;
329 auto diff_w = (float) color.w - (float) background.w;
330 auto b_r = (float) background.r;
331 auto b_g = (float) background.g;
332 auto b_b = (float) background.b;
333 auto b_w = (float) background.w;
334 for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
335 for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
336 uint8_t pixel = 0;
337 for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
338 if (bitmask == 0) {
339 pixel_data = progmem_read_byte(data++);
340 bitmask = 0x80;
341 }
342 pixel <<= 1;
343 if ((pixel_data & bitmask) != 0)
344 pixel |= 1;
345 bitmask >>= 1;
346 }
347 if (pixel == bpp_max) {
348 display->draw_pixel_at(glyph_x, glyph_y, color);
349 } else if (pixel != 0) {
350 auto on = (float) pixel / (float) bpp_max;
351 auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g),
352 (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w));
353 display->draw_pixel_at(glyph_x, glyph_y, blended);
354 }
355 }
356 }
357 x_at += glyph->advance;
358 }
359}
360#endif
361} // namespace esphome::font
Lightweight read-only view over a const array stored in RODATA (will typically be in flash memory) Av...
Definition helpers.h:131
void rectangle(int x1, int y1, int width, int height, Color color=COLOR_ON)
Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+...
Definition display.cpp:99
void draw_pixel_at(int x, int y)
Set a single pixel at the specified coordinates to default color.
Definition display.h:335
lv_font_t lv_font_
Definition font.h:91
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override
Definition font.cpp:266
const Glyph * find_glyph(uint32_t codepoint) const
Definition font.cpp:248
void print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) override
Definition font.cpp:299
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next)
Definition font.cpp:94
int get_height()
Definition font.h:68
uint32_t last_letter_
Definition font.h:95
const Glyph * get_glyph_data_(uint32_t unicode_letter)
Definition font.cpp:111
int get_baseline()
Definition font.h:67
const Glyph * last_data_
Definition font.h:96
static const void * get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf)
Definition font.cpp:15
uint8_t bpp_
Definition font.h:89
ConstVector< Glyph > glyphs_
Definition font.h:82
Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, uint8_t bpp=1)
Construct the font with the given glyphs.
Definition font.cpp:226
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:43
static void uint32_t
uint8_t w
Definition color.h:42
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6