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