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/. */
7 #include "DashedCornerFinder.h"
11 #include "BorderCache.h"
12 #include "BorderConsts.h"
13 #include "nsTHashMap.h"
19 struct BestDashLength
{
20 typedef mozilla::gfx::Float Float
;
25 BestDashLength() : dashLength(0.0f
), count(0) {}
27 BestDashLength(Float aDashLength
, size_t aCount
)
28 : dashLength(aDashLength
), count(aCount
) {}
31 static const size_t DashedCornerCacheSize
= 256;
32 nsTHashMap
<FourFloatsHashKey
, BestDashLength
> DashedCornerCache
;
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]),
44 mBestDashLength(DOT_LENGTH
* DASH_LENGTH
),
45 mHasZeroBorderWidth(false),
47 mMaxCount(aCornerDim
.width
+ aCornerDim
.height
),
51 NS_ASSERTION(aBorderWidthH
> 0.0f
|| aBorderWidthV
> 0.0f
,
52 "At least one side should have non-zero width.");
54 DetermineType(aBorderWidthH
, aBorderWidthV
);
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];
71 // See the comment at mType declaration for each condition.
74 fabs(mOuterBezier
.mPoints
[0].x
- mOuterBezier
.mPoints
[3].x
);
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;
82 Float borderLength
= M_PI
* curveHeight
/ 2.0f
;
84 Float dashWidth
= aBorderWidthH
* DOT_LENGTH
* DASH_LENGTH
;
85 size_t count
= ceil(borderLength
/ dashWidth
);
89 mCount
= count
/ 2 + 1;
90 mBestDashLength
= borderLength
/ (aBorderWidthH
* count
);
93 Float minBorderWidth
= std::min(aBorderWidthH
, aBorderWidthV
);
94 if (minBorderWidth
== 0.0f
) {
95 mHasZeroBorderWidth
= true;
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
);
103 FindBestDashLength(minBorderWidth
, maxBorderWidth
, minBorderRadius
,
108 bool DashedCornerFinder::HasMore(void) const {
109 if (mHasZeroBorderWidth
) {
110 return mI
< mMaxCount
&& mHasMore
;
116 DashedCornerFinder::Result
DashedCornerFinder::Next(void) {
117 Float lastOuterT
, lastInnerT
, outerT
, innerT
;
123 if (mType
== PERFECT
) {
124 lastOuterT
= lastInnerT
= (mI
* 2.0f
- 0.5f
) / ((mCount
- 1) * 2.0f
);
126 Float last2OuterT
= mLastOuterT
;
127 Float last2InnerT
= mLastInnerT
;
129 (void)FindNext(mBestDashLength
);
132 // mLastOuterT lastOuterT
135 // +---+---+---+---+ <- last2OuterT
139 // +---+---+---+---+ <- last2InnerT
142 // mLastInnerT lastInnerT
143 lastOuterT
= (mLastOuterT
+ last2OuterT
) / 2.0f
;
144 lastInnerT
= (mLastInnerT
+ last2InnerT
) / 2.0f
;
148 if ((!mHasZeroBorderWidth
&& mI
== mCount
- 1) ||
149 (mHasZeroBorderWidth
&& !mHasMore
)) {
153 if (mType
== PERFECT
) {
154 outerT
= innerT
= (mI
* 2.0f
+ 0.5f
) / ((mCount
- 1) * 2.0f
);
156 Float last2OuterT
= mLastOuterT
;
157 Float last2InnerT
= mLastInnerT
;
159 (void)FindNext(mBestDashLength
);
162 // outerT last2OuterT
165 // mLastOuterT -> +---+---+---+---+
169 // mLastInnerT -> +---+---+---+---+
172 // innerT last2InnerT
173 outerT
= (mLastOuterT
+ last2OuterT
) / 2.0f
;
174 innerT
= (mLastInnerT
+ last2InnerT
) / 2.0f
;
180 Bezier outerSectionBezier
;
181 Bezier innerSectionBezier
;
182 GetSubBezier(&outerSectionBezier
, mOuterBezier
, lastOuterT
, outerT
);
183 GetSubBezier(&innerSectionBezier
, mInnerBezier
, lastInnerT
, innerT
);
184 return DashedCornerFinder::Result(outerSectionBezier
, innerSectionBezier
);
187 void DashedCornerFinder::Reset(void) {
188 mLastOuterP
= mOuterBezier
.mPoints
[0];
189 mLastInnerP
= mInnerBezier
.mPoints
[0];
195 Float
DashedCornerFinder::FindNext(Float dashLength
) {
197 Float lower
= mLastOuterT
;
199 Point OuterP
, InnerP
;
200 // Start from upper bound to check if this is the last segment.
201 Float outerT
= upper
;
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
);
211 // Calculate approximate dash length.
214 // L = (OuterL + InnerL) / 2
215 // dashLength = L / W
218 // OuterP ___--- | ---___ mLastOuterP
226 // | ____+---- mLastInnerP
233 // OuterP ___--- ---___ mLastOuterP
236 // | ___----------______ |
240 // | InnerL ______------+
241 // | ____----- mLastInnerP
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
) {
256 } else if (L
< W
* dashLength
- LENGTH_MARGIN
) {
258 // This is the last segment with shorter dashLength.
267 outerT
= (upper
+ lower
) / 2.0f
;
270 mLastOuterP
= OuterP
;
271 mLastInnerP
= InnerP
;
272 mLastOuterT
= outerT
;
273 mLastInnerT
= innerT
;
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.
290 FourFloats
key(aMinBorderWidth
, aMaxBorderWidth
, aMinBorderRadius
,
293 if (DashedCornerCache
.Get(key
, &best
)) {
295 mBestDashLength
= best
.dashLength
;
300 Float upper
= DOT_LENGTH
* DASH_LENGTH
;
301 Float dashLength
= upper
;
302 size_t targetCount
= 0;
304 const Float LENGTH_MARGIN
= 0.1f
;
305 for (size_t j
= 0; j
< MAX_LOOP
; j
++) {
307 Float actualDashLength
;
308 if (!GetCountAndLastDashLength(dashLength
, &count
, &actualDashLength
)) {
317 // If only 1 segment fits, fill entire region
332 // targetCount should be 2n.
346 // | 1 | 2 | 3 | 4 | 5 | 6 |
347 // +---+---+---+---+---+---+---+---+---+---+---+---+
348 // |###| | |###|###| | |###|###| | |###|
349 // |###| | |###|###| | |###|###| | |###|
350 // |###| | |###|###| | |###|###| | |###|
351 // +---+---+---+---+---+---+---+---+---+---+---+---+
354 targetCount
= count
+ 1;
359 mCount
= targetCount
/ 2 + 1;
362 if (count
== targetCount
) {
363 mBestDashLength
= dashLength
;
365 // actualDashLength won't be greater than dashLength.
366 if (actualDashLength
> dashLength
- LENGTH_MARGIN
) {
370 // We started from upper bound, no need to update range when j == 0.
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.
378 if (count
> targetCount
) {
386 dashLength
= (upper
+ lower
) / 2.0f
;
389 if (DashedCornerCache
.Count() > DashedCornerCacheSize
) {
390 DashedCornerCache
.Clear();
392 DashedCornerCache
.InsertOrUpdate(key
,
393 BestDashLength(mBestDashLength
, mCount
));
396 bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength
,
398 Float
* aActualDashLength
) {
399 // Return the number of segments and the last segment's dashLength for
400 // the given dashLength.
404 for (size_t i
= 0; i
< mMaxCount
; i
++) {
405 Float actualDashLength
= FindNext(aDashLength
);
406 if (mLastOuterT
>= 1.0f
) {
408 *aActualDashLength
= actualDashLength
;
416 } // namespace mozilla