diff options
Diffstat (limited to 'layout/painting/DashedCornerFinder.cpp')
-rw-r--r-- | layout/painting/DashedCornerFinder.cpp | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/layout/painting/DashedCornerFinder.cpp b/layout/painting/DashedCornerFinder.cpp new file mode 100644 index 0000000000..2fe9ddcb82 --- /dev/null +++ b/layout/painting/DashedCornerFinder.cpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DashedCornerFinder.h" + +#include <utility> + +#include "BorderCache.h" +#include "BorderConsts.h" + +namespace mozilla { + +using namespace gfx; + +struct BestDashLength { + typedef mozilla::gfx::Float Float; + + Float dashLength; + size_t count; + + BestDashLength() : dashLength(0.0f), count(0) {} + + BestDashLength(Float aDashLength, size_t aCount) + : dashLength(aDashLength), count(aCount) {} +}; + +static const size_t DashedCornerCacheSize = 256; +nsDataHashtable<FourFloatsHashKey, BestDashLength> DashedCornerCache; + +DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier, + const Bezier& aInnerBezier, + Float aBorderWidthH, Float aBorderWidthV, + const Size& aCornerDim) + : mOuterBezier(aOuterBezier), + mInnerBezier(aInnerBezier), + mLastOuterP(aOuterBezier.mPoints[0]), + mLastInnerP(aInnerBezier.mPoints[0]), + mLastOuterT(0.0f), + mLastInnerT(0.0f), + mBestDashLength(DOT_LENGTH * DASH_LENGTH), + mHasZeroBorderWidth(false), + mHasMore(true), + mMaxCount(aCornerDim.width + aCornerDim.height), + mType(OTHER), + mI(0), + mCount(0) { + NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f, + "At least one side should have non-zero width."); + + DetermineType(aBorderWidthH, aBorderWidthV); + + Reset(); +} + +void DashedCornerFinder::DetermineType(Float aBorderWidthH, + Float aBorderWidthV) { + if (aBorderWidthH < aBorderWidthV) { + // Always draw from wider side to thinner side. + std::swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]); + std::swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]); + std::swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]); + std::swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]); + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + } + + // See the comment at mType declaration for each condition. + + Float borderRadiusA = + fabs(mOuterBezier.mPoints[0].x - mOuterBezier.mPoints[3].x); + Float borderRadiusB = + fabs(mOuterBezier.mPoints[0].y - mOuterBezier.mPoints[3].y); + if (aBorderWidthH == aBorderWidthV && borderRadiusA == borderRadiusB && + borderRadiusA > aBorderWidthH * 2.0f) { + Float curveHeight = borderRadiusA - aBorderWidthH / 2.0; + + mType = PERFECT; + Float borderLength = M_PI * curveHeight / 2.0f; + + Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH; + size_t count = ceil(borderLength / dashWidth); + if (count % 2) { + count++; + } + mCount = count / 2 + 1; + mBestDashLength = borderLength / (aBorderWidthH * count); + } + + Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV); + if (minBorderWidth == 0.0f) { + mHasZeroBorderWidth = true; + } + + if (mType == OTHER && !mHasZeroBorderWidth) { + Float minBorderRadius = std::min(borderRadiusA, borderRadiusB); + Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB); + Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV); + + FindBestDashLength(minBorderWidth, maxBorderWidth, minBorderRadius, + maxBorderRadius); + } +} + +bool DashedCornerFinder::HasMore(void) const { + if (mHasZeroBorderWidth) { + return mI < mMaxCount && mHasMore; + } + + return mI < mCount; +} + +DashedCornerFinder::Result DashedCornerFinder::Next(void) { + Float lastOuterT, lastInnerT, outerT, innerT; + + if (mI == 0) { + lastOuterT = 0.0f; + lastInnerT = 0.0f; + } else { + if (mType == PERFECT) { + lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // mLastOuterT lastOuterT + // | | + // v v + // +---+---+---+---+ <- last2OuterT + // | |###|###| | + // | |###|###| | + // | |###|###| | + // +---+---+---+---+ <- last2InnerT + // ^ ^ + // | | + // mLastInnerT lastInnerT + lastOuterT = (mLastOuterT + last2OuterT) / 2.0f; + lastInnerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + if ((!mHasZeroBorderWidth && mI == mCount - 1) || + (mHasZeroBorderWidth && !mHasMore)) { + outerT = 1.0f; + innerT = 1.0f; + } else { + if (mType == PERFECT) { + outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f); + } else { + Float last2OuterT = mLastOuterT; + Float last2InnerT = mLastInnerT; + + (void)FindNext(mBestDashLength); + + // + // outerT last2OuterT + // | | + // v v + // mLastOuterT -> +---+---+---+---+ + // | |###|###| | + // | |###|###| | + // | |###|###| | + // mLastInnerT -> +---+---+---+---+ + // ^ ^ + // | | + // innerT last2InnerT + outerT = (mLastOuterT + last2OuterT) / 2.0f; + innerT = (mLastInnerT + last2InnerT) / 2.0f; + } + } + + mI++; + + Bezier outerSectionBezier; + Bezier innerSectionBezier; + GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT); + GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT); + return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier); +} + +void DashedCornerFinder::Reset(void) { + mLastOuterP = mOuterBezier.mPoints[0]; + mLastInnerP = mInnerBezier.mPoints[0]; + mLastOuterT = 0.0f; + mLastInnerT = 0.0f; + mHasMore = true; +} + +Float DashedCornerFinder::FindNext(Float dashLength) { + Float upper = 1.0f; + Float lower = mLastOuterT; + + Point OuterP, InnerP; + // Start from upper bound to check if this is the last segment. + Float outerT = upper; + Float innerT; + Float W = 0.0f; + Float L = 0.0f; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t i = 0; i < MAX_LOOP; i++) { + OuterP = GetBezierPoint(mOuterBezier, outerT); + InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT); + + // Calculate approximate dash length. + // + // W = (W1 + W2) / 2 + // L = (OuterL + InnerL) / 2 + // dashLength = L / W + // + // ____----+----____ + // OuterP ___--- | ---___ mLastOuterP + // +--- | ---+ + // | | | + // | | | + // | W | W1 | + // | | | + // W2 | | | + // | | ______------+ + // | ____+---- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + // OuterL + // ____---------____ + // OuterP ___--- ---___ mLastOuterP + // +--- ---+ + // | L | + // | ___----------______ | + // | __--- -----+ + // | __-- | + // +-- | + // | InnerL ______------+ + // | ____----- mLastInnerP + // | ___--- + // | __--- + // +-- + // InnerP + Float W1 = (mLastOuterP - mLastInnerP).Length(); + Float W2 = (OuterP - InnerP).Length(); + Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT); + Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT); + W = (W1 + W2) / 2.0f; + L = (OuterL + InnerL) / 2.0f; + if (L > W * dashLength + LENGTH_MARGIN) { + if (i > 0) { + upper = outerT; + } + } else if (L < W * dashLength - LENGTH_MARGIN) { + if (i == 0) { + // This is the last segment with shorter dashLength. + mHasMore = false; + break; + } + lower = outerT; + } else { + break; + } + + outerT = (upper + lower) / 2.0f; + } + + mLastOuterP = OuterP; + mLastInnerP = InnerP; + mLastOuterT = outerT; + mLastInnerT = innerT; + + if (W == 0.0f) { + return 1.0f; + } + + return L / W; +} + +void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth, + Float aMaxBorderWidth, + Float aMinBorderRadius, + Float aMaxBorderRadius) { + // If dashLength is not calculateable, find it with binary search, + // such that there exists i that OuterP_i == OuterP_n and + // InnerP_i == InnerP_n with given dashLength. + + FourFloats key(aMinBorderWidth, aMaxBorderWidth, aMinBorderRadius, + aMaxBorderRadius); + BestDashLength best; + if (DashedCornerCache.Get(key, &best)) { + mCount = best.count; + mBestDashLength = best.dashLength; + return; + } + + Float lower = 1.0f; + Float upper = DOT_LENGTH * DASH_LENGTH; + Float dashLength = upper; + size_t targetCount = 0; + + const Float LENGTH_MARGIN = 0.1f; + for (size_t j = 0; j < MAX_LOOP; j++) { + size_t count; + Float actualDashLength; + if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) { + if (j == 0) { + mCount = mMaxCount; + break; + } + } + + if (j == 0) { + if (count == 1) { + // If only 1 segment fits, fill entire region + // + // count = 1 + // mCount = 1 + // | 1 | + // +---+---+ + // |###|###| + // |###|###| + // |###|###| + // +---+---+ + // 1 + mCount = 1; + break; + } + + // targetCount should be 2n. + // + // targetCount = 2 + // mCount = 2 + // | 1 | 2 | + // +---+---+---+---+ + // |###| | |###| + // |###| | |###| + // |###| | |###| + // +---+---+---+---+ + // 1 2 + // + // targetCount = 6 + // mCount = 4 + // | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // |###| | |###|###| | |###|###| | |###| + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // 1 2 3 4 + if (count % 2) { + targetCount = count + 1; + } else { + targetCount = count; + } + + mCount = targetCount / 2 + 1; + } + + if (count == targetCount) { + mBestDashLength = dashLength; + + // actualDashLength won't be greater than dashLength. + if (actualDashLength > dashLength - LENGTH_MARGIN) { + break; + } + + // We started from upper bound, no need to update range when j == 0. + if (j > 0) { + upper = dashLength; + } + } else { + // |j == 0 && count != targetCount| means that |targetCount = count + 1|, + // and we started from upper bound, no need to update range when j == 0. + if (j > 0) { + if (count > targetCount) { + lower = dashLength; + } else { + upper = dashLength; + } + } + } + + dashLength = (upper + lower) / 2.0f; + } + + if (DashedCornerCache.Count() > DashedCornerCacheSize) { + DashedCornerCache.Clear(); + } + DashedCornerCache.Put(key, BestDashLength(mBestDashLength, mCount)); +} + +bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength, + size_t* aCount, + Float* aActualDashLength) { + // Return the number of segments and the last segment's dashLength for + // the given dashLength. + + Reset(); + + for (size_t i = 0; i < mMaxCount; i++) { + Float actualDashLength = FindNext(aDashLength); + if (mLastOuterT >= 1.0f) { + *aCount = i + 1; + *aActualDashLength = actualDashLength; + return true; + } + } + + return false; +} + +} // namespace mozilla |