0

[PDF Ink Signatures] Fix PdfInkModule invalidation areas

When CanonicalInkEnvelopeToInvalidationScreenRect() was initially added
it intentionally ignored the origin of the page content rectangle.
This was due to callers making an extra offset modification to the
returned result before actually doing the invalidation.  This omission
was a mistake, because the callers add an offset for the available area
but not for the page content origin.  This means that there could be a
shift in the resulting invalidation area that didn't account for that
origin.  This shift becomes much more noticeable once a user scrolls
the document.

Update the CanonicalPositionToScreenPosition() helper to account for
that offset, which now makes it a real inverse operation of the
EventPositionToCanonicalPosition() function.  This is because available
area offsets are also already accounted for in event positions.

This removes the need for the prior workaround function
CanonicalInkEnvelopeToExpandedInvalidationScreenRect().

Fixed: 375445386, 376301209
Change-Id: I6df4303e803ead43b1d54d503350b8ee11545ec7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6020758
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1383301}
This commit is contained in:
Alan Screen
2024-11-14 23:02:12 +00:00
committed by Chromium LUCI CQ
parent 29853a0be9
commit f483be856a
4 changed files with 116 additions and 36 deletions

@ -105,25 +105,6 @@ SkRect GetDrawPageClipRect(const gfx::Rect& content_rect,
return gfx::RectFToSkRect(clip_rect);
}
gfx::Rect CanonicalInkEnvelopeToExpandedInvalidationScreenRect(
const ink::Envelope& envelope,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
gfx::Rect rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, orientation, page_content_rect, scale_factor);
// TODO(crbug.com/376301209): The bounds for `ink::Envelope` are smaller
// than the resulting PDF paths, which can lead to a clipping artifact during
// invalidation. This workaround extends the invalidation area to ensure all
// of the stroke object is covered by the invalidation area. This workaround
// should be removed once the reason for the discrepancy has been determined
// and resolved.
constexpr float kInkBoundsExpansion = 5.0f;
rect.Outset(kInkBoundsExpansion * scale_factor);
return rect;
}
} // namespace
PdfInkModule::PdfInkModule(PdfInkModuleClient& client)
@ -543,7 +524,7 @@ bool PdfInkModule::FinishStroke(const gfx::PointF& position,
CHECK(undo_redo_success);
}
client_->Invalidate(CanonicalInkEnvelopeToExpandedInvalidationScreenRect(
client_->Invalidate(CanonicalInkEnvelopeToInvalidationScreenRect(
invalidate_envelope, client_->GetOrientation(),
client_->GetPageContentsRect(state.page_index), client_->GetZoom()));
}
@ -693,7 +674,7 @@ bool PdfInkModule::EraseHelper(const gfx::PointF& position, int page_index) {
}
// If `invalidate_envelope` isn't empty, then something got erased.
client_->Invalidate(CanonicalInkEnvelopeToExpandedInvalidationScreenRect(
client_->Invalidate(CanonicalInkEnvelopeToInvalidationScreenRect(
invalidate_envelope, client_->GetOrientation(),
client_->GetPageContentsRect(page_index), client_->GetZoom()));
return true;
@ -979,7 +960,7 @@ void PdfInkModule::ApplyUndoRedoCommandsHelper(
stroke_ids.erase(id);
}
client_->Invalidate(CanonicalInkEnvelopeToExpandedInvalidationScreenRect(
client_->Invalidate(CanonicalInkEnvelopeToInvalidationScreenRect(
invalidate_envelope, client_->GetOrientation(),
client_->GetPageContentsRect(page_index), client_->GetZoom()));
page_indices_with_thumbnail_updates.insert(page_index);
@ -1021,7 +1002,7 @@ void PdfInkModule::ApplyUndoRedoCommandsHelper(
shape_ids.erase(id);
}
client_->Invalidate(CanonicalInkEnvelopeToExpandedInvalidationScreenRect(
client_->Invalidate(CanonicalInkEnvelopeToInvalidationScreenRect(
invalidate_envelope, client_->GetOrientation(),
client_->GetPageContentsRect(page_index), client_->GetZoom()));
page_indices_with_thumbnail_updates.insert(page_index);

@ -992,7 +992,7 @@ TEST_F(PdfInkModuleStrokeTest, CanonicalAnnotationPoints) {
kCanonicalMouseUpPosition}})));
}
TEST_F(PdfInkModuleStrokeTest, InvalidationsFromStroke) {
TEST_F(PdfInkModuleStrokeTest, BasicLayoutInvalidationsFromStroke) {
InitializeSimpleSinglePageBasicLayout();
RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
@ -1003,7 +1003,36 @@ TEST_F(PdfInkModuleStrokeTest, InvalidationsFromStroke) {
gfx::Size(14.0f, 14.0f));
const gfx::Rect kInvalidationAreaMouseUp(gfx::Point(18.0f, 15.0f),
gfx::Size(14.0f, 12.0f));
const gfx::Rect kInvalidationAreaFinishedStroke(3.0f, 8.0f, 35.0f, 17.0f);
const gfx::Rect kInvalidationAreaFinishedStroke(8.0f, 13.0f, 25.0f, 7.0f);
EXPECT_THAT(
client().invalidations(),
ElementsAre(kInvalidationAreaMouseDown, kInvalidationAreaMouseMove,
kInvalidationAreaMouseUp, kInvalidationAreaFinishedStroke));
}
TEST_F(PdfInkModuleStrokeTest, TransformedLayoutInvalidationsFromStroke) {
// Setup to support examining the invalidation areas from page stroke points
// for a layout that is more complicated than what is provide by
// `InitializeSimpleSinglePageBasicLayout()`. Include viewport offset,
// scroll, rotation, and zoom.
constexpr gfx::SizeF kPageSize(100.0f, 120.0f);
constexpr gfx::PointF kPageOrigin(5.0f, -15.0f);
constexpr gfx::RectF kPageLayout(kPageOrigin, kPageSize);
client().set_page_layouts(base::span_from_ref(kPageLayout));
client().set_page_visibility(0, true);
client().set_orientation(PageOrientation::kClockwise180);
client().set_zoom(2.0f);
RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
// The default brush param size is 3.0.
const gfx::Rect kInvalidationAreaMouseDown(gfx::Point(8.0f, 13.0f),
gfx::Size(4.0f, 4.0f));
const gfx::Rect kInvalidationAreaMouseMove(gfx::Point(8.0f, 13.0f),
gfx::Size(14.0f, 14.0f));
const gfx::Rect kInvalidationAreaMouseUp(gfx::Point(18.0f, 15.0f),
gfx::Size(14.0f, 12.0f));
const gfx::Rect kInvalidationAreaFinishedStroke(7.0f, 12.0f, 27.0f, 9.0f);
EXPECT_THAT(
client().invalidations(),
ElementsAre(kInvalidationAreaMouseDown, kInvalidationAreaMouseMove,
@ -1493,8 +1522,8 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoInvalidationsBasic) {
// This size is smaller than the area of the merged invalidation constants
// above because InkStrokeModeler modeled the "V" shaped input into an input
// with a much gentler line slope.
const gfx::Rect kInvalidationAreaEntireStroke(gfx::Point(3.0f, 8.0f),
gfx::Size(35.0f, 17.0f));
const gfx::Rect kInvalidationAreaEntireStroke(gfx::Point(8.0f, 13.0f),
gfx::Size(25.0f, 7.0f));
EXPECT_THAT(
client().invalidations(),
ElementsAre(kInvalidationAreaMouseDown, kInvalidationAreaMouseMove,
@ -1533,8 +1562,8 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoInvalidationsScaledRotated90) {
// This size is smaller than the area of the merged invalidation constants
// above because InkStrokeModeler modeled the "V" shaped input into an input
// with a much gentler line slope.
const gfx::Rect kInvalidationAreaEntireStroke(gfx::Point(-3.0f, 2.0f),
gfx::Size(47.0f, 29.0f));
const gfx::Rect kInvalidationAreaEntireStroke(gfx::Point(7.0f, 12.0f),
gfx::Size(27.0f, 9.0f));
EXPECT_THAT(
client().invalidations(),
ElementsAre(kInvalidationAreaMouseDown, kInvalidationAreaMouseMove,

@ -22,12 +22,11 @@ namespace chrome_pdf {
namespace {
// Performs what is mostly an inverse operation of
// `EventPositionToCanonicalPosition()`, to convert from canonical coordinates
// to screen coordinates. This is not a strict inverse because it
// intentionally does not adjust for `page_content_rect` offset from origin.
// This is due to its usage for invalidation calculations, where clients account
// for that offset separately.
// Performs an inverse operation of `EventPositionToCanonicalPosition()`, to
// convert from canonical coordinates to screen coordinates.
// TODO(crbug.com/379003898): Change EventPositionToCanonicalPosition() to
// return gfx::AxisTransform2d, so that callers can just use the inverse of
// the transform instead of this helper.
gfx::PointF CanonicalPositionToScreenPosition(
const gfx::PointF& canonical_position,
PageOrientation orientation,
@ -57,6 +56,8 @@ gfx::PointF CanonicalPositionToScreenPosition(
page_content_rect.height() - screen_position.x() - 1);
break;
}
// Account for scrolling, which is in the page content's origin.
screen_position += page_content_rect.origin().OffsetFromOrigin();
return screen_position;
}

@ -36,6 +36,9 @@ constexpr gfx::Size kPageSizeLandscape2x(kPageSizeLandscape.width() * 2,
constexpr float kScaleFactor1x = 1.0f;
constexpr float kScaleFactor2x = 2.0f;
// Non-identity page scroll used in tests.
constexpr gfx::Point kPageScrollOffset(15, 25);
// Standard page content area for tests.
constexpr gfx::Rect kPageContentAreaPortraitNoOffset(gfx::Point(),
kPageSizePortrait);
@ -308,7 +311,7 @@ TEST(PdfInkTransformTest,
ink::Envelope envelope(InkPointFromGfxPoint({20.0f, 35.0f}));
envelope.Add(InkPointFromGfxPoint({40.0f, 45.0f}));
// Invalidation rectangle for `envelope` should result in save value as
// Invalidation rectangle for `envelope` should result in same value as
// input.
gfx::Rect screen_rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, PageOrientation::kOriginal, kPageContentRect, kScaleFactor1x);
@ -415,6 +418,72 @@ TEST(PdfInkTransformTest,
}
}
TEST(PdfInkTransformTest,
CanonicalInkEnvelopeToInvalidationScreenRectScrolled) {
// Representation of page contents in screen coordinates without scale or
// rotation, but with a scroll.
constexpr gfx::Rect kPageContentRect(kPageScrollOffset, kPageSizePortrait);
{
// Envelope that covers the entire page contents, in canonical coordinates.
ink::Envelope envelope(InkPointFromGfxPoint(kCanonicalPositionTopLeft));
envelope.Add(InkPointFromGfxPoint(kCanonicalPositionBottomRight));
// Invalidation rectangle for `envelope` should result in the same value as
// the entire page contents.
gfx::Rect screen_rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, PageOrientation::kOriginal, kPageContentRect, kScaleFactor1x);
EXPECT_EQ(screen_rect, kPageContentRect);
}
{
// Envelope that covers a portion of page contents, in canonical
// coordinates.
ink::Envelope envelope(InkPointFromGfxPoint({20.0f, 35.0f}));
envelope.Add(InkPointFromGfxPoint({40.0f, 45.0f}));
// Invalidation rectangle for `envelope` should result in same value as
// input but shifted for the scroll amount.
gfx::Rect screen_rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, PageOrientation::kOriginal, kPageContentRect, kScaleFactor1x);
EXPECT_EQ(screen_rect, gfx::Rect(35.0f, 60.0f, 21.0f, 11.0f));
}
}
TEST(PdfInkTransformTest,
CanonicalInkEnvelopeToInvalidationScreenRectScrolledScaledAndRotated) {
// Scaled and rotated representation of page contents in screen coordinates.
constexpr gfx::Rect kPageContentRect(kPageScrollOffset, kPageSizeLandscape2x);
{
// Envelope that covers the entire page contents, in canonical coordinates.
ink::Envelope envelope(InkPointFromGfxPoint(kCanonicalPositionTopLeft));
envelope.Add(InkPointFromGfxPoint(kCanonicalPositionBottomRight +
kCanonicalPositionHalf));
// Invalidation rectangle for `envelope` should be scaled and rotated,
// resulting in the same value as the entire page contents.
gfx::Rect screen_rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, PageOrientation::kClockwise90, kPageContentRect,
kScaleFactor2x);
EXPECT_EQ(screen_rect, kPageContentRect);
}
{
// Envelope that covers a portion of page contents, in canonical
// coordinates.
ink::Envelope envelope(InkPointFromGfxPoint({20.0f, 35.0f}));
envelope.Add(InkPointFromGfxPoint({40.0f, 45.0f}));
// Invalidation rectangle for `envelope` should be scaled and rotated, and
// shifted for the scroll amount.
gfx::Rect screen_rect = CanonicalInkEnvelopeToInvalidationScreenRect(
envelope, PageOrientation::kClockwise90, kPageContentRect,
kScaleFactor2x);
EXPECT_EQ(screen_rect, gfx::Rect(44.0f, 65.0f, 21.0f, 41.0f));
}
}
TEST(PdfInkTransformTest, GetCanonicalToPdfTransform) {
{
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(/*page_height=*/0);