From f6e4a1a0f828125ca6c925128b95cf7aee303463 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:59:08 +0000 Subject: [PATCH] Enable text wrapping for printf on displays. --- esphome/components/display/display.cpp | 79 ++++++++++++++++++++++++++ esphome/components/display/display.h | 15 +++++ 2 files changed, 94 insertions(+) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 63c74e09ca..76d0c2c851 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -1,5 +1,7 @@ #include "display.h" #include "display_color_utils.h" +#include +#include #include #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 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(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(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 pages) { for (auto *page : pages) diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 34feafea6e..58d60343f8 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -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.