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