Add FPDFAnnot_GetInkListPath() API

This is somewhat similar to FPDFAnnot_GetVertices(), but this is for ink
annotations and here the value is an array of paths.

So first add an FPDFAnnot_GetInkListCount() to get the number of paths,
then FPDFAnnot_GetInkListPath() can be used to get the individual paths.

Change-Id: I204a5a53e949fdbb7b264711c27107fe62c9f2be
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/76350
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/constants/annotation_common.h b/constants/annotation_common.h
index 6f96e62..be64206 100644
--- a/constants/annotation_common.h
+++ b/constants/annotation_common.h
@@ -29,6 +29,9 @@
 // Entries for polygon and polyline annotations.
 constexpr char kVertices[] = "Vertices";
 
+// Entries for ink annotations
+constexpr char kInkList[] = "InkList";
+
 }  // namespace annotation
 }  // namespace pdfium
 
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index 13c73f6..51b4332 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -296,6 +296,18 @@
   return pFormControl ? pForm->GetWidget(pFormControl) : nullptr;
 }
 
+CPDF_Array* GetInkList(FPDF_ANNOTATION annot) {
+  FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot);
+  if (subtype != FPDF_ANNOT_INK)
+    return 0;
+
+  CPDF_Dictionary* annot_dict = GetAnnotDictFromFPDFAnnotation(annot);
+  if (!annot_dict)
+    return 0;
+
+  return annot_dict->GetArrayFor(pdfium::annotation::kInkList);
+}
+
 }  // namespace
 
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
@@ -837,6 +849,44 @@
   return points_len;
 }
 
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot) {
+  CPDF_Array* ink_list = GetInkList(annot);
+  if (!ink_list)
+    return 0;
+
+  return ink_list->size();
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot,
+                         unsigned long path_index,
+                         FS_POINTF* buffer,
+                         unsigned long length) {
+  unsigned long path_count = FPDFAnnot_GetInkListCount(annot);
+  if (path_index >= path_count)
+    return 0;
+
+  CPDF_Array* ink_list = GetInkList(annot);
+  if (!ink_list)
+    return 0;
+
+  CPDF_Array* path = ink_list->GetArrayAt(path_index);
+  if (!path)
+    return 0;
+
+  // Truncate to an even number.
+  unsigned long points_len = path->size() / 2;
+  if (buffer && length >= points_len) {
+    for (unsigned long i = 0; i < points_len; ++i) {
+      buffer[i].x = path->GetNumberAt(i * 2);
+      buffer[i].y = path->GetNumberAt(i * 2 + 1);
+    }
+  }
+
+  return points_len;
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_HasKey(FPDF_ANNOTATION annot,
                                                      FPDF_BYTESTRING key) {
   CPDF_Dictionary* pAnnotDict = GetAnnotDictFromFPDFAnnotation(annot);
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index 2b66e32..233f842 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -3232,7 +3232,7 @@
     ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
     ASSERT_TRUE(annot);
 
-    // FPDFSignatureObj_GetTime() positive testing.
+    // FPDFAnnot_GetVertices() positive testing.
     unsigned long size = FPDFAnnot_GetVertices(annot.get(), nullptr, 0);
     const size_t kExpectedSize = 3;
     ASSERT_EQ(kExpectedSize, size);
@@ -3288,3 +3288,92 @@
 
   UnloadPage(page);
 }
+
+TEST_F(FPDFAnnotEmbedderTest, InkAnnotation) {
+  ASSERT_TRUE(OpenDocument("ink_annot.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+  EXPECT_EQ(2, FPDFPage_GetAnnotCount(page));
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+    ASSERT_TRUE(annot);
+
+    // FPDFAnnot_GetInkListCount() and FPDFAnnot_GetInkListPath() positive
+    // testing.
+    unsigned long size = FPDFAnnot_GetInkListCount(annot.get());
+    const size_t kExpectedSize = 1;
+    ASSERT_EQ(kExpectedSize, size);
+    const unsigned long kPathIndex = 0;
+    unsigned long path_size =
+        FPDFAnnot_GetInkListPath(annot.get(), kPathIndex, nullptr, 0);
+    const size_t kExpectedPathSize = 3;
+    ASSERT_EQ(kExpectedPathSize, path_size);
+    std::vector<FS_POINTF> path_buffer(path_size);
+    EXPECT_EQ(path_size,
+              FPDFAnnot_GetInkListPath(annot.get(), kPathIndex,
+                                       path_buffer.data(), path_size));
+    EXPECT_FLOAT_EQ(159.0f, path_buffer[0].x);
+    EXPECT_FLOAT_EQ(296.0f, path_buffer[0].y);
+    EXPECT_FLOAT_EQ(350.0f, path_buffer[1].x);
+    EXPECT_FLOAT_EQ(411.0f, path_buffer[1].y);
+    EXPECT_FLOAT_EQ(472.0f, path_buffer[2].x);
+    EXPECT_FLOAT_EQ(243.42f, path_buffer[2].y);
+
+    // FPDFAnnot_GetInkListCount() and FPDFAnnot_GetInkListPath() negative
+    // testing.
+    EXPECT_EQ(0U, FPDFAnnot_GetInkListCount(nullptr));
+    EXPECT_EQ(0U, FPDFAnnot_GetInkListPath(nullptr, 0, nullptr, 0));
+
+    // out of bounds path_index.
+    EXPECT_EQ(0U, FPDFAnnot_GetInkListPath(nullptr, 42, nullptr, 0));
+
+    // path_buffer is not overwritten if it is too small.
+    path_buffer.resize(1);
+    path_buffer[0].x = 42;
+    path_buffer[0].y = 43;
+    path_size = FPDFAnnot_GetInkListPath(
+        annot.get(), kPathIndex, path_buffer.data(), path_buffer.size());
+    EXPECT_EQ(kExpectedSize, size);
+    EXPECT_FLOAT_EQ(42, path_buffer[0].x);
+    EXPECT_FLOAT_EQ(43, path_buffer[0].y);
+  }
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 1));
+    ASSERT_TRUE(annot);
+
+    // This has an odd number of elements in the path array, ignore the last
+    // element.
+    unsigned long size = FPDFAnnot_GetInkListCount(annot.get());
+    const size_t kExpectedSize = 1;
+    ASSERT_EQ(kExpectedSize, size);
+    const unsigned long kPathIndex = 0;
+    unsigned long path_size =
+        FPDFAnnot_GetInkListPath(annot.get(), kPathIndex, nullptr, 0);
+    const size_t kExpectedPathSize = 3;
+    ASSERT_EQ(kExpectedPathSize, path_size);
+    std::vector<FS_POINTF> path_buffer(path_size);
+    EXPECT_EQ(path_size,
+              FPDFAnnot_GetInkListPath(annot.get(), kPathIndex,
+                                       path_buffer.data(), path_size));
+    EXPECT_FLOAT_EQ(259.0f, path_buffer[0].x);
+    EXPECT_FLOAT_EQ(396.0f, path_buffer[0].y);
+    EXPECT_FLOAT_EQ(450.0f, path_buffer[1].x);
+    EXPECT_FLOAT_EQ(511.0f, path_buffer[1].y);
+    EXPECT_FLOAT_EQ(572.0f, path_buffer[2].x);
+    EXPECT_FLOAT_EQ(343.0f, path_buffer[2].y);
+  }
+
+  {
+    // Wrong annotation type.
+    ScopedFPDFAnnotation polygon_annot(
+        FPDFPage_CreateAnnot(page, FPDF_ANNOT_POLYGON));
+    EXPECT_EQ(0U, FPDFAnnot_GetInkListCount(polygon_annot.get()));
+    const unsigned long kPathIndex = 0;
+    EXPECT_EQ(0U, FPDFAnnot_GetInkListPath(polygon_annot.get(), kPathIndex,
+                                           nullptr, 0));
+  }
+
+  UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index f3155d6..b47b04f 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -59,6 +59,8 @@
     CHK(FPDFAnnot_GetFormFieldName);
     CHK(FPDFAnnot_GetFormFieldType);
     CHK(FPDFAnnot_GetFormFieldValue);
+    CHK(FPDFAnnot_GetInkListCount);
+    CHK(FPDFAnnot_GetInkListPath);
     CHK(FPDFAnnot_GetLink);
     CHK(FPDFAnnot_GetLinkedAnnot);
     CHK(FPDFAnnot_GetNumberValue);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index 7159602..d121344 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -412,6 +412,34 @@
                       unsigned long length);
 
 // Experimental API.
+// Get the number of paths in the ink list of an ink annotation.
+//
+//   annot  - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot()
+//
+// Returns the number of paths in the ink list if the annotation is of type ink,
+// 0 otherwise.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetInkListCount(FPDF_ANNOTATION annot);
+
+// Experimental API.
+// Get a path in the ink list of an ink annotation. |buffer| is an array of
+// points of the path. If |length| is less than the returned length, or |annot|
+// or |buffer| is NULL, |buffer| will not be modified.
+//
+//   annot  - handle to an annotation, as returned by e.g. FPDFPage_GetAnnot()
+//   path_index - index of the path
+//   buffer - buffer for holding the points.
+//   length - length of the buffer in points.
+//
+// Returns the number of points of the path if the annotation is of type ink, 0
+// otherwise.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetInkListPath(FPDF_ANNOTATION annot,
+                         unsigned long path_index,
+                         FS_POINTF* buffer,
+                         unsigned long length);
+
+// Experimental API.
 // Check if |annot|'s dictionary has |key| as a key.
 //
 //   annot  - handle to an annotation.
diff --git a/testing/resources/ink_annot.in b/testing/resources/ink_annot.in
new file mode 100644
index 0000000..da90b29
--- /dev/null
+++ b/testing/resources/ink_annot.in
@@ -0,0 +1,48 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-1)
+  /F 4
+  /InkList [[159 296 350 411 472 243.42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-2)
+  /F 4
+  /InkList [[259 396 450 511 572 343 42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/ink_annot.pdf b/testing/resources/ink_annot.pdf
new file mode 100644
index 0000000..306f28d
--- /dev/null
+++ b/testing/resources/ink_annot.pdf
@@ -0,0 +1,60 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-1)
+  /F 4
+  /InkList [[159 296 350 411 472 243.42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+5 0 obj <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-2)
+  /F 4
+  /InkList [[259 396 450 511 572 343 42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000251 00000 n 
+0000000422 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+593
+%%EOF