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