diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 564f2bd7..5a543b6c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6817,7 +6817,7 @@ uint16_t mode_2Dscrollingtext(void) { char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; unsigned maxLen = (SEGMENT.name) ? min(WLED_MAX_SEGNAME_LEN, (int)strlen(SEGMENT.name)) : 0; // WLEDMM make it robust against too long segment names -#if !defined(WLED_ENABLE_FULL_FONTS) +#if !defined(WLED_ENABLE_FULL_FONTS) || defined(ESP8266) if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; // unicode killer #else if (SEGMENT.name) for (size_t i=0,j=0; i drawText() will fall back to just forwarding each char to drawCharacter() void wu_pixel(uint32_t x, uint32_t y, CRGB c); diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index b1f48980..5a6146ac 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -877,41 +877,35 @@ bool Segment::jsonToPixels(char * name, uint8_t fileNr) { return true; } -#include "src/font/console_font_4x6.h" -#include "src/font/console_font_5x8.h" -#include "src/font/console_font_5x12.h" -#include "src/font/console_font_6x8.h" -#include "src/font/console_font_7x9.h" +#include "wled_fonts.hpp" #if defined(WLED_ENABLE_FULL_FONTS) #include "src/font/codepages.h" #endif // unicode-aware wrapper for drawCharacter(), to be called from mode_2Dscrollingtext() -void Segment::drawText(const unsigned char* text, size_t maxLen, int maxLetters, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, bool drawShadow) { +void Segment::drawText(const unsigned char* text, size_t maxLen, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, bool drawShadow) { if (!isActive()) return; // not active - // ToDO: find font _first/_last, based on width / height + //size_t maxLetters = WLED_MAX_SEGNAME_LEN; + const size_t numberOfChars = strlen((const char *) text); // size in bytes // toDo check if this is duplicate -> maxLen + #if defined(WLED_ENABLE_FULL_FONTS) + FontInfo_t font = getFontInfo(w, h); // use central font selection legic uint16_t decoded_text[WLED_MAX_SEGNAME_LEN+1] = { 0 }; // UTF-16 converted text. Cannot be longer than WLED_MAX_SEGNAME_LEN size_t utf16_index = 0; for(const unsigned char* now = text; now != nullptr && now[0] != '\0'; now = nextUnicode(now, maxLen)) { if (utf16_index < WLED_MAX_SEGNAME_LEN) { decoded_text[utf16_index] = unicodeToWchar16(now, maxLen); // UTF-8 decode into decoded_text decoded_text[utf16_index] = wchar16ToCodepage437(decoded_text[utf16_index]); // decoded_text to CP437 (in-place conversion) - if ((decoded_text[utf16_index] >= 1) && ((decoded_text[utf16_index] <= 254))) utf16_index++; // don't advance on NUL or codes not suppoted in DrawCharacter + if ((decoded_text[utf16_index] >= font.firstChar) && ((decoded_text[utf16_index] <= font.lastChar))) // don't advance on NUL, or on codes not suppoted in DrawCharacter + utf16_index++; } } decoded_text[utf16_index] = 0; // NUL terminate string - size_t textLength = min(utf16_index, size_t(maxLetters)); - + size_t textLength = min(utf16_index, numberOfChars); #else const unsigned char* decoded_text = text; // fallback - size_t textLength = min(strnlen((char*)text, maxLen), size_t(maxLetters)); + size_t textLength = min(strnlen((char*)text, maxLen), numberOfChars); #endif - - // toDo: ensure that decoded_text[i] is between console_font_YxZ_first and console_font_YxZ_last - // if (chr < 32 || chr > 126) --> clamp chr - // chr -= 32; // align with font table entries - // pass characters to drawCharacter() for (int i = 0; i < textLength; i++) { SEGMENT.drawCharacter((unsigned char) decoded_text[i], x + w*i, y, w, h, color, col2, drawShadow); @@ -922,18 +916,12 @@ void Segment::drawText(const unsigned char* text, size_t maxLen, int maxLetters, // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, bool drawShadow) { if (!isActive()) return; // not active -#if !defined(WLED_ENABLE_FULL_FONTS) - if (chr < 32 || chr > 126) return; // legacy mode - only ASCII 32-126 supported - chr -= 32; // align with font table entries -#else - // ToDO: clamp to actual font limits - if (chr < 1 || chr > 254) return; // sanity check // ToDO needs improvements - chr = chr -1; // all fonts start at 1 // ToDO needs improvements -#endif - const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); - const int font = w*h; + FontInfo_t font = getFontInfo(w, h); // use central font selection logic + if (!font.isProgMem || font.width_bytes > 1) return; // do nothing for not (yet) supported font features: width_bytes > 1, !isProgMem + if (chr < font.firstChar || chr > font.lastChar) return; // do nothing when out of limits + chr = chr - font.firstChar; // adjust chr to point to the first allowed character byte CRGB col = CRGB(color); CRGBPalette16 grad = CRGBPalette16(col, (col2 != BLACK) ? CRGB(col2) : col); @@ -941,16 +929,21 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, //if (w<5 || w>6 || h!=8) return; if (drawShadow) w++; // one more column for shadow on right side - for (int i = 0; i= rows) break; // drawing off-screen uint8_t bits = 0; uint8_t bits_up = 0; // WLEDMM this is the previous line: font[(chr * h) + i -1] - // ToDO: move font selection logic into separate function - - switch (font) { // font = w * h +#if 1 + // new code - experimental + bits = pgm_read_byte_near(&font.raw[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&font.raw[(chr * h) + i -1]); +#else + // legacy code, will be deleted after some tests + int pixels = w*h; + switch (pixels) { // font = w * h case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_4x6[(chr * h) + i -1]); break; // 4x6 font @@ -968,17 +961,18 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, break; // 5x12 font default: return; } +#endif if (col2 != BLACK) col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); uint32_t fgCol = uint32_t(col) & 0x00FFFFFF; // WLEDMM cache color value - for (int j = 0; j 0 && x0 < cols" if ((bits>>(j+(8-w))) & 0x01) { // bit set & drawing on-screen setPixelColorXY(x0, y0, fgCol); } else { if (drawShadow) { - // WLEDMM + // WLEDMM if ((j < (w-1)) && (bits>>(j+(8-w) +1)) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel to the right is set else if ((j > 0) && (bits>>(j+(8-w) -1)) & 0x01) setPixelColorXY(x0, y0, bgCol);// blank when pixel to the left is set else if ((bits_up>>(j+(8-w))) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel above is set diff --git a/wled00/src/font/codepage437.cpp b/wled00/src/font/codepage437.cpp index 767c992a..ebdb8891 100644 --- a/wled00/src/font/codepage437.cpp +++ b/wled00/src/font/codepage437.cpp @@ -1,5 +1,3 @@ -#if defined(WLED_ENABLE_FULL_FONTS) - /* @title WLED(-MM) - unicode to CP437 conversion @repo https://github.com/MoonModules/WLED-MM, https://github.com/wled/WLED @@ -7,6 +5,8 @@ @license Licensed under the EUPL-1.2 or later */ +#if defined(WLED_ENABLE_FULL_FONTS) + #include "codepages.h" #include diff --git a/wled00/src/font/codepages.h b/wled00/src/font/codepages.h index dbb9e4eb..04317050 100644 --- a/wled00/src/font/codepages.h +++ b/wled00/src/font/codepages.h @@ -8,7 +8,7 @@ #undef WLED_ENABLE_FULL_FONTS #endif -// visual replacements when decoding fails +// UTF-16 visual replacements when decoding fails //constexpr uint16_t UNKNOWN_CODE = 0x2219; // ∙ multiplication dot (try this if you don't like the middle dot) constexpr uint16_t UNKNOWN_CODE = 0x00B7; // · middle dot = unknown code (generic error) constexpr uint16_t BAD_CODE = 0x2022; // • bigger dot = cannot decode (unicode malformed) @@ -19,6 +19,11 @@ constexpr uint16_t EXT_CODE = 0x263B; // ☻ smiling face = extended cod // return "•" in case of input errors, and for unsupported/invalid UTF-8 uint16_t unicodeToWchar16(const unsigned char* utf8, size_t maxLen); // unicodetool.cpp +#if defined(WLED_ENABLE_FULL_FONTS) +// translates unicode 2-byte (UTF-16) "code point" into corresponding character in codepage 437 (IBM PC aka PC-8) +uint16_t wchar16ToCodepage437(uint16_t wideChar); // codepage437.cpp +#endif + // returns a pointer to the next unicode item - can be used to "advance" conversion after unicodeToWchar16() // return nullptr at end of input const unsigned char* nextUnicode(const unsigned char* utf8, size_t maxLen); // unicodetool.cpp @@ -30,7 +35,16 @@ size_t strlenUC(const unsigned char* utf8); // Important: calling code is responsible to provide a string with at least _where_ chars size_t cutUnicodeAt(const unsigned char* utf8, size_t where); -// translates unicode 2-byte (UTF-16) "code point" into corresponding character in codepage 437 (IBM PC aka PC-8) -uint16_t wchar16ToCodepage437(uint16_t wideChar); // codepage437.cpp + +// special utility function for @troyhacks ;-) +// removes all unicode letter from a C style char[] - conversion is in-place, to avoid heap fragging +// doesn't work on PROGMEM strings, unless you strdup() them into RAM before calling this function +inline void killUnicode(unsigned char* utf8) { + if (utf8 == nullptr) return; + size_t clean_index = 0; + for (size_t i=0; utf8[i] != '\0'; i++) + if ((utf8[i] > 0) && (utf8[i] < 128)) utf8[clean_index++] = utf8[i]; // only keep pure ASCII; unicode extended chars start at 128 + utf8[clean_index] = '\0'; // ensure proper string termination +} #endif diff --git a/wled00/wled_fonts.hpp b/wled00/wled_fonts.hpp new file mode 100644 index 00000000..909e1e65 --- /dev/null +++ b/wled00/wled_fonts.hpp @@ -0,0 +1,87 @@ +#pragma once +#ifndef WLED_FONTS_H +#define WLED_FONTS_H +#include // needed to get uint16_t definition +#include // helps for code analysis with clang + +// always disable unicode for 8266 builds - not enough program space +#if !defined(ARDUINO_ARCH_ESP32) && defined(WLED_ENABLE_FULL_FONTS) +#undef WLED_ENABLE_FULL_FONTS +#endif + + +// pull in all fonts +#include "src/font/console_font_4x6.h" +#include "src/font/console_font_5x8.h" +#include "src/font/console_font_5x12.h" +#include "src/font/console_font_6x8.h" +#include "src/font/console_font_7x9.h" + + +// fontInfo struct returned by getFontInfo +typedef struct { + unsigned firstChar; // first supported glyph (32 for standard "reduced" fonts) + unsigned lastChar; // last supported glyph (126 for standard "reduced" fonts) + unsigned width_bytes; // single letter width in bytes (default:1) + // unsigned height_bytes; // future support + bool isProgMem; // raw data points into ProgMem => 8266 needs pgm_read_byte_near() + const unsigned char* raw; // array of bytes with raw pixeldata (typicially lives in PROGMEM) + // note: we only support monospaced fonts +} FontInfo_t; + +// logic for font selection based on width and height +inline FontInfo_t getFontInfo(unsigned width, unsigned height) { + static FontInfo_t font = {0}; // not sure if this needs to be static - just wanted to be sure that the function return value is not in the stack area (use-after-free). + + unsigned pixels = width * height; + switch (pixels) { + // WLED standard fonts (PROGMEM) + case 24: // 4x6 font + font.raw = console_font_4x6; + font.isProgMem = true; + font.firstChar = console_font_4x6_first; + font.lastChar = console_font_4x6_last; + font.width_bytes= 1; + break; + case 40: // 5x8 font + font.raw = console_font_5x8; + font.isProgMem = true; + font.firstChar = console_font_5x8_first; + font.lastChar = console_font_5x8_last; + font.width_bytes= 1; + break; + case 48: // 6x8 font + font.raw = console_font_6x8; + font.isProgMem = true; + font.firstChar = console_font_6x8_first; + font.lastChar = console_font_6x8_last; + font.width_bytes= 1; + break; + case 63: // 7x9 font + font.raw = console_font_7x9; + font.isProgMem = true; + font.firstChar = console_font_7x9_first; + font.lastChar = console_font_7x9_last; + font.width_bytes= 1; + break; + case 60: // 5x12 font + font.raw = console_font_5x12; + font.isProgMem = true; + font.firstChar = console_font_5x12_first; + font.lastChar = console_font_5x12_last; + font.width_bytes= 1; + break; + + // you can add any custom fonts here + + default: // no font + font.raw = nullptr; + font.isProgMem = false; + font.firstChar = 1; + font.lastChar = 1; + font.width_bytes= 1; + } + return font; +} + +#endif