Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCSSRenderingBorders.h"
#include "gfxUtils.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "BorderConsts.h"
#include "DashedCornerFinder.h"
#include "DottedCornerFinder.h"
#include "ImageRegion.h"
#include "nsLayoutUtils.h"
#include "nsStyleConsts.h"
#include "nsContentUtils.h"
#include "nsCSSColorUtils.h"
#include "nsCSSRendering.h"
#include "nsCSSRenderingGradients.h"
#include "nsDisplayList.h"
#include "nsExpirationTracker.h"
#include "nsIScriptError.h"
#include "nsClassHashtable.h"
#include "nsPresContext.h"
#include "nsStyleStruct.h"
#include "gfx2DGlue.h"
#include "gfxGradientCache.h"
#include "mozilla/image/WebRenderImageProvider.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/Range.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
#define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
/**
* nsCSSRendering::PaintBorder
* nsCSSRendering::PaintOutline
* -> DrawBorders
*
* DrawBorders
* -> Ability to use specialized approach?
* |- Draw using specialized function
* |- separate corners?
* |- dashed side mask
* |
* -> can border be drawn in 1 pass? (e.g., solid border same color all
* around)
* |- DrawBorderSides with all 4 sides
* -> more than 1 pass?
* |- for each corner
* |- clip to DoCornerClipSubPath
* |- for each side adjacent to corner
* |- clip to GetSideClipSubPath
* |- DrawBorderSides with one side
* |- for each side
* |- GetSideClipWithoutCornersRect
* |- DrawDashedOrDottedSide || DrawBorderSides with one side
*/
static void ComputeBorderCornerDimensions(const Float* aBorderWidths,
const RectCornerRadii& aRadii,
RectCornerRadii* aDimsResult);
// given a side index, get the previous and next side index
#define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
#define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
// given a corner index, get the previous and next corner index
#define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
#define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
// from the given base color and the background color, turn
// color into a color for the given border pattern style
static sRGBColor MakeBorderColor(nscolor aColor,
BorderColorStyle aBorderColorStyle);
// Given a line index (an index starting from the outside of the
// border going inwards) and an array of line styles, calculate the
// color that that stripe of the border should be rendered in.
static sRGBColor ComputeColorForLine(uint32_t aLineIndex,
const BorderColorStyle* aBorderColorStyle,
uint32_t aBorderColorStyleCount,
nscolor aBorderColor);
// little helper function to check if the array of 4 floats given are
// equal to the given value
static bool CheckFourFloatsEqual(const Float* vals, Float k) {
return (vals[0] == k && vals[1] == k && vals[2] == k && vals[3] == k);
}
static bool IsZeroSize(const Size& sz) {
return sz.width == 0.0 || sz.height == 0.0;
}
/* static */
bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) {
return IsZeroSize(corners[eCornerTopLeft]) &&
IsZeroSize(corners[eCornerTopRight]) &&
IsZeroSize(corners[eCornerBottomRight]) &&
IsZeroSize(corners[eCornerBottomLeft]);
}
static mozilla::Side GetHorizontalSide(Corner aCorner) {
return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom;
}
static mozilla::Side GetVerticalSide(Corner aCorner) {
return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight;
}
static Corner GetCWCorner(mozilla::Side aSide) {
return Corner(NEXT_SIDE(aSide));
}
static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); }
static bool IsSingleSide(mozilla::SideBits aSides) {
return aSides == SideBits::eTop || aSides == SideBits::eRight ||
aSides == SideBits::eBottom || aSides == SideBits::eLeft;
}
static bool IsHorizontalSide(mozilla::Side aSide) {
return aSide == eSideTop || aSide == eSideBottom;
}
typedef enum {
// Normal solid square corner. Will be rectangular, the size of the
// adjacent sides. If the corner has a border radius, the corner
// will always be solid, since we don't do dotted/dashed etc.
CORNER_NORMAL,
// Paint the corner in whatever style is not dotted/dashed of the
// adjacent corners.
CORNER_SOLID,
// Paint the corner as a dot, the size of the bigger of the adjacent
// sides.
CORNER_DOT
} CornerStyle;
nsCSSBorderRenderer::nsCSSBorderRenderer(
nsPresContext* aPresContext, DrawTarget* aDrawTarget,
const Rect& aDirtyRect, Rect& aOuterRect,
const StyleBorderStyle* aBorderStyles, const Float* aBorderWidths,
RectCornerRadii& aBorderRadii, const nscolor* aBorderColors,
bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect)
: mPresContext(aPresContext),
mDrawTarget(aDrawTarget),
mDirtyRect(aDirtyRect),
mOuterRect(aOuterRect),
mBorderRadii(aBorderRadii),
mBackfaceIsVisible(aBackfaceIsVisible),
mLocalClip(aClipRect) {
PodCopy(mBorderStyles, aBorderStyles, 4);
PodCopy(mBorderWidths, aBorderWidths, 4);
PodCopy(mBorderColors, aBorderColors, 4);
mInnerRect = mOuterRect;
mInnerRect.Deflate(Margin(
mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths[0] : 0,
mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths[1] : 0,
mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths[2] : 0,
mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths[3] : 0));
ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii,
&mBorderCornerDimensions);
mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0);
mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll);
mAllBordersSameWidth = AllBordersSameWidth();
mAvoidStroke = false;
}
/* static */
void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
const Float* aBorderSizes,
RectCornerRadii* aInnerRadiiRet) {
RectCornerRadii& iRadii = *aInnerRadiiRet;
iRadii[C_TL].width =
std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]);
iRadii[C_TL].height =
std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]);
iRadii[C_TR].width =
std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]);
iRadii[C_TR].height =
std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]);
iRadii[C_BR].width =
std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]);
iRadii[C_BR].height =
std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]);
iRadii[C_BL].width =
std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]);
iRadii[C_BL].height =
std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]);
}
/* static */
void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
const Float* aBorderSizes,
RectCornerRadii* aOuterRadiiRet) {
RectCornerRadii& oRadii = *aOuterRadiiRet;
// default all corners to sharp corners
oRadii = RectCornerRadii(0.f);
// round the edges that have radii > 0.0 to start with
if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) {
oRadii[C_TL].width =
std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]);
oRadii[C_TL].height =
std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]);
}
if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) {
oRadii[C_TR].width =
std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]);
oRadii[C_TR].height =
std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]);
}
if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) {
oRadii[C_BR].width =
std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]);
oRadii[C_BR].height =
std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]);
}
if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) {
oRadii[C_BL].width =
std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]);
oRadii[C_BL].height =
std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]);
}
}
/*static*/ void ComputeBorderCornerDimensions(const Float* aBorderWidths,
const RectCornerRadii& aRadii,
RectCornerRadii* aDimsRet) {
Float leftWidth = aBorderWidths[eSideLeft];
Float topWidth = aBorderWidths[eSideTop];
Float rightWidth = aBorderWidths[eSideRight];
Float bottomWidth = aBorderWidths[eSideBottom];
if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) {
// These will always be in pixel units from CSS
(*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
(*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
(*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
(*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
} else {
// Always round up to whole pixels for the corners; it's safe to
// make the corners bigger than necessary, and this way we ensure
// that we avoid seams.
(*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
ceil(std::max(topWidth, aRadii[C_TL].height)));
(*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
ceil(std::max(topWidth, aRadii[C_TR].height)));
(*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
ceil(std::max(bottomWidth, aRadii[C_BR].height)));
(*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
ceil(std::max(bottomWidth, aRadii[C_BL].height)));
}
}
bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame(
mozilla::SideBits aSides) {
NS_ASSERTION(aSides != SideBits::eNone &&
(aSides & ~SideBits::eAll) == SideBits::eNone,
"AreBorderSidesSame: invalid whichSides!");
/* First check if the specified styles and colors are the same for all sides
*/
int firstStyle = 0;
for (const auto i : mozilla::AllPhysicalSides()) {
if (firstStyle == i) {
if ((static_cast<mozilla::SideBits>(1 << i) & aSides) ==
SideBits::eNone) {
firstStyle++;
}
continue;
}
if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) {
continue;
}
if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
mBorderColors[firstStyle] != mBorderColors[i]) {
return false;
}
}
/* Then if it's one of the two-tone styles and we're not
* just comparing the TL or BR sides */
switch (mBorderStyles[firstStyle]) {
case StyleBorderStyle::Groove:
case StyleBorderStyle::Ridge:
case StyleBorderStyle::Inset:
case StyleBorderStyle::Outset:
return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) ==
SideBits::eNone ||
(aSides & ~(SideBits::eBottom | SideBits::eRight)) ==
SideBits::eNone);
default:
return true;
}
}
bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle,
Corner aCorner) {
switch (aStyle) {
case StyleBorderStyle::Solid:
return true;
case StyleBorderStyle::Inset:
case StyleBorderStyle::Outset:
return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
case StyleBorderStyle::Groove:
case StyleBorderStyle::Ridge:
return mOneUnitBorder &&
(aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
case StyleBorderStyle::Double:
return mOneUnitBorder;
default:
return false;
}
}
bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) {
// Corner between dotted borders with same width and small radii is
// merged into single dot.
//
// widthH / 2.0
// |<---------->|
// | |
// |radius.width|
// |<--->| |
// | | |
// | _+------+------------+-----
// | / ###|### |
// |/ #######|####### |
// + #########|######### |
// | ##########|########## |
// | ###########|########### |
// | ###########|########### |
// |############|############|
// +------------+############|
// |#########################|
// | ####################### |
// | ####################### |
// | ##################### |
// | ################### |
// | ############### |
// | ####### |
// +-------------------------+----
// | |
// | |
mozilla::Side sideH(GetHorizontalSide(aCorner));
mozilla::Side sideV(GetVerticalSide(aCorner));
StyleBorderStyle styleH = mBorderStyles[sideH];
StyleBorderStyle styleV = mBorderStyles[sideV];
if (styleH != styleV || styleH != StyleBorderStyle::Dotted) {
return false;
}
Float widthH = mBorderWidths[sideH];
Float widthV = mBorderWidths[sideV];
if (widthH != widthV) {
return false;
}
Size radius = mBorderRadii[aCorner];
return IsZeroSize(radius) ||
(radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
}
BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner(
StyleBorderStyle aStyle, Corner aCorner) {
// note that this function assumes that the corner is already solid,
// as per the earlier function
switch (aStyle) {
case StyleBorderStyle::Solid:
case StyleBorderStyle::Double:
return BorderColorStyleSolid;
case StyleBorderStyle::Inset:
case StyleBorderStyle::Groove:
if (aCorner == eCornerTopLeft) {
return BorderColorStyleDark;
} else if (aCorner == eCornerBottomRight) {
return BorderColorStyleLight;
}
break;
case StyleBorderStyle::Outset:
case StyleBorderStyle::Ridge:
if (aCorner == eCornerTopLeft) {
return BorderColorStyleLight;
} else if (aCorner == eCornerBottomRight) {
return BorderColorStyleDark;
}
break;
default:
return BorderColorStyleNone;
}
return BorderColorStyleNone;
}
Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) {
Point offset(0.f, 0.f);
if (aCorner == C_TR || aCorner == C_BR)
offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
if (aCorner == C_BR || aCorner == C_BL)
offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]);
}
Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) {
Point offset(0.f, 0.f);
// The offset from the outside rect to the start of this side's
// box. For the top and bottom sides, the height of the box
// must be the border height; the x start must take into account
// the corner size (which may be bigger than the right or left
// side's width). The same applies to the right and left sides.
if (aSide == eSideTop) {
offset.x = mBorderCornerDimensions[C_TL].width;
} else if (aSide == eSideRight) {
offset.x = mOuterRect.Width() - mBorderWidths[eSideRight];
offset.y = mBorderCornerDimensions[C_TR].height;
} else if (aSide == eSideBottom) {
offset.x = mBorderCornerDimensions[C_BL].width;
offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom];
} else if (aSide == eSideLeft) {
offset.y = mBorderCornerDimensions[C_TL].height;
}
// The sum of the width & height of the corners adjacent to the
// side. This relies on the relationship between side indexing and
// corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
// with both proceeding clockwise.
Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] +
mBorderCornerDimensions[GetCWCorner(aSide)];
Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum);
if (IsHorizontalSide(aSide))
rect.height = mBorderWidths[aSide];
else
rect.width = mBorderWidths[aSide];
return rect;
}
// The side border type and the adjacent border types are
// examined and one of the different types of clipping (listed
// below) is selected.
typedef enum {
// clip to the trapezoid formed by the corners of the
// inner and outer rectangles for the given side
//
// +---------------
// |\%%%%%%%%%%%%%%
// | \%%%%%%%%%%%%
// | \%%%%%%%%%%%
// | \%%%%%%%%%
// | +--------
// | |
// | |
SIDE_CLIP_TRAPEZOID,
// clip to the trapezoid formed by the outer rectangle
// corners and the center of the region, making sure
// that diagonal lines all go directly from the outside
// corner to the inside corner, but that they then continue on
// to the middle.
//
// This is needed for correctly clipping rounded borders,
// which might extend past the SIDE_CLIP_TRAPEZOID trap.
//
// +-------__--+---
// \%%%%_-%%%%%%%%
// \+-%%%%%%%%%%
// / \%%%%%%%%%%
// / \%%%%%%%%%
// | +%%_-+---
// | +%%%%%%
// | / \%%%%%
// + + \%%%
// | | +-
SIDE_CLIP_TRAPEZOID_FULL,
// clip to the rectangle formed by the given side including corner.
// This is used by the non-dotted side next to dotted side.
//
// +---------------
// |%%%%%%%%%%%%%%%
// |%%%%%%%%%%%%%%%
// |%%%%%%%%%%%%%%%
// |%%%%%%%%%%%%%%%
// +------+--------
// | |
// | |
SIDE_CLIP_RECTANGLE_CORNER,
// clip to the rectangle formed by the given side excluding corner.
// This is used by the dotted side next to non-dotted side.
//
// +------+--------
// | |%%%%%%%%
// | |%%%%%%%%
// | |%%%%%%%%
// | |%%%%%%%%
// | +--------
// | |
// | |
SIDE_CLIP_RECTANGLE_NO_CORNER,
} SideClipType;
// Given three points, p0, p1, and midPoint, move p1 further in to the
// rectangle (of which aMidPoint is the center) so that it reaches the
// closer of the horizontal or vertical lines intersecting the midpoint,
// while maintaing the slope of the line. If p0 and p1 are the same,
// just move p1 to midPoint (since there's no slope to maintain).
// FIXME: Extending only to the midpoint isn't actually sufficient for
// boxes with asymmetric radii.
static void MaybeMoveToMidPoint(Point& aP0, Point& aP1,
const Point& aMidPoint) {
Point ps = aP1 - aP0;
if (ps.x == 0.0) {
if (ps.y == 0.0) {
aP1 = aMidPoint;
} else {
aP1.y = aMidPoint.y;
}
} else {
if (ps.y == 0.0) {
aP1.x = aMidPoint.x;
} else {
Float k =
std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y);
aP1 = aP0 + ps * k;
}
}
}
already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath(
mozilla::Side aSide) {
// the clip proceeds clockwise from the top left corner;
// so "start" in each case is the start of the region from that side.
//
// the final path will be formed like:
// s0 ------- e0
// | /
// s1 ----- e1
//
// that is, the second point will always be on the inside
Point start[2];
Point end[2];
#define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted)
bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
#undef IS_DOTTED
SideClipType startType = SIDE_CLIP_TRAPEZOID;
SideClipType endType = SIDE_CLIP_TRAPEZOID;
if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
startType = SIDE_CLIP_TRAPEZOID_FULL;
} else if (startIsDotted && !isDotted) {
startType = SIDE_CLIP_RECTANGLE_CORNER;
} else if (!startIsDotted && isDotted) {
startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
}
if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
endType = SIDE_CLIP_TRAPEZOID_FULL;
} else if (endIsDotted && !isDotted) {
endType = SIDE_CLIP_RECTANGLE_CORNER;
} else if (!endIsDotted && isDotted) {
endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
}
Point midPoint = mInnerRect.Center();
start[0] = mOuterRect.CCWCorner(aSide);
start[1] = mInnerRect.CCWCorner(aSide);
end[0] = mOuterRect.CWCorner(aSide);
end[1] = mInnerRect.CWCorner(aSide);
if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
MaybeMoveToMidPoint(start[0], start[1], midPoint);
} else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
if (IsHorizontalSide(aSide)) {
start[1] =
Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
} else {
start[1] =
Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
}
} else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
if (IsHorizontalSide(aSide)) {
start[0] =
Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
} else {
start[0] =
Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
}
}
if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
MaybeMoveToMidPoint(end[0], end[1], midPoint);
} else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
if (IsHorizontalSide(aSide)) {
end[1] =
Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
} else {
end[1] =
Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
}
} else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
if (IsHorizontalSide(aSide)) {
end[0] =
Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
} else {
end[0] =
Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
}
}
RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
builder->MoveTo(start[0]);
builder->LineTo(end[0]);
builder->LineTo(end[1]);
builder->LineTo(start[1]);
builder->Close();
return builder->Finish();
}
Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
Corner aCorner,
bool* aIsUnfilled,
Float aDotOffset) {
// Calculate the end point of the side for dashed/dotted border, that is also
// the end point of the corner curve. The point is specified by aSide and
// aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
//
//
// aCorner aSide
// +--------------------
// |
// |
// | +----------
// | the end point
// |
// | +----------
// | |
// | |
// | |
//
// The position of the point depends on the border-style, border-width, and
// border-radius of the side, corner, and the adjacent side beyond the corner,
// to make those sides (and corner) interact well.
//
// If the style of aSide is dotted and the dot at the point should be
// unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
const Float signsList[4][2] = {
{+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
const Float(&signs)[2] = signsList[aCorner];
*aIsUnfilled = false;
Point P = mOuterRect.AtCorner(aCorner);
StyleBorderStyle style = mBorderStyles[aSide];
Float borderWidth = mBorderWidths[aSide];
Size dim = mBorderCornerDimensions[aCorner];
bool isHorizontal = IsHorizontalSide(aSide);
//
// aCorner aSide
// +--------------
// |
// | +----------
// | |
// otherSide | |
// | |
mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
? PREV_SIDE(aSide)
: NEXT_SIDE(aSide);
StyleBorderStyle otherStyle = mBorderStyles[otherSide];
Float otherBorderWidth = mBorderWidths[otherSide];
Size radius = mBorderRadii[aCorner];
if (IsZeroSize(radius)) {
radius.width = 0.0f;
radius.height = 0.0f;
}
if (style == StyleBorderStyle::Dotted) {
// Offset the dot's location along the side toward the corner by a
// multiple of its width.
if (isHorizontal) {
P.x -= signs[0] * aDotOffset * borderWidth;
} else {
P.y -= signs[1] * aDotOffset * borderWidth;
}
}
if (style == StyleBorderStyle::Dotted &&
otherStyle == StyleBorderStyle::Dotted) {
if (borderWidth == otherBorderWidth) {
if (radius.width < borderWidth / 2.0f &&
radius.height < borderWidth / 2.0f) {
// Two dots are merged into one and placed at the corner.
//
// borderWidth / 2.0
// |<---------->|
// | |
// |radius.width|
// |<--->| |
// | | |
// | _+------+------------+-----
// | / ###|### |
// |/ #######|####### |
// + #########|######### |
// | ##########|########## |
// | ###########|########### |
// | ###########|########### |
// |############|############|
// +------------+############|
// |########### P ###########|
// | ####################### |
// | ####################### |
// | ##################### |
// | ################### |
// | ############### |
// | ####### |
// +-------------------------+----
// | |
// | |
P.x += signs[0] * borderWidth / 2.0f;
P.y += signs[1] * borderWidth / 2.0f;
} else {
// Two dots are drawn separately.
//
// borderWidth * 1.5
// |<------------>|
// | |
// |radius.width |
// |<----->| |
// | | |
// | _--+-+----+---
// | _- | ##|##
// | / | ###|###
// |/ |####|####
// | |####+####
// | |### P ###
// + | ###|###
// | | ##|##
// +---------+----+---
// | ##### |
// | ####### |
// |#########|
// +----+----+
// |#########|
// | ####### |
// | ##### |
// | |
//
// There should be enough gap between 2 dots even if radius.width is
// small but larger than borderWidth / 2.0. borderWidth * 1.5 is the
// value that there's imaginally unfilled dot at the corner. The
// unfilled dot may overflow from the outer curve, but filled dots
// doesn't, so this could be acceptable solution at least for now.
// We may have to find better model/value.
//
// imaginally unfilled dot at the corner
// |
// v +----+---
// ***** | ##|##
// ******* | ###|###
// *********|####|####
// *********|####+####
// *********|### P ###
// ******* | ###|###
// ***** | ##|##
// +---------+----+---
// | ##### |
// | ####### |
// |#########|
// +----+----+
// |#########|
// | ####### |
// | ##### |
// | |
Float minimum = borderWidth * 1.5f;
if (isHorizontal) {
P.x += signs[0] * std::max(radius.width, minimum);
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
P.y += signs[1] * std::max(radius.height, minimum);
}
}
return P;
}
if (borderWidth < otherBorderWidth) {
// This side is smaller than other side, other side draws the corner.
//
// otherBorderWidth + borderWidth / 2.0
// |<---------->|
// | |
// +---------+--+--------
// | ##### | *|* ###
// | ####### |**|**#####
// |#########|**+**##+##
// |####+####|* P *#####
// |#########| *** ###
// | ####### +-----------
// | ##### | ^
// | | |
// | | first dot is not filled
// | |
//
// radius.width
// |<----------------->|
// | |
// | ___---+-------------
// | __-- #|# ###
// | _- ##|## #####
// | / ##+## ##+##
// | / # P # #####
// | | #|# ###
// | | __--+-------------
// || _- ^
// || / |
// | / first dot is filled
// | |
// | |
// | ##### |
// | ####### |
// |#########|
// +----+----+
// |#########|
// | ####### |
// | ##### |
Float minimum = otherBorderWidth + borderWidth / 2.0f;
if (isHorizontal) {
if (radius.width < minimum) {
*aIsUnfilled = true;
P.x += signs[0] * minimum;
} else {
P.x += signs[0] * radius.width;
}
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
if (radius.height < minimum) {
*aIsUnfilled = true;
P.y += signs[1] * minimum;
} else {
P.y += signs[1] * radius.height;
}
}
return P;
}
// This side is larger than other side, this side draws the corner.
//
// borderWidth / 2.0
// |<-->|
// | |
// +----+---------------------
// | ##|## #####
// | ###|### #######
// |####|#### #########
// |####+#### ####+####
// |### P ### #########
// | ####### #######
// | ##### #####
// +-----+---------------------
// | *** |
// |*****|
// |**+**| <-- first dot in other side is not filled
// |*****|
// | *** |
// | ### |
// |#####|
// |##+##|
// |#####|
// | ### |
// | |
if (isHorizontal) {
P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
}
return P;
}
if (style == StyleBorderStyle::Dotted) {
// If only this side is dotted, other side draws the corner.
//
// otherBorderWidth + borderWidth / 2.0
// |<------->|
// | |
// +------+--+--------
// |## ##| *|* ###
// |## ##|**|**#####
// |## ##|**+**##+##
// |## ##|* P *#####
// |## ##| *** ###
// |## ##+-----------
// |## ##| ^
// |## ##| |
// |## ##| first dot is not filled
// |## ##|
//
// radius.width
// |<----------------->|
// | |
// | ___---+-------------
// | __-- #|# ###
// | _- ##|## #####
// | / ##+## ##+##
// | / # P # #####
// | | #|# ###
// | | __--+-------------
// || _- ^
// || / |
// | / first dot is filled
// | |
// | |
// | |
// | |
// | |
// +------+
// |## ##|
// |## ##|
// |## ##|
Float minimum = otherBorderWidth + borderWidth / 2.0f;
if (isHorizontal) {
if (radius.width < minimum) {
*aIsUnfilled = true;
P.x += signs[0] * minimum;
} else {
P.x += signs[0] * radius.width;
}
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
if (radius.height < minimum) {
*aIsUnfilled = true;
P.y += signs[1] * minimum;
} else {
P.y += signs[1] * radius.height;
}
}
return P;
}
if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) {
// If other side is dotted and radius=0, draw side to the end of corner.
//
// +-------------------------------
// |########## ##########
// P +########## ##########
// |########## ##########
// +-----+-------------------------
// | *** |
// |*****|
// |**+**| <-- first dot in other side is not filled
// |*****|
// | *** |
// | ### |
// |#####|
// |##+##|
// |#####|
// | ### |
// | |
if (isHorizontal) {
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
}
return P;
}
// Other cases.
//
// dim.width
// |<----------------->|
// | |
// | ___---+------------------
// | __-- |####### ###
// | _- P +####### ###
// | / |####### ###
// | / __---+------------------
// | | __--
// | | /
// || /
// || |
// | |
// | |
// | |
// | |
// +-+-+
// |###|
// |###|
// |###|
// |###|
// |###|
// | |
// | |
if (isHorizontal) {
P.x += signs[0] * dim.width;
P.y += signs[1] * borderWidth / 2.0f;
} else {
P.x += signs[0] * borderWidth / 2.0f;
P.y += signs[1] * dim.height;
}
return P;
}
void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
Bezier* aInnerBezier,
Corner aCorner) {
// Return bezier control points for outer and inner curve for given corner.
//
// ___---+ outer curve
// __-- |
// _- |
// / |
// / |
// | |
// | __--+ inner curve
// | _-
// | /
// | /
// | |
// | |
// | |
// | |
// | |
// +---------+
mozilla::Side sideH(GetHorizontalSide(aCorner));
mozilla::Side sideV(GetVerticalSide(aCorner));
Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
ceil(mBorderRadii[aCorner].height));
Size innerCornerSize(
ceil(std::max(0.0f, mBorderRadii[aCorner].width - mBorderWidths[sideV])),
ceil(
std::max(0.0f, mBorderRadii[aCorner].height - mBorderWidths[sideH])));
GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
outerCornerSize);
GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
innerCornerSize);
}
void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
const Rect& aInnerRect,
const RectCornerRadii& aBorderRadii,
const Float* aBorderSizes,
SideBits aSides,
const ColorPattern& aColor) {
// Note that this function is allowed to draw more than just the
// requested sides.
// If we have a border radius, do full rounded rectangles
// and fill, regardless of what sides we're asked to draw.
if (!AllCornersZeroSize(aBorderRadii)) {
RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
RectCornerRadii innerRadii;
ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
// do the outer border
AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
// then do the inner border CCW
AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
RefPtr<Path> path = builder->Finish();
mDrawTarget->Fill(path, aColor);
return;
}
// If we're asked to draw all sides of an equal-sized border,
// stroking is fastest. This is a fairly common path, but partial
// sides is probably second in the list -- there are a bunch of
// common border styles, such as inset and outset, that are
// top-left/bottom-right split.
if (aSides == SideBits::eAll &&
CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) && !mAvoidStroke) {
Float strokeWidth = aBorderSizes[0];
Rect r(aOuterRect);
r.Deflate(strokeWidth / 2.f);
mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
return;
}
// Otherwise, we have unequal sized borders or we're only
// drawing some sides; create rectangles for each side
// and fill them.
Rect r[4];
// compute base rects for each side
if (aSides & SideBits::eTop) {
r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(),
aBorderSizes[eSideTop]);
}
if (aSides & SideBits::eBottom) {
r[eSideBottom] =
Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom],
aOuterRect.Width(), aBorderSizes[eSideBottom]);
}
if (aSides & SideBits::eLeft) {
r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes[eSideLeft],
aOuterRect.Height());
}
if (aSides & SideBits::eRight) {
r[eSideRight] =
Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(),
aBorderSizes[eSideRight], aOuterRect.Height());
}
// If two sides meet at a corner that we're rendering, then
// make sure that we adjust one of the sides to avoid overlap.
// This is especially important in the case of colors with
// an alpha channel.
if ((aSides & (SideBits::eTop | SideBits::eLeft)) ==
(SideBits::eTop | SideBits::eLeft)) {
// adjust the left's top down a bit
r[eSideLeft].y += aBorderSizes[eSideTop];
r[eSideLeft].height -= aBorderSizes[eSideTop];
}
if ((aSides & (SideBits::eTop | SideBits::eRight)) ==
(SideBits::eTop | SideBits::eRight)) {
// adjust the top's left a bit
r[eSideTop].width -= aBorderSizes[eSideRight];
}
if ((aSides & (SideBits::eBottom | SideBits::eRight)) ==
(SideBits::eBottom | SideBits::eRight)) {
// adjust the right's bottom a bit
r[eSideRight].height -= aBorderSizes[eSideBottom];
}
if ((aSides & (SideBits::eBottom | SideBits::eLeft)) ==
(SideBits::eBottom | SideBits::eLeft)) {
// adjust the bottom's left a bit
r[eSideBottom].x += aBorderSizes[eSideLeft];
r[eSideBottom].width -= aBorderSizes[eSideLeft];
}
// Filling these one by one is faster than filling them all at once.
for (uint32_t i = 0; i < 4; i++) {
if (aSides & static_cast<mozilla::SideBits>(1 << i)) {
MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
mDrawTarget->FillRect(r[i], aColor);
}
}
}
sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) {
nscolor colors[2];
int k = 0;
switch (aBorderColorStyle) {
case BorderColorStyleNone:
return sRGBColor(0.f, 0.f, 0.f, 0.f); // transparent black
case BorderColorStyleLight:
k = 1;
[[fallthrough]];
case BorderColorStyleDark:
NS_GetSpecial3DColors(colors, aColor);
return sRGBColor::FromABGR(colors[k]);
case BorderColorStyleSolid:
default:
return sRGBColor::FromABGR(aColor);
}
}
sRGBColor ComputeColorForLine(uint32_t aLineIndex,
const BorderColorStyle* aBorderColorStyle,
uint32_t aBorderColorStyleCount,
nscolor aBorderColor) {
NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]);
}
void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) {
if (aSides == SideBits::eNone ||
(aSides & ~SideBits::eAll) != SideBits::eNone) {
NS_WARNING("DrawBorderSides: invalid sides!");
return;
}
StyleBorderStyle borderRenderStyle = StyleBorderStyle::None;
nscolor borderRenderColor;
uint32_t borderColorStyleCount = 0;
BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
BorderColorStyle* borderColorStyle = nullptr;
for (const auto i : mozilla::AllPhysicalSides()) {
if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) {
continue;
}
borderRenderStyle = mBorderStyles[i];
borderRenderColor = mBorderColors[i];
break;
}
if (borderRenderStyle == StyleBorderStyle::None ||
borderRenderStyle == StyleBorderStyle::Hidden) {
return;
}
if (borderRenderStyle == StyleBorderStyle::Dashed ||
borderRenderStyle == StyleBorderStyle::Dotted) {
// Draw each corner separately, with the given side's color.
if (aSides & SideBits::eTop) {
DrawDashedOrDottedCorner(eSideTop, C_TL);
} else if (aSides & SideBits::eLeft) {
DrawDashedOrDottedCorner(eSideLeft, C_TL);
}
if (aSides & SideBits::eTop) {
DrawDashedOrDottedCorner(eSideTop, C_TR);
} else if (aSides & SideBits::eRight) {
DrawDashedOrDottedCorner(eSideRight, C_TR);
}
if (aSides & SideBits::eBottom) {
DrawDashedOrDottedCorner(eSideBottom, C_BL);
} else if (aSides & SideBits::eLeft) {
DrawDashedOrDottedCorner(eSideLeft, C_BL);
}
if (aSides & SideBits::eBottom) {
DrawDashedOrDottedCorner(eSideBottom, C_BR);
} else if (aSides & SideBits::eRight) {
DrawDashedOrDottedCorner(eSideRight, C_BR);
}
return;
}
// The borderColorStyle array goes from the outer to the inner style.
//
// If the border width is 1, we need to change the borderRenderStyle
// a bit to make sure that we get the right colors -- e.g. 'ridge'
// with a 1px border needs to look like solid, not like 'outset'.
if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge ||
borderRenderStyle == StyleBorderStyle::Groove ||
borderRenderStyle == StyleBorderStyle::Double)) {
borderRenderStyle = StyleBorderStyle::Solid;
}
switch (borderRenderStyle) {
case StyleBorderStyle::Solid:
borderColorStyleTopLeft[0] = BorderColorStyleSolid;
borderColorStyleBottomRight[0] = BorderColorStyleSolid;
borderColorStyleCount = 1;
break;
case StyleBorderStyle::Groove:
borderColorStyleTopLeft[0] = BorderColorStyleDark;
borderColorStyleTopLeft[1] = BorderColorStyleLight;
borderColorStyleBottomRight[0] = BorderColorStyleLight;
borderColorStyleBottomRight[1] = BorderColorStyleDark;
borderColorStyleCount = 2;
break;
case StyleBorderStyle::Ridge:
borderColorStyleTopLeft[0] = BorderColorStyleLight;
borderColorStyleTopLeft[1] = BorderColorStyleDark;
borderColorStyleBottomRight[0] = BorderColorStyleDark;
borderColorStyleBottomRight[1] = BorderColorStyleLight;
borderColorStyleCount = 2;
break;
case StyleBorderStyle::Double:
borderColorStyleTopLeft[0] = BorderColorStyleSolid;
borderColorStyleTopLeft[1] = BorderColorStyleNone;
borderColorStyleTopLeft[2] = BorderColorStyleSolid;
borderColorStyleBottomRight[0] = BorderColorStyleSolid;
borderColorStyleBottomRight[1] = BorderColorStyleNone;
borderColorStyleBottomRight[2] = BorderColorStyleSolid;
borderColorStyleCount = 3;
break;
case StyleBorderStyle::Inset:
borderColorStyleTopLeft[0] = BorderColorStyleDark;
borderColorStyleBottomRight[0] = BorderColorStyleLight;
borderColorStyleCount = 1;
break;
case StyleBorderStyle::Outset:
borderColorStyleTopLeft[0] = BorderColorStyleLight;
borderColorStyleBottomRight[0] = BorderColorStyleDark;
borderColorStyleCount = 1;
break;
default:
MOZ_ASSERT_UNREACHABLE("Unhandled border style!!");
break;
}
// The only way to get to here is by having a
// borderColorStyleCount < 1 or > 3; this should never happen,
// since -moz-border-colors doesn't get handled here.
NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
"Non-border-colors case with borderColorStyleCount < 1 or > 3; "
"what happened?");
// The caller should never give us anything with a mix
// of TL/BR if the border style would require a
// TL/BR split.
if (aSides & (SideBits::eBottom | SideBits::eRight)) {
borderColorStyle = borderColorStyleBottomRight;
} else {
borderColorStyle = borderColorStyleTopLeft;
}
// Distribute the border across the available space.
Float borderWidths[3][4];
if (borderColorStyleCount == 1) {
for (const auto i : mozilla::AllPhysicalSides()) {
borderWidths[0][i] = mBorderWidths[i];
}
} else if (borderColorStyleCount == 2) {
// with 2 color styles, any extra pixel goes to the outside
for (const auto i : mozilla::AllPhysicalSides()) {
borderWidths[0][i] =
int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2;
borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2;
}
} else if (borderColorStyleCount == 3) {
// with 3 color styles, any extra pixel (or lack of extra pixel)
// goes to the middle
for (const auto i : mozilla::AllPhysicalSides()) {
if (mBorderWidths[i] == 1.0) {
borderWidths[0][i] = 1.f;
borderWidths[1][i] = borderWidths[2][i] = 0.f;
} else {
int32_t rest = int32_t(mBorderWidths[i]) % 3;
borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] =
(int32_t(mBorderWidths[i]) - rest) / 3;
if (rest == 1) {
borderWidths[1][i] += 1.f;
} else if (rest == 2) {
borderWidths[0][i] += 1.f;
borderWidths[2][i] += 1.f;
}
}
}
}
// make a copy that we can modify
RectCornerRadii radii = mBorderRadii;
Rect soRect(mOuterRect);
Rect siRect(mOuterRect);
// If adjacent side is dotted and radius=0, draw side to the end of corner.
//
// +--------------------------------
// |################################
// |
// |################################
// +-----+--------------------------
// | |
// | |
// | |
// | |
// | |
// | ### |
// |#####|
// |#####|
// |#####|
// | ### |
// | |
bool noMarginTop = false;
bool noMarginRight = false;
bool noMarginBottom = false;
bool noMarginLeft = false;
// If there is at least one dotted side, every side is rendered separately.
if (IsSingleSide(aSides)) {
if (aSides == SideBits::eTop) {
if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_TR])) {
noMarginRight = true;
}
if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_TL])) {
noMarginLeft = true;
}
} else if (aSides == SideBits::eRight) {
if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_TR])) {
noMarginTop = true;
}
if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_BR])) {
noMarginBottom = true;
}
} else if (aSides == SideBits::eBottom) {
if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_BR])) {
noMarginRight = true;
}
if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_BL])) {
noMarginLeft = true;
}
} else {
if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_TL])) {
noMarginTop = true;
}
if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
IsZeroSize(mBorderRadii[C_BL])) {
noMarginBottom = true;