blob: df573ab407d4beb4a5b8938ed3b58e9a214344e3 [file] [log] [blame]
/*
* Copyright (C) 1997 Martin Jones (mjones@kde.org)
* (C) 1997 Torben Weis (weis@kde.org)
* (C) 1998 Waldo Bastian (bastian@kde.org)
* (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2013 Apple Inc.
* All rights reserved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/layout/LayoutTableSection.h"
#include <algorithm>
#include <limits>
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutTableCol.h"
#include "core/layout/LayoutTableRow.h"
#include "core/layout/LayoutView.h"
#include "core/layout/SubtreeLayoutScope.h"
#include "core/paint/TableSectionPainter.h"
#include "platform/wtf/HashSet.h"
namespace blink {
using namespace HTMLNames;
// This variable is used to balance the memory consumption vs the paint
// invalidation time on big tables.
static unsigned g_min_table_size_to_use_fast_paint_path_with_overflowing_cell =
75 * 75;
static inline void SetRowLogicalHeightToRowStyleLogicalHeight(
LayoutTableSection::RowStruct& row) {
DCHECK(row.row_layout_object);
row.logical_height = row.row_layout_object->Style()->LogicalHeight();
}
static inline void UpdateLogicalHeightForCell(
LayoutTableSection::RowStruct& row,
const LayoutTableCell* cell) {
// We ignore height settings on rowspan cells.
if (cell->RowSpan() != 1)
return;
Length logical_height = cell->Style()->LogicalHeight();
if (logical_height.IsPositive()) {
Length c_row_logical_height = row.logical_height;
switch (logical_height.GetType()) {
case kPercent:
// TODO(alancutter): Make this work correctly for calc lengths.
if (!(c_row_logical_height.IsPercentOrCalc()) ||
(c_row_logical_height.IsPercent() &&
c_row_logical_height.Percent() < logical_height.Percent()))
row.logical_height = logical_height;
break;
case kFixed:
if (c_row_logical_height.GetType() < kPercent ||
(c_row_logical_height.IsFixed() &&
c_row_logical_height.Value() < logical_height.Value()))
row.logical_height = logical_height;
break;
default:
break;
}
}
}
void CellSpan::EnsureConsistency(const unsigned maximum_span_size) {
static_assert(std::is_same<decltype(start_), unsigned>::value,
"Asserts below assume m_start is unsigned");
static_assert(std::is_same<decltype(end_), unsigned>::value,
"Asserts below assume m_end is unsigned");
CHECK_LE(start_, maximum_span_size);
CHECK_LE(end_, maximum_span_size);
CHECK_LE(start_, end_);
}
LayoutTableSection::CellStruct::CellStruct() : in_col_span(false) {}
LayoutTableSection::CellStruct::~CellStruct() {}
LayoutTableSection::LayoutTableSection(Element* element)
: LayoutTableBoxComponent(element),
c_col_(0),
c_row_(0),
outer_border_start_(0),
outer_border_end_(0),
outer_border_before_(0),
outer_border_after_(0),
needs_cell_recalc_(false),
force_slow_paint_path_with_overflowing_cell_(false),
has_multiple_cell_levels_(false),
has_spanning_cells_(false) {
// init LayoutObject attributes
SetInline(false); // our object is not Inline
}
LayoutTableSection::~LayoutTableSection() {}
void LayoutTableSection::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
DCHECK(Style()->Display() == EDisplay::kTableFooterGroup ||
Style()->Display() == EDisplay::kTableRowGroup ||
Style()->Display() == EDisplay::kTableHeaderGroup);
LayoutTableBoxComponent::StyleDidChange(diff, old_style);
PropagateStyleToAnonymousChildren();
if (!old_style)
return;
LayoutTable* table = this->Table();
if (!table)
return;
LayoutTableBoxComponent::InvalidateCollapsedBordersOnStyleChange(
*this, *table, diff, *old_style);
if (LayoutTableBoxComponent::DoCellsHaveDirtyWidth(*this, *table, diff,
*old_style))
MarkAllCellsWidthsDirtyAndOrNeedsLayout(
LayoutTable::kMarkDirtyAndNeedsLayout);
}
void LayoutTableSection::WillBeRemovedFromTree() {
LayoutTableBoxComponent::WillBeRemovedFromTree();
// Preventively invalidate our cells as we may be re-inserted into
// a new table which would require us to rebuild our structure.
SetNeedsCellRecalc();
}
void LayoutTableSection::AddChild(LayoutObject* child,
LayoutObject* before_child) {
if (!child->IsTableRow()) {
LayoutObject* last = before_child;
if (!last)
last = LastRow();
if (last && last->IsAnonymous() && !last->IsBeforeOrAfterContent()) {
if (before_child == last)
before_child = last->SlowFirstChild();
last->AddChild(child, before_child);
return;
}
if (before_child && !before_child->IsAnonymous() &&
before_child->Parent() == this) {
LayoutObject* row = before_child->PreviousSibling();
if (row && row->IsTableRow() && row->IsAnonymous()) {
row->AddChild(child);
return;
}
}
// If beforeChild is inside an anonymous cell/row, insert into the cell or
// into the anonymous row containing it, if there is one.
LayoutObject* last_box = last;
while (last_box && last_box->Parent()->IsAnonymous() &&
!last_box->IsTableRow())
last_box = last_box->Parent();
if (last_box && last_box->IsAnonymous() &&
!last_box->IsBeforeOrAfterContent()) {
last_box->AddChild(child, before_child);
return;
}
LayoutObject* row = LayoutTableRow::CreateAnonymousWithParent(this);
AddChild(row, before_child);
row->AddChild(child);
return;
}
if (before_child)
SetNeedsCellRecalc();
unsigned insertion_row = c_row_;
++c_row_;
c_col_ = 0;
EnsureRows(c_row_);
LayoutTableRow* row = ToLayoutTableRow(child);
grid_[insertion_row].row_layout_object = row;
row->SetRowIndex(insertion_row);
if (!before_child)
SetRowLogicalHeightToRowStyleLogicalHeight(grid_[insertion_row]);
if (before_child && before_child->Parent() != this)
before_child = SplitAnonymousBoxesAroundChild(before_child);
DCHECK(!before_child || before_child->IsTableRow());
LayoutTableBoxComponent::AddChild(child, before_child);
}
static inline void CheckThatVectorIsDOMOrdered(
const Vector<LayoutTableCell*, 1>& cells) {
#ifndef NDEBUG
// This function should be called on a non-empty vector.
DCHECK_GT(cells.size(), 0u);
const LayoutTableCell* previous_cell = cells[0];
for (size_t i = 1; i < cells.size(); ++i) {
const LayoutTableCell* current_cell = cells[i];
// The check assumes that all cells belong to the same row group.
DCHECK_EQ(previous_cell->Section(), current_cell->Section());
// 2 overlapping cells can't be on the same row.
DCHECK_NE(current_cell->Row(), previous_cell->Row());
// Look backwards in the tree for the previousCell's row. If we are
// DOM ordered, we should find it.
const LayoutTableRow* row = current_cell->Row();
for (; row && row != previous_cell->Row(); row = row->PreviousRow()) {
}
DCHECK_EQ(row, previous_cell->Row());
previous_cell = current_cell;
}
#endif // NDEBUG
}
void LayoutTableSection::AddCell(LayoutTableCell* cell, LayoutTableRow* row) {
// We don't insert the cell if we need cell recalc as our internal columns'
// representation will have drifted from the table's representation. Also
// recalcCells will call addCell at a later time after sync'ing our columns'
// with the table's.
if (NeedsCellRecalc())
return;
unsigned r_span = cell->RowSpan();
unsigned c_span = cell->ColSpan();
if (r_span > 1 || c_span > 1)
has_spanning_cells_ = true;
const Vector<LayoutTable::ColumnStruct>& columns =
Table()->EffectiveColumns();
unsigned insertion_row = row->RowIndex();
// ### mozilla still seems to do the old HTML way, even for strict DTD
// (see the annotation on table cell layouting in the CSS specs and the
// testcase below:
// <TABLE border>
// <TR><TD>1 <TD rowspan="2">2 <TD>3 <TD>4
// <TR><TD colspan="2">5
// </TABLE>
unsigned n_cols = NumCols(insertion_row);
while (c_col_ < n_cols && (CellAt(insertion_row, c_col_).HasCells() ||
CellAt(insertion_row, c_col_).in_col_span))
c_col_++;
UpdateLogicalHeightForCell(grid_[insertion_row], cell);
EnsureRows(insertion_row + r_span);
grid_[insertion_row].row_layout_object = row;
unsigned col = c_col_;
// tell the cell where it is
bool in_col_span = false;
unsigned col_size = columns.size();
while (c_span) {
unsigned current_span;
if (c_col_ >= col_size) {
Table()->AppendEffectiveColumn(c_span);
current_span = c_span;
} else {
if (c_span < columns[c_col_].span)
Table()->SplitEffectiveColumn(c_col_, c_span);
current_span = columns[c_col_].span;
}
for (unsigned r = 0; r < r_span; r++) {
EnsureCols(insertion_row + r, c_col_ + 1);
CellStruct& c = CellAt(insertion_row + r, c_col_);
DCHECK(cell);
c.cells.push_back(cell);
CheckThatVectorIsDOMOrdered(c.cells);
// If cells overlap then we take the slow path for painting.
if (c.cells.size() > 1)
has_multiple_cell_levels_ = true;
if (in_col_span)
c.in_col_span = true;
}
c_col_++;
c_span -= current_span;
in_col_span = true;
}
cell->SetAbsoluteColumnIndex(Table()->EffectiveColumnToAbsoluteColumn(col));
}
bool LayoutTableSection::RowHasOnlySpanningCells(unsigned row) {
unsigned total_cols = grid_[row].row.size();
if (!total_cols)
return false;
for (unsigned col = 0; col < total_cols; col++) {
const CellStruct& row_span_cell = CellAt(row, col);
// Empty cell is not a valid cell so it is not a rowspan cell.
if (row_span_cell.cells.IsEmpty())
return false;
if (row_span_cell.cells[0]->RowSpan() == 1)
return false;
}
return true;
}
void LayoutTableSection::PopulateSpanningRowsHeightFromCell(
LayoutTableCell* cell,
struct SpanningRowsHeight& spanning_rows_height) {
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
spanning_rows_height.spanning_cell_height_ignoring_border_spacing =
cell->LogicalHeightForRowSizing();
spanning_rows_height.row_height.resize(row_span);
spanning_rows_height.total_rows_height = 0;
for (unsigned row = 0; row < row_span; row++) {
unsigned actual_row = row + row_index;
spanning_rows_height.row_height[row] = row_pos_[actual_row + 1] -
row_pos_[actual_row] -
BorderSpacingForRow(actual_row);
if (!spanning_rows_height.row_height[row])
spanning_rows_height.is_any_row_with_only_spanning_cells |=
RowHasOnlySpanningCells(actual_row);
spanning_rows_height.total_rows_height +=
spanning_rows_height.row_height[row];
spanning_rows_height.spanning_cell_height_ignoring_border_spacing -=
BorderSpacingForRow(actual_row);
}
// We don't span the following row so its border-spacing (if any) should be
// included.
spanning_rows_height.spanning_cell_height_ignoring_border_spacing +=
BorderSpacingForRow(row_index + row_span - 1);
}
void LayoutTableSection::DistributeExtraRowSpanHeightToPercentRows(
LayoutTableCell* cell,
float total_percent,
int& extra_row_spanning_height,
Vector<int>& rows_height) {
if (!extra_row_spanning_height || !total_percent)
return;
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
float percent = std::min(total_percent, 100.0f);
const int table_height = row_pos_[grid_.size()] + extra_row_spanning_height;
// Our algorithm matches Firefox. Extra spanning height would be distributed
// Only in first percent height rows those total percent is 100. Other percent
// rows would be uneffected even extra spanning height is remain.
int accumulated_position_increase = 0;
for (unsigned row = row_index; row < (row_index + row_span); row++) {
if (percent > 0 && extra_row_spanning_height > 0) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (grid_[row].logical_height.IsPercent()) {
int to_add =
(table_height *
std::min(grid_[row].logical_height.Percent(), percent) / 100) -
rows_height[row - row_index];
to_add = std::max(std::min(to_add, extra_row_spanning_height), 0);
accumulated_position_increase += to_add;
extra_row_spanning_height -= to_add;
percent -= grid_[row].logical_height.Percent();
}
}
row_pos_[row + 1] += accumulated_position_increase;
}
}
static void UpdatePositionIncreasedWithRowHeight(
int extra_height,
float row_height,
float total_height,
int& accumulated_position_increase,
double& remainder) {
// Without the cast we lose enough precision to cause heights to miss pixels
// (and trigger asserts) in some layout tests.
double proportional_position_increase =
remainder + (extra_height * double(row_height)) / total_height;
// The epsilon is to push any values that are close to a whole number but
// aren't due to floating point imprecision. The epsilons are not accumulated,
// any that aren't necessary are lost in the cast to int.
int position_increase_int = proportional_position_increase + 0.000001;
accumulated_position_increase += position_increase_int;
remainder = proportional_position_increase - position_increase_int;
}
// This is mainly used to distribute whole extra rowspanning height in percent
// rows when all spanning rows are percent rows.
// Distributing whole extra rowspanning height in percent rows based on the
// ratios of percent because this method works same as percent distribution when
// only percent rows are present and percent is 100. Also works perfectly fine
// when percent is not equal to 100.
void LayoutTableSection::DistributeWholeExtraRowSpanHeightToPercentRows(
LayoutTableCell* cell,
float total_percent,
int& extra_row_spanning_height,
Vector<int>& rows_height) {
if (!extra_row_spanning_height || !total_percent)
return;
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
double remainder = 0;
int accumulated_position_increase = 0;
for (unsigned row = row_index; row < (row_index + row_span); row++) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (grid_[row].logical_height.IsPercent()) {
UpdatePositionIncreasedWithRowHeight(
extra_row_spanning_height, grid_[row].logical_height.Percent(),
total_percent, accumulated_position_increase, remainder);
}
row_pos_[row + 1] += accumulated_position_increase;
}
DCHECK(!round(remainder)) << "remainder was " << remainder;
extra_row_spanning_height -= accumulated_position_increase;
}
void LayoutTableSection::DistributeExtraRowSpanHeightToAutoRows(
LayoutTableCell* cell,
int total_auto_rows_height,
int& extra_row_spanning_height,
Vector<int>& rows_height) {
if (!extra_row_spanning_height || !total_auto_rows_height)
return;
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
int accumulated_position_increase = 0;
double remainder = 0;
// Aspect ratios of auto rows should not change otherwise table may look
// different than user expected. So extra height distributed in auto spanning
// rows based on their weight in spanning cell.
for (unsigned row = row_index; row < (row_index + row_span); row++) {
if (grid_[row].logical_height.IsAuto()) {
UpdatePositionIncreasedWithRowHeight(
extra_row_spanning_height, rows_height[row - row_index],
total_auto_rows_height, accumulated_position_increase, remainder);
}
row_pos_[row + 1] += accumulated_position_increase;
}
DCHECK(!round(remainder)) << "remainder was " << remainder;
extra_row_spanning_height -= accumulated_position_increase;
}
void LayoutTableSection::DistributeExtraRowSpanHeightToRemainingRows(
LayoutTableCell* cell,
int total_remaining_rows_height,
int& extra_row_spanning_height,
Vector<int>& rows_height) {
if (!extra_row_spanning_height || !total_remaining_rows_height)
return;
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
int accumulated_position_increase = 0;
double remainder = 0;
// Aspect ratios of the rows should not change otherwise table may look
// different than user expected. So extra height distribution in remaining
// spanning rows based on their weight in spanning cell.
for (unsigned row = row_index; row < (row_index + row_span); row++) {
if (!grid_[row].logical_height.IsPercentOrCalc()) {
UpdatePositionIncreasedWithRowHeight(
extra_row_spanning_height, rows_height[row - row_index],
total_remaining_rows_height, accumulated_position_increase,
remainder);
}
row_pos_[row + 1] += accumulated_position_increase;
}
DCHECK(!round(remainder)) << "remainder was " << remainder;
extra_row_spanning_height -= accumulated_position_increase;
}
static bool CellIsFullyIncludedInOtherCell(const LayoutTableCell* cell1,
const LayoutTableCell* cell2) {
return (cell1->RowIndex() >= cell2->RowIndex() &&
(cell1->RowIndex() + cell1->RowSpan()) <=
(cell2->RowIndex() + cell2->RowSpan()));
}
// To avoid unneeded extra height distributions, we apply the following sorting
// algorithm:
static bool CompareRowSpanCellsInHeightDistributionOrder(
const LayoutTableCell* cell1,
const LayoutTableCell* cell2) {
// Sorting bigger height cell first if cells are at same index with same span
// because we will skip smaller height cell to distribute it's extra height.
if (cell1->RowIndex() == cell2->RowIndex() &&
cell1->RowSpan() == cell2->RowSpan())
return (cell1->LogicalHeightForRowSizing() >
cell2->LogicalHeightForRowSizing());
// Sorting inner most cell first because if inner spanning cell'e extra height
// is distributed then outer spanning cell's extra height will adjust
// accordingly. In reverse order, there is more chances that outer spanning
// cell's height will exceed than defined by user.
if (CellIsFullyIncludedInOtherCell(cell1, cell2))
return true;
// Sorting lower row index first because first we need to apply the extra
// height of spanning cell which comes first in the table so lower rows's
// position would increment in sequence.
if (!CellIsFullyIncludedInOtherCell(cell2, cell1))
return (cell1->RowIndex() < cell2->RowIndex());
return false;
}
unsigned LayoutTableSection::CalcRowHeightHavingOnlySpanningCells(
unsigned row,
int& accumulated_cell_position_increase,
unsigned row_to_apply_extra_height,
unsigned& extra_table_height_to_propgate,
Vector<int>& rows_count_with_only_spanning_cells) {
DCHECK(RowHasOnlySpanningCells(row));
unsigned total_cols = grid_[row].row.size();
if (!total_cols)
return 0;
unsigned row_height = 0;
for (unsigned col = 0; col < total_cols; col++) {
const CellStruct& row_span_cell = CellAt(row, col);
if (!row_span_cell.cells.size())
continue;
LayoutTableCell* cell = row_span_cell.cells[0];
if (cell->RowSpan() < 2)
continue;
const unsigned cell_row_index = cell->RowIndex();
const unsigned cell_row_span = cell->RowSpan();
// As we are going from the top of the table to the bottom to calculate the
// row heights for rows that only contain spanning cells and all previous
// rows are processed we only need to find the number of rows with spanning
// cells from the current cell to the end of the current cells spanning
// height.
unsigned start_row_for_spanning_cell_count = std::max(cell_row_index, row);
unsigned end_row = cell_row_index + cell_row_span;
unsigned spanning_cells_rows_count_having_zero_height =
rows_count_with_only_spanning_cells[end_row - 1];
if (start_row_for_spanning_cell_count)
spanning_cells_rows_count_having_zero_height -=
rows_count_with_only_spanning_cells
[start_row_for_spanning_cell_count - 1];
int total_rowspan_cell_height =
(row_pos_[end_row] - row_pos_[cell_row_index]) -
BorderSpacingForRow(end_row - 1);
total_rowspan_cell_height += accumulated_cell_position_increase;
if (row_to_apply_extra_height >= cell_row_index &&
row_to_apply_extra_height < end_row)
total_rowspan_cell_height += extra_table_height_to_propgate;
if (total_rowspan_cell_height < cell->LogicalHeightForRowSizing()) {
unsigned extra_height_required =
cell->LogicalHeightForRowSizing() - total_rowspan_cell_height;
row_height = std::max(
row_height,
extra_height_required / spanning_cells_rows_count_having_zero_height);
}
}
return row_height;
}
void LayoutTableSection::UpdateRowsHeightHavingOnlySpanningCells(
LayoutTableCell* cell,
struct SpanningRowsHeight& spanning_rows_height,
unsigned& extra_height_to_propagate,
Vector<int>& rows_count_with_only_spanning_cells) {
DCHECK(spanning_rows_height.row_height.size());
int accumulated_position_increase = 0;
const unsigned row_span = cell->RowSpan();
const unsigned row_index = cell->RowIndex();
DCHECK_EQ(row_span, spanning_rows_height.row_height.size());
for (unsigned row = 0; row < spanning_rows_height.row_height.size(); row++) {
unsigned actual_row = row + row_index;
if (!spanning_rows_height.row_height[row] &&
RowHasOnlySpanningCells(actual_row)) {
spanning_rows_height.row_height[row] =
CalcRowHeightHavingOnlySpanningCells(
actual_row, accumulated_position_increase, row_index + row_span,
extra_height_to_propagate, rows_count_with_only_spanning_cells);
accumulated_position_increase += spanning_rows_height.row_height[row];
}
row_pos_[actual_row + 1] += accumulated_position_increase;
}
spanning_rows_height.total_rows_height += accumulated_position_increase;
}
// Distribute rowSpan cell height in rows those comes in rowSpan cell based on
// the ratio of row's height if 1 RowSpan cell height is greater than the total
// height of rows in rowSpan cell.
void LayoutTableSection::DistributeRowSpanHeightToRows(
SpanningLayoutTableCells& row_span_cells) {
DCHECK(row_span_cells.size());
// 'rowSpanCells' list is already sorted based on the cells rowIndex in
// ascending order
// Arrange row spanning cell in the order in which we need to process first.
std::sort(row_span_cells.begin(), row_span_cells.end(),
CompareRowSpanCellsInHeightDistributionOrder);
unsigned extra_height_to_propagate = 0;
unsigned last_row_index = 0;
unsigned last_row_span = 0;
Vector<int> rows_count_with_only_spanning_cells;
// At this stage, Height of the rows are zero for the one containing only
// spanning cells.
int count = 0;
for (unsigned row = 0; row < grid_.size(); row++) {
if (RowHasOnlySpanningCells(row))
count++;
rows_count_with_only_spanning_cells.push_back(count);
}
for (unsigned i = 0; i < row_span_cells.size(); i++) {
LayoutTableCell* cell = row_span_cells[i];
unsigned row_index = cell->RowIndex();
unsigned row_span = cell->RowSpan();
unsigned spanning_cell_end_index = row_index + row_span;
unsigned last_spanning_cell_end_index = last_row_index + last_row_span;
// Only the highest spanning cell will distribute its extra height in a row
// if more than one spanning cell is present at the same level.
if (row_index == last_row_index && row_span == last_row_span)
continue;
int original_before_position = row_pos_[spanning_cell_end_index];
// When 2 spanning cells are ending at same row index then while extra
// height distribution of first spanning cell updates position of the last
// row so getting the original position of the last row in second spanning
// cell need to reduce the height changed by first spanning cell.
if (spanning_cell_end_index == last_spanning_cell_end_index)
original_before_position -= extra_height_to_propagate;
if (extra_height_to_propagate) {
for (unsigned row = last_spanning_cell_end_index + 1;
row <= spanning_cell_end_index; row++)
row_pos_[row] += extra_height_to_propagate;
}
last_row_index = row_index;
last_row_span = row_span;
struct SpanningRowsHeight spanning_rows_height;
PopulateSpanningRowsHeightFromCell(cell, spanning_rows_height);
// Here we are handling only row(s) who have only rowspanning cells and do
// not have any empty cell.
if (spanning_rows_height.is_any_row_with_only_spanning_cells)
UpdateRowsHeightHavingOnlySpanningCells(
cell, spanning_rows_height, extra_height_to_propagate,
rows_count_with_only_spanning_cells);
// This code handle row(s) that have rowspanning cell(s) and at least one
// empty cell. Such rows are not handled below and end up having a height of
// 0. That would mean content overlapping if one of their cells has any
// content. To avoid the problem, we add all the remaining spanning cells'
// height to the last spanned row. This means that we could grow a row past
// its 'height' or break percentage spreading however this is better than
// overlapping content.
// FIXME: Is there a better algorithm?
if (!spanning_rows_height.total_rows_height) {
if (spanning_rows_height.spanning_cell_height_ignoring_border_spacing)
row_pos_[spanning_cell_end_index] +=
spanning_rows_height.spanning_cell_height_ignoring_border_spacing +
BorderSpacingForRow(spanning_cell_end_index - 1);
extra_height_to_propagate =
row_pos_[spanning_cell_end_index] - original_before_position;
continue;
}
if (spanning_rows_height.spanning_cell_height_ignoring_border_spacing <=
spanning_rows_height.total_rows_height) {
extra_height_to_propagate =
row_pos_[row_index + row_span] - original_before_position;
continue;
}
// Below we are handling only row(s) who have at least one visible cell
// without rowspan value.
float total_percent = 0;
int total_auto_rows_height = 0;
int total_remaining_rows_height = spanning_rows_height.total_rows_height;
// FIXME: Inner spanning cell height should not change if it have fixed
// height when it's parent spanning cell is distributing it's extra height
// in rows.
// Calculate total percentage, total auto rows height and total rows height
// except percent rows.
for (unsigned row = row_index; row < spanning_cell_end_index; row++) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (grid_[row].logical_height.IsPercent()) {
total_percent += grid_[row].logical_height.Percent();
total_remaining_rows_height -=
spanning_rows_height.row_height[row - row_index];
} else if (grid_[row].logical_height.IsAuto()) {
total_auto_rows_height +=
spanning_rows_height.row_height[row - row_index];
}
}
int extra_row_spanning_height =
spanning_rows_height.spanning_cell_height_ignoring_border_spacing -
spanning_rows_height.total_rows_height;
if (total_percent < 100 && !total_auto_rows_height &&
!total_remaining_rows_height) {
// Distributing whole extra rowspanning height in percent row when only
// non-percent rows height is 0.
DistributeWholeExtraRowSpanHeightToPercentRows(
cell, total_percent, extra_row_spanning_height,
spanning_rows_height.row_height);
} else {
DistributeExtraRowSpanHeightToPercentRows(
cell, total_percent, extra_row_spanning_height,
spanning_rows_height.row_height);
DistributeExtraRowSpanHeightToAutoRows(cell, total_auto_rows_height,
extra_row_spanning_height,
spanning_rows_height.row_height);
DistributeExtraRowSpanHeightToRemainingRows(
cell, total_remaining_rows_height, extra_row_spanning_height,
spanning_rows_height.row_height);
}
DCHECK(!extra_row_spanning_height);
// Getting total changed height in the table
extra_height_to_propagate =
row_pos_[spanning_cell_end_index] - original_before_position;
}
if (extra_height_to_propagate) {
// Apply changed height by rowSpan cells to rows present at the end of the
// table
for (unsigned row = last_row_index + last_row_span + 1; row <= grid_.size();
row++)
row_pos_[row] += extra_height_to_propagate;
}
}
// Find out the baseline of the cell
// If the cell's baseline is more than the row's baseline then the cell's
// baseline become the row's baseline and if the row's baseline goes out of the
// row's boundaries then adjust row height accordingly.
void LayoutTableSection::UpdateBaselineForCell(LayoutTableCell* cell,
unsigned row,
int& baseline_descent) {
if (!cell->IsBaselineAligned())
return;
// Ignoring the intrinsic padding as it depends on knowing the row's baseline,
// which won't be accurate until the end of this function.
int baseline_position =
cell->CellBaselinePosition() - cell->IntrinsicPaddingBefore();
if (baseline_position >
cell->BorderBefore() +
(cell->PaddingBefore() - cell->IntrinsicPaddingBefore())) {
grid_[row].baseline = std::max(grid_[row].baseline, baseline_position);
int cell_start_row_baseline_descent = 0;
if (cell->RowSpan() == 1) {
baseline_descent =
std::max(baseline_descent,
cell->LogicalHeightForRowSizing() - baseline_position);
cell_start_row_baseline_descent = baseline_descent;
}
row_pos_[row + 1] =
std::max<int>(row_pos_[row + 1], row_pos_[row] + grid_[row].baseline +
cell_start_row_baseline_descent);
}
}
int LayoutTableSection::VBorderSpacingBeforeFirstRow() const {
// We ignore the border-spacing on any non-top section, as it is already
// included in the previous section's last row position.
if (this != Table()->TopSection())
return 0;
return Table()->VBorderSpacing();
}
int LayoutTableSection::CalcRowLogicalHeight() {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layout_forbidden_scope(*this);
#endif
DCHECK(!NeedsLayout());
LayoutTableCell* cell;
// We may have to forcefully lay out cells here, in which case we need a
// layout state.
LayoutState state(*this);
row_pos_.resize(grid_.size() + 1);
row_pos_[0] = VBorderSpacingBeforeFirstRow();
SpanningLayoutTableCells row_span_cells;
// At fragmentainer breaks we need to prevent rowspanned cells (and whatever
// else) from distributing their extra height requirements over the rows that
// it spans. Otherwise we'd need to refragment afterwards.
unsigned index_of_first_stretchable_row = 0;
for (unsigned r = 0; r < grid_.size(); r++) {
grid_[r].baseline = -1;
int baseline_descent = 0;
if (state.IsPaginated() && grid_[r].row_layout_object)
row_pos_[r] += grid_[r].row_layout_object->PaginationStrut().Ceil();
if (grid_[r].logical_height.IsSpecified()) {
// Our base size is the biggest logical height from our cells' styles
// (excluding row spanning cells).
row_pos_[r + 1] =
std::max(row_pos_[r] + MinimumValueForLength(grid_[r].logical_height,
LayoutUnit())
.Round(),
0);
} else {
// Non-specified lengths are ignored because the row already accounts for
// the cells intrinsic logical height.
row_pos_[r + 1] = std::max(row_pos_[r], 0);
}
Row& row = grid_[r].row;
unsigned total_cols = row.size();
for (unsigned c = 0; c < total_cols; c++) {
CellStruct& current = CellAt(r, c);
if (current.in_col_span)
continue;
for (unsigned i = 0; i < current.cells.size(); i++) {
cell = current.cells[i];
// For row spanning cells, we only handle them for the first row they
// span. This ensures we take their baseline into account.
if (cell->RowIndex() != r)
continue;
if (r < index_of_first_stretchable_row ||
(state.IsPaginated() &&
CrossesPageBoundary(
LayoutUnit(row_pos_[r]),
LayoutUnit(cell->LogicalHeightForRowSizing())))) {
// Entering or extending a range of unstretchable rows. We enter this
// mode when a cell in a row crosses a fragmentainer boundary, and
// we'll stay in this mode until we get to a row where we're past all
// rowspanned cells that we encountered while in this mode.
DCHECK(state.IsPaginated());
unsigned row_index_below_cell = r + cell->RowSpan();
index_of_first_stretchable_row =
std::max(index_of_first_stretchable_row, row_index_below_cell);
} else if (cell->RowSpan() > 1) {
DCHECK(!row_span_cells.Contains(cell));
row_span_cells.push_back(cell);
}
if (cell->HasOverrideLogicalContentHeight()) {
cell->ClearIntrinsicPadding();
cell->ClearOverrideSize();
cell->ForceChildLayout();
}
if (cell->RowSpan() == 1)
row_pos_[r + 1] = std::max(
row_pos_[r + 1], row_pos_[r] + cell->LogicalHeightForRowSizing());
// Find out the baseline. The baseline is set on the first row in a
// rowSpan.
UpdateBaselineForCell(cell, r, baseline_descent);
}
}
if (r < index_of_first_stretchable_row && grid_[r].row_layout_object) {
// We're not allowed to resize this row. Just scratch what we've
// calculated so far, and use the height that we got during initial
// layout instead.
row_pos_[r + 1] =
row_pos_[r] + grid_[r].row_layout_object->LogicalHeight().ToInt();
}
// Add the border-spacing to our final position.
row_pos_[r + 1] += BorderSpacingForRow(r);
row_pos_[r + 1] = std::max(row_pos_[r + 1], row_pos_[r]);
}
if (!row_span_cells.IsEmpty())
DistributeRowSpanHeightToRows(row_span_cells);
DCHECK(!NeedsLayout());
return row_pos_[grid_.size()];
}
void LayoutTableSection::UpdateLayout() {
DCHECK(NeedsLayout());
LayoutAnalyzer::Scope analyzer(*this);
CHECK(!NeedsCellRecalc());
DCHECK(!Table()->NeedsSectionRecalc());
// addChild may over-grow m_grid but we don't want to throw away the memory
// too early as addChild can be called in a loop (e.g during parsing). Doing
// it now ensures we have a stable-enough structure.
grid_.ShrinkToFit();
LayoutState state(*this);
const Vector<int>& column_pos = Table()->EffectiveColumnPositions();
LayoutUnit row_logical_top(VBorderSpacingBeforeFirstRow());
SubtreeLayoutScope layouter(*this);
for (unsigned r = 0; r < grid_.size(); ++r) {
Row& row = grid_[r].row;
unsigned cols = row.size();
// First, propagate our table layout's information to the cells. This will
// mark the row as needing layout if there was a column logical width
// change.
for (unsigned start_column = 0; start_column < cols; ++start_column) {
CellStruct& current = row[start_column];
LayoutTableCell* cell = current.PrimaryCell();
if (!cell || current.in_col_span)
continue;
unsigned end_col = start_column;
unsigned cspan = cell->ColSpan();
while (cspan && end_col < cols) {
DCHECK_LT(end_col, Table()->EffectiveColumns().size());
cspan -= Table()->EffectiveColumns()[end_col].span;
end_col++;
}
int table_layout_logical_width = column_pos[end_col] -
column_pos[start_column] -
Table()->HBorderSpacing();
cell->SetCellLogicalWidth(table_layout_logical_width, layouter);
}
if (LayoutTableRow* row_layout_object = grid_[r].row_layout_object) {
if (state.IsPaginated())
row_layout_object->SetLogicalTop(row_logical_top);
if (!row_layout_object->NeedsLayout())
MarkChildForPaginationRelayoutIfNeeded(*row_layout_object, layouter);
row_layout_object->LayoutIfNeeded();
if (state.IsPaginated()) {
AdjustRowForPagination(*row_layout_object, layouter);
UpdateFragmentationInfoForChild(*row_layout_object);
row_logical_top = row_layout_object->LogicalBottom();
row_logical_top += LayoutUnit(Table()->VBorderSpacing());
}
}
}
ClearNeedsLayout();
}
void LayoutTableSection::DistributeExtraLogicalHeightToPercentRows(
int& extra_logical_height,
int total_percent) {
if (!total_percent)
return;
unsigned total_rows = grid_.size();
int total_height = row_pos_[total_rows] + extra_logical_height;
int total_logical_height_added = 0;
total_percent = std::min(total_percent, 100);
int row_height = row_pos_[1] - row_pos_[0];
for (unsigned r = 0; r < total_rows; ++r) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (total_percent > 0 && grid_[r].logical_height.IsPercent()) {
int to_add = std::min<int>(
extra_logical_height,
(total_height * grid_[r].logical_height.Percent() / 100) -
row_height);
// If toAdd is negative, then we don't want to shrink the row (this bug
// affected Outlook Web Access).
to_add = std::max(0, to_add);
total_logical_height_added += to_add;
extra_logical_height -= to_add;
total_percent -= grid_[r].logical_height.Percent();
}
DCHECK_GE(total_rows, 1u);
if (r < total_rows - 1)
row_height = row_pos_[r + 2] - row_pos_[r + 1];
row_pos_[r + 1] += total_logical_height_added;
}
}
void LayoutTableSection::DistributeExtraLogicalHeightToAutoRows(
int& extra_logical_height,
unsigned auto_rows_count) {
if (!auto_rows_count)
return;
int total_logical_height_added = 0;
for (unsigned r = 0; r < grid_.size(); ++r) {
if (auto_rows_count > 0 && grid_[r].logical_height.IsAuto()) {
// Recomputing |extraLogicalHeightForRow| guarantees that we properly
// ditribute round |extraLogicalHeight|.
int extra_logical_height_for_row = extra_logical_height / auto_rows_count;
total_logical_height_added += extra_logical_height_for_row;
extra_logical_height -= extra_logical_height_for_row;
--auto_rows_count;
}
row_pos_[r + 1] += total_logical_height_added;
}
}
void LayoutTableSection::DistributeRemainingExtraLogicalHeight(
int& extra_logical_height) {
unsigned total_rows = grid_.size();
if (extra_logical_height <= 0 || !row_pos_[total_rows])
return;
// FIXME: m_rowPos[totalRows] - m_rowPos[0] is the total rows' size.
int total_row_size = row_pos_[total_rows];
int total_logical_height_added = 0;
int previous_row_position = row_pos_[0];
for (unsigned r = 0; r < total_rows; r++) {
// weight with the original height
total_logical_height_added += extra_logical_height *
(row_pos_[r + 1] - previous_row_position) /
total_row_size;
previous_row_position = row_pos_[r + 1];
row_pos_[r + 1] += total_logical_height_added;
}
extra_logical_height -= total_logical_height_added;
}
int LayoutTableSection::DistributeExtraLogicalHeightToRows(
int extra_logical_height) {
if (!extra_logical_height)
return extra_logical_height;
unsigned total_rows = grid_.size();
if (!total_rows)
return extra_logical_height;
if (!row_pos_[total_rows] && NextSibling())
return extra_logical_height;
unsigned auto_rows_count = 0;
int total_percent = 0;
for (unsigned r = 0; r < total_rows; r++) {
if (grid_[r].logical_height.IsAuto())
++auto_rows_count;
else if (grid_[r].logical_height.IsPercent())
total_percent += grid_[r].logical_height.Percent();
}
int remaining_extra_logical_height = extra_logical_height;
DistributeExtraLogicalHeightToPercentRows(remaining_extra_logical_height,
total_percent);
DistributeExtraLogicalHeightToAutoRows(remaining_extra_logical_height,
auto_rows_count);
DistributeRemainingExtraLogicalHeight(remaining_extra_logical_height);
return extra_logical_height - remaining_extra_logical_height;
}
bool CellHasExplicitlySpecifiedHeight(const LayoutTableCell& cell) {
if (cell.Style()->LogicalHeight().IsFixed())
return true;
LayoutBlock* cb = cell.ContainingBlock();
if (cb->AvailableLogicalHeightForPercentageComputation() == -1)
return false;
return true;
}
static bool ShouldFlexCellChild(const LayoutTableCell& cell,
LayoutObject* cell_descendant) {
if (!CellHasExplicitlySpecifiedHeight(cell))
return false;
if (cell_descendant->Style()->OverflowY() == EOverflow::kVisible ||
cell_descendant->Style()->OverflowY() == EOverflow::kHidden)
return true;
return cell_descendant->IsBox() &&
ToLayoutBox(cell_descendant)->ShouldBeConsideredAsReplaced();
}
void LayoutTableSection::LayoutRows() {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layout_forbidden_scope(*this);
#endif
DCHECK(!NeedsLayout());
LayoutAnalyzer::Scope analyzer(*this);
// FIXME: Changing the height without a layout can change the overflow so it
// seems wrong.
unsigned total_rows = grid_.size();
// Set the width of our section now. The rows will also be this width.
SetLogicalWidth(Table()->ContentLogicalWidth());
int vspacing = Table()->VBorderSpacing();
LayoutState state(*this);
// Set the rows' location and size.
for (unsigned r = 0; r < total_rows; r++) {
LayoutTableRow* row_layout_object = grid_[r].row_layout_object;
if (row_layout_object) {
row_layout_object->SetLogicalLocation(LayoutPoint(0, row_pos_[r]));
row_layout_object->SetLogicalWidth(LogicalWidth());
LayoutUnit row_logical_height(row_pos_[r + 1] - row_pos_[r] - vspacing);
if (state.IsPaginated() && r + 1 < total_rows) {
// If the next row has a pagination strut, we need to subtract it. It
// should not be included in this row's height.
if (LayoutTableRow* next_row_object = grid_[r + 1].row_layout_object)
row_logical_height -= next_row_object->PaginationStrut();
}
row_layout_object->SetLogicalHeight(row_logical_height);
row_layout_object->UpdateAfterLayout();
}
}
// Vertically align and flex the cells in each row.
for (unsigned r = 0; r < total_rows; r++) {
LayoutTableRow* row_layout_object = grid_[r].row_layout_object;
unsigned n_cols = NumCols(r);
for (unsigned c = 0; c < n_cols; c++) {
LayoutTableCell* cell = OriginatingCellAt(r, c);
if (!cell)
continue;
int r_height;
int row_logical_top;
unsigned row_span = std::max(1U, cell->RowSpan());
unsigned end_row_index = std::min(r + row_span, total_rows) - 1;
LayoutTableRow* last_row_object = grid_[end_row_index].row_layout_object;
if (last_row_object && row_layout_object) {
row_logical_top = row_layout_object->LogicalTop().ToInt();
r_height = last_row_object->LogicalBottom().ToInt() - row_logical_top;
} else {
r_height = row_pos_[end_row_index + 1] - row_pos_[r] - vspacing;
row_logical_top = row_pos_[r];
}
RelayoutCellIfFlexed(*cell, r, r_height);
SubtreeLayoutScope layouter(*cell);
EVerticalAlign cell_vertical_align;
// If the cell crosses a fragmentainer boundary, just align it at the
// top. That's how it was laid out initially, before we knew the final
// row height, and re-aligning it now could result in the cell being
// fragmented differently, which could change its height and thus violate
// the requested alignment. Give up instead of risking circular
// dependencies and unstable layout.
if (state.IsPaginated() &&
CrossesPageBoundary(LayoutUnit(row_logical_top),
LayoutUnit(r_height)))
cell_vertical_align = EVerticalAlign::kTop;
else
cell_vertical_align = cell->Style()->VerticalAlign();
cell->ComputeIntrinsicPadding(r_height, cell_vertical_align, layouter);
LayoutRect old_cell_rect = cell->FrameRect();
SetLogicalPositionForCell(cell, c);
cell->LayoutIfNeeded();
LayoutSize child_offset(cell->Location() - old_cell_rect.Location());
if (child_offset.Width() || child_offset.Height()) {
// If the child moved, we have to issue paint invalidations to it as
// well as any floating/positioned descendants. An exception is if we
// need a layout. In this case, we know we're going to issue paint
// invalidations ourselves (and the child) anyway.
if (!Table()->SelfNeedsLayout())
cell->SetMayNeedPaintInvalidation();
}
}
if (row_layout_object)
row_layout_object->ComputeOverflow();
}
DCHECK(!NeedsLayout());
SetLogicalHeight(LayoutUnit(row_pos_[total_rows]));
ComputeOverflowFromDescendants();
}
int LayoutTableSection::PaginationStrutForRow(LayoutTableRow* row,
LayoutUnit logical_offset) const {
DCHECK(row);
if (row->GetPaginationBreakability() == kAllowAnyBreaks)
return 0;
LayoutUnit page_logical_height = PageLogicalHeightForOffset(logical_offset);
if (!page_logical_height)
return 0;
// If the row is too tall for the page don't insert a strut.
LayoutUnit row_logical_height = row->LogicalHeight();
if (row_logical_height > page_logical_height)
return 0;
LayoutUnit remaining_logical_height = PageRemainingLogicalHeightForOffset(
logical_offset, LayoutBlock::kAssociateWithLatterPage);
if (remaining_logical_height >= row_logical_height)
return 0; // It fits fine where it is. No need to break.
LayoutUnit pagination_strut = CalculatePaginationStrutToFitContent(
logical_offset, remaining_logical_height, row_logical_height);
if (pagination_strut == remaining_logical_height &&
remaining_logical_height == page_logical_height) {
// Don't break if we were at the top of a page, and we failed to fit the
// content completely. No point in leaving a page completely blank.
return 0;
}
// Table layout parts only work on integers, so we have to round. Round up, to
// make sure that no fraction ever gets left behind in the previous
// fragmentainer.
return pagination_strut.Ceil();
}
void LayoutTableSection::ComputeOverflowFromDescendants() {
unsigned total_cells_count = NumRows() * Table()->NumEffectiveColumns();
unsigned max_allowed_overflowing_cells_count =
total_cells_count <
g_min_table_size_to_use_fast_paint_path_with_overflowing_cell
? 0
: kGMaxAllowedOverflowingCellRatioForFastPaintPath *
total_cells_count;
overflow_.reset();
overflowing_cells_.clear();
force_slow_paint_path_with_overflowing_cell_ = false;
#if DCHECK_IS_ON()
bool has_overflowing_cell = false;
#endif
for (auto* row = FirstRow(); row; row = row->NextRow()) {
AddOverflowFromChild(*row);
for (auto* cell = row->FirstCell(); cell; cell = cell->NextCell()) {
// Let the section's self visual overflow cover the cell's whole collapsed
// borders. This ensures correct raster invalidation on section border
// style change.
// TODO(wangxianzhu): When implementing row as DisplayItemClient of
// collapsed borders, the following logic should be replaced by
// invalidation of rows on section border style change. crbug.com/663208.
if (const auto* collapsed_borders = cell->GetCollapsedBorderValues()) {
LayoutRect rect = cell->RectForOverflowPropagation(
collapsed_borders->LocalVisualRect());
rect.MoveBy(cell->Location());
AddSelfVisualOverflow(rect);
}
if (force_slow_paint_path_with_overflowing_cell_ ||
!cell->HasVisualOverflow())
continue;
#if DCHECK_IS_ON()
has_overflowing_cell = true;
#endif
if (overflowing_cells_.size() >= max_allowed_overflowing_cells_count) {
// We need to set force_slow_paint_path_with_overflowing_cell_ only if
// there is at least one overflowing cell as the hit testing code relies
// on this information.
force_slow_paint_path_with_overflowing_cell_ = true;
// The slow path does not make any use of the overflowing cells info,
// don't hold on to the memory.
overflowing_cells_.clear();
continue;
}
overflowing_cells_.insert(cell);
}
}
#if DCHECK_IS_ON()
DCHECK_EQ(has_overflowing_cell, this->HasOverflowingCell());
#endif
}
bool LayoutTableSection::RecalcChildOverflowAfterStyleChange() {
DCHECK(ChildNeedsOverflowRecalcAfterStyleChange());
ClearChildNeedsOverflowRecalcAfterStyleChange();
unsigned total_rows = grid_.size();
bool children_overflow_changed = false;
for (unsigned r = 0; r < total_rows; r++) {
LayoutTableRow* row_layouter = RowLayoutObjectAt(r);
if (!row_layouter ||
!row_layouter->ChildNeedsOverflowRecalcAfterStyleChange())
continue;
row_layouter->ClearChildNeedsOverflowRecalcAfterStyleChange();
bool row_children_overflow_changed = false;
unsigned n_cols = NumCols(r);
for (unsigned c = 0; c < n_cols; c++) {
auto* cell = OriginatingCellAt(r, c);
if (!cell || !cell->NeedsOverflowRecalcAfterStyleChange())
continue;
row_children_overflow_changed |= cell->RecalcOverflowAfterStyleChange();
}
if (row_children_overflow_changed)
row_layouter->ComputeOverflow();
children_overflow_changed |= row_children_overflow_changed;
}
if (children_overflow_changed)
ComputeOverflowFromDescendants();
return children_overflow_changed;
}
void LayoutTableSection::MarkAllCellsWidthsDirtyAndOrNeedsLayout(
LayoutTable::WhatToMarkAllCells what_to_mark) {
for (LayoutTableRow* row = FirstRow(); row; row = row->NextRow()) {
for (LayoutTableCell* cell = row->FirstCell(); cell;
cell = cell->NextCell()) {
cell->SetPreferredLogicalWidthsDirty();
if (what_to_mark == LayoutTable::kMarkDirtyAndNeedsLayout)
cell->SetChildNeedsLayout();
}
}
}
int LayoutTableSection::CalcBlockDirectionOuterBorder(
BlockBorderSide side) const {
if (!grid_.size() || !Table()->NumEffectiveColumns())
return 0;
int border_width = 0;
const BorderValue& sb =
side == kBorderBefore ? Style()->BorderBefore() : Style()->BorderAfter();
if (sb.Style() == kBorderStyleHidden)
return -1;
if (sb.Style() > kBorderStyleHidden)
border_width = sb.Width();
const BorderValue& rb = side == kBorderBefore
? FirstRow()->Style()->BorderBefore()
: LastRow()->Style()->BorderAfter();
if (rb.Style() == kBorderStyleHidden)
return -1;
if (rb.Style() > kBorderStyleHidden && rb.Width() > border_width)
border_width = rb.Width();
bool all_hidden = true;
unsigned r = side == kBorderBefore ? 0 : grid_.size() - 1;
unsigned n_cols = NumCols(r);
for (unsigned c = 0; c < n_cols; c++) {
const CellStruct& current = CellAt(r, c);
if (current.in_col_span || !current.HasCells())
continue;
const ComputedStyle& primary_cell_style = current.PrimaryCell()->StyleRef();
// FIXME: Make this work with perpendicular and flipped cells.
const BorderValue& cb = side == kBorderBefore
? primary_cell_style.BorderBefore()
: primary_cell_style.BorderAfter();
// FIXME: Don't repeat for the same col group
LayoutTableCol* col =
Table()->ColElementAtAbsoluteColumn(c).InnermostColOrColGroup();
if (col) {
const BorderValue& gb = side == kBorderBefore
? col->Style()->BorderBefore()
: col->Style()->BorderAfter();
if (gb.Style() == kBorderStyleHidden || cb.Style() == kBorderStyleHidden)
continue;
all_hidden = false;
if (gb.Style() > kBorderStyleHidden && gb.Width() > border_width)
border_width = gb.Width();
if (cb.Style() > kBorderStyleHidden && cb.Width() > border_width)
border_width = cb.Width();
} else {
if (cb.Style() == kBorderStyleHidden)
continue;
all_hidden = false;
if (cb.Style() > kBorderStyleHidden && cb.Width() > border_width)
border_width = cb.Width();
}
}
if (all_hidden)
return -1;
if (side == kBorderAfter)
border_width++; // Distribute rounding error
return border_width / 2;
}
int LayoutTableSection::CalcInlineDirectionOuterBorder(
InlineBorderSide side) const {
unsigned total_cols = Table()->NumEffectiveColumns();
if (!grid_.size() || !total_cols)
return 0;
unsigned col_index = side == kBorderStart ? 0 : total_cols - 1;
int border_width = 0;
const BorderValue& sb =
side == kBorderStart ? Style()->BorderStart() : Style()->BorderEnd();
if (sb.Style() == kBorderStyleHidden)
return -1;
if (sb.Style() > kBorderStyleHidden)
border_width = sb.Width();
if (LayoutTableCol* col = Table()
->ColElementAtAbsoluteColumn(col_index)
.InnermostColOrColGroup()) {
const BorderValue& gb = side == kBorderStart ? col->Style()->BorderStart()
: col->Style()->BorderEnd();
if (gb.Style() == kBorderStyleHidden)
return -1;
if (gb.Style() > kBorderStyleHidden && gb.Width() > border_width)
border_width = gb.Width();
}
bool all_hidden = true;
for (unsigned r = 0; r < grid_.size(); r++) {
if (col_index >= NumCols(r))
continue;
const CellStruct& current = CellAt(r, col_index);
if (!current.HasCells())
continue;
// FIXME: Don't repeat for the same cell
const ComputedStyle& primary_cell_style = current.PrimaryCell()->StyleRef();
const ComputedStyle& primary_cell_parent_style =
current.PrimaryCell()->Parent()->StyleRef();
// FIXME: Make this work with perpendicular and flipped cells.
const BorderValue& cb = side == kBorderStart
? primary_cell_style.BorderStart()
: primary_cell_style.BorderEnd();
const BorderValue& rb = side == kBorderStart
? primary_cell_parent_style.BorderStart()
: primary_cell_parent_style.BorderEnd();
if (cb.Style() == kBorderStyleHidden || rb.Style() == kBorderStyleHidden)
continue;
all_hidden = false;
if (cb.Style() > kBorderStyleHidden && cb.Width() > border_width)
border_width = cb.Width();
if (rb.Style() > kBorderStyleHidden && rb.Width() > border_width)
border_width = rb.Width();
}
if (all_hidden)
return -1;
if ((side == kBorderStart) != Table()->Style()->IsLeftToRightDirection())
border_width++; // Distribute rounding error
return border_width / 2;
}
void LayoutTableSection::RecalcOuterBorder() {
outer_border_before_ = CalcBlockDirectionOuterBorder(kBorderBefore);
outer_border_after_ = CalcBlockDirectionOuterBorder(kBorderAfter);
outer_border_start_ = CalcInlineDirectionOuterBorder(kBorderStart);
outer_border_end_ = CalcInlineDirectionOuterBorder(kBorderEnd);
}
int LayoutTableSection::FirstLineBoxBaseline() const {
if (!grid_.size())
return -1;
int first_line_baseline = grid_[0].baseline;
if (first_line_baseline >= 0)
return first_line_baseline + row_pos_[0];
const Row& first_row = grid_[0].row;
for (size_t i = 0; i < first_row.size(); ++i) {
const CellStruct& cs = first_row.at(i);
const LayoutTableCell* cell = cs.PrimaryCell();
if (cell)
first_line_baseline =
std::max<int>(first_line_baseline,
(cell->LogicalTop() + cell->BorderBefore() +
cell->PaddingBefore() + cell->ContentLogicalHeight())
.ToInt());
}
return first_line_baseline;
}
void LayoutTableSection::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
TableSectionPainter(*this).Paint(paint_info, paint_offset);
}
LayoutRect LayoutTableSection::LogicalRectForWritingModeAndDirection(
const LayoutRect& rect) const {
LayoutRect table_aligned_rect(rect);
FlipForWritingMode(table_aligned_rect);
if (!Style()->IsHorizontalWritingMode())
table_aligned_rect = table_aligned_rect.TransposedRect();
const Vector<int>& column_pos = Table()->EffectiveColumnPositions();
// FIXME: The table's direction should determine our row's direction, not the
// section's (see bug 96691).
if (!Style()->IsLeftToRightDirection())
table_aligned_rect.SetX(column_pos[column_pos.size() - 1] -
table_aligned_rect.MaxX());
return table_aligned_rect;
}
CellSpan LayoutTableSection::DirtiedRows(const LayoutRect& damage_rect) const {
if (force_slow_paint_path_with_overflowing_cell_)
return FullSectionRowSpan();
if (!grid_.size())
return CellSpan(0, 0);
CellSpan covered_rows = SpannedRows(damage_rect);
// To issue paint invalidations for the border we might need to paint
// invalidate the first or last row even if they are not spanned themselves.
CHECK_LT(covered_rows.Start(), row_pos_.size());
if (covered_rows.Start() == row_pos_.size() - 1 &&
row_pos_[row_pos_.size() - 1] + Table()->OuterBorderAfter() >=
damage_rect.Y())
covered_rows.DecreaseStart();
if (!covered_rows.end() &&
row_pos_[0] - Table()->OuterBorderBefore() <= damage_rect.MaxY())
covered_rows.IncreaseEnd();
covered_rows.EnsureConsistency(grid_.size());
if (!has_spanning_cells_ || !covered_rows.Start() ||
covered_rows.Start() >= grid_.size())
return covered_rows;
// If there are any cells spanning into the first row, expand coveredRows
// to cover the primary cells.
unsigned n_cols = NumCols(covered_rows.Start());
unsigned smallest_row = covered_rows.Start();
CellSpan covered_columns = SpannedEffectiveColumns(damage_rect);
for (unsigned c = covered_columns.Start();
c < std::min(covered_columns.end(), n_cols); ++c) {
if (const auto* cell = PrimaryCellAt(covered_rows.Start(), c)) {
smallest_row = std::min(smallest_row, cell->RowIndex());
if (!smallest_row)
break;
}
}
return CellSpan(smallest_row, covered_rows.end());
}
CellSpan LayoutTableSection::DirtiedEffectiveColumns(
const LayoutRect& damage_rect) const {
if (force_slow_paint_path_with_overflowing_cell_)
return FullTableEffectiveColumnSpan();
CHECK(Table()->NumEffectiveColumns());
CellSpan covered_columns = SpannedEffectiveColumns(damage_rect);
const Vector<int>& column_pos = Table()->EffectiveColumnPositions();
// To issue paint invalidations for the border we might need to paint
// invalidate the first or last column even if they are not spanned
// themselves.
CHECK_LT(covered_columns.Start(), column_pos.size());
if (covered_columns.Start() == column_pos.size() - 1 &&
column_pos[column_pos.size() - 1] + Table()->OuterBorderEnd() >=
damage_rect.X())
covered_columns.DecreaseStart();
if (!covered_columns.end() &&
column_pos[0] - Table()->OuterBorderStart() <= damage_rect.MaxX())
covered_columns.IncreaseEnd();
covered_columns.EnsureConsistency(Table()->NumEffectiveColumns());
if (!has_spanning_cells_ || !covered_columns.Start())
return covered_columns;
// If there are any cells spanning into the first column, expand
// coveredRows to cover the primary cells.
unsigned smallest_column = covered_columns.Start();
CellSpan covered_rows = SpannedRows(damage_rect);
for (unsigned r = covered_rows.Start(); r < covered_rows.end(); ++r) {
const auto& row = grid_[r].row;
if (covered_columns.Start() < row.size()) {
unsigned c = covered_columns.Start();
while (c && row[c].in_col_span)
--c;
smallest_column = std::min(c, smallest_column);
if (!smallest_column)
break;
}
}
return CellSpan(smallest_column, covered_columns.end());
}
CellSpan LayoutTableSection::SpannedRows(const LayoutRect& flipped_rect) const {
// Find the first row that starts after rect top.
unsigned next_row =
std::upper_bound(row_pos_.begin(), row_pos_.end(), flipped_rect.Y()) -
row_pos_.begin();
// After all rows.
if (next_row == row_pos_.size())
return CellSpan(row_pos_.size() - 1, row_pos_.size() - 1);
unsigned start_row = next_row > 0 ? next_row - 1 : 0;
// Find the first row that starts after rect bottom.
unsigned end_row;
if (row_pos_[next_row] >= flipped_rect.MaxY()) {
end_row = next_row;
} else {
end_row = std::upper_bound(row_pos_.begin() + next_row, row_pos_.end(),
flipped_rect.MaxY()) -
row_pos_.begin();
if (end_row == row_pos_.size())
end_row = row_pos_.size() - 1;
}
return CellSpan(start_row, end_row);
}
CellSpan LayoutTableSection::SpannedEffectiveColumns(
const LayoutRect& flipped_rect) const {
const Vector<int>& column_pos = Table()->EffectiveColumnPositions();
// Find the first column that starts after rect left.
// lower_bound doesn't handle the edge between two cells properly as it would
// wrongly return the cell on the logical top/left.
// upper_bound on the other hand properly returns the cell on the logical
// bottom/right, which also matches the behavior of other browsers.
unsigned next_column =
std::upper_bound(column_pos.begin(), column_pos.end(), flipped_rect.X()) -
column_pos.begin();
if (next_column == column_pos.size())
return CellSpan(column_pos.size() - 1,
column_pos.size() - 1); // After all columns.
unsigned start_column = next_column > 0 ? next_column - 1 : 0;
// Find the first column that starts after rect right.
unsigned end_column;
if (column_pos[next_column] >= flipped_rect.MaxX()) {
end_column = next_column;
} else {
end_column = std::upper_bound(column_pos.begin() + next_column,
column_pos.end(), flipped_rect.MaxX()) -
column_pos.begin();
if (end_column == column_pos.size())
end_column = column_pos.size() - 1;
}
return CellSpan(start_column, end_column);
}
void LayoutTableSection::RecalcCells() {
DCHECK(needs_cell_recalc_);
// We reset the flag here to ensure that |addCell| works. This is safe to do
// as fillRowsWithDefaultStartingAtPosition makes sure we match the table's
// columns representation.
needs_cell_recalc_ = false;
c_col_ = 0;
c_row_ = 0;
grid_.clear();
for (LayoutTableRow* row = FirstRow(); row; row = row->NextRow()) {
unsigned insertion_row = c_row_;
++c_row_;
c_col_ = 0;
EnsureRows(c_row_);
grid_[insertion_row].row_layout_object = row;
row->SetRowIndex(insertion_row);
SetRowLogicalHeightToRowStyleLogicalHeight(grid_[insertion_row]);
for (LayoutTableCell* cell = row->FirstCell(); cell;
cell = cell->NextCell())
AddCell(cell, row);
}
grid_.ShrinkToFit();
SetNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::kUnknown);
}
// FIXME: This function could be made O(1) in certain cases (like for the
// non-most-constrainive cells' case).
void LayoutTableSection::RowLogicalHeightChanged(LayoutTableRow* row) {
if (NeedsCellRecalc())
return;
unsigned row_index = row->RowIndex();
SetRowLogicalHeightToRowStyleLogicalHeight(grid_[row_index]);
for (LayoutTableCell* cell = grid_[row_index].row_layout_object->FirstCell();
cell; cell = cell->NextCell())
UpdateLogicalHeightForCell(grid_[row_index], cell);
}
void LayoutTableSection::SetNeedsCellRecalc() {
needs_cell_recalc_ = true;
if (LayoutTable* t = Table())
t->SetNeedsSectionRecalc();
}
unsigned LayoutTableSection::NumEffectiveColumns() const {
unsigned result = 0;
for (unsigned r = 0; r < grid_.size(); ++r) {
unsigned n_cols = NumCols(r);
for (unsigned c = result; c < n_cols; ++c) {
const CellStruct& cell = CellAt(r, c);
if (cell.HasCells() || cell.in_col_span)
result = c;
}
}
return result + 1;
}
BorderValue LayoutTableSection::BorderAdjoiningStartCell(
const LayoutTableCell* cell) const {
#if DCHECK_IS_ON()
DCHECK(cell->IsFirstOrLastCellInRow());
#endif
return HasSameDirectionAs(cell) ? Style()->BorderStart()
: Style()->BorderEnd();
}
BorderValue LayoutTableSection::BorderAdjoiningEndCell(
const LayoutTableCell* cell) const {
#if DCHECK_IS_ON()
DCHECK(cell->IsFirstOrLastCellInRow());
#endif
return HasSameDirectionAs(cell) ? Style()->BorderEnd()
: Style()->BorderStart();
}
const LayoutTableCell* LayoutTableSection::FirstRowCellAdjoiningTableStart()
const {
unsigned adjoining_start_cell_column_index =
HasSameDirectionAs(Table()) ? 0 : Table()->LastEffectiveColumnIndex();
return PrimaryCellAt(0, adjoining_start_cell_column_index);
}
const LayoutTableCell* LayoutTableSection::FirstRowCellAdjoiningTableEnd()
const {
unsigned adjoining_end_cell_column_index =
HasSameDirectionAs(Table()) ? Table()->LastEffectiveColumnIndex() : 0;
return PrimaryCellAt(0, adjoining_end_cell_column_index);
}
LayoutTableCell* LayoutTableSection::OriginatingCellAt(
unsigned row,
unsigned effective_column) {
auto& row_vector = grid_[row].row;
if (effective_column >= row_vector.size())
return nullptr;
auto& cell_struct = row_vector[effective_column];
if (cell_struct.in_col_span)
return nullptr;
if (auto* cell = cell_struct.PrimaryCell()) {
if (cell->RowIndex() == row)
return cell;
}
return nullptr;
}
void LayoutTableSection::AppendEffectiveColumn(unsigned pos) {
DCHECK(!needs_cell_recalc_);
for (unsigned row = 0; row < grid_.size(); ++row)
grid_[row].row.resize(pos + 1);
}
void LayoutTableSection::SplitEffectiveColumn(unsigned pos, unsigned first) {
DCHECK(!needs_cell_recalc_);
if (c_col_ > pos)
c_col_++;
for (unsigned row = 0; row < grid_.size(); ++row) {
Row& r = grid_[row].row;
EnsureCols(row, pos + 2);
r.insert(pos + 1, CellStruct());
if (r[pos].HasCells()) {
r[pos + 1].cells.AppendVector(r[pos].cells);
LayoutTableCell* cell = r[pos].PrimaryCell();
DCHECK(cell);
DCHECK_GE(cell->ColSpan(), (r[pos].in_col_span ? 1u : 0));
unsigned colleft = cell->ColSpan() - r[pos].in_col_span;
if (first > colleft)
r[pos + 1].in_col_span = 0;
else
r[pos + 1].in_col_span = first + r[pos].in_col_span;
} else {
r[pos + 1].in_col_span = 0;
}
}
}
// Hit Testing
bool LayoutTableSection::NodeAtPoint(
HitTestResult& result,
const HitTestLocation& location_in_container,
const LayoutPoint& accumulated_offset,
HitTestAction action) {
// If we have no children then we have nothing to do.
if (!FirstRow())
return false;
// Table sections cannot ever be hit tested. Effectively they do not exist.
// Just forward to our children always.
LayoutPoint adjusted_location = accumulated_offset + Location();
if (HasOverflowClip() &&
!location_in_container.Intersects(OverflowClipRect(adjusted_location)))
return false;
if (HasOverflowingCell()) {
for (LayoutTableRow* row = LastRow(); row; row = row->PreviousRow()) {
// FIXME: We have to skip over inline flows, since they can show up inside
// table rows at the moment (a demoted inline <form> for example). If we
// ever implement a table-specific hit-test method (which we should do for
// performance reasons anyway), then we can remove this check.
if (!row->HasSelfPaintingLayer()) {
LayoutPoint child_point =
FlipForWritingModeForChild(row, adjusted_location);
if (row->NodeAtPoint(result, location_in_container, child_point,
action)) {
UpdateHitTestResult(
result,
ToLayoutPoint(location_in_container.Point() - child_point));
return true;
}
}
}
return false;
}
RecalcCellsIfNeeded();
LayoutRect hit_test_rect = LayoutRect(location_in_container.BoundingBox());
hit_test_rect.MoveBy(-adjusted_location);
LayoutRect table_aligned_rect =
LogicalRectForWritingModeAndDirection(hit_test_rect);
CellSpan row_span = SpannedRows(table_aligned_rect);
CellSpan column_span = SpannedEffectiveColumns(table_aligned_rect);
// Now iterate over the spanned rows and columns.
for (unsigned hit_row = row_span.Start(); hit_row < row_span.end();
++hit_row) {
unsigned n_cols = NumCols(hit_row);
for (unsigned hit_column = column_span.Start();
hit_column < n_cols && hit_column < column_span.end(); ++hit_column) {
CellStruct& current = CellAt(hit_row, hit_column);
// If the cell is empty, there's nothing to do
if (!current.HasCells())
continue;
for (unsigned i = current.cells.size(); i;) {
--i;
LayoutTableCell* cell = current.cells[i];
LayoutPoint cell_point =
FlipForWritingModeForChild(cell, adjusted_location);
if (static_cast<LayoutObject*>(cell)->NodeAtPoint(
result, location_in_container, cell_point, action)) {
UpdateHitTestResult(
result, location_in_container.Point() - ToLayoutSize(cell_point));
return true;
}
}
if (!result.GetHitTestRequest().ListBased())
break;
}
if (!result.GetHitTestRequest().ListBased())
break;
}
return false;
}
LayoutTableSection* LayoutTableSection::CreateAnonymousWithParent(
const LayoutObject* parent) {
RefPtr<ComputedStyle> new_style =
ComputedStyle::CreateAnonymousStyleWithDisplay(parent->StyleRef(),
EDisplay::kTableRowGroup);
LayoutTableSection* new_section = new LayoutTableSection(nullptr);
new_section->SetDocumentForAnonymous(&parent->GetDocument());
new_section->SetStyle(std::move(new_style));
return new_section;
}
void LayoutTableSection::SetLogicalPositionForCell(
LayoutTableCell* cell,
unsigned effective_column) const {
LayoutPoint cell_location(0, row_pos_[cell->RowIndex()]);
int horizontal_border_spacing = Table()->HBorderSpacing();
// FIXME: The table's direction should determine our row's direction, not the
// section's (see bug 96691).
if (!Style()->IsLeftToRightDirection())
cell_location.SetX(LayoutUnit(
Table()->EffectiveColumnPositions()[Table()->NumEffectiveColumns()] -
Table()->EffectiveColumnPositions()
[Table()->AbsoluteColumnToEffectiveColumn(
cell->AbsoluteColumnIndex() + cell->ColSpan())] +
horizontal_border_spacing));
else
cell_location.SetX(
LayoutUnit(Table()->EffectiveColumnPositions()[effective_column] +
horizontal_border_spacing));
cell->SetLogicalLocation(cell_location);
}
void LayoutTableSection::RelayoutCellIfFlexed(LayoutTableCell& cell,
int row_index,
int row_height) {
// Force percent height children to lay themselves out again.
// This will cause these children to grow to fill the cell.
// FIXME: There is still more work to do here to fully match WinIE (should
// it become necessary to do so). In quirks mode, WinIE behaves like we
// do, but it will clip the cells that spill out of the table section.
// strict mode, Mozilla and WinIE both regrow the table to accommodate the
// new height of the cell (thus letting the percentages cause growth one
// time only). We may also not be handling row-spanning cells correctly.
//
// Note also the oddity where replaced elements always flex, and yet blocks/
// tables do not necessarily flex. WinIE is crazy and inconsistent, and we
// can't hope to match the behavior perfectly, but we'll continue to refine it
// as we discover new bugs. :)
bool cell_children_flex = false;
bool flex_all_children = CellHasExplicitlySpecifiedHeight(cell) ||
(!Table()->Style()->LogicalHeight().IsAuto() &&
row_height != cell.LogicalHeight());
for (LayoutObject* child = cell.FirstChild(); child;
child = child->NextSibling()) {
if (!child->IsText() && child->Style()->LogicalHeight().IsPercentOrCalc() &&
(flex_all_children || ShouldFlexCellChild(cell, child)) &&
(!child->IsTable() || ToLayoutTable(child)->HasSections())) {
cell_children_flex = true;
break;
}
}
if (!cell_children_flex) {
if (TrackedLayoutBoxListHashSet* percent_height_descendants =
cell.PercentHeightDescendants()) {
for (auto* descendant : *percent_height_descendants) {
if (flex_all_children || ShouldFlexCellChild(cell, descendant)) {
cell_children_flex = true;
break;
}
}
}
}
if (!cell_children_flex)
return;
// Alignment within a cell is based off the calculated height, which becomes
// irrelevant once the cell has been resized based off its percentage.
cell.SetOverrideLogicalContentHeightFromRowHeight(LayoutUnit(row_height));
cell.ForceChildLayout();
// If the baseline moved, we may have to update the data for our row. Find
// out the new baseline.
if (cell.IsBaselineAligned()) {
int baseline = cell.CellBaselinePosition();
if (baseline > cell.BorderBefore() + cell.PaddingBefore())
grid_[row_index].baseline = std::max(grid_[row_index].baseline, baseline);
}
}
int LayoutTableSection::LogicalHeightForRow(
const LayoutTableRow& row_object) const {
unsigned row_index = row_object.RowIndex();
DCHECK_LT(row_index, grid_.size());
int logical_height = 0;
const Row& row = grid_[row_index].row;
unsigned cols = row.size();
for (unsigned col_index = 0; col_index < cols; col_index++) {
const CellStruct& cell_struct = CellAt(row_index, col_index);
const LayoutTableCell* cell = cell_struct.PrimaryCell();
if (!cell || cell_struct.in_col_span)
continue;
unsigned row_span = cell->RowSpan();
if (row_span == 1) {
logical_height =
std::max(logical_height, cell->LogicalHeightForRowSizing());
continue;
}
unsigned row_index_for_cell = cell->RowIndex();
if (row_index == grid_.size() - 1 ||
(row_span > 1 && row_index - row_index_for_cell == row_span - 1)) {
// This is the last row of the rowspanned cell. Add extra height if
// needed.
if (LayoutTableRow* first_row_for_cell =
grid_[row_index_for_cell].row_layout_object) {
int min_logical_height = cell->LogicalHeightForRowSizing();
// Subtract space provided by previous rows.
min_logical_height -= row_object.LogicalTop().ToInt() -
first_row_for_cell->LogicalTop().ToInt();
logical_height = std::max(logical_height, min_logical_height);
}
}
}
if (grid_[row_index].logical_height.IsSpecified()) {
LayoutUnit specified_logical_height =
MinimumValueForLength(grid_[row_index].logical_height, LayoutUnit());
logical_height = std::max(logical_height, specified_logical_height.ToInt());
}
return logical_height;
}
void LayoutTableSection::AdjustRowForPagination(LayoutTableRow& row_object,
SubtreeLayoutScope& layouter) {
row_object.SetPaginationStrut(LayoutUnit());
row_object.SetLogicalHeight(LayoutUnit(LogicalHeightForRow(row_object)));
int pagination_strut =
PaginationStrutForRow(&row_object, row_object.LogicalTop());
bool row_is_at_top_of_column = false;
LayoutUnit offset_from_top_of_page;
if (!pagination_strut) {
LayoutUnit page_logical_height =
PageLogicalHeightForOffset(row_object.LogicalTop());
if (page_logical_height && Table()->Header() && Table()->Header() != this &&
Table()->RowOffsetFromRepeatingHeader()) {
offset_from_top_of_page =
page_logical_height -
PageRemainingLogicalHeightForOffset(row_object.LogicalTop(),
kAssociateWithLatterPage);
row_is_at_top_of_column =
!offset_from_top_of_page ||
offset_from_top_of_page <= Table()->VBorderSpacing();
}
if (!row_is_at_top_of_column)
return;
}
// We need to push this row to the next fragmentainer. If there are repeated
// table headers, we need to make room for those at the top of the next
// fragmentainer, above this row. Otherwise, this row will just go at the top
// of the next fragmentainer.
LayoutTableSection* header = Table()->Header();
if (row_object.IsFirstRowInSectionAfterHeader())
Table()->SetRowOffsetFromRepeatingHeader(LayoutUnit());
// Border spacing from the previous row has pushed this row just past the top
// of the page, so we must reposition it to the top of the page and avoid any
// repeating header.
if (row_is_at_top_of_column && offset_from_top_of_page)
pagination_strut -= offset_from_top_of_page.ToInt();
// If we have a header group we will paint it at the top of each page,
// move the rows down to accomodate it.
if (header && header != this)
pagination_strut += Table()->RowOffsetFromRepeatingHeader().ToInt();
row_object.SetPaginationStrut(LayoutUnit(pagination_strut));
// We have inserted a pagination strut before the row. Adjust the logical top
// and re-lay out. We no longer want to break inside the row, but rather
// *before* it. From the previous layout pass, there are most likely
// pagination struts inside some cell in this row that we need to get rid of.
row_object.SetLogicalTop(row_object.LogicalTop() + pagination_strut);
layouter.SetChildNeedsLayout(&row_object);
row_object.LayoutIfNeeded();
// It's very likely that re-laying out (and nuking pagination struts inside
// cells) gave us a new height.
row_object.SetLogicalHeight(LayoutUnit(LogicalHeightForRow(row_object)));
}
bool LayoutTableSection::IsRepeatingHeaderGroup() const {
if (GetPaginationBreakability() == LayoutBox::kAllowAnyBreaks)
return false;
// TODO(rhogan): Should we paint a header repeatedly if it's self-painting?
if (HasSelfPaintingLayer())
return false;
LayoutUnit page_height = Table()->PageLogicalHeightForOffset(LayoutUnit());
if (!page_height)
return false;
if (LogicalHeight() > page_height)
return false;
// If the first row of the section after the header group doesn't fit on the
// page, then don't repeat the header on each page.
// See https://drafts.csswg.org/css-tables-3/#repeated-headers
LayoutTableSection* section_below = Table()->SectionBelow(this);
if (!section_below)
return true;
if (LayoutTableRow* first_row = section_below->FirstRow()) {
if (first_row->PaginationStrut() ||
first_row->LogicalHeight() > page_height)
return false;
}
return true;
}
bool LayoutTableSection::MapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
VisualRectFlags flags) const {
if (ancestor == this)
return true;
// Repeating table headers are painted once per fragmentation page/column.
// This does not go through the regular fragmentation machinery, so we need
// special code to expand the invalidation rect to contain all positions of
// the header in all columns.
// Note that this is in flow thread coordinates, not visual coordinates. The
// enclosing LayoutFlowThread will convert to visual coordinates.
if (Table()->Header() == this && IsRepeatingHeaderGroup()) {
transform_state.Flatten();
FloatRect rect = transform_state.LastPlanarQuad().BoundingBox();
rect.SetHeight(Table()->LogicalHeight());
transform_state.SetQuad(FloatQuad(rect));
}
return LayoutTableBoxComponent::MapToVisualRectInAncestorSpaceInternal(
ancestor, transform_state, flags);
}
bool LayoutTableSection::PaintedOutputOfObjectHasNoEffectRegardlessOfSize()
const {
// LayoutTableSection paints background from columns.
if (Table()->HasColElements())
return false;
return LayoutTableBoxComponent::
PaintedOutputOfObjectHasNoEffectRegardlessOfSize();
}
} // namespace blink