1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCSSRenderingBorders.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 "ImageRegion.h"
18 #include "nsLayoutUtils.h"
19 #include "nsStyleConsts.h"
20 #include "nsContentUtils.h"
21 #include "nsCSSColorUtils.h"
22 #include "nsCSSRendering.h"
23 #include "nsCSSRenderingGradients.h"
24 #include "nsDisplayList.h"
25 #include "nsExpirationTracker.h"
26 #include "nsIScriptError.h"
27 #include "nsClassHashtable.h"
28 #include "nsPresContext.h"
29 #include "nsStyleStruct.h"
30 #include "gfx2DGlue.h"
31 #include "gfxGradientCache.h"
32 #include "mozilla/layers/StackingContextHelper.h"
33 #include "mozilla/layers/RenderRootStateManager.h"
34 #include "mozilla/layers/WebRenderLayerManager.h"
35 #include "mozilla/ProfilerLabels.h"
36 #include "mozilla/Range.h"
39 using namespace mozilla
;
40 using namespace mozilla::gfx
;
41 using namespace mozilla::image
;
43 #define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
46 * nsCSSRendering::PaintBorder
47 * nsCSSRendering::PaintOutline
51 * -> Ability to use specialized approach?
52 * |- Draw using specialized function
53 * |- separate corners?
56 * -> can border be drawn in 1 pass? (e.g., solid border same color all
58 * |- DrawBorderSides with all 4 sides
59 * -> more than 1 pass?
61 * |- clip to DoCornerClipSubPath
62 * |- for each side adjacent to corner
63 * |- clip to GetSideClipSubPath
64 * |- DrawBorderSides with one side
66 * |- GetSideClipWithoutCornersRect
67 * |- DrawDashedOrDottedSide || DrawBorderSides with one side
70 static void ComputeBorderCornerDimensions(const Float
* aBorderWidths
,
71 const RectCornerRadii
& aRadii
,
72 RectCornerRadii
* aDimsResult
);
74 // given a side index, get the previous and next side index
75 #define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
76 #define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
78 // given a corner index, get the previous and next corner index
79 #define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
80 #define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
82 // from the given base color and the background color, turn
83 // color into a color for the given border pattern style
84 static sRGBColor
MakeBorderColor(nscolor aColor
,
85 BorderColorStyle aBorderColorStyle
);
87 // Given a line index (an index starting from the outside of the
88 // border going inwards) and an array of line styles, calculate the
89 // color that that stripe of the border should be rendered in.
90 static sRGBColor
ComputeColorForLine(uint32_t aLineIndex
,
91 const BorderColorStyle
* aBorderColorStyle
,
92 uint32_t aBorderColorStyleCount
,
93 nscolor aBorderColor
);
95 // little helper function to check if the array of 4 floats given are
96 // equal to the given value
97 static bool CheckFourFloatsEqual(const Float
* vals
, Float k
) {
98 return (vals
[0] == k
&& vals
[1] == k
&& vals
[2] == k
&& vals
[3] == k
);
101 static bool IsZeroSize(const Size
& sz
) {
102 return sz
.width
== 0.0 || sz
.height
== 0.0;
106 bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii
& corners
) {
107 return IsZeroSize(corners
[eCornerTopLeft
]) &&
108 IsZeroSize(corners
[eCornerTopRight
]) &&
109 IsZeroSize(corners
[eCornerBottomRight
]) &&
110 IsZeroSize(corners
[eCornerBottomLeft
]);
113 static mozilla::Side
GetHorizontalSide(Corner aCorner
) {
114 return (aCorner
== C_TL
|| aCorner
== C_TR
) ? eSideTop
: eSideBottom
;
117 static mozilla::Side
GetVerticalSide(Corner aCorner
) {
118 return (aCorner
== C_TL
|| aCorner
== C_BL
) ? eSideLeft
: eSideRight
;
121 static Corner
GetCWCorner(mozilla::Side aSide
) {
122 return Corner(NEXT_SIDE(aSide
));
125 static Corner
GetCCWCorner(mozilla::Side aSide
) { return Corner(aSide
); }
127 static bool IsSingleSide(mozilla::SideBits aSides
) {
128 return aSides
== SideBits::eTop
|| aSides
== SideBits::eRight
||
129 aSides
== SideBits::eBottom
|| aSides
== SideBits::eLeft
;
132 static bool IsHorizontalSide(mozilla::Side aSide
) {
133 return aSide
== eSideTop
|| aSide
== eSideBottom
;
137 // Normal solid square corner. Will be rectangular, the size of the
138 // adjacent sides. If the corner has a border radius, the corner
139 // will always be solid, since we don't do dotted/dashed etc.
142 // Paint the corner in whatever style is not dotted/dashed of the
146 // Paint the corner as a dot, the size of the bigger of the adjacent
151 nsCSSBorderRenderer::nsCSSBorderRenderer(
152 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
,
153 const Rect
& aDirtyRect
, Rect
& aOuterRect
,
154 const StyleBorderStyle
* aBorderStyles
, const Float
* aBorderWidths
,
155 RectCornerRadii
& aBorderRadii
, const nscolor
* aBorderColors
,
156 bool aBackfaceIsVisible
, const Maybe
<Rect
>& aClipRect
)
157 : mPresContext(aPresContext
),
158 mDrawTarget(aDrawTarget
),
159 mDirtyRect(aDirtyRect
),
160 mOuterRect(aOuterRect
),
161 mBorderRadii(aBorderRadii
),
162 mBackfaceIsVisible(aBackfaceIsVisible
),
163 mLocalClip(aClipRect
) {
164 PodCopy(mBorderStyles
, aBorderStyles
, 4);
165 PodCopy(mBorderWidths
, aBorderWidths
, 4);
166 PodCopy(mBorderColors
, aBorderColors
, 4);
167 mInnerRect
= mOuterRect
;
168 mInnerRect
.Deflate(Margin(
169 mBorderStyles
[0] != StyleBorderStyle::None
? mBorderWidths
[0] : 0,
170 mBorderStyles
[1] != StyleBorderStyle::None
? mBorderWidths
[1] : 0,
171 mBorderStyles
[2] != StyleBorderStyle::None
? mBorderWidths
[2] : 0,
172 mBorderStyles
[3] != StyleBorderStyle::None
? mBorderWidths
[3] : 0));
174 ComputeBorderCornerDimensions(mBorderWidths
, mBorderRadii
,
175 &mBorderCornerDimensions
);
177 mOneUnitBorder
= CheckFourFloatsEqual(mBorderWidths
, 1.0);
178 mNoBorderRadius
= AllCornersZeroSize(mBorderRadii
);
179 mAllBordersSameStyle
= AreBorderSideFinalStylesSame(SideBits::eAll
);
180 mAllBordersSameWidth
= AllBordersSameWidth();
181 mAvoidStroke
= false;
185 void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii
& aRadii
,
186 const Float
* aBorderSizes
,
187 RectCornerRadii
* aInnerRadiiRet
) {
188 RectCornerRadii
& iRadii
= *aInnerRadiiRet
;
191 std::max(0.f
, aRadii
[C_TL
].width
- aBorderSizes
[eSideLeft
]);
192 iRadii
[C_TL
].height
=
193 std::max(0.f
, aRadii
[C_TL
].height
- aBorderSizes
[eSideTop
]);
196 std::max(0.f
, aRadii
[C_TR
].width
- aBorderSizes
[eSideRight
]);
197 iRadii
[C_TR
].height
=
198 std::max(0.f
, aRadii
[C_TR
].height
- aBorderSizes
[eSideTop
]);
201 std::max(0.f
, aRadii
[C_BR
].width
- aBorderSizes
[eSideRight
]);
202 iRadii
[C_BR
].height
=
203 std::max(0.f
, aRadii
[C_BR
].height
- aBorderSizes
[eSideBottom
]);
206 std::max(0.f
, aRadii
[C_BL
].width
- aBorderSizes
[eSideLeft
]);
207 iRadii
[C_BL
].height
=
208 std::max(0.f
, aRadii
[C_BL
].height
- aBorderSizes
[eSideBottom
]);
212 void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii
& aRadii
,
213 const Float
* aBorderSizes
,
214 RectCornerRadii
* aOuterRadiiRet
) {
215 RectCornerRadii
& oRadii
= *aOuterRadiiRet
;
217 // default all corners to sharp corners
218 oRadii
= RectCornerRadii(0.f
);
220 // round the edges that have radii > 0.0 to start with
221 if (aRadii
[C_TL
].width
> 0.f
&& aRadii
[C_TL
].height
> 0.f
) {
223 std::max(0.f
, aRadii
[C_TL
].width
+ aBorderSizes
[eSideLeft
]);
224 oRadii
[C_TL
].height
=
225 std::max(0.f
, aRadii
[C_TL
].height
+ aBorderSizes
[eSideTop
]);
228 if (aRadii
[C_TR
].width
> 0.f
&& aRadii
[C_TR
].height
> 0.f
) {
230 std::max(0.f
, aRadii
[C_TR
].width
+ aBorderSizes
[eSideRight
]);
231 oRadii
[C_TR
].height
=
232 std::max(0.f
, aRadii
[C_TR
].height
+ aBorderSizes
[eSideTop
]);
235 if (aRadii
[C_BR
].width
> 0.f
&& aRadii
[C_BR
].height
> 0.f
) {
237 std::max(0.f
, aRadii
[C_BR
].width
+ aBorderSizes
[eSideRight
]);
238 oRadii
[C_BR
].height
=
239 std::max(0.f
, aRadii
[C_BR
].height
+ aBorderSizes
[eSideBottom
]);
242 if (aRadii
[C_BL
].width
> 0.f
&& aRadii
[C_BL
].height
> 0.f
) {
244 std::max(0.f
, aRadii
[C_BL
].width
+ aBorderSizes
[eSideLeft
]);
245 oRadii
[C_BL
].height
=
246 std::max(0.f
, aRadii
[C_BL
].height
+ aBorderSizes
[eSideBottom
]);
250 /*static*/ void ComputeBorderCornerDimensions(const Float
* aBorderWidths
,
251 const RectCornerRadii
& aRadii
,
252 RectCornerRadii
* aDimsRet
) {
253 Float leftWidth
= aBorderWidths
[eSideLeft
];
254 Float topWidth
= aBorderWidths
[eSideTop
];
255 Float rightWidth
= aBorderWidths
[eSideRight
];
256 Float bottomWidth
= aBorderWidths
[eSideBottom
];
258 if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii
)) {
259 // These will always be in pixel units from CSS
260 (*aDimsRet
)[C_TL
] = Size(leftWidth
, topWidth
);
261 (*aDimsRet
)[C_TR
] = Size(rightWidth
, topWidth
);
262 (*aDimsRet
)[C_BR
] = Size(rightWidth
, bottomWidth
);
263 (*aDimsRet
)[C_BL
] = Size(leftWidth
, bottomWidth
);
265 // Always round up to whole pixels for the corners; it's safe to
266 // make the corners bigger than necessary, and this way we ensure
267 // that we avoid seams.
268 (*aDimsRet
)[C_TL
] = Size(ceil(std::max(leftWidth
, aRadii
[C_TL
].width
)),
269 ceil(std::max(topWidth
, aRadii
[C_TL
].height
)));
270 (*aDimsRet
)[C_TR
] = Size(ceil(std::max(rightWidth
, aRadii
[C_TR
].width
)),
271 ceil(std::max(topWidth
, aRadii
[C_TR
].height
)));
272 (*aDimsRet
)[C_BR
] = Size(ceil(std::max(rightWidth
, aRadii
[C_BR
].width
)),
273 ceil(std::max(bottomWidth
, aRadii
[C_BR
].height
)));
274 (*aDimsRet
)[C_BL
] = Size(ceil(std::max(leftWidth
, aRadii
[C_BL
].width
)),
275 ceil(std::max(bottomWidth
, aRadii
[C_BL
].height
)));
279 bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame(
280 mozilla::SideBits aSides
) {
281 NS_ASSERTION(aSides
!= SideBits::eNone
&&
282 (aSides
& ~SideBits::eAll
) == SideBits::eNone
,
283 "AreBorderSidesSame: invalid whichSides!");
285 /* First check if the specified styles and colors are the same for all sides
288 for (const auto i
: mozilla::AllPhysicalSides()) {
289 if (firstStyle
== i
) {
290 if ((static_cast<mozilla::SideBits
>(1 << i
) & aSides
) ==
297 if ((static_cast<mozilla::SideBits
>(1 << i
) & aSides
) == SideBits::eNone
) {
301 if (mBorderStyles
[firstStyle
] != mBorderStyles
[i
] ||
302 mBorderColors
[firstStyle
] != mBorderColors
[i
]) {
307 /* Then if it's one of the two-tone styles and we're not
308 * just comparing the TL or BR sides */
309 switch (mBorderStyles
[firstStyle
]) {
310 case StyleBorderStyle::Groove
:
311 case StyleBorderStyle::Ridge
:
312 case StyleBorderStyle::Inset
:
313 case StyleBorderStyle::Outset
:
314 return ((aSides
& ~(SideBits::eTop
| SideBits::eLeft
)) ==
316 (aSides
& ~(SideBits::eBottom
| SideBits::eRight
)) ==
323 bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle
,
326 case StyleBorderStyle::Solid
:
329 case StyleBorderStyle::Inset
:
330 case StyleBorderStyle::Outset
:
331 return (aCorner
== eCornerTopLeft
|| aCorner
== eCornerBottomRight
);
333 case StyleBorderStyle::Groove
:
334 case StyleBorderStyle::Ridge
:
335 return mOneUnitBorder
&&
336 (aCorner
== eCornerTopLeft
|| aCorner
== eCornerBottomRight
);
338 case StyleBorderStyle::Double
:
339 return mOneUnitBorder
;
346 bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner
) {
347 // Corner between dotted borders with same width and small radii is
348 // merged into single dot.
356 // | _+------+------------+-----
358 // |/ #######|####### |
359 // + #########|######### |
360 // | ##########|########## |
361 // | ###########|########### |
362 // | ###########|########### |
363 // |############|############|
364 // +------------+############|
365 // |#########################|
366 // | ####################### |
367 // | ####################### |
368 // | ##################### |
369 // | ################### |
370 // | ############### |
372 // +-------------------------+----
375 mozilla::Side
sideH(GetHorizontalSide(aCorner
));
376 mozilla::Side
sideV(GetVerticalSide(aCorner
));
377 StyleBorderStyle styleH
= mBorderStyles
[sideH
];
378 StyleBorderStyle styleV
= mBorderStyles
[sideV
];
379 if (styleH
!= styleV
|| styleH
!= StyleBorderStyle::Dotted
) {
383 Float widthH
= mBorderWidths
[sideH
];
384 Float widthV
= mBorderWidths
[sideV
];
385 if (widthH
!= widthV
) {
389 Size radius
= mBorderRadii
[aCorner
];
390 return IsZeroSize(radius
) ||
391 (radius
.width
< widthH
/ 2.0f
&& radius
.height
< widthH
/ 2.0f
);
394 BorderColorStyle
nsCSSBorderRenderer::BorderColorStyleForSolidCorner(
395 StyleBorderStyle aStyle
, Corner aCorner
) {
396 // note that this function assumes that the corner is already solid,
397 // as per the earlier function
399 case StyleBorderStyle::Solid
:
400 case StyleBorderStyle::Double
:
401 return BorderColorStyleSolid
;
403 case StyleBorderStyle::Inset
:
404 case StyleBorderStyle::Groove
:
405 if (aCorner
== eCornerTopLeft
) {
406 return BorderColorStyleDark
;
407 } else if (aCorner
== eCornerBottomRight
) {
408 return BorderColorStyleLight
;
412 case StyleBorderStyle::Outset
:
413 case StyleBorderStyle::Ridge
:
414 if (aCorner
== eCornerTopLeft
) {
415 return BorderColorStyleLight
;
416 } else if (aCorner
== eCornerBottomRight
) {
417 return BorderColorStyleDark
;
421 return BorderColorStyleNone
;
424 return BorderColorStyleNone
;
427 Rect
nsCSSBorderRenderer::GetCornerRect(Corner aCorner
) {
428 Point
offset(0.f
, 0.f
);
430 if (aCorner
== C_TR
|| aCorner
== C_BR
)
431 offset
.x
= mOuterRect
.Width() - mBorderCornerDimensions
[aCorner
].width
;
432 if (aCorner
== C_BR
|| aCorner
== C_BL
)
433 offset
.y
= mOuterRect
.Height() - mBorderCornerDimensions
[aCorner
].height
;
435 return Rect(mOuterRect
.TopLeft() + offset
, mBorderCornerDimensions
[aCorner
]);
438 Rect
nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide
) {
439 Point
offset(0.f
, 0.f
);
441 // The offset from the outside rect to the start of this side's
442 // box. For the top and bottom sides, the height of the box
443 // must be the border height; the x start must take into account
444 // the corner size (which may be bigger than the right or left
445 // side's width). The same applies to the right and left sides.
446 if (aSide
== eSideTop
) {
447 offset
.x
= mBorderCornerDimensions
[C_TL
].width
;
448 } else if (aSide
== eSideRight
) {
449 offset
.x
= mOuterRect
.Width() - mBorderWidths
[eSideRight
];
450 offset
.y
= mBorderCornerDimensions
[C_TR
].height
;
451 } else if (aSide
== eSideBottom
) {
452 offset
.x
= mBorderCornerDimensions
[C_BL
].width
;
453 offset
.y
= mOuterRect
.Height() - mBorderWidths
[eSideBottom
];
454 } else if (aSide
== eSideLeft
) {
455 offset
.y
= mBorderCornerDimensions
[C_TL
].height
;
458 // The sum of the width & height of the corners adjacent to the
459 // side. This relies on the relationship between side indexing and
460 // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
461 // with both proceeding clockwise.
462 Size sideCornerSum
= mBorderCornerDimensions
[GetCCWCorner(aSide
)] +
463 mBorderCornerDimensions
[GetCWCorner(aSide
)];
464 Rect
rect(mOuterRect
.TopLeft() + offset
, mOuterRect
.Size() - sideCornerSum
);
466 if (IsHorizontalSide(aSide
))
467 rect
.height
= mBorderWidths
[aSide
];
469 rect
.width
= mBorderWidths
[aSide
];
474 // The side border type and the adjacent border types are
475 // examined and one of the different types of clipping (listed
476 // below) is selected.
479 // clip to the trapezoid formed by the corners of the
480 // inner and outer rectangles for the given side
492 // clip to the trapezoid formed by the outer rectangle
493 // corners and the center of the region, making sure
494 // that diagonal lines all go directly from the outside
495 // corner to the inside corner, but that they then continue on
498 // This is needed for correctly clipping rounded borders,
499 // which might extend past the SIDE_CLIP_TRAPEZOID trap.
511 SIDE_CLIP_TRAPEZOID_FULL
,
513 // clip to the rectangle formed by the given side including corner.
514 // This is used by the non-dotted side next to dotted side.
524 SIDE_CLIP_RECTANGLE_CORNER
,
526 // clip to the rectangle formed by the given side excluding corner.
527 // This is used by the dotted side next to non-dotted side.
537 SIDE_CLIP_RECTANGLE_NO_CORNER
,
540 // Given three points, p0, p1, and midPoint, move p1 further in to the
541 // rectangle (of which aMidPoint is the center) so that it reaches the
542 // closer of the horizontal or vertical lines intersecting the midpoint,
543 // while maintaing the slope of the line. If p0 and p1 are the same,
544 // just move p1 to midPoint (since there's no slope to maintain).
545 // FIXME: Extending only to the midpoint isn't actually sufficient for
546 // boxes with asymmetric radii.
547 static void MaybeMoveToMidPoint(Point
& aP0
, Point
& aP1
,
548 const Point
& aMidPoint
) {
549 Point ps
= aP1
- aP0
;
562 std::min((aMidPoint
.x
- aP0
.x
) / ps
.x
, (aMidPoint
.y
- aP0
.y
) / ps
.y
);
568 already_AddRefed
<Path
> nsCSSBorderRenderer::GetSideClipSubPath(
569 mozilla::Side aSide
) {
570 // the clip proceeds clockwise from the top left corner;
571 // so "start" in each case is the start of the region from that side.
573 // the final path will be formed like:
578 // that is, the second point will always be on the inside
583 #define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted)
584 bool isDotted
= IS_DOTTED(mBorderStyles
[aSide
]);
585 bool startIsDotted
= IS_DOTTED(mBorderStyles
[PREV_SIDE(aSide
)]);
586 bool endIsDotted
= IS_DOTTED(mBorderStyles
[NEXT_SIDE(aSide
)]);
589 SideClipType startType
= SIDE_CLIP_TRAPEZOID
;
590 SideClipType endType
= SIDE_CLIP_TRAPEZOID
;
592 if (!IsZeroSize(mBorderRadii
[GetCCWCorner(aSide
)])) {
593 startType
= SIDE_CLIP_TRAPEZOID_FULL
;
594 } else if (startIsDotted
&& !isDotted
) {
595 startType
= SIDE_CLIP_RECTANGLE_CORNER
;
596 } else if (!startIsDotted
&& isDotted
) {
597 startType
= SIDE_CLIP_RECTANGLE_NO_CORNER
;
600 if (!IsZeroSize(mBorderRadii
[GetCWCorner(aSide
)])) {
601 endType
= SIDE_CLIP_TRAPEZOID_FULL
;
602 } else if (endIsDotted
&& !isDotted
) {
603 endType
= SIDE_CLIP_RECTANGLE_CORNER
;
604 } else if (!endIsDotted
&& isDotted
) {
605 endType
= SIDE_CLIP_RECTANGLE_NO_CORNER
;
608 Point midPoint
= mInnerRect
.Center();
610 start
[0] = mOuterRect
.CCWCorner(aSide
);
611 start
[1] = mInnerRect
.CCWCorner(aSide
);
613 end
[0] = mOuterRect
.CWCorner(aSide
);
614 end
[1] = mInnerRect
.CWCorner(aSide
);
616 if (startType
== SIDE_CLIP_TRAPEZOID_FULL
) {
617 MaybeMoveToMidPoint(start
[0], start
[1], midPoint
);
618 } else if (startType
== SIDE_CLIP_RECTANGLE_CORNER
) {
619 if (IsHorizontalSide(aSide
)) {
621 Point(mOuterRect
.CCWCorner(aSide
).x
, mInnerRect
.CCWCorner(aSide
).y
);
624 Point(mInnerRect
.CCWCorner(aSide
).x
, mOuterRect
.CCWCorner(aSide
).y
);
626 } else if (startType
== SIDE_CLIP_RECTANGLE_NO_CORNER
) {
627 if (IsHorizontalSide(aSide
)) {
629 Point(mInnerRect
.CCWCorner(aSide
).x
, mOuterRect
.CCWCorner(aSide
).y
);
632 Point(mOuterRect
.CCWCorner(aSide
).x
, mInnerRect
.CCWCorner(aSide
).y
);
636 if (endType
== SIDE_CLIP_TRAPEZOID_FULL
) {
637 MaybeMoveToMidPoint(end
[0], end
[1], midPoint
);
638 } else if (endType
== SIDE_CLIP_RECTANGLE_CORNER
) {
639 if (IsHorizontalSide(aSide
)) {
641 Point(mOuterRect
.CWCorner(aSide
).x
, mInnerRect
.CWCorner(aSide
).y
);
644 Point(mInnerRect
.CWCorner(aSide
).x
, mOuterRect
.CWCorner(aSide
).y
);
646 } else if (endType
== SIDE_CLIP_RECTANGLE_NO_CORNER
) {
647 if (IsHorizontalSide(aSide
)) {
649 Point(mInnerRect
.CWCorner(aSide
).x
, mOuterRect
.CWCorner(aSide
).y
);
652 Point(mOuterRect
.CWCorner(aSide
).x
, mInnerRect
.CWCorner(aSide
).y
);
656 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
657 builder
->MoveTo(start
[0]);
658 builder
->LineTo(end
[0]);
659 builder
->LineTo(end
[1]);
660 builder
->LineTo(start
[1]);
662 return builder
->Finish();
665 Point
nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide
,
669 // Calculate the end point of the side for dashed/dotted border, that is also
670 // the end point of the corner curve. The point is specified by aSide and
671 // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
675 // +--------------------
686 // The position of the point depends on the border-style, border-width, and
687 // border-radius of the side, corner, and the adjacent side beyond the corner,
688 // to make those sides (and corner) interact well.
690 // If the style of aSide is dotted and the dot at the point should be
691 // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
693 const Float signsList
[4][2] = {
694 {+1.0f
, +1.0f
}, {-1.0f
, +1.0f
}, {-1.0f
, -1.0f
}, {+1.0f
, -1.0f
}};
695 const Float(&signs
)[2] = signsList
[aCorner
];
697 *aIsUnfilled
= false;
699 Point P
= mOuterRect
.AtCorner(aCorner
);
700 StyleBorderStyle style
= mBorderStyles
[aSide
];
701 Float borderWidth
= mBorderWidths
[aSide
];
702 Size dim
= mBorderCornerDimensions
[aCorner
];
703 bool isHorizontal
= IsHorizontalSide(aSide
);
712 mozilla::Side otherSide
= ((uint8_t)aSide
== (uint8_t)aCorner
)
715 StyleBorderStyle otherStyle
= mBorderStyles
[otherSide
];
716 Float otherBorderWidth
= mBorderWidths
[otherSide
];
717 Size radius
= mBorderRadii
[aCorner
];
718 if (IsZeroSize(radius
)) {
720 radius
.height
= 0.0f
;
722 if (style
== StyleBorderStyle::Dotted
) {
723 // Offset the dot's location along the side toward the corner by a
724 // multiple of its width.
726 P
.x
-= signs
[0] * aDotOffset
* borderWidth
;
728 P
.y
-= signs
[1] * aDotOffset
* borderWidth
;
731 if (style
== StyleBorderStyle::Dotted
&&
732 otherStyle
== StyleBorderStyle::Dotted
) {
733 if (borderWidth
== otherBorderWidth
) {
734 if (radius
.width
< borderWidth
/ 2.0f
&&
735 radius
.height
< borderWidth
/ 2.0f
) {
736 // Two dots are merged into one and placed at the corner.
744 // | _+------+------------+-----
746 // |/ #######|####### |
747 // + #########|######### |
748 // | ##########|########## |
749 // | ###########|########### |
750 // | ###########|########### |
751 // |############|############|
752 // +------------+############|
753 // |########### P ###########|
754 // | ####################### |
755 // | ####################### |
756 // | ##################### |
757 // | ################### |
758 // | ############### |
760 // +-------------------------+----
763 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
764 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
766 // Two dots are drawn separately.
782 // +---------+----+---
792 // There should be enough gap between 2 dots even if radius.width is
793 // small but larger than borderWidth / 2.0. borderWidth * 1.5 is the
794 // value that there's imaginally unfilled dot at the corner. The
795 // unfilled dot may overflow from the outer curve, but filled dots
796 // doesn't, so this could be acceptable solution at least for now.
797 // We may have to find better model/value.
799 // imaginally unfilled dot at the corner
804 // *********|####|####
805 // *********|####+####
806 // *********|### P ###
809 // +---------+----+---
818 Float minimum
= borderWidth
* 1.5f
;
820 P
.x
+= signs
[0] * std::max(radius
.width
, minimum
);
821 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
823 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
824 P
.y
+= signs
[1] * std::max(radius
.height
, minimum
);
831 if (borderWidth
< otherBorderWidth
) {
832 // This side is smaller than other side, other side draws the corner.
834 // otherBorderWidth + borderWidth / 2.0
837 // +---------+--+--------
839 // | ####### |**|**#####
840 // |#########|**+**##+##
841 // |####+####|* P *#####
842 // |#########| *** ###
843 // | ####### +-----------
846 // | | first dot is not filled
850 // |<----------------->|
852 // | ___---+-------------
858 // | | __--+-------------
861 // | / first dot is filled
871 Float minimum
= otherBorderWidth
+ borderWidth
/ 2.0f
;
873 if (radius
.width
< minimum
) {
875 P
.x
+= signs
[0] * minimum
;
877 P
.x
+= signs
[0] * radius
.width
;
879 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
881 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
882 if (radius
.height
< minimum
) {
884 P
.y
+= signs
[1] * minimum
;
886 P
.y
+= signs
[1] * radius
.height
;
893 // This side is larger than other side, this side draws the corner.
898 // +----+---------------------
901 // |####|#### #########
902 // |####+#### ####+####
903 // |### P ### #########
906 // +-----+---------------------
909 // |**+**| <-- first dot in other side is not filled
919 P
.x
+= signs
[0] * std::max(radius
.width
, borderWidth
/ 2.0f
);
920 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
922 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
923 P
.y
+= signs
[1] * std::max(radius
.height
, borderWidth
/ 2.0f
);
928 if (style
== StyleBorderStyle::Dotted
) {
929 // If only this side is dotted, other side draws the corner.
931 // otherBorderWidth + borderWidth / 2.0
934 // +------+--+--------
940 // |## ##+-----------
943 // |## ##| first dot is not filled
947 // |<----------------->|
949 // | ___---+-------------
955 // | | __--+-------------
958 // | / first dot is filled
968 Float minimum
= otherBorderWidth
+ borderWidth
/ 2.0f
;
970 if (radius
.width
< minimum
) {
972 P
.x
+= signs
[0] * minimum
;
974 P
.x
+= signs
[0] * radius
.width
;
976 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
978 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
979 if (radius
.height
< minimum
) {
981 P
.y
+= signs
[1] * minimum
;
983 P
.y
+= signs
[1] * radius
.height
;
989 if (otherStyle
== StyleBorderStyle::Dotted
&& IsZeroSize(radius
)) {
990 // If other side is dotted and radius=0, draw side to the end of corner.
992 // +-------------------------------
993 // |########## ##########
994 // P +########## ##########
995 // |########## ##########
996 // +-----+-------------------------
999 // |**+**| <-- first dot in other side is not filled
1009 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
1011 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
1019 // |<----------------->|
1021 // | ___---+------------------
1022 // | __-- |####### ###
1023 // | _- P +####### ###
1025 // | / __---+------------------
1043 P
.x
+= signs
[0] * dim
.width
;
1044 P
.y
+= signs
[1] * borderWidth
/ 2.0f
;
1046 P
.x
+= signs
[0] * borderWidth
/ 2.0f
;
1047 P
.y
+= signs
[1] * dim
.height
;
1053 void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier
* aOuterBezier
,
1054 Bezier
* aInnerBezier
,
1056 // Return bezier control points for outer and inner curve for given corner.
1058 // ___---+ outer curve
1064 // | __--+ inner curve
1075 mozilla::Side
sideH(GetHorizontalSide(aCorner
));
1076 mozilla::Side
sideV(GetVerticalSide(aCorner
));
1078 Size
outerCornerSize(ceil(mBorderRadii
[aCorner
].width
),
1079 ceil(mBorderRadii
[aCorner
].height
));
1080 Size
innerCornerSize(
1081 ceil(std::max(0.0f
, mBorderRadii
[aCorner
].width
- mBorderWidths
[sideV
])),
1083 std::max(0.0f
, mBorderRadii
[aCorner
].height
- mBorderWidths
[sideH
])));
1085 GetBezierPointsForCorner(aOuterBezier
, aCorner
, mOuterRect
.AtCorner(aCorner
),
1088 GetBezierPointsForCorner(aInnerBezier
, aCorner
, mInnerRect
.AtCorner(aCorner
),
1092 void nsCSSBorderRenderer::FillSolidBorder(const Rect
& aOuterRect
,
1093 const Rect
& aInnerRect
,
1094 const RectCornerRadii
& aBorderRadii
,
1095 const Float
* aBorderSizes
,
1097 const ColorPattern
& aColor
) {
1098 // Note that this function is allowed to draw more than just the
1101 // If we have a border radius, do full rounded rectangles
1102 // and fill, regardless of what sides we're asked to draw.
1103 if (!AllCornersZeroSize(aBorderRadii
)) {
1104 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
1106 RectCornerRadii innerRadii
;
1107 ComputeInnerRadii(aBorderRadii
, aBorderSizes
, &innerRadii
);
1109 // do the outer border
1110 AppendRoundedRectToPath(builder
, aOuterRect
, aBorderRadii
, true);
1112 // then do the inner border CCW
1113 AppendRoundedRectToPath(builder
, aInnerRect
, innerRadii
, false);
1115 RefPtr
<Path
> path
= builder
->Finish();
1117 mDrawTarget
->Fill(path
, aColor
);
1121 // If we're asked to draw all sides of an equal-sized border,
1122 // stroking is fastest. This is a fairly common path, but partial
1123 // sides is probably second in the list -- there are a bunch of
1124 // common border styles, such as inset and outset, that are
1125 // top-left/bottom-right split.
1126 if (aSides
== SideBits::eAll
&&
1127 CheckFourFloatsEqual(aBorderSizes
, aBorderSizes
[0]) && !mAvoidStroke
) {
1128 Float strokeWidth
= aBorderSizes
[0];
1130 r
.Deflate(strokeWidth
/ 2.f
);
1131 mDrawTarget
->StrokeRect(r
, aColor
, StrokeOptions(strokeWidth
));
1135 // Otherwise, we have unequal sized borders or we're only
1136 // drawing some sides; create rectangles for each side
1141 // compute base rects for each side
1142 if (aSides
& SideBits::eTop
) {
1143 r
[eSideTop
] = Rect(aOuterRect
.X(), aOuterRect
.Y(), aOuterRect
.Width(),
1144 aBorderSizes
[eSideTop
]);
1147 if (aSides
& SideBits::eBottom
) {
1149 Rect(aOuterRect
.X(), aOuterRect
.YMost() - aBorderSizes
[eSideBottom
],
1150 aOuterRect
.Width(), aBorderSizes
[eSideBottom
]);
1153 if (aSides
& SideBits::eLeft
) {
1154 r
[eSideLeft
] = Rect(aOuterRect
.X(), aOuterRect
.Y(), aBorderSizes
[eSideLeft
],
1155 aOuterRect
.Height());
1158 if (aSides
& SideBits::eRight
) {
1160 Rect(aOuterRect
.XMost() - aBorderSizes
[eSideRight
], aOuterRect
.Y(),
1161 aBorderSizes
[eSideRight
], aOuterRect
.Height());
1164 // If two sides meet at a corner that we're rendering, then
1165 // make sure that we adjust one of the sides to avoid overlap.
1166 // This is especially important in the case of colors with
1167 // an alpha channel.
1169 if ((aSides
& (SideBits::eTop
| SideBits::eLeft
)) ==
1170 (SideBits::eTop
| SideBits::eLeft
)) {
1171 // adjust the left's top down a bit
1172 r
[eSideLeft
].y
+= aBorderSizes
[eSideTop
];
1173 r
[eSideLeft
].height
-= aBorderSizes
[eSideTop
];
1176 if ((aSides
& (SideBits::eTop
| SideBits::eRight
)) ==
1177 (SideBits::eTop
| SideBits::eRight
)) {
1178 // adjust the top's left a bit
1179 r
[eSideTop
].width
-= aBorderSizes
[eSideRight
];
1182 if ((aSides
& (SideBits::eBottom
| SideBits::eRight
)) ==
1183 (SideBits::eBottom
| SideBits::eRight
)) {
1184 // adjust the right's bottom a bit
1185 r
[eSideRight
].height
-= aBorderSizes
[eSideBottom
];
1188 if ((aSides
& (SideBits::eBottom
| SideBits::eLeft
)) ==
1189 (SideBits::eBottom
| SideBits::eLeft
)) {
1190 // adjust the bottom's left a bit
1191 r
[eSideBottom
].x
+= aBorderSizes
[eSideLeft
];
1192 r
[eSideBottom
].width
-= aBorderSizes
[eSideLeft
];
1195 // Filling these one by one is faster than filling them all at once.
1196 for (uint32_t i
= 0; i
< 4; i
++) {
1197 if (aSides
& static_cast<mozilla::SideBits
>(1 << i
)) {
1198 MaybeSnapToDevicePixels(r
[i
], *mDrawTarget
, true);
1199 mDrawTarget
->FillRect(r
[i
], aColor
);
1204 sRGBColor
MakeBorderColor(nscolor aColor
, BorderColorStyle aBorderColorStyle
) {
1208 switch (aBorderColorStyle
) {
1209 case BorderColorStyleNone
:
1210 return sRGBColor(0.f
, 0.f
, 0.f
, 0.f
); // transparent black
1212 case BorderColorStyleLight
:
1215 case BorderColorStyleDark
:
1216 NS_GetSpecial3DColors(colors
, aColor
);
1217 return sRGBColor::FromABGR(colors
[k
]);
1219 case BorderColorStyleSolid
:
1221 return sRGBColor::FromABGR(aColor
);
1225 sRGBColor
ComputeColorForLine(uint32_t aLineIndex
,
1226 const BorderColorStyle
* aBorderColorStyle
,
1227 uint32_t aBorderColorStyleCount
,
1228 nscolor aBorderColor
) {
1229 NS_ASSERTION(aLineIndex
< aBorderColorStyleCount
, "Invalid lineIndex given");
1231 return MakeBorderColor(aBorderColor
, aBorderColorStyle
[aLineIndex
]);
1234 void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides
) {
1235 if (aSides
== SideBits::eNone
||
1236 (aSides
& ~SideBits::eAll
) != SideBits::eNone
) {
1237 NS_WARNING("DrawBorderSides: invalid sides!");
1241 StyleBorderStyle borderRenderStyle
= StyleBorderStyle::None
;
1242 nscolor borderRenderColor
;
1244 uint32_t borderColorStyleCount
= 0;
1245 BorderColorStyle borderColorStyleTopLeft
[3], borderColorStyleBottomRight
[3];
1246 BorderColorStyle
* borderColorStyle
= nullptr;
1248 for (const auto i
: mozilla::AllPhysicalSides()) {
1249 if ((aSides
& static_cast<mozilla::SideBits
>(1 << i
)) == SideBits::eNone
) {
1252 borderRenderStyle
= mBorderStyles
[i
];
1253 borderRenderColor
= mBorderColors
[i
];
1257 if (borderRenderStyle
== StyleBorderStyle::None
||
1258 borderRenderStyle
== StyleBorderStyle::Hidden
) {
1262 if (borderRenderStyle
== StyleBorderStyle::Dashed
||
1263 borderRenderStyle
== StyleBorderStyle::Dotted
) {
1264 // Draw each corner separately, with the given side's color.
1265 if (aSides
& SideBits::eTop
) {
1266 DrawDashedOrDottedCorner(eSideTop
, C_TL
);
1267 } else if (aSides
& SideBits::eLeft
) {
1268 DrawDashedOrDottedCorner(eSideLeft
, C_TL
);
1271 if (aSides
& SideBits::eTop
) {
1272 DrawDashedOrDottedCorner(eSideTop
, C_TR
);
1273 } else if (aSides
& SideBits::eRight
) {
1274 DrawDashedOrDottedCorner(eSideRight
, C_TR
);
1277 if (aSides
& SideBits::eBottom
) {
1278 DrawDashedOrDottedCorner(eSideBottom
, C_BL
);
1279 } else if (aSides
& SideBits::eLeft
) {
1280 DrawDashedOrDottedCorner(eSideLeft
, C_BL
);
1283 if (aSides
& SideBits::eBottom
) {
1284 DrawDashedOrDottedCorner(eSideBottom
, C_BR
);
1285 } else if (aSides
& SideBits::eRight
) {
1286 DrawDashedOrDottedCorner(eSideRight
, C_BR
);
1291 // The borderColorStyle array goes from the outer to the inner style.
1293 // If the border width is 1, we need to change the borderRenderStyle
1294 // a bit to make sure that we get the right colors -- e.g. 'ridge'
1295 // with a 1px border needs to look like solid, not like 'outset'.
1296 if (mOneUnitBorder
&& (borderRenderStyle
== StyleBorderStyle::Ridge
||
1297 borderRenderStyle
== StyleBorderStyle::Groove
||
1298 borderRenderStyle
== StyleBorderStyle::Double
)) {
1299 borderRenderStyle
= StyleBorderStyle::Solid
;
1302 switch (borderRenderStyle
) {
1303 case StyleBorderStyle::Solid
:
1304 borderColorStyleTopLeft
[0] = BorderColorStyleSolid
;
1306 borderColorStyleBottomRight
[0] = BorderColorStyleSolid
;
1308 borderColorStyleCount
= 1;
1311 case StyleBorderStyle::Groove
:
1312 borderColorStyleTopLeft
[0] = BorderColorStyleDark
;
1313 borderColorStyleTopLeft
[1] = BorderColorStyleLight
;
1315 borderColorStyleBottomRight
[0] = BorderColorStyleLight
;
1316 borderColorStyleBottomRight
[1] = BorderColorStyleDark
;
1318 borderColorStyleCount
= 2;
1321 case StyleBorderStyle::Ridge
:
1322 borderColorStyleTopLeft
[0] = BorderColorStyleLight
;
1323 borderColorStyleTopLeft
[1] = BorderColorStyleDark
;
1325 borderColorStyleBottomRight
[0] = BorderColorStyleDark
;
1326 borderColorStyleBottomRight
[1] = BorderColorStyleLight
;
1328 borderColorStyleCount
= 2;
1331 case StyleBorderStyle::Double
:
1332 borderColorStyleTopLeft
[0] = BorderColorStyleSolid
;
1333 borderColorStyleTopLeft
[1] = BorderColorStyleNone
;
1334 borderColorStyleTopLeft
[2] = BorderColorStyleSolid
;
1336 borderColorStyleBottomRight
[0] = BorderColorStyleSolid
;
1337 borderColorStyleBottomRight
[1] = BorderColorStyleNone
;
1338 borderColorStyleBottomRight
[2] = BorderColorStyleSolid
;
1340 borderColorStyleCount
= 3;
1343 case StyleBorderStyle::Inset
:
1344 borderColorStyleTopLeft
[0] = BorderColorStyleDark
;
1345 borderColorStyleBottomRight
[0] = BorderColorStyleLight
;
1347 borderColorStyleCount
= 1;
1350 case StyleBorderStyle::Outset
:
1351 borderColorStyleTopLeft
[0] = BorderColorStyleLight
;
1352 borderColorStyleBottomRight
[0] = BorderColorStyleDark
;
1354 borderColorStyleCount
= 1;
1358 MOZ_ASSERT_UNREACHABLE("Unhandled border style!!");
1362 // The only way to get to here is by having a
1363 // borderColorStyleCount < 1 or > 3; this should never happen,
1364 // since -moz-border-colors doesn't get handled here.
1365 NS_ASSERTION(borderColorStyleCount
> 0 && borderColorStyleCount
< 4,
1366 "Non-border-colors case with borderColorStyleCount < 1 or > 3; "
1369 // The caller should never give us anything with a mix
1370 // of TL/BR if the border style would require a
1372 if (aSides
& (SideBits::eBottom
| SideBits::eRight
)) {
1373 borderColorStyle
= borderColorStyleBottomRight
;
1375 borderColorStyle
= borderColorStyleTopLeft
;
1378 // Distribute the border across the available space.
1379 Float borderWidths
[3][4];
1381 if (borderColorStyleCount
== 1) {
1382 for (const auto i
: mozilla::AllPhysicalSides()) {
1383 borderWidths
[0][i
] = mBorderWidths
[i
];
1385 } else if (borderColorStyleCount
== 2) {
1386 // with 2 color styles, any extra pixel goes to the outside
1387 for (const auto i
: mozilla::AllPhysicalSides()) {
1388 borderWidths
[0][i
] =
1389 int32_t(mBorderWidths
[i
]) / 2 + int32_t(mBorderWidths
[i
]) % 2;
1390 borderWidths
[1][i
] = int32_t(mBorderWidths
[i
]) / 2;
1392 } else if (borderColorStyleCount
== 3) {
1393 // with 3 color styles, any extra pixel (or lack of extra pixel)
1394 // goes to the middle
1395 for (const auto i
: mozilla::AllPhysicalSides()) {
1396 if (mBorderWidths
[i
] == 1.0) {
1397 borderWidths
[0][i
] = 1.f
;
1398 borderWidths
[1][i
] = borderWidths
[2][i
] = 0.f
;
1400 int32_t rest
= int32_t(mBorderWidths
[i
]) % 3;
1401 borderWidths
[0][i
] = borderWidths
[2][i
] = borderWidths
[1][i
] =
1402 (int32_t(mBorderWidths
[i
]) - rest
) / 3;
1405 borderWidths
[1][i
] += 1.f
;
1406 } else if (rest
== 2) {
1407 borderWidths
[0][i
] += 1.f
;
1408 borderWidths
[2][i
] += 1.f
;
1414 // make a copy that we can modify
1415 RectCornerRadii radii
= mBorderRadii
;
1417 Rect
soRect(mOuterRect
);
1418 Rect
siRect(mOuterRect
);
1420 // If adjacent side is dotted and radius=0, draw side to the end of corner.
1422 // +--------------------------------
1423 // |################################
1425 // |################################
1426 // +-----+--------------------------
1438 bool noMarginTop
= false;
1439 bool noMarginRight
= false;
1440 bool noMarginBottom
= false;
1441 bool noMarginLeft
= false;
1443 // If there is at least one dotted side, every side is rendered separately.
1444 if (IsSingleSide(aSides
)) {
1445 if (aSides
== SideBits::eTop
) {
1446 if (mBorderStyles
[eSideRight
] == StyleBorderStyle::Dotted
&&
1447 IsZeroSize(mBorderRadii
[C_TR
])) {
1448 noMarginRight
= true;
1450 if (mBorderStyles
[eSideLeft
] == StyleBorderStyle::Dotted
&&
1451 IsZeroSize(mBorderRadii
[C_TL
])) {
1452 noMarginLeft
= true;
1454 } else if (aSides
== SideBits::eRight
) {
1455 if (mBorderStyles
[eSideTop
] == StyleBorderStyle::Dotted
&&
1456 IsZeroSize(mBorderRadii
[C_TR
])) {
1459 if (mBorderStyles
[eSideBottom
] == StyleBorderStyle::Dotted
&&
1460 IsZeroSize(mBorderRadii
[C_BR
])) {
1461 noMarginBottom
= true;
1463 } else if (aSides
== SideBits::eBottom
) {
1464 if (mBorderStyles
[eSideRight
] == StyleBorderStyle::Dotted
&&
1465 IsZeroSize(mBorderRadii
[C_BR
])) {
1466 noMarginRight
= true;
1468 if (mBorderStyles
[eSideLeft
] == StyleBorderStyle::Dotted
&&
1469 IsZeroSize(mBorderRadii
[C_BL
])) {
1470 noMarginLeft
= true;
1473 if (mBorderStyles
[eSideTop
] == StyleBorderStyle::Dotted
&&
1474 IsZeroSize(mBorderRadii
[C_TL
])) {
1477 if (mBorderStyles
[eSideBottom
] == StyleBorderStyle::Dotted
&&
1478 IsZeroSize(mBorderRadii
[C_BL
])) {
1479 noMarginBottom
= true;
1484 for (unsigned int i
= 0; i
< borderColorStyleCount
; i
++) {
1485 // walk siRect inwards at the start of the loop to get the
1486 // correct inner rect.
1488 // If noMarginTop is false:
1489 // --------------------+
1493 // ----------------+ |
1497 // If noMarginTop is true:
1498 // ----------------+<--+
1505 siRect
.Deflate(Margin(noMarginTop
? 0 : borderWidths
[i
][0],
1506 noMarginRight
? 0 : borderWidths
[i
][1],
1507 noMarginBottom
? 0 : borderWidths
[i
][2],
1508 noMarginLeft
? 0 : borderWidths
[i
][3]));
1510 if (borderColorStyle
[i
] != BorderColorStyleNone
) {
1511 sRGBColor c
= ComputeColorForLine(
1512 i
, borderColorStyle
, borderColorStyleCount
, borderRenderColor
);
1513 ColorPattern
color(ToDeviceColor(c
));
1515 FillSolidBorder(soRect
, siRect
, radii
, borderWidths
[i
], aSides
, color
);
1518 ComputeInnerRadii(radii
, borderWidths
[i
], &radii
);
1520 // And now soRect is the same as siRect, for the next line in.
1525 void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions
* aStrokeOptions
,
1527 mozilla::Side aSide
,
1528 Float aBorderLength
,
1530 MOZ_ASSERT(mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
||
1531 mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
1532 "Style should be dashed or dotted.");
1534 StyleBorderStyle style
= mBorderStyles
[aSide
];
1535 Float borderWidth
= mBorderWidths
[aSide
];
1537 // Dashed line starts and ends with half segment in most case.
1539 // __--+---+---+---+---+---+---+---+---+--__
1540 // |###| | |###|###| | |###|
1541 // |###| | |###|###| | |###|
1542 // |###| | |###|###| | |###|
1543 // __--+---+---+---+---+---+---+---+---+--__
1545 // If radius=0 and other side is either dotted or 0-width, it starts or ends
1546 // with full segment.
1548 // +---+---+---+---+---+---+---+---+---+---+
1549 // |###|###| | |###|###| | |###|###|
1550 // |###|###| | |###|###| | |###|###|
1551 // |###|###| | |###|###| | |###|###|
1552 // +---++--+---+---+---+---+---+---+--++---+
1562 bool fullStart
= false, fullEnd
= false;
1564 if (style
== StyleBorderStyle::Dashed
) {
1565 // If either end of the side is not connecting onto a corner then we want a
1566 // full dash at that end.
1568 // Note that in the case that a corner is empty, either the adjacent side
1569 // has zero width, or else DrawBorders() set the corner to be empty
1570 // (it does that if the adjacent side has zero length and the border widths
1571 // of this and the adjacent sides are thin enough that the corner will be
1572 // insignificantly small).
1574 if (mBorderRadii
[GetCCWCorner(aSide
)].IsEmpty() &&
1575 (mBorderCornerDimensions
[GetCCWCorner(aSide
)].IsEmpty() ||
1576 mBorderStyles
[PREV_SIDE(aSide
)] == StyleBorderStyle::Dotted
||
1577 // XXX why this <=1 check?
1578 borderWidth
<= 1.0f
)) {
1582 if (mBorderRadii
[GetCWCorner(aSide
)].IsEmpty() &&
1583 (mBorderCornerDimensions
[GetCWCorner(aSide
)].IsEmpty() ||
1584 mBorderStyles
[NEXT_SIDE(aSide
)] == StyleBorderStyle::Dotted
)) {
1588 halfDash
= borderWidth
* DOT_LENGTH
* DASH_LENGTH
/ 2.0f
;
1590 halfDash
= borderWidth
* DOT_LENGTH
/ 2.0f
;
1593 if (style
== StyleBorderStyle::Dashed
&& aBorderLength
> 0.0f
) {
1594 // The number of half segments, with maximum dash length.
1595 int32_t count
= floor(aBorderLength
/ halfDash
);
1596 Float minHalfDash
= borderWidth
* DOT_LENGTH
/ 2.0f
;
1598 if (fullStart
&& fullEnd
) {
1599 // count should be 4n + 2
1604 // +---+---+---+---+---+---+---+---+---+---+
1605 // |###|###| | |###|###| | |###|###|
1606 // |###|###| | |###|###| | |###|###|
1607 // |###|###| | |###|###| | |###|###|
1608 // +---+---+---+---+---+---+---+---+---+---+
1610 // If border is too short, draw solid line.
1611 if (aBorderLength
< 6.0f
* minHalfDash
) {
1615 if (count
% 4 == 0) {
1617 } else if (count
% 4 == 1) {
1619 } else if (count
% 4 == 3) {
1622 } else if (fullStart
|| fullEnd
) {
1623 // count should be 4n + 1
1628 // +---+---+---+---+---+---+---+---+---+
1629 // |###|###| | |###|###| | |###|
1630 // |###|###| | |###|###| | |###|
1631 // |###|###| | |###|###| | |###|
1632 // +---+---+---+---+---+---+---+---+---+
1637 // +---+---+---+---+---+---+---+---+---+
1638 // |###| | |###|###| | |###|###|
1639 // |###| | |###|###| | |###|###|
1640 // |###| | |###|###| | |###|###|
1641 // +---+---+---+---+---+---+---+---+---+
1643 // If border is too short, draw solid line.
1644 if (aBorderLength
< 5.0f
* minHalfDash
) {
1648 if (count
% 4 == 0) {
1650 } else if (count
% 4 == 2) {
1652 } else if (count
% 4 == 3) {
1656 // count should be 4n
1661 // +---+---+---+---+---+---+---+---+
1662 // |###| | |###|###| | |###|
1663 // |###| | |###|###| | |###|
1664 // |###| | |###|###| | |###|
1665 // +---+---+---+---+---+---+---+---+
1667 // If border is too short, draw solid line.
1668 if (aBorderLength
< 4.0f
* minHalfDash
) {
1672 if (count
% 4 == 1) {
1674 } else if (count
% 4 == 2) {
1676 } else if (count
% 4 == 3) {
1680 halfDash
= aBorderLength
/ count
;
1683 Float fullDash
= halfDash
* 2.0f
;
1685 aDash
[0] = fullDash
;
1686 aDash
[1] = fullDash
;
1688 if (style
== StyleBorderStyle::Dashed
&& fullDash
> 1.0f
) {
1690 // Draw half segments on both ends.
1691 aStrokeOptions
->mDashOffset
= halfDash
;
1693 } else if (style
!= StyleBorderStyle::Dotted
&& isCorner
) {
1694 // If side ends with filled full segment, corner should start with unfilled
1695 // full segment. Not needed for dotted corners, as they overlap one dot with
1699 // ------------>|<---------------------------
1701 // __+---+---+---+---+---+---+---+---+
1702 // _+- | |###|###| | |###|###| |
1703 // /##| | |###|###| | |###|###| |
1704 // +####| | |###|###| | |###|###| |
1705 // /#\####| _+--+---+---+---+---+---+---+---+
1710 aStrokeOptions
->mDashOffset
= fullDash
;
1713 aStrokeOptions
->mDashPattern
= aDash
;
1714 aStrokeOptions
->mDashLength
= 2;
1716 PrintAsFormatString("dash: %f %f\n", aDash
[0], aDash
[1]);
1719 static Float
GetBorderLength(mozilla::Side aSide
, const Point
& aStart
,
1720 const Point
& aEnd
) {
1721 if (aSide
== eSideTop
) {
1722 return aEnd
.x
- aStart
.x
;
1724 if (aSide
== eSideRight
) {
1725 return aEnd
.y
- aStart
.y
;
1727 if (aSide
== eSideBottom
) {
1728 return aStart
.x
- aEnd
.x
;
1730 return aStart
.y
- aEnd
.y
;
1733 void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide
) {
1734 // Draw dashed/dotted side with following approach.
1737 // Draw dashed line along the side, with appropriate dash length and gap
1738 // to make the side symmetric as far as possible. Dash length equals to
1739 // the gap, and the ratio of the dash length to border-width is the maximum
1740 // value in in [1, 3] range.
1741 // In most case, line ends with half segment, to joint with corner easily.
1742 // If adjacent side is dotted or 0px and border-radius for the corner
1743 // between them is 0, the line ends with full segment.
1744 // (see comment for GetStraightBorderPoint for more detail)
1747 // If border-width <= 2.0, draw 1:1 dashed line.
1748 // Otherwise, draw circles along the side, with appropriate gap that makes
1749 // the side symmetric as far as possible. The ratio of the gap to
1750 // border-width is the maximum value in [0.5, 1] range in most case.
1751 // if the side is too short and there's only 2 dots, it can be more smaller.
1752 // If there's no space to place 2 dots at the side, draw single dot at the
1753 // middle of the side.
1754 // In most case, line ends with filled dot, to joint with corner easily,
1755 // If adjacent side is dotted with larger border-width, or other style,
1756 // the line ends with unfilled dot.
1757 // (see comment for GetStraightBorderPoint for more detail)
1759 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
||
1760 mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
1761 "Style should be dashed or dotted.");
1763 Float borderWidth
= mBorderWidths
[aSide
];
1764 if (borderWidth
== 0.0f
) {
1768 if (mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
&& borderWidth
> 2.0f
) {
1769 DrawDottedSideSlow(aSide
);
1773 nscolor borderColor
= mBorderColors
[aSide
];
1775 // Get the start and end points of the side, ensuring that any dot origins get
1776 // pushed outward to account for stroking.
1778 GetStraightBorderPoint(aSide
, GetCCWCorner(aSide
), &ignored
, 0.5f
);
1779 Point end
= GetStraightBorderPoint(aSide
, GetCWCorner(aSide
), &ignored
, 0.5f
);
1780 if (borderWidth
< 2.0f
) {
1781 // Round start to draw dot on each pixel.
1782 if (IsHorizontalSide(aSide
)) {
1783 start
.x
= round(start
.x
);
1785 start
.y
= round(start
.y
);
1789 Float borderLength
= GetBorderLength(aSide
, start
, end
);
1790 if (borderLength
< 0.0f
) {
1794 StrokeOptions
strokeOptions(borderWidth
);
1796 SetupDashedOptions(&strokeOptions
, dash
, aSide
, borderLength
, false);
1798 // For dotted sides that can merge with their prior dotted sides, advance the
1799 // dash offset to measure the distance around the combined path. This prevents
1800 // two dots from bunching together at a corner.
1801 mozilla::Side mergeSide
= aSide
;
1802 while (IsCornerMergeable(GetCCWCorner(mergeSide
))) {
1803 mergeSide
= PREV_SIDE(mergeSide
);
1804 // If we looped all the way around, measure starting at the top side, since
1805 // we need to pick a fixed location to start measuring distance from still.
1806 if (mergeSide
== aSide
) {
1807 mergeSide
= eSideTop
;
1811 while (mergeSide
!= aSide
) {
1812 // Measure the length of the merged side starting from a possibly
1813 // unmergeable corner up to the merged corner. A merged corner effectively
1814 // has no border radius, so we can just use the cheaper AtCorner to find the
1817 GetBorderLength(mergeSide
,
1818 GetStraightBorderPoint(
1819 mergeSide
, GetCCWCorner(mergeSide
), &ignored
, 0.5f
),
1820 mOuterRect
.AtCorner(GetCWCorner(mergeSide
)));
1821 // Add in the merged side length. Also offset the dash progress by an extra
1822 // dot's width to avoid drawing a dot that would overdraw where the merged
1823 // side would have ended in a gap, i.e. O_O_
1825 strokeOptions
.mDashOffset
+= mergeLength
+ borderWidth
;
1826 mergeSide
= NEXT_SIDE(mergeSide
);
1829 DrawOptions drawOptions
;
1830 if (mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
) {
1831 drawOptions
.mAntialiasMode
= AntialiasMode::NONE
;
1834 mDrawTarget
->StrokeLine(start
, end
, ColorPattern(ToDeviceColor(borderColor
)),
1835 strokeOptions
, drawOptions
);
1838 void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide
) {
1839 // Draw each circles separately for dotted with borderWidth > 2.0.
1840 // Dashed line with CapStyle::ROUND doesn't render perfect circles.
1842 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
1843 "Style should be dotted.");
1845 Float borderWidth
= mBorderWidths
[aSide
];
1846 if (borderWidth
== 0.0f
) {
1850 nscolor borderColor
= mBorderColors
[aSide
];
1851 bool isStartUnfilled
, isEndUnfilled
;
1853 GetStraightBorderPoint(aSide
, GetCCWCorner(aSide
), &isStartUnfilled
);
1854 Point end
= GetStraightBorderPoint(aSide
, GetCWCorner(aSide
), &isEndUnfilled
);
1856 // Corner is not mergeable.
1859 // Corner between different colors.
1860 // Two dots are merged into one, and both side draw half dot.
1863 // Corner between same colors, CCW corner of the side.
1864 // Two dots are merged into one, and this side draw entire dot.
1866 // MERGE_ALL MERGE_NONE
1869 // +-----------------------+----+
1870 // | ## ## ## | ## |
1871 // |#### #### #### |####|
1872 // |#### #### #### |####|
1873 // | ## ## ## | ## |
1874 // +----+------------------+ |
1882 // Corner between same colors, CW corner of the side.
1883 // Two dots are merged into one, and this side doesn't draw dot.
1885 } mergeStart
= NO_MERGE
,
1886 mergeEnd
= NO_MERGE
;
1888 if (IsCornerMergeable(GetCCWCorner(aSide
))) {
1889 if (borderColor
== mBorderColors
[PREV_SIDE(aSide
)]) {
1890 mergeStart
= MERGE_ALL
;
1892 mergeStart
= MERGE_HALF
;
1896 if (IsCornerMergeable(GetCWCorner(aSide
))) {
1897 if (borderColor
== mBorderColors
[NEXT_SIDE(aSide
)]) {
1898 mergeEnd
= MERGE_NONE
;
1900 mergeEnd
= MERGE_HALF
;
1904 Float borderLength
= GetBorderLength(aSide
, start
, end
);
1905 if (borderLength
< 0.0f
) {
1906 if (isStartUnfilled
|| isEndUnfilled
) {
1909 borderLength
= 0.0f
;
1910 start
= end
= (start
+ end
) / 2.0f
;
1913 Float dotWidth
= borderWidth
* DOT_LENGTH
;
1914 Float radius
= borderWidth
/ 2.0f
;
1915 if (borderLength
< dotWidth
) {
1916 // If dots on start and end may overlap, draw a dot at the middle of them.
1918 // ___---+-------+---___
1919 // __-- | ##### | --__
1928 // __--+-------+--__
1931 // If that circle overflows from outer rect, do not draw it.
1946 if (!mOuterRect
.Contains(Rect(start
.x
- radius
, start
.y
- radius
,
1947 borderWidth
, borderWidth
))) {
1951 if (isStartUnfilled
|| isEndUnfilled
) {
1955 Point P
= (start
+ end
) / 2;
1956 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
1957 builder
->MoveTo(Point(P
.x
+ radius
, P
.y
));
1958 builder
->Arc(P
, radius
, 0.0f
, Float(2.0 * M_PI
));
1959 RefPtr
<Path
> path
= builder
->Finish();
1960 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
1964 if (mergeStart
== MERGE_HALF
|| mergeEnd
== MERGE_HALF
) {
1978 // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
1991 Point
I(0.0f
, 0.0f
), J(0.0f
, 0.0f
);
1992 if (aSide
== eSideTop
) {
1995 } else if (aSide
== eSideRight
) {
1998 } else if (aSide
== eSideBottom
) {
2001 } else if (aSide
== eSideLeft
) {
2006 Point So
, Si
, Eo
, Ei
;
2008 So
= (start
+ (-I
+ -J
) * borderWidth
/ 2.0f
);
2009 Si
= (mergeStart
== MERGE_HALF
) ? (start
+ (I
+ J
) * borderWidth
/ 2.0f
)
2010 : (start
+ (-I
+ J
) * borderWidth
/ 2.0f
);
2011 Eo
= (end
+ (I
- J
) * borderWidth
/ 2.0f
);
2012 Ei
= (mergeEnd
== MERGE_HALF
) ? (end
+ (-I
+ J
) * borderWidth
/ 2.0f
)
2013 : (end
+ (I
+ J
) * borderWidth
/ 2.0f
);
2015 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2016 builder
->MoveTo(So
);
2017 builder
->LineTo(Eo
);
2018 builder
->LineTo(Ei
);
2019 builder
->LineTo(Si
);
2021 RefPtr
<Path
> path
= builder
->Finish();
2023 mDrawTarget
->PushClip(path
);
2026 size_t count
= round(borderLength
/ dotWidth
);
2027 if (isStartUnfilled
== isEndUnfilled
) {
2028 // Split into 2n segments.
2033 // Split into 2n+1 segments.
2034 if (count
% 2 == 0) {
2039 // A: radius == borderWidth / 2.0
2040 // B: borderLength / count == borderWidth * (1 - overlap)
2043 // |<-->|<------>|<------>|<------>|<------>|<-->|
2045 // +----+--------+--------+--------+--------+----+
2046 // | ##|## **|** ##|## **|** ##|## |
2047 // | ###|### ***|*** ###|### ***|*** ###|### |
2048 // |####|####****|****####|####****|****####|####|
2049 // |####+####****+****####+####****+****####+####|
2050 // |# start #****|****####|####****|****## end ##|
2051 // | ###|### ***|*** ###|### ***|*** ###|### |
2052 // | ##|## **|** ##|## **|** ##|## |
2053 // +----+----+---+--------+--------+---+----+----+
2057 // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
2059 size_t from
= isStartUnfilled
? 1 : 0;
2061 // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
2063 if (mergeEnd
== MERGE_NONE
) {
2071 Point fromP
= (start
* (count
- from
) + end
* from
) / count
;
2072 Point toP
= (start
* (count
- to
) + end
* to
) / count
;
2073 // Extend dirty rect to avoid clipping pixel for anti-aliasing.
2074 const Float AA_MARGIN
= 2.0f
;
2076 if (aSide
== eSideTop
) {
2077 // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
2078 // to render only paths that may overlap mDirtyRect.
2080 // mDirtyRect + radius margin
2081 // +--+---------------------+--+
2084 // + +---------------------+ +
2085 // from ===> |from to | <=== to
2086 // +-----+-----+-----+-----+-----+-----+-----+-----+
2087 // ### |### ### ###| ###
2088 // ##### ##### ##### ##### #####
2089 // ##### ##### ##### ##### #####
2090 // ##### ##### ##### ##### #####
2091 // ### |### ### ###| ###
2093 // + +---------------------+ +
2096 // +--+---------------------+--+
2098 Float left
= mDirtyRect
.x
- radius
- AA_MARGIN
;
2099 if (fromP
.x
< left
) {
2100 size_t tmp
= ceil(count
* (left
- start
.x
) / (end
.x
- start
.x
));
2102 // We increment by 2, so odd/even should match between before/after.
2103 if ((tmp
& 1) != (from
& 1)) {
2110 Float right
= mDirtyRect
.x
+ mDirtyRect
.width
+ radius
+ AA_MARGIN
;
2111 if (toP
.x
> right
) {
2112 size_t tmp
= floor(count
* (right
- start
.x
) / (end
.x
- start
.x
));
2114 if ((tmp
& 1) != (to
& 1)) {
2121 } else if (aSide
== eSideRight
) {
2122 Float top
= mDirtyRect
.y
- radius
- AA_MARGIN
;
2123 if (fromP
.y
< top
) {
2124 size_t tmp
= ceil(count
* (top
- start
.y
) / (end
.y
- start
.y
));
2126 if ((tmp
& 1) != (from
& 1)) {
2133 Float bottom
= mDirtyRect
.y
+ mDirtyRect
.height
+ radius
+ AA_MARGIN
;
2134 if (toP
.y
> bottom
) {
2135 size_t tmp
= floor(count
* (bottom
- start
.y
) / (end
.y
- start
.y
));
2137 if ((tmp
& 1) != (to
& 1)) {
2144 } else if (aSide
== eSideBottom
) {
2145 Float right
= mDirtyRect
.x
+ mDirtyRect
.width
+ radius
+ AA_MARGIN
;
2146 if (fromP
.x
> right
) {
2147 size_t tmp
= ceil(count
* (right
- start
.x
) / (end
.x
- start
.x
));
2149 if ((tmp
& 1) != (from
& 1)) {
2156 Float left
= mDirtyRect
.x
- radius
- AA_MARGIN
;
2158 size_t tmp
= floor(count
* (left
- start
.x
) / (end
.x
- start
.x
));
2160 if ((tmp
& 1) != (to
& 1)) {
2167 } else if (aSide
== eSideLeft
) {
2168 Float bottom
= mDirtyRect
.y
+ mDirtyRect
.height
+ radius
+ AA_MARGIN
;
2169 if (fromP
.y
> bottom
) {
2170 size_t tmp
= ceil(count
* (bottom
- start
.y
) / (end
.y
- start
.y
));
2172 if ((tmp
& 1) != (from
& 1)) {
2179 Float top
= mDirtyRect
.y
- radius
- AA_MARGIN
;
2181 size_t tmp
= floor(count
* (top
- start
.y
) / (end
.y
- start
.y
));
2183 if ((tmp
& 1) != (to
& 1)) {
2192 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2193 size_t segmentCount
= 0;
2194 for (size_t i
= from
; i
<= to
; i
+= 2) {
2195 if (segmentCount
> BORDER_SEGMENT_COUNT_MAX
) {
2196 RefPtr
<Path
> path
= builder
->Finish();
2197 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2198 builder
= mDrawTarget
->CreatePathBuilder();
2202 Point P
= (start
* (count
- i
) + end
* i
) / count
;
2203 builder
->MoveTo(Point(P
.x
+ radius
, P
.y
));
2204 builder
->Arc(P
, radius
, 0.0f
, Float(2.0 * M_PI
));
2207 RefPtr
<Path
> path
= builder
->Finish();
2208 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2210 if (mergeStart
== MERGE_HALF
|| mergeEnd
== MERGE_HALF
) {
2211 mDrawTarget
->PopClip();
2215 void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide
,
2217 // Draw dashed/dotted corner with following approach.
2220 // If both side has same border-width and border-width <= 2.0, draw dashed
2221 // line along the corner, with appropriate dash length and gap to make the
2222 // corner symmetric as far as possible. Dash length equals to the gap, and
2223 // the ratio of the dash length to border-width is the maximum value in in
2225 // Otherwise, draw dashed segments along the corner, keeping same dash
2226 // length ratio to border-width at that point.
2227 // (see DashedCornerFinder.h for more detail)
2228 // Line ends with half segments, to joint with both side easily.
2231 // If both side has same border-width and border-width <= 2.0, draw 1:1
2232 // dashed line along the corner.
2233 // Otherwise Draw circles along the corner, with appropriate gap that makes
2234 // the corner symmetric as far as possible. The size of the circle may
2235 // change along the corner, that is tangent to the outer curver and the
2236 // inner curve. The ratio of the gap to circle diameter is the maximum
2237 // value in [0.5, 1] range.
2238 // (see DottedCornerFinder.h for more detail)
2239 // Corner ends with filled dots but those dots are drawn by
2240 // DrawDashedOrDottedSide. So this may draw no circles if there's no space
2241 // between 2 dots at both ends.
2243 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
||
2244 mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
2245 "Style should be dashed or dotted.");
2247 if (IsCornerMergeable(aCorner
)) {
2248 // DrawDashedOrDottedSide will draw corner.
2252 mozilla::Side
sideH(GetHorizontalSide(aCorner
));
2253 mozilla::Side
sideV(GetVerticalSide(aCorner
));
2254 Float borderWidthH
= mBorderWidths
[sideH
];
2255 Float borderWidthV
= mBorderWidths
[sideV
];
2256 if (borderWidthH
== 0.0f
&& borderWidthV
== 0.0f
) {
2260 StyleBorderStyle styleH
= mBorderStyles
[sideH
];
2261 StyleBorderStyle styleV
= mBorderStyles
[sideV
];
2263 // Corner between dotted and others with radius=0 is drawn by side.
2264 if (IsZeroSize(mBorderRadii
[aCorner
]) &&
2265 (styleV
== StyleBorderStyle::Dotted
||
2266 styleH
== StyleBorderStyle::Dotted
)) {
2271 std::max(mBorderRadii
[aCorner
].width
, mBorderRadii
[aCorner
].height
);
2272 if (maxRadius
> BORDER_DOTTED_CORNER_MAX_RADIUS
) {
2273 DrawFallbackSolidCorner(aSide
, aCorner
);
2277 if (borderWidthH
!= borderWidthV
|| borderWidthH
> 2.0f
) {
2278 StyleBorderStyle style
= mBorderStyles
[aSide
];
2279 if (style
== StyleBorderStyle::Dotted
) {
2280 DrawDottedCornerSlow(aSide
, aCorner
);
2282 DrawDashedCornerSlow(aSide
, aCorner
);
2287 nscolor borderColor
= mBorderColors
[aSide
];
2290 // Get the start and end points of the corner arc, ensuring that any dot
2291 // origins get pushed backwards towards the edges of the corner rect to
2292 // account for stroking.
2293 points
[0] = GetStraightBorderPoint(sideH
, aCorner
, &ignored
, -0.5f
);
2294 points
[3] = GetStraightBorderPoint(sideV
, aCorner
, &ignored
, -0.5f
);
2295 // Round points to draw dot on each pixel.
2296 if (borderWidthH
< 2.0f
) {
2297 points
[0].x
= round(points
[0].x
);
2299 if (borderWidthV
< 2.0f
) {
2300 points
[3].y
= round(points
[3].y
);
2302 points
[1] = points
[0];
2303 points
[1].x
+= kKappaFactor
* (points
[3].x
- points
[0].x
);
2304 points
[2] = points
[3];
2305 points
[2].y
+= kKappaFactor
* (points
[0].y
- points
[3].y
);
2307 Float len
= GetQuarterEllipticArcLength(fabs(points
[0].x
- points
[3].x
),
2308 fabs(points
[0].y
- points
[3].y
));
2311 StrokeOptions
strokeOptions(borderWidthH
);
2312 SetupDashedOptions(&strokeOptions
, dash
, aSide
, len
, true);
2314 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2315 builder
->MoveTo(points
[0]);
2316 builder
->BezierTo(points
[1], points
[2], points
[3]);
2317 RefPtr
<Path
> path
= builder
->Finish();
2318 mDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(borderColor
)),
2322 void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide
,
2324 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
2325 "Style should be dotted.");
2327 mozilla::Side
sideH(GetHorizontalSide(aCorner
));
2328 mozilla::Side
sideV(GetVerticalSide(aCorner
));
2329 Float R0
= mBorderWidths
[sideH
] / 2.0f
;
2330 Float Rn
= mBorderWidths
[sideV
] / 2.0f
;
2331 if (R0
== 0.0f
&& Rn
== 0.0f
) {
2335 nscolor borderColor
= mBorderColors
[aSide
];
2338 GetOuterAndInnerBezier(&outerBezier
, &innerBezier
, aCorner
);
2341 Point C0
= GetStraightBorderPoint(sideH
, aCorner
, &ignored
);
2342 Point Cn
= GetStraightBorderPoint(sideV
, aCorner
, &ignored
);
2343 DottedCornerFinder
finder(outerBezier
, innerBezier
, aCorner
,
2344 mBorderRadii
[aCorner
].width
,
2345 mBorderRadii
[aCorner
].height
, C0
, R0
, Cn
, Rn
,
2346 mBorderCornerDimensions
[aCorner
]);
2348 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2349 size_t segmentCount
= 0;
2350 const Float AA_MARGIN
= 2.0f
;
2351 Rect marginedDirtyRect
= mDirtyRect
;
2352 marginedDirtyRect
.Inflate(std::max(R0
, Rn
) + AA_MARGIN
);
2353 bool entered
= false;
2354 while (finder
.HasMore()) {
2355 if (segmentCount
> BORDER_SEGMENT_COUNT_MAX
) {
2356 RefPtr
<Path
> path
= builder
->Finish();
2357 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2358 builder
= mDrawTarget
->CreatePathBuilder();
2362 DottedCornerFinder::Result result
= finder
.Next();
2364 if (marginedDirtyRect
.Contains(result
.C
) && result
.r
> 0) {
2366 builder
->MoveTo(Point(result
.C
.x
+ result
.r
, result
.C
.y
));
2367 builder
->Arc(result
.C
, result
.r
, 0, Float(2.0 * M_PI
));
2369 } else if (entered
) {
2373 RefPtr
<Path
> path
= builder
->Finish();
2374 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2377 static inline bool DashedPathOverlapsRect(Rect
& pathRect
,
2378 const Rect
& marginedDirtyRect
,
2379 DashedCornerFinder::Result
& result
) {
2380 // Calculate a rect that contains all control points of the |result| path,
2381 // and check if it intersects with |marginedDirtyRect|.
2382 pathRect
.SetRect(result
.outerSectionBezier
.mPoints
[0].x
,
2383 result
.outerSectionBezier
.mPoints
[0].y
, 0, 0);
2384 pathRect
.ExpandToEnclose(result
.outerSectionBezier
.mPoints
[1]);
2385 pathRect
.ExpandToEnclose(result
.outerSectionBezier
.mPoints
[2]);
2386 pathRect
.ExpandToEnclose(result
.outerSectionBezier
.mPoints
[3]);
2387 pathRect
.ExpandToEnclose(result
.innerSectionBezier
.mPoints
[0]);
2388 pathRect
.ExpandToEnclose(result
.innerSectionBezier
.mPoints
[1]);
2389 pathRect
.ExpandToEnclose(result
.innerSectionBezier
.mPoints
[2]);
2390 pathRect
.ExpandToEnclose(result
.innerSectionBezier
.mPoints
[3]);
2392 return pathRect
.Intersects(marginedDirtyRect
);
2395 void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide
,
2397 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
,
2398 "Style should be dashed.");
2400 mozilla::Side
sideH(GetHorizontalSide(aCorner
));
2401 mozilla::Side
sideV(GetVerticalSide(aCorner
));
2402 Float borderWidthH
= mBorderWidths
[sideH
];
2403 Float borderWidthV
= mBorderWidths
[sideV
];
2404 if (borderWidthH
== 0.0f
&& borderWidthV
== 0.0f
) {
2408 nscolor borderColor
= mBorderColors
[aSide
];
2411 GetOuterAndInnerBezier(&outerBezier
, &innerBezier
, aCorner
);
2413 DashedCornerFinder
finder(outerBezier
, innerBezier
, borderWidthH
,
2414 borderWidthV
, mBorderCornerDimensions
[aCorner
]);
2416 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2417 size_t segmentCount
= 0;
2418 const Float AA_MARGIN
= 2.0f
;
2419 Rect marginedDirtyRect
= mDirtyRect
;
2420 marginedDirtyRect
.Inflate(AA_MARGIN
);
2422 bool entered
= false;
2423 while (finder
.HasMore()) {
2424 if (segmentCount
> BORDER_SEGMENT_COUNT_MAX
) {
2425 RefPtr
<Path
> path
= builder
->Finish();
2426 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2427 builder
= mDrawTarget
->CreatePathBuilder();
2431 DashedCornerFinder::Result result
= finder
.Next();
2433 if (DashedPathOverlapsRect(pathRect
, marginedDirtyRect
, result
)) {
2435 builder
->MoveTo(result
.outerSectionBezier
.mPoints
[0]);
2436 builder
->BezierTo(result
.outerSectionBezier
.mPoints
[1],
2437 result
.outerSectionBezier
.mPoints
[2],
2438 result
.outerSectionBezier
.mPoints
[3]);
2439 builder
->LineTo(result
.innerSectionBezier
.mPoints
[3]);
2440 builder
->BezierTo(result
.innerSectionBezier
.mPoints
[2],
2441 result
.innerSectionBezier
.mPoints
[1],
2442 result
.innerSectionBezier
.mPoints
[0]);
2443 builder
->LineTo(result
.outerSectionBezier
.mPoints
[0]);
2445 } else if (entered
) {
2450 if (outerBezier
.mPoints
[0].x
!= innerBezier
.mPoints
[0].x
) {
2451 // Fill gap before the first section.
2467 // +--------------+ ^
2469 // | | innerPoint[0]
2471 builder
->MoveTo(outerBezier
.mPoints
[0]);
2472 builder
->LineTo(innerBezier
.mPoints
[0]);
2473 builder
->LineTo(Point(innerBezier
.mPoints
[0].x
, outerBezier
.mPoints
[0].y
));
2474 builder
->LineTo(outerBezier
.mPoints
[0]);
2477 if (outerBezier
.mPoints
[3].y
!= innerBezier
.mPoints
[3].y
) {
2478 // Fill gap after the last section.
2483 // | _+-----------+--
2493 // |############\ _-+--
2494 // +--------------+<-- innerPoint[3]
2498 builder
->MoveTo(outerBezier
.mPoints
[3]);
2499 builder
->LineTo(innerBezier
.mPoints
[3]);
2500 builder
->LineTo(Point(outerBezier
.mPoints
[3].x
, innerBezier
.mPoints
[3].y
));
2501 builder
->LineTo(outerBezier
.mPoints
[3]);
2504 RefPtr
<Path
> path
= builder
->Finish();
2505 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2508 void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide
,
2510 // Render too large dashed or dotted corner with solid style, to avoid hangup
2511 // inside DashedCornerFinder and DottedCornerFinder.
2513 NS_ASSERTION(mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
||
2514 mBorderStyles
[aSide
] == StyleBorderStyle::Dotted
,
2515 "Style should be dashed or dotted.");
2517 nscolor borderColor
= mBorderColors
[aSide
];
2520 GetOuterAndInnerBezier(&outerBezier
, &innerBezier
, aCorner
);
2522 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
2524 builder
->MoveTo(outerBezier
.mPoints
[0]);
2525 builder
->BezierTo(outerBezier
.mPoints
[1], outerBezier
.mPoints
[2],
2526 outerBezier
.mPoints
[3]);
2527 builder
->LineTo(innerBezier
.mPoints
[3]);
2528 builder
->BezierTo(innerBezier
.mPoints
[2], innerBezier
.mPoints
[1],
2529 innerBezier
.mPoints
[0]);
2530 builder
->LineTo(outerBezier
.mPoints
[0]);
2532 RefPtr
<Path
> path
= builder
->Finish();
2533 mDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
2535 if (!mPresContext
->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
2536 mPresContext
->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
2537 nsContentUtils::ReportToConsole(
2538 nsIScriptError::warningFlag
, "CSS"_ns
, mPresContext
->Document(),
2539 nsContentUtils::eCSS_PROPERTIES
,
2540 mBorderStyles
[aSide
] == StyleBorderStyle::Dashed
2541 ? "TooLargeDashedRadius"
2542 : "TooLargeDottedRadius");
2546 bool nsCSSBorderRenderer::AllBordersSameWidth() {
2547 if (mBorderWidths
[0] == mBorderWidths
[1] &&
2548 mBorderWidths
[0] == mBorderWidths
[2] &&
2549 mBorderWidths
[0] == mBorderWidths
[3]) {
2556 bool nsCSSBorderRenderer::AllBordersSolid() {
2557 for (const auto i
: mozilla::AllPhysicalSides()) {
2558 if (mBorderStyles
[i
] == StyleBorderStyle::Solid
||
2559 mBorderStyles
[i
] == StyleBorderStyle::None
||
2560 mBorderStyles
[i
] == StyleBorderStyle::Hidden
) {
2569 static bool IsVisible(StyleBorderStyle aStyle
) {
2570 if (aStyle
!= StyleBorderStyle::None
&& aStyle
!= StyleBorderStyle::Hidden
) {
2579 twoFloats
operator*(const Size
& aSize
) const {
2580 return {a
* aSize
.width
, b
* aSize
.height
};
2583 twoFloats
operator*(Float aScale
) const { return {a
* aScale
, b
* aScale
}; }
2585 twoFloats
operator+(const Point
& aPoint
) const {
2586 return {a
+ aPoint
.x
, b
+ aPoint
.y
};
2589 operator Point() const { return Point(a
, b
); }
2592 void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() {
2593 // Easy enough to deal with.
2594 Rect rect
= mOuterRect
;
2597 const twoFloats cornerAdjusts
[4] = {
2598 {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}};
2599 for (const auto side
: mozilla::AllPhysicalSides()) {
2600 Point firstCorner
= rect
.CCWCorner(side
) + cornerAdjusts
[side
];
2601 Point secondCorner
= rect
.CWCorner(side
) + cornerAdjusts
[side
];
2603 ColorPattern
color(ToDeviceColor(mBorderColors
[side
]));
2605 mDrawTarget
->StrokeLine(firstCorner
, secondCorner
, color
);
2609 // Intersect a ray from the inner corner to the outer corner
2610 // with the border radius, yielding the intersection point.
2611 static Point
IntersectBorderRadius(const Point
& aCenter
, const Size
& aRadius
,
2612 const Point
& aInnerCorner
,
2613 const Point
& aCornerDirection
) {
2614 Point toCorner
= aCornerDirection
;
2615 // transform to-corner ray to unit-circle space
2616 toCorner
.x
/= aRadius
.width
;
2617 toCorner
.y
/= aRadius
.height
;
2618 // normalize to-corner ray
2619 Float cornerDist
= toCorner
.Length();
2620 if (cornerDist
< 1.0e-6f
) {
2621 return aInnerCorner
;
2623 toCorner
= toCorner
/ cornerDist
;
2624 // ray from inner corner to border radius center
2625 Point toCenter
= aCenter
- aInnerCorner
;
2626 // transform to-center ray to unit-circle space
2627 toCenter
.x
/= aRadius
.width
;
2628 toCenter
.y
/= aRadius
.height
;
2629 // compute offset of intersection with border radius unit circle
2630 Float offset
= toCenter
.DotProduct(toCorner
);
2631 // compute discriminant to check for intersections
2632 Float discrim
= 1.0f
- toCenter
.DotProduct(toCenter
) + offset
* offset
;
2633 // choose farthest intersection
2634 offset
+= sqrtf(std::max(discrim
, 0.0f
));
2635 // transform to-corner ray back out of unit-circle space
2636 toCorner
.x
*= aRadius
.width
;
2637 toCorner
.y
*= aRadius
.height
;
2638 return aInnerCorner
+ toCorner
* offset
;
2641 // Calculate the split point and split angle for a border radius with
2643 static inline void SplitBorderRadius(const Point
& aCenter
, const Size
& aRadius
,
2644 const Point
& aOuterCorner
,
2645 const Point
& aInnerCorner
,
2646 const twoFloats
& aCornerMults
,
2647 Float aStartAngle
, Point
& aSplit
,
2648 Float
& aSplitAngle
) {
2649 Point cornerDir
= aOuterCorner
- aInnerCorner
;
2650 if (cornerDir
.x
== cornerDir
.y
&& aRadius
.IsSquare()) {
2651 // optimize 45-degree intersection with circle since we can assume
2652 // the circle center lies along the intersection edge
2653 aSplit
= aCenter
- aCornerMults
* (aRadius
* Float(1.0f
/ M_SQRT2
));
2654 aSplitAngle
= aStartAngle
+ 0.5f
* M_PI
/ 2.0f
;
2656 aSplit
= IntersectBorderRadius(aCenter
, aRadius
, aInnerCorner
, cornerDir
);
2657 aSplitAngle
= atan2f((aSplit
.y
- aCenter
.y
) / aRadius
.height
,
2658 (aSplit
.x
- aCenter
.x
) / aRadius
.width
);
2662 // Compute the size of the skirt needed, given the color alphas
2663 // of each corner side and the slope between them.
2664 static void ComputeCornerSkirtSize(Float aAlpha1
, Float aAlpha2
, Float aSlopeY
,
2665 Float aSlopeX
, Float
& aSizeResult
,
2666 Float
& aSlopeResult
) {
2667 // If either side is (almost) invisible or there is no diagonal edge,
2668 // then don't try to render a skirt.
2669 if (aAlpha1
< 0.01f
|| aAlpha2
< 0.01f
) {
2672 aSlopeX
= fabs(aSlopeX
);
2673 aSlopeY
= fabs(aSlopeY
);
2674 if (aSlopeX
< 1.0e-6f
|| aSlopeY
< 1.0e-6f
) {
2678 // If first and second color don't match, we need to split the corner in
2679 // half. The diagonal edges created may not have full pixel coverage given
2680 // anti-aliasing, so we need to compute a small subpixel skirt edge. This
2681 // assumes each half has half coverage to start with, and that coverage
2682 // increases as the skirt is pushed over, with the end result that we want
2683 // to roughly preserve the alpha value along this edge.
2684 // Given slope m, alphas a and A, use quadratic formula to solve for S in:
2685 // a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
2687 // S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
2688 // and substitute k = (1+m)/(2*m):
2689 // S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
2690 Float slope
= aSlopeY
/ aSlopeX
;
2691 Float slopeScale
= (1.0f
+ slope
) / (2.0f
* slope
);
2692 Float discrim
= slopeScale
* slopeScale
+
2693 (1 - aAlpha2
/ (aAlpha1
* (1.0f
- 0.49f
* aAlpha2
))) / slope
;
2695 aSizeResult
= slopeScale
- sqrtf(discrim
);
2696 aSlopeResult
= slope
;
2700 // Draws a border radius with possibly different sides.
2701 // A skirt is drawn underneath the corner intersection to hide possible
2702 // seams when anti-aliased drawing is used.
2703 static void DrawBorderRadius(
2704 DrawTarget
* aDrawTarget
, Corner c
, const Point
& aOuterCorner
,
2705 const Point
& aInnerCorner
, const twoFloats
& aCornerMultPrev
,
2706 const twoFloats
& aCornerMultNext
, const Size
& aCornerDims
,
2707 const Size
& aOuterRadius
, const Size
& aInnerRadius
,
2708 const DeviceColor
& aFirstColor
, const DeviceColor
& aSecondColor
,
2709 Float aSkirtSize
, Float aSkirtSlope
) {
2710 // Connect edge to outer arc start point
2711 Point outerCornerStart
= aOuterCorner
+ aCornerMultPrev
* aCornerDims
;
2712 // Connect edge to outer arc end point
2713 Point outerCornerEnd
= aOuterCorner
+ aCornerMultNext
* aCornerDims
;
2714 // Connect edge to inner arc start point
2715 Point innerCornerStart
=
2716 outerCornerStart
+ aCornerMultNext
* (aCornerDims
- aInnerRadius
);
2717 // Connect edge to inner arc end point
2718 Point innerCornerEnd
=
2719 outerCornerEnd
+ aCornerMultPrev
* (aCornerDims
- aInnerRadius
);
2721 // Outer arc start point
2722 Point outerArcStart
= aOuterCorner
+ aCornerMultPrev
* aOuterRadius
;
2723 // Outer arc end point
2724 Point outerArcEnd
= aOuterCorner
+ aCornerMultNext
* aOuterRadius
;
2725 // Inner arc start point
2726 Point innerArcStart
= aInnerCorner
+ aCornerMultPrev
* aInnerRadius
;
2727 // Inner arc end point
2728 Point innerArcEnd
= aInnerCorner
+ aCornerMultNext
* aInnerRadius
;
2730 // Outer radius center
2732 aOuterCorner
+ (aCornerMultPrev
+ aCornerMultNext
) * aOuterRadius
;
2733 // Inner radius center
2735 aInnerCorner
+ (aCornerMultPrev
+ aCornerMultNext
) * aInnerRadius
;
2737 RefPtr
<PathBuilder
> builder
;
2740 if (aFirstColor
.a
> 0) {
2741 builder
= aDrawTarget
->CreatePathBuilder();
2742 builder
->MoveTo(outerCornerStart
);
2745 if (aFirstColor
!= aSecondColor
) {
2746 // Start and end angles of corner quadrant
2747 Float startAngle
= (c
* M_PI
) / 2.0f
- M_PI
,
2748 endAngle
= startAngle
+ M_PI
/ 2.0f
, outerSplitAngle
, innerSplitAngle
;
2749 Point outerSplit
, innerSplit
;
2751 // Outer half-way point
2752 SplitBorderRadius(outerCenter
, aOuterRadius
, aOuterCorner
, aInnerCorner
,
2753 aCornerMultPrev
+ aCornerMultNext
, startAngle
, outerSplit
,
2755 // Inner half-way point
2756 if (aInnerRadius
.IsEmpty()) {
2757 innerSplit
= aInnerCorner
;
2758 innerSplitAngle
= endAngle
;
2760 SplitBorderRadius(innerCenter
, aInnerRadius
, aOuterCorner
, aInnerCorner
,
2761 aCornerMultPrev
+ aCornerMultNext
, startAngle
,
2762 innerSplit
, innerSplitAngle
);
2765 // Draw first half with first color
2766 if (aFirstColor
.a
> 0) {
2767 AcuteArcToBezier(builder
.get(), outerCenter
, aOuterRadius
, outerArcStart
,
2768 outerSplit
, startAngle
, outerSplitAngle
);
2769 // Draw skirt as part of first half
2770 if (aSkirtSize
> 0) {
2771 builder
->LineTo(outerSplit
+ aCornerMultNext
* aSkirtSize
);
2772 builder
->LineTo(innerSplit
-
2773 aCornerMultPrev
* (aSkirtSize
* aSkirtSlope
));
2775 AcuteArcToBezier(builder
.get(), innerCenter
, aInnerRadius
, innerSplit
,
2776 innerArcStart
, innerSplitAngle
, startAngle
);
2777 if ((innerCornerStart
- innerArcStart
).DotProduct(aCornerMultPrev
) > 0) {
2778 builder
->LineTo(innerCornerStart
);
2781 path
= builder
->Finish();
2782 aDrawTarget
->Fill(path
, ColorPattern(aFirstColor
));
2785 // Draw second half with second color
2786 if (aSecondColor
.a
> 0) {
2787 builder
= aDrawTarget
->CreatePathBuilder();
2788 builder
->MoveTo(outerCornerEnd
);
2789 if ((innerArcEnd
- innerCornerEnd
).DotProduct(aCornerMultNext
) < 0) {
2790 builder
->LineTo(innerCornerEnd
);
2792 AcuteArcToBezier(builder
.get(), innerCenter
, aInnerRadius
, innerArcEnd
,
2793 innerSplit
, endAngle
, innerSplitAngle
);
2794 AcuteArcToBezier(builder
.get(), outerCenter
, aOuterRadius
, outerSplit
,
2795 outerArcEnd
, outerSplitAngle
, endAngle
);
2797 path
= builder
->Finish();
2798 aDrawTarget
->Fill(path
, ColorPattern(aSecondColor
));
2800 } else if (aFirstColor
.a
> 0) {
2801 // Draw corner with single color
2802 AcuteArcToBezier(builder
.get(), outerCenter
, aOuterRadius
, outerArcStart
,
2804 builder
->LineTo(outerCornerEnd
);
2805 if ((innerArcEnd
- innerCornerEnd
).DotProduct(aCornerMultNext
) < 0) {
2806 builder
->LineTo(innerCornerEnd
);
2808 AcuteArcToBezier(builder
.get(), innerCenter
, aInnerRadius
, innerArcEnd
,
2809 innerArcStart
, -kKappaFactor
);
2810 if ((innerCornerStart
- innerArcStart
).DotProduct(aCornerMultPrev
) > 0) {
2811 builder
->LineTo(innerCornerStart
);
2814 path
= builder
->Finish();
2815 aDrawTarget
->Fill(path
, ColorPattern(aFirstColor
));
2819 // Draw a corner with possibly different sides.
2820 // A skirt is drawn underneath the corner intersection to hide possible
2821 // seams when anti-aliased drawing is used.
2822 static void DrawCorner(DrawTarget
* aDrawTarget
, const Point
& aOuterCorner
,
2823 const Point
& aInnerCorner
,
2824 const twoFloats
& aCornerMultPrev
,
2825 const twoFloats
& aCornerMultNext
,
2826 const Size
& aCornerDims
, const DeviceColor
& aFirstColor
,
2827 const DeviceColor
& aSecondColor
, Float aSkirtSize
,
2828 Float aSkirtSlope
) {
2829 // Corner box start point
2830 Point cornerStart
= aOuterCorner
+ aCornerMultPrev
* aCornerDims
;
2831 // Corner box end point
2832 Point cornerEnd
= aOuterCorner
+ aCornerMultNext
* aCornerDims
;
2834 RefPtr
<PathBuilder
> builder
;
2837 if (aFirstColor
.a
> 0) {
2838 builder
= aDrawTarget
->CreatePathBuilder();
2839 builder
->MoveTo(cornerStart
);
2842 if (aFirstColor
!= aSecondColor
) {
2843 // Draw first half with first color
2844 if (aFirstColor
.a
> 0) {
2845 builder
->LineTo(aOuterCorner
);
2846 // Draw skirt as part of first half
2847 if (aSkirtSize
> 0) {
2848 builder
->LineTo(aOuterCorner
+ aCornerMultNext
* aSkirtSize
);
2849 builder
->LineTo(aInnerCorner
-
2850 aCornerMultPrev
* (aSkirtSize
* aSkirtSlope
));
2852 builder
->LineTo(aInnerCorner
);
2854 path
= builder
->Finish();
2855 aDrawTarget
->Fill(path
, ColorPattern(aFirstColor
));
2858 // Draw second half with second color
2859 if (aSecondColor
.a
> 0) {
2860 builder
= aDrawTarget
->CreatePathBuilder();
2861 builder
->MoveTo(cornerEnd
);
2862 builder
->LineTo(aInnerCorner
);
2863 builder
->LineTo(aOuterCorner
);
2865 path
= builder
->Finish();
2866 aDrawTarget
->Fill(path
, ColorPattern(aSecondColor
));
2868 } else if (aFirstColor
.a
> 0) {
2869 // Draw corner with single color
2870 builder
->LineTo(aOuterCorner
);
2871 builder
->LineTo(cornerEnd
);
2872 builder
->LineTo(aInnerCorner
);
2874 path
= builder
->Finish();
2875 aDrawTarget
->Fill(path
, ColorPattern(aFirstColor
));
2879 void nsCSSBorderRenderer::DrawSolidBorder() {
2880 const twoFloats cornerMults
[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}};
2882 const twoFloats centerAdjusts
[4] = {
2883 {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}};
2885 RectCornerRadii innerRadii
;
2886 ComputeInnerRadii(mBorderRadii
, mBorderWidths
, &innerRadii
);
2888 Rect strokeRect
= mOuterRect
;
2889 strokeRect
.Deflate(Margin(mBorderWidths
[0] / 2.0, mBorderWidths
[1] / 2.0,
2890 mBorderWidths
[2] / 2.0, mBorderWidths
[3] / 2.0));
2892 for (const auto i
: mozilla::AllPhysicalSides()) {
2893 // We now draw the current side and the CW corner following it.
2894 // The CCW corner of this side was already drawn in the previous iteration.
2895 // The side will be drawn as an explicit stroke, and the CW corner will be
2896 // filled separately.
2897 // If the next side does not have a matching color, then we split the
2898 // corner into two halves, one of each side's color and draw both.
2899 // Thus, the CCW corner of the next side will end up drawn here.
2901 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
2902 Corner c
= Corner((i
+ 1) % 4);
2903 Corner prevCorner
= Corner(i
);
2905 // i+2 and i+3 respectively. These are used to index into the corner
2906 // multiplier table, and were deduced by calculating out the long form
2907 // of each corner and finding a pattern in the signs and values.
2908 int i1
= (i
+ 1) % 4;
2909 int i2
= (i
+ 2) % 4;
2910 int i3
= (i
+ 3) % 4;
2912 Float sideWidth
= 0.0f
;
2913 DeviceColor firstColor
, secondColor
;
2914 if (IsVisible(mBorderStyles
[i
]) && mBorderWidths
[i
]) {
2915 // draw the side since it is visible
2916 sideWidth
= mBorderWidths
[i
];
2917 firstColor
= ToDeviceColor(mBorderColors
[i
]);
2918 // if the next side is visible, use its color for corner
2919 secondColor
= IsVisible(mBorderStyles
[i1
]) && mBorderWidths
[i1
]
2920 ? ToDeviceColor(mBorderColors
[i1
])
2922 } else if (IsVisible(mBorderStyles
[i1
]) && mBorderWidths
[i1
]) {
2923 // assign next side's color to both corner sides
2924 firstColor
= ToDeviceColor(mBorderColors
[i1
]);
2925 secondColor
= firstColor
;
2927 // neither side is visible, so nothing to do
2931 Point outerCorner
= mOuterRect
.AtCorner(c
);
2932 Point innerCorner
= mInnerRect
.AtCorner(c
);
2934 // start and end points of border side stroke between corners
2935 Point sideStart
= mOuterRect
.AtCorner(prevCorner
) +
2936 cornerMults
[i2
] * mBorderCornerDimensions
[prevCorner
];
2937 Point sideEnd
= outerCorner
+ cornerMults
[i
] * mBorderCornerDimensions
[c
];
2938 // check if the side is visible and not inverted
2939 if (sideWidth
> 0 && firstColor
.a
> 0 &&
2940 -(sideEnd
- sideStart
).DotProduct(cornerMults
[i
]) > 0) {
2941 mDrawTarget
->StrokeLine(sideStart
+ centerAdjusts
[i
] * sideWidth
,
2942 sideEnd
+ centerAdjusts
[i
] * sideWidth
,
2943 ColorPattern(firstColor
),
2944 StrokeOptions(sideWidth
));
2947 Float skirtSize
= 0.0f
, skirtSlope
= 0.0f
;
2948 // the sides don't match, so compute a skirt
2949 if (firstColor
!= secondColor
&&
2950 mPresContext
->Type() != nsPresContext::eContext_Print
) {
2951 Point cornerDir
= outerCorner
- innerCorner
;
2952 ComputeCornerSkirtSize(
2953 firstColor
.a
, secondColor
.a
, cornerDir
.DotProduct(cornerMults
[i
]),
2954 cornerDir
.DotProduct(cornerMults
[i3
]), skirtSize
, skirtSlope
);
2957 if (!mBorderRadii
[c
].IsEmpty()) {
2958 // the corner has a border radius
2959 DrawBorderRadius(mDrawTarget
, c
, outerCorner
, innerCorner
, cornerMults
[i
],
2960 cornerMults
[i3
], mBorderCornerDimensions
[c
],
2961 mBorderRadii
[c
], innerRadii
[c
], firstColor
, secondColor
,
2962 skirtSize
, skirtSlope
);
2963 } else if (!mBorderCornerDimensions
[c
].IsEmpty()) {
2964 // a corner with no border radius
2965 DrawCorner(mDrawTarget
, outerCorner
, innerCorner
, cornerMults
[i
],
2966 cornerMults
[i3
], mBorderCornerDimensions
[c
], firstColor
,
2967 secondColor
, skirtSize
, skirtSlope
);
2972 void nsCSSBorderRenderer::DrawBorders() {
2973 if (mAllBordersSameStyle
&& (mBorderStyles
[0] == StyleBorderStyle::None
||
2974 mBorderStyles
[0] == StyleBorderStyle::Hidden
||
2975 mBorderColors
[0] == NS_RGBA(0, 0, 0, 0))) {
2976 // All borders are the same style, and the style is either none or hidden,
2977 // or the color is transparent.
2981 if (mAllBordersSameWidth
&& mBorderWidths
[0] == 0.0) {
2982 // Some of the mAllBordersSameWidth codepaths depend on the border
2983 // width being greater than zero.
2987 AutoRestoreTransform autoRestoreTransform
;
2988 Matrix mat
= mDrawTarget
->GetTransform();
2990 // Clamp the CTM to be pixel-aligned; we do this only
2991 // for translation-only matrices now, but we could do it
2992 // if the matrix has just a scale as well. We should not
2993 // do it if there's a rotation.
2994 if (mat
.HasNonTranslation()) {
2995 if (!mat
.HasNonAxisAlignedTransform()) {
2996 // Scale + transform. Avoid stroke fast-paths so that we have a chance
2997 // of snapping to pixel boundaries.
2998 mAvoidStroke
= true;
3001 mat
._31
= floor(mat
._31
+ 0.5);
3002 mat
._32
= floor(mat
._32
+ 0.5);
3003 autoRestoreTransform
.Init(mDrawTarget
);
3004 mDrawTarget
->SetTransform(mat
);
3006 // round mOuterRect and mInnerRect; they're already an integer
3007 // number of pixels apart and should stay that way after
3008 // rounding. We don't do this if there's a scale in the current transform
3009 // since this loses information that might be relevant when we're scaling.
3014 // Initial values only used when the border colors/widths are all the same:
3015 ColorPattern
color(ToDeviceColor(mBorderColors
[eSideTop
]));
3016 StrokeOptions
strokeOptions(mBorderWidths
[eSideTop
]); // stroke width
3018 // First there's a couple of 'special cases' that have specifically optimized
3019 // drawing paths, when none of these can be used we move on to the generalized
3020 // border drawing code.
3021 if (mAllBordersSameStyle
&& mAllBordersSameWidth
&&
3022 mBorderStyles
[0] == StyleBorderStyle::Solid
&& mNoBorderRadius
&&
3024 // Very simple case.
3025 Rect rect
= mOuterRect
;
3026 rect
.Deflate(mBorderWidths
[0] / 2.0);
3027 mDrawTarget
->StrokeRect(rect
, color
, strokeOptions
);
3031 if (mAllBordersSameStyle
&& mBorderStyles
[0] == StyleBorderStyle::Solid
&&
3032 !mAvoidStroke
&& !mNoBorderRadius
) {
3033 // Relatively simple case.
3034 RoundedRect
borderInnerRect(mOuterRect
, mBorderRadii
);
3035 borderInnerRect
.Deflate(mBorderWidths
[eSideTop
], mBorderWidths
[eSideBottom
],
3036 mBorderWidths
[eSideLeft
],
3037 mBorderWidths
[eSideRight
]);
3039 // Instead of stroking we just use two paths: an inner and an outer.
3040 // This allows us to draw borders that we couldn't when stroking. For
3041 // example, borders with a border width >= the border radius. (i.e. when
3042 // there are square corners on the inside)
3044 // Further, this approach can be more efficient because the backend
3045 // doesn't need to compute an offset curve to stroke the path. We know that
3046 // the rounded parts are elipses we can offset exactly and can just compute
3047 // a new cubic approximation.
3048 RefPtr
<PathBuilder
> builder
= mDrawTarget
->CreatePathBuilder();
3049 AppendRoundedRectToPath(builder
, mOuterRect
, mBorderRadii
, true);
3050 AppendRoundedRectToPath(builder
, borderInnerRect
.rect
,
3051 borderInnerRect
.corners
, false);
3052 RefPtr
<Path
> path
= builder
->Finish();
3053 mDrawTarget
->Fill(path
, color
);
3057 const bool allBordersSolid
= AllBordersSolid();
3059 // This leaves the border corners non-interpolated for single width borders.
3060 // Doing this is slightly faster and shouldn't be a problem visually.
3061 if (allBordersSolid
&& mAllBordersSameWidth
&& mBorderWidths
[0] == 1 &&
3062 mNoBorderRadius
&& !mAvoidStroke
) {
3063 DrawSingleWidthSolidBorder();
3067 if (allBordersSolid
&& !mAvoidStroke
) {
3072 PrintAsString(" mOuterRect: ");
3073 PrintAsString(mOuterRect
);
3074 PrintAsStringNewline();
3075 PrintAsString(" mInnerRect: ");
3076 PrintAsString(mInnerRect
);
3077 PrintAsStringNewline();
3078 PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n",
3079 mBorderColors
[0], mBorderColors
[1], mBorderColors
[2],
3082 // if conditioning the outside rect failed, then bail -- the outside
3083 // rect is supposed to enclose the entire border
3085 gfxRect outerRect
= ThebesRect(mOuterRect
);
3086 gfxUtils::ConditionRect(outerRect
);
3087 if (outerRect
.IsEmpty()) {
3090 mOuterRect
= ToRect(outerRect
);
3092 gfxRect innerRect
= ThebesRect(mInnerRect
);
3093 gfxUtils::ConditionRect(innerRect
);
3094 mInnerRect
= ToRect(innerRect
);
3097 SideBits dashedSides
= SideBits::eNone
;
3098 bool forceSeparateCorners
= false;
3100 for (const auto i
: mozilla::AllPhysicalSides()) {
3101 StyleBorderStyle style
= mBorderStyles
[i
];
3102 if (style
== StyleBorderStyle::Dashed
||
3103 style
== StyleBorderStyle::Dotted
) {
3104 // we need to draw things separately for dashed/dotting
3105 forceSeparateCorners
= true;
3106 dashedSides
|= static_cast<mozilla::SideBits
>(1 << i
);
3110 PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n",
3111 mAllBordersSameStyle
,
3112 static_cast<unsigned int>(dashedSides
));
3114 if (mAllBordersSameStyle
&& !forceSeparateCorners
) {
3115 /* Draw everything in one go */
3116 DrawBorderSides(SideBits::eAll
);
3117 PrintAsStringNewline("---------------- (1)");
3119 AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS
);
3121 /* We have more than one pass to go. Draw the corners separately from the
3124 // The corner is going to have negligible size if its two adjacent border
3125 // sides are only 1px wide and there is no border radius. In that case we
3126 // skip the overhead of painting the corner by setting the width or height
3127 // of the corner to zero, which effectively extends one of the corner's
3128 // adjacent border sides. We extend the longer adjacent side so that
3129 // opposite sides will be the same length, which is necessary for opposite
3130 // dashed/dotted sides to be symmetrical.
3132 // if width > height
3133 // +--+--------------+--+ +--------------------+
3135 // +--+--------------+--+ +--+--------------+--+
3137 // | | | | => | | | |
3139 // +--+--------------+--+ +--+--------------+--+
3141 // +--+--------------+--+ +--------------------+
3143 // if width <= height
3144 // +--+--------+--+ +--+--------+--+
3146 // +--+--------+--+ | +--------+ |
3150 // | | | | => | | | |
3154 // +--+--------+--+ | +--------+ |
3156 // +--+--------+--+ +--+--------+--+
3158 // Note that if we have different border widths we could end up with
3159 // opposite sides of different length. For example, if the left and
3160 // bottom borders are 2px wide instead of 1px, we will end up doing
3163 // +----+------------+--+ +----+---------------+
3165 // +----+------------+--+ +----+------------+--+
3167 // | | | | => | | | |
3169 // +----+------------+--+ +----+------------+--+
3172 // +----+------------+--+ +----+------------+--+
3174 // XXX Should we only do this optimization if |mAllBordersSameWidth| is
3177 // XXX In fact is this optimization even worth the complexity it adds to
3178 // the code? 1px wide dashed borders are not overly common, and drawing
3179 // corners for them is not that expensive.
3180 for (const auto corner
: mozilla::AllPhysicalCorners()) {
3181 const mozilla::Side sides
[2] = {mozilla::Side(corner
), PREV_SIDE(corner
)};
3183 if (!IsZeroSize(mBorderRadii
[corner
])) {
3187 if (mBorderWidths
[sides
[0]] == 1.0 && mBorderWidths
[sides
[1]] == 1.0) {
3188 if (mOuterRect
.Width() > mOuterRect
.Height()) {
3189 mBorderCornerDimensions
[corner
].width
= 0.0;
3191 mBorderCornerDimensions
[corner
].height
= 0.0;
3196 // First, the corners
3197 for (const auto corner
: mozilla::AllPhysicalCorners()) {
3198 // if there's no corner, don't do all this work for it
3199 if (IsZeroSize(mBorderCornerDimensions
[corner
])) {
3203 const int sides
[2] = {corner
, PREV_SIDE(corner
)};
3205 static_cast<SideBits
>((1 << sides
[0]) | (1 << sides
[1]));
3207 bool simpleCornerStyle
= AreBorderSideFinalStylesSame(sideBits
);
3209 // If we don't have anything complex going on in this corner,
3210 // then we can just fill the corner with a solid color, and avoid
3211 // the potentially expensive clip.
3212 if (simpleCornerStyle
&& IsZeroSize(mBorderRadii
[corner
]) &&
3213 IsSolidCornerStyle(mBorderStyles
[sides
[0]], corner
)) {
3214 sRGBColor color
= MakeBorderColor(
3215 mBorderColors
[sides
[0]],
3216 BorderColorStyleForSolidCorner(mBorderStyles
[sides
[0]], corner
));
3217 mDrawTarget
->FillRect(GetCornerRect(corner
),
3218 ColorPattern(ToDeviceColor(color
)));
3222 // clip to the corner
3223 mDrawTarget
->PushClipRect(GetCornerRect(corner
));
3225 if (simpleCornerStyle
) {
3226 // we don't need a group for this corner, the sides are the same,
3227 // but we weren't able to render just a solid block for the corner.
3228 DrawBorderSides(sideBits
);
3230 // Sides are different. We could draw using OP_ADD to
3231 // get correct color blending behaviour at the seam. We'd need
3232 // to do it in an offscreen surface to ensure that we're
3233 // always compositing on transparent black. If the colors
3234 // don't have transparency and the current destination surface
3235 // has an alpha channel, we could just clear the region and
3236 // avoid the temporary, but that situation doesn't happen all
3237 // that often in practice (we double buffer to no-alpha
3238 // surfaces). We choose just to seam though, as the performance
3239 // advantages outway the modest easthetic improvement.
3241 for (int cornerSide
= 0; cornerSide
< 2; cornerSide
++) {
3242 mozilla::Side side
= mozilla::Side(sides
[cornerSide
]);
3243 StyleBorderStyle style
= mBorderStyles
[side
];
3245 PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n",
3246 corner
, cornerSide
, side
,
3247 static_cast<int>(style
));
3249 RefPtr
<Path
> path
= GetSideClipSubPath(side
);
3250 mDrawTarget
->PushClip(path
);
3252 DrawBorderSides(static_cast<mozilla::SideBits
>(1 << side
));
3254 mDrawTarget
->PopClip();
3258 mDrawTarget
->PopClip();
3260 PrintAsStringNewline();
3263 // in the case of a single-unit border, we already munged the
3264 // corners up above; so we can just draw the top left and bottom
3265 // right sides separately, if they're the same.
3267 // We need to check for mNoBorderRadius, because when there is
3268 // one, FillSolidBorder always draws the full rounded rectangle
3269 // and expects there to be a clip in place.
3270 SideBits alreadyDrawnSides
= SideBits::eNone
;
3271 if (mOneUnitBorder
&& mNoBorderRadius
&&
3272 (dashedSides
& (SideBits::eTop
| SideBits::eLeft
)) == SideBits::eNone
) {
3273 bool tlBordersSameStyle
=
3274 AreBorderSideFinalStylesSame(SideBits::eTop
| SideBits::eLeft
);
3275 bool brBordersSameStyle
=
3276 AreBorderSideFinalStylesSame(SideBits::eBottom
| SideBits::eRight
);
3278 if (tlBordersSameStyle
) {
3279 DrawBorderSides(SideBits::eTop
| SideBits::eLeft
);
3280 alreadyDrawnSides
|= (SideBits::eTop
| SideBits::eLeft
);
3283 if (brBordersSameStyle
&&
3284 (dashedSides
& (SideBits::eBottom
| SideBits::eRight
)) ==
3286 DrawBorderSides(SideBits::eBottom
| SideBits::eRight
);
3287 alreadyDrawnSides
|= (SideBits::eBottom
| SideBits::eRight
);
3291 // We're done with the corners, now draw the sides.
3292 for (const auto side
: mozilla::AllPhysicalSides()) {
3293 // if we drew it above, skip it
3294 if (alreadyDrawnSides
& static_cast<mozilla::SideBits
>(1 << side
)) {
3298 // If there's no border on this side, skip it
3299 if (mBorderWidths
[side
] == 0.0 ||
3300 mBorderStyles
[side
] == StyleBorderStyle::Hidden
||
3301 mBorderStyles
[side
] == StyleBorderStyle::None
) {
3305 if (dashedSides
& static_cast<mozilla::SideBits
>(1 << side
)) {
3306 // Dashed sides will always draw just the part ignoring the
3307 // corners for the side, so no need to clip.
3308 DrawDashedOrDottedSide(side
);
3310 PrintAsStringNewline("---------------- (d)");
3314 // Undashed sides will currently draw the entire side,
3315 // including parts that would normally be covered by a corner,
3316 // so we need to clip.
3318 // XXX Optimization -- it would be good to make this work like
3319 // DrawDashedOrDottedSide, and have a DrawOneSide function that just
3320 // draws one side and not the corners, because then we can
3321 // avoid the potentially expensive clip.
3322 mDrawTarget
->PushClipRect(GetSideClipWithoutCornersRect(side
));
3324 DrawBorderSides(static_cast<mozilla::SideBits
>(1 << side
));
3326 mDrawTarget
->PopClip();
3328 PrintAsStringNewline("---------------- (*)");
3333 void nsCSSBorderRenderer::CreateWebRenderCommands(
3334 nsDisplayItem
* aItem
, wr::DisplayListBuilder
& aBuilder
,
3335 wr::IpcResourceUpdateQueue
& aResources
,
3336 const layers::StackingContextHelper
& aSc
) {
3337 LayoutDeviceRect outerRect
= LayoutDeviceRect::FromUnknownRect(mOuterRect
);
3338 wr::LayoutRect roundedRect
= wr::ToLayoutRect(outerRect
);
3339 wr::LayoutRect clipRect
= roundedRect
;
3340 wr::BorderSide side
[4];
3341 for (const auto i
: mozilla::AllPhysicalSides()) {
3343 wr::ToBorderSide(ToDeviceColor(mBorderColors
[i
]), mBorderStyles
[i
]);
3346 wr::BorderRadius borderRadius
= wr::ToBorderRadius(mBorderRadii
);
3349 LayoutDeviceRect localClip
=
3350 LayoutDeviceRect::FromUnknownRect(mLocalClip
.value());
3351 clipRect
= wr::ToLayoutRect(localClip
.Intersect(outerRect
));
3354 Range
<const wr::BorderSide
> wrsides(side
, 4);
3355 aBuilder
.PushBorder(roundedRect
, clipRect
, mBackfaceIsVisible
,
3356 wr::ToBorderWidths(mBorderWidths
[0], mBorderWidths
[1],
3357 mBorderWidths
[2], mBorderWidths
[3]),
3358 wrsides
, borderRadius
);
3362 Maybe
<nsCSSBorderImageRenderer
>
3363 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
3364 nsPresContext
* aPresContext
, nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
3365 const nsStyleBorder
& aStyleBorder
, const nsRect
& aDirtyRect
,
3366 Sides aSkipSides
, uint32_t aFlags
, ImgDrawResult
* aDrawResult
) {
3367 MOZ_ASSERT(aDrawResult
);
3369 if (aDirtyRect
.IsEmpty()) {
3370 *aDrawResult
= ImgDrawResult::SUCCESS
;
3374 nsImageRenderer
imgRenderer(aForFrame
, &aStyleBorder
.mBorderImageSource
,
3376 if (!imgRenderer
.PrepareImage()) {
3377 *aDrawResult
= imgRenderer
.PrepareResult();
3381 // We should always get here with the frame's border, but we may construct an
3382 // nsStyleBorder om the stack to deal with :visited and other shenaningans.
3384 // We always copy the border image and such from the non-visited one, so
3385 // there's no need to do anything with it.
3386 MOZ_ASSERT(aStyleBorder
.GetBorderImageRequest() ==
3387 aForFrame
->StyleBorder()->GetBorderImageRequest());
3389 nsCSSBorderImageRenderer
renderer(aForFrame
, aBorderArea
, aStyleBorder
,
3390 aSkipSides
, imgRenderer
);
3391 *aDrawResult
= ImgDrawResult::SUCCESS
;
3392 return Some(renderer
);
3395 ImgDrawResult
nsCSSBorderImageRenderer::DrawBorderImage(
3396 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
3397 nsIFrame
* aForFrame
, const nsRect
& aDirtyRect
) {
3398 // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3399 // in case we need it.
3400 gfxContextAutoSaveRestore autoSR
;
3402 if (!mClip
.IsEmpty()) {
3403 autoSR
.EnsureSaved(&aRenderingContext
);
3404 aRenderingContext
.Clip(NSRectToSnappedRect(
3405 mClip
, aForFrame
->PresContext()->AppUnitsPerDevPixel(),
3406 *aRenderingContext
.GetDrawTarget()));
3409 // intrinsicSize.CanComputeConcreteSize() return false means we can not
3410 // read intrinsic size from aStyleBorder.mBorderImageSource.
3411 // In this condition, we pass imageSize(a resolved size comes from
3412 // default sizing algorithm) to renderer as the viewport size.
3413 CSSSizeOrRatio intrinsicSize
= mImageRenderer
.ComputeIntrinsicSize();
3414 Maybe
<nsSize
> svgViewportSize
=
3415 intrinsicSize
.CanComputeConcreteSize() ? Nothing() : Some(mImageSize
);
3416 bool hasIntrinsicRatio
= intrinsicSize
.HasRatio();
3417 mImageRenderer
.PurgeCacheForViewportChange(svgViewportSize
,
3420 // These helper tables recharacterize the 'slice' and 'width' margins
3421 // in a more convenient form: they are the x/y/width/height coords
3422 // required for various bands of the border, and they have been transformed
3423 // to be relative to the innerRect (for 'slice') or the page (for 'border').
3424 enum { LEFT
, MIDDLE
, RIGHT
, TOP
= LEFT
, BOTTOM
= RIGHT
};
3425 const nscoord borderX
[3] = {
3427 mArea
.x
+ mWidths
.left
,
3428 mArea
.x
+ mArea
.width
- mWidths
.right
,
3430 const nscoord borderY
[3] = {
3432 mArea
.y
+ mWidths
.top
,
3433 mArea
.y
+ mArea
.height
- mWidths
.bottom
,
3435 const nscoord borderWidth
[3] = {
3437 mArea
.width
- mWidths
.left
- mWidths
.right
,
3440 const nscoord borderHeight
[3] = {
3442 mArea
.height
- mWidths
.top
- mWidths
.bottom
,
3445 const int32_t sliceX
[3] = {
3448 mImageSize
.width
- mSlice
.right
,
3450 const int32_t sliceY
[3] = {
3453 mImageSize
.height
- mSlice
.bottom
,
3455 const int32_t sliceWidth
[3] = {
3457 std::max(mImageSize
.width
- mSlice
.left
- mSlice
.right
, 0),
3460 const int32_t sliceHeight
[3] = {
3462 std::max(mImageSize
.height
- mSlice
.top
- mSlice
.bottom
, 0),
3466 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
3468 for (int i
= LEFT
; i
<= RIGHT
; i
++) {
3469 for (int j
= TOP
; j
<= BOTTOM
; j
++) {
3470 StyleBorderImageRepeat fillStyleH
, fillStyleV
;
3473 if (i
== MIDDLE
&& j
== MIDDLE
) {
3474 // Discard the middle portion unless set to fill.
3480 // The middle image's width is scaled by the same factor as the
3481 // top image unless that factor is zero or infinity, in which
3482 // case the scaling factor of the bottom is substituted, and
3483 // failing that, the width is not scaled. The height of the
3484 // middle image is scaled by the same factor as the left image
3485 // unless that factor is zero or infinity, in which case the
3486 // scaling factor of the right image is substituted, and failing
3487 // that, the height is not scaled.
3488 gfxFloat hFactor
, vFactor
;
3490 if (0 < mWidths
.left
&& 0 < mSlice
.left
) {
3491 vFactor
= gfxFloat(mWidths
.left
) / mSlice
.left
;
3492 } else if (0 < mWidths
.right
&& 0 < mSlice
.right
) {
3493 vFactor
= gfxFloat(mWidths
.right
) / mSlice
.right
;
3498 if (0 < mWidths
.top
&& 0 < mSlice
.top
) {
3499 hFactor
= gfxFloat(mWidths
.top
) / mSlice
.top
;
3500 } else if (0 < mWidths
.bottom
&& 0 < mSlice
.bottom
) {
3501 hFactor
= gfxFloat(mWidths
.bottom
) / mSlice
.bottom
;
3506 unitSize
.width
= sliceWidth
[i
] * hFactor
;
3507 unitSize
.height
= sliceHeight
[j
] * vFactor
;
3508 fillStyleH
= mRepeatModeHorizontal
;
3509 fillStyleV
= mRepeatModeVertical
;
3511 } else if (i
== MIDDLE
) { // top, bottom
3512 // Sides are always stretched to the thickness of their border,
3513 // and stretched proportionately on the other axis.
3515 if (0 < borderHeight
[j
] && 0 < sliceHeight
[j
]) {
3516 factor
= gfxFloat(borderHeight
[j
]) / sliceHeight
[j
];
3521 unitSize
.width
= sliceWidth
[i
] * factor
;
3522 unitSize
.height
= borderHeight
[j
];
3523 fillStyleH
= mRepeatModeHorizontal
;
3524 fillStyleV
= StyleBorderImageRepeat::Stretch
;
3526 } else if (j
== MIDDLE
) { // left, right
3528 if (0 < borderWidth
[i
] && 0 < sliceWidth
[i
]) {
3529 factor
= gfxFloat(borderWidth
[i
]) / sliceWidth
[i
];
3534 unitSize
.width
= borderWidth
[i
];
3535 unitSize
.height
= sliceHeight
[j
] * factor
;
3536 fillStyleH
= StyleBorderImageRepeat::Stretch
;
3537 fillStyleV
= mRepeatModeVertical
;
3540 // Corners are always stretched to fit the corner.
3541 unitSize
.width
= borderWidth
[i
];
3542 unitSize
.height
= borderHeight
[j
];
3543 fillStyleH
= StyleBorderImageRepeat::Stretch
;
3544 fillStyleV
= StyleBorderImageRepeat::Stretch
;
3547 nsRect
destArea(borderX
[i
], borderY
[j
], borderWidth
[i
], borderHeight
[j
]);
3548 nsRect
subArea(sliceX
[i
], sliceY
[j
], sliceWidth
[i
], sliceHeight
[j
]);
3549 if (subArea
.IsEmpty()) continue;
3551 nsIntRect intSubArea
= subArea
.ToOutsidePixels(AppUnitsPerCSSPixel());
3552 result
&= mImageRenderer
.DrawBorderImageComponent(
3553 aPresContext
, aRenderingContext
, aDirtyRect
, destArea
,
3554 CSSIntRect(intSubArea
.x
, intSubArea
.y
, intSubArea
.width
,
3556 fillStyleH
, fillStyleV
, unitSize
, j
* (RIGHT
+ 1) + i
,
3557 svgViewportSize
, hasIntrinsicRatio
);
3564 ImgDrawResult
nsCSSBorderImageRenderer::CreateWebRenderCommands(
3565 nsDisplayItem
* aItem
, nsIFrame
* aForFrame
,
3566 mozilla::wr::DisplayListBuilder
& aBuilder
,
3567 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
3568 const mozilla::layers::StackingContextHelper
& aSc
,
3569 mozilla::layers::RenderRootStateManager
* aManager
,
3570 nsDisplayListBuilder
* aDisplayListBuilder
) {
3571 if (!mImageRenderer
.IsReady()) {
3572 return ImgDrawResult::NOT_READY
;
3578 const int32_t appUnitsPerDevPixel
=
3579 aForFrame
->PresContext()->AppUnitsPerDevPixel();
3580 for (const auto i
: mozilla::AllPhysicalSides()) {
3581 slice
[i
] = (float)(mSlice
.Side(i
)) / appUnitsPerDevPixel
;
3582 widths
[i
] = (float)(mWidths
.Side(i
)) / appUnitsPerDevPixel
;
3584 // The outset is already taken into account by the adjustments to mArea
3585 // in our constructor. We use mArea as our dest rect so we can just supply
3586 // zero outsets to WebRender.
3590 LayoutDeviceRect destRect
=
3591 LayoutDeviceRect::FromAppUnits(mArea
, appUnitsPerDevPixel
);
3593 wr::LayoutRect dest
= wr::ToLayoutRect(destRect
);
3595 wr::LayoutRect clip
= dest
;
3596 if (!mClip
.IsEmpty()) {
3597 LayoutDeviceRect clipRect
=
3598 LayoutDeviceRect::FromAppUnits(mClip
, appUnitsPerDevPixel
);
3599 clip
= wr::ToLayoutRect(clipRect
);
3602 ImgDrawResult drawResult
= ImgDrawResult::SUCCESS
;
3603 switch (mImageRenderer
.GetType()) {
3604 case StyleImage::Tag::Rect
:
3605 case StyleImage::Tag::Url
: {
3606 RefPtr
<imgIContainer
> img
= mImageRenderer
.GetImage();
3607 if (!img
|| img
->GetType() == imgIContainer::TYPE_VECTOR
) {
3608 // Vector images will redraw each segment of the border up to 8 times.
3609 // We draw using a restricted region derived from the segment's clip and
3610 // scale the image accordingly (see ClippedImage::Draw). If we follow
3611 // this convention as is for WebRender, we will need to rasterize the
3612 // entire vector image scaled up without the restriction region, which
3613 // means our main thread CPU and memory footprints will be much higher.
3614 // Ideally we would be able to provide a raster image for each segment
3615 // of the border. For now we use fallback.
3616 return ImgDrawResult::NOT_SUPPORTED
;
3619 uint32_t flags
= aDisplayListBuilder
->GetImageDecodeFlags();
3621 LayoutDeviceRect imageRect
= LayoutDeviceRect::FromAppUnits(
3622 nsRect(nsPoint(), mImageRenderer
.GetSize()), appUnitsPerDevPixel
);
3624 Maybe
<SVGImageContext
> svgContext
;
3625 Maybe
<ImageIntRegion
> region
;
3626 gfx::IntSize decodeSize
=
3627 nsLayoutUtils::ComputeImageContainerDrawingParameters(
3628 img
, aForFrame
, imageRect
, imageRect
, aSc
, flags
, svgContext
,
3631 RefPtr
<layers::ImageContainer
> container
;
3632 drawResult
= img
->GetImageContainerAtSize(
3633 aManager
->LayerManager(), decodeSize
, svgContext
, region
, flags
,
3634 getter_AddRefs(container
));
3639 mozilla::wr::ImageRendering rendering
= wr::ToImageRendering(
3640 nsLayoutUtils::GetSamplingFilterForFrame(aItem
->Frame()));
3642 Maybe
<wr::ImageKey
> key
= aManager
->CommandBuilder().CreateImageKey(
3643 aItem
, container
, aBuilder
, aResources
, rendering
, aSc
, size
,
3645 if (key
.isNothing()) {
3650 float epsilon
= 0.0001;
3651 bool noVerticalBorders
= widths
[0] <= epsilon
&& widths
[2] < epsilon
;
3652 bool noHorizontalBorders
= widths
[1] <= epsilon
&& widths
[3] < epsilon
;
3654 // Border image with no border. It's a little silly but WebRender
3655 // currently does not handle this. We could fall back to a blob image
3656 // but there are reftests that are sensible to the test going through a
3657 // blob while the reference doesn't.
3658 if (noVerticalBorders
&& noHorizontalBorders
) {
3659 aBuilder
.PushImage(dest
, clip
, !aItem
->BackfaceIsHidden(), rendering
,
3664 // Fall-back if we want to fill the middle area and opposite edges are
3666 // TODO(bug 1609893): moving some of the repetition handling code out
3667 // of the image shader will make it easier to handle these cases
3669 if (noHorizontalBorders
|| noVerticalBorders
) {
3670 return ImgDrawResult::NOT_SUPPORTED
;
3674 wr::WrBorderImage params
{
3675 wr::ToBorderWidths(widths
[0], widths
[1], widths
[2], widths
[3]),
3677 mImageSize
.width
/ appUnitsPerDevPixel
,
3678 mImageSize
.height
/ appUnitsPerDevPixel
,
3680 wr::ToDeviceIntSideOffsets(slice
[0], slice
[1], slice
[2], slice
[3]),
3681 wr::ToLayoutSideOffsets(outset
[0], outset
[1], outset
[2], outset
[3]),
3682 wr::ToRepeatMode(mRepeatModeHorizontal
),
3683 wr::ToRepeatMode(mRepeatModeVertical
)};
3685 aBuilder
.PushBorderImage(dest
, clip
, !aItem
->BackfaceIsHidden(), params
);
3688 case StyleImage::Tag::Gradient
: {
3689 const StyleGradient
& gradient
= *mImageRenderer
.GetGradientData();
3690 nsCSSGradientRenderer renderer
= nsCSSGradientRenderer::Create(
3691 aForFrame
->PresContext(), aForFrame
->Style(), gradient
, mImageSize
);
3693 wr::ExtendMode extendMode
;
3694 nsTArray
<wr::GradientStop
> stops
;
3695 LayoutDevicePoint lineStart
;
3696 LayoutDevicePoint lineEnd
;
3697 LayoutDeviceSize gradientRadius
;
3698 LayoutDevicePoint gradientCenter
;
3699 float gradientAngle
;
3700 renderer
.BuildWebRenderParameters(1.0, extendMode
, stops
, lineStart
,
3701 lineEnd
, gradientRadius
, gradientCenter
,
3704 if (gradient
.IsLinear()) {
3705 LayoutDevicePoint startPoint
=
3706 LayoutDevicePoint(dest
.origin
.x
, dest
.origin
.y
) + lineStart
;
3707 LayoutDevicePoint endPoint
=
3708 LayoutDevicePoint(dest
.origin
.x
, dest
.origin
.y
) + lineEnd
;
3710 aBuilder
.PushBorderGradient(
3711 dest
, clip
, !aItem
->BackfaceIsHidden(),
3712 wr::ToBorderWidths(widths
[0], widths
[1], widths
[2], widths
[3]),
3713 (float)(mImageSize
.width
) / appUnitsPerDevPixel
,
3714 (float)(mImageSize
.height
) / appUnitsPerDevPixel
, mFill
,
3715 wr::ToDeviceIntSideOffsets(slice
[0], slice
[1], slice
[2], slice
[3]),
3716 wr::ToLayoutPoint(startPoint
), wr::ToLayoutPoint(endPoint
), stops
,
3718 wr::ToLayoutSideOffsets(outset
[0], outset
[1], outset
[2],
3720 } else if (gradient
.IsRadial()) {
3721 aBuilder
.PushBorderRadialGradient(
3722 dest
, clip
, !aItem
->BackfaceIsHidden(),
3723 wr::ToBorderWidths(widths
[0], widths
[1], widths
[2], widths
[3]),
3724 mFill
, wr::ToLayoutPoint(lineStart
),
3725 wr::ToLayoutSize(gradientRadius
), stops
, extendMode
,
3726 wr::ToLayoutSideOffsets(outset
[0], outset
[1], outset
[2],
3729 MOZ_ASSERT(gradient
.IsConic());
3730 aBuilder
.PushBorderConicGradient(
3731 dest
, clip
, !aItem
->BackfaceIsHidden(),
3732 wr::ToBorderWidths(widths
[0], widths
[1], widths
[2], widths
[3]),
3733 mFill
, wr::ToLayoutPoint(gradientCenter
), gradientAngle
, stops
,
3735 wr::ToLayoutSideOffsets(outset
[0], outset
[1], outset
[2],
3741 MOZ_ASSERT_UNREACHABLE("Unsupport border image type");
3742 drawResult
= ImgDrawResult::NOT_SUPPORTED
;
3748 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
3749 const nsCSSBorderImageRenderer
& aRhs
)
3750 : mImageRenderer(aRhs
.mImageRenderer
),
3751 mImageSize(aRhs
.mImageSize
),
3752 mSlice(aRhs
.mSlice
),
3753 mWidths(aRhs
.mWidths
),
3754 mImageOutset(aRhs
.mImageOutset
),
3757 mRepeatModeHorizontal(aRhs
.mRepeatModeHorizontal
),
3758 mRepeatModeVertical(aRhs
.mRepeatModeVertical
),
3760 Unused
<< mImageRenderer
.PrepareResult();
3763 nsCSSBorderImageRenderer
& nsCSSBorderImageRenderer::operator=(
3764 const nsCSSBorderImageRenderer
& aRhs
) {
3765 mImageRenderer
= aRhs
.mImageRenderer
;
3766 mImageSize
= aRhs
.mImageSize
;
3767 mSlice
= aRhs
.mSlice
;
3768 mWidths
= aRhs
.mWidths
;
3769 mImageOutset
= aRhs
.mImageOutset
;
3772 mRepeatModeHorizontal
= aRhs
.mRepeatModeHorizontal
;
3773 mRepeatModeVertical
= aRhs
.mRepeatModeVertical
;
3775 Unused
<< mImageRenderer
.PrepareResult();
3780 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
3781 nsIFrame
* aForFrame
, const nsRect
& aBorderArea
,
3782 const nsStyleBorder
& aStyleBorder
, Sides aSkipSides
,
3783 const nsImageRenderer
& aImageRenderer
)
3784 : mImageRenderer(aImageRenderer
) {
3785 // Determine the border image area, which by default corresponds to the
3786 // border box but can be modified by 'border-image-outset'.
3787 // Note that 'border-radius' do not apply to 'border-image' borders per
3788 // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3789 nsMargin
borderWidths(aStyleBorder
.GetComputedBorder());
3790 mImageOutset
= aStyleBorder
.GetImageOutset();
3791 if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder
) &&
3792 !aSkipSides
.IsEmpty()) {
3793 mArea
= nsCSSRendering::BoxDecorationRectForBorder(
3794 aForFrame
, aBorderArea
, aSkipSides
, &aStyleBorder
);
3795 if (mArea
.IsEqualEdges(aBorderArea
)) {
3796 // No need for a clip, just skip the sides we don't want.
3797 borderWidths
.ApplySkipSides(aSkipSides
);
3798 mImageOutset
.ApplySkipSides(aSkipSides
);
3799 mArea
.Inflate(mImageOutset
);
3801 // We're drawing borders around the joined continuation boxes so we need
3802 // to clip that to the slice that we want for this frame.
3803 mArea
.Inflate(mImageOutset
);
3804 mImageOutset
.ApplySkipSides(aSkipSides
);
3805 mClip
= aBorderArea
;
3806 mClip
.Inflate(mImageOutset
);
3809 mArea
= aBorderArea
;
3810 mArea
.Inflate(mImageOutset
);
3813 // Calculate the image size used to compute slice points.
3814 CSSSizeOrRatio intrinsicSize
= mImageRenderer
.ComputeIntrinsicSize();
3815 mImageSize
= nsImageRenderer::ComputeConcreteSize(
3816 CSSSizeOrRatio(), intrinsicSize
, mArea
.Size());
3817 mImageRenderer
.SetPreferredSize(intrinsicSize
, mImageSize
);
3819 // Compute the used values of 'border-image-slice' and 'border-image-width';
3820 // we do them together because the latter can depend on the former.
3823 for (const auto s
: mozilla::AllPhysicalSides()) {
3824 const auto& slice
= aStyleBorder
.mBorderImageSlice
.offsets
.Get(s
);
3825 int32_t imgDimension
=
3826 SideIsVertical(s
) ? mImageSize
.width
: mImageSize
.height
;
3827 nscoord borderDimension
= SideIsVertical(s
) ? mArea
.width
: mArea
.height
;
3829 if (slice
.IsNumber()) {
3830 value
= nsPresContext::CSSPixelsToAppUnits(NS_lround(slice
.AsNumber()));
3832 MOZ_ASSERT(slice
.IsPercentage());
3833 value
= slice
.AsPercentage()._0
* imgDimension
;
3838 if (value
> imgDimension
) {
3839 value
= imgDimension
;
3841 mSlice
.Side(s
) = value
;
3843 const auto& width
= aStyleBorder
.mBorderImageWidth
.Get(s
);
3844 switch (width
.tag
) {
3845 case StyleBorderImageSideWidth::Tag::LengthPercentage
:
3847 std::max(0, width
.AsLengthPercentage().Resolve(borderDimension
));
3849 case StyleBorderImageSideWidth::Tag::Number
:
3850 value
= width
.AsNumber() * borderWidths
.Side(s
);
3852 case StyleBorderImageSideWidth::Tag::Auto
:
3853 value
= mSlice
.Side(s
);
3856 MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area");
3860 // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3861 // because we expect value to be non-negative.
3862 MOZ_ASSERT(value
>= 0);
3863 mWidths
.Side(s
) = NSToCoordRoundWithClamp(value
);
3864 MOZ_ASSERT(mWidths
.Side(s
) >= 0);
3867 // "If two opposite border-image-width offsets are large enough that they
3868 // overlap, their used values are proportionately reduced until they no
3870 uint32_t combinedBorderWidth
=
3871 uint32_t(mWidths
.left
) + uint32_t(mWidths
.right
);
3872 double scaleX
= combinedBorderWidth
> uint32_t(mArea
.width
)
3873 ? mArea
.width
/ double(combinedBorderWidth
)
3875 uint32_t combinedBorderHeight
=
3876 uint32_t(mWidths
.top
) + uint32_t(mWidths
.bottom
);
3877 double scaleY
= combinedBorderHeight
> uint32_t(mArea
.height
)
3878 ? mArea
.height
/ double(combinedBorderHeight
)
3880 double scale
= std::min(scaleX
, scaleY
);
3882 mWidths
.left
*= scale
;
3883 mWidths
.right
*= scale
;
3884 mWidths
.top
*= scale
;
3885 mWidths
.bottom
*= scale
;
3886 NS_ASSERTION(mWidths
.left
+ mWidths
.right
<= mArea
.width
&&
3887 mWidths
.top
+ mWidths
.bottom
<= mArea
.height
,
3888 "rounding error in width reduction???");
3891 mRepeatModeHorizontal
= aStyleBorder
.mBorderImageRepeatH
;
3892 mRepeatModeVertical
= aStyleBorder
.mBorderImageRepeatV
;
3893 mFill
= aStyleBorder
.mBorderImageSlice
.fill
;