Add FORM_GetFocusedAnnot and FORM_SetFocusedAnnot APIs

Add APIs to retrieve the current focused annotation and to update the
current focused annotation. This would enable an embedder to update the
current focused annotation or retrieve the currently focused annotation
as needed.

Embedder tests have been added to validate the added APIs.

Bug: chromium:1023284
Change-Id: I32f355d029a97ffc545c9a3034b8d025cbfca35b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/66230
Commit-Queue:  Ankit Kumar 🌪️ <ankk@microsoft.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_formfill.cpp b/fpdfsdk/fpdf_formfill.cpp
index 085fec3..3ddcd2a 100644
--- a/fpdfsdk/fpdf_formfill.cpp
+++ b/fpdfsdk/fpdf_formfill.cpp
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "core/fpdfapi/page/cpdf_annotcontext.h"
 #include "core/fpdfapi/page/cpdf_occontext.h"
 #include "core/fpdfapi/page/cpdf_page.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
@@ -20,6 +21,7 @@
 #include "core/fpdfdoc/cpdf_interactiveform.h"
 #include "core/fxge/cfx_defaultrenderdevice.h"
 #include "fpdfsdk/cpdfsdk_actionhandler.h"
+#include "fpdfsdk/cpdfsdk_annot.h"
 #include "fpdfsdk/cpdfsdk_baannothandler.h"
 #include "fpdfsdk/cpdfsdk_formfillenvironment.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
@@ -571,6 +573,72 @@
   return pFormFillEnv->KillFocusAnnot(0);
 }
 
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle,
+                     int* page_index,
+                     FPDF_ANNOTATION* annot) {
+  if (!page_index || !annot)
+    return false;
+
+  CPDFSDK_FormFillEnvironment* form_fill_env =
+      CPDFSDKFormFillEnvironmentFromFPDFFormHandle(handle);
+  if (!form_fill_env)
+    return false;
+
+  // Set |page_index| and |annot| to default values. This is returned when there
+  // is no focused annotation.
+  *page_index = -1;
+  *annot = nullptr;
+
+  CPDFSDK_Annot* cpdfsdk_annot = form_fill_env->GetFocusAnnot();
+  if (!cpdfsdk_annot)
+    return true;
+
+  CPDFSDK_PageView* page_view = cpdfsdk_annot->GetPageView();
+  if (!page_view->IsValid())
+    return true;
+
+  CPDF_Page* page = cpdfsdk_annot->GetPDFPage();
+  if (!page)
+    return true;
+
+  CPDF_Dictionary* annot_dict = cpdfsdk_annot->GetPDFAnnot()->GetAnnotDict();
+  auto annot_context = pdfium::MakeUnique<CPDF_AnnotContext>(annot_dict, page);
+
+  *page_index = page_view->GetPageIndex();
+  // Caller takes ownership.
+  *annot = FPDFAnnotationFromCPDFAnnotContext(annot_context.release());
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle,
+                     FPDF_PAGE page,
+                     FPDF_ANNOTATION annot) {
+  CPDFSDK_FormFillEnvironment* form_fill_env =
+      CPDFSDKFormFillEnvironmentFromFPDFFormHandle(handle);
+  if (!form_fill_env)
+    return false;
+
+  CPDF_AnnotContext* annot_context = CPDFAnnotContextFromFPDFAnnotation(annot);
+  if (!annot_context)
+    return false;
+
+  IPDF_Page* pdf_page = IPDFPageFromFPDFPage(page);
+  if (!pdf_page)
+    return false;
+
+  CPDFSDK_PageView* page_view = form_fill_env->GetPageView(pdf_page, true);
+  if (!page_view->IsValid())
+    return false;
+
+  CPDF_Dictionary* annot_dict = annot_context->GetAnnotDict();
+
+  ObservedPtr<CPDFSDK_Annot> cpdfsdk_annot(
+      page_view->GetAnnotByDict(annot_dict));
+  return form_fill_env->SetFocusAnnot(&cpdfsdk_annot);
+}
+
 FPDF_EXPORT void FPDF_CALLCONV FPDF_FFLDraw(FPDF_FORMHANDLE hHandle,
                                             FPDF_BITMAP bitmap,
                                             FPDF_PAGE page,
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index 3e09a0a..4ac3022 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -638,6 +638,93 @@
   UnloadPage(page);
 }
 
+TEST_F(FPDFFormFillEmbedderTest, GetFocusedAnnotation) {
+  ASSERT_TRUE(OpenDocument("annotiter.pdf"));
+  std::vector<FPDF_PAGE> pages;
+  for (size_t i = 0; i < 3; ++i) {
+    pages.push_back(LoadPage(i));
+    ASSERT_TRUE(pages.back());
+  }
+
+  // Ensure that there is no focused annotation.
+  FPDF_ANNOTATION annot = nullptr;
+  int page_index = -2;
+  ASSERT_TRUE(FORM_GetFocusedAnnot(form_handle(), &page_index, &annot));
+  EXPECT_FALSE(annot);
+  EXPECT_EQ(-1, page_index);
+
+  // Validate that nullptr values are handled properly.
+  EXPECT_FALSE(FORM_GetFocusedAnnot(nullptr, &page_index, &annot));
+  EXPECT_FALSE(FORM_GetFocusedAnnot(form_handle(), &page_index, nullptr));
+  EXPECT_FALSE(FORM_GetFocusedAnnot(form_handle(), nullptr, &annot));
+
+  const CFX_PointF right_bottom_annot_point(410.0f, 210.0f);
+  constexpr int kExpectedAnnotIndex = 3;
+
+  for (size_t i = 0; i < pages.size(); ++i) {
+    // Invoke click on the form field to bring it to focus.
+    FORM_OnMouseMove(form_handle(), pages[i], 0, right_bottom_annot_point.x,
+                     right_bottom_annot_point.y);
+    FORM_OnLButtonDown(form_handle(), pages[i], 0, right_bottom_annot_point.x,
+                       right_bottom_annot_point.y);
+    FORM_OnLButtonUp(form_handle(), pages[i], 0, right_bottom_annot_point.x,
+                     right_bottom_annot_point.y);
+
+    ASSERT_TRUE(FORM_GetFocusedAnnot(form_handle(), &page_index, &annot));
+    ASSERT_TRUE(annot);
+
+    EXPECT_EQ(kExpectedAnnotIndex, FPDFPage_GetAnnotIndex(pages[i], annot));
+    EXPECT_EQ(static_cast<int>(i), page_index);
+
+    FPDFPage_CloseAnnot(annot);
+  }
+
+  for (FPDF_PAGE page : pages)
+    UnloadPage(page);
+}
+
+TEST_F(FPDFFormFillEmbedderTest, SetFocusedAnnotation) {
+  ASSERT_TRUE(OpenDocument("annotiter.pdf"));
+  std::vector<FPDF_PAGE> pages;
+  for (size_t i = 0; i < 3; ++i) {
+    pages.push_back(LoadPage(i));
+    ASSERT_TRUE(pages.back());
+  }
+
+  // Ensure that there is no focused annotation.
+  FPDF_ANNOTATION annot = nullptr;
+  int page_index = -2;
+  ASSERT_TRUE(FORM_GetFocusedAnnot(form_handle(), &page_index, &annot));
+  EXPECT_FALSE(annot);
+  EXPECT_EQ(-1, page_index);
+
+  // Validate that nullptr values are handled properly.
+  EXPECT_FALSE(FORM_SetFocusedAnnot(nullptr, pages[0], annot));
+  EXPECT_FALSE(FORM_SetFocusedAnnot(form_handle(), nullptr, annot));
+  EXPECT_FALSE(FORM_SetFocusedAnnot(form_handle(), pages[0], nullptr));
+
+  constexpr int kExpectedAnnotIndex = 2;
+
+  for (size_t i = 0; i < pages.size(); ++i) {
+    // Setting focus on an annotation on page i.
+    ScopedFPDFAnnotation focused_annot(
+        FPDFPage_GetAnnot(pages[i], kExpectedAnnotIndex));
+    ASSERT_TRUE(focused_annot);
+
+    ASSERT_TRUE(
+        FORM_SetFocusedAnnot(form_handle(), pages[i], focused_annot.get()));
+
+    ASSERT_TRUE(FORM_GetFocusedAnnot(form_handle(), &page_index, &annot));
+    EXPECT_EQ(kExpectedAnnotIndex, FPDFPage_GetAnnotIndex(pages[i], annot));
+    EXPECT_EQ(static_cast<int>(i), page_index);
+
+    FPDFPage_CloseAnnot(annot);
+  }
+
+  for (FPDF_PAGE page : pages)
+    UnloadPage(page);
+}
+
 class DoURIActionBlockedDelegate final : public EmbedderTest::Delegate {
  public:
   void DoURIAction(FPDF_BYTESTRING uri) override {
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 94cd3ce..60bd6fd 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -239,6 +239,7 @@
     CHK(FORM_DoDocumentOpenAction);
     CHK(FORM_DoPageAAction);
     CHK(FORM_ForceToKillFocus);
+    CHK(FORM_GetFocusedAnnot);
     CHK(FORM_GetFocusedText);
     CHK(FORM_GetSelectedText);
     CHK(FORM_IsIndexSelected);
@@ -256,6 +257,7 @@
     CHK(FORM_OnRButtonUp);
     CHK(FORM_Redo);
     CHK(FORM_ReplaceSelection);
+    CHK(FORM_SetFocusedAnnot);
     CHK(FORM_SetIndexSelected);
     CHK(FORM_Undo);
     CHK(FPDFDOC_ExitFormFillEnvironment);
diff --git a/public/fpdf_formfill.h b/public/fpdf_formfill.h
index ea50d76..9b6c4d4 100644
--- a/public/fpdf_formfill.h
+++ b/public/fpdf_formfill.h
@@ -1572,6 +1572,53 @@
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
 FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle);
 
+/*
+ * Experimental API.
+ * Function: FORM_GetFocusedAnnot.
+ *       Call this member function to get the currently focused annotation.
+ * Parameters:
+ *       handle      -   Handle to the form fill module, as returned by
+ *                       FPDFDOC_InitFormFillEnvironment().
+ *       page_index  -   Buffer to hold the index number of the page which
+ *                       contains the focused annotation. 0 for the first page.
+ *                       Can't be NULL.
+ *       annot       -   Buffer to hold the focused annotation. Can't be NULL.
+ * Return Value:
+ *       On success, return true and write to the out parameters. Otherwise return
+ *       false and leave the out parameters unmodified.
+ * Comments:
+ *       Not currently supported for XFA forms - will report no focused
+ *       annotation.
+ *       Must call FPDFPage_CloseAnnot() when the annotation returned in |annot|
+ *       by this function is no longer needed.
+ *       This will return true and set |page_index| to -1 and |annot| to NULL, if
+ *       there is no focused annotation.
+ */
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FORM_GetFocusedAnnot(FPDF_FORMHANDLE handle,
+                     int* page_index,
+                     FPDF_ANNOTATION* annot);
+
+/*
+ * Experimental API.
+ * Function: FORM_SetFocusedAnnot.
+ *       Call this member function to set the currently focused annotation.
+ * Parameters:
+ *       handle      -   Handle to the form fill module, as returned by
+ *                       FPDFDOC_InitFormFillEnvironment().
+ *       page        -   Handle to a page.
+ *       annot       -   Handle to an annotation.
+ * Return Value:
+ *       True indicates success; otherwise false.
+ * Comments:
+ *       |annot| can't be NULL. To kill focus, use FORM_ForceToKillFocus()
+ *       instead.
+ */
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FORM_SetFocusedAnnot(FPDF_FORMHANDLE handle,
+                     FPDF_PAGE page,
+                     FPDF_ANNOTATION annot);
+
 // Form Field Types
 // The names of the defines are stable, but the specific values associated with
 // them are not, so do not hardcode their values.