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