Add FPDFPageObj_GetClipPath() API

This adds an API to expose the data inside CPDF_ClipPath.

Next to the initial getter, this also adds:

- FPDFClipPath_CountPaths() to get number of paths inside the clip path
- FPDFClipPath_CountPathSegments() to get the number of segments inside
  one specific path
- FPDFClipPath_GetPathSegment() to get a FPDF_PATHSEGMENT, which can be
  already analyzed using the existing FPDFPathSegment_*() APIs.

Bug: pdfium:1332
Change-Id: I6a8d99fd80d1df2c3e1c1406757f20d9f9873f0d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/58310
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index 3276066..f3af839 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -431,6 +431,102 @@
   VerifySavedDocument(612, 792, kLastMD5);
 }
 
+TEST_F(FPDFEditEmbedderTest, ClipPath) {
+  // Load document with a clipped rectangle.
+  EXPECT_TRUE(OpenDocument("clip_path.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ASSERT_EQ(1, FPDFPage_CountObjects(page));
+
+  FPDF_PAGEOBJECT triangle = FPDFPage_GetObject(page, 0);
+  ASSERT_TRUE(triangle);
+
+  // Test that we got the expected triangle.
+  ASSERT_EQ(4, FPDFPath_CountSegments(triangle));
+
+  FPDF_PATHSEGMENT segment = FPDFPath_GetPathSegment(triangle, 0);
+  float x;
+  float y;
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(10, x);
+  EXPECT_EQ(10, y);
+  EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFPath_GetPathSegment(triangle, 1);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(25, x);
+  EXPECT_EQ(40, y);
+  EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFPath_GetPathSegment(triangle, 2);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(40, x);
+  EXPECT_EQ(10, y);
+  EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFPath_GetPathSegment(triangle, 3);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_TRUE(FPDFPathSegment_GetClose(segment));
+
+  // Test FPDFPageObj_GetClipPath().
+  ASSERT_EQ(nullptr, FPDFPageObj_GetClipPath(nullptr));
+
+  FPDF_CLIPPATH clip_path = FPDFPageObj_GetClipPath(triangle);
+  ASSERT_TRUE(clip_path);
+
+  // Test FPDFClipPath_CountPaths().
+  ASSERT_EQ(-1, FPDFClipPath_CountPaths(nullptr));
+  ASSERT_EQ(1, FPDFClipPath_CountPaths(clip_path));
+
+  // Test FPDFClipPath_CountPathSegments().
+  ASSERT_EQ(-1, FPDFClipPath_CountPathSegments(nullptr, 0));
+  ASSERT_EQ(-1, FPDFClipPath_CountPathSegments(clip_path, -1));
+  ASSERT_EQ(-1, FPDFClipPath_CountPathSegments(clip_path, 1));
+  ASSERT_EQ(4, FPDFClipPath_CountPathSegments(clip_path, 0));
+
+  // FPDFClipPath_GetPathSegment() negative testing.
+  ASSERT_EQ(nullptr, FPDFClipPath_GetPathSegment(nullptr, 0, 0));
+  ASSERT_EQ(nullptr, FPDFClipPath_GetPathSegment(clip_path, -1, 0));
+  ASSERT_EQ(nullptr, FPDFClipPath_GetPathSegment(clip_path, 1, 0));
+  ASSERT_EQ(nullptr, FPDFClipPath_GetPathSegment(clip_path, 0, -1));
+  ASSERT_EQ(nullptr, FPDFClipPath_GetPathSegment(clip_path, 0, 4));
+
+  // FPDFClipPath_GetPathSegment() positive testing.
+  segment = FPDFClipPath_GetPathSegment(clip_path, 0, 0);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(10, x);
+  EXPECT_EQ(15, y);
+  EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFClipPath_GetPathSegment(clip_path, 0, 1);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(40, x);
+  EXPECT_EQ(15, y);
+  EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFClipPath_GetPathSegment(clip_path, 0, 2);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(40, x);
+  EXPECT_EQ(35, y);
+  EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  segment = FPDFClipPath_GetPathSegment(clip_path, 0, 3);
+  EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y));
+  EXPECT_EQ(10, x);
+  EXPECT_EQ(35, y);
+  EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment));
+  EXPECT_FALSE(FPDFPathSegment_GetClose(segment));
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFEditEmbedderTest, SetText) {
   // Load document with some text.
   EXPECT_TRUE(OpenDocument("hello_world.pdf"));
diff --git a/fpdfsdk/fpdf_transformpage.cpp b/fpdfsdk/fpdf_transformpage.cpp
index 9ad5982..59c5fb5 100644
--- a/fpdfsdk/fpdf_transformpage.cpp
+++ b/fpdfsdk/fpdf_transformpage.cpp
@@ -272,6 +272,59 @@
   pPageObj->TransformGeneralState(matrix);
 }
 
+FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV
+FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object) {
+  CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  if (!pPageObj)
+    return nullptr;
+
+  return FPDFClipPathFromCPDFClipPath(&pPageObj->m_ClipPath);
+}
+
+FPDF_EXPORT int FPDF_CALLCONV FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path) {
+  CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
+  if (!pClipPath)
+    return -1;
+
+  return pClipPath->GetPathCount();
+}
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, int path_index) {
+  CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
+  if (!pClipPath)
+    return -1;
+
+  if (path_index < 0 ||
+      static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
+    return -1;
+  }
+
+  return pdfium::CollectionSize<int>(
+      pClipPath->GetPath(path_index).GetPoints());
+}
+
+FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV
+FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,
+                            int path_index,
+                            int segment_index) {
+  CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
+  if (!pClipPath)
+    return nullptr;
+
+  if (path_index < 0 ||
+      static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
+    return nullptr;
+  }
+
+  const std::vector<FX_PATHPOINT>& points =
+      pClipPath->GetPath(path_index).GetPoints();
+  if (!pdfium::IndexInBounds(points, segment_index))
+    return nullptr;
+
+  return FPDFPathSegmentFromFXPathPoint(&points[segment_index]);
+}
+
 FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV FPDF_CreateClipPath(float left,
                                                             float bottom,
                                                             float right,
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 9f58b8b..cf1c14f 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -343,6 +343,10 @@
     CHK(FPDFPage_GetThumbnailAsBitmap);
 
     // fpdf_transformpage.h
+    CHK(FPDFClipPath_CountPathSegments);
+    CHK(FPDFClipPath_CountPaths);
+    CHK(FPDFClipPath_GetPathSegment);
+    CHK(FPDFPageObj_GetClipPath);
     CHK(FPDFPageObj_TransformClipPath);
     CHK(FPDFPage_GetArtBox);
     CHK(FPDFPage_GetBleedBox);
diff --git a/public/fpdf_transformpage.h b/public/fpdf_transformpage.h
index a2eb6b0..38ef113 100644
--- a/public/fpdf_transformpage.h
+++ b/public/fpdf_transformpage.h
@@ -220,6 +220,51 @@
                               double e,
                               double f);
 
+// Experimental API.
+// Get the clip path of the page object.
+//
+//   page object - Handle to a page object. Returned by e.g.
+//                 FPDFPage_GetObject().
+//
+// Caller does not take ownership of the returned FPDF_CLIPPATH. Instead, it
+// remains valid until FPDF_ClosePage() is called for the page containing
+// page_object.
+FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV
+FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object);
+
+// Experimental API.
+// Get number of paths inside |clip_path|.
+//
+//   clip_path - handle to a clip_path.
+//
+// Returns the number of objects in |clip_path| or -1 on failure.
+FPDF_EXPORT int FPDF_CALLCONV FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path);
+
+// Experimental API.
+// Get number of segments inside one path of |clip_path|.
+//
+//   clip_path  - handle to a clip_path.
+//   path_index - index into the array of paths of the clip path.
+//
+// Returns the number of segments or -1 on failure.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, int path_index);
+
+// Experimental API.
+// Get segment in one specific path of |clip_path| at index.
+//
+//   clip_path     - handle to a clip_path.
+//   path_index    - the index of a path.
+//   segment_index - the index of a segment.
+//
+// Returns the handle to the segment, or NULL on failure.  The caller does not
+// take ownership of the returned FPDF_CLIPPATH. Instead, it remains valid until
+// FPDF_ClosePage() is called for the page containing page_object.
+FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV
+FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,
+                            int path_index,
+                            int segment_index);
+
 /**
  * Create a new clip path, with a rectangle inserted.
  *
diff --git a/testing/resources/clip_path.in b/testing/resources/clip_path.in
new file mode 100644
index 0000000..fd13f8e
--- /dev/null
+++ b/testing/resources/clip_path.in
@@ -0,0 +1,39 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /MediaBox [0 0 100 50]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 4 0 R
+  /Parent 2 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+10 15 m
+40 15 l
+40 35 l
+10 35 l
+W n
+0 0 1 RG
+10 10 m
+25 40 l
+40 10 l
+s
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/clip_path.pdf b/testing/resources/clip_path.pdf
new file mode 100644
index 0000000..8d27bcb
--- /dev/null
+++ b/testing/resources/clip_path.pdf
@@ -0,0 +1,50 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /MediaBox [0 0 100 50]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Contents 4 0 R
+  /Parent 2 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 71
+>>
+stream
+10 15 m
+40 15 l
+40 35 l
+10 35 l
+W n
+0 0 1 RG
+10 10 m
+25 40 l
+40 10 l
+s
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000156 00000 n 
+0000000225 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+347
+%%EOF