Bug 1868962 - Part 21: Look up time zone methods only once. r=mgaudet
[gecko.git] / layout / painting / DashedCornerFinder.cpp
blobc892179df9cc157b161224c5e1ad10bdaf8673ca
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"
9 #include <utility>
11 #include "BorderCache.h"
12 #include "BorderConsts.h"
13 #include "nsTHashMap.h"
15 namespace mozilla {
17 using namespace gfx;
19 struct BestDashLength {
20 typedef mozilla::gfx::Float Float;
22 Float dashLength;
23 size_t count;
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]),
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.");
54 DetermineType(aBorderWidthH, aBorderWidthV);
56 Reset();
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.
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;
81 mType = PERFECT;
82 Float borderLength = M_PI * curveHeight / 2.0f;
84 Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH;
85 size_t count = ceil(borderLength / dashWidth);
86 if (count % 2) {
87 count++;
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,
104 maxBorderRadius);
108 bool DashedCornerFinder::HasMore(void) const {
109 if (mHasZeroBorderWidth) {
110 return mI < mMaxCount && mHasMore;
113 return mI < mCount;
116 DashedCornerFinder::Result DashedCornerFinder::Next(void) {
117 Float lastOuterT, lastInnerT, outerT, innerT;
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;
129 (void)FindNext(mBestDashLength);
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;
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;
159 (void)FindNext(mBestDashLength);
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;
178 mI++;
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];
190 mLastOuterT = 0.0f;
191 mLastInnerT = 0.0f;
192 mHasMore = true;
195 Float DashedCornerFinder::FindNext(Float dashLength) {
196 Float upper = 1.0f;
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;
202 Float innerT;
203 Float W = 0.0f;
204 Float L = 0.0f;
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.
213 // W = (W1 + W2) / 2
214 // L = (OuterL + InnerL) / 2
215 // dashLength = L / W
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;
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;
262 lower = outerT;
263 } else {
264 break;
267 outerT = (upper + lower) / 2.0f;
270 mLastOuterP = OuterP;
271 mLastInnerP = InnerP;
272 mLastOuterT = outerT;
273 mLastInnerT = innerT;
275 if (W == 0.0f) {
276 return 1.0f;
279 return L / W;
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,
291 aMaxBorderRadius);
292 BestDashLength best;
293 if (DashedCornerCache.Get(key, &best)) {
294 mCount = best.count;
295 mBestDashLength = best.dashLength;
296 return;
299 Float lower = 1.0f;
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++) {
306 size_t count;
307 Float actualDashLength;
308 if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) {
309 if (j == 0) {
310 mCount = mMaxCount;
311 break;
315 if (j == 0) {
316 if (count == 1) {
317 // If only 1 segment fits, fill entire region
319 // count = 1
320 // mCount = 1
321 // | 1 |
322 // +---+---+
323 // |###|###|
324 // |###|###|
325 // |###|###|
326 // +---+---+
327 // 1
328 mCount = 1;
329 break;
332 // targetCount should be 2n.
334 // targetCount = 2
335 // mCount = 2
336 // | 1 | 2 |
337 // +---+---+---+---+
338 // |###| | |###|
339 // |###| | |###|
340 // |###| | |###|
341 // +---+---+---+---+
342 // 1 2
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;
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) {
367 break;
370 // We started from upper bound, no need to update range when j == 0.
371 if (j > 0) {
372 upper = dashLength;
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;
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,
397 size_t* aCount,
398 Float* aActualDashLength) {
399 // Return the number of segments and the last segment's dashLength for
400 // the given dashLength.
402 Reset();
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;
413 return false;
416 } // namespace mozilla