blob: 26b5005d5a85eb8e8bc77df3bada6ad32f5f7a5a [file] [log] [blame]
// 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/background_image_geometry.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/layout_table_col.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/style/border_edge.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
namespace blink {
namespace {
// Return the amount of space to leave between image tiles for the
// background-repeat: space property.
inline LayoutUnit GetSpaceBetweenImageTiles(LayoutUnit area_size,
LayoutUnit tile_size) {
int number_of_tiles = (area_size / tile_size).ToInt();
LayoutUnit space(-1);
if (number_of_tiles > 1) {
// Spec doesn't specify rounding, so use the same method as for
// background-repeat: round.
space = (area_size - number_of_tiles * tile_size) / (number_of_tiles - 1);
}
return space;
}
bool FixedBackgroundPaintsInLocalCoordinates(
const LayoutObject& obj,
const GlobalPaintFlags global_paint_flags) {
if (!obj.IsLayoutView())
return false;
const LayoutView& view = ToLayoutView(obj);
// TODO(wangxianzhu): For CAP, inline this function into
// FixedBackgroundPaintsInLocalCoordinates().
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
return view.GetBackgroundPaintLocation() !=
kBackgroundPaintInScrollingContents;
}
if (global_paint_flags & kGlobalPaintFlattenCompositingLayers)
return false;
PaintLayer* root_layer = view.Layer();
if (!root_layer || root_layer->GetCompositingState() == kNotComposited)
return false;
CompositedLayerMapping* mapping = root_layer->GetCompositedLayerMapping();
return !mapping->BackgroundPaintsOntoScrollingContentsLayer();
}
IntPoint AccumulatedScrollOffsetForFixedBackground(
const LayoutBoxModelObject& object,
const LayoutBoxModelObject* container) {
IntPoint result;
if (&object == container)
return result;
LayoutObject::AncestorSkipInfo skip_info(container);
for (const LayoutBlock* block = object.ContainingBlock(&skip_info);
block && !skip_info.AncestorSkipped();
block = block->ContainingBlock(&skip_info)) {
if (block->HasOverflowClip())
result += block->ScrolledContentOffset();
if (block == container)
break;
}
return result;
}
} // anonymous namespace
bool NeedsFullSizeDestination(const FillLayer& fill_layer) {
// When dealing with a mask, the dest rect needs to maintain the full size
// and the mask should be expanded to fill it out. This allows the mask to
// correctly mask the entire area it is meant to. This is unnecessary on the
// last layer, so the normal background path is taken for efficiency when
// creating the paint shader later on.
return fill_layer.GetType() == EFillLayerType::kMask && fill_layer.Next() &&
fill_layer.Composite() != kCompositeSourceOver;
}
void BackgroundImageGeometry::SetNoRepeatX(const FillLayer& fill_layer,
LayoutUnit x_offset,
LayoutUnit snapped_x_offset) {
if (NeedsFullSizeDestination(fill_layer)) {
SetPhaseX(-x_offset.ToFloat());
SetSpaceSize(
LayoutSize(unsnapped_dest_rect_.Width(), SpaceSize().Height()));
return;
}
// The snapped offset may not yet be snapped, so make sure it is an integer.
snapped_x_offset = LayoutUnit(RoundToInt(snapped_x_offset));
if (x_offset > 0) {
DCHECK(snapped_x_offset >= LayoutUnit());
// Move the dest rect if the offset is positive. The image "stays" where
// it is over the dest rect, so this effectively modifies the phase.
unsnapped_dest_rect_.Move(x_offset, LayoutUnit());
snapped_dest_rect_.Move(snapped_x_offset, LayoutUnit());
// Make the dest as wide as a tile, which will reduce the dest
// rect if the tile is too small to fill the paint_rect. If not,
// the dest rect will be clipped when intersected with the paint
// rect.
unsnapped_dest_rect_.SetWidth(tile_size_.Width());
snapped_dest_rect_.SetWidth(tile_size_.Width());
SetPhaseX(0);
} else {
// Otherwise, if the offset is negative use it to move the image under
// the dest rect (since we can't paint outside the paint_rect).
SetPhaseX(-x_offset.ToFloat());
// Reduce the width of the dest rect to draw only the portion of the
// tile that remains visible after offsetting the image.
// TODO(schenney): This might grow the dest rect if the dest rect has
// been adjusted for opaque borders.
unsnapped_dest_rect_.SetWidth(tile_size_.Width() + x_offset);
snapped_dest_rect_.SetWidth(tile_size_.Width() + snapped_x_offset);
}
// Force the horizontal space to zero, retaining vertical.
SetSpaceSize(LayoutSize(LayoutUnit(), SpaceSize().Height()));
}
void BackgroundImageGeometry::SetNoRepeatY(const FillLayer& fill_layer,
LayoutUnit y_offset,
LayoutUnit snapped_y_offset) {
if (NeedsFullSizeDestination(fill_layer)) {
SetPhaseY(-y_offset.ToFloat());
SetSpaceSize(
LayoutSize(SpaceSize().Width(), unsnapped_dest_rect_.Height()));
return;
}
// The snapped offset may not yet be snapped, so make sure it is an integer.
snapped_y_offset = LayoutUnit(RoundToInt(snapped_y_offset));
if (y_offset > 0) {
DCHECK(snapped_y_offset >= LayoutUnit());
// Move the dest rect if the offset is positive. The image "stays" where
// it is in the paint rect, so this effectively modifies the phase.
unsnapped_dest_rect_.Move(LayoutUnit(), y_offset);
snapped_dest_rect_.Move(LayoutUnit(), snapped_y_offset);
// Make the dest as wide as a tile, which will reduce the dest
// rect if the tile is too small to fill the paint_rect. If not,
// the dest rect will be clipped when intersected with the paint
// rect.
unsnapped_dest_rect_.SetHeight(tile_size_.Height());
snapped_dest_rect_.SetHeight(tile_size_.Height());
SetPhaseY(0);
} else {
// Otherwise, if the offset is negative, use it to move the image under
// the dest rect (since we can't paint outside the paint_rect).
SetPhaseY(-y_offset.ToFloat());
// Reduce the height of the dest rect to draw only the portion of the
// tile that remains visible after offsetting the image.
// TODO(schenney): This might grow the dest rect if the dest rect has
// been adjusted for opaque borders.
unsnapped_dest_rect_.SetHeight(tile_size_.Height() + y_offset);
snapped_dest_rect_.SetHeight(tile_size_.Height() + snapped_y_offset);
}
// Force the vertical space to zero, retaining horizontal.
SetSpaceSize(LayoutSize(SpaceSize().Width(), LayoutUnit()));
}
void BackgroundImageGeometry::SetRepeatX(const FillLayer& fill_layer,
LayoutUnit available_width,
LayoutUnit extra_offset) {
// All values are unsnapped to accurately set phase in the presence of
// zoom and large values. That is, accurately render the
// background-position value.
if (tile_size_.Width()) {
// Recompute computed_position because here we need to resolve against
// unsnapped widths to correctly set the phase.
LayoutUnit computed_position =
MinimumValueForLength(fill_layer.PositionX(), available_width) -
offset_in_background_.X();
// Identify the number of tiles that fit within the computed
// position in the direction we should be moving.
float number_of_tiles_in_position;
if (fill_layer.BackgroundXOrigin() == BackgroundEdgeOrigin::kRight) {
number_of_tiles_in_position =
(available_width - computed_position + extra_offset).ToFloat() /
tile_size_.Width().ToFloat();
} else {
number_of_tiles_in_position =
(computed_position + extra_offset).ToFloat() /
tile_size_.Width().ToFloat();
}
// Assuming a non-integral number of tiles, find out how much of the
// partial tile is visible. That is the phase.
float fractional_position_within_tile =
1.0f -
(number_of_tiles_in_position - truncf(number_of_tiles_in_position));
SetPhaseX(fractional_position_within_tile * tile_size_.Width());
} else {
SetPhaseX(0);
}
SetSpaceSize(LayoutSize(LayoutUnit(), SpaceSize().Height()));
}
void BackgroundImageGeometry::SetRepeatY(const FillLayer& fill_layer,
LayoutUnit available_height,
LayoutUnit extra_offset) {
// All values are unsnapped to accurately set phase in the presence of
// zoom and large values. That is, accurately render the
// background-position value.
if (tile_size_.Height()) {
// Recompute computed_position because here we need to resolve against
// unsnapped widths to correctly set the phase.
LayoutUnit computed_position =
MinimumValueForLength(fill_layer.PositionY(), available_height) -
offset_in_background_.Y();
// Identify the number of tiles that fit within the computed
// position in the direction we should be moving.
float number_of_tiles_in_position;
if (fill_layer.BackgroundYOrigin() == BackgroundEdgeOrigin::kBottom) {
number_of_tiles_in_position =
(available_height - computed_position + extra_offset).ToFloat() /
tile_size_.Height().ToFloat();
} else {
number_of_tiles_in_position =
(computed_position + extra_offset).ToFloat() /
tile_size_.Height().ToFloat();
}
// Assuming a non-integral number of tiles, find out how much of the
// partial tile is visible. That is the phase.
float fractional_position_within_tile =
1.0f -
(number_of_tiles_in_position - truncf(number_of_tiles_in_position));
SetPhaseY(fractional_position_within_tile * tile_size_.Height());
} else {
SetPhaseY(0);
}
SetSpaceSize(LayoutSize(SpaceSize().Width(), LayoutUnit()));
}
void BackgroundImageGeometry::SetSpaceX(LayoutUnit space,
LayoutUnit extra_offset) {
SetSpaceSize(LayoutSize(space, SpaceSize().Height()));
// Modify the phase to start a full tile at the edge of the paint area
LayoutUnit actual_width = tile_size_.Width() + space;
SetPhaseX(actual_width ? actual_width - fmodf(extra_offset, actual_width)
: 0);
}
void BackgroundImageGeometry::SetSpaceY(LayoutUnit space,
LayoutUnit extra_offset) {
SetSpaceSize(LayoutSize(SpaceSize().Width(), space));
// Modify the phase to start a full tile at the edge of the paint area
LayoutUnit actual_height = tile_size_.Height() + space;
SetPhaseY(actual_height ? actual_height - fmodf(extra_offset, actual_height)
: 0);
}
void BackgroundImageGeometry::UseFixedAttachment(
const LayoutPoint& attachment_point) {
LayoutPoint aligned_point = attachment_point;
phase_.Move(
std::max((aligned_point.X() - unsnapped_dest_rect_.X()).ToFloat(), 0.f),
std::max((aligned_point.Y() - unsnapped_dest_rect_.Y()).ToFloat(), 0.f));
}
enum ColumnGroupDirection { kColumnGroupStart, kColumnGroupEnd };
static void ExpandToTableColumnGroup(const LayoutTableCell& cell,
const LayoutTableCol& column_group,
LayoutUnit& value,
ColumnGroupDirection column_direction) {
auto sibling_cell = column_direction == kColumnGroupStart
? &LayoutTableCell::PreviousCell
: &LayoutTableCell::NextCell;
for (const auto* sibling = (cell.*sibling_cell)(); sibling;
sibling = (sibling->*sibling_cell)()) {
LayoutTableCol* innermost_col =
cell.Table()
->ColElementAtAbsoluteColumn(sibling->AbsoluteColumnIndex())
.InnermostColOrColGroup();
if (!innermost_col || innermost_col->EnclosingColumnGroup() != column_group)
break;
value += sibling->Size().Width();
}
}
LayoutPoint BackgroundImageGeometry::GetOffsetForCell(
const LayoutTableCell& cell,
const LayoutBox& positioning_box) {
LayoutSize border_spacing = LayoutSize(cell.Table()->HBorderSpacing(),
cell.Table()->VBorderSpacing());
if (positioning_box.IsTableSection())
return cell.Location() - border_spacing;
if (positioning_box.IsTableRow()) {
return LayoutPoint(cell.Location().X(), LayoutUnit()) -
LayoutSize(border_spacing.Width(), LayoutUnit());
}
LayoutRect sections_rect(LayoutPoint(), cell.Table()->Size());
cell.Table()->SubtractCaptionRect(sections_rect);
LayoutUnit height_of_captions =
cell.Table()->Size().Height() - sections_rect.Height();
LayoutPoint offset_in_background = LayoutPoint(
LayoutUnit(), (cell.Section()->Location().Y() -
cell.Table()->BorderBefore() - height_of_captions) +
cell.Location().Y());
DCHECK(positioning_box.IsLayoutTableCol());
if (ToLayoutTableCol(positioning_box).IsTableColumn()) {
return offset_in_background -
LayoutSize(LayoutUnit(), border_spacing.Height());
}
DCHECK(ToLayoutTableCol(positioning_box).IsTableColumnGroup());
LayoutUnit offset = offset_in_background.X();
ExpandToTableColumnGroup(cell, ToLayoutTableCol(positioning_box), offset,
kColumnGroupStart);
offset_in_background.Move(offset, LayoutUnit());
return offset_in_background -
LayoutSize(LayoutUnit(), border_spacing.Height());
}
LayoutSize BackgroundImageGeometry::GetBackgroundObjectDimensions(
const LayoutTableCell& cell,
const LayoutBox& positioning_box) {
LayoutSize border_spacing = LayoutSize(cell.Table()->HBorderSpacing(),
cell.Table()->VBorderSpacing());
if (positioning_box.IsTableSection())
return positioning_box.Size() - border_spacing - border_spacing;
if (positioning_box.IsTableRow()) {
return positioning_box.Size() -
LayoutSize(border_spacing.Width(), LayoutUnit()) -
LayoutSize(border_spacing.Width(), LayoutUnit());
}
DCHECK(positioning_box.IsLayoutTableCol());
LayoutRect sections_rect(LayoutPoint(), cell.Table()->Size());
cell.Table()->SubtractCaptionRect(sections_rect);
LayoutUnit column_height = sections_rect.Height() -
cell.Table()->BorderBefore() -
border_spacing.Height() - border_spacing.Height();
if (ToLayoutTableCol(positioning_box).IsTableColumn())
return LayoutSize(cell.Size().Width(), column_height);
DCHECK(ToLayoutTableCol(positioning_box).IsTableColumnGroup());
LayoutUnit width = cell.Size().Width();
ExpandToTableColumnGroup(cell, ToLayoutTableCol(positioning_box), width,
kColumnGroupStart);
ExpandToTableColumnGroup(cell, ToLayoutTableCol(positioning_box), width,
kColumnGroupEnd);
return LayoutSize(width, column_height);
}
namespace {
bool ShouldUseFixedAttachment(const FillLayer& fill_layer) {
if (RuntimeEnabledFeatures::FastMobileScrollingEnabled()) {
// As a side effect of an optimization to blit on scroll, we do not honor
// the CSS property "background-attachment: fixed" because it may result in
// rendering artifacts. Note, these artifacts only appear if we are blitting
// on scroll of a page that has fixed background images.
return false;
}
return fill_layer.Attachment() == EFillAttachment::kFixed;
}
LayoutRect FixedAttachmentPositioningArea(const LayoutBoxModelObject& obj,
const LayoutBoxModelObject* container,
const GlobalPaintFlags flags) {
LocalFrameView* frame_view = obj.View()->GetFrameView();
if (!frame_view)
return LayoutRect();
ScrollableArea* layout_viewport = frame_view->LayoutViewport();
DCHECK(layout_viewport);
LayoutRect rect = LayoutRect(
LayoutPoint(), LayoutSize(layout_viewport->VisibleContentRect().Size()));
if (FixedBackgroundPaintsInLocalCoordinates(obj, flags))
return rect;
// The LayoutView is the only object that can paint a fixed background into
// its scrolling contents layer, so it gets a special adjustment here.
if (obj.IsLayoutView()) {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
DCHECK_EQ(obj.GetBackgroundPaintLocation(),
kBackgroundPaintInScrollingContents);
rect.SetLocation(IntPoint(ToLayoutView(obj).ScrolledContentOffset()));
} else if (auto* mapping = obj.Layer()->GetCompositedLayerMapping()) {
if (mapping->BackgroundPaintsOntoScrollingContentsLayer())
rect.SetLocation(IntPoint(ToLayoutView(obj).ScrolledContentOffset()));
}
}
rect.MoveBy(AccumulatedScrollOffsetForFixedBackground(obj, container));
if (container)
rect.MoveBy(LayoutPoint(-container->LocalToAbsolute(FloatPoint())));
// By now we have converted the viewport rect to the border box space of
// |container|, however |container| does not necessarily create a paint
// offset translation node, thus its paint offset must be added to convert
// the rect to the space of the transform node.
// TODO(trchen): This function does only one simple thing -- mapping the
// viewport rect from frame space to whatever space the current paint
// context uses. However we can't always invoke geometry mapper because
// there are at least one caller uses this before PrePaint phase.
if (container) {
DCHECK_GE(container->GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kPrePaintClean);
rect.MoveBy(container->FirstFragment().PaintOffset());
}
return rect;
}
} // Anonymous namespace
BackgroundImageGeometry::BackgroundImageGeometry(const LayoutView& view)
: box_(view),
positioning_box_(view.RootBox()),
has_non_local_geometry_(false),
painting_view_(true),
painting_table_cell_(false),
cell_using_container_background_(false) {
// The background of the box generated by the root element covers the
// entire canvas and will be painted by the view object, but the we should
// still use the root element box for positioning.
positioning_size_override_ = view.RootBox().Size();
}
BackgroundImageGeometry::BackgroundImageGeometry(
const LayoutBoxModelObject& obj)
: box_(obj),
positioning_box_(obj),
has_non_local_geometry_(false),
painting_view_(false),
painting_table_cell_(false),
cell_using_container_background_(false) {
// Specialized constructor should be used for LayoutView.
DCHECK(!obj.IsLayoutView());
}
BackgroundImageGeometry::BackgroundImageGeometry(
const LayoutTableCell& cell,
const LayoutObject* background_object)
: box_(cell),
positioning_box_(background_object && !background_object->IsTableCell()
? ToLayoutBoxModelObject(*background_object)
: cell),
has_non_local_geometry_(false),
painting_view_(false),
painting_table_cell_(true) {
cell_using_container_background_ =
background_object && !background_object->IsTableCell();
if (cell_using_container_background_) {
offset_in_background_ =
GetOffsetForCell(cell, ToLayoutBox(*background_object));
positioning_size_override_ =
GetBackgroundObjectDimensions(cell, ToLayoutBox(*background_object));
}
}
void BackgroundImageGeometry::ComputeDestRectAdjustments(
const FillLayer& fill_layer,
const LayoutRect& unsnapped_positioning_area,
bool disallow_border_derived_adjustment,
LayoutRectOutsets& unsnapped_dest_adjust,
LayoutRectOutsets& snapped_dest_adjust) const {
switch (fill_layer.Clip()) {
case EFillBox::kContent:
// If the PaddingOutsets are zero then this is equivalent to
// kPadding and we should apply the snapping logic.
if (!positioning_box_.PaddingOutsets().IsZero()) {
unsnapped_dest_adjust = positioning_box_.PaddingOutsets();
unsnapped_dest_adjust += positioning_box_.BorderBoxOutsets();
// We're not trying to match a border position, so don't snap.
snapped_dest_adjust = unsnapped_dest_adjust;
return;
}
FALLTHROUGH;
case EFillBox::kPadding:
unsnapped_dest_adjust = positioning_box_.BorderBoxOutsets();
if (disallow_border_derived_adjustment) {
// Nothing to drive snapping behavior, so don't snap.
snapped_dest_adjust = unsnapped_dest_adjust;
} else {
// Force the snapped dest rect to match the inner border to
// avoid gaps between the background and border.
// TODO(schenney) The LayoutUnit(float) constructor always
// rounds down. We should FromFloatFloor or FromFloatCeil to
// move toward the border.
FloatRect inner_border_rect =
positioning_box_.StyleRef()
.GetRoundedInnerBorderFor(unsnapped_positioning_area)
.Rect();
snapped_dest_adjust.SetLeft(LayoutUnit(inner_border_rect.X()) -
unsnapped_dest_rect_.X());
snapped_dest_adjust.SetTop(LayoutUnit(inner_border_rect.Y()) -
unsnapped_dest_rect_.Y());
snapped_dest_adjust.SetRight(unsnapped_dest_rect_.MaxX() -
LayoutUnit(inner_border_rect.MaxX()));
snapped_dest_adjust.SetBottom(unsnapped_dest_rect_.MaxY() -
LayoutUnit(inner_border_rect.MaxY()));
}
return;
case EFillBox::kBorder: {
if (disallow_border_derived_adjustment) {
// All adjustments remain 0.
return;
}
// The dest rects can be adjusted. The snapped dest rect is forced
// to match the inner border to avoid gaps between the background and
// border, while the unsnapped dest moves according to the
// border box outsets. This leaves the unsnapped dest accurately
// conveying the content creator's intent when used for determining
// the pixels to use from sprite maps and other size and positioning
// properties.
// Note that the snapped adjustments do not have the same effect as
// pixel snapping the unsnapped rectangle. Border snapping snaps both
// the size and position of the borders, sometimes adjusting the inner
// border by more than a pixel when done (particularly under magnifying
// zoom).
// TODO(schenney) The LayoutUnit(float) constructor always
// rounds down. We should FromFloatFloor or FromFloatCeil to
// move toward the border.
BorderEdge edges[4];
positioning_box_.StyleRef().GetBorderEdgeInfo(edges);
FloatRect inner_border_rect =
positioning_box_.StyleRef()
.GetRoundedInnerBorderFor(unsnapped_positioning_area)
.Rect();
LayoutRectOutsets box_outsets = positioning_box_.BorderBoxOutsets();
if (edges[static_cast<unsigned>(BoxSide::kTop)].ObscuresBackground()) {
snapped_dest_adjust.SetTop(LayoutUnit(inner_border_rect.Y()) -
unsnapped_dest_rect_.Y());
unsnapped_dest_adjust.SetTop(box_outsets.Top());
}
if (edges[static_cast<unsigned>(BoxSide::kRight)].ObscuresBackground()) {
snapped_dest_adjust.SetRight(unsnapped_dest_rect_.MaxX() -
LayoutUnit(inner_border_rect.MaxX()));
unsnapped_dest_adjust.SetRight(box_outsets.Right());
}
if (edges[static_cast<unsigned>(BoxSide::kBottom)].ObscuresBackground()) {
snapped_dest_adjust.SetBottom(unsnapped_dest_rect_.MaxY() -
LayoutUnit(inner_border_rect.MaxY()));
unsnapped_dest_adjust.SetBottom(box_outsets.Bottom());
}
if (edges[static_cast<unsigned>(BoxSide::kLeft)].ObscuresBackground()) {
snapped_dest_adjust.SetLeft(LayoutUnit(inner_border_rect.X()) -
unsnapped_dest_rect_.X());
unsnapped_dest_adjust.SetLeft(box_outsets.Left());
}
}
return;
case EFillBox::kText:
return;
}
}
void BackgroundImageGeometry::ComputePositioningAreaAdjustments(
const FillLayer& fill_layer,
const LayoutRect& unsnapped_positioning_area,
bool disallow_border_derived_adjustment,
LayoutRectOutsets& unsnapped_box_outset,
LayoutRectOutsets& snapped_box_outset) const {
switch (fill_layer.Origin()) {
case EFillBox::kContent:
// If the PaddingOutsets are zero then this is equivalent to
// kPadding and we should apply the snapping logic.
if (!positioning_box_.PaddingOutsets().IsZero()) {
unsnapped_box_outset = positioning_box_.PaddingOutsets();
unsnapped_box_outset += positioning_box_.BorderBoxOutsets();
// We're not trying to match a border position, so don't snap.
snapped_box_outset = unsnapped_box_outset;
return;
}
FALLTHROUGH;
case EFillBox::kPadding:
unsnapped_box_outset = positioning_box_.BorderBoxOutsets();
if (disallow_border_derived_adjustment) {
snapped_box_outset = unsnapped_box_outset;
} else {
// Force the snapped positioning area to fill to the borders.
// Note that the snapped adjustments do not have the same effect as
// pixel snapping the unsnapped rectangle. Border snapping snaps both
// the size and position of the borders, sometimes adjusting the inner
// border by more than a pixel when done (particularly under magnifying
// zoom).
FloatRect inner_border_rect =
positioning_box_.StyleRef()
.GetRoundedInnerBorderFor(unsnapped_positioning_area)
.Rect();
snapped_box_outset.SetLeft(LayoutUnit(inner_border_rect.X()) -
unsnapped_positioning_area.X());
snapped_box_outset.SetTop(LayoutUnit(inner_border_rect.Y()) -
unsnapped_positioning_area.Y());
snapped_box_outset.SetRight(unsnapped_positioning_area.MaxX() -
LayoutUnit(inner_border_rect.MaxX()));
snapped_box_outset.SetBottom(unsnapped_positioning_area.MaxY() -
LayoutUnit(inner_border_rect.MaxY()));
}
return;
case EFillBox::kBorder:
// All adjustments remain 0.
snapped_box_outset = unsnapped_box_outset = LayoutRectOutsets(0, 0, 0, 0);
return;
case EFillBox::kText:
return;
}
}
void BackgroundImageGeometry::ComputePositioningArea(
const LayoutBoxModelObject* container,
PaintPhase paint_phase,
GlobalPaintFlags flags,
const FillLayer& fill_layer,
const LayoutRect& paint_rect,
LayoutRect& unsnapped_positioning_area,
LayoutRect& snapped_positioning_area,
LayoutPoint& unsnapped_box_offset,
LayoutPoint& snapped_box_offset) {
if (ShouldUseFixedAttachment(fill_layer)) {
// No snapping for fixed attachment.
SetHasNonLocalGeometry();
offset_in_background_ = LayoutPoint();
unsnapped_positioning_area =
FixedAttachmentPositioningArea(box_, container, flags);
unsnapped_dest_rect_ = snapped_dest_rect_ = snapped_positioning_area =
unsnapped_positioning_area;
} else {
unsnapped_dest_rect_ = paint_rect;
if (painting_view_ || cell_using_container_background_)
unsnapped_positioning_area.SetSize(positioning_size_override_);
else
unsnapped_positioning_area = unsnapped_dest_rect_;
// Attempt to shrink the destination rect if possible while also ensuring
// that it paints to the border:
//
// * for background-clip content-box/padding-box, we can restrict to the
// respective box, but for padding-box we also try to force alignment
// with the inner border.
//
// * for border-box, we can modify individual edges iff the border fully
// obscures the background.
//
// It is unsafe to derive dest from border information when any of the
// following is true:
// * the layer is not painted as part of a regular background phase
// (e.g.paint_phase == kMask)
// * non-SrcOver compositing is active
// * painting_view_ is set, meaning we're dealing with a
// LayoutView - for which dest rect is overflowing (expanded to cover
// the whole canvas).
// * We are painting table cells using the table background, or the table
// has collapsed borders
// * There is a border image, because it may not be opaque or may be outset.
bool disallow_border_derived_adjustment =
!ShouldPaintSelfBlockBackground(paint_phase) ||
fill_layer.Composite() != CompositeOperator::kCompositeSourceOver ||
painting_view_ || painting_table_cell_ ||
positioning_box_.StyleRef().BorderImage().GetImage() ||
positioning_box_.StyleRef().BorderCollapse() ==
EBorderCollapse::kCollapse;
// Compute all the outsets we need to apply to the rectangles. These
// outsets also include the snapping behavior.
LayoutRectOutsets unsnapped_dest_adjust;
LayoutRectOutsets snapped_dest_adjust;
LayoutRectOutsets unsnapped_box_outset;
LayoutRectOutsets snapped_box_outset;
ComputeDestRectAdjustments(fill_layer, unsnapped_positioning_area,
disallow_border_derived_adjustment,
unsnapped_dest_adjust, snapped_dest_adjust);
ComputePositioningAreaAdjustments(fill_layer, unsnapped_positioning_area,
disallow_border_derived_adjustment,
unsnapped_box_outset, snapped_box_outset);
// Apply the adjustments.
snapped_dest_rect_ = unsnapped_dest_rect_;
snapped_dest_rect_.Contract(snapped_dest_adjust);
snapped_dest_rect_ = LayoutRect(PixelSnappedIntRect(snapped_dest_rect_));
unsnapped_dest_rect_.Contract(unsnapped_dest_adjust);
snapped_positioning_area = unsnapped_positioning_area;
snapped_positioning_area.Contract(snapped_box_outset);
snapped_positioning_area =
LayoutRect(PixelSnappedIntRect(snapped_positioning_area));
unsnapped_positioning_area.Contract(unsnapped_box_outset);
// Offset of the positioning area from the corner of the
// positioning_box_.
// TODO(schenney): Could we enable dest adjust for collapsed
// borders if we computed this based on the actual offset between
// the rects?
unsnapped_box_offset =
LayoutPoint(unsnapped_box_outset.Left() - unsnapped_dest_adjust.Left(),
unsnapped_box_outset.Top() - unsnapped_dest_adjust.Top());
snapped_box_offset =
LayoutPoint(snapped_box_outset.Left() - snapped_dest_adjust.Left(),
snapped_box_outset.Top() - snapped_dest_adjust.Top());
// For view backgrounds, the input paint rect is specified in root element
// local coordinate (i.e. a transform is applied on the context for
// painting), and is expanded to cover the whole canvas. Since left/top is
// relative to the paint rect, we need to offset them back.
if (painting_view_) {
unsnapped_box_offset -= paint_rect.Location();
snapped_box_offset -= paint_rect.Location();
}
}
}
void BackgroundImageGeometry::CalculateFillTileSize(
const FillLayer& fill_layer,
const LayoutSize& unsnapped_positioning_area_size,
const LayoutSize& snapped_positioning_area_size) {
StyleImage* image = fill_layer.GetImage();
EFillSizeType type = fill_layer.SizeType();
// Tile size is snapped for relative sized images (typically generated
// content) and unsnapped for intrinsically sized content. Once we choose
// here we stop tracking whether the tile size is snapped or unsnapped.
LayoutSize positioning_area_size = image->ImageHasRelativeSize()
? snapped_positioning_area_size
: unsnapped_positioning_area_size;
LayoutSize image_intrinsic_size(image->ImageSize(
positioning_box_.GetDocument(),
positioning_box_.StyleRef().EffectiveZoom(), positioning_area_size));
switch (type) {
case EFillSizeType::kSizeLength: {
tile_size_ = positioning_area_size;
const Length& layer_width = fill_layer.SizeLength().Width();
const Length& layer_height = fill_layer.SizeLength().Height();
if (layer_width.IsFixed()) {
tile_size_.SetWidth(LayoutUnit(layer_width.Value()));
} else if (layer_width.IsPercentOrCalc()) {
tile_size_.SetWidth(
ValueForLength(layer_width, positioning_area_size.Width()));
}
if (layer_height.IsFixed()) {
tile_size_.SetHeight(LayoutUnit(layer_height.Value()));
} else if (layer_height.IsPercentOrCalc()) {
tile_size_.SetHeight(
ValueForLength(layer_height, positioning_area_size.Height()));
}
// If one of the values is auto we have to use the appropriate
// scale to maintain our aspect ratio.
if (layer_width.IsAuto() && !layer_height.IsAuto()) {
if (image->ImageHasRelativeSize()) {
// Spec says that auto should be 100% in the absence of
// an intrinsic ratio or size.
tile_size_.SetWidth(positioning_area_size.Width());
} else if (image_intrinsic_size.Height()) {
float adjusted_width = image_intrinsic_size.Width().ToFloat() /
image_intrinsic_size.Height().ToFloat() *
tile_size_.Height().ToFloat();
if (image_intrinsic_size.Width() >= 1 && adjusted_width < 1)
adjusted_width = 1;
tile_size_.SetWidth(LayoutUnit(adjusted_width));
}
} else if (!layer_width.IsAuto() && layer_height.IsAuto()) {
if (image->ImageHasRelativeSize()) {
// Spec says that auto should be 100% in the absence of
// an intrinsic ratio or size.
tile_size_.SetHeight(positioning_area_size.Height());
} else if (image_intrinsic_size.Width()) {
float adjusted_height = image_intrinsic_size.Height().ToFloat() /
image_intrinsic_size.Width().ToFloat() *
tile_size_.Width().ToFloat();
if (image_intrinsic_size.Height() >= 1 && adjusted_height < 1)
adjusted_height = 1;
tile_size_.SetHeight(LayoutUnit(adjusted_height));
}
} else if (layer_width.IsAuto() && layer_height.IsAuto()) {
// If both width and height are auto, use the image's intrinsic size.
tile_size_ = image_intrinsic_size;
}
tile_size_.ClampNegativeToZero();
return;
}
case EFillSizeType::kContain:
case EFillSizeType::kCover: {
// Always use the snapped positioning area size for this computation,
// so that we resize the image to completely fill the actual painted
// area.
float horizontal_scale_factor =
image_intrinsic_size.Width()
? snapped_positioning_area_size.Width().ToFloat() /
image_intrinsic_size.Width()
: 1.0f;
float vertical_scale_factor =
image_intrinsic_size.Height()
? snapped_positioning_area_size.Height().ToFloat() /
image_intrinsic_size.Height()
: 1.0f;
// Force the dimension that determines the size to exactly match the
// positioning_area_size in that dimension, so that rounding of floating
// point approximation to LayoutUnit do not shrink the image to smaller
// than the positioning_area_size.
if (type == EFillSizeType::kContain) {
// Snap the dependent dimension to avoid bleeding/blending artifacts
// at the edge of the image when we paint it.
if (horizontal_scale_factor < vertical_scale_factor) {
tile_size_ = LayoutSize(
snapped_positioning_area_size.Width(),
LayoutUnit(std::max(1.0f, roundf(image_intrinsic_size.Height() *
horizontal_scale_factor))));
} else {
tile_size_ = LayoutSize(
LayoutUnit(std::max(1.0f, roundf(image_intrinsic_size.Width() *
vertical_scale_factor))),
snapped_positioning_area_size.Height());
}
return;
}
if (horizontal_scale_factor > vertical_scale_factor) {
tile_size_ =
LayoutSize(snapped_positioning_area_size.Width(),
LayoutUnit(std::max(1.0f, image_intrinsic_size.Height() *
horizontal_scale_factor)));
} else {
tile_size_ =
LayoutSize(LayoutUnit(std::max(1.0f, image_intrinsic_size.Width() *
vertical_scale_factor)),
snapped_positioning_area_size.Height());
}
return;
}
case EFillSizeType::kSizeNone:
// This value should only be used while resolving style.
NOTREACHED();
}
NOTREACHED();
return;
}
void BackgroundImageGeometry::Calculate(const LayoutBoxModelObject* container,
PaintPhase paint_phase,
GlobalPaintFlags flags,
const FillLayer& fill_layer,
const LayoutRect& paint_rect) {
// Unsnapped positioning area is used to derive quantities
// that reference source image maps and define non-integer values, such
// as phase and position.
LayoutRect unsnapped_positioning_area;
// Snapped positioning area is used for sizing images based on the
// background area (like cover and contain), and for setting the repeat
// spacing.
LayoutRect snapped_positioning_area;
// Additional offset from the corner of the positioning_box_
LayoutPoint unsnapped_box_offset;
LayoutPoint snapped_box_offset;
// This method also sets the destination rects.
ComputePositioningArea(container, paint_phase, flags, fill_layer, paint_rect,
unsnapped_positioning_area, snapped_positioning_area,
unsnapped_box_offset, snapped_box_offset);
// Sets the tile_size_.
CalculateFillTileSize(fill_layer, unsnapped_positioning_area.Size(),
snapped_positioning_area.Size());
EFillRepeat background_repeat_x = fill_layer.RepeatX();
EFillRepeat background_repeat_y = fill_layer.RepeatY();
// Maintain both snapped and unsnapped available widths and heights.
// Unsnapped values are used for most thing, but snapped are used
// to computed sizes that must fill the area, such as round and space.
LayoutUnit unsnapped_available_width =
unsnapped_positioning_area.Width() - tile_size_.Width();
LayoutUnit unsnapped_available_height =
unsnapped_positioning_area.Height() - tile_size_.Height();
LayoutUnit snapped_available_width =
snapped_positioning_area.Width() - tile_size_.Width();
LayoutUnit snapped_available_height =
snapped_positioning_area.Height() - tile_size_.Height();
LayoutSize snapped_positioning_area_size = snapped_positioning_area.Size();
// Computed position is for placing things within the destination, so use
// snapped values.
LayoutUnit computed_x_position =
MinimumValueForLength(fill_layer.PositionX(), snapped_available_width) -
offset_in_background_.X();
LayoutUnit computed_y_position =
MinimumValueForLength(fill_layer.PositionY(), snapped_available_height) -
offset_in_background_.Y();
if (background_repeat_x == EFillRepeat::kRoundFill &&
snapped_positioning_area_size.Width() > LayoutUnit() &&
tile_size_.Width() > LayoutUnit()) {
int nr_tiles = std::max(
1,
RoundToInt(snapped_positioning_area_size.Width() / tile_size_.Width()));
LayoutUnit rounded_width = snapped_positioning_area_size.Width() / nr_tiles;
// Maintain aspect ratio if background-size: auto is set
if (fill_layer.SizeLength().Height().IsAuto() &&
background_repeat_y != EFillRepeat::kRoundFill) {
tile_size_.SetHeight(tile_size_.Height() * rounded_width /
tile_size_.Width());
}
tile_size_.SetWidth(rounded_width);
// Force the first tile to line up with the edge of the positioning area.
SetPhaseX(tile_size_.Width()
? tile_size_.Width() -
fmodf(computed_x_position + unsnapped_box_offset.X(),
tile_size_.Width())
: 0);
SetSpaceSize(LayoutSize());
}
if (background_repeat_y == EFillRepeat::kRoundFill &&
snapped_positioning_area_size.Height() > LayoutUnit() &&
tile_size_.Height() > LayoutUnit()) {
int nr_tiles =
std::max(1, RoundToInt(snapped_positioning_area_size.Height() /
tile_size_.Height()));
LayoutUnit rounded_height =
snapped_positioning_area_size.Height() / nr_tiles;
// Maintain aspect ratio if background-size: auto is set
if (fill_layer.SizeLength().Width().IsAuto() &&
background_repeat_x != EFillRepeat::kRoundFill) {
tile_size_.SetWidth(tile_size_.Width() * rounded_height /
tile_size_.Height());
}
tile_size_.SetHeight(rounded_height);
// Force the first tile to line up with the edge of the positioning area.
SetPhaseY(tile_size_.Height()
? tile_size_.Height() -
fmodf(computed_y_position + unsnapped_box_offset.Y(),
tile_size_.Height())
: 0);
SetSpaceSize(LayoutSize());
}
if (background_repeat_x == EFillRepeat::kRepeatFill) {
// Repeat must set the phase accurately, so use unsnapped values.
SetRepeatX(fill_layer, unsnapped_available_width, unsnapped_box_offset.X());
} else if (background_repeat_x == EFillRepeat::kSpaceFill &&
tile_size_.Width() > LayoutUnit()) {
// SpaceFill uses snapped values to fill the painted area.
LayoutUnit space = GetSpaceBetweenImageTiles(
snapped_positioning_area_size.Width(), tile_size_.Width());
if (space >= LayoutUnit())
SetSpaceX(space, snapped_box_offset.X());
else
background_repeat_x = EFillRepeat::kNoRepeatFill;
}
if (background_repeat_x == EFillRepeat::kNoRepeatFill) {
// NoRepeat moves the dest rects, so needs both snapped and
// unsnapped parameters.
LayoutUnit x_offset =
fill_layer.BackgroundXOrigin() == BackgroundEdgeOrigin::kRight
? unsnapped_available_width - computed_x_position
: computed_x_position;
LayoutUnit snapped_x_offset =
fill_layer.BackgroundXOrigin() == BackgroundEdgeOrigin::kRight
? snapped_available_width - computed_x_position
: computed_x_position;
SetNoRepeatX(fill_layer, unsnapped_box_offset.X() + x_offset,
snapped_box_offset.X() + snapped_x_offset);
if (offset_in_background_.X() > tile_size_.Width())
unsnapped_dest_rect_ = snapped_dest_rect_ = LayoutRect();
}
if (background_repeat_y == EFillRepeat::kRepeatFill) {
// Repeat must set the phase accurately, so use unsnapped values.
SetRepeatY(fill_layer, unsnapped_available_height,
unsnapped_box_offset.Y());
} else if (background_repeat_y == EFillRepeat::kSpaceFill &&
tile_size_.Height() > LayoutUnit()) {
// SpaceFill uses snapped values to fill the painted area.
LayoutUnit space = GetSpaceBetweenImageTiles(
snapped_positioning_area_size.Height(), tile_size_.Height());
if (space >= LayoutUnit())
SetSpaceY(space, snapped_box_offset.Y());
else
background_repeat_y = EFillRepeat::kNoRepeatFill;
}
if (background_repeat_y == EFillRepeat::kNoRepeatFill) {
// NoRepeat moves the dest rects, so needs both snapped and
// unsnapped parameters.
LayoutUnit y_offset =
fill_layer.BackgroundYOrigin() == BackgroundEdgeOrigin::kBottom
? unsnapped_available_height - computed_y_position
: computed_y_position;
LayoutUnit snapped_y_offset =
fill_layer.BackgroundYOrigin() == BackgroundEdgeOrigin::kBottom
? snapped_available_height - computed_y_position
: computed_y_position;
SetNoRepeatY(fill_layer, unsnapped_box_offset.Y() + y_offset,
snapped_box_offset.Y() + snapped_y_offset);
if (offset_in_background_.Y() > tile_size_.Height())
unsnapped_dest_rect_ = snapped_dest_rect_ = LayoutRect();
}
if (ShouldUseFixedAttachment(fill_layer))
UseFixedAttachment(paint_rect.Location());
// Clip the final output rect to the paint rect, maintaining snapping.
unsnapped_dest_rect_.Intersect(paint_rect);
snapped_dest_rect_.Intersect(LayoutRect(PixelSnappedIntRect(paint_rect)));
}
const ImageResourceObserver& BackgroundImageGeometry::ImageClient() const {
return painting_view_ ? box_ : positioning_box_;
}
const Document& BackgroundImageGeometry::ImageDocument() const {
return box_.GetDocument();
}
const ComputedStyle& BackgroundImageGeometry::ImageStyle() const {
const bool use_style_from_positioning_box =
painting_view_ || cell_using_container_background_;
return (use_style_from_positioning_box ? positioning_box_ : box_).StyleRef();
}
InterpolationQuality BackgroundImageGeometry::ImageInterpolationQuality()
const {
return box_.StyleRef().GetInterpolationQuality();
}
} // namespace blink