mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-11-07 16:27:21 +00:00
A name that opens with emoji, such as a flag and an island, drew an empty avatar. The chat icon taken from the first emoji in a name (#640) took the first code point with ANY row in the emoji table, and the table maps the invisible joiners (U+200D, the U+FE0E/FE0F variation selectors) to an empty 2 x 2 image so text never shows them. The island's U+FE0F won, and the icon was that empty image. A ZWJ sequence such as man + laptop did the same through its U+200D. - The emoji pick skips the empty joiner rows; the real emoji after them still becomes the icon. A picked chat icon that is a joiner, or a text symbol the font cannot draw, keeps the plain avatar instead of a blank. - Initials take whole code points and only letters or digits: emoji, flags, symbols and combining accents are skipped, so the flag name shows "TR" instead of two flag halves, "ana" with a tilde shows its capital. A letter the font only has as the '*' stand-in becomes its plain letter, or the next letter; "?" when the name has none, never blank. - The logic lives in src/ui-touch/AvatarText.h (pure) with a host test (test/test_avatar_text.cpp) that first reproduces the blank pick with the old rule. The section-label uppercase table moved there unchanged. - web demo backend: WADA_DEMO_CONTACTS="a|b|..." adds contacts for a scripted headless run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
4.9 KiB
C++
88 lines
4.9 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Avatar text (#674): the initials and the emoji icon taken from a name.
|
|
// Build: g++ -std=c++17 -I src -o /tmp/test_avatar_text test/test_avatar_text.cpp
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <set>
|
|
|
|
#include "ui-touch/AvatarText.h"
|
|
|
|
// A font like the device's: ASCII and Latin-1, no flags or emoji.
|
|
static bool latinFont(uint32_t cp, void*) { return cp < 0x100; }
|
|
// A narrower one: ASCII only, so an accented letter needs its plain letter.
|
|
static bool asciiFont(uint32_t cp, void*) { return cp < 0x80; }
|
|
// The firmware's chained faces, which also carry Greek and Cyrillic.
|
|
static bool wideFont(uint32_t cp, void*) { return cp < 0x530; }
|
|
|
|
// The baked emoji set as the firmware has it: the rows that matter here. The joiner
|
|
// and the variation selectors are in the table as an empty 2 x 2 image (so text never
|
|
// shows them); the visible check is what the UI passes, true for a real picture only.
|
|
static const std::set<uint32_t> kTable = {0x200D, 0xFE0E, 0xFE0F, 0x1F1F8, 0x1F600, 0x1F44D};
|
|
static const std::set<uint32_t> kInvisible = {0x200D, 0xFE0E, 0xFE0F, 0x1F1F8};
|
|
static bool anyRow(uint32_t cp, void*) { return kTable.count(cp) != 0; } // the old check
|
|
static bool visibleRow(uint32_t cp, void*) { return kTable.count(cp) && !kInvisible.count(cp); }
|
|
|
|
static std::string ini(const char* name, AvatarText::HasGlyph has = latinFont) {
|
|
char out[12];
|
|
AvatarText::initials(name, out, sizeof out, has, nullptr);
|
|
return out;
|
|
}
|
|
|
|
// The names from the report: a flag (U+1F1F5 U+1F1F9) and an island with its emoji
|
|
// variation selector (U+1F3DD U+FE0F), and a man (U+1F468) after the name.
|
|
static const char kTristaoEdc[] = "\xF0\x9F\x87\xB5\xF0\x9F\x87\xB9\xF0\x9F\x8F\x9D\xEF\xB8\x8F Trist\xC3\xA3o EDC";
|
|
static const char kTristaoMobile[] = "Trist\xC3\xA3o Mobile \xF0\x9F\x91\xA8";
|
|
static const char kTristaoZwj[] = "Trist\xC3\xA3o \xF0\x9F\x91\xA8\xE2\x80\x8D\xF0\x9F\x92\xBB"; // man + ZWJ + laptop
|
|
|
|
int main() {
|
|
char eb[20];
|
|
|
|
// The bug, reproduced with the old rule (any table row counts): the island's
|
|
// variation selector is the "first emoji", an empty image, so the tile is blank.
|
|
assert(AvatarText::firstEmoji(kTristaoEdc, eb, sizeof eb, anyRow, nullptr));
|
|
assert(!strcmp(eb, "\xEF\xB8\x8F"));
|
|
assert(AvatarText::firstEmoji(kTristaoZwj, eb, sizeof eb, anyRow, nullptr));
|
|
assert(!strcmp(eb, "\xE2\x80\x8D"));
|
|
// Fixed: only a visible picture is an icon; these names keep their dot pattern.
|
|
assert(!AvatarText::firstEmoji(kTristaoEdc, eb, sizeof eb, visibleRow, nullptr));
|
|
assert(!AvatarText::firstEmoji(kTristaoMobile, eb, sizeof eb, visibleRow, nullptr));
|
|
assert(!AvatarText::firstEmoji(kTristaoZwj, eb, sizeof eb, visibleRow, nullptr));
|
|
assert(!AvatarText::firstEmoji("\xF0\x9F\x87\xB8\xF0\x9F\x87\xAA Sweden", eb, sizeof eb, visibleRow, nullptr));
|
|
// A real emoji still becomes the icon (#640), after a joiner too.
|
|
assert(AvatarText::firstEmoji("Base \xEF\xB8\x8F\xF0\x9F\x98\x80 camp", eb, sizeof eb, visibleRow, nullptr));
|
|
assert(!strcmp(eb, "\xF0\x9F\x98\x80"));
|
|
assert(!AvatarText::firstEmoji("\xC2\xA9 corp", eb, sizeof eb, visibleRow, nullptr));
|
|
assert(!AvatarText::firstEmoji("Plain name", eb, sizeof eb, visibleRow, nullptr));
|
|
|
|
// Initials: whole code points, in capitals, emoji and symbols skipped.
|
|
assert(ini("\xC3\xA3na") == "\xC3\x83N"); // "ãna" -> "ÃN"
|
|
assert(ini("Jo\xC3\xA3o") == "JO");
|
|
assert(ini("Jo""a\xCC\x83o") == "JO"); // a + combining tilde
|
|
assert(ini("\xC3\x89mile") == "\xC3\x89M"); // "Émile" -> "ÉM"
|
|
assert(ini("Zo\xC3\xAB") == "ZO");
|
|
assert(ini(kTristaoEdc) == "TR"); // was the two flag halves
|
|
assert(ini(kTristaoMobile) == "TR");
|
|
assert(ini("#hiking") == "HI");
|
|
assert(ini(" [base] camp") == "BA");
|
|
assert(ini("42 Node") == "42");
|
|
assert(ini("\xD0\xB4\xD0\xB8\xD0\xBC\xD0\xB0", wideFont) == "\xD0\x94\xD0\x98"); // Cyrillic "дима" -> "ДИ"
|
|
// A font without the accented capital: its plain letter.
|
|
assert(ini("\xC3\xA3na", asciiFont) == "AN");
|
|
// Nothing to draw: "?", never blank.
|
|
assert(ini("\xF0\x9F\x87\xB5\xF0\x9F\x87\xB9") == "?");
|
|
assert(ini("") == "?");
|
|
assert(ini(nullptr) == "?");
|
|
assert(ini("\xFF\xFE") == "?"); // malformed bytes
|
|
// Two letters only, and never past the buffer.
|
|
char small[4];
|
|
AvatarText::initials("\xC3\xA3\xC3\xA9", small, sizeof small, latinFont, nullptr);
|
|
assert(!strcmp(small, "\xC3\x83")); // the second does not fit
|
|
|
|
// The uppercase table the section labels use, moved here unchanged.
|
|
assert(AvatarText::upper('a') == 'A' && AvatarText::upper(0xE3) == 0xC3 && AvatarText::upper(0xF7) == 0xF7);
|
|
assert(AvatarText::upper(0x101) == 0x100 && AvatarText::upper(0x3C2) == 0x3A3 && AvatarText::upper(0x451) == 0x401);
|
|
|
|
puts("avatar text: emoji icon skips joiners, initials are whole letters, never blank PASS");
|
|
}
|