blob: 576e3ba0cceeb498e43f898eaeed4c608cd687cb [file] [log] [blame]
// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fpdfdoc/cpdf_formfield.h"
#include <map>
#include <memory>
#include <set>
#include <utility>
#include "constants/form_fields.h"
#include "constants/form_flags.h"
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/parser/cfdf_document.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fpdfdoc/cpdf_defaultappearance.h"
#include "core/fpdfdoc/cpdf_formcontrol.h"
#include "core/fpdfdoc/cpdf_interactiveform.h"
#include "core/fpdfdoc/cpvt_generateap.h"
#include "third_party/base/check.h"
#include "third_party/base/stl_util.h"
namespace {
const CPDF_Object* GetFieldAttrRecursive(const CPDF_Dictionary* pFieldDict,
const ByteString& name,
int nLevel) {
static constexpr int kGetFieldMaxRecursion = 32;
if (!pFieldDict || nLevel > kGetFieldMaxRecursion)
return nullptr;
const CPDF_Object* pAttr = pFieldDict->GetDirectObjectFor(name);
if (pAttr)
return pAttr;
return GetFieldAttrRecursive(
pFieldDict->GetDictFor(pdfium::form_fields::kParent), name, nLevel + 1);
}
} // namespace
// static
Optional<FormFieldType> CPDF_FormField::IntToFormFieldType(int value) {
if (value >= static_cast<int>(FormFieldType::kUnknown) &&
value < static_cast<int>(kFormFieldTypeCount)) {
return {static_cast<FormFieldType>(value)};
}
return {};
}
// static
const CPDF_Object* CPDF_FormField::GetFieldAttr(
const CPDF_Dictionary* pFieldDict,
const ByteString& name) {
return GetFieldAttrRecursive(pFieldDict, name, 0);
}
// static
CPDF_Object* CPDF_FormField::GetFieldAttr(CPDF_Dictionary* pFieldDict,
const ByteString& name) {
return const_cast<CPDF_Object*>(GetFieldAttrRecursive(
static_cast<const CPDF_Dictionary*>(pFieldDict), name, 0));
}
// static
WideString CPDF_FormField::GetFullNameForDict(CPDF_Dictionary* pFieldDict) {
WideString full_name;
std::set<CPDF_Dictionary*> visited;
CPDF_Dictionary* pLevel = pFieldDict;
while (pLevel) {
visited.insert(pLevel);
WideString short_name = pLevel->GetUnicodeTextFor(pdfium::form_fields::kT);
if (!short_name.IsEmpty()) {
if (full_name.IsEmpty())
full_name = std::move(short_name);
else
full_name = short_name + L'.' + full_name;
}
pLevel = pLevel->GetDictFor(pdfium::form_fields::kParent);
if (pdfium::Contains(visited, pLevel))
break;
}
return full_name;
}
CPDF_FormField::CPDF_FormField(CPDF_InteractiveForm* pForm,
CPDF_Dictionary* pDict)
: m_pForm(pForm), m_pDict(pDict) {
InitFieldFlags();
}
CPDF_FormField::~CPDF_FormField() = default;
void CPDF_FormField::InitFieldFlags() {
const CPDF_Object* ft_attr =
GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kFT);
ByteString type_name = ft_attr ? ft_attr->GetString() : ByteString();
uint32_t flags = GetFieldFlags();
m_bRequired = flags & pdfium::form_flags::kRequired;
m_bNoExport = flags & pdfium::form_flags::kNoExport;
if (type_name == pdfium::form_fields::kBtn) {
if (flags & pdfium::form_flags::kButtonRadio) {
m_Type = kRadioButton;
m_bIsUnison = flags & pdfium::form_flags::kButtonRadiosInUnison;
} else if (flags & pdfium::form_flags::kButtonPushbutton) {
m_Type = kPushButton;
} else {
m_Type = kCheckBox;
m_bIsUnison = true;
}
} else if (type_name == pdfium::form_fields::kTx) {
if (flags & pdfium::form_flags::kTextFileSelect)
m_Type = kFile;
else if (flags & pdfium::form_flags::kTextRichText)
m_Type = kRichText;
else
m_Type = kText;
LoadDA();
} else if (type_name == pdfium::form_fields::kCh) {
if (flags & pdfium::form_flags::kChoiceCombo) {
m_Type = kComboBox;
} else {
m_Type = kListBox;
m_bIsMultiSelectListBox = flags & pdfium::form_flags::kChoiceMultiSelect;
}
m_bUseSelectedIndices = UseSelectedIndicesObject();
LoadDA();
} else if (type_name == pdfium::form_fields::kSig) {
m_Type = kSign;
}
}
WideString CPDF_FormField::GetFullName() const {
return GetFullNameForDict(m_pDict.Get());
}
bool CPDF_FormField::ResetField(NotificationOption notify) {
switch (m_Type) {
case kCheckBox:
case kRadioButton: {
int iCount = CountControls();
// TODO(weili): Check whether anything special needs to be done for
// |m_bIsUnison|.
for (int i = 0; i < iCount; i++) {
CheckControl(i, GetControl(i)->IsDefaultChecked(),
NotificationOption::kDoNotNotify);
}
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify())
m_pForm->GetFormNotify()->AfterCheckedStatusChange(this);
break;
}
case kComboBox:
case kListBox: {
ClearSelection(NotificationOption::kDoNotNotify);
WideString csValue;
int iIndex = GetDefaultSelectedItem();
if (iIndex >= 0)
csValue = GetOptionLabel(iIndex);
if (notify == NotificationOption::kNotify &&
!NotifyListOrComboBoxBeforeChange(csValue)) {
return false;
}
SetItemSelection(iIndex, true, NotificationOption::kDoNotNotify);
if (notify == NotificationOption::kNotify)
NotifyListOrComboBoxAfterChange();
break;
}
case kText:
case kRichText:
case kFile:
default: {
const CPDF_Object* pDV = GetDefaultValueObject();
WideString csDValue;
if (pDV)
csDValue = pDV->GetUnicodeText();
WideString csValue;
{
// Limit the scope of |pV| because it may get invalidated below.
const CPDF_Object* pV = GetValueObject();
if (pV)
csValue = pV->GetUnicodeText();
}
bool bHasRV = !!GetFieldAttr(m_pDict.Get(), "RV");
if (!bHasRV && (csDValue == csValue))
return false;
if (notify == NotificationOption::kNotify &&
!NotifyBeforeValueChange(csDValue)) {
return false;
}
if (pDV) {
RetainPtr<CPDF_Object> pClone = pDV->Clone();
if (!pClone)
return false;
m_pDict->SetFor(pdfium::form_fields::kV, std::move(pClone));
if (bHasRV) {
m_pDict->SetFor("RV", pDV->Clone());
}
} else {
m_pDict->RemoveFor(pdfium::form_fields::kV);
m_pDict->RemoveFor("RV");
}
if (notify == NotificationOption::kNotify)
NotifyAfterValueChange();
break;
}
}
return true;
}
int CPDF_FormField::CountControls() const {
return pdfium::CollectionSize<int>(GetControls());
}
CPDF_FormControl* CPDF_FormField::GetControl(int index) const {
return GetControls()[index].Get();
}
int CPDF_FormField::GetControlIndex(const CPDF_FormControl* pControl) const {
if (!pControl)
return -1;
const auto& controls = GetControls();
auto it = std::find(controls.begin(), controls.end(), pControl);
return it != controls.end() ? it - controls.begin() : -1;
}
FormFieldType CPDF_FormField::GetFieldType() const {
switch (m_Type) {
case kPushButton:
return FormFieldType::kPushButton;
case kCheckBox:
return FormFieldType::kCheckBox;
case kRadioButton:
return FormFieldType::kRadioButton;
case kComboBox:
return FormFieldType::kComboBox;
case kListBox:
return FormFieldType::kListBox;
case kText:
case kRichText:
case kFile:
return FormFieldType::kTextField;
case kSign:
return FormFieldType::kSignature;
default:
return FormFieldType::kUnknown;
}
}
CPDF_AAction CPDF_FormField::GetAdditionalAction() const {
CPDF_Object* pObj = GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kAA);
return CPDF_AAction(pObj ? pObj->GetDict() : nullptr);
}
WideString CPDF_FormField::GetAlternateName() const {
const CPDF_Object* pObj =
GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kTU);
return pObj ? pObj->GetUnicodeText() : WideString();
}
WideString CPDF_FormField::GetMappingName() const {
const CPDF_Object* pObj =
GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kTM);
return pObj ? pObj->GetUnicodeText() : WideString();
}
uint32_t CPDF_FormField::GetFieldFlags() const {
const CPDF_Object* pObj =
GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kFf);
return pObj ? pObj->GetInteger() : 0;
}
ByteString CPDF_FormField::GetDefaultStyle() const {
const CPDF_Object* pObj = GetFieldAttr(m_pDict.Get(), "DS");
return pObj ? pObj->GetString() : ByteString();
}
void CPDF_FormField::SetOpt(RetainPtr<CPDF_Object> pOpt) {
m_pDict->SetFor("Opt", std::move(pOpt));
}
WideString CPDF_FormField::GetValue(bool bDefault) const {
if (GetType() == kCheckBox || GetType() == kRadioButton)
return GetCheckValue(bDefault);
const CPDF_Object* pValue =
bDefault ? GetDefaultValueObject() : GetValueObject();
if (!pValue) {
if (!bDefault && m_Type != kText)
pValue = GetDefaultValueObject();
if (!pValue)
return WideString();
}
switch (pValue->GetType()) {
case CPDF_Object::kString:
case CPDF_Object::kStream:
return pValue->GetUnicodeText();
case CPDF_Object::kArray:
pValue = pValue->AsArray()->GetDirectObjectAt(0);
if (pValue)
return pValue->GetUnicodeText();
break;
default:
break;
}
return WideString();
}
WideString CPDF_FormField::GetValue() const {
return GetValue(false);
}
WideString CPDF_FormField::GetDefaultValue() const {
return GetValue(true);
}
bool CPDF_FormField::SetValue(const WideString& value,
bool bDefault,
NotificationOption notify) {
switch (m_Type) {
case kCheckBox:
case kRadioButton: {
SetCheckValue(value, bDefault, notify);
return true;
}
case kFile:
case kRichText:
case kText:
case kComboBox: {
WideString csValue = value;
if (notify == NotificationOption::kNotify &&
!NotifyBeforeValueChange(csValue)) {
return false;
}
ByteString key(bDefault ? pdfium::form_fields::kDV
: pdfium::form_fields::kV);
m_pDict->SetNewFor<CPDF_String>(key, csValue);
int iIndex = FindOption(csValue);
if (iIndex < 0) {
if (m_Type == kRichText && !bDefault) {
m_pDict->SetFor("RV", m_pDict->GetObjectFor(key)->Clone());
}
m_pDict->RemoveFor("I");
} else {
if (!bDefault) {
ClearSelection(NotificationOption::kDoNotNotify);
SetItemSelection(iIndex, true, NotificationOption::kDoNotNotify);
}
}
if (notify == NotificationOption::kNotify)
NotifyAfterValueChange();
break;
}
case kListBox: {
int iIndex = FindOption(value);
if (iIndex < 0)
return false;
if (bDefault && iIndex == GetDefaultSelectedItem())
return false;
if (notify == NotificationOption::kNotify &&
!NotifyBeforeSelectionChange(value)) {
return false;
}
if (!bDefault) {
ClearSelection(NotificationOption::kDoNotNotify);
SetItemSelection(iIndex, true, NotificationOption::kDoNotNotify);
}
if (notify == NotificationOption::kNotify)
NotifyAfterSelectionChange();
break;
}
default:
break;
}
return true;
}
bool CPDF_FormField::SetValue(const WideString& value,
NotificationOption notify) {
return SetValue(value, false, notify);
}
int CPDF_FormField::GetMaxLen() const {
if (const CPDF_Object* pObj = GetFieldAttr(m_pDict.Get(), "MaxLen"))
return pObj->GetInteger();
for (auto& pControl : GetControls()) {
if (!pControl)
continue;
const CPDF_Dictionary* pWidgetDict = pControl->GetWidget();
if (pWidgetDict->KeyExist("MaxLen"))
return pWidgetDict->GetIntegerFor("MaxLen");
}
return 0;
}
int CPDF_FormField::CountSelectedItems() const {
const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
if (!pValue)
return 0;
if (pValue->IsString() || pValue->IsNumber())
return pValue->GetString().IsEmpty() ? 0 : 1;
const CPDF_Array* pArray = pValue->AsArray();
return pArray ? pArray->size() : 0;
}
int CPDF_FormField::GetSelectedIndex(int index) const {
const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
if (!pValue)
return -1;
if (pValue->IsNumber())
return pValue->GetInteger();
WideString sel_value;
if (pValue->IsString()) {
if (index != 0)
return -1;
sel_value = pValue->GetUnicodeText();
} else {
const CPDF_Array* pArray = pValue->AsArray();
if (!pArray || index < 0)
return -1;
const CPDF_Object* elementValue = pArray->GetDirectObjectAt(index);
sel_value = elementValue ? elementValue->GetUnicodeText() : WideString();
}
if (index < CountSelectedOptions()) {
int iOptIndex = GetSelectedOptionIndex(index);
WideString csOpt = GetOptionValue(iOptIndex);
if (csOpt == sel_value)
return iOptIndex;
}
for (int i = 0; i < CountOptions(); i++) {
if (sel_value == GetOptionValue(i))
return i;
}
return -1;
}
bool CPDF_FormField::ClearSelection(NotificationOption notify) {
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify()) {
WideString csValue;
int iIndex = GetSelectedIndex(0);
if (iIndex >= 0)
csValue = GetOptionLabel(iIndex);
if (!NotifyListOrComboBoxBeforeChange(csValue))
return false;
}
m_pDict->RemoveFor(pdfium::form_fields::kV);
m_pDict->RemoveFor("I");
if (notify == NotificationOption::kNotify)
NotifyListOrComboBoxAfterChange();
return true;
}
bool CPDF_FormField::IsItemSelected(int index) const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
if (index < 0 || index >= CountOptions())
return false;
// First consider the /I entry if it is valid, then fall back to the /V entry.
return m_bUseSelectedIndices ? IsSelectedIndex(index)
: IsSelectedOption(GetOptionValue(index));
}
bool CPDF_FormField::SetItemSelection(int index,
bool bSelected,
NotificationOption notify) {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
if (index < 0 || index >= CountOptions())
return false;
WideString opt_value = GetOptionValue(index);
if (notify == NotificationOption::kNotify &&
!NotifyListOrComboBoxBeforeChange(opt_value)) {
return false;
}
if (bSelected)
SetItemSelectionSelected(index, opt_value);
else
SetItemSelectionUnselected(index, opt_value);
// UseSelectedIndicesObject() has a non-trivial linearithmic run-time, so run
// only if necessary.
if (!m_bUseSelectedIndices)
m_bUseSelectedIndices = UseSelectedIndicesObject();
if (notify == NotificationOption::kNotify)
NotifyListOrComboBoxAfterChange();
return true;
}
void CPDF_FormField::SetItemSelectionSelected(int index,
const WideString& opt_value) {
if (GetType() != kListBox) {
m_pDict->SetNewFor<CPDF_String>(pdfium::form_fields::kV, opt_value);
CPDF_Array* pI = m_pDict->SetNewFor<CPDF_Array>("I");
pI->AppendNew<CPDF_Number>(index);
return;
}
SelectOption(index, true, NotificationOption::kDoNotNotify);
if (!m_bIsMultiSelectListBox) {
m_pDict->SetNewFor<CPDF_String>(pdfium::form_fields::kV, opt_value);
return;
}
CPDF_Array* pArray = m_pDict->SetNewFor<CPDF_Array>(pdfium::form_fields::kV);
for (int i = 0; i < CountOptions(); i++) {
if (i == index || IsItemSelected(i))
pArray->AppendNew<CPDF_String>(GetOptionValue(i));
}
}
void CPDF_FormField::SetItemSelectionUnselected(int index,
const WideString& opt_value) {
const CPDF_Object* pValue = GetValueObject();
if (!pValue)
return;
if (GetType() != kListBox) {
m_pDict->RemoveFor(pdfium::form_fields::kV);
m_pDict->RemoveFor("I");
return;
}
SelectOption(index, false, NotificationOption::kDoNotNotify);
if (pValue->IsString()) {
if (pValue->GetUnicodeText() == opt_value) {
m_pDict->RemoveFor(pdfium::form_fields::kV);
}
return;
}
if (!pValue->IsArray())
return;
auto pArray = pdfium::MakeRetain<CPDF_Array>();
for (int i = 0; i < CountOptions(); i++) {
if (i != index && IsItemSelected(i))
pArray->AppendNew<CPDF_String>(GetOptionValue(i));
}
if (pArray->size() > 0) {
m_pDict->SetFor(pdfium::form_fields::kV, pArray);
}
}
bool CPDF_FormField::IsItemDefaultSelected(int index) const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
if (index < 0 || index >= CountOptions())
return false;
int iDVIndex = GetDefaultSelectedItem();
return iDVIndex >= 0 && iDVIndex == index;
}
int CPDF_FormField::GetDefaultSelectedItem() const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
const CPDF_Object* pValue = GetDefaultValueObject();
if (!pValue)
return -1;
WideString csDV = pValue->GetUnicodeText();
if (csDV.IsEmpty())
return -1;
for (int i = 0; i < CountOptions(); i++) {
if (csDV == GetOptionValue(i))
return i;
}
return -1;
}
int CPDF_FormField::CountOptions() const {
const CPDF_Array* pArray = ToArray(GetFieldAttr(m_pDict.Get(), "Opt"));
return pArray ? pArray->size() : 0;
}
WideString CPDF_FormField::GetOptionText(int index, int sub_index) const {
const CPDF_Array* pArray = ToArray(GetFieldAttr(m_pDict.Get(), "Opt"));
if (!pArray)
return WideString();
const CPDF_Object* pOption = pArray->GetDirectObjectAt(index);
if (!pOption)
return WideString();
if (const CPDF_Array* pOptionArray = pOption->AsArray())
pOption = pOptionArray->GetDirectObjectAt(sub_index);
const CPDF_String* pString = ToString(pOption);
return pString ? pString->GetUnicodeText() : WideString();
}
WideString CPDF_FormField::GetOptionLabel(int index) const {
return GetOptionText(index, 1);
}
WideString CPDF_FormField::GetOptionValue(int index) const {
return GetOptionText(index, 0);
}
int CPDF_FormField::FindOption(const WideString& csOptValue) const {
for (int i = 0; i < CountOptions(); i++) {
if (GetOptionValue(i) == csOptValue)
return i;
}
return -1;
}
bool CPDF_FormField::CheckControl(int iControlIndex,
bool bChecked,
NotificationOption notify) {
DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
CPDF_FormControl* pControl = GetControl(iControlIndex);
if (!pControl)
return false;
if (!bChecked && pControl->IsChecked() == bChecked)
return false;
const WideString csWExport = pControl->GetExportValue();
int iCount = CountControls();
for (int i = 0; i < iCount; i++) {
CPDF_FormControl* pCtrl = GetControl(i);
if (m_bIsUnison) {
WideString csEValue = pCtrl->GetExportValue();
if (csEValue == csWExport) {
if (pCtrl->GetOnStateName() == pControl->GetOnStateName())
pCtrl->CheckControl(bChecked);
else if (bChecked)
pCtrl->CheckControl(false);
} else if (bChecked) {
pCtrl->CheckControl(false);
}
} else {
if (i == iControlIndex)
pCtrl->CheckControl(bChecked);
else if (bChecked)
pCtrl->CheckControl(false);
}
}
const CPDF_Object* pOpt = GetFieldAttr(m_pDict.Get(), "Opt");
if (!ToArray(pOpt)) {
ByteString csBExport = PDF_EncodeText(csWExport);
if (bChecked) {
m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, csBExport);
} else {
ByteString csV;
const CPDF_Object* pV = GetValueObject();
if (pV)
csV = pV->GetString();
if (csV == csBExport)
m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, "Off");
}
} else if (bChecked) {
m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV,
ByteString::Format("%d", iControlIndex));
}
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify())
m_pForm->GetFormNotify()->AfterCheckedStatusChange(this);
return true;
}
WideString CPDF_FormField::GetCheckValue(bool bDefault) const {
DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
WideString csExport = L"Off";
int iCount = CountControls();
for (int i = 0; i < iCount; i++) {
CPDF_FormControl* pControl = GetControl(i);
bool bChecked =
bDefault ? pControl->IsDefaultChecked() : pControl->IsChecked();
if (bChecked) {
csExport = pControl->GetExportValue();
break;
}
}
return csExport;
}
bool CPDF_FormField::SetCheckValue(const WideString& value,
bool bDefault,
NotificationOption notify) {
DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
int iCount = CountControls();
for (int i = 0; i < iCount; i++) {
CPDF_FormControl* pControl = GetControl(i);
WideString csExport = pControl->GetExportValue();
bool val = csExport == value;
if (!bDefault) {
CheckControl(GetControlIndex(pControl), val,
NotificationOption::kDoNotNotify);
}
if (val)
break;
}
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify())
m_pForm->GetFormNotify()->AfterCheckedStatusChange(this);
return true;
}
int CPDF_FormField::GetTopVisibleIndex() const {
const CPDF_Object* pObj = GetFieldAttr(m_pDict.Get(), "TI");
return pObj ? pObj->GetInteger() : 0;
}
int CPDF_FormField::CountSelectedOptions() const {
const CPDF_Array* pArray = ToArray(GetSelectedIndicesObject());
return pArray ? pArray->size() : 0;
}
int CPDF_FormField::GetSelectedOptionIndex(int index) const {
const CPDF_Array* pArray = ToArray(GetSelectedIndicesObject());
if (!pArray)
return -1;
int iCount = pArray->size();
if (iCount < 0 || index >= iCount)
return -1;
return pArray->GetIntegerAt(index);
}
bool CPDF_FormField::IsSelectedOption(const WideString& wsOptValue) const {
const CPDF_Object* pValueObject = GetValueObject();
if (!pValueObject)
return false;
const CPDF_Array* pValueArray = pValueObject->AsArray();
if (pValueArray) {
CPDF_ArrayLocker locker(pValueArray);
for (const auto& pObj : locker) {
if (pObj->IsString() && pObj->GetUnicodeText() == wsOptValue)
return true;
}
}
return pValueObject->IsString() &&
pValueObject->GetUnicodeText() == wsOptValue;
}
bool CPDF_FormField::IsSelectedIndex(int iOptIndex) const {
const CPDF_Object* pSelectedIndicesObject = GetSelectedIndicesObject();
if (!pSelectedIndicesObject)
return false;
const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
if (pSelectedIndicesArray) {
CPDF_ArrayLocker locker(pSelectedIndicesArray);
for (const auto& pObj : locker) {
if (pObj->IsNumber() && pObj->GetInteger() == iOptIndex)
return true;
}
}
return pSelectedIndicesObject->IsNumber() &&
pSelectedIndicesObject->GetInteger() == iOptIndex;
}
bool CPDF_FormField::SelectOption(int iOptIndex,
bool bSelected,
NotificationOption notify) {
CPDF_Array* pArray = m_pDict->GetArrayFor("I");
if (!pArray) {
if (!bSelected)
return true;
pArray = m_pDict->SetNewFor<CPDF_Array>("I");
}
bool bReturn = false;
for (size_t i = 0; i < pArray->size(); i++) {
int iFind = pArray->GetIntegerAt(i);
if (iFind == iOptIndex) {
if (bSelected)
return true;
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify()) {
WideString csValue = GetOptionLabel(iOptIndex);
if (!NotifyListOrComboBoxBeforeChange(csValue))
return false;
}
pArray->RemoveAt(i);
bReturn = true;
break;
}
if (iFind > iOptIndex) {
if (!bSelected)
continue;
if (notify == NotificationOption::kNotify && m_pForm->GetFormNotify()) {
WideString csValue = GetOptionLabel(iOptIndex);
if (!NotifyListOrComboBoxBeforeChange(csValue))
return false;
}
pArray->InsertNewAt<CPDF_Number>(i, iOptIndex);
bReturn = true;
break;
}
}
if (!bReturn) {
if (bSelected)
pArray->AppendNew<CPDF_Number>(iOptIndex);
if (pArray->IsEmpty())
m_pDict->RemoveFor("I");
}
if (notify == NotificationOption::kNotify)
NotifyListOrComboBoxAfterChange();
return true;
}
bool CPDF_FormField::UseSelectedIndicesObject() const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
const CPDF_Object* pSelectedIndicesObject = GetSelectedIndicesObject();
if (!pSelectedIndicesObject)
return false;
// If there's not value object, then just use the indices object.
const CPDF_Object* pValueObject = GetValueObject();
if (!pValueObject)
return true;
// Verify that the selected indices object is either an array or a number and
// count the number of indices.
size_t selected_indices_size;
const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
if (pSelectedIndicesArray)
selected_indices_size = pSelectedIndicesArray->size();
else if (pSelectedIndicesObject->IsNumber())
selected_indices_size = 1;
else
return false;
// Verify that the number of values is equal to |selected_indices_size|. Then,
// count the number of occurances of each of the distinct values in the values
// object.
std::map<WideString, size_t> values;
const CPDF_Array* pValueArray = pValueObject->AsArray();
if (pValueArray) {
if (pValueArray->size() != selected_indices_size)
return false;
CPDF_ArrayLocker locker(pValueArray);
for (const auto& pObj : locker) {
if (pObj->IsString())
values[pObj->GetUnicodeText()]++;
}
} else if (pValueObject->IsString()) {
if (selected_indices_size != 1)
return false;
values[pValueObject->GetUnicodeText()]++;
}
// Validate each index in the selected indices object. Then, verify that items
// identified by selected indices entry do not differ from those in the values
// entry of the field dictionary.
const int num_options = CountOptions();
if (pSelectedIndicesArray) {
CPDF_ArrayLocker locker(pSelectedIndicesArray);
for (const auto& pObj : locker) {
if (!pObj->IsNumber())
return false;
int index = pObj->GetInteger();
if (index < 0 || index >= num_options)
return false;
WideString wsOptValue = GetOptionValue(index);
auto it = values.find(wsOptValue);
if (it == values.end())
return false;
it->second--;
if (it->second == 0)
values.erase(it);
}
return values.empty();
}
DCHECK(pSelectedIndicesObject->IsNumber());
int index = pSelectedIndicesObject->GetInteger();
if (index < 0 || index >= num_options)
return false;
return pdfium::Contains(values, GetOptionValue(index));
}
void CPDF_FormField::LoadDA() {
CPDF_Dictionary* pFormDict = m_pForm->GetFormDict();
if (!pFormDict)
return;
ByteString DA;
if (const CPDF_Object* pObj = GetFieldAttr(m_pDict.Get(), "DA"))
DA = pObj->GetString();
if (DA.IsEmpty())
DA = pFormDict->GetStringFor("DA");
if (DA.IsEmpty())
return;
CPDF_Dictionary* pDR = pFormDict->GetDictFor("DR");
if (!pDR)
return;
CPDF_Dictionary* pFont = pDR->GetDictFor("Font");
if (!ValidateFontResourceDict(pFont))
return;
CPDF_DefaultAppearance appearance(DA);
Optional<ByteString> font_name = appearance.GetFont(&m_FontSize);
if (!font_name)
return;
CPDF_Dictionary* pFontDict = pFont->GetDictFor(*font_name);
if (!pFontDict)
return;
auto* pData = CPDF_DocPageData::FromDocument(m_pForm->GetDocument());
m_pFont = pData->GetFont(pFontDict);
}
bool CPDF_FormField::NotifyBeforeSelectionChange(const WideString& value) {
auto* pNotify = m_pForm->GetFormNotify();
return !pNotify || pNotify->BeforeSelectionChange(this, value);
}
void CPDF_FormField::NotifyAfterSelectionChange() {
auto* pNotify = m_pForm->GetFormNotify();
if (pNotify)
pNotify->AfterSelectionChange(this);
}
bool CPDF_FormField::NotifyBeforeValueChange(const WideString& value) {
auto* pNotify = m_pForm->GetFormNotify();
return !pNotify || pNotify->BeforeValueChange(this, value);
}
void CPDF_FormField::NotifyAfterValueChange() {
auto* pNotify = m_pForm->GetFormNotify();
if (pNotify)
pNotify->AfterValueChange(this);
}
bool CPDF_FormField::NotifyListOrComboBoxBeforeChange(const WideString& value) {
switch (GetType()) {
case kListBox:
return NotifyBeforeSelectionChange(value);
case kComboBox:
return NotifyBeforeValueChange(value);
default:
return true;
}
}
void CPDF_FormField::NotifyListOrComboBoxAfterChange() {
switch (GetType()) {
case kListBox:
NotifyAfterSelectionChange();
break;
case kComboBox:
NotifyAfterValueChange();
break;
default:
break;
}
}
const CPDF_Object* CPDF_FormField::GetDefaultValueObject() const {
return GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kDV);
}
const CPDF_Object* CPDF_FormField::GetValueObject() const {
return GetFieldAttr(m_pDict.Get(), pdfium::form_fields::kV);
}
const CPDF_Object* CPDF_FormField::GetSelectedIndicesObject() const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
return GetFieldAttr(m_pDict.Get(), "I");
}
const CPDF_Object* CPDF_FormField::GetValueOrSelectedIndicesObject() const {
DCHECK(GetType() == kComboBox || GetType() == kListBox);
const CPDF_Object* pValue = GetValueObject();
return pValue ? pValue : GetSelectedIndicesObject();
}
const std::vector<UnownedPtr<CPDF_FormControl>>& CPDF_FormField::GetControls()
const {
return m_pForm->GetControlsForField(this);
}