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;