Commit 1f9589cf authored by Aleks Totic's avatar Aleks Totic Committed by Commit Bot

[TablesNG] Fix baseline computation

This rewrite fixes several bugs:
- if no cell's are baseline aligned, baseline should be
  bottom content edge of non-baseline aligned cells.
- if row is empty, and has css block size, its baseline
should be top of the row.

Interesting incompatibility with FF: FF applies css height
only if at least one row is non-empty.

I also have exhaustive baseline tests that are not part
of this CL. I can add if you'd like:

external/wpt/css/css-tables/tentative/baseline-table.html

Bug: 958381, 1143297
Change-Id: I5e6fe3d6e96f8da9d5305e4366111ee303918ea3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2515266
Commit-Queue: Aleks Totic <atotic@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823877}
parent 293df2bb
...@@ -118,15 +118,13 @@ NGTableTypes::Row ComputeMinimumRowBlockSize( ...@@ -118,15 +118,13 @@ NGTableTypes::Row ComputeMinimumRowBlockSize(
// This cannot be done today, because fragments with frozen scrollbars // This cannot be done today, because fragments with frozen scrollbars
// will be cached. Needs to be fixed in NG framework. // will be cached. Needs to be fixed in NG framework.
base::Optional<LayoutUnit> max_baseline; LayoutUnit max_cell_block_size;
LayoutUnit max_descent;
LayoutUnit row_block_size;
base::Optional<float> row_percent; base::Optional<float> row_percent;
bool is_constrained = false; bool is_constrained = false;
bool is_empty = true; bool is_empty = true;
bool baseline_depends_on_percentage_block_size_descendant = false;
bool has_rowspan_start = false; bool has_rowspan_start = false;
wtf_size_t start_cell_index = cell_block_constraints->size(); wtf_size_t start_cell_index = cell_block_constraints->size();
NGRowBaselineTabulator row_baseline_tabulator;
// Gather block sizes of all cells. // Gather block sizes of all cells.
for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell; for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell;
...@@ -147,27 +145,21 @@ NGTableTypes::Row ComputeMinimumRowBlockSize( ...@@ -147,27 +145,21 @@ NGTableTypes::Row ComputeMinimumRowBlockSize(
bool is_parallel = bool is_parallel =
IsParallelWritingMode(table_writing_direction.GetWritingMode(), IsParallelWritingMode(table_writing_direction.GetWritingMode(),
cell.Style().GetWritingMode()); cell.Style().GetWritingMode());
LayoutUnit baseline;
// https://www.w3.org/TR/css-tables-3/#row-layout "If there is no such
// line box or table-row, the baseline is the bottom of content edge of
// the cell box."
// Only baseline-aligned cells contribute to row baseline.
if (is_parallel &&
NGTableAlgorithmUtils::IsBaseline(cell_style.VerticalAlign())) {
if (layout_result->HasDescendantThatDependsOnPercentageBlockSize())
baseline_depends_on_percentage_block_size_descendant = true;
baseline = fragment.FirstBaselineOrSynthesize();
max_baseline = std::max(max_baseline.value_or(LayoutUnit()), baseline);
}
const wtf_size_t rowspan = cell.TableCellRowspan(); const wtf_size_t rowspan = cell.TableCellRowspan();
NGTableTypes::CellBlockConstraint cell_block_constraint = NGTableTypes::CellBlockConstraint cell_block_constraint =
NGTableTypes::CreateCellBlockConstraint( NGTableTypes::CreateCellBlockConstraint(
cell, fragment.BlockSize(), baseline, cell_borders, row_index, cell, fragment.BlockSize(), fragment.FirstBaselineOrSynthesize(),
colspan_cell_tabulator->CurrentColumn(), rowspan); cell_borders, row_index, colspan_cell_tabulator->CurrentColumn(),
rowspan);
colspan_cell_tabulator->ProcessCell(cell); colspan_cell_tabulator->ProcessCell(cell);
cell_block_constraints->push_back(cell_block_constraint); cell_block_constraints->push_back(cell_block_constraint);
is_constrained |= cell_block_constraint.is_constrained && rowspan == 1; is_constrained |= cell_block_constraint.is_constrained && rowspan == 1;
row_baseline_tabulator.ProcessCell(
fragment, cell_block_constraint.min_block_size,
NGTableAlgorithmUtils::IsBaseline(cell_style.VerticalAlign()),
is_parallel,
layout_result->HasDescendantThatDependsOnPercentageBlockSize());
// Compute cell's css block size. // Compute cell's css block size.
base::Optional<LayoutUnit> cell_css_block_size; base::Optional<LayoutUnit> cell_css_block_size;
...@@ -202,46 +194,42 @@ NGTableTypes::Row ComputeMinimumRowBlockSize( ...@@ -202,46 +194,42 @@ NGTableTypes::Row ComputeMinimumRowBlockSize(
row_percent = std::max(row_percent.value_or(0), *cell_css_percent); row_percent = std::max(row_percent.value_or(0), *cell_css_percent);
// Cell's block layout ignores CSS block size properties. Row must use it // Cell's block layout ignores CSS block size properties. Row must use it
// to compute it's minimum block size. // to compute it's minimum block size.
if (cell_css_block_size) max_cell_block_size =
row_block_size = std::max(row_block_size, *cell_css_block_size); std::max({max_cell_block_size, cell_block_constraint.min_block_size,
if (NGTableAlgorithmUtils::IsBaseline( cell_css_block_size.value_or(LayoutUnit())});
cell_block_constraint.vertical_align)) {
max_descent = std::max(max_descent,
cell_block_constraint.min_block_size - baseline);
row_block_size = std::max(
row_block_size, max_baseline.value_or(LayoutUnit()) + max_descent);
} else {
row_block_size =
std::max(row_block_size, cell_block_constraint.min_block_size);
}
} else { } else {
has_rowspan_start = true; has_rowspan_start = true;
rowspan_cells->push_back(NGTableTypes::CreateRowspanCell( rowspan_cells->push_back(NGTableTypes::CreateRowspanCell(
row_index, rowspan, &cell_block_constraint, cell_css_block_size)); row_index, rowspan, &cell_block_constraint, cell_css_block_size));
} }
} }
// Apply row's CSS block size. // Apply row's CSS block size.
if (!is_empty) { const Length& row_specified_block_length = row.Style().LogicalHeight();
const Length& row_specified_block_length = row.Style().LogicalHeight(); if (row_specified_block_length.IsPercent()) {
if (row_specified_block_length.IsPercent()) { is_constrained = true;
is_constrained = true; row_percent =
row_percent = std::max(row_percent.value_or(0), std::max(row_percent.value_or(0), row_specified_block_length.Percent());
row_specified_block_length.Percent()); } else if (row_specified_block_length.IsFixed()) {
} else if (row_specified_block_length.IsFixed()) { is_constrained = true;
is_constrained = true; max_cell_block_size = std::max(
row_block_size = std::max(LayoutUnit(row_specified_block_length.Value()), LayoutUnit(row_specified_block_length.Value()), max_cell_block_size);
row_block_size);
}
} }
const LayoutUnit row_block_size =
row_baseline_tabulator.ComputeRowBlockSize(max_cell_block_size);
const LayoutUnit row_baseline =
row_baseline_tabulator.ComputeBaseline(row_block_size);
return NGTableTypes::Row{ return NGTableTypes::Row{
row_block_size, row_block_size,
max_baseline.value_or(row_block_size), row_baseline,
row_percent, row_percent,
start_cell_index, start_cell_index,
cell_block_constraints->size() - start_cell_index, cell_block_constraints->size() - start_cell_index,
is_constrained, is_constrained,
baseline_depends_on_percentage_block_size_descendant, row_baseline_tabulator
.ComputeBaselineDependsOnPercentageBlockDescendant(),
has_rowspan_start, has_rowspan_start,
/* is_collapsed */ is_section_collapsed || /* is_collapsed */ is_section_collapsed ||
row.Style().Visibility() == EVisibility::kCollapse}; row.Style().Visibility() == EVisibility::kCollapse};
...@@ -577,4 +565,62 @@ void NGColspanCellTabulator::ProcessCell(const NGBlockNode& cell) { ...@@ -577,4 +565,62 @@ void NGColspanCellTabulator::ProcessCell(const NGBlockNode& cell) {
current_column_ += colspan; current_column_ += colspan;
} }
void NGRowBaselineTabulator::ProcessCell(
const NGBoxFragment& fragment,
const LayoutUnit cell_min_block_size,
const bool is_baseline_aligned,
const bool is_parallel,
const bool descendant_depends_on_percentage_block_size) {
if (is_parallel && is_baseline_aligned) {
max_cell_baseline_depends_on_percentage_block_descendant_ |=
descendant_depends_on_percentage_block_size;
const LayoutUnit cell_baseline = fragment.FirstBaselineOrSynthesize();
max_cell_ascent_ =
std::max(max_cell_ascent_.value_or(LayoutUnit::Min()), cell_baseline);
max_cell_descent_ = std::max(max_cell_descent_.value_or(LayoutUnit::Min()),
cell_min_block_size - cell_baseline);
}
// https://www.w3.org/TR/css-tables-3/#row-layout "If there is no such
// line box or table-row, the baseline is the bottom of content edge of
// the cell box."
if (!max_cell_ascent_) {
fallback_cell_depends_on_percentage_block_descendant_ |=
descendant_depends_on_percentage_block_size;
const LayoutUnit cell_block_end_border_padding =
fragment.Padding().block_end + fragment.Borders().block_end;
fallback_cell_descent_ =
std::min(fallback_cell_descent_.value_or(LayoutUnit::Max()),
cell_block_end_border_padding);
}
}
LayoutUnit NGRowBaselineTabulator::ComputeRowBlockSize(
const LayoutUnit max_cell_block_size) {
if (max_cell_ascent_) {
return std::max(max_cell_block_size,
*max_cell_ascent_ + *max_cell_descent_);
}
return max_cell_block_size;
}
LayoutUnit NGRowBaselineTabulator::ComputeBaseline(
const LayoutUnit row_block_size) {
if (max_cell_ascent_)
return *max_cell_ascent_;
if (fallback_cell_descent_)
return (row_block_size - *fallback_cell_descent_).ClampNegativeToZero();
// Empty row's baseline is top.
return LayoutUnit();
}
bool NGRowBaselineTabulator::
ComputeBaselineDependsOnPercentageBlockDescendant() {
if (max_cell_ascent_)
return max_cell_baseline_depends_on_percentage_block_descendant_;
if (fallback_cell_descent_)
return fallback_cell_depends_on_percentage_block_descendant_;
return false;
}
} // namespace blink } // namespace blink
...@@ -10,11 +10,12 @@ ...@@ -10,11 +10,12 @@
namespace blink { namespace blink {
struct LogicalSize; class NGBlockNode;
enum class NGCacheSlot; class NGBoxFragment;
class NGConstraintSpace; class NGConstraintSpace;
class NGTableBorders; class NGTableBorders;
class NGBlockNode; enum class NGCacheSlot;
struct LogicalSize;
// Table size distribution algorithms. // Table size distribution algorithms.
class NGTableAlgorithmUtils { class NGTableAlgorithmUtils {
...@@ -102,6 +103,36 @@ class NGColspanCellTabulator { ...@@ -102,6 +103,36 @@ class NGColspanCellTabulator {
Vector<Cell> colspanned_cells_; Vector<Cell> colspanned_cells_;
}; };
// NGRowBaselineTabulator computes baseline information for row.
// Standard: https://www.w3.org/TR/css-tables-3/#row-layout
// Baseline is either max-baseline of baseline-aligned cells,
// or bottom content edge of non-baseline-aligned cells.
class NGRowBaselineTabulator {
public:
void ProcessCell(const NGBoxFragment& fragment,
const LayoutUnit cell_min_block_size,
bool is_baseline_aligned,
bool is_parallel,
bool descendant_depends_on_percentage_block_size);
LayoutUnit ComputeRowBlockSize(const LayoutUnit max_cell_block_size);
LayoutUnit ComputeBaseline(const LayoutUnit row_block_size);
bool ComputeBaselineDependsOnPercentageBlockDescendant();
private:
// Cell baseline is computed from baseline-aligned cells.
base::Optional<LayoutUnit> max_cell_ascent_;
base::Optional<LayoutUnit> max_cell_descent_;
bool max_cell_baseline_depends_on_percentage_block_descendant_ = false;
// Non-baseline aligned cells are used to compute baseline if baseline
// cells are not available.
base::Optional<LayoutUnit> fallback_cell_descent_;
bool fallback_cell_depends_on_percentage_block_descendant_ = false;
};
} // namespace blink } // namespace blink
WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS( WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(
......
...@@ -90,32 +90,33 @@ scoped_refptr<const NGLayoutResult> NGTableRowLayoutAlgorithm::Layout() { ...@@ -90,32 +90,33 @@ scoped_refptr<const NGLayoutResult> NGTableRowLayoutAlgorithm::Layout() {
LayoutUnit row_baseline = row.baseline; LayoutUnit row_baseline = row.baseline;
wtf_size_t cell_index = row.start_cell_index; wtf_size_t cell_index = row.start_cell_index;
if (row.has_baseline_aligned_percentage_block_size_descendants) { if (row.has_baseline_aligned_percentage_block_size_descendants) {
NGRowBaselineTabulator row_baseline_tabulator;
for (NGBlockNode cell = To<NGBlockNode>(Node().FirstChild()); cell; for (NGBlockNode cell = To<NGBlockNode>(Node().FirstChild()); cell;
cell = To<NGBlockNode>(cell.NextSibling()), ++cell_index) { cell = To<NGBlockNode>(cell.NextSibling()), ++cell_index) {
bool is_parallel = IsParallelWritingMode(table_writing_mode, bool is_parallel = IsParallelWritingMode(table_writing_mode,
cell.Style().GetWritingMode()); cell.Style().GetWritingMode());
if (!NGTableAlgorithmUtils::IsBaseline(cell.Style().VerticalAlign()) ||
!is_parallel)
continue;
wtf_size_t cell_location_start_column; wtf_size_t cell_location_start_column;
NGConstraintSpace cell_constraint_space = CreateCellConstraintSpace( NGConstraintSpace cell_constraint_space = CreateCellConstraintSpace(
cell, cell_index, base::nullopt, row.is_collapsed, cell, cell_index, base::nullopt, row.is_collapsed,
&cell_location_start_column); &cell_location_start_column);
scoped_refptr<const NGLayoutResult> layout_result = scoped_refptr<const NGLayoutResult> layout_result =
cell.Layout(cell_constraint_space); cell.Layout(cell_constraint_space);
NGBoxFragment fragment(
LayoutUnit baseline = table_data.table_writing_direction,
NGBoxFragment( To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()));
table_data.table_writing_direction, row_baseline_tabulator.ProcessCell(
To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment())) fragment, row.block_size,
.FirstBaselineOrSynthesize(); NGTableAlgorithmUtils::IsBaseline(cell.Style().VerticalAlign()),
row_baseline = std::max(row_baseline, baseline); is_parallel,
layout_result->HasDescendantThatDependsOnPercentageBlockSize());
} }
row_baseline = row_baseline_tabulator.ComputeBaseline(row.block_size);
} }
// Generate cell fragments. // Generate cell fragments.
base::Optional<LayoutUnit> reported_row_baseline;
cell_index = row.start_cell_index; cell_index = row.start_cell_index;
NGRowBaselineTabulator row_baseline_tabulator;
for (NGBlockNode cell = To<NGBlockNode>(Node().FirstChild()); cell; for (NGBlockNode cell = To<NGBlockNode>(Node().FirstChild()); cell;
cell = To<NGBlockNode>(cell.NextSibling()), ++cell_index) { cell = To<NGBlockNode>(cell.NextSibling()), ++cell_index) {
wtf_size_t cell_location_start_column; wtf_size_t cell_location_start_column;
...@@ -129,20 +130,20 @@ scoped_refptr<const NGLayoutResult> NGTableRowLayoutAlgorithm::Layout() { ...@@ -129,20 +130,20 @@ scoped_refptr<const NGLayoutResult> NGTableRowLayoutAlgorithm::Layout() {
table_data.table_border_spacing.inline_size, table_data.table_border_spacing.inline_size,
LayoutUnit()); LayoutUnit());
container_builder_.AddResult(*cell_result, cell_offset); container_builder_.AddResult(*cell_result, cell_offset);
NGBoxFragment fragment(
if (NGTableAlgorithmUtils::IsBaseline(cell.Style().VerticalAlign())) { table_data.table_writing_direction,
LayoutUnit baseline = To<NGPhysicalBoxFragment>(cell_result->PhysicalFragment()));
NGBoxFragment( bool is_parallel = IsParallelWritingMode(table_writing_mode,
table_data.table_writing_direction, cell.Style().GetWritingMode());
To<NGPhysicalBoxFragment>(cell_result->PhysicalFragment())) row_baseline_tabulator.ProcessCell(
.FirstBaselineOrSynthesize(); fragment, row.block_size,
reported_row_baseline = NGTableAlgorithmUtils::IsBaseline(cell.Style().VerticalAlign()),
std::max(reported_row_baseline.value_or(LayoutUnit::Min()), baseline); is_parallel,
} cell_result->HasDescendantThatDependsOnPercentageBlockSize());
} }
container_builder_.SetFragmentBlockSize(row.block_size); container_builder_.SetFragmentBlockSize(row.block_size);
container_builder_.SetBaseline( container_builder_.SetBaseline(
reported_row_baseline.value_or(row.block_size)); row_baseline_tabulator.ComputeBaseline(row.block_size));
if (row.is_collapsed) if (row.is_collapsed)
container_builder_.SetIsHiddenForPaint(true); container_builder_.SetIsHiddenForPaint(true);
container_builder_.SetIsTableNGPart(); container_builder_.SetIsTableNGPart();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment