2019-09-25 12:40:37 +03:00
# include <LibGUI/GPainter.h>
2019-06-15 23:41:15 +02:00
# include <LibHTML/DOM/Element.h>
# include <LibHTML/Layout/LayoutBlock.h>
2019-10-03 15:20:13 +02:00
# include <LibHTML/Layout/LayoutInline.h>
2019-06-15 22:49:44 +02:00
2019-10-04 12:12:39 +02:00
LayoutBlock : : LayoutBlock ( const Node * node , NonnullRefPtr < StyleProperties > style_properties )
2019-09-21 15:32:17 +03:00
: LayoutNode ( node , move ( style_properties ) )
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-04 15:56:36 +02:00
append_child ( adopt ( * new LayoutBlock ( nullptr , style ( ) ) ) ) ;
2019-07-08 17:42:23 +02:00
}
return * last_child ( ) ;
}
2019-06-20 23:00:26 +02:00
void LayoutBlock : : layout ( )
{
2019-07-01 07:28:37 +02:00
compute_width ( ) ;
2019-08-18 08:37:53 +02:00
compute_position ( ) ;
2019-10-03 15:20:13 +02:00
if ( children_are_inline ( ) )
layout_inline_children ( ) ;
else
layout_block_children ( ) ;
compute_height ( ) ;
}
void LayoutBlock : : layout_block_children ( )
{
ASSERT ( ! children_are_inline ( ) ) ;
2019-08-18 08:37:53 +02:00
int content_height = 0 ;
for_each_child ( [ & ] ( auto & child ) {
child . layout ( ) ;
2019-10-04 15:50:50 +02:00
content_height = child . rect ( ) . bottom ( ) + child . box_model ( ) . full_margin ( ) . bottom - rect ( ) . top ( ) ;
2019-08-18 08:37:53 +02:00
} ) ;
rect ( ) . set_height ( content_height ) ;
2019-10-03 15:20:13 +02:00
}
2019-06-20 23:00:26 +02:00
2019-10-03 15:20:13 +02:00
void LayoutBlock : : layout_inline_children ( )
{
ASSERT ( children_are_inline ( ) ) ;
m_line_boxes . clear ( ) ;
for_each_child ( [ & ] ( auto & child ) {
ASSERT ( child . is_inline ( ) ) ;
2019-10-05 23:20:35 +02:00
child . split_into_lines ( * this ) ;
2019-10-03 15:20:13 +02:00
} ) ;
int content_height = 0 ;
for ( auto & line_box : m_line_boxes ) {
int max_height = 0 ;
for ( auto & fragment : line_box . fragments ( ) ) {
max_height = max ( max_height , fragment . rect ( ) . height ( ) ) ;
}
for ( auto & fragment : line_box . fragments ( ) ) {
// Vertically align everyone's bottom to the line.
// FIXME: Support other kinds of vertical alignment.
fragment . rect ( ) . set_x ( rect ( ) . x ( ) + fragment . rect ( ) . x ( ) ) ;
fragment . rect ( ) . set_y ( rect ( ) . y ( ) + content_height + ( max_height - fragment . rect ( ) . height ( ) ) ) ;
2019-10-05 23:20:35 +02:00
if ( fragment . layout_node ( ) . is_replaced ( ) )
const_cast < LayoutNode & > ( fragment . layout_node ( ) ) . set_rect ( fragment . rect ( ) ) ;
2019-10-03 15:20:13 +02:00
}
content_height + = max_height ;
}
rect ( ) . set_height ( content_height ) ;
2019-07-01 07:28:37 +02:00
}
void LayoutBlock : : compute_width ( )
{
2019-10-04 15:56:36 +02:00
auto & style_properties = this - > style ( ) ;
2019-07-24 07:34:07 +02:00
2019-08-18 08:09:56 +02:00
auto auto_value = Length ( ) ;
auto zero_value = Length ( 0 , Length : : Type : : Absolute ) ;
2019-09-21 15:32:17 +03:00
auto width = style_properties . length_or_fallback ( " width " , auto_value ) ;
auto margin_left = style_properties . length_or_fallback ( " margin-left " , zero_value ) ;
auto margin_right = style_properties . length_or_fallback ( " margin-right " , zero_value ) ;
auto border_left = style_properties . length_or_fallback ( " border-left " , zero_value ) ;
auto border_right = style_properties . length_or_fallback ( " border-right " , zero_value ) ;
auto padding_left = style_properties . length_or_fallback ( " padding-left " , zero_value ) ;
auto padding_right = style_properties . length_or_fallback ( " padding-right " , zero_value ) ;
2019-07-24 07:34:07 +02:00
2019-09-25 12:42:10 +03:00
# ifdef HTML_DEBUG
2019-08-18 08:09:56 +02: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
2019-07-26 08:05:14 +02:00
int total_px = 0 ;
for ( auto & value : { margin_left , border_left , padding_left , width , padding_right , border_right , margin_right } ) {
2019-08-18 08:09:56 +02:00
total_px + = value . to_px ( ) ;
2019-07-26 08:05:14 +02:00
}
2019-09-25 12:42:10 +03:00
# ifdef HTML_DEBUG
2019-07-26 08:05:14 +02:00
dbg ( ) < < " Total: " < < total_px ;
2019-09-25 12:42:10 +03:00
# endif
2019-07-26 08:05:14 +02: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.
2019-08-18 08:09:56 +02:00
if ( width . is_auto ( ) & & total_px > containing_block ( ) - > rect ( ) . width ( ) ) {
if ( margin_left . is_auto ( ) )
2019-07-26 08:05:14 +02:00
margin_left = zero_value ;
2019-08-18 08:09:56 +02:00
if ( margin_right . is_auto ( ) )
2019-07-26 08:05:14 +02:00
margin_right = zero_value ;
}
2019-08-18 08:09:56 +02:00
// 10.3.3 cont'd.
auto underflow_px = containing_block ( ) - > rect ( ) . 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 ) {
width = Length ( underflow_px , Length : : Type : : Absolute ) ;
} else {
width = zero_value ;
margin_right = Length ( margin_right . to_px ( ) + underflow_px , Length : : Type : : Absolute ) ;
}
} else {
if ( ! margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_right = Length ( margin_right . to_px ( ) + underflow_px , Length : : Type : : Absolute ) ;
} else if ( ! margin_left . is_auto ( ) & & margin_right . is_auto ( ) ) {
margin_right = Length ( underflow_px , Length : : Type : : Absolute ) ;
} else if ( margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_left = Length ( underflow_px , Length : : Type : : Absolute ) ;
} else { // margin_left.is_auto() && margin_right.is_auto()
auto half_of_the_underflow = Length ( underflow_px / 2 , Length : : Type : : Absolute ) ;
margin_left = half_of_the_underflow ;
margin_right = half_of_the_underflow ;
}
}
rect ( ) . set_width ( width . to_px ( ) ) ;
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 ( )
{
2019-10-04 15:56:36 +02:00
auto & style_properties = this - > style ( ) ;
2019-08-18 08:37:53 +02:00
auto auto_value = Length ( ) ;
auto zero_value = Length ( 0 , Length : : Type : : Absolute ) ;
2019-09-21 15:32:17 +03:00
auto width = style_properties . length_or_fallback ( " width " , auto_value ) ;
2019-09-25 12:28:35 +03:00
2019-10-04 15:50:50 +02:00
box_model ( ) . margin ( ) . top = style_properties . length_or_fallback ( " margin-top " , zero_value ) ;
box_model ( ) . margin ( ) . bottom = style_properties . length_or_fallback ( " margin-bottom " , zero_value ) ;
box_model ( ) . border ( ) . top = style_properties . length_or_fallback ( " border-top " , zero_value ) ;
box_model ( ) . border ( ) . bottom = style_properties . length_or_fallback ( " border-bottom " , zero_value ) ;
box_model ( ) . padding ( ) . top = style_properties . length_or_fallback ( " padding-top " , zero_value ) ;
box_model ( ) . padding ( ) . bottom = style_properties . length_or_fallback ( " padding-bottom " , zero_value ) ;
rect ( ) . set_x ( containing_block ( ) - > rect ( ) . x ( ) + box_model ( ) . margin ( ) . left . to_px ( ) + box_model ( ) . border ( ) . left . to_px ( ) + box_model ( ) . padding ( ) . left . to_px ( ) ) ;
2019-09-25 12:28:35 +03:00
int top_border = - 1 ;
if ( previous_sibling ( ) ! = nullptr ) {
auto & previous_sibling_rect = previous_sibling ( ) - > rect ( ) ;
2019-10-04 15:50:50 +02:00
auto & previous_sibling_style = previous_sibling ( ) - > box_model ( ) ;
2019-09-25 12:28:35 +03:00
top_border = previous_sibling_rect . y ( ) + previous_sibling_rect . height ( ) ;
top_border + = previous_sibling_style . full_margin ( ) . bottom ;
} else {
top_border = containing_block ( ) - > rect ( ) . y ( ) ;
}
2019-10-04 15:50:50 +02:00
rect ( ) . set_y ( top_border + box_model ( ) . full_margin ( ) . top ) ;
2019-08-18 08:37:53 +02:00
}
2019-07-01 07:28:37 +02:00
void LayoutBlock : : compute_height ( )
{
2019-10-04 15:56:36 +02:00
auto & style_properties = this - > style ( ) ;
2019-09-21 15:32:17 +03:00
auto height_property = style_properties . property ( " height " ) ;
2019-08-18 08:37:53 +02:00
if ( ! height_property . has_value ( ) )
return ;
auto height_length = height_property . value ( ) - > to_length ( ) ;
if ( height_length . is_absolute ( ) )
rect ( ) . set_height ( height_length . to_px ( ) ) ;
2019-06-20 23:00:26 +02:00
}
2019-09-25 12:40:37 +03:00
void LayoutBlock : : render ( RenderingContext & context )
{
LayoutNode : : render ( context ) ;
// FIXME: position this properly
2019-10-04 15:56:36 +02:00
if ( style ( ) . string_or_fallback ( " display " , " block " ) = = " list-item " ) {
2019-09-25 12:40:37 +03:00
Rect bullet_rect {
rect ( ) . x ( ) - 8 ,
rect ( ) . y ( ) + 4 ,
3 ,
3
} ;
2019-10-05 10:27:59 +02:00
context . painter ( ) . fill_rect ( bullet_rect , style ( ) . color_or_fallback ( " color " , Color : : Black ) ) ;
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 ( ) ) {
fragment . render ( context ) ;
}
}
}
}
bool LayoutBlock : : children_are_inline ( ) const
{
return first_child ( ) & & ! first_child ( ) - > is_block ( ) ;
}
HitTestResult LayoutBlock : : hit_test ( const Point & position ) const
{
if ( ! children_are_inline ( ) )
return LayoutNode : : hit_test ( position ) ;
HitTestResult result ;
for ( auto & line_box : m_line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
if ( fragment . rect ( ) . contains ( position ) ) {
return { fragment . layout_node ( ) } ;
}
}
}
return { } ;
2019-09-25 12:40:37 +03:00
}