Bug 105692 - PDF compliance: Always respect Annot Rect
Summary: PDF compliance: Always respect Annot Rect
Status: RESOLVED MOVED
Alias: None
Product: poppler
Classification: Unclassified
Component: general (show other bugs)
Version: unspecified
Hardware: Other All
: medium normal
Assignee: poppler-bugs
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-03-22 14:13 UTC by Tobias Deiminger
Modified: 2018-08-21 10:48 UTC (History)
2 users (show)

See Also:
i915 platform:
i915 features:


Attachments
Apply template method design pattern to Annot::draw (58.66 KB, patch)
2018-03-23 16:10 UTC, Tobias Deiminger
Details | Splinter Review
Apply template method design pattern to Annot::draw (Rev 2) (63.03 KB, patch)
2018-03-24 15:13 UTC, Tobias Deiminger
Details | Splinter Review
Always respect Annot Rect for AnnotText icons (4.09 KB, patch)
2018-03-24 16:06 UTC, Tobias Deiminger
Details | Splinter Review
Apply template method design pattern to Annot::draw (Rev 3) (63.31 KB, patch)
2018-04-05 19:30 UTC, Tobias Deiminger
Details | Splinter Review
Apply template method design pattern to Annot::draw (Rev 4) (63.18 KB, patch)
2018-04-05 21:30 UTC, Tobias Deiminger
Details | Splinter Review
Alternate all-in-one patch (80.85 KB, patch)
2018-04-06 20:58 UTC, Tobias Deiminger
Details | Splinter Review

Description Tobias Deiminger 2018-03-22 14:13:07 UTC
To be compliant to ISO 32k, we always have to respect the geometry of Annot /Rect. See [0]. Currently there are cases where poppler draws annotations at another size. Namely this happens when poppler generates appearances at runtime to substitute missing AP entries for AnnotText, AnnotLine, AnnotTextMarkup, AnnotPolygon and AnnotInk (e.g. [1], [2]).

For example, you can end up with a popup note drawn as 24 x 24 pts even if Annot Rect says 16 x 16 pts. Poppler does not update Annot Rect, it keeps telling 16 x 16. Therefore the new size is also not reflected in public APIs. So the bug is not only about PDF non-conformance, but it also leaves clients unaware of the actually rendered size [4].

ISO 32k contains a detailed description about how to fit appearance streams into Annot Rect. See "Algorithm: Appearance streams" from ISO 32000-1:2008, 12.5.5. To sum up:
a) make a quadrilateral from AP BBox by applying apperances transformation Matrix, and calculate a temporary transformed AP BBox that encompasses the quadrilateral
b) calculate a temporary Matrix A that scales the transformed AP BBox into Annot Rect
c) use this Matrix A to draw the appearance

The existing method Gfx::drawAnnot nicely implements this algorithm. We just have to call Gfx::drawAnnot with the original Annot Rect size instead passing other values.

Patches will follow, comments are appreciated in advance.

[0] https://lists.freedesktop.org/archives/poppler/2018-March/012909.html
[1] https://cgit.freedesktop.org/poppler/poppler/tree/poppler/Annot.cc?h=poppler-0.63.0#n2486
[2] https://cgit.freedesktop.org/poppler/poppler/tree/poppler/Annot.cc?h=poppler-0.63.0#n5890
[3] https://cgit.freedesktop.org/poppler/poppler/tree/poppler/Gfx.cc?h=poppler-0.63.0#n5125
[4] https://bugs.kde.org/show_bug.cgi?id=388458
Comment 1 Tobias Deiminger 2018-03-22 14:16:01 UTC
The behavior was added by Fabio D'Urso - Fabio, are you still around (or does anybody know)? It would be great to learn about your intents regarding the commits below:

Commit 7684c325, "AnnotText: Always force 24x24 size with custom stamps, not only on first rendering"

Commit c6296cd8, "Do not trust the rect of AnnotLine, AnnotPolygon and AnnotInk when drawing"

Commit 3d4985f1, "Do not trust the rect of AnnotTextMarkup when drawing"
Comment 2 Tobias Deiminger 2018-03-23 16:10:09 UTC
Created attachment 138316 [details] [review]
Apply template method design pattern to Annot::draw

Annot::draw becomes a template method. Goal is to make the code more schematic and understandable, decrease redundancy and gain places where we can hook in future changes. I loosely followed the GoF description.

Note: This patch doesn't change any public behavior. It just prepares the ground for other things I plan to do. If you like it, you can use it standalone, no matter what happens to the bug here in #105692. I'll attach separate patches later that actually deal with the bug.
Comment 3 Tobias Deiminger 2018-03-24 15:13:29 UTC
Created attachment 138340 [details] [review]
Apply template method design pattern to Annot::draw (Rev 2)

Rebase on master (5e7aef9). And, no need to have public template primitives, make them private/protected.

Btw., is this the correct way to handle continuous rebasing or other minor changes for a patch?
Comment 4 Tobias Deiminger 2018-03-24 16:06:58 UTC
Created attachment 138341 [details] [review]
Always respect Annot Rect for AnnotText icons

(2nd part of patch series - based on https://bugs.freedesktop.org/attachment.cgi?id=138340)

Instead of forcing 24x24, always use the size of Annot Rect when drawing icons in user space (even on first rendering - to rephrase Fabios commit message from 7684c325). It fixes the most important part of #105692, imho.

For our hardcoded icons we only need 24x24 while we're in appearance XObject coordinate system. For user space, Gfx::drawAnnot does the transformation (translate + scale) to whatever size and position is in Annot Rect.

Note: For the same reason the AP BBox can start at (0,0). We don't need an offset. I changed the AP BBox to 0, 0, 24, 24. That's simpler, and other embedded appearance streams I've seen from other PDF writers do it that way, too.
Comment 5 Albert Astals Cid 2018-03-26 20:14:00 UTC
Is this ready to get reviewed or are we still waiting on Leonard's answer on the question you made on the mailing list?
Comment 6 Tobias Deiminger 2018-03-26 22:35:52 UTC
Patch 1 "Apply template method design pattern to Annot::draw" is ready.

For Patches 2..x (some will follow) I'd say wait a bit.

There seem to exist alternate solutions. One is to always respect the original Rect, as it's done by the 2nd patch. Another option would be to temporary reset Rect to whatever poppler likes, but then propagate the new size to clients in QT Annotatin::boundary() and glib poppler_annot_get_rectangle(). Leonard will tell which options are PDF compliant. And we have to judge what's best from clients perspective. Hopefully guys from evince or other projects will comment here too.
Comment 7 Albert Astals Cid 2018-04-04 08:10:43 UTC
So you removed

-void AnnotLink::draw(Gfx *gfx, GBool printing) {
-  if (!isVisible (printing))
-    return;
-
-  annotLocker();
-  // draw the appearance stream
-  Object obj = appearance.fetch(gfx->getXRef());
-  gfx->drawAnnot(&obj, border, color,
-		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
-}


meaning it'll use Annot::draw which ignores the border.

That seems like a bug? Or am i reading it wrong?
Comment 8 Tobias Deiminger 2018-04-04 08:49:41 UTC
> meaning it'll use Annot::draw which ignores the border.
> 
> That seems like a bug? Or am i reading it wrong?

Indeed, good catch! I'll provide an override AnnotLink::drawAppearance, which can care for the border.
Comment 9 Tobias Deiminger 2018-04-05 19:30:54 UTC
Created attachment 138638 [details] [review]
Apply template method design pattern to Annot::draw (Rev 3)

Don't forget border when drawing AnnotLink. Rebase on master 0a79dc3.
Comment 10 Albert Astals Cid 2018-04-05 20:29:07 UTC
Can you please rebase it again?

Sorry ^_^
Comment 11 Tobias Deiminger 2018-04-05 21:30:27 UTC
Created attachment 138645 [details] [review]
Apply template method design pattern to Annot::draw (Rev 4)

Continuous rebasing ¯\_(ツ)_/¯ I like that AnnotAppearanceBuilder...
Comment 12 Albert Astals Cid 2018-04-05 22:19:08 UTC
(In reply to Tobias Deiminger from comment #11)
> Created attachment 138645 [details] [review] [review]
> Apply template method design pattern to Annot::draw (Rev 4)
> 
> Continuous rebasing ¯\_(ツ)_/¯ I like that AnnotAppearanceBuilder...

I'm a bit conflicted with this patch.

I see it adds some structure, that is always nice, on the other hand it doesn't really save that many lines as shown by the base implementations (that is what we end up reusing) are basically one line each.

Also the new structure makes you add the fontResourceDict which is a bit harder to manage than before since now it spans two methods instead of one.

On the other hand now we don't create the fonts dict on every draw that is a good thing too (not that it's a hot path but all speedups are good)

Let me sleep over it :D

Comments either from you or from anyone else that add pros for this patch are also welcome :)
Comment 13 Tobias Deiminger 2018-04-06 05:20:36 UTC
(In reply to Albert Astals Cid from comment #12)
> Comments either from you or from anyone else that add pros for this patch
> are also welcome :)
The size fix (not submitted yet) for the remaining types like AnnotLine and AnnotPolygonfrom will probably look something like this:
 void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const {
-  *x1 = rect->x1;
-  *y1 = rect->y1;
-  *x2 = rect->x2;
-  *y2 = rect->y2;
+  PDFRectangle drawRect = getDrawRect();
+  *x1 = drawRect.x1;
+  *y1 = drawRect.y1;
+  *x2 = drawRect.x2;
+  *y2 = drawRect.y2;
 }

I.e. always report the actually drawn size instead of the size stored in /Rect, because it can differ in corner cases and the drawn size is what's more important to API clients. That measure is only possible if there exists that getDrawRect method from attachment 138645 [details] [review]. That would be a (however still fictional) pro from my point of view.
Comment 14 Albert Astals Cid 2018-04-06 10:04:29 UTC
(In reply to Tobias Deiminger from comment #13)
> (In reply to Albert Astals Cid from comment #12)
> > Comments either from you or from anyone else that add pros for this patch
> > are also welcome :)
> The size fix (not submitted yet) for the remaining types like AnnotLine and
> AnnotPolygonfrom will probably look something like this:
>  void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const {
> -  *x1 = rect->x1;
> -  *y1 = rect->y1;
> -  *x2 = rect->x2;
> -  *y2 = rect->y2;
> +  PDFRectangle drawRect = getDrawRect();
> +  *x1 = drawRect.x1;
> +  *y1 = drawRect.y1;
> +  *x2 = drawRect.x2;
> +  *y2 = drawRect.y2;
>  }
> 
> I.e. always report the actually drawn size instead of the size stored in
> /Rect, because it can differ in corner cases and the drawn size is what's
> more important to API clients. That measure is only possible if there exists
> that getDrawRect method from attachment 138645 [details] [review] [review]. That
> would be a (however still fictional) pro from my point of view.

Thinking about this, i really think if we're going to go that route, it's better to kill appearBBox as a member, just use it to when we're creating the new appearance and then adjust the rect member.

I don't like having the getrect function not really returning the rect member, i've looked at the code and seems we really use rect for drawing and geometrical stuff (i.e. inRect) so it should be fine updating it in my opinion.
Comment 15 Tobias Deiminger 2018-04-06 12:19:22 UTC
(In reply to Albert Astals Cid from comment #14)
> Thinking about this, i really think if we're going to go that route, it's
> better to kill appearBBox as a member, just use it to when we're creating
> the new appearance and then adjust the rect member.
Yeah, I thought about that already. I hesitated a bit, maybe you can help. It's this: We can't use Annot::setRect to adjust the rect member, because this (a) also creates an adjusted annotObj["Rect"] Object, and (b) calls invalidateAppearance.
(a) would cause the size change to be saved into the document which I think would be an unexpected side effect
(b) would invalidate our just created appearance, so we never get an valid appearance

If we instead bypass Annot::setRect on appearance creation and update Annot::rect directly, it could work. But I have not yet checked for side effects of Annot::rect != Annot::annotObj["Rect"]. If there's no side effect then we should be able to do what you suggested and discard both the Annot::appearBBox member and the Annot::getDrawRect method and as a bonus things are simplified a bit.

> I don't like having the getrect function not really returning the rect
> member, i've looked at the code and seems we really use rect for drawing and
> geometrical stuff (i.e. inRect) so it should be fine updating it in my
> opinion.
In places such as inRect we would have to use getDrawRect() instead of Annot::rect too, to be consistent. But if your suggestion above works, we don't have to bother anyway.
Comment 16 Tobias Deiminger 2018-04-06 20:58:40 UTC
Created attachment 138666 [details] [review]
Alternate all-in-one patch

This patch tries what was discussed in comment 14 and comment 15:
- Get rid of Annot::appearBBox member, local AnnotAppearanceBBox'es are enough.
- Get rid of Annot::getDrawRect again, no longer needed because Annot::rect always equals draw size.
- Keep the template pattern approach (just my own decision atm)
- Fix AnnotLine, AnnotTextMarkup, AnnotPolygon, AnnotInk by adjusting Annot::rect (but not annotObj["Rect"]!) to generated appearance. See also [0].
- Fix AnnotText by scaling default appearance (24x24) to size of annotObj["Rect"].

I did some first tests with Okular and it worked as expected. Albert, what do you think, can we proceed this way?

Based on master a8e93f4.

[0] If I got it right, Acrobat/Reader handles the case the same. See https://lists.freedesktop.org/archives/poppler/2012-April/008956.html ("[..] generate a new bounding box that incorporates the entire line as that is what one would expect [...]")
Comment 17 GitLab Migration User 2018-08-21 10:48:12 UTC
-- GitLab Migration Automatic Message --

This bug has been migrated to freedesktop.org's GitLab instance and has been closed from further activity.

You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.freedesktop.org/poppler/poppler/issues/379.


Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.