blob: d2600848b8fa66b2bcedd18214db135a17c6308c [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/box_border_painter.h"
#include <algorithm>
#include "third_party/blink/renderer/core/paint/box_painter.h"
#include "third_party/blink/renderer/core/paint/object_painter.h"
#include "third_party/blink/renderer/core/paint/paint_auto_dark_mode.h"
#include "third_party/blink/renderer/core/paint/rounded_border_geometry.h"
#include "third_party/blink/renderer/core/style/border_edge.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/stroke_data.h"
#include "third_party/blink/renderer/platform/graphics/styled_stroke_data.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace blink {
namespace {
enum BorderEdgeFlag {
kTopBorderEdge = 1 << static_cast<unsigned>(BoxSide::kTop),
kRightBorderEdge = 1 << static_cast<unsigned>(BoxSide::kRight),
kBottomBorderEdge = 1 << static_cast<unsigned>(BoxSide::kBottom),
kLeftBorderEdge = 1 << static_cast<unsigned>(BoxSide::kLeft),
kAllBorderEdges =
kTopBorderEdge | kBottomBorderEdge | kLeftBorderEdge | kRightBorderEdge
};
inline BorderEdgeFlag EdgeFlagForSide(BoxSide side) {
return static_cast<BorderEdgeFlag>(1 << static_cast<unsigned>(side));
}
inline bool IncludesEdge(BorderEdgeFlags flags, BoxSide side) {
return flags & EdgeFlagForSide(side);
}
inline bool IncludesAdjacentEdges(BorderEdgeFlags flags) {
// The set includes adjacent edges iff it contains at least one horizontal and
// one vertical edge.
return (flags & (kTopBorderEdge | kBottomBorderEdge)) &&
(flags & (kLeftBorderEdge | kRightBorderEdge));
}
inline bool StyleRequiresClipPolygon(EBorderStyle style) {
// These are drawn with a stroke, so we have to clip to get corner miters.
return style == EBorderStyle::kDotted || style == EBorderStyle::kDashed;
}
inline bool BorderStyleFillsBorderArea(EBorderStyle style) {
return !(style == EBorderStyle::kDotted || style == EBorderStyle::kDashed ||
style == EBorderStyle::kDouble);
}
inline bool BorderStyleHasInnerDetail(EBorderStyle style) {
return style == EBorderStyle::kGroove || style == EBorderStyle::kRidge ||
style == EBorderStyle::kDouble;
}
inline bool BorderStyleIsDottedOrDashed(EBorderStyle style) {
return style == EBorderStyle::kDotted || style == EBorderStyle::kDashed;
}
// BorderStyleOutset darkens the bottom and right (and maybe lightens the top
// and left) BorderStyleInset darkens the top and left (and maybe lightens the
// bottom and right).
inline bool BorderStyleHasUnmatchedColorsAtCorner(EBorderStyle style,
BoxSide side,
BoxSide adjacent_side) {
// These styles match at the top/left and bottom/right.
if (style == EBorderStyle::kInset || style == EBorderStyle::kGroove ||
style == EBorderStyle::kRidge || style == EBorderStyle::kOutset) {
const BorderEdgeFlags top_right_flags =
EdgeFlagForSide(BoxSide::kTop) | EdgeFlagForSide(BoxSide::kRight);
const BorderEdgeFlags bottom_left_flags =
EdgeFlagForSide(BoxSide::kBottom) | EdgeFlagForSide(BoxSide::kLeft);
BorderEdgeFlags flags =
EdgeFlagForSide(side) | EdgeFlagForSide(adjacent_side);
return flags == top_right_flags || flags == bottom_left_flags;
}
return false;
}
inline bool BorderWillArcInnerEdge(const gfx::SizeF& first_radius,
const gfx::SizeF& second_radius) {
return !first_radius.IsZero() || !second_radius.IsZero();
}
inline bool WillOverdraw(BoxSide side,
EBorderStyle style,
BorderEdgeFlags completed_edges) {
// If we're done with this side, it will obviously not overdraw any portion of
// the current edge.
if (IncludesEdge(completed_edges, side))
return false;
// The side is still to be drawn. It overdraws the current edge iff it has a
// solid fill style.
return BorderStyleFillsBorderArea(style);
}
inline bool BorderStylesRequireMiter(BoxSide side,
BoxSide adjacent_side,
EBorderStyle style,
EBorderStyle adjacent_style) {
if (style == EBorderStyle::kDouble ||
adjacent_style == EBorderStyle::kDouble ||
adjacent_style == EBorderStyle::kGroove ||
adjacent_style == EBorderStyle::kRidge)
return true;
if (BorderStyleIsDottedOrDashed(style) !=
BorderStyleIsDottedOrDashed(adjacent_style))
return true;
if (style != adjacent_style)
return true;
return BorderStyleHasUnmatchedColorsAtCorner(style, side, adjacent_side);
}
void SetToRightSideRect(gfx::Rect& rect, int edge_width) {
rect.set_x(rect.right() - edge_width);
rect.set_width(edge_width);
}
void SetToBottomSideRect(gfx::Rect& rect, int edge_width) {
rect.set_y(rect.bottom() - edge_width);
rect.set_height(edge_width);
}
gfx::Rect CalculateSideRect(const FloatRoundedRect& outer_border,
const BorderEdge& edge,
BoxSide side) {
gfx::Rect side_rect = gfx::ToRoundedRect(outer_border.Rect());
int width = edge.Width();
switch (side) {
case BoxSide::kTop:
side_rect.set_height(width);
break;
case BoxSide::kBottom:
SetToBottomSideRect(side_rect, width);
break;
case BoxSide::kLeft:
side_rect.set_width(width);
break;
case BoxSide::kRight:
SetToRightSideRect(side_rect, width);
break;
}
return side_rect;
}
FloatRoundedRect CalculateAdjustedInnerBorder(
const FloatRoundedRect& inner_border,
BoxSide side) {
// Expand the inner border as necessary to make it a rounded rect (i.e. radii
// contained within each edge). This function relies on the fact we only get
// radii not contained within each edge if one of the radii for an edge is
// zero, so we can shift the arc towards the zero radius corner.
FloatRoundedRect::Radii new_radii = inner_border.GetRadii();
gfx::RectF new_rect = inner_border.Rect();
float overshoot;
float max_radii;
switch (side) {
case BoxSide::kTop:
overshoot = new_radii.TopLeft().width() + new_radii.TopRight().width() -
new_rect.width();
// FIXME: once we start pixel-snapping rounded rects after this point, the
// overshoot concept should disappear.
if (overshoot > 0.1) {
new_rect.set_width(new_rect.width() + overshoot);
if (!new_radii.TopLeft().width())
new_rect.Offset(-overshoot, 0);
}
new_radii.SetBottomLeft(gfx::SizeF(0, 0));
new_radii.SetBottomRight(gfx::SizeF(0, 0));
max_radii =
std::max(new_radii.TopLeft().height(), new_radii.TopRight().height());
if (max_radii > new_rect.height())
new_rect.set_height(max_radii);
break;
case BoxSide::kBottom:
overshoot = new_radii.BottomLeft().width() +
new_radii.BottomRight().width() - new_rect.width();
if (overshoot > 0.1) {
new_rect.set_width(new_rect.width() + overshoot);
if (!new_radii.BottomLeft().width())
new_rect.Offset(-overshoot, 0);
}
new_radii.SetTopLeft(gfx::SizeF(0, 0));
new_radii.SetTopRight(gfx::SizeF(0, 0));
max_radii = std::max(new_radii.BottomLeft().height(),
new_radii.BottomRight().height());
if (max_radii > new_rect.height()) {
new_rect.Offset(0, new_rect.height() - max_radii);
new_rect.set_height(max_radii);
}
break;
case BoxSide::kLeft:
overshoot = new_radii.TopLeft().height() +
new_radii.BottomLeft().height() - new_rect.height();
if (overshoot > 0.1) {
new_rect.set_height(new_rect.height() + overshoot);
if (!new_radii.TopLeft().height())
new_rect.Offset(0, -overshoot);
}
new_radii.SetTopRight(gfx::SizeF(0, 0));
new_radii.SetBottomRight(gfx::SizeF(0, 0));
max_radii =
std::max(new_radii.TopLeft().width(), new_radii.BottomLeft().width());
if (max_radii > new_rect.width())
new_rect.set_width(max_radii);
break;
case BoxSide::kRight:
overshoot = new_radii.TopRight().height() +
new_radii.BottomRight().height() - new_rect.height();
if (overshoot > 0.1) {
new_rect.set_height(new_rect.height() + overshoot);
if (!new_radii.TopRight().height())
new_rect.Offset(0, -overshoot);
}
new_radii.SetTopLeft(gfx::SizeF(0, 0));
new_radii.SetBottomLeft(gfx::SizeF(0, 0));
max_radii = std::max(new_radii.TopRight().width(),
new_radii.BottomRight().width());
if (max_radii > new_rect.width()) {
new_rect.Offset(new_rect.width() - max_radii, 0);
new_rect.set_width(max_radii);
}
break;
}
return FloatRoundedRect(new_rect, new_radii);
}
void DrawSolidBorderRect(GraphicsContext& context,
const gfx::Rect& border_rect,
int border_width,
const Color& color,
const AutoDarkMode& auto_dark_mode) {
gfx::RectF stroke_rect(border_rect);
stroke_rect.Outset(-border_width / 2.f);
bool was_antialias = context.ShouldAntialias();
if (!was_antialias)
context.SetShouldAntialias(true);
context.SetStrokeColor(color);
context.SetStrokeThickness(border_width);
context.StrokeRect(stroke_rect, auto_dark_mode);
if (!was_antialias)
context.SetShouldAntialias(false);
}
void DrawBleedAdjustedDRRect(GraphicsContext& context,
BackgroundBleedAvoidance bleed_avoidance,
const FloatRoundedRect& outer,
const FloatRoundedRect& inner,
Color color,
const AutoDarkMode& auto_dark_mode) {
switch (bleed_avoidance) {
case kBackgroundBleedClipLayer: {
// BackgroundBleedClipLayer clips the outer rrect for the whole layer.
// Based on this, we can avoid background bleeding by filling the
// *outside* of inner rrect, all the way to the layer bounds (enclosing
// int rect for the clip, in device space).
SkPath path;
path.addRRect(SkRRect(inner));
path.setFillType(SkPathFillType::kInverseWinding);
cc::PaintFlags flags;
flags.setColor(color.toSkColor4f());
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
context.DrawPath(path, flags, auto_dark_mode);
break;
}
case kBackgroundBleedClipOnly:
if (outer.IsRounded()) {
// BackgroundBleedClipOnly clips the outer rrect corners for us.
FloatRoundedRect adjusted_outer = outer;
adjusted_outer.SetRadii(FloatRoundedRect::Radii());
context.FillDRRect(adjusted_outer, inner, color, auto_dark_mode);
break;
}
[[fallthrough]];
default:
context.FillDRRect(outer, inner, color, auto_dark_mode);
break;
}
}
// The LUTs below assume specific enum values.
static_assert(EBorderStyle::kNone == static_cast<EBorderStyle>(0),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kHidden == static_cast<EBorderStyle>(1),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kInset == static_cast<EBorderStyle>(2),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kGroove == static_cast<EBorderStyle>(3),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kOutset == static_cast<EBorderStyle>(4),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kRidge == static_cast<EBorderStyle>(5),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kDotted == static_cast<EBorderStyle>(6),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kDashed == static_cast<EBorderStyle>(7),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kSolid == static_cast<EBorderStyle>(8),
"unexpected EBorderStyle value");
static_assert(EBorderStyle::kDouble == static_cast<EBorderStyle>(9),
"unexpected EBorderStyle value");
static_assert(static_cast<unsigned>(BoxSide::kTop) == 0,
"unexpected BoxSide value");
static_assert(static_cast<unsigned>(BoxSide::kRight) == 1,
"unexpected BoxSide value");
static_assert(static_cast<unsigned>(BoxSide::kBottom) == 2,
"unexpected BoxSide value");
static_assert(static_cast<unsigned>(BoxSide::kLeft) == 3,
"unexpected BoxSide value");
// Style-based paint order: non-solid edges (dashed/dotted/double) are painted
// before solid edges (inset/outset/groove/ridge/solid) to maximize overdraw
// opportunities.
const unsigned kStylePriority[] = {
0, // EBorderStyle::kNone
0, // EBorderStyle::kHidden
2, // EBorderStyle::kInset
2, // EBorderStyle::kGroove
2, // EBorderStyle::kOutset
2, // EBorderStyle::kRidge,
1, // EBorderStyle::kDotted
1, // EBorderStyle::kDashed
3, // EBorderStyle::kSolid
1, // EBorderStyle::kDouble
};
// Given the same style, prefer drawing in non-adjacent order to minimize the
// number of sides which require miters.
const unsigned kSidePriority[] = {
0, // BoxSide::kTop
2, // BoxSide::kRight
1, // BoxSide::kBottom
3, // BoxSide::kLeft
};
// Edges sharing the same opacity. Stores both a side list and an edge bitfield
// to support constant time iteration + membership tests.
struct OpacityGroup {
DISALLOW_NEW();
public:
explicit OpacityGroup(float alpha) : edge_flags(0), alpha(alpha) {}
Vector<BoxSide, 4> sides;
BorderEdgeFlags edge_flags;
float alpha;
};
void ClipPolygon(GraphicsContext& context,
const gfx::PointF vertices[],
unsigned vertices_size,
bool antialiased) {
SkPath path;
path.moveTo(gfx::PointFToSkPoint(vertices[0]));
for (unsigned i = 1; i < vertices_size; ++i) {
path.lineTo(gfx::PointFToSkPoint(vertices[i]));
}
context.ClipPath(path, antialiased ? kAntiAliased : kNotAntiAliased);
}
void DrawDashedOrDottedBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
BoxSide side,
Color color,
int thickness,
EBorderStyle style,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
DCHECK_GT(thickness, 0);
GraphicsContextStateSaver state_saver(context);
context.SetShouldAntialias(antialias);
context.SetStrokeColor(color);
StyledStrokeData styled_stroke;
styled_stroke.SetThickness(thickness);
styled_stroke.SetStyle(style == EBorderStyle::kDashed ? kDashedStroke
: kDottedStroke);
switch (side) {
case BoxSide::kBottom:
case BoxSide::kTop: {
int mid_y = y1 + thickness / 2;
context.DrawLine(gfx::Point(x1, mid_y), gfx::Point(x2, mid_y),
styled_stroke, auto_dark_mode);
break;
}
case BoxSide::kRight:
case BoxSide::kLeft: {
int mid_x = x1 + thickness / 2;
context.DrawLine(gfx::Point(mid_x, y1), gfx::Point(mid_x, y2),
styled_stroke, auto_dark_mode);
break;
}
}
}
void DrawLineForBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
BoxSide side,
Color color,
EBorderStyle style,
int adjacent_width1,
int adjacent_width2,
bool antialias,
const AutoDarkMode& auto_dark_mode);
Color CalculateBorderStyleColor(const EBorderStyle& style,
const BoxSide& side,
const Color& color) {
bool is_darken = (side == BoxSide::kTop || side == BoxSide::kLeft) ==
(style == EBorderStyle::kInset);
Color dark_color = color.Dark();
// Inset, outset, ridge, and groove paint a darkened or "shadow" edge:
// https://w3c.github.io/csswg-drafts/css-backgrounds/#border-style. By
// default, darken |color| for the darker edge and use |color| for the lighter
// edge.
if (is_darken) {
return dark_color;
}
auto should_lighten_color = [color, dark_color]() -> bool {
// This constant is used to determine if there is enough contrast between
// the darkened edge and |color|. If not, also lighten |color| for the
// lighter edge.
constexpr float kMinimumBorderEdgeContrastRatio = 1.75f;
return color_utils::GetContrastRatio(color.toSkColor4f(),
dark_color.toSkColor4f()) <
kMinimumBorderEdgeContrastRatio;
};
// The following condition skips should_lighten_color() when the result is
// know to be false. The values came from a brute force search of r, b, g
// values, see https://crrev.com/c/4200827/3.
if (color.Red() >= 150 || color.Green() >= 92) {
DCHECK(!should_lighten_color());
return color;
}
return should_lighten_color() ? color.Light() : color;
}
void DrawDoubleBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
int length,
BoxSide side,
Color color,
int thickness,
int adjacent_width1,
int adjacent_width2,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
int third_of_thickness = (thickness + 1) / 3;
DCHECK_GT(third_of_thickness, 0);
if (!adjacent_width1 && !adjacent_width2) {
context.SetFillColor(color);
bool was_antialiased = context.ShouldAntialias();
context.SetShouldAntialias(antialias);
switch (side) {
case BoxSide::kTop:
case BoxSide::kBottom:
context.FillRect(gfx::Rect(x1, y1, length, third_of_thickness),
auto_dark_mode);
context.FillRect(
gfx::Rect(x1, y2 - third_of_thickness, length, third_of_thickness),
auto_dark_mode);
break;
case BoxSide::kLeft:
case BoxSide::kRight:
context.FillRect(gfx::Rect(x1, y1, third_of_thickness, length),
auto_dark_mode);
context.FillRect(
gfx::Rect(x2 - third_of_thickness, y1, third_of_thickness, length),
auto_dark_mode);
break;
}
context.SetShouldAntialias(was_antialiased);
return;
}
int adjacent1_big_third =
((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 3;
int adjacent2_big_third =
((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 3;
switch (side) {
case BoxSide::kTop:
DrawLineForBoxSide(
context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), y1,
x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0),
y1 + third_of_thickness, side, color, EBorderStyle::kSolid,
adjacent1_big_third, adjacent2_big_third, antialias, auto_dark_mode);
DrawLineForBoxSide(context,
x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0),
y2 - third_of_thickness,
x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), y2,
side, color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
break;
case BoxSide::kLeft:
DrawLineForBoxSide(context, x1,
y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0),
x1 + third_of_thickness,
y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side,
color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
DrawLineForBoxSide(context, x2 - third_of_thickness,
y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), x2,
y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side,
color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
break;
case BoxSide::kBottom:
DrawLineForBoxSide(
context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), y1,
x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0),
y1 + third_of_thickness, side, color, EBorderStyle::kSolid,
adjacent1_big_third, adjacent2_big_third, antialias, auto_dark_mode);
DrawLineForBoxSide(context,
x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0),
y2 - third_of_thickness,
x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), y2,
side, color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
break;
case BoxSide::kRight:
DrawLineForBoxSide(context, x1,
y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0),
x1 + third_of_thickness,
y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side,
color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
DrawLineForBoxSide(context, x2 - third_of_thickness,
y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), x2,
y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side,
color, EBorderStyle::kSolid, adjacent1_big_third,
adjacent2_big_third, antialias, auto_dark_mode);
break;
default:
break;
}
}
void DrawRidgeOrGrooveBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
BoxSide side,
Color color,
EBorderStyle style,
int adjacent_width1,
int adjacent_width2,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
EBorderStyle s1;
EBorderStyle s2;
if (style == EBorderStyle::kGroove) {
s1 = EBorderStyle::kInset;
s2 = EBorderStyle::kOutset;
} else {
s1 = EBorderStyle::kOutset;
s2 = EBorderStyle::kInset;
}
int adjacent1_big_half =
((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 2;
int adjacent2_big_half =
((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 2;
switch (side) {
case BoxSide::kTop:
DrawLineForBoxSide(context, x1 + std::max(-adjacent_width1, 0) / 2, y1,
x2 - std::max(-adjacent_width2, 0) / 2,
(y1 + y2 + 1) / 2, side, color, s1, adjacent1_big_half,
adjacent2_big_half, antialias, auto_dark_mode);
DrawLineForBoxSide(
context, x1 + std::max(adjacent_width1 + 1, 0) / 2, (y1 + y2 + 1) / 2,
x2 - std::max(adjacent_width2 + 1, 0) / 2, y2, side, color, s2,
adjacent_width1 / 2, adjacent_width2 / 2, antialias, auto_dark_mode);
break;
case BoxSide::kLeft:
DrawLineForBoxSide(context, x1, y1 + std::max(-adjacent_width1, 0) / 2,
(x1 + x2 + 1) / 2,
y2 - std::max(-adjacent_width2, 0) / 2, side, color,
s1, adjacent1_big_half, adjacent2_big_half, antialias,
auto_dark_mode);
DrawLineForBoxSide(
context, (x1 + x2 + 1) / 2, y1 + std::max(adjacent_width1 + 1, 0) / 2,
x2, y2 - std::max(adjacent_width2 + 1, 0) / 2, side, color, s2,
adjacent_width1 / 2, adjacent_width2 / 2, antialias, auto_dark_mode);
break;
case BoxSide::kBottom:
DrawLineForBoxSide(context, x1 + std::max(adjacent_width1, 0) / 2, y1,
x2 - std::max(adjacent_width2, 0) / 2,
(y1 + y2 + 1) / 2, side, color, s2, adjacent1_big_half,
adjacent2_big_half, antialias, auto_dark_mode);
DrawLineForBoxSide(context, x1 + std::max(-adjacent_width1 + 1, 0) / 2,
(y1 + y2 + 1) / 2,
x2 - std::max(-adjacent_width2 + 1, 0) / 2, y2, side,
color, s1, adjacent_width1 / 2, adjacent_width2 / 2,
antialias, auto_dark_mode);
break;
case BoxSide::kRight:
DrawLineForBoxSide(
context, x1, y1 + std::max(adjacent_width1, 0) / 2, (x1 + x2 + 1) / 2,
y2 - std::max(adjacent_width2, 0) / 2, side, color, s2,
adjacent1_big_half, adjacent2_big_half, antialias, auto_dark_mode);
DrawLineForBoxSide(context, (x1 + x2 + 1) / 2,
y1 + std::max(-adjacent_width1 + 1, 0) / 2, x2,
y2 - std::max(-adjacent_width2 + 1, 0) / 2, side,
color, s1, adjacent_width1 / 2, adjacent_width2 / 2,
antialias, auto_dark_mode);
break;
}
}
void FillQuad(GraphicsContext& context,
const gfx::PointF quad[],
const Color& color,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
SkPath path;
path.moveTo(gfx::PointFToSkPoint(quad[0]));
path.lineTo(gfx::PointFToSkPoint(quad[1]));
path.lineTo(gfx::PointFToSkPoint(quad[2]));
path.lineTo(gfx::PointFToSkPoint(quad[3]));
cc::PaintFlags flags(context.FillFlags());
flags.setAntiAlias(antialias);
flags.setColor(color.toSkColor4f());
context.DrawPath(path, flags, auto_dark_mode);
}
void DrawSolidBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
BoxSide side,
Color color,
int adjacent_width1,
int adjacent_width2,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
DCHECK_GE(x2, x1);
DCHECK_GE(y2, y1);
if (!adjacent_width1 && !adjacent_width2) {
// Tweak antialiasing to match the behavior of fillQuad();
// this matters for rects in transformed contexts.
bool was_antialiased = context.ShouldAntialias();
if (antialias != was_antialiased)
context.SetShouldAntialias(antialias);
context.FillRect(gfx::Rect(x1, y1, x2 - x1, y2 - y1), color,
auto_dark_mode);
if (antialias != was_antialiased)
context.SetShouldAntialias(was_antialiased);
return;
}
gfx::PointF quad[4];
switch (side) {
case BoxSide::kTop:
quad[0] = gfx::PointF(x1 + std::max(-adjacent_width1, 0), y1);
quad[1] = gfx::PointF(x1 + std::max(adjacent_width1, 0), y2);
quad[2] = gfx::PointF(x2 - std::max(adjacent_width2, 0), y2);
quad[3] = gfx::PointF(x2 - std::max(-adjacent_width2, 0), y1);
break;
case BoxSide::kBottom:
quad[0] = gfx::PointF(x1 + std::max(adjacent_width1, 0), y1);
quad[1] = gfx::PointF(x1 + std::max(-adjacent_width1, 0), y2);
quad[2] = gfx::PointF(x2 - std::max(-adjacent_width2, 0), y2);
quad[3] = gfx::PointF(x2 - std::max(adjacent_width2, 0), y1);
break;
case BoxSide::kLeft:
quad[0] = gfx::PointF(x1, y1 + std::max(-adjacent_width1, 0));
quad[1] = gfx::PointF(x1, y2 - std::max(-adjacent_width2, 0));
quad[2] = gfx::PointF(x2, y2 - std::max(adjacent_width2, 0));
quad[3] = gfx::PointF(x2, y1 + std::max(adjacent_width1, 0));
break;
case BoxSide::kRight:
quad[0] = gfx::PointF(x1, y1 + std::max(adjacent_width1, 0));
quad[1] = gfx::PointF(x1, y2 - std::max(adjacent_width2, 0));
quad[2] = gfx::PointF(x2, y2 - std::max(-adjacent_width2, 0));
quad[3] = gfx::PointF(x2, y1 + std::max(-adjacent_width1, 0));
break;
}
FillQuad(context, quad, color, antialias, auto_dark_mode);
}
void DrawLineForBoxSide(GraphicsContext& context,
int x1,
int y1,
int x2,
int y2,
BoxSide side,
Color color,
EBorderStyle style,
int adjacent_width1,
int adjacent_width2,
bool antialias,
const AutoDarkMode& auto_dark_mode) {
int thickness;
int length;
if (side == BoxSide::kTop || side == BoxSide::kBottom) {
thickness = y2 - y1;
length = x2 - x1;
} else {
thickness = x2 - x1;
length = y2 - y1;
}
// We would like this check to be an ASSERT as we don't want to draw empty
// borders. However nothing guarantees that the following recursive calls to
// DrawLineForBoxSide() will have positive thickness and length.
if (length <= 0 || thickness <= 0) {
return;
}
style = BorderEdge::EffectiveStyle(style, thickness);
switch (style) {
case EBorderStyle::kNone:
case EBorderStyle::kHidden:
return;
case EBorderStyle::kDotted:
case EBorderStyle::kDashed:
DrawDashedOrDottedBoxSide(context, x1, y1, x2, y2, side, color, thickness,
style, antialias, auto_dark_mode);
break;
case EBorderStyle::kDouble:
DrawDoubleBoxSide(context, x1, y1, x2, y2, length, side, color, thickness,
adjacent_width1, adjacent_width2, antialias,
auto_dark_mode);
break;
case EBorderStyle::kRidge:
case EBorderStyle::kGroove:
DrawRidgeOrGrooveBoxSide(context, x1, y1, x2, y2, side, color, style,
adjacent_width1, adjacent_width2, antialias,
auto_dark_mode);
break;
case EBorderStyle::kInset:
case EBorderStyle::kOutset:
color = CalculateBorderStyleColor(style, side, color);
[[fallthrough]];
case EBorderStyle::kSolid:
DrawSolidBoxSide(context, x1, y1, x2, y2, side, color, adjacent_width1,
adjacent_width2, antialias, auto_dark_mode);
break;
}
}
void FindIntersection(const gfx::PointF& p1,
const gfx::PointF& p2,
const gfx::PointF& d1,
const gfx::PointF& d2,
gfx::PointF& intersection) {
float px_length = p2.x() - p1.x();
float py_length = p2.y() - p1.y();
float dx_length = d2.x() - d1.x();
float dy_length = d2.y() - d1.y();
float denom = px_length * dy_length - py_length * dx_length;
if (!denom)
return;
float param =
((d1.x() - p1.x()) * dy_length - (d1.y() - p1.y()) * dx_length) / denom;
intersection.set_x(p1.x() + param * px_length);
intersection.set_y(p1.y() + param * py_length);
}
} // anonymous namespace
// Holds edges grouped by opacity and sorted in paint order.
struct BoxBorderPainter::ComplexBorderInfo {
STACK_ALLOCATED();
public:
explicit ComplexBorderInfo(const BoxBorderPainter& border_painter) {
Vector<BoxSide, 4> sorted_sides;
// First, collect all visible sides.
for (unsigned i = border_painter.first_visible_edge_; i < 4; ++i) {
BoxSide side = static_cast<BoxSide>(i);
if (IncludesEdge(border_painter.visible_edge_set_, side))
sorted_sides.push_back(side);
}
DCHECK(!sorted_sides.empty());
// Then sort them in paint order, based on three (prioritized) criteria:
// alpha, style, side.
std::sort(sorted_sides.begin(), sorted_sides.end(),
[&border_painter](BoxSide a, BoxSide b) -> bool {
const BorderEdge& edge_a = border_painter.Edge(a);
const BorderEdge& edge_b = border_painter.Edge(b);
const float alpha_a = edge_a.GetColor().Alpha();
const float alpha_b = edge_b.GetColor().Alpha();
if (alpha_a != alpha_b)
return alpha_a < alpha_b;
const unsigned style_priority_a =
kStylePriority[static_cast<unsigned>(edge_a.BorderStyle())];
const unsigned style_priority_b =
kStylePriority[static_cast<unsigned>(edge_b.BorderStyle())];
if (style_priority_a != style_priority_b)
return style_priority_a < style_priority_b;
return kSidePriority[static_cast<unsigned>(a)] <
kSidePriority[static_cast<unsigned>(b)];
});
// Finally, build the opacity group structures.
BuildOpacityGroups(border_painter, sorted_sides);
if (border_painter.is_rounded_)
rounded_border_path.AddRoundedRect(border_painter.outer_);
}
Vector<OpacityGroup, 4> opacity_groups;
// Potentially used when drawing rounded borders.
Path rounded_border_path;
private:
void BuildOpacityGroups(const BoxBorderPainter& border_painter,
const Vector<BoxSide, 4>& sorted_sides) {
float current_alpha = 0.0f;
for (BoxSide side : sorted_sides) {
const BorderEdge& edge = border_painter.Edge(side);
const float edge_alpha = edge.GetColor().Alpha();
DCHECK_GT(edge_alpha, 0.0f);
DCHECK_GE(edge_alpha, current_alpha);
// TODO(crbug.com/1434423): This float comparison looks very brittle. We
// need to deduce the original intention of the code here. Also, this path
// is clearly un-tested and caused some serious regressions when touched.
// See crbug.com/1445288
if (edge_alpha != current_alpha) {
opacity_groups.push_back(OpacityGroup(edge_alpha));
current_alpha = edge_alpha;
}
DCHECK(!opacity_groups.empty());
OpacityGroup& current_group = opacity_groups.back();
current_group.sides.push_back(side);
current_group.edge_flags |= EdgeFlagForSide(side);
}
DCHECK(!opacity_groups.empty());
}
};
void BoxBorderPainter::DrawDoubleBorder() const {
DCHECK(is_uniform_color_);
DCHECK(is_uniform_style_);
DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble);
DCHECK(visible_edge_set_ == kAllBorderEdges);
const Color& color = FirstEdge().GetColor();
// When painting outlines, we ignore outer/inner radii.
const auto force_rectangular = !outer_.IsRounded() && !inner_.IsRounded();
AutoDarkMode auto_dark_mode(PaintAutoDarkMode(style_, element_role_));
// outer stripe
const PhysicalBoxStrut outer_third_outsets =
DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeOuter);
FloatRoundedRect outer_third_rect =
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, border_rect_, outer_third_outsets, sides_to_include_);
if (force_rectangular)
outer_third_rect.SetRadii(FloatRoundedRect::Radii());
DrawBleedAdjustedDRRect(context_, bleed_avoidance_, outer_, outer_third_rect,
color, auto_dark_mode);
// inner stripe
const PhysicalBoxStrut inner_third_outsets =
DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeInner);
FloatRoundedRect inner_third_rect =
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, border_rect_, inner_third_outsets, sides_to_include_);
if (force_rectangular)
inner_third_rect.SetRadii(FloatRoundedRect::Radii());
context_.FillDRRect(inner_third_rect, inner_, color, auto_dark_mode);
}
bool BoxBorderPainter::PaintBorderFastPath() const {
if (!is_uniform_color_ || !is_uniform_style_ || !inner_.IsRenderable())
return false;
if (FirstEdge().BorderStyle() != EBorderStyle::kSolid &&
FirstEdge().BorderStyle() != EBorderStyle::kDouble)
return false;
if (visible_edge_set_ == kAllBorderEdges) {
if (FirstEdge().BorderStyle() == EBorderStyle::kSolid) {
if (is_uniform_width_ && !outer_.IsRounded()) {
// 4-side, solid, uniform-width, rectangular border => one drawRect()
DrawSolidBorderRect(context_, gfx::ToRoundedRect(outer_.Rect()),
FirstEdge().Width(), FirstEdge().GetColor(),
PaintAutoDarkMode(style_, element_role_));
} else {
// 4-side, solid border => one drawDRRect()
DrawBleedAdjustedDRRect(context_, bleed_avoidance_, outer_, inner_,
FirstEdge().GetColor(),
PaintAutoDarkMode(style_, element_role_));
}
} else {
// 4-side, double border => 2x drawDRRect()
DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble);
DrawDoubleBorder();
}
return true;
}
// This is faster than the normal complex border path only if it avoids
// creating transparency layers (when the border is translucent).
if (FirstEdge().BorderStyle() == EBorderStyle::kSolid &&
!outer_.IsRounded() && has_transparency_) {
DCHECK(visible_edge_set_ != kAllBorderEdges);
// solid, rectangular border => one drawPath()
Path path;
path.SetWindRule(RULE_NONZERO);
for (auto side :
{BoxSide::kTop, BoxSide::kRight, BoxSide::kBottom, BoxSide::kLeft}) {
const BorderEdge& curr_edge = Edge(side);
if (curr_edge.ShouldRender()) {
path.AddRect(gfx::RectF(CalculateSideRect(outer_, curr_edge, side)));
}
}
context_.SetFillColor(FirstEdge().GetColor());
context_.FillPath(path, PaintAutoDarkMode(style_, element_role_));
return true;
}
return false;
}
BoxBorderPainter::BoxBorderPainter(GraphicsContext& context,
const PhysicalRect& border_rect,
const ComputedStyle& style,
BackgroundBleedAvoidance bleed_avoidance,
PhysicalBoxSides sides_to_include)
: context_(context),
border_rect_(border_rect),
style_(style),
bleed_avoidance_(bleed_avoidance),
sides_to_include_(sides_to_include),
visible_edge_count_(0),
first_visible_edge_(0),
visible_edge_set_(0),
is_uniform_style_(true),
is_uniform_width_(true),
is_uniform_color_(true),
is_rounded_(false),
has_transparency_(false) {
style.GetBorderEdgeInfo(edges_, sides_to_include);
ComputeBorderProperties();
// No need to compute the rrects if we don't have any borders to draw.
if (!visible_edge_set_)
return;
outer_ = RoundedBorderGeometry::PixelSnappedRoundedBorder(style_, border_rect,
sides_to_include);
inner_ = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(
style_, border_rect, sides_to_include);
// Make sure that the border width isn't larger than the border box, which
// can pixel snap smaller.
float max_width = outer_.Rect().width();
float max_height = outer_.Rect().height();
Edge(BoxSide::kTop).ClampWidth(max_height);
Edge(BoxSide::kRight).ClampWidth(max_width);
Edge(BoxSide::kBottom).ClampWidth(max_height);
Edge(BoxSide::kLeft).ClampWidth(max_width);
is_rounded_ = outer_.IsRounded();
element_role_ = DarkModeFilter::ElementRole::kBorder;
}
BoxBorderPainter::BoxBorderPainter(GraphicsContext& context,
const ComputedStyle& style,
const PhysicalRect& border_rect,
int width,
const PhysicalBoxStrut& inner_outsets)
: context_(context),
border_rect_(border_rect),
outer_outsets_(inner_outsets + PhysicalBoxStrut(LayoutUnit(width))),
style_(style),
bleed_avoidance_(kBackgroundBleedNone),
sides_to_include_(PhysicalBoxSides()),
visible_edge_count_(0),
first_visible_edge_(0),
visible_edge_set_(0),
is_uniform_style_(true),
is_uniform_width_(true),
is_uniform_color_(true),
is_rounded_(false),
has_transparency_(false) {
DCHECK(style.HasOutline());
BorderEdge edge(width,
style.VisitedDependentColor(GetCSSPropertyOutlineColor()),
style.OutlineStyle());
for (auto& e : edges_)
e = edge;
ComputeBorderProperties();
outer_ = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style, border_rect, outer_outsets_);
is_rounded_ = outer_.IsRounded();
inner_ = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style, border_rect, inner_outsets);
element_role_ = DarkModeFilter::ElementRole::kBackground;
}
void BoxBorderPainter::ComputeBorderProperties() {
for (unsigned i = 0; i < std::size(edges_); ++i) {
const BorderEdge& edge = edges_[i];
if (!edge.ShouldRender()) {
if (edge.PresentButInvisible()) {
is_uniform_width_ = false;
is_uniform_color_ = false;
}
continue;
}
DCHECK(!edge.GetColor().IsFullyTransparent());
visible_edge_count_++;
visible_edge_set_ |= EdgeFlagForSide(static_cast<BoxSide>(i));
if (!edge.GetColor().IsOpaque()) {
has_transparency_ = true;
}
if (visible_edge_count_ == 1) {
first_visible_edge_ = i;
continue;
}
is_uniform_style_ &=
edge.BorderStyle() == edges_[first_visible_edge_].BorderStyle();
is_uniform_width_ &= edge.Width() == edges_[first_visible_edge_].Width();
is_uniform_color_ &= edge.SharesColorWith(edges_[first_visible_edge_]);
}
}
void BoxBorderPainter::Paint() const {
if (!visible_edge_count_ || outer_.Rect().IsEmpty())
return;
if (PaintBorderFastPath())
return;
bool clip_to_outer_border = outer_.IsRounded();
GraphicsContextStateSaver state_saver(context_, clip_to_outer_border);
if (clip_to_outer_border) {
// For BackgroundBleedClip{Only,Layer}, the outer rrect clip is already
// applied.
if (!BleedAvoidanceIsClipping(bleed_avoidance_))
context_.ClipRoundedRect(outer_);
if (inner_.IsRenderable() && !inner_.IsEmpty())
context_.ClipOutRoundedRect(inner_);
}
const ComplexBorderInfo border_info(*this);
PaintOpacityGroup(border_info, 0, 1);
}
// In order to maximize the use of overdraw as a corner seam avoidance
// technique, we draw translucent border sides using the following algorithm:
//
// 1) cluster sides sharing the same opacity into "opacity groups"
// [ComplexBorderInfo]
// 2) sort groups in increasing opacity order [ComplexBorderInfo]
// 3) reverse-iterate over groups (decreasing opacity order), pushing nested
// transparency layers with adjusted/relative opacity [paintOpacityGroup]
// 4) iterate over groups (increasing opacity order), painting actual group
// contents and then ending their corresponding transparency layer
// [PaintOpacityGroup]
//
// Layers are created in decreasing opacity order (top -> bottom), while actual
// border sides are drawn in increasing opacity order (bottom -> top). At each
// level, opacity is adjusted to account for accumulated/ancestor layer alpha.
// Because opacity is applied via layers, the actual draw paint is opaque.
//
// As an example, let's consider a border with the following sides/opacities:
//
// top: 1.0
// right: 0.25
// bottom: 0.5
// left: 0.25
//
// These are grouped and sorted in ComplexBorderInfo as follows:
//
// group[0]: { alpha: 1.0, sides: top }
// group[1]: { alpha: 0.5, sides: bottom }
// group[2]: { alpha: 0.25, sides: right, left }
//
// Applying the algorithm yields the following paint sequence:
//
// // no layer needed for group 0 (alpha = 1)
// beginLayer(0.5) // layer for group 1
// beginLayer(0.5) // layer for group 2 (alpha: 0.5 * 0.5 = 0.25)
// paintSides(right, left) // paint group 2
// endLayer
// paintSides(bottom) // paint group 1
// endLayer
// paintSides(top) // paint group 0
//
// Note that we're always drawing using opaque paints on top of less-opaque
// content - hence we can use overdraw to mask portions of the previous sides.
//
BorderEdgeFlags BoxBorderPainter::PaintOpacityGroup(
const ComplexBorderInfo& border_info,
unsigned index,
float effective_opacity) const {
DCHECK(effective_opacity > 0 && effective_opacity <= 1);
const wtf_size_t opacity_group_count = border_info.opacity_groups.size();
// For overdraw logic purposes, treat missing/transparent edges as completed.
if (index >= opacity_group_count)
return ~visible_edge_set_;
// Groups are sorted in increasing opacity order, but we need to create layers
// in decreasing opacity order - hence the reverse iteration.
const OpacityGroup& group =
border_info.opacity_groups[opacity_group_count - index - 1];
// Adjust this group's paint opacity to account for ancestor transparency
// layers (needed in case we avoid creating a layer below).
float paint_alpha = group.alpha / effective_opacity;
DCHECK_LE(paint_alpha, 1.0f);
// For the last (bottom) group, we can skip the layer even in the presence of
// opacity iff it contains no adjecent edges (no in-group overdraw
// possibility).
bool needs_layer =
group.alpha != 1.0f && (IncludesAdjacentEdges(group.edge_flags) ||
(index + 1 < border_info.opacity_groups.size()));
if (needs_layer) {
DCHECK_LT(group.alpha, effective_opacity);
context_.BeginLayer(group.alpha / effective_opacity);
effective_opacity = group.alpha;
// Group opacity is applied via a layer => we draw the members using opaque
// paint.
paint_alpha = 1.0f;
}
// Recursion may seem unpalatable here, but
// a) it has an upper bound of 4
// b) only triggers at all when mixing border sides with different opacities
// c) it allows us to express the layer nesting algorithm more naturally
BorderEdgeFlags completed_edges =
PaintOpacityGroup(border_info, index + 1, effective_opacity);
// Paint the actual group edges with an alpha adjusted to account for
// ancenstor layers opacity.
for (BoxSide side : group.sides) {
PaintSide(border_info, side, paint_alpha, completed_edges);
completed_edges |= EdgeFlagForSide(side);
}
if (needs_layer)
context_.EndLayer();
return completed_edges;
}
void BoxBorderPainter::PaintSide(const ComplexBorderInfo& border_info,
BoxSide side,
float alpha,
BorderEdgeFlags completed_edges) const {
const BorderEdge& edge = Edge(side);
DCHECK(edge.ShouldRender());
const Color color = Color::FromColorSpace(
edge.GetColor().GetColorSpace(), edge.GetColor().Param0(),
edge.GetColor().Param1(), edge.GetColor().Param2(), alpha);
gfx::Rect side_rect = gfx::ToRoundedRect(outer_.Rect());
const Path* path = nullptr;
// TODO(fmalita): find a way to consolidate these without sacrificing
// readability.
switch (side) {
case BoxSide::kTop: {
bool use_path =
is_rounded_ && (BorderStyleHasInnerDetail(edge.BorderStyle()) ||
BorderWillArcInnerEdge(inner_.GetRadii().TopLeft(),
inner_.GetRadii().TopRight()));
if (use_path) {
path = &border_info.rounded_border_path;
} else {
side_rect.set_height(edge.Width());
}
PaintOneBorderSide(side_rect, BoxSide::kTop, BoxSide::kLeft,
BoxSide::kRight, path, color, completed_edges);
break;
}
case BoxSide::kBottom: {
bool use_path = is_rounded_ &&
(BorderStyleHasInnerDetail(edge.BorderStyle()) ||
BorderWillArcInnerEdge(inner_.GetRadii().BottomLeft(),
inner_.GetRadii().BottomRight()));
if (use_path) {
path = &border_info.rounded_border_path;
} else {
SetToBottomSideRect(side_rect, edge.Width());
}
PaintOneBorderSide(side_rect, BoxSide::kBottom, BoxSide::kLeft,
BoxSide::kRight, path, color, completed_edges);
break;
}
case BoxSide::kLeft: {
bool use_path =
is_rounded_ && (BorderStyleHasInnerDetail(edge.BorderStyle()) ||
BorderWillArcInnerEdge(inner_.GetRadii().BottomLeft(),
inner_.GetRadii().TopLeft()));
if (use_path) {
path = &border_info.rounded_border_path;
} else {
side_rect.set_width(edge.Width());
}
PaintOneBorderSide(side_rect, BoxSide::kLeft, BoxSide::kTop,
BoxSide::kBottom, path, color, completed_edges);
break;
}
case BoxSide::kRight: {
bool use_path = is_rounded_ &&
(BorderStyleHasInnerDetail(edge.BorderStyle()) ||
BorderWillArcInnerEdge(inner_.GetRadii().BottomRight(),
inner_.GetRadii().TopRight()));
if (use_path) {
path = &border_info.rounded_border_path;
} else {
SetToRightSideRect(side_rect, edge.Width());
}
PaintOneBorderSide(side_rect, BoxSide::kRight, BoxSide::kTop,
BoxSide::kBottom, path, color, completed_edges);
break;
}
default:
NOTREACHED();
}
}
BoxBorderPainter::MiterType BoxBorderPainter::ComputeMiter(
BoxSide side,
BoxSide adjacent_side,
BorderEdgeFlags completed_edges) const {
const BorderEdge& adjacent_edge = Edge(adjacent_side);
// No miters for missing edges.
if (!adjacent_edge.UsedWidth()) {
return kNoMiter;
}
// The adjacent edge will overdraw this corner, resulting in a correct miter.
if (WillOverdraw(adjacent_side, adjacent_edge.BorderStyle(), completed_edges))
return kNoMiter;
// Color transitions require miters. Use miters compatible with the AA drawing
// mode to avoid introducing extra clips.
if (!ColorsMatchAtCorner(side, adjacent_side))
return kSoftMiter;
// Non-anti-aliased miters ensure correct same-color seaming when required by
// style.
if (BorderStylesRequireMiter(side, adjacent_side, Edge(side).BorderStyle(),
adjacent_edge.BorderStyle()))
return kHardMiter;
// Overdraw the adjacent edge when the colors match and we have no style
// restrictions.
return kNoMiter;
}
bool BoxBorderPainter::MitersRequireClipping(MiterType miter1,
MiterType miter2,
EBorderStyle style) {
// Clipping is required if any of the present miters doesn't match the current
// AA mode.
bool should_clip = miter1 == kHardMiter || miter2 == kHardMiter;
// Some styles require clipping for any type of miter.
should_clip = should_clip || ((miter1 != kNoMiter || miter2 != kNoMiter) &&
StyleRequiresClipPolygon(style));
return should_clip;
}
void BoxBorderPainter::PaintOneBorderSide(
const gfx::Rect& side_rect,
BoxSide side,
BoxSide adjacent_side1,
BoxSide adjacent_side2,
const Path* path,
Color color,
BorderEdgeFlags completed_edges) const {
const BorderEdge& edge_to_render = Edge(side);
DCHECK(edge_to_render.Width());
const BorderEdge& adjacent_edge1 = Edge(adjacent_side1);
const BorderEdge& adjacent_edge2 = Edge(adjacent_side2);
if (path) {
MiterType miter1 =
ColorsMatchAtCorner(side, adjacent_side1) ? kHardMiter : kSoftMiter;
MiterType miter2 =
ColorsMatchAtCorner(side, adjacent_side2) ? kHardMiter : kSoftMiter;
GraphicsContextStateSaver state_saver(context_);
ClipBorderSidePolygon(side, miter1, miter2);
if (!inner_.IsRenderable()) {
FloatRoundedRect adjusted_inner_rect =
CalculateAdjustedInnerBorder(inner_, side);
if (!adjusted_inner_rect.IsEmpty()) {
context_.ClipOutRoundedRect(adjusted_inner_rect);
}
}
int stroke_thickness =
std::max(std::max(edge_to_render.Width(), adjacent_edge1.Width()),
adjacent_edge2.Width());
DrawBoxSideFromPath(*path, edge_to_render.Width(), stroke_thickness, side,
color, edge_to_render.BorderStyle());
} else {
MiterType miter1 = ComputeMiter(side, adjacent_side1, completed_edges);
MiterType miter2 = ComputeMiter(side, adjacent_side2, completed_edges);
bool should_clip =
MitersRequireClipping(miter1, miter2, edge_to_render.BorderStyle());
GraphicsContextStateSaver clip_state_saver(context_, should_clip);
if (should_clip) {
ClipBorderSidePolygon(side, miter1, miter2);
// Miters are applied via clipping, no need to draw them.
miter1 = miter2 = kNoMiter;
}
DrawLineForBoxSide(
context_, side_rect.x(), side_rect.y(), side_rect.right(),
side_rect.bottom(), side, color, edge_to_render.BorderStyle(),
miter1 != kNoMiter ? adjacent_edge1.Width() : 0,
miter2 != kNoMiter ? adjacent_edge2.Width() : 0,
/*antialias*/ true, PaintAutoDarkMode(style_, element_role_));
}
}
void BoxBorderPainter::DrawBoxSideFromPath(const Path& border_path,
int border_thickness,
int stroke_thickness,
BoxSide side,
Color color,
EBorderStyle border_style) const {
if (border_thickness <= 0)
return;
// The caller should have adjusted border_style.
DCHECK_EQ(border_style,
BorderEdge::EffectiveStyle(border_style, border_thickness));
switch (border_style) {
case EBorderStyle::kNone:
case EBorderStyle::kHidden:
return;
case EBorderStyle::kDotted:
case EBorderStyle::kDashed: {
DrawDashedDottedBoxSideFromPath(border_thickness, stroke_thickness, color,
border_style);
return;
}
case EBorderStyle::kDouble: {
DrawDoubleBoxSideFromPath(border_path, border_thickness, stroke_thickness,
side, color);
return;
}
case EBorderStyle::kRidge:
case EBorderStyle::kGroove: {
DrawRidgeGrooveBoxSideFromPath(border_path, border_thickness,
stroke_thickness, side, color,
border_style);
return;
}
case EBorderStyle::kInset:
case EBorderStyle::kOutset:
color = CalculateBorderStyleColor(border_style, side, color);
break;
default:
break;
}
context_.SetFillColor(color);
context_.FillRect(gfx::ToRoundedRect(outer_.Rect()),
PaintAutoDarkMode(style_, element_role_));
}
void BoxBorderPainter::DrawDashedDottedBoxSideFromPath(
int border_thickness,
int stroke_thickness,
Color color,
EBorderStyle border_style) const {
// Convert the path to be down the middle of the dots or dashes.
Path centerline_path;
centerline_path.AddRoundedRect(
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, border_rect_, CenterOutsets(), sides_to_include_));
context_.SetStrokeColor(color);
const StrokeStyle stroke_style =
border_style == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke;
if (!StyledStrokeData::StrokeIsDashed(border_thickness, stroke_style)) {
DrawWideDottedBoxSideFromPath(centerline_path, border_thickness);
return;
}
// The stroke is doubled here because the provided path is the
// outside edge of the border so half the stroke is clipped off, with
// the extra multiplier so that the clipping mask can antialias
// the edges to prevent jaggies.
const float thickness_multiplier = 2 * 1.1f;
StyledStrokeData styled_stroke;
styled_stroke.SetThickness(stroke_thickness * thickness_multiplier);
styled_stroke.SetStyle(stroke_style);
// TODO(crbug.com/344234): stroking the border path causes issues with
// tight corners.
const StrokeData stroke_data = styled_stroke.ConvertToStrokeData(
{static_cast<int>(centerline_path.length()), border_thickness,
centerline_path.IsClosed()});
context_.SetStroke(stroke_data);
context_.StrokePath(centerline_path,
PaintAutoDarkMode(style_, element_role_));
}
void BoxBorderPainter::DrawWideDottedBoxSideFromPath(
const Path& border_path,
int border_thickness) const {
StyledStrokeData styled_stroke;
styled_stroke.SetThickness(border_thickness);
styled_stroke.SetStyle(kDottedStroke);
// TODO(crbug.com/344234): stroking the border path causes issues with
// tight corners.
const StrokeData stroke_data = styled_stroke.ConvertToStrokeData(
{static_cast<int>(border_path.length()), border_thickness,
border_path.IsClosed()});
context_.SetStroke(stroke_data);
context_.StrokePath(border_path, PaintAutoDarkMode(style_, element_role_));
}
void BoxBorderPainter::DrawDoubleBoxSideFromPath(const Path& border_path,
int border_thickness,
int stroke_thickness,
BoxSide side,
Color color) const {
// Draw inner border line
{
GraphicsContextStateSaver state_saver(context_);
const PhysicalBoxStrut inner_outsets =
DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeInner);
FloatRoundedRect inner_clip =
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, border_rect_, inner_outsets, sides_to_include_);
context_.ClipRoundedRect(inner_clip);
DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side,
color, EBorderStyle::kSolid);
}
// Draw outer border line
{
GraphicsContextStateSaver state_saver(context_);
PhysicalRect used_border_rect = border_rect_;
PhysicalBoxStrut outer_outsets =
DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeOuter);
if (BleedAvoidanceIsClipping(bleed_avoidance_)) {
used_border_rect.Inflate(LayoutUnit(1));
outer_outsets.Inflate(LayoutUnit(-1));
}
FloatRoundedRect outer_clip =
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, used_border_rect, outer_outsets, sides_to_include_);
context_.ClipOutRoundedRect(outer_clip);
DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side,
color, EBorderStyle::kSolid);
}
}
void BoxBorderPainter::DrawRidgeGrooveBoxSideFromPath(
const Path& border_path,
int border_thickness,
int stroke_thickness,
BoxSide side,
Color color,
EBorderStyle border_style) const {
EBorderStyle s1;
EBorderStyle s2;
if (border_style == EBorderStyle::kGroove) {
s1 = EBorderStyle::kInset;
s2 = EBorderStyle::kOutset;
} else {
s1 = EBorderStyle::kOutset;
s2 = EBorderStyle::kInset;
}
// Paint full border
DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side,
color, s1);
// Paint inner only
GraphicsContextStateSaver state_saver(context_);
FloatRoundedRect clip_rect =
RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets(
style_, border_rect_, CenterOutsets(), sides_to_include_);
context_.ClipRoundedRect(clip_rect);
DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side,
color, s2);
}
gfx::Rect BoxBorderPainter::CalculateSideRectIncludingInner(
BoxSide side) const {
gfx::Rect side_rect = gfx::ToRoundedRect(outer_.Rect());
int width;
switch (side) {
case BoxSide::kTop:
width = side_rect.height() - Edge(BoxSide::kBottom).Width();
side_rect.set_height(width);
break;
case BoxSide::kBottom:
width = side_rect.height() - Edge(BoxSide::kTop).Width();
SetToBottomSideRect(side_rect, width);
break;
case BoxSide::kLeft:
width = side_rect.width() - Edge(BoxSide::kRight).Width();
side_rect.set_width(width);
break;
case BoxSide::kRight:
width = side_rect.width() - Edge(BoxSide::kLeft).Width();
SetToRightSideRect(side_rect, width);
break;
}
return side_rect;
}
void BoxBorderPainter::ClipBorderSidePolygon(BoxSide side,
MiterType first_miter,
MiterType second_miter) const {
DCHECK(first_miter != kNoMiter || second_miter != kNoMiter);
// The boundary of the edge for fill.
gfx::PointF edge_quad[4];
Vector<gfx::PointF, 5> edge_pentagon;
// Point 1 of the rectilinear bounding box of edge_quad.
gfx::PointF bound_quad1;
// Point 2 of the rectilinear bounding box of edge_quad.
gfx::PointF bound_quad2;
// For each side, create a quad that encompasses all parts of that side that
// may draw, including areas inside the innerBorder.
//
// 0----------------3
// 3 \ / 0
// |\ 1----------- 2 /|
// | 2 1 |
// | | | |
// | | | |
// | 1 2 |
// |/ 2------------1 \|
// 0 / \ 3
// 3----------------0
// Points 1 and 2 of each quad are initially the corresponding corners of the
// inner rect. If an inner corner is rounded, the corresponding point will be
// moved inside to ensure the quad contains the half corner.
// However, if the inner border is not renderable, and line 1-2 would clip the
// rounded corner near the miter, we need to insert a point between 1 and 2 to
// create a pentagon.
// 0-------------3 0-------------3 0-------------4
// |\ /| |\ /| |\ /|
// | 1---------2 | | \---------2 | | \---------3 |
// | | | | | |\ /| | | |\ | |
// | | | | | | \ / | | | | \ | |
// | | | | --> | | \ / | | --> | | \ | |
// | | | | | | 1 | | | | 1----2 |
// | | | | | | | | | | | |
// | /---------\ | | /---------\ | | /---------\ |
// ------------- ------------- -------------
const gfx::PointF inner_points[4] = {
inner_.Rect().origin(),
inner_.Rect().top_right(),
inner_.Rect().bottom_right(),
inner_.Rect().bottom_left(),
};
const gfx::PointF outer_points[4] = {
outer_.Rect().origin(),
outer_.Rect().top_right(),
outer_.Rect().bottom_right(),
outer_.Rect().bottom_left(),
};
// Offset size and direction to expand clipping quad
const static float kExtensionLength = 1e-1f;
gfx::Vector2dF extension_offset;
switch (side) {
case BoxSide::kTop:
edge_quad[0] = outer_points[0];
edge_quad[1] = inner_points[0];
edge_quad[2] = inner_points[1];
edge_quad[3] = outer_points[1];
DCHECK(edge_quad[0].y() == edge_quad[3].y());
DCHECK(edge_quad[1].y() == edge_quad[2].y());
bound_quad1 = gfx::PointF(edge_quad[0].x(), edge_quad[1].y());
bound_quad2 = gfx::PointF(edge_quad[3].x(), edge_quad[2].y());
extension_offset.set_x(-kExtensionLength);
extension_offset.set_y(0);
if (!inner_.GetRadii().TopLeft().IsZero()) {
FindIntersection(
edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x() + inner_.GetRadii().TopLeft().width(),
edge_quad[1].y()),
gfx::PointF(
edge_quad[1].x(),
edge_quad[1].y() + inner_.GetRadii().TopLeft().height()),
edge_quad[1]);
DCHECK(bound_quad1.y() <= edge_quad[1].y());
bound_quad1.set_y(edge_quad[1].y());
bound_quad2.set_y(edge_quad[1].y());
if (edge_quad[1].y() > inner_points[2].y()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[3],
inner_points[2], edge_quad[1]);
}
if (edge_quad[1].x() > inner_points[2].x()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[1],
inner_points[2], edge_quad[1]);
}
if (edge_quad[2].y() < edge_quad[1].y() &&
edge_quad[2].x() > edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[2].x(), edge_quad[1].y()),
edge_quad[2], edge_quad[3]};
}
}
if (!inner_.GetRadii().TopRight().IsZero()) {
FindIntersection(
edge_quad[3], edge_quad[2],
gfx::PointF(edge_quad[2].x() - inner_.GetRadii().TopRight().width(),
edge_quad[2].y()),
gfx::PointF(
edge_quad[2].x(),
edge_quad[2].y() + inner_.GetRadii().TopRight().height()),
edge_quad[2]);
if (bound_quad1.y() < edge_quad[2].y()) {
bound_quad1.set_y(edge_quad[2].y());
bound_quad2.set_y(edge_quad[2].y());
}
if (edge_quad[2].y() > inner_points[3].y()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[3],
inner_points[2], edge_quad[2]);
}
if (edge_quad[2].x() < inner_points[3].x()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[0],
inner_points[3], edge_quad[2]);
}
if (edge_quad[2].y() > edge_quad[1].y() &&
edge_quad[2].x() > edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x(), edge_quad[2].y()),
edge_quad[2], edge_quad[3]};
}
}
break;
case BoxSide::kLeft:
// Swap the order of adjacent edges to allow common code
std::swap(first_miter, second_miter);
edge_quad[0] = outer_points[3];
edge_quad[1] = inner_points[3];
edge_quad[2] = inner_points[0];
edge_quad[3] = outer_points[0];
DCHECK(edge_quad[0].x() == edge_quad[3].x());
DCHECK(edge_quad[1].x() == edge_quad[2].x());
bound_quad1 = gfx::PointF(edge_quad[1].x(), edge_quad[0].y());
bound_quad2 = gfx::PointF(edge_quad[2].x(), edge_quad[3].y());
extension_offset.set_x(0);
extension_offset.set_y(kExtensionLength);
if (!inner_.GetRadii().TopLeft().IsZero()) {
FindIntersection(
edge_quad[3], edge_quad[2],
gfx::PointF(edge_quad[2].x() + inner_.GetRadii().TopLeft().width(),
edge_quad[2].y()),
gfx::PointF(
edge_quad[2].x(),
edge_quad[2].y() + inner_.GetRadii().TopLeft().height()),
edge_quad[2]);
DCHECK(bound_quad2.x() <= edge_quad[2].x());
bound_quad1.set_x(edge_quad[2].x());
bound_quad2.set_x(edge_quad[2].x());
if (edge_quad[2].y() > inner_points[2].y()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[3],
inner_points[2], edge_quad[2]);
}
if (edge_quad[2].x() > inner_points[2].x()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[1],
inner_points[2], edge_quad[2]);
}
if (edge_quad[2].y() < edge_quad[1].y() &&
edge_quad[2].x() > edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[2].x(), edge_quad[1].y()),
edge_quad[2], edge_quad[3]};
}
}
if (!inner_.GetRadii().BottomLeft().IsZero()) {
FindIntersection(
edge_quad[0], edge_quad[1],
gfx::PointF(
edge_quad[1].x() + inner_.GetRadii().BottomLeft().width(),
edge_quad[1].y()),
gfx::PointF(
edge_quad[1].x(),
edge_quad[1].y() - inner_.GetRadii().BottomLeft().height()),
edge_quad[1]);
if (bound_quad1.x() < edge_quad[1].x()) {
bound_quad1.set_x(edge_quad[1].x());
bound_quad2.set_x(edge_quad[1].x());
}
if (edge_quad[1].y() < inner_points[1].y()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[0],
inner_points[1], edge_quad[1]);
}
if (edge_quad[1].x() > inner_points[1].x()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[1],
inner_points[2], edge_quad[1]);
}
if (edge_quad[2].y() < edge_quad[1].y() &&
edge_quad[2].x() < edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x(), edge_quad[2].y()),
edge_quad[2], edge_quad[3]};
}
}
break;
case BoxSide::kBottom:
// Swap the order of adjacent edges to allow common code
std::swap(first_miter, second_miter);
edge_quad[0] = outer_points[2];
edge_quad[1] = inner_points[2];
edge_quad[2] = inner_points[3];
edge_quad[3] = outer_points[3];
DCHECK(edge_quad[0].y() == edge_quad[3].y());
DCHECK(edge_quad[1].y() == edge_quad[2].y());
bound_quad1 = gfx::PointF(edge_quad[0].x(), edge_quad[1].y());
bound_quad2 = gfx::PointF(edge_quad[3].x(), edge_quad[2].y());
extension_offset.set_x(kExtensionLength);
extension_offset.set_y(0);
if (!inner_.GetRadii().BottomLeft().IsZero()) {
FindIntersection(
edge_quad[3], edge_quad[2],
gfx::PointF(
edge_quad[2].x() + inner_.GetRadii().BottomLeft().width(),
edge_quad[2].y()),
gfx::PointF(
edge_quad[2].x(),
edge_quad[2].y() - inner_.GetRadii().BottomLeft().height()),
edge_quad[2]);
DCHECK(bound_quad2.y() >= edge_quad[2].y());
bound_quad1.set_y(edge_quad[2].y());
bound_quad2.set_y(edge_quad[2].y());
if (edge_quad[2].y() < inner_points[1].y()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[0],
inner_points[1], edge_quad[2]);
}
if (edge_quad[2].x() > inner_points[1].x()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[1],
inner_points[2], edge_quad[2]);
}
if (edge_quad[2].y() < edge_quad[1].y() &&
edge_quad[2].x() < edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x(), edge_quad[2].y()),
edge_quad[2], edge_quad[3]};
}
}
if (!inner_.GetRadii().BottomRight().IsZero()) {
FindIntersection(
edge_quad[0], edge_quad[1],
gfx::PointF(
edge_quad[1].x() - inner_.GetRadii().BottomRight().width(),
edge_quad[1].y()),
gfx::PointF(
edge_quad[1].x(),
edge_quad[1].y() - inner_.GetRadii().BottomRight().height()),
edge_quad[1]);
if (bound_quad1.y() > edge_quad[1].y()) {
bound_quad1.set_y(edge_quad[1].y());
bound_quad2.set_y(edge_quad[1].y());
}
if (edge_quad[1].y() < inner_points[0].y()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[0],
inner_points[1], edge_quad[1]);
}
if (edge_quad[1].x() < inner_points[0].x()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[0],
inner_points[3], edge_quad[1]);
}
if (edge_quad[2].x() < edge_quad[1].x() &&
edge_quad[2].y() > edge_quad[1].y()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[2].x(), edge_quad[1].y()),
edge_quad[2], edge_quad[3]};
}
}
break;
case BoxSide::kRight:
edge_quad[0] = outer_points[1];
edge_quad[1] = inner_points[1];
edge_quad[2] = inner_points[2];
edge_quad[3] = outer_points[2];
DCHECK(edge_quad[0].x() == edge_quad[3].x());
DCHECK(edge_quad[1].x() == edge_quad[2].x());
bound_quad1 = gfx::PointF(edge_quad[1].x(), edge_quad[0].y());
bound_quad2 = gfx::PointF(edge_quad[2].x(), edge_quad[3].y());
extension_offset.set_x(0);
extension_offset.set_y(-kExtensionLength);
if (!inner_.GetRadii().TopRight().IsZero()) {
FindIntersection(
edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x() - inner_.GetRadii().TopRight().width(),
edge_quad[1].y()),
gfx::PointF(
edge_quad[1].x(),
edge_quad[1].y() + inner_.GetRadii().TopRight().height()),
edge_quad[1]);
DCHECK(bound_quad1.x() >= edge_quad[1].x());
bound_quad1.set_x(edge_quad[1].x());
bound_quad2.set_x(edge_quad[1].x());
if (edge_quad[1].y() > inner_points[3].y()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[3],
inner_points[2], edge_quad[1]);
}
if (edge_quad[1].x() < inner_points[3].x()) {
FindIntersection(edge_quad[0], edge_quad[1], inner_points[0],
inner_points[3], edge_quad[1]);
}
if (edge_quad[2].y() > edge_quad[1].y() &&
edge_quad[2].x() > edge_quad[1].x()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[1].x(), edge_quad[2].y()),
edge_quad[2], edge_quad[3]};
}
}
if (!inner_.GetRadii().BottomRight().IsZero()) {
FindIntersection(
edge_quad[3], edge_quad[2],
gfx::PointF(
edge_quad[2].x() - inner_.GetRadii().BottomRight().width(),
edge_quad[2].y()),
gfx::PointF(
edge_quad[2].x(),
edge_quad[2].y() - inner_.GetRadii().BottomRight().height()),
edge_quad[2]);
if (bound_quad1.x() > edge_quad[2].x()) {
bound_quad1.set_x(edge_quad[2].x());
bound_quad2.set_x(edge_quad[2].x());
}
if (edge_quad[2].y() < inner_points[0].y()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[0],
inner_points[1], edge_quad[2]);
}
if (edge_quad[2].x() < inner_points[0].x()) {
FindIntersection(edge_quad[3], edge_quad[2], inner_points[0],
inner_points[3], edge_quad[2]);
}
if (edge_quad[2].x() < edge_quad[1].x() &&
edge_quad[2].y() > edge_quad[1].y()) {
edge_pentagon = {edge_quad[0], edge_quad[1],
gfx::PointF(edge_quad[2].x(), edge_quad[1].y()),
edge_quad[2], edge_quad[3]};
}
}
break;
}
if (first_miter == second_miter) {
if (!edge_pentagon.empty() && !inner_.IsRenderable()) {
DCHECK_EQ(edge_pentagon.size(), 5u);
ClipPolygon(context_, edge_pentagon.data(), 5, first_miter == kSoftMiter);
return;
}
ClipPolygon(context_, edge_quad, 4, first_miter == kSoftMiter);
return;
}
// If antialiasing settings for the first edge and second edge are different,
// they have to be addressed separately. We do this by applying 2 clips, one
// for each miter, with the appropriate anti-aliasing setting. Each clip uses
// 3 sides of the quad rectilinear bounding box and a 4th side aligned with
// the miter edge. We extend the clip in the miter direction to ensure overlap
// as each edge is drawn.
if (first_miter != kNoMiter) {
gfx::PointF clipping_quad[4];
clipping_quad[0] = edge_quad[0] + extension_offset;
FindIntersection(edge_quad[0], edge_quad[1], bound_quad1, bound_quad2,
clipping_quad[1]);
clipping_quad[1] += extension_offset;
clipping_quad[2] = bound_quad2;
clipping_quad[3] = edge_quad[3];
ClipPolygon(context_, clipping_quad, 4, first_miter == kSoftMiter);
}
if (second_miter != kNoMiter) {
gfx::PointF clipping_quad[4];
clipping_quad[0] = edge_quad[0];
clipping_quad[1] = bound_quad1;
FindIntersection(edge_quad[2], edge_quad[3], bound_quad1, bound_quad2,
clipping_quad[2]);
clipping_quad[2] -= extension_offset;
clipping_quad[3] = edge_quad[3] - extension_offset;
ClipPolygon(context_, clipping_quad, 4, second_miter == kSoftMiter);
}
}
PhysicalBoxStrut BoxBorderPainter::DoubleStripeOutsets(
BorderEdge::DoubleBorderStripe stripe) const {
return outer_outsets_ -
PhysicalBoxStrut(
Edge(BoxSide::kTop).GetDoubleBorderStripeWidth(stripe),
Edge(BoxSide::kRight).GetDoubleBorderStripeWidth(stripe),
Edge(BoxSide::kBottom).GetDoubleBorderStripeWidth(stripe),
Edge(BoxSide::kLeft).GetDoubleBorderStripeWidth(stripe));
}
PhysicalBoxStrut BoxBorderPainter::CenterOutsets() const {
return outer_outsets_ -
PhysicalBoxStrut(Edge(BoxSide::kTop).UsedWidth() * 0.5,
Edge(BoxSide::kRight).UsedWidth() * 0.5,
Edge(BoxSide::kBottom).UsedWidth() * 0.5,
Edge(BoxSide::kLeft).UsedWidth() * 0.5);
}
bool BoxBorderPainter::ColorsMatchAtCorner(BoxSide side,
BoxSide adjacent_side) const {
if (!Edge(adjacent_side).ShouldRender())
return false;
if (!Edge(side).SharesColorWith(Edge(adjacent_side)))
return false;
return !BorderStyleHasUnmatchedColorsAtCorner(Edge(side).BorderStyle(), side,
adjacent_side);
}
void BoxBorderPainter::DrawBoxSide(GraphicsContext& context,
const gfx::Rect& snapped_edge_rect,
BoxSide side,
Color color,
EBorderStyle style,
const AutoDarkMode& auto_dark_mode) {
DrawLineForBoxSide(context, snapped_edge_rect.x(), snapped_edge_rect.y(),
snapped_edge_rect.right(), snapped_edge_rect.bottom(),
side, color, style, 0, 0, true, auto_dark_mode);
}
} // namespace blink