[poppler] poppler/Form.cc poppler/Form.h poppler/Link.cc poppler/Link.h qt5/src

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon May 18 18:02:45 UTC 2020


 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 <mkasik at redhat.com>
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 <oliver.sander at tu-dresden.de>
 // Copyright 2019 Tomoyuki Kubota <himajin100000 at gmail.com>
 // Copyright 2019 João Netto <joaonetto901 at gmail.com>
+// Copyright 2020 Marek Kasik <mkasik at redhat.com>
 //
 //========================================================================
 
@@ -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 <oliver.sander at tu-dresden.de>
 // Copyright 2019 João Netto <joaonetto901 at gmail.com>
 // Copyright 2020 Nelson Benítez León <nbenitezl at gmail.com>
+// Copyright 2020 Marek Kasik <mkasik at redhat.com>
 //
 //========================================================================
 
@@ -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 <intevation at intevation.de>
 // Copyright (C) 2018, 2020 Adam Reichold <adam.reichold at t-online.de>
 // Copyright (C) 2019, 2020 Oliver Sander <oliver.sander at tu-dresden.de>
+// Copyright (C) 2020 Marek Kasik <mkasik at redhat.com>
 //
 // 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 <intevation at intevation.de>
 // Copyright (C) 2019, 2020 Oliver Sander <oliver.sander at tu-dresden.de>
 // Copyright (C) 2020 Adam Reichold <adam.reichold at t-online.de>
+// Copyright (C) 2020 Marek Kasik <mkasik at redhat.com>
 //
 // 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;
   }


More information about the poppler mailing list