1 // Copyright 2016 PDFium Authors. All rights reserved.
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_pathdata.h"
8
9 #include "core/fxcrt/fx_system.h"
10 #include "third_party/base/numerics/safe_math.h"
11
12 namespace {
13
IsFoldingVerticalLine(const CFX_PointF & a,const CFX_PointF & b,const CFX_PointF & c)14 bool IsFoldingVerticalLine(const CFX_PointF& a,
15 const CFX_PointF& b,
16 const CFX_PointF& c) {
17 return a.x == b.x && b.x == c.x && (b.y - a.y) * (b.y - c.y) > 0;
18 }
19
IsFoldingHorizontalLine(const CFX_PointF & a,const CFX_PointF & b,const CFX_PointF & c)20 bool IsFoldingHorizontalLine(const CFX_PointF& a,
21 const CFX_PointF& b,
22 const CFX_PointF& c) {
23 return a.y == b.y && b.y == c.y && (b.x - a.x) * (b.x - c.x) > 0;
24 }
25
IsFoldingDiagonalLine(const CFX_PointF & a,const CFX_PointF & b,const CFX_PointF & c)26 bool IsFoldingDiagonalLine(const CFX_PointF& a,
27 const CFX_PointF& b,
28 const CFX_PointF& c) {
29 return a.x != b.x && c.x != b.x && a.y != b.y && c.y != b.y &&
30 (a.y - b.y) * (c.x - b.x) == (c.y - b.y) * (a.x - b.x);
31 }
32
UpdateLineEndPoints(CFX_FloatRect * rect,const CFX_PointF & start_pos,const CFX_PointF & end_pos,float hw)33 void UpdateLineEndPoints(CFX_FloatRect* rect,
34 const CFX_PointF& start_pos,
35 const CFX_PointF& end_pos,
36 float hw) {
37 if (start_pos.x == end_pos.x) {
38 if (start_pos.y == end_pos.y) {
39 rect->UpdateRect(end_pos + CFX_PointF(hw, hw));
40 rect->UpdateRect(end_pos - CFX_PointF(hw, hw));
41 return;
42 }
43
44 float point_y;
45 if (end_pos.y < start_pos.y)
46 point_y = end_pos.y - hw;
47 else
48 point_y = end_pos.y + hw;
49
50 rect->UpdateRect(CFX_PointF(end_pos.x + hw, point_y));
51 rect->UpdateRect(CFX_PointF(end_pos.x - hw, point_y));
52 return;
53 }
54
55 if (start_pos.y == end_pos.y) {
56 float point_x;
57 if (end_pos.x < start_pos.x)
58 point_x = end_pos.x - hw;
59 else
60 point_x = end_pos.x + hw;
61
62 rect->UpdateRect(CFX_PointF(point_x, end_pos.y + hw));
63 rect->UpdateRect(CFX_PointF(point_x, end_pos.y - hw));
64 return;
65 }
66
67 CFX_PointF diff = end_pos - start_pos;
68 float ll = FXSYS_sqrt2(diff.x, diff.y);
69 float mx = end_pos.x + hw * diff.x / ll;
70 float my = end_pos.y + hw * diff.y / ll;
71 float dx1 = hw * diff.y / ll;
72 float dy1 = hw * diff.x / ll;
73 rect->UpdateRect(CFX_PointF(mx - dx1, my + dy1));
74 rect->UpdateRect(CFX_PointF(mx + dx1, my - dy1));
75 }
76
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)77 void UpdateLineJoinPoints(CFX_FloatRect* rect,
78 const CFX_PointF& start_pos,
79 const CFX_PointF& mid_pos,
80 const CFX_PointF& end_pos,
81 float half_width,
82 float miter_limit) {
83 float start_k = 0;
84 float start_c = 0;
85 float end_k = 0;
86 float end_c = 0;
87 float start_len = 0;
88 float start_dc = 0;
89 float end_len = 0;
90 float end_dc = 0;
91 float one_twentieth = 1.0f / 20;
92
93 bool bStartVert = fabs(start_pos.x - mid_pos.x) < one_twentieth;
94 bool bEndVert = fabs(mid_pos.x - end_pos.x) < one_twentieth;
95 if (bStartVert && bEndVert) {
96 int start_dir = mid_pos.y > start_pos.y ? 1 : -1;
97 float point_y = mid_pos.y + half_width * start_dir;
98 rect->UpdateRect(CFX_PointF(mid_pos.x + half_width, point_y));
99 rect->UpdateRect(CFX_PointF(mid_pos.x - half_width, point_y));
100 return;
101 }
102
103 if (!bStartVert) {
104 CFX_PointF start_to_mid = start_pos - mid_pos;
105 start_k = (mid_pos.y - start_pos.y) / (mid_pos.x - start_pos.x);
106 start_c = mid_pos.y - (start_k * mid_pos.x);
107 start_len = FXSYS_sqrt2(start_to_mid.x, start_to_mid.y);
108 start_dc =
109 static_cast<float>(fabs(half_width * start_len / start_to_mid.x));
110 }
111 if (!bEndVert) {
112 CFX_PointF end_to_mid = end_pos - mid_pos;
113 end_k = end_to_mid.y / end_to_mid.x;
114 end_c = mid_pos.y - (end_k * mid_pos.x);
115 end_len = FXSYS_sqrt2(end_to_mid.x, end_to_mid.y);
116 end_dc = static_cast<float>(fabs(half_width * end_len / end_to_mid.x));
117 }
118 if (bStartVert) {
119 CFX_PointF outside(start_pos.x, 0);
120 if (end_pos.x < start_pos.x)
121 outside.x += half_width;
122 else
123 outside.x -= half_width;
124
125 if (start_pos.y < (end_k * start_pos.x) + end_c)
126 outside.y = (end_k * outside.x) + end_c + end_dc;
127 else
128 outside.y = (end_k * outside.x) + end_c - end_dc;
129
130 rect->UpdateRect(outside);
131 return;
132 }
133
134 if (bEndVert) {
135 CFX_PointF outside(end_pos.x, 0);
136 if (start_pos.x < end_pos.x)
137 outside.x += half_width;
138 else
139 outside.x -= half_width;
140
141 if (end_pos.y < (start_k * end_pos.x) + start_c)
142 outside.y = (start_k * outside.x) + start_c + start_dc;
143 else
144 outside.y = (start_k * outside.x) + start_c - start_dc;
145
146 rect->UpdateRect(outside);
147 return;
148 }
149
150 if (fabs(start_k - end_k) < one_twentieth) {
151 int start_dir = mid_pos.x > start_pos.x ? 1 : -1;
152 int end_dir = end_pos.x > mid_pos.x ? 1 : -1;
153 if (start_dir == end_dir)
154 UpdateLineEndPoints(rect, mid_pos, end_pos, half_width);
155 else
156 UpdateLineEndPoints(rect, start_pos, mid_pos, half_width);
157 return;
158 }
159
160 float start_outside_c = start_c;
161 if (end_pos.y < (start_k * end_pos.x) + start_c)
162 start_outside_c += start_dc;
163 else
164 start_outside_c -= start_dc;
165
166 float end_outside_c = end_c;
167 if (start_pos.y < (end_k * start_pos.x) + end_c)
168 end_outside_c += end_dc;
169 else
170 end_outside_c -= end_dc;
171
172 float join_x = (end_outside_c - start_outside_c) / (start_k - end_k);
173 float join_y = start_k * join_x + start_outside_c;
174 rect->UpdateRect(CFX_PointF(join_x, join_y));
175 }
176
177 } // namespace
178
179 FX_PATHPOINT::FX_PATHPOINT() = default;
180
FX_PATHPOINT(const CFX_PointF & point,FXPT_TYPE type,bool close)181 FX_PATHPOINT::FX_PATHPOINT(const CFX_PointF& point, FXPT_TYPE type, bool close)
182 : m_Point(point), m_Type(type), m_CloseFigure(close) {}
183
184 FX_PATHPOINT::FX_PATHPOINT(const FX_PATHPOINT& other) = default;
185
186 FX_PATHPOINT::~FX_PATHPOINT() = default;
187
188 CFX_PathData::CFX_PathData() = default;
189
190 CFX_PathData::CFX_PathData(const CFX_PathData& src) = default;
191
192 CFX_PathData::CFX_PathData(CFX_PathData&& src) = default;
193
194 CFX_PathData::~CFX_PathData() = default;
195
Clear()196 void CFX_PathData::Clear() {
197 m_Points.clear();
198 }
199
ClosePath()200 void CFX_PathData::ClosePath() {
201 if (m_Points.empty())
202 return;
203 m_Points.back().m_CloseFigure = true;
204 }
205
Append(const CFX_PathData * pSrc,const CFX_Matrix * pMatrix)206 void CFX_PathData::Append(const CFX_PathData* pSrc, const CFX_Matrix* pMatrix) {
207 if (pSrc->m_Points.empty())
208 return;
209
210 size_t cur_size = m_Points.size();
211 m_Points.insert(m_Points.end(), pSrc->m_Points.begin(), pSrc->m_Points.end());
212
213 if (!pMatrix)
214 return;
215
216 for (size_t i = cur_size; i < m_Points.size(); i++)
217 m_Points[i].m_Point = pMatrix->Transform(m_Points[i].m_Point);
218 }
219
AppendPoint(const CFX_PointF & point,FXPT_TYPE type,bool closeFigure)220 void CFX_PathData::AppendPoint(const CFX_PointF& point,
221 FXPT_TYPE type,
222 bool closeFigure) {
223 m_Points.push_back(FX_PATHPOINT(point, type, closeFigure));
224 }
225
AppendLine(const CFX_PointF & pt1,const CFX_PointF & pt2)226 void CFX_PathData::AppendLine(const CFX_PointF& pt1, const CFX_PointF& pt2) {
227 if (m_Points.empty() || fabs(m_Points.back().m_Point.x - pt1.x) > 0.001 ||
228 fabs(m_Points.back().m_Point.y - pt1.y) > 0.001) {
229 AppendPoint(pt1, FXPT_TYPE::MoveTo, false);
230 }
231 AppendPoint(pt2, FXPT_TYPE::LineTo, false);
232 }
233
AppendFloatRect(const CFX_FloatRect & rect)234 void CFX_PathData::AppendFloatRect(const CFX_FloatRect& rect) {
235 return AppendRect(rect.left, rect.bottom, rect.right, rect.top);
236 }
237
AppendRect(float left,float bottom,float right,float top)238 void CFX_PathData::AppendRect(float left,
239 float bottom,
240 float right,
241 float top) {
242 CFX_PointF left_bottom(left, bottom);
243 CFX_PointF left_top(left, top);
244 CFX_PointF right_top(right, top);
245 CFX_PointF right_bottom(right, bottom);
246
247 AppendLine(left_bottom, left_top);
248 AppendLine(left_top, right_top);
249 AppendLine(right_top, right_bottom);
250 AppendLine(right_bottom, left_bottom);
251 ClosePath();
252 }
253
GetBoundingBox() const254 CFX_FloatRect CFX_PathData::GetBoundingBox() const {
255 if (m_Points.empty())
256 return CFX_FloatRect();
257
258 CFX_FloatRect rect;
259 rect.InitRect(m_Points[0].m_Point);
260 for (size_t i = 1; i < m_Points.size(); i++)
261 rect.UpdateRect(m_Points[i].m_Point);
262 return rect;
263 }
264
GetBoundingBox(float line_width,float miter_limit) const265 CFX_FloatRect CFX_PathData::GetBoundingBox(float line_width,
266 float miter_limit) const {
267 CFX_FloatRect rect(100000.0f, 100000.0f, -100000.0f, -100000.0f);
268 size_t iPoint = 0;
269 float half_width = line_width;
270 int iStartPoint = 0;
271 int iEndPoint = 0;
272 int iMiddlePoint = 0;
273 bool bJoin;
274 while (iPoint < m_Points.size()) {
275 if (m_Points[iPoint].IsTypeAndOpen(FXPT_TYPE::MoveTo)) {
276 if (iPoint + 1 == m_Points.size())
277 break;
278
279 iStartPoint = iPoint + 1;
280 iEndPoint = iPoint;
281 bJoin = false;
282 } else {
283 if (m_Points[iPoint].IsTypeAndOpen(FXPT_TYPE::BezierTo)) {
284 rect.UpdateRect(m_Points[iPoint].m_Point);
285 rect.UpdateRect(m_Points[iPoint + 1].m_Point);
286 iPoint += 2;
287 }
288 if (iPoint == m_Points.size() - 1 ||
289 m_Points[iPoint + 1].IsTypeAndOpen(FXPT_TYPE::MoveTo)) {
290 iStartPoint = iPoint - 1;
291 iEndPoint = iPoint;
292 bJoin = false;
293 } else {
294 iStartPoint = iPoint - 1;
295 iMiddlePoint = iPoint;
296 iEndPoint = iPoint + 1;
297 bJoin = true;
298 }
299 }
300
301 CFX_PointF start_pos = m_Points[iStartPoint].m_Point;
302 CFX_PointF end_pos = m_Points[iEndPoint].m_Point;
303 if (bJoin) {
304 CFX_PointF mid_pos = m_Points[iMiddlePoint].m_Point;
305 UpdateLineJoinPoints(&rect, start_pos, mid_pos, end_pos, half_width,
306 miter_limit);
307 } else {
308 UpdateLineEndPoints(&rect, start_pos, end_pos, half_width);
309 }
310 iPoint++;
311 }
312 return rect;
313 }
314
Transform(const CFX_Matrix & matrix)315 void CFX_PathData::Transform(const CFX_Matrix& matrix) {
316 for (auto& point : m_Points)
317 point.m_Point = matrix.Transform(point.m_Point);
318 }
319
GetZeroAreaPath(const CFX_Matrix * pMatrix,bool bAdjust,CFX_PathData * NewPath,bool * bThin,bool * setIdentity) const320 bool CFX_PathData::GetZeroAreaPath(const CFX_Matrix* pMatrix,
321 bool bAdjust,
322 CFX_PathData* NewPath,
323 bool* bThin,
324 bool* setIdentity) const {
325 *setIdentity = false;
326 if (m_Points.size() < 3)
327 return false;
328
329 if (m_Points.size() == 3 && m_Points[0].m_Type == FXPT_TYPE::MoveTo &&
330 m_Points[1].m_Type == FXPT_TYPE::LineTo &&
331 m_Points[2].m_Type == FXPT_TYPE::LineTo &&
332 m_Points[0].m_Point == m_Points[2].m_Point) {
333 for (size_t i = 0; i < 2; i++) {
334 CFX_PointF point = m_Points[i].m_Point;
335 if (bAdjust) {
336 if (pMatrix)
337 point = pMatrix->Transform(point);
338
339 point = CFX_PointF(static_cast<int>(point.x) + 0.5f,
340 static_cast<int>(point.y) + 0.5f);
341 }
342 NewPath->AppendPoint(
343 point, i == 0 ? FXPT_TYPE::MoveTo : FXPT_TYPE::LineTo, false);
344 }
345 if (bAdjust && pMatrix)
346 *setIdentity = true;
347
348 // Note, they both have to be not equal.
349 if (m_Points[0].m_Point.x != m_Points[1].m_Point.x &&
350 m_Points[0].m_Point.y != m_Points[1].m_Point.y) {
351 *bThin = true;
352 }
353 return true;
354 }
355
356 if (((m_Points.size() > 3) && (m_Points.size() % 2))) {
357 int mid = m_Points.size() / 2;
358 bool bZeroArea = false;
359 CFX_PathData t_path;
360 for (int i = 0; i < mid; i++) {
361 if (!(m_Points[mid - i - 1].m_Point == m_Points[mid + i + 1].m_Point &&
362 m_Points[mid - i - 1].m_Type != FXPT_TYPE::BezierTo &&
363 m_Points[mid + i + 1].m_Type != FXPT_TYPE::BezierTo)) {
364 bZeroArea = true;
365 break;
366 }
367
368 t_path.AppendPoint(m_Points[mid - i].m_Point, FXPT_TYPE::MoveTo, false);
369 t_path.AppendPoint(m_Points[mid - i - 1].m_Point, FXPT_TYPE::LineTo,
370 false);
371 }
372 if (!bZeroArea) {
373 NewPath->Append(&t_path, nullptr);
374 *bThin = true;
375 return true;
376 }
377 }
378
379 int startPoint = 0;
380 for (size_t i = 0; i < m_Points.size(); i++) {
381 FXPT_TYPE point_type = m_Points[i].m_Type;
382 if (point_type == FXPT_TYPE::MoveTo) {
383 startPoint = i;
384 continue;
385 }
386
387 if (point_type == FXPT_TYPE::BezierTo) {
388 i += 2;
389 continue;
390 }
391
392 ASSERT(point_type == FXPT_TYPE::LineTo);
393 int next_index =
394 (i + 1 - startPoint) % (m_Points.size() - startPoint) + startPoint;
395 const FX_PATHPOINT& next = m_Points[next_index];
396 if (next.m_Type == FXPT_TYPE::BezierTo || next.m_Type == FXPT_TYPE::MoveTo)
397 continue;
398
399 const FX_PATHPOINT& prev = m_Points[i - 1];
400 const FX_PATHPOINT& cur = m_Points[i];
401 if (IsFoldingVerticalLine(prev.m_Point, cur.m_Point, next.m_Point)) {
402 bool use_prev = fabs(cur.m_Point.y - prev.m_Point.y) <
403 fabs(cur.m_Point.y - next.m_Point.y);
404 const FX_PATHPOINT& start = use_prev ? prev : cur;
405 const FX_PATHPOINT& end = use_prev ? m_Points[next_index - 1] : next;
406 NewPath->AppendPoint(start.m_Point, FXPT_TYPE::MoveTo, false);
407 NewPath->AppendPoint(end.m_Point, FXPT_TYPE::LineTo, false);
408 continue;
409 }
410
411 if (IsFoldingHorizontalLine(prev.m_Point, cur.m_Point, next.m_Point) ||
412 IsFoldingDiagonalLine(prev.m_Point, cur.m_Point, next.m_Point)) {
413 bool use_prev = fabs(cur.m_Point.x - prev.m_Point.x) <
414 fabs(cur.m_Point.x - next.m_Point.x);
415 const FX_PATHPOINT& start = use_prev ? prev : cur;
416 const FX_PATHPOINT& end = use_prev ? m_Points[next_index - 1] : next;
417 NewPath->AppendPoint(start.m_Point, FXPT_TYPE::MoveTo, false);
418 NewPath->AppendPoint(end.m_Point, FXPT_TYPE::LineTo, false);
419 continue;
420 }
421 }
422
423 size_t new_path_size = NewPath->GetPoints().size();
424 if (m_Points.size() > 3 && new_path_size > 0)
425 *bThin = true;
426 return new_path_size != 0;
427 }
428
IsRect() const429 bool CFX_PathData::IsRect() const {
430 if (m_Points.size() != 5 && m_Points.size() != 4)
431 return false;
432
433 if ((m_Points.size() == 5 && m_Points[0].m_Point != m_Points[4].m_Point) ||
434 m_Points[0].m_Point == m_Points[2].m_Point ||
435 m_Points[1].m_Point == m_Points[3].m_Point) {
436 return false;
437 }
438 // Note, both x,y have to not equal.
439 if (m_Points[0].m_Point.x != m_Points[3].m_Point.x &&
440 m_Points[0].m_Point.y != m_Points[3].m_Point.y) {
441 return false;
442 }
443
444 for (int i = 1; i < 4; i++) {
445 if (m_Points[i].m_Type != FXPT_TYPE::LineTo)
446 return false;
447 // Note, both x,y have to not equal.
448 if (m_Points[i].m_Point.x != m_Points[i - 1].m_Point.x &&
449 m_Points[i].m_Point.y != m_Points[i - 1].m_Point.y) {
450 return false;
451 }
452 }
453 return m_Points.size() == 5 || m_Points[3].m_CloseFigure;
454 }
455
IsRect(const CFX_Matrix * pMatrix,CFX_FloatRect * pRect) const456 bool CFX_PathData::IsRect(const CFX_Matrix* pMatrix,
457 CFX_FloatRect* pRect) const {
458 if (!pMatrix) {
459 if (!IsRect())
460 return false;
461
462 if (pRect) {
463 pRect->left = m_Points[0].m_Point.x;
464 pRect->right = m_Points[2].m_Point.x;
465 pRect->bottom = m_Points[0].m_Point.y;
466 pRect->top = m_Points[2].m_Point.y;
467 pRect->Normalize();
468 }
469 return true;
470 }
471
472 if (m_Points.size() != 5 && m_Points.size() != 4)
473 return false;
474
475 if ((m_Points.size() == 5 && m_Points[0].m_Point != m_Points[4].m_Point) ||
476 m_Points[1].m_Point == m_Points[3].m_Point) {
477 return false;
478 }
479 // Note, both x,y not equal.
480 if (m_Points.size() == 4 && m_Points[0].m_Point.x != m_Points[3].m_Point.x &&
481 m_Points[0].m_Point.y != m_Points[3].m_Point.y) {
482 return false;
483 }
484
485 CFX_PointF points[5];
486 for (size_t i = 0; i < m_Points.size(); i++) {
487 points[i] = pMatrix->Transform(m_Points[i].m_Point);
488
489 if (i == 0)
490 continue;
491 if (m_Points[i].m_Type != FXPT_TYPE::LineTo)
492 return false;
493 if (points[i].x != points[i - 1].x && points[i].y != points[i - 1].y)
494 return false;
495 }
496
497 if (pRect) {
498 pRect->left = points[0].x;
499 pRect->right = points[2].x;
500 pRect->bottom = points[0].y;
501 pRect->top = points[2].y;
502 pRect->Normalize();
503 }
504 return true;
505 }
506
507 CFX_RetainablePathData::CFX_RetainablePathData() = default;
508
509 // Note: can't default the copy constructor since Retainable<> has a deleted
510 // copy constructor (as it should). Instead, we want the default Retainable<>
511 // constructor to be invoked so as to create a copy with a ref-count of 1 as
512 // of the time it is created, then populate the remainder of the members from
513 // the |src| object.
CFX_RetainablePathData(const CFX_RetainablePathData & src)514 CFX_RetainablePathData::CFX_RetainablePathData(
515 const CFX_RetainablePathData& src)
516 : CFX_PathData(src) {}
517
518 CFX_RetainablePathData::~CFX_RetainablePathData() = default;
519
Clone() const520 RetainPtr<CFX_RetainablePathData> CFX_RetainablePathData::Clone() const {
521 return pdfium::MakeRetain<CFX_RetainablePathData>(*this);
522 }
523