• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fpdfdoc/cpdf_formfield.h"
8 
9 #include <map>
10 #include <set>
11 #include <utility>
12 
13 #include "constants/form_fields.h"
14 #include "constants/form_flags.h"
15 #include "core/fpdfapi/font/cpdf_font.h"
16 #include "core/fpdfapi/page/cpdf_docpagedata.h"
17 #include "core/fpdfapi/parser/cfdf_document.h"
18 #include "core/fpdfapi/parser/cpdf_array.h"
19 #include "core/fpdfapi/parser/cpdf_dictionary.h"
20 #include "core/fpdfapi/parser/cpdf_name.h"
21 #include "core/fpdfapi/parser/cpdf_number.h"
22 #include "core/fpdfapi/parser/cpdf_stream.h"
23 #include "core/fpdfapi/parser/cpdf_string.h"
24 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
25 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
26 #include "core/fpdfdoc/cpdf_defaultappearance.h"
27 #include "core/fpdfdoc/cpdf_formcontrol.h"
28 #include "core/fpdfdoc/cpdf_generateap.h"
29 #include "core/fpdfdoc/cpdf_interactiveform.h"
30 #include "core/fxcrt/check.h"
31 #include "core/fxcrt/containers/contains.h"
32 #include "core/fxcrt/stl_util.h"
33 
34 namespace {
35 
GetFieldAttrRecursive(const CPDF_Dictionary * pFieldDict,const ByteString & name,int nLevel)36 RetainPtr<const CPDF_Object> GetFieldAttrRecursive(
37     const CPDF_Dictionary* pFieldDict,
38     const ByteString& name,
39     int nLevel) {
40   static constexpr int kGetFieldMaxRecursion = 32;
41   if (!pFieldDict || nLevel > kGetFieldMaxRecursion)
42     return nullptr;
43 
44   RetainPtr<const CPDF_Object> pAttr = pFieldDict->GetDirectObjectFor(name);
45   if (pAttr)
46     return pAttr;
47 
48   return GetFieldAttrRecursive(
49       pFieldDict->GetDictFor(pdfium::form_fields::kParent).Get(), name,
50       nLevel + 1);
51 }
52 
IsComboOrListField(CPDF_FormField::Type type)53 bool IsComboOrListField(CPDF_FormField::Type type) {
54   switch (type) {
55     case CPDF_FormField::kComboBox:
56     case CPDF_FormField::kListBox:
57       return true;
58     default:
59       return false;
60   }
61 }
62 
63 }  // namespace
64 
65 // static
IntToFormFieldType(int value)66 std::optional<FormFieldType> CPDF_FormField::IntToFormFieldType(int value) {
67   if (value >= static_cast<int>(FormFieldType::kUnknown) &&
68       value < static_cast<int>(kFormFieldTypeCount)) {
69     return static_cast<FormFieldType>(value);
70   }
71   return std::nullopt;
72 }
73 
74 // static
GetFieldAttrForDict(const CPDF_Dictionary * pFieldDict,const ByteString & name)75 RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttrForDict(
76     const CPDF_Dictionary* pFieldDict,
77     const ByteString& name) {
78   return GetFieldAttrRecursive(pFieldDict, name, 0);
79 }
80 
81 // static
GetMutableFieldAttrForDict(CPDF_Dictionary * pFieldDict,const ByteString & name)82 RetainPtr<CPDF_Object> CPDF_FormField::GetMutableFieldAttrForDict(
83     CPDF_Dictionary* pFieldDict,
84     const ByteString& name) {
85   return pdfium::WrapRetain(const_cast<CPDF_Object*>(
86       GetFieldAttrRecursive(pFieldDict, name, 0).Get()));
87 }
88 
89 // static
GetFullNameForDict(const CPDF_Dictionary * pFieldDict)90 WideString CPDF_FormField::GetFullNameForDict(
91     const CPDF_Dictionary* pFieldDict) {
92   WideString full_name;
93   std::set<const CPDF_Dictionary*> visited;
94   const CPDF_Dictionary* pLevel = pFieldDict;
95   while (pLevel) {
96     visited.insert(pLevel);
97     WideString short_name = pLevel->GetUnicodeTextFor(pdfium::form_fields::kT);
98     if (!short_name.IsEmpty()) {
99       if (full_name.IsEmpty())
100         full_name = std::move(short_name);
101       else
102         full_name = short_name + L'.' + full_name;
103     }
104     pLevel = pLevel->GetDictFor(pdfium::form_fields::kParent).Get();
105     if (pdfium::Contains(visited, pLevel))
106       break;
107   }
108   return full_name;
109 }
110 
CPDF_FormField(CPDF_InteractiveForm * pForm,RetainPtr<CPDF_Dictionary> pDict)111 CPDF_FormField::CPDF_FormField(CPDF_InteractiveForm* pForm,
112                                RetainPtr<CPDF_Dictionary> pDict)
113     : m_pForm(pForm), m_pDict(std::move(pDict)) {
114   InitFieldFlags();
115 }
116 
117 CPDF_FormField::~CPDF_FormField() = default;
118 
InitFieldFlags()119 void CPDF_FormField::InitFieldFlags() {
120   RetainPtr<const CPDF_Object> ft_attr =
121       GetFieldAttrInternal(pdfium::form_fields::kFT);
122   ByteString type_name = ft_attr ? ft_attr->GetString() : ByteString();
123   uint32_t flags = GetFieldFlags();
124   m_bRequired = flags & pdfium::form_flags::kRequired;
125   m_bNoExport = flags & pdfium::form_flags::kNoExport;
126 
127   if (type_name == pdfium::form_fields::kBtn) {
128     if (flags & pdfium::form_flags::kButtonRadio) {
129       m_Type = kRadioButton;
130       m_bIsUnison = flags & pdfium::form_flags::kButtonRadiosInUnison;
131     } else if (flags & pdfium::form_flags::kButtonPushbutton) {
132       m_Type = kPushButton;
133     } else {
134       m_Type = kCheckBox;
135       m_bIsUnison = true;
136     }
137   } else if (type_name == pdfium::form_fields::kTx) {
138     if (flags & pdfium::form_flags::kTextFileSelect)
139       m_Type = kFile;
140     else if (flags & pdfium::form_flags::kTextRichText)
141       m_Type = kRichText;
142     else
143       m_Type = kText;
144   } else if (type_name == pdfium::form_fields::kCh) {
145     if (flags & pdfium::form_flags::kChoiceCombo) {
146       m_Type = kComboBox;
147     } else {
148       m_Type = kListBox;
149       m_bIsMultiSelectListBox = flags & pdfium::form_flags::kChoiceMultiSelect;
150     }
151     m_bUseSelectedIndices = UseSelectedIndicesObject();
152   } else if (type_name == pdfium::form_fields::kSig) {
153     m_Type = kSign;
154   }
155 }
156 
GetFullName() const157 WideString CPDF_FormField::GetFullName() const {
158   return GetFullNameForDict(m_pDict.Get());
159 }
160 
GetFieldAttr(const ByteString & name) const161 RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttr(
162     const ByteString& name) const {
163   return GetFieldAttrInternal(name);
164 }
165 
GetFieldDict() const166 RetainPtr<const CPDF_Dictionary> CPDF_FormField::GetFieldDict() const {
167   return pdfium::WrapRetain(GetFieldDictInternal());
168 }
169 
ResetField()170 bool CPDF_FormField::ResetField() {
171   switch (m_Type) {
172     case kCheckBox:
173     case kRadioButton: {
174       int iCount = CountControls();
175       // TODO(weili): Check whether anything special needs to be done for
176       // |m_bIsUnison|.
177       for (int i = 0; i < iCount; i++) {
178         CheckControl(i, GetControl(i)->IsDefaultChecked(),
179                      NotificationOption::kDoNotNotify);
180       }
181       m_pForm->NotifyAfterCheckedStatusChange(this);
182       break;
183     }
184     case kComboBox:
185     case kListBox: {
186       ClearSelection(NotificationOption::kDoNotNotify);
187       WideString csValue;
188       int iIndex = GetDefaultSelectedItem();
189       if (iIndex >= 0)
190         csValue = GetOptionLabel(iIndex);
191       if (!NotifyListOrComboBoxBeforeChange(csValue)) {
192         return false;
193       }
194       SetItemSelection(iIndex, NotificationOption::kDoNotNotify);
195       NotifyListOrComboBoxAfterChange();
196       break;
197     }
198     case kText:
199     case kRichText:
200     case kFile:
201     default: {
202       WideString csDValue;
203       WideString csValue;
204       {
205         // Limit scope of |pDV| and |pV| because they may get invalidated
206         // during notification below.
207         RetainPtr<const CPDF_Object> pDV = GetDefaultValueObject();
208         if (pDV)
209           csDValue = pDV->GetUnicodeText();
210 
211         RetainPtr<const CPDF_Object> pV = GetValueObject();
212         if (pV)
213           csValue = pV->GetUnicodeText();
214       }
215 
216       bool bHasRV = !!GetFieldAttrInternal(pdfium::form_fields::kRV);
217       if (!bHasRV && (csDValue == csValue))
218         return false;
219 
220       if (!m_pForm->NotifyBeforeValueChange(this, csDValue))
221         return false;
222 
223       {
224         // Limit scope of |pDV| because it may get invalidated during
225         // notification below.
226         RetainPtr<const CPDF_Object> pDV = GetDefaultValueObject();
227         if (pDV) {
228           RetainPtr<CPDF_Object> pClone = pDV->Clone();
229           if (!pClone)
230             return false;
231 
232           m_pDict->SetFor(pdfium::form_fields::kV, std::move(pClone));
233           if (bHasRV) {
234             m_pDict->SetFor(pdfium::form_fields::kRV, pDV->Clone());
235           }
236         } else {
237           m_pDict->RemoveFor(pdfium::form_fields::kV);
238           m_pDict->RemoveFor(pdfium::form_fields::kRV);
239         }
240       }
241       m_pForm->NotifyAfterValueChange(this);
242       break;
243     }
244   }
245   return true;
246 }
247 
CountControls() const248 int CPDF_FormField::CountControls() const {
249   return fxcrt::CollectionSize<int>(GetControls());
250 }
251 
GetControl(int index) const252 CPDF_FormControl* CPDF_FormField::GetControl(int index) const {
253   return GetControls()[index];
254 }
255 
GetControlIndex(const CPDF_FormControl * pControl) const256 int CPDF_FormField::GetControlIndex(const CPDF_FormControl* pControl) const {
257   if (!pControl)
258     return -1;
259 
260   const auto& controls = GetControls();
261   auto it = std::find(controls.begin(), controls.end(), pControl);
262   if (it == controls.end())
263     return -1;
264 
265   return pdfium::checked_cast<int>(it - controls.begin());
266 }
267 
GetFieldType() const268 FormFieldType CPDF_FormField::GetFieldType() const {
269   switch (m_Type) {
270     case kPushButton:
271       return FormFieldType::kPushButton;
272     case kCheckBox:
273       return FormFieldType::kCheckBox;
274     case kRadioButton:
275       return FormFieldType::kRadioButton;
276     case kComboBox:
277       return FormFieldType::kComboBox;
278     case kListBox:
279       return FormFieldType::kListBox;
280     case kText:
281     case kRichText:
282     case kFile:
283       return FormFieldType::kTextField;
284     case kSign:
285       return FormFieldType::kSignature;
286     default:
287       return FormFieldType::kUnknown;
288   }
289 }
290 
GetAdditionalAction() const291 CPDF_AAction CPDF_FormField::GetAdditionalAction() const {
292   RetainPtr<const CPDF_Object> pObj =
293       GetFieldAttrInternal(pdfium::form_fields::kAA);
294   return CPDF_AAction(pObj ? pObj->GetDict() : nullptr);
295 }
296 
GetAlternateName() const297 WideString CPDF_FormField::GetAlternateName() const {
298   RetainPtr<const CPDF_Object> pObj =
299       GetFieldAttrInternal(pdfium::form_fields::kTU);
300   return pObj ? pObj->GetUnicodeText() : WideString();
301 }
302 
GetMappingName() const303 WideString CPDF_FormField::GetMappingName() const {
304   RetainPtr<const CPDF_Object> pObj =
305       GetFieldAttrInternal(pdfium::form_fields::kTM);
306   return pObj ? pObj->GetUnicodeText() : WideString();
307 }
308 
GetFieldFlags() const309 uint32_t CPDF_FormField::GetFieldFlags() const {
310   RetainPtr<const CPDF_Object> pObj =
311       GetFieldAttrInternal(pdfium::form_fields::kFf);
312   return pObj ? pObj->GetInteger() : 0;
313 }
314 
SetFieldFlags(uint32_t dwFlags)315 void CPDF_FormField::SetFieldFlags(uint32_t dwFlags) {
316   m_pDict->SetNewFor<CPDF_Number>(pdfium::form_fields::kFf,
317                                   static_cast<int>(dwFlags));
318 }
319 
GetValue(bool bDefault) const320 WideString CPDF_FormField::GetValue(bool bDefault) const {
321   if (GetType() == kCheckBox || GetType() == kRadioButton)
322     return GetCheckValue(bDefault);
323 
324   RetainPtr<const CPDF_Object> pValue =
325       bDefault ? GetDefaultValueObject() : GetValueObject();
326   if (!pValue) {
327     if (!bDefault && m_Type != kText)
328       pValue = GetDefaultValueObject();
329     if (!pValue)
330       return WideString();
331   }
332 
333   switch (pValue->GetType()) {
334     case CPDF_Object::kString:
335     case CPDF_Object::kStream:
336       return pValue->GetUnicodeText();
337     case CPDF_Object::kArray: {
338       RetainPtr<const CPDF_Object> pNewValue =
339           pValue->AsArray()->GetDirectObjectAt(0);
340       if (pNewValue)
341         return pNewValue->GetUnicodeText();
342       break;
343     }
344     default:
345       break;
346   }
347   return WideString();
348 }
349 
GetValue() const350 WideString CPDF_FormField::GetValue() const {
351   return GetValue(false);
352 }
353 
GetDefaultValue() const354 WideString CPDF_FormField::GetDefaultValue() const {
355   return GetValue(true);
356 }
357 
SetValue(const WideString & value,bool bDefault,NotificationOption notify)358 bool CPDF_FormField::SetValue(const WideString& value,
359                               bool bDefault,
360                               NotificationOption notify) {
361   switch (GetType()) {
362     case kCheckBox:
363     case kRadioButton: {
364       SetCheckValue(value, bDefault, notify);
365       return true;
366     }
367     case kFile:
368     case kRichText:
369     case kText:
370     case kComboBox: {
371       WideString csValue = value;
372       if (notify == NotificationOption::kNotify &&
373           !m_pForm->NotifyBeforeValueChange(this, csValue)) {
374         return false;
375       }
376       ByteString key(bDefault ? pdfium::form_fields::kDV
377                               : pdfium::form_fields::kV);
378       m_pDict->SetNewFor<CPDF_String>(key, csValue.AsStringView());
379 
380       int iIndex;
381       if (GetType() == kComboBox) {
382         iIndex = FindOption(csValue);
383       } else {
384         iIndex = -1;
385       }
386       if (iIndex < 0) {
387         if (m_Type == kRichText && !bDefault) {
388           m_pDict->SetFor(pdfium::form_fields::kRV,
389                           m_pDict->GetObjectFor(key)->Clone());
390         }
391         m_pDict->RemoveFor("I");
392       } else {
393         if (!bDefault) {
394           ClearSelection(NotificationOption::kDoNotNotify);
395           SetItemSelection(iIndex, NotificationOption::kDoNotNotify);
396         }
397       }
398       if (notify == NotificationOption::kNotify)
399         m_pForm->NotifyAfterValueChange(this);
400       break;
401     }
402     case kListBox: {
403       int iIndex = FindOption(value);
404       if (iIndex < 0)
405         return false;
406 
407       if (bDefault && iIndex == GetDefaultSelectedItem())
408         return false;
409 
410       if (notify == NotificationOption::kNotify &&
411           !m_pForm->NotifyBeforeSelectionChange(this, value)) {
412         return false;
413       }
414       if (!bDefault) {
415         ClearSelection(NotificationOption::kDoNotNotify);
416         SetItemSelection(iIndex, NotificationOption::kDoNotNotify);
417       }
418       if (notify == NotificationOption::kNotify)
419         m_pForm->NotifyAfterSelectionChange(this);
420       break;
421     }
422     default:
423       break;
424   }
425   return true;
426 }
427 
SetValue(const WideString & value,NotificationOption notify)428 bool CPDF_FormField::SetValue(const WideString& value,
429                               NotificationOption notify) {
430   return SetValue(value, false, notify);
431 }
432 
GetMaxLen() const433 int CPDF_FormField::GetMaxLen() const {
434   RetainPtr<const CPDF_Object> pObj = GetFieldAttrInternal("MaxLen");
435   if (pObj)
436     return pObj->GetInteger();
437 
438   for (auto& pControl : GetControls()) {
439     if (!pControl)
440       continue;
441 
442     RetainPtr<const CPDF_Dictionary> pWidgetDict = pControl->GetWidgetDict();
443     if (pWidgetDict->KeyExist("MaxLen"))
444       return pWidgetDict->GetIntegerFor("MaxLen");
445   }
446   return 0;
447 }
448 
CountSelectedItems() const449 int CPDF_FormField::CountSelectedItems() const {
450   const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
451   if (!pValue)
452     return 0;
453 
454   if (pValue->IsString() || pValue->IsNumber())
455     return pValue->GetString().IsEmpty() ? 0 : 1;
456   const CPDF_Array* pArray = pValue->AsArray();
457   return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
458 }
459 
GetSelectedIndex(int index) const460 int CPDF_FormField::GetSelectedIndex(int index) const {
461   const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
462   if (!pValue)
463     return -1;
464 
465   if (pValue->IsNumber())
466     return pValue->GetInteger();
467 
468   WideString sel_value;
469   if (pValue->IsString()) {
470     if (index != 0)
471       return -1;
472     sel_value = pValue->GetUnicodeText();
473   } else {
474     const CPDF_Array* pArray = pValue->AsArray();
475     if (!pArray || index < 0)
476       return -1;
477 
478     RetainPtr<const CPDF_Object> elementValue =
479         pArray->GetDirectObjectAt(index);
480     sel_value = elementValue ? elementValue->GetUnicodeText() : WideString();
481   }
482   if (index < CountSelectedOptions()) {
483     int iOptIndex = GetSelectedOptionIndex(index);
484     WideString csOpt = GetOptionValue(iOptIndex);
485     if (csOpt == sel_value)
486       return iOptIndex;
487   }
488   for (int i = 0; i < CountOptions(); i++) {
489     if (sel_value == GetOptionValue(i))
490       return i;
491   }
492   return -1;
493 }
494 
ClearSelection(NotificationOption notify)495 bool CPDF_FormField::ClearSelection(NotificationOption notify) {
496   if (notify == NotificationOption::kNotify) {
497     WideString csValue;
498     int iIndex = GetSelectedIndex(0);
499     if (iIndex >= 0)
500       csValue = GetOptionLabel(iIndex);
501     if (!NotifyListOrComboBoxBeforeChange(csValue))
502       return false;
503   }
504   m_pDict->RemoveFor(pdfium::form_fields::kV);
505   m_pDict->RemoveFor("I");
506   if (notify == NotificationOption::kNotify)
507     NotifyListOrComboBoxAfterChange();
508   return true;
509 }
510 
IsItemSelected(int index) const511 bool CPDF_FormField::IsItemSelected(int index) const {
512   CHECK(IsComboOrListField(GetType()));
513   if (index < 0 || index >= CountOptions()) {
514     return false;
515   }
516   // First consider the /I entry if it is valid, then fall back to the /V entry.
517   return m_bUseSelectedIndices ? IsSelectedIndex(index)
518                                : IsSelectedOption(GetOptionValue(index));
519 }
520 
SetItemSelection(int index,NotificationOption notify)521 bool CPDF_FormField::SetItemSelection(int index, NotificationOption notify) {
522   CHECK(IsComboOrListField(GetType()));
523   if (index < 0 || index >= CountOptions()) {
524     return false;
525   }
526   WideString opt_value = GetOptionValue(index);
527   if (notify == NotificationOption::kNotify &&
528       !NotifyListOrComboBoxBeforeChange(opt_value)) {
529     return false;
530   }
531 
532   SetItemSelectionSelected(index, opt_value);
533 
534   // UseSelectedIndicesObject() has a non-trivial linearithmic run-time, so run
535   // only if necessary.
536   if (!m_bUseSelectedIndices)
537     m_bUseSelectedIndices = UseSelectedIndicesObject();
538 
539   if (notify == NotificationOption::kNotify)
540     NotifyListOrComboBoxAfterChange();
541   return true;
542 }
543 
SetItemSelectionSelected(int index,const WideString & opt_value)544 void CPDF_FormField::SetItemSelectionSelected(int index,
545                                               const WideString& opt_value) {
546   if (GetType() != kListBox) {
547     m_pDict->SetNewFor<CPDF_String>(pdfium::form_fields::kV,
548                                     opt_value.AsStringView());
549     auto pI = m_pDict->SetNewFor<CPDF_Array>("I");
550     pI->AppendNew<CPDF_Number>(index);
551     return;
552   }
553 
554   SelectOption(index);
555   if (!m_bIsMultiSelectListBox) {
556     m_pDict->SetNewFor<CPDF_String>(pdfium::form_fields::kV,
557                                     opt_value.AsStringView());
558     return;
559   }
560 
561   auto pArray = m_pDict->SetNewFor<CPDF_Array>(pdfium::form_fields::kV);
562   for (int i = 0; i < CountOptions(); i++) {
563     if (i == index || IsItemSelected(i))
564       pArray->AppendNew<CPDF_String>(GetOptionValue(i).AsStringView());
565   }
566 }
567 
GetDefaultSelectedItem() const568 int CPDF_FormField::GetDefaultSelectedItem() const {
569   CHECK(IsComboOrListField(GetType()));
570   RetainPtr<const CPDF_Object> pValue = GetDefaultValueObject();
571   if (!pValue)
572     return -1;
573   WideString csDV = pValue->GetUnicodeText();
574   if (csDV.IsEmpty())
575     return -1;
576   for (int i = 0; i < CountOptions(); i++) {
577     if (csDV == GetOptionValue(i))
578       return i;
579   }
580   return -1;
581 }
582 
HasOptField() const583 bool CPDF_FormField::HasOptField() const {
584   switch (GetType()) {
585     case CPDF_FormField::kCheckBox:
586     case CPDF_FormField::kRadioButton:
587     case CPDF_FormField::kComboBox:
588     case CPDF_FormField::kListBox:
589       return true;
590     default:
591       return false;
592   }
593 }
594 
CountOptions() const595 int CPDF_FormField::CountOptions() const {
596   CHECK(HasOptField());
597   RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
598   return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
599 }
600 
GetOptionText(int index,int sub_index) const601 WideString CPDF_FormField::GetOptionText(int index, int sub_index) const {
602   CHECK(HasOptField());
603   RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
604   if (!pArray)
605     return WideString();
606 
607   RetainPtr<const CPDF_Object> pOption = pArray->GetDirectObjectAt(index);
608   if (!pOption)
609     return WideString();
610 
611   const CPDF_Array* pOptionArray = pOption->AsArray();
612   if (pOptionArray)
613     pOption = pOptionArray->GetDirectObjectAt(sub_index);
614 
615   if (!pOption)
616     return WideString();
617 
618   const CPDF_String* pString = pOption->AsString();
619   return pString ? pString->GetUnicodeText() : WideString();
620 }
621 
GetOptionLabel(int index) const622 WideString CPDF_FormField::GetOptionLabel(int index) const {
623   return GetOptionText(index, 1);
624 }
625 
GetOptionValue(int index) const626 WideString CPDF_FormField::GetOptionValue(int index) const {
627   return GetOptionText(index, 0);
628 }
629 
FindOption(const WideString & csOptValue) const630 int CPDF_FormField::FindOption(const WideString& csOptValue) const {
631   for (int i = 0; i < CountOptions(); i++) {
632     if (GetOptionValue(i) == csOptValue)
633       return i;
634   }
635   return -1;
636 }
637 
CheckControl(int iControlIndex,bool bChecked,NotificationOption notify)638 bool CPDF_FormField::CheckControl(int iControlIndex,
639                                   bool bChecked,
640                                   NotificationOption notify) {
641   DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
642   CPDF_FormControl* pControl = GetControl(iControlIndex);
643   if (!pControl)
644     return false;
645   if (!bChecked && pControl->IsChecked() == bChecked)
646     return false;
647 
648   const WideString csWExport = pControl->GetExportValue();
649   int iCount = CountControls();
650   for (int i = 0; i < iCount; i++) {
651     CPDF_FormControl* pCtrl = GetControl(i);
652     if (m_bIsUnison) {
653       WideString csEValue = pCtrl->GetExportValue();
654       if (csEValue == csWExport) {
655         if (pCtrl->GetOnStateName() == pControl->GetOnStateName())
656           pCtrl->CheckControl(bChecked);
657         else if (bChecked)
658           pCtrl->CheckControl(false);
659       } else if (bChecked) {
660         pCtrl->CheckControl(false);
661       }
662     } else {
663       if (i == iControlIndex)
664         pCtrl->CheckControl(bChecked);
665       else if (bChecked)
666         pCtrl->CheckControl(false);
667     }
668   }
669 
670   RetainPtr<const CPDF_Object> pOpt = GetFieldAttrInternal("Opt");
671   if (!ToArray(pOpt)) {
672     ByteString csBExport = PDF_EncodeText(csWExport.AsStringView());
673     if (bChecked) {
674       m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, csBExport);
675     } else {
676       ByteString csV;
677       const CPDF_Object* pV = GetValueObject();
678       if (pV)
679         csV = pV->GetString();
680       if (csV == csBExport)
681         m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, "Off");
682     }
683   } else if (bChecked) {
684     m_pDict->SetNewFor<CPDF_Name>(pdfium::form_fields::kV,
685                                   ByteString::FormatInteger(iControlIndex));
686   }
687   if (notify == NotificationOption::kNotify)
688     m_pForm->NotifyAfterCheckedStatusChange(this);
689   return true;
690 }
691 
GetCheckValue(bool bDefault) const692 WideString CPDF_FormField::GetCheckValue(bool bDefault) const {
693   DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
694   auto csExport = WideString::FromASCII("Off");
695   int iCount = CountControls();
696   for (int i = 0; i < iCount; i++) {
697     CPDF_FormControl* pControl = GetControl(i);
698     bool bChecked =
699         bDefault ? pControl->IsDefaultChecked() : pControl->IsChecked();
700     if (bChecked) {
701       csExport = pControl->GetExportValue();
702       break;
703     }
704   }
705   return csExport;
706 }
707 
SetCheckValue(const WideString & value,bool bDefault,NotificationOption notify)708 bool CPDF_FormField::SetCheckValue(const WideString& value,
709                                    bool bDefault,
710                                    NotificationOption notify) {
711   DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
712   int iCount = CountControls();
713   for (int i = 0; i < iCount; i++) {
714     CPDF_FormControl* pControl = GetControl(i);
715     WideString csExport = pControl->GetExportValue();
716     bool val = csExport == value;
717     if (!bDefault) {
718       CheckControl(GetControlIndex(pControl), val,
719                    NotificationOption::kDoNotNotify);
720     }
721     if (val)
722       break;
723   }
724   if (notify == NotificationOption::kNotify)
725     m_pForm->NotifyAfterCheckedStatusChange(this);
726   return true;
727 }
728 
GetTopVisibleIndex() const729 int CPDF_FormField::GetTopVisibleIndex() const {
730   RetainPtr<const CPDF_Object> pObj = GetFieldAttrInternal("TI");
731   return pObj ? pObj->GetInteger() : 0;
732 }
733 
CountSelectedOptions() const734 int CPDF_FormField::CountSelectedOptions() const {
735   RetainPtr<const CPDF_Array> pArray = ToArray(GetSelectedIndicesObject());
736   return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
737 }
738 
GetSelectedOptionIndex(int index) const739 int CPDF_FormField::GetSelectedOptionIndex(int index) const {
740   if (index < 0)
741     return 0;
742 
743   RetainPtr<const CPDF_Array> pArray = ToArray(GetSelectedIndicesObject());
744   if (!pArray)
745     return -1;
746 
747   return index < fxcrt::CollectionSize<int>(*pArray)
748              ? pArray->GetIntegerAt(index)
749              : -1;
750 }
751 
IsSelectedOption(const WideString & wsOptValue) const752 bool CPDF_FormField::IsSelectedOption(const WideString& wsOptValue) const {
753   RetainPtr<const CPDF_Object> pValueObject = GetValueObject();
754   if (!pValueObject)
755     return false;
756 
757   const CPDF_Array* pValueArray = pValueObject->AsArray();
758   if (pValueArray) {
759     CPDF_ArrayLocker locker(pValueArray);
760     for (const auto& pObj : locker) {
761       if (pObj->IsString() && pObj->GetUnicodeText() == wsOptValue)
762         return true;
763     }
764   }
765 
766   return pValueObject->IsString() &&
767          pValueObject->GetUnicodeText() == wsOptValue;
768 }
769 
IsSelectedIndex(int iOptIndex) const770 bool CPDF_FormField::IsSelectedIndex(int iOptIndex) const {
771   RetainPtr<const CPDF_Object> pSelectedIndicesObject =
772       GetSelectedIndicesObject();
773   if (!pSelectedIndicesObject)
774     return false;
775 
776   const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
777   if (pSelectedIndicesArray) {
778     CPDF_ArrayLocker locker(pSelectedIndicesArray);
779     for (const auto& pObj : locker) {
780       if (pObj->IsNumber() && pObj->GetInteger() == iOptIndex)
781         return true;
782     }
783   }
784 
785   return pSelectedIndicesObject->IsNumber() &&
786          pSelectedIndicesObject->GetInteger() == iOptIndex;
787 }
788 
SelectOption(int iOptIndex)789 void CPDF_FormField::SelectOption(int iOptIndex) {
790   RetainPtr<CPDF_Array> pArray = m_pDict->GetOrCreateArrayFor("I");
791   for (size_t i = 0; i < pArray->size(); i++) {
792     int iFind = pArray->GetIntegerAt(i);
793     if (iFind == iOptIndex)
794       return;
795 
796     if (iFind > iOptIndex) {
797       pArray->InsertNewAt<CPDF_Number>(i, iOptIndex);
798       return;
799     }
800   }
801   pArray->AppendNew<CPDF_Number>(iOptIndex);
802 }
803 
UseSelectedIndicesObject() const804 bool CPDF_FormField::UseSelectedIndicesObject() const {
805   CHECK(IsComboOrListField(GetType()));
806 
807   RetainPtr<const CPDF_Object> pSelectedIndicesObject =
808       GetSelectedIndicesObject();
809   if (!pSelectedIndicesObject)
810     return false;
811 
812   // If there's not value object, then just use the indices object.
813   RetainPtr<const CPDF_Object> pValueObject = GetValueObject();
814   if (!pValueObject)
815     return true;
816 
817   // Verify that the selected indices object is either an array or a number and
818   // count the number of indices.
819   size_t selected_indices_size;
820   const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
821   if (pSelectedIndicesArray)
822     selected_indices_size = pSelectedIndicesArray->size();
823   else if (pSelectedIndicesObject->IsNumber())
824     selected_indices_size = 1;
825   else
826     return false;
827 
828   // Verify that the number of values is equal to |selected_indices_size|. Then,
829   // count the number of occurrences of each of the distinct values in the
830   // values object.
831   std::map<WideString, size_t> values;
832   const CPDF_Array* pValueArray = pValueObject->AsArray();
833   if (pValueArray) {
834     if (pValueArray->size() != selected_indices_size)
835       return false;
836     CPDF_ArrayLocker locker(pValueArray);
837     for (const auto& pObj : locker) {
838       if (pObj->IsString())
839         values[pObj->GetUnicodeText()]++;
840     }
841   } else if (pValueObject->IsString()) {
842     if (selected_indices_size != 1)
843       return false;
844     values[pValueObject->GetUnicodeText()]++;
845   }
846 
847   // Validate each index in the selected indices object. Then, verify that items
848   // identified by selected indices entry do not differ from those in the values
849   // entry of the field dictionary.
850   const int num_options = CountOptions();
851   if (pSelectedIndicesArray) {
852     CPDF_ArrayLocker locker(pSelectedIndicesArray);
853     for (const auto& pObj : locker) {
854       if (!pObj->IsNumber())
855         return false;
856 
857       int index = pObj->GetInteger();
858       if (index < 0 || index >= num_options)
859         return false;
860 
861       WideString wsOptValue = GetOptionValue(index);
862       auto it = values.find(wsOptValue);
863       if (it == values.end())
864         return false;
865 
866       it->second--;
867       if (it->second == 0)
868         values.erase(it);
869     }
870 
871     return values.empty();
872   }
873 
874   DCHECK(pSelectedIndicesObject->IsNumber());
875   int index = pSelectedIndicesObject->GetInteger();
876   if (index < 0 || index >= num_options)
877     return false;
878 
879   return pdfium::Contains(values, GetOptionValue(index));
880 }
881 
NotifyListOrComboBoxBeforeChange(const WideString & value)882 bool CPDF_FormField::NotifyListOrComboBoxBeforeChange(const WideString& value) {
883   switch (GetType()) {
884     case kListBox:
885       return m_pForm->NotifyBeforeSelectionChange(this, value);
886     case kComboBox:
887       return m_pForm->NotifyBeforeValueChange(this, value);
888     default:
889       return true;
890   }
891 }
892 
NotifyListOrComboBoxAfterChange()893 void CPDF_FormField::NotifyListOrComboBoxAfterChange() {
894   switch (GetType()) {
895     case kListBox:
896       m_pForm->NotifyAfterSelectionChange(this);
897       break;
898     case kComboBox:
899       m_pForm->NotifyAfterValueChange(this);
900       break;
901     default:
902       break;
903   }
904 }
905 
GetFieldAttrInternal(const ByteString & name) const906 RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttrInternal(
907     const ByteString& name) const {
908   return GetFieldAttrRecursive(m_pDict.Get(), name, 0);
909 }
910 
GetFieldDictInternal() const911 const CPDF_Dictionary* CPDF_FormField::GetFieldDictInternal() const {
912   return m_pDict.Get();
913 }
914 
GetDefaultValueObject() const915 RetainPtr<const CPDF_Object> CPDF_FormField::GetDefaultValueObject() const {
916   return GetFieldAttrInternal(pdfium::form_fields::kDV);
917 }
918 
GetValueObject() const919 RetainPtr<const CPDF_Object> CPDF_FormField::GetValueObject() const {
920   return GetFieldAttrInternal(pdfium::form_fields::kV);
921 }
922 
GetSelectedIndicesObject() const923 RetainPtr<const CPDF_Object> CPDF_FormField::GetSelectedIndicesObject() const {
924   CHECK(IsComboOrListField(GetType()));
925   return GetFieldAttrInternal("I");
926 }
927 
GetValueOrSelectedIndicesObject() const928 RetainPtr<const CPDF_Object> CPDF_FormField::GetValueOrSelectedIndicesObject()
929     const {
930   CHECK(IsComboOrListField(GetType()));
931   RetainPtr<const CPDF_Object> pValue = GetValueObject();
932   return pValue ? pValue : GetSelectedIndicesObject();
933 }
934 
GetControls() const935 const std::vector<UnownedPtr<CPDF_FormControl>>& CPDF_FormField::GetControls()
936     const {
937   return m_pForm->GetControlsForField(this);
938 }
939