diff options
Diffstat (limited to 'libqpdf')
-rw-r--r-- | libqpdf/QPDFAcroFormDocumentHelper.cc | 252 | ||||
-rw-r--r-- | libqpdf/QPDFAnnotationObjectHelper.cc | 75 | ||||
-rw-r--r-- | libqpdf/QPDFFormFieldObjectHelper.cc | 190 | ||||
-rw-r--r-- | libqpdf/QPDFPageObjectHelper.cc | 23 | ||||
-rw-r--r-- | libqpdf/build.mk | 3 |
5 files changed, 543 insertions, 0 deletions
diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc new file mode 100644 index 00000000..7e70fd92 --- /dev/null +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -0,0 +1,252 @@ +#include <qpdf/QPDFAcroFormDocumentHelper.hh> + +#include <qpdf/QTC.hh> +#include <qpdf/QPDFPageDocumentHelper.hh> + +QPDFAcroFormDocumentHelper::Members::~Members() +{ +} + +QPDFAcroFormDocumentHelper::Members::Members() : + cache_valid(false) +{ +} + +QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : + QPDFDocumentHelper(qpdf), + m(new Members()) +{ +} + +void +QPDFAcroFormDocumentHelper::invalidateCache() +{ + this->m->cache_valid = false; + this->m->field_to_annotations.clear(); + this->m->annotation_to_field.clear(); +} + +bool +QPDFAcroFormDocumentHelper::hasAcroForm() +{ + return this->qpdf.getRoot().hasKey("/AcroForm"); +} + +std::vector<QPDFFormFieldObjectHelper> +QPDFAcroFormDocumentHelper::getFormFields() +{ + analyze(); + std::vector<QPDFFormFieldObjectHelper> result; + for (std::map<QPDFObjGen, + std::vector<QPDFAnnotationObjectHelper> >::iterator iter = + this->m->field_to_annotations.begin(); + iter != this->m->field_to_annotations.end(); ++iter) + { + result.push_back(this->qpdf.getObjectByObjGen((*iter).first)); + } + return result; +} + +std::vector<QPDFAnnotationObjectHelper> +QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) +{ + analyze(); + std::vector<QPDFAnnotationObjectHelper> result; + QPDFObjGen og(h.getObjectHandle().getObjGen()); + if (this->m->field_to_annotations.count(og)) + { + result = this->m->field_to_annotations[og]; + } + return result; +} + +std::vector<QPDFAnnotationObjectHelper> +QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) +{ + return h.getAnnotations("/Widget"); +} + +QPDFFormFieldObjectHelper +QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) +{ + QPDFObjectHandle oh = h.getObjectHandle(); + if (! (oh.isDictionary() && + oh.getKey("/Subtype").isName() && + (oh.getKey("/Subtype").getName() == "/Widget"))) + { + throw std::logic_error( + "QPDFAnnotationObjectHelper::getFieldForAnnotation called for" + " non-/Widget annotation"); + } + analyze(); + QPDFFormFieldObjectHelper result(QPDFObjectHandle::newNull()); + QPDFObjGen og(oh.getObjGen()); + if (this->m->annotation_to_field.count(og)) + { + result = this->m->annotation_to_field[og]; + } + return result; +} + +void +QPDFAcroFormDocumentHelper::analyze() +{ + if (this->m->cache_valid) + { + return; + } + this->m->cache_valid = true; + QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm"); + if (! (acroform.isDictionary() && acroform.hasKey("/Fields"))) + { + return; + } + QPDFObjectHandle fields = acroform.getKey("/Fields"); + if (! fields.isArray()) + { + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array"); + acroform.warnIfPossible( + "/Fields key of /AcroForm dictionary is not an array; ignoring"); + fields = QPDFObjectHandle::newArray(); + } + + // Traverse /AcroForm to find annotations and map them + // bidirectionally to fields. + + std::set<QPDFObjGen> visited; + size_t nfields = fields.getArrayNItems(); + QPDFObjectHandle null(QPDFObjectHandle::newNull()); + for (size_t i = 0; i < nfields; ++i) + { + traverseField(fields.getArrayItem(i), null, 0, visited); + } + + // All Widget annotations should have been encountered by + // traversing /AcroForm, but in case any weren't, find them by + // walking through pages, and treat any widget annotation that is + // not associated with a field as its own field. This just ensures + // that requesting the field for any annotation we find through a + // page's /Annots list will have some associated field. Note that + // a file that contains this kind of error will probably not + // actually work with most viewers. + + QPDFPageDocumentHelper dh(this->qpdf); + std::vector<QPDFPageObjectHelper> pages = dh.getAllPages(); + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin(); + iter != pages.end(); ++iter) + { + QPDFPageObjectHelper ph(*iter); + std::vector<QPDFAnnotationObjectHelper> annots = + getWidgetAnnotationsForPage(ph); + for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 = + annots.begin(); + i2 != annots.end(); ++i2) + { + QPDFObjectHandle annot((*i2).getObjectHandle()); + QPDFObjGen og(annot.getObjGen()); + if (this->m->annotation_to_field.count(og) == 0) + { + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget"); + // This is not supposed to happen, but it's easy + // enough for us to handle this case. Treat the + // annotation as its own field. This could allow qpdf + // to sensibly handle a case such as a PDF creator + // adding a self-contained annotation (merged with the + // field dictionary) to the page's /Annots array and + // forgetting to also put it in /AcroForm. + annot.warnIfPossible( + "this widget annotation is not" + " reachable from /AcroForm in the document catalog"); + this->m->annotation_to_field[og] = + QPDFFormFieldObjectHelper(annot); + this->m->field_to_annotations[og].push_back( + QPDFAnnotationObjectHelper(annot)); + } + } + } +} + +void +QPDFAcroFormDocumentHelper::traverseField( + QPDFObjectHandle field, QPDFObjectHandle parent, int depth, + std::set<QPDFObjGen>& visited) +{ + if (depth > 100) + { + // Arbitrarily cut off recursion at a fixed depth to avoid + // specially crafted files that could cause stack overflow. + return; + } + if (! field.isIndirect()) + { + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field"); + field.warnIfPossible( + "encountered a direct object as a field or annotation while" + " traversing /AcroForm; ignoring field or annotation"); + return; + } + if (! field.isDictionary()) + { + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field"); + field.warnIfPossible( + "encountered a non-dictionary as a field or annotation while" + " traversing /AcroForm; ignoring field or annotation"); + return; + } + QPDFObjGen og(field.getObjGen()); + if (visited.count(og) != 0) + { + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); + field.warnIfPossible("loop detected while traversing /AcroForm"); + return; + } + visited.insert(og); + + // A dictionary encountered while traversing the /AcroForm field + // may be a form field, an annotation, or the merger of the two. A + // field that has no fields below it is a terminal. If a terminal + // field looks like an annotation, it is an annotation because + // annotation dictionary fields can be merged with terminal field + // dictionaries. Otherwise, the annotation fields might be there + // to be inherited by annotations below it. + + bool is_annotation = false; + bool is_field = (0 == depth); + QPDFObjectHandle kids = field.getKey("/Kids"); + if (kids.isArray()) + { + is_field = true; + size_t nkids = kids.getArrayNItems(); + for (size_t k = 0; k < nkids; ++k) + { + traverseField(kids.getArrayItem(k), field, 1 + depth, visited); + } + } + else + { + if (field.hasKey("/Parent")) + { + is_field = true; + } + if (field.hasKey("/Subtype") || + field.hasKey("/Rect") || + field.hasKey("/AP")) + { + is_annotation = true; + } + } + + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", + (depth == 0) ? 0 : 1); + QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", + (is_field ? 0 : 1)); + + if (is_annotation) + { + QPDFObjectHandle our_field = (is_field ? field : parent); + this->m->field_to_annotations[our_field.getObjGen()].push_back( + QPDFAnnotationObjectHelper(field)); + this->m->annotation_to_field[og] = + QPDFFormFieldObjectHelper(our_field); + } +} diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc new file mode 100644 index 00000000..ee1d8180 --- /dev/null +++ b/libqpdf/QPDFAnnotationObjectHelper.cc @@ -0,0 +1,75 @@ +#include <qpdf/QPDFAnnotationObjectHelper.hh> +#include <qpdf/QTC.hh> + +QPDFAnnotationObjectHelper::Members::~Members() +{ +} + +QPDFAnnotationObjectHelper::Members::Members() +{ +} + +QPDFAnnotationObjectHelper::QPDFAnnotationObjectHelper(QPDFObjectHandle oh) : + QPDFObjectHelper(oh) +{ +} + +std::string +QPDFAnnotationObjectHelper::getSubtype() +{ + return this->oh.getKey("/Subtype").getName(); +} + +QPDFObjectHandle::Rectangle +QPDFAnnotationObjectHelper::getRect() +{ + return this->oh.getKey("/Rect").getArrayAsRectangle(); +} + +QPDFObjectHandle +QPDFAnnotationObjectHelper::getAppearanceDictionary() +{ + return this->oh.getKey("/AP"); +} + +std::string +QPDFAnnotationObjectHelper::getAppearanceState() +{ + if (this->oh.getKey("/AS").isName()) + { + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AS present"); + return this->oh.getKey("/AS").getName(); + } + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AS absent"); + return ""; +} + +QPDFObjectHandle +QPDFAnnotationObjectHelper::getAppearanceStream( + std::string const& which, + std::string const& state) +{ + QPDFObjectHandle ap = getAppearanceDictionary(); + std::string desired_state = state.empty() ? getAppearanceState() : state; + if (ap.isDictionary()) + { + QPDFObjectHandle ap_sub = ap.getKey(which); + if (ap_sub.isStream() && desired_state.empty()) + { + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AP stream"); + return ap_sub; + } + if (ap_sub.isDictionary() && (! desired_state.empty())) + { + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AP dictionary"); + QPDFObjectHandle ap_sub_val = ap_sub.getKey(desired_state); + if (ap_sub_val.isStream()) + { + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AN sub stream"); + return ap_sub_val; + } + } + } + QTC::TC("qpdf", "QPDFAnnotationObjectHelper AN null"); + return QPDFObjectHandle::newNull(); +} diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc new file mode 100644 index 00000000..e6445af3 --- /dev/null +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -0,0 +1,190 @@ +#include <qpdf/QPDFFormFieldObjectHelper.hh> +#include <qpdf/QTC.hh> + +QPDFFormFieldObjectHelper::Members::~Members() +{ +} + +QPDFFormFieldObjectHelper::Members::Members() +{ +} + +QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper(QPDFObjectHandle oh) : + QPDFObjectHelper(oh), + m(new Members()) +{ +} + +QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper() : + QPDFObjectHelper(QPDFObjectHandle::newNull()), + m(new Members()) +{ +} + +bool +QPDFFormFieldObjectHelper::isNull() +{ + return this->oh.isNull(); +} + +QPDFFormFieldObjectHelper +QPDFFormFieldObjectHelper::getParent() +{ + return this->oh.getKey("/Parent"); // may be null +} + +QPDFObjectHandle +QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) +{ + QPDFObjectHandle node = this->oh; + QPDFObjectHandle result(node.getKey(name)); + std::set<QPDFObjGen> seen; + while (result.isNull() && node.hasKey("/Parent")) + { + seen.insert(node.getObjGen()); + node = node.getKey("/Parent"); + if (seen.count(node.getObjGen())) + { + break; + } + result = node.getKey(name); + if (! result.isNull()) + { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper non-trivial inheritance"); + } + } + return result; +} + +std::string +QPDFFormFieldObjectHelper::getInheritableFieldValueAsString( + std::string const& name) +{ + QPDFObjectHandle fv = getInheritableFieldValue(name); + std::string result; + if (fv.isString()) + { + result = fv.getUTF8Value(); + } + return result; +} + +std::string +QPDFFormFieldObjectHelper::getInheritableFieldValueAsName( + std::string const& name) +{ + QPDFObjectHandle fv = getInheritableFieldValue(name); + std::string result; + if (fv.isName()) + { + result = fv.getName(); + } + return result; +} + +std::string +QPDFFormFieldObjectHelper::getFieldType() +{ + return getInheritableFieldValueAsName("/FT"); +} + +std::string +QPDFFormFieldObjectHelper::getFullyQualifiedName() +{ + std::string result; + QPDFObjectHandle node = this->oh; + std::set<QPDFObjGen> seen; + while ((! node.isNull()) && (seen.count(node.getObjGen()) == 0)) + { + if (node.getKey("/T").isString()) + { + if (! result.empty()) + { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper non-trivial qualified name"); + result = "." + result; + } + result = node.getKey("/T").getUTF8Value() + result; + } + seen.insert(node.getObjGen()); + node = node.getKey("/Parent"); + } + return result; +} + +std::string +QPDFFormFieldObjectHelper::getPartialName() +{ + std::string result; + if (this->oh.getKey("/T").isString()) + { + result = this->oh.getKey("/T").getUTF8Value(); + } + return result; +} + +std::string +QPDFFormFieldObjectHelper::getAlternativeName() +{ + if (this->oh.getKey("/TU").isString()) + { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU present"); + return this->oh.getKey("/TU").getUTF8Value(); + } + QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU absent"); + return getFullyQualifiedName(); +} + +std::string +QPDFFormFieldObjectHelper::getMappingName() +{ + if (this->oh.getKey("/TM").isString()) + { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM present"); + return this->oh.getKey("/TM").getUTF8Value(); + } + QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM absent"); + return getAlternativeName(); +} + +QPDFObjectHandle +QPDFFormFieldObjectHelper::getValue() +{ + return getInheritableFieldValue("/V"); +} + +std::string +QPDFFormFieldObjectHelper::getValueAsString() +{ + return getInheritableFieldValueAsString("/V"); +} + +QPDFObjectHandle +QPDFFormFieldObjectHelper::getDefaultValue() +{ + return getInheritableFieldValue("/DV"); +} + +std::string +QPDFFormFieldObjectHelper::getDefaultValueAsString() +{ + return getInheritableFieldValueAsString("/DV"); +} + +std::string +QPDFFormFieldObjectHelper::getDefaultAppearance() +{ + return getInheritableFieldValueAsString("/DA"); +} + +int +QPDFFormFieldObjectHelper::getQuadding() +{ + int result = 0; + QPDFObjectHandle fv = getInheritableFieldValue("/Q"); + if (fv.isInteger()) + { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper Q present"); + result = static_cast<int>(fv.getIntValue()); + } + return result; +} diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index b16d8751..47bc2117 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -19,6 +19,29 @@ QPDFPageObjectHelper::getPageImages() return this->oh.getPageImages(); } +std::vector<QPDFAnnotationObjectHelper> +QPDFPageObjectHelper::getAnnotations(std::string const& only_subtype) +{ + std::vector<QPDFAnnotationObjectHelper> result; + QPDFObjectHandle annots = this->oh.getKey("/Annots"); + if (annots.isArray()) + { + size_t nannots = annots.getArrayNItems(); + for (size_t i = 0; i < nannots; ++i) + { + QPDFObjectHandle annot = annots.getArrayItem(i); + if (only_subtype.empty() || + (annot.isDictionary() && + annot.getKey("/Subtype").isName() && + (only_subtype == annot.getKey("/Subtype").getName()))) + { + result.push_back(QPDFAnnotationObjectHelper(annot)); + } + } + } + return result; +} + std::vector<QPDFObjectHandle> QPDFPageObjectHelper::getPageContents() { diff --git a/libqpdf/build.mk b/libqpdf/build.mk index 07cfc81e..437c683e 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -35,7 +35,10 @@ SRCS_libqpdf = \ libqpdf/Pl_StdioFile.cc \ libqpdf/Pl_TIFFPredictor.cc \ libqpdf/QPDF.cc \ + libqpdf/QPDFAcroFormDocumentHelper.cc \ + libqpdf/QPDFAnnotationObjectHelper.cc \ libqpdf/QPDFExc.cc \ + libqpdf/QPDFFormFieldObjectHelper.cc \ libqpdf/QPDFObjGen.cc \ libqpdf/QPDFObject.cc \ libqpdf/QPDFObjectHandle.cc \ |