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/. */
7 #include "DottedCornerFinder.h"
11 #include "BorderCache.h"
12 #include "BorderConsts.h"
13 #include "nsTHashMap.h"
19 static inline Float
Square(Float x
) { return x
* x
; }
21 static Point
PointRotateCCW90(const Point
& aP
) { return Point(aP
.y
, -aP
.x
); }
27 BestOverlap() : overlap(0.0f
), count(0) {}
29 BestOverlap(Float aOverlap
, size_t aCount
)
30 : overlap(aOverlap
), count(aCount
) {}
33 static const size_t DottedCornerCacheSize
= 256;
34 nsTHashMap
<FourFloatsHashKey
, BestOverlap
> DottedCornerCache
;
36 DottedCornerFinder::DottedCornerFinder(const Bezier
& aOuterBezier
,
37 const Bezier
& aInnerBezier
,
38 Corner aCorner
, Float aBorderRadiusX
,
39 Float aBorderRadiusY
, const Point
& aC0
,
40 Float aR0
, const Point
& aCn
, Float aRn
,
41 const Size
& aCornerDim
)
42 : mOuterBezier(aOuterBezier
),
43 mInnerBezier(aInnerBezier
),
45 mNormalSign((aCorner
== C_TL
|| aCorner
== C_BR
) ? -1.0f
: 1.0f
),
50 mMaxR(std::max(aR0
, aRn
)),
51 mCenterCurveOrigin(mC0
.x
, mCn
.y
),
53 mInnerCurveOrigin(mInnerBezier
.mPoints
[0].x
, mInnerBezier
.mPoints
[3].y
),
55 mHasZeroBorderWidth(false),
57 mMaxCount(aCornerDim
.width
+ aCornerDim
.height
),
61 NS_ASSERTION(mR0
> 0.0f
|| mRn
> 0.0f
,
62 "At least one side should have non-zero radius.");
64 mInnerWidth
= fabs(mInnerBezier
.mPoints
[0].x
- mInnerBezier
.mPoints
[3].x
);
65 mInnerHeight
= fabs(mInnerBezier
.mPoints
[0].y
- mInnerBezier
.mPoints
[3].y
);
67 DetermineType(aBorderRadiusX
, aBorderRadiusY
);
72 static bool IsSingleCurve(Float aMinR
, Float aMaxR
, Float aMinBorderRadius
,
73 Float aMaxBorderRadius
) {
74 return aMinR
> 0.0f
&& aMinBorderRadius
> aMaxR
* 4.0f
&&
75 aMinBorderRadius
/ aMaxBorderRadius
> 0.5f
;
78 void DottedCornerFinder::DetermineType(Float aBorderRadiusX
,
79 Float aBorderRadiusY
) {
80 // Calculate parameters for the center curve before swap.
81 Float centerCurveWidth
= fabs(mC0
.x
- mCn
.x
);
82 Float centerCurveHeight
= fabs(mC0
.y
- mCn
.y
);
83 Point
cornerPoint(mCn
.x
, mC0
.y
);
87 // Always draw from wider side to thinner side.
90 std::swap(mInnerBezier
.mPoints
[0], mInnerBezier
.mPoints
[3]);
91 std::swap(mInnerBezier
.mPoints
[1], mInnerBezier
.mPoints
[2]);
92 std::swap(mOuterBezier
.mPoints
[0], mOuterBezier
.mPoints
[3]);
93 std::swap(mOuterBezier
.mPoints
[1], mOuterBezier
.mPoints
[2]);
94 mNormalSign
= -mNormalSign
;
98 // See the comment at mType declaration for each condition.
100 Float minR
= std::min(mR0
, mRn
);
101 Float minBorderRadius
= std::min(aBorderRadiusX
, aBorderRadiusY
);
102 Float maxBorderRadius
= std::max(aBorderRadiusX
, aBorderRadiusY
);
103 if (IsSingleCurve(minR
, mMaxR
, minBorderRadius
, maxBorderRadius
)) {
106 if (minBorderRadius
== maxBorderRadius
) {
108 borderLength
= M_PI
* centerCurveHeight
/ 2.0f
;
110 mCenterCurveR
= centerCurveWidth
;
112 mType
= SINGLE_CURVE_AND_RADIUS
;
114 GetQuarterEllipticArcLength(centerCurveWidth
, centerCurveHeight
);
117 Float diameter
= mR0
* 2.0f
;
118 size_t count
= round(borderLength
/ diameter
);
122 mCount
= count
/ 2 - 1;
124 mBestOverlap
= 1.0f
- borderLength
/ (diameter
* count
);
127 mType
= SINGLE_CURVE
;
131 if (mType
== SINGLE_CURVE_AND_RADIUS
|| mType
== SINGLE_CURVE
) {
132 Size
cornerSize(centerCurveWidth
, centerCurveHeight
);
133 GetBezierPointsForCorner(&mCenterBezier
, mCorner
, cornerPoint
, cornerSize
);
135 std::swap(mCenterBezier
.mPoints
[0], mCenterBezier
.mPoints
[3]);
136 std::swap(mCenterBezier
.mPoints
[1], mCenterBezier
.mPoints
[2]);
141 mHasZeroBorderWidth
= true;
144 if ((mType
== SINGLE_CURVE
|| mType
== OTHER
) && !mHasZeroBorderWidth
) {
145 FindBestOverlap(minR
, minBorderRadius
, maxBorderRadius
);
149 bool DottedCornerFinder::HasMore(void) const {
150 if (mHasZeroBorderWidth
) {
151 return mI
< mMaxCount
&& mHasMore
;
157 DottedCornerFinder::Result
DottedCornerFinder::Next(void) {
160 if (mType
== PERFECT
) {
161 Float phi
= mI
* 4.0f
* mR0
* (1 - mBestOverlap
) / mCenterCurveR
;
162 if (mCorner
== C_TL
) {
163 phi
= -M_PI
/ 2.0f
- phi
;
164 } else if (mCorner
== C_TR
) {
165 phi
= -M_PI
/ 2.0f
+ phi
;
166 } else if (mCorner
== C_BR
) {
167 phi
= M_PI
/ 2.0f
- phi
;
169 phi
= M_PI
/ 2.0f
+ phi
;
172 Point
C(mCenterCurveOrigin
.x
+ mCenterCurveR
* cos(phi
),
173 mCenterCurveOrigin
.y
+ mCenterCurveR
* sin(phi
));
174 return DottedCornerFinder::Result(C
, mR0
);
177 // Find unfilled and filled circles.
178 (void)FindNext(mBestOverlap
);
180 (void)FindNext(mBestOverlap
);
183 return Result(mLastC
, mLastR
);
186 void DottedCornerFinder::Reset(void) {
193 void DottedCornerFinder::FindPointAndRadius(Point
& C
, Float
& r
,
194 const Point
& innerTangent
,
195 const Point
& normal
, Float t
) {
196 // Find radius for the given tangent point on the inner curve such that the
197 // circle is also tangent to the outer curve.
199 NS_ASSERTION(mType
== OTHER
, "Wrong mType");
203 const Float DIST_MARGIN
= 0.1f
;
204 for (size_t i
= 0; i
< MAX_LOOP
; i
++) {
205 r
= (upper
+ lower
) / 2.0f
;
206 C
= innerTangent
+ normal
* r
;
208 Point Near
= FindBezierNearestPoint(mOuterBezier
, C
, t
);
209 Float distSquare
= (C
- Near
).LengthSquare();
211 if (distSquare
> Square(r
+ DIST_MARGIN
)) {
213 } else if (distSquare
< Square(r
- DIST_MARGIN
)) {
221 Float
DottedCornerFinder::FindNext(Float overlap
) {
222 Float lower
= mLastT
;
229 Float factor
= (1.0f
- overlap
);
231 Float circlesDist
= 0.0f
;
232 Float expectedDist
= 0.0f
;
234 const Float DIST_MARGIN
= 0.1f
;
235 if (mType
== SINGLE_CURVE_AND_RADIUS
) {
238 expectedDist
= (r
+ mLastR
) * factor
;
240 // Find C_i on the center curve.
241 for (size_t i
= 0; i
< MAX_LOOP
; i
++) {
242 t
= (upper
+ lower
) / 2.0f
;
243 C
= GetBezierPoint(mCenterBezier
, t
);
245 // Check overlap along arc.
246 circlesDist
= GetBezierLength(mCenterBezier
, mLastT
, t
);
247 if (circlesDist
< expectedDist
- DIST_MARGIN
) {
249 } else if (circlesDist
> expectedDist
+ DIST_MARGIN
) {
255 } else if (mType
== SINGLE_CURVE
) {
256 // Find C_i on the center curve, and calculate r_i.
257 for (size_t i
= 0; i
< MAX_LOOP
; i
++) {
258 t
= (upper
+ lower
) / 2.0f
;
259 C
= GetBezierPoint(mCenterBezier
, t
);
261 Point Diff
= GetBezierDifferential(mCenterBezier
, t
);
262 Float DiffLength
= Diff
.Length();
263 if (DiffLength
== 0.0f
) {
264 // Basically this shouldn't happen.
265 // If differential is 0, we cannot calculate tangent circle,
267 t
= (t
+ upper
) / 2.0f
;
271 Point normal
= PointRotateCCW90(Diff
/ DiffLength
) * (-mNormalSign
);
272 r
= CalculateDistanceToEllipticArc(C
, normal
, mInnerCurveOrigin
,
273 mInnerWidth
, mInnerHeight
);
275 // Check overlap along arc.
276 circlesDist
= GetBezierLength(mCenterBezier
, mLastT
, t
);
277 expectedDist
= (r
+ mLastR
) * factor
;
278 if (circlesDist
< expectedDist
- DIST_MARGIN
) {
280 } else if (circlesDist
> expectedDist
+ DIST_MARGIN
) {
287 Float distSquareMax
= Square(mMaxR
* 3.0f
);
288 Float circlesDistSquare
= 0.0f
;
291 for (size_t i
= 0; i
< MAX_LOOP
; i
++) {
292 t
= (upper
+ lower
) / 2.0f
;
293 Point innerTangent
= GetBezierPoint(mInnerBezier
, t
);
294 if ((innerTangent
- mLastC
).LengthSquare() > distSquareMax
) {
295 // It's clear that this tangent point is too far, skip it.
300 Point Diff
= GetBezierDifferential(mInnerBezier
, t
);
301 Float DiffLength
= Diff
.Length();
302 if (DiffLength
== 0.0f
) {
303 // Basically this shouldn't happen.
304 // If differential is 0, we cannot calculate tangent circle,
306 t
= (t
+ upper
) / 2.0f
;
310 Point normal
= PointRotateCCW90(Diff
/ DiffLength
) * mNormalSign
;
311 FindPointAndRadius(C
, r
, innerTangent
, normal
, t
);
313 // Check overlap with direct distance.
314 circlesDistSquare
= (C
- mLastC
).LengthSquare();
315 expectedDist
= (r
+ mLastR
) * factor
;
316 if (circlesDistSquare
< Square(expectedDist
- DIST_MARGIN
)) {
318 } else if (circlesDistSquare
> Square(expectedDist
+ DIST_MARGIN
)) {
325 circlesDist
= sqrt(circlesDistSquare
);
328 if (mHasZeroBorderWidth
) {
329 // When calculating circle around r=0, it may result in wrong radius that
330 // is bigger than previous circle. Detect it and stop calculating.
331 const Float R_MARGIN
= 0.1f
;
332 if (mLastR
< R_MARGIN
&& r
> mLastR
) {
343 if (mHasZeroBorderWidth
) {
344 const Float T_MARGIN
= 0.001f
;
345 if (mLastT
>= 1.0f
- T_MARGIN
||
346 (mLastC
- mCn
).LengthSquare() < Square(mLastR
)) {
351 if (expectedDist
== 0.0f
) {
355 return 1.0f
- circlesDist
* factor
/ expectedDist
;
358 void DottedCornerFinder::FindBestOverlap(Float aMinR
, Float aMinBorderRadius
,
359 Float aMaxBorderRadius
) {
360 // If overlap is not calculateable, find it with binary search,
361 // such that there exists i that C_i == C_n with the given overlap.
363 FourFloats
key(aMinR
, mMaxR
, aMinBorderRadius
, aMaxBorderRadius
);
365 if (DottedCornerCache
.Get(key
, &best
)) {
367 mBestOverlap
= best
.overlap
;
373 // Start from lower bound to find the minimum number of circles.
374 Float overlap
= 0.0f
;
375 mBestOverlap
= overlap
;
376 size_t targetCount
= 0;
378 const Float OVERLAP_MARGIN
= 0.1f
;
379 for (size_t j
= 0; j
< MAX_LOOP
; j
++) {
384 if (!GetCountAndLastOverlap(overlap
, &count
, &actualOverlap
)) {
392 if (count
< 3 || (count
== 3 && actualOverlap
> 0.5f
)) {
393 // |count == 3 && actualOverlap > 0.5f| means there could be
394 // a circle but it is too near from both ends.
396 // if actualOverlap == 0.0
398 // +-------+-------+-------+-------+
399 // | ##### | ***** | ##### | ##### |
400 // |#######|*******|#######|#######|
401 // |###+###|***+***|###+###|###+###|
402 // |# C_0 #|* C_1 *|# C_2 #|# C_n #|
403 // | ##### | ***** | ##### | ##### |
404 // +-------+-------+-------+-------+
407 // +-------+---+-------+---+-------+
408 // | ##### | | ##### | | ##### |
409 // |#######| |#######| |#######|
410 // |###+###| |###+###| |###+###| Find the best overlap to place
411 // |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them
412 // | ##### | | ##### | | ##### |
413 // +-------+---+-------+---|-------+
415 // if actualOverlap == 0.5
417 // +-------+-------+-------+---+
418 // | ##### | ***** | ##### |## |
419 // |#######|*******|##### C_n #|
420 // |###+###|***+***|###+###+###|
421 // |# C_0 #|* C_1 *|# C_2 #|###|
422 // | ##### | ***** | ##### |## |
423 // +-------+-------+-------+---+
426 // +-------+-+-------+-+-------+
427 // | ##### | | ##### | | ##### |
428 // |#######| |#######| |#######|
429 // |###+###| |###+###| |###+###| Even if we place C_1 at the middle
430 // |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
431 // | ##### | | ##### | | ##### |
432 // +-------+-+-------+-|-------+
435 // +-------+-----------+-------+
436 // | ##### | | ##### |
437 // |#######| |#######|
438 // |###+###| |###+###| Do not draw any circle
439 // |# C_0 #| |# C_n #|
440 // | ##### | | ##### |
441 // +-------+-----------+-------+
446 // targetCount should be 2n, as we're searching C_1 to C_n.
451 // +-------+-------+-------+-------+-------+
452 // | ##### | ***** | ##### | ***** | ##### |
453 // |#######|*******|#######|*******|#######|
454 // |###+###|***+***|###+###|***+***|###+###|
455 // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
456 // | ##### | ***** | ##### | ***** | ##### |
457 // +-------+-------+-------+-------+-------+
463 // +-------+-------+-------+-------+-------+-------+-------+
464 // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
465 // |#######|*******|#######|*******|#######|*******|#######|
466 // |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
467 // |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
468 // | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
469 // +-------+-------+-------+-------+-------+-------+-------+
472 targetCount
= count
+ 1;
477 mCount
= targetCount
/ 2 - 1;
480 if (count
== targetCount
) {
481 mBestOverlap
= overlap
;
483 if (fabs(actualOverlap
- overlap
) < OVERLAP_MARGIN
) {
487 // We started from upper bound, no need to update range when j == 0.
489 if (actualOverlap
> overlap
) {
496 // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
497 // and we started from upper bound, no need to update range when j == 0.
499 if (count
> targetCount
) {
507 overlap
= (upper
+ lower
) / 2.0f
;
510 if (DottedCornerCache
.Count() > DottedCornerCacheSize
) {
511 DottedCornerCache
.Clear();
513 DottedCornerCache
.InsertOrUpdate(key
, BestOverlap(mBestOverlap
, mCount
));
516 bool DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap
, size_t* aCount
,
517 Float
* aActualOverlap
) {
518 // Return the number of circles and the last circles' overlap for the
523 const Float T_MARGIN
= 0.001f
;
524 const Float DIST_MARGIN
= 0.1f
;
525 const Float DIST_MARGIN_SQUARE
= Square(DIST_MARGIN
);
526 for (size_t i
= 0; i
< mMaxCount
; i
++) {
527 Float actualOverlap
= FindNext(aOverlap
);
528 if (mLastT
>= 1.0f
- T_MARGIN
||
529 (mLastC
- mCn
).LengthSquare() < DIST_MARGIN_SQUARE
) {
531 *aActualOverlap
= actualOverlap
;
539 } // namespace mozilla