Enable text wrapping for printf on displays.

This commit is contained in:
Dave T 2024-10-11 13:59:08 +00:00
parent cedb671f07
commit f6e4a1a0f8
2 changed files with 94 additions and 0 deletions

View file

@ -1,5 +1,7 @@
#include "display.h"
#include "display_color_utils.h"
#include <cstdarg>
#include <sstream>
#include <utility>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@ -480,6 +482,83 @@ void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, int width, int height, BaseFont *font, float line_height, const char *format, ...) {
// Text wrapping printf based on https://gist.github.com/alvesvaren/767d9585f0ecbef18ef1c7c0492c4332
va_list arg;
va_start(arg, format);
TextAlign align = TextAlign::TOP_LEFT;
// Calculate the bounds of a single space character
int space_x1, space_y1, space_width, space_height;
this->get_text_bounds(x, y, " ", font, align, &space_x1, &space_y1, &space_width, &space_height);
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret == 0)
return;
// Break text into words
std::vector<std::string> words;
std::stringstream ss(buffer);
std::string word;
while (std::getline(ss, word, ' ')) {
words.push_back(word);
}
// Initialize variables for the wrapped text
int line_x = x;
int line_y = y;
int x_max = x + width;
int y_max = y + height;
int word_x1, word_y1, word_width, word_height;
bool last = false;
std::string line;
// Iterate through the words and wrap them
for (const auto &w : words) {
this->get_text_bounds(line_x, line_y, w.c_str(), font, align, &word_x1, &word_y1, &word_width, &word_height);
if (line_x + word_width >= x_max) {
// If the next line would overspill the height box, get ready to stop.
if (line_y + 2 * static_cast<int>(word_height * line_height) > y_max) {
last = true;
size_t length = line.length();
const std::string cont = "...";
if (length >= cont.length()) {
line.replace(length - cont.length(), cont.length(), cont);
}
}
// Print the current line and move to the next line
this->printf(x, line_y, font, align, "%s", line.c_str());
if (last) {
break;
}
line_y += static_cast<int>(word_height * line_height);
line_x = x;
// Clear the line buffer
line.clear();
}
// Add the word to the line buffer and move the cursor
line += w;
line_x += word_width + space_width;
// If it's not the last word, add a space
if (!line.empty() && &w != &words.back()) {
line += " ";
}
}
// Print the last line
if (!line.empty()) {
this->printf(x, line_y, font, align, "%s", line.c_str());
}
va_end(arg);
}
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void Display::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)

View file

@ -430,6 +430,21 @@ class Display : public PollingComponent {
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the printf-format `format` and print the result in a word-wrapping text box, with the anchor point at
* [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param width The x width of the of the box to to fill before word-wrapping.
* @param height The y height of the of the box to to fill before truncating with a '...'.
* @param font The font to draw the text with.
* @param line_height A multiplier for the line height. Set to 1.0 for standard line spacing.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, int width, int height, BaseFont *font, float line_height, const char *format, ...)
__attribute__((format(printf, 8, 9)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.