text modifier length calculation fix This PR fixes an issue with text modifiers applied to text whose size in bytes (or characters) does not match its size in code points Diffs= 27ac9fcbb text modifier length calculation fix (#6494) Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index 5f86ca1..72aa73a 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -f5e458f807056bf2522620aafaa60a048cd38d47 +27ac9fcbbe57e46afa449852b369401b4b579411
diff --git a/include/rive/text/text_value_run.hpp b/include/rive/text/text_value_run.hpp index 3bdca17..5a61882 100644 --- a/include/rive/text/text_value_run.hpp +++ b/include/rive/text/text_value_run.hpp
@@ -1,6 +1,7 @@ #ifndef _RIVE_TEXT_VALUE_RUN_HPP_ #define _RIVE_TEXT_VALUE_RUN_HPP_ #include "rive/generated/text/text_value_run_base.hpp" +#include "rive/text/utf.hpp" namespace rive { @@ -11,6 +12,22 @@ StatusCode onAddedClean(CoreContext* context) override; StatusCode onAddedDirty(CoreContext* context) override; TextStyle* style() { return m_style; } + uint32_t length() + { + if (m_length == -1) + { + + const uint8_t* ptr = (const uint8_t*)text().c_str(); + uint32_t n = 0; + while (*ptr) + { + UTF::NextUTF8(&ptr); + n += 1; + } + m_length = n; + } + return m_length; + } uint32_t offset() const; protected: @@ -19,6 +36,7 @@ private: TextStyle* m_style = nullptr; + uint32_t m_length = -1; }; } // namespace rive
diff --git a/src/text/text_modifier_range.cpp b/src/text/text_modifier_range.cpp index 5480d18..a2c0a46 100644 --- a/src/text/text_modifier_range.cpp +++ b/src/text/text_modifier_range.cpp
@@ -62,7 +62,7 @@ if (m_run != nullptr) { start = m_run->offset(); - end = start + (uint32_t)m_run->text().size(); + end = start + m_run->length(); } switch (units()) {
diff --git a/src/text/text_value_run.cpp b/src/text/text_value_run.cpp index 3a86641..9db0396 100644 --- a/src/text/text_value_run.cpp +++ b/src/text/text_value_run.cpp
@@ -7,7 +7,11 @@ using namespace rive; -void TextValueRun::textChanged() { parent()->as<Text>()->markShapeDirty(); } +void TextValueRun::textChanged() +{ + m_length = -1; + parent()->as<Text>()->markShapeDirty(); +} StatusCode TextValueRun::onAddedClean(CoreContext* context) { @@ -60,13 +64,13 @@ Text* text = parent()->as<Text>(); uint32_t offset = 0; - for (const TextValueRun* run : text->runs()) + for (TextValueRun* run : text->runs()) { if (run == this) { break; } - offset += (uint32_t)run->text().size(); + offset += run->length(); } return offset; #else
diff --git a/test/assets/test_modifier_run.riv b/test/assets/test_modifier_run.riv new file mode 100644 index 0000000..deaab5d --- /dev/null +++ b/test/assets/test_modifier_run.riv Binary files differ
diff --git a/test/text_test.cpp b/test/text_test.cpp index 1afaf93..36e8d6b 100644 --- a/test/text_test.cpp +++ b/test/text_test.cpp
@@ -221,7 +221,7 @@ } // Run from 4-9 got selected. REQUIRE(firstRange->run()->offset() == 4); - REQUIRE(firstRange->run()->text().size() == 5); + REQUIRE(firstRange->run()->length() == 5); for (int i = 4; i < 9; i++) { REQUIRE(firstModifierGroup->coverage(i) != 0.0f); @@ -266,6 +266,53 @@ } } +TEST_CASE("run modifier ranges select runs with varying text size", "[text]") +{ + auto file = ReadRiveFile("../../test/assets/test_modifier_run.riv"); + auto artboard = file->artboard(); + + artboard->advance(0.0f); + rive::NoOpRenderer renderer; + artboard->draw(&renderer); + + { + /* + Full text is: + text for first run[UN]line separator[UN]text for second run + [UN] is the united nations flag + the first run is 18 characters long + the second run is 16 characters long + the second run is 20 characters long + */ + auto characterSelectedText = artboard->find<rive::Text>("MultiRunText"); + REQUIRE(characterSelectedText != nullptr); + REQUIRE(characterSelectedText->haveModifiers()); + REQUIRE(characterSelectedText->modifierGroups().size() == 1); + auto firstModifierGroup = characterSelectedText->modifierGroups()[0]; + REQUIRE(firstModifierGroup->ranges().size() == 1); + auto firstRange = firstModifierGroup->ranges()[0]; + REQUIRE(firstRange->run() != nullptr); + for (int i = 0; i < 18; i++) + { + REQUIRE(firstModifierGroup->coverage(i) == 0.0f); + } + // // Run from 18-33 got selected. + REQUIRE(firstRange->run()->offset() == 18); + REQUIRE(firstRange->run()->length() == 16); + // We confirm that the size and the length of the text are different and they're + // both correct + REQUIRE(firstRange->run()->text().size() == 22); + for (int i = 18; i < 34; i++) + { + REQUIRE(firstModifierGroup->coverage(i) != 0.0f); + } + for (int i = 34; i < 54; i++) + { + REQUIRE(firstModifierGroup->coverage(i) == 0.0f); + } + } +} + TEST_CASE("double new line type works", "[text]") { auto file = ReadRiveFile("../../test/assets/double_line.riv");