[poppler] glib/poppler-annot.cc glib/poppler-page.cc glib/poppler-private.h

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Tue Jun 30 17:48:16 UTC 2020


 glib/poppler-annot.cc  |   86 +++++++++++++++++++++++---------
 glib/poppler-page.cc   |  131 ++++++++++++++++++++++++++++++++++++++++++++++++-
 glib/poppler-private.h |   13 ++++
 3 files changed, 204 insertions(+), 26 deletions(-)

New commits:
commit 434d7a86d952cf6bc8b66eb5f71991847150d682
Author: Nelson Benítez León <nbenitezl at gmail.com>
Date:   Tue Jun 30 17:48:14 2020 +0000

    poppler-glib: fix adding annots in rotated pages
    
    This commit adds support to poppler-glib to
    correctly add annotations to pages that are
    rotated.
    
    After this commit, annotations added through
    Evince will be correctly positioned on rotated
    pages (without any code change needed in Evince),
    both for normal and 'flagNoRotate' type
    annots and also for rotated pages that have a
    cropbox set in.
    
    As per PDF spec, annotations in rotated pages
    must be saved un-rotated, and the pdf client
    will display them rotated according to the
    rotation of the page they're in.
    
    Poppler-glib was not un-rotating them when
    saving them in core poppler, we now do it
    after this change, and poppler-glib will
    continue to serve them to apps with page's
    rotation applied, through the
    poppler_page_get_annot_mapping() API.
    
    No new API has been added or changed as of
    this commit.
    
    Evince issue:
    https://gitlab.gnome.org/GNOME/evince/-/issues/1385
    
    Poppler issue #256

diff --git a/glib/poppler-annot.cc b/glib/poppler-annot.cc
index b59926b4..3a312255 100644
--- a/glib/poppler-annot.cc
+++ b/glib/poppler-annot.cc
@@ -23,6 +23,11 @@
 #include "poppler.h"
 #include "poppler-private.h"
 
+#define ZERO_CROPBOX(c) (!(c && (c->x1 > 0.01 || c->y1 > 0.01)))
+
+const PDFRectangle *_poppler_annot_get_cropbox_and_page (PopplerAnnot *poppler_annot,
+                                                         Page         **page_out);
+
 /**
  * SECTION:poppler-annot
  * @short_description: Annotations
@@ -264,29 +269,20 @@ _poppler_annot_text_markup_new (Annot *annot)
   return _poppler_create_annot (POPPLER_TYPE_ANNOT_TEXT_MARKUP, annot);
 }
 
-/* If @crop_box parameter is non null, it will add the crop_box offset
- * to the coordinates of the returned quads */
 static AnnotQuadrilaterals *
-create_annot_quads_from_poppler_quads (GArray             *quads,
-                                       const PDFRectangle *crop_box)
+create_annot_quads_from_poppler_quads (GArray *quads)
 {
-  PDFRectangle zerobox;
   g_assert (quads->len > 0);
 
-  if (!crop_box) {
-    zerobox = PDFRectangle();
-    crop_box = &zerobox;
-  }
-
   auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(quads->len);
   for (guint i = 0; i < quads->len; i++) {
     PopplerQuadrilateral *quadrilateral = &g_array_index (quads, PopplerQuadrilateral, i);
 
     quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral (
-      quadrilateral->p1.x + crop_box->x1, quadrilateral->p1.y + crop_box->y1,
-      quadrilateral->p2.x + crop_box->x1, quadrilateral->p2.y + crop_box->y1,
-      quadrilateral->p3.x + crop_box->x1, quadrilateral->p3.y + crop_box->y1,
-      quadrilateral->p4.x + crop_box->x1, quadrilateral->p4.y + crop_box->y1);
+      quadrilateral->p1.x, quadrilateral->p1.y,
+      quadrilateral->p2.x, quadrilateral->p2.y,
+      quadrilateral->p3.x, quadrilateral->p3.y,
+      quadrilateral->p4.x, quadrilateral->p4.y );
   }
 
   return new AnnotQuadrilaterals (std::move(quads_array), quads->len);
@@ -1027,9 +1023,11 @@ poppler_annot_get_page_index (PopplerAnnot *poppler_annot)
 }
 
 /* Returns cropbox rect for the page where the passed in @poppler_annot is in,
- * or NULL when could not retrieve the cropbox */
+ * or NULL when could not retrieve the cropbox. If @page_out is non-null then
+ * it will be set with the page that @poppler_annot is in. */
 const PDFRectangle *
-_poppler_annot_get_cropbox (PopplerAnnot *poppler_annot)
+_poppler_annot_get_cropbox_and_page (PopplerAnnot *poppler_annot,
+                                     Page         **page_out)
 {
   int page_index;
 
@@ -1041,6 +1039,9 @@ _poppler_annot_get_cropbox (PopplerAnnot *poppler_annot)
 
     page = poppler_annot->annot->getDoc()->getPage(page_index);
     if (page) {
+      if (page_out)
+        *page_out = page;
+
       return page->getCropBox ();
     }
   }
@@ -1048,6 +1049,14 @@ _poppler_annot_get_cropbox (PopplerAnnot *poppler_annot)
   return nullptr;
 }
 
+/* Returns cropbox rect for the page where the passed in @poppler_annot is in,
+ * or NULL when could not retrieve the cropbox */
+const PDFRectangle *
+_poppler_annot_get_cropbox (PopplerAnnot *poppler_annot)
+{
+  return _poppler_annot_get_cropbox_and_page (poppler_annot, nullptr);
+}
+
 /**
  * poppler_annot_get_rectangle:
  * @poppler_annot: a #PopplerAnnot
@@ -1065,11 +1074,12 @@ poppler_annot_get_rectangle (PopplerAnnot     *poppler_annot,
   PDFRectangle *annot_rect;
   const PDFRectangle *crop_box;
   PDFRectangle zerobox;
+  Page *page = nullptr;
 
   g_return_if_fail (POPPLER_IS_ANNOT (poppler_annot));
   g_return_if_fail (poppler_rect != nullptr);
 
-  crop_box = _poppler_annot_get_cropbox (poppler_annot);
+  crop_box = _poppler_annot_get_cropbox_and_page (poppler_annot, &page);
   if (!crop_box) {
     zerobox = PDFRectangle();
     crop_box = &zerobox;
@@ -1098,20 +1108,33 @@ poppler_annot_set_rectangle (PopplerAnnot     *poppler_annot,
 {
   const PDFRectangle *crop_box;
   PDFRectangle zerobox;
+  double x1, y1, x2, y2;
+  Page *page = nullptr;
 
   g_return_if_fail (POPPLER_IS_ANNOT (poppler_annot));
   g_return_if_fail (poppler_rect != nullptr);
 
-  crop_box = _poppler_annot_get_cropbox (poppler_annot);
+  crop_box = _poppler_annot_get_cropbox_and_page (poppler_annot, &page);
   if (!crop_box) {
     zerobox = PDFRectangle();
     crop_box = &zerobox;
   }
 
-  poppler_annot->annot->setRect (poppler_rect->x1 + crop_box->x1,
-                                 poppler_rect->y1 + crop_box->y1,
-                                 poppler_rect->x2 + crop_box->x1,
-                                 poppler_rect->y2 + crop_box->y1 );
+  x1 = poppler_rect->x1;
+  y1 = poppler_rect->y1;
+  x2 = poppler_rect->x2;
+  y2 = poppler_rect->y2;
+
+  if (page && SUPPORTED_ROTATION (page->getRotate ())) {
+    /* annot is inside a rotated page, as core poppler rect must be saved
+     * un-rotated, let's proceed to un-rotate rect before saving */
+    _unrotate_rect_for_annot_and_page (page, poppler_annot->annot, &x1, &y1, &x2, &y2);
+  }
+
+  poppler_annot->annot->setRect (x1 + crop_box->x1,
+                                 y1 + crop_box->y1,
+                                 x2 + crop_box->x1,
+                                 y2 + crop_box->y1 );
 }
 
 /* PopplerAnnotMarkup */
@@ -1673,15 +1696,30 @@ void
 poppler_annot_text_markup_set_quadrilaterals (PopplerAnnotTextMarkup *poppler_annot,
                                               GArray                 *quadrilaterals)
 {
+  AnnotQuadrilaterals *quads, *quads_temp;
   AnnotTextMarkup *annot;
   const PDFRectangle* crop_box;
+  Page *page = nullptr;
 
   g_return_if_fail (POPPLER_IS_ANNOT_TEXT_MARKUP (poppler_annot));
   g_return_if_fail (quadrilaterals != nullptr && quadrilaterals->len > 0);
 
   annot = static_cast<AnnotTextMarkup *>(POPPLER_ANNOT (poppler_annot)->annot);
-  crop_box = _poppler_annot_get_cropbox (POPPLER_ANNOT (poppler_annot));
-  AnnotQuadrilaterals *quads = create_annot_quads_from_poppler_quads (quadrilaterals, crop_box);
+  crop_box = _poppler_annot_get_cropbox_and_page (POPPLER_ANNOT (poppler_annot), &page);
+  quads = create_annot_quads_from_poppler_quads (quadrilaterals);
+
+  if (page && SUPPORTED_ROTATION (page->getRotate ())) {
+      quads_temp = _page_new_quads_unrotated (page, quads);
+      delete quads;
+      quads = quads_temp;
+  }
+
+  if (!ZERO_CROPBOX (crop_box)) {
+    quads_temp = quads;
+    quads = new_quads_from_offset_cropbox (crop_box, quads, TRUE);
+    delete quads_temp;
+  }
+
   annot->setQuadrilaterals (quads);
   delete quads;
 }
diff --git a/glib/poppler-page.cc b/glib/poppler-page.cc
index 78edfe0c..f237bb69 100644
--- a/glib/poppler-page.cc
+++ b/glib/poppler-page.cc
@@ -33,7 +33,7 @@
 #include "poppler.h"
 #include "poppler-private.h"
 
-#define SUPPORTED_ROTATION(r) (r == 90 || r == 180 || r == 270)
+static void _page_unrotate_xy (Page *page, double *x, double *y);
 
 /**
  * SECTION:poppler-page
@@ -1532,7 +1532,7 @@ poppler_page_free_annot_mapping (GList *list)
 
 /* Adds or removes (according to @add parameter) the passed in @crop_box from the
  * passed in @quads and returns it as a new #AnnotQuadrilaterals object */
-static AnnotQuadrilaterals *
+AnnotQuadrilaterals *
 new_quads_from_offset_cropbox (const PDFRectangle* crop_box,
                                AnnotQuadrilaterals *quads,
                                gboolean add)
@@ -1558,6 +1558,119 @@ new_quads_from_offset_cropbox (const PDFRectangle* crop_box,
   return new AnnotQuadrilaterals (std::move(quads_array), len);
 }
 
+/* This function undoes the rotation of @page in the passed-in @x @y point.
+ * In other words, it moves the point to where it'll be located if @page
+ * was put to zero rotation (unrotated) */
+static void
+_page_unrotate_xy (Page   *page,
+                   double *x,
+                   double *y)
+{
+  double page_width, page_height, temp;
+  gint rotation = page->getRotate ();
+
+  if (rotation == 90 || rotation == 270) {
+    page_height = page->getCropWidth ();
+    page_width = page->getCropHeight ();
+  } else {
+    page_width = page->getCropWidth ();
+    page_height = page->getCropHeight ();
+  }
+
+  if (rotation == 90) {
+    temp = *x;
+    *x = page_height - *y;
+    *y = temp;
+  } else if (rotation == 180) {
+    *x = page_width - *x;
+    *y = page_height - *y;
+  } else if (rotation == 270) {
+    temp = *x;
+    *x = *y;
+    *y = page_width - temp;
+  }
+}
+
+AnnotQuadrilaterals *
+_page_new_quads_unrotated (Page                *page,
+                           AnnotQuadrilaterals *quads)
+{
+  double x1, y1, x2, y2, x3, y3, x4, y4;
+  int len = quads->getQuadrilateralsLength();
+  auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(len);
+
+  for (int i = 0; i < len; i++) {
+     x1 = quads->getX1(i);
+     y1 = quads->getY1(i);
+     x2 = quads->getX2(i);
+     y2 = quads->getY2(i);
+     x3 = quads->getX3(i);
+     y3 = quads->getY3(i);
+     x4 = quads->getX4(i);
+     y4 = quads->getY4(i);
+
+     _page_unrotate_xy (page, &x1, &y1);
+     _page_unrotate_xy (page, &x2, &y2);
+     _page_unrotate_xy (page, &x3, &y3);
+     _page_unrotate_xy (page, &x4, &y4);
+
+     quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral (
+                      x1, y1, x2, y2, x3, y3, x4, y4);
+  }
+
+  return new AnnotQuadrilaterals (std::move(quads_array), len);
+}
+
+/* @x1 @y1 @x2 @y2 are both 'in' and 'out' parameters, representing
+ * the diagonal of a rectangle which is the 'rect' area of @annot
+ * which is inside @page.
+ *
+ * If @page is unrotated (i.e. has zero rotation) this function does
+ * nothing, otherwise this function un-rotates the passed-in rect
+ * coords according to @page rotation so as the returned coords are
+ * those of the rect if page was put to zero rotation (unrotated).
+ * This is mandated by PDF spec when saving annotation coords (see
+ * 8.4.2 Annotation Flags) which also explains the special rotation
+ * that needs to be done when @annot has the flagNoRotate set to
+ * true, which this function follows. */
+void
+_unrotate_rect_for_annot_and_page (Page  *page,
+                                   Annot *annot,
+                                   double *x1,
+                                   double *y1,
+                                   double *x2,
+                                   double *y2)
+{
+  gboolean flag_no_rotate;
+
+  if (!SUPPORTED_ROTATION (page->getRotate ()))
+    return;
+  /* Normalize received rect diagonal to be from UpperLeft to BottomRight,
+   * as our algorithm follows that */
+  if (*y2 > *y1) {
+    double temp = *y1;
+    *y1 = *y2;
+    *y2 = temp;
+  }
+  if (G_UNLIKELY (*x1 > *x2)) {
+    double temp = *x1;
+    *x1 = *x2;
+    *x2 = temp;
+  }
+  flag_no_rotate = annot->getFlags () & Annot::flagNoRotate;
+  if (flag_no_rotate) {
+    /* For this case rotating just the upperleft point is enough */
+    double annot_height = *y1 - *y2;
+    double annot_width = *x2 - *x1;
+    _page_unrotate_xy (page, x1, y1);
+    *x2 = *x1 + annot_width;
+    *y2 = *y1 - annot_height;
+  } else {
+    _page_unrotate_xy (page, x1, y1);
+    _page_unrotate_xy (page, x2, y2);
+  }
+}
+
 /**
  * poppler_page_add_annot:
  * @page: a #PopplerPage
@@ -1572,6 +1685,7 @@ poppler_page_add_annot (PopplerPage  *page,
 			PopplerAnnot *annot)
 {
   double x1, y1, x2, y2;
+  gboolean page_is_rotated;
   const PDFRectangle *crop_box;
   const PDFRectangle *page_crop_box;
 
@@ -1581,6 +1695,14 @@ poppler_page_add_annot (PopplerPage  *page,
   /* Add the page's cropBox to the coordinates of rect field of annot */
   page_crop_box = page->page->getCropBox ();
   annot->annot->getRect(&x1, &y1, &x2, &y2);
+
+  page_is_rotated = SUPPORTED_ROTATION (page->page->getRotate ());
+  if (page_is_rotated) {
+    /* annot is inside a rotated page, as core poppler rect must be saved
+     * un-rotated, let's proceed to un-rotate rect before saving */
+    _unrotate_rect_for_annot_and_page (page->page, annot->annot, &x1, &y1, &x2, &y2);
+  }
+
   annot->annot->setRect(x1 + page_crop_box->x1,
                         y1 + page_crop_box->y1,
                         x2 + page_crop_box->x1,
@@ -1596,6 +1718,11 @@ poppler_page_add_annot (PopplerPage  *page,
       quads = new_quads_from_offset_cropbox (crop_box, annot_markup->getQuadrilaterals(), FALSE);
       annot_markup->setQuadrilaterals( quads );
     }
+    if (page_is_rotated) {
+      /* Quadrilateral's coords need to be saved un-rotated (same as rect coords) */
+      quads = _page_new_quads_unrotated (page->page, annot_markup->getQuadrilaterals());
+      annot_markup->setQuadrilaterals( quads );
+    }
     /* Add to annot's quadrilaterals the offset for the cropbox of the new page */
     quads = new_quads_from_offset_cropbox (page_crop_box, annot_markup->getQuadrilaterals(), TRUE);
     annot_markup->setQuadrilaterals( quads );
diff --git a/glib/poppler-private.h b/glib/poppler-private.h
index daa633d1..7726ec78 100644
--- a/glib/poppler-private.h
+++ b/glib/poppler-private.h
@@ -20,6 +20,8 @@
 #include <StructElement.h>
 #endif
 
+#define SUPPORTED_ROTATION(r) (r == 90 || r == 180 || r == 270)
+
 struct _PopplerDocument
 {
   /*< private >*/
@@ -116,6 +118,17 @@ GList         *_poppler_document_get_layer_rbgroup (PopplerDocument *document,
 PopplerPage   *_poppler_page_new   (PopplerDocument *document,
 				    Page            *page,
 				    int              index);
+void _unrotate_rect_for_annot_and_page (Page *page,
+					Annot *annot,
+					double *x1,
+					double *y1,
+					double *x2,
+					double *y2);
+AnnotQuadrilaterals *_page_new_quads_unrotated (Page                *page,
+						AnnotQuadrilaterals *quads);
+AnnotQuadrilaterals *new_quads_from_offset_cropbox (const PDFRectangle  *crop_box,
+						    AnnotQuadrilaterals *quads,
+						    gboolean             add);
 PopplerAction *_poppler_action_new (PopplerDocument *document,
 				    const LinkAction      *link,
 				    const gchar     *title);


More information about the poppler mailing list