| // Copyright 2014 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/inline_flow_box_painter.h" |
| |
| #include "third_party/blink/renderer/core/layout/api/line_layout_api_shim.h" |
| #include "third_party/blink/renderer/core/layout/line/inline_flow_box.h" |
| #include "third_party/blink/renderer/core/layout/line/root_inline_box.h" |
| #include "third_party/blink/renderer/core/paint/background_image_geometry.h" |
| #include "third_party/blink/renderer/core/paint/box_model_object_painter.h" |
| #include "third_party/blink/renderer/core/paint/box_painter_base.h" |
| #include "third_party/blink/renderer/core/paint/nine_piece_image_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| inline Node* GetNode(const LayoutObject* box_model) { |
| Node* node = nullptr; |
| for (const LayoutObject* obj = box_model; obj && !node; obj = obj->Parent()) |
| node = obj->GeneratingNode(); |
| return node; |
| } |
| |
| inline const LayoutBoxModelObject* GetBoxModelObject( |
| const InlineFlowBox& flow_box) { |
| return ToLayoutBoxModelObject( |
| LineLayoutAPIShim::LayoutObjectFrom(flow_box.BoxModelObject())); |
| } |
| |
| } // anonymous namespace |
| |
| InlineFlowBoxPainter::InlineFlowBoxPainter(const InlineFlowBox& flow_box) |
| : InlineBoxPainterBase( |
| *GetBoxModelObject(flow_box), |
| &GetBoxModelObject(flow_box)->GetDocument(), |
| GetNode(GetBoxModelObject(flow_box)), |
| flow_box.GetLineLayoutItem().StyleRef(), |
| flow_box.GetLineLayoutItem().StyleRef(flow_box.IsFirstLineStyle())), |
| inline_flow_box_(flow_box) { |
| object_has_multiple_boxes_ = inline_flow_box_.PrevForSameLayoutObject() || |
| inline_flow_box_.NextForSameLayoutObject(); |
| bool force_include_logical_edges = |
| (!inline_flow_box_.PrevForSameLayoutObject() && |
| !inline_flow_box_.NextForSameLayoutObject()) || |
| !inline_flow_box_.Parent(); |
| include_logical_left_edge_for_box_shadow_ = |
| force_include_logical_edges || inline_flow_box_.IncludeLogicalLeftEdge(); |
| include_logical_right_edge_for_box_shadow_ = |
| force_include_logical_edges || inline_flow_box_.IncludeLogicalRightEdge(); |
| } |
| |
| void InlineFlowBoxPainter::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| const LayoutUnit line_top, |
| const LayoutUnit line_bottom) { |
| DCHECK(!ShouldPaintSelfOutline(paint_info.phase) && |
| !ShouldPaintDescendantOutlines(paint_info.phase)); |
| |
| LayoutRect overflow_rect( |
| inline_flow_box_.VisualOverflowRect(line_top, line_bottom)); |
| inline_flow_box_.FlipForWritingMode(overflow_rect); |
| overflow_rect.MoveBy(paint_offset); |
| |
| if (!paint_info.GetCullRect().IntersectsCullRect(overflow_rect)) |
| return; |
| |
| if (paint_info.phase == PaintPhase::kMask) { |
| PaintMask(paint_info, paint_offset); |
| return; |
| } |
| |
| if (paint_info.phase == PaintPhase::kForeground) { |
| // Paint our background, border and box-shadow. |
| PaintBackgroundBorderShadow(paint_info, paint_offset); |
| } |
| |
| // Paint our children. |
| PaintInfo child_info(paint_info); |
| for (InlineBox* curr = inline_flow_box_.FirstChild(); curr; |
| curr = curr->NextOnLine()) { |
| if (curr->GetLineLayoutItem().IsText() || |
| !curr->BoxModelObject().HasSelfPaintingLayer()) |
| curr->Paint(child_info, paint_offset, line_top, line_bottom); |
| } |
| } |
| |
| static LayoutRect ClipRectForNinePieceImageStrip(const InlineFlowBox& box, |
| const NinePieceImage& image, |
| const LayoutRect& paint_rect) { |
| LayoutRect clip_rect(paint_rect); |
| const ComputedStyle& style = box.GetLineLayoutItem().StyleRef(); |
| LayoutRectOutsets outsets = style.ImageOutsets(image); |
| if (box.IsHorizontal()) { |
| clip_rect.SetY(paint_rect.Y() - outsets.Top()); |
| clip_rect.SetHeight(paint_rect.Height() + outsets.Top() + outsets.Bottom()); |
| if (box.IncludeLogicalLeftEdge()) { |
| clip_rect.SetX(paint_rect.X() - outsets.Left()); |
| clip_rect.SetWidth(paint_rect.Width() + outsets.Left()); |
| } |
| if (box.IncludeLogicalRightEdge()) |
| clip_rect.SetWidth(clip_rect.Width() + outsets.Right()); |
| } else { |
| clip_rect.SetX(paint_rect.X() - outsets.Left()); |
| clip_rect.SetWidth(paint_rect.Width() + outsets.Left() + outsets.Right()); |
| if (box.IncludeLogicalLeftEdge()) { |
| clip_rect.SetY(paint_rect.Y() - outsets.Top()); |
| clip_rect.SetHeight(paint_rect.Height() + outsets.Top()); |
| } |
| if (box.IncludeLogicalRightEdge()) |
| clip_rect.SetHeight(clip_rect.Height() + outsets.Bottom()); |
| } |
| return clip_rect; |
| } |
| |
| LayoutRect InlineFlowBoxPainter::PaintRectForImageStrip( |
| const LayoutRect& 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 logical_offset_on_line; |
| LayoutUnit total_logical_width; |
| if (direction == TextDirection::kLtr) { |
| for (const InlineFlowBox* curr = inline_flow_box_.PrevForSameLayoutObject(); |
| curr; curr = curr->PrevForSameLayoutObject()) |
| logical_offset_on_line += curr->LogicalWidth(); |
| total_logical_width = logical_offset_on_line; |
| for (const InlineFlowBox* curr = &inline_flow_box_; curr; |
| curr = curr->NextForSameLayoutObject()) |
| total_logical_width += curr->LogicalWidth(); |
| } else { |
| for (const InlineFlowBox* curr = inline_flow_box_.NextForSameLayoutObject(); |
| curr; curr = curr->NextForSameLayoutObject()) |
| logical_offset_on_line += curr->LogicalWidth(); |
| total_logical_width = logical_offset_on_line; |
| for (const InlineFlowBox* curr = &inline_flow_box_; curr; |
| curr = curr->PrevForSameLayoutObject()) |
| total_logical_width += curr->LogicalWidth(); |
| } |
| LayoutUnit strip_x = |
| paint_rect.X() - |
| (inline_flow_box_.IsHorizontal() ? logical_offset_on_line : LayoutUnit()); |
| LayoutUnit strip_y = |
| paint_rect.Y() - |
| (inline_flow_box_.IsHorizontal() ? LayoutUnit() : logical_offset_on_line); |
| LayoutUnit strip_width = inline_flow_box_.IsHorizontal() ? total_logical_width |
| : paint_rect.Width(); |
| LayoutUnit strip_height = inline_flow_box_.IsHorizontal() |
| ? paint_rect.Height() |
| : total_logical_width; |
| return LayoutRect(strip_x, strip_y, strip_width, strip_height); |
| } |
| |
| InlineBoxPainterBase::BorderPaintingType |
| InlineFlowBoxPainter::GetBorderPaintType(const LayoutRect& adjusted_frame_rect, |
| IntRect& adjusted_clip_rect) const { |
| adjusted_clip_rect = PixelSnappedIntRect(adjusted_frame_rect); |
| if (inline_flow_box_.Parent() && |
| inline_flow_box_.GetLineLayoutItem().StyleRef().HasBorderDecoration()) { |
| const NinePieceImage& border_image = |
| inline_flow_box_.GetLineLayoutItem().StyleRef().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 || (!inline_flow_box_.PrevForSameLayoutObject() && |
| !inline_flow_box_.NextForSameLayoutObject())) |
| return kPaintBordersWithoutClip; |
| |
| // We have a border image that spans multiple lines. |
| adjusted_clip_rect = PixelSnappedIntRect(ClipRectForNinePieceImageStrip( |
| inline_flow_box_, border_image, adjusted_frame_rect)); |
| return kPaintBordersWithClip; |
| } |
| return kDontPaintBorders; |
| } |
| |
| void InlineFlowBoxPainter::PaintBackgroundBorderShadow( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| DCHECK(paint_info.phase == PaintPhase::kForeground); |
| |
| if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled()) |
| RecordHitTestData(paint_info, paint_offset); |
| |
| if (inline_flow_box_.GetLineLayoutItem().StyleRef().Visibility() != |
| EVisibility::kVisible) |
| return; |
| |
| // You can use p::first-line to specify a background. If so, the root line |
| // boxes for a line may actually have to paint a background. |
| LayoutObject* inline_flow_box_layout_object = |
| LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.GetLineLayoutItem()); |
| bool should_paint_box_decoration_background; |
| if (inline_flow_box_.Parent()) |
| should_paint_box_decoration_background = |
| inline_flow_box_layout_object->HasBoxDecorationBackground(); |
| else |
| should_paint_box_decoration_background = |
| inline_flow_box_.IsFirstLineStyle() && line_style_ != style_; |
| |
| if (!should_paint_box_decoration_background) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, inline_flow_box_, |
| DisplayItem::kBoxDecorationBackground)) |
| return; |
| |
| DrawingRecorder recorder(paint_info.context, inline_flow_box_, |
| DisplayItem::kBoxDecorationBackground); |
| |
| LayoutRect paint_rect = AdjustedPaintRect(paint_offset); |
| |
| const auto& box_model = *ToLayoutBoxModelObject( |
| LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.BoxModelObject())); |
| BackgroundImageGeometry geometry(box_model); |
| BoxModelObjectPainter box_painter(box_model, &inline_flow_box_); |
| PaintBoxDecorationBackground(box_painter, paint_info, paint_offset, |
| paint_rect, geometry, |
| inline_flow_box_.IncludeLogicalLeftEdge(), |
| inline_flow_box_.IncludeLogicalRightEdge()); |
| } |
| |
| void InlineFlowBoxPainter::PaintMask(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| DCHECK_EQ(PaintPhase::kMask, paint_info.phase); |
| const auto& box_model = *ToLayoutBoxModelObject( |
| LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.BoxModelObject())); |
| if (!box_model.HasMask() || |
| box_model.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, inline_flow_box_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase))) |
| return; |
| DrawingRecorder recorder( |
| paint_info.context, inline_flow_box_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase)); |
| |
| LayoutRect paint_rect = AdjustedPaintRect(paint_offset); |
| |
| const auto& mask_nine_piece_image = box_model.StyleRef().MaskBoxImage(); |
| const auto* mask_box_image = mask_nine_piece_image.GetImage(); |
| |
| // Figure out if we need to push a transparency layer to render our mask. |
| BackgroundImageGeometry geometry(box_model); |
| BoxModelObjectPainter box_painter(box_model, &inline_flow_box_); |
| PaintFillLayers(box_painter, paint_info, Color::kTransparent, |
| box_model.StyleRef().MaskLayers(), paint_rect, geometry); |
| |
| bool has_box_image = mask_box_image && mask_box_image->CanRender(); |
| if (!has_box_image || !mask_box_image->IsLoaded()) { |
| // Don't paint anything while we wait for the image to load. |
| return; |
| } |
| |
| // The simple case is where we are the only box for this object. In those |
| // cases only a single call to draw is required. |
| if (!inline_flow_box_.PrevForSameLayoutObject() && |
| !inline_flow_box_.NextForSameLayoutObject()) { |
| NinePieceImagePainter::Paint(paint_info.context, box_model, |
| box_model.GetDocument(), GetNode(&box_model), |
| paint_rect, box_model.StyleRef(), |
| mask_nine_piece_image); |
| } else { |
| // We have a mask image that spans multiple lines. |
| // FIXME: What the heck do we do with RTL here? The math we're using is |
| // obviously not right, but it isn't even clear how this should work at all. |
| LayoutRect image_strip_paint_rect = PaintRectForImageStrip( |
| LayoutRect(paint_rect.Location(), paint_rect.Size()), |
| TextDirection::kLtr); |
| FloatRect clip_rect(ClipRectForNinePieceImageStrip( |
| inline_flow_box_, mask_nine_piece_image, paint_rect)); |
| GraphicsContextStateSaver state_saver(paint_info.context); |
| // TODO(chrishtr): this should be pixel-snapped. |
| paint_info.context.Clip(clip_rect); |
| NinePieceImagePainter::Paint(paint_info.context, box_model, |
| box_model.GetDocument(), GetNode(&box_model), |
| image_strip_paint_rect, box_model.StyleRef(), |
| mask_nine_piece_image); |
| } |
| } |
| |
| // This method should not be needed. See crbug.com/530659. |
| LayoutRect InlineFlowBoxPainter::FrameRectClampedToLineTopAndBottomIfNeeded() |
| const { |
| LayoutRect rect(inline_flow_box_.FrameRect()); |
| |
| bool no_quirks_mode = |
| inline_flow_box_.GetLineLayoutItem().GetDocument().InNoQuirksMode(); |
| if (!no_quirks_mode && !inline_flow_box_.HasTextChildren() && |
| !(inline_flow_box_.DescendantsHaveSameLineHeightAndBaseline() && |
| inline_flow_box_.HasTextDescendants())) { |
| const RootInlineBox& root_box = inline_flow_box_.Root(); |
| LayoutUnit logical_top = |
| inline_flow_box_.IsHorizontal() ? rect.Y() : rect.X(); |
| LayoutUnit logical_height = |
| inline_flow_box_.IsHorizontal() ? rect.Height() : rect.Width(); |
| LayoutUnit bottom = |
| std::min(root_box.LineBottom(), logical_top + logical_height); |
| logical_top = std::max(root_box.LineTop(), logical_top); |
| logical_height = bottom - logical_top; |
| if (inline_flow_box_.IsHorizontal()) { |
| rect.SetY(logical_top); |
| rect.SetHeight(logical_height); |
| } else { |
| rect.SetX(logical_top); |
| rect.SetWidth(logical_height); |
| } |
| } |
| return rect; |
| } |
| |
| LayoutRect InlineFlowBoxPainter::AdjustedPaintRect( |
| const LayoutPoint& paint_offset) const { |
| LayoutRect frame_rect = FrameRectClampedToLineTopAndBottomIfNeeded(); |
| LayoutRect local_rect(frame_rect); |
| inline_flow_box_.FlipForWritingMode(local_rect); |
| LayoutPoint adjusted_paint_offset = paint_offset + local_rect.Location(); |
| return LayoutRect(adjusted_paint_offset, frame_rect.Size()); |
| } |
| |
| void InlineFlowBoxPainter::RecordHitTestData(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| LayoutObject* layout_object = |
| LineLayoutAPIShim::LayoutObjectFrom(inline_flow_box_.GetLineLayoutItem()); |
| |
| auto touch_action = layout_object->EffectiveWhitelistedTouchAction(); |
| if (touch_action == TouchAction::kTouchActionAuto) |
| return; |
| |
| HitTestData::RecordTouchActionRect( |
| paint_info.context, inline_flow_box_, |
| TouchActionRect(AdjustedPaintRect(paint_offset), touch_action)); |
| } |
| |
| } // namespace blink |