Commit e4badf4d authored by Nelson Benítez León's avatar Nelson Benítez León

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
parent e90fe93e
......@@ -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;
......
......@@ -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);
......
......@@ -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;
......
......@@ -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,
......
......@@ -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
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment