Text fixes

Bunch of text fixes:
- gradients respect origin on text
- changing font size updates the text editor selection
- apply @avivian's transform constraint fix from https://github.com/rive-app/rive/pull/5689 to cpp

Diffs=
6af562d4c Text fixes (#5696)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index 368df58..dfa38f4 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-fcccdeccddd2b9c47992f131f9ba741bccee5da8
+6af562d4cc2b8e309169c754a64cbc0211b9d68b
diff --git a/include/rive/text/text.hpp b/include/rive/text/text.hpp
index 6cc54c0..db1818e 100644
--- a/include/rive/text/text.hpp
+++ b/include/rive/text/text.hpp
@@ -221,7 +221,6 @@
     std::vector<OrderedLine> m_orderedLines;
     GlyphRun m_ellipsisRun;
     std::unique_ptr<RenderPath> m_clipRenderPath;
-    Mat2D m_originWorldTransform;
     AABB m_bounds;
     std::vector<TextModifierGroup*> m_modifierGroups;
 
diff --git a/src/constraints/transform_constraint.cpp b/src/constraints/transform_constraint.cpp
index 2c157e8..fa30ac4 100644
--- a/src/constraints/transform_constraint.cpp
+++ b/src/constraints/transform_constraint.cpp
@@ -8,14 +8,10 @@
 
 const Mat2D TransformConstraint::targetTransform() const
 {
-    if (originX() != 0.0f || originY() != 0.0f)
-    {
-        AABB bounds = m_Target->localBounds();
-        Mat2D local = Mat2D::fromTranslate(bounds.left() + bounds.width() * originX(),
-                                           bounds.top() + bounds.height() * originY());
-        return m_Target->worldTransform() * local;
-    }
-    return m_Target->worldTransform();
+    AABB bounds = m_Target->localBounds();
+    Mat2D local = Mat2D::fromTranslate(bounds.left() + bounds.width() * originX(),
+                                       bounds.top() + bounds.height() * originY());
+    return m_Target->worldTransform() * local;
 }
 
 void TransformConstraint::constrain(TransformComponent* component)
diff --git a/src/text/text.cpp b/src/text/text.cpp
index c99fab1..af0d763 100644
--- a/src/text/text.cpp
+++ b/src/text/text.cpp
@@ -259,48 +259,71 @@
 
     // Build up ordered runs as we go.
     int paragraphIndex = 0;
-    int lineIndex = 0;
     float y = 0.0f;
     float minY = 0.0f;
-    float maxX = 0.0f;
+    float maxWidth = 0.0f;
 
     int ellipsisLine = -1;
     bool isEllipsisLineLast = false;
     // Find the line to put the ellipsis on (line before the one that
     // overflows).
-    if (overflow() == TextOverflow::ellipsis && sizing() == TextSizing::fixed)
-    {
-        int lastLineIndex = -1;
-        for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
-        {
-            for (const GlyphLine& line : paragraphLines)
-            {
-                lastLineIndex++;
-                if (y + line.bottom <= height())
-                {
-                    ellipsisLine++;
-                }
-            }
+    bool wantEllipsis = overflow() == TextOverflow::ellipsis && sizing() == TextSizing::fixed;
 
-            if (!paragraphLines.empty())
-            {
-                y += paragraphLines.back().bottom;
-            }
-            y += paragraphSpace;
-        }
-        if (ellipsisLine == -1)
+    int lastLineIndex = -1;
+    for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
+    {
+        const Paragraph& paragraph = m_shape[paragraphIndex++];
+        for (const GlyphLine& line : paragraphLines)
         {
-            // Nothing fits, just show the first line and ellipse it.
-            ellipsisLine = 0;
+            const GlyphRun& endRun = paragraph.runs[line.endRunIndex];
+            const GlyphRun& startRun = paragraph.runs[line.startRunIndex];
+            float width = endRun.xpos[line.endGlyphIndex] - startRun.xpos[line.startGlyphIndex];
+            if (width > maxWidth)
+            {
+                maxWidth = width;
+            }
+            lastLineIndex++;
+            if (wantEllipsis && y + line.bottom <= height())
+            {
+                ellipsisLine++;
+            }
         }
-        isEllipsisLineLast = lastLineIndex == ellipsisLine;
-        y = 0.0f;
+
+        if (!paragraphLines.empty())
+        {
+            y += paragraphLines.back().bottom;
+        }
+        y += paragraphSpace;
     }
+    if (wantEllipsis && ellipsisLine == -1)
+    {
+        // Nothing fits, just show the first line and ellipse it.
+        ellipsisLine = 0;
+    }
+    isEllipsisLineLast = lastLineIndex == ellipsisLine;
+
+    int lineIndex = 0;
+    paragraphIndex = 0;
     if (textOrigin() == TextOrigin::baseline && !m_lines.empty() && !m_lines[0].empty())
     {
         y -= m_lines[0][0].baseline;
         minY = y;
     }
+    switch (sizing())
+    {
+        case TextSizing::autoWidth:
+            m_bounds = AABB(0.0f, minY, maxWidth, std::max(minY, y - paragraphSpace));
+            break;
+        case TextSizing::autoHeight:
+            m_bounds = AABB(0.0f, minY, width(), std::max(minY, y - paragraphSpace));
+            break;
+        case TextSizing::fixed:
+            m_bounds = AABB(0.0f, minY, width(), minY + height());
+            break;
+    }
+
+    y = -m_bounds.height() * originY();
+    paragraphIndex = 0;
 
     bool hasModifiers = haveModifiers();
     if (hasModifiers)
@@ -345,7 +368,7 @@
                                                         &m_ellipsisRun));
             }
             const OrderedLine& orderedLine = m_orderedLines[lineIndex];
-            float x = line.startX;
+            float x = -m_bounds.width() * originX() + line.startX;
             float renderY = y + line.baseline;
             for (auto glyphItr : orderedLine)
             {
@@ -415,10 +438,6 @@
                     style->propagateOpacity(renderOpacity());
                 }
             }
-            if (x > maxX)
-            {
-                maxX = x;
-            }
             if (lineIndex == ellipsisLine)
             {
                 return;
@@ -431,25 +450,6 @@
         }
         y += paragraphSpace;
     }
-    switch (sizing())
-    {
-        case TextSizing::autoWidth:
-            m_bounds = AABB(0.0f, minY, maxX, std::max(minY, y - paragraphSpace));
-            break;
-        case TextSizing::autoHeight:
-            m_bounds = AABB(0.0f, minY, width(), std::max(minY, y - paragraphSpace));
-            break;
-        case TextSizing::fixed:
-            m_bounds = AABB(0.0f, minY, width(), minY + height());
-            break;
-    }
-}
-
-void Text::updateOriginWorldTransform()
-{
-    m_originWorldTransform =
-        m_WorldTransform *
-        Mat2D::fromTranslate(-m_bounds.width() * originX(), -m_bounds.height() * originY());
 }
 
 const TextStyle* Text::styleFromShaperId(uint16_t id) const
@@ -466,7 +466,7 @@
         // transformations.
         renderer->save();
     }
-    renderer->transform(m_originWorldTransform);
+    renderer->transform(m_WorldTransform);
     if (overflow() == TextOverflow::clipped && m_clipRenderPath)
     {
         renderer->clipPath(m_clipRenderPath.get());
@@ -624,11 +624,6 @@
 {
     Super::update(value);
 
-    if (hasDirt(value, ComponentDirt::WorldTransform))
-    {
-        updateOriginWorldTransform();
-    }
-
     if (hasDirt(value, ComponentDirt::Path))
     {
         // We have modifiers that need shaping we'll need to compute the coverage