fix(text): stop fitFontSize hanging on an inf or huge font size (#14497) 6f7a868171 Co-authored-by: Guido Rosso <guido@rive.app>
diff --git a/.rive_head b/.rive_head index e0af92f..c38dead 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -ba0755c07bebb845c10d2fb7cb565c9e0b161d78 +6f7a868171e0112a4c0385487889521bd46f6309
diff --git a/src/text/text.cpp b/src/text/text.cpp index 363db3a..a10ca06 100644 --- a/src/text/text.cpp +++ b/src/text/text.cpp
@@ -451,6 +451,11 @@ if (style != nullptr && style->font() != nullptr && !valueRun->text().empty()) { + // No scale turns inf finite (inf * 0 is NaN). std::max drops NaN. + if (std::isinf(style->fontSize())) + { + return 1.0f; + } maxSize = std::max(maxSize, style->fontSize()); } } @@ -514,10 +519,12 @@ return widthFits && heightFits; }; + // Above 2^24 floats skip integers, and hi + 1 must not overflow int. + constexpr float kMaxSearchSize = (float)(1 << 24); // Binary search for the largest integer top size in [1, floor(maxSize)] // that fits. If nothing fits we fall back to the minimum size (1). int lo = 1; - int hi = std::max(1, (int)maxSize); + int hi = (int)std::min(maxSize, kMaxSearchSize); int best = 1; while (lo <= hi) {
diff --git a/tests/unit_tests/runtime/text_test.cpp b/tests/unit_tests/runtime/text_test.cpp index a6f7197..f56e304 100644 --- a/tests/unit_tests/runtime/text_test.cpp +++ b/tests/unit_tests/runtime/text_test.cpp
@@ -313,6 +313,46 @@ REQUIRE(text->m_transform.xx() == Approx(1.0f)); } +// Shapes ellipsis.riv at fontSize in a width x height fitFontSize box and +// returns the shaped run size. +static float fitRunSize(float fontSize, float width, float height) +{ + auto file = ReadRiveFile("assets/ellipsis.riv"); + auto artboard = file->artboard(); + auto text = artboard->find<rive::Text>()[0]; + for (auto style : artboard->find<rive::TextStyle>()) + { + style->fontSize(fontSize); + } + text->sizingValue((uint32_t)rive::TextSizing::fixed); + text->width(width); + text->height(height); + text->overflow(rive::TextOverflow::fitFontSize); + artboard->advance(0.0f); + rive::NoOpRenderer renderer; + artboard->draw(&renderer); + REQUIRE(!text->shape().empty()); + REQUIRE(!text->shape()[0].runs.empty()); + return text->shape()[0].runs[0].size; +} + +TEST_CASE("fitFontSize keeps a non-finite font size", "[text]") +{ + // inf used to overflow the int search and never return. + CHECK(std::isinf(fitRunSize(INFINITY, 300.0f, 60.0f))); + CHECK(std::isnan(fitRunSize(NAN, 300.0f, 60.0f))); +} + +TEST_CASE("fitFontSize caps the search at 2^24", "[text]") +{ + // Every size fits, so the search tops out at the cap. + CHECK(fitRunSize(3e9f, 1e38f, 1e38f) == Approx((float)(1 << 24))); + // Too big for a normal box: shrinks to the size a normal font fits at. + float fitted = fitRunSize(3e9f, 300.0f, 60.0f); + CHECK(fitted < 60.0f); + CHECK(fitted == Approx(fitRunSize(100.0f, 300.0f, 60.0f))); +} + TEST_CASE("fitFontSize scales custom line height and letter spacing " "proportionally", "[text]") @@ -1252,6 +1292,35 @@ CHECK(trimmedHeight == Approx(127.32f).margin(0.5f)); } +TEST_CASE("fitFontSize measure returns for a huge font size", "[text]") +{ + auto file = importTextWithMinorVersion(4); + auto artboard = file->artboardDefault(); + rive::Text* title = nullptr; + for (auto text : artboard->find<rive::Text>()) + { + if (!text->runs().empty() && !text->runs()[0]->text().empty()) + { + title = text; + break; + } + } + REQUIRE(title != nullptr); + title->fitFontSizeResizesBox(true); + for (float size : {INFINITY, NAN, 3e9f}) + { + for (auto style : artboard->find<rive::TextStyle>()) + { + style->fontSize(size); + } + title->measureLayout(1e38f, + rive::LayoutMeasureMode::atMost, + 1e38f, + rive::LayoutMeasureMode::atMost); + artboard->advance(0.0f); + } +} + TEST_CASE("Text with fit font size correctly resizes its text box", "[silver]") { rive::SerializingFactory silver;