2020-01-18 09:38:21 +01:00
/*
* Copyright ( c ) 2018 - 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 .
*/
2020-02-06 20:33:02 +01:00
# include <LibGUI/Painter.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/CSS/StyleResolver.h>
# include <LibWeb/DOM/Element.h>
2020-06-12 13:27:28 +02:00
# include <LibWeb/Dump.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/Layout/LayoutBlock.h>
2020-06-12 14:19:03 +02:00
# include <LibWeb/Layout/LayoutDocument.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/Layout/LayoutInline.h>
# include <LibWeb/Layout/LayoutReplaced.h>
# include <LibWeb/Layout/LayoutText.h>
2020-06-05 19:13:27 +02:00
# include <LibWeb/Layout/LayoutWidget.h>
2019-10-20 12:30:25 +02:00
# include <math.h>
2019-06-15 22:49:44 +02:00
2020-03-07 10:27:02 +01:00
namespace Web {
2019-10-07 09:23:53 +02:00
LayoutBlock : : LayoutBlock ( const Node * node , NonnullRefPtr < StyleProperties > style )
2019-10-15 16:48:38 +02:00
: LayoutBox ( node , move ( style ) )
2019-06-15 22:49:44 +02:00
{
}
LayoutBlock : : ~ LayoutBlock ( )
{
}
2019-06-20 23:00:26 +02:00
2019-07-08 17:42:23 +02:00
LayoutNode & LayoutBlock : : inline_wrapper ( )
{
2019-09-25 11:56:16 +03:00
if ( ! last_child ( ) | | ! last_child ( ) - > is_block ( ) | | last_child ( ) - > node ( ) ! = nullptr ) {
2019-10-05 23:47:06 +02:00
append_child ( adopt ( * new LayoutBlock ( nullptr , style_for_anonymous_block ( ) ) ) ) ;
2019-10-17 23:32:08 +02:00
last_child ( ) - > set_children_are_inline ( true ) ;
2019-07-08 17:42:23 +02:00
}
return * last_child ( ) ;
}
2020-05-27 19:20:49 +02:00
void LayoutBlock : : layout ( LayoutMode layout_mode )
2019-06-20 23:00:26 +02:00
{
2019-07-01 07:28:37 +02:00
compute_width ( ) ;
2020-05-05 16:06:22 +02:00
if ( ! is_inline ( ) )
compute_position ( ) ;
2019-08-18 08:37:53 +02:00
2020-05-27 19:20:49 +02:00
layout_children ( layout_mode ) ;
2019-10-03 15:20:13 +02:00
compute_height ( ) ;
2020-06-12 13:27:28 +02:00
if ( layout_mode = = LayoutMode : : Default )
2020-06-12 14:19:03 +02:00
layout_absolutely_positioned_descendants ( ) ;
2020-06-12 13:27:28 +02:00
}
2020-06-12 14:19:03 +02:00
void LayoutBlock : : layout_absolutely_positioned_descendants ( )
2020-06-12 13:27:28 +02:00
{
for ( auto & box : m_absolutely_positioned_descendants ) {
box - > layout ( LayoutMode : : Default ) ;
auto & box_model = box - > box_model ( ) ;
auto & style = box - > style ( ) ;
auto zero_value = Length ( 0 , Length : : Type : : Px ) ;
auto specified_width = style . length_or_fallback ( CSS : : PropertyID : : Width , Length ( ) , width ( ) ) ;
box_model . margin ( ) . top = style . length_or_fallback ( CSS : : PropertyID : : MarginTop , { } , height ( ) ) ;
box_model . margin ( ) . right = style . length_or_fallback ( CSS : : PropertyID : : MarginRight , { } , width ( ) ) ;
box_model . margin ( ) . bottom = style . length_or_fallback ( CSS : : PropertyID : : MarginBottom , { } , height ( ) ) ;
box_model . margin ( ) . left = style . length_or_fallback ( CSS : : PropertyID : : MarginLeft , { } , width ( ) ) ;
box_model . offset ( ) . top = style . length_or_fallback ( CSS : : PropertyID : : Top , { } , height ( ) ) ;
box_model . offset ( ) . right = style . length_or_fallback ( CSS : : PropertyID : : Right , { } , width ( ) ) ;
box_model . offset ( ) . bottom = style . length_or_fallback ( CSS : : PropertyID : : Bottom , { } , height ( ) ) ;
box_model . offset ( ) . left = style . length_or_fallback ( CSS : : PropertyID : : Left , { } , width ( ) ) ;
if ( box_model . offset ( ) . left . is_auto ( ) & & specified_width . is_auto ( ) & & box_model . offset ( ) . right . is_auto ( ) ) {
if ( box_model . margin ( ) . left . is_auto ( ) )
box_model . margin ( ) . left = zero_value ;
if ( box_model . margin ( ) . right . is_auto ( ) )
box_model . margin ( ) . right = zero_value ;
}
Gfx : : FloatPoint used_offset ;
float x_offset = box_model . offset ( ) . left . to_px ( * box )
+ box_model . border_box ( * box ) . left
- box_model . offset ( ) . right . to_px ( * box )
- box_model . border_box ( * box ) . right ;
float y_offset = box_model . offset ( ) . top . to_px ( * box )
+ box_model . border_box ( * box ) . top
- box_model . offset ( ) . bottom . to_px ( * box )
- box_model . border_box ( * box ) . bottom ;
if ( ! box_model . offset ( ) . left . is_auto ( ) ) {
used_offset . set_x ( x_offset + box_model . margin ( ) . left . to_px ( * box ) ) ;
} else if ( ! box_model . offset ( ) . right . is_auto ( ) ) {
used_offset . set_x ( width ( ) + x_offset - box - > width ( ) - box_model . margin ( ) . right . to_px ( * box ) ) ;
}
if ( ! box_model . offset ( ) . top . is_auto ( ) ) {
used_offset . set_y ( y_offset + box_model . margin ( ) . top . to_px ( * box ) ) ;
} else if ( ! box_model . offset ( ) . bottom . is_auto ( ) ) {
used_offset . set_y ( height ( ) + y_offset - box - > height ( ) - box_model . margin ( ) . bottom . to_px ( * box ) ) ;
}
box - > set_offset ( used_offset ) ;
}
}
void LayoutBlock : : add_absolutely_positioned_descendant ( LayoutBox & box )
{
m_absolutely_positioned_descendants . set ( box ) ;
2019-10-03 15:20:13 +02:00
}
2020-05-27 19:20:49 +02:00
void LayoutBlock : : layout_children ( LayoutMode layout_mode )
2020-05-26 21:53:10 +02:00
{
if ( children_are_inline ( ) )
2020-05-27 19:20:49 +02:00
layout_inline_children ( layout_mode ) ;
2020-05-26 21:53:10 +02:00
else
2020-05-27 19:20:49 +02:00
layout_block_children ( layout_mode ) ;
2020-05-26 21:53:10 +02:00
}
2020-05-27 19:20:49 +02:00
void LayoutBlock : : layout_block_children ( LayoutMode layout_mode )
2019-10-03 15:20:13 +02:00
{
ASSERT ( ! children_are_inline ( ) ) ;
2019-11-18 16:25:38 +01:00
float content_height = 0 ;
2019-08-18 08:37:53 +02:00
for_each_child ( [ & ] ( auto & child ) {
2019-10-17 23:32:08 +02:00
// FIXME: What should we do here? Something like a <table> might have a bunch of useless text children..
if ( child . is_inline ( ) )
return ;
2019-10-15 16:48:38 +02:00
auto & child_block = static_cast < LayoutBlock & > ( child ) ;
2020-05-27 19:20:49 +02:00
child_block . layout ( layout_mode ) ;
2020-06-05 19:13:27 +02:00
if ( ! child_block . is_absolutely_positioned ( ) )
2020-06-12 13:36:39 +02:00
content_height = max ( content_height , child_block . effective_offset ( ) . y ( ) + child_block . height ( ) + child_block . box_model ( ) . margin_box ( * this ) . bottom ) ;
2019-08-18 08:37:53 +02:00
} ) ;
2020-05-27 19:20:49 +02:00
if ( layout_mode ! = LayoutMode : : Default ) {
2020-05-26 21:53:10 +02:00
float max_width = 0 ;
for_each_child ( [ & ] ( auto & child ) {
2020-06-05 19:13:27 +02:00
if ( child . is_box ( ) & & ! child . is_absolutely_positioned ( ) )
2020-05-26 21:53:10 +02:00
max_width = max ( max_width , to < LayoutBox > ( child ) . width ( ) ) ;
} ) ;
2020-06-10 10:42:29 +02:00
set_width ( max_width ) ;
2020-05-26 21:53:10 +02:00
}
2020-06-10 10:42:29 +02:00
set_height ( content_height ) ;
2019-10-03 15:20:13 +02:00
}
2019-06-20 23:00:26 +02:00
2020-05-27 19:20:49 +02:00
void LayoutBlock : : layout_inline_children ( LayoutMode layout_mode )
2019-10-03 15:20:13 +02:00
{
ASSERT ( children_are_inline ( ) ) ;
m_line_boxes . clear ( ) ;
for_each_child ( [ & ] ( auto & child ) {
ASSERT ( child . is_inline ( ) ) ;
2020-05-27 19:20:49 +02:00
child . split_into_lines ( * this , layout_mode ) ;
2019-10-03 15:20:13 +02:00
} ) ;
2019-10-20 17:18:28 +02:00
for ( auto & line_box : m_line_boxes ) {
line_box . trim_trailing_whitespace ( ) ;
}
2020-06-07 17:55:46 +02:00
float min_line_height = style ( ) . line_height ( * this ) ;
2019-11-25 18:12:12 +01:00
float line_spacing = min_line_height - style ( ) . font ( ) . glyph_height ( ) ;
2019-11-18 16:25:38 +01:00
float content_height = 0 ;
2019-10-03 15:20:13 +02:00
2019-10-16 20:32:17 +02:00
// FIXME: This should be done by the CSS parser!
CSS : : ValueID text_align = CSS : : ValueID : : Left ;
auto text_align_string = style ( ) . string_or_fallback ( CSS : : PropertyID : : TextAlign , " left " ) ;
if ( text_align_string = = " center " )
text_align = CSS : : ValueID : : Center ;
else if ( text_align_string = = " left " )
text_align = CSS : : ValueID : : Left ;
else if ( text_align_string = = " right " )
text_align = CSS : : ValueID : : Right ;
2019-10-20 12:30:25 +02:00
else if ( text_align_string = = " justify " )
text_align = CSS : : ValueID : : Justify ;
2019-10-16 20:32:17 +02:00
2020-05-26 21:53:10 +02:00
float max_linebox_width = 0 ;
2019-10-03 15:20:13 +02:00
for ( auto & line_box : m_line_boxes ) {
2019-11-18 16:25:38 +01:00
float max_height = min_line_height ;
2019-10-03 15:20:13 +02:00
for ( auto & fragment : line_box . fragments ( ) ) {
2020-06-10 10:42:29 +02:00
max_height = max ( max_height , fragment . height ( ) ) ;
2019-10-03 15:20:13 +02:00
}
2019-10-16 20:32:17 +02:00
2020-06-10 10:42:29 +02:00
float x_offset = 0 ;
2019-10-20 17:18:28 +02:00
float excess_horizontal_space = ( float ) width ( ) - line_box . width ( ) ;
2019-10-20 12:30:25 +02:00
2019-10-16 20:32:17 +02:00
switch ( text_align ) {
case CSS : : ValueID : : Center :
2019-10-20 12:30:25 +02:00
x_offset + = excess_horizontal_space / 2 ;
2019-10-16 20:32:17 +02:00
break ;
case CSS : : ValueID : : Right :
2019-10-20 12:30:25 +02:00
x_offset + = excess_horizontal_space ;
2019-10-16 20:32:17 +02:00
break ;
case CSS : : ValueID : : Left :
2019-10-20 12:30:25 +02:00
case CSS : : ValueID : : Justify :
2019-10-16 20:32:17 +02:00
default :
break ;
}
2019-10-20 17:18:28 +02:00
float excess_horizontal_space_including_whitespace = excess_horizontal_space ;
2019-10-20 12:30:25 +02:00
int whitespace_count = 0 ;
if ( text_align = = CSS : : ValueID : : Justify ) {
for ( auto & fragment : line_box . fragments ( ) ) {
if ( fragment . is_justifiable_whitespace ( ) ) {
+ + whitespace_count ;
2020-06-10 10:42:29 +02:00
excess_horizontal_space_including_whitespace + = fragment . width ( ) ;
2019-10-20 12:30:25 +02:00
}
}
}
2019-10-20 17:18:28 +02:00
float justified_space_width = whitespace_count ? ( excess_horizontal_space_including_whitespace / ( float ) whitespace_count ) : 0 ;
2019-10-20 12:30:25 +02:00
2020-02-25 14:49:47 +01:00
for ( size_t i = 0 ; i < line_box . fragments ( ) . size ( ) ; + + i ) {
2019-10-20 12:30:25 +02:00
auto & fragment = line_box . fragments ( ) [ i ] ;
2020-06-05 19:13:27 +02:00
if ( fragment . layout_node ( ) . is_absolutely_positioned ( ) )
continue ;
2019-10-03 15:20:13 +02:00
// Vertically align everyone's bottom to the line.
// FIXME: Support other kinds of vertical alignment.
2020-06-10 10:42:29 +02:00
fragment . set_offset ( { roundf ( x_offset + fragment . offset ( ) . x ( ) ) , content_height + ( max_height - fragment . height ( ) ) - ( line_spacing / 2 ) } ) ;
2019-10-05 23:20:35 +02:00
2019-10-20 12:30:25 +02:00
if ( text_align = = CSS : : ValueID : : Justify ) {
if ( fragment . is_justifiable_whitespace ( ) ) {
2020-06-10 10:42:29 +02:00
if ( fragment . width ( ) ! = justified_space_width ) {
float diff = justified_space_width - fragment . width ( ) ;
fragment . set_width ( justified_space_width ) ;
2019-10-20 12:30:25 +02:00
// Shift subsequent sibling fragments to the right to adjust for change in width.
2020-02-25 14:49:47 +01:00
for ( size_t j = i + 1 ; j < line_box . fragments ( ) . size ( ) ; + + j ) {
2020-06-10 10:42:29 +02:00
auto offset = line_box . fragments ( ) [ j ] . offset ( ) ;
offset . move_by ( diff , 0 ) ;
line_box . fragments ( ) [ j ] . set_offset ( offset ) ;
2019-10-20 12:30:25 +02:00
}
}
}
}
2020-05-05 16:06:22 +02:00
if ( fragment . layout_node ( ) . is_inline_block ( ) ) {
auto & inline_block = const_cast < LayoutBlock & > ( to < LayoutBlock > ( fragment . layout_node ( ) ) ) ;
2020-06-10 10:42:29 +02:00
inline_block . set_size ( fragment . size ( ) ) ;
2020-05-27 19:20:49 +02:00
inline_block . layout ( layout_mode ) ;
2020-05-05 16:06:22 +02:00
}
2019-10-20 17:18:28 +02:00
float final_line_box_width = 0 ;
for ( auto & fragment : line_box . fragments ( ) )
2020-06-10 10:42:29 +02:00
final_line_box_width + = fragment . width ( ) ;
2019-10-20 17:18:28 +02:00
line_box . m_width = final_line_box_width ;
2020-05-26 21:53:10 +02:00
max_linebox_width = max ( max_linebox_width , final_line_box_width ) ;
2019-10-03 15:20:13 +02:00
}
content_height + = max_height ;
}
2020-05-27 19:20:49 +02:00
if ( layout_mode ! = LayoutMode : : Default ) {
2020-06-10 10:42:29 +02:00
set_width ( max_linebox_width ) ;
2020-05-26 21:53:10 +02:00
}
2020-06-10 10:42:29 +02:00
set_height ( content_height ) ;
2019-07-01 07:28:37 +02:00
}
void LayoutBlock : : compute_width ( )
{
2019-10-07 09:23:53 +02:00
auto & style = this - > style ( ) ;
2019-07-24 07:34:07 +02:00
2019-08-18 08:09:56 +02:00
auto auto_value = Length ( ) ;
2020-06-07 17:55:46 +02:00
auto zero_value = Length ( 0 , Length : : Type : : Px ) ;
2019-07-24 07:34:07 +02:00
2019-11-18 12:21:37 +01:00
Length margin_left ;
Length margin_right ;
Length border_left ;
Length border_right ;
Length padding_left ;
Length padding_right ;
2020-05-11 23:04:59 +02:00
auto & containing_block = * this - > containing_block ( ) ;
2019-11-18 12:21:37 +01:00
auto try_compute_width = [ & ] ( const auto & a_width ) {
Length width = a_width ;
2019-09-25 12:42:10 +03:00
# ifdef HTML_DEBUG
2019-11-18 12:21:37 +01:00
dbg ( ) < < " Left: " < < margin_left < < " + " < < border_left < < " + " < < padding_left ;
dbg ( ) < < " Right: " < < margin_right < < " + " < < border_right < < " + " < < padding_right ;
2019-09-25 12:42:10 +03:00
# endif
2020-05-11 23:04:59 +02:00
margin_left = style . length_or_fallback ( CSS : : PropertyID : : MarginLeft , zero_value , containing_block . width ( ) ) ;
margin_right = style . length_or_fallback ( CSS : : PropertyID : : MarginRight , zero_value , containing_block . width ( ) ) ;
2019-11-18 12:21:37 +01:00
border_left = style . length_or_fallback ( CSS : : PropertyID : : BorderLeftWidth , zero_value ) ;
border_right = style . length_or_fallback ( CSS : : PropertyID : : BorderRightWidth , zero_value ) ;
2020-05-11 23:04:59 +02:00
padding_left = style . length_or_fallback ( CSS : : PropertyID : : PaddingLeft , zero_value , containing_block . width ( ) ) ;
padding_right = style . length_or_fallback ( CSS : : PropertyID : : PaddingRight , zero_value , containing_block . width ( ) ) ;
2019-11-18 12:21:37 +01:00
2019-11-18 16:25:38 +01:00
float total_px = 0 ;
2019-11-18 12:21:37 +01:00
for ( auto & value : { margin_left , border_left , padding_left , width , padding_right , border_right , margin_right } ) {
2020-06-07 17:55:46 +02:00
total_px + = value . to_px ( * this ) ;
2019-11-18 12:21:37 +01:00
}
2019-07-26 08:05:14 +02:00
2019-09-25 12:42:10 +03:00
# ifdef HTML_DEBUG
2019-11-18 12:21:37 +01:00
dbg ( ) < < " Total: " < < total_px ;
2019-09-25 12:42:10 +03:00
# endif
2019-07-26 08:05:14 +02:00
2020-05-26 21:53:10 +02:00
if ( ! is_replaced ( ) & & ! is_inline ( ) ) {
// 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 > containing_block . width ( ) ) {
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
}
2019-08-18 08:09:56 +02:00
2020-05-26 21:53:10 +02:00
// 10.3.3 cont'd.
auto underflow_px = containing_block . width ( ) - 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 ) {
2020-06-07 17:55:46 +02:00
width = Length ( underflow_px , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else {
width = zero_value ;
2020-06-07 17:55:46 +02:00
margin_right = Length ( margin_right . to_px ( * this ) + underflow_px , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
}
} else {
if ( ! margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
2020-06-07 17:55:46 +02:00
margin_right = Length ( margin_right . to_px ( * this ) + underflow_px , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else if ( ! margin_left . is_auto ( ) & & margin_right . is_auto ( ) ) {
2020-06-07 17:55:46 +02:00
margin_right = Length ( underflow_px , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else if ( margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
2020-06-07 17:55:46 +02:00
margin_left = Length ( underflow_px , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else { // margin_left.is_auto() && margin_right.is_auto()
2020-06-07 17:55:46 +02:00
auto half_of_the_underflow = Length ( underflow_px / 2 , Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
margin_left = half_of_the_underflow ;
margin_right = half_of_the_underflow ;
}
}
} else if ( ! is_replaced ( ) & & is_inline_block ( ) ) {
// 10.3.9 'Inline-block', non-replaced elements in normal flow
2019-11-18 12:21:37 +01:00
2020-05-26 21:53:10 +02:00
// A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
2019-11-18 12:21:37 +01:00
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
2020-05-26 21:53:10 +02:00
// If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements.
if ( width . is_auto ( ) ) {
auto greatest_child_width = [ & ] {
float max_width = 0 ;
if ( children_are_inline ( ) ) {
for ( auto & box : line_boxes ( ) ) {
max_width = max ( max_width , box . width ( ) ) ;
}
} else {
for_each_child ( [ & ] ( auto & child ) {
if ( child . is_box ( ) )
max_width = max ( max_width , to < LayoutBox > ( child ) . width ( ) ) ;
} ) ;
}
return max_width ;
} ;
// 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 = containing_block . width ( )
2020-06-07 17:55:46 +02:00
- margin_left . to_px ( * this ) - border_left . to_px ( * this ) - padding_left . to_px ( * this )
- padding_right . to_px ( * this ) - border_right . to_px ( * this ) - margin_right . to_px ( * this ) ;
2020-05-26 21:53:10 +02:00
// Calculate the preferred width by formatting the content without breaking lines
// other than where explicit line breaks occur.
layout_children ( LayoutMode : : OnlyRequiredLineBreaks ) ;
float preferred_width = greatest_child_width ( ) ;
// Also calculate the preferred minimum width, e.g., by trying all possible line breaks.
// CSS 2.2 does not define the exact algorithm.
layout_children ( LayoutMode : : AllPossibleLineBreaks ) ;
float preferred_minimum_width = greatest_child_width ( ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
2020-06-07 17:55:46 +02:00
width = Length ( min ( max ( preferred_minimum_width , available_width ) , preferred_width ) , Length : : Type : : Px ) ;
2019-11-18 12:21:37 +01:00
}
}
2020-05-26 21:53:10 +02:00
2019-11-18 12:21:37 +01:00
return width ;
} ;
2020-05-11 23:04:59 +02:00
auto specified_width = style . length_or_fallback ( CSS : : PropertyID : : Width , auto_value , containing_block . width ( ) ) ;
2019-11-18 12:21:37 +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'.
2020-05-11 23:04:59 +02:00
auto specified_max_width = style . length_or_fallback ( CSS : : PropertyID : : MaxWidth , auto_value , containing_block . width ( ) ) ;
2019-11-18 12:21:37 +01:00
if ( ! specified_max_width . is_auto ( ) ) {
2020-06-07 17:55:46 +02:00
if ( used_width . to_px ( * this ) > specified_max_width . to_px ( * this ) ) {
2019-11-18 12:21:37 +01:00
used_width = try_compute_width ( specified_max_width ) ;
2019-08-18 08:09:56 +02:00
}
2019-11-18 12:21:37 +01:00
}
// 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'.
2020-05-11 23:04:59 +02:00
auto specified_min_width = style . length_or_fallback ( CSS : : PropertyID : : MinWidth , auto_value , containing_block . width ( ) ) ;
2019-11-18 12:21:37 +01:00
if ( ! specified_min_width . is_auto ( ) ) {
2020-06-07 17:55:46 +02:00
if ( used_width . to_px ( * this ) < specified_min_width . to_px ( * this ) ) {
2019-11-18 12:21:37 +01:00
used_width = try_compute_width ( specified_min_width ) ;
2019-08-18 08:09:56 +02:00
}
}
2020-06-10 10:42:29 +02:00
set_width ( used_width . to_px ( * this ) ) ;
2019-10-04 15:50:50 +02:00
box_model ( ) . margin ( ) . left = margin_left ;
box_model ( ) . margin ( ) . right = margin_right ;
box_model ( ) . border ( ) . left = border_left ;
box_model ( ) . border ( ) . right = border_right ;
box_model ( ) . padding ( ) . left = padding_left ;
box_model ( ) . padding ( ) . right = padding_right ;
2019-07-01 07:28:37 +02:00
}
2019-08-18 08:37:53 +02:00
void LayoutBlock : : compute_position ( )
{
2020-06-12 13:27:28 +02:00
if ( is_absolutely_positioned ( ) ) {
const_cast < LayoutBlock * > ( containing_block ( ) ) - > add_absolutely_positioned_descendant ( * this ) ;
return ;
}
2019-08-18 08:37:53 +02:00
2020-06-12 13:27:28 +02:00
auto & style = this - > style ( ) ;
2020-06-07 17:55:46 +02:00
auto zero_value = Length ( 0 , Length : : Type : : Px ) ;
2020-05-11 23:04:59 +02:00
auto & containing_block = * this - > containing_block ( ) ;
2020-05-12 01:01:45 +01:00
box_model ( ) . margin ( ) . top = style . length_or_fallback ( CSS : : PropertyID : : MarginTop , zero_value , containing_block . width ( ) ) ;
box_model ( ) . margin ( ) . bottom = style . length_or_fallback ( CSS : : PropertyID : : MarginBottom , zero_value , containing_block . width ( ) ) ;
2019-10-08 15:34:19 +02:00
box_model ( ) . border ( ) . top = style . length_or_fallback ( CSS : : PropertyID : : BorderTopWidth , zero_value ) ;
box_model ( ) . border ( ) . bottom = style . length_or_fallback ( CSS : : PropertyID : : BorderBottomWidth , zero_value ) ;
2020-05-12 01:01:45 +01:00
box_model ( ) . padding ( ) . top = style . length_or_fallback ( CSS : : PropertyID : : PaddingTop , zero_value , containing_block . width ( ) ) ;
box_model ( ) . padding ( ) . bottom = style . length_or_fallback ( CSS : : PropertyID : : PaddingBottom , zero_value , containing_block . width ( ) ) ;
2020-03-23 17:29:15 +01:00
2020-06-07 17:55:46 +02:00
float position_x = box_model ( ) . margin ( ) . left . to_px ( * this )
+ box_model ( ) . border ( ) . left . to_px ( * this )
+ box_model ( ) . padding ( ) . left . to_px ( * this )
+ box_model ( ) . offset ( ) . left . to_px ( * this ) ;
2020-03-23 17:29:15 +01:00
2020-06-12 13:36:39 +02:00
float position_y = box_model ( ) . margin_box ( * this ) . top
2020-06-07 17:55:46 +02:00
+ box_model ( ) . offset ( ) . top . to_px ( * this ) ;
2020-03-23 17:29:15 +01:00
2020-06-12 13:27:28 +02:00
LayoutBlock * relevant_sibling = previous_sibling ( ) ;
while ( relevant_sibling ! = nullptr ) {
if ( relevant_sibling - > style ( ) . position ( ) ! = CSS : : Position : : Absolute )
break ;
relevant_sibling = relevant_sibling - > previous_sibling ( ) ;
}
2020-03-23 17:29:15 +01:00
2020-06-12 13:27:28 +02:00
if ( relevant_sibling ) {
auto & previous_sibling_style = relevant_sibling - > box_model ( ) ;
position_y + = relevant_sibling - > effective_offset ( ) . y ( ) + relevant_sibling - > height ( ) ;
2020-06-12 13:36:39 +02:00
position_y + = previous_sibling_style . margin_box ( * this ) . bottom ;
2019-09-25 12:28:35 +03:00
}
2020-03-23 17:29:15 +01:00
2020-06-10 10:42:29 +02:00
set_offset ( { position_x , position_y } ) ;
2019-08-18 08:37:53 +02:00
}
2019-07-01 07:28:37 +02:00
void LayoutBlock : : compute_height ( )
{
2019-10-07 09:23:53 +02:00
auto & style = this - > style ( ) ;
2020-06-10 15:28:56 +02:00
auto specified_height = style . length_or_fallback ( CSS : : PropertyID : : Height , Length ( ) , containing_block ( ) - > height ( ) ) ;
auto specified_max_height = style . length_or_fallback ( CSS : : PropertyID : : MaxHeight , Length ( ) , containing_block ( ) - > height ( ) ) ;
if ( ! specified_height . is_auto ( ) ) {
float used_height = specified_height . to_px ( * this ) ;
if ( ! specified_max_height . is_auto ( ) )
used_height = min ( used_height , specified_max_height . to_px ( * this ) ) ;
set_height ( used_height ) ;
}
2019-06-20 23:00:26 +02:00
}
2019-09-25 12:40:37 +03:00
void LayoutBlock : : render ( RenderingContext & context )
{
2019-10-09 21:25:29 +02:00
if ( ! is_visible ( ) )
return ;
2019-10-15 19:12:12 +02:00
LayoutBox : : render ( context ) ;
2019-09-25 12:40:37 +03:00
2019-10-03 15:20:13 +02:00
if ( children_are_inline ( ) ) {
for ( auto & line_box : m_line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
2019-10-12 15:02:53 +02:00
if ( context . should_show_line_box_borders ( ) )
2020-06-10 10:42:29 +02:00
context . painter ( ) . draw_rect ( enclosing_int_rect ( fragment . absolute_rect ( ) ) , Color : : Green ) ;
2019-10-03 15:20:13 +02:00
fragment . render ( context ) ;
}
}
}
}
2020-06-10 10:57:59 +02:00
HitTestResult LayoutBlock : : hit_test ( const Gfx : : IntPoint & position ) const
2019-10-03 15:20:13 +02:00
{
if ( ! children_are_inline ( ) )
2019-10-15 22:02:11 +02:00
return LayoutBox : : hit_test ( position ) ;
2019-10-03 15:20:13 +02:00
HitTestResult result ;
for ( auto & line_box : m_line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
2020-06-10 10:42:29 +02:00
if ( enclosing_int_rect ( fragment . absolute_rect ( ) ) . contains ( position ) ) {
2020-05-23 21:06:24 +02:00
if ( fragment . layout_node ( ) . is_block ( ) )
return to < LayoutBlock > ( fragment . layout_node ( ) ) . hit_test ( position ) ;
2019-11-05 22:13:26 +01:00
return { fragment . layout_node ( ) , fragment . text_index_at ( position . x ( ) ) } ;
2019-10-03 15:20:13 +02:00
}
}
}
2020-03-20 12:41:31 +01:00
// FIXME: This should be smarter about the text position if we're hitting a block
// that has text inside it, but `position` is to the right of the text box.
2020-06-10 10:42:29 +02:00
return { absolute_rect ( ) . contains ( position . x ( ) , position . y ( ) ) ? this : nullptr } ;
2019-09-25 12:40:37 +03:00
}
2019-10-05 23:47:06 +02:00
NonnullRefPtr < StyleProperties > LayoutBlock : : style_for_anonymous_block ( ) const
{
auto new_style = StyleProperties : : create ( ) ;
2019-10-08 15:34:19 +02:00
style ( ) . for_each_property ( [ & ] ( auto property_id , auto & value ) {
if ( StyleResolver : : is_inherited_property ( property_id ) )
new_style - > set_property ( property_id , value ) ;
2019-10-05 23:47:06 +02:00
} ) ;
return new_style ;
}
2019-10-13 17:24:00 +02:00
LineBox & LayoutBlock : : ensure_last_line_box ( )
{
if ( m_line_boxes . is_empty ( ) )
m_line_boxes . append ( LineBox ( ) ) ;
return m_line_boxes . last ( ) ;
}
LineBox & LayoutBlock : : add_line_box ( )
{
m_line_boxes . append ( LineBox ( ) ) ;
return m_line_boxes . last ( ) ;
}
2020-03-07 10:27:02 +01:00
2020-05-27 19:20:49 +02:00
void LayoutBlock : : split_into_lines ( LayoutBlock & container , LayoutMode layout_mode )
2020-05-05 16:06:22 +02:00
{
ASSERT ( is_inline ( ) ) ;
2020-05-27 19:20:49 +02:00
layout ( layout_mode ) ;
2020-05-05 16:06:22 +02:00
auto * line_box = & container . ensure_last_line_box ( ) ;
if ( line_box - > width ( ) > 0 & & line_box - > width ( ) + width ( ) > container . width ( ) )
line_box = & container . add_line_box ( ) ;
line_box - > add_fragment ( * this , 0 , 0 , width ( ) , height ( ) ) ;
}
2020-03-07 10:27:02 +01:00
}