diff --git a/docs/nimble_companion_trial.md b/docs/nimble_companion_trial.md index 1d9f5c06..e3992f1e 100644 --- a/docs/nimble_companion_trial.md +++ b/docs/nimble_companion_trial.md @@ -22,9 +22,9 @@ It is not the generic XIAO profile for separately wired radio modules. Both boards retain their parent Full Companion features, USB mOTA sender, WiFi update slots, and PSRAM-backed offline queue. The V4 also retains direct MQTT. -The [small-screen Picopixel layout](v4_pixel5_font_trial.md) uses 5-pixel -capital letters, a compact channel/sender line and full message previews. -A named V4 trial profile is available for hardware qualification. +The [small-screen message layout](v4_pixel5_font_trial.md) uses 6-pixel capitals +on 128x64 panels and 5-pixel capitals on very tiny panels. It includes a compact +channel/sender line and full message previews. Font selection is automatic. ## Build diff --git a/docs/v4_pixel5_font_trial.md b/docs/v4_pixel5_font_trial.md index 47326036..76a8f8c9 100644 --- a/docs/v4_pixel5_font_trial.md +++ b/docs/v4_pixel5_font_trial.md @@ -1,23 +1,33 @@ -# Small-screen Picopixel message font +# Small-screen message fonts -Small-screen Companion builds use Picopixel for received message text and the -channel/sender line, including `Ch 0 Public`. This covers SSD1306 and SH1106 -OLEDs, ST7735 small TFTs, and the U8g2 T-Echo Card display. The shared renderer -uses the same font data on every driver. Larger displays keep their existing -font. Menus, Bluetooth PINs and WiFi setup QR codes keep their normal layout. +Small-screen Companion builds automatically select a compact font for received +message text and the channel/sender line, including `Ch 0 Public`. This covers +SSD1306 and SH1106 OLEDs, ST7735 small TFTs, and the U8g2 T-Echo Card display. +The shared renderer selects by logical panel dimensions, including rotation: -Capital letters are 5 logical pixels high; descenders can occupy a sixth -pixel, so lines advance by 7 pixels. Most characters advance by 4 pixels -horizontally. The normal OLED font uses 7-pixel letters, 8-pixel line spacing -and 6-pixel character advances. Small TFTs retain their existing logical to -physical scaling. +| Panel size | Font | Capital height | Line spacing | +| --- | --- | ---: | ---: | +| At least 128 x 64, or 64 x 128 rotated | Squeezed Regular 6 | 6 pixels | 8 pixels | +| Smaller panels, including 72 x 40, 128 x 32 and 64 x 48 | Picopixel | 5 pixels | 7 pixels | -On a 128 x 64 OLED, compacting the channel/sender line moves message text from -y=25 to y=21. Six complete small-font rows fit instead of five, roughly 30 -additional characters depending on the letters. Text wraps at character -boundaries; the last line ends with `...` if it still cannot fit. Unsupported -characters appear as `?`. Long channel/sender names are ellipsized so they -cannot overlap the message. +Both fonts allow one pixel for descenders and one blank pixel row between +lines. Character widths vary: most letters advance by 4 pixels, with narrow +letters taking less space and `M` and `W` taking 6 pixels. Squeezed Regular 6 +is a [public-domain font by Oliver Kraus](https://github.com/olikraus/u8g2/wiki/fntgrpu8g#squeezed_r6). +Its bitmap data is stored as constants without heap allocation. + +On a 128 x 64 OLED, the compact channel/sender line starts at y=14 and message +text at y=22. Five complete 6px-font rows fit. The software comparison fits +three representative 160-character messages in those five rows. Capacity +depends on the characters; messages with many wide letters can still overflow. +Text wraps at character boundaries, with `...` on the last line if necessary. +Unsupported characters appear as `?`. Long channel/sender names are ellipsized +to stay on their own line. An enabled channel footer reserves its own space. + +Larger display classes keep their existing font. Menus, Bluetooth PINs and WiFi +setup QR codes keep their normal layout. Small TFTs retain their existing +logical to physical scaling. The normal OLED font uses 7-pixel letters, +8-pixel line spacing and 6-pixel character advances. The preview buffer holds a complete 160-byte MeshCore message plus its terminator. Previously the main message UI allocated 78 bytes, leaving room @@ -36,15 +46,17 @@ Enable `platformio.nimble.ini` in the ignored `platformio.local.ini` as shown in the [NimBLE trial guide](nimble_companion_trial.md#build), then run: ```sh -OUTPUT_DIR=.releases/v4-pixel5 bash build.sh build-firmware \ - heltec_v4_2_v4_3_companion_radio_full_femon_nimble_pixel5 \ - --firmware-version v1.17.1.5-halo-keymind-cascade-pixel5-trial \ +OUTPUT_DIR=.releases/v4-smallfont bash build.sh build-firmware \ + heltec_v4_2_v4_3_companion_radio_full_femon_nimble \ + --firmware-version v1.17.1.5-halo-keymind-cascade-squeezed6-trial \ --radio-preset usa-cascadia --profile cascade --standard --require-ota ``` -The named trial keeps NimBLE, 350 contacts, 40 channels, the V4's 512-frame +This build keeps NimBLE, 350 contacts, 40 channels, the V4's 512-frame PSRAM queue, USB mOTA sending and WiFi OTA support. The smaller font is also the default in ordinary small-screen Companion builds from this source. +The older `_nimble_pixel5` environment name remains available, but also uses +the automatic 5px/6px selection. Use the application `.bin` for WiFi OTA. A clean USB install uses the merged image at address 0. When manually writing the application at `0x10000`, an @@ -66,16 +78,18 @@ python3 -B test/test_firmware_ram.py pio test -e native -f test_display_driver -f test_companion_message_history ``` -The native tests exercise the shared renderer, five-pixel capitals, complete -rows at display edges, long sender lines, overflow markers and tiny/rotated -screen geometry. The additional Adafruit comparison checks all 95 printable -ASCII glyphs pixel for pixel against the original Picopixel font, under -address/undefined-behavior sanitizers. That comparison needs a cached +The native tests exercise both capital heights, automatic font selection, +160-character messages, complete rows at display edges, long sender lines, +reserved footers, overflow markers and tiny/rotated screen geometry. The +additional rendering comparison checks all 95 printable ASCII glyphs in each +font, under address/undefined-behavior sanitizers. Picopixel is compared pixel +for pixel with Adafruit GFX; Squeezed Regular 6 is compared with the upstream +BDF fixture independently of the converted C++ tables. This needs a cached PlatformIO Adafruit GFX library; set `MESHCORE_GFX_LIBRARY` to its directory if needed. It reports a skip when the library is absent. The native tests do not require that dependency. Run only one PlatformIO command at a time. -## Hardware and build results, 2026-09-08 +## Earlier 5px hardware and build results, 2026-09-08 Source revision `eeea15ef` passed seven representative firmware builds: diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 5290406a..1b9df2fb 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -4,7 +4,7 @@ #include #include #if UI_SMALL_MESSAGE_FONT == 1 - #include + #include #endif #include #include "../MyMesh.h" @@ -1444,7 +1444,7 @@ public: #if UI_SMALL_MESSAGE_FONT == 1 mesh::ui::drawSmallMessageBody(display, p->origin, p->message, - layout.origin_y, layout.origin_y + 7, + layout.origin_y, UI_MESSAGE_CHANNEL_FOOTER == 1 ? display.height() - layout.filter_height : display.height()); #else diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index eeb70fbe..78a2a13b 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -1,6 +1,6 @@ #include "UITask.h" #if UI_SMALL_MESSAGE_FONT == 1 - #include + #include #endif #include #include @@ -246,7 +246,7 @@ void UITask::renderCurrScreen() { _display->print(_node_prefs->node_name); #if UI_SMALL_MESSAGE_FONT == 1 - mesh::ui::drawSmallMessageBody(*_display, _origin, _msg, 12, 19); + mesh::ui::drawSmallMessageBody(*_display, _origin, _msg, 12); #else _display->setCursor(0, 12); _display->setColor(UIColor::secondary_txt); diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index d2236bb5..b72738f1 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -1,6 +1,6 @@ #include "UITask.h" #if UI_SMALL_MESSAGE_FONT == 1 - #include + #include #endif #include #include @@ -460,7 +460,7 @@ public: } int render(DisplayDriver& display) override { // The 72x40 interface reserves its top 8px for the scrolling status bar. - mesh::ui::drawSmallMessageBody(display, _origin, _message, 10, 17); + mesh::ui::drawSmallMessageBody(display, _origin, _message, 10); return 1000; } bool handleInput(char c) override { diff --git a/platformio.nimble.ini b/platformio.nimble.ini index 3e141c05..68b9bb4e 100644 --- a/platformio.nimble.ini +++ b/platformio.nimble.ini @@ -19,6 +19,7 @@ lib_deps = ${companion_nimble_trial.lib_deps} lib_ignore = ${companion_nimble_trial.lib_ignore} +; Legacy trial name; UI_SMALL_MESSAGE_FONT now selects 5px or 6px by panel size. [env:heltec_v4_2_v4_3_companion_radio_full_femon_nimble_pixel5] extends = env:heltec_v4_2_v4_3_companion_radio_full_femon_nimble build_flags = diff --git a/src/helpers/ui/Pixel5FontData.h b/src/helpers/ui/Pixel5FontData.h index 3b3c0abd..a2fa8e85 100644 --- a/src/helpers/ui/Pixel5FontData.h +++ b/src/helpers/ui/Pixel5FontData.h @@ -30,17 +30,11 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include +#include "SmallFontGlyph.h" namespace mesh { namespace ui { -struct Pixel5Glyph { - uint16_t bitmap_offset; - uint8_t width, height, advance; - int8_t x_offset, y_offset; -}; - static const uint8_t pixel5Bitmaps[] = { 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, @@ -58,7 +52,7 @@ static const uint8_t pixel5Bitmaps[] = { 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; -static const Pixel5Glyph pixel5Glyphs[] = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' +static const SmallFontGlyph pixel5Glyphs[] = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' {0, 1, 5, 2, 0, -4}, // 0x21 '!' {1, 3, 2, 4, 0, -4}, // 0x22 '"' {2, 5, 5, 6, 0, -4}, // 0x23 '#' diff --git a/src/helpers/ui/SmallFontGlyph.h b/src/helpers/ui/SmallFontGlyph.h new file mode 100644 index 00000000..47d17cf6 --- /dev/null +++ b/src/helpers/ui/SmallFontGlyph.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace mesh { +namespace ui { + +// Packed monochrome bitmap metrics, with GFX-style baseline offsets. +struct SmallFontGlyph { + uint16_t bitmap_offset; + uint8_t width, height, advance; + int8_t x_offset, y_offset; +}; + +} // namespace ui +} // namespace mesh diff --git a/src/helpers/ui/Pixel5Text.h b/src/helpers/ui/SmallMessageText.h similarity index 60% rename from src/helpers/ui/Pixel5Text.h rename to src/helpers/ui/SmallMessageText.h index 1c2f1f39..5edf5b6b 100644 --- a/src/helpers/ui/Pixel5Text.h +++ b/src/helpers/ui/SmallMessageText.h @@ -3,25 +3,40 @@ #include "DisplayDriver.h" #include "DisplayTextLayout.h" #include "Pixel5FontData.h" +#include "Squeezed6FontData.h" namespace mesh { namespace ui { // Draw through the common pixel interface so OLED, TFT and U8g2 panels use // identical glyphs. This stack wrapper leaves the driver's normal font alone. -class Pixel5Text : public DisplayDriver { +class SmallMessageText : public DisplayDriver { DisplayDriver& _display; + const bool _six_pixel; int _x = 0, _y = 0; - static const Pixel5Glyph& glyph(uint8_t c) { - return pixel5Glyphs[(c >= 32 && c <= 126 ? c : '?') - 32]; + const SmallFontGlyph& glyph(uint8_t c) const { + const int index = (c >= 32 && c <= 126 ? c : '?') - 32; + return _six_pixel ? squeezed6Glyphs[index] : pixel5Glyphs[index]; } public: - explicit Pixel5Text(DisplayDriver& display) - : DisplayDriver(display.width(), display.height()), _display(display) {} - static constexpr int line_height = 7; - static constexpr int glyph_height = 6; // 5px capitals plus descenders + explicit SmallMessageText(DisplayDriver& display) + : DisplayDriver(display.width(), display.height()), _display(display), + _six_pixel((width() >= 128 && height() >= 64) + || (width() >= 64 && height() >= 128)) {} + + // Preserve Picopixel on very tiny panels, including rotated 72x40 and + // 128x32 screens. This wrapper is used only by the compact message UI. + int capitalHeight() const { return _six_pixel ? 6 : 5; } + int glyphHeight() const { return capitalHeight() + 1; } // descenders + int lineHeight() const { return glyphHeight() + 1; } // blank row + int lineCount(int y, int bottom = -1) const { + if (bottom < 0 || bottom > height()) bottom = height(); + const int remaining = bottom - y; + return remaining < glyphHeight() ? 0 + : 1 + (remaining - glyphHeight()) / lineHeight(); + } bool isOn() override { return _display.isOn(); } void turnOn() override { _display.turnOn(); } @@ -48,13 +63,14 @@ public: } void print(const char* str) override { while (*str) { - const Pixel5Glyph& g = glyph((uint8_t)*str++); + const SmallFontGlyph& g = glyph((uint8_t)*str++); + const uint8_t* bitmap = _six_pixel ? squeezed6Bitmaps : pixel5Bitmaps; for (int row = 0; row < g.height; ++row) { - const int y = _y + 4 + g.y_offset + row; + const int y = _y + capitalHeight() - 1 + g.y_offset + row; if (y < 0 || y >= height()) continue; for (int col = 0; col < g.width; ++col) { const int bit = row * g.width + col; - if (!(pixel5Bitmaps[g.bitmap_offset + bit / 8] + if (!(bitmap[g.bitmap_offset + bit / 8] & (0x80 >> (bit % 8)))) continue; const int x = _x + g.x_offset + col; if (x >= 0 && x < width()) _display.fillRect(x, y, 1, 1); @@ -65,32 +81,23 @@ public: } void printWordWrap(const char* str, int max_width) override { if (max_width > width() - _x) max_width = width() - _x; - const int lines = (height() - _y + line_height - glyph_height) / line_height; - drawTextWrapped(*this, _x, _y, max_width, line_height, lines, str); + drawTextWrapped(*this, _x, _y, max_width, lineHeight(), lineCount(_y), str); } }; -inline int smallMessageLineCount(int height, int message_y) { - const int remaining = height - message_y; - return remaining < Pixel5Text::glyph_height ? 0 - : (remaining + Pixel5Text::line_height - Pixel5Text::glyph_height) - / Pixel5Text::line_height; -} - inline void drawSmallMessageBody(DisplayDriver& display, const char* origin, const char* message, int origin_y = 14, - int message_y = 21, int bottom = -1) { - Pixel5Text text(display); + int bottom = -1) { + SmallMessageText text(display); + const int message_y = origin_y + text.lineHeight(); char translated[161]; text.translateUTF8ToBlocks(translated, origin, sizeof(translated)); text.setColor(UIColor::secondary_txt); text.drawTextEllipsized(0, origin_y, display.width(), translated); text.translateUTF8ToBlocks(translated, message, sizeof(translated)); text.setColor(UIColor::primary_txt); - const int height = bottom < 0 || bottom > display.height() - ? display.height() : bottom; - drawTextWrapped(text, 0, message_y, display.width(), Pixel5Text::line_height, - smallMessageLineCount(height, message_y), translated); + drawTextWrapped(text, 0, message_y, display.width(), text.lineHeight(), + text.lineCount(message_y, bottom), translated); } } // namespace ui diff --git a/src/helpers/ui/Squeezed6FontData.h b/src/helpers/ui/Squeezed6FontData.h new file mode 100644 index 00000000..3412839c --- /dev/null +++ b/src/helpers/ui/Squeezed6FontData.h @@ -0,0 +1,134 @@ +#pragma once + +// Squeezed Regular 6 by Oliver Kraus. Public domain. +// Lossless conversion of the ASCII glyphs from the upstream BDF. +// Source: https://raw.githubusercontent.com/olikraus/u8g2/57d191014bebc263868526491e7f7c33ddcf6042/tools/font/bdf/u8g2_squeezed_regular_6.bdf +// BDF SHA-256: a50740bd62accaa104e52da98210b3db10e03d75c6fbac1a31010177fad92277 +// Reference: test/fixtures/small_message_font/squeezed6.bdf + +#include "SmallFontGlyph.h" + +namespace mesh { +namespace ui { + +static const uint8_t squeezed6Bitmaps[] = { + 0xF4, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x23, 0xE8, 0xE2, 0xF8, 0x80, 0xA5, + 0x29, 0x40, 0x4A, 0x4A, 0xA5, 0xC0, 0x6A, 0x90, 0x95, 0x60, 0xAB, 0xAA, + 0x5D, 0x00, 0xC0, 0xC0, 0x80, 0x25, 0x29, 0x00, 0x56, 0xDA, 0x80, 0x75, + 0x50, 0x96, 0xB0, 0xD9, 0x60, 0xB7, 0x92, 0x40, 0xE9, 0x60, 0x53, 0x5A, + 0x80, 0xE5, 0x24, 0x80, 0x55, 0x5A, 0x80, 0x55, 0x92, 0x80, 0xA0, 0x98, + 0x64, 0xCC, 0x98, 0x9A, 0x20, 0x69, 0xBB, 0x87, 0x57, 0xDB, 0x40, 0xD7, + 0x5B, 0x80, 0x6A, 0x90, 0xD6, 0xDB, 0x80, 0xEE, 0xB0, 0xEE, 0xA0, 0x72, + 0xDA, 0xC0, 0xB7, 0xDB, 0x40, 0xFC, 0x55, 0x70, 0xB7, 0x5B, 0x40, 0xAA, + 0xB0, 0x8E, 0xEB, 0x18, 0xC4, 0x9D, 0xB9, 0x99, 0x56, 0xDA, 0x80, 0xD7, + 0x49, 0x00, 0x56, 0xDA, 0xC0, 0xD7, 0x5B, 0x40, 0x69, 0x60, 0xE9, 0x24, + 0x80, 0xB6, 0xDB, 0xC0, 0xB6, 0xD4, 0x80, 0x8C, 0x63, 0x5A, 0xA8, 0xB5, + 0x2B, 0x40, 0xB6, 0xA4, 0x80, 0xD6, 0xB0, 0xEA, 0xB0, 0x91, 0x22, 0x40, + 0xD5, 0x70, 0x54, 0xC0, 0x90, 0x76, 0xB0, 0x93, 0x5B, 0x80, 0x69, 0x25, + 0xDA, 0xC0, 0x57, 0x30, 0x6E, 0xA0, 0x75, 0x9C, 0x93, 0x5B, 0x40, 0xBC, + 0x45, 0x58, 0x92, 0xEB, 0x40, 0xFC, 0xF5, 0x6B, 0x50, 0xD6, 0xD0, 0x56, + 0xA0, 0xD6, 0xE8, 0x76, 0xB2, 0xBA, 0x40, 0x66, 0xAE, 0x90, 0xB6, 0xF0, + 0xB6, 0xE0, 0x8D, 0x6A, 0xA0, 0xA9, 0x50, 0xB5, 0x94, 0xDB, 0x2B, 0x24, + 0x40, 0xFC, 0x89, 0xA5, 0x00, 0x5A, +}; + +static const SmallFontGlyph squeezed6Glyphs[] = { + {0, 0, 0, 2, 0, 1}, // ASCII 32 + {0, 1, 6, 2, 0, -5}, // ASCII 33 + {1, 3, 2, 4, 0, -5}, // ASCII 34 + {2, 5, 5, 6, 0, -4}, // ASCII 35 + {6, 5, 7, 6, 0, -5}, // ASCII 36 + {11, 3, 6, 4, 0, -5}, // ASCII 37 + {14, 4, 6, 5, 0, -5}, // ASCII 38 + {17, 1, 2, 2, 0, -5}, // ASCII 39 + {18, 2, 6, 3, 0, -5}, // ASCII 40 + {20, 2, 6, 3, 0, -5}, // ASCII 41 + {22, 3, 5, 4, 0, -5}, // ASCII 42 + {24, 3, 3, 4, 0, -4}, // ASCII 43 + {26, 1, 2, 2, 0, 0}, // ASCII 44 + {27, 2, 1, 3, 0, -3}, // ASCII 45 + {28, 1, 1, 2, 0, 0}, // ASCII 46 + {29, 3, 6, 4, 0, -5}, // ASCII 47 + {32, 3, 6, 4, 0, -5}, // ASCII 48 + {35, 2, 6, 3, 0, -5}, // ASCII 49 + {37, 2, 6, 3, 0, -5}, // ASCII 50 + {39, 2, 6, 3, 0, -5}, // ASCII 51 + {41, 3, 6, 4, 0, -5}, // ASCII 52 + {44, 2, 6, 3, 0, -5}, // ASCII 53 + {46, 3, 6, 4, 0, -5}, // ASCII 54 + {49, 3, 6, 4, 0, -5}, // ASCII 55 + {52, 3, 6, 4, 0, -5}, // ASCII 56 + {55, 3, 6, 4, 0, -5}, // ASCII 57 + {58, 1, 3, 2, 0, -3}, // ASCII 58 + {59, 1, 5, 2, 0, -3}, // ASCII 59 + {60, 2, 3, 3, 0, -4}, // ASCII 60 + {61, 2, 3, 3, 0, -4}, // ASCII 61 + {62, 2, 3, 3, 0, -4}, // ASCII 62 + {63, 2, 6, 3, 0, -5}, // ASCII 63 + {65, 4, 6, 5, 0, -5}, // ASCII 64 + {68, 3, 6, 4, 0, -5}, // ASCII 65 + {71, 3, 6, 4, 0, -5}, // ASCII 66 + {74, 2, 6, 3, 0, -5}, // ASCII 67 + {76, 3, 6, 4, 0, -5}, // ASCII 68 + {79, 2, 6, 3, 0, -5}, // ASCII 69 + {81, 2, 6, 3, 0, -5}, // ASCII 70 + {83, 3, 6, 4, 0, -5}, // ASCII 71 + {86, 3, 6, 4, 0, -5}, // ASCII 72 + {89, 1, 6, 2, 0, -5}, // ASCII 73 + {90, 2, 6, 3, 0, -5}, // ASCII 74 + {92, 3, 6, 4, 0, -5}, // ASCII 75 + {95, 2, 6, 3, 0, -5}, // ASCII 76 + {97, 5, 6, 6, 0, -5}, // ASCII 77 + {101, 4, 6, 5, 0, -5}, // ASCII 78 + {104, 3, 6, 4, 0, -5}, // ASCII 79 + {107, 3, 6, 4, 0, -5}, // ASCII 80 + {110, 3, 6, 4, 0, -5}, // ASCII 81 + {113, 3, 6, 4, 0, -5}, // ASCII 82 + {116, 2, 6, 3, 0, -5}, // ASCII 83 + {118, 3, 6, 4, 0, -5}, // ASCII 84 + {121, 3, 6, 4, 0, -5}, // ASCII 85 + {124, 3, 6, 4, 0, -5}, // ASCII 86 + {127, 5, 6, 6, 0, -5}, // ASCII 87 + {131, 3, 6, 4, 0, -5}, // ASCII 88 + {134, 3, 6, 4, 0, -5}, // ASCII 89 + {137, 2, 6, 3, 0, -5}, // ASCII 90 + {139, 2, 6, 3, 0, -5}, // ASCII 91 + {141, 3, 6, 4, 0, -5}, // ASCII 92 + {144, 2, 6, 3, 0, -5}, // ASCII 93 + {146, 3, 2, 4, 0, -5}, // ASCII 94 + {147, 2, 1, 3, 0, 0}, // ASCII 95 + {148, 2, 2, 3, 0, -5}, // ASCII 96 + {149, 3, 4, 4, 0, -3}, // ASCII 97 + {151, 3, 6, 4, 0, -5}, // ASCII 98 + {154, 2, 4, 3, 0, -3}, // ASCII 99 + {155, 3, 6, 4, 0, -5}, // ASCII 100 + {158, 3, 4, 4, 0, -3}, // ASCII 101 + {160, 2, 6, 3, 0, -5}, // ASCII 102 + {162, 3, 5, 4, 0, -3}, // ASCII 103 + {164, 3, 6, 4, 0, -5}, // ASCII 104 + {167, 1, 6, 2, 0, -5}, // ASCII 105 + {168, 2, 7, 3, 0, -5}, // ASCII 106 + {170, 3, 6, 4, 0, -5}, // ASCII 107 + {173, 1, 6, 2, 0, -5}, // ASCII 108 + {174, 5, 4, 6, 0, -3}, // ASCII 109 + {177, 3, 4, 4, 0, -3}, // ASCII 110 + {179, 3, 4, 4, 0, -3}, // ASCII 111 + {181, 3, 5, 4, 0, -3}, // ASCII 112 + {183, 3, 5, 4, 0, -3}, // ASCII 113 + {185, 3, 4, 4, 0, -3}, // ASCII 114 + {187, 2, 4, 3, 0, -3}, // ASCII 115 + {188, 2, 6, 3, 0, -5}, // ASCII 116 + {190, 3, 4, 4, 0, -3}, // ASCII 117 + {192, 3, 4, 4, 0, -3}, // ASCII 118 + {194, 5, 4, 6, 0, -3}, // ASCII 119 + {197, 3, 4, 4, 0, -3}, // ASCII 120 + {199, 3, 5, 4, 0, -3}, // ASCII 121 + {201, 2, 4, 3, 0, -3}, // ASCII 122 + {202, 3, 6, 4, 0, -5}, // ASCII 123 + {205, 1, 6, 2, 0, -5}, // ASCII 124 + {206, 3, 6, 4, 0, -5}, // ASCII 125 + {209, 4, 2, 5, 0, -5}, // ASCII 126 +}; + +} // namespace ui +} // namespace mesh diff --git a/test/fixtures/small_message_font/squeezed6.bdf b/test/fixtures/small_message_font/squeezed6.bdf new file mode 100644 index 00000000..97f16205 --- /dev/null +++ b/test/fixtures/small_message_font/squeezed6.bdf @@ -0,0 +1,1159 @@ +STARTFONT 2.1 +COMMENT Exported by Fony v1.4.7 +FONT u8g2_squeezed_regular_6 +SIZE 7 96 96 +FONTBOUNDINGBOX 9 6 0 -1 +STARTPROPERTIES 6 +COPYRIGHT "public domain" +RESOLUTION_X 96 +RESOLUTION_Y 96 +FONT_ASCENT 6 +FONT_DESCENT 1 +DEFAULT_CHAR 0 +ENDPROPERTIES +CHARS 95 +STARTCHAR 032 +ENCODING 32 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 0 0 0 0 +BITMAP +ENDCHAR +STARTCHAR 033 +ENCODING 33 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 6 0 0 +BITMAP +80 +80 +80 +80 +00 +80 +ENDCHAR +STARTCHAR 034 +ENCODING 34 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 2 0 4 +BITMAP +A0 +A0 +ENDCHAR +STARTCHAR 035 +ENCODING 35 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +50 +F8 +50 +F8 +50 +ENDCHAR +STARTCHAR 036 +ENCODING 36 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 7 0 -1 +BITMAP +20 +78 +A0 +70 +28 +F0 +20 +ENDCHAR +STARTCHAR 037 +ENCODING 37 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +20 +40 +40 +80 +A0 +ENDCHAR +STARTCHAR 038 +ENCODING 38 +SWIDTH 360 0 +DWIDTH 5 0 +BBX 4 6 0 0 +BITMAP +40 +A0 +40 +A0 +A0 +50 +ENDCHAR +STARTCHAR 039 +ENCODING 39 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 2 0 4 +BITMAP +80 +80 +ENDCHAR +STARTCHAR 040 +ENCODING 40 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +80 +80 +80 +80 +40 +ENDCHAR +STARTCHAR 041 +ENCODING 41 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +80 +40 +40 +40 +40 +80 +ENDCHAR +STARTCHAR 042 +ENCODING 42 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 5 0 1 +BITMAP +A0 +40 +E0 +40 +A0 +ENDCHAR +STARTCHAR 043 +ENCODING 43 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 3 0 2 +BITMAP +40 +E0 +40 +ENDCHAR +STARTCHAR 044 +ENCODING 44 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 2 0 -1 +BITMAP +80 +80 +ENDCHAR +STARTCHAR 045 +ENCODING 45 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 1 0 3 +BITMAP +C0 +ENDCHAR +STARTCHAR 046 +ENCODING 46 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 1 0 0 +BITMAP +80 +ENDCHAR +STARTCHAR 047 +ENCODING 47 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +20 +20 +40 +40 +80 +80 +ENDCHAR +STARTCHAR 048 +ENCODING 48 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +A0 +A0 +A0 +40 +ENDCHAR +STARTCHAR 049 +ENCODING 49 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +C0 +40 +40 +40 +40 +ENDCHAR +STARTCHAR 050 +ENCODING 50 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +80 +40 +40 +80 +80 +C0 +ENDCHAR +STARTCHAR 051 +ENCODING 51 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +40 +80 +40 +40 +80 +ENDCHAR +STARTCHAR 052 +ENCODING 52 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +E0 +20 +20 +20 +ENDCHAR +STARTCHAR 053 +ENCODING 53 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +80 +80 +40 +40 +80 +ENDCHAR +STARTCHAR 054 +ENCODING 54 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +80 +C0 +A0 +A0 +40 +ENDCHAR +STARTCHAR 055 +ENCODING 55 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +20 +40 +40 +40 +40 +ENDCHAR +STARTCHAR 056 +ENCODING 56 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +40 +A0 +A0 +40 +ENDCHAR +STARTCHAR 057 +ENCODING 57 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +60 +20 +20 +40 +ENDCHAR +STARTCHAR 058 +ENCODING 58 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 3 0 1 +BITMAP +80 +00 +80 +ENDCHAR +STARTCHAR 059 +ENCODING 59 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 5 0 -1 +BITMAP +80 +00 +00 +80 +80 +ENDCHAR +STARTCHAR 060 +ENCODING 60 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 3 0 2 +BITMAP +40 +80 +40 +ENDCHAR +STARTCHAR 061 +ENCODING 61 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 3 0 2 +BITMAP +C0 +00 +C0 +ENDCHAR +STARTCHAR 062 +ENCODING 62 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 3 0 2 +BITMAP +80 +40 +80 +ENDCHAR +STARTCHAR 063 +ENCODING 63 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +80 +40 +80 +80 +00 +80 +ENDCHAR +STARTCHAR 064 +ENCODING 64 +SWIDTH 360 0 +DWIDTH 5 0 +BBX 4 6 0 0 +BITMAP +60 +90 +B0 +B0 +80 +70 +ENDCHAR +STARTCHAR 065 +ENCODING 65 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 066 +ENCODING 66 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +C0 +A0 +C0 +A0 +A0 +C0 +ENDCHAR +STARTCHAR 067 +ENCODING 67 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +80 +80 +80 +80 +40 +ENDCHAR +STARTCHAR 068 +ENCODING 68 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +C0 +A0 +A0 +A0 +A0 +C0 +ENDCHAR +STARTCHAR 069 +ENCODING 69 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +80 +C0 +80 +80 +C0 +ENDCHAR +STARTCHAR 070 +ENCODING 70 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +80 +C0 +80 +80 +80 +ENDCHAR +STARTCHAR 071 +ENCODING 71 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +60 +80 +A0 +A0 +A0 +60 +ENDCHAR +STARTCHAR 072 +ENCODING 72 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 073 +ENCODING 73 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 6 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR 074 +ENCODING 74 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +40 +40 +40 +40 +C0 +ENDCHAR +STARTCHAR 075 +ENCODING 75 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 076 +ENCODING 76 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +80 +80 +80 +80 +80 +C0 +ENDCHAR +STARTCHAR 077 +ENCODING 77 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 6 0 0 +BITMAP +88 +D8 +A8 +88 +88 +88 +ENDCHAR +STARTCHAR 078 +ENCODING 78 +SWIDTH 360 0 +DWIDTH 5 0 +BBX 4 6 0 0 +BITMAP +90 +D0 +B0 +90 +90 +90 +ENDCHAR +STARTCHAR 079 +ENCODING 79 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +A0 +A0 +A0 +40 +ENDCHAR +STARTCHAR 080 +ENCODING 80 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +C0 +A0 +C0 +80 +80 +80 +ENDCHAR +STARTCHAR 081 +ENCODING 81 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +40 +A0 +A0 +A0 +A0 +60 +ENDCHAR +STARTCHAR 082 +ENCODING 82 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +C0 +A0 +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 083 +ENCODING 83 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +80 +80 +40 +40 +80 +ENDCHAR +STARTCHAR 084 +ENCODING 84 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR 085 +ENCODING 85 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR 086 +ENCODING 86 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +A0 +A0 +40 +40 +ENDCHAR +STARTCHAR 087 +ENCODING 87 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 6 0 0 +BITMAP +88 +88 +88 +A8 +A8 +50 +ENDCHAR +STARTCHAR 088 +ENCODING 88 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +40 +40 +A0 +A0 +ENDCHAR +STARTCHAR 089 +ENCODING 89 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +A0 +40 +40 +40 +ENDCHAR +STARTCHAR 090 +ENCODING 90 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +40 +40 +80 +80 +C0 +ENDCHAR +STARTCHAR 091 +ENCODING 91 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +80 +80 +80 +80 +C0 +ENDCHAR +STARTCHAR 092 +ENCODING 92 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +80 +80 +40 +40 +20 +20 +ENDCHAR +STARTCHAR 093 +ENCODING 93 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +C0 +40 +40 +40 +40 +C0 +ENDCHAR +STARTCHAR 094 +ENCODING 94 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 2 0 4 +BITMAP +40 +A0 +ENDCHAR +STARTCHAR 095 +ENCODING 95 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 1 0 0 +BITMAP +C0 +ENDCHAR +STARTCHAR 096 +ENCODING 96 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 2 0 4 +BITMAP +80 +40 +ENDCHAR +STARTCHAR 097 +ENCODING 97 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +60 +A0 +A0 +60 +ENDCHAR +STARTCHAR 098 +ENCODING 98 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +80 +80 +C0 +A0 +A0 +C0 +ENDCHAR +STARTCHAR 099 +ENCODING 99 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 4 0 0 +BITMAP +40 +80 +80 +40 +ENDCHAR +STARTCHAR 100 +ENCODING 100 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +20 +20 +60 +A0 +A0 +60 +ENDCHAR +STARTCHAR 101 +ENCODING 101 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +40 +A0 +C0 +60 +ENDCHAR +STARTCHAR 102 +ENCODING 102 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +40 +80 +C0 +80 +80 +80 +ENDCHAR +STARTCHAR 103 +ENCODING 103 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 5 0 -1 +BITMAP +60 +A0 +60 +20 +C0 +ENDCHAR +STARTCHAR 104 +ENCODING 104 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +80 +80 +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 105 +ENCODING 105 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 6 0 0 +BITMAP +80 +00 +80 +80 +80 +80 +ENDCHAR +STARTCHAR 106 +ENCODING 106 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 7 0 -1 +BITMAP +40 +00 +40 +40 +40 +40 +80 +ENDCHAR +STARTCHAR 107 +ENCODING 107 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +80 +80 +A0 +C0 +A0 +A0 +ENDCHAR +STARTCHAR 108 +ENCODING 108 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 6 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR 109 +ENCODING 109 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 4 0 0 +BITMAP +F0 +A8 +A8 +A8 +ENDCHAR +STARTCHAR 110 +ENCODING 110 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR 111 +ENCODING 111 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +40 +A0 +A0 +40 +ENDCHAR +STARTCHAR 112 +ENCODING 112 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 5 0 -1 +BITMAP +C0 +A0 +A0 +C0 +80 +ENDCHAR +STARTCHAR 113 +ENCODING 113 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 5 0 -1 +BITMAP +60 +A0 +A0 +60 +20 +ENDCHAR +STARTCHAR 114 +ENCODING 114 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +A0 +C0 +80 +80 +ENDCHAR +STARTCHAR 115 +ENCODING 115 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 4 0 0 +BITMAP +40 +80 +40 +80 +ENDCHAR +STARTCHAR 116 +ENCODING 116 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 6 0 0 +BITMAP +80 +80 +C0 +80 +80 +40 +ENDCHAR +STARTCHAR 117 +ENCODING 117 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR 118 +ENCODING 118 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +A0 +A0 +A0 +C0 +ENDCHAR +STARTCHAR 119 +ENCODING 119 +SWIDTH 432 0 +DWIDTH 6 0 +BBX 5 4 0 0 +BITMAP +88 +A8 +A8 +50 +ENDCHAR +STARTCHAR 120 +ENCODING 120 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 4 0 0 +BITMAP +A0 +40 +40 +A0 +ENDCHAR +STARTCHAR 121 +ENCODING 121 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 5 0 -1 +BITMAP +A0 +A0 +60 +20 +40 +ENDCHAR +STARTCHAR 122 +ENCODING 122 +SWIDTH 216 0 +DWIDTH 3 0 +BBX 2 4 0 0 +BITMAP +C0 +40 +80 +C0 +ENDCHAR +STARTCHAR 123 +ENCODING 123 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +20 +40 +C0 +40 +40 +20 +ENDCHAR +STARTCHAR 124 +ENCODING 124 +SWIDTH 144 0 +DWIDTH 2 0 +BBX 1 6 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR 125 +ENCODING 125 +SWIDTH 288 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +80 +40 +60 +40 +40 +80 +ENDCHAR +STARTCHAR 126 +ENCODING 126 +SWIDTH 360 0 +DWIDTH 5 0 +BBX 4 2 0 4 +BITMAP +50 +A0 +ENDCHAR +ENDFONT diff --git a/test/fixtures/ssd1306_picopixel/render.cpp b/test/fixtures/ssd1306_picopixel/render.cpp index f0cbc381..49fb5c03 100644 --- a/test/fixtures/ssd1306_picopixel/render.cpp +++ b/test/fixtures/ssd1306_picopixel/render.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -12,8 +12,13 @@ void save(const char* file) { f << "P1\n128 64\n"; for (int y=0;y<64;y++) { for (int x=0;x<128;x++) f << c.getPixel(x,y) << ' '; f << '\n'; } } +class TestSSD1306Display : public SSD1306Display { +public: + void setCanvasSize(int w, int h) { setDimensions(w, h); } +}; + int main(int argc, char** argv) { - SSD1306Display d; + TestSSD1306Display d; assert(d.begin()); auto& c=*Adafruit_SSD1306::last; const char* message="GIANT KILLER: Out on the Mercerwood mesh today, just the V4 and a small battery Checking the smaller font so the rest of this message is visible. 0123456789 END"; @@ -32,7 +37,21 @@ int main(int argc, char** argv) { #if UI_SMALL_MESSAGE_FONT == 1 assert(c.outside==0); assert(d.getTextWidth("ABC")==18); // regular driver font untouched - mesh::ui::Pixel5Text text(d); + mesh::ui::SmallMessageText six(d); + assert(six.capitalHeight()==6); + for (int ch=32;ch<=126;ch++) { + d.startFrame(); six.setCursor(0,0); + const std::string glyph(1,char(ch)); + six.print(glyph.c_str()); + // Compared with the independent upstream BDF in the Python harness. + std::cout << "six " << ch << ' ' << six.getTextWidth(glyph.c_str()) << ' '; + for (int y=0;y<8;y++) for (int x=0;x<8;x++) + std::cout << int(c.getPixel(x,y)); + std::cout << '\n'; + } + d.setCanvasSize(72,40); + mesh::ui::SmallMessageText text(d); + assert(text.capitalHeight()==5); GFXcanvas1 reference(128,64); reference.setFont(&Picopixel); reference.setTextColor(1); @@ -44,6 +63,6 @@ int main(int argc, char** argv) { assert(c.getPixel(x,y)==reference.getPixel(x,y)); assert(text.getTextWidth(std::string(1,char(ch)).c_str())==PicopixelGlyphs[ch-32].xAdvance); } - std::cout << "95 glyphs match Adafruit GFX Picopixel pixel for pixel; normal font preserved\n"; + std::cout << "95 tiny-screen glyphs match Adafruit GFX Picopixel; normal font preserved\n"; #endif } diff --git a/test/test_display_driver/test_display_driver.cpp b/test/test_display_driver/test_display_driver.cpp index f52f67d5..e6afb2de 100644 --- a/test/test_display_driver/test_display_driver.cpp +++ b/test/test_display_driver/test_display_driver.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -134,9 +134,9 @@ TEST(DisplayDriver, EllipsizesOnlyAtUTF8CodepointBoundaries) { EXPECT_TRUE(isValidUTF8(display.printed.c_str())); } -TEST(Pixel5Text, CapitalsAreFivePixelsAndNormalFontIsUntouched) { - TestDisplay display(128, 64, 6); - mesh::ui::Pixel5Text text(display); +TEST(SmallMessageText, TinyPanelsKeepFivePixelCapitalsAndNormalFont) { + TestDisplay display(72, 40, 6); + mesh::ui::SmallMessageText text(display); text.setCursor(0, 0); text.print("A"); const char* rows[] = {"010", "101", "111", "101", "101"}; @@ -152,7 +152,41 @@ TEST(Pixel5Text, CapitalsAreFivePixelsAndNormalFontIsUntouched) { EXPECT_EQ(18, display.getTextWidth("ABC")); } -TEST(Pixel5Text, CompactOriginAllowsSixCompleteMessageRows) { +TEST(SmallMessageText, LargerPanelsUseSixPixelCapitalsAndNormalFont) { + TestDisplay display(128, 64, 6); + mesh::ui::SmallMessageText text(display); + text.setCursor(0, 0); + text.print("A"); + const char* rows[] = {"010", "101", "111", "101", "101", "101"}; + bool pixels[6][3] = {}; + for (const auto& pixel : display.fills) { + ASSERT_GE(pixel.x, 0); ASSERT_LT(pixel.x, 3); + ASSERT_GE(pixel.y, 0); ASSERT_LT(pixel.y, 6); + pixels[pixel.y][pixel.x] = true; + } + for (int y = 0; y < 6; ++y) for (int x = 0; x < 3; ++x) + EXPECT_EQ(rows[y][x] == '1', pixels[y][x]); + EXPECT_EQ(11, text.getTextWidth("ABC")); + EXPECT_EQ(18, display.getTextWidth("ABC")); +} + +TEST(SmallMessageText, FontChoiceAndLineSpacingFollowPanelGeometry) { + const int cases[][3] = { + {128, 64, 6}, {64, 128, 6}, {160, 80, 6}, + {72, 40, 5}, {40, 72, 5}, {128, 32, 5}, {32, 128, 5}, + {64, 48, 5}, {48, 64, 5}}; + for (const auto& dimensions : cases) { + TestDisplay display(dimensions[0], dimensions[1]); + mesh::ui::SmallMessageText text(display); + EXPECT_EQ(dimensions[2], text.capitalHeight()); + EXPECT_EQ(dimensions[2] + 1, text.glyphHeight()); + EXPECT_EQ(dimensions[2] + 2, text.lineHeight()); + EXPECT_EQ(0, text.lineCount(display.height() - text.glyphHeight() + 1)); + EXPECT_EQ(1, text.lineCount(display.height() - text.glyphHeight())); + } +} + +TEST(SmallMessageText, CompactOriginAllowsFiveCompleteSixPixelMessageRows) { TestDisplay display(128, 64); mesh::ui::drawSmallMessageBody(display, std::string(61, 'W').c_str(), std::string(160, 'W').c_str()); @@ -162,33 +196,87 @@ TEST(Pixel5Text, CompactOriginAllowsSixCompleteMessageRows) { EXPECT_GE(pixel.x, 0); EXPECT_LT(pixel.x, 128); EXPECT_GE(pixel.y, 14); EXPECT_LT(pixel.y, 64); // The origin stays in its own line; its long name cannot cover the body. - EXPECT_TRUE(pixel.y < 20 || pixel.y >= 21); + EXPECT_TRUE(pixel.y < 21 || pixel.y >= 22); for (int i = 0; i < 3; ++i) - if (pixel.y == 60 && pixel.x == 120 + 2 * i) final_dots[i] = true; + if (pixel.y == 59 && pixel.x == 120 + 2 * i) final_dots[i] = true; } for (bool dot : final_dots) EXPECT_TRUE(dot); - EXPECT_EQ(5, mesh::ui::smallMessageLineCount(64, 25)); - EXPECT_EQ(6, mesh::ui::smallMessageLineCount(64, 21)); - EXPECT_EQ(3, mesh::ui::smallMessageLineCount(40, 17)); - EXPECT_EQ(0, mesh::ui::smallMessageLineCount(64, 59)); + mesh::ui::SmallMessageText text(display); + EXPECT_EQ(5, text.lineCount(22)); + EXPECT_EQ(4, text.lineCount(22, 56)); + EXPECT_EQ(5, text.lineCount(22, 80)); // never extend past the panel + EXPECT_EQ(0, text.lineCount(22, 20)); } -TEST(Pixel5Text, GlyphsStayInsideTinyAndRotatedScreens) { - for (auto dimensions : {std::pair{72, 40}, {64, 128}, {128, 64}}) { +TEST(SmallMessageText, TinyInterfaceKeepsThreeCompleteFivePixelRows) { + TestDisplay display(72, 40); + mesh::ui::SmallMessageText text(display); + EXPECT_EQ(3, text.lineCount(17)); + mesh::ui::drawSmallMessageBody(display, std::string(61, 'W').c_str(), + std::string(160, 'W').c_str(), 10); + bool final_dots[3] = {}; + for (const auto& pixel : display.fills) { + EXPECT_GE(pixel.x, 0); EXPECT_LT(pixel.x, 72); + EXPECT_GE(pixel.y, 10); EXPECT_LT(pixel.y, 40); + EXPECT_TRUE(pixel.y < 16 || pixel.y >= 17); + for (int i = 0; i < 3; ++i) + if (pixel.y == 35 && pixel.x == 60 + 2 * i) final_dots[i] = true; + } + for (bool dot : final_dots) EXPECT_TRUE(dot); +} + +TEST(SmallMessageText, MessageBodyRespectsReservedFooter) { + TestDisplay display(128, 64); + mesh::ui::drawSmallMessageBody(display, "Ch 0 Public [4h]:", + std::string(160, 'W').c_str(), 14, 56); + ASSERT_FALSE(display.fills.empty()); + for (const auto& pixel : display.fills) EXPECT_LT(pixel.y, 56); +} + +TEST(SmallMessageText, Full160CharacterSamplesFitInFiveRows) { + class RecordingText : public mesh::ui::SmallMessageText { + public: + std::string drawn; + explicit RecordingText(DisplayDriver& display) : SmallMessageText(display) {} + void print(const char* str) override { + drawn += str; + SmallMessageText::print(str); + } + }; + const char* samples[] = { + "GIANT KILLER: Out on the Mercerwood mesh today, just the V4 and a small " + "battery Checking the smaller font so the rest of this message is visible. " + "0123456789 END", + "NimBLE-XIAO-Trial: Picopixel: 5 pixel letters and a compact channel line. " + "More of this message now fits on the small screen. " + "0123456789 0123456789 012345678 END"}; + for (const char* sample : samples) { + ASSERT_EQ(160U, strlen(sample)); + TestDisplay display(128, 64); + RecordingText text(display); + EXPECT_EQ(5, mesh::ui::drawTextWrapped(text, 0, 22, 128, + text.lineHeight(), text.lineCount(22), sample)); + EXPECT_EQ(sample, text.drawn); + } +} + +TEST(SmallMessageText, GlyphsStayInsideTinyAndRotatedScreens) { + for (auto dimensions : {std::pair{72, 40}, {40, 72}, + {128, 32}, {32, 128}, {64, 128}, {128, 64}}) { TestDisplay display(dimensions.first, dimensions.second); - mesh::ui::Pixel5Text text(display); + mesh::ui::SmallMessageText text(display); for (int c = 32; c <= 126; ++c) { display.fills.clear(); - text.setCursor(0, display.height() - 6); + text.setCursor(0, display.height() - text.glyphHeight()); text.printWordWrap(std::string(160, char(c)).c_str(), display.width()); for (const auto& pixel : display.fills) { EXPECT_GE(pixel.x, 0); EXPECT_LT(pixel.x, display.width()); - EXPECT_GE(pixel.y, display.height() - 6); + EXPECT_GE(pixel.y, display.height() - text.glyphHeight()); EXPECT_LT(pixel.y, display.height()); } } display.fills.clear(); - text.setCursor(0, display.height() - 5); + text.setCursor(0, display.height() - text.glyphHeight() + 1); text.printWordWrap("must not start a clipped final line", display.width()); EXPECT_TRUE(display.fills.empty()); } diff --git a/test/test_ssd1306_picopixel.py b/test/test_ssd1306_picopixel.py index ff649907..61cc787e 100644 --- a/test/test_ssd1306_picopixel.py +++ b/test/test_ssd1306_picopixel.py @@ -3,11 +3,13 @@ Build a V4 environment first to install Adafruit GFX, or point MESHCORE_GFX_LIBRARY to that library's directory. Only the hardware panel, -Arduino strings and Print glue are replaced; the shared pixel renderer is compared with the original Adafruit font. +Arduino strings and Print glue are replaced. Picopixel is compared with the +original Adafruit font; Squeezed Regular 6 with its upstream BDF glyphs. """ import os from pathlib import Path +import re import subprocess import tempfile import unittest @@ -16,7 +18,28 @@ ROOT = Path(__file__).resolve().parents[1] FIXTURE = ROOT / "test/fixtures/ssd1306_picopixel" -class SSD1306PicopixelTest(unittest.TestCase): +class SSD1306SmallMessageFontTest(unittest.TestCase): + def assert_squeezed_glyphs_match_upstream(self, output): + rendered = {} + for line in output.splitlines(): + if line.startswith("six "): + _, code, advance, pixels = line.split() + rendered[int(code)] = (int(advance), pixels) + self.assertEqual(set(rendered), set(range(32, 127))) + source = (ROOT / "test/fixtures/small_message_font/squeezed6.bdf").read_text() + for entry in source.split("STARTCHAR ")[1:]: + code = int(re.search(r"^ENCODING (\d+)", entry, re.M)[1]) + advance = int(re.search(r"^DWIDTH (\d+)", entry, re.M)[1]) + w, h, x, y = map(int, re.search(r"^BBX (.+)$", entry, re.M)[1].split()) + rows = entry.split("BITMAP\n", 1)[1].split("ENDCHAR", 1)[0].splitlines() + expected = ["0"] * 64 + for row, bits in enumerate(rows): + for col in range(w): + if int(bits, 16) & (1 << (len(bits) * 4 - 1 - col)): + expected[(6 - h - y + row) * 8 + x + col] = "1" + with self.subTest(glyph=chr(code)): + self.assertEqual(rendered[code], (advance, "".join(expected))) + def test_actual_font_bounds_wrapping_and_default_font_restoration(self): configured = os.environ.get("MESHCORE_GFX_LIBRARY") candidates = [Path(configured)] if configured else sorted( @@ -29,7 +52,7 @@ class SSD1306PicopixelTest(unittest.TestCase): with tempfile.TemporaryDirectory(prefix="meshcore-picopixel-") as temp: for enabled in (0, 1): - with self.subTest(picopixel=enabled): + with self.subTest(small_message_font=enabled): binary = Path(temp) / f"render-{enabled}" result = subprocess.run([ "c++", "-std=c++17", "-g", @@ -45,6 +68,8 @@ class SSD1306PicopixelTest(unittest.TestCase): result = subprocess.run( [str(binary)], text=True, capture_output=True) self.assertEqual(result.returncode, 0, result.stderr) + if enabled: + self.assert_squeezed_glyphs_match_upstream(result.stdout) if __name__ == "__main__":