aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDFAnnotationObjectHelper.cc
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2018-12-24 23:54:48 +0100
committerJay Berkenbilt <ejb@ql.org>2019-01-01 14:07:21 +0100
commit104fd6da522c8828571580cb30f324c3cbe7283f (patch)
treeecf6b20dd9fbd6f70c53efda935504114f23701f /libqpdf/QPDFAnnotationObjectHelper.cc
parent5059ec0d35547c0b3328c3089221b7cb3c3ac45d (diff)
downloadqpdf-104fd6da522c8828571580cb30f324c3cbe7283f.tar.zst
Add matrix and annotation appearance stream handling
Generate page content fragment for rendering appearance streams including all matrix calculation.
Diffstat (limited to 'libqpdf/QPDFAnnotationObjectHelper.cc')
-rw-r--r--libqpdf/QPDFAnnotationObjectHelper.cc192
1 files changed, 192 insertions, 0 deletions
diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc
index ee1d8180..a5b824f3 100644
--- a/libqpdf/QPDFAnnotationObjectHelper.cc
+++ b/libqpdf/QPDFAnnotationObjectHelper.cc
@@ -1,5 +1,10 @@
#include <qpdf/QPDFAnnotationObjectHelper.hh>
#include <qpdf/QTC.hh>
+#include <qpdf/QPDFMatrix.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDF.hh>
+#include <qpdf/QPDFNameTreeObjectHelper.hh>
+#include <algorithm>
QPDFAnnotationObjectHelper::Members::~Members()
{
@@ -73,3 +78,190 @@ QPDFAnnotationObjectHelper::getAppearanceStream(
QTC::TC("qpdf", "QPDFAnnotationObjectHelper AN null");
return QPDFObjectHandle::newNull();
}
+
+std::string
+QPDFAnnotationObjectHelper::getAnnotationAppearanceMatrix(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.
+
+ // The algorithm for computing the appearance matrix described in
+ // section 12.5.5 of the ISO-32000 PDF spec. It is as follows:
+
+ // 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
+ // translating the lower left corner and then scaling so that
+ // the upper right corner also matches.
+
+ // 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, 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.
+
+ // You can see that the above algorithm works by imagining the
+ // following:
+
+ // * 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.
+
+ QPDFObjectHandle rect_obj = this->oh.getKey("/Rect");
+ QPDFObjectHandle flags = this->oh.getKey("/F");
+ QPDFObjectHandle as = getAppearanceStream("/N").getDict();
+ QPDFObjectHandle bbox_obj = as.getKey("/BBox");
+ QPDFObjectHandle matrix_obj = as.getKey("/Matrix");
+
+ 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();
+ if (rotate && flags.isInteger() && (flags.getIntValue() & 16))
+ {
+ // 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();
+ std::vector<double> bx(4);
+ std::vector<double> by(4);
+ matrix.transform(bbox.llx, bbox.lly, bx.at(0), by.at(0));
+ matrix.transform(bbox.llx, bbox.ury, bx.at(1), by.at(1));
+ matrix.transform(bbox.urx, bbox.lly, bx.at(2), by.at(2));
+ matrix.transform(bbox.urx, bbox.ury, bx.at(3), by.at(3));
+ // Find the transformed appearance box
+ double t_llx = *std::min_element(bx.begin(), bx.end());
+ double t_urx = *std::max_element(bx.begin(), bx.end());
+ double t_lly = *std::min_element(by.begin(), by.end());
+ double t_ury = *std::max_element(by.begin(), by.end());
+ 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);
+ // 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()))
+ {
+ return "";
+ }
+
+ QPDFObjectHandle::Rectangle rect =
+ as.getDict().getKey("/BBox").getArrayAsRectangle();
+ std::string cm = getAnnotationAppearanceMatrix(rotate);
+ if (cm.empty())
+ {
+ return "";
+ }
+ std::string as_content = (
+ "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;
+}