Coverage Report

Created: 2018-09-25 14:53

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