OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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 "core/paint/NGBoxFragmentPainter.h" |
| 6 |
| 7 #include "core/layout/BackgroundBleedAvoidance.h" |
| 8 #include "core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| 9 #include "core/layout/ng/inline/ng_physical_text_fragment.h" |
| 10 #include "core/layout/ng/ng_physical_box_fragment.h" |
| 11 #include "core/paint/BoxBorderPainter.h" |
| 12 #include "core/paint/BoxDecorationData.h" |
| 13 #include "core/paint/NGTextFragmentPainter.h" |
| 14 #include "core/paint/PaintInfo.h" |
| 15 #include "core/paint/PaintLayer.h" |
| 16 #include "core/paint/RoundedInnerRectClipper.h" |
| 17 #include "core/style/FillLayer.h" |
| 18 #include "platform/geometry/LayoutRectOutsets.h" |
| 19 #include "platform/graphics/GraphicsContextStateSaver.h" |
| 20 #include "platform/graphics/paint/DrawingRecorder.h" |
| 21 |
| 22 namespace blink { |
| 23 |
| 24 void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info, |
| 25 const LayoutPoint& paint_offset) { |
| 26 PaintChildren(paint_info, paint_offset); |
| 27 } |
| 28 |
| 29 void NGBoxFragmentPainter::PaintChildren(const PaintInfo& paint_info, |
| 30 const LayoutPoint& paint_offset) { |
| 31 PaintInfo child_info(paint_info); |
| 32 for (const auto& child : box_fragment_->Children()) { |
| 33 if (child->Type() == NGPhysicalBoxFragment::kFragmentLineBox) { |
| 34 const NGPhysicalLineBoxFragment* line_box_fragment = |
| 35 ToNGPhysicalLineBoxFragment(child.Get()); |
| 36 PaintLineBox(line_box_fragment, child_info, paint_offset); |
| 37 } |
| 38 } |
| 39 } |
| 40 |
| 41 void NGBoxFragmentPainter::PaintLineBox( |
| 42 const NGPhysicalLineBoxFragment* fragment, |
| 43 const PaintInfo& paint_info, |
| 44 const LayoutPoint& paint_offset) { |
| 45 // TODO: Should this check if the line boxes intersects with the dirty rect |
| 46 // like legacy layout or do we want to change the invalidation logic? |
| 47 |
| 48 LayoutRect overflow_rect(box_fragment_->VisualOverflowRect()); |
| 49 overflow_rect.MoveBy(paint_offset); |
| 50 |
| 51 // TODO(eae): Should this go here, in ::Paint or in the NGTextFragmentPainter |
| 52 // class? |
| 53 DrawingRecorder recorder( |
| 54 paint_info.context, *fragment, |
| 55 DisplayItem::PaintPhaseToDrawingType(paint_info.phase), |
| 56 PixelSnappedIntRect(overflow_rect)); |
| 57 |
| 58 for (const auto& child : fragment->Children()) { |
| 59 if (child->Type() == NGPhysicalBoxFragment::kFragmentLineBox) { |
| 60 CHECK(false); |
| 61 } else if (child->Type() == NGPhysicalBoxFragment::kFragmentText) { |
| 62 const NGPhysicalTextFragment* text_fragment = |
| 63 ToNGPhysicalTextFragment(child.Get()); |
| 64 NGTextFragmentPainter(text_fragment) |
| 65 .Paint(GetDocument(), paint_info, paint_offset); |
| 66 } |
| 67 } |
| 68 } |
| 69 |
| 70 bool IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| 71 const NGPhysicalFragment* fragment, |
| 72 const PaintInfo& paint_info) { |
| 73 // TODO(eae): Implement. Will require changing the type of |
| 74 // PaintInfo::paint_container_ or using the NGBoxFragment container object. |
| 75 return false; |
| 76 } |
| 77 |
| 78 void NGBoxFragmentPainter::PaintBoxDecorationBackground( |
| 79 const PaintInfo& paint_info, |
| 80 const LayoutPoint& paint_offset) { |
| 81 LayoutRect paint_rect; |
| 82 if (IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| 83 box_fragment_, paint_info)) { |
| 84 // For the case where we are painting the background into the scrolling |
| 85 // contents layer of a composited scroller we need to include the entire |
| 86 // overflow rect. |
| 87 |
| 88 // The background painting code assumes that the borders are part of the |
| 89 // paintRect so we expand the paintRect by the border size when painting the |
| 90 // background into the scrolling contents layer. |
| 91 } else { |
| 92 // TODO(eae): We need better converters for ng geometry types. Long term we |
| 93 // probably want to change the paint code to take NGPhysical* but that is a |
| 94 // much bigger change. |
| 95 NGPhysicalSize size = box_fragment_->Size(); |
| 96 paint_rect = LayoutRect(LayoutPoint(), LayoutSize(size.width, size.height)); |
| 97 } |
| 98 |
| 99 paint_rect.MoveBy(paint_offset); |
| 100 PaintBoxDecorationBackgroundWithRect(paint_info, paint_offset, paint_rect); |
| 101 } |
| 102 |
| 103 void NGBoxFragmentPainter::PaintBoxDecorationBackgroundWithRect( |
| 104 const PaintInfo& paint_info, |
| 105 const LayoutPoint& paint_offset, |
| 106 const LayoutRect& paint_rect) { |
| 107 bool painting_overflow_contents = |
| 108 IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| 109 box_fragment_, paint_info); |
| 110 const ComputedStyle& style = box_fragment_->Style(); |
| 111 |
| 112 // TODO(eae): Implement support for painting overflow contents. |
| 113 const DisplayItemClient& display_item_client = *box_fragment_; |
| 114 if (DrawingRecorder::UseCachedDrawingIfPossible( |
| 115 paint_info.context, display_item_client, |
| 116 DisplayItem::kBoxDecorationBackground)) |
| 117 return; |
| 118 |
| 119 DrawingRecorder recorder( |
| 120 paint_info.context, display_item_client, |
| 121 DisplayItem::kBoxDecorationBackground, |
| 122 FloatRect(BoundsForDrawingRecorder(paint_info, paint_offset))); |
| 123 BoxDecorationData box_decoration_data(box_fragment_); |
| 124 GraphicsContextStateSaver state_saver(paint_info.context, false); |
| 125 |
| 126 if (!painting_overflow_contents) { |
| 127 // FIXME: Should eventually give the theme control over whether the box |
| 128 // shadow should paint, since controls could have custom shadows of their |
| 129 // own. |
| 130 PaintNormalBoxShadow(paint_info, paint_rect, style); |
| 131 |
| 132 if (BleedAvoidanceIsClipping(box_decoration_data.bleed_avoidance)) { |
| 133 state_saver.Save(); |
| 134 FloatRoundedRect border = style.GetRoundedBorderFor(paint_rect); |
| 135 paint_info.context.ClipRoundedRect(border); |
| 136 |
| 137 if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
| 138 paint_info.context.BeginLayer(); |
| 139 } |
| 140 } |
| 141 |
| 142 // TODO(layout-dev): Support theme painting. |
| 143 |
| 144 // TODO(eae): Support SkipRootBackground painting. |
| 145 bool should_paint_background = true; |
| 146 if (should_paint_background) { |
| 147 PaintBackground(paint_info, paint_rect, |
| 148 box_decoration_data.background_color, |
| 149 box_decoration_data.bleed_avoidance); |
| 150 } |
| 151 |
| 152 if (!painting_overflow_contents) { |
| 153 PaintInsetBoxShadow(paint_info, paint_rect, style); |
| 154 |
| 155 if (box_decoration_data.has_border_decoration) { |
| 156 PaintBorder(box_fragment_, paint_info, paint_rect, style, |
| 157 box_decoration_data.bleed_avoidance); |
| 158 } |
| 159 } |
| 160 |
| 161 if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
| 162 paint_info.context.EndLayer(); |
| 163 } |
| 164 |
| 165 void NGBoxFragmentPainter::PaintBackground( |
| 166 const PaintInfo& paint_info, |
| 167 const LayoutRect& paint_rect, |
| 168 const Color& background_color, |
| 169 BackgroundBleedAvoidance bleed_avoidance) { |
| 170 // if (layout_box_.IsDocumentElement()) |
| 171 // return; |
| 172 // if (layout_box_.BackgroundStolenForBeingBody()) |
| 173 // return; |
| 174 // if (layout_box_.BackgroundIsKnownToBeObscured()) |
| 175 // return; |
| 176 PaintFillLayers(paint_info, background_color, |
| 177 box_fragment_->Style().BackgroundLayers(), paint_rect, |
| 178 bleed_avoidance); |
| 179 } |
| 180 |
| 181 void NGBoxFragmentPainter::PaintFillLayers( |
| 182 const PaintInfo& paint_info, |
| 183 const Color& c, |
| 184 const FillLayer& fill_layer, |
| 185 const LayoutRect& rect, |
| 186 BackgroundBleedAvoidance avoidance, |
| 187 SkBlendMode op, |
| 188 const NGPhysicalFragment* bg_fragment) { |
| 189 FillLayerOcclusionOutputList reversed_paint_list; |
| 190 bool should_draw_background_in_separate_buffer = |
| 191 CalculateFillLayerOcclusionCulling(reversed_paint_list, fill_layer, |
| 192 GetDocument(), box_fragment_->Style()); |
| 193 |
| 194 // TODO(trchen): We can optimize out isolation group if we have a |
| 195 // non-transparent background color and the bottom layer encloses all other |
| 196 // layers. |
| 197 |
| 198 GraphicsContext& context = paint_info.context; |
| 199 |
| 200 if (should_draw_background_in_separate_buffer) |
| 201 context.BeginLayer(); |
| 202 |
| 203 for (auto it = reversed_paint_list.rbegin(); it != reversed_paint_list.rend(); |
| 204 ++it) { |
| 205 PaintFillLayer(box_fragment_, paint_info, c, **it, rect, avoidance, 0, |
| 206 LayoutSize(), op, bg_fragment); |
| 207 } |
| 208 |
| 209 if (should_draw_background_in_separate_buffer) |
| 210 context.EndLayer(); |
| 211 } |
| 212 |
| 213 namespace { |
| 214 |
| 215 inline bool PaintFastBottomLayer( |
| 216 const NGPhysicalBoxFragment* fragment, |
| 217 const PaintInfo& paint_info, |
| 218 const BoxPainterBase::FillLayerInfo& info, |
| 219 const FillLayer& layer, |
| 220 const LayoutRect& rect, |
| 221 BackgroundBleedAvoidance bleed_avoidance, |
| 222 bool has_line_box_sibling, |
| 223 const LayoutSize& box_size, |
| 224 SkBlendMode op, |
| 225 const NGPhysicalFragment* background_fragment |
| 226 /*Optional<BackgroundImageGeometry>& geometry*/) { |
| 227 // Painting a background image from an ancestor onto a cell is a complex case. |
| 228 // if (obj.IsTableCell() && bg_fragment && |
| 229 // !bg_fragment->IsTableCell()) |
| 230 // return false; |
| 231 // Complex cases not handled on the fast path. |
| 232 // if (!info.is_bottom_layer || !info.is_border_fill || |
| 233 // info.is_clipped_with_local_scrolling) |
| 234 // return false; |
| 235 |
| 236 // Transparent layer, nothing to paint. |
| 237 if (!info.should_paint_color && !info.should_paint_image) |
| 238 return true; |
| 239 |
| 240 // When the layer has an image, figure out whether it is covered by a single |
| 241 // tile. |
| 242 FloatRect image_tile; |
| 243 if (info.should_paint_image) { |
| 244 // TODO(layout-dev): Add support for background images. |
| 245 return false; |
| 246 } |
| 247 |
| 248 // At this point we're committed to the fast path: the destination (r)rect |
| 249 // fits within a single tile, and we can paint it using direct draw(R)Rect() |
| 250 // calls. |
| 251 GraphicsContext& context = paint_info.context; |
| 252 FloatRoundedRect border = |
| 253 info.is_rounded_fill |
| 254 ? BoxPainterBase::BackgroundRoundedRectAdjustedForBleedAvoidance( |
| 255 fragment->Style(), rect, bleed_avoidance, has_line_box_sibling, |
| 256 box_size, info.include_left_edge, info.include_right_edge) |
| 257 : FloatRoundedRect(PixelSnappedIntRect(rect)); |
| 258 |
| 259 // Optional<RoundedInnerRectClipper> clipper; |
| 260 // if (info.is_rounded_fill && !border.IsRenderable()) { |
| 261 // When the rrect is not renderable, we resort to clipping. |
| 262 // RoundedInnerRectClipper handles this case via discrete, corner-wise |
| 263 // clipping. |
| 264 // clipper.emplace(obj, paint_info, rect, border, kApplyToContext); |
| 265 // border.SetRadii(FloatRoundedRect::Radii()); |
| 266 //} |
| 267 |
| 268 // Paint the color if needed. |
| 269 if (info.should_paint_color) |
| 270 context.FillRoundedRect(border, info.color); |
| 271 |
| 272 return true; |
| 273 } |
| 274 |
| 275 } // anonymous namespace |
| 276 |
| 277 void NGBoxFragmentPainter::PaintFillLayer( |
| 278 const NGPhysicalBoxFragment* fragment, |
| 279 const PaintInfo& paint_info, |
| 280 const Color& color, |
| 281 const FillLayer& bg_layer, |
| 282 const LayoutRect& rect, |
| 283 BackgroundBleedAvoidance avoidance, |
| 284 const NGPhysicalFragment* box, |
| 285 const LayoutSize& box_size, |
| 286 SkBlendMode op, |
| 287 const NGPhysicalFragment* bg_fragment) { |
| 288 GraphicsContext& context = paint_info.context; |
| 289 const ComputedStyle& style = fragment->Style(); |
| 290 if (rect.IsEmpty()) |
| 291 return; |
| 292 |
| 293 const BoxPainterBase::FillLayerInfo info( |
| 294 fragment->GetLayoutObject()->GetDocument(), style, |
| 295 fragment->HasOverflowClip(), color, bg_layer, avoidance, |
| 296 box ? (box->BorderEdges() & NGPhysicalTextFragment::kLeftBorder) : true, |
| 297 box ? (box->BorderEdges() & NGPhysicalTextFragment::kRightBorder) : true); |
| 298 |
| 299 // Optional<BackgroundImageGeometry> geometry; |
| 300 // bool has_line_box_sibling = box && (box->NextLineBox() || |
| 301 // box->PrevLineBox()); |
| 302 bool has_line_box_sibling = false; |
| 303 |
| 304 // Fast path for drawing simple color backgrounds. |
| 305 if (PaintFastBottomLayer(fragment, paint_info, info, bg_layer, rect, |
| 306 avoidance, has_line_box_sibling, box_size, op, |
| 307 bg_fragment)) { |
| 308 return; |
| 309 } |
| 310 |
| 311 NGPixelSnappedPhysicalBoxStrut borders = fragment->BorderWidths(); |
| 312 NGPhysicalBoxStrut paddings; // TODO(layout-dev): Implement |
| 313 |
| 314 Optional<RoundedInnerRectClipper> clip_to_border; |
| 315 if (info.is_rounded_fill) { |
| 316 FloatRoundedRect border = |
| 317 info.is_border_fill |
| 318 ? BackgroundRoundedRectAdjustedForBleedAvoidance( |
| 319 style, rect, avoidance, has_line_box_sibling, box_size, |
| 320 info.include_left_edge, info.include_right_edge) |
| 321 : GetBackgroundRoundedRect(style, rect, has_line_box_sibling, |
| 322 box_size, info.include_left_edge, |
| 323 info.include_right_edge); |
| 324 |
| 325 // Clip to the padding or content boxes as necessary. |
| 326 if (bg_layer.Clip() == kContentFillBox) { |
| 327 // TODO(layout-dev): Do we need to include the padding here? |
| 328 LayoutRectOutsets border_inset( |
| 329 LayoutUnit(borders.top), LayoutUnit(borders.right), |
| 330 LayoutUnit(borders.bottom), LayoutUnit(borders.left)); |
| 331 border = style.GetRoundedInnerBorderFor( |
| 332 LayoutRect(border.Rect()), border_inset, info.include_left_edge, |
| 333 info.include_right_edge); |
| 334 } else if (bg_layer.Clip() == kPaddingFillBox) { |
| 335 border = style.GetRoundedInnerBorderFor(LayoutRect(border.Rect()), |
| 336 info.include_left_edge, |
| 337 info.include_right_edge); |
| 338 } |
| 339 |
| 340 clip_to_border.emplace(*fragment, paint_info, rect, border, |
| 341 kApplyToContext); |
| 342 } |
| 343 |
| 344 GraphicsContextStateSaver clip_with_scrolling_state_saver( |
| 345 context, info.is_clipped_with_local_scrolling); |
| 346 LayoutRect scrolled_paint_rect = rect; |
| 347 if (info.is_clipped_with_local_scrolling && |
| 348 !IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| 349 fragment, paint_info)) { |
| 350 // Clip to the overflow area. |
| 351 // TODO(chrishtr): this should be pixel-snapped. |
| 352 // TODO(layout-dev): We don't know the location... |
| 353 // context.Clip(FloatRect(this_box.OverflowClipRect(rect.Location()))); |
| 354 |
| 355 // Adjust the paint rect to reflect a scrolled content box with borders at |
| 356 // the ends. |
| 357 // TODO(layout_dev): Support scrolling |
| 358 // IntSize offset = this_box.ScrolledContentOffset(); |
| 359 // scrolled_paint_rect.Move(-offset); |
| 360 // scrolled_paint_rect.SetWidth(borders.left + this_box.ScrollWidth() + |
| 361 // borders.right); scrolled_paint_rect.SetHeight(this_box.BorderTop() + |
| 362 // this_box.ScrollHeight() + |
| 363 // this_box.BorderBottom()); |
| 364 } |
| 365 |
| 366 GraphicsContextStateSaver background_clip_state_saver(context, false); |
| 367 IntRect mask_rect; |
| 368 |
| 369 switch (bg_layer.Clip()) { |
| 370 case kPaddingFillBox: |
| 371 case kContentFillBox: { |
| 372 if (info.is_rounded_fill) |
| 373 break; |
| 374 |
| 375 // Clip to the padding or content boxes as necessary. |
| 376 bool include_padding = bg_layer.Clip() == kContentFillBox; |
| 377 LayoutRect clip_rect( |
| 378 scrolled_paint_rect.X() + borders.left, |
| 379 scrolled_paint_rect.Y() + borders.top, |
| 380 scrolled_paint_rect.Width() - borders.left - borders.right, |
| 381 scrolled_paint_rect.Height() - borders.top - borders.bottom); |
| 382 |
| 383 if (include_padding) { |
| 384 clip_rect.ContractEdges(paddings.top, paddings.right, paddings.bottom, |
| 385 paddings.left); |
| 386 } |
| 387 |
| 388 background_clip_state_saver.Save(); |
| 389 // TODO(chrishtr): this should be pixel-snapped. |
| 390 context.Clip(FloatRect(clip_rect)); |
| 391 |
| 392 break; |
| 393 } |
| 394 case kTextFillBox: { |
| 395 // First figure out how big the mask has to be. It should be no bigger |
| 396 // than what we need to actually render, so we should intersect the dirty |
| 397 // rect with the border box of the background. |
| 398 mask_rect = PixelSnappedIntRect(rect); |
| 399 |
| 400 // We draw the background into a separate layer, to be later masked with |
| 401 // yet another layer holding the text content. |
| 402 background_clip_state_saver.Save(); |
| 403 context.Clip(mask_rect); |
| 404 context.BeginLayer(); |
| 405 |
| 406 break; |
| 407 } |
| 408 case kBorderFillBox: |
| 409 break; |
| 410 default: |
| 411 NOTREACHED(); |
| 412 break; |
| 413 } |
| 414 |
| 415 // Paint the color first underneath all images, culled if background image |
| 416 // occludes it. |
| 417 // TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the |
| 418 // culling test by verifying whether the background image covers the entire |
| 419 // painting area. |
| 420 if (info.is_bottom_layer && info.color.Alpha() && info.should_paint_color) { |
| 421 IntRect background_rect(PixelSnappedIntRect(scrolled_paint_rect)); |
| 422 context.FillRect(background_rect, info.color); |
| 423 } |
| 424 |
| 425 // No progressive loading of the background image. |
| 426 if (info.should_paint_image) { |
| 427 // TODO(layout-dev): Add support for image painting. |
| 428 DCHECK(false); |
| 429 } |
| 430 |
| 431 if (bg_layer.Clip() == kTextFillBox) { |
| 432 // Create the text mask layer. |
| 433 context.BeginLayer(1, SkBlendMode::kDstIn); |
| 434 |
| 435 // Now draw the text into the mask. We do this by painting using a special |
| 436 // paint phase that signals to |
| 437 // InlineTextBoxes that they should just add their contents to the clip. |
| 438 PaintInfo info(context, mask_rect, kPaintPhaseTextClip, |
| 439 kGlobalPaintNormalPhase, 0); |
| 440 // if (box) { |
| 441 // const RootInlineBox& root = box->Root(); |
| 442 // box->Paint(info, |
| 443 // LayoutPoint(scrolled_paint_rect.X() - box->X(), |
| 444 // scrolled_paint_rect.Y() - box->Y()), |
| 445 // root.LineTop(), root.LineBottom()); |
| 446 //} else { |
| 447 // // FIXME: this should only have an effect for the line box list within |
| 448 // // |obj|. Change this to create a LineBoxListPainter directly. |
| 449 // LayoutSize local_offset = |
| 450 // obj.IsBox() ? ToLayoutBox(&obj)->LocationOffset() : LayoutSize(); |
| 451 // obj.Paint(info, scrolled_paint_rect.Location() - local_offset); |
| 452 //} |
| 453 |
| 454 context.EndLayer(); |
| 455 context.EndLayer(); |
| 456 } |
| 457 } |
| 458 |
| 459 LayoutRect NGBoxFragmentPainter::BoundsForDrawingRecorder( |
| 460 const PaintInfo& paint_info, |
| 461 const LayoutPoint& adjusted_paint_offset) { |
| 462 LayoutRect bounds = box_fragment_->VisualOverflowRect(); |
| 463 bounds.MoveBy(adjusted_paint_offset); |
| 464 return bounds; |
| 465 } |
| 466 |
| 467 void NGBoxFragmentPainter::PaintBorder(const NGPhysicalBoxFragment* fragment, |
| 468 const PaintInfo& info, |
| 469 const LayoutRect& rect, |
| 470 const ComputedStyle& style, |
| 471 BackgroundBleedAvoidance bleed_avoidance, |
| 472 bool include_logical_left_edge, |
| 473 bool include_logical_right_edge) { |
| 474 // TODO(layout-dev): Add support for image border painting. |
| 475 const BoxBorderPainter border_painter(rect, style, bleed_avoidance, |
| 476 include_logical_left_edge, |
| 477 include_logical_right_edge); |
| 478 border_painter.PaintBorder(info, rect); |
| 479 } |
| 480 |
| 481 const Document& NGBoxFragmentPainter::GetDocument() const { |
| 482 return box_fragment_->GetLayoutObject()->GetDocument(); |
| 483 } |
| 484 |
| 485 } // namespace blink |
OLD | NEW |