[stable] Removes assumption that basis scalar and rounded_scalar match (#165428)

## Impacted Users
Impeller, all platforms

## Impact Description
Text that is scaled over 48x will render at the incorrect size and position

## Workaround
Change the font size instead of using a scale, but this isn't always clear from the framework usage.

## Risk
If it is wrong, text will be rendered incorrectly.

## Test Coverage
A golden test was written but they aren't automatically executed on release branches.

## Validation Steps
https://github.com/flutter/flutter/issues/165130 has reproduction code
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d0ea8d..61338d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,10 @@
 -->
 ## Flutter 3.29 Changes
 
+### [3.29.3](https://github.com/flutter/flutter/releases/tag/3.29.3)
+- [flutter/165166](https://github.com/flutter/flutter/pull/165166) - Impeller,
+  All platforms, Text that is scaled over 48x renders incorrectly.
+
 ### [3.29.2](https://github.com/flutter/flutter/releases/tag/3.29.2)
 
 - [dart 3.7.2 changelog](https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md#372)
diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
index 8ad7ac1..2244489 100644
--- a/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
@@ -160,6 +160,35 @@
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
+TEST_P(AiksTest, CanRenderTextFrameWithScalingOverflow) {
+  Scalar scale = 60.0;
+  Scalar offsetx = -500.0;
+  Scalar offsety = 700.0;
+  auto callback = [&]() -> sk_sp<DisplayList> {
+    if (AiksTest::ImGuiBegin("Controls", nullptr,
+                             ImGuiWindowFlags_AlwaysAutoResize)) {
+      ImGui::SliderFloat("scale", &scale, 1.f, 300.f);
+      ImGui::SliderFloat("offsetx", &offsetx, -600.f, 100.f);
+      ImGui::SliderFloat("offsety", &offsety, 600.f, 2048.f);
+      ImGui::End();
+    }
+    DisplayListBuilder builder;
+    builder.Scale(GetContentScale().x, GetContentScale().y);
+    DlPaint paint;
+    paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
+    builder.DrawPaint(paint);
+    builder.Scale(scale, scale);
+
+    RenderTextInCanvasSkia(
+        GetContext(), builder, "test", "Roboto-Regular.ttf",
+        TextRenderOptions{
+            .position = SkPoint::Make(offsetx / scale, offsety / scale),
+        });
+    return builder.Build();
+  };
+  ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
 TEST_P(AiksTest, CanRenderTextFrameWithFractionScaling) {
   Scalar fine_scale = 0.f;
   bool is_subpixel = false;
diff --git a/engine/src/flutter/impeller/entity/contents/text_contents.cc b/engine/src/flutter/impeller/entity/contents/text_contents.cc
index 6ed000c..27bb4a6 100644
--- a/engine/src/flutter/impeller/entity/contents/text_contents.cc
+++ b/engine/src/flutter/impeller/entity/contents/text_contents.cc
@@ -77,6 +77,22 @@
   }
 }
 
+namespace {
+Scalar AttractToOne(Scalar x) {
+  // Epsilon was decided by looking at the floating point inaccuracies in
+  // the ScaledK test.
+  const Scalar epsilon = 0.005f;
+  if (std::abs(x - 1.f) < epsilon) {
+    return 1.f;
+  }
+  if (std::abs(x + 1.f) < epsilon) {
+    return -1.f;
+  }
+  return x;
+}
+
+}  // namespace
+
 void TextContents::ComputeVertexData(
     VS::PerVertexData* vtx_contents,
     const std::shared_ptr<TextFrame>& frame,
@@ -165,7 +181,8 @@
         atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
       }
 
-      Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
+      Scalar inverted_rounded_scale = 1.f / rounded_scale;
+      Rect scaled_bounds = glyph_bounds.Scale(inverted_rounded_scale);
       // For each glyph, we compute two rectangles. One for the vertex
       // positions and one for the texture coordinates (UVs). The atlas
       // glyph bounds are used to compute UVs in cases where the
@@ -175,8 +192,14 @@
       Point uv_size = SizeToPoint(atlas_glyph_bounds.GetSize()) / atlas_size;
 
       Matrix unscaled_basis =
-          Matrix::MakeScale({basis_transform.m[0] > 0 ? 1.f : -1.f,
-                             basis_transform.m[5] > 0 ? 1.f : -1.f, 1.f});
+          basis_transform * Matrix::MakeScale({inverted_rounded_scale,
+                                               inverted_rounded_scale, 1});
+
+      // In typical scales < 48x these values should be -1 or 1. We round to
+      // those to avoid inaccuracies.
+      unscaled_basis.m[0] = AttractToOne(unscaled_basis.m[0]);
+      unscaled_basis.m[5] = AttractToOne(unscaled_basis.m[5]);
+
       Point unrounded_glyph_position =
           // This is for RTL text.
           unscaled_basis * glyph_bounds.GetLeftTop() +
diff --git a/engine/src/flutter/testing/impeller_golden_tests_output.txt b/engine/src/flutter/testing/impeller_golden_tests_output.txt
index 79cc526..af7e23b 100644
--- a/engine/src/flutter/testing/impeller_golden_tests_output.txt
+++ b/engine/src/flutter/testing/impeller_golden_tests_output.txt
@@ -487,6 +487,9 @@
 impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_Metal.png
 impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_OpenGLES.png
 impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_Vulkan.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_Metal.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_OpenGLES.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_Vulkan.png
 impeller_Play_AiksTest_CanRenderTextFrame_Metal.png
 impeller_Play_AiksTest_CanRenderTextFrame_OpenGLES.png
 impeller_Play_AiksTest_CanRenderTextFrame_Vulkan.png