Adding APIs to get count, index and export value of form control
This CL adds three public APIs to get count, index and export value of
radio button and checkbox form controls in a form control group. The
APIs FPDFAnnot_GetFormControlCount(), FPDFAnnot_GetFormControlIndex()
and FPDFAnnot_GetFormFieldExportValue() are added to fpdf_annot.h.
This CL also includes tests to validate these APIs for radio buttons
and checkboxes.
Bug: pdfium:1527
Change-Id: I502cf1809a811f8cf6593c391cd145fb87b0db9e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/69890
Commit-Queue: Mansi Awasthi <maawas@microsoft.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/cpdfsdk_widget.cpp b/fpdfsdk/cpdfsdk_widget.cpp
index 4ecc705..fd7801d 100644
--- a/fpdfsdk/cpdfsdk_widget.cpp
+++ b/fpdfsdk/cpdfsdk_widget.cpp
@@ -464,6 +464,11 @@
return pFormField->GetDefaultValue();
}
+WideString CPDFSDK_Widget::GetExportValue() const {
+ CPDF_FormControl* pFormCtrl = GetFormControl();
+ return pFormCtrl->GetExportValue();
+}
+
WideString CPDFSDK_Widget::GetOptionLabel(int nIndex) const {
CPDF_FormField* pFormField = GetFormField();
return pFormField->GetOptionLabel(nIndex);
diff --git a/fpdfsdk/cpdfsdk_widget.h b/fpdfsdk/cpdfsdk_widget.h
index 8214e08..56ce8c8 100644
--- a/fpdfsdk/cpdfsdk_widget.h
+++ b/fpdfsdk/cpdfsdk_widget.h
@@ -68,6 +68,7 @@
int GetSelectedIndex(int nIndex) const;
WideString GetValue() const;
WideString GetDefaultValue() const;
+ WideString GetExportValue() const;
WideString GetOptionLabel(int nIndex) const;
int CountOptions() const;
bool IsOptionSelected(int nIndex) const;
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index b414c8f..2cbab85 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -272,6 +272,27 @@
return pPDFForm->GetFieldByDict(pAnnotDict);
}
+CPDFSDK_Widget* GetRadioButtonOrCheckBoxWidget(FPDF_FORMHANDLE hHandle,
+ FPDF_ANNOTATION annot) {
+ CPDF_Dictionary* pAnnotDict = GetAnnotDictFromFPDFAnnotation(annot);
+ if (!pAnnotDict)
+ return nullptr;
+
+ CPDFSDK_InteractiveForm* pForm = FormHandleToInteractiveForm(hHandle);
+ if (!pForm)
+ return nullptr;
+
+ CPDF_InteractiveForm* pPDFForm = pForm->GetInteractiveForm();
+ CPDF_FormField* pFormField = pPDFForm->GetFieldByDict(pAnnotDict);
+ if (!pFormField || (pFormField->GetType() != CPDF_FormField::kCheckBox &&
+ pFormField->GetType() != CPDF_FormField::kRadioButton)) {
+ return nullptr;
+ }
+
+ CPDF_FormControl* pFormControl = pPDFForm->GetControlByDict(pAnnotDict);
+ return pFormControl ? pForm->GetWidget(pFormControl) : nullptr;
+}
+
} // namespace
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
@@ -1105,29 +1126,7 @@
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);
+ CPDFSDK_Widget* pWidget = GetRadioButtonOrCheckBoxWidget(hHandle, annot);
return pWidget && pWidget->IsChecked();
}
@@ -1199,3 +1198,38 @@
return FPDFLinkFromCPDFDictionary(
CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict());
}
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot) {
+ CPDF_FormField* pFormField = GetFormField(hHandle, annot);
+ return pFormField ? pFormField->CountControls() : -1;
+}
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot) {
+ CPDF_Dictionary* pAnnotDict = GetAnnotDictFromFPDFAnnotation(annot);
+ if (!pAnnotDict)
+ return -1;
+
+ CPDFSDK_InteractiveForm* pForm = FormHandleToInteractiveForm(hHandle);
+ if (!pForm)
+ return -1;
+
+ CPDF_InteractiveForm* pPDFForm = pForm->GetInteractiveForm();
+ CPDF_FormField* pFormField = pPDFForm->GetFieldByDict(pAnnotDict);
+ CPDF_FormControl* pFormControl = pPDFForm->GetControlByDict(pAnnotDict);
+ return pFormField ? pFormField->GetControlIndex(pFormControl) : -1;
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle,
+ FPDF_ANNOTATION annot,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen) {
+ CPDFSDK_Widget* pWidget = GetRadioButtonOrCheckBoxWidget(hHandle, annot);
+ if (!pWidget)
+ return 0;
+
+ return Utf16EncodeMaybeCopyAndReturnLength(pWidget->GetExportValue(), buffer,
+ buflen);
+}
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index 261a177..a2d280f 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -2776,3 +2776,184 @@
UnloadPage(page);
}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlCountRadioButton) {
+ // Open a file with radio button widget annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("click_form.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ // Checks for bad annot.
+ EXPECT_EQ(-1,
+ FPDFAnnot_GetFormControlCount(form_handle(), /*annot=*/nullptr));
+
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 3));
+ ASSERT_TRUE(annot);
+
+ // Checks for bad form handle.
+ EXPECT_EQ(-1,
+ FPDFAnnot_GetFormControlCount(/*hHandle=*/nullptr, annot.get()));
+
+ EXPECT_EQ(3, FPDFAnnot_GetFormControlCount(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlCountCheckBox) {
+ // Open a file with checkbox 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);
+ EXPECT_EQ(1, FPDFAnnot_GetFormControlCount(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlCountInvalidAnnotation) {
+ // Open a file with ink annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+ ASSERT_TRUE(annot);
+ EXPECT_EQ(-1, FPDFAnnot_GetFormControlCount(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlIndexRadioButton) {
+ // Open a file with radio button widget annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("click_form.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ // Checks for bad annot.
+ EXPECT_EQ(-1,
+ FPDFAnnot_GetFormControlIndex(form_handle(), /*annot=*/nullptr));
+
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 3));
+ ASSERT_TRUE(annot);
+
+ // Checks for bad form handle.
+ EXPECT_EQ(-1,
+ FPDFAnnot_GetFormControlIndex(/*hHandle=*/nullptr, annot.get()));
+
+ EXPECT_EQ(1, FPDFAnnot_GetFormControlIndex(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlIndexCheckBox) {
+ // Open a file with checkbox 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);
+ EXPECT_EQ(0, FPDFAnnot_GetFormControlIndex(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormControlIndexInvalidAnnotation) {
+ // Open a file with ink annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+ ASSERT_TRUE(annot);
+ EXPECT_EQ(-1, FPDFAnnot_GetFormControlIndex(form_handle(), annot.get()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormFieldExportValueRadioButton) {
+ // Open a file with radio button widget annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("click_form.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ // Checks for bad annot.
+ EXPECT_EQ(0u, FPDFAnnot_GetFormFieldExportValue(
+ form_handle(), /*annot=*/nullptr,
+ /*buffer=*/nullptr, /*buflen=*/0));
+
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 6));
+ ASSERT_TRUE(annot);
+
+ // Checks for bad form handle.
+ EXPECT_EQ(0u, FPDFAnnot_GetFormFieldExportValue(
+ /*hHandle=*/nullptr, annot.get(),
+ /*buffer=*/nullptr, /*buflen=*/0));
+
+ unsigned long length_bytes =
+ FPDFAnnot_GetFormFieldExportValue(form_handle(), annot.get(),
+ /*buffer=*/nullptr, /*buflen=*/0);
+ ASSERT_EQ(14u, length_bytes);
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+ EXPECT_EQ(14u, FPDFAnnot_GetFormFieldExportValue(form_handle(), annot.get(),
+ buf.data(), length_bytes));
+ EXPECT_EQ(L"value2", GetPlatformWString(buf.data()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormFieldExportValueCheckBox) {
+ // Open a file with checkbox 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);
+
+ unsigned long length_bytes =
+ FPDFAnnot_GetFormFieldExportValue(form_handle(), annot.get(),
+ /*buffer=*/nullptr, /*buflen=*/0);
+ ASSERT_EQ(8u, length_bytes);
+ std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(length_bytes);
+ EXPECT_EQ(8u, FPDFAnnot_GetFormFieldExportValue(form_handle(), annot.get(),
+ buf.data(), length_bytes));
+ EXPECT_EQ(L"Yes", GetPlatformWString(buf.data()));
+ }
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFAnnotEmbedderTest, GetFormFieldExportValueInvalidAnnotation) {
+ // Open a file with ink annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, 0));
+ ASSERT_TRUE(annot);
+ EXPECT_EQ(0u, FPDFAnnot_GetFormFieldExportValue(form_handle(), annot.get(),
+ /*buffer=*/nullptr,
+ /*buflen=*/0));
+ }
+
+ UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index c2c77d6..9ee6778 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -50,7 +50,10 @@
CHK(FPDFAnnot_GetFocusableSubtypes);
CHK(FPDFAnnot_GetFocusableSubtypesCount);
CHK(FPDFAnnot_GetFontSize);
+ CHK(FPDFAnnot_GetFormControlCount);
+ CHK(FPDFAnnot_GetFormControlIndex);
CHK(FPDFAnnot_GetFormFieldAtPoint);
+ CHK(FPDFAnnot_GetFormFieldExportValue);
CHK(FPDFAnnot_GetFormFieldFlags);
CHK(FPDFAnnot_GetFormFieldName);
CHK(FPDFAnnot_GetFormFieldType);
diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h
index b1fe901..9561d84 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -755,6 +755,54 @@
// if the input annot is NULL or input annot's subtype is not link.
FPDF_EXPORT FPDF_LINK FPDF_CALLCONV FPDFAnnot_GetLink(FPDF_ANNOTATION annot);
+// Experimental API.
+// Gets the count of annotations in the |annot|'s control group.
+// A group of interactive form annotations is collectively called a form
+// control group. Here, |annot|, an interactive form annotation, should be
+// either a radio button or a checkbox.
+//
+// hHandle - handle to the form fill module, returned by
+// FPDFDOC_InitFormFillEnvironment.
+// annot - handle to an annotation.
+//
+// Returns number of controls in its control group or -1 on error.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFAnnot_GetFormControlCount(FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot);
+
+// Experimental API.
+// Gets the index of |annot| in |annot|'s control group.
+// A group of interactive form annotations is collectively called a form
+// control group. Here, |annot|, an interactive form annotation, should be
+// either a radio button or a checkbox.
+//
+// hHandle - handle to the form fill module, returned by
+// FPDFDOC_InitFormFillEnvironment.
+// annot - handle to an annotation.
+//
+// Returns index of a given |annot| in its control group or -1 on error.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFAnnot_GetFormControlIndex(FPDF_FORMHANDLE hHandle, FPDF_ANNOTATION annot);
+
+// Experimental API.
+// Gets the export value of |annot| which is an interactive form annotation.
+// Intended for use with radio button and checkbox widget annotations.
+// |buffer| is only modified if |buflen| is longer than the length of contents.
+// In case of error, nothing will be added to |buffer| and the return value
+// will be 0. Note that return value of empty string is 2 for "\0\0".
+//
+// hHandle - handle to the form fill module, returned by
+// FPDFDOC_InitFormFillEnvironment().
+// annot - handle to an interactive form annotation.
+// buffer - buffer for holding the value string, encoded in UTF-16LE.
+// buflen - length of the buffer in bytes.
+//
+// Returns the length of the string value in bytes.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFAnnot_GetFormFieldExportValue(FPDF_FORMHANDLE hHandle,
+ FPDF_ANNOTATION annot,
+ FPDF_WCHAR* buffer,
+ unsigned long buflen);
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus