blob: 0adaeba09973626ebacbae77b298e80222db18ac [file] [log] [blame]
/*
* Copyright (C) 2012 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/layout/layout_multi_column_set.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h"
#include "third_party/blink/renderer/core/layout/multi_column_fragmentainer_group.h"
#include "third_party/blink/renderer/core/paint/multi_column_set_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
namespace blink {
LayoutMultiColumnSet::LayoutMultiColumnSet(LayoutFlowThread* flow_thread)
: LayoutBlockFlow(nullptr),
fragmentainer_groups_(*this),
flow_thread_(flow_thread),
initial_height_calculated_(false),
last_actual_column_count_(0) {}
LayoutMultiColumnSet* LayoutMultiColumnSet::CreateAnonymous(
LayoutFlowThread& flow_thread,
const ComputedStyle& parent_style) {
Document& document = flow_thread.GetDocument();
LayoutMultiColumnSet* layout_object = new LayoutMultiColumnSet(&flow_thread);
layout_object->SetDocumentForAnonymous(&document);
layout_object->SetStyle(ComputedStyle::CreateAnonymousStyleWithDisplay(
parent_style, EDisplay::kBlock));
return layout_object;
}
unsigned LayoutMultiColumnSet::FragmentainerGroupIndexAtFlowThreadOffset(
LayoutUnit flow_thread_offset,
PageBoundaryRule rule) const {
DCHECK_GT(fragmentainer_groups_.size(), 0u);
if (flow_thread_offset <= 0)
return 0;
// TODO(mstensho): Introduce an interval tree or similar to speed up this.
for (unsigned index = 0; index < fragmentainer_groups_.size(); index++) {
const auto& row = fragmentainer_groups_[index];
if (rule == kAssociateWithLatterPage) {
if (row.LogicalTopInFlowThread() <= flow_thread_offset &&
row.LogicalBottomInFlowThread() > flow_thread_offset)
return index;
} else if (row.LogicalTopInFlowThread() < flow_thread_offset &&
row.LogicalBottomInFlowThread() >= flow_thread_offset) {
return index;
}
}
return fragmentainer_groups_.size() - 1;
}
const MultiColumnFragmentainerGroup&
LayoutMultiColumnSet::FragmentainerGroupAtVisualPoint(
const LayoutPoint& visual_point) const {
DCHECK_GT(fragmentainer_groups_.size(), 0u);
LayoutUnit block_offset =
IsHorizontalWritingMode() ? visual_point.Y() : visual_point.X();
for (unsigned index = 0; index < fragmentainer_groups_.size(); index++) {
const auto& row = fragmentainer_groups_[index];
if (row.LogicalTop() + row.GroupLogicalHeight() > block_offset)
return row;
}
return fragmentainer_groups_.Last();
}
LayoutUnit LayoutMultiColumnSet::PageLogicalHeightForOffset(
LayoutUnit offset) const {
DCHECK(IsPageLogicalHeightKnown());
const MultiColumnFragmentainerGroup& last_row = LastFragmentainerGroup();
if (offset >= last_row.LogicalTopInFlowThread() +
FragmentainerGroupCapacity(last_row)) {
// The offset is outside the bounds of the fragmentainer groups that we
// have established at this point. If we're nested inside another
// fragmentation context, and are allowed to create additional rows, we
// need to calculate the height on our own.
const LayoutMultiColumnFlowThread* flow_thread = MultiColumnFlowThread();
FragmentationContext* enclosing_fragmentation_context =
flow_thread->EnclosingFragmentationContext();
if (enclosing_fragmentation_context &&
NeedsNewFragmentainerGroupAt(offset, kAssociateWithLatterPage) &&
enclosing_fragmentation_context->IsFragmentainerLogicalHeightKnown()) {
// We'd ideally like to translate |offset| to an offset in the coordinate
// space of the enclosing fragmentation context here, but that's hard,
// since the offset is out of bounds. So just use the bottom we have
// found so far.
LayoutUnit enclosing_context_bottom =
last_row.BlockOffsetInEnclosingFragmentationContext() +
last_row.GroupLogicalHeight();
LayoutUnit extra_row_height =
enclosing_fragmentation_context->FragmentainerLogicalHeightAt(
enclosing_context_bottom);
LayoutUnit current_multicol_height = LogicalTopFromMulticolContentEdge() +
last_row.LogicalTop() +
last_row.GroupLogicalHeight();
// Constrain against specified height / max-height.
LayoutUnit multicol_height_with_extra_row =
std::min(current_multicol_height + extra_row_height,
flow_thread->MaxColumnLogicalHeight());
extra_row_height =
multicol_height_with_extra_row - current_multicol_height;
return extra_row_height.ClampNegativeToZero();
}
}
return FragmentainerGroupAtFlowThreadOffset(offset, kAssociateWithLatterPage)
.ColumnLogicalHeight();
}
LayoutUnit LayoutMultiColumnSet::PageRemainingLogicalHeightForOffset(
LayoutUnit offset_in_flow_thread,
PageBoundaryRule page_boundary_rule) const {
const MultiColumnFragmentainerGroup& row =
FragmentainerGroupAtFlowThreadOffset(offset_in_flow_thread,
page_boundary_rule);
LayoutUnit page_logical_height = row.ColumnLogicalHeight();
LayoutUnit page_logical_bottom =
row.ColumnLogicalTopForOffset(offset_in_flow_thread) +
page_logical_height;
LayoutUnit remaining_logical_height =
page_logical_bottom - offset_in_flow_thread;
if (page_boundary_rule == kAssociateWithFormerPage) {
// An offset exactly at a column boundary will act as being part of the
// former column in question (i.e. no remaining space), rather than being
// part of the latter (i.e. one whole column length of remaining space).
remaining_logical_height =
IntMod(remaining_logical_height, page_logical_height);
} else if (!remaining_logical_height) {
// When pageBoundaryRule is AssociateWithLatterPage, we shouldn't just
// return 0 if there's no space left, because in that case we're at a
// column boundary, in which case we should return the amount of space
// remaining in the *next* column. Note that the page height itself may be
// 0, though.
remaining_logical_height = page_logical_height;
}
return remaining_logical_height;
}
bool LayoutMultiColumnSet::IsPageLogicalHeightKnown() const {
return FirstFragmentainerGroup().IsLogicalHeightKnown();
}
bool LayoutMultiColumnSet::NewFragmentainerGroupsAllowed() const {
if (!IsPageLogicalHeightKnown()) {
// If we have no clue about the height of the multicol container, bail. This
// situation occurs initially when an auto-height multicol container is
// nested inside another auto-height multicol container. We need at least an
// estimated height of the outer multicol container before we can check what
// an inner fragmentainer group has room for.
// Its height is indefinite for now.
return false;
}
if (IsInitialHeightCalculated()) {
// We only insert additional fragmentainer groups in the initial layout
// pass. We only want to balance columns in the last fragmentainer group (if
// we need to balance at all), so we want that last fragmentainer group to
// be the same one in all layout passes that follow.
return false;
}
return true;
}
LayoutUnit LayoutMultiColumnSet::NextLogicalTopForUnbreakableContent(
LayoutUnit flow_thread_offset,
LayoutUnit content_logical_height) const {
DCHECK(flow_thread_offset.MightBeSaturated() ||
PageLogicalTopForOffset(flow_thread_offset) == flow_thread_offset);
if (!MultiColumnFlowThread()->EnclosingFragmentationContext()) {
// If there's no enclosing fragmentation context, there'll ever be only one
// row, and all columns there will have the same height.
return flow_thread_offset;
}
// Assert the problematic situation. If we have no problem with the column
// height, why are we even here?
DCHECK_LT(PageLogicalHeightForOffset(flow_thread_offset),
content_logical_height);
// There's a likelihood for subsequent rows to be taller than the first one.
// TODO(mstensho): if we're doubly nested (e.g. multicol in multicol in
// multicol), we need to look beyond the first row here.
const MultiColumnFragmentainerGroup& first_row = FirstFragmentainerGroup();
LayoutUnit first_row_logical_bottom_in_flow_thread =
first_row.LogicalTopInFlowThread() +
FragmentainerGroupCapacity(first_row);
if (flow_thread_offset >= first_row_logical_bottom_in_flow_thread)
return flow_thread_offset; // We're not in the first row. Give up.
LayoutUnit new_logical_height =
PageLogicalHeightForOffset(first_row_logical_bottom_in_flow_thread);
if (content_logical_height > new_logical_height) {
// The next outer column or page doesn't have enough space either. Give up
// and stay where we are.
return flow_thread_offset;
}
return first_row_logical_bottom_in_flow_thread;
}
LayoutMultiColumnSet* LayoutMultiColumnSet::NextSiblingMultiColumnSet() const {
for (LayoutObject* sibling = NextSibling(); sibling;
sibling = sibling->NextSibling()) {
if (sibling->IsLayoutMultiColumnSet())
return ToLayoutMultiColumnSet(sibling);
}
return nullptr;
}
LayoutMultiColumnSet* LayoutMultiColumnSet::PreviousSiblingMultiColumnSet()
const {
for (LayoutObject* sibling = PreviousSibling(); sibling;
sibling = sibling->PreviousSibling()) {
if (sibling->IsLayoutMultiColumnSet())
return ToLayoutMultiColumnSet(sibling);
}
return nullptr;
}
bool LayoutMultiColumnSet::NeedsNewFragmentainerGroupAt(
LayoutUnit offset_in_flow_thread,
PageBoundaryRule page_boundary_rule) const {
// First the cheap check: Perhaps the last fragmentainer group has sufficient
// capacity?
const MultiColumnFragmentainerGroup& last_row = LastFragmentainerGroup();
LayoutUnit max_logical_bottom_in_flow_thread =
last_row.LogicalTopInFlowThread() + FragmentainerGroupCapacity(last_row);
if (page_boundary_rule == kAssociateWithFormerPage) {
if (offset_in_flow_thread <= max_logical_bottom_in_flow_thread)
return false;
} else if (offset_in_flow_thread < max_logical_bottom_in_flow_thread) {
return false;
}
// So, there's not enough room in the last fragmentainer group. However,
// there can only ever be one fragmentainer group per column set if we're not
// nested inside another fragmentation context. We'll just create overflowing
// columns if the fragmentainer group cannot hold all the content.
if (!MultiColumnFlowThread()->EnclosingFragmentationContext())
return false;
// If we have reached the limits of what a LayoutUnit can hold, we better
// stop, or we'd end up with zero-height columns.
if (offset_in_flow_thread.MightBeSaturated())
return false;
// We're in a nested fragmentation context, and the last fragmentainer group
// cannot hold content at the specified offset without overflowing. This
// usually warrants a new fragmentainer group; however, this will not be the
// case if we have already allocated all available block space in this
// multicol container. When setting up a new fragmentainer group, we always
// constrain against the remaining portion of any specified
// height/max-height. This means that we shouldn't allow creation of
// fragmentainer groups below the bottom of the multicol container, or we'd
// end up with zero-height fragmentainer groups (or actually 1px; see
// heightAdjustedForRowOffset() in MultiColumnFragmentainerGroup, which
// guards against zero-height groups), i.e. potentially a lot of pretty
// useless fragmentainer groups, and possibly broken layout too. Instead,
// we'll just allow additional (overflowing) columns to be created in the
// last fragmentainer group, similar to what we do when we're not nested.
LayoutUnit logical_bottom =
last_row.LogicalTop() + last_row.GroupLogicalHeight();
LayoutUnit space_used = logical_bottom + LogicalTopFromMulticolContentEdge();
LayoutUnit max_column_height =
MultiColumnFlowThread()->MaxColumnLogicalHeight();
return max_column_height - space_used > LayoutUnit();
}
MultiColumnFragmentainerGroup&
LayoutMultiColumnSet::AppendNewFragmentainerGroup() {
MultiColumnFragmentainerGroup new_group(*this);
{ // Extra scope here for previousGroup; it's potentially invalid once we
// modify the m_fragmentainerGroups Vector.
MultiColumnFragmentainerGroup& previous_group =
fragmentainer_groups_.Last();
// This is the flow thread block offset where |previousGroup| ends and
// |newGroup| takes over.
LayoutUnit block_offset_in_flow_thread =
previous_group.LogicalTopInFlowThread() +
FragmentainerGroupCapacity(previous_group);
previous_group.SetLogicalBottomInFlowThread(block_offset_in_flow_thread);
new_group.SetLogicalTopInFlowThread(block_offset_in_flow_thread);
new_group.SetLogicalTop(previous_group.LogicalTop() +
previous_group.GroupLogicalHeight());
new_group.ResetColumnHeight();
}
fragmentainer_groups_.Append(new_group);
return fragmentainer_groups_.Last();
}
LayoutUnit LayoutMultiColumnSet::LogicalTopFromMulticolContentEdge() const {
// We subtract the position of the first column set or spanner placeholder,
// rather than the "before" border+padding of the multicol container. This
// distinction doesn't matter after layout, but during layout it does:
// The flow thread (i.e. the multicol contents) is laid out before the column
// sets and spanner placeholders, which means that compesating for a top
// border+padding that hasn't yet been baked into the offset will produce the
// wrong results in the first layout pass, and we'd end up performing a wasted
// layout pass in many cases.
const LayoutBox& first_column_box =
*MultiColumnFlowThread()->FirstMultiColumnBox();
// The top margin edge of the first column set or spanner placeholder is flush
// with the top content edge of the multicol container. The margin here never
// collapses with other margins, so we can just subtract it. Column sets never
// have margins, but spanner placeholders may.
LayoutUnit first_column_box_margin_edge =
first_column_box.LogicalTop() -
MultiColumnBlockFlow()->MarginBeforeForChild(first_column_box);
return LogicalTop() - first_column_box_margin_edge;
}
LayoutUnit LayoutMultiColumnSet::LogicalTopInFlowThread() const {
return FirstFragmentainerGroup().LogicalTopInFlowThread();
}
LayoutUnit LayoutMultiColumnSet::LogicalBottomInFlowThread() const {
return LastFragmentainerGroup().LogicalBottomInFlowThread();
}
bool LayoutMultiColumnSet::HeightIsAuto() const {
LayoutMultiColumnFlowThread* flow_thread = MultiColumnFlowThread();
if (!flow_thread->IsLayoutPagedFlowThread()) {
// If support for the column-fill property isn't enabled, we want to behave
// as if column-fill were auto, so that multicol containers with specified
// height don't get their columns balanced (auto-height multicol containers
// will still get their columns balanced, even if column-fill isn't
// 'balance' - in accordance with the spec).
// Pretending that column-fill is auto also matches the old multicol
// implementation, which has no support for this property.
if (MultiColumnBlockFlow()->StyleRef().GetColumnFill() ==
EColumnFill::kBalance)
return true;
if (LayoutBox* next = NextSiblingBox()) {
if (next->IsLayoutMultiColumnSpannerPlaceholder()) {
// If we're followed by a spanner, we need to balance.
return true;
}
}
}
return !flow_thread->ColumnHeightAvailable();
}
LayoutSize LayoutMultiColumnSet::FlowThreadTranslationAtOffset(
LayoutUnit block_offset,
PageBoundaryRule rule,
CoordinateSpaceConversion mode) const {
return FragmentainerGroupAtFlowThreadOffset(block_offset, rule)
.FlowThreadTranslationAtOffset(block_offset, rule, mode);
}
LayoutPoint LayoutMultiColumnSet::VisualPointToFlowThreadPoint(
const LayoutPoint& visual_point) const {
const MultiColumnFragmentainerGroup& row =
FragmentainerGroupAtVisualPoint(visual_point);
return row.VisualPointToFlowThreadPoint(visual_point -
row.OffsetFromColumnSet());
}
LayoutUnit LayoutMultiColumnSet::PageLogicalTopForOffset(
LayoutUnit offset) const {
return FragmentainerGroupAtFlowThreadOffset(offset, kAssociateWithLatterPage)
.ColumnLogicalTopForOffset(offset);
}
bool LayoutMultiColumnSet::RecalculateColumnHeight() {
if (old_logical_top_ != LogicalTop() &&
MultiColumnFlowThread()->EnclosingFragmentationContext()) {
// Preceding spanners or column sets have been moved or resized. This means
// that the fragmentainer groups that we have inserted need to be
// re-inserted. Restart column balancing.
ResetColumnHeight();
return true;
}
bool changed = false;
for (auto& group : fragmentainer_groups_)
changed = group.RecalculateColumnHeight(*this) || changed;
initial_height_calculated_ = true;
return changed;
}
void LayoutMultiColumnSet::ResetColumnHeight() {
fragmentainer_groups_.DeleteExtraGroups();
fragmentainer_groups_.First().ResetColumnHeight();
tallest_unbreakable_logical_height_ = LayoutUnit();
initial_height_calculated_ = false;
}
void LayoutMultiColumnSet::BeginFlow(LayoutUnit offset_in_flow_thread) {
// At this point layout is exactly at the beginning of this set. Store block
// offset from flow thread start.
fragmentainer_groups_.First().SetLogicalTopInFlowThread(
offset_in_flow_thread);
}
void LayoutMultiColumnSet::EndFlow(LayoutUnit offset_in_flow_thread) {
// At this point layout is exactly at the end of this set. Store block offset
// from flow thread start. This set is now considered "flowed", although we
// may have to revisit it later (with beginFlow()), e.g. if a subtree in the
// flow thread has to be laid out over again because the initial margin
// collapsing estimates were wrong.
fragmentainer_groups_.Last().SetLogicalBottomInFlowThread(
offset_in_flow_thread);
}
void LayoutMultiColumnSet::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
LayoutBlockFlow::StyleDidChange(diff, old_style);
// column-rule is specified on the parent (the multicol container) of this
// object, but it's the column sets that are in charge of painting them.
// A column rule is pretty much like any other box decoration, like borders.
// We need to say that we have box decorations here, so that the columnn set
// is invalidated when it gets laid out. We cannot check here whether the
// multicol container actually has a visible column rule or not, because we
// may not have been inserted into the tree yet. Painting a column set is
// cheap anyway, because the only thing it can paint is the column rule, while
// actual multicol content is handled by the flow thread.
SetHasBoxDecorationBackground(true);
}
void LayoutMultiColumnSet::UpdateLayout() {
if (RecalculateColumnHeight())
MultiColumnFlowThread()->SetColumnHeightsChanged();
LayoutBlockFlow::UpdateLayout();
auto actual_column_count = ActualColumnCount();
if (actual_column_count != last_actual_column_count_) {
// At least we need to paint column rules differently when actual column
// count changes.
SetShouldDoFullPaintInvalidation();
last_actual_column_count_ = actual_column_count;
}
}
void LayoutMultiColumnSet::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) const {
min_logical_width = flow_thread_->MinPreferredLogicalWidth();
max_logical_width = flow_thread_->MaxPreferredLogicalWidth();
}
void LayoutMultiColumnSet::ComputeLogicalHeight(
LayoutUnit,
LayoutUnit logical_top,
LogicalExtentComputedValues& computed_values) const {
LayoutUnit logical_height;
// Under some circumstances column heights are unknown at this point. This
// happens e.g. when this column set got pushed down by a preceding spanner
// (and when that affects the available space for this column set). Just set
// the height to 0 for now. Another layout pass has already been scheduled, to
// calculate the correct height.
if (IsPageLogicalHeightKnown()) {
for (const auto& group : fragmentainer_groups_)
logical_height += group.GroupLogicalHeight();
}
computed_values.extent_ = logical_height;
computed_values.position_ = logical_top;
}
PositionWithAffinity LayoutMultiColumnSet::PositionForPoint(
const LayoutPoint& point) const {
// Convert the visual point to a flow thread point.
const MultiColumnFragmentainerGroup& row =
FragmentainerGroupAtVisualPoint(point);
LayoutPoint flow_thread_point = row.VisualPointToFlowThreadPoint(
point + row.OffsetFromColumnSet(),
MultiColumnFragmentainerGroup::kSnapToColumn);
// Then drill into the flow thread, where we'll find the actual content.
return FlowThread()->PositionForPoint(flow_thread_point);
}
LayoutUnit LayoutMultiColumnSet::ColumnGap() const {
LayoutBlockFlow* parent_block = MultiColumnBlockFlow();
if (parent_block->StyleRef().ColumnGap().IsNormal()) {
// "1em" is recommended as the normal gap setting. Matches <p> margins.
return LayoutUnit(
parent_block->StyleRef().GetFontDescription().ComputedPixelSize());
}
return ValueForLength(parent_block->StyleRef().ColumnGap().GetLength(),
AvailableLogicalWidth());
}
unsigned LayoutMultiColumnSet::ActualColumnCount() const {
// FIXME: remove this method. It's a meaningless question to ask the set "how
// many columns do you actually have?", since that may vary for each row.
return FirstFragmentainerGroup().ActualColumnCount();
}
void LayoutMultiColumnSet::PaintObject(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
MultiColumnSetPainter(*this).PaintObject(paint_info, paint_offset);
}
LayoutRect LayoutMultiColumnSet::FragmentsBoundingBox(
const LayoutRect& bounding_box_in_flow_thread) const {
LayoutRect result;
for (const auto& group : fragmentainer_groups_)
result.Unite(group.FragmentsBoundingBox(bounding_box_in_flow_thread));
return result;
}
void LayoutMultiColumnSet::ComputeVisualOverflow(
const LayoutRect& previous_visual_overflow_rect,
bool recompute_floats) {
AddVisualOverflowFromChildren();
AddVisualEffectOverflow();
AddVisualOverflowFromTheme();
if (recompute_floats || CreatesNewFormattingContext() ||
HasSelfPaintingLayer())
AddVisualOverflowFromFloats();
if (VisualOverflowRect() != previous_visual_overflow_rect) {
if (Layer())
Layer()->SetNeedsCompositingInputsUpdate();
GetFrameView()->SetIntersectionObservationState(LocalFrameView::kDesired);
}
}
void LayoutMultiColumnSet::AddVisualOverflowFromChildren() {
// It's useless to calculate overflow if we haven't determined the page
// logical height yet.
if (!IsPageLogicalHeightKnown())
return;
LayoutRect overflow_rect;
for (const auto& group : fragmentainer_groups_) {
LayoutRect rect = group.CalculateOverflow();
rect.Move(group.OffsetFromColumnSet());
overflow_rect.Unite(rect);
}
AddContentsVisualOverflow(overflow_rect);
}
void LayoutMultiColumnSet::AddLayoutOverflowFromChildren() {
// It's useless to calculate overflow if we haven't determined the page
// logical height yet.
if (!IsPageLogicalHeightKnown())
return;
LayoutRect overflow_rect;
for (const auto& group : fragmentainer_groups_) {
LayoutRect rect = group.CalculateOverflow();
rect.Move(group.OffsetFromColumnSet());
overflow_rect.Unite(rect);
}
AddLayoutOverflow(overflow_rect);
}
void LayoutMultiColumnSet::InsertedIntoTree() {
LayoutBlockFlow::InsertedIntoTree();
AttachToFlowThread();
}
void LayoutMultiColumnSet::WillBeRemovedFromTree() {
LayoutBlockFlow::WillBeRemovedFromTree();
DetachFromFlowThread();
}
void LayoutMultiColumnSet::AttachToFlowThread() {
if (DocumentBeingDestroyed())
return;
if (!flow_thread_)
return;
flow_thread_->AddColumnSetToThread(this);
}
void LayoutMultiColumnSet::DetachFromFlowThread() {
if (flow_thread_) {
flow_thread_->RemoveColumnSetFromThread(this);
flow_thread_ = nullptr;
}
}
LayoutRect LayoutMultiColumnSet::FlowThreadPortionRect() const {
LayoutRect portion_rect(LayoutUnit(), LogicalTopInFlowThread(),
PageLogicalWidth(), LogicalHeightInFlowThread());
if (!IsHorizontalWritingMode())
return portion_rect.TransposedRect();
return portion_rect;
}
bool LayoutMultiColumnSet::ComputeColumnRuleBounds(
const LayoutPoint& paint_offset,
Vector<LayoutRect>& column_rule_bounds) const {
if (FlowThread()->IsLayoutPagedFlowThread())
return false;
// Reference: https://www.w3.org/TR/css3-multicol/#column-gaps-and-rules
const ComputedStyle& block_style = MultiColumnBlockFlow()->StyleRef();
bool rule_transparent = block_style.ColumnRuleIsTransparent();
EBorderStyle rule_style = block_style.ColumnRuleStyle();
LayoutUnit rule_thickness(block_style.ColumnRuleWidth());
LayoutUnit col_gap = ColumnGap();
bool render_rule =
ComputedStyle::BorderStyleIsVisible(rule_style) && !rule_transparent;
if (!render_rule)
return false;
unsigned col_count = ActualColumnCount();
if (col_count <= 1)
return false;
bool left_to_right = StyleRef().IsLeftToRightDirection();
LayoutUnit curr_logical_left_offset =
left_to_right ? LayoutUnit() : ContentLogicalWidth();
LayoutUnit rule_add = BorderAndPaddingLogicalLeft();
LayoutUnit rule_logical_left =
left_to_right ? LayoutUnit() : ContentLogicalWidth();
LayoutUnit inline_direction_size = PageLogicalWidth();
for (unsigned i = 0; i < col_count; i++) {
// Move to the next position.
if (left_to_right) {
rule_logical_left += inline_direction_size + col_gap / 2;
curr_logical_left_offset += inline_direction_size + col_gap;
} else {
rule_logical_left -= (inline_direction_size + col_gap / 2);
curr_logical_left_offset -= (inline_direction_size + col_gap);
}
// Now compute the final bounds.
if (i < col_count - 1) {
LayoutUnit rule_left, rule_right, rule_top, rule_bottom;
if (IsHorizontalWritingMode()) {
rule_left = paint_offset.X() + rule_logical_left - rule_thickness / 2 +
rule_add;
rule_right = rule_left + rule_thickness;
rule_top = paint_offset.Y() + BorderTop() + PaddingTop();
rule_bottom = rule_top + ContentHeight();
} else {
rule_left = paint_offset.X() + BorderLeft() + PaddingLeft();
rule_right = rule_left + ContentWidth();
rule_top = paint_offset.Y() + rule_logical_left - rule_thickness / 2 +
rule_add;
rule_bottom = rule_top + rule_thickness;
}
column_rule_bounds.push_back(LayoutRect(
rule_left, rule_top, rule_right - rule_left, rule_bottom - rule_top));
}
rule_logical_left = curr_logical_left_offset;
}
return true;
}
LayoutRect LayoutMultiColumnSet::LocalVisualRectIgnoringVisibility() const {
LayoutRect block_flow_bounds =
LayoutBlockFlow::LocalVisualRectIgnoringVisibility();
// Now add in column rule bounds, if present.
Vector<LayoutRect> column_rule_bounds;
if (ComputeColumnRuleBounds(LayoutPoint(), column_rule_bounds)) {
for (auto& bound : column_rule_bounds)
block_flow_bounds.Unite(bound);
}
return block_flow_bounds;
}
void LayoutMultiColumnSet::UpdateFromNG() {
DCHECK_EQ(fragmentainer_groups_.size(), 1U);
auto& group = fragmentainer_groups_[0];
group.UpdateFromNG(LogicalHeight());
ComputeOverflow(LogicalHeight());
}
} // namespace blink