[poppler] poppler/Form.cc poppler/Form.h poppler/Page.h

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Sun Mar 28 15:18:36 UTC 2021


 poppler/Form.cc |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 poppler/Form.h  |    2 +-
 poppler/Page.h  |    2 ++
 3 files changed, 53 insertions(+), 3 deletions(-)

New commits:
commit 40cc13db621f2f49f55c4e4390bf2e862cc05d41
Author: Nelson Benítez León <nbenitezl at gmail.com>
Date:   Tue Mar 9 16:47:27 2021 -0400

    Forms: fix unclicking standalone form buttons
    
    We can find pdf forms that eg. have three related
    radio buttons where their *only* relationship is
    having the same full qualified name.
    
    Such a form is supported by Adobe Reader and
    produced by LaTeX through TeXStudio. So, when
    clicking one such radio button, Poppler failed
    to unclick the other radio buttons as they are
    not children nor parent of the clicked one (which
    is the ordinary way to set radio button groups
    as per the PDF standard).
    
    So, when clicking a radio button, let's add support
    to find other radio buttons in the same page that
    are only related by having same fully qualified name
    (these may be in standalone or normal fields).
    
    Where trying to find where PDF standard covers this
    special case of related radio buttons, the closest
    thing we can find is section "Field names" inside
    chapter "8.6.2 Field Dictionaries" in 1.7 PDF spec.
    
    Issue #1034

diff --git a/poppler/Form.cc b/poppler/Form.cc
index 4d42aedf..70bfb3c6 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -269,6 +269,54 @@ void FormWidgetButton::setState(bool astate)
 
     parent()->setState(astate ? getOnStr() : (char *)"Off");
     // Parent will call setAppearanceState()
+
+    // Now handle standAlone fields which are related to this one by having the same
+    // fully qualified name. This is *partially* by spec, as seen in "Field names"
+    // section inside "8.6.2 Field Dictionaries" in 1.7 PDF spec. Issue #1034
+
+    if (!astate) // We're only interested when this field is being set to ON,
+        return; // to check if it has related fields and then set them OFF
+
+    unsigned this_page_num, this_field_num;
+    decodeID(getID(), &this_page_num, &this_field_num);
+    Page *this_page = doc->getCatalog()->getPage(this_page_num);
+    const FormField *this_field = getField();
+    if (!this_page->hasStandaloneFields() || this_field == nullptr)
+        return;
+
+    auto this_page_widgets = this_page->getFormWidgets();
+    const FormButtonType this_button_type = getButtonType();
+
+    const int tot = this_page_widgets->getNumWidgets();
+    for (int i = 0; i < tot; i++) {
+        bool found_related = false;
+        FormWidget *wid = this_page_widgets->getWidget(i);
+        const bool same_fqn = wid->getFullyQualifiedName()->cmp(getFullyQualifiedName()) == 0;
+        const bool same_button_type = wid->getType() == formButton && static_cast<const FormWidgetButton *>(wid)->getButtonType() == this_button_type;
+
+        if (same_fqn && same_button_type) {
+            if (this_field->isStandAlone()) {
+                //'this_field' is standAlone, so we need to search in both standAlone fields and normal fields
+                if (this_field != wid->getField()) { // so take care to not choose our same field
+                    found_related = true;
+                }
+            } else {
+                //'this_field' is not standAlone, so we just need to search in standAlone fields
+                if (wid->getField()->isStandAlone()) {
+                    found_related = true;
+                }
+            }
+        }
+
+        if (found_related) {
+            FormFieldButton *ffb = static_cast<FormFieldButton *>(wid->getField());
+            if (ffb == nullptr) {
+                error(errInternal, -1, "FormWidgetButton::setState : FormFieldButton expected\n");
+                continue;
+            }
+            ffb->setState((char *)"Off", true);
+        }
+    }
 }
 
 bool FormWidgetButton::getState() const
@@ -1285,7 +1333,7 @@ void FormFieldButton::fillChildrenSiblingsID()
     }
 }
 
-bool FormFieldButton::setState(const char *state)
+bool FormFieldButton::setState(const char *state, bool ignoreToggleOff)
 {
     // A check button could behave as a radio button
     // when it's in a set of more than 1 buttons
@@ -1302,7 +1350,7 @@ bool FormFieldButton::setState(const char *state)
 
     bool isOn = strcmp(state, "Off") != 0;
 
-    if (!isOn && noAllOff)
+    if (!isOn && noAllOff && !ignoreToggleOff)
         return false; // Don't allow to set all radio to off
 
     const char *current = getAppearanceState();
diff --git a/poppler/Form.h b/poppler/Form.h
index f978958d..39786ed9 100644
--- a/poppler/Form.h
+++ b/poppler/Form.h
@@ -425,7 +425,7 @@ public:
     bool noToggleToOff() const { return noAllOff; }
 
     // returns true if the state modification is accepted
-    bool setState(const char *state);
+    bool setState(const char *state, bool ignoreToggleOff = false);
     bool getState(const char *state) const;
 
     const char *getAppearanceState() const { return appearanceState.isName() ? appearanceState.getName() : nullptr; }
diff --git a/poppler/Page.h b/poppler/Page.h
index 4f8a3d3d..228e31e2 100644
--- a/poppler/Page.h
+++ b/poppler/Page.h
@@ -240,6 +240,8 @@ public:
     // Get the page's default CTM.
     void getDefaultCTM(double *ctm, double hDPI, double vDPI, int rotate, bool useMediaBox, bool upsideDown);
 
+    bool hasStandaloneFields() const { return !standaloneFields.empty(); }
+
 private:
     // replace xref
     void replaceXRef(XRef *xrefA);


More information about the poppler mailing list