aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDFAnnotationObjectHelper.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf/QPDFAnnotationObjectHelper.cc')
-rw-r--r--libqpdf/QPDFAnnotationObjectHelper.cc208
1 files changed, 208 insertions, 0 deletions
diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc
index ee1d8180..698f80e1 100644
--- a/libqpdf/QPDFAnnotationObjectHelper.cc
+++ b/libqpdf/QPDFAnnotationObjectHelper.cc
@@ -1,5 +1,9 @@
#include <qpdf/QPDFAnnotationObjectHelper.hh>
#include <qpdf/QTC.hh>
+#include <qpdf/QPDFMatrix.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDF.hh>
+#include <qpdf/QPDFNameTreeObjectHelper.hh>
QPDFAnnotationObjectHelper::Members::~Members()
{
@@ -44,6 +48,13 @@ QPDFAnnotationObjectHelper::getAppearanceState()
return "";
}
+int
+QPDFAnnotationObjectHelper::getFlags()
+{
+ QPDFObjectHandle flags_obj = this->oh.getKey("/F");
+ return flags_obj.isInteger() ? flags_obj.getIntValue() : 0;
+}
+
QPDFObjectHandle
QPDFAnnotationObjectHelper::getAppearanceStream(
std::string const& which,
@@ -73,3 +84,200 @@ QPDFAnnotationObjectHelper::getAppearanceStream(
QTC::TC("qpdf", "QPDFAnnotationObjectHelper AN null");
return QPDFObjectHandle::newNull();
}
+
+std::string
+QPDFAnnotationObjectHelper::getPageContentForAppearance(
+ std::string const& name, int rotate,
+ int required_flags, int forbidden_flags)
+{
+ 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.
+
+ // 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.
+
+ // 2. Find the minimum upright rectangle that encompasses the
+ // resulting quadrilateral. This is the "transformed appearance
+ // box", T.
+
+ // 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
+ // scaling so that the sizes match and translating so that the
+ // scaled T exactly overlaps /Rect.
+
+ // 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:
+
+ // 1. Perform the rotation on /BBox box prior to transforming it
+ // with /Matrix (by replacing matrix with concatenation of
+ // matrix onto the rotation)
+
+ // 2. Rotate the destination rectangle by the specified amount
+
+ // 3. Apply the rotation to A as computed above to get the final
+ // appearance matrix.
+
+ QPDFObjectHandle rect_obj = this->oh.getKey("/Rect");
+ QPDFObjectHandle as = getAppearanceStream("/N").getDict();
+ QPDFObjectHandle bbox_obj = as.getKey("/BBox");
+ QPDFObjectHandle matrix_obj = as.getKey("/Matrix");
+
+ int flags = getFlags();
+ if (flags & forbidden_flags)
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper forbidden flags");
+ return "";
+ }
+ if ((flags & required_flags) != required_flags)
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper missing required flags");
+ return "";
+ }
+
+ if (! (bbox_obj.isRectangle() && rect_obj.isRectangle()))
+ {
+ return "";
+ }
+ QPDFMatrix matrix;
+ if (matrix_obj.isMatrix())
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper explicit matrix");
+ matrix = QPDFMatrix(matrix_obj.getArrayAsMatrix());
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper default matrix");
+ }
+ QPDFObjectHandle::Rectangle rect = rect_obj.getArrayAsRectangle();
+ bool do_rotate = (rotate && (flags & an_no_rotate));
+ if (do_rotate)
+ {
+ // If the the annotation flags include the NoRotate bit and
+ // the page is rotated, we have to rotate the annotation about
+ // its upper left corner by the same amount in the opposite
+ // direction so that it will remain upright in absolute
+ // coordinates. Since the semantics of /Rotate for a page are
+ // to rotate the page, while the effect of rotating using a
+ // transformation matrix is to rotate the coordinate system,
+ // the opposite directionality is explicit in the code.
+ QPDFMatrix mr;
+ mr.rotatex90(rotate);
+ mr.concat(matrix);
+ matrix = mr;
+ double rect_w = rect.urx - rect.llx;
+ double rect_h = rect.ury - rect.lly;
+ switch (rotate)
+ {
+ case 90:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 90");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx,
+ rect.ury,
+ rect.llx + rect_h,
+ rect.ury + rect_w);
+ break;
+ case 180:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 180");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx - rect_w,
+ rect.ury,
+ rect.llx,
+ rect.ury + rect_h);
+ break;
+ case 270:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 270");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx - rect_h,
+ rect.ury - rect_w,
+ rect.llx,
+ rect.ury);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ }
+
+ // Transform bounding box by matrix to get T
+ QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
+ QPDFObjectHandle::Rectangle T = matrix.transformRectangle(bbox);
+ if ((T.urx == T.llx) || (T.ury == T.lly))
+ {
+ // avoid division by zero
+ return "";
+ }
+ // Compute a matrix to transform the appearance box to the rectangle
+ QPDFMatrix AA;
+ AA.translate(rect.llx, rect.lly);
+ AA.scale((rect.urx - rect.llx) / (T.urx - T.llx),
+ (rect.ury - rect.lly) / (T.ury - T.lly));
+ AA.translate(-T.llx, -T.lly);
+ if (do_rotate)
+ {
+ AA.rotatex90(rotate);
+ }
+
+ as.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
+ return (
+ "q\n" +
+ AA.unparse() + " cm\n" +
+ name + " Do\n" +
+ "Q\n");
+}