Add tests to show FPDFText_GetLooseCharBox() bug with rotated text

Use rotated_text.pdf, where text is rotated by multiples of 45 degrees,
and a newly added rotated_text_90.pdf, to show
FPDFText_GetLooseCharBox() returns unexpected results compared to
FPDFText_GetCharBox().

Bug: 42270642
Change-Id: Ie9b2211e9eaf0b4d18aa6b59b675ca13a24d4238
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/127994
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp
index e61f1e7..fde1b18 100644
--- a/fpdfsdk/fpdf_text_embeddertest.cpp
+++ b/fpdfsdk/fpdf_text_embeddertest.cpp
@@ -52,6 +52,28 @@
   }
 }
 
+// For use with rotated_text_90.pdf.
+int GetRotatedText90FirstCharIndexForQuadrant(int quadrant) {
+  // Unlike hello_world.pdf, rotated_text_90.pdf has an extra CRLF after
+  // "Hello," and an extra space before "Goodbye".
+  static constexpr size_t kSubstringsSize[] = {
+      std::char_traits<char>::length("Hello,\r\n "),
+      std::char_traits<char>::length("world!\r\n "),
+      std::char_traits<char>::length("Goodbye, ")};
+  switch (quadrant) {
+    case 0:
+      return 0;
+    case 1:
+      return kSubstringsSize[0];
+    case 2:
+      return kSubstringsSize[0] + kSubstringsSize[1];
+    case 3:
+      return kSubstringsSize[0] + kSubstringsSize[1] + kSubstringsSize[2];
+    default:
+      NOTREACHED();
+  }
+}
+
 }  // namespace
 
 class FPDFTextEmbedderTest : public EmbedderTest {};
@@ -1762,6 +1784,146 @@
   EXPECT_NEAR(kExpectedLooseCharHeight, rect.top - rect.bottom, 0.00001);
 }
 
+TEST_F(FPDFTextEmbedderTest, CharBoxForRotated45DegreesText) {
+  ASSERT_TRUE(OpenDocument("rotated_text.pdf"));
+  ScopedEmbedderTestPage page = LoadScopedPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFTextPage text_page(FPDFText_LoadPage(page.get()));
+  ASSERT_TRUE(text_page);
+
+  // Sanity check the characters.
+  EXPECT_EQ(static_cast<uint32_t>('H'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedTextFirstCharIndexForQuadrant(0)));
+  EXPECT_EQ(static_cast<uint32_t>('w'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedTextFirstCharIndexForQuadrant(1)));
+  EXPECT_EQ(static_cast<uint32_t>('G'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedTextFirstCharIndexForQuadrant(2)));
+  EXPECT_EQ(static_cast<uint32_t>('w'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedTextFirstCharIndexForQuadrant(3)));
+
+  // Check the character box size.
+  double left;
+  double right;
+  double bottom;
+  double top;
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedTextFirstCharIndexForQuadrant(0),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(11.192, right - left, 0.001);
+  EXPECT_NEAR(11.192, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedTextFirstCharIndexForQuadrant(1),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(10.055, right - left, 0.001);
+  EXPECT_NEAR(10.055, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedTextFirstCharIndexForQuadrant(2),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(11.209, right - left, 0.001);
+  EXPECT_NEAR(11.209, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedTextFirstCharIndexForQuadrant(3),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(10.055, right - left, 0.001);
+  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.
+  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);
+  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);
+  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);
+  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);
+}
+
+TEST_F(FPDFTextEmbedderTest, CharBoxForRotated90DegreesText) {
+  ASSERT_TRUE(OpenDocument("rotated_text_90.pdf"));
+  ScopedEmbedderTestPage page = LoadScopedPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFTextPage text_page(FPDFText_LoadPage(page.get()));
+  ASSERT_TRUE(text_page);
+
+  // Sanity check the characters.
+  EXPECT_EQ(static_cast<uint32_t>('H'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedText90FirstCharIndexForQuadrant(0)));
+  EXPECT_EQ(static_cast<uint32_t>('w'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedText90FirstCharIndexForQuadrant(1)));
+  EXPECT_EQ(static_cast<uint32_t>('G'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedText90FirstCharIndexForQuadrant(2)));
+  EXPECT_EQ(static_cast<uint32_t>('w'),
+            FPDFText_GetUnicode(text_page.get(),
+                                GetRotatedText90FirstCharIndexForQuadrant(3)));
+
+  // Check the character box size.
+  double left;
+  double right;
+  double bottom;
+  double top;
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedText90FirstCharIndexForQuadrant(0),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(7.968, right - left, 0.001);
+  EXPECT_NEAR(7.86, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedText90FirstCharIndexForQuadrant(1),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(5.616, right - left, 0.001);
+  EXPECT_NEAR(8.604, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedText90FirstCharIndexForQuadrant(2),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(7.8, right - left, 0.001);
+  EXPECT_NEAR(8.052, top - bottom, 0.001);
+  ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(),
+                                  GetRotatedText90FirstCharIndexForQuadrant(3),
+                                  &left, &right, &bottom, &top));
+  EXPECT_NEAR(5.616, right - left, 0.001);
+  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.
+  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);
+  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);
+  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);
+  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);
+}
+
 TEST_F(FPDFTextEmbedderTest, SmallType3Glyph) {
   ASSERT_TRUE(OpenDocument("bug_1591.pdf"));
   ScopedEmbedderTestPage page = LoadScopedPage(0);
diff --git a/testing/resources/rotated_text_90.in b/testing/resources/rotated_text_90.in
new file mode 100644
index 0000000..a8e7394
--- /dev/null
+++ b/testing/resources/rotated_text_90.in
@@ -0,0 +1,58 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+0 0 Td
+/F1 12 Tf
+1 0 0 1 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+0 1 -1 0 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-1 0 0 -1 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0 -1 1 0 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/rotated_text_90.pdf b/testing/resources/rotated_text_90.pdf
new file mode 100644
index 0000000..a3a316d
--- /dev/null
+++ b/testing/resources/rotated_text_90.pdf
@@ -0,0 +1,70 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+  /Length 210
+>>
+stream
+BT
+0 0 Td
+/F1 12 Tf
+1 0 0 1 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+0 1 -1 0 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-1 0 0 -1 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0 -1 1 0 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000283 00000 n 
+0000000361 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+623
+%%EOF