aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDFAnnotationObjectHelper.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf/QPDFAnnotationObjectHelper.cc')
-rw-r--r--libqpdf/QPDFAnnotationObjectHelper.cc151
1 files changed, 80 insertions, 71 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");
}