Get the rotation angle of a character.

Leveraging the transformation matrix attribute of a character, we can
easily find the angle by which it is rotated. The angle is used in a
separate change on Chromium. For more information, see
https://chromium-review.googlesource.com/c/chromium/src/+/1718989

Bug: chromium:985604
Change-Id: Ifc2e46f0e1eeca8f1e10218f0d1b8ff9fcef3d29
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/58233
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_text.cpp b/fpdfsdk/fpdf_text.cpp
index cc7ca5b..2421ea2 100644
--- a/fpdfsdk/fpdf_text.cpp
+++ b/fpdfsdk/fpdf_text.cpp
@@ -121,6 +121,27 @@
   return length;
 }
 
+FPDF_EXPORT double FPDF_CALLCONV FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page,
+                                                       int index) {
+  CPDF_TextPage* textpage = GetTextPageForValidIndex(text_page, index);
+  if (!textpage)
+    return -1;
+
+  FPDF_CHAR_INFO charinfo;
+  textpage->GetCharInfo(index, &charinfo);
+  // On the left is our current Matrix and on the right a generic rotation
+  // matrix for our coordinate space.
+  // | a  b  0 |    | cos(t)  -sin(t)  0 |
+  // | c  d  0 |    | sin(t)   cos(t)  0 |
+  // | e  f  1 |    |   0        0     1 |
+  // Calculate the angle of the vector
+  double angle = atan2(charinfo.m_Matrix.c, charinfo.m_Matrix.a);
+  if (angle < 0)
+    angle = 2 * FX_PI + angle;
+
+  return angle;
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_GetCharBox(FPDF_TEXTPAGE text_page,
                                                         int index,
                                                         double* left,
diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp
index e211d2d..521831e 100644
--- a/fpdfsdk/fpdf_text_embeddertest.cpp
+++ b/fpdfsdk/fpdf_text_embeddertest.cpp
@@ -1213,3 +1213,41 @@
   FPDFText_ClosePage(text_page);
   UnloadPage(page);
 }
+
+TEST_F(FPDFTextEmbedderTest, GetCharAngle) {
+  ASSERT_TRUE(OpenDocument("rotated_text.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  FPDF_TEXTPAGE text_page = FPDFText_LoadPage(page);
+  ASSERT_TRUE(text_page);
+
+  static constexpr int kSubstringsSize[] = {FX_ArraySize("Hello,"),
+                                            FX_ArraySize(" world!\r\n"),
+                                            FX_ArraySize("Goodbye,")};
+
+  // -1 for CountChars not including the \0, but +1 for the extra control
+  // character.
+  EXPECT_EQ(kHelloGoodbyeTextSize, FPDFText_CountChars(text_page));
+
+  EXPECT_EQ(-1, FPDFText_GetCharAngle(nullptr, 0));
+  EXPECT_EQ(-1, FPDFText_GetCharAngle(text_page, -1));
+  EXPECT_EQ(-1, FPDFText_GetCharAngle(text_page, kHelloGoodbyeTextSize + 1));
+
+  // Test GetCharAngle for every quadrant
+  EXPECT_NEAR(FX_PI / 4.0, FPDFText_GetCharAngle(text_page, 0), 0.001);
+  EXPECT_NEAR(3 * FX_PI / 4.0,
+              FPDFText_GetCharAngle(text_page, kSubstringsSize[0]), 0.001);
+  EXPECT_NEAR(
+      5 * FX_PI / 4.0,
+      FPDFText_GetCharAngle(text_page, kSubstringsSize[0] + kSubstringsSize[1]),
+      0.001);
+  EXPECT_NEAR(
+      7 * FX_PI / 4.0,
+      FPDFText_GetCharAngle(text_page, kSubstringsSize[0] + kSubstringsSize[1] +
+                                           kSubstringsSize[2]),
+      0.001);
+
+  FPDFText_ClosePage(text_page);
+  UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index a9897ee..9f58b8b 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -324,6 +324,7 @@
     CHK(FPDFText_FindPrev);
     CHK(FPDFText_FindStart);
     CHK(FPDFText_GetBoundedText);
+    CHK(FPDFText_GetCharAngle);
     CHK(FPDFText_GetCharBox);
     CHK(FPDFText_GetCharIndexAtPos);
     CHK(FPDFText_GetCharOrigin);
diff --git a/public/fpdf_text.h b/public/fpdf_text.h
index 7bd84bf..63793bb 100644
--- a/public/fpdf_text.h
+++ b/public/fpdf_text.h
@@ -112,6 +112,20 @@
                      unsigned long buflen,
                      int* flags);
 
+// Experimental API.
+// Function: FPDFText_GetCharAngle
+//          Get character rotation angle.
+// Parameters:
+//          text_page   -   Handle to a text page information structure.
+//                          Returned by FPDFText_LoadPage function.
+//          index       -   Zero-based index of the character.
+// Return Value:
+//          On success, return the angle value in radian. Value will always be
+//          greater or equal to 0. If |text_page| is invalid, or if |index| is
+//          out of bounds, then return -1.
+FPDF_EXPORT double FPDF_CALLCONV FPDFText_GetCharAngle(FPDF_TEXTPAGE text_page,
+                                                       int index);
+
 // Function: FPDFText_GetCharBox
 //          Get bounding box of a particular character.
 // Parameters:
diff --git a/testing/resources/rotated_text.in b/testing/resources/rotated_text.in
new file mode 100644
index 0000000..6cd5b23
--- /dev/null
+++ b/testing/resources/rotated_text.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
+0.70710678118 -0.70710678118 0.70710678118 0.70710678118 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 -0.70710678118 0.70710678118 -0.70710678118 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 0.70710678118 -0.70710678118 -0.70710678118 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0.70710678118 0.70710678118 -0.70710678118 0.70710678118 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/rotated_text.pdf b/testing/resources/rotated_text.pdf
new file mode 100644
index 0000000..d6d8db8
--- /dev/null
+++ b/testing/resources/rotated_text.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 406
+>>
+stream
+BT
+0 0 Td
+/F1 12 Tf
+0.70710678118 -0.70710678118 0.70710678118 0.70710678118 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 -0.70710678118 0.70710678118 -0.70710678118 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 0.70710678118 -0.70710678118 -0.70710678118 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0.70710678118 0.70710678118 -0.70710678118 0.70710678118 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000161 00000 n 
+0000000287 00000 n 
+0000000365 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+823
+%%EOF