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