1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "DottedCornerFinder.h"
8 
9 #include <utility>
10 
11 #include "BorderCache.h"
12 #include "BorderConsts.h"
13 
14 namespace mozilla {
15 
16 using namespace gfx;
17 
Square(Float x)18 static inline Float Square(Float x) { return x * x; }
19 
PointRotateCCW90(const Point & aP)20 static Point PointRotateCCW90(const Point& aP) { return Point(aP.y, -aP.x); }
21 
22 struct BestOverlap {
23   Float overlap;
24   size_t count;
25 
BestOverlapmozilla::BestOverlap26   BestOverlap() : overlap(0.0f), count(0) {}
27 
BestOverlapmozilla::BestOverlap28   BestOverlap(Float aOverlap, size_t aCount)
29       : overlap(aOverlap), count(aCount) {}
30 };
31 
32 static const size_t DottedCornerCacheSize = 256;
33 nsTHashMap<FourFloatsHashKey, BestOverlap> DottedCornerCache;
34 
DottedCornerFinder(const Bezier & aOuterBezier,const Bezier & aInnerBezier,Corner aCorner,Float aBorderRadiusX,Float aBorderRadiusY,const Point & aC0,Float aR0,const Point & aCn,Float aRn,const Size & aCornerDim)35 DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier,
36                                        const Bezier& aInnerBezier,
37                                        Corner aCorner, Float aBorderRadiusX,
38                                        Float aBorderRadiusY, const Point& aC0,
39                                        Float aR0, const Point& aCn, Float aRn,
40                                        const Size& aCornerDim)
41     : mOuterBezier(aOuterBezier),
42       mInnerBezier(aInnerBezier),
43       mCorner(aCorner),
44       mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f),
45       mC0(aC0),
46       mCn(aCn),
47       mR0(aR0),
48       mRn(aRn),
49       mMaxR(std::max(aR0, aRn)),
50       mCenterCurveOrigin(mC0.x, mCn.y),
51       mCenterCurveR(0.0),
52       mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y),
53       mBestOverlap(0.0f),
54       mHasZeroBorderWidth(false),
55       mHasMore(true),
56       mMaxCount(aCornerDim.width + aCornerDim.height),
57       mType(OTHER),
58       mI(0),
59       mCount(0) {
60   NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f,
61                "At least one side should have non-zero radius.");
62 
63   mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
64   mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
65 
66   DetermineType(aBorderRadiusX, aBorderRadiusY);
67 
68   Reset();
69 }
70 
IsSingleCurve(Float aMinR,Float aMaxR,Float aMinBorderRadius,Float aMaxBorderRadius)71 static bool IsSingleCurve(Float aMinR, Float aMaxR, Float aMinBorderRadius,
72                           Float aMaxBorderRadius) {
73   return aMinR > 0.0f && aMinBorderRadius > aMaxR * 4.0f &&
74          aMinBorderRadius / aMaxBorderRadius > 0.5f;
75 }
76 
DetermineType(Float aBorderRadiusX,Float aBorderRadiusY)77 void DottedCornerFinder::DetermineType(Float aBorderRadiusX,
78                                        Float aBorderRadiusY) {
79   // Calculate parameters for the center curve before swap.
80   Float centerCurveWidth = fabs(mC0.x - mCn.x);
81   Float centerCurveHeight = fabs(mC0.y - mCn.y);
82   Point cornerPoint(mCn.x, mC0.y);
83 
84   bool swapped = false;
85   if (mR0 < mRn) {
86     // Always draw from wider side to thinner side.
87     std::swap(mC0, mCn);
88     std::swap(mR0, mRn);
89     std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
90     std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
91     std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
92     std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
93     mNormalSign = -mNormalSign;
94     swapped = true;
95   }
96 
97   // See the comment at mType declaration for each condition.
98 
99   Float minR = std::min(mR0, mRn);
100   Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY);
101   Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY);
102   if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) {
103     if (mR0 == mRn) {
104       Float borderLength;
105       if (minBorderRadius == maxBorderRadius) {
106         mType = PERFECT;
107         borderLength = M_PI * centerCurveHeight / 2.0f;
108 
109         mCenterCurveR = centerCurveWidth;
110       } else {
111         mType = SINGLE_CURVE_AND_RADIUS;
112         borderLength =
113             GetQuarterEllipticArcLength(centerCurveWidth, centerCurveHeight);
114       }
115 
116       Float diameter = mR0 * 2.0f;
117       size_t count = round(borderLength / diameter);
118       if (count % 2) {
119         count++;
120       }
121       mCount = count / 2 - 1;
122       if (mCount > 0) {
123         mBestOverlap = 1.0f - borderLength / (diameter * count);
124       }
125     } else {
126       mType = SINGLE_CURVE;
127     }
128   }
129 
130   if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) {
131     Size cornerSize(centerCurveWidth, centerCurveHeight);
132     GetBezierPointsForCorner(&mCenterBezier, mCorner, cornerPoint, cornerSize);
133     if (swapped) {
134       std::swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]);
135       std::swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]);
136     }
137   }
138 
139   if (minR == 0.0f) {
140     mHasZeroBorderWidth = true;
141   }
142 
143   if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
144     FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
145   }
146 }
147 
HasMore(void) const148 bool DottedCornerFinder::HasMore(void) const {
149   if (mHasZeroBorderWidth) {
150     return mI < mMaxCount && mHasMore;
151   }
152 
153   return mI < mCount;
154 }
155 
Next(void)156 DottedCornerFinder::Result DottedCornerFinder::Next(void) {
157   mI++;
158 
159   if (mType == PERFECT) {
160     Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR;
161     if (mCorner == C_TL) {
162       phi = -M_PI / 2.0f - phi;
163     } else if (mCorner == C_TR) {
164       phi = -M_PI / 2.0f + phi;
165     } else if (mCorner == C_BR) {
166       phi = M_PI / 2.0f - phi;
167     } else {
168       phi = M_PI / 2.0f + phi;
169     }
170 
171     Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
172             mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
173     return DottedCornerFinder::Result(C, mR0);
174   }
175 
176   // Find unfilled and filled circles.
177   (void)FindNext(mBestOverlap);
178   if (mHasMore) {
179     (void)FindNext(mBestOverlap);
180   }
181 
182   return Result(mLastC, mLastR);
183 }
184 
Reset(void)185 void DottedCornerFinder::Reset(void) {
186   mLastC = mC0;
187   mLastR = mR0;
188   mLastT = 0.0f;
189   mHasMore = true;
190 }
191 
FindPointAndRadius(Point & C,Float & r,const Point & innerTangent,const Point & normal,Float t)192 void DottedCornerFinder::FindPointAndRadius(Point& C, Float& r,
193                                             const Point& innerTangent,
194                                             const Point& normal, Float t) {
195   // Find radius for the given tangent point on the inner curve such that the
196   // circle is also tangent to the outer curve.
197 
198   NS_ASSERTION(mType == OTHER, "Wrong mType");
199 
200   Float lower = 0.0f;
201   Float upper = mMaxR;
202   const Float DIST_MARGIN = 0.1f;
203   for (size_t i = 0; i < MAX_LOOP; i++) {
204     r = (upper + lower) / 2.0f;
205     C = innerTangent + normal * r;
206 
207     Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
208     Float distSquare = (C - Near).LengthSquare();
209 
210     if (distSquare > Square(r + DIST_MARGIN)) {
211       lower = r;
212     } else if (distSquare < Square(r - DIST_MARGIN)) {
213       upper = r;
214     } else {
215       break;
216     }
217   }
218 }
219 
FindNext(Float overlap)220 Float DottedCornerFinder::FindNext(Float overlap) {
221   Float lower = mLastT;
222   Float upper = 1.0f;
223   Float t;
224 
225   Point C = mLastC;
226   Float r = 0.0f;
227 
228   Float factor = (1.0f - overlap);
229 
230   Float circlesDist = 0.0f;
231   Float expectedDist = 0.0f;
232 
233   const Float DIST_MARGIN = 0.1f;
234   if (mType == SINGLE_CURVE_AND_RADIUS) {
235     r = mR0;
236 
237     expectedDist = (r + mLastR) * factor;
238 
239     // Find C_i on the center curve.
240     for (size_t i = 0; i < MAX_LOOP; i++) {
241       t = (upper + lower) / 2.0f;
242       C = GetBezierPoint(mCenterBezier, t);
243 
244       // Check overlap along arc.
245       circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
246       if (circlesDist < expectedDist - DIST_MARGIN) {
247         lower = t;
248       } else if (circlesDist > expectedDist + DIST_MARGIN) {
249         upper = t;
250       } else {
251         break;
252       }
253     }
254   } else if (mType == SINGLE_CURVE) {
255     // Find C_i on the center curve, and calculate r_i.
256     for (size_t i = 0; i < MAX_LOOP; i++) {
257       t = (upper + lower) / 2.0f;
258       C = GetBezierPoint(mCenterBezier, t);
259 
260       Point Diff = GetBezierDifferential(mCenterBezier, t);
261       Float DiffLength = Diff.Length();
262       if (DiffLength == 0.0f) {
263         // Basically this shouldn't happen.
264         // If differential is 0, we cannot calculate tangent circle,
265         // skip this point.
266         t = (t + upper) / 2.0f;
267         continue;
268       }
269 
270       Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
271       r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
272                                          mInnerWidth, mInnerHeight);
273 
274       // Check overlap along arc.
275       circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
276       expectedDist = (r + mLastR) * factor;
277       if (circlesDist < expectedDist - DIST_MARGIN) {
278         lower = t;
279       } else if (circlesDist > expectedDist + DIST_MARGIN) {
280         upper = t;
281       } else {
282         break;
283       }
284     }
285   } else {
286     Float distSquareMax = Square(mMaxR * 3.0f);
287     Float circlesDistSquare = 0.0f;
288 
289     // Find C_i and r_i.
290     for (size_t i = 0; i < MAX_LOOP; i++) {
291       t = (upper + lower) / 2.0f;
292       Point innerTangent = GetBezierPoint(mInnerBezier, t);
293       if ((innerTangent - mLastC).LengthSquare() > distSquareMax) {
294         // It's clear that this tangent point is too far, skip it.
295         upper = t;
296         continue;
297       }
298 
299       Point Diff = GetBezierDifferential(mInnerBezier, t);
300       Float DiffLength = Diff.Length();
301       if (DiffLength == 0.0f) {
302         // Basically this shouldn't happen.
303         // If differential is 0, we cannot calculate tangent circle,
304         // skip this point.
305         t = (t + upper) / 2.0f;
306         continue;
307       }
308 
309       Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
310       FindPointAndRadius(C, r, innerTangent, normal, t);
311 
312       // Check overlap with direct distance.
313       circlesDistSquare = (C - mLastC).LengthSquare();
314       expectedDist = (r + mLastR) * factor;
315       if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) {
316         lower = t;
317       } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) {
318         upper = t;
319       } else {
320         break;
321       }
322     }
323 
324     circlesDist = sqrt(circlesDistSquare);
325   }
326 
327   if (mHasZeroBorderWidth) {
328     // When calculating circle around r=0, it may result in wrong radius that
329     // is bigger than previous circle.  Detect it and stop calculating.
330     const Float R_MARGIN = 0.1f;
331     if (mLastR < R_MARGIN && r > mLastR) {
332       mHasMore = false;
333       mLastR = 0.0f;
334       return 0.0f;
335     }
336   }
337 
338   mLastT = t;
339   mLastC = C;
340   mLastR = r;
341 
342   if (mHasZeroBorderWidth) {
343     const Float T_MARGIN = 0.001f;
344     if (mLastT >= 1.0f - T_MARGIN ||
345         (mLastC - mCn).LengthSquare() < Square(mLastR)) {
346       mHasMore = false;
347     }
348   }
349 
350   if (expectedDist == 0.0f) {
351     return 0.0f;
352   }
353 
354   return 1.0f - circlesDist * factor / expectedDist;
355 }
356 
FindBestOverlap(Float aMinR,Float aMinBorderRadius,Float aMaxBorderRadius)357 void DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius,
358                                          Float aMaxBorderRadius) {
359   // If overlap is not calculateable, find it with binary search,
360   // such that there exists i that C_i == C_n with the given overlap.
361 
362   FourFloats key(aMinR, mMaxR, aMinBorderRadius, aMaxBorderRadius);
363   BestOverlap best;
364   if (DottedCornerCache.Get(key, &best)) {
365     mCount = best.count;
366     mBestOverlap = best.overlap;
367     return;
368   }
369 
370   Float lower = 0.0f;
371   Float upper = 0.5f;
372   // Start from lower bound to find the minimum number of circles.
373   Float overlap = 0.0f;
374   mBestOverlap = overlap;
375   size_t targetCount = 0;
376 
377   const Float OVERLAP_MARGIN = 0.1f;
378   for (size_t j = 0; j < MAX_LOOP; j++) {
379     Reset();
380 
381     size_t count;
382     Float actualOverlap;
383     if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
384       if (j == 0) {
385         mCount = mMaxCount;
386         break;
387       }
388     }
389 
390     if (j == 0) {
391       if (count < 3 || (count == 3 && actualOverlap > 0.5f)) {
392         // |count == 3 && actualOverlap > 0.5f| means there could be
393         // a circle but it is too near from both ends.
394         //
395         // if actualOverlap == 0.0
396         //               1       2       3
397         //   +-------+-------+-------+-------+
398         //   | ##### | ***** | ##### | ##### |
399         //   |#######|*******|#######|#######|
400         //   |###+###|***+***|###+###|###+###|
401         //   |# C_0 #|* C_1 *|# C_2 #|# C_n #|
402         //   | ##### | ***** | ##### | ##### |
403         //   +-------+-------+-------+-------+
404         //                   |
405         //                   V
406         //   +-------+---+-------+---+-------+
407         //   | ##### |   | ##### |   | ##### |
408         //   |#######|   |#######|   |#######|
409         //   |###+###|   |###+###|   |###+###| Find the best overlap to place
410         //   |# C_0 #|   |# C_1 #|   |# C_n #| C_1 at the middle of them
411         //   | ##### |   | ##### |   | ##### |
412         //   +-------+---+-------+---|-------+
413         //
414         // if actualOverlap == 0.5
415         //               1       2     3
416         //   +-------+-------+-------+---+
417         //   | ##### | ***** | ##### |## |
418         //   |#######|*******|##### C_n #|
419         //   |###+###|***+***|###+###+###|
420         //   |# C_0 #|* C_1 *|# C_2 #|###|
421         //   | ##### | ***** | ##### |## |
422         //   +-------+-------+-------+---+
423         //                 |
424         //                 V
425         //   +-------+-+-------+-+-------+
426         //   | ##### | | ##### | | ##### |
427         //   |#######| |#######| |#######|
428         //   |###+###| |###+###| |###+###| Even if we place C_1 at the middle
429         //   |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
430         //   | ##### | | ##### | | ##### |
431         //   +-------+-+-------+-|-------+
432         //                 |
433         //                 V
434         //   +-------+-----------+-------+
435         //   | ##### |           | ##### |
436         //   |#######|           |#######|
437         //   |###+###|           |###+###| Do not draw any circle
438         //   |# C_0 #|           |# C_n #|
439         //   | ##### |           | ##### |
440         //   +-------+-----------+-------+
441         mCount = 0;
442         break;
443       }
444 
445       // targetCount should be 2n, as we're searching C_1 to C_n.
446       //
447       //   targetCount = 4
448       //   mCount = 1
449       //               1       2       3       4
450       //   +-------+-------+-------+-------+-------+
451       //   | ##### | ***** | ##### | ***** | ##### |
452       //   |#######|*******|#######|*******|#######|
453       //   |###+###|***+***|###+###|***+***|###+###|
454       //   |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
455       //   | ##### | ***** | ##### | ***** | ##### |
456       //   +-------+-------+-------+-------+-------+
457       //                       1
458       //
459       //   targetCount = 6
460       //   mCount = 2
461       //               1       2       3       4       5       6
462       //   +-------+-------+-------+-------+-------+-------+-------+
463       //   | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
464       //   |#######|*******|#######|*******|#######|*******|#######|
465       //   |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
466       //   |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
467       //   | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
468       //   +-------+-------+-------+-------+-------+-------+-------+
469       //                       1               2
470       if (count % 2) {
471         targetCount = count + 1;
472       } else {
473         targetCount = count;
474       }
475 
476       mCount = targetCount / 2 - 1;
477     }
478 
479     if (count == targetCount) {
480       mBestOverlap = overlap;
481 
482       if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
483         break;
484       }
485 
486       // We started from upper bound, no need to update range when j == 0.
487       if (j > 0) {
488         if (actualOverlap > overlap) {
489           lower = overlap;
490         } else {
491           upper = overlap;
492         }
493       }
494     } else {
495       // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
496       // and we started from upper bound, no need to update range when j == 0.
497       if (j > 0) {
498         if (count > targetCount) {
499           upper = overlap;
500         } else {
501           lower = overlap;
502         }
503       }
504     }
505 
506     overlap = (upper + lower) / 2.0f;
507   }
508 
509   if (DottedCornerCache.Count() > DottedCornerCacheSize) {
510     DottedCornerCache.Clear();
511   }
512   DottedCornerCache.InsertOrUpdate(key, BestOverlap(mBestOverlap, mCount));
513 }
514 
GetCountAndLastOverlap(Float aOverlap,size_t * aCount,Float * aActualOverlap)515 bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap, size_t* aCount,
516                                                 Float* aActualOverlap) {
517   // Return the number of circles and the last circles' overlap for the
518   // given overlap.
519 
520   Reset();
521 
522   const Float T_MARGIN = 0.001f;
523   const Float DIST_MARGIN = 0.1f;
524   const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN);
525   for (size_t i = 0; i < mMaxCount; i++) {
526     Float actualOverlap = FindNext(aOverlap);
527     if (mLastT >= 1.0f - T_MARGIN ||
528         (mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) {
529       *aCount = i + 1;
530       *aActualOverlap = actualOverlap;
531       return true;
532     }
533   }
534 
535   return false;
536 }
537 
538 }  // namespace mozilla
539