aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf')
-rw-r--r--libqpdf/QPDFAnnotationObjectHelper.cc151
-rw-r--r--libqpdf/QPDFObjectHandle.cc80
-rw-r--r--libqpdf/QPDFPageDocumentHelper.cc40
3 files changed, 158 insertions, 113 deletions
diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc
index 4c305149..ca4af907 100644
--- a/libqpdf/QPDFAnnotationObjectHelper.cc
+++ b/libqpdf/QPDFAnnotationObjectHelper.cc
@@ -80,19 +80,66 @@ QPDFAnnotationObjectHelper::getAppearanceStream(
}
std::string
-QPDFAnnotationObjectHelper::getAnnotationAppearanceMatrix(int rotate)
+QPDFAnnotationObjectHelper::getPageContentForAppearance(
+ std::string const& name, int rotate)
{
- // The appearance matrix is the transformation in effect when
- // rendering an appearance stream's content. The appearance stream
- // itself is a form XObject, which has a /BBox and an optional
- // /Matrix. The /BBox describes the bounding box of the annotation
- // in unrotated page coordinates. /Matrix may be applied to the
- // bounding box to transform the bounding box. The effect of this
- // is that the transformed box is still fit within the area the
- // annotation designates in its /Rect field.
+ if (! getAppearanceStream("/N").isStream())
+ {
+ return "";
+ }
+
+ // The appearance matrix computed by this method is the
+ // transformation matrix that needs to be in effect when drawing
+ // this annotation's appearance stream on the page. The algorithm
+ // for computing the appearance matrix described in section 12.5.5
+ // of the ISO-32000 PDF spec is similar but not identical to what
+ // we are doing here.
+
+ // When rendering an appearance stream associated with an
+ // annotation, there are four relevant components:
+ //
+ // * The appearance stream's bounding box (/BBox)
+ // * The appearance stream's matrix (/Matrix)
+ // * The annotation's rectangle (/Rect)
+ // * In the case of form fields with the NoRotate flag, the
+ // page's rotation
+
+ // When rendering a form xobject in isolation, just drawn with a
+ // /Do operator, the is no form field, so page rotation is not
+ // relevant, and there is no annotation, so /Rect is not relevant,
+ // so only /BBox and /Matrix are relevant. The effect of these are
+ // as follows:
+
+ // * /BBox is treated as a clipping region
+ // * /Matrix is applied as a transformation prior to rendering the
+ // appearance stream.
+
+ // There is no relationship between /BBox and /Matrix in this
+ // case.
+
+ // When rendering a form xobject in the context of an annotation,
+ // things are a little different. In particular, a matrix is
+ // established such that /BBox, when transformed by /Matrix, would
+ // fit completely inside of /Rect. /BBox is no longer a clipping
+ // region. To illustrate the difference, consider a /Matrix of
+ // [2 0 0 2 0 0], which is scaling by a factor of two along both
+ // axes. If the appearance stream drew a rectangle equal to /BBox,
+ // in the case of the form xobject in isolation, this matrix would
+ // cause only the lower-left quadrant of the rectangle to be
+ // visible since the scaling would cause the rest of it to fall
+ // outside of the clipping region. In the case of the form xobject
+ // displayed in the context of an annotation, such a matrix would
+ // have no effect at all because it would be applied to the
+ // bounding box first, and then when the resulting enclosing
+ // quadrilateral was transformed to fit into /Rect, the effect of
+ // the scaling would be undone.
+
+ // Our job is to create a transformation matrix that compensates
+ // for these differences so that the appearance stream of an
+ // annotation can be drawn as a regular form xobject.
- // The algorithm for computing the appearance matrix described in
- // section 12.5.5 of the ISO-32000 PDF spec. It is as follows:
+ // To do this, we perform the following steps, which overlap
+ // significantly with the algorithm in 12.5.5:
// 1. Transform the four corners of /BBox by applying /Matrix to
// them, creating an arbitrarily transformed quadrilateral.
@@ -103,38 +150,22 @@ QPDFAnnotationObjectHelper::getAnnotationAppearanceMatrix(int rotate)
// 3. Compute matrix A that maps the lower left and upper right
// corners of T to the annotation's /Rect. This can be done by
- // translating the lower left corner and then scaling so that
- // the upper right corner also matches.
+ // scaling so that the sizes match and translating so that the
+ // scaled T exactly overlaps /Rect.
- // 4. Concatenate /Matrix to A to get matrix AA. This matrix
- // translates from appearance stream coordinates to page
- // coordinates.
+ // If the annotation's /F flag has bit 4 set, this means that
+ // annotation is to be rotated about its upper left corner to
+ // counteract any rotation of the page so it remains upright. To
+ // achieve this effect, we do the following extra steps:
- // If the annotation's /F flag has bit 4 set, we modify the matrix
- // to also rotate the annotation in the opposite direction, and we
- // adjust the destination rectangle by rotating it about the upper
- // left hand corner so that the annotation will appear upright on
- // the rotated page.
+ // 1. Perform the rotation on /BBox box prior to transforming it
+ // with /Matrix (by replacing matrix with concatenation of
+ // matrix onto the rotation)
- // You can see that the above algorithm works by imagining the
- // following:
+ // 2. Rotate the destination rectangle by the specified amount
- // * In the simple case of where /BBox = /Rect and /Matrix is the
- // identity matrix, the transformed quadrilateral in step 1 will
- // be the bounding box. Since the bounding box is upright, T
- // will be the bounding box. Since /BBox = /Rect, matrix A is
- // the identity matrix, and matrix AA in step 4 is also the
- // identity matrix.
- //
- // * Imagine that the rectangle is different from the bounding
- // box. In this case, matrix A just transforms the bounding box
- // to the rectangle by scaling and translating, effectively
- // squeezing or stretching it into /Rect.
- //
- // * Imagine that /Matrix rotates the annotation by 30 degrees.
- // The transformed bounding box would stick out, and T would be
- // too big. In this case, matrix A shrinks T down until it fits
- // in /Rect.
+ // 3. Apply the rotation to A as computed above to get the final
+ // appearance matrix.
QPDFObjectHandle rect_obj = this->oh.getKey("/Rect");
QPDFObjectHandle flags = this->oh.getKey("/F");
@@ -157,7 +188,9 @@ QPDFAnnotationObjectHelper::getAnnotationAppearanceMatrix(int rotate)
QTC::TC("qpdf", "QPDFAnnotationObjectHelper default matrix");
}
QPDFObjectHandle::Rectangle rect = rect_obj.getArrayAsRectangle();
- if (rotate && flags.isInteger() && (flags.getIntValue() & 16))
+ bool do_rotate = (rotate && flags.isInteger() &&
+ (flags.getIntValue() & 16));
+ if (do_rotate)
{
// If the the annotation flags include the NoRotate bit and
// the page is rotated, we have to rotate the annotation about
@@ -229,39 +262,15 @@ QPDFAnnotationObjectHelper::getAnnotationAppearanceMatrix(int rotate)
AA.scale((rect.urx - rect.llx) / (t_urx - t_llx),
(rect.ury - rect.lly) / (t_ury - t_lly));
AA.translate(-t_llx, -t_lly);
- // Concatenate the user-specified matrix
- AA.concat(matrix);
- return AA.unparse();
-}
-
-std::string
-QPDFAnnotationObjectHelper::getPageContentForAppearance(int rotate)
-{
- QPDFObjectHandle as = getAppearanceStream("/N");
- if (! (as.isStream() && as.getDict().getKey("/BBox").isRectangle()))
+ if (do_rotate)
{
- return "";
+ AA.rotatex90(rotate);
}
- QPDFObjectHandle::Rectangle rect =
- as.getDict().getKey("/BBox").getArrayAsRectangle();
- std::string cm = getAnnotationAppearanceMatrix(rotate);
- if (cm.empty())
- {
- return "";
- }
- std::string as_content = (
+ as.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
+ return (
"q\n" +
- cm + " cm\n" +
- QUtil::double_to_string(rect.llx, 5) + " " +
- QUtil::double_to_string(rect.lly, 5) + " " +
- QUtil::double_to_string(rect.urx - rect.llx, 5) + " " +
- QUtil::double_to_string(rect.ury - rect.lly, 5) + " " +
- "re W n\n");
- PointerHolder<Buffer> buf = as.getStreamData(qpdf_dl_all);
- as_content += std::string(
- reinterpret_cast<char *>(buf->getBuffer()),
- buf->getSize());
- as_content += "\nQ\n";
- return as_content;
+ AA.unparse() + " cm\n" +
+ name + " Do\n" +
+ "Q\n");
}
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index 300027b9..a0d45c86 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -826,23 +826,8 @@ QPDFObjectHandle::isOrHasName(std::string const& value)
}
void
-QPDFObjectHandle::mergeDictionary(QPDFObjectHandle other)
+QPDFObjectHandle::mergeResources(QPDFObjectHandle other)
{
- std::set<QPDFObjGen> visiting;
- mergeDictionaryInternal(other, visiting, 0);
-}
-
-void
-QPDFObjectHandle::mergeDictionaryInternal(
- QPDFObjectHandle other,
- std::set<QPDFObjGen>& visiting,
- int depth)
-{
- if (depth > 100)
- {
- // Arbitrarily limit depth to avoid stack overflow
- return;
- }
if (! (isDictionary() && other.isDictionary()))
{
QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
@@ -859,33 +844,22 @@ QPDFObjectHandle::mergeDictionaryInternal(
QPDFObjectHandle this_val = getKey(key);
if (this_val.isDictionary() && other_val.isDictionary())
{
- if (this_val.isIndirect() && other_val.isIndirect() &&
- (this_val.getObjGen() == other_val.getObjGen()))
+ if (this_val.isIndirect())
{
- QTC::TC("qpdf", "QPDFObjectHandle merge equal indirect");
+ QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
+ this_val = this_val.shallowCopy();
+ replaceKey(key, this_val);
}
- else if (this_val.isIndirect() &&
- (visiting.count(this_val.getObjGen())))
+ std::set<std::string> other_val_keys = other_val.getKeys();
+ for (std::set<std::string>::iterator i2 =
+ other_val_keys.begin();
+ i2 != other_val_keys.end(); ++i2)
{
- QTC::TC("qpdf", "QPDFObjectHandle merge loop");
- }
- else
- {
- QPDFObjGen loop;
- if (this_val.isIndirect())
+ if (! this_val.hasKey(*i2))
{
- loop = this_val.getObjGen();
- visiting.insert(loop);
QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
- this_val = this_val.shallowCopy();
- replaceKey(key, this_val);
- }
- QTC::TC("qpdf", "QPDFObjectHandle nested merge");
- this_val.mergeDictionaryInternal(
- other_val, visiting, 1 + depth);
- if (loop.getObj())
- {
- visiting.erase(loop);
+ this_val.replaceKey(
+ *i2, other_val.getKey(*i2).shallowCopy());
}
}
}
@@ -923,9 +897,37 @@ QPDFObjectHandle::mergeDictionaryInternal(
else
{
QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
- replaceKey(key, other_val);
+ replaceKey(key, other_val.shallowCopy());
+ }
+ }
+}
+
+std::set<std::string>
+QPDFObjectHandle::getResourceNames()
+{
+ // Return second-level dictionary keys
+ std::set<std::string> result;
+ if (! isDictionary())
+ {
+ return result;
+ }
+ std::set<std::string> keys = getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ std::string const& key = *iter;
+ QPDFObjectHandle val = getKey(key);
+ if (val.isDictionary())
+ {
+ std::set<std::string> val_keys = val.getKeys();
+ for (std::set<std::string>::iterator i2 = val_keys.begin();
+ i2 != val_keys.end(); ++i2)
+ {
+ result.insert(*i2);
+ }
}
}
+ return result;
}
// Indirect object accessors
diff --git a/libqpdf/QPDFPageDocumentHelper.cc b/libqpdf/QPDFPageDocumentHelper.cc
index 4d117017..7b171e9a 100644
--- a/libqpdf/QPDFPageDocumentHelper.cc
+++ b/libqpdf/QPDFPageDocumentHelper.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDFPageDocumentHelper.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
+#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
QPDFPageDocumentHelper::Members::~Members()
@@ -121,6 +122,7 @@ QPDFPageDocumentHelper::flattenAnnotationsForPage(
{
rotate = rotate_obj.getIntValue();
}
+ int next_fx = 1;
for (std::vector<QPDFAnnotationObjectHelper>::iterator iter =
annots.begin();
iter != annots.end(); ++iter)
@@ -140,18 +142,50 @@ QPDFPageDocumentHelper::flattenAnnotationsForPage(
}
if (process)
{
- resources.mergeDictionary(as.getDict().getKey("/Resources"));
if (is_widget)
{
QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
- resources.mergeDictionary(ff.getInheritableFieldValue("/DR"));
+ 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");
}
- new_content += aoh.getPageContentForAppearance(rotate);
+ std::set<std::string> names = resources.getResourceNames();
+ std::string name;
+ while (next_fx < 1000000)
+ {
+ std::string candidate = "/Fxo" + QUtil::int_to_string(next_fx);
+ ++next_fx;
+ if (names.count(candidate) == 0)
+ {
+ name = candidate;
+ break;
+ }
+ }
+ if (name.empty())
+ {
+ // There are already more than a million /Fxo names.
+ // Somehow I doubt this is going to actually happen.
+ // Just pick a name and forget conflicts.
+ name = "/FxConflict";
+ }
+ resources.mergeResources(
+ QPDFObjectHandle::parse(
+ "<< /XObject << " + name + " null >> >>"));
+ resources.getKey("/XObject").replaceKey(name, as);
+ new_content += aoh.getPageContentForAppearance(name, rotate);
}
else
{