tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DashedCornerFinder.cpp (12697B)


      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 "DashedCornerFinder.h"
      8 
      9 #include <utility>
     10 
     11 #include "BorderCache.h"
     12 #include "BorderConsts.h"
     13 #include "nsTHashMap.h"
     14 
     15 namespace mozilla {
     16 
     17 using namespace gfx;
     18 
     19 struct BestDashLength {
     20  typedef mozilla::gfx::Float Float;
     21 
     22  Float dashLength;
     23  size_t count;
     24 
     25  BestDashLength() : dashLength(0.0f), count(0) {}
     26 
     27  BestDashLength(Float aDashLength, size_t aCount)
     28      : dashLength(aDashLength), count(aCount) {}
     29 };
     30 
     31 static const size_t DashedCornerCacheSize = 256;
     32 MOZ_RUNINIT nsTHashMap<FourFloatsHashKey, BestDashLength> DashedCornerCache;
     33 
     34 DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier,
     35                                       const Bezier& aInnerBezier,
     36                                       Float aBorderWidthH, Float aBorderWidthV,
     37                                       const Size& aCornerDim)
     38    : mOuterBezier(aOuterBezier),
     39      mInnerBezier(aInnerBezier),
     40      mLastOuterP(aOuterBezier.mPoints[0]),
     41      mLastInnerP(aInnerBezier.mPoints[0]),
     42      mLastOuterT(0.0f),
     43      mLastInnerT(0.0f),
     44      mBestDashLength(DOT_LENGTH * DASH_LENGTH),
     45      mHasZeroBorderWidth(false),
     46      mHasMore(true),
     47      mMaxCount(aCornerDim.width + aCornerDim.height),
     48      mType(OTHER),
     49      mI(0),
     50      mCount(0) {
     51  NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f,
     52               "At least one side should have non-zero width.");
     53 
     54  DetermineType(aBorderWidthH, aBorderWidthV);
     55 
     56  Reset();
     57 }
     58 
     59 void DashedCornerFinder::DetermineType(Float aBorderWidthH,
     60                                       Float aBorderWidthV) {
     61  if (aBorderWidthH < aBorderWidthV) {
     62    // Always draw from wider side to thinner side.
     63    std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
     64    std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
     65    std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
     66    std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
     67    mLastOuterP = mOuterBezier.mPoints[0];
     68    mLastInnerP = mInnerBezier.mPoints[0];
     69  }
     70 
     71  // See the comment at mType declaration for each condition.
     72 
     73  Float borderRadiusA =
     74      fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x);
     75  Float borderRadiusB =
     76      fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y);
     77  if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB &&
     78      borderRadiusA > aBorderWidthH * 2.0f) {
     79    Float curveHeight = borderRadiusA - aBorderWidthH / 2.0;
     80 
     81    mType = PERFECT;
     82    Float borderLength = M_PI * curveHeight / 2.0f;
     83 
     84    Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH;
     85    size_t count = ceil(borderLength / dashWidth);
     86    if (count % 2) {
     87      count++;
     88    }
     89    mCount = count / 2 + 1;
     90    mBestDashLength = borderLength / (aBorderWidthH * count);
     91  }
     92 
     93  Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV);
     94  if (minBorderWidth == 0.0f) {
     95    mHasZeroBorderWidth = true;
     96  }
     97 
     98  if (mType == OTHER && !mHasZeroBorderWidth) {
     99    Float minBorderRadius = std::min(borderRadiusA, borderRadiusB);
    100    Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB);
    101    Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV);
    102 
    103    FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius,
    104                       maxBorderRadius);
    105  }
    106 }
    107 
    108 bool DashedCornerFinder::HasMore(void) const {
    109  if (mHasZeroBorderWidth) {
    110    return mI < mMaxCount && mHasMore;
    111  }
    112 
    113  return mI < mCount;
    114 }
    115 
    116 DashedCornerFinder::Result DashedCornerFinder::Next(void) {
    117  Float lastOuterT, lastInnerT, outerT, innerT;
    118 
    119  if (mI == 0) {
    120    lastOuterT = 0.0f;
    121    lastInnerT = 0.0f;
    122  } else {
    123    if (mType == PERFECT) {
    124      lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f);
    125    } else {
    126      Float last2OuterT = mLastOuterT;
    127      Float last2InnerT = mLastInnerT;
    128 
    129      (void)FindNext(mBestDashLength);
    130 
    131      //
    132      //          mLastOuterT   lastOuterT
    133      //                    |   |
    134      //                    v   v
    135      //            +---+---+---+---+ <- last2OuterT
    136      //            |   |###|###|   |
    137      //            |   |###|###|   |
    138      //            |   |###|###|   |
    139      //            +---+---+---+---+ <- last2InnerT
    140      //                    ^   ^
    141      //                    |   |
    142      //          mLastInnerT   lastInnerT
    143      lastOuterT = (mLastOuterT + last2OuterT) / 2.0f;
    144      lastInnerT = (mLastInnerT + last2InnerT) / 2.0f;
    145    }
    146  }
    147 
    148  if ((!mHasZeroBorderWidth && mI == mCount - 1) ||
    149      (mHasZeroBorderWidth && !mHasMore)) {
    150    outerT = 1.0f;
    151    innerT = 1.0f;
    152  } else {
    153    if (mType == PERFECT) {
    154      outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f);
    155    } else {
    156      Float last2OuterT = mLastOuterT;
    157      Float last2InnerT = mLastInnerT;
    158 
    159      (void)FindNext(mBestDashLength);
    160 
    161      //
    162      //               outerT   last2OuterT
    163      //                    |   |
    164      //                    v   v
    165      // mLastOuterT -> +---+---+---+---+
    166      //                |   |###|###|   |
    167      //                |   |###|###|   |
    168      //                |   |###|###|   |
    169      // mLastInnerT -> +---+---+---+---+
    170      //                    ^   ^
    171      //                    |   |
    172      //               innerT   last2InnerT
    173      outerT = (mLastOuterT + last2OuterT) / 2.0f;
    174      innerT = (mLastInnerT + last2InnerT) / 2.0f;
    175    }
    176  }
    177 
    178  mI++;
    179 
    180  Bezier outerSectionBezier;
    181  Bezier innerSectionBezier;
    182  GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT);
    183  GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT);
    184  return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier);
    185 }
    186 
    187 void DashedCornerFinder::Reset(void) {
    188  mLastOuterP = mOuterBezier.mPoints[0];
    189  mLastInnerP = mInnerBezier.mPoints[0];
    190  mLastOuterT = 0.0f;
    191  mLastInnerT = 0.0f;
    192  mHasMore = true;
    193 }
    194 
    195 Float DashedCornerFinder::FindNext(Float dashLength) {
    196  Float upper = 1.0f;
    197  Float lower = mLastOuterT;
    198 
    199  Point OuterP, InnerP;
    200  // Start from upper bound to check if this is the last segment.
    201  Float outerT = upper;
    202  Float innerT;
    203  Float W = 0.0f;
    204  Float L = 0.0f;
    205 
    206  const Float LENGTH_MARGIN = 0.1f;
    207  for (size_t i = 0; i < MAX_LOOP; i++) {
    208    OuterP = GetBezierPoint(mOuterBezier, outerT);
    209    InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT);
    210 
    211    // Calculate approximate dash length.
    212    //
    213    //   W = (W1 + W2) / 2
    214    //   L = (OuterL + InnerL) / 2
    215    //   dashLength = L / W
    216    //
    217    //              ____----+----____
    218    // OuterP ___---        |         ---___    mLastOuterP
    219    //    +---              |               ---+
    220    //    |                  |                 |
    221    //    |                  |                 |
    222    //     |               W |              W1 |
    223    //     |                  |                |
    224    //  W2 |                  |                |
    225    //     |                  |    ______------+
    226    //     |              ____+----             mLastInnerP
    227    //      |       ___---
    228    //      |  __---
    229    //      +--
    230    //    InnerP
    231    //                     OuterL
    232    //              ____---------____
    233    // OuterP ___---                  ---___    mLastOuterP
    234    //    +---                              ---+
    235    //    |                  L                 |
    236    //    |            ___----------______     |
    237    //     |      __---                   -----+
    238    //     |  __--                             |
    239    //     +--                                 |
    240    //     |                InnerL ______------+
    241    //     |              ____-----             mLastInnerP
    242    //      |       ___---
    243    //      |  __---
    244    //      +--
    245    //    InnerP
    246    Float W1 = (mLastOuterP - mLastInnerP).Length();
    247    Float W2 = (OuterP - InnerP).Length();
    248    Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT);
    249    Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT);
    250    W = (W1 + W2) / 2.0f;
    251    L = (OuterL + InnerL) / 2.0f;
    252    if (L > W * dashLength + LENGTH_MARGIN) {
    253      if (i > 0) {
    254        upper = outerT;
    255      }
    256    } else if (L < W * dashLength - LENGTH_MARGIN) {
    257      if (i == 0) {
    258        // This is the last segment with shorter dashLength.
    259        mHasMore = false;
    260        break;
    261      }
    262      lower = outerT;
    263    } else {
    264      break;
    265    }
    266 
    267    outerT = (upper + lower) / 2.0f;
    268  }
    269 
    270  mLastOuterP = OuterP;
    271  mLastInnerP = InnerP;
    272  mLastOuterT = outerT;
    273  mLastInnerT = innerT;
    274 
    275  if (W == 0.0f) {
    276    return 1.0f;
    277  }
    278 
    279  return L / W;
    280 }
    281 
    282 void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth,
    283                                            Float aMaxBorderWidth,
    284                                            Float aMinBorderRadius,
    285                                            Float aMaxBorderRadius) {
    286  // If dashLength is not calculateable, find it with binary search,
    287  // such that there exists i that OuterP_i == OuterP_n and
    288  // InnerP_i == InnerP_n with given dashLength.
    289 
    290  FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius,
    291                 aMaxBorderRadius);
    292  BestDashLength best;
    293  if (DashedCornerCache.Get(key, &best)) {
    294    mCount = best.count;
    295    mBestDashLength = best.dashLength;
    296    return;
    297  }
    298 
    299  Float lower = 1.0f;
    300  Float upper = DOT_LENGTH * DASH_LENGTH;
    301  Float dashLength = upper;
    302  size_t targetCount = 0;
    303 
    304  const Float LENGTH_MARGIN = 0.1f;
    305  for (size_t j = 0; j < MAX_LOOP; j++) {
    306    size_t count;
    307    Float actualDashLength;
    308    if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) {
    309      if (j == 0) {
    310        mCount = mMaxCount;
    311        break;
    312      }
    313    }
    314 
    315    if (j == 0) {
    316      if (count == 1) {
    317        // If only 1 segment fits, fill entire region
    318        //
    319        //   count = 1
    320        //   mCount = 1
    321        //   |   1   |
    322        //   +---+---+
    323        //   |###|###|
    324        //   |###|###|
    325        //   |###|###|
    326        //   +---+---+
    327        //       1
    328        mCount = 1;
    329        break;
    330      }
    331 
    332      // targetCount should be 2n.
    333      //
    334      //   targetCount = 2
    335      //   mCount = 2
    336      //   |   1   |   2   |
    337      //   +---+---+---+---+
    338      //   |###|   |   |###|
    339      //   |###|   |   |###|
    340      //   |###|   |   |###|
    341      //   +---+---+---+---+
    342      //     1           2
    343      //
    344      //   targetCount = 6
    345      //   mCount = 4
    346      //   |   1   |   2   |   3   |   4   |   5   |   6   |
    347      //   +---+---+---+---+---+---+---+---+---+---+---+---+
    348      //   |###|   |   |###|###|   |   |###|###|   |   |###|
    349      //   |###|   |   |###|###|   |   |###|###|   |   |###|
    350      //   |###|   |   |###|###|   |   |###|###|   |   |###|
    351      //   +---+---+---+---+---+---+---+---+---+---+---+---+
    352      //     1             2               3             4
    353      if (count % 2) {
    354        targetCount = count + 1;
    355      } else {
    356        targetCount = count;
    357      }
    358 
    359      mCount = targetCount / 2 + 1;
    360    }
    361 
    362    if (count == targetCount) {
    363      mBestDashLength = dashLength;
    364 
    365      // actualDashLength won't be greater than dashLength.
    366      if (actualDashLength > dashLength - LENGTH_MARGIN) {
    367        break;
    368      }
    369 
    370      // We started from upper bound, no need to update range when j == 0.
    371      if (j > 0) {
    372        upper = dashLength;
    373      }
    374    } else {
    375      // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
    376      // and we started from upper bound, no need to update range when j == 0.
    377      if (j > 0) {
    378        if (count > targetCount) {
    379          lower = dashLength;
    380        } else {
    381          upper = dashLength;
    382        }
    383      }
    384    }
    385 
    386    dashLength = (upper + lower) / 2.0f;
    387  }
    388 
    389  if (DashedCornerCache.Count() > DashedCornerCacheSize) {
    390    DashedCornerCache.Clear();
    391  }
    392  DashedCornerCache.InsertOrUpdate(key,
    393                                   BestDashLength(mBestDashLength, mCount));
    394 }
    395 
    396 bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength,
    397                                                   size_t* aCount,
    398                                                   Float* aActualDashLength) {
    399  // Return the number of segments and the last segment's dashLength for
    400  // the given dashLength.
    401 
    402  Reset();
    403 
    404  for (size_t i = 0; i < mMaxCount; i++) {
    405    Float actualDashLength = FindNext(aDashLength);
    406    if (mLastOuterT >= 1.0f) {
    407      *aCount = i + 1;
    408      *aActualDashLength = actualDashLength;
    409      return true;
    410    }
    411  }
    412 
    413  return false;
    414 }
    415 
    416 }  // namespace mozilla