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 {
2020-07-28 19:48:57 +02:00
LayoutBlock : : LayoutBlock ( DOM : : Document & document , DOM : : Node * node , NonnullRefPtr < CSS : : StyleProperties > style )
2020-06-24 19:41:12 +02:00
: LayoutBox ( document , 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 ) {
2020-06-24 19:41:12 +02:00
append_child ( adopt ( * new LayoutBlock ( document ( ) , 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 ( ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
layout_inside ( layout_mode ) ;
2019-10-03 15:20:13 +02:00
compute_height ( ) ;
2020-06-12 13:27:28 +02:00
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
layout_absolutely_positioned_descendants ( ) ;
2020-06-12 13:27:28 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
void LayoutBlock : : layout_absolutely_positioned_descendant ( LayoutBox & box )
2020-06-12 13:27:28 +02:00
{
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
box . layout ( LayoutMode : : Default ) ;
auto & box_model = box . box_model ( ) ;
2020-07-26 20:01:35 +02:00
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
2020-06-12 13:27:28 +02:00
2020-06-24 15:38:21 +02:00
auto specified_width = box . style ( ) . width ( ) . resolved_or_auto ( box , width ( ) ) ;
2020-06-12 13:27:28 +02:00
2020-06-24 17:45:42 +02:00
box_model . margin . left = box . style ( ) . margin ( ) . left . resolved_or_auto ( box , width ( ) ) ;
box_model . margin . top = box . style ( ) . margin ( ) . top . resolved_or_auto ( box , height ( ) ) ;
box_model . margin . right = box . style ( ) . margin ( ) . right . resolved_or_auto ( box , width ( ) ) ;
box_model . margin . bottom = box . style ( ) . margin ( ) . bottom . resolved_or_auto ( box , height ( ) ) ;
2020-06-24 11:22:34 +02:00
2020-07-26 20:01:35 +02:00
box_model . border . left = CSS : : Length : : make_px ( box . style ( ) . border_left ( ) . width ) ;
box_model . border . right = CSS : : Length : : make_px ( box . style ( ) . border_right ( ) . width ) ;
box_model . border . top = CSS : : Length : : make_px ( box . style ( ) . border_top ( ) . width ) ;
box_model . border . bottom = CSS : : Length : : make_px ( box . style ( ) . border_bottom ( ) . width ) ;
2020-06-25 12:56:20 +02:00
2020-06-24 17:45:42 +02:00
box_model . offset . left = box . style ( ) . offset ( ) . left . resolved_or_auto ( box , width ( ) ) ;
box_model . offset . top = box . style ( ) . offset ( ) . top . resolved_or_auto ( box , height ( ) ) ;
box_model . offset . right = box . style ( ) . offset ( ) . right . resolved_or_auto ( box , width ( ) ) ;
box_model . offset . bottom = box . style ( ) . offset ( ) . bottom . resolved_or_auto ( box , height ( ) ) ;
2020-06-24 11:22:34 +02:00
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 ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
}
2020-06-12 13:27:28 +02:00
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
Gfx : : FloatPoint used_offset ;
2020-06-25 15:00:30 +02:00
if ( ! box_model . offset . left . is_auto ( ) ) {
float x_offset = box_model . offset . left . to_px ( box )
+ box_model . border_box ( box ) . left ;
2020-06-24 11:22:34 +02:00
used_offset . set_x ( x_offset + box_model . margin . left . to_px ( box ) ) ;
2020-06-25 15:00:30 +02:00
} else if ( ! box_model . offset . right . is_auto ( ) ) {
float x_offset = 0
- box_model . offset . right . to_px ( box )
- box_model . border_box ( box ) . right ;
2020-06-24 11:22:34 +02:00
used_offset . set_x ( width ( ) + x_offset - box . width ( ) - box_model . margin . right . to_px ( box ) ) ;
2020-06-25 15:00:30 +02:00
} else {
float x_offset = box_model . margin_box ( box ) . left ;
used_offset . set_x ( x_offset ) ;
2020-06-12 13:27:28 +02:00
}
2020-06-24 11:22:34 +02:00
if ( ! box_model . offset . top . is_auto ( ) ) {
2020-06-25 15:00:30 +02:00
float y_offset = box_model . offset . top . to_px ( box )
+ box_model . border_box ( box ) . top ;
2020-06-24 11:22:34 +02:00
used_offset . set_y ( y_offset + box_model . margin . top . to_px ( box ) ) ;
} else if ( ! box_model . offset . bottom . is_auto ( ) ) {
2020-06-25 15:00:30 +02:00
float y_offset = 0
- box_model . offset . bottom . to_px ( box )
- box_model . border_box ( box ) . bottom ;
2020-06-24 11:22:34 +02:00
used_offset . set_y ( height ( ) + y_offset - box . height ( ) - box_model . margin . bottom . to_px ( box ) ) ;
2020-06-25 15:00:30 +02:00
} else {
float y_offset = box_model . margin_box ( box ) . top ;
used_offset . set_y ( y_offset ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
}
box . set_offset ( used_offset ) ;
2019-10-03 15:20:13 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
void LayoutBlock : : layout_inside ( 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
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
layout_contained_boxes ( layout_mode ) ;
2020-05-26 21:53:10 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
void LayoutBlock : : layout_absolutely_positioned_descendants ( )
{
for_each_in_subtree_of_type < LayoutBox > ( [ & ] ( auto & box ) {
if ( box . is_absolutely_positioned ( ) & & box . containing_block ( ) = = this ) {
layout_absolutely_positioned_descendant ( box ) ;
}
return IterationDecision : : Continue ;
} ) ;
}
void LayoutBlock : : layout_contained_boxes ( LayoutMode layout_mode )
2019-10-03 15:20:13 +02:00
{
2019-11-18 16:25:38 +01:00
float content_height = 0 ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
float content_width = 0 ;
for_each_in_subtree_of_type < LayoutBox > ( [ & ] ( auto & box ) {
if ( box . is_absolutely_positioned ( ) | | box . containing_block ( ) ! = this )
return IterationDecision : : Continue ;
box . layout ( layout_mode ) ;
if ( box . is_replaced ( ) )
2020-07-26 17:16:18 +02:00
place_block_level_replaced_element_in_normal_flow ( downcast < LayoutReplaced > ( box ) ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
else if ( box . is_block ( ) )
2020-07-26 17:16:18 +02:00
place_block_level_non_replaced_element_in_normal_flow ( downcast < LayoutBlock > ( box ) ) ;
2020-06-14 22:07:00 +02:00
else
dbg ( ) < < " FIXME: LayoutBlock::layout_contained_boxes doesn't know how to place a " < < box . class_name ( ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
content_height = max ( content_height , box . effective_offset ( ) . y ( ) + box . height ( ) + box . box_model ( ) . margin_box ( * this ) . bottom ) ;
2020-07-26 17:16:18 +02:00
content_width = max ( content_width , downcast < LayoutBox > ( box ) . width ( ) ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
return IterationDecision : : Continue ;
2019-08-18 08:37:53 +02:00
} ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
2020-06-23 18:55:25 +02:00
if ( layout_mode ! = LayoutMode : : Default ) {
2020-06-24 15:38:21 +02:00
if ( style ( ) . width ( ) . is_undefined ( ) | | style ( ) . width ( ) . is_auto ( ) )
2020-06-23 18:55:25 +02:00
set_width ( content_width ) ;
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48: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 ( ) ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
if ( child . is_absolutely_positioned ( ) )
2020-06-12 15:25:37 +02:00
return ;
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-25 12:40:07 +02:00
// If there's an empty line box at the bottom, just remove it instead of giving it height.
if ( ! m_line_boxes . is_empty ( ) & & m_line_boxes . last ( ) . fragments ( ) . is_empty ( ) )
m_line_boxes . take_last ( ) ;
2020-06-24 14:34:40 +02:00
auto text_align = style ( ) . text_align ( ) ;
2020-06-24 13:51:14 +02:00
float min_line_height = specified_style ( ) . line_height ( * this ) ;
float line_spacing = min_line_height - specified_style ( ) . font ( ) . glyph_height ( ) ;
2019-11-18 16:25:38 +01:00
float content_height = 0 ;
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 ) {
2020-06-23 23:28:40 +02:00
case CSS : : TextAlign : : Center :
case CSS : : TextAlign : : VendorSpecificCenter :
2019-10-20 12:30:25 +02:00
x_offset + = excess_horizontal_space / 2 ;
2019-10-16 20:32:17 +02:00
break ;
2020-06-23 23:28:40 +02:00
case CSS : : TextAlign : : Right :
2019-10-20 12:30:25 +02:00
x_offset + = excess_horizontal_space ;
2019-10-16 20:32:17 +02:00
break ;
2020-06-23 23:28:40 +02:00
case CSS : : TextAlign : : Left :
case CSS : : TextAlign : : 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 ;
2020-06-23 23:28:40 +02:00
if ( text_align = = CSS : : TextAlign : : Justify ) {
2019-10-20 12:30:25 +02:00
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
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
2020-06-23 23:28:40 +02:00
if ( text_align = = CSS : : TextAlign : : Justify ) {
2019-10-20 12:30:25 +02:00
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 ( ) ) {
2020-07-26 17:16:18 +02:00
auto & inline_block = const_cast < LayoutBlock & > ( downcast < 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
}
2020-06-18 18:28:32 +02:00
void LayoutBlock : : compute_width_for_absolutely_positioned_block ( )
2019-07-01 07:28:37 +02:00
{
2020-06-18 18:28:32 +02:00
auto & containing_block = * this - > containing_block ( ) ;
2020-07-26 20:01:35 +02:00
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 18:28:32 +02:00
2020-07-26 20:01:35 +02:00
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2020-06-24 19:41:12 +02:00
const auto border_left = style ( ) . border_left ( ) . width ;
const auto border_right = style ( ) . border_right ( ) . width ;
2020-06-25 12:14:26 +02:00
const auto padding_left = style ( ) . padding ( ) . left . resolved ( zero_value , * this , containing_block . width ( ) ) ;
const auto padding_right = style ( ) . padding ( ) . right . resolved ( zero_value , * this , containing_block . width ( ) ) ;
2020-06-23 19:20:31 +02:00
2020-06-18 21:30:19 +02:00
auto try_compute_width = [ & ] ( const auto & a_width ) {
2020-06-24 17:45:42 +02:00
margin_left = style ( ) . margin ( ) . left . resolved ( zero_value , * this , containing_block . width ( ) ) ;
margin_right = style ( ) . margin ( ) . right . resolved ( zero_value , * this , containing_block . width ( ) ) ;
2020-06-24 19:41:12 +02:00
2020-06-24 17:45:42 +02:00
auto left = style ( ) . offset ( ) . left . resolved_or_auto ( * this , containing_block . width ( ) ) ;
auto right = style ( ) . offset ( ) . right . resolved_or_auto ( * this , containing_block . width ( ) ) ;
2020-06-18 21:30:19 +02:00
auto width = a_width ;
auto solve_for_left = [ & ] {
2020-07-26 20:01:35 +02:00
return CSS : : Length ( containing_block . width ( ) - margin_left . to_px ( * this ) - border_left - padding_left . to_px ( * this ) - width . to_px ( * this ) - padding_right . to_px ( * this ) - border_right - margin_right . to_px ( * this ) - right . to_px ( * this ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:30:19 +02:00
} ;
auto solve_for_width = [ & ] {
2020-07-26 20:01:35 +02:00
return CSS : : Length ( containing_block . width ( ) - left . to_px ( * this ) - margin_left . to_px ( * this ) - border_left - padding_left . to_px ( * this ) - padding_right . to_px ( * this ) - border_right - margin_right . to_px ( * this ) - right . to_px ( * this ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:30:19 +02:00
} ;
auto solve_for_right = [ & ] {
2020-07-26 20:01:35 +02:00
return CSS : : Length ( containing_block . width ( ) - left . to_px ( * this ) - margin_left . to_px ( * this ) - border_left - padding_left . to_px ( * this ) - width . to_px ( * this ) - padding_right . to_px ( * this ) - border_right - margin_right . to_px ( * this ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:30:19 +02:00
} ;
// If all three of 'left', 'width', and 'right' are 'auto':
if ( left . is_auto ( ) & & width . is_auto ( ) & & right . is_auto ( ) ) {
// First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
if ( margin_left . is_auto ( ) )
2020-07-26 20:01:35 +02:00
margin_left = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 21:30:19 +02:00
if ( margin_right . is_auto ( ) )
2020-07-26 20:01:35 +02:00
margin_right = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 21:30:19 +02:00
// Then, if the 'direction' property of the element establishing the static-position containing block
// is 'ltr' set 'left' to the static position and apply rule number three below;
// otherwise, set 'right' to the static position and apply rule number one below.
// FIXME: This is very hackish.
2020-07-26 20:01:35 +02:00
left = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 21:30:19 +02:00
goto Rule3 ;
}
2020-06-18 18:28:32 +02:00
2020-06-18 21:30:19 +02:00
if ( ! left . is_auto ( ) & & ! width . is_auto ( ) & & ! right . is_auto ( ) ) {
// FIXME: This should be solved in a more complicated way.
return width ;
}
2020-06-18 18:28:32 +02:00
2020-06-18 21:16:29 +02:00
if ( margin_left . is_auto ( ) )
2020-07-26 20:01:35 +02:00
margin_left = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 21:16:29 +02:00
if ( margin_right . is_auto ( ) )
2020-07-26 20:01:35 +02:00
margin_right = CSS : : Length : : make_px ( 0 ) ;
2020-06-18 21:16:29 +02:00
2020-06-18 21:30:19 +02:00
// 1. 'left' and 'width' are 'auto' and 'right' is not 'auto',
// then the width is shrink-to-fit. Then solve for 'left'
if ( left . is_auto ( ) & & width . is_auto ( ) & & ! right . is_auto ( ) ) {
auto result = calculate_shrink_to_fit_width ( ) ;
solve_for_left ( ) ;
auto available_width = solve_for_width ( ) ;
2020-07-26 20:01:35 +02:00
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width . to_px ( * this ) ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:30:19 +02:00
}
2020-06-18 21:16:29 +02:00
2020-06-18 21:30:19 +02:00
// 2. 'left' and 'right' are 'auto' and 'width' is not 'auto',
// then if the 'direction' property of the element establishing
// the static-position containing block is 'ltr' set 'left'
// to the static position, otherwise set 'right' to the static position.
// Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
else if ( left . is_auto ( ) & & right . is_auto ( ) & & ! width . is_auto ( ) ) {
// FIXME: Check direction
// FIXME: Use the static-position containing block
left = zero_value ;
right = solve_for_right ( ) ;
}
2019-07-24 07:34:07 +02:00
2020-06-18 21:30:19 +02:00
// 3. 'width' and 'right' are 'auto' and 'left' is not 'auto',
// then the width is shrink-to-fit. Then solve for 'right'
else if ( width . is_auto ( ) & & right . is_auto ( ) & & ! left . is_auto ( ) ) {
Rule3 :
auto result = calculate_shrink_to_fit_width ( ) ;
right = solve_for_right ( ) ;
auto available_width = solve_for_width ( ) ;
2020-07-26 20:01:35 +02:00
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width . to_px ( * this ) ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:30:19 +02:00
}
2020-06-18 18:28:32 +02:00
2020-06-18 21:30:19 +02:00
// 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
else if ( left . is_auto ( ) & & ! width . is_auto ( ) & & ! right . is_auto ( ) ) {
left = solve_for_left ( ) ;
}
2020-06-18 18:28:32 +02:00
2020-06-18 21:30:19 +02:00
// 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
else if ( width . is_auto ( ) & & ! left . is_auto ( ) & & ! right . is_auto ( ) ) {
width = solve_for_width ( ) ;
}
// 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
else if ( right . is_auto ( ) & & ! left . is_auto ( ) & & ! width . is_auto ( ) ) {
right = solve_for_right ( ) ;
}
2020-06-18 18:28:32 +02:00
2020-06-18 21:30:19 +02:00
return width ;
} ;
2020-06-24 15:38:21 +02:00
auto specified_width = style ( ) . width ( ) . resolved_or_auto ( * this , containing_block . width ( ) ) ;
2020-06-18 21:30:19 +02: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-06-24 16:03:25 +02:00
auto specified_max_width = style ( ) . max_width ( ) . resolved_or_auto ( * this , containing_block . width ( ) ) ;
2020-06-18 21:30:19 +02:00
if ( ! specified_max_width . is_auto ( ) ) {
if ( used_width . to_px ( * this ) > specified_max_width . to_px ( * this ) ) {
used_width = try_compute_width ( specified_max_width ) ;
}
2020-06-18 18:28:32 +02:00
}
2020-06-18 21:30:19 +02: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-06-24 16:03:25 +02:00
auto specified_min_width = style ( ) . min_width ( ) . resolved_or_auto ( * this , containing_block . width ( ) ) ;
2020-06-18 21:30:19 +02:00
if ( ! specified_min_width . is_auto ( ) ) {
if ( used_width . to_px ( * this ) < specified_min_width . to_px ( * this ) ) {
used_width = try_compute_width ( specified_min_width ) ;
}
2020-06-18 18:28:32 +02:00
}
2020-06-18 21:30:19 +02:00
set_width ( used_width . to_px ( * this ) ) ;
2020-06-23 19:20:31 +02:00
2020-06-24 11:22:34 +02:00
box_model ( ) . margin . left = margin_left ;
box_model ( ) . margin . right = margin_right ;
2020-07-26 20:01:35 +02:00
box_model ( ) . border . left = CSS : : Length : : make_px ( border_left ) ;
box_model ( ) . border . right = CSS : : Length : : make_px ( border_right ) ;
2020-06-24 11:22:34 +02:00
box_model ( ) . padding . left = padding_left ;
box_model ( ) . padding . right = padding_right ;
2020-06-18 18:28:32 +02:00
}
2020-06-28 15:11:08 +02:00
float LayoutBlock : : width_of_logical_containing_block ( ) const
{
auto * containing_block = this - > containing_block ( ) ;
ASSERT ( containing_block ) ;
return containing_block - > width ( ) ;
}
2020-06-18 18:28:32 +02:00
void LayoutBlock : : compute_width ( )
{
if ( is_absolutely_positioned ( ) )
return compute_width_for_absolutely_positioned_block ( ) ;
2020-06-28 15:11:08 +02:00
float width_of_containing_block = this - > width_of_logical_containing_block ( ) ;
2020-07-26 20:01:35 +02:00
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
2019-07-24 07:34:07 +02:00
2020-07-26 20:01:35 +02:00
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2020-06-28 15:11:08 +02:00
const auto padding_left = style ( ) . padding ( ) . left . resolved_or_zero ( * this , width_of_containing_block ) ;
const auto padding_right = style ( ) . padding ( ) . right . resolved_or_zero ( * this , width_of_containing_block ) ;
2020-05-11 23:04:59 +02:00
2019-11-18 12:21:37 +01:00
auto try_compute_width = [ & ] ( const auto & a_width ) {
2020-07-26 20:01:35 +02:00
CSS : : Length width = a_width ;
2020-06-28 15:11:08 +02:00
margin_left = style ( ) . margin ( ) . left . resolved_or_zero ( * this , width_of_containing_block ) ;
margin_right = style ( ) . margin ( ) . right . resolved_or_zero ( * this , width_of_containing_block ) ;
2019-11-18 12:21:37 +01:00
2020-06-24 19:41:12 +02:00
float total_px = style ( ) . border_left ( ) . width + style ( ) . border_right ( ) . width ;
for ( auto & value : { margin_left , padding_left , width , padding_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.
2020-06-28 15:11:08 +02:00
if ( width . is_auto ( ) & & total_px > width_of_containing_block ) {
2020-05-26 21:53:10 +02:00
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.
2020-06-28 15:11:08 +02:00
auto underflow_px = width_of_containing_block - total_px ;
2020-05-26 21:53:10 +02:00
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-07-26 20:01:35 +02:00
width = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else {
width = zero_value ;
2020-07-26 20:01:35 +02:00
margin_right = CSS : : Length ( margin_right . to_px ( * this ) + underflow_px , CSS : : Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
}
} else {
if ( ! margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
2020-07-26 20:01:35 +02:00
margin_right = CSS : : Length ( margin_right . to_px ( * this ) + underflow_px , CSS : : Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else if ( ! margin_left . is_auto ( ) & & margin_right . is_auto ( ) ) {
2020-07-26 20:01:35 +02:00
margin_right = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else if ( margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
2020-07-26 20:01:35 +02:00
margin_left = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
2020-05-26 21:53:10 +02:00
} else { // margin_left.is_auto() && margin_right.is_auto()
2020-07-26 20:01:35 +02:00
auto half_of_the_underflow = CSS : : Length ( underflow_px / 2 , CSS : : 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.
2020-06-18 21:16:29 +02:00
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.
2020-06-28 15:11:08 +02:00
float available_width = width_of_containing_block
2020-06-24 19:41:12 +02:00
- margin_left . to_px ( * this ) - style ( ) . border_left ( ) . width - padding_left . to_px ( * this )
- padding_right . to_px ( * this ) - style ( ) . border_right ( ) . width - margin_right . to_px ( * this ) ;
2020-06-18 21:16:29 +02:00
auto result = calculate_shrink_to_fit_width ( ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
2020-07-26 20:01:35 +02:00
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
2020-06-18 21:16:29 +02:00
}
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-06-28 15:11:08 +02:00
auto specified_width = style ( ) . width ( ) . resolved_or_auto ( * this , width_of_containing_block ) ;
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-06-28 15:11:08 +02:00
auto specified_max_width = style ( ) . max_width ( ) . resolved_or_auto ( * this , width_of_containing_block ) ;
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-06-28 15:11:08 +02:00
auto specified_min_width = style ( ) . min_width ( ) . resolved_or_auto ( * this , width_of_containing_block ) ;
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 ) ) ;
2020-06-24 11:22:34 +02:00
box_model ( ) . margin . left = margin_left ;
box_model ( ) . margin . right = margin_right ;
2020-07-26 20:01:35 +02:00
box_model ( ) . border . left = CSS : : Length : : make_px ( style ( ) . border_left ( ) . width ) ;
box_model ( ) . border . right = CSS : : Length : : make_px ( style ( ) . border_right ( ) . width ) ;
2020-06-24 11:22:34 +02:00
box_model ( ) . padding . left = padding_left ;
box_model ( ) . padding . right = padding_right ;
2019-07-01 07:28:37 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
void LayoutBlock : : place_block_level_replaced_element_in_normal_flow ( LayoutReplaced & box )
2019-08-18 08:37:53 +02:00
{
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
ASSERT ( ! is_absolutely_positioned ( ) ) ;
auto & containing_block = * this ;
auto & replaced_element_box_model = box . box_model ( ) ;
2020-06-24 19:41:12 +02:00
replaced_element_box_model . margin . top = box . style ( ) . margin ( ) . top . resolved_or_zero ( * this , containing_block . width ( ) ) ;
replaced_element_box_model . margin . bottom = box . style ( ) . margin ( ) . bottom . resolved_or_zero ( * this , containing_block . width ( ) ) ;
2020-07-26 20:01:35 +02:00
replaced_element_box_model . border . top = CSS : : Length : : make_px ( box . style ( ) . border_top ( ) . width ) ;
replaced_element_box_model . border . bottom = CSS : : Length : : make_px ( box . style ( ) . border_bottom ( ) . width ) ;
2020-06-24 19:41:12 +02:00
replaced_element_box_model . padding . top = box . style ( ) . padding ( ) . top . resolved_or_zero ( * this , containing_block . width ( ) ) ;
replaced_element_box_model . padding . bottom = box . style ( ) . padding ( ) . bottom . resolved_or_zero ( * this , containing_block . width ( ) ) ;
2020-05-11 23:04:59 +02:00
2020-06-24 11:22:34 +02:00
float x = replaced_element_box_model . margin . left . to_px ( * this )
+ replaced_element_box_model . border . left . to_px ( * this )
+ replaced_element_box_model . padding . left . to_px ( * this )
+ replaced_element_box_model . offset . left . to_px ( * this ) ;
2020-03-23 17:29:15 +01:00
2020-06-24 11:22:34 +02:00
float y = replaced_element_box_model . margin_box ( * this ) . top + box_model ( ) . offset . top . to_px ( * this ) ;
2020-03-23 17:29:15 +01:00
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
box . set_offset ( x , y ) ;
}
2020-06-18 21:16:29 +02:00
LayoutBlock : : ShrinkToFitResult LayoutBlock : : calculate_shrink_to_fit_width ( )
2020-06-18 18:28:32 +02:00
{
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 ( ) )
2020-07-26 17:16:18 +02:00
max_width = max ( max_width , downcast < LayoutBox > ( child ) . width ( ) ) ;
2020-06-18 18:28:32 +02:00
} ) ;
}
return max_width ;
} ;
// Calculate the preferred width by formatting the content without breaking lines
// other than where explicit line breaks occur.
layout_inside ( 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_inside ( LayoutMode : : AllPossibleLineBreaks ) ;
float preferred_minimum_width = greatest_child_width ( ) ;
2020-06-18 21:16:29 +02:00
return { preferred_width , preferred_minimum_width } ;
2020-06-18 18:28:32 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
void LayoutBlock : : place_block_level_non_replaced_element_in_normal_flow ( LayoutBlock & block )
{
2020-07-26 20:01:35 +02:00
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
auto & containing_block = * this ;
auto & box = block . box_model ( ) ;
2020-06-24 17:45:42 +02:00
auto & style = block . style ( ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
2020-06-24 17:45:42 +02:00
box . margin . top = style . margin ( ) . top . resolved ( zero_value , * this , containing_block . width ( ) ) ;
box . margin . bottom = style . margin ( ) . bottom . resolved ( zero_value , * this , containing_block . width ( ) ) ;
2020-07-26 20:01:35 +02:00
box . border . top = CSS : : Length : : make_px ( style . border_top ( ) . width ) ;
box . border . bottom = CSS : : Length : : make_px ( style . border_bottom ( ) . width ) ;
2020-06-24 17:45:42 +02:00
box . padding . top = style . padding ( ) . top . resolved ( zero_value , * this , containing_block . width ( ) ) ;
box . padding . bottom = style . padding ( ) . bottom . resolved ( zero_value , * this , containing_block . width ( ) ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
2020-06-24 11:22:34 +02:00
float x = box . margin . left . to_px ( * this )
+ box . border . left . to_px ( * this )
+ box . padding . left . to_px ( * this )
+ box . offset . left . to_px ( * this ) ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
2020-06-24 14:34:40 +02:00
if ( this - > style ( ) . text_align ( ) = = CSS : : TextAlign : : VendorSpecificCenter ) {
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
x = ( containing_block . width ( ) / 2 ) - block . width ( ) / 2 ;
2020-06-13 10:54:58 +02:00
}
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
float y = box . margin_box ( * this ) . top
2020-06-24 11:22:34 +02:00
+ box . offset . top . to_px ( * this ) ;
2020-03-23 17:29:15 +01:00
2020-06-28 10:51:11 +02:00
// NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc.
float collapsed_bottom_margin_of_preceding_siblings = 0 ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
auto * relevant_sibling = block . previous_sibling ( ) ;
2020-06-12 13:27:28 +02:00
while ( relevant_sibling ! = nullptr ) {
2020-06-28 10:51:11 +02:00
if ( ! relevant_sibling - > is_absolutely_positioned ( ) & & ! relevant_sibling - > is_floating ( ) ) {
collapsed_bottom_margin_of_preceding_siblings = max ( collapsed_bottom_margin_of_preceding_siblings , relevant_sibling - > box_model ( ) . margin . bottom . to_px ( * relevant_sibling ) ) ;
if ( relevant_sibling - > height ( ) > 0 )
break ;
}
2020-06-12 13:27:28 +02:00
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 ) {
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
y + = relevant_sibling - > effective_offset ( ) . y ( ) + relevant_sibling - > height ( ) ;
2020-06-12 17:41:14 +02:00
2020-06-28 10:51:11 +02:00
// Collapse top margin with bottom margin of preceding siblings if needed
2020-06-24 11:22:34 +02:00
float my_margin_top = box . margin . top . to_px ( * this ) ;
2020-06-12 18:09:40 +02:00
2020-06-28 10:51:11 +02:00
if ( my_margin_top < 0 | | collapsed_bottom_margin_of_preceding_siblings < 0 ) {
2020-06-12 18:09:40 +02:00
// Negative margins present.
2020-06-28 10:51:11 +02:00
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 ) ;
2020-06-12 18:09:40 +02:00
float final_margin = largest_positive_margin - largest_negative_margin ;
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
y + = final_margin - my_margin_top ;
2020-06-28 10:51:11 +02:00
} else if ( collapsed_bottom_margin_of_preceding_siblings > my_margin_top ) {
2020-06-12 17:41:14 +02:00
// Sibling's margin is larger than mine, adjust so we use sibling's.
2020-06-28 10:51:11 +02:00
y + = collapsed_bottom_margin_of_preceding_siblings - my_margin_top ;
2020-06-12 17:41:14 +02:00
}
2019-09-25 12:28:35 +03:00
}
2020-03-23 17:29:15 +01:00
LibWeb: Reorganize layout algorithm
Previously, layout recursively performed these steps (roughly):
1. Compute own width
2. Compute own position
3. Layout in-flow children
4. Compute own height
5. Layout absolutely positioned descendants
However, step (2) was pretty inconsistent. Some things computed their
own position, others had their parent do it for them, etc.
To get closer to CSS spec language, and make things easier in general,
this patch reorganizes the algorithm into:
1. Compute own width & height
2. Compute width & height of in-flow managed descendants
3. Move in-flow managed descendants to their final position
4. Layout absolutely positioned descendants
Block layout is now driven by the containing block, which will iterate
the descendants it's responsible for. There are a lot of inefficient
patterns in this logic right now, but they can easily be replaced with
better iteration functions once we settle on a long-term architecture.
Since the ICB (LayoutDocument) is at (0, 0), it doesn't rely on a
containing block to move it into place.
This code is still evolving along with my understanding of CSS layout,
so it's likely that we'll reorganize this again sooner or later. :^)
2020-06-14 18:48:10 +02:00
block . set_offset ( x , y ) ;
2019-08-18 08:37:53 +02:00
}
2019-07-01 07:28:37 +02:00
void LayoutBlock : : compute_height ( )
{
2020-06-24 16:11:15 +02:00
auto & containing_block = * this - > containing_block ( ) ;
2020-06-10 15:28:56 +02:00
2020-07-26 20:01:35 +02:00
CSS : : Length specified_height ;
2020-06-25 16:04:57 +02:00
if ( style ( ) . height ( ) . is_percentage ( ) & & ! containing_block . style ( ) . height ( ) . is_absolute ( ) ) {
2020-07-26 20:01:35 +02:00
specified_height = CSS : : Length : : make_auto ( ) ;
2020-06-25 16:04:57 +02:00
} else {
specified_height = style ( ) . height ( ) . resolved_or_auto ( * this , containing_block . height ( ) ) ;
}
2020-06-24 16:11:15 +02:00
auto specified_max_height = style ( ) . max_height ( ) . resolved_or_auto ( * this , containing_block . height ( ) ) ;
2020-06-10 15:28:56 +02:00
2020-06-24 17:45:42 +02:00
box_model ( ) . margin . top = style ( ) . margin ( ) . top . resolved_or_zero ( * this , containing_block . width ( ) ) ;
box_model ( ) . margin . bottom = style ( ) . margin ( ) . bottom . resolved_or_zero ( * this , containing_block . width ( ) ) ;
2020-07-26 20:01:35 +02:00
box_model ( ) . border . top = CSS : : Length : : make_px ( style ( ) . border_top ( ) . width ) ;
box_model ( ) . border . bottom = CSS : : Length : : make_px ( style ( ) . border_bottom ( ) . width ) ;
2020-06-24 17:45:42 +02:00
box_model ( ) . padding . top = style ( ) . padding ( ) . top . resolved_or_zero ( * this , containing_block . width ( ) ) ;
box_model ( ) . padding . bottom = style ( ) . padding ( ) . bottom . resolved_or_zero ( * this , containing_block . width ( ) ) ;
2020-06-23 19:20:31 +02:00
2020-06-10 15:28:56 +02:00
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
2020-06-18 21:35:44 +02:00
void LayoutBlock : : paint ( PaintContext & context , PaintPhase phase )
2019-09-25 12:40:37 +03:00
{
2019-10-09 21:25:29 +02:00
if ( ! is_visible ( ) )
return ;
2020-06-18 21:35:44 +02:00
LayoutBox : : paint ( context , phase ) ;
2019-09-25 12:40:37 +03:00
2020-06-18 18:57:35 +02:00
// FIXME: Inline backgrounds etc.
if ( phase = = PaintPhase : : Foreground ) {
if ( children_are_inline ( ) ) {
for ( auto & line_box : m_line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
if ( context . should_show_line_box_borders ( ) )
context . painter ( ) . draw_rect ( enclosing_int_rect ( fragment . absolute_rect ( ) ) , Color : : Green ) ;
2020-06-28 23:07:44 +02:00
fragment . paint ( context ) ;
2020-06-18 18:57:35 +02:00
}
2019-10-03 15:20:13 +02:00
}
}
}
}
2020-08-05 16:55:56 +02:00
HitTestResult LayoutBlock : : hit_test ( const Gfx : : IntPoint & position , HitTestType type ) const
2019-10-03 15:20:13 +02:00
{
if ( ! children_are_inline ( ) )
2020-08-05 16:55:56 +02:00
return LayoutBox : : hit_test ( position , type ) ;
2019-10-03 15:20:13 +02:00
2020-06-29 00:37:39 +02:00
HitTestResult last_good_candidate ;
2019-10-03 15:20:13 +02:00
for ( auto & line_box : m_line_boxes ) {
for ( auto & fragment : line_box . fragments ( ) ) {
2020-07-26 17:16:18 +02:00
if ( is < LayoutBox > ( fragment . layout_node ( ) ) & & downcast < LayoutBox > ( fragment . layout_node ( ) ) . stacking_context ( ) )
2020-07-01 19:02:28 +02:00
continue ;
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 ( ) )
2020-08-05 16:55:56 +02:00
return downcast < LayoutBlock > ( fragment . layout_node ( ) ) . hit_test ( position , type ) ;
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-06-29 00:37:39 +02:00
if ( fragment . absolute_rect ( ) . top ( ) < = position . y ( ) )
last_good_candidate = { 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
2020-08-05 16:55:56 +02:00
if ( type = = HitTestType : : TextCursor & & last_good_candidate . layout_node )
2020-06-29 00:37:39 +02:00
return last_good_candidate ;
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
2020-07-26 20:01:35 +02:00
NonnullRefPtr < CSS : : StyleProperties > LayoutBlock : : style_for_anonymous_block ( ) const
2019-10-05 23:47:06 +02:00
{
2020-07-26 20:01:35 +02:00
auto new_style = CSS : : StyleProperties : : create ( ) ;
2019-10-05 23:47:06 +02:00
2020-06-24 13:51:14 +02:00
specified_style ( ) . for_each_property ( [ & ] ( auto property_id , auto & value ) {
2020-07-26 20:01:35 +02:00
if ( CSS : : StyleResolver : : is_inherited_property ( property_id ) )
2019-10-08 15:34:19 +02:00
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
{
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 ( ) ;
2020-06-18 21:16:29 +02:00
if ( layout_mode ! = LayoutMode : : OnlyRequiredLineBreaks & & line_box - > width ( ) > 0 & & line_box - > width ( ) + width ( ) > container . width ( ) ) {
2020-05-05 16:06:22 +02:00
line_box = & container . add_line_box ( ) ;
2020-06-18 21:16:29 +02:00
}
2020-05-05 16:06:22 +02:00
line_box - > add_fragment ( * this , 0 , 0 , width ( ) , height ( ) ) ;
}
2020-03-07 10:27:02 +01:00
}