xref: /aosp_15_r20/external/pdfium/core/fxge/cfx_path.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2016 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 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fxge/cfx_path.h"
8 
9 #include <math.h>
10 
11 #include <algorithm>
12 #include <iterator>
13 
14 #include "core/fxcrt/fx_system.h"
15 #include "third_party/base/check_op.h"
16 #include "third_party/base/numerics/safe_math.h"
17 
18 namespace {
19 
IsRectPreTransform(const std::vector<CFX_Path::Point> & points)20 bool IsRectPreTransform(const std::vector<CFX_Path::Point>& points) {
21   if (points.size() != 5 && points.size() != 4)
22     return false;
23 
24   if (points.size() == 5 && points[0].m_Point != points[4].m_Point)
25     return false;
26 
27   if (points[0].m_Point == points[2].m_Point ||
28       points[1].m_Point == points[3].m_Point) {
29     return false;
30   }
31 
32   for (size_t i = 1; i < points.size(); ++i) {
33     if (points[i].m_Type != CFX_Path::Point::Type::kLine)
34       return false;
35   }
36   return true;
37 }
38 
XYBothNotEqual(const CFX_PointF & p1,const CFX_PointF & p2)39 bool XYBothNotEqual(const CFX_PointF& p1, const CFX_PointF& p2) {
40   return p1.x != p2.x && p1.y != p2.y;
41 }
42 
IsRectImpl(const std::vector<CFX_Path::Point> & points)43 bool IsRectImpl(const std::vector<CFX_Path::Point>& points) {
44   if (!IsRectPreTransform(points))
45     return false;
46 
47   for (int i = 1; i < 4; i++) {
48     if (XYBothNotEqual(points[i].m_Point, points[i - 1].m_Point))
49       return false;
50   }
51 
52   if (XYBothNotEqual(points[0].m_Point, points[3].m_Point))
53     return false;
54 
55   return true;
56 }
57 
CreateRectFromPoints(const CFX_PointF & p1,const CFX_PointF & p2)58 CFX_FloatRect CreateRectFromPoints(const CFX_PointF& p1, const CFX_PointF& p2) {
59   CFX_FloatRect rect(p1.x, p1.y, p2.x, p2.y);
60   rect.Normalize();
61   return rect;
62 }
63 
PathPointsNeedNormalization(const std::vector<CFX_Path::Point> & points)64 bool PathPointsNeedNormalization(const std::vector<CFX_Path::Point>& points) {
65   return points.size() > 5;
66 }
67 
GetNormalizedPoints(const std::vector<CFX_Path::Point> & points)68 std::vector<CFX_Path::Point> GetNormalizedPoints(
69     const std::vector<CFX_Path::Point>& points) {
70   DCHECK(PathPointsNeedNormalization(points));
71 
72   if (points[0].m_Point != points.back().m_Point)
73     return {};
74 
75   std::vector<CFX_Path::Point> normalized;
76   normalized.reserve(6);
77   normalized.push_back(points[0]);
78   for (auto it = points.begin() + 1; it != points.end(); ++it) {
79     // Exactly 5 points left. Stop normalizing and take what is left.
80     if (normalized.size() + std::distance(it, points.end()) == 5) {
81       std::copy(it, points.end(), std::back_inserter(normalized));
82       break;
83     }
84 
85     // If the line does not move, skip this point.
86     const auto& point = *it;
87     if (point.m_Type == CFX_Path::Point::Type::kLine && !point.m_CloseFigure &&
88         !normalized.back().m_CloseFigure &&
89         point.m_Point == normalized.back().m_Point) {
90       continue;
91     }
92 
93     normalized.push_back(point);
94 
95     // Too many points. Not considered as a rectangle.
96     if (normalized.size() > 5)
97       return {};
98   }
99 
100   DCHECK_EQ(5u, normalized.size());
101   return normalized;
102 }
103 
UpdateLineEndPoints(CFX_FloatRect * rect,const CFX_PointF & start_pos,const CFX_PointF & end_pos,float hw)104 void UpdateLineEndPoints(CFX_FloatRect* rect,
105                          const CFX_PointF& start_pos,
106                          const CFX_PointF& end_pos,
107                          float hw) {
108   if (start_pos.x == end_pos.x) {
109     if (start_pos.y == end_pos.y) {
110       rect->UpdateRect(end_pos + CFX_PointF(hw, hw));
111       rect->UpdateRect(end_pos - CFX_PointF(hw, hw));
112       return;
113     }
114 
115     float point_y;
116     if (end_pos.y < start_pos.y)
117       point_y = end_pos.y - hw;
118     else
119       point_y = end_pos.y + hw;
120 
121     rect->UpdateRect(CFX_PointF(end_pos.x + hw, point_y));
122     rect->UpdateRect(CFX_PointF(end_pos.x - hw, point_y));
123     return;
124   }
125 
126   if (start_pos.y == end_pos.y) {
127     float point_x;
128     if (end_pos.x < start_pos.x)
129       point_x = end_pos.x - hw;
130     else
131       point_x = end_pos.x + hw;
132 
133     rect->UpdateRect(CFX_PointF(point_x, end_pos.y + hw));
134     rect->UpdateRect(CFX_PointF(point_x, end_pos.y - hw));
135     return;
136   }
137 
138   CFX_PointF diff = end_pos - start_pos;
139   float ll = FXSYS_sqrt2(diff.x, diff.y);
140   float mx = end_pos.x + hw * diff.x / ll;
141   float my = end_pos.y + hw * diff.y / ll;
142   float dx1 = hw * diff.y / ll;
143   float dy1 = hw * diff.x / ll;
144   rect->UpdateRect(CFX_PointF(mx - dx1, my + dy1));
145   rect->UpdateRect(CFX_PointF(mx + dx1, my - dy1));
146 }
147 
UpdateLineJoinPoints(CFX_FloatRect * rect,const CFX_PointF & start_pos,const CFX_PointF & mid_pos,const CFX_PointF & end_pos,float half_width,float miter_limit)148 void UpdateLineJoinPoints(CFX_FloatRect* rect,
149                           const CFX_PointF& start_pos,
150                           const CFX_PointF& mid_pos,
151                           const CFX_PointF& end_pos,
152                           float half_width,
153                           float miter_limit) {
154   float start_k = 0;
155   float start_c = 0;
156   float end_k = 0;
157   float end_c = 0;
158   float start_len = 0;
159   float start_dc = 0;
160   float end_len = 0;
161   float end_dc = 0;
162   float one_twentieth = 1.0f / 20;
163 
164   bool bStartVert = fabs(start_pos.x - mid_pos.x) < one_twentieth;
165   bool bEndVert = fabs(mid_pos.x - end_pos.x) < one_twentieth;
166   if (bStartVert && bEndVert) {
167     int start_dir = mid_pos.y > start_pos.y ? 1 : -1;
168     float point_y = mid_pos.y + half_width * start_dir;
169     rect->UpdateRect(CFX_PointF(mid_pos.x + half_width, point_y));
170     rect->UpdateRect(CFX_PointF(mid_pos.x - half_width, point_y));
171     return;
172   }
173 
174   if (!bStartVert) {
175     CFX_PointF start_to_mid = start_pos - mid_pos;
176     start_k = (mid_pos.y - start_pos.y) / (mid_pos.x - start_pos.x);
177     start_c = mid_pos.y - (start_k * mid_pos.x);
178     start_len = FXSYS_sqrt2(start_to_mid.x, start_to_mid.y);
179     start_dc = fabsf(half_width * start_len / start_to_mid.x);
180   }
181   if (!bEndVert) {
182     CFX_PointF end_to_mid = end_pos - mid_pos;
183     end_k = end_to_mid.y / end_to_mid.x;
184     end_c = mid_pos.y - (end_k * mid_pos.x);
185     end_len = FXSYS_sqrt2(end_to_mid.x, end_to_mid.y);
186     end_dc = fabs(half_width * end_len / end_to_mid.x);
187   }
188   if (bStartVert) {
189     CFX_PointF outside(start_pos.x, 0);
190     if (end_pos.x < start_pos.x)
191       outside.x += half_width;
192     else
193       outside.x -= half_width;
194 
195     if (start_pos.y < (end_k * start_pos.x) + end_c)
196       outside.y = (end_k * outside.x) + end_c + end_dc;
197     else
198       outside.y = (end_k * outside.x) + end_c - end_dc;
199 
200     rect->UpdateRect(outside);
201     return;
202   }
203 
204   if (bEndVert) {
205     CFX_PointF outside(end_pos.x, 0);
206     if (start_pos.x < end_pos.x)
207       outside.x += half_width;
208     else
209       outside.x -= half_width;
210 
211     if (end_pos.y < (start_k * end_pos.x) + start_c)
212       outside.y = (start_k * outside.x) + start_c + start_dc;
213     else
214       outside.y = (start_k * outside.x) + start_c - start_dc;
215 
216     rect->UpdateRect(outside);
217     return;
218   }
219 
220   if (fabs(start_k - end_k) < one_twentieth) {
221     int start_dir = mid_pos.x > start_pos.x ? 1 : -1;
222     int end_dir = end_pos.x > mid_pos.x ? 1 : -1;
223     if (start_dir == end_dir)
224       UpdateLineEndPoints(rect, mid_pos, end_pos, half_width);
225     else
226       UpdateLineEndPoints(rect, start_pos, mid_pos, half_width);
227     return;
228   }
229 
230   float start_outside_c = start_c;
231   if (end_pos.y < (start_k * end_pos.x) + start_c)
232     start_outside_c += start_dc;
233   else
234     start_outside_c -= start_dc;
235 
236   float end_outside_c = end_c;
237   if (start_pos.y < (end_k * start_pos.x) + end_c)
238     end_outside_c += end_dc;
239   else
240     end_outside_c -= end_dc;
241 
242   float join_x = (end_outside_c - start_outside_c) / (start_k - end_k);
243   float join_y = start_k * join_x + start_outside_c;
244   rect->UpdateRect(CFX_PointF(join_x, join_y));
245 }
246 
247 }  // namespace
248 
249 CFX_Path::Point::Point() = default;
250 
Point(const CFX_PointF & point,Type type,bool close)251 CFX_Path::Point::Point(const CFX_PointF& point, Type type, bool close)
252     : m_Point(point), m_Type(type), m_CloseFigure(close) {}
253 
254 CFX_Path::Point::Point(const Point& other) = default;
255 
256 CFX_Path::Point::~Point() = default;
257 
258 CFX_Path::CFX_Path() = default;
259 
260 CFX_Path::CFX_Path(const CFX_Path& src) = default;
261 
262 CFX_Path::CFX_Path(CFX_Path&& src) noexcept = default;
263 
264 CFX_Path::~CFX_Path() = default;
265 
Clear()266 void CFX_Path::Clear() {
267   m_Points.clear();
268 }
269 
ClosePath()270 void CFX_Path::ClosePath() {
271   if (m_Points.empty())
272     return;
273   m_Points.back().m_CloseFigure = true;
274 }
275 
Append(const CFX_Path & src,const CFX_Matrix * matrix)276 void CFX_Path::Append(const CFX_Path& src, const CFX_Matrix* matrix) {
277   if (src.m_Points.empty())
278     return;
279 
280   size_t cur_size = m_Points.size();
281   m_Points.insert(m_Points.end(), src.m_Points.begin(), src.m_Points.end());
282 
283   if (!matrix)
284     return;
285 
286   for (size_t i = cur_size; i < m_Points.size(); i++)
287     m_Points[i].m_Point = matrix->Transform(m_Points[i].m_Point);
288 }
289 
AppendPoint(const CFX_PointF & point,Point::Type type)290 void CFX_Path::AppendPoint(const CFX_PointF& point, Point::Type type) {
291   m_Points.push_back(Point(point, type, /*close=*/false));
292 }
293 
AppendPointAndClose(const CFX_PointF & point,Point::Type type)294 void CFX_Path::AppendPointAndClose(const CFX_PointF& point, Point::Type type) {
295   m_Points.push_back(Point(point, type, /*close=*/true));
296 }
297 
AppendLine(const CFX_PointF & pt1,const CFX_PointF & pt2)298 void CFX_Path::AppendLine(const CFX_PointF& pt1, const CFX_PointF& pt2) {
299   if (m_Points.empty() || fabs(m_Points.back().m_Point.x - pt1.x) > 0.001 ||
300       fabs(m_Points.back().m_Point.y - pt1.y) > 0.001) {
301     AppendPoint(pt1, CFX_Path::Point::Type::kMove);
302   }
303   AppendPoint(pt2, CFX_Path::Point::Type::kLine);
304 }
305 
AppendFloatRect(const CFX_FloatRect & rect)306 void CFX_Path::AppendFloatRect(const CFX_FloatRect& rect) {
307   return AppendRect(rect.left, rect.bottom, rect.right, rect.top);
308 }
309 
AppendRect(float left,float bottom,float right,float top)310 void CFX_Path::AppendRect(float left, float bottom, float right, float top) {
311   CFX_PointF left_bottom(left, bottom);
312   CFX_PointF left_top(left, top);
313   CFX_PointF right_top(right, top);
314   CFX_PointF right_bottom(right, bottom);
315 
316   AppendLine(left_bottom, left_top);
317   AppendLine(left_top, right_top);
318   AppendLine(right_top, right_bottom);
319   AppendLine(right_bottom, left_bottom);
320   ClosePath();
321 }
322 
GetBoundingBox() const323 CFX_FloatRect CFX_Path::GetBoundingBox() const {
324   if (m_Points.empty())
325     return CFX_FloatRect();
326 
327   CFX_FloatRect rect(m_Points[0].m_Point);
328   for (size_t i = 1; i < m_Points.size(); ++i)
329     rect.UpdateRect(m_Points[i].m_Point);
330   return rect;
331 }
332 
GetBoundingBoxForStrokePath(float line_width,float miter_limit) const333 CFX_FloatRect CFX_Path::GetBoundingBoxForStrokePath(float line_width,
334                                                     float miter_limit) const {
335   CFX_FloatRect rect(100000.0f, 100000.0f, -100000.0f, -100000.0f);
336   size_t iPoint = 0;
337   float half_width = line_width;
338   size_t iStartPoint = 0;
339   size_t iEndPoint = 0;
340   size_t iMiddlePoint = 0;
341   bool bJoin;
342   while (iPoint < m_Points.size()) {
343     if (m_Points[iPoint].m_Type == CFX_Path::Point::Type::kMove) {
344       if (iPoint + 1 == m_Points.size()) {
345         if (m_Points[iPoint].m_CloseFigure) {
346           // Update `rect` right away since this is the final point to be drawn.
347           rect.UpdateRect(m_Points[iPoint].m_Point);
348         }
349         break;
350       }
351 
352       iStartPoint = iPoint + 1;
353       iEndPoint = iPoint;
354       bJoin = false;
355     } else {
356       if (m_Points[iPoint].IsTypeAndOpen(CFX_Path::Point::Type::kBezier)) {
357         // Callers are responsible for adding Beziers in sets of 3.
358         CHECK_LT(iPoint + 2, m_Points.size());
359         DCHECK_EQ(m_Points[iPoint + 1].m_Type, CFX_Path::Point::Type::kBezier);
360         DCHECK_EQ(m_Points[iPoint + 2].m_Type, CFX_Path::Point::Type::kBezier);
361         rect.UpdateRect(m_Points[iPoint].m_Point);
362         rect.UpdateRect(m_Points[iPoint + 1].m_Point);
363         iPoint += 2;
364       }
365       if (iPoint == m_Points.size() - 1 ||
366           m_Points[iPoint + 1].m_Type == CFX_Path::Point::Type::kMove) {
367         iStartPoint = iPoint - 1;
368         iEndPoint = iPoint;
369         bJoin = false;
370       } else {
371         iStartPoint = iPoint - 1;
372         iMiddlePoint = iPoint;
373         iEndPoint = iPoint + 1;
374         bJoin = true;
375       }
376     }
377     CHECK_LT(iStartPoint, m_Points.size());
378     CHECK_LT(iEndPoint, m_Points.size());
379     if (bJoin) {
380       CHECK_LT(iMiddlePoint, m_Points.size());
381       UpdateLineJoinPoints(
382           &rect, m_Points[iStartPoint].m_Point, m_Points[iMiddlePoint].m_Point,
383           m_Points[iEndPoint].m_Point, half_width, miter_limit);
384     } else {
385       UpdateLineEndPoints(&rect, m_Points[iStartPoint].m_Point,
386                           m_Points[iEndPoint].m_Point, half_width);
387     }
388     ++iPoint;
389   }
390   return rect;
391 }
392 
Transform(const CFX_Matrix & matrix)393 void CFX_Path::Transform(const CFX_Matrix& matrix) {
394   for (auto& point : m_Points)
395     point.m_Point = matrix.Transform(point.m_Point);
396 }
397 
IsRect() const398 bool CFX_Path::IsRect() const {
399   if (PathPointsNeedNormalization(m_Points))
400     return IsRectImpl(GetNormalizedPoints(m_Points));
401   return IsRectImpl(m_Points);
402 }
403 
GetRect(const CFX_Matrix * matrix) const404 absl::optional<CFX_FloatRect> CFX_Path::GetRect(
405     const CFX_Matrix* matrix) const {
406   bool do_normalize = PathPointsNeedNormalization(m_Points);
407   std::vector<Point> normalized;
408   if (do_normalize)
409     normalized = GetNormalizedPoints(m_Points);
410   const std::vector<Point>& path_points = do_normalize ? normalized : m_Points;
411 
412   if (!matrix) {
413     if (!IsRectImpl(path_points))
414       return absl::nullopt;
415 
416     return CreateRectFromPoints(path_points[0].m_Point, path_points[2].m_Point);
417   }
418 
419   if (!IsRectPreTransform(path_points))
420     return absl::nullopt;
421 
422   CFX_PointF points[5];
423   for (size_t i = 0; i < path_points.size(); ++i) {
424     points[i] = matrix->Transform(path_points[i].m_Point);
425 
426     if (i == 0)
427       continue;
428     if (XYBothNotEqual(points[i], points[i - 1]))
429       return absl::nullopt;
430   }
431 
432   if (XYBothNotEqual(points[0], points[3]))
433     return absl::nullopt;
434 
435   return CreateRectFromPoints(points[0], points[2]);
436 }
437 
438 CFX_RetainablePath::CFX_RetainablePath() = default;
439 
440 // Note: can't default the copy constructor since Retainable<> has a deleted
441 // copy constructor (as it should). Instead, we want the default Retainable<>
442 // constructor to be invoked so as to create a copy with a ref-count of 1 as
443 // of the time it is created, then populate the remainder of the members from
444 // the |src| object.
CFX_RetainablePath(const CFX_RetainablePath & src)445 CFX_RetainablePath::CFX_RetainablePath(const CFX_RetainablePath& src)
446     : CFX_Path(src) {}
447 
448 CFX_RetainablePath::~CFX_RetainablePath() = default;
449 
Clone() const450 RetainPtr<CFX_RetainablePath> CFX_RetainablePath::Clone() const {
451   return pdfium::MakeRetain<CFX_RetainablePath>(*this);
452 }
453