ladybird/Libraries/LibWeb/Layout/TableFormattingContext.h
Tim Ledbetter 7d6ce58cf2 LibWeb: Constrain table caption available space to table's border-box
Previously, table captions were laid out using the parent's available
space before the table's width was calculated. This resulted in
captions not being properly constrained to the table's width.
2026-02-03 14:47:51 +01:00

195 lines
7 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibWeb/Layout/FormattingContext.h>
#include <LibWeb/Layout/TableGrid.h>
#include <LibWeb/Layout/TableWrapper.h>
namespace Web::Layout {
enum class TableDimension {
Row,
Column
};
class TableFormattingContext final : public FormattingContext {
public:
explicit TableFormattingContext(LayoutState&, LayoutMode, Box const&, FormattingContext* parent);
~TableFormattingContext();
void run_until_width_calculation(AvailableSpace const& available_space);
virtual void run(AvailableSpace const&) override;
virtual CSSPixels automatic_content_width() const override;
virtual CSSPixels automatic_content_height() const override;
StaticPositionRect calculate_static_position_rect(Box const&) const;
Box const& table_box() const { return context_box(); }
TableWrapper const& table_wrapper() const
{
return as<TableWrapper>(*table_box().containing_block());
}
static bool border_is_less_specific(CSS::BorderData const& a, CSS::BorderData const& b);
virtual void parent_context_did_dimension_child_root_box() override;
private:
CSSPixels run_caption_layout(CSS::CaptionSide, AvailableSpace const&);
CSSPixels compute_capmin();
void compute_constrainedness();
void compute_cell_measures();
void compute_outer_content_sizes();
template<class RowOrColumn>
void initialize_table_measures();
template<class RowOrColumn>
void compute_table_measures();
template<class RowOrColumn>
void compute_intrinsic_percentage(size_t max_cell_span);
void compute_table_width();
void distribute_width_to_columns();
void distribute_excess_width_to_columns(CSSPixels available_width);
void distribute_excess_width_to_columns_fixed_mode(CSSPixels excess_width);
void compute_table_height();
void distribute_height_to_rows();
void position_row_boxes();
void position_cell_boxes();
void border_conflict_resolution();
CSSPixels border_spacing_horizontal() const;
CSSPixels border_spacing_vertical() const;
void finish_grid_initialization(TableGrid const&);
CSSPixels compute_columns_total_used_width() const;
void commit_candidate_column_widths(Vector<CSSPixels> const& candidate_widths);
void assign_columns_width_linear_combination(Vector<CSSPixels> const& candidate_widths, CSSPixels available_width);
template<class ColumnFilter, class BaseWidthGetter>
bool distribute_excess_width_proportionally_to_base_width(CSSPixels excess_width, ColumnFilter column_filter, BaseWidthGetter base_width_getter);
template<class ColumnFilter>
bool distribute_excess_width_equally(CSSPixels excess_width, ColumnFilter column_filter);
template<class ColumnFilter>
bool distribute_excess_width_by_intrinsic_percentage(CSSPixels excess_width, ColumnFilter column_filter);
bool use_fixed_mode_layout() const;
CSSPixels m_table_height { 0 };
CSSPixels m_automatic_content_height { 0 };
Optional<AvailableSpace> m_available_space;
struct Column {
CSSPixels left_offset { 0 };
CSSPixels min_size { 0 };
CSSPixels max_size { 0 };
CSSPixels used_width { 0 };
bool has_intrinsic_percentage { false };
double intrinsic_percentage { 0 };
// Store whether the column is constrained: https://www.w3.org/TR/css-tables-3/#constrainedness
bool is_constrained { false };
// Store whether the column has originating cells, defined in https://www.w3.org/TR/css-tables-3/#terminology.
bool has_originating_cells { false };
};
using Cell = TableGrid::Cell;
using Row = TableGrid::Row;
// Accessors to enable direction-agnostic table measurement.
template<class RowOrColumn>
static size_t cell_span(Cell const& cell);
template<class RowOrColumn>
static size_t cell_index(Cell const& cell);
template<class RowOrColumn>
static CSSPixels cell_min_size(Cell const& cell);
template<class RowOrColumn>
static CSSPixels cell_max_size(Cell const& cell);
template<class RowOrColumn>
static double cell_percentage_contribution(Cell const& cell);
template<class RowOrColumn>
static bool cell_has_intrinsic_percentage(Cell const& cell);
template<class RowOrColumn>
void initialize_intrinsic_percentages_from_rows_or_columns();
template<class RowOrColumn>
void initialize_intrinsic_percentages_from_cells();
template<class RowOrColumn>
CSSPixels border_spacing();
template<class RowOrColumn>
Vector<RowOrColumn>& table_rows_or_columns();
CSSPixels compute_row_content_height(Cell const& cell) const;
enum class ConflictingSide {
Top,
Bottom,
Left,
Right,
};
struct ConflictingEdge {
GC::Ptr<Node const> element;
Painting::PaintableBox::ConflictingElementKind element_kind;
ConflictingSide side;
Optional<size_t> row;
Optional<size_t> column;
};
static TableFormattingContext::ConflictingEdge const& winning_conflicting_edge(TableFormattingContext::ConflictingEdge const& a, TableFormattingContext::ConflictingEdge const& b);
static CSS::BorderData const& border_data_conflicting_edge(ConflictingEdge const& conflicting_edge);
static Painting::PaintableBox::BorderDataWithElementKind const border_data_with_element_kind_from_conflicting_edge(ConflictingEdge const& conflicting_edge);
class BorderConflictFinder {
public:
BorderConflictFinder(TableFormattingContext const* context);
Vector<ConflictingEdge> conflicting_edges(Cell const&, ConflictingSide) const;
private:
void collect_conflicting_col_elements();
void collect_conflicting_row_group_elements();
void collect_cell_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
void collect_row_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
void collect_row_group_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
void collect_column_group_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
void collect_table_box_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
GC::Ptr<Node const> get_col_element(size_t index) const
{
if (index >= m_col_elements_by_index.size())
return {};
return m_col_elements_by_index[index];
}
struct RowGroupInfo {
GC::Ptr<Node const> row_group;
size_t start_index;
size_t row_count;
};
Vector<GC::Ptr<Node const>> m_col_elements_by_index;
Vector<Optional<RowGroupInfo>> m_row_group_elements_by_index;
TableFormattingContext const* m_context;
};
Vector<Cell> m_cells;
Vector<Vector<Optional<Cell const&>>> m_cells_by_coordinate;
Vector<Column> m_columns;
Vector<Row> m_rows;
};
}