0

[PDF Ink Signatures] Fix stroke shift from crop box offset

If a page has an optional CropBox which has a different offset compared
to its required MediaBox, then this can impact the positioning for Ink
strokes applied to the page.

Update the transform generation used when applying Ink strokes into the
PDF page to account for any offset from the intersection of these two
boxes.

Fixed: 402043701
Change-Id: I4126dc52ddf3533580a94d2f186a46b043f0985b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6346007
Commit-Queue: Alan Screen <awscreen@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Andy Phan <andyphan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1432426}
This commit is contained in:
Alan Screen
2025-03-13 15:53:29 -07:00
committed by Chromium LUCI CQ
parent 918bb8e03a
commit 3e2ce84c84
9 changed files with 49 additions and 14 deletions

@ -200,12 +200,14 @@ gfx::Rect CanonicalInkEnvelopeToInvalidationScreenRect(
return gfx::ToEnclosingRect(gfx::RectF(x, y, w, h));
}
gfx::AxisTransform2d GetCanonicalToPdfTransform(float page_height) {
gfx::AxisTransform2d GetCanonicalToPdfTransform(
float page_height,
const gfx::Vector2dF& translate) {
CHECK_GE(page_height, 0);
return gfx::AxisTransform2d::FromScaleAndTranslation(
{kUnitConversionFactorPixelsToPoints,
-kUnitConversionFactorPixelsToPoints},
{0, page_height});
{translate.x(), page_height + translate.y()});
}
} // namespace chrome_pdf

@ -131,13 +131,17 @@ gfx::Rect CanonicalInkEnvelopeToInvalidationScreenRect(
// Returns a transform that converts from canonical coordinates (which has a
// top-left origin and a different DPI), to PDF coordinates (which has a
// bottom-left origin).
// bottom-left origin). The translation accounts for any difference from the
// defined physical page size to the cropped, visible portion of the PDF page.
//
// `page_height` is in points. It must not be negative.
// `translate` is in points.
//
// Note that callers can call gfx::AxisTransform2d::Invert() to get a transform
// that does conversions in the opposite direction.
gfx::AxisTransform2d GetCanonicalToPdfTransform(float page_height);
gfx::AxisTransform2d GetCanonicalToPdfTransform(
float page_height,
const gfx::Vector2dF& translate);
} // namespace chrome_pdf

@ -662,15 +662,29 @@ TEST(PdfInkTransformTest,
TEST(PdfInkTransformTest, GetCanonicalToPdfTransform) {
{
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(/*page_height=*/0);
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(
/*page_height=*/0, /*translate=*/gfx::Vector2dF());
EXPECT_EQ(gfx::Vector2dF(0.75f, -0.75f), tr.scale());
EXPECT_EQ(gfx::Vector2dF(0, 0), tr.translation());
}
{
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(/*page_height=*/712);
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(
/*page_height=*/712, /*translate=*/gfx::Vector2dF());
EXPECT_EQ(gfx::Vector2dF(0.75f, -0.75f), tr.scale());
EXPECT_EQ(gfx::Vector2dF(0, 712), tr.translation());
}
{
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(
/*page_height=*/0, /*translate=*/gfx::Vector2dF(50, 60));
EXPECT_EQ(gfx::Vector2dF(0.75f, -0.75f), tr.scale());
EXPECT_EQ(gfx::Vector2dF(50, 60), tr.translation());
}
{
gfx::AxisTransform2d tr = GetCanonicalToPdfTransform(
/*page_height=*/1008, /*translate=*/gfx::Vector2dF(50, 60));
EXPECT_EQ(gfx::Vector2dF(0.75f, -0.75f), tr.scale());
EXPECT_EQ(gfx::Vector2dF(50, 1068), tr.translation());
}
}
} // namespace chrome_pdf

@ -15,6 +15,7 @@
#include "pdf/pdf_ink_constants.h"
#include "pdf/pdf_ink_conversions.h"
#include "pdf/pdf_ink_transform.h"
#include "pdf/pdf_transform.h"
#include "pdf/pdfium/pdfium_api_wrappers.h"
#include "printing/units.h"
#include "third_party/ink/src/ink/geometry/mesh.h"
@ -171,8 +172,15 @@ std::vector<ReadV2InkPathResult> ReadV2InkPathsFromPageAsModeledShapes(
return results;
}
// Get the intersection between the page's MediaBox and CropBox, to find
// the translation offset for the shapes' transform.
FS_RECTF bounding_box;
auto result = FPDF_GetPageBoundingBox(page, &bounding_box);
CHECK(result);
const gfx::Vector2dF offset(bounding_box.left, bounding_box.bottom);
gfx::AxisTransform2d transform =
GetCanonicalToPdfTransform(FPDF_GetPageHeightF(page));
GetCanonicalToPdfTransform(FPDF_GetPageHeightF(page), offset);
transform.Invert();
const int page_object_count = FPDFPage_CountObjects(page);

@ -14,6 +14,7 @@
#include "pdf/pdf_ink_constants.h"
#include "pdf/pdf_ink_conversions.h"
#include "pdf/pdf_ink_transform.h"
#include "pdf/pdf_transform.h"
#include "third_party/ink/src/ink/brush/brush_coat.h"
#include "third_party/ink/src/ink/brush/brush_tip.h"
#include "third_party/ink/src/ink/geometry/mesh.h"
@ -123,8 +124,15 @@ std::vector<ScopedFPDFPageObject> WriteShapeToNewPathsOnPage(
FPDF_PAGE page) {
CHECK(page);
// Get the intersection between the page's MediaBox and CropBox, to find
// the translation offset for the shape's transform.
FS_RECTF bounding_box;
auto result = FPDF_GetPageBoundingBox(page, &bounding_box);
CHECK(result);
const gfx::Vector2dF offset(bounding_box.left, bounding_box.bottom);
const gfx::AxisTransform2d transform =
GetCanonicalToPdfTransform(FPDF_GetPageHeightF(page));
GetCanonicalToPdfTransform(FPDF_GetPageHeightF(page), offset);
std::vector<ScopedFPDFPageObject> results;
ModeledShapeOutlinesIterator it(shape);

@ -193,8 +193,6 @@ TEST_P(PDFiumInkWriterTest, WriteToCroppedPage) {
std::vector<uint8_t> saved_pdf_data = engine->GetSaveData();
ASSERT_TRUE(!saved_pdf_data.empty());
// TODO(crbug.com/402043701): The cropped image is incorrect since the
// stroke shows up at (37,35) instead of at (92,35).
base::FilePath expectation_path = GetInkTestDataFilePath(
GetTestDataPathWithPlatformSuffix("ink_writer_cropped.png"));
CheckPdfRendering(saved_pdf_data, /*page_index=*/0, gfx::Size(145, 97),
@ -226,12 +224,13 @@ TEST_P(PDFiumInkWriterTest, WriteToCroppedPage) {
bool get_bounds = FPDFPageObj_GetBounds(saved_results[0].page_object, &left,
&bottom, &right, &top);
ASSERT_TRUE(get_bounds);
// TODO(crbug.com/402043701): The object's position in the PDF page is
// relative to the MediaBox, not the CropBox. So its bounding box should be
// 55 pixels to the right of 92.3787, at 147.3787.
// While the cropped image shows the stroke on the visible page at an X coord
// of 92, that object's position in the PDF page is relative to the MediaBox,
// not the CropBox. So its bounding box should be 55 points to the right of
// that.
EXPECT_RECTF_NEAR(gfx::RectF(gfx::PointF(left, bottom),
gfx::SizeF(right - left, top - bottom)),
gfx::RectF(gfx::PointF(92.3787f, 49.5206f),
gfx::RectF(gfx::PointF(147.3787f, 49.5206f),
gfx::SizeF(12.5402f, 11.6486f)),
/*abs_error=*/0.0001f);
}

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 1019 B

After

(image error) Size: 1023 B