Fix FPDFText_GetLooseCharBox() to handle rotation

Change the cpdf_textpage.cpp code that implements the character box
calculations used by FPDFText_GetLooseCharBox() to properly handle the
transformation matrix. Then FPDFText_GetLooseCharBox() will no longer
return dimensions of 0 when matrix.a is 0, and it will no longer return
negative dimensions when the text is flipped.

Bug: 42270642
Change-Id: Iab85cbf64cd1f8266ab20b63ad35bddefd05733c
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/127995
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/core/fpdftext/cpdf_textpage.cpp b/core/fpdftext/cpdf_textpage.cpp
index 518b744..6d982d4 100644
--- a/core/fpdftext/cpdf_textpage.cpp
+++ b/core/fpdftext/cpdf_textpage.cpp
@@ -290,15 +290,18 @@
     int ascent = charinfo.text_object()->GetFont()->GetTypeAscent();
     int descent = charinfo.text_object()->GetFont()->GetTypeDescent();
     if (ascent != descent) {
-      float width = charinfo.matrix().a *
-                    charinfo.text_object()->GetCharWidth(charinfo.char_code());
-      float font_scale = charinfo.matrix().a * font_size / (ascent - descent);
+      float width = charinfo.text_object()->GetCharWidth(charinfo.char_code());
+      float font_scale = font_size / (ascent - descent);
 
-      float left = charinfo.origin().x;
-      float right = charinfo.origin().x + (is_vert_writing ? -width : width);
-      float bottom = charinfo.origin().y + descent * font_scale;
-      float top = charinfo.origin().y + ascent * font_scale;
-      return CFX_FloatRect(left, bottom, right, top);
+      CFX_Matrix inverse_matrix = charinfo.matrix().GetInverse();
+      CFX_PointF original_origin = inverse_matrix.Transform(charinfo.origin());
+
+      float left = original_origin.x;
+      float right = original_origin.x + (is_vert_writing ? -width : width);
+      float bottom = original_origin.y + descent * font_scale;
+      float top = original_origin.y + ascent * font_scale;
+      CFX_FloatRect char_box(left, bottom, right, top);
+      return charinfo.matrix().TransformRect(char_box);
     }
   }
 
diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp
index fde1b18..92ac221 100644
--- a/fpdfsdk/fpdf_text_embeddertest.cpp
+++ b/fpdfsdk/fpdf_text_embeddertest.cpp
@@ -1736,7 +1736,7 @@
   static constexpr double kExpectedCharWidth = 8.460;
   static constexpr double kExpectedCharHeight = 6.600;
   static constexpr float kExpectedLooseCharWidth = 8.664f;
-  static constexpr float kExpectedLooseCharHeight = 12.0f;
+  static constexpr float kExpectedLooseCharHeight = 10.0f;
 
   ASSERT_TRUE(OpenDocument("font_matrix.pdf"));
   ScopedEmbedderTestPage page = LoadScopedPage(0);
@@ -1833,25 +1833,24 @@
   EXPECT_NEAR(10.055, top - bottom, 0.001);
 
   // Check the loose character box size.
-  // TODO(crbug.com/42270642): Sizes should be bigger than the
-  // FPDFText_GetCharBox() sizes, and certainly not negative.
+  static constexpr float kExpectedLooseCharDimension = 14.612f;
   FS_RECTF rect;
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedTextFirstCharIndexForQuadrant(0), &rect));
-  EXPECT_NEAR(6.126f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(8.485f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedTextFirstCharIndexForQuadrant(1), &rect));
-  EXPECT_NEAR(-6.126f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(-8.485f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedTextFirstCharIndexForQuadrant(2), &rect));
-  EXPECT_NEAR(-6.126f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(-8.485f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedTextFirstCharIndexForQuadrant(3), &rect));
-  EXPECT_NEAR(6.126f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(8.485f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharDimension, rect.top - rect.bottom, 0.001f);
 }
 
 TEST_F(FPDFTextEmbedderTest, CharBoxForRotated90DegreesText) {
@@ -1903,25 +1902,25 @@
   EXPECT_NEAR(8.604, top - bottom, 0.001);
 
   // Check the loose character box size.
-  // TODO(crbug.com/42270642): Sizes should be bigger than the
-  // FPDFText_GetCharBox() sizes, and certainly not negative nor zero.
+  static constexpr float kExpectedLooseCharWidth = 8.664f;
+  static constexpr float kExpectedLooseCharHeight = 12.0f;
   FS_RECTF rect;
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedText90FirstCharIndexForQuadrant(0), &rect));
-  EXPECT_NEAR(8.664f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(12.0f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharWidth, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharHeight, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedText90FirstCharIndexForQuadrant(1), &rect));
-  EXPECT_NEAR(0.0f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(0.0f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharHeight, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharWidth, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedText90FirstCharIndexForQuadrant(2), &rect));
-  EXPECT_NEAR(-8.664f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(-12.0f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharWidth, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharHeight, rect.top - rect.bottom, 0.001f);
   ASSERT_TRUE(FPDFText_GetLooseCharBox(
       text_page.get(), GetRotatedText90FirstCharIndexForQuadrant(3), &rect));
-  EXPECT_NEAR(0.0f, rect.right - rect.left, 0.001f);
-  EXPECT_NEAR(0.0f, rect.top - rect.bottom, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharHeight, rect.right - rect.left, 0.001f);
+  EXPECT_NEAR(kExpectedLooseCharWidth, rect.top - rect.bottom, 0.001f);
 }
 
 TEST_F(FPDFTextEmbedderTest, SmallType3Glyph) {