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