Add experimental FPDFPageObj_GetRotatedBounds() API. This new API allows callers to get a closer bound for rotated objects, compared to FPDFPageObj_GetBounds(). Also fix some existing IWYU issues along the way. Bug: pdfium:1840 Change-Id: Iba5b12b8ca0094682c895e9db70840aaea84f0f0 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/94470 Reviewed-by: Nigi <nigi@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_imageobject.cpp b/core/fpdfapi/page/cpdf_imageobject.cpp index 278fb84..4d99a89 100644 --- a/core/fpdfapi/page/cpdf_imageobject.cpp +++ b/core/fpdfapi/page/cpdf_imageobject.cpp
@@ -9,6 +9,7 @@ #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/page/cpdf_image.h" #include "core/fpdfapi/parser/cpdf_stream.h" +#include "core/fxcrt/fx_coordinates.h" #include "core/fxge/dib/cfx_dibbase.h" #include "core/fxge/dib/cfx_dibitmap.h" @@ -45,6 +46,7 @@ void CPDF_ImageObject::CalcBoundingBox() { static constexpr CFX_FloatRect kRect(0.0f, 0.0f, 1.0f, 1.0f); + SetOriginalRect(kRect); SetRect(m_Matrix.TransformRect(kRect)); }
diff --git a/core/fpdfapi/page/cpdf_pageobject.cpp b/core/fpdfapi/page/cpdf_pageobject.cpp index bde1704..4ce2504 100644 --- a/core/fpdfapi/page/cpdf_pageobject.cpp +++ b/core/fpdfapi/page/cpdf_pageobject.cpp
@@ -6,6 +6,8 @@ #include "core/fpdfapi/page/cpdf_pageobject.h" +#include "core/fxcrt/fx_coordinates.h" + CPDF_PageObject::CPDF_PageObject(int32_t content_stream) : m_ContentStream(content_stream) {}
diff --git a/core/fpdfapi/page/cpdf_pageobject.h b/core/fpdfapi/page/cpdf_pageobject.h index 60626af..b88be0c 100644 --- a/core/fpdfapi/page/cpdf_pageobject.h +++ b/core/fpdfapi/page/cpdf_pageobject.h
@@ -60,6 +60,8 @@ void TransformClipPath(const CFX_Matrix& matrix); void TransformGeneralState(const CFX_Matrix& matrix); + void SetOriginalRect(const CFX_FloatRect& rect) { m_OriginalRect = rect; } + const CFX_FloatRect& GetOriginalRect() const { return m_OriginalRect; } void SetRect(const CFX_FloatRect& rect) { m_Rect = rect; } const CFX_FloatRect& GetRect() const { return m_Rect; } FX_RECT GetBBox() const; @@ -89,6 +91,7 @@ CFX_FloatRect m_Rect; private: + CFX_FloatRect m_OriginalRect; CPDF_ContentMarks m_ContentMarks; bool m_bDirty = false; int32_t m_ContentStream;
diff --git a/core/fpdfapi/page/cpdf_textobject.cpp b/core/fpdfapi/page/cpdf_textobject.cpp index 33e1b0b..79e9300 100644 --- a/core/fpdfapi/page/cpdf_textobject.cpp +++ b/core/fpdfapi/page/cpdf_textobject.cpp
@@ -10,6 +10,7 @@ #include "core/fpdfapi/font/cpdf_cidfont.h" #include "core/fpdfapi/font/cpdf_font.h" +#include "core/fxcrt/fx_coordinates.h" #include "third_party/base/check.h" #include "third_party/base/span.h" @@ -325,9 +326,11 @@ max_y = max_y * fontsize / 1000; } - CFX_FloatRect rect = - GetTextMatrix().TransformRect(CFX_FloatRect(min_x, min_y, max_x, max_y)); + SetOriginalRect(CFX_FloatRect(min_x, min_y, max_x, max_y)); + CFX_FloatRect rect = GetTextMatrix().TransformRect(GetOriginalRect()); if (TextRenderingModeIsStrokeMode(m_TextState.GetTextMode())) { + // TODO(crbug.com/pdfium/1840): Does the original rect need a similar + // adjustment? const float half_width = m_GraphState.GetLineWidth() / 2; rect.Inflate(half_width, half_width); }
diff --git a/core/fpdfapi/page/cpdf_textobject.h b/core/fpdfapi/page/cpdf_textobject.h index d52aad7..5e7adfc 100644 --- a/core/fpdfapi/page/cpdf_textobject.h +++ b/core/fpdfapi/page/cpdf_textobject.h
@@ -14,6 +14,7 @@ #include <vector> #include "core/fpdfapi/page/cpdf_pageobject.h" +#include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/fx_string.h" #include "core/fxcrt/retain_ptr.h"
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp index 8a2d269..985823a 100644 --- a/fpdfsdk/fpdf_editpage.cpp +++ b/fpdfsdk/fpdf_editpage.cpp
@@ -786,6 +786,44 @@ } FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, + FS_QUADPOINTSF* quad_points) { + CPDF_PageObject* cpage_object = CPDFPageObjectFromFPDFPageObject(page_object); + if (!cpage_object || !quad_points) + return false; + + CFX_Matrix matrix; + switch (cpage_object->GetType()) { + case CPDF_PageObject::Type::kText: + matrix = cpage_object->AsText()->GetTextMatrix(); + break; + case CPDF_PageObject::Type::kImage: + matrix = cpage_object->AsImage()->matrix(); + break; + default: + // TODO(crbug.com/pdfium/1840): Support more object types. + return false; + } + + const CFX_FloatRect& bbox = cpage_object->GetOriginalRect(); + const CFX_PointF bottom_left = matrix.Transform({bbox.left, bbox.bottom}); + const CFX_PointF bottom_right = matrix.Transform({bbox.right, bbox.bottom}); + const CFX_PointF top_right = matrix.Transform({bbox.right, bbox.top}); + const CFX_PointF top_left = matrix.Transform({bbox.left, bbox.top}); + + // See PDF 32000-1:2008, figure 64 for the QuadPoints ordering. + quad_points->x1 = bottom_left.x; + quad_points->y1 = bottom_left.y; + quad_points->x2 = bottom_right.x; + quad_points->y2 = bottom_right.y; + quad_points->x3 = top_right.x; + quad_points->y3 = top_right.y; + quad_points->x4 = top_left.x; + quad_points->y4 = top_left.y; + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPageObj_SetStrokeColor(FPDF_PAGEOBJECT page_object, unsigned int R, unsigned int G,
diff --git a/fpdfsdk/fpdf_editpage_embeddertest.cpp b/fpdfsdk/fpdf_editpage_embeddertest.cpp index 2af4e54..fe2719b 100644 --- a/fpdfsdk/fpdf_editpage_embeddertest.cpp +++ b/fpdfsdk/fpdf_editpage_embeddertest.cpp
@@ -292,3 +292,167 @@ UnloadPage(page); } + +TEST_F(FPDFEditPageEmbedderTest, GetRotatedBoundsBadParameters) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(obj)); + + FS_QUADPOINTSF quad; + ASSERT_FALSE(FPDFPageObj_GetRotatedBounds(nullptr, nullptr)); + ASSERT_FALSE(FPDFPageObj_GetRotatedBounds(obj, nullptr)); + ASSERT_FALSE(FPDFPageObj_GetRotatedBounds(nullptr, &quad)); + + UnloadPage(page); +} + +TEST_F(FPDFEditPageEmbedderTest, GetBoundsForNormalText) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(obj)); + + constexpr float kExpectedLeft = 20.348f; + constexpr float kExpectedBottom = 48.164f; + constexpr float kExpectedRight = 83.36f; + constexpr float kExpectedTop = 58.328f; + + float left; + float bottom; + float right; + float top; + ASSERT_TRUE(FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)); + EXPECT_FLOAT_EQ(kExpectedLeft, left); + EXPECT_FLOAT_EQ(kExpectedBottom, bottom); + EXPECT_FLOAT_EQ(kExpectedRight, right); + EXPECT_FLOAT_EQ(kExpectedTop, top); + + FS_QUADPOINTSF quad; + ASSERT_TRUE(FPDFPageObj_GetRotatedBounds(obj, &quad)); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x1); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y1); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x2); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y2); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x3); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y3); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x4); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y4); + + UnloadPage(page); +} + +TEST_F(FPDFEditPageEmbedderTest, GetBoundsForRotatedText) { + ASSERT_TRUE(OpenDocument("rotated_text.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(obj)); + + constexpr float kExpectedLeft = 98.9478f; + constexpr float kExpectedBottom = 78.2607f; + constexpr float kExpectedRight = 126.32983f; + constexpr float kExpectedTop = 105.64272f; + + float left; + float bottom; + float right; + float top; + ASSERT_TRUE(FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)); + EXPECT_FLOAT_EQ(kExpectedLeft, left); + EXPECT_FLOAT_EQ(kExpectedBottom, bottom); + EXPECT_FLOAT_EQ(kExpectedRight, right); + EXPECT_FLOAT_EQ(kExpectedTop, top); + + FS_QUADPOINTSF quad; + ASSERT_TRUE(FPDFPageObj_GetRotatedBounds(obj, &quad)); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x1); + EXPECT_FLOAT_EQ(98.4557f, quad.y1); + EXPECT_FLOAT_EQ(119.14279f, quad.x2); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y2); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x3); + EXPECT_FLOAT_EQ(85.447739f, quad.y3); + EXPECT_FLOAT_EQ(106.13486f, quad.x4); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y4); + + UnloadPage(page); +} + +TEST_F(FPDFEditPageEmbedderTest, GetBoundsForNormalImage) { + ASSERT_TRUE(OpenDocument("matte.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 2); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + + constexpr float kExpectedLeft = 0.0f; + constexpr float kExpectedBottom = 90.0f; + constexpr float kExpectedRight = 40.0f; + constexpr float kExpectedTop = 150.0f; + + float left; + float bottom; + float right; + float top; + ASSERT_TRUE(FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)); + EXPECT_FLOAT_EQ(kExpectedLeft, left); + EXPECT_FLOAT_EQ(kExpectedBottom, bottom); + EXPECT_FLOAT_EQ(kExpectedRight, right); + EXPECT_FLOAT_EQ(kExpectedTop, top); + + FS_QUADPOINTSF quad; + ASSERT_TRUE(FPDFPageObj_GetRotatedBounds(obj, &quad)); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x1); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y1); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x2); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y2); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x3); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y3); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x4); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y4); + + UnloadPage(page); +} + +TEST_F(FPDFEditPageEmbedderTest, GetBoundsForRotatedImage) { + ASSERT_TRUE(OpenDocument("rotated_image.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + + constexpr float kExpectedLeft = 100.0f; + constexpr float kExpectedBottom = 70.0f; + constexpr float kExpectedRight = 170.0f; + constexpr float kExpectedTop = 140.0f; + + float left; + float bottom; + float right; + float top; + ASSERT_TRUE(FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)); + EXPECT_FLOAT_EQ(kExpectedLeft, left); + EXPECT_FLOAT_EQ(kExpectedBottom, bottom); + EXPECT_FLOAT_EQ(kExpectedRight, right); + EXPECT_FLOAT_EQ(kExpectedTop, top); + + FS_QUADPOINTSF quad; + ASSERT_TRUE(FPDFPageObj_GetRotatedBounds(obj, &quad)); + EXPECT_FLOAT_EQ(kExpectedLeft, quad.x1); + EXPECT_FLOAT_EQ(100.0f, quad.y1); + EXPECT_FLOAT_EQ(130.0f, quad.x2); + EXPECT_FLOAT_EQ(kExpectedBottom, quad.y2); + EXPECT_FLOAT_EQ(kExpectedRight, quad.x3); + EXPECT_FLOAT_EQ(110.0f, quad.y3); + EXPECT_FLOAT_EQ(140.0f, quad.x4); + EXPECT_FLOAT_EQ(kExpectedTop, quad.y4); + + UnloadPage(page); +}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index e524f30..6e47876 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -208,6 +208,7 @@ CHK(FPDFPageObj_GetLineJoin); CHK(FPDFPageObj_GetMark); CHK(FPDFPageObj_GetMatrix); + CHK(FPDFPageObj_GetRotatedBounds); CHK(FPDFPageObj_GetStrokeColor); CHK(FPDFPageObj_GetStrokeWidth); CHK(FPDFPageObj_GetType);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index 8f4e7f0..c902998 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h
@@ -761,7 +761,7 @@ // right - pointer where the right coordinate will be stored // top - pointer where the top coordinate will be stored // -// Returns TRUE on success. +// On success, returns TRUE and fills in the 4 coordinates. FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPageObj_GetBounds(FPDF_PAGEOBJECT page_object, float* left, @@ -769,6 +769,25 @@ float* right, float* top); +// Experimental API. +// Get the quad points that bounds |page_object|. +// +// page_object - handle to a page object. +// quad_points - pointer where the quadrilateral points will be stored. +// +// On success, returns TRUE and fills in |quad_points|. +// +// Similar to FPDFPageObj_GetBounds(), this returns the bounds of a page +// object. When the object is rotated by a non-multiple of 90 degrees, this API +// returns a tighter bound that cannot be represented with just the 4 sides of +// a rectangle. +// +// Currently only works the following |page_object| types: FPDF_PAGEOBJ_TEXT and +// FPDF_PAGEOBJ_IMAGE. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_GetRotatedBounds(FPDF_PAGEOBJECT page_object, + FS_QUADPOINTSF* quad_points); + // Set the blend mode of |page_object|. // // page_object - handle to a page object.
diff --git a/testing/resources/rotated_image.in b/testing/resources/rotated_image.in new file mode 100644 index 0000000..cf82fe8 --- /dev/null +++ b/testing/resources/rotated_image.in
@@ -0,0 +1,52 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 200] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /XObject << + /Img 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +30 -30 40 40 100 100 cm +/Img Do +Q +endstream +endobj +{{object 5 0}} << + /Type /XObject + /Subtype /Image + /Width 50 + /Height 50 + /BitsPerComponent 8 + /ColorSpace /DeviceRGB + /Filter [/ASCIIHexDecode /FlateDecode] + {{streamlen}} +>> +stream +789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF
diff --git a/testing/resources/rotated_image.pdf b/testing/resources/rotated_image.pdf new file mode 100644 index 0000000..5bb1836 --- /dev/null +++ b/testing/resources/rotated_image.pdf
@@ -0,0 +1,64 @@ +%PDF-1.7 +% ò¤ô +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +endobj +2 0 obj << + /Type /Pages + /MediaBox [0 0 200 200] + /Count 1 + /Kids [3 0 R] +>> +endobj +3 0 obj << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /XObject << + /Img 5 0 R + >> + >> +>> +endobj +4 0 obj << + /Length 36 +>> +stream +q +30 -30 40 40 100 100 cm +/Img Do +Q +endstream +endobj +5 0 obj << + /Type /XObject + /Subtype /Image + /Width 50 + /Height 50 + /BitsPerComponent 8 + /ColorSpace /DeviceRGB + /Filter [/ASCIIHexDecode /FlateDecode] + /Length 71 +>> +stream +789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000015 00000 n +0000000068 00000 n +0000000157 00000 n +0000000287 00000 n +0000000374 00000 n +trailer << + /Root 1 0 R + /Size 6 +>> +startxref +644 +%%EOF