Skip to content

Commit 95e6399

Browse files
authored
add return values to drawString & drawStringMaxWidth (#365)
* get rid of utf8ascii() Make the drawString*() functions and getStringWidth() directly convert UTF-8 on the fly if needed. This saves an extra malloc for the converted string in most cases which then needs to be free()d and allows to count drawn chars even for UTF-8 strings later. Keep the utf8ascii() function to not break the API for derived classes. * drawStringInternal: return number of chars drawn Return the nuber of characters that was drawn. If this is less then the string length, then the text was too long for the display. This allows e.g. for custom word wrapping. * drawString: return number of characters drawn * drawStringMaxWidth: return chars written in first line This allows do scroll easily through longer texts, by noting the number of chars drawn in first line and then starting the text with this offset in the next display cycle * drawStringMaxWidth: fix UTF-8 width calculation * add SSD1306ScrollVerticalDemo example this shows how the return value of drawStringMaxWidth() can be used
1 parent c5a8e8c commit 95e6399

File tree

4 files changed

+158
-33
lines changed

4 files changed

+158
-33
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,19 @@ void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t
231231
## Text operations
232232

233233
``` C++
234-
void drawString(int16_t x, int16_t y, const String &text);
234+
// Draws a string at the given location, returns how many chars have been written
235+
uint16_t drawString(int16_t x, int16_t y, const String &text);
235236

236237
// Draws a String with a maximum width at the given location.
237238
// If the given String is wider than the specified width
238239
// The text will be wrapped to the next line at a space or dash
239-
void drawStringMaxWidth(int16_t x, int16_t y, int16_t maxLineWidth, const String &text);
240+
// returns 0 if everything fits on the screen or the numbers of characters in the
241+
// first line if not
242+
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
240243

241244
// Returns the width of the const char* with the current
242245
// font settings
243-
uint16_t getStringWidth(const char* text, uint16_t length);
246+
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false);
244247

245248
// Convencience method for the const char version
246249
uint16_t getStringWidth(const String &text);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2022 by Stefan Seyfried
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
// Include the correct display library
26+
// For a connection via I2C using Wire include
27+
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
28+
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
29+
// or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"`
30+
// For a connection via I2C using brzo_i2c (must be installed) include
31+
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
32+
// #include "SSD1306Brzo.h"
33+
// #include "SH1106Brzo.h"
34+
// For a connection via SPI include
35+
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
36+
// #include "SSD1306Spi.h"
37+
// #include "SH1106Spi.h"
38+
39+
// Use the corresponding display class:
40+
41+
// Initialize the OLED display using SPI
42+
// D5 -> CLK
43+
// D7 -> MOSI (DOUT)
44+
// D0 -> RES
45+
// D2 -> DC
46+
// D8 -> CS
47+
// SSD1306Spi display(D0, D2, D8);
48+
// or
49+
// SH1106Spi display(D0, D2);
50+
51+
// Initialize the OLED display using brzo_i2c
52+
// D3 -> SDA
53+
// D5 -> SCL
54+
// SSD1306Brzo display(0x3c, D3, D5);
55+
// or
56+
// SH1106Brzo display(0x3c, D3, D5);
57+
58+
// Initialize the OLED display using Wire library
59+
SSD1306Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL - SDA and SCL usually populate automatically based on your board's pins_arduino.h e.g. https://github.com/esp8266/Arduino/blob/master/variants/nodemcu/pins_arduino.h
60+
// SH1106Wire display(0x3c, SDA, SCL);
61+
62+
// UTF-8 sprinkled within, because it tests special conditions in the char-counting code
63+
const String loremipsum = "Lorem ipsum dolor sit ämet, "
64+
"consetetur sadipscing elitr, sed diam nonümy eirmöd "
65+
"tempor invidunt ut labore et dolore mägnä aliquyam erat, "
66+
"sed diam voluptua. At vero eos et accusam et justo duo "
67+
"dolores et ea rebum. Stet clita kasd gubergren, no sea "
68+
"takimata sanctus est Lorem ipsum dolor sit amet. "
69+
"äöü-ÄÖÜ/߀é/çØ.";
70+
71+
void setup() {
72+
display.init();
73+
display.setContrast(255);
74+
display.setTextAlignment(TEXT_ALIGN_LEFT);
75+
display.setFont(ArialMT_Plain_16);
76+
display.display();
77+
}
78+
79+
void loop() {
80+
static uint16_t start_at = 0;
81+
display.clear();
82+
uint16_t firstline = display.drawStringMaxWidth(0, 0, 128, loremipsum.substring(start_at));
83+
display.display();
84+
if (firstline != 0) {
85+
start_at += firstline;
86+
} else {
87+
start_at = 0;
88+
delay(1000); // additional pause before going back to start
89+
}
90+
delay(1000);
91+
}

src/OLEDDisplay.cpp

+54-25
Original file line numberDiff line numberDiff line change
@@ -553,13 +553,14 @@ void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const uint8_t *ico,
553553
}
554554
}
555555

556-
void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) {
556+
uint16_t OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8) {
557557
uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS);
558558
uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS);
559559
uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS) * JUMPTABLE_BYTES;
560560

561561
uint16_t cursorX = 0;
562562
uint16_t cursorY = 0;
563+
uint16_t charCount = 0;
563564

564565
switch (textAlignment) {
565566
case TEXT_ALIGN_CENTER_BOTH:
@@ -576,16 +577,23 @@ void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, u
576577
}
577578

578579
// Don't draw anything if it is not on the screen.
579-
if (xMove + textWidth < 0 || xMove > this->width() ) {return;}
580-
if (yMove + textHeight < 0 || yMove > this->height()) {return;}
580+
if (xMove + textWidth < 0 || xMove >= this->width() ) {return 0;}
581+
if (yMove + textHeight < 0 || yMove >= this->height()) {return 0;}
581582

582583
for (uint16_t j = 0; j < textLength; j++) {
583584
int16_t xPos = xMove + cursorX;
584585
int16_t yPos = yMove + cursorY;
585586
if (xPos > this->width())
586587
break; // no need to continue
587-
588-
uint8_t code = text[j];
588+
charCount++;
589+
590+
uint8_t code;
591+
if (utf8) {
592+
code = (this->fontTableLookupFunction)(text[j]);
593+
if (code == 0)
594+
continue;
595+
} else
596+
code = text[j];
589597
if (code >= firstChar) {
590598
uint8_t charCode = code - firstChar;
591599

@@ -605,14 +613,19 @@ void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, u
605613
cursorX += currentCharWidth;
606614
}
607615
}
616+
return charCount;
608617
}
609618

610619

611-
void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String &strUser) {
620+
uint16_t OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String &strUser) {
612621
uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
613622

614623
// char* text must be freed!
615-
char* text = utf8ascii(strUser);
624+
char* text = strdup(strUser.c_str());
625+
if (!text) {
626+
DEBUG_OLEDDISPLAY("[OLEDDISPLAY][drawString] Can't allocate char array.\n");
627+
return 0;
628+
}
616629

617630
uint16_t yOffset = 0;
618631
// If the string should be centered vertically too
@@ -627,14 +640,16 @@ void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String &strUser
627640
yOffset = (lb * lineHeight) / 2;
628641
}
629642

643+
uint16_t charDrawn = 0;
630644
uint16_t line = 0;
631645
char* textPart = strtok(text,"\n");
632646
while (textPart != NULL) {
633647
uint16_t length = strlen(textPart);
634-
drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length));
648+
charDrawn += drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length, true), true);
635649
textPart = strtok(NULL, "\n");
636650
}
637651
free(text);
652+
return charDrawn;
638653
}
639654

640655
void OLEDDisplay::drawStringf( int16_t x, int16_t y, char* buffer, String format, ... )
@@ -646,11 +661,11 @@ void OLEDDisplay::drawStringf( int16_t x, int16_t y, char* buffer, String format
646661
drawString( x, y, buffer );
647662
}
648663

649-
void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, const String &strUser) {
664+
uint16_t OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, const String &strUser) {
650665
uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS);
651666
uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
652667

653-
char* text = utf8ascii(strUser);
668+
const char* text = strUser.c_str();
654669

655670
uint16_t length = strlen(text);
656671
uint16_t lastDrawnPos = 0;
@@ -659,9 +674,14 @@ void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxL
659674

660675
uint16_t preferredBreakpoint = 0;
661676
uint16_t widthAtBreakpoint = 0;
677+
uint16_t firstLineChars = 0;
678+
uint16_t drawStringResult = 1; // later tested for 0 == error, so initialize to 1
662679

663680
for (uint16_t i = 0; i < length; i++) {
664-
strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
681+
char c = (this->fontTableLookupFunction)(text[i]);
682+
if (c == 0)
683+
continue;
684+
strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
665685

666686
// Always try to break on a space, dash or slash
667687
if (text[i] == ' ' || text[i]== '-' || text[i] == '/') {
@@ -674,33 +694,45 @@ void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxL
674694
preferredBreakpoint = i;
675695
widthAtBreakpoint = strWidth;
676696
}
677-
drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint);
697+
drawStringResult = drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint, true);
698+
if (firstLineChars == 0)
699+
firstLineChars = preferredBreakpoint;
678700
lastDrawnPos = preferredBreakpoint;
679701
// It is possible that we did not draw all letters to i so we need
680702
// to account for the width of the chars from `i - preferredBreakpoint`
681703
// by calculating the width we did not draw yet.
682704
strWidth = strWidth - widthAtBreakpoint;
683705
preferredBreakpoint = 0;
706+
if (drawStringResult == 0) // we are past the display already?
707+
break;
684708
}
685709
}
686710

687711
// Draw last part if needed
688-
if (lastDrawnPos < length) {
689-
drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos));
712+
if (drawStringResult != 0 && lastDrawnPos < length) {
713+
drawStringResult = drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos, true), true);
690714
}
691715

692-
free(text);
716+
if (drawStringResult == 0 || (yMove + lineNumber * lineHeight) >= this->height()) // text did not fit on screen
717+
return firstLineChars;
718+
return 0; // everything was drawn
693719
}
694720

695-
uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) {
721+
uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length, bool utf8) {
696722
uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS);
697723

698724
uint16_t stringWidth = 0;
699725
uint16_t maxWidth = 0;
700726

701-
while (length--) {
702-
stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
703-
if (text[length] == 10) {
727+
for (uint16_t i = 0; i < length; i++) {
728+
char c = text[i];
729+
if (utf8) {
730+
c = (this->fontTableLookupFunction)(c);
731+
if (c == 0)
732+
continue;
733+
}
734+
stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
735+
if (c == 10) {
704736
maxWidth = max(maxWidth, stringWidth);
705737
stringWidth = 0;
706738
}
@@ -710,10 +742,7 @@ uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) {
710742
}
711743

712744
uint16_t OLEDDisplay::getStringWidth(const String &strUser) {
713-
char* text = utf8ascii(strUser);
714-
uint16_t length = strlen(text);
715-
uint16_t width = getStringWidth(text, length);
716-
free(text);
745+
uint16_t width = getStringWidth(strUser.c_str(), strUser.length());
717746
return width;
718747
}
719748

@@ -806,7 +835,7 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) {
806835
length++;
807836
// Draw string on line `line` from lastPos to length
808837
// Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT
809-
drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0);
838+
drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0, false);
810839
// Remember last pos
811840
lastPos = i;
812841
// Reset length
@@ -818,7 +847,7 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) {
818847
}
819848
// Draw the remaining string
820849
if (length > 0) {
821-
drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0);
850+
drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0, false);
822851
}
823852
}
824853

src/OLEDDisplay.h

+7-5
Original file line numberDiff line numberDiff line change
@@ -242,20 +242,22 @@ class OLEDDisplay : public Stream {
242242

243243
/* Text functions */
244244

245-
// Draws a string at the given location
246-
void drawString(int16_t x, int16_t y, const String &text);
245+
// Draws a string at the given location, returns how many chars have been written
246+
uint16_t drawString(int16_t x, int16_t y, const String &text);
247247

248248
// Draws a formatted string (like printf) at the given location
249249
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... );
250250

251251
// Draws a String with a maximum width at the given location.
252252
// If the given String is wider than the specified width
253253
// The text will be wrapped to the next line at a space or dash
254-
void drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
254+
// returns 0 if everything fits on the screen or the numbers of characters in the
255+
// first line if not
256+
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
255257

256258
// Returns the width of the const char* with the current
257259
// font settings
258-
uint16_t getStringWidth(const char* text, uint16_t length);
260+
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false);
259261

260262
// Convencience method for the const char version
261263
uint16_t getStringWidth(const String &text);
@@ -382,7 +384,7 @@ class OLEDDisplay : public Stream {
382384

383385
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline));
384386

385-
void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth);
387+
uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8);
386388

387389
FontTableLookupFunction fontTableLookupFunction;
388390
};

0 commit comments

Comments
 (0)