blob: 8f672eaf5db44f2daf1b6158e5b5b900547704c0 [file] [log] [blame]
// Copyright 2014 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 "core/paint/InlineFlowBoxPainter.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/layout/line/RootInlineBox.h"
#include "core/paint/BackgroundImageGeometry.h"
#include "core/paint/BoxModelObjectPainter.h"
#include "core/paint/BoxPainterBase.h"
#include "core/paint/NinePieceImagePainter.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "platform/graphics/paint/DrawingRecorder.h"
namespace blink {
void InlineFlowBoxPainter::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset,
const LayoutUnit line_top,
const LayoutUnit line_bottom) {
DCHECK(!ShouldPaintSelfOutline(paint_info.phase) &&
!ShouldPaintDescendantOutlines(paint_info.phase));
LayoutRect overflow_rect(
inline_flow_box_.VisualOverflowRect(line_top, line_bottom));
inline_flow_box_.FlipForWritingMode(overflow_rect);
overflow_rect.MoveBy(paint_offset);
if (!paint_info.GetCullRect().IntersectsCullRect(overflow_rect))
return;
if (paint_info.phase == PaintPhase::kMask) {
PaintMask(paint_info, paint_offset);
return;
}
if (paint_info.phase == PaintPhase::kForeground) {
// Paint our background, border and box-shadow.
PaintBoxDecorationBackground(paint_info, paint_offset);
}
// Paint our children.
PaintInfo child_info(paint_info);
for (InlineBox* curr = inline_flow_box_.FirstChild(); curr;
curr = curr->NextOnLine()) {
if (curr->GetLineLayoutItem().IsText() ||
!curr->BoxModelObject().HasSelfPaintingLayer())
curr->Paint(child_info, paint_offset, line_top, line_bottom);
}
}
void InlineFlowBoxPainter::PaintFillLayers(const PaintInfo& paint_info,
const Color& c,
const FillLayer& fill_layer,
const LayoutRect& rect,
SkBlendMode op) {
// FIXME: This should be a for loop or similar. It's a little non-trivial to
// do so, however, since the layers need to be painted in reverse order.
if (fill_layer.Next())
PaintFillLayers(paint_info, c, *fill_layer.Next(), rect, op);
PaintFillLayer(paint_info, c, fill_layer, rect, op);
}
void InlineFlowBoxPainter::PaintFillLayer(const PaintInfo& paint_info,
const Color& c,
const FillLayer& fill_layer,
const LayoutRect& rect,
SkBlendMode op) {
LayoutBoxModelObject* box_model = ToLayoutBoxModelObject(
LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.BoxModelObject()));
BackgroundImageGeometry geometry(*box_model);
StyleImage* img = fill_layer.GetImage();
bool has_fill_image = img && img->CanRender();
BoxModelObjectPainter box_model_painter(*box_model, &inline_flow_box_,
rect.Size());
if ((!has_fill_image &&
!inline_flow_box_.GetLineLayoutItem().Style()->HasBorderRadius()) ||
(!inline_flow_box_.PrevLineBox() && !inline_flow_box_.NextLineBox()) ||
!inline_flow_box_.Parent()) {
box_model_painter.PaintFillLayer(paint_info, c, fill_layer, rect,
kBackgroundBleedNone, geometry, op);
} else if (inline_flow_box_.GetLineLayoutItem()
.Style()
->BoxDecorationBreak() == EBoxDecorationBreak::kClone) {
GraphicsContextStateSaver state_saver(paint_info.context);
paint_info.context.Clip(PixelSnappedIntRect(rect));
box_model_painter.PaintFillLayer(paint_info, c, fill_layer, rect,
kBackgroundBleedNone, geometry, op);
} else {
// We have a fill image that spans multiple lines.
// FIXME: frameSize ought to be the same as rect.size().
LayoutSize frame_size(inline_flow_box_.Width(), inline_flow_box_.Height());
LayoutRect image_strip_paint_rect = PaintRectForImageStrip(
rect.Location(), frame_size,
inline_flow_box_.GetLineLayoutItem().Style()->Direction());
GraphicsContextStateSaver state_saver(paint_info.context);
// TODO(chrishtr): this should likely be pixel-snapped.
paint_info.context.Clip(PixelSnappedIntRect(rect));
box_model_painter.PaintFillLayer(paint_info, c, fill_layer,
image_strip_paint_rect,
kBackgroundBleedNone, geometry, op);
}
}
inline bool InlineFlowBoxPainter::ShouldForceIncludeLogicalEdges() const {
return (!inline_flow_box_.PrevLineBox() && !inline_flow_box_.NextLineBox()) ||
!inline_flow_box_.Parent();
}
inline bool InlineFlowBoxPainter::IncludeLogicalLeftEdgeForBoxShadow() const {
return ShouldForceIncludeLogicalEdges() ||
inline_flow_box_.IncludeLogicalLeftEdge();
}
inline bool InlineFlowBoxPainter::IncludeLogicalRightEdgeForBoxShadow() const {
return ShouldForceIncludeLogicalEdges() ||
inline_flow_box_.IncludeLogicalRightEdge();
}
void InlineFlowBoxPainter::PaintNormalBoxShadow(const PaintInfo& info,
const ComputedStyle& s,
const LayoutRect& paint_rect) {
BoxPainterBase::PaintNormalBoxShadow(info, paint_rect, s,
IncludeLogicalLeftEdgeForBoxShadow(),
IncludeLogicalRightEdgeForBoxShadow());
}
void InlineFlowBoxPainter::PaintInsetBoxShadow(const PaintInfo& info,
const ComputedStyle& s,
const LayoutRect& paint_rect) {
BoxPainterBase::PaintInsetBoxShadowWithBorderRect(
info, paint_rect, s, IncludeLogicalLeftEdgeForBoxShadow(),
IncludeLogicalRightEdgeForBoxShadow());
}
static LayoutRect ClipRectForNinePieceImageStrip(const InlineFlowBox& box,
const NinePieceImage& image,
const LayoutRect& paint_rect) {
LayoutRect clip_rect(paint_rect);
const ComputedStyle& style = box.GetLineLayoutItem().StyleRef();
LayoutRectOutsets outsets = style.ImageOutsets(image);
if (box.IsHorizontal()) {
clip_rect.SetY(paint_rect.Y() - outsets.Top());
clip_rect.SetHeight(paint_rect.Height() + outsets.Top() + outsets.Bottom());
if (box.IncludeLogicalLeftEdge()) {
clip_rect.SetX(paint_rect.X() - outsets.Left());
clip_rect.SetWidth(paint_rect.Width() + outsets.Left());
}
if (box.IncludeLogicalRightEdge())
clip_rect.SetWidth(clip_rect.Width() + outsets.Right());
} else {
clip_rect.SetX(paint_rect.X() - outsets.Left());
clip_rect.SetWidth(paint_rect.Width() + outsets.Left() + outsets.Right());
if (box.IncludeLogicalLeftEdge()) {
clip_rect.SetY(paint_rect.Y() - outsets.Top());
clip_rect.SetHeight(paint_rect.Height() + outsets.Top());
}
if (box.IncludeLogicalRightEdge())
clip_rect.SetHeight(clip_rect.Height() + outsets.Bottom());
}
return clip_rect;
}
LayoutRect InlineFlowBoxPainter::PaintRectForImageStrip(
const LayoutPoint& paint_offset,
const LayoutSize& frame_size,
TextDirection direction) const {
// We have a fill/border/mask image that spans multiple lines.
// We need to adjust the offset by the width of all previous lines.
// Think of background painting on inlines as though you had one long line, a
// single continuous strip. Even though that strip has been broken up across
// multiple lines, you still paint it as though you had one single line. This
// means each line has to pick up the background where the previous line left
// off.
LayoutUnit logical_offset_on_line;
LayoutUnit total_logical_width;
if (direction == TextDirection::kLtr) {
for (const InlineFlowBox* curr = inline_flow_box_.PrevLineBox(); curr;
curr = curr->PrevLineBox())
logical_offset_on_line += curr->LogicalWidth();
total_logical_width = logical_offset_on_line;
for (const InlineFlowBox* curr = &inline_flow_box_; curr;
curr = curr->NextLineBox())
total_logical_width += curr->LogicalWidth();
} else {
for (const InlineFlowBox* curr = inline_flow_box_.NextLineBox(); curr;
curr = curr->NextLineBox())
logical_offset_on_line += curr->LogicalWidth();
total_logical_width = logical_offset_on_line;
for (const InlineFlowBox* curr = &inline_flow_box_; curr;
curr = curr->PrevLineBox())
total_logical_width += curr->LogicalWidth();
}
LayoutUnit strip_x =
paint_offset.X() -
(inline_flow_box_.IsHorizontal() ? logical_offset_on_line : LayoutUnit());
LayoutUnit strip_y =
paint_offset.Y() -
(inline_flow_box_.IsHorizontal() ? LayoutUnit() : logical_offset_on_line);
LayoutUnit strip_width = inline_flow_box_.IsHorizontal() ? total_logical_width
: frame_size.Width();
LayoutUnit strip_height = inline_flow_box_.IsHorizontal()
? frame_size.Height()
: total_logical_width;
return LayoutRect(strip_x, strip_y, strip_width, strip_height);
}
InlineFlowBoxPainter::BorderPaintingType
InlineFlowBoxPainter::GetBorderPaintType(const LayoutRect& adjusted_frame_rect,
IntRect& adjusted_clip_rect) const {
adjusted_clip_rect = PixelSnappedIntRect(adjusted_frame_rect);
if (inline_flow_box_.Parent() &&
inline_flow_box_.GetLineLayoutItem().Style()->HasBorderDecoration()) {
const NinePieceImage& border_image =
inline_flow_box_.GetLineLayoutItem().Style()->BorderImage();
StyleImage* border_image_source = border_image.GetImage();
bool has_border_image =
border_image_source && border_image_source->CanRender();
if (has_border_image && !border_image_source->IsLoaded())
return kDontPaintBorders;
// The simple case is where we either have no border image or we are the
// only box for this object. In those cases only a single call to draw is
// required.
if (!has_border_image ||
(!inline_flow_box_.PrevLineBox() && !inline_flow_box_.NextLineBox()))
return kPaintBordersWithoutClip;
// We have a border image that spans multiple lines.
adjusted_clip_rect = PixelSnappedIntRect(ClipRectForNinePieceImageStrip(
inline_flow_box_, border_image, adjusted_frame_rect));
return kPaintBordersWithClip;
}
return kDontPaintBorders;
}
static inline Node* GetNode(const LayoutObject* box_model) {
Node* node = nullptr;
for (const LayoutObject* obj = box_model; obj && !node; obj = obj->Parent())
node = obj->GeneratingNode();
return node;
}
void InlineFlowBoxPainter::PaintBoxDecorationBackground(
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
DCHECK(paint_info.phase == PaintPhase::kForeground);
if (inline_flow_box_.GetLineLayoutItem().Style()->Visibility() !=
EVisibility::kVisible)
return;
// You can use p::first-line to specify a background. If so, the root line
// boxes for a line may actually have to paint a background.
LayoutObject* inline_flow_box_layout_object =
LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.GetLineLayoutItem());
const ComputedStyle* style_to_use =
inline_flow_box_.GetLineLayoutItem().Style(
inline_flow_box_.IsFirstLineStyle());
bool should_paint_box_decoration_background;
if (inline_flow_box_.Parent())
should_paint_box_decoration_background =
inline_flow_box_layout_object->HasBoxDecorationBackground();
else
should_paint_box_decoration_background =
inline_flow_box_.IsFirstLineStyle() &&
style_to_use != inline_flow_box_.GetLineLayoutItem().Style();
if (!should_paint_box_decoration_background)
return;
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, inline_flow_box_,
DisplayItem::kBoxDecorationBackground))
return;
DrawingRecorder recorder(paint_info.context, inline_flow_box_,
DisplayItem::kBoxDecorationBackground);
LayoutRect frame_rect = FrameRectClampedToLineTopAndBottomIfNeeded();
// Move x/y to our coordinates.
LayoutRect local_rect(frame_rect);
inline_flow_box_.FlipForWritingMode(local_rect);
LayoutPoint adjusted_paint_offset = paint_offset + local_rect.Location();
LayoutRect adjusted_frame_rect =
LayoutRect(adjusted_paint_offset, frame_rect.Size());
IntRect adjusted_clip_rect;
BorderPaintingType border_painting_type =
GetBorderPaintType(adjusted_frame_rect, adjusted_clip_rect);
// Shadow comes first and is behind the background and border.
PaintNormalBoxShadow(paint_info, *style_to_use, adjusted_frame_rect);
Color background_color = inline_flow_box_layout_object->ResolveColor(
*style_to_use, GetCSSPropertyBackgroundColor());
PaintFillLayers(paint_info, background_color,
style_to_use->BackgroundLayers(), adjusted_frame_rect);
PaintInsetBoxShadow(paint_info, *style_to_use, adjusted_frame_rect);
const LayoutObject* box_model = ToLayoutBoxModelObject(
LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.BoxModelObject()));
switch (border_painting_type) {
case kDontPaintBorders:
break;
case kPaintBordersWithoutClip:
BoxPainterBase::PaintBorder(
*box_model, box_model->GetDocument(), GetNode(box_model), paint_info,
adjusted_frame_rect,
inline_flow_box_.GetLineLayoutItem().StyleRef(
inline_flow_box_.IsFirstLineStyle()),
kBackgroundBleedNone, inline_flow_box_.IncludeLogicalLeftEdge(),
inline_flow_box_.IncludeLogicalRightEdge());
break;
case kPaintBordersWithClip:
// FIXME: What the heck do we do with RTL here? The math we're using is
// obviously not right, but it isn't even clear how this should work at
// all.
LayoutRect image_strip_paint_rect = PaintRectForImageStrip(
adjusted_paint_offset, frame_rect.Size(), TextDirection::kLtr);
GraphicsContextStateSaver state_saver(paint_info.context);
paint_info.context.Clip(adjusted_clip_rect);
BoxPainterBase::PaintBorder(*box_model, box_model->GetDocument(),
GetNode(box_model), paint_info,
image_strip_paint_rect,
inline_flow_box_.GetLineLayoutItem().StyleRef(
inline_flow_box_.IsFirstLineStyle()));
break;
}
}
void InlineFlowBoxPainter::PaintMask(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
DCHECK_EQ(PaintPhase::kMask, paint_info.phase);
const auto& box_model = *ToLayoutBoxModelObject(
LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.BoxModelObject()));
DCHECK(box_model.HasMask());
if (box_model.StyleRef().Visibility() != EVisibility::kVisible)
return;
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, inline_flow_box_,
DisplayItem::PaintPhaseToDrawingType(paint_info.phase)))
return;
DrawingRecorder recorder(
paint_info.context, inline_flow_box_,
DisplayItem::PaintPhaseToDrawingType(paint_info.phase));
LayoutRect frame_rect = FrameRectClampedToLineTopAndBottomIfNeeded();
// Move x/y to our coordinates.
LayoutRect local_rect(frame_rect);
inline_flow_box_.FlipForWritingMode(local_rect);
LayoutPoint adjusted_paint_offset = paint_offset + local_rect.Location();
const auto& mask_nine_piece_image = box_model.StyleRef().MaskBoxImage();
const auto* mask_box_image = mask_nine_piece_image.GetImage();
// Figure out if we need to push a transparency layer to render our mask.
bool push_transparency_layer = false;
SkBlendMode composite_op = SkBlendMode::kSrcOver;
DCHECK(box_model.HasLayer());
if (!box_model.Layer()->MaskBlendingAppliedByCompositor(paint_info)) {
if ((mask_box_image && box_model.StyleRef().MaskLayers().HasImage()) ||
box_model.StyleRef().MaskLayers().Next()) {
push_transparency_layer = true;
paint_info.context.BeginLayer(1.0f, SkBlendMode::kDstIn);
} else {
// TODO(fmalita): passing a dst-in xfer mode down to
// paintFillLayers/paintNinePieceImage seems dangerous: it is only
// correct if applied atomically (single draw call). While the heuristic
// above presumably ensures that is the case, this approach seems super
// fragile. We should investigate dropping this optimization in favour
// of the more robust layer branch above.
composite_op = SkBlendMode::kDstIn;
}
}
LayoutRect paint_rect = LayoutRect(adjusted_paint_offset, frame_rect.Size());
PaintFillLayers(paint_info, Color::kTransparent,
box_model.StyleRef().MaskLayers(), paint_rect, composite_op);
bool has_box_image = mask_box_image && mask_box_image->CanRender();
if (!has_box_image || !mask_box_image->IsLoaded()) {
if (push_transparency_layer)
paint_info.context.EndLayer();
return; // Don't paint anything while we wait for the image to load.
}
// The simple case is where we are the only box for this object. In those
// cases only a single call to draw is required.
if (!inline_flow_box_.PrevLineBox() && !inline_flow_box_.NextLineBox()) {
NinePieceImagePainter::Paint(paint_info.context, box_model,
box_model.GetDocument(), GetNode(&box_model),
paint_rect, box_model.StyleRef(),
mask_nine_piece_image, composite_op);
} else {
// We have a mask image that spans multiple lines.
// FIXME: What the heck do we do with RTL here? The math we're using is
// obviously not right, but it isn't even clear how this should work at all.
LayoutRect image_strip_paint_rect = PaintRectForImageStrip(
adjusted_paint_offset, frame_rect.Size(), TextDirection::kLtr);
FloatRect clip_rect(ClipRectForNinePieceImageStrip(
inline_flow_box_, mask_nine_piece_image, paint_rect));
GraphicsContextStateSaver state_saver(paint_info.context);
// TODO(chrishtr): this should be pixel-snapped.
paint_info.context.Clip(clip_rect);
NinePieceImagePainter::Paint(paint_info.context, box_model,
box_model.GetDocument(), GetNode(&box_model),
image_strip_paint_rect, box_model.StyleRef(),
mask_nine_piece_image, composite_op);
}
if (push_transparency_layer)
paint_info.context.EndLayer();
}
// This method should not be needed. See crbug.com/530659.
LayoutRect InlineFlowBoxPainter::FrameRectClampedToLineTopAndBottomIfNeeded()
const {
LayoutRect rect(inline_flow_box_.FrameRect());
bool no_quirks_mode =
inline_flow_box_.GetLineLayoutItem().GetDocument().InNoQuirksMode();
if (!no_quirks_mode && !inline_flow_box_.HasTextChildren() &&
!(inline_flow_box_.DescendantsHaveSameLineHeightAndBaseline() &&
inline_flow_box_.HasTextDescendants())) {
const RootInlineBox& root_box = inline_flow_box_.Root();
LayoutUnit logical_top =
inline_flow_box_.IsHorizontal() ? rect.Y() : rect.X();
LayoutUnit logical_height =
inline_flow_box_.IsHorizontal() ? rect.Height() : rect.Width();
LayoutUnit bottom =
std::min(root_box.LineBottom(), logical_top + logical_height);
logical_top = std::max(root_box.LineTop(), logical_top);
logical_height = bottom - logical_top;
if (inline_flow_box_.IsHorizontal()) {
rect.SetY(logical_top);
rect.SetHeight(logical_height);
} else {
rect.SetX(logical_top);
rect.SetWidth(logical_height);
}
}
return rect;
}
} // namespace blink