Add unit tests for CFX_PathData.
Add unit tests for CFX_PathData, rather than receive test coverage via
integration tests only. The test cases include one for paths with more
than 5 points that draw rectangles, and one that shows
CFX_PathData::GetRect() cannot agree with itself on whether 4 points
draw a rectangle or not. Implement CFX_FloatRect::operator== to make
testing easier.
Bug: pdfium:1683
Change-Id: Ibd36a13d84ce893eb4b2bdcf0afad3706fe968d6
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/80850
Reviewed-by: Hui Yingst <nigi@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fxcrt/fx_coordinates.h b/core/fxcrt/fx_coordinates.h
index e96bf10..fb2c0be 100644
--- a/core/fxcrt/fx_coordinates.h
+++ b/core/fxcrt/fx_coordinates.h
@@ -302,6 +302,11 @@
// Rounds LBRT values.
FX_RECT ToRoundedFxRect() const;
+ bool operator==(const CFX_FloatRect& other) const {
+ return left == other.left && right == other.right && top == other.top &&
+ bottom == other.bottom;
+ }
+
float left = 0.0f;
float bottom = 0.0f;
float right = 0.0f;
diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn
index 4a79bc8..465ea73 100644
--- a/core/fxge/BUILD.gn
+++ b/core/fxge/BUILD.gn
@@ -210,6 +210,7 @@
sources = [
"cfx_folderfontinfo_unittest.cpp",
"cfx_fontmapper_unittest.cpp",
+ "cfx_pathdata_unittest.cpp",
"dib/cfx_cmyk_to_srgb_unittest.cpp",
"dib/cfx_dibbase_unittest.cpp",
"dib/cfx_dibitmap_unittest.cpp",
diff --git a/core/fxge/cfx_pathdata_unittest.cpp b/core/fxge/cfx_pathdata_unittest.cpp
new file mode 100644
index 0000000..4dbc1b9
--- /dev/null
+++ b/core/fxge/cfx_pathdata_unittest.cpp
@@ -0,0 +1,313 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxge/cfx_pathdata.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CFX_PathData, BasicTest) {
+ CFX_PathData path;
+ path.AppendRect(/*left=*/1, /*bottom=*/2, /*right=*/3, /*top=*/5);
+ EXPECT_EQ(5u, path.GetPoints().size());
+ EXPECT_TRUE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(1, 2, 3, 5));
+
+ const CFX_Matrix kScaleMatrix(1, 0, 0, 2, 60, 70);
+ rect = path.GetRect(&kScaleMatrix);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(61, 74, 63, 80));
+
+ path.Clear();
+ EXPECT_EQ(0u, path.GetPoints().size());
+ EXPECT_FALSE(path.IsRect());
+
+ // As is, 4 points do not make a rect without a closed path.
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({1, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({1, 0}, FXPT_TYPE::LineTo);
+ EXPECT_EQ(4u, path.GetPoints().size());
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ // As is, 4 points with a closed path makes a rect.
+ path.ClosePath();
+ EXPECT_EQ(4u, path.GetPoints().size());
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 1, 1));
+
+ path.Transform(kScaleMatrix);
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(60, 70, 61, 72));
+
+ path.Clear();
+ path.AppendFloatRect({1, 2, 3, 5});
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(1, 2, 3, 5));
+}
+
+TEST(CFX_PathData, ShearTransform) {
+ CFX_PathData path;
+ path.AppendRect(/*left=*/1, /*bottom=*/2, /*right=*/3, /*top=*/5);
+
+ const CFX_Matrix kShearMatrix(1, 2, 0, 1, 0, 0);
+ EXPECT_TRUE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(&kShearMatrix);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Transform(kShearMatrix);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ const CFX_Matrix shear_inverse_matrix = kShearMatrix.GetInverse();
+ rect = path.GetRect(&shear_inverse_matrix);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(1, 2, 3, 5));
+
+ path.Transform(shear_inverse_matrix);
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(1, 2, 3, 5));
+}
+
+TEST(CFX_PathData, Hexagon) {
+ CFX_PathData path;
+ path.AppendPoint({1, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({3, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 2}, FXPT_TYPE::LineTo);
+ path.AppendPoint({1, 2}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ ASSERT_EQ(6u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(5));
+ EXPECT_FALSE(path.IsClosingFigure(5));
+ EXPECT_FALSE(path.IsRect());
+ EXPECT_FALSE(path.GetRect(nullptr).has_value());
+
+ path.ClosePath();
+ ASSERT_EQ(6u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(5));
+ EXPECT_TRUE(path.IsClosingFigure(5));
+ EXPECT_FALSE(path.IsRect());
+ EXPECT_FALSE(path.GetRect(nullptr).has_value());
+
+ // Calling ClosePath() repeatedly makes no difference.
+ path.ClosePath();
+ ASSERT_EQ(6u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(5));
+ EXPECT_TRUE(path.IsClosingFigure(5));
+ EXPECT_FALSE(path.IsRect());
+ EXPECT_FALSE(path.GetRect(nullptr).has_value());
+
+ // A hexagon with the same start/end point is still not a rectangle.
+ path.Clear();
+ path.AppendPoint({1, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({3, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 2}, FXPT_TYPE::LineTo);
+ path.AppendPoint({1, 2}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({1, 0}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ EXPECT_FALSE(path.GetRect(nullptr).has_value());
+}
+
+TEST(CFX_PathData, ClosePath) {
+ CFX_PathData path;
+ path.AppendLine({0, 0}, {0, 1});
+ path.AppendLine({0, 1}, {1, 1});
+ path.AppendLine({1, 1}, {1, 0});
+ ASSERT_EQ(4u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(3));
+ EXPECT_FALSE(path.IsClosingFigure(3));
+
+ // TODO(crbug.com/pdfium/1683): Resolve disagreement between these 2 calls,
+ // and the call with `kIdentityMatrix` below.
+ EXPECT_FALSE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ const CFX_Matrix kIdentityMatrix;
+ ASSERT_TRUE(kIdentityMatrix.IsIdentity());
+ rect = path.GetRect(&kIdentityMatrix);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 1, 1));
+
+ path.ClosePath();
+ ASSERT_EQ(4u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(3));
+ EXPECT_TRUE(path.IsClosingFigure(3));
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 1, 1));
+
+ // Calling ClosePath() repeatedly makes no difference.
+ path.ClosePath();
+ ASSERT_EQ(4u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(3));
+ EXPECT_TRUE(path.IsClosingFigure(3));
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 1, 1));
+
+ path.AppendPointAndClose({0, 0}, FXPT_TYPE::LineTo);
+ ASSERT_EQ(5u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(3));
+ EXPECT_TRUE(path.IsClosingFigure(3));
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(4));
+ EXPECT_TRUE(path.IsClosingFigure(4));
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 1, 1));
+}
+
+TEST(CFX_PathData, FivePointRect) {
+ CFX_PathData path;
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ ASSERT_EQ(5u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(4));
+ EXPECT_FALSE(path.IsClosingFigure(4));
+ EXPECT_TRUE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 2, 1));
+
+ path.ClosePath();
+ ASSERT_EQ(5u, path.GetPoints().size());
+ EXPECT_EQ(FXPT_TYPE::LineTo, path.GetType(4));
+ EXPECT_TRUE(path.IsClosingFigure(4));
+ EXPECT_TRUE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 2, 1));
+}
+
+TEST(CFX_PathData, SixPlusPointRect) {
+ CFX_PathData path;
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ // TODO(crbug.com/pdfium/1683): Treat this as a rect.
+ EXPECT_FALSE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ // TODO(crbug.com/pdfium/1683): Treat this as a rect.
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+}
+
+TEST(CFX_PathData, NotRect) {
+ CFX_PathData path;
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0.1f}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.ClosePath();
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({3, 1}, FXPT_TYPE::LineTo);
+ path.AppendPointAndClose({0, 1}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPointAndClose({0, 1}, FXPT_TYPE::MoveTo);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({3, 0}, FXPT_TYPE::LineTo);
+ path.AppendPointAndClose({0, 1}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+
+ path.Clear();
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({2, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ EXPECT_FALSE(path.IsRect());
+ rect = path.GetRect(nullptr);
+ EXPECT_FALSE(rect.has_value());
+}
+
+TEST(CFX_PathData, EmptyRect) {
+ // Document existing behavior where an empty rect is still considered a rect.
+ CFX_PathData path;
+ path.AppendPoint({0, 0}, FXPT_TYPE::MoveTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 1}, FXPT_TYPE::LineTo);
+ path.AppendPoint({0, 0}, FXPT_TYPE::LineTo);
+ EXPECT_TRUE(path.IsRect());
+ Optional<CFX_FloatRect> rect = path.GetRect(nullptr);
+ ASSERT_TRUE(rect.has_value());
+ EXPECT_EQ(rect.value(), CFX_FloatRect(0, 0, 0, 1));
+}