| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // 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 "base/stl_util.h" |
| #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_info.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/wtf/vector.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 ColorsMatchAtCorner(BoxSide side, |
| BoxSide adjacent_side, |
| const BorderEdge edges[]) { |
| if (!edges[static_cast<unsigned>(adjacent_side)].ShouldRender()) |
| return false; |
| |
| if (!edges[static_cast<unsigned>(side)].SharesColorWith( |
| edges[static_cast<unsigned>(adjacent_side)])) |
| return false; |
| |
| return !BorderStyleHasUnmatchedColorsAtCorner( |
| edges[static_cast<unsigned>(side)].BorderStyle(), side, adjacent_side); |
| } |
| |
| inline bool BorderWillArcInnerEdge(const FloatSize& first_radius, |
| const FloatSize& 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); |
| } |
| |
| FloatRect CalculateSideRect(const FloatRoundedRect& outer_border, |
| const BorderEdge& edge, |
| int side) { |
| FloatRect side_rect = outer_border.Rect(); |
| float width = edge.Width(); |
| |
| if (side == static_cast<unsigned>(BoxSide::kTop)) |
| side_rect.SetHeight(width); |
| else if (side == static_cast<unsigned>(BoxSide::kBottom)) |
| side_rect.ShiftYEdgeTo(side_rect.MaxY() - width); |
| else if (side == static_cast<unsigned>(BoxSide::kLeft)) |
| side_rect.SetWidth(width); |
| else |
| side_rect.ShiftXEdgeTo(side_rect.MaxX() - width); |
| |
| return side_rect; |
| } |
| |
| FloatRect CalculateSideRectIncludingInner(const FloatRoundedRect& outer_border, |
| const BorderEdge edges[], |
| BoxSide side) { |
| FloatRect side_rect = outer_border.Rect(); |
| float width; |
| |
| switch (side) { |
| case BoxSide::kTop: |
| width = side_rect.Height() - |
| edges[static_cast<unsigned>(BoxSide::kBottom)].Width(); |
| side_rect.SetHeight(width); |
| break; |
| case BoxSide::kBottom: |
| width = side_rect.Height() - |
| edges[static_cast<unsigned>(BoxSide::kTop)].Width(); |
| side_rect.ShiftYEdgeTo(side_rect.MaxY() - width); |
| break; |
| case BoxSide::kLeft: |
| width = side_rect.Width() - |
| edges[static_cast<unsigned>(BoxSide::kRight)].Width(); |
| side_rect.SetWidth(width); |
| break; |
| case BoxSide::kRight: |
| width = side_rect.Width() - |
| edges[static_cast<unsigned>(BoxSide::kLeft)].Width(); |
| side_rect.ShiftXEdgeTo(side_rect.MaxX() - 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(); |
| FloatRect 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.SetWidth(new_rect.Width() + overshoot); |
| if (!new_radii.TopLeft().Width()) |
| new_rect.Move(-overshoot, 0); |
| } |
| new_radii.SetBottomLeft(FloatSize(0, 0)); |
| new_radii.SetBottomRight(FloatSize(0, 0)); |
| max_radii = |
| std::max(new_radii.TopLeft().Height(), new_radii.TopRight().Height()); |
| if (max_radii > new_rect.Height()) |
| new_rect.SetHeight(max_radii); |
| break; |
| |
| case BoxSide::kBottom: |
| overshoot = new_radii.BottomLeft().Width() + |
| new_radii.BottomRight().Width() - new_rect.Width(); |
| if (overshoot > 0.1) { |
| new_rect.SetWidth(new_rect.Width() + overshoot); |
| if (!new_radii.BottomLeft().Width()) |
| new_rect.Move(-overshoot, 0); |
| } |
| new_radii.SetTopLeft(FloatSize(0, 0)); |
| new_radii.SetTopRight(FloatSize(0, 0)); |
| max_radii = std::max(new_radii.BottomLeft().Height(), |
| new_radii.BottomRight().Height()); |
| if (max_radii > new_rect.Height()) { |
| new_rect.Move(0, new_rect.Height() - max_radii); |
| new_rect.SetHeight(max_radii); |
| } |
| break; |
| |
| case BoxSide::kLeft: |
| overshoot = new_radii.TopLeft().Height() + |
| new_radii.BottomLeft().Height() - new_rect.Height(); |
| if (overshoot > 0.1) { |
| new_rect.SetHeight(new_rect.Height() + overshoot); |
| if (!new_radii.TopLeft().Height()) |
| new_rect.Move(0, -overshoot); |
| } |
| new_radii.SetTopRight(FloatSize(0, 0)); |
| new_radii.SetBottomRight(FloatSize(0, 0)); |
| max_radii = |
| std::max(new_radii.TopLeft().Width(), new_radii.BottomLeft().Width()); |
| if (max_radii > new_rect.Width()) |
| new_rect.SetWidth(max_radii); |
| break; |
| |
| case BoxSide::kRight: |
| overshoot = new_radii.TopRight().Height() + |
| new_radii.BottomRight().Height() - new_rect.Height(); |
| if (overshoot > 0.1) { |
| new_rect.SetHeight(new_rect.Height() + overshoot); |
| if (!new_radii.TopRight().Height()) |
| new_rect.Move(0, -overshoot); |
| } |
| new_radii.SetTopLeft(FloatSize(0, 0)); |
| new_radii.SetBottomLeft(FloatSize(0, 0)); |
| max_radii = std::max(new_radii.TopRight().Width(), |
| new_radii.BottomRight().Width()); |
| if (max_radii > new_rect.Width()) { |
| new_rect.Move(new_rect.Width() - max_radii, 0); |
| new_rect.SetWidth(max_radii); |
| } |
| break; |
| } |
| |
| return FloatRoundedRect(new_rect, new_radii); |
| } |
| |
| LayoutRectOutsets DoubleStripeInsets(const BorderEdge edges[], |
| BorderEdge::DoubleBorderStripe stripe) { |
| // Insets are representes as negative outsets. |
| return LayoutRectOutsets( |
| -edges[static_cast<unsigned>(BoxSide::kTop)].GetDoubleBorderStripeWidth( |
| stripe), |
| -edges[static_cast<unsigned>(BoxSide::kRight)].GetDoubleBorderStripeWidth( |
| stripe), |
| -edges[static_cast<unsigned>(BoxSide::kBottom)] |
| .GetDoubleBorderStripeWidth(stripe), |
| -edges[static_cast<unsigned>(BoxSide::kLeft)].GetDoubleBorderStripeWidth( |
| stripe)); |
| } |
| |
| float ClampOrRound(float border_width) { |
| // Make sure non-zero borders never disappear |
| if (border_width > 0.0f && border_width <= 1.0f) |
| return 1.0f; |
| |
| return roundf(border_width); |
| } |
| |
| void DrawSolidBorderRect(GraphicsContext& context, |
| const FloatRect& border_rect, |
| float border_width, |
| const Color& color) { |
| FloatRect stroke_rect(border_rect); |
| border_width = ClampOrRound(border_width); |
| stroke_rect.Inflate(-border_width / 2); |
| |
| bool was_antialias = context.ShouldAntialias(); |
| if (!was_antialias) |
| context.SetShouldAntialias(true); |
| |
| context.SetStrokeStyle(kSolidStroke); |
| context.SetStrokeColor(color); |
| context.StrokeRect(stroke_rect, border_width); |
| |
| if (!was_antialias) |
| context.SetShouldAntialias(false); |
| } |
| |
| void DrawBleedAdjustedDRRect(GraphicsContext& context, |
| BackgroundBleedAvoidance bleed_avoidance, |
| const FloatRoundedRect& outer, |
| const FloatRoundedRect& inner, |
| Color color) { |
| 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(inner); |
| path.setFillType(SkPath::kInverseWinding_FillType); |
| |
| PaintFlags flags; |
| flags.setColor(color.Rgb()); |
| flags.setStyle(PaintFlags::kFill_Style); |
| flags.setAntiAlias(true); |
| context.DrawPath(path, flags); |
| |
| 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); |
| break; |
| } |
| FALLTHROUGH; |
| default: |
| context.FillDRRect(outer, inner, color); |
| 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 /* BorderStyleNone */, 0 /* BorderStyleHidden */, |
| 2 /* BorderStyleInset */, 2 /* BorderStyleGroove */, |
| 2 /* BorderStyleOutset */, 2 /* BorderStyleRidge */, |
| 1 /* BorderStyleDotted */, 1 /* BorderStyleDashed */, |
| 3 /* BorderStyleSolid */, 1 /* BorderStyleDouble */ |
| }; |
| |
| // Given the same style, prefer drawing in non-adjacent order to minimize the |
| // number of sides which require miters. |
| const unsigned kSidePriority[] = { |
| 0, /* BSTop */ |
| 2, /* BSRight */ |
| 1, /* BSBottom */ |
| 3, /* BSLeft */ |
| }; |
| |
| // Edges sharing the same opacity. Stores both a side list and an edge bitfield |
| // to support constant time iteration + membership tests. |
| struct OpacityGroup { |
| OpacityGroup(unsigned alpha) : edge_flags(0), alpha(alpha) {} |
| |
| Vector<BoxSide, 4> sides; |
| BorderEdgeFlags edge_flags; |
| unsigned alpha; |
| }; |
| |
| void ClipQuad(GraphicsContext& context, |
| const FloatPoint quad[], |
| bool antialiased) { |
| SkPath path; |
| path.moveTo(FloatPointToSkPoint(quad[0])); |
| path.lineTo(FloatPointToSkPoint(quad[1])); |
| path.lineTo(FloatPointToSkPoint(quad[2])); |
| path.lineTo(FloatPointToSkPoint(quad[3])); |
| |
| context.ClipPath(path, antialiased ? kAntiAliased : kNotAntiAliased); |
| } |
| |
| } // anonymous namespace |
| |
| // Holds edges grouped by opacity and sorted in paint order. |
| struct BoxBorderPainter::ComplexBorderInfo { |
| ComplexBorderInfo(const BoxBorderPainter& border_painter, bool anti_alias) |
| : anti_alias(anti_alias) { |
| 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.IsEmpty()); |
| |
| // 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.edges_[static_cast<unsigned>(a)]; |
| const BorderEdge& edge_b = |
| border_painter.edges_[static_cast<unsigned>(b)]; |
| |
| const unsigned alpha_a = edge_a.color.Alpha(); |
| const unsigned alpha_b = edge_b.color.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; |
| |
| bool anti_alias; |
| |
| private: |
| void BuildOpacityGroups(const BoxBorderPainter& border_painter, |
| const Vector<BoxSide, 4>& sorted_sides) { |
| unsigned current_alpha = 0; |
| for (BoxSide side : sorted_sides) { |
| const BorderEdge& edge = |
| border_painter.edges_[static_cast<unsigned>(side)]; |
| const unsigned edge_alpha = edge.color.Alpha(); |
| |
| DCHECK_GT(edge_alpha, 0u); |
| DCHECK_GE(edge_alpha, current_alpha); |
| if (edge_alpha != current_alpha) { |
| opacity_groups.push_back(OpacityGroup(edge_alpha)); |
| current_alpha = edge_alpha; |
| } |
| |
| DCHECK(!opacity_groups.IsEmpty()); |
| OpacityGroup& current_group = opacity_groups.back(); |
| current_group.sides.push_back(side); |
| current_group.edge_flags |= EdgeFlagForSide(side); |
| } |
| |
| DCHECK(!opacity_groups.IsEmpty()); |
| } |
| }; |
| |
| void BoxBorderPainter::DrawDoubleBorder(GraphicsContext& context, |
| const LayoutRect& border_rect) const { |
| DCHECK(is_uniform_color_); |
| DCHECK(is_uniform_style_); |
| DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble); |
| DCHECK(visible_edge_set_ == kAllBorderEdges); |
| |
| const Color color = FirstEdge().color; |
| |
| // When painting outlines, we ignore outer/inner radii. |
| const auto force_rectangular = !outer_.IsRounded() && !inner_.IsRounded(); |
| |
| // outer stripe |
| const LayoutRectOutsets outer_third_insets = |
| DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeOuter); |
| FloatRoundedRect outer_third_rect = style_.GetRoundedInnerBorderFor( |
| border_rect, outer_third_insets, include_logical_left_edge_, |
| include_logical_right_edge_); |
| if (force_rectangular) |
| outer_third_rect.SetRadii(FloatRoundedRect::Radii()); |
| DrawBleedAdjustedDRRect(context, bleed_avoidance_, outer_, outer_third_rect, |
| color); |
| |
| // inner stripe |
| const LayoutRectOutsets inner_third_insets = |
| DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeInner); |
| FloatRoundedRect inner_third_rect = style_.GetRoundedInnerBorderFor( |
| border_rect, inner_third_insets, include_logical_left_edge_, |
| include_logical_right_edge_); |
| if (force_rectangular) |
| inner_third_rect.SetRadii(FloatRoundedRect::Radii()); |
| context.FillDRRect(inner_third_rect, inner_, color); |
| } |
| |
| bool BoxBorderPainter::PaintBorderFastPath( |
| GraphicsContext& context, |
| const LayoutRect& border_rect) 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, outer_.Rect(), FirstEdge().Width(), |
| FirstEdge().color); |
| } else { |
| // 4-side, solid border => one drawDRRect() |
| DrawBleedAdjustedDRRect(context, bleed_avoidance_, outer_, inner_, |
| FirstEdge().color); |
| } |
| } else { |
| // 4-side, double border => 2x drawDRRect() |
| DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble); |
| DrawDoubleBorder(context, border_rect); |
| } |
| |
| 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_alpha_) { |
| DCHECK(visible_edge_set_ != kAllBorderEdges); |
| // solid, rectangular border => one drawPath() |
| Path path; |
| path.SetWindRule(RULE_NONZERO); |
| |
| for (unsigned int i = static_cast<unsigned>(BoxSide::kTop); |
| i <= static_cast<unsigned>(BoxSide::kLeft); ++i) { |
| const BorderEdge& curr_edge = edges_[i]; |
| if (curr_edge.ShouldRender()) |
| path.AddRect(CalculateSideRect(outer_, curr_edge, i)); |
| } |
| |
| context.SetFillColor(FirstEdge().color); |
| context.FillPath(path); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| BoxBorderPainter::BoxBorderPainter(const LayoutRect& border_rect, |
| const ComputedStyle& style, |
| BackgroundBleedAvoidance bleed_avoidance, |
| bool include_logical_left_edge, |
| bool include_logical_right_edge) |
| : style_(style), |
| bleed_avoidance_(bleed_avoidance), |
| include_logical_left_edge_(include_logical_left_edge), |
| include_logical_right_edge_(include_logical_right_edge), |
| 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_alpha_(false) { |
| style.GetBorderEdgeInfo(edges_, include_logical_left_edge, |
| include_logical_right_edge); |
| ComputeBorderProperties(); |
| |
| // No need to compute the rrects if we don't have any borders to draw. |
| if (!visible_edge_set_) |
| return; |
| |
| outer_ = style_.GetRoundedBorderFor(border_rect, include_logical_left_edge, |
| include_logical_right_edge); |
| inner_ = style_.GetRoundedInnerBorderFor( |
| border_rect, include_logical_left_edge, include_logical_right_edge); |
| |
| // 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(); |
| edges_[static_cast<unsigned>(BoxSide::kTop)].ClampWidth(max_height); |
| edges_[static_cast<unsigned>(BoxSide::kRight)].ClampWidth(max_width); |
| edges_[static_cast<unsigned>(BoxSide::kBottom)].ClampWidth(max_height); |
| edges_[static_cast<unsigned>(BoxSide::kLeft)].ClampWidth(max_width); |
| |
| is_rounded_ = outer_.IsRounded(); |
| } |
| |
| BoxBorderPainter::BoxBorderPainter(const ComputedStyle& style, |
| const LayoutRect& outer, |
| const LayoutRect& inner, |
| const BorderEdge& uniform_edge_info) |
| : style_(style), |
| bleed_avoidance_(kBackgroundBleedNone), |
| include_logical_left_edge_(true), |
| include_logical_right_edge_(true), |
| outer_(FloatRect(outer)), |
| inner_(FloatRect(inner)), |
| 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_alpha_(false) { |
| for (auto& edge : edges_) |
| edge = uniform_edge_info; |
| |
| ComputeBorderProperties(); |
| } |
| |
| void BoxBorderPainter::ComputeBorderProperties() { |
| for (unsigned i = 0; i < base::size(edges_); ++i) { |
| const BorderEdge& edge = edges_[i]; |
| |
| if (!edge.ShouldRender()) { |
| if (edge.PresentButInvisible()) { |
| is_uniform_width_ = false; |
| is_uniform_color_ = false; |
| } |
| |
| continue; |
| } |
| |
| DCHECK_GT(edge.color.Alpha(), 0); |
| |
| visible_edge_count_++; |
| visible_edge_set_ |= EdgeFlagForSide(static_cast<BoxSide>(i)); |
| |
| has_alpha_ |= edge.color.HasAlpha(); |
| |
| 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.color == edges_[first_visible_edge_].color; |
| } |
| } |
| |
| void BoxBorderPainter::PaintBorder(const PaintInfo& info, |
| const LayoutRect& rect) const { |
| if (!visible_edge_count_ || outer_.Rect().IsEmpty()) |
| return; |
| |
| GraphicsContext& graphics_context = info.context; |
| |
| if (PaintBorderFastPath(graphics_context, rect)) |
| return; |
| |
| bool clip_to_outer_border = outer_.IsRounded(); |
| GraphicsContextStateSaver state_saver(graphics_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_)) |
| graphics_context.ClipRoundedRect(outer_); |
| |
| if (inner_.IsRenderable() && !inner_.IsEmpty()) |
| graphics_context.ClipOutRoundedRect(inner_); |
| } |
| |
| const ComplexBorderInfo border_info(*this, true); |
| PaintOpacityGroup(graphics_context, 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 acount 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( |
| GraphicsContext& context, |
| 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). |
| unsigned paint_alpha = group.alpha / effective_opacity; |
| DCHECK_LE(paint_alpha, 255u); |
| |
| // 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 != 255 && (IncludesAdjacentEdges(group.edge_flags) || |
| (index + 1 < border_info.opacity_groups.size())); |
| |
| if (needs_layer) { |
| const float group_opacity = static_cast<float>(group.alpha) / 255; |
| DCHECK_LT(group_opacity, effective_opacity); |
| |
| context.BeginLayer(group_opacity / effective_opacity); |
| effective_opacity = group_opacity; |
| |
| // Group opacity is applied via a layer => we draw the members using opaque |
| // paint. |
| paint_alpha = 255; |
| } |
| |
| // 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(context, 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(context, border_info, side, paint_alpha, completed_edges); |
| completed_edges |= EdgeFlagForSide(side); |
| } |
| |
| if (needs_layer) |
| context.EndLayer(); |
| |
| return completed_edges; |
| } |
| |
| void BoxBorderPainter::PaintSide(GraphicsContext& context, |
| const ComplexBorderInfo& border_info, |
| BoxSide side, |
| unsigned alpha, |
| BorderEdgeFlags completed_edges) const { |
| const BorderEdge& edge = edges_[static_cast<unsigned>(side)]; |
| DCHECK(edge.ShouldRender()); |
| const Color color(edge.color.Red(), edge.color.Green(), edge.color.Blue(), |
| alpha); |
| |
| FloatRect side_rect = 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.SetHeight(ClampOrRound(edge.Width())); |
| |
| PaintOneBorderSide(context, side_rect, BoxSide::kTop, BoxSide::kLeft, |
| BoxSide::kRight, path, border_info.anti_alias, 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 |
| side_rect.ShiftYEdgeTo(side_rect.MaxY() - ClampOrRound(edge.Width())); |
| |
| PaintOneBorderSide(context, side_rect, BoxSide::kBottom, BoxSide::kLeft, |
| BoxSide::kRight, path, border_info.anti_alias, 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.SetWidth(ClampOrRound(edge.Width())); |
| |
| PaintOneBorderSide(context, side_rect, BoxSide::kLeft, BoxSide::kTop, |
| BoxSide::kBottom, path, border_info.anti_alias, 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 |
| side_rect.ShiftXEdgeTo(side_rect.MaxX() - ClampOrRound(edge.Width())); |
| |
| PaintOneBorderSide(context, side_rect, BoxSide::kRight, BoxSide::kTop, |
| BoxSide::kBottom, path, border_info.anti_alias, color, |
| completed_edges); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| BoxBorderPainter::MiterType BoxBorderPainter::ComputeMiter( |
| BoxSide side, |
| BoxSide adjacent_side, |
| BorderEdgeFlags completed_edges, |
| bool antialias) const { |
| const BorderEdge& adjacent_edge = |
| edges_[static_cast<unsigned>(adjacent_side)]; |
| |
| // No miters for missing edges. |
| if (!adjacent_edge.is_present) |
| 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, edges_)) |
| return antialias ? kSoftMiter : kHardMiter; |
| |
| // Non-anti-aliased miters ensure correct same-color seaming when required by |
| // style. |
| if (BorderStylesRequireMiter( |
| side, adjacent_side, |
| edges_[static_cast<unsigned>(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, |
| bool antialias) { |
| // Clipping is required if any of the present miters doesn't match the current |
| // AA mode. |
| bool should_clip = antialias ? miter1 == kHardMiter || miter2 == kHardMiter |
| : miter1 == kSoftMiter || miter2 == kSoftMiter; |
| |
| // 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( |
| GraphicsContext& graphics_context, |
| const FloatRect& side_rect, |
| BoxSide side, |
| BoxSide adjacent_side1, |
| BoxSide adjacent_side2, |
| const Path* path, |
| bool antialias, |
| Color color, |
| BorderEdgeFlags completed_edges) const { |
| const BorderEdge& edge_to_render = edges_[static_cast<unsigned>(side)]; |
| DCHECK(edge_to_render.Width()); |
| const BorderEdge& adjacent_edge1 = |
| edges_[static_cast<unsigned>(adjacent_side1)]; |
| const BorderEdge& adjacent_edge2 = |
| edges_[static_cast<unsigned>(adjacent_side2)]; |
| |
| if (path) { |
| MiterType miter1 = ColorsMatchAtCorner(side, adjacent_side1, edges_) |
| ? kHardMiter |
| : kSoftMiter; |
| MiterType miter2 = ColorsMatchAtCorner(side, adjacent_side2, edges_) |
| ? kHardMiter |
| : kSoftMiter; |
| |
| GraphicsContextStateSaver state_saver(graphics_context); |
| if (inner_.IsRenderable()) |
| ClipBorderSidePolygon(graphics_context, side, miter1, miter2); |
| else |
| ClipBorderSideForComplexInnerPath(graphics_context, side); |
| float stroke_thickness = |
| std::max(std::max(edge_to_render.Width(), adjacent_edge1.Width()), |
| adjacent_edge2.Width()); |
| DrawBoxSideFromPath(graphics_context, LayoutRect(outer_.Rect()), *path, |
| edge_to_render.Width(), stroke_thickness, side, color, |
| edge_to_render.BorderStyle()); |
| } else { |
| MiterType miter1 = |
| ComputeMiter(side, adjacent_side1, completed_edges, antialias); |
| MiterType miter2 = |
| ComputeMiter(side, adjacent_side2, completed_edges, antialias); |
| bool should_clip = MitersRequireClipping( |
| miter1, miter2, edge_to_render.BorderStyle(), antialias); |
| |
| GraphicsContextStateSaver clip_state_saver(graphics_context, should_clip); |
| if (should_clip) { |
| ClipBorderSidePolygon(graphics_context, side, miter1, miter2); |
| |
| // Miters are applied via clipping, no need to draw them. |
| miter1 = miter2 = kNoMiter; |
| } |
| |
| ObjectPainter::DrawLineForBoxSide( |
| graphics_context, side_rect.X(), side_rect.Y(), side_rect.MaxX(), |
| side_rect.MaxY(), side, color, edge_to_render.BorderStyle(), |
| miter1 != kNoMiter ? ClampOrRound(adjacent_edge1.Width()) : 0, |
| miter2 != kNoMiter ? ClampOrRound(adjacent_edge2.Width()) : 0, |
| antialias); |
| } |
| } |
| |
| void BoxBorderPainter::DrawBoxSideFromPath(GraphicsContext& graphics_context, |
| const LayoutRect& border_rect, |
| const Path& border_path, |
| float border_thickness, |
| float stroke_thickness, |
| BoxSide side, |
| Color color, |
| EBorderStyle border_style) const { |
| if (border_thickness <= 0) |
| return; |
| |
| if (border_style == EBorderStyle::kDouble && border_thickness < 3) |
| border_style = EBorderStyle::kSolid; |
| |
| switch (border_style) { |
| case EBorderStyle::kNone: |
| case EBorderStyle::kHidden: |
| return; |
| case EBorderStyle::kDotted: |
| case EBorderStyle::kDashed: { |
| DrawDashedDottedBoxSideFromPath(graphics_context, border_rect, |
| border_thickness, stroke_thickness, color, |
| border_style); |
| return; |
| } |
| case EBorderStyle::kDouble: { |
| DrawDoubleBoxSideFromPath(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, |
| color); |
| return; |
| } |
| case EBorderStyle::kRidge: |
| case EBorderStyle::kGroove: { |
| DrawRidgeGrooveBoxSideFromPath(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, |
| color, border_style); |
| return; |
| } |
| case EBorderStyle::kInset: |
| if (side == BoxSide::kTop || side == BoxSide::kLeft) |
| color = color.Dark(); |
| break; |
| case EBorderStyle::kOutset: |
| if (side == BoxSide::kBottom || side == BoxSide::kRight) |
| color = color.Dark(); |
| break; |
| default: |
| break; |
| } |
| |
| graphics_context.SetStrokeStyle(kNoStroke); |
| graphics_context.SetFillColor(color); |
| graphics_context.DrawRect(PixelSnappedIntRect(border_rect)); |
| } |
| |
| void BoxBorderPainter::DrawDashedDottedBoxSideFromPath( |
| GraphicsContext& graphics_context, |
| const LayoutRect& border_rect, |
| float border_thickness, |
| float stroke_thickness, |
| Color color, |
| EBorderStyle border_style) const { |
| // Convert the path to be down the middle of the dots or dashes. |
| const LayoutRectOutsets center_offsets( |
| -edges_[static_cast<unsigned>(BoxSide::kTop)].UsedWidth() * 0.5, |
| -edges_[static_cast<unsigned>(BoxSide::kRight)].UsedWidth() * 0.5, |
| -edges_[static_cast<unsigned>(BoxSide::kBottom)].UsedWidth() * 0.5, |
| -edges_[static_cast<unsigned>(BoxSide::kLeft)].UsedWidth() * 0.5); |
| Path centerline_path; |
| centerline_path.AddRoundedRect(style_.GetRoundedInnerBorderFor( |
| border_rect, center_offsets, include_logical_left_edge_, |
| include_logical_right_edge_)); |
| |
| graphics_context.SetStrokeColor(color); |
| |
| if (!StrokeData::StrokeIsDashed(border_thickness, |
| border_style == EBorderStyle::kDashed |
| ? kDashedStroke |
| : kDottedStroke)) { |
| DrawWideDottedBoxSideFromPath(graphics_context, 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; |
| graphics_context.SetStrokeThickness(stroke_thickness * thickness_multiplier); |
| graphics_context.SetStrokeStyle( |
| border_style == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke); |
| |
| // TODO(schenney): stroking the border path causes issues with tight corners: |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=344234 |
| graphics_context.StrokePath(centerline_path, centerline_path.length(), |
| border_thickness); |
| } |
| |
| void BoxBorderPainter::DrawWideDottedBoxSideFromPath( |
| GraphicsContext& graphics_context, |
| const Path& border_path, |
| float border_thickness) const { |
| graphics_context.SetStrokeThickness(border_thickness); |
| graphics_context.SetStrokeStyle(kDottedStroke); |
| graphics_context.SetLineCap(kRoundCap); |
| |
| // TODO(schenney): stroking the border path causes issues with tight corners: |
| // https://bugs.webkit.org/show_bug.cgi?id=58711 |
| graphics_context.StrokePath(border_path, border_path.length(), |
| border_thickness); |
| } |
| |
| void BoxBorderPainter::DrawDoubleBoxSideFromPath( |
| GraphicsContext& graphics_context, |
| const LayoutRect& border_rect, |
| const Path& border_path, |
| float border_thickness, |
| float stroke_thickness, |
| BoxSide side, |
| Color color) const { |
| // Draw inner border line |
| { |
| GraphicsContextStateSaver state_saver(graphics_context); |
| const LayoutRectOutsets inner_insets = |
| DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeInner); |
| FloatRoundedRect inner_clip = style_.GetRoundedInnerBorderFor( |
| border_rect, inner_insets, include_logical_left_edge_, |
| include_logical_right_edge_); |
| |
| graphics_context.ClipRoundedRect(inner_clip); |
| DrawBoxSideFromPath(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, color, |
| EBorderStyle::kSolid); |
| } |
| |
| // Draw outer border line |
| { |
| GraphicsContextStateSaver state_saver(graphics_context); |
| LayoutRect outer_rect = border_rect; |
| LayoutRectOutsets outer_insets = |
| DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeOuter); |
| |
| if (BleedAvoidanceIsClipping(bleed_avoidance_)) { |
| outer_rect.Inflate(1); |
| outer_insets.SetTop(outer_insets.Top() - 1); |
| outer_insets.SetRight(outer_insets.Right() - 1); |
| outer_insets.SetBottom(outer_insets.Bottom() - 1); |
| outer_insets.SetLeft(outer_insets.Left() - 1); |
| } |
| |
| FloatRoundedRect outer_clip = style_.GetRoundedInnerBorderFor( |
| outer_rect, outer_insets, include_logical_left_edge_, |
| include_logical_right_edge_); |
| graphics_context.ClipOutRoundedRect(outer_clip); |
| DrawBoxSideFromPath(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, color, |
| EBorderStyle::kSolid); |
| } |
| } |
| |
| void BoxBorderPainter::DrawRidgeGrooveBoxSideFromPath( |
| GraphicsContext& graphics_context, |
| const LayoutRect& border_rect, |
| const Path& border_path, |
| float border_thickness, |
| float 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(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, color, s1); |
| |
| // Paint inner only |
| GraphicsContextStateSaver state_saver(graphics_context); |
| int top_width = edges_[static_cast<unsigned>(BoxSide::kTop)].UsedWidth() / 2; |
| int bottom_width = |
| edges_[static_cast<unsigned>(BoxSide::kBottom)].UsedWidth() / 2; |
| int left_width = |
| edges_[static_cast<unsigned>(BoxSide::kLeft)].UsedWidth() / 2; |
| int right_width = |
| edges_[static_cast<unsigned>(BoxSide::kRight)].UsedWidth() / 2; |
| |
| FloatRoundedRect clip_rect = style_.GetRoundedInnerBorderFor( |
| border_rect, |
| LayoutRectOutsets(-top_width, -right_width, -bottom_width, -left_width), |
| include_logical_left_edge_, include_logical_right_edge_); |
| |
| graphics_context.ClipRoundedRect(clip_rect); |
| DrawBoxSideFromPath(graphics_context, border_rect, border_path, |
| border_thickness, stroke_thickness, side, color, s2); |
| } |
| |
| void BoxBorderPainter::ClipBorderSideForComplexInnerPath( |
| GraphicsContext& graphics_context, |
| BoxSide side) const { |
| graphics_context.Clip(CalculateSideRectIncludingInner(outer_, edges_, side)); |
| FloatRoundedRect adjusted_inner_rect = |
| CalculateAdjustedInnerBorder(inner_, side); |
| if (!adjusted_inner_rect.IsEmpty()) |
| graphics_context.ClipOutRoundedRect(adjusted_inner_rect); |
| } |
| |
| void BoxBorderPainter::ClipBorderSidePolygon(GraphicsContext& graphics_context, |
| BoxSide side, |
| MiterType first_miter, |
| MiterType second_miter) const { |
| DCHECK(first_miter != kNoMiter || second_miter != kNoMiter); |
| |
| FloatPoint edge_quad[4]; // The boundary of the edge for fill |
| FloatPoint |
| bound_quad1; // Point 1 of the rectilinear bounding box of EdgeQuad |
| FloatPoint |
| bound_quad2; // Point 2 of the rectilinear bounding box of EdgeQuad |
| |
| const LayoutRect outer_rect(outer_.Rect()); |
| const LayoutRect inner_rect(inner_.Rect()); |
| |
| // 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 |
| |
| // Offset size and direction to expand clipping quad |
| const static float kExtensionLength = 1e-1f; |
| FloatSize extension_offset; |
| switch (side) { |
| case BoxSide::kTop: |
| edge_quad[0] = FloatPoint(outer_rect.MinXMinYCorner()); |
| edge_quad[1] = FloatPoint(inner_rect.MinXMinYCorner()); |
| edge_quad[2] = FloatPoint(inner_rect.MaxXMinYCorner()); |
| edge_quad[3] = FloatPoint(outer_rect.MaxXMinYCorner()); |
| |
| DCHECK(edge_quad[0].Y() == edge_quad[3].Y()); |
| DCHECK(edge_quad[1].Y() == edge_quad[2].Y()); |
| |
| bound_quad1 = FloatPoint(edge_quad[0].X(), edge_quad[1].Y()); |
| bound_quad2 = FloatPoint(edge_quad[3].X(), edge_quad[2].Y()); |
| |
| extension_offset.SetWidth(-kExtensionLength); |
| extension_offset.SetHeight(0); |
| |
| if (!inner_.GetRadii().TopLeft().IsZero()) { |
| FindIntersection( |
| edge_quad[0], edge_quad[1], |
| FloatPoint(edge_quad[1].X() + inner_.GetRadii().TopLeft().Width(), |
| edge_quad[1].Y()), |
| FloatPoint(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.SetY(edge_quad[1].Y()); |
| bound_quad2.SetY(edge_quad[1].Y()); |
| } |
| |
| if (!inner_.GetRadii().TopRight().IsZero()) { |
| FindIntersection( |
| edge_quad[3], edge_quad[2], |
| FloatPoint(edge_quad[2].X() - inner_.GetRadii().TopRight().Width(), |
| edge_quad[2].Y()), |
| FloatPoint( |
| 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.SetY(edge_quad[2].Y()); |
| bound_quad2.SetY(edge_quad[2].Y()); |
| } |
| } |
| break; |
| |
| case BoxSide::kLeft: |
| // Swap the order of adjacent edges to allow common code |
| std::swap(first_miter, second_miter); |
| edge_quad[0] = FloatPoint(outer_rect.MinXMaxYCorner()); |
| edge_quad[1] = FloatPoint(inner_rect.MinXMaxYCorner()); |
| edge_quad[2] = FloatPoint(inner_rect.MinXMinYCorner()); |
| edge_quad[3] = FloatPoint(outer_rect.MinXMinYCorner()); |
| |
| DCHECK(edge_quad[0].X() == edge_quad[3].X()); |
| DCHECK(edge_quad[1].X() == edge_quad[2].X()); |
| |
| bound_quad1 = FloatPoint(edge_quad[1].X(), edge_quad[0].Y()); |
| bound_quad2 = FloatPoint(edge_quad[2].X(), edge_quad[3].Y()); |
| |
| extension_offset.SetWidth(0); |
| extension_offset.SetHeight(kExtensionLength); |
| |
| if (!inner_.GetRadii().TopLeft().IsZero()) { |
| FindIntersection( |
| edge_quad[3], edge_quad[2], |
| FloatPoint(edge_quad[2].X() + inner_.GetRadii().TopLeft().Width(), |
| edge_quad[2].Y()), |
| FloatPoint(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.SetX(edge_quad[2].X()); |
| bound_quad2.SetX(edge_quad[2].X()); |
| } |
| |
| if (!inner_.GetRadii().BottomLeft().IsZero()) { |
| FindIntersection( |
| edge_quad[0], edge_quad[1], |
| FloatPoint( |
| edge_quad[1].X() + inner_.GetRadii().BottomLeft().Width(), |
| edge_quad[1].Y()), |
| FloatPoint( |
| 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.SetX(edge_quad[1].X()); |
| bound_quad2.SetX(edge_quad[1].X()); |
| } |
| } |
| break; |
| |
| case BoxSide::kBottom: |
| // Swap the order of adjacent edges to allow common code |
| std::swap(first_miter, second_miter); |
| edge_quad[0] = FloatPoint(outer_rect.MaxXMaxYCorner()); |
| edge_quad[1] = FloatPoint(inner_rect.MaxXMaxYCorner()); |
| edge_quad[2] = FloatPoint(inner_rect.MinXMaxYCorner()); |
| edge_quad[3] = FloatPoint(outer_rect.MinXMaxYCorner()); |
| |
| DCHECK(edge_quad[0].Y() == edge_quad[3].Y()); |
| DCHECK(edge_quad[1].Y() == edge_quad[2].Y()); |
| |
| bound_quad1 = FloatPoint(edge_quad[0].X(), edge_quad[1].Y()); |
| bound_quad2 = FloatPoint(edge_quad[3].X(), edge_quad[2].Y()); |
| |
| extension_offset.SetWidth(kExtensionLength); |
| extension_offset.SetHeight(0); |
| |
| if (!inner_.GetRadii().BottomLeft().IsZero()) { |
| FindIntersection( |
| edge_quad[3], edge_quad[2], |
| FloatPoint( |
| edge_quad[2].X() + inner_.GetRadii().BottomLeft().Width(), |
| edge_quad[2].Y()), |
| FloatPoint( |
| 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.SetY(edge_quad[2].Y()); |
| bound_quad2.SetY(edge_quad[2].Y()); |
| } |
| |
| if (!inner_.GetRadii().BottomRight().IsZero()) { |
| FindIntersection( |
| edge_quad[0], edge_quad[1], |
| FloatPoint( |
| edge_quad[1].X() - inner_.GetRadii().BottomRight().Width(), |
| edge_quad[1].Y()), |
| FloatPoint( |
| 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.SetY(edge_quad[1].Y()); |
| bound_quad2.SetY(edge_quad[1].Y()); |
| } |
| } |
| break; |
| |
| case BoxSide::kRight: |
| edge_quad[0] = FloatPoint(outer_rect.MaxXMinYCorner()); |
| edge_quad[1] = FloatPoint(inner_rect.MaxXMinYCorner()); |
| edge_quad[2] = FloatPoint(inner_rect.MaxXMaxYCorner()); |
| edge_quad[3] = FloatPoint(outer_rect.MaxXMaxYCorner()); |
| |
| DCHECK(edge_quad[0].X() == edge_quad[3].X()); |
| DCHECK(edge_quad[1].X() == edge_quad[2].X()); |
| |
| bound_quad1 = FloatPoint(edge_quad[1].X(), edge_quad[0].Y()); |
| bound_quad2 = FloatPoint(edge_quad[2].X(), edge_quad[3].Y()); |
| |
| extension_offset.SetWidth(0); |
| extension_offset.SetHeight(-kExtensionLength); |
| |
| if (!inner_.GetRadii().TopRight().IsZero()) { |
| FindIntersection( |
| edge_quad[0], edge_quad[1], |
| FloatPoint(edge_quad[1].X() - inner_.GetRadii().TopRight().Width(), |
| edge_quad[1].Y()), |
| FloatPoint( |
| 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.SetX(edge_quad[1].X()); |
| bound_quad2.SetX(edge_quad[1].X()); |
| } |
| |
| if (!inner_.GetRadii().BottomRight().IsZero()) { |
| FindIntersection( |
| edge_quad[3], edge_quad[2], |
| FloatPoint( |
| edge_quad[2].X() - inner_.GetRadii().BottomRight().Width(), |
| edge_quad[2].Y()), |
| FloatPoint( |
| 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.SetX(edge_quad[2].X()); |
| bound_quad2.SetX(edge_quad[2].X()); |
| } |
| } |
| break; |
| } |
| |
| if (first_miter == second_miter) { |
| ClipQuad(graphics_context, edge_quad, 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) { |
| FloatPoint 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]; |
| |
| ClipQuad(graphics_context, clipping_quad, first_miter == kSoftMiter); |
| } |
| |
| if (second_miter != kNoMiter) { |
| FloatPoint 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; |
| |
| ClipQuad(graphics_context, clipping_quad, second_miter == kSoftMiter); |
| } |
| } |
| |
| } // namespace blink |