Add API for adding an inklist to a newly created ink annotation.

There is no API to add an inklist for an ink annotation. Since inklist
is a required field for a PDF spec compliant ink annotation, ink
annotation without inklist won't render on conformant PDF readers
(e.g., Adobe Reader)

This CL adds API and relevant tests for adding an inklist to an ink
annotation.

Bug: pdfium:1160
Change-Id: Idc6029e54e4b2c942c1ceccf6d8e8da34f77b3bc
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/67110
Commit-Queue: Shikha Walia <shwali@microsoft.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index 7c3b849..44b2b0f 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -27,6 +27,7 @@
 #include "core/fpdfdoc/cpdf_formfield.h"
 #include "core/fpdfdoc/cpdf_interactiveform.h"
 #include "core/fpdfdoc/cpvt_generateap.h"
+#include "core/fxcrt/fx_safe_types.h"
 #include "core/fxge/cfx_color.h"
 #include "fpdfsdk/cpdfsdk_formfillenvironment.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
@@ -427,6 +428,35 @@
   return true;
 }
 
+FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot,
+                                                     const FS_POINTF* points,
+                                                     size_t point_count) {
+  if (FPDFAnnot_GetSubtype(annot) != FPDF_ANNOT_INK || !points ||
+      point_count == 0 ||
+      !pdfium::base::IsValueInRangeForNumericType<int32_t>(point_count)) {
+    return -1;
+  }
+
+  CPDF_Dictionary* annot_dict = GetAnnotDictFromFPDFAnnotation(annot);
+
+  CPDF_Array* inklist = annot_dict->GetArrayFor("InkList");
+  if (!inklist)
+    inklist = annot_dict->SetNewFor<CPDF_Array>("InkList");
+
+  FX_SAFE_SIZE_T safe_ink_size = inklist->size();
+  safe_ink_size += 1;
+  if (!safe_ink_size.IsValid<int32_t>())
+    return -1;
+
+  CPDF_Array* ink_coord_list = inklist->AppendNew<CPDF_Array>();
+  for (size_t i = 0; i < point_count; i++) {
+    ink_coord_list->AppendNew<CPDF_Number>(points[i].x);
+    ink_coord_list->AppendNew<CPDF_Number>(points[i].y);
+  }
+
+  return static_cast<int>(inklist->size() - 1);
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
 FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj) {
   CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot);
diff --git a/fpdfsdk/fpdf_annot_unittest.cpp b/fpdfsdk/fpdf_annot_unittest.cpp
index a5f619e..e961541 100644
--- a/fpdfsdk/fpdf_annot_unittest.cpp
+++ b/fpdfsdk/fpdf_annot_unittest.cpp
@@ -9,6 +9,7 @@
 #include "constants/annotation_common.h"
 #include "core/fpdfapi/page/cpdf_annotcontext.h"
 #include "core/fpdfapi/page/cpdf_pagemodule.h"
+#include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
 #include "public/cpp/fpdf_scopers.h"
@@ -135,3 +136,86 @@
   bool alpha_source_flag = gs_dict->GetBooleanFor("AIS", true);
   EXPECT_FALSE(alpha_source_flag);
 }
+
+TEST_F(PDFAnnotTest, InkListAPIValidations) {
+  ScopedFPDFDocument doc(FPDF_CreateNewDocument());
+  ASSERT_TRUE(doc);
+  ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100));
+  ASSERT_TRUE(page);
+
+  // Create a new ink annotation.
+  ScopedFPDFAnnotation ink_annot(
+      FPDFPage_CreateAnnot(page.get(), FPDF_ANNOT_INK));
+  ASSERT_TRUE(ink_annot);
+  CPDF_AnnotContext* context =
+      CPDFAnnotContextFromFPDFAnnotation(ink_annot.get());
+  ASSERT_TRUE(context);
+  CPDF_Dictionary* annot_dict = context->GetAnnotDict();
+  ASSERT_TRUE(annot_dict);
+
+  static constexpr FS_POINTF kFirstInkStroke[] = {
+      {80.0f, 90.0f}, {81.0f, 91.0f}, {82.0f, 92.0f},
+      {83.0f, 93.0f}, {84.0f, 94.0f}, {85.0f, 95.0f}};
+  static constexpr size_t kFirstStrokePointCount =
+      FX_ArraySize(kFirstInkStroke);
+
+  static constexpr FS_POINTF kSecondInkStroke[] = {
+      {70.0f, 90.0f}, {71.0f, 91.0f}, {72.0f, 92.0f}};
+  static constexpr size_t kSecondStrokePointCount =
+      FX_ArraySize(kSecondInkStroke);
+
+  static constexpr FS_POINTF kThirdInkStroke[] = {{60.0f, 90.0f},
+                                                  {61.0f, 91.0f},
+                                                  {62.0f, 92.0f},
+                                                  {63.0f, 93.0f},
+                                                  {64.0f, 94.0f}};
+  static constexpr size_t kThirdStrokePointCount =
+      FX_ArraySize(kThirdInkStroke);
+
+  // Negative test: |annot| is passed as nullptr.
+  EXPECT_EQ(-1, FPDFAnnot_AddInkStroke(nullptr, kFirstInkStroke,
+                                       kFirstStrokePointCount));
+
+  // Negative test: |annot| is not ink annotation.
+  // Create a new highlight annotation.
+  ScopedFPDFAnnotation highlight_annot(
+      FPDFPage_CreateAnnot(page.get(), FPDF_ANNOT_HIGHLIGHT));
+  ASSERT_TRUE(highlight_annot);
+  EXPECT_EQ(-1, FPDFAnnot_AddInkStroke(highlight_annot.get(), kFirstInkStroke,
+                                       kFirstStrokePointCount));
+
+  // Negative test: passing |point_count| as  0.
+  EXPECT_EQ(-1, FPDFAnnot_AddInkStroke(ink_annot.get(), kFirstInkStroke, 0));
+
+  // Negative test: passing |points| array as nullptr.
+  EXPECT_EQ(-1, FPDFAnnot_AddInkStroke(ink_annot.get(), nullptr,
+                                       kFirstStrokePointCount));
+
+  // Negative test: passing |point_count| more than ULONG_MAX/2.
+  EXPECT_EQ(-1, FPDFAnnot_AddInkStroke(ink_annot.get(), kSecondInkStroke,
+                                       ULONG_MAX / 2 + 1));
+
+  // InkStroke should get added to ink annotation. Also inklist should get
+  // created.
+  EXPECT_EQ(0, FPDFAnnot_AddInkStroke(ink_annot.get(), kFirstInkStroke,
+                                      kFirstStrokePointCount));
+
+  CPDF_Array* inklist = annot_dict->GetArrayFor("InkList");
+  ASSERT_TRUE(inklist);
+  EXPECT_EQ(1u, inklist->size());
+  EXPECT_EQ(kFirstStrokePointCount * 2, inklist->GetArrayAt(0)->size());
+
+  // Adding another inkStroke to ink annotation with all valid paremeters.
+  // InkList already exists in ink_annot.
+  EXPECT_EQ(1, FPDFAnnot_AddInkStroke(ink_annot.get(), kSecondInkStroke,
+                                      kSecondStrokePointCount));
+  EXPECT_EQ(2u, inklist->size());
+  EXPECT_EQ(kSecondStrokePointCount * 2, inklist->GetArrayAt(1)->size());
+
+  // Adding one more InkStroke to the ink annotation. |point_count| passed is
+  // less than the data available in |buffer|.
+  EXPECT_EQ(2, FPDFAnnot_AddInkStroke(ink_annot.get(), kThirdInkStroke,
+                                      kThirdStrokePointCount - 1));
+  EXPECT_EQ(3u, inklist->size());
+  EXPECT_EQ((kThirdStrokePointCount - 1) * 2, inklist->GetArrayAt(2)->size());
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 0cf0e1c..ba2f1ed 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -39,6 +39,7 @@
 // Function to call from gtest harness to ensure linker resolution.
 int CheckPDFiumCApi() {
     // fpdf_annot.h
+    CHK(FPDFAnnot_AddInkStroke);
     CHK(FPDFAnnot_AppendAttachmentPoints);
     CHK(FPDFAnnot_AppendObject);
     CHK(FPDFAnnot_CountAttachmentPoints);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index b10f249..531a97f 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -194,6 +194,23 @@
 FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj);
 
 // Experimental API.
+// Add a new InkStroke, represented by an array of points, to the InkList of
+// |annot|. The API creates an InkList if one doesn't already exist in |annot|.
+// This API works only for ink annotations. Please refer section 12.5.6.13 in
+// PDF 32000-1:2008 Specification.
+//
+//   annot       - handle to an annotation.
+//   points      - pointer to a FS_POINTF array representing input points.
+//   point_count - number of elements in |points| array. This should not exceed
+//                 the maximum value that can be represented by an int32_t).
+//
+// Returns the 0-based index at which the new InkStroke is added in the InkList
+// of the |annot|. Returns -1 on failure.
+FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_AddInkStroke(FPDF_ANNOTATION annot,
+                                                     const FS_POINTF* points,
+                                                     size_t point_count);
+
+// Experimental API.
 // Add |obj| to |annot|. |obj| must have been created by
 // FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(), and
 // will be owned by |annot|. Note that an |obj| cannot belong to more than one