blob: acddffe60bd144e44a171eaf2fdd52ade9c85913 [file] [log] [blame]
// Copyright 2018 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/ng/ng_inline_box_fragment_painter.h"
#include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/paint/background_image_geometry.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_phase.h"
#include "third_party/blink/renderer/core/style/nine_piece_image.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/scoped_display_item_fragment.h"
namespace blink {
namespace {
template <class Items>
bool HasMultipleItems(const Items items) {
auto iter = items.begin();
DCHECK(iter != items.end());
return iter != items.end() && ++iter != items.end();
}
inline bool MayHaveMultipleFragmentItems(const NGFragmentItem& item,
const LayoutObject& layout_object) {
return !item.IsFirstForNode() || !item.IsLastForNode() ||
// TODO(crbug.com/1061423): NGInlineCursor is currently unable to deal
// with objects split into multiple fragmentainers (e.g. columns). Just
// return true if it's possible that this object participates in a
// fragmentation context. This will give false positives, but that
// should be harmless, given the way the return value is used by the
// caller.
UNLIKELY(layout_object.IsInsideFlowThread());
}
} // namespace
PhysicalBoxSides NGInlineBoxFragmentPainter::SidesToInclude() const {
return PhysicalFragment().SidesToInclude();
}
void NGInlineBoxFragmentPainter::Paint(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
ScopedDisplayItemFragment display_item_fragment(
paint_info.context, inline_box_item_.FragmentId());
const PhysicalOffset adjusted_paint_offset =
paint_offset + inline_box_item_.OffsetInContainerFragment();
if (paint_info.phase == PaintPhase::kForeground)
PaintBackgroundBorderShadow(paint_info, adjusted_paint_offset);
const bool suppress_box_decoration_background = true;
DCHECK(inline_box_cursor_);
NGBoxFragmentPainter box_painter(*inline_box_cursor_, inline_box_item_,
PhysicalFragment());
box_painter.PaintObject(paint_info, adjusted_paint_offset,
suppress_box_decoration_background);
}
void NGInlineBoxFragmentPainterBase::PaintBackgroundBorderShadow(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK(paint_info.phase == PaintPhase::kForeground);
if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible)
return;
// You can use p::first-line to specify a background. If so, the direct child
// inline boxes of line boxes may actually have to paint a background.
// TODO(layout-dev): Cache HasBoxDecorationBackground on the fragment like
// we do for LayoutObject. Querying Style each time is too costly.
bool should_paint_box_decoration_background =
inline_box_fragment_.GetLayoutObject()->HasBoxDecorationBackground() ||
inline_box_fragment_.UsesFirstLineStyle();
if (!should_paint_box_decoration_background)
return;
const DisplayItemClient& display_item_client = GetDisplayItemClient();
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, display_item_client,
DisplayItem::kBoxDecorationBackground))
return;
PhysicalRect frame_rect = inline_box_fragment_.LocalRect();
PhysicalRect adjusted_frame_rect(paint_offset, frame_rect.size);
DrawingRecorder recorder(paint_info.context, display_item_client,
DisplayItem::kBoxDecorationBackground,
VisualRect(paint_offset));
DCHECK(inline_box_fragment_.GetLayoutObject());
const LayoutObject& layout_object = *inline_box_fragment_.GetLayoutObject();
bool object_may_have_multiple_boxes =
MayHaveMultipleFragmentItems(inline_box_item_, layout_object);
// TODO(eae): Switch to LayoutNG version of BackgroundImageGeometry.
BackgroundImageGeometry geometry(*static_cast<const LayoutBoxModelObject*>(
inline_box_fragment_.GetLayoutObject()));
DCHECK(inline_box_cursor_);
NGBoxFragmentPainter box_painter(
*inline_box_cursor_, inline_box_item_,
To<NGPhysicalBoxFragment>(inline_box_fragment_));
PaintBoxDecorationBackground(
box_painter, paint_info, paint_offset, adjusted_frame_rect, geometry,
object_may_have_multiple_boxes, SidesToInclude());
}
IntRect NGInlineBoxFragmentPainterBase::VisualRect(
const PhysicalOffset& paint_offset) {
PhysicalRect overflow_rect = inline_box_item_.SelfInkOverflow();
overflow_rect.Move(paint_offset);
return EnclosingIntRect(overflow_rect);
}
void NGLineBoxFragmentPainter::PaintBackgroundBorderShadow(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK_EQ(paint_info.phase, PaintPhase::kForeground);
DCHECK_EQ(inline_box_fragment_.Type(), NGPhysicalFragment::kFragmentLineBox);
DCHECK(NeedsPaint(inline_box_fragment_));
// |NGFragmentItem| uses the fragment id when painting the background of
// line boxes. Please see |NGFragmentItem::kInitialLineFragmentId|.
DCHECK_NE(paint_info.context.GetPaintController().CurrentFragment(), 0u);
if (line_style_ == style_ ||
line_style_.Visibility() != EVisibility::kVisible)
return;
const DisplayItemClient& display_item_client = GetDisplayItemClient();
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, display_item_client,
DisplayItem::kBoxDecorationBackground))
return;
// Compute the content box for the `::first-line` box. It's different from
// fragment size because the height of line box includes `line-height` while
// the height of inline box does not. The box "behaves similar to that of an
// inline-level element".
// https://drafts.csswg.org/css-pseudo-4/#first-line-styling
const NGPhysicalLineBoxFragment& line_box = PhysicalFragment();
const FontHeight line_metrics = line_box.Metrics();
const FontHeight text_metrics = line_style_.GetFontHeight();
const WritingMode writing_mode = line_style_.GetWritingMode();
PhysicalRect rect;
if (IsHorizontalWritingMode(writing_mode)) {
rect.offset.top = line_metrics.ascent - text_metrics.ascent;
rect.size = {line_box.Size().width, text_metrics.LineHeight()};
} else {
rect.offset.left =
line_box.Size().width - line_metrics.ascent - text_metrics.descent;
rect.size = {text_metrics.LineHeight(), line_box.Size().height};
}
rect.offset += paint_offset;
DrawingRecorder recorder(paint_info.context, display_item_client,
DisplayItem::kBoxDecorationBackground,
VisualRect(paint_offset));
const LayoutBlockFlow& layout_block_flow =
*To<LayoutBlockFlow>(block_fragment_.GetLayoutObject());
BackgroundImageGeometry geometry(layout_block_flow);
NGBoxFragmentPainter box_painter(block_fragment_);
PaintBoxDecorationBackground(
box_painter, paint_info, paint_offset, rect, geometry,
/*object_has_multiple_boxes*/ false, PhysicalBoxSides());
}
void NGInlineBoxFragmentPainterBase::ComputeFragmentOffsetOnLine(
TextDirection direction,
LayoutUnit* offset_on_line,
LayoutUnit* total_width) const {
WritingDirectionMode writing_direction =
inline_box_fragment_.Style().GetWritingDirection();
NGInlineCursor cursor;
DCHECK(inline_box_fragment_.GetLayoutObject());
cursor.MoveTo(*inline_box_fragment_.GetLayoutObject());
LayoutUnit before;
LayoutUnit after;
bool before_self = true;
for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
DCHECK(cursor.CurrentItem());
if (cursor.CurrentItem() == &inline_box_item_) {
before_self = false;
continue;
}
const NGPhysicalBoxFragment* box_fragment = cursor.Current().BoxFragment();
DCHECK(box_fragment);
if (before_self)
before += NGFragment(writing_direction, *box_fragment).InlineSize();
else
after += NGFragment(writing_direction, *box_fragment).InlineSize();
}
*total_width =
before + after +
NGFragment(writing_direction, inline_box_fragment_).InlineSize();
// We're iterating over the fragments in physical order before so we need to
// swap before and after for RTL.
*offset_on_line = direction == TextDirection::kLtr ? before : after;
}
PhysicalRect NGInlineBoxFragmentPainterBase::PaintRectForImageStrip(
const PhysicalRect& paint_rect,
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 offset_on_line;
LayoutUnit total_width;
ComputeFragmentOffsetOnLine(direction, &offset_on_line, &total_width);
if (inline_box_fragment_.Style().IsHorizontalWritingMode()) {
return PhysicalRect(paint_rect.X() - offset_on_line, paint_rect.Y(),
total_width, paint_rect.Height());
}
return PhysicalRect(paint_rect.X(), paint_rect.Y() - offset_on_line,
paint_rect.Width(), total_width);
}
InlineBoxPainterBase::BorderPaintingType
NGInlineBoxFragmentPainterBase::GetBorderPaintType(
const PhysicalRect& adjusted_frame_rect,
IntRect& adjusted_clip_rect,
bool object_has_multiple_boxes) const {
const ComputedStyle& style = inline_box_fragment_.Style();
if (!style.HasBorderDecoration())
return kDontPaintBorders;
const NinePieceImage& border_image = 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 || !object_has_multiple_boxes) {
adjusted_clip_rect = PixelSnappedIntRect(adjusted_frame_rect);
return kPaintBordersWithoutClip;
}
// We have a border image that spans multiple lines.
adjusted_clip_rect = PixelSnappedIntRect(ClipRectForNinePieceImageStrip(
style, SidesToInclude(), border_image, adjusted_frame_rect));
return kPaintBordersWithClip;
}
void NGInlineBoxFragmentPainterBase::PaintNormalBoxShadow(
const PaintInfo& info,
const ComputedStyle& s,
const PhysicalRect& paint_rect) {
BoxPainterBase::PaintNormalBoxShadow(info, paint_rect, s, SidesToInclude());
}
void NGInlineBoxFragmentPainterBase::PaintInsetBoxShadow(
const PaintInfo& info,
const ComputedStyle& s,
const PhysicalRect& paint_rect) {
BoxPainterBase::PaintInsetBoxShadowWithBorderRect(info, paint_rect, s,
SidesToInclude());
}
// Paint all fragments for the |layout_inline|. This function is used only for
// self-painting |LayoutInline|.
void NGInlineBoxFragmentPainter::PaintAllFragments(
const LayoutInline& layout_inline,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
// TODO(kojii): If the block flow is dirty, children of these fragments
// maybe already deleted. crbug.com/963103
const LayoutBlockFlow* block_flow = layout_inline.ContainingNGBlockFlow();
if (UNLIKELY(block_flow->NeedsLayout()))
return;
NGInlineCursor cursor(*block_flow);
cursor.MoveTo(layout_inline);
if (!cursor)
return;
// Convert from inline fragment index to container fragment index, as the
// inline may not start in the first fragment generated for the inline
// formatting context.
wtf_size_t target_fragment_idx =
cursor.ContainerFragmentIndex() +
paint_info.context.GetPaintController().CurrentFragment();
for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
if (target_fragment_idx != cursor.ContainerFragmentIndex())
continue;
const NGFragmentItem* item = cursor.CurrentItem();
DCHECK(item);
const NGPhysicalBoxFragment* box_fragment = item->BoxFragment();
DCHECK(box_fragment);
NGInlineBoxFragmentPainter(cursor, *item, *box_fragment)
.Paint(paint_info, paint_offset);
}
}
#if DCHECK_IS_ON()
void NGInlineBoxFragmentPainter::CheckValid() const {
DCHECK(inline_box_cursor_);
DCHECK_EQ(inline_box_cursor_->Current().Item(), &inline_box_item_);
DCHECK_EQ(inline_box_fragment_.Type(),
NGPhysicalFragment::NGFragmentType::kFragmentBox);
DCHECK_EQ(inline_box_fragment_.BoxType(),
NGPhysicalFragment::NGBoxType::kInlineBox);
}
#endif
} // namespace blink