mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-27 21:19:56 +00:00
fix: support NomadNet grayscale colors
This commit is contained in:
@@ -59,7 +59,31 @@ bool first_codepoint(const std::string& value, std::size_t offset,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool color(const std::string& value, uint32_t& result) {
|
||||
bool parse_micron_color(const std::string& value, uint32_t& result) {
|
||||
if (value.size() == 3 && value[0] == 'g') {
|
||||
if (!std::isdigit(static_cast<unsigned char>(value[1])) ||
|
||||
!std::isdigit(static_cast<unsigned char>(value[2]))) return false;
|
||||
const uint32_t percent = static_cast<uint32_t>(value[1] - '0') * 10 +
|
||||
static_cast<uint32_t>(value[2] - '0');
|
||||
// Urwid first scales gNN to 0..255, then chooses the nearest xterm
|
||||
// grayscale ramp entry. Preserve that quantization before RGB565.
|
||||
const uint32_t scaled = (percent * 255 * 2 + 100) / 200;
|
||||
static constexpr uint8_t levels[] = {
|
||||
0, 8, 18, 28, 38, 48, 58, 68, 78, 88, 98, 108, 118,
|
||||
128, 132, 148, 158, 168, 178, 188, 198, 208, 218, 228, 238, 255,
|
||||
};
|
||||
uint8_t gray = levels[sizeof(levels) - 1];
|
||||
for (std::size_t i = 0; i + 1 < sizeof(levels); ++i) {
|
||||
const uint32_t midpoint =
|
||||
(static_cast<uint32_t>(levels[i]) + levels[i + 1] + 1) / 2;
|
||||
if (scaled < midpoint) {
|
||||
gray = levels[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = static_cast<uint32_t>(gray) * 0x010101;
|
||||
return true;
|
||||
}
|
||||
if (value.size() != 3 && value.size() != 6) return false;
|
||||
if (!std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isxdigit(c); })) return false;
|
||||
char* end = nullptr;
|
||||
@@ -127,7 +151,7 @@ void parse_inline(Document& doc, Block& block, const std::string& line, Style& s
|
||||
if (i < line.size() && line[i] == 'T') { ++i; count = 6; }
|
||||
if (i + count <= line.size()) {
|
||||
uint32_t value = 0;
|
||||
if (color(line.substr(i, count), value)) {
|
||||
if (parse_micron_color(line.substr(i, count), value)) {
|
||||
style.has_foreground = true;
|
||||
style.foreground = value;
|
||||
i += count;
|
||||
@@ -138,7 +162,7 @@ void parse_inline(Document& doc, Block& block, const std::string& line, Style& s
|
||||
if (i < line.size() && line[i] == 'T') { ++i; count = 6; }
|
||||
if (i + count <= line.size()) {
|
||||
uint32_t value = 0;
|
||||
if (color(line.substr(i, count), value)) {
|
||||
if (parse_micron_color(line.substr(i, count), value)) {
|
||||
style.has_background = true;
|
||||
style.background = value;
|
||||
i += count;
|
||||
@@ -236,13 +260,13 @@ Document DocumentParser::parse(const char* source, std::size_t size) const {
|
||||
}
|
||||
if (line.rfind("#!bg=", 0) == 0) {
|
||||
uint32_t value = 0;
|
||||
if (color(line.substr(5), value)) { doc.has_background = true; doc.background = value; }
|
||||
if (parse_micron_color(line.substr(5), value)) { doc.has_background = true; doc.background = value; }
|
||||
else doc.malformed = true;
|
||||
continue;
|
||||
}
|
||||
if (line.rfind("#!fg=", 0) == 0) {
|
||||
uint32_t value = 0;
|
||||
if (color(line.substr(5), value)) { doc.has_foreground = true; doc.foreground = value; }
|
||||
if (parse_micron_color(line.substr(5), value)) { doc.has_foreground = true; doc.foreground = value; }
|
||||
else doc.malformed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -231,6 +231,52 @@ int main(int argc, char** argv) {
|
||||
check("FT inline truecolor parses", saw_truecolor_foreground);
|
||||
check("BT inline truecolor parses", saw_truecolor_background);
|
||||
|
||||
// Byte-exact Urwid 2.6.16 AttrSpec g00..g99 truecolor expansion.
|
||||
static constexpr uint32_t grayscale_rgb[100] = {
|
||||
0x000000, 0x000000, 0x080808, 0x080808, 0x080808, 0x121212, 0x121212, 0x121212, 0x121212, 0x1c1c1c,
|
||||
0x1c1c1c, 0x1c1c1c, 0x1c1c1c, 0x262626, 0x262626, 0x262626, 0x262626, 0x303030, 0x303030, 0x303030,
|
||||
0x303030, 0x3a3a3a, 0x3a3a3a, 0x3a3a3a, 0x3a3a3a, 0x444444, 0x444444, 0x444444, 0x444444, 0x4e4e4e,
|
||||
0x4e4e4e, 0x4e4e4e, 0x4e4e4e, 0x585858, 0x585858, 0x585858, 0x585858, 0x626262, 0x626262, 0x626262,
|
||||
0x626262, 0x6c6c6c, 0x6c6c6c, 0x6c6c6c, 0x6c6c6c, 0x767676, 0x767676, 0x767676, 0x767676, 0x808080,
|
||||
0x808080, 0x848484, 0x848484, 0x848484, 0x848484, 0x949494, 0x949494, 0x949494, 0x949494, 0x949494,
|
||||
0x9e9e9e, 0x9e9e9e, 0x9e9e9e, 0x9e9e9e, 0xa8a8a8, 0xa8a8a8, 0xa8a8a8, 0xa8a8a8, 0xb2b2b2, 0xb2b2b2,
|
||||
0xb2b2b2, 0xb2b2b2, 0xbcbcbc, 0xbcbcbc, 0xbcbcbc, 0xbcbcbc, 0xc6c6c6, 0xc6c6c6, 0xc6c6c6, 0xc6c6c6,
|
||||
0xd0d0d0, 0xd0d0d0, 0xd0d0d0, 0xd0d0d0, 0xdadada, 0xdadada, 0xdadada, 0xdadada, 0xe4e4e4, 0xe4e4e4,
|
||||
0xe4e4e4, 0xe4e4e4, 0xeeeeee, 0xeeeeee, 0xeeeeee, 0xeeeeee, 0xeeeeee, 0xffffff, 0xffffff, 0xffffff,
|
||||
};
|
||||
for (std::size_t percent = 0; percent < 100; ++percent) {
|
||||
std::string token = "g00";
|
||||
token[1] = static_cast<char>('0' + percent / 10);
|
||||
token[2] = static_cast<char>('0' + percent % 10);
|
||||
const auto foreground_page = parser.parse(std::string("#!fg=") + token + "\ntext");
|
||||
const auto background_page = parser.parse(std::string("#!bg=") + token + "\ntext");
|
||||
const std::string name = std::string("page grayscale matches Urwid for ") + token;
|
||||
check(name.c_str(),
|
||||
foreground_page.has_foreground && foreground_page.foreground == grayscale_rgb[percent] &&
|
||||
background_page.has_background && background_page.background == grayscale_rgb[percent] &&
|
||||
!foreground_page.malformed && !background_page.malformed);
|
||||
}
|
||||
auto inline_grayscale = parser.parse("`Fg02dark`f `Bg51mid`b `Fg99light`f");
|
||||
bool saw_dark_gray = false;
|
||||
bool saw_mid_gray_background = false;
|
||||
bool saw_light_gray = false;
|
||||
for (const auto& run : inline_grayscale.blocks.front().runs) {
|
||||
if (run.text == "dark")
|
||||
saw_dark_gray = run.has_foreground && run.foreground == 0x080808;
|
||||
else if (run.text == "mid")
|
||||
saw_mid_gray_background = run.has_background && run.background == 0x848484;
|
||||
else if (run.text == "light")
|
||||
saw_light_gray = run.has_foreground && run.foreground == 0xffffff;
|
||||
}
|
||||
check("inline grayscale foreground and background match Urwid",
|
||||
saw_dark_gray && saw_mid_gray_background && saw_light_gray);
|
||||
for (const char* invalid : {"g0", "g100", "G50", "gx0", "g0x"}) {
|
||||
const auto malformed_grayscale = parser.parse(std::string("#!fg=") + invalid + "\ntext");
|
||||
const std::string name = std::string("malformed grayscale is rejected: ") + invalid;
|
||||
check(name.c_str(),
|
||||
malformed_grayscale.malformed && !malformed_grayscale.has_foreground);
|
||||
}
|
||||
|
||||
CompactPage::RunRecord light_background_run{};
|
||||
light_background_run.style = CompactPage::HAS_BACKGROUND;
|
||||
light_background_run.background = 0xe8b4f0;
|
||||
|
||||
Reference in New Issue
Block a user