2020-11-22 13:38:18 +01:00
/*
* Copyright ( c ) 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
# include <LibWeb/CSS/Length.h>
# include <LibWeb/DOM/Node.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/BlockBox.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/BlockFormattingContext.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Box.h>
# include <LibWeb/Layout/InitialContainingBlockBox.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/InlineFormattingContext.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/ListItemBox.h>
2021-02-10 08:58:26 +01:00
# include <LibWeb/Layout/ReplacedBox.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Page/Frame.h>
namespace Web : : Layout {
2020-11-25 20:08:52 +01:00
BlockFormattingContext : : BlockFormattingContext ( Box & context_box , FormattingContext * parent )
: FormattingContext ( context_box , parent )
2020-11-22 13:38:18 +01:00
{
}
BlockFormattingContext : : ~ BlockFormattingContext ( )
{
}
bool BlockFormattingContext : : is_initial ( ) const
{
2021-01-01 18:55:47 +01:00
return is < InitialContainingBlockBox > ( context_box ( ) ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : run ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
if ( is_initial ( ) ) {
layout_initial_containing_block ( layout_mode ) ;
return ;
}
2020-12-06 19:48:02 +01:00
// FIXME: BFC currently computes the width+height of the target box.
2020-11-22 13:38:18 +01:00
// This is necessary to be able to place absolutely positioned descendants.
// The same work is also done by the parent BFC for each of its blocks..
if ( layout_mode = = LayoutMode : : Default )
2020-12-06 19:48:02 +01:00
compute_width ( box ) ;
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
if ( box . children_are_inline ( ) ) {
layout_inline_children ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
} else {
2020-12-06 19:48:02 +01:00
layout_block_level_children ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-14 11:33:11 +01:00
if ( layout_mode = = LayoutMode : : Default ) {
2020-12-06 19:48:02 +01:00
compute_height ( box ) ;
2020-12-14 11:33:11 +01:00
box . for_each_child_of_type < Box > ( [ & ] ( auto & child_box ) {
if ( child_box . is_absolutely_positioned ( ) ) {
2021-01-06 18:18:46 +01:00
layout_absolutely_positioned_element ( child_box ) ;
2020-12-14 11:33:11 +01:00
}
return IterationDecision : : Continue ;
} ) ;
}
2020-11-22 13:38:18 +01:00
}
2020-11-22 15:53:01 +01:00
void BlockFormattingContext : : compute_width ( Box & box )
2020-11-22 13:38:18 +01:00
{
2021-01-06 18:18:46 +01:00
if ( box . is_absolutely_positioned ( ) ) {
compute_width_for_absolutely_positioned_element ( box ) ;
return ;
}
2021-01-01 18:55:47 +01:00
if ( is < ReplacedBox > ( box ) ) {
2020-11-22 15:53:01 +01:00
// FIXME: This should not be done *by* ReplacedBox
auto & replaced = downcast < ReplacedBox > ( box ) ;
2020-11-22 13:38:18 +01:00
replaced . prepare_for_replaced_layout ( ) ;
2020-12-11 22:27:09 +01:00
compute_width_for_block_level_replaced_element_in_normal_flow ( replaced ) ;
2020-11-22 13:38:18 +01:00
return ;
}
2020-12-06 01:08:14 +01:00
if ( box . is_floating ( ) ) {
compute_width_for_floating_box ( box ) ;
return ;
}
2021-01-06 10:34:31 +01:00
auto & computed_values = box . computed_values ( ) ;
2020-11-22 13:38:18 +01:00
float width_of_containing_block = box . width_of_logical_containing_block ( ) ;
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2021-01-06 10:34:31 +01:00
const auto padding_left = computed_values . padding ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
const auto padding_right = computed_values . padding ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
auto try_compute_width = [ & ] ( const auto & a_width ) {
CSS : : Length width = a_width ;
2021-01-06 10:34:31 +01:00
margin_left = computed_values . margin ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
margin_right = computed_values . margin ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
float total_px = computed_values . border_left ( ) . width + computed_values . border_right ( ) . width ;
2020-11-22 13:38:18 +01:00
for ( auto & value : { margin_left , padding_left , width , padding_right , margin_right } ) {
total_px + = value . to_px ( box ) ;
}
2020-12-11 22:31:29 +01:00
if ( ! box . is_inline ( ) ) {
2020-11-22 13:38:18 +01:00
// 10.3.3 Block-level, non-replaced elements in normal flow
// If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
if ( width . is_auto ( ) & & total_px > width_of_containing_block ) {
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
}
// 10.3.3 cont'd.
auto underflow_px = width_of_containing_block - total_px ;
if ( width . is_auto ( ) ) {
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
if ( underflow_px > = 0 ) {
width = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else {
width = zero_value ;
margin_right = CSS : : Length ( margin_right . to_px ( box ) + underflow_px , CSS : : Length : : Type : : Px ) ;
}
} else {
if ( ! margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_right = CSS : : Length ( margin_right . to_px ( box ) + underflow_px , CSS : : Length : : Type : : Px ) ;
} else if ( ! margin_left . is_auto ( ) & & margin_right . is_auto ( ) ) {
margin_right = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else if ( margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_left = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else { // margin_left.is_auto() && margin_right.is_auto()
auto half_of_the_underflow = CSS : : Length ( underflow_px / 2 , CSS : : Length : : Type : : Px ) ;
margin_left = half_of_the_underflow ;
margin_right = half_of_the_underflow ;
}
}
2020-12-11 22:31:29 +01:00
} else if ( box . is_inline_block ( ) ) {
2020-11-22 13:38:18 +01:00
// 10.3.9 'Inline-block', non-replaced elements in normal flow
// A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
// If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements.
if ( width . is_auto ( ) ) {
// Find the available width: in this case, this is the width of the containing
// block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
// 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
float available_width = width_of_containing_block
2021-01-06 10:34:31 +01:00
- margin_left . to_px ( box ) - computed_values . border_left ( ) . width - padding_left . to_px ( box )
- padding_right . to_px ( box ) - computed_values . border_right ( ) . width - margin_right . to_px ( box ) ;
2020-11-22 13:38:18 +01:00
auto result = calculate_shrink_to_fit_widths ( box ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
}
}
return width ;
} ;
2021-01-06 10:34:31 +01:00
auto specified_width = computed_values . width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = try_compute_width ( specified_width ) ;
// 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'.
2021-01-06 10:34:31 +01:00
auto specified_max_width = computed_values . max_width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
if ( ! specified_max_width . is_auto ( ) ) {
if ( used_width . to_px ( box ) > specified_max_width . to_px ( box ) ) {
used_width = try_compute_width ( specified_max_width ) ;
}
}
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'.
2021-01-06 10:34:31 +01:00
auto specified_min_width = computed_values . min_width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
if ( ! specified_min_width . is_auto ( ) ) {
if ( used_width . to_px ( box ) < specified_min_width . to_px ( box ) ) {
used_width = try_compute_width ( specified_min_width ) ;
}
}
box . set_width ( used_width . to_px ( box ) ) ;
2020-12-12 21:02:06 +01:00
box . box_model ( ) . margin . left = margin_left . to_px ( box ) ;
box . box_model ( ) . margin . right = margin_right . to_px ( box ) ;
2021-01-06 10:34:31 +01:00
box . box_model ( ) . border . left = computed_values . border_left ( ) . width ;
box . box_model ( ) . border . right = computed_values . border_right ( ) . width ;
2020-12-12 21:02:06 +01:00
box . box_model ( ) . padding . left = padding_left . to_px ( box ) ;
box . box_model ( ) . padding . right = padding_right . to_px ( box ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 01:08:14 +01:00
void BlockFormattingContext : : compute_width_for_floating_box ( Box & box )
{
// 10.3.5 Floating, non-replaced elements
2021-01-06 10:34:31 +01:00
auto & computed_values = box . computed_values ( ) ;
2020-12-06 01:08:14 +01:00
float width_of_containing_block = box . width_of_logical_containing_block ( ) ;
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2021-01-06 10:34:31 +01:00
const auto padding_left = computed_values . padding ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
const auto padding_right = computed_values . padding ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-12-06 01:08:14 +01:00
// If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
2021-01-06 10:34:31 +01:00
auto width = computed_values . width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-12-06 01:08:14 +01:00
// If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
if ( width . is_auto ( ) ) {
// Find the available width: in this case, this is the width of the containing
// block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
// 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
float available_width = width_of_containing_block
2021-01-06 10:34:31 +01:00
- margin_left . to_px ( box ) - computed_values . border_left ( ) . width - padding_left . to_px ( box )
- padding_right . to_px ( box ) - computed_values . border_right ( ) . width - margin_right . to_px ( box ) ;
2020-12-06 01:08:14 +01:00
auto result = calculate_shrink_to_fit_widths ( box ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
}
float final_width = width . resolved_or_zero ( box , width_of_containing_block ) . to_px ( box ) ;
box . set_width ( final_width ) ;
}
2020-12-11 22:27:09 +01:00
void BlockFormattingContext : : compute_width_for_block_level_replaced_element_in_normal_flow ( ReplacedBox & box )
{
box . set_width ( compute_width_for_replaced_element ( box ) ) ;
}
2021-03-08 22:49:34 +01:00
float BlockFormattingContext : : compute_auto_height_for_block_level_element ( const Box & box )
2020-12-11 22:27:09 +01:00
{
2021-03-08 22:49:34 +01:00
Optional < float > top ;
Optional < float > bottom ;
2020-12-11 22:27:09 +01:00
2021-03-08 22:49:34 +01:00
if ( box . children_are_inline ( ) ) {
// If it only has inline-level children, the height is the distance between
// the top of the topmost line box and the bottom of the bottommost line box.
if ( ! box . line_boxes ( ) . is_empty ( ) ) {
for ( auto & fragment : box . line_boxes ( ) . first ( ) . fragments ( ) ) {
if ( ! top . has_value ( ) | | fragment . offset ( ) . y ( ) < top . value ( ) )
top = fragment . offset ( ) . y ( ) ;
}
for ( auto & fragment : box . line_boxes ( ) . last ( ) . fragments ( ) ) {
if ( ! bottom . has_value ( ) | | ( fragment . offset ( ) . y ( ) + fragment . height ( ) ) > bottom . value ( ) )
bottom = fragment . offset ( ) . y ( ) + fragment . height ( ) ;
}
}
} else {
// If it has block-level children, the height is the distance between
// the top margin-edge of the topmost block-level child box
// and the bottom margin-edge of the bottommost block-level child box.
box . for_each_child_of_type < Box > ( [ & ] ( Layout : : Box & child_box ) {
if ( box . is_absolutely_positioned ( ) | | box . is_floating ( ) )
return IterationDecision : : Continue ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
float child_box_top = child_box . effective_offset ( ) . y ( ) - child_box . box_model ( ) . margin_box ( ) . top ;
float child_box_bottom = child_box . effective_offset ( ) . y ( ) + child_box . height ( ) + child_box . box_model ( ) . margin_box ( ) . bottom ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
if ( ! top . has_value ( ) | | child_box_top < top . value ( ) )
top = child_box_top ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
if ( ! bottom . has_value ( ) | | child_box_bottom > bottom . value ( ) )
bottom = child_box_bottom ;
return IterationDecision : : Continue ;
} ) ;
2020-11-22 13:38:18 +01:00
}
2021-03-08 22:49:34 +01:00
return bottom . value_or ( 0 ) - top . value_or ( 0 ) ;
}
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
void BlockFormattingContext : : compute_height ( Box & box )
{
auto & computed_values = box . computed_values ( ) ;
auto & containing_block = * box . containing_block ( ) ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
// First, resolve the top/bottom parts of the surrounding box model.
2021-01-06 10:34:31 +01:00
box . box_model ( ) . margin . top = computed_values . margin ( ) . top . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
box . box_model ( ) . margin . bottom = computed_values . margin ( ) . bottom . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
box . box_model ( ) . border . top = computed_values . border_top ( ) . width ;
box . box_model ( ) . border . bottom = computed_values . border_bottom ( ) . width ;
box . box_model ( ) . padding . top = computed_values . padding ( ) . top . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
box . box_model ( ) . padding . bottom = computed_values . padding ( ) . bottom . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
// Then work out what the height is, based on box type and CSS properties.
float height = 0 ;
if ( is < ReplacedBox > ( box ) ) {
height = compute_height_for_replaced_element ( downcast < ReplacedBox > ( box ) ) ;
} else {
if ( box . computed_values ( ) . height ( ) . is_undefined_or_auto ( ) ) {
height = compute_auto_height_for_block_level_element ( box ) ;
} else {
CSS : : Length specified_height ;
if ( computed_values . height ( ) . is_percentage ( ) & & ! containing_block . computed_values ( ) . height ( ) . is_absolute ( ) ) {
specified_height = CSS : : Length : : make_auto ( ) ;
} else {
specified_height = computed_values . height ( ) . resolved_or_auto ( box , containing_block . height ( ) ) ;
}
auto specified_max_height = computed_values . max_height ( ) . resolved_or_auto ( box , containing_block . height ( ) ) ;
if ( ! specified_height . is_auto ( ) ) {
float used_height = specified_height . to_px ( box ) ;
if ( ! specified_max_height . is_auto ( ) )
used_height = min ( used_height , specified_max_height . to_px ( box ) ) ;
height = used_height ;
}
}
2020-11-22 13:38:18 +01:00
}
2021-03-08 22:49:34 +01:00
box . set_height ( height ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_inline_children ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
2020-12-06 19:48:02 +01:00
InlineFormattingContext context ( box , this ) ;
context . run ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_block_level_children ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
float content_height = 0 ;
float content_width = 0 ;
2020-12-06 19:48:02 +01:00
box . for_each_child_of_type < Box > ( [ & ] ( auto & child_box ) {
2020-12-14 11:33:11 +01:00
if ( child_box . is_absolutely_positioned ( ) )
2020-12-06 19:48:02 +01:00
return IterationDecision : : Continue ;
2020-12-12 19:31:46 +01:00
if ( child_box . is_floating ( ) ) {
layout_floating_child ( child_box , box ) ;
2020-11-22 13:38:18 +01:00
return IterationDecision : : Continue ;
2020-12-12 19:31:46 +01:00
}
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
compute_width ( child_box ) ;
layout_inside ( child_box , layout_mode ) ;
compute_height ( child_box ) ;
2020-11-22 13:38:18 +01:00
2021-01-01 18:55:47 +01:00
if ( is < ReplacedBox > ( child_box ) )
2020-12-06 19:48:02 +01:00
place_block_level_replaced_element_in_normal_flow ( child_box , box ) ;
2021-01-01 18:55:47 +01:00
else if ( is < BlockBox > ( child_box ) )
2020-12-06 19:48:02 +01:00
place_block_level_non_replaced_element_in_normal_flow ( child_box , box ) ;
2020-11-22 13:38:18 +01:00
// FIXME: This should be factored differently. It's uncool that we mutate the tree *during* layout!
// Instead, we should generate the marker box during the tree build.
2020-12-06 19:48:02 +01:00
if ( is < ListItemBox > ( child_box ) )
downcast < ListItemBox > ( child_box ) . layout_marker ( ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
content_height = max ( content_height , child_box . effective_offset ( ) . y ( ) + child_box . height ( ) + child_box . box_model ( ) . margin_box ( ) . bottom ) ;
2020-12-06 19:48:02 +01:00
content_width = max ( content_width , downcast < Box > ( child_box ) . width ( ) ) ;
2020-11-22 13:38:18 +01:00
return IterationDecision : : Continue ;
} ) ;
if ( layout_mode ! = LayoutMode : : Default ) {
2021-01-06 10:34:31 +01:00
if ( box . computed_values ( ) . width ( ) . is_undefined ( ) | | box . computed_values ( ) . width ( ) . is_auto ( ) )
2020-12-06 19:48:02 +01:00
box . set_width ( content_width ) ;
2020-11-22 13:38:18 +01:00
}
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : place_block_level_replaced_element_in_normal_flow ( Box & child_box , Box & containing_block )
2020-11-22 13:38:18 +01:00
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! containing_block . is_absolutely_positioned ( ) ) ;
2020-12-06 19:48:02 +01:00
auto & replaced_element_box_model = child_box . box_model ( ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
replaced_element_box_model . margin . top = child_box . computed_values ( ) . margin ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . margin . bottom = child_box . computed_values ( ) . margin ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . border . top = child_box . computed_values ( ) . border_top ( ) . width ;
replaced_element_box_model . border . bottom = child_box . computed_values ( ) . border_bottom ( ) . width ;
replaced_element_box_model . padding . top = child_box . computed_values ( ) . padding ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . padding . bottom = child_box . computed_values ( ) . padding ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float x = replaced_element_box_model . margin . left
+ replaced_element_box_model . border . left
+ replaced_element_box_model . padding . left
+ replaced_element_box_model . offset . left ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float y = replaced_element_box_model . margin_box ( ) . top + containing_block . box_model ( ) . offset . top ;
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
child_box . set_offset ( x , y ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : place_block_level_non_replaced_element_in_normal_flow ( Box & child_box , Box & containing_block )
2020-11-22 13:38:18 +01:00
{
2020-12-06 19:48:02 +01:00
auto & box_model = child_box . box_model ( ) ;
2021-01-06 10:34:31 +01:00
auto & computed_values = child_box . computed_values ( ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
box_model . margin . top = computed_values . margin ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . margin . bottom = computed_values . margin ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . border . top = computed_values . border_top ( ) . width ;
box_model . border . bottom = computed_values . border_bottom ( ) . width ;
box_model . padding . top = computed_values . padding ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . padding . bottom = computed_values . padding ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float x = box_model . margin . left
+ box_model . border . left
+ box_model . padding . left
+ box_model . offset . left ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
if ( containing_block . computed_values ( ) . text_align ( ) = = CSS : : TextAlign : : LibwebCenter ) {
2020-12-06 19:48:02 +01:00
x = ( containing_block . width ( ) / 2 ) - child_box . width ( ) / 2 ;
2020-11-22 13:38:18 +01:00
}
2020-12-12 21:02:06 +01:00
float y = box_model . margin_box ( ) . top
+ box_model . offset . top ;
2020-11-22 13:38:18 +01:00
// NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc.
float collapsed_bottom_margin_of_preceding_siblings = 0 ;
2020-12-06 19:48:02 +01:00
auto * relevant_sibling = child_box . previous_sibling_of_type < Layout : : BlockBox > ( ) ;
2020-11-22 13:38:18 +01:00
while ( relevant_sibling ! = nullptr ) {
if ( ! relevant_sibling - > is_absolutely_positioned ( ) & & ! relevant_sibling - > is_floating ( ) ) {
2020-12-12 21:02:06 +01:00
collapsed_bottom_margin_of_preceding_siblings = max ( collapsed_bottom_margin_of_preceding_siblings , relevant_sibling - > box_model ( ) . margin . bottom ) ;
2021-01-02 03:46:26 +01:00
if ( relevant_sibling - > border_box_height ( ) > 0 )
2020-11-22 13:38:18 +01:00
break ;
}
relevant_sibling = relevant_sibling - > previous_sibling ( ) ;
}
if ( relevant_sibling ) {
2020-12-04 21:23:04 +01:00
y + = relevant_sibling - > effective_offset ( ) . y ( )
+ relevant_sibling - > height ( )
2020-12-12 21:02:06 +01:00
+ relevant_sibling - > box_model ( ) . border_box ( ) . bottom ;
2020-11-22 13:38:18 +01:00
// Collapse top margin with bottom margin of preceding siblings if needed
2020-12-12 21:02:06 +01:00
float my_margin_top = box_model . margin . top ;
2020-11-22 13:38:18 +01:00
if ( my_margin_top < 0 | | collapsed_bottom_margin_of_preceding_siblings < 0 ) {
// Negative margins present.
float largest_negative_margin = - min ( my_margin_top , collapsed_bottom_margin_of_preceding_siblings ) ;
float largest_positive_margin = ( my_margin_top < 0 & & collapsed_bottom_margin_of_preceding_siblings < 0 ) ? 0 : max ( my_margin_top , collapsed_bottom_margin_of_preceding_siblings ) ;
float final_margin = largest_positive_margin - largest_negative_margin ;
y + = final_margin - my_margin_top ;
} else if ( collapsed_bottom_margin_of_preceding_siblings > my_margin_top ) {
// Sibling's margin is larger than mine, adjust so we use sibling's.
y + = collapsed_bottom_margin_of_preceding_siblings - my_margin_top ;
}
}
2021-01-06 10:34:31 +01:00
if ( child_box . computed_values ( ) . clear ( ) = = CSS : : Clear : : Left | | child_box . computed_values ( ) . clear ( ) = = CSS : : Clear : : Both ) {
2020-12-06 01:45:51 +01:00
if ( ! m_left_floating_boxes . is_empty ( ) ) {
float clearance_y = 0 ;
for ( auto * floating_box : m_left_floating_boxes ) {
2020-12-12 21:02:06 +01:00
clearance_y = max ( clearance_y , floating_box - > effective_offset ( ) . y ( ) + floating_box - > box_model ( ) . margin_box ( ) . bottom ) ;
2020-12-06 01:45:51 +01:00
}
y = max ( y , clearance_y ) ;
m_left_floating_boxes . clear ( ) ;
}
}
2021-01-06 10:34:31 +01:00
if ( child_box . computed_values ( ) . clear ( ) = = CSS : : Clear : : Right | | child_box . computed_values ( ) . clear ( ) = = CSS : : Clear : : Both ) {
2020-12-06 01:45:51 +01:00
if ( ! m_right_floating_boxes . is_empty ( ) ) {
float clearance_y = 0 ;
for ( auto * floating_box : m_right_floating_boxes ) {
2020-12-12 21:02:06 +01:00
clearance_y = max ( clearance_y , floating_box - > effective_offset ( ) . y ( ) + floating_box - > box_model ( ) . margin_box ( ) . bottom ) ;
2020-12-06 01:45:51 +01:00
}
y = max ( y , clearance_y ) ;
m_right_floating_boxes . clear ( ) ;
}
}
2020-12-06 19:48:02 +01:00
child_box . set_offset ( x , y ) ;
2020-11-22 13:38:18 +01:00
}
void BlockFormattingContext : : layout_initial_containing_block ( LayoutMode layout_mode )
{
auto viewport_rect = context_box ( ) . frame ( ) . viewport_rect ( ) ;
2020-11-22 15:53:01 +01:00
auto & icb = downcast < Layout : : InitialContainingBlockBox > ( context_box ( ) ) ;
2020-11-22 13:38:18 +01:00
icb . build_stacking_context_tree ( ) ;
icb . set_width ( viewport_rect . width ( ) ) ;
2020-12-06 19:48:02 +01:00
layout_block_level_children ( context_box ( ) , layout_mode ) ;
2020-11-22 13:38:18 +01:00
2021-02-23 20:42:32 +01:00
VERIFY ( ! icb . children_are_inline ( ) ) ;
2020-11-22 13:38:18 +01:00
// FIXME: The ICB should have the height of the viewport.
// Instead of auto-sizing the ICB, we should spill into overflow.
float lowest_bottom = 0 ;
2020-11-22 15:53:01 +01:00
icb . for_each_child_of_type < Box > ( [ & ] ( auto & child ) {
2020-11-22 13:38:18 +01:00
lowest_bottom = max ( lowest_bottom , child . absolute_rect ( ) . bottom ( ) ) ;
} ) ;
2020-12-17 00:58:23 +01:00
// FIXME: This is a hack and should be managed by an overflow mechanism.
icb . set_height ( max ( static_cast < float > ( viewport_rect . height ( ) ) , lowest_bottom ) ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 20:57:17 +01:00
static Gfx : : FloatRect rect_in_coordinate_space ( const Box & box , const Box & context_box )
{
Gfx : : FloatRect rect { box . effective_offset ( ) , box . size ( ) } ;
for ( auto * ancestor = box . parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
if ( is < Box > ( * ancestor ) ) {
auto offset = downcast < Box > ( * ancestor ) . effective_offset ( ) ;
rect . move_by ( offset ) ;
}
if ( ancestor = = & context_box )
break ;
}
return rect ;
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_floating_child ( Box & box , Box & containing_block )
2020-12-05 20:10:39 +01:00
{
2021-02-23 20:42:32 +01:00
VERIFY ( box . is_floating ( ) ) ;
2020-12-05 20:10:39 +01:00
compute_width ( box ) ;
layout_inside ( box , LayoutMode : : Default ) ;
compute_height ( box ) ;
2020-12-06 16:51:19 +01:00
// First we place the box normally (to get the right y coordinate.)
2020-12-06 19:48:02 +01:00
place_block_level_non_replaced_element_in_normal_flow ( box , containing_block ) ;
2020-12-06 16:51:19 +01:00
// Then we float it to the left or right.
float x = box . effective_offset ( ) . x ( ) ;
2020-12-06 20:57:17 +01:00
auto box_in_context_rect = rect_in_coordinate_space ( box , context_box ( ) ) ;
float y_in_context_box = box_in_context_rect . y ( ) ;
// Next, float to the left and/or right
2021-01-06 10:34:31 +01:00
if ( box . computed_values ( ) . float_ ( ) = = CSS : : Float : : Left ) {
2020-12-06 16:51:19 +01:00
if ( ! m_left_floating_boxes . is_empty ( ) ) {
auto & previous_floating_box = * m_left_floating_boxes . last ( ) ;
2020-12-06 20:57:17 +01:00
auto previous_rect = rect_in_coordinate_space ( previous_floating_box , context_box ( ) ) ;
if ( previous_rect . contains_vertically ( y_in_context_box ) ) {
// This box touches another already floating box. Stack to the right.
x = previous_floating_box . effective_offset ( ) . x ( ) + previous_floating_box . width ( ) ;
} else {
// This box does not touch another floating box, go all the way to the left.
x = 0 ;
2020-12-06 21:11:28 +01:00
// Also, forget all previous left-floating boxes while we're here since they're no longer relevant.
m_left_floating_boxes . clear ( ) ;
2020-12-06 20:57:17 +01:00
}
} else {
// This is the first left-floating box. Go all the way to the left.
x = 0 ;
2020-12-06 16:51:19 +01:00
}
m_left_floating_boxes . append ( & box ) ;
2021-01-06 10:34:31 +01:00
} else if ( box . computed_values ( ) . float_ ( ) = = CSS : : Float : : Right ) {
2020-12-06 16:51:19 +01:00
if ( ! m_right_floating_boxes . is_empty ( ) ) {
auto & previous_floating_box = * m_right_floating_boxes . last ( ) ;
2020-12-06 20:57:17 +01:00
auto previous_rect = rect_in_coordinate_space ( previous_floating_box , context_box ( ) ) ;
if ( previous_rect . contains_vertically ( y_in_context_box ) ) {
// This box touches another already floating box. Stack to the left.
x = previous_floating_box . effective_offset ( ) . x ( ) - box . width ( ) ;
} else {
// This box does not touch another floating box, go all the way to the right.
x = containing_block . width ( ) - box . width ( ) ;
2020-12-06 21:11:28 +01:00
// Also, forget all previous right-floating boxes while we're here since they're no longer relevant.
m_right_floating_boxes . clear ( ) ;
2020-12-06 20:57:17 +01:00
}
2020-12-06 16:51:19 +01:00
} else {
2020-12-06 20:57:17 +01:00
// This is the first right-floating box. Go all the way to the right.
2020-12-06 19:48:02 +01:00
x = containing_block . width ( ) - box . width ( ) ;
2020-12-06 16:51:19 +01:00
}
m_right_floating_boxes . append ( & box ) ;
}
2020-12-06 20:57:17 +01:00
box . set_offset ( x , box . effective_offset ( ) . y ( ) ) ;
2020-12-05 20:10:39 +01:00
}
2020-11-22 13:38:18 +01:00
}