diff options
Diffstat (limited to 'libqpdf/QPDFAnnotationObjectHelper.cc')
-rw-r--r-- | libqpdf/QPDFAnnotationObjectHelper.cc | 208 |
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"); +} |