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