Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : // vim:cindent:ts=2:et:sw=2:
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 "nsCSSRenderingBorders.h"
8 :
9 : #include "gfxUtils.h"
10 : #include "mozilla/ArrayUtils.h"
11 : #include "mozilla/gfx/2D.h"
12 : #include "mozilla/gfx/Helpers.h"
13 : #include "mozilla/gfx/PathHelpers.h"
14 : #include "BorderConsts.h"
15 : #include "DashedCornerFinder.h"
16 : #include "DottedCornerFinder.h"
17 : #include "nsLayoutUtils.h"
18 : #include "nsStyleConsts.h"
19 : #include "nsContentUtils.h"
20 : #include "nsCSSColorUtils.h"
21 : #include "GeckoProfiler.h"
22 : #include "nsExpirationTracker.h"
23 : #include "RoundedRect.h"
24 : #include "nsIScriptError.h"
25 : #include "nsClassHashtable.h"
26 : #include "nsPresContext.h"
27 : #include "nsStyleStruct.h"
28 : #include "mozilla/gfx/2D.h"
29 : #include "gfx2DGlue.h"
30 : #include "gfxGradientCache.h"
31 : #include "mozilla/layers/StackingContextHelper.h"
32 : #include "mozilla/layers/WebRenderDisplayItemLayer.h"
33 : #include "mozilla/Range.h"
34 : #include <algorithm>
35 :
36 : using namespace mozilla;
37 : using namespace mozilla::gfx;
38 :
39 : #define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
40 :
41 : /**
42 : * nsCSSRendering::PaintBorder
43 : * nsCSSRendering::PaintOutline
44 : * -> DrawBorders
45 : *
46 : * DrawBorders
47 : * -> Ability to use specialized approach?
48 : * |- Draw using specialized function
49 : * |- separate corners?
50 : * |- dashed side mask
51 : * |
52 : * -> can border be drawn in 1 pass? (e.g., solid border same color all around)
53 : * |- DrawBorderSides with all 4 sides
54 : * -> more than 1 pass?
55 : * |- for each corner
56 : * |- clip to DoCornerClipSubPath
57 : * |- for each side adjacent to corner
58 : * |- clip to GetSideClipSubPath
59 : * |- DrawBorderSides with one side
60 : * |- for each side
61 : * |- GetSideClipWithoutCornersRect
62 : * |- DrawDashedOrDottedSide || DrawBorderSides with one side
63 : */
64 :
65 : static void ComputeBorderCornerDimensions(const Float* aBorderWidths,
66 : const RectCornerRadii& aRadii,
67 : RectCornerRadii *aDimsResult);
68 :
69 : // given a side index, get the previous and next side index
70 : #define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
71 : #define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
72 :
73 : // given a corner index, get the previous and next corner index
74 : #define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
75 : #define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
76 :
77 : // from the given base color and the background color, turn
78 : // color into a color for the given border pattern style
79 : static Color MakeBorderColor(nscolor aColor,
80 : nscolor aBackgroundColor,
81 : BorderColorStyle aBorderColorStyle);
82 :
83 :
84 : // Given a line index (an index starting from the outside of the
85 : // border going inwards) and an array of line styles, calculate the
86 : // color that that stripe of the border should be rendered in.
87 : static Color ComputeColorForLine(uint32_t aLineIndex,
88 : const BorderColorStyle* aBorderColorStyle,
89 : uint32_t aBorderColorStyleCount,
90 : nscolor aBorderColor,
91 : nscolor aBackgroundColor);
92 :
93 : static Color ComputeCompositeColorForLine(uint32_t aLineIndex,
94 : const nsBorderColors* aBorderColors);
95 :
96 : // little helper function to check if the array of 4 floats given are
97 : // equal to the given value
98 : static bool
99 34 : CheckFourFloatsEqual(const Float *vals, Float k)
100 : {
101 34 : return (vals[0] == k &&
102 0 : vals[1] == k &&
103 34 : vals[2] == k &&
104 34 : vals[3] == k);
105 : }
106 :
107 : static bool
108 170 : IsZeroSize(const Size& sz) {
109 170 : return sz.width == 0.0 || sz.height == 0.0;
110 : }
111 :
112 : /* static */ bool
113 68 : nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) {
114 102 : return IsZeroSize(corners[eCornerTopLeft]) &&
115 68 : IsZeroSize(corners[eCornerTopRight]) &&
116 136 : IsZeroSize(corners[eCornerBottomRight]) &&
117 102 : IsZeroSize(corners[eCornerBottomLeft]);
118 : }
119 :
120 : static mozilla::Side
121 0 : GetHorizontalSide(Corner aCorner)
122 : {
123 0 : return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom;
124 : }
125 :
126 : static mozilla::Side
127 0 : GetVerticalSide(Corner aCorner)
128 : {
129 0 : return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight;
130 : }
131 :
132 : static Corner
133 0 : GetCWCorner(mozilla::Side aSide)
134 : {
135 0 : return Corner(NEXT_SIDE(aSide));
136 : }
137 :
138 : static Corner
139 0 : GetCCWCorner(mozilla::Side aSide)
140 : {
141 0 : return Corner(aSide);
142 : }
143 :
144 : static bool
145 0 : IsSingleSide(int aSides)
146 : {
147 0 : return aSides == eSideBitsTop || aSides == eSideBitsRight ||
148 0 : aSides == eSideBitsBottom || aSides == eSideBitsLeft;
149 : }
150 :
151 : static bool
152 0 : IsHorizontalSide(mozilla::Side aSide)
153 : {
154 0 : return aSide == eSideTop || aSide == eSideBottom;
155 : }
156 :
157 : typedef enum {
158 : // Normal solid square corner. Will be rectangular, the size of the
159 : // adjacent sides. If the corner has a border radius, the corner
160 : // will always be solid, since we don't do dotted/dashed etc.
161 : CORNER_NORMAL,
162 :
163 : // Paint the corner in whatever style is not dotted/dashed of the
164 : // adjacent corners.
165 : CORNER_SOLID,
166 :
167 : // Paint the corner as a dot, the size of the bigger of the adjacent
168 : // sides.
169 : CORNER_DOT
170 : } CornerStyle;
171 :
172 34 : nsCSSBorderRenderer::nsCSSBorderRenderer(nsPresContext* aPresContext,
173 : const nsIDocument* aDocument,
174 : DrawTarget* aDrawTarget,
175 : const Rect& aDirtyRect,
176 : Rect& aOuterRect,
177 : const uint8_t* aBorderStyles,
178 : const Float* aBorderWidths,
179 : RectCornerRadii& aBorderRadii,
180 : const nscolor* aBorderColors,
181 : nsBorderColors* const* aCompositeColors,
182 34 : nscolor aBackgroundColor)
183 : : mPresContext(aPresContext),
184 : mDocument(aDocument),
185 : mDrawTarget(aDrawTarget),
186 : mDirtyRect(aDirtyRect),
187 : mOuterRect(aOuterRect),
188 : mBorderRadii(aBorderRadii),
189 34 : mBackgroundColor(aBackgroundColor)
190 : {
191 34 : PodCopy(mBorderStyles, aBorderStyles, 4);
192 34 : PodCopy(mBorderWidths, aBorderWidths, 4);
193 34 : PodCopy(mBorderColors, aBorderColors, 4);
194 34 : if (aCompositeColors) {
195 34 : PodCopy(mCompositeColors, aCompositeColors, 4);
196 : } else {
197 : static nsBorderColors * const noColors[4] = { nullptr };
198 0 : PodCopy(mCompositeColors, noColors, 4);
199 : }
200 :
201 34 : mInnerRect = mOuterRect;
202 34 : mInnerRect.Deflate(
203 136 : Margin(mBorderStyles[0] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[0] : 0,
204 34 : mBorderStyles[1] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[1] : 0,
205 34 : mBorderStyles[2] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[2] : 0,
206 68 : mBorderStyles[3] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[3] : 0));
207 :
208 34 : ComputeBorderCornerDimensions(mBorderWidths,
209 34 : mBorderRadii, &mBorderCornerDimensions);
210 :
211 34 : mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0);
212 34 : mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
213 34 : mAvoidStroke = false;
214 34 : }
215 :
216 : /* static */ void
217 17 : nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
218 : const Float* aBorderSizes,
219 : RectCornerRadii* aInnerRadiiRet)
220 : {
221 17 : RectCornerRadii& iRadii = *aInnerRadiiRet;
222 :
223 17 : iRadii[C_TL].width = std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]);
224 17 : iRadii[C_TL].height = std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]);
225 :
226 17 : iRadii[C_TR].width = std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]);
227 17 : iRadii[C_TR].height = std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]);
228 :
229 17 : iRadii[C_BR].width = std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]);
230 17 : iRadii[C_BR].height = std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]);
231 :
232 17 : iRadii[C_BL].width = std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]);
233 17 : iRadii[C_BL].height = std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]);
234 17 : }
235 :
236 : /* static */ void
237 9 : nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
238 : const Float* aBorderSizes,
239 : RectCornerRadii* aOuterRadiiRet)
240 : {
241 9 : RectCornerRadii& oRadii = *aOuterRadiiRet;
242 :
243 : // default all corners to sharp corners
244 9 : oRadii = RectCornerRadii(0.f);
245 :
246 : // round the edges that have radii > 0.0 to start with
247 9 : if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) {
248 9 : oRadii[C_TL].width = std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]);
249 9 : oRadii[C_TL].height = std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]);
250 : }
251 :
252 9 : if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) {
253 9 : oRadii[C_TR].width = std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]);
254 9 : oRadii[C_TR].height = std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]);
255 : }
256 :
257 9 : if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) {
258 9 : oRadii[C_BR].width = std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]);
259 9 : oRadii[C_BR].height = std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]);
260 : }
261 :
262 9 : if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) {
263 9 : oRadii[C_BL].width = std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]);
264 9 : oRadii[C_BL].height = std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]);
265 : }
266 9 : }
267 :
268 : /*static*/ void
269 34 : ComputeBorderCornerDimensions(const Float* aBorderWidths,
270 : const RectCornerRadii& aRadii,
271 : RectCornerRadii* aDimsRet)
272 : {
273 34 : Float leftWidth = aBorderWidths[eSideLeft];
274 34 : Float topWidth = aBorderWidths[eSideTop];
275 34 : Float rightWidth = aBorderWidths[eSideRight];
276 34 : Float bottomWidth = aBorderWidths[eSideBottom];
277 :
278 34 : if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) {
279 : // These will always be in pixel units from CSS
280 17 : (*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
281 17 : (*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
282 17 : (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
283 17 : (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
284 : } else {
285 : // Always round up to whole pixels for the corners; it's safe to
286 : // make the corners bigger than necessary, and this way we ensure
287 : // that we avoid seams.
288 34 : (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
289 17 : ceil(std::max(topWidth, aRadii[C_TL].height)));
290 34 : (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
291 17 : ceil(std::max(topWidth, aRadii[C_TR].height)));
292 34 : (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
293 17 : ceil(std::max(bottomWidth, aRadii[C_BR].height)));
294 34 : (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
295 17 : ceil(std::max(bottomWidth, aRadii[C_BL].height)));
296 : }
297 34 : }
298 :
299 : bool
300 102 : nsCSSBorderRenderer::AreBorderSideFinalStylesSame(uint8_t aSides)
301 : {
302 102 : NS_ASSERTION(aSides != 0 && (aSides & ~eSideBitsAll) == 0,
303 : "AreBorderSidesSame: invalid whichSides!");
304 :
305 : /* First check if the specified styles and colors are the same for all sides */
306 102 : int firstStyle = 0;
307 425 : NS_FOR_CSS_SIDES (i) {
308 367 : if (firstStyle == i) {
309 136 : if (((1 << i) & aSides) == 0)
310 34 : firstStyle++;
311 136 : continue;
312 : }
313 :
314 231 : if (((1 << i) & aSides) == 0) {
315 90 : continue;
316 : }
317 :
318 379 : if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
319 238 : mBorderColors[firstStyle] != mBorderColors[i] ||
320 194 : !nsBorderColors::Equal(mCompositeColors[firstStyle],
321 97 : mCompositeColors[i]))
322 44 : return false;
323 : }
324 :
325 : /* Then if it's one of the two-tone styles and we're not
326 : * just comparing the TL or BR sides */
327 58 : switch (mBorderStyles[firstStyle]) {
328 : case NS_STYLE_BORDER_STYLE_GROOVE:
329 : case NS_STYLE_BORDER_STYLE_RIDGE:
330 : case NS_STYLE_BORDER_STYLE_INSET:
331 : case NS_STYLE_BORDER_STYLE_OUTSET:
332 0 : return ((aSides & ~(eSideBitsTop | eSideBitsLeft)) == 0 ||
333 0 : (aSides & ~(eSideBitsBottom | eSideBitsRight)) == 0);
334 : }
335 :
336 58 : return true;
337 : }
338 :
339 : bool
340 0 : nsCSSBorderRenderer::IsSolidCornerStyle(uint8_t aStyle, Corner aCorner)
341 : {
342 0 : switch (aStyle) {
343 : case NS_STYLE_BORDER_STYLE_SOLID:
344 0 : return true;
345 :
346 : case NS_STYLE_BORDER_STYLE_INSET:
347 : case NS_STYLE_BORDER_STYLE_OUTSET:
348 0 : return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
349 :
350 : case NS_STYLE_BORDER_STYLE_GROOVE:
351 : case NS_STYLE_BORDER_STYLE_RIDGE:
352 0 : return mOneUnitBorder && (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
353 :
354 : case NS_STYLE_BORDER_STYLE_DOUBLE:
355 0 : return mOneUnitBorder;
356 :
357 : default:
358 0 : return false;
359 : }
360 : }
361 :
362 : bool
363 0 : nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner)
364 : {
365 : // Corner between dotted borders with same width and small radii is
366 : // merged into single dot.
367 : //
368 : // widthH / 2.0
369 : // |<---------->|
370 : // | |
371 : // |radius.width|
372 : // |<--->| |
373 : // | | |
374 : // | _+------+------------+-----
375 : // | / ###|### |
376 : // |/ #######|####### |
377 : // + #########|######### |
378 : // | ##########|########## |
379 : // | ###########|########### |
380 : // | ###########|########### |
381 : // |############|############|
382 : // +------------+############|
383 : // |#########################|
384 : // | ####################### |
385 : // | ####################### |
386 : // | ##################### |
387 : // | ################### |
388 : // | ############### |
389 : // | ####### |
390 : // +-------------------------+----
391 : // | |
392 : // | |
393 0 : mozilla::Side sideH(GetHorizontalSide(aCorner));
394 0 : mozilla::Side sideV(GetVerticalSide(aCorner));
395 0 : uint8_t styleH = mBorderStyles[sideH];
396 0 : uint8_t styleV = mBorderStyles[sideV];
397 0 : if (styleH != styleV || styleH != NS_STYLE_BORDER_STYLE_DOTTED) {
398 0 : return false;
399 : }
400 :
401 0 : Float widthH = mBorderWidths[sideH];
402 0 : Float widthV = mBorderWidths[sideV];
403 0 : if (widthH != widthV) {
404 0 : return false;
405 : }
406 :
407 0 : Size radius = mBorderRadii[aCorner];
408 0 : return IsZeroSize(radius) ||
409 0 : (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
410 : }
411 :
412 : BorderColorStyle
413 0 : nsCSSBorderRenderer::BorderColorStyleForSolidCorner(uint8_t aStyle, Corner aCorner)
414 : {
415 : // note that this function assumes that the corner is already solid,
416 : // as per the earlier function
417 0 : switch (aStyle) {
418 : case NS_STYLE_BORDER_STYLE_SOLID:
419 : case NS_STYLE_BORDER_STYLE_DOUBLE:
420 0 : return BorderColorStyleSolid;
421 :
422 : case NS_STYLE_BORDER_STYLE_INSET:
423 : case NS_STYLE_BORDER_STYLE_GROOVE:
424 0 : if (aCorner == eCornerTopLeft)
425 0 : return BorderColorStyleDark;
426 0 : else if (aCorner == eCornerBottomRight)
427 0 : return BorderColorStyleLight;
428 0 : break;
429 :
430 : case NS_STYLE_BORDER_STYLE_OUTSET:
431 : case NS_STYLE_BORDER_STYLE_RIDGE:
432 0 : if (aCorner == eCornerTopLeft)
433 0 : return BorderColorStyleLight;
434 0 : else if (aCorner == eCornerBottomRight)
435 0 : return BorderColorStyleDark;
436 0 : break;
437 : }
438 :
439 0 : return BorderColorStyleNone;
440 : }
441 :
442 : Rect
443 0 : nsCSSBorderRenderer::GetCornerRect(Corner aCorner)
444 : {
445 0 : Point offset(0.f, 0.f);
446 :
447 0 : if (aCorner == C_TR || aCorner == C_BR)
448 0 : offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
449 0 : if (aCorner == C_BR || aCorner == C_BL)
450 0 : offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
451 :
452 0 : return Rect(mOuterRect.TopLeft() + offset,
453 0 : mBorderCornerDimensions[aCorner]);
454 : }
455 :
456 : Rect
457 0 : nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide)
458 : {
459 0 : Point offset(0.f, 0.f);
460 :
461 : // The offset from the outside rect to the start of this side's
462 : // box. For the top and bottom sides, the height of the box
463 : // must be the border height; the x start must take into account
464 : // the corner size (which may be bigger than the right or left
465 : // side's width). The same applies to the right and left sides.
466 0 : if (aSide == eSideTop) {
467 0 : offset.x = mBorderCornerDimensions[C_TL].width;
468 0 : } else if (aSide == eSideRight) {
469 0 : offset.x = mOuterRect.Width() - mBorderWidths[eSideRight];
470 0 : offset.y = mBorderCornerDimensions[C_TR].height;
471 0 : } else if (aSide == eSideBottom) {
472 0 : offset.x = mBorderCornerDimensions[C_BL].width;
473 0 : offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom];
474 0 : } else if (aSide == eSideLeft) {
475 0 : offset.y = mBorderCornerDimensions[C_TL].height;
476 : }
477 :
478 : // The sum of the width & height of the corners adjacent to the
479 : // side. This relies on the relationship between side indexing and
480 : // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
481 : // with both proceeding clockwise.
482 0 : Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)]
483 0 : + mBorderCornerDimensions[GetCWCorner(aSide)];
484 0 : Rect rect(mOuterRect.TopLeft() + offset,
485 0 : mOuterRect.Size() - sideCornerSum);
486 :
487 0 : if (IsHorizontalSide(aSide))
488 0 : rect.height = mBorderWidths[aSide];
489 : else
490 0 : rect.width = mBorderWidths[aSide];
491 :
492 0 : return rect;
493 : }
494 :
495 : // The side border type and the adjacent border types are
496 : // examined and one of the different types of clipping (listed
497 : // below) is selected.
498 :
499 : typedef enum {
500 : // clip to the trapezoid formed by the corners of the
501 : // inner and outer rectangles for the given side
502 : //
503 : // +---------------
504 : // |\%%%%%%%%%%%%%%
505 : // | \%%%%%%%%%%%%
506 : // | \%%%%%%%%%%%
507 : // | \%%%%%%%%%
508 : // | +--------
509 : // | |
510 : // | |
511 : SIDE_CLIP_TRAPEZOID,
512 :
513 : // clip to the trapezoid formed by the outer rectangle
514 : // corners and the center of the region, making sure
515 : // that diagonal lines all go directly from the outside
516 : // corner to the inside corner, but that they then continue on
517 : // to the middle.
518 : //
519 : // This is needed for correctly clipping rounded borders,
520 : // which might extend past the SIDE_CLIP_TRAPEZOID trap.
521 : //
522 : // +-------__--+---
523 : // \%%%%_-%%%%%%%%
524 : // \+-%%%%%%%%%%
525 : // / \%%%%%%%%%%
526 : // / \%%%%%%%%%
527 : // | +%%_-+---
528 : // | +%%%%%%
529 : // | / \%%%%%
530 : // + + \%%%
531 : // | | +-
532 : SIDE_CLIP_TRAPEZOID_FULL,
533 :
534 : // clip to the rectangle formed by the given side including corner.
535 : // This is used by the non-dotted side next to dotted side.
536 : //
537 : // +---------------
538 : // |%%%%%%%%%%%%%%%
539 : // |%%%%%%%%%%%%%%%
540 : // |%%%%%%%%%%%%%%%
541 : // |%%%%%%%%%%%%%%%
542 : // +------+--------
543 : // | |
544 : // | |
545 : SIDE_CLIP_RECTANGLE_CORNER,
546 :
547 : // clip to the rectangle formed by the given side excluding corner.
548 : // This is used by the dotted side next to non-dotted side.
549 : //
550 : // +------+--------
551 : // | |%%%%%%%%
552 : // | |%%%%%%%%
553 : // | |%%%%%%%%
554 : // | |%%%%%%%%
555 : // | +--------
556 : // | |
557 : // | |
558 : SIDE_CLIP_RECTANGLE_NO_CORNER,
559 : } SideClipType;
560 :
561 : // Given three points, p0, p1, and midPoint, move p1 further in to the
562 : // rectangle (of which aMidPoint is the center) so that it reaches the
563 : // closer of the horizontal or vertical lines intersecting the midpoint,
564 : // while maintaing the slope of the line. If p0 and p1 are the same,
565 : // just move p1 to midPoint (since there's no slope to maintain).
566 : // FIXME: Extending only to the midpoint isn't actually sufficient for
567 : // boxes with asymmetric radii.
568 : static void
569 0 : MaybeMoveToMidPoint(Point& aP0, Point& aP1, const Point& aMidPoint)
570 : {
571 0 : Point ps = aP1 - aP0;
572 :
573 0 : if (ps.x == 0.0) {
574 0 : if (ps.y == 0.0) {
575 0 : aP1 = aMidPoint;
576 : } else {
577 0 : aP1.y = aMidPoint.y;
578 : }
579 : } else {
580 0 : if (ps.y == 0.0) {
581 0 : aP1.x = aMidPoint.x;
582 : } else {
583 0 : Float k = std::min((aMidPoint.x - aP0.x) / ps.x,
584 0 : (aMidPoint.y - aP0.y) / ps.y);
585 0 : aP1 = aP0 + ps * k;
586 : }
587 : }
588 0 : }
589 :
590 : already_AddRefed<Path>
591 0 : nsCSSBorderRenderer::GetSideClipSubPath(mozilla::Side aSide)
592 : {
593 : // the clip proceeds clockwise from the top left corner;
594 : // so "start" in each case is the start of the region from that side.
595 : //
596 : // the final path will be formed like:
597 : // s0 ------- e0
598 : // | /
599 : // s1 ----- e1
600 : //
601 : // that is, the second point will always be on the inside
602 :
603 0 : Point start[2];
604 0 : Point end[2];
605 :
606 : #define IS_DOTTED(_s) ((_s) == NS_STYLE_BORDER_STYLE_DOTTED)
607 0 : bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
608 0 : bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
609 0 : bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
610 : #undef IS_DOTTED
611 :
612 0 : SideClipType startType = SIDE_CLIP_TRAPEZOID;
613 0 : SideClipType endType = SIDE_CLIP_TRAPEZOID;
614 :
615 0 : if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
616 0 : startType = SIDE_CLIP_TRAPEZOID_FULL;
617 0 : } else if (startIsDotted && !isDotted) {
618 0 : startType = SIDE_CLIP_RECTANGLE_CORNER;
619 0 : } else if (!startIsDotted && isDotted) {
620 0 : startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
621 : }
622 :
623 0 : if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
624 0 : endType = SIDE_CLIP_TRAPEZOID_FULL;
625 0 : } else if (endIsDotted && !isDotted) {
626 0 : endType = SIDE_CLIP_RECTANGLE_CORNER;
627 0 : } else if (!endIsDotted && isDotted) {
628 0 : endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
629 : }
630 :
631 0 : Point midPoint = mInnerRect.Center();
632 :
633 0 : start[0] = mOuterRect.CCWCorner(aSide);
634 0 : start[1] = mInnerRect.CCWCorner(aSide);
635 :
636 0 : end[0] = mOuterRect.CWCorner(aSide);
637 0 : end[1] = mInnerRect.CWCorner(aSide);
638 :
639 0 : if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
640 0 : MaybeMoveToMidPoint(start[0], start[1], midPoint);
641 0 : } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
642 0 : if (IsHorizontalSide(aSide)) {
643 0 : start[1] = Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
644 : } else {
645 0 : start[1] = Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
646 : }
647 0 : } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
648 0 : if (IsHorizontalSide(aSide)) {
649 0 : start[0] = Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
650 : } else {
651 0 : start[0] = Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
652 : }
653 : }
654 :
655 0 : if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
656 0 : MaybeMoveToMidPoint(end[0], end[1], midPoint);
657 0 : } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
658 0 : if (IsHorizontalSide(aSide)) {
659 0 : end[1] = Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
660 : } else {
661 0 : end[1] = Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
662 : }
663 0 : } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
664 0 : if (IsHorizontalSide(aSide)) {
665 0 : end[0] = Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
666 : } else {
667 0 : end[0] = Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
668 : }
669 : }
670 :
671 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
672 0 : builder->MoveTo(start[0]);
673 0 : builder->LineTo(end[0]);
674 0 : builder->LineTo(end[1]);
675 0 : builder->LineTo(start[1]);
676 0 : builder->Close();
677 0 : return builder->Finish();
678 : }
679 :
680 : Point
681 0 : nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
682 : Corner aCorner,
683 : bool* aIsUnfilled,
684 : Float aDotOffset)
685 : {
686 : // Calculate the end point of the side for dashed/dotted border, that is also
687 : // the end point of the corner curve. The point is specified by aSide and
688 : // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
689 : //
690 : //
691 : // aCorner aSide
692 : // +--------------------
693 : // |
694 : // |
695 : // | +----------
696 : // | the end point
697 : // |
698 : // | +----------
699 : // | |
700 : // | |
701 : // | |
702 : //
703 : // The position of the point depends on the border-style, border-width, and
704 : // border-radius of the side, corner, and the adjacent side beyond the corner,
705 : // to make those sides (and corner) interact well.
706 : //
707 : // If the style of aSide is dotted and the dot at the point should be
708 : // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
709 :
710 : const Float signsList[4][2] = {
711 : { +1.0f, +1.0f },
712 : { -1.0f, +1.0f },
713 : { -1.0f, -1.0f },
714 : { +1.0f, -1.0f }
715 0 : };
716 0 : const Float (& signs)[2] = signsList[aCorner];
717 :
718 0 : *aIsUnfilled = false;
719 :
720 0 : Point P = mOuterRect.AtCorner(aCorner);
721 0 : uint8_t style = mBorderStyles[aSide];
722 0 : Float borderWidth = mBorderWidths[aSide];
723 0 : Size dim = mBorderCornerDimensions[aCorner];
724 0 : bool isHorizontal = IsHorizontalSide(aSide);
725 : //
726 : // aCorner aSide
727 : // +--------------
728 : // |
729 : // | +----------
730 : // | |
731 : // otherSide | |
732 : // | |
733 0 : mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
734 0 : ? PREV_SIDE(aSide)
735 0 : : NEXT_SIDE(aSide);
736 0 : uint8_t otherStyle = mBorderStyles[otherSide];
737 0 : Float otherBorderWidth = mBorderWidths[otherSide];
738 0 : Size radius = mBorderRadii[aCorner];
739 0 : if (IsZeroSize(radius)) {
740 0 : radius.width = 0.0f;
741 0 : radius.height = 0.0f;
742 : }
743 0 : if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
744 : // Offset the dot's location along the side toward the corner by a
745 : // multiple of its width.
746 0 : if (isHorizontal) {
747 0 : P.x -= signs[0] * aDotOffset * borderWidth;
748 : } else {
749 0 : P.y -= signs[1] * aDotOffset * borderWidth;
750 : }
751 : }
752 0 : if (style == NS_STYLE_BORDER_STYLE_DOTTED &&
753 : otherStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
754 0 : if (borderWidth == otherBorderWidth) {
755 0 : if (radius.width < borderWidth / 2.0f &&
756 0 : radius.height < borderWidth / 2.0f) {
757 : // Two dots are merged into one and placed at the corner.
758 : //
759 : // borderWidth / 2.0
760 : // |<---------->|
761 : // | |
762 : // |radius.width|
763 : // |<--->| |
764 : // | | |
765 : // | _+------+------------+-----
766 : // | / ###|### |
767 : // |/ #######|####### |
768 : // + #########|######### |
769 : // | ##########|########## |
770 : // | ###########|########### |
771 : // | ###########|########### |
772 : // |############|############|
773 : // +------------+############|
774 : // |########### P ###########|
775 : // | ####################### |
776 : // | ####################### |
777 : // | ##################### |
778 : // | ################### |
779 : // | ############### |
780 : // | ####### |
781 : // +-------------------------+----
782 : // | |
783 : // | |
784 0 : P.x += signs[0] * borderWidth / 2.0f;
785 0 : P.y += signs[1] * borderWidth / 2.0f;
786 : } else {
787 : // Two dots are drawn separately.
788 : //
789 : // borderWidth * 1.5
790 : // |<------------>|
791 : // | |
792 : // |radius.width |
793 : // |<----->| |
794 : // | | |
795 : // | _--+-+----+---
796 : // | _- | ##|##
797 : // | / | ###|###
798 : // |/ |####|####
799 : // | |####+####
800 : // | |### P ###
801 : // + | ###|###
802 : // | | ##|##
803 : // +---------+----+---
804 : // | ##### |
805 : // | ####### |
806 : // |#########|
807 : // +----+----+
808 : // |#########|
809 : // | ####### |
810 : // | ##### |
811 : // | |
812 : //
813 : // There should be enough gap between 2 dots even if radius.width is
814 : // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the
815 : // value that there's imaginally unfilled dot at the corner. The
816 : // unfilled dot may overflow from the outer curve, but filled dots
817 : // doesn't, so this could be acceptable solution at least for now.
818 : // We may have to find better model/value.
819 : //
820 : // imaginally unfilled dot at the corner
821 : // |
822 : // v +----+---
823 : // ***** | ##|##
824 : // ******* | ###|###
825 : // *********|####|####
826 : // *********|####+####
827 : // *********|### P ###
828 : // ******* | ###|###
829 : // ***** | ##|##
830 : // +---------+----+---
831 : // | ##### |
832 : // | ####### |
833 : // |#########|
834 : // +----+----+
835 : // |#########|
836 : // | ####### |
837 : // | ##### |
838 : // | |
839 0 : Float minimum = borderWidth * 1.5f;
840 0 : if (isHorizontal) {
841 0 : P.x += signs[0] * std::max(radius.width, minimum);
842 0 : P.y += signs[1] * borderWidth / 2.0f;
843 : } else {
844 0 : P.x += signs[0] * borderWidth / 2.0f;
845 0 : P.y += signs[1] * std::max(radius.height, minimum);
846 : }
847 : }
848 :
849 0 : return P;
850 : }
851 :
852 0 : if (borderWidth < otherBorderWidth) {
853 : // This side is smaller than other side, other side draws the corner.
854 : //
855 : // otherBorderWidth + borderWidth / 2.0
856 : // |<---------->|
857 : // | |
858 : // +---------+--+--------
859 : // | ##### | *|* ###
860 : // | ####### |**|**#####
861 : // |#########|**+**##+##
862 : // |####+####|* P *#####
863 : // |#########| *** ###
864 : // | ####### +-----------
865 : // | ##### | ^
866 : // | | |
867 : // | | first dot is not filled
868 : // | |
869 : //
870 : // radius.width
871 : // |<----------------->|
872 : // | |
873 : // | ___---+-------------
874 : // | __-- #|# ###
875 : // | _- ##|## #####
876 : // | / ##+## ##+##
877 : // | / # P # #####
878 : // | | #|# ###
879 : // | | __--+-------------
880 : // || _- ^
881 : // || / |
882 : // | / first dot is filled
883 : // | |
884 : // | |
885 : // | ##### |
886 : // | ####### |
887 : // |#########|
888 : // +----+----+
889 : // |#########|
890 : // | ####### |
891 : // | ##### |
892 0 : Float minimum = otherBorderWidth + borderWidth / 2.0f;
893 0 : if (isHorizontal) {
894 0 : if (radius.width < minimum) {
895 0 : *aIsUnfilled = true;
896 0 : P.x += signs[0] * minimum;
897 : } else {
898 0 : P.x += signs[0] * radius.width;
899 : }
900 0 : P.y += signs[1] * borderWidth / 2.0f;
901 : } else {
902 0 : P.x += signs[0] * borderWidth / 2.0f;
903 0 : if (radius.height < minimum) {
904 0 : *aIsUnfilled = true;
905 0 : P.y += signs[1] * minimum;
906 : } else {
907 0 : P.y += signs[1] * radius.height;
908 : }
909 : }
910 :
911 0 : return P;
912 : }
913 :
914 : // This side is larger than other side, this side draws the corner.
915 : //
916 : // borderWidth / 2.0
917 : // |<-->|
918 : // | |
919 : // +----+---------------------
920 : // | ##|## #####
921 : // | ###|### #######
922 : // |####|#### #########
923 : // |####+#### ####+####
924 : // |### P ### #########
925 : // | ####### #######
926 : // | ##### #####
927 : // +-----+---------------------
928 : // | *** |
929 : // |*****|
930 : // |**+**| <-- first dot in other side is not filled
931 : // |*****|
932 : // | *** |
933 : // | ### |
934 : // |#####|
935 : // |##+##|
936 : // |#####|
937 : // | ### |
938 : // | |
939 0 : if (isHorizontal) {
940 0 : P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
941 0 : P.y += signs[1] * borderWidth / 2.0f;
942 : } else {
943 0 : P.x += signs[0] * borderWidth / 2.0f;
944 0 : P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
945 : }
946 0 : return P;
947 : }
948 :
949 0 : if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
950 : // If only this side is dotted, other side draws the corner.
951 : //
952 : // otherBorderWidth + borderWidth / 2.0
953 : // |<------->|
954 : // | |
955 : // +------+--+--------
956 : // |## ##| *|* ###
957 : // |## ##|**|**#####
958 : // |## ##|**+**##+##
959 : // |## ##|* P *#####
960 : // |## ##| *** ###
961 : // |## ##+-----------
962 : // |## ##| ^
963 : // |## ##| |
964 : // |## ##| first dot is not filled
965 : // |## ##|
966 : //
967 : // radius.width
968 : // |<----------------->|
969 : // | |
970 : // | ___---+-------------
971 : // | __-- #|# ###
972 : // | _- ##|## #####
973 : // | / ##+## ##+##
974 : // | / # P # #####
975 : // | | #|# ###
976 : // | | __--+-------------
977 : // || _- ^
978 : // || / |
979 : // | / first dot is filled
980 : // | |
981 : // | |
982 : // | |
983 : // | |
984 : // | |
985 : // +------+
986 : // |## ##|
987 : // |## ##|
988 : // |## ##|
989 0 : Float minimum = otherBorderWidth + borderWidth / 2.0f;
990 0 : if (isHorizontal) {
991 0 : if (radius.width < minimum) {
992 0 : *aIsUnfilled = true;
993 0 : P.x += signs[0] * minimum;
994 : } else {
995 0 : P.x += signs[0] * radius.width;
996 : }
997 0 : P.y += signs[1] * borderWidth / 2.0f;
998 : } else {
999 0 : P.x += signs[0] * borderWidth / 2.0f;
1000 0 : if (radius.height < minimum) {
1001 0 : *aIsUnfilled = true;
1002 0 : P.y += signs[1] * minimum;
1003 : } else {
1004 0 : P.y += signs[1] * radius.height;
1005 : }
1006 : }
1007 0 : return P;
1008 : }
1009 :
1010 0 : if (otherStyle == NS_STYLE_BORDER_STYLE_DOTTED && IsZeroSize(radius)) {
1011 : // If other side is dotted and radius=0, draw side to the end of corner.
1012 : //
1013 : // +-------------------------------
1014 : // |########## ##########
1015 : // P +########## ##########
1016 : // |########## ##########
1017 : // +-----+-------------------------
1018 : // | *** |
1019 : // |*****|
1020 : // |**+**| <-- first dot in other side is not filled
1021 : // |*****|
1022 : // | *** |
1023 : // | ### |
1024 : // |#####|
1025 : // |##+##|
1026 : // |#####|
1027 : // | ### |
1028 : // | |
1029 0 : if (isHorizontal) {
1030 0 : P.y += signs[1] * borderWidth / 2.0f;
1031 : } else {
1032 0 : P.x += signs[0] * borderWidth / 2.0f;
1033 : }
1034 0 : return P;
1035 : }
1036 :
1037 : // Other cases.
1038 : //
1039 : // dim.width
1040 : // |<----------------->|
1041 : // | |
1042 : // | ___---+------------------
1043 : // | __-- |####### ###
1044 : // | _- P +####### ###
1045 : // | / |####### ###
1046 : // | / __---+------------------
1047 : // | | __--
1048 : // | | /
1049 : // || /
1050 : // || |
1051 : // | |
1052 : // | |
1053 : // | |
1054 : // | |
1055 : // +-+-+
1056 : // |###|
1057 : // |###|
1058 : // |###|
1059 : // |###|
1060 : // |###|
1061 : // | |
1062 : // | |
1063 0 : if (isHorizontal) {
1064 0 : P.x += signs[0] * dim.width;
1065 0 : P.y += signs[1] * borderWidth / 2.0f;
1066 : } else {
1067 0 : P.x += signs[0] * borderWidth / 2.0f;
1068 0 : P.y += signs[1] * dim.height;
1069 : }
1070 :
1071 0 : return P;
1072 : }
1073 :
1074 : void
1075 0 : nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
1076 : Bezier* aInnerBezier,
1077 : Corner aCorner)
1078 : {
1079 : // Return bezier control points for outer and inner curve for given corner.
1080 : //
1081 : // ___---+ outer curve
1082 : // __-- |
1083 : // _- |
1084 : // / |
1085 : // / |
1086 : // | |
1087 : // | __--+ inner curve
1088 : // | _-
1089 : // | /
1090 : // | /
1091 : // | |
1092 : // | |
1093 : // | |
1094 : // | |
1095 : // | |
1096 : // +---------+
1097 :
1098 0 : mozilla::Side sideH(GetHorizontalSide(aCorner));
1099 0 : mozilla::Side sideV(GetVerticalSide(aCorner));
1100 :
1101 0 : Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
1102 0 : ceil(mBorderRadii[aCorner].height));
1103 0 : Size innerCornerSize(ceil(std::max(0.0f, mBorderRadii[aCorner].width -
1104 0 : mBorderWidths[sideV])),
1105 0 : ceil(std::max(0.0f, mBorderRadii[aCorner].height -
1106 0 : mBorderWidths[sideH])));
1107 :
1108 0 : GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
1109 0 : outerCornerSize);
1110 :
1111 0 : GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
1112 0 : innerCornerSize);
1113 0 : }
1114 :
1115 : void
1116 0 : nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
1117 : const Rect& aInnerRect,
1118 : const RectCornerRadii& aBorderRadii,
1119 : const Float* aBorderSizes,
1120 : int aSides,
1121 : const ColorPattern& aColor)
1122 : {
1123 : // Note that this function is allowed to draw more than just the
1124 : // requested sides.
1125 :
1126 : // If we have a border radius, do full rounded rectangles
1127 : // and fill, regardless of what sides we're asked to draw.
1128 0 : if (!AllCornersZeroSize(aBorderRadii)) {
1129 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
1130 :
1131 0 : RectCornerRadii innerRadii;
1132 0 : ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
1133 :
1134 : // do the outer border
1135 0 : AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
1136 :
1137 : // then do the inner border CCW
1138 0 : AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
1139 :
1140 0 : RefPtr<Path> path = builder->Finish();
1141 :
1142 0 : mDrawTarget->Fill(path, aColor);
1143 0 : return;
1144 : }
1145 :
1146 : // If we're asked to draw all sides of an equal-sized border,
1147 : // stroking is fastest. This is a fairly common path, but partial
1148 : // sides is probably second in the list -- there are a bunch of
1149 : // common border styles, such as inset and outset, that are
1150 : // top-left/bottom-right split.
1151 0 : if (aSides == eSideBitsAll &&
1152 0 : CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) &&
1153 0 : !mAvoidStroke)
1154 : {
1155 0 : Float strokeWidth = aBorderSizes[0];
1156 0 : Rect r(aOuterRect);
1157 0 : r.Deflate(strokeWidth / 2.f);
1158 0 : mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
1159 0 : return;
1160 : }
1161 :
1162 : // Otherwise, we have unequal sized borders or we're only
1163 : // drawing some sides; create rectangles for each side
1164 : // and fill them.
1165 :
1166 0 : Rect r[4];
1167 :
1168 : // compute base rects for each side
1169 0 : if (aSides & eSideBitsTop) {
1170 0 : r[eSideTop] =
1171 0 : Rect(aOuterRect.X(), aOuterRect.Y(),
1172 : aOuterRect.Width(), aBorderSizes[eSideTop]);
1173 : }
1174 :
1175 0 : if (aSides & eSideBitsBottom) {
1176 0 : r[eSideBottom] =
1177 0 : Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom],
1178 0 : aOuterRect.Width(), aBorderSizes[eSideBottom]);
1179 : }
1180 :
1181 0 : if (aSides & eSideBitsLeft) {
1182 0 : r[eSideLeft] =
1183 0 : Rect(aOuterRect.X(), aOuterRect.Y(),
1184 0 : aBorderSizes[eSideLeft], aOuterRect.Height());
1185 : }
1186 :
1187 0 : if (aSides & eSideBitsRight) {
1188 0 : r[eSideRight] =
1189 0 : Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(),
1190 0 : aBorderSizes[eSideRight], aOuterRect.Height());
1191 : }
1192 :
1193 : // If two sides meet at a corner that we're rendering, then
1194 : // make sure that we adjust one of the sides to avoid overlap.
1195 : // This is especially important in the case of colors with
1196 : // an alpha channel.
1197 :
1198 0 : if ((aSides & (eSideBitsTop | eSideBitsLeft)) == (eSideBitsTop | eSideBitsLeft)) {
1199 : // adjust the left's top down a bit
1200 0 : r[eSideLeft].y += aBorderSizes[eSideTop];
1201 0 : r[eSideLeft].height -= aBorderSizes[eSideTop];
1202 : }
1203 :
1204 0 : if ((aSides & (eSideBitsTop | eSideBitsRight)) == (eSideBitsTop | eSideBitsRight)) {
1205 : // adjust the top's left a bit
1206 0 : r[eSideTop].width -= aBorderSizes[eSideRight];
1207 : }
1208 :
1209 0 : if ((aSides & (eSideBitsBottom | eSideBitsRight)) == (eSideBitsBottom | eSideBitsRight)) {
1210 : // adjust the right's bottom a bit
1211 0 : r[eSideRight].height -= aBorderSizes[eSideBottom];
1212 : }
1213 :
1214 0 : if ((aSides & (eSideBitsBottom | eSideBitsLeft)) == (eSideBitsBottom | eSideBitsLeft)) {
1215 : // adjust the bottom's left a bit
1216 0 : r[eSideBottom].x += aBorderSizes[eSideLeft];
1217 0 : r[eSideBottom].width -= aBorderSizes[eSideLeft];
1218 : }
1219 :
1220 : // Filling these one by one is faster than filling them all at once.
1221 0 : for (uint32_t i = 0; i < 4; i++) {
1222 0 : if (aSides & (1 << i)) {
1223 0 : MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
1224 0 : mDrawTarget->FillRect(r[i], aColor);
1225 : }
1226 : }
1227 : }
1228 :
1229 : Color
1230 0 : MakeBorderColor(nscolor aColor, nscolor aBackgroundColor,
1231 : BorderColorStyle aBorderColorStyle)
1232 : {
1233 : nscolor colors[2];
1234 0 : int k = 0;
1235 :
1236 0 : switch (aBorderColorStyle) {
1237 : case BorderColorStyleNone:
1238 0 : return Color(0.f, 0.f, 0.f, 0.f); // transparent black
1239 :
1240 : case BorderColorStyleLight:
1241 0 : k = 1;
1242 : MOZ_FALLTHROUGH;
1243 : case BorderColorStyleDark:
1244 0 : NS_GetSpecial3DColors(colors, aBackgroundColor, aColor);
1245 0 : return Color::FromABGR(colors[k]);
1246 :
1247 : case BorderColorStyleSolid:
1248 : default:
1249 0 : return Color::FromABGR(aColor);
1250 : }
1251 : }
1252 :
1253 : Color
1254 0 : ComputeColorForLine(uint32_t aLineIndex,
1255 : const BorderColorStyle* aBorderColorStyle,
1256 : uint32_t aBorderColorStyleCount,
1257 : nscolor aBorderColor,
1258 : nscolor aBackgroundColor)
1259 : {
1260 0 : NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
1261 :
1262 : return MakeBorderColor(aBorderColor, aBackgroundColor,
1263 0 : aBorderColorStyle[aLineIndex]);
1264 : }
1265 :
1266 : Color
1267 0 : ComputeCompositeColorForLine(uint32_t aLineIndex,
1268 : const nsBorderColors* aBorderColors)
1269 : {
1270 0 : while (aLineIndex-- && aBorderColors->mNext)
1271 0 : aBorderColors = aBorderColors->mNext;
1272 :
1273 0 : return Color::FromABGR(aBorderColors->mColor);
1274 : }
1275 :
1276 : void
1277 0 : nsCSSBorderRenderer::DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *aCompositeColors)
1278 : {
1279 0 : RectCornerRadii radii = mBorderRadii;
1280 :
1281 : // the generic composite colors path; each border is 1px in size
1282 0 : Rect soRect = mOuterRect;
1283 0 : Float maxBorderWidth = 0;
1284 0 : NS_FOR_CSS_SIDES (i) {
1285 0 : maxBorderWidth = std::max(maxBorderWidth, Float(mBorderWidths[i]));
1286 : }
1287 :
1288 : Float fakeBorderSizes[4];
1289 :
1290 0 : Point itl = mInnerRect.TopLeft();
1291 0 : Point ibr = mInnerRect.BottomRight();
1292 :
1293 0 : for (uint32_t i = 0; i < uint32_t(maxBorderWidth); i++) {
1294 0 : ColorPattern color(ToDeviceColor(
1295 0 : ComputeCompositeColorForLine(i, aCompositeColors)));
1296 :
1297 0 : Rect siRect = soRect;
1298 0 : siRect.Deflate(1.0);
1299 :
1300 : // now cap the rects to the real mInnerRect
1301 0 : Point tl = siRect.TopLeft();
1302 0 : Point br = siRect.BottomRight();
1303 :
1304 0 : tl.x = std::min(tl.x, itl.x);
1305 0 : tl.y = std::min(tl.y, itl.y);
1306 :
1307 0 : br.x = std::max(br.x, ibr.x);
1308 0 : br.y = std::max(br.y, ibr.y);
1309 :
1310 0 : siRect = Rect(tl.x, tl.y, br.x - tl.x , br.y - tl.y);
1311 :
1312 0 : fakeBorderSizes[eSideTop] = siRect.TopLeft().y - soRect.TopLeft().y;
1313 0 : fakeBorderSizes[eSideRight] = soRect.TopRight().x - siRect.TopRight().x;
1314 0 : fakeBorderSizes[eSideBottom] = soRect.BottomRight().y - siRect.BottomRight().y;
1315 0 : fakeBorderSizes[eSideLeft] = siRect.BottomLeft().x - soRect.BottomLeft().x;
1316 :
1317 0 : FillSolidBorder(soRect, siRect, radii, fakeBorderSizes, aSides, color);
1318 :
1319 0 : soRect = siRect;
1320 :
1321 0 : ComputeInnerRadii(radii, fakeBorderSizes, &radii);
1322 : }
1323 0 : }
1324 :
1325 : void
1326 0 : nsCSSBorderRenderer::DrawBorderSides(int aSides)
1327 : {
1328 0 : if (aSides == 0 || (aSides & ~eSideBitsAll) != 0) {
1329 0 : NS_WARNING("DrawBorderSides: invalid sides!");
1330 0 : return;
1331 : }
1332 :
1333 0 : uint8_t borderRenderStyle = NS_STYLE_BORDER_STYLE_NONE;
1334 : nscolor borderRenderColor;
1335 0 : const nsBorderColors *compositeColors = nullptr;
1336 :
1337 0 : uint32_t borderColorStyleCount = 0;
1338 : BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
1339 0 : BorderColorStyle *borderColorStyle = nullptr;
1340 :
1341 0 : NS_FOR_CSS_SIDES (i) {
1342 0 : if ((aSides & (1 << i)) == 0)
1343 0 : continue;
1344 0 : borderRenderStyle = mBorderStyles[i];
1345 0 : borderRenderColor = mBorderColors[i];
1346 0 : compositeColors = mCompositeColors[i];
1347 0 : break;
1348 : }
1349 :
1350 0 : if (borderRenderStyle == NS_STYLE_BORDER_STYLE_NONE ||
1351 : borderRenderStyle == NS_STYLE_BORDER_STYLE_HIDDEN)
1352 0 : return;
1353 :
1354 0 : if (borderRenderStyle == NS_STYLE_BORDER_STYLE_DASHED ||
1355 : borderRenderStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
1356 : // Draw each corner separately, with the given side's color.
1357 0 : if (aSides & eSideBitsTop) {
1358 0 : DrawDashedOrDottedCorner(eSideTop, C_TL);
1359 0 : } else if (aSides & eSideBitsLeft) {
1360 0 : DrawDashedOrDottedCorner(eSideLeft, C_TL);
1361 : }
1362 :
1363 0 : if (aSides & eSideBitsTop) {
1364 0 : DrawDashedOrDottedCorner(eSideTop, C_TR);
1365 0 : } else if (aSides & eSideBitsRight) {
1366 0 : DrawDashedOrDottedCorner(eSideRight, C_TR);
1367 : }
1368 :
1369 0 : if (aSides & eSideBitsBottom) {
1370 0 : DrawDashedOrDottedCorner(eSideBottom, C_BL);
1371 0 : } else if (aSides & eSideBitsLeft) {
1372 0 : DrawDashedOrDottedCorner(eSideLeft, C_BL);
1373 : }
1374 :
1375 0 : if (aSides & eSideBitsBottom) {
1376 0 : DrawDashedOrDottedCorner(eSideBottom, C_BR);
1377 0 : } else if (aSides & eSideBitsRight) {
1378 0 : DrawDashedOrDottedCorner(eSideRight, C_BR);
1379 : }
1380 0 : return;
1381 : }
1382 :
1383 : // -moz-border-colors is a hack; if we have it for a border, then
1384 : // it's always drawn solid, and each color is given 1px. The last
1385 : // color is used for the remainder of the border's size. Just
1386 : // hand off to another function to do all that.
1387 0 : if (compositeColors) {
1388 0 : Float maxBorderWidth(0);
1389 0 : NS_FOR_CSS_SIDES (i) {
1390 0 : maxBorderWidth = std::max(maxBorderWidth, mBorderWidths[i]);
1391 : }
1392 0 : if (maxBorderWidth <= MAX_COMPOSITE_BORDER_WIDTH) {
1393 0 : DrawBorderSidesCompositeColors(aSides, compositeColors);
1394 0 : return;
1395 : }
1396 0 : NS_WARNING("DrawBorderSides: too large border width for composite colors");
1397 : }
1398 :
1399 : // We're not doing compositeColors, so we can calculate the
1400 : // borderColorStyle based on the specified style. The
1401 : // borderColorStyle array goes from the outer to the inner style.
1402 : //
1403 : // If the border width is 1, we need to change the borderRenderStyle
1404 : // a bit to make sure that we get the right colors -- e.g. 'ridge'
1405 : // with a 1px border needs to look like solid, not like 'outset'.
1406 0 : if (mOneUnitBorder &&
1407 0 : (borderRenderStyle == NS_STYLE_BORDER_STYLE_RIDGE ||
1408 0 : borderRenderStyle == NS_STYLE_BORDER_STYLE_GROOVE ||
1409 : borderRenderStyle == NS_STYLE_BORDER_STYLE_DOUBLE))
1410 0 : borderRenderStyle = NS_STYLE_BORDER_STYLE_SOLID;
1411 :
1412 0 : switch (borderRenderStyle) {
1413 : case NS_STYLE_BORDER_STYLE_SOLID:
1414 0 : borderColorStyleTopLeft[0] = BorderColorStyleSolid;
1415 :
1416 0 : borderColorStyleBottomRight[0] = BorderColorStyleSolid;
1417 :
1418 0 : borderColorStyleCount = 1;
1419 0 : break;
1420 :
1421 : case NS_STYLE_BORDER_STYLE_GROOVE:
1422 0 : borderColorStyleTopLeft[0] = BorderColorStyleDark;
1423 0 : borderColorStyleTopLeft[1] = BorderColorStyleLight;
1424 :
1425 0 : borderColorStyleBottomRight[0] = BorderColorStyleLight;
1426 0 : borderColorStyleBottomRight[1] = BorderColorStyleDark;
1427 :
1428 0 : borderColorStyleCount = 2;
1429 0 : break;
1430 :
1431 : case NS_STYLE_BORDER_STYLE_RIDGE:
1432 0 : borderColorStyleTopLeft[0] = BorderColorStyleLight;
1433 0 : borderColorStyleTopLeft[1] = BorderColorStyleDark;
1434 :
1435 0 : borderColorStyleBottomRight[0] = BorderColorStyleDark;
1436 0 : borderColorStyleBottomRight[1] = BorderColorStyleLight;
1437 :
1438 0 : borderColorStyleCount = 2;
1439 0 : break;
1440 :
1441 : case NS_STYLE_BORDER_STYLE_DOUBLE:
1442 0 : borderColorStyleTopLeft[0] = BorderColorStyleSolid;
1443 0 : borderColorStyleTopLeft[1] = BorderColorStyleNone;
1444 0 : borderColorStyleTopLeft[2] = BorderColorStyleSolid;
1445 :
1446 0 : borderColorStyleBottomRight[0] = BorderColorStyleSolid;
1447 0 : borderColorStyleBottomRight[1] = BorderColorStyleNone;
1448 0 : borderColorStyleBottomRight[2] = BorderColorStyleSolid;
1449 :
1450 0 : borderColorStyleCount = 3;
1451 0 : break;
1452 :
1453 : case NS_STYLE_BORDER_STYLE_INSET:
1454 0 : borderColorStyleTopLeft[0] = BorderColorStyleDark;
1455 0 : borderColorStyleBottomRight[0] = BorderColorStyleLight;
1456 :
1457 0 : borderColorStyleCount = 1;
1458 0 : break;
1459 :
1460 : case NS_STYLE_BORDER_STYLE_OUTSET:
1461 0 : borderColorStyleTopLeft[0] = BorderColorStyleLight;
1462 0 : borderColorStyleBottomRight[0] = BorderColorStyleDark;
1463 :
1464 0 : borderColorStyleCount = 1;
1465 0 : break;
1466 :
1467 : default:
1468 0 : NS_NOTREACHED("Unhandled border style!!");
1469 0 : break;
1470 : }
1471 :
1472 : // The only way to get to here is by having a
1473 : // borderColorStyleCount < 1 or > 3; this should never happen,
1474 : // since -moz-border-colors doesn't get handled here.
1475 0 : NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
1476 : "Non-border-colors case with borderColorStyleCount < 1 or > 3; what happened?");
1477 :
1478 : // The caller should never give us anything with a mix
1479 : // of TL/BR if the border style would require a
1480 : // TL/BR split.
1481 0 : if (aSides & (eSideBitsBottom | eSideBitsRight))
1482 0 : borderColorStyle = borderColorStyleBottomRight;
1483 : else
1484 0 : borderColorStyle = borderColorStyleTopLeft;
1485 :
1486 : // Distribute the border across the available space.
1487 : Float borderWidths[3][4];
1488 :
1489 0 : if (borderColorStyleCount == 1) {
1490 0 : NS_FOR_CSS_SIDES (i) {
1491 0 : borderWidths[0][i] = mBorderWidths[i];
1492 : }
1493 0 : } else if (borderColorStyleCount == 2) {
1494 : // with 2 color styles, any extra pixel goes to the outside
1495 0 : NS_FOR_CSS_SIDES (i) {
1496 0 : borderWidths[0][i] = int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2;
1497 0 : borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2;
1498 : }
1499 0 : } else if (borderColorStyleCount == 3) {
1500 : // with 3 color styles, any extra pixel (or lack of extra pixel)
1501 : // goes to the middle
1502 0 : NS_FOR_CSS_SIDES (i) {
1503 0 : if (mBorderWidths[i] == 1.0) {
1504 0 : borderWidths[0][i] = 1.f;
1505 0 : borderWidths[1][i] = borderWidths[2][i] = 0.f;
1506 : } else {
1507 0 : int32_t rest = int32_t(mBorderWidths[i]) % 3;
1508 0 : borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] = (int32_t(mBorderWidths[i]) - rest) / 3;
1509 :
1510 0 : if (rest == 1) {
1511 0 : borderWidths[1][i] += 1.f;
1512 0 : } else if (rest == 2) {
1513 0 : borderWidths[0][i] += 1.f;
1514 0 : borderWidths[2][i] += 1.f;
1515 : }
1516 : }
1517 : }
1518 : }
1519 :
1520 : // make a copy that we can modify
1521 0 : RectCornerRadii radii = mBorderRadii;
1522 :
1523 0 : Rect soRect(mOuterRect);
1524 0 : Rect siRect(mOuterRect);
1525 :
1526 : // If adjacent side is dotted and radius=0, draw side to the end of corner.
1527 : //
1528 : // +--------------------------------
1529 : // |################################
1530 : // |
1531 : // |################################
1532 : // +-----+--------------------------
1533 : // | |
1534 : // | |
1535 : // | |
1536 : // | |
1537 : // | |
1538 : // | ### |
1539 : // |#####|
1540 : // |#####|
1541 : // |#####|
1542 : // | ### |
1543 : // | |
1544 0 : bool noMarginTop = false;
1545 0 : bool noMarginRight = false;
1546 0 : bool noMarginBottom = false;
1547 0 : bool noMarginLeft = false;
1548 :
1549 : // If there is at least one dotted side, every side is rendered separately.
1550 0 : if (IsSingleSide(aSides)) {
1551 0 : if (aSides == eSideBitsTop) {
1552 0 : if (mBorderStyles[eSideRight] == NS_STYLE_BORDER_STYLE_DOTTED &&
1553 0 : IsZeroSize(mBorderRadii[C_TR])) {
1554 0 : noMarginRight = true;
1555 : }
1556 0 : if (mBorderStyles[eSideLeft] == NS_STYLE_BORDER_STYLE_DOTTED &&
1557 0 : IsZeroSize(mBorderRadii[C_TL])) {
1558 0 : noMarginLeft = true;
1559 : }
1560 0 : } else if (aSides == eSideBitsRight) {
1561 0 : if (mBorderStyles[eSideTop] == NS_STYLE_BORDER_STYLE_DOTTED &&
1562 0 : IsZeroSize(mBorderRadii[C_TR])) {
1563 0 : noMarginTop = true;
1564 : }
1565 0 : if (mBorderStyles[eSideBottom] == NS_STYLE_BORDER_STYLE_DOTTED &&
1566 0 : IsZeroSize(mBorderRadii[C_BR])) {
1567 0 : noMarginBottom = true;
1568 : }
1569 0 : } else if (aSides == eSideBitsBottom) {
1570 0 : if (mBorderStyles[eSideRight] == NS_STYLE_BORDER_STYLE_DOTTED &&
1571 0 : IsZeroSize(mBorderRadii[C_BR])) {
1572 0 : noMarginRight = true;
1573 : }
1574 0 : if (mBorderStyles[eSideLeft] == NS_STYLE_BORDER_STYLE_DOTTED &&
1575 0 : IsZeroSize(mBorderRadii[C_BL])) {
1576 0 : noMarginLeft = true;
1577 : }
1578 : } else {
1579 0 : if (mBorderStyles[eSideTop] == NS_STYLE_BORDER_STYLE_DOTTED &&
1580 0 : IsZeroSize(mBorderRadii[C_TL])) {
1581 0 : noMarginTop = true;
1582 : }
1583 0 : if (mBorderStyles[eSideBottom] == NS_STYLE_BORDER_STYLE_DOTTED &&
1584 0 : IsZeroSize(mBorderRadii[C_BL])) {
1585 0 : noMarginBottom = true;
1586 : }
1587 : }
1588 : }
1589 :
1590 0 : for (unsigned int i = 0; i < borderColorStyleCount; i++) {
1591 : // walk siRect inwards at the start of the loop to get the
1592 : // correct inner rect.
1593 : //
1594 : // If noMarginTop is false:
1595 : // --------------------+
1596 : // /|
1597 : // / |
1598 : // L |
1599 : // ----------------+ |
1600 : // | |
1601 : // | |
1602 : //
1603 : // If noMarginTop is true:
1604 : // ----------------+<--+
1605 : // | |
1606 : // | |
1607 : // | |
1608 : // | |
1609 : // | |
1610 : // | |
1611 0 : siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0],
1612 : noMarginRight ? 0 : borderWidths[i][1],
1613 : noMarginBottom ? 0 : borderWidths[i][2],
1614 0 : noMarginLeft ? 0 : borderWidths[i][3]));
1615 :
1616 0 : if (borderColorStyle[i] != BorderColorStyleNone) {
1617 : Color c = ComputeColorForLine(i, borderColorStyle, borderColorStyleCount,
1618 0 : borderRenderColor, mBackgroundColor);
1619 0 : ColorPattern color(ToDeviceColor(c));
1620 :
1621 0 : FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color);
1622 : }
1623 :
1624 0 : ComputeInnerRadii(radii, borderWidths[i], &radii);
1625 :
1626 : // And now soRect is the same as siRect, for the next line in.
1627 0 : soRect = siRect;
1628 : }
1629 : }
1630 :
1631 : void
1632 0 : nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions,
1633 : Float aDash[2],
1634 : mozilla::Side aSide,
1635 : Float aBorderLength, bool isCorner)
1636 : {
1637 0 : uint8_t style = mBorderStyles[aSide];
1638 0 : Float borderWidth = mBorderWidths[aSide];
1639 :
1640 : // Dashed line starts and ends with half segment in most case.
1641 : //
1642 : // __--+---+---+---+---+---+---+---+---+--__
1643 : // |###| | |###|###| | |###|
1644 : // |###| | |###|###| | |###|
1645 : // |###| | |###|###| | |###|
1646 : // __--+---+---+---+---+---+---+---+---+--__
1647 : //
1648 : // If radius=0 and other side is either dotted or 0-width, it starts or ends
1649 : // with full segment.
1650 : //
1651 : // +---+---+---+---+---+---+---+---+---+---+
1652 : // |###|###| | |###|###| | |###|###|
1653 : // |###|###| | |###|###| | |###|###|
1654 : // |###|###| | |###|###| | |###|###|
1655 : // +---++--+---+---+---+---+---+---+--++---+
1656 : // | | | |
1657 : // | | | |
1658 : // | | | |
1659 : // | | | |
1660 : // | ## | | ## |
1661 : // |####| |####|
1662 : // |####| |####|
1663 : // | ## | | ## |
1664 : // | | | |
1665 0 : bool fullStart = false, fullEnd = false;
1666 : Float halfDash;
1667 0 : if (style == NS_STYLE_BORDER_STYLE_DASHED) {
1668 0 : if (IsZeroSize(mBorderRadii[GetCCWCorner(aSide)]) &&
1669 0 : (mBorderStyles[PREV_SIDE(aSide)] == NS_STYLE_BORDER_STYLE_DOTTED ||
1670 0 : mBorderWidths[PREV_SIDE(aSide)] == 0.0f ||
1671 : borderWidth <= 1.0f)) {
1672 0 : fullStart = true;
1673 : }
1674 :
1675 0 : if (IsZeroSize(mBorderRadii[GetCWCorner(aSide)]) &&
1676 0 : (mBorderStyles[NEXT_SIDE(aSide)] == NS_STYLE_BORDER_STYLE_DOTTED ||
1677 0 : mBorderWidths[NEXT_SIDE(aSide)] == 0.0f)) {
1678 0 : fullEnd = true;
1679 : }
1680 :
1681 0 : halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f;
1682 : } else {
1683 0 : halfDash = borderWidth * DOT_LENGTH / 2.0f;
1684 : }
1685 :
1686 0 : if (style == NS_STYLE_BORDER_STYLE_DASHED && aBorderLength > 0.0f) {
1687 : // The number of half segments, with maximum dash length.
1688 0 : int32_t count = floor(aBorderLength / halfDash);
1689 0 : Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f;
1690 :
1691 0 : if (fullStart && fullEnd) {
1692 : // count should be 4n + 2
1693 : //
1694 : // 1 + 4 + 4 + 1
1695 : //
1696 : // | | | | |
1697 : // +---+---+---+---+---+---+---+---+---+---+
1698 : // |###|###| | |###|###| | |###|###|
1699 : // |###|###| | |###|###| | |###|###|
1700 : // |###|###| | |###|###| | |###|###|
1701 : // +---+---+---+---+---+---+---+---+---+---+
1702 :
1703 : // If border is too short, draw solid line.
1704 0 : if (aBorderLength < 6.0f * minHalfDash)
1705 0 : return;
1706 :
1707 0 : if (count % 4 == 0) {
1708 0 : count += 2;
1709 0 : } else if (count % 4 == 1) {
1710 0 : count += 1;
1711 0 : } else if (count % 4 == 3) {
1712 0 : count += 3;
1713 : }
1714 0 : } else if (fullStart || fullEnd) {
1715 : // count should be 4n + 1
1716 : //
1717 : // 1 + 4 + 4
1718 : //
1719 : // | | | |
1720 : // +---+---+---+---+---+---+---+---+---+
1721 : // |###|###| | |###|###| | |###|
1722 : // |###|###| | |###|###| | |###|
1723 : // |###|###| | |###|###| | |###|
1724 : // +---+---+---+---+---+---+---+---+---+
1725 : //
1726 : // 4 + 4 + 1
1727 : //
1728 : // | | | |
1729 : // +---+---+---+---+---+---+---+---+---+
1730 : // |###| | |###|###| | |###|###|
1731 : // |###| | |###|###| | |###|###|
1732 : // |###| | |###|###| | |###|###|
1733 : // +---+---+---+---+---+---+---+---+---+
1734 :
1735 : // If border is too short, draw solid line.
1736 0 : if (aBorderLength < 5.0f * minHalfDash)
1737 0 : return;
1738 :
1739 0 : if (count % 4 == 0) {
1740 0 : count += 1;
1741 0 : } else if (count % 4 == 2) {
1742 0 : count += 3;
1743 0 : } else if (count % 4 == 3) {
1744 0 : count += 2;
1745 : }
1746 : } else {
1747 : // count should be 4n
1748 : //
1749 : // 4 + 4
1750 : //
1751 : // | | |
1752 : // +---+---+---+---+---+---+---+---+
1753 : // |###| | |###|###| | |###|
1754 : // |###| | |###|###| | |###|
1755 : // |###| | |###|###| | |###|
1756 : // +---+---+---+---+---+---+---+---+
1757 :
1758 : // If border is too short, draw solid line.
1759 0 : if (aBorderLength < 4.0f * minHalfDash)
1760 0 : return;
1761 :
1762 0 : if (count % 4 == 1) {
1763 0 : count += 3;
1764 0 : } else if (count % 4 == 2) {
1765 0 : count += 2;
1766 0 : } else if (count % 4 == 3) {
1767 0 : count += 1;
1768 : }
1769 : }
1770 0 : halfDash = aBorderLength / count;
1771 : }
1772 :
1773 0 : Float fullDash = halfDash * 2.0f;
1774 :
1775 0 : aDash[0] = fullDash;
1776 0 : aDash[1] = fullDash;
1777 :
1778 0 : if (style == NS_STYLE_BORDER_STYLE_DASHED && fullDash > 1.0f) {
1779 0 : if (!fullStart) {
1780 : // Draw half segments on both ends.
1781 0 : aStrokeOptions->mDashOffset = halfDash;
1782 : }
1783 0 : } else if (style != NS_STYLE_BORDER_STYLE_DOTTED && isCorner) {
1784 : // If side ends with filled full segment, corner should start with unfilled
1785 : // full segment. Not needed for dotted corners, as they overlap one dot with
1786 : // the side's end.
1787 : //
1788 : // corner side
1789 : // ------------>|<---------------------------
1790 : // |
1791 : // __+---+---+---+---+---+---+---+---+
1792 : // _+- | |###|###| | |###|###| |
1793 : // /##| | |###|###| | |###|###| |
1794 : // +####| | |###|###| | |###|###| |
1795 : // /#\####| _+--+---+---+---+---+---+---+---+
1796 : // |####\##+-
1797 : // |#####+-
1798 : // +--###/
1799 : // | --+
1800 0 : aStrokeOptions->mDashOffset = fullDash;
1801 : }
1802 :
1803 0 : aStrokeOptions->mDashPattern = aDash;
1804 0 : aStrokeOptions->mDashLength = 2;
1805 :
1806 0 : PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]);
1807 : }
1808 :
1809 : static Float
1810 0 : GetBorderLength(mozilla::Side aSide,
1811 : const Point& aStart, const Point& aEnd)
1812 : {
1813 0 : if (aSide == eSideTop) {
1814 0 : return aEnd.x - aStart.x;
1815 : }
1816 0 : if (aSide == eSideRight) {
1817 0 : return aEnd.y - aStart.y;
1818 : }
1819 0 : if (aSide == eSideBottom) {
1820 0 : return aStart.x - aEnd.x;
1821 : }
1822 0 : return aStart.y - aEnd.y;
1823 : }
1824 :
1825 : void
1826 0 : nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide)
1827 : {
1828 : // Draw dashed/dotted side with following approach.
1829 : //
1830 : // dashed side
1831 : // Draw dashed line along the side, with appropriate dash length and gap
1832 : // to make the side symmetric as far as possible. Dash length equals to
1833 : // the gap, and the ratio of the dash length to border-width is the maximum
1834 : // value in in [1, 3] range.
1835 : // In most case, line ends with half segment, to joint with corner easily.
1836 : // If adjacent side is dotted or 0px and border-radius for the corner
1837 : // between them is 0, the line ends with full segment.
1838 : // (see comment for GetStraightBorderPoint for more detail)
1839 : //
1840 : // dotted side
1841 : // If border-width <= 2.0, draw 1:1 dashed line.
1842 : // Otherwise, draw circles along the side, with appropriate gap that makes
1843 : // the side symmetric as far as possible. The ratio of the gap to
1844 : // border-width is the maximum value in [0.5, 1] range in most case.
1845 : // if the side is too short and there's only 2 dots, it can be more smaller.
1846 : // If there's no space to place 2 dots at the side, draw single dot at the
1847 : // middle of the side.
1848 : // In most case, line ends with filled dot, to joint with corner easily,
1849 : // If adjacent side is dotted with larger border-width, or other style,
1850 : // the line ends with unfilled dot.
1851 : // (see comment for GetStraightBorderPoint for more detail)
1852 :
1853 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
1854 : mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
1855 : "Style should be dashed or dotted.");
1856 :
1857 0 : Float borderWidth = mBorderWidths[aSide];
1858 0 : if (borderWidth == 0.0f) {
1859 0 : return;
1860 : }
1861 :
1862 0 : if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED &&
1863 : borderWidth > 2.0f) {
1864 0 : DrawDottedSideSlow(aSide);
1865 0 : return;
1866 : }
1867 :
1868 0 : nscolor borderColor = mBorderColors[aSide];
1869 : bool ignored;
1870 : // Get the start and end points of the side, ensuring that any dot origins get
1871 : // pushed outward to account for stroking.
1872 0 : Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
1873 0 : Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
1874 0 : if (borderWidth < 2.0f) {
1875 : // Round start to draw dot on each pixel.
1876 0 : if (IsHorizontalSide(aSide)) {
1877 0 : start.x = round(start.x);
1878 : } else {
1879 0 : start.y = round(start.y);
1880 : }
1881 : }
1882 :
1883 0 : Float borderLength = GetBorderLength(aSide, start, end);
1884 0 : if (borderLength < 0.0f) {
1885 0 : return;
1886 : }
1887 :
1888 0 : StrokeOptions strokeOptions(borderWidth);
1889 : Float dash[2];
1890 0 : SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
1891 :
1892 : // For dotted sides that can merge with their prior dotted sides, advance the
1893 : // dash offset to measure the distance around the combined path. This prevents
1894 : // two dots from bunching together at a corner.
1895 0 : mozilla::Side mergeSide = aSide;
1896 0 : while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
1897 0 : mergeSide = PREV_SIDE(mergeSide);
1898 : // If we looped all the way around, measure starting at the top side, since
1899 : // we need to pick a fixed location to start measuring distance from still.
1900 0 : if (mergeSide == aSide) {
1901 0 : mergeSide = eSideTop;
1902 0 : break;
1903 : }
1904 : }
1905 0 : while (mergeSide != aSide) {
1906 : // Measure the length of the merged side starting from a possibly unmergeable
1907 : // corner up to the merged corner. A merged corner effectively has no border
1908 : // radius, so we can just use the cheaper AtCorner to find the end point.
1909 : Float mergeLength =
1910 : GetBorderLength(mergeSide,
1911 0 : GetStraightBorderPoint(mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
1912 0 : mOuterRect.AtCorner(GetCWCorner(mergeSide)));
1913 : // Add in the merged side length. Also offset the dash progress by an extra
1914 : // dot's width to avoid drawing a dot that would overdraw where the merged side
1915 : // would have ended in a gap, i.e. O_O_
1916 : // O
1917 0 : strokeOptions.mDashOffset += mergeLength + borderWidth;
1918 0 : mergeSide = NEXT_SIDE(mergeSide);
1919 : }
1920 :
1921 0 : DrawOptions drawOptions;
1922 0 : if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED) {
1923 0 : drawOptions.mAntialiasMode = AntialiasMode::NONE;
1924 : }
1925 :
1926 0 : mDrawTarget->StrokeLine(start, end,
1927 0 : ColorPattern(ToDeviceColor(borderColor)),
1928 : strokeOptions,
1929 0 : drawOptions);
1930 : }
1931 :
1932 : void
1933 0 : nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide)
1934 : {
1935 : // Draw each circles separately for dotted with borderWidth > 2.0.
1936 : // Dashed line with CapStyle::ROUND doesn't render perfect circles.
1937 :
1938 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
1939 : "Style should be dotted.");
1940 :
1941 0 : Float borderWidth = mBorderWidths[aSide];
1942 0 : if (borderWidth == 0.0f) {
1943 0 : return;
1944 : }
1945 :
1946 0 : nscolor borderColor = mBorderColors[aSide];
1947 : bool isStartUnfilled, isEndUnfilled;
1948 : Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide),
1949 0 : &isStartUnfilled);
1950 : Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide),
1951 0 : &isEndUnfilled);
1952 : enum {
1953 : // Corner is not mergeable.
1954 : NO_MERGE,
1955 :
1956 : // Corner between different colors.
1957 : // Two dots are merged into one, and both side draw half dot.
1958 : MERGE_HALF,
1959 :
1960 : // Corner between same colors, CCW corner of the side.
1961 : // Two dots are merged into one, and this side draw entire dot.
1962 : //
1963 : // MERGE_ALL MERGE_NONE
1964 : // | |
1965 : // v v
1966 : // +-----------------------+----+
1967 : // | ## ## ## | ## |
1968 : // |#### #### #### |####|
1969 : // |#### #### #### |####|
1970 : // | ## ## ## | ## |
1971 : // +----+------------------+ |
1972 : // | | | |
1973 : // | | | |
1974 : // | | | |
1975 : // | ## | | ## |
1976 : // |####| |####|
1977 : MERGE_ALL,
1978 :
1979 : // Corner between same colors, CW corner of the side.
1980 : // Two dots are merged into one, and this side doesn't draw dot.
1981 : MERGE_NONE
1982 0 : } mergeStart = NO_MERGE, mergeEnd = NO_MERGE;
1983 0 : if (IsCornerMergeable(GetCCWCorner(aSide))) {
1984 0 : if (borderColor == mBorderColors[PREV_SIDE(aSide)]) {
1985 0 : mergeStart = MERGE_ALL;
1986 : } else {
1987 0 : mergeStart = MERGE_HALF;
1988 : }
1989 : }
1990 :
1991 0 : if (IsCornerMergeable(GetCWCorner(aSide))) {
1992 0 : if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) {
1993 0 : mergeEnd = MERGE_NONE;
1994 : } else {
1995 0 : mergeEnd = MERGE_HALF;
1996 : }
1997 : }
1998 :
1999 0 : Float borderLength = GetBorderLength(aSide, start, end);
2000 0 : if (borderLength < 0.0f) {
2001 0 : if (isStartUnfilled || isEndUnfilled) {
2002 0 : return;
2003 : }
2004 0 : borderLength = 0.0f;
2005 0 : start = end = (start + end) / 2.0f;
2006 : }
2007 :
2008 0 : Float dotWidth = borderWidth * DOT_LENGTH;
2009 0 : Float radius = borderWidth / 2.0f;
2010 0 : if (borderLength < dotWidth) {
2011 : // If dots on start and end may overlap, draw a dot at the middle of them.
2012 : //
2013 : // ___---+-------+---___
2014 : // __-- | ##### | --__
2015 : // #|#######|#
2016 : // ##|#######|##
2017 : // ###|#######|###
2018 : // ###+###+###+###
2019 : // start ## end #
2020 : // ##|#######|##
2021 : // #|#######|#
2022 : // | ##### |
2023 : // __--+-------+--__
2024 : // _- -_
2025 : //
2026 : // If that circle overflows from outer rect, do not draw it.
2027 : //
2028 : // +-------+
2029 : // | ##### |
2030 : // #|#######|#
2031 : // ##|#######|##
2032 : // ###|#######|###
2033 : // ###|###+###|###
2034 : // ###|#######|###
2035 : // ##|#######|##
2036 : // #|#######|#
2037 : // | ##### |
2038 : // +--+-+--+
2039 : // | | | |
2040 : // | | | |
2041 0 : if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius,
2042 : borderWidth, borderWidth))) {
2043 0 : return;
2044 : }
2045 :
2046 0 : if (isStartUnfilled || isEndUnfilled) {
2047 0 : return;
2048 : }
2049 :
2050 0 : Point P = (start + end) / 2;
2051 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2052 0 : builder->MoveTo(Point(P.x + radius, P.y));
2053 0 : builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
2054 0 : RefPtr<Path> path = builder->Finish();
2055 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2056 0 : return;
2057 : }
2058 :
2059 0 : if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
2060 : // MERGE_HALF
2061 : // Eo
2062 : // -------+----+
2063 : // ##### /
2064 : // ######/
2065 : // ######/
2066 : // ####+
2067 : // ##/ end
2068 : // /
2069 : // /
2070 : // --+
2071 : // Ei
2072 : //
2073 : // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
2074 : // Eo
2075 : // ------------+
2076 : // ##### |
2077 : // ####### |
2078 : // #########|
2079 : // ####+####|
2080 : // ## end ##|
2081 : // ####### |
2082 : // ##### |
2083 : // ------------+
2084 : // Ei
2085 :
2086 0 : Point I(0.0f, 0.0f), J(0.0f, 0.0f);
2087 0 : if (aSide == eSideTop) {
2088 0 : I.x = 1.0f;
2089 0 : J.y = 1.0f;
2090 0 : } else if (aSide == eSideRight) {
2091 0 : I.y = 1.0f;
2092 0 : J.x = -1.0f;
2093 0 : } else if (aSide == eSideBottom) {
2094 0 : I.x = -1.0f;
2095 0 : J.y = -1.0f;
2096 0 : } else if (aSide == eSideLeft) {
2097 0 : I.y = -1.0f;
2098 0 : J.x = 1.0f;
2099 : }
2100 :
2101 0 : Point So, Si, Eo, Ei;
2102 :
2103 0 : So = (start + (-I + -J) * borderWidth / 2.0f);
2104 : Si = (mergeStart == MERGE_HALF)
2105 0 : ? (start + (I + J) * borderWidth / 2.0f)
2106 0 : : (start + (-I + J) * borderWidth / 2.0f);
2107 0 : Eo = (end + (I - J) * borderWidth / 2.0f);
2108 : Ei = (mergeEnd == MERGE_HALF)
2109 0 : ? (end + (-I + J) * borderWidth / 2.0f)
2110 0 : : (end + (I + J) * borderWidth / 2.0f);
2111 :
2112 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2113 0 : builder->MoveTo(So);
2114 0 : builder->LineTo(Eo);
2115 0 : builder->LineTo(Ei);
2116 0 : builder->LineTo(Si);
2117 0 : builder->Close();
2118 0 : RefPtr<Path> path = builder->Finish();
2119 :
2120 0 : mDrawTarget->PushClip(path);
2121 : }
2122 :
2123 0 : size_t count = round(borderLength / dotWidth);
2124 0 : if (isStartUnfilled == isEndUnfilled) {
2125 : // Split into 2n segments.
2126 0 : if (count % 2) {
2127 0 : count++;
2128 : }
2129 : } else {
2130 : // Split into 2n+1 segments.
2131 0 : if (count % 2 == 0) {
2132 0 : count++;
2133 : }
2134 : }
2135 :
2136 : // A: radius == borderWidth / 2.0
2137 : // B: borderLength / count == borderWidth * (1 - overlap)
2138 : //
2139 : // A B B B B A
2140 : // |<-->|<------>|<------>|<------>|<------>|<-->|
2141 : // | | | | | | |
2142 : // +----+--------+--------+--------+--------+----+
2143 : // | ##|## **|** ##|## **|** ##|## |
2144 : // | ###|### ***|*** ###|### ***|*** ###|### |
2145 : // |####|####****|****####|####****|****####|####|
2146 : // |####+####****+****####+####****+****####+####|
2147 : // |# start #****|****####|####****|****## end ##|
2148 : // | ###|### ***|*** ###|### ***|*** ###|### |
2149 : // | ##|## **|** ##|## **|** ##|## |
2150 : // +----+----+---+--------+--------+---+----+----+
2151 : // | | | |
2152 : // | | | |
2153 :
2154 : // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
2155 : // 2j points.
2156 0 : size_t from = isStartUnfilled ? 1 : 0;
2157 :
2158 : // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
2159 0 : size_t to = count;
2160 0 : if (mergeEnd == MERGE_NONE) {
2161 0 : if (to > 2) {
2162 0 : to -= 2;
2163 : } else {
2164 0 : to = 0;
2165 : }
2166 : }
2167 :
2168 0 : Point fromP = (start * (count - from) + end * from) / count;
2169 0 : Point toP = (start * (count - to) + end * to) / count;
2170 : // Extend dirty rect to avoid clipping pixel for anti-aliasing.
2171 0 : const Float AA_MARGIN = 2.0f;
2172 :
2173 0 : if (aSide == eSideTop) {
2174 : // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
2175 : // to render only paths that may overlap mDirtyRect.
2176 : //
2177 : // mDirtyRect + radius margin
2178 : // +--+---------------------+--+
2179 : // | |
2180 : // | mDirtyRect |
2181 : // + +---------------------+ +
2182 : // from ===> |from to | <=== to
2183 : // +-----+-----+-----+-----+-----+-----+-----+-----+
2184 : // ### |### ### ###| ###
2185 : // ##### ##### ##### ##### #####
2186 : // ##### ##### ##### ##### #####
2187 : // ##### ##### ##### ##### #####
2188 : // ### |### ### ###| ###
2189 : // | | | |
2190 : // + +---------------------+ +
2191 : // | |
2192 : // | |
2193 : // +--+---------------------+--+
2194 :
2195 0 : Float left = mDirtyRect.x - radius - AA_MARGIN;
2196 0 : if (fromP.x < left) {
2197 0 : size_t tmp = ceil(count * (left - start.x) / (end.x - start.x));
2198 0 : if (tmp > from) {
2199 : // We increment by 2, so odd/even should match between before/after.
2200 0 : if ((tmp & 1) != (from & 1)) {
2201 0 : from = tmp - 1;
2202 : } else {
2203 0 : from = tmp;
2204 : }
2205 : }
2206 : }
2207 0 : Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
2208 0 : if (toP.x > right) {
2209 0 : size_t tmp = floor(count * (right - start.x) / (end.x - start.x));
2210 0 : if (tmp < to) {
2211 0 : if ((tmp & 1) != (to & 1)) {
2212 0 : to = tmp + 1;
2213 : } else {
2214 0 : to = tmp;
2215 : }
2216 : }
2217 : }
2218 0 : } else if (aSide == eSideRight) {
2219 0 : Float top = mDirtyRect.y - radius - AA_MARGIN;
2220 0 : if (fromP.y < top) {
2221 0 : size_t tmp = ceil(count * (top - start.y) / (end.y - start.y));
2222 0 : if (tmp > from) {
2223 0 : if ((tmp & 1) != (from & 1)) {
2224 0 : from = tmp - 1;
2225 : } else {
2226 0 : from = tmp;
2227 : }
2228 : }
2229 : }
2230 0 : Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
2231 0 : if (toP.y > bottom) {
2232 0 : size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y));
2233 0 : if (tmp < to) {
2234 0 : if ((tmp & 1) != (to & 1)) {
2235 0 : to = tmp + 1;
2236 : } else {
2237 0 : to = tmp;
2238 : }
2239 : }
2240 : }
2241 0 : } else if (aSide == eSideBottom) {
2242 0 : Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
2243 0 : if (fromP.x > right) {
2244 0 : size_t tmp = ceil(count * (right - start.x) / (end.x - start.x));
2245 0 : if (tmp > from) {
2246 0 : if ((tmp & 1) != (from & 1)) {
2247 0 : from = tmp - 1;
2248 : } else {
2249 0 : from = tmp;
2250 : }
2251 : }
2252 : }
2253 0 : Float left = mDirtyRect.x - radius - AA_MARGIN;
2254 0 : if (toP.x < left) {
2255 0 : size_t tmp = floor(count * (left - start.x) / (end.x - start.x));
2256 0 : if (tmp < to) {
2257 0 : if ((tmp & 1) != (to & 1)) {
2258 0 : to = tmp + 1;
2259 : } else {
2260 0 : to = tmp;
2261 : }
2262 : }
2263 : }
2264 0 : } else if (aSide == eSideLeft) {
2265 0 : Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
2266 0 : if (fromP.y > bottom) {
2267 0 : size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y));
2268 0 : if (tmp > from) {
2269 0 : if ((tmp & 1) != (from & 1)) {
2270 0 : from = tmp - 1;
2271 : } else {
2272 0 : from = tmp;
2273 : }
2274 : }
2275 : }
2276 0 : Float top = mDirtyRect.y - radius - AA_MARGIN;
2277 0 : if (toP.y < top) {
2278 0 : size_t tmp = floor(count * (top - start.y) / (end.y - start.y));
2279 0 : if (tmp < to) {
2280 0 : if ((tmp & 1) != (to & 1)) {
2281 0 : to = tmp + 1;
2282 : } else {
2283 0 : to = tmp;
2284 : }
2285 : }
2286 : }
2287 : }
2288 :
2289 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2290 0 : size_t segmentCount = 0;
2291 0 : for (size_t i = from; i <= to; i += 2) {
2292 0 : if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2293 0 : RefPtr<Path> path = builder->Finish();
2294 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2295 0 : builder = mDrawTarget->CreatePathBuilder();
2296 0 : segmentCount = 0;
2297 : }
2298 :
2299 0 : Point P = (start * (count - i) + end * i) / count;
2300 0 : builder->MoveTo(Point(P.x + radius, P.y));
2301 0 : builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
2302 0 : segmentCount++;
2303 : }
2304 0 : RefPtr<Path> path = builder->Finish();
2305 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2306 :
2307 0 : if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
2308 0 : mDrawTarget->PopClip();
2309 : }
2310 : }
2311 :
2312 : void
2313 0 : nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide,
2314 : Corner aCorner)
2315 : {
2316 : // Draw dashed/dotted corner with following approach.
2317 : //
2318 : // dashed corner
2319 : // If both side has same border-width and border-width <= 2.0, draw dashed
2320 : // line along the corner, with appropriate dash length and gap to make the
2321 : // corner symmetric as far as possible. Dash length equals to the gap, and
2322 : // the ratio of the dash length to border-width is the maximum value in in
2323 : // [1, 3] range.
2324 : // Otherwise, draw dashed segments along the corner, keeping same dash
2325 : // length ratio to border-width at that point.
2326 : // (see DashedCornerFinder.h for more detail)
2327 : // Line ends with half segments, to joint with both side easily.
2328 : //
2329 : // dotted corner
2330 : // If both side has same border-width and border-width <= 2.0, draw 1:1
2331 : // dashed line along the corner.
2332 : // Otherwise Draw circles along the corner, with appropriate gap that makes
2333 : // the corner symmetric as far as possible. The size of the circle may
2334 : // change along the corner, that is tangent to the outer curver and the
2335 : // inner curve. The ratio of the gap to circle diameter is the maximum
2336 : // value in [0.5, 1] range.
2337 : // (see DottedCornerFinder.h for more detail)
2338 : // Corner ends with filled dots but those dots are drawn by
2339 : // DrawDashedOrDottedSide. So this may draw no circles if there's no space
2340 : // between 2 dots at both ends.
2341 :
2342 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
2343 : mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
2344 : "Style should be dashed or dotted.");
2345 :
2346 0 : if (IsCornerMergeable(aCorner)) {
2347 : // DrawDashedOrDottedSide will draw corner.
2348 0 : return;
2349 : }
2350 :
2351 0 : mozilla::Side sideH(GetHorizontalSide(aCorner));
2352 0 : mozilla::Side sideV(GetVerticalSide(aCorner));
2353 0 : Float borderWidthH = mBorderWidths[sideH];
2354 0 : Float borderWidthV = mBorderWidths[sideV];
2355 0 : if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
2356 0 : return;
2357 : }
2358 :
2359 0 : Float styleH = mBorderStyles[sideH];
2360 0 : Float styleV = mBorderStyles[sideV];
2361 :
2362 : // Corner between dotted and others with radius=0 is drawn by side.
2363 0 : if (IsZeroSize(mBorderRadii[aCorner]) &&
2364 0 : (styleV == NS_STYLE_BORDER_STYLE_DOTTED ||
2365 : styleH == NS_STYLE_BORDER_STYLE_DOTTED)) {
2366 0 : return;
2367 : }
2368 :
2369 0 : Float maxRadius = std::max(mBorderRadii[aCorner].width,
2370 0 : mBorderRadii[aCorner].height);
2371 0 : if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) {
2372 0 : DrawFallbackSolidCorner(aSide, aCorner);
2373 0 : return;
2374 : }
2375 :
2376 0 : if (borderWidthH != borderWidthV || borderWidthH > 2.0f) {
2377 0 : uint8_t style = mBorderStyles[aSide];
2378 0 : if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
2379 0 : DrawDottedCornerSlow(aSide, aCorner);
2380 : } else {
2381 0 : DrawDashedCornerSlow(aSide, aCorner);
2382 : }
2383 0 : return;
2384 : }
2385 :
2386 0 : nscolor borderColor = mBorderColors[aSide];
2387 0 : Point points[4];
2388 : bool ignored;
2389 : // Get the start and end points of the corner arc, ensuring that any dot
2390 : // origins get pushed backwards towards the edges of the corner rect to
2391 : // account for stroking.
2392 0 : points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
2393 0 : points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
2394 : // Round points to draw dot on each pixel.
2395 0 : if (borderWidthH < 2.0f) {
2396 0 : points[0].x = round(points[0].x);
2397 : }
2398 0 : if (borderWidthV < 2.0f) {
2399 0 : points[3].y = round(points[3].y);
2400 : }
2401 0 : points[1] = points[0];
2402 0 : points[1].x += kKappaFactor * (points[3].x - points[0].x);
2403 0 : points[2] = points[3];
2404 0 : points[2].y += kKappaFactor * (points[0].y - points[3].y);
2405 :
2406 0 : Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x),
2407 0 : fabs(points[0].y - points[3].y));
2408 :
2409 : Float dash[2];
2410 0 : StrokeOptions strokeOptions(borderWidthH);
2411 0 : SetupDashedOptions(&strokeOptions, dash, aSide, len, true);
2412 :
2413 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2414 0 : builder->MoveTo(points[0]);
2415 0 : builder->BezierTo(points[1], points[2], points[3]);
2416 0 : RefPtr<Path> path = builder->Finish();
2417 0 : mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
2418 0 : strokeOptions);
2419 : }
2420 :
2421 : void
2422 0 : nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide,
2423 : Corner aCorner)
2424 : {
2425 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
2426 : "Style should be dotted.");
2427 :
2428 0 : mozilla::Side sideH(GetHorizontalSide(aCorner));
2429 0 : mozilla::Side sideV(GetVerticalSide(aCorner));
2430 0 : Float R0 = mBorderWidths[sideH] / 2.0f;
2431 0 : Float Rn = mBorderWidths[sideV] / 2.0f;
2432 0 : if (R0 == 0.0f && Rn == 0.0f) {
2433 0 : return;
2434 : }
2435 :
2436 0 : nscolor borderColor = mBorderColors[aSide];
2437 0 : Bezier outerBezier;
2438 0 : Bezier innerBezier;
2439 0 : GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2440 :
2441 : bool ignored;
2442 0 : Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored);
2443 0 : Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored);
2444 : DottedCornerFinder finder(outerBezier, innerBezier, aCorner,
2445 0 : mBorderRadii[aCorner].width,
2446 0 : mBorderRadii[aCorner].height,
2447 0 : C0, R0, Cn, Rn, mBorderCornerDimensions[aCorner]);
2448 :
2449 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2450 0 : size_t segmentCount = 0;
2451 0 : const Float AA_MARGIN = 2.0f;
2452 0 : Rect marginedDirtyRect = mDirtyRect;
2453 0 : marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN);
2454 0 : bool entered = false;
2455 0 : while (finder.HasMore()) {
2456 0 : if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2457 0 : RefPtr<Path> path = builder->Finish();
2458 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2459 0 : builder = mDrawTarget->CreatePathBuilder();
2460 0 : segmentCount = 0;
2461 : }
2462 :
2463 0 : DottedCornerFinder::Result result = finder.Next();
2464 :
2465 0 : if (marginedDirtyRect.Contains(result.C) && result.r > 0) {
2466 0 : entered = true;
2467 0 : builder->MoveTo(Point(result.C.x + result.r, result.C.y));
2468 0 : builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI));
2469 0 : segmentCount++;
2470 0 : } else if (entered) {
2471 0 : break;
2472 : }
2473 : }
2474 0 : RefPtr<Path> path = builder->Finish();
2475 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2476 : }
2477 :
2478 : static inline bool
2479 0 : DashedPathOverlapsRect(Rect& pathRect,
2480 : const Rect& marginedDirtyRect,
2481 : DashedCornerFinder::Result& result)
2482 : {
2483 : // Calculate a rect that contains all control points of the |result| path,
2484 : // and check if it intersects with |marginedDirtyRect|.
2485 0 : pathRect.SetRect(result.outerSectionBezier.mPoints[0].x,
2486 0 : result.outerSectionBezier.mPoints[0].y, 0, 0);
2487 0 : pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]);
2488 0 : pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]);
2489 0 : pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]);
2490 0 : pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]);
2491 0 : pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]);
2492 0 : pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]);
2493 0 : pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]);
2494 :
2495 0 : return pathRect.Intersects(marginedDirtyRect);
2496 : }
2497 :
2498 : void
2499 0 : nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide,
2500 : Corner aCorner)
2501 : {
2502 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED,
2503 : "Style should be dashed.");
2504 :
2505 0 : mozilla::Side sideH(GetHorizontalSide(aCorner));
2506 0 : mozilla::Side sideV(GetVerticalSide(aCorner));
2507 0 : Float borderWidthH = mBorderWidths[sideH];
2508 0 : Float borderWidthV = mBorderWidths[sideV];
2509 0 : if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
2510 0 : return;
2511 : }
2512 :
2513 0 : nscolor borderColor = mBorderColors[aSide];
2514 0 : Bezier outerBezier;
2515 0 : Bezier innerBezier;
2516 0 : GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2517 :
2518 : DashedCornerFinder finder(outerBezier, innerBezier,
2519 : borderWidthH, borderWidthV,
2520 0 : mBorderCornerDimensions[aCorner]);
2521 :
2522 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2523 0 : size_t segmentCount = 0;
2524 0 : const Float AA_MARGIN = 2.0f;
2525 0 : Rect marginedDirtyRect = mDirtyRect;
2526 0 : marginedDirtyRect.Inflate(AA_MARGIN);
2527 0 : Rect pathRect;
2528 0 : bool entered = false;
2529 0 : while (finder.HasMore()) {
2530 0 : if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2531 0 : RefPtr<Path> path = builder->Finish();
2532 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2533 0 : builder = mDrawTarget->CreatePathBuilder();
2534 0 : segmentCount = 0;
2535 : }
2536 :
2537 0 : DashedCornerFinder::Result result = finder.Next();
2538 :
2539 0 : if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) {
2540 0 : entered = true;
2541 0 : builder->MoveTo(result.outerSectionBezier.mPoints[0]);
2542 0 : builder->BezierTo(result.outerSectionBezier.mPoints[1],
2543 : result.outerSectionBezier.mPoints[2],
2544 0 : result.outerSectionBezier.mPoints[3]);
2545 0 : builder->LineTo(result.innerSectionBezier.mPoints[3]);
2546 0 : builder->BezierTo(result.innerSectionBezier.mPoints[2],
2547 : result.innerSectionBezier.mPoints[1],
2548 0 : result.innerSectionBezier.mPoints[0]);
2549 0 : builder->LineTo(result.outerSectionBezier.mPoints[0]);
2550 0 : segmentCount++;
2551 0 : } else if (entered) {
2552 0 : break;
2553 : }
2554 : }
2555 :
2556 0 : if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) {
2557 : // Fill gap before the first section.
2558 : //
2559 : // outnerPoint[0]
2560 : // |
2561 : // v
2562 : // _+-----------+--
2563 : // / \##########|
2564 : // / \#########|
2565 : // + \########|
2566 : // |\ \######|
2567 : // | \ \#####|
2568 : // | \ \####|
2569 : // | \ \##|
2570 : // | \ \#|
2571 : // | \ \|
2572 : // | \ _-+--
2573 : // +--------------+ ^
2574 : // | | |
2575 : // | | innerPoint[0]
2576 : // | |
2577 0 : builder->MoveTo(outerBezier.mPoints[0]);
2578 0 : builder->LineTo(innerBezier.mPoints[0]);
2579 0 : builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y));
2580 0 : builder->LineTo(outerBezier.mPoints[0]);
2581 : }
2582 :
2583 0 : if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) {
2584 : // Fill gap after the last section.
2585 : //
2586 : // outnerPoint[3]
2587 : // |
2588 : // |
2589 : // | _+-----------+--
2590 : // | / \ |
2591 : // v/ \ |
2592 : // + \ |
2593 : // |\ \ |
2594 : // |##\ \ |
2595 : // |####\ \ |
2596 : // |######\ \ |
2597 : // |########\ \ |
2598 : // |##########\ \|
2599 : // |############\ _-+--
2600 : // +--------------+<-- innerPoint[3]
2601 : // | |
2602 : // | |
2603 : // | |
2604 0 : builder->MoveTo(outerBezier.mPoints[3]);
2605 0 : builder->LineTo(innerBezier.mPoints[3]);
2606 0 : builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y));
2607 0 : builder->LineTo(outerBezier.mPoints[3]);
2608 : }
2609 :
2610 0 : RefPtr<Path> path = builder->Finish();
2611 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2612 : }
2613 :
2614 : void
2615 0 : nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide,
2616 : Corner aCorner)
2617 : {
2618 : // Render too large dashed or dotted corner with solid style, to avoid hangup
2619 : // inside DashedCornerFinder and DottedCornerFinder.
2620 :
2621 0 : NS_ASSERTION(mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED ||
2622 : mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED,
2623 : "Style should be dashed or dotted.");
2624 :
2625 0 : nscolor borderColor = mBorderColors[aSide];
2626 0 : Bezier outerBezier;
2627 0 : Bezier innerBezier;
2628 0 : GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2629 :
2630 0 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2631 :
2632 0 : builder->MoveTo(outerBezier.mPoints[0]);
2633 0 : builder->BezierTo(outerBezier.mPoints[1],
2634 : outerBezier.mPoints[2],
2635 0 : outerBezier.mPoints[3]);
2636 0 : builder->LineTo(innerBezier.mPoints[3]);
2637 0 : builder->BezierTo(innerBezier.mPoints[2],
2638 : innerBezier.mPoints[1],
2639 0 : innerBezier.mPoints[0]);
2640 0 : builder->LineTo(outerBezier.mPoints[0]);
2641 :
2642 0 : RefPtr<Path> path = builder->Finish();
2643 0 : mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2644 :
2645 0 : if (mDocument) {
2646 0 : if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
2647 0 : mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
2648 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
2649 0 : NS_LITERAL_CSTRING("CSS"),
2650 : mDocument,
2651 : nsContentUtils::eCSS_PROPERTIES,
2652 0 : mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DASHED
2653 : ? "TooLargeDashedRadius"
2654 0 : : "TooLargeDottedRadius");
2655 : }
2656 : }
2657 0 : }
2658 :
2659 : bool
2660 34 : nsCSSBorderRenderer::AllBordersSameWidth()
2661 : {
2662 56 : if (mBorderWidths[0] == mBorderWidths[1] &&
2663 39 : mBorderWidths[0] == mBorderWidths[2] &&
2664 17 : mBorderWidths[0] == mBorderWidths[3])
2665 : {
2666 17 : return true;
2667 : }
2668 :
2669 17 : return false;
2670 : }
2671 :
2672 : bool
2673 17 : nsCSSBorderRenderer::AllBordersSolid(bool *aHasCompositeColors)
2674 : {
2675 17 : *aHasCompositeColors = false;
2676 85 : NS_FOR_CSS_SIDES(i) {
2677 68 : if (mCompositeColors[i] != nullptr) {
2678 0 : *aHasCompositeColors = true;
2679 : }
2680 109 : if (mBorderStyles[i] == NS_STYLE_BORDER_STYLE_SOLID ||
2681 41 : mBorderStyles[i] == NS_STYLE_BORDER_STYLE_NONE ||
2682 0 : mBorderStyles[i] == NS_STYLE_BORDER_STYLE_HIDDEN)
2683 : {
2684 68 : continue;
2685 : }
2686 0 : return false;
2687 : }
2688 :
2689 17 : return true;
2690 : }
2691 :
2692 136 : bool IsVisible(int aStyle)
2693 : {
2694 136 : if (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
2695 : aStyle != NS_STYLE_BORDER_STYLE_HIDDEN) {
2696 54 : return true;
2697 : }
2698 82 : return false;
2699 : }
2700 :
2701 : struct twoFloats
2702 : {
2703 : Float a, b;
2704 :
2705 108 : twoFloats operator*(const Size& aSize) const {
2706 108 : return { a * aSize.width, b * aSize.height };
2707 : }
2708 :
2709 26 : twoFloats operator*(Float aScale) const {
2710 26 : return { a * aScale, b * aScale };
2711 : }
2712 :
2713 0 : twoFloats operator+(const Point& aPoint) const {
2714 0 : return { a + aPoint.x, b + aPoint.y };
2715 : }
2716 :
2717 147 : operator Point() const {
2718 147 : return Point(a, b);
2719 : }
2720 : };
2721 :
2722 : void
2723 0 : nsCSSBorderRenderer::DrawSingleWidthSolidBorder()
2724 : {
2725 : // Easy enough to deal with.
2726 0 : Rect rect = mOuterRect;
2727 0 : rect.Deflate(0.5);
2728 :
2729 : const twoFloats cornerAdjusts[4] = { { +0.5, 0 },
2730 : { 0, +0.5 },
2731 : { -0.5, 0 },
2732 0 : { 0, -0.5 } };
2733 0 : NS_FOR_CSS_SIDES(side) {
2734 0 : Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
2735 0 : Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
2736 :
2737 0 : ColorPattern color(ToDeviceColor(mBorderColors[side]));
2738 :
2739 0 : mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
2740 : }
2741 0 : }
2742 :
2743 : // Intersect a ray from the inner corner to the outer corner
2744 : // with the border radius, yielding the intersection point.
2745 : static Point
2746 0 : IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
2747 : const Point& aInnerCorner,
2748 : const Point& aCornerDirection)
2749 : {
2750 0 : Point toCorner = aCornerDirection;
2751 : // transform to-corner ray to unit-circle space
2752 0 : toCorner.x /= aRadius.width;
2753 0 : toCorner.y /= aRadius.height;
2754 : // normalize to-corner ray
2755 0 : Float cornerDist = toCorner.Length();
2756 0 : if (cornerDist < 1.0e-6f) {
2757 0 : return aInnerCorner;
2758 : }
2759 0 : toCorner = toCorner / cornerDist;
2760 : // ray from inner corner to border radius center
2761 0 : Point toCenter = aCenter - aInnerCorner;
2762 : // transform to-center ray to unit-circle space
2763 0 : toCenter.x /= aRadius.width;
2764 0 : toCenter.y /= aRadius.height;
2765 : // compute offset of intersection with border radius unit circle
2766 0 : Float offset = toCenter.DotProduct(toCorner);
2767 : // compute discriminant to check for intersections
2768 0 : Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
2769 : // choose farthest intersection
2770 0 : offset += sqrtf(std::max(discrim, 0.0f));
2771 : // transform to-corner ray back out of unit-circle space
2772 0 : toCorner.x *= aRadius.width;
2773 0 : toCorner.y *= aRadius.height;
2774 0 : return aInnerCorner + toCorner * offset;
2775 : }
2776 :
2777 : // Calculate the split point and split angle for a border radius with
2778 : // differing sides.
2779 : static inline void
2780 0 : SplitBorderRadius(const Point& aCenter, const Size& aRadius,
2781 : const Point& aOuterCorner, const Point& aInnerCorner,
2782 : const twoFloats& aCornerMults, Float aStartAngle,
2783 : Point& aSplit, Float& aSplitAngle)
2784 : {
2785 0 : Point cornerDir = aOuterCorner - aInnerCorner;
2786 0 : if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
2787 : // optimize 45-degree intersection with circle since we can assume
2788 : // the circle center lies along the intersection edge
2789 0 : aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
2790 0 : aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
2791 : } else {
2792 0 : aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
2793 0 : aSplitAngle =
2794 0 : atan2f((aSplit.y - aCenter.y) / aRadius.height,
2795 0 : (aSplit.x - aCenter.x) / aRadius.width);
2796 : }
2797 0 : }
2798 :
2799 : // Compute the size of the skirt needed, given the color alphas
2800 : // of each corner side and the slope between them.
2801 : static void
2802 0 : ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2,
2803 : Float aSlopeY, Float aSlopeX,
2804 : Float& aSizeResult, Float& aSlopeResult)
2805 : {
2806 : // If either side is (almost) invisible or there is no diagonal edge,
2807 : // then don't try to render a skirt.
2808 0 : if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
2809 0 : return;
2810 : }
2811 0 : aSlopeX = fabs(aSlopeX);
2812 0 : aSlopeY = fabs(aSlopeY);
2813 0 : if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
2814 0 : return;
2815 : }
2816 :
2817 : // If first and second color don't match, we need to split the corner in
2818 : // half. The diagonal edges created may not have full pixel coverage given
2819 : // anti-aliasing, so we need to compute a small subpixel skirt edge. This
2820 : // assumes each half has half coverage to start with, and that coverage
2821 : // increases as the skirt is pushed over, with the end result that we want
2822 : // to roughly preserve the alpha value along this edge.
2823 : // Given slope m, alphas a and A, use quadratic formula to solve for S in:
2824 : // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
2825 : // yielding:
2826 : // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
2827 : // and substitute k = (1+m)/(2*m):
2828 : // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
2829 0 : Float slope = aSlopeY / aSlopeX;
2830 0 : Float slopeScale = (1.0f + slope) / (2.0f * slope);
2831 : Float discrim =
2832 0 : slopeScale*slopeScale +
2833 0 : (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
2834 0 : if (discrim >= 0) {
2835 0 : aSizeResult = slopeScale - sqrtf(discrim);
2836 0 : aSlopeResult = slope;
2837 : }
2838 : }
2839 :
2840 : // Draws a border radius with possibly different sides.
2841 : // A skirt is drawn underneath the corner intersection to hide possible
2842 : // seams when anti-aliased drawing is used.
2843 : static void
2844 0 : DrawBorderRadius(DrawTarget* aDrawTarget,
2845 : Corner c,
2846 : const Point& aOuterCorner, const Point& aInnerCorner,
2847 : const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
2848 : const Size& aCornerDims,
2849 : const Size& aOuterRadius, const Size& aInnerRadius,
2850 : const Color& aFirstColor, const Color& aSecondColor,
2851 : Float aSkirtSize, Float aSkirtSlope)
2852 : {
2853 : // Connect edge to outer arc start point
2854 0 : Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
2855 : // Connect edge to outer arc end point
2856 0 : Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
2857 : // Connect edge to inner arc start point
2858 : Point innerCornerStart =
2859 : outerCornerStart +
2860 0 : aCornerMultNext * (aCornerDims - aInnerRadius);
2861 : // Connect edge to inner arc end point
2862 : Point innerCornerEnd =
2863 : outerCornerEnd +
2864 0 : aCornerMultPrev * (aCornerDims - aInnerRadius);
2865 :
2866 : // Outer arc start point
2867 0 : Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
2868 : // Outer arc end point
2869 0 : Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
2870 : // Inner arc start point
2871 0 : Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
2872 : // Inner arc end point
2873 0 : Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
2874 :
2875 : // Outer radius center
2876 0 : Point outerCenter = aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
2877 : // Inner radius center
2878 0 : Point innerCenter = aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
2879 :
2880 0 : RefPtr<PathBuilder> builder;
2881 0 : RefPtr<Path> path;
2882 :
2883 0 : if (aFirstColor.a > 0) {
2884 0 : builder = aDrawTarget->CreatePathBuilder();
2885 0 : builder->MoveTo(outerCornerStart);
2886 : }
2887 :
2888 0 : if (aFirstColor != aSecondColor) {
2889 : // Start and end angles of corner quadrant
2890 0 : Float startAngle = (c * M_PI) / 2.0f - M_PI,
2891 0 : endAngle = startAngle + M_PI / 2.0f,
2892 : outerSplitAngle, innerSplitAngle;
2893 0 : Point outerSplit, innerSplit;
2894 :
2895 : // Outer half-way point
2896 : SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
2897 0 : aCornerMultPrev + aCornerMultNext, startAngle,
2898 0 : outerSplit, outerSplitAngle);
2899 : // Inner half-way point
2900 0 : if (aInnerRadius.IsEmpty()) {
2901 0 : innerSplit = aInnerCorner;
2902 0 : innerSplitAngle = endAngle;
2903 : } else {
2904 : SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
2905 0 : aCornerMultPrev + aCornerMultNext, startAngle,
2906 0 : innerSplit, innerSplitAngle);
2907 : }
2908 :
2909 : // Draw first half with first color
2910 0 : if (aFirstColor.a > 0) {
2911 0 : AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
2912 0 : outerArcStart, outerSplit, startAngle, outerSplitAngle);
2913 : // Draw skirt as part of first half
2914 0 : if (aSkirtSize > 0) {
2915 0 : builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
2916 0 : builder->LineTo(innerSplit - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
2917 : }
2918 0 : AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
2919 0 : innerSplit, innerArcStart, innerSplitAngle, startAngle);
2920 0 : if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
2921 0 : builder->LineTo(innerCornerStart);
2922 : }
2923 0 : builder->Close();
2924 0 : path = builder->Finish();
2925 0 : aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2926 : }
2927 :
2928 : // Draw second half with second color
2929 0 : if (aSecondColor.a > 0) {
2930 0 : builder = aDrawTarget->CreatePathBuilder();
2931 0 : builder->MoveTo(outerCornerEnd);
2932 0 : if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
2933 0 : builder->LineTo(innerCornerEnd);
2934 : }
2935 0 : AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
2936 0 : innerArcEnd, innerSplit, endAngle, innerSplitAngle);
2937 0 : AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
2938 0 : outerSplit, outerArcEnd, outerSplitAngle, endAngle);
2939 0 : builder->Close();
2940 0 : path = builder->Finish();
2941 0 : aDrawTarget->Fill(path, ColorPattern(aSecondColor));
2942 : }
2943 0 : } else if (aFirstColor.a > 0) {
2944 : // Draw corner with single color
2945 0 : AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius,
2946 0 : outerArcStart, outerArcEnd);
2947 0 : builder->LineTo(outerCornerEnd);
2948 0 : if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
2949 0 : builder->LineTo(innerCornerEnd);
2950 : }
2951 0 : AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius,
2952 0 : innerArcEnd, innerArcStart, -kKappaFactor);
2953 0 : if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
2954 0 : builder->LineTo(innerCornerStart);
2955 : }
2956 0 : builder->Close();
2957 0 : path = builder->Finish();
2958 0 : aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2959 : }
2960 0 : }
2961 :
2962 : // Draw a corner with possibly different sides.
2963 : // A skirt is drawn underneath the corner intersection to hide possible
2964 : // seams when anti-aliased drawing is used.
2965 : static void
2966 3 : DrawCorner(DrawTarget* aDrawTarget,
2967 : const Point& aOuterCorner, const Point& aInnerCorner,
2968 : const twoFloats& aCornerMultPrev, const twoFloats& aCornerMultNext,
2969 : const Size& aCornerDims,
2970 : const Color& aFirstColor, const Color& aSecondColor,
2971 : Float aSkirtSize, Float aSkirtSlope)
2972 : {
2973 : // Corner box start point
2974 3 : Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
2975 : // Corner box end point
2976 3 : Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
2977 :
2978 6 : RefPtr<PathBuilder> builder;
2979 6 : RefPtr<Path> path;
2980 :
2981 3 : if (aFirstColor.a > 0) {
2982 3 : builder = aDrawTarget->CreatePathBuilder();
2983 3 : builder->MoveTo(cornerStart);
2984 : }
2985 :
2986 3 : if (aFirstColor != aSecondColor) {
2987 : // Draw first half with first color
2988 0 : if (aFirstColor.a > 0) {
2989 0 : builder->LineTo(aOuterCorner);
2990 : // Draw skirt as part of first half
2991 0 : if (aSkirtSize > 0) {
2992 0 : builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
2993 0 : builder->LineTo(aInnerCorner - aCornerMultPrev * (aSkirtSize * aSkirtSlope));
2994 : }
2995 0 : builder->LineTo(aInnerCorner);
2996 0 : builder->Close();
2997 0 : path = builder->Finish();
2998 0 : aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2999 : }
3000 :
3001 : // Draw second half with second color
3002 0 : if (aSecondColor.a > 0) {
3003 0 : builder = aDrawTarget->CreatePathBuilder();
3004 0 : builder->MoveTo(cornerEnd);
3005 0 : builder->LineTo(aInnerCorner);
3006 0 : builder->LineTo(aOuterCorner);
3007 0 : builder->Close();
3008 0 : path = builder->Finish();
3009 0 : aDrawTarget->Fill(path, ColorPattern(aSecondColor));
3010 : }
3011 3 : } else if (aFirstColor.a > 0) {
3012 : // Draw corner with single color
3013 3 : builder->LineTo(aOuterCorner);
3014 3 : builder->LineTo(cornerEnd);
3015 3 : builder->LineTo(aInnerCorner);
3016 3 : builder->Close();
3017 3 : path = builder->Finish();
3018 3 : aDrawTarget->Fill(path, ColorPattern(aFirstColor));
3019 : }
3020 3 : }
3021 :
3022 : void
3023 17 : nsCSSBorderRenderer::DrawNoCompositeColorSolidBorder()
3024 : {
3025 : const twoFloats cornerMults[4] = { { -1, 0 },
3026 : { 0, -1 },
3027 : { +1, 0 },
3028 17 : { 0, +1 } };
3029 :
3030 : const twoFloats centerAdjusts[4] = { { 0, +0.5 },
3031 : { -0.5, 0 },
3032 : { 0, -0.5 },
3033 17 : { +0.5, 0 } };
3034 :
3035 17 : RectCornerRadii innerRadii;
3036 17 : ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
3037 :
3038 17 : Rect strokeRect = mOuterRect;
3039 51 : strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0,
3040 51 : mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0));
3041 :
3042 85 : NS_FOR_CSS_SIDES(i) {
3043 : // We now draw the current side and the CW corner following it.
3044 : // The CCW corner of this side was already drawn in the previous iteration.
3045 : // The side will be drawn as an explicit stroke, and the CW corner will be
3046 : // filled separately.
3047 : // If the next side does not have a matching color, then we split the
3048 : // corner into two halves, one of each side's color and draw both.
3049 : // Thus, the CCW corner of the next side will end up drawn here.
3050 :
3051 : // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
3052 68 : Corner c = Corner((i+1) % 4);
3053 68 : Corner prevCorner = Corner(i);
3054 :
3055 : // i+2 and i+3 respectively. These are used to index into the corner
3056 : // multiplier table, and were deduced by calculating out the long form
3057 : // of each corner and finding a pattern in the signs and values.
3058 68 : int i1 = (i+1) % 4;
3059 68 : int i2 = (i+2) % 4;
3060 68 : int i3 = (i+3) % 4;
3061 :
3062 68 : Float sideWidth = 0.0f;
3063 68 : Color firstColor, secondColor;
3064 68 : if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) {
3065 : // draw the side since it is visible
3066 27 : sideWidth = mBorderWidths[i];
3067 27 : firstColor = ToDeviceColor(mBorderColors[i]);
3068 : // if the next side is visible, use its color for corner
3069 : secondColor =
3070 27 : IsVisible(mBorderStyles[i1]) && mBorderWidths[i1] ?
3071 3 : ToDeviceColor(mBorderColors[i1]) :
3072 3 : firstColor;
3073 41 : } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) {
3074 : // assign next side's color to both corner sides
3075 24 : firstColor = ToDeviceColor(mBorderColors[i1]);
3076 24 : secondColor = firstColor;
3077 : } else {
3078 : // neither side is visible, so nothing to do
3079 17 : continue;
3080 : }
3081 :
3082 51 : Point outerCorner = mOuterRect.AtCorner(c);
3083 51 : Point innerCorner = mInnerRect.AtCorner(c);
3084 :
3085 : // start and end points of border side stroke between corners
3086 : Point sideStart =
3087 102 : mOuterRect.AtCorner(prevCorner) +
3088 153 : cornerMults[i2] * mBorderCornerDimensions[prevCorner];
3089 51 : Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
3090 : // check if the side is visible and not inverted
3091 179 : if (sideWidth > 0 && firstColor.a > 0 &&
3092 103 : -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) {
3093 65 : mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
3094 26 : sideEnd + centerAdjusts[i] * sideWidth,
3095 26 : ColorPattern(firstColor),
3096 52 : StrokeOptions(sideWidth));
3097 : }
3098 :
3099 51 : Float skirtSize = 0.0f, skirtSlope = 0.0f;
3100 : // the sides don't match, so compute a skirt
3101 51 : if (firstColor != secondColor &&
3102 0 : mPresContext->Type() != nsPresContext::eContext_Print) {
3103 0 : Point cornerDir = outerCorner - innerCorner;
3104 0 : ComputeCornerSkirtSize(firstColor.a, secondColor.a,
3105 0 : cornerDir.DotProduct(cornerMults[i]),
3106 0 : cornerDir.DotProduct(cornerMults[i3]),
3107 0 : skirtSize, skirtSlope);
3108 : }
3109 :
3110 51 : if (!mBorderRadii[c].IsEmpty()) {
3111 : // the corner has a border radius
3112 0 : DrawBorderRadius(mDrawTarget,
3113 : c, outerCorner, innerCorner,
3114 0 : cornerMults[i], cornerMults[i3],
3115 0 : mBorderCornerDimensions[c],
3116 0 : mBorderRadii[c], innerRadii[c],
3117 0 : firstColor, secondColor, skirtSize, skirtSlope);
3118 51 : } else if (!mBorderCornerDimensions[c].IsEmpty()) {
3119 : // a corner with no border radius
3120 6 : DrawCorner(mDrawTarget,
3121 : outerCorner, innerCorner,
3122 3 : cornerMults[i], cornerMults[i3],
3123 3 : mBorderCornerDimensions[c],
3124 6 : firstColor, secondColor, skirtSize, skirtSlope);
3125 : }
3126 : }
3127 17 : }
3128 :
3129 : void
3130 0 : nsCSSBorderRenderer::DrawRectangularCompositeColors()
3131 : {
3132 : nsBorderColors *currentColors[4];
3133 0 : memcpy(currentColors, mCompositeColors, sizeof(nsBorderColors*) * 4);
3134 0 : Rect rect = mOuterRect;
3135 0 : rect.Deflate(0.5);
3136 :
3137 : const twoFloats cornerAdjusts[4] = { { +0.5, 0 },
3138 : { 0, +0.5 },
3139 : { -0.5, 0 },
3140 0 : { 0, -0.5 } };
3141 :
3142 0 : for (int i = 0; i < mBorderWidths[0]; i++) {
3143 0 : NS_FOR_CSS_SIDES(side) {
3144 0 : int sideNext = (side + 1) % 4;
3145 :
3146 0 : Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
3147 0 : Point secondCorner = rect.CWCorner(side) - cornerAdjusts[side];
3148 :
3149 : Color currentColor = Color::FromABGR(
3150 0 : currentColors[side] ? currentColors[side]->mColor
3151 0 : : mBorderColors[side]);
3152 :
3153 0 : mDrawTarget->StrokeLine(firstCorner, secondCorner,
3154 0 : ColorPattern(ToDeviceColor(currentColor)));
3155 :
3156 0 : Point cornerTopLeft = rect.CWCorner(side) - Point(0.5, 0.5);
3157 : Color nextColor = Color::FromABGR(
3158 0 : currentColors[sideNext] ? currentColors[sideNext]->mColor
3159 0 : : mBorderColors[sideNext]);
3160 :
3161 0 : Color cornerColor((currentColor.r + nextColor.r) / 2.f,
3162 0 : (currentColor.g + nextColor.g) / 2.f,
3163 0 : (currentColor.b + nextColor.b) / 2.f,
3164 0 : (currentColor.a + nextColor.a) / 2.f);
3165 0 : mDrawTarget->FillRect(Rect(cornerTopLeft, Size(1, 1)),
3166 0 : ColorPattern(ToDeviceColor(cornerColor)));
3167 :
3168 0 : if (side != 0) {
3169 : // We'll have to keep side 0 for the color averaging on side 3.
3170 0 : if (currentColors[side] && currentColors[side]->mNext) {
3171 0 : currentColors[side] = currentColors[side]->mNext;
3172 : }
3173 : }
3174 : }
3175 : // Now advance the color for side 0.
3176 0 : if (currentColors[0] && currentColors[0]->mNext) {
3177 0 : currentColors[0] = currentColors[0]->mNext;
3178 : }
3179 0 : rect.Deflate(1);
3180 : }
3181 0 : }
3182 :
3183 : void
3184 34 : nsCSSBorderRenderer::DrawBorders()
3185 : {
3186 34 : bool forceSeparateCorners = false;
3187 :
3188 : // Examine the border style to figure out if we can draw it in one
3189 : // go or not.
3190 34 : bool tlBordersSame = AreBorderSideFinalStylesSame(eSideBitsTop | eSideBitsLeft);
3191 34 : bool brBordersSame = AreBorderSideFinalStylesSame(eSideBitsBottom | eSideBitsRight);
3192 34 : bool allBordersSame = AreBorderSideFinalStylesSame(eSideBitsAll);
3193 51 : if (allBordersSame &&
3194 34 : ((mCompositeColors[0] == nullptr &&
3195 34 : (mBorderStyles[0] == NS_STYLE_BORDER_STYLE_NONE ||
3196 34 : mBorderStyles[0] == NS_STYLE_BORDER_STYLE_HIDDEN ||
3197 34 : mBorderColors[0] == NS_RGBA(0,0,0,0))) ||
3198 17 : (mCompositeColors[0] &&
3199 0 : (mCompositeColors[0]->mColor == NS_RGBA(0,0,0,0) &&
3200 0 : !mCompositeColors[0]->mNext))))
3201 : {
3202 : // All borders are the same style, and the style is either none or hidden, or the color
3203 : // is transparent.
3204 : // This also checks if the first composite color is transparent, and there are
3205 : // no others. It doesn't check if there are subsequent transparent ones, because
3206 : // that would be very silly.
3207 34 : return;
3208 : }
3209 :
3210 34 : AutoRestoreTransform autoRestoreTransform;
3211 34 : Matrix mat = mDrawTarget->GetTransform();
3212 :
3213 : // Clamp the CTM to be pixel-aligned; we do this only
3214 : // for translation-only matrices now, but we could do it
3215 : // if the matrix has just a scale as well. We should not
3216 : // do it if there's a rotation.
3217 34 : if (mat.HasNonTranslation()) {
3218 0 : if (!mat.HasNonAxisAlignedTransform()) {
3219 : // Scale + transform. Avoid stroke fast-paths so that we have a chance
3220 : // of snapping to pixel boundaries.
3221 0 : mAvoidStroke = true;
3222 : }
3223 : } else {
3224 34 : mat._31 = floor(mat._31 + 0.5);
3225 34 : mat._32 = floor(mat._32 + 0.5);
3226 34 : autoRestoreTransform.Init(mDrawTarget);
3227 34 : mDrawTarget->SetTransform(mat);
3228 :
3229 : // round mOuterRect and mInnerRect; they're already an integer
3230 : // number of pixels apart and should stay that way after
3231 : // rounding. We don't do this if there's a scale in the current transform
3232 : // since this loses information that might be relevant when we're scaling.
3233 34 : mOuterRect.Round();
3234 34 : mInnerRect.Round();
3235 : }
3236 :
3237 34 : bool allBordersSameWidth = AllBordersSameWidth();
3238 :
3239 34 : if (allBordersSameWidth && mBorderWidths[0] == 0.0) {
3240 : // Some of the allBordersSameWidth codepaths depend on the border
3241 : // width being greater than zero.
3242 0 : return;
3243 : }
3244 :
3245 : // Initial values only used when the border colors/widths are all the same:
3246 34 : ColorPattern color(ToDeviceColor(mBorderColors[eSideTop]));
3247 34 : StrokeOptions strokeOptions(mBorderWidths[eSideTop]); // stroke width
3248 :
3249 : bool allBordersSolid;
3250 :
3251 : // First there's a couple of 'special cases' that have specifically optimized
3252 : // drawing paths, when none of these can be used we move on to the generalized
3253 : // border drawing code.
3254 51 : if (allBordersSame &&
3255 34 : mCompositeColors[0] == nullptr &&
3256 17 : allBordersSameWidth &&
3257 34 : mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
3258 17 : mNoBorderRadius &&
3259 0 : !mAvoidStroke)
3260 : {
3261 : // Very simple case.
3262 0 : Rect rect = mOuterRect;
3263 0 : rect.Deflate(mBorderWidths[0] / 2.0);
3264 0 : mDrawTarget->StrokeRect(rect, color, strokeOptions);
3265 0 : return;
3266 : }
3267 :
3268 51 : if (allBordersSame &&
3269 34 : mCompositeColors[0] == nullptr &&
3270 34 : mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
3271 34 : !mAvoidStroke &&
3272 17 : !mNoBorderRadius)
3273 : {
3274 : // Relatively simple case.
3275 17 : gfxRect outerRect = ThebesRect(mOuterRect);
3276 17 : RoundedRect borderInnerRect(outerRect, mBorderRadii);
3277 51 : borderInnerRect.Deflate(mBorderWidths[eSideTop],
3278 17 : mBorderWidths[eSideBottom],
3279 17 : mBorderWidths[eSideLeft],
3280 34 : mBorderWidths[eSideRight]);
3281 :
3282 : // Instead of stroking we just use two paths: an inner and an outer.
3283 : // This allows us to draw borders that we couldn't when stroking. For example,
3284 : // borders with a border width >= the border radius. (i.e. when there are
3285 : // square corners on the inside)
3286 : //
3287 : // Further, this approach can be more efficient because the backend
3288 : // doesn't need to compute an offset curve to stroke the path. We know that
3289 : // the rounded parts are elipses we can offset exactly and can just compute
3290 : // a new cubic approximation.
3291 34 : RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
3292 17 : AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
3293 17 : AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
3294 34 : RefPtr<Path> path = builder->Finish();
3295 17 : mDrawTarget->Fill(path, color);
3296 17 : return;
3297 : }
3298 :
3299 : bool hasCompositeColors;
3300 :
3301 17 : allBordersSolid = AllBordersSolid(&hasCompositeColors);
3302 : // This leaves the border corners non-interpolated for single width borders.
3303 : // Doing this is slightly faster and shouldn't be a problem visually.
3304 17 : if (allBordersSolid &&
3305 0 : allBordersSameWidth &&
3306 0 : mCompositeColors[0] == nullptr &&
3307 0 : mBorderWidths[0] == 1 &&
3308 0 : mNoBorderRadius &&
3309 0 : !mAvoidStroke)
3310 : {
3311 0 : DrawSingleWidthSolidBorder();
3312 0 : return;
3313 : }
3314 :
3315 34 : if (allBordersSolid && !hasCompositeColors &&
3316 17 : !mAvoidStroke)
3317 : {
3318 17 : DrawNoCompositeColorSolidBorder();
3319 17 : return;
3320 : }
3321 :
3322 0 : if (allBordersSolid &&
3323 0 : allBordersSameWidth &&
3324 0 : mNoBorderRadius &&
3325 0 : !mAvoidStroke)
3326 : {
3327 : // Easy enough to deal with.
3328 0 : DrawRectangularCompositeColors();
3329 0 : return;
3330 : }
3331 :
3332 : // If we have composite colors -and- border radius,
3333 : // then use separate corners so we get OP_ADD for the corners.
3334 : // Otherwise, we'll get artifacts as we draw stacked 1px-wide curves.
3335 0 : if (allBordersSame && mCompositeColors[0] != nullptr && !mNoBorderRadius)
3336 0 : forceSeparateCorners = true;
3337 :
3338 0 : PrintAsString(" mOuterRect: "); PrintAsString(mOuterRect); PrintAsStringNewline();
3339 0 : PrintAsString(" mInnerRect: "); PrintAsString(mInnerRect); PrintAsStringNewline();
3340 0 : PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n", mBorderColors[0], mBorderColors[1], mBorderColors[2], mBorderColors[3]);
3341 :
3342 : // if conditioning the outside rect failed, then bail -- the outside
3343 : // rect is supposed to enclose the entire border
3344 : {
3345 0 : gfxRect outerRect = ThebesRect(mOuterRect);
3346 0 : gfxUtils::ConditionRect(outerRect);
3347 0 : if (outerRect.IsEmpty())
3348 0 : return;
3349 0 : mOuterRect = ToRect(outerRect);
3350 :
3351 0 : gfxRect innerRect = ThebesRect(mInnerRect);
3352 0 : gfxUtils::ConditionRect(innerRect);
3353 0 : mInnerRect = ToRect(innerRect);
3354 : }
3355 :
3356 0 : int dashedSides = 0;
3357 :
3358 0 : NS_FOR_CSS_SIDES(i) {
3359 0 : uint8_t style = mBorderStyles[i];
3360 0 : if (style == NS_STYLE_BORDER_STYLE_DASHED ||
3361 : style == NS_STYLE_BORDER_STYLE_DOTTED)
3362 : {
3363 : // pretend that all borders aren't the same; we need to draw
3364 : // things separately for dashed/dotting
3365 0 : allBordersSame = false;
3366 0 : dashedSides |= (1 << i);
3367 : }
3368 : }
3369 :
3370 0 : PrintAsFormatString(" allBordersSame: %d dashedSides: 0x%02x\n", allBordersSame, dashedSides);
3371 :
3372 0 : if (allBordersSame && !forceSeparateCorners) {
3373 : /* Draw everything in one go */
3374 0 : DrawBorderSides(eSideBitsAll);
3375 0 : PrintAsStringNewline("---------------- (1)");
3376 : } else {
3377 0 : AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS);
3378 :
3379 : /* We have more than one pass to go. Draw the corners separately from the sides. */
3380 :
3381 : /*
3382 : * If we have a 1px-wide border, the corners are going to be
3383 : * negligible, so don't bother doing anything fancy. Just extend
3384 : * the top and bottom borders to the right 1px and the left border
3385 : * to the bottom 1px. We do this by twiddling the corner dimensions,
3386 : * which causes the right to happen later on. Only do this if we have
3387 : * a 1.0 unit border all around and no border radius.
3388 : */
3389 :
3390 0 : NS_FOR_CSS_FULL_CORNERS(corner) {
3391 0 : const mozilla::Side sides[2] = { mozilla::Side(corner), PREV_SIDE(corner) };
3392 :
3393 0 : if (!IsZeroSize(mBorderRadii[corner]))
3394 0 : continue;
3395 :
3396 0 : if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) {
3397 0 : if (corner == eCornerTopLeft || corner == eCornerTopRight)
3398 0 : mBorderCornerDimensions[corner].width = 0.0;
3399 : else
3400 0 : mBorderCornerDimensions[corner].height = 0.0;
3401 : }
3402 : }
3403 :
3404 : // First, the corners
3405 0 : NS_FOR_CSS_FULL_CORNERS(corner) {
3406 : // if there's no corner, don't do all this work for it
3407 0 : if (IsZeroSize(mBorderCornerDimensions[corner]))
3408 0 : continue;
3409 :
3410 0 : const int sides[2] = { corner, PREV_SIDE(corner) };
3411 0 : int sideBits = (1 << sides[0]) | (1 << sides[1]);
3412 :
3413 0 : bool simpleCornerStyle = mCompositeColors[sides[0]] == nullptr &&
3414 0 : mCompositeColors[sides[1]] == nullptr &&
3415 0 : AreBorderSideFinalStylesSame(sideBits);
3416 :
3417 : // If we don't have anything complex going on in this corner,
3418 : // then we can just fill the corner with a solid color, and avoid
3419 : // the potentially expensive clip.
3420 0 : if (simpleCornerStyle &&
3421 0 : IsZeroSize(mBorderRadii[corner]) &&
3422 0 : IsSolidCornerStyle(mBorderStyles[sides[0]], corner))
3423 : {
3424 0 : Color color = MakeBorderColor(mBorderColors[sides[0]],
3425 : mBackgroundColor,
3426 0 : BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner));
3427 0 : mDrawTarget->FillRect(GetCornerRect(corner),
3428 0 : ColorPattern(ToDeviceColor(color)));
3429 0 : continue;
3430 : }
3431 :
3432 : // clip to the corner
3433 0 : mDrawTarget->PushClipRect(GetCornerRect(corner));
3434 :
3435 0 : if (simpleCornerStyle) {
3436 : // we don't need a group for this corner, the sides are the same,
3437 : // but we weren't able to render just a solid block for the corner.
3438 0 : DrawBorderSides(sideBits);
3439 : } else {
3440 : // Sides are different. We could draw using OP_ADD to
3441 : // get correct color blending behaviour at the seam. We'd need
3442 : // to do it in an offscreen surface to ensure that we're
3443 : // always compositing on transparent black. If the colors
3444 : // don't have transparency and the current destination surface
3445 : // has an alpha channel, we could just clear the region and
3446 : // avoid the temporary, but that situation doesn't happen all
3447 : // that often in practice (we double buffer to no-alpha
3448 : // surfaces). We choose just to seam though, as the performance
3449 : // advantages outway the modest easthetic improvement.
3450 :
3451 0 : for (int cornerSide = 0; cornerSide < 2; cornerSide++) {
3452 0 : mozilla::Side side = mozilla::Side(sides[cornerSide]);
3453 0 : uint8_t style = mBorderStyles[side];
3454 :
3455 0 : PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n", corner, cornerSide, side, style);
3456 :
3457 0 : RefPtr<Path> path = GetSideClipSubPath(side);
3458 0 : mDrawTarget->PushClip(path);
3459 :
3460 0 : DrawBorderSides(1 << side);
3461 :
3462 0 : mDrawTarget->PopClip();
3463 : }
3464 : }
3465 :
3466 0 : mDrawTarget->PopClip();
3467 :
3468 0 : PrintAsStringNewline();
3469 : }
3470 :
3471 : // in the case of a single-unit border, we already munged the
3472 : // corners up above; so we can just draw the top left and bottom
3473 : // right sides separately, if they're the same.
3474 : //
3475 : // We need to check for mNoBorderRadius, because when there is
3476 : // one, FillSolidBorder always draws the full rounded rectangle
3477 : // and expects there to be a clip in place.
3478 0 : int alreadyDrawnSides = 0;
3479 0 : if (mOneUnitBorder &&
3480 0 : mNoBorderRadius &&
3481 0 : (dashedSides & (eSideBitsTop | eSideBitsLeft)) == 0)
3482 : {
3483 0 : if (tlBordersSame) {
3484 0 : DrawBorderSides(eSideBitsTop | eSideBitsLeft);
3485 0 : alreadyDrawnSides |= (eSideBitsTop | eSideBitsLeft);
3486 : }
3487 :
3488 0 : if (brBordersSame && (dashedSides & (eSideBitsBottom | eSideBitsRight)) == 0) {
3489 0 : DrawBorderSides(eSideBitsBottom | eSideBitsRight);
3490 0 : alreadyDrawnSides |= (eSideBitsBottom | eSideBitsRight);
3491 : }
3492 : }
3493 :
3494 : // We're done with the corners, now draw the sides.
3495 0 : NS_FOR_CSS_SIDES (side) {
3496 : // if we drew it above, skip it
3497 0 : if (alreadyDrawnSides & (1 << side))
3498 0 : continue;
3499 :
3500 : // If there's no border on this side, skip it
3501 0 : if (mBorderWidths[side] == 0.0 ||
3502 0 : mBorderStyles[side] == NS_STYLE_BORDER_STYLE_HIDDEN ||
3503 0 : mBorderStyles[side] == NS_STYLE_BORDER_STYLE_NONE)
3504 0 : continue;
3505 :
3506 :
3507 0 : if (dashedSides & (1 << side)) {
3508 : // Dashed sides will always draw just the part ignoring the
3509 : // corners for the side, so no need to clip.
3510 0 : DrawDashedOrDottedSide(side);
3511 :
3512 0 : PrintAsStringNewline("---------------- (d)");
3513 0 : continue;
3514 : }
3515 :
3516 : // Undashed sides will currently draw the entire side,
3517 : // including parts that would normally be covered by a corner,
3518 : // so we need to clip.
3519 : //
3520 : // XXX Optimization -- it would be good to make this work like
3521 : // DrawDashedOrDottedSide, and have a DrawOneSide function that just
3522 : // draws one side and not the corners, because then we can
3523 : // avoid the potentially expensive clip.
3524 0 : mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side));
3525 :
3526 0 : DrawBorderSides(1 << side);
3527 :
3528 0 : mDrawTarget->PopClip();
3529 :
3530 0 : PrintAsStringNewline("---------------- (*)");
3531 : }
3532 : }
3533 : }
3534 :
3535 : bool
3536 0 : nsCSSBorderRenderer::CanCreateWebRenderCommands()
3537 : {
3538 0 : NS_FOR_CSS_SIDES(i) {
3539 0 : if (mCompositeColors[i] != nullptr) {
3540 0 : return false;
3541 : }
3542 :
3543 0 : if (mBorderStyles[i] == NS_STYLE_BORDER_STYLE_DOUBLE ||
3544 0 : mBorderStyles[i] == NS_STYLE_BORDER_STYLE_DOTTED ||
3545 0 : mBorderStyles[i] == NS_STYLE_BORDER_STYLE_DASHED) {
3546 0 : return false;
3547 : }
3548 : }
3549 :
3550 0 : return true;
3551 : }
3552 :
3553 : void
3554 0 : nsCSSBorderRenderer::CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder,
3555 : const layers::StackingContextHelper& aSc)
3556 : {
3557 0 : LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect);
3558 0 : WrRect transformedRect = aSc.ToRelativeWrRect(outerRect);
3559 : WrBorderSide side[4];
3560 0 : NS_FOR_CSS_SIDES(i) {
3561 0 : side[i] = wr::ToWrBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]);
3562 : }
3563 :
3564 0 : WrBorderRadius borderRadius = wr::ToWrBorderRadius(LayerSize(mBorderRadii[0].width, mBorderRadii[0].height),
3565 0 : LayerSize(mBorderRadii[1].width, mBorderRadii[1].height),
3566 0 : LayerSize(mBorderRadii[3].width, mBorderRadii[3].height),
3567 0 : LayerSize(mBorderRadii[2].width, mBorderRadii[2].height));
3568 0 : Range<const WrBorderSide> wrsides(side, 4);
3569 : aBuilder.PushBorder(transformedRect,
3570 : transformedRect,
3571 0 : wr::ToWrBorderWidths(mBorderWidths[0], mBorderWidths[1], mBorderWidths[2], mBorderWidths[3]),
3572 : wrsides,
3573 0 : borderRadius);
3574 0 : }
3575 :
3576 : /* static */Maybe<nsCSSBorderImageRenderer>
3577 3 : nsCSSBorderImageRenderer::CreateBorderImageRenderer(nsPresContext* aPresContext,
3578 : nsIFrame* aForFrame,
3579 : const nsRect& aBorderArea,
3580 : const nsStyleBorder& aStyleBorder,
3581 : const nsRect& aDirtyRect,
3582 : Sides aSkipSides,
3583 : uint32_t aFlags,
3584 : DrawResult* aDrawResult)
3585 : {
3586 3 : MOZ_ASSERT(aDrawResult);
3587 :
3588 3 : if (aDirtyRect.IsEmpty()) {
3589 0 : *aDrawResult = DrawResult::SUCCESS;
3590 0 : return Nothing();
3591 : }
3592 :
3593 6 : nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource, aFlags);
3594 3 : if (!imgRenderer.PrepareImage()) {
3595 0 : *aDrawResult = imgRenderer.PrepareResult();
3596 0 : return Nothing();
3597 : }
3598 :
3599 : // Ensure we get invalidated for loads and animations of the image.
3600 : // We need to do this here because this might be the only code that
3601 : // knows about the association of the style data with the frame.
3602 : // XXX We shouldn't really... since if anybody is passing in a
3603 : // different style, they'll potentially have the wrong size for the
3604 : // border too.
3605 3 : aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
3606 :
3607 : nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea,
3608 6 : aStyleBorder, aSkipSides, imgRenderer);
3609 3 : *aDrawResult = DrawResult::SUCCESS;
3610 3 : return Some(renderer);
3611 : }
3612 :
3613 : DrawResult
3614 3 : nsCSSBorderImageRenderer::DrawBorderImage(nsPresContext* aPresContext,
3615 : gfxContext& aRenderingContext,
3616 : nsIFrame* aForFrame,
3617 : const nsRect& aDirtyRect)
3618 : {
3619 : // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3620 : // in case we need it.
3621 6 : gfxContextAutoSaveRestore autoSR;
3622 :
3623 3 : if (!mClip.IsEmpty()) {
3624 0 : autoSR.EnsureSaved(&aRenderingContext);
3625 0 : aRenderingContext.Clip(NSRectToSnappedRect(mClip,
3626 0 : aForFrame->PresContext()->AppUnitsPerDevPixel(),
3627 0 : *aRenderingContext.GetDrawTarget()));
3628 : }
3629 :
3630 : // intrinsicSize.CanComputeConcreteSize() return false means we can not
3631 : // read intrinsic size from aStyleBorder.mBorderImageSource.
3632 : // In this condition, we pass imageSize(a resolved size comes from
3633 : // default sizing algorithm) to renderer as the viewport size.
3634 3 : CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
3635 3 : Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
3636 6 : Nothing() : Some(mImageSize);
3637 3 : bool hasIntrinsicRatio = intrinsicSize.HasRatio();
3638 3 : mImageRenderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
3639 :
3640 : // These helper tables recharacterize the 'slice' and 'width' margins
3641 : // in a more convenient form: they are the x/y/width/height coords
3642 : // required for various bands of the border, and they have been transformed
3643 : // to be relative to the innerRect (for 'slice') or the page (for 'border').
3644 : enum {
3645 : LEFT, MIDDLE, RIGHT,
3646 : TOP = LEFT, BOTTOM = RIGHT
3647 : };
3648 : const nscoord borderX[3] = {
3649 3 : mArea.x + 0,
3650 3 : mArea.x + mWidths.left,
3651 3 : mArea.x + mArea.width - mWidths.right,
3652 9 : };
3653 : const nscoord borderY[3] = {
3654 3 : mArea.y + 0,
3655 3 : mArea.y + mWidths.top,
3656 3 : mArea.y + mArea.height - mWidths.bottom,
3657 9 : };
3658 : const nscoord borderWidth[3] = {
3659 3 : mWidths.left,
3660 3 : mArea.width - mWidths.left - mWidths.right,
3661 3 : mWidths.right,
3662 9 : };
3663 : const nscoord borderHeight[3] = {
3664 3 : mWidths.top,
3665 3 : mArea.height - mWidths.top - mWidths.bottom,
3666 3 : mWidths.bottom,
3667 9 : };
3668 : const int32_t sliceX[3] = {
3669 : 0,
3670 3 : mSlice.left,
3671 3 : mImageSize.width - mSlice.right,
3672 9 : };
3673 : const int32_t sliceY[3] = {
3674 : 0,
3675 3 : mSlice.top,
3676 3 : mImageSize.height - mSlice.bottom,
3677 9 : };
3678 : const int32_t sliceWidth[3] = {
3679 3 : mSlice.left,
3680 6 : std::max(mImageSize.width - mSlice.left - mSlice.right, 0),
3681 3 : mSlice.right,
3682 9 : };
3683 : const int32_t sliceHeight[3] = {
3684 3 : mSlice.top,
3685 6 : std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0),
3686 3 : mSlice.bottom,
3687 9 : };
3688 :
3689 3 : DrawResult result = DrawResult::SUCCESS;
3690 :
3691 12 : for (int i = LEFT; i <= RIGHT; i++) {
3692 36 : for (int j = TOP; j <= BOTTOM; j++) {
3693 : uint8_t fillStyleH, fillStyleV;
3694 27 : nsSize unitSize;
3695 :
3696 27 : if (i == MIDDLE && j == MIDDLE) {
3697 : // Discard the middle portion unless set to fill.
3698 3 : if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL == mFill) {
3699 6 : continue;
3700 : }
3701 :
3702 0 : NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL == mFill,
3703 : "Unexpected border image fill");
3704 :
3705 : // css-background:
3706 : // The middle image's width is scaled by the same factor as the
3707 : // top image unless that factor is zero or infinity, in which
3708 : // case the scaling factor of the bottom is substituted, and
3709 : // failing that, the width is not scaled. The height of the
3710 : // middle image is scaled by the same factor as the left image
3711 : // unless that factor is zero or infinity, in which case the
3712 : // scaling factor of the right image is substituted, and failing
3713 : // that, the height is not scaled.
3714 : gfxFloat hFactor, vFactor;
3715 :
3716 0 : if (0 < mWidths.left && 0 < mSlice.left)
3717 0 : vFactor = gfxFloat(mWidths.left) / mSlice.left;
3718 0 : else if (0 < mWidths.right && 0 < mSlice.right)
3719 0 : vFactor = gfxFloat(mWidths.right) / mSlice.right;
3720 : else
3721 0 : vFactor = 1;
3722 :
3723 0 : if (0 < mWidths.top && 0 < mSlice.top)
3724 0 : hFactor = gfxFloat(mWidths.top) / mSlice.top;
3725 0 : else if (0 < mWidths.bottom && 0 < mSlice.bottom)
3726 0 : hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom;
3727 : else
3728 0 : hFactor = 1;
3729 :
3730 0 : unitSize.width = sliceWidth[i] * hFactor;
3731 0 : unitSize.height = sliceHeight[j] * vFactor;
3732 0 : fillStyleH = mRepeatModeHorizontal;
3733 0 : fillStyleV = mRepeatModeVertical;
3734 :
3735 24 : } else if (i == MIDDLE) { // top, bottom
3736 : // Sides are always stretched to the thickness of their border,
3737 : // and stretched proportionately on the other axis.
3738 : gfxFloat factor;
3739 6 : if (0 < borderHeight[j] && 0 < sliceHeight[j])
3740 0 : factor = gfxFloat(borderHeight[j]) / sliceHeight[j];
3741 : else
3742 6 : factor = 1;
3743 :
3744 6 : unitSize.width = sliceWidth[i] * factor;
3745 6 : unitSize.height = borderHeight[j];
3746 6 : fillStyleH = mRepeatModeHorizontal;
3747 6 : fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3748 :
3749 18 : } else if (j == MIDDLE) { // left, right
3750 : gfxFloat factor;
3751 6 : if (0 < borderWidth[i] && 0 < sliceWidth[i])
3752 3 : factor = gfxFloat(borderWidth[i]) / sliceWidth[i];
3753 : else
3754 3 : factor = 1;
3755 :
3756 6 : unitSize.width = borderWidth[i];
3757 6 : unitSize.height = sliceHeight[j] * factor;
3758 6 : fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3759 6 : fillStyleV = mRepeatModeVertical;
3760 :
3761 : } else {
3762 : // Corners are always stretched to fit the corner.
3763 12 : unitSize.width = borderWidth[i];
3764 12 : unitSize.height = borderHeight[j];
3765 12 : fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3766 12 : fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
3767 : }
3768 :
3769 48 : nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
3770 48 : nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
3771 24 : if (subArea.IsEmpty())
3772 0 : continue;
3773 :
3774 24 : nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
3775 : result &=
3776 : mImageRenderer.DrawBorderImageComponent(aPresContext,
3777 : aRenderingContext, aDirtyRect,
3778 48 : destArea, CSSIntRect(intSubArea.x,
3779 : intSubArea.y,
3780 : intSubArea.width,
3781 : intSubArea.height),
3782 : fillStyleH, fillStyleV,
3783 24 : unitSize, j * (RIGHT + 1) + i,
3784 72 : svgViewportSize, hasIntrinsicRatio);
3785 : }
3786 : }
3787 :
3788 6 : return result;
3789 : }
3790 :
3791 3 : nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(const nsCSSBorderImageRenderer& aRhs)
3792 : : mImageRenderer(aRhs.mImageRenderer)
3793 : , mImageSize(aRhs.mImageSize)
3794 : , mSlice(aRhs.mSlice)
3795 : , mWidths(aRhs.mWidths)
3796 : , mImageOutset(aRhs.mImageOutset)
3797 : , mArea(aRhs.mArea)
3798 : , mClip(aRhs.mClip)
3799 3 : , mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal)
3800 3 : , mRepeatModeVertical(aRhs.mRepeatModeVertical)
3801 9 : , mFill(aRhs.mFill)
3802 : {
3803 3 : Unused << mImageRenderer.PrepareResult();
3804 3 : }
3805 :
3806 : nsCSSBorderImageRenderer&
3807 0 : nsCSSBorderImageRenderer::operator=(const nsCSSBorderImageRenderer& aRhs)
3808 : {
3809 0 : mImageRenderer = aRhs.mImageRenderer;
3810 0 : mImageSize = aRhs.mImageSize;
3811 0 : mSlice = aRhs.mSlice;
3812 0 : mWidths = aRhs.mWidths;
3813 0 : mImageOutset = aRhs.mImageOutset;
3814 0 : mArea = aRhs.mArea;
3815 0 : mClip = aRhs.mClip;
3816 0 : mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal;
3817 0 : mRepeatModeVertical = aRhs.mRepeatModeVertical;
3818 0 : mFill = aRhs.mFill;
3819 0 : Unused << mImageRenderer.PrepareResult();
3820 :
3821 0 : return *this;
3822 : }
3823 :
3824 3 : nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(nsIFrame* aForFrame,
3825 : const nsRect& aBorderArea,
3826 : const nsStyleBorder& aStyleBorder,
3827 : Sides aSkipSides,
3828 3 : const nsImageRenderer& aImageRenderer)
3829 3 : : mImageRenderer(aImageRenderer)
3830 : {
3831 : // Determine the border image area, which by default corresponds to the
3832 : // border box but can be modified by 'border-image-outset'.
3833 : // Note that 'border-radius' do not apply to 'border-image' borders per
3834 : // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3835 3 : nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3836 3 : mImageOutset = aStyleBorder.GetImageOutset();
3837 3 : if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
3838 0 : mArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
3839 : aSkipSides, &aStyleBorder);
3840 0 : if (mArea.IsEqualEdges(aBorderArea)) {
3841 : // No need for a clip, just skip the sides we don't want.
3842 0 : borderWidths.ApplySkipSides(aSkipSides);
3843 0 : mImageOutset.ApplySkipSides(aSkipSides);
3844 0 : mArea.Inflate(mImageOutset);
3845 : } else {
3846 : // We're drawing borders around the joined continuation boxes so we need
3847 : // to clip that to the slice that we want for this frame.
3848 0 : mArea.Inflate(mImageOutset);
3849 0 : mImageOutset.ApplySkipSides(aSkipSides);
3850 0 : mClip = aBorderArea;
3851 0 : mClip.Inflate(mImageOutset);
3852 : }
3853 : } else {
3854 3 : mArea = aBorderArea;
3855 3 : mArea.Inflate(mImageOutset);
3856 : }
3857 :
3858 : // Calculate the image size used to compute slice points.
3859 3 : CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
3860 6 : mImageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
3861 : intrinsicSize,
3862 9 : mArea.Size());
3863 3 : mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize);
3864 :
3865 : // Compute the used values of 'border-image-slice' and 'border-image-width';
3866 : // we do them together because the latter can depend on the former.
3867 3 : nsMargin slice;
3868 3 : nsMargin border;
3869 15 : NS_FOR_CSS_SIDES(s) {
3870 24 : nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
3871 12 : int32_t imgDimension = SideIsVertical(s)
3872 12 : ? mImageSize.width : mImageSize.height;
3873 12 : nscoord borderDimension = SideIsVertical(s)
3874 12 : ? mArea.width : mArea.height;
3875 : double value;
3876 12 : switch (coord.GetUnit()) {
3877 : case eStyleUnit_Percent:
3878 0 : value = coord.GetPercentValue() * imgDimension;
3879 0 : break;
3880 : case eStyleUnit_Factor:
3881 12 : value = nsPresContext::CSSPixelsToAppUnits(
3882 12 : NS_lround(coord.GetFactorValue()));
3883 12 : break;
3884 : default:
3885 0 : NS_NOTREACHED("unexpected CSS unit for image slice");
3886 0 : value = 0;
3887 0 : break;
3888 : }
3889 12 : if (value < 0)
3890 0 : value = 0;
3891 12 : if (value > imgDimension)
3892 0 : value = imgDimension;
3893 12 : mSlice.Side(s) = value;
3894 :
3895 12 : coord = aStyleBorder.mBorderImageWidth.Get(s);
3896 12 : switch (coord.GetUnit()) {
3897 : case eStyleUnit_Coord: // absolute dimension
3898 0 : value = coord.GetCoordValue();
3899 0 : break;
3900 : case eStyleUnit_Percent:
3901 0 : value = coord.GetPercentValue() * borderDimension;
3902 0 : break;
3903 : case eStyleUnit_Factor:
3904 12 : value = coord.GetFactorValue() * borderWidths.Side(s);
3905 12 : break;
3906 : case eStyleUnit_Auto: // same as the slice value, in CSS pixels
3907 0 : value = mSlice.Side(s);
3908 0 : break;
3909 : default:
3910 0 : NS_NOTREACHED("unexpected CSS unit for border image area division");
3911 0 : value = 0;
3912 0 : break;
3913 : }
3914 : // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3915 : // because we expect value to be non-negative.
3916 12 : MOZ_ASSERT(value >= 0);
3917 12 : mWidths.Side(s) = NSToCoordRoundWithClamp(value);
3918 12 : MOZ_ASSERT(mWidths.Side(s) >= 0);
3919 : }
3920 :
3921 : // "If two opposite border-image-width offsets are large enough that they
3922 : // overlap, their used values are proportionately reduced until they no
3923 : // longer overlap."
3924 3 : uint32_t combinedBorderWidth = uint32_t(mWidths.left) +
3925 3 : uint32_t(mWidths.right);
3926 3 : double scaleX = combinedBorderWidth > uint32_t(mArea.width)
3927 3 : ? mArea.width / double(combinedBorderWidth)
3928 3 : : 1.0;
3929 3 : uint32_t combinedBorderHeight = uint32_t(mWidths.top) +
3930 3 : uint32_t(mWidths.bottom);
3931 3 : double scaleY = combinedBorderHeight > uint32_t(mArea.height)
3932 3 : ? mArea.height / double(combinedBorderHeight)
3933 3 : : 1.0;
3934 3 : double scale = std::min(scaleX, scaleY);
3935 3 : if (scale < 1.0) {
3936 0 : mWidths.left *= scale;
3937 0 : mWidths.right *= scale;
3938 0 : mWidths.top *= scale;
3939 0 : mWidths.bottom *= scale;
3940 0 : NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width &&
3941 : mWidths.top + mWidths.bottom <= mArea.height,
3942 : "rounding error in width reduction???");
3943 : }
3944 :
3945 3 : mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH;
3946 3 : mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV;
3947 3 : mFill = aStyleBorder.mBorderImageFill;
3948 3 : }
|