diff --git a/Tests/LibWeb/Layout/expected/grid/fit-content-1.txt b/Tests/LibWeb/Layout/expected/grid/fit-content-1.txt new file mode 100644 index 00000000000..a9b9b0b780b --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/fit-content-1.txt @@ -0,0 +1,21 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x17 children: not-inline + Box at (8,8) content-size 784x17 [GFC] children: not-inline + BlockContainer at (8,8) content-size 6.34375x17 [BFC] children: inline + frag 0 from TextNode start: 0, length: 1, rect: [8,8 6.34375x17] baseline: 13.296875 + "1" + TextNode <#text> + BlockContainer at (14.34375,8) content-size 8.8125x17 [BFC] children: inline + frag 0 from TextNode start: 0, length: 1, rect: [14.34375,8 8.8125x17] baseline: 13.296875 + "2" + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x17] + PaintableBox (Box
.container) [8,8 784x17] + PaintableWithLines (BlockContainer
.one) [8,8 6.34375x17] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
.two) [14.34375,8 8.8125x17] + TextPaintable (TextNode<#text>) \ No newline at end of file diff --git a/Tests/LibWeb/Layout/expected/grid/fit-content-2.txt b/Tests/LibWeb/Layout/expected/grid/fit-content-2.txt new file mode 100644 index 00000000000..9d3d224e50c --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/fit-content-2.txt @@ -0,0 +1,46 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x200 children: not-inline + Box at (18,18) content-size 764x180 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (23,23) content-size 156.984375x170 [BFC] children: inline + frag 0 from TextNode start: 0, length: 15, rect: [23,23 126.109375x17] baseline: 13.296875 + "Item as wide as" + frag 1 from TextNode start: 16, length: 12, rect: [23,40 98.125x17] baseline: 13.296875 + "the content." + TextNode <#text> + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (194.984375,23) content-size 290x170 [BFC] children: inline + frag 0 from TextNode start: 1, length: 34, rect: [194.984375,23 278.625x17] baseline: 13.296875 + "Item with more text in it. Because" + frag 1 from TextNode start: 36, length: 33, rect: [194.984375,40 274.53125x17] baseline: 13.296875 + "the contents of it are wider than" + frag 2 from TextNode start: 70, length: 35, rect: [194.984375,57 289.90625x17] baseline: 13.296875 + "the maximum width, it is clamped at" + frag 3 from TextNode start: 106, length: 11, rect: [194.984375,74 86.609375x17] baseline: 13.296875 + "300 pixels." + TextNode <#text> + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (499.984375,23) content-size 277.015625x170 [BFC] children: inline + frag 0 from TextNode start: 0, length: 13, rect: [499.984375,23 102.53125x17] baseline: 13.296875 + "Flexible item" + TextNode <#text> + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,208) content-size 784x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x200] + PaintableBox (Box
#container) [8,8 784x200] + PaintableWithLines (BlockContainer
) [18,18 166.984375x180] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
) [189.984375,18 300x180] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
) [494.984375,18 287.015625x180] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,208 784x0] \ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/grid/fit-content-1.html b/Tests/LibWeb/Layout/input/grid/fit-content-1.html new file mode 100644 index 00000000000..22c108ce069 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/fit-content-1.html @@ -0,0 +1,15 @@ +
1
2
\ No newline at end of file diff --git a/Tests/LibWeb/Layout/input/grid/fit-content-2.html b/Tests/LibWeb/Layout/input/grid/fit-content-2.html new file mode 100644 index 00000000000..98a2975f908 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/fit-content-2.html @@ -0,0 +1,25 @@ +
+
Item as wide as the content.
+
+Item with more text in it. Because the contents of it are wider than the +maximum width, it is clamped at 300 pixels. +
+
Flexible item
+
+ diff --git a/Userland/Libraries/LibWeb/CSS/GridTrackSize.cpp b/Userland/Libraries/LibWeb/CSS/GridTrackSize.cpp index c84775dc7be..4076d614f08 100644 --- a/Userland/Libraries/LibWeb/CSS/GridTrackSize.cpp +++ b/Userland/Libraries/LibWeb/CSS/GridTrackSize.cpp @@ -10,6 +10,13 @@ namespace Web::CSS { +GridSize::GridSize(Type type, LengthPercentage length_percentage) + : m_value(move(length_percentage)) +{ + VERIFY(type == Type::FitContent); + m_type = type; +} + GridSize::GridSize(LengthPercentage length_percentage) : m_type(Type::LengthPercentage) , m_value(move(length_percentage)) @@ -63,7 +70,7 @@ bool GridSize::is_fixed(Layout::AvailableSize const& available_size) const bool GridSize::is_intrinsic(Layout::AvailableSize const& available_size) const { - return is_auto(available_size) || is_max_content() || is_min_content(); + return is_auto(available_size) || is_max_content() || is_min_content() || is_fit_content(); } GridSize GridSize::make_auto() @@ -73,7 +80,7 @@ GridSize GridSize::make_auto() Size GridSize::css_size() const { - VERIFY(m_type == Type::LengthPercentage); + VERIFY(m_type == Type::LengthPercentage || m_type == Type::FitContent); auto& length_percentage = m_value.get(); if (length_percentage.is_auto()) return CSS::Size::make_auto(); @@ -88,6 +95,7 @@ String GridSize::to_string() const { switch (m_type) { case Type::LengthPercentage: + case Type::FitContent: return m_value.get().to_string(); case Type::FlexibleLength: return m_value.get().to_string(); @@ -116,6 +124,21 @@ String GridMinMax::to_string() const return MUST(builder.to_string()); } +GridFitContent::GridFitContent(GridSize max_grid_size) + : m_max_grid_size(max_grid_size) +{ +} + +GridFitContent::GridFitContent() + : m_max_grid_size(GridSize::make_auto()) +{ +} + +String GridFitContent::to_string() const +{ + return MUST(String::formatted("fit-content({})", m_max_grid_size.to_string())); +} + GridRepeat::GridRepeat(GridTrackSizeList grid_track_size_list, int repeat_count) : m_type(Type::Default) , m_grid_track_size_list(grid_track_size_list) @@ -156,6 +179,12 @@ String GridRepeat::to_string() const return MUST(builder.to_string()); } +ExplicitGridTrack::ExplicitGridTrack(CSS::GridFitContent grid_fit_content) + : m_type(Type::FitContent) + , m_grid_fit_content(grid_fit_content) +{ +} + ExplicitGridTrack::ExplicitGridTrack(CSS::GridMinMax grid_minmax) : m_type(Type::MinMax) , m_grid_minmax(grid_minmax) @@ -177,6 +206,8 @@ ExplicitGridTrack::ExplicitGridTrack(CSS::GridSize grid_size) String ExplicitGridTrack::to_string() const { switch (m_type) { + case Type::FitContent: + return m_grid_fit_content.to_string(); case Type::MinMax: return m_grid_minmax.to_string(); case Type::Repeat: diff --git a/Userland/Libraries/LibWeb/CSS/GridTrackSize.h b/Userland/Libraries/LibWeb/CSS/GridTrackSize.h index 0e23027ca9a..9fbc30dd2e6 100644 --- a/Userland/Libraries/LibWeb/CSS/GridTrackSize.h +++ b/Userland/Libraries/LibWeb/CSS/GridTrackSize.h @@ -17,10 +17,12 @@ public: enum class Type { LengthPercentage, FlexibleLength, + FitContent, MaxContent, MinContent, }; + GridSize(Type, LengthPercentage); GridSize(LengthPercentage); GridSize(Flex); GridSize(Type); @@ -34,6 +36,7 @@ public: bool is_auto(Layout::AvailableSize const&) const; bool is_fixed(Layout::AvailableSize const&) const; bool is_flexible_length() const { return m_type == Type::FlexibleLength; } + bool is_fit_content() const { return m_type == Type::FitContent; } bool is_max_content() const { return m_type == Type::MaxContent; } bool is_min_content() const { return m_type == Type::MinContent; } @@ -42,7 +45,6 @@ public: // https://www.w3.org/TR/css-grid-2/#layout-algorithm // An intrinsic sizing function (min-content, max-content, auto, fit-content()). - // FIXME: Add missing properties once implemented. bool is_intrinsic(Layout::AvailableSize const&) const; bool is_definite() const @@ -64,6 +66,23 @@ private: Variant m_value; }; +class GridFitContent { +public: + GridFitContent(GridSize); + GridFitContent(); + + GridSize max_grid_size() const& { return m_max_grid_size; } + + String to_string() const; + bool operator==(GridFitContent const& other) const + { + return m_max_grid_size == other.m_max_grid_size; + } + +private: + GridSize m_max_grid_size; +}; + class GridMinMax { public: GridMinMax(CSS::GridSize min_grid_size, CSS::GridSize max_grid_size); @@ -149,14 +168,23 @@ private: class ExplicitGridTrack { public: enum class Type { + FitContent, MinMax, Repeat, Default, }; + ExplicitGridTrack(CSS::GridFitContent); ExplicitGridTrack(CSS::GridRepeat); ExplicitGridTrack(CSS::GridMinMax); ExplicitGridTrack(CSS::GridSize); + bool is_fit_content() const { return m_type == Type::FitContent; } + GridFitContent fit_content() const + { + VERIFY(is_fit_content()); + return m_grid_fit_content; + } + bool is_repeat() const { return m_type == Type::Repeat; } GridRepeat repeat() const { @@ -183,6 +211,8 @@ public: String to_string() const; bool operator==(ExplicitGridTrack const& other) const { + if (is_fit_content() && other.is_fit_content()) + return m_grid_fit_content == other.fit_content(); if (is_repeat() && other.is_repeat()) return m_grid_repeat == other.repeat(); if (is_minmax() && other.is_minmax()) @@ -194,6 +224,7 @@ public: private: Type m_type; + GridFitContent m_grid_fit_content; GridRepeat m_grid_repeat; GridMinMax m_grid_minmax; GridSize m_grid_size; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 547febc70b2..4215e474059 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -5910,6 +5910,21 @@ Optional Parser::parse_grid_size(ComponentValue const& component_ return {}; } +Optional Parser::parse_fit_content(Vector const& component_values) +{ + // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-fit-content + // 'fit-content( )' + // Represents the formula max(minimum, min(limit, max-content)), where minimum represents an auto minimum (which is often, but not always, + // equal to a min-content minimum), and limit is the track sizing function passed as an argument to fit-content(). + // This is essentially calculated as the smaller of minmax(auto, max-content) and minmax(auto, limit). + auto function_tokens = TokenStream(component_values); + function_tokens.skip_whitespace(); + auto maybe_length_percentage = parse_length_percentage(function_tokens); + if (maybe_length_percentage.has_value()) + return CSS::GridFitContent(CSS::GridSize(CSS::GridSize::Type::FitContent, maybe_length_percentage.value())); + return {}; +} + Optional Parser::parse_min_max(Vector const& component_values) { // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax @@ -6067,6 +6082,11 @@ Optional Parser::parse_track_sizing_function(ComponentVa return CSS::ExplicitGridTrack(maybe_min_max_value.value()); else return {}; + } else if (function_token.name().equals_ignoring_ascii_case("fit-content"sv)) { + auto maybe_fit_content_value = parse_fit_content(function_token.values()); + if (maybe_fit_content_value.has_value()) + return CSS::ExplicitGridTrack(maybe_fit_content_value.value()); + return {}; } else if (auto maybe_calculated = parse_calculated_value(token)) { return CSS::ExplicitGridTrack(GridSize(LengthPercentage(maybe_calculated.release_nonnull()))); } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 320ba396a26..00f60bf8269 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -250,6 +250,7 @@ private: Optional parse_unicode_range(StringView); Vector parse_unicode_ranges(TokenStream&); Optional parse_grid_size(ComponentValue const&); + Optional parse_fit_content(Vector const&); Optional parse_min_max(Vector const&); Optional parse_repeat(Vector const&); Optional parse_track_sizing_function(ComponentValue const&); diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index e72ebfd9eda..5b9368cbab9 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -137,6 +137,7 @@ class FrequencyOrCalculated; class FrequencyPercentage; class FrequencyStyleValue; class GridAutoFlowStyleValue; +class GridFitContent; class GridMinMax; class GridRepeat; class GridSize; diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp index de99f8e65af..648e3274989 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -17,6 +17,13 @@ GridFormattingContext::GridTrack GridFormattingContext::GridTrack::create_from_d // NOTE: repeat() is expected to be expanded beforehand. VERIFY(!definition.is_repeat()); + if (definition.is_fit_content()) { + return GridTrack { + .min_track_sizing_function = CSS::GridSize::make_auto(), + .max_track_sizing_function = definition.fit_content().max_grid_size(), + }; + } + if (definition.is_minmax()) { return GridTrack { .min_track_sizing_function = definition.minmax().min_grid_size(), @@ -422,6 +429,7 @@ void GridFormattingContext::initialize_grid_tracks_from_definition(GridDimension for (auto _ = 0; _ < repeat_count; _++) { switch (track_definition.type()) { case CSS::ExplicitGridTrack::Type::Default: + case CSS::ExplicitGridTrack::Type::FitContent: case CSS::ExplicitGridTrack::Type::MinMax: tracks.append(GridTrack::create_from_definition(track_definition)); break; @@ -852,13 +860,18 @@ void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossin // 6. For max-content maximums: Lastly continue to increase the growth limit of tracks with a max track // sizing function of max-content by distributing extra space as needed to account for these items' max- - // content contributions. + // content contributions. However, limit the growth of any fit-content() tracks by their fit-content() argument. auto item_max_content_contribution = calculate_max_content_contribution(item, dimension); distribute_extra_space_across_spanned_tracks_growth_limit(item_max_content_contribution, spanned_tracks, [&](GridTrack const& track) { - return track.max_track_sizing_function.is_max_content() || track.max_track_sizing_function.is_auto(available_size); + return track.max_track_sizing_function.is_max_content() || track.max_track_sizing_function.is_auto(available_size) || track.max_track_sizing_function.is_fit_content(); }); for (auto& track : spanned_tracks) { - if (!track.growth_limit.has_value()) { + if (track.max_track_sizing_function.is_fit_content()) { + track.growth_limit = css_clamp( + track.planned_increase, + track.base_size, + track.max_track_sizing_function.css_size().to_px(grid_container(), available_size.to_px_or_zero())); + } else if (!track.growth_limit.has_value()) { // If the affected size is an infinite growth limit, set it to the track’s base size plus the planned increase. track.growth_limit = track.base_size + track.planned_increase; } else { @@ -2039,7 +2052,7 @@ void GridFormattingContext::init_grid_lines(GridDimension dimension) line_names.extend(item.get().names); } else if (item.has()) { auto const& explicit_track = item.get(); - if (explicit_track.is_default() || explicit_track.is_minmax()) { + if (explicit_track.is_default() || explicit_track.is_minmax() || explicit_track.is_fit_content()) { lines.append({ .names = line_names }); line_names.clear(); } else if (explicit_track.is_repeat()) {