mirror of
https://github.com/qpdf/qpdf.git
synced 2024-05-29 00:10:54 +00:00
65ef0bf313
With the exception of form field annotations when /NeedAppearances is true, remove annotations that don't have appearance streams when flattening. There is no reason to keep these when flattening since they are invisible. This may include unchecked checkboxes, unshown popup windows, etc.
244 lines
8.1 KiB
C++
244 lines
8.1 KiB
C++
#include <qpdf/QPDFPageDocumentHelper.hh>
|
|
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
|
#include <qpdf/QUtil.hh>
|
|
#include <qpdf/QTC.hh>
|
|
|
|
QPDFPageDocumentHelper::Members::~Members()
|
|
{
|
|
}
|
|
|
|
QPDFPageDocumentHelper::Members::Members()
|
|
{
|
|
}
|
|
|
|
QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) :
|
|
QPDFDocumentHelper(qpdf)
|
|
{
|
|
}
|
|
|
|
std::vector<QPDFPageObjectHelper>
|
|
QPDFPageDocumentHelper::getAllPages()
|
|
{
|
|
std::vector<QPDFObjectHandle> const& pages_v = this->qpdf.getAllPages();
|
|
std::vector<QPDFPageObjectHelper> pages;
|
|
for (std::vector<QPDFObjectHandle>::const_iterator iter = pages_v.begin();
|
|
iter != pages_v.end(); ++iter)
|
|
{
|
|
pages.push_back(QPDFPageObjectHelper(*iter));
|
|
}
|
|
return pages;
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::pushInheritedAttributesToPage()
|
|
{
|
|
this->qpdf.pushInheritedAttributesToPage();
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::removeUnreferencedResources()
|
|
{
|
|
std::vector<QPDFPageObjectHelper> pages = getAllPages();
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
|
iter != pages.end(); ++iter)
|
|
{
|
|
(*iter).removeUnreferencedResources();
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first)
|
|
{
|
|
this->qpdf.addPage(newpage.getObjectHandle(), first);
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::addPageAt(QPDFPageObjectHelper newpage, bool before,
|
|
QPDFPageObjectHelper refpage)
|
|
{
|
|
this->qpdf.addPageAt(newpage.getObjectHandle(), before,
|
|
refpage.getObjectHandle());
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
|
|
{
|
|
this->qpdf.removePage(page.getObjectHandle());
|
|
}
|
|
|
|
|
|
void
|
|
QPDFPageDocumentHelper::flattenAnnotations(
|
|
int required_flags,
|
|
int forbidden_flags)
|
|
{
|
|
QPDFAcroFormDocumentHelper afdh(this->qpdf);
|
|
if (afdh.getNeedAppearances())
|
|
{
|
|
this->qpdf.getRoot().getKey("/AcroForm").warnIfPossible(
|
|
"document does not have updated appearance streams,"
|
|
" so form fields will not be flattened");
|
|
}
|
|
pushInheritedAttributesToPage();
|
|
std::vector<QPDFPageObjectHelper> pages = getAllPages();
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
|
iter != pages.end(); ++iter)
|
|
{
|
|
QPDFPageObjectHelper ph(*iter);
|
|
QPDFObjectHandle page_oh = ph.getObjectHandle();
|
|
if (page_oh.getKey("/Resources").isIndirect())
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper indirect resources");
|
|
page_oh.replaceKey("/Resources",
|
|
page_oh.getKey("/Resources").shallowCopy());
|
|
}
|
|
QPDFObjectHandle resources = ph.getObjectHandle().getKey("/Resources");
|
|
if (! resources.isDictionary())
|
|
{
|
|
// This should never happen and is not exercised in the
|
|
// test suite
|
|
resources = QPDFObjectHandle::newDictionary();
|
|
}
|
|
flattenAnnotationsForPage(ph, resources, afdh,
|
|
required_flags, forbidden_flags);
|
|
}
|
|
if (! afdh.getNeedAppearances())
|
|
{
|
|
this->qpdf.getRoot().removeKey("/AcroForm");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFPageDocumentHelper::flattenAnnotationsForPage(
|
|
QPDFPageObjectHelper& page,
|
|
QPDFObjectHandle& resources,
|
|
QPDFAcroFormDocumentHelper& afdh,
|
|
int required_flags,
|
|
int forbidden_flags)
|
|
{
|
|
bool need_appearances = afdh.getNeedAppearances();
|
|
std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
|
|
std::vector<QPDFObjectHandle> new_annots;
|
|
std::string new_content;
|
|
int rotate = 0;
|
|
QPDFObjectHandle rotate_obj =
|
|
page.getObjectHandle().getKey("/Rotate");
|
|
if (rotate_obj.isInteger() && rotate_obj.getIntValue())
|
|
{
|
|
rotate = rotate_obj.getIntValue();
|
|
}
|
|
int next_fx = 1;
|
|
for (std::vector<QPDFAnnotationObjectHelper>::iterator iter =
|
|
annots.begin();
|
|
iter != annots.end(); ++iter)
|
|
{
|
|
QPDFAnnotationObjectHelper& aoh(*iter);
|
|
QPDFObjectHandle as = aoh.getAppearanceStream("/N");
|
|
bool is_widget = (aoh.getSubtype() == "/Widget");
|
|
bool process = true;
|
|
if (need_appearances && is_widget)
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances");
|
|
process = false;
|
|
}
|
|
if (process && as.isStream())
|
|
{
|
|
if (is_widget)
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
|
|
QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
|
|
QPDFObjectHandle as_resources =
|
|
as.getDict().getKey("/Resources");
|
|
if (as_resources.isIndirect())
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources");
|
|
as.getDict().replaceKey(
|
|
"/Resources", as_resources.shallowCopy());
|
|
as_resources = as.getDict().getKey("/Resources");
|
|
}
|
|
as_resources.mergeResources(
|
|
ff.getInheritableFieldValue("/DR"));
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
|
|
}
|
|
std::set<std::string> names = resources.getResourceNames();
|
|
std::string name;
|
|
int max_fx = next_fx + names.size() + 1;
|
|
while (next_fx <= max_fx)
|
|
{
|
|
std::string candidate = "/Fxo" + QUtil::int_to_string(next_fx);
|
|
if (names.count(candidate) == 0)
|
|
{
|
|
name = candidate;
|
|
break;
|
|
}
|
|
++next_fx;
|
|
}
|
|
if (name.empty())
|
|
{
|
|
// This could only happen if there is a coding error.
|
|
// The number of candidates we test is more than the
|
|
// number of keys we're checking against.
|
|
name = "/FxConflict";
|
|
}
|
|
std::string content = aoh.getPageContentForAppearance(
|
|
name, rotate, required_flags, forbidden_flags);
|
|
if (! content.empty())
|
|
{
|
|
resources.mergeResources(
|
|
QPDFObjectHandle::parse(
|
|
"<< /XObject << " + name + " null >> >>"));
|
|
resources.getKey("/XObject").replaceKey(name, as);
|
|
names.insert(name);
|
|
++next_fx;
|
|
}
|
|
new_content += content;
|
|
}
|
|
else if (process)
|
|
{
|
|
// If an annotation has no appearance stream, just drop
|
|
// the annotation when flattening. This can happen for
|
|
// unchecked checkboxes and radio buttons, popup windows
|
|
// associated with comments that aren't visible, and other
|
|
// types of annotations that aren't visible.
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance");
|
|
}
|
|
else
|
|
{
|
|
new_annots.push_back(aoh.getObjectHandle());
|
|
}
|
|
}
|
|
if (new_annots.size() != annots.size())
|
|
{
|
|
QPDFObjectHandle page_oh = page.getObjectHandle();
|
|
if (new_annots.empty())
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots");
|
|
page_oh.removeKey("/Annots");
|
|
}
|
|
else
|
|
{
|
|
QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
|
|
QPDFObjectHandle new_annots_oh =
|
|
QPDFObjectHandle::newArray(new_annots);
|
|
if (old_annots.isIndirect())
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots");
|
|
this->qpdf.replaceObject(
|
|
old_annots.getObjGen(), new_annots_oh);
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots");
|
|
page_oh.replaceKey("/Annots", new_annots_oh);
|
|
}
|
|
}
|
|
page.addPageContents(
|
|
QPDFObjectHandle::newStream(&qpdf, "q\n"), true);
|
|
page.addPageContents(
|
|
QPDFObjectHandle::newStream(&qpdf, "\nQ\n" + new_content), false);
|
|
}
|
|
}
|