Index: third_party/WebKit/Source/core/paint/NGBoxFragmentPainter.cpp |
diff --git a/third_party/WebKit/Source/core/paint/NGBoxFragmentPainter.cpp b/third_party/WebKit/Source/core/paint/NGBoxFragmentPainter.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0ed2a1de7963bfd85dfceeeef83490547b07a882 |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/paint/NGBoxFragmentPainter.cpp |
@@ -0,0 +1,485 @@ |
+// Copyright 2017 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/NGBoxFragmentPainter.h" |
+ |
+#include "core/layout/BackgroundBleedAvoidance.h" |
+#include "core/layout/ng/inline/ng_physical_line_box_fragment.h" |
+#include "core/layout/ng/inline/ng_physical_text_fragment.h" |
+#include "core/layout/ng/ng_physical_box_fragment.h" |
+#include "core/paint/BoxBorderPainter.h" |
+#include "core/paint/BoxDecorationData.h" |
+#include "core/paint/NGTextFragmentPainter.h" |
+#include "core/paint/PaintInfo.h" |
+#include "core/paint/PaintLayer.h" |
+#include "core/paint/RoundedInnerRectClipper.h" |
+#include "core/style/FillLayer.h" |
+#include "platform/geometry/LayoutRectOutsets.h" |
+#include "platform/graphics/GraphicsContextStateSaver.h" |
+#include "platform/graphics/paint/DrawingRecorder.h" |
+ |
+namespace blink { |
+ |
+void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info, |
+ const LayoutPoint& paint_offset) { |
+ PaintChildren(paint_info, paint_offset); |
+} |
+ |
+void NGBoxFragmentPainter::PaintChildren(const PaintInfo& paint_info, |
+ const LayoutPoint& paint_offset) { |
+ PaintInfo child_info(paint_info); |
+ for (const auto& child : box_fragment_->Children()) { |
+ if (child->Type() == NGPhysicalBoxFragment::kFragmentLineBox) { |
+ const NGPhysicalLineBoxFragment* line_box_fragment = |
+ ToNGPhysicalLineBoxFragment(child.Get()); |
+ PaintLineBox(line_box_fragment, child_info, paint_offset); |
+ } |
+ } |
+} |
+ |
+void NGBoxFragmentPainter::PaintLineBox( |
+ const NGPhysicalLineBoxFragment* fragment, |
+ const PaintInfo& paint_info, |
+ const LayoutPoint& paint_offset) { |
+ // TODO: Should this check if the line boxes intersects with the dirty rect |
+ // like legacy layout or do we want to change the invalidation logic? |
+ |
+ LayoutRect overflow_rect(box_fragment_->VisualOverflowRect()); |
+ overflow_rect.MoveBy(paint_offset); |
+ |
+ // TODO(eae): Should this go here, in ::Paint or in the NGTextFragmentPainter |
+ // class? |
+ DrawingRecorder recorder( |
+ paint_info.context, *fragment, |
+ DisplayItem::PaintPhaseToDrawingType(paint_info.phase), |
+ PixelSnappedIntRect(overflow_rect)); |
+ |
+ for (const auto& child : fragment->Children()) { |
+ if (child->Type() == NGPhysicalBoxFragment::kFragmentLineBox) { |
+ CHECK(false); |
+ } else if (child->Type() == NGPhysicalBoxFragment::kFragmentText) { |
+ const NGPhysicalTextFragment* text_fragment = |
+ ToNGPhysicalTextFragment(child.Get()); |
+ NGTextFragmentPainter(text_fragment) |
+ .Paint(GetDocument(), paint_info, paint_offset); |
+ } |
+ } |
+} |
+ |
+bool IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
+ const NGPhysicalFragment* fragment, |
+ const PaintInfo& paint_info) { |
+ // TODO(eae): Implement. Will require changing the type of |
+ // PaintInfo::paint_container_ or using the NGBoxFragment container object. |
+ return false; |
+} |
+ |
+void NGBoxFragmentPainter::PaintBoxDecorationBackground( |
+ const PaintInfo& paint_info, |
+ const LayoutPoint& paint_offset) { |
+ LayoutRect paint_rect; |
+ if (IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
+ box_fragment_, paint_info)) { |
+ // For the case where we are painting the background into the scrolling |
+ // contents layer of a composited scroller we need to include the entire |
+ // overflow rect. |
+ |
+ // The background painting code assumes that the borders are part of the |
+ // paintRect so we expand the paintRect by the border size when painting the |
+ // background into the scrolling contents layer. |
+ } else { |
+ // TODO(eae): We need better converters for ng geometry types. Long term we |
+ // probably want to change the paint code to take NGPhysical* but that is a |
+ // much bigger change. |
+ NGPhysicalSize size = box_fragment_->Size(); |
+ paint_rect = LayoutRect(LayoutPoint(), LayoutSize(size.width, size.height)); |
+ } |
+ |
+ paint_rect.MoveBy(paint_offset); |
+ PaintBoxDecorationBackgroundWithRect(paint_info, paint_offset, paint_rect); |
+} |
+ |
+void NGBoxFragmentPainter::PaintBoxDecorationBackgroundWithRect( |
+ const PaintInfo& paint_info, |
+ const LayoutPoint& paint_offset, |
+ const LayoutRect& paint_rect) { |
+ bool painting_overflow_contents = |
+ IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
+ box_fragment_, paint_info); |
+ const ComputedStyle& style = box_fragment_->Style(); |
+ |
+ // TODO(eae): Implement support for painting overflow contents. |
+ const DisplayItemClient& display_item_client = *box_fragment_; |
+ if (DrawingRecorder::UseCachedDrawingIfPossible( |
+ paint_info.context, display_item_client, |
+ DisplayItem::kBoxDecorationBackground)) |
+ return; |
+ |
+ DrawingRecorder recorder( |
+ paint_info.context, display_item_client, |
+ DisplayItem::kBoxDecorationBackground, |
+ FloatRect(BoundsForDrawingRecorder(paint_info, paint_offset))); |
+ BoxDecorationData box_decoration_data(box_fragment_); |
+ GraphicsContextStateSaver state_saver(paint_info.context, false); |
+ |
+ if (!painting_overflow_contents) { |
+ // FIXME: Should eventually give the theme control over whether the box |
+ // shadow should paint, since controls could have custom shadows of their |
+ // own. |
+ PaintNormalBoxShadow(paint_info, paint_rect, style); |
+ |
+ if (BleedAvoidanceIsClipping(box_decoration_data.bleed_avoidance)) { |
+ state_saver.Save(); |
+ FloatRoundedRect border = style.GetRoundedBorderFor(paint_rect); |
+ paint_info.context.ClipRoundedRect(border); |
+ |
+ if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
+ paint_info.context.BeginLayer(); |
+ } |
+ } |
+ |
+ // TODO(layout-dev): Support theme painting. |
+ |
+ // TODO(eae): Support SkipRootBackground painting. |
+ bool should_paint_background = true; |
+ if (should_paint_background) { |
+ PaintBackground(paint_info, paint_rect, |
+ box_decoration_data.background_color, |
+ box_decoration_data.bleed_avoidance); |
+ } |
+ |
+ if (!painting_overflow_contents) { |
+ PaintInsetBoxShadow(paint_info, paint_rect, style); |
+ |
+ if (box_decoration_data.has_border_decoration) { |
+ PaintBorder(box_fragment_, paint_info, paint_rect, style, |
+ box_decoration_data.bleed_avoidance); |
+ } |
+ } |
+ |
+ if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
+ paint_info.context.EndLayer(); |
+} |
+ |
+void NGBoxFragmentPainter::PaintBackground( |
+ const PaintInfo& paint_info, |
+ const LayoutRect& paint_rect, |
+ const Color& background_color, |
+ BackgroundBleedAvoidance bleed_avoidance) { |
+ // if (layout_box_.IsDocumentElement()) |
+ // return; |
+ // if (layout_box_.BackgroundStolenForBeingBody()) |
+ // return; |
+ // if (layout_box_.BackgroundIsKnownToBeObscured()) |
+ // return; |
+ PaintFillLayers(paint_info, background_color, |
+ box_fragment_->Style().BackgroundLayers(), paint_rect, |
+ bleed_avoidance); |
+} |
+ |
+void NGBoxFragmentPainter::PaintFillLayers( |
+ const PaintInfo& paint_info, |
+ const Color& c, |
+ const FillLayer& fill_layer, |
+ const LayoutRect& rect, |
+ BackgroundBleedAvoidance avoidance, |
+ SkBlendMode op, |
+ const NGPhysicalFragment* bg_fragment) { |
+ FillLayerOcclusionOutputList reversed_paint_list; |
+ bool should_draw_background_in_separate_buffer = |
+ CalculateFillLayerOcclusionCulling(reversed_paint_list, fill_layer, |
+ GetDocument(), box_fragment_->Style()); |
+ |
+ // TODO(trchen): We can optimize out isolation group if we have a |
+ // non-transparent background color and the bottom layer encloses all other |
+ // layers. |
+ |
+ GraphicsContext& context = paint_info.context; |
+ |
+ if (should_draw_background_in_separate_buffer) |
+ context.BeginLayer(); |
+ |
+ for (auto it = reversed_paint_list.rbegin(); it != reversed_paint_list.rend(); |
+ ++it) { |
+ PaintFillLayer(box_fragment_, paint_info, c, **it, rect, avoidance, 0, |
+ LayoutSize(), op, bg_fragment); |
+ } |
+ |
+ if (should_draw_background_in_separate_buffer) |
+ context.EndLayer(); |
+} |
+ |
+namespace { |
+ |
+inline bool PaintFastBottomLayer( |
+ const NGPhysicalBoxFragment* fragment, |
+ const PaintInfo& paint_info, |
+ const BoxPainterBase::FillLayerInfo& info, |
+ const FillLayer& layer, |
+ const LayoutRect& rect, |
+ BackgroundBleedAvoidance bleed_avoidance, |
+ bool has_line_box_sibling, |
+ const LayoutSize& box_size, |
+ SkBlendMode op, |
+ const NGPhysicalFragment* background_fragment |
+ /*Optional<BackgroundImageGeometry>& geometry*/) { |
+ // Painting a background image from an ancestor onto a cell is a complex case. |
+ // if (obj.IsTableCell() && bg_fragment && |
+ // !bg_fragment->IsTableCell()) |
+ // return false; |
+ // Complex cases not handled on the fast path. |
+ // if (!info.is_bottom_layer || !info.is_border_fill || |
+ // info.is_clipped_with_local_scrolling) |
+ // return false; |
+ |
+ // Transparent layer, nothing to paint. |
+ if (!info.should_paint_color && !info.should_paint_image) |
+ return true; |
+ |
+ // When the layer has an image, figure out whether it is covered by a single |
+ // tile. |
+ FloatRect image_tile; |
+ if (info.should_paint_image) { |
+ // TODO(layout-dev): Add support for background images. |
+ return false; |
+ } |
+ |
+ // At this point we're committed to the fast path: the destination (r)rect |
+ // fits within a single tile, and we can paint it using direct draw(R)Rect() |
+ // calls. |
+ GraphicsContext& context = paint_info.context; |
+ FloatRoundedRect border = |
+ info.is_rounded_fill |
+ ? BoxPainterBase::BackgroundRoundedRectAdjustedForBleedAvoidance( |
+ fragment->Style(), rect, bleed_avoidance, has_line_box_sibling, |
+ box_size, info.include_left_edge, info.include_right_edge) |
+ : FloatRoundedRect(PixelSnappedIntRect(rect)); |
+ |
+ // Optional<RoundedInnerRectClipper> clipper; |
+ // if (info.is_rounded_fill && !border.IsRenderable()) { |
+ // When the rrect is not renderable, we resort to clipping. |
+ // RoundedInnerRectClipper handles this case via discrete, corner-wise |
+ // clipping. |
+ // clipper.emplace(obj, paint_info, rect, border, kApplyToContext); |
+ // border.SetRadii(FloatRoundedRect::Radii()); |
+ //} |
+ |
+ // Paint the color if needed. |
+ if (info.should_paint_color) |
+ context.FillRoundedRect(border, info.color); |
+ |
+ return true; |
+} |
+ |
+} // anonymous namespace |
+ |
+void NGBoxFragmentPainter::PaintFillLayer( |
+ const NGPhysicalBoxFragment* fragment, |
+ const PaintInfo& paint_info, |
+ const Color& color, |
+ const FillLayer& bg_layer, |
+ const LayoutRect& rect, |
+ BackgroundBleedAvoidance avoidance, |
+ const NGPhysicalFragment* box, |
+ const LayoutSize& box_size, |
+ SkBlendMode op, |
+ const NGPhysicalFragment* bg_fragment) { |
+ GraphicsContext& context = paint_info.context; |
+ const ComputedStyle& style = fragment->Style(); |
+ if (rect.IsEmpty()) |
+ return; |
+ |
+ const BoxPainterBase::FillLayerInfo info( |
+ fragment->GetLayoutObject()->GetDocument(), style, |
+ fragment->HasOverflowClip(), color, bg_layer, avoidance, |
+ box ? (box->BorderEdges() & NGPhysicalTextFragment::kLeftBorder) : true, |
+ box ? (box->BorderEdges() & NGPhysicalTextFragment::kRightBorder) : true); |
+ |
+ // Optional<BackgroundImageGeometry> geometry; |
+ // bool has_line_box_sibling = box && (box->NextLineBox() || |
+ // box->PrevLineBox()); |
+ bool has_line_box_sibling = false; |
+ |
+ // Fast path for drawing simple color backgrounds. |
+ if (PaintFastBottomLayer(fragment, paint_info, info, bg_layer, rect, |
+ avoidance, has_line_box_sibling, box_size, op, |
+ bg_fragment)) { |
+ return; |
+ } |
+ |
+ NGPixelSnappedPhysicalBoxStrut borders = fragment->BorderWidths(); |
+ NGPhysicalBoxStrut paddings; // TODO(layout-dev): Implement |
+ |
+ Optional<RoundedInnerRectClipper> clip_to_border; |
+ if (info.is_rounded_fill) { |
+ FloatRoundedRect border = |
+ info.is_border_fill |
+ ? BackgroundRoundedRectAdjustedForBleedAvoidance( |
+ style, rect, avoidance, has_line_box_sibling, box_size, |
+ info.include_left_edge, info.include_right_edge) |
+ : GetBackgroundRoundedRect(style, rect, has_line_box_sibling, |
+ box_size, info.include_left_edge, |
+ info.include_right_edge); |
+ |
+ // Clip to the padding or content boxes as necessary. |
+ if (bg_layer.Clip() == kContentFillBox) { |
+ // TODO(layout-dev): Do we need to include the padding here? |
+ LayoutRectOutsets border_inset( |
+ LayoutUnit(borders.top), LayoutUnit(borders.right), |
+ LayoutUnit(borders.bottom), LayoutUnit(borders.left)); |
+ border = style.GetRoundedInnerBorderFor( |
+ LayoutRect(border.Rect()), border_inset, info.include_left_edge, |
+ info.include_right_edge); |
+ } else if (bg_layer.Clip() == kPaddingFillBox) { |
+ border = style.GetRoundedInnerBorderFor(LayoutRect(border.Rect()), |
+ info.include_left_edge, |
+ info.include_right_edge); |
+ } |
+ |
+ clip_to_border.emplace(*fragment, paint_info, rect, border, |
+ kApplyToContext); |
+ } |
+ |
+ GraphicsContextStateSaver clip_with_scrolling_state_saver( |
+ context, info.is_clipped_with_local_scrolling); |
+ LayoutRect scrolled_paint_rect = rect; |
+ if (info.is_clipped_with_local_scrolling && |
+ !IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
+ fragment, paint_info)) { |
+ // Clip to the overflow area. |
+ // TODO(chrishtr): this should be pixel-snapped. |
+ // TODO(layout-dev): We don't know the location... |
+ // context.Clip(FloatRect(this_box.OverflowClipRect(rect.Location()))); |
+ |
+ // Adjust the paint rect to reflect a scrolled content box with borders at |
+ // the ends. |
+ // TODO(layout_dev): Support scrolling |
+ // IntSize offset = this_box.ScrolledContentOffset(); |
+ // scrolled_paint_rect.Move(-offset); |
+ // scrolled_paint_rect.SetWidth(borders.left + this_box.ScrollWidth() + |
+ // borders.right); scrolled_paint_rect.SetHeight(this_box.BorderTop() + |
+ // this_box.ScrollHeight() + |
+ // this_box.BorderBottom()); |
+ } |
+ |
+ GraphicsContextStateSaver background_clip_state_saver(context, false); |
+ IntRect mask_rect; |
+ |
+ switch (bg_layer.Clip()) { |
+ case kPaddingFillBox: |
+ case kContentFillBox: { |
+ if (info.is_rounded_fill) |
+ break; |
+ |
+ // Clip to the padding or content boxes as necessary. |
+ bool include_padding = bg_layer.Clip() == kContentFillBox; |
+ LayoutRect clip_rect( |
+ scrolled_paint_rect.X() + borders.left, |
+ scrolled_paint_rect.Y() + borders.top, |
+ scrolled_paint_rect.Width() - borders.left - borders.right, |
+ scrolled_paint_rect.Height() - borders.top - borders.bottom); |
+ |
+ if (include_padding) { |
+ clip_rect.ContractEdges(paddings.top, paddings.right, paddings.bottom, |
+ paddings.left); |
+ } |
+ |
+ background_clip_state_saver.Save(); |
+ // TODO(chrishtr): this should be pixel-snapped. |
+ context.Clip(FloatRect(clip_rect)); |
+ |
+ break; |
+ } |
+ case kTextFillBox: { |
+ // First figure out how big the mask has to be. It should be no bigger |
+ // than what we need to actually render, so we should intersect the dirty |
+ // rect with the border box of the background. |
+ mask_rect = PixelSnappedIntRect(rect); |
+ |
+ // We draw the background into a separate layer, to be later masked with |
+ // yet another layer holding the text content. |
+ background_clip_state_saver.Save(); |
+ context.Clip(mask_rect); |
+ context.BeginLayer(); |
+ |
+ break; |
+ } |
+ case kBorderFillBox: |
+ break; |
+ default: |
+ NOTREACHED(); |
+ break; |
+ } |
+ |
+ // Paint the color first underneath all images, culled if background image |
+ // occludes it. |
+ // TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the |
+ // culling test by verifying whether the background image covers the entire |
+ // painting area. |
+ if (info.is_bottom_layer && info.color.Alpha() && info.should_paint_color) { |
+ IntRect background_rect(PixelSnappedIntRect(scrolled_paint_rect)); |
+ context.FillRect(background_rect, info.color); |
+ } |
+ |
+ // No progressive loading of the background image. |
+ if (info.should_paint_image) { |
+ // TODO(layout-dev): Add support for image painting. |
+ DCHECK(false); |
+ } |
+ |
+ if (bg_layer.Clip() == kTextFillBox) { |
+ // Create the text mask layer. |
+ context.BeginLayer(1, SkBlendMode::kDstIn); |
+ |
+ // Now draw the text into the mask. We do this by painting using a special |
+ // paint phase that signals to |
+ // InlineTextBoxes that they should just add their contents to the clip. |
+ PaintInfo info(context, mask_rect, kPaintPhaseTextClip, |
+ kGlobalPaintNormalPhase, 0); |
+ // if (box) { |
+ // const RootInlineBox& root = box->Root(); |
+ // box->Paint(info, |
+ // LayoutPoint(scrolled_paint_rect.X() - box->X(), |
+ // scrolled_paint_rect.Y() - box->Y()), |
+ // root.LineTop(), root.LineBottom()); |
+ //} else { |
+ // // FIXME: this should only have an effect for the line box list within |
+ // // |obj|. Change this to create a LineBoxListPainter directly. |
+ // LayoutSize local_offset = |
+ // obj.IsBox() ? ToLayoutBox(&obj)->LocationOffset() : LayoutSize(); |
+ // obj.Paint(info, scrolled_paint_rect.Location() - local_offset); |
+ //} |
+ |
+ context.EndLayer(); |
+ context.EndLayer(); |
+ } |
+} |
+ |
+LayoutRect NGBoxFragmentPainter::BoundsForDrawingRecorder( |
+ const PaintInfo& paint_info, |
+ const LayoutPoint& adjusted_paint_offset) { |
+ LayoutRect bounds = box_fragment_->VisualOverflowRect(); |
+ bounds.MoveBy(adjusted_paint_offset); |
+ return bounds; |
+} |
+ |
+void NGBoxFragmentPainter::PaintBorder(const NGPhysicalBoxFragment* fragment, |
+ const PaintInfo& info, |
+ const LayoutRect& rect, |
+ const ComputedStyle& style, |
+ BackgroundBleedAvoidance bleed_avoidance, |
+ bool include_logical_left_edge, |
+ bool include_logical_right_edge) { |
+ // TODO(layout-dev): Add support for image border painting. |
+ const BoxBorderPainter border_painter(rect, style, bleed_avoidance, |
+ include_logical_left_edge, |
+ include_logical_right_edge); |
+ border_painter.PaintBorder(info, rect); |
+} |
+ |
+const Document& NGBoxFragmentPainter::GetDocument() const { |
+ return box_fragment_->GetLayoutObject()->GetDocument(); |
+} |
+ |
+} // namespace blink |