1 // Copyright 2021 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/fxge/cfx_path.h"
6
7 #include "core/fxcrt/fx_coordinates.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9
TEST(CFX_Path,BasicTest)10 TEST(CFX_Path, BasicTest) {
11 CFX_Path path;
12 path.AppendRect(/*left=*/1, /*bottom=*/2, /*right=*/3, /*top=*/5);
13 EXPECT_EQ(5u, path.GetPoints().size());
14 EXPECT_TRUE(path.IsRect());
15 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
16 ASSERT_TRUE(rect.has_value());
17 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), rect.value());
18 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), path.GetBoundingBox());
19
20 const CFX_Matrix kScaleMatrix(1, 0, 0, 2, 60, 70);
21 rect = path.GetRect(&kScaleMatrix);
22 ASSERT_TRUE(rect.has_value());
23 EXPECT_EQ(CFX_FloatRect(61, 74, 63, 80), rect.value());
24 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), path.GetBoundingBox());
25
26 path.Clear();
27 EXPECT_EQ(0u, path.GetPoints().size());
28 EXPECT_FALSE(path.IsRect());
29 EXPECT_EQ(CFX_FloatRect(), path.GetBoundingBox());
30
31 // 4 points without a closed path makes a rect.
32 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
33 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
34 path.AppendPoint({1, 1}, CFX_Path::Point::Type::kLine);
35 path.AppendPoint({1, 0}, CFX_Path::Point::Type::kLine);
36 EXPECT_EQ(4u, path.GetPoints().size());
37 EXPECT_TRUE(path.IsRect());
38 rect = path.GetRect(nullptr);
39 ASSERT_TRUE(rect.has_value());
40 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
41 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), path.GetBoundingBox());
42
43 // 4 points with a closed path also makes a rect.
44 path.ClosePath();
45 EXPECT_EQ(4u, path.GetPoints().size());
46 EXPECT_TRUE(path.IsRect());
47 rect = path.GetRect(nullptr);
48 ASSERT_TRUE(rect.has_value());
49 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
50 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), path.GetBoundingBox());
51
52 path.Transform(kScaleMatrix);
53 EXPECT_TRUE(path.IsRect());
54 rect = path.GetRect(nullptr);
55 ASSERT_TRUE(rect.has_value());
56 EXPECT_EQ(CFX_FloatRect(60, 70, 61, 72), rect.value());
57 EXPECT_EQ(CFX_FloatRect(60, 70, 61, 72), path.GetBoundingBox());
58
59 path.Clear();
60 path.AppendFloatRect({1, 2, 3, 5});
61 EXPECT_TRUE(path.IsRect());
62 rect = path.GetRect(nullptr);
63 ASSERT_TRUE(rect.has_value());
64 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), rect.value());
65 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), path.GetBoundingBox());
66 }
67
TEST(CFX_Path,ShearTransform)68 TEST(CFX_Path, ShearTransform) {
69 CFX_Path path;
70 path.AppendRect(/*left=*/1, /*bottom=*/2, /*right=*/3, /*top=*/5);
71
72 const CFX_Matrix kShearMatrix(1, 2, 0, 1, 0, 0);
73 EXPECT_TRUE(path.IsRect());
74 absl::optional<CFX_FloatRect> rect = path.GetRect(&kShearMatrix);
75 EXPECT_FALSE(rect.has_value());
76 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), path.GetBoundingBox());
77
78 path.Transform(kShearMatrix);
79 EXPECT_FALSE(path.IsRect());
80 rect = path.GetRect(nullptr);
81 EXPECT_FALSE(rect.has_value());
82 EXPECT_EQ(CFX_FloatRect(1, 4, 3, 11), path.GetBoundingBox());
83
84 const CFX_Matrix shear_inverse_matrix = kShearMatrix.GetInverse();
85 rect = path.GetRect(&shear_inverse_matrix);
86 ASSERT_TRUE(rect.has_value());
87 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), rect.value());
88 EXPECT_EQ(CFX_FloatRect(1, 4, 3, 11), path.GetBoundingBox());
89
90 path.Transform(shear_inverse_matrix);
91 EXPECT_TRUE(path.IsRect());
92 rect = path.GetRect(nullptr);
93 ASSERT_TRUE(rect.has_value());
94 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), rect.value());
95 EXPECT_EQ(CFX_FloatRect(1, 2, 3, 5), path.GetBoundingBox());
96 }
97
TEST(CFX_Path,Hexagon)98 TEST(CFX_Path, Hexagon) {
99 CFX_Path path;
100 path.AppendPoint({1, 0}, CFX_Path::Point::Type::kMove);
101 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
102 path.AppendPoint({3, 1}, CFX_Path::Point::Type::kLine);
103 path.AppendPoint({2, 2}, CFX_Path::Point::Type::kLine);
104 path.AppendPoint({1, 2}, CFX_Path::Point::Type::kLine);
105 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
106 ASSERT_EQ(6u, path.GetPoints().size());
107 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(5));
108 EXPECT_FALSE(path.IsClosingFigure(5));
109 EXPECT_FALSE(path.IsRect());
110 EXPECT_FALSE(path.GetRect(nullptr).has_value());
111 EXPECT_EQ(CFX_FloatRect(0, 0, 3, 2), path.GetBoundingBox());
112
113 path.ClosePath();
114 ASSERT_EQ(6u, path.GetPoints().size());
115 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(5));
116 EXPECT_TRUE(path.IsClosingFigure(5));
117 EXPECT_FALSE(path.IsRect());
118 EXPECT_FALSE(path.GetRect(nullptr).has_value());
119
120 // Calling ClosePath() repeatedly makes no difference.
121 path.ClosePath();
122 ASSERT_EQ(6u, path.GetPoints().size());
123 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(5));
124 EXPECT_TRUE(path.IsClosingFigure(5));
125 EXPECT_FALSE(path.IsRect());
126 EXPECT_FALSE(path.GetRect(nullptr).has_value());
127
128 // A hexagon with the same start/end point is still not a rectangle.
129 path.Clear();
130 path.AppendPoint({1, 0}, CFX_Path::Point::Type::kMove);
131 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
132 path.AppendPoint({3, 1}, CFX_Path::Point::Type::kLine);
133 path.AppendPoint({2, 2}, CFX_Path::Point::Type::kLine);
134 path.AppendPoint({1, 2}, CFX_Path::Point::Type::kLine);
135 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
136 path.AppendPoint({1, 0}, CFX_Path::Point::Type::kLine);
137 EXPECT_FALSE(path.IsRect());
138 EXPECT_FALSE(path.GetRect(nullptr).has_value());
139 EXPECT_EQ(CFX_FloatRect(0, 0, 3, 2), path.GetBoundingBox());
140 }
141
TEST(CFX_Path,ClosePath)142 TEST(CFX_Path, ClosePath) {
143 CFX_Path path;
144 path.AppendLine({0, 0}, {0, 1});
145 path.AppendLine({0, 1}, {1, 1});
146 path.AppendLine({1, 1}, {1, 0});
147 ASSERT_EQ(4u, path.GetPoints().size());
148 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(3));
149 EXPECT_FALSE(path.IsClosingFigure(3));
150 EXPECT_TRUE(path.IsRect());
151 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
152 ASSERT_TRUE(rect.has_value());
153 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
154
155 const CFX_Matrix kIdentityMatrix;
156 ASSERT_TRUE(kIdentityMatrix.IsIdentity());
157 rect = path.GetRect(&kIdentityMatrix);
158 ASSERT_TRUE(rect.has_value());
159 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
160
161 path.ClosePath();
162 ASSERT_EQ(4u, path.GetPoints().size());
163 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(3));
164 EXPECT_TRUE(path.IsClosingFigure(3));
165 EXPECT_TRUE(path.IsRect());
166 rect = path.GetRect(nullptr);
167 ASSERT_TRUE(rect.has_value());
168 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
169
170 // Calling ClosePath() repeatedly makes no difference.
171 path.ClosePath();
172 ASSERT_EQ(4u, path.GetPoints().size());
173 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(3));
174 EXPECT_TRUE(path.IsClosingFigure(3));
175 EXPECT_TRUE(path.IsRect());
176 rect = path.GetRect(nullptr);
177 ASSERT_TRUE(rect.has_value());
178 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
179
180 path.AppendPointAndClose({0, 0}, CFX_Path::Point::Type::kLine);
181 ASSERT_EQ(5u, path.GetPoints().size());
182 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(3));
183 EXPECT_TRUE(path.IsClosingFigure(3));
184 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(4));
185 EXPECT_TRUE(path.IsClosingFigure(4));
186 EXPECT_TRUE(path.IsRect());
187 rect = path.GetRect(nullptr);
188 ASSERT_TRUE(rect.has_value());
189 EXPECT_EQ(CFX_FloatRect(0, 0, 1, 1), rect.value());
190 }
191
TEST(CFX_Path,FivePointRect)192 TEST(CFX_Path, FivePointRect) {
193 CFX_Path path;
194 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
195 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
196 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
197 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
198 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
199 ASSERT_EQ(5u, path.GetPoints().size());
200 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(4));
201 EXPECT_FALSE(path.IsClosingFigure(4));
202 EXPECT_TRUE(path.IsRect());
203 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
204 ASSERT_TRUE(rect.has_value());
205 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), rect.value());
206
207 path.ClosePath();
208 ASSERT_EQ(5u, path.GetPoints().size());
209 EXPECT_EQ(CFX_Path::Point::Type::kLine, path.GetType(4));
210 EXPECT_TRUE(path.IsClosingFigure(4));
211 EXPECT_TRUE(path.IsRect());
212 rect = path.GetRect(nullptr);
213 ASSERT_TRUE(rect.has_value());
214 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), rect.value());
215 }
216
TEST(CFX_Path,SixPlusPointRect)217 TEST(CFX_Path, SixPlusPointRect) {
218 CFX_Path path;
219 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
220 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
221 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
222 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
223 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
224 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
225 EXPECT_TRUE(path.IsRect());
226 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
227 ASSERT_TRUE(rect.has_value());
228 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), rect.value());
229 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
230
231 path.Clear();
232 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
233 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
234 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
235 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
236 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
237 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
238 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
239 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
240 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
241 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
242 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
243 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
244 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
245 EXPECT_TRUE(path.IsRect());
246 rect = path.GetRect(nullptr);
247 ASSERT_TRUE(rect.has_value());
248 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), rect.value());
249 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
250 }
251
TEST(CFX_Path,NotRect)252 TEST(CFX_Path, NotRect) {
253 CFX_Path path;
254 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
255 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
256 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
257 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
258 path.AppendPoint({0, 0.1f}, CFX_Path::Point::Type::kLine);
259 EXPECT_FALSE(path.IsRect());
260 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
261 EXPECT_FALSE(rect.has_value());
262 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
263
264 path.ClosePath();
265 EXPECT_FALSE(path.IsRect());
266 rect = path.GetRect(nullptr);
267 EXPECT_FALSE(rect.has_value());
268 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
269
270 path.Clear();
271 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
272 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
273 path.AppendPoint({3, 1}, CFX_Path::Point::Type::kLine);
274 path.AppendPointAndClose({0, 1}, CFX_Path::Point::Type::kLine);
275 EXPECT_FALSE(path.IsRect());
276 rect = path.GetRect(nullptr);
277 EXPECT_FALSE(rect.has_value());
278 EXPECT_EQ(CFX_FloatRect(0, 0, 3, 1), path.GetBoundingBox());
279
280 path.Clear();
281 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
282 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
283 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
284 path.AppendPointAndClose({0, 1}, CFX_Path::Point::Type::kMove);
285 EXPECT_FALSE(path.IsRect());
286 rect = path.GetRect(nullptr);
287 EXPECT_FALSE(rect.has_value());
288 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
289
290 path.Clear();
291 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
292 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
293 path.AppendPoint({3, 0}, CFX_Path::Point::Type::kLine);
294 path.AppendPointAndClose({0, 1}, CFX_Path::Point::Type::kLine);
295 EXPECT_FALSE(path.IsRect());
296 rect = path.GetRect(nullptr);
297 EXPECT_FALSE(rect.has_value());
298 EXPECT_EQ(CFX_FloatRect(0, 0, 3, 1), path.GetBoundingBox());
299
300 path.Clear();
301 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
302 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
303 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
304 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
305 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
306 EXPECT_FALSE(path.IsRect());
307 rect = path.GetRect(nullptr);
308 EXPECT_FALSE(rect.has_value());
309 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
310
311 path.Clear();
312 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
313 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
314 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
315 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
316 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
317 EXPECT_FALSE(path.IsRect());
318 rect = path.GetRect(nullptr);
319 EXPECT_FALSE(rect.has_value());
320 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 1), path.GetBoundingBox());
321
322 path.Clear();
323 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
324 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kLine);
325 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
326 path.AppendPoint({2, 2}, CFX_Path::Point::Type::kLine);
327 EXPECT_FALSE(path.IsRect());
328 rect = path.GetRect(nullptr);
329 EXPECT_FALSE(rect.has_value());
330 const CFX_Matrix kScaleMatrix(1, 0, 0, 2, 60, 70);
331 rect = path.GetRect(&kScaleMatrix);
332 EXPECT_FALSE(rect.has_value());
333 EXPECT_EQ(CFX_FloatRect(0, 0, 2, 2), path.GetBoundingBox());
334 }
335
TEST(CFX_Path,EmptyRect)336 TEST(CFX_Path, EmptyRect) {
337 // Document existing behavior where an empty rect is still considered a rect.
338 CFX_Path path;
339 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kMove);
340 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
341 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
342 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
343 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
344 EXPECT_TRUE(path.IsRect());
345 absl::optional<CFX_FloatRect> rect = path.GetRect(nullptr);
346 ASSERT_TRUE(rect.has_value());
347 EXPECT_EQ(CFX_FloatRect(0, 0, 0, 1), rect.value());
348 EXPECT_EQ(CFX_FloatRect(0, 0, 0, 1), path.GetBoundingBox());
349 }
350
TEST(CFX_Path,Append)351 TEST(CFX_Path, Append) {
352 CFX_Path path;
353 path.AppendPoint({5, 6}, CFX_Path::Point::Type::kMove);
354 ASSERT_EQ(1u, path.GetPoints().size());
355 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(0));
356
357 CFX_Path empty_path;
358 path.Append(empty_path, nullptr);
359 ASSERT_EQ(1u, path.GetPoints().size());
360 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(0));
361
362 path.Append(path, nullptr);
363 ASSERT_EQ(2u, path.GetPoints().size());
364 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(0));
365 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(1));
366
367 const CFX_Matrix kScaleMatrix(1, 0, 0, 2, 60, 70);
368 path.Append(path, &kScaleMatrix);
369 ASSERT_EQ(4u, path.GetPoints().size());
370 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(0));
371 EXPECT_EQ(CFX_PointF(5, 6), path.GetPoint(1));
372 EXPECT_EQ(CFX_PointF(65, 82), path.GetPoint(2));
373 EXPECT_EQ(CFX_PointF(65, 82), path.GetPoint(3));
374 }
375
TEST(CFX_Path,GetBoundingBoxForStrokePath)376 TEST(CFX_Path, GetBoundingBoxForStrokePath) {
377 static constexpr float kLineWidth = 1.0f;
378 static constexpr float kMiterLimit = 1.0f;
379
380 {
381 // Test the case that the first/last point is "move" and it closes the
382 // paths.
383 CFX_Path path;
384 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kMove);
385 path.ClosePath();
386 EXPECT_EQ(CFX_FloatRect(2, 0, 2, 0),
387 path.GetBoundingBoxForStrokePath(kLineWidth, kMiterLimit));
388 }
389
390 {
391 // Test on a regular rect path.
392 CFX_Path path;
393 path.AppendPoint({2, 0}, CFX_Path::Point::Type::kMove);
394 path.AppendPoint({2, 1}, CFX_Path::Point::Type::kLine);
395 path.AppendPoint({0, 1}, CFX_Path::Point::Type::kLine);
396 path.AppendPoint({0, 0}, CFX_Path::Point::Type::kLine);
397 path.ClosePath();
398 EXPECT_EQ(CFX_FloatRect(-1, -1, 3, 2),
399 path.GetBoundingBoxForStrokePath(kLineWidth, kMiterLimit));
400
401 // If the final point is "move" and the path remains open, it should not
402 // affect the bounding rect.
403 path.AppendPoint({20, 20}, CFX_Path::Point::Type::kMove);
404 EXPECT_EQ(CFX_FloatRect(-1, -1, 3, 2),
405 path.GetBoundingBoxForStrokePath(kLineWidth, kMiterLimit));
406 }
407 }
408