From 8e693843fae12f21c2f1084b33094445fd443c0b Mon Sep 17 00:00:00 2001 From: fallenoak Date: Tue, 9 Dec 2025 22:34:14 -0600 Subject: [PATCH] feat(gx): handle remaining codes in GxuDetermineQuotedCode --- src/gx/font/GxuFont.cpp | 192 ++++++++++++++++++++++++++++++++++++--- test/gx/font/GxuFont.cpp | 137 ++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 11 deletions(-) diff --git a/src/gx/font/GxuFont.cpp b/src/gx/font/GxuFont.cpp index 326873d..a7bc1b3 100644 --- a/src/gx/font/GxuFont.cpp +++ b/src/gx/font/GxuFont.cpp @@ -23,19 +23,29 @@ QUOTEDCODE GxuDetermineQuotedCode(const char* text, int32_t& advance, CImVector* switch (wide) { case 0x0: - case 0xFFFFFFFF: + case 0xFFFFFFFF: { return CODE_INVALIDCODE; + } + + case '\r': { + auto next = SUniSGetUTF8(reinterpret_cast(text + 1), &advance); + advance = next == '\n' ? 2 : 1; - case '\r': - advance = 2 - (SUniSGetUTF8(reinterpret_cast(text + 1), &advance) != '\n'); return CODE_NEWLINE; + } - case '\n': + case '\n': { advance = 1; + return CODE_NEWLINE; + } + + default: { + break; + } } - if (wide != '|' || flags & 0x800) { + if (wide != '|' || flags & FLAG_IGNORE_PIPES) { return CODE_INVALIDCODE; } @@ -46,22 +56,182 @@ QUOTEDCODE GxuDetermineQuotedCode(const char* text, int32_t& advance, CImVector* } switch (quotedCode) { + case 'C': + case 'c': { + if (flags & FLAG_IGNORE_COLORS) { + return CODE_INVALIDCODE; + } + + int32_t offset = advance + 1; + uint8_t value[4]; + + for (int32_t i = 0; i < 4; i++) { + if (!text[offset + 0] || !text[offset + 1]) { + return CODE_INVALIDCODE; + } + + char hex[4]; + hex[0] = text[offset + 0]; + hex[1] = text[offset + 1]; + hex[2] = '\0'; + + char* end = nullptr; + + auto v = strtol(hex, &end, 16); + + // Error parsing hex + if (end && *end) { + return CODE_INVALIDCODE; + } + + value[i] = v; + + offset += 2; + } + + if (color) { + // Alpha is ignored + color->value = CImVector::MakeARGB(0xFF, value[1], value[2], value[3]); + } + + advance = 10; + + return CODE_COLORON; + } + + case 'H': { + if (flags & FLAG_IGNORE_HYPERLINKS) { + return CODE_INVALIDCODE; + } + + auto linkText = text + advance; + + while (*linkText) { + auto code = SUniSGetUTF8(reinterpret_cast(linkText), &advance); + linkText += advance; + + if (code == '|') { + break; + } + } + + if (!*linkText) { + return CODE_INVALIDCODE; + } + + auto endCode = SUniSGetUTF8(reinterpret_cast(linkText), &advance); + linkText += advance; + + // Null terminator or end code isn't |h (end link payload) + if (!*linkText || endCode != 'h') { + return CODE_INVALIDCODE; + } + + // Empty link (no payload) + if (linkText - text == 4) { + return CODE_INVALIDCODE; + } + + // Empty display text + if (linkText[0] == '|' && linkText[1] == 'h') { + return CODE_INVALIDCODE; + } + + advance = linkText - text; + + return CODE_HYPERLINKSTART; + } + case 'N': case 'n': { - if (flags & 0x200) { + if (flags & FLAG_IGNORE_NEWLINES) { return CODE_INVALIDCODE; } advance = 2; + return CODE_NEWLINE; } - // TODO handle other control codes + case 'R': + case 'r': { + if (flags & FLAG_IGNORE_COLORS) { + return CODE_INVALIDCODE; + } + + advance = 2; + + return CODE_COLORRESTORE; + } + + case 'T': { + if (flags & FLAG_IGNORE_TEXTURES) { + return CODE_INVALIDCODE; + } + + auto textureText = text + advance; + + while (*textureText) { + auto code = SUniSGetUTF8(reinterpret_cast(textureText), &advance); + textureText += advance; + + if (code == '|') { + break; + } + } + + if (!*textureText) { + return CODE_INVALIDCODE; + } + + auto endCode = SUniSGetUTF8(reinterpret_cast(textureText), &advance); + textureText += advance; + + // Null terminator or end code isn't |t + if (!*textureText || endCode != 't') { + return CODE_INVALIDCODE; + } + + // Empty texture (no payload) + if (textureText - text == 4) { + return CODE_INVALIDCODE; + } + + advance = textureText - text; + + return CODE_TEXTURESTART; + } + + case 'h': { + if (flags & FLAG_IGNORE_HYPERLINKS) { + return CODE_INVALIDCODE; + } + + advance = 2; + + return CODE_HYPERLINKSTOP; + } + + case 't': { + if (flags & FLAG_IGNORE_TEXTURES) { + return CODE_INVALIDCODE; + } + + advance = 2; + + return CODE_TEXTURESTOP; + } + + case '|': { + advance = 2; + + return CODE_PIPE; + } + + default: { + return CODE_INVALIDCODE; + } } - - // TODO remainder of function - - return CODE_INVALIDCODE; } int32_t GxuFontAddToBatch(CGxStringBatch* batch, CGxString* string) { diff --git a/test/gx/font/GxuFont.cpp b/test/gx/font/GxuFont.cpp index 5dcbbee..8af316e 100644 --- a/test/gx/font/GxuFont.cpp +++ b/test/gx/font/GxuFont.cpp @@ -70,4 +70,141 @@ TEST_CASE("GxuDetermineQuotedCode", "[gx]") { REQUIRE(quotedCode == CODE_NEWLINE); REQUIRE(advance == 2); } + + SECTION("recognizes colors") { + const char* str = "test1|c00123456test2|rtest3"; + uint32_t flags = 0x0; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + CImVector color; + + // |c00123456 + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, &color, flags, wide); + REQUIRE(quotedCode == CODE_COLORON); + REQUIRE(advance == 10); + REQUIRE(color.value == 0xFF123456); + str += advance; + + // |r + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_COLORRESTORE); + REQUIRE(advance == 2); + } + + SECTION("recognizes colors with uppercase codes") { + const char* str = "test1|C00123456test2|Rtest3"; + uint32_t flags = 0x0; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + CImVector color; + + // |C00123456 + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, &color, flags, wide); + REQUIRE(quotedCode == CODE_COLORON); + REQUIRE(advance == 10); + REQUIRE(color.value == 0xFF123456); + str += advance; + + // |R + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_COLORRESTORE); + REQUIRE(advance == 2); + } + + SECTION("ignores colors when FLAG_IGNORE_COLORS is set") { + const char* str = "test1|c00123456test2|rtest3"; + uint32_t flags = FLAG_IGNORE_COLORS; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + + // |c00123456 + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_INVALIDCODE); + + // |r + str += 15; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_INVALIDCODE); + } + + SECTION("recognizes hyperlinks") { + const char* str = "test1|Hspell:2061:0|h[Flash Heal]|htest3"; + uint32_t flags = 0x0; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + + // |Hspell:2061:0|h + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_HYPERLINKSTART); + REQUIRE(advance == 16); + str += advance; + + // |h + str += 12; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_HYPERLINKSTOP); + REQUIRE(advance == 2); + } + + SECTION("ignores hyperlinks when FLAG_IGNORE_HYPERLINKS is set") { + const char* str = "test1|Hspell:2061:0|h[Flash Heal]|htest3"; + uint32_t flags = FLAG_IGNORE_HYPERLINKS; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + + // |Hspell:2061:0|h + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_INVALIDCODE); + + // |h + str += 28; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_INVALIDCODE); + } + + SECTION("recognizes textures") { + const char* str = "test1|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1.blp:0|ttest3"; + uint32_t flags = 0x0; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + + // |TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1.blp:0|t + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_TEXTURESTART); + REQUIRE(advance == 57); + } + + SECTION("ignores textures when FLAG_IGNORE_TEXTURES is set") { + const char* str = "test1|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1.blp:0|ttest3"; + uint32_t flags = FLAG_IGNORE_TEXTURES; + + int32_t advance; + uint32_t wide; + QUOTEDCODE quotedCode; + + // |TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1.blp:0|t + str += 5; + quotedCode = GxuDetermineQuotedCode(str, advance, nullptr, flags, wide); + REQUIRE(quotedCode == CODE_INVALIDCODE); + } }