1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h"
6
7 #include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h"
8 #include "third_party/blink/renderer/core/layout/layout_object.h"
9 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
10 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
11 #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
12 #include "third_party/blink/renderer/core/paint/background_image_geometry.h"
13 #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
14 #include "third_party/blink/renderer/core/paint/paint_info.h"
15 #include "third_party/blink/renderer/core/paint/paint_layer.h"
16 #include "third_party/blink/renderer/core/paint/paint_phase.h"
17 #include "third_party/blink/renderer/core/style/nine_piece_image.h"
18 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
19 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
20 #include "third_party/blink/renderer/platform/graphics/paint/scoped_display_item_fragment.h"
21
22 namespace blink {
23
24 namespace {
25
26 template <class Items>
HasMultipleItems(const Items items)27 bool HasMultipleItems(const Items items) {
28 auto iter = items.begin();
29 DCHECK(iter != items.end());
30 return iter != items.end() && ++iter != items.end();
31 }
32
HasMultiplePaintFragments(const LayoutObject & layout_object)33 inline bool HasMultiplePaintFragments(const LayoutObject& layout_object) {
34 return HasMultipleItems(NGPaintFragment::InlineFragmentsFor(&layout_object));
35 }
36
MayHaveMultipleFragmentItems(const NGFragmentItem & item,const LayoutObject & layout_object)37 inline bool MayHaveMultipleFragmentItems(const NGFragmentItem& item,
38 const LayoutObject& layout_object) {
39 return !item.IsFirstForNode() || !item.IsLastForNode() ||
40 // TODO(crbug.com/1061423): NGInlineCursor is currently unable to deal
41 // with objects split into multiple fragmentainers (e.g. columns). Just
42 // return true if it's possible that this object participates in a
43 // fragmentation context. This will give false positives, but that
44 // should be harmless, given the way the return value is used by the
45 // caller.
46 UNLIKELY(layout_object.IsInsideFlowThread());
47 }
48
49 } // namespace
50
SidesToInclude() const51 PhysicalBoxSides NGInlineBoxFragmentPainter::SidesToInclude() const {
52 return PhysicalFragment().SidesToInclude();
53 }
54
Paint(const PaintInfo & paint_info,const PhysicalOffset & paint_offset)55 void NGInlineBoxFragmentPainter::Paint(const PaintInfo& paint_info,
56 const PhysicalOffset& paint_offset) {
57 base::Optional<ScopedDisplayItemFragment> display_item_fragment;
58 if (inline_box_item_) {
59 display_item_fragment.emplace(paint_info.context,
60 inline_box_item_->FragmentId());
61 }
62
63 const PhysicalOffset adjusted_paint_offset =
64 paint_offset + (inline_box_paint_fragment_
65 ? inline_box_paint_fragment_->Offset()
66 : inline_box_item_->OffsetInContainerBlock());
67 if (paint_info.phase == PaintPhase::kForeground)
68 PaintBackgroundBorderShadow(paint_info, adjusted_paint_offset);
69
70 const bool suppress_box_decoration_background = true;
71 if (inline_box_paint_fragment_) {
72 NGBoxFragmentPainter box_painter(PhysicalFragment(),
73 inline_box_paint_fragment_);
74 box_painter.PaintObject(paint_info, adjusted_paint_offset,
75 suppress_box_decoration_background);
76 return;
77 }
78 DCHECK(inline_box_cursor_);
79 DCHECK(inline_box_item_);
80 NGBoxFragmentPainter box_painter(*inline_box_cursor_, *inline_box_item_,
81 PhysicalFragment());
82 box_painter.PaintObject(paint_info, adjusted_paint_offset,
83 suppress_box_decoration_background);
84 }
85
PaintBackgroundBorderShadow(const PaintInfo & paint_info,const PhysicalOffset & paint_offset)86 void NGInlineBoxFragmentPainterBase::PaintBackgroundBorderShadow(
87 const PaintInfo& paint_info,
88 const PhysicalOffset& paint_offset) {
89 DCHECK(paint_info.phase == PaintPhase::kForeground);
90 if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible)
91 return;
92
93 // You can use p::first-line to specify a background. If so, the direct child
94 // inline boxes of line boxes may actually have to paint a background.
95 // TODO(layout-dev): Cache HasBoxDecorationBackground on the fragment like
96 // we do for LayoutObject. Querying Style each time is too costly.
97 bool should_paint_box_decoration_background =
98 inline_box_fragment_.GetLayoutObject()->HasBoxDecorationBackground() ||
99 inline_box_fragment_.UsesFirstLineStyle();
100
101 if (!should_paint_box_decoration_background)
102 return;
103
104 const DisplayItemClient& display_item_client = GetDisplayItemClient();
105 if (DrawingRecorder::UseCachedDrawingIfPossible(
106 paint_info.context, display_item_client,
107 DisplayItem::kBoxDecorationBackground))
108 return;
109
110 PhysicalRect frame_rect = inline_box_fragment_.LocalRect();
111 PhysicalRect adjusted_frame_rect(paint_offset, frame_rect.size);
112
113 DrawingRecorder recorder(paint_info.context, display_item_client,
114 DisplayItem::kBoxDecorationBackground,
115 VisualRect(paint_offset));
116
117 DCHECK(inline_box_fragment_.GetLayoutObject());
118 const LayoutObject& layout_object = *inline_box_fragment_.GetLayoutObject();
119 DCHECK(inline_box_paint_fragment_ || inline_box_item_);
120 bool object_may_have_multiple_boxes =
121 inline_box_paint_fragment_
122 ? HasMultiplePaintFragments(layout_object)
123 : MayHaveMultipleFragmentItems(*inline_box_item_, layout_object);
124
125 // TODO(eae): Switch to LayoutNG version of BackgroundImageGeometry.
126 BackgroundImageGeometry geometry(*static_cast<const LayoutBoxModelObject*>(
127 inline_box_fragment_.GetLayoutObject()));
128 if (inline_box_paint_fragment_) {
129 NGBoxFragmentPainter box_painter(
130 To<NGPhysicalBoxFragment>(inline_box_fragment_),
131 inline_box_paint_fragment_);
132 PaintBoxDecorationBackground(
133 box_painter, paint_info, paint_offset, adjusted_frame_rect, geometry,
134 object_may_have_multiple_boxes, SidesToInclude());
135 return;
136 }
137 DCHECK(inline_box_cursor_);
138 NGBoxFragmentPainter box_painter(
139 *inline_box_cursor_, *inline_box_item_,
140 To<NGPhysicalBoxFragment>(inline_box_fragment_));
141 PaintBoxDecorationBackground(
142 box_painter, paint_info, paint_offset, adjusted_frame_rect, geometry,
143 object_may_have_multiple_boxes, SidesToInclude());
144 }
145
VisualRect(const PhysicalOffset & paint_offset)146 IntRect NGInlineBoxFragmentPainterBase::VisualRect(
147 const PhysicalOffset& paint_offset) {
148 PhysicalRect overflow_rect;
149 if (inline_box_paint_fragment_) {
150 overflow_rect = inline_box_paint_fragment_->SelfInkOverflow();
151 } else {
152 DCHECK(inline_box_item_);
153 overflow_rect = inline_box_item_->SelfInkOverflow();
154 }
155 overflow_rect.Move(paint_offset);
156 return EnclosingIntRect(overflow_rect);
157 }
158
PaintBackgroundBorderShadow(const PaintInfo & paint_info,const PhysicalOffset & paint_offset)159 void NGLineBoxFragmentPainter::PaintBackgroundBorderShadow(
160 const PaintInfo& paint_info,
161 const PhysicalOffset& paint_offset) {
162 DCHECK_EQ(paint_info.phase, PaintPhase::kForeground);
163 DCHECK_EQ(inline_box_fragment_.Type(), NGPhysicalFragment::kFragmentLineBox);
164 DCHECK(NeedsPaint(inline_box_fragment_));
165 #if DCHECK_IS_ON()
166 if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) {
167 DCHECK(inline_box_item_);
168 // |NGFragmentItem| uses the fragment id when painting the background of
169 // line boxes. Please see |NGFragmentItem::kInitialLineFragmentId|.
170 DCHECK_NE(paint_info.context.GetPaintController().CurrentFragment(), 0u);
171 }
172 #endif
173
174 if (line_style_ == style_ ||
175 line_style_.Visibility() != EVisibility::kVisible)
176 return;
177
178 const DisplayItemClient& display_item_client = GetDisplayItemClient();
179 if (DrawingRecorder::UseCachedDrawingIfPossible(
180 paint_info.context, display_item_client,
181 DisplayItem::kBoxDecorationBackground))
182 return;
183
184 // Compute the content box for the `::first-line` box. It's different from
185 // fragment size because the height of line box includes `line-height` while
186 // the height of inline box does not. The box "behaves similar to that of an
187 // inline-level element".
188 // https://drafts.csswg.org/css-pseudo-4/#first-line-styling
189 const NGPhysicalLineBoxFragment& line_box = PhysicalFragment();
190 const FontHeight line_metrics = line_box.Metrics();
191 const FontHeight text_metrics = line_style_.GetFontHeight();
192 const WritingMode writing_mode = line_style_.GetWritingMode();
193 PhysicalRect rect;
194 if (IsHorizontalWritingMode(writing_mode)) {
195 rect.offset.top = line_metrics.ascent - text_metrics.ascent;
196 rect.size = {line_box.Size().width, text_metrics.LineHeight()};
197 } else {
198 rect.offset.left =
199 line_box.Size().width - line_metrics.ascent - text_metrics.descent;
200 rect.size = {text_metrics.LineHeight(), line_box.Size().height};
201 }
202 rect.offset += paint_offset;
203
204 DrawingRecorder recorder(paint_info.context, display_item_client,
205 DisplayItem::kBoxDecorationBackground,
206 VisualRect(paint_offset));
207
208 const LayoutBlockFlow& layout_block_flow =
209 *To<LayoutBlockFlow>(block_fragment_.GetLayoutObject());
210 BackgroundImageGeometry geometry(layout_block_flow);
211 NGBoxFragmentPainter box_painter(block_fragment_, block_paint_fragment_);
212 PaintBoxDecorationBackground(
213 box_painter, paint_info, paint_offset, rect, geometry,
214 /*object_has_multiple_boxes*/ false, PhysicalBoxSides());
215 }
216
ComputeFragmentOffsetOnLine(TextDirection direction,LayoutUnit * offset_on_line,LayoutUnit * total_width) const217 void NGInlineBoxFragmentPainterBase::ComputeFragmentOffsetOnLine(
218 TextDirection direction,
219 LayoutUnit* offset_on_line,
220 LayoutUnit* total_width) const {
221 WritingDirectionMode writing_direction =
222 inline_box_fragment_.Style().GetWritingDirection();
223 NGInlineCursor cursor;
224 DCHECK(inline_box_fragment_.GetLayoutObject());
225 cursor.MoveTo(*inline_box_fragment_.GetLayoutObject());
226
227 LayoutUnit before;
228 LayoutUnit after;
229 bool before_self = true;
230 for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
231 if (inline_box_paint_fragment_) {
232 DCHECK(cursor.CurrentPaintFragment());
233 if (cursor.CurrentPaintFragment() == inline_box_paint_fragment_) {
234 before_self = false;
235 continue;
236 }
237 } else {
238 DCHECK(inline_box_item_);
239 DCHECK(cursor.CurrentItem());
240 if (cursor.CurrentItem() == inline_box_item_) {
241 before_self = false;
242 continue;
243 }
244 }
245 const NGPhysicalBoxFragment* box_fragment = cursor.Current().BoxFragment();
246 DCHECK(box_fragment);
247 if (before_self)
248 before += NGFragment(writing_direction, *box_fragment).InlineSize();
249 else
250 after += NGFragment(writing_direction, *box_fragment).InlineSize();
251 }
252
253 *total_width =
254 before + after +
255 NGFragment(writing_direction, inline_box_fragment_).InlineSize();
256
257 // We're iterating over the fragments in physical order before so we need to
258 // swap before and after for RTL.
259 *offset_on_line = direction == TextDirection::kLtr ? before : after;
260 }
261
PaintRectForImageStrip(const PhysicalRect & paint_rect,TextDirection direction) const262 PhysicalRect NGInlineBoxFragmentPainterBase::PaintRectForImageStrip(
263 const PhysicalRect& paint_rect,
264 TextDirection direction) const {
265 // We have a fill/border/mask image that spans multiple lines.
266 // We need to adjust the offset by the width of all previous lines.
267 // Think of background painting on inlines as though you had one long line, a
268 // single continuous strip. Even though that strip has been broken up across
269 // multiple lines, you still paint it as though you had one single line. This
270 // means each line has to pick up the background where the previous line left
271 // off.
272 LayoutUnit offset_on_line;
273 LayoutUnit total_width;
274 ComputeFragmentOffsetOnLine(direction, &offset_on_line, &total_width);
275
276 if (inline_box_fragment_.Style().IsHorizontalWritingMode()) {
277 return PhysicalRect(paint_rect.X() - offset_on_line, paint_rect.Y(),
278 total_width, paint_rect.Height());
279 }
280 return PhysicalRect(paint_rect.X(), paint_rect.Y() - offset_on_line,
281 paint_rect.Width(), total_width);
282 }
283
NGClipRectForNinePieceImageStrip(const ComputedStyle & style,PhysicalBoxSides sides_to_include,const NinePieceImage & image,const PhysicalRect & paint_rect)284 static PhysicalRect NGClipRectForNinePieceImageStrip(
285 const ComputedStyle& style,
286 PhysicalBoxSides sides_to_include,
287 const NinePieceImage& image,
288 const PhysicalRect& paint_rect) {
289 PhysicalRect clip_rect(paint_rect);
290 LayoutRectOutsets outsets = style.ImageOutsets(image);
291 if (sides_to_include.left) {
292 clip_rect.SetX(paint_rect.X() - outsets.Left());
293 clip_rect.SetWidth(paint_rect.Width() + outsets.Left());
294 }
295 if (sides_to_include.right)
296 clip_rect.SetWidth(clip_rect.Width() + outsets.Right());
297 if (sides_to_include.top) {
298 clip_rect.SetY(paint_rect.Y() - outsets.Top());
299 clip_rect.SetHeight(paint_rect.Height() + outsets.Top());
300 }
301 if (sides_to_include.bottom)
302 clip_rect.SetHeight(clip_rect.Height() + outsets.Bottom());
303 return clip_rect;
304 }
305
306 InlineBoxPainterBase::BorderPaintingType
GetBorderPaintType(const PhysicalRect & adjusted_frame_rect,IntRect & adjusted_clip_rect,bool object_has_multiple_boxes) const307 NGInlineBoxFragmentPainterBase::GetBorderPaintType(
308 const PhysicalRect& adjusted_frame_rect,
309 IntRect& adjusted_clip_rect,
310 bool object_has_multiple_boxes) const {
311 const ComputedStyle& style = inline_box_fragment_.Style();
312 if (!style.HasBorderDecoration())
313 return kDontPaintBorders;
314
315 const NinePieceImage& border_image = style.BorderImage();
316 StyleImage* border_image_source = border_image.GetImage();
317 bool has_border_image =
318 border_image_source && border_image_source->CanRender();
319 if (has_border_image && !border_image_source->IsLoaded())
320 return kDontPaintBorders;
321
322 // The simple case is where we either have no border image or we are the
323 // only box for this object. In those cases only a single call to draw is
324 // required.
325 if (!has_border_image || !object_has_multiple_boxes) {
326 adjusted_clip_rect = PixelSnappedIntRect(adjusted_frame_rect);
327 return kPaintBordersWithoutClip;
328 }
329
330 // We have a border image that spans multiple lines.
331 adjusted_clip_rect = PixelSnappedIntRect(NGClipRectForNinePieceImageStrip(
332 style, SidesToInclude(), border_image, adjusted_frame_rect));
333 return kPaintBordersWithClip;
334 }
335
PaintNormalBoxShadow(const PaintInfo & info,const ComputedStyle & s,const PhysicalRect & paint_rect)336 void NGInlineBoxFragmentPainterBase::PaintNormalBoxShadow(
337 const PaintInfo& info,
338 const ComputedStyle& s,
339 const PhysicalRect& paint_rect) {
340 BoxPainterBase::PaintNormalBoxShadow(info, paint_rect, s, SidesToInclude());
341 }
342
PaintInsetBoxShadow(const PaintInfo & info,const ComputedStyle & s,const PhysicalRect & paint_rect)343 void NGInlineBoxFragmentPainterBase::PaintInsetBoxShadow(
344 const PaintInfo& info,
345 const ComputedStyle& s,
346 const PhysicalRect& paint_rect) {
347 BoxPainterBase::PaintInsetBoxShadowWithBorderRect(info, paint_rect, s,
348 SidesToInclude());
349 }
350
351 // Paint all fragments for the |layout_inline|. This function is used only for
352 // self-painting |LayoutInline|.
PaintAllFragments(const LayoutInline & layout_inline,const PaintInfo & paint_info,const PhysicalOffset & paint_offset)353 void NGInlineBoxFragmentPainter::PaintAllFragments(
354 const LayoutInline& layout_inline,
355 const PaintInfo& paint_info,
356 const PhysicalOffset& paint_offset) {
357 // TODO(kojii): If the block flow is dirty, children of these fragments
358 // maybe already deleted. crbug.com/963103
359 const LayoutBlockFlow* block_flow = layout_inline.ContainingNGBlockFlow();
360 if (UNLIKELY(block_flow->NeedsLayout()))
361 return;
362
363 if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) {
364 auto fragments = NGPaintFragment::InlineFragmentsFor(&layout_inline);
365
366 // TODO(kojii): The root of this inline formatting context should have a
367 // PaintFragment, but it looks like there's a case it doesn't stand.
368 // crbug.com/969096
369 CHECK(block_flow->PaintFragment() || fragments.IsEmpty());
370
371 for (const NGPaintFragment* fragment : fragments) {
372 PhysicalOffset child_offset = paint_offset +
373 fragment->OffsetInContainerBlock() -
374 fragment->Offset();
375 DCHECK(fragment->PhysicalFragment().IsBox());
376 NGInlineBoxFragmentPainter(*fragment).Paint(paint_info, child_offset);
377 }
378 return;
379 }
380
381 NGInlineCursor cursor(*block_flow);
382 cursor.MoveTo(layout_inline);
383 if (!cursor)
384 return;
385 // Convert from inline fragment index to container fragment index, as the
386 // inline may not start in the first fragment generated for the inline
387 // formatting context.
388 wtf_size_t target_fragment_idx =
389 cursor.CurrentContainerFragmentIndex() +
390 paint_info.context.GetPaintController().CurrentFragment();
391 for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
392 if (target_fragment_idx != cursor.CurrentContainerFragmentIndex())
393 continue;
394 const NGFragmentItem* item = cursor.CurrentItem();
395 DCHECK(item);
396 const NGPhysicalBoxFragment* box_fragment = item->BoxFragment();
397 DCHECK(box_fragment);
398 NGInlineBoxFragmentPainter(cursor, *item, *box_fragment)
399 .Paint(paint_info, paint_offset);
400 }
401 }
402
403 #if DCHECK_IS_ON()
CheckValid() const404 void NGInlineBoxFragmentPainter::CheckValid() const {
405 if (inline_box_item_) {
406 DCHECK(inline_box_cursor_);
407 DCHECK_EQ(inline_box_cursor_->Current().Item(), inline_box_item_);
408 }
409
410 DCHECK_EQ(inline_box_fragment_.Type(),
411 NGPhysicalFragment::NGFragmentType::kFragmentBox);
412 DCHECK_EQ(inline_box_fragment_.BoxType(),
413 NGPhysicalFragment::NGBoxType::kInlineBox);
414 }
415 #endif
416
417 } // namespace blink
418