mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 12:17:45 +00:00
LibWeb: Add support for table caption
Adds layout support and the CSS caption-side property.
This commit is contained in:
parent
656f72adc0
commit
940d9b98ae
15 changed files with 353 additions and 21 deletions
|
@ -35,6 +35,7 @@ public:
|
|||
static int font_weight() { return 400; }
|
||||
static CSS::FontVariant font_variant() { return CSS::FontVariant::Normal; }
|
||||
static CSS::Float float_() { return CSS::Float::None; }
|
||||
static CSS::CaptionSide caption_side() { return CSS::CaptionSide::Top; }
|
||||
static CSS::Clear clear() { return CSS::Clear::None; }
|
||||
static CSS::Clip clip() { return CSS::Clip::make_auto(); }
|
||||
static CSS::Cursor cursor() { return CSS::Cursor::Auto; }
|
||||
|
@ -220,6 +221,7 @@ class ComputedValues {
|
|||
public:
|
||||
AspectRatio aspect_ratio() const { return m_noninherited.aspect_ratio; }
|
||||
CSS::Float float_() const { return m_noninherited.float_; }
|
||||
CSS::CaptionSide caption_side() const { return m_inherited.caption_side; }
|
||||
CSS::Clear clear() const { return m_noninherited.clear; }
|
||||
CSS::Clip clip() const { return m_noninherited.clip; }
|
||||
CSS::Cursor cursor() const { return m_inherited.cursor; }
|
||||
|
@ -328,6 +330,7 @@ protected:
|
|||
float font_size { InitialValues::font_size() };
|
||||
int font_weight { InitialValues::font_weight() };
|
||||
CSS::FontVariant font_variant { InitialValues::font_variant() };
|
||||
CSS::CaptionSide caption_side { InitialValues::caption_side() };
|
||||
Color color { InitialValues::color() };
|
||||
Optional<Color> accent_color {};
|
||||
CSS::Cursor cursor { InitialValues::cursor() };
|
||||
|
@ -431,6 +434,7 @@ public:
|
|||
void set_font_size(float font_size) { m_inherited.font_size = font_size; }
|
||||
void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; }
|
||||
void set_font_variant(CSS::FontVariant font_variant) { m_inherited.font_variant = font_variant; }
|
||||
void set_caption_side(CSS::CaptionSide caption_side) { m_inherited.caption_side = caption_side; }
|
||||
void set_color(Color color) { m_inherited.color = color; }
|
||||
void set_clip(CSS::Clip const& clip) { m_noninherited.clip = clip; }
|
||||
void set_content(ContentData const& content) { m_noninherited.content = content; }
|
||||
|
|
|
@ -84,6 +84,10 @@
|
|||
"border-box",
|
||||
"content-box"
|
||||
],
|
||||
"caption-side": [
|
||||
"top",
|
||||
"bottom"
|
||||
],
|
||||
"clear": [
|
||||
"none",
|
||||
"left",
|
||||
|
|
|
@ -628,9 +628,8 @@
|
|||
"caption-side": {
|
||||
"inherited": true,
|
||||
"initial": "top",
|
||||
"valid-identifiers": [
|
||||
"bottom",
|
||||
"top"
|
||||
"valid-types": [
|
||||
"caption-side"
|
||||
]
|
||||
},
|
||||
"clear": {
|
||||
|
|
|
@ -523,6 +523,9 @@ ErrorOr<RefPtr<StyleValue const>> ResolvedCSSStyleDeclaration::style_value_for_p
|
|||
}
|
||||
case PropertyID::BoxSizing:
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().box_sizing()));
|
||||
case PropertyID::CaptionSide: {
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().caption_side()));
|
||||
}
|
||||
case PropertyID::Clear:
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().clear()));
|
||||
case PropertyID::Clip:
|
||||
|
|
|
@ -373,6 +373,12 @@ Optional<CSS::ImageRendering> StyleProperties::image_rendering() const
|
|||
return value_id_to_image_rendering(value->to_identifier());
|
||||
}
|
||||
|
||||
Optional<CSS::CaptionSide> StyleProperties::caption_side() const
|
||||
{
|
||||
auto value = property(CSS::PropertyID::CaptionSide);
|
||||
return value_id_to_caption_side(value->to_identifier());
|
||||
}
|
||||
|
||||
CSS::Clip StyleProperties::clip() const
|
||||
{
|
||||
auto value = property(CSS::PropertyID::Clip);
|
||||
|
|
|
@ -50,6 +50,7 @@ public:
|
|||
Color color_or_fallback(CSS::PropertyID, Layout::NodeWithStyle const&, Color fallback) const;
|
||||
Optional<CSS::TextAlign> text_align() const;
|
||||
Optional<CSS::TextJustify> text_justify() const;
|
||||
Optional<CSS::CaptionSide> caption_side() const;
|
||||
CSS::Clip clip() const;
|
||||
CSS::Display display() const;
|
||||
Optional<CSS::Float> float_() const;
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
virtual void determine_width_of_child(Box const&, AvailableSpace const&) override;
|
||||
virtual void determine_height_of_child(Box const&, AvailableSpace const&) override;
|
||||
|
||||
void resolve_vertical_box_model_metrics(Box const&);
|
||||
|
||||
private:
|
||||
CSSPixels compute_auto_height_for_block_level_element(Box const&, AvailableSpace const&);
|
||||
|
||||
|
@ -66,7 +68,6 @@ private:
|
|||
void layout_block_level_children(BlockContainer const&, LayoutMode, AvailableSpace const&);
|
||||
void layout_inline_children(BlockContainer const&, LayoutMode, AvailableSpace const&);
|
||||
|
||||
void resolve_vertical_box_model_metrics(Box const&);
|
||||
void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const&);
|
||||
void place_block_level_element_in_normal_flow_vertically(Box const&, CSSPixels y);
|
||||
|
||||
|
|
|
@ -533,6 +533,10 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
|
|||
if (float_.has_value())
|
||||
computed_values.set_float(float_.value());
|
||||
|
||||
auto caption_side = computed_style.caption_side();
|
||||
if (caption_side.has_value())
|
||||
computed_values.set_caption_side(caption_side.value());
|
||||
|
||||
auto clear = computed_style.clear();
|
||||
if (clear.has_value())
|
||||
computed_values.set_clear(clear.value());
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <LibWeb/DOM/Node.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLTableCellElement.h>
|
||||
#include <LibWeb/Layout/BlockFormattingContext.h>
|
||||
#include <LibWeb/Layout/Box.h>
|
||||
#include <LibWeb/Layout/InlineFormattingContext.h>
|
||||
#include <LibWeb/Layout/TableFormattingContext.h>
|
||||
|
@ -60,6 +61,34 @@ static void for_each_child_box_matching(Box const& parent, Matcher matcher, Call
|
|||
});
|
||||
}
|
||||
|
||||
CSSPixels TableFormattingContext::run_caption_layout(LayoutMode layout_mode, CSS::CaptionSide phase)
|
||||
{
|
||||
CSSPixels caption_height = 0;
|
||||
for (auto* child = table_box().first_child(); child; child = child->next_sibling()) {
|
||||
if (!child->display().is_table_caption() || child->computed_values().caption_side() != phase) {
|
||||
continue;
|
||||
}
|
||||
// The caption boxes are principal block-level boxes that retain their own content, padding, margin, and border areas,
|
||||
// and are rendered as normal block boxes inside the table wrapper box, as described in https://www.w3.org/TR/CSS22/tables.html#model
|
||||
auto caption_context = make<BlockFormattingContext>(m_state, *verify_cast<BlockContainer>(child), this);
|
||||
caption_context->run(table_box(), layout_mode, *m_available_space);
|
||||
VERIFY(child->is_box());
|
||||
auto const& child_box = static_cast<Box const&>(*child);
|
||||
// FIXME: Since caption only has inline children, BlockFormattingContext doesn't resolve the vertical metrics.
|
||||
// We need to do it manually here.
|
||||
caption_context->resolve_vertical_box_model_metrics(child_box);
|
||||
auto const& caption_state = m_state.get(child_box);
|
||||
if (phase == CSS::CaptionSide::Top) {
|
||||
m_state.get_mutable(table_box()).set_content_y(caption_state.margin_box_height());
|
||||
} else {
|
||||
m_state.get_mutable(child_box).set_content_y(
|
||||
m_state.get(table_box()).margin_box_height() + caption_state.margin_box_top());
|
||||
}
|
||||
caption_height += caption_state.margin_box_height();
|
||||
}
|
||||
return caption_height;
|
||||
}
|
||||
|
||||
void TableFormattingContext::calculate_row_column_grid(Box const& box)
|
||||
{
|
||||
// Implements https://html.spec.whatwg.org/multipage/tables.html#forming-a-table
|
||||
|
@ -224,6 +253,21 @@ void TableFormattingContext::compute_table_measures()
|
|||
}
|
||||
}
|
||||
|
||||
CSSPixels TableFormattingContext::compute_capmin()
|
||||
{
|
||||
// The caption width minimum (CAPMIN) is the largest of the table captions min-content contribution:
|
||||
// https://drafts.csswg.org/css-tables-3/#computing-the-table-width
|
||||
CSSPixels capmin = 0;
|
||||
for (auto* child = table_box().first_child(); child; child = child->next_sibling()) {
|
||||
if (!child->display().is_table_caption()) {
|
||||
continue;
|
||||
}
|
||||
VERIFY(child->is_box());
|
||||
capmin = max(calculate_min_content_width(static_cast<Box const&>(*child)), capmin);
|
||||
}
|
||||
return capmin;
|
||||
}
|
||||
|
||||
void TableFormattingContext::compute_table_width()
|
||||
{
|
||||
// https://drafts.csswg.org/css-tables-3/#computing-the-table-width
|
||||
|
@ -253,7 +297,7 @@ void TableFormattingContext::compute_table_width()
|
|||
}
|
||||
|
||||
// The used min-width of a table is the greater of the resolved min-width, CAPMIN, and GRIDMIN.
|
||||
auto used_min_width = grid_min;
|
||||
auto used_min_width = max(grid_min, compute_capmin());
|
||||
if (!computed_values.min_width().is_auto()) {
|
||||
used_min_width = max(used_min_width, computed_values.min_width().to_px(table_box(), width_of_table_wrapper_containing_block));
|
||||
}
|
||||
|
@ -616,7 +660,7 @@ void TableFormattingContext::position_row_boxes(CSSPixels& total_content_height)
|
|||
{
|
||||
auto const& table_state = m_state.get(table_box());
|
||||
|
||||
CSSPixels row_top_offset = table_state.border_top + table_state.padding_top;
|
||||
CSSPixels row_top_offset = table_state.offset.y() + table_state.padding_top;
|
||||
CSSPixels row_left_offset = table_state.border_left + table_state.padding_left;
|
||||
for (size_t y = 0; y < m_rows.size(); y++) {
|
||||
auto& row = m_rows[y];
|
||||
|
@ -655,7 +699,7 @@ void TableFormattingContext::position_row_boxes(CSSPixels& total_content_height)
|
|||
row_group_top_offset += row_group_height;
|
||||
});
|
||||
|
||||
total_content_height = max(row_top_offset, row_group_top_offset) - table_state.border_top - table_state.padding_top;
|
||||
total_content_height = max(row_top_offset, row_group_top_offset) - table_state.offset.y() - table_state.padding_top;
|
||||
}
|
||||
|
||||
void TableFormattingContext::position_cell_boxes()
|
||||
|
@ -727,6 +771,8 @@ void TableFormattingContext::run(Box const& box, LayoutMode layout_mode, Availab
|
|||
{
|
||||
m_available_space = available_space;
|
||||
|
||||
auto total_captions_height = run_caption_layout(layout_mode, CSS::CaptionSide::Top);
|
||||
|
||||
CSSPixels total_content_height = 0;
|
||||
|
||||
// Determine the number of rows/columns the table requires.
|
||||
|
@ -755,6 +801,13 @@ void TableFormattingContext::run(Box const& box, LayoutMode layout_mode, Availab
|
|||
|
||||
m_state.get_mutable(table_box()).set_content_height(total_content_height);
|
||||
|
||||
total_captions_height += run_caption_layout(layout_mode, CSS::CaptionSide::Bottom);
|
||||
|
||||
// Table captions are positioned between the table margins and its borders (outside the grid box borders) as described in
|
||||
// https://www.w3.org/TR/css-tables-3/#bounding-box-assignment
|
||||
// A visual representation of this model can be found at https://www.w3.org/TR/css-tables-3/images/table_container.png
|
||||
m_state.get_mutable(table_box()).margin_bottom += total_captions_height;
|
||||
|
||||
// FIXME: This is a total hack, we should respect the 'height' property.
|
||||
m_automatic_content_height = total_content_height;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
CSSPixels run_caption_layout(LayoutMode, CSS::CaptionSide);
|
||||
CSSPixels compute_capmin();
|
||||
void calculate_row_column_grid(Box const&);
|
||||
void compute_table_measures();
|
||||
void compute_table_width();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue