FPDFAnnot_IsChecked API for checkboxes and radio buttons.

Part of feature work to make PDFium more usable by mobile platforms
that need to provide accessibility labels that reflect the state of the
widget.

R=thestig@chromium.org

Bug: b/135921512
Change-Id: I4f6d79335c0d29a592202577a2c238b6a110d98f
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/56870
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index 7a2a164..3bca040 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -949,3 +949,31 @@
   *value = pWidget->GetFontSize();
   return true;
 }
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle,
+                                                        FPDF_ANNOTATION annot) {
+  CPDFSDK_InteractiveForm* pForm = FormHandleToInteractiveForm(hHandle);
+  if (!pForm)
+    return false;
+
+  CPDF_Dictionary* pAnnotDict = GetAnnotDictFromFPDFAnnotation(annot);
+  if (!pAnnotDict)
+    return false;
+
+  CPDF_InteractiveForm* pPDFForm = pForm->GetInteractiveForm();
+  CPDF_FormField* pFormField = pPDFForm->GetFieldByDict(pAnnotDict);
+  if (!pFormField)
+    return false;
+
+  if (pFormField->GetType() != CPDF_FormField::kCheckBox &&
+      pFormField->GetType() != CPDF_FormField::kRadioButton) {
+    return false;
+  }
+
+  CPDF_FormControl* pFormControl = pPDFForm->GetControlByDict(pAnnotDict);
+  if (!pFormControl)
+    return false;
+
+  CPDFSDK_Widget* pWidget = pForm->GetWidget(pFormControl);
+  return pWidget && pWidget->IsChecked();
+}
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index ca71f24..aaf3657 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -2042,3 +2042,112 @@
 
   UnloadPage(page);
 }
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedCheckbox) {
+  // Open a file with checkbox and radiobuttons widget annotations and load its
+  // first page.
+  ASSERT_TRUE(OpenDocument("click_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 1));
+    ASSERT_TRUE(annot);
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+  }
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedCheckboxReadOnly) {
+  // Open a file with checkbox and radiobutton widget annotations and load its
+  // first page.
+  ASSERT_TRUE(OpenDocument("click_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+    ASSERT_TRUE(annot);
+    ASSERT_TRUE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+  }
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedRadioButton) {
+  // Open a file with checkbox and radiobutton widget annotations and load its
+  // first page.
+  ASSERT_TRUE(OpenDocument("click_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 5));
+    ASSERT_TRUE(annot);
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+
+    annot.reset(FPDFPage_GetAnnot(page, 6));
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+
+    annot.reset(FPDFPage_GetAnnot(page, 7));
+    ASSERT_TRUE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+  }
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedRadioButtonReadOnly) {
+  // Open a file with checkbox and radiobutton widget annotations and load its
+  // first page.
+  ASSERT_TRUE(OpenDocument("click_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 2));
+    ASSERT_TRUE(annot);
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+
+    annot.reset(FPDFPage_GetAnnot(page, 3));
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+
+    annot.reset(FPDFPage_GetAnnot(page, 4));
+    ASSERT_TRUE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+  }
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedInvalidArguments) {
+  // Open a file with checkbox and radiobuttons widget annotations and load its
+  // first page.
+  ASSERT_TRUE(OpenDocument("click_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+    ASSERT_TRUE(annot);
+    ASSERT_FALSE(FPDFAnnot_IsChecked(nullptr, annot.get()));
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), nullptr));
+    ASSERT_FALSE(FPDFAnnot_IsChecked(nullptr, nullptr));
+  }
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, IsCheckedInvalidWidgetType) {
+  // Open a file with text widget annotations and load its first page.
+  ASSERT_TRUE(OpenDocument("text_form.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+    ASSERT_TRUE(annot);
+    ASSERT_FALSE(FPDFAnnot_IsChecked(form_handle(), annot.get()));
+  }
+
+  UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 73df47e..a9897ee 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -60,6 +60,7 @@
     CHK(FPDFAnnot_GetValueType);
     CHK(FPDFAnnot_HasAttachmentPoints);
     CHK(FPDFAnnot_HasKey);
+    CHK(FPDFAnnot_IsChecked);
     CHK(FPDFAnnot_IsObjectSupportedSubtype);
     CHK(FPDFAnnot_IsSupportedSubtype);
     CHK(FPDFAnnot_RemoveObject);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index ee81ae8..e6e7765 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -608,6 +608,18 @@
                       FPDF_ANNOTATION annot,
                       float* value);
 
+// Experimental API.
+// Determine if |annot| is a form widget that is checked. Intended for use with
+// checkbox and radio button widgets.
+//
+//   hHandle - handle to the form fill module, returned by
+//             FPDFDOC_InitFormFillEnvironment.
+//   annot   - handle to an annotation.
+//
+// Returns true if |annot| is a form widget and is checked, false otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_IsChecked(FPDF_FORMHANDLE hHandle,
+                                                        FPDF_ANNOTATION annot);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus