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"
18 struct BestDashLength
{
19 typedef mozilla::gfx::Float Float
;
24 BestDashLength() : dashLength(0.0f
), count(0) {}
26 BestDashLength(Float aDashLength
, size_t aCount
)
27 : dashLength(aDashLength
), count(aCount
) {}
30 static const size_t DashedCornerCacheSize
= 256;
31 nsTHashMap
<FourFloatsHashKey
, BestDashLength
> DashedCornerCache
;
33 DashedCornerFinder::DashedCornerFinder(const Bezier
& aOuterBezier
,
34 const Bezier
& aInnerBezier
,
35 Float aBorderWidthH
, Float aBorderWidthV
,
36 const Size
& aCornerDim
)
37 : mOuterBezier(aOuterBezier
),
38 mInnerBezier(aInnerBezier
),
39 mLastOuterP(aOuterBezier
.mPoints
[0]),
40 mLastInnerP(aInnerBezier
.mPoints
[0]),
43 mBestDashLength(DOT_LENGTH
* DASH_LENGTH
),
44 mHasZeroBorderWidth(false),
46 mMaxCount(aCornerDim
.width
+ aCornerDim
.height
),
50 NS_ASSERTION(aBorderWidthH
> 0.0f
|| aBorderWidthV
> 0.0f
,
51 "At least one side should have non-zero width.");
53 DetermineType(aBorderWidthH
, aBorderWidthV
);
58 void DashedCornerFinder::DetermineType(Float aBorderWidthH
,
59 Float aBorderWidthV
) {
60 if (aBorderWidthH
< aBorderWidthV
) {
61 // Always draw from wider side to thinner side.
62 std::swap(mInnerBezier
.mPoints
[0], mInnerBezier
.mPoints
[3]);
63 std::swap(mInnerBezier
.mPoints
[1], mInnerBezier
.mPoints
[2]);
64 std::swap(mOuterBezier
.mPoints
[0], mOuterBezier
.mPoints
[3]);
65 std::swap(mOuterBezier
.mPoints
[1], mOuterBezier
.mPoints
[2]);
66 mLastOuterP
= mOuterBezier
.mPoints
[0];
67 mLastInnerP
= mInnerBezier
.mPoints
[0];
70 // See the comment at mType declaration for each condition.
73 fabs(mOuterBezier
.mPoints
[0].x
- mOuterBezier
.mPoints
[3].x
);
75 fabs(mOuterBezier
.mPoints
[0].y
- mOuterBezier
.mPoints
[3].y
);
76 if (aBorderWidthH
== aBorderWidthV
&& borderRadiusA
== borderRadiusB
&&
77 borderRadiusA
> aBorderWidthH
* 2.0f
) {
78 Float curveHeight
= borderRadiusA
- aBorderWidthH
/ 2.0;
81 Float borderLength
= M_PI
* curveHeight
/ 2.0f
;
83 Float dashWidth
= aBorderWidthH
* DOT_LENGTH
* DASH_LENGTH
;
84 size_t count
= ceil(borderLength
/ dashWidth
);
88 mCount
= count
/ 2 + 1;
89 mBestDashLength
= borderLength
/ (aBorderWidthH
* count
);
92 Float minBorderWidth
= std::min(aBorderWidthH
, aBorderWidthV
);
93 if (minBorderWidth
== 0.0f
) {
94 mHasZeroBorderWidth
= true;
97 if (mType
== OTHER
&& !mHasZeroBorderWidth
) {
98 Float minBorderRadius
= std::min(borderRadiusA
, borderRadiusB
);
99 Float maxBorderRadius
= std::max(borderRadiusA
, borderRadiusB
);
100 Float maxBorderWidth
= std::max(aBorderWidthH
, aBorderWidthV
);
102 FindBestDashLength(minBorderWidth
, maxBorderWidth
, minBorderRadius
,
107 bool DashedCornerFinder::HasMore(void) const {
108 if (mHasZeroBorderWidth
) {
109 return mI
< mMaxCount
&& mHasMore
;
115 DashedCornerFinder::Result
DashedCornerFinder::Next(void) {
116 Float lastOuterT
, lastInnerT
, outerT
, innerT
;
122 if (mType
== PERFECT
) {
123 lastOuterT
= lastInnerT
= (mI
* 2.0f
- 0.5f
) / ((mCount
- 1) * 2.0f
);
125 Float last2OuterT
= mLastOuterT
;
126 Float last2InnerT
= mLastInnerT
;
128 (void)FindNext(mBestDashLength
);
131 // mLastOuterT lastOuterT
134 // +---+---+---+---+ <- last2OuterT
138 // +---+---+---+---+ <- last2InnerT
141 // mLastInnerT lastInnerT
142 lastOuterT
= (mLastOuterT
+ last2OuterT
) / 2.0f
;
143 lastInnerT
= (mLastInnerT
+ last2InnerT
) / 2.0f
;
147 if ((!mHasZeroBorderWidth
&& mI
== mCount
- 1) ||
148 (mHasZeroBorderWidth
&& !mHasMore
)) {
152 if (mType
== PERFECT
) {
153 outerT
= innerT
= (mI
* 2.0f
+ 0.5f
) / ((mCount
- 1) * 2.0f
);
155 Float last2OuterT
= mLastOuterT
;
156 Float last2InnerT
= mLastInnerT
;
158 (void)FindNext(mBestDashLength
);
161 // outerT last2OuterT
164 // mLastOuterT -> +---+---+---+---+
168 // mLastInnerT -> +---+---+---+---+
171 // innerT last2InnerT
172 outerT
= (mLastOuterT
+ last2OuterT
) / 2.0f
;
173 innerT
= (mLastInnerT
+ last2InnerT
) / 2.0f
;
179 Bezier outerSectionBezier
;
180 Bezier innerSectionBezier
;
181 GetSubBezier(&outerSectionBezier
, mOuterBezier
, lastOuterT
, outerT
);
182 GetSubBezier(&innerSectionBezier
, mInnerBezier
, lastInnerT
, innerT
);
183 return DashedCornerFinder::Result(outerSectionBezier
, innerSectionBezier
);
186 void DashedCornerFinder::Reset(void) {
187 mLastOuterP
= mOuterBezier
.mPoints
[0];
188 mLastInnerP
= mInnerBezier
.mPoints
[0];
194 Float
DashedCornerFinder::FindNext(Float dashLength
) {
196 Float lower
= mLastOuterT
;
198 Point OuterP
, InnerP
;
199 // Start from upper bound to check if this is the last segment.
200 Float outerT
= upper
;
205 const Float LENGTH_MARGIN
= 0.1f
;
206 for (size_t i
= 0; i
< MAX_LOOP
; i
++) {
207 OuterP
= GetBezierPoint(mOuterBezier
, outerT
);
208 InnerP
= FindBezierNearestPoint(mInnerBezier
, OuterP
, outerT
, &innerT
);
210 // Calculate approximate dash length.
213 // L = (OuterL + InnerL) / 2
214 // dashLength = L / W
217 // OuterP ___--- | ---___ mLastOuterP
225 // | ____+---- mLastInnerP
232 // OuterP ___--- ---___ mLastOuterP
235 // | ___----------______ |
239 // | InnerL ______------+
240 // | ____----- mLastInnerP
245 Float W1
= (mLastOuterP
- mLastInnerP
).Length();
246 Float W2
= (OuterP
- InnerP
).Length();
247 Float OuterL
= GetBezierLength(mOuterBezier
, mLastOuterT
, outerT
);
248 Float InnerL
= GetBezierLength(mInnerBezier
, mLastInnerT
, innerT
);
249 W
= (W1
+ W2
) / 2.0f
;
250 L
= (OuterL
+ InnerL
) / 2.0f
;
251 if (L
> W
* dashLength
+ LENGTH_MARGIN
) {
255 } else if (L
< W
* dashLength
- LENGTH_MARGIN
) {
257 // This is the last segment with shorter dashLength.
266 outerT
= (upper
+ lower
) / 2.0f
;
269 mLastOuterP
= OuterP
;
270 mLastInnerP
= InnerP
;
271 mLastOuterT
= outerT
;
272 mLastInnerT
= innerT
;
281 void DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth
,
282 Float aMaxBorderWidth
,
283 Float aMinBorderRadius
,
284 Float aMaxBorderRadius
) {
285 // If dashLength is not calculateable, find it with binary search,
286 // such that there exists i that OuterP_i == OuterP_n and
287 // InnerP_i == InnerP_n with given dashLength.
289 FourFloats
key(aMinBorderWidth
, aMaxBorderWidth
, aMinBorderRadius
,
292 if (DashedCornerCache
.Get(key
, &best
)) {
294 mBestDashLength
= best
.dashLength
;
299 Float upper
= DOT_LENGTH
* DASH_LENGTH
;
300 Float dashLength
= upper
;
301 size_t targetCount
= 0;
303 const Float LENGTH_MARGIN
= 0.1f
;
304 for (size_t j
= 0; j
< MAX_LOOP
; j
++) {
306 Float actualDashLength
;
307 if (!GetCountAndLastDashLength(dashLength
, &count
, &actualDashLength
)) {
316 // If only 1 segment fits, fill entire region
331 // targetCount should be 2n.
345 // | 1 | 2 | 3 | 4 | 5 | 6 |
346 // +---+---+---+---+---+---+---+---+---+---+---+---+
347 // |###| | |###|###| | |###|###| | |###|
348 // |###| | |###|###| | |###|###| | |###|
349 // |###| | |###|###| | |###|###| | |###|
350 // +---+---+---+---+---+---+---+---+---+---+---+---+
353 targetCount
= count
+ 1;
358 mCount
= targetCount
/ 2 + 1;
361 if (count
== targetCount
) {
362 mBestDashLength
= dashLength
;
364 // actualDashLength won't be greater than dashLength.
365 if (actualDashLength
> dashLength
- LENGTH_MARGIN
) {
369 // We started from upper bound, no need to update range when j == 0.
374 // |j == 0 && count != targetCount| means that |targetCount = count + 1|,
375 // and we started from upper bound, no need to update range when j == 0.
377 if (count
> targetCount
) {
385 dashLength
= (upper
+ lower
) / 2.0f
;
388 if (DashedCornerCache
.Count() > DashedCornerCacheSize
) {
389 DashedCornerCache
.Clear();
391 DashedCornerCache
.InsertOrUpdate(key
,
392 BestDashLength(mBestDashLength
, mCount
));
395 bool DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength
,
397 Float
* aActualDashLength
) {
398 // Return the number of segments and the last segment's dashLength for
399 // the given dashLength.
403 for (size_t i
= 0; i
< mMaxCount
; i
++) {
404 Float actualDashLength
= FindNext(aDashLength
);
405 if (mLastOuterT
>= 1.0f
) {
407 *aActualDashLength
= actualDashLength
;
415 } // namespace mozilla