poppler/Form.cc         |  225 ++++++++++++++++++++++++++++++++++++++++++++++--
 poppler/Form.h          |   24 +++++
 poppler/Link.cc         |   41 ++++++++
 poppler/Link.h          |   28 +++++
 qt5/src/poppler-page.cc |    4 
 5 files changed, 314 insertions(+), 8 deletions(-)

New commits:
commit 0582ea9633d4597b98b3a9c9c7a8c673634aef29
Author: Marek Kasik <[email protected]>
Date:   Mon May 18 18:02:43 2020 +0000

    Add support for ResetForm action
    
    Add ability to reset FormField class, its descendants and Form class. This 
takes hierarchy into account so that resetting a field resets also its 
children. If exclude flag is specified then all fields are reset except those 
which are listed in Fields key.
    
    FormFieldText set DV key as its V key if DV is available. Otherwise, it 
just removes the V key.
    
    FormFieldChoice unselect selected items and set the default ones if 
specified.
    
    FormFieldButton set default apearance state if it is available, otherwise 
it just removes V key (and set the state to "Off" if it is check button, which 
is what Adobe Reader does in this situation).
    
    FormFieldSignature is not reset.
    
    Add LinkResetForm class which stores information needed for resetting of 
fields of forms.
    
    Issue #225

diff --git a/poppler/Form.cc b/poppler/Form.cc
index 5018bc9d..9fb9276a 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -27,6 +27,7 @@
 // Copyright 2019, 2020 Oliver Sander <[email protected]>
 // Copyright 2019 Tomoyuki Kubota <[email protected]>
 // Copyright 2019 João Netto <[email protected]>
+// Copyright 2020 Marek Kasik <[email protected]>
 //
 //========================================================================
 
@@ -851,6 +852,67 @@ void FormField::setReadOnly (bool value)
   updateChildrenAppearance();
 }
 
+void FormField::reset(const std::vector<std::string>& excludedFields)
+{
+  resetChildren(excludedFields);
+}
+
+void FormField::resetChildren(const std::vector<std::string>& excludedFields)
+{
+  if (!terminal) {
+    for (int i = 0; i < numChildren; i++)
+      children[i]->reset(excludedFields);
+  }
+}
+
+bool FormField::isAmongExcludedFields(const std::vector<std::string>& 
excludedFields)
+{
+  Ref fieldRef;
+
+  for (const std::string &field : excludedFields) {
+    if (field.compare(field.size() - 2, 2, " R") == 0) {
+      if (sscanf(field.c_str(), "%d %d R", &fieldRef.num, &fieldRef.gen) == 2 
&&
+          fieldRef == getRef())
+        return true;
+    } else {
+      if (field == getFullyQualifiedName()->toStr())
+        return true;
+    }
+  }
+
+  return false;
+}
+
+FormField* FormField::findFieldByRef (Ref aref)
+{
+  if (terminal) {
+    if (this->getRef() == aref)
+      return this;
+  } else {
+    for (int i = 0; i < numChildren; i++) {
+      FormField* result = children[i]->findFieldByRef(aref);
+      if (result)
+        return result;
+    }
+  }
+  return nullptr;
+}
+
+FormField* FormField::findFieldByFullyQualifiedName (const std::string &name)
+{
+  if (terminal) {
+    if (getFullyQualifiedName()->cmp(name.c_str()) == 0)
+      return this;
+  } else {
+    for (int i = 0; i < numChildren; i++) {
+      FormField* result = children[i]->findFieldByFullyQualifiedName(name);
+      if (result)
+        return result;
+    }
+  }
+  return nullptr;
+}
+
 //------------------------------------------------------------------------
 // FormFieldButton
 //------------------------------------------------------------------------
@@ -863,6 +925,7 @@ FormFieldButton::FormFieldButton(PDFDoc *docA, Object 
&&dictObj, const Ref refA,
   siblings = nullptr;
   numSiblings = 0;
   appearanceState.setToNull();
+  defaultAppearanceState.setToNull();
 
   btype = formButtonCheck;
   Object obj1 = Form::fieldLookup(dict, "Ff");
@@ -889,6 +952,7 @@ FormFieldButton::FormFieldButton(PDFDoc *docA, Object 
&&dictObj, const Ref refA,
     // Even though V is inheritable we are interested in the value of this
     // field, if not present it's probably because it's a button in a set.
     appearanceState = dict->lookup("V");
+    defaultAppearanceState = Form::fieldLookup(dict, "DV");
   }
 }
 
@@ -1021,6 +1085,24 @@ FormFieldButton::~FormFieldButton()
     gfree(siblings);
 }
 
+void FormFieldButton::reset(const std::vector<std::string>& excludedFields)
+{
+  if (!isAmongExcludedFields(excludedFields)) {
+    if (getDefaultAppearanceState()) {
+      setState(getDefaultAppearanceState());
+    } else {
+      obj.getDict()->remove("V");
+
+      // Clear check button if it doesn't have default value.
+      // This behaviour is what Adobe Reader does, it is not written in 
specification.
+      if (btype == formButtonCheck)
+        setState("Off");
+    }
+  }
+
+  resetChildren(excludedFields);
+}
+
 //------------------------------------------------------------------------
 // FormFieldText
 //------------------------------------------------------------------------
@@ -1031,6 +1113,7 @@ FormFieldText::FormFieldText(PDFDoc *docA, Object 
&&dictObj, const Ref refA, For
   Object obj1;
   content = nullptr;
   internalContent = nullptr;
+  defaultContent = nullptr;
   multiline = password = fileSelect = doNotSpellCheck = doNotScroll = comb = 
richText = false;
   maxLen = 0;
 
@@ -1058,16 +1141,35 @@ FormFieldText::FormFieldText(PDFDoc *docA, Object 
&&dictObj, const Ref refA, For
     maxLen = obj1.getInt();
   }
 
-  obj1 = Form::fieldLookup(dict, "V");
+  fillContent(fillDefaultValue);
+  fillContent(fillValue);
+}
+
+void FormFieldText::fillContent(FillValueType fillType)
+{
+  Dict* dict = obj.getDict();
+  Object obj1;
+
+  obj1 = Form::fieldLookup(dict, fillType == fillDefaultValue ? "DV" : "V");
   if (obj1.isString()) {
     if (obj1.getString()->hasUnicodeMarker()) {
       if (obj1.getString()->getLength() > 2)
-        content = obj1.getString()->copy();
+        {
+          if (fillType == fillDefaultValue)
+            defaultContent = obj1.getString()->copy();
+          else
+            content = obj1.getString()->copy();
+        }
     } else if (obj1.getString()->getLength() > 0) {
       //non-unicode string -- assume pdfDocEncoding and try to convert to 
UTF16BE
       int tmp_length;
       char* tmp_str = pdfDocEncodingToUTF16(obj1.getString()->toStr(), 
&tmp_length);
-      content = new GooString(tmp_str, tmp_length);
+
+      if (fillType == fillDefaultValue)
+        defaultContent = new GooString(tmp_str, tmp_length);
+      else
+        content = new GooString(tmp_str, tmp_length);
+
       delete [] tmp_str;
     }
   }
@@ -1113,6 +1215,18 @@ FormFieldText::~FormFieldText()
 {
   delete content;
   delete internalContent;
+  delete defaultContent;
+}
+
+void FormFieldText::reset(const std::vector<std::string>& excludedFields)
+{
+  if (!isAmongExcludedFields(excludedFields)) {
+    setContentCopy(defaultContent);
+    if (defaultContent == nullptr)
+      obj.getDict()->remove("V");
+  }
+
+  resetChildren(excludedFields);
 }
 
 double FormFieldText::getTextFontSize()
@@ -1214,6 +1328,7 @@ FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object 
&&aobj, const Ref refA, Fo
 {
   numChoices = 0;
   choices = nullptr;
+  defaultChoices = nullptr;
   editedChoice = nullptr;
   topIdx = 0;
 
@@ -1290,7 +1405,25 @@ FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object 
&&aobj, const Ref refA, Fo
     // Note: According to PDF specs, /V should *never* contain the exportVal.
     // However, if /Opt is an array of (exportVal,optionName) pairs, acroread
     // seems to expect the exportVal instead of the optionName and so we do 
too.
-    obj1 = Form::fieldLookup(dict, "V");
+    fillChoices(fillValue);
+  }
+
+  fillChoices(fillDefaultValue);
+}
+
+void FormFieldChoice::fillChoices(FillValueType fillType)
+{
+  const char *key = fillType == fillDefaultValue ? "DV" : "V";
+  Dict* dict = obj.getDict();
+  Object obj1;
+
+  obj1 = Form::fieldLookup(dict, key);
+  if (obj1.isString() || obj1.isArray()) {
+    if (fillType == fillDefaultValue) {
+      defaultChoices = new bool[numChoices];
+      memset(defaultChoices, 0, sizeof(bool) * numChoices);
+    }
+
     if (obj1.isString()) {
       bool optionFound = false;
 
@@ -1306,13 +1439,16 @@ FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object 
&&aobj, const Ref refA, Fo
         }
 
         if (optionFound) {
-          choices[i].selected = true;
+          if (fillType == fillDefaultValue)
+            defaultChoices[i] = true;
+          else
+            choices[i].selected = true;
           break; // We've determined that this option is selected. No need to 
keep on scanning
         }
       }
 
       // Set custom value if /V doesn't refer to any predefined option and the 
field is user-editable
-      if (!optionFound && edit) {
+      if (fillType == fillValue && !optionFound && edit) {
         editedChoice = obj1.getString()->copy();
       }
     } else if (obj1.isArray()) {
@@ -1320,7 +1456,7 @@ FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object 
&&aobj, const Ref refA, Fo
         for (int j = 0; j < obj1.arrayGetLength(); j++) {
           const Object obj2 = obj1.arrayGet(j);
           if (!obj2.isString()) {
-            error(errSyntaxError, -1, "FormWidgetChoice:: V array contains a 
non string object");
+            error(errSyntaxError, -1, "FormWidgetChoice:: {0:s} array contains 
a non string object", key);
             continue;
           }
 
@@ -1337,7 +1473,10 @@ FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object 
&&aobj, const Ref refA, Fo
           }
 
           if (matches) {
-            choices[i].selected = true;
+            if (fillType == fillDefaultValue)
+              defaultChoices[i] = true;
+            else
+              choices[i].selected = true;
             break; // We've determined that this option is selected. No need 
to keep on scanning
           }
         }
@@ -1353,6 +1492,7 @@ FormFieldChoice::~FormFieldChoice()
     delete choices[i].optionName;
   }
   delete [] choices;
+  delete [] defaultChoices;
   delete editedChoice;
 }
 
@@ -1503,6 +1643,25 @@ const GooString *FormFieldChoice::getSelectedChoice() 
const {
   return nullptr;
 }
 
+void FormFieldChoice::reset(const std::vector<std::string>& excludedFields)
+{
+  if (!isAmongExcludedFields(excludedFields)) {
+    delete editedChoice;
+    editedChoice = nullptr;
+
+    if (defaultChoices) {
+      for (int i = 0; i < numChoices; i++)
+        choices[i].selected = defaultChoices[i];
+    } else {
+      unselectAll();
+    }
+  }
+
+  resetChildren(excludedFields);
+
+  updateSelection();
+}
+
 //------------------------------------------------------------------------
 // FormFieldSignature
 //------------------------------------------------------------------------
@@ -1993,6 +2152,56 @@ FormWidget* Form::findWidgetByRef (Ref aref)
   return nullptr;
 }
 
+FormField* Form::findFieldByRef(Ref aref) const
+{
+  for (int i = 0; i < numFields; i++) {
+    FormField *result = rootFields[i]->findFieldByRef(aref);
+    if (result) return result;
+  }
+  return nullptr;
+}
+
+FormField* Form::findFieldByFullyQualifiedName(const std::string &name) const
+{
+  for (int i = 0; i < numFields; i++) {
+    FormField *result = rootFields[i]->findFieldByFullyQualifiedName(name);
+    if (result) return result;
+  }
+  return nullptr;
+}
+
+void Form::reset (const std::vector<std::string>& fields, bool excludeFields)
+{
+  FormField  *foundField;
+  const bool  resetAllFields = fields.empty();
+
+  if (resetAllFields) {
+    for (int i = 0; i < numFields; i++) {
+      rootFields[i]->reset(std::vector<std::string>());
+    }
+  } else {
+    if (!excludeFields) {
+      for (const std::string &field : fields) {
+        Ref fieldRef;
+
+        if (field.compare(field.size() - 2, 2, " R") == 0 &&
+            sscanf(field.c_str(), "%d %d R", &fieldRef.num, &fieldRef.gen) == 
2) {
+          foundField = findFieldByRef(fieldRef);
+        } else {
+          foundField = findFieldByFullyQualifiedName(field);
+        }
+
+        if (foundField)
+          foundField->reset(std::vector<std::string>());
+      }
+    } else {
+      for (int i = 0; i < numFields; i++) {
+        rootFields[i]->reset(fields);
+      }
+    }
+  }
+}
+
 //------------------------------------------------------------------------
 // FormPageWidgets
 //------------------------------------------------------------------------
diff --git a/poppler/Form.h b/poppler/Form.h
index cf5b4a6f..ad85cd18 100644
--- a/poppler/Form.h
+++ b/poppler/Form.h
@@ -21,6 +21,7 @@
 // Copyright 2019, 2020 Oliver Sander <[email protected]>
 // Copyright 2019 João Netto <[email protected]>
 // Copyright 2020 Nelson Benítez León <[email protected]>
+// Copyright 2020 Marek Kasik <[email protected]>
 //
 //========================================================================
 
@@ -73,6 +74,11 @@ enum FormSignatureType {
   ETSI_CAdES_detached
 };
 
+enum FillValueType {
+  fillValue,
+  fillDefaultValue
+};
+
 class Form;
 class FormField;
 class FormFieldButton;
@@ -330,11 +336,16 @@ public:
 
   void printTree(int indent = 0);
   virtual void print(int indent = 0);
+  virtual void reset(const std::vector<std::string>& excludedFields);
+  void resetChildren(const std::vector<std::string>& excludedFields);
+  FormField* findFieldByRef(Ref aref);
+  FormField* findFieldByFullyQualifiedName(const std::string &name);
 
  protected:
   void _createWidget (Object *obj, Ref aref);
   void createChildren(std::set<int> *usedParents);
   void updateChildrenAppearance();
+  bool isAmongExcludedFields(const std::vector<std::string>& excludedFields);
 
   FormFieldType type;           // field type
   Ref ref;
@@ -383,6 +394,7 @@ public:
   bool getState(const char *state) const;
 
   const char *getAppearanceState() const { return appearanceState.isName() ? 
appearanceState.getName() : nullptr; }
+  const char *getDefaultAppearanceState() const { return 
defaultAppearanceState.isName() ? defaultAppearanceState.getName() : nullptr; }
 
   void fillChildrenSiblingsID () override;
   
@@ -394,6 +406,7 @@ public:
   int getNumSiblings () const { return numSiblings; }
 
   void print(int indent) override;
+  void reset(const std::vector<std::string>& excludedFields) override;
 
   ~FormFieldButton() override;
 protected:
@@ -408,6 +421,7 @@ protected:
   int active_child; //only used for combo box
   bool noAllOff;
   Object appearanceState; // V
+  Object defaultAppearanceState; // DV
 };
 
 //------------------------------------------------------------------------
@@ -440,14 +454,17 @@ public:
   void setTextFontSize(int fontSize);
 
   void print(int indent) override;
+  void reset(const std::vector<std::string>& excludedFields) override;
 
   static int tokenizeDA(const GooString* daString, std::vector<GooString*>* 
daToks, const char* searchTok);
 
 protected:
   int parseDA(std::vector<GooString*>* daToks);
+  void fillContent(FillValueType fillType);
 
   GooString* content;
   GooString* internalContent;
+  GooString* defaultContent;
   bool multiline;
   bool password;
   bool fileSelect;
@@ -502,10 +519,12 @@ public:
   int getTopIndex() const { return topIdx; }
 
   void print(int indent) override;
+  void reset(const std::vector<std::string>& excludedFields) override;
 
 protected:
   void unselectAll();
   void updateSelection();
+  void fillChoices(FillValueType fillType);
 
   bool combo;
   bool edit;
@@ -521,6 +540,7 @@ protected:
 
   int numChoices;
   ChoiceOpt* choices;
+  bool* defaultChoices;
   GooString* editedChoice;
   int topIdx; // TI
 };
@@ -595,11 +615,15 @@ public:
   Object* getDefaultResourcesObj() { return &resDict; }
 
   FormWidget* findWidgetByRef (Ref aref);
+  FormField* findFieldByRef(Ref aref) const;
+  FormField* findFieldByFullyQualifiedName(const std::string &name) const;
 
   void postWidgetsLoad();
 
   const std::vector<Ref> &getCalculateOrder() const { return calculateOrder; }
 
+  void reset(const std::vector<std::string>& fields, bool excludeFields);
+
 private:
   FormField** rootFields;
   int numFields;
diff --git a/poppler/Link.cc b/poppler/Link.cc
index 23d4c99d..38f4f3dd 100644
--- a/poppler/Link.cc
+++ b/poppler/Link.cc
@@ -24,6 +24,7 @@
 // Copyright (C) 2018 Intevation GmbH <[email protected]>
 // Copyright (C) 2018, 2020 Adam Reichold <[email protected]>
 // Copyright (C) 2019, 2020 Oliver Sander <[email protected]>
+// Copyright (C) 2020 Marek Kasik <[email protected]>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -129,6 +130,10 @@ std::unique_ptr<LinkAction> LinkAction::parseAction(const 
Object *obj, const Goo
   } else if (obj2.isName("Hide")) {
     action = std::make_unique<LinkHide>(obj);
 
+  // ResetForm action
+  } else if (obj2.isName("ResetForm")) {
+    action = std::make_unique<LinkResetForm>(obj);
+
   // unknown action
   } else if (obj2.isName()) {
     action = std::make_unique<LinkUnknown>(obj2.getName());
@@ -832,6 +837,42 @@ LinkHide::LinkHide(const Object *hideObj) {
 
 LinkHide::~LinkHide() = default;
 
+//------------------------------------------------------------------------
+// LinkResetForm
+//------------------------------------------------------------------------
+
+LinkResetForm::LinkResetForm(const Object *obj) {
+  Object obj1;
+
+  exclude = false;
+
+  obj1 = obj->dictLookup("Fields");
+  if (obj1.isArray()) {
+    fields.resize(obj1.arrayGetLength());
+    for (int i = 0; i < obj1.arrayGetLength(); ++i) {
+      const Object &obj2 = obj1.arrayGetNF(i);
+      if (obj2.isName())
+        fields[i] = std::string (obj2.getName ());
+      else if (obj2.isRef()) {
+        fields[i] = std::to_string(obj2.getRef().num);
+        fields[i].append(" ");
+        fields[i].append(std::to_string(obj2.getRef().gen));
+        fields[i].append(" R");
+      }
+    }
+  }
+
+  obj1 = obj->dictLookup("Flags");
+  if (obj1.isInt()) {
+    int flags = obj1.getInt();
+
+    if (flags & 0x1)
+      exclude = true;
+  }
+}
+
+LinkResetForm::~LinkResetForm() = default;
+
 //------------------------------------------------------------------------
 // LinkUnknown
 //------------------------------------------------------------------------
diff --git a/poppler/Link.h b/poppler/Link.h
index d3ebafcf..bade7fa3 100644
--- a/poppler/Link.h
+++ b/poppler/Link.h
@@ -22,6 +22,7 @@
 // Copyright (C) 2018 Intevation GmbH <[email protected]>
 // Copyright (C) 2019, 2020 Oliver Sander <[email protected]>
 // Copyright (C) 2020 Adam Reichold <[email protected]>
+// Copyright (C) 2020 Marek Kasik <[email protected]>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -59,6 +60,7 @@ enum LinkActionKind {
   actionJavaScript,            // JavaScript action
   actionOCGState,               // Set-OCG-State action
   actionHide,                  // Hide action
+  actionResetForm,             // ResetForm action
   actionUnknown                        // anything else
 };
 
@@ -491,6 +493,32 @@ private:
   bool show;
 };
 
+//------------------------------------------------------------------------
+// LinkResetForm
+//------------------------------------------------------------------------
+
+class LinkResetForm: public LinkAction {
+public:
+
+  // Build a LinkResetForm.
+  LinkResetForm(const Object *nameObj);
+
+  ~LinkResetForm() override;
+
+  bool isOk() const override { return true; }
+
+  LinkActionKind getKind() const override { return actionResetForm; }
+
+  const std::vector<std::string>& getFields() const { return fields; }
+  bool getExclude() const { return exclude; }
+
+private:
+
+  std::vector<std::string> fields;
+  bool exclude;
+};
+
+
 //------------------------------------------------------------------------
 // LinkUnknown
 //------------------------------------------------------------------------
diff --git a/qt5/src/poppler-page.cc b/qt5/src/poppler-page.cc
index 35bec6c9..dab4bc43 100644
--- a/qt5/src/poppler-page.cc
+++ b/qt5/src/poppler-page.cc
@@ -355,6 +355,10 @@ Link* PageData::convertLinkActionToLink(::LinkAction * a, 
DocumentData *parentDo
     }
     break;
 
+    case actionResetForm:
+      // Not handled in Qt5 front-end yet
+    break;
+
     case actionUnknown:
     break;
   }
_______________________________________________
poppler mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/poppler

Reply via email to