[poppler] poppler/Annot.h poppler/Form.cc poppler/Form.h poppler/Page.cc poppler/Page.h qt5/tests

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Apr 6 21:54:08 UTC 2020


 poppler/Annot.h           |    1 
 poppler/Form.cc           |   16 +++++++++++++++
 poppler/Form.h            |    9 +++++++-
 poppler/Page.cc           |   48 +++++++++++++++++++++++++++++++++++++++++++++-
 poppler/Page.h            |    8 +++++++
 qt5/tests/check_forms.cpp |   31 +++++++++++++++++++++++++++++
 6 files changed, 111 insertions(+), 2 deletions(-)

New commits:
commit e4badf4d745b8e8f9a0a25b6c3cc97fbadbbb499
Author: Nelson Benítez León <nbenitezl at gmail.com>
Date:   Sat Mar 28 16:35:16 2020 -0400

    support 'de facto' tooltip feature
    
    Most pdf readers implement a tooltip feature by
    showing the string content of 'TU' field of a
    widget annotation that is not linked to any
    form field.
    
    Normally, widget annotations carry a reference to a
    form field which are used together to implement the
    different form widgets. But, the PDF spec does not
    forbid standalone (i.e. not linked to any form field)
    widget annotations, and the fact is they're been used
    by most pdf readers to show a tooltip when the area
    of that AnnotWidget is hovered.
    
    Some API added for this feature:
    
    bool FormField::isStandAlone()
    void FormField::setStandAlone (bool value)
    
    A standalone FormField means it's not part of Catalog's
    Field array, because of that we store them in a new
    member inside Page class:
    
    std::vector<FormField*> standaloneFields;
    
    and send them alongside the rest of FormWidgets in the
    existant API:
    
    FormPageWidgets *Page::getFormWidgets();
    
    Poppler issue #34
    
    Evince issue:
    https://gitlab.gnome.org/GNOME/evince/issues/842

diff --git a/poppler/Annot.h b/poppler/Annot.h
index 9ded9d1f..87a1642c 100644
--- a/poppler/Annot.h
+++ b/poppler/Annot.h
@@ -696,6 +696,7 @@ public:
   PDFDoc *getDoc() const { return doc; }
   bool getHasRef() const { return hasRef; }
   Ref getRef() const { return ref; }
+  Dict *getDict() const { return annotObj.getDict(); }
   AnnotSubtype getType() const { return type; }
   PDFRectangle *getRect() const { return rect.get(); }
   void getRect(double *x1, double *y1, double *x2, double *y2) const;
diff --git a/poppler/Form.cc b/poppler/Form.cc
index 024449ab..9685bb3a 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -646,6 +646,7 @@ FormField::FormField(PDFDoc *docA, Object &&aobj, const Ref aref, FormField *par
   fullyQualifiedName = nullptr;
   quadding = quaddingLeftJustified;
   hasQuadding = false;
+  standAlone = false;
 
   //childs
   Object obj1 = dict->lookup("Kids");
@@ -2025,6 +2026,21 @@ FormPageWidgets::FormPageWidgets (Annots *annots, unsigned int page, Form *form)
   }
 }
 
+void FormPageWidgets::addWidgets(const std::vector<FormField*>& addedWidgets, unsigned int page)
+{
+  if (addedWidgets.empty())
+    return;
+
+  size += addedWidgets.size();
+  widgets = (FormWidget**)greallocn(widgets, size, sizeof(FormWidget*));
+
+  for (auto frmField : addedWidgets) {
+    FormWidget *frmWidget = frmField->getWidget(0);
+    frmWidget->setID(FormWidget::encodeID(page, numWidgets));
+    widgets[numWidgets++] = frmWidget;
+  }
+}
+
 FormPageWidgets::~FormPageWidgets()
 {
   gfree (widgets);
diff --git a/poppler/Form.h b/poppler/Form.h
index b4081ea0..f3dc658f 100644
--- a/poppler/Form.h
+++ b/poppler/Form.h
@@ -303,6 +303,8 @@ public:
 
   void setReadOnly (bool value);
   bool isReadOnly () const { return readOnly; }
+  void setStandAlone (bool value) { standAlone = value; }
+  bool isStandAlone () const { return standAlone; }
 
   GooString* getDefaultAppearance() const { return defaultAppearance; }
   bool hasTextQuadding() const { return hasQuadding; }
@@ -353,6 +355,9 @@ public:
   bool hasQuadding;
   VariableTextQuadding quadding;
 
+  //True when FormField is not part of Catalog's Field array (or there isn't one).
+  bool standAlone;
+
 private:
   FormField() {}
 };
@@ -564,7 +569,8 @@ public:
   static Object fieldLookup(Dict *field, const char *key);
   
   /* Creates a new Field of the type specified in obj's dict.
-     used in Form::Form and FormField::FormField */
+     used in Form::Form , FormField::FormField and
+     Page::loadStandaloneFields */
   static FormField *createFieldFromDict (Object &&obj, PDFDoc *docA, const Ref aref, FormField *parent, std::set<int> *usedParents);
 
   Object *getObj () const { return acroForm; }
@@ -613,6 +619,7 @@ public:
 
   int getNumWidgets() const { return numWidgets; }
   FormWidget* getWidget(int i) const { return widgets[i]; }
+  void addWidgets(const std::vector<FormField*>& addedWidgets, unsigned int page);
 
 private:
   FormWidget** widgets;
diff --git a/poppler/Page.cc b/poppler/Page.cc
index 024fe3da..d68ad2d1 100644
--- a/poppler/Page.cc
+++ b/poppler/Page.cc
@@ -320,6 +320,9 @@ Page::Page(PDFDoc *docA, int numA, Object &&pageDict, Ref pageRefA, PageAttrs *a
 Page::~Page() {
   delete attrs;
   delete annots;
+  for (auto frmField : standaloneFields) {
+    delete frmField;
+  }
 }
 
 Dict *Page::getResourceDict() { 
@@ -355,10 +358,50 @@ void Page::replaceXRef(XRef *xrefA) {
   delete pageDict;
 }
 
+/* Loads standalone fields into Page, should be called once per page only */
+void Page::loadStandaloneFields(Annots *annotations, Form *form) {
+  const int numAnnots = annotations ? annotations->getNumAnnots() : 0;
+  if (numAnnots < 1)
+    return;
+
+  /* Look for standalone annots, identified by being: 1) of type Widget
+   * 2) of subtype Button 3) not referenced from the Catalog's Form Field array */
+  for (int i = 0; i < numAnnots; ++i) {
+    Annot *annot = annotations->getAnnot(i);
+
+    if (annot->getType() != Annot::typeWidget || !annot->getHasRef())
+      continue;
+
+    const Ref r = annot->getRef();
+    if (form && form->findWidgetByRef(r))
+      continue; // this annot is referenced inside Form, skip it
+
+    std::set<int> parents;
+    FormField *field = Form::createFieldFromDict (Object(annot->getDict()),
+                                                  annot->getDoc(), r, nullptr, &parents);
+
+    if (field && field->getType() == formButton && field->getNumWidgets() == 1) {
+
+      field->setStandAlone(true);
+      FormWidget *formWidget = field->getWidget(0);
+
+      if (!formWidget->getWidgetAnnotation())
+        formWidget->createWidgetAnnotation();
+
+      standaloneFields.push_back(field);
+
+    } else if (field) {
+      delete field;
+    }
+  }
+}
+
 Annots *Page::getAnnots(XRef *xrefA) {
   if (!annots) {
     Object obj = getAnnotsObject(xrefA);
     annots = new Annots(doc, num, &obj);
+    // Load standalone fields once for the page
+    loadStandaloneFields(annots, doc->getCatalog()->getForm());
   }
 
   return annots;
@@ -454,7 +497,10 @@ Links *Page::getLinks() {
 }
 
 FormPageWidgets *Page::getFormWidgets() {
-  return new FormPageWidgets(getAnnots(), num, doc->getCatalog()->getForm());
+  FormPageWidgets *frmPageWidgets = new FormPageWidgets(getAnnots(), num, doc->getCatalog()->getForm());
+  frmPageWidgets->addWidgets(standaloneFields, num);
+
+  return frmPageWidgets;
 }
 
 void Page::display(OutputDev *out, double hDPI, double vDPI,
diff --git a/poppler/Page.h b/poppler/Page.h
index e5d59449..98a19e1c 100644
--- a/poppler/Page.h
+++ b/poppler/Page.h
@@ -51,6 +51,7 @@ class Annot;
 class Gfx;
 class FormPageWidgets;
 class Form;
+class FormField;
 
 //------------------------------------------------------------------------
 
@@ -286,6 +287,13 @@ private:
   double duration;              // page duration
   bool ok;			// true if page is valid
   mutable std::recursive_mutex mutex;
+  // standalone widgets are special FormWidget's inside a Page that *are not*
+  // referenced from the Catalog's Field array. That means they are standlone,
+  // i.e. the PDF document does not have a FormField associated with them. We
+  // create standalone FormFields to contain those special FormWidgets, as
+  // they are 'de facto' being used to implement tooltips. See #34
+  std::vector<FormField*> standaloneFields;
+  void loadStandaloneFields(Annots *annotations, Form *form);
 };
 
 #endif
diff --git a/qt5/tests/check_forms.cpp b/qt5/tests/check_forms.cpp
index fb7c3baf..4ab58f09 100644
--- a/qt5/tests/check_forms.cpp
+++ b/qt5/tests/check_forms.cpp
@@ -16,6 +16,7 @@ private slots:
     void testSetIcon();// Test that setIcon will always be valid.
     void testSetPrintable();
     void testSetAppearanceText();
+    void testStandAloneWidgets(); // check for 'de facto' tooltips. Issue #34
     void testUnicodeFieldAttributes();
 };
 
@@ -47,6 +48,36 @@ void TestForms::testCheckbox()
     QCOMPARE( chkFormFieldButton->state() , true );
 }
 
+void TestForms::testStandAloneWidgets()
+{
+    // Check for 'de facto' tooltips. Issue #34
+    QScopedPointer< Poppler::Document > document(Poppler::Document::load(TESTDATADIR "/unittestcases/tooltip.pdf"));
+    QVERIFY( document );
+
+    QScopedPointer< Poppler::Page > page(document->page(0));
+    QVERIFY( page );
+
+    QList<Poppler::FormField*> forms = page->formFields();
+
+    QCOMPARE( forms.size() , 3 );
+
+    Q_FOREACH (Poppler::FormField *field, forms) {
+        QCOMPARE( field->type() , Poppler::FormField::FormButton );
+
+        Poppler::FormFieldButton *fieldButton = static_cast<Poppler::FormFieldButton *>(field);
+        QCOMPARE( fieldButton->buttonType() , Poppler::FormFieldButton::Push );
+
+        FormField *ff = Poppler::FormFieldData::getFormWidget( fieldButton )->getField();
+        QVERIFY( ff );
+        QCOMPARE( ff->isStandAlone() , true );
+
+        // tooltip.pdf has only these 3 standalone widgets
+        QVERIFY( field->uiName() == QStringLiteral("This is a tooltip!") ||
+                 field->uiName() == QStringLiteral("Sulfuric acid") ||
+                 field->uiName() == QStringLiteral("little Gauß") );
+    }
+}
+
 void TestForms::testCheckboxIssue159()
 {
     // Test for checkbox issue #159


More information about the poppler mailing list